private static SceneNode LoadINF1FromFile(SceneNode rootNode, EndianBinaryReader reader, long chunkStart) { ushort unknown1 = reader.ReadUInt16(); // A lot of Link's models have it but no idea what it means. Alt. doc says: "0 for BDL, 01 for BMD" ushort padding = reader.ReadUInt16(); uint packetCount = reader.ReadUInt32(); // Total number of Packets across all Batches in file. uint vertexCount = reader.ReadUInt32(); // Total number of vertexes across all batches within the file. uint hierarchyDataOffset = reader.ReadUInt32(); // The Hierarchy defines how Joints, Materials and Shapes are laid out. This allows them to bind a material // and draw multiple shapes (batches) with the material. It also complicates drawing things, but whatever. reader.BaseStream.Position = chunkStart + hierarchyDataOffset; List<InfoNode> infoNodes = new List<InfoNode>(); InfoNode curNode = null; do { curNode = new InfoNode(); curNode.Type = (HierarchyDataTypes)reader.ReadUInt16(); curNode.Value = reader.ReadUInt16(); // "Index into Joint, Material, or Shape table. infoNodes.Add(curNode); } while (curNode.Type != HierarchyDataTypes.Finish); ConvertInfoHiearchyToSceneGraph(ref rootNode, infoNodes, 0); return rootNode; }
private static Envelopes LoadEVP1FromStream(EndianBinaryReader reader, long chunkStart) { Envelopes envelopes = new Envelopes(); ushort numEnvelopes = reader.ReadUInt16(); reader.ReadUInt16(); // Padding // numEnvelope many uint8 - each one describes how many bones belong to this index. uint boneCountOffset = reader.ReadUInt32(); // "sum over all bytes in boneCountOffset many shorts (index into some joint stuff? into matrix table?)" uint indexDataOffset = reader.ReadUInt32(); // Bone Weights (as many floats here as there are ushorts at indexDataOffset) uint weightOffset = reader.ReadUInt32(); // Matrix Table (3x4 float array) - Inverse Bind Pose uint boneMatrixOffset = reader.ReadUInt32(); // - Is this the number of bones which influence the vert? reader.BaseStream.Position = chunkStart + boneCountOffset; for (int b = 0; b < numEnvelopes; b++) envelopes.numBonesAffecting.Add(reader.ReadByte()); // ??? reader.BaseStream.Position = chunkStart + indexDataOffset; for (int m = 0; m < envelopes.numBonesAffecting.Count; m++) { for (int j = 0; j < envelopes.numBonesAffecting[m]; j++) { envelopes.indexRemap.Add(reader.ReadUInt16()); } } // Bone Weights reader.BaseStream.Position = chunkStart + weightOffset; for (int w = 0; w < envelopes.numBonesAffecting.Count; w++) { for (int j = 0; j < envelopes.numBonesAffecting[w]; j++) { envelopes.weights.Add(reader.ReadSingle()); } } // Inverse Bind Pose Matrices reader.BaseStream.Position = chunkStart + boneMatrixOffset; for (int w = 0; w < numEnvelopes; w++) { Matrix3x4 matrix = new Matrix3x4(); for (int j = 0; j < 3; j++) { for (int k = 0; k < 4; k++) matrix[j, k] = reader.ReadSingle(); } envelopes.inverseBindPose.Add(matrix); } return envelopes; }
private static DrawInfo LoadDRW1FromStream(EndianBinaryReader reader, long chunkStart) { DrawInfo drawInfo = new DrawInfo(); ushort sectionCount = reader.ReadUInt16(); Trace.Assert(reader.ReadUInt16() == 0xFFFF); // Padding - 0xFFFF uint isWeightedOffset = reader.ReadUInt32(); uint indexOffset = reader.ReadUInt32(); reader.BaseStream.Position = chunkStart + isWeightedOffset; for (int k = 0; k < sectionCount; k++) drawInfo.IsWeighted.Add(reader.ReadBoolean()); reader.BaseStream.Position = chunkStart + indexOffset; for (int k = 0; k < sectionCount; k++) drawInfo.Indexes.Add(reader.ReadUInt16()); return drawInfo; }
public static StringTable FromStream(EndianBinaryReader stream) { StringTable newTable = new StringTable(); long headerStart = stream.BaseStream.Position; ushort stringCount = stream.ReadUInt16(); stream.ReadUInt16(); // Padding for (int i = 0; i < stringCount; i++) { // Jump us to the string 'header' by calculating 0x4 for for the StringTable header and then 0x4 for each string header. stream.BaseStream.Position = headerStart + ((i + 1) * 0x4); ushort stringHash = stream.ReadUInt16(); ushort stringOffset = stream.ReadUInt16(); // Jump forward to the offset to read the string. stream.BaseStream.Position = headerStart + stringOffset; string value = stream.ReadStringUntil('\0'); newTable.Strings.Add(new Entry(value, stringHash)); } return newTable; }
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. } }
private static List<SkeletonBone> LoadJNT1SectionFromStream(EndianBinaryReader reader, long chunkStart) { List<SkeletonBone> skeletonBones = new List<SkeletonBone>(); ushort numJoints = reader.ReadUInt16(); ushort padding = reader.ReadUInt16(); uint jointDataOffset = reader.ReadUInt32(); // Relative to JNT1 Header Start uint jointRemapOffset = reader.ReadUInt32(); uint stringTableOffset = reader.ReadUInt32(); // Grab the joint names from file reader.BaseStream.Position = chunkStart + stringTableOffset; StringTable jointNames = StringTable.FromStream(reader); // This is always 0 to (numJoints-1) - unsure if it's a joint // remap (likely) or a string-table remap (less likely). It's unknown // if any models don't follow this 0 to (n-1) pattern. ushort[] jointRemaps = new ushort[numJoints]; reader.BaseStream.Position = chunkStart + jointRemapOffset; for (int j = 0; j < numJoints; j++) jointRemaps[j] = reader.ReadUInt16(); // Grab the actual joints reader.BaseStream.Position = chunkStart + jointDataOffset; for (int j = 0; j < numJoints; j++) { SkeletonBone bone = new SkeletonBone(); bone.Name = jointNames[j]; ushort unknown1 = reader.ReadUInt16(); // Values of 0, 2 or 1. yaz0r calls it Matrix Type (referring to 'MatrixTable' which is an index into DRW1 - Draw type? // If value is 1 or 2 then Bounding Box / Radius is Vector3.Zero / 0f // And seems to be 0 if a joint has direct non-joint children (Maybe 0 is a 'bone with children' and the BBMin/Max contain // the bounds of children? ushort unknown2 = reader.ReadUInt16(); // No one seems to know what it is, often 0xFF or 0 or 1. May be two individual bytes with a pad afterwards. for (int f = 0; f < 3; f++) bone.Scale[f] = reader.ReadSingle(); Vector3 eulerAngles = new Vector3(); for (int f = 0; f < 3; f++) eulerAngles[f] = (reader.ReadInt16() * (180 / 32786f)); Quaternion xAxis = Quaternion.FromAxisAngle(new Vector3(1, 0, 0), eulerAngles.X * MathE.Deg2Rad); Quaternion yAxis = Quaternion.FromAxisAngle(new Vector3(0, 1, 0), eulerAngles.Y * MathE.Deg2Rad); Quaternion zAxis = Quaternion.FromAxisAngle(new Vector3(0, 0, 1), eulerAngles.Z * MathE.Deg2Rad); // Swizzling to the ZYX order seems to be the right one. Quaternion finalRot = zAxis * yAxis * xAxis; bone.Rotation = finalRot; Trace.Assert(reader.ReadUInt16() == 0xFFFF); // Padding for (int f = 0; f < 3; f++) bone.Translation[f] = reader.ReadSingle(); bone.BoundingSphereDiameter = reader.ReadSingle(); for (int f = 0; f < 3; f++) bone.BoundingBoxMin[f] = reader.ReadSingle(); for (int f = 0; f < 3; f++) bone.BoundingBoxMax[f] = reader.ReadSingle(); skeletonBones.Add(bone); } return skeletonBones; }