From a5259c17fec510a9cc5a2f7c63b68008872bd09b Mon Sep 17 00:00:00 2001 From: dexy Date: Thu, 5 Dec 2019 21:05:31 +1100 Subject: [PATCH] Replaced world grid with quadtrees, improved loading speed of cutscenes --- .../GameFiles/FileTypes/CacheDatFile.cs | 16 + CodeWalker.Core/World/Ped.cs | 16 +- CodeWalker.Core/World/Space.cs | 1542 ++++++++--------- CodeWalker.Core/World/Vehicle.cs | 4 +- CodeWalker.Core/World/Weapon.cs | 2 +- Forms/ModelForm.cs | 2 +- PedsForm.cs | 2 +- Project/Panels/GenerateNavMeshPanel.cs | 225 +-- World/CutsceneForm.cs | 2 +- WorldForm.cs | 2 +- 10 files changed, 887 insertions(+), 926 deletions(-) diff --git a/CodeWalker.Core/GameFiles/FileTypes/CacheDatFile.cs b/CodeWalker.Core/GameFiles/FileTypes/CacheDatFile.cs index 08ee6c0..6749651 100644 --- a/CodeWalker.Core/GameFiles/FileTypes/CacheDatFile.cs +++ b/CodeWalker.Core/GameFiles/FileTypes/CacheDatFile.cs @@ -625,6 +625,22 @@ namespace CodeWalker.GameFiles } } + public void GetVisibleChildren(ref Vector3 p, List items) + { + if (Children == null) return; + for (int i = 0; i < Children.Length; i++) + { + var c = Children[i]; + if (c == null) continue; + var cmin = c.streamingExtentsMin; + var cmax = c.streamingExtentsMax; + if ((p.X >= cmin.X) && (p.X <= cmax.X) && (p.Y >= cmin.Y) && (p.Y <= cmax.Y)) + { + items.Add(c); + c.GetVisibleChildren(ref p, items); + } + } + } public override string ToString() { diff --git a/CodeWalker.Core/World/Ped.cs b/CodeWalker.Core/World/Ped.cs index ac02348..fc780fd 100644 --- a/CodeWalker.Core/World/Ped.cs +++ b/CodeWalker.Core/World/Ped.cs @@ -101,7 +101,7 @@ namespace CodeWalker.World Yld = gfc.GetFileUncached(clothFile); while ((Yld != null) && (!Yld.Loaded)) { - Thread.Sleep(20);//kinda hacky + Thread.Sleep(1);//kinda hacky gfc.TryLoadEnqueue(Yld); } } @@ -110,22 +110,22 @@ namespace CodeWalker.World while ((Ydd != null) && (!Ydd.Loaded)) { - Thread.Sleep(20);//kinda hacky + Thread.Sleep(1);//kinda hacky Ydd = gfc.GetYdd(pedhash); } while ((Ytd != null) && (!Ytd.Loaded)) { - Thread.Sleep(20);//kinda hacky + Thread.Sleep(1);//kinda hacky Ytd = gfc.GetYtd(pedhash); } while ((Ycd != null) && (!Ycd.Loaded)) { - Thread.Sleep(20);//kinda hacky + Thread.Sleep(1);//kinda hacky Ycd = gfc.GetYcd(ycdhash); } while ((Yft != null) && (!Yft.Loaded)) { - Thread.Sleep(20);//kinda hacky + Thread.Sleep(1);//kinda hacky Yft = gfc.GetYft(pedhash); } @@ -169,7 +169,7 @@ namespace CodeWalker.World var ydd = gfc.GetFileUncached(file); while ((ydd != null) && (!ydd.Loaded)) { - Thread.Sleep(20);//kinda hacky + Thread.Sleep(1);//kinda hacky gfc.TryLoadEnqueue(ydd); } if (ydd?.Drawables?.Length > 0) @@ -193,7 +193,7 @@ namespace CodeWalker.World var ytd = gfc.GetFileUncached(file); while ((ytd != null) && (!ytd.Loaded)) { - Thread.Sleep(20);//kinda hacky + Thread.Sleep(1);//kinda hacky gfc.TryLoadEnqueue(ytd); } if (ytd?.TextureDict?.Textures?.data_items.Length > 0) @@ -216,7 +216,7 @@ namespace CodeWalker.World var yld = gfc.GetFileUncached(file); while ((yld != null) && (!yld.Loaded)) { - Thread.Sleep(20);//kinda hacky + Thread.Sleep(1);//kinda hacky gfc.TryLoadEnqueue(yld); } if (yld?.ClothDictionary?.Clothes?.data_items?.Length > 0) diff --git a/CodeWalker.Core/World/Space.cs b/CodeWalker.Core/World/Space.cs index 98eee1d..b9c7f4d 100644 --- a/CodeWalker.Core/World/Space.cs +++ b/CodeWalker.Core/World/Space.cs @@ -18,8 +18,12 @@ namespace CodeWalker.World private GameFileCache GameFileCache = null; - public SpaceGrid Grid; - private Dictionary visibleBoundsDict = new Dictionary(); + + + public SpaceMapDataStore MapDataStore; + public SpaceBoundsStore BoundsStore; + + private Dictionary interiorLookup = new Dictionary(); private Dictionary interiorManifest = new Dictionary(); @@ -58,9 +62,14 @@ namespace CodeWalker.World InitCacheData(); - updateStatus("Building world grid..."); + updateStatus("Building map data store..."); - InitMapGrid(); + InitMapDataStore(); + + + updateStatus("Building bounds store..."); + + InitBoundsStore(); updateStatus("Loading paths..."); @@ -313,100 +322,45 @@ namespace CodeWalker.World } - private void InitMapGrid() + private void InitMapDataStore() { - Grid = new SpaceGrid(); + MapDataStore = new SpaceMapDataStore(); - //List containers = new List(); - //List critnodes = new List(); - //List hdnodes = new List(); - //List lodnodes = new List(); - //List strmnodes = new List(); - //List intnodes = new List(); - //List occlnodes = new List(); - //List grassnodes = new List(); - //List lodlightsnodes = new List(); + List rootnodes = new List(); + List lodnodes = new List(); + List orphnodes = new List(); foreach (var node in nodedict.Values) { - bool addtogrid = false; - byte t = (byte)(node.ContentFlags & 0xFF); - switch (node.ContentFlags)// t) + if (node.ParentName == 0) { - case 0: //for mods/unused stuff? could be interesting. - addtogrid = true; - break; - case 16://"container" node? - //containers.Add(node); - break; - case 18: - case 82: //HD nodes - //hdnodes.Add(node); - addtogrid = true; - break; - case 1: - case 65: //Stream nodes - //strmnodes.Add(node); - addtogrid = true; - break; - case 513: - case 577: //critical nodes - //critnodes.Add(node); - addtogrid = true; - break; - case 9: - case 73: //interior nodes - //intnodes.Add(node); - addtogrid = true; - break; - case 2: - case 4: - case 20: - case 66: - case 514: //LOD nodes - //lodnodes.Add(node); - addtogrid = true; - break; - case 128: - case 256: //LOD lights nodes - //lodlightsnodes.Add(node); - addtogrid = true; - break; - case 32: //occlusion nodes - //occlnodes.Add(node); - addtogrid = true; - break; - case 1088: //grass nodes - //grassnodes.Add(node); - addtogrid = true; - break; - default: - addtogrid = true; - break; + rootnodes.Add(node); + if (((node.ContentFlags & 2) > 0) || ((node.ContentFlags & 4) > 0) || ((node.ContentFlags & 16) > 0)) + { + lodnodes.Add(node); + } + else + { + orphnodes.Add(node); + } } - - - if (addtogrid) - { - Grid.AddNode(node); - } - } - foreach (var item in boundsdict.Values) - { - Grid.AddBounds(item); - } + MapDataStore.Init(rootnodes); - foreach (var intprx in interiorProxies.Values) - { - Grid.AddInterior(intprx); - } } + private void InitBoundsStore() + { + + BoundsStore = new SpaceBoundsStore(); + + BoundsStore.Init(boundsdict.Values.ToList()); + + } private void InitNodeGrid() { @@ -786,7 +740,7 @@ namespace CodeWalker.World public void Update(float elapsed) { if (!Inited) return; - if (Grid == null) return; + if (BoundsStore == null) return; if (elapsed > 0.1f) elapsed = 0.1f; @@ -1044,115 +998,39 @@ namespace CodeWalker.World public void GetVisibleYmaps(Camera cam, int hour, MetaHash weather, Dictionary ymaps) { if (!Inited) return; - if (Grid == null) return; - - - ymaps.Clear(); - - //var pos = Grid.GetCellPos(cam.Position); - var cell = Grid.GetCell(cam.Position); - if (cell.NodesList != null) + if (MapDataStore == null) return; + var items = MapDataStore.GetItems(ref cam.Position); + for (int i = 0; i < items.Count; i++) { - for (int n = 0; n < cell.NodesList.Count; n++) + var item = items[i]; + var hash = item.Name; + if (!ymaps.ContainsKey(hash)) { - var node = cell.NodesList[n]; - var hash = node.Name; - if (!ymaps.ContainsKey(hash)) + var ymap = (hash > 0) ? GameFileCache.GetYmap(hash) : null; + while ((ymap != null) && (ymap.Loaded)) { - var ymap = (hash > 0) ? GameFileCache.GetYmap(hash) : null; - while ((ymap != null) && (ymap.Loaded)) - { - if (!IsYmapAvailable(hash, hour, weather)) break; - ymaps[hash] = ymap; - hash = ymap._CMapData.parent; - if (ymaps.ContainsKey(hash)) break; - ymap = (hash > 0) ? GameFileCache.GetYmap(hash) : null; - } + if (!IsYmapAvailable(hash, hour, weather)) break; + ymaps[hash] = ymap; + hash = ymap._CMapData.parent; + if (ymaps.ContainsKey(hash)) break; + ymap = (hash > 0) ? GameFileCache.GetYmap(hash) : null; } } } - - - - //int gridrange = 0; - - //var pos = Grid.GetCellPos(cam.Position); - //int minx = Math.Min(Math.Max(pos.X - gridrange, 0), SpaceGrid.LastCell); - //int maxx = Math.Min(Math.Max(pos.X + gridrange, 0), SpaceGrid.LastCell); - //int miny = Math.Min(Math.Max(pos.Y - gridrange, 0), SpaceGrid.LastCell); - //int maxy = Math.Min(Math.Max(pos.Y + gridrange, 0), SpaceGrid.LastCell); - - //for (int x = minx; x <= maxx; x++) - //{ - // for (int y = miny; y <= maxy; y++) - // { - // var cell = Grid.GetCell(new Vector2I(x, y)); - // if (cell.NodesList != null) - // { - // for (int n = 0; n < cell.NodesList.Count; n++) - // { - // var node = cell.NodesList[n]; - // var hash = node.Name; - // if (!ymaps.ContainsKey(hash)) - // { - // var ymap = (hash > 0) ? GameFileCache.GetYmap(hash) : null; - // if ((ymap != null) && (ymap.Loaded)) - // { - // ymaps[hash] = ymap; - // } - // } - // } - // } - // } - //} - } public void GetVisibleBounds(Camera cam, int gridrange, bool[] layers, List boundslist) { if (!Inited) return; - if (Grid == null) return; - - visibleBoundsDict.Clear(); - - var pos = Grid.GetCellPos(cam.Position); - int minx = Math.Min(Math.Max(pos.X - gridrange, 0), SpaceGrid.LastCell); - int maxx = Math.Min(Math.Max(pos.X + gridrange, 0), SpaceGrid.LastCell); - int miny = Math.Min(Math.Max(pos.Y - gridrange, 0), SpaceGrid.LastCell); - int maxy = Math.Min(Math.Max(pos.Y + gridrange, 0), SpaceGrid.LastCell); - - for (int x = minx; x <= maxx; x++) - { - for (int y = miny; y <= maxy; y++) - { - var cell = Grid.GetCell(new Vector2I(x, y)); - if (cell.BoundsList != null) - { - foreach (var item in cell.BoundsList) - { - uint l = item.Layer; - if (l < 3) - { - if (!layers[l]) continue; - } - else - { } - - visibleBoundsDict[new SpaceBoundsKey(item.Name, item.Min)] = item; - } - } - } - } - - //var cell = grid.GetCell(cam.Position); - //if (cell.BoundsList != null) - //{ - // boundslist.AddRange(cell.BoundsList); - //} - - boundslist.AddRange(visibleBoundsDict.Values); + if (BoundsStore == null) return; + float dist = 50.0f * gridrange; + var pos = cam.Position; + var min = pos - dist; + var max = pos + dist; + var items = BoundsStore.GetItems(ref min, ref max); + boundslist.AddRange(items); } @@ -1174,7 +1052,7 @@ namespace CodeWalker.World public void GetVisibleYnvs(Camera cam, int gridrange, List ynvs) { if (!Inited) return; - if (Grid == null) return; + if (NavGrid == null) return; ynvs.Clear(); @@ -1213,18 +1091,11 @@ namespace CodeWalker.World int polytestcount = 0; int nodetestcount = 0; bool testcomplete = true; - var cellpos = Grid.GetCellPos(ray.Position); - var cell = Grid.GetCell(cellpos); - var startz = ray.Position.Z; - var maxcells = 5; - var cellcount = 0; var box = new BoundingBox(); var tsph = new BoundingSphere(); var rayt = new Ray(); var rp = ray.Position; var rd = ray.Direction; - float dirx = (rd.X < 0) ? -1 : (rd.X > 0) ? 1 : 0; - float diry = (rd.Y < 0) ? -1 : (rd.Y > 0) ? 1 : 0; var boxhitdist = float.MaxValue; var itemhitdist = float.MaxValue; Vector3 p1, p2, p3, p4, a1, a2, a3; @@ -1235,250 +1106,217 @@ namespace CodeWalker.World BoundMaterial_s hitmat = new BoundMaterial_s(); Vector3 hitnorm = Vector3.Zero; Vector3 hitpos = Vector3.Zero; - while (cell != null) + + if (BoundsStore == null) return res; + var boundslist = BoundsStore.GetItems(ref ray); + + foreach (var bound in boundslist) { - if (cell.BoundsList != null) + uint l = bound.Layer; + if ((layers != null) && (l < 3)) { - foreach (var bound in cell.BoundsList) + if (!layers[l]) continue; + } + + box.Minimum = bound.Min; + box.Maximum = bound.Max; + float boxhitdisttest; + if (ray.Intersects(ref box, out boxhitdisttest)) + { + YbnFile ybn = GameFileCache.GetYbn(bound.Name); + if (ybn == null) + { continue; } //ybn not found? + if (!ybn.Loaded) + { testcomplete = false; continue; } //ybn not loaded yet... + + bool ybnhit = false; + var b = ybn.Bounds; + box.Minimum = b.BoundingBoxMin; + box.Maximum = b.BoundingBoxMax; + float itemboxhitdisttest; + if (!ray.Intersects(ref box, out itemboxhitdisttest)) + { continue; } //ray doesn't hit this ybn + if (itemboxhitdisttest > itemhitdist) + { continue; } //already a closer hit. + if (itemboxhitdisttest > maxdist) + { continue; } + + switch (b.Type) { - uint l = bound.Layer; - if ((layers != null) && (l < 3)) - { - if (!layers[l]) continue; - } - - box.Minimum = bound.Min; - box.Maximum = bound.Max; - float boxhitdisttest; - if (ray.Intersects(ref box, out boxhitdisttest)) - { - YbnFile ybn = GameFileCache.GetYbn(bound.Name); - if (ybn == null) - { continue; } //ybn not found? - if (!ybn.Loaded) - { testcomplete = false; continue; } //ybn not loaded yet... - - bool ybnhit = false; - var b = ybn.Bounds; - box.Minimum = b.BoundingBoxMin; - box.Maximum = b.BoundingBoxMax; - float itemboxhitdisttest; - if (!ray.Intersects(ref box, out itemboxhitdisttest)) - { continue; } //ray doesn't hit this ybn - if (itemboxhitdisttest > itemhitdist) - { continue; } //already a closer hit. - if (itemboxhitdisttest > maxdist) + case 10: //BoundComposite + BoundComposite boundcomp = b as BoundComposite; + if (boundcomp == null) { continue; } - - switch (b.Type) + var compchilds = boundcomp.Children?.data_items; + if (compchilds == null) + { continue; } + for (int i = 0; i < compchilds.Length; i++) { - case 10: //BoundComposite - BoundComposite boundcomp = b as BoundComposite; - if (boundcomp == null) + BoundBVH bgeom = compchilds[i] as BoundBVH; + if (bgeom == null) + { continue; } + if (bgeom.Polygons == null) + { continue; } + if ((bgeom.BVH?.Nodes?.data_items == null) || (bgeom.BVH?.Trees?.data_items == null)) + { continue; } + + box.Minimum = bgeom.BoundingBoxMin; + box.Maximum = bgeom.BoundingBoxMax; + float bvhboxhittest; + if (!ray.Intersects(ref box, out bvhboxhittest)) + { continue; } + if (bvhboxhittest > itemhitdist) + { continue; } //already a closer hit. + + var q = bgeom.BVH.Quantum.XYZ(); + var c = bgeom.BVH.BoundingBoxCenter.XYZ(); + var cg = bgeom.CenterGeom; + for (int t = 0; t < bgeom.BVH.Trees.data_items.Length; t++) + { + var tree = bgeom.BVH.Trees.data_items[t]; + box.Minimum = new Vector3(tree.MinX, tree.MinY, tree.MinZ) * q + c; + box.Maximum = new Vector3(tree.MaxX, tree.MaxY, tree.MaxZ) * q + c; + if (!ray.Intersects(ref box, out bvhboxhittest)) { continue; } - var compchilds = boundcomp.Children?.data_items; - if (compchilds == null) + if (bvhboxhittest > itemhitdist) + { continue; } //already a closer hit. + if (bvhboxhittest > maxdist) { continue; } - for (int i = 0; i < compchilds.Length; i++) + + int nodeind = tree.NodeIndex1; + int lastind = tree.NodeIndex2; + while (nodeind < lastind) { - BoundBVH bgeom = compchilds[i] as BoundBVH; - if (bgeom == null) - { continue; } - if (bgeom.Polygons == null) - { continue; } - if ((bgeom.BVH?.Nodes?.data_items == null) || (bgeom.BVH?.Trees?.data_items == null)) - { continue; } - - box.Minimum = bgeom.BoundingBoxMin; - box.Maximum = bgeom.BoundingBoxMax; - float bvhboxhittest; - if (!ray.Intersects(ref box, out bvhboxhittest)) - { continue; } - if (bvhboxhittest > itemhitdist) - { continue; } //already a closer hit. - - var q = bgeom.BVH.Quantum.XYZ(); - var c = bgeom.BVH.BoundingBoxCenter.XYZ(); - var cg = bgeom.CenterGeom; - for (int t = 0; t < bgeom.BVH.Trees.data_items.Length; t++) + var node = bgeom.BVH.Nodes.data_items[nodeind]; + box.Minimum = new Vector3(node.MinX, node.MinY, node.MinZ) * q + c; + box.Maximum = new Vector3(node.MaxX, node.MaxY, node.MaxZ) * q + c; + bool nodehit = ray.Intersects(ref box, out bvhboxhittest); + bool nodeskip = !nodehit || (bvhboxhittest > itemhitdist); + if (node.PolyCount <= 0) //intermediate node with child nodes { - var tree = bgeom.BVH.Trees.data_items[t]; - box.Minimum = new Vector3(tree.MinX, tree.MinY, tree.MinZ) * q + c; - box.Maximum = new Vector3(tree.MaxX, tree.MaxY, tree.MaxZ) * q + c; - if (!ray.Intersects(ref box, out bvhboxhittest)) - { continue; } - if (bvhboxhittest > itemhitdist) - { continue; } //already a closer hit. - if (bvhboxhittest > maxdist) - { continue; } - - int nodeind = tree.NodeIndex1; - int lastind = tree.NodeIndex2; - while (nodeind < lastind) + if (nodeskip) { - var node = bgeom.BVH.Nodes.data_items[nodeind]; - box.Minimum = new Vector3(node.MinX, node.MinY, node.MinZ) * q + c; - box.Maximum = new Vector3(node.MaxX, node.MaxY, node.MaxZ) * q + c; - bool nodehit = ray.Intersects(ref box, out bvhboxhittest); - bool nodeskip = !nodehit || (bvhboxhittest > itemhitdist); - if (node.PolyCount <= 0) //intermediate node with child nodes - { - if (nodeskip) - { - nodeind += node.PolyId; //(child node count) - } - else - { - nodeind++; - } - } - else //leaf node, with polygons - { - if (!nodeskip) - { - var lastp = node.PolyId + node.PolyCount; - lastp = Math.Min(lastp, (int)bgeom.PolygonsCount); - for (int p = node.PolyId; p < lastp; p++) - { - var polygon = bgeom.Polygons[p]; - bool polyhit = false; - switch (polygon.Type) - { - case BoundPolygonType.Triangle: - var ptri = polygon as BoundPolygonTriangle; - p1 = bgeom.GetVertex(ptri.vertIndex1) + cg; - p2 = bgeom.GetVertex(ptri.vertIndex2) + cg; - p3 = bgeom.GetVertex(ptri.vertIndex3) + cg; - polyhit = ray.Intersects(ref p1, ref p2, ref p3, out polyhittestdist); - if (polyhit) n1 = Vector3.Normalize(Vector3.Cross(p2 - p1, p3 - p1)); - break; - case BoundPolygonType.Sphere: - var psph = polygon as BoundPolygonSphere; - tsph.Center = bgeom.GetVertex(psph.sphereIndex) + cg; - tsph.Radius = psph.sphereRadius; - polyhit = ray.Intersects(ref tsph, out polyhittestdist); - if (polyhit) n1 = Vector3.Normalize((ray.Position + ray.Direction * polyhittestdist) - tsph.Center); - break; - case BoundPolygonType.Capsule: - //TODO - break; - case BoundPolygonType.Box: - var pbox = polygon as BoundPolygonBox; - p1 = bgeom.GetVertex(pbox.boxIndex1);// + cg; //corner - p2 = bgeom.GetVertex(pbox.boxIndex2);// + cg; - p3 = bgeom.GetVertex(pbox.boxIndex3);// + cg; - p4 = bgeom.GetVertex(pbox.boxIndex4);// + cg; - a1 = ((p3 + p4) - (p1 + p2)) * 0.5f; - a2 = p3 - (p1 + a1); - a3 = p4 - (p1 + a1); - Vector3 bs = new Vector3(a1.Length(), a2.Length(), a3.Length()); - Vector3 m1 = a1 / bs.X; - Vector3 m2 = a2 / bs.Y; - Vector3 m3 = a3 / bs.Z; - if ((bs.X < bs.Y) && (bs.X < bs.Z)) m1 = Vector3.Cross(m2, m3); - else if (bs.Y < bs.Z) m2 = Vector3.Cross(m1, m3); - else m3 = Vector3.Cross(m1, m2); - Vector3 tp = rp - (p1 + cg); - rayt.Position = new Vector3(Vector3.Dot(tp, m1), Vector3.Dot(tp, m2), Vector3.Dot(tp, m3)); - rayt.Direction = new Vector3(Vector3.Dot(rd, m1), Vector3.Dot(rd, m2), Vector3.Dot(rd, m3)); - box.Minimum = Vector3.Zero; - box.Maximum = bs; - polyhit = rayt.Intersects(ref box, out polyhittestdist); - if (polyhit) - { - Vector3 hpt = rayt.Position + rayt.Direction * polyhittestdist; - const float eps = 0.002f; - if (Math.Abs(hpt.X) < eps) n1 = -m1; - else if (Math.Abs(hpt.X - bs.X) < eps) n1 = m1; - else if (Math.Abs(hpt.Y) < eps) n1 = -m2; - else if (Math.Abs(hpt.Y - bs.Y) < eps) n1 = m2; - else if (Math.Abs(hpt.Z) < eps) n1 = -m3; - else if (Math.Abs(hpt.Z - bs.Z) < eps) n1 = m3; - else - { n1 = Vector3.UnitZ; } //ray starts inside the box... - } - break; - case BoundPolygonType.Cylinder: - //TODO - break; - } - if (polyhit && (polyhittestdist < itemhitdist) && (polyhittestdist < maxdist)) - { - itemhitdist = polyhittestdist; - ybnhit = true; - hit = true; - hitnorm = n1; - hitpoly = polygon; - - byte matind = ((bgeom.PolygonMaterialIndices != null) && (p < bgeom.PolygonMaterialIndices.Length)) ? bgeom.PolygonMaterialIndices[p] : (byte)0; - BoundMaterial_s mat = ((bgeom.Materials != null) && (matind < bgeom.Materials.Length)) ? bgeom.Materials[matind] : new BoundMaterial_s(); - hitmat = mat; - } - polytestcount++; - } - } - nodeind++; - } - nodetestcount++; + nodeind += node.PolyId; //(child node count) + } + else + { + nodeind++; } } + else //leaf node, with polygons + { + if (!nodeskip) + { + var lastp = node.PolyId + node.PolyCount; + lastp = Math.Min(lastp, (int)bgeom.PolygonsCount); + for (int p = node.PolyId; p < lastp; p++) + { + var polygon = bgeom.Polygons[p]; + bool polyhit = false; + switch (polygon.Type) + { + case BoundPolygonType.Triangle: + var ptri = polygon as BoundPolygonTriangle; + p1 = bgeom.GetVertex(ptri.vertIndex1) + cg; + p2 = bgeom.GetVertex(ptri.vertIndex2) + cg; + p3 = bgeom.GetVertex(ptri.vertIndex3) + cg; + polyhit = ray.Intersects(ref p1, ref p2, ref p3, out polyhittestdist); + if (polyhit) n1 = Vector3.Normalize(Vector3.Cross(p2 - p1, p3 - p1)); + break; + case BoundPolygonType.Sphere: + var psph = polygon as BoundPolygonSphere; + tsph.Center = bgeom.GetVertex(psph.sphereIndex) + cg; + tsph.Radius = psph.sphereRadius; + polyhit = ray.Intersects(ref tsph, out polyhittestdist); + if (polyhit) n1 = Vector3.Normalize((ray.Position + ray.Direction * polyhittestdist) - tsph.Center); + break; + case BoundPolygonType.Capsule: + //TODO + break; + case BoundPolygonType.Box: + var pbox = polygon as BoundPolygonBox; + p1 = bgeom.GetVertex(pbox.boxIndex1);// + cg; //corner + p2 = bgeom.GetVertex(pbox.boxIndex2);// + cg; + p3 = bgeom.GetVertex(pbox.boxIndex3);// + cg; + p4 = bgeom.GetVertex(pbox.boxIndex4);// + cg; + a1 = ((p3 + p4) - (p1 + p2)) * 0.5f; + a2 = p3 - (p1 + a1); + a3 = p4 - (p1 + a1); + Vector3 bs = new Vector3(a1.Length(), a2.Length(), a3.Length()); + Vector3 m1 = a1 / bs.X; + Vector3 m2 = a2 / bs.Y; + Vector3 m3 = a3 / bs.Z; + if ((bs.X < bs.Y) && (bs.X < bs.Z)) m1 = Vector3.Cross(m2, m3); + else if (bs.Y < bs.Z) m2 = Vector3.Cross(m1, m3); + else m3 = Vector3.Cross(m1, m2); + Vector3 tp = rp - (p1 + cg); + rayt.Position = new Vector3(Vector3.Dot(tp, m1), Vector3.Dot(tp, m2), Vector3.Dot(tp, m3)); + rayt.Direction = new Vector3(Vector3.Dot(rd, m1), Vector3.Dot(rd, m2), Vector3.Dot(rd, m3)); + box.Minimum = Vector3.Zero; + box.Maximum = bs; + polyhit = rayt.Intersects(ref box, out polyhittestdist); + if (polyhit) + { + Vector3 hpt = rayt.Position + rayt.Direction * polyhittestdist; + const float eps = 0.002f; + if (Math.Abs(hpt.X) < eps) n1 = -m1; + else if (Math.Abs(hpt.X - bs.X) < eps) n1 = m1; + else if (Math.Abs(hpt.Y) < eps) n1 = -m2; + else if (Math.Abs(hpt.Y - bs.Y) < eps) n1 = m2; + else if (Math.Abs(hpt.Z) < eps) n1 = -m3; + else if (Math.Abs(hpt.Z - bs.Z) < eps) n1 = m3; + else + { n1 = Vector3.UnitZ; } //ray starts inside the box... + } + break; + case BoundPolygonType.Cylinder: + //TODO + break; + } + if (polyhit && (polyhittestdist < itemhitdist) && (polyhittestdist < maxdist)) + { + itemhitdist = polyhittestdist; + ybnhit = true; + hit = true; + hitnorm = n1; + hitpoly = polygon; + + byte matind = ((bgeom.PolygonMaterialIndices != null) && (p < bgeom.PolygonMaterialIndices.Length)) ? bgeom.PolygonMaterialIndices[p] : (byte)0; + BoundMaterial_s mat = ((bgeom.Materials != null) && (matind < bgeom.Materials.Length)) ? bgeom.Materials[matind] : new BoundMaterial_s(); + hitmat = mat; + } + polytestcount++; + } + } + nodeind++; + } + nodetestcount++; } - break; - case 3: //BoundBox - found in drawables - TODO - BoundBox boundbox = b as BoundBox; - if (boundbox == null) - { continue; } - break; - case 0: //BoundSphere - found in drawables - TODO - BoundSphere boundsphere = b as BoundSphere; - if (boundsphere == null) - { continue; } - break; - default: - break; + } } - - - if (ybnhit) - { - boxhitdist = boxhitdisttest; - //hit = true; - } - - } + break; + case 3: //BoundBox - found in drawables - TODO + BoundBox boundbox = b as BoundBox; + if (boundbox == null) + { continue; } + break; + case 0: //BoundSphere - found in drawables - TODO + BoundSphere boundsphere = b as BoundSphere; + if (boundsphere == null) + { continue; } + break; + default: + break; } + + + if (ybnhit) + { + boxhitdist = boxhitdisttest; + //hit = true; + } + } - - - //walk the line to the next cell... - - cellcount++; - if (cellcount >= maxcells) break; - if ((dirx == 0) && (diry == 0)) break; //vertical ray - - Vector3 cellwp = Grid.GetWorldPos(cellpos); - float compx = (dirx < 0) ? cellwp.X : cellwp.X + SpaceGrid.CellSize; - float compy = (diry < 0) ? cellwp.Y : cellwp.Y + SpaceGrid.CellSize; - float deltx = Math.Abs(compx - rp.X); - float delty = Math.Abs(compy - rp.Y); - float nextd = 0; - if (deltx < delty) - { - cellpos.X += (int)dirx; - nextd = (rd.X != 0) ? (deltx / rd.X) : 0; - } - else - { - cellpos.Y += (int)diry; - nextd = (rd.Y != 0) ? (delty / rd.Y) : 0; - } - - cell = Grid.GetCell(cellpos); - - if (nextd > itemhitdist) - { break; } //next cell is further away than current hit.. no need to continue - if (nextd > maxdist) - { break; } //next cell is out of the testing range.. stop now } if (hit) @@ -1508,9 +1346,6 @@ namespace CodeWalker.World bool testcomplete = true; Vector3 sphmin = sph.Center - sph.Radius; Vector3 sphmax = sph.Center + sph.Radius; - var cellmin = Grid.GetCellPos(sphmin); - var cellmax = Grid.GetCellPos(sphmax); - var cellcount = 0; var box = new BoundingBox(); var tsph = new BoundingSphere(); var spht = new BoundingSphere(); @@ -1525,207 +1360,202 @@ namespace CodeWalker.World BoundPolygon hitpoly = null; Vector3 hitnorm = Vector3.Zero; Vector3 hitpos = Vector3.Zero; - for (int x = cellmin.X; x <= cellmax.X; x++) + + + if (BoundsStore == null) return res; + var boundslist = BoundsStore.GetItems(ref sphmin, ref sphmax); + + + foreach (var bound in boundslist) { - for (int y = cellmin.Y; y <= cellmax.Y; y++) + box.Minimum = bound.Min; + box.Maximum = bound.Max; + if (sph.Intersects(ref box)) { - var cell = Grid.GetCell(new Vector2I(x, y)); - if (cell == null) continue; - if (cell.BoundsList == null) continue; + YbnFile ybn = GameFileCache.GetYbn(bound.Name); + if (ybn == null) + { continue; } //ybn not found? + if (!ybn.Loaded) + { testcomplete = false; continue; } //ybn not loaded yet... - foreach (var bound in cell.BoundsList) + //bool ybnhit = false; + var b = ybn.Bounds; + box.Minimum = b.BoundingBoxMin; + box.Maximum = b.BoundingBoxMax; + if (!sph.Intersects(ref box)) + { continue; } //ray doesn't hit this ybn + + switch (b.Type) { - box.Minimum = bound.Min; - box.Maximum = bound.Max; - if (sph.Intersects(ref box)) - { - YbnFile ybn = GameFileCache.GetYbn(bound.Name); - if (ybn == null) - { continue; } //ybn not found? - if (!ybn.Loaded) - { testcomplete = false; continue; } //ybn not loaded yet... - - //bool ybnhit = false; - var b = ybn.Bounds; - box.Minimum = b.BoundingBoxMin; - box.Maximum = b.BoundingBoxMax; - if (!sph.Intersects(ref box)) - { continue; } //ray doesn't hit this ybn - - switch (b.Type) + case 10: //BoundComposite + BoundComposite boundcomp = b as BoundComposite; + if (boundcomp == null) + { continue; } + var compchilds = boundcomp.Children?.data_items; + if (compchilds == null) + { continue; } + for (int i = 0; i < compchilds.Length; i++) { - case 10: //BoundComposite - BoundComposite boundcomp = b as BoundComposite; - if (boundcomp == null) + BoundBVH bgeom = compchilds[i] as BoundBVH; + if (bgeom == null) + { continue; } + if (bgeom.Polygons == null) + { continue; } + if ((bgeom.BVH?.Nodes?.data_items == null) || (bgeom.BVH?.Trees?.data_items == null)) + { continue; } + + box.Minimum = bgeom.BoundingBoxMin; + box.Maximum = bgeom.BoundingBoxMax; + if (!sph.Intersects(ref box)) + { continue; } + + var q = bgeom.BVH.Quantum.XYZ(); + var c = bgeom.BVH.BoundingBoxCenter.XYZ(); + var cg = bgeom.CenterGeom; + for (int t = 0; t < bgeom.BVH.Trees.data_items.Length; t++) + { + var tree = bgeom.BVH.Trees.data_items[t]; + box.Minimum = new Vector3(tree.MinX, tree.MinY, tree.MinZ) * q + c; + box.Maximum = new Vector3(tree.MaxX, tree.MaxY, tree.MaxZ) * q + c; + if (!sph.Intersects(ref box)) { continue; } - var compchilds = boundcomp.Children?.data_items; - if (compchilds == null) - { continue; } - for (int i = 0; i < compchilds.Length; i++) + + int nodeind = tree.NodeIndex1; + int lastind = tree.NodeIndex2; + while (nodeind < lastind) { - BoundBVH bgeom = compchilds[i] as BoundBVH; - if (bgeom == null) - { continue; } - if (bgeom.Polygons == null) - { continue; } - if ((bgeom.BVH?.Nodes?.data_items == null) || (bgeom.BVH?.Trees?.data_items == null)) - { continue; } - - box.Minimum = bgeom.BoundingBoxMin; - box.Maximum = bgeom.BoundingBoxMax; - if (!sph.Intersects(ref box)) - { continue; } - - var q = bgeom.BVH.Quantum.XYZ(); - var c = bgeom.BVH.BoundingBoxCenter.XYZ(); - var cg = bgeom.CenterGeom; - for (int t = 0; t < bgeom.BVH.Trees.data_items.Length; t++) + var node = bgeom.BVH.Nodes.data_items[nodeind]; + box.Minimum = new Vector3(node.MinX, node.MinY, node.MinZ) * q + c; + box.Maximum = new Vector3(node.MaxX, node.MaxY, node.MaxZ) * q + c; + bool nodehit = sph.Intersects(ref box); + bool nodeskip = !nodehit; + if (node.PolyCount <= 0) //intermediate node with child nodes { - var tree = bgeom.BVH.Trees.data_items[t]; - box.Minimum = new Vector3(tree.MinX, tree.MinY, tree.MinZ) * q + c; - box.Maximum = new Vector3(tree.MaxX, tree.MaxY, tree.MaxZ) * q + c; - if (!sph.Intersects(ref box)) - { continue; } - - int nodeind = tree.NodeIndex1; - int lastind = tree.NodeIndex2; - while (nodeind < lastind) + if (nodeskip) { - var node = bgeom.BVH.Nodes.data_items[nodeind]; - box.Minimum = new Vector3(node.MinX, node.MinY, node.MinZ) * q + c; - box.Maximum = new Vector3(node.MaxX, node.MaxY, node.MaxZ) * q + c; - bool nodehit = sph.Intersects(ref box); - bool nodeskip = !nodehit; - if (node.PolyCount <= 0) //intermediate node with child nodes - { - if (nodeskip) - { - nodeind += node.PolyId; //(child node count) - } - else - { - nodeind++; - } - } - else //leaf node, with polygons - { - if (!nodeskip) - { - var lastp = node.PolyId + node.PolyCount; - lastp = Math.Min(lastp, (int)bgeom.PolygonsCount); - for (int p = node.PolyId; p < lastp; p++) - { - var polygon = bgeom.Polygons[p]; - bool polyhit = false; - switch (polygon.Type) - { - case BoundPolygonType.Triangle: - var ptri = polygon as BoundPolygonTriangle; - p1 = bgeom.GetVertex(ptri.vertIndex1) + cg; - p2 = bgeom.GetVertex(ptri.vertIndex2) + cg; - p3 = bgeom.GetVertex(ptri.vertIndex3) + cg; - polyhit = sph.Intersects(ref p1, ref p2, ref p3); - if (polyhit) n1 = Vector3.Normalize(Vector3.Cross(p2 - p1, p3 - p1)); - break; - case BoundPolygonType.Sphere: - var psph = polygon as BoundPolygonSphere; - tsph.Center = bgeom.GetVertex(psph.sphereIndex) + cg; - tsph.Radius = psph.sphereRadius; - polyhit = sph.Intersects(ref tsph); - if (polyhit) n1 = Vector3.Normalize(sph.Center - tsph.Center); - break; - case BoundPolygonType.Capsule: - //TODO - break; - case BoundPolygonType.Box: - var pbox = polygon as BoundPolygonBox; - p1 = bgeom.GetVertex(pbox.boxIndex1);// + cg; //corner - p2 = bgeom.GetVertex(pbox.boxIndex2);// + cg; - p3 = bgeom.GetVertex(pbox.boxIndex3);// + cg; - p4 = bgeom.GetVertex(pbox.boxIndex4);// + cg; - a1 = ((p3 + p4) - (p1 + p2)) * 0.5f; - a2 = p3 - (p1 + a1); - a3 = p4 - (p1 + a1); - Vector3 bs = new Vector3(a1.Length(), a2.Length(), a3.Length()); - Vector3 m1 = a1 / bs.X; - Vector3 m2 = a2 / bs.Y; - Vector3 m3 = a3 / bs.Z; - if ((bs.X < bs.Y) && (bs.X < bs.Z)) m1 = Vector3.Cross(m2, m3); - else if (bs.Y < bs.Z) m2 = Vector3.Cross(m1, m3); - else m3 = Vector3.Cross(m1, m2); - Vector3 tp = sp - (p1 + cg); - spht.Center = new Vector3(Vector3.Dot(tp, m1), Vector3.Dot(tp, m2), Vector3.Dot(tp, m3)); - spht.Radius = sph.Radius; - box.Minimum = Vector3.Zero; - box.Maximum = bs; - polyhit = spht.Intersects(ref box); - if (polyhit) - { - Vector3 smin = spht.Center - spht.Radius; - Vector3 smax = spht.Center + spht.Radius; - float eps = spht.Radius * 0.8f; - n1 = Vector3.Zero; - if (Math.Abs(smax.X) < eps) n1 -= m1; - else if (Math.Abs(smin.X - bs.X) < eps) n1 += m1; - if (Math.Abs(smax.Y) < eps) n1 -= m2; - else if (Math.Abs(smin.Y - bs.Y) < eps) n1 += m2; - if (Math.Abs(smax.Z) < eps) n1 -= m3; - else if (Math.Abs(smin.Z - bs.Z) < eps) n1 += m3; - float n1l = n1.Length(); - if (n1l > 0.0f) n1 = n1 / n1l; - else n1 = Vector3.UnitZ; - } - break; - case BoundPolygonType.Cylinder: - //TODO - break; - } - if (polyhit) // && (polyhittestdist < itemhitdist) && (polyhittestdist < maxdist)) - { - hitpoly = polygon; - //itemhitdist = polyhittestdist; - //ybnhit = true; - hit = true; - hitnorm = n1; - } - polytestcount++; - } - } - nodeind++; - } - nodetestcount++; + nodeind += node.PolyId; //(child node count) + } + else + { + nodeind++; } } + else //leaf node, with polygons + { + if (!nodeskip) + { + var lastp = node.PolyId + node.PolyCount; + lastp = Math.Min(lastp, (int)bgeom.PolygonsCount); + for (int p = node.PolyId; p < lastp; p++) + { + var polygon = bgeom.Polygons[p]; + bool polyhit = false; + switch (polygon.Type) + { + case BoundPolygonType.Triangle: + var ptri = polygon as BoundPolygonTriangle; + p1 = bgeom.GetVertex(ptri.vertIndex1) + cg; + p2 = bgeom.GetVertex(ptri.vertIndex2) + cg; + p3 = bgeom.GetVertex(ptri.vertIndex3) + cg; + polyhit = sph.Intersects(ref p1, ref p2, ref p3); + if (polyhit) n1 = Vector3.Normalize(Vector3.Cross(p2 - p1, p3 - p1)); + break; + case BoundPolygonType.Sphere: + var psph = polygon as BoundPolygonSphere; + tsph.Center = bgeom.GetVertex(psph.sphereIndex) + cg; + tsph.Radius = psph.sphereRadius; + polyhit = sph.Intersects(ref tsph); + if (polyhit) n1 = Vector3.Normalize(sph.Center - tsph.Center); + break; + case BoundPolygonType.Capsule: + //TODO + break; + case BoundPolygonType.Box: + var pbox = polygon as BoundPolygonBox; + p1 = bgeom.GetVertex(pbox.boxIndex1);// + cg; //corner + p2 = bgeom.GetVertex(pbox.boxIndex2);// + cg; + p3 = bgeom.GetVertex(pbox.boxIndex3);// + cg; + p4 = bgeom.GetVertex(pbox.boxIndex4);// + cg; + a1 = ((p3 + p4) - (p1 + p2)) * 0.5f; + a2 = p3 - (p1 + a1); + a3 = p4 - (p1 + a1); + Vector3 bs = new Vector3(a1.Length(), a2.Length(), a3.Length()); + Vector3 m1 = a1 / bs.X; + Vector3 m2 = a2 / bs.Y; + Vector3 m3 = a3 / bs.Z; + if ((bs.X < bs.Y) && (bs.X < bs.Z)) m1 = Vector3.Cross(m2, m3); + else if (bs.Y < bs.Z) m2 = Vector3.Cross(m1, m3); + else m3 = Vector3.Cross(m1, m2); + Vector3 tp = sp - (p1 + cg); + spht.Center = new Vector3(Vector3.Dot(tp, m1), Vector3.Dot(tp, m2), Vector3.Dot(tp, m3)); + spht.Radius = sph.Radius; + box.Minimum = Vector3.Zero; + box.Maximum = bs; + polyhit = spht.Intersects(ref box); + if (polyhit) + { + Vector3 smin = spht.Center - spht.Radius; + Vector3 smax = spht.Center + spht.Radius; + float eps = spht.Radius * 0.8f; + n1 = Vector3.Zero; + if (Math.Abs(smax.X) < eps) n1 -= m1; + else if (Math.Abs(smin.X - bs.X) < eps) n1 += m1; + if (Math.Abs(smax.Y) < eps) n1 -= m2; + else if (Math.Abs(smin.Y - bs.Y) < eps) n1 += m2; + if (Math.Abs(smax.Z) < eps) n1 -= m3; + else if (Math.Abs(smin.Z - bs.Z) < eps) n1 += m3; + float n1l = n1.Length(); + if (n1l > 0.0f) n1 = n1 / n1l; + else n1 = Vector3.UnitZ; + } + break; + case BoundPolygonType.Cylinder: + //TODO + break; + } + if (polyhit) // && (polyhittestdist < itemhitdist) && (polyhittestdist < maxdist)) + { + hitpoly = polygon; + //itemhitdist = polyhittestdist; + //ybnhit = true; + hit = true; + hitnorm = n1; + } + polytestcount++; + } + } + nodeind++; + } + nodetestcount++; } - break; - case 3: //BoundBox - found in drawables - TODO - BoundBox boundbox = b as BoundBox; - if (boundbox == null) - { continue; } - break; - case 0: //BoundSphere - found in drawables - TODO - BoundSphere boundsphere = b as BoundSphere; - if (boundsphere == null) - { continue; } - break; - default: - break; + } } - - - //if (ybnhit) - //{ - // //boxhitdist = boxhitdisttest; - // //hit = true; - //} - - } + break; + case 3: //BoundBox - found in drawables - TODO + BoundBox boundbox = b as BoundBox; + if (boundbox == null) + { continue; } + break; + case 0: //BoundSphere - found in drawables - TODO + BoundSphere boundsphere = b as BoundSphere; + if (boundsphere == null) + { continue; } + break; + default: + break; } - cellcount++; + + //if (ybnhit) + //{ + // //boxhitdist = boxhitdisttest; + // //hit = true; + //} + } } + //if (hit) //{ // hitpos = ray.Position + ray.Direction * itemhitdist; @@ -1745,205 +1575,8 @@ namespace CodeWalker.World } - public class SpaceGrid - { - public const int CellCount = 500; //cells along a side, total cell count is this squared - public const int LastCell = CellCount - 1; //the last cell index in the array - public const float WorldSize = 10000.0f; //max world grid size +/- 10000 units - public const float CellSize = 2.0f * WorldSize / (float)CellCount;//40.0f; //size of a cell - public const float CellSizeInv = 1.0f / CellSize; //inverse of the cell size. - public const float CellSizeHalf = CellSize * 0.5f; //half the cell size - - public int TotalBoundsCount = 0; //total number of bounds in this grid - public int TotalBoundsRefCount = 0; //total number of bounds placements in cells - private int MaxBoundsInCell = 0; //biggest number of bounds added to a single cell - private SpaceGridCell DensestBoundsCell = null; - - public int TotalNodeCount = 0; //total map nodes in grid - public int TotalNodeRefCount = 0; //total number of map node placements in cells - public int MaxNodesInCell = 0; //biggest number of nodes added to a single cell - private SpaceGridCell DensestNodeCell = null; - - public int TotalInteriorCount = 0; - public int TotalInteriorRefCount = 0; - public int MaxInteriorsInCell = 0; - private SpaceGridCell DensestInteriorCell = null; - - public SpaceGridCell[,] Cells { get; set; } = new SpaceGridCell[CellCount, CellCount]; - public Vector3 GetWorldPos(Vector2I p) - { - Vector3 ind = new Vector3(p.X, p.Y, 0.0f); - return (ind * CellSize) - new Vector3(WorldSize, WorldSize, 0); - } - public Vector2I GetCellPos(Vector3 p) - { - Vector3 ind = (p + WorldSize) * CellSizeInv; - int x = (int)ind.X; - int y = (int)ind.Y; - x = (x < 0) ? 0 : (x > LastCell) ? LastCell : x; - y = (y < 0) ? 0 : (y > LastCell) ? LastCell : y; - return new Vector2I(x, y); - } - public SpaceGridCell GetCell(Vector2I g) - { - if ((g.X < 0) || (g.Y < 0) || (g.X >= CellCount) || (g.Y >= CellCount)) - { - return null; - } - var cell = Cells[g.X, g.Y]; - if (cell == null) - { - cell = new SpaceGridCell(); - Cells[g.X, g.Y] = cell; - } - return cell; - } - public SpaceGridCell GetCell(Vector3 p) - { - return GetCell(GetCellPos(p)); - } - - public void AddBounds(BoundsStoreItem item) - { - Vector2I min = GetCellPos(item.Min); - Vector2I max = GetCellPos(item.Max); - - int cellcount = 0; - for (int x = min.X; x <= max.X; x++) - { - for (int y = min.Y; y <= max.Y; y++) - { - var cell = GetCell(new Vector2I(x, y)); - cell.AddBounds(item); - TotalBoundsRefCount++; - if (cell.BoundsList.Count > MaxBoundsInCell) - { - MaxBoundsInCell = cell.BoundsList.Count; - DensestBoundsCell = cell; - } - cellcount++; - } - } - if (cellcount == 0) - { } - - TotalBoundsCount++; - } - - public void AddNode(MapDataStoreNode node) - { - bool useouter = true;// false; - //switch (node.Unk01) - //{ - // case 2: - // case 4: - // case 20: - // case 66: - // case 514://lods - // useouter = true; - // break; - // case 128: - // case 256://lodlights - // useouter = true; - // break; - // case 18: - // case 82://HD nodes - // useouter = true; - // break; - //} - - //Vector2I min = GetCellPos(node.OuterBBMin); - //Vector2I max = GetCellPos(node.OuterBBMax); - Vector2I min = GetCellPos(useouter ? node.streamingExtentsMin : node.entitiesExtentsMin); - Vector2I max = GetCellPos(useouter ? node.streamingExtentsMax : node.entitiesExtentsMax); - - for (int x = min.X; x <= max.X; x++) - { - for (int y = min.Y; y <= max.Y; y++) - { - var cell = GetCell(new Vector2I(x, y)); - cell.AddNode(node); - TotalNodeRefCount++; - if (cell.NodesList.Count > MaxNodesInCell) - { - MaxNodesInCell = cell.NodesList.Count; - DensestNodeCell = cell; - } - } - } - - TotalNodeCount++; - } - - public void AddInterior(CInteriorProxy intprx) - { - Vector2I min = GetCellPos(intprx.BBMin); - Vector2I max = GetCellPos(intprx.BBMax); - - int cellcount = 0; - for (int x = min.X; x <= max.X; x++) - { - for (int y = min.Y; y <= max.Y; y++) - { - var cell = GetCell(new Vector2I(x, y)); - cell.AddInterior(intprx); - TotalInteriorRefCount++; - if (cell.InteriorList.Count > MaxInteriorsInCell) - { - MaxInteriorsInCell = cell.InteriorList.Count; - DensestInteriorCell = cell; - } - cellcount++; - } - } - if (cellcount == 0) - { } - - TotalInteriorCount++; - } - } - public class SpaceGridCell - { - public List NodesList; - public List BoundsList; - public List InteriorList; - - public void AddNode(MapDataStoreNode node) - { - if (NodesList == null) - { - NodesList = new List(); - } - NodesList.Add(node); - } - - public void AddBounds(BoundsStoreItem item) - { - if (BoundsList == null) - { - BoundsList = new List(5); - } - BoundsList.Add(item); - } - public void RemoveBounds(BoundsStoreItem item) - { - if (BoundsList != null) - { - BoundsList.Remove(item); - } - } - - public void AddInterior(CInteriorProxy intprx) - { - if (InteriorList == null) - { - InteriorList = new List(5); - } - InteriorList.Add(intprx); - } - } public struct SpaceBoundsKey { public MetaHash Name { get; set; } @@ -1956,6 +1589,317 @@ namespace CodeWalker.World } + public class SpaceMapDataStore + { + public SpaceMapDataStoreNode RootNode; + public int SplitThreshold = 10; + + public List VisibleItems = new List(); + + public void Init(List rootnodes) + { + RootNode = new SpaceMapDataStoreNode(); + RootNode.Owner = this; + foreach (var item in rootnodes) + { + RootNode.Add(item); + } + RootNode.TrySplit(SplitThreshold); + } + + public List GetItems(ref Vector3 p) + { + VisibleItems.Clear(); + + if (RootNode != null) + { + RootNode.GetItems(ref p, VisibleItems); + } + + return VisibleItems; + } + } + public class SpaceMapDataStoreNode + { + public SpaceMapDataStore Owner = null; + public SpaceMapDataStoreNode[] Children = null; + public List Items = null; + public Vector3 BBMin = new Vector3(float.MaxValue); + public Vector3 BBMax = new Vector3(float.MinValue); + public int Depth = 0; + + public void Add(MapDataStoreNode item) + { + if (Items == null) + { + Items = new List(); + } + BBMin = Vector3.Min(BBMin, item.streamingExtentsMin); + BBMax = Vector3.Max(BBMax, item.streamingExtentsMax); + Items.Add(item); + } + + public void TrySplit(int threshold) + { + if ((Items == null) || (Items.Count <= threshold)) + { return; } + + Children = new SpaceMapDataStoreNode[4]; + + var newItems = new List(); + + var ncen = (BBMax + BBMin) * 0.5f; + var next = (BBMax - BBMin) * 0.5f; + var nsiz = Math.Max(next.X, next.Y); + var nsizh = nsiz * 0.5f; + + foreach (var item in Items) + { + var imin = item.streamingExtentsMin; + var imax = item.streamingExtentsMax; + var icen = (imax + imin) * 0.5f; + var iext = (imax - imin) * 0.5f; + var isiz = Math.Max(iext.X, iext.Y); + + if (isiz >= nsizh) + { + newItems.Add(item); + } + else + { + var cind = ((icen.X > ncen.X) ? 1 : 0) + ((icen.Y > ncen.Y) ? 2 : 0); + var c = Children[cind]; + if (c == null) + { + c = new SpaceMapDataStoreNode(); + c.Owner = Owner; + c.Depth = Depth + 1; + Children[cind] = c; + } + c.Add(item); + } + } + + for (int i = 0; i < 4; i++) + { + var c = Children[i]; + if (c != null) + { + c.TrySplit(threshold); + } + } + + Items = newItems; + } + + public void GetItems(ref Vector3 p, List items) + { + if ((p.X >= BBMin.X) && (p.X <= BBMax.X) && (p.Y >= BBMin.Y) && (p.Y <= BBMax.Y)) + { + if (Items != null) + { + for (int i = 0; i < Items.Count; i++) + { + var item = Items[i]; + var imin = item.streamingExtentsMin; + var imax = item.streamingExtentsMax; + if ((p.X >= imin.X) && (p.X <= imax.X) && (p.Y >= imin.Y) && (p.Y <= imax.Y)) + { + items.Add(item); + item.GetVisibleChildren(ref p, items); + } + } + } + if (Children != null) + { + for (int i = 0; i < 4; i++) + { + var c = Children[i]; + if (c != null) + { + c.GetItems(ref p, items); + } + } + } + } + } + } + + + public class SpaceBoundsStore + { + public SpaceBoundsStoreNode RootNode; + public int SplitThreshold = 10; + + public List VisibleItems = new List(); + + public void Init(List items) + { + RootNode = new SpaceBoundsStoreNode(); + RootNode.Owner = this; + foreach (var item in items) + { + RootNode.Add(item); + } + RootNode.TrySplit(SplitThreshold); + } + + public List GetItems(ref Vector3 min, ref Vector3 max) + { + VisibleItems.Clear(); + + if (RootNode != null) + { + RootNode.GetItems(ref min, ref max, VisibleItems); + } + + return VisibleItems; + } + public List GetItems(ref Ray ray) + { + VisibleItems.Clear(); + + if (RootNode != null) + { + RootNode.GetItems(ref ray, VisibleItems); + } + + return VisibleItems; + } + } + public class SpaceBoundsStoreNode + { + public SpaceBoundsStore Owner = null; + public SpaceBoundsStoreNode[] Children = null; + public List Items = null; + public Vector3 BBMin = new Vector3(float.MaxValue); + public Vector3 BBMax = new Vector3(float.MinValue); + public int Depth = 0; + + public void Add(BoundsStoreItem item) + { + if (Items == null) + { + Items = new List(); + } + BBMin = Vector3.Min(BBMin, item.Min); + BBMax = Vector3.Max(BBMax, item.Max); + Items.Add(item); + } + + public void TrySplit(int threshold) + { + if ((Items == null) || (Items.Count <= threshold)) + { return; } + + Children = new SpaceBoundsStoreNode[4]; + + var newItems = new List(); + + var ncen = (BBMax + BBMin) * 0.5f; + var next = (BBMax - BBMin) * 0.5f; + var nsiz = Math.Max(next.X, next.Y); + var nsizh = nsiz * 0.5f; + + foreach (var item in Items) + { + var imin = item.Min; + var imax = item.Max; + var icen = (imax + imin) * 0.5f; + var iext = (imax - imin) * 0.5f; + var isiz = Math.Max(iext.X, iext.Y); + + if (isiz >= nsizh) + { + newItems.Add(item); + } + else + { + var cind = ((icen.X > ncen.X) ? 1 : 0) + ((icen.Y > ncen.Y) ? 2 : 0); + var c = Children[cind]; + if (c == null) + { + c = new SpaceBoundsStoreNode(); + c.Owner = Owner; + c.Depth = Depth + 1; + Children[cind] = c; + } + c.Add(item); + } + } + + for (int i = 0; i < 4; i++) + { + var c = Children[i]; + if (c != null) + { + c.TrySplit(threshold); + } + } + + Items = newItems; + } + + public void GetItems(ref Vector3 min, ref Vector3 max, List items) + { + if ((max.X >= BBMin.X) && (min.X <= BBMax.X) && (max.Y >= BBMin.Y) && (min.Y <= BBMax.Y)) + { + if (Items != null) + { + for (int i = 0; i < Items.Count; i++) + { + var item = Items[i]; + if ((max.X >= item.Min.X) && (min.X <= item.Max.X) && (max.Y >= item.Min.Y) && (min.Y <= item.Max.Y)) + { + items.Add(item); + } + } + } + if (Children != null) + { + for (int i = 0; i < 4; i++) + { + var c = Children[i]; + if (c != null) + { + c.GetItems(ref min, ref max, items); + } + } + } + } + } + public void GetItems(ref Ray ray, List items) + { + var box = new BoundingBox(BBMin, BBMax); + if (ray.Intersects(ref box)) + { + if (Items != null) + { + for (int i = 0; i < Items.Count; i++) + { + var item = Items[i]; + box = new BoundingBox(item.Min, item.Max); + if (ray.Intersects(box)) + { + items.Add(item); + } + } + } + if (Children != null) + { + for (int i = 0; i < 4; i++) + { + var c = Children[i]; + if (c != null) + { + c.GetItems(ref ray, items); + } + } + } + } + } + } + public class SpaceNodeGrid { diff --git a/CodeWalker.Core/World/Vehicle.cs b/CodeWalker.Core/World/Vehicle.cs index 4428f73..06a1046 100644 --- a/CodeWalker.Core/World/Vehicle.cs +++ b/CodeWalker.Core/World/Vehicle.cs @@ -51,7 +51,7 @@ namespace CodeWalker.World Yft = gfc.GetYft(ModelHash); while ((Yft != null) && (!Yft.Loaded)) { - Thread.Sleep(20);//kinda hacky + Thread.Sleep(1);//kinda hacky Yft = gfc.GetYft(ModelHash); } @@ -65,7 +65,7 @@ namespace CodeWalker.World ConvRoofDict = gfc.GetYcd(ycdhash); while ((ConvRoofDict != null) && (!ConvRoofDict.Loaded)) { - Thread.Sleep(20);//kinda hacky + Thread.Sleep(1);//kinda hacky ConvRoofDict = gfc.GetYcd(ycdhash); } ClipMapEntry cme = null; diff --git a/CodeWalker.Core/World/Weapon.cs b/CodeWalker.Core/World/Weapon.cs index 1bcfbe8..a1737a9 100644 --- a/CodeWalker.Core/World/Weapon.cs +++ b/CodeWalker.Core/World/Weapon.cs @@ -46,7 +46,7 @@ namespace CodeWalker.World while ((Ydr != null) && (!Ydr.Loaded)) { - Thread.Sleep(20);//kinda hacky + Thread.Sleep(1);//kinda hacky Ydr = gfc.GetYdr(useHash); } diff --git a/Forms/ModelForm.cs b/Forms/ModelForm.cs index 375038c..d8b7a24 100644 --- a/Forms/ModelForm.cs +++ b/Forms/ModelForm.cs @@ -800,7 +800,7 @@ namespace CodeWalker.Forms var ycd = gameFileCache.GetYcd(ycdhash); while ((ycd != null) && (!ycd.Loaded)) { - Thread.Sleep(20);//kinda hacky + Thread.Sleep(1);//kinda hacky ycd = gameFileCache.GetYcd(ycdhash); } diff --git a/PedsForm.cs b/PedsForm.cs index ce915ad..937051a 100644 --- a/PedsForm.cs +++ b/PedsForm.cs @@ -826,7 +826,7 @@ namespace CodeWalker.Peds var ycd = GameFileCache.GetYcd(ycdhash); while ((ycd != null) && (!ycd.Loaded)) { - Thread.Sleep(20);//kinda hacky + Thread.Sleep(1);//kinda hacky ycd = GameFileCache.GetYcd(ycdhash); } diff --git a/Project/Panels/GenerateNavMeshPanel.cs b/Project/Panels/GenerateNavMeshPanel.cs index 0100874..084cd48 100644 --- a/Project/Panels/GenerateNavMeshPanel.cs +++ b/Project/Panels/GenerateNavMeshPanel.cs @@ -82,8 +82,6 @@ namespace CodeWalker.Project.Panels float density = 0.5f; //distance between vertices for the initial grid //float clipdz = 0.5f; //any polygons with greater steepness should be removed - Vector2I imin = space.Grid.GetCellPos(new Vector3(min, 0)); - Vector2I imax = space.Grid.GetCellPos(new Vector3(max, 0)); //Vector2 vertexCounts = (max - min) / density; //int vertexCountX = (int)vertexCounts.X; @@ -113,126 +111,129 @@ namespace CodeWalker.Project.Panels var polys = new List(); - for (int x = imin.X; x <= imax.X; x++) //generate verts for each world cell + var vertexCountXY = (max - min) / density; + int vertexCountX = (int)vertexCountXY.X+1; + int vertexCountY = (int)vertexCountXY.Y+1; + //int vertexCountTot = vertexCountX * vertexCountY; + vgrid.BeginGrid(vertexCountX, vertexCountY); + + Ray ray = new Ray(Vector3.Zero, new Vector3(0, 0, -1));//for casting with + + UpdateStatus("Loading YBNs..."); + + var bmin = new Vector3(min, 0); + var bmax = new Vector3(max, 0); + var boundslist = space.BoundsStore.GetItems(ref bmin, ref bmax); + + //pre-warm the bounds cache for this area, and find the min/max Z + foreach (var boundsitem in boundslist) { - for (int y = imin.Y; y <= imax.Y; y++) + YbnFile ybn = gameFileCache.GetYbn(boundsitem.Name); + if (ybn == null) + { continue; } //ybn not found? + if (!ybn.Loaded) //ybn not loaded yet... { - Vector2I gi = new Vector2I(x, y); - var cell = space.Grid.GetCell(gi); - var cellstr = gi.ToString(); - var cellmin = space.Grid.GetWorldPos(gi); - var cellmax = cellmin + SpaceGrid.CellSize; - var vertexCountXY = (cellmax - cellmin) / density; - int vertexCountX = (int)vertexCountXY.X+1; - int vertexCountY = (int)vertexCountXY.Y+1; - //int vertexCountTot = vertexCountX * vertexCountY; - vgrid.BeginGrid(vertexCountX, vertexCountY); - cellmin.Z = 0.0f;//min probably not needed here - cellmax.Z = 0.0f; - Ray ray = new Ray(Vector3.Zero, new Vector3(0, 0, -1));//for casting with - - UpdateStatus("Loading cell " + cellstr + " ..."); - - //pre-warm the bounds cache for this cell, and find the min/max Z - if (cell.BoundsList != null) + UpdateStatus("Loading ybn: " + boundsitem.Name.ToString() + " ..."); + int waitCount = 0; + while (!ybn.Loaded) { - foreach (var boundsitem in cell.BoundsList) + waitCount++; + if (waitCount > 10000) { - YbnFile ybn = gameFileCache.GetYbn(boundsitem.Name); - if (ybn == null) - { continue; } //ybn not found? - if (!ybn.Loaded) //ybn not loaded yet... - { - UpdateStatus("Loading ybn: " + boundsitem.Name.ToString() + " ..."); - int waitCount = 0; - while (!ybn.Loaded) - { - waitCount++; - if (waitCount > 10000) - { - UpdateStatus("Timeout waiting for ybn " + boundsitem.Name.ToString() + " to load!"); - Thread.Sleep(1000); //just to let the message display for a second... - break; - } - Thread.Sleep(20);//~50fps should be fine - ybn = gameFileCache.GetYbn(boundsitem.Name); //try queue it again.. - } - } - if (ybn.Loaded && (ybn.Bounds != null)) - { - cellmin.Z = Math.Min(cellmin.Z, ybn.Bounds.BoundingBoxMin.Z); - cellmax.Z = Math.Max(cellmax.Z, ybn.Bounds.BoundingBoxMax.Z); - } + UpdateStatus("Timeout waiting for ybn " + boundsitem.Name.ToString() + " to load!"); + Thread.Sleep(1000); //just to let the message display for a second... + break; } + Thread.Sleep(20);//~50fps should be fine + ybn = gameFileCache.GetYbn(boundsitem.Name); //try queue it again.. } - - - - //ray-cast each XY vertex position, and find the height and surface from ybn's - //continue casting down to find more surfaces... - - UpdateStatus("Processing cell " + cellstr + " ..."); - - for (int vx = 0; vx < vertexCountX; vx++) - { - for (int vy = 0; vy < vertexCountY; vy++) - { - vgrid.BeginCell(vx, vy); - var vcoffset = new Vector3(vx, vy, 0) * density; - ray.Position = cellmin + vcoffset; - ray.Position.Z = cellmax.Z + 1.0f;//start the ray at the top of the cell - var intres = space.RayIntersect(ray, float.MaxValue, layers); - hitTestCount++; - while (intres.Hit)// && (intres.HitDist > 0)) - { - if (intres.HitDist > 0) - { - hitCount++; - vert.Position = intres.Position; - vert.Normal = intres.Normal; - vert.Material = intres.Material.Type; - vert.PolyFlags = (ushort)intres.Material.Flags; - vert.PrevIDX = -1; - vert.PrevIDY = -1; - vert.NextIDX = -1; - vert.NextIDY = -1; - vert.CompPrevX = false; - vert.CompPrevY = false; - vert.CompNextX = false; - vert.CompNextY = false; - vert.PolyID = -1; - vgrid.AddVertex(ref vert); - - if (vgrid.CurVertexCount > 15) //too many hits? - { break; } - } - //continue down until no more hits..... step by 3m - ray.Position.Z = intres.Position.Z - 3.0f; - intres = space.RayIntersect(ray, float.MaxValue, layers); - } - vgrid.EndCell(vx, vy); - } - } - - vgrid.EndGrid(); //build vertex array - - - - - - vgrid.ConnectVertices(); - - - var genPolys = vgrid.GenPolys2(); - polys.AddRange(genPolys); - - newCount += genPolys.Count; - - + } + if (ybn.Loaded && (ybn.Bounds != null)) + { + bmin.Z = Math.Min(bmin.Z, ybn.Bounds.BoundingBoxMin.Z); + bmax.Z = Math.Max(bmax.Z, ybn.Bounds.BoundingBoxMax.Z); } } + + //ray-cast each XY vertex position, and find the height and surface from ybn's + //continue casting down to find more surfaces... + + UpdateStatus("Processing..."); + + for (int vx = 0; vx < vertexCountX; vx++) + { + for (int vy = 0; vy < vertexCountY; vy++) + { + vgrid.BeginCell(vx, vy); + var vcoffset = new Vector3(vx, vy, 0) * density; + ray.Position = bmin + vcoffset; + ray.Position.Z = bmax.Z + 1.0f;//start the ray at the top of the cell + var intres = space.RayIntersect(ray, float.MaxValue, layers); + hitTestCount++; + while (intres.Hit)// && (intres.HitDist > 0)) + { + if (intres.HitDist > 0) + { + hitCount++; + vert.Position = intres.Position; + vert.Normal = intres.Normal; + vert.Material = intres.Material.Type; + vert.PolyFlags = (ushort)intres.Material.Flags; + vert.PrevIDX = -1; + vert.PrevIDY = -1; + vert.NextIDX = -1; + vert.NextIDY = -1; + vert.CompPrevX = false; + vert.CompPrevY = false; + vert.CompNextX = false; + vert.CompNextY = false; + vert.PolyID = -1; + vgrid.AddVertex(ref vert); + + if (vgrid.CurVertexCount > 15) //too many hits? + { break; } + } + //continue down until no more hits..... step by 3m + ray.Position.Z = intres.Position.Z - 3.0f; + intres = space.RayIntersect(ray, float.MaxValue, layers); + } + vgrid.EndCell(vx, vy); + } + } + + vgrid.EndGrid(); //build vertex array + + + + + + vgrid.ConnectVertices(); + + + var genPolys = vgrid.GenPolys2(); + polys.AddRange(genPolys); + + newCount += genPolys.Count; + + + + + + + + + + + + + + + + + + //try merge generated polys into bigger ones, while keeping convex! UpdateStatus("Building edge dictionary..."); var edgeDict = new Dictionary(); diff --git a/World/CutsceneForm.cs b/World/CutsceneForm.cs index d6ac882..e7671a0 100644 --- a/World/CutsceneForm.cs +++ b/World/CutsceneForm.cs @@ -381,7 +381,7 @@ namespace CodeWalker.World var ycd = GameFileCache.GetYcd(ycdhash); while ((ycd != null) && (!ycd.Loaded)) { - Thread.Sleep(20);//bite me + Thread.Sleep(1);//bite me ycd = GameFileCache.GetYcd(ycdhash); } if (ycd != null) diff --git a/WorldForm.cs b/WorldForm.cs index 37442da..c45d577 100644 --- a/WorldForm.cs +++ b/WorldForm.cs @@ -2213,7 +2213,7 @@ namespace CodeWalker public SpaceRayIntersectResult GetSpaceMouseRay() { SpaceRayIntersectResult ret = new SpaceRayIntersectResult(); - if (space.Inited && space.Grid != null) + if (space.Inited && space.BoundsStore != null) { Ray mray = new Ray(); mray.Position = camera.MouseRay.Position + camera.Position;