mirror of
https://mirror.ghproxy.com/https://github.com/dexyfex/CodeWalker
synced 2026-05-14 17:05:10 +08:00
Merge branch 'master' of https://github.com/dexyfex/CodeWalker
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CodeWalker.Core.GameFiles.FileTypes.Builders
|
||||
{
|
||||
public class YndBuilder
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,473 @@
|
||||
using CodeWalker.GameFiles;
|
||||
using CodeWalker.World;
|
||||
using SharpDX;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CodeWalker.Core.GameFiles.FileTypes.Builders
|
||||
{
|
||||
public class YnvBuilder
|
||||
{
|
||||
/*
|
||||
*
|
||||
* YnvBuilder by dexyfex
|
||||
*
|
||||
* This class allows for conversion of navmesh data in a generic format into .ynv files.
|
||||
* The usage is to call AddPoly() with an array of vertex positions for each polygon.
|
||||
* Polygons should be wound in an anticlockwise direction.
|
||||
* The returned YnvPoly object needs to have its Edges array set by the importer.
|
||||
* YnvPoly.Edges is an array of YnvEdge, with one edge for each vertex in the poly.
|
||||
* The first edge should join the first and second vertices, and the last edge should
|
||||
* join the last and first vertices.
|
||||
* The YnvEdge Poly1 and Poly2 both need to be set to the same value, which is the
|
||||
* corresponding YnvPoly object that was returned by AddPoly.
|
||||
* Flags values on the polygons and edges also need to be set by the importer.
|
||||
*
|
||||
* Once the polygons and edges have all been added, the Build() method should be called,
|
||||
* which will return a list of YnvFile objects. Call the Save() method on each of those
|
||||
* to get the byte array for the .ynv file. The correct filename is given by the
|
||||
* YnvFile.Name property.
|
||||
* Note that the .ynv building process will split polygons that cross .ynv area borders,
|
||||
* and assign all the new polygons into the correct .ynv's.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
private List<YnvPoly> PolyList = new List<YnvPoly>();
|
||||
|
||||
private SpaceNavGrid NavGrid = null;
|
||||
private List<YnvFile> YnvFiles = null;
|
||||
|
||||
public YnvPoly AddPoly(Vector3[] verts)
|
||||
{
|
||||
if ((verts == null) || (verts.Length < 3))
|
||||
{ return null; }
|
||||
|
||||
YnvPoly poly = new YnvPoly();
|
||||
poly.AreaID = 0x3FFF;
|
||||
poly.Index = PolyList.Count;
|
||||
poly.Vertices = verts;
|
||||
|
||||
PolyList.Add(poly);
|
||||
|
||||
return poly;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public List<YnvFile> Build(bool forVehicle)
|
||||
{
|
||||
NavGrid = new SpaceNavGrid();
|
||||
YnvFiles = new List<YnvFile>();
|
||||
|
||||
if (forVehicle) //for vehicle YNV, only need a single ynv, no splitting
|
||||
{
|
||||
//TODO!
|
||||
}
|
||||
else //for static world ynv, need to split polys and generate a set of ynv's.
|
||||
{
|
||||
//1: split polys going over nav grid borders, first by X then by Y
|
||||
var splitpolysX = SplitPolys(PolyList, true);
|
||||
var splitpolysY = SplitPolys(splitpolysX, false);
|
||||
|
||||
//2: assign polys into their new ynv's
|
||||
AddPolysIntoGrid(splitpolysY);
|
||||
|
||||
|
||||
//3: fix up generated ynv's
|
||||
FinalizeYnvs(YnvFiles);
|
||||
|
||||
}
|
||||
|
||||
return YnvFiles;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private List<YnvPoly> SplitPolys(List<YnvPoly> polys, bool xaxis)
|
||||
{
|
||||
var newpolys = new List<YnvPoly>();
|
||||
|
||||
var verts1 = new List<Vector3>();
|
||||
var verts2 = new List<Vector3>();
|
||||
var edges1 = new List<YnvEdge>();
|
||||
var edges2 = new List<YnvEdge>();
|
||||
|
||||
var polysplits = new Dictionary<YnvPoly, YnvPolySplit>();
|
||||
|
||||
foreach (var poly in polys) //split along borders
|
||||
{
|
||||
var verts = poly.Vertices;
|
||||
if (verts == null)
|
||||
{ continue; }//ignore empty polys..
|
||||
if (verts.Length < 3)
|
||||
{ continue; }//not enough verts for a triangle!
|
||||
|
||||
Vector2I gprev = NavGrid.GetCellPos(verts[0]);
|
||||
int split1 = 0;
|
||||
int split2 = 0;
|
||||
for (int i = 1; i < verts.Length; i++)
|
||||
{
|
||||
Vector2I g = NavGrid.GetCellPos(verts[i]);
|
||||
int g1 = xaxis ? g.X : g.Y;
|
||||
int g2 = xaxis ? gprev.X : gprev.Y;
|
||||
if (g1 != g2) //this poly is crossing a border
|
||||
{
|
||||
if (split1 == 0) { split1 = i; }
|
||||
else { split2 = i; break; }
|
||||
}
|
||||
gprev = g;
|
||||
}
|
||||
if (split1 > 0)
|
||||
{
|
||||
var split2beg = (split2 > 0) ? split2 - 1 : verts.Length - 1;
|
||||
var split2end = split2beg + 1;
|
||||
var sv11 = verts[split1 - 1];
|
||||
var sv12 = verts[split1];
|
||||
var sv21 = verts[split2beg];
|
||||
var sv22 = verts[split2];
|
||||
var sp1 = GetSplitPos(sv11, sv12, xaxis);
|
||||
var sp2 = GetSplitPos(sv21, sv22, xaxis);
|
||||
|
||||
//if ((sp1 == sp2) || (sp1 == sv11) || (sp1 == sv12) || (sp2 == sv21) || (sp2 == sv22))
|
||||
if (!IsValidSplit(sp1, sp2, sv11, sv12, sv21, sv22))
|
||||
{
|
||||
//split did nothing, just leave this poly alone
|
||||
newpolys.Add(poly);
|
||||
}
|
||||
else
|
||||
{
|
||||
//split it!
|
||||
var poly1 = new YnvPoly();
|
||||
var poly2 = new YnvPoly();
|
||||
poly1.RawData = poly.RawData;
|
||||
poly2.RawData = poly.RawData;
|
||||
verts1.Clear();
|
||||
verts2.Clear();
|
||||
|
||||
for (int i = 0; i < split1; i++) verts1.Add(verts[i]);
|
||||
verts1.Add(sp1);
|
||||
verts1.Add(sp2);
|
||||
for (int i = split2end; i < verts.Length; i++) verts1.Add(verts[i]);
|
||||
|
||||
verts2.Add(sp1);
|
||||
for (int i = split1; i < split2end; i++) verts2.Add(verts[i]);
|
||||
verts2.Add(sp2);
|
||||
|
||||
poly1.Vertices = verts1.ToArray();
|
||||
poly2.Vertices = verts2.ToArray();
|
||||
|
||||
|
||||
//save this information for the edge splitting pass
|
||||
var polysplit = new YnvPolySplit();
|
||||
polysplit.Orig = poly;
|
||||
polysplit.New1 = poly1;
|
||||
polysplit.New2 = poly2;
|
||||
polysplit.Split1 = split1;
|
||||
polysplit.Split2 = split2end;
|
||||
polysplits[poly] = polysplit;
|
||||
|
||||
|
||||
newpolys.Add(poly1);
|
||||
newpolys.Add(poly2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//no need to split
|
||||
newpolys.Add(poly);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach (var polysplit in polysplits.Values) //build new edges for split polys
|
||||
{
|
||||
//the two edges that were split each need to be turned into two new edges (1 for each poly).
|
||||
//also, the split itself needs to be added as a new edge to the original poly.
|
||||
|
||||
var poly = polysplit.Orig;
|
||||
var poly1 = polysplit.New1;
|
||||
var poly2 = polysplit.New2;
|
||||
var edges = poly.Edges;
|
||||
var verts = poly.Vertices;
|
||||
var ec = edges?.Length ?? 0;
|
||||
if (ec <= 0)
|
||||
{ continue; }//shouldn't happen - no edges?
|
||||
if (ec != poly.Vertices?.Length)
|
||||
{ continue; }//shouldn't happen
|
||||
|
||||
var split1beg = polysplit.Split1 - 1;
|
||||
var split1end = polysplit.Split1;
|
||||
var split2beg = polysplit.Split2 - 1;
|
||||
var split2end = polysplit.Split2;
|
||||
|
||||
edges1.Clear();
|
||||
edges2.Clear();
|
||||
|
||||
var se1 = edges[split1beg]; //the two original edges that got split
|
||||
var se2 = edges[split2beg];
|
||||
var sp1 = TryGetSplit(polysplits, se1.Poly1);//could use Poly2, but they should be the same..
|
||||
var sp2 = TryGetSplit(polysplits, se2.Poly1);
|
||||
var sv1a = verts[split1beg];
|
||||
var sv2a = verts[split2beg];
|
||||
var sp1a = sp1?.GetNearest(sv1a);
|
||||
var sp1b = sp1?.GetOther(sp1a);
|
||||
var sp2b = sp2?.GetNearest(sv2a);
|
||||
var sp2a = sp2?.GetOther(sp2b);
|
||||
var edge1a = new YnvEdge(se1, sp1a);
|
||||
var edge1b = new YnvEdge(se1, sp1b);
|
||||
var edge2a = new YnvEdge(se2, sp2a);
|
||||
var edge2b = new YnvEdge(se2, sp2b);
|
||||
var splita = new YnvEdge(se1, poly2);
|
||||
var splitb = new YnvEdge(se1, poly1);
|
||||
|
||||
for (int i = 0; i < split1beg; i++) edges1.Add(edges[i]);//untouched edges
|
||||
edges1.Add(edge1a);
|
||||
edges1.Add(splita);
|
||||
edges1.Add(edge2a);
|
||||
for (int i = split2end; i < ec; i++) edges1.Add(edges[i]);//untouched edges
|
||||
|
||||
edges2.Add(edge1b);
|
||||
for (int i = split1end; i < split2beg; i++) edges2.Add(edges[i]);//untouched edges
|
||||
edges2.Add(edge2b);
|
||||
edges2.Add(splitb);
|
||||
|
||||
|
||||
poly1.Edges = edges1.ToArray();
|
||||
poly2.Edges = edges2.ToArray();
|
||||
|
||||
if (poly1.Edges.Length != poly1.Vertices.Length)
|
||||
{ }//debug
|
||||
if (poly2.Edges.Length != poly2.Vertices.Length)
|
||||
{ }//debug
|
||||
|
||||
}
|
||||
|
||||
foreach (var poly in newpolys) //fix any untouched edges that joined to split polys
|
||||
{
|
||||
if (poly.Edges?.Length != poly.Vertices?.Length)
|
||||
{ continue; }//shouldn't happen (no edges?)
|
||||
for (int i = 0; i < poly.Edges.Length; i++)
|
||||
{
|
||||
var edge = poly.Edges[i];
|
||||
var vert = poly.Vertices[i];
|
||||
if (edge == null)
|
||||
{ continue; }//shouldn't happen
|
||||
if (edge.Poly1 != edge.Poly2)
|
||||
{ continue; }//shouldn't happen?
|
||||
if (edge.Poly1 == null)
|
||||
{ continue; }//probably this edge joins to nothing
|
||||
|
||||
|
||||
YnvPolySplit polysplit;
|
||||
if (polysplits.TryGetValue(edge.Poly1, out polysplit))
|
||||
{
|
||||
var newpoly = polysplit.GetNearest(vert);
|
||||
if (newpoly == null)
|
||||
{ }//debug
|
||||
edge.Poly1 = newpoly;
|
||||
edge.Poly2 = newpoly;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return newpolys;
|
||||
}
|
||||
|
||||
private Vector3 GetSplitPos(Vector3 a, Vector3 b, bool xaxis)
|
||||
{
|
||||
Vector3 ca = NavGrid.GetCellRel(a);
|
||||
Vector3 cb = NavGrid.GetCellRel(b);
|
||||
float fa = xaxis ? ca.X : ca.Y;
|
||||
float fb = xaxis ? cb.X : cb.Y;
|
||||
float f = 0;
|
||||
if (fb > fa)
|
||||
{
|
||||
float ib = (float)Math.Floor(fb);
|
||||
f = (ib - fa) / (fb - fa);
|
||||
}
|
||||
else
|
||||
{
|
||||
float ia = (float)Math.Floor(fa);
|
||||
f = (fa - ia) / (fa - fb);
|
||||
}
|
||||
if (f < 0.0f)
|
||||
{ }//debug
|
||||
if (f > 1.0f)
|
||||
{ }//debug
|
||||
return a + (b - a) * Math.Min(Math.Max(f, 0.0f), 1.0f);
|
||||
}
|
||||
|
||||
private bool IsValidSplit(Vector3 s1, Vector3 s2, Vector3 v1a, Vector3 v1b, Vector3 v2a, Vector3 v2b)
|
||||
{
|
||||
if (XYEqual(s1, s2)) return false;
|
||||
if (XYEqual(s1, v1a)) return false;
|
||||
if (XYEqual(s1, v1b)) return false;
|
||||
if (XYEqual(s2, v2a)) return false;
|
||||
if (XYEqual(s2, v2b)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool XYEqual(Vector3 v1, Vector3 v2)
|
||||
{
|
||||
return ((v1.X == v2.X) && (v1.Y == v2.Y));
|
||||
}
|
||||
|
||||
private class YnvPolySplit
|
||||
{
|
||||
public YnvPoly Orig;
|
||||
public YnvPoly New1;
|
||||
public YnvPoly New2;
|
||||
public int Split1;
|
||||
public int Split2;
|
||||
public YnvPoly GetNearest(Vector3 v)
|
||||
{
|
||||
if (New1?.Vertices == null) return New2;
|
||||
if (New2?.Vertices == null) return New1;
|
||||
float len1 = float.MaxValue;
|
||||
float len2 = float.MaxValue;
|
||||
for (int i = 0; i < New1.Vertices.Length; i++)
|
||||
{
|
||||
len1 = Math.Min(len1, (v - New1.Vertices[i]).LengthSquared());
|
||||
}
|
||||
if (len1 == 0.0f) return New1;
|
||||
for (int i = 0; i < New2.Vertices.Length; i++)
|
||||
{
|
||||
len2 = Math.Min(len2, (v - New2.Vertices[i]).LengthSquared());
|
||||
}
|
||||
if (len2 == 0.0f) return New2;
|
||||
return (len1 <= len2) ? New1 : New2;
|
||||
}
|
||||
public YnvPoly GetOther(YnvPoly p)
|
||||
{
|
||||
if (p == New1) return New2;
|
||||
return New1;
|
||||
}
|
||||
}
|
||||
private YnvPolySplit TryGetSplit(Dictionary<YnvPoly, YnvPolySplit> polysplits, YnvPoly poly)
|
||||
{
|
||||
if (poly == null) return null;
|
||||
YnvPolySplit r = null;
|
||||
polysplits.TryGetValue(poly, out r);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void AddPolysIntoGrid(List<YnvPoly> polys)
|
||||
{
|
||||
foreach (var poly in polys)
|
||||
{
|
||||
poly.CalculatePosition();
|
||||
var pos = poly.Position;
|
||||
var cell = NavGrid.GetCell(pos);
|
||||
|
||||
var ynv = cell.Ynv;
|
||||
if (ynv == null)
|
||||
{
|
||||
ynv = new YnvFile();
|
||||
ynv.Name = "navmesh[" + cell.FileX.ToString() + "][" + cell.FileY.ToString() + "]";
|
||||
ynv.Nav = new NavMesh();
|
||||
ynv.Nav.SetDefaults(false);
|
||||
ynv.Nav.AABBSize = new Vector3(NavGrid.CellSize, NavGrid.CellSize, 0.0f);
|
||||
ynv.Nav.SectorTree = new NavMeshSector();
|
||||
ynv.Nav.SectorTree.AABBMin = new Vector4(NavGrid.GetCellMin(cell), 0.0f);
|
||||
ynv.Nav.SectorTree.AABBMax = new Vector4(NavGrid.GetCellMax(cell), 0.0f);
|
||||
ynv.AreaID = cell.X + cell.Y * 100;
|
||||
ynv.Polys = new List<YnvPoly>();
|
||||
ynv.HasChanged = true;//mark it for the project window
|
||||
ynv.RpfFileEntry = new RpfResourceFileEntry();
|
||||
ynv.RpfFileEntry.Name = ynv.Name + ".ynv";
|
||||
ynv.RpfFileEntry.Path = string.Empty;
|
||||
cell.Ynv = ynv;
|
||||
YnvFiles.Add(ynv);
|
||||
}
|
||||
|
||||
poly.AreaID = (ushort)ynv.AreaID;
|
||||
poly.Index = ynv.Polys.Count;
|
||||
poly.Ynv = ynv;
|
||||
ynv.Polys.Add(poly);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void FinalizeYnvs(List<YnvFile> ynvs)
|
||||
{
|
||||
|
||||
foreach (var ynv in ynvs)
|
||||
{
|
||||
//find zmin and zmax and update AABBSize and SectorTree root
|
||||
float zmin = float.MaxValue;
|
||||
float zmax = float.MinValue;
|
||||
foreach (var poly in ynv.Polys)
|
||||
{
|
||||
foreach (var vert in poly.Vertices)
|
||||
{
|
||||
zmin = Math.Min(zmin, vert.Z);
|
||||
zmax = Math.Max(zmax, vert.Z);
|
||||
}
|
||||
}
|
||||
var yn = ynv.Nav;
|
||||
var ys = yn.SectorTree;
|
||||
yn.AABBSize = new Vector3(yn.AABBSize.X, yn.AABBSize.Y, zmax - zmin);
|
||||
ys.AABBMin = new Vector4(ys.AABBMin.X, ys.AABBMin.Y, zmin, 0.0f);
|
||||
ys.AABBMax = new Vector4(ys.AABBMax.X, ys.AABBMax.Y, zmax, 0.0f);
|
||||
|
||||
|
||||
ynv.UpdateContentFlags(false);
|
||||
|
||||
|
||||
|
||||
//fix up flags on edges that cross ynv borders
|
||||
foreach (var poly in ynv.Polys)
|
||||
{
|
||||
bool border = false;
|
||||
if (poly.Edges == null)
|
||||
{ continue; }
|
||||
foreach (var edge in poly.Edges)
|
||||
{
|
||||
if (edge.Poly1 != null)
|
||||
{
|
||||
if (edge.Poly1.AreaID != poly.AreaID)
|
||||
{
|
||||
edge._RawData._Poly1.Unk2 = 0;//crash without this
|
||||
edge._RawData._Poly2.Unk2 = 0;//crash without this
|
||||
edge._RawData._Poly2.Unk3 = 4;////// edge._RawData._Poly2.Unk3 | 4;
|
||||
border = true;
|
||||
|
||||
////DEBUG don't join edges
|
||||
//edge.Poly1 = null;
|
||||
//edge.Poly2 = null;
|
||||
//edge.AreaID1 = 0x3FFF;
|
||||
//edge.AreaID2 = 0x3FFF;
|
||||
//edge._RawData._Poly1.PolyID = 0x3FFF;
|
||||
//edge._RawData._Poly2.PolyID = 0x3FFF;
|
||||
//edge._RawData._Poly1.Unk2 = 1;
|
||||
//edge._RawData._Poly2.Unk2 = 1;
|
||||
//edge._RawData._Poly1.Unk3 = 0;
|
||||
//edge._RawData._Poly2.Unk3 = 0;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
poly.B19_IsCellEdge = border;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using CodeWalker.Core.Utils;
|
||||
using CodeWalker.World;
|
||||
|
||||
namespace CodeWalker.GameFiles
|
||||
{
|
||||
@@ -925,6 +927,7 @@ namespace CodeWalker.GameFiles
|
||||
GrassInstanceBatches = batches.ToArray();
|
||||
|
||||
HasChanged = true;
|
||||
UpdateGrassPhysDict(true);
|
||||
}
|
||||
|
||||
public bool RemoveGrassBatch(YmapGrassInstanceBatch batch)
|
||||
@@ -949,6 +952,10 @@ namespace CodeWalker.GameFiles
|
||||
}
|
||||
}
|
||||
|
||||
if (batches.Count <= 0)
|
||||
{
|
||||
UpdateGrassPhysDict(false);
|
||||
}
|
||||
|
||||
GrassInstanceBatches = batches.ToArray();
|
||||
|
||||
@@ -1137,6 +1144,62 @@ namespace CodeWalker.GameFiles
|
||||
}
|
||||
|
||||
|
||||
private void UpdateGrassPhysDict(bool add)
|
||||
{
|
||||
var physDict = physicsDictionaries?.ToList() ?? new List<MetaHash>();
|
||||
var vproc1 = JenkHash.GenHash("v_proc1");
|
||||
var vproc2 = JenkHash.GenHash("v_proc2"); // I think you need vproc2 as well.
|
||||
var change = false;
|
||||
if (!physDict.Contains(vproc1))
|
||||
{
|
||||
change = true;
|
||||
if (add) physDict.Add(vproc1);
|
||||
else physDict.Remove(vproc1);
|
||||
}
|
||||
if (!physDict.Contains(vproc2))
|
||||
{
|
||||
change = true;
|
||||
if (add) physDict.Add(vproc2);
|
||||
else physDict.Remove(vproc2);
|
||||
}
|
||||
if (change) physicsDictionaries = physDict.ToArray();
|
||||
}
|
||||
|
||||
public void InitYmapEntityArchetypes(GameFileCache gfc)
|
||||
{
|
||||
if (AllEntities != null)
|
||||
{
|
||||
for (int i = 0; i < AllEntities.Length; i++)
|
||||
{
|
||||
var ent = AllEntities[i];
|
||||
var arch = gfc.GetArchetype(ent.CEntityDef.archetypeName);
|
||||
ent.SetArchetype(arch);
|
||||
if (ent.IsMlo) ent.MloInstance.InitYmapEntityArchetypes(gfc);
|
||||
}
|
||||
}
|
||||
if (GrassInstanceBatches != null)
|
||||
{
|
||||
for (int i = 0; i < GrassInstanceBatches.Length; i++)
|
||||
{
|
||||
var batch = GrassInstanceBatches[i];
|
||||
batch.Archetype = gfc.GetArchetype(batch.Batch.archetypeName);
|
||||
}
|
||||
}
|
||||
|
||||
if (TimeCycleModifiers != null)
|
||||
{
|
||||
for (int i = 0; i < TimeCycleModifiers.Length; i++)
|
||||
{
|
||||
var tcm = TimeCycleModifiers[i];
|
||||
World.TimecycleMod wtcm;
|
||||
if (gfc.TimeCycleModsDict.TryGetValue(tcm.CTimeCycleModifier.name.Hash, out wtcm))
|
||||
{
|
||||
tcm.TimeCycleModData = wtcm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static uint SetBit(uint value, int bit)
|
||||
{
|
||||
@@ -1166,7 +1229,7 @@ namespace CodeWalker.GameFiles
|
||||
public bool IsMlo { get; set; }
|
||||
public MloInstanceData MloInstance { get; set; }
|
||||
public YmapEntityDef MloParent { get; set; }
|
||||
public MCMloEntitySet MloEntitySet { get; set; }
|
||||
public MloInstanceEntitySet MloEntitySet { get; set; }
|
||||
public Vector3 MloRefPosition { get; set; }
|
||||
public Quaternion MloRefOrientation { get; set; }
|
||||
public MetaWrapper[] Extensions { get; set; }
|
||||
@@ -1269,56 +1332,57 @@ namespace CodeWalker.GameFiles
|
||||
{
|
||||
MloInstance = new MloInstanceData();
|
||||
}
|
||||
MloInstance.CreateYmapEntities(this, mloa);
|
||||
if (mloa != null)
|
||||
{
|
||||
if (!IsMlo)
|
||||
{
|
||||
IsMlo = true;
|
||||
MloInstance._Instance = new CMloInstanceDef { CEntityDef = _CEntityDef };
|
||||
|
||||
List<YmapEntityDef> mloEntities = Ymap.MloEntities?.ToList() ?? new List<YmapEntityDef>();
|
||||
mloEntities.Add(this);
|
||||
Ymap.MloEntities = mloEntities.ToArray();
|
||||
}
|
||||
|
||||
MloInstance.CreateYmapEntities(this, mloa);
|
||||
}
|
||||
|
||||
if (BSRadius == 0.0f)
|
||||
{
|
||||
BSRadius = CEntityDef.lodDist;//need something so it doesn't get culled...
|
||||
}
|
||||
}
|
||||
else if (IsMlo) // archetype is no longer an mlo
|
||||
{
|
||||
IsMlo = false;
|
||||
MloInstance = null;
|
||||
|
||||
if (Ymap.MloEntities != null)
|
||||
{
|
||||
List<YmapEntityDef> mloEntities = Ymap.MloEntities.ToList();
|
||||
if (mloEntities.Remove(this))
|
||||
{
|
||||
Ymap.MloEntities = mloEntities.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void SetPosition(Vector3 pos)
|
||||
{
|
||||
Position = pos;
|
||||
if (MloParent != null)
|
||||
{
|
||||
//TODO: SetPosition for interior entities!
|
||||
Position = pos;
|
||||
var inst = MloParent.MloInstance;
|
||||
if (inst != null)
|
||||
{
|
||||
//transform world position into mlo space
|
||||
//MloRefPosition = ...
|
||||
//MloRefOrientation = ...
|
||||
}
|
||||
_CEntityDef.position = Quaternion.Normalize(Quaternion.Invert(MloParent.Orientation)).Multiply(pos - MloParent.Position);
|
||||
MloRefPosition = _CEntityDef.position;
|
||||
UpdateBB();
|
||||
UpdateMloArchetype();
|
||||
}
|
||||
else
|
||||
{
|
||||
Position = pos;
|
||||
_CEntityDef.position = pos;
|
||||
|
||||
if (Archetype != null)
|
||||
{
|
||||
BSCenter = Orientation.Multiply(Archetype.BSCenter) * Scale;
|
||||
}
|
||||
if ((Archetype != null) && (Orientation == Quaternion.Identity))
|
||||
{
|
||||
BBMin = (Archetype.BBMin * Scale) + Position;
|
||||
BBMax = (Archetype.BBMax * Scale) + Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
BBMin = Position - (BSRadius);
|
||||
BBMax = Position + (BSRadius);
|
||||
////not ideal: should transform all 8 corners!
|
||||
}
|
||||
|
||||
|
||||
|
||||
UpdateWidgetPosition();
|
||||
UpdateBB();
|
||||
}
|
||||
|
||||
|
||||
@@ -1328,40 +1392,51 @@ namespace CodeWalker.GameFiles
|
||||
MloInstance.UpdateEntities();
|
||||
}
|
||||
|
||||
UpdateWidgetPosition();
|
||||
}
|
||||
|
||||
public void SetOrientation(Quaternion ori)
|
||||
private void UpdateBB()
|
||||
{
|
||||
Quaternion inv = Quaternion.Normalize(Quaternion.Invert(ori));
|
||||
Orientation = ori;
|
||||
_CEntityDef.rotation = new Vector4(inv.X, inv.Y, inv.Z, inv.W);
|
||||
|
||||
if (MloInstance != null)
|
||||
{
|
||||
MloInstance.SetOrientation(ori);
|
||||
}
|
||||
|
||||
|
||||
if (Archetype != null)
|
||||
{
|
||||
BSCenter = Orientation.Multiply(Archetype.BSCenter) * Scale;
|
||||
}
|
||||
|
||||
UpdateWidgetPosition();
|
||||
UpdateWidgetOrientation();
|
||||
if ((Archetype != null) && (Orientation == Quaternion.Identity))
|
||||
{
|
||||
BBMin = (Archetype.BBMin * Scale) + Position;
|
||||
BBMax = (Archetype.BBMax * Scale) + Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
BBMin = Position - (BSRadius);
|
||||
BBMax = Position + (BSRadius);
|
||||
////not ideal: should transform all 8 corners!
|
||||
}
|
||||
}
|
||||
public void SetOrientationInv(Quaternion inv)
|
||||
|
||||
public void SetOrientation(Quaternion ori, bool inverse = false)
|
||||
{
|
||||
Quaternion ori = Quaternion.Normalize(Quaternion.Invert(inv));
|
||||
Orientation = ori;
|
||||
_CEntityDef.rotation = new Vector4(inv.X, inv.Y, inv.Z, inv.W);
|
||||
if (MloParent != null)
|
||||
{
|
||||
var mloInv = Quaternion.Normalize(Quaternion.Invert(MloParent.Orientation));
|
||||
Quaternion rel = Quaternion.Normalize(Quaternion.Multiply(mloInv, ori));
|
||||
Quaternion inv = Quaternion.Normalize(Quaternion.Invert(rel));
|
||||
Orientation = ori;
|
||||
_CEntityDef.rotation = inv.ToVector4();
|
||||
}
|
||||
else
|
||||
{
|
||||
Quaternion inv = inverse ? ori : Quaternion.Normalize(Quaternion.Invert(ori));
|
||||
ori = inverse ? Quaternion.Normalize(Quaternion.Invert(ori)) : ori;
|
||||
Orientation = ori;
|
||||
_CEntityDef.rotation = inv.ToVector4();
|
||||
}
|
||||
|
||||
if (MloInstance != null)
|
||||
{
|
||||
MloInstance.SetOrientation(ori);
|
||||
}
|
||||
|
||||
|
||||
if (Archetype != null)
|
||||
{
|
||||
BSCenter = Orientation.Multiply(Archetype.BSCenter) * Scale;
|
||||
@@ -1376,14 +1451,35 @@ namespace CodeWalker.GameFiles
|
||||
Scale = new Vector3(s.X, s.X, s.Z);
|
||||
_CEntityDef.scaleXY = s.X;
|
||||
_CEntityDef.scaleZ = s.Z;
|
||||
|
||||
MloInstanceData mloInstance = MloParent?.MloInstance;
|
||||
if (mloInstance != null)
|
||||
{
|
||||
var mcEntity = mloInstance.TryGetArchetypeEntity(this);
|
||||
if (mcEntity != null)
|
||||
{
|
||||
mcEntity._Data.scaleXY = s.X;
|
||||
mcEntity._Data.scaleZ = s.Z;
|
||||
}
|
||||
}
|
||||
if (Archetype != null)
|
||||
{
|
||||
float smax = Math.Max(Scale.X, Scale.Z);
|
||||
BSRadius = Archetype.BSRadius * smax;
|
||||
}
|
||||
|
||||
SetPosition(Position);//update the BB
|
||||
}
|
||||
|
||||
private void UpdateMloArchetype()
|
||||
{
|
||||
if (!(MloParent.Archetype is MloArchetype mloArchetype)) return;
|
||||
if (Index >= mloArchetype.entities.Length) return;
|
||||
|
||||
MCEntityDef entity = mloArchetype.entities[Index];
|
||||
entity._Data.position = _CEntityDef.position;
|
||||
entity._Data.rotation = _CEntityDef.rotation;
|
||||
}
|
||||
|
||||
|
||||
public void SetPivotPosition(Vector3 pos)
|
||||
@@ -1493,6 +1589,8 @@ namespace CodeWalker.GameFiles
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
public class YmapGrassInstanceBatch
|
||||
{
|
||||
private const float BatchVertMultiplier = 0.00001525878f;
|
||||
|
||||
public Archetype Archetype { get; set; } //cached by GameFileCache on loading...
|
||||
public rage__fwGrassInstanceListDef Batch { get; set; }
|
||||
public rage__fwGrassInstanceListDef__InstanceData[] Instances { get; set; }
|
||||
@@ -1504,10 +1602,478 @@ namespace CodeWalker.GameFiles
|
||||
public float Distance; //used for rendering
|
||||
public YmapFile Ymap { get; set; }
|
||||
|
||||
private List<BoundingBox> grassBounds; // for brush
|
||||
public bool BrushEnabled; // for brush
|
||||
public float BrushRadius = 5f; // for brush
|
||||
public bool HasChanged; // for brush and renderer
|
||||
|
||||
// TODO: Make configurable.
|
||||
const float BoundingSize = 0.3F;
|
||||
static readonly Vector3 GrassMinMax = Vector3.One * BoundingSize;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Batch.ToString();
|
||||
}
|
||||
|
||||
public void UpdateInstanceCount()
|
||||
{
|
||||
var b = Batch;
|
||||
var ins = b.InstanceList;
|
||||
ins.Count1 = (ushort)Instances.Length;
|
||||
b.InstanceList = ins;
|
||||
Batch = b;
|
||||
}
|
||||
|
||||
public bool IsPointBlockedByInstance(Vector3 point)
|
||||
{
|
||||
return grassBounds.Any(bb => bb.Contains(point) == ContainmentType.Contains);
|
||||
}
|
||||
|
||||
private void ReInitializeBoundingCache()
|
||||
{
|
||||
// cache is already initialized correctly.
|
||||
if (grassBounds != null && (grassBounds.Count == Instances.Length))
|
||||
return;
|
||||
|
||||
// Clear the current bounding cache.
|
||||
if (grassBounds == null)
|
||||
grassBounds = new List<BoundingBox>();
|
||||
else grassBounds?.Clear();
|
||||
|
||||
foreach (var inst in Instances)
|
||||
{
|
||||
// create bounding box for this instance.
|
||||
var worldPos = GetGrassWorldPos(inst.Position, new BoundingBox(AABBMin, AABBMax));
|
||||
var bb = new BoundingBox(worldPos - GrassMinMax, worldPos + GrassMinMax);
|
||||
grassBounds.Add(bb);
|
||||
}
|
||||
}
|
||||
|
||||
public bool EraseInstancesAtMouse(
|
||||
YmapGrassInstanceBatch batch,
|
||||
SpaceRayIntersectResult mouseRay,
|
||||
float radius)
|
||||
{
|
||||
rage__spdAABB batchAABB = batch.Batch.BatchAABB;
|
||||
var oldInstanceBounds = new BoundingBox
|
||||
(
|
||||
batchAABB.min.XYZ(),
|
||||
batchAABB.max.XYZ()
|
||||
);
|
||||
var deleteSphere = new BoundingSphere(mouseRay.Position, radius);
|
||||
|
||||
// check each instance to see if it's in the delete sphere
|
||||
// thankfully we've just avoided an O(n^2) op using this bounds stuff (doesn't mean it's super fast though,
|
||||
// but it's not super slow either, even at like 50,000 instances)
|
||||
var insList = new List<rage__fwGrassInstanceListDef__InstanceData>();
|
||||
foreach (var instance in batch.Instances)
|
||||
{
|
||||
// get the world pos
|
||||
var worldPos = GetGrassWorldPos(instance.Position, oldInstanceBounds);
|
||||
|
||||
// create a boundary around the instance.
|
||||
var instanceBounds = new BoundingBox(worldPos - GrassMinMax, worldPos + GrassMinMax);
|
||||
|
||||
// check if the sphere contains the boundary.
|
||||
var bb = new BoundingBox(instanceBounds.Minimum, instanceBounds.Maximum);
|
||||
var ct = deleteSphere.Contains(ref bb);
|
||||
if (ct == ContainmentType.Contains || ct == ContainmentType.Intersects)
|
||||
{
|
||||
//delInstances.Add(instance); // Add a copy of this instance
|
||||
continue;
|
||||
}
|
||||
insList.Add(instance);
|
||||
}
|
||||
if (insList.Count == Instances.Length)
|
||||
return false;
|
||||
|
||||
var newBounds = GetNewGrassBounds(insList, oldInstanceBounds);
|
||||
// recalc instances
|
||||
var b = RecalcBatch(newBounds, batch);
|
||||
batch.Batch = b;
|
||||
insList = RecalculateInstances(insList, oldInstanceBounds, newBounds);
|
||||
batch.Instances = insList.ToArray();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CreateInstancesAtMouse(
|
||||
YmapGrassInstanceBatch batch,
|
||||
SpaceRayIntersectResult mouseRay,
|
||||
float radius,
|
||||
int amount,
|
||||
Func<Vector3, SpaceRayIntersectResult> spawnRayFunc,
|
||||
Color color,
|
||||
int ao,
|
||||
int scale,
|
||||
Vector3 pad,
|
||||
bool randomScale)
|
||||
{
|
||||
|
||||
ReInitializeBoundingCache();
|
||||
var spawnPosition = mouseRay.Position;
|
||||
var positions = new List<Vector3>();
|
||||
var normals = new List<Vector3>();
|
||||
|
||||
// Get rand positions.
|
||||
GetSpawns(spawnPosition, spawnRayFunc, positions, normals, radius, amount);
|
||||
if (positions.Count <= 0) return;
|
||||
|
||||
// get the instance list
|
||||
var instances =
|
||||
batch.Instances?.ToList() ?? new List<rage__fwGrassInstanceListDef__InstanceData>();
|
||||
var batchAABB = batch.Batch.BatchAABB;
|
||||
|
||||
// make sure to store the old instance bounds for the original
|
||||
// grass instances
|
||||
var oldInstanceBounds = new BoundingBox(batchAABB.min.XYZ(), batchAABB.max.XYZ());
|
||||
|
||||
if (positions.Count <= 0)
|
||||
return;
|
||||
|
||||
// Begin the spawn bounds.
|
||||
var grassBound = new BoundingBox(positions[0] - GrassMinMax, positions[0] + GrassMinMax);
|
||||
grassBound = EncapsulatePositions(positions, grassBound);
|
||||
|
||||
// Calculate the new spawn bounds.
|
||||
var newInstanceBounds = new BoundingBox(oldInstanceBounds.Minimum, oldInstanceBounds.Maximum);
|
||||
newInstanceBounds = instances.Count > 0
|
||||
? newInstanceBounds.Encapsulate(grassBound)
|
||||
: new BoundingBox(grassBound.Minimum, grassBound.Maximum);
|
||||
|
||||
// now we need to recalculate the position of each instance
|
||||
instances = RecalculateInstances(instances, oldInstanceBounds, newInstanceBounds);
|
||||
|
||||
// Add new instances at each spawn position with
|
||||
// the parameters in the brush.
|
||||
SpawnInstances(positions, normals, instances, newInstanceBounds, color, ao, scale, pad, randomScale);
|
||||
|
||||
// then recalc the bounds of the grass batch
|
||||
var b = RecalcBatch(newInstanceBounds, batch);
|
||||
|
||||
// plug our values back in and refresh the ymap.
|
||||
batch.Batch = b;
|
||||
|
||||
// Give back the new intsances
|
||||
batch.Instances = instances.ToArray();
|
||||
grassBounds.Clear();
|
||||
}
|
||||
|
||||
// bhv approach recommended by dexy.
|
||||
public YmapGrassInstanceBatch[] OptimizeInstances(YmapGrassInstanceBatch batch, float minRadius)
|
||||
{
|
||||
// this function will return an array of grass instance batches
|
||||
// that are split up into sectors (groups) with a specific size.
|
||||
// say for instance we have 30,000 instances spread across a large
|
||||
// distance. We will split those instances into a grid-like group
|
||||
// and return the groups as an array of batches.
|
||||
var oldInstanceBounds = new BoundingBox(batch.Batch.BatchAABB.min.XYZ(), batch.Batch.BatchAABB.max.XYZ());
|
||||
|
||||
if (oldInstanceBounds.Radius() < minRadius)
|
||||
{
|
||||
return new [] { batch };
|
||||
}
|
||||
|
||||
// Get our optimized grassInstances
|
||||
var split = SplitGrassRecursive(batch.Instances.ToList(), oldInstanceBounds, minRadius);
|
||||
|
||||
// Initiate a new batch list.
|
||||
var newBatches = new List<YmapGrassInstanceBatch>();
|
||||
|
||||
foreach (var grassList in split)
|
||||
{
|
||||
// Create a new batch
|
||||
var newBatch = new YmapGrassInstanceBatch
|
||||
{
|
||||
Archetype = batch.Archetype,
|
||||
Ymap = batch.Ymap
|
||||
};
|
||||
|
||||
// Get the boundary of the grassInstances
|
||||
var newInstanceBounds = GetNewGrassBounds(grassList, oldInstanceBounds);
|
||||
|
||||
// Recalculate the batch boundaries.
|
||||
var b = RecalcBatch(newInstanceBounds, newBatch);
|
||||
newBatch.Batch = b;
|
||||
|
||||
var ins = RecalculateInstances(grassList, oldInstanceBounds, newInstanceBounds);
|
||||
newBatch.Instances = ins.ToArray();
|
||||
newBatches.Add(newBatch);
|
||||
}
|
||||
|
||||
return newBatches.ToArray();
|
||||
}
|
||||
|
||||
private List<List<rage__fwGrassInstanceListDef__InstanceData>> SplitGrassRecursive(
|
||||
IReadOnlyList<rage__fwGrassInstanceListDef__InstanceData> grassInstances,
|
||||
BoundingBox batchAABB,
|
||||
float minRadius = 15F
|
||||
)
|
||||
{
|
||||
var ret = new List<List<rage__fwGrassInstanceListDef__InstanceData>>();
|
||||
var oldPoints = SplitGrass(grassInstances, batchAABB);
|
||||
while (true)
|
||||
{
|
||||
var stop = true;
|
||||
var newPoints = new List<List<rage__fwGrassInstanceListDef__InstanceData>>();
|
||||
foreach (var mb in oldPoints)
|
||||
{
|
||||
// for some reason we got a null group?
|
||||
if (mb == null)
|
||||
continue;
|
||||
|
||||
// Get the bounds of the grassInstances list
|
||||
var radius = GetNewGrassBounds(mb, batchAABB).Radius();
|
||||
|
||||
// check if the radius of the grassInstances
|
||||
if (radius <= minRadius)
|
||||
{
|
||||
// this point list is within the minimum
|
||||
// radius.
|
||||
ret.Add(mb);
|
||||
continue; // we don't need to continue.
|
||||
}
|
||||
|
||||
// since we're here let's keep going
|
||||
stop = false;
|
||||
|
||||
// split the grassInstances again
|
||||
var s = SplitGrass(mb, batchAABB);
|
||||
|
||||
// add it into the new grassInstances list.
|
||||
newPoints.AddRange(s);
|
||||
}
|
||||
|
||||
// set the old grassInstances to the new grassInstances.
|
||||
oldPoints = newPoints.ToArray();
|
||||
|
||||
// if we're done, and all grassInstances are within the desired size
|
||||
// then end the loop.
|
||||
if (stop) break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private List<rage__fwGrassInstanceListDef__InstanceData>[] SplitGrass(
|
||||
IReadOnlyList<rage__fwGrassInstanceListDef__InstanceData> points,
|
||||
BoundingBox batchAABB)
|
||||
{
|
||||
var pointGroup = new List<rage__fwGrassInstanceListDef__InstanceData>[2];
|
||||
|
||||
// Calculate the bounds of these grassInstances.
|
||||
var m = GetNewGrassBounds(points, batchAABB);
|
||||
|
||||
// Get the center and size
|
||||
var mm = new Vector3
|
||||
{
|
||||
X = Math.Abs(m.Minimum.X - m.Maximum.X),
|
||||
Y = Math.Abs(m.Minimum.Y - m.Maximum.Y),
|
||||
Z = Math.Abs(m.Minimum.Z - m.Maximum.Z)
|
||||
};
|
||||
|
||||
// x is the greatest axis...
|
||||
if (mm.X > mm.Y && mm.X > mm.Z)
|
||||
{
|
||||
// Calculate both boundaries.
|
||||
var lhs = new BoundingBox(m.Minimum, m.Maximum - new Vector3(mm.X * 0.5F, 0, 0));
|
||||
var rhs = new BoundingBox(m.Minimum + new Vector3(mm.X * 0.5F, 0, 0), m.Maximum);
|
||||
|
||||
// Set the grassInstances accordingly.
|
||||
pointGroup[0] = points
|
||||
.Where(p => lhs.Contains(GetGrassWorldPos(p.Position, batchAABB)) == ContainmentType.Contains).ToList();
|
||||
pointGroup[1] = points
|
||||
.Where(p => rhs.Contains(GetGrassWorldPos(p.Position, batchAABB)) == ContainmentType.Contains).ToList();
|
||||
}
|
||||
// y is the greatest axis...
|
||||
else if (mm.Y > mm.X && mm.Y > mm.Z)
|
||||
{
|
||||
// Calculate both boundaries.
|
||||
var lhs = new BoundingBox(m.Minimum, m.Maximum - new Vector3(0, mm.Y * 0.5F, 0));
|
||||
var rhs = new BoundingBox(m.Minimum + new Vector3(0, mm.Y * 0.5F, 0), m.Maximum);
|
||||
|
||||
// Set the grassInstances accordingly.
|
||||
pointGroup[0] = points
|
||||
.Where(p => lhs.Contains(GetGrassWorldPos(p.Position, batchAABB)) == ContainmentType.Contains).ToList();
|
||||
pointGroup[1] = points
|
||||
.Where(p => rhs.Contains(GetGrassWorldPos(p.Position, batchAABB)) == ContainmentType.Contains).ToList();
|
||||
}
|
||||
// z is the greatest axis...
|
||||
else if (mm.Z > mm.X && mm.Z > mm.Y)
|
||||
{
|
||||
// Calculate both boundaries.
|
||||
var lhs = new BoundingBox(m.Minimum, m.Maximum - new Vector3(0, 0, mm.Z * 0.5F));
|
||||
var rhs = new BoundingBox(m.Minimum + new Vector3(0, 0, mm.Z * 0.5F), m.Maximum);
|
||||
|
||||
// Set the grassInstances accordingly.
|
||||
pointGroup[0] = points
|
||||
.Where(p => lhs.Contains(GetGrassWorldPos(p.Position, batchAABB)) == ContainmentType.Contains).ToList();
|
||||
pointGroup[1] = points
|
||||
.Where(p => rhs.Contains(GetGrassWorldPos(p.Position, batchAABB)) == ContainmentType.Contains).ToList();
|
||||
}
|
||||
return pointGroup;
|
||||
}
|
||||
|
||||
private static BoundingBox GetNewGrassBounds(IReadOnlyList<rage__fwGrassInstanceListDef__InstanceData> newGrass, BoundingBox oldAABB)
|
||||
{
|
||||
var grassPositions = newGrass.Select(x => GetGrassWorldPos(x.Position, oldAABB)).ToArray();
|
||||
return BoundingBox.FromPoints(grassPositions).Expand(1f);
|
||||
}
|
||||
|
||||
private void SpawnInstances(
|
||||
IReadOnlyList<Vector3> positions,
|
||||
IReadOnlyList<Vector3> normals,
|
||||
ICollection<rage__fwGrassInstanceListDef__InstanceData> instanceList,
|
||||
BoundingBox instanceBounds,
|
||||
Color color,
|
||||
int ao,
|
||||
int scale,
|
||||
Vector3 pad,
|
||||
bool randomScale)
|
||||
{
|
||||
for (var i = 0; i < positions.Count; i++)
|
||||
{
|
||||
var pos = positions[i];
|
||||
// create the new instance.
|
||||
var newInstance = CreateNewInstance(normals[i], color, ao, scale, pad, randomScale);
|
||||
|
||||
// get the grass position of the new instance and add it to the
|
||||
// instance list
|
||||
var grassPosition = GetGrassPos(pos, instanceBounds);
|
||||
newInstance.Position = grassPosition;
|
||||
instanceList.Add(newInstance);
|
||||
}
|
||||
}
|
||||
|
||||
private rage__fwGrassInstanceListDef__InstanceData CreateNewInstance(Vector3 normal, Color color, int ao, int scale, Vector3 pad,
|
||||
bool randomScale = false)
|
||||
{
|
||||
//Vector3 pad = FloatUtil.ParseVector3String(PadTextBox.Text);
|
||||
//int scale = (int)ScaleNumericUpDown.Value;
|
||||
var rand = new Random();
|
||||
if (randomScale)
|
||||
scale = rand.Next(scale / 2, scale);
|
||||
var newInstance = new rage__fwGrassInstanceListDef__InstanceData
|
||||
{
|
||||
Ao = (byte)ao,
|
||||
Scale = (byte)scale,
|
||||
Color = new ArrayOfBytes3 { b0 = color.R, b1 = color.G, b2 = color.B },
|
||||
Pad = new ArrayOfBytes3 { b0 = (byte)pad.X, b1 = (byte)pad.Y, b2 = (byte)pad.Z },
|
||||
NormalX = (byte)((normal.X + 1) * 0.5F * 255F),
|
||||
NormalY = (byte)((normal.Y + 1) * 0.5F * 255F)
|
||||
};
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
private rage__fwGrassInstanceListDef RecalcBatch(BoundingBox newInstanceBounds, YmapGrassInstanceBatch batch)
|
||||
{
|
||||
batch.AABBMax = newInstanceBounds.Maximum;
|
||||
batch.AABBMin = newInstanceBounds.Minimum;
|
||||
batch.Position = newInstanceBounds.Center();
|
||||
batch.Radius = newInstanceBounds.Radius();
|
||||
var b = batch.Batch;
|
||||
b.BatchAABB = new rage__spdAABB
|
||||
{
|
||||
min =
|
||||
new Vector4(newInstanceBounds.Minimum,
|
||||
0), // Let's pass the new stuff into the batchabb as well just because.
|
||||
max = new Vector4(newInstanceBounds.Maximum, 0)
|
||||
};
|
||||
return b;
|
||||
}
|
||||
|
||||
private void GetSpawns(
|
||||
Vector3 origin, Func<Vector3,
|
||||
SpaceRayIntersectResult> spawnRayFunc,
|
||||
ICollection<Vector3> positions,
|
||||
ICollection<Vector3> normals,
|
||||
float radius,
|
||||
int resolution = 28)
|
||||
{
|
||||
var rand = new Random();
|
||||
for (var i = 0; i < resolution; i++)
|
||||
{
|
||||
var randX = (float)rand.NextDouble(-radius, radius);
|
||||
var randY = (float)rand.NextDouble(-radius, radius);
|
||||
if (Math.Abs(randX) > 0 && Math.Abs(randY) > 0)
|
||||
{
|
||||
randX *= .7071f;
|
||||
randY *= .7071f;
|
||||
}
|
||||
var posOffset = origin + new Vector3(randX, randY, 2f);
|
||||
var spaceRay = spawnRayFunc.Invoke(posOffset);
|
||||
if (!spaceRay.Hit) continue;
|
||||
// not truly O(n^2) but may be slow...
|
||||
// actually just did some testing, not slow at all.
|
||||
if (IsPointBlockedByInstance(spaceRay.Position)) continue;
|
||||
normals.Add(spaceRay.Normal);
|
||||
positions.Add(spaceRay.Position);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<rage__fwGrassInstanceListDef__InstanceData> RecalculateInstances(
|
||||
List<rage__fwGrassInstanceListDef__InstanceData> instances,
|
||||
BoundingBox oldInstanceBounds,
|
||||
BoundingBox newInstanceBounds)
|
||||
{
|
||||
var refreshList = new List<rage__fwGrassInstanceListDef__InstanceData>();
|
||||
foreach (var inst in instances)
|
||||
{
|
||||
// Copy instance
|
||||
var copy =
|
||||
new rage__fwGrassInstanceListDef__InstanceData
|
||||
{
|
||||
Position = inst.Position,
|
||||
Ao = inst.Ao,
|
||||
Color = inst.Color,
|
||||
NormalX = inst.NormalX,
|
||||
NormalY = inst.NormalY,
|
||||
Pad = inst.Pad,
|
||||
Scale = inst.Scale
|
||||
};
|
||||
// get the position from where we would be in the old bounds, and move it to
|
||||
// the position it needs to be in the new bounds.
|
||||
var oldPos = GetGrassWorldPos(copy.Position, oldInstanceBounds);
|
||||
//var oldPos = oldInstanceBounds.min + oldInstanceBounds.Size * (grassPos * BatchVertMultiplier);
|
||||
copy.Position = GetGrassPos(oldPos, newInstanceBounds);
|
||||
refreshList.Add(copy);
|
||||
}
|
||||
instances = refreshList.ToList();
|
||||
return instances;
|
||||
}
|
||||
|
||||
private static BoundingBox EncapsulatePositions(IEnumerable<Vector3> positions, BoundingBox bounds)
|
||||
{
|
||||
foreach (var pos in positions)
|
||||
{
|
||||
var posBounds = new BoundingBox(pos - (GrassMinMax + 0.1f), pos + (GrassMinMax + 0.1f));
|
||||
bounds = bounds.Encapsulate(posBounds);
|
||||
}
|
||||
return bounds;
|
||||
}
|
||||
|
||||
private static ArrayOfUshorts3 GetGrassPos(Vector3 worldPos, BoundingBox batchAABB)
|
||||
{
|
||||
var offset = worldPos - batchAABB.Minimum;
|
||||
var size = batchAABB.Size();
|
||||
var percentage =
|
||||
new Vector3(
|
||||
offset.X / size.X,
|
||||
offset.Y / size.Y,
|
||||
offset.Z / size.Z
|
||||
);
|
||||
var instancePos = percentage / BatchVertMultiplier;
|
||||
return new ArrayOfUshorts3
|
||||
{
|
||||
u0 = (ushort)instancePos.X,
|
||||
u1 = (ushort)instancePos.Y,
|
||||
u2 = (ushort)instancePos.Z
|
||||
};
|
||||
}
|
||||
|
||||
private static Vector3 GetGrassWorldPos(ArrayOfUshorts3 grassPos, BoundingBox batchAABB)
|
||||
{
|
||||
return batchAABB.Minimum + batchAABB.Size() * (grassPos.XYZ() * BatchVertMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
@@ -1548,7 +2114,6 @@ namespace CodeWalker.GameFiles
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
public class YmapTimeCycleModifier
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
public List<Vector3> Vertices { get; set; }
|
||||
public List<ushort> Indices { get; set; }
|
||||
public List<NavMeshAdjPoly> AdjPolys { get; set; }
|
||||
public List<YnvEdge> Edges { get; set; }
|
||||
public List<YnvPoly> Polys { get; set; }
|
||||
public List<YnvPortal> Portals { get; set; }
|
||||
public List<YnvPoint> Points { get; set; }
|
||||
@@ -48,6 +48,16 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
|
||||
|
||||
//getters for property grids viewing of the lists
|
||||
public Vector3[] AllVertices { get { return Vertices?.ToArray(); } }
|
||||
public ushort[] AllIndices { get { return Indices?.ToArray(); } }
|
||||
public YnvEdge[] AllEdges { get { return Edges?.ToArray(); } }
|
||||
public YnvPoly[] AllPolys { get { return Polys?.ToArray(); } }
|
||||
public YnvPortal[] AllPortals { get { return Portals?.ToArray(); } }
|
||||
public YnvPoint[] AllPoints { get { return Points?.ToArray(); } }
|
||||
|
||||
|
||||
|
||||
|
||||
public YnvFile() : base(null, GameFileType.Ynv)
|
||||
{
|
||||
@@ -101,9 +111,16 @@ namespace CodeWalker.GameFiles
|
||||
{
|
||||
Indices = Nav.Indices.GetFullList();
|
||||
}
|
||||
if (Nav.AdjPolys != null)
|
||||
if (Nav.Edges != null)
|
||||
{
|
||||
AdjPolys = Nav.AdjPolys.GetFullList();
|
||||
var edges = Nav.Edges.GetFullList();
|
||||
Edges = new List<YnvEdge>(edges.Count);
|
||||
for (int i = 0; i < edges.Count; i++)
|
||||
{
|
||||
YnvEdge edge = new YnvEdge();
|
||||
edge.Init(this, edges[i]);
|
||||
Edges.Add(edge);
|
||||
}
|
||||
}
|
||||
if (Nav.Polys != null)
|
||||
{
|
||||
@@ -114,16 +131,7 @@ namespace CodeWalker.GameFiles
|
||||
YnvPoly poly = new YnvPoly();
|
||||
poly.Init(this, polys[i]);
|
||||
poly.Index = i;
|
||||
poly.CalculatePosition(); //calc poly center for display purposes..
|
||||
Polys.Add(poly);
|
||||
|
||||
if (poly.PortalType > 0)
|
||||
{
|
||||
if (poly.PortalType != 2) //seems to be what portal links need to understand..
|
||||
{ }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (Nav.Portals != null)
|
||||
@@ -209,23 +217,97 @@ namespace CodeWalker.GameFiles
|
||||
Vector3 aabbsizeinv = 1.0f / aabbsize;
|
||||
|
||||
var vertlist = new List<NavMeshVertex>();
|
||||
if (Vertices != null)
|
||||
{
|
||||
for (int i = 0; i < Vertices.Count; i++)
|
||||
{
|
||||
vertlist.Add(NavMeshVertex.Create((Vertices[i] - posoffset) * aabbsizeinv));
|
||||
}
|
||||
}
|
||||
var indslist = new List<ushort>();
|
||||
var edgelist = new List<NavMeshEdge>();
|
||||
var polylist = new List<NavMeshPoly>();
|
||||
if (Polys != null)
|
||||
var portallist = new List<NavMeshPortal>();
|
||||
var portallinks = new List<ushort>();
|
||||
|
||||
var vertdict = new Dictionary<Vector3, ushort>();
|
||||
var areadict = new Dictionary<uint, uint>();
|
||||
var arealist = new List<uint>();
|
||||
var areaid = Nav.AreaID;
|
||||
EnsureEdgeAreaID(areaid, areadict, arealist);
|
||||
EnsureEdgeAreaID(0x3FFF, areadict, arealist);
|
||||
EnsureEdgeAreaID(areaid - 100, areadict, arealist);
|
||||
EnsureEdgeAreaID(areaid - 1, areadict, arealist);
|
||||
EnsureEdgeAreaID(areaid + 1, areadict, arealist);
|
||||
EnsureEdgeAreaID(areaid + 100, areadict, arealist);
|
||||
|
||||
|
||||
|
||||
if (Polys != null) //rebuild vertices, indices, edges and polys lists from poly data.
|
||||
{
|
||||
for (int i = 0; i < Polys.Count; i++)
|
||||
{
|
||||
Polys[i].Index = i;
|
||||
polylist.Add(Polys[i].RawData);
|
||||
var poly = Polys[i];
|
||||
var vc = poly.Vertices?.Length ?? 0;
|
||||
//poly.AreaID = (ushort)Nav.AreaID;
|
||||
poly._RawData.IndexID = (ushort)indslist.Count;
|
||||
for (int n = 0; n < vc; n++)
|
||||
{
|
||||
Vector3 v = poly.Vertices[n];
|
||||
YnvEdge e = ((poly.Edges != null) && (n < poly.Edges.Length)) ? poly.Edges[n] : null;
|
||||
ushort ind;
|
||||
if (!vertdict.TryGetValue(v, out ind))
|
||||
{
|
||||
ind = (ushort)vertlist.Count;
|
||||
vertdict[v] = ind;
|
||||
vertlist.Add(NavMeshVertex.Create(Vector3.Clamp((v - posoffset) * aabbsizeinv, Vector3.Zero, Vector3.One)));
|
||||
}
|
||||
if ((poly.Indices != null) && (n < poly.Indices.Length))
|
||||
{
|
||||
poly.Indices[n] = ind;
|
||||
}
|
||||
indslist.Add(ind);
|
||||
|
||||
NavMeshEdge edge;
|
||||
if (e != null)
|
||||
{
|
||||
if (e.Poly1 != null)
|
||||
{
|
||||
e.PolyID1 = (uint)e.Poly1.Index;
|
||||
e.AreaID1 = e.Poly1.AreaID;
|
||||
if (e.AreaID1 == 0x3FFF)
|
||||
{ }//debug
|
||||
}
|
||||
if (e.Poly2 != null)
|
||||
{
|
||||
e.PolyID2 = (uint)e.Poly2.Index;
|
||||
e.AreaID2 = e.Poly2.AreaID;
|
||||
if (e.AreaID2 == 0x3FFF)
|
||||
{ }//debug
|
||||
}
|
||||
if ((e.AreaID1 == 0) || (e.AreaID2 == 0))
|
||||
{ }//debug
|
||||
e._RawData._Poly1.AreaIDInd = EnsureEdgeAreaID(e.AreaID1, areadict, arealist);
|
||||
e._RawData._Poly2.AreaIDInd = EnsureEdgeAreaID(e.AreaID2, areadict, arealist);
|
||||
edge = e.RawData;
|
||||
}
|
||||
else
|
||||
{
|
||||
var areaind = EnsureEdgeAreaID(0x3FFF, areadict, arealist);
|
||||
edge = new NavMeshEdge();//create an empty edge
|
||||
edge._Poly1.PolyID = 0x3FFF;
|
||||
edge._Poly2.PolyID = 0x3FFF;
|
||||
edge._Poly1.AreaIDInd = areaind;
|
||||
edge._Poly2.AreaIDInd = areaind;
|
||||
}
|
||||
edgelist.Add(edge);
|
||||
}
|
||||
poly._RawData.IndexCount = vc;
|
||||
poly._RawData.PortalLinkID = (uint)portallinks.Count;//these shouldn't be directly editable!
|
||||
poly._RawData.PortalLinkCount = (byte)(poly.PortalLinks?.Length ?? 0);
|
||||
if (poly.PortalLinks != null)
|
||||
{
|
||||
portallinks.AddRange(poly.PortalLinks);
|
||||
}
|
||||
poly.Index = i;//this should be redundant...
|
||||
poly.CalculateAABB();//make sure this is up to date!
|
||||
polylist.Add(poly.RawData);
|
||||
}
|
||||
}
|
||||
var portallist = new List<NavMeshPortal>();
|
||||
|
||||
if (Portals != null)
|
||||
{
|
||||
for (int i = 0; i < Portals.Count; i++)
|
||||
@@ -238,7 +320,7 @@ namespace CodeWalker.GameFiles
|
||||
}
|
||||
}
|
||||
|
||||
if (Points != null)
|
||||
if (Points != null) //points will be built into the sector tree
|
||||
{
|
||||
for (int i = 0; i < Points.Count; i++)
|
||||
{
|
||||
@@ -259,10 +341,10 @@ namespace CodeWalker.GameFiles
|
||||
Nav.Indices = new NavMeshList<ushort>();
|
||||
Nav.Indices.VFT = 1080158424;
|
||||
}
|
||||
if (Nav.AdjPolys == null)
|
||||
if (Nav.Edges == null)
|
||||
{
|
||||
Nav.AdjPolys = new NavMeshList<NavMeshAdjPoly>();
|
||||
Nav.AdjPolys.VFT = 1080158440;
|
||||
Nav.Edges = new NavMeshList<NavMeshEdge>();
|
||||
Nav.Edges.VFT = 1080158440;
|
||||
}
|
||||
if (Nav.Polys == null)
|
||||
{
|
||||
@@ -272,16 +354,24 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
|
||||
Nav.Vertices.RebuildList(vertlist);
|
||||
Nav.VerticesCount = Nav.Vertices.ItemCount;
|
||||
|
||||
Nav.Indices.RebuildList(Indices);
|
||||
Nav.Indices.RebuildList(indslist);
|
||||
|
||||
Nav.AdjPolys.RebuildList(AdjPolys);
|
||||
Nav.Edges.RebuildList(edgelist);
|
||||
Nav.EdgesIndicesCount = Nav.Indices.ItemCount;
|
||||
|
||||
Nav.Polys.RebuildList(polylist);
|
||||
Nav.PolysCount = Nav.Polys.ItemCount;
|
||||
|
||||
Nav.Portals = (portallist.Count > 0) ? portallist.ToArray() : null;
|
||||
Nav.PortalsCount = (uint)(Nav.Portals?.Length ?? 0);
|
||||
//TODO: update portal links data.....
|
||||
Nav.PortalLinks = (portallinks.Count > 0) ? portallinks.ToArray() : null;
|
||||
Nav.PortalLinksCount = (uint)(Nav.PortalLinks?.Length ?? 0);
|
||||
|
||||
var adjAreaIds = new NavMeshUintArray();
|
||||
adjAreaIds.Set(arealist.ToArray());
|
||||
Nav.AdjAreaIDs = adjAreaIds;
|
||||
|
||||
|
||||
for (int i = 0; i < Nav.Polys.ListParts.Count; i++) //reassign part id's on all the polys...
|
||||
@@ -315,12 +405,27 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
}
|
||||
|
||||
private uint EnsureEdgeAreaID(uint areaid, Dictionary<uint, uint> areadict, List<uint> arealist)
|
||||
{
|
||||
uint ind;
|
||||
if (!areadict.TryGetValue(areaid, out ind))
|
||||
{
|
||||
ind = (uint)arealist.Count;
|
||||
areadict[areaid] = ind;
|
||||
arealist.Add(areaid);
|
||||
}
|
||||
return ind;
|
||||
}
|
||||
|
||||
|
||||
private void BuildSectorTree(NavMeshSector node, int depth, ref uint pointindex)
|
||||
{
|
||||
Vector3 min = node.AABBMin.XYZ();
|
||||
Vector3 max = node.AABBMax.XYZ();
|
||||
Vector3 cen = (min + max) * 0.5f;
|
||||
|
||||
//totbytes += (uint)node.BlockLength;
|
||||
|
||||
if (depth <= 0)
|
||||
{
|
||||
//go through polys and points and create new lists for this node
|
||||
@@ -329,6 +434,8 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
data.PointsStartID = pointindex;
|
||||
|
||||
//totbytes += (uint)data.BlockLength;
|
||||
|
||||
if (Polys != null)
|
||||
{
|
||||
List<ushort> polyids = new List<ushort>();
|
||||
@@ -345,6 +452,7 @@ namespace CodeWalker.GameFiles
|
||||
{
|
||||
data.PolyIDs = polyids.ToArray();
|
||||
}
|
||||
//totbytes += (uint)(polyids.Count * 2);
|
||||
}
|
||||
|
||||
if (Points != null)
|
||||
@@ -363,6 +471,7 @@ namespace CodeWalker.GameFiles
|
||||
data.Points = points.ToArray();
|
||||
pointindex += (uint)points.Count;
|
||||
}
|
||||
//totbytes += (uint)(points.Count * 8);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -419,6 +528,18 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
|
||||
|
||||
public void UpdateContentFlags(bool vehicle)
|
||||
{
|
||||
NavMeshFlags f = NavMeshFlags.None;
|
||||
//if (Nav.VerticesCount > 0) f = f | NavMeshFlags.Vertices;
|
||||
//if (Nav.PortalsCount > 0) f = f | NavMeshFlags.Portals;
|
||||
if (Polys?.Count > 0) f = f | NavMeshFlags.Vertices;
|
||||
if (Portals?.Count > 0) f = f | NavMeshFlags.Portals;
|
||||
if (vehicle) f = f | NavMeshFlags.Vehicle;
|
||||
else f = f | NavMeshFlags.Unknown8;
|
||||
Nav.ContentFlags = f;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void UpdateAllNodePositions()
|
||||
@@ -474,67 +595,39 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
//go through the nav mesh polys and generate verts to render...
|
||||
|
||||
if ((Vertices == null) || (Vertices.Count == 0)) return;
|
||||
if ((Indices == null) || (Indices.Count == 0)) return;
|
||||
if ((Polys == null) || (Polys.Count == 0)) return;
|
||||
|
||||
|
||||
int vc = Vertices.Count;
|
||||
|
||||
List<EditorVertex> rverts = new List<EditorVertex>();
|
||||
EditorVertex p0 = new EditorVertex();
|
||||
EditorVertex p1 = new EditorVertex();
|
||||
EditorVertex p2 = new EditorVertex();
|
||||
foreach (var ypoly in Polys)
|
||||
{
|
||||
var poly = ypoly.RawData;
|
||||
if ((ypoly.Vertices == null) || (ypoly.Vertices.Length < 3))
|
||||
{ continue; }
|
||||
|
||||
var colour = ypoly.GetColour();
|
||||
var colourval = (uint)colour.ToRgba();
|
||||
|
||||
var ic = poly.IndexCount;
|
||||
var startid = poly.IndexID;
|
||||
var endid = startid + ic;
|
||||
if (startid >= Indices.Count)
|
||||
{ continue; }
|
||||
if (endid > Indices.Count)
|
||||
{ continue; }
|
||||
|
||||
|
||||
if(ic<3)
|
||||
{ continue; }//not enough verts to make a triangle...
|
||||
|
||||
if (ic > 15)
|
||||
{ }
|
||||
|
||||
|
||||
EditorVertex p0 = new EditorVertex();
|
||||
EditorVertex p1 = new EditorVertex();
|
||||
EditorVertex p2 = new EditorVertex();
|
||||
p0.Colour = colourval;
|
||||
p1.Colour = colourval;
|
||||
p2.Colour = colourval;
|
||||
|
||||
var startind = Indices[startid];
|
||||
if (startind >= vc)
|
||||
{ continue; }
|
||||
|
||||
p0.Position = Vertices[startind];
|
||||
p0.Position = ypoly.Vertices[0];
|
||||
|
||||
//build triangles for the poly.
|
||||
int tricount = ic - 2;
|
||||
int tricount = ypoly.Vertices.Length - 2;
|
||||
for (int t = 0; t < tricount; t++)
|
||||
{
|
||||
int tid = startid + t;
|
||||
int ind1 = Indices[tid + 1];
|
||||
int ind2 = Indices[tid + 2];
|
||||
if ((ind1 >= vc) || (ind2 >= vc))
|
||||
{ continue; }
|
||||
|
||||
p1.Position = Vertices[ind1];
|
||||
p2.Position = Vertices[ind2];
|
||||
|
||||
p1.Position = ypoly.Vertices[t + 1];
|
||||
p2.Position = ypoly.Vertices[t + 2];
|
||||
rverts.Add(p0);
|
||||
rverts.Add(p1);
|
||||
rverts.Add(p2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TriangleVerts = rverts.ToArray();
|
||||
@@ -572,14 +665,14 @@ namespace CodeWalker.GameFiles
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))] public class YnvPoly
|
||||
{
|
||||
public NavMeshPoly _RawData;
|
||||
public NavMeshPoly RawData { get { return _RawData; } set { _RawData = value; } }
|
||||
|
||||
public YnvFile Ynv { get; set; }
|
||||
public NavMeshPoly RawData { get { return _RawData; } set { _RawData = value; } }
|
||||
|
||||
public ushort AreaID { get { return _RawData.AreaID; } set { _RawData.AreaID = value; } }
|
||||
public ushort PartID { get { return _RawData.PartID; } set { _RawData.PartID = value; } }
|
||||
public ushort PortalLinkID { get { return _RawData.PortalLinkID; } set { _RawData.PortalLinkID = value; } }
|
||||
public byte PortalType { get { return _RawData.PortalType; } set { _RawData.PortalType = value; } }
|
||||
public uint PortalLinkID { get { return _RawData.PortalLinkID; } set { _RawData.PortalLinkID = value; } }
|
||||
public byte PortalLinkCount { get { return _RawData.PortalLinkCount; } set { _RawData.PortalLinkCount = value; } }
|
||||
public byte Flags1 { get { return (byte)(_RawData.Unknown_00h & 0xFF); } set { _RawData.Unknown_00h = (ushort)((_RawData.Unknown_00h & 0xFF00) | (value & 0xFF)); } }
|
||||
public byte Flags2 { get { return (byte)((_RawData.Unknown_24h.Value >> 0) & 0xFF); } set { _RawData.Unknown_24h = ((_RawData.Unknown_24h.Value & 0xFFFFFF00u) | ((value & 0xFFu) << 0)); } }
|
||||
public byte Flags3 { get { return (byte)((_RawData.Unknown_24h.Value >> 9) & 0xFF); } set { _RawData.Unknown_24h = ((_RawData.Unknown_24h.Value & 0xFFFE01FFu) | ((value & 0xFFu) << 9)); } }
|
||||
@@ -624,14 +717,84 @@ namespace CodeWalker.GameFiles
|
||||
public Vector3 Position { get; set; }
|
||||
public int Index { get; set; }
|
||||
|
||||
public ushort[] Indices { get; set; }
|
||||
public Vector3[] Vertices { get; set; }
|
||||
public YnvEdge[] Edges { get; set; }
|
||||
public ushort[] PortalLinks { get; set; }
|
||||
|
||||
|
||||
public void Init(YnvFile ynv, NavMeshPoly poly)
|
||||
{
|
||||
Ynv = ynv;
|
||||
RawData = poly;
|
||||
|
||||
LoadIndices();
|
||||
LoadPortalLinks();
|
||||
CalculatePosition(); //calc poly center for display purposes..
|
||||
}
|
||||
|
||||
|
||||
public void LoadIndices()
|
||||
{
|
||||
//load indices, vertices and edges
|
||||
var indices = Ynv.Indices;
|
||||
var vertices = Ynv.Vertices;
|
||||
var edges = Ynv.Edges;
|
||||
if ((indices == null) || (vertices == null) || (edges == null))
|
||||
{ return; }
|
||||
var vc = vertices.Count;
|
||||
var ic = _RawData.IndexCount;
|
||||
var startid = _RawData.IndexID;
|
||||
var endid = startid + ic;
|
||||
if (startid >= indices.Count)
|
||||
{ return; }
|
||||
if (endid > indices.Count)
|
||||
{ return; }
|
||||
if (endid > edges.Count)
|
||||
{ return; }
|
||||
|
||||
Indices = new ushort[ic];
|
||||
Vertices = new Vector3[ic];
|
||||
Edges = new YnvEdge[ic];
|
||||
|
||||
int i = 0;
|
||||
for (int id = startid; id < endid; id++)
|
||||
{
|
||||
var ind = indices[id];
|
||||
|
||||
Indices[i] = ind;
|
||||
Vertices[i] = (ind < vc) ? vertices[ind] : Vector3.Zero;
|
||||
Edges[i] = edges[id];
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadPortalLinks()
|
||||
{
|
||||
if (PortalLinkCount == 0)
|
||||
{ return; }
|
||||
var links = Ynv.Nav?.PortalLinks;
|
||||
if (links == null)
|
||||
{ return; }
|
||||
|
||||
var ll = links.Length;
|
||||
|
||||
PortalLinks = new ushort[PortalLinkCount];
|
||||
|
||||
int offset = (int)PortalLinkID;
|
||||
for (int i = 0; i < PortalLinkCount; i++)
|
||||
{
|
||||
int idx = offset + i;
|
||||
PortalLinks[i] = (idx < ll) ? links[idx] : (ushort)0;
|
||||
}
|
||||
|
||||
if (PortalLinkCount != 2)
|
||||
{ }//debug
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void SetPosition(Vector3 pos)
|
||||
{
|
||||
Vector3 delta = pos - Position;
|
||||
@@ -687,7 +850,7 @@ namespace CodeWalker.GameFiles
|
||||
//if ((u5 & 8388608) > 0) colour.Red += 1.0f; //slope facing -X,-Y (southwest)
|
||||
//if (u5 >= 16777216) { } //other bits unused
|
||||
|
||||
var u1 = _RawData.PortalType;
|
||||
var u1 = _RawData.PortalLinkCount;
|
||||
//if ((u1 & 1) > 0) colour.Red += 1.0f; //portal - don't interact?
|
||||
//if ((u1 & 2) > 0) colour.Green += 1.0f; //portal - ladder/fence interaction?
|
||||
//if ((u1 & 4) > 0) colour.Blue += 1.0f; //portal - fence interaction / go away from?
|
||||
@@ -709,32 +872,36 @@ namespace CodeWalker.GameFiles
|
||||
public void CalculatePosition()
|
||||
{
|
||||
//calc poly center for display purposes.
|
||||
var indices = Ynv.Indices;
|
||||
var vertices = Ynv.Vertices;
|
||||
if ((indices == null) || (vertices == null))
|
||||
{ return; }
|
||||
var vc = vertices.Count;
|
||||
var ic = _RawData.IndexCount;
|
||||
var startid = _RawData.IndexID;
|
||||
var endid = startid + ic;
|
||||
if (startid >= indices.Count)
|
||||
{ return; }
|
||||
if (endid > indices.Count)
|
||||
{ return; }
|
||||
Vector3 pcenter = Vector3.Zero;
|
||||
float pcount = 0.0f;
|
||||
for (int id = startid; id < endid; id++)
|
||||
if (Vertices != null)
|
||||
{
|
||||
var ind = indices[id];
|
||||
if (ind >= vc)
|
||||
{ continue; }
|
||||
|
||||
pcenter += vertices[ind];
|
||||
pcount += 1.0f;
|
||||
for (int i = 0; i < Vertices.Length; i++)
|
||||
{
|
||||
pcenter += Vertices[i];
|
||||
}
|
||||
}
|
||||
Position = pcenter * (1.0f / pcount);
|
||||
float c = ((float)Vertices?.Length);
|
||||
if (c == 0.0f) c = 1.0f;
|
||||
Position = pcenter * (1.0f / c);
|
||||
}
|
||||
|
||||
public void CalculateAABB()
|
||||
{
|
||||
Vector3 min = Vector3.Zero;
|
||||
Vector3 max = Vector3.Zero;
|
||||
if ((Vertices != null) && (Vertices.Length > 0))
|
||||
{
|
||||
min = new Vector3(float.MaxValue);
|
||||
max = new Vector3(float.MinValue);
|
||||
for (int i = 0; i < Vertices.Length; i++)
|
||||
{
|
||||
min = Vector3.Min(min, Vertices[i]);
|
||||
max = Vector3.Max(max, Vertices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
_RawData.CellAABB = new NavMeshAABB() { Min = min, Max = max };
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
@@ -870,4 +1037,57 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))] public class YnvEdge
|
||||
{
|
||||
public NavMeshEdge _RawData;
|
||||
public NavMeshEdge RawData { get { return _RawData; } set { _RawData = value; } }
|
||||
public YnvFile Ynv { get; set; }
|
||||
|
||||
|
||||
public uint AreaID1 { get; set; }
|
||||
public uint AreaID2 { get; set; }
|
||||
public uint PolyID1 { get { return _RawData._Poly1.PolyID; } set { _RawData._Poly1.PolyID = value; } }
|
||||
public uint PolyID2 { get { return _RawData._Poly2.PolyID; } set { _RawData._Poly2.PolyID = value; } }
|
||||
public YnvPoly Poly1 { get; set; }
|
||||
public YnvPoly Poly2 { get; set; }
|
||||
|
||||
|
||||
public YnvEdge() { }
|
||||
public YnvEdge(YnvEdge copy, YnvPoly poly)
|
||||
{
|
||||
_RawData = copy._RawData;
|
||||
_RawData._Poly1.PolyID = 0x3FFF;
|
||||
_RawData._Poly2.PolyID = 0x3FFF;
|
||||
Poly1 = poly;
|
||||
Poly2 = poly;
|
||||
AreaID1 = 0x3FFF;
|
||||
AreaID2 = 0x3FFF;
|
||||
}
|
||||
|
||||
public void Init(YnvFile ynv, NavMeshEdge edge)
|
||||
{
|
||||
Ynv = ynv;
|
||||
RawData = edge;
|
||||
|
||||
if (ynv.Nav == null) return;
|
||||
var n = ynv.Nav;
|
||||
|
||||
var ai1 = edge.Poly1.AreaIDInd;
|
||||
var ai2 = edge.Poly2.AreaIDInd;
|
||||
|
||||
AreaID1 = (ai1 < n.AdjAreaIDs.Count) ? n.AdjAreaIDs.Get(ai1) : 16383;
|
||||
AreaID2 = (ai2 < n.AdjAreaIDs.Count) ? n.AdjAreaIDs.Get(ai2) : 16383;
|
||||
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return AreaID1.ToString() + ", " + AreaID2.ToString() + ", " + PolyID1.ToString() + ", " + PolyID2.ToString() + ", " +
|
||||
_RawData._Poly1.Unk2.ToString() + ", " + _RawData._Poly2.Unk2.ToString() + ", " +
|
||||
_RawData._Poly1.Unk3.ToString() + ", " + _RawData._Poly2.Unk3.ToString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ namespace CodeWalker.GameFiles
|
||||
public uint NameHash { get; set; }
|
||||
public string[] Strings { get; set; }
|
||||
|
||||
|
||||
public CMapTypes CMapTypes { get; set; }
|
||||
public CMapTypes _CMapTypes;
|
||||
public CMapTypes CMapTypes { get { return _CMapTypes; } set { _CMapTypes = value; } }
|
||||
|
||||
public Archetype[] AllArchetypes { get; set; }
|
||||
|
||||
@@ -50,6 +50,111 @@ namespace CodeWalker.GameFiles
|
||||
return (RpfFileEntry != null) ? RpfFileEntry.Name : string.Empty;
|
||||
}
|
||||
|
||||
public byte[] Save()
|
||||
{
|
||||
MetaBuilder mb = new MetaBuilder();
|
||||
|
||||
var mdb = mb.EnsureBlock(MetaName.CMapTypes);
|
||||
|
||||
CMapTypes mapTypes = _CMapTypes;
|
||||
|
||||
if (Extensions == null || Extensions.Length <= 0)
|
||||
mapTypes.extensions = new Array_StructurePointer();
|
||||
else
|
||||
mapTypes.extensions = mb.AddWrapperArrayPtr(Extensions);
|
||||
|
||||
if ((AllArchetypes != null) && (AllArchetypes.Length > 0))
|
||||
{
|
||||
for (int i = 0; i < AllArchetypes.Length; i++)
|
||||
{
|
||||
var arch = AllArchetypes[i]; //save the extensions first..
|
||||
if (arch._BaseArchetypeDef.extensions.Count1 > 0)
|
||||
{
|
||||
arch._BaseArchetypeDef.extensions = mb.AddWrapperArrayPtr(arch.Extensions);
|
||||
}
|
||||
}
|
||||
|
||||
MetaPOINTER[] ptrs = new MetaPOINTER[AllArchetypes.Length];
|
||||
for (int i = 0; i < AllArchetypes.Length; i++)
|
||||
{
|
||||
var arch = AllArchetypes[i];
|
||||
switch (arch)
|
||||
{
|
||||
case TimeArchetype t:
|
||||
ptrs[i] = mb.AddItemPtr(MetaName.CTimeArchetypeDef, t._TimeArchetypeDef);
|
||||
break;
|
||||
case MloArchetype m:
|
||||
try
|
||||
{
|
||||
m._MloArchetypeDef._MloArchetypeDef.entities = mb.AddWrapperArrayPtr(m.entities);
|
||||
m._MloArchetypeDef._MloArchetypeDef.rooms = mb.AddWrapperArray(m.rooms);
|
||||
m._MloArchetypeDef._MloArchetypeDef.portals = mb.AddWrapperArray(m.portals);
|
||||
m._MloArchetypeDef._MloArchetypeDef.entitySets = mb.AddWrapperArray(m.entitySets);
|
||||
m._MloArchetypeDef._MloArchetypeDef.timeCycleModifiers = mb.AddItemArrayPtr(MetaName.CTimeCycleModifier, m.timeCycleModifiers);
|
||||
}
|
||||
catch/* (Exception e)*/
|
||||
{
|
||||
//todo: log save error.
|
||||
}
|
||||
ptrs[i] = mb.AddItemPtr(MetaName.CMloArchetypeDef, m._MloArchetypeDef);
|
||||
break;
|
||||
case Archetype a:
|
||||
ptrs[i] = mb.AddItemPtr(MetaName.CBaseArchetypeDef, a._BaseArchetypeDef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mapTypes.archetypes = mb.AddPointerArray(ptrs);
|
||||
}
|
||||
else
|
||||
{
|
||||
mapTypes.archetypes = new Array_StructurePointer();
|
||||
}
|
||||
|
||||
if (CompositeEntityTypes != null && CompositeEntityTypes.Length > 0)
|
||||
{
|
||||
var cptrs = new MetaPOINTER[CompositeEntityTypes.Length];
|
||||
|
||||
for (int i = 0; i < cptrs.Length; i++)
|
||||
{
|
||||
var cet = CompositeEntityTypes[i];
|
||||
cptrs[i] = mb.AddItemPtr(MetaName.CCompositeEntityType, cet);
|
||||
}
|
||||
mapTypes.compositeEntityTypes = mb.AddItemArrayPtr(MetaName.CCompositeEntityType, cptrs);
|
||||
}
|
||||
|
||||
mapTypes.name = NameHash;
|
||||
mapTypes.dependencies = new Array_uint(); // is this never used? possibly a todo?
|
||||
|
||||
mb.AddStructureInfo(MetaName.CMapTypes);
|
||||
mb.AddStructureInfo(MetaName.CBaseArchetypeDef);
|
||||
mb.AddStructureInfo(MetaName.CMloArchetypeDef);
|
||||
mb.AddStructureInfo(MetaName.CTimeArchetypeDef);
|
||||
mb.AddStructureInfo(MetaName.CCompositeEntityType);
|
||||
mb.AddStructureInfo(MetaName.CMloRoomDef);
|
||||
mb.AddStructureInfo(MetaName.CMloPortalDef);
|
||||
mb.AddStructureInfo(MetaName.CMloEntitySet);
|
||||
|
||||
if ((AllArchetypes != null && AllArchetypes.Length > 0))
|
||||
{
|
||||
mb.AddEnumInfo((MetaName)1991964615); // ASSET_TYPE_
|
||||
}
|
||||
|
||||
if ((AllArchetypes != null) && (AllArchetypes.Any(x => x is MloArchetype m && m.entities.Length > 0)))
|
||||
{
|
||||
mb.AddStructureInfo(MetaName.CEntityDef);
|
||||
mb.AddEnumInfo((MetaName)1264241711); //LODTYPES_
|
||||
mb.AddEnumInfo((MetaName)648413703); //PRI_
|
||||
}
|
||||
|
||||
mb.AddItem(MetaName.CMapTypes, mapTypes);
|
||||
|
||||
Meta meta = mb.GetMeta();
|
||||
byte[] data = ResourceBuilder.Build(meta, 2);
|
||||
|
||||
HasChanged = false;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public void Load(byte[] data)
|
||||
{
|
||||
@@ -94,12 +199,12 @@ namespace CodeWalker.GameFiles
|
||||
Meta = rd.ReadBlock<Meta>();
|
||||
|
||||
|
||||
CMapTypes = MetaTypes.GetTypedData<CMapTypes>(Meta, MetaName.CMapTypes);
|
||||
_CMapTypes = MetaTypes.GetTypedData<CMapTypes>(Meta, MetaName.CMapTypes);
|
||||
|
||||
|
||||
List<Archetype> allarchs = new List<Archetype>();
|
||||
|
||||
var ptrs = MetaTypes.GetPointerArray(Meta, CMapTypes.archetypes);
|
||||
var ptrs = MetaTypes.GetPointerArray(Meta, _CMapTypes.archetypes);
|
||||
if (ptrs != null)
|
||||
{
|
||||
for (int i = 0; i < ptrs.Length; i++)
|
||||
@@ -151,7 +256,8 @@ namespace CodeWalker.GameFiles
|
||||
AllArchetypes = allarchs.ToArray();
|
||||
|
||||
|
||||
Extensions = MetaTypes.GetExtensions(Meta, CMapTypes.extensions);
|
||||
Extensions = MetaTypes.GetExtensions(Meta, _CMapTypes.extensions);
|
||||
|
||||
if (Extensions != null)
|
||||
{ }
|
||||
|
||||
@@ -163,11 +269,11 @@ namespace CodeWalker.GameFiles
|
||||
//CEntityDefs = MetaTypes.GetTypedDataArray<CEntityDef>(Meta, MetaName.CEntityDef);
|
||||
|
||||
|
||||
CompositeEntityTypes = MetaTypes.ConvertDataArray<CCompositeEntityType>(Meta, MetaName.CCompositeEntityType, CMapTypes.compositeEntityTypes);
|
||||
CompositeEntityTypes = MetaTypes.ConvertDataArray<CCompositeEntityType>(Meta, MetaName.CCompositeEntityType, _CMapTypes.compositeEntityTypes);
|
||||
if (CompositeEntityTypes != null)
|
||||
{ }
|
||||
|
||||
NameHash = CMapTypes.name;
|
||||
NameHash = _CMapTypes.name;
|
||||
if (NameHash == 0)
|
||||
{
|
||||
int ind = entry.NameLower.LastIndexOf('.');
|
||||
@@ -242,7 +348,38 @@ namespace CodeWalker.GameFiles
|
||||
}
|
||||
|
||||
|
||||
public void AddArchetype(Archetype archetype)
|
||||
{
|
||||
if (AllArchetypes == null)
|
||||
AllArchetypes = new Archetype[0];
|
||||
|
||||
List<Archetype> archetypes = AllArchetypes.ToList();
|
||||
archetype.Ytyp = this;
|
||||
archetypes.Add(archetype);
|
||||
AllArchetypes = archetypes.ToArray();
|
||||
}
|
||||
|
||||
public Archetype AddArchetype()
|
||||
{
|
||||
var a = new Archetype();
|
||||
a._BaseArchetypeDef.assetType = Unk_1991964615.ASSET_TYPE_DRAWABLE;
|
||||
a._BaseArchetypeDef.lodDist = 60;
|
||||
a._BaseArchetypeDef.hdTextureDist = 15;
|
||||
a.Ytyp = this;
|
||||
AddArchetype(a);
|
||||
return a;
|
||||
}
|
||||
|
||||
public bool RemoveArchetype(Archetype archetype)
|
||||
{
|
||||
List<Archetype> archetypes = AllArchetypes.ToList();
|
||||
if (archetypes.Remove(archetype))
|
||||
{
|
||||
AllArchetypes = archetypes.ToArray();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1805,116 +1805,6 @@ namespace CodeWalker.GameFiles
|
||||
}
|
||||
|
||||
|
||||
public void InitYmapEntityArchetypes(YmapFile file)
|
||||
{
|
||||
if (file == null) return;
|
||||
if (file.AllEntities != null)
|
||||
{
|
||||
for (int i = 0; i < file.AllEntities.Length; i++)
|
||||
{
|
||||
var ent = file.AllEntities[i];
|
||||
var arch = GetArchetype(ent.CEntityDef.archetypeName);
|
||||
ent.SetArchetype(arch);
|
||||
|
||||
if (ent.MloInstance != null)
|
||||
{
|
||||
var entities = ent.MloInstance.Entities;
|
||||
if (entities != null)
|
||||
{
|
||||
for (int j = 0; j < entities.Length; j++)
|
||||
{
|
||||
var ient = entities[j];
|
||||
var iarch = GetArchetype(ient.CEntityDef.archetypeName);
|
||||
ient.SetArchetype(iarch);
|
||||
if (iarch == null)
|
||||
{ } //can't find archetype - des stuff eg {des_prologue_door}
|
||||
}
|
||||
|
||||
|
||||
//update archetype room AABB's.. bad to have this here? where else to put it?
|
||||
var mloa = arch as MloArchetype;
|
||||
if (mloa != null)
|
||||
{
|
||||
Vector3 mlobbmin = Vector3.Zero;
|
||||
Vector3 mlobbmax = Vector3.Zero;
|
||||
Vector3[] c = new Vector3[8];
|
||||
var rooms = mloa.rooms;
|
||||
if (rooms != null)
|
||||
{
|
||||
for (int j = 0; j < rooms.Length; j++)
|
||||
{
|
||||
var room = rooms[j];
|
||||
if ((room.AttachedObjects == null) || (room.AttachedObjects.Length == 0)) continue;
|
||||
Vector3 min = new Vector3(float.MaxValue);
|
||||
Vector3 max = new Vector3(float.MinValue);
|
||||
for (int k = 0; k < room.AttachedObjects.Length; k++)
|
||||
{
|
||||
var objid = room.AttachedObjects[k];
|
||||
if (objid < entities.Length)
|
||||
{
|
||||
var rooment = entities[objid];
|
||||
if ((rooment != null) && (rooment.Archetype != null))
|
||||
{
|
||||
var earch = rooment.Archetype;
|
||||
var pos = rooment._CEntityDef.position;
|
||||
var ori = rooment.Orientation;
|
||||
Vector3 abmin = earch.BBMin * rooment.Scale; //entity box
|
||||
Vector3 abmax = earch.BBMax * rooment.Scale;
|
||||
c[0] = abmin;
|
||||
c[1] = new Vector3(abmin.X, abmin.Y, abmax.Z);
|
||||
c[2] = new Vector3(abmin.X, abmax.Y, abmin.Z);
|
||||
c[3] = new Vector3(abmin.X, abmax.Y, abmax.Z);
|
||||
c[4] = new Vector3(abmax.X, abmin.Y, abmin.Z);
|
||||
c[5] = new Vector3(abmax.X, abmin.Y, abmax.Z);
|
||||
c[6] = new Vector3(abmax.X, abmax.Y, abmin.Z);
|
||||
c[7] = abmax;
|
||||
for (int n = 0; n < 8; n++)
|
||||
{
|
||||
Vector3 corn = ori.Multiply(c[n]) + pos;
|
||||
min = Vector3.Min(min, corn);
|
||||
max = Vector3.Max(max, corn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
room.BBMin_CW = min;
|
||||
room.BBMax_CW = max;
|
||||
mlobbmin = Vector3.Min(mlobbmin, min);
|
||||
mlobbmax = Vector3.Max(mlobbmax, max);
|
||||
}
|
||||
}
|
||||
mloa.BBMin = mlobbmin;
|
||||
mloa.BBMax = mlobbmax;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
if (file.GrassInstanceBatches != null)
|
||||
{
|
||||
for (int i = 0; i < file.GrassInstanceBatches.Length; i++)
|
||||
{
|
||||
var batch = file.GrassInstanceBatches[i];
|
||||
batch.Archetype = GetArchetype(batch.Batch.archetypeName);
|
||||
}
|
||||
}
|
||||
|
||||
if (file.TimeCycleModifiers != null)
|
||||
{
|
||||
for (int i = 0; i < file.TimeCycleModifiers.Length; i++)
|
||||
{
|
||||
var tcm = file.TimeCycleModifiers[i];
|
||||
World.TimecycleMod wtcm;
|
||||
if (TimeCycleModsDict.TryGetValue(tcm.CTimeCycleModifier.name.Hash, out wtcm))
|
||||
{
|
||||
tcm.TimeCycleModData = wtcm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1956,8 +1846,9 @@ namespace CodeWalker.GameFiles
|
||||
if (req.Loaded) AddTextureLookups(req as YtdFile);
|
||||
break;
|
||||
case GameFileType.Ymap:
|
||||
req.Loaded = LoadFile(req as YmapFile);
|
||||
if (req.Loaded) InitYmapEntityArchetypes(req as YmapFile);
|
||||
YmapFile y = req as YmapFile;
|
||||
req.Loaded = LoadFile(y);
|
||||
if (req.Loaded) y.InitYmapEntityArchetypes(this);
|
||||
break;
|
||||
case GameFileType.Yft:
|
||||
req.Loaded = LoadFile(req as YftFile);
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace CodeWalker.GameFiles
|
||||
public virtual MetaName Type => MetaName.CBaseArchetypeDef;
|
||||
|
||||
public CBaseArchetypeDef _BaseArchetypeDef;
|
||||
public CBaseArchetypeDef BaseArchetypeDef { get { return _BaseArchetypeDef; } set { _BaseArchetypeDef = value; } }
|
||||
public CBaseArchetypeDef BaseArchetypeDef => _BaseArchetypeDef; // for browsing.
|
||||
|
||||
public MetaHash Hash { get; set; }
|
||||
public YtypFile Ytyp { get; set; }
|
||||
@@ -31,16 +31,16 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
|
||||
|
||||
public string Name
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
get
|
||||
{
|
||||
return _BaseArchetypeDef.name.ToString();
|
||||
}
|
||||
}
|
||||
public string AssetName
|
||||
public string AssetName
|
||||
{
|
||||
get
|
||||
get
|
||||
{
|
||||
return _BaseArchetypeDef.assetName.ToString();
|
||||
}
|
||||
@@ -49,7 +49,7 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
protected void InitVars(ref CBaseArchetypeDef arch)
|
||||
{
|
||||
BaseArchetypeDef = arch;
|
||||
_BaseArchetypeDef = arch;
|
||||
Hash = arch.assetName;
|
||||
if (Hash.Hash == 0) Hash = arch.name;
|
||||
DrawableDict = arch.drawableDictionary;
|
||||
@@ -83,10 +83,8 @@ namespace CodeWalker.GameFiles
|
||||
public class TimeArchetype : Archetype
|
||||
{
|
||||
public override MetaName Type => MetaName.CTimeArchetypeDef;
|
||||
|
||||
public CTimeArchetypeDefData _TimeArchetypeDef;
|
||||
public CTimeArchetypeDefData TimeArchetypeDef { get { return _TimeArchetypeDef; } set { _TimeArchetypeDef = value; } }
|
||||
|
||||
public CTimeArchetypeDef _TimeArchetypeDef;
|
||||
public CTimeArchetypeDef TimeArchetypeDef => _TimeArchetypeDef; // for browsing.
|
||||
|
||||
public uint TimeFlags { get; set; }
|
||||
public bool[] ActiveHours { get; set; }
|
||||
@@ -98,9 +96,9 @@ namespace CodeWalker.GameFiles
|
||||
{
|
||||
Ytyp = ytyp;
|
||||
InitVars(ref arch._BaseArchetypeDef);
|
||||
TimeArchetypeDef = arch.TimeArchetypeDef;
|
||||
_TimeArchetypeDef = arch;
|
||||
|
||||
TimeFlags = _TimeArchetypeDef.timeFlags;
|
||||
TimeFlags = arch.TimeArchetypeDef.timeFlags;
|
||||
ActiveHours = new bool[24];
|
||||
ActiveHoursText = new string[24];
|
||||
for (int i = 0; i < 24; i++)
|
||||
@@ -128,8 +126,10 @@ namespace CodeWalker.GameFiles
|
||||
{
|
||||
public override MetaName Type => MetaName.CMloArchetypeDef;
|
||||
|
||||
public CMloArchetypeDefData _MloArchetypeDef;
|
||||
public CMloArchetypeDefData MloArchetypeDef { get { return _MloArchetypeDef; } set { _MloArchetypeDef = value; } }
|
||||
|
||||
public CMloArchetypeDef MloArchetypeDef => _MloArchetypeDef; // for browsing.
|
||||
public CMloArchetypeDef _MloArchetypeDef;
|
||||
public CMloArchetypeDefData _MloArchetypeDefData;
|
||||
|
||||
public MCEntityDef[] entities { get; set; }
|
||||
public MCMloRoomDef[] rooms { get; set; }
|
||||
@@ -137,36 +137,147 @@ namespace CodeWalker.GameFiles
|
||||
public MCMloEntitySet[] entitySets { get; set; }
|
||||
public CMloTimeCycleModifier[] timeCycleModifiers { get; set; }
|
||||
|
||||
|
||||
public void Init(YtypFile ytyp, ref CMloArchetypeDef arch)
|
||||
{
|
||||
Ytyp = ytyp;
|
||||
InitVars(ref arch._BaseArchetypeDef);
|
||||
MloArchetypeDef = arch.MloArchetypeDef;
|
||||
_MloArchetypeDef = arch;
|
||||
_MloArchetypeDefData = arch.MloArchetypeDef;
|
||||
}
|
||||
|
||||
public bool AddEntity(YmapEntityDef ent, int roomIndex)
|
||||
{
|
||||
if (ent == null) return false;
|
||||
|
||||
// entity already exists in our array. so we'll just add
|
||||
// it to the instanced entities list and continue.
|
||||
MloInstanceData mloInstance = ent.MloParent?.MloInstance;
|
||||
MCEntityDef ymcent = mloInstance?.TryGetArchetypeEntity(ent);
|
||||
if (ymcent != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (roomIndex > rooms.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Room index {roomIndex} exceeds the amount of rooms in {Name}.");
|
||||
}
|
||||
|
||||
var mcEntityDef = new MCEntityDef(ref ent._CEntityDef, this);
|
||||
|
||||
// Add the new entity def to the entities list.
|
||||
AddEntity(ent, mcEntityDef);
|
||||
|
||||
// Update the attached objects in the room index specified.
|
||||
AttachEntityToRoom(ent, roomIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
// attaches the specified ymap entity index to the room at roomIndex.
|
||||
private void AttachEntityToRoom(YmapEntityDef ent, int roomIndex)
|
||||
{
|
||||
if (roomIndex > rooms.Length)
|
||||
{
|
||||
return; // the room index is bigger than the rooms we have...
|
||||
}
|
||||
var attachedObjs = rooms[roomIndex].AttachedObjects?.ToList() ?? new List<uint>();
|
||||
attachedObjs.Add((uint)ent.Index);
|
||||
rooms[roomIndex].AttachedObjects = attachedObjs.ToArray();
|
||||
}
|
||||
|
||||
// Adds an entity to the entities array and then set's the index of the
|
||||
// ymap entity to the index of the new MCEntityDef.
|
||||
private void AddEntity(YmapEntityDef ent, MCEntityDef mcEntityDef)
|
||||
{
|
||||
if (ent == null || mcEntityDef == null) return; // no entity?...
|
||||
// initialize the array.
|
||||
if (entities == null) entities = new MCEntityDef[0];
|
||||
|
||||
List<MCEntityDef> entList = entities.ToList();
|
||||
entList.Add(mcEntityDef);
|
||||
ent.Index = entList.IndexOf(mcEntityDef);
|
||||
entities = entList.ToArray();
|
||||
}
|
||||
|
||||
public bool RemoveEntity(YmapEntityDef ent)
|
||||
{
|
||||
if (ent.Index >= entities.Length) return false;
|
||||
|
||||
MCEntityDef delent = entities[ent.Index];
|
||||
MloInstanceData inst = ent.MloParent?.MloInstance;
|
||||
if (inst == null) return false;
|
||||
|
||||
if (delent != null)
|
||||
{
|
||||
MCEntityDef[] newentities = new MCEntityDef[entities.Length - 1];
|
||||
bool didDel = false;
|
||||
int index = 0;
|
||||
int delIndex = 0;
|
||||
for (int i = 0; i < entities.Length; i++)
|
||||
{
|
||||
if (entities[i] == delent)
|
||||
{
|
||||
delIndex = i;
|
||||
didDel = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
newentities[index] = entities[i];
|
||||
YmapEntityDef ymapEntityDef = inst.TryGetYmapEntity(newentities[index]);
|
||||
if (ymapEntityDef != null) ymapEntityDef.Index = index;
|
||||
index++;
|
||||
}
|
||||
entities = newentities;
|
||||
|
||||
if (didDel) FixRoomIndexes(delIndex);
|
||||
return didDel;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void FixRoomIndexes(int deletedIndex)
|
||||
{
|
||||
foreach (var room in rooms)
|
||||
{
|
||||
List<uint> newAttachedObjects = new List<uint>();
|
||||
if (room.AttachedObjects == null)
|
||||
continue;
|
||||
foreach (var objIndex in room.AttachedObjects)
|
||||
{
|
||||
if (objIndex == deletedIndex) continue;
|
||||
if (objIndex > deletedIndex)
|
||||
newAttachedObjects.Add(objIndex - 1); // move the index back so it matches the index in the entitiy array.
|
||||
else newAttachedObjects.Add(objIndex); // else just add the index to the attached objects.
|
||||
}
|
||||
room.AttachedObjects = newAttachedObjects.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadChildren(Meta meta)
|
||||
{
|
||||
var centities = MetaTypes.ConvertDataArray<CEntityDef>(meta, MetaName.CEntityDef, _MloArchetypeDef.entities);
|
||||
var centities = MetaTypes.ConvertDataArray<CEntityDef>(meta, MetaName.CEntityDef, _MloArchetypeDefData.entities);
|
||||
if (centities != null)
|
||||
{
|
||||
entities = new MCEntityDef[centities.Length];
|
||||
for (int i = 0; i < centities.Length; i++)
|
||||
{
|
||||
entities[i] = new MCEntityDef(meta, centities[i]);
|
||||
entities[i] = new MCEntityDef(meta, ref centities[i]) { Archetype = this };
|
||||
}
|
||||
}
|
||||
|
||||
var crooms = MetaTypes.ConvertDataArray<CMloRoomDef>(meta, MetaName.CMloRoomDef, _MloArchetypeDef.rooms);
|
||||
var crooms = MetaTypes.ConvertDataArray<CMloRoomDef>(meta, MetaName.CMloRoomDef, _MloArchetypeDefData.rooms);
|
||||
if (crooms != null)
|
||||
{
|
||||
rooms = new MCMloRoomDef[crooms.Length];
|
||||
for (int i = 0; i < crooms.Length; i++)
|
||||
{
|
||||
rooms[i] = new MCMloRoomDef(meta, crooms[i]);
|
||||
rooms[i] = new MCMloRoomDef(meta, crooms[i]) { Archetype = this, Index = i };
|
||||
}
|
||||
}
|
||||
|
||||
var cportals = MetaTypes.ConvertDataArray<CMloPortalDef>(meta, MetaName.CMloPortalDef, _MloArchetypeDef.portals);
|
||||
var cportals = MetaTypes.ConvertDataArray<CMloPortalDef>(meta, MetaName.CMloPortalDef, _MloArchetypeDefData.portals);
|
||||
if (cportals != null)
|
||||
{
|
||||
portals = new MCMloPortalDef[cportals.Length];
|
||||
@@ -176,7 +287,7 @@ namespace CodeWalker.GameFiles
|
||||
}
|
||||
}
|
||||
|
||||
var centitySets = MetaTypes.ConvertDataArray<CMloEntitySet>(meta, MetaName.CMloEntitySet, _MloArchetypeDef.entitySets);
|
||||
var centitySets = MetaTypes.ConvertDataArray<CMloEntitySet>(meta, MetaName.CMloEntitySet, _MloArchetypeDefData.entitySets);
|
||||
if (centitySets != null)
|
||||
{
|
||||
entitySets = new MCMloEntitySet[centitySets.Length];
|
||||
@@ -187,14 +298,44 @@ namespace CodeWalker.GameFiles
|
||||
}
|
||||
|
||||
|
||||
timeCycleModifiers = MetaTypes.ConvertDataArray<CMloTimeCycleModifier>(meta, MetaName.CMloTimeCycleModifier, _MloArchetypeDef.timeCycleModifiers);
|
||||
timeCycleModifiers = MetaTypes.ConvertDataArray<CMloTimeCycleModifier>(meta, MetaName.CMloTimeCycleModifier, _MloArchetypeDefData.timeCycleModifiers);
|
||||
|
||||
}
|
||||
|
||||
public MCMloRoomDef GetEntityRoom(MCEntityDef ent)
|
||||
{
|
||||
int objectIndex = -1;
|
||||
for (int i = 0; i < entities.Length; i++)
|
||||
{
|
||||
MCEntityDef e = entities[i];
|
||||
if (e == ent)
|
||||
{
|
||||
objectIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (objectIndex == -1) return null;
|
||||
|
||||
MCMloRoomDef room = null;
|
||||
for (int i = 0; i < rooms.Length; i++)
|
||||
{
|
||||
MCMloRoomDef r = rooms[i];
|
||||
for (int j = 0; j < r.AttachedObjects.Length; j++)
|
||||
{
|
||||
uint ind = r.AttachedObjects[j];
|
||||
if (ind == objectIndex)
|
||||
{
|
||||
room = r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (room != null) break;
|
||||
}
|
||||
|
||||
return room;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
public class MloInstanceData
|
||||
{
|
||||
@@ -204,7 +345,12 @@ namespace CodeWalker.GameFiles
|
||||
public uint[] defaultEntitySets { get; set; }
|
||||
|
||||
public YmapEntityDef[] Entities { get; set; }
|
||||
public Dictionary<MetaHash, MloInstanceEntitySet> EntitySets { get; set; }
|
||||
|
||||
public MloInstanceData()
|
||||
{
|
||||
EntitySets = new Dictionary<MetaHash, MloInstanceEntitySet>();
|
||||
}
|
||||
|
||||
public void CreateYmapEntities(YmapEntityDef owner, MloArchetype mloa)
|
||||
{
|
||||
@@ -230,27 +376,182 @@ namespace CodeWalker.GameFiles
|
||||
var entitySet = entitySets[i];
|
||||
if (entitySet.Entities != null)
|
||||
{
|
||||
EntitySets[entitySet._Data.name] = new MloInstanceEntitySet(entitySet, this);
|
||||
MloInstanceEntitySet instset = EntitySets[entitySet._Data.name];
|
||||
for (int j = 0; j < entitySet.Entities.Length; j++)
|
||||
{
|
||||
YmapEntityDef e = CreateYmapEntity(owner, entitySet.Entities[j], lasti);
|
||||
e.MloEntitySet = entitySet;
|
||||
entlist.Add(e);
|
||||
EntitySets[entitySet._Data.name].Entities.Add(e);
|
||||
e.MloEntitySet = instset;
|
||||
lasti++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultEntitySets != null)
|
||||
if ((defaultEntitySets != null) && (entitySets != null))
|
||||
{
|
||||
for (var i = 0; i < defaultEntitySets.Length; i++)
|
||||
{
|
||||
uint index = defaultEntitySets[i];
|
||||
if (index >= entitySets.Length) continue;
|
||||
MCMloEntitySet set = entitySets[index];
|
||||
MloInstanceEntitySet instset = EntitySets[set._Data.name];
|
||||
instset.Visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
Entities = entlist.ToArray();
|
||||
}
|
||||
|
||||
private YmapEntityDef CreateYmapEntity(YmapEntityDef owner, MCEntityDef ment, int i)
|
||||
public void InitYmapEntityArchetypes(GameFileCache gfc)
|
||||
{
|
||||
YmapEntityDef e = new YmapEntityDef(null, i, ref ment._Data);
|
||||
if (Owner == null) return;
|
||||
var arch = Owner.Archetype;
|
||||
|
||||
if (Entities != null)
|
||||
{
|
||||
for (int j = 0; j < Entities.Length; j++)
|
||||
{
|
||||
var ient = Entities[j];
|
||||
var iarch = gfc.GetArchetype(ient.CEntityDef.archetypeName);
|
||||
ient.SetArchetype(iarch);
|
||||
|
||||
if (iarch == null)
|
||||
{ } //can't find archetype - des stuff eg {des_prologue_door}
|
||||
}
|
||||
|
||||
UpdateBBs(arch);
|
||||
}
|
||||
|
||||
if (EntitySets != null)
|
||||
{
|
||||
foreach (var entitySet in EntitySets)
|
||||
{
|
||||
var entities = entitySet.Value.Entities;
|
||||
if (entities == null) continue;
|
||||
|
||||
for (int i = 0; i < entities.Count; i++)
|
||||
{
|
||||
var ient = entities[i];
|
||||
var iarch = gfc.GetArchetype(ient.CEntityDef.archetypeName);
|
||||
ient.SetArchetype(iarch);
|
||||
|
||||
if (iarch == null)
|
||||
{ } //can't find archetype - des stuff eg {des_prologue_door}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateBBs(Archetype arch)
|
||||
{
|
||||
//update archetype room AABB's.. bad to have this here? where else to put it?
|
||||
var mloa = arch as MloArchetype;
|
||||
if (mloa != null)
|
||||
{
|
||||
Vector3 mlobbmin = Vector3.Zero;
|
||||
Vector3 mlobbmax = Vector3.Zero;
|
||||
Vector3[] c = new Vector3[8];
|
||||
var rooms = mloa.rooms;
|
||||
if (rooms != null)
|
||||
{
|
||||
for (int j = 0; j < rooms.Length; j++)
|
||||
{
|
||||
var room = rooms[j];
|
||||
if ((room.AttachedObjects == null) || (room.AttachedObjects.Length == 0)) continue;
|
||||
Vector3 min = new Vector3(float.MaxValue);
|
||||
Vector3 max = new Vector3(float.MinValue);
|
||||
for (int k = 0; k < room.AttachedObjects.Length; k++)
|
||||
{
|
||||
var objid = room.AttachedObjects[k];
|
||||
if (objid < Entities.Length)
|
||||
{
|
||||
var rooment = Entities[objid];
|
||||
if ((rooment != null) && (rooment.Archetype != null))
|
||||
{
|
||||
var earch = rooment.Archetype;
|
||||
var pos = rooment._CEntityDef.position;
|
||||
var ori = rooment.Orientation;
|
||||
Vector3 abmin = earch.BBMin * rooment.Scale; //entity box
|
||||
Vector3 abmax = earch.BBMax * rooment.Scale;
|
||||
c[0] = abmin;
|
||||
c[1] = new Vector3(abmin.X, abmin.Y, abmax.Z);
|
||||
c[2] = new Vector3(abmin.X, abmax.Y, abmin.Z);
|
||||
c[3] = new Vector3(abmin.X, abmax.Y, abmax.Z);
|
||||
c[4] = new Vector3(abmax.X, abmin.Y, abmin.Z);
|
||||
c[5] = new Vector3(abmax.X, abmin.Y, abmax.Z);
|
||||
c[6] = new Vector3(abmax.X, abmax.Y, abmin.Z);
|
||||
c[7] = abmax;
|
||||
for (int n = 0; n < 8; n++)
|
||||
{
|
||||
Vector3 corn = ori.Multiply(c[n]) + pos;
|
||||
min = Vector3.Min(min, corn);
|
||||
max = Vector3.Max(max, corn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
room.BBMin_CW = min;
|
||||
room.BBMax_CW = max;
|
||||
mlobbmin = Vector3.Min(mlobbmin, min);
|
||||
mlobbmax = Vector3.Max(mlobbmax, max);
|
||||
}
|
||||
}
|
||||
mloa.BBMin = mlobbmin;
|
||||
mloa.BBMax = mlobbmax;
|
||||
}
|
||||
}
|
||||
|
||||
public bool DeleteEntity(YmapEntityDef ent)
|
||||
{
|
||||
if (Entities == null)
|
||||
{
|
||||
throw new NullReferenceException("The Entities list returned null in our MloInstanceData. This could be an issue with initialization. The MloInstance probably doesn't exist.");
|
||||
}
|
||||
|
||||
if (ent.Index >= Entities.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("The index of the entity was greater than the amount of entities that exist in this MloInstance. Likely an issue with initializion.");
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
YmapEntityDef[] newentities = new YmapEntityDef[Entities.Length - 1];
|
||||
YmapEntityDef delentity = Entities[ent.Index];
|
||||
bool del = false;
|
||||
|
||||
for (int i = 0; i < Entities.Length; i++)
|
||||
{
|
||||
if (Entities[i] == delentity)
|
||||
{
|
||||
del = true;
|
||||
continue;
|
||||
}
|
||||
newentities[index] = Entities[i];
|
||||
newentities[index].Index = index;
|
||||
index++;
|
||||
}
|
||||
if (!del)
|
||||
throw new ArgumentException("The entity specified was not found in this MloInstance. It cannot be deleted.");
|
||||
|
||||
if (Owner.Archetype is MloArchetype arch)
|
||||
{
|
||||
if (arch.RemoveEntity(ent))
|
||||
{
|
||||
if (ent.MloEntitySet != null)
|
||||
if (!ent.MloEntitySet.Entities.Remove(ent))
|
||||
return false;
|
||||
// Delete was successful...
|
||||
Entities = newentities;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
throw new InvalidCastException("The owner of this archetype's archetype definition is not an MloArchetype.");
|
||||
}
|
||||
|
||||
public YmapEntityDef CreateYmapEntity(YmapEntityDef owner, MCEntityDef ment, int index)
|
||||
{
|
||||
YmapEntityDef e = new YmapEntityDef(null, index, ref ment._Data);
|
||||
e.Extensions = ment.Extensions;
|
||||
e.MloRefPosition = e.Position;
|
||||
e.MloRefOrientation = e.Orientation;
|
||||
@@ -259,9 +560,31 @@ namespace CodeWalker.GameFiles
|
||||
e.Orientation = Quaternion.Multiply(owner.Orientation, e.MloRefOrientation);
|
||||
e.UpdateWidgetPosition();
|
||||
e.UpdateWidgetOrientation();
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
public MCEntityDef TryGetArchetypeEntity(YmapEntityDef ymapEntity)
|
||||
{
|
||||
if (ymapEntity == null) return null;
|
||||
if (Owner?.Archetype == null) return null;
|
||||
if (!(Owner.Archetype is MloArchetype mloa)) return null;
|
||||
if (ymapEntity.Index >= mloa.entities.Length) return null;
|
||||
|
||||
var entity = mloa.entities[ymapEntity.Index];
|
||||
return entity;
|
||||
}
|
||||
|
||||
public YmapEntityDef TryGetYmapEntity(MCEntityDef mcEntity)
|
||||
{
|
||||
if (mcEntity == null) return null;
|
||||
if (Owner?.Archetype == null) return null;
|
||||
if (!(Owner.Archetype is MloArchetype mloa)) return null;
|
||||
|
||||
var index = Array.FindIndex(mloa.entities, x => x == mcEntity);
|
||||
if (index == -1 || index >= Entities.Length) return null;
|
||||
return Entities[index];
|
||||
}
|
||||
|
||||
public void SetPosition(Vector3 pos)
|
||||
{
|
||||
@@ -285,17 +608,52 @@ namespace CodeWalker.GameFiles
|
||||
for (int i = 0; i < Entities.Length; i++)
|
||||
{
|
||||
YmapEntityDef e = Entities[i];
|
||||
e.Position = Owner.Position + Owner.Orientation.Multiply(e.MloRefPosition);
|
||||
e.Orientation = Quaternion.Multiply(Owner.Orientation, e.MloRefOrientation);
|
||||
e.UpdateWidgetPosition();
|
||||
e.UpdateWidgetOrientation();
|
||||
UpdateEntity(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void UpdateEntity(YmapEntityDef e)
|
||||
{
|
||||
e.Position = Owner.Position + Owner.Orientation.Multiply(e.MloRefPosition);
|
||||
e.Orientation = Quaternion.Multiply(Owner.Orientation, e.MloRefOrientation);
|
||||
e.UpdateWidgetPosition();
|
||||
e.UpdateWidgetOrientation();
|
||||
}
|
||||
|
||||
public void AddEntity(YmapEntityDef e)
|
||||
{
|
||||
if (e == null) return;
|
||||
if (Entities == null) Entities = new YmapEntityDef[0];
|
||||
var entities = Entities.ToList();
|
||||
entities.Add(e);
|
||||
Entities = entities.ToArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
public class MloInstanceEntitySet
|
||||
{
|
||||
public MloInstanceEntitySet(MCMloEntitySet entSet, MloInstanceData instance)
|
||||
{
|
||||
EntitySet = entSet;
|
||||
Entities = new List<YmapEntityDef>();
|
||||
Instance = instance;
|
||||
}
|
||||
|
||||
public MCMloEntitySet EntitySet { get; set; }
|
||||
public List<YmapEntityDef> Entities { get; set; }
|
||||
public MloInstanceData Instance { get; set; }
|
||||
|
||||
public uint[] Locations
|
||||
{
|
||||
get { return EntitySet?.Locations; }
|
||||
set { if (EntitySet != null) EntitySet.Locations = value; }
|
||||
}
|
||||
|
||||
public bool Visible { get; set; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -935,6 +935,10 @@ namespace CodeWalker.GameFiles
|
||||
[TC(typeof(EXP))] public struct ArrayOfUshorts3 //array of 3 ushorts
|
||||
{
|
||||
public ushort u0, u1, u2;
|
||||
public Vector3 XYZ()
|
||||
{
|
||||
return new Vector3(u0, u1, u2);
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return u0.ToString() + ", " + u1.ToString() + ", " + u2.ToString();
|
||||
|
||||
@@ -538,6 +538,7 @@ namespace CodeWalker.GameFiles
|
||||
CExtensionDefSpawnPointOverride = 2716862120,
|
||||
CExtensionDefWindDisturbance = 569228403,
|
||||
CFiringPatternInfo = 4139644871,
|
||||
CFiringPatternInfoManager = 3698419088,
|
||||
CGrabHelper__Tunables = 1898505781,
|
||||
Chances = 3434267272,
|
||||
changeSetName = 3618800523,
|
||||
@@ -1605,6 +1606,7 @@ namespace CodeWalker.GameFiles
|
||||
VEHICLE_LAYOUTS_FILE = 2004032454,
|
||||
VEHICLE_METADATA_FILE = 4125139733,
|
||||
VEHICLE_POPULATION_FILE = 4010054647,
|
||||
VEHICLE_RESPONSE_DEFAULT = 3986150789,
|
||||
VEHICLE_RESPONSE_ARMY_BASE = 317362887,
|
||||
VEHICLE_RESPONSE_COUNTRYSIDE = 2467847847,
|
||||
VEHICLE_SHOP_DLC_FILE = 3203173146,
|
||||
@@ -1621,6 +1623,13 @@ namespace CodeWalker.GameFiles
|
||||
VFXINTERIORINFO_FILE = 354822867,
|
||||
VFXPEDINFO_FILE = 962370952,
|
||||
VFXREGIONINFO_FILE = 3633596549,
|
||||
vfxregioninfo_default = 526963733,
|
||||
vfxregioninfo_desert = 1202232026,
|
||||
vfxregioninfo_beach = 4239901007,
|
||||
vfxregioninfo_slum = 4267832995,
|
||||
vfxregioninfo_woodland = 1397181648,
|
||||
vfxregioninfo_mountain = 3282595980,
|
||||
vfxregioninfo_countryside = 2691334223,
|
||||
vfxTagHashName = 1944993828,
|
||||
VFXVEHICLEINFO_FILE = 1918258814,
|
||||
vfxVehicleInfos = 1829968483,
|
||||
@@ -3528,9 +3537,15 @@ namespace CodeWalker.GameFiles
|
||||
spName = 4254542050,
|
||||
|
||||
|
||||
//from rubidium
|
||||
//from rubidium / dav90 PSO XML / zonebind
|
||||
mpName = 2031203854,
|
||||
zoneName = 257000,
|
||||
vfxRegion = 3384955624,
|
||||
vehDirtMin = 1831590592,
|
||||
vehDirtMax = 625824556,
|
||||
vehDirtGrowScale = 2919812941,
|
||||
pedDirtMin = 1861946207,
|
||||
pedDirtMax = 3150688023,
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2428,7 +2428,9 @@ namespace CodeWalker.GameFiles
|
||||
public Vector3 BBMax { get { return (_Data.bbMax); } }
|
||||
public Vector3 BBMin_CW { get; set; }
|
||||
public Vector3 BBMax_CW { get; set; }
|
||||
|
||||
|
||||
public MloArchetype Archetype { get; set; }
|
||||
public int Index { get; set; }
|
||||
|
||||
public MCMloRoomDef() { }
|
||||
public MCMloRoomDef(Meta meta, CMloRoomDef data)
|
||||
@@ -2610,7 +2612,7 @@ namespace CodeWalker.GameFiles
|
||||
Entities = new MCEntityDef[ents.Length];
|
||||
for (int i = 0; i < ents.Length; i++)
|
||||
{
|
||||
Entities[i] = new MCEntityDef(meta, ents[i]);
|
||||
Entities[i] = new MCEntityDef(meta, ref ents[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2750,23 +2752,26 @@ namespace CodeWalker.GameFiles
|
||||
[TC(typeof(EXP))] public class MCEntityDef : MetaWrapper
|
||||
{
|
||||
public CEntityDef _Data;
|
||||
public CEntityDef Data { get { return _Data; } }
|
||||
public CEntityDef Data { get { return _Data; } set { _Data = value; } }
|
||||
public MetaWrapper[] Extensions { get; set; }
|
||||
|
||||
|
||||
public MCEntityDef() { }
|
||||
public MloArchetype Archetype { get; set; } // for browsing/reference purposes
|
||||
|
||||
public MCEntityDef(MCEntityDef copy)
|
||||
{
|
||||
if (copy != null)
|
||||
{
|
||||
_Data = copy.Data;
|
||||
}
|
||||
if (copy != null) _Data = copy.Data;
|
||||
}
|
||||
public MCEntityDef(Meta meta, CEntityDef d)
|
||||
public MCEntityDef(Meta meta, ref CEntityDef d)
|
||||
{
|
||||
_Data = d;
|
||||
Extensions = MetaTypes.GetExtensions(meta, _Data.extensions);
|
||||
}
|
||||
public MCEntityDef(ref CEntityDef d, MloArchetype arch)
|
||||
{
|
||||
_Data = d;
|
||||
Archetype = arch;
|
||||
}
|
||||
|
||||
public override void Load(Meta meta, MetaPOINTER ptr)
|
||||
{
|
||||
|
||||
@@ -1646,7 +1646,12 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
public static string FormatHash(MetaHash h) //for use with WriteItemArray
|
||||
{
|
||||
return h.ToString();
|
||||
var str = JenkIndex.TryGetString(h);
|
||||
if (!string.IsNullOrEmpty(str)) return str;
|
||||
str = GlobalText.TryGetString(h);
|
||||
if (!string.IsNullOrEmpty(str)) return str;
|
||||
return HashString(h);// "hash_" + h.Hex;
|
||||
//return h.ToString();
|
||||
}
|
||||
public static string FormatVector2(Vector2 v) //for use with WriteItemArray
|
||||
{
|
||||
|
||||
@@ -23,6 +23,95 @@
|
||||
//shamelessly stolen and mangled
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
|
||||
Regarding saving PSO files:
|
||||
|
||||
[for checksum - use whole file]
|
||||
Brick - Today
|
||||
uint32_t joaat_checksum(const void* memory, const size_t length)
|
||||
{
|
||||
uint32_t hash = 0x3FAC7125;
|
||||
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
{
|
||||
hash += static_cast<const int8_t*>(memory)[i];
|
||||
hash += hash << 10;
|
||||
hash ^= hash >> 6;
|
||||
}
|
||||
|
||||
hash += hash << 3;
|
||||
hash ^= hash >> 11;
|
||||
hash += hash << 15;
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
|
||||
[before doing checksum for file:]
|
||||
v12->Checksum = 0;
|
||||
v12->FileSize = 0;
|
||||
v12->Magic = 'SKHC';
|
||||
v12->Size = 0x14000000;
|
||||
v22 = v12;
|
||||
LOBYTE(v12->Platform) = platformChar[0];
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Brick - Today
|
||||
This is a table i made a while ago for the pso types btw
|
||||
| Index | Type | Size | Align | Name | Serialization
|
||||
| 0 | Simple | 1 | 1 | bool |
|
||||
| 1 | Simple | 1 | 1 | s8 |
|
||||
| 2 | Simple | 1 | 1 | u8 |
|
||||
| 3 | Simple | 2 | 2 | s16 |
|
||||
| 4 | Simple | 2 | 2 | u16 |
|
||||
| 5 | Simple | 4 | 4 | s32 |
|
||||
| 6 | Simple | 4 | 4 | u32 |
|
||||
| 7 | Simple | 4 | 4 | f32 |
|
||||
| 8 | Vector | 8 | 4 | vec2 |
|
||||
| 9 | Vector | 16 | 16 | vec3 |
|
||||
| 10 | Vector | 16 | 16 | vec4 |
|
||||
| 11 | String | 0 | 0 | string |
|
||||
| 12 | Struct | 0 | 0 | struct |
|
||||
| 13 | Array | 0 | 0 | array |
|
||||
| 14 | Enum | 0 | 0 | enum |
|
||||
| 15 | Bitset | 0 | 0 | bitset |
|
||||
| 16 | Map | 0 | 0 | map |
|
||||
| 17 | Matrix | 64 | 16 | matrix43 | shuffled
|
||||
| 18 | Matrix | 64 | 16 | matrix44 | shuffled
|
||||
| 19 | Vector | 16 | 16 | vec4 | x, y, x, x
|
||||
| 20 | Vector | 16 | 16 | vec4 | x, y, z, x
|
||||
| 21 | Vector | 16 | 16 | vec4 | x, y, z, w
|
||||
| 22 | Matrix | 48 | 16 | matrix34 |
|
||||
| 23 | Matrix | 64 | 16 | matrix43 |
|
||||
| 24 | Matrix | 64 | 16 | matrix44 |
|
||||
| 25 | Simple | 16 | 16 | f32_vec4 | fill all with f32
|
||||
| 26 | Simple | 16 | 16 | bool_int4 | fill all with 0x00000000 or 0xFFFFFFFF depending on bool
|
||||
| 27 | Vector | 16 | 16 | bool4_int4 | fill each with 0x00000000 or 0xFFFFFFFF depending on bools
|
||||
| 28 | Simple | 8 | 8 | s32_i64 | sign extended s32
|
||||
| 29 | Simple | 8 | 8 | s32_u64 | sign extended s32
|
||||
| 30 | Simple | 2 | 2 | f16 | f64 converted to f16
|
||||
| 31 | Simple | 8 | 8 | s64 |
|
||||
| 32 | Simple | 8 | 8 | u64 |
|
||||
| 33 | Simple | 8 | 8 | f64 |
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml;
|
||||
using SharpDX;
|
||||
@@ -182,7 +183,7 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
mb.AddEnumInfo(_infos.EnumNameHash);
|
||||
|
||||
int val = GetEnumInt(entry.ReferenceKey, cnode.InnerText);
|
||||
int val = GetEnumInt(entry.ReferenceKey, cnode.InnerText, entry.DataType);
|
||||
Write(val, data, entry.DataOffset);
|
||||
break;
|
||||
}
|
||||
@@ -193,7 +194,7 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
mb.AddEnumInfo(_infos.EnumNameHash);
|
||||
|
||||
int val = GetEnumInt(entry.ReferenceKey, cnode.InnerText);
|
||||
int val = GetEnumInt(entry.ReferenceKey, cnode.InnerText, entry.DataType);
|
||||
Write((short)val, data, entry.DataOffset);
|
||||
break;
|
||||
}
|
||||
@@ -296,11 +297,16 @@ namespace CodeWalker.GameFiles
|
||||
private static void GetParsedArrayOfBytes(XmlNode node, byte[] data, MetaStructureEntryInfo_s entry, MetaStructureEntryInfo_s arrEntry)
|
||||
{
|
||||
int offset = entry.DataOffset;
|
||||
string[] split;
|
||||
|
||||
var ns = NumberStyles.Any;
|
||||
var ic = CultureInfo.InvariantCulture;
|
||||
var sa = new[] { ' ' };
|
||||
var so = StringSplitOptions.RemoveEmptyEntries;
|
||||
var split = node.InnerText.Trim().Split(sa, so); //split = Split(node.InnerText, 2); to read as unsplitted HEX
|
||||
|
||||
switch (arrEntry.DataType)
|
||||
{
|
||||
default:
|
||||
default: //expecting hex string.
|
||||
split = Split(node.InnerText, 2);
|
||||
for (int j = 0; j < split.Length; j++)
|
||||
{
|
||||
@@ -309,67 +315,81 @@ namespace CodeWalker.GameFiles
|
||||
offset += sizeof(byte);
|
||||
}
|
||||
break;
|
||||
case MetaStructureEntryDataType.SignedByte:
|
||||
split = node.InnerText.Split(); //split = Split(node.InnerText, 2); to read as unsplitted HEX
|
||||
case MetaStructureEntryDataType.SignedByte: //expecting space-separated array.
|
||||
for (int j = 0; j < split.Length; j++)
|
||||
{
|
||||
sbyte val = Convert.ToSByte(split[j], 10);
|
||||
data[offset] = (byte)val;
|
||||
offset += sizeof(sbyte);
|
||||
sbyte val;// = Convert.ToSByte(split[j], 10);
|
||||
if (sbyte.TryParse(split[j].Trim(), ns, ic, out val))
|
||||
{
|
||||
data[offset] = (byte)val;
|
||||
offset += sizeof(sbyte);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MetaStructureEntryDataType.UnsignedByte:
|
||||
split = node.InnerText.Split();
|
||||
case MetaStructureEntryDataType.UnsignedByte: //expecting space-separated array.
|
||||
for (int j = 0; j < split.Length; j++)
|
||||
{
|
||||
byte val = Convert.ToByte(split[j], 10);
|
||||
data[offset] = val;
|
||||
offset += sizeof(byte);
|
||||
byte val;// = Convert.ToByte(split[j], 10);
|
||||
if (byte.TryParse(split[j].Trim(), ns, ic, out val))
|
||||
{
|
||||
data[offset] = val;
|
||||
offset += sizeof(byte);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MetaStructureEntryDataType.SignedShort:
|
||||
split = node.InnerText.Split();
|
||||
case MetaStructureEntryDataType.SignedShort: //expecting space-separated array.
|
||||
for (int j = 0; j < split.Length; j++)
|
||||
{
|
||||
short val = Convert.ToInt16(split[j], 10);
|
||||
Write(val, data, offset);
|
||||
offset += sizeof(short);
|
||||
short val;// = Convert.ToInt16(split[j], 10);
|
||||
if (short.TryParse(split[j].Trim(), ns, ic, out val))
|
||||
{
|
||||
Write(val, data, offset);
|
||||
offset += sizeof(short);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MetaStructureEntryDataType.UnsignedShort:
|
||||
split = node.InnerText.Split();
|
||||
case MetaStructureEntryDataType.UnsignedShort: //expecting space-separated array.
|
||||
for (int j = 0; j < split.Length; j++)
|
||||
{
|
||||
ushort val = Convert.ToUInt16(split[j], 10);
|
||||
Write(val, data, offset);
|
||||
offset += sizeof(ushort);
|
||||
ushort val;// = Convert.ToUInt16(split[j], 10);
|
||||
if (ushort.TryParse(split[j].Trim(), ns, ic, out val))
|
||||
{
|
||||
Write(val, data, offset);
|
||||
offset += sizeof(ushort);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MetaStructureEntryDataType.SignedInt:
|
||||
split = node.InnerText.Split();
|
||||
case MetaStructureEntryDataType.SignedInt: //expecting space-separated array.
|
||||
for (int j = 0; j < split.Length; j++)
|
||||
{
|
||||
int val = Convert.ToInt32(split[j], 10);
|
||||
Write(val, data, offset);
|
||||
offset += sizeof(int);
|
||||
int val;// = Convert.ToInt32(split[j], 10);
|
||||
if (int.TryParse(split[j].Trim(), ns, ic, out val))
|
||||
{
|
||||
Write(val, data, offset);
|
||||
offset += sizeof(int);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MetaStructureEntryDataType.UnsignedInt:
|
||||
split = node.InnerText.Split();
|
||||
case MetaStructureEntryDataType.UnsignedInt: //expecting space-separated array.
|
||||
for (int j = 0; j < split.Length; j++)
|
||||
{
|
||||
uint val = Convert.ToUInt32(split[j], 10);
|
||||
Write(val, data, offset);
|
||||
offset += sizeof(uint);
|
||||
uint val;// = Convert.ToUInt32(split[j], 10);
|
||||
if (uint.TryParse(split[j].Trim(), ns, ic, out val))
|
||||
{
|
||||
Write(val, data, offset);
|
||||
offset += sizeof(uint);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MetaStructureEntryDataType.Float:
|
||||
split = node.InnerText.Split();
|
||||
case MetaStructureEntryDataType.Float: //expecting space-separated array.
|
||||
for (int j = 0; j < split.Length; j++)
|
||||
{
|
||||
float val = FloatUtil.Parse(split[j]);
|
||||
Write(val, data, offset);
|
||||
offset += sizeof(float);
|
||||
float val;// = FloatUtil.Parse(split[j]);
|
||||
if (FloatUtil.TryParse(split[j].Trim(), out val))
|
||||
{
|
||||
Write(val, data, offset);
|
||||
offset += sizeof(float);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -685,9 +705,8 @@ namespace CodeWalker.GameFiles
|
||||
return chunks.ToArray();
|
||||
}
|
||||
|
||||
private static int GetEnumInt(MetaName type, string enumString)
|
||||
private static int GetEnumInt(MetaName type, string enumString, MetaStructureEntryDataType dataType)
|
||||
{
|
||||
var enumName = (MetaName)(uint)GetHash(enumString);
|
||||
var infos = MetaTypes.GetEnumInfo(type);
|
||||
|
||||
if (infos == null)
|
||||
@@ -695,13 +714,48 @@ namespace CodeWalker.GameFiles
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (int j = 0; j < infos.Entries.Length; j++)
|
||||
{
|
||||
var entry = infos.Entries[j];
|
||||
|
||||
if (entry.EntryNameHash == enumName)
|
||||
bool isFlags = (dataType == MetaStructureEntryDataType.IntFlags1) ||
|
||||
(dataType == MetaStructureEntryDataType.IntFlags2);// ||
|
||||
//(dataType == MetaStructureEntryDataType.ShortFlags);
|
||||
|
||||
if (isFlags)
|
||||
{
|
||||
//flags enum. (multiple values, comma-separated)
|
||||
var split = enumString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
int enumVal = 0;
|
||||
|
||||
for (int i = 0; i < split.Length; i++)
|
||||
{
|
||||
return entry.EntryValue;
|
||||
var enumName = (MetaName)(uint)GetHash(split[i].Trim());
|
||||
|
||||
for (int j = 0; j < infos.Entries.Length; j++)
|
||||
{
|
||||
var entry = infos.Entries[j];
|
||||
if (entry.EntryNameHash == enumName)
|
||||
{
|
||||
enumVal += (1 << entry.EntryValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return enumVal;
|
||||
}
|
||||
else
|
||||
{
|
||||
//single value enum.
|
||||
|
||||
var enumName = (MetaName)(uint)GetHash(enumString);
|
||||
|
||||
for (int j = 0; j < infos.Entries.Length; j++)
|
||||
{
|
||||
var entry = infos.Entries[j];
|
||||
|
||||
if (entry.EntryNameHash == enumName)
|
||||
{
|
||||
return entry.EntryValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1283,7 +1283,7 @@ namespace CodeWalker.GameFiles
|
||||
public string HeatsTyre { get; set; }
|
||||
public string Material { get; set; }
|
||||
|
||||
public Color4 Colour { get; set; }
|
||||
public Color Colour { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
@@ -1293,14 +1293,19 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
public static class BoundsMaterialTypes
|
||||
{
|
||||
private static Dictionary<string, Color4> ColourDict;
|
||||
private static Dictionary<string, Color> ColourDict;
|
||||
private static List<BoundsMaterialData> Materials;
|
||||
|
||||
public static void Init(GameFileCache gameFileCache)
|
||||
{
|
||||
var rpfman = gameFileCache.RpfMan;
|
||||
|
||||
InitColours();
|
||||
var dic = new Dictionary<string,Color>();
|
||||
string filename2 = "common.rpf\\data\\effects\\materialfx.dat";
|
||||
string txt2 = rpfman.GetFileUTF8Text(filename2);
|
||||
AddMaterialfxDat(txt2, dic);
|
||||
|
||||
ColourDict = dic;
|
||||
|
||||
var list = new List<BoundsMaterialData>();
|
||||
string filename = "common.rpf\\data\\materials\\materials.dat";
|
||||
@@ -1314,25 +1319,47 @@ namespace CodeWalker.GameFiles
|
||||
Materials = list;
|
||||
}
|
||||
|
||||
private static void InitColours()
|
||||
//Only gets the colors
|
||||
private static void AddMaterialfxDat(string txt, Dictionary<string, Color> dic)
|
||||
{
|
||||
var dict = new Dictionary<string, Color4>();
|
||||
string txt = File.ReadAllText("Materials.txt");
|
||||
string[] lines = txt.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
for (int i = 0; i < lines.Length; i++)
|
||||
dic.Clear();
|
||||
if (txt == null) return;
|
||||
|
||||
string[] lines = txt.Split('\n');
|
||||
string startLine = "MTLFX_TABLE_START";
|
||||
string endLine = "MTLFX_TABLE_END";
|
||||
|
||||
for (int i = 1; i < lines.Length; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
if (line.Length < 10) continue;
|
||||
|
||||
if (line[0] == '#') continue;
|
||||
if (line.StartsWith(startLine)) continue;
|
||||
if (line.StartsWith(endLine)) break;
|
||||
|
||||
string[] parts = line.Split(new[] { '\t', ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 2) continue;
|
||||
string name = parts[0].Trim();
|
||||
string cstr = parts[1].Trim();
|
||||
uint cval = Convert.ToUInt32(cstr, 16);
|
||||
Color4 c = new Color4(cval);
|
||||
dict[name] = c;
|
||||
|
||||
if (parts.Length < 5) continue; // FXGroup R G B ...
|
||||
|
||||
int cp = 0;
|
||||
Color c = new Color();
|
||||
c.A = 0xFF;
|
||||
string fxgroup = string.Empty;
|
||||
for (int p = 0; p < parts.Length; p++)
|
||||
{
|
||||
string part = parts[p].Trim();
|
||||
if (string.IsNullOrWhiteSpace(part)) continue;
|
||||
switch (cp)
|
||||
{
|
||||
case 0: fxgroup = part; break;
|
||||
case 1: c.R = byte.Parse(part); break;
|
||||
case 2: c.G = byte.Parse(part); break;
|
||||
case 3: c.B = byte.Parse(part); break;
|
||||
}
|
||||
cp++;
|
||||
}
|
||||
dic.Add(fxgroup, c);
|
||||
}
|
||||
ColourDict = dict;
|
||||
}
|
||||
|
||||
private static void AddMaterialsDat(string txt, List<BoundsMaterialData> list)
|
||||
@@ -1384,14 +1411,14 @@ namespace CodeWalker.GameFiles
|
||||
if (cp != 23)
|
||||
{ }
|
||||
|
||||
Color4 c;
|
||||
if ((ColourDict != null) && (ColourDict.TryGetValue(d.Name, out c)))
|
||||
Color c;
|
||||
if ((ColourDict != null) && (ColourDict.TryGetValue(d.FXGroup, out c)))
|
||||
{
|
||||
d.Colour = c;
|
||||
}
|
||||
else
|
||||
{
|
||||
d.Colour = new Color4(0xFFCCCCCC);
|
||||
d.Colour = new Color(0xFFCCCCCC);
|
||||
}
|
||||
|
||||
|
||||
@@ -1416,6 +1443,13 @@ namespace CodeWalker.GameFiles
|
||||
return Materials[type.Index];
|
||||
}
|
||||
|
||||
public static BoundsMaterialData GetMaterial(byte index)
|
||||
{
|
||||
if (Materials == null) return null;
|
||||
if ((int)index >= Materials.Count) return null;
|
||||
return Materials[index];
|
||||
}
|
||||
|
||||
public static string GetMaterialName(BoundsMaterialType type)
|
||||
{
|
||||
var m = GetMaterial(type);
|
||||
@@ -1423,10 +1457,10 @@ namespace CodeWalker.GameFiles
|
||||
return m.Name;
|
||||
}
|
||||
|
||||
public static Color4 GetMaterialColour(BoundsMaterialType type)
|
||||
public static Color GetMaterialColour(BoundsMaterialType type)
|
||||
{
|
||||
var m = GetMaterial(type);
|
||||
if (m == null) return new Color4(0xFFCCCCCC);
|
||||
if (m == null) return new Color(0xFFCCCCCC);
|
||||
return m.Colour;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -718,16 +718,16 @@ namespace CodeWalker.GameFiles
|
||||
//public float TranslationX { get; set; }
|
||||
//public float TranslationY { get; set; }
|
||||
//public float TranslationZ { get; set; }
|
||||
public uint Unknown_1Ch { get; set; } // 0x00000000
|
||||
public float Unknown_20h { get; set; } // 1.0
|
||||
public float Unknown_24h { get; set; } // 1.0
|
||||
public float Unknown_28h { get; set; } // 1.0
|
||||
public float Unknown_2Ch { get; set; } // 1.0
|
||||
public ushort Unknown_30h { get; set; } //limb end index? IK chain?
|
||||
public uint Unknown_1Ch { get; set; } // 0x00000000 RHW?
|
||||
public float ScaleX { get; set; } // 1.0
|
||||
public float ScaleY { get; set; } // 1.0
|
||||
public float ScaleZ { get; set; } // 1.0
|
||||
public float Unknown_2Ch { get; set; } // 1.0 RHW?
|
||||
public ushort NextSiblingIndex { get; set; } //limb end index? IK chain?
|
||||
public short ParentIndex { get; set; }
|
||||
public uint Unknown_34h { get; set; } // 0x00000000
|
||||
public ulong NamePointer { get; set; }
|
||||
public ushort Unknown_40h { get; set; }
|
||||
public ushort Flags { get; set; }
|
||||
public ushort Unknown_42h { get; set; }
|
||||
public ushort Id { get; set; }
|
||||
public ushort Unknown_46h { get; set; }
|
||||
@@ -756,15 +756,15 @@ namespace CodeWalker.GameFiles
|
||||
//this.TranslationY = reader.ReadSingle();
|
||||
//this.TranslationZ = reader.ReadSingle();
|
||||
this.Unknown_1Ch = reader.ReadUInt32();
|
||||
this.Unknown_20h = reader.ReadSingle();
|
||||
this.Unknown_24h = reader.ReadSingle();
|
||||
this.Unknown_28h = reader.ReadSingle();
|
||||
this.ScaleX = reader.ReadSingle();
|
||||
this.ScaleY = reader.ReadSingle();
|
||||
this.ScaleZ = reader.ReadSingle();
|
||||
this.Unknown_2Ch = reader.ReadSingle();
|
||||
this.Unknown_30h = reader.ReadUInt16();
|
||||
this.NextSiblingIndex = reader.ReadUInt16();
|
||||
this.ParentIndex = reader.ReadInt16();
|
||||
this.Unknown_34h = reader.ReadUInt32();
|
||||
this.NamePointer = reader.ReadUInt64();
|
||||
this.Unknown_40h = reader.ReadUInt16();
|
||||
this.Flags = reader.ReadUInt16();
|
||||
this.Unknown_42h = reader.ReadUInt16();
|
||||
this.Id = reader.ReadUInt16();
|
||||
this.Unknown_46h = reader.ReadUInt16();
|
||||
@@ -796,15 +796,15 @@ namespace CodeWalker.GameFiles
|
||||
//writer.Write(this.TranslationY);
|
||||
//writer.Write(this.TranslationZ);
|
||||
writer.Write(this.Unknown_1Ch);
|
||||
writer.Write(this.Unknown_20h);
|
||||
writer.Write(this.Unknown_24h);
|
||||
writer.Write(this.Unknown_28h);
|
||||
writer.Write(this.ScaleX);
|
||||
writer.Write(this.ScaleY);
|
||||
writer.Write(this.ScaleZ);
|
||||
writer.Write(this.Unknown_2Ch);
|
||||
writer.Write(this.Unknown_30h);
|
||||
writer.Write(this.NextSiblingIndex);
|
||||
writer.Write(this.ParentIndex);
|
||||
writer.Write(this.Unknown_34h);
|
||||
writer.Write(this.NamePointer);
|
||||
writer.Write(this.Unknown_40h);
|
||||
writer.Write(this.Flags);
|
||||
writer.Write(this.Unknown_42h);
|
||||
writer.Write(this.Id);
|
||||
writer.Write(this.Unknown_46h);
|
||||
@@ -968,12 +968,8 @@ namespace CodeWalker.GameFiles
|
||||
public float Unknown_50h { get; set; } // -pi
|
||||
public float Unknown_54h { get; set; } // pi
|
||||
public float Unknown_58h { get; set; } // 1.0
|
||||
public float Unknown_5Ch { get; set; }
|
||||
public float Unknown_60h { get; set; }
|
||||
public float Unknown_64h { get; set; }
|
||||
public float Unknown_68h { get; set; }
|
||||
public float Unknown_6Ch { get; set; }
|
||||
public float Unknown_70h { get; set; }
|
||||
public Vector3 Min { get; set; }
|
||||
public Vector3 Max { get; set; }
|
||||
public float Unknown_74h { get; set; } // pi
|
||||
public float Unknown_78h { get; set; } // -pi
|
||||
public float Unknown_7Ch { get; set; } // pi
|
||||
|
||||
@@ -54,8 +54,8 @@ namespace CodeWalker.GameFiles
|
||||
public uint Unused_078h { get; set; } // 0x00000000
|
||||
public uint Unused_07Ch { get; set; } // 0x00000000
|
||||
public ulong IndicesPointer { get; set; }
|
||||
public ulong AdjPolysPointer { get; set; }
|
||||
public uint AdjPolysIndicesCount { get; set; }
|
||||
public ulong EdgesPointer { get; set; }
|
||||
public uint EdgesIndicesCount { get; set; }
|
||||
public NavMeshUintArray AdjAreaIDs { get; set; }
|
||||
public ulong PolysPointer { get; set; }
|
||||
public ulong SectorTreePointer { get; set; }
|
||||
@@ -79,7 +79,7 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
public NavMeshList<NavMeshVertex> Vertices { get; set; }
|
||||
public NavMeshList<ushort> Indices { get; set; }
|
||||
public NavMeshList<NavMeshAdjPoly> AdjPolys { get; set; }
|
||||
public NavMeshList<NavMeshEdge> Edges { get; set; }
|
||||
public NavMeshList<NavMeshPoly> Polys { get; set; }
|
||||
public NavMeshSector SectorTree { get; set; }
|
||||
public NavMeshPortal[] Portals { get; set; }
|
||||
@@ -109,8 +109,8 @@ namespace CodeWalker.GameFiles
|
||||
Unused_078h = reader.ReadUInt32();
|
||||
Unused_07Ch = reader.ReadUInt32();
|
||||
IndicesPointer = reader.ReadUInt64();
|
||||
AdjPolysPointer = reader.ReadUInt64();
|
||||
AdjPolysIndicesCount = reader.ReadUInt32();
|
||||
EdgesPointer = reader.ReadUInt64();
|
||||
EdgesIndicesCount = reader.ReadUInt32();
|
||||
AdjAreaIDs = reader.ReadStruct<NavMeshUintArray>();
|
||||
PolysPointer = reader.ReadUInt64();
|
||||
SectorTreePointer = reader.ReadUInt64();
|
||||
@@ -135,7 +135,7 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
Vertices = reader.ReadBlockAt<NavMeshList<NavMeshVertex>>(VerticesPointer);
|
||||
Indices = reader.ReadBlockAt<NavMeshList<ushort>>(IndicesPointer);
|
||||
AdjPolys = reader.ReadBlockAt<NavMeshList<NavMeshAdjPoly>>(AdjPolysPointer);
|
||||
Edges = reader.ReadBlockAt<NavMeshList<NavMeshEdge>>(EdgesPointer);
|
||||
Polys = reader.ReadBlockAt<NavMeshList<NavMeshPoly>>(PolysPointer);
|
||||
SectorTree = reader.ReadBlockAt<NavMeshSector>(SectorTreePointer);
|
||||
Portals = reader.ReadStructsAt<NavMeshPortal>(PortalsPointer, PortalsCount);
|
||||
@@ -150,7 +150,7 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
VerticesPointer = (ulong)(Vertices != null ? Vertices.FilePosition : 0);
|
||||
IndicesPointer = (ulong)(Indices != null ? Indices.FilePosition : 0);
|
||||
AdjPolysPointer = (ulong)(AdjPolys != null ? AdjPolys.FilePosition : 0);
|
||||
EdgesPointer = (ulong)(Edges != null ? Edges.FilePosition : 0);
|
||||
PolysPointer = (ulong)(Polys != null ? Polys.FilePosition : 0);
|
||||
SectorTreePointer = (ulong)(SectorTree != null ? SectorTree.FilePosition : 0);
|
||||
PortalsPointer = (ulong)(PortalsBlock?.FilePosition ?? 0);
|
||||
@@ -158,6 +158,39 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
|
||||
|
||||
//uint totbytes = 0;
|
||||
//Stack<NavMeshSector> sectorstack = new Stack<NavMeshSector>();
|
||||
//if (SectorTree != null) sectorstack.Push(SectorTree);
|
||||
//while (sectorstack.Count > 0)
|
||||
//{
|
||||
// var sector = sectorstack.Pop();
|
||||
// if (sector.SubTree1 != null) sectorstack.Push(sector.SubTree1);
|
||||
// if (sector.SubTree2 != null) sectorstack.Push(sector.SubTree2);
|
||||
// if (sector.SubTree3 != null) sectorstack.Push(sector.SubTree3);
|
||||
// if (sector.SubTree4 != null) sectorstack.Push(sector.SubTree4);
|
||||
// if (sector.Data != null)
|
||||
// {
|
||||
// var sdata = sector.Data;
|
||||
// totbytes += (uint)(sdata.PolyIDsBlock?.BlockLength ?? 0);
|
||||
// totbytes += (uint)(sdata.PointsBlock?.BlockLength ?? 0);
|
||||
// }
|
||||
//}
|
||||
//totbytes += PadSize(VerticesCount * (uint)Vertices.ItemSize);
|
||||
//totbytes += PadSize(EdgesIndicesCount * (uint)Indices.ItemSize);
|
||||
//totbytes += PadSize(EdgesIndicesCount * (uint)Edges.ItemSize);
|
||||
//totbytes += PadSize(PolysCount * (uint)Polys.ItemSize);
|
||||
////totbytes += (uint)BlockLength;
|
||||
//totbytes += (uint)Vertices.ListParts.BlockLength;//Vertices.ListPartsCount * 16;
|
||||
//totbytes += (uint)Indices.ListParts.BlockLength;//Indices.ListPartsCount * 16;
|
||||
//totbytes += (uint)Edges.ListParts.BlockLength;//Edges.ListPartsCount * 16;
|
||||
//totbytes += (uint)Polys.ListParts.BlockLength;//Polys.ListPartsCount * 16;
|
||||
//totbytes += (uint)(PortalsBlock?.BlockLength ?? 0);//PortalsCount * 28;
|
||||
//totbytes += (uint)(PortalLinksBlock?.BlockLength ?? 0);//PortalLinksCount * 2;
|
||||
//int remaining = ((int)TotalBytes) - ((int)totbytes);
|
||||
//if (totbytes != TotalBytes)
|
||||
//{ }
|
||||
|
||||
|
||||
writer.Write((uint)ContentFlags);
|
||||
writer.Write(VersionUnk1);
|
||||
writer.Write(Unused_018h);
|
||||
@@ -169,8 +202,8 @@ namespace CodeWalker.GameFiles
|
||||
writer.Write(Unused_078h);
|
||||
writer.Write(Unused_07Ch);
|
||||
writer.Write(IndicesPointer);
|
||||
writer.Write(AdjPolysPointer);
|
||||
writer.Write(AdjPolysIndicesCount);
|
||||
writer.Write(EdgesPointer);
|
||||
writer.Write(EdgesIndicesCount);
|
||||
writer.WriteStruct(AdjAreaIDs);
|
||||
writer.Write(PolysPointer);
|
||||
writer.Write(SectorTreePointer);
|
||||
@@ -192,13 +225,19 @@ namespace CodeWalker.GameFiles
|
||||
writer.Write(Unused_16Ch);
|
||||
}
|
||||
|
||||
private uint PadSize(uint s)
|
||||
{
|
||||
const uint align = 16;
|
||||
if ((s % align) != 0) s += (align - (s % align));
|
||||
return s;
|
||||
}
|
||||
|
||||
public override IResourceBlock[] GetReferences()
|
||||
{
|
||||
var list = new List<IResourceBlock>(base.GetReferences());
|
||||
if (Vertices != null) list.Add(Vertices);
|
||||
if (Indices != null) list.Add(Indices);
|
||||
if (AdjPolys != null) list.Add(AdjPolys);
|
||||
if (Edges != null) list.Add(Edges);
|
||||
if (Polys != null) list.Add(Polys);
|
||||
if (SectorTree != null) list.Add(SectorTree);
|
||||
|
||||
@@ -221,6 +260,15 @@ namespace CodeWalker.GameFiles
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void SetDefaults(bool vehicle)
|
||||
{
|
||||
VersionUnk1 = 0x00010011;
|
||||
VersionUnk2 = vehicle ? 0 : 0x85CB3561;
|
||||
Transform = Matrix.Identity;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "(Size: " + FloatUtil.GetVector3String(AABBSize) + ")";
|
||||
@@ -325,6 +373,50 @@ namespace CodeWalker.GameFiles
|
||||
}
|
||||
}
|
||||
|
||||
public uint Get(uint i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
default:
|
||||
case 0: return v00;
|
||||
case 1: return v01;
|
||||
case 2: return v02;
|
||||
case 3: return v03;
|
||||
case 4: return v04;
|
||||
case 5: return v05;
|
||||
case 6: return v06;
|
||||
case 7: return v07;
|
||||
case 8: return v08;
|
||||
case 9: return v09;
|
||||
case 10: return v10;
|
||||
case 11: return v11;
|
||||
case 12: return v12;
|
||||
case 13: return v13;
|
||||
case 14: return v14;
|
||||
case 15: return v15;
|
||||
case 16: return v16;
|
||||
case 17: return v17;
|
||||
case 18: return v18;
|
||||
case 19: return v19;
|
||||
case 20: return v20;
|
||||
case 21: return v21;
|
||||
case 22: return v22;
|
||||
case 23: return v23;
|
||||
case 24: return v24;
|
||||
case 25: return v25;
|
||||
case 26: return v26;
|
||||
case 27: return v27;
|
||||
case 28: return v28;
|
||||
case 29: return v29;
|
||||
case 30: return v30;
|
||||
case 31: return v31;
|
||||
}
|
||||
}
|
||||
|
||||
public void Set(uint[] arr)
|
||||
{
|
||||
Values = arr;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
@@ -459,7 +551,7 @@ namespace CodeWalker.GameFiles
|
||||
}
|
||||
ListParts = parts;
|
||||
ListOffsets = offsets.ToArray();
|
||||
|
||||
ItemCount = (uint)items.Count;
|
||||
}
|
||||
|
||||
|
||||
@@ -592,19 +684,21 @@ namespace CodeWalker.GameFiles
|
||||
|
||||
|
||||
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))] public struct NavMeshAdjPoly
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))] public struct NavMeshEdge
|
||||
{
|
||||
public NavMeshAdjPolyPart Unknown_0h { get; set; }
|
||||
public NavMeshAdjPolyPart Unknown_4h { get; set; }
|
||||
public NavMeshEdgePart _Poly1;
|
||||
public NavMeshEdgePart _Poly2;
|
||||
public NavMeshEdgePart Poly1 { get { return _Poly1; } set { _Poly1 = value; } }
|
||||
public NavMeshEdgePart Poly2 { get { return _Poly2; } set { _Poly2 = value; } }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return //Unknown_0h.Bin + " | " + Unknown_4h.Bin + " | " +
|
||||
Unknown_0h.ToString() + " | " + Unknown_4h.ToString();
|
||||
return //Poly1.Bin + " | " + Poly2.Bin + " | " +
|
||||
_Poly1.ToString() + " | " + _Poly2.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))] public struct NavMeshAdjPolyPart
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))] public struct NavMeshEdgePart
|
||||
{
|
||||
public uint Value { get; set; }
|
||||
|
||||
@@ -616,14 +710,15 @@ namespace CodeWalker.GameFiles
|
||||
}
|
||||
}
|
||||
|
||||
public uint AreaIDInd { get { return (Value >> 0) & 0x1F; } }
|
||||
public uint PolyID { get { return (Value >> 5) & 0x3FFF; } }
|
||||
public uint Unk2 { get { return (Value >> 19) & 0x3; } }
|
||||
public uint Unk3 { get { return (Value >> 21) & 0x7FF; } }
|
||||
public uint AreaIDInd { get { return (Value >> 0) & 0x1F; } set { Value = (Value & 0xFFFFFFE0) | (value & 0x1F); } }
|
||||
public uint PolyID { get { return (Value >> 5) & 0x3FFF; } set { Value = (Value & 0xFFF8001F) | ((value & 0x3FFF) << 5); } }
|
||||
public uint Unk2 { get { return (Value >> 19) & 0x3; } set { Value = (Value & 0xFFE7FFFF) | ((value & 0x3) << 19); } }
|
||||
public uint Unk3 { get { return (Value >> 21) & 0x7FF; } set { Value = (Value & 0x001FFFFF) | ((value & 0x7FF) << 21); } }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return AreaIDInd.ToString() + ", " + PolyID.ToString() + ", " + Unk2.ToString() + ", " + Unk3.ToString();
|
||||
string pid = (PolyID == 0x3FFF) ? "-" : PolyID.ToString();
|
||||
return AreaIDInd.ToString() + ", " + pid + ", " + Unk2.ToString() + ", " + Unk3.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -642,16 +737,16 @@ namespace CodeWalker.GameFiles
|
||||
public NavMeshAABB CellAABB { get; set; }
|
||||
public FlagsUint Unknown_24h { get; set; }
|
||||
public FlagsUint Unknown_28h { get; set; }
|
||||
public ushort PartFlags { get; set; }
|
||||
public ushort PortalLinkID { get; set; }
|
||||
public uint PartFlags { get; set; }
|
||||
|
||||
|
||||
//public int IndexUnk { get { return (IndexFlags >> 0) & 31; } } //always 0
|
||||
public int IndexCount { get { return (IndexFlags >> 5); } }
|
||||
public int IndexCount { get { return (IndexFlags >> 5); } set { IndexFlags = (ushort)((IndexFlags & 31) | ((value & 0x7FF) << 5)); } }
|
||||
|
||||
//public int PartUnk1 { get { return (PartFlags >> 0) & 0xF; } } //always 0
|
||||
public ushort PartID { get { return (ushort)((PartFlags >> 4) & 0xFF); } set { PartFlags = (ushort)((PartFlags & 0xF00F) | ((value & 0xFF) << 4)); } }
|
||||
public byte PortalType { get { return (byte)((PartFlags >> 12) & 0xF); } set { PartFlags = (ushort)((PartFlags & 0x0FFF) | ((value & 0xF) << 12)); } }
|
||||
public ushort PartID { get { return (ushort)((PartFlags >> 4) & 0xFF); } set { PartFlags = ((PartFlags & 0xFFFFF00F) | (((uint)value & 0xFF) << 4)); } }
|
||||
public byte PortalLinkCount { get { return (byte)((PartFlags >> 12) & 0x7); } set { PartFlags = ((PartFlags & 0xFFFF8FFF) | (((uint)value & 0x7) << 12)); } }
|
||||
public uint PortalLinkID { get { return ((PartFlags >> 15) & 0x1FFFF); } set { PartFlags = ((PartFlags & 0x7FFF) | ((value & 0x1FFFF) << 15)); } }
|
||||
|
||||
|
||||
public ushort Unknown_28h_16 { get { return (ushort)((Unknown_28h.Value & 0xFFFF)); } set { Unknown_28h = (Unknown_28h.Value & 0xFFFF0000) | (value & 0xFFFFu); } }
|
||||
@@ -672,7 +767,7 @@ namespace CodeWalker.GameFiles
|
||||
Unknown_28h.Hex + ", " +
|
||||
//PartFlags.ToString() + ", " + //PartUnk1.ToString() + ", " +
|
||||
PartID.ToString() + ", " +
|
||||
PortalType.ToString() + ", " +
|
||||
PortalLinkCount.ToString() + ", " +
|
||||
PortalLinkID.ToString();
|
||||
}
|
||||
}
|
||||
@@ -790,8 +885,8 @@ namespace CodeWalker.GameFiles
|
||||
public ushort[] PolyIDs { get; set; }
|
||||
public NavMeshPoint[] Points { get; set; }
|
||||
|
||||
private ResourceSystemStructBlock<ushort> PolyIDsBlock = null;
|
||||
private ResourceSystemStructBlock<NavMeshPoint> PointsBlock = null;
|
||||
public ResourceSystemStructBlock<ushort> PolyIDsBlock = null;
|
||||
public ResourceSystemStructBlock<NavMeshPoint> PointsBlock = null;
|
||||
|
||||
public override void Read(ResourceDataReader reader, params object[] parameters)
|
||||
{
|
||||
|
||||
@@ -626,6 +626,7 @@ namespace CodeWalker.GameFiles
|
||||
RpfFileEntry entry = CreateResourceFileEntry(ref data, 0);
|
||||
if ((data != null) && (entry != null))
|
||||
{
|
||||
data = ResourceBuilder.Decompress(data);
|
||||
file = new T();
|
||||
file.Load(data, entry);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user