2447 lines
104 KiB
C#
2447 lines
104 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 = 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;
|
|
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)
|
|
{
|
|
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, 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);
|
|
}
|
|
|
|
//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]];
|
|
// vert.boneIds.Add((int)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++)
|
|
{
|
|
Console.WriteLine("weight " + BoneWeights[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 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;
|
|
|
|
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, 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(1);
|
|
else if (header.Version == CMBVersion.MM3DS)
|
|
mesh.unks = reader.ReadBytes(9);
|
|
else if (header.Version >= CMBVersion.LM3DS)
|
|
mesh.unks = reader.ReadBytes(85);
|
|
|
|
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
|
|
{
|
|
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 uint PolygonOffset { get; set; }
|
|
|
|
public TextureMap[] TextureMaps { get; set; }
|
|
public TextureMatrix[] TextureMaticies { 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 Unknown1 { get; set; }
|
|
public ushort Unknown2 { get; set; }
|
|
public ushort Unknown3 { 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 ? PolygonOffset / 0x10000 : 0;
|
|
Unknown1 = reader.ReadUInt32();
|
|
Unknown2 = reader.ReadUInt16();
|
|
Unknown3 = reader.ReadUInt16();
|
|
|
|
//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();
|
|
}
|
|
|
|
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(Unknown1);
|
|
writer.Write(Unknown2);
|
|
writer.Write(Unknown3);
|
|
|
|
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);
|
|
}
|
|
|
|
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 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
|
|
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);
|
|
}
|
|
}
|
|
}
|