CodeWalker/CodeWalker.Core/GameFiles/FileTypes/YndFile.cs
2019-11-01 20:30:29 +11:00

1401 lines
47 KiB
C#

using SharpDX;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
namespace CodeWalker.GameFiles
{
[TypeConverter(typeof(ExpandableObjectConverter))] public class YndFile : GameFile, PackedFile, BasePathData
{
public NodeDictionary NodeDictionary { get; set; }
public YndNode[] Nodes { get; set; }
public YndLink[] Links { get; set; }
public YndJunction[] Junctions { get; set; }
public EditorVertex[] LinkedVerts { get; set; }//populated by the space (needs to use grid of all ynd's!)
public EditorVertex[] TriangleVerts { get; set; } //used for junctions display
public Vector4[] NodePositions { get; set; }
public Vector3 BBMin { get; set; }
public Vector3 BBMax { get; set; }
public int CellX { get; set; }
public int CellY { get; set; }
public int AreaID
{
get
{
return CellY * 32 + CellX;
}
set
{
CellX = value % 32;
CellY = value / 32;
UpdateBoundingBox();
}
}
public PathBVH BVH { get; set; }
//fields used by the editor:
public bool HasChanged { get; set; } = false;
public List<string> SaveWarnings = null;
public bool BuildStructsOnSave { get; set; } = true;
public YndFile() : base(null, GameFileType.Ynd)
{
}
public YndFile(RpfFileEntry entry) : base(entry, GameFileType.Ynd)
{
}
public void Load(byte[] data)
{
//direct load from a raw, compressed ynd file (openIV-compatible format)
RpfFile.LoadResourceFile(this, data, 1);
Loaded = true;
LoadQueued = true;
}
public void Load(byte[] data, RpfFileEntry entry)
{
Name = entry.Name;
RpfFileEntry = entry;
RpfResourceFileEntry resentry = entry as RpfResourceFileEntry;
if (resentry == null)
{
throw new Exception("File entry wasn't a resource! (is it binary data?)");
}
ResourceDataReader rd = new ResourceDataReader(resentry, data);
NodeDictionary = rd.ReadBlock<NodeDictionary>();
InitNodesFromDictionary();
UpdateAllNodePositions();
//links will be populated by the space... maybe move that code here?
string areaidstr = Name.ToLowerInvariant().Replace("nodes", "").Replace(".ynd", "");
int areaid = 0;
int.TryParse(areaidstr, out areaid);
AreaID = areaid;
UpdateBoundingBox();
BuildBVH();
Loaded = true;
LoadQueued = true;
}
public byte[] Save()
{
if (BuildStructsOnSave)
{
BuildStructs();
}
byte[] data = ResourceBuilder.Build(NodeDictionary, 1); //ynd is version 1...
return data;
}
public void BuildStructs()
{
List<NodeLink> newlinks = new List<NodeLink>();
List<NodeJunction> newjuncs = new List<NodeJunction>();
List<NodeJunctionRef> newjuncrefs = new List<NodeJunctionRef>();
List<byte> newjuncheightmaps = new List<byte>();
if (Nodes != null)
{
int count = Nodes.Length;
var nodes = new Node[count];
for (int i = 0; i < count; i++)
{
var node = Nodes[i];
if (node.Links != null)
{
node.LinkCount = node.Links.Length;
node.LinkID = (ushort)newlinks.Count;
for (int l = 0; l < node.Links.Length; l++)
{
var nlink = node.Links[l];
var rlink = nlink.RawData;
if (nlink.Node2 != null)
{
rlink.AreaID = nlink.Node2.AreaID;
rlink.NodeID = nlink.Node2.NodeID;
}
newlinks.Add(rlink);
}
}
else
{
node.LinkCount = 0;
}
//LinkCount = node.LinkCountFlags.Value >> 3;
//LinkCountUnk = node.LinkCountFlags.Value & 7;
byte lcflags = (byte)((node.LinkCount << 3) + (node.LinkCountUnk & 7));
node._RawData.LinkCountFlags = lcflags;
nodes[i] = node.RawData;
if ((node.HasJunction) && (node.Junction != null))
{
var nj = node.Junction;
var heightmapoff = newjuncheightmaps.Count;
var heightmap = nj.Heightmap.GetBytes();
nj._RawData.HeightmapPtr = (ushort)heightmapoff;
var jref = nj.RefData;
jref.AreaID = node.AreaID;
jref.NodeID = node.NodeID;
jref.JunctionID = (ushort)newjuncs.Count;
//jref.Unk0 = 0;//always 0?
nj.RefData = jref;//move this somewhere else..??
newjuncs.Add(nj.RawData);
newjuncrefs.Add(jref);
newjuncheightmaps.AddRange(heightmap);
}
}
NodeDictionary.Nodes = nodes;
NodeDictionary.NodesCount = (uint)count;
NodeDictionary.Links = newlinks.ToArray();
NodeDictionary.LinksCount = (uint)newlinks.Count;
NodeDictionary.Junctions = newjuncs.ToArray();
NodeDictionary.JunctionsCount = (uint)newjuncs.Count;
NodeDictionary.JunctionRefs = newjuncrefs.ToArray();
NodeDictionary.JunctionRefsCount0 = (ushort)newjuncrefs.Count;
NodeDictionary.JunctionRefsCount1 = (ushort)newjuncrefs.Count;
NodeDictionary.JunctionHeightmapBytes = newjuncheightmaps.ToArray();
NodeDictionary.JunctionHeightmapBytesCount = (uint)newjuncheightmaps.Count;
}
else
{
NodeDictionary.Nodes = null;
NodeDictionary.NodesCount = 0;
NodeDictionary.Links = null;
NodeDictionary.LinksCount = 0;
NodeDictionary.Junctions = null;
NodeDictionary.JunctionsCount = 0;
NodeDictionary.JunctionRefs = null;
NodeDictionary.JunctionRefsCount0 = 0;
NodeDictionary.JunctionRefsCount1 = 0;
NodeDictionary.JunctionHeightmapBytes = null;
NodeDictionary.JunctionHeightmapBytesCount = 0;
}
}
public void InitNodesFromDictionary()
{
if (NodeDictionary != null)
{
if (NodeDictionary.Nodes != null)
{
var nodes = NodeDictionary.Nodes;
Nodes = new YndNode[nodes.Length];
for (int i = 0; i < nodes.Length; i++)
{
var n = new YndNode();
n.Init(this, nodes[i]);
Nodes[i] = n;
if (n.NodeID != i)
{ } //never hit here - nodeid's have to match the index!
}
}
if ((NodeDictionary.JunctionRefs != null) && (NodeDictionary.Junctions != null))
{
var juncrefs = NodeDictionary.JunctionRefs;
var juncs = NodeDictionary.Junctions;
Junctions = new YndJunction[juncrefs.Length];
for (int i = 0; i < juncrefs.Length; i++)
{
var juncref = juncrefs[i];
if (juncref.JunctionID >= juncs.Length)
{ continue; }
var j = new YndJunction();
j.Init(this, juncs[juncref.JunctionID], juncref);
j.Heightmap = new YndJunctionHeightmap(NodeDictionary.JunctionHeightmapBytes, j);
Junctions[i] = j;
}
}
}
}
public YndNode AddNode()
{
int cnt = Nodes?.Length ?? 0;
YndNode yn = new YndNode();
Node n = new Node();
n.AreaID = (ushort)AreaID;
n.NodeID = (ushort)cnt;
yn.Init(this, n);
int ncnt = cnt + 1;
YndNode[] nnodes = new YndNode[ncnt];
for (int i = 0; i < cnt; i++)
{
nnodes[i] = Nodes[i];
}
nnodes[cnt] = yn;
Nodes = nnodes;
NodeDictionary.NodesCount = (uint)ncnt;
return yn;
}
public bool RemoveNode(YndNode node)
{
List<YndNode> newnodes = new List<YndNode>();
int cnt = Nodes?.Length ?? 0;
bool r = false;
int ri = -1;
for (int i = 0; i < cnt; i++)
{
var tn = Nodes[i];
if (tn != node)
{
newnodes.Add(tn);
}
else
{
r = true;
ri = i;
}
}
Nodes = newnodes.ToArray();
NodeDictionary.NodesCount = (uint)newnodes.Count;
NodeDictionary.NodesCountVehicle = Math.Min(NodeDictionary.NodesCountVehicle, NodeDictionary.NodesCount);
NodeDictionary.NodesCountPed = Math.Min(NodeDictionary.NodesCountPed, NodeDictionary.NodesCount);
//remap node ID's...
List<YndLink> remlinks = new List<YndLink>();
if (ri >= 0)
{
for (int i = 0; i < Nodes.Length; i++)
{
var n = Nodes[i];
if (n.NodeID != i)
{
n.NodeID = (ushort)i;
//update nodeid's in links...
for (int j = 0; j < Nodes.Length; j++)
{
var tn = Nodes[j];
if ((tn != null) && (tn.Links != null))
{
for (int bl = 0; bl < tn.Links.Length; bl++)
{
var backlink = tn.Links[bl];
if (backlink.Node2 == n)
{
backlink._RawData.NodeID = (ushort)i;
}
}
}
}
}
//remove any links referencing the node.
remlinks.Clear();
if (n.Links != null)
{
for (int l = 0; l < n.Links.Length; l++)
{
var nlink = n.Links[l];
if (nlink.Node2 == node)
{
remlinks.Add(nlink);
}
}
for (int l = 0; l < remlinks.Count; l++)
{
n.RemoveLink(remlinks[l]);
}
}
}
}
UpdateAllNodePositions();
return r;
}
public void UpdateBoundingBox()
{
Vector3 corner = new Vector3(-8192, -8192, -2048);
Vector3 cellsize = new Vector3(512, 512, 4096);
BBMin = corner + (cellsize * new Vector3(CellX, CellY, 0));
BBMax = BBMin + cellsize;
}
public void UpdateAllNodePositions()
{
int cnt = Nodes?.Length ?? 0;
if (cnt <= 0)
{
NodePositions = null;
return;
}
var np = new Vector4[cnt];
for (int i = 0; i < cnt; i++)
{
np[i] = new Vector4(Nodes[i].Position, 1.0f);
}
NodePositions = np;
}
public void UpdateTriangleVertices()
{
//note: called from space.BuildYndVerts()
UpdateLinkTriangleVertices();
//UpdateJunctionTriangleVertices();
}
private void UpdateLinkTriangleVertices()
{
//build triangles for the path links display
int vc = 0;
if (Links != null)
{
vc = Links.Length * 6;
}
List<EditorVertex> verts = new List<EditorVertex>(vc);
EditorVertex v0 = new EditorVertex();
EditorVertex v1 = new EditorVertex();
EditorVertex v2 = new EditorVertex();
EditorVertex v3 = new EditorVertex();
if ((Links != null) && (Nodes != null))
{
foreach (var node in Nodes)
{
foreach (var link in node.Links)
{
var p0 = link.Node1?.Position ?? Vector3.Zero;
var p1 = link.Node2?.Position ?? Vector3.Zero;
var diff = p1 - p0;
var dir = Vector3.Normalize(diff);
var ax = Vector3.Cross(dir, Vector3.UnitZ);
float lanestot = link.LaneCountForward + link.LaneCountBackward;
//float backfrac = Math.Min(Math.Max(link.LaneCountBackward / lanestot, 0.1f), 0.9f);
//float lanewidth = 7.0f;
//float inner = totwidth*(backfrac-0.5f);
//float outer = totwidth*0.5f;
float lanewidth = node.IsPedNode ? 0.5f : 5.5f;
float inner = link.LaneOffset * lanewidth;// 0.0f;
float outer = inner + Math.Max(lanewidth * link.LaneCountForward, 0.5f);
float totwidth = lanestot * lanewidth;
float halfwidth = totwidth * 0.5f;
if (link.LaneCountBackward == 0)
{
inner -= halfwidth;
outer -= halfwidth;
}
if (link.LaneCountForward == 0)
{
inner += halfwidth;
outer += halfwidth;
}
v0.Position = p1 + ax * inner;
v1.Position = p0 + ax * inner;
v2.Position = p1 + ax * outer;
v3.Position = p0 + ax * outer;
var c = (uint)link.GetColour().ToRgba();
v0.Colour = c;
v1.Colour = c;
v2.Colour = c;
v3.Colour = c;
verts.Add(v0);
verts.Add(v1);
verts.Add(v2);
verts.Add(v2);
verts.Add(v1);
verts.Add(v3);
}
}
}
if (verts.Count > 0)
{
TriangleVerts = verts.ToArray();
}
else
{
TriangleVerts = null;
}
}
private void UpdateJunctionTriangleVertices()
{
//build triangles for the junctions bytes display....
int vc = 0;
if (Junctions != null)
{
foreach (var j in Junctions)
{
var d = j.Heightmap;
if (d == null) continue;
vc += d.CountX * d.CountY * 6;
}
}
List<EditorVertex> verts = new List<EditorVertex>(vc);
EditorVertex v0 = new EditorVertex();
EditorVertex v1 = new EditorVertex();
EditorVertex v2 = new EditorVertex();
EditorVertex v3 = new EditorVertex();
if (Nodes != null)
{
foreach (var node in Nodes)
{
if (node.Junction == null) continue;
var j = node.Junction;
var d = j.Heightmap;
if (d == null) continue;
float maxz = j.MaxZ / 32.0f;
float minz = j.MinZ / 32.0f;
float rngz = maxz - minz;
float posx = j.PositionX / 4.0f;
float posy = j.PositionY / 4.0f;
Vector3 pos = new Vector3(posx, posy, 0.0f);
Vector3 siz = new Vector3(d.CountX, d.CountY, 0.0f) * 2.0f;
//Vector3 siz = new Vector3(jx, jy, 0.0f);
Vector3 cnr = pos;// - siz * 0.5f;
//Vector3 inc = new Vector3(1.0f/jx)
cnr.Z = minz;// + 2.0f;
for (int y = 1; y < d.CountY; y++) //rows progress up the Y axis.
{
var row0 = d.Rows[y - 1];
var row1 = d.Rows[y];
float offy = y * 2.0f;
for (int x = 1; x < d.CountX; x++) //values progress along the X axis.
{
var val0 = row0.Values[x - 1] / 255.0f;
var val1 = row0.Values[x] / 255.0f;
var val2 = row1.Values[x - 1] / 255.0f;
var val3 = row1.Values[x] / 255.0f;
float offx = x * 2.0f;
v0.Position = cnr + new Vector3(offx - 2.0f, offy - 2.0f, val0 * rngz);
v1.Position = cnr + new Vector3(offx + 0.0f, offy - 2.0f, val1 * rngz);
v2.Position = cnr + new Vector3(offx - 2.0f, offy + 0.0f, val2 * rngz);
v3.Position = cnr + new Vector3(offx + 0.0f, offy + 0.0f, val3 * rngz);
v0.Colour = (uint)new Color4(val0, 1.0f - val0, 0.0f, 1.0f).ToRgba();
v1.Colour = (uint)new Color4(val1, 1.0f - val1, 0.0f, 1.0f).ToRgba();
v2.Colour = (uint)new Color4(val2, 1.0f - val2, 0.0f, 1.0f).ToRgba();
v3.Colour = (uint)new Color4(val3, 1.0f - val3, 0.0f, 1.0f).ToRgba();
verts.Add(v0);
verts.Add(v1);
verts.Add(v2);
verts.Add(v2);
verts.Add(v1);
verts.Add(v3);
}
}
}
}
if (verts.Count > 0)
{
TriangleVerts = verts.ToArray();
}
else
{
TriangleVerts = null;
}
}
public void UpdateBvhForNode(YndNode node)
{
//this needs to be called when a node's position changes...
//if it changes a lot, need to recalc the BVH for mouse intersection optimisation purposes.
//if (BVH == null) return;
//BVH.UpdateForNode(node);
BuildBVH();
//also updates the NodePositions for the visible vertex
if (Nodes != null)
{
for (int i = 0; i < Nodes.Length; i++)
{
if (Nodes[i] == node)
{
NodePositions[i] = new Vector4(node.Position, 1.0f);
break;
}
}
}
}
public void BuildBVH()
{
BVH = new PathBVH(Nodes, 10, 10);
}
public EditorVertex[] GetPathVertices()
{
return LinkedVerts;
}
public EditorVertex[] GetTriangleVertices()
{
return TriangleVerts;
}
public Vector4[] GetNodePositions()
{
return NodePositions;
}
public override string ToString()
{
return RpfFileEntry?.ToString() ?? string.Empty;
}
}
[TypeConverter(typeof(ExpandableObjectConverter))] public class YndNode : BasePathNode
{
public Node _RawData;
public YndFile Ynd { get; set; }
public Node RawData { get { return _RawData; } set { _RawData = value; } }
public Vector3 Position { get; set; }
public int LinkCount { get; set; }
public int LinkCountUnk { get; set; }
public YndLink[] Links { get; set; }
public ushort AreaID { get { return _RawData.AreaID; } set { _RawData.AreaID = value; } }
public ushort NodeID { get { return _RawData.NodeID; } set { _RawData.NodeID = value; } }
public ushort LinkID { get { return _RawData.LinkID; } set { _RawData.LinkID = value; } }
public FlagsByte Flags0 { get { return _RawData.Flags0; } set { _RawData.Flags0 = value; } }
public FlagsByte Flags1 { get { return _RawData.Flags1; } set { _RawData.Flags1 = value; } }
public FlagsByte Flags2 { get { return _RawData.Flags2; } set { _RawData.Flags2 = value; } }
public FlagsByte Flags3 { get { return _RawData.Flags3; } set { _RawData.Flags3 = value; } }
public FlagsByte Flags4 { get { return _RawData.Flags4; } set { _RawData.Flags4 = value; } }
public TextHash StreetName { get { return _RawData.StreetName; } set { _RawData.StreetName = value; } }
public Color4 Colour { get; set; }
public YndJunction Junction { get; set; }
public bool HasJunction;
public bool IsPedNode
{
get
{
return false;// ((Flags4.Value >> 4) & 7) == 7;
}
}
public void Init(YndFile ynd, Node node)
{
Ynd = ynd;
RawData = node;
Vector3 p = new Vector3();
p.X = node.PositionX / 4.0f;
p.Y = node.PositionY / 4.0f;
p.Z = node.PositionZ / 32.0f;
Position = p;
LinkCount = node.LinkCountFlags.Value >> 3;
LinkCountUnk = node.LinkCountFlags.Value & 7;
Colour = GetColour();
}
public Color4 GetColour()
{
Color4 c = new Color4(LinkCountUnk / 7.0f, Flags0.Value / 255.0f, Flags1.Value / 255.0f, 1.0f);
//Color4 c = new Color4(0.0f, 0.0f, 0.0f, 1.0f);
//c.Red = (LinkCountUnk >> 1) / 3.0f;
//c.Red = (Flags3.Value >> 1) / 127.0f;
//c.Red = ((Flags4.Value >> 1) & 7) / 7.0f; //density value?
//c.Green = 1.0f - c.Red;
//if ((Flags0.Value & 1) > 0) c.Red += 1.0f; //script activated? N Yankton only + small piece in self storage
//if ((Flags0.Value & 2) > 0) c.Red += 1.0f; //car can use / gps?
//if ((Flags0.Value & 4) > 0) c.Red += 1.0f; //***not used
//if ((Flags0.Value & 8) > 0) c.Red += 1.0f; //gravel surface? country roads mostly
//if ((Flags0.Value & 16) > 0) c.Red += 1.0f; //***not used
//if ((Flags0.Value & 32) > 0) c.Red += 1.0f; //slow speed? hills roads, prison boundary, carparks, airport roads etc
//if ((Flags0.Value & 64) > 0) c.Red += 1.0f; //intersection entry 1 (has priority)?
//if ((Flags0.Value & 128) > 0) c.Green += 1.0f; //intersection entry 2 unk?
//if ((Flags1.Value & 1) > 0) c.Red += 1.0f; //left turn lane?
//if ((Flags1.Value & 2) > 0) c.Red += 1.0f; //left turn node of no return
//if ((Flags1.Value & 4) > 0) c.Red += 1.0f; //right turn node of no return
//if ((Flags1.Value & 8) > 0) c.Red += 1.0f; //entry for traffic lights / boom gates etc
//if ((Flags1.Value & 16) > 0) c.Red += 1.0f; //entry for traffic lights / boom gates etc + peds crossing
//if ((Flags1.Value & 32) > 0) c.Red += 1.0f; //intersection entry 3 unk?
//if ((Flags1.Value & 64) > 0) c.Red += 1.0f; //entry for traffic lights / boom gates etc + peds crossing
//if ((Flags1.Value & 128) > 0) c.Red += 1.0f; //intersection minor/stop, T?
////[16 bits pos Z here]
//if ((Flags2.Value & 1) > 0) c.Red += 1.0f; //slow traffic? peds? carparks? military? GPS disable routing??
//if ((Flags2.Value & 2) > 0) c.Red += 1.0f; //***not used
//if ((Flags2.Value & 4) > 0) c.Red += 1.0f; //intersection decision?
//if ((Flags2.Value & 8) > 0) c.Red += 1.0f; //***not used
//if ((Flags2.Value & 16) > 0) c.Red += 1.0f; //slower traffic?
//if ((Flags2.Value & 32) > 0) c.Red += 1.0f; //water/boat
//if ((Flags2.Value & 64) > 0) c.Red += 1.0f; //freeways /peds?
//if ((Flags2.Value & 128) > 0) c.Red += 1.0f; //not a main road...?
//if ((LinkCountUnk & 1) > 0) c.Red += 1.0f; //has junction heightmap
//if ((LinkCountUnk & 2) > 0) c.Red += 1.0f; //speed/density/type related? not runways, not freeways
//if ((LinkCountUnk & 4) > 0) c.Red += 1.0f; //higher speed? eg freeway
////[5 bits LinkCount here]
//if ((Flags3.Value & 1) > 0) c.Red += 1.0f; //is in an interior
//if ((Flags3.Value & 2) > 0) c.Red += 1.0f; //heuristic val?
//if ((Flags3.Value & 4) > 0) c.Red += 1.0f; //heuristic val?
//if ((Flags3.Value & 8) > 0) c.Red += 1.0f; //heuristic val?
//if ((Flags3.Value & 16) > 0) c.Red += 1.0f; //heuristic val?
//if ((Flags3.Value & 32) > 0) c.Red += 1.0f; //heuristic val?
//if ((Flags3.Value & 64) > 0) c.Red += 1.0f; //heuristic val?
//if ((Flags3.Value & 128) > 0) c.Red += 1.0f; //heuristic val?
//if ((Flags4.Value & 1) > 0) c.Red += 1.0f; //slow traffic?
//if ((Flags4.Value & 2) > 0) c.Red += 1.0f; //density/popgroup value..?
//if ((Flags4.Value & 4) > 0) c.Green += 1.0f; //density/popgroup value..?
//if ((Flags4.Value & 8) > 0) c.Blue += 1.0f; //density/popgroup value..?
//if ((Flags4.Value & 16) > 0) c.Red += 1.0f; //special/peds path?
//if ((Flags4.Value & 32) > 0) c.Green += 1.0f; //special/peds path?
//if ((Flags4.Value & 64) > 0) c.Blue += 1.0f; //special/peds path?
//if ((Flags4.Value & 128) > 0) c.Blue += 1.0f; //intersection entry left turn?
////regarding paths.xml:
////rubidium - Today at 8:37 AM
//also, quick glimpse over the xml for attributes:
////> grep - i "attribute name" paths.xml | awk - F'^"' ' { print $2 }' | sort - u
//Block If No Lanes
//Cannot Go Left
//Cannot Go Right
//Density
//Disabled
//Dont Use For Navigation
//GpsBothWays
//Highway
//Indicate Keep Left
//Indicate Keep Right
//Lanes In
//Lanes Out
//Left Turns Only
//Narrowroad
//No Big Vehicles
//NoGps
//Off Road
//Shortcut
//Slip Lane
//Special
//Speed
//Streetname
//Tunnel
//Water
//Width
return c;
}
public void SetPosition(Vector3 pos)
{
_RawData.PositionX = (short)(pos.X * 4.0f);
_RawData.PositionY = (short)(pos.Y * 4.0f);
_RawData.PositionZ = (short)(pos.Z * 32.0f);
Vector3 newpos = pos;
//newpos.X = _RawData.PositionX / 4.0f;
//newpos.Y = _RawData.PositionY / 4.0f;
//newpos.Z = _RawData.PositionZ / 32.0f;
Position = newpos;
UpdateLinkLengths();
}
public void UpdateLinkLengths()
{
if (Links == null) return;
for (int i = 0; i < Links.Length; i++)
{
var link = Links[i];
link.UpdateLength(); //update this node's links
var n2 = link.Node2;
if ((n2 == null) || (n2.Links == null)) continue;
for (int j = 0; j < n2.Links.Length; j++)
{
var n2l = n2.Links[j];
if (n2l.Node2 == this)
{
n2l.UpdateLength(); //update back links
}
}
}
}
public YndLink AddLink(YndNode tonode = null)
{
YndLink l = new YndLink();
l._RawData.AreaID = AreaID;
l.Node1 = this;
if (tonode != null)
{
l.Node2 = tonode;
l._RawData.AreaID = tonode.AreaID;
l._RawData.NodeID = tonode.NodeID;
}
else if ((Ynd.Nodes != null) && (Ynd.Nodes.Length > 0))
{
l.Node2 = Ynd.Nodes[0];
}
else
{
l.Node2 = this;
l._RawData.NodeID = NodeID;
}
l.UpdateLength();
int cnt = Links?.Length ?? 0;
int ncnt = cnt + 1;
YndLink[] nlinks = new YndLink[ncnt];
for (int i = 0; i < cnt; i++)
{
nlinks[i] = Links[i];
}
nlinks[cnt] = l;
Links = nlinks;
LinkCount = ncnt;
return l;
}
public bool RemoveLink(YndLink l)
{
List<YndLink> newlinks = new List<YndLink>();
int cnt = Links?.Length ?? 0;
bool r = false;
for (int i = 0; i < cnt; i++)
{
var tl = Links[i];
if (tl != l)
{
newlinks.Add(tl);
}
else
{
r = true;
}
}
Links = newlinks.ToArray();
LinkCount = newlinks.Count;
return r;
}
public override string ToString()
{
//return AreaID.ToString() + "." + NodeID.ToString();
return StreetName.ToString() + ", " + Position.X.ToString() + ", " + Position.Y.ToString() + ", " + Position.Z.ToString() + ", " + NodeID.ToString();
}
}
[TypeConverter(typeof(ExpandableObjectConverter))] public class YndLink
{
public YndFile Ynd { get; set; }
public YndNode Node1 { get; set; }
public YndNode Node2 { get; set; }
public NodeLink _RawData;
public NodeLink RawData { get { return _RawData; } set { _RawData = value; } }
public FlagsByte Flags0 { get { return _RawData.Flags0; } set { _RawData.Flags0 = value; } }
public FlagsByte Flags1 { get { return _RawData.Flags1; } set { _RawData.Flags1 = value; } }
public FlagsByte Flags2 { get { return _RawData.Flags2; } set { _RawData.Flags2 = value; } }
public FlagsByte LinkLength { get { return _RawData.LinkLength; } set { _RawData.LinkLength = value; } }
public int LaneCountForward { get { return (Flags2.Value >> 5) & 7; } }
public int LaneCountBackward { get { return (Flags2.Value >> 2) & 7; } }
public int OffsetValue { get { return (Flags1.Value >> 4) & 7; } }
public bool NegativeOffset { get { return (Flags1.Value >> 7) > 0; } }
public float LaneOffset { get { return (OffsetValue / 7.0f) * (NegativeOffset ? -0.5f : 0.5f); } }
public void Init(YndFile ynd, YndNode node1, YndNode node2, NodeLink link)
{
Ynd = ynd;
Node1 = node1;
Node2 = node2;
RawData = link;
}
public void UpdateLength()
{
if (Node1 == null) return;
if (Node2 == null) return;
LinkLength = (byte)Math.Min(255, (Node2.Position - Node1.Position).Length());
}
public void CopyFlags(YndLink link)
{
if (link == null) return;
Flags0 = link.Flags0;
Flags1 = link.Flags1;
Flags2 = link.Flags2;
}
public Color4 GetColour()
{
//float f0 = Flags0.Value / 255.0f;
//float f1 = Flags1.Value / 255.0f;
//float f2 = Flags2.Value / 255.0f;
//var c = new Color4(f0, f1, f2, 1.0f);
var c = new Color4(0.0f, 0.0f, 0.0f, 0.5f);
c.Green = LaneCountForward / 7.0f;
c.Red = LaneCountBackward / 7.0f;
//if ((Flags0.Value & 1) > 0) c.Red = 1.0f; //? some small pieces in city, roads at docks, and mall at beach
//if ((Flags0.Value & 2) > 0) c.Red = 1.0f; //3x segments joining east canal paths to roads, also josh's driveway - scripted?
//if ((Flags0.Value & 4) > 0) c.Red = 1.0f; //? looks fairly random, 0 for water, alternating - slope related?
//if ((Flags0.Value & 8) > 0) c.Red = 1.0f; //? like above
//if ((Flags0.Value & 16) > 0) c.Red = 1.0f; //? similar to above, but less
//if ((Flags0.Value & 32) > 0) c.Red = 1.0f; //? like above
//if ((Flags0.Value & 64) > 0) c.Red = 1.0f; //? slightly less random
//if ((Flags0.Value & 128) > 0) c.Red = 1.0f; //? still looks random...
//if ((Flags1.Value & 1) > 0) c.Red = 1.0f; //***not used?
//if ((Flags1.Value & 2) > 0) c.Red = 1.0f; //?possibly width/type bit
//if ((Flags1.Value & 4) > 0) c.Red = 1.0f; //avoid routing? no through road / no other exit?
//if ((Flags1.Value & 8) > 0) c.Red = 1.0f; //prefer routing? exit from dead end?
//if ((Flags1.Value & 16) > 0) c.Red = 1.0f; //offset value. mostly single lane, carpark access, golf course, alleyways, driveways, beach area etc
//if ((Flags1.Value & 32) > 0) c.Green = 1.0f; //offset value. similar to above
//if ((Flags1.Value & 64) > 0) c.Green = 1.0f; //offset value. similar to above
//if ((Flags1.Value & 128) > 0) c.Red = 1.0f; //offset value. (sign) similar to above (all paired with their back links!)
//if ((Flags2.Value & 1) > 0) c.Red = 1.0f; //angled link - merge? enter/exit divided road section, most big junctions, always paired
//if ((Flags2.Value & 2) > 0) c.Red = 1.0f; //lane change/u-turn link? always paired
//if ((Flags2.Value & 4) > 0) c.Red = 1.0f; //lane count back dir
//if ((Flags2.Value & 8) > 0) c.Red = 1.0f; //lane count back dir
//if ((Flags2.Value & 16) > 0) c.Red = 1.0f; //lane count back dir
//if ((Flags2.Value & 32) > 0) c.Green = 1.0f; //lane count forward dir
//if ((Flags2.Value & 64) > 0) c.Green = 1.0f; //lane count forward dir
//if ((Flags2.Value & 128) > 0) c.Green = 1.0f; //lane count forward dir
////var lanesfwd = (Flags2.Value >> 5) & 7;
////var lanesbck = (Flags2.Value >> 2) & 7;
//////if ((lanesfwd > 0) && (lanesbck > 0) && (lanesfwd != lanesbck))
//////{ }
//var t = (Flags1.Value >> 4)&1;
//c.Red = t / 1.0f;
//c.Green = 1.0f - c.Red;
////if (((Flags1.Value & 128) > 0))// && ((Flags1.Value & 64) == 0))
////{ c.Red += 1.0f; }
return c;
}
public override string ToString()
{
return Node2._RawData.ToString();
}
}
[TypeConverter(typeof(ExpandableObjectConverter))] public class YndJunction
{
public YndFile Ynd { get; set; }
public NodeJunction _RawData;
public NodeJunction RawData { get { return _RawData; } set { _RawData = value; } }
public NodeJunctionRef RefData { get; set; }
public YndJunctionHeightmap Heightmap { get; set; }
public short MaxZ { get; set; }
public short MinZ { get; set; }
public short PositionX { get; set; }
public short PositionY { get; set; }
public void Init(YndFile ynd, NodeJunction junc, NodeJunctionRef reff)
{
Ynd = ynd;
RawData = junc;
RefData = reff;
MaxZ = junc.MaxZ;
MinZ = junc.MinZ;
PositionX = junc.PositionX;
PositionY = junc.PositionY;
}
public void ResizeHeightmap()
{
Heightmap.Resize(_RawData.HeightmapDimX, _RawData.HeightmapDimY);
}
public void SetHeightmap(string text)
{
Heightmap.SetData(text);
}
public override string ToString()
{
return RefData.ToString();
}
}
[TypeConverter(typeof(ExpandableObjectConverter))] public class YndJunctionHeightmap
{
public YndJunctionHeightmapRow[] Rows { get; set; }
public int CountX { get; set; }
public int CountY { get; set; }
public YndJunctionHeightmap(byte[] data, YndJunction junc)
{
if (data == null)
{ return; }
var d = junc.RawData;
int s = d.HeightmapPtr;
CountX = d.HeightmapDimX;
CountY = d.HeightmapDimY;
if ((s + CountX * CountY) > data.Length)
{ return; }
Rows = new YndJunctionHeightmapRow[CountY];
for (int y = 0; y < CountY; y++)
{
int o = s + y * CountX;
byte[] vals = new byte[CountX];
Buffer.BlockCopy(data, o, vals, 0, CountX);
Rows[y] = new YndJunctionHeightmapRow(vals);
}
}
public byte[] GetBytes()
{
int cnt = CountX * CountY;
var bytes = new byte[cnt];
for (int y = 0; y < CountY; y++)
{
int o = y * CountX;
var rowvals = Rows[y].Values;
Buffer.BlockCopy(rowvals, 0, bytes, o, CountX);
}
return bytes;
}
public void Resize(int cx, int cy)
{
var nrows = new YndJunctionHeightmapRow[cy];
for (int y = 0; y < cy; y++)
{
byte[] nvals = new byte[cx];
int minx = Math.Min(cx, CountX);
if ((Rows != null) && (y < Rows.Length))
{
Buffer.BlockCopy(Rows[y].Values, 0, nvals, 0, minx);
}
nrows[y] = new YndJunctionHeightmapRow(nvals);
}
Rows = nrows;
CountX = cx;
CountY = cy;
}
public void SetData(string text)
{
var rsplit = new[] { '\n' };
var csplit = new[] { ' ' };
string[] rowstrs = text.Split(rsplit, StringSplitOptions.RemoveEmptyEntries);
for (int y = 0; y < rowstrs.Length; y++)
{
string[] colstrs = rowstrs[y].Trim().Split(csplit, StringSplitOptions.RemoveEmptyEntries);
int cx = colstrs.Length;
byte[] vals = new byte[cx];
for (int x = 0; x < cx; x++)
{
byte.TryParse(colstrs[x], out vals[x]);
}
int minx = Math.Min(cx, CountX);
if ((Rows != null) && (y < Rows.Length))
{
var nrow = new byte[CountX];
Buffer.BlockCopy(vals, 0, nrow, 0, minx);
Rows[y].Values = nrow;
}
}
}
public string GetDataString()
{
StringBuilder sb = new StringBuilder();
if (Rows != null)
{
foreach (var row in Rows)
{
sb.AppendLine(row.ToString());
}
}
return sb.ToString();
}
public override string ToString()
{
return CountX.ToString() + " x " + CountY.ToString();
}
}
[TypeConverter(typeof(ExpandableObjectConverter))] public class YndJunctionHeightmapRow
{
public byte[] Values { get; set; }
public YndJunctionHeightmapRow(byte[] vals)
{
Values = vals;
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < Values.Length; i++)
{
if (i > 0) sb.Append(" ");
sb.Append(Values[i].ToString().PadLeft(3, '0'));
//sb.Append(Convert.ToString(Values[i], 16).ToUpper().PadLeft(2, '0'));
}
return sb.ToString();
}
}
public class PathBVHNode
{
public int Depth;
public int MaxDepth;
public int Threshold;
public List<BasePathNode> Nodes;
public BoundingBox Box;
public BoundingSphere Sphere;
public PathBVHNode Node1;
public PathBVHNode Node2;
public void CalcBounds()
{
if ((Nodes == null) || (Nodes.Count <= 0)) return;
Box.Minimum = new Vector3(float.MaxValue);
Box.Maximum = new Vector3(float.MinValue);
foreach (var node in Nodes)
{
Box.Minimum = Vector3.Min(Box.Minimum, node.Position);
Box.Maximum = Vector3.Max(Box.Maximum, node.Position);
}
Sphere.Center = (Box.Minimum + Box.Maximum) * 0.5f;
Sphere.Radius = (Box.Maximum - Box.Minimum).Length() * 0.5f;
}
public void Build()
{
if ((Nodes == null) || (Nodes.Count <= Threshold) || (Depth >= MaxDepth)) return;
Vector3 avgsum = Vector3.Zero;
foreach (var node in Nodes)
{
avgsum += node.Position;
}
Vector3 avg = avgsum * (1.0f / Nodes.Count);
int countx = 0, county = 0, countz = 0;
foreach (var node in Nodes)
{
if (node.Position.X < avg.X) countx++;
if (node.Position.Y < avg.Y) county++;
if (node.Position.Z < avg.Z) countz++;
}
int target = Nodes.Count / 2;
int dx = Math.Abs(target - countx);
int dy = Math.Abs(target - county);
int dz = Math.Abs(target - countz);
int axis = -1;
if ((dx <= dy) && (dx <= dz)) axis = 0; //x seems best
else if (dy <= dz) axis = 1; //y seems best
else axis = 2; //z seems best
List<BasePathNode> l1 = new List<BasePathNode>();
List<BasePathNode> l2 = new List<BasePathNode>();
foreach (var node in Nodes)
{
bool s = false;
switch (axis)
{
default:
case 0: s = (node.Position.X > avg.X); break;
case 1: s = (node.Position.Y > avg.Y); break;
case 2: s = (node.Position.Z > avg.Z); break;
}
if (s) l1.Add(node);
else l2.Add(node);
}
var cdepth = Depth + 1;
Node1 = new PathBVHNode();
Node1.Depth = cdepth;
Node1.MaxDepth = MaxDepth;
Node1.Threshold = Threshold;
Node1.Nodes = new List<BasePathNode>(l1);
Node1.CalcBounds();
Node1.Build();
Node2 = new PathBVHNode();
Node2.Depth = cdepth;
Node2.MaxDepth = MaxDepth;
Node2.Threshold = Threshold;
Node2.Nodes = new List<BasePathNode>(l2);
Node2.CalcBounds();
Node2.Build();
}
public void UpdateForNode(BasePathNode node)
{
if (!Nodes.Contains(node)) return;
Box.Minimum = Vector3.Min(Box.Minimum, node.Position);
Box.Maximum = Vector3.Max(Box.Maximum, node.Position);
if (Node1 != null) Node1.UpdateForNode(node);
if (Node2 != null) Node2.UpdateForNode(node);
}
}
public class PathBVH : PathBVHNode
{
public PathBVH(IEnumerable<BasePathNode> nodes, int threshold, int maxdepth)
{
Threshold = threshold;
MaxDepth = maxdepth;
Nodes = (nodes != null) ? new List<BasePathNode>(nodes) : new List<BasePathNode>();
CalcBounds();
Build();
}
}
public interface BasePathNode
{
Vector3 Position { get; set; }
}
public interface BasePathData
{
//reuse this interface for file types that need to get paths rendered...
EditorVertex[] GetPathVertices();
EditorVertex[] GetTriangleVertices();
Vector4[] GetNodePositions();
}
public class YndXml : MetaXmlBase
{
public static string GetXml(YndFile ynd)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(XmlHeader);
if ((ynd != null) && (ynd.NodeDictionary != null))
{
var name = "NodeDictionary";
OpenTag(sb, 0, name);
ynd.NodeDictionary.WriteXml(sb, 1);
CloseTag(sb, 0, name);
}
return sb.ToString();
}
}
public class XmlYnd
{
public static YndFile GetYnd(string xml)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);
return GetYnd(doc);
}
public static YndFile GetYnd(XmlDocument doc)
{
YndFile ynd = new YndFile();
ynd.NodeDictionary = new NodeDictionary();
ynd.NodeDictionary.ReadXml(doc.DocumentElement);
ynd.InitNodesFromDictionary();
ynd.BuildStructsOnSave = false; //structs don't need to be rebuilt here!
return ynd;
}
public static TextHash GetTextHash(string str)
{
if (string.IsNullOrEmpty(str))
{
return 0;
}
if (str.StartsWith("hash_"))
{
return Convert.ToUInt32(str.Substring(5), 16);
}
else
{
uint h = GlobalText.TryFindHash(str);
if (h != 0)
{
return h;
}
return JenkHash.GenHash(str);
}
}
}
}