/// <summary> /// Load a gc attach from a file /// </summary> /// <param name="source">Byte source from a file</param> /// <param name="address">Address at which the attach is located</param> /// <param name="imageBase">Address image base</param> /// <param name="labels">Labels for the data to use</param> /// <returns></returns> public static GCAttach Read(byte[] source, uint address, uint imageBase, Dictionary <uint, string> labels) { string name; if (labels.ContainsKey(address)) { name = labels[address]; } else { name = "attach_" + address.ToString("X8"); } // The struct is 36/0x24 bytes long uint vertexAddress = source.ToUInt32(address) - imageBase; //uint gap = source.ToUInt32(address + 4); uint opaqueAddress = source.ToUInt32(address + 8) - imageBase; uint transparentAddress = source.ToUInt32(address + 12) - imageBase; int opaqueCount = source.ToInt16(address + 16); int transparentCount = source.ToInt16(address + 18); address += 20; Bounds bounds = Bounds.Read(source, ref address); // reading vertex data List <VertexSet> vertexData = new(); VertexSet vertexSet = VertexSet.Read(source, vertexAddress, imageBase); while (vertexSet.Attribute != VertexAttribute.Null) { vertexData.Add(vertexSet); vertexAddress += 16; vertexSet = VertexSet.Read(source, vertexAddress, imageBase); } IndexAttributes indexAttribs = IndexAttributes.HasPosition; List <Mesh> opaqueMeshes = new(); for (int i = 0; i < opaqueCount; i++) { Mesh mesh = Mesh.Read(source, opaqueAddress, imageBase, ref indexAttribs); opaqueMeshes.Add(mesh); opaqueAddress += 16; } indexAttribs = IndexAttributes.HasPosition; List <Mesh> transparentMeshes = new(); for (int i = 0; i < transparentCount; i++) { Mesh mesh = Mesh.Read(source, transparentAddress, imageBase, ref indexAttribs); transparentMeshes.Add(mesh); transparentAddress += 16; } return(new GCAttach(vertexData.ToArray(), opaqueMeshes.ToArray(), transparentMeshes.ToArray()) { Name = name, MeshBounds = bounds }); }
public override uint Write(EndianWriter writer, uint imageBase, bool DX, Dictionary <string, uint> labels) { VertexSet[] sets = VertexData.Values.ToArray(); uint[] vtxAddresses = new uint[VertexData.Count]; for (int i = 0; i < sets.Length; i++) { VertexSet vtxSet = sets[i]; vtxAddresses[i] = writer.Position + imageBase; IOType outputType = vtxSet.DataType.ToStructType(); switch (vtxSet.Attribute) { case VertexAttribute.Position: case VertexAttribute.Normal: foreach (Vector3 vec in vtxSet.Vector3Data) { vec.Write(writer, outputType); } break; case VertexAttribute.Color0: case VertexAttribute.Color1: foreach (Color col in vtxSet.ColorData) { col.Write(writer, outputType); } break; case VertexAttribute.Tex0: case VertexAttribute.Tex1: case VertexAttribute.Tex2: case VertexAttribute.Tex3: case VertexAttribute.Tex4: case VertexAttribute.Tex5: case VertexAttribute.Tex6: case VertexAttribute.Tex7: foreach (Vector2 uv in vtxSet.UVData) { (uv * 256).Write(writer, outputType); } break; case VertexAttribute.PositionMatrixId: case VertexAttribute.Null: default: throw new FormatException($"Vertex set had an invalid or unavailable type: {vtxSet.Attribute}"); } } uint vtxAddr = writer.Position + imageBase; // writing vertex attributes for (int i = 0; i < sets.Length; i++) { VertexSet vtxSet = sets[i]; writer.Write(new byte[] { (byte)vtxSet.Attribute, (byte)vtxSet.StructSize }); writer.WriteUInt16((ushort)vtxSet.DataLength); uint structure = (uint)vtxSet.StructType; structure |= (uint)((byte)vtxSet.DataType << 4); writer.WriteUInt32(structure); writer.WriteUInt32(vtxAddresses[i]); writer.WriteUInt32((uint)(vtxSet.DataLength * vtxSet.StructSize)); } // empty vtx attribute byte[] nullVtx = new byte[16]; nullVtx[0] = 0xFF; writer.Write(nullVtx); // writing geometry data uint[] WriteMeshData(Mesh[] meshes) { uint[] result = new uint[meshes.Length * 4]; IndexAttributes indexAttribs = IndexAttributes.HasPosition; for (int i = 0, ri = 0; i < meshes.Length; i++, ri += 4) { Mesh m = meshes[i]; IndexAttributes?t = m.IndexAttributes; if (t.HasValue) { indexAttribs = t.Value; } // writing parameters result[ri] = writer.Position + imageBase; result[ri + 1] = (uint)m.Parameters.Length; foreach (IParameter p in m.Parameters) { p.Write(writer); } // writing polygons uint addr = writer.Position; foreach (Poly p in m.Polys) { p.Write(writer, indexAttribs); } result[ri + 2] = addr + imageBase; result[ri + 3] = writer.Position - addr; } return(result); } uint[] opaqueMeshStructs = WriteMeshData(OpaqueMeshes); uint[] transparentMeshStructs = WriteMeshData(TransparentMeshes); // writing geometry properties uint opaqueAddress = writer.Position + imageBase; foreach (uint i in opaqueMeshStructs) { writer.Write(i); } uint transparentAddress = writer.Position + imageBase; foreach (uint i in transparentMeshStructs) { writer.Write(i); } uint address = writer.Position + imageBase; labels.AddLabel(Name, address); writer.WriteUInt32(vtxAddr); writer.WriteUInt32(0); writer.WriteUInt32(opaqueAddress); writer.WriteUInt32(transparentAddress); writer.WriteUInt16((ushort)OpaqueMeshes.Length); writer.WriteUInt16((ushort)TransparentMeshes.Length); MeshBounds.Write(writer); return(address); }
/// <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); }