public static PrimitiveType GLPrimitiveType(GXPrimitiveType gxprim) { switch (gxprim) { case GXPrimitiveType.Lines: return(PrimitiveType.Lines); case GXPrimitiveType.LineStrip: return(PrimitiveType.LineStrip); case GXPrimitiveType.Points: return(PrimitiveType.Points); case GXPrimitiveType.Quads: return(PrimitiveType.Quads); case GXPrimitiveType.TriangleFan: return(PrimitiveType.TriangleFan); case GXPrimitiveType.Triangles: return(PrimitiveType.Triangles); case GXPrimitiveType.TriangleStrip: return(PrimitiveType.TriangleStrip); default: return(PrimitiveType.Points); } }
public List <MeshVertexIndex> ConvertTopologyToTriangles(GXPrimitiveType fromType, List <MeshVertexIndex> indexes) { List <MeshVertexIndex> sortedIndexes = new List <MeshVertexIndex>(); if (fromType == GXPrimitiveType.TriangleStrip) { for (int v = 2; v < indexes.Count; v++) { bool isEven = v % 2 != 0; MeshVertexIndex[] newTri = new MeshVertexIndex[3]; newTri[0] = indexes[v - 2]; newTri[1] = isEven ? indexes[v] : indexes[v - 1]; newTri[2] = isEven ? indexes[v - 1] : indexes[v]; // Check against degenerate triangles (a triangle which shares indexes) if (newTri[0] != newTri[1] && newTri[1] != newTri[2] && newTri[2] != newTri[0]) { sortedIndexes.AddRange(newTri); } else { System.Console.WriteLine("Degenerate triangle detected, skipping TriangleStrip conversion to triangle."); } } } else if (fromType == GXPrimitiveType.TriangleFan) { for (int v = 1; v < indexes.Count - 1; v++) { // Triangle is always, v, v+1, and index[0]? MeshVertexIndex[] newTri = new MeshVertexIndex[3]; newTri[0] = indexes[v]; newTri[1] = indexes[v + 1]; newTri[2] = indexes[0]; // Check against degenerate triangles (a triangle which shares indexes) if (newTri[0] != newTri[1] && newTri[1] != newTri[2] && newTri[2] != newTri[0]) { sortedIndexes.AddRange(newTri); } else { System.Console.WriteLine("Degenerate triangle detected, skipping TriangleFan conversion to triangle."); } } } else if (fromType == GXPrimitiveType.Triangles) { // The good news is, Triangles just go straight though! sortedIndexes.AddRange(indexes); } else { System.Console.WriteLine("Unsupported GXPrimitiveType: {0} in conversion to Triangle List.", fromType); } return(sortedIndexes); }
private List <GXVertex> ReadMdlPrimitives(EndianBinaryReader reader, ShapePacket curPak) { // OK. As far as we can tell, mdl suckerpunches our normal // understanding of the GC's GX, so we have to deal with a // hardcoded implementation. List <GXVertex> outList = new List <GXVertex>(); GXPrimitiveType curPrim = (GXPrimitiveType)reader.ReadByte(); while (curPrim != GXPrimitiveType.None) { List <GXVertex> tempVerts = new List <GXVertex>(); ushort vertCount = reader.ReadUInt16(); for (int i = 0; i < vertCount; i++) { List <int> tempList = new List <int>(); byte mtxPos1 = (byte)(reader.ReadByte() / 3); byte mtxPos2 = (byte)(reader.ReadByte() / 3); byte mtxPos3 = (byte)(reader.ReadByte() / 3); if (mtxPos1 != mtxPos2) { } ushort posIndex = reader.ReadUInt16(); ushort normalIndex = reader.ReadUInt16(); ushort tex0Index = reader.ReadUInt16(); if (ActiveAttributes.Contains(GXAttribute.PositionMatrixIndex)) { tempList.Add((int)mtxPos1); } if (ActiveAttributes.Contains(GXAttribute.Position)) { tempList.Add(posIndex); } if (ActiveAttributes.Contains(GXAttribute.Normal)) { tempList.Add(normalIndex); } if (ActiveAttributes.Contains(GXAttribute.Tex0)) { tempList.Add(tex0Index); } tempVerts.Add(new GXVertex(tempList.ToArray())); } outList.AddRange(ConvertTopologyToTriangles(curPrim, tempVerts)); curPrim = (GXPrimitiveType)reader.ReadByte(); } return(outList); }
/// <summary> /// /// </summary> /// <param name="type"></param> /// <param name="vertices"></param> /// <param name="parent"></param> /// <returns></returns> private List <IOVertex> ConvertGXDLtoTriangleList(GXPrimitiveType type, List <SBHsdVertex> vertices, SBHsdBone parent) { var list = new List <IOVertex>(); foreach (var v in vertices) { var vertex = new IOVertex() { Position = new System.Numerics.Vector3(v.POS.X, v.POS.Y, v.POS.Z), Normal = new System.Numerics.Vector3(v.NRM.X, v.NRM.Y, v.NRM.Z), }; vertex.SetUV(v.UV0.X, v.UV0.Y, 0); for (int i = 0; i < 4; i++) { if (v.Weight[i] > 0) { vertex.Envelope.Weights.Add(new IOBoneWeight() { BoneName = "JOBJ_" + (int)v.Bone[i], Weight = v.Weight[i] }); } } if (parent != null) { vertex.Transform(TktoNumeric(parent.WorldTransform)); if (vertex.Envelope.Weights.Count == 0) { vertex.Envelope.Weights.Add(new IOBoneWeight() { BoneName = parent.Name, Weight = 1 }); } } if (v.Weight.X == 1) { vertex.Transform(TktoNumeric(Skeleton.Bones[(int)v.Bone.X].WorldTransform)); } list.Add(vertex); } if (type == GXPrimitiveType.TriangleStrip) { TriangleConvert.StripToList(list, out list); } if (type == GXPrimitiveType.Quads) { TriangleConvert.QuadToList(list, out list); } return(list); }
public bool Read(BinaryReaderExt Reader, GX_Attribute[] Attributes) { PrimitiveType = (GXPrimitiveType)Reader.ReadByte(); if (PrimitiveType == 0) { return(false); } var count = Reader.ReadInt16(); Indices = new GX_IndexGroup[count]; for (int j = 0; j < count; j++) { GX_IndexGroup g = new GX_IndexGroup(); g.Indices = new ushort[Attributes.Length]; int i = 0; Indices[j] = g; foreach (var att in Attributes) { if (att.AttributeName == GXAttribName.GX_VA_NULL) { continue; } switch (att.AttributeType) { case GXAttribType.GX_DIRECT: if (att.AttributeName == GXAttribName.GX_VA_CLR0) { g.Clr0 = ReadDirectGXColor(Reader, (int)att.CompType); } else if (att.AttributeName == GXAttribName.GX_VA_CLR1) { g.Clr1 = ReadDirectGXColor(Reader, (int)att.CompType); } else { g.Indices[i] = Reader.ReadByte(); } break; case GXAttribType.GX_INDEX8: g.Indices[i] = Reader.ReadByte(); break; case GXAttribType.GX_INDEX16: g.Indices[i] = Reader.ReadUInt16(); break; } i++; } } return(true); }
public bool Read(HSDReader Reader, HSD_AttributeGroup Attributes) { PrimitiveType = (GXPrimitiveType)Reader.ReadByte(); if (PrimitiveType == 0) { return(false); } Count = Reader.ReadUInt16(); Indices = new GXIndexGroup[Count]; for (int j = 0; j < Count; j++) { GXIndexGroup g = new GXIndexGroup(); g.Indices = new ushort[Attributes.Attributes.Count]; int i = 0; Indices[j] = g; foreach (GXVertexBuffer att in Attributes.Attributes) { switch (att.AttributeType) { case GXAttribType.GX_DIRECT: if (att.Name == GXAttribName.GX_VA_CLR0) { g.Clr0 = ReadGXClr(Reader, (int)att.CompType); } else if (att.Name == GXAttribName.GX_VA_CLR1) { g.Clr1 = ReadGXClr(Reader, (int)att.CompType); } else { g.Indices[i] = Reader.ReadByte(); } break; case GXAttribType.GX_INDEX8: g.Indices[i] = Reader.ReadByte(); break; case GXAttribType.GX_INDEX16: g.Indices[i] = Reader.ReadUInt16(); break; } i++; } } return(true); }
private List <IOVertex> ConvertGXDLtoTriangleList(GXPrimitiveType type, List <SBHsdVertex> vertices, SBHsdBone parent) { var list = new List <IOVertex>(); foreach (var v in vertices) { var vertex = new IOVertex() { Position = GXtoGL.GLVector3(v.POS), Normal = GXtoGL.GLVector3(v.NRM), UV0 = GXtoGL.GLVector2(v.UV0), BoneIndices = v.Bone, BoneWeights = v.Weight, }; if (parent != null) { vertex.Position = Vector3.TransformPosition(vertex.Position, parent.WorldTransform); vertex.Normal = Vector3.TransformNormal(vertex.Normal, parent.WorldTransform); if (vertex.BoneWeights.X == 0) { vertex.BoneWeights.X = 1; vertex.BoneIndices.X = Skeleton.IndexOfBone(parent); } } if (v.Weight.X == 1) { vertex.Position = Vector3.TransformPosition(vertex.Position, Skeleton.Bones[(int)v.Bone.X].WorldTransform); vertex.Normal = Vector3.TransformNormal(vertex.Normal, Skeleton.Bones[(int)v.Bone.X].WorldTransform); } list.Add(vertex); } if (type == GXPrimitiveType.TriangleStrip) { TriangleConvert.StripToList(list, out list); } if (type == GXPrimitiveType.Quads) { TriangleConvert.QuadToList(list, out list); } return(list); }
private List <GXVertex> ConvertTopologyToTriangles(GXPrimitiveType originalType, List <GXVertex> vertices) { List <GXVertex> tris = new List <GXVertex>(); switch (originalType) { case GXPrimitiveType.Triangles: return(tris); case GXPrimitiveType.TriangleStrip: for (int v = 2; v < vertices.Count; v++) { bool even = v % 2 != 0; var tri = new GXVertex[3]; tri[0] = vertices[v - 2]; tri[1] = even ? vertices[v] : vertices[v - 1]; tri[2] = even ? vertices[v - 1] : vertices[v]; if (tri[0] != tri[1] && tri[1] != tri[2] && tri[2] != tri[0]) { tris.AddRange(tri); } } break; case GXPrimitiveType.TriangleFan: for (int v = 1; v < vertices.Count; v++) { var tri = new GXVertex[3]; tri[0] = vertices[v]; tri[1] = vertices[v + 1]; tri[2] = vertices[0]; if (tri[0] != tri[1] && tri[1] != tri[2] && tri[2] != tri[0]) { tris.AddRange(tri); } } break; } return(tris); }
private List <GXVertex> ReadPrimitives(EndianBinaryReader reader) { List <GXVertex> verts = new List <GXVertex>(); GXPrimitiveType curPrim = (GXPrimitiveType)reader.ReadByte(); while (curPrim != GXPrimitiveType.None) { List <GXVertex> temp = new List <GXVertex>(); ushort vertCount = reader.ReadUInt16(); for (int i = 0; i < vertCount; i++) { temp.Add(new GXVertex(reader, ActiveAttributes.Count)); } verts.AddRange(ConvertTopologyToTriangles(curPrim, temp)); curPrim = (GXPrimitiveType)reader.ReadByte(); } return(verts); }
public void ReadSHP1FromStream(EndianBinaryReader reader, long tagStart) { #region Load data from SHP1 header short shapeCount = reader.ReadInt16(); Trace.Assert(reader.ReadUInt16() == 0xFFFF); // Padding int shapeOffset = reader.ReadInt32(); // Another index remap table. int remapTableOffset = reader.ReadInt32(); Trace.Assert(reader.ReadInt32() == 0); int attributeOffset = reader.ReadInt32(); // Offset to the Matrix Table which holds a list of ushorts used for ?? int matrixTableOffset = reader.ReadInt32(); // Offset to the array of primitive's data. int primitiveDataOffset = reader.ReadInt32(); int matrixDataOffset = reader.ReadInt32(); int packetLocationOffset = reader.ReadInt32(); #endregion for (int s = 0; s < shapeCount; s++) { // Shapes can have different attributes for each shape. (ie: Some have only Position, while others have Pos & TexCoord, etc.) Each // shape (which has a consistent number of attributes) it is split into individual packets, which are a collection of geometric primitives. // Each packet can have individual unique skinning data. reader.BaseStream.Position = tagStart + shapeOffset + (0x28 * s); /* 0x28 is the size of one Shape entry*/ Shape shape = new Shape(); #region Load data from shape struct shape.MatrixType = reader.ReadByte(); Trace.Assert(reader.ReadByte() == 0xFF); // Padding // Number of Packets (of data) contained in this Shape ushort grp_count = reader.ReadUInt16(); // Offset from the start of the Attribute List to the attributes this particular batch uses. ushort batchAttributeOffset = reader.ReadUInt16(); ushort firstMatrixIndex = reader.ReadUInt16(); ushort firstGrpIndex = reader.ReadUInt16(); Trace.Assert(reader.ReadUInt16() == 0xFFFF); // Padding float boundingSphereDiameter = reader.ReadSingle(); Vector3 bboxMin = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); Vector3 bboxMax = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); shape.BoundingSphereDiameter = boundingSphereDiameter; shape.BoundingBox = new FAABox(bboxMin, bboxMax); #endregion Shapes.Add(shape); // Determine which Attributes this particular shape uses. reader.BaseStream.Position = tagStart + attributeOffset + batchAttributeOffset; List <ShapeVertexAttribute> attributes = new List <ShapeVertexAttribute>(); while (true) { ShapeVertexAttribute attribute = new ShapeVertexAttribute((VertexArrayType)reader.ReadInt32(), (VertexDataType)reader.ReadInt32()); if (attribute.ArrayType == VertexArrayType.NullAttr) { break; } attributes.Add(attribute); // We'll enable the attributes for the shape here, but we'll skip PositionMatrixIndex. // We're going to use our own attributes for skinning - SkinIndices and SkinWeights. if (attribute.ArrayType != VertexArrayType.PositionMatrixIndex) { shape.VertexDescription.EnableAttribute(ArrayTypeToShader(attribute.ArrayType)); } } for (ushort p = 0; p < grp_count; p++) { MatrixGroup grp = new MatrixGroup(new List <MeshVertexIndex>(), new SkinDataTable(0)); // The packets are all stored linearly and then they point to the specific size and offset of the data for this particular packet. reader.BaseStream.Position = tagStart + packetLocationOffset + ((firstGrpIndex + p) * 0x8); /* 0x8 is the size of one Packet entry */ int packetSize = reader.ReadInt32(); int packetOffset = reader.ReadInt32(); // Read Matrix Data for Packet reader.BaseStream.Position = tagStart + matrixDataOffset + (firstMatrixIndex + p) * 0x08; /* 0x8 is the size of one Matrix Data */ ushort matrixUnknown0 = reader.ReadUInt16(); ushort matrixCount = reader.ReadUInt16(); uint matrixFirstIndex = reader.ReadUInt32(); SkinDataTable matrixData = new SkinDataTable(matrixUnknown0); grp.MatrixDataTable = matrixData; // Read Matrix Table data. The Matrix Table is skinning information for the packet which indexes into the DRW1 section for more info. reader.BaseStream.Position = tagStart + matrixTableOffset + (matrixFirstIndex * 0x2); /* 0x2 is the size of one Matrix Table entry */ for (int m = 0; m < matrixCount; m++) { matrixData.MatrixTable.Add(reader.ReadUInt16()); } // Read the Primitive Data reader.BaseStream.Position = tagStart + primitiveDataOffset + packetOffset; uint numPrimitiveBytesRead = 0; while (numPrimitiveBytesRead < packetSize) { // The game pads the chunk out with zeros, so if there's a primitive with type zero (invalid) then we early out of the loop. GXPrimitiveType type = (GXPrimitiveType)reader.ReadByte(); if (type == 0 || numPrimitiveBytesRead >= packetSize) { break; } // The number of vertices this primitive has indexes for ushort vertexCount = reader.ReadUInt16(); numPrimitiveBytesRead += 0x3; // 2 bytes for vertex count, one byte for GXPrimitiveType. List <MeshVertexIndex> primitiveVertices = new List <MeshVertexIndex>(); for (int v = 0; v < vertexCount; v++) { MeshVertexIndex newVert = new MeshVertexIndex(); primitiveVertices.Add(newVert); // Each vertex has an index for each ShapeAttribute specified by the Shape that we belong to. So we'll loop through // each index and load it appropriately (as vertices can have different data sizes). foreach (ShapeVertexAttribute curAttribute in attributes) { int index = 0; uint numBytesRead = 0; switch (curAttribute.DataType) { case VertexDataType.Unsigned8: case VertexDataType.Signed8: index = reader.ReadByte(); numBytesRead = 1; break; case VertexDataType.Unsigned16: case VertexDataType.Signed16: index = reader.ReadUInt16(); numBytesRead = 2; break; case VertexDataType.Float32: case VertexDataType.None: default: System.Console.WriteLine("Unknown Data Type {0} for ShapeAttribute!", curAttribute.DataType); break; } // We now have the index into the datatype this array points to. We can now inspect the array type of the // attribute to get the value out of the correct source array. switch (curAttribute.ArrayType) { case VertexArrayType.Position: newVert.Position = index; break; case VertexArrayType.PositionMatrixIndex: newVert.PosMtxIndex = index / 3; break; case VertexArrayType.Normal: newVert.Normal = index; break; case VertexArrayType.Color0: newVert.Color0 = index; break; case VertexArrayType.Color1: newVert.Color1 = index; break; case VertexArrayType.Tex0: newVert.Tex0 = index; break; case VertexArrayType.Tex1: newVert.Tex1 = index; break; case VertexArrayType.Tex2: newVert.Tex2 = index; break; case VertexArrayType.Tex3: newVert.Tex3 = index; break; case VertexArrayType.Tex4: newVert.Tex4 = index; break; case VertexArrayType.Tex5: newVert.Tex5 = index; break; case VertexArrayType.Tex6: newVert.Tex6 = index; break; case VertexArrayType.Tex7: newVert.Tex7 = index; break; default: System.Console.WriteLine("Unsupported ArrayType {0} for ShapeAttribute!", curAttribute.ArrayType); break; } numPrimitiveBytesRead += numBytesRead; } } // All vertices have now been loaded into the primitiveIndexes array. We can now convert them if needed // to triangle lists, instead of triangle fans, strips, etc. grp.Indices.AddRange(ConvertTopologyToTriangles(type, primitiveVertices)); } shape.MatrixGroups.Add(grp); } } FixGroupSkinningIndices(); }
private static void LoadSHP1SectionFromFile(MeshVertexAttributeHolder vertexData, Mesh j3dMesh, EndianBinaryReader reader, long chunkStart) { short batchCount = reader.ReadInt16(); short padding = reader.ReadInt16(); int batchOffset = reader.ReadInt32(); int unknownTableOffset = reader.ReadInt32(); // Another one of those 0->(n-1) counters. I think all sections have it? Might be part of the way they used inheritance to write files. int alwaysZero = reader.ReadInt32(); Trace.Assert(alwaysZero == 0); int attributeOffset = reader.ReadInt32(); int matrixTableOffset = reader.ReadInt32(); int primitiveDataOffset = reader.ReadInt32(); int matrixDataOffset = reader.ReadInt32(); int packetLocationOffset = reader.ReadInt32(); // Batches can have different attributes (ie: some have pos, some have normal, some have texcoords, etc.) they're split by batches, // where everything in the batch uses the same set of vertex attributes. Each batch then has several packets, which are a collection // of primitives. for (int b = 0; b < batchCount; b++) { MeshBatch meshBatch = new MeshBatch(); j3dMesh.SubMeshes.Add(meshBatch); int overallVertexCount = 0; meshBatch.PrimitveType = OpenTK.Graphics.OpenGL.PrimitiveType.TriangleStrip; // HackHack, this varies per primitive. // We need to look on each primitive and convert them to trianglestrips, most are TS some are TF's. // We re-use the list struct here to dynamically add paired pos/col/tex as we load them // then we convert them into arrays for the MeshBatch afterwards. MeshVertexAttributeHolder meshVertexData = new MeshVertexAttributeHolder(); // chunkStart + batchOffset gets you the position where the batches are listed // 0x28 * b gives you the right batch - a batch is 0x28 in length reader.BaseStream.Position = chunkStart + batchOffset + (0x28 * b); long batchStart = reader.BaseStream.Position; byte matrixType = reader.ReadByte(); Trace.Assert(reader.ReadByte() == 0xFF); // Padding ushort packetCount = reader.ReadUInt16(); ushort batchAttributeOffset = reader.ReadUInt16(); ushort firstMatrixIndex = reader.ReadUInt16(); ushort firstPacketIndex = reader.ReadUInt16(); ushort unknownpadding = reader.ReadUInt16(); Trace.Assert(unknownpadding == 0xFFFF); float boundingSphereDiameter = reader.ReadSingle(); Vector3 boundingBoxMin = new Vector3(); boundingBoxMin.X = reader.ReadSingle(); boundingBoxMin.Y = reader.ReadSingle(); boundingBoxMin.Z = reader.ReadSingle(); Vector3 boundingBoxMax = new Vector3(); boundingBoxMax.X = reader.ReadSingle(); boundingBoxMax.Y = reader.ReadSingle(); boundingBoxMax.Z = reader.ReadSingle(); // We need to figure out how many primitive attributes there are in the SHP1 section. This can differ from the number of // attributes in the VTX1 section, as the SHP1 can also include things like PositionMatrixIndex, so the count can be different. // This also varies *per batch* as not all batches will have the things like PositionMatrixIndex. reader.BaseStream.Position = chunkStart + attributeOffset + batchAttributeOffset; var batchAttributes = new List <ShapeAttribute>(); do { ShapeAttribute attribute = new ShapeAttribute(); attribute.ArrayType = (VertexArrayType)reader.ReadInt32(); attribute.DataType = (VertexDataType)reader.ReadInt32(); if (attribute.ArrayType == VertexArrayType.NullAttr) { break; } batchAttributes.Add(attribute); } while (true); for (ushort p = 0; p < packetCount; p++) { // Packet Location reader.BaseStream.Position = chunkStart + packetLocationOffset; reader.BaseStream.Position += (firstPacketIndex + p) * 0x8; // A Packet Location is 0x8 long, so we skip ahead to the right one. int packetSize = reader.ReadInt32(); int packetOffset = reader.ReadInt32(); // Read the matrix data for this packet reader.BaseStream.Position = chunkStart + matrixDataOffset + (firstMatrixIndex + p) * 0x08; ushort matrixUnknown0 = reader.ReadUInt16(); ushort matrixCount = reader.ReadUInt16(); uint matrixFirstIndex = reader.ReadUInt32(); // Skip ahead to the actual data. reader.BaseStream.Position = chunkStart + matrixTableOffset + (matrixFirstIndex * 0x2); List <ushort> matrixTable = new List <ushort>(); for (int m = 0; m < matrixCount; m++) { matrixTable.Add(reader.ReadUInt16()); } // Jump the read head to the location of the primitives for this packet. reader.BaseStream.Position = chunkStart + primitiveDataOffset + packetOffset; int numVertexesAtPacketStart = meshVertexData.PositionMatrixIndexes.Count; uint numPrimitiveBytesRead = 0; while (numPrimitiveBytesRead < packetSize) { // Jump to the primitives // Primitives GXPrimitiveType type = (GXPrimitiveType)reader.ReadByte(); // Game pads the chunks out with zeros, so this is the signal for an early break; if (type == 0 || numPrimitiveBytesRead >= packetSize) { break; } ushort vertexCount = reader.ReadUInt16(); meshBatch.PrimitveType = type == GXPrimitiveType.TriangleStrip ? OpenTK.Graphics.OpenGL.PrimitiveType.TriangleStrip : OpenTK.Graphics.OpenGL.PrimitiveType.TriangleFan; //if (type != GXPrimitiveType.TriangleStrip) //{ // WLog.Warning(LogCategory.ModelLoading, null, "Unsupported GXPrimitiveType {0}", type); //} numPrimitiveBytesRead += 0x3; // Advance us by 3 for the Primitive header. for (int v = 0; v < vertexCount; v++) { meshVertexData.Indexes.Add(overallVertexCount); overallVertexCount++; // Iterate through the attribute types. I think the actual vertices are stored in interleaved format, // ie: there's say 13 vertexes but those 13 vertexes will have a pos/color/tex index listed after it // depending on the overall attributes of the file. for (int attrib = 0; attrib < batchAttributes.Count; attrib++) { // Jump to primitive location //reader.BaseStream.Position = chunkStart + primitiveDataOffset + numPrimitiveBytesRead + packetOffset; // Now that we know how big the vertex type is stored in (either a Signed8 or a Signed16) we can read that much data // and then we can use that index and index into int val = 0; uint numBytesRead = 0; switch (batchAttributes[attrib].DataType) { case VertexDataType.Signed8: val = reader.ReadByte(); numBytesRead = 1; break; case VertexDataType.Signed16: val = reader.ReadInt16(); numBytesRead = 2; break; default: WLog.Warning(LogCategory.ModelLoading, null, "Unknown Batch Index Type: {0}", batchAttributes[attrib].DataType); break; } // Now that we know what the index is, we can retrieve it from the appropriate array // and stick it into our vertex. The J3D format removes all duplicate vertex attributes // so we need to re-duplicate them here so that we can feed them to a PC GPU in a normal fashion. switch (batchAttributes[attrib].ArrayType) { case VertexArrayType.Position: meshVertexData.Position.Add(vertexData.Position[val]); break; case VertexArrayType.PositionMatrixIndex: meshVertexData.PositionMatrixIndexes.Add(val); break; case VertexArrayType.Normal: meshVertexData.Normal.Add(vertexData.Normal[val]); break; case VertexArrayType.Color0: meshVertexData.Color0.Add(vertexData.Color0[val]); break; case VertexArrayType.Color1: meshVertexData.Color1.Add(vertexData.Color1[val]); break; case VertexArrayType.Tex0: meshVertexData.Tex0.Add(vertexData.Tex0[val]); break; case VertexArrayType.Tex1: meshVertexData.Tex1.Add(vertexData.Tex1[val]); break; case VertexArrayType.Tex2: meshVertexData.Tex2.Add(vertexData.Tex2[val]); break; case VertexArrayType.Tex3: meshVertexData.Tex3.Add(vertexData.Tex3[val]); break; case VertexArrayType.Tex4: meshVertexData.Tex4.Add(vertexData.Tex4[val]); break; case VertexArrayType.Tex5: meshVertexData.Tex5.Add(vertexData.Tex5[val]); break; case VertexArrayType.Tex6: meshVertexData.Tex6.Add(vertexData.Tex6[val]); break; case VertexArrayType.Tex7: meshVertexData.Tex7.Add(vertexData.Tex7[val]); break; default: WLog.Warning(LogCategory.ModelLoading, null, "Unsupported attribType {0}", batchAttributes[attrib].ArrayType); break; } numPrimitiveBytesRead += numBytesRead; } // Gonna try a weird hack, where if the mesh doesn't have PMI values, we're going to use just use the packet index into the matrixtable // so that all meshes always have PMI values, to abstract out the ones that don't seem to (but still have matrixtable) junk. It's a guess // here. if (batchAttributes.Find(x => x.ArrayType == VertexArrayType.PositionMatrixIndex) == null) { meshVertexData.PositionMatrixIndexes.Add(p); } } // After we write a primitive, write a special null-terminator which signifies the GPU to do a primitive restart for the next tri-strip. meshVertexData.Indexes.Add(0xFFFF); } // The Matrix Table is per-packet, so we need to reach into the the matrix table after processing each packet // and transform the indexes. Yuck. Yay. for (int j = numVertexesAtPacketStart; j < meshVertexData.PositionMatrixIndexes.Count; j++) { // Yes you divide this by 3. No, no one knows why. $20 to the person who figures out why. meshBatch.drawIndexes.Add(matrixTable[meshVertexData.PositionMatrixIndexes[j] / 3]); } } meshBatch.Vertices = meshVertexData.Position.ToArray(); meshBatch.Color0 = meshVertexData.Color0.ToArray(); meshBatch.Color1 = meshVertexData.Color1.ToArray(); meshBatch.TexCoord0 = meshVertexData.Tex0.ToArray(); meshBatch.TexCoord1 = meshVertexData.Tex0.ToArray(); meshBatch.TexCoord2 = meshVertexData.Tex0.ToArray(); meshBatch.TexCoord3 = meshVertexData.Tex0.ToArray(); meshBatch.TexCoord4 = meshVertexData.Tex0.ToArray(); meshBatch.TexCoord5 = meshVertexData.Tex0.ToArray(); meshBatch.TexCoord6 = meshVertexData.Tex0.ToArray(); meshBatch.TexCoord7 = meshVertexData.Tex0.ToArray(); meshBatch.Indexes = meshVertexData.Indexes.ToArray(); meshBatch.PositionMatrixIndexs = meshVertexData.PositionMatrixIndexes; // This should be obsolete as they should be transformed already. } }
public Primitive(GXPrimitiveType type) { PrimitiveType = type; Vertices = new List <Vertex>(); }
/// <summary> /// Creates a primitive group for the vertex buffer /// </summary> /// <param name="type"></param> /// <param name="Vertices"></param> /// <param name="Attributes"></param> /// <returns></returns> private GX_PrimitiveGroup Compress(GXPrimitiveType type, GX_Vertex[] Vertices, GXAttribName[] Attributes) { GX_PrimitiveGroup g = new GX_PrimitiveGroup(); g.PrimitiveType = type; g.Indices = new GX_IndexGroup[Vertices.Length]; int IndexGroupIndex = 0; foreach (GX_Vertex v in Vertices) { GX_IndexGroup ig = new GX_IndexGroup(); ig.Indices = new ushort[Attributes.Length]; int i = 0; foreach (var b in Attributes) { switch (b) { case GXAttribName.GX_VA_CLR0: ig.Clr0 = v.CLR0.ToBytes(); break; case GXAttribName.GX_VA_CLR1: ig.Clr1 = v.CLR1.ToBytes(); break; case GXAttribName.GX_VA_PNMTXIDX: ig.Indices[i] = v.PNMTXIDX; break; case GXAttribName.GX_VA_TEX0MTXIDX: ig.Indices[i] = v.TEX0MTXIDX; break; case GXAttribName.GX_VA_TEX1MTXIDX: ig.Indices[i] = v.TEX1MTXIDX; break; case GXAttribName.GX_VA_NULL: break; case GXAttribName.GX_VA_POS: ig.Indices[i] = GetIndex(b, new float[] { v.POS.X, v.POS.Y, v.POS.Z }); break; case GXAttribName.GX_VA_NRM: ig.Indices[i] = GetIndex(b, new float[] { v.NRM.X, v.NRM.Y, v.NRM.Z }); break; case GXAttribName.GX_VA_NBT: ig.Indices[i] = GetIndex(b, new float[] { v.NRM.X, v.NRM.Y, v.NRM.Z, v.BITAN.X, v.BITAN.Y, v.BITAN.Z, v.TAN.X, v.TAN.Y, v.TAN.Z }); break; case GXAttribName.GX_VA_TEX0: ig.Indices[i] = GetIndex(b, new float[] { v.TEX0.X, v.TEX0.Y }); break; case GXAttribName.GX_VA_TEX1: ig.Indices[i] = GetIndex(b, new float[] { v.TEX1.X, v.TEX1.Y }); break; case GXAttribName.GX_VA_TEX2: ig.Indices[i] = GetIndex(b, new float[] { v.TEX2.X, v.TEX2.Y }); break; case GXAttribName.GX_VA_TEX3: ig.Indices[i] = GetIndex(b, new float[] { v.TEX3.X, v.TEX3.Y }); break; case GXAttribName.GX_VA_TEX4: ig.Indices[i] = GetIndex(b, new float[] { v.TEX4.X, v.TEX4.Y }); break; case GXAttribName.GX_VA_TEX5: ig.Indices[i] = GetIndex(b, new float[] { v.TEX5.X, v.TEX5.Y }); break; case GXAttribName.GX_VA_TEX6: ig.Indices[i] = GetIndex(b, new float[] { v.TEX6.X, v.TEX6.Y }); break; case GXAttribName.GX_VA_TEX7: ig.Indices[i] = GetIndex(b, new float[] { v.TEX7.X, v.TEX7.Y }); break; //case GXAttribName.GX_VA_CLR0: ig.Indices[i] = GetIndex(b, v.CLR0); break; default: throw new Exception("Error Building " + b); } i++; } g.Indices[IndexGroupIndex++] = ig; } return(g); }
public void ReadSHP1FromStream(EndianBinaryReader reader, long tagStart, MeshVertexHolder compressedVertexData) { ShapeCount = reader.ReadInt16(); Trace.Assert(reader.ReadUInt16() == 0xFFFF); // Padding int shapeOffset = reader.ReadInt32(); // Another index remap table. int remapTableOffset = reader.ReadInt32(); Trace.Assert(reader.ReadInt32() == 0); int attributeOffset = reader.ReadInt32(); // Offset to the Matrix Table which holds a list of ushorts used for ?? int matrixTableOffset = reader.ReadInt32(); // Offset to the array of primitive's data. int primitiveDataOffset = reader.ReadInt32(); int matrixDataOffset = reader.ReadInt32(); int packetLocationOffset = reader.ReadInt32(); reader.BaseStream.Position = tagStart + remapTableOffset; ShapeRemapTable = new List <short>(); for (int i = 0; i < ShapeCount; i++) { ShapeRemapTable.Add(reader.ReadInt16()); } for (int s = 0; s < ShapeCount; s++) { // Shapes can have different attributes for each shape. (ie: Some have only Position, while others have Pos & TexCoord, etc.) Each // shape (which has a consistent number of attributes) it is split into individual packets, which are a collection of geometric primitives. // Each packet can have individual unique skinning data. reader.BaseStream.Position = tagStart + shapeOffset + (0x28 * s) /* 0x28 is the size of one Shape entry*/; long shapeStart = reader.BaseStream.Position; Shape shape = new Shape(); shape.MatrixType = reader.ReadByte(); Trace.Assert(reader.ReadByte() == 0xFF); // Padding // Number of Packets (of data) contained in this Shape ushort packetCount = reader.ReadUInt16(); // Offset from the start of the Attribute List to the attributes this particular batch uses. ushort batchAttributeOffset = reader.ReadUInt16(); ushort firstMatrixIndex = reader.ReadUInt16(); ushort firstPacketIndex = reader.ReadUInt16(); Trace.Assert(reader.ReadUInt16() == 0xFFFF); // Padding float boundingSphereDiameter = reader.ReadSingle(); Vector3 bboxMin = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); Vector3 bboxMax = new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); // Determine which Attributes this particular shape uses. reader.BaseStream.Position = tagStart + attributeOffset + batchAttributeOffset; List <ShapeVertexAttribute> attributes = new List <ShapeVertexAttribute>(); do { ShapeVertexAttribute attribute = new ShapeVertexAttribute((VertexArrayType)reader.ReadInt32(), (VertexDataType)reader.ReadInt32()); if (attribute.ArrayType == VertexArrayType.NullAttr) { break; } attributes.Add(attribute); } while (true); shape.BoundingSphereDiameter = boundingSphereDiameter; shape.BoundingBox = new FAABox(bboxMin, bboxMax); shape.Attributes = attributes; Shapes.Add(shape); int numVertexRead = 0; for (ushort p = 0; p < packetCount; p++) { // The packets are all stored linearly and then they point to the specific size and offset of the data for this particular packet. reader.BaseStream.Position = tagStart + packetLocationOffset + ((firstPacketIndex + p) * 0x8); /* 0x8 is the size of one Packet entry */ int packetSize = reader.ReadInt32(); int packetOffset = reader.ReadInt32(); // Read Matrix Data for Packet reader.BaseStream.Position = tagStart + matrixDataOffset + (firstMatrixIndex + p) * 0x08; /* 0x8 is the size of one Matrix Data */ ushort matrixUnknown0 = reader.ReadUInt16(); ushort matrixCount = reader.ReadUInt16(); uint matrixFirstIndex = reader.ReadUInt32(); Shape.SkinDataTable matrixData = new Shape.SkinDataTable(matrixUnknown0); shape.MatrixDataTable.Add(matrixData); matrixData.FirstRelevantVertexIndex = shape.VertexData.Position.Count; // Read Matrix Table data. The Matrix Table is skinning information for the packet which indexes into the DRW1 section for more info. reader.BaseStream.Position = tagStart + matrixTableOffset + (matrixFirstIndex * 0x2); /* 0x2 is the size of one Matrix Table entry */ for (int m = 0; m < matrixCount; m++) { matrixData.MatrixTable.Add(reader.ReadUInt16()); } // Read the Primitive Data reader.BaseStream.Position = tagStart + primitiveDataOffset + packetOffset; uint numPrimitiveBytesRead = 0; while (numPrimitiveBytesRead < packetSize) { // The game pads the chunk out with zeros, so if there's a primitive with type zero (invalid) then we early out of the loop. GXPrimitiveType type = (GXPrimitiveType)reader.ReadByte(); if (type == 0 || numPrimitiveBytesRead >= packetSize) { break; } // The number of vertices this primitive has indexes for ushort vertexCount = reader.ReadUInt16(); numPrimitiveBytesRead += 0x3; // 2 bytes for vertex count, one byte for GXPrimitiveType. List <MeshVertexIndex> primitiveVertices = new List <MeshVertexIndex>(); for (int v = 0; v < vertexCount; v++) { MeshVertexIndex newVert = new MeshVertexIndex(); primitiveVertices.Add(newVert); // Each vertex has an index for each ShapeAttribute specified by the Shape that we belong to. So we'll loop through // each index and load it appropriately (as vertices can have different data sizes). foreach (ShapeVertexAttribute curAttribute in attributes) { int index = 0; uint numBytesRead = 0; switch (curAttribute.DataType) { case VertexDataType.Unsigned8: case VertexDataType.Signed8: index = reader.ReadByte(); numBytesRead = 1; break; case VertexDataType.Unsigned16: case VertexDataType.Signed16: index = reader.ReadUInt16(); numBytesRead = 2; break; case VertexDataType.Float32: case VertexDataType.None: default: System.Console.WriteLine("Unknown Data Type {0} for ShapeAttribute!", curAttribute.DataType); break; } // We now have the index into the datatype this array points to. We can now inspect the array type of the // attribute to get the value out of the correct source array. switch (curAttribute.ArrayType) { case VertexArrayType.Position: newVert.Position = index; break; case VertexArrayType.PositionMatrixIndex: newVert.PosMtxIndex = index; break; case VertexArrayType.Normal: newVert.Normal = index; break; case VertexArrayType.Color0: newVert.Color0 = index; break; case VertexArrayType.Color1: newVert.Color1 = index; break; case VertexArrayType.Tex0: newVert.Tex0 = index; break; case VertexArrayType.Tex1: newVert.Tex1 = index; break; case VertexArrayType.Tex2: newVert.Tex2 = index; break; case VertexArrayType.Tex3: newVert.Tex3 = index; break; case VertexArrayType.Tex4: newVert.Tex4 = index; break; case VertexArrayType.Tex5: newVert.Tex5 = index; break; case VertexArrayType.Tex6: newVert.Tex6 = index; break; case VertexArrayType.Tex7: newVert.Tex7 = index; break; default: System.Console.WriteLine("Unsupported ArrayType {0} for ShapeAttribute!", curAttribute.ArrayType); break; } numPrimitiveBytesRead += numBytesRead; } } // All vertices have now been loaded into the primitiveIndexes array. We can now convert them if needed // to triangle lists, instead of triangle fans, strips, etc. var triangleList = ConvertTopologyToTriangles(type, primitiveVertices); for (int i = 0; i < triangleList.Count; i++) { shape.Indexes.Add(numVertexRead); numVertexRead++; var tri = triangleList[i]; if (tri.Position >= 0) { shape.VertexData.Position.Add(compressedVertexData.Position[tri.Position]); } if (tri.Normal >= 0) { shape.VertexData.Normal.Add(compressedVertexData.Normal[tri.Normal]); } if (tri.Binormal >= 0) { shape.VertexData.Binormal.Add(compressedVertexData.Binormal[tri.Binormal]); } if (tri.Color0 >= 0) { shape.VertexData.Color0.Add(compressedVertexData.Color0[tri.Color0]); } if (tri.Color1 >= 0) { shape.VertexData.Color1.Add(compressedVertexData.Color1[tri.Color1]); } if (tri.Tex0 >= 0) { shape.VertexData.Tex0.Add(compressedVertexData.Tex0[tri.Tex0]); } if (tri.Tex1 >= 0) { shape.VertexData.Tex1.Add(compressedVertexData.Tex1[tri.Tex1]); } if (tri.Tex2 >= 0) { shape.VertexData.Tex2.Add(compressedVertexData.Tex2[tri.Tex2]); } if (tri.Tex3 >= 0) { shape.VertexData.Tex3.Add(compressedVertexData.Tex3[tri.Tex3]); } if (tri.Tex4 >= 0) { shape.VertexData.Tex4.Add(compressedVertexData.Tex4[tri.Tex4]); } if (tri.Tex5 >= 0) { shape.VertexData.Tex5.Add(compressedVertexData.Tex5[tri.Tex5]); } if (tri.Tex6 >= 0) { shape.VertexData.Tex6.Add(compressedVertexData.Tex6[tri.Tex6]); } if (tri.Tex7 >= 0) { shape.VertexData.Tex7.Add(compressedVertexData.Tex7[tri.Tex7]); } // We pre-divide the index here just to make life simpler. For some reason the index is multiplied by three // and it's not entirely clear why. Might be related to doing skinning on the GPU and offsetting the correct // number of floats in a matrix? if (tri.PosMtxIndex >= 0) { shape.VertexData.PositionMatrixIndexes.Add(tri.PosMtxIndex / 3); } else { shape.VertexData.PositionMatrixIndexes.Add(0); } } } // Set the last relevant vertex for this packet. matrixData.LastRelevantVertexIndex = shape.VertexData.Position.Count; } shape.UploadBuffersToGPU(false); } }
/// <summary> /// Creates a primitive group for the vertex buffer /// </summary> /// <param name="type"></param> /// <param name="Vertices"></param> /// <param name="Attributes"></param> /// <returns></returns> public GXPrimitiveGroup Compress(GXPrimitiveType type, GXVertex[] Vertices, HSD_AttributeGroup Attributes) { GXPrimitiveGroup g = new GXPrimitiveGroup(); g.PrimitiveType = type; g.Indices = new GXIndexGroup[Vertices.Length]; int IndexGroupIndex = 0; foreach (GXVertex v in Vertices) { GXIndexGroup ig = new GXIndexGroup(); ig.Indices = new ushort[Attributes.Attributes.Count]; int i = 0; foreach (GXVertexBuffer b in Attributes.Attributes) { switch (b.AttributeType) { case GXAttribType.GX_DIRECT: if (b.Name == GXAttribName.GX_VA_CLR0) { ig.Clr0 = new byte[] { (byte)(v.Clr0.R * 0xFF), (byte)(v.Clr0.G * 0xFF), (byte)(v.Clr0.B * 0xFF), (byte)(v.Clr0.A * 0xFF) } } ; else if (b.Name == GXAttribName.GX_VA_CLR1) { ig.Clr1 = new byte[] { (byte)(v.Clr1.R * 0xFF), (byte)(v.Clr1.G * 0xFF), (byte)(v.Clr1.B * 0xFF), (byte)(v.Clr1.A * 0xFF) } } ; else if (b.Name == GXAttribName.GX_VA_PNMTXIDX) { ig.Indices[i] = v.PMXID; } if (b.Name == GXAttribName.GX_VA_TEX0MTXIDX) { ig.Indices[i] = v.TEX0MTXIDX; } break; default: switch (b.Name) { case GXAttribName.GX_VA_POS: ig.Indices[i] = GetIndex(b, v.Pos); break; case GXAttribName.GX_VA_NRM: ig.Indices[i] = GetIndex(b, v.Nrm); break; case GXAttribName.GX_VA_TEX0: ig.Indices[i] = GetIndex(b, v.TEX0); break; case GXAttribName.GX_VA_TEX1: ig.Indices[i] = GetIndex(b, v.TEX1); break; case GXAttribName.GX_VA_CLR0: ig.Indices[i] = GetIndex(b, v.Clr0); break; default: throw new Exception("Error Building " + b.Name); } break; } i++; } g.Indices[IndexGroupIndex++] = ig; } return(g); }