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 rot = brush.angle.ToGodotVec3(false); | ||||
| 			var scale = scaleProp == null ? Vector3.One : scaleProp.value.ToGodotVec3(false); | ||||
| 			var model = Timing.TimeStage("Get Models", () => | ||||
| 			{ | ||||
| 				return Context.Instance.ModelLoader.Load(modelName); | ||||
| 			}); | ||||
| 			if (model != null) | ||||
| 			var meshDetails = Timing.TimeStage("Get Models", () => Context.Instance.ModelLoader.Load(modelName)); | ||||
| 			if (meshDetails.Length != 0) | ||||
| 			{ | ||||
| 				var model = new Node3D(); | ||||
| 				model.Position = pos; | ||||
| 				model.RotationDegrees = rot; | ||||
| 				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) | ||||
| 				{ | ||||
| 					path = ""; | ||||
|  | @ -299,31 +300,36 @@ public partial class Mission : Node3D | |||
| 				} | ||||
| 
 | ||||
| 				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), | ||||
| 							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}")) | ||||
| 							var overrideMat = new StandardMaterial3D | ||||
| 							{ | ||||
| 								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) | ||||
| 				{ | ||||
| 					model.Transparency = 1.0f - renderAlpha.value; | ||||
| 					if (renderAlpha != null) | ||||
| 					{ | ||||
| 						meshInstance.Transparency = 1.0f - renderAlpha.value; | ||||
| 					} | ||||
| 					 | ||||
| 					model.AddChild(meshInstance); | ||||
| 				} | ||||
| 				 | ||||
| 				model.AddToGroup(OBJECT_MODELS_GROUP); | ||||
|  |  | |||
|  | @ -19,7 +19,13 @@ public partial class Model : Node3D | |||
|         } | ||||
| 
 | ||||
|         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); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -2,16 +2,22 @@ using System.Collections.Generic; | |||
| using System.IO; | ||||
| using Godot; | ||||
| using KeepersCompound.LGS; | ||||
| using Quaternion = System.Numerics.Quaternion; | ||||
| 
 | ||||
| namespace KeepersCompound.TMV; | ||||
| 
 | ||||
| // TODO: Work out a way to share base game models again in the cache | ||||
| 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; | ||||
|     } | ||||
|      | ||||
|     public MeshInstance3D Load(string modelName, bool forceLoad = false) | ||||
|     private readonly Dictionary<(string, string), MeshDetails[]> _cache = new(); | ||||
| 
 | ||||
|     public MeshDetails[] Load(string modelName, bool forceLoad = false) | ||||
|     { | ||||
|         var campaignResources = Context.Instance.CampaignResources; | ||||
|         var campaignName = campaignResources.name; | ||||
|  | @ -19,65 +25,60 @@ public class ModelLoader | |||
|          | ||||
|         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 model = Timing.TimeStage("Load Models", () => { return LoadModel(modelName); }); | ||||
|         _cache[(campaignName, modelName)] = model; | ||||
|         return model?.Duplicate() as MeshInstance3D; | ||||
|         var details = Timing.TimeStage("Load Models", () => LoadModel(modelName)); | ||||
|         _cache[(campaignName, modelName)] = details; | ||||
|         return details; | ||||
|     } | ||||
| 
 | ||||
|     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 modelPath = campaignResources.GetResourcePath(ResourceType.Object, modelName); | ||||
|         if (modelPath == null) | ||||
|         { | ||||
|             return null; | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         var modelFile = new ModelFile(modelPath); | ||||
|         if (modelFile == null) | ||||
|         { | ||||
|             GD.Print($"Failed to load model file: {modelPath}"); | ||||
|             return null; | ||||
|         } | ||||
|          | ||||
|         // 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; | ||||
|             } | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         var materials = new List<StandardMaterial3D>(); | ||||
|  | @ -124,54 +125,70 @@ public class ModelLoader | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         var surfaceDataMap = new Dictionary<int, MeshSurfaceData>(); | ||||
|         foreach (var poly in modelFile.Polygons) | ||||
|         var objCount = modelFile.Objects.Length; | ||||
|         var meshDetails = new MeshDetails[objCount]; | ||||
|         for (var i = 0; i < objCount; i++) | ||||
|         { | ||||
|             var vertices = new List<Vector3>(); | ||||
|             var normal = modelFile.Normals[poly.Normal].ToGodotVec3(); | ||||
|             var uvs = new List<Vector2>(); | ||||
|             for (var i = 0; i < poly.VertexCount; i++) | ||||
|             var subObj = modelFile.Objects[i]; | ||||
|             var jointIdx = subObj.Joint; | ||||
|             var transform = subObj.Type == 0 ? Transform3D.Identity : subObj.Transform.ToGodotTransform3D(); | ||||
|             var surfaceDataMap = new Dictionary<int, MeshSurfaceData>(); | ||||
|             foreach (var poly in modelFile.Polygons) | ||||
|             { | ||||
|                 var vertex = modelFile.Vertices[poly.VertexIndices[i]]; | ||||
|                 vertices.Add(vertex.ToGodotVec3()); | ||||
|                 if (i < poly.UvIndices.Length) | ||||
|                 var v0 = poly.VertexIndices[0]; | ||||
|                 if (v0 < subObj.PointIdx || v0 >= subObj.PointIdx + subObj.PointCount) | ||||
|                 { | ||||
|                     var uv = modelFile.Uvs[poly.UvIndices[i]]; | ||||
|                     uvs.Add(new Vector2(uv.X, uv.Y)); | ||||
|                     continue; | ||||
|                 } | ||||
|                 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)) | ||||
|             { | ||||
|                 surfaceDataMap.Add(poly.Data, new MeshSurfaceData()); | ||||
|             } | ||||
| 
 | ||||
|             surfaceDataMap[poly.Data].AddPolygon(vertices, normal, uvs, uvs); | ||||
|             var pos = -modelFile.Header.Center.ToGodotVec3(); | ||||
|             var meshInstance = new MeshInstance3D { Mesh = mesh, Position = pos }; | ||||
|             meshDetails[i] = new MeshDetails(jointIdx, transform, meshInstance); | ||||
|         } | ||||
| 
 | ||||
|         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 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; | ||||
|         return meshDetails; | ||||
|     } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue