/* recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org 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 System.Diagnostics; using System.Threading.Tasks; using DotRecast.Core; using DotRecast.Detour.Dynamic; using DotRecast.Detour.Dynamic.Colliders; using DotRecast.Detour.Dynamic.Io; using DotRecast.Recast.Demo.Builder; using DotRecast.Recast.Demo.Draw; using DotRecast.Recast.Demo.Geom; using DotRecast.Recast.Demo.Tools.Gizmos; using DotRecast.Recast.Demo.UI; using Silk.NET.Windowing; using static DotRecast.Recast.Demo.Draw.DebugDraw; using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives; using static DotRecast.Detour.DetourCommon; namespace DotRecast.Recast.Demo.Tools; public class DynamicUpdateTool : Tool { private enum ToolMode { BUILD, COLLIDERS, RAYCAST } private enum ColliderShape { SPHERE, CAPSULE, BOX, CYLINDER, COMPOSITE, CONVEX, TRIMESH_BRIDGE, TRIMESH_HOUSE } private Sample sample; private ToolMode mode = ToolMode.BUILD; private readonly float[] cellSize = new[] { 0.3f }; private PartitionType partitioning = PartitionType.WATERSHED; private bool filterLowHangingObstacles = true; private bool filterLedgeSpans = true; private bool filterWalkableLowHeightSpans = true; private readonly float[] walkableHeight = new[] { 2f }; private readonly float[] walkableRadius = new[] { 0.6f }; private readonly float[] walkableClimb = new[] { 0.9f }; private readonly float[] walkableSlopeAngle = new[] { 45f }; private readonly float[] minRegionArea = new[] { 6f }; private readonly float[] regionMergeSize = new[] { 36f }; private readonly float[] maxEdgeLen = new[] { 12f }; private readonly float[] maxSimplificationError = new[] { 1.3f }; private readonly int[] vertsPerPoly = new[] { 6 }; private bool buildDetailMesh = true; private bool compression = true; private readonly float[] detailSampleDist = new[] { 6f }; private readonly float[] detailSampleMaxError = new[] { 1f }; private bool showColliders = false; private long buildTime; private long raycastTime; private ColliderShape colliderShape = ColliderShape.SPHERE; private DynamicNavMesh dynaMesh; private readonly TaskFactory executor; private readonly Dictionary colliders = new(); private readonly Dictionary colliderGizmos = new(); private readonly Random random = Random.Shared; private readonly DemoInputGeomProvider bridgeGeom; private readonly DemoInputGeomProvider houseGeom; private readonly DemoInputGeomProvider convexGeom; private bool sposSet; private bool eposSet; private float[] spos; private float[] epos; private bool raycastHit; private float[] raycastHitPos; public DynamicUpdateTool() { executor = Task.Factory; bridgeGeom = DemoObjImporter.load(Loader.ToBytes("bridge.obj")); houseGeom = DemoObjImporter.load(Loader.ToBytes("house.obj")); convexGeom = DemoObjImporter.load(Loader.ToBytes("convex.obj")); } public override void setSample(Sample sample) { this.sample = sample; } public override void handleClick(float[] s, float[] p, bool shift) { if (mode == ToolMode.COLLIDERS) { if (!shift) { Tuple colliderWithGizmo = null; if (dynaMesh != null) { if (colliderShape == ColliderShape.SPHERE) { colliderWithGizmo = sphereCollider(p); } else if (colliderShape == ColliderShape.CAPSULE) { colliderWithGizmo = capsuleCollider(p); } else if (colliderShape == ColliderShape.BOX) { colliderWithGizmo = boxCollider(p); } else if (colliderShape == ColliderShape.CYLINDER) { colliderWithGizmo = cylinderCollider(p); } else if (colliderShape == ColliderShape.COMPOSITE) { colliderWithGizmo = compositeCollider(p); } else if (colliderShape == ColliderShape.TRIMESH_BRIDGE) { colliderWithGizmo = trimeshBridge(p); } else if (colliderShape == ColliderShape.TRIMESH_HOUSE) { colliderWithGizmo = trimeshHouse(p); } else if (colliderShape == ColliderShape.CONVEX) { colliderWithGizmo = convexTrimesh(p); } } if (colliderWithGizmo != null) { long id = dynaMesh.addCollider(colliderWithGizmo.Item1); colliders.Add(id, colliderWithGizmo.Item1); colliderGizmos.Add(id, colliderWithGizmo.Item2); } } } if (mode == ToolMode.RAYCAST) { if (shift) { sposSet = true; spos = ArrayUtils.CopyOf(p, p.Length); } else { eposSet = true; epos = ArrayUtils.CopyOf(p, p.Length); } if (sposSet && eposSet && dynaMesh != null) { float[] sp = { spos[0], spos[1] + 1.3f, spos[2] }; float[] ep = { epos[0], epos[1] + 1.3f, epos[2] }; long t1 = Stopwatch.GetTimestamp(); float? hitPos = dynaMesh.voxelQuery().raycast(sp, ep); long t2 = Stopwatch.GetTimestamp(); raycastTime = (t2 - t1) / 1_000_000L; raycastHit = hitPos.HasValue; raycastHitPos = hitPos.HasValue ? new float[] { sp[0] + hitPos.Value * (ep[0] - sp[0]), sp[1] + hitPos.Value * (ep[1] - sp[1]), sp[2] + hitPos.Value * (ep[2] - sp[2]) } : ep; } } } private Tuple sphereCollider(float[] p) { float radius = 1 + (float)random.NextDouble() * 10; return Tuple.Create( new SphereCollider(p, radius, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER, dynaMesh.config.walkableClimb), GizmoFactory.sphere(p, radius)); } private Tuple capsuleCollider(float[] p) { float radius = 0.4f + (float)random.NextDouble() * 4f; float[] a = new float[] { (1f - 2 * (float)random.NextDouble()), 0.01f + (float)random.NextDouble(), (1f - 2 * (float)random.NextDouble()) }; vNormalize(a); float len = 1f + (float)random.NextDouble() * 20f; a[0] *= len; a[1] *= len; a[2] *= len; float[] start = new float[] { p[0], p[1], p[2] }; float[] end = new float[] { p[0] + a[0], p[1] + a[1], p[2] + a[2] }; return Tuple.Create(new CapsuleCollider(start, end, radius, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER, dynaMesh.config.walkableClimb), GizmoFactory.capsule(start, end, radius)); } private Tuple boxCollider(float[] p) { float[] extent = new float[] { 0.5f + (float)random.NextDouble() * 6f, 0.5f + (float)random.NextDouble() * 6f, 0.5f + (float)random.NextDouble() * 6f }; float[] forward = new float[] { (1f - 2 * (float)random.NextDouble()), 0, (1f - 2 * (float)random.NextDouble()) }; float[] up = new float[] { (1f - 2 * (float)random.NextDouble()), 0.01f + (float)random.NextDouble(), (1f - 2 * (float)random.NextDouble()) }; float[][] halfEdges = BoxCollider.getHalfEdges(up, forward, extent); return Tuple.Create( new BoxCollider(p, halfEdges, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER, dynaMesh.config.walkableClimb), GizmoFactory.box(p, halfEdges)); } private Tuple cylinderCollider(float[] p) { float radius = 0.7f + (float)random.NextDouble() * 4f; float[] a = new float[] { (1f - 2 * (float)random.NextDouble()), 0.01f + (float)random.NextDouble(), (1f - 2 * (float)random.NextDouble()) }; vNormalize(a); float len = 2f + (float)random.NextDouble() * 20f; a[0] *= len; a[1] *= len; a[2] *= len; float[] start = new float[] { p[0], p[1], p[2] }; float[] end = new float[] { p[0] + a[0], p[1] + a[1], p[2] + a[2] }; return Tuple.Create(new CylinderCollider(start, end, radius, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER, dynaMesh.config.walkableClimb), GizmoFactory.cylinder(start, end, radius)); } private Tuple compositeCollider(float[] p) { float[] baseExtent = new float[] { 5, 3, 8 }; float[] baseCenter = new float[] { p[0], p[1] + 3, p[2] }; float[] baseUp = new float[] { 0, 1, 0 }; float[] forward = new float[] { (1f - 2 * (float)random.NextDouble()), 0, (1f - 2 * (float)random.NextDouble()) }; vNormalize(forward); float[] side = DemoMath.vCross(forward, baseUp); BoxCollider @base = new BoxCollider(baseCenter, BoxCollider.getHalfEdges(baseUp, forward, baseExtent), SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD, dynaMesh.config.walkableClimb); float[] roofExtent = new float[] { 4.5f, 4.5f, 8f }; float[] rx = GLU.build_4x4_rotation_matrix(45, forward[0], forward[1], forward[2]); float[] roofUp = mulMatrixVector(new float[3], rx, baseUp); float[] roofCenter = new float[] { p[0], p[1] + 6, p[2] }; BoxCollider roof = new BoxCollider(roofCenter, BoxCollider.getHalfEdges(roofUp, forward, roofExtent), SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD, dynaMesh.config.walkableClimb); float[] trunkStart = new float[] { baseCenter[0] - forward[0] * 15 + side[0] * 6, p[1], baseCenter[2] - forward[2] * 15 + side[2] * 6 }; float[] trunkEnd = new float[] { trunkStart[0], trunkStart[1] + 10, trunkStart[2] }; CapsuleCollider trunk = new CapsuleCollider(trunkStart, trunkEnd, 0.5f, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD, dynaMesh.config.walkableClimb); float[] crownCenter = new float[] { baseCenter[0] - forward[0] * 15 + side[0] * 6, p[1] + 10, baseCenter[2] - forward[2] * 15 + side[2] * 6 }; SphereCollider crown = new SphereCollider(crownCenter, 4f, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GRASS, dynaMesh.config.walkableClimb); CompositeCollider collider = new CompositeCollider(@base, roof, trunk, crown); ColliderGizmo baseGizmo = GizmoFactory.box(baseCenter, BoxCollider.getHalfEdges(baseUp, forward, baseExtent)); ColliderGizmo roofGizmo = GizmoFactory.box(roofCenter, BoxCollider.getHalfEdges(roofUp, forward, roofExtent)); ColliderGizmo trunkGizmo = GizmoFactory.capsule(trunkStart, trunkEnd, 0.5f); ColliderGizmo crownGizmo = GizmoFactory.sphere(crownCenter, 4f); ColliderGizmo gizmo = GizmoFactory.composite(baseGizmo, roofGizmo, trunkGizmo, crownGizmo); return Tuple.Create(collider, gizmo); } private Tuple trimeshBridge(float[] p) { return trimeshCollider(p, bridgeGeom); } private Tuple trimeshHouse(float[] p) { return trimeshCollider(p, houseGeom); } private Tuple convexTrimesh(float[] p) { float[] verts = transformVertices(p, convexGeom, 360); ConvexTrimeshCollider collider = new ConvexTrimeshCollider(verts, convexGeom.faces, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD, dynaMesh.config.walkableClimb * 10); return Tuple.Create(collider, GizmoFactory.trimesh(verts, convexGeom.faces)); } private Tuple trimeshCollider(float[] p, DemoInputGeomProvider geom) { float[] verts = transformVertices(p, geom, 0); TrimeshCollider collider = new TrimeshCollider(verts, geom.faces, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD, dynaMesh.config.walkableClimb * 10); return Tuple.Create(collider, GizmoFactory.trimesh(verts, geom.faces)); } private float[] transformVertices(float[] p, DemoInputGeomProvider geom, float ax) { float[] rx = GLU.build_4x4_rotation_matrix((float)random.NextDouble() * ax, 1, 0, 0); float[] ry = GLU.build_4x4_rotation_matrix((float)random.NextDouble() * 360, 0, 1, 0); float[] m = GLU.mul(rx, ry); float[] verts = new float[geom.vertices.Length]; float[] v = new float[3]; float[] vr = new float[3]; for (int i = 0; i < geom.vertices.Length; i += 3) { v[0] = geom.vertices[i]; v[1] = geom.vertices[i + 1]; v[2] = geom.vertices[i + 2]; mulMatrixVector(vr, m, v); vr[0] += p[0]; vr[1] += p[1] - 0.1f; vr[2] += p[2]; verts[i] = vr[0]; verts[i + 1] = vr[1]; verts[i + 2] = vr[2]; } return verts; } private float[] mulMatrixVector(float[] resultvector, float[] matrix, float[] pvector) { resultvector[0] = matrix[0] * pvector[0] + matrix[4] * pvector[1] + matrix[8] * pvector[2]; resultvector[1] = matrix[1] * pvector[0] + matrix[5] * pvector[1] + matrix[9] * pvector[2]; resultvector[2] = matrix[2] * pvector[0] + matrix[6] * pvector[1] + matrix[10] * pvector[2]; return resultvector; } public override void handleClickRay(float[] start, float[] dir, bool shift) { if (mode == ToolMode.COLLIDERS) { if (shift) { foreach (var e in colliders) { if (hit(start, dir, e.Value.bounds())) { dynaMesh.removeCollider(e.Key); colliders.Remove(e.Key); colliderGizmos.Remove(e.Key); break; } } } } } private bool hit(float[] point, float[] dir, float[] bounds) { float cx = 0.5f * (bounds[0] + bounds[3]); float cy = 0.5f * (bounds[1] + bounds[4]); float cz = 0.5f * (bounds[2] + bounds[5]); float dx = 0.5f * (bounds[3] - bounds[0]); float dy = 0.5f * (bounds[4] - bounds[1]); float dz = 0.5f * (bounds[5] - bounds[2]); float rSqr = dx * dx + dy * dy + dz * dz; float mx = point[0] - cx; float my = point[1] - cy; float mz = point[2] - cz; float c = mx * mx + my * my + mz * mz - rSqr; if (c <= 0.0f) { return true; } float b = mx * dir[0] + my * dir[1] + mz * dir[2]; if (b > 0.0f) { return false; } float disc = b * b - c; return disc >= 0.0f; } public override void handleRender(NavMeshRenderer renderer) { if (mode == ToolMode.COLLIDERS) { if (showColliders) { colliderGizmos.Values.forEach(g => g.render(renderer.getDebugDraw())); } } if (mode == ToolMode.RAYCAST) { RecastDebugDraw dd = renderer.getDebugDraw(); int startCol = duRGBA(128, 25, 0, 192); int endCol = duRGBA(51, 102, 0, 129); if (sposSet) { drawAgent(dd, spos, startCol); } if (eposSet) { drawAgent(dd, epos, endCol); } dd.depthMask(false); if (raycastHitPos != null) { int spathCol = raycastHit ? duRGBA(128, 32, 16, 220) : duRGBA(64, 128, 240, 220); dd.begin(LINES, 2.0f); dd.vertex(spos[0], spos[1] + 1.3f, spos[2], spathCol); dd.vertex(raycastHitPos[0], raycastHitPos[1], raycastHitPos[2], spathCol); dd.end(); } dd.depthMask(true); } } private void drawAgent(RecastDebugDraw dd, float[] pos, int col) { float r = sample.getSettingsUI().getAgentRadius(); float h = sample.getSettingsUI().getAgentHeight(); float c = sample.getSettingsUI().getAgentMaxClimb(); dd.depthMask(false); // Agent dimensions. dd.debugDrawCylinderWire(pos[0] - r, pos[1] + 0.02f, pos[2] - r, pos[0] + r, pos[1] + h, pos[2] + r, col, 2.0f); dd.debugDrawCircle(pos[0], pos[1] + c, pos[2], r, duRGBA(0, 0, 0, 64), 1.0f); int colb = duRGBA(0, 0, 0, 196); dd.begin(LINES); dd.vertex(pos[0], pos[1] - c, pos[2], colb); dd.vertex(pos[0], pos[1] + c, pos[2], colb); dd.vertex(pos[0] - r / 2, pos[1] + 0.02f, pos[2], colb); dd.vertex(pos[0] + r / 2, pos[1] + 0.02f, pos[2], colb); dd.vertex(pos[0], pos[1] + 0.02f, pos[2] - r / 2, colb); dd.vertex(pos[0], pos[1] + 0.02f, pos[2] + r / 2, colb); dd.end(); dd.depthMask(true); } public override void handleUpdate(float dt) { if (dynaMesh != null) { updateDynaMesh(); } } private void updateDynaMesh() { long t = Stopwatch.GetTimestamp(); try { bool updated = dynaMesh.update(executor).Result; if (updated) { buildTime = (Stopwatch.GetTimestamp() - t) / 1_000_000; sample.update(null, dynaMesh.recastResults(), dynaMesh.navMesh()); sample.setChanged(false); } } catch (Exception e) { Console.WriteLine(e); } } public override void layout(IWindow ctx) { // nk_layout_row_dynamic(ctx, 18, 1); // if (nk_option_label(ctx, "Build", mode == ToolMode.BUILD)) { // mode = ToolMode.BUILD; // } // nk_layout_row_dynamic(ctx, 18, 1); // if (nk_option_label(ctx, "Colliders", mode == ToolMode.COLLIDERS)) { // mode = ToolMode.COLLIDERS; // } // nk_layout_row_dynamic(ctx, 18, 1); // if (nk_option_label(ctx, "Raycast", mode == ToolMode.RAYCAST)) { // mode = ToolMode.RAYCAST; // } // // nk_layout_row_dynamic(ctx, 1, 1); // nk_spacing(ctx, 1); // if (mode == ToolMode.BUILD) { // nk_layout_row_dynamic(ctx, 18, 1); // if (nk_button_text(ctx, "Load Voxels...")) { // load(); // } // if (dynaMesh != null) { // nk_layout_row_dynamic(ctx, 18, 1); // compression = nk_check_text(ctx, "Compression", compression); // if (nk_button_text(ctx, "Save Voxels...")) { // save(); // } // } // nk_layout_row_dynamic(ctx, 1, 1); // nk_spacing(ctx, 1); // nk_layout_row_dynamic(ctx, 18, 1); // nk_label(ctx, "Rasterization", NK_TEXT_ALIGN_LEFT); // nk_layout_row_dynamic(ctx, 18, 2); // nk_label(ctx, "Cell Size", NK_TEXT_ALIGN_LEFT); // nk_label(ctx, string.format("%.2f", cellSize[0]), NK_TEXT_ALIGN_RIGHT); // nk_layout_row_dynamic(ctx, 1, 1); // nk_spacing(ctx, 1); // nk_layout_row_dynamic(ctx, 18, 1); // nk_label(ctx, "Agent", NK_TEXT_ALIGN_LEFT); // nk_layout_row_dynamic(ctx, 20, 1); // nk_property_float(ctx, "Height", 0f, walkableHeight, 5f, 0.01f, 0.01f); // nk_layout_row_dynamic(ctx, 20, 1); // nk_property_float(ctx, "Radius", 0f, walkableRadius, 10f, 0.01f, 0.01f); // nk_layout_row_dynamic(ctx, 20, 1); // nk_property_float(ctx, "Max Climb", 0f, walkableClimb, 10f, 0.01f, 0.01f); // nk_layout_row_dynamic(ctx, 18, 2); // nk_label(ctx, "Max Slope", NK_TEXT_ALIGN_LEFT); // nk_label(ctx, string.format("%.0f", walkableSlopeAngle[0]), NK_TEXT_ALIGN_RIGHT); // // nk_layout_row_dynamic(ctx, 1, 1); // nk_spacing(ctx, 1); // nk_layout_row_dynamic(ctx, 18, 1); // nk_label(ctx, "Partitioning", NK_TEXT_ALIGN_LEFT); // partitioning = NuklearUIHelper.nk_radio(ctx, PartitionType.values(), partitioning, // p => p.name().substring(0, 1) + p.name().substring(1).toLowerCase()); // // nk_layout_row_dynamic(ctx, 1, 1); // nk_spacing(ctx, 1); // nk_layout_row_dynamic(ctx, 18, 1); // nk_label(ctx, "Filtering", NK_TEXT_ALIGN_LEFT); // nk_layout_row_dynamic(ctx, 18, 1); // filterLowHangingObstacles = nk_option_text(ctx, "Low Hanging Obstacles", filterLowHangingObstacles); // nk_layout_row_dynamic(ctx, 18, 1); // filterLedgeSpans = nk_option_text(ctx, "Ledge Spans", filterLedgeSpans); // nk_layout_row_dynamic(ctx, 18, 1); // filterWalkableLowHeightSpans = nk_option_text(ctx, "Walkable Low Height Spans", filterWalkableLowHeightSpans); // // nk_layout_row_dynamic(ctx, 1, 1); // nk_spacing(ctx, 1); // nk_layout_row_dynamic(ctx, 18, 1); // nk_label(ctx, "Region", NK_TEXT_ALIGN_LEFT); // nk_layout_row_dynamic(ctx, 20, 1); // nk_property_float(ctx, "Min Region Size", 0, minRegionArea, 150, 0.1f, 0.1f); // nk_layout_row_dynamic(ctx, 20, 1); // nk_property_float(ctx, "Merged Region Size", 0, regionMergeSize, 400, 0.1f, 0.1f); // // nk_layout_row_dynamic(ctx, 1, 1); // nk_spacing(ctx, 1); // nk_layout_row_dynamic(ctx, 18, 1); // nk_label(ctx, "Polygonization", NK_TEXT_ALIGN_LEFT); // nk_layout_row_dynamic(ctx, 20, 1); // nk_property_float(ctx, "Max Edge Length", 0f, maxEdgeLen, 50f, 0.1f, 0.1f); // nk_layout_row_dynamic(ctx, 20, 1); // nk_property_float(ctx, "Max Edge Error", 0.1f, maxSimplificationError, 10f, 0.1f, 0.1f); // nk_layout_row_dynamic(ctx, 20, 1); // nk_property_int(ctx, "Verts Per Poly", 3, vertsPerPoly, 12, 1, 1); // // nk_layout_row_dynamic(ctx, 1, 1); // nk_spacing(ctx, 1); // nk_layout_row_dynamic(ctx, 18, 1); // nk_label(ctx, "Detail Mesh", NK_TEXT_ALIGN_LEFT); // nk_layout_row_dynamic(ctx, 20, 1); // buildDetailMesh = nk_check_text(ctx, "Enable", buildDetailMesh); // nk_layout_row_dynamic(ctx, 20, 1); // nk_property_float(ctx, "Sample Distance", 0f, detailSampleDist, 16f, 0.1f, 0.1f); // nk_layout_row_dynamic(ctx, 20, 1); // nk_property_float(ctx, "Max Sample Error", 0f, detailSampleMaxError, 16f, 0.1f, 0.1f); // nk_layout_row_dynamic(ctx, 1, 1); // nk_spacing(ctx, 1); // nk_layout_row_dynamic(ctx, 20, 1); // if (nk_button_text(ctx, "Build")) { // if (dynaMesh != null) { // buildDynaMesh(); // sample.setChanged(false); // } // } // } // if (mode == ToolMode.COLLIDERS) { // nk_layout_row_dynamic(ctx, 1, 1); // nk_spacing(ctx, 1); // nk_layout_row_dynamic(ctx, 18, 1); // nk_label(ctx, "Colliders", NK_TEXT_ALIGN_LEFT); // nk_layout_row_dynamic(ctx, 20, 1); // showColliders = nk_check_text(ctx, "Show", showColliders); // nk_layout_row_dynamic(ctx, 20, 1); // if (nk_option_label(ctx, "Sphere", colliderShape == ColliderShape.SPHERE)) { // colliderShape = ColliderShape.SPHERE; // } // nk_layout_row_dynamic(ctx, 18, 1); // if (nk_option_label(ctx, "Capsule", colliderShape == ColliderShape.CAPSULE)) { // colliderShape = ColliderShape.CAPSULE; // } // nk_layout_row_dynamic(ctx, 18, 1); // if (nk_option_label(ctx, "Box", colliderShape == ColliderShape.BOX)) { // colliderShape = ColliderShape.BOX; // } // nk_layout_row_dynamic(ctx, 18, 1); // if (nk_option_label(ctx, "Cylinder", colliderShape == ColliderShape.CYLINDER)) { // colliderShape = ColliderShape.CYLINDER; // } // nk_layout_row_dynamic(ctx, 18, 1); // if (nk_option_label(ctx, "Composite", colliderShape == ColliderShape.COMPOSITE)) { // colliderShape = ColliderShape.COMPOSITE; // } // nk_layout_row_dynamic(ctx, 18, 1); // if (nk_option_label(ctx, "Convex Trimesh", colliderShape == ColliderShape.CONVEX)) { // colliderShape = ColliderShape.CONVEX; // } // nk_layout_row_dynamic(ctx, 18, 1); // if (nk_option_label(ctx, "Trimesh Bridge", colliderShape == ColliderShape.TRIMESH_BRIDGE)) { // colliderShape = ColliderShape.TRIMESH_BRIDGE; // } // nk_layout_row_dynamic(ctx, 18, 1); // if (nk_option_label(ctx, "Trimesh House", colliderShape == ColliderShape.TRIMESH_HOUSE)) { // colliderShape = ColliderShape.TRIMESH_HOUSE; // } // } // nk_layout_row_dynamic(ctx, 2, 1); // nk_spacing(ctx, 1); // nk_layout_row_dynamic(ctx, 18, 1); // if (mode == ToolMode.RAYCAST) { // nk_label(ctx, string.format("Raycast Time: %d ms", raycastTime), NK_TEXT_ALIGN_LEFT); // if (sposSet) { // nk_layout_row_dynamic(ctx, 18, 1); // nk_label(ctx, string.format("Start: %.3f, %.3f, %.3f", spos[0], spos[1] + 1.3f, spos[2]), NK_TEXT_ALIGN_LEFT); // } // if (eposSet) { // nk_layout_row_dynamic(ctx, 18, 1); // nk_label(ctx, string.format("End: %.3f, %.3f, %.3f", epos[0], epos[1] + 1.3f, epos[2]), NK_TEXT_ALIGN_LEFT); // } // if (raycastHit) { // nk_layout_row_dynamic(ctx, 18, 1); // nk_label(ctx, string.format("Hit: %.3f, %.3f, %.3f", raycastHitPos[0], raycastHitPos[1], raycastHitPos[2]), // NK_TEXT_ALIGN_LEFT); // } // } else { // nk_label(ctx, string.format("Build Time: %d ms", buildTime), NK_TEXT_ALIGN_LEFT); // } } private void load() { // try (MemoryStack stack = stackPush()) { // PointerBuffer aFilterPatterns = stack.mallocPointer(1); // aFilterPatterns.put(stack.UTF8("*.voxels")); // aFilterPatterns.flip(); // string filename = TinyFileDialogs.tinyfd_openFileDialog("Open Voxel File", "", aFilterPatterns, "Voxel File", false); // if (filename != null) { // load(filename); // } // } } private void load(string filename) { // File file = new File(filename); // if (file.exists()) { // VoxelFileReader reader = new VoxelFileReader(); // try (FileInputStream fis = new FileInputStream(file)) { // VoxelFile voxelFile = reader.read(fis); // dynaMesh = new DynamicNavMesh(voxelFile); // dynaMesh.config.keepIntermediateResults = true; // updateUI(); // buildDynaMesh(); // colliders.clear(); // } catch (Exception e) { // Console.WriteLine(e); // dynaMesh = null; // } // } } private void save() { // try (MemoryStack stack = stackPush()) { // PointerBuffer aFilterPatterns = stack.mallocPointer(1); // aFilterPatterns.put(stack.UTF8("*.voxels")); // aFilterPatterns.flip(); // string filename = TinyFileDialogs.tinyfd_saveFileDialog("Save Voxel File", "", aFilterPatterns, "Voxel File"); // if (filename != null) { // save(filename); // } // } } private void save(string filename) { // File file = new File(filename); // try (FileOutputStream fos = new FileOutputStream(file)) { // VoxelFile voxelFile = VoxelFile.from(dynaMesh); // VoxelFileWriter writer = new VoxelFileWriter(); // writer.write(fos, voxelFile, compression); // } catch (Exception e) { // Console.WriteLine(e); // } } private void buildDynaMesh() { configDynaMesh(); long t = Stopwatch.GetTimestamp(); try { var _ = dynaMesh.build(executor).Result; } catch (Exception e) { Console.WriteLine(e); } buildTime = (Stopwatch.GetTimestamp() - t) / 1_000_000; sample.update(null, dynaMesh.recastResults(), dynaMesh.navMesh()); } private void configDynaMesh() { dynaMesh.config.partitionType = partitioning; dynaMesh.config.walkableHeight = walkableHeight[0]; dynaMesh.config.walkableSlopeAngle = walkableSlopeAngle[0]; dynaMesh.config.walkableRadius = walkableRadius[0]; dynaMesh.config.walkableClimb = walkableClimb[0]; dynaMesh.config.filterLowHangingObstacles = filterLowHangingObstacles; dynaMesh.config.filterLedgeSpans = filterLedgeSpans; dynaMesh.config.filterWalkableLowHeightSpans = filterWalkableLowHeightSpans; dynaMesh.config.minRegionArea = minRegionArea[0]; dynaMesh.config.regionMergeArea = regionMergeSize[0]; dynaMesh.config.maxEdgeLen = maxEdgeLen[0]; dynaMesh.config.maxSimplificationError = maxSimplificationError[0]; dynaMesh.config.vertsPerPoly = vertsPerPoly[0]; dynaMesh.config.buildDetailMesh = buildDetailMesh; dynaMesh.config.detailSampleDistance = detailSampleDist[0]; dynaMesh.config.detailSampleMaxError = detailSampleMaxError[0]; } private void updateUI() { cellSize[0] = dynaMesh.config.cellSize; partitioning = dynaMesh.config.partitionType; walkableHeight[0] = dynaMesh.config.walkableHeight; walkableSlopeAngle[0] = dynaMesh.config.walkableSlopeAngle; walkableRadius[0] = dynaMesh.config.walkableRadius; walkableClimb[0] = dynaMesh.config.walkableClimb; minRegionArea[0] = dynaMesh.config.minRegionArea; regionMergeSize[0] = dynaMesh.config.regionMergeArea; maxEdgeLen[0] = dynaMesh.config.maxEdgeLen; maxSimplificationError[0] = dynaMesh.config.maxSimplificationError; vertsPerPoly[0] = dynaMesh.config.vertsPerPoly; buildDetailMesh = dynaMesh.config.buildDetailMesh; detailSampleDist[0] = dynaMesh.config.detailSampleDistance; detailSampleMaxError[0] = dynaMesh.config.detailSampleMaxError; filterLowHangingObstacles = dynaMesh.config.filterLowHangingObstacles; filterLedgeSpans = dynaMesh.config.filterLedgeSpans; filterWalkableLowHeightSpans = dynaMesh.config.filterWalkableLowHeightSpans; } public override string getName() { return "Dynamic Updates"; } }