Add mesh reading and conversion support

This commit is contained in:
Yukai Li 2021-03-17 03:18:15 -06:00
parent 65dc96c4d6
commit aa1277a4e1
25 changed files with 2212 additions and 9 deletions

157
LibDgf/Mesh/Db2.cs Normal file
View file

@ -0,0 +1,157 @@
using LibDgf.Ps2.Vif;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace LibDgf.Mesh
{
public class Db2
{
BinaryReader br;
public List<Db2Element> Elements { get; set; } = new List<Db2Element>();
public void Read(BinaryReader br)
{
uint vifStreamLength = br.ReadUInt32();
uint version = br.ReadUInt32();
if (version != 2) throw new InvalidDataException("DB version is not 2.");
uint reserved1 = br.ReadUInt32();
uint reserved2 = br.ReadUInt32();
//if (reserved1 != 0 || reserved2 != 0)
// System.Diagnostics.Debugger.Break();
Elements.Clear();
var startPos = br.BaseStream.Position;
var endPos = startPos + vifStreamLength;
this.br = br;
try
{
ExpectNop();
ExpectNop();
ExpectStCycl();
ExpectUnpackV4_32();
while (br.BaseStream.Position < endPos)
{
Db2Element element = new Db2Element();
element.ElementLength = br.ReadInt32();
element.Flags = br.ReadUInt32();
//if ((element.Flags & 0xFFFFE0E0) != 0)
// System.Diagnostics.Debugger.Break();
element.TextureIndex = br.ReadInt32();
element.Reserved = br.ReadUInt32();
//if (element.Reserved != 0)
// System.Diagnostics.Debugger.Break();
int numReglistWords = (int)(element.Flags & 0xf);
if (numReglistWords != 0)
{
element.GsRegs = br.ReadBytes((numReglistWords + 1) * 16);
}
element.GifTagFan = br.ReadBytes(16);
element.GifTagStrip = br.ReadBytes(16);
// Retrieve number of vertices from GIFtag NLOOP
int numVertices = element.VertexCount;
for (int i = 0; i < numVertices; ++i)
{
element.Vertices.Add(br.ReadV4_32());
}
ExpectUnpackV3_16((byte)numVertices);
for (int i = 0; i < numVertices; ++i)
{
element.VertexNormals.Add(new Tuple<double, double, double>(
Utils.Convert12BitFixedToDouble(br.ReadInt16()),
Utils.Convert12BitFixedToDouble(br.ReadInt16()),
Utils.Convert12BitFixedToDouble(br.ReadInt16())
));
}
ReadAlign(startPos, 4);
// Do STs exist if no texture?
ExpectUnpackV2_16((byte)numVertices);
for (int i = 0; i < numVertices; ++i)
{
element.STCoordinates.Add(new Tuple<double, double>(
Utils.Convert12BitFixedToDouble(br.ReadInt16()),
Utils.Convert12BitFixedToDouble(br.ReadInt16())
));
}
ReadAlign(startPos, 16); // Skipping over some NOPs in the process, assume that's what they are
ExpectMsCnt();
Elements.Add(element);
ExpectNop();
if (br.BaseStream.Position == endPos - 8)
{
ExpectNop();
ExpectNop();
}
else
{
ExpectStCycl();
ExpectUnpackV4_32();
}
}
}
finally
{
this.br = null;
}
}
void ReadAlign(long startPos, int bytes)
{
var aligned = (br.BaseStream.Position - startPos + bytes - 1) / bytes * bytes;
br.BaseStream.Seek(startPos + aligned - br.BaseStream.Position, SeekOrigin.Current);
}
#region Sanity checking functions
VifCode ExpectNop()
{
return ExpectVifCode(0x00000000, 0x7f000000);
}
VifCode ExpectStCycl()
{
return ExpectVifCode(0x01000404, 0x7f00ffff);
}
VifCodeUnpack ExpectUnpackV4_32()
{
return (VifCodeUnpack)ExpectVifCode(0x6c008000, 0x6f00c000);
}
VifCodeUnpack ExpectUnpackV3_16(byte num)
{
return (VifCodeUnpack)ExpectVifCode(0x69008000 | ((uint)num << 16), 0x6fffc000);
}
VifCodeUnpack ExpectUnpackV2_16(byte num)
{
return (VifCodeUnpack)ExpectVifCode(0x65008000 | ((uint)num << 16), 0x6fffc000);
}
VifCode ExpectMsCnt()
{
return ExpectVifCode(0x17000000, 0x7f000000);
}
VifCode ExpectVifCode(uint value, uint mask)
{
uint read = br.ReadUInt32();
if ((read & mask) != value)
throw new InvalidDataException($"VIFcode expectation failed at {br.BaseStream.Position - 4:x8}.");
return new VifCode { Value = read };
}
#endregion
}
}

89
LibDgf/Mesh/Db2Element.cs Normal file
View file

@ -0,0 +1,89 @@
using LibDgf.Ps2.Vif;
using System;
using System.Collections.Generic;
using System.Text;
namespace LibDgf.Mesh
{
public class Db2Element
{
// Size of element in 128-bit words, including header, excluding surrounding VIFcodes
public int ElementLength { get; set; }
// 0x000f: number of REGLIST words (excluding leading GIFtag)
// 0x0010: has texture
// 0x00e0: reserved
// 0x0100: has vertex color (deprecated, probably)
// 0x0e00: GIFtag index - with standard REGLIST primitive: 3 = triangle fan, 4 = triangle strip
// 0x1000: disable lighting
// 0x80000: textures enabled (only set at runtime)
public uint Flags { get; set; }
public int TextureIndex { get; set; } // Actually byte
public uint Reserved { get; set; }
public byte[] GsRegs { get; set; } // One GS primitive, CLAMP_1 and TEX0_1
public byte[] GifTagFan { get; set; }
public byte[] GifTagStrip { get; set; }
public List<VuVector> Vertices { get; } = new List<VuVector>();
public List<Tuple<double, double, double>> VertexNormals { get; } = new List<Tuple<double, double, double>>();
public List<Tuple<double, double>> STCoordinates { get; } = new List<Tuple<double, double>>();
// Flags broken out
public bool HasTexture
{
get
{
return (Flags & 0x10) != 0;
}
set
{
Flags = (uint)((Flags & ~0x10) | ((value ? 1u : 0) << 4));
}
}
public int GifTagIndex
{
get
{
return (int)((Flags >> 9) & 7);
}
set
{
Flags = (uint)((Flags & ~0xe00) | (((uint)value & 7) << 9));
}
}
public bool DisableLighting
{
get
{
return (Flags & 0x1000) != 0;
}
set
{
Flags = (uint)((Flags & ~0x1000) | ((value ? 1u : 0) << 12));
}
}
public int VertexCount
{
get
{
return BitConverter.ToInt32(GifTagStrip, 0) & 0x7ff;
}
set
{
int num = BitConverter.ToInt32(GifTagStrip, 0);
num = (num & ~0x7ff) | (value & 0x7ff);
byte[] bytes = BitConverter.GetBytes(num);
Buffer.BlockCopy(bytes, 0, GifTagStrip, 0, bytes.Length);
num = BitConverter.ToInt32(GifTagFan, 0);
num = (num & ~0x7ff) | (value & 0x7ff);
bytes = BitConverter.GetBytes(num);
Buffer.BlockCopy(bytes, 0, GifTagFan, 0, bytes.Length);
}
}
}
}

92
LibDgf/Mesh/Pdb.cs Normal file
View file

@ -0,0 +1,92 @@
using LibDgf.Ps2.Vif;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace LibDgf.Mesh
{
public class Pdb
{
VuVector[] boundingBox;
public int BoundingBoxType { get; set; }
public VuFloat BoundingBallSize { get; set; }
public VuVector[] BoundingBox
{
get
{
return boundingBox;
}
set
{
if (value != null && value.Length != 8) throw new ArgumentException("Wrong number of vertices for bounding box.", nameof(value));
boundingBox = value;
}
}
public Tdb Specular { get; set; }
public Tdb Diffuse { get; set; }
public Tdb Metallic { get; set; }
public void Read(BinaryReader br)
{
var startOffset = br.BaseStream.Position;
BoundingBoxType = br.ReadInt32();
BoundingBallSize = br.ReadPs2Float();
uint boundingBoxOffset = br.ReadUInt32();
uint boundingBoxLength = br.ReadUInt32();
uint[] tdbOffsets = new uint[3];
uint[] tdbLengths = new uint[tdbOffsets.Length];
for (int i = 0; i < tdbOffsets.Length; ++i)
{
tdbOffsets[i] = br.ReadUInt32();
tdbLengths[i] = br.ReadUInt32();
}
// Junk bytes 0xc8 to 0xcf here
if (boundingBoxLength != 0)
{
br.BaseStream.Seek(startOffset + boundingBoxOffset, SeekOrigin.Begin);
BoundingBox = br.ReadBoundingBox();
}
else
{
BoundingBox = null;
}
if (tdbLengths[0] != 0)
{
br.BaseStream.Seek(startOffset + tdbOffsets[0], SeekOrigin.Begin);
Specular = new Tdb();
Specular.Read(br);
}
else
{
Specular = null;
}
if (tdbLengths[1] != 0)
{
br.BaseStream.Seek(startOffset + tdbOffsets[1], SeekOrigin.Begin);
Diffuse = new Tdb();
Diffuse.Read(br);
}
else
{
Diffuse = null;
}
if (tdbLengths[2] != 0)
{
br.BaseStream.Seek(startOffset + tdbOffsets[2], SeekOrigin.Begin);
Metallic = new Tdb();
Metallic.Read(br);
}
else
{
Metallic = null;
}
}
}
}

54
LibDgf/Mesh/Tdb.cs Normal file
View file

@ -0,0 +1,54 @@
using LibDgf.Ps2.Vif;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace LibDgf.Mesh
{
public class Tdb
{
VuVector[] boundingBox;
public TdbFlags Flags { get; set; }
public List<TdbTexture> Textures { get; } = new List<TdbTexture>();
public VuVector[] BoundingBox
{
get
{
return boundingBox;
}
set
{
if (value != null && value.Length != 8) throw new ArgumentException("Wrong number of vertices for bounding box.", nameof(value));
boundingBox = value;
}
}
public Db2 Mesh { get; set; }
public void Read(BinaryReader br)
{
var startPos = br.BaseStream.Position;
Flags = (TdbFlags)br.ReadByte();
Textures.Clear();
byte numTextures = br.ReadByte();
for (int i = 0; i < numTextures; ++i)
{
var tex = new TdbTexture();
tex.Read(br);
Textures.Add(tex);
}
var read = br.BaseStream.Position - startPos;
var aligned = (read + 15) & ~15;
br.BaseStream.Seek(aligned - read, SeekOrigin.Current);
if ((Flags & TdbFlags.SkipBoundingBox) == 0)
{
BoundingBox = br.ReadBoundingBox();
}
Mesh = new Db2();
Mesh.Read(br);
}
}
}

13
LibDgf/Mesh/TdbFlags.cs Normal file
View file

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace LibDgf.Mesh
{
[Flags]
public enum TdbFlags : byte
{
None = 0,
SkipBoundingBox = 1,
}
}

21
LibDgf/Mesh/TdbTexture.cs Normal file
View file

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace LibDgf.Mesh
{
public class TdbTexture
{
public ushort DatIndex { get; set; }
public ushort ImageBufferBase { get; set; }
public ushort ClutBufferBase { get; set; }
public void Read(BinaryReader br)
{
DatIndex = br.ReadUInt16();
ImageBufferBase = br.ReadUInt16();
ClutBufferBase = br.ReadUInt16();
}
}
}