/// <summary> /// Replaces skinning indices of 65535 (ushort.max) with the proper indices from previous groups. /// </summary> private void FixGroupSkinningIndices() { foreach (Shape s in Shapes) { for (int i = 0; i < s.MatrixGroups.Count; i++) { MatrixGroup cur_group = s.MatrixGroups[i]; for (int j = 0; j < cur_group.MatrixDataTable.MatrixTable.Count; j++) { ushort cur_index = cur_group.MatrixDataTable.MatrixTable[j]; if (cur_index == ushort.MaxValue) { for (int k = i - 1; k > -1; k--) { ushort last_index = s.MatrixGroups[k].MatrixDataTable.MatrixTable[j]; if (last_index != ushort.MaxValue) { cur_group.MatrixDataTable.MatrixTable[j] = last_index; break; } } } } } } }
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(); }