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 CodeWalker.GameFiles;
using Collections.Pooled; using Collections.Pooled;
using CommunityToolkit.HighPerformance; using CommunityToolkit.HighPerformance;
using Iced.Intel;
using SharpDX; using SharpDX;
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@ -127,7 +130,7 @@ namespace CodeWalker.Benchmarks
} }
} }
[Params(10, 100, 1000)] [Params(1000)]
public int Length { get; set; } = 10000; public int Length { get; set; } = 10000;
[Params(100)] [Params(100)]
@ -147,10 +150,14 @@ namespace CodeWalker.Benchmarks
private int randomValue; private int randomValue;
private uint[] ushorts = new uint[0]; private byte[] bytes = new byte[16];
private string Str = "iakslgbhfibnrihbderpiugaehigoI BIHGVUIVDSOUFVBOUADGBOIUYfgiuywetrg872q13rh9872`134tgyihsbaopuJGUIYODGBFIOUFgvbouailksdbhnfp"; private string Str = "iakslgbhfibnrihbderpiugaehigoI BIHGVUIVDSOUFVBOUADGBOIUYfgiuywetrg872q13rh9872`134tgyihsbaopuJGUIYODGBFIOUFgvbouailksdbhnfp";
private char[] chars1 = new char[0];
private Vector3 WorldPos;
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup()
{ {
@ -159,19 +166,39 @@ namespace CodeWalker.Benchmarks
valueByte = 0; valueByte = 0;
valueUint = 0; valueUint = 0;
ushorts = new uint[Length]; Str = "";
for (int i = 0; i < Length; i++) for (int i = 0; i < Length; i++)
{ {
ushorts[i] = (uint)random.Next(0, int.MaxValue); if (random.Next(0, 10) <= 1)
}
var hashes = MemoryMarshal.Cast<uint, MetaHash>(ushorts);
for (int i = 0; i < Length; i++)
{ {
Console.WriteLine($"{ushorts[i]} -> {hashes[i]}"); Str += random.Next('A', 'Z');
} else
{
Str += random.Next('a', 'z');
} }
}
chars1 = Str.ToCharArray();
//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"); //Console.WriteLine("Setup done");
@ -245,6 +272,234 @@ namespace CodeWalker.Benchmarks
// return Str.AsSpan().IndexOf('\0'); // 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() private PooledList<SimpleType3> getPooledListClass()
{ {
var list = new PooledList<SimpleType3>(); var list = new PooledList<SimpleType3>();
@ -343,22 +598,25 @@ namespace CodeWalker.Benchmarks
} }
} }
[Benchmark] [Benchmark]
public void ReverseEndianness() [SkipLocalsInit]
public void WriteData()
{ {
//BinaryPrimitives.ReverseEndianness(MemoryMarshal.Cast<float, uint>(ushorts), MemoryMarshal.Cast<float, uint>(ushorts)); WriteData(bytes);
} }
[Benchmark] [Benchmark]
public void SwapBytes() [SkipLocalsInit]
public void WriteDataOld()
{ {
var _ushorts = ushorts; WriteDataOld(bytes);
for (int i = 0; i < _ushorts.Length; i++) }
[Benchmark]
[SkipLocalsInit]
public void WriteDataOneGo()
{ {
_ushorts[i] = MetaTypes.SwapBytes(_ushorts[i]); WriteDataOneGo(bytes);
}
} }
} }
} }

View File

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

View File

@ -297,8 +297,7 @@ namespace CodeWalker.GameFiles
public override string ToString() public override string ToString()
{ {
return string.Format("{0}, {1}, {2}", return $"{Start}, {Count}, {Offset}";
Start, Count, Offset);
} }
} }
@ -399,7 +398,7 @@ namespace CodeWalker.GameFiles
public override string ToString() 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 public class WaterFlow : WaterItem

View File

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

View File

@ -13,6 +13,9 @@ using System.Diagnostics.CodeAnalysis;
using CommunityToolkit.HighPerformance; using CommunityToolkit.HighPerformance;
using Collections.Pooled; using Collections.Pooled;
using System.Threading; using System.Threading;
using System.Collections;
using System.Globalization;
using CodeWalker.Core.Utils.TypeConverters;
namespace CodeWalker.GameFiles; namespace CodeWalker.GameFiles;
@ -41,7 +44,7 @@ public class YmapFile : GameFile, PackedFile
public YmapEntityDef[] RootEntities { get; set; } = []; public YmapEntityDef[] RootEntities { get; set; } = [];
public YmapEntityDef[] MloEntities { 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 public YmapFile? Parent
{ {
@ -52,6 +55,12 @@ public class YmapFile : GameFile, PackedFile
return null; return null;
} }
if (target is not null && target.IsDisposed)
{
parent.SetTarget(null);
return null;
}
return target; return target;
} }
set set
@ -296,9 +305,9 @@ public class YmapFile : GameFile, PackedFile
{ {
//build the entity hierarchy. //build the entity hierarchy.
List<YmapEntityDef> roots = new List<YmapEntityDef>(instcount); var roots = PooledListPool<YmapEntityDef>.Shared.Get();
List<YmapEntityDef> alldefs = new List<YmapEntityDef>(instcount); var alldefs = PooledListPool<YmapEntityDef>.Shared.Get();
List<YmapEntityDef> mlodefs = null; YmapEntityDef[]? mlodefs = null;
if (CEntityDefs.Length > 0) if (CEntityDefs.Length > 0)
{ {
@ -310,7 +319,7 @@ public class YmapFile : GameFile, PackedFile
} }
if (CMloInstanceDefs.Length > 0) if (CMloInstanceDefs.Length > 0)
{ {
mlodefs = new List<YmapEntityDef>(CMloInstanceDefs.Length); mlodefs = new YmapEntityDef[CMloInstanceDefs.Length];
for (int i = 0; i < CMloInstanceDefs.Length; i++) for (int i = 0; i < CMloInstanceDefs.Length; i++)
{ {
YmapEntityDef d = new YmapEntityDef(this, i, CMloInstanceDefs[i]); YmapEntityDef d = new YmapEntityDef(this, i, CMloInstanceDefs[i]);
@ -320,29 +329,31 @@ public class YmapFile : GameFile, PackedFile
d.MloInstance.defaultEntitySets = defentsets; d.MloInstance.defaultEntitySets = defentsets;
} }
alldefs.Add(d); 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; int pind = d._CEntityDef.parentIndex;
bool isroot = false; bool isroot = false;
if ((pind < 0) || (pind >= alldefs.Count) || d.LodInParentYmap) if (pind < 0 || pind >= allEntities.Length || d.LodInParentYmap)
{ {
isroot = true; isroot = true;
} }
else else
{ {
YmapEntityDef p = alldefs[pind]; YmapEntityDef p = allEntities[pind];
if ((p._CEntityDef.lodLevel <= d._CEntityDef.lodLevel) || if ((p._CEntityDef.lodLevel <= d._CEntityDef.lodLevel) ||
((p._CEntityDef.lodLevel == rage__eLodType.LODTYPES_DEPTH_ORPHANHD) && ((p._CEntityDef.lodLevel == rage__eLodType.LODTYPES_DEPTH_ORPHANHD) &&
(d._CEntityDef.lodLevel != rage__eLodType.LODTYPES_DEPTH_ORPHANHD))) (d._CEntityDef.lodLevel != rage__eLodType.LODTYPES_DEPTH_ORPHANHD)))
{ {
isroot = true; isroot = true;
p = null;
} }
} }
@ -352,22 +363,24 @@ public class YmapFile : GameFile, PackedFile
} }
else else
{ {
YmapEntityDef p = alldefs[pind]; YmapEntityDef p = allEntities[pind];
p.AddChild(d); 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) RootEntities = roots.ToArray();
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 (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) 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 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; public object LodManagerRenderable = null;
@ -2079,7 +2096,8 @@ public class YmapEntityDef
public void ChildListToArray() public void ChildListToArray()
{ {
if (ChildList == null) return; if (ChildList is null || ChildList.Count == 0)
return;
//if (Children == null) //if (Children == null)
//{ //{
Children = ChildList.ToArray(); Children = ChildList.ToArray();

View File

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

View File

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

View File

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

View File

@ -1931,7 +1931,9 @@ namespace CodeWalker.GameFiles
return null; //couldn't find the strings data section. return null; //couldn't find the strings data section.
} }
using PooledList<string> strings = new PooledList<string>(); PooledList<string> strings = PooledListPool<string>.Shared.Get();
try
{
var currentblock = startblock; var currentblock = startblock;
int currentblockind = startblockind; int currentblockind = startblockind;
while (currentblock != null) while (currentblock != null)
@ -1990,6 +1992,11 @@ namespace CodeWalker.GameFiles
} }
return strings.ToArray(); return strings.ToArray();
} }
finally
{
PooledListPool<string>.Shared.Return(strings);
}
}
[SkipLocalsInit] [SkipLocalsInit]
public static string? GetString(this Meta meta, in CharPointer ptr) public static string? GetString(this Meta meta, in CharPointer ptr)
@ -4921,7 +4928,7 @@ namespace CodeWalker.GameFiles
public void AddScenarioPoint(MCExtensionDefSpawnPoint p) public void AddScenarioPoint(MCExtensionDefSpawnPoint p)
{ {
List<MCExtensionDefSpawnPoint> newpoints = new List<MCExtensionDefSpawnPoint>(); List<MCExtensionDefSpawnPoint> newpoints = new List<MCExtensionDefSpawnPoint>();
if (ScenarioPoints != null) if (ScenarioPoints is not null)
{ {
newpoints.AddRange(ScenarioPoints); 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 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\"?>"; public const string XmlHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";

View File

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

View File

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

View File

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

View File

@ -25,7 +25,7 @@ using System.Threading.Tasks;
namespace CodeWalker.GameFiles namespace CodeWalker.GameFiles
{ {
public struct FileCounts public record struct FileCounts
{ {
public uint Rpfs; public uint Rpfs;
public uint Files; public uint Files;
@ -54,16 +54,6 @@ namespace CodeWalker.GameFiles
BinaryFiles = a.BinaryFiles + b.BinaryFiles 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 public class RpfFile
@ -285,7 +275,6 @@ namespace CodeWalker.GameFiles
rfe.IsEncrypted = rfe.IsExtension(".ysc");//any other way to know..? rfe.IsEncrypted = rfe.IsExtension(".ysc");//any other way to know..?
} }
allEntries.Add(e); 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); BaseRpfs = new List<RpfFile>(1300);
ModRpfs = new List<RpfFile>(0); ModRpfs = new List<RpfFile>(0);
DlcRpfs = new List<RpfFile>(3500); DlcRpfs = new List<RpfFile>(3500);
AllRpfs = new List<RpfFile>(5000); AllRpfs ??= new List<RpfFile>(0);
DlcNoModRpfs = new List<RpfFile>(3500); DlcNoModRpfs = new List<RpfFile>(3500);
AllNoModRpfs = new List<RpfFile>(5000); AllNoModRpfs = new List<RpfFile>(5000);
RpfDict = new Dictionary<string, RpfFile>(DefaultRpfDictCapacity, StringComparer.OrdinalIgnoreCase); RpfDict = new Dictionary<string, RpfFile>(StringComparer.OrdinalIgnoreCase);
EntryDict = new Dictionary<string, RpfEntry>(DefaultEntryDictCapacity, StringComparer.OrdinalIgnoreCase); EntryDict = new Dictionary<string, RpfEntry>(StringComparer.OrdinalIgnoreCase);
ModRpfDict = new Dictionary<string, RpfFile>(StringComparer.OrdinalIgnoreCase); ModRpfDict = new Dictionary<string, RpfFile>(StringComparer.OrdinalIgnoreCase);
ModEntryDict = new Dictionary<string, RpfEntry>(StringComparer.OrdinalIgnoreCase); ModEntryDict = new Dictionary<string, RpfEntry>(StringComparer.OrdinalIgnoreCase);
FileCounts _fileCounts = default;
var rpfs = new ConcurrentBag<RpfFile>(); var rpfs = new ConcurrentBag<RpfFile>();
Parallel.ForEach(allfiles, (rpfpath) => Parallel.ForEach(allfiles, (rpfpath) =>
{ {
@ -79,21 +81,27 @@ namespace CodeWalker.GameFiles
{ {
RpfFile rf = new RpfFile(rpfpath, rpfpath.Replace(replpath, "")); RpfFile rf = new RpfFile(rpfpath, rpfpath.Replace(replpath, ""));
if (ExcludePaths != null) if (ExcludePaths is not null)
{ {
bool excl = false; 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; excl = true;
break; 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) if (rf.LastException != null) //incase of corrupted rpf (or renamed NG encrypted RPF)
{ {
@ -115,11 +123,10 @@ namespace CodeWalker.GameFiles
return rpf.AllEntries?.Count ?? 0 + rpf.Children?.Sum(calculateSum) ?? 0; return rpf.AllEntries?.Count ?? 0 + rpf.Children?.Sum(calculateSum) ?? 0;
} }
var minCapacity = rpfs.Sum(calculateSum); AllRpfs.EnsureCapacity((int)_fileCounts.Rpfs);
if (minCapacity > AllRpfs.Capacity) RpfDict.EnsureCapacity((int)_fileCounts.Rpfs);
{ AllNoModRpfs.EnsureCapacity((int)_fileCounts.Rpfs);
AllRpfs.Capacity = minCapacity; EntryDict.EnsureCapacity((int)_fileCounts.Rpfs + (int)_fileCounts.Files);
}
foreach (var rpf in rpfs) foreach (var rpf in rpfs)
{ {
@ -142,8 +149,7 @@ namespace CodeWalker.GameFiles
IsInited = true; 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}"); 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))); 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)); ismod = ismod || (file.Path.StartsWith("mods\\", StringComparison.OrdinalIgnoreCase));
if (file.AllEntries != null) if (file.AllEntries is not null)
{ {
AllRpfs.Add(file); AllRpfs.Add(file);
if (!ismod) if (!ismod)
@ -237,7 +243,7 @@ namespace CodeWalker.GameFiles
} }
else else
{ {
EntryDict[entry.Path] = entry; _ = EntryDict.TryAdd(entry.Path, entry);
} }
} }
@ -246,7 +252,7 @@ namespace CodeWalker.GameFiles
{ {
file.LastError = ex.ToString(); file.LastError = ex.ToString();
file.LastException = ex; 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 namespace CodeWalker.GameFiles
{ {
[SkipLocalsInit]
public class GTACrypto public class GTACrypto
{ {
@ -232,8 +233,8 @@ namespace CodeWalker.GameFiles
table[15][data[15]] ^ table[15][data[15]] ^
key[3]; key[3];
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data[..8]), x1 | (ulong)x2 << 32); Unsafe.WriteUnaligned(ref data[0], x1 | (ulong)x2 << 32);
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data.Slice(8, 8)), x3 | (ulong)x4 << 32); Unsafe.WriteUnaligned(ref data[8], x3 | (ulong)x4 << 32);
//Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data[..4]), x1); //Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data[..4]), x1);
//Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data.Slice(4, 4)), x2); //Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data.Slice(4, 4)), x2);
@ -271,8 +272,8 @@ namespace CodeWalker.GameFiles
table[12][data[12]] ^ table[12][data[12]] ^
key[3]; key[3];
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data[..8]), x1 | (ulong)x2 << 32); Unsafe.WriteUnaligned(ref data[0], x1 | (ulong)x2 << 32);
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data.Slice(8, 8)), x3 | (ulong)x4 << 32); Unsafe.WriteUnaligned(ref data[8], x3 | (ulong)x4 << 32);
//Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data[..4]), x1); //Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data[..4]), x1);
//Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(data.Slice(4, 4)), x2); //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 class JenkIndex
{ {
//public static ConcurrentDictionary<uint, string> Index = new ConcurrentDictionary<uint, string>(Environment.ProcessorCount * 2, 2000000); //public static ConcurrentDictionary<uint, string> Index = new ConcurrentDictionary<uint, string>(Environment.ProcessorCount * 2, 2000000);
@ -400,6 +401,7 @@ namespace CodeWalker.GameFiles
} }
return res; return res;
} }
public static string TryGetString(uint hash) public static string TryGetString(uint hash)
{ {
string res; string res;
@ -417,8 +419,5 @@ namespace CodeWalker.GameFiles
var res = Index.Values; var res = Index.Values;
return res; return res;
} }
} }
} }

View File

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

View File

@ -12,16 +12,6 @@ namespace CodeWalker
public static class MatrixExtensions 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) 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; 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); 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 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 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); return new Vector3(x, y, z);
//this quick mul ignores W... //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 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 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... //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 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 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 CommunityToolkit.HighPerformance.Buffers;
using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.ObjectPool;
using System; using System;
@ -19,31 +21,49 @@ namespace CodeWalker.Core.Utils
public class PooledListObjectPolicy<T> : PooledObjectPolicy<PooledList<T>> public class PooledListObjectPolicy<T> : PooledObjectPolicy<PooledList<T>>
{ {
private readonly ClearMode clearMode;
public PooledListObjectPolicy(ClearMode _clearMode = ClearMode.Auto)
{
clearMode = _clearMode;
}
public PooledList<T> Get() public PooledList<T> Get()
{ {
return new PooledList<T>(); return new PooledList<T>(clearMode);
} }
public override PooledList<T> Create() public override PooledList<T> Create()
{ {
return new PooledList<T>(); return new PooledList<T>(clearMode);
} }
public override bool Return(PooledList<T> list) 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(); list.Clear();
return true; 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 public static class StringPoolExtension
{ {
[SkipLocalsInit] [SkipLocalsInit]

View File

@ -1,46 +1,99 @@
using System; using Microsoft.Extensions.ObjectPool;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading;
using System.Xml.Linq;
namespace CodeWalker.Core.Utils namespace CodeWalker.Core.Utils
{ {
public class DisposableTimer : IDisposable public class DisposableTimer : Stopwatch, IDisposable, IResettable
{ {
public static event Action<TimeSpan, string> TimerStopped; public static event Action<TimeSpan, string> OnDispose;
public Stopwatch Stopwatch { get; init; } public Stopwatch Stopwatch => this;
static DisposableTimer() static DisposableTimer()
{ {
#if DEBUG #if DEBUG
TimerStopped += (timeSpan, name) => Debug.WriteLine($"{name} took {timeSpan.TotalMilliseconds} ms"); TimerStopped += (timeSpan, name) => Debug.WriteLine($"{name} took {timeSpan.TotalMilliseconds} ms");
#endif #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 string Name { get; private set; }
public DisposableTimer(string name) public DisposableTimer([CallerMemberName] string name = "") : base()
{ {
Stopwatch = Stopwatch.StartNew(); Start();
Name = name; Name = name;
} }
public DisposableTimer(string name, bool start) public DisposableTimer(string name, bool start)
{ {
Name = name; Name = name;
if (start) if (start)
{ {
Stopwatch = Stopwatch.StartNew(); Start();
} else
{
Stopwatch = new Stopwatch();
} }
} }
public void Dispose() public void Dispose()
{ {
Stopwatch.Stop(); Stop();
TimerStopped?.Invoke(Stopwatch.Elapsed, Name ?? string.Empty); 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@ -21,8 +22,31 @@ namespace CodeWalker
public static class TextUtil 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 //shamelessly stolen from stackoverflow, and a bit mangled

View File

@ -8,6 +8,18 @@ using CodeWalker.GameFiles;
namespace CodeWalker 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 class Vectors
{ {
public static Vector3 XYZ(in this Vector4 v) public static Vector3 XYZ(in this Vector4 v)

View File

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

View File

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

View File

@ -503,7 +503,7 @@ namespace CodeWalker
action?.Invoke(args); action?.Invoke(args);
} }
public static SwitchToUiAwaitable SwitchToUi(this Form form) public static SwitchToUiAwaitable SwitchToUiContext(this Form form)
{ {
return new SwitchToUiAwaitable(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); BoundsMaterialTypes.Init(FileCache);
UpdateStatus?.Invoke("Loading scenario types..."); UpdateStatus?.Invoke("Loading scenario types...");
Scenarios.EnsureScenarioTypes(FileCache); await Scenarios.EnsureScenarioTypes(FileCache);
UpdateStatus?.Invoke("File cache loaded."); UpdateStatus?.Invoke("File cache loaded.");
} }
@ -2548,7 +2548,8 @@ namespace CodeWalker
collectDrawable(drawable); collectDrawable(drawable);
collectTextures(drawable); collectTextures(drawable);
} }
} else if (entry.IsExtension(".ytd")) }
else if (entry.IsExtension(".ytd"))
{ {
UpdateStatus?.Invoke(entry.Path); UpdateStatus?.Invoke(entry.Path);
YtdFile ytd = RpfFile.GetFile<YtdFile>(entry, fileData); YtdFile ytd = RpfFile.GetFile<YtdFile>(entry, fileData);
@ -2605,7 +2606,7 @@ namespace CodeWalker
foreach (var tp in texparams) foreach (var tp in texparams)
{ {
//MetaXml.WriteCustomItemArray(sb, tp.Value.Textures, 3, "Texture"); //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) foreach(var texture in tp.Value.Textures)
{ {
@ -2618,15 +2619,15 @@ namespace CodeWalker
{ {
var svp = s.GetSortedList(vp.Value); var svp = s.GetSortedList(vp.Value);
var defval = svp.FirstOrDefault(); 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) foreach (var ap in arrparams)
{ {
var defval = ap.Value.FirstOrDefault(); 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) 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"); MetaXml.CloseTag(sb, 3, "Item");
} }
@ -5248,7 +5249,15 @@ namespace CodeWalker
ImageIndex = 1; //FOLDER imageIndex ImageIndex = 1; //FOLDER imageIndex
var ic = fld.GetItemCount(); var ic = fld.GetItemCount();
fileSize = ic; fileSize = ic;
fileSizeText = $"{ic} item{((ic != 1) ? "s" : "")}"; if (ic != 1)
{
fileSizeText = $"{ic} items";
}
else
{
fileSizeText = $"{ic} item";
}
} }
} }
else else

View File

@ -1147,7 +1147,7 @@ namespace CodeWalker.Forms
int ih = (int)fh; int ih = (int)fh;
int im = v - (ih * 60); int im = v - (ih * 60);
if (ih == 24) ih = 0; 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); var tgnode = tmnode.Nodes.Add(gname);
tgnode.Tag = geom; 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 pl = geom.Shader.ParametersList;
var h = pl.Hashes; var h = pl.Hashes;
@ -1446,9 +1446,9 @@ namespace CodeWalker.Forms
var tstr = tex.Name.Trim(); var tstr = tex.Name.Trim();
if (tex is Texture t) 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; tnode.Tag = tex;
} }
} }

View File

@ -1091,7 +1091,7 @@ namespace CodeWalker
int ih = (int)fh; int ih = (int)fh;
int im = v - (ih * 60); int im = v - (ih * 60);
if (ih == 24) ih = 0; 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); UnkVec1TextBox.Text = FloatUtil.GetVector4String(z.UnkVec1);
UnkVec2TextBox.Text = FloatUtil.GetVector4String(z.UnkVec2); UnkVec2TextBox.Text = FloatUtil.GetVector4String(z.UnkVec2);
UnkVec3TextBox.Text = FloatUtil.GetVector2String(z.UnkVec3); 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; Flags0TextBox.Text = z.Flags0.Hex;
Flags1TextBox.Text = z.Flags1.Hex; Flags1TextBox.Text = z.Flags1.Hex;
Unk13TextBox.Text = z.Unk13.Hex; Unk13TextBox.Text = z.Unk13.Hex;
@ -110,7 +110,7 @@ namespace CodeWalker.Project.Panels
SceneTextBox.Text = z.Scene.ToString(); SceneTextBox.Text = z.Scene.ToString();
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
if (z.Rules != null) if (z.Rules is not null)
{ {
foreach (var hash in z.Rules) foreach (var hash in z.Rules)
{ {

View File

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

View File

@ -812,11 +812,7 @@ namespace CodeWalker.Project.Panels
}); });
} }
UpdateStatus($"Process complete. {hitTestCount} hit tests, {hitCount} hits, {newCount} new polys");
var statf = "{0} hit tests, {1} hits, {2} new polys";
var stats = string.Format(statf, hitTestCount, hitCount, newCount);
UpdateStatus("Process complete. " + stats);
GenerateComplete(); 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.Name = "Nodes";
nodesnode.Tag = ynd; nodesnode.Tag = ynd;
var nodes = ynd.Nodes; var nodes = ynd.Nodes;
for (int i = 0; i < nodes.Length; i++) for (int i = 0; i < nodes.Length; i++)
{ {
var ynode = nodes[i]; var ynode = nodes[i];
var nnode = ynode.RawData; var nnode = ynode._RawData;
var tnode = nodesnode.Nodes.Add(nnode.ToString()); var tnode = nodesnode.Nodes.Add(nnode.ToString());
tnode.Tag = ynode; tnode.Tag = ynode;
} }
@ -2315,7 +2315,7 @@ namespace CodeWalker.Project.Panels
var tn = FindYmapTreeNode(ymap); var tn = FindYmapTreeNode(ymap);
if (tn != null) if (tn != null)
{ {
await this.SwitchToUi(); await this.SwitchToUiContext();
tn.Text = ymap.RpfFileEntry?.Name ?? ymap.Name; tn.Text = ymap.RpfFileEntry?.Name ?? ymap.Name;
} }
} }
@ -2357,7 +2357,7 @@ namespace CodeWalker.Project.Panels
var tn = FindYnvTreeNode(ynv); var tn = FindYnvTreeNode(ynv);
if (tn != null) if (tn != null)
{ {
await this.SwitchToUi(); await this.SwitchToUiContext();
tn.Text = ynv.RpfFileEntry?.Name ?? ynv.Name; tn.Text = ynv.RpfFileEntry?.Name ?? ynv.Name;
} }
} }
@ -2366,7 +2366,7 @@ namespace CodeWalker.Project.Panels
var tn = FindTrainTrackTreeNode(track); var tn = FindTrainTrackTreeNode(track);
if (tn != null) if (tn != null)
{ {
await this.SwitchToUi(); await this.SwitchToUiContext();
tn.Text = track.RpfFileEntry?.Name ?? track.Name; tn.Text = track.RpfFileEntry?.Name ?? track.Name;
} }
} }
@ -2375,7 +2375,7 @@ namespace CodeWalker.Project.Panels
var tn = FindScenarioTreeNode(scenarios); var tn = FindScenarioTreeNode(scenarios);
if (tn != null) if (tn != null)
{ {
await this.SwitchToUi(); await this.SwitchToUiContext();
tn.Text = scenarios.RpfFileEntry?.Name ?? scenarios.Name; tn.Text = scenarios.RpfFileEntry?.Name ?? scenarios.Name;
} }
} }
@ -2384,7 +2384,7 @@ namespace CodeWalker.Project.Panels
var tn = FindAudioRelTreeNode(rel); var tn = FindAudioRelTreeNode(rel);
if (tn != null) if (tn != null)
{ {
await this.SwitchToUi(); await this.SwitchToUiContext();
tn.Text = rel.RpfFileEntry?.Name ?? rel.Name; tn.Text = rel.RpfFileEntry?.Name ?? rel.Name;
} }
} }
@ -2393,7 +2393,7 @@ namespace CodeWalker.Project.Panels
var tn = FindArchetypeTreeNode(archetype); var tn = FindArchetypeTreeNode(archetype);
if (tn != null) if (tn != null)
{ {
await this.SwitchToUi(); await this.SwitchToUiContext();
tn.Text = archetype._BaseArchetypeDef.ToString(); tn.Text = archetype._BaseArchetypeDef.ToString();
} }
} }

View File

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

View File

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

View File

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

View File

@ -1938,7 +1938,7 @@ namespace CodeWalker.Rendering
{ {
var rndbl = GetArchetypeRenderable(ent.Archetype); var rndbl = GetArchetypeRenderable(ent.Archetype);
ent.LodManagerRenderable = rndbl; ent.LodManagerRenderable = rndbl;
if (rndbl != null) if (rndbl is not null)
{ {
RenderEntities.Add(ent); 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; return null;
Renderable rndbl = null; if (!ArchetypeRenderables.TryGetValue(arch, out Renderable? rndbl))
if (!ArchetypeRenderables.TryGetValue(arch, out rndbl))
{ {
var drawable = gameFileCache.TryGetDrawable(arch); var drawable = gameFileCache.TryGetDrawable(arch);
rndbl = TryGetRenderable(arch, drawable); rndbl = TryGetRenderable(arch, drawable);
if (rndbl != null && rndbl.IsLoaded) if (rndbl is not null && rndbl.IsLoaded)
{ {
ArchetypeRenderables[arch] = rndbl; ArchetypeRenderables[arch] = rndbl;
} }
} }
if ((rndbl != null) && rndbl.IsLoaded && (rndbl.AllTexturesLoaded || !waitforchildrentoload)) if (rndbl is not null && rndbl.IsLoaded && (rndbl.AllTexturesLoaded || !waitforchildrentoload))
{ {
return rndbl; return rndbl;
} }
@ -2454,9 +2453,7 @@ namespace CodeWalker.Rendering
public void RenderYmap(YmapFile ymap) public void RenderYmap(YmapFile ymap)
{ {
if (ymap is null) if (ymap is null || !ymap.Loaded)
return;
if (!ymap.Loaded)
return; return;
if (ymap.AllEntities.Length > 0 && ymap.RootEntities.Length > 0) 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); RenderYmapCarGenerators(ymap);
} }
if (rendergrass && ymap.GrassInstanceBatches != null && ymap.GrassInstanceBatches.Length > 0) if (rendergrass && ymap.GrassInstanceBatches is not null && ymap.GrassInstanceBatches.Length > 0)
{ {
RenderYmapGrass(ymap); RenderYmapGrass(ymap);
} }
if (renderdistlodlights && timecycle.IsNightTime && ymap.DistantLODLights != null) if (renderdistlodlights && timecycle.IsNightTime && ymap.DistantLODLights is not null)
{ {
RenderYmapDistantLODLights(ymap); RenderYmapDistantLODLights(ymap);
} }
@ -2715,14 +2712,13 @@ namespace CodeWalker.Rendering
var maxdist = 200 * renderworldDetailDistMult; var maxdist = 200 * renderworldDetailDistMult;
var maxdist2 = maxdist * maxdist; 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; var bscent = cg.Position - camera.Position;
float bsrad = cg._CCarGen.perpendicularLength; float bsrad = cg._CCarGen.perpendicularLength;
if (bscent.LengthSquared() > maxdist2) if (bscent.LengthSquared() > maxdist2)
continue; //don't render distant cars.. continue; //don't render distant cars..
if (!camera.ViewFrustum.ContainsSphereNoClipNoOpt(ref bscent, bsrad)) if (!camera.ViewFrustum.ContainsSphereNoClipNoOpt(ref bscent, bsrad))
{ {
continue; //frustum cull cars... continue; //frustum cull cars...
@ -2737,43 +2733,28 @@ namespace CodeWalker.Rendering
} }
private void RenderWheels(Archetype? arch, YmapEntityDef ent, FragType fragment, uint txdhash = 0, ClipMapEntry? animClip = null)
public bool RenderFragment(Archetype arch, YmapEntityDef ent, FragType f, uint txdhash = 0, ClipMapEntry animClip = null)
{ {
if (fragment.PhysicsLODGroup?.PhysicsLOD1?.Children?.data_items is null)
return;
RenderDrawable(f.Drawable, arch, ent, txdhash, null, null, animClip); var pl1 = fragment.PhysicsLODGroup.PhysicsLOD1;
if (f.DrawableCloth != null) //cloth
{
RenderDrawable(f.DrawableCloth, arch, ent, txdhash, null, null, animClip);
}
//vehicle wheels...
if ((f.PhysicsLODGroup != null) && (f.PhysicsLODGroup.PhysicsLOD1 != null))
{
var pl1 = f.PhysicsLODGroup.PhysicsLOD1;
//var groupnames = pl1?.GroupNames?.data_items; //var groupnames = pl1?.GroupNames?.data_items;
var groups = pl1?.Groups?.data_items; //var groups = pl1.Groups?.data_items;
FragDrawable wheel_f = null; FragDrawable? wheel_f = null;
FragDrawable wheel_r = null; FragDrawable? wheel_r = null;
if (pl1.Children?.data_items != null)
foreach (var pch in pl1.Children.data_items)
{ {
for (int i = 0; i < pl1.Children.data_items.Length; i++)
{
var pch = pl1.Children.data_items[i];
//var groupname = pch.GroupNameHash; //var groupname = pch.GroupNameHash;
//if ((pl1.Groups?.data_items != null) && (i < pl1.Groups.data_items.Length)) //if ((pl1.Groups?.data_items != null) && (i < pl1.Groups.data_items.Length))
//{ //{
// //var group = pl1.Groups.data_items[i]; // //var group = pl1.Groups.data_items[i];
//} //}
if ((pch.Drawable1 != null) && (pch.Drawable1.AllModels.Length != 0)) if (pch.Drawable1 is not null && pch.Drawable1.AllModels.Length != 0)
{ {
switch (pch.BoneTag) switch (pch.BoneTag)
@ -2802,7 +2783,7 @@ namespace CodeWalker.Rendering
} }
else else
{ } { }
if ((pch.Drawable2 != null) && (pch.Drawable2.AllModels.Length != 0)) if (pch.Drawable2 is not null && pch.Drawable2.AllModels.Length != 0)
{ {
RenderDrawable(pch.Drawable2, arch, ent, txdhash, null, null, animClip); RenderDrawable(pch.Drawable2, arch, ent, txdhash, null, null, animClip);
} }
@ -2810,18 +2791,18 @@ namespace CodeWalker.Rendering
{ } { }
} }
if ((wheel_f != null) || (wheel_r != null)) if (wheel_f is not null || wheel_r != null)
{ {
for (int i = 0; i < pl1.Children.data_items.Length; i++) for (int i = 0; i < pl1.Children.data_items.Length; i++)
{ {
var pch = pl1.Children.data_items[i]; var pch = pl1.Children.data_items[i];
FragDrawable dwbl = pch.Drawable1; FragDrawable dwbl = pch.Drawable1;
FragDrawable dwblcopy = null; FragDrawable? dwblcopy = null;
switch (pch.BoneTag) switch (pch.BoneTag)
{ {
case 27922: //wheel_lf case 27922: //wheel_lf
case 26418: //wheel_rf case 26418: //wheel_rf
dwblcopy = wheel_f != null ? wheel_f : wheel_r; dwblcopy = wheel_f ?? wheel_r;
break; break;
case 29921: //wheel_lm1 case 29921: //wheel_lm1
case 29922: //wheel_lm2 case 29922: //wheel_lm2
@ -2831,7 +2812,7 @@ namespace CodeWalker.Rendering
case 5858: //wheel_rm2 case 5858: //wheel_rm2
case 5859: //wheel_rm3 case 5859: //wheel_rm3
case 26398: //wheel_rr case 26398: //wheel_rr
dwblcopy = wheel_r != null ? wheel_r : wheel_f; dwblcopy = wheel_r ?? wheel_f;
break; break;
default: default:
break; break;
@ -2856,11 +2837,11 @@ namespace CodeWalker.Rendering
// break; // break;
//} //}
if (dwblcopy != null) if (dwblcopy is not null)
{ {
if (dwbl != null) if (dwbl is not null)
{ {
if ((dwbl != dwblcopy) && (dwbl.AllModels.Length == 0)) if (dwbl != dwblcopy && dwbl.AllModels.Length == 0)
{ {
dwbl.Owner = dwblcopy; dwbl.Owner = dwblcopy;
dwbl.AllModels = dwblcopy.AllModels; //hopefully this is all that's need to render, otherwise drawable is actually getting edited! dwbl.AllModels = dwblcopy.AllModels; //hopefully this is all that's need to render, otherwise drawable is actually getting edited!
@ -2881,43 +2862,28 @@ namespace CodeWalker.Rendering
{ } { }
} }
} }
}
} }
bool isselected = SelectionFlagsTestAll || (f.Drawable == SelectedDrawable); private static readonly uint ColourBlue = (uint)Color.Blue.ToRgba();
if (isselected) private static readonly uint ColourRed = (uint)Color.Red.ToRgba();
private void RenderFragmentWindows(Archetype? arch, YmapEntityDef ent, FragType fragment, uint txdhash = 0, ClipMapEntry? animClip = null)
{ {
var darr = f.DrawableArray?.data_items; if (!renderfragwindows)
if (darr != null) return;
{
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 eori = Quaternion.Identity;
var epos = Vector3.Zero; var epos = Vector3.Zero;
if (ent != null) if (ent is not null)
{ {
eori = ent.Orientation; eori = ent.Orientation;
epos = ent.Position; epos = ent.Position;
} }
if (f.GlassWindows?.data_items != null) if (fragment.GlassWindows?.data_items is not null)
{ {
for (int i = 0; i < f.GlassWindows.data_items.Length; i++) foreach(var gw in fragment.GlassWindows.data_items)
{ {
var gw = f.GlassWindows.data_items[i];
var projt = gw.ProjectionRow1;//row0? or row3? maybe investigate more var projt = gw.ProjectionRow1;//row0? or row3? maybe investigate more
var proju = gw.ProjectionRow2;//row1 of XYZ>UV projection var proju = gw.ProjectionRow2;//row1 of XYZ>UV projection
var projv = gw.ProjectionRow3;//row2 of XYZ>UV projection var projv = gw.ProjectionRow3;//row2 of XYZ>UV projection
@ -2931,36 +2897,41 @@ namespace CodeWalker.Rendering
var grplod = gw.GroupLOD; var grplod = gw.GroupLOD;
var xforms = grplod?.FragTransforms?.Matrices; var xforms = grplod?.FragTransforms?.Matrices;
var xoffs = Vector3.Zero; var xoffs = Vector3.Zero;
if ((grp != null) && (xforms != null) && (grp.ChildIndex < xforms.Length) && (grplod != null)) if (grp is not null && xforms is not null && grp.ChildIndex < xforms.Length && grplod is not null)
{ {
var xform = xforms[grp.ChildIndex]; var xform = xforms[grp.ChildIndex];
xoffs = xform.TranslationVector + grplod.PositionOffset; xoffs = xform.TranslationVector + grplod.PositionOffset;
} }
var m = new Matrix(); var m = new Matrix(
m.Row1 = new Vector4(projt, 0); projt.X, projt.Y, projt.Z, 0,
m.Row2 = new Vector4(proju, 0); proju.X, proju.Y, proju.Z, 0,
m.Row3 = new Vector4(projv, 0); projv.X, projv.Y, projv.Z, 0,
m.Row4 = new Vector4(xoffs, 1); 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 v0 = m.Multiply(new Vector3(1, 0, 0));
var v1 = m.Multiply(new Vector3(1, 0, 1)); var v1 = m.Multiply(new Vector3(1, 0, 1));
var v2 = m.Multiply(new Vector3(1, 1, 1)); var v2 = m.Multiply(new Vector3(1, 1, 1));
var v3 = m.Multiply(new Vector3(1, 1, 0)); var v3 = m.Multiply(new Vector3(1, 1, 0));
var c0 = eori.Multiply(v0) + epos; var c0 = eori.Multiply(in v0) + epos;
var c1 = eori.Multiply(v1) + epos; var c1 = eori.Multiply(in v1) + epos;
var c2 = eori.Multiply(v2) + epos; var c2 = eori.Multiply(in v2) + epos;
var c3 = eori.Multiply(v3) + epos; var c3 = eori.Multiply(in v3) + epos;
RenderSelectionLine(c0, c1, colblu); RenderSelectionLine(in c0, in c1, ColourBlue);
RenderSelectionLine(c1, c2, colblu); RenderSelectionLine(in c1, in c2, ColourBlue);
RenderSelectionLine(c2, c3, colblu); RenderSelectionLine(in c2, in c3, ColourBlue);
RenderSelectionLine(c3, c0, colblu); RenderSelectionLine(in c3, in c0, ColourBlue);
//RenderSelectionLine(c0, c0 + tangt, colred); //RenderSelectionLine(c0, c0 + tangt, colred);
} }
} }
if (f.VehicleGlassWindows?.Windows != null) if (fragment.VehicleGlassWindows?.Windows is not null)
{ {
for (int i = 0; i < f.VehicleGlassWindows.Windows.Length; i++) foreach(var vgw in fragment.VehicleGlassWindows.Windows)
{ {
var vgw = f.VehicleGlassWindows.Windows[i];
//var grp = vgw.Group; //var grp = vgw.Group;
//var grplod = vgw.GroupLOD; //var grplod = vgw.GroupLOD;
var m = vgw.Projection; var m = vgw.Projection;
@ -2973,14 +2944,14 @@ namespace CodeWalker.Rendering
var v1 = m.MultiplyW(new Vector3(min.X, max.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 v2 = m.MultiplyW(new Vector3(max.X, max.Y, 0));
var v3 = m.MultiplyW(new Vector3(max.X, min.Y, 0)); var v3 = m.MultiplyW(new Vector3(max.X, min.Y, 0));
var c0 = eori.Multiply(v0) + epos; var c0 = eori.Multiply(in v0) + epos;
var c1 = eori.Multiply(v1) + epos; var c1 = eori.Multiply(in v1) + epos;
var c2 = eori.Multiply(v2) + epos; var c2 = eori.Multiply(in v2) + epos;
var c3 = eori.Multiply(v3) + epos; var c3 = eori.Multiply(in v3) + epos;
RenderSelectionLine(c0, c1, colblu); RenderSelectionLine(in c0, in c1, ColourBlue);
RenderSelectionLine(c1, c2, colblu); RenderSelectionLine(in c1, in c2, ColourBlue);
RenderSelectionLine(c2, c3, colblu); RenderSelectionLine(in c2, in c3, ColourBlue);
RenderSelectionLine(c3, c0, colblu); RenderSelectionLine(in c3, in c0, ColourBlue);
if (vgw.ShatterMap != null) if (vgw.ShatterMap != null)
{ {
var width = vgw.ShatterMapWidth; var width = vgw.ShatterMapWidth;
@ -3011,23 +2982,47 @@ namespace CodeWalker.Rendering
} }
} }
public bool RenderFragment(Archetype? arch, YmapEntityDef ent, FragType fragment, uint txdhash = 0, ClipMapEntry? animClip = null)
{
RenderDrawable(fragment.Drawable, arch, ent, txdhash, null, null, animClip);
if (fragment.DrawableCloth is not null) //cloth
{
RenderDrawable(fragment.DrawableCloth, arch, ent, txdhash, null, null, animClip);
}
//vehicle wheels...
RenderWheels(arch, ent, fragment, txdhash, animClip);
bool isselected = SelectionFlagsTestAll || (fragment.Drawable == SelectedDrawable);
if (isselected && fragment.DrawableArray?.data_items is not null)
{
foreach(var draw in fragment.DrawableArray.data_items)
{
RenderDrawable(draw, arch, ent, txdhash, null, null, animClip);
}
}
RenderFragmentWindows(arch, ent, fragment, txdhash, animClip);
return true; 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. //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; Vector3 camrel = entpos - camera.Position;
Quaternion orientation = Quaternion.Identity; Quaternion orientation = Quaternion.Identity;
Vector3 scale = Vector3.One; Vector3 scale = Vector3.One;
Vector3 bscent = camrel; Vector3 bscent = camrel;
if (entity != null) if (entity is not null)
{ {
orientation = entity.Orientation; orientation = entity.Orientation;
scale = entity.Scale; scale = entity.Scale;
@ -3051,19 +3046,14 @@ namespace CodeWalker.Rendering
if (boundsmode == BoundsShaderMode.Sphere) if (boundsmode == BoundsShaderMode.Sphere)
{ {
if ((bsrad < renderboundsmaxrad) && (dist < renderboundsmaxdist)) if (bsrad < renderboundsmaxrad && dist < renderboundsmaxdist)
{ {
MapSphere ms = new MapSphere BoundingSpheres.Add(new MapSphere(bscent, bsrad));
{
CamRelPos = bscent,
Radius = bsrad,
};
BoundingSpheres.Add(ms);
} }
} }
if (boundsmode == BoundsShaderMode.Box) if (boundsmode == BoundsShaderMode.Box)
{ {
if ((dist < renderboundsmaxdist)) if (dist < renderboundsmaxdist)
{ {
BoundingBoxes.Add( BoundingBoxes.Add(
new MapBox( new MapBox(
@ -3077,16 +3067,13 @@ namespace CodeWalker.Rendering
} }
} }
if (rndbl is null)
bool res = false;
if (rndbl == null)
{ {
var drawable = gameFileCache.TryGetDrawable(arche); var drawable = gameFileCache.TryGetDrawable(arche);
rndbl = TryGetRenderable(arche, drawable); rndbl = TryGetRenderable(arche, drawable);
} }
if (rndbl == null || !rndbl.IsLoaded) if (rndbl is null || !rndbl.IsLoaded)
{ {
return false; 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... //fragments have extra drawables! need to render those too... TODO: handle fragments properly...
if (rndbl.Key is FragDrawable fd) if (rndbl.Key is FragDrawable fd)
{ {
var frag = fd.OwnerFragment; 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); rndbl = TryGetRenderable(arche, frag.DrawableCloth);
if (rndbl != null && rndbl.IsLoaded) if (rndbl != null && rndbl.IsLoaded)
@ -3121,14 +3108,14 @@ namespace CodeWalker.Rendering
return res; 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. //enqueue a single drawable for rendering.
if (drawable is null) if (drawable is null)
return false; return false;
Renderable rndbl = TryGetRenderable(arche, drawable, txdHash, txdExtra, diffOverride); Renderable? rndbl = TryGetRenderable(arche, drawable, txdHash, txdExtra, diffOverride);
if (rndbl is null || !rndbl.IsLoaded) if (rndbl is null || !rndbl.IsLoaded)
return false; return false;
@ -3176,11 +3163,10 @@ namespace CodeWalker.Rendering
Vector3 bbmax = (arche != null) ? arche.BBMax : rndbl.Key.BoundingBoxMax; Vector3 bbmax = (arche != null) ? arche.BBMax : rndbl.Key.BoundingBoxMax;
Vector3 bscen = (arche != null) ? arche.BSCenter : rndbl.Key.BoundingCenter; Vector3 bscen = (arche != null) ? arche.BSCenter : rndbl.Key.BoundingCenter;
float radius = (arche != null) ? arche.BSRadius : rndbl.Key.BoundingSphereRadius; float radius = (arche != null) ? arche.BSRadius : rndbl.Key.BoundingSphereRadius;
float distance = 0;//(camrel + bscen).Length();
bool interiorent = false;
bool castshadow = true; bool castshadow = true;
if (entity != null) float distance;
if (entity is not null)
{ {
position = entity.Position; position = entity.Position;
scale = entity.Scale; scale = entity.Scale;
@ -3191,8 +3177,7 @@ namespace CodeWalker.Rendering
bscen = entity.BSCenter; bscen = entity.BSCenter;
camrel += position; camrel += position;
distance = entity.Distance; distance = entity.Distance;
castshadow = (entity.MloParent == null);//don't cast sun/moon shadows if this is an interior entity - optimisation! castshadow = (entity.MloParent is null);//don't cast sun/moon shadows if this is an interior entity - optimisation!
interiorent = (entity.MloParent != null);
} }
else else
{ {
@ -3213,7 +3198,7 @@ namespace CodeWalker.Rendering
{ {
rndbl.UpdateAnims(currentRealTime); rndbl.UpdateAnims(currentRealTime);
} }
if (rndbl.Cloth != null) if (rndbl.Cloth is not null)
{ {
rndbl.Cloth.Update(currentRealTime); rndbl.Cloth.Update(currentRealTime);
} }
@ -3246,7 +3231,7 @@ namespace CodeWalker.Rendering
RenderSkeleton(rndbl, entity); RenderSkeleton(rndbl, entity);
} }
if (renderlights && Shaders.deferred && (rndbl.Lights != null)) if (renderlights && Shaders.deferred && rndbl.Lights is not null)
{ {
entity?.EnsureLights(rndbl.Key); entity?.EnsureLights(rndbl.Key);
@ -3286,7 +3271,7 @@ namespace CodeWalker.Rendering
bool retval = true;// false; bool retval = true;// false;
if ((rndbl.AllTexturesLoaded || !waitforchildrentoload)) if (rndbl.AllTexturesLoaded || !waitforchildrentoload)
{ {
RenderableGeometryInst rginst = new RenderableGeometryInst(); RenderableGeometryInst rginst = new RenderableGeometryInst();
rginst.Inst.Renderable = rndbl; rginst.Inst.Renderable = rndbl;
@ -3305,10 +3290,8 @@ namespace CodeWalker.Rendering
RenderableModel[] models = isselected ? rndbl.HDModels : rndbl.GetModels(distance); 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 (isselected)
{ {
if (SelectionModelDrawFlags.ContainsKey(model.DrawableModel)) if (SelectionModelDrawFlags.ContainsKey(model.DrawableModel))
@ -3322,9 +3305,8 @@ namespace CodeWalker.Rendering
continue; continue;
} //filter out reflection proxy models... } //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; var dgeom = geom.DrawableGeom;
if (dgeom.UpdateRenderableParameters) //when edited by material editor if (dgeom.UpdateRenderableParameters) //when edited by material editor
@ -3361,21 +3343,18 @@ namespace CodeWalker.Rendering
return retval; return retval;
} }
public void RenderCar(Vector3 pos, in Quaternion ori, MetaHash modelHash, MetaHash modelSetHash, bool valign = false)
public void RenderCar(Vector3 pos, Quaternion ori, MetaHash modelHash, MetaHash modelSetHash, bool valign = false)
{ {
uint carhash = modelHash; uint carhash = modelHash;
if ((carhash == 0) && (modelSetHash != 0)) if (carhash == 0 && modelSetHash != 0)
{ {
//find the pop group... and choose a vehicle.. //find the pop group... and choose a vehicle..
var stypes = Scenarios.ScenarioTypes; var stypes = Scenarios.ScenarioTypes;
if (stypes != null) if (stypes is not null)
{ {
var modelset = stypes.GetVehicleModelSet(modelSetHash); 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); carhash = JenkHash.GenHash(modelset.Models[0].NameLower);
} }
@ -3383,8 +3362,8 @@ namespace CodeWalker.Rendering
} }
if (carhash == 0) carhash = 418536135; //"infernus" if (carhash == 0) carhash = 418536135; //"infernus"
YftFile caryft = gameFileCache.GetYft(carhash); YftFile? caryft = gameFileCache.GetYft(carhash);
if ((caryft != null) && (caryft.Loaded) && (caryft.Fragment != null)) if (caryft is not null && caryft.Loaded && caryft.Fragment is not null)
{ {
if (valign) if (valign)
{ {
@ -3515,15 +3494,14 @@ namespace CodeWalker.Rendering
{ {
RenderDrawable(drawable, null, ped.RenderEntity, 0, td, texture, ac, cloth, expr); RenderDrawable(drawable, null, ped.RenderEntity, 0, td, texture, ac, cloth, expr);
} }
} }
public void RenderHideEntity(YmapEntityDef ent) public void RenderHideEntity(YmapEntityDef ent)
{ {
var hash = ent?.EntityHash ?? 0; var hash = ent.EntityHash;
if (hash == 0) return; if (hash == 0)
return;
HideEntities[hash] = ent; 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) if (drawable is null)
return null; return null;
@ -3628,7 +3606,7 @@ namespace CodeWalker.Rendering
if (rndbl is null) if (rndbl is null)
return null; return null;
if ((clipDict != 0) && (rndbl.ClipDict == null)) if (clipDict != 0 && rndbl.ClipDict is null)
{ {
var ycd = gameFileCache.GetYcd(clipDict); var ycd = gameFileCache.GetYcd(clipDict);
if (ycd is not null && ycd.Loaded) if (ycd is not null && ycd.Loaded)
@ -3753,7 +3731,7 @@ namespace CodeWalker.Rendering
foreach (var geom in model.Geometries) foreach (var geom in model.Geometries)
{ {
if (geom.Textures != null) if (geom.HasTextures)
{ {
for (int i = 0; i < geom.Textures.Length; i++) for (int i = 0; i < geom.Textures.Length; i++)
{ {
@ -3863,7 +3841,7 @@ namespace CodeWalker.Rendering
RenderableTexture? rhdtex = null; RenderableTexture? rhdtex = null;
if (renderhdtextures) if (renderhdtextures)
{ {
Texture hdtex = geom.TexturesHD[i]; Texture? hdtex = geom.TexturesHD[i];
if (hdtex is null) if (hdtex is null)
{ {
//look for a replacement HD texture... //look for a replacement HD texture...
@ -3919,6 +3897,7 @@ namespace CodeWalker.Rendering
public readonly Archetype Archetype = archetype; public readonly Archetype Archetype = archetype;
public readonly YmapEntityDef Entity = entity; public readonly YmapEntityDef Entity = entity;
} }
public readonly struct RenderedBoundComposite(RenderableBoundComposite boundComp, YmapEntityDef entity) public readonly struct RenderedBoundComposite(RenderableBoundComposite boundComp, YmapEntityDef entity)
{ {
public readonly RenderableBoundComposite BoundComp = boundComp; public readonly RenderableBoundComposite BoundComp = boundComp;
@ -4006,7 +3985,7 @@ namespace CodeWalker.Rendering
} }
if (ymap.Parent != pymap) 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); ymap.ConnectToParent(pymap);
} }
} }
@ -4025,8 +4004,11 @@ namespace CodeWalker.Rendering
{ {
var ymap = CurrentYmaps[remYmap]; var ymap = CurrentYmaps[remYmap];
CurrentYmaps.Remove(remYmap); CurrentYmaps.Remove(remYmap);
Console.WriteLine($"Removing ymap {ymap.Name} {ymap.FilePath}");
var remEnts = ymap.LodManagerOldEntities ?? ymap.AllEntities; 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++) for (int i = 0; i < remEnts.Length; i++)
{ {
@ -4063,10 +4045,13 @@ namespace CodeWalker.Rendering
} }
if (ymap._CMapData.parent != 0 && ymap.Parent is null) //skip adding ymaps until parents are available 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; continue;
} }
if (CurrentYmaps.TryAdd(key, ymap)) if (CurrentYmaps.TryAdd(key, ymap))
{ {
Console.WriteLine($"Adding ymap {ymap.Name} {ymap.FilePath}");
foreach (var ent in ymap.AllEntities) foreach (var ent in ymap.AllEntities)
{ {
if (ent.Parent is not null) if (ent.Parent is not null)

View File

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

View File

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

View File

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

View File

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

View File

@ -112,7 +112,9 @@ namespace CodeWalker.Tools
string searchfolder = FileSearchFolderTextBox.Text; string searchfolder = FileSearchFolderTextBox.Text;
AbortOperation = false; AbortOperation = false;
if (InProgress) return; if (InProgress)
return;
if (searchfolder.Length == 0) if (searchfolder.Length == 0)
{ {
MessageBox.Show("Please select a folder..."); MessageBox.Show("Please select a folder...");
@ -165,10 +167,10 @@ namespace CodeWalker.Tools
InProgress = true; InProgress = true;
Task.Run(() => _ = Task.Run(() =>
{ {
FileSearchAddResult("Searching " + searchfolder + "..."); FileSearchAddResult($"Searching {searchfolder}...");
string[] filenames = Directory.GetFiles(searchfolder); string[] filenames = Directory.GetFiles(searchfolder);
@ -195,13 +197,13 @@ namespace CodeWalker.Tools
if (hitlen1 == bytelen) if (hitlen1 == bytelen)
{ {
FileSearchAddResult(finf.Name + ":" + (i - bytelen)); FileSearchAddResult($"{finf.Name}:{i - bytelen}");
matchcount++; matchcount++;
hitlen1 = 0; hitlen1 = 0;
} }
if (hitlen2 == bytelen) if (hitlen2 == bytelen)
{ {
FileSearchAddResult(finf.Name + ":" + (i - bytelen)); FileSearchAddResult($"{finf.Name}:{i - bytelen}");
matchcount++; matchcount++;
hitlen2 = 0; 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(); FileSearchComplete();
InProgress = false; InProgress = false;
}); });
@ -479,7 +481,7 @@ namespace CodeWalker.Tools
{ continue; } { 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); byte[] filebytes = fentry.File.ExtractFile(fentry);
if (filebytes == null) continue; if (filebytes == null) continue;
@ -655,7 +657,7 @@ namespace CodeWalker.Tools
{ {
if (entry is RpfDirectoryEntry rde) if (entry is RpfDirectoryEntry rde)
{ {
FileInfoLabel.Text = rde.Path + " (Directory)"; FileInfoLabel.Text = $"{rde.Path} (Directory)";
DataTextBox.Text = "[Please select a data file]"; DataTextBox.Text = "[Please select a data file]";
} }
else else
@ -678,7 +680,7 @@ namespace CodeWalker.Tools
byte[] data = rfe.File.ExtractFile(rfe); byte[] data = rfe.File.ExtractFile(rfe);
int datalen = (data != null) ? data.Length : 0; 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 if (ShowLargeFileContentsCheckBox.Checked || (datalen < 524287)) //512K

View File

@ -1097,7 +1097,7 @@ namespace CodeWalker.Tools
{ continue; } { 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); byte[] filebytes = fentry.File.ExtractFile(fentry);
if (filebytes == null) continue; if (filebytes == null) continue;

View File

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

View File

@ -167,7 +167,7 @@ namespace CodeWalker.Tools
if (hasstr && hastxt) 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) else if (hasstr)
{ {

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using SharpDX; using SharpDX;
using SharpDX.Direct3D11; using SharpDX.Direct3D11;
using CodeWalker.Utils; using CodeWalker.Utils;
using CodeWalker.Core.Utils;
namespace CodeWalker namespace CodeWalker
{ {
@ -49,7 +50,7 @@ namespace CodeWalker
} }
catch (Exception ex) 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 class MapMarker
{ {
public MapIcon Icon { get; set; } public MapIcon? Icon { get; set; }
public Vector3 WorldPos { get; set; } //actual world pos public Vector3 WorldPos { get; set; } //actual world pos
public Vector3 CamRelPos { get; set; } //updated per frame public Vector3 CamRelPos { get; set; } //updated per frame
public Vector3 ScreenPos { get; set; } //position on screen (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 List<string> Properties { get; set; } //additional data
public bool IsMovable { get; set; } public bool IsMovable { get; set; }
public float Distance { get; set; } //length of CamRelPos, updated per frame 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); 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(enumerator.Current.Trim(), out p.Z);
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());
} }
WorldPos = p; 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() public override string ToString()
@ -118,12 +125,12 @@ namespace CodeWalker
string cstr = Get3DWorldPosString(); string cstr = Get3DWorldPosString();
if (!string.IsNullOrEmpty(Name)) if (!string.IsNullOrEmpty(Name))
{ {
cstr += ", " + Name; cstr += $", {Name}";
if (Properties != null) if (Properties is not null)
{ {
foreach (string prop in Properties) foreach (string prop in Properties)
{ {
cstr += ", " + prop; cstr += $", {prop}";
} }
} }
} }
@ -132,11 +139,11 @@ namespace CodeWalker
public string Get2DWorldPosString() 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() 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(); var tstr = tex.Name.Trim();
if (tex is Texture t) 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; tnode.Tag = tex;
} }
} }
@ -750,7 +750,7 @@ namespace CodeWalker
int ih = (int)fh; int ih = (int)fh;
int im = v - (ih * 60); int im = v - (ih * 60);
if (ih == 24) ih = 0; 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.Enabled == false) continue;
if (obj.HideEntity != null) if (obj.HideEntity is not null)
{ {
renderer.RenderHideEntity(obj.HideEntity); renderer.RenderHideEntity(obj.HideEntity);
} }

View File

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

File diff suppressed because it is too large Load Diff