Switch-Toolbox/File_Format_Library/FileFormats/Grezzo/CMB.cs
2020-02-08 15:24:54 -05:00

2522 lines
108 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;
using FirstPlugin.Forms;
using SPICA.Formats.CtrH3D.Model.Material;
using SPICA.PICA.Commands;
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();
}
}
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)
{
LoadEditor<STPropertyGrid>();
}
public T LoadEditor<T>() where T : UserControl, new()
{
ViewportEditor editor = (ViewportEditor)LibraryGUI.GetActiveContent(typeof(ViewportEditor));
if (editor == null)
{
editor = new ViewportEditor(true);
editor.Dock = DockStyle.Fill;
LibraryGUI.LoadEditor(editor);
}
if (!DrawablesLoaded)
{
ObjectEditor.AddContainer(DrawableContainer);
DrawablesLoaded = true;
}
if (Runtime.UseOpenGL)
editor.LoadViewport(DrawableContainer);
foreach (var subControl in editor.GetEditorPanel().Controls)
if (subControl.GetType() == typeof(T))
return subControl as T;
T control = new T();
control.Dock = DockStyle.Fill;
editor.LoadEditor(control);
return control;
}
public CMB_Renderer Renderer;
public DrawableContainer DrawableContainer = new DrawableContainer();
List<STGenericMaterial> Materials = new List<STGenericMaterial>();
public Header header;
STTextureFolder texFolder;
public STSkeleton Skeleton;
public void Load(System.IO.Stream stream)
{
CanSave = true;
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;
Renderer.Skeleton = Skeleton;
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)
{
var bonesOrdered = header.SectionData.SkeletonChunk.Bones.OrderBy(x => x.ID).ToList();
foreach (var bone in bonesOrdered)
{
STBone genericBone = new STBone(Skeleton);
genericBone.parentIndex = bone.ParentIndex;
genericBone.Checked = true;
genericBone.Text = $"Bone {bone.ID}";
genericBone.RotationType = STBone.BoneRotationType.Euler;
genericBone.Position = new OpenTK.Vector3(
bone.Translation.X,
bone.Translation.Y,
bone.Translation.Z
);
genericBone.EulerRotation = new OpenTK.Vector3(
bone.Rotation.X,
bone.Rotation.Y,
bone.Rotation.Z
);
genericBone.Scale = new OpenTK.Vector3(
bone.Scale.X,
bone.Scale.Y,
bone.Scale.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(tex);
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, this);
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, this);
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;
List<ushort> SkinnedBoneTable = new List<ushort>();
foreach (var prim in shape.Primatives)
{
if (prim.BoneIndexTable != null) {
SkinnedBoneTable.AddRange(prim.BoneIndexTable);
foreach (var bone in prim.BoneIndexTable)
Console.WriteLine($"{genericMesh.Text} SkeletonB {Skeleton.bones[(int)bone].Text} index {bone}");
}
}
//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++)
{
if (BoneIndices[j] < SkinnedBoneTable.Count)
vert.boneIds.Add((int)SkinnedBoneTable[(int)BoneIndices[j]]);
// Console.WriteLine("boneIds " + BoneIndices[j]);
// ushort index = shape.Primatives[0].BoneIndexTable[(uint)BoneIndices[j]];
}
}
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 : CtrLibrary.H3DMaterialWrapper
{
private CMB ParentCMB;
public Material CMBMaterial { get; set; }
public override List<STGenericObject> FindMappedMeshes()
{
List<STGenericObject> meshes = new List<STGenericObject>();
foreach (var mesh in ParentCMB.Renderer.Meshes)
if (mesh.GetMaterial() == this)
meshes.Add(mesh);
return meshes;
}
public CMBMaterialWrapper(Material material, CMB cmb) : base(material.CTRMaterial)
{
ParentCMB = cmb;
CMBMaterial = material;
}
public override void OnClick(TreeView treeView)
{
var editor = ParentCMB.LoadEditor<CtrLibrary.Forms.BCHMaterialEditor>();
editor.LoadMaterial(this);
/* 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 CMB CMBParent;
public int TextureIndex { get; set; }
public TextureMap TextureMapData;
public override STGenericTexture GetTexture()
{
foreach (var tex in CMBParent.Renderer.TextureList)
if (tex.Text == this.Name)
return tex;
return null;
}
public CMBTextureMapWrapper(TextureMap texMap, CMB cmb)
{
CMBParent = cmb;
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 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;
VertexAttribute.VertexData = new Syroot.Maths.Vector4F[VertexCount];
if (VertexAttribute.Mode == SepdVertexAttribMode.ARRAY)
{
for (int v = 0; v < VertexCount; v++) {
VertexAttribute.VertexData[v] = ReadVertexBufferData(reader, VertexAttribute, elementCount);
}
}
else
{
VertexAttribute.VertexData[0] = new Syroot.Maths.Vector4F(
VertexAttribute.Constants[0],
VertexAttribute.Constants[1],
VertexAttribute.Constants[2],
VertexAttribute.Constants[3]);
}
}
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, long startPos, BufferSlice Slice,
SepdVertexAttribute VertexAttribute, int elementCount)
{
if (Slice == null || VertexAttribute == null)
return;
writer.SeekBegin(startPos + VertexAttribute.StartPosition + Slice.Offset);
for (int v = 0; v < VertexAttribute.VertexData?.Length; 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);
}
_offsetPos += 4;
if (MaterialChunk != null)
{
writer.WriteUint32Offset(pos + (_offsetPos));
MaterialChunk.Write(writer, header);
}
_offsetPos += 4;
if (TextureChunk != null)
{
writer.WriteUint32Offset(pos + (_offsetPos));
TextureChunk.Write(writer, header);
}
_offsetPos += 4;
if (SkeletalMeshChunk != null)
{
writer.WriteUint32Offset(pos + (_offsetPos));
SkeletalMeshChunk.Write(writer, header);
}
_offsetPos += 4;
if (LUTSChunk != null)
{
writer.WriteUint32Offset(pos + (_offsetPos));
LUTSChunk.Write(writer, header);
}
_offsetPos += 4;
if (VertexAttributesChunk != null)
{
writer.WriteUint32Offset(pos + (_offsetPos));
long vatrPos = writer.Position;
VertexAttributesChunk.Write(writer, header);
foreach (var shape in SkeletalMeshChunk.ShapeChunk.SeperateShapes)
{
WriteVertexBufferData(writer, vatrPos, VertexAttributesChunk.PositionSlice, shape.Position, 3);
WriteVertexBufferData(writer, vatrPos, VertexAttributesChunk.NormalSlice, shape.Normal, 3);
WriteVertexBufferData(writer, vatrPos, VertexAttributesChunk.TangentSlice, shape.Tangent, 3);
WriteVertexBufferData(writer, vatrPos, VertexAttributesChunk.ColorSlice, shape.Color, 4);
WriteVertexBufferData(writer, vatrPos, VertexAttributesChunk.Texcoord0Slice, shape.TexCoord0, 2);
WriteVertexBufferData(writer, vatrPos, VertexAttributesChunk.Texcoord1Slice, shape.TexCoord1, 2);
WriteVertexBufferData(writer, vatrPos, VertexAttributesChunk.Texcoord2Slice, shape.TexCoord2, 2);
WriteVertexBufferData(writer, vatrPos, VertexAttributesChunk.BoneIndicesSlice, shape.BoneIndices, shape.boneDimension);
WriteVertexBufferData(writer, vatrPos, VertexAttributesChunk.BoneWeightsSlice, shape.BoneWeights, shape.boneDimension);
}
writer.WriteSectionSizeU32(vatrPos + 4, vatrPos, writer.Position);
}
if (SkeletalMeshChunk != null && SkeletalMeshChunk.ShapeChunk.SeperateShapes.Count > 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
{
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);
}
}
}
}
writer.Align(64);
}
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.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, pos);
MeshChunk.Write(writer, header);
}
if (ShapeChunk != null)
{
writer.WriteUint32Offset(_offsetPos + 4, pos);
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();
mesh.ID = reader.ReadByte();
Meshes.Add(mesh);
if (header.Version == CMBVersion.MM3DS)
mesh.unks = reader.ReadBytes(8);
else if (header.Version >= CMBVersion.LM3DS)
mesh.unks = reader.ReadBytes(84);
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].SepdIndex);
writer.Write(Meshes[i].MaterialIndex);
writer.Write(Meshes[i].ID);
if (Meshes[i].unks != null)
writer.Write(Meshes[i].unks);
}
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 byte ID { 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 + (i * 2), 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;
public ushort Unknown;
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();
Unknown = reader.ReadUInt16();
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((ushort)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);
writer.Write((ushort)boneDimension);
writer.Write((ushort)Unknown);
long offsetPos = writer.Position;
writer.Write(new ushort[Primatives.Count]);
writer.Write((ushort)0);
for (int i = 0; i < Primatives.Count; i++)
{
writer.WriteUint16Offset(offsetPos + (i * 2), pos);
Primatives[i].Write(writer, header);
}
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 - 8);
}
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)
{
long pos = writer.Position;
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((ushort)Bones[i].ID);
writer.Write((short)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);
}
long endPos = writer.Position;
using (writer.TemporarySeek(pos + 4, System.IO.SeekOrigin.Begin)) {
writer.Write((uint)(endPos - pos));
}
}
}
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 + 12 + (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
writer.Write(Materials.Count);
for (int i = 0; i < Materials.Count; i++)
Materials[i].Write(writer, header);
for (int i = 0; i < Materials.Count; i++) {
foreach (var combiner in Materials[i].TextureCombiners)
{
writer.Write(combiner.combineRGB, false);
writer.Write(combiner.combineAlpha, false);
writer.Write(combiner.scaleRGB, false);
writer.Write(combiner.scaleAlpha, false);
writer.Write(combiner.bufferInputRGB, false);
writer.Write(combiner.bufferInputAlpha, false);
writer.Write(combiner.source0RGB, false);
writer.Write(combiner.source1RGB, false);
writer.Write(combiner.source2RGB, false);
writer.Write(combiner.op0RGB, false);
writer.Write(combiner.op1RGB, false);
writer.Write(combiner.op2RGB, false);
writer.Write(combiner.source0Alpha, false);
writer.Write(combiner.source1Alpha, false);
writer.Write(combiner.source2Alpha, false);
writer.Write(combiner.op0Alpha, false);
writer.Write(combiner.op1Alpha, false);
writer.Write(combiner.op2Alpha, false);
writer.Write(combiner.constantIndex);
}
}
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
{
private Header CMBHeader;
private H3DMaterial ctrMaterial;
public H3DMaterial CTRMaterial
{
get
{
if (ctrMaterial == null)
ctrMaterial = ToH3DMaterial();
return ctrMaterial;
}
}
public bool IsTransparent = false;
public bool IsFragmentLightingEnabled;
public bool IsVertexLightingEnabled;
public bool IsHemiSphereLightingEnabled;
public bool IsHemiSphereOcclusionEnabled;
public CullMode CullMode { get; set; }
public bool IsPolygonOffsetEnabled { get; set; }
public ushort PolygonOffset { get; set; }
public TextureMap[] TextureMaps { get; set; }
public TextureMatrix[] TextureMaticies { get; set; }
public ushort LightSetIndex { get; set; }
public ushort FogIndex { get; set; }
public List<TextureCombiner> TextureCombiners { get; set; }
public STColor8[] 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 BlendEquationMode BlendingEquationRGB { get; set; }
public BlendingFactor BlendingFactorSrcAlpha { get; set; }
public BlendingFactor BlendingFactorDestAlpha { get; set; }
public BlendEquationMode BlendingEquationAlpha { get; set; }
public float BlendColorR { get; set; }
public float BlendColorG { get; set; }
public float BlendColorB { get; set; }
public float BlendColorA { get; set; }
public float BufferColorR { get; set; }
public float BufferColorG { get; set; }
public float BufferColorB { get; set; }
public float BufferColorA { get; set; }
public short BumpMapIndex { get; set; }
public ushort BumpMapMode { get; set; }
public short IsBumpRenormalize { get; set; }
public LayerConfig LayerConfig { get; set; }
public FresnelSelector FresnelSelector { get; set; }
public bool IsClampHighLight { get; set; }
public bool IsDistribution0Enabled { get; set; }
public bool IsDistribution1Enabled { get; set; }
public bool IsGeometricFactor0Enabled { get; set; }
public bool IsGeometricFactor1Enabled { get; set; }
public bool IsReflectionEnabled { get; set; }
private byte[] data;
public H3DMaterial ToH3DMaterial()
{
H3DMaterial h3dMaterial = new H3DMaterial();
var matParams = h3dMaterial.MaterialParams;
if (IsVertexLightingEnabled)
matParams.Flags |= H3DMaterialFlags.IsVertexLightingEnabled;
if (IsFragmentLightingEnabled)
matParams.Flags |= H3DMaterialFlags.IsFragmentLightingEnabled;
if (IsHemiSphereLightingEnabled)
matParams.Flags |= H3DMaterialFlags.IsHemiSphereLightingEnabled;
if (IsHemiSphereOcclusionEnabled)
matParams.Flags |= H3DMaterialFlags.IsHemiSphereOcclusionEnabled;
switch (LayerConfig)
{
case LayerConfig.LayerConfig0: matParams.TranslucencyKind = H3DTranslucencyKind.LayerConfig0; break;
case LayerConfig.LayerConfig1: matParams.TranslucencyKind = H3DTranslucencyKind.LayerConfig1; break;
case LayerConfig.LayerConfig2: matParams.TranslucencyKind = H3DTranslucencyKind.LayerConfig2; break;
case LayerConfig.LayerConfig3: matParams.TranslucencyKind = H3DTranslucencyKind.LayerConfig3; break;
case LayerConfig.LayerConfig4: matParams.TranslucencyKind = H3DTranslucencyKind.LayerConfig4; break;
case LayerConfig.LayerConfig5: matParams.TranslucencyKind = H3DTranslucencyKind.LayerConfig5; break;
case LayerConfig.LayerConfig6: matParams.TranslucencyKind = H3DTranslucencyKind.LayerConfig6; break;
case LayerConfig.LayerConfig7: matParams.TranslucencyKind = H3DTranslucencyKind.LayerConfig7; break;
}
for (int i = 0; i < TextureMaps?.Length; i++)
{
string texture = GetTextureName(TextureMaps[i].TextureIndex);
if (texture == string.Empty)
continue;
if (i == 0) h3dMaterial.Texture0Name = texture;
if (i == 0) h3dMaterial.Texture1Name = texture;
if (i == 0) h3dMaterial.Texture2Name = texture;
h3dMaterial.TextureMappers[i].WrapU = ConvertWrapMode(TextureMaps[i].WrapS);
h3dMaterial.TextureMappers[i].WrapV = ConvertWrapMode(TextureMaps[i].WrapT);
h3dMaterial.TextureMappers[i].MagFilter = ConvertTexMagFilter(TextureMaps[i].MagFiler);
h3dMaterial.TextureMappers[i].MinFilter = ConvertTexMinFilter(TextureMaps[i].MinFiler);
h3dMaterial.TextureMappers[i].LODBias = TextureMaps[i].LodBias;
h3dMaterial.TextureMappers[i].MinLOD = (byte)(TextureMaps[i].MinLOD / 255);
h3dMaterial.TextureMappers[i].BorderColor = new SPICA.Math3D.RGBA(
TextureMaps[i].borderColorR,
TextureMaps[i].borderColorG,
TextureMaps[i].borderColorB,
TextureMaps[i].borderColorA);
matParams.TextureCoords[i].TransformType = H3DTextureTransformType.DccMaya;
matParams.TextureCoords[i].MappingType = H3DTextureMappingType.UvCoordinateMap;
matParams.TextureCoords[i].Scale = new System.Numerics.Vector2(
TextureMaticies[i].Scale.X, TextureMaticies[i].Scale.Y);
matParams.TextureCoords[i].Translation = new System.Numerics.Vector2(
TextureMaticies[i].Translate.X, TextureMaticies[i].Translate.Y);
matParams.TextureCoords[i].Rotation = TextureMaticies[i].Rotate;
}
matParams.DiffuseColor = new SPICA.Math3D.RGBA(255,255,255,255);
matParams.Specular0Color = new SPICA.Math3D.RGBA(0, 0, 0, 255);
matParams.Specular1Color = new SPICA.Math3D.RGBA(0, 0, 0, 255);
matParams.EmissionColor = new SPICA.Math3D.RGBA(0, 0, 0, 255);
matParams.Constant0Color = ConvertRGBA(ConstantColors[0]);
matParams.Constant1Color = ConvertRGBA(ConstantColors[1]);
matParams.Constant2Color = ConvertRGBA(ConstantColors[2]);
matParams.Constant3Color = ConvertRGBA(ConstantColors[3]);
matParams.Constant4Color = ConvertRGBA(ConstantColors[4]);
matParams.Constant5Color = ConvertRGBA(ConstantColors[5]);
matParams.BlendColor = ConvertRGBA(BlendColorR, BlendColorG, BlendColorB, BlendColorA);
matParams.TexEnvBufferColor = ConvertRGBA(BufferColorR, BufferColorG, BufferColorB, BufferColorA);
if (CullMode == CullMode.BACK)
matParams.FaceCulling = PICAFaceCulling.BackFace;
else if (CullMode == CullMode.FRONT)
matParams.FaceCulling = PICAFaceCulling.FrontFace;
else
matParams.FaceCulling = PICAFaceCulling.Never;
matParams.AlphaTest.Enabled = AlphaTestEnable;
matParams.AlphaTest.Function = ConvertAlphaFunction(AlphaTestFunction);
matParams.AlphaTest.Reference = (byte)(AlphaTestReference / 0xff);
matParams.BlendFunction.ColorSrcFunc = ConvertBlendFunc(BlendingFactorSrcRGB);
matParams.BlendFunction.ColorDstFunc = ConvertBlendFunc(BlendingFactorDestRGB);
matParams.BlendFunction.AlphaSrcFunc = ConvertBlendFunc(BlendingFactorSrcAlpha);
matParams.BlendFunction.AlphaDstFunc = ConvertBlendFunc(BlendingFactorDestAlpha);
for (int i = 0; i < TextureCombiners.Count; i++)
{
var combiner = TextureCombiners[i];
var h3dStage = new PICATexEnvStage();
/* h3dStage.Source.Color[0] = ConvertCombinerSrc[combiner.source0RGB];
h3dStage.Source.Color[1] = ConvertCombinerSrc[combiner.source1RGB];
h3dStage.Source.Color[2] = ConvertCombinerSrc[combiner.source2RGB];
h3dStage.Source.Alpha[0] = ConvertCombinerSrc[combiner.source0Alpha];
h3dStage.Source.Alpha[1] = ConvertCombinerSrc[combiner.source1Alpha];
h3dStage.Source.Alpha[2] = ConvertCombinerSrc[combiner.source2Alpha];
h3dStage.Operand.Alpha[0] = ConvertConvertCombinerAlphaOp[combiner.op0Alpha];
h3dStage.Operand.Alpha[1] = ConvertConvertCombinerAlphaOp[combiner.op1Alpha];
h3dStage.Operand.Alpha[2] = ConvertConvertCombinerAlphaOp[combiner.op2Alpha];
h3dStage.Operand.Color[0] = ConvertConvertCombinerColorOp[combiner.op0RGB];
h3dStage.Operand.Color[1] = ConvertConvertCombinerColorOp[combiner.op1RGB];
h3dStage.Scale.Color = ConvertScale[combiner.scaleRGB];
h3dStage.Scale.Alpha = ConvertScale[combiner.scaleAlpha];
h3dStage.Combiner.Alpha = ConvertConvertCombiner[combiner.combineAlpha];
h3dStage.Combiner.Color = ConvertConvertCombiner[combiner.combineRGB];*/
matParams.TexEnvStages[i] = h3dStage;
}
matParams.LUTInputAbsolute.Dist0 = LUTTable.reflectance0SamplerIsAbs;
matParams.LUTInputAbsolute.Dist1 = LUTTable.reflectance1SamplerIsAbs;
matParams.LUTInputAbsolute.ReflecR = LUTTable.reflectanceRSamplerIsAbs;
matParams.LUTInputAbsolute.ReflecG = LUTTable.reflectanceGSamplerIsAbs;
matParams.LUTInputAbsolute.ReflecB = LUTTable.reflectanceBSamplerIsAbs;
matParams.LUTInputAbsolute.Fresnel = LUTTable.fresnelSamplerIsAbs;
return h3dMaterial;
}
private Dictionary<CombineResultOpDMP, PICATextureCombinerMode> ConvertConvertCombiner =
new Dictionary<CombineResultOpDMP, PICATextureCombinerMode>()
{
{ CombineResultOpDMP.ADD, PICATextureCombinerMode.Add },
{ CombineResultOpDMP.ADD_MULT, PICATextureCombinerMode.AddMult },
{ CombineResultOpDMP.ADD_SIGNED, PICATextureCombinerMode.AddSigned },
{ CombineResultOpDMP.DOT3_RGB, PICATextureCombinerMode.DotProduct3Rgb },
{ CombineResultOpDMP.DOT3_RGBA, PICATextureCombinerMode.DotProduct3Rgba },
{ CombineResultOpDMP.INTERPOLATE, PICATextureCombinerMode.Interpolate },
{ CombineResultOpDMP.MODULATE, PICATextureCombinerMode.Modulate },
{ CombineResultOpDMP.MULT_ADD, PICATextureCombinerMode.MultAdd },
{ CombineResultOpDMP.REPLACE, PICATextureCombinerMode.Replace },
{ CombineResultOpDMP.SUBTRACT, PICATextureCombinerMode.Subtract },
};
private Dictionary<CombineSourceDMP, PICATextureCombinerSource> ConvertCombinerSrc =
new Dictionary<CombineSourceDMP, PICATextureCombinerSource>()
{
{ CombineSourceDMP.CONSTANT, PICATextureCombinerSource.Constant },
{ CombineSourceDMP.FRAGMENT_PRIMARY_COLOR, PICATextureCombinerSource.FragmentPrimaryColor },
{ CombineSourceDMP.FRAGMENT_SECONDARY_COLOR, PICATextureCombinerSource.FragmentSecondaryColor },
{ CombineSourceDMP.PREVIOUS, PICATextureCombinerSource.Previous },
{ CombineSourceDMP.PREVIOUS_BUFFER, PICATextureCombinerSource.PreviousBuffer },
{ CombineSourceDMP.PRIMARY_COLOR, PICATextureCombinerSource.PrimaryColor },
{ CombineSourceDMP.TEXTURE0, PICATextureCombinerSource.Texture0 },
{ CombineSourceDMP.TEXTURE1, PICATextureCombinerSource.Texture1 },
{ CombineSourceDMP.TEXTURE2, PICATextureCombinerSource.Texture2 },
{ CombineSourceDMP.TEXTURE3, PICATextureCombinerSource.Texture3 },
};
private Dictionary<CombineOpDMP, PICATextureCombinerAlphaOp> ConvertConvertCombinerAlphaOp =
new Dictionary<CombineOpDMP, PICATextureCombinerAlphaOp>()
{
{ CombineOpDMP.ONE_MINUS_SRC_COLOR, PICATextureCombinerAlphaOp.OneMinusAlpha },
{ CombineOpDMP.ONE_MINUS_SRC_ALPHA, PICATextureCombinerAlphaOp.OneMinusAlpha },
{ CombineOpDMP.ONE_MINUS_SRC_R, PICATextureCombinerAlphaOp.OneMinusRed },
{ CombineOpDMP.ONE_MINUS_SRC_G, PICATextureCombinerAlphaOp.OneMinusGreen },
{ CombineOpDMP.ONE_MINUS_SRC_B, PICATextureCombinerAlphaOp.OneMinusBlue },
{ CombineOpDMP.SRC_ALPHA, PICATextureCombinerAlphaOp.Alpha },
{ CombineOpDMP.SRC_COLOR, PICATextureCombinerAlphaOp.Alpha },
{ CombineOpDMP.SRC_R, PICATextureCombinerAlphaOp.Red },
{ CombineOpDMP.SRC_B, PICATextureCombinerAlphaOp.Blue },
{ CombineOpDMP.SRC_G, PICATextureCombinerAlphaOp.Green },
};
private Dictionary<CombineOpDMP, PICATextureCombinerColorOp> ConvertConvertCombinerColorOp =
new Dictionary<CombineOpDMP, PICATextureCombinerColorOp>()
{
{ CombineOpDMP.ONE_MINUS_SRC_COLOR, PICATextureCombinerColorOp.OneMinusColor },
{ CombineOpDMP.ONE_MINUS_SRC_ALPHA, PICATextureCombinerColorOp.OneMinusAlpha },
{ CombineOpDMP.ONE_MINUS_SRC_R, PICATextureCombinerColorOp.OneMinusRed },
{ CombineOpDMP.ONE_MINUS_SRC_G, PICATextureCombinerColorOp.OneMinusGreen },
{ CombineOpDMP.ONE_MINUS_SRC_B, PICATextureCombinerColorOp.OneMinusBlue },
{ CombineOpDMP.SRC_ALPHA, PICATextureCombinerColorOp.Alpha },
{ CombineOpDMP.SRC_COLOR, PICATextureCombinerColorOp.Color },
{ CombineOpDMP.SRC_R, PICATextureCombinerColorOp.Red },
{ CombineOpDMP.SRC_B, PICATextureCombinerColorOp.Blue },
{ CombineOpDMP.SRC_G, PICATextureCombinerColorOp.Green },
};
private Dictionary<CombineScaleDMP, PICATextureCombinerScale> ConvertScale =
new Dictionary<CombineScaleDMP, PICATextureCombinerScale>()
{
{ CombineScaleDMP._1, PICATextureCombinerScale.One },
{ CombineScaleDMP._2, PICATextureCombinerScale.Two },
{ CombineScaleDMP._4, PICATextureCombinerScale.Four },
};
private PICABlendEquation ConvertEquation()
{
return PICABlendEquation.FuncAdd;
}
private PICABlendFunc ConvertBlendFunc(BlendingFactor factor)
{
switch (factor)
{
case BlendingFactor.ConstantAlpha: return PICABlendFunc.ConstantAlpha;
case BlendingFactor.ConstantColor: return PICABlendFunc.ConstantColor;
case BlendingFactor.DstAlpha: return PICABlendFunc.DestinationAlpha;
case BlendingFactor.DstColor: return PICABlendFunc.DestinationColor;
case BlendingFactor.One: return PICABlendFunc.One;
case BlendingFactor.OneMinusConstantAlpha: return PICABlendFunc.OneMinusConstantAlpha;
case BlendingFactor.OneMinusConstantColor: return PICABlendFunc.OneMinusConstantColor;
case BlendingFactor.OneMinusDstAlpha: return PICABlendFunc.OneMinusDestinationAlpha;
case BlendingFactor.OneMinusDstColor: return PICABlendFunc.OneMinusDestinationColor;
case BlendingFactor.OneMinusSrcAlpha: return PICABlendFunc.OneMinusSourceAlpha;
case BlendingFactor.OneMinusSrcColor: return PICABlendFunc.OneMinusSourceColor;
case BlendingFactor.Src1Alpha: return PICABlendFunc.SourceAlpha;
case BlendingFactor.Src1Color: return PICABlendFunc.SourceColor;
case BlendingFactor.SrcAlpha: return PICABlendFunc.SourceColor;
case BlendingFactor.SrcColor: return PICABlendFunc.SourceColor;
case BlendingFactor.SrcAlphaSaturate: return PICABlendFunc.SourceAlphaSaturate;
case BlendingFactor.Zero: return PICABlendFunc.Zero;
default: return PICABlendFunc.Zero;
}
}
private PICATestFunc ConvertAlphaFunction(AlphaFunction func)
{
switch (func)
{
case AlphaFunction.Always: return PICATestFunc.Always;
case AlphaFunction.Equal: return PICATestFunc.Equal;
case AlphaFunction.Gequal: return PICATestFunc.Gequal;
case AlphaFunction.Greater: return PICATestFunc.Greater;
case AlphaFunction.Lequal: return PICATestFunc.Lequal ;
case AlphaFunction.Less: return PICATestFunc.Less;
case AlphaFunction.Never: return PICATestFunc.Never;
case AlphaFunction.Notequal: return PICATestFunc.Notequal;
default: return PICATestFunc.Always;
}
}
private SPICA.Math3D.RGBA ConvertRGBA(STColor8 color)
{
return new SPICA.Math3D.RGBA(color.R, color.G, color.B, color.A);
}
private SPICA.Math3D.RGBA ConvertRGBA(float R, float G, float B, float A = 255)
{
return new SPICA.Math3D.RGBA((byte)Utils.FloatToIntClamp(R),
(byte)Utils.FloatToIntClamp(G),
(byte)Utils.FloatToIntClamp(B),
(byte)Utils.FloatToIntClamp(A));
}
private H3DTextureMagFilter ConvertTexMagFilter(TextureFilter filterMode)
{
switch (filterMode)
{
case TextureFilter.LINEAR: return H3DTextureMagFilter.Linear;
case TextureFilter.NEAREST: return H3DTextureMagFilter.Nearest;
default: return H3DTextureMagFilter.Linear;
}
}
private H3DTextureMinFilter ConvertTexMinFilter(TextureFilter filterMode)
{
switch (filterMode)
{
case TextureFilter.LINEAR: return H3DTextureMinFilter.Linear;
case TextureFilter.LINEAR_MIPMAP_LINEAR: return H3DTextureMinFilter.LinearMipmapLinear;
case TextureFilter.LINEAR_MIPMAP_NEAREST: return H3DTextureMinFilter.LinearMipmapNearest;
case TextureFilter.NEAREST: return H3DTextureMinFilter.Nearest;
case TextureFilter.NEAREST_MIPMAP_LINEAR: return H3DTextureMinFilter.NearestMipmapLinear;
case TextureFilter.NEAREST_MIPMAP_NEAREST: return H3DTextureMinFilter.NearestMipmapNearest;
default: return H3DTextureMinFilter.Linear;
}
}
private PICATextureWrap ConvertWrapMode(CMBTextureWrapMode wrapMode)
{
switch (wrapMode)
{
case CMBTextureWrapMode.CLAMP: return PICATextureWrap.ClampToBorder;
case CMBTextureWrapMode.CLAMP_TO_EDGE: return PICATextureWrap.ClampToEdge;
case CMBTextureWrapMode.MIRRORED_REPEAT: return PICATextureWrap.Mirror;
case CMBTextureWrapMode.REPEAT: return PICATextureWrap.Repeat;
default: return PICATextureWrap.Repeat;
}
}
private string GetTextureName(int index)
{
if (index != -1 && index < CMBHeader.SectionData.TextureChunk?.Textures?.Count)
return CMBHeader.SectionData.TextureChunk.Textures[index].Name;
else
return "";
}
public LightTable LUTTable;
public struct LightTable
{
public bool reflectanceRSamplerIsAbs;
public LUTInput reflectanceRSamplerInput;
public uint reflectanceRSamplerScale;
public bool reflectanceGSamplerIsAbs;
public LUTInput reflectanceGSamplerInput;
public uint reflectanceGSamplerScale;
public bool reflectanceBSamplerIsAbs;
public LUTInput reflectanceBSamplerInput;
public uint reflectanceBSamplerScale;
public bool reflectance0SamplerIsAbs;
public LUTInput reflectance0SamplerInput;
public uint reflectance0SamplerScale;
public bool reflectance1SamplerIsAbs;
public LUTInput reflectance1SamplerInput;
public uint reflectance1SamplerScale;
public bool fresnelSamplerIsAbs;
public LUTInput fresnelSamplerInput;
public uint fresnelSamplerScale;
}
public uint TotalUsedTextures { get; set; }
public uint TotalUsedTextureCoords { get; set; }
public void Read(FileReader reader, Header header, MaterialChunk materialChunkParent)
{
int materialSize = 0x15C;
if (header.Version >= CMBVersion.MM3DS)
materialSize = 0x16C;
CMBHeader = header;
TextureMaps = new TextureMap[3];
TextureMaticies = new TextureMatrix[3];
TextureCombiners = new List<TextureCombiner>();
long pos = reader.Position;
IsFragmentLightingEnabled = reader.ReadBoolean();
IsVertexLightingEnabled = reader.ReadBoolean();
IsHemiSphereLightingEnabled = reader.ReadBoolean();
IsHemiSphereOcclusionEnabled = reader.ReadBoolean();
CullMode = reader.ReadEnum<CullMode>(true); //byte
IsPolygonOffsetEnabled = reader.ReadBoolean(); //byte
PolygonOffset = reader.ReadUInt16();
PolygonOffset = IsPolygonOffsetEnabled ? (ushort)((int)PolygonOffset / 0x10000) : (ushort)0;
TotalUsedTextures = reader.ReadUInt32();
TotalUsedTextureCoords = reader.ReadUInt32();
//Texture bind data
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();
}
LightSetIndex = reader.ReadUInt16();
FogIndex = reader.ReadUInt16();
for (int j = 0; j < 3; j++)
{
TextureMaticies[j] = new TextureMatrix();
TextureMaticies[j].Scale = reader.ReadVec2SY();
TextureMaticies[j].Rotate = reader.ReadSingle();
TextureMaticies[j].Translate = reader.ReadVec2SY();
TextureMaticies[j].MatrixMode = reader.ReadByte();
TextureMaticies[j].ReferenceCamera = reader.ReadByte();
TextureMaticies[j].MappingMethod = reader.ReadByte();
TextureMaticies[j].CoordinateIndex = reader.ReadByte();
}
long dataPos = reader.Position;
data = reader.ReadBytes(materialSize - (int)(dataPos - pos));
reader.SeekBegin(dataPos);
uint unkColor0 = reader.ReadUInt32();
ConstantColors = new STColor8[6];
ConstantColors[0] = STColor8.FromBytes(reader.ReadBytes(4));
ConstantColors[1] = STColor8.FromBytes(reader.ReadBytes(4));
ConstantColors[2] = STColor8.FromBytes(reader.ReadBytes(4));
ConstantColors[3] = STColor8.FromBytes(reader.ReadBytes(4));
ConstantColors[4] = STColor8.FromBytes(reader.ReadBytes(4));
ConstantColors[5] = STColor8.FromBytes(reader.ReadBytes(4));
BufferColorR = reader.ReadSingle();
BufferColorG = reader.ReadSingle();
BufferColorB = reader.ReadSingle();
BufferColorA = reader.ReadSingle();
BumpMapIndex = reader.ReadInt16();
BumpMapMode = reader.ReadUInt16();
IsBumpRenormalize = reader.ReadInt16();
reader.ReadInt16(); //padding
LayerConfig = (LayerConfig)reader.ReadUInt16();
reader.ReadInt16(); //padding
FresnelSelector = (FresnelSelector)reader.ReadUInt16();
IsClampHighLight = reader.ReadBoolean();
IsDistribution0Enabled = reader.ReadBoolean();
IsDistribution1Enabled = reader.ReadBoolean();
IsGeometricFactor0Enabled = reader.ReadBoolean();
IsGeometricFactor1Enabled = reader.ReadBoolean();
IsReflectionEnabled = reader.ReadBoolean();
// Fragment lighting table.
LUTTable.reflectanceRSamplerIsAbs = reader.ReadBoolean();
LUTTable.reflectanceRSamplerInput = (LUTInput)reader.ReadUInt16();
LUTTable.reflectanceRSamplerScale = reader.ReadUInt32();
LUTTable.reflectanceGSamplerIsAbs = reader.ReadBoolean();
LUTTable.reflectanceGSamplerInput = (LUTInput)reader.ReadUInt16();
LUTTable.reflectanceGSamplerScale = reader.ReadUInt32();
LUTTable.reflectanceBSamplerIsAbs = reader.ReadBoolean();
LUTTable.reflectanceBSamplerInput = (LUTInput)reader.ReadUInt16();
LUTTable.reflectanceBSamplerScale = reader.ReadUInt32();
LUTTable.reflectance0SamplerIsAbs = reader.ReadBoolean();
LUTTable.reflectance0SamplerInput = (LUTInput)reader.ReadUInt16();
LUTTable.reflectance0SamplerScale = reader.ReadUInt32();
LUTTable.reflectance1SamplerIsAbs = reader.ReadBoolean();
LUTTable.reflectance1SamplerInput = (LUTInput)reader.ReadUInt16();
LUTTable.reflectance1SamplerScale = reader.ReadUInt32();
LUTTable.fresnelSamplerIsAbs = reader.ReadBoolean();
LUTTable.fresnelSamplerInput = (LUTInput)reader.ReadUInt16();
LUTTable.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.ReadUInt16(); //padding
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;
BlendEnaled = reader.ReadBoolean();
//Unknown.
reader.ReadByte();
reader.ReadByte();
reader.ReadByte();
BlendingFactorSrcAlpha = (BlendingFactor)reader.ReadUInt16();
BlendingFactorDestAlpha = (BlendingFactor)reader.ReadUInt16();
BlendingEquationAlpha = (BlendEquationMode)reader.ReadUInt16();
BlendingFactorSrcRGB = (BlendingFactor)reader.ReadUInt16();
BlendingFactorDestRGB = (BlendingFactor)reader.ReadUInt16();
BlendingEquationRGB = (BlendEquationMode)reader.ReadUInt16();
BlendColorR = reader.ReadSingle();
BlendColorG = reader.ReadSingle();
BlendColorB = reader.ReadSingle();
BlendColorA = reader.ReadSingle();
IsTransparent = BlendEnaled;
if (header.Version > CMBVersion.OOT3DS)
{
byte StencilEnabled = reader.ReadByte();
byte StencilReferenceValue = reader.ReadByte();
byte BufferMask = reader.ReadByte();
byte Buffer = reader.ReadByte();
ushort StencilFunc = reader.ReadUInt16();
ushort FailOP = reader.ReadUInt16();
ushort ZFailOP = reader.ReadUInt16();
ushort ZPassOP = reader.ReadUInt16();
float unk6 = reader.ReadSingle();
}
}
public void Write(FileWriter writer, Header header)
{
long pos = writer.Position;
writer.Write(IsFragmentLightingEnabled);
writer.Write(IsVertexLightingEnabled);
writer.Write(IsHemiSphereLightingEnabled);
writer.Write(IsHemiSphereOcclusionEnabled);
writer.Write(CullMode, true);
writer.Write(IsPolygonOffsetEnabled);
writer.Write(PolygonOffset);
writer.Write(TotalUsedTextures);
writer.Write(TotalUsedTextureCoords);
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(TextureMaps[j].MinLOD);
writer.Write(TextureMaps[j].LodBias);
writer.Write(TextureMaps[j].borderColorR);
writer.Write(TextureMaps[j].borderColorG);
writer.Write(TextureMaps[j].borderColorB);
writer.Write(TextureMaps[j].borderColorA);
}
writer.Write(LightSetIndex);
writer.Write(FogIndex);
for (int j = 0; j < 3; j++)
{
writer.Write(TextureMaticies[j].Scale);
writer.Write(TextureMaticies[j].Rotate);
writer.Write(TextureMaticies[j].Translate);
writer.Write(TextureMaticies[j].MatrixMode);
writer.Write(TextureMaticies[j].ReferenceCamera);
writer.Write(TextureMaticies[j].MappingMethod);
writer.Write(TextureMaticies[j].CoordinateIndex);
}
writer.Write(data);
}
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 Syroot.Maths.Vector2F Scale { get; set; }
public Syroot.Maths.Vector2F Translate { get; set; }
public float Rotate { get; set; }
public byte MatrixMode { get; set; }
public byte ReferenceCamera { get; set; }
public byte MappingMethod { get; set; }
public byte CoordinateIndex { 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
writer.Write(Textures.Count);
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);
}
}
}