public override void WriteModel(bool save = true) { int b = 0; GXDisplayListPacker dlpacker = new GXDisplayListPacker(); ModelBase.BoneDefRoot boneTree = m_Model.m_BoneTree; Dictionary<string, ModelBase.MaterialDef> materials = m_Model.m_Materials; Dictionary<string, ModelBase.TextureDefBase> textures = m_Model.m_Textures; NitroFile bmd = m_ModelFile; bmd.Clear(); // Vertices mustn't be written in their transformed state, otherwise they'll have their transformations applied a second // time. We apply the inverse transformation so that when the transformation is applied they appear correctly. m_Model.ApplyInverseTransformations(); float largest = 0f; foreach (ModelBase.BoneDef bone in boneTree) { foreach (ModelBase.GeometryDef geometry in bone.m_Geometries.Values) { foreach (ModelBase.PolyListDef polyList in geometry.m_PolyLists.Values) { foreach (ModelBase.FaceListDef faceList in polyList.m_FaceLists) { foreach (ModelBase.FaceDef face in faceList.m_Faces) { foreach (ModelBase.VertexDef vert in face.m_Vertices) { Vector3 vtx = vert.m_Position; if (vtx.X > largest) largest = vtx.X; if (vtx.Y > largest) largest = vtx.Y; if (vtx.Z > largest) largest = vtx.Z; if (-vtx.X > largest) largest = -vtx.X; if (-vtx.Y > largest) largest = -vtx.Y; if (-vtx.Z > largest) largest = -vtx.Z; } } } } } } float scaleModel = 1f; uint scaleval = 0; while (largest > (32767f / 4096f)) { scaleval++; scaleModel /= 2f; largest /= 2f; } if (scaleval > 31) { MessageBox.Show("Your modelFile is too large to be imported. Try scaling it down.", Program.AppTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); return; } foreach (ModelBase.BoneDef bone in boneTree) { foreach (ModelBase.GeometryDef geometry in bone.m_Geometries.Values) { foreach (ModelBase.PolyListDef polyList in geometry.m_PolyLists.Values) { foreach (ModelBase.FaceListDef faceList in polyList.m_FaceLists) { foreach (ModelBase.FaceDef face in faceList.m_Faces) { for (int vert = 0; vert < face.m_Vertices.Length; vert++) { face.m_Vertices[vert].m_Position.X *= scaleModel; face.m_Vertices[vert].m_Position.Y *= scaleModel; face.m_Vertices[vert].m_Position.Z *= scaleModel; } } } } } } Dictionary<string, ConvertedTexture> convertedTextures = new Dictionary<string, ConvertedTexture>(); uint ntex = 0, npal = 0; int texsize = 0; foreach (KeyValuePair<string, ModelBase.MaterialDef> _mat in materials) { ModelBase.MaterialDef mat = _mat.Value; string matname = _mat.Key; if (mat.m_TextureDefID != null) { ModelBase.TextureDefBase texture = m_Model.m_Textures[mat.m_TextureDefID]; if (!convertedTextures.ContainsKey(texture.m_ID)) { ConvertedTexture tex = ConvertTexture(texture); tex.m_TextureID = ntex; tex.m_PaletteID = npal; if (tex.m_TextureData != null) { ntex++; texsize += tex.m_TextureData.Length; } if (tex.m_PaletteData != null) { npal++; texsize += tex.m_PaletteData.Length; } convertedTextures.Add(texture.m_ID, tex); } } } if (texsize >= 49152) { if (MessageBox.Show("Your textures would occupy more than 48k of VRAM.\nThis could cause glitches or freezes.\n\nImport anyway?", Program.AppTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.No) return; } bmd.Write32(0x00, scaleval); uint curoffset = 0x3C; bmd.Write32(0x0C, (uint)materials.Count); bmd.Write32(0x10, curoffset); uint dllistoffset = curoffset; curoffset += (uint)(materials.Count * 8); int lastColourARGB = -1; // build display lists b = 0; foreach (ModelBase.MaterialDef mat in materials.Values) { bmd.Write32(dllistoffset, 1); bmd.Write32(dllistoffset + 0x4, curoffset); dllistoffset += 0x8; Vector2 tcscale = (mat.m_TextureDefID != null) ? new Vector2(textures[mat.m_TextureDefID].GetWidth(), textures[mat.m_TextureDefID].GetHeight()) : Vector2.Zero; string curmaterial = mat.m_ID; // For each bone, for each geometry, for each polylist whose material matches the // current material, for each face, add it to the current display list float largesttc = 0f; foreach (ModelBase.BoneDef bone in boneTree) { foreach (ModelBase.GeometryDef geometry in bone.m_Geometries.Values) { foreach (ModelBase.PolyListDef polyList in geometry.m_PolyLists.Values) { if (!polyList.m_MaterialName.Equals(curmaterial)) continue; foreach (ModelBase.FaceListDef faceList in polyList.m_FaceLists) { foreach (ModelBase.FaceDef face in faceList.m_Faces) { foreach (ModelBase.VertexDef vert in face.m_Vertices) { Vector2? txc = vert.m_TextureCoordinate; if (txc == null) continue; Vector2 scaledTxc = Vector2.Multiply((Vector2)txc, tcscale); if (Math.Abs(scaledTxc.X) > largesttc) largesttc = Math.Abs(scaledTxc.X); if (Math.Abs(scaledTxc.Y) > largesttc) largesttc = Math.Abs(scaledTxc.Y); } } } } } } float _tcscale = largesttc / (32767f / 16f); if (_tcscale > 1f) { _tcscale = (float)Math.Ceiling(_tcscale * 4096f) / 4096f; mat.m_TextureScale = new Vector2(_tcscale, _tcscale); Vector2.Divide(ref tcscale, _tcscale, out tcscale); } else mat.m_TextureScale = Vector2.Zero; dlpacker.ClearCommands(); int lastface = -1; int lastmatrix = -1; Vector4 lastvtx = new Vector4(0f, 0f, 0f, 12345678f); Vector3 lastnrm = Vector3.Zero; foreach (ModelBase.BoneDef bone in boneTree) { foreach (ModelBase.GeometryDef geometry in bone.m_Geometries.Values) { foreach (ModelBase.PolyListDef polyList in geometry.m_PolyLists.Values) { if (!polyList.m_MaterialName.Equals(curmaterial)) continue; foreach (ModelBase.FaceListDef faceList in polyList.m_FaceLists) { if (faceList.m_Type.Equals(ModelBase.PolyListType.TriangleStrip)) { dlpacker.AddCommand(0x40, (uint)VertexListPrimitiveTypes.TriangleStrip);// Begin Vertex List AddTriangleStripToDisplayList(dlpacker, ref lastColourARGB, ref tcscale, ref lastmatrix, ref lastvtx, ref lastnrm, faceList.m_Faces); dlpacker.AddCommand(0x41);//End Vertex List } else { foreach (ModelBase.FaceDef face in faceList.m_Faces) { int nvtx = face.m_NumVertices; if (nvtx != lastface || lastface > 4) { uint vtxtype = 0; switch (nvtx) { case 1: case 2: case 3: vtxtype = (uint)VertexListPrimitiveTypes.SeparateTriangles; break; case 4: vtxtype = (uint)VertexListPrimitiveTypes.SeparateQuadrilaterals; break; default: vtxtype = (uint)VertexListPrimitiveTypes.TriangleStrip; break; } if (lastface != -1) dlpacker.AddCommand(0x41);// End Vertex List dlpacker.AddCommand(0x40, vtxtype);// Begin Vertex List lastface = nvtx; } switch (nvtx) { case 1: // point AddSinglePointToDisplayList(dlpacker, ref lastColourARGB, ref tcscale, ref lastmatrix, ref lastvtx, ref lastnrm, face); break; case 2: // line AddLineToDisplayList(dlpacker, ref lastColourARGB, ref tcscale, ref lastmatrix, ref lastvtx, ref lastnrm, face); break; case 3: // triangle AddTriangleToDisplayList(dlpacker, ref lastColourARGB, ref tcscale, ref lastmatrix, ref lastvtx, ref lastnrm, face); break; case 4: // quad AddQuadrilateralToDisplayList(dlpacker, ref lastColourARGB, ref tcscale, ref lastmatrix, ref lastvtx, ref lastnrm, face); break; default: // whatever (import as triangle strip) // todo break; } } dlpacker.AddCommand(0x41); lastface = -1; } } } } } byte[] dlist = dlpacker.GetDisplayList(); // Display list header uint dllist_data_offset = (uint)(((curoffset + 0x10 + (boneTree.Count)) + 3) & ~3); bmd.Write32(curoffset, (uint)m_Model.m_BoneTransformsMap.Count);// Number of transforms bmd.Write32(curoffset + 0x4, curoffset + 0x10);// Offset to transforms list bmd.Write32(curoffset + 0x8, (uint)dlist.Length);// Size of the display list data in bytes bmd.Write32(curoffset + 0xC, dllist_data_offset);// Offset to the display list data, make room for transforms list curoffset += 0x10; /* The transforms list is a series of bytes. * Every time a Matrix restore (0x14) command is issued by the display list, the command's parameter (the matrix ID) * is used as an index into the transforms list. The ID obtained from the transforms list is then used as an index into * the transform/bone map (series of shorts whose offset is defined in the file header, at 0x2C). The ID finally obtained * is the ID of the bone whose transform matrix will be used to transform oncoming geometry. */ for (int j = 0; j < m_Model.m_BoneTransformsMap.Count; j++) { bmd.Write8(curoffset, (byte)j); curoffset += 0x1; } curoffset = dllist_data_offset; bmd.WriteBlock(curoffset, dlist); curoffset += (uint)dlist.Length; b++; } bmd.Write32(0x2C, curoffset); // transform / bone map // Eg. Bone 26 may be mapped to matrix 6. If a Matrix Restore command is issued with Matrix ID of 6, // it'll be used as an index into this list and should return bone ID 26. foreach (string key in m_Model.m_BoneTransformsMap.GetFirstToSecond().Keys) { bmd.Write16(curoffset, (ushort)m_Model.m_BoneTree.GetBoneIndex(key)); curoffset += 2; } curoffset = (uint)((curoffset + 3) & ~3); // build bones bmd.Write32(0x4, (uint)boneTree.Count);// Number of bones bmd.Write32(0x8, curoffset); uint bextraoffset = (uint)(curoffset + (0x40 * boneTree.Count)); int boneID = 0; foreach (ModelBase.BoneDef bone in boneTree) { bmd.Write32(curoffset + 0x00, (uint)boneID); // bone ID bmd.Write16(curoffset + 0x08, (ushort)boneTree.GetParentOffset(bone));// Offset in bones to parent bone (signed 16-bit. 0=no parent, -1=parent is the previous bone, ...) bmd.Write16(curoffset + 0x0A, (bone.m_HasChildren) ? (ushort)1 : (ushort)0);// 1 if the bone has children, 0 otherwise bmd.Write32(curoffset + 0x0C, (uint)boneTree.GetNextSiblingOffset(bone));// Offset in bones to the next sibling bone (0=bone is last child of its parent) bmd.Write32(curoffset + 0x10, (uint)bone.m_20_12Scale[0]);// X scale (32-bit signed, 20:12 fixed point. Think GX command 0x1B) bmd.Write32(curoffset + 0x14, (uint)bone.m_20_12Scale[1]);// Y scale bmd.Write32(curoffset + 0x18, (uint)bone.m_20_12Scale[2]);// Z scale bmd.Write16(curoffset + 0x1C, (ushort)bone.m_4_12Rotation[0]);// X rotation (16-bit signed, 0x0400 = 90°) bmd.Write16(curoffset + 0x1E, (ushort)bone.m_4_12Rotation[1]);// Y rotation bmd.Write16(curoffset + 0x20, (ushort)bone.m_4_12Rotation[2]);// Z rotation bmd.Write16(curoffset + 0x22, 0);// Zero (padding) bmd.Write32(curoffset + 0x24, (uint)bone.m_20_12Translation[0]);// X translation (32-bit signed, 20:12 fixed point. Think GX command 0x1C) bmd.Write32(curoffset + 0x28, (uint)bone.m_20_12Translation[1]);// Y translation bmd.Write32(curoffset + 0x2C, (uint)bone.m_20_12Translation[2]);// Z translation bmd.Write32(curoffset + 0x30, (uint)bone.m_MaterialsInBranch.Count);// Number of displaylist/material pairs bmd.Write32(curoffset + 0x3C, (bone.m_Billboard) ? (uint)1 : (uint)0);// Bit0: bone is rendered facing the camera (billboard); Bit2: ??? bmd.Write32(curoffset + 0x34, bextraoffset);// Material IDs list for (byte j = 0; j < bone.m_MaterialsInBranch.Count; j++) { bmd.Write8(bextraoffset, (byte)materials[bone.m_MaterialsInBranch[j]].m_Index); bextraoffset++; } bmd.Write32(curoffset + 0x38, bextraoffset);// Displaylist IDs list for (byte j = 0; j < bone.m_MaterialsInBranch.Count; j++) { bmd.Write8(bextraoffset, (byte)materials[bone.m_MaterialsInBranch[j]].m_Index); bextraoffset++; } bmd.Write32(curoffset + 0x04, bextraoffset);// Bone names (null-terminated ASCII string) bmd.WriteString(bextraoffset, bone.m_ID, 0); bextraoffset += (uint)(bone.m_ID.Length + 1); boneID++; curoffset += 0x40; } curoffset = (uint)((bextraoffset + 3) & ~3); // build materials bmd.Write32(0x24, (uint)materials.Count); bmd.Write32(0x28, curoffset); uint mextraoffset = (uint)(curoffset + (0x30 * materials.Count)); foreach (KeyValuePair<string, ModelBase.MaterialDef> _mat in materials) { ModelBase.MaterialDef mat = _mat.Value; string matname = _mat.Key; uint texid = 0xFFFFFFFF, palid = 0xFFFFFFFF; uint teximage_param = 0x00000000; uint texscaleS = 0x00001000; uint texscaleT = 0x00001000; if (mat.m_TextureDefID != null && convertedTextures.ContainsKey(mat.m_TextureDefID)) { ConvertedTexture tex = convertedTextures[mat.m_TextureDefID]; texid = tex.m_TextureID; palid = (tex.m_PaletteData != null) ? tex.m_PaletteID : 0xFFFFFFFF; // 16 Repeat in S Direction (0=Clamp Texture, 1=Repeat Texture) // 17 Repeat in T Direction (0=Clamp Texture, 1=Repeat Texture) // 18 Flip in S Direction (0=No, 1=Flip each 2nd Texture) (requires Repeat) // 19 Flip in T Direction (0=No, 1=Flip each 2nd Texture) (requires Repeat) if (mat.m_TexTiling[0] == ModelBase.MaterialDef.TexTiling.Repeat) teximage_param |= 0x00010000; else if (mat.m_TexTiling[0] == ModelBase.MaterialDef.TexTiling.Flip) teximage_param |= 0x00040000; if (mat.m_TexTiling[1] == ModelBase.MaterialDef.TexTiling.Repeat) teximage_param |= 0x00030000; else if (mat.m_TexTiling[1] == ModelBase.MaterialDef.TexTiling.Flip) teximage_param |= 0x00080000; if (mat.m_TextureScale.X > 0f && mat.m_TextureScale.Y > 0f) { teximage_param |= 0x40000000; texscaleS = (uint)(int)(mat.m_TextureScale.X * 4096); texscaleT = (uint)(int)(mat.m_TextureScale.Y * 4096); } // 30-31 Texture Coordinates Transformation Mode (0..3, see below) // Texture Coordinates Transformation Modes: // 0 Do not Transform texture coordinates // 1 TexCoord source // 2 Normal source // 3 Vertex source teximage_param |= (uint)(((byte)mat.m_TexGenMode) << 30); } // Cmd 29h POLYGON_ATTR - Set Polygon Attributes (W) uint polyattr = 0x00000000; // 0-3 Light 0..3 Enable Flags (each bit: 0=Disable, 1=Enable) for (int i = 0; i < 4; i++) { if (mat.m_Lights[i] == true) polyattr |= (uint)(0x01 << i); } // 4-5 Polygon Mode (0=Modulation,1=Decal,2=Toon/Highlight Shading,3=Shadow) switch (mat.m_PolygonMode) { case ModelBase.MaterialDef.PolygonMode.Decal: polyattr |= 0x10; break; case ModelBase.MaterialDef.PolygonMode.Toon_HighlightShading: polyattr |= 0x20; break; case ModelBase.MaterialDef.PolygonMode.Shadow: polyattr |= 0x30; break; } // 6 Polygon Back Surface (0=Hide, 1=Render) ;Line-segments are always // 7 Polygon Front Surface (0=Hide, 1=Render) ;rendered (no front/back) if (_mat.Value.m_PolygonDrawingFace == ModelBase.MaterialDef.PolygonDrawingFace.FrontAndBack) polyattr |= 0xC0; else if (_mat.Value.m_PolygonDrawingFace == ModelBase.MaterialDef.PolygonDrawingFace.Front) polyattr |= 0x80; else if (_mat.Value.m_PolygonDrawingFace == ModelBase.MaterialDef.PolygonDrawingFace.Back) polyattr |= 0x40; // 12 Far-plane intersecting polygons (0=Hide, 1=Render/clipped) if (mat.m_FarClipping) polyattr |= 0x1000; // 13 1-Dot polygons behind DISP_1DOT_DEPTH (0=Hide, 1=Render) if (mat.m_RenderOnePixelPolygons) polyattr |= 0x2000; // 14 Depth Test, Draw Pixels with Depth (0=Less, 1=Equal) (usually 0) if (mat.m_DepthTestDecal) polyattr |= 0x4000; // 15 Fog Enable (0=Disable, 1=Enable) if (mat.m_FogFlag) polyattr |= 0x8000; // 16-20 Alpha (0=Wire-Frame, 1..30=Translucent, 31=Solid) uint alpha = (uint)(mat.m_Alpha >> 3); polyattr |= (alpha << 16); // Set material colours uint diffuse_ambient = 0x00000000; diffuse_ambient |= Helper.ColorToBGR15(mat.m_Diffuse); diffuse_ambient |= 0x8000; diffuse_ambient |= (uint)(Helper.ColorToBGR15(mat.m_Diffuse) << 0x10); uint specular_emission = 0x00000000; specular_emission |= Helper.ColorToBGR15(mat.m_Specular); specular_emission |= (uint)(Helper.ColorToBGR15(mat.m_Emission) << 0x10); bmd.Write32(curoffset + 0x04, texid); bmd.Write32(curoffset + 0x08, palid); bmd.Write32(curoffset + 0x0C, texscaleS); bmd.Write32(curoffset + 0x10, texscaleT); bmd.Write16(curoffset + 0x14, (ushort)((mat.m_TextureRotation * 2048.0f) / Math.PI)); bmd.Write16(curoffset + 0x16, 0); bmd.Write32(curoffset + 0x18, (uint)(mat.m_TextureTranslation.X * 4096.0f)); bmd.Write32(curoffset + 0x1C, (uint)(mat.m_TextureTranslation.Y * 4096.0f)); bmd.Write32(curoffset + 0x20, teximage_param); bmd.Write32(curoffset + 0x24, polyattr); bmd.Write32(curoffset + 0x28, diffuse_ambient); bmd.Write32(curoffset + 0x2C, specular_emission); bmd.Write32(curoffset + 0x00, mextraoffset); bmd.WriteString(mextraoffset, matname, 0); mextraoffset += (uint)(matname.Length + 1); curoffset += 0x30; } curoffset = (uint)((mextraoffset + 3) & ~3); uint texoffset = curoffset; bmd.Write32(0x14, ntex); bmd.Write32(0x18, texoffset); // Offset to texture names uint textraoffset = (uint)(texoffset + (0x14 * ntex)); // Write texture entries foreach (ConvertedTexture tex in convertedTextures.Values) { curoffset = (uint)(texoffset + (0x14 * tex.m_TextureID)); bmd.Write32(curoffset + 0x08, (uint)tex.m_TextureDataLength); bmd.Write16(curoffset + 0x0C, (ushort)(8 << (int)((tex.m_DSTexParam >> 20) & 0x7))); bmd.Write16(curoffset + 0x0E, (ushort)(8 << (int)((tex.m_DSTexParam >> 23) & 0x7))); bmd.Write32(curoffset + 0x10, tex.m_DSTexParam); bmd.Write32(curoffset + 0x00, textraoffset); bmd.WriteString(textraoffset, tex.m_TextureName, 0); textraoffset += (uint)(tex.m_TextureName.Length + 1); } curoffset = (uint)((textraoffset + 3) & ~3); uint paloffset = curoffset; bmd.Write32(0x1C, npal); bmd.Write32(0x20, paloffset); // Offset to palette names uint pextraoffset = (uint)(paloffset + (0x10 * npal)); // Write texture palette entries foreach (ConvertedTexture tex in convertedTextures.Values) { if (tex.m_PaletteData == null) continue; curoffset = (uint)(paloffset + (0x10 * tex.m_PaletteID)); bmd.Write32(curoffset + 0x08, (uint)tex.m_PaletteData.Length); bmd.Write32(curoffset + 0x0C, 0xFFFFFFFF); bmd.Write32(curoffset + 0x00, pextraoffset); bmd.WriteString(pextraoffset, tex.m_PaletteName, 0); pextraoffset += (uint)(tex.m_PaletteName.Length + 1); } curoffset = (uint)((pextraoffset + 3) & ~3); // this must point to the texture data block bmd.Write32(0x38, curoffset); // Write texture and texture palette data foreach (ConvertedTexture tex in convertedTextures.Values) { bmd.WriteBlock(curoffset, tex.m_TextureData); bmd.Write32((uint)(texoffset + (0x14 * tex.m_TextureID) + 0x4), curoffset); curoffset += (uint)tex.m_TextureData.Length; curoffset = (uint)((curoffset + 3) & ~3); if (tex.m_PaletteData != null) { bmd.WriteBlock(curoffset, tex.m_PaletteData); bmd.Write32((uint)(paloffset + (0x10 * tex.m_PaletteID) + 0x4), curoffset); curoffset += (uint)tex.m_PaletteData.Length; curoffset = (uint)((curoffset + 3) & ~3); } } if (save) bmd.SaveChanges(); }