public void ApplyChanges() { // Models are always the first entry per CGFX standard var models = cgfx.Data.Entries[0]?.Entries; // Only one model per file for now var model = (DICTObjModel)models.First().EntryObject; // Textures are the second var textures = cgfx.Data.Entries[1]?.Entries.Select(e => e.EntryObject).Cast <DICTObjTexture>().ToList(); if (textures != null && textures.Count > 0) { for (var t = 0; t < textures.Count; t++) { if (Textures[t].TextureBitmap != null) { var textureBitmap = Textures[t].TextureBitmap; var textureData = textures[t]; if (textureData != null) { if (textureBitmap.Width != textureData.Width || textureBitmap.Height != textureData.Height) { throw new InvalidOperationException($"{textureData.Name} import: Bitmap mismatched expected dimensions -- texture data expected {textureData.Width}x{textureData.Height}, importing image is {textureBitmap.Width}x{textureBitmap.Height}"); } var imgData = textureBitmap.LockBits(new Rectangle(0, 0, (int)textureData.Width, (int)textureData.Height), ImageLockMode.ReadOnly, textureBitmap.PixelFormat); var rawRGBAData = new byte[(imgData.Height * imgData.Stride)]; Marshal.Copy(imgData.Scan0, rawRGBAData, 0, rawRGBAData.Length); textureBitmap.UnlockBits(imgData); if (textureBitmap.PixelFormat == PixelFormat.Format24bppRgb) { // Need to upconvert the data to RGBA before going in! var newData = new byte[textureBitmap.Width * textureBitmap.Height * 4]; var newDataOffset = 0; for (var oldDataOffset = 0; oldDataOffset < rawRGBAData.Length; oldDataOffset += 3) { newData[newDataOffset + 0] = rawRGBAData[oldDataOffset + 0]; newData[newDataOffset + 1] = rawRGBAData[oldDataOffset + 1]; newData[newDataOffset + 2] = rawRGBAData[oldDataOffset + 2]; newData[newDataOffset + 3] = 0xFF; newDataOffset += 4; } rawRGBAData = newData; } else if (textureBitmap.PixelFormat != PixelFormat.Format32bppArgb) { throw new InvalidOperationException($"{textureData.Name} import: Unsupported image format {textureBitmap.PixelFormat}"); } var utility = new Utility(null, null, Endianness.Little); textureData.SetTexture(utility, rawRGBAData); // TODO -- try disabling the safety checks... } } } } for (var m = 0; m < model.Meshes.Length; m++) { var mesh = model.Meshes[m]; var shape = model.Shapes[mesh.ShapeIndex]; var SMMesh = Meshes[m]; // NOTE: These are all making the same assumptions as noted in the constructor var vertexBuffer = shape.VertexBuffers.Where(vb => vb is VertexBufferInterleaved).Cast <VertexBufferInterleaved>().Single(); var subMesh = shape.SubMeshes[0]; var faceHeader = subMesh.Faces[0]; var faceDescriptor = faceHeader.FaceDescriptors[0]; // Vertices are stored (in GPU-compatible form) var vertexBufferIndex = shape.VertexBuffers.IndexOf(vertexBuffer); var attributes = vertexBuffer.Attributes.Select(a => VertexBufferCodec.PICAAttribute.GetPICAAttribute(a)).ToList(); // 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 var vertices = SMMesh.Vertices.Select(v => new VertexBufferCodec.PICAVertex { Position = new Vector4(v.Position.X, v.Position.Y, v.Position.Z, 0), Normal = new Vector4(v.Normal.X, v.Normal.Y, v.Normal.Z, 0), TexCoord0 = new Vector4(v.TexCoord.X, v.TexCoord.Y, v.TexCoord.Z, 0), Indices = GetBoneIndices(v.BoneIndices), Weights = GetBoneWeights(v.Weights), Color = v.Color }).ToArray(); // Same as when we loaded it in the first place, the bone indices need to be changed into a list var boneReferences = // Get the absolute indices SMMesh.Vertices.SelectMany(v => v.BoneIndices).Distinct().ToList() // Remap to the bones .SelectMany(smshape => model.Skeleton.Bones.Entries.Select(b => b.EntryObject).Cast <DICTObjBone>().Where(b => b.Index == smshape)) .ToList(); var boneReferenceIndexes = boneReferences.Select(b => b.Index).ToList(); // Reassign the indices so they're relative to the bone references pool for (var v = 0; v < vertices.Length; v++) { for (var bi = 0; bi < boneIndexCount; bi++) { var absoluteIndex = vertices[v].Indices[bi]; vertices[v].Indices[bi] = boneReferenceIndexes.IndexOf(absoluteIndex); } } // Convert the vertices back to native interleaved format vertexBuffer.RawBuffer = VertexBufferCodec.GetBuffer(vertices, vertexBuffer.Attributes.Select(a => VertexBufferCodec.PICAAttribute.GetPICAAttribute(a))); // Align if ending on odd byte if ((vertexBuffer.RawBuffer.Length & 1) == 1) { vertexBuffer.RawBuffer = vertexBuffer.RawBuffer.Concat(new byte[] { 0 }).ToArray(); } // Replace the bone references in the SubMesh with the generated list // NOTE: Don't do this if boneIndexCount = 0, because apparently there // is always one defined even if not explicitly used... // // NOTE 2: I handle this during the Load case but not here but just // assuming if the submesh has bone refs then the object probably does // want to use them even if this is zero; seems MAYBE that means that // vertices don't specify a bone reference if it applies to the entire // mesh. Could use some more research to verify this... // // The big issue is if the editor tries to assign vertices in this mesh // to other bones and the assignment will BE IGNORED. if (boneIndexCount > 0) { subMesh.BoneReferences.Clear(); subMesh.BoneReferences.AddRange(boneReferences); } //// Get indicies of triangle faces var indices = SMMesh.Triangles.SelectMany(t => new[] { t.v1, t.v2, t.v3 }).ToList(); faceDescriptor.Indices.Clear(); faceDescriptor.Indices.AddRange(indices); } }