Optimizations and refactoring

Added pooling to most of the Space Init functions (BVH Builders, etc)

Added multi threading to search world and Space Init functions
This commit is contained in:
Niek Schoemaker 2024-01-08 05:00:55 +01:00
parent ed68bd59fd
commit da3dc2f8f3
No known key found for this signature in database
GPG Key ID: BDF9404CFECB0006
56 changed files with 2412 additions and 1768 deletions

View File

@ -8,11 +8,14 @@ using CodeWalker.Core.Utils;
using CodeWalker.GameFiles;
using Collections.Pooled;
using CommunityToolkit.HighPerformance;
using Iced.Intel;
using SharpDX;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
@ -127,7 +130,7 @@ namespace CodeWalker.Benchmarks
}
}
[Params(10, 100, 1000)]
[Params(1000)]
public int Length { get; set; } = 10000;
[Params(100)]
@ -147,10 +150,14 @@ namespace CodeWalker.Benchmarks
private int randomValue;
private uint[] ushorts = new uint[0];
private byte[] bytes = new byte[16];
private string Str = "iakslgbhfibnrihbderpiugaehigoI BIHGVUIVDSOUFVBOUADGBOIUYfgiuywetrg872q13rh9872`134tgyihsbaopuJGUIYODGBFIOUFgvbouailksdbhnfp";
private char[] chars1 = new char[0];
private Vector3 WorldPos;
[GlobalSetup]
public void Setup()
{
@ -159,19 +166,39 @@ namespace CodeWalker.Benchmarks
valueByte = 0;
valueUint = 0;
ushorts = new uint[Length];
Str = "";
for (int i = 0; i < Length; i++)
{
ushorts[i] = (uint)random.Next(0, int.MaxValue);
if (random.Next(0, 10) <= 1)
{
Str += random.Next('A', 'Z');
} else
{
Str += random.Next('a', 'z');
}
}
var hashes = MemoryMarshal.Cast<uint, MetaHash>(ushorts);
chars1 = Str.ToCharArray();
for (int i = 0; i < Length; i++)
{
Console.WriteLine($"{ushorts[i]} -> {hashes[i]}");
}
//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);
// Shortcut
Console.WriteLine(GetBytesReadable(1234));
Console.WriteLine(GetBytesReadableNew(1234));
Console.WriteLine(GetBytesReadable(100234));
Console.WriteLine(GetBytesReadableNew(100234));
Console.WriteLine(GetBytesReadable(long.MaxValue));
Console.WriteLine(GetBytesReadableNew(long.MaxValue));
WorldPos = new Vector3(random.NextFloat(float.MinValue, float.MaxValue), random.NextFloat(float.MinValue, float.MaxValue), random.NextFloat(float.MinValue, float.MaxValue));
//Console.WriteLine("Setup done");
@ -245,6 +272,234 @@ namespace CodeWalker.Benchmarks
// return Str.AsSpan().IndexOf('\0');
//}
public static string GetBytesReadable(long i)
{
//shamelessly stolen from stackoverflow, and a bit mangled
// Returns the human-readable file size for an arbitrary, 64-bit file size
// The default format is "0.### XB", e.g. "4.2 KB" or "1.434 GB"
// Get absolute value
long absolute_i = Math.Abs(i);
// Determine the suffix and readable value
string suffix;
double readable;
if (absolute_i >= 0x1000000000000000) // Exabyte
{
suffix = "EB";
readable = (i >> 50);
}
else if (absolute_i >= 0x4000000000000) // Petabyte
{
suffix = "PB";
readable = (i >> 40);
}
else if (absolute_i >= 0x10000000000) // Terabyte
{
suffix = "TB";
readable = (i >> 30);
}
else if (absolute_i >= 0x40000000) // Gigabyte
{
suffix = "GB";
readable = (i >> 20);
}
else if (absolute_i >= 0x100000) // Megabyte
{
suffix = "MB";
readable = (i >> 10);
}
else if (absolute_i >= 0x400) // Kilobyte
{
suffix = "KB";
readable = i;
}
else
{
return i.ToString("0 bytes"); // Byte
}
// Divide by 1024 to get fractional value
readable = (readable / 1024);
string fmt = "0.### ";
if (readable > 1000)
{
fmt = "0";
}
else if (readable > 100)
{
fmt = "0.#";
}
else if (readable > 10)
{
fmt = "0.##";
}
// Return formatted number with suffix
return $"{readable.ToString(fmt)}{suffix}";
}
static string[] sizeSuffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB"];
public static string GetBytesReadableNew(long size)
{
//shamelessly stolen from stackoverflow, and a bit mangled
// Returns the human-readable file size for an arbitrary, 64-bit file size
// The default format is "0.### XB", e.g. "4.2 KB" or "1.434 GB"
// Get absolute value
Debug.Assert(sizeSuffixes.Length > 0);
if (size == 0)
{
return $"{0:0.#} {sizeSuffixes[0]}";
}
var absSize = Math.Abs(size);
var fpPower = Math.Log(absSize, 1024);
var intPower = (int)fpPower;
var normSize = absSize / Math.Pow(1024, intPower);
return $"{normSize:G4} {sizeSuffixes[intPower]}";
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SkipLocalsInit]
public static byte ToLower(char c)
{
return ToLower((byte)c);
//return (c >= 'A' && c <= 'Z') ? (byte)(c - 'A' + 'a') : (byte)c;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SkipLocalsInit]
public static byte ToLower(byte c)
{
return ('A' <= c && c <= 'Z') ? (byte)(c | 0x20) : c;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SkipLocalsInit]
public static byte ToLowerDirect(char c)
{
return (byte)(('A' <= c && c <= 'Z') ? (byte)(c | 0x20) : (byte)c);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SkipLocalsInit]
public static uint GenHashLower(ReadOnlySpan<char> data)
{
uint h = 0;
foreach (var b in data)
{
h += ToLower(b);
h += h << 10;
h ^= h >> 6;
}
h += h << 3;
h ^= h >> 11;
h += h << 15;
return h;
}
[SkipLocalsInit]
public static void WriteDataOneGo(Span<byte> data)
{
Unsafe.WriteUnaligned(ref data[0], new Int128(ulong.MaxValue, ulong.MaxValue));
}
[SkipLocalsInit]
public static void WriteData(Span<byte> data)
{
Unsafe.WriteUnaligned<ulong>(ref data[0], ulong.MaxValue);
Unsafe.WriteUnaligned<ulong>(ref data[8], ulong.MaxValue);
}
[SkipLocalsInit]
public static void WriteDataOld(Span<byte> data)
{
Unsafe.WriteUnaligned<ulong>(ref MemoryMarshal.GetReference(data[..8]), ulong.MaxValue);
Unsafe.WriteUnaligned<ulong>(ref MemoryMarshal.GetReference(data.Slice(8, 8)), ulong.MaxValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SkipLocalsInit]
public static uint GenHashLowerDirect(ReadOnlySpan<char> data)
{
uint h = 0;
foreach (var b in data)
{
h += ToLowerDirect(b);
h += h << 10;
h ^= h >> 6;
}
h += h << 3;
h ^= h >> 11;
h += h << 15;
return h;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SkipLocalsInit]
public static uint GenHashLowerVectorized(Span<char> data)
{
Ascii.ToLowerInPlace(data, out _);
uint h = 0;
for (int i = 0; i < data.Length; i++)
{
h += (byte)data[i];
h += h << 10;
h ^= h >> 6;
}
h += h << 3;
h ^= h >> 11;
h += h << 15;
return h;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SkipLocalsInit]
public static uint GenHashLowerVectorized(ReadOnlySpan<char> data)
{
Span<char> chars = stackalloc char[data.Length];
Ascii.ToLower(data, chars, out _);
uint h = 0;
foreach (var b in chars)
{
h += (byte)b;
h += h << 10;
h ^= h >> 6;
}
h += h << 3;
h ^= h >> 11;
h += h << 15;
return h;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SkipLocalsInit]
public static uint GenHashLower(string data)
{
uint h = 0;
foreach (var b in data)
{
h += ToLower(b);
h += h << 10;
h ^= h >> 6;
}
h += h << 3;
h ^= h >> 11;
h += h << 15;
return h;
}
private PooledList<SimpleType3> getPooledListClass()
{
var list = new PooledList<SimpleType3>();
@ -343,22 +598,25 @@ namespace CodeWalker.Benchmarks
}
}
[Benchmark]
public void ReverseEndianness()
[SkipLocalsInit]
public void WriteData()
{
//BinaryPrimitives.ReverseEndianness(MemoryMarshal.Cast<float, uint>(ushorts), MemoryMarshal.Cast<float, uint>(ushorts));
WriteData(bytes);
}
[Benchmark]
public void SwapBytes()
[SkipLocalsInit]
public void WriteDataOld()
{
var _ushorts = ushorts;
for (int i = 0; i < _ushorts.Length; i++)
{
_ushorts[i] = MetaTypes.SwapBytes(_ushorts[i]);
}
WriteDataOld(bytes);
}
[Benchmark]
[SkipLocalsInit]
public void WriteDataOneGo()
{
WriteDataOneGo(bytes);
}
}
}

View File

@ -859,8 +859,10 @@ namespace CodeWalker.GameFiles
for (uint i = 0; i < 8; i++)
{
var th = h + (i << 29);
if (!string.IsNullOrEmpty(JenkIndex.TryGetString(th))) return th;
if (MetaNames.TryGetString(th, out string str)) return th;
if (JenkIndex.TryGetString(th, out _))
return th;
if (MetaNames.TryGetString(th, out _))
return th;
}
return h;
}
@ -869,13 +871,16 @@ namespace CodeWalker.GameFiles
{
get
{
if (CachedName != null) return CachedName;
if (CachedName is not null)
return CachedName;
var ha = HashAdjusted;
var str = JenkIndex.TryGetString(ha);
if (!string.IsNullOrEmpty(str)) CachedName = str;
else if (MetaNames.TryGetString(ha, out str)) CachedName = str;
else CachedName = "0x" + Hash.Hex;
return CachedName;
if (JenkIndex.TryGetString(ha, out CachedName))
return CachedName;
else if (MetaNames.TryGetString(ha, out CachedName))
return CachedName;
else
return CachedName = $"0x{Hash.Hex}";
}
}
private string CachedName;

View File

@ -1,4 +1,6 @@
using Collections.Pooled;
using CodeWalker.Core.Utils;
using Collections.Pooled;
using Microsoft.Extensions.ObjectPool;
using SharpDX;
using System;
using System.Collections.Generic;
@ -60,8 +62,8 @@ namespace CodeWalker.GameFiles
uint modlen;
bool indates = false;
using var dates = new PooledList<CacheFileDate>();
using var allMapNodes = new PooledList<MapDataStoreNode>();
using var allCInteriorProxies = new PooledList<CInteriorProxy>();
var allMapNodes = PooledListPool<MapDataStoreNode>.Shared.Get();
using var allCInteriorProxies = new PooledList<CInteriorProxy>();;
using var allBoundsStoreItems = new PooledList<BoundsStoreItem>();
for (int i = 100; i < data.Length; i++)
{
@ -105,8 +107,6 @@ namespace CodeWalker.GameFiles
{
allCInteriorProxies.Add(new CInteriorProxy(br));
}
if (allCInteriorProxies.Count != structcount)
{ }//all pass here
i += (int)(modlen + 4);
break;
case "BoundsStore":
@ -118,14 +118,10 @@ namespace CodeWalker.GameFiles
{
allBoundsStoreItems.Add(new BoundsStoreItem(br));
}
if (allBoundsStoreItems.Count != structcount)
{ }//all pass here
i += (int)(modlen + 4);
break;
default:
if (!indates)
{ } //just testing
else
if (indates)
{
string line = new string(charArr.Slice(0, length));
dates.Add(new CacheFileDate(line));//eg: 2740459947 (hash of: platform:/data/cdimages/scaleform_frontend.rpf) 130680580712018938 8944
@ -146,8 +142,10 @@ namespace CodeWalker.GameFiles
AllCInteriorProxies = allCInteriorProxies.ToArray();
AllBoundsStoreItems = allBoundsStoreItems.ToArray();
PooledListPool<MapDataStoreNode>.Shared.Return(allMapNodes);
MapNodeDict = new Dictionary<uint, MapDataStoreNode>();
var rootMapNodes = new List<MapDataStoreNode>();
var rootMapNodes = PooledListPool<MapDataStoreNode>.Shared.Get();
foreach (var mapnode in AllMapNodes)
{
MapNodeDict[mapnode.Name] = mapnode;
@ -155,33 +153,26 @@ namespace CodeWalker.GameFiles
{
rootMapNodes.Add(mapnode);
}
if (mapnode.UnkExtra != null)
{ }//notsure what to do with this
}
foreach (var mapnode in AllMapNodes)
{
MapDataStoreNode pnode;
if (MapNodeDict.TryGetValue(mapnode.ParentName, out pnode))
if (MapNodeDict.TryGetValue(mapnode.ParentName, out MapDataStoreNode pnode))
{
pnode.AddChildToList(mapnode);
}
else if ((mapnode.ParentName != 0))
{ }
}
foreach (var mapnode in AllMapNodes)
{
mapnode.ChildrenListToArray();
}
RootMapNodes = rootMapNodes.ToArray();
PooledListPool<MapDataStoreNode>.Shared.Return(rootMapNodes);
BoundsStoreDict = new Dictionary<MetaHash, BoundsStoreItem>();
foreach (BoundsStoreItem item in AllBoundsStoreItems)
{
BoundsStoreItem mbsi = null;
if (BoundsStoreDict.TryGetValue(item.Name, out mbsi))
{ }
BoundsStoreDict[item.Name] = item;
}
@ -199,8 +190,6 @@ namespace CodeWalker.GameFiles
{
mnode.AddInteriorToList(prx);
}
else
{ }
}
foreach (var mapnode in AllMapNodes)
{
@ -434,8 +423,11 @@ namespace CodeWalker.GameFiles
[TypeConverter(typeof(ExpandableObjectConverter))] public class BoundsStoreItem : IMetaXmlItem
{
public MetaHash Name { get; set; }
public Vector3 Min { get; set; }
public Vector3 Max { get; set; }
public Vector3 _Min;
public ref Vector3 Min => ref _Min;
public Vector3 _Max;
public ref Vector3 Max => ref _Max;
public uint Layer { get; set; }
public BoundsStoreItem()
@ -795,10 +787,10 @@ namespace CodeWalker.GameFiles
public MapDataStoreNodeExtra UnkExtra { get; set; }
public MapDataStoreNode[] Children { get; set; }
private List<MapDataStoreNode> ChildrenList; //used when building the array
private PooledList<MapDataStoreNode>? ChildrenList; //used when building the array
public CInteriorProxy[] InteriorProxies { get; set; }
private List<CInteriorProxy> InteriorProxyList;
private PooledList<CInteriorProxy>? InteriorProxyList;
public MapDataStoreNode()
{ }
@ -826,15 +818,6 @@ namespace CodeWalker.GameFiles
Unk3 = br.ReadByte(); //slod flag?
Unk4 = br.ReadByte();
if (Unk1 != 0)
{ }
if (Unk2 != 0)
{ }
if (Unk3 != 0)
{ }
if (Unk4 != 0)
{ } //no hits here now..
//if (Unk4 == 0xFE)
//{
// //this seems to never be hit anymore...
@ -896,10 +879,9 @@ namespace CodeWalker.GameFiles
Unk4 = (byte)Xml.GetChildUIntAttribute(node, "unk4");
}
public void AddChildToList(MapDataStoreNode child)
{
ChildrenList ??= new List<MapDataStoreNode>();
ChildrenList ??= PooledListPool<MapDataStoreNode>.Shared.Get();
ChildrenList.Add(child);
}
public void ChildrenListToArray()
@ -907,12 +889,13 @@ namespace CodeWalker.GameFiles
if (ChildrenList is not null)
{
Children = ChildrenList.ToArray();
PooledListPool<MapDataStoreNode>.Shared.Return(ChildrenList);
ChildrenList = null; //plz get this GC
}
}
public void AddInteriorToList(CInteriorProxy iprx)
{
InteriorProxyList ??= new List<CInteriorProxy>();
InteriorProxyList ??= PooledListPool<CInteriorProxy>.Shared.Get();
InteriorProxyList.Add(iprx);
}
public void InteriorProxyListToArray()
@ -920,6 +903,7 @@ namespace CodeWalker.GameFiles
if (InteriorProxyList is not null)
{
InteriorProxies = InteriorProxyList.ToArray();
PooledListPool<CInteriorProxy>.Shared.Return(InteriorProxyList);
InteriorProxyList = null; //plz get this GC
}
}

View File

@ -297,8 +297,7 @@ namespace CodeWalker.GameFiles
public override string ToString()
{
return string.Format("{0}, {1}, {2}",
Start, Count, Offset);
return $"{Start}, {Count}, {Offset}";
}
}
@ -399,7 +398,7 @@ namespace CodeWalker.GameFiles
public override string ToString()
{
return string.Format("{0} - Size: {1}, Pos: {2}", Type, Size, Position);
return $"{Type} - Size: {Size}, Pos: {Position}";
}
}
public class WaterFlow : WaterItem

View File

@ -11,10 +11,10 @@ namespace CodeWalker.GameFiles
{
[TypeConverter(typeof(ExpandableObjectConverter))] public class YddFile : GameFile, PackedFile
{
public DrawableDictionary DrawableDict { get; set; }
public DrawableDictionary? DrawableDict { get; set; }
public Dictionary<uint, Drawable> Dict { get; set; }
public Drawable[] Drawables { get; set; }
public Dictionary<uint, Drawable>? Dict { get; set; }
public Drawable[]? Drawables { get; set; }
public YddFile() : base(null, GameFileType.Ydd)
{

View File

@ -13,6 +13,9 @@ using System.Diagnostics.CodeAnalysis;
using CommunityToolkit.HighPerformance;
using Collections.Pooled;
using System.Threading;
using System.Collections;
using System.Globalization;
using CodeWalker.Core.Utils.TypeConverters;
namespace CodeWalker.GameFiles;
@ -41,7 +44,7 @@ public class YmapFile : GameFile, PackedFile
public YmapEntityDef[] RootEntities { get; set; } = [];
public YmapEntityDef[] MloEntities { get; set; } = [];
private WeakReference<YmapFile> parent = new WeakReference<YmapFile>(null);
private WeakReference<YmapFile?> parent = new WeakReference<YmapFile?>(null);
public YmapFile? Parent
{
@ -52,6 +55,12 @@ public class YmapFile : GameFile, PackedFile
return null;
}
if (target is not null && target.IsDisposed)
{
parent.SetTarget(null);
return null;
}
return target;
}
set
@ -296,9 +305,9 @@ public class YmapFile : GameFile, PackedFile
{
//build the entity hierarchy.
List<YmapEntityDef> roots = new List<YmapEntityDef>(instcount);
List<YmapEntityDef> alldefs = new List<YmapEntityDef>(instcount);
List<YmapEntityDef> mlodefs = null;
var roots = PooledListPool<YmapEntityDef>.Shared.Get();
var alldefs = PooledListPool<YmapEntityDef>.Shared.Get();
YmapEntityDef[]? mlodefs = null;
if (CEntityDefs.Length > 0)
{
@ -310,7 +319,7 @@ public class YmapFile : GameFile, PackedFile
}
if (CMloInstanceDefs.Length > 0)
{
mlodefs = new List<YmapEntityDef>(CMloInstanceDefs.Length);
mlodefs = new YmapEntityDef[CMloInstanceDefs.Length];
for (int i = 0; i < CMloInstanceDefs.Length; i++)
{
YmapEntityDef d = new YmapEntityDef(this, i, CMloInstanceDefs[i]);
@ -320,29 +329,31 @@ public class YmapFile : GameFile, PackedFile
d.MloInstance.defaultEntitySets = defentsets;
}
alldefs.Add(d);
mlodefs.Add(d);
mlodefs[i] = d;
}
}
var allEntities = alldefs.ToArray();
PooledListPool<YmapEntityDef>.Shared.Return(alldefs);
MloEntities = mlodefs ?? [];
AllEntities = allEntities;
for (int i = 0; i < alldefs.Count; i++)
foreach(var d in allEntities)
{
YmapEntityDef d = alldefs[i];
int pind = d._CEntityDef.parentIndex;
bool isroot = false;
if ((pind < 0) || (pind >= alldefs.Count) || d.LodInParentYmap)
if (pind < 0 || pind >= allEntities.Length || d.LodInParentYmap)
{
isroot = true;
}
else
{
YmapEntityDef p = alldefs[pind];
YmapEntityDef p = allEntities[pind];
if ((p._CEntityDef.lodLevel <= d._CEntityDef.lodLevel) ||
((p._CEntityDef.lodLevel == rage__eLodType.LODTYPES_DEPTH_ORPHANHD) &&
(d._CEntityDef.lodLevel != rage__eLodType.LODTYPES_DEPTH_ORPHANHD)))
{
isroot = true;
p = null;
}
}
@ -352,22 +363,24 @@ public class YmapFile : GameFile, PackedFile
}
else
{
YmapEntityDef p = alldefs[pind];
YmapEntityDef p = allEntities[pind];
p.AddChild(d);
}
}
for (int i = 0; i < alldefs.Count; i++)
foreach(var d in allEntities)
{
alldefs[i].ChildListToArray();
d.ChildListToArray();
}
AllEntities = alldefs.ToArray();
RootEntities = roots.ToArray();
MloEntities = mlodefs?.ToArray() ?? Array.Empty<YmapEntityDef>();
foreach(var ent in AllEntities)
PooledListPool<YmapEntityDef>.Shared.Return(roots);
foreach(var ent in allEntities)
{
ent.Extensions = MetaTypes.GetExtensions(Meta, in ent._CEntityDef.extensions) ?? Array.Empty<MetaWrapper>();
ent.Extensions = MetaTypes.GetExtensions(Meta, in ent._CEntityDef.extensions) ?? [];
}
}
@ -963,7 +976,7 @@ public class YmapFile : GameFile, PackedFile
if (pind >= 0) //connect root entities to parents if they have them..
{
if (pymap.AllEntities != null && pymap.AllEntities.Length > 0)
if (pymap.AllEntities is not null && pymap.AllEntities.Length > 0)
{
if (pind < pymap.AllEntities.Length)
{
@ -1740,7 +1753,11 @@ public class YmapEntityDef
public uint EntityHash { get; set; } = 0; //used by CW as a unique position+name identifier
public LinkedList<YmapEntityDef> LodManagerChildren { get; set; } = null;
[TypeConverter(typeof(LinkedListConverter<YmapEntityDef>))]
public LinkedList<YmapEntityDef>? LodManagerChildren { get; set; } = null;
public YmapEntityDef[]? LodManagerChildrenDisp => LodManagerChildren?.ToArray();
public object LodManagerRenderable = null;
@ -2079,7 +2096,8 @@ public class YmapEntityDef
public void ChildListToArray()
{
if (ChildList == null) return;
if (ChildList is null || ChildList.Count == 0)
return;
//if (Children == null)
//{
Children = ChildList.ToArray();

View File

@ -1,5 +1,7 @@
using CodeWalker.World;
using CodeWalker.Core.Utils;
using CodeWalker.World;
using Collections.Pooled;
using Microsoft.Extensions.ObjectPool;
using SharpDX;
using System;
using System.Collections.Generic;
@ -226,9 +228,7 @@ namespace CodeWalker.GameFiles
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;
Nodes[i] = new YndNode(this, nodes[i]);
}
}
if (NodeDictionary.JunctionRefs is not null && NodeDictionary.Junctions is not null)
@ -254,17 +254,17 @@ namespace CodeWalker.GameFiles
public YndNode AddNode()
{
int cnt = Nodes?.Length ?? 0;
YndNode yn = new YndNode();
Node n = new Node();
n.AreaID = (ushort)AreaID;
n.NodeID = (ushort)(Nodes?.Length ?? 0);
yn.Init(this, n);
YndNode yn = new YndNode(this, n);
int ncnt = cnt + 1;
YndNode[] nnodes = new YndNode[ncnt];
for (int i = 0; i < cnt; i++)
{
nnodes[i] = Nodes[i];
nnodes[i] = Nodes![i];
}
nnodes[cnt] = yn;
Nodes = nnodes;
@ -419,7 +419,7 @@ namespace CodeWalker.GameFiles
{
if (Nodes is null || Nodes.Length == 0)
{
NodePositions = Array.Empty<Vector4>();
NodePositions = [];
return;
}
int cnt = Nodes.Length;
@ -441,72 +441,69 @@ namespace CodeWalker.GameFiles
private void UpdateLinkTriangleVertices()
{
//build triangles for the path links display
int vc = 0;
if (Links != null)
if (Links is null || Nodes is null)
{
vc = Links.Length * 6;
TriangleVerts = [];
return;
}
using PooledList<EditorVertex> verts = new PooledList<EditorVertex>(vc);
//EditorVertex v0 = new EditorVertex();
//EditorVertex v1 = new EditorVertex();
//EditorVertex v2 = new EditorVertex();
//EditorVertex v3 = new EditorVertex();
if (Links is not null && Nodes is not null)
//build triangles for the path links display
PooledList<EditorVertex> verts = PooledListPool<EditorVertex>.Shared.Get();
var vectorZero = Vector3.Zero;
foreach (var node in Nodes)
{
foreach (var node in Nodes)
if (node.Links is null)
{
if (node.Links is null)
continue;
}
foreach (var link in node.Links)
{
ref var p0 = ref link.Node1.Position;
ref var p1 = ref link.Node2.Position;
var dir = link.GetDirection();
var ax = Vectors.Cross(in dir, in 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 = link.GetLaneWidth();
float inner = link.LaneOffset * lanewidth;// 0.0f;
float outer = inner + lanewidth * link.LaneCountForward;
float totwidth = lanestot * lanewidth;
float halfwidth = totwidth * 0.5f;
if (link.LaneCountBackward == 0)
{
continue;
inner -= halfwidth;
outer -= halfwidth;
}
if (link.LaneCountForward == 0)
{
inner += halfwidth;
outer += halfwidth;
}
foreach (var link in node.Links)
{
var p0 = link.Node1?.Position ?? Vector3.Zero;
var p1 = link.Node2?.Position ?? Vector3.Zero;
var dir = link.GetDirection();
var ax = Vector3.Cross(dir, Vector3.UnitZ);
var c = link.GetColourUint();
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;
var v0 = new EditorVertex(p1 + ax * inner, c);
var v1 = new EditorVertex(p0 + ax * inner, c);
var v2 = new EditorVertex(p1 + ax * outer, c);
var v3 = new EditorVertex(p0 + ax * outer, c);
float lanewidth = link.GetLaneWidth();
float inner = link.LaneOffset * lanewidth;// 0.0f;
float outer = inner + lanewidth * link.LaneCountForward;
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;
}
var c = (uint)link.GetColour().ToRgba();
var v0 = new EditorVertex(p1 + ax * inner, c);
var v1 = new EditorVertex(p0 + ax * inner, c);
var v2 = new EditorVertex(p1 + ax * outer, c);
var v3 = new EditorVertex(p0 + ax * outer, c);
verts.Add(v0);
verts.Add(v1);
verts.Add(v2);
verts.Add(v2);
verts.Add(v1);
verts.Add(v3);
}
verts.Add(v0);
verts.Add(v1);
verts.Add(v2);
verts.Add(v2);
verts.Add(v1);
verts.Add(v3);
}
}
@ -519,17 +516,19 @@ namespace CodeWalker.GameFiles
{
TriangleVerts = [];
}
PooledListPool<EditorVertex>.Shared.Return(verts);
}
private void UpdateJunctionTriangleVertices(YndNode[]? selectedNodes)
{
if (selectedNodes is null)
if (selectedNodes is null || selectedNodes.Length == 0)
{
return;
}
//build triangles for the junctions bytes display....
using PooledList<EditorVertex> verts = new PooledList<EditorVertex>();
PooledList<EditorVertex> verts = PooledListPool<EditorVertex>.Shared.Get();
EditorVertex v0;
EditorVertex v1;
EditorVertex v2;
@ -537,10 +536,9 @@ namespace CodeWalker.GameFiles
foreach (var node in selectedNodes)
{
if (node.Ynd != this)
continue;
if (node.Junction is null)
if (node.Ynd != this || node.Junction is null)
continue;
var j = node.Junction;
var d = j.Heightmap;
if (d is null)
@ -604,6 +602,8 @@ namespace CodeWalker.GameFiles
TriangleVerts = vertsarr;
}
}
PooledListPool<EditorVertex>.Shared.Return(verts);
}
@ -725,7 +725,9 @@ namespace CodeWalker.GameFiles
public ref Vector3 Position => ref _Position;
public int LinkCount { get; set; }
public int LinkCountUnk { get; set; }
public YndLink[]? Links { get; set; }
private YndLink[]? _links;
public YndLink[]? Links { get => _links; set => _links = value; }
public ushort AreaID { get => _RawData.AreaID; set => _RawData.AreaID = value; }
public ushort NodeID { get => _RawData.NodeID; set => _RawData.NodeID = value; }
@ -737,7 +739,9 @@ namespace CodeWalker.GameFiles
public FlagsByte Flags4 { get => _RawData.Flags4; set => _RawData.Flags4 = value; }
public TextHash StreetName { get => _RawData.StreetName; set => _RawData.StreetName = value; }
public Color4 Colour { get; set; }
public Color4 Colour => new Color4(ColourRgba);
public uint ColourRgba { get; set; }
public YndJunction Junction { get; set; }
public bool HasJunction;
@ -875,17 +879,16 @@ namespace CodeWalker.GameFiles
public bool IsPedNode => IsSpecialTypeAPedNode(Special);// If Special is 10, 14 or 18 this is a ped node.
public void Init(YndFile ynd, Node node)
public YndNode(YndFile ynd, Node node)
{
Ynd = ynd;
RawData = node;
_RawData = node;
Position = new Vector3(node.PositionX / 4.0f, node.PositionY / 4.0f, node.PositionZ / 32.0f);
LinkCount = node.LinkCountFlags.Value >> 3;
LinkCountUnk = node.LinkCountFlags.Value & 7;
Colour = GetColour();
ColourRgba = (uint)GetColour().ToRgba();
}
public Color4 GetColour()
@ -995,13 +998,10 @@ namespace CodeWalker.GameFiles
public YndLink AddLink(YndNode? tonode = null, bool bidirectional = true)
{
if (Links == null)
{
Links = Array.Empty<YndLink>();
}
Links ??= [];
var existing = Links.FirstOrDefault(el => el.Node2 == tonode);
if (existing != null)
if (existing is not null)
{
return existing;
}
@ -1026,15 +1026,10 @@ namespace CodeWalker.GameFiles
}
l.UpdateLength();
int cnt = Links?.Length ?? 0;
int cnt = Links.Length;
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;
Array.Resize(ref _links, ncnt);
_links[cnt] = l;
LinkCount = ncnt;
if (bidirectional)
@ -1168,15 +1163,15 @@ namespace CodeWalker.GameFiles
if (AreaID != expectedArea?.ID)
{
var nodeYnd = space.NodeGrid.GetCell(AreaID).Ynd;
var newYnd = expectedArea?.Ynd;
if (newYnd is null)
{
SetPosition(oldPosition);
affectedFiles = Array.Empty<YndFile>();
affectedFiles = [];
return;
}
var nodeYnd = space.NodeGrid.GetCell(AreaID)?.Ynd;
if ((nodeYnd is null) ||
nodeYnd.RemoveYndNode(space, this, false, out var affectedFilesFromDelete))
{
@ -1207,10 +1202,7 @@ namespace CodeWalker.GameFiles
public void GenerateYndNodeJunctionHeightMap(Space space)
{
if (Junction == null)
{
Junction = new YndJunction();
}
Junction ??= new YndJunction();
var junc = Junction;
var maxZ = junc.MaxZ / 32f;
@ -1279,7 +1271,8 @@ namespace CodeWalker.GameFiles
}
}
[TypeConverter(typeof(ExpandableObjectConverter))] public class YndLink
[TypeConverter(typeof(ExpandableObjectConverter))]
public class YndLink
{
public YndFile Ynd { get; set; }
public YndNode Node1 { get; set; }
@ -1296,11 +1289,11 @@ namespace CodeWalker.GameFiles
}
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 NodeLink RawData { get => _RawData; set => _RawData = value; }
public FlagsByte Flags0 { get => _RawData.Flags0; set => _RawData.Flags0 = value; }
public FlagsByte Flags1 { get => _RawData.Flags1; set => _RawData.Flags1 = value; }
public FlagsByte Flags2 { get => _RawData.Flags2; set => _RawData.Flags2 = value; }
public FlagsByte LinkLength { get => _RawData.LinkLength; set => _RawData.LinkLength = value; }
public int LaneCountForward
{
@ -1333,16 +1326,19 @@ namespace CodeWalker.GameFiles
set => Flags2 = value ? (byte)(Flags2 | 2) : (byte)(Flags2 &~ 2);
}
public YndLink()
{
}
public void Init(YndFile ynd, YndNode node1, YndNode node2, NodeLink link)
public YndLink(YndFile ynd, YndNode node1, YndNode node2, NodeLink link)
{
Ynd = ynd;
Node1 = node1;
Node2 = node2;
RawData = link;
_RawData = link;
}
public void UpdateLength()
{
if (Node1 is null || Node2 is null)
@ -1398,6 +1394,54 @@ namespace CodeWalker.GameFiles
Node2?.CheckIfJunction();
}
public uint GetColourUint()
{
if (Shortcut)
{
// new Color4(0.0f, 0.2f, 0.2f, 0.5f);
return 2134061824;
}
var node1 = Node1;
var node2 = Node2;
if (node1.IsDisabledUnk0
|| node1.IsDisabledUnk1
|| node2.IsDisabledUnk0
|| node2.IsDisabledUnk1)
{
if (node1.OffRoad || node2.OffRoad)
{
return 2130772741;
}
return 2130706437;
}
if (node1.IsPedNode || node2.IsPedNode)
{
return 2130716211;
}
if (node1.OffRoad || node2.OffRoad)
{
return 2131371825;
}
if (DontUseForNavigation)
{
return 2134048819;
}
if (LaneCountForward == 0)
{
return 2130706559;
}
return 2130719488;
}
public Color4 GetColour()
{
//float f0 = Flags0.Value / 255.0f;
@ -1414,29 +1458,33 @@ namespace CodeWalker.GameFiles
return c;
}
if (Node1.IsDisabledUnk0
|| Node1.IsDisabledUnk1
|| Node2.IsDisabledUnk0
|| Node2.IsDisabledUnk1)
var node1 = Node1;
var node2 = Node2;
if (node1.IsDisabledUnk0
|| node1.IsDisabledUnk1
|| node2.IsDisabledUnk0
|| node2.IsDisabledUnk1)
{
if (Node1.OffRoad || Node2.OffRoad)
c.Red = 0.02f;
if (node1.OffRoad || node2.OffRoad)
{
c.Red = 0.0196f;
c.Green = 0.0156f;
c.Blue = 0.0043f;
}
c.Red = 0.02f;
return c;
}
if (Node1.IsPedNode || Node2.IsPedNode)
if (node1.IsPedNode || node2.IsPedNode)
{
c.Red = 0.2f;
c.Green = 0.15f;
return c;
}
if (Node1.OffRoad || Node2.OffRoad)
if (node1.OffRoad || node2.OffRoad)
{
c.Red = 0.196f;
c.Green = 0.156f;
@ -1706,7 +1754,7 @@ namespace CodeWalker.GameFiles
Sphere.Radius = (Box.Maximum - Box.Minimum).Length() * 0.5f;
}
private static ObjectPool<PooledList<BasePathNode>> basePathNodeListPool => PooledListPool<BasePathNode>.Shared;
public void Build()
{
if (Nodes == null || Nodes.Length == 0 || Nodes.Length <= Threshold || Depth >= MaxDepth)
@ -1715,9 +1763,9 @@ namespace CodeWalker.GameFiles
Vector3 avgsum = Vector3.Zero;
foreach (var node in Nodes)
{
avgsum += node.Position;
Vector3.Add(ref node.Position, ref avgsum, out avgsum);
}
Vector3 avg = avgsum * Nodes.Length;
Vector3 avg = avgsum / Nodes.Length;
int countx = 0, county = 0, countz = 0;
foreach (var node in Nodes)
@ -1735,14 +1783,16 @@ namespace CodeWalker.GameFiles
int dy = Math.Abs(target - county);
int dz = Math.Abs(target - countz);
int axis = -1;
int axis;
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
using PooledList<BasePathNode> l1 = new PooledList<BasePathNode>(Nodes.Length);
using PooledList<BasePathNode> l2 = new PooledList<BasePathNode>(Nodes.Length);
PooledList<BasePathNode> l1 = PooledListPool<BasePathNode>.Shared.Get();
PooledList<BasePathNode> l2 = PooledListPool<BasePathNode>.Shared.Get();
l1.Clear();
l2.Clear();
foreach (var node in Nodes)
{
var s = axis switch
@ -1759,13 +1809,19 @@ namespace CodeWalker.GameFiles
var cdepth = Depth + 1;
var nodesL1 = l1.ToArray();
PooledListPool<BasePathNode>.Shared.Return(l1);
var nodesL2 = l2.ToArray();
PooledListPool<BasePathNode>.Shared.Return(l2);
Node1 = new PathBVHNode
{
Depth = cdepth,
MaxDepth = MaxDepth,
Threshold = Threshold,
Nodes = l1.ToArray(),
Nodes = nodesL1,
};
Node1.CalcBounds();
Node1.Build();
@ -1774,7 +1830,7 @@ namespace CodeWalker.GameFiles
Depth = cdepth,
MaxDepth = MaxDepth,
Threshold = Threshold,
Nodes = l2.ToArray(),
Nodes = nodesL2,
};
Node2.CalcBounds();
Node2.Build();

View File

@ -6,6 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
namespace CodeWalker.GameFiles
{
@ -14,45 +15,43 @@ namespace CodeWalker.GameFiles
public static class LoadState
{
public const int None = 0;
public const int Loaded = 1;
public const int LoadQueued = 2;
public const int Loaded = 1 << 0;
public const int LoadQueued = 1 << 1;
public const int Disposed = 1 << 2;
public static bool ToggleFlag(ref int currentValue, int flagToToggle, bool value)
{
if (value)
{
return (Interlocked.Or(ref currentValue, flagToToggle) & flagToToggle) == 0;
}
else
{
return (Interlocked.And(ref currentValue, ~flagToToggle) & flagToToggle) == flagToToggle;
}
}
}
public abstract class GameFile : Cacheable<GameFileCacheKey>, IDisposable
{
public byte LoadAttempts = 0;
public int loadState = (int)LoadState.None;
[NotifyParentProperty(true)]
public bool LoadQueued {
get => (loadState & LoadState.LoadQueued) == LoadState.LoadQueued;
set {
if (value)
{
Interlocked.Or(ref loadState, LoadState.LoadQueued);
}
else
{
Interlocked.And(ref loadState, ~LoadState.LoadQueued);
}
}
set => SetLoadQueued(value);
}
[NotifyParentProperty(true)]
public bool Loaded
{
get => (loadState & LoadState.Loaded) == LoadState.Loaded;
set
{
if (value)
{
Interlocked.Or(ref loadState, LoadState.Loaded);
}
else
{
Interlocked.And(ref loadState, ~LoadState.Loaded);
}
}
set => SetLoaded(value);
}
public bool IsDisposed {
get => (loadState & LoadState.Disposed) == LoadState.Disposed;
set => SetDisposed(value);
}
public DateTime LastLoadTime = DateTime.MinValue;
@ -60,7 +59,6 @@ namespace CodeWalker.GameFiles
public string Name { get; set; }
public string FilePath { get; set; } //used by the project form.
public GameFileType Type { get; set; }
public bool IsDisposed { get; set; } = false;
@ -84,29 +82,11 @@ namespace CodeWalker.GameFiles
}
}
public bool SetLoadQueued(bool value)
{
if (value)
{
return (Interlocked.Or(ref loadState, LoadState.LoadQueued) & LoadState.LoadQueued) == 0;
}
else
{
return (Interlocked.And(ref loadState, ~LoadState.LoadQueued) & LoadState.LoadQueued) == LoadState.LoadQueued;
}
}
public bool SetLoadQueued(bool value) => LoadState.ToggleFlag(ref loadState, LoadState.LoadQueued, value);
public bool SetLoaded(bool value)
{
if (value)
{
return (Interlocked.Or(ref loadState, LoadState.Loaded) & LoadState.Loaded) == 0;
}
else
{
return (Interlocked.And(ref loadState, ~LoadState.Loaded) & LoadState.Loaded) == LoadState.Loaded;
}
}
public bool SetLoaded(bool value) => LoadState.ToggleFlag(ref loadState, LoadState.Loaded, value);
public bool SetDisposed(bool value) => LoadState.ToggleFlag(ref loadState, LoadState.Disposed, value);
public override string ToString() => string.IsNullOrEmpty(Name) ? JenkIndex.GetString(Key.Hash) : Name;
@ -188,6 +168,7 @@ namespace CodeWalker.GameFiles
DistantLights = 30,
Ypdb = 31,
PedShopMeta = 32,
None = 33,
}

View File

@ -1052,11 +1052,12 @@ namespace CodeWalker.GameFiles
YftDict ??= new Dictionary<uint, RpfFileEntry>(40000);
YcdDict ??= new Dictionary<uint, RpfFileEntry>(20000);
YedDict ??= new Dictionary<uint, RpfFileEntry>(300);
foreach (var rpffile in AllRpfs)
foreach (var rpffile in AllRpfs.AsSpan())
{
if (rpffile.AllEntries is null)
continue;
foreach (var entry in rpffile.AllEntries)
foreach (var entry in rpffile.AllEntries.Span)
{
if (entry is RpfFileEntry fentry)
{
@ -1106,7 +1107,7 @@ namespace CodeWalker.GameFiles
{
if (rpffile.AllEntries is null)
continue;
foreach (var entry in rpffile.AllEntries)
foreach (var entry in rpffile.AllEntries.Span)
{
if (entry is RpfFileEntry fentry)
{
@ -3415,9 +3416,6 @@ namespace CodeWalker.GameFiles
}
var csv = sb.ToString();
}

View File

@ -1931,64 +1931,71 @@ namespace CodeWalker.GameFiles
return null; //couldn't find the strings data section.
}
using PooledList<string> strings = new PooledList<string>();
var currentblock = startblock;
int currentblockind = startblockind;
while (currentblock != null)
PooledList<string> strings = PooledListPool<string>.Shared.Get();
try
{
//read strings from the block.
int startindex = 0;
int endindex = 0;
var data = currentblock.Data;
foreach(var span in data.AsSpan().EnumerateSplit((byte)0))
var currentblock = startblock;
int currentblockind = startblockind;
while (currentblock != null)
{
if (!span.IsEmpty)
//read strings from the block.
int startindex = 0;
int endindex = 0;
var data = currentblock.Data;
foreach (var span in data.AsSpan().EnumerateSplit((byte)0))
{
string str = Encoding.ASCII.GetStringPooled(span);
strings.Add(str);
if (!span.IsEmpty)
{
string str = Encoding.ASCII.GetStringPooled(span);
strings.Add(str);
}
}
//for (int b = 0; b < data.Length; b++)
//{
// if (data[b] == 0)
// {
// startindex = endindex;
// endindex = b;
// if (endindex > startindex)
// {
// string str = Encoding.ASCII.GetString(data.AsSpan(startindex, endindex - startindex));
// strings.Add(str);
// endindex++; //start next string after the 0.
// }
// }
//}
//if (endindex != data.Length - 1)
//{
// startindex = endindex;
// endindex = data.Length - 1;
// if (endindex > startindex)
// {
// string str = Encoding.ASCII.GetString(data.AsSpan(startindex, endindex - startindex));
// strings.Add(str);
// strings2.Add(str);
// }
//}
currentblockind++;
if (currentblockind >= datablocks.Count)
break; //last block, can't go any further
currentblock = datablocks[currentblockind];
if (currentblock.StructureNameHash != (MetaName)MetaTypeName.STRING)
break; //not the right block type, can't go further
}
//for (int b = 0; b < data.Length; b++)
//{
// if (data[b] == 0)
// {
// startindex = endindex;
// endindex = b;
// if (endindex > startindex)
// {
// string str = Encoding.ASCII.GetString(data.AsSpan(startindex, endindex - startindex));
// strings.Add(str);
// endindex++; //start next string after the 0.
// }
// }
//}
//if (endindex != data.Length - 1)
//{
// startindex = endindex;
// endindex = data.Length - 1;
// if (endindex > startindex)
// {
// string str = Encoding.ASCII.GetString(data.AsSpan(startindex, endindex - startindex));
// strings.Add(str);
// strings2.Add(str);
// }
//}
currentblockind++;
if (currentblockind >= datablocks.Count)
break; //last block, can't go any further
currentblock = datablocks[currentblockind];
if (currentblock.StructureNameHash != (MetaName)MetaTypeName.STRING)
break; //not the right block type, can't go further
if (strings.Count <= 0)
{
return null; //don't return empty array...
}
return strings.ToArray();
}
if (strings.Count <= 0)
finally
{
return null; //don't return empty array...
PooledListPool<string>.Shared.Return(strings);
}
return strings.ToArray();
}
[SkipLocalsInit]
@ -4921,7 +4928,7 @@ namespace CodeWalker.GameFiles
public void AddScenarioPoint(MCExtensionDefSpawnPoint p)
{
List<MCExtensionDefSpawnPoint> newpoints = new List<MCExtensionDefSpawnPoint>();
if (ScenarioPoints != null)
if (ScenarioPoints is not null)
{
newpoints.AddRange(ScenarioPoints);
}

View File

@ -1907,18 +1907,9 @@ namespace CodeWalker.GameFiles
}
public class StringBuilderPooledObjectPolicyLogged : StringBuilderPooledObjectPolicy
{
public override bool Return(StringBuilder obj)
{
Console.WriteLine($"StringBuilder returned with capacity: {obj.Capacity} {obj.Length}");
return base.Return(obj);
}
}
public class MetaXmlBase
{
public static ObjectPool<StringBuilder> StringBuilderPool = ObjectPool.Create<StringBuilder>(new StringBuilderPooledObjectPolicyLogged { MaximumRetainedCapacity = 4 * 1024 * 1024, InitialCapacity = 1024 * 32 });
public static ObjectPool<StringBuilder> StringBuilderPool = ObjectPool.Create<StringBuilder>(new StringBuilderPooledObjectPolicy { MaximumRetainedCapacity = 4 * 1024 * 1024, InitialCapacity = 1024 * 32 });
public const string XmlHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";

View File

@ -450,7 +450,6 @@ namespace CodeWalker.GameFiles
[TypeConverter(typeof(ExpandableObjectConverter))]
public class RbfStructure : IRbfType, IDisposable
{
private static ObjectPool<PooledList<IRbfType>> listPool = ObjectPool.Create(new DefaultPooledObjectPolicy<PooledList<IRbfType>>());
public string Name { get; set; }
public PooledList<IRbfType>? Children { get; set; }
@ -519,13 +518,13 @@ namespace CodeWalker.GameFiles
internal void AddChild(IRbfType value)
{
Children ??= listPool.Get();
Children ??= PooledListPool<IRbfType>.Shared.Get();
Children.Add(value);
}
internal void AddAttribute(IRbfType value)
{
Attributes ??= listPool.Get();
Attributes ??= PooledListPool<IRbfType>.Shared.Get();
Attributes.Add(value);
}
@ -546,11 +545,11 @@ namespace CodeWalker.GameFiles
{
if (Children is PooledList<IRbfType> children)
{
listPool.Return(children);
PooledListPool<IRbfType>.Shared.Return(children);
}
if (Attributes is PooledList<IRbfType> attributes)
{
listPool.Return(attributes);
PooledListPool<IRbfType>.Shared.Return(attributes);
}
GC.SuppressFinalize(this);

View File

@ -3300,16 +3300,7 @@ namespace CodeWalker.GameFiles
}
public override void WriteXml(StringBuilder sb, int indent)
{
var s = string.Format("{0} m=\"{1}\" v1=\"{2}\" v2=\"{3}\" v3=\"{4}\" f1=\"{5}\" f2=\"{6}\" f3=\"{7}\"",
Type,
MaterialIndex,
vertIndex1,
vertIndex2,
vertIndex3,
vertFlag1 ? 1 : 0,
vertFlag2 ? 1 : 0,
vertFlag3 ? 1 : 0
);
var s = $"{Type} m=\"{MaterialIndex}\" v1=\"{vertIndex1}\" v2=\"{vertIndex2}\" v3=\"{vertIndex3}\" f1=\"{(vertFlag1 ? 1 : 0)}\" f2=\"{(vertFlag2 ? 1 : 0)}\" f3=\"{(vertFlag3 ? 1 : 0)}\"";
YbnXml.SelfClosingTag(sb, indent, s);
}
public override void ReadXml(XmlNode node)
@ -3324,7 +3315,7 @@ namespace CodeWalker.GameFiles
}
public override string ToString()
{
return base.ToString() + ": " + vertIndex1.ToString() + ", " + vertIndex2.ToString() + ", " + vertIndex3.ToString();
return $"{base.ToString()}: {vertIndex1}, {vertIndex2}, {vertIndex3}";
}
}
[TC(typeof(EXP))] public class BoundPolygonSphere : BoundPolygon
@ -3335,20 +3326,8 @@ namespace CodeWalker.GameFiles
public uint unused0 { get; set; }
public uint unused1 { get; set; }
public override Vector3 BoxMin
{
get
{
return Position - sphereRadius;
}
}
public override Vector3 BoxMax
{
get
{
return Position + sphereRadius;
}
}
public override Vector3 BoxMin => Position - sphereRadius;
public override Vector3 BoxMax => Position + sphereRadius;
public override Vector3 Scale
{
get
@ -3571,13 +3550,7 @@ namespace CodeWalker.GameFiles
}
public override void WriteXml(StringBuilder sb, int indent)
{
var s = string.Format("{0} m=\"{1}\" v1=\"{2}\" v2=\"{3}\" radius=\"{4}\"",
Type,
MaterialIndex,
capsuleIndex1,
capsuleIndex2,
FloatUtil.ToString(capsuleRadius)
);
var s = $"{Type} m=\"{MaterialIndex}\" v1=\"{capsuleIndex1}\" v2=\"{capsuleIndex2}\" radius=\"{FloatUtil.ToString(capsuleRadius)}\"";
YbnXml.SelfClosingTag(sb, indent, s);
}
public override void ReadXml(XmlNode node)
@ -3587,10 +3560,7 @@ namespace CodeWalker.GameFiles
capsuleIndex2 = (ushort)Xml.GetUIntAttribute(node, "v2");
capsuleRadius = Xml.GetFloatAttribute(node, "radius");
}
public override string ToString()
{
return base.ToString() + ": " + capsuleIndex1.ToString() + ", " + capsuleIndex2.ToString() + ", " + capsuleRadius.ToString();
}
public override string ToString() => $"{base.ToString()}: {capsuleIndex1}, {capsuleIndex2}, {capsuleRadius}";
}
[TC(typeof(EXP))] public class BoundPolygonBox : BoundPolygon
{
@ -3790,10 +3760,7 @@ namespace CodeWalker.GameFiles
boxIndex3 = (short)Xml.GetIntAttribute(node, "v3");
boxIndex4 = (short)Xml.GetIntAttribute(node, "v4");
}
public override string ToString()
{
return base.ToString() + ": " + boxIndex1.ToString() + ", " + boxIndex2.ToString() + ", " + boxIndex3.ToString() + ", " + boxIndex4.ToString();
}
public override string ToString() => $"{base.ToString()}: {boxIndex1}, {boxIndex2}, {boxIndex3}, {boxIndex4}";
}
[TC(typeof(EXP))] public class BoundPolygonCylinder : BoundPolygon
{
@ -3815,20 +3782,8 @@ namespace CodeWalker.GameFiles
set { if (Owner != null) Owner.SetVertexPos(cylinderIndex2, value); }
}
public override Vector3 BoxMin
{
get
{
return Vector3.Min(Vertex1, Vertex2) - cylinderRadius;//not perfect but meh
}
}
public override Vector3 BoxMax
{
get
{
return Vector3.Max(Vertex1, Vertex2) + cylinderRadius;//not perfect but meh
}
}
public override Vector3 BoxMin => Vector3.Min(Vertex1, Vertex2) - cylinderRadius;//not perfect but meh
public override Vector3 BoxMax => Vector3.Max(Vertex1, Vertex2) + cylinderRadius;//not perfect but meh
public override Vector3 Scale
{
get
@ -3853,10 +3808,7 @@ namespace CodeWalker.GameFiles
}
public override Vector3 Position
{
get
{
return (Vertex1 + Vertex2) * 0.5f;
}
get => (Vertex1 + Vertex2) * 0.5f;
set
{
var offset = value - Position;
@ -3962,10 +3914,7 @@ namespace CodeWalker.GameFiles
cylinderIndex2 = (ushort)Xml.GetUIntAttribute(node, "v2");
cylinderRadius = Xml.GetFloatAttribute(node, "radius");
}
public override string ToString()
{
return base.ToString() + ": " + cylinderIndex1.ToString() + ", " + cylinderIndex2.ToString() + ", " + cylinderRadius.ToString();
}
public override string ToString() => $"{base.ToString()}: {cylinderIndex1}, {cylinderIndex2}, {cylinderRadius}";
}
@ -4311,10 +4260,7 @@ namespace CodeWalker.GameFiles
set { MaxX = (short)value.X; MaxY = (short)value.Y; MaxZ = (short)value.Z; }
}
public override readonly string ToString()
{
return $"{NodeIndex1}, {NodeIndex2} ({NodeIndex2 - NodeIndex1} nodes)";
}
public override readonly string ToString() => $"{NodeIndex1}, {NodeIndex2} ({NodeIndex2 - NodeIndex1} nodes)";
}
[TC(typeof(EXP))]
public struct BVHNode_s
@ -4339,10 +4285,7 @@ namespace CodeWalker.GameFiles
set { MaxX = (short)value.X; MaxY = (short)value.Y; MaxZ = (short)value.Z; }
}
public override readonly string ToString()
{
return $"{ItemId}: {ItemCount}";
}
public override readonly string ToString() => $"{ItemId}: {ItemCount}";
}
@ -4851,46 +4794,34 @@ namespace CodeWalker.GameFiles
return !(left == right);
}
}
[TC(typeof(EXP))] public struct BoundMaterialColour
[TC(typeof(EXP))]
public struct BoundMaterialColour
{
public byte R { get; set; }
public byte G { get; set; }
public byte B { get; set; }
public byte A { get; set; } //GIMS EVO saves this as "opacity" 0-100
public override string ToString()
{
//return Type.ToString() + ", " + Unk0.ToString() + ", " + Unk1.ToString() + ", " + Unk2.ToString();
return R.ToString() + ", " + G.ToString() + ", " + B.ToString() + ", " + A.ToString();
}
public override string ToString() => $"{R}, {G}, {B}, {A}";
//return Type.ToString() + ", " + Unk0.ToString() + ", " + Unk1.ToString() + ", " + Unk2.ToString();
}
[TC(typeof(EXP))] public struct BoundsMaterialType
[TC(typeof(EXP))]
public struct BoundsMaterialType
{
public byte Index { get; set; }
public BoundsMaterialData MaterialData
{
get
{
return BoundsMaterialTypes.GetMaterial(this);
}
}
public BoundsMaterialData MaterialData => BoundsMaterialTypes.GetMaterial(this);
public override string ToString()
{
return BoundsMaterialTypes.GetMaterialName(this);
}
public override string ToString() => BoundsMaterialTypes.GetMaterialName(this);
public static implicit operator byte(BoundsMaterialType matType)
{
return matType.Index; //implicit conversion
}
public static implicit operator byte(BoundsMaterialType matType) => matType.Index;
public static implicit operator BoundsMaterialType(byte b)
{
return new BoundsMaterialType() { Index = b };
}
public static implicit operator BoundsMaterialType(byte b) => new BoundsMaterialType() { Index = b };
}
[TC(typeof(EXP))] public class BoundsMaterialData
[TC(typeof(EXP))]
public class BoundsMaterialData
{
public string Name { get; set; }
public string Filter { get; set; }
@ -4918,10 +4849,7 @@ namespace CodeWalker.GameFiles
public Color Colour { get; set; }
public override string ToString()
{
return Name;
}
public override string ToString() => Name;
}
public static class BoundsMaterialTypes

View File

@ -98,7 +98,7 @@ namespace CodeWalker.GameFiles
public string Name { get; set; }
public FragBoneTransforms BoneTransforms { get; set; }
public ResourcePointerArray64<FragGlassWindow> GlassWindows { get; set; }
public FragPhysicsLODGroup PhysicsLODGroup { get; set; }
public FragPhysicsLODGroup? PhysicsLODGroup { get; set; }
public FragDrawable DrawableCloth { get; set; }
public FragVehicleGlassWindows VehicleGlassWindows { get; set; }
@ -1995,10 +1995,7 @@ namespace CodeWalker.GameFiles
[TypeConverter(typeof(ExpandableObjectConverter))] public class FragPhysicsLODGroup : ResourceSystemBlock
{
public override long BlockLength
{
get { return 48; }
}
public override long BlockLength => 48;
// structure data
public uint VFT { get; set; } = 1080055472;
@ -2010,9 +2007,9 @@ namespace CodeWalker.GameFiles
public ulong Unknown_28h; // 0x0000000000000000
// reference data
public FragPhysicsLOD PhysicsLOD1 { get; set; }
public FragPhysicsLOD PhysicsLOD2 { get; set; }
public FragPhysicsLOD PhysicsLOD3 { get; set; }
public FragPhysicsLOD? PhysicsLOD1 { get; set; }
public FragPhysicsLOD? PhysicsLOD2 { get; set; }
public FragPhysicsLOD? PhysicsLOD3 { get; set; }
public override void Read(ResourceDataReader reader, params object[] parameters)
{
@ -2167,8 +2164,8 @@ namespace CodeWalker.GameFiles
public FragPhysArticulatedBodyType ArticulatedBodyType { get; set; }
public float[] ChildrenUnkFloats { get; set; }
public FragPhysGroupNamesBlock GroupNames { get; set; }
public ResourcePointerArray64<FragPhysTypeGroup> Groups { get; set; }
public ResourcePointerArray64<FragPhysTypeChild> Children { get; set; }
public ResourcePointerArray64<FragPhysTypeGroup>? Groups { get; set; }
public ResourcePointerArray64<FragPhysTypeChild>? Children { get; set; }
public FragPhysArchetype Archetype1 { get; set; }
public FragPhysArchetype Archetype2 { get; set; }
public Bounds Bound { get; set; }

View File

@ -105,6 +105,12 @@ namespace CodeWalker.GameFiles
//public float Unknown_23 { get; set; }
//public float Unknown_24 { get; set; }
public Matrix3_s(Vector4 row1, Vector4 row2, Vector4 row3)
{
Row1 = row1;
Row2 = row2;
Row3 = row3;
}
public Matrix3_s(float[] a)
{
@ -215,8 +221,8 @@ namespace CodeWalker.GameFiles
return new Matrix(Column1.X, Column1.Y, Column1.Z, 0, Column2.X, Column2.Y, Column2.Z, 0, Column3.X, Column3.Y, Column3.Z, 0, Column4.X, Column4.Y, Column4.Z, 1);
}
public static Matrix4F_s Identity => new Matrix4F_s(true);
public static Matrix4F_s Zero => new Matrix4F_s(false);
public readonly static Matrix4F_s Identity = new Matrix4F_s(true);
public readonly static Matrix4F_s Zero = new Matrix4F_s(false);
}

View File

@ -25,7 +25,7 @@ using System.Threading.Tasks;
namespace CodeWalker.GameFiles
{
public struct FileCounts
public record struct FileCounts
{
public uint Rpfs;
public uint Files;
@ -54,16 +54,6 @@ namespace CodeWalker.GameFiles
BinaryFiles = a.BinaryFiles + b.BinaryFiles
};
}
public static bool operator ==(in FileCounts left, in FileCounts right)
{
return left.Equals(in right);
}
public static bool operator !=(in FileCounts left, in FileCounts right)
{
return !(left == right);
}
}
public class RpfFile
@ -285,7 +275,6 @@ namespace CodeWalker.GameFiles
rfe.IsEncrypted = rfe.IsExtension(".ysc");//any other way to know..?
}
allEntries.Add(e);
}
@ -548,11 +537,18 @@ namespace CodeWalker.GameFiles
}
}
}
private Dictionary<string, RpfEntry[]>? FilesByFileType;
public RpfEntry[] GetFilesByFileType(string ext)
{
FilesByFileType ??= new Dictionary<string, RpfEntry[]>();
if (!FilesByFileType.TryGetValue(ext, out var entries))
{
entries = AllEntries.Where(p => p.IsExtension(ext)).ToArray();
FilesByFileType[ext] = entries;
}
return entries;
}

View File

@ -64,14 +64,16 @@ namespace CodeWalker.GameFiles
BaseRpfs = new List<RpfFile>(1300);
ModRpfs = new List<RpfFile>(0);
DlcRpfs = new List<RpfFile>(3500);
AllRpfs = new List<RpfFile>(5000);
AllRpfs ??= new List<RpfFile>(0);
DlcNoModRpfs = new List<RpfFile>(3500);
AllNoModRpfs = new List<RpfFile>(5000);
RpfDict = new Dictionary<string, RpfFile>(DefaultRpfDictCapacity, StringComparer.OrdinalIgnoreCase);
EntryDict = new Dictionary<string, RpfEntry>(DefaultEntryDictCapacity, StringComparer.OrdinalIgnoreCase);
RpfDict = new Dictionary<string, RpfFile>(StringComparer.OrdinalIgnoreCase);
EntryDict = new Dictionary<string, RpfEntry>(StringComparer.OrdinalIgnoreCase);
ModRpfDict = new Dictionary<string, RpfFile>(StringComparer.OrdinalIgnoreCase);
ModEntryDict = new Dictionary<string, RpfEntry>(StringComparer.OrdinalIgnoreCase);
FileCounts _fileCounts = default;
var rpfs = new ConcurrentBag<RpfFile>();
Parallel.ForEach(allfiles, (rpfpath) =>
{
@ -79,21 +81,27 @@ namespace CodeWalker.GameFiles
{
RpfFile rf = new RpfFile(rpfpath, rpfpath.Replace(replpath, ""));
if (ExcludePaths != null)
if (ExcludePaths is not null)
{
bool excl = false;
for (int i = 0; i < ExcludePaths.Length; i++)
foreach(var path in ExcludePaths)
{
if (rf.Path.StartsWith(ExcludePaths[i], StringComparison.OrdinalIgnoreCase))
if (rf.Path.StartsWith(path, StringComparison.OrdinalIgnoreCase))
{
excl = true;
break;
}
}
if (excl) return; //skip files in exclude paths.
if (excl)
return; //skip files in exclude paths.
}
rf.ScanStructure(updateStatus, errorLog, out _);
rf.ScanStructure(updateStatus, errorLog, out var fileCounts);
lock(this)
{
_fileCounts += fileCounts;
}
if (rf.LastException != null) //incase of corrupted rpf (or renamed NG encrypted RPF)
{
@ -115,13 +123,12 @@ namespace CodeWalker.GameFiles
return rpf.AllEntries?.Count ?? 0 + rpf.Children?.Sum(calculateSum) ?? 0;
}
var minCapacity = rpfs.Sum(calculateSum);
if (minCapacity > AllRpfs.Capacity)
{
AllRpfs.Capacity = minCapacity;
}
AllRpfs.EnsureCapacity((int)_fileCounts.Rpfs);
RpfDict.EnsureCapacity((int)_fileCounts.Rpfs);
AllNoModRpfs.EnsureCapacity((int)_fileCounts.Rpfs);
EntryDict.EnsureCapacity((int)_fileCounts.Rpfs + (int)_fileCounts.Files);
foreach(var rpf in rpfs)
foreach (var rpf in rpfs)
{
AddRpfFile(rpf, false, false);
}
@ -142,8 +149,7 @@ namespace CodeWalker.GameFiles
IsInited = true;
}
Console.WriteLine($"fileCounts: {_fileCounts}");
Console.WriteLine($"AllRpfs: {AllRpfs.Count}; RpfDict: {RpfDict.Count}; EntryDict: {EntryDict.Count}; BaseRpfs: {BaseRpfs.Count}; ModRpfs: {ModRpfs.Count}; DlcRpfs: {DlcRpfs.Count}; DlcNoModRpfs: {DlcNoModRpfs.Count}; AllNoModRpfs: {AllNoModRpfs.Count}; ModRpfDict: {ModRpfDict.Count}; ModEntryDict: {ModEntryDict.Count}");
}
@ -191,7 +197,7 @@ namespace CodeWalker.GameFiles
isdlc = isdlc || (file.Name.EndsWith(".rpf", StringComparison.OrdinalIgnoreCase) && (file.Name.StartsWith("dlc", StringComparison.OrdinalIgnoreCase) || file.Name.Equals("update.rpf", StringComparison.OrdinalIgnoreCase)));
ismod = ismod || (file.Path.StartsWith("mods\\", StringComparison.OrdinalIgnoreCase));
if (file.AllEntries != null)
if (file.AllEntries is not null)
{
AllRpfs.Add(file);
if (!ismod)
@ -237,7 +243,7 @@ namespace CodeWalker.GameFiles
}
else
{
EntryDict[entry.Path] = entry;
_ = EntryDict.TryAdd(entry.Path, entry);
}
}
@ -246,7 +252,7 @@ namespace CodeWalker.GameFiles
{
file.LastError = ex.ToString();
file.LastException = ex;
ErrorLog?.Invoke(entry.Path + ": " + ex.ToString());
ErrorLog?.Invoke($"{entry.Path}: {ex}");
}
}
}

View File

@ -40,6 +40,7 @@ using static System.Runtime.InteropServices.JavaScript.JSType;
namespace CodeWalker.GameFiles
{
[SkipLocalsInit]
public class GTACrypto
{
@ -232,8 +233,8 @@ namespace CodeWalker.GameFiles
table[15][data[15]] ^
key[3];
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data[..8]), x1 | (ulong)x2 << 32);
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data.Slice(8, 8)), x3 | (ulong)x4 << 32);
Unsafe.WriteUnaligned(ref data[0], x1 | (ulong)x2 << 32);
Unsafe.WriteUnaligned(ref data[8], x3 | (ulong)x4 << 32);
//Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data[..4]), x1);
//Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data.Slice(4, 4)), x2);
@ -271,8 +272,8 @@ namespace CodeWalker.GameFiles
table[12][data[12]] ^
key[3];
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data[..8]), x1 | (ulong)x2 << 32);
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data.Slice(8, 8)), x3 | (ulong)x4 << 32);
Unsafe.WriteUnaligned(ref data[0], x1 | (ulong)x2 << 32);
Unsafe.WriteUnaligned(ref data[8], x3 | (ulong)x4 << 32);
//Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data[..4]), x1);
//Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data.Slice(4, 4)), x2);

View File

@ -292,6 +292,7 @@ namespace CodeWalker.GameFiles
}
}
[SkipLocalsInit]
public static class JenkIndex
{
//public static ConcurrentDictionary<uint, string> Index = new ConcurrentDictionary<uint, string>(Environment.ProcessorCount * 2, 2000000);
@ -400,6 +401,7 @@ namespace CodeWalker.GameFiles
}
return res;
}
public static string TryGetString(uint hash)
{
string res;
@ -417,8 +419,5 @@ namespace CodeWalker.GameFiles
var res = Index.Values;
return res;
}
}
}

View File

@ -1171,7 +1171,7 @@ namespace CodeWalker.GameFiles
}
catch (Exception ex)
{
UpdateStatus?.Invoke("Error! " + ex.ToString());
UpdateStatus?.Invoke($"Error! {ex}");
errorfiles.Add(entry);
}
if (xmltest && (ybn != null) && (ybn.Bounds != null))
@ -1179,18 +1179,16 @@ namespace CodeWalker.GameFiles
var xml = YbnXml.GetXml(ybn);
var ybn2 = XmlYbn.GetYbn(xml);
var xml2 = YbnXml.GetXml(ybn2);
if (xml.Length != xml2.Length)
{ }
}
if (savetest && (ybn != null) && (ybn.Bounds != null))
{
if (entry is not RpfFileEntry fentry)
{ continue; } //shouldn't happen
continue; //shouldn't happen
var bytes = ybn.Save();
if (!reloadtest)
{ continue; }
continue;
string origlen = TextUtil.GetBytesReadable(fentry.FileSize);
string bytelen = TextUtil.GetBytesReadable(bytes.Length);
@ -1200,9 +1198,9 @@ namespace CodeWalker.GameFiles
RpfFile.LoadResourceFile(ybn2, bytes, 43);
if (ybn2.Bounds == null)
{ continue; }
continue;
if (ybn2.Bounds.Type != ybn.Bounds.Type)
{ continue; }
continue;
//quick check of roundtrip
switch (ybn2.Bounds.Type)
@ -1211,30 +1209,30 @@ namespace CodeWalker.GameFiles
{
var a = ybn.Bounds as BoundSphere;
if (ybn2.Bounds is not BoundSphere b)
{ continue; }
continue;
break;
}
case BoundsType.Capsule:
{
var a = ybn.Bounds as BoundCapsule;
if (ybn2.Bounds is not BoundCapsule b)
{ continue; }
continue;
break;
}
case BoundsType.Box:
{
var a = ybn.Bounds as BoundBox;
if (ybn2.Bounds is not BoundBox b)
{ continue; }
continue;
break;
}
case BoundsType.Geometry:
{
var a = ybn.Bounds as BoundGeometry;
if (ybn2.Bounds is not BoundGeometry b)
{ continue; }
continue;
if (a.Polygons?.Length != b.Polygons?.Length)
{ continue; }
continue;
for (int i = 0; i < a.Polygons.Length; i++)
{
var pa = a.Polygons[i];
@ -1248,17 +1246,15 @@ namespace CodeWalker.GameFiles
{
var a = ybn.Bounds as BoundBVH;
if (ybn2.Bounds is not BoundBVH b)
{ continue; }
continue;
if (a.BVH?.Nodes?.data_items?.Length != b.BVH?.Nodes?.data_items?.Length)
{ }
if (a.Polygons?.Length != b.Polygons?.Length)
{ continue; }
continue;
for (int i = 0; i < a.Polygons.Length; i++)
{
var pa = a.Polygons[i];
var pb = b.Polygons[i];
if (pa.Type != pb.Type)
{ }
}
break;
}
@ -1275,7 +1271,7 @@ namespace CodeWalker.GameFiles
{
var a = ybn.Bounds as BoundDisc;
if (ybn2.Bounds is not BoundDisc b)
{ continue; }
continue;
break;
}
case BoundsType.Cylinder:

View File

@ -12,16 +12,6 @@ namespace CodeWalker
public static class MatrixExtensions
{
public static Vector3 MultiplyW(in this Matrix m, Vector3 v)
{
float x = (((m.M11 * v.X) + (m.M21 * v.Y)) + (m.M31 * v.Z)) + m.M41;
float y = (((m.M12 * v.X) + (m.M22 * v.Y)) + (m.M32 * v.Z)) + m.M42;
float z = (((m.M13 * v.X) + (m.M23 * v.Y)) + (m.M33 * v.Z)) + m.M43;
float w = (((m.M14 * v.X) + (m.M24 * v.Y)) + (m.M34 * v.Z)) + m.M44;
float iw = 1.0f / Math.Abs(w);
return new Vector3(x * iw, y * iw, z * iw);
}
public static Vector3 MultiplyW(in this Matrix m, in Vector3 v)
{
float x = (((m.M11 * v.X) + (m.M21 * v.Y)) + (m.M31 * v.Z)) + m.M41;
@ -32,7 +22,7 @@ namespace CodeWalker
return new Vector3(x * iw, y * iw, z * iw);
}
public static Vector3 Multiply(in this Matrix m, Vector3 v)
public static Vector3 Multiply(in this Matrix m, in Vector3 v)
{
float x = (((m.M11 * v.X) + (m.M21 * v.Y)) + (m.M31 * v.Z)) + m.M41;
float y = (((m.M12 * v.X) + (m.M22 * v.Y)) + (m.M32 * v.Z)) + m.M42;
@ -40,7 +30,7 @@ namespace CodeWalker
return new Vector3(x, y, z);
//this quick mul ignores W...
}
public static Vector3 MultiplyRot(in this Matrix m, Vector3 v)
public static Vector3 MultiplyRot(in this Matrix m, in Vector3 v)
{
float x = (((m.M11 * v.X) + (m.M21 * v.Y)) + (m.M31 * v.Z));// + m.M41;
float y = (((m.M12 * v.X) + (m.M22 * v.Y)) + (m.M32 * v.Z));// + m.M42;
@ -49,7 +39,7 @@ namespace CodeWalker
//this quick mul ignores W and translation...
}
public static Vector4 Multiply(in this Matrix m, Vector4 v)
public static Vector4 Multiply(in this Matrix m, in Vector4 v)
{
float x = (((m.M11 * v.X) + (m.M21 * v.Y)) + (m.M31 * v.Z)) + m.M41;
float y = (((m.M12 * v.X) + (m.M22 * v.Y)) + (m.M32 * v.Z)) + m.M42;

View File

@ -1,4 +1,6 @@
using Collections.Pooled;
using CodeWalker.GameFiles;
using Collections.Pooled;
using CommunityToolkit.Diagnostics;
using CommunityToolkit.HighPerformance.Buffers;
using Microsoft.Extensions.ObjectPool;
using System;
@ -19,31 +21,49 @@ namespace CodeWalker.Core.Utils
public class PooledListObjectPolicy<T> : PooledObjectPolicy<PooledList<T>>
{
private readonly ClearMode clearMode;
public PooledListObjectPolicy(ClearMode _clearMode = ClearMode.Auto)
{
clearMode = _clearMode;
}
public PooledList<T> Get()
{
return new PooledList<T>();
return new PooledList<T>(clearMode);
}
public override PooledList<T> Create()
{
return new PooledList<T>();
return new PooledList<T>(clearMode);
}
public override bool Return(PooledList<T> list)
{
foreach (var entry in list.Span)
{
if (entry is IDisposable disposable)
disposable.Dispose();
if (entry is IResettable resettable)
resettable.TryReset();
}
list.Clear();
return true;
}
}
public static class PooledListPool<T>
{
private static readonly ObjectPool<PooledList<T>> s_shared = ObjectPool.Create(new PooledListObjectPolicy<T>(ClearMode.Never));
public static ObjectPool<PooledList<T>> Shared => s_shared;
}
public static class PooledListExtensions
{
public static int EnsureCapacity<T>(this PooledList<T> list, int capacity)
{
ArgumentOutOfRangeException.ThrowIfLessThan(capacity, 0, nameof(capacity));
if (list.Capacity < capacity)
{
list.Capacity = capacity;
}
return list.Capacity;
}
}
public static class StringPoolExtension
{
[SkipLocalsInit]

View File

@ -1,46 +1,99 @@
using System;
using Microsoft.Extensions.ObjectPool;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Xml.Linq;
namespace CodeWalker.Core.Utils
{
public class DisposableTimer : IDisposable
public class DisposableTimer : Stopwatch, IDisposable, IResettable
{
public static event Action<TimeSpan, string> TimerStopped;
public Stopwatch Stopwatch { get; init; }
public static event Action<TimeSpan, string> OnDispose;
public Stopwatch Stopwatch => this;
static DisposableTimer()
{
#if DEBUG
TimerStopped += (timeSpan, name) => Debug.WriteLine($"{name} took {timeSpan.TotalMilliseconds} ms");
#endif
TimerStopped += (timeSpan, name) => Console.WriteLine($"{name} took {timeSpan.TotalMilliseconds} ms");
OnDispose += (timeSpan, name) => Console.WriteLine($"{name} took {timeSpan.TotalMilliseconds} ms");
}
public string Name { get; private set; }
public DisposableTimer(string name)
public DisposableTimer([CallerMemberName] string name = "") : base()
{
Stopwatch = Stopwatch.StartNew();
Start();
Name = name;
}
public DisposableTimer(string name, bool start)
{
Name = name;
if (start)
{
Stopwatch = Stopwatch.StartNew();
} else
{
Stopwatch = new Stopwatch();
Start();
}
}
public void Dispose()
{
Stopwatch.Stop();
TimerStopped?.Invoke(Stopwatch.Elapsed, Name ?? string.Empty);
Stop();
OnDispose?.Invoke(Elapsed, Name ?? string.Empty);
GC.SuppressFinalize(this);
}
public bool TryReset()
{
Reset();
return true;
}
}
public class SummableDisposableTimer : DisposableTimer
{
public event Action<TimeSpan, string> OnDispose;
public SummableDisposableTimer([CallerMemberName] string name = "") : base(name)
{
}
public void Dispose()
{
Stop();
}
}
public class DisposableTimerSummed : IDisposable
{
private long _elapsed;
public TimeSpan TimeSpan => new TimeSpan(_elapsed);
public string Name { get; set; }
public DisposableTimerSummed([CallerMemberName] string name = "")
{
Name = name;
}
public DisposableTimer GetTimer([CallerMemberName] string name = "")
{
var timer = new SummableDisposableTimer(name);
timer.OnDispose += (time, _) =>
{
Interlocked.Add(ref _elapsed, time.Ticks);
};
return timer;
}
public void Dispose()
{
Console.WriteLine($"{Name} took {TimeSpan.TotalMilliseconds} ms");
GC.SuppressFinalize(this);
}
}
}

View File

@ -0,0 +1,186 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace CodeWalker.Core.Utils.TypeConverters;
public class LinkedListPropertyDescripter<T> : PropertyDescriptor
{
private readonly LinkedListNode<T> node;
public LinkedListPropertyDescripter(LinkedListNode<T> node)
: base(CSharpName(node.Value.GetType()), null)
{
this.node = node;
}
private static string CSharpName(Type type)
{
var sb = new StringBuilder();
var name = type.Name;
if (!type.IsGenericType)
return name;
sb.Append(name.Substring(0, name.IndexOf('`')));
sb.Append("<");
sb.Append(string.Join(", ", type.GetGenericArguments()
.Select(CSharpName)));
sb.Append(">");
return sb.ToString();
}
public override object GetValue(object component)
{
return node.Value;
}
public override bool IsReadOnly => true;
public override string Name => node.Value.ToString();
public override Type PropertyType => node.Value.GetType();
public override Type ComponentType => node.List.GetType();
public override bool ShouldSerializeValue(object component) => false;
public override bool CanResetValue(object component) => false;
public override void ResetValue(object component)
{
}
public override void SetValue(object component, object value)
{
}
}
public class LinkedListConverter<T> : CollectionConverter
{
public override bool GetPropertiesSupported(ITypeDescriptorContext context) => true;
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
LinkedList<T> list = value as LinkedList<T>;
if (list == null || list.Count == 0)
return base.GetProperties(context, value, attributes);
var items = new PropertyDescriptorCollection(null);
foreach (var item in list)
{
var node = list.Find(item);
items.Add(new LinkedListPropertyDescripter<T>(node));
}
return items;
}
}
public class ExpandableCollectionPropertyDescriptor : PropertyDescriptor
{
private IList collection;
private readonly int _index;
public ExpandableCollectionPropertyDescriptor(IList coll, int idx)
: base(GetDisplayName(coll, idx), null)
{
collection = coll;
_index = idx;
}
private static string GetDisplayName(IList list, int index)
{
return $"[{index}] " + CSharpName(list[index].GetType());
}
private static string CSharpName(Type type)
{
var sb = new StringBuilder();
var name = type.Name;
if (!type.IsGenericType)
return name;
sb.Append(name.Substring(0, name.IndexOf('`')));
sb.Append("<");
sb.Append(string.Join(", ", type.GetGenericArguments()
.Select(CSharpName)));
sb.Append(">");
return sb.ToString();
}
public override bool CanResetValue(object component)
{
return true;
}
public override Type ComponentType
{
get { return this.collection.GetType(); }
}
public override object GetValue(object component)
{
return collection[_index];
}
public override bool IsReadOnly
{
get { return false; }
}
public override string Name
{
get { return _index.ToString(CultureInfo.InvariantCulture); }
}
public override Type PropertyType
{
get { return collection[_index].GetType(); }
}
public override void ResetValue(object component)
{
}
public override bool ShouldSerializeValue(object component)
{
return true;
}
public override void SetValue(object component, object value)
{
collection[_index] = value;
}
}
public class ListConverter : CollectionConverter
{
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
IList list = value as IList;
if (list == null || list.Count == 0)
return base.GetProperties(context, value, attributes);
var items = new PropertyDescriptorCollection(null);
for (int i = 0; i < list.Count; i++)
{
object item = list[i];
items.Add(new ExpandableCollectionPropertyDescriptor(list, i));
}
return items;
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
@ -21,8 +22,31 @@ namespace CodeWalker
public static class TextUtil
{
static string[] sizeSuffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB"];
public static string GetBytesReadable(long i)
public static string GetBytesReadable(long size)
{
//shamelessly stolen from stackoverflow, and a bit mangled
// Returns the human-readable file size for an arbitrary, 64-bit file size
// The default format is "0.### XB", e.g. "4.2 KB" or "1.434 GB"
// Get absolute value
Debug.Assert(sizeSuffixes.Length > 0);
if (size == 0)
{
return "0 B";
}
var absSize = Math.Abs(size);
var fpPower = Math.Log(absSize, 1024);
var intPower = (int)fpPower;
var normSize = absSize / Math.Pow(1024, intPower);
return $"{normSize:G4} {sizeSuffixes[intPower]}";
}
public static string GetBytesReadableOld(long i)
{
//shamelessly stolen from stackoverflow, and a bit mangled

View File

@ -8,6 +8,18 @@ using CodeWalker.GameFiles;
namespace CodeWalker
{
public static class Matrices
{
public static Matrix FromVectors(in Vector4 row1, in Vector4 row2, in Vector4 row3, in Vector4 row4)
{
return new Matrix(
row1.X, row1.Y, row1.Z, row1.Z,
row2.X, row2.Y, row2.Z, row2.Z,
row3.X, row3.Y, row3.Z, row3.Z,
row4.X, row4.Y, row4.Z, row4.Z
);
}
}
public static class Vectors
{
public static Vector3 XYZ(in this Vector4 v)

View File

@ -11,9 +11,11 @@ using System.Xml.Linq;
using CodeWalker.Core.Utils;
using System.Runtime.InteropServices;
using System.Diagnostics.Metrics;
using System.Runtime.CompilerServices;
namespace CodeWalker.World
{
[SkipLocalsInit]
public class Scenarios
{
public volatile bool Inited = false;
@ -32,9 +34,10 @@ namespace CodeWalker.World
Timecycle = timecycle;
GameFileCache = gameFileCache;
EnsureScenarioTypes(gameFileCache);
using (new DisposableTimer("EnsureScenarioTypes"))
{
await EnsureScenarioTypes(gameFileCache);
}
ScenarioRegions.Clear();
@ -56,10 +59,15 @@ namespace CodeWalker.World
YmtFile? manifestymt = await rpfman.GetFileAsync<YmtFile>(manifestfilename);
if (manifestymt is not null && manifestymt.CScenarioPointManifest is not null)
{
var regionDefs = manifestymt.CScenarioPointManifest.RegionDefs;
foreach (var region in manifestymt.CScenarioPointManifest.RegionDefs)
ScenarioRegions.EnsureCapacity(regionDefs.Length);
using var timerSummed = new DisposableTimerSummed("LoadYmtFile");
await Parallel.ForAsync(0, regionDefs.Length, async (i, cancellationToken) =>
{
string regionfilename = region.Name.ToString() + ".ymt"; //JenkIndex lookup... ymt should have already loaded path strings into it! maybe change this...
var region = regionDefs[i];
string regionfilename = $"{region.Name}.ymt"; //JenkIndex lookup... ymt should have already loaded path strings into it! maybe change this...
string basefilename = regionfilename.Replace("platform:", "x64a.rpf");
string updfilename = regionfilename.Replace("platform:", "update\\update.rpf\\x64");
string usefilename = updfilename;
@ -68,14 +76,19 @@ namespace CodeWalker.World
{
usefilename = basefilename;
}
YmtFile? regionymt = await rpfman.GetFileAsync<YmtFile>(usefilename) ?? await rpfman.GetFileAsync<YmtFile>(basefilename);
//YmtFile? regionymt = await rpfman.GetFileAsync<YmtFile>(usefilename) ?? await rpfman.GetFileAsync<YmtFile>(basefilename);
if (regionymt is not null)
{
var sregion = regionymt.ScenarioRegion;
if (sregion != null)
if (sregion is not null)
{
ScenarioRegions.Add(regionymt);
lock(ScenarioRegions)
{
ScenarioRegions.Add(regionymt);
}
@ -90,23 +103,19 @@ namespace CodeWalker.World
//System.IO.File.WriteAllBytes("C:\\CodeWalker.Projects\\YmtTest\\AllYmts\\" + regionymt.Name, data);
}
}
}
});
}
Inited = true;
}
public static void EnsureScenarioTypes(GameFileCache gfc)
public static async Task EnsureScenarioTypes(GameFileCache gfc)
{
//if (ScenarioTypes == null)
//{
var stypes = new ScenarioTypes();
stypes.Load(gfc);
await stypes.LoadAsync(gfc);
ScenarioTypes = stypes;
//}
}
@ -294,67 +303,66 @@ namespace CodeWalker.World
EnsureNode(node);
}
List<MCScenarioChainingEdge> chainedges = new List<MCScenarioChainingEdge>();
if ((r.Paths.Chains != null) && (r.Paths.Edges != null))
if (r.Paths.Chains is not null && r.Paths.Edges is not null)
{
List<MCScenarioChainingEdge> chainedges = new List<MCScenarioChainingEdge>();
var rp = r.Paths;
var rpc = rp.Chains;
var rpe = rp.Edges;
var rpeLength = rp.Edges.Length;
var rpn = rp.Nodes;
var rpnLength = rpn.Length;
foreach (var chain in rpc)
{
chainedges.Clear();
if (chain.EdgeIds != null)
if (chain.EdgeIds is not null)
{
chainedges.Clear();
foreach (var edgeId in chain.EdgeIds)
{
if (edgeId >= rpe.Length)
{ continue; }
if (edgeId >= rpeLength)
continue;
var edge = rpe[edgeId];
if (edge.NodeIndexFrom >= rpn.Length)
{ continue; }
if (edge.NodeIndexTo >= rpn.Length)
{ continue; }
if (edge.NodeIndexFrom >= rpnLength)
continue;
if (edge.NodeIndexTo >= rpnLength)
continue;
edge.NodeFrom = rpn[edge.NodeIndexFrom];
edge.NodeTo = rpn[edge.NodeIndexTo];
var nfc = edge.NodeFrom?.Chain;
var ntc = edge.NodeTo?.Chain;
if ((nfc != null) && (nfc != chain))
{ }
if ((ntc != null) && (ntc != chain))
{ }
if (edge.NodeFrom != null) edge.NodeFrom.Chain = chain;
if (edge.NodeTo != null) edge.NodeTo.Chain = chain;
if (edge.NodeFrom is not null)
edge.NodeFrom.Chain = chain;
if (edge.NodeTo is not null)
edge.NodeTo.Chain = chain;
chainedges.Add(edge);
}
}
chain.Edges = chainedges.ToArray();
chain.Edges = chainedges.ToArray();
}
else
{
chain.Edges = [];
}
}
}
}
if (r.Points != null)
if (r.Points is not null)
{
if (r.Points.MyPoints != null)
if (r.Points.MyPoints is not null)
{
foreach (var point in r.Points.MyPoints)
{
EnsureNode(point);
}
}
if (r.Points.LoadSavePoints != null)
if (r.Points.LoadSavePoints is not null)
{
foreach (var point in r.Points.LoadSavePoints)
{
@ -363,15 +371,15 @@ namespace CodeWalker.World
}
}
if (r.Clusters != null) //spawn groups
if (r.Clusters is not null) //spawn groups
{
foreach (var cluster in r.Clusters)
{
EnsureClusterNode(cluster);
if (cluster.Points != null)
if (cluster.Points is not null)
{
if (cluster.Points.MyPoints != null)
if (cluster.Points.MyPoints is not null)
{
foreach (var point in cluster.Points.MyPoints)
{
@ -379,7 +387,7 @@ namespace CodeWalker.World
node.Cluster = cluster;
}
}
if (cluster.Points.LoadSavePoints != null)
if (cluster.Points.LoadSavePoints is not null)
{
foreach (var point in cluster.Points.LoadSavePoints)
{
@ -391,13 +399,13 @@ namespace CodeWalker.World
}
}
if (r.EntityOverrides != null)
if (r.EntityOverrides is not null)
{
foreach (var overr in r.EntityOverrides)
{
EnsureEntityNode(overr);
if (overr.ScenarioPoints != null)
if (overr.ScenarioPoints is not null)
{
foreach (var point in overr.ScenarioPoints)
{
@ -407,10 +415,8 @@ namespace CodeWalker.World
}
}
}
}
//Nodes = NodeDict.Values.ToList();
}
@ -422,47 +428,46 @@ namespace CodeWalker.World
public void BuildVertices()
{
List<EditorVertex> pathverts = new List<EditorVertex>();
PathVerts = [];
uint cred = (uint)Color.Red.ToRgba();
uint cblu = (uint)Color.Blue.ToRgba();
uint cgrn = (uint)Color.Green.ToBgra();
uint cblk = (uint)Color.Black.ToBgra();
if (Ymt?.CScenarioPointRegion is not null)
var r = Ymt?.CScenarioPointRegion?.Paths;
if (r?.Nodes is not null && r?.Chains is not null && r?.Edges is not null)
{
var r = Ymt.CScenarioPointRegion;
List<EditorVertex> pathverts = new List<EditorVertex>();
if ((r.Paths != null) && (r.Paths.Nodes != null))
var nodes = r.Nodes;
var nodesLength = nodes.Length;
foreach (var chain in r.Chains)
{
if ((r.Paths.Chains != null) && (r.Paths.Edges != null))
{
foreach (var chain in r.Paths.Chains)
{
if (chain.Edges == null) continue;
foreach (var edge in chain.Edges)
{
var vid1 = edge._Data.NodeIndexFrom;
var vid2 = edge._Data.NodeIndexTo;
if ((vid1 >= r.Paths.Nodes.Length) || (vid2 >= r.Paths.Nodes.Length)) continue;
var v1 = r.Paths.Nodes[vid1];
var v2 = r.Paths.Nodes[vid2];
byte cr1 = (v1.HasIncomingEdges) ? (byte)255 : (byte)0;
byte cr2 = (v2.HasIncomingEdges) ? (byte)255 : (byte)0;
byte cg = 0;// (chain._Data.Unk_1156691834 > 1) ? (byte)255 : (byte)0;
//cg = ((v1.Unk1 != 0) || (v2.Unk1 != 0)) ? (byte)255 : (byte)0;
//cg = (edge.Action == CScenarioChainingEdge__eAction.Unk_7865678) ? (byte)255 : (byte)0;
//cg = ((v1.UnkValTest != 0) || (v2.UnkValTest != 0)) ? (byte)255 : (byte)0;
if (chain.Edges is null)
continue;
byte cb1 = (byte)(255 - cr1);
byte cb2 = (byte)(255 - cr2);
var colour1 = (uint)new Color(cr1, cg, cb1, (byte)255).ToRgba();
var colour2 = (uint)new Color(cr2, cg, cb2, (byte)255).ToRgba();
pathverts.Add(new EditorVertex(v1.Position, colour1));
pathverts.Add(new EditorVertex(v2.Position, colour2));
}
}
foreach (var edge in chain.Edges)
{
var vid1 = edge._Data.NodeIndexFrom;
var vid2 = edge._Data.NodeIndexTo;
if ((vid1 >= nodesLength) || (vid2 >= nodesLength))
continue;
var v1 = nodes[vid1];
var v2 = nodes[vid2];
byte cr1 = (v1.HasIncomingEdges) ? (byte)255 : (byte)0;
byte cr2 = (v2.HasIncomingEdges) ? (byte)255 : (byte)0;
byte cg = 0;// (chain._Data.Unk_1156691834 > 1) ? (byte)255 : (byte)0;
//cg = ((v1.Unk1 != 0) || (v2.Unk1 != 0)) ? (byte)255 : (byte)0;
//cg = (edge.Action == CScenarioChainingEdge__eAction.Unk_7865678) ? (byte)255 : (byte)0;
//cg = ((v1.UnkValTest != 0) || (v2.UnkValTest != 0)) ? (byte)255 : (byte)0;
byte cb1 = (byte)(255 - cr1);
byte cb2 = (byte)(255 - cr2);
var colour1 = (uint)new Color(cr1, cg, cb1, (byte)255).ToRgba();
var colour2 = (uint)new Color(cr2, cg, cb2, (byte)255).ToRgba();
pathverts.Add(new EditorVertex(v1.Position, colour1));
pathverts.Add(new EditorVertex(v2.Position, colour2));
}
}
@ -503,28 +508,21 @@ namespace CodeWalker.World
// }
//}
if (pathverts.Count > 0)
{
PathVerts = pathverts.ToArray();
}
}
if (pathverts.Count > 0)
var _nodes = Nodes;
var count = _nodes.Count;
Vector4[] nodePositions = new Vector4[count];
for (int i = 0; i < count; i++)
{
PathVerts = pathverts.ToArray();
}
else
{
PathVerts = [];
nodePositions[i] = new Vector4(_nodes[i].Position, 1.0f);
}
Vector4[] nodes = new Vector4[Nodes.Count];
for (int i = 0; i < Nodes.Count; i++)
{
nodes[i] = new Vector4(Nodes[i].Position, 1.0f);
}
NodePositions = nodes;
NodePositions = nodePositions;
}
@ -582,7 +580,6 @@ namespace CodeWalker.World
exnode.LoadSavePoint = point;
exnode.Position = point.Position;
exnode.Orientation = point.Orientation;
NodeDict[point.Position] = exnode;
Nodes.Add(exnode);
}
return exnode;
@ -599,7 +596,6 @@ namespace CodeWalker.World
exnode = new ScenarioNode(cluster.Region?.Ymt);
exnode.Cluster = cluster;
exnode.Position = cluster.Position;
NodeDict[cluster.Position] = exnode;
Nodes.Add(exnode);
}
return exnode;
@ -618,7 +614,6 @@ namespace CodeWalker.World
exnode.ClusterMyPoint = point;
exnode.Position = point.Position;
exnode.Orientation = point.Orientation;
NodeDict[point.Position] = exnode;
Nodes.Add(exnode);
}
return exnode;
@ -635,7 +630,6 @@ namespace CodeWalker.World
exnode = new ScenarioNode(point.ScenarioRegion?.Ymt);
exnode.ClusterLoadSavePoint = point;
exnode.Position = point.Position;
NodeDict[point.Position] = exnode;
Nodes.Add(exnode);
}
return exnode;
@ -653,7 +647,6 @@ namespace CodeWalker.World
exnode.EntityPoint = point;
exnode.Position = point.Position;
exnode.Orientation = point.Orientation;
NodeDict[point.Position] = exnode;
Nodes.Add(exnode);
}
return exnode;
@ -670,7 +663,6 @@ namespace CodeWalker.World
exnode = new ScenarioNode(entity.Region?.Ymt);
exnode.Entity = entity;
exnode.Position = entity.Position;
NodeDict[entity.Position] = exnode;
Nodes.Add(exnode);
}
return exnode;
@ -733,21 +725,24 @@ namespace CodeWalker.World
}
if ((Region != null) && (Region.Points != null))
if (Region is not null && Region.Points is not null)
{
if (n.MyPoint != null) Region.Points.AddMyPoint(n.MyPoint);
if (n.LoadSavePoint != null) Region.Points.AddLoadSavePoint(n.LoadSavePoint);
if ((n.Cluster != null) && (n.Cluster.Points != null))
if (n.Cluster is not null && n.Cluster.Points is not null)
{
if (n.ClusterMyPoint != null) n.Cluster.Points.AddMyPoint(n.ClusterMyPoint);
if (n.ClusterLoadSavePoint != null) n.Cluster.Points.AddLoadSavePoint(n.ClusterLoadSavePoint);
if (n.ClusterMyPoint is not null)
n.Cluster.Points.AddMyPoint(n.ClusterMyPoint);
if (n.ClusterLoadSavePoint is not null)
n.Cluster.Points.AddLoadSavePoint(n.ClusterLoadSavePoint);
}
if ((n.Entity != null) && (n.Entity.ScenarioPoints != null))
if ((n.Entity is not null) && (n.Entity.ScenarioPoints is not null))
{
if (n.EntityPoint != null) n.Entity.AddScenarioPoint(n.EntityPoint);
if (n.EntityPoint is not null)
n.Entity.AddScenarioPoint(n.EntityPoint);
}
}
if ((Region != null) && (Region.Paths != null))
if (Region is not null && Region.Paths is not null)
{
if (n.ChainingNode != null)
{
@ -1701,37 +1696,50 @@ namespace CodeWalker.World
public class ScenarioTypes
{
private readonly object SyncRoot = new object(); //keep this thread-safe.. technically shouldn't be necessary, but best to be safe
private Dictionary<uint, ScenarioTypeRef> TypeRefs { get; set; }
private Dictionary<uint, ScenarioType> Types { get; set; }
private Dictionary<uint, ScenarioTypeGroup> TypeGroups { get; set; }
private Dictionary<uint, AmbientModelSet> PropSets { get; set; }
private Dictionary<uint, AmbientModelSet> PedModelSets { get; set; }
private Dictionary<uint, AmbientModelSet> VehicleModelSets { get; set; }
private Dictionary<uint, ConditionalAnimsGroup> AnimGroups { get; set; }
private Dictionary<uint, ScenarioTypeRef>? TypeRefs { get; set; }
private Dictionary<uint, ScenarioType>? Types { get; set; }
private Dictionary<uint, ScenarioTypeGroup>? TypeGroups { get; set; }
private Dictionary<uint, AmbientModelSet>? PropSets { get; set; }
private Dictionary<uint, AmbientModelSet>? PedModelSets { get; set; }
private Dictionary<uint, AmbientModelSet>? VehicleModelSets { get; set; }
private Dictionary<uint, ConditionalAnimsGroup>? AnimGroups { get; set; }
public void Load(GameFileCache gfc)
public async Task LoadAsync(GameFileCache gfc)
{
lock (SyncRoot)
{
Types = LoadTypes(gfc, "common:\\data\\ai\\scenarios.meta");
TypeGroups = LoadTypeGroups(gfc, "common:\\data\\ai\\scenarios.meta");
PropSets = LoadModelSets(gfc, "common:\\data\\ai\\propsets.meta");
PedModelSets = LoadModelSets(gfc, "common:\\data\\ai\\ambientpedmodelsets.meta");
VehicleModelSets = LoadModelSets(gfc, "common:\\data\\ai\\vehiclemodelsets.meta");
AnimGroups = LoadAnimGroups(gfc, "common:\\data\\ai\\conditionalanims.meta");
await Task.WhenAll([
Task.Run(() => Types = LoadTypes(gfc, "common:\\data\\ai\\scenarios.meta")),
Task.Run(() => TypeGroups = LoadTypeGroups(gfc, "common:\\data\\ai\\scenarios.meta")),
Task.Run(() => PropSets = LoadModelSets(gfc, "common:\\data\\ai\\propsets.meta")),
Task.Run(() => PedModelSets = LoadModelSets(gfc, "common:\\data\\ai\\ambientpedmodelsets.meta")),
Task.Run(() => VehicleModelSets = LoadModelSets(gfc, "common:\\data\\ai\\vehiclemodelsets.meta")),
Task.Run(() => AnimGroups = LoadAnimGroups(gfc, "common:\\data\\ai\\conditionalanims.meta")),
]);
TypeRefs = new Dictionary<uint, ScenarioTypeRef>(Types.Count + TypeGroups.Count);
foreach (var kvp in Types)
TypeRefs = new Dictionary<uint, ScenarioTypeRef>(Types.Count + TypeGroups.Count);
lock (TypeRefs)
{
if (Types is not null)
{
TypeRefs[kvp.Key] = new ScenarioTypeRef(kvp.Value);
lock (Types)
{
foreach (var (key, value) in Types)
{
TypeRefs[key] = new ScenarioTypeRef(value);
}
}
}
foreach (var kvp in TypeGroups)
if (TypeGroups is not null)
{
TypeRefs[kvp.Key] = new ScenarioTypeRef(kvp.Value);
lock(TypeGroups)
{
foreach (var (key, value) in TypeGroups)
{
TypeRefs[key] = new ScenarioTypeRef(value);
}
}
}
}
}
@ -1764,6 +1772,9 @@ namespace CodeWalker.World
var typesxml = xml.DocumentElement;
var items = typesxml.SelectNodes("Scenarios/Item");
if (items is null)
return types;
foreach (XmlNode item in items)
{
var typestr = Xml.GetStringAttribute(item, "type");
@ -1821,6 +1832,9 @@ namespace CodeWalker.World
var typesxml = xml.DocumentElement;
var items = typesxml.SelectNodes("ScenarioTypeGroups/Item");
if (items is null)
return types;
foreach (XmlNode item in items)
{
ScenarioTypeGroup group = new ScenarioTypeGroup();
@ -1853,6 +1867,9 @@ namespace CodeWalker.World
var setsxml = xml.DocumentElement;
var items = setsxml.SelectNodes("ModelSets/Item");
if (items is null)
return sets;
var noneset = new AmbientModelSet();
noneset.Name = "NONE";
noneset.NameHash = JenkHash.GenHash("none");
@ -1880,7 +1897,7 @@ namespace CodeWalker.World
var xml = LoadXml(gfc, filename);
if (xml?.DocumentElement == null)
if (xml?.DocumentElement is null)
{
return groups;
}
@ -1888,6 +1905,9 @@ namespace CodeWalker.World
var setsxml = xml.DocumentElement;
var items = setsxml.SelectNodes("ConditionalAnimsGroup/Item");
if (items is null)
return groups;
foreach (XmlNode item in items)
{
ConditionalAnimsGroup group = new ConditionalAnimsGroup();
@ -1910,153 +1930,161 @@ namespace CodeWalker.World
public ScenarioTypeRef? GetScenarioTypeRef(uint hash)
{
lock (SyncRoot)
if (TypeRefs is null)
return null;
lock (TypeRefs)
{
if (TypeRefs == null)
return null;
ScenarioTypeRef st;
TypeRefs.TryGetValue(hash, out st);
TypeRefs.TryGetValue(hash, out var st);
return st;
}
}
public ScenarioType? GetScenarioType(uint hash)
{
lock (SyncRoot)
if (Types is null)
return null;
lock (Types)
{
if (Types == null)
return null;
Types.TryGetValue(hash, out var st);
return st;
}
}
public ScenarioTypeGroup? GetScenarioTypeGroup(uint hash)
{
lock (SyncRoot)
if (TypeGroups is null)
return null;
lock (TypeGroups)
{
if (TypeGroups == null)
return null;
TypeGroups.TryGetValue(hash, out var tg);
return tg;
}
}
public AmbientModelSet? GetPropSet(uint hash)
{
lock (SyncRoot)
if (PropSets is null)
return null;
lock (PropSets)
{
if (PropSets == null)
return null;
PropSets.TryGetValue(hash, out var ms);
return ms;
}
}
public AmbientModelSet? GetPedModelSet(uint hash)
{
lock (SyncRoot)
if (PedModelSets is null)
return null;
lock(PedModelSets)
{
if (PedModelSets == null)
return null;
if(!PedModelSets.TryGetValue(hash, out var ms))
ref var ms = ref CollectionsMarshal.GetValueRefOrAddDefault(PedModelSets, hash, out var exists);
if (!exists)
{
string s_hash = hash.ToString("X");
ms = new AmbientModelSet();
ms.Name = $"UNKNOWN PED MODELSET ({s_hash})";
ms.NameHash = new MetaHash(hash);
ms.Models = [];
PedModelSets.Add(hash, ms);
}
return ms;
}
}
public AmbientModelSet? GetVehicleModelSet(uint hash)
{
lock (SyncRoot)
if (VehicleModelSets is null)
return null;
lock(VehicleModelSets)
{
if (VehicleModelSets == null)
return null;
if(!VehicleModelSets.TryGetValue(hash, out var ms))
ref var ms = ref CollectionsMarshal.GetValueRefOrAddDefault(VehicleModelSets, hash, out var exists);
if (!exists)
{
string s_hash = hash.ToString("X");
ms = new AmbientModelSet();
ms.Name = $"UNKNOWN VEHICLE MODELSET ({s_hash})";
ms.NameHash = new MetaHash(hash);
ms.Models = [];
VehicleModelSets.Add(hash, ms);
}
return ms;
}
}
public ConditionalAnimsGroup? GetAnimGroup(uint hash)
{
lock (SyncRoot)
if (AnimGroups == null)
return null;
lock(AnimGroups)
{
if (AnimGroups == null)
return null;
ConditionalAnimsGroup ag;
AnimGroups.TryGetValue(hash, out ag);
AnimGroups.TryGetValue(hash, out var ag);
return ag;
}
}
public ScenarioTypeRef[] GetScenarioTypeRefs()
{
lock (SyncRoot)
if (TypeRefs is null)
return [];
lock(TypeRefs)
{
if (TypeRefs == null)
return [];
return TypeRefs.Values.ToArray();
}
}
public ScenarioType[] GetScenarioTypes()
{
lock (SyncRoot)
if (Types is null)
return [];
lock (Types)
{
if (Types == null)
return [];
return Types.Values.ToArray();
}
}
public ScenarioTypeGroup[] GetScenarioTypeGroups()
{
lock (SyncRoot)
if (TypeGroups is null)
return [];
lock (TypeGroups)
{
if (TypeGroups == null)
return [];
return TypeGroups.Values.ToArray();
}
}
public AmbientModelSet[] GetPropSets()
{
lock (SyncRoot)
if (PropSets is null)
return [];
lock (PropSets)
{
if (PropSets == null)
return [];
return PropSets.Values.ToArray();
}
}
public AmbientModelSet[] GetPedModelSets()
{
lock (SyncRoot)
if (PedModelSets is null)
return [];
lock (PedModelSets)
{
if (PedModelSets == null)
return [];
return PedModelSets.Values.ToArray();
}
}
public AmbientModelSet[] GetVehicleModelSets()
{
lock (SyncRoot)
if (VehicleModelSets is null)
return [];
lock (VehicleModelSets)
{
if (VehicleModelSets == null)
return [];
return VehicleModelSets.Values.ToArray();
}
}
public ConditionalAnimsGroup[] GetAnimGroups()
{
lock (SyncRoot)
if (AnimGroups is null)
return [];
lock (AnimGroups)
{
if (AnimGroups == null)
return [];
return AnimGroups.Values.ToArray();
}
}
@ -2174,7 +2202,7 @@ namespace CodeWalker.World
NameHash = JenkHash.GenHashLower(Name);
var models = node.SelectNodes("Models/Item");
var modellist = new List<AmbientModel>();
var modellist = new List<AmbientModel>(models.Count);
foreach (XmlNode item in models)
{
AmbientModel model = new AmbientModel();

View File

@ -2,10 +2,13 @@
using CodeWalker.GameFiles;
using Collections.Pooled;
using CommunityToolkit.HighPerformance;
using Microsoft.Extensions.ObjectPool;
using SharpDX;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
@ -60,34 +63,32 @@ namespace CodeWalker.World
GameFileCache = gameFileCache;
var task = Task.Run(() => InitNodeGrid("Space Init"));
updateStatus?.Invoke("Scanning manifests...");
InitManifestData();
InitManifestData("Space Init");
updateStatus?.Invoke("Scanning caches...");
await InitCacheDataAsync();
await InitCacheDataAsync("Space Init");
updateStatus?.Invoke("Building map data store...");
InitMapDataStore();
InitMapDataStore("Space Init");
updateStatus?.Invoke("Building bounds store...");
InitBoundsStore();
updateStatus?.Invoke("Loading paths...");
InitNodeGrid();
InitBoundsStore("Space Init");
updateStatus?.Invoke("Loading nav meshes...");
InitNavGrid();
InitNavGrid("Space Init");
await task;
Inited = true;
@ -95,9 +96,9 @@ namespace CodeWalker.World
}
private void InitManifestData()
private void InitManifestData([CallerMemberName] string callerName = "")
{
using var _ = new DisposableTimer("InitManifestData");
using var _ = new DisposableTimer($"{callerName} -> {nameof(InitManifestData)}");
interiorLookup.Clear();
interiorManifest.Clear();
ymaptimes.Clear();
@ -105,7 +106,7 @@ namespace CodeWalker.World
dataGroupDict.Clear();
var manifests = GameFileCache.AllManifests;
foreach (var manifest in manifests)
foreach (var manifest in manifests.AsSpan())
{
//build interior lookup - maps child->parent interior bounds
@ -166,10 +167,10 @@ namespace CodeWalker.World
}
private async Task InitCacheDataAsync()
private async Task InitCacheDataAsync([CallerMemberName] string callerName = "")
{
//build the grid from the cached data
using var _ = new DisposableTimer("InitCacheDataAsync");
using var _ = new DisposableTimer($"{callerName} -> {nameof(InitCacheDataAsync)}");
var caches = GameFileCache.AllCacheFiles;
nodedict = new Dictionary<MetaHash, MapDataStoreNode>(6000);
//List<BoundsStoreItem> intlist = new List<BoundsStoreItem>();
@ -252,8 +253,8 @@ namespace CodeWalker.World
using var ymapTimer = new DisposableTimer("ymaps");
using var ybnTimer = new DisposableTimer("ybns");
using var ymapTimer = new DisposableTimer("ymaps", false);
using var ybnTimer = new DisposableTimer("ybns", false);
using(new DisposableTimer("maprpfs.Values"))
{
//try and generate the cache data for uncached ymaps... mainly for mod maps!
@ -264,6 +265,7 @@ namespace CodeWalker.World
{
if (entry.IsExtension(".ymap"))
{
ymapTimer.Stopwatch.Start();
if (!nodedict.ContainsKey(new MetaHash(entry.ShortNameHash)))
{
//non-cached ymap. mostly only mods... but some interesting test things also
@ -277,9 +279,11 @@ namespace CodeWalker.World
}
}
}
ymapTimer.Stopwatch.Stop();
}
if (entry.IsExtension(".ybn"))
{
ybnTimer.Stopwatch.Start();
MetaHash ehash = new MetaHash(entry.ShortNameHash);
if (!usedboundsdict.ContainsKey(ehash))
{
@ -297,6 +301,7 @@ namespace CodeWalker.World
}
}
}
ybnTimer.Stopwatch.Stop();
}
}
}
@ -306,33 +311,29 @@ namespace CodeWalker.World
}
private void InitMapDataStore()
private void InitMapDataStore([CallerMemberName] string callerName = "")
{
using var _ = new DisposableTimer("InitMapDataStore");
using var _ = new DisposableTimer($"{callerName} -> {nameof(InitMapDataStore)}");
MapDataStore = new SpaceMapDataStore();
MapDataStore.Init(nodedict.Values);
}
private void InitBoundsStore()
private void InitBoundsStore([CallerMemberName] string callerName = "")
{
using var _ = new DisposableTimer("InitBoundsStore");
using var _ = new DisposableTimer($"{callerName} -> {nameof(InitBoundsStore)}");
BoundsStore = new SpaceBoundsStore();
BoundsStore.Init(boundsdict.Values);
}
private void InitNodeGrid()
private Dictionary<uint, RpfFileEntry> GetYndEntries()
{
using var _ = new DisposableTimer("InitNodeGrid");
NodeGrid = new SpaceNodeGrid();
AllYnds.Clear();
using var addRpfYndTimer = new DisposableTimer($"{nameof(InitNodeGrid)} -> AddRpfYnds");
var rpfman = GameFileCache.RpfMan;
Dictionary<uint, RpfFileEntry> yndentries = new Dictionary<uint, RpfFileEntry>();
foreach (var rpffile in GameFileCache.BaseRpfs) //load nodes from base rpfs
foreach (var rpffile in GameFileCache.BaseRpfs.AsSpan()) //load nodes from base rpfs
{
AddRpfYnds(rpffile, yndentries);
}
@ -341,27 +342,39 @@ namespace CodeWalker.World
var updrpf = rpfman.FindRpfFile("update\\update.rpf"); //load nodes from patch area...
if (updrpf is not null)
{
foreach (var rpffile in updrpf.Children)
foreach (var rpffile in updrpf.Children.Span)
{
AddRpfYnds(rpffile, yndentries);
}
}
foreach (var dlcrpf in GameFileCache.DlcActiveRpfs) //load nodes from current dlc rpfs
foreach (var dlcrpf in GameFileCache.DlcActiveRpfs.AsSpan()) //load nodes from current dlc rpfs
{
if (dlcrpf.Path.StartsWith("x64", StringComparison.OrdinalIgnoreCase))
continue; //don't override update.rpf YNDs with x64 ones! *hack
foreach (var rpffile in dlcrpf.Children)
foreach (var rpffile in dlcrpf.Children.Span)
{
AddRpfYnds(rpffile, yndentries);
}
}
}
return yndentries;
}
private async Task InitNodeGrid([CallerMemberName] string callerName = "")
{
using var _ = new DisposableTimer($"{callerName} -> {nameof(InitNodeGrid)}");
NodeGrid = new SpaceNodeGrid();
AllYnds.Clear();
var yndentries = GetYndEntries();
var addRpfYndTimer = new DisposableTimer($"{nameof(InitNodeGrid)} -> BuildNodeGrid");
Vector3 corner = new Vector3(-8192, -8192, -2048);
Vector3 cellsize = new Vector3(512, 512, 4096);
for (int x = 0; x < NodeGrid.CellCountX; x++)
await Parallel.ForAsync(0, NodeGrid.CellCountX, async (x, cancellationToken) =>
{
for (int y = 0; y < NodeGrid.CellCountY; y++)
{
@ -370,14 +383,17 @@ namespace CodeWalker.World
uint fnhash = JenkHash.GenHash(fname);
if (yndentries.TryGetValue(fnhash, out RpfFileEntry? fentry))
{
cell.Ynd = RpfManager.GetFile<YndFile>(fentry);
cell.Ynd = await RpfManager.GetFileAsync<YndFile>(fentry);
cell.Ynd.BBMin = corner + (cellsize * new Vector3(x, y, 0));
cell.Ynd.BBMax = cell.Ynd.BBMin + cellsize;
cell.Ynd.CellX = x;
cell.Ynd.CellY = y;
cell.Ynd.Loaded = true;
AllYnds[fnhash] = cell.Ynd;
lock(AllYnds)
{
AllYnds[fnhash] = cell.Ynd;
}
#region node flags test
@ -458,19 +474,24 @@ namespace CodeWalker.World
}
}
}
});
addRpfYndTimer.Dispose();
addRpfYndTimer = new DisposableTimer($"{nameof(InitNodeGrid)} -> BuildYndData");
//join the dots....
//StringBuilder sb = new StringBuilder();
List<EditorVertex> tverts = new List<EditorVertex>();
using PooledList<YndLink> tlinks = new PooledList<YndLink>();
using PooledList<YndLink> nlinks = new PooledList<YndLink>();
foreach (var ynd in AllYnds.Values)
{
BuildYndData(ynd, tverts, tlinks, nlinks);
var allYnds = AllYnds.Values.ToArray();
//sb.Append(ynd.nodestr);
}
Parallel.ForEach(allYnds, BuildYndData);
//foreach (var ynd in AllYnds.Values)
//{
// BuildYndData(ynd);
// //sb.Append(ynd.nodestr);
//}
addRpfYndTimer.Dispose();
//string str = sb.ToString();
}
@ -482,11 +503,12 @@ namespace CodeWalker.World
NodeGrid.UpdateYnd(ynd);
}
private void AddRpfYnds(RpfFile rpffile, Dictionary<uint, RpfFileEntry> yndentries)
private static void AddRpfYnds(RpfFile rpffile, Dictionary<uint, RpfFileEntry> yndentries)
{
if (rpffile.AllEntries is null)
return;
foreach (var entry in rpffile.AllEntries)
foreach (var entry in rpffile.AllEntries.Span)
{
if (entry is RpfFileEntry fentry)
{
@ -498,19 +520,20 @@ namespace CodeWalker.World
}
}
public void BuildYndLinks(YndFile ynd, IList<YndLink>? tlinks = null, IList<YndLink>? nlinks = null)
public void BuildYndLinks(YndFile ynd)
{
var ynodes = ynd.Nodes;
var nodes = ynd.NodeDictionary?.Nodes;
var links = ynd.NodeDictionary?.Links;
if (ynodes is null || nodes is null || links is null) return;
if (ynodes is null || nodes is null || links is null)
return;
int nodecount = ynodes.Length;
//build the links arrays.
tlinks ??= new PooledList<YndLink>();
nlinks ??= new PooledList<YndLink>();
var tlinks = PooledListPool<YndLink>.Shared.Get();
var nlinks = PooledListPool<YndLink>.Shared.Get();
tlinks.Clear();
for (int i = 0; i < nodecount; i++)
@ -524,24 +547,24 @@ namespace CodeWalker.World
var llid = linkid + l;
if (llid >= links.Length) continue;
var link = links[llid];
YndNode tnode;
YndNode? tnode;
if (link.AreaID == node.AreaID)
{
if (link.NodeID >= ynodes.Length)
{ continue; }
continue;
tnode = ynodes[link.NodeID];
}
else
{
tnode = NodeGrid.GetYndNode(link.AreaID, link.NodeID);
if (tnode == null)
{ continue; }
if ((Math.Abs(tnode.Ynd.CellX - ynd.CellX) > 1) || (Math.Abs(tnode.Ynd.CellY - ynd.CellY) > 1))
{ /*continue;*/ } //non-adjacent cell? seems to be the carrier problem...
continue;
//if ((Math.Abs(tnode.Ynd.CellX - ynd.CellX) > 1) || (Math.Abs(tnode.Ynd.CellY - ynd.CellY) > 1))
//{ /*continue;*/ } //non-adjacent cell? seems to be the carrier problem...
}
YndLink yl = new YndLink();
yl.Init(ynd, node, tnode, link);
YndLink yl = new YndLink(ynd, node, tnode, link);
tlinks.Add(yl);
nlinks.Add(yl);
}
@ -549,10 +572,13 @@ namespace CodeWalker.World
}
ynd.Links = tlinks.ToArray();
PooledListPool<YndLink>.Shared.Return(tlinks);
PooledListPool<YndLink>.Shared.Return(nlinks);
}
public void BuildYndVerts(YndFile ynd, YndNode[]? selectedNodes, IList<EditorVertex>? tverts = null)
public void BuildYndVerts(YndFile ynd, YndNode[]? selectedNodes = null)
{
var laneColour = (uint) new Color4(0f, 0f, 1f, 1f).ToRgba();
var laneColour = 4294901760; // (uint) new Color4(0f, 0f, 1f, 1f).ToRgba();
var ynodes = ynd.Nodes;
if (ynodes is null)
return;
@ -560,7 +586,7 @@ namespace CodeWalker.World
int nodecount = ynodes.Length;
//build the main linked vertex array (used by the renderable to draw the lines).
tverts ??= new PooledList<EditorVertex>();
var tverts = PooledListPool<EditorVertex>.Shared.Get();
tverts.Clear();
for (int i = 0; i < nodecount; i++)
{
@ -569,14 +595,12 @@ namespace CodeWalker.World
continue;
var nvert = new EditorVertex(node.Position, (uint)node.Colour.ToRgba());
var nvert = new EditorVertex(node.Position, (uint)node.ColourRgba);
for (int l = 0; l < node.Links.Length; l++)
foreach(var yl in node.Links)
{
YndLink yl = node.Links[l];
var laneDir = yl.GetDirection();
var laneDirCross = Vector3.Cross(laneDir, Vector3.UnitZ);
var laneDirCross = Vectors.Cross(in laneDir, in Vector3.UnitZ);
var laneWidth = yl.GetLaneWidth();
var laneHalfWidth = laneWidth / 2;
var offset = yl.IsTwoWay()
@ -587,9 +611,9 @@ namespace CodeWalker.World
var tnode = yl.Node2;
if (tnode == null)
if (tnode is null)
continue; //invalid links could hit here
var tvert = new EditorVertex(tnode.Position, (uint)tnode.Colour.ToRgba());
var tvert = new EditorVertex(tnode.Position, (uint)tnode.ColourRgba);
tverts.Add(nvert);
tverts.Add(tvert);
@ -619,6 +643,8 @@ namespace CodeWalker.World
}
ynd.LinkedVerts = tverts.ToArray();
PooledListPool<EditorVertex>.Shared.Return(tverts);
ynd.UpdateTriangleVertices(selectedNodes);
}
public void BuildYndJuncs(YndFile ynd)
@ -631,8 +657,7 @@ namespace CodeWalker.World
for (int i = 0; i < junccount; i++)
{
var junc = yjuncs[i];
var cell = NodeGrid.GetCell(junc.RefData.AreaID);
if (cell?.Ynd?.Nodes is null)
if (!NodeGrid.TryGetCell(junc.RefData.AreaID, out var cell) || cell?.Ynd?.Nodes is null)
continue;
var jynd = cell.Ynd;
@ -656,15 +681,14 @@ namespace CodeWalker.World
}
}
public void BuildYndData(YndFile ynd, IList<EditorVertex>? tverts = null, IList<YndLink>? tlinks = null, IList<YndLink>? nlinks = null)
public void BuildYndData(YndFile ynd)
{
BuildYndLinks(ynd, tlinks, nlinks);
ArgumentNullException.ThrowIfNull(ynd, nameof(ynd));
BuildYndLinks(ynd);
BuildYndJuncs(ynd);
BuildYndVerts(ynd, null, tverts);
BuildYndVerts(ynd);
}
public YndFile[] GetYndFilesThatDependOnYndFile(YndFile file)
@ -723,9 +747,9 @@ namespace CodeWalker.World
}
private void InitNavGrid()
private void InitNavGrid([CallerMemberName] string callerName = "")
{
using var _ = new DisposableTimer("InitNavGrid");
using var _ = new DisposableTimer($"{callerName} -> InitNavGrid");
NavGrid = new SpaceNavGrid();
var rpfman = GameFileCache.RpfMan;
@ -774,16 +798,14 @@ namespace CodeWalker.World
private void AddRpfYnvs(RpfFile rpffile, Dictionary<uint, RpfFileEntry> ynventries)
{
if (rpffile.AllEntries == null) return;
foreach (var entry in rpffile.AllEntries)
if (rpffile.AllEntries is null)
return;
foreach (var entry in rpffile.AllEntries.Span)
{
if (entry is RpfFileEntry)
if (entry is RpfFileEntry fentry)
{
RpfFileEntry fentry = entry as RpfFileEntry;
if (entry.IsExtension(".ynv"))
{
if (ynventries.ContainsKey(entry.NameHash))
{ }
ynventries[entry.NameHash] = fentry;
}
}
@ -794,10 +816,13 @@ namespace CodeWalker.World
public void Update(float elapsed)
{
if (!Inited) return;
if (BoundsStore == null) return;
if (!Inited)
return;
if (BoundsStore is null)
return;
if (elapsed > 0.1f) elapsed = 0.1f;
if (elapsed > 0.1f)
elapsed = 0.1f;
Collisions.Clear();
@ -806,11 +831,13 @@ namespace CodeWalker.World
EnabledEntities.Clear();
foreach (var e in PersistentEntities)
{
if (e.Enabled) EnabledEntities.Add(e);
if (e.Enabled)
EnabledEntities.Add(e);
}
foreach (var e in TemporaryEntities)
{
if (e.Enabled) EnabledEntities.Add(e);
if (e.Enabled)
EnabledEntities.Add(e);
}
@ -1044,9 +1071,9 @@ namespace CodeWalker.World
{
if (ymapweathertypes.TryGetValue(ymaphash, out var weathers))
{
for (int i = 0; i < weathers.Length; i++)
foreach(var _weather in weathers)
{
if (weathers[i] == weather)
if (_weather == weather)
return true;
}
return false;
@ -1057,10 +1084,9 @@ namespace CodeWalker.World
public void GetVisibleYmaps(Camera cam, int hour, MetaHash weather, Dictionary<MetaHash, YmapFile> ymaps)
{
if (!Inited)
return;
if (MapDataStore is null)
if (!Inited || MapDataStore is null)
return;
CurrentHour = hour;
CurrentWeather = weather;
var items = MapDataStore.GetItems(in cam.Position);
@ -1156,7 +1182,7 @@ namespace CodeWalker.World
{
var hash = cell.YnvEntry.ShortNameHash;
var ynv = (hash > 0) ? GameFileCache.GetYnv(hash) : null;
if ((ynv != null) && (ynv.Loaded))
if (ynv != null && ynv.Loaded)
{
ynvs.Add(ynv);
}
@ -1196,8 +1222,8 @@ namespace CodeWalker.World
continue;
} //already a closer hit
YbnFile ybn = GameFileCache.GetYbn(bound.Name);
if (ybn == null)
YbnFile? ybn = GameFileCache.GetYbn(bound.Name);
if (ybn is null)
{
continue;
} //ybn not found?
@ -1917,10 +1943,7 @@ namespace CodeWalker.World
{
RootNode = new SpaceBoundsStoreNode();
RootNode.Owner = this;
foreach (var item in items)
{
RootNode.Add(item);
}
RootNode.AddRange(items);
RootNode.TrySplit(SplitThreshold);
}
@ -1932,6 +1955,7 @@ namespace CodeWalker.World
return VisibleItems;
}
public List<BoundsStoreItem> GetItems(ref Ray ray, bool[]? layers = null)
{
VisibleItems.Clear();
@ -1941,44 +1965,58 @@ namespace CodeWalker.World
return VisibleItems;
}
}
public class SpaceBoundsStoreNode
{
public SpaceBoundsStore Owner = null;
public SpaceBoundsStoreNode[] Children = null;
public List<BoundsStoreItem> Items = null;
public SpaceBoundsStore? Owner = null;
public SpaceBoundsStoreNode[]? Children = null;
public PooledList<BoundsStoreItem>? Items = null;
public Vector3 BBMin = new Vector3(float.MaxValue);
public Vector3 BBMax = new Vector3(float.MinValue);
public int Depth = 0;
public void Add(BoundsStoreItem item)
{
if (Items == null)
{
Items = new List<BoundsStoreItem>();
}
BBMin = Vector3.Min(BBMin, item.Min);
BBMax = Vector3.Max(BBMax, item.Max);
Items ??= PooledListPool<BoundsStoreItem>.Shared.Get();
Vectors.Min(in BBMin, in item.Min, out BBMin);
Vectors.Max(in BBMax, in item.Max, out BBMax);
Items.Add(item);
}
public void AddRange(IEnumerable<BoundsStoreItem> items)
{
Items ??= PooledListPool<BoundsStoreItem>.Shared.Get();
if (items.TryGetNonEnumeratedCount(out var count))
{
Items.EnsureCapacity(Items.Count + count);
}
foreach (var item in items)
{
Vectors.Min(in BBMin, in item.Min, out BBMin);
Vectors.Max(in BBMax, in item.Max, out BBMax);
Items.Add(item);
}
}
public void TrySplit(int threshold)
{
if ((Items == null) || (Items.Count <= threshold))
{ return; }
if (Items is null || Items.Count <= threshold)
return;
Children = new SpaceBoundsStoreNode[4];
var newItems = new List<BoundsStoreItem>();
var newItems = PooledListPool<BoundsStoreItem>.Shared.Get();
var ncen = (BBMax + BBMin) * 0.5f;
var next = (BBMax - BBMin) * 0.5f;
var nsiz = Math.Max(next.X, next.Y);
var nsizh = nsiz * 0.5f;
foreach (var item in Items)
foreach (var item in Items.Span)
{
var imin = item.Min;
var imax = item.Max;
ref readonly var imin = ref item.Min;
ref readonly var imax = ref item.Max;
var icen = (imax + imin) * 0.5f;
var iext = (imax - imin) * 0.5f;
var isiz = Math.Max(iext.X, iext.Y);
@ -1991,7 +2029,7 @@ namespace CodeWalker.World
{
var cind = ((icen.X > ncen.X) ? 1 : 0) + ((icen.Y > ncen.Y) ? 2 : 0);
var c = Children[cind];
if (c == null)
if (c is null)
{
c = new SpaceBoundsStoreNode();
c.Owner = Owner;
@ -2007,25 +2045,28 @@ namespace CodeWalker.World
Children[i]?.TrySplit(threshold);
}
if (Items is not null)
{
PooledListPool<BoundsStoreItem>.Shared.Return(Items);
}
Items = newItems;
}
public void GetItems(in Vector3 min, in Vector3 max, List<BoundsStoreItem> items, bool[] layers = null)
public void GetItems(in Vector3 min, in Vector3 max, List<BoundsStoreItem> items, bool[]? layers = null)
{
if ((max.X >= BBMin.X) && (min.X <= BBMax.X) && (max.Y >= BBMin.Y) && (min.Y <= BBMax.Y))
if (max.X >= BBMin.X && min.X <= BBMax.X && max.Y >= BBMin.Y && min.Y <= BBMax.Y)
{
if (Items != null)
{
for (int i = 0; i < Items.Count; i++)
foreach(var item in Items.Span)
{
var item = Items[i];
if ((layers != null) && (item.Layer < 3) && (!layers[item.Layer]))
if (layers is not null && item.Layer < 3 && !layers[item.Layer])
{
continue;
}
if ((max.X >= item.Min.X) && (min.X <= item.Max.X) && (max.Y >= item.Min.Y) && (min.Y <= item.Max.Y))
if (max.X >= item.Min.X && min.X <= item.Max.X && max.Y >= item.Min.Y && min.Y <= item.Max.Y)
{
items.Add(item);
}
@ -2040,6 +2081,7 @@ namespace CodeWalker.World
}
}
}
public void GetItems(ref Ray ray, List<BoundsStoreItem> items, bool[]? layers = null)
{
var box = new BoundingBox(BBMin, BBMax);
@ -2047,10 +2089,8 @@ namespace CodeWalker.World
{
if (Items is not null)
{
for (int i = 0; i < Items.Count; i++)
foreach(var item in Items.Span)
{
var item = Items[i];
if (layers is not null && item.Layer < 3 && !layers[item.Layer])
{
continue;
@ -2101,23 +2141,37 @@ namespace CodeWalker.World
}
}
public SpaceNodeGridCell GetCell(int id)
public SpaceNodeGridCell? GetCell(int id)
{
int x = id % CellCountX;
int y = id / CellCountX;
if ((x >= 0) && (x < CellCountX) && (y >= 0) && (y < CellCountY))
if (x >= 0 && x < CellCountX && y >= 0 && y < CellCountY)
{
return Cells[x, y];
}
return null;
}
public bool TryGetCell(int id, [MaybeNullWhen(false)] out SpaceNodeGridCell cell)
{
int x = id % CellCountX;
int y = id / CellCountX;
if (x >= 0 && x < CellCountX && y >= 0 && y < CellCountY)
{
cell = Cells[x, y];
return true;
}
cell = default;
return false;
}
public SpaceNodeGridCell? GetCellForPosition(in Vector3 position)
{
var x = (int)((position.X - CornerX) / CellSize);
var y = (int)((position.Y - CornerY) / CellSize);
if ((x >= 0) && (x < CellCountX) && (y >= 0) && (y < CellCountY))
if (x >= 0 && x < CellCountX && y >= 0 && y < CellCountY)
{
return Cells[x, y];
}
@ -2128,11 +2182,14 @@ namespace CodeWalker.World
public YndNode? GetYndNode(ushort areaid, ushort nodeid)
{
var cell = GetCell(areaid);
if (cell?.Ynd?.Nodes is null)
if (!TryGetCell(areaid, out var cell) || cell?.Ynd?.Nodes is null)
{
return null;
}
if (nodeid >= cell.Ynd.Nodes.Length)
{ return null; }
return null;
return cell.Ynd.Nodes[nodeid];
}
@ -2160,7 +2217,7 @@ namespace CodeWalker.World
public int Y;
public int ID;
public YndFile Ynd;
public YndFile? Ynd;
public SpaceNodeGridCell(int x, int y)
{

View File

@ -1,4 +1,5 @@
using CodeWalker.GameFiles;
using CodeWalker.Core.Utils;
using CodeWalker.GameFiles;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@ -96,7 +97,7 @@ namespace CodeWalker.World
JenkIndex.Ensure(namel);
nameHash = JenkHash.GenHash(namel);
List<TimecycleModValue> vals = new List<TimecycleModValue>();
var vals = PooledListPool<TimecycleModValue>.Shared.Get();
foreach (XmlNode valnode in node.ChildNodes)
{
if (!(valnode is XmlElement)) continue;
@ -108,7 +109,7 @@ namespace CodeWalker.World
Dict[val.name] = val;
}
Values = vals.ToArray();
PooledListPool<TimecycleModValue>.Shared.Return(vals);
}
public override string ToString()

View File

@ -503,7 +503,7 @@ namespace CodeWalker
action?.Invoke(args);
}
public static SwitchToUiAwaitable SwitchToUi(this Form form)
public static SwitchToUiAwaitable SwitchToUiContext(this Form form)
{
return new SwitchToUiAwaitable(form);
}

View File

@ -0,0 +1,186 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace CodeWalker.WinForms.Utils;
public class LinkedListPropertyDescripter<T> : PropertyDescriptor
{
private readonly LinkedListNode<T> node;
public LinkedListPropertyDescripter(LinkedListNode<T> node)
: base(CSharpName(node.Value.GetType()), null)
{
this.node = node;
}
private static string CSharpName(Type type)
{
var sb = new StringBuilder();
var name = type.Name;
if (!type.IsGenericType)
return name;
sb.Append(name.Substring(0, name.IndexOf('`')));
sb.Append("<");
sb.Append(string.Join(", ", type.GetGenericArguments()
.Select(CSharpName)));
sb.Append(">");
return sb.ToString();
}
public override object GetValue(object component)
{
return node.Value;
}
public override bool IsReadOnly => true;
public override string Name => node.Value.ToString();
public override Type PropertyType => node.Value.GetType();
public override Type ComponentType => node.List.GetType();
public override bool ShouldSerializeValue(object component) => false;
public override bool CanResetValue(object component) => false;
public override void ResetValue(object component)
{
}
public override void SetValue(object component, object value)
{
}
}
public class LinkedListConverter<T> : CollectionConverter
{
public override bool GetPropertiesSupported(ITypeDescriptorContext context) => true;
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
LinkedList<T> list = value as LinkedList<T>;
if (list == null || list.Count == 0)
return base.GetProperties(context, value, attributes);
var items = new PropertyDescriptorCollection(null);
foreach(var item in list)
{
var node = list.Find(item);
items.Add(new LinkedListPropertyDescripter<T>(node));
}
return items;
}
}
public class ExpandableCollectionPropertyDescriptor : PropertyDescriptor
{
private IList collection;
private readonly int _index;
public ExpandableCollectionPropertyDescriptor(IList coll, int idx)
: base(GetDisplayName(coll, idx), null)
{
collection = coll;
_index = idx;
}
private static string GetDisplayName(IList list, int index)
{
return $"[{index}] " + CSharpName(list[index].GetType());
}
private static string CSharpName(Type type)
{
var sb = new StringBuilder();
var name = type.Name;
if (!type.IsGenericType)
return name;
sb.Append(name.Substring(0, name.IndexOf('`')));
sb.Append("<");
sb.Append(string.Join(", ", type.GetGenericArguments()
.Select(CSharpName)));
sb.Append(">");
return sb.ToString();
}
public override bool CanResetValue(object component)
{
return true;
}
public override Type ComponentType
{
get { return this.collection.GetType(); }
}
public override object GetValue(object component)
{
return collection[_index];
}
public override bool IsReadOnly
{
get { return false; }
}
public override string Name
{
get { return _index.ToString(CultureInfo.InvariantCulture); }
}
public override Type PropertyType
{
get { return collection[_index].GetType(); }
}
public override void ResetValue(object component)
{
}
public override bool ShouldSerializeValue(object component)
{
return true;
}
public override void SetValue(object component, object value)
{
collection[_index] = value;
}
}
public class ListConverter : CollectionConverter
{
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
IList list = value as IList;
if (list == null || list.Count == 0)
return base.GetProperties(context, value, attributes);
var items = new PropertyDescriptorCollection(null);
for (int i = 0; i < list.Count; i++)
{
object item = list[i];
items.Add(new ExpandableCollectionPropertyDescriptor(list, i));
}
return items;
}
}

View File

@ -292,7 +292,7 @@ namespace CodeWalker
BoundsMaterialTypes.Init(FileCache);
UpdateStatus?.Invoke("Loading scenario types...");
Scenarios.EnsureScenarioTypes(FileCache);
await Scenarios.EnsureScenarioTypes(FileCache);
UpdateStatus?.Invoke("File cache loaded.");
}
@ -2548,7 +2548,8 @@ namespace CodeWalker
collectDrawable(drawable);
collectTextures(drawable);
}
} else if (entry.IsExtension(".ytd"))
}
else if (entry.IsExtension(".ytd"))
{
UpdateStatus?.Invoke(entry.Path);
YtdFile ytd = RpfFile.GetFile<YtdFile>(entry, fileData);
@ -2605,7 +2606,7 @@ namespace CodeWalker
foreach (var tp in texparams)
{
//MetaXml.WriteCustomItemArray(sb, tp.Value.Textures, 3, "Texture");
MetaXml.OpenTag(sb, 3, string.Format(otstr, ((ShaderParamNames)tp.Key).ToString(), "Texture"));
MetaXml.OpenTag(sb, 3, $"Item name=\"{(ShaderParamNames)tp.Key}\"");
foreach(var texture in tp.Value.Textures)
{
@ -2618,15 +2619,15 @@ namespace CodeWalker
{
var svp = s.GetSortedList(vp.Value);
var defval = svp.FirstOrDefault();
MetaXml.SelfClosingTag(sb, 3, string.Format(otstr, ((ShaderParamNames)vp.Key).ToString(), "Vector") + " " + FloatUtil.GetVector4XmlString(defval));
MetaXml.SelfClosingTag(sb, 3, $"Item name=\"{(ShaderParamNames)vp.Key}\" type=\"Vector\" {FloatUtil.GetVector4XmlString(in defval)}");
}
foreach (var ap in arrparams)
{
var defval = ap.Value.FirstOrDefault();
MetaXml.OpenTag(sb, 3, string.Format(otstr, ((ShaderParamNames)ap.Key).ToString(), "Array"));
MetaXml.OpenTag(sb, 3, $"Item name=\"{(ShaderParamNames)ap.Key}\" type=\"Array\"");
foreach (var vec in defval)
{
MetaXml.SelfClosingTag(sb, 4, "Value " + FloatUtil.GetVector4XmlString(vec));
MetaXml.SelfClosingTag(sb, 4, $"Value {FloatUtil.GetVector4XmlString(in vec)}");
}
MetaXml.CloseTag(sb, 3, "Item");
}
@ -5248,7 +5249,15 @@ namespace CodeWalker
ImageIndex = 1; //FOLDER imageIndex
var ic = fld.GetItemCount();
fileSize = ic;
fileSizeText = $"{ic} item{((ic != 1) ? "s" : "")}";
if (ic != 1)
{
fileSizeText = $"{ic} items";
}
else
{
fileSizeText = $"{ic} item";
}
}
}
else

View File

@ -1147,7 +1147,7 @@ namespace CodeWalker.Forms
int ih = (int)fh;
int im = v - (ih * 60);
if (ih == 24) ih = 0;
TimeOfDayLabel.Text = string.Format("{0:00}:{1:00}", ih, im);
TimeOfDayLabel.Text = $"{ih:00}:{im:00}";
}
@ -1432,7 +1432,7 @@ namespace CodeWalker.Forms
var tgnode = tmnode.Nodes.Add(gname);
tgnode.Tag = geom;
if ((geom.Shader != null) && (geom.Shader.ParametersList != null) && (geom.Shader.ParametersList.Hashes != null))
if (geom.Shader?.ParametersList?.Hashes is not null)
{
var pl = geom.Shader.ParametersList;
var h = pl.Hashes;
@ -1446,9 +1446,9 @@ namespace CodeWalker.Forms
var tstr = tex.Name.Trim();
if (tex is Texture t)
{
tstr = string.Format("{0} ({1}x{2}, embedded)", tex.Name, t.Width, t.Height);
tstr = $"{tex.Name} ({t.Width}x{t.Height}, embedded)";
}
var tnode = tgnode.Nodes.Add(hash.ToString().Trim() + ": " + tstr);
var tnode = tgnode.Nodes.Add($"{hash}: {tstr}");
tnode.Tag = tex;
}
}

View File

@ -1091,7 +1091,7 @@ namespace CodeWalker
int ih = (int)fh;
int im = v - (ih * 60);
if (ih == 24) ih = 0;
TimeOfDayLabel.Text = string.Format("{0:00}:{1:00}", ih, im);
TimeOfDayLabel.Text = $"{ih:00}:{im:00}";
}

View File

@ -102,7 +102,7 @@ namespace CodeWalker.Project.Panels
UnkVec1TextBox.Text = FloatUtil.GetVector4String(z.UnkVec1);
UnkVec2TextBox.Text = FloatUtil.GetVector4String(z.UnkVec2);
UnkVec3TextBox.Text = FloatUtil.GetVector2String(z.UnkVec3);
UnkBytesTextBox.Text = string.Format("{0}, {1}, {2}", z.Unk14, z.Unk15, z.Unk16);
UnkBytesTextBox.Text = $"{z.Unk14}, {z.Unk15}, {z.Unk16}";
Flags0TextBox.Text = z.Flags0.Hex;
Flags1TextBox.Text = z.Flags1.Hex;
Unk13TextBox.Text = z.Unk13.Hex;
@ -110,7 +110,7 @@ namespace CodeWalker.Project.Panels
SceneTextBox.Text = z.Scene.ToString();
StringBuilder sb = new StringBuilder();
if (z.Rules != null)
if (z.Rules is not null)
{
foreach (var hash in z.Rules)
{

View File

@ -78,7 +78,7 @@ namespace CodeWalker.Project.Panels
else
{
populatingui = true;
var n = CurrentPathNode.RawData;
var n = CurrentPathNode._RawData;
//YndNodePanel.Enabled = true;
PathNodeDeleteButton.Enabled = ProjectForm.YndExistsInProject(CurrentYndFile);
PathNodeAddToProjectButton.Enabled = !PathNodeDeleteButton.Enabled;

View File

@ -812,11 +812,7 @@ namespace CodeWalker.Project.Panels
});
}
var statf = "{0} hit tests, {1} hits, {2} new polys";
var stats = string.Format(statf, hitTestCount, hitCount, newCount);
UpdateStatus("Process complete. " + stats);
UpdateStatus($"Process complete. {hitTestCount} hit tests, {hitCount} hits, {newCount} new polys");
GenerateComplete();
});
}

View File

@ -615,16 +615,16 @@ namespace CodeWalker.Project.Panels
if ((ynd.Nodes != null) && (ynd.Nodes.Length > 0))
if (ynd.Nodes is not null && ynd.Nodes.Length > 0)
{
var nodesnode = node.Nodes.Add("Nodes (" + ynd.Nodes.Length.ToString() + ")");
var nodesnode = node.Nodes.Add($"Nodes ({ynd.Nodes.Length})");
nodesnode.Name = "Nodes";
nodesnode.Tag = ynd;
var nodes = ynd.Nodes;
for (int i = 0; i < nodes.Length; i++)
{
var ynode = nodes[i];
var nnode = ynode.RawData;
var nnode = ynode._RawData;
var tnode = nodesnode.Nodes.Add(nnode.ToString());
tnode.Tag = ynode;
}
@ -2315,7 +2315,7 @@ namespace CodeWalker.Project.Panels
var tn = FindYmapTreeNode(ymap);
if (tn != null)
{
await this.SwitchToUi();
await this.SwitchToUiContext();
tn.Text = ymap.RpfFileEntry?.Name ?? ymap.Name;
}
}
@ -2357,7 +2357,7 @@ namespace CodeWalker.Project.Panels
var tn = FindYnvTreeNode(ynv);
if (tn != null)
{
await this.SwitchToUi();
await this.SwitchToUiContext();
tn.Text = ynv.RpfFileEntry?.Name ?? ynv.Name;
}
}
@ -2366,7 +2366,7 @@ namespace CodeWalker.Project.Panels
var tn = FindTrainTrackTreeNode(track);
if (tn != null)
{
await this.SwitchToUi();
await this.SwitchToUiContext();
tn.Text = track.RpfFileEntry?.Name ?? track.Name;
}
}
@ -2375,7 +2375,7 @@ namespace CodeWalker.Project.Panels
var tn = FindScenarioTreeNode(scenarios);
if (tn != null)
{
await this.SwitchToUi();
await this.SwitchToUiContext();
tn.Text = scenarios.RpfFileEntry?.Name ?? scenarios.Name;
}
}
@ -2384,7 +2384,7 @@ namespace CodeWalker.Project.Panels
var tn = FindAudioRelTreeNode(rel);
if (tn != null)
{
await this.SwitchToUi();
await this.SwitchToUiContext();
tn.Text = rel.RpfFileEntry?.Name ?? rel.Name;
}
}
@ -2393,7 +2393,7 @@ namespace CodeWalker.Project.Panels
var tn = FindArchetypeTreeNode(archetype);
if (tn != null)
{
await this.SwitchToUi();
await this.SwitchToUiContext();
tn.Text = archetype._BaseArchetypeDef.ToString();
}
}

View File

@ -34,7 +34,7 @@ namespace CodeWalker.Project
public RpfManager RpfMan { get; private set; }
public bool IsProjectLoaded => CurrentProjectFile != null;
public bool IsProjectLoaded => CurrentProjectFile is not null;
public ProjectFile? CurrentProjectFile;
private MapSelection[]? CurrentMulti;
@ -99,17 +99,17 @@ namespace CodeWalker.Project
public readonly object ProjectSyncRoot = new object();
private Dictionary<string, YbnFile> visibleybns = new Dictionary<string, YbnFile>(StringComparer.OrdinalIgnoreCase);
private Dictionary<int, YndFile> visibleynds = new Dictionary<int, YndFile>();
private Dictionary<int, YnvFile> visibleynvs = new Dictionary<int, YnvFile>();
private Dictionary<string, TrainTrack> visibletrains = new Dictionary<string, TrainTrack>(StringComparer.OrdinalIgnoreCase);
private Dictionary<string, YmtFile> visiblescenarios = new Dictionary<string, YmtFile>(StringComparer.OrdinalIgnoreCase);
private Dictionary<uint, YmapEntityDef> visiblemloentities = new Dictionary<uint, YmapEntityDef>();
private Dictionary<uint, RelFile> visibleaudiofiles = new Dictionary<uint, RelFile>();
private readonly Dictionary<string, YbnFile> visibleybns = new Dictionary<string, YbnFile>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<int, YndFile> visibleynds = new Dictionary<int, YndFile>();
private readonly Dictionary<int, YnvFile> visibleynvs = new Dictionary<int, YnvFile>();
private readonly Dictionary<string, TrainTrack> visibletrains = new Dictionary<string, TrainTrack>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, YmtFile> visiblescenarios = new Dictionary<string, YmtFile>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<uint, YmapEntityDef> visiblemloentities = new Dictionary<uint, YmapEntityDef>();
private readonly Dictionary<uint, RelFile> visibleaudiofiles = new Dictionary<uint, RelFile>();
private Dictionary<uint, YbnFile> projectybns = new Dictionary<uint, YbnFile>();//used for handling interior ybns
private readonly Dictionary<uint, YbnFile> projectybns = new Dictionary<uint, YbnFile>();//used for handling interior ybns
private List<YmapEntityDef> interiorslist = new List<YmapEntityDef>(); //used for handling interiors ybns
private readonly List<YmapEntityDef> interiorslist = new List<YmapEntityDef>(); //used for handling interiors ybns
private bool ShowProjectItemInProcess = false;
@ -120,7 +120,7 @@ namespace CodeWalker.Project
InitializeComponent();
SetTheme(Settings.Default.ProjectWindowTheme, false);
_ = SetTheme(Settings.Default.ProjectWindowTheme, false);
ShowDefaultPanels();
if (!GameFileCache.IsInited)
@ -264,15 +264,23 @@ namespace CodeWalker.Project
return null;
}
public void ShowDefaultPanels()
public async void ShowDefaultPanels()
{
ShowProjectExplorer();
ShowWelcomePanel();
try
{
await ShowProjectExplorer();
await ShowWelcomePanel();
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
}
public void ShowProjectExplorer()
public async ValueTask ShowProjectExplorer()
{
if ((ProjectExplorer == null) || (ProjectExplorer.IsDisposed) || (ProjectExplorer.Disposing))
await this.SwitchToUiContext();
if (ProjectExplorer is null || ProjectExplorer.IsDisposed || ProjectExplorer.Disposing)
{
ProjectExplorer = new ProjectExplorerPanel(this);
ProjectExplorer.OnItemSelected += ProjectExplorer_OnItemSelected;
@ -285,8 +293,9 @@ namespace CodeWalker.Project
ProjectExplorer.Show();
}
}
public void ShowWelcomePanel()
public async ValueTask ShowWelcomePanel()
{
await this.SwitchToUiContext();
ShowPreviewPanel(() => new WelcomePanel());
}
public void ShowPreviewPanel<T>(Func<T> createFunc, Action<T>? updateAction = null) where T : ProjectPanel
@ -7498,7 +7507,7 @@ namespace CodeWalker.Project
var curybn = bounds?.GetRootYbn();
var eray = mray;
if (hidegtavmap && (curybn != null))
if (hidegtavmap && (curybn is not null))
{
curHit.Clear();
}
@ -7506,7 +7515,7 @@ namespace CodeWalker.Project
lock (ProjectSyncRoot)
{
if (renderitems && (CurrentProjectFile != null))
if (renderitems && CurrentProjectFile is not null)
{
for (int i = 0; i < CurrentProjectFile.YbnFiles.Count; i++)
{
@ -8707,8 +8716,6 @@ namespace CodeWalker.Project
await ymap.LoadAsync(data);
ymap.InitYmapEntityArchetypes(GameFileCache); //this needs to be done after calling YmapFile.Load()
GameFileCache?.AddProjectFile(ymap);
}
private async Task LoadYtypFromFileAsync(YtypFile ytyp, string filename)
{

View File

@ -884,7 +884,8 @@ namespace CodeWalker.Project
}
if (Bounds != sel.CollisionBounds) wf.SelectObject(Bounds);
if (Bounds != sel.CollisionBounds)
wf.SelectObject(Bounds);
wf.SetWidgetPosition(p);
UpdateGraphics(wf);

View File

@ -4,7 +4,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<ApplicationRevision>1</ApplicationRevision>
<ApplicationRevision>2</ApplicationRevision>
<ApplicationVersion>1.0.0.*</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Release</Configuration>

View File

@ -13,6 +13,7 @@ using SharpDX;
using System.Threading;
using System.Diagnostics;
using Collections.Pooled;
using System.Diagnostics.CodeAnalysis;
namespace CodeWalker.Rendering
{
@ -76,11 +77,11 @@ namespace CodeWalker.Rendering
public YtdFile[]? HDtxds;
public bool AllTexturesLoaded = false;
public RenderableModel[] HDModels = Array.Empty<RenderableModel>();
public RenderableModel[] HDModels = [];
public RenderableModel[]? MedModels;
public RenderableModel[]? LowModels;
public RenderableModel[]? VlowModels;
public RenderableModel[] AllModels = Array.Empty<RenderableModel>();
public RenderableModel[] AllModels = [];
public float LodDistanceHigh;
public float LodDistanceMed;
@ -96,9 +97,9 @@ namespace CodeWalker.Rendering
public bool HasAnims = false;
public double CurrentAnimTime = 0;
public YcdFile ClipDict;
public ClipMapEntry ClipMapEntry;
public Expression Expression;
public YcdFile? ClipDict;
public ClipMapEntry? ClipMapEntry;
public Expression? Expression;
public Dictionary<ushort, RenderableModel>? ModelBoneLinks;
public bool EnableRootMotion = false; //used to toggle whether or not to include root motion when playing animations
@ -370,19 +371,19 @@ namespace CodeWalker.Rendering
{
if (distance > LodDistanceVLow)
{
return Array.Empty<RenderableModel>();
return [];
}
else if (distance > LodDistanceLow)
{
return VlowModels ?? Array.Empty<RenderableModel>();
return VlowModels ?? [];
}
else if (distance > LodDistanceMed)
{
return LowModels ?? Array.Empty<RenderableModel>();
return LowModels ?? [];
}
else if (distance > LodDistanceHigh)
{
return MedModels ?? Array.Empty<RenderableModel>();
return MedModels ?? [];
}
else
{
@ -434,7 +435,7 @@ namespace CodeWalker.Rendering
LoadQueued = false;
}
public override string ToString()
public override string? ToString()
{
return Key.ToString();
}
@ -442,48 +443,46 @@ namespace CodeWalker.Rendering
public void ResetBoneTransforms()
{
if (Skeleton == null) return;
if (Skeleton is null)
return;
Skeleton.ResetBoneTransforms();
UpdateBoneTransforms();
}
private void UpdateBoneTransforms()
{
if (Skeleton?.Bones?.Items == null) return;
if (Skeleton?.Bones?.Items is null)
return;
Skeleton.UpdateBoneTransforms();
var bones = Skeleton.Bones?.Items;
var bonetransforms = Skeleton.BoneTransforms;
if (AllModels is null || AllModels.Length == 0 || bones is null)
return;
var drawbl = Key;
if (AllModels == null) return;
for (int i = 0; i < AllModels.Length; i++)
foreach(var model in AllModels)
{
var model = AllModels[i];
if (model?.Geometries == null) continue;
for (int g = 0; g < model.Geometries.Length; g++)
if (model?.Geometries is null)
continue;
foreach(var geom in model.Geometries)
{
var geom = model.Geometries[g];
var boneids = geom?.DrawableGeom?.BoneIds;
if (boneids == null) continue;
if (boneids is null)
continue;
if (boneids.Length != bones.Length)
{
var idc = boneids.Length;
if (geom.BoneTransforms == null)
{
geom.BoneTransforms = new Matrix3_s[idc];
}
geom.BoneTransforms ??= new Matrix3_s[idc];
for (int b = 0; b < idc; b++)
{
var id = boneids[b];
if (id < bonetransforms.Length)
{
geom.BoneTransforms[b] = bonetransforms[id];
if (id != b)
{ }
}
else
{ }
}
}
}
@ -500,12 +499,13 @@ namespace CodeWalker.Rendering
realTime = ClipMapEntry.PlayTime;
}
if (CurrentAnimTime == realTime) return;//already updated this!
if (CurrentAnimTime == realTime)
return;//already updated this!
CurrentAnimTime = realTime;
EnableRootMotion = ClipMapEntry?.EnableRootMotion ?? false;
if (ClipMapEntry != null)
if (ClipMapEntry is not null)
{
UpdateAnim(ClipMapEntry); //animate skeleton/models
}
@ -551,15 +551,7 @@ namespace CodeWalker.Rendering
}
private void UpdateAnim(Animation anim, float t)
{
if (anim is null)
{
return;
}
if (anim.BoneIds?.data_items is null)
{
return;
}
if (anim.Sequences?.data_items is null)
if (anim?.BoneIds?.data_items is null || anim.Sequences?.data_items is null)
{
return;
}
@ -567,8 +559,6 @@ namespace CodeWalker.Rendering
bool interpolate = true; //how to know? eg. cs4_14_hickbar_anim shouldn't
var frame = anim.GetFramePosition(t);
var dwbl = this.Key;
var skel = Skeleton;
var bones = skel?.BonesSorted;//.Bones?.Items;//
if (bones is null)
@ -585,19 +575,15 @@ namespace CodeWalker.Rendering
var boneid = boneiditem.BoneId;
var track = boneiditem.Track;
if (Expression?.BoneTracksDict != null)
if (Expression?.BoneTracksDict is not null)
{
var exprbt = new ExpressionTrack() { BoneId = boneid, Track = track, Flags = boneiditem.Unk0 };
var exprbtmap = exprbt;
if ((track == 24) || (track == 25) || (track == 26))
if (track == 24 || track == 25 || track == 26)
{
if (Expression.BoneTracksDict.TryGetValue(exprbt, out exprbtmap))
var exprbt = new ExpressionTrack() { BoneId = boneid, Track = track, Flags = boneiditem.Unk0 };
if (Expression.BoneTracksDict.TryGetValue(exprbt, out var exprbtmap))
{
boneid = exprbtmap.BoneId;
}
else
{ }
}
}
@ -676,21 +662,20 @@ namespace CodeWalker.Rendering
}
}
for (int i = 0; i < bones.Length; i++)
foreach(var bone in bones)
{
var bone = bones[i];
var tag = bone.Tag;
switch (bone.Tag)
tag = tag switch
{
case 23639: tag = 58271; break; //RB_L_ThighRoll: SKEL_L_Thigh
case 6442: tag = 51826; break; //RB_R_ThighRoll: SKEL_R_Thigh
//case 61007: tag = 61163; break; //RB_L_ForeArmRoll: SKEL_L_Forearm //NOT GOOD
//case 5232: tag = 45509; break; //RB_L_ArmRoll: SKEL_L_UpperArm
}
if ((tag != bone.Tag) && (tag != bone.Parent?.Tag))
23639 => (ushort)58271,
6442 => (ushort)51826,
//61007 => 61163; //RB_L_ForeArmRoll: SKEL_L_Forearm //NOT GOOD
//5232 => 45509; //RB_L_ArmRoll: SKEL_L_UpperArm
_ => tag,
};
if (tag != bone.Tag && tag != bone.Parent?.Tag)
{
var obone = bone;
if (skel.BonesMap.TryGetValue(tag, out obone))
if (skel.BonesMap.TryGetValue(tag, out var obone))
{
bone.AnimRotation = obone.AnimRotation;
}
@ -699,9 +684,8 @@ namespace CodeWalker.Rendering
if (ModelBoneLinks is not null)
{
for (int i = 0; i < bones.Length; i++)
foreach (var bone in bones)
{
var bone = bones[i];
bone.UpdateAnimTransform();
bone.UpdateSkinTransform();
@ -713,7 +697,6 @@ namespace CodeWalker.Rendering
continue;
bmodel.Transform = bone.AnimTransform;
}
}
}
@ -871,11 +854,11 @@ namespace CodeWalker.Rendering
public uint VertexDataSize { get; set; }
public uint IndexDataSize { get; set; }
public uint TotalDataSize { get; set; }
public TextureBase[] Textures;
public Texture[] TexturesHD;
public RenderableTexture?[]? RenderableTextures;
public RenderableTexture?[]? RenderableTexturesHD;
public ShaderParamNames[] TextureParamHashes;
public TextureBase[]? Textures;
public Texture?[]? TexturesHD { get; set; }
public RenderableTexture?[]? RenderableTextures { get; set; }
public RenderableTexture?[]? RenderableTexturesHD { get; set; }
public ShaderParamNames[]? TextureParamHashes { get; set; }
public PrimitiveTopology Topology { get; set; }
public bool IsFragment = false;
public bool IsEmissive { get; set; } = false;
@ -904,11 +887,14 @@ namespace CodeWalker.Rendering
public float HeightOpacity { get; set; } = 0; //for terrainfoam
public bool HDTextureEnable = true;
public bool globalAnimUVEnable = false;
public ClipMapEntry ClipMapEntryUV = null;
public ClipMapEntry? ClipMapEntryUV = null;
public bool isHair = false;
public bool disableRendering = false;
public Matrix3_s[] BoneTransforms = null;
public Matrix3_s[]? BoneTransforms = null;
[MemberNotNullWhen(true, nameof(Textures), nameof(RenderableTextures), nameof(RenderableTexturesHD), nameof(TexturesHD), nameof(TextureParamHashes))]
public bool HasTextures => Textures is not null;
public static ShaderParamNames[] GetTextureSamplerList()
{
@ -1128,8 +1114,6 @@ namespace CodeWalker.Rendering
RenderableTexturesHD = new RenderableTexture[texs.Count]; //these will get populated at render time.
}
}
}
public void Load(Device device)

View File

@ -1938,7 +1938,7 @@ namespace CodeWalker.Rendering
{
var rndbl = GetArchetypeRenderable(ent.Archetype);
ent.LodManagerRenderable = rndbl;
if (rndbl != null)
if (rndbl is not null)
{
RenderEntities.Add(ent);
}
@ -2427,22 +2427,21 @@ namespace CodeWalker.Rendering
private Renderable GetArchetypeRenderable(Archetype arch)
private Renderable? GetArchetypeRenderable(Archetype arch)
{
if (arch == null)
if (arch is null)
return null;
Renderable rndbl = null;
if (!ArchetypeRenderables.TryGetValue(arch, out rndbl))
if (!ArchetypeRenderables.TryGetValue(arch, out Renderable? rndbl))
{
var drawable = gameFileCache.TryGetDrawable(arch);
rndbl = TryGetRenderable(arch, drawable);
if (rndbl != null && rndbl.IsLoaded)
if (rndbl is not null && rndbl.IsLoaded)
{
ArchetypeRenderables[arch] = rndbl;
}
}
if ((rndbl != null) && rndbl.IsLoaded && (rndbl.AllTexturesLoaded || !waitforchildrentoload))
if (rndbl is not null && rndbl.IsLoaded && (rndbl.AllTexturesLoaded || !waitforchildrentoload))
{
return rndbl;
}
@ -2454,9 +2453,7 @@ namespace CodeWalker.Rendering
public void RenderYmap(YmapFile ymap)
{
if (ymap is null)
return;
if (!ymap.Loaded)
if (ymap is null || !ymap.Loaded)
return;
if (ymap.AllEntities.Length > 0 && ymap.RootEntities.Length > 0)
@ -2493,15 +2490,15 @@ namespace CodeWalker.Rendering
}
}
if (rendercars && ymap.CarGenerators != null && ymap.CarGenerators.Length > 0)
if (rendercars && ymap.CarGenerators is not null && ymap.CarGenerators.Length > 0)
{
RenderYmapCarGenerators(ymap);
}
if (rendergrass && ymap.GrassInstanceBatches != null && ymap.GrassInstanceBatches.Length > 0)
if (rendergrass && ymap.GrassInstanceBatches is not null && ymap.GrassInstanceBatches.Length > 0)
{
RenderYmapGrass(ymap);
}
if (renderdistlodlights && timecycle.IsNightTime && ymap.DistantLODLights != null)
if (renderdistlodlights && timecycle.IsNightTime && ymap.DistantLODLights is not null)
{
RenderYmapDistantLODLights(ymap);
}
@ -2715,14 +2712,13 @@ namespace CodeWalker.Rendering
var maxdist = 200 * renderworldDetailDistMult;
var maxdist2 = maxdist * maxdist;
for (int i = 0; i < ymap.CarGenerators.Length; i++)
foreach(var cg in ymap.CarGenerators)
{
var cg = ymap.CarGenerators[i];
var bscent = cg.Position - camera.Position;
float bsrad = cg._CCarGen.perpendicularLength;
if (bscent.LengthSquared() > maxdist2)
continue; //don't render distant cars..
if (!camera.ViewFrustum.ContainsSphereNoClipNoOpt(ref bscent, bsrad))
{
continue; //frustum cull cars...
@ -2737,297 +2733,296 @@ namespace CodeWalker.Rendering
}
private void RenderWheels(Archetype? arch, YmapEntityDef ent, FragType fragment, uint txdhash = 0, ClipMapEntry? animClip = null)
{
if (fragment.PhysicsLODGroup?.PhysicsLOD1?.Children?.data_items is null)
return;
var pl1 = fragment.PhysicsLODGroup.PhysicsLOD1;
//var groupnames = pl1?.GroupNames?.data_items;
//var groups = pl1.Groups?.data_items;
FragDrawable? wheel_f = null;
FragDrawable? wheel_r = null;
foreach (var pch in pl1.Children.data_items)
{
//var groupname = pch.GroupNameHash;
//if ((pl1.Groups?.data_items != null) && (i < pl1.Groups.data_items.Length))
//{
// //var group = pl1.Groups.data_items[i];
//}
if (pch.Drawable1 is not null && pch.Drawable1.AllModels.Length != 0)
{
switch (pch.BoneTag)
{
case 27922: //wheel_lf
case 26418: //wheel_rf
wheel_f = pch.Drawable1;
break;
case 29921: //wheel_lm1
case 29922: //wheel_lm2
case 29923: //wheel_lm3
case 27902: //wheel_lr
case 5857: //wheel_rm1
case 5858: //wheel_rm2
case 5859: //wheel_rm3
case 26398: //wheel_rr
wheel_r = pch.Drawable1;
break;
default:
RenderDrawable(pch.Drawable1, arch, ent, txdhash, null, null, animClip);
break;
}
}
else
{ }
if (pch.Drawable2 is not null && pch.Drawable2.AllModels.Length != 0)
{
RenderDrawable(pch.Drawable2, arch, ent, txdhash, null, null, animClip);
}
else
{ }
}
if (wheel_f is not null || wheel_r != null)
{
for (int i = 0; i < pl1.Children.data_items.Length; i++)
{
var pch = pl1.Children.data_items[i];
FragDrawable dwbl = pch.Drawable1;
FragDrawable? dwblcopy = null;
switch (pch.BoneTag)
{
case 27922: //wheel_lf
case 26418: //wheel_rf
dwblcopy = wheel_f ?? wheel_r;
break;
case 29921: //wheel_lm1
case 29922: //wheel_lm2
case 29923: //wheel_lm3
case 27902: //wheel_lr
case 5857: //wheel_rm1
case 5858: //wheel_rm2
case 5859: //wheel_rm3
case 26398: //wheel_rr
dwblcopy = wheel_r ?? wheel_f;
break;
default:
break;
}
//switch (pch.GroupNameHash)
//{
// case 3311608449: //wheel_lf
// case 1705452237: //wheel_lm1
// case 1415282742: //wheel_lm2
// case 3392433122: //wheel_lm3
// case 133671269: //wheel_rf
// case 2908525601: //wheel_rm1
// case 2835549038: //wheel_rm2
// case 4148013026: //wheel_rm3
// dwblcopy = wheel_f != null ? wheel_f : wheel_r;
// break;
// case 1695736278: //wheel_lr
// case 1670111368: //wheel_rr
// dwblcopy = wheel_r != null ? wheel_r : wheel_f;
// break;
// default:
// break;
//}
if (dwblcopy is not null)
{
if (dwbl is not null)
{
if (dwbl != dwblcopy && dwbl.AllModels.Length == 0)
{
dwbl.Owner = dwblcopy;
dwbl.AllModels = dwblcopy.AllModels; //hopefully this is all that's need to render, otherwise drawable is actually getting edited!
//dwbl.DrawableModelsHigh = dwblcopy.DrawableModelsHigh;
//dwbl.DrawableModelsMedium = dwblcopy.DrawableModelsMedium;
//dwbl.DrawableModelsLow = dwblcopy.DrawableModelsLow;
//dwbl.DrawableModelsVeryLow = dwblcopy.DrawableModelsVeryLow;
//dwbl.VertexDecls = dwblcopy.VertexDecls;
}
RenderDrawable(dwbl, arch, ent, txdhash /*, null, null, animClip*/);
}
else
{ }
}
else
{ }
}
}
}
public bool RenderFragment(Archetype arch, YmapEntityDef ent, FragType f, uint txdhash = 0, ClipMapEntry animClip = null)
private static readonly uint ColourBlue = (uint)Color.Blue.ToRgba();
private static readonly uint ColourRed = (uint)Color.Red.ToRgba();
private void RenderFragmentWindows(Archetype? arch, YmapEntityDef ent, FragType fragment, uint txdhash = 0, ClipMapEntry? animClip = null)
{
if (!renderfragwindows)
return;
var eori = Quaternion.Identity;
var epos = Vector3.Zero;
if (ent is not null)
{
eori = ent.Orientation;
epos = ent.Position;
}
if (fragment.GlassWindows?.data_items is not null)
{
foreach(var gw in fragment.GlassWindows.data_items)
{
var projt = gw.ProjectionRow1;//row0? or row3? maybe investigate more
var proju = gw.ProjectionRow2;//row1 of XYZ>UV projection
var projv = gw.ProjectionRow3;//row2 of XYZ>UV projection
//var unk01 = new Vector2(gw.UnkFloat13, gw.UnkFloat14);//offset?
//var unk02 = new Vector2(gw.UnkFloat15, gw.UnkFloat16);//scale? sum of this and above often gives integers eg 1, 6
//var thick = gw.Thickness; //thickness of the glass
//var unkuv = new Vector2(gw.UnkFloat18, gw.UnkFloat19); //another scale in UV space..?
//var tangt = gw.Tangent;//direction of surface tangent
//var bones = f.Drawable?.Skeleton?.Bones?.Items; //todo: use bones instead?
var grp = gw.Group;
var grplod = gw.GroupLOD;
var xforms = grplod?.FragTransforms?.Matrices;
var xoffs = Vector3.Zero;
if (grp is not null && xforms is not null && grp.ChildIndex < xforms.Length && grplod is not null)
{
var xform = xforms[grp.ChildIndex];
xoffs = xform.TranslationVector + grplod.PositionOffset;
}
var m = new Matrix(
projt.X, projt.Y, projt.Z, 0,
proju.X, proju.Y, proju.Z, 0,
projv.X, projv.Y, projv.Z, 0,
xoffs.X, xoffs.Y, xoffs.Z, 1
);
//m.Row1 = new Vector4(projt, 0);
//m.Row2 = new Vector4(proju, 0);
//m.Row3 = new Vector4(projv, 0);
//m.Row4 = new Vector4(xoffs, 1);
var v0 = m.Multiply(new Vector3(1, 0, 0));
var v1 = m.Multiply(new Vector3(1, 0, 1));
var v2 = m.Multiply(new Vector3(1, 1, 1));
var v3 = m.Multiply(new Vector3(1, 1, 0));
var c0 = eori.Multiply(in v0) + epos;
var c1 = eori.Multiply(in v1) + epos;
var c2 = eori.Multiply(in v2) + epos;
var c3 = eori.Multiply(in v3) + epos;
RenderSelectionLine(in c0, in c1, ColourBlue);
RenderSelectionLine(in c1, in c2, ColourBlue);
RenderSelectionLine(in c2, in c3, ColourBlue);
RenderSelectionLine(in c3, in c0, ColourBlue);
//RenderSelectionLine(c0, c0 + tangt, colred);
}
}
if (fragment.VehicleGlassWindows?.Windows is not null)
{
foreach(var vgw in fragment.VehicleGlassWindows.Windows)
{
//var grp = vgw.Group;
//var grplod = vgw.GroupLOD;
var m = vgw.Projection;
m.M44 = 1.0f;
m.Transpose();
m.Invert();//ouch
var min = (new Vector3(0, 0, 0));
var max = (new Vector3(vgw.ShatterMapWidth, vgw.ItemDataCount, 1));
var v0 = m.MultiplyW(new Vector3(min.X, min.Y, 0));
var v1 = m.MultiplyW(new Vector3(min.X, max.Y, 0));
var v2 = m.MultiplyW(new Vector3(max.X, max.Y, 0));
var v3 = m.MultiplyW(new Vector3(max.X, min.Y, 0));
var c0 = eori.Multiply(in v0) + epos;
var c1 = eori.Multiply(in v1) + epos;
var c2 = eori.Multiply(in v2) + epos;
var c3 = eori.Multiply(in v3) + epos;
RenderSelectionLine(in c0, in c1, ColourBlue);
RenderSelectionLine(in c1, in c2, ColourBlue);
RenderSelectionLine(in c2, in c3, ColourBlue);
RenderSelectionLine(in c3, in c0, ColourBlue);
if (vgw.ShatterMap != null)
{
var width = vgw.ShatterMapWidth;
var height = vgw.ShatterMap.Length;
for (int y = 0; y < height; y++)
{
var smr = vgw.ShatterMap[y];
for (int x = 0; x < width; x++)
{
var v = smr.GetValue(x);
if (v < 0 || v > 255)
continue;
var col = (uint)(new Color(v, v, v, 127).ToRgba());
v0 = m.MultiplyW(new Vector3(x, y, 0));
v1 = m.MultiplyW(new Vector3(x, y + 1, 0));
v2 = m.MultiplyW(new Vector3(x + 1, y + 1, 0));
v3 = m.MultiplyW(new Vector3(x + 1, y, 0));
c0 = eori.Multiply(v0) + epos;
c1 = eori.Multiply(v1) + epos;
c2 = eori.Multiply(v2) + epos;
c3 = eori.Multiply(v3) + epos;
RenderSelectionQuad(c0, c1, c2, c3, col);//extra ouch
}
}
}
}
}
}
public bool RenderFragment(Archetype? arch, YmapEntityDef ent, FragType fragment, uint txdhash = 0, ClipMapEntry? animClip = null)
{
RenderDrawable(f.Drawable, arch, ent, txdhash, null, null, animClip);
RenderDrawable(fragment.Drawable, arch, ent, txdhash, null, null, animClip);
if (f.DrawableCloth != null) //cloth
if (fragment.DrawableCloth is not null) //cloth
{
RenderDrawable(f.DrawableCloth, arch, ent, txdhash, null, null, animClip);
RenderDrawable(fragment.DrawableCloth, arch, ent, txdhash, null, null, animClip);
}
//vehicle wheels...
if ((f.PhysicsLODGroup != null) && (f.PhysicsLODGroup.PhysicsLOD1 != null))
RenderWheels(arch, ent, fragment, txdhash, animClip);
bool isselected = SelectionFlagsTestAll || (fragment.Drawable == SelectedDrawable);
if (isselected && fragment.DrawableArray?.data_items is not null)
{
var pl1 = f.PhysicsLODGroup.PhysicsLOD1;
//var groupnames = pl1?.GroupNames?.data_items;
var groups = pl1?.Groups?.data_items;
FragDrawable wheel_f = null;
FragDrawable wheel_r = null;
if (pl1.Children?.data_items != null)
foreach(var draw in fragment.DrawableArray.data_items)
{
for (int i = 0; i < pl1.Children.data_items.Length; i++)
{
var pch = pl1.Children.data_items[i];
//var groupname = pch.GroupNameHash;
//if ((pl1.Groups?.data_items != null) && (i < pl1.Groups.data_items.Length))
//{
// //var group = pl1.Groups.data_items[i];
//}
if ((pch.Drawable1 != null) && (pch.Drawable1.AllModels.Length != 0))
{
switch (pch.BoneTag)
{
case 27922: //wheel_lf
case 26418: //wheel_rf
wheel_f = pch.Drawable1;
break;
case 29921: //wheel_lm1
case 29922: //wheel_lm2
case 29923: //wheel_lm3
case 27902: //wheel_lr
case 5857: //wheel_rm1
case 5858: //wheel_rm2
case 5859: //wheel_rm3
case 26398: //wheel_rr
wheel_r = pch.Drawable1;
break;
default:
RenderDrawable(pch.Drawable1, arch, ent, txdhash, null, null, animClip);
break;
}
}
else
{ }
if ((pch.Drawable2 != null) && (pch.Drawable2.AllModels.Length != 0))
{
RenderDrawable(pch.Drawable2, arch, ent, txdhash, null, null, animClip);
}
else
{ }
}
if ((wheel_f != null) || (wheel_r != null))
{
for (int i = 0; i < pl1.Children.data_items.Length; i++)
{
var pch = pl1.Children.data_items[i];
FragDrawable dwbl = pch.Drawable1;
FragDrawable dwblcopy = null;
switch (pch.BoneTag)
{
case 27922: //wheel_lf
case 26418: //wheel_rf
dwblcopy = wheel_f != null ? wheel_f : wheel_r;
break;
case 29921: //wheel_lm1
case 29922: //wheel_lm2
case 29923: //wheel_lm3
case 27902: //wheel_lr
case 5857: //wheel_rm1
case 5858: //wheel_rm2
case 5859: //wheel_rm3
case 26398: //wheel_rr
dwblcopy = wheel_r != null ? wheel_r : wheel_f;
break;
default:
break;
}
//switch (pch.GroupNameHash)
//{
// case 3311608449: //wheel_lf
// case 1705452237: //wheel_lm1
// case 1415282742: //wheel_lm2
// case 3392433122: //wheel_lm3
// case 133671269: //wheel_rf
// case 2908525601: //wheel_rm1
// case 2835549038: //wheel_rm2
// case 4148013026: //wheel_rm3
// dwblcopy = wheel_f != null ? wheel_f : wheel_r;
// break;
// case 1695736278: //wheel_lr
// case 1670111368: //wheel_rr
// dwblcopy = wheel_r != null ? wheel_r : wheel_f;
// break;
// default:
// break;
//}
if (dwblcopy != null)
{
if (dwbl != null)
{
if ((dwbl != dwblcopy) && (dwbl.AllModels.Length == 0))
{
dwbl.Owner = dwblcopy;
dwbl.AllModels = dwblcopy.AllModels; //hopefully this is all that's need to render, otherwise drawable is actually getting edited!
//dwbl.DrawableModelsHigh = dwblcopy.DrawableModelsHigh;
//dwbl.DrawableModelsMedium = dwblcopy.DrawableModelsMedium;
//dwbl.DrawableModelsLow = dwblcopy.DrawableModelsLow;
//dwbl.DrawableModelsVeryLow = dwblcopy.DrawableModelsVeryLow;
//dwbl.VertexDecls = dwblcopy.VertexDecls;
}
RenderDrawable(dwbl, arch, ent, txdhash /*, null, null, animClip*/);
}
else
{ }
}
else
{ }
}
}
}
}
bool isselected = SelectionFlagsTestAll || (f.Drawable == SelectedDrawable);
if (isselected)
{
var darr = f.DrawableArray?.data_items;
if (darr != null)
{
for (int i = 0; i < darr.Length; i++)
{
RenderDrawable(darr[i], arch, ent, txdhash, null, null, animClip);
}
}
}
if (renderfragwindows)
{
var colblu = (uint)(new Color(0, 0, 255, 255).ToRgba());
var colred = (uint)(new Color(255, 0, 0, 255).ToRgba());
var eori = Quaternion.Identity;
var epos = Vector3.Zero;
if (ent != null)
{
eori = ent.Orientation;
epos = ent.Position;
}
if (f.GlassWindows?.data_items != null)
{
for (int i = 0; i < f.GlassWindows.data_items.Length; i++)
{
var gw = f.GlassWindows.data_items[i];
var projt = gw.ProjectionRow1;//row0? or row3? maybe investigate more
var proju = gw.ProjectionRow2;//row1 of XYZ>UV projection
var projv = gw.ProjectionRow3;//row2 of XYZ>UV projection
//var unk01 = new Vector2(gw.UnkFloat13, gw.UnkFloat14);//offset?
//var unk02 = new Vector2(gw.UnkFloat15, gw.UnkFloat16);//scale? sum of this and above often gives integers eg 1, 6
//var thick = gw.Thickness; //thickness of the glass
//var unkuv = new Vector2(gw.UnkFloat18, gw.UnkFloat19); //another scale in UV space..?
//var tangt = gw.Tangent;//direction of surface tangent
//var bones = f.Drawable?.Skeleton?.Bones?.Items; //todo: use bones instead?
var grp = gw.Group;
var grplod = gw.GroupLOD;
var xforms = grplod?.FragTransforms?.Matrices;
var xoffs = Vector3.Zero;
if ((grp != null) && (xforms != null) && (grp.ChildIndex < xforms.Length) && (grplod != null))
{
var xform = xforms[grp.ChildIndex];
xoffs = xform.TranslationVector + grplod.PositionOffset;
}
var m = new Matrix();
m.Row1 = new Vector4(projt, 0);
m.Row2 = new Vector4(proju, 0);
m.Row3 = new Vector4(projv, 0);
m.Row4 = new Vector4(xoffs, 1);
var v0 = m.Multiply(new Vector3(1, 0, 0));
var v1 = m.Multiply(new Vector3(1, 0, 1));
var v2 = m.Multiply(new Vector3(1, 1, 1));
var v3 = m.Multiply(new Vector3(1, 1, 0));
var c0 = eori.Multiply(v0) + epos;
var c1 = eori.Multiply(v1) + epos;
var c2 = eori.Multiply(v2) + epos;
var c3 = eori.Multiply(v3) + epos;
RenderSelectionLine(c0, c1, colblu);
RenderSelectionLine(c1, c2, colblu);
RenderSelectionLine(c2, c3, colblu);
RenderSelectionLine(c3, c0, colblu);
//RenderSelectionLine(c0, c0 + tangt, colred);
}
}
if (f.VehicleGlassWindows?.Windows != null)
{
for (int i = 0; i < f.VehicleGlassWindows.Windows.Length; i++)
{
var vgw = f.VehicleGlassWindows.Windows[i];
//var grp = vgw.Group;
//var grplod = vgw.GroupLOD;
var m = vgw.Projection;
m.M44 = 1.0f;
m.Transpose();
m.Invert();//ouch
var min = (new Vector3(0, 0, 0));
var max = (new Vector3(vgw.ShatterMapWidth, vgw.ItemDataCount, 1));
var v0 = m.MultiplyW(new Vector3(min.X, min.Y, 0));
var v1 = m.MultiplyW(new Vector3(min.X, max.Y, 0));
var v2 = m.MultiplyW(new Vector3(max.X, max.Y, 0));
var v3 = m.MultiplyW(new Vector3(max.X, min.Y, 0));
var c0 = eori.Multiply(v0) + epos;
var c1 = eori.Multiply(v1) + epos;
var c2 = eori.Multiply(v2) + epos;
var c3 = eori.Multiply(v3) + epos;
RenderSelectionLine(c0, c1, colblu);
RenderSelectionLine(c1, c2, colblu);
RenderSelectionLine(c2, c3, colblu);
RenderSelectionLine(c3, c0, colblu);
if (vgw.ShatterMap != null)
{
var width = vgw.ShatterMapWidth;
var height = vgw.ShatterMap.Length;
for (int y = 0; y < height; y++)
{
var smr = vgw.ShatterMap[y];
for (int x = 0; x < width; x++)
{
var v = smr.GetValue(x);
if (v < 0 || v > 255)
continue;
var col = (uint)(new Color(v, v, v, 127).ToRgba());
v0 = m.MultiplyW(new Vector3(x, y, 0));
v1 = m.MultiplyW(new Vector3(x, y+1, 0));
v2 = m.MultiplyW(new Vector3(x+1, y+1, 0));
v3 = m.MultiplyW(new Vector3(x+1, y, 0));
c0 = eori.Multiply(v0) + epos;
c1 = eori.Multiply(v1) + epos;
c2 = eori.Multiply(v2) + epos;
c3 = eori.Multiply(v3) + epos;
RenderSelectionQuad(c0, c1, c2, c3, col);//extra ouch
}
}
}
}
RenderDrawable(draw, arch, ent, txdhash, null, null, animClip);
}
}
RenderFragmentWindows(arch, ent, fragment, txdhash, animClip);
return true;
}
public bool RenderArchetype(Archetype arche, YmapEntityDef entity, Renderable rndbl = null, bool cull = true, ClipMapEntry animClip = null)
public bool RenderArchetype(Archetype arche, YmapEntityDef entity, Renderable? rndbl = null, bool cull = true, ClipMapEntry? animClip = null)
{
//enqueue a single archetype for rendering.
if (arche == null) return false;
if (arche is null)
return false;
Vector3 entpos = (entity != null) ? entity.Position : Vector3.Zero;
Vector3 entpos = (entity is not null) ? entity.Position : Vector3.Zero;
Vector3 camrel = entpos - camera.Position;
Quaternion orientation = Quaternion.Identity;
Vector3 scale = Vector3.One;
Vector3 bscent = camrel;
if (entity != null)
if (entity is not null)
{
orientation = entity.Orientation;
scale = entity.Scale;
@ -3051,19 +3046,14 @@ namespace CodeWalker.Rendering
if (boundsmode == BoundsShaderMode.Sphere)
{
if ((bsrad < renderboundsmaxrad) && (dist < renderboundsmaxdist))
if (bsrad < renderboundsmaxrad && dist < renderboundsmaxdist)
{
MapSphere ms = new MapSphere
{
CamRelPos = bscent,
Radius = bsrad,
};
BoundingSpheres.Add(ms);
BoundingSpheres.Add(new MapSphere(bscent, bsrad));
}
}
if (boundsmode == BoundsShaderMode.Box)
{
if ((dist < renderboundsmaxdist))
if (dist < renderboundsmaxdist)
{
BoundingBoxes.Add(
new MapBox(
@ -3077,16 +3067,13 @@ namespace CodeWalker.Rendering
}
}
bool res = false;
if (rndbl == null)
if (rndbl is null)
{
var drawable = gameFileCache.TryGetDrawable(arche);
rndbl = TryGetRenderable(arche, drawable);
}
if (rndbl == null || !rndbl.IsLoaded)
if (rndbl is null || !rndbl.IsLoaded)
{
return false;
}
@ -3099,14 +3086,14 @@ namespace CodeWalker.Rendering
}
res = RenderRenderable(rndbl, arche, entity);
bool res = RenderRenderable(rndbl, arche, entity);
//fragments have extra drawables! need to render those too... TODO: handle fragments properly...
if (rndbl.Key is FragDrawable fd)
{
var frag = fd.OwnerFragment;
if ((frag != null) && (frag.DrawableCloth != null)) //cloth...
if (frag is not null && frag.DrawableCloth != null) //cloth...
{
rndbl = TryGetRenderable(arche, frag.DrawableCloth);
if (rndbl != null && rndbl.IsLoaded)
@ -3121,14 +3108,14 @@ namespace CodeWalker.Rendering
return res;
}
public bool RenderDrawable(DrawableBase drawable, Archetype arche, YmapEntityDef entity, uint txdHash = 0, TextureDictionary? txdExtra = null, Texture? diffOverride = null, ClipMapEntry? animClip = null, ClothInstance? cloth = null, Expression? expr = null)
public bool RenderDrawable(DrawableBase drawable, Archetype? arche, YmapEntityDef entity, uint txdHash = 0, TextureDictionary? txdExtra = null, Texture? diffOverride = null, ClipMapEntry? animClip = null, ClothInstance? cloth = null, Expression? expr = null)
{
//enqueue a single drawable for rendering.
if (drawable is null)
return false;
Renderable rndbl = TryGetRenderable(arche, drawable, txdHash, txdExtra, diffOverride);
Renderable? rndbl = TryGetRenderable(arche, drawable, txdHash, txdExtra, diffOverride);
if (rndbl is null || !rndbl.IsLoaded)
return false;
@ -3176,11 +3163,10 @@ namespace CodeWalker.Rendering
Vector3 bbmax = (arche != null) ? arche.BBMax : rndbl.Key.BoundingBoxMax;
Vector3 bscen = (arche != null) ? arche.BSCenter : rndbl.Key.BoundingCenter;
float radius = (arche != null) ? arche.BSRadius : rndbl.Key.BoundingSphereRadius;
float distance = 0;//(camrel + bscen).Length();
bool interiorent = false;
bool castshadow = true;
if (entity != null)
float distance;
if (entity is not null)
{
position = entity.Position;
scale = entity.Scale;
@ -3191,8 +3177,7 @@ namespace CodeWalker.Rendering
bscen = entity.BSCenter;
camrel += position;
distance = entity.Distance;
castshadow = (entity.MloParent == null);//don't cast sun/moon shadows if this is an interior entity - optimisation!
interiorent = (entity.MloParent != null);
castshadow = (entity.MloParent is null);//don't cast sun/moon shadows if this is an interior entity - optimisation!
}
else
{
@ -3213,7 +3198,7 @@ namespace CodeWalker.Rendering
{
rndbl.UpdateAnims(currentRealTime);
}
if (rndbl.Cloth != null)
if (rndbl.Cloth is not null)
{
rndbl.Cloth.Update(currentRealTime);
}
@ -3246,7 +3231,7 @@ namespace CodeWalker.Rendering
RenderSkeleton(rndbl, entity);
}
if (renderlights && Shaders.deferred && (rndbl.Lights != null))
if (renderlights && Shaders.deferred && rndbl.Lights is not null)
{
entity?.EnsureLights(rndbl.Key);
@ -3286,7 +3271,7 @@ namespace CodeWalker.Rendering
bool retval = true;// false;
if ((rndbl.AllTexturesLoaded || !waitforchildrentoload))
if (rndbl.AllTexturesLoaded || !waitforchildrentoload)
{
RenderableGeometryInst rginst = new RenderableGeometryInst();
rginst.Inst.Renderable = rndbl;
@ -3305,10 +3290,8 @@ namespace CodeWalker.Rendering
RenderableModel[] models = isselected ? rndbl.HDModels : rndbl.GetModels(distance);
for (int mi = 0; mi < models.Length; mi++)
foreach(var model in models)
{
var model = models[mi];
if (isselected)
{
if (SelectionModelDrawFlags.ContainsKey(model.DrawableModel))
@ -3322,9 +3305,8 @@ namespace CodeWalker.Rendering
continue;
} //filter out reflection proxy models...
for (int gi = 0; gi < model.Geometries.Length; gi++)
foreach(var geom in model.Geometries)
{
var geom = model.Geometries[gi];
var dgeom = geom.DrawableGeom;
if (dgeom.UpdateRenderableParameters) //when edited by material editor
@ -3361,21 +3343,18 @@ namespace CodeWalker.Rendering
return retval;
}
public void RenderCar(Vector3 pos, Quaternion ori, MetaHash modelHash, MetaHash modelSetHash, bool valign = false)
public void RenderCar(Vector3 pos, in Quaternion ori, MetaHash modelHash, MetaHash modelSetHash, bool valign = false)
{
uint carhash = modelHash;
if ((carhash == 0) && (modelSetHash != 0))
if (carhash == 0 && modelSetHash != 0)
{
//find the pop group... and choose a vehicle..
var stypes = Scenarios.ScenarioTypes;
if (stypes != null)
if (stypes is not null)
{
var modelset = stypes.GetVehicleModelSet(modelSetHash);
if ((modelset != null) && (modelset.Models != null) && (modelset.Models.Length > 0))
if (modelset is not null && modelset.Models is not null && modelset.Models.Length > 0)
{
carhash = JenkHash.GenHash(modelset.Models[0].NameLower);
}
@ -3383,8 +3362,8 @@ namespace CodeWalker.Rendering
}
if (carhash == 0) carhash = 418536135; //"infernus"
YftFile caryft = gameFileCache.GetYft(carhash);
if ((caryft != null) && (caryft.Loaded) && (caryft.Fragment != null))
YftFile? caryft = gameFileCache.GetYft(carhash);
if (caryft is not null && caryft.Loaded && caryft.Fragment is not null)
{
if (valign)
{
@ -3515,15 +3494,14 @@ namespace CodeWalker.Rendering
{
RenderDrawable(drawable, null, ped.RenderEntity, 0, td, texture, ac, cloth, expr);
}
}
public void RenderHideEntity(YmapEntityDef ent)
{
var hash = ent?.EntityHash ?? 0;
if (hash == 0) return;
var hash = ent.EntityHash;
if (hash == 0)
return;
HideEntities[hash] = ent;
}
@ -3601,7 +3579,7 @@ namespace CodeWalker.Rendering
private Renderable? TryGetRenderable(Archetype arche, DrawableBase drawable, uint txdHash = 0, TextureDictionary? txdExtra = null, Texture? diffOverride = null)
private Renderable? TryGetRenderable(Archetype? arche, DrawableBase drawable, uint txdHash = 0, TextureDictionary? txdExtra = null, Texture? diffOverride = null)
{
if (drawable is null)
return null;
@ -3628,7 +3606,7 @@ namespace CodeWalker.Rendering
if (rndbl is null)
return null;
if ((clipDict != 0) && (rndbl.ClipDict == null))
if (clipDict != 0 && rndbl.ClipDict is null)
{
var ycd = gameFileCache.GetYcd(clipDict);
if (ycd is not null && ycd.Loaded)
@ -3753,7 +3731,7 @@ namespace CodeWalker.Rendering
foreach (var geom in model.Geometries)
{
if (geom.Textures != null)
if (geom.HasTextures)
{
for (int i = 0; i < geom.Textures.Length; i++)
{
@ -3863,7 +3841,7 @@ namespace CodeWalker.Rendering
RenderableTexture? rhdtex = null;
if (renderhdtextures)
{
Texture hdtex = geom.TexturesHD[i];
Texture? hdtex = geom.TexturesHD[i];
if (hdtex is null)
{
//look for a replacement HD texture...
@ -3919,6 +3897,7 @@ namespace CodeWalker.Rendering
public readonly Archetype Archetype = archetype;
public readonly YmapEntityDef Entity = entity;
}
public readonly struct RenderedBoundComposite(RenderableBoundComposite boundComp, YmapEntityDef entity)
{
public readonly RenderableBoundComposite BoundComp = boundComp;
@ -4006,7 +3985,7 @@ namespace CodeWalker.Rendering
}
if (ymap.Parent != pymap)
{
Console.WriteLine($"Connected ymap {ymap.Name} to parent {pymap.Name}");
Console.WriteLine($"Connected ymap {ymap.Name} {ymap.FilePath} to parent {pymap.Name} {pymap.FilePath}");
ymap.ConnectToParent(pymap);
}
}
@ -4025,8 +4004,11 @@ namespace CodeWalker.Rendering
{
var ymap = CurrentYmaps[remYmap];
CurrentYmaps.Remove(remYmap);
Console.WriteLine($"Removing ymap {ymap.Name} {ymap.FilePath}");
var remEnts = ymap.LodManagerOldEntities ?? ymap.AllEntities;
if (remEnts != null) // remove this ymap's entities from the tree.....
if (remEnts is not null) // remove this ymap's entities from the tree.....
{
for (int i = 0; i < remEnts.Length; i++)
{
@ -4063,11 +4045,14 @@ namespace CodeWalker.Rendering
}
if (ymap._CMapData.parent != 0 && ymap.Parent is null) //skip adding ymaps until parents are available
{
// We have to make sure to re-add children when map get's reloaded (this breaks LOD's for project files otherwise)
CurrentYmaps.Remove(key, out _);
continue;
}
if (CurrentYmaps.TryAdd(key, ymap))
{
foreach(var ent in ymap.AllEntities)
Console.WriteLine($"Adding ymap {ymap.Name} {ymap.FilePath}");
foreach (var ent in ymap.AllEntities)
{
if (ent.Parent is not null)
{

View File

@ -106,12 +106,9 @@ namespace CodeWalker.Rendering
PSSceneVars = new GpuVarsBuffer<CableShaderPSSceneVars>(device);
PSGeomVars = new GpuVarsBuffer<CableShaderPSGeomVars>(device);
//supported layout - requires Position, Normal, Colour, Texcoord
layouts.Add(VertexType.Default, new InputLayout(device, vsbytes, VertexTypeGTAV.GetLayout(VertexType.Default)));
texsampler = new SamplerState(device, new SamplerStateDescription()
{
AddressU = TextureAddressMode.Wrap,

View File

@ -76,7 +76,6 @@ namespace CodeWalker.Rendering
MinimumLod = 0,
MipLodBias = 0,
});
}

View File

@ -167,12 +167,7 @@ namespace CodeWalker.Rendering
defaultBoneMatrices = new Matrix3_s[255];
for (int i = 0; i < 255; i++)
{
defaultBoneMatrices[i] = new Matrix3_s
{
Row1 = Vector4.UnitX,
Row2 = Vector4.UnitY,
Row3 = Vector4.UnitZ
};
defaultBoneMatrices[i] = new Matrix3_s(Vector4.UnitX, Vector4.UnitY, Vector4.UnitZ);
}
}

View File

@ -28,7 +28,6 @@ namespace CodeWalker.Rendering.Utils
}
else
{
Console.WriteLine(folder);
return folder;
}
}

View File

@ -112,7 +112,9 @@ namespace CodeWalker.Tools
string searchfolder = FileSearchFolderTextBox.Text;
AbortOperation = false;
if (InProgress) return;
if (InProgress)
return;
if (searchfolder.Length == 0)
{
MessageBox.Show("Please select a folder...");
@ -165,10 +167,10 @@ namespace CodeWalker.Tools
InProgress = true;
Task.Run(() =>
_ = Task.Run(() =>
{
FileSearchAddResult("Searching " + searchfolder + "...");
FileSearchAddResult($"Searching {searchfolder}...");
string[] filenames = Directory.GetFiles(searchfolder);
@ -195,13 +197,13 @@ namespace CodeWalker.Tools
if (hitlen1 == bytelen)
{
FileSearchAddResult(finf.Name + ":" + (i - bytelen));
FileSearchAddResult($"{finf.Name}:{i - bytelen}");
matchcount++;
hitlen1 = 0;
}
if (hitlen2 == bytelen)
{
FileSearchAddResult(finf.Name + ":" + (i - bytelen));
FileSearchAddResult($"{finf.Name}:{i - bytelen}");
matchcount++;
hitlen2 = 0;
}
@ -218,7 +220,7 @@ namespace CodeWalker.Tools
}
FileSearchAddResult(string.Format("Search complete. {0} results found.", matchcount));
FileSearchAddResult($"Search complete. {matchcount} results found.");
FileSearchComplete();
InProgress = false;
});
@ -479,7 +481,7 @@ namespace CodeWalker.Tools
{ continue; }
}
UpdateStatus(string.Format("{0} - Searching {1}/{2} : {3}", duration.ToString(@"hh\:mm\:ss"), curfile, totfiles, fentry.Path));
UpdateStatus($"{duration:hh\\:mm\\:ss} - Searching {curfile}/{totfiles} : {fentry.Path}");
byte[] filebytes = fentry.File.ExtractFile(fentry);
if (filebytes == null) continue;
@ -655,7 +657,7 @@ namespace CodeWalker.Tools
{
if (entry is RpfDirectoryEntry rde)
{
FileInfoLabel.Text = rde.Path + " (Directory)";
FileInfoLabel.Text = $"{rde.Path} (Directory)";
DataTextBox.Text = "[Please select a data file]";
}
else
@ -678,7 +680,7 @@ namespace CodeWalker.Tools
byte[] data = rfe.File.ExtractFile(rfe);
int datalen = (data != null) ? data.Length : 0;
FileInfoLabel.Text = rfe.Path + " (" + typestr + " file) - " + TextUtil.GetBytesReadable(datalen);
FileInfoLabel.Text = $"{rfe.Path} ({typestr} file) - {TextUtil.GetBytesReadable(datalen)}";
if (ShowLargeFileContentsCheckBox.Checked || (datalen < 524287)) //512K

View File

@ -1097,7 +1097,7 @@ namespace CodeWalker.Tools
{ continue; }
}
UpdateStatus(string.Format("{0} - Searching {1}/{2} : {3}", duration.ToString(@"hh\:mm\:ss"), curfile, totfiles, fentry.Path));
UpdateStatus($"{duration:hh\\:mm\\:ss} - Searching {curfile}/{totfiles} : {fentry.Path}");
byte[] filebytes = fentry.File.ExtractFile(fentry);
if (filebytes == null) continue;

View File

@ -1,6 +1,7 @@
using CodeWalker.GameFiles;
using CodeWalker.Properties;
using CodeWalker.Utils;
using CommunityToolkit.Diagnostics;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@ -64,20 +65,17 @@ namespace CodeWalker.Tools
}
private void UpdateExtractStatus(string text)
private async void UpdateExtractStatus(string text)
{
try
{
if (InvokeRequired)
{
Invoke(new Action(() => { UpdateExtractStatus(text); }));
}
else
{
ExtractStatusLabel.Text = text;
}
await this.SwitchToUiContext();
ExtractStatusLabel.Text = text;
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
catch { }
}
private void ExtractButton_Click(object sender, EventArgs e)
@ -91,12 +89,12 @@ namespace CodeWalker.Tools
}
if (!Directory.Exists(FolderTextBox.Text))
{
MessageBox.Show("Folder doesn't exist: " + FolderTextBox.Text);
MessageBox.Show($"Folder doesn't exist: {FolderTextBox.Text}");
return;
}
if (!Directory.Exists(OutputFolderTextBox.Text))
{
MessageBox.Show("Folder doesn't exist: " + OutputFolderTextBox.Text);
MessageBox.Show($"Folder doesn't exist: {OutputFolderTextBox.Text}");
return;
}
//if (Directory.GetFiles(OutputFolderTextBox.Text, "*.ysc", SearchOption.AllDirectories).Length > 0)
@ -118,7 +116,7 @@ namespace CodeWalker.Tools
bool bydd = YddCheckBox.Checked;
bool byft = YftCheckBox.Checked;
Task.Run(() =>
Task.Run(async () =>
{
UpdateExtractStatus("Keys loaded.");
@ -146,22 +144,39 @@ namespace CodeWalker.Tools
if (bytd && entry.IsExtension(".ytd"))
{
UpdateExtractStatus(entry.Path);
YtdFile ytd = RpfManager.GetFile<YtdFile>(entry);
if (ytd is null) throw new Exception("Couldn't load file.");
if (ytd.TextureDict is null) throw new Exception("Couldn't load texture dictionary.");
if (ytd.TextureDict.Textures is null) throw new Exception("Couldn't load texture dictionary texture array.");
if (ytd.TextureDict.Textures.data_items is null) throw new Exception("Texture dictionary had no entries...");
YtdFile? ytd = await RpfManager.GetFileAsync<YtdFile>(entry);
if (ytd is null)
{
ThrowHelper.ThrowInvalidOperationException($"Couldn't load ytd file {entry.Path ?? entry.File.Path}");
}
if (ytd.TextureDict is null)
{
ThrowHelper.ThrowInvalidOperationException($"Couldn't load texture dictionary for file {entry.Path ?? entry.File.Path}");
}
if (ytd.TextureDict.Textures is null)
{
ThrowHelper.ThrowInvalidOperationException($"Couldn't load texture dictionary texture array for file {entry.Path ?? entry.File.Path}");
}
if (ytd.TextureDict.Textures.data_items is null)
{
ThrowHelper.ThrowInvalidOperationException($"Texture dictionary had no entries... for file {entry.Path ?? entry.File.Path}");
}
foreach (var tex in ytd.TextureDict.Textures.data_items)
{
SaveTexture(tex, entry, outputpath);
await SaveTextureAsync(tex, entry, outputpath);
}
}
else if (bydr && entry.IsExtension(".ydr"))
{
UpdateExtractStatus(entry.Path);
YdrFile ydr = RpfManager.GetFile<YdrFile>(entry);
if (ydr is null) throw new Exception("Couldn't load file.");
if (ydr.Drawable is null) throw new Exception("Couldn't load drawable.");
YdrFile? ydr = await RpfManager.GetFileAsync<YdrFile>(entry);
if (ydr is null)
{
ThrowHelper.ThrowInvalidOperationException($"Couldn't load ydr file {entry.Path ?? entry.File.Path}");
}
if (ydr.Drawable is null) {
ThrowHelper.ThrowInvalidOperationException($"Couldn't load drawable. array for file {entry.Path ?? entry.File.Path}");
}
if (ydr.Drawable.ShaderGroup is not null)
{
var ydrtd = ydr.Drawable.ShaderGroup.TextureDictionary;
@ -169,7 +184,7 @@ namespace CodeWalker.Tools
{
foreach (var tex in ydrtd.Textures.data_items)
{
SaveTexture(tex, entry, outputpath);
await SaveTextureAsync(tex, entry, outputpath);
}
}
}
@ -177,22 +192,30 @@ namespace CodeWalker.Tools
else if (bydd && entry.IsExtension(".ydd"))
{
UpdateExtractStatus(entry.Path);
YddFile ydd = RpfManager.GetFile<YddFile>(entry);
if (ydd == null) throw new Exception("Couldn't load file.");
YddFile? ydd = await RpfManager.GetFileAsync<YddFile>(entry);
if (ydd is null)
{
ThrowHelper.ThrowInvalidOperationException($"Couldn't load ydd file {entry.Path ?? entry.File.Path}");
}
//if (ydd.DrawableDict == null) throw new Exception("Couldn't load drawable dictionary.");
//if (ydd.DrawableDict.Drawables == null) throw new Exception("Drawable dictionary had no items...");
//if (ydd.DrawableDict.Drawables.data_items == null) throw new Exception("Drawable dictionary had no items...");
if ((ydd.Dict==null)||(ydd.Dict.Count==0)) throw new Exception("Drawable dictionary had no items...");
if (ydd.Dict is null || ydd.Dict.Count == 0)
{
ThrowHelper.ThrowInvalidDataException("Drawable dictionary has no items...");
}
foreach (var drawable in ydd.Dict.Values)
{
if (drawable.ShaderGroup != null)
{
var ydrtd = drawable.ShaderGroup.TextureDictionary;
if ((ydrtd != null) && (ydrtd.Textures != null) && (ydrtd.Textures.data_items != null))
if (ydrtd?.Textures?.data_items != null)
{
foreach (var tex in ydrtd.Textures.data_items)
{
SaveTexture(tex, entry, outputpath);
await SaveTextureAsync(tex, entry, outputpath);
}
}
}
@ -201,9 +224,15 @@ namespace CodeWalker.Tools
else if (byft && entry.IsExtension(".yft"))
{
UpdateExtractStatus(entry.Path);
YftFile yft = RpfManager.GetFile<YftFile>(entry);
if (yft == null) throw new Exception("Couldn't load file.");
if (yft.Fragment == null) throw new Exception("Couldn't load fragment.");
YftFile? yft = await RpfManager.GetFileAsync<YftFile>(entry);
if (yft is null)
{
ThrowHelper.ThrowInvalidOperationException($"Couldn't load yft file {entry.Path ?? entry.File.Path}");
}
if (yft.Fragment is null)
{
ThrowHelper.ThrowInvalidDataException($"Couldn't load fragment for file {entry.Path ?? entry.File.Path}");
}
if (yft.Fragment.Drawable != null)
{
if (yft.Fragment.Drawable.ShaderGroup != null)
@ -213,7 +242,7 @@ namespace CodeWalker.Tools
{
foreach (var tex in ydrtd.Textures.data_items)
{
SaveTexture(tex, entry, outputpath);
await SaveTextureAsync(tex, entry, outputpath);
}
}
}
@ -222,7 +251,8 @@ namespace CodeWalker.Tools
}
catch (Exception ex)
{
string err = entry.Name + ": " + ex.Message;
Console.WriteLine(ex);
string err = $"{entry.Name}: {ex.Message}";
UpdateExtractStatus(err);
errsb.AppendLine(err);
}
@ -237,14 +267,14 @@ namespace CodeWalker.Tools
});
}
private void SaveTexture(Texture tex, RpfEntry entry, string folder)
private static async Task SaveTextureAsync(Texture tex, RpfEntry entry, string folder)
{
//DirectXTex
byte[] dds = DDSIO.GetDDSFile(tex);
string bpath = folder + "\\" + entry.Name + "_" + tex.Name;
string bpath = $"{folder}\\{entry.Name}_{tex.Name}";
string fpath = bpath + ".dds";
int c = 1;
while (File.Exists(fpath))
@ -253,8 +283,7 @@ namespace CodeWalker.Tools
c++;
}
File.WriteAllBytes(fpath, dds);
await File.WriteAllBytesAsync(fpath, dds);
}

View File

@ -167,7 +167,7 @@ namespace CodeWalker.Tools
if (hasstr && hastxt)
{
MatchTextBox.Text = string.Format("JenkIndex match:\r\n{0}\r\nGlobalText match:\r\n{1}", str, txt);
MatchTextBox.Text = $"JenkIndex match:\r\n{str}\r\nGlobalText match:\r\n{txt}";
}
else if (hasstr)
{

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using SharpDX;
using SharpDX.Direct3D11;
using CodeWalker.Utils;
using CodeWalker.Core.Utils;
namespace CodeWalker
{
@ -49,7 +50,7 @@ namespace CodeWalker
}
catch (Exception ex)
{
errorAction("Could not load map icon " + Filepath + " for " + Name + "!\n\n" + ex.ToString());
errorAction($"Could not load map icon {Filepath} for {Name}!\n\n{ex}");
}
}
@ -75,42 +76,48 @@ namespace CodeWalker
public class MapMarker
{
public MapIcon Icon { get; set; }
public MapIcon? Icon { get; set; }
public Vector3 WorldPos { get; set; } //actual world pos
public Vector3 CamRelPos { get; set; } //updated per frame
public Vector3 ScreenPos { get; set; } //position on screen (updated per frame)
public string Name { get; set; }
public string Name { get; set; } = string.Empty;
public List<string> Properties { get; set; } //additional data
public bool IsMovable { get; set; }
public float Distance { get; set; } //length of CamRelPos, updated per frame
public void Parse(string s)
public void Parse(ReadOnlySpan<char> s)
{
Vector3 p = new Vector3(0.0f);
string[] ss = s.Split(',');
if (ss.Length > 1)
var enumerator = s.EnumerateSplit(',');
if (!enumerator.MoveNext())
return;
FloatUtil.TryParse(enumerator.Current.Trim(), out p.X);
if (!enumerator.MoveNext())
return;
FloatUtil.TryParse(enumerator.Current.Trim(), out p.Y);
if (enumerator.MoveNext())
{
FloatUtil.TryParse(ss[0].Trim(), out p.X);
FloatUtil.TryParse(ss[1].Trim(), out p.Y);
}
if (ss.Length > 2)
{
FloatUtil.TryParse(ss[2].Trim(), out p.Z);
}
if (ss.Length > 3)
{
Name = ss[3].Trim();
}
else
{
Name = string.Empty;
}
for (int i = 4; i < ss.Length; i++)
{
if (Properties == null) Properties = new List<string>();
Properties.Add(ss[i].Trim());
FloatUtil.TryParse(enumerator.Current.Trim(), out p.Z);
}
WorldPos = p;
if (enumerator.MoveNext())
{
Name = enumerator.Current.Trim().ToString();
}
while(enumerator.MoveNext())
{
Properties ??= new List<string>();
Properties.Add(enumerator.Current.Trim().ToString());
}
}
public override string ToString()
@ -118,12 +125,12 @@ namespace CodeWalker
string cstr = Get3DWorldPosString();
if (!string.IsNullOrEmpty(Name))
{
cstr += ", " + Name;
if (Properties != null)
cstr += $", {Name}";
if (Properties is not null)
{
foreach (string prop in Properties)
{
cstr += ", " + prop;
cstr += $", {prop}";
}
}
}
@ -132,11 +139,11 @@ namespace CodeWalker
public string Get2DWorldPosString()
{
return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", WorldPos.X, WorldPos.Y);
return string.Create(CultureInfo.InvariantCulture, $"{WorldPos.X}, {WorldPos.Y}");
}
public string Get3DWorldPosString()
{
return string.Format(CultureInfo.InvariantCulture, "{0}, {1}, {2}", WorldPos.X, WorldPos.Y, WorldPos.Z);
return string.Create(CultureInfo.InvariantCulture, $"{WorldPos.X}, {WorldPos.Y}, {WorldPos.Z}");
}

View File

@ -551,9 +551,9 @@ namespace CodeWalker
var tstr = tex.Name.Trim();
if (tex is Texture t)
{
tstr = string.Format("{0} ({1}x{2}, embedded)", tex.Name, t.Width, t.Height);
tstr = $"{tex.Name} ({t.Width}x{t.Height}, embedded)";
}
var tnode = tgnode.Nodes.Add(hash.ToString().Trim() + ": " + tstr);
var tnode = tgnode.Nodes.Add($"{hash}: {tstr}");
tnode.Tag = tex;
}
}
@ -750,7 +750,7 @@ namespace CodeWalker
int ih = (int)fh;
int im = v - (ih * 60);
if (ih == 24) ih = 0;
TimeOfDayLabel.Text = string.Format("{0:00}:{1:00}", ih, im);
TimeOfDayLabel.Text = $"{ih:00}:{im:00}";
}

View File

@ -767,7 +767,7 @@ namespace CodeWalker.World
{
if (obj.Enabled == false) continue;
if (obj.HideEntity != null)
if (obj.HideEntity is not null)
{
renderer.RenderHideEntity(obj.HideEntity);
}

View File

@ -162,7 +162,7 @@ namespace CodeWalker.World
LastUpdate.Restart();
try
{
await this.SwitchToUi();
await this.SwitchToUiContext();
ArchetypeSearchStatusLabel.Text = text;
}
catch(Exception ex)
@ -175,7 +175,7 @@ namespace CodeWalker.World
{
try
{
await this.SwitchToUi();
await this.SwitchToUiContext();
if (ArchetypeResults.Contains(arch))
return;
ArchetypeResults.Add(arch);
@ -191,7 +191,7 @@ namespace CodeWalker.World
{
try
{
await this.SwitchToUi();
await this.SwitchToUiContext();
ArchetypeSearchTextBox.Enabled = true;
ArchetypeSearchButton.Enabled = true;
ArchetypeSearchAbortButton.Enabled = false;
@ -416,7 +416,7 @@ namespace CodeWalker.World
LastUpdate.Restart();
try
{
await this.SwitchToUi();
await this.SwitchToUiContext();
EntitySearchStatusLabel.Text = text;
}
catch (Exception ex)
@ -429,7 +429,7 @@ namespace CodeWalker.World
{
try
{
await this.SwitchToUi();
await this.SwitchToUiContext();
if (EntityResults.Contains(ent))
return;
EntityResults.Add(ent);
@ -445,7 +445,7 @@ namespace CodeWalker.World
{
try
{
await this.SwitchToUi();
await this.SwitchToUiContext();
EntitySearchTextBox.Enabled = true;
EntitySearchButton.Enabled = true;
EntitySearchAbortButton.Enabled = false;

File diff suppressed because it is too large Load Diff