private int tmdPrimitivePacketHashCode(TMDPrimitivePacket p) { var hashCode = p.Flags.GetHashCode(); hashCode = (hashCode * 397) ^ p.Options.GetHashCode(); hashCode = (hashCode * 397) ^ p.Type.GetHashCode(); hashCode = (hashCode * 397) ^ p.ILen.GetHashCode(); hashCode = (hashCode * 397) ^ p.OLen.GetHashCode(); hashCode = (hashCode * 397) ^ p.PacketData.Vertices.Length.GetHashCode(); ITMDColoredPrimitivePacket coloredPacket = p.PacketData as ITMDColoredPrimitivePacket; if (coloredPacket != null) { foreach (var col in coloredPacket.Colors) { hashCode = (hashCode * 397) ^ col.GetHashCode(); } } ITMDLitPrimitivePacket litPacket = p.PacketData as ITMDLitPrimitivePacket; if (litPacket != null) { foreach (var norm in litPacket.Normals) { hashCode = (hashCode * 397) ^ norm.GetHashCode(); } } ITMDTexturedPrimitivePacket texturedPacket = p.PacketData as ITMDTexturedPrimitivePacket; if (texturedPacket != null) { hashCode = (hashCode * 397) ^ texturedPacket.Texture.TexturePageNumber.GetHashCode(); hashCode = (hashCode * 397) ^ texturedPacket.Texture.ColorMode.GetHashCode(); hashCode = (hashCode * 397) ^ texturedPacket.Texture.AlphaBlendRate.GetHashCode(); hashCode = (hashCode * 397) ^ texturedPacket.ColorLookup.XPosition.GetHashCode(); hashCode = (hashCode * 397) ^ texturedPacket.ColorLookup.YPosition.GetHashCode(); foreach (var uv in texturedPacket.UVs) { hashCode = (hashCode * 397) ^ uv.GetHashCode(); } } foreach (var vert in p.PacketData.Vertices) { hashCode = (hashCode * 397) ^ vert.GetHashCode(); } hashCode = (hashCode * 397) ^ p.SpriteSize.GetHashCode(); return(hashCode); }
public Mesh CreateTMDObjectMesh(TMDObject obj) { clearCachedLists(); foreach (var prim in obj.Primitives) { // currently only polygon primitives are supported if (prim.Type != TMDPrimitivePacket.Types.POLYGON) { continue; } // figure out which index list to use based on whether or not this primitive is alpha blended List <int> indicesList = (prim.Options & TMDPrimitivePacket.OptionsFlags.AlphaBlended) != 0 ? _alphaBlendIndices : _indices; // get interfaces for different packet types from LibLSD ITMDPrimitivePacket primitivePacket = prim.PacketData; ITMDTexturedPrimitivePacket texturedPrimitivePacket = prim.PacketData as ITMDTexturedPrimitivePacket; ITMDColoredPrimitivePacket coloredPrimitivePacket = prim.PacketData as ITMDColoredPrimitivePacket; ITMDLitPrimitivePacket litPrimitivePacket = prim.PacketData as ITMDLitPrimitivePacket; for (int i = 0; i < primitivePacket.Vertices.Length; i++) { // get index into vertices array int vertIndex = primitivePacket.Vertices[i]; _packetIndices[i] = _verts.Count; // create variables for each of the vertex types Vec3 vertPos = obj.Vertices[vertIndex]; Vector3 vec3VertPos = new Vector3(vertPos.X, -vertPos.Y, vertPos.Z) / 2048f; Color32 vertCol = Color.white; Vector3 vertNorm = Vector3.zero; Vector2 vertUV = Vector2.one; // handle packet colour if (coloredPrimitivePacket != null) { Vec3 packetVertCol = coloredPrimitivePacket.Colors[coloredPrimitivePacket.Colors.Length > 1 ? i : 0]; vertCol = new Color(packetVertCol.X, packetVertCol.Y, packetVertCol.Z); if (vertCol.r > 0 && vertCol.g > 0 && vertCol.b > 0 && (prim.Options & TMDPrimitivePacket.OptionsFlags.Textured) == 0 && (prim.Options & TMDPrimitivePacket.OptionsFlags.AlphaBlended) != 0) { vertCol.a = 127; } } // handle packet normals if (litPrimitivePacket != null) { TMDNormal packetVertNorm = obj.Normals[litPrimitivePacket.Normals[litPrimitivePacket.Normals.Length > 1 ? i : 0]]; vertNorm = new Vector3(packetVertNorm.X, packetVertNorm.Y, packetVertNorm.Z); } // handle packet UVs if (texturedPrimitivePacket != null) { // calculate which texture page we're on int texPage = texturedPrimitivePacket.Texture.TexturePageNumber; int texPageXPos = ((texPage % 16) * 128) - 640; int texPageYPos = texPage < 16 ? 256 : 0; // derive UV coords from the texture page int uvIndex = i * 2; int vramXPos = texPageXPos + texturedPrimitivePacket.UVs[uvIndex]; int vramYPos = texPageYPos + (256 - texturedPrimitivePacket.UVs[uvIndex + 1]); float uCoord = vramXPos / (float)PsxVram.VRAM_WIDTH; float vCoord = vramYPos / (float)PsxVram.VRAM_HEIGHT; vertUV = new Vector2(uCoord, vCoord); // check for overlapping UVs and fix them slightly foreach (var uv in _uvs) { if (uv.Equals(vertUV)) { vertUV += new Vector2(0.0001f, 0.0001f); } } } // add all computed aspects of vertex to lists _verts.Add(vec3VertPos); _normals.Add(vertNorm); _colors.Add(vertCol); _uvs.Add(vertUV); } // we want to add extra indices if this primitive is a quad (to triangulate) bool isQuad = (prim.Options & TMDPrimitivePacket.OptionsFlags.Quad) != 0; _polyIndices.Add(_packetIndices[0]); _polyIndices.Add(_packetIndices[1]); _polyIndices.Add(_packetIndices[2]); if (isQuad) { _polyIndices.Add(_packetIndices[2]); _polyIndices.Add(_packetIndices[1]); _polyIndices.Add(_packetIndices[3]); } // if this primitive is double sided we want to add more vertices with opposite winding order if ((prim.Flags & TMDPrimitivePacket.PrimitiveFlags.DoubleSided) != 0) { _polyIndices.Add(_packetIndices[1]); _polyIndices.Add(_packetIndices[0]); _polyIndices.Add(_packetIndices[2]); if (isQuad) { _polyIndices.Add(_packetIndices[1]); _polyIndices.Add(_packetIndices[2]); _polyIndices.Add(_packetIndices[3]); } } // add the indices to the list indicesList.AddRange(_polyIndices); _polyIndices.Clear(); } Mesh result = new Mesh(); result.SetVertices(_verts); result.SetNormals(_normals); result.SetColors(_colors); result.SetUVs(0, _uvs); // regular mesh if (_indices.Count >= 3) { result.SetTriangles(_indices, 0, false, 0); } // alpha blended mesh if (_alphaBlendIndices.Count >= 3) { result.subMeshCount = 2; result.SetTriangles(_alphaBlendIndices, 1, false, 0); } return(result); }
public static string TMDToOBJFile(TMD tmd) { int vertCount = (int)tmd.NumberOfVertices; int faceCount = 0; foreach (var tmdObj in tmd.ObjectTable) { faceCount += (int)tmdObj.NumPrimitives; } ObjBuilder obj = new ObjBuilder(); WriteOBJHeader(obj, vertCount, faceCount); List <Vector3> positions = new List <Vector3>(); List <Vector3> normals = new List <Vector3>(); foreach (var tmdObj in tmd.ObjectTable) { foreach (Vec3 vert in tmdObj.Vertices) { positions.Add(new Vector3(vert.X, vert.Y, vert.Z)); } foreach (var norm in tmdObj.Normals) { normals.Add(new Vector3(norm.X, norm.Y, norm.Z)); } } foreach (var pos in positions) { obj.Vertex(pos); } foreach (var norm in normals) { obj.Normal(norm); } foreach (var tmdObj in tmd.ObjectTable) { foreach (var prim in tmdObj.Primitives) { if (prim.PacketData is ITMDTexturedPrimitivePacket uvPacket) { for (int j = 0; j < prim.PacketData.Vertices.Length; j++) { int uvIndex = j * 2; obj.UV(new Vector2(uvPacket.UVs[uvIndex] / 255f, uvPacket.UVs[uvIndex + 1] / 255f)); } } } } ObjFaceBuilder faceBuilder = new ObjFaceBuilder(); int indexBase = 0; for (int i = 0; i < tmd.Header.NumObjects; i++) { obj.Group($"Object {i}"); int uvCount = 1; foreach (var prim in tmd.ObjectTable[i].Primitives) { ITMDPrimitivePacket primitivePacket = prim.PacketData; ITMDTexturedPrimitivePacket texPrimitivePacket = prim.PacketData as ITMDTexturedPrimitivePacket; ITMDLitPrimitivePacket litPrimitivePacket = prim.PacketData as ITMDLitPrimitivePacket; int[] indices = new int[primitivePacket.Vertices.Length]; for (int vert = 0; vert < primitivePacket.Vertices.Length; vert++) { int v = indexBase + primitivePacket.Vertices[vert] + 1; indices[vert] = v; int?t = null; int?n = null; if (texPrimitivePacket != null) { t = uvCount++; } if (litPrimitivePacket != null) { n = litPrimitivePacket.Normals[vert] + 1; } } bool isQuad = (prim.Options & TMDPrimitivePacket.OptionsFlags.Quad) != 0; bool isDoubleSided = (prim.Flags & TMDPrimitivePacket.PrimitiveFlags.DoubleSided) != 0; faceBuilder.Vertex(indices[1]); faceBuilder.Vertex(indices[0]); faceBuilder.Vertex(indices[2]); obj.Face(faceBuilder.Build()); faceBuilder.Clear(); if (isQuad) { faceBuilder.Vertex(indices[1]); faceBuilder.Vertex(indices[2]); faceBuilder.Vertex(indices[3]); obj.Face(faceBuilder.Build()); faceBuilder.Clear(); } if (isDoubleSided) { obj.Comment("DOuble sided!"); faceBuilder.Vertex(indices[0]); faceBuilder.Vertex(indices[1]); faceBuilder.Vertex(indices[2]); obj.Face(faceBuilder.Build()); faceBuilder.Clear(); if (isQuad) { faceBuilder.Vertex(indices[2]); faceBuilder.Vertex(indices[1]); faceBuilder.Vertex(indices[3]); obj.Face(faceBuilder.Build()); faceBuilder.Clear(); } } faceBuilder.Clear(); } indexBase += tmd.ObjectTable[i].Vertices.Length; } return(obj.ToString()); }
public static IRenderable MeshFromTMDObject(TMDObject obj, Shader shader, bool headless) { Vec3[] verts = new Vec3[obj.NumVertices]; List <Vertex> vertList = new List <Vertex>(); List <int> indices = new List <int>(); for (int i = 0; i < obj.NumVertices; i++) { verts[i] = obj.Vertices[i] / 2048f; } foreach (var prim in obj.Primitives) { if (prim.Type != TMDPrimitivePacket.Types.POLYGON) { continue; } ITMDPrimitivePacket primitivePacket = prim.PacketData; ITMDTexturedPrimitivePacket texPrimitivePacket = prim.PacketData as ITMDTexturedPrimitivePacket; ITMDColoredPrimitivePacket colPrimitivePacket = prim.PacketData as ITMDColoredPrimitivePacket; ITMDLitPrimitivePacket litPrimitivePacket = prim.PacketData as ITMDLitPrimitivePacket; List <int> polyIndices = new List <int>(); int[] packetIndices = new int[primitivePacket.Vertices.Length]; for (int i = 0; i < primitivePacket.Vertices.Length; i++) { int vertIndex = primitivePacket.Vertices[i]; packetIndices[i] = vertList.Count; Vector3 vertPos = new Vector3(verts[vertIndex].X, verts[vertIndex].Y, verts[vertIndex].Z); Vector4 vertCol = Vector4.One; Vector3 vertNorm = Vector3.Zero; Vector2 vertUV = Vector2.One; // handle packet colour if (colPrimitivePacket != null) { Vec3 packetVertCol = colPrimitivePacket.Colors[colPrimitivePacket.Colors.Length > 1 ? i : 0]; vertCol = new Vector4(packetVertCol.X, packetVertCol.Y, packetVertCol.Z, 1f); } // handle packet normals if (litPrimitivePacket != null) { TMDNormal packetVertNorm = obj.Normals[ litPrimitivePacket.Normals[litPrimitivePacket.Normals.Length > 1 ? i : 0]]; vertNorm = new Vector3(packetVertNorm.X, packetVertNorm.Y, packetVertNorm.Z); } // handle packet UVs if (texPrimitivePacket != null) { int texPage = texPrimitivePacket.Texture.TexturePageNumber; // the PSX VRAM is split into 32 texture pages, 16 on top and 16 on bottom // a texture page is 128x256 pixels large // pages 0-4 and 16-20 are used as a double buffer, with a width of 640 (5*128) // the X position of the texture page is it's row index multiplied by the width // we're also subtracting the width of the double buffer as we don't want to include // it - we're using modern graphics so this will be on the video card! int texPageXPos = ((texPage % TEX_PAGE_PER_ROW) * TEX_PAGE_WIDTH) - DOUBLE_BUFFER_WIDTH; // more simply, the Y position of the texture page is 0 or the height of a texture page, based // on if the texture page number is on the 2nd row of pages or not int texPageYPos = texPage <= TEX_PAGE_PER_ROW ? TEX_PAGE_HEIGHT : 0; int uvIndex = i * 2; // 2 UVs per vertex // the UV information we get from the TMD model is UVs into a specific texture page, using // only that texture page's coordinate system // here, we're adding the texture page position offsets (into VRAM) to the TMD UVs, transforming // them into the VRAM coordinate space int vramXPos = texPageXPos + texPrimitivePacket.UVs[uvIndex]; // we're subtracting from the texture page height for the Y position as OpenGL uses flipped // Y coordinates for textures compared with the PSX int vramYPos = texPageYPos + (TEX_PAGE_HEIGHT - texPrimitivePacket.UVs[uvIndex + 1]); // finally, we're normalizing the UVs from pixels float uCoord = vramXPos / (float)VRAM_WIDTH; float vCoord = vramYPos / (float)VRAM_HEIGHT; vertUV = new Vector2(uCoord, vCoord); } vertList.Add(new Vertex(vertPos, vertNorm, vertUV, vertCol)); } bool isQuad = (prim.Options & TMDPrimitivePacket.OptionsFlags.Quad) != 0; polyIndices.Add(packetIndices[1]); polyIndices.Add(packetIndices[0]); polyIndices.Add(packetIndices[2]); if (isQuad) { polyIndices.Add(packetIndices[1]); polyIndices.Add(packetIndices[2]); polyIndices.Add(packetIndices[3]); } // if primitive is double sided poly we need to add other side with reverse winding if ((prim.Flags & TMDPrimitivePacket.PrimitiveFlags.DoubleSided) != 0) { polyIndices.Add(packetIndices[0]); polyIndices.Add(packetIndices[1]); polyIndices.Add(packetIndices[2]); if (isQuad) { polyIndices.Add(packetIndices[2]); polyIndices.Add(packetIndices[1]); polyIndices.Add(packetIndices[3]); } } indices.AddRange(polyIndices); } return(headless ? (IRenderable) new HeadlessMesh(vertList.ToArray(), indices.ToArray()) : (IRenderable) new Mesh(vertList.ToArray(), indices.ToArray(), shader)); }
public static IRenderable MeshFromTMDObject(TMDObject obj, Shader shader) { Vec3[] verts = new Vec3[obj.NumVertices]; List <Vertex> vertList = new List <Vertex>(); List <int> indices = new List <int>(); for (int i = 0; i < obj.NumVertices; i++) { verts[i] = obj.Vertices[i] / 2048f; } foreach (var prim in obj.Primitives) { if (prim.Type != TMDPrimitivePacket.Types.POLYGON) { continue; } ITMDPrimitivePacket primitivePacket = prim.PacketData; ITMDTexturedPrimitivePacket texPrimitivePacket = prim.PacketData as ITMDTexturedPrimitivePacket; ITMDColoredPrimitivePacket colPrimitivePacket = prim.PacketData as ITMDColoredPrimitivePacket; ITMDLitPrimitivePacket litPrimitivePacket = prim.PacketData as ITMDLitPrimitivePacket; List <int> polyIndices = new List <int>(); int[] packetIndices = new int[primitivePacket.Vertices.Length]; for (int i = 0; i < primitivePacket.Vertices.Length; i++) { int vertIndex = primitivePacket.Vertices[i]; packetIndices[i] = vertList.Count; Vector3 vertPos = new Vector3(verts[vertIndex].X, verts[vertIndex].Y, verts[vertIndex].Z); Vector4 vertCol = Vector4.One; Vector3 vertNorm = Vector3.Zero; Vector2 vertUV = Vector2.One; // handle packet colour if (colPrimitivePacket != null) { Vec3 packetVertCol = colPrimitivePacket.Colors[colPrimitivePacket.Colors.Length > 1 ? i : 0]; vertCol = new Vector4(packetVertCol.X, packetVertCol.Y, packetVertCol.Z, 1f); } // handle packet normals if (litPrimitivePacket != null) { TMDNormal packetVertNorm = obj.Normals[ litPrimitivePacket.Normals[litPrimitivePacket.Normals.Length > 1 ? i : 0]]; vertNorm = new Vector3(packetVertNorm.X, packetVertNorm.Y, packetVertNorm.Z); } // handle packet UVs if (texPrimitivePacket != null) { int texPage = texPrimitivePacket.Texture.TexturePageNumber; int texPageXPos = ((texPage % 16) * 128) - 640; int texPageYPos = texPage < 16 ? 256 : 0; int uvIndex = i * 2; int vramXPos = texPageXPos + texPrimitivePacket.UVs[uvIndex]; int vramYPos = texPageYPos + (256 - texPrimitivePacket.UVs[uvIndex + 1]); float uCoord = vramXPos / (float)VRAMController.VRAM_WIDTH; float vCoord = vramYPos / (float)VRAMController.VRAM_HEIGHT; vertUV = new Vector2(uCoord, vCoord); } vertList.Add(new Vertex(vertPos, vertNorm, vertUV, vertCol)); } bool isQuad = (prim.Options & TMDPrimitivePacket.OptionsFlags.Quad) != 0; polyIndices.Add(packetIndices[1]); polyIndices.Add(packetIndices[0]); polyIndices.Add(packetIndices[2]); if (isQuad) { polyIndices.Add(packetIndices[1]); polyIndices.Add(packetIndices[2]); polyIndices.Add(packetIndices[3]); } // if primitive is double sided poly we need to add other side with reverse winding if ((prim.Flags & TMDPrimitivePacket.PrimitiveFlags.DoubleSided) != 0) { polyIndices.Add(packetIndices[0]); polyIndices.Add(packetIndices[1]); polyIndices.Add(packetIndices[2]); if (isQuad) { polyIndices.Add(packetIndices[2]); polyIndices.Add(packetIndices[1]); polyIndices.Add(packetIndices[3]); } } indices.AddRange(polyIndices); } Mesh toReturn = new Mesh(vertList.ToArray(), indices.ToArray(), shader); return(toReturn); }
/// <summary> /// Create a mesh from an object stored inside a TMD model file. /// </summary> /// <param name="obj">The TMD object to create a mesh from.</param> /// <returns>The Mesh created from the object.</returns> public static Mesh MeshFromTMDObject(TMDObject obj) { // create the mesh, and lists of vertices, normals, colors, uvs, and indices Mesh result = new Mesh(); List <Vector3> verts = new List <Vector3>(); List <Vector3> normals = new List <Vector3>(); List <Color32> colors = new List <Color32>(); List <Vector2> uvs = new List <Vector2>(); List <int> indices = new List <int>(); List <int> alphaBlendIndices = new List <int>(); // alpha blended polygons are stored in a submesh // TMD objects are built from 'primitives' foreach (var prim in obj.Primitives) { // currently only polygon primitives are supported if (prim.Type != TMDPrimitivePacket.Types.POLYGON) { continue; } // check which index list to use based on whether this primitive is alpha blended or not List <int> indicesList = (prim.Options & TMDPrimitivePacket.OptionsFlags.AlphaBlended) != 0 ? alphaBlendIndices : indices; // get interfaces for different packet types from LibLSD ITMDPrimitivePacket primitivePacket = prim.PacketData; ITMDTexturedPrimitivePacket texturedPrimitivePacket = prim.PacketData as ITMDTexturedPrimitivePacket; ITMDColoredPrimitivePacket coloredPrimitivePacket = prim.PacketData as ITMDColoredPrimitivePacket; ITMDLitPrimitivePacket litPrimitivePacket = prim.PacketData as ITMDLitPrimitivePacket; // for each vertex in the primitive List <int> polyIndices = new List <int>(); int[] packetIndices = new int[primitivePacket.Vertices.Length]; for (int i = 0; i < primitivePacket.Vertices.Length; i++) { // get its index into the vertices array int vertIndex = primitivePacket.Vertices[i]; packetIndices[i] = verts.Count; // create variables for each of the vertex types Vec3 vertPos = obj.Vertices[vertIndex]; Vector3 vec3VertPos = new Vector3(vertPos.X, -vertPos.Y, vertPos.Z) / 2048f; Color32 vertCol = Color.white; Vector3 vertNorm = Vector3.zero; Vector2 vertUV = Vector2.one; // handle packet colour if (coloredPrimitivePacket != null) { Vec3 packetVertCol = coloredPrimitivePacket.Colors[coloredPrimitivePacket.Colors.Length > 1 ? i : 0]; vertCol = new Color(packetVertCol.X, packetVertCol.Y, packetVertCol.Z); if (vertCol.r > 0 && vertCol.g > 0 && vertCol.b > 0 && (prim.Options & TMDPrimitivePacket.OptionsFlags.Textured) == 0 && (prim.Options & TMDPrimitivePacket.OptionsFlags.AlphaBlended) != 0) { vertCol.a = 127; } } // handle packet normals if (litPrimitivePacket != null) { TMDNormal packetVertNorm = obj.Normals[litPrimitivePacket.Normals[litPrimitivePacket.Normals.Length > 1 ? i : 0]]; vertNorm = new Vector3(packetVertNorm.X, packetVertNorm.Y, packetVertNorm.Z); } // handle packet UVs if (texturedPrimitivePacket != null) { // calculate which texture page we're on int texPage = texturedPrimitivePacket.Texture.TexturePageNumber; int texPageXPos = ((texPage % 16) * 128) - 640; int texPageYPos = texPage < 16 ? 256 : 0; // derive UV coords from the texture page int uvIndex = i * 2; int vramXPos = texPageXPos + texturedPrimitivePacket.UVs[uvIndex]; int vramYPos = texPageYPos + (256 - texturedPrimitivePacket.UVs[uvIndex + 1]); float uCoord = (vramXPos + 0.5f) / PsxVram.VRAM_WIDTH; // half-texel correction to prevent bleeding float vCoord = (vramYPos - 0.5f) / PsxVram.VRAM_HEIGHT; // half-texel correction to prevent bleeding vertUV = new Vector2(uCoord, vCoord); } // add all computed aspects of vertex to lists verts.Add(vec3VertPos); normals.Add(vertNorm); colors.Add(vertCol); uvs.Add(vertUV); } // we want to add extra indices if this primitive is a quad (to triangulate) bool isQuad = (prim.Options & TMDPrimitivePacket.OptionsFlags.Quad) != 0; polyIndices.Add(packetIndices[0]); polyIndices.Add(packetIndices[1]); polyIndices.Add(packetIndices[2]); if (isQuad) { polyIndices.Add(packetIndices[2]); polyIndices.Add(packetIndices[1]); polyIndices.Add(packetIndices[3]); } // if this primitive is double sided we want to add more vertices with opposite winding order if ((prim.Flags & TMDPrimitivePacket.PrimitiveFlags.DoubleSided) != 0) { polyIndices.Add(packetIndices[1]); polyIndices.Add(packetIndices[0]); polyIndices.Add(packetIndices[2]); if (isQuad) { polyIndices.Add(packetIndices[1]); polyIndices.Add(packetIndices[2]); polyIndices.Add(packetIndices[3]); } } // add the indices to the list indicesList.AddRange(polyIndices); } // set the mesh arrays result.vertices = verts.ToArray(); result.normals = normals.ToArray(); result.colors32 = colors.ToArray(); result.uv = uvs.ToArray(); // regular mesh if (indices.Count >= 3) { result.SetTriangles(indices, 0, false, 0); } // alpha blended mesh if (alphaBlendIndices.Count >= 3) { result.subMeshCount = 2; result.SetTriangles(alphaBlendIndices, 1, false, 0); } return(result); }