mirror of
https://mirror.ghproxy.com/https://github.com/dexyfex/CodeWalker
synced 2024-11-29 18:32:55 +08:00
1dd9bc34a2
`ShaderGroup.ShaderGroupBlocksSize` (aka `Unknown_30h`) contains the byte size divided by 16 of the ShaderGroup block and its children blocks, when they are packed contiguously in the resource file. `DrawableBase.DrawableModelsBlocksSize` (aka `Unknown_9Ah`) is similar but with the size of the `DrawableModelsBlock`. Incorrect values can cause crashes when the model is unloaded. Discussed here https://discord.com/channels/329138547833700352/329142468102586368/1139264754767122533
994 lines
37 KiB
C#
994 lines
37 KiB
C#
using CodeWalker.GameFiles;
|
|
using SharpDX;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace CodeWalker
|
|
{
|
|
public class FbxConverter
|
|
{
|
|
public bool InvertTexcoordV { get; set; } = true;
|
|
|
|
|
|
public YdrFile ConvertToYdr(string name, byte[] fbxdata)
|
|
{
|
|
var fdoc = FbxIO.Read(fbxdata);
|
|
if (fdoc == null)
|
|
return null;
|
|
|
|
var dwbl = TryConvertDrawable(fdoc, name);
|
|
|
|
|
|
YdrFile ydr = new YdrFile();
|
|
ydr.Drawable = dwbl;
|
|
ydr.Name = name;
|
|
|
|
return ydr;
|
|
}
|
|
|
|
|
|
|
|
private Drawable TryConvertDrawable(FbxDocument fdoc, string name)
|
|
{
|
|
|
|
var rootnodes = fdoc.GetSceneNodes();
|
|
|
|
var mlists = new List<List<FbxModel>>();
|
|
var mlistall = new List<FbxModel>();
|
|
|
|
foreach (var node in rootnodes)
|
|
{
|
|
if (node.Name == "Model")
|
|
{
|
|
var mlist = TryConvertModels(node); //flatten any models structure with depth >2
|
|
if (mlist != null)
|
|
{
|
|
mlists.Add(mlist);
|
|
mlistall.AddRange(mlist);
|
|
}
|
|
}
|
|
}
|
|
|
|
var mlHigh = new List<DrawableModel>();
|
|
var mlMed = new List<DrawableModel>();
|
|
var mlLow = new List<DrawableModel>();
|
|
var mlVlow = new List<DrawableModel>();
|
|
var mlUnks = new List<DrawableModel>();
|
|
var mlAll = new List<DrawableModel>();
|
|
foreach (var m in mlistall)
|
|
{
|
|
var mnl = m.Name.ToLowerInvariant();
|
|
if (mnl.EndsWith("_vlow"))
|
|
{
|
|
mlVlow.Add(m.Model);
|
|
}
|
|
else if (mnl.EndsWith("_low"))
|
|
{
|
|
mlLow.Add(m.Model);
|
|
}
|
|
else if (mnl.EndsWith("_med"))
|
|
{
|
|
mlMed.Add(m.Model);
|
|
}
|
|
else if (mnl.EndsWith("_high"))
|
|
{
|
|
mlHigh.Add(m.Model);
|
|
}
|
|
else
|
|
{
|
|
mlUnks.Add(m.Model);
|
|
}
|
|
}
|
|
if (mlHigh.Count == 0)//mlUnks could be embedded collisions... ignore for now
|
|
{
|
|
mlHigh.AddRange(mlUnks);
|
|
}
|
|
mlAll.AddRange(mlHigh);
|
|
mlAll.AddRange(mlMed);
|
|
mlAll.AddRange(mlLow);
|
|
mlAll.AddRange(mlVlow);
|
|
|
|
|
|
|
|
var allVerts = new List<Vector3>();
|
|
var bbMin = new Vector3(float.MaxValue);
|
|
var bbMax = new Vector3(float.MinValue);
|
|
var bsCen = Vector3.Zero;
|
|
var bsRad = 0.0f;
|
|
foreach (var m in mlistall)
|
|
{
|
|
if (m?.Model?.Geometries == null) continue;
|
|
foreach (var g in m.Model.Geometries)
|
|
{
|
|
var vb = g.VertexData.VertexBytes;
|
|
var vs = g.VertexData.VertexStride;
|
|
var vc = g.VertexData.VertexCount;
|
|
for (int i = 0; i < vc; i++)
|
|
{
|
|
var vp = MetaTypes.ConvertData<Vector3>(vb, i * vs);//position offset should always be 0!
|
|
allVerts.Add(vp);
|
|
bbMin = Vector3.Min(bbMin, vp);
|
|
bbMax = Vector3.Max(bbMax, vp);
|
|
//bsCen += vp;
|
|
}
|
|
}
|
|
}
|
|
if (allVerts.Count > 0)
|
|
{
|
|
//bsCen = bsCen / allVerts.Count;
|
|
bsCen = (bbMin + bbMax) * 0.5f;
|
|
foreach (var vp in allVerts)
|
|
{
|
|
bsRad = Math.Max(bsRad, (vp - bsCen).Length());
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var sgrp = new ShaderGroup();
|
|
var slist = new List<ShaderFX>();
|
|
var smapp = new List<ushort>();
|
|
foreach (var m in mlAll)
|
|
{
|
|
if (m?.Geometries == null) continue;
|
|
smapp.Clear();
|
|
foreach (var g in m.Geometries)
|
|
{
|
|
smapp.Add((ushort)slist.Count);
|
|
slist.Add(g.Shader);
|
|
}
|
|
m.ShaderMapping = smapp.ToArray();//TODO: re-use shaders!!
|
|
}
|
|
sgrp.Shaders = new ResourcePointerArray64<ShaderFX>();
|
|
sgrp.Shaders.data_items = slist.ToArray();
|
|
sgrp.ShadersCount1 = (ushort)slist.Count;
|
|
sgrp.ShadersCount2 = (ushort)slist.Count;
|
|
sgrp.VFT = 1080113376;//is this needed?
|
|
sgrp.Unknown_4h = 1;
|
|
|
|
|
|
var d = new Drawable();
|
|
d.Name = name + ".#dr";
|
|
d.ShaderGroup = sgrp;
|
|
d.BoundingCenter = bsCen;
|
|
d.BoundingSphereRadius = bsRad;
|
|
d.BoundingBoxMin = bbMin;
|
|
d.BoundingBoxMax = bbMax;
|
|
d.LodDistHigh = 9998;//lod dist defaults
|
|
d.LodDistMed = 9998;
|
|
d.LodDistLow = 9998;
|
|
d.LodDistVlow = 9998;
|
|
d.FileVFT = 1079446584;
|
|
d.FileUnknown = 1;
|
|
d.DrawableModels = new DrawableModelsBlock();
|
|
if (mlHigh.Count > 0)
|
|
{
|
|
d.DrawableModels.High = mlHigh.ToArray();
|
|
d.FlagsHigh = 1;//what flags should be used??
|
|
}
|
|
if (mlMed.Count > 0)
|
|
{
|
|
d.DrawableModels.Med = mlMed.ToArray();
|
|
d.LodDistHigh = bsRad * 2.0f; //when med models present, generate a high lod dist..
|
|
d.FlagsMed = 1;
|
|
}
|
|
if (mlLow.Count > 0)
|
|
{
|
|
d.DrawableModels.Low = mlLow.ToArray();
|
|
d.LodDistMed = bsRad * 8.0f; //when low models present, generate a med lod dist..
|
|
d.FlagsLow = 1;
|
|
}
|
|
if (mlVlow.Count > 0)
|
|
{
|
|
d.DrawableModels.VLow = mlVlow.ToArray();
|
|
d.LodDistLow = bsRad * 32.0f; //when vlow models present, generate a low lod dist..
|
|
d.FlagsVlow = 1;
|
|
}
|
|
|
|
d.BuildRenderMasks();
|
|
|
|
d.LightAttributes = new ResourceSimpleList64<LightAttributes>();
|
|
//todo: light attributes?
|
|
|
|
|
|
return d;
|
|
}
|
|
|
|
|
|
|
|
private List<FbxModel> TryConvertModels(FbxNode mnode)
|
|
{
|
|
var result = new List<FbxModel>();
|
|
|
|
var nodemodel = TryConvertModel(mnode);
|
|
if (nodemodel != null)
|
|
{
|
|
result.Add(nodemodel);
|
|
}
|
|
|
|
foreach (var cnode in mnode.Connections)
|
|
{
|
|
if (cnode.Name == "Model")
|
|
{
|
|
var mlist = TryConvertModels(cnode);
|
|
if (mlist != null)
|
|
{
|
|
result.AddRange(mlist);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private FbxModel TryConvertModel(FbxNode mnode)
|
|
{
|
|
|
|
FbxNode geonode = null;
|
|
var matnodes = new List<FbxNode>();
|
|
foreach (var cnode in mnode.Connections)
|
|
{
|
|
if (cnode == null) continue;
|
|
switch (cnode.Name)
|
|
{
|
|
case "Geometry":
|
|
geonode = cnode;
|
|
break;
|
|
case "Material":
|
|
matnodes.Add(cnode);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (geonode == null) return null;
|
|
if (matnodes.Count == 0)
|
|
return null; //need atleast one material...
|
|
|
|
var fnEdges = geonode["Edges"]?.Value as int[];//do we need this? maybe for collision/navmesh
|
|
var fnVerts = geonode["Vertices"]?.Value as double[];
|
|
var fnIndices = geonode["PolygonVertexIndex"]?.Value as int[];
|
|
|
|
if ((fnVerts == null) || (fnIndices == null))
|
|
{ return null; } //no mesh data.. abort!
|
|
|
|
var fnNormals = new List<FbxNode>();
|
|
var fnBinormals = new List<FbxNode>();
|
|
var fnTangents = new List<FbxNode>();
|
|
var fnTexcoords = new List<FbxNode>();
|
|
var fnColours = new List<FbxNode>();
|
|
var fnMaterials = new List<FbxNode>();
|
|
|
|
foreach (var cnode in geonode.Nodes)
|
|
{
|
|
if (cnode == null) continue;
|
|
switch (cnode.Name)
|
|
{
|
|
case "LayerElementNormal": fnNormals.Add(cnode); break;
|
|
case "LayerElementBinormal": fnBinormals.Add(cnode); break;
|
|
case "LayerElementTangent": fnTangents.Add(cnode); break;
|
|
case "LayerElementUV": fnTexcoords.Add(cnode); break;
|
|
case "LayerElementColor": fnColours.Add(cnode); break;
|
|
case "LayerElementMaterial": fnMaterials.Add(cnode); break;
|
|
case "LayerElementSmoothing": break;//ignore currently
|
|
case "Layer": break;//ignore- merge all layers data instead
|
|
}
|
|
}
|
|
|
|
var nNormals = fnNormals.Count;
|
|
var nBinormals = fnBinormals.Count;
|
|
var nTangents = fnTangents.Count;
|
|
var nTexcoords = fnTexcoords.Count;
|
|
var nColours = fnColours.Count;
|
|
var nMaterials = fnMaterials.Count;
|
|
|
|
var fPolys = new List<FbxPolygon>();
|
|
var fPolyVerts = new List<FbxVertex>();
|
|
var fPolysByMat = new List<FbxPolygon>[matnodes.Count];
|
|
|
|
foreach (var fnIndex in fnIndices) //build the polygons.
|
|
{
|
|
var pVert = new FbxVertex();
|
|
pVert.Position = GetVector3FromDoubleArray(fnVerts, (fnIndex < 0) ? (-fnIndex-1) : fnIndex);
|
|
pVert.Normals = nNormals > 0 ? new Vector3[nNormals] : null;
|
|
pVert.Binormals = nBinormals > 0 ? new Vector3[nBinormals] : null;
|
|
pVert.Tangents = nTangents > 0 ? new Vector3[nTangents] : null;
|
|
pVert.Texcoords = nTexcoords > 0 ? new Vector2[nTexcoords] : null;
|
|
pVert.Colours = nColours > 0 ? new Vector4[nColours] : null;
|
|
fPolyVerts.Add(pVert);
|
|
if (fnIndex < 0) //yeah because negative index means end of polygon...
|
|
{
|
|
var fPoly = new FbxPolygon();
|
|
fPoly.Vertices = fPolyVerts.ToArray();
|
|
fPoly.Materials = nMaterials > 0 ? new FbxNode[nMaterials] : null;
|
|
fPolyVerts.Clear();
|
|
fPolys.Add(fPoly);
|
|
if (fPoly.Vertices.Length > 3)
|
|
{ } //more than 3 vertices in this poly! will need to split it into triangles!! but do it later since all poly verts are needed for next steps
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < nNormals; i++)
|
|
{
|
|
var fnNorms = fnNormals[i];
|
|
var arNorms = fnNorms["Normals"]?.Value as double[];
|
|
var aiNorms = fnNorms["NormalIndex"]?.Value as int[];
|
|
if (!IsByPolygonVertexMapType(fnNorms))
|
|
{ continue; }
|
|
var indexed = IsIndexToDirectRefType(fnNorms);
|
|
if (indexed && (aiNorms == null))
|
|
{ continue; } //need the index array if it's IndexToDirect!
|
|
int j = 0;
|
|
foreach (var fPoly in fPolys)
|
|
{
|
|
foreach (var fVert in fPoly.Vertices)
|
|
{
|
|
var ai = indexed ? aiNorms[j] : j;
|
|
fVert.Normals[i] = GetVector3FromDoubleArray(arNorms, ai);
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
for (int i = 0; i < nBinormals; i++)
|
|
{
|
|
var fnBinorms = fnBinormals[i];
|
|
var arBinorms = fnBinorms["Binormals"]?.Value as double[];
|
|
var aiBinorms = fnBinorms["BinormalIndex"]?.Value as int[];
|
|
if (!IsByPolygonVertexMapType(fnBinorms))
|
|
{ continue; }
|
|
var indexed = IsIndexToDirectRefType(fnBinorms);
|
|
if (indexed && (aiBinorms == null))
|
|
{ continue; } //need the index array if it's IndexToDirect!
|
|
int j = 0;
|
|
foreach (var fPoly in fPolys)
|
|
{
|
|
foreach (var fVert in fPoly.Vertices)
|
|
{
|
|
var ai = indexed ? aiBinorms[j] : j;
|
|
fVert.Binormals[i] = GetVector3FromDoubleArray(arBinorms, ai);
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
for (int i = 0; i < nTangents; i++)
|
|
{
|
|
var fnTangs = fnTangents[i];
|
|
var arTangs = fnTangs["Tangents"]?.Value as double[];
|
|
var aiTangs = fnTangs["TangentIndex"]?.Value as int[];
|
|
if (!IsByPolygonVertexMapType(fnTangs))
|
|
{ continue; }
|
|
var indexed = IsIndexToDirectRefType(fnTangs);
|
|
if (indexed && (aiTangs == null))
|
|
{ continue; } //need the index array if it's IndexToDirect!
|
|
int j = 0;
|
|
foreach (var fPoly in fPolys)
|
|
{
|
|
foreach (var fVert in fPoly.Vertices)
|
|
{
|
|
var ai = indexed ? aiTangs[j] : j;
|
|
fVert.Tangents[i] = GetVector3FromDoubleArray(arTangs, ai);
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
for (int i = 0; i < nTexcoords; i++)
|
|
{
|
|
var fnTexcs = fnTexcoords[i];
|
|
var arTexcs = fnTexcs["UV"]?.Value as double[];
|
|
var aiTexcs = fnTexcs["UVIndex"]?.Value as int[];
|
|
if (!IsByPolygonVertexMapType(fnTexcs))
|
|
{ continue; }
|
|
var indexed = IsIndexToDirectRefType(fnTexcs);
|
|
if (indexed && (aiTexcs == null))
|
|
{ continue; } //need the index array if it's IndexToDirect!
|
|
int j = 0;
|
|
foreach (var fPoly in fPolys)
|
|
{
|
|
foreach (var fVert in fPoly.Vertices)
|
|
{
|
|
var ai = indexed ? aiTexcs[j] : j;
|
|
var tc = GetVector2FromDoubleArray(arTexcs, ai);
|
|
fVert.Texcoords[i] = InvertTexcoordV ? new Vector2(tc.X, -tc.Y) : tc;//whyyyy
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
for (int i = 0; i < nColours; i++)
|
|
{
|
|
var fnCols = fnColours[i];
|
|
var arCols = fnCols["Colors"]?.Value as double[];
|
|
var aiCols = fnCols["ColorIndex"]?.Value as int[];
|
|
if (!IsByPolygonVertexMapType(fnCols))
|
|
{ continue; }
|
|
var indexed = IsIndexToDirectRefType(fnCols);
|
|
if (indexed && (aiCols == null))
|
|
{ continue; } //need the index array if it's IndexToDirect!
|
|
int j = 0;
|
|
foreach (var fPoly in fPolys)
|
|
{
|
|
foreach (var fVert in fPoly.Vertices)
|
|
{
|
|
var ai = indexed ? aiCols[j] : j;
|
|
fVert.Colours[i] = GetVector4FromDoubleArray(arCols, ai);
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
for (int i = 0; i < nMaterials; i++)
|
|
{
|
|
var fnMats = fnMaterials[i];
|
|
var arMats = fnMats["Materials"]?.Value as int[];
|
|
var mapType = fnMats["MappingInformationType"]?.Value as string;
|
|
var refType = fnMats["ReferenceInformationType"]?.Value as string;
|
|
var allSame = false;
|
|
switch (mapType)
|
|
{
|
|
case "ByPolygon": break;
|
|
case "AllSame": allSame = true; break;
|
|
default:
|
|
continue;
|
|
}
|
|
switch (refType)
|
|
{
|
|
case "IndexToDirect": break;
|
|
default:
|
|
continue;
|
|
}
|
|
for (int j = 0; j < fPolys.Count; j++)
|
|
{
|
|
var fPoly = fPolys[j];
|
|
var iMat = allSame ? arMats[0] : arMats[j];
|
|
fPoly.Materials[i] = matnodes[iMat];
|
|
|
|
//group all the polygons by material...
|
|
var matPolys = fPolysByMat[iMat];
|
|
if (matPolys == null)
|
|
{
|
|
matPolys = new List<FbxPolygon>();
|
|
fPolysByMat[iMat] = matPolys;
|
|
}
|
|
matPolys.Add(fPoly);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
var dModel = new DrawableModel();
|
|
|
|
var dGeoms = new List<DrawableGeometry>();
|
|
var dGeomAABBs = new List<AABB_s>();
|
|
var dModelAABB = new AABB_s();
|
|
for (int i = 0; i < fPolysByMat.Length; i++)
|
|
{
|
|
AABB_s dGeomAABB;
|
|
var dGeom = TryConvertGeometry(fPolysByMat[i], matnodes[i], out dGeomAABB);
|
|
if (dGeom != null)
|
|
{
|
|
dGeoms.Add(dGeom);
|
|
dGeomAABBs.Add(dGeomAABB);
|
|
}
|
|
}
|
|
if (dGeomAABBs.Count > 1)//need to include whole model AABB first, if more than one geometry..
|
|
{
|
|
var dGeomAABBs2 = new List<AABB_s>();
|
|
dModelAABB.Min = new Vector4(float.MaxValue);
|
|
dModelAABB.Max = new Vector4(float.MinValue);
|
|
foreach (var aabb in dGeomAABBs)
|
|
{
|
|
dModelAABB.Min = Vector4.Min(dModelAABB.Min, aabb.Min);
|
|
dModelAABB.Max = Vector4.Max(dModelAABB.Max, aabb.Max);
|
|
}
|
|
dGeomAABBs2.Add(dModelAABB);
|
|
dGeomAABBs2.AddRange(dGeomAABBs);
|
|
dGeomAABBs = dGeomAABBs2;
|
|
}
|
|
|
|
|
|
dModel.VFT = 1080101496;//is this needed?
|
|
dModel.Unknown_4h = 1;
|
|
dModel.RenderMaskFlags = 0x00FF; //GIMS "Mask"
|
|
dModel.Geometries = dGeoms.ToArray();
|
|
dModel.GeometriesCount1 = (ushort)dGeoms.Count;
|
|
dModel.GeometriesCount2 = (ushort)dGeoms.Count;
|
|
dModel.GeometriesCount3 = (ushort)dGeoms.Count;
|
|
dModel.BoundsData = dGeomAABBs.ToArray();
|
|
//shader mappings array will be added when adding models to drawable.
|
|
|
|
|
|
|
|
var fModel = new FbxModel();
|
|
fModel.Name = (mnode.Properties.Count > 1) ? (mnode.Properties[1] as string)?.Replace("Model::", "") : null;
|
|
fModel.Node = mnode;
|
|
fModel.Model = dModel;
|
|
|
|
return fModel;
|
|
}
|
|
|
|
private DrawableGeometry TryConvertGeometry(List<FbxPolygon> fPolys, FbxNode matNode, out AABB_s aabb)
|
|
{
|
|
aabb = new AABB_s();
|
|
|
|
if (matNode == null) return null;
|
|
if (fPolys == null) return null;
|
|
if (fPolys.Count == 0) return null;
|
|
|
|
|
|
var dShader = TryConvertMaterial(matNode);
|
|
var dVertDecl = GetVertexDeclaration(dShader);
|
|
|
|
var vDict = new Dictionary<FbxVertex, ushort>();
|
|
var vList = new List<FbxVertex>();
|
|
var iList = new List<ushort>();
|
|
|
|
foreach (var fPoly in fPolys)
|
|
{
|
|
if (fPoly.Vertices == null) continue;
|
|
if (vList.Count >= 65535)
|
|
break;//too many vertices in this geometry!!
|
|
|
|
ushort i0 = 0;//first generated index
|
|
ushort iP = 0;//previous generated index
|
|
ushort iN = 0;//current index
|
|
for (int v = 0; v < fPoly.Vertices.Length; v++)
|
|
{
|
|
var vert = fPoly.Vertices[v];
|
|
vert.GenVertexBytes(dVertDecl);
|
|
|
|
if (!vDict.TryGetValue(vert, out iN))
|
|
{
|
|
iN = (ushort)vList.Count;
|
|
vDict[vert] = iN;
|
|
vList.Add(vert);
|
|
}
|
|
else
|
|
{ }//found identical vertex, use its index
|
|
if (v == 0) i0 = iN;
|
|
if (v < 3) //first triangle
|
|
{
|
|
iList.Add(iN);
|
|
}
|
|
else //for each extra vertex, make triangle from v0, vN-1, vN - assumes convex polygon!!
|
|
{
|
|
iList.Add(i0);
|
|
iList.Add(iP);
|
|
iList.Add(iN);
|
|
}
|
|
iP = iN;
|
|
}
|
|
}
|
|
|
|
|
|
var vStride = dVertDecl.Stride;
|
|
var vBytes = new byte[vList.Count * vStride];
|
|
for (int i = 0; i < vList.Count; i++)
|
|
{
|
|
var v = vList[i].Bytes;
|
|
var o = i * vStride;
|
|
for (int j = 0; j < vStride; j++)
|
|
{
|
|
vBytes[o + j] = v[j];
|
|
}
|
|
}
|
|
|
|
if (vList.Count > 0)
|
|
{
|
|
aabb.Min = new Vector4(float.MaxValue);
|
|
aabb.Max = new Vector4(float.MinValue);
|
|
foreach (var vert in vList)
|
|
{
|
|
var v = new Vector4(vert.Position, vert.Position.X);
|
|
aabb.Min = Vector4.Min(aabb.Min, v);
|
|
aabb.Max = Vector4.Max(aabb.Max, v);
|
|
}
|
|
}
|
|
|
|
|
|
var vData = new VertexData();
|
|
vData.Info = dVertDecl;
|
|
vData.VertexType = (VertexType)dVertDecl.Flags;
|
|
vData.VertexStride = dVertDecl.Stride;
|
|
vData.VertexCount = vList.Count;
|
|
vData.VertexBytes = vBytes;
|
|
|
|
var vBuff = new VertexBuffer();
|
|
vBuff.Data1 = vData;
|
|
vBuff.Data2 = vData;
|
|
vBuff.Info = dVertDecl;
|
|
vBuff.VertexCount = (uint)vList.Count;
|
|
vBuff.VertexStride = vStride;
|
|
vBuff.VFT = 1080153064;//is this needed?
|
|
vBuff.Unknown_4h = 1;
|
|
|
|
var iBuff = new IndexBuffer();
|
|
iBuff.IndicesCount = (uint)iList.Count;
|
|
iBuff.Indices = iList.ToArray();
|
|
iBuff.VFT = 1080111576;//is this needed?
|
|
iBuff.Unknown_4h = 1;
|
|
|
|
|
|
var dGeom = new DrawableGeometry();
|
|
dGeom.Shader = dShader;
|
|
dGeom.VertexData = vData;
|
|
dGeom.VertexBuffer = vBuff;
|
|
dGeom.IndexBuffer = iBuff;
|
|
dGeom.VFT = 1080133736;//is this needed?
|
|
dGeom.Unknown_4h = 1;
|
|
dGeom.IndicesCount = (uint)iList.Count;
|
|
dGeom.TrianglesCount = (uint)iList.Count / 3;
|
|
dGeom.VerticesCount = (ushort)vList.Count;
|
|
dGeom.Unknown_62h = 3; //indices per triangle..?
|
|
dGeom.VertexStride = vStride;
|
|
dGeom.BoneIdsCount = 0;//todo: bones
|
|
|
|
|
|
return dGeom;
|
|
}
|
|
|
|
private ShaderFX TryConvertMaterial(FbxNode matNode)
|
|
{
|
|
var shader = new ShaderFX();
|
|
|
|
var spsName = "default";
|
|
var texConns = new List<FbxNode>();
|
|
var texNames = new List<string>();
|
|
|
|
#region 3dsmax/GIMS properties
|
|
//var floatValueNames = new List<string>();
|
|
//var floatValues = new List<Vector4>();
|
|
//var texValueNames = new List<string>();
|
|
//var texValues = new List<FbxNode>();
|
|
//var matProps = matNode["Properties70"];
|
|
//foreach (var matProp in matProps.Nodes)//currently broken due to GIMS not doing things right
|
|
//{
|
|
// if (matProp == null) continue;
|
|
// if (matProp.Name != "P") continue;
|
|
// var propStr = GetStringFromObjectList(matProp.Properties, 4);
|
|
// var propId = matProp.Value as string;
|
|
// if (propId == null) continue;
|
|
// if (propId == "3dsMax|params|SPSName") spsName = propStr?.ToLowerInvariant() ?? "default";
|
|
// if (propId.StartsWith("3dsMax|params|FloatValueNames|FloatValueNames")) floatValueNames.Add(propStr);
|
|
// if (propId.StartsWith("3dsMax|params|FloatValues|FloatValues")) floatValues.Add(GetVector4FromObjectList(matProp.Properties, 4));
|
|
// if (propId.StartsWith("3dsMax|params|TexValueNames|TexValueNames")) texValueNames.Add(propStr);
|
|
// if (propId.StartsWith("3dsMax|params|TexValues|TexValues")) texValues.Add(matProp);
|
|
//}
|
|
#endregion
|
|
|
|
foreach (var conn in matNode.Connections)
|
|
{
|
|
if (conn.Name == "Texture")
|
|
{
|
|
texConns.Add(conn);
|
|
var texName = GetStringFromObjectList(conn.Properties, 1)?.Replace("Texture::", "");
|
|
var ftexName = conn["FileName"]?.Value as string;
|
|
if (ftexName != null)
|
|
{
|
|
try
|
|
{
|
|
texName = Path.GetFileNameWithoutExtension(ftexName);
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
texNames.Add(texName);
|
|
}
|
|
}
|
|
|
|
if (texNames.Count > 1)
|
|
{
|
|
spsName = "normal";
|
|
}
|
|
|
|
var spsFileName = spsName + ".sps";
|
|
|
|
shader.Name = JenkHash.GenHash(spsName);
|
|
shader.FileName = JenkHash.GenHash(spsFileName);
|
|
|
|
shader.ParametersList = new ShaderParametersBlock();
|
|
var paramsBlock = shader.ParametersList;
|
|
var pNames = new List<ShaderParamNames>();
|
|
var pVals = new List<ShaderParameter>();
|
|
|
|
|
|
shader.Unknown_Ch = 0;
|
|
shader.RenderBucket = 0;
|
|
shader.Unknown_12h = 32768;//shrugs
|
|
shader.Unknown_1Ch = 0;
|
|
shader.Unknown_24h = 0;
|
|
shader.Unknown_26h = 0;
|
|
shader.Unknown_28h = 0;
|
|
|
|
|
|
switch (spsName)
|
|
{
|
|
default:
|
|
case "default":
|
|
//shader.RenderBucket = 3;
|
|
//shader.ParameterSize = 208;
|
|
//shader.ParameterDataSize = 272;
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.DiffuseSampler, GetTextureBaseParam(texNames, 0));//assume first texture is diffuse...
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.matMaterialColorScale, new Vector4(1, 0, 0, 1));
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.HardAlphaBlend, new Vector4(0, 0, 0, 0));
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.useTessellation, new Vector4(0, 0, 0, 0));
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.wetnessMultiplier, new Vector4(1, 0, 0, 0));
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.globalAnimUV1, new Vector4(0, 1, 0, 0));
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.globalAnimUV0, new Vector4(1, 0, 0, 0));
|
|
break;
|
|
case "normal":
|
|
//shader.RenderBucket = 0;
|
|
//shader.ParameterSize = 320;
|
|
//shader.ParameterDataSize = 400;
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.DiffuseSampler, GetTextureBaseParam(texNames, 0));//assume first texture is diffuse...
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.BumpSampler, GetTextureBaseParam(texNames, 1));//assume 2nd texture is normalmap..
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.HardAlphaBlend, new Vector4(1, 0, 0, 0));
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.useTessellation, new Vector4(0, 0, 0, 0));
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.wetnessMultiplier, new Vector4(1, 0, 0, 0));
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.bumpiness, new Vector4(1, 0, 0, 0));
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.specularIntensityMult, new Vector4(0.5f, 0, 0, 0));
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.specularFalloffMult, new Vector4(20, 0, 0, 0));//too metallic?
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.specularFresnel, new Vector4(0.9f, 0, 0, 0));
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.globalAnimUV1, new Vector4(0, 1, 0, 0));
|
|
AddShaderParam(pNames, pVals, ShaderParamNames.globalAnimUV0, new Vector4(1, 0, 0, 0));
|
|
break;
|
|
}
|
|
|
|
for (int i = 0; i < pVals.Count; i++)
|
|
{
|
|
var pVal = pVals[i];
|
|
if (pVal.DataType == 1)
|
|
{
|
|
pVal.Unknown_1h = (byte)(160 + ((pVals.Count - 1) - i));//seriously wtf is this and why
|
|
}
|
|
}
|
|
|
|
MetaName[] nameHashes = new MetaName[pNames.Count];
|
|
for (int i = 0; i < pNames.Count; i++)
|
|
{
|
|
nameHashes[i] = (MetaName)pNames[i];
|
|
}
|
|
|
|
paramsBlock.Hashes = nameHashes;
|
|
paramsBlock.Parameters = pVals.ToArray();
|
|
paramsBlock.Count = pVals.Count;
|
|
|
|
shader.ParameterSize = paramsBlock.ParametersSize;
|
|
shader.ParameterDataSize = (ushort)(paramsBlock.BlockLength + 36);//but why +36?
|
|
shader.ParameterCount = (byte)pVals.Count;
|
|
shader.TextureParametersCount = paramsBlock.TextureParamsCount;
|
|
shader.RenderBucketMask = (1u << shader.RenderBucket) | 0xFF00;
|
|
|
|
|
|
return shader;
|
|
}
|
|
|
|
private TextureBase GetTextureBaseParam(List<string> texNames, int index)
|
|
{
|
|
var name = "givemechecker";
|
|
if (texNames.Count > index)
|
|
{
|
|
var nameval = texNames[index];
|
|
if (nameval != null)
|
|
{
|
|
name = texNames[index];
|
|
}
|
|
}
|
|
var texParam = new TextureBase();
|
|
texParam.Unknown_4h = 1;
|
|
texParam.Unknown_30h = 1;// 131073;//wtf is this? 2x shorts, 0x00020001
|
|
texParam.Unknown_32h = 2;
|
|
texParam.Name = name;
|
|
texParam.NameHash = JenkHash.GenHash(name.ToLowerInvariant());
|
|
return texParam;
|
|
}
|
|
private void AddShaderParam(List<ShaderParamNames> paramNames, List<ShaderParameter> paramValues, ShaderParamNames paramName, object paramValue)
|
|
{
|
|
var p = new ShaderParameter();
|
|
p.Data = paramValue;
|
|
if (paramValue is TextureBase)
|
|
{
|
|
p.DataType = 0;
|
|
p.Unknown_1h = (byte)((paramNames.Count > 0) ? paramNames.Count + 1 : 0);//seriously wtf is this?
|
|
}
|
|
else if (paramValue is Vector4)
|
|
{
|
|
p.DataType = 1;
|
|
}
|
|
else
|
|
{ }
|
|
|
|
paramNames.Add(paramName);
|
|
paramValues.Add(p);
|
|
}
|
|
|
|
private VertexDeclaration GetVertexDeclaration(ShaderFX shader)
|
|
{
|
|
var d = new VertexDeclaration();
|
|
d.Types = VertexDeclarationTypes.GTAV1;
|
|
d.Unknown_6h = 0;
|
|
|
|
switch (shader.Name)
|
|
{
|
|
default:
|
|
case 3839837909: //default
|
|
d.Flags = 89;
|
|
d.Stride = 36;
|
|
d.Count = 4;
|
|
break;
|
|
case 1330140418: //normal
|
|
d.Flags = 16473;
|
|
d.Stride = 52;
|
|
d.Count = 5;
|
|
break;
|
|
}
|
|
|
|
return d;
|
|
}
|
|
|
|
|
|
private bool IsByPolygonVertexMapType(FbxNode node)
|
|
{
|
|
var mapType = node["MappingInformationType"]?.Value as string;
|
|
if (mapType != "ByPolygonVertex")
|
|
{ return false; } //any other types?
|
|
return true;
|
|
}
|
|
private bool IsIndexToDirectRefType(FbxNode node)
|
|
{
|
|
var refType = node["ReferenceInformationType"]?.Value as string;
|
|
var indexed = false;
|
|
switch (refType)
|
|
{
|
|
case "Direct": break;
|
|
case "IndexToDirect": indexed = true; break;
|
|
default:
|
|
break;//shouldn't be possible
|
|
}
|
|
return indexed;
|
|
}
|
|
|
|
private Vector2 GetVector2FromDoubleArray(double[] arr, int i)
|
|
{
|
|
var aIndX = i * 2;
|
|
var aIndY = aIndX + 1;
|
|
var pX = aIndX < arr.Length ? arr[aIndX] : 0;
|
|
var pY = aIndY < arr.Length ? arr[aIndY] : 0;
|
|
return new Vector2((float)pX, (float)pY);
|
|
}
|
|
private Vector3 GetVector3FromDoubleArray(double[] arr, int i)
|
|
{
|
|
var aIndX = i * 3;
|
|
var aIndY = aIndX + 1;
|
|
var aIndZ = aIndX + 2;
|
|
var pX = aIndX < arr.Length ? arr[aIndX] : 0;
|
|
var pY = aIndY < arr.Length ? arr[aIndY] : 0;
|
|
var pZ = aIndZ < arr.Length ? arr[aIndZ] : 0;
|
|
return new Vector3((float)pX, (float)pY, (float)pZ);
|
|
}
|
|
private Vector4 GetVector4FromDoubleArray(double[] arr, int i)
|
|
{
|
|
var aIndX = i * 4;
|
|
var aIndY = aIndX + 1;
|
|
var aIndZ = aIndX + 2;
|
|
var aIndW = aIndX + 3;
|
|
var pX = aIndX < arr.Length ? arr[aIndX] : 0;
|
|
var pY = aIndY < arr.Length ? arr[aIndY] : 0;
|
|
var pZ = aIndZ < arr.Length ? arr[aIndZ] : 0;
|
|
var pW = aIndW < arr.Length ? arr[aIndW] : 0;
|
|
return new Vector4((float)pX, (float)pY, (float)pZ, (float)pW);
|
|
}
|
|
private Vector4 GetVector4FromObjectList(List<object> list, int i)
|
|
{
|
|
var aIndX = i;
|
|
var aIndY = aIndX + 1;
|
|
var aIndZ = aIndX + 2;
|
|
var aIndW = aIndX + 3;
|
|
var pX = aIndX < list.Count ? list[aIndX] : 0;
|
|
var pY = aIndY < list.Count ? list[aIndY] : 0;
|
|
var pZ = aIndZ < list.Count ? list[aIndZ] : 0;
|
|
var pW = aIndW < list.Count ? list[aIndW] : 0;
|
|
var r = Vector4.Zero;
|
|
if (pX is double) r.X = (float)(double)pX;
|
|
if (pY is double) r.Y = (float)(double)pY;
|
|
if (pZ is double) r.Z = (float)(double)pZ;
|
|
if (pW is double) r.W = (float)(double)pW;
|
|
return r;
|
|
}
|
|
private string GetStringFromObjectList(List<object> list, int i)
|
|
{
|
|
return (list.Count > i) ? list[i] as string : string.Empty;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public class FbxModel
|
|
{
|
|
public string Name { get; set; }
|
|
public FbxNode Node { get; set; }
|
|
public DrawableModel Model { get; set; }
|
|
|
|
public override string ToString()
|
|
{
|
|
return Name;
|
|
}
|
|
}
|
|
|
|
public class FbxPolygon
|
|
{
|
|
public FbxVertex[] Vertices { get; set; }
|
|
public FbxNode[] Materials { get; set; }
|
|
}
|
|
|
|
public class FbxVertex : IEquatable<FbxVertex>
|
|
{
|
|
public Vector3 Position { get; set; }
|
|
public Vector3[] Normals { get; set; }
|
|
public Vector3[] Binormals { get; set; }
|
|
public Vector3[] Tangents { get; set; }
|
|
public Vector2[] Texcoords { get; set; }
|
|
public Vector4[] Colours { get; set; }
|
|
public byte[] Bytes { get; set; }
|
|
|
|
public void GenVertexBytes(VertexDeclaration decl)
|
|
{
|
|
Bytes = new byte[decl.Stride];
|
|
|
|
switch ((VertexType)decl.Flags)
|
|
{
|
|
default:
|
|
case VertexType.Default://PNCT
|
|
WriteBytes(Position, 0);
|
|
WriteBytes((Normals?.Length > 0) ? Normals[0] : Vector3.UnitZ, 12);
|
|
WriteBytes(GetColourInt((Colours?.Length > 0) ? Colours[0] : Vector4.One), 24);
|
|
WriteBytes((Texcoords?.Length > 0) ? Texcoords[0] : Vector2.Zero, 28);
|
|
break;
|
|
case VertexType.DefaultEx://PNCTX
|
|
WriteBytes(Position, 0);
|
|
WriteBytes((Normals?.Length > 0) ? Normals[0] : Vector3.UnitZ, 12);
|
|
WriteBytes(GetColourInt((Colours?.Length > 0) ? Colours[0] : Vector4.One), 24);
|
|
WriteBytes((Texcoords?.Length > 0) ? Texcoords[0] : Vector2.Zero, 28);
|
|
WriteBytes(new Vector4((Tangents?.Length > 0) ? Tangents[0] : Vector3.UnitX, 0), 36);
|
|
//WriteBytes(new Vector4((Binormals?.Length > 0) ? Binormals[0] : Vector3.UnitX, 0), 36);
|
|
break;
|
|
}
|
|
|
|
|
|
}
|
|
private void WriteBytes<T>(T val, int offset) where T : struct
|
|
{
|
|
var data = MetaTypes.ConvertToBytes(val);
|
|
for (int i = 0; i < data.Length; i++)
|
|
{
|
|
Bytes[offset + i] = data[i];
|
|
}
|
|
}
|
|
private int GetColourInt(Vector4 c)
|
|
{
|
|
Color v = new Color(c);
|
|
return v.ToRgba();
|
|
}
|
|
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
return Equals(obj as FbxVertex);
|
|
}
|
|
public bool Equals(FbxVertex other)
|
|
{
|
|
return (other != null)
|
|
&& ((Bytes == null) ? (other.Bytes == null) : Bytes.SequenceEqual(other.Bytes));
|
|
}
|
|
public override int GetHashCode()
|
|
{
|
|
var hashCode = -907793594;
|
|
if (Bytes != null) hashCode = hashCode * -1521134295 + ((IStructuralEquatable)Bytes).GetHashCode(EqualityComparer<byte>.Default);
|
|
return hashCode;
|
|
}
|
|
}
|
|
|
|
}
|