private Vector3 CalcFaceNormal(SMMesh mesh, SMTriangle triangle) { var v1 = mesh.Vertices[triangle.v1]; var v2 = mesh.Vertices[triangle.v2]; var v3 = mesh.Vertices[triangle.v3]; var UX = v2.Position.X - v1.Position.X; var UY = v2.Position.Y - v1.Position.Y; var UZ = v2.Position.Z - v1.Position.Z; var VX = v3.Position.X - v1.Position.X; var VY = v3.Position.Y - v1.Position.Y; var VZ = v3.Position.Z - v1.Position.Z; var NX = UY * VZ - UZ * VY; var NY = UZ * VX - UX * VZ; var NZ = UX * VY - UY * VX; var len = Math.Sqrt(NX * NX + NY * NY + NZ * NZ); return(new Vector3 { X = (float)(NX / len), Y = (float)(NY / len), Z = (float)(NZ / len) }); }
// This REQUIRES a backing CGFX file and doesn't contain enough data to regenerate one from scratch public SimplifiedModel(CGFX cgfx) { this.cgfx = cgfx; // Models are always the first entry per CGFX standard var models = cgfx.Data.Entries[0]?.Entries; // Textures are the second var textures = cgfx.Data.Entries[1]?.Entries.Select(e => e.EntryObject).Cast <DICTObjTexture>().ToList(); if (textures != null && textures.Count > 0) { Textures = new SMTexture[textures.Count]; for (var t = 0; t < textures.Count; t++) { Bitmap textureBitmap = null; var name = textures[t].Name; var textureData = textures[t]; if (textureData != null) { var textureRGBA = TextureCodec.ConvertTextureToRGBA(new Utility(null, null, Endianness.Little), textureData.TextureCGFXData, textureData.TextureFormat, (int)textureData.Width, (int)textureData.Height); textureBitmap = new Bitmap((int)textureData.Width, (int)textureData.Height, PixelFormat.Format32bppArgb); var imgData = textureBitmap.LockBits(new Rectangle(0, 0, textureBitmap.Width, textureBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); Marshal.Copy(textureRGBA, 0, imgData.Scan0, textureRGBA.Length); textureBitmap.UnlockBits(imgData); textureBitmap.RotateFlip(RotateFlipType.RotateNoneFlipY); } var smTexture = new SMTexture(name, textureBitmap); Textures[t] = smTexture; } } if (models != null && models.Count > 0) { // This probably isn't a difficult problem to work around, but it's out of my scope at this time if (models.Count != 1) { throw new InvalidOperationException("File contains more than one model; only supporting one for now."); } var model = (DICTObjModel)models.First().EntryObject; // NOTE: Currently NOT committing the skeleton back, we're just keeping it for model software var bones = model.Skeleton?.Bones.Entries.Select(e => e.EntryObject).Cast <DICTObjBone>().ToList() ?? new List <DICTObjBone>(); Bones = bones.Select(b => new SMBone { Name = b.Name, ParentName = b.Parent?.Name, Rotation = b.Rotation, Translation = b.Translation, Scale = b.Scale, LocalTransform = b.LocalTransform }).ToArray(); Meshes = new SMMesh[model.Meshes.Length]; for (var m = 0; m < model.Meshes.Length; m++) { var mesh = model.Meshes[m]; var shape = model.Shapes[mesh.ShapeIndex]; // There might be some clever way of handling multiple vertex buffers // (if it actually happens) but I'm not worried about it. Only looking // for a single one that is VertexBufferInterleaved. var vertexBuffersInterleaved = shape.VertexBuffers.Where(vb => vb is VertexBufferInterleaved); if (vertexBuffersInterleaved.Count() != 1) { throw new InvalidOperationException("Unsupported count of VertexBuffers in VertexBufferInterleaved format"); } // Only expecting / supporting 1 SubMesh entry if (shape.SubMeshes.Count != 1) { throw new InvalidOperationException("Unsupported amount of SubMeshes"); } var subMesh = shape.SubMeshes[0]; // The BoneReferences in the SubMesh are what the vertex's local index references. var boneReferences = subMesh.BoneReferences; // These aren't "faces" in the geometrical sense, but rather a header of sorts if (subMesh.Faces.Count != 1) { throw new InvalidOperationException("Unsupported amount of Faces"); } var faceHeader = subMesh.Faces[0]; // Again, just one FaceDescriptor... if (faceHeader.FaceDescriptors.Count != 1) { throw new InvalidOperationException("Unsupported amount of FaceDescriptors"); } var faceDescriptor = faceHeader.FaceDescriptors[0]; // We're also only supporting triangles at this point; the model format probably // allows for more groups of geometry, but again, out of my scope if (faceDescriptor.PrimitiveMode != FaceDescriptor.PICAPrimitiveMode.Triangles) { throw new InvalidOperationException("Only supporting triangles format"); } // Vertices are stored (in GPU-compatible form) var vertexBuffer = (VertexBufferInterleaved)vertexBuffersInterleaved.Single(); var vertexBufferIndex = shape.VertexBuffers.IndexOf(vertexBuffer); var attributes = vertexBuffer.Attributes.Select(a => VertexBufferCodec.PICAAttribute.GetPICAAttribute(a)).ToList(); // The following are the only VertexAttributes we are supporting at this time var supportedAttributes = new List <VertexBuffer.PICAAttributeName> { VertexBuffer.PICAAttributeName.Position, VertexBuffer.PICAAttributeName.Normal, VertexBuffer.PICAAttributeName.TexCoord0, VertexBuffer.PICAAttributeName.BoneIndex, VertexBuffer.PICAAttributeName.BoneWeight, // Caution: Vertex color may not be supported by all model editors! VertexBuffer.PICAAttributeName.Color }; // Check if any unsupported attributes are in use var unsupportedAttributes = attributes.Where(a => !supportedAttributes.Contains(a.Name)).Select(a => a.Name); if (unsupportedAttributes.Any()) { throw new InvalidOperationException($"This model is using the following unsupported attributes: {string.Join(", ", unsupportedAttributes)}"); } var nativeVertices = VertexBufferCodec.GetVertices(shape, vertexBufferIndex); // Convert to the simplified vertices var boneIndexCount = GetElementsOfAttribute(attributes, VertexBuffer.PICAAttributeName.BoneIndex); // How many bone indices are actually used var boneWeightCount = GetElementsOfAttribute(attributes, VertexBuffer.PICAAttributeName.BoneWeight); // How many bone weights are actually used // FIXME? There seems to be, on occasion, a bone relationship that points to // the entire mesh but not assigned to any of the vertices. So basically the // vertices are recorded as having no bone indices but in fact are all dependent // upon associating with bone index 0 (?) This will force it to use at least // one bone index even if zero is specified for this case. if (boneReferences != null && boneReferences.Count > 0) { boneIndexCount = Math.Max(boneIndexCount, 1); } var vertices = nativeVertices.Select(v => new SMVertex { Position = new Vector3(v.Position.X, v.Position.Y, v.Position.Z), Normal = new Vector3(v.Normal.X, v.Normal.Y, v.Normal.Z), TexCoord = new Vector3(v.TexCoord0.X, v.TexCoord0.Y, v.TexCoord0.Z), BoneIndices = (new[] { v.Indices.b0, v.Indices.b1, v.Indices.b2, v.Indices.b3 }).Take(boneIndexCount).ToArray(), Weights = (new[] { v.Weights.w0, v.Weights.w1, v.Weights.w2, v.Weights.w3 }).Take(boneWeightCount).ToArray(), Color = v.Color // Caution! Not all 3D model editors may support vertex color! }).ToList(); // The vertices use relative bone indices based on the SubMesh definitions, // which we're going to make absolute now for (var v = 0; v < vertices.Count; v++) { var vertex = vertices[v]; for (var i = 0; i < vertex.BoneIndices.Length; i++) { vertex.BoneIndices[i] = boneReferences[vertex.BoneIndices[i]].Index; // Also, if no bone weights are available, assign a weight of 1 to the first bone. // This won't be stored ultimately as the PICA attributes won't specify it. if (vertex.Weights.Length == 0) { vertex.Weights = new float[] { 1.0f }; } } } // Deconstruct into triangle faces var triangles = new List <SMTriangle>(); var indices = faceDescriptor.Indices; for (var i = 0; i < indices.Count; i += 3) { triangles.Add(new SMTriangle { v1 = indices[i + 0], v2 = indices[i + 1], v3 = indices[i + 2] }); } // Finally, assign material, if available (mostly for the model editor's benefit) var material = model.ModelMaterials.Entries.Select(e => e.EntryObject).Cast <DICTObjModelMaterial>().ToList()[mesh.MaterialId]; var texture = material.TextureMappers.First().TextureReference; var name = texture.ReferenceName; var smTexture = (Textures != null) ? Textures.Where(t => t.Name == name).SingleOrDefault() : null; if (smTexture == null) { smTexture = new SMTexture(name, null); } Meshes[m] = new SMMesh(vertices, triangles, shape, vertexBufferIndex, smTexture); } } }