I'm reverse engineering the GameCube game Zelda: Twilight Princess and recreating it in Unity. Specifically, I'm parsing binary files that contain data for meshes, textures, and materials to generate Unity GameObjects with meshes and materials.
My current implementation works, but it's kinda slow. One simple area takes about 100ms. When also creating all the npc's, objects and so on, it takes up to 2 seconds. When starting the game, I load all those binary files, while that my CPU usage is pretty low during the loading process (like 3% or something). I’m not sure how to structure the tasks better.
Here’s a simplified version of my code structure:
Load the binary file and parse the headers.
- For each "chunk" (tag) in the file:
- Decode the tag (e.g., vertices, textures, materials).
- Store the parsed data in corresponding objects (e.g., INF1, SHP1, etc.).
- After all chunks are parsed, create meshes and assign materials in Unity.
I hope somebody could help me!
Here’s the core of my current implementation (stripped down for brevity):
public class BMD : MonoBehaviour
{
public string FullBMD = "";
public INF1 INF1Tag;
public VTX1 VTX1Tag;
public EVP1 EVP1Tag;
public DRW1 DRW1Tag;
public JNT1 JNT1Tag;
public SHP1 SHP1Tag;
public MAT3 MAT3Tag;
public TEX1 TEX1Tag;
public Material DefaultMaterial;
// This method takes 90ms to execute, so a faster loading process is required
void Start()
{
using (EndianBinaryReader reader = new EndianBinaryReader(File.ReadAllBytes(FullBMD), Endian.Big))
{
reader.Skip(8);
int size = reader.ReadInt32();
int numChunks = reader.ReadInt32();
reader.Skip(16);
for (int i = 0; i < numChunks; i++)
{
long tagStart = reader.BaseStream.Position;
string tagName = reader.ReadString(4);
int tagSize = reader.ReadInt32();
switch (tagName)
{
case "INF1":
INF1Tag = new INF1();
INF1Tag.LoadINF1FromStream(reader, tagStart);
break;
case "VTX1":
VTX1Tag = new VTX1();
VTX1Tag.LoadVTX1FromStream(reader, tagStart, tagSize);
break;
case "EVP1":
EVP1Tag = new EVP1();
EVP1Tag.LoadEVP1FromStream(reader, tagStart);
break;
case "DRW1":
DRW1Tag = new DRW1();
DRW1Tag.LoadDRW1FromStream(reader, tagStart);
break;
case "JNT1":
JNT1Tag = new JNT1();
JNT1Tag.LoadJNT1FromStream(reader, tagStart);
JNT1Tag.CalculateParentJointsForSkeleton(INF1Tag.HierarchyRoot);
break;
case "SHP1":
SHP1Tag = new SHP1();
SHP1Tag.ReadSHP1FromStream(reader, tagStart, VTX1Tag.VertexData);
break;
case "MAT3":
MAT3Tag = new MAT3();
MAT3Tag.LoadMAT3FromStream(reader, tagStart);
break;
case "TEX1":
TEX1Tag = new TEX1();
TEX1Tag.LoadTEX1FromStream(this, reader, tagStart, new List<BTI>());
break;
case "MDL3":
break;
}
reader.BaseStream.Position = tagStart + tagSize;
}
// After loading data, create the meshes
CreateMeshes();
}
}
// This method takes 10ms to execute
private void CreateMeshes()
{
List<MeshFilter> meshFilters = new List<MeshFilter>();
List<MeshRenderer> meshRenderers = new List<MeshRenderer>();
List<Material> meshMaterials = new List<Material>();
List<GameObject> childs = new List<GameObject>();
foreach (SHP1.Shape shape in SHP1Tag.Shapes)
{
GameObject go = new GameObject("shape" + SHP1Tag.Shapes.IndexOf(shape));
childs.Add(go);
Material3 material = MAT3Tag.MaterialList[MAT3Tag.MaterialRemapTable[shape.MaterialIndex]];
BTI texture = TEX1Tag.BTIs[MAT3Tag.TextureRemapTable[baseTexture]];
List<Vector3> verts = shape.OverrideVertPos.Count > 0 ? shape.OverrideVertPos : shape.VertexData.Position;
List<Vector3> normals = shape.OverrideNormals.Count > 0 ? shape.OverrideNormals : shape.VertexData.Normal;
// Create mesh
Mesh mesh = new Mesh();
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
mesh.vertices = verts.ToArray();
int numVertices = verts.Count;
int numTriangles = numVertices / 3;
int[] triangles = new int[numTriangles * 3]; // Single sided
for (int i = 0; i < numTriangles; i++)
{
triangles[i * 3] = i * 3;
triangles[i * 3 + 1] = i * 3 + 1;
triangles[i * 3 + 2] = i * 3 + 2;
}
mesh.triangles = triangles;
mesh.normals = normals.ToArray();
mesh.uv = shape.VertexData.Tex0.ToArray();
mesh.colors = shape.VertexData.Color0.ToArray();
// Assign material to shader
Material mat = new Material(DefaultMaterial);
mat.mainTexture = texture.Texture;
MeshFilter filter = go.AddComponent<MeshFilter>();
filter.sharedMesh = mesh;
MeshRenderer renderer = go.AddComponent<MeshRenderer>();
renderer.sharedMaterial = mat;
meshFilters.Add(filter);
meshRenderers.Add(renderer);
meshMaterials.Add(mat);
}
// Combine meshes
CombineMeshesToASingle();
}
}