From 5827a43dd8472c95d6d406185a4881ea56c6d4b3 Mon Sep 17 00:00:00 2001 From: ikpil Date: Wed, 12 Jun 2024 00:41:39 +0900 Subject: [PATCH] Changed `RcChunkyTriMesh` to separate the function and variable. --- CHANGELOG.md | 7 +- .../Geom/DemoInputGeomProvider.cs | 2 +- src/DotRecast.Recast/Geom/RcChunkyTriMesh.cs | 270 +--------------- src/DotRecast.Recast/Geom/RcChunkyTriMeshs.cs | 305 ++++++++++++++++++ src/DotRecast.Recast/Geom/RcTriMesh.cs | 5 +- 5 files changed, 316 insertions(+), 273 deletions(-) create mode 100644 src/DotRecast.Recast/Geom/RcChunkyTriMeshs.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 96910a5..b1972c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,9 +13,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fixed bug where the dynamic voxel save file browser doesn't appear in Recast.Demo ### Changed -- Changed to reuse samples and edges list in BuildPolyDetail() -- Changed 'heights', 'areas', 'cons', and 'regs' arrays to byte arrays for uniformity and efficiency in DtTileCacheLayer -- Changed 'reg', 'area' arrays to byte arrays for uniformity and efficiency in DtTileCacheContour +- Changed to reuse samples and edges list in `BuildPolyDetail()` +- Changed `heights`, `areas`, `cons`, and `regs` arrays to byte arrays for uniformity and efficiency in `DtTileCacheLayer` +- Changed `reg`, `area` arrays to byte arrays for uniformity and efficiency in `DtTileCacheContour` +- Changed `RcChunkyTriMesh` to separate the function and variable. ### Removed - Removed RcMeshDetails.VdistSq2(float[], float[]) diff --git a/src/DotRecast.Recast.Toolset/Geom/DemoInputGeomProvider.cs b/src/DotRecast.Recast.Toolset/Geom/DemoInputGeomProvider.cs index 9d6ba80..498f70b 100644 --- a/src/DotRecast.Recast.Toolset/Geom/DemoInputGeomProvider.cs +++ b/src/DotRecast.Recast.Toolset/Geom/DemoInputGeomProvider.cs @@ -150,7 +150,7 @@ namespace DotRecast.Recast.Toolset.Geom q.X = src.X + (dst.X - src.X) * btmax; q.Y = src.Z + (dst.Z - src.Z) * btmax; - List chunks = _mesh.chunkyTriMesh.GetChunksOverlappingSegment(p, q); + List chunks = RcChunkyTriMeshs.GetChunksOverlappingSegment(_mesh.chunkyTriMesh, p, q); if (0 == chunks.Count) { return false; diff --git a/src/DotRecast.Recast/Geom/RcChunkyTriMesh.cs b/src/DotRecast.Recast/Geom/RcChunkyTriMesh.cs index d62a8d7..4e7a2ad 100644 --- a/src/DotRecast.Recast/Geom/RcChunkyTriMesh.cs +++ b/src/DotRecast.Recast/Geom/RcChunkyTriMesh.cs @@ -18,278 +18,14 @@ freely, subject to the following restrictions: 3. This notice may not be removed or altered from any source distribution. */ -using System; using System.Collections.Generic; -using DotRecast.Core.Numerics; namespace DotRecast.Recast.Geom { public class RcChunkyTriMesh { - private List nodes; - private int ntris; - private int maxTrisPerChunk; - - private void CalcExtends(BoundsItem[] items, int imin, int imax, ref RcVec2f bmin, ref RcVec2f bmax) - { - bmin.X = items[imin].bmin.X; - bmin.Y = items[imin].bmin.Y; - - bmax.X = items[imin].bmax.X; - bmax.Y = items[imin].bmax.Y; - - for (int i = imin + 1; i < imax; ++i) - { - BoundsItem it = items[i]; - if (it.bmin.X < bmin.X) - { - bmin.X = it.bmin.X; - } - - if (it.bmin.Y < bmin.Y) - { - bmin.Y = it.bmin.Y; - } - - if (it.bmax.X > bmax.X) - { - bmax.X = it.bmax.X; - } - - if (it.bmax.Y > bmax.Y) - { - bmax.Y = it.bmax.Y; - } - } - } - - private int LongestAxis(float x, float y) - { - return y > x ? 1 : 0; - } - - private void Subdivide(BoundsItem[] items, int imin, int imax, int trisPerChunk, List nodes, int[] inTris) - { - int inum = imax - imin; - - RcChunkyTriMeshNode node = new RcChunkyTriMeshNode(); - nodes.Add(node); - - if (inum <= trisPerChunk) - { - // Leaf - CalcExtends(items, imin, imax, ref node.bmin, ref node.bmax); - - // Copy triangles. - node.i = nodes.Count; - node.tris = new int[inum * 3]; - - int dst = 0; - for (int i = imin; i < imax; ++i) - { - int src = items[i].i * 3; - node.tris[dst++] = inTris[src]; - node.tris[dst++] = inTris[src + 1]; - node.tris[dst++] = inTris[src + 2]; - } - } - else - { - // Split - CalcExtends(items, imin, imax, ref node.bmin, ref node.bmax); - - int axis = LongestAxis(node.bmax.X - node.bmin.X, node.bmax.Y - node.bmin.Y); - - if (axis == 0) - { - Array.Sort(items, imin, imax - imin, BoundsItemXComparer.Shared); - // Sort along x-axis - } - else if (axis == 1) - { - Array.Sort(items, imin, imax - imin, BoundsItemYComparer.Shared); - // Sort along y-axis - } - - int isplit = imin + inum / 2; - - // Left - Subdivide(items, imin, isplit, trisPerChunk, nodes, inTris); - // Right - Subdivide(items, isplit, imax, trisPerChunk, nodes, inTris); - - // Negative index means escape. - node.i = -nodes.Count; - } - } - - public RcChunkyTriMesh(float[] verts, int[] tris, int ntris, int trisPerChunk) - { - int nchunks = (ntris + trisPerChunk - 1) / trisPerChunk; - - nodes = new List(nchunks); - this.ntris = ntris; - - // Build tree - BoundsItem[] items = new BoundsItem[ntris]; - - for (int i = 0; i < ntris; i++) - { - int t = i * 3; - BoundsItem it = items[i] = new BoundsItem(); - it.i = i; - // Calc triangle XZ bounds. - it.bmin.X = it.bmax.X = verts[tris[t] * 3 + 0]; - it.bmin.Y = it.bmax.Y = verts[tris[t] * 3 + 2]; - for (int j = 1; j < 3; ++j) - { - int v = tris[t + j] * 3; - if (verts[v] < it.bmin.X) - { - it.bmin.X = verts[v]; - } - - if (verts[v + 2] < it.bmin.Y) - { - it.bmin.Y = verts[v + 2]; - } - - if (verts[v] > it.bmax.X) - { - it.bmax.X = verts[v]; - } - - if (verts[v + 2] > it.bmax.Y) - { - it.bmax.Y = verts[v + 2]; - } - } - } - - Subdivide(items, 0, ntris, trisPerChunk, nodes, tris); - - // Calc max tris per node. - maxTrisPerChunk = 0; - foreach (RcChunkyTriMeshNode node in nodes) - { - bool isLeaf = node.i >= 0; - if (!isLeaf) - { - continue; - } - - if (node.tris.Length / 3 > maxTrisPerChunk) - { - maxTrisPerChunk = node.tris.Length / 3; - } - } - } - - private bool CheckOverlapRect(float[] amin, float[] amax, RcVec2f bmin, RcVec2f bmax) - { - bool overlap = true; - overlap = (amin[0] > bmax.X || amax[0] < bmin.X) ? false : overlap; - overlap = (amin[1] > bmax.Y || amax[1] < bmin.Y) ? false : overlap; - return overlap; - } - - public List GetChunksOverlappingRect(float[] bmin, float[] bmax) - { - // Traverse tree - List ids = new List(); - int i = 0; - while (i < nodes.Count) - { - RcChunkyTriMeshNode node = nodes[i]; - bool overlap = CheckOverlapRect(bmin, bmax, node.bmin, node.bmax); - bool isLeafNode = node.i >= 0; - - if (isLeafNode && overlap) - { - ids.Add(node); - } - - if (overlap || isLeafNode) - { - i++; - } - else - { - i = -node.i; - } - } - - return ids; - } - - public List GetChunksOverlappingSegment(RcVec2f p, RcVec2f q) - { - // Traverse tree - List ids = new List(); - int i = 0; - while (i < nodes.Count) - { - RcChunkyTriMeshNode node = nodes[i]; - bool overlap = CheckOverlapSegment(p, q, node.bmin, node.bmax); - bool isLeafNode = node.i >= 0; - - if (isLeafNode && overlap) - { - ids.Add(node); - } - - if (overlap || isLeafNode) - { - i++; - } - else - { - i = -node.i; - } - } - - return ids; - } - - private bool CheckOverlapSegment(RcVec2f p, RcVec2f q, RcVec2f bmin, RcVec2f bmax) - { - const float EPSILON = 1e-6f; - - float tmin = 0; - float tmax = 1; - var d = new RcVec2f(); - d.X = q.X - p.X; - d.Y = q.Y - p.Y; - - for (int i = 0; i < 2; i++) - { - if (MathF.Abs(d.Get(i)) < EPSILON) - { - // Ray is parallel to slab. No hit if origin not within slab - if (p.Get(i) < bmin.Get(i) || p.Get(i) > bmax.Get(i)) - return false; - } - else - { - // Compute intersection t value of ray with near and far plane of slab - float ood = 1.0f / d.Get(i); - float t1 = (bmin.Get(i) - p.Get(i)) * ood; - float t2 = (bmax.Get(i) - p.Get(i)) * ood; - if (t1 > t2) - { - (t1, t2) = (t2, t1); - } - - if (t1 > tmin) - tmin = t1; - if (t2 < tmax) - tmax = t2; - if (tmin > tmax) - return false; - } - } - - return true; - } + public List nodes; + public int ntris; + public int maxTrisPerChunk; } } \ No newline at end of file diff --git a/src/DotRecast.Recast/Geom/RcChunkyTriMeshs.cs b/src/DotRecast.Recast/Geom/RcChunkyTriMeshs.cs new file mode 100644 index 0000000..e7f1f8c --- /dev/null +++ b/src/DotRecast.Recast/Geom/RcChunkyTriMeshs.cs @@ -0,0 +1,305 @@ +/* +Copyright (c) 2009-2010 Mikko Mononen memon@inside.org +recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org +DotRecast Copyright (c) 2023-2024 Choi Ikpil ikpil@naver.com + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +using System; +using System.Collections.Generic; +using DotRecast.Core.Numerics; + +namespace DotRecast.Recast.Geom +{ + public static class RcChunkyTriMeshs + { + /// Creates partitioned triangle mesh (AABB tree), + /// where each node contains at max trisPerChunk triangles. + public static bool CreateChunkyTriMesh(float[] verts, int[] tris, int ntris, int trisPerChunk, RcChunkyTriMesh cm) + { + int nchunks = (ntris + trisPerChunk - 1) / trisPerChunk; + + cm.nodes = new List(nchunks); + cm.ntris = ntris; + + // Build tree + BoundsItem[] items = new BoundsItem[ntris]; + for (int i = 0; i < ntris; ++i) + { + items[i] = new BoundsItem(); + } + + for (int i = 0; i < ntris; i++) + { + int t = i * 3; + BoundsItem it = items[i]; + it.i = i; + // Calc triangle XZ bounds. + it.bmin.X = it.bmax.X = verts[tris[t] * 3 + 0]; + it.bmin.Y = it.bmax.Y = verts[tris[t] * 3 + 2]; + for (int j = 1; j < 3; ++j) + { + int v = tris[t + j] * 3; + if (verts[v] < it.bmin.X) + { + it.bmin.X = verts[v]; + } + + if (verts[v + 2] < it.bmin.Y) + { + it.bmin.Y = verts[v + 2]; + } + + if (verts[v] > it.bmax.X) + { + it.bmax.X = verts[v]; + } + + if (verts[v + 2] > it.bmax.Y) + { + it.bmax.Y = verts[v + 2]; + } + } + } + + Subdivide(items, 0, ntris, trisPerChunk, cm.nodes, tris); + + items = null; + + // Calc max tris per node. + cm.maxTrisPerChunk = 0; + foreach (RcChunkyTriMeshNode node in cm.nodes) + { + bool isLeaf = node.i >= 0; + if (!isLeaf) + { + continue; + } + + if (node.tris.Length / 3 > cm.maxTrisPerChunk) + { + cm.maxTrisPerChunk = node.tris.Length / 3; + } + } + + return true; + } + + /// Returns the chunk indices which overlap the input rectable. + public static List GetChunksOverlappingRect(RcChunkyTriMesh cm, float[] bmin, float[] bmax) + { + // Traverse tree + List ids = new List(); + int i = 0; + while (i < cm.nodes.Count) + { + RcChunkyTriMeshNode node = cm.nodes[i]; + bool overlap = CheckOverlapRect(bmin, bmax, node.bmin, node.bmax); + bool isLeafNode = node.i >= 0; + + if (isLeafNode && overlap) + { + ids.Add(node); + } + + if (overlap || isLeafNode) + { + i++; + } + else + { + i = -node.i; + } + } + + return ids; + } + + /// Returns the chunk indices which overlap the input segment. + public static List GetChunksOverlappingSegment(RcChunkyTriMesh cm, RcVec2f p, RcVec2f q) + { + // Traverse tree + List ids = new List(); + int i = 0; + while (i < cm.nodes.Count) + { + RcChunkyTriMeshNode node = cm.nodes[i]; + bool overlap = CheckOverlapSegment(p, q, node.bmin, node.bmax); + bool isLeafNode = node.i >= 0; + + if (isLeafNode && overlap) + { + ids.Add(node); + } + + if (overlap || isLeafNode) + { + i++; + } + else + { + i = -node.i; + } + } + + return ids; + } + + + private static void CalcExtends(BoundsItem[] items, int imin, int imax, ref RcVec2f bmin, ref RcVec2f bmax) + { + bmin.X = items[imin].bmin.X; + bmin.Y = items[imin].bmin.Y; + + bmax.X = items[imin].bmax.X; + bmax.Y = items[imin].bmax.Y; + + for (int i = imin + 1; i < imax; ++i) + { + BoundsItem it = items[i]; + if (it.bmin.X < bmin.X) + { + bmin.X = it.bmin.X; + } + + if (it.bmin.Y < bmin.Y) + { + bmin.Y = it.bmin.Y; + } + + if (it.bmax.X > bmax.X) + { + bmax.X = it.bmax.X; + } + + if (it.bmax.Y > bmax.Y) + { + bmax.Y = it.bmax.Y; + } + } + } + + private static int LongestAxis(float x, float y) + { + return y > x ? 1 : 0; + } + + private static void Subdivide(BoundsItem[] items, int imin, int imax, int trisPerChunk, List nodes, int[] inTris) + { + int inum = imax - imin; + + RcChunkyTriMeshNode node = new RcChunkyTriMeshNode(); + nodes.Add(node); + + if (inum <= trisPerChunk) + { + // Leaf + CalcExtends(items, imin, imax, ref node.bmin, ref node.bmax); + + // Copy triangles. + node.i = nodes.Count; + node.tris = new int[inum * 3]; + + int dst = 0; + for (int i = imin; i < imax; ++i) + { + int src = items[i].i * 3; + node.tris[dst++] = inTris[src]; + node.tris[dst++] = inTris[src + 1]; + node.tris[dst++] = inTris[src + 2]; + } + } + else + { + // Split + CalcExtends(items, imin, imax, ref node.bmin, ref node.bmax); + + int axis = LongestAxis(node.bmax.X - node.bmin.X, node.bmax.Y - node.bmin.Y); + + if (axis == 0) + { + Array.Sort(items, imin, imax - imin, BoundsItemXComparer.Shared); + // Sort along x-axis + } + else if (axis == 1) + { + Array.Sort(items, imin, imax - imin, BoundsItemYComparer.Shared); + // Sort along y-axis + } + + int isplit = imin + inum / 2; + + // Left + Subdivide(items, imin, isplit, trisPerChunk, nodes, inTris); + // Right + Subdivide(items, isplit, imax, trisPerChunk, nodes, inTris); + + // Negative index means escape. + node.i = -nodes.Count; + } + } + + private static bool CheckOverlapRect(float[] amin, float[] amax, RcVec2f bmin, RcVec2f bmax) + { + bool overlap = true; + overlap = (amin[0] > bmax.X || amax[0] < bmin.X) ? false : overlap; + overlap = (amin[1] > bmax.Y || amax[1] < bmin.Y) ? false : overlap; + return overlap; + } + + + private static bool CheckOverlapSegment(RcVec2f p, RcVec2f q, RcVec2f bmin, RcVec2f bmax) + { + const float EPSILON = 1e-6f; + + float tmin = 0; + float tmax = 1; + var d = new RcVec2f(); + d.X = q.X - p.X; + d.Y = q.Y - p.Y; + + for (int i = 0; i < 2; i++) + { + if (MathF.Abs(d.Get(i)) < EPSILON) + { + // Ray is parallel to slab. No hit if origin not within slab + if (p.Get(i) < bmin.Get(i) || p.Get(i) > bmax.Get(i)) + return false; + } + else + { + // Compute intersection t value of ray with near and far plane of slab + float ood = 1.0f / d.Get(i); + float t1 = (bmin.Get(i) - p.Get(i)) * ood; + float t2 = (bmax.Get(i) - p.Get(i)) * ood; + if (t1 > t2) + { + (t1, t2) = (t2, t1); + } + + if (t1 > tmin) + tmin = t1; + if (t2 < tmax) + tmax = t2; + if (tmin > tmax) + return false; + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/DotRecast.Recast/Geom/RcTriMesh.cs b/src/DotRecast.Recast/Geom/RcTriMesh.cs index cba5dd4..5cd22d1 100644 --- a/src/DotRecast.Recast/Geom/RcTriMesh.cs +++ b/src/DotRecast.Recast/Geom/RcTriMesh.cs @@ -32,7 +32,8 @@ namespace DotRecast.Recast.Geom { this.vertices = vertices; this.faces = faces; - chunkyTriMesh = new RcChunkyTriMesh(vertices, faces, faces.Length / 3, 32); + chunkyTriMesh = new RcChunkyTriMesh(); + RcChunkyTriMeshs.CreateChunkyTriMesh(vertices, faces, faces.Length / 3, 32, chunkyTriMesh); } public int[] GetTris() @@ -47,7 +48,7 @@ namespace DotRecast.Recast.Geom public List GetChunksOverlappingRect(float[] bmin, float[] bmax) { - return chunkyTriMesh.GetChunksOverlappingRect(bmin, bmax); + return RcChunkyTriMeshs.GetChunksOverlappingRect(chunkyTriMesh, bmin, bmax); } } } \ No newline at end of file