1
0
mirror of synced 2024-12-04 20:08:00 +01:00
Switch-Toolbox/File_Format_Library/FileFormats/Grezzo/CMB.cs
KillzXGaming 9c3ac9ae9f Improve DAE exporting.
A brand new DAE exporter is now used built off the one used by Ploaj and Crossmod devs. This replaces the old assimp exporter which was very buggy.
The DAE exporter has many improvments such as multiple materials per mesh, node tree fixes, material improvements, and lots more.
Single binded bfres (only using a bone index) will now be rigged if exported as DAE.
More progress on CMB saving. It's almost done, still needs some adjustments.
Fixed GFMDL rigging on certain models (thanks to RTB's script to reference what controlled the bone type).
2019-11-15 19:27:03 -05:00

1984 lines
78 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Toolbox.Library.Forms;
using Toolbox.Library;
using Toolbox.Library.IO;
using Toolbox.Library.Rendering;
using Grezzo.CmbEnums;
using OpenTK.Graphics.OpenGL;
namespace FirstPlugin
{
public class CMB : TreeNodeFile, IFileFormat, IContextMenuNode
{
public FileType FileType { get; set; } = FileType.Layout;
public bool CanSave { get; set; }
public string[] Description { get; set; } = new string[] { "*CTR Model Binary" };
public string[] Extension { get; set; } = new string[] { ".cmb" };
public string FileName { get; set; }
public string FilePath { get; set; }
public IFileInfo IFileInfo { get; set; }
public bool Identify(System.IO.Stream stream)
{
using (var reader = new Toolbox.Library.IO.FileReader(stream, true))
{
return reader.CheckSignature(4, "cmb ");
}
}
public Type[] Types
{
get
{
List<Type> types = new List<Type>();
return types.ToArray();
}
}
Viewport viewport
{
get
{
var editor = LibraryGUI.GetObjectEditor();
return editor.GetViewport();
}
set
{
var editor = LibraryGUI.GetObjectEditor();
editor.LoadViewport(value);
}
}
public ToolStripItem[] GetContextMenuItems()
{
List<ToolStripItem> Items = new List<ToolStripItem>();
Items.Add(new ToolStripMenuItem("Save", null, SaveAction, Keys.Control | Keys.S));
Items.Add(new ToolStripMenuItem("Export", null, ExportAction, Keys.Control | Keys.E));
return Items.ToArray();
}
private void SaveAction(object sender, EventArgs e)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = Utils.GetAllFilters(this);
sfd.FileName = FileName;
if (sfd.ShowDialog() == DialogResult.OK)
{
STFileSaver.SaveFileFormat(this, sfd.FileName);
}
}
private void ExportAction(object sender, EventArgs e)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "Supported Formats|*.dae;";
if (sfd.ShowDialog() == DialogResult.OK)
{
ExportModelSettings exportDlg = new ExportModelSettings();
if (exportDlg.ShowDialog() == DialogResult.OK)
ExportModel(sfd.FileName, exportDlg.Settings);
}
}
public void ExportModel(string fileName, DAE.ExportSettings settings)
{
var model = new STGenericModel();
model.Materials = Materials;
model.Objects = Renderer.Meshes;
var textures = new List<STGenericTexture>();
foreach (var tex in Renderer.TextureList)
textures.Add(tex);
DAE.Export(fileName, settings, model, textures, Skeleton);
}
bool DrawablesLoaded = false;
public override void OnClick(TreeView treeView)
{
if (Runtime.UseOpenGL)
{
if (viewport == null)
{
viewport = new Viewport(ObjectEditor.GetDrawableContainers());
viewport.Dock = DockStyle.Fill;
}
if (!DrawablesLoaded)
{
ObjectEditor.AddContainer(DrawableContainer);
DrawablesLoaded = true;
}
viewport.ReloadDrawables(DrawableContainer);
LibraryGUI.LoadEditor(viewport);
viewport.Text = Text;
}
}
public CMB_Renderer Renderer;
public DrawableContainer DrawableContainer = new DrawableContainer();
List<STGenericMaterial> Materials = new List<STGenericMaterial>();
public Header header;
STTextureFolder texFolder;
STSkeleton Skeleton;
public void Load(System.IO.Stream stream)
{
CanSave = false;
Renderer = new CMB_Renderer();
DrawableContainer.Drawables.Add(Renderer);
Skeleton = new STSkeleton();
//These models/skeletons come out massive so scale them with an overridden scale
Skeleton.PreviewScale = Renderer.PreviewScale;
Skeleton.BonePointScale = 40;
DrawableContainer.Drawables.Add(Skeleton);
header = new Header();
header.Read(new FileReader(stream));
Text = header.Name;
DrawableContainer.Name = Text;
//Load textures
if (header.SectionData.TextureChunk != null)
{
texFolder = new TextureFolder("Texture");
TreeNode meshFolder = new TreeNode("Meshes");
TreeNode materialFolder = new TreeNode("Materials");
TreeNode skeletonFolder = new TreeNode("Skeleton");
bool HasTextures = header.SectionData.TextureChunk != null &&
header.SectionData.TextureChunk.Textures.Count != 0;
bool HasMeshes = header.SectionData.SkeletalMeshChunk != null &&
header.SectionData.SkeletalMeshChunk.ShapeChunk.SeperateShapes.Count != 0;
bool HasSkeleton = header.SectionData.SkeletonChunk != null &&
header.SectionData.SkeletonChunk.Bones.Count != 0;
bool HasMaterials = header.SectionData.MaterialChunk != null &&
header.SectionData.MaterialChunk.Materials.Count != 0;
if (HasSkeleton)
{
foreach (var bone in header.SectionData.SkeletonChunk.Bones)
{
STBone genericBone = new STBone(Skeleton);
genericBone.parentIndex = bone.ParentIndex;
genericBone.position = new float[3];
genericBone.scale = new float[3];
genericBone.rotation = new float[4];
genericBone.Checked = true;
genericBone.Text = $"Bone {bone.ID}";
genericBone.RotationType = STBone.BoneRotationType.Euler;
genericBone.position[0] = bone.Translation.X;
genericBone.position[1] = bone.Translation.Y;
genericBone.position[2] = bone.Translation.Z;
genericBone.scale[0] = bone.Scale.X;
genericBone.scale[1] = bone.Scale.Y;
genericBone.scale[2] = bone.Scale.Z;
genericBone.rotation[0] = bone.Rotation.X;
genericBone.rotation[1] = bone.Rotation.Y;
genericBone.rotation[2] = bone.Rotation.Z;
Skeleton.bones.Add(genericBone);
}
foreach (var bone in Skeleton.bones)
{
if (bone.Parent == null)
skeletonFolder.Nodes.Add(bone);
}
Skeleton.reset();
Skeleton.update();
}
if (HasTextures)
{
int texIndex = 0;
foreach (var tex in header.SectionData.TextureChunk.Textures)
{
var texWrapper = new CTXB.TextureWrapper();
texWrapper.Text = $"Texture {texIndex++}";
texWrapper.ImageKey = "texture";
texWrapper.SelectedImageKey = texWrapper.ImageKey;
if (tex.Name != string.Empty)
texWrapper.Text = tex.Name;
texWrapper.Width = tex.Width;
texWrapper.Height = tex.Height;
texWrapper.Format = CTR_3DS.ConvertPICAToGenericFormat(tex.PicaFormat);
texWrapper.ImageData = tex.ImageData;
texFolder.Nodes.Add(texWrapper);
Renderer.TextureList.Add(texWrapper);
}
}
if (HasMaterials)
{
int materialIndex = 0;
foreach (var mat in header.SectionData.MaterialChunk.Materials)
{
CMBMaterialWrapper material = new CMBMaterialWrapper(mat);
material.Text = $"Material {materialIndex++}";
materialFolder.Nodes.Add(material);
Materials.Add(material);
bool HasDiffuse = false;
foreach (var tex in mat.TextureMaps)
{
if (tex.TextureIndex != -1)
{
CMBTextureMapWrapper matTexture = new CMBTextureMapWrapper(tex);
matTexture.TextureIndex = tex.TextureIndex;
material.TextureMaps.Add(matTexture);
if (tex.TextureIndex < Renderer.TextureList.Count && tex.TextureIndex >= 0)
{
matTexture.Name = Renderer.TextureList[tex.TextureIndex].Text;
material.Nodes.Add(matTexture.Name);
}
if (!HasDiffuse && matTexture.Name != "bg_syadowmap") //Quick hack till i do texture env stuff
{
matTexture.Type = STGenericMatTexture.TextureType.Diffuse;
HasDiffuse = true;
}
}
}
}
}
if (HasMeshes)
{
int MeshIndex = 0;
foreach (var mesh in header.SectionData.SkeletalMeshChunk.MeshChunk.Meshes)
{
STGenericMaterial mat = new STGenericMaterial();
if (Materials.Count > mesh.MaterialIndex) //Incase materials for some reason are in a seperate file, check this
mat = Materials[mesh.MaterialIndex];
CmbMeshWrapper genericMesh = new CmbMeshWrapper(mat);
genericMesh.Text = $"Mesh {MeshIndex++}";
genericMesh.MaterialIndex = mesh.MaterialIndex;
//Wow this is long
var shape = header.SectionData.SkeletalMeshChunk.ShapeChunk.SeperateShapes[(int)mesh.SepdIndex];
genericMesh.Shape = shape;
//Now load the vertex and face data
if (shape.Position.VertexData != null)
{
int VertexCount = shape.Position.VertexData.Length;
for (int v = 0; v < VertexCount; v++)
{
Vertex vert = new Vertex();
vert.pos = new OpenTK.Vector3(
shape.Position.VertexData[v].X,
shape.Position.VertexData[v].Y,
shape.Position.VertexData[v].Z);
if (shape.Normal.VertexData != null && shape.Normal.VertexData.Length > v)
{
vert.nrm = new OpenTK.Vector3(
shape.Normal.VertexData[v].X,
shape.Normal.VertexData[v].Y,
shape.Normal.VertexData[v].Z).Normalized();
}
if (shape.Color.VertexData != null && shape.Color.VertexData.Length > v)
{
vert.col = new OpenTK.Vector4(
shape.Color.VertexData[v].X,
shape.Color.VertexData[v].Y,
shape.Color.VertexData[v].Z,
shape.Color.VertexData[v].W).Normalized();
}
if (shape.TexCoord0.VertexData != null && shape.TexCoord0.VertexData.Length > v)
{
vert.uv0 = new OpenTK.Vector2(
shape.TexCoord0.VertexData[v].X,
shape.TexCoord0.VertexData[v].Y);
}
if (shape.TexCoord1.VertexData != null)
{
}
if (shape.TexCoord2.VertexData != null)
{
}
for (int i = 0; i < 16; i++)
{
if (i < shape.Primatives[0].BoneIndexTable.Length)
{
int boneId = shape.Primatives[0].BoneIndexTable[i];
if (shape.Primatives[0].SkinningMode == SkinningMode.RIGID_SKINNING)
{
vert.pos = OpenTK.Vector3.TransformPosition(vert.pos, Skeleton.bones[boneId].Transform);
vert.nrm = OpenTK.Vector3.TransformNormal(vert.nrm, Skeleton.bones[boneId].Transform);
}
}
}
bool HasSkinning = shape.Primatives[0].SkinningMode != SkinningMode.SINGLE_BONE
&& shape.BoneIndices.Type == CmbDataType.UByte; //Noclip checks the type for ubyte so do the same
bool HasWeights = shape.Primatives[0].SkinningMode == SkinningMode.SMOOTH_SKINNING;
/* if (shape.BoneIndices.VertexData != null && HasSkinning && shape.BoneIndices.VertexData.Length > v)
{
var BoneIndices = shape.BoneIndices.VertexData[v];
for (int j = 0; j < shape.boneDimension; j++)
{
ushort index = shape.Primatives[0].BoneIndexTable[(uint)BoneIndices[j]];
vert.boneIds.Add((int)index);
}
}
if (shape.BoneWeights.VertexData != null && HasWeights && shape.BoneWeights.VertexData.Length > v)
{
var BoneWeights = shape.BoneWeights.VertexData[v];
for (int j = 0; j < shape.boneDimension; j++)
{
vert.boneWeights.Add(BoneWeights[j]);
}
}*/
genericMesh.vertices.Add(vert);
}
}
foreach (var prim in shape.Primatives)
{
STGenericPolygonGroup group = new STGenericPolygonGroup();
genericMesh.PolygonGroups.Add(group);
for (int i = 0; i < prim.Primatives[0].Indices.Length; i++)
{
group.faces.Add((int)prim.Primatives[0].Indices[i]);
}
}
Renderer.Meshes.Add(genericMesh);
meshFolder.Nodes.Add(genericMesh);
}
}
if (meshFolder.Nodes.Count > 0)
Nodes.Add(meshFolder);
if (skeletonFolder.Nodes.Count > 0)
Nodes.Add(skeletonFolder);
if (materialFolder.Nodes.Count > 0)
Nodes.Add(materialFolder);
if (texFolder.Nodes.Count > 0)
Nodes.Add(texFolder);
}
}
public void Unload()
{
}
public void Save(System.IO.Stream stream)
{
using (var writer = new FileWriter(stream)) {
header.Write(writer);
}
}
public enum CMBVersion
{
OOT3DS,
MM3DS,
LM3DS,
}
public class CMBMaterialWrapper : STGenericMaterial
{
public Material Material { get; set; }
public CMBMaterialWrapper(Material material)
{
Material = material;
}
public override void OnClick(TreeView treeView)
{
STPropertyGrid editor = (STPropertyGrid)LibraryGUI.GetActiveContent(typeof(STPropertyGrid));
if (editor == null)
{
editor = new STPropertyGrid();
LibraryGUI.LoadEditor(editor);
}
editor.Text = Text;
editor.Dock = DockStyle.Fill;
editor.LoadProperty(Material, null);
}
}
public class CMBTextureMapWrapper : STGenericMatTexture
{
public int TextureIndex { get; set; }
public TextureMap TextureMapData;
public CMBTextureMapWrapper(TextureMap texMap)
{
TextureMapData = texMap;
//Linear filtering looks better according to noclip
if (TextureMapData.MinFiler == TextureFilter.LINEAR_MIPMAP_NEAREST)
TextureMapData.MinFiler = TextureFilter.LINEAR_MIPMAP_LINEAR;
this.WrapModeS = ConvertWrapMode(TextureMapData.WrapS);
this.WrapModeT = ConvertWrapMode(TextureMapData.WrapT);
this.MinFilter = ConvertMinFilterMode(TextureMapData.MinFiler);
this.MagFilter = ConvertMagFilterMode(TextureMapData.MagFiler);
}
private STTextureMinFilter ConvertMinFilterMode(TextureFilter PicaFilterMode)
{
switch (PicaFilterMode)
{
case TextureFilter.LINEAR: return STTextureMinFilter.Linear;
case TextureFilter.LINEAR_MIPMAP_LINEAR: return STTextureMinFilter.LinearMipMapNearest;
case TextureFilter.LINEAR_MIPMAP_NEAREST: return STTextureMinFilter.NearestMipmapLinear;
case TextureFilter.NEAREST: return STTextureMinFilter.Nearest;
case TextureFilter.NEAREST_MIPMAP_LINEAR: return STTextureMinFilter.NearestMipmapLinear;
case TextureFilter.NEAREST_MIPMAP_NEAREST: return STTextureMinFilter.NearestMipmapNearest;
default: return 0;
}
}
private STTextureMagFilter ConvertMagFilterMode(TextureFilter PicaFilterMode)
{
switch (PicaFilterMode)
{
case TextureFilter.LINEAR: return STTextureMagFilter.Linear;
case TextureFilter.NEAREST: return STTextureMagFilter.Nearest;
default: return 0;
}
}
private STTextureWrapMode ConvertWrapMode(CMBTextureWrapMode PicaWrapMode)
{
switch (PicaWrapMode)
{
case CMBTextureWrapMode.REPEAT: return STTextureWrapMode.Repeat;
case CMBTextureWrapMode.MIRRORED_REPEAT: return STTextureWrapMode.Mirror;
case CMBTextureWrapMode.CLAMP: return STTextureWrapMode.Clamp;
case CMBTextureWrapMode.CLAMP_TO_EDGE: return STTextureWrapMode.Clamp;
default: return STTextureWrapMode.Repeat;
}
}
}
public class CmbMeshWrapper : GenericRenderedObject
{
public SeperateShape Shape { get; set; }
STGenericMaterial material;
public CmbMeshWrapper(STGenericMaterial mat) {
material = mat;
}
public override STGenericMaterial GetMaterial()
{
return material;
}
}
private class TextureFolder : STTextureFolder, ITextureContainer
{
public bool DisplayIcons => true;
public List<STGenericTexture> TextureList
{
get
{
List<STGenericTexture> textures = new List<STGenericTexture>();
foreach (STGenericTexture node in Nodes)
textures.Add(node);
return textures;
}
set {}
}
public TextureFolder(string text) : base(text)
{
}
}
public class Header
{
public string Name { get; set; }
public CMBVersion Version;
public uint ChunkCount; //Fixed count per game
public uint Unknown;
public SectionData SectionData;
public void Read(FileReader reader)
{
string magic = reader.ReadSignature(4, "cmb ");
uint FileSize = reader.ReadUInt32();
ChunkCount = reader.ReadUInt32();
Unknown = reader.ReadUInt32();
Name = reader.ReadString(0x10).TrimEnd('\0');
//Check the chunk count used by the game
if (ChunkCount == 0x0F)
Version = CMBVersion.LM3DS;
else if (ChunkCount == 0x0A)
Version = CMBVersion.MM3DS;
else if (ChunkCount == 0x06)
Version = CMBVersion.OOT3DS;
else
throw new Exception("Unexpected chunk count! " + ChunkCount);
SectionData = new SectionData();
SectionData.Read(reader, this);
}
public void Write(FileWriter writer)
{
writer.WriteSignature("cmb ");
writer.Write(uint.MaxValue); //Reserve space for file size offset
writer.Write(ChunkCount);
writer.Write(Unknown);
writer.WriteString(Name, 0x10);
SectionData.Write(writer, this);
//Write the total file size
using (writer.TemporarySeek(4, System.IO.SeekOrigin.Begin))
{
writer.Write((uint)writer.BaseStream.Length);
}
}
}
public class SectionData
{
public SkeletonChunk SkeletonChunk;
public QuadTreeChunk QuadTreeChunk;
public MaterialChunk MaterialChunk;
public TextureChunk TextureChunk;
public SkeletalMeshChunk SkeletalMeshChunk;
public LUTSChunk LUTSChunk;
public VertexAttributesChunk VertexAttributesChunk;
public ushort[] Indices;
public void Read(FileReader reader, Header header)
{
uint numIndices = reader.ReadUInt32();
SkeletonChunk = ReadChunkSection<SkeletonChunk>(reader, header);
if (header.Version >= CMBVersion.MM3DS)
QuadTreeChunk = ReadChunkSection<QuadTreeChunk>(reader, header);
MaterialChunk = ReadChunkSection<MaterialChunk>(reader, header);
TextureChunk = ReadChunkSection<TextureChunk>(reader, header);
SkeletalMeshChunk = ReadChunkSection<SkeletalMeshChunk>(reader, header);
LUTSChunk = ReadChunkSection<LUTSChunk>(reader, header);
VertexAttributesChunk = ReadChunkSection<VertexAttributesChunk>(reader, header);
uint indexBufferOffset = reader.ReadUInt32();
uint textureDataOffset = reader.ReadUInt32();
if (header.Version >= CMBVersion.MM3DS)
reader.ReadUInt32(); //Padding?
if (VertexAttributesChunk != null)
{
long bufferStart = VertexAttributesChunk.StartPosition;
foreach (var shape in SkeletalMeshChunk.ShapeChunk.SeperateShapes)
{
ReadVertexDataFromSlice(reader, VertexAttributesChunk.PositionSlice, shape.Position, 3, bufferStart);
ReadVertexDataFromSlice(reader, VertexAttributesChunk.NormalSlice, shape.Normal, 3, bufferStart);
ReadVertexDataFromSlice(reader, VertexAttributesChunk.TangentSlice, shape.Tangent, 3, bufferStart);
ReadVertexDataFromSlice(reader, VertexAttributesChunk.ColorSlice, shape.Color, 4, bufferStart);
ReadVertexDataFromSlice(reader, VertexAttributesChunk.Texcoord0Slice, shape.TexCoord0, 2, bufferStart);
ReadVertexDataFromSlice(reader, VertexAttributesChunk.Texcoord1Slice, shape.TexCoord1, 2, bufferStart);
ReadVertexDataFromSlice(reader, VertexAttributesChunk.Texcoord2Slice, shape.TexCoord2, 2, bufferStart);
ReadVertexDataFromSlice(reader, VertexAttributesChunk.BoneIndicesSlice, shape.BoneIndices, shape.boneDimension, bufferStart);
ReadVertexDataFromSlice(reader, VertexAttributesChunk.BoneWeightsSlice, shape.BoneWeights, shape.boneDimension, bufferStart);
}
}
if (indexBufferOffset != 0)
{
foreach (var shape in SkeletalMeshChunk.ShapeChunk.SeperateShapes)
{
foreach (var prim in shape.Primatives)
{
foreach (var subprim in prim.Primatives) //Note 3DS usually only has one sub primative
{
subprim.Indices = new uint[subprim.IndexCount];
reader.SeekBegin(indexBufferOffset + subprim.Offset);
switch (subprim.IndexType)
{
case CmbDataType.UByte:
for (int i = 0; i < subprim.IndexCount; i++)
subprim.Indices[i] = reader.ReadByte();
break;
case CmbDataType.UShort:
for (int i = 0; i < subprim.IndexCount; i++)
subprim.Indices[i] = reader.ReadUInt16();
break;
case CmbDataType.UInt:
for (int i = 0; i < subprim.IndexCount; i++)
subprim.Indices[i] = reader.ReadUInt32();
break;
default:
throw new Exception("Unsupported index type! " + subprim.IndexType);
}
}
}
}
}
foreach (var tex in TextureChunk.Textures)
{
reader.SeekBegin(textureDataOffset + tex.DataOffset);
tex.ImageData = reader.ReadBytes((int)tex.ImageSize);
}
}
private static void ReadVertexDataFromSlice(FileReader reader, BufferSlice Slice, SepdVertexAttribute VertexAttribute, int elementCount, long bufferStart)
{
if (Slice == null || Slice.Size == 0)
return;
reader.SeekBegin(bufferStart + VertexAttribute.StartPosition + Slice.Offset);
int StrideSize = CalculateStrideSize(VertexAttribute.Type, elementCount);
int VertexCount = (int)Slice.Size / StrideSize;
Console.WriteLine($"{VertexAttribute.GetType()} {VertexAttribute.Type} {elementCount} {StrideSize} {VertexCount}");
VertexAttribute.VertexData = new Syroot.Maths.Vector4F[VertexCount];
for (int v = 0; v < VertexCount; v++)
{
VertexAttribute.VertexData[v] = ReadVertexBufferData(reader, VertexAttribute, elementCount);
}
}
private static Syroot.Maths.Vector4F ReadVertexBufferData(FileReader reader, SepdVertexAttribute VertexAttribute, int elementCount)
{
List<float> values = new List<float>();
for (int i = 0; i < elementCount; i++)
{
switch (VertexAttribute.Type)
{
case CmbDataType.Byte:
values.Add(reader.ReadSByte());
break;
case CmbDataType.Float:
values.Add(reader.ReadSingle());
break;
case CmbDataType.Int:
values.Add(reader.ReadInt32());
break;
case CmbDataType.Short:
values.Add(reader.ReadInt16());
break;
case CmbDataType.UByte:
values.Add(reader.ReadByte());
break;
case CmbDataType.UInt:
values.Add(reader.ReadUInt32());
break;
case CmbDataType.UShort:
values.Add(reader.ReadUInt16());
break;
default: throw new Exception("Unknown format! " + VertexAttribute.Type);
}
}
while (values.Count < 4) values.Add(0);
return new Syroot.Maths.Vector4F(
values[0] * VertexAttribute.Scale,
values[1] * VertexAttribute.Scale,
values[2] * VertexAttribute.Scale,
values[3] * VertexAttribute.Scale);
}
private static void WriteVertexBufferData(FileWriter writer, BufferSlice Slice,
SepdVertexAttribute VertexAttribute, int elementCount)
{
int StrideSize = CalculateStrideSize(VertexAttribute.Type, elementCount);
int VertexCount = (int)Slice.Size / StrideSize;
for (int v = 0; v < VertexCount; v++)
{
WriteVertexBufferData(writer, VertexAttribute, VertexAttribute.VertexData[v], elementCount);
}
}
private static void WriteVertexBufferData(FileWriter writer, SepdVertexAttribute VertexAttribute,
Syroot.Maths.Vector4F value, int elementCount)
{
float[] values = new float[4] { value.X, value.Y, value.Z, value.W };
for (int i = 0; i < elementCount; i++)
{
switch (VertexAttribute.Type)
{
case CmbDataType.Byte:
writer.Write((sbyte)values[i]);
break;
case CmbDataType.Float:
writer.Write(values[i]);
break;
case CmbDataType.Int:
writer.Write((int)values[i]);
break;
case CmbDataType.Short:
writer.Write((short)values[i]);
break;
case CmbDataType.UByte:
writer.Write((byte)values[i]);
break;
case CmbDataType.UInt:
writer.Write((uint)values[i]);
break;
case CmbDataType.UShort:
writer.Write((ushort)values[i]);
break;
default: throw new Exception("Unknown format! " + VertexAttribute.Type);
}
}
}
private static int CalculateStrideSize(CmbDataType type, int elementCount)
{
switch (type)
{
case CmbDataType.Byte: return elementCount * sizeof(sbyte);
case CmbDataType.Float: return elementCount * sizeof(float);
case CmbDataType.Int: return elementCount * sizeof(int);
case CmbDataType.Short: return elementCount * sizeof(short);
case CmbDataType.UByte: return elementCount * sizeof(byte);
case CmbDataType.UInt: return elementCount * sizeof(uint);
case CmbDataType.UShort: return elementCount * sizeof(ushort);
default: throw new Exception("Unknwon format! " + type);
}
}
private uint GetTotalIndexCount()
{
uint total = 0;
foreach (var shape in SkeletalMeshChunk.ShapeChunk.SeperateShapes)
{
foreach (var prim in shape.Primatives)
{
foreach (var subprim in prim.Primatives) //Note 3DS usually only has one sub primative
total += (uint)subprim.Indices.Length;
}
}
return total;
}
public void Write(FileWriter writer, Header header)
{
long pos = writer.Position;
writer.Write(GetTotalIndexCount());
//Reserve space for all the offses
writer.Write(0); //SkeletonChunk
if (header.Version >= CMBVersion.MM3DS)
writer.Write(0); //QuadTreeChunk
writer.Write(0); //MaterialChunk
writer.Write(0); //TextureChunk
writer.Write(0); //SkeletalMeshChunk
writer.Write(0); //LUTSChunk
writer.Write(0); //VertexAttributesChunk
writer.Write(0); //indexBufferOffset
writer.Write(0); //textureDataOffset
if (header.Version >= CMBVersion.MM3DS)
writer.Write(0); //padding or unknown unused section
//Write sections and offsets
int _offsetPos = 4;
if (SkeletonChunk != null)
{
writer.WriteUint32Offset(pos + _offsetPos);
SkeletonChunk.Write(writer, header);
}
if (QuadTreeChunk != null && header.Version >= CMBVersion.MM3DS)
{
writer.WriteUint32Offset(pos + (_offsetPos += 4));
QuadTreeChunk.Write(writer, header);
}
if (MaterialChunk != null)
{
writer.WriteUint32Offset(pos + (_offsetPos += 4));
MaterialChunk.Write(writer, header);
}
if (TextureChunk != null)
{
writer.WriteUint32Offset(pos + (_offsetPos += 4));
TextureChunk.Write(writer, header);
}
if (SkeletalMeshChunk != null)
{
writer.WriteUint32Offset(pos + (_offsetPos += 4));
SkeletalMeshChunk.Write(writer, header);
}
if (LUTSChunk != null)
{
writer.WriteUint32Offset(pos + (_offsetPos += 4));
LUTSChunk.Write(writer, header);
}
if (VertexAttributesChunk != null)
{
writer.WriteUint32Offset(pos + (_offsetPos += 4));
VertexAttributesChunk.Write(writer, header);
}
if (Indices != null && Indices.Length > 0)
{
writer.WriteUint32Offset(pos + (_offsetPos += 4));
long indexBufferPos = writer.Position;
foreach (var shape in SkeletalMeshChunk.ShapeChunk.SeperateShapes)
{
foreach (var prim in shape.Primatives)
{
foreach (var subprim in prim.Primatives) //Note 3DS usually only has one sub primative
{
subprim.Indices = new uint[subprim.IndexCount];
writer.SeekBegin(indexBufferPos + subprim.Offset);
switch (subprim.IndexType)
{
case CmbDataType.UByte:
for (int i = 0; i < subprim.IndexCount; i++)
writer.Write((byte)subprim.Indices[i]);
break;
case CmbDataType.UShort:
for (int i = 0; i < subprim.IndexCount; i++)
writer.Write((ushort)subprim.Indices[i]);
break;
case CmbDataType.UInt:
for (int i = 0; i < subprim.IndexCount; i++)
writer.Write((uint)subprim.Indices[i]);
break;
default:
throw new Exception("Unsupported index type! " + subprim.IndexType);
}
}
}
}
}
if (TextureChunk != null && TextureChunk.Textures.Count > 0)
{
long dataStart = writer.Position;
writer.WriteUint32Offset(pos + (_offsetPos += 4));
//Save image data
foreach (var tex in TextureChunk.Textures)
{
writer.SeekBegin(tex.DataOffset + dataStart);
writer.Write(tex.ImageData);
}
}
}
}
//Connects all the meshes, vertex attributes, and shape data together
public class SkeletalMeshChunk : IChunkCommon
{
private const string Magic = "sklm";
public MeshesChunk MeshChunk { get; set; }
public ShapesChunk ShapeChunk { get; set; }
public void Read(FileReader reader, Header header)
{
long pos = reader.Position;
reader.ReadSignature(4, Magic);
uint sectionSize = reader.ReadUInt32();
MeshChunk = ReadChunkSection<MeshesChunk>(reader, header, pos);
ShapeChunk = ReadChunkSection<ShapesChunk>(reader, header,pos);
}
public void Write(FileWriter writer, Header header)
{
long pos = writer.Position;
writer.WriteSignature(Magic);
writer.Write(uint.MaxValue);//SectionSize
long _offsetPos = writer.Position;
writer.Write(uint.MaxValue);//MeshChunk
writer.Write(uint.MaxValue);//ShapeChunk
if (MeshChunk != null)
{
writer.WriteUint32Offset(_offsetPos);
MeshChunk.Write(writer, header);
}
if (ShapeChunk != null)
{
writer.WriteUint32Offset(_offsetPos + 4);
ShapeChunk.Write(writer, header);
}
long endPos = writer.Position;
using (writer.TemporarySeek(pos + 4, System.IO.SeekOrigin.Begin)) {
writer.Write((uint)(endPos - pos));
}
}
}
public class MeshesChunk : IChunkCommon
{
private const string Magic = "mshs";
public List<Mesh> Meshes = new List<Mesh>();
public uint Unknown;
public void Read(FileReader reader, Header header)
{
reader.ReadSignature(4, Magic);
uint sectionSize = reader.ReadUInt32();
uint meshCount = reader.ReadUInt32();
Unknown = reader.ReadUInt32();
long meshPos = reader.Position;
for (int i = 0; i < meshCount; i++)
{
Mesh mesh = new Mesh();
mesh.SepdIndex = reader.ReadUInt16();
mesh.MaterialIndex = reader.ReadByte();
Meshes.Add(mesh);
if (header.Version == CMBVersion.OOT3DS)
mesh.unks = reader.ReadBytes(4);
else if (header.Version == CMBVersion.MM3DS)
mesh.unks = reader.ReadBytes(0x0C);
else if (header.Version >= CMBVersion.LM3DS)
mesh.unks = reader.ReadBytes(0x58);
Console.WriteLine($"SepdIndex {mesh.SepdIndex}");
Console.WriteLine($"MaterialIndex { mesh.MaterialIndex}");
}
}
public void Write(FileWriter writer, Header header)
{
long pos = writer.Position;
writer.WriteSignature(Magic);
writer.Write(uint.MaxValue);//SectionSize
writer.Write(Meshes.Count);
writer.Write(Unknown);
for (int i = 0; i < Meshes.Count; i++) {
writer.Write(Meshes[i].unks);
writer.Write(Meshes[i].SepdIndex);
writer.Write(Meshes[i].MaterialIndex);
}
long endPos = writer.Position;
using (writer.TemporarySeek(pos + 4, System.IO.SeekOrigin.Begin)) {
writer.Write((uint)(endPos - pos));
}
}
public class Mesh
{
public byte[] unks;
public ushort SepdIndex { get; set; }
public byte MaterialIndex { get; set; }
}
}
public class ShapesChunk : IChunkCommon
{
private const string Magic = "shp ";
public uint Unknown;
public List<SeperateShape> SeperateShapes = new List<SeperateShape>();
public void Read(FileReader reader, Header header)
{
long pos = reader.Position;
reader.ReadSignature(4, Magic);
uint sectionSize = reader.ReadUInt32();
uint sepdCount = reader.ReadUInt32();
Unknown = reader.ReadUInt32();
ushort[] offsets = reader.ReadUInt16s((int)sepdCount);
for (int i = 0; i < sepdCount; i++)
{
reader.SeekBegin(pos + offsets[i]);
var sepd = new SeperateShape();
sepd.Read(reader, header);
SeperateShapes.Add(sepd);
}
}
public void Write(FileWriter writer, Header header)
{
long pos = writer.Position;
writer.WriteSignature(Magic);
writer.Write(uint.MaxValue);//SectionSize
writer.Write(SeperateShapes.Count);
writer.Write(Unknown);
long offsetPos = writer.Position;
writer.Write(new ushort[SeperateShapes.Count]);
for (int i = 0; i < SeperateShapes.Count; i++)
{
writer.WriteUint16Offset(offsetPos, pos);
SeperateShapes[i].Write(writer, header);
}
long endPos = writer.Position;
using (writer.TemporarySeek(pos + 4, System.IO.SeekOrigin.Begin)) {
writer.Write((uint)(endPos - pos));
}
}
}
public class SeperateShape : IChunkCommon
{
private const string Magic = "sepd";
public SepdVertexAttribute Position { get; set; }
public SepdVertexAttribute Normal { get; set; }
public SepdVertexAttribute Tangent { get; set; }
public SepdVertexAttribute Color { get; set; }
public SepdVertexAttribute TexCoord0 { get; set; }
public SepdVertexAttribute TexCoord1 { get; set; }
public SepdVertexAttribute TexCoord2 { get; set; }
public SepdVertexAttribute BoneIndices { get; set; }
public SepdVertexAttribute BoneWeights { get; set; }
public List<PrimativesChunk> Primatives = new List<PrimativesChunk>();
public ushort boneDimension;
private byte[] unks;
public void Read(FileReader reader, Header header)
{
long pos = reader.Position;
reader.ReadSignature(4, Magic);
uint sectionSize = reader.ReadUInt32();
uint count = reader.ReadUInt16();
if (header.Version >= CMBVersion.LM3DS)
unks = reader.ReadBytes(50);
else
unks = reader.ReadBytes(26);
Position = ReadVertexAttrib(reader);
Normal = ReadVertexAttrib(reader);
if (header.Version >= CMBVersion.MM3DS)
Tangent = ReadVertexAttrib(reader);
Color = ReadVertexAttrib(reader);
TexCoord0 = ReadVertexAttrib(reader);
TexCoord1 = ReadVertexAttrib(reader);
TexCoord2 = ReadVertexAttrib(reader);
BoneIndices = ReadVertexAttrib(reader);
BoneWeights = ReadVertexAttrib(reader);
boneDimension = reader.ReadUInt16();
reader.ReadUInt16(); //padding
ushort[] Offsets = reader.ReadUInt16s((int)count);
for (int i = 0; i < count; i++)
{
reader.SeekBegin(pos + Offsets[i]);
PrimativesChunk prim = new PrimativesChunk();
prim.Read(reader, header);
Primatives.Add(prim);
}
}
public void Write(FileWriter writer, Header header)
{
long pos = writer.Position;
writer.WriteSignature(Magic);
writer.Write(uint.MaxValue); //section size
writer.Write(Primatives.Count);
writer.Write(unks);
WriteVertexAttrib(writer, Position);
WriteVertexAttrib(writer, Normal);
if (header.Version >= CMBVersion.MM3DS)
WriteVertexAttrib(writer, Tangent);
WriteVertexAttrib(writer, Color);
WriteVertexAttrib(writer, TexCoord0);
WriteVertexAttrib(writer, TexCoord1);
WriteVertexAttrib(writer, TexCoord2);
WriteVertexAttrib(writer, BoneIndices);
WriteVertexAttrib(writer, BoneWeights);
WriteVertexAttrib(writer, Tangent);
long endPos = writer.Position;
using (writer.TemporarySeek(pos + 4, System.IO.SeekOrigin.Begin)) {
writer.Write((uint)(endPos - pos));
}
}
private SepdVertexAttribute ReadVertexAttrib(FileReader reader)
{
long pos = reader.Position;
SepdVertexAttribute att = new SepdVertexAttribute();
att.StartPosition = reader.ReadUInt32();
att.Scale = reader.ReadSingle();
att.Type = reader.ReadEnum<CmbDataType>(true);
att.Mode = reader.ReadEnum<SepdVertexAttribMode>(true);
att.Constants = new float[4];
att.Constants[0] = reader.ReadSingle();
att.Constants[1] = reader.ReadSingle();
att.Constants[2] = reader.ReadSingle();
att.Constants[3] = reader.ReadSingle();
reader.SeekBegin(pos + 0x1C);
return att;
}
private void WriteVertexAttrib(FileWriter writer, SepdVertexAttribute att)
{
long pos = writer.Position;
writer.Write(att.StartPosition);
writer.Write(att.Scale);
writer.Write(att.Type, true);
writer.Write(att.Mode, true);
writer.Write(att.Constants[0]);
writer.Write(att.Constants[1]);
writer.Write(att.Constants[2]);
writer.Write(att.Constants[3]);
writer.SeekBegin(pos + 0x1C);
}
}
public class SepdVertexAttribute
{
public uint StartPosition { get; set; }
public float Scale { get; set; }
public CmbDataType Type { get; set; }
public SepdVertexAttribMode Mode { get; set; }
public Syroot.Maths.Vector4F[] VertexData { get; set; }
public float[] Constants { get; set; }
}
public class PrimativesChunk : IChunkCommon
{
private const string Magic = "prms";
public SkinningMode SkinningMode;
public List<SubPrimativeChunk> Primatives = new List<SubPrimativeChunk>();
public ushort[] BoneIndexTable { get; set; }
public void Read(FileReader reader, Header header)
{
long pos = reader.Position;
reader.ReadSignature(4, Magic);
uint sectionSize = reader.ReadUInt32();
uint count = reader.ReadUInt32();
SkinningMode = reader.ReadEnum<SkinningMode>(true);
ushort boneTableCount = reader.ReadUInt16();
uint boneIndexOffset = reader.ReadUInt32();
uint primativeOffset = reader.ReadUInt32();
reader.SeekBegin(pos + boneIndexOffset);
BoneIndexTable = reader.ReadUInt16s(boneTableCount);
reader.SeekBegin(pos + primativeOffset);
for (int i = 0; i < count; i++)
{
SubPrimativeChunk prim = new SubPrimativeChunk();
prim.Read(reader, header);
Primatives.Add(prim);
}
}
public void Write(FileWriter writer, Header header)
{
long pos = writer.Position;
writer.WriteSignature(Magic);
writer.Write(uint.MaxValue);//SectionSize
writer.Write(Primatives.Count);
writer.Write(SkinningMode, true);
writer.Write((ushort)BoneIndexTable.Length);
long boneIndexOfsPos = writer.Position;
writer.Write(uint.MaxValue); //bone index offset
long primativeOfsPos = writer.Position;
writer.Write(uint.MaxValue); //primative offset
writer.WriteUint32Offset(boneIndexOfsPos, pos);
writer.Write(BoneIndexTable);
writer.Align(4);
writer.WriteUint32Offset(primativeOfsPos, pos);
for (int i = 0; i < Primatives.Count; i++)
Primatives[i].Write(writer, header);
long endPos = writer.Position;
using (writer.TemporarySeek(pos + 4, System.IO.SeekOrigin.Begin)) {
writer.Write((uint)(endPos - pos));
}
}
}
public class SubPrimativeChunk : IChunkCommon
{
private const string Magic = "prm ";
public SkinningMode SkinningMode { get; private set; }
public CmbDataType IndexType { get; private set; }
public ushort IndexCount { get; private set; }
public uint Offset { get; private set; }
private uint[] _indices;
public uint[] Indices
{
get
{
return _indices;
}
set
{
_indices = value;
}
}
public uint Unknown;
public uint Unknown2;
public void Read(FileReader reader, Header header)
{
long pos = reader.Position;
reader.ReadSignature(4, Magic);
uint sectionSize = reader.ReadUInt32();
Unknown = reader.ReadUInt32();
Unknown2 = reader.ReadUInt32();
IndexType = reader.ReadEnum<CmbDataType>(true);
reader.Seek(2); //padding
IndexCount = reader.ReadUInt16();
//This value is the index, so we'll use it as an offset
//Despite the data type, this is always * 2
Offset = (uint)reader.ReadUInt16() * sizeof(ushort);
}
public void Write(FileWriter writer, Header header)
{
long pos = writer.Position;
writer.WriteSignature(Magic);
writer.Write(uint.MaxValue);//SectionSize
writer.Write(Unknown);
writer.Write(Unknown2);
writer.Write(IndexType, true);
writer.Seek(2);
writer.Write(IndexCount);
writer.Write((ushort)(Offset / sizeof(ushort)));
long endPos = writer.Position;
using (writer.TemporarySeek(pos + 4, System.IO.SeekOrigin.Begin)) {
writer.Write((uint)(endPos - pos));
}
}
}
public class LUTSChunk : IChunkCommon
{
private const string Magic = "luts";
private byte[] data;
public void Read(FileReader reader, Header header)
{
reader.ReadSignature(4, Magic);
uint sectionSize = reader.ReadUInt32();
data = reader.getSection((uint)reader.Position, sectionSize);
}
public void Write(FileWriter writer, Header header)
{
long pos = writer.Position;
writer.WriteSignature(Magic);
writer.Write(uint.MaxValue);//SectionSize
writer.Write(data);
long endPos = writer.Position;
using (writer.TemporarySeek(pos + 4, System.IO.SeekOrigin.Begin)) {
writer.Write((uint)(endPos - pos));
}
}
}
public class VertexAttributesChunk : IChunkCommon
{
private const string Magic = "vatr";
public BufferSlice PositionSlice;
public BufferSlice NormalSlice;
public BufferSlice TangentSlice; //Used in MM3DS and newer
public BufferSlice ColorSlice;
public BufferSlice Texcoord0Slice;
public BufferSlice Texcoord1Slice;
public BufferSlice Texcoord2Slice;
public BufferSlice BoneIndicesSlice;
public BufferSlice BoneWeightsSlice;
public long StartPosition;
public uint MaxIndex;
public void Read(FileReader reader, Header header)
{
StartPosition = reader.Position;
reader.ReadSignature(4, Magic);
uint sectionSize = reader.ReadUInt32();
MaxIndex = reader.ReadUInt32();
PositionSlice = ReadSlice(reader);
NormalSlice = ReadSlice(reader);
if (header.Version >= CMBVersion.MM3DS)
TangentSlice = ReadSlice(reader);
ColorSlice = ReadSlice(reader);
Texcoord0Slice = ReadSlice(reader);
Texcoord1Slice = ReadSlice(reader);
Texcoord2Slice = ReadSlice(reader);
BoneIndicesSlice = ReadSlice(reader);
BoneWeightsSlice = ReadSlice(reader);
}
public void Write(FileWriter writer, Header header)
{
long pos = writer.Position;
writer.WriteSignature(Magic);
writer.Write(uint.MaxValue);//SectionSize
writer.Write(MaxIndex);
WriteSlice(writer, PositionSlice);
WriteSlice(writer, NormalSlice);
if (header.Version >= CMBVersion.MM3DS)
WriteSlice(writer, TangentSlice);
WriteSlice(writer, ColorSlice);
WriteSlice(writer, Texcoord0Slice);
WriteSlice(writer, Texcoord1Slice);
WriteSlice(writer, Texcoord2Slice);
WriteSlice(writer, BoneIndicesSlice);
WriteSlice(writer, BoneWeightsSlice);
long endPos = writer.Position;
using (writer.TemporarySeek(pos + 4, System.IO.SeekOrigin.Begin)) {
writer.Write((uint)(endPos - pos));
}
}
private void WriteSlice(FileWriter writer, BufferSlice slice) {
writer.Write(slice.Size);
writer.Write(slice.Offset);
}
private BufferSlice ReadSlice(FileReader reader)
{
BufferSlice slice = new BufferSlice();
slice.Size = reader.ReadUInt32();
slice.Offset = reader.ReadUInt32();
return slice;
}
}
public class BufferSlice
{
public uint Offset;
public uint Size;
}
public class SkeletonChunk : IChunkCommon
{
private const string Magic = "skl ";
public List<BoneChunk> Bones = new List<BoneChunk>();
public uint Unknown;
public void Read(FileReader reader, Header header)
{
reader.ReadSignature(4, Magic);
uint sectionSize = reader.ReadUInt32();
uint boneCount = reader.ReadUInt32();
Unknown = reader.ReadUInt32();
for (int i = 0; i < boneCount; i++)
{
BoneChunk bone = new BoneChunk();
bone.ID = reader.ReadInt16() & 0xFFFF;
bone.ParentIndex = reader.ReadInt16();
bone.Scale = reader.ReadVec3SY();
bone.Rotation = reader.ReadVec3SY();
bone.Translation = reader.ReadVec3SY();
if (header.Version >= CMBVersion.MM3DS)
bone.Unknown = reader.ReadInt32();
Bones.Add(bone);
}
}
public void Write(FileWriter writer, Header header)
{
writer.WriteSignature(Magic);
writer.Write(uint.MaxValue);//SectionSize
writer.Write(Bones.Count);
writer.Write(Unknown);
for (int i = 0; i < Bones.Count; i++)
{
writer.Write(Bones[i].ID);
writer.Write(Bones[i].ParentIndex);
writer.Write(Bones[i].Scale);
writer.Write(Bones[i].Rotation);
writer.Write(Bones[i].Translation);
if (header.Version >= CMBVersion.MM3DS)
writer.Write(Bones[i].Unknown);
}
}
}
public class BoneChunk
{
public int ID { get; set; }
public int ParentIndex { get; set; }
public Syroot.Maths.Vector3F Scale { get; set; }
public Syroot.Maths.Vector3F Rotation { get; set; }
public Syroot.Maths.Vector3F Translation { get; set; }
//An unknown value used in versions MM3DS and newer
public int Unknown { get; set; }
}
public class QuadTreeChunk : IChunkCommon
{
private const string Magic = "qtrs";
byte[] data;
public void Read(FileReader reader, Header header)
{
reader.ReadSignature(4, Magic);
uint sectionSize = reader.ReadUInt32();
data = reader.getSection((uint)reader.Position, sectionSize);
}
public void Write(FileWriter writer, Header header)
{
long pos = writer.Position;
writer.WriteSignature(Magic);
writer.Write(uint.MaxValue);//SectionSize
writer.Write(data);
long endPos = writer.Position;
using (writer.TemporarySeek(pos + 4, System.IO.SeekOrigin.Begin))
{
writer.Write((uint)(endPos - pos));
}
}
}
public class MaterialChunk : IChunkCommon
{
private const string Magic = "mats";
public List<Material> Materials = new List<Material>();
internal int textureCombinerSettingsTableOffs;
public void Read(FileReader reader, Header header)
{
long pos = reader.Position;
reader.ReadSignature(4, Magic);
uint sectionSize = reader.ReadUInt32();
uint count = reader.ReadUInt32();
int materialSize = 0x15C;
if (header.Version >= CMBVersion.MM3DS)
materialSize = 0x16C;
Console.WriteLine($"materialSize {materialSize.ToString("x")}");
textureCombinerSettingsTableOffs = (int)(pos + (count * materialSize));
for (int i = 0; i < count; i++)
{
reader.SeekBegin(pos + 0xC + (i * materialSize));
Material mat = new Material();
mat.Read(reader, header, this);
Materials.Add(mat);
}
}
public void Write(FileWriter writer, Header header)
{
long pos = writer.Position;
writer.WriteSignature(Magic);
writer.Write(uint.MaxValue);//SectionSize
for (int i = 0; i < Materials.Count; i++)
Materials[i].Write(writer, header);
long endPos = writer.Position;
using (writer.TemporarySeek(pos + 4, System.IO.SeekOrigin.Begin)) {
writer.Write((uint)(endPos - pos));
}
}
}
//Thanks for noclip for material RE stuff
//https://github.com/magcius/noclip.website/blob/9270b9e5022c691703689990f9c536cd9058e5cd/src/oot3d/cmb.ts#L232
public class Material
{
public bool IsTransparent = false;
public CullMode CullMode { get; set; }
public bool IsPolygonOffsetEnabled { get; set; }
public uint PolygonOffset { get; set; }
public TextureMap[] TextureMaps { get; set; }
public TextureMatrix[] TextureMaticies { get; set; }
public List<TextureCombiner> TextureCombiners { get; set; }
public STColor[] ConstantColors { get; set; }
public bool AlphaTestEnable { get; set; }
public float AlphaTestReference { get; set; }
public bool DepthTestEnable { get; set; }
public bool DepthWriteEnable { get; set; }
public AlphaFunction AlphaTestFunction { get; set; }
public DepthFunction DepthTestFunction { get; set; }
public bool BlendEnaled { get; set; }
public BlendingFactor BlendingFactorSrcRGB { get; set; }
public BlendingFactor BlendingFactorDestRGB { get; set; }
public BlendingFactor BlendingFactorSrcAlpha { get; set; }
public BlendingFactor BlendingFactorDestAlpha { get; set; }
public float BlendColorR { get; set; }
public float BlendColorG { get; set; }
public float BlendColorB { get; set; }
public float BlendColorA { get; set; }
public void Read(FileReader reader, Header header, MaterialChunk materialChunkParent)
{
TextureMaps = new TextureMap[3];
TextureMaticies = new TextureMatrix[3];
TextureCombiners = new List<TextureCombiner>();
long pos = reader.Position;
CullMode = reader.ReadEnum<CullMode>(true); //byte
IsPolygonOffsetEnabled = reader.ReadBoolean(); //byte
PolygonOffset = reader.ReadUInt32();
PolygonOffset = IsPolygonOffsetEnabled ? PolygonOffset / 0x10000 : 0;
//Texture bind data
reader.SeekBegin(pos + 0x10);
for (int j = 0; j < 3; j++)
{
TextureMaps[j] = new TextureMap();
TextureMaps[j].TextureIndex = reader.ReadInt16();
reader.ReadInt16(); //padding
TextureMaps[j].MinFiler = (TextureFilter)reader.ReadUInt16();
TextureMaps[j].MagFiler = (TextureFilter)reader.ReadUInt16();
TextureMaps[j].WrapS = (CMBTextureWrapMode)reader.ReadUInt16();
TextureMaps[j].WrapT = (CMBTextureWrapMode)reader.ReadUInt16();
TextureMaps[j].MinLOD = reader.ReadSingle();
TextureMaps[j].LodBias = reader.ReadSingle();
TextureMaps[j].borderColorR = reader.ReadByte();
TextureMaps[j].borderColorG = reader.ReadByte();
TextureMaps[j].borderColorB = reader.ReadByte();
TextureMaps[j].borderColorA = reader.ReadByte();
}
for (int j = 0; j < 3; j++)
{
TextureMaticies[j] = new TextureMatrix();
TextureMaticies[j].Flags = reader.ReadUInt32();
TextureMaticies[j].Scale = reader.ReadVec2SY();
TextureMaticies[j].Translate = reader.ReadVec2SY();
TextureMaticies[j].Rotate = reader.ReadSingle();
}
uint unkColor0 = reader.ReadUInt32();
ConstantColors = new STColor[6];
ConstantColors[0] = STColor.FromBytes(reader.ReadBytes(4));
ConstantColors[1] = STColor.FromBytes(reader.ReadBytes(4));
ConstantColors[2] = STColor.FromBytes(reader.ReadBytes(4));
ConstantColors[3] = STColor.FromBytes(reader.ReadBytes(4));
ConstantColors[4] = STColor.FromBytes(reader.ReadBytes(4));
ConstantColors[5] = STColor.FromBytes(reader.ReadBytes(4));
float bufferColorR = reader.ReadSingle();
float bufferColorG = reader.ReadSingle();
float bufferColorB = reader.ReadSingle();
float bufferColorA = reader.ReadSingle();
int bumpTextureIndex = reader.ReadInt16();
int lightEnvBumpUsage = reader.ReadInt16();
// Fragment lighting table.
byte reflectanceRSamplerIsAbs = reader.ReadByte();
ushort reflectanceRSamplerInput = reader.ReadUInt16();
uint reflectanceRSamplerScale = reader.ReadUInt32();
byte reflectanceGSamplerIsAbs = reader.ReadByte();
ushort reflectanceGSamplerInput = reader.ReadUInt16();
uint reflectanceGSamplerScale = reader.ReadUInt32();
byte reflectanceBSamplerIsAbs = reader.ReadByte();
ushort reflectanceBSamplerInput = reader.ReadUInt16();
uint reflectanceBSamplerScale = reader.ReadUInt32();
byte reflectance0SamplerIsAbs = reader.ReadByte();
ushort reflectance0SamplerInput = reader.ReadUInt16();
uint reflectance0SamplerScale = reader.ReadUInt32();
byte reflectance1SamplerIsAbs = reader.ReadByte();
ushort reflectance1SamplerInput = reader.ReadUInt16();
uint reflectance1SamplerScale = reader.ReadUInt32();
byte fresnelSamplerIsAbs = reader.ReadByte();
ushort fresnelSamplerInput = reader.ReadUInt16();
uint fresnelSamplerScale = reader.ReadUInt32();
reader.SeekBegin(pos + 0x120);
uint textureCombinerTableCount = reader.ReadUInt32();
int textureCombinerTableIdx = (int)pos + 0x124;
for (int i = 0; i < textureCombinerTableCount; i++)
{
reader.SeekBegin(textureCombinerTableIdx + 0x00);
ushort textureCombinerIndex = reader.ReadUInt16();
reader.SeekBegin(materialChunkParent.textureCombinerSettingsTableOffs + textureCombinerIndex * 0x28);
TextureCombiner combner = new TextureCombiner();
combner.combineRGB = reader.ReadEnum<CombineResultOpDMP>(false);
combner.combineAlpha = reader.ReadEnum<CombineResultOpDMP>(false);
combner.scaleRGB = reader.ReadEnum<CombineScaleDMP>(false);
combner.scaleAlpha = reader.ReadEnum<CombineScaleDMP>(false);
combner.bufferInputRGB = reader.ReadEnum<CombineBufferInputDMP>(false);
combner.bufferInputAlpha = reader.ReadEnum<CombineBufferInputDMP>(false);
combner.source0RGB = reader.ReadEnum<CombineSourceDMP>(false);
combner.source1RGB = reader.ReadEnum<CombineSourceDMP>(false);
combner.source2RGB = reader.ReadEnum<CombineSourceDMP>(false);
combner.op0RGB = reader.ReadEnum<CombineOpDMP>(false);
combner.op1RGB = reader.ReadEnum<CombineOpDMP>(false);
combner.op2RGB = reader.ReadEnum<CombineOpDMP>(false);
combner.source0Alpha = reader.ReadEnum<CombineSourceDMP>(false);
combner.source1Alpha = reader.ReadEnum<CombineSourceDMP>(false);
combner.source2Alpha = reader.ReadEnum<CombineSourceDMP>(false);
combner.op0Alpha = reader.ReadEnum<CombineOpDMP>(false);
combner.op1Alpha = reader.ReadEnum<CombineOpDMP>(false);
combner.op2Alpha = reader.ReadEnum<CombineOpDMP>(false);
combner.constantIndex = reader.ReadUInt32();
TextureCombiners.Add(combner);
textureCombinerTableIdx += 0x2;
}
reader.SeekBegin(pos + 0x130);
AlphaTestEnable = reader.ReadBoolean();
AlphaTestReference = reader.ReadByte() / 0xFF;
AlphaTestFunction = (AlphaFunction)reader.ReadUInt16();
DepthTestEnable = reader.ReadBoolean();
DepthWriteEnable = reader.ReadBoolean();
DepthTestFunction = (DepthFunction)reader.ReadUInt16();
if (!AlphaTestEnable)
AlphaTestFunction = AlphaFunction.Always;
if (!DepthTestEnable)
DepthTestFunction = DepthFunction.Always;
reader.SeekBegin(pos + 0x138);
BlendEnaled = reader.ReadBoolean();
Console.WriteLine("BlendEnaled " + BlendEnaled);
//Unknown.
reader.ReadByte();
reader.ReadUInt16();
reader.SeekBegin(pos + 0x13C);
BlendingFactorSrcAlpha = (BlendingFactor)reader.ReadUInt16();
BlendingFactorDestAlpha = (BlendingFactor)reader.ReadUInt16();
reader.SeekBegin(pos + 0x144);
BlendingFactorSrcRGB = (BlendingFactor)reader.ReadUInt16();
BlendingFactorDestRGB = (BlendingFactor)reader.ReadUInt16();
// BlendingFunctionAlpha = (AlphaFunction)reader.ReadUInt16();
reader.SeekBegin(pos + 0x14C);
BlendColorR = reader.ReadSingle();
BlendColorG = reader.ReadSingle();
BlendColorB = reader.ReadSingle();
BlendColorA = reader.ReadSingle();
IsTransparent = BlendEnaled;
Console.WriteLine(ToString());
}
public void Write(FileWriter writer, Header header)
{
long pos = writer.Position;
writer.Write(CullMode, true);
writer.Write(IsPolygonOffsetEnabled);
writer.Write(PolygonOffset);
writer.SeekBegin(pos + 0x10);
for (int j = 0; j < 3; j++)
{
writer.Write(TextureMaps[j].TextureIndex);
writer.Write((ushort)0);
writer.Write((ushort)TextureMaps[j].MinFiler);
writer.Write((ushort)TextureMaps[j].MagFiler);
writer.Write((ushort)TextureMaps[j].WrapS);
writer.Write((ushort)TextureMaps[j].WrapT);
writer.Write((ushort)TextureMaps[j].LodBias);
writer.Write(TextureMaps[j].borderColorR);
writer.Write(TextureMaps[j].borderColorG);
writer.Write(TextureMaps[j].borderColorB);
writer.Write(TextureMaps[j].borderColorA);
}
for (int j = 0; j < 3; j++)
{
writer.Write(TextureMaticies[j].Flags);
writer.Write(TextureMaticies[j].Scale);
writer.Write(TextureMaticies[j].Translate);
writer.Write(TextureMaticies[j].Rotate);
}
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append($"----------------------------------------------------\n");
sb.Append($"AlphaTest {AlphaTestEnable} {AlphaTestFunction} {AlphaTestReference}\n");
sb.Append($"DepthTest {DepthTestEnable} {DepthTestFunction} DepthWrite {DepthTestFunction}\n");
sb.Append($"BlendingFactorSrcRGB {BlendingFactorSrcRGB}\n");
sb.Append($"BlendingFactorDestRGB {BlendingFactorDestRGB}\n");
sb.Append($"BlendingFactorSrcAlpha {BlendingFactorSrcAlpha}\n");
sb.Append($"BlendingFactorDestAlpha {BlendingFactorDestAlpha}\n");
sb.Append($"BlendEnaled {BlendEnaled}\n");
sb.Append($"----------------------------------------------------\n");
sb.AppendLine();
return sb.ToString();
}
}
public class TextureCombiner
{
public CombineResultOpDMP combineRGB;
public CombineResultOpDMP combineAlpha;
public CombineScaleDMP scaleRGB;
public CombineScaleDMP scaleAlpha;
public CombineBufferInputDMP bufferInputRGB;
public CombineBufferInputDMP bufferInputAlpha;
public CombineSourceDMP source0RGB;
public CombineSourceDMP source1RGB;
public CombineSourceDMP source2RGB;
public CombineOpDMP op0RGB;
public CombineOpDMP op1RGB;
public CombineOpDMP op2RGB;
public CombineSourceDMP source0Alpha;
public CombineSourceDMP source1Alpha;
public CombineSourceDMP source2Alpha;
public CombineOpDMP op0Alpha;
public CombineOpDMP op1Alpha;
public CombineOpDMP op2Alpha;
public uint constantIndex;
}
public class TextureMatrix
{
public uint Flags { get; set; }
public Syroot.Maths.Vector2F Scale { get; set; }
public Syroot.Maths.Vector2F Translate { get; set; }
public float Rotate { get; set; }
}
public class TextureMap
{
public short TextureIndex { get; set; }
public TextureFilter MinFiler { get; set; }
public TextureFilter MagFiler { get; set; }
public CMBTextureWrapMode WrapS { get; set; }
public CMBTextureWrapMode WrapT { get; set; }
public float MinLOD { get; set; }
public float LodBias { get; set; }
public byte borderColorR { get; set; }
public byte borderColorG { get; set; }
public byte borderColorB { get; set; }
public byte borderColorA { get; set; }
}
public class TextureChunk : IChunkCommon
{
private const string Magic = "tex ";
public List<CTXB.Texture> Textures = new List<CTXB.Texture>();
public void Read(FileReader reader, Header header)
{
reader.ReadSignature(4, Magic);
uint sectionSize = reader.ReadUInt32();
uint TextureCount = reader.ReadUInt32();
for (int i = 0; i < TextureCount; i++)
Textures.Add(new CTXB.Texture(reader));
}
public void Write(FileWriter writer, Header header)
{
long pos = writer.Position;
writer.WriteSignature(Magic);
writer.Write(uint.MaxValue);//SectionSize
for (int i = 0; i < Textures.Count; i++)
Textures[i].Write(writer);
//Write the total file size
writer.WriteSectionSizeU32(pos + 4, pos, writer.Position);
}
}
public static T ReadChunkSection<T>(FileReader reader, Header header, long startPos = 0)
where T : IChunkCommon, new()
{
long pos = reader.Position;
//Read offset and seek it
uint offset = reader.ReadUInt32();
reader.SeekBegin(startPos + offset);
//Create chunk instance
T chunk = new T();
chunk.Read(reader, header);
//Seek back and shift 4 from reading offset
reader.SeekBegin(pos + 0x4);
return chunk;
}
public interface IChunkCommon
{
void Read(FileReader reader, Header header);
void Write(FileWriter writer, Header header);
}
}
}