public unsafe IModel LoadModel(GloModelFileBundle gloModelFileBundle) { var gloFile = gloModelFileBundle.GloFile; var textureDirectories = gloModelFileBundle.TextureDirectories; var fps = 20; var glo = gloFile.Impl.ReadNew <Glo>(Endianness.LittleEndian); var textureFilesByName = new Dictionary <string, IFileHierarchyFile>(); foreach (var textureDirectory in textureDirectories) { foreach (var textureFile in textureDirectory.Files) { textureFilesByName[textureFile.Name.ToLower()] = textureFile; } } /*new MeshCsvWriter().WriteToFile( * glo, new FinFile(Path.Join(outputDirectory.FullName, "mesh.csv"))); * new FaceCsvWriter().WriteToFile( * glo, new FinFile(Path.Join(outputDirectory.FullName, "face.csv"))); * new VertexCsvWriter().WriteToFile( * glo, new FinFile(Path.Join(outputDirectory.FullName, "vertex.csv")));*/ var finModel = new ModelImpl(); var finSkin = finModel.Skin; var finRootBone = finModel.Skeleton.Root; var finTextureMap = new LazyDictionary <string, ITexture?>( textureFilename => { if (!textureFilesByName.TryGetValue(textureFilename.ToLower(), out var textureFile)) { return(null); } using var rawTextureImage = FinImage.FromFile(textureFile.Impl); var textureImageWithAlpha = GloModelLoader.AddTransparencyToGloImage_(rawTextureImage); var finTexture = finModel.MaterialManager.CreateTexture( textureImageWithAlpha); finTexture.Name = textureFilename; if (this.mirrorTextures_.Contains(textureFilename)) { finTexture.WrapModeU = WrapMode.MIRROR_REPEAT; finTexture.WrapModeV = WrapMode.MIRROR_REPEAT; } else { finTexture.WrapModeU = WrapMode.REPEAT; finTexture.WrapModeV = WrapMode.REPEAT; } return(finTexture); }); var withCullingMap = new LazyDictionary <string, IMaterial>(textureFilename => { var finTexture = finTextureMap[textureFilename]; if (finTexture == null) { return(finModel.MaterialManager.AddStandardMaterial()); } return(finModel.MaterialManager.AddTextureMaterial(finTexture)); }); var withoutCullingMap = new LazyDictionary <string, IMaterial>( textureFilename => { var finTexture = finTextureMap[textureFilename]; IMaterial finMaterial = finTexture == null ? finModel.MaterialManager .AddStandardMaterial() : finModel.MaterialManager .AddTextureMaterial( finTexture); finMaterial.CullingMode = CullingMode.SHOW_BOTH; return(finMaterial); }); var firstMeshMap = new Dictionary <string, GloMesh>(); // TODO: Consider separating these out as separate models foreach (var gloObject in glo.Objects) { var finObjectRootBone = finRootBone.AddRoot(0, 0, 0); var meshQueue = new Queue <(GloMesh, IBone)>(); foreach (var topLevelGloMesh in gloObject.Meshes) { meshQueue.Enqueue((topLevelGloMesh, finObjectRootBone)); } List <(IAnimation, int, int)> finAndGloAnimations = new(); foreach (var animSeg in gloObject.AnimSegs) { var startFrame = (int)animSeg.StartFrame; var endFrame = (int)animSeg.EndFrame; var finAnimation = finModel.AnimationManager.AddAnimation(); finAnimation.Name = animSeg.Name; finAnimation.FrameCount = (int)(animSeg.EndFrame - animSeg.StartFrame + 1); finAnimation.FrameRate = fps * animSeg.Speed; finAndGloAnimations.Add((finAnimation, startFrame, endFrame)); } while (meshQueue.Count > 0) { var(gloMesh, parentFinBone) = meshQueue.Dequeue(); var name = gloMesh.Name; GloMesh idealMesh; if (!firstMeshMap.TryGetValue(name, out idealMesh)) { firstMeshMap[name] = idealMesh = gloMesh; } var position = gloMesh.MoveKeys[0].Xyz; var rotation = gloMesh.RotateKeys[0]; var quaternion = new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W); var xyzRadians = QuaternionUtil.ToEulerRadians(quaternion); var scale = gloMesh.ScaleKeys[0].Scale; var finBone = parentFinBone .AddChild(position.X, position.Y, position.Z) .SetLocalRotationRadians( xyzRadians.X, xyzRadians.Y, xyzRadians.Z) .SetLocalScale(scale.X, scale.Y, scale.Z); finBone.Name = name + "_bone"; var child = gloMesh.Pointers.Child; if (child != null) { meshQueue.Enqueue((child, finBone)); } var next = gloMesh.Pointers.Next; if (next != null) { meshQueue.Enqueue((next, parentFinBone)); } foreach (var(finAnimation, startFrame, endFrame) in finAndGloAnimations) { var finBoneTracks = finAnimation.AddBoneTracks(finBone); long prevTime = -1; foreach (var moveKey in gloMesh.MoveKeys) { Asserts.True(moveKey.Time > prevTime); prevTime = moveKey.Time; if (!(moveKey.Time >= startFrame && moveKey.Time <= endFrame)) { continue; } var time = (int)(moveKey.Time - startFrame); Asserts.True(time >= 0 && time < finAnimation.FrameCount); var moveValue = moveKey.Xyz; finBoneTracks.Positions.Set(time, 0, moveValue.X); finBoneTracks.Positions.Set(time, 1, moveValue.Y); finBoneTracks.Positions.Set(time, 2, moveValue.Z); } prevTime = -1; foreach (var rotateKey in gloMesh.RotateKeys) { Asserts.True(rotateKey.Time > prevTime); prevTime = rotateKey.Time; if (!(rotateKey.Time >= startFrame && rotateKey.Time <= endFrame)) { continue; } var time = (int)(rotateKey.Time - startFrame); Asserts.True(time >= 0 && time < finAnimation.FrameCount); var quaternionKey = new Quaternion(rotateKey.X, rotateKey.Y, rotateKey.Z, rotateKey.W); var xyzRadiansKey = QuaternionUtil.ToEulerRadians(quaternionKey); finBoneTracks.Rotations.Set(time, 0, xyzRadiansKey.X); finBoneTracks.Rotations.Set(time, 1, xyzRadiansKey.Y); finBoneTracks.Rotations.Set(time, 2, xyzRadiansKey.Z); } prevTime = -1; foreach (var scaleKey in gloMesh.ScaleKeys) { Asserts.True(scaleKey.Time > prevTime); prevTime = scaleKey.Time; if (!(scaleKey.Time >= startFrame && scaleKey.Time <= endFrame)) { continue; } var time = (int)(scaleKey.Time - startFrame); Asserts.True(time >= 0 && time < finAnimation.FrameCount); var scaleValue = scaleKey.Scale; finBoneTracks.Scales.Set(time, 0, scaleValue.X); finBoneTracks.Scales.Set(time, 1, scaleValue.Y); finBoneTracks.Scales.Set(time, 2, scaleValue.Z); } } // Anything with these names are debug objects and can be ignored. if (this.hiddenNames_.Contains(name)) { continue; } var finMesh = finSkin.AddMesh(); finMesh.Name = name; var gloVertices = idealMesh.Vertices; string previousTextureName = null; IMaterial?previousMaterial = null; foreach (var gloFace in idealMesh.Faces) { // TODO: What can we do if texture filename is empty? var textureFilename = gloFace.TextureFilename; var gloFaceColor = gloFace.Color; var finFaceColor = ColorImpl.FromRgbaBytes( gloFaceColor.R, gloFaceColor.G, gloFaceColor.B, gloFaceColor.A); var enableBackfaceCulling = (gloFace.Flags & 1 << 2) == 0; IMaterial?finMaterial; if (textureFilename == previousTextureName) { finMaterial = previousMaterial; } else { previousTextureName = textureFilename; finMaterial = enableBackfaceCulling ? withCullingMap[textureFilename] : withoutCullingMap[textureFilename]; previousMaterial = finMaterial; } // Face flag: // 0: potentially some kind of repeat mode?? var color = (gloFace.Flags & 1 << 6) != 0 ? ColorImpl.FromRgbaBytes(255, 0, 0, 255) : ColorImpl.FromRgbaBytes(0, 255, 0, 255); var finFaceVertices = new IVertex[3]; for (var v = 0; v < 3; ++v) { var gloVertexRef = gloFace.VertexRefs[v]; var gloVertex = gloVertices[gloVertexRef.Index]; var finVertex = finSkin .AddVertex(gloVertex.X, gloVertex.Y, gloVertex.Z) .SetUv(gloVertexRef.U, gloVertexRef.V); //.SetColor(color); finVertex.SetBoneWeights(finSkin.GetOrCreateBoneWeights( PreprojectMode.BONE, finBone)); finFaceVertices[v] = finVertex; } // TODO: Merge triangles together var finTriangles = new (IVertex, IVertex, IVertex)[1];
public IModel LoadModel(ModlModelFileBundle modelFileBundle) { var flipSign = ModlFlags.FLIP_HORIZONTALLY ? -1 : 1; var modlFile = modelFileBundle.ModlFile; using var er = new EndianBinaryReader(modlFile.Impl.OpenRead(), Endianness.LittleEndian); var bwModel = modelFileBundle.ModlType switch { ModlType.BW1 => (IModl)er.ReadNew <Bw1Modl>(), ModlType.BW2 => er.ReadNew <Bw2Modl>(), }; var model = new ModelImpl(); var finMesh = model.Skin.AddMesh(); var finBones = new IBone[bwModel.Nodes.Count]; var finBonesByModlNode = new Dictionary <IBwNode, IBone>(); var finBonesByIdentifier = new Dictionary <string, IBone>(); { var nodeQueue = new FinTuple2Queue <IBone, ushort>((model.Skeleton.Root, 0)); while (nodeQueue.TryDequeue(out var parentFinBone, out var modlNodeId)) { var modlNode = bwModel.Nodes[modlNodeId]; var transform = modlNode.Transform; var bonePosition = transform.Position; var modlRotation = transform.Rotation; var rotation = new Quaternion( flipSign * modlRotation.X, modlRotation.Y, modlRotation.Z, flipSign * modlRotation.W); var eulerRadians = QuaternionUtil.ToEulerRadians(rotation); var finBone = parentFinBone .AddChild(flipSign * bonePosition.X, bonePosition.Y, bonePosition.Z) .SetLocalRotationRadians( eulerRadians.X, eulerRadians.Y, eulerRadians.Z); var identifier = modlNode.GetIdentifier(); finBone.Name = identifier; finBones[modlNodeId] = finBone; finBonesByModlNode[modlNode] = finBone; finBonesByIdentifier[identifier] = finBone; if (bwModel.CnctParentToChildren.TryGetList( modlNodeId, out var modlChildIds)) { nodeQueue.Enqueue( modlChildIds !.Select(modlChildId => (finBone, modlChildId))); } } foreach (var animFile in modelFileBundle.AnimFiles ?? Array.Empty <IFileHierarchyFile>()) { var anim = modelFileBundle.ModlType switch { ModlType.BW1 => (IAnim)animFile.Impl.ReadNew <Bw1Anim>( Endianness.BigEndian), ModlType.BW2 => animFile.Impl.ReadNew <Bw2Anim>( Endianness.BigEndian) }; var maxFrameCount = -1; foreach (var animBone in anim.AnimBones) { maxFrameCount = (int)Math.Max(maxFrameCount, Math.Max( animBone .PositionKeyframeCount, animBone .RotationKeyframeCount)); } var finAnimation = model.AnimationManager.AddAnimation(); finAnimation.Name = animFile.NameWithoutExtension; finAnimation.FrameRate = 30; finAnimation.FrameCount = maxFrameCount; for (var b = 0; b < anim.AnimBones.Count; ++b) { var animBone = anim.AnimBones[b]; var animBoneFrames = anim.AnimBoneFrames[b]; var animNodeIdentifier = animBone.GetIdentifier(); if (!finBonesByIdentifier.TryGetValue( animNodeIdentifier, out var finBone)) { // TODO: Gross hack for the vet models, what's the real fix??? if (animNodeIdentifier == Bw1Node.GetIdentifier(33)) { finBone = finBonesByIdentifier[Bw1Node.GetIdentifier(34)]; } else if (finBonesByIdentifier.TryGetValue( animNodeIdentifier + 'X', out var xBone)) { finBone = xBone; } else if (finBonesByIdentifier.TryGetValue( "BONE_" + animNodeIdentifier, out var prefixBone)) { finBone = prefixBone; } else if (animNodeIdentifier == "WF_GRUNT_BACKPAC") { // TODO: Is this right????? finBone = finBonesByIdentifier["BONE_BCK_MISC"]; } else { ; } } var finBoneTracks = finAnimation.AddBoneTracks(finBone !); var fbtPositions = finBoneTracks.Positions; for (var f = 0; f < animBone.PositionKeyframeCount; ++f) { var(fPX, fPY, fPZ) = animBoneFrames.PositionFrames[f]; fbtPositions.Set(f, 0, flipSign * fPX); fbtPositions.Set(f, 1, fPY); fbtPositions.Set(f, 2, fPZ); } var fbtRotations = finBoneTracks.Rotations; for (var f = 0; f < animBone.RotationKeyframeCount; ++f) { var(fRX, fRY, fRZ, frW) = animBoneFrames.RotationFrames[f]; var animationQuaternion = new Quaternion(flipSign * fRX, fRY, fRZ, flipSign * frW); var eulerRadians = QuaternionUtil.ToEulerRadians(animationQuaternion); fbtRotations.Set(f, 0, eulerRadians.X); fbtRotations.Set(f, 1, eulerRadians.Y); fbtRotations.Set(f, 2, eulerRadians.Z); } } } var textureDictionary = new LazyDictionary <string, ITexture>( textureName => { var textureFile = modlFile.Parent.Files.Single( file => file.Name.ToLower() == $"{textureName}.png"); var image = FinImage.FromFile(textureFile.Impl); var finTexture = model.MaterialManager.CreateTexture(image); finTexture.Name = textureName; // TODO: Need to handle wrapping finTexture.WrapModeU = WrapMode.REPEAT; finTexture.WrapModeV = WrapMode.REPEAT; return(finTexture); }); foreach (var modlNode in bwModel.Nodes) { var finMaterials = modlNode.Materials.Select(modlMaterial => { var textureName = modlMaterial.Texture1.ToLower(); if (textureName == "") { return(null); } var finTexture = textureDictionary[textureName]; var finMaterial = model.MaterialManager .AddTextureMaterial(finTexture); return(finMaterial); }) .ToArray(); foreach (var modlMesh in modlNode.Meshes) { var finMaterial = finMaterials[modlMesh.MaterialIndex]; foreach (var triangleStrip in modlMesh.TriangleStrips) { var vertices = new IVertex[triangleStrip.VertexAttributeIndicesList.Count]; for (var i = 0; i < vertices.Length; i++) { var vertexAttributeIndices = triangleStrip.VertexAttributeIndicesList[i]; var position = modlNode.Positions[vertexAttributeIndices.PositionIndex]; var vertex = vertices[i] = model.Skin.AddVertex( flipSign * position.X * modlNode.Scale, position.Y * modlNode.Scale, position.Z * modlNode.Scale); if (vertexAttributeIndices.NormalIndex != null) { var normal = modlNode.Normals[ vertexAttributeIndices.NormalIndex.Value]; vertex.SetLocalNormal(flipSign * normal.X, normal.Y, normal.Z); } if (vertexAttributeIndices.NodeIndex != null) { var finBone = finBones[vertexAttributeIndices.NodeIndex.Value]; vertex.SetBoneWeights( model.Skin .GetOrCreateBoneWeights( PreprojectMode.NONE, new BoneWeight(finBone, null, 1))); } else { var finBone = finBonesByModlNode[modlNode]; vertex.SetBoneWeights( model.Skin.GetOrCreateBoneWeights( PreprojectMode.BONE, finBone)); } var texCoordIndex0 = vertexAttributeIndices.TexCoordIndices[0]; var texCoordIndex1 = vertexAttributeIndices.TexCoordIndices[1]; if (texCoordIndex1 != null) { int texCoordIndex; if (texCoordIndex0 != null) { texCoordIndex = (texCoordIndex0.Value << 8) | texCoordIndex1.Value; } else { texCoordIndex = texCoordIndex1.Value; } var uv = modlNode.UvMaps[0][texCoordIndex]; vertex.SetUv(uv.U, uv.V); } } var triangleStripPrimitive = finMesh.AddTriangleStrip(vertices); if (finMaterial != null) { triangleStripPrimitive.SetMaterial(finMaterial); } } } } } return(model); } }