public static List<Chunk> ReadVerts(ILogger log, byte[] fileData, int offset, int endOffset) { var chunks = new List<Chunk>(); Chunk currentChunk = new Chunk(); Chunk previousChunk = null; while (offset < endOffset) { int vifCommand = fileData[offset + 3] & 0x7f; int numCommand = fileData[offset + 2] & 0xff; int immCommand = DataUtil.getLEShort(fileData, offset); switch (vifCommand) { case NOP_CMD: DebugWrite(HexUtil.formatHex(offset) + " "); DebugWriteLine("NOP"); offset += 4; break; case STCYCL_CMD: DebugWrite(HexUtil.formatHex(offset) + " "); DebugWriteLine("STCYCL: WL: " + (immCommand >> 8) + " CL: " + (immCommand & 0xFF)); offset += 4; break; case ITOP_CMD: DebugWrite(HexUtil.formatHex(offset) + " "); DebugWriteLine("ITOP: " + immCommand); offset += 4; break; case STMOD_CMD: DebugWrite(HexUtil.formatHex(offset) + " "); DebugWriteLine("STMOD: " + immCommand); offset += 4; break; case MSCAL_CMD: DebugWrite(HexUtil.formatHex(offset) + " "); DebugWriteLine("MSCAL: " + immCommand); if (immCommand != 66 && immCommand != 68 && immCommand != 70) { DebugWriteLine("**** Microcode " + immCommand + " not supported"); } currentChunk.mscalID = immCommand; chunks.Add(currentChunk); previousChunk = currentChunk; currentChunk = new Chunk(); offset += 4; break; case STMASK_CMD: DebugWrite(HexUtil.formatHex(offset) + " "); offset += 4; int stmask = DataUtil.getLEInt(fileData, offset); DebugWriteLine("STMASK: " + stmask); offset += 4; break; case FLUSH_CMD: DebugWrite(HexUtil.formatHex(offset) + " "); DebugWriteLine("FLUSH"); offset += 4; break; case DIRECT_CMD: DebugWrite(HexUtil.formatHex(offset) + " "); DebugWriteLine("DIRECT, " + immCommand*16 + " bytes"); GIFTag[] tags = new GIFTag[immCommand]; for (int i = 0; i < immCommand; i++) { tags[i] = new GIFTag(); tags[i].parse(fileData, offset + 4 + i*16); } currentChunk.DIRECTGifTags.AddRange(tags); offset += 4; offset += immCommand * 16; break; default: if ((vifCommand & 0x60) == 0x60) { // unpack command bool mask = ((vifCommand & 0x10) == 0x10); int vn = (vifCommand >> 2) & 3; int vl = vifCommand & 3; int addr = immCommand & 0x1ff; bool flag = (immCommand & 0x8000) == 0x8000; bool usn = (immCommand & 0x4000) == 0x4000; DebugWrite(HexUtil.formatHex(offset) + " "); String debugMsg = "UNPACK: vn: " + vn + ", vl: " + vl + ", Addr: " + addr + ", num: " + numCommand; if (flag) { debugMsg += ", Flag"; } if (usn) { debugMsg += ", Unsigned"; } if (mask) { debugMsg += ", Mask"; } DebugWriteLine(debugMsg); offset += 4; if (vn == 1 && vl == 1) { // v2-16 // I don't know why but the UVs come after the MSCAL instruction. if (previousChunk != null) { for (int uvnum = 0; uvnum < numCommand; ++uvnum) { short u = DataUtil.getLEShort(fileData, offset); short v = DataUtil.getLEShort(fileData, offset + 2); previousChunk.uvs.Add(new UV(u, v)); offset += 4; } } else { int numBytes = numCommand * 4; offset += numBytes; } } else if (vn == 2 && vl == 1) { // v3-16 // each vertex is 128 bits, so num is the number of vertices for (int vnum = 0; vnum < numCommand; ++vnum) { if (!usn) { short x = DataUtil.getLEShort(fileData, offset); short y = DataUtil.getLEShort(fileData, offset + 2); short z = DataUtil.getLEShort(fileData, offset + 4); offset += 6; Vertex vertex = new Vertex(); vertex.x = x; vertex.y = y; vertex.z = z; currentChunk.vertices.Add(vertex); } else { int x = DataUtil.getLEUShort(fileData, offset); int y = DataUtil.getLEUShort(fileData, offset + 2); int z = DataUtil.getLEUShort(fileData, offset + 4); offset += 6; VLoc vloc = new VLoc(); vloc.v1 = x; vloc.v2 = y; vloc.v3 = z; currentChunk.vlocs.Add(vloc); } } offset = (offset + 3) & ~3; } else if (vn == 2 && vl == 2) { // v3-8 int idx = offset; for (int vnum = 0; vnum < numCommand; ++vnum) { SByteVector vec = new SByteVector(); vec.x = (sbyte)fileData[idx++]; vec.y = (sbyte)fileData[idx++]; vec.z = (sbyte)fileData[idx++]; currentChunk.normals.Add(vec); } int numBytes = ((numCommand * 3) + 3) & ~3; offset += numBytes; } else if (vn == 3 && vl == 0) { // v4-32 log.LogLine("v4-32 data, " + numCommand + (numCommand == 1 ? " entry" : " entries") + ", addr=" + addr); if (1 == numCommand) { currentChunk.gifTag0 = new GIFTag(); currentChunk.gifTag0.parse(fileData, offset); DebugWrite(HexUtil.formatHex(offset) + " "); DebugWriteLine("GifTag: " + currentChunk.gifTag0.ToString()); } else if (2 == numCommand) { currentChunk.gifTag0 = new GIFTag(); currentChunk.gifTag0.parse(fileData, offset); currentChunk.gifTag1 = new GIFTag(); currentChunk.gifTag1.parse(fileData, offset + 16); DebugWrite(HexUtil.formatHex(offset) + " "); DebugWriteLine("GifTag0: " + currentChunk.gifTag0.ToString()); DebugWrite(HexUtil.formatHex(offset) + " "); DebugWriteLine("GifTag1: " + currentChunk.gifTag1.ToString()); } else { log.LogLine("unknown number of gif commands."); } int numBytes = numCommand * 16; offset += numBytes; } else if (vn == 3 && vl == 1) { // v4-16 log.LogLine("v4-16 data, " + numCommand + (numCommand == 1 ? " entry" : " entries") + ", addr=" + addr); int numShorts = numCommand * 4; if (usn) { currentChunk.extraVlocs = new ushort[numShorts]; for (int i = 0; i < numCommand; ++i) { currentChunk.extraVlocs[i*4] = DataUtil.getLEUShort(fileData, offset + i * 8); currentChunk.extraVlocs[i * 4 + 1] = DataUtil.getLEUShort(fileData, offset + i * 8 + 2); currentChunk.extraVlocs[i * 4 + 2] = DataUtil.getLEUShort(fileData, offset + i * 8 + 4); currentChunk.extraVlocs[i * 4 + 3] = DataUtil.getLEUShort(fileData, offset + i * 8 + 6); } } else { log.LogLine("Unsupported tag"); } offset += numShorts * 2; } else if (vn == 3 && vl == 2) { // v4-8 int numBytes = numCommand * 4; currentChunk.vertexWeights = new List<VertexWeight>(); int curVertex=0; for (int i = 0; i < numCommand; ++i) { VertexWeight vw = new VertexWeight(); vw.startVertex = curVertex; vw.bone1 = fileData[offset++] / 4; vw.boneWeight1 = fileData[offset++]; vw.bone2 = fileData[offset++]; if (vw.bone2 == 0xFF) { // Single bone vw.boneWeight2 = 0; int count = fileData[offset++]; curVertex += count; } else { vw.bone2 /= 4; vw.boneWeight2 = fileData[offset++]; ++curVertex; if (vw.boneWeight1 + vw.boneWeight2 < 255) { ++i; vw.bone3 = fileData[offset++] / 4; vw.boneWeight3 = fileData[offset++]; vw.bone4 = fileData[offset++]; int bw4 = fileData[offset++]; if (vw.bone4 != 255) { vw.bone4 /= 4; vw.boneWeight4 = bw4; } } } vw.endVertex = curVertex - 1; currentChunk.vertexWeights.Add(vw); } } else { DebugWriteLine("Unknown vnvl combination: vn=" + vn + ", vl=" + vl); offset = endOffset; } } else { DebugWriteLine("Unknown command: " + vifCommand); offset = endOffset; } break; } } return chunks; }
public static Model3D CreateModel3D(List<Mesh> meshGroups, BitmapSource texture, AnimData pose, int frame) { GeometryModel3D model = new GeometryModel3D(); var mesh3D = new MeshGeometry3D(); int numVertices = 0; foreach (var meshGroup in meshGroups) { numVertices += meshGroup.Positions.Count; } var triangleIndices = new Int32Collection(); var positions = new Point3DCollection(numVertices); var normals = new Vector3DCollection(numVertices); var uvCoords = new PointCollection(numVertices); int vstart = 0; foreach (var meshGroup in meshGroups) { Boolean hasVertexWeights = meshGroup.vertexWeights.Count > 0; int vwNum = 0; VertexWeight vw = new VertexWeight(); if (meshGroup.vertexWeights.Count > 0) { vw = meshGroup.vertexWeights[vwNum]; } int vnum = 0; foreach (var vertex in meshGroup.Positions) { var point = vertex; if (frame >= 0 && pose != null) { if (vw.endVertex < vnum) { ++vwNum; vw = meshGroup.vertexWeights[vwNum]; if (vnum < vw.startVertex || vnum > vw.endVertex) { Debug.Fail("Vertex " + vnum + " out of range of bone weights " + vw.startVertex + " -> " + vw.endVertex); } } int bone1No = vw.bone1; Point3D bindingPos1 = pose.bindingPose[bone1No]; AnimMeshPose bone1Pose = pose.perFrameFKPoses[frame, bone1No]; var joint1Pos = bone1Pose.Position; if (vw.bone2 == 0xFF) { if (bone1No == 1) { bone1No = 1; } Matrix3D m = Matrix3D.Identity; m.Translate(new Vector3D(-bindingPos1.X, -bindingPos1.Y, -bindingPos1.Z)); // Inverse binding matrix m.Rotate(bone1Pose.Rotation); m.Translate(new Vector3D(bone1Pose.Position.X, bone1Pose.Position.Y, bone1Pose.Position.Z)); point = m.Transform(point); } else { // multi-bone int bone2No = vw.bone2; Point3D bindingPos2 = pose.bindingPose[bone2No]; AnimMeshPose bone2Pose = pose.perFrameFKPoses[frame, bone2No]; double boneSum = vw.boneWeight1 + vw.boneWeight2; double bone1Coeff = vw.boneWeight1 / boneSum; double bone2Coeff = vw.boneWeight2 / boneSum; Matrix3D m = Matrix3D.Identity; m.Translate(new Vector3D(-bindingPos1.X, -bindingPos1.Y, -bindingPos1.Z)); // Inverse binding matrix m.Rotate(bone1Pose.Rotation); m.Translate(new Vector3D(bone1Pose.Position.X, bone1Pose.Position.Y, bone1Pose.Position.Z)); var point1 = m.Transform(point); // Now rotate Matrix3D m2 = Matrix3D.Identity; m2.Translate(new Vector3D(-bindingPos2.X, -bindingPos2.Y, -bindingPos2.Z)); // Inverse binding matrix m2.Rotate(bone2Pose.Rotation); m2.Translate(new Vector3D(bone2Pose.Position.X, bone2Pose.Position.Y, bone2Pose.Position.Z)); var point2 = m2.Transform(point); point = new Point3D(point1.X * bone1Coeff + point2.X * bone2Coeff, point1.Y * bone1Coeff + point2.Y * bone2Coeff, point1.Z * bone1Coeff + point2.Z * bone2Coeff); } } positions.Add(point); ++vnum; } foreach (var normal in meshGroup.Normals) { normals.Add(normal); } foreach (var ti in meshGroup.TriangleIndices) { triangleIndices.Add(ti+vstart); } foreach (var uv in meshGroup.TextureCoordinates) { uvCoords.Add(uv); } vstart += meshGroup.Positions.Count; } mesh3D.TriangleIndices = triangleIndices; mesh3D.Positions = positions; mesh3D.TextureCoordinates = uvCoords; mesh3D.Normals = normals; model.Geometry = mesh3D; var dm = new DiffuseMaterial(); if (texture != null && texture.Width > 0 && texture.Height > 0) { var ib = new ImageBrush(texture); ib.ViewportUnits = BrushMappingMode.Absolute; // May be needed at a later point //ib.TileMode = TileMode.Tile; dm.Brush = ib; } else { var dg = new DrawingGroup(); // Background dg.Children.Add(new GeometryDrawing() { Brush = new SolidColorBrush(Colors.Black), Geometry = new RectangleGeometry(new Rect(0, 0, 2, 2)) }); // Tiles dg.Children.Add(new GeometryDrawing() { Brush = new SolidColorBrush(Colors.Violet), Geometry = new RectangleGeometry(new Rect(0, 0, 1, 1)) }); dg.Children.Add(new GeometryDrawing() { Brush = new SolidColorBrush(Colors.Violet), Geometry = new RectangleGeometry(new Rect(1, 1, 1, 1)) }); dm.Brush = new DrawingBrush(dg){ TileMode = TileMode.Tile, Transform = new ScaleTransform(0.1,0.1)}; } model.Material = dm; return model; }