Files
SM64Lib/SM64Lib/Model/Conversion/Fast3DWriting/Fast3DWriter.cs
2024-08-18 09:25:16 +02:00

2333 lines
79 KiB
C#

using global::SM64Lib.N64Graphics;
using global::System.Drawing;
using global::System.Numerics;
using Microsoft.VisualBasic.CompilerServices;
using System.Data;
using System.Runtime.CompilerServices;
using Z.Collections.Extensions;
using static Microsoft.VisualBasic.Conversion;
namespace SM64Lib.Model.Conversion.Fast3DWriting;
public enum TextureFilter
{
Point = 0,
Bilerp = 2,
Average = 3
}
public enum PaletteColorStorageType
{
None = 0,
RGBA16 = 2,
IA16 = 3
}
/// <summary>
/// Reduces the number of duplicate verticies.
/// </summary>
public enum ReduceVericesLevel : byte
{
/// <summary>
/// No reduction.
/// </summary>
Level0 = 0,
/// <summary>
/// Reduce only in the same 0x04 group. (Best choise!)
/// </summary>
Level1 = 1,
/// <summary>
/// Reduce and push up. (A little buggy!)
/// </summary>
Level2 = 2
}
public class ConvertResult
{
public States State { get; set; } = States.Successfully;
public uint PtrStart { get; set; } = 0;
public uint PtrVertex { get; set; } = 0;
public List<SM64Lib.Geolayout.Geopointer> PtrGeometry { get; private set; } = [];
public List<SM64Lib.Levels.ScrolTex.ManagedScrollingTexture> ScrollingCommands { get; private set; } = [];
public Dictionary<short, string> ScrollingNames { get; private set; } = [];
public MemoryStream Data { get; private set; } = new MemoryStream();
public enum States
{
Successfully,
Error
}
}
public class ConvertSettings
{
public uint? SegmentedAddress { get; set; }
public uint SizeLimit { get; set; } = 0x150000;
public double Scale { get; set; } = 500;
public Vector3 OffsetPosition { get; set; } = Vector3.Zero;
public ReduceVericesLevel ReduceVertLevel { get; set; } = ReduceVericesLevel.Level1;
public string ColorTexData { get; set; } = "";
public string GeoModeData { get; set; } = "";
public string TexTypeData { get; set; } = "";
public bool ResizeTextures { get; set; } = false;
public bool CenterModel { get; set; } = false;
public Model.Fog Fog { get; set; } = null;
public string CollisionData { get; set; } = "";
public sbyte ForceDisplaylist { get; set; } = -1;
public float TransparencyLimit { get; set; } = 0.5f;
public Model.Fast3D.TextureFormatSettings TextureFormatSettings { get; set; } = null;
public bool EnableFog
{
get
{
return Fog is object;
}
}
}
public class SettingsException : Exception
{
public SettingsException() : base("Undefined ConvertSettings error.")
{
}
public SettingsException(string message) : base(message)
{
}
}
public class Fast3DWriter
{
private enum MaterialType
{
None,
TextureSolid,
TextureAlpha,
TextureTransparent,
ColorSolid,
ColorTransparent
}
private class Vertex
{
public short X { get; set; } = 0;
public short Y { get; set; } = 0;
public short Z { get; set; } = 0;
}
private class Normal
{
public byte A { get; set; } = 0;
public byte B { get; set; } = 0;
public byte C { get; set; } = 0;
public byte D { get; set; } = 0;
}
private class VertexColor
{
public byte R { get; set; } = 0;
public byte G { get; set; } = 0;
public byte B { get; set; } = 0;
public byte A { get; set; } = 0;
}
private class TexCord
{
public float U { get; set; } = 0;
public float V { get; set; } = 0;
}
private class Material
{
public string Name { get; set; } = "";
public bool HasTexture { get; set; }
public bool HasPalette { get; set; }
public bool HasTextureAlpha { get; set; }
public bool HasTransparency { get; set; }
public bool EnableTextureColor { get; set; }
public bool EnableAlphaMask { get; set; } // For I4/I8 textures.
public bool CameFromBMP { get; set; } // For I4/I8 textures.
public uint Color { get; set; } = 0;
public byte Opacity { get; set; } = 0;
public byte OpacityOrg { get; set; } = 0;
public uint Offset { get; set; } = 0;
public uint PaletteOffset { get; set; } = 0;
public uint Size { get; set; } = 0;
public uint PaletteSize { get; set; } = 0;
public uint TexWidth { get; set; } = 0;
public uint TexHeight { get; set; } = 0;
public MaterialType Type { get; set; } = MaterialType.None;
public N64Codec TexType { get; set; } = N64Codec.RGBA16;
public ushort Collision { get; set; } = 0;
public byte Collisionp1 { get; set; } = 0;
public byte Collisionp2 { get; set; } = 0;
public bool EnableGeoMode { get; set; }
public uint GeoMode { get; set; } = 0;
public TextureEntry Texture { get; set; } = null;
public bool EnableScrolling { get; set; } = false;
public DisplaylistSelectionSettings DisplaylistSelection { get; set; } = new Fast3DWriting.DisplaylistSelectionSettings();
public FaceCullingMode FaceCullingMode { get; set; } = Conversion.FaceCullingMode.Back;
public bool EnableMirrorS { get; set; } = false;
public bool EnableMirrorT { get; set; } = false;
public bool EnableClampS { get; set; } = false;
public bool EnableClampT { get; set; } = false;
public bool EnableCrystalEffect { get; set; } = false;
public float? TransparencyLimit { get; set; } = 0;
public TextureFilter TexFilter { get; set; } = TextureFilter.Bilerp;
}
private class FinalVertexData
{
public byte[] Data { get; set; } = new byte[16];
public bool EnableVertexColor { get; set; } = false;
public bool EnableVertexTransparent
{
get
{
byte db = Data.LastOrDefault();
return db < 0xFF;
}
}
}
private class FvGroup
{
public short NumTri { get; set; } = 0;
public sbyte[] indexList = new sbyte[2049];
public List<FinalVertexData> FinalVertexData { get; private set; } = [];
public short VertexDataCount
{
get
{
return Convert.ToInt16(FinalVertexData.Count);
}
}
public bool EnableVertexColors
{
get
{
foreach (FinalVertexData fvd in FinalVertexData)
{
if (fvd.EnableVertexColor)
return true;
}
return false;
}
}
public bool EnableVertexAlpha
{
get
{
foreach (FinalVertexData fvd in FinalVertexData)
{
if (fvd.EnableVertexTransparent)
return true;
}
return false;
}
}
}
private class F3D
{
public byte[] data = new byte[8];
}
private class VertexGroupList
{
public int Position { get; set; } = 0;
public int Length { get; set; } = 0;
public Material Material { get; set; } = null;
public int GroupsCount
{
get
{
return FinalVertexGroups.Count;
}
}
public int StartIndex { get; set; } = 0;
public List<FvGroup> FinalVertexGroups { get; private set; } = [];
public bool EnableVertexColors
{
get
{
foreach (FvGroup fvg in FinalVertexGroups)
{
foreach (FinalVertexData fvd in fvg.FinalVertexData)
{
if (fvd.EnableVertexColor)
return true;
}
}
return false;
}
}
public bool EnableVertexAlpha
{
get
{
if (!EnableVertexColors)
return false;
foreach (FvGroup fvg in FinalVertexGroups)
{
foreach (FinalVertexData fvd in fvg.FinalVertexData)
{
if (fvd.EnableVertexColor)
{
if (fvd.Data.LastOrDefault() < 0xFF)
return true;
}
}
}
return false;
}
}
}
private class TextureEntry
{
public uint Width { get; set; } = 0;
public uint Height { get; set; } = 0;
public byte[] Data { get; set; } = Array.Empty<byte>();
public byte[] Palette { get; set; } = Array.Empty<byte>();
public Image OriginalImage { get; set; } = null;
}
private class ObjPtrs
{
public string name = Convert.ToString(0);
public uint start_ptr = 0;
public uint import_lenght = 0;
public uint solid_ptr = 0;
public uint alpha_ptr = 0;
public uint trans_ptr = 0;
public uint geo_ptr = 0;
public uint col_ptr = 0;
public uint vertex_start = 0;
}
public struct ScrollTex
{
public int Offset { get; set; }
public int VertsCount { get; set; }
public int MaterialAddress { get; set; }
public string MaterialName { get; set; }
public ScrollTex(int offset, byte faceCount, int matAddr, string matName)
{
Offset = offset;
VertsCount = faceCount;
MaterialAddress = matAddr;
MaterialName = matName;
}
}
private List<Vertex> verts = [];
private List<Normal> norms = [];
private List<VertexColor> vertexColors = [];
private List<TexCord> uvs = [];
private List<Material> materials = [];
private List<Pilz.S3DFileParser.Material> ignoreFacesWithMaterial = [];
private Dictionary<Pilz.S3DFileParser.Material, Material> materialBindings = [];
private List<VertexGroupList> vertexGroups = [];
private List<FinalVertexData> finalVertData = [];
private List<TextureEntry> textureBank = [];
private List<ScrollTex> scrollTexts = [];
private Material currentMaterial;
private int currentFace = 0;
private const byte GEOLAYER_SOLID = 1;
private const byte GEOLAYER_ALPHA = 4;
private const byte GEOLAYER_TRANSPARENT = 5;
private byte curSeg = 0;
private uint startSegOffset = 0;
private byte[] defaultColor = new byte[24];
private ConvertSettings settings = null;
private SM64Lib.Data.BinaryData impdata = null;
private ConvertResult conRes = new();
private readonly byte[] ColtypesWithParams = new byte[] { 14, 44, 36, 37, 39, 45 };
private uint CurSegAddress
{
get
{
return Convert.ToUInt32(curSeg) << 24;
}
}
private object TXL2WORDS(object txls, object b_txl)
{
return NewLateBinding.LateGet(null, typeof(Math), "Max", new object[]
{
1,
Operators.DivideObject(Operators.MultiplyObject(txls, b_txl), 8)
}, null, null, null);
}
private object CALC_DXT(object width, object b_txl)
{
return Operators.DivideObject(2048, this.TXL2WORDS(RuntimeHelpers.GetObjectValue(width), RuntimeHelpers.GetObjectValue(b_txl)));
}
private object TXL2WORDS_4b(object txls)
{
return NewLateBinding.LateGet(null, typeof(Math), "Max", new object[]
{
1,
Operators.DivideObject(txls, 16)
}, null, null, null);
}
private object CALC_DXT_4b(object width)
{
return Operators.DivideObject(2048, this.TXL2WORDS_4b(RuntimeHelpers.GetObjectValue(width)));
}
private void SetLightAndDarkValues(Pilz.S3DFileParser.Shading s)
{
// Ambient light color
defaultColor[0] = s.AmbientColor.R;
defaultColor[1] = s.AmbientColor.G;
defaultColor[2] = s.AmbientColor.B;
defaultColor[3] = 0;
defaultColor[4] = s.AmbientColor.R;
defaultColor[5] = s.AmbientColor.G;
defaultColor[6] = s.AmbientColor.B;
defaultColor[7] = 0;
// Diffuse light color
defaultColor[8] = s.DiffuseColor.R;
defaultColor[9] = s.DiffuseColor.G;
defaultColor[10] = s.DiffuseColor.B;
defaultColor[11] = 0;
defaultColor[12] = s.DiffuseColor.R;
defaultColor[13] = s.DiffuseColor.G;
defaultColor[14] = s.DiffuseColor.B;
defaultColor[15] = 0;
// Diffuse light direction
if (s.DiffusePosition is not null)
{
float d = (float)(127 / Math.Sqrt(s.DiffusePosition.X * s.DiffusePosition.X + s.DiffusePosition.Y * s.DiffusePosition.Y + s.DiffusePosition.Z * s.DiffusePosition.Z));
if (float.IsInfinity(d))
d = 0;
defaultColor[16] = (byte)Convert.ToSByte(Math.Round(s.DiffusePosition.X * d));
defaultColor[17] = (byte)Convert.ToSByte(Math.Round(s.DiffusePosition.Y * d));
defaultColor[18] = (byte)Convert.ToSByte(Math.Round(s.DiffusePosition.Z * d));
}
else
{
defaultColor[16] = 0x49; // = Most SM64E Like ||| Default by Nintendo: &H28
defaultColor[17] = 0x49;
defaultColor[18] = 0x49;
}
defaultColor[19] = 0;
defaultColor[20] = 0;
defaultColor[21] = 0;
defaultColor[22] = 0;
defaultColor[23] = 0;
}
private void ResetVariables()
{
vertexGroups.Clear();
verts.Clear();
norms.Clear();
vertexColors.Clear();
uvs.Clear();
materials.Clear();
finalVertData.Clear();
currentFace = 0;
}
private void CheckGeoModeInfo(Material m)
{
m.GeoMode = 0;
var gma = settings.GeoModeData.Split(',');
foreach (string gme in gma)
{
var gmd = gme.Split(':');
if (m.Name.Equals(gmd[0]))
{
m.GeoMode = Convert.ToUInt32(gmd[1]);
m.EnableGeoMode = true;
return;
}
}
}
private void CheckColorTexInfo(Material m)
{
m.Color = 0;
var gma = settings.ColorTexData.Split(',');
foreach (string gme in gma)
{
var gmd = gme.Split(':');
if (m.Name.Equals(gmd[0]))
{
m.Color = Convert.ToUInt32(gmd[1]);
m.EnableTextureColor = true;
return;
}
}
}
private void processMaterialColor(string str, Material mat)
{
var splitColor = str.Replace(".", ",").Split(' ');
uint r = (uint)(Convert.ToSingle(splitColor[0]) * 255);
uint g = (uint)(Convert.ToSingle(splitColor[1]) * 255);
uint b = (uint)(Convert.ToSingle(splitColor[2]) * 255);
mat.Color = Convert.ToUInt32(r << 24 | g << 16 | b << 8 | (long)0xFF);
}
private void processMaterialColorAlpha(float alpha, Material mat)
{
mat.Color &= 0xFFFFFF00U;
mat.Color |= Convert.ToByte(Convert.ToInt64(0xFF * alpha) & 0xFF);
if (alpha < 1.0F)
{
mat.Type = MaterialType.ColorTransparent;
mat.HasTransparency = true;
}
else if (!mat.HasTexture)
{
mat.Type = MaterialType.ColorSolid;
}
}
private void checkN64CodecInfo(Material m)
{
var gma = settings.TexTypeData.Split(',');
foreach (string gme in gma)
{
var gmd = gme.Split(':');
if (m.Name.Equals(gmd[0]))
{
var switchExpr = gmd[1];
switch (switchExpr)
{
case "rgba16":
{
m.TexType = N64Codec.RGBA16;
break;
}
case "rgba32":
{
m.TexType = N64Codec.RGBA32;
break;
}
case "ia4":
{
m.TexType = N64Codec.IA4;
break;
}
case "ia8":
{
m.TexType = N64Codec.IA8;
break;
}
case "ia16":
{
m.TexType = N64Codec.IA16;
break;
}
case "i4":
{
m.TexType = N64Codec.I4;
if (gmd.Count() > 2 && (gmd[2] ?? "") == "a")
m.EnableAlphaMask = true;
break;
}
case "i8":
{
m.TexType = N64Codec.I8;
if (gmd.Count() > 2 && (gmd[2] ?? "") == "a")
m.EnableAlphaMask = true;
break;
}
}
return;
}
}
m.TexType = N64Codec.RGBA16;
}
private Material[] GetDuplicates(Material mat)
{
var foundCopies = new List<Material>();
if (mat.HasTexture)
{
foreach (Material checkMat in materials)
{
if (checkMat != mat)
{
if (checkMat.HasTexture)
{
if (mat.TexType == checkMat.TexType && SM64Lib.General.CompareTwoByteArrays(mat.Texture.Data, checkMat.Texture.Data))
{
if (!foundCopies.Contains(checkMat))
{
foundCopies.Add(checkMat);
}
}
}
}
}
}
// For Each copyMat As Material In foundCopies
// copyMat.texture = mat.texture
// copyMat.offset = mat.offset
// copyMat.texWidth = mat.texWidth
// copyMat.texHeight = mat.texHeight
// copyMat.size = 0
// copyMat.isTextureCopy = True
// Next
return foundCopies.ToArray();
}
private void MergeDuplicatedTextures()
{
var matsToRemove = new List<Material>();
foreach (Material mat in materials)
{
if (!matsToRemove.Contains(mat))
{
foreach (Material dup in GetDuplicates(mat))
{
if (!matsToRemove.Contains(dup))
{
// Remove material
matsToRemove.Add(dup);
// Update material references for vertex group
foreach (VertexGroupList mp in vertexGroups)
{
if (mp.Material == dup)
mp.Material = mat;
}
// Update material references for material bindings
foreach (var kvp in materialBindings)
{
if (kvp.Value == dup)
materialBindings[kvp.Key] = mat;
}
}
}
}
}
foreach (Material mat in matsToRemove)
{
if (mat.HasTexture && textureBank.Contains(mat.Texture))
textureBank.Remove(mat.Texture);
materials.Remove(mat);
}
}
private void ProcessImage(Pilz.S3DFileParser.Object3D obj, Image img, Material mat)
{
TextureEntry entry = null;
foreach (TextureEntry tex in textureBank)
{
if (tex.OriginalImage == img)
{
entry = tex;
}
}
// Create & Add texture entry
entry ??= new TextureEntry()
{
Width = mat.TexWidth,
Height = mat.TexHeight,
OriginalImage = img
};
// Load Texture from File
var bmp = new Bitmap(img);
// Set texture size
mat.TexWidth = Convert.ToUInt32(bmp.Width);
mat.TexHeight = Convert.ToUInt32(bmp.Height);
// Convert texture
var argimageData = entry.Data;
var argpaletteData = entry.Palette;
N64Graphics.N64Graphics.Convert(ref argimageData, ref argpaletteData, mat.TexType, (Bitmap)img);
entry.Data = argimageData;
entry.Palette = argpaletteData;
mat.Type = MaterialType.TextureSolid;
// Check for alpha and transparency
{
var transparentPixels = 0;
var setAsAlpha = false;
var setAsTransparent = false;
// Check textures
for (int y = 0, loopTo = bmp.Height - 1; y <= loopTo; y++)
{
for (int x = 0, loopTo1 = bmp.Width - 1; x <= loopTo1; x++)
{
var pix = bmp.GetPixel(x, y);
switch (mat.TexType)
{
case N64Codec.RGBA16:
case N64Codec.RGBA32:
case N64Codec.IA4:
case N64Codec.IA8:
case N64Codec.IA16:
case N64Codec.CI4:
case N64Codec.CI8:
if (pix.A == 0)
setAsAlpha = true;
else if (pix.A < 0xFF)
transparentPixels++;
break;
case N64Codec.I4:
case N64Codec.I8:
if (pix.A < 0xFF || mat.EnableAlphaMask)
transparentPixels++;
break;
}
}
}
// Set as transparent if reached the limit
var transparentPixelsPercent = (float)transparentPixels / (mat.TexWidth * mat.TexHeight);
var reachedTransparentLimit = transparentPixelsPercent > (mat.TransparencyLimit ?? settings.TransparencyLimit);
switch (mat.TexType)
{
case N64Codec.RGBA16:
case N64Codec.RGBA32:
case N64Codec.IA4:
case N64Codec.IA8:
case N64Codec.IA16:
case N64Codec.CI4:
case N64Codec.CI8:
if (reachedTransparentLimit)
setAsTransparent = true;
break;
case N64Codec.I4:
case N64Codec.I8:
if (reachedTransparentLimit || mat.EnableAlphaMask)
setAsTransparent = true;
break;
}
if (setAsTransparent)
{
mat.Type = MaterialType.TextureTransparent;
mat.HasTransparency = true;
}
else if (setAsAlpha)
{
mat.HasTextureAlpha = true;
mat.Type = MaterialType.TextureAlpha;
}
}
if (!textureBank.Contains(entry))
textureBank.Add(entry);
mat.Texture = entry;
mat.HasTexture = true;
mat.HasPalette = entry.Palette.Any();
}
private void ProcessObject3DModel(Pilz.S3DFileParser.Object3D obj)
{
var texFormatSettings = settings.TextureFormatSettings;
// Process Materials
ProcessObject3DMaterials(obj, texFormatSettings);
foreach (Pilz.S3DFileParser.Mesh mesh in obj.Meshes)
{
int curIndexStart = verts.Count;
// Process Vertices
foreach (var vert in mesh.Vertices)
{
var v = new Vertex()
{
X = General.KeepInInt16Range(General.Round(vert.X * settings.Scale + settings.OffsetPosition.X)),
Y = General.KeepInInt16Range(General.Round(vert.Y * settings.Scale + settings.OffsetPosition.Y)),
Z = General.KeepInInt16Range(General.Round(vert.Z * settings.Scale + settings.OffsetPosition.Z))
};
verts.Add(v);
}
// Process Normals
foreach (var norm in mesh.Normals)
{
var n = new Normal()
{
A = Datatypecastes.LongToByte((long)General.Round(norm.X * 0x7F)),
B = Datatypecastes.LongToByte((long)General.Round(norm.Y * 0x7F)),
C = Datatypecastes.LongToByte((long)General.Round(norm.Z * 0x7F)),
D = 0xFF
};
norms.Add(n);
}
// Process UVs
foreach (Pilz.S3DFileParser.UV tuv in mesh.UVs)
{
var uv = new TexCord()
{
U = (float)General.Round(tuv.U * 32F * 32F),
V = (float)General.Round(-((tuv.V - 1) * 32F * 32F))
};
uvs.Add(uv);
}
// Process Vertex Colors
foreach (var vertcol in mesh.VertexColors)
{
var vc = new VertexColor()
{
R = Datatypecastes.LongToByte((long)General.Round(vertcol.R * 0xFF)),
G = Datatypecastes.LongToByte((long)General.Round(vertcol.G * 0xFF)),
B = Datatypecastes.LongToByte((long)General.Round(vertcol.B * 0xFF)),
A = Datatypecastes.LongToByte((long)General.Round(vertcol.A * 0xFF))
};
vertexColors.Add(vc);
}
// Process Faces
object curTexture = null;
foreach (var face in mesh.Faces) // .OrderBy(Function(n) n.Texture)
{
if (!ignoreFacesWithMaterial.Contains(face.Material))
{
if (curTexture is null || !curTexture.Equals(face.Material))
{
curTexture = face.Material;
string curMatName = obj.Materials.FirstOrDefault(n => n.Value.Equals(face.Material)).Key;
var curMat = materials.FirstOrDefault(n => n.Name.Equals(curMatName));
var mp = new VertexGroupList()
{
Position = currentFace,
Material = curMat
};
currentMaterial = curMat;
mp.Length = 0;
vertexGroups.Add(mp);
}
materialBindings.TryGetValue(face.Material, out Material mat);
Vertex va = null;
TexCord ta = null;
var tanew = new TexCord();
Normal na = null;
VertexColor vca = null;
Vertex vb = null;
TexCord tb = null;
var tbnew = new TexCord();
Normal nb = null;
VertexColor vcb = null;
Vertex vc = null;
TexCord tc = null;
var tcnew = new TexCord();
Normal nc = null;
VertexColor vcc = null;
void getVals(Pilz.S3DFileParser.Point point, ref Vertex vert, ref TexCord t, ref Normal normal, ref VertexColor vertcol)
{
if (point.Vertex is not null)
vert = verts[curIndexStart + mesh.Vertices.IndexOf(point.Vertex)];
if (point.UV is not null)
t = uvs[curIndexStart + mesh.UVs.IndexOf(point.UV)];
if (point.Normal is not null)
normal = norms[curIndexStart + mesh.Normals.IndexOf(point.Normal)];
if (point.VertexColor is not null)
vertcol = vertexColors[curIndexStart + mesh.VertexColors.IndexOf(point.VertexColor)];
};
getVals(face.Points[0], ref va, ref ta, ref na, ref vca);
getVals(face.Points[1], ref vb, ref tb, ref nb, ref vcb);
getVals(face.Points[2], ref vc, ref tc, ref nc, ref vcc);
var fa = new FinalVertexData();
var fb = new FinalVertexData();
var fc = new FinalVertexData();
// Modify UV cordinates based on material.
void modifyUVCordinates(TexCord tnew, TexCord t)
{
tnew.U = (float)(t.U * (mat.TexWidth / 32.0) - 16); // "-16" fixes the UVs offset
tnew.V = (float)(t.V * (mat.TexHeight / 32.0) - 16); // "-16" fixes the UVs offset
};
modifyUVCordinates(tanew, ta);
modifyUVCordinates(tbnew, tb);
modifyUVCordinates(tcnew, tc);
// Fix UVs to reduce number of (large) faces with broken textures
FixUVs(tanew, tbnew, tcnew, Convert.ToInt32(mat.TexWidth), Convert.ToInt32(mat.TexHeight));
// Vertex Structure: xxxxyyyyzzzz0000uuuuvvvvrrggbbaa
void buildVertexStructure(FinalVertexData final, Vertex vert, VertexColor vertcol, TexCord tnew, Normal normal)
{
final.Data[0] = Convert.ToByte(vert.X >> 8 & 0xFF);
final.Data[1] = Convert.ToByte(vert.X & 0xFF);
final.Data[2] = Convert.ToByte(vert.Y >> 8 & 0xFF);
final.Data[3] = Convert.ToByte(vert.Y & 0xFF);
final.Data[4] = Convert.ToByte(vert.Z >> 8 & 0xFF);
final.Data[5] = Convert.ToByte(vert.Z & 0xFF);
final.Data[6] = 0;
final.Data[7] = 0;
int uInt, vInt;
uInt = Convert.ToInt32(Math.Round(tnew.U));
vInt = Convert.ToInt32(Math.Round(tnew.V));
final.Data[8] = Convert.ToByte(uInt >> 8 & 0xFF);
final.Data[9] = Convert.ToByte(uInt & 0xFF);
final.Data[10] = Convert.ToByte(vInt >> 8 & 0xFF);
final.Data[11] = Convert.ToByte(vInt & 0xFF);
if (vertcol is not null && !mat.EnableCrystalEffect)
{
final.Data[12] = vertcol.R;
final.Data[13] = vertcol.G;
final.Data[14] = vertcol.B;
final.Data[15] = vertcol.A;
final.EnableVertexColor = true;
// FIXME: Add warning if Type is not TextureSolid
if (final.EnableVertexTransparent)
{
mat.Type = MaterialType.TextureTransparent;
mat.HasTransparency = mat.HasTransparency || final.EnableVertexTransparent;
}
}
else
{
final.Data[12] = normal.A;
final.Data[13] = normal.B;
final.Data[14] = normal.C;
final.Data[15] = normal.D;
final.EnableVertexColor = false;
}
};
buildVertexStructure(fa, va, vca, tanew, na);
buildVertexStructure(fb, vb, vcb, tbnew, nb);
buildVertexStructure(fc, vc, vcc, tcnew, nc);
finalVertData.AddRange(new[] { fa, fb, fc });
currentFace += 1;
}
}
}
}
private void ProcessObject3DMaterials(Pilz.S3DFileParser.Object3D obj, Model.Fast3D.TextureFormatSettings texFormatSettings)
{
var tasks = new List<Task>();
// Start converting each image
foreach (var kvp in obj.Materials)
{
ProcessObject3DMaterial(obj, kvp, texFormatSettings);
}
}
private void ProcessObject3DMaterial(Pilz.S3DFileParser.Object3D obj, KeyValuePair<string, Pilz.S3DFileParser.Material> kvp, Model.Fast3D.TextureFormatSettings texFormatSettings)
{
int size = 0;
var curEntry = texFormatSettings.GetEntry(kvp.Key);
if (curEntry.Include)
{
// Create new Material
var m = new Material()
{
Type = MaterialType.ColorSolid,
Color = 0,
HasTexture = false,
HasTextureAlpha = false,
HasTransparency = false,
Name = kvp.Key,
Collision = 0,
Opacity = 0xFF,
OpacityOrg = 0xFF,
EnableGeoMode = false,
EnableTextureColor = false,
EnableAlphaMask = false,
CameFromBMP = false,
EnableScrolling = curEntry.IsScrollingTexture,
DisplaylistSelection = curEntry.DisplaylistSelection,
EnableMirrorS = curEntry.EnableMirrorS,
EnableMirrorT = curEntry.EnableMirrorT,
EnableClampS = curEntry.EnableClampS,
EnableClampT = curEntry.EnableClampT,
EnableCrystalEffect = curEntry.EnableCrystalEffect,
FaceCullingMode = curEntry.FaceCullingMode,
TransparencyLimit = curEntry.TransparencyLimit,
TexFilter = curEntry.TextureFilter
};
// Set default size
size = 0x10;
// Check some things
CheckGeoModeInfo(m);
CheckColorTexInfo(m);
// Add material
materials.Add(m);
materialBindings.Add(kvp.Value, m);
// Process Material Color
if (!m.EnableTextureColor)
{
uint r = kvp.Value.Color.Value.R;
uint g = kvp.Value.Color.Value.G;
uint b = kvp.Value.Color.Value.B;
uint a = kvp.Value.Color.Value.A;
m.Color = r << 24 | g << 16 | b << 8 | a;
if (a == (long)0xFF)
{
m.Type = MaterialType.ColorSolid;
}
else
{
m.Type = MaterialType.ColorTransparent;
}
}
// Check Texture Type
if (texFormatSettings is not null)
{
m.TexType = N64Graphics.N64Graphics.StringCodec(texFormatSettings.GetEntry(kvp.Key).TextureFormat);
}
// Process Material Image
if (kvp.Value.Image is not null)
{
ProcessImage(obj, kvp.Value.Image, m);
size = m.Texture.Data.Length;
}
// Process Material Color Alpha
if (kvp.Value.Opacity is not null)
{
float tempopacity = (float)kvp.Value.Opacity;
m.Opacity = Convert.ToByte(Convert.ToInt64(tempopacity * 0xFF) & 0xFF);
m.OpacityOrg = m.Opacity;
processMaterialColorAlpha(tempopacity, m);
}
// Set offset and size
m.Size = Convert.ToUInt32(size);
if (m.Texture?.Palette is not null)
{
m.PaletteSize = Convert.ToUInt32(m.Texture.Palette.Length);
}
}
else
ignoreFacesWithMaterial.Add(kvp.Value);
}
private void FixUVs(TexCord uv1, TexCord uv2, TexCord uv3, int matWidth, int matHeight)
{
if (matWidth <= 0 || matHeight <= 0) return;
TexCord[] uvs = null;
int jump = 0;
float RoundToJump(float val)
{
var mod = val % jump;
if (mod >= 0.5 * jump)
val = jump * (float)Math.Ceiling(val / jump); // Round up
else
val = jump * (float)Math.Floor(val / jump); // Round down
return val;
}
jump = matWidth * 0x40;
uvs = new[] { uv1, uv2, uv3 }.OrderBy(n => n.U).ToArray();
if (jump != 0)
{
// Move near 0
float varM;
varM = RoundToJump((uvs[0].U + uvs[2].U) / 2);
uvs[0].U -= varM;
uvs[1].U -= varM;
uvs[2].U -= varM;
// Keep in bounds (max bounds)
while (uvs.Last().U > short.MaxValue)
{
uvs[0].U -= jump;
uvs[1].U -= jump;
uvs[2].U -= jump;
}
// Keep in bounds (min bounds)
while (uvs.First().U < short.MinValue)
{
uvs[0].U += jump;
uvs[1].U += jump;
uvs[2].U += jump;
}
}
jump = matHeight * 0x40;
uvs = new[] { uv1, uv2, uv3 }.OrderBy(n => n.V).ToArray();
if (jump != 0)
{
// Move near 0
float varM;
varM = RoundToJump((uvs[0].V + uvs[2].V) / 2);
uvs[0].V -= varM;
uvs[1].V -= varM;
uvs[2].V -= varM;
// Keep in bounds (max bounds)
while (uvs.Last().V > short.MaxValue)
{
uvs[0].V -= jump;
uvs[1].V -= jump;
uvs[2].V -= jump;
}
// Keep in bounds (min bounds)
while (uvs.First().V < short.MinValue)
{
uvs[0].V += jump;
uvs[1].V += jump;
uvs[2].V += jump;
}
}
}
private void removeDuplicateVertices(ReduceVericesLevel level)
{
if ((int)level < 1)
return;
int dupCnt = 0;
foreach (VertexGroupList mp in vertexGroups)
{
for (int g = 0; g < mp.GroupsCount; g++)
{
var fvg = mp.FinalVertexGroups[g];
if (fvg.VertexDataCount < 1) continue;
for (int i = 0; i < fvg.VertexDataCount; i++)
{
int j = i + 1;
while (j < fvg.VertexDataCount)
{
if (SM64Lib.General.CompareTwoByteArrays(fvg.FinalVertexData[i].Data, fvg.FinalVertexData[j].Data, 16))
{
moveElementsInGroupUpward(fvg, 1, j);
UpdateIndexList(fvg, Convert.ToByte(j), Convert.ToByte(i));
UpdatePositions(mp.StartIndex);
dupCnt += 1;
}
j += 1;
}
}
if (g < mp.GroupsCount - 1 && (int)level > 1)
{
if (MoveVertsBack(mp.FinalVertexGroups[g], mp.FinalVertexGroups[g + 1]))
{
g -= 1;
}
}
}
}
}
private void UpdateIndexList(FvGroup grp, byte removed, byte replaceWith)
{
for (int i = 0; i < grp.NumTri * 3; i++)
{
if (grp.indexList[i] < removed) continue;
if (grp.indexList[i] == removed)
{
grp.indexList[i] = Convert.ToSByte(replaceWith);
}
else
{
grp.indexList[i] -= 1;
}
}
}
private void UpdatePositions(int vs)
{
foreach (VertexGroupList mp in vertexGroups)
{
if (mp.StartIndex <= vs)
continue;
if (mp.StartIndex < 0x10)
continue;
mp.StartIndex -= 0x10;
}
}
private bool MoveVertsBack(FvGroup to, FvGroup from)
{
if (from.VertexDataCount < 3)
return false;
if (to.VertexDataCount < 14)
{
to.FinalVertexData.Add(from.FinalVertexData[0]);
to.FinalVertexData.Add(from.FinalVertexData[1]);
to.FinalVertexData.Add(from.FinalVertexData[2]);
to.indexList[to.NumTri * 3] = Convert.ToSByte(to.VertexDataCount);
to.indexList[to.NumTri * 3 + 1] = Convert.ToSByte(to.VertexDataCount + 1);
to.indexList[to.NumTri * 3 + 2] = Convert.ToSByte(to.VertexDataCount + 2);
moveElementsInGroupUpward(from, 3, 0);
to.NumTri += 1;
return true;
}
return false;
}
private void moveElementsInGroupUpward(FvGroup grp, int amount, int start)
{
for (int i = 0; i < amount; i++)
grp.FinalVertexData.RemoveAt(start);
}
private void BuildVertexGroups()
{
int vs = 0;
for (int i = 0; i < vertexGroups.Count; i++)
{
{
var withBlock = vertexGroups[i];
withBlock.StartIndex = vs;
int length = 0;
if (i < vertexGroups.Count - 1)
{
length = vertexGroups[i + 1].Position - withBlock.Position;
}
else
{
length = currentFace - withBlock.Position;
}
int groupsCount = 0;
if (length % 5 == 0)
{
groupsCount = (int)(length / (double)5);
}
else
{
groupsCount = (int)(Math.Truncate(length / (double)5) + 1);
}
for (int j = 0; j < groupsCount; j++)
withBlock.FinalVertexGroups.Add(new FvGroup() { NumTri = 0 });
for (int g = 0; g < groupsCount; g++)
{
int s = 5;
if (g == groupsCount - 1)
s = length % 5;
if (s == 0)
s = 5;
var curFvg = withBlock.FinalVertexGroups[g];
curFvg.NumTri = Convert.ToInt16(s);
for (int j = 0; j < s; j++)
{
int from = (withBlock.Position + j + g * 5) * 3;
curFvg.FinalVertexData.Add(finalVertData[from]);
curFvg.FinalVertexData.Add(finalVertData[from + 1]);
curFvg.FinalVertexData.Add(finalVertData[from + 2]);
curFvg.indexList[j * 3] = Convert.ToSByte(j * 3);
curFvg.indexList[j * 3 + 1] = Convert.ToSByte(j * 3 + 1);
curFvg.indexList[j * 3 + 2] = Convert.ToSByte(j * 3 + 2);
}
vs += curFvg.VertexDataCount * 0x10;
}
}
}
}
private F3D StrToF3D(string str)
{
var cmd = new F3D();
var b = str.Replace(".", ",").Split(' ');
for (int i = 0; i < b.Length; i++)
cmd.data[i] = Convert.ToByte($"&H{b[i]}");
return cmd;
}
private void ImpF3D(string str)
{
ImpF3D(StrToF3D(str));
}
private void ImpF3D(params byte[] data)
{
impdata.Write(data);
}
private void ImpF3D(F3D f3d)
{
impdata.Write(f3d.data);
}
private void ImpFogStart(int layer)
{
ImpF3D("BA 00 14 02 00 10 00 00");
string cmdF8 = "";
cmdF8 = $"F8 00 00 00 {Hex(settings.Fog.Color.R)} {Hex(settings.Fog.Color.G)} {Hex(settings.Fog.Color.B)} FF";
ImpF3D(cmdF8);
switch (layer)
{
case 0:
{
ImpF3D("B9 00 03 1D C8 11 22 30");
break;
}
case 1:
{
ImpF3D("B9 00 03 1D C8 11 20 78");
break;
}
case 2:
{
ImpF3D("B9 00 03 1D C8 11 2D 58");
break;
}
case 3:
{
ImpF3D("B9 00 03 1D C8 10 4D D8");
break;
}
// ImpF3D("B9 00 03 1D 00 11 24 78")
case 4:
{
ImpF3D("B9 00 03 1D C8 11 30 78");
break;
}
case 5:
{
ImpF3D("B9 00 03 1D C8 10 49 D8");
break;
}
case 6:
{
ImpF3D("B9 00 03 1D C8 10 4D D8");
break;
}
case 7:
{
ImpF3D("B9 00 03 1D C8 10 45 D8");
break;
}
}
var switchExpr = settings.Fog.Type;
switch (switchExpr)
{
case global::SM64Lib.Model.FogPreset.SubtleFog1:
{
ImpF3D("BC 00 00 08 19 00 E8 00");
break;
}
case global::SM64Lib.Model.FogPreset.SubtleFog2:
{
ImpF3D("BC 00 00 08 12 00 F0 00");
break;
}
case global::SM64Lib.Model.FogPreset.ModerateFog1:
{
ImpF3D("BC 00 00 08 0E 49 F2 B7");
break;
}
case global::SM64Lib.Model.FogPreset.ModerateFog2:
{
ImpF3D("BC 00 00 08 0C 80 F4 80");
break;
}
case global::SM64Lib.Model.FogPreset.ModerateFog3:
{
ImpF3D("BC 00 00 08 0A 00 F7 00");
break;
}
case global::SM64Lib.Model.FogPreset.ModerateFog4:
{
ImpF3D("BC 00 00 08 08 55 F8 AB");
break;
}
case global::SM64Lib.Model.FogPreset.IntenseFog:
{
ImpF3D("BC 00 00 08 07 24 F9 DC");
break;
}
case global::SM64Lib.Model.FogPreset.VeryIntenseFog:
{
ImpF3D("BC 00 00 08 05 00 FC 00");
break;
}
case global::SM64Lib.Model.FogPreset.HardcoreFog:
{
ImpF3D("BC 00 00 08 02 50 FF 00");
break;
}
}
ImpF3D("B7 00 00 00 00 01 00 00");
}
private void ImpFogEnd(int layer)
{
ImpF3D("BA 00 14 02 00 00 00 00");
switch (layer)
{
case 0:
{
ImpF3D("B9 00 03 1D 00 44 22 30");
break;
}
case 1:
{
ImpF3D("B9 00 03 1D 00 44 20 78");
break;
}
case 2:
{
ImpF3D("B9 00 03 1D 00 44 2D 58");
break;
}
case 3:
{
ImpF3D("B9 00 03 1D 00 40 4D D8");
break;
}
// ImpF3D("B9 00 03 1D 00 44 24 78")
case 4:
{
ImpF3D("B9 00 03 1D 00 44 30 78");
break;
}
case 5:
{
ImpF3D("B9 00 03 1D 00 40 49 D8");
break;
}
case 6:
{
ImpF3D("B9 00 03 1D 00 40 4D D8");
break;
}
case 7:
{
ImpF3D("B9 00 03 1D 00 40 45 D8");
break;
}
}
ImpF3D("B6 00 00 00 00 01 00 00"); // B6 00 00 00 00 01 02 00 --> Smoothen Shading?
}
private byte GetTypeFromTexType(N64Codec texType, bool advanced = false)
{
switch (texType)
{
case N64Codec.CI4:
{
return Convert.ToByte(advanced ? 0x50 : 0x40);
}
case N64Codec.CI8:
{
return Convert.ToByte(advanced ? 0x50 : 0x48);
}
case N64Codec.I4:
{
return Convert.ToByte(advanced ? 0x90 : 0x80);
}
case N64Codec.I8:
{
return Convert.ToByte(advanced ? 0x90 : 0x88);
}
case N64Codec.IA4:
{
return Convert.ToByte(advanced ? 0x70 : 0x60);
}
case N64Codec.IA8:
{
return Convert.ToByte(advanced ? 0x70 : 0x68);
}
case N64Codec.IA16:
{
return 0x70;
}
case N64Codec.RGBA16:
{
return 0x10;
}
case N64Codec.RGBA32:
{
return 0x18;
}
default:
{
return default;
}
}
}
private byte BytesPerType(N64Codec type)
{
switch (type)
{
case N64Codec.RGBA16:
{
return 2;
}
case N64Codec.RGBA32:
{
return 4;
}
case N64Codec.I4:
case N64Codec.IA4:
case N64Codec.CI4:
{
return 0; // Special case
}
case N64Codec.IA8:
case N64Codec.I8:
case N64Codec.CI8:
{
return 1;
}
default:
{
return 2;
}
}
}
private byte GetTexelIncrement(N64Codec type)
{
switch (type)
{
case N64Codec.I4:
case N64Codec.IA4:
{
return 3;
}
case N64Codec.IA8:
case N64Codec.I8:
{
return 1;
}
default:
{
return 0;
}
}
}
private byte GetTexelShift(N64Codec type)
{
switch (type)
{
case N64Codec.I4:
case N64Codec.IA4:
case N64Codec.CI4:
{
return 2;
}
case N64Codec.IA8:
case N64Codec.I8:
case N64Codec.CI8:
{
return 1;
}
default:
{
return 0;
}
}
}
private void ImpCmdFD(uint offset, N64Codec texType)
{
uint off = startSegOffset + offset;
byte type = GetTypeFromTexType(texType, true);
ImpF3D($"FD {Hex(type)} 00 00 {Hex(curSeg & 0xFF)} {Hex(off >> 16 & (long)0xFF)} {Hex(off >> 8 & (long)0xFF)} {Hex(off & (long)0xFF)}");
}
private void ImpCmdF5_First(N64Codec texType)
{
byte type = GetTypeFromTexType(texType, true);
ImpF3D($"F5 {Hex(type)} 00 00 07 00 00 00");
}
private void ImpCmdF5_Second(Material mat, uint texWidth, uint texHeight)
{
// Create upper
byte type = GetTypeFromTexType(mat.TexType);
float lineScale = 1.0F;
byte bpt = BytesPerType(mat.TexType);
if (bpt != 0)
{
lineScale = (float)(bpt / 4.0);
}
else
{
lineScale = 0.125F;
}
if (mat.TexType == N64Codec.RGBA32)
{
lineScale /= 2;
}
ushort line = Convert.ToUInt16(Convert.ToUInt16(Math.Truncate(texWidth * lineScale)) & 0x1FF);
uint upper = Convert.ToUInt32((Convert.ToUInt32(type) << 16 | Convert.ToUInt32(line) << 8) & (long)0xFFFFFF);
// Create lower (shift)
byte maskS = Convert.ToByte(Convert.ToInt64(Math.Ceiling(Math.Log(texWidth, 2))) & 0xF);
byte maskT = Convert.ToByte(Convert.ToInt64(Math.Ceiling(Math.Log(texHeight, 2))) & 0xF);
uint lower = Convert.ToUInt32((Convert.ToUInt32(maskT) << 14 | Convert.ToUInt32(maskS) << 4) & (long)0xFFFFFF); // &HFFC3F0 for only shift
if (mat.EnableMirrorS)
{
lower = Convert.ToUInt32(lower | (long)0x100); // S axis
}
if (mat.EnableMirrorT)
{
lower = Convert.ToUInt32(lower | (long)0x40000); // T axis
}
if (mat.EnableClampS)
{
lower = Convert.ToUInt32(lower | (long)0x80000); // T axis
}
if (mat.EnableClampT)
{
lower = Convert.ToUInt32(lower | (long)0x200); // S axis
}
// Create Command
ImpF3D($"F5 {Hex(upper >> 16 & (long)0xFF)} {Hex(upper >> 8 & (long)0xFF)} {Hex(upper & (long)0xFF)} 00 {Hex(lower >> 16 & (long)0xFF)} {Hex(lower >> 8 & (long)0xFF)} {Hex(lower & (long)0xFF)}");
}
private void AddCmdF3(Material mat)
{
uint numTexels = (uint)((mat.TexWidth * mat.TexHeight + GetTexelIncrement(mat.TexType) >> GetTexelShift(mat.TexType)) - (long)1);
int bpt = BytesPerType(mat.TexType);
uint tl;
uint lower;
string cmd;
if (bpt != 0)
{
tl = Convert.ToUInt32(Operators.AndObject(CALC_DXT(mat.TexWidth, bpt), 4095));
}
else
{
tl = Convert.ToUInt32(Operators.AndObject(CALC_DXT_4b(mat.TexWidth), 4095));
}
lower = Convert.ToUInt32((numTexels << 12 | tl) & (long)0xFFFFFF);
cmd = $"F3 00 00 00 07 {Hex(lower >> 16 & (long)0xFF)} {Hex(lower >> 8 & (long)0xFF)} {Hex(lower & (long)0xFF)}";
ImpF3D(cmd);
}
private void AddCmdF2(Material mat)
{
ushort width = Convert.ToUInt16(mat.TexWidth - (long)1 << 2 & 0xFFF);
ushort height = Convert.ToUInt16(mat.TexHeight - (long)1 << 2 & 0xFFF);
uint data = Convert.ToUInt32(Convert.ToInt32(width) << 12 | height);
string cmd = $"F2 00 00 00 00 {Hex(data >> 16 & (long)0xFF)} {Hex(data >> 8 & (long)0xFF)} {Hex(data & (long)0xFF)}";
ImpF3D(cmd);
}
private void AddCmdFC(Material mat, ref string lastCmd)
{
string cmd = string.Empty;
var colorFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.CCMUX.SHADE);
bool isColorPresent = Convert.ToDouble(Convert.ToString(mat.Color) + Convert.ToString(0xFFFFFF00U)) != 0xFFFFFF00U;
if (mat.HasTexture)
{
colorFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Multiply(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.CCMUX.TEXEL0, Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.CCMUX.SHADE);
}
else
{
var switchExpr1 = mat.Type;
switch (switchExpr1)
{
case MaterialType.None:
case MaterialType.TextureSolid:
case MaterialType.TextureAlpha:
case MaterialType.TextureTransparent:
{
colorFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.CCMUX.SHADE);
break;
}
case MaterialType.ColorSolid:
case MaterialType.ColorTransparent:
{
if (isColorPresent)
{
colorFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Multiply(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.CCMUX.SHADE, Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.CCMUX.ENVIRONMENT);
}
else
{
colorFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.CCMUX.SHADE);
}
break;
}
}
}
var alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.SHADE);
bool isTransPresent = Convert.ToDouble(Convert.ToString(mat.Color) + Convert.ToString(0xFFU)) != 0xFFU;
if (mat.HasTexture)
{
var switchExpr2 = mat.Type;
switch (switchExpr2)
{
case MaterialType.None:
case MaterialType.TextureSolid:
case MaterialType.ColorSolid:
{
alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.ONE);
break;
}
case MaterialType.TextureAlpha:
case MaterialType.TextureTransparent:
{
// With Fog multiplying SHADE is not something you want because it will be alpha fog so just output TEXEL0 and hope it is fine
if (settings.EnableFog)
{
alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.TEXEL0);
}
else
{
alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Multiply((byte)Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.TEXEL0, (byte)Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.SHADE);
}
break;
}
case MaterialType.ColorTransparent:
{
if (isTransPresent)
{
alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Multiply((byte)Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.TEXEL0, (byte)Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.ENVIRONMENT);
}
else if (settings.EnableFog)
{
alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.TEXEL0);
}
else
{
alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Multiply((byte)Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.TEXEL0, (byte)Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.SHADE);
}
break;
}
}
}
else
{
var switchExpr3 = mat.Type;
switch (switchExpr3)
{
case MaterialType.None:
case MaterialType.TextureSolid:
case MaterialType.ColorSolid:
{
alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.ONE);
break;
}
case MaterialType.TextureAlpha:
case MaterialType.TextureTransparent:
{
// With Fog multiplying SHADE is not something you want because it will be alpha fog so just output TEXEL0
if (settings.EnableFog)
{
alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.ONE);
}
else
{
alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.SHADE);
}
break;
}
case MaterialType.ColorTransparent:
{
if (isTransPresent)
{
// If there is no material, may as well provide more options for alpha modulate
if (settings.EnableFog)
{
alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.ENVIRONMENT);
}
else
{
alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Multiply((byte)Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.ENVIRONMENT, (byte)Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.SHADE);
}
}
else if (settings.EnableFog)
{
alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.ONE);
}
else
{
alphaFormula = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Formula.Output(Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.ACMUX.SHADE);
}
break;
}
}
}
cmd = Model.Fast3D.DisplayLists.Script.Commands.F3D_SETCOMBINE.Make(colorFormula, alphaFormula, settings.EnableFog);
if (!string.IsNullOrEmpty(cmd) && (lastCmd ?? "") != (cmd ?? ""))
{
ImpF3D(cmd);
lastCmd = cmd;
}
}
private void ImpTriCmds(Material mat, FvGroup grp, int offset, ref bool enabledVertexColors)
{
if (grp.VertexDataCount < 3)
return;
uint off = (uint)(startSegOffset + offset);
int amount = grp.VertexDataCount * 0x10;
if (mat.EnableScrolling)
{
AddScrollingTexture(grp, Convert.ToInt32(off), Convert.ToInt32(mat.Offset), mat.Name);
}
if (grp.EnableVertexColors)
{
if (!enabledVertexColors)
{
ImpF3D("B6 00 00 00 00 02 00 00");
enabledVertexColors = true;
}
}
else if (enabledVertexColors)
{
ImpF3D("B7 00 00 00 00 02 00 00");
enabledVertexColors = false;
}
ImpF3D($"04 {Hex(amount - 0x10 & 0xFF)} 00 {Hex(amount & 0xFF)} {Hex(curSeg)} {Hex(off >> 16 & (long)0xFF)} {Hex(off >> 8 & (long)0xFF)} {Hex(off & (long)0xFF)}");
for (int i = 0, loopTo = grp.NumTri - 1; i <= loopTo; i++)
{
byte a = Convert.ToByte(grp.indexList[i * 3] * 0xA);
byte b = Convert.ToByte(grp.indexList[i * 3 + 1] * 0xA);
byte c = Convert.ToByte(grp.indexList[i * 3 + 2] * 0xA);
ImpF3D($"BF 00 00 00 00 {Hex(a)} {Hex(b)} {Hex(c)}");
}
}
private void ResetCrystalEffectCommands(ref bool hasCrystalEffectEnabled, ref bool needToResetCrystalEffectCommands)
{
ImpF3D("B6 00 00 00 00 04 00 00");
ImpF3D("BB 00 00 01 FF FF FF FF");
hasCrystalEffectEnabled = false;
needToResetCrystalEffectCommands = false;
}
private void ImpMaterialCmds(Material mat, ref bool hasCrystalEffectEnabled, ref bool needToResetCrystalEffectCommands, ref bool disabledBackfaceCulling)
{
if (mat.EnableCrystalEffect)
{
if (!hasCrystalEffectEnabled)
{
ImpF3D($"B7 00 00 00 00 04 00 00");
ImpF3D("BB 00 00 01 08 00 08 00");
hasCrystalEffectEnabled = true;
needToResetCrystalEffectCommands = true;
}
}
else if (needToResetCrystalEffectCommands)
{
ResetCrystalEffectCommands(ref hasCrystalEffectEnabled, ref needToResetCrystalEffectCommands);
}
if (mat.HasPalette)
{
ImpCmdFD(mat.PaletteOffset, N64Codec.RGBA16);
ImpF3D("F5 00 01 00 01 00 00 00");
ushort num = Convert.ToUInt16(Convert.ToInt64(mat.PaletteSize / (double)2 - 1) << 6);
ImpF3D($"F0 00 00 00 01 {Hex((num >> 8) & 0xFF)} {Hex(num & 0xFF)} 00");
}
if (((mat.FaceCullingMode & FaceCullingMode.Back) != FaceCullingMode.Back))
{
if (!disabledBackfaceCulling)
{
ImpF3D($"B6 00 00 00 00 00 20 00"); // Disable back face culling
disabledBackfaceCulling = true;
}
}
else if (disabledBackfaceCulling)
{
ImpF3D($"B7 00 00 00 00 00 20 00"); // Enable back face culling
disabledBackfaceCulling = false;
}
if (mat.HasTexture)
{
ImpCmdFD(mat.Offset, mat.TexType);
ImpCmdF5_First(mat.TexType);
ImpF3D("E6 00 00 00 00 00 00 00");
AddCmdF3(mat);
ImpF3D("E7 00 00 00 00 00 00 00");
ImpCmdF5_Second(mat, mat.TexWidth, mat.TexHeight);
AddCmdF2(mat);
}
}
private void AddScrollingTexture(FvGroup grp, int vertPtr, int matAddr, string matName)
{
var scrollTex = new ScrollTex(Convert.ToInt32(curSeg) << 24 | vertPtr, Convert.ToByte(grp.VertexDataCount), matAddr, matName);
scrollTexts.Add(scrollTex);
}
private void MergeScrollingTextures()
{
short curScrollingGroupID = 0;
var scrollingGroups = new Dictionary<int, short>();
scrollTexts = new List<ScrollTex>(scrollTexts.OrderBy(n => n.Offset));
short getScrollingGroup(int curMatAddr)
{
if (scrollingGroups.ContainsKey(curMatAddr))
return scrollingGroups[curMatAddr];
else
{
short newID = curScrollingGroupID;
scrollingGroups.Add(curMatAddr, newID);
curScrollingGroupID += 1;
return newID;
}
};
while (scrollTexts.Count > 0)
{
int startOff = scrollTexts[0].Offset;
int endOff = startOff + scrollTexts[0].VertsCount * 0x10;
int count = 0;
int curMatAddr = scrollTexts[0].MaterialAddress;
string curMatName = scrollTexts[0].MaterialName;
foreach (ScrollTex st in scrollTexts)
{
if (st.MaterialAddress == curMatAddr)
{
if (st.Offset <= endOff)
{
if ((endOff - startOff) / (double)0x10 <= ushort.MaxValue)
{
int newEndOffset = st.Offset + st.VertsCount * 0x10;
endOff = newEndOffset;
}
else
break;
}
else if (st.Offset > endOff)
break;
count += 1;
}
else
break;
}
int vertsCount = (int)((endOff - startOff) / (double)0x10);
if (vertsCount > 0)
{
short groupID = getScrollingGroup(curMatAddr);
scrollTexts.RemoveRange(0, count);
conRes.ScrollingCommands.Add(new SM64Lib.Levels.ScrolTex.ManagedScrollingTexture(Convert.ToUInt16(vertsCount), startOff, groupID));
conRes.ScrollingNames.AddIfNotContainsKey(groupID, curMatName);
}
}
}
private void ImpColorCmdFB(Material mat)
{
byte r = Convert.ToByte(mat.Color >> 24 & (long)0xFF);
byte g = Convert.ToByte(mat.Color >> 16 & (long)0xFF);
byte b = Convert.ToByte(mat.Color >> 8 & (long)0xFF);
ImpF3D($"FB 00 00 00 {Hex(r)} {Hex(g)} {Hex(b)} {Hex(mat.Opacity)}");
}
private void AlignPosition()
{
impdata.RoundUpPosition();
}
/// <summary>
/// Adds a command that is requied on the end of a display list if CI textures are enabled
/// </summary>
private void SetOtherMode_H_Revert()
{
// Reset CI texture palette color format
ImpF3D("BA 00 0E 02 00 00 00 00");
// If it ever is required, then use this command instead. It also reverts the texture filter to "Bilerp".
//ImpF3D("BA 00 0C 04 00 00 20 00");
}
/// <summary>
/// Adds a command to enable CI textures
/// </summary>
private void SetOtherMode_H(Material mat, ref bool ciEnabled, ref bool nonDefaultFilter)
{
var citextypes = new[] { N64Codec.CI4, N64Codec.CI8 };
var iscitexture = citextypes.Contains(mat.TexType);
var paletteColorStorageType = iscitexture ? PaletteColorStorageType.RGBA16 : PaletteColorStorageType.None;
byte ss = 0xC;
byte nn = 0x4;
int options = 0;
// G_MDSFT_TEXTLUT (Palette color storage type)
ciEnabled = iscitexture;
options |= ((int)paletteColorStorageType) << 0xE;
// G_MDSFT_TEXTFILT (Texture Filter)
nonDefaultFilter = mat.TexFilter != TextureFilter.Bilerp;
options |= ((int)mat.TexFilter) << 0xC;
// Normally we need to set "ss" to E for G_MDSFT_TEXTLUT and to C for G_MDSFT_TEXTFILT.
// Because it they are nearby the other, we can combine them, so set to the lower value C and increase "nn" by the difference (+2 = 4).
// This saves one command. Else it would be two commands, for each option one command.
impdata.Write(new byte[] { 0xBA, 0, ss, nn });
impdata.Write(options);
}
private void ImportObj(Pilz.S3DFileParser.Object3D model)
{
bool enabledVertexColors;
bool enableForcing = settings.ForceDisplaylist != -1;
uint importStart = 0;
uint startVerts = 0;
Material lastMaterial = null;
uint lastFBColor = default;
bool hasCrystalEffectEnabled, needToResetCrystalEffectCommands, disabledBackfaceCulling;
bool ciEnabled;
bool nonDefaultFilter;
string lastCmdFC;
var dlsToCreate = new List<DisplaylistProps>();
var dicMatDlIDs = new Dictionary<Material, int>();
ProcessObject3DModel(model);
conRes.PtrStart = Convert.ToUInt32(CurSegAddress | impdata.Position);
importStart = Convert.ToUInt32(impdata.Position);
// Write default color
impdata.Write(defaultColor);
// Remove duplicated textures
// FIXME: This function does not account for materials properties like Opacity
// MergeDuplicatedTextures()
// Write materials
foreach (Material mt in materials)
{
if (mt.HasTexture)
{
mt.Offset = Convert.ToUInt32(impdata.Position);
impdata.Write(mt.Texture.Data);
AlignPosition();
if (mt.HasPalette)
{
mt.PaletteOffset = Convert.ToUInt32(impdata.Position);
impdata.Write(mt.Texture.Palette);
AlignPosition();
}
}
}
// Prepaire vertices
BuildVertexGroups();
removeDuplicateVertices(settings.ReduceVertLevel);
// Write vertices
conRes.PtrVertex = Convert.ToUInt32(CurSegAddress | impdata.Position);
startVerts = Convert.ToUInt32(impdata.Position);
foreach (VertexGroupList mp in vertexGroups)
{
for (int g = 0; g < mp.GroupsCount; g++)
{
if (mp.FinalVertexGroups[g].VertexDataCount >= 1)
{
for (int i = 0; i < mp.FinalVertexGroups[g].VertexDataCount; i++)
{
var data = mp.FinalVertexGroups[g].FinalVertexData[i].Data;
impdata.Write(data);
}
}
}
}
int createDefaultDL(Geolayout.DefaultGeolayers layerID)
{
DisplaylistProps dlProp = null;
int newLayerID = ((int)layerID + 1) * -1;
foreach (DisplaylistProps dl in dlsToCreate)
{
if (dlProp is null && dl.ID == newLayerID)
dlProp = dl;
}
dlProp ??= new DisplaylistProps(newLayerID);
dlProp.Layer = layerID;
if (!dlsToCreate.Contains(dlProp))
dlsToCreate.Add(dlProp);
return dlProp.ID;
};
int createCustomDL(int dlID)
{
DisplaylistProps dlProp = null;
// Search dlProp
foreach (var prop in settings.TextureFormatSettings.CustomDisplayLists)
{
if (dlProp is null && prop.ID == dlID)
dlProp = prop;
}
if (!dlsToCreate.Contains(dlProp))
dlsToCreate.Add(dlProp);
return dlProp.ID;
};
// Check which DLs should be created
if (enableForcing)
{
int dlID = createDefaultDL((Geolayout.DefaultGeolayers)settings.ForceDisplaylist);
foreach (Material mat in materials)
dicMatDlIDs.Add(mat, dlID);
}
else
{
foreach (Material mat in materials)
{
int dlID = -1;
var switchExpr = mat.DisplaylistSelection.SelectionMode;
switch (switchExpr)
{
case DisplaylistSelectionMode.Automatic:
if (mat.HasTransparency)
dlID = createDefaultDL(Geolayout.DefaultGeolayers.Translucent);
else if (mat.HasTextureAlpha)
dlID = createDefaultDL(Geolayout.DefaultGeolayers.Alpha);
else
dlID = createDefaultDL(Geolayout.DefaultGeolayers.Solid);
break;
case DisplaylistSelectionMode.Default:
dlID = createDefaultDL(mat.DisplaylistSelection.DefaultGeolayer);
break;
case DisplaylistSelectionMode.Custom:
dlID = createCustomDL(mat.DisplaylistSelection.CustomDisplaylistID);
break;
}
dicMatDlIDs.Add(mat, dlID);
}
}
// Create DLs
foreach (DisplaylistProps dlProps in dlsToCreate)
{
// Add Geopointer
conRes.PtrGeometry.Add(new Geolayout.Geopointer((byte)dlProps.Layer, Convert.ToInt32(CurSegAddress | impdata.Position)));
// Reset some stuff
enabledVertexColors = false;
hasCrystalEffectEnabled = false;
needToResetCrystalEffectCommands = false;
disabledBackfaceCulling = false;
ciEnabled = false;
nonDefaultFilter = false;
lastMaterial = null;
lastFBColor = default;
lastCmdFC = string.Empty;
// Create DL
ImpF3D("E7 00 00 00 00 00 00 00");
if (settings.EnableFog)
ImpF3D("B9 00 02 01 00 00 00 00");
ImpF3D("B7 00 00 00 00 00 00 00");
ImpF3D("BB 00 00 01 FF FF FF FF");
ImpF3D("E8 00 00 00 00 00 00 00");
ImpF3D("E6 00 00 00 00 00 00 00");
ImpF3D("03 88 00 10 0E 00 00 00");
ImpF3D("03 86 00 10 0E 00 00 08");
if (settings.EnableFog)
ImpFogStart((int)dlProps.Layer);
for (int i = 0, loopTo2 = vertexGroups.Count - 1; i <= loopTo2; i++)
{
var mp = vertexGroups[i];
if (dicMatDlIDs[mp.Material] != dlProps.ID)
continue;
// Geomode
if (mp.Material.EnableGeoMode)
{
ImpF3D("B6 00 00 00 FF FF FF FF");
ImpF3D($"B7 00 00 00 {Hex(mp.Material.GeoMode >> 24 & (long)0xFF)} {Hex(mp.Material.GeoMode >> 16 & (long)0xFF)} {Hex(mp.Material.GeoMode >> 8 & (long)0xFF)} {Hex(mp.Material.GeoMode & (long)0xFF)}");
}
if (lastMaterial != mp.Material)
{
lastMaterial = mp.Material;
AddCmdFC(mp.Material, ref lastCmdFC);
if ((mp.Material.Type == MaterialType.ColorSolid || mp.Material.Type == MaterialType.ColorTransparent) && lastFBColor != mp.Material.Color)
ImpColorCmdFB(mp.Material);
SetOtherMode_H(mp.Material, ref ciEnabled, ref nonDefaultFilter);
ImpMaterialCmds(mp.Material, ref hasCrystalEffectEnabled, ref needToResetCrystalEffectCommands, ref disabledBackfaceCulling);
}
int grpOff = 0;
for (int ii = 0, loopTo3 = mp.GroupsCount - 1; ii <= loopTo3; ii++)
{
ImpTriCmds(mp.Material, mp.FinalVertexGroups[ii], (int)(startVerts + (mp.StartIndex + grpOff)), ref enabledVertexColors);
grpOff += mp.FinalVertexGroups[ii].VertexDataCount * 0x10;
}
if (mp.Material.EnableGeoMode)
{
if (i + 1 < vertexGroups.Count && vertexGroups[i + 1].Material.EnableGeoMode)
continue;
ImpF3D("B6 00 00 00 FF FF FF FF");
ImpF3D("B7 00 00 00 00 02 20 05");
}
}
if (enabledVertexColors)
ImpF3D("B7 00 00 00 00 02 00 00");
if (settings.EnableFog)
ImpFogEnd((int)dlProps.Layer);
ImpF3D("FC FF FF FF FF FE 79 3C");
ImpF3D("BB 00 00 00 FF FF FF FF");
if (needToResetCrystalEffectCommands)
ResetCrystalEffectCommands(ref hasCrystalEffectEnabled, ref needToResetCrystalEffectCommands);
if (ciEnabled /*|| nonDefaultFilter*/)
SetOtherMode_H_Revert();
ImpF3D("B8 00 00 00 00 00 00 00");
MergeScrollingTextures();
}
ResetVariables();
}
/// <summary>
/// Converts a Object3D to an N64 Model and an SM64 Collision.
/// </summary>
/// <param name="s">The stream where to write the Fast3D and Collision data.</param>
/// <param name="settings">The convert settings.</param>
/// <param name="input">The input model.</param>
/// <returns></returns>
public ConvertResult ConvertModel(Stream s, ConvertSettings settings, Pilz.S3DFileParser.Object3D input)
{
this.settings = settings;
impdata = new Data.BinaryStreamData(s);
// Segmented Address
if (settings.SegmentedAddress is not null)
{
startSegOffset = (uint)(settings.SegmentedAddress & 0xFFFFFF);
curSeg = (byte)(settings.SegmentedAddress >> 24 & 0xFF);
}
// Shading
SetLightAndDarkValues(input.Shading);
// Convert
ImportObj(input);
ResetVariables();
return conRes;
}
}