Model SubObjects are now separate meshes and can have joints applied correctly
This commit is contained in:
parent
a35bdb8ce3
commit
7dc1912390
|
@ -261,16 +261,17 @@ public partial class Mission : Node3D
|
||||||
var pos = brush.position.ToGodotVec3();
|
var pos = brush.position.ToGodotVec3();
|
||||||
var rot = brush.angle.ToGodotVec3(false);
|
var rot = brush.angle.ToGodotVec3(false);
|
||||||
var scale = scaleProp == null ? Vector3.One : scaleProp.value.ToGodotVec3(false);
|
var scale = scaleProp == null ? Vector3.One : scaleProp.value.ToGodotVec3(false);
|
||||||
var model = Timing.TimeStage("Get Models", () =>
|
var meshDetails = Timing.TimeStage("Get Models", () => Context.Instance.ModelLoader.Load(modelName));
|
||||||
{
|
if (meshDetails.Length != 0)
|
||||||
return Context.Instance.ModelLoader.Load(modelName);
|
|
||||||
});
|
|
||||||
if (model != null)
|
|
||||||
{
|
{
|
||||||
|
var model = new Node3D();
|
||||||
model.Position = pos;
|
model.Position = pos;
|
||||||
model.RotationDegrees = rot;
|
model.RotationDegrees = rot;
|
||||||
model.Scale = scale;
|
model.Scale = scale;
|
||||||
|
|
||||||
|
// TODO: Apply real joints
|
||||||
|
var meshes = ModelLoader.TransformMeshes([45, 180, 0, 0, 0, 0], meshDetails);
|
||||||
|
|
||||||
bool GetTextReplPath(PropString prop, out string path)
|
bool GetTextReplPath(PropString prop, out string path)
|
||||||
{
|
{
|
||||||
path = "";
|
path = "";
|
||||||
|
@ -299,35 +300,40 @@ public partial class Mission : Node3D
|
||||||
}
|
}
|
||||||
|
|
||||||
var repls = new PropString[] { txtRepl0, txtRepl1, txtRepl2, txtRepl3 };
|
var repls = new PropString[] { txtRepl0, txtRepl1, txtRepl2, txtRepl3 };
|
||||||
for (var i = 0; i < 4; i++)
|
foreach (var meshInstance in meshes)
|
||||||
{
|
{
|
||||||
if (GetTextReplPath(repls[i], out var path))
|
for (var i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
var overrideMat = new StandardMaterial3D
|
if (GetTextReplPath(repls[i], out var path))
|
||||||
{
|
{
|
||||||
AlbedoTexture = TextureLoader.LoadTexture(path),
|
var overrideMat = new StandardMaterial3D
|
||||||
Transparency = BaseMaterial3D.TransparencyEnum.AlphaDepthPrePass,
|
|
||||||
};
|
|
||||||
|
|
||||||
var surfaceCount = model.Mesh.GetSurfaceCount();
|
|
||||||
for (var idx = 0; idx < surfaceCount; idx++)
|
|
||||||
{
|
|
||||||
var surfaceMat = model.Mesh.SurfaceGetMaterial(idx);
|
|
||||||
if (surfaceMat.HasMeta($"TxtRepl{i}"))
|
|
||||||
{
|
{
|
||||||
model.SetSurfaceOverrideMaterial(idx, overrideMat);
|
AlbedoTexture = TextureLoader.LoadTexture(path),
|
||||||
|
Transparency = BaseMaterial3D.TransparencyEnum.AlphaDepthPrePass,
|
||||||
|
};
|
||||||
|
|
||||||
|
var surfaceCount = meshInstance.Mesh.GetSurfaceCount();
|
||||||
|
for (var idx = 0; idx < surfaceCount; idx++)
|
||||||
|
{
|
||||||
|
var surfaceMat = meshInstance.Mesh.SurfaceGetMaterial(idx);
|
||||||
|
if (surfaceMat.HasMeta($"TxtRepl{i}"))
|
||||||
|
{
|
||||||
|
meshInstance.SetSurfaceOverrideMaterial(idx, overrideMat);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (renderAlpha != null)
|
if (renderAlpha != null)
|
||||||
{
|
{
|
||||||
model.Transparency = 1.0f - renderAlpha.value;
|
meshInstance.Transparency = 1.0f - renderAlpha.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
model.AddChild(meshInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
model.AddToGroup(OBJECT_MODELS_GROUP);
|
model.AddToGroup(OBJECT_MODELS_GROUP);
|
||||||
|
|
||||||
AddChild(model);
|
AddChild(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,13 @@ public partial class Model : Node3D
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.Instance.SetCampaign(campaignName);
|
Context.Instance.SetCampaign(campaignName);
|
||||||
var model = Context.Instance.ModelLoader.Load(modelPath);
|
var model = new Node3D();
|
||||||
|
var meshDetails = Context.Instance.ModelLoader.Load(modelPath);
|
||||||
|
var meshes = ModelLoader.TransformMeshes([0, 0, 0, 0, 0, 0], meshDetails);
|
||||||
|
foreach (var meshInstance in meshes)
|
||||||
|
{
|
||||||
|
model.AddChild(meshInstance);
|
||||||
|
}
|
||||||
AddChild(model);
|
AddChild(model);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,82 +2,83 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Godot;
|
using Godot;
|
||||||
using KeepersCompound.LGS;
|
using KeepersCompound.LGS;
|
||||||
using Quaternion = System.Numerics.Quaternion;
|
|
||||||
|
|
||||||
namespace KeepersCompound.TMV;
|
namespace KeepersCompound.TMV;
|
||||||
|
|
||||||
// TODO: Work out a way to share base game models again in the cache
|
// TODO: Work out a way to share base game models again in the cache
|
||||||
public class ModelLoader
|
public class ModelLoader
|
||||||
{
|
{
|
||||||
private readonly Dictionary<(string, string), MeshInstance3D> _cache = new();
|
public struct MeshDetails(int jointIdx, Transform3D transform, MeshInstance3D mesh)
|
||||||
|
{
|
||||||
|
public readonly int JointIdx = jointIdx;
|
||||||
|
public readonly Transform3D Transform = transform;
|
||||||
|
public readonly MeshInstance3D Mesh = mesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<(string, string), MeshDetails[]> _cache = new();
|
||||||
|
|
||||||
public MeshInstance3D Load(string modelName, bool forceLoad = false)
|
public MeshDetails[] Load(string modelName, bool forceLoad = false)
|
||||||
{
|
{
|
||||||
var campaignResources = Context.Instance.CampaignResources;
|
var campaignResources = Context.Instance.CampaignResources;
|
||||||
var campaignName = campaignResources.name;
|
var campaignName = campaignResources.name;
|
||||||
campaignName ??= "";
|
campaignName ??= "";
|
||||||
|
|
||||||
if (!forceLoad)
|
if (!forceLoad)
|
||||||
{
|
{
|
||||||
if (_cache.TryGetValue((campaignName, modelName), out var fmModel))
|
if (_cache.TryGetValue((campaignName, modelName), out var fmDetails))
|
||||||
{
|
{
|
||||||
return fmModel?.Duplicate() as MeshInstance3D;
|
return fmDetails;
|
||||||
}
|
}
|
||||||
else if (_cache.TryGetValue(("", modelName), out var omModel))
|
if (_cache.TryGetValue(("", modelName), out var omDetails))
|
||||||
{
|
{
|
||||||
return omModel?.Duplicate() as MeshInstance3D;
|
return omDetails;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't care if this is null actually, we'll still cache that it's null lol
|
var details = Timing.TimeStage("Load Models", () => LoadModel(modelName));
|
||||||
var model = Timing.TimeStage("Load Models", () => { return LoadModel(modelName); });
|
_cache[(campaignName, modelName)] = details;
|
||||||
_cache[(campaignName, modelName)] = model;
|
return details;
|
||||||
return model?.Duplicate() as MeshInstance3D;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MeshInstance3D LoadModel(string modelName)
|
public static MeshInstance3D[] TransformMeshes(float[] joints, MeshDetails[] meshDetails)
|
||||||
|
{
|
||||||
|
var meshes = new List<MeshInstance3D>();
|
||||||
|
foreach (var details in meshDetails)
|
||||||
|
{
|
||||||
|
var mesh = details.Mesh.Duplicate() as MeshInstance3D;
|
||||||
|
if (details.JointIdx != -1)
|
||||||
|
{
|
||||||
|
var ang = float.DegreesToRadians(joints[details.JointIdx]);
|
||||||
|
var r1 = new Quaternion(new Vector3(0, 0, 1), ang);
|
||||||
|
var r2 = details.Transform.Basis.GetRotationQuaternion();
|
||||||
|
var basis = new Basis(r2 * r1);
|
||||||
|
mesh.SetTransform(new Transform3D(basis, details.Transform.Origin));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mesh.SetTransform(details.Transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
meshes.Add(mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [..meshes];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MeshDetails[] LoadModel(string modelName)
|
||||||
{
|
{
|
||||||
var campaignResources = Context.Instance.CampaignResources;
|
var campaignResources = Context.Instance.CampaignResources;
|
||||||
var modelPath = campaignResources.GetResourcePath(ResourceType.Object, modelName);
|
var modelPath = campaignResources.GetResourcePath(ResourceType.Object, modelName);
|
||||||
if (modelPath == null)
|
if (modelPath == null)
|
||||||
{
|
{
|
||||||
return null;
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
var modelFile = new ModelFile(modelPath);
|
var modelFile = new ModelFile(modelPath);
|
||||||
if (modelFile == null)
|
if (modelFile == null)
|
||||||
{
|
{
|
||||||
GD.Print($"Failed to load model file: {modelPath}");
|
GD.Print($"Failed to load model file: {modelPath}");
|
||||||
return null;
|
return [];
|
||||||
}
|
|
||||||
|
|
||||||
// Transform subobjs
|
|
||||||
// TODO: Traverse children properly
|
|
||||||
// TODO: Apply to normals
|
|
||||||
// TODO: Apply joints(??)
|
|
||||||
// TODO: Handle Slide joints
|
|
||||||
// !HACK: Hardcoded joint :))
|
|
||||||
var ang = float.DegreesToRadians(45);
|
|
||||||
foreach (var subObj in modelFile.Objects)
|
|
||||||
{
|
|
||||||
if (subObj.Type == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var translation = subObj.Transform.Translation;
|
|
||||||
var rotation = subObj.Transform;
|
|
||||||
rotation.Translation = System.Numerics.Vector3.Zero;
|
|
||||||
var jointRotation = Quaternion.CreateFromYawPitchRoll(0, ang, 0);
|
|
||||||
|
|
||||||
for (var i = subObj.PointIdx; i < subObj.PointIdx + subObj.PointCount; i++)
|
|
||||||
{
|
|
||||||
var v = modelFile.Vertices[i];
|
|
||||||
v = System.Numerics.Vector3.Transform(v, jointRotation);
|
|
||||||
v = System.Numerics.Vector3.Transform(v, rotation);
|
|
||||||
v += translation;
|
|
||||||
modelFile.Vertices[i] = v;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var materials = new List<StandardMaterial3D>();
|
var materials = new List<StandardMaterial3D>();
|
||||||
|
@ -124,54 +125,70 @@ public class ModelLoader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var surfaceDataMap = new Dictionary<int, MeshSurfaceData>();
|
var objCount = modelFile.Objects.Length;
|
||||||
foreach (var poly in modelFile.Polygons)
|
var meshDetails = new MeshDetails[objCount];
|
||||||
|
for (var i = 0; i < objCount; i++)
|
||||||
{
|
{
|
||||||
var vertices = new List<Vector3>();
|
var subObj = modelFile.Objects[i];
|
||||||
var normal = modelFile.Normals[poly.Normal].ToGodotVec3();
|
var jointIdx = subObj.Joint;
|
||||||
var uvs = new List<Vector2>();
|
var transform = subObj.Type == 0 ? Transform3D.Identity : subObj.Transform.ToGodotTransform3D();
|
||||||
for (var i = 0; i < poly.VertexCount; i++)
|
var surfaceDataMap = new Dictionary<int, MeshSurfaceData>();
|
||||||
|
foreach (var poly in modelFile.Polygons)
|
||||||
{
|
{
|
||||||
var vertex = modelFile.Vertices[poly.VertexIndices[i]];
|
var v0 = poly.VertexIndices[0];
|
||||||
vertices.Add(vertex.ToGodotVec3());
|
if (v0 < subObj.PointIdx || v0 >= subObj.PointIdx + subObj.PointCount)
|
||||||
if (i < poly.UvIndices.Length)
|
|
||||||
{
|
{
|
||||||
var uv = modelFile.Uvs[poly.UvIndices[i]];
|
continue;
|
||||||
uvs.Add(new Vector2(uv.X, uv.Y));
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
var vertices = new List<Vector3>();
|
||||||
|
var normal = modelFile.Normals[poly.Normal].ToGodotVec3();
|
||||||
|
var uvs = new List<Vector2>();
|
||||||
|
for (var j = 0; j < poly.VertexCount; j++)
|
||||||
{
|
{
|
||||||
uvs.Add(Vector2.Zero);
|
var vertex = modelFile.Vertices[poly.VertexIndices[j]];
|
||||||
|
vertices.Add(vertex.ToGodotVec3());
|
||||||
|
if (j < poly.UvIndices.Length)
|
||||||
|
{
|
||||||
|
var uv = modelFile.Uvs[poly.UvIndices[j]];
|
||||||
|
uvs.Add(new Vector2(uv.X, uv.Y));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uvs.Add(Vector2.Zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!surfaceDataMap.ContainsKey(poly.Data))
|
||||||
|
{
|
||||||
|
surfaceDataMap.Add(poly.Data, new MeshSurfaceData());
|
||||||
|
}
|
||||||
|
|
||||||
|
surfaceDataMap[poly.Data].AddPolygon(vertices, normal, uvs, uvs);
|
||||||
|
}
|
||||||
|
|
||||||
|
var mesh = new ArrayMesh();
|
||||||
|
foreach (var (materialId, surfaceData) in surfaceDataMap)
|
||||||
|
{
|
||||||
|
var array = surfaceData.BuildSurfaceArray();
|
||||||
|
var surfaceIdx = mesh.GetSurfaceCount();
|
||||||
|
mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, array);
|
||||||
|
for (var j = 0; j < materials.Count; j++)
|
||||||
|
{
|
||||||
|
var m = modelFile.Materials[j];
|
||||||
|
if (m.Slot == materialId)
|
||||||
|
{
|
||||||
|
mesh.SurfaceSetMaterial(surfaceIdx, materials[j]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!surfaceDataMap.ContainsKey(poly.Data))
|
var pos = -modelFile.Header.Center.ToGodotVec3();
|
||||||
{
|
var meshInstance = new MeshInstance3D { Mesh = mesh, Position = pos };
|
||||||
surfaceDataMap.Add(poly.Data, new MeshSurfaceData());
|
meshDetails[i] = new MeshDetails(jointIdx, transform, meshInstance);
|
||||||
}
|
|
||||||
|
|
||||||
surfaceDataMap[poly.Data].AddPolygon(vertices, normal, uvs, uvs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var mesh = new ArrayMesh();
|
return meshDetails;
|
||||||
foreach (var (materialId, surfaceData) in surfaceDataMap)
|
|
||||||
{
|
|
||||||
var array = surfaceData.BuildSurfaceArray();
|
|
||||||
var surfaceIdx = mesh.GetSurfaceCount();
|
|
||||||
mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, array);
|
|
||||||
for (var i = 0; i < materials.Count; i++)
|
|
||||||
{
|
|
||||||
var m = modelFile.Materials[i];
|
|
||||||
if (m.Slot == materialId)
|
|
||||||
{
|
|
||||||
mesh.SurfaceSetMaterial(surfaceIdx, materials[i]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var pos = -modelFile.Header.Center.ToGodotVec3();
|
|
||||||
var meshInstance = new MeshInstance3D { Mesh = mesh, Position = pos };
|
|
||||||
return meshInstance;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue