/// <summary> /// Converts a buffer model to the GC format /// </summary> /// <param name="model">The model to converter</param> /// <param name="optimize">Whether to optimize the attaches</param> /// <param name="ignoreWeights">If conversion should still happen, despite weights existing</param> /// <param name="forceUpdate">Whether to convert, regardless of whether the attaches are already GC</param> public static void ConvertModelToGC(NJObject model, bool optimize = true, bool ignoreWeights = false, bool forceUpdate = false) { if (model.Parent != null) { throw new FormatException($"Model {model.Name} is not hierarchy root!"); } if (model.AttachFormat == AttachFormat.GC && !forceUpdate) { return; } if (model.HasWeight && !ignoreWeights) { throw new FormatException("Model is weighted, cannot convert to basic format!"); } AttachHelper.ProcessWeightlessModel(model, (cacheAtc, ogAtc) => { // getting the vertex information Vector3[] positions = new Vector3[cacheAtc.vertices.Length]; Vector3[] normals = new Vector3[positions.Length]; for (int i = 0; i < positions.Length; i++) { var vtx = cacheAtc.vertices[i]; positions[i] = vtx.Position; normals[i] = vtx.Normal; } // getting the corner information int cornerCount = 0; for (int i = 0; i < cacheAtc.corners.Length; i++) { cornerCount += cacheAtc.corners[i].Length; } Vector2[] texcoords = new Vector2[cornerCount]; Color[] colors = new Color[cornerCount]; Corner[][] corners = new Corner[cacheAtc.corners.Length][]; ushort cornerIndex = 0; for (int i = 0; i < corners.Length; i++) { BufferCorner[] bufferCorners = cacheAtc.corners[i]; Corner[] meshCorners = new Corner[bufferCorners.Length]; for (int j = 0; j < bufferCorners.Length; j++) { BufferCorner bcorner = bufferCorners[j]; texcoords[cornerIndex] = bcorner.Texcoord; colors[cornerIndex] = bcorner.Color; meshCorners[j] = new Corner() { PositionIndex = bcorner.VertexIndex, NormalIndex = bcorner.VertexIndex, UV0Index = cornerIndex, Color0Index = cornerIndex }; cornerIndex++; } corners[i] = meshCorners; } bool hasUVs = texcoords.Any(x => x != default); // if it has no normals, always use colors (even if they are all white) bool hasColors = colors.Any(x => x != Color.White) || !normals.Any(x => x != Vector3.UnitY); // Puttin together the vertex sets VertexSet[] vertexData = new VertexSet[2 + (hasUVs ? 1 : 0)]; IndexAttributeParameter iaParam = new() { IndexAttributes = IndexAttributes.HasPosition }; if (positions.Length > 256) { iaParam.IndexAttributes |= IndexAttributes.Position16BitIndex; } vertexData[0] = new VertexSet(positions, false); if (hasColors) { iaParam.IndexAttributes |= IndexAttributes.HasColor; if (colors.Length > 256) { iaParam.IndexAttributes |= IndexAttributes.Color16BitIndex; } vertexData[1] = new VertexSet(colors); } else { iaParam.IndexAttributes |= IndexAttributes.HasNormal; if (normals.Length > 256) { iaParam.IndexAttributes |= IndexAttributes.Normal16BitIndex; } vertexData[1] = new VertexSet(normals, true); } if (hasUVs) { iaParam.IndexAttributes |= IndexAttributes.HasUV; if (texcoords.Length > 256) { iaParam.IndexAttributes |= IndexAttributes.UV16BitIndex; } vertexData[2] = new VertexSet(texcoords); } // stitching polygons together BufferMaterial currentMaterial = null; Mesh ProcessBufferMesh(int index) { // generating parameter info List <IParameter> parameters = new(); BufferMaterial cacheMaterial = cacheAtc.materials[index]; if (currentMaterial == null) { parameters.Add(new VtxAttrFmtParameter(VertexAttribute.Position)); parameters.Add(new VtxAttrFmtParameter(hasColors ? VertexAttribute.Color0 : VertexAttribute.Normal)); if (hasUVs) { parameters.Add(new VtxAttrFmtParameter(VertexAttribute.Tex0)); } parameters.Add(iaParam); if (cacheMaterial == null) { currentMaterial = new BufferMaterial() { MaterialAttributes = MaterialAttributes.noSpecular }; } else { currentMaterial = cacheMaterial; } parameters.Add(new LightingParameter() { LightingAttributes = LightingParameter.DefaultLighting.LightingAttributes, ShadowStencil = currentMaterial.ShadowStencil }); parameters.Add(new BlendAlphaParameter() { SourceAlpha = currentMaterial.SourceBlendMode, DestAlpha = currentMaterial.DestinationBlendmode }); parameters.Add(new AmbientColorParameter() { AmbientColor = currentMaterial.Ambient }); TextureParameter texParam = new(); texParam.TextureID = (ushort)currentMaterial.TextureIndex; if (!currentMaterial.ClampU) { texParam.Tiling |= GCTileMode.RepeatU; } if (!currentMaterial.ClampV) { texParam.Tiling |= GCTileMode.RepeatV; } if (currentMaterial.MirrorU) { texParam.Tiling |= GCTileMode.MirrorU; } if (currentMaterial.MirrorV) { texParam.Tiling |= GCTileMode.MirrorV; } parameters.Add(texParam); parameters.Add(Unknown9Parameter.DefaultValues); parameters.Add(new TexCoordGenParameter() { TexCoordID = currentMaterial.TexCoordID, TexGenType = currentMaterial.TexGenType, TexGenSrc = currentMaterial.TexGenSrc, MatrixID = currentMaterial.MatrixID }); } else { if (currentMaterial.ShadowStencil != cacheMaterial.ShadowStencil) { parameters.Add(new LightingParameter() { ShadowStencil = cacheMaterial.ShadowStencil }); } if (currentMaterial.SourceBlendMode != cacheMaterial.SourceBlendMode || currentMaterial.DestinationBlendmode != cacheMaterial.DestinationBlendmode) { parameters.Add(new BlendAlphaParameter() { SourceAlpha = cacheMaterial.SourceBlendMode, DestAlpha = cacheMaterial.DestinationBlendmode }); } if (currentMaterial.Ambient != cacheMaterial.Ambient) { parameters.Add(new AmbientColorParameter() { AmbientColor = cacheMaterial.Ambient }); } if (currentMaterial.TextureIndex != cacheMaterial.TextureIndex || currentMaterial.MirrorU != cacheMaterial.MirrorU || currentMaterial.MirrorV != cacheMaterial.MirrorV || currentMaterial.ClampU != cacheMaterial.ClampU || currentMaterial.ClampV != cacheMaterial.ClampV) { TextureParameter texParam = new(); texParam.TextureID = (ushort)cacheMaterial.TextureIndex; if (!cacheMaterial.ClampU) { texParam.Tiling |= GCTileMode.RepeatU; } if (!cacheMaterial.ClampV) { texParam.Tiling |= GCTileMode.RepeatV; } if (cacheMaterial.MirrorU) { texParam.Tiling |= GCTileMode.MirrorU; } if (cacheMaterial.MirrorV) { texParam.Tiling |= GCTileMode.MirrorV; } parameters.Add(texParam); } if (currentMaterial.TexCoordID != cacheMaterial.TexCoordID || currentMaterial.TexGenType != cacheMaterial.TexGenType || currentMaterial.TexGenSrc != cacheMaterial.TexGenSrc || currentMaterial.MatrixID != cacheMaterial.MatrixID) { parameters.Add(new TexCoordGenParameter() { TexCoordID = cacheMaterial.TexCoordID, TexGenType = cacheMaterial.TexGenType, TexGenSrc = cacheMaterial.HasAttribute(MaterialAttributes.normalMapping) ? TexGenSrc.Normal : cacheMaterial.TexGenSrc, MatrixID = cacheMaterial.MatrixID }); } currentMaterial = cacheMaterial; } // note: a single triangle polygon can only carry 0xFFFF corners, so about 22k tris Corner[] triangleCorners = corners[index]; List <Poly> polygons = new(); if (triangleCorners.Length > 0xFFFF) { int remainingLength = triangleCorners.Length; int offset = 0; while (remainingLength > 0) { Corner[] finalCorners = new Corner[Math.Max(0xFFFF, remainingLength)]; Array.Copy(triangleCorners, offset, finalCorners, 0, finalCorners.Length); offset += finalCorners.Length; remainingLength -= finalCorners.Length; Poly triangle = new(PolyType.Triangles, finalCorners); polygons.Add(triangle); }
/// <summary> /// Converts the buffer data of a model to BASIC attaches /// </summary> /// <param name="model">The tip of the model hierarchy to convert</param> /// <param name="optimize">Whether to optimize the data</param> /// <param name="ignoreWeights">Convert regardless of weight information being lost</param> /// <param name="forceUpdate">Still convert, even if the attaches are Basic already</param> public static void ConvertModelToBasic(NJObject model, bool optimize = true, bool ignoreWeights = false, bool forceUpdate = false) { if (model.Parent != null) { throw new FormatException($"Model {model.Name} is not hierarchy root!"); } if (model.AttachFormat == AttachFormat.BASIC && !forceUpdate) { return; } if (model.HasWeight && !ignoreWeights) { throw new FormatException("Model is weighted, cannot convert to basic format!"); } AttachHelper.ProcessWeightlessModel(model, (cacheAtc, ogAtc) => { // getting the vertex information Vector3[] positions = new Vector3[cacheAtc.vertices.Length]; Vector3[] normals = new Vector3[positions.Length]; bool hasNormals = false; for (int i = 0; i < positions.Length; i++) { var vtx = cacheAtc.vertices[i]; positions[i] = vtx.Position; normals[i] = vtx.Normal; if (vtx.Normal != Vector3.UnitY) { hasNormals = true; } } if (!hasNormals) { normals = null; } // putting together polygons Mesh[] meshes = new Mesh[cacheAtc.corners.Length]; Material[] materials = new Material[cacheAtc.corners.Length]; for (int i = 0; i < cacheAtc.corners.Length; i++) { // creating the material Material mat = new(); BufferMaterial bmat = cacheAtc.materials[i]; if (bmat != null) { mat.DiffuseColor = bmat.Diffuse; mat.SpecularColor = bmat.Specular; mat.Exponent = bmat.SpecularExponent; mat.TextureID = bmat.TextureIndex; mat.FilterMode = bmat.TextureFiltering; mat.MipmapDAdjust = bmat.MipmapDistanceAdjust; mat.SuperSample = bmat.AnisotropicFiltering; mat.ClampU = bmat.ClampU; mat.ClampV = bmat.ClampV; mat.MirrorU = bmat.MirrorU; mat.MirrorV = bmat.MirrorV; mat.UseAlpha = bmat.UseAlpha; mat.SourceAlpha = bmat.SourceBlendMode; mat.DestinationAlpha = bmat.DestinationBlendmode; mat.DoubleSided = !bmat.Culling; mat.IgnoreLighting = bmat.HasAttribute(MaterialAttributes.noDiffuse); mat.IgnoreSpecular = bmat.HasAttribute(MaterialAttributes.noSpecular); mat.UseTexture = bmat.HasAttribute(MaterialAttributes.useTexture); mat.EnvironmentMap = bmat.HasAttribute(MaterialAttributes.normalMapping); } materials[i] = mat; // creating the polygons BufferCorner[] bCorners = cacheAtc.corners[i]; IPoly[] triangles = new IPoly[bCorners.Length / 3]; Vector2[] texcoords = new Vector2[bCorners.Length]; Color[] colors = new Color[bCorners.Length]; Triangle current = new(); for (int j = 0; j < bCorners.Length; j++) { BufferCorner corner = bCorners[j]; int vIndex = j % 3; current.Indices[vIndex] = corner.VertexIndex; if (vIndex == 2) { triangles[(j - 2) / 3] = current; current = new Triangle(); } texcoords[j] = corner.Texcoord; colors[j] = corner.Color; } bool hasTexcoords = texcoords.Any(x => x != default); bool hasColors = colors.Any(x => x != Color.White); Mesh basicmesh = new (BASICPolyType.Triangles, triangles, false, hasColors, hasTexcoords, (ushort)i); if (hasColors) { basicmesh.Colors = colors; } if (hasTexcoords) { basicmesh.Texcoords = texcoords; } meshes[i] = basicmesh; }