This commit is contained in:
ikpil 2023-03-17 01:48:49 +09:00
parent 41c7b777ab
commit 06e6f53101
322 changed files with 32903 additions and 27543 deletions

View File

@ -2,8 +2,6 @@
namespace DotRecast.Core
{
public static class ArrayUtils
{
public static T[] CopyOf<T>(T[] source, int startIdx, int length)

View File

@ -2,8 +2,6 @@
namespace DotRecast.Core
{
public class AtomicBoolean
{
private volatile int _location;

View File

@ -2,8 +2,6 @@
namespace DotRecast.Core
{
public class AtomicFloat
{
private volatile float _location;

View File

@ -2,15 +2,12 @@
namespace DotRecast.Core
{
public class AtomicInteger
{
private volatile int _location;
public AtomicInteger() : this(0)
{
}
public AtomicInteger(int location)
@ -64,6 +61,5 @@ public class AtomicInteger
{
return Interlocked.Add(ref _location, value);
}
}
}

View File

@ -2,8 +2,6 @@
namespace DotRecast.Core
{
public class AtomicLong
{
private long _location;

View File

@ -3,8 +3,6 @@ using System.Buffers.Binary;
namespace DotRecast.Core
{
public class ByteBuffer
{
private ByteOrder _order;
@ -31,11 +29,13 @@ public class ByteBuffer
_order = order;
}
public int limit() {
public int limit()
{
return _bytes.Length - _position;
}
public int remaining() {
public int remaining()
{
int rem = limit();
return rem > 0 ? rem : 0;
}

View File

@ -1,12 +1,9 @@
namespace DotRecast.Core
{
public enum ByteOrder
{
/// <summary>Default on most Windows systems</summary>
LITTLE_ENDIAN,
BIG_ENDIAN,
}
}

View File

@ -3,8 +3,6 @@ using System.Collections.Generic;
namespace DotRecast.Core
{
public static class CollectionExtensions
{
public static void forEach<T>(this IEnumerable<T> collection, Action<T> action)

View File

@ -20,38 +20,44 @@ using System.Collections.Generic;
namespace DotRecast.Core
{
public static class ConvexUtils {
public static class ConvexUtils
{
// Calculates convex hull on xz-plane of points on 'pts',
// stores the indices of the resulting hull in 'out' and
// returns number of points on hull.
public static List<int> convexhull(List<float> pts) {
public static List<int> convexhull(List<float> pts)
{
int npts = pts.Count / 3;
List<int> @out = new List<int>();
// Find lower-leftmost point.
int hull = 0;
for (int i = 1; i < npts; ++i) {
for (int i = 1; i < npts; ++i)
{
float[] a = new float[] { pts[i * 3], pts[i * 3 + 1], pts[i * 3 + 2] };
float[] b = new float[] { pts[hull * 3], pts[hull * 3 + 1], pts[hull * 3 + 2] };
if (cmppt(a, b)) {
if (cmppt(a, b))
{
hull = i;
}
}
// Gift wrap hull.
int endpt = 0;
do {
do
{
@out.Add(hull);
endpt = 0;
for (int j = 1; j < npts; ++j) {
for (int j = 1; j < npts; ++j)
{
float[] a = new float[] { pts[hull * 3], pts[hull * 3 + 1], pts[hull * 3 + 2] };
float[] b = new float[] { pts[endpt * 3], pts[endpt * 3 + 1], pts[endpt * 3 + 2] };
float[] c = new float[] { pts[j * 3], pts[j * 3 + 1], pts[j * 3 + 2] };
if (hull == endpt || left(a, b, c)) {
if (hull == endpt || left(a, b, c))
{
endpt = j;
}
}
hull = endpt;
} while (endpt != @out[0]);
@ -59,31 +65,39 @@ public static class ConvexUtils {
}
// Returns true if 'a' is more lower-left than 'b'.
private static bool cmppt(float[] a, float[] b) {
if (a[0] < b[0]) {
private static bool cmppt(float[] a, float[] b)
{
if (a[0] < b[0])
{
return true;
}
if (a[0] > b[0]) {
if (a[0] > b[0])
{
return false;
}
if (a[2] < b[2]) {
if (a[2] < b[2])
{
return true;
}
if (a[2] > b[2]) {
if (a[2] > b[2])
{
return false;
}
return false;
}
// Returns true if 'c' is left of line 'a'-'b'.
private static bool left(float[] a, float[] b, float[] c) {
private static bool left(float[] a, float[] b, float[] c)
{
float u1 = b[0] - a[0];
float v1 = b[2] - a[2];
float u2 = c[0] - a[0];
float v2 = c[2] - a[2];
return u1 * v2 - v1 * u2 < 0;
}
}
}

View File

@ -22,17 +22,18 @@ using System;
namespace DotRecast.Core
{
public class DemoMath {
public static float vDistSqr(float[] v1, float[] v2, int i) {
public class DemoMath
{
public static float vDistSqr(float[] v1, float[] v2, int i)
{
float dx = v2[i] - v1[0];
float dy = v2[i + 1] - v1[1];
float dz = v2[i + 2] - v1[2];
return dx * dx + dy * dy + dz * dz;
}
public static float[] vCross(float[] v1, float[] v2) {
public static float[] vCross(float[] v1, float[] v2)
{
float[] dest = new float[3];
dest[0] = v1[1] * v2[2] - v1[2] * v2[1];
dest[1] = v1[2] * v2[0] - v1[0] * v2[2];
@ -40,44 +41,53 @@ public class DemoMath {
return dest;
}
public static float vDot(float[] v1, float[] v2) {
public static float vDot(float[] v1, float[] v2)
{
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
}
public static float sqr(float f) {
public static float sqr(float f)
{
return f * f;
}
public static float getPathLen(float[] path, int npath) {
public static float getPathLen(float[] path, int npath)
{
float totd = 0;
for (int i = 0; i < npath - 1; ++i) {
for (int i = 0; i < npath - 1; ++i)
{
totd += (float)Math.Sqrt(vDistSqr(path, i * 3, (i + 1) * 3));
}
return totd;
}
public static float vDistSqr(float[] v, int i, int j) {
public static float vDistSqr(float[] v, int i, int j)
{
float dx = v[i] - v[j];
float dy = v[i + 1] - v[j + 1];
float dz = v[i + 2] - v[j + 2];
return dx * dx + dy * dy + dz * dz;
}
public static float step(float threshold, float v) {
public static float step(float threshold, float v)
{
return v < threshold ? 0.0f : 1.0f;
}
public static int clamp(int v, int min, int max) {
public static int clamp(int v, int min, int max)
{
return Math.Max(Math.Min(v, max), min);
}
public static float clamp(float v, float min, float max) {
public static float clamp(float v, float min, float max)
{
return Math.Max(Math.Min(v, max), min);
}
public static float lerp(float f, float g, float u) {
public static float lerp(float f, float g, float u)
{
return u * g + (1f - u) * f;
}
}
}

View File

@ -2,8 +2,6 @@
namespace DotRecast.Core
{
public static class Loader
{
public static byte[] ToBytes(string filename)

View File

@ -22,13 +22,10 @@ using System;
namespace DotRecast.Core
{
using System.Collections.Generic;
public class OrderedQueue<T>
{
private readonly List<T> _items;
private readonly Comparison<T> _comparison;
@ -43,7 +40,8 @@ public class OrderedQueue<T>
return _items.Count;
}
public void clear() {
public void clear()
{
_items.Clear();
}
@ -59,12 +57,14 @@ public class OrderedQueue<T>
return node;
}
public void Enqueue(T item) {
public void Enqueue(T item)
{
_items.Add(item);
_items.Sort(_comparison);
}
public void Remove(T item) {
public void Remove(T item)
{
_items.Remove(item);
}
@ -73,5 +73,4 @@ public class OrderedQueue<T>
return 0 == _items.Count;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -23,23 +23,27 @@ using System.Collections.Generic;
namespace DotRecast.Detour.Crowd
{
using static DetourCommon;
/// Represents an agent managed by a #dtCrowd object.
/// @ingroup crowd
public class CrowdAgent {
public class CrowdAgent
{
/// The type of navigation mesh polygon the agent is currently traversing.
/// @ingroup crowd
public enum CrowdAgentState {
DT_CROWDAGENT_STATE_INVALID, /// < The agent is not in a valid state.
DT_CROWDAGENT_STATE_WALKING, /// < The agent is traversing a normal navigation mesh polygon.
public enum CrowdAgentState
{
DT_CROWDAGENT_STATE_INVALID,
/// < The agent is not in a valid state.
DT_CROWDAGENT_STATE_WALKING,
/// < The agent is traversing a normal navigation mesh polygon.
DT_CROWDAGENT_STATE_OFFMESH, /// < The agent is traversing an off-mesh connection.
};
public enum MoveRequestState {
public enum MoveRequestState
{
DT_CROWDAGENT_TARGET_NONE,
DT_CROWDAGENT_TARGET_FAILED,
DT_CROWDAGENT_TARGET_VALID,
@ -50,57 +54,88 @@ public class CrowdAgent {
};
public readonly long idx;
/// The type of mesh polygon the agent is traversing. (See: #CrowdAgentState)
public CrowdAgentState state;
/// True if the agent has valid path (targetState == DT_CROWDAGENT_TARGET_VALID) and the path does not lead to the
/// requested position, else false.
public bool partial;
/// The path corridor the agent is using.
public PathCorridor corridor;
/// The local boundary data for the agent.
public LocalBoundary boundary;
/// Time since the agent's path corridor was optimized.
public float topologyOptTime;
/// The known neighbors of the agent.
public List<Crowd.CrowdNeighbour> neis = new List<Crowd.CrowdNeighbour>();
/// The desired speed.
public float desiredSpeed;
public float[] npos = new float[3]; /// < The current agent position. [(x, y, z)]
public float[] disp = new float[3]; /// < A temporary value used to accumulate agent displacement during iterative
public float[] npos = new float[3];
/// < The current agent position. [(x, y, z)]
public float[] disp = new float[3];
/// < A temporary value used to accumulate agent displacement during iterative
/// collision resolution. [(x, y, z)]
public float[] dvel = new float[3]; /// < The desired velocity of the agent. Based on the current path, calculated
public float[] dvel = new float[3];
/// < The desired velocity of the agent. Based on the current path, calculated
/// from
/// scratch each frame. [(x, y, z)]
public float[] nvel = new float[3]; /// < The desired velocity adjusted by obstacle avoidance, calculated from scratch each
/// frame. [(x, y, z)]
public float[] vel = new float[3]; /// < The actual velocity of the agent. The change from nvel -> vel is
/// constrained by max acceleration. [(x, y, z)]
public float[] nvel = new float[3];
/// < The desired velocity adjusted by obstacle avoidance, calculated from scratch each
/// frame. [(x, y, z)]
public float[] vel = new float[3];
/// < The actual velocity of the agent. The change from nvel -> vel is
/// constrained by max acceleration. [(x, y, z)]
/// The agent's configuration parameters.
public CrowdAgentParams option;
/// The local path corridor corners for the agent.
public List<StraightPathItem> corners = new List<StraightPathItem>();
public MoveRequestState targetState; /// < State of the movement request.
public long targetRef; /// < Target polyref of the movement request.
public float[] targetPos = new float[3]; /// < Target position of the movement request (or velocity in case of
public MoveRequestState targetState;
/// < State of the movement request.
public long targetRef;
/// < Target polyref of the movement request.
public float[] targetPos = new float[3];
/// < Target position of the movement request (or velocity in case of
/// DT_CROWDAGENT_TARGET_VELOCITY).
public PathQueryResult targetPathQueryResult; /// < Path finder query
public bool targetReplan; /// < Flag indicating that the current path is being replanned.
public float targetReplanTime; /// <Time since the agent's target was replanned.
public PathQueryResult targetPathQueryResult;
/// < Path finder query
public bool targetReplan;
/// < Flag indicating that the current path is being replanned.
public float targetReplanTime;
/// <Time since the agent's target was replanned.
public float targetReplanWaitTime;
public CrowdAgentAnimation animation;
public CrowdAgent(int idx) {
public CrowdAgent(int idx)
{
this.idx = idx;
corridor = new PathCorridor();
boundary = new LocalBoundary();
animation = new CrowdAgentAnimation();
}
public void integrate(float dt) {
public void integrate(float dt)
{
// Fake dynamic constraint.
float maxDelta = option.maxAcceleration * dt;
float[] dv = vSub(nvel, vel);
@ -116,13 +151,17 @@ public class CrowdAgent {
vSet(vel, 0, 0, 0);
}
public bool overOffmeshConnection(float radius) {
public bool overOffmeshConnection(float radius)
{
if (0 == corners.Count)
return false;
bool offMeshConnection = ((corners[corners.Count - 1].getFlags()
& NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) ? true : false;
if (offMeshConnection) {
& NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
? true
: false;
if (offMeshConnection)
{
float distSq = vDist2DSqr(npos, corners[corners.Count - 1].getPos());
if (distSq < radius * radius)
return true;
@ -131,7 +170,8 @@ public class CrowdAgent {
return false;
}
public float getDistanceToGoal(float range) {
public float getDistanceToGoal(float range)
{
if (0 == corners.Count)
return range;
@ -142,10 +182,11 @@ public class CrowdAgent {
return range;
}
public float[] calcSmoothSteerDirection() {
public float[] calcSmoothSteerDirection()
{
float[] dir = new float[3];
if (0 < corners.Count) {
if (0 < corners.Count)
{
int ip0 = 0;
int ip1 = Math.Min(1, corners.Count - 1);
float[] p0 = corners[ip0].getPos();
@ -167,29 +208,36 @@ public class CrowdAgent {
vNormalize(dir);
}
return dir;
}
public float[] calcStraightSteerDirection() {
public float[] calcStraightSteerDirection()
{
float[] dir = new float[3];
if (0 < corners.Count) {
if (0 < corners.Count)
{
dir = vSub(corners[0].getPos(), npos);
dir[1] = 0;
vNormalize(dir);
}
return dir;
}
public void setTarget(long refs, float[] pos) {
public void setTarget(long refs, float[] pos)
{
targetRef = refs;
vCopy(targetPos, pos);
targetPathQueryResult = null;
if (targetRef != 0) {
if (targetRef != 0)
{
targetState = MoveRequestState.DT_CROWDAGENT_TARGET_REQUESTING;
} else {
}
else
{
targetState = MoveRequestState.DT_CROWDAGENT_TARGET_FAILED;
}
}
}
}

View File

@ -17,18 +17,16 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.Crowd
{
public class CrowdAgentAnimation {
public class CrowdAgentAnimation
{
public bool active;
public float[] initPos = new float[3];
public float[] startPos = new float[3];
public float[] endPos = new float[3];
public long polyRef;
public float t, tmax;
}
}

View File

@ -22,33 +22,44 @@ using System;
namespace DotRecast.Detour.Crowd
{
/// Configuration parameters for a crowd agent.
/// @ingroup crowd
public class CrowdAgentParams {
public float radius; /// < Agent radius. [Limit: >= 0]
public float height; /// < Agent height. [Limit: > 0]
public float maxAcceleration; /// < Maximum allowed acceleration. [Limit: >= 0]
public float maxSpeed; /// < Maximum allowed speed. [Limit: >= 0]
public class CrowdAgentParams
{
public float radius;
/// < Agent radius. [Limit: >= 0]
public float height;
/// < Agent height. [Limit: > 0]
public float maxAcceleration;
/// < Maximum allowed acceleration. [Limit: >= 0]
public float maxSpeed;
/// < Maximum allowed speed. [Limit: >= 0]
/// Defines how close a collision element must be before it is considered for steering behaviors. [Limits: > 0]
public float collisionQueryRange;
public float pathOptimizationRange; /// < The path visibility optimization range. [Limit: > 0]
public float pathOptimizationRange;
/// < The path visibility optimization range. [Limit: > 0]
/// How aggresive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0]
public float separationWeight;
/// Crowd agent update flags.
public const int DT_CROWD_ANTICIPATE_TURNS = 1;
public const int DT_CROWD_OBSTACLE_AVOIDANCE = 2;
public const int DT_CROWD_SEPARATION = 4;
public const int DT_CROWD_OPTIMIZE_VIS = 8; /// < Use #dtPathCorridor::optimizePathVisibility() to optimize
/// the agent path.
public const int DT_CROWD_OPTIMIZE_TOPO = 16; /// < Use dtPathCorridor::optimizePathTopology() to optimize
/// the agent path.
public const int DT_CROWD_OPTIMIZE_VIS = 8;
/// < Use #dtPathCorridor::optimizePathVisibility() to optimize
/// the agent path.
public const int DT_CROWD_OPTIMIZE_TOPO = 16;
/// < Use dtPathCorridor::optimizePathTopology() to optimize
/// the agent path.
/// Flags that impact steering behavior. (See: #UpdateFlags)
public int updateFlags;

View File

@ -18,53 +18,60 @@ freely, subject to the following restrictions:
namespace DotRecast.Detour.Crowd
{
public class CrowdConfig {
public class CrowdConfig
{
public readonly float maxAgentRadius;
/**
* Max number of path requests in the queue
*/
public int pathQueueSize = 32;
/**
* Max number of sliced path finding iterations executed per update (used to handle longer paths and replans)
*/
public int maxFindPathIterations = 100;
/**
* Max number of sliced path finding iterations executed per agent to find the initial path to target
*/
public int maxTargetFindPathIterations = 20;
/**
* Min time between topology optimizations (in seconds)
*/
public float topologyOptimizationTimeThreshold = 0.5f;
/**
* The number of polygons from the beginning of the corridor to check to ensure path validity
*/
public int checkLookAhead = 10;
/**
* Min time between target re-planning (in seconds)
*/
public float targetReplanDelay = 1.0f;
/**
* Max number of sliced path finding iterations executed per topology optimization per agent
*/
public int maxTopologyOptimizationIterations = 32;
public float collisionResolveFactor = 0.7f;
/**
* Max number of neighbour agents to consider in obstacle avoidance processing
*/
public int maxObstacleAvoidanceCircles = 6;
/**
* Max number of neighbour segments to consider in obstacle avoidance processing
*/
public int maxObstacleAvoidanceSegments = 8;
public CrowdConfig(float maxAgentRadius) {
public CrowdConfig(float maxAgentRadius)
{
this.maxAgentRadius = maxAgentRadius;
}
}
}

View File

@ -23,48 +23,53 @@ using System.Linq;
namespace DotRecast.Detour.Crowd
{
public class CrowdTelemetry {
public class CrowdTelemetry
{
public const int TIMING_SAMPLES = 10;
private float _maxTimeToEnqueueRequest;
private float _maxTimeToFindPath;
private readonly Dictionary<string, long> _executionTimings = new Dictionary<string, long>();
private readonly Dictionary<string, List<long>> _executionTimingSamples = new Dictionary<string, List<long>>();
public float maxTimeToEnqueueRequest() {
public float maxTimeToEnqueueRequest()
{
return _maxTimeToEnqueueRequest;
}
public float maxTimeToFindPath() {
public float maxTimeToFindPath()
{
return _maxTimeToFindPath;
}
public Dictionary<string, long> executionTimings() {
public Dictionary<string, long> executionTimings()
{
return _executionTimings;
}
public void start() {
public void start()
{
_maxTimeToEnqueueRequest = 0;
_maxTimeToFindPath = 0;
_executionTimings.Clear();
}
public void recordMaxTimeToEnqueueRequest(float time) {
public void recordMaxTimeToEnqueueRequest(float time)
{
_maxTimeToEnqueueRequest = Math.Max(_maxTimeToEnqueueRequest, time);
}
public void recordMaxTimeToFindPath(float time) {
public void recordMaxTimeToFindPath(float time)
{
_maxTimeToFindPath = Math.Max(_maxTimeToFindPath, time);
}
public void start(string name) {
public void start(string name)
{
_executionTimings.Add(name, Stopwatch.GetTimestamp());
}
public void stop(string name) {
public void stop(string name)
{
long duration = Stopwatch.GetTimestamp() - _executionTimings[name];
if (!_executionTimingSamples.TryGetValue(name, out var s))
{
@ -72,12 +77,13 @@ public class CrowdTelemetry {
_executionTimingSamples.Add(name, s);
}
if (s.Count == TIMING_SAMPLES) {
if (s.Count == TIMING_SAMPLES)
{
s.RemoveAt(0);
}
s.Add(duration);
_executionTimings[name] = (long)s.Average();
}
}
}

View File

@ -23,17 +23,17 @@ using System.Collections.Generic;
namespace DotRecast.Detour.Crowd
{
using static DetourCommon;
public class LocalBoundary {
public class LocalBoundary
{
public const int MAX_LOCAL_SEGS = 8;
private class Segment {
private class Segment
{
/** Segment start/end */
public float[] s = new float[6];
/** Distance for pruning. */
public float d;
}
@ -42,67 +42,91 @@ public class LocalBoundary {
List<Segment> m_segs = new List<Segment>();
List<long> m_polys = new List<long>();
public LocalBoundary() {
public LocalBoundary()
{
m_center[0] = m_center[1] = m_center[2] = float.MaxValue;
}
public void reset() {
public void reset()
{
m_center[0] = m_center[1] = m_center[2] = float.MaxValue;
m_polys.Clear();
m_segs.Clear();
}
protected void addSegment(float dist, float[] s) {
protected void addSegment(float dist, float[] s)
{
// Insert neighbour based on the distance.
Segment seg = new Segment();
Array.Copy(s, seg.s, 6);
seg.d = dist;
if (0 == m_segs.Count) {
if (0 == m_segs.Count)
{
m_segs.Add(seg);
} else if (dist >= m_segs[m_segs.Count - 1].d) {
if (m_segs.Count >= MAX_LOCAL_SEGS) {
}
else if (dist >= m_segs[m_segs.Count - 1].d)
{
if (m_segs.Count >= MAX_LOCAL_SEGS)
{
return;
}
m_segs.Add(seg);
} else {
}
else
{
// Insert inbetween.
int i;
for (i = 0; i < m_segs.Count; ++i) {
if (dist <= m_segs[i].d) {
for (i = 0; i < m_segs.Count; ++i)
{
if (dist <= m_segs[i].d)
{
break;
}
}
m_segs.Insert(i, seg);
}
while (m_segs.Count > MAX_LOCAL_SEGS) {
while (m_segs.Count > MAX_LOCAL_SEGS)
{
m_segs.RemoveAt(m_segs.Count - 1);
}
}
public void update(long refs, float[] pos, float collisionQueryRange, NavMeshQuery navquery, QueryFilter filter) {
if (refs == 0) {
public void update(long refs, float[] pos, float collisionQueryRange, NavMeshQuery navquery, QueryFilter filter)
{
if (refs == 0)
{
reset();
return;
}
vCopy(m_center, pos);
// First query non-overlapping polygons.
Result<FindLocalNeighbourhoodResult> res = navquery.findLocalNeighbourhood(refs, pos, collisionQueryRange,
filter);
if (res.succeeded()) {
if (res.succeeded())
{
m_polys = res.result.getRefs();
m_segs.Clear();
// Secondly, store all polygon edges.
for (int j = 0; j < m_polys.Count; ++j) {
for (int j = 0; j < m_polys.Count; ++j)
{
Result<GetPolyWallSegmentsResult> result = navquery.getPolyWallSegments(m_polys[j], false, filter);
if (result.succeeded()) {
if (result.succeeded())
{
GetPolyWallSegmentsResult gpws = result.result;
for (int k = 0; k < gpws.getSegmentRefs().Count; ++k) {
for (int k = 0; k < gpws.getSegmentRefs().Count; ++k)
{
float[] s = gpws.getSegmentVerts()[k];
// Skip too distant segments.
Tuple<float, float> distseg = distancePtSegSqr2D(pos, s, 0, 3);
if (distseg.Item1 > sqr(collisionQueryRange)) {
if (distseg.Item1 > sqr(collisionQueryRange))
{
continue;
}
addSegment(distseg.Item1, s);
}
}
@ -110,14 +134,18 @@ public class LocalBoundary {
}
}
public bool isValid(NavMeshQuery navquery, QueryFilter filter) {
if (m_polys.Count == 0) {
public bool isValid(NavMeshQuery navquery, QueryFilter filter)
{
if (m_polys.Count == 0)
{
return false;
}
// Check that all polygons still pass query filter.
foreach (long refs in m_polys) {
if (!navquery.isValidPolyRef(refs, filter)) {
foreach (long refs in m_polys)
{
if (!navquery.isValidPolyRef(refs, filter))
{
return false;
}
}
@ -125,17 +153,19 @@ public class LocalBoundary {
return true;
}
public float[] getCenter() {
public float[] getCenter()
{
return m_center;
}
public float[] getSegment(int j) {
public float[] getSegment(int j)
{
return m_segs[j].s;
}
public int getSegmentCount() {
public int getSegmentCount()
{
return m_segs.Count;
}
}
}

View File

@ -23,51 +23,70 @@ using DotRecast.Detour.Crowd.Tracking;
namespace DotRecast.Detour.Crowd
{
using static DetourCommon;
public class ObstacleAvoidanceQuery {
public class ObstacleAvoidanceQuery
{
public const int DT_MAX_PATTERN_DIVS = 32;
public const int DT_MAX_PATTERN_DIVS = 32; /// < Max numver of adaptive divs.
public const int DT_MAX_PATTERN_RINGS = 4; /// < Max number of adaptive rings.
/// < Max numver of adaptive divs.
public const int DT_MAX_PATTERN_RINGS = 4;
public class ObstacleCircle {
/// < Max number of adaptive rings.
public class ObstacleCircle
{
/** Position of the obstacle */
public readonly float[] p = new float[3];
/** Velocity of the obstacle */
public readonly float[] vel = new float[3];
/** Velocity of the obstacle */
public readonly float[] dvel = new float[3];
/** Radius of the obstacle */
public float rad;
/** Use for side selection during sampling. */
public readonly float[] dp = new float[3];
/** Use for side selection during sampling. */
public readonly float[] np = new float[3];
}
public class ObstacleSegment {
public class ObstacleSegment
{
/** End points of the obstacle segment */
public readonly float[] p = new float[3];
/** End points of the obstacle segment */
public readonly float[] q = new float[3];
public bool touch;
}
public class ObstacleAvoidanceParams {
public class ObstacleAvoidanceParams
{
public float velBias;
public float weightDesVel;
public float weightCurVel;
public float weightSide;
public float weightToi;
public float horizTime;
public int gridSize; /// < grid
public int adaptiveDivs; /// < adaptive
public int adaptiveRings; /// < adaptive
public int adaptiveDepth; /// < adaptive
public int gridSize;
public ObstacleAvoidanceParams() {
/// < grid
public int adaptiveDivs;
/// < adaptive
public int adaptiveRings;
/// < adaptive
public int adaptiveDepth;
/// < adaptive
public ObstacleAvoidanceParams()
{
velBias = 0.4f;
weightDesVel = 2.0f;
weightCurVel = 0.75f;
@ -80,7 +99,8 @@ public class ObstacleAvoidanceQuery {
adaptiveDepth = 5;
}
public ObstacleAvoidanceParams(ObstacleAvoidanceParams option) {
public ObstacleAvoidanceParams(ObstacleAvoidanceParams option)
{
velBias = option.velBias;
weightDesVel = option.weightDesVel;
weightCurVel = option.weightCurVel;
@ -107,27 +127,33 @@ public class ObstacleAvoidanceQuery {
private readonly ObstacleSegment[] m_segments;
private int m_nsegments;
public ObstacleAvoidanceQuery(int maxCircles, int maxSegments) {
public ObstacleAvoidanceQuery(int maxCircles, int maxSegments)
{
m_maxCircles = maxCircles;
m_ncircles = 0;
m_circles = new ObstacleCircle[m_maxCircles];
for (int i = 0; i < m_maxCircles; i++) {
for (int i = 0; i < m_maxCircles; i++)
{
m_circles[i] = new ObstacleCircle();
}
m_maxSegments = maxSegments;
m_nsegments = 0;
m_segments = new ObstacleSegment[m_maxSegments];
for (int i = 0; i < m_maxSegments; i++) {
for (int i = 0; i < m_maxSegments; i++)
{
m_segments[i] = new ObstacleSegment();
}
}
public void reset() {
public void reset()
{
m_ncircles = 0;
m_nsegments = 0;
}
public void addCircle(float[] pos, float rad, float[] vel, float[] dvel) {
public void addCircle(float[] pos, float rad, float[] vel, float[] dvel)
{
if (m_ncircles >= m_maxCircles)
return;
@ -138,7 +164,8 @@ public class ObstacleAvoidanceQuery {
vCopy(cir.dvel, dvel);
}
public void addSegment(float[] p, float[] q) {
public void addSegment(float[] p, float[] q)
{
if (m_nsegments >= m_maxSegments)
return;
ObstacleSegment seg = m_segments[m_nsegments++];
@ -146,25 +173,31 @@ public class ObstacleAvoidanceQuery {
vCopy(seg.q, q);
}
public int getObstacleCircleCount() {
public int getObstacleCircleCount()
{
return m_ncircles;
}
public ObstacleCircle getObstacleCircle(int i) {
public ObstacleCircle getObstacleCircle(int i)
{
return m_circles[i];
}
public int getObstacleSegmentCount() {
public int getObstacleSegmentCount()
{
return m_nsegments;
}
public ObstacleSegment getObstacleSegment(int i) {
public ObstacleSegment getObstacleSegment(int i)
{
return m_segments[i];
}
private void prepare(float[] pos, float[] dvel) {
private void prepare(float[] pos, float[] dvel)
{
// Prepare obstacles
for (int i = 0; i < m_ncircles; ++i) {
for (int i = 0; i < m_ncircles; ++i)
{
ObstacleCircle cir = m_circles[i];
// Side
@ -178,16 +211,20 @@ public class ObstacleAvoidanceQuery {
dv = vSub(cir.dvel, dvel);
float a = triArea2D(orig, cir.dp, dv);
if (a < 0.01f) {
if (a < 0.01f)
{
cir.np[0] = -cir.dp[2];
cir.np[2] = cir.dp[0];
} else {
}
else
{
cir.np[0] = cir.dp[2];
cir.np[2] = -cir.dp[0];
}
}
for (int i = 0; i < m_nsegments; ++i) {
for (int i = 0; i < m_nsegments; ++i)
{
ObstacleSegment seg = m_segments[i];
// Precalc if the agent is really close to the segment.
@ -197,7 +234,8 @@ public class ObstacleAvoidanceQuery {
}
}
SweepCircleCircleResult sweepCircleCircle(float[] c0, float r0, float[] v, float[] c1, float r1) {
SweepCircleCircleResult sweepCircleCircle(float[] c0, float r0, float[] v, float[] c1, float r1)
{
const float EPS = 0.0001f;
float[] s = vSub(c1, c0);
float r = r0 + r1;
@ -216,7 +254,8 @@ public class ObstacleAvoidanceQuery {
return new SweepCircleCircleResult(true, (b - rd) * a, (b + rd) * a);
}
Tuple<bool, float> isectRaySeg(float[] ap, float[] u, float[] bp, float[] bq) {
Tuple<bool, float> isectRaySeg(float[] ap, float[] u, float[] bp, float[] bq)
{
float[] v = vSub(bq, bp);
float[] w = vSub(ap, bp);
float d = vPerp2D(u, v);
@ -243,7 +282,8 @@ public class ObstacleAvoidanceQuery {
* threshold penalty for early out
*/
private float processSample(float[] vcand, float cs, float[] pos, float rad, float[] vel, float[] dvel,
float minPenalty, ObstacleAvoidanceDebugData debug) {
float minPenalty, ObstacleAvoidanceDebugData debug)
{
// penalty for straying away from the desired and current velocities
float vpen = m_params.weightDesVel * (vDist2D(vcand, dvel) * m_invVmax);
float vcpen = m_params.weightCurVel * (vDist2D(vcand, vel) * m_invVmax);
@ -260,7 +300,8 @@ public class ObstacleAvoidanceQuery {
float side = 0;
int nside = 0;
for (int i = 0; i < m_ncircles; ++i) {
for (int i = 0; i < m_ncircles; ++i)
{
ObstacleCircle cir = m_circles[i];
// RVO
@ -278,14 +319,17 @@ public class ObstacleAvoidanceQuery {
float htmin = sres.htmin, htmax = sres.htmax;
// Handle overlapping obstacles.
if (htmin < 0.0f && htmax > 0.0f) {
if (htmin < 0.0f && htmax > 0.0f)
{
// Avoid more when overlapped.
htmin = -htmin * 0.5f;
}
if (htmin >= 0.0f) {
if (htmin >= 0.0f)
{
// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
if (htmin < tmin) {
if (htmin < tmin)
{
tmin = htmin;
if (tmin < tThresold)
return minPenalty;
@ -293,11 +337,13 @@ public class ObstacleAvoidanceQuery {
}
}
for (int i = 0; i < m_nsegments; ++i) {
for (int i = 0; i < m_nsegments; ++i)
{
ObstacleSegment seg = m_segments[i];
float htmin = 0;
if (seg.touch) {
if (seg.touch)
{
// Special case when the agent is very close to the segment.
float[] sdir = vSub(seg.q, seg.p);
float[] snorm = new float[3];
@ -308,7 +354,9 @@ public class ObstacleAvoidanceQuery {
continue;
// Else immediate collision.
htmin = 0.0f;
} else {
}
else
{
Tuple<bool, float> ires = isectRaySeg(pos, vcand, seg.p, seg.q);
if (!ires.Item1)
continue;
@ -319,7 +367,8 @@ public class ObstacleAvoidanceQuery {
htmin *= 2.0f;
// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
if (htmin < tmin) {
if (htmin < tmin)
{
tmin = htmin;
if (tmin < tThresold)
return minPenalty;
@ -342,7 +391,8 @@ public class ObstacleAvoidanceQuery {
}
public Tuple<int, float[]> sampleVelocityGrid(float[] pos, float rad, float vmax, float[] vel, float[] dvel,
ObstacleAvoidanceParams option, ObstacleAvoidanceDebugData debug) {
ObstacleAvoidanceParams option, ObstacleAvoidanceDebugData debug)
{
prepare(pos, dvel);
m_params = option;
m_invHorizTime = 1.0f / m_params.horizTime;
@ -363,8 +413,10 @@ public class ObstacleAvoidanceQuery {
float minPenalty = float.MaxValue;
int ns = 0;
for (int y = 0; y < m_params.gridSize; ++y) {
for (int x = 0; x < m_params.gridSize; ++x) {
for (int y = 0; y < m_params.gridSize; ++y)
{
for (int x = 0; x < m_params.gridSize; ++x)
{
float[] vcand = new float[3];
vSet(vcand, cvx + x * cs - half, 0f, cvz + y * cs - half);
@ -373,7 +425,8 @@ public class ObstacleAvoidanceQuery {
float penalty = processSample(vcand, cs, pos, rad, vel, dvel, minPenalty, debug);
ns++;
if (penalty < minPenalty) {
if (penalty < minPenalty)
{
minPenalty = penalty;
vCopy(nvel, vcand);
}
@ -384,7 +437,8 @@ public class ObstacleAvoidanceQuery {
}
// vector normalization that ignores the y-component.
void dtNormalize2D(float[] v) {
void dtNormalize2D(float[] v)
{
float d = (float)Math.Sqrt(v[0] * v[0] + v[2] * v[2]);
if (d == 0)
return;
@ -394,7 +448,8 @@ public class ObstacleAvoidanceQuery {
}
// vector normalization that ignores the y-component.
float[] dtRotate2D(float[] v, float ang) {
float[] dtRotate2D(float[] v, float ang)
{
float[] dest = new float[3];
float c = (float)Math.Cos(ang);
float s = (float)Math.Sin(ang);
@ -407,7 +462,8 @@ public class ObstacleAvoidanceQuery {
static readonly float DT_PI = 3.14159265f;
public Tuple<int, float[]> sampleVelocityAdaptive(float[] pos, float rad, float vmax, float[] vel,
float[] dvel, ObstacleAvoidanceParams option, ObstacleAvoidanceDebugData debug) {
float[] dvel, ObstacleAvoidanceParams option, ObstacleAvoidanceDebugData debug)
{
prepare(pos, dvel);
m_params = option;
m_invHorizTime = 1.0f / m_params.horizTime;
@ -448,7 +504,8 @@ public class ObstacleAvoidanceQuery {
pat[npat * 2 + 1] = 0;
npat++;
for (int j = 0; j < nr; ++j) {
for (int j = 0; j < nr; ++j)
{
float r = (float)(nr - j) / (float)nr;
pat[npat * 2 + 0] = ddir[(j % 2) * 3] * r;
pat[npat * 2 + 1] = ddir[(j % 2) * 3 + 2] * r;
@ -456,7 +513,8 @@ public class ObstacleAvoidanceQuery {
int last2 = last1;
npat++;
for (int i = 1; i < nd - 1; i += 2) {
for (int i = 1; i < nd - 1; i += 2)
{
// get next point on the "right" (rotate CW)
pat[npat * 2 + 0] = pat[last1] * ca + pat[last1 + 1] * sa;
pat[npat * 2 + 1] = -pat[last1] * sa + pat[last1 + 1] * ca;
@ -469,7 +527,8 @@ public class ObstacleAvoidanceQuery {
npat += 2;
}
if ((nd & 1) == 0) {
if ((nd & 1) == 0)
{
pat[npat * 2 + 2] = pat[last2] * ca - pat[last2 + 1] * sa;
pat[npat * 2 + 3] = pat[last2] * sa + pat[last2 + 1] * ca;
npat++;
@ -481,12 +540,14 @@ public class ObstacleAvoidanceQuery {
float[] res = new float[3];
vSet(res, dvel[0] * m_params.velBias, 0, dvel[2] * m_params.velBias);
int ns = 0;
for (int k = 0; k < depth; ++k) {
for (int k = 0; k < depth; ++k)
{
float minPenalty = float.MaxValue;
float[] bvel = new float[3];
vSet(bvel, 0, 0, 0);
for (int i = 0; i < npat; ++i) {
for (int i = 0; i < npat; ++i)
{
float[] vcand = new float[3];
vSet(vcand, res[0] + pat[i * 2 + 0] * cr, 0f, res[2] + pat[i * 2 + 1] * cr);
if (sqr(vcand[0]) + sqr(vcand[2]) > sqr(vmax + 0.001f))
@ -494,7 +555,8 @@ public class ObstacleAvoidanceQuery {
float penalty = processSample(vcand, cr / 10, pos, rad, vel, dvel, minPenalty, debug);
ns++;
if (penalty < minPenalty) {
if (penalty < minPenalty)
{
minPenalty = penalty;
vCopy(bvel, vcand);
}
@ -504,10 +566,10 @@ public class ObstacleAvoidanceQuery {
cr *= 0.5f;
}
vCopy(nvel, res);
return Tuple.Create(ns, nvel);
}
}
}

View File

@ -23,8 +23,6 @@ using System.Collections.Generic;
namespace DotRecast.Detour.Crowd
{
using static DetourCommon;
/**
@ -64,33 +62,40 @@ using static DetourCommon;
* replanning may be needed. E.g. If you move the target, check #getLastPoly() to see if it is the expected polygon.
*
*/
public class PathCorridor {
public class PathCorridor
{
private readonly float[] m_pos = new float[3];
private readonly float[] m_target = new float[3];
private List<long> m_path;
protected List<long> mergeCorridorStartMoved(List<long> path, List<long> visited) {
protected List<long> mergeCorridorStartMoved(List<long> path, List<long> visited)
{
int furthestPath = -1;
int furthestVisited = -1;
// Find furthest common polygon.
for (int i = path.Count - 1; i >= 0; --i) {
for (int i = path.Count - 1; i >= 0; --i)
{
bool found = false;
for (int j = visited.Count - 1; j >= 0; --j) {
if (path[i] == visited[j]) {
for (int j = visited.Count - 1; j >= 0; --j)
{
if (path[i] == visited[j])
{
furthestPath = i;
furthestVisited = j;
found = true;
}
}
if (found) {
if (found)
{
break;
}
}
// If no intersection found just return current path.
if (furthestPath == -1 || furthestVisited == -1) {
if (furthestPath == -1 || furthestVisited == -1)
{
return path;
}
@ -99,34 +104,43 @@ public class PathCorridor {
// Adjust beginning of the buffer to include the visited.
List<long> result = new List<long>();
// Store visited
for (int i = visited.Count - 1; i > furthestVisited; --i) {
for (int i = visited.Count - 1; i > furthestVisited; --i)
{
result.Add(visited[i]);
}
result.AddRange(path.GetRange(furthestPath, path.Count - furthestPath));
return result;
}
protected List<long> mergeCorridorEndMoved(List<long> path, List<long> visited) {
protected List<long> mergeCorridorEndMoved(List<long> path, List<long> visited)
{
int furthestPath = -1;
int furthestVisited = -1;
// Find furthest common polygon.
for (int i = 0; i < path.Count; ++i) {
for (int i = 0; i < path.Count; ++i)
{
bool found = false;
for (int j = visited.Count - 1; j >= 0; --j) {
if (path[i] == visited[j]) {
for (int j = visited.Count - 1; j >= 0; --j)
{
if (path[i] == visited[j])
{
furthestPath = i;
furthestVisited = j;
found = true;
}
}
if (found) {
if (found)
{
break;
}
}
// If no intersection found just return current path.
if (furthestPath == -1 || furthestVisited == -1) {
if (furthestPath == -1 || furthestVisited == -1)
{
return path;
}
@ -136,28 +150,34 @@ public class PathCorridor {
return result;
}
protected List<long> mergeCorridorStartShortcut(List<long> path, List<long> visited) {
protected List<long> mergeCorridorStartShortcut(List<long> path, List<long> visited)
{
int furthestPath = -1;
int furthestVisited = -1;
// Find furthest common polygon.
for (int i = path.Count - 1; i >= 0; --i) {
for (int i = path.Count - 1; i >= 0; --i)
{
bool found = false;
for (int j = visited.Count - 1; j >= 0; --j) {
if (path[i] == visited[j]) {
for (int j = visited.Count - 1; j >= 0; --j)
{
if (path[i] == visited[j])
{
furthestPath = i;
furthestVisited = j;
found = true;
}
}
if (found) {
if (found)
{
break;
}
}
// If no intersection found just return current path.
if (furthestPath == -1 || furthestVisited <= 0) {
if (furthestPath == -1 || furthestVisited <= 0)
{
return path;
}
@ -172,7 +192,8 @@ public class PathCorridor {
/**
* Allocates the corridor's path buffer.
*/
public PathCorridor() {
public PathCorridor()
{
m_path = new List<long>();
}
@ -184,7 +205,8 @@ public class PathCorridor {
* @param pos
* The new position in the corridor. [(x, y, z)]
*/
public void reset(long refs, float[] pos) {
public void reset(long refs, float[] pos)
{
m_path.Clear();
m_path.Add(refs);
vCopy(m_pos, pos);
@ -210,31 +232,41 @@ public class PathCorridor {
* @param[in] navquery The query object used to build the corridor.
* @return Corners
*/
public List<StraightPathItem> findCorners(int maxCorners, NavMeshQuery navquery, QueryFilter filter) {
public List<StraightPathItem> findCorners(int maxCorners, NavMeshQuery navquery, QueryFilter filter)
{
List<StraightPathItem> path = new List<StraightPathItem>();
Result<List<StraightPathItem>> result = navquery.findStraightPath(m_pos, m_target, m_path, maxCorners, 0);
if (result.succeeded()) {
if (result.succeeded())
{
path = result.result;
// Prune points in the beginning of the path which are too close.
int start = 0;
foreach (StraightPathItem spi in path) {
foreach (StraightPathItem spi in path)
{
if ((spi.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0
|| vDist2DSqr(spi.getPos(), m_pos) > MIN_TARGET_DIST) {
|| vDist2DSqr(spi.getPos(), m_pos) > MIN_TARGET_DIST)
{
break;
}
start++;
}
int end = path.Count;
// Prune points after an off-mesh connection.
for (int i = start; i < path.Count; i++) {
for (int i = start; i < path.Count; i++)
{
StraightPathItem spi = path[i];
if ((spi.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) {
if ((spi.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
{
end = i + 1;
break;
}
}
path = path.GetRange(start, end - start);
}
return path;
}
@ -265,14 +297,15 @@ public class PathCorridor {
* @param filter
* The filter to apply to the operation.
*/
public void optimizePathVisibility(float[] next, float pathOptimizationRange, NavMeshQuery navquery,
QueryFilter filter) {
QueryFilter filter)
{
// Clamp the ray to max distance.
float dist = vDist2D(m_pos, next);
// If too close to the goal, do not try to optimize.
if (dist < 0.01f) {
if (dist < 0.01f)
{
return;
}
@ -285,8 +318,10 @@ public class PathCorridor {
float[] goal = vMad(m_pos, delta, pathOptimizationRange / dist);
Result<RaycastHit> rc = navquery.raycast(m_path[0], m_pos, goal, filter, 0, 0);
if (rc.succeeded()) {
if (rc.result.path.Count > 1 && rc.result.t > 0.99f) {
if (rc.succeeded())
{
if (rc.result.path.Count > 1 && rc.result.t > 0.99f)
{
m_path = mergeCorridorStartShortcut(m_path, rc.result.path);
}
}
@ -308,8 +343,10 @@ public class PathCorridor {
* The filter to apply to the operation.
*
*/
public bool optimizePathTopology(NavMeshQuery navquery, QueryFilter filter, int maxIterations) {
if (m_path.Count < 3) {
public bool optimizePathTopology(NavMeshQuery navquery, QueryFilter filter, int maxIterations)
{
if (m_path.Count < 3)
{
return false;
}
@ -317,7 +354,8 @@ public class PathCorridor {
navquery.updateSlicedFindPath(maxIterations);
Result<List<long>> fpr = navquery.finalizeSlicedFindPathPartial(m_path);
if (fpr.succeeded() && fpr.result.Count > 0) {
if (fpr.succeeded() && fpr.result.Count > 0)
{
m_path = mergeCorridorStartShortcut(m_path, fpr.result);
return true;
}
@ -326,16 +364,20 @@ public class PathCorridor {
}
public bool moveOverOffmeshConnection(long offMeshConRef, long[] refs, float[] start, float[] end,
NavMeshQuery navquery) {
NavMeshQuery navquery)
{
// Advance the path up to and over the off-mesh connection.
long prevRef = 0, polyRef = m_path[0];
int npos = 0;
while (npos < m_path.Count && polyRef != offMeshConRef) {
while (npos < m_path.Count && polyRef != offMeshConRef)
{
prevRef = polyRef;
polyRef = m_path[npos];
npos++;
}
if (npos == m_path.Count) {
if (npos == m_path.Count)
{
// Could not find offMeshConRef
return false;
}
@ -347,12 +389,14 @@ public class PathCorridor {
NavMesh nav = navquery.getAttachedNavMesh();
Result<Tuple<float[], float[]>> startEnd = nav.getOffMeshConnectionPolyEndPoints(refs[0], refs[1]);
if (startEnd.succeeded()) {
if (startEnd.succeeded())
{
vCopy(m_pos, startEnd.result.Item2);
vCopy(start, startEnd.result.Item1);
vCopy(end, startEnd.result.Item2);
return true;
}
return false;
}
@ -379,19 +423,24 @@ public class PathCorridor {
* @param filter
* The filter to apply to the operation.
*/
public bool movePosition(float[] npos, NavMeshQuery navquery, QueryFilter filter) {
public bool movePosition(float[] npos, NavMeshQuery navquery, QueryFilter filter)
{
// Move along navmesh and update new position.
Result<MoveAlongSurfaceResult> masResult = navquery.moveAlongSurface(m_path[0], m_pos, npos, filter);
if (masResult.succeeded()) {
if (masResult.succeeded())
{
m_path = mergeCorridorStartMoved(m_path, masResult.result.getVisited());
// Adjust the position to stay on top of the navmesh.
vCopy(m_pos, masResult.result.getResultPos());
Result<float> hr = navquery.getPolyHeight(m_path[0], masResult.result.getResultPos());
if (hr.succeeded()) {
if (hr.succeeded())
{
m_pos[1] = hr.result;
}
return true;
}
return false;
}
@ -412,11 +461,13 @@ public class PathCorridor {
* @param filter
* The filter to apply to the operation.
*/
public bool moveTargetPosition(float[] npos, NavMeshQuery navquery, QueryFilter filter) {
public bool moveTargetPosition(float[] npos, NavMeshQuery navquery, QueryFilter filter)
{
// Move along navmesh and update new position.
Result<MoveAlongSurfaceResult> masResult = navquery.moveAlongSurface(m_path[m_path.Count - 1], m_target,
npos, filter);
if (masResult.succeeded()) {
if (masResult.succeeded())
{
m_path = mergeCorridorEndMoved(m_path, masResult.result.getVisited());
// TODO: should we do that?
// Adjust the position to stay on top of the navmesh.
@ -427,6 +478,7 @@ public class PathCorridor {
vCopy(m_target, masResult.result.getResultPos());
return true;
}
return false;
}
@ -440,47 +492,57 @@ public class PathCorridor {
* @param path
* The path corridor.
*/
public void setCorridor(float[] target, List<long> path) {
public void setCorridor(float[] target, List<long> path)
{
vCopy(m_target, target);
m_path = new List<long>(path);
}
public void fixPathStart(long safeRef, float[] safePos) {
public void fixPathStart(long safeRef, float[] safePos)
{
vCopy(m_pos, safePos);
if (m_path.Count < 3 && m_path.Count > 0) {
if (m_path.Count < 3 && m_path.Count > 0)
{
long p = m_path[m_path.Count - 1];
m_path.Clear();
m_path.Add(safeRef);
m_path.Add(0L);
m_path.Add(p);
} else {
}
else
{
m_path.Clear();
m_path.Add(safeRef);
m_path.Add(0L);
}
}
public void trimInvalidPath(long safeRef, float[] safePos, NavMeshQuery navquery, QueryFilter filter) {
public void trimInvalidPath(long safeRef, float[] safePos, NavMeshQuery navquery, QueryFilter filter)
{
// Keep valid path as far as possible.
int n = 0;
while (n < m_path.Count && navquery.isValidPolyRef(m_path[n], filter)) {
while (n < m_path.Count && navquery.isValidPolyRef(m_path[n], filter))
{
n++;
}
if (n == 0) {
if (n == 0)
{
// The first polyref is bad, use current safe values.
vCopy(m_pos, safePos);
m_path.Clear();
m_path.Add(safeRef);
} else if (n < m_path.Count) {
}
else if (n < m_path.Count)
{
m_path = m_path.GetRange(0, n);
// The path is partially usable.
}
// Clamp target pos to last poly
Result<float[]> result = navquery.closestPointOnPolyBoundary(m_path[m_path.Count - 1], m_target);
if (result.succeeded()) {
if (result.succeeded())
{
vCopy(m_target, result.result);
}
}
@ -498,11 +560,14 @@ public class PathCorridor {
* The filter to apply to the operation.
* @return
*/
public bool isValid(int maxLookAhead, NavMeshQuery navquery, QueryFilter filter) {
public bool isValid(int maxLookAhead, NavMeshQuery navquery, QueryFilter filter)
{
// Check that all polygons still pass query filter.
int n = Math.Min(m_path.Count, maxLookAhead);
for (int i = 0; i < n; ++i) {
if (!navquery.isValidPolyRef(m_path[i], filter)) {
for (int i = 0; i < n; ++i)
{
if (!navquery.isValidPolyRef(m_path[i], filter))
{
return false;
}
}
@ -515,7 +580,8 @@ public class PathCorridor {
*
* @return The current position within the corridor.
*/
public float[] getPos() {
public float[] getPos()
{
return m_pos;
}
@ -524,7 +590,8 @@ public class PathCorridor {
*
* @return The current target within the corridor.
*/
public float[] getTarget() {
public float[] getTarget()
{
return m_target;
}
@ -533,7 +600,8 @@ public class PathCorridor {
*
* @return The polygon reference id of the first polygon in the corridor. (Or zero if there is no path.)
*/
public long getFirstPoly() {
public long getFirstPoly()
{
return 0 == m_path.Count ? 0 : m_path[0];
}
@ -542,14 +610,16 @@ public class PathCorridor {
*
* @return The polygon reference id of the last polygon in the corridor. (Or zero if there is no path.)
*/
public long getLastPoly() {
public long getLastPoly()
{
return 0 == m_path.Count ? 0 : m_path[m_path.Count - 1];
}
/**
* The corridor's path.
*/
public List<long> getPath() {
public List<long> getPath()
{
return m_path;
}
@ -558,9 +628,9 @@ public class PathCorridor {
*
* @return The number of polygons in the current corridor path.
*/
public int getPathCount() {
public int getPathCount()
{
return m_path.Count;
}
}
}

View File

@ -17,20 +17,22 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.Crowd
{
public class PathQuery {
public class PathQuery
{
/// Path find start and end location.
public float[] startPos = new float[3];
public float[] endPos = new float[3];
public long startRef;
public long endRef;
public QueryFilter filter; /// < TODO: This is potentially dangerous!
public QueryFilter filter;
/// < TODO: This is potentially dangerous!
public readonly PathQueryResult result = new PathQueryResult();
public NavMeshQuery navQuery;
}
}

View File

@ -20,11 +20,9 @@ using System.Collections.Generic;
namespace DotRecast.Detour.Crowd
{
public class PathQueryResult {
public class PathQueryResult
{
public Status status;
public List<long> path = new List<long>();
}
}

View File

@ -22,55 +22,67 @@ using System.Collections.Generic;
namespace DotRecast.Detour.Crowd
{
using static DetourCommon;
public class PathQueue {
public class PathQueue
{
private readonly CrowdConfig config;
private readonly LinkedList<PathQuery> queue = new LinkedList<PathQuery>();
public PathQueue(CrowdConfig config) {
public PathQueue(CrowdConfig config)
{
this.config = config;
}
public void update(NavMesh navMesh) {
public void update(NavMesh navMesh)
{
// Update path request until there is nothing to update or up to maxIters pathfinder iterations has been
// consumed.
int iterCount = config.maxFindPathIterations;
while (iterCount > 0) {
while (iterCount > 0)
{
PathQuery? q = queue.First?.Value;
if (q == null) {
if (q == null)
{
break;
}
// Handle query start.
if (q.result.status == null) {
if (q.result.status == null)
{
q.navQuery = new NavMeshQuery(navMesh);
q.result.status = q.navQuery.initSlicedFindPath(q.startRef, q.endRef, q.startPos, q.endPos, q.filter, 0);
}
// Handle query in progress.
if (q.result.status.isInProgress()) {
if (q.result.status.isInProgress())
{
Result<int> res = q.navQuery.updateSlicedFindPath(iterCount);
q.result.status = res.status;
iterCount -= res.result;
}
if (q.result.status.isSuccess()) {
if (q.result.status.isSuccess())
{
Result<List<long>> path = q.navQuery.finalizeSlicedFindPath();
q.result.status = path.status;
q.result.path = path.result;
}
if (!(q.result.status.isFailed() || q.result.status.isSuccess())) {
if (!(q.result.status.isFailed() || q.result.status.isSuccess()))
{
queue.AddFirst(q);
}
}
}
public PathQueryResult request(long startRef, long endRef, float[] startPos, float[] endPos, QueryFilter filter) {
if (queue.Count >= config.pathQueueSize) {
public PathQueryResult request(long startRef, long endRef, float[] startPos, float[] endPos, QueryFilter filter)
{
if (queue.Count >= config.pathQueueSize)
{
return null;
}
PathQuery q = new PathQuery();
vCopy(q.startPos, startPos);
q.startRef = startRef;
@ -81,7 +93,5 @@ public class PathQueue {
queue.AddLast(q);
return q.result;
}
}
}

View File

@ -24,53 +24,62 @@ using System.Linq;
namespace DotRecast.Detour.Crowd
{
public class ProximityGrid {
public class ProximityGrid
{
private readonly float m_cellSize;
private readonly float m_invCellSize;
private readonly Dictionary<ItemKey, List<CrowdAgent>> items;
public ProximityGrid(float m_cellSize) {
public ProximityGrid(float m_cellSize)
{
this.m_cellSize = m_cellSize;
m_invCellSize = 1.0f / m_cellSize;
items = new Dictionary<ItemKey, List<CrowdAgent>>();
}
void clear() {
void clear()
{
items.Clear();
}
public void addItem(CrowdAgent agent, float minx, float miny, float maxx, float maxy) {
public void addItem(CrowdAgent agent, float minx, float miny, float maxx, float maxy)
{
int iminx = (int)Math.Floor(minx * m_invCellSize);
int iminy = (int)Math.Floor(miny * m_invCellSize);
int imaxx = (int)Math.Floor(maxx * m_invCellSize);
int imaxy = (int)Math.Floor(maxy * m_invCellSize);
for (int y = iminy; y <= imaxy; ++y) {
for (int x = iminx; x <= imaxx; ++x) {
for (int y = iminy; y <= imaxy; ++y)
{
for (int x = iminx; x <= imaxx; ++x)
{
ItemKey key = new ItemKey(x, y);
if (!items.TryGetValue(key, out var ids)) {
if (!items.TryGetValue(key, out var ids))
{
ids = new List<CrowdAgent>();
items.Add(key, ids);
}
ids.Add(agent);
}
}
}
public HashSet<CrowdAgent> queryItems(float minx, float miny, float maxx, float maxy) {
public HashSet<CrowdAgent> queryItems(float minx, float miny, float maxx, float maxy)
{
int iminx = (int)Math.Floor(minx * m_invCellSize);
int iminy = (int)Math.Floor(miny * m_invCellSize);
int imaxx = (int)Math.Floor(maxx * m_invCellSize);
int imaxy = (int)Math.Floor(maxy * m_invCellSize);
HashSet<CrowdAgent> result = new HashSet<CrowdAgent>();
for (int y = iminy; y <= imaxy; ++y) {
for (int x = iminx; x <= imaxx; ++x) {
for (int y = iminy; y <= imaxy; ++y)
{
for (int x = iminx; x <= imaxx; ++x)
{
ItemKey key = new ItemKey(x, y);
if (items.TryGetValue(key, out var ids)) {
if (items.TryGetValue(key, out var ids))
{
result.UnionWith(ids);
}
}
@ -79,28 +88,32 @@ public class ProximityGrid {
return result;
}
public List<int[]> getItemCounts() {
public List<int[]> getItemCounts()
{
return items
.Where(e => e.Value.Count > 0)
.Select(e => new int[] { e.Key.x, e.Key.y, e.Value.Count })
.ToList();
}
public float getCellSize() {
public float getCellSize()
{
return m_cellSize;
}
private class ItemKey {
private class ItemKey
{
public readonly int x;
public readonly int y;
public ItemKey(int x, int y) {
public ItemKey(int x, int y)
{
this.x = x;
this.y = y;
}
public override int GetHashCode() {
public override int GetHashCode()
{
int prime = 31;
int result = 1;
result = prime * result + x;
@ -108,7 +121,8 @@ public class ProximityGrid {
return result;
}
public override bool Equals(object? obj) {
public override bool Equals(object? obj)
{
if (this == obj)
return true;
@ -127,8 +141,6 @@ public class ProximityGrid {
return true;
}
};
}
}

View File

@ -17,22 +17,20 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.Crowd
{
public class SweepCircleCircleResult {
public class SweepCircleCircleResult
{
public readonly bool intersection;
public readonly float htmin;
public readonly float htmax;
public SweepCircleCircleResult(bool intersection, float htmin, float htmax) {
public SweepCircleCircleResult(bool intersection, float htmin, float htmax)
{
this.intersection = intersection;
this.htmin = htmin;
this.htmax = htmax;
}
}
}

View File

@ -17,17 +17,14 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.Crowd.Tracking
{
public class CrowdAgentDebugInfo {
public class CrowdAgentDebugInfo
{
public CrowdAgent agent;
public float[] optStart = new float[3];
public float[] optEnd = new float[3];
public ObstacleAvoidanceDebugData vod;
}
}

View File

@ -22,11 +22,10 @@ using System;
namespace DotRecast.Detour.Crowd.Tracking
{
using static DetourCommon;
public class ObstacleAvoidanceDebugData {
public class ObstacleAvoidanceDebugData
{
int m_nsamples;
int m_maxSamples;
float[] m_vel;
@ -37,7 +36,8 @@ public class ObstacleAvoidanceDebugData {
float[] m_spen;
float[] m_tpen;
public ObstacleAvoidanceDebugData(int maxSamples) {
public ObstacleAvoidanceDebugData(int maxSamples)
{
m_maxSamples = maxSamples;
m_vel = new float[3 * m_maxSamples];
m_pen = new float[m_maxSamples];
@ -48,25 +48,30 @@ public class ObstacleAvoidanceDebugData {
m_tpen = new float[m_maxSamples];
}
public void reset() {
public void reset()
{
m_nsamples = 0;
}
void normalizeArray(float[] arr, int n) {
void normalizeArray(float[] arr, int n)
{
// Normalize penaly range.
float minPen = float.MaxValue;
float maxPen = -float.MaxValue;
for (int i = 0; i < n; ++i) {
for (int i = 0; i < n; ++i)
{
minPen = Math.Min(minPen, arr[i]);
maxPen = Math.Max(maxPen, arr[i]);
}
float penRange = maxPen - minPen;
float s = penRange > 0.001f ? (1.0f / penRange) : 1;
for (int i = 0; i < n; ++i)
arr[i] = clamp((arr[i] - minPen) * s, 0.0f, 1.0f);
}
public void normalizeSamples() {
public void normalizeSamples()
{
normalizeArray(m_pen, m_nsamples);
normalizeArray(m_vpen, m_nsamples);
normalizeArray(m_vcpen, m_nsamples);
@ -74,7 +79,8 @@ public class ObstacleAvoidanceDebugData {
normalizeArray(m_tpen, m_nsamples);
}
public void addSample(float[] vel, float ssize, float pen, float vpen, float vcpen, float spen, float tpen) {
public void addSample(float[] vel, float ssize, float pen, float vpen, float vcpen, float spen, float tpen)
{
if (m_nsamples >= m_maxSamples)
return;
m_vel[m_nsamples * 3] = vel[0];
@ -89,11 +95,13 @@ public class ObstacleAvoidanceDebugData {
m_nsamples++;
}
public int getSampleCount() {
public int getSampleCount()
{
return m_nsamples;
}
public float[] getSampleVelocity(int i) {
public float[] getSampleVelocity(int i)
{
float[] vel = new float[3];
vel[0] = m_vel[i * 3];
vel[1] = m_vel[i * 3 + 1];
@ -101,27 +109,33 @@ public class ObstacleAvoidanceDebugData {
return vel;
}
public float getSampleSize(int i) {
public float getSampleSize(int i)
{
return m_ssize[i];
}
public float getSamplePenalty(int i) {
public float getSamplePenalty(int i)
{
return m_pen[i];
}
public float getSampleDesiredVelocityPenalty(int i) {
public float getSampleDesiredVelocityPenalty(int i)
{
return m_vpen[i];
}
public float getSampleCurrentVelocityPenalty(int i) {
public float getSampleCurrentVelocityPenalty(int i)
{
return m_vcpen[i];
}
public float getSamplePreferredSidePenalty(int i) {
public float getSamplePreferredSidePenalty(int i)
{
return m_spen[i];
}
public float getSampleCollisionTimePenalty(int i) {
public float getSampleCollisionTimePenalty(int i)
{
return m_tpen[i];
}
}

View File

@ -21,28 +21,27 @@ using DotRecast.Detour.Dynamic.Colliders;
namespace DotRecast.Detour.Dynamic
{
public class AddColliderQueueItem : UpdateQueueItem {
public class AddColliderQueueItem : UpdateQueueItem
{
private readonly long colliderId;
private readonly Collider collider;
private readonly ICollection<DynamicTile> _affectedTiles;
public AddColliderQueueItem(long colliderId, Collider collider, ICollection<DynamicTile> affectedTiles) {
public AddColliderQueueItem(long colliderId, Collider collider, ICollection<DynamicTile> affectedTiles)
{
this.colliderId = colliderId;
this.collider = collider;
_affectedTiles = affectedTiles;
}
public ICollection<DynamicTile> affectedTiles() {
public ICollection<DynamicTile> affectedTiles()
{
return _affectedTiles;
}
public void process(DynamicTile tile) {
public void process(DynamicTile tile)
{
tile.addCollider(colliderId, collider);
}
}
}

View File

@ -20,21 +20,21 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders
{
public abstract class AbstractCollider : Collider {
public abstract class AbstractCollider : Collider
{
protected readonly int area;
protected readonly float flagMergeThreshold;
protected readonly float[] _bounds;
public AbstractCollider(int area, float flagMergeThreshold, float[] bounds) {
public AbstractCollider(int area, float flagMergeThreshold, float[] bounds)
{
this.area = area;
this.flagMergeThreshold = flagMergeThreshold;
this._bounds = bounds;
}
public float[] bounds() {
public float[] bounds()
{
return _bounds;
}
@ -42,7 +42,5 @@ public abstract class AbstractCollider : Collider {
{
///?
}
}
}

View File

@ -21,12 +21,11 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders
{
public class BoxCollider : AbstractCollider {
public class BoxCollider : AbstractCollider
{
private readonly float[] center;
private readonly float[][] halfEdges;
public BoxCollider(float[] center, float[][] halfEdges, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, bounds(center, halfEdges))
{
@ -34,10 +33,15 @@ public class BoxCollider : AbstractCollider {
this.halfEdges = halfEdges;
}
private static float[] bounds(float[] center, float[][] halfEdges) {
float[] bounds = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity };
for (int i = 0; i < 8; ++i) {
private static float[] bounds(float[] center, float[][] halfEdges)
{
float[] bounds = new float[]
{
float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity
};
for (int i = 0; i < 8; ++i)
{
float s0 = (i & 1) != 0 ? 1f : -1f;
float s1 = (i & 2) != 0 ? 1f : -1f;
float s2 = (i & 4) != 0 ? 1f : -1f;
@ -51,15 +55,18 @@ public class BoxCollider : AbstractCollider {
bounds[4] = Math.Max(bounds[4], vy);
bounds[5] = Math.Max(bounds[5], vz);
}
return bounds;
}
public void rasterize(Heightfield hf, Telemetry telemetry) {
public void rasterize(Heightfield hf, Telemetry telemetry)
{
RecastFilledVolumeRasterization.rasterizeBox(hf, center, halfEdges, area, (int)Math.Floor(flagMergeThreshold / hf.ch),
telemetry);
}
public static float[][] getHalfEdges(float[] up, float[] forward, float[] extent) {
public static float[][] getHalfEdges(float[] up, float[] forward, float[] extent)
{
float[][] halfEdges = new float[][] { new float[3], new float[] { up[0], up[1], up[2] }, new float[3] };
RecastVectors.normalize(halfEdges[1]);
RecastVectors.cross(halfEdges[0], up, forward);
@ -77,7 +84,5 @@ public class BoxCollider : AbstractCollider {
halfEdges[2][2] *= extent[2];
return halfEdges;
}
}
}

View File

@ -21,10 +21,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders
{
public class CapsuleCollider : AbstractCollider {
public class CapsuleCollider : AbstractCollider
{
private readonly float[] start;
private readonly float[] end;
private readonly float radius;
@ -37,17 +35,20 @@ public class CapsuleCollider : AbstractCollider {
this.radius = radius;
}
public void rasterize(Heightfield hf, Telemetry telemetry) {
public void rasterize(Heightfield hf, Telemetry telemetry)
{
RecastFilledVolumeRasterization.rasterizeCapsule(hf, start, end, radius, area, (int)Math.Floor(flagMergeThreshold / hf.ch),
telemetry);
}
private static float[] bounds(float[] start, float[] end, float radius) {
return new float[] { Math.Min(start[0], end[0]) - radius, Math.Min(start[1], end[1]) - radius,
private static float[] bounds(float[] start, float[] end, float radius)
{
return new float[]
{
Math.Min(start[0], end[0]) - radius, Math.Min(start[1], end[1]) - radius,
Math.Min(start[2], end[2]) - radius, Math.Max(start[0], end[0]) + radius, Math.Max(start[1], end[1]) + radius,
Math.Max(start[2], end[2]) + radius };
Math.Max(start[2], end[2]) + radius
};
}
}
}

View File

@ -20,12 +20,9 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders
{
public interface Collider {
public interface Collider
{
float[] bounds();
void rasterize(Heightfield hf, Telemetry telemetry);
}
}

View File

@ -23,31 +23,37 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders
{
public class CompositeCollider : Collider {
public class CompositeCollider : Collider
{
private readonly List<Collider> colliders;
private readonly float[] _bounds;
public CompositeCollider(List<Collider> colliders) {
public CompositeCollider(List<Collider> colliders)
{
this.colliders = colliders;
_bounds = bounds(colliders);
}
public CompositeCollider(params Collider[] colliders) {
public CompositeCollider(params Collider[] colliders)
{
this.colliders = colliders.ToList();
_bounds = bounds(this.colliders);
}
public float[] bounds() {
public float[] bounds()
{
return _bounds;
}
private static float[] bounds(List<Collider> colliders) {
float[] bounds = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity };
foreach (Collider collider in colliders) {
private static float[] bounds(List<Collider> colliders)
{
float[] bounds = new float[]
{
float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity
};
foreach (Collider collider in colliders)
{
float[] b = collider.bounds();
bounds[0] = Math.Min(bounds[0], b[0]);
bounds[1] = Math.Min(bounds[1], b[1]);
@ -56,14 +62,14 @@ public class CompositeCollider : Collider {
bounds[4] = Math.Max(bounds[4], b[4]);
bounds[5] = Math.Max(bounds[5], b[5]);
}
return bounds;
}
public void rasterize(Heightfield hf, Telemetry telemetry) {
public void rasterize(Heightfield hf, Telemetry telemetry)
{
foreach (var c in colliders)
c.rasterize(hf, telemetry);
}
}
}

View File

@ -21,30 +21,29 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders
{
public class ConvexTrimeshCollider : AbstractCollider {
public class ConvexTrimeshCollider : AbstractCollider
{
private readonly float[] vertices;
private readonly int[] triangles;
public ConvexTrimeshCollider(float[] vertices, int[] triangles, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, TrimeshCollider.computeBounds(vertices)) {
base(area, flagMergeThreshold, TrimeshCollider.computeBounds(vertices))
{
this.vertices = vertices;
this.triangles = triangles;
}
public ConvexTrimeshCollider(float[] vertices, int[] triangles, float[] bounds, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, bounds) {
base(area, flagMergeThreshold, bounds)
{
this.vertices = vertices;
this.triangles = triangles;
}
public void rasterize(Heightfield hf, Telemetry telemetry) {
public void rasterize(Heightfield hf, Telemetry telemetry)
{
RecastFilledVolumeRasterization.rasterizeConvex(hf, vertices, triangles, area,
(int)Math.Floor(flagMergeThreshold / hf.ch), telemetry);
}
}
}

View File

@ -21,32 +21,34 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders
{
public class CylinderCollider : AbstractCollider {
public class CylinderCollider : AbstractCollider
{
private readonly float[] start;
private readonly float[] end;
private readonly float radius;
public CylinderCollider(float[] start, float[] end, float radius, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, bounds(start, end, radius)) {
base(area, flagMergeThreshold, bounds(start, end, radius))
{
this.start = start;
this.end = end;
this.radius = radius;
}
public void rasterize(Heightfield hf, Telemetry telemetry) {
public void rasterize(Heightfield hf, Telemetry telemetry)
{
RecastFilledVolumeRasterization.rasterizeCylinder(hf, start, end, radius, area, (int)Math.Floor(flagMergeThreshold / hf.ch),
telemetry);
}
private static float[] bounds(float[] start, float[] end, float radius) {
return new float[] { Math.Min(start[0], end[0]) - radius, Math.Min(start[1], end[1]) - radius,
private static float[] bounds(float[] start, float[] end, float radius)
{
return new float[]
{
Math.Min(start[0], end[0]) - radius, Math.Min(start[1], end[1]) - radius,
Math.Min(start[2], end[2]) - radius, Math.Max(start[0], end[0]) + radius, Math.Max(start[1], end[1]) + radius,
Math.Max(start[2], end[2]) + radius };
Math.Max(start[2], end[2]) + radius
};
}
}
}

View File

@ -21,29 +21,31 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders
{
public class SphereCollider : AbstractCollider {
public class SphereCollider : AbstractCollider
{
private readonly float[] center;
private readonly float radius;
public SphereCollider(float[] center, float radius, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, bounds(center, radius)) {
base(area, flagMergeThreshold, bounds(center, radius))
{
this.center = center;
this.radius = radius;
}
public void rasterize(Heightfield hf, Telemetry telemetry) {
public void rasterize(Heightfield hf, Telemetry telemetry)
{
RecastFilledVolumeRasterization.rasterizeSphere(hf, center, radius, area, (int)Math.Floor(flagMergeThreshold / hf.ch),
telemetry);
}
private static float[] bounds(float[] center, float radius) {
return new float[] { center[0] - radius, center[1] - radius, center[2] - radius, center[0] + radius, center[1] + radius,
center[2] + radius };
private static float[] bounds(float[] center, float radius)
{
return new float[]
{
center[0] - radius, center[1] - radius, center[2] - radius, center[0] + radius, center[1] + radius,
center[2] + radius
};
}
}
}

View File

@ -21,28 +21,30 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders
{
public class TrimeshCollider : AbstractCollider {
public class TrimeshCollider : AbstractCollider
{
private readonly float[] vertices;
private readonly int[] triangles;
public TrimeshCollider(float[] vertices, int[] triangles, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, computeBounds(vertices)) {
base(area, flagMergeThreshold, computeBounds(vertices))
{
this.vertices = vertices;
this.triangles = triangles;
}
public TrimeshCollider(float[] vertices, int[] triangles, float[] bounds, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, bounds) {
base(area, flagMergeThreshold, bounds)
{
this.vertices = vertices;
this.triangles = triangles;
}
public static float[] computeBounds(float[] vertices) {
public static float[] computeBounds(float[] vertices)
{
float[] bounds = new float[] { vertices[0], vertices[1], vertices[2], vertices[0], vertices[1], vertices[2] };
for (int i = 3; i < vertices.Length; i += 3) {
for (int i = 3; i < vertices.Length; i += 3)
{
bounds[0] = Math.Min(bounds[0], vertices[i]);
bounds[1] = Math.Min(bounds[1], vertices[i + 1]);
bounds[2] = Math.Min(bounds[2], vertices[i + 2]);
@ -50,16 +52,17 @@ public class TrimeshCollider : AbstractCollider {
bounds[4] = Math.Max(bounds[4], vertices[i + 1]);
bounds[5] = Math.Max(bounds[5], vertices[i + 2]);
}
return bounds;
}
public void rasterize(Heightfield hf, Telemetry telemetry) {
for (int i = 0; i < triangles.Length; i += 3) {
public void rasterize(Heightfield hf, Telemetry telemetry)
{
for (int i = 0; i < triangles.Length; i += 3)
{
RecastRasterization.rasterizeTriangle(hf, vertices, triangles[i], triangles[i + 1], triangles[i + 2], area,
(int)Math.Floor(flagMergeThreshold / hf.ch), telemetry);
}
}
}
}

View File

@ -29,10 +29,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic
{
public class DynamicNavMesh {
public class DynamicNavMesh
{
public const int MAX_VERTS_PER_POLY = 6;
public readonly DynamicNavMeshConfig config;
private readonly RecastBuilder builder;
@ -44,7 +42,8 @@ public class DynamicNavMesh {
private NavMesh _navMesh;
private bool dirty = true;
public DynamicNavMesh(VoxelFile voxelFile) {
public DynamicNavMesh(VoxelFile voxelFile)
{
config = new DynamicNavMeshConfig(voxelFile.useTiles, voxelFile.tileSizeX, voxelFile.tileSizeZ, voxelFile.cellSize);
config.walkableHeight = voxelFile.walkableHeight;
config.walkableRadius = voxelFile.walkableRadius;
@ -67,41 +66,50 @@ public class DynamicNavMesh {
navMeshParams.tileHeight = voxelFile.cellSize * voxelFile.tileSizeZ;
navMeshParams.maxTiles = voxelFile.tiles.Count;
navMeshParams.maxPolys = 0x8000;
foreach (var t in voxelFile.tiles) {
foreach (var t in voxelFile.tiles)
{
_tiles.Add(lookupKey(t.tileX, t.tileZ), new DynamicTile(t));
};
}
;
telemetry = new Telemetry();
}
public NavMesh navMesh() {
public NavMesh navMesh()
{
return _navMesh;
}
/**
* Voxel queries require checkpoints to be enabled in {@link DynamicNavMeshConfig}
*/
public VoxelQuery voxelQuery() {
public VoxelQuery voxelQuery()
{
return new VoxelQuery(navMeshParams.orig, navMeshParams.tileWidth, navMeshParams.tileHeight, lookupHeightfield);
}
private Heightfield lookupHeightfield(int x, int z) {
private Heightfield lookupHeightfield(int x, int z)
{
return getTileAt(x, z)?.checkpoint.heightfield;
}
public long addCollider(Collider collider) {
public long addCollider(Collider collider)
{
long cid = currentColliderId.IncrementAndGet();
updateQueue.Add(new AddColliderQueueItem(cid, collider, getTiles(collider.bounds())));
return cid;
}
public void removeCollider(long colliderId) {
public void removeCollider(long colliderId)
{
updateQueue.Add(new RemoveColliderQueueItem(colliderId, getTilesByCollider(colliderId)));
}
/**
* Perform full build of the nav mesh
*/
public void build() {
public void build()
{
processQueue();
rebuild(_tiles.Values);
}
@ -109,34 +117,42 @@ public class DynamicNavMesh {
/**
* Perform incremental update of the nav mesh
*/
public bool update() {
public bool update()
{
return rebuild(processQueue());
}
private bool rebuild(ICollection<DynamicTile> stream) {
private bool rebuild(ICollection<DynamicTile> stream)
{
foreach (var dynamicTile in stream)
rebuild(dynamicTile);
return updateNavMesh();
}
private HashSet<DynamicTile> processQueue() {
private HashSet<DynamicTile> processQueue()
{
var items = consumeQueue();
foreach (var item in items) {
foreach (var item in items)
{
process(item);
}
return items.SelectMany(i => i.affectedTiles()).ToHashSet();
}
private List<UpdateQueueItem> consumeQueue() {
private List<UpdateQueueItem> consumeQueue()
{
List<UpdateQueueItem> items = new List<UpdateQueueItem>();
while (updateQueue.TryTake(out var item)) {
while (updateQueue.TryTake(out var item))
{
items.Add(item);
}
return items;
}
private void process(UpdateQueueItem item) {
private void process(UpdateQueueItem item)
{
foreach (var tile in item.affectedTiles())
{
item.process(tile);
@ -146,7 +162,8 @@ public class DynamicNavMesh {
/**
* Perform full build concurrently using the given {@link ExecutorService}
*/
public Task<bool> build(TaskFactory executor) {
public Task<bool> build(TaskFactory executor)
{
processQueue();
return rebuild(_tiles.Values, executor);
}
@ -154,7 +171,8 @@ public class DynamicNavMesh {
/**
* Perform incremental update concurrently using the given {@link ExecutorService}
*/
public Task<bool> update(TaskFactory executor) {
public Task<bool> update(TaskFactory executor)
{
return rebuild(processQueue(), executor);
}
@ -164,38 +182,49 @@ public class DynamicNavMesh {
return Task.WhenAll(tasks).ContinueWith(k => updateNavMesh());
}
private ICollection<DynamicTile> getTiles(float[] bounds) {
if (bounds == null) {
private ICollection<DynamicTile> getTiles(float[] bounds)
{
if (bounds == null)
{
return _tiles.Values;
}
int minx = (int)Math.Floor((bounds[0] - navMeshParams.orig[0]) / navMeshParams.tileWidth);
int minz = (int)Math.Floor((bounds[2] - navMeshParams.orig[2]) / navMeshParams.tileHeight);
int maxx = (int)Math.Floor((bounds[3] - navMeshParams.orig[0]) / navMeshParams.tileWidth);
int maxz = (int)Math.Floor((bounds[5] - navMeshParams.orig[2]) / navMeshParams.tileHeight);
List<DynamicTile> tiles = new List<DynamicTile>();
for (int z = minz; z <= maxz; ++z) {
for (int x = minx; x <= maxx; ++x) {
for (int z = minz; z <= maxz; ++z)
{
for (int x = minx; x <= maxx; ++x)
{
DynamicTile tile = getTileAt(x, z);
if (tile != null) {
if (tile != null)
{
tiles.Add(tile);
}
}
}
return tiles;
}
private List<DynamicTile> getTilesByCollider(long cid) {
private List<DynamicTile> getTilesByCollider(long cid)
{
return _tiles.Values.Where(t => t.containsCollider(cid)).ToList();
}
private void rebuild(DynamicTile tile) {
private void rebuild(DynamicTile tile)
{
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.walkableHeight = config.walkableHeight;
dirty = dirty | tile.build(builder, config, telemetry);
}
private bool updateNavMesh() {
if (dirty) {
private bool updateNavMesh()
{
if (dirty)
{
NavMesh navMesh = new NavMesh(navMeshParams, MAX_VERTS_PER_POLY);
foreach (var t in _tiles.Values)
t.addTo(navMesh);
@ -204,27 +233,30 @@ public class DynamicNavMesh {
dirty = false;
return true;
}
return false;
}
private DynamicTile getTileAt(int x, int z) {
private DynamicTile getTileAt(int x, int z)
{
return _tiles.TryGetValue(lookupKey(x, z), out var tile)
? tile
: null;
}
private long lookupKey(long x, long z) {
private long lookupKey(long x, long z)
{
return (z << 32) | x;
}
public List<VoxelTile> voxelTiles() {
public List<VoxelTile> voxelTiles()
{
return _tiles.Values.Select(t => t.voxelTile).ToList();
}
public List<RecastBuilderResult> recastResults() {
public List<RecastBuilderResult> recastResults()
{
return _tiles.Values.Select(t => t.recastResult).ToList();
}
}
}

View File

@ -20,10 +20,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic
{
public class DynamicNavMeshConfig {
public class DynamicNavMeshConfig
{
public readonly bool useTiles;
public readonly int tileSizeX;
public readonly int tileSizeZ;
@ -48,13 +46,12 @@ public class DynamicNavMeshConfig {
public bool enableCheckpoints = true;
public bool keepIntermediateResults = false;
public DynamicNavMeshConfig(bool useTiles, int tileSizeX, int tileSizeZ, float cellSize) {
public DynamicNavMeshConfig(bool useTiles, int tileSizeX, int tileSizeZ, float cellSize)
{
this.useTiles = useTiles;
this.tileSizeX = tileSizeX;
this.tileSizeZ = tileSizeZ;
this.cellSize = cellSize;
}
}
}

View File

@ -27,10 +27,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic
{
public class DynamicTile {
public class DynamicTile
{
public readonly VoxelTile voxelTile;
public DynamicTileCheckpoint checkpoint;
public RecastBuilderResult recastResult;
@ -39,12 +37,15 @@ public class DynamicTile {
private bool dirty = true;
private long id;
public DynamicTile(VoxelTile voxelTile) {
public DynamicTile(VoxelTile voxelTile)
{
this.voxelTile = voxelTile;
}
public bool build(RecastBuilder builder, DynamicNavMeshConfig config, Telemetry telemetry) {
if (dirty) {
public bool build(RecastBuilder builder, DynamicNavMeshConfig config, Telemetry telemetry)
{
if (dirty)
{
Heightfield heightfield = buildHeightfield(config, telemetry);
RecastBuilderResult r = buildRecast(builder, config, voxelTile, heightfield, telemetry);
NavMeshDataCreateParams option = navMeshCreateParams(voxelTile.tileX, voxelTile.tileZ, voxelTile.cellSize,
@ -52,26 +53,34 @@ public class DynamicTile {
meshData = NavMeshBuilder.createNavMeshData(option);
return true;
}
return false;
}
private Heightfield buildHeightfield(DynamicNavMeshConfig config, Telemetry telemetry) {
private Heightfield buildHeightfield(DynamicNavMeshConfig config, Telemetry telemetry)
{
ICollection<long> rasterizedColliders = checkpoint != null ? checkpoint.colliders : ImmutableHashSet<long>.Empty;
Heightfield heightfield = checkpoint != null ? checkpoint.heightfield : voxelTile.heightfield();
foreach (var (cid, c) in colliders) {
if (!rasterizedColliders.Contains(cid)) {
foreach (var (cid, c) in colliders)
{
if (!rasterizedColliders.Contains(cid))
{
heightfield.bmax[1] = Math.Max(heightfield.bmax[1], c.bounds()[4] + heightfield.ch * 2);
c.rasterize(heightfield, telemetry);
}
}
if (config.enableCheckpoints) {
if (config.enableCheckpoints)
{
checkpoint = new DynamicTileCheckpoint(heightfield, colliders.Keys.ToHashSet());
}
return heightfield;
}
private RecastBuilderResult buildRecast(RecastBuilder builder, DynamicNavMeshConfig config, VoxelTile vt,
Heightfield heightfield, Telemetry telemetry) {
Heightfield heightfield, Telemetry telemetry)
{
RecastConfig rcConfig = new RecastConfig(config.useTiles, config.tileSizeX, config.tileSizeZ, vt.borderSize,
config.partitionType, vt.cellSize, vt.cellHeight, config.walkableSlopeAngle, true, true, true,
config.walkableHeight, config.walkableRadius, config.walkableClimb, config.minRegionArea, config.regionMergeArea,
@ -79,36 +88,45 @@ public class DynamicTile {
Math.Min(DynamicNavMesh.MAX_VERTS_PER_POLY, config.vertsPerPoly), true, config.detailSampleDistance,
config.detailSampleMaxError, null);
RecastBuilderResult r = builder.build(vt.tileX, vt.tileZ, null, rcConfig, heightfield, telemetry);
if (config.keepIntermediateResults) {
if (config.keepIntermediateResults)
{
recastResult = r;
}
return r;
}
public void addCollider(long cid, Collider collider) {
public void addCollider(long cid, Collider collider)
{
colliders[cid] = collider;
dirty = true;
}
public bool containsCollider(long cid) {
public bool containsCollider(long cid)
{
return colliders.ContainsKey(cid);
}
public void removeCollider(long colliderId) {
if (colliders.TryRemove(colliderId, out var collider)) {
public void removeCollider(long colliderId)
{
if (colliders.TryRemove(colliderId, out var collider))
{
dirty = true;
checkpoint = null;
}
}
private NavMeshDataCreateParams navMeshCreateParams(int tilex, int tileZ, float cellSize, float cellHeight,
DynamicNavMeshConfig config, RecastBuilderResult rcResult) {
DynamicNavMeshConfig config, RecastBuilderResult rcResult)
{
PolyMesh m_pmesh = rcResult.getMesh();
PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
for (int i = 0; i < m_pmesh.npolys; ++i) {
for (int i = 0; i < m_pmesh.npolys; ++i)
{
m_pmesh.flags[i] = 1;
}
option.tileX = tilex;
option.tileZ = tileZ;
option.verts = m_pmesh.verts;
@ -118,13 +136,15 @@ public class DynamicTile {
option.polyFlags = m_pmesh.flags;
option.polyCount = m_pmesh.npolys;
option.nvp = m_pmesh.nvp;
if (m_dmesh != null) {
if (m_dmesh != null)
{
option.detailMeshes = m_dmesh.meshes;
option.detailVerts = m_dmesh.verts;
option.detailVertsCount = m_dmesh.nverts;
option.detailTris = m_dmesh.tris;
option.detailTriCount = m_dmesh.ntris;
}
option.walkableHeight = config.walkableHeight;
option.walkableRadius = config.walkableRadius;
option.walkableClimb = config.walkableClimb;
@ -144,14 +164,17 @@ public class DynamicTile {
return option;
}
public void addTo(NavMesh navMesh) {
if (meshData != null) {
public void addTo(NavMesh navMesh)
{
if (meshData != null)
{
id = navMesh.addTile(meshData, 0, 0);
} else {
}
else
{
navMesh.removeTile(id);
id = 0;
}
}
}
}

View File

@ -18,48 +18,53 @@ freely, subject to the following restrictions:
using System.Collections.Generic;
using DotRecast.Recast;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Dynamic
{
public class DynamicTileCheckpoint {
public class DynamicTileCheckpoint
{
public readonly Heightfield heightfield;
public readonly ISet<long> colliders;
public DynamicTileCheckpoint(Heightfield heightfield, ISet<long> colliders) {
public DynamicTileCheckpoint(Heightfield heightfield, ISet<long> colliders)
{
this.colliders = colliders;
this.heightfield = clone(heightfield);
}
private Heightfield clone(Heightfield source) {
private Heightfield clone(Heightfield source)
{
Heightfield clone = new Heightfield(source.width, source.height, vCopy(source.bmin), vCopy(source.bmax), source.cs,
source.ch, source.borderSize);
for (int z = 0, pz = 0; z < source.height; z++, pz += source.width) {
for (int x = 0; x < source.width; x++) {
for (int z = 0, pz = 0; z < source.height; z++, pz += source.width)
{
for (int x = 0; x < source.width; x++)
{
Span span = source.spans[pz + x];
Span prevCopy = null;
while (span != null) {
while (span != null)
{
Span copy = new Span();
copy.smin = span.smin;
copy.smax = span.smax;
copy.area = span.area;
if (prevCopy == null) {
if (prevCopy == null)
{
clone.spans[pz + x] = copy;
} else {
}
else
{
prevCopy.next = copy;
}
prevCopy = copy;
span = span.next;
}
}
}
return clone;
}
}
}

View File

@ -20,62 +20,74 @@ using DotRecast.Core;
namespace DotRecast.Detour.Dynamic.Io
{
public static class ByteUtils {
public static int getInt(byte[] data, int position, ByteOrder order) {
public static class ByteUtils
{
public static int getInt(byte[] data, int position, ByteOrder order)
{
return order == ByteOrder.BIG_ENDIAN ? getIntBE(data, position) : getIntLE(data, position);
}
public static int getIntBE(byte[] data, int position) {
public static int getIntBE(byte[] data, int position)
{
return ((data[position] & 0xff) << 24) | ((data[position + 1] & 0xff) << 16) | ((data[position + 2] & 0xff) << 8)
| (data[position + 3] & 0xff);
}
public static int getIntLE(byte[] data, int position) {
public static int getIntLE(byte[] data, int position)
{
return ((data[position + 3] & 0xff) << 24) | ((data[position + 2] & 0xff) << 16) | ((data[position + 1] & 0xff) << 8)
| (data[position] & 0xff);
}
public static int getShort(byte[] data, int position, ByteOrder order) {
public static int getShort(byte[] data, int position, ByteOrder order)
{
return order == ByteOrder.BIG_ENDIAN ? getShortBE(data, position) : getShortLE(data, position);
}
public static int getShortBE(byte[] data, int position) {
public static int getShortBE(byte[] data, int position)
{
return ((data[position] & 0xff) << 8) | (data[position + 1] & 0xff);
}
public static int getShortLE(byte[] data, int position) {
public static int getShortLE(byte[] data, int position)
{
return ((data[position + 1] & 0xff) << 8) | (data[position] & 0xff);
}
public static int putInt(int value, byte[] data, int position, ByteOrder order) {
if (order == ByteOrder.BIG_ENDIAN) {
public static int putInt(int value, byte[] data, int position, ByteOrder order)
{
if (order == ByteOrder.BIG_ENDIAN)
{
data[position] = (byte)((uint)value >> 24);
data[position + 1] = (byte)((uint)value >> 16);
data[position + 2] = (byte)((uint)value >> 8);
data[position + 3] = (byte)(value & 0xFF);
} else {
}
else
{
data[position] = (byte)(value & 0xFF);
data[position + 1] = (byte)((uint)value >> 8);
data[position + 2] = (byte)((uint)value >> 16);
data[position + 3] = (byte)((uint)value >> 24);
}
return position + 4;
}
public static int putShort(int value, byte[] data, int position, ByteOrder order) {
if (order == ByteOrder.BIG_ENDIAN) {
public static int putShort(int value, byte[] data, int position, ByteOrder order)
{
if (order == ByteOrder.BIG_ENDIAN)
{
data[position] = (byte)((uint)value >> 8);
data[position + 1] = (byte)(value & 0xFF);
} else {
}
else
{
data[position] = (byte)(value & 0xFF);
data[position + 1] = (byte)((uint)value >> 8);
}
return position + 2;
}
}
}

View File

@ -22,23 +22,21 @@ using K4os.Compression.LZ4;
namespace DotRecast.Detour.Dynamic.Io
{
public class LZ4VoxelTileCompressor {
public byte[] decompress(byte[] data) {
public class LZ4VoxelTileCompressor
{
public byte[] decompress(byte[] data)
{
int compressedSize = ByteUtils.getIntBE(data, 0);
return LZ4Pickler.Unpickle(data.AsSpan(4, compressedSize));
}
public byte[] compress(byte[] data) {
public byte[] compress(byte[] data)
{
byte[] compressed = LZ4Pickler.Pickle(data, LZ4Level.L12_MAX);
byte[] result = new byte[4 + compressed.Length];
ByteUtils.putInt(compressed.Length, result, 0, ByteOrder.BIG_ENDIAN);
Array.Copy(compressed, 0, result, 4, compressed.Length);
return result;
}
}
}

View File

@ -23,10 +23,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Io
{
public class VoxelFile {
public class VoxelFile
{
public static readonly ByteOrder PREFERRED_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
public const int MAGIC = 'V' << 24 | 'O' << 16 | 'X' << 8 | 'L';
public const int VERSION_EXPORTER_MASK = 0xF000;
@ -58,20 +56,23 @@ public class VoxelFile {
public float[] bounds = new float[6];
public readonly List<VoxelTile> tiles = new List<VoxelTile>();
public void addTile(VoxelTile tile) {
public void addTile(VoxelTile tile)
{
tiles.Add(tile);
}
public RecastConfig getConfig(VoxelTile tile, PartitionType partitionType, int maxPolyVerts, int regionMergeSize,
bool filterLowHangingObstacles, bool filterLedgeSpans, bool filterWalkableLowHeightSpans,
AreaModification walkbableAreaMod, bool buildMeshDetail, float detailSampleDist, float detailSampleMaxError) {
AreaModification walkbableAreaMod, bool buildMeshDetail, float detailSampleDist, float detailSampleMaxError)
{
return new RecastConfig(useTiles, tileSizeX, tileSizeZ, tile.borderSize, partitionType, cellSize, tile.cellHeight,
walkableSlopeAngle, filterLowHangingObstacles, filterLedgeSpans, filterWalkableLowHeightSpans, walkableHeight,
walkableRadius, walkableClimb, minRegionArea, regionMergeArea, maxEdgeLen, maxSimplificationError, maxPolyVerts,
buildMeshDetail, detailSampleDist, detailSampleMaxError, walkbableAreaMod);
}
public static VoxelFile from(RecastConfig config, List<RecastBuilderResult> results) {
public static VoxelFile from(RecastConfig config, List<RecastBuilderResult> results)
{
VoxelFile f = new VoxelFile();
f.version = 1;
f.partitionType = config.partitionType;
@ -94,9 +95,13 @@ public class VoxelFile {
f.useTiles = config.useTiles;
f.tileSizeX = config.tileSizeX;
f.tileSizeZ = config.tileSizeZ;
f.bounds = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity };
foreach (RecastBuilderResult r in results) {
f.bounds = new float[]
{
float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity
};
foreach (RecastBuilderResult r in results)
{
f.tiles.Add(new VoxelTile(r.tileX, r.tileZ, r.getSolidHeightfield()));
f.bounds[0] = Math.Min(f.bounds[0], r.getSolidHeightfield().bmin[0]);
f.bounds[1] = Math.Min(f.bounds[1], r.getSolidHeightfield().bmin[1]);
@ -105,10 +110,12 @@ public class VoxelFile {
f.bounds[4] = Math.Max(f.bounds[4], r.getSolidHeightfield().bmax[1]);
f.bounds[5] = Math.Max(f.bounds[5], r.getSolidHeightfield().bmax[2]);
}
return f;
}
public static VoxelFile from(DynamicNavMesh mesh) {
public static VoxelFile from(DynamicNavMesh mesh)
{
VoxelFile f = new VoxelFile();
f.version = 1;
DynamicNavMeshConfig config = mesh.config;
@ -132,9 +139,13 @@ public class VoxelFile {
f.useTiles = config.useTiles;
f.tileSizeX = config.tileSizeX;
f.tileSizeZ = config.tileSizeZ;
f.bounds = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity };
foreach (VoxelTile vt in mesh.voxelTiles()) {
f.bounds = new float[]
{
float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity
};
foreach (VoxelTile vt in mesh.voxelTiles())
{
Heightfield heightfield = vt.heightfield();
f.tiles.Add(new VoxelTile(vt.tileX, vt.tileZ, heightfield));
f.bounds[0] = Math.Min(f.bounds[0], vt.boundsMin[0]);
@ -144,9 +155,8 @@ public class VoxelFile {
f.bounds[4] = Math.Max(f.bounds[4], vt.boundsMax[1]);
f.bounds[5] = Math.Max(f.bounds[5], vt.boundsMax[2]);
}
return f;
}
}
}

View File

@ -22,23 +22,26 @@ using DotRecast.Detour.Io;
namespace DotRecast.Detour.Dynamic.Io
{
public class VoxelFileReader {
public class VoxelFileReader
{
private readonly LZ4VoxelTileCompressor compressor = new LZ4VoxelTileCompressor();
public VoxelFile read(BinaryReader stream) {
public VoxelFile read(BinaryReader stream)
{
ByteBuffer buf = IOUtils.toByteBuffer(stream);
VoxelFile file = new VoxelFile();
int magic = buf.getInt();
if (magic != VoxelFile.MAGIC) {
if (magic != VoxelFile.MAGIC)
{
magic = IOUtils.swapEndianness(magic);
if (magic != VoxelFile.MAGIC) {
if (magic != VoxelFile.MAGIC)
{
throw new IOException("Invalid magic");
}
buf.order(buf.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
}
file.version = buf.getInt();
bool isExportedFromAstar = (file.version & VoxelFile.VERSION_EXPORTER_MASK) == 0;
bool compression = (file.version & VoxelFile.VERSION_COMPRESSION_MASK) == VoxelFile.VERSION_COMPRESSION_LZ4;
@ -50,19 +53,23 @@ public class VoxelFileReader {
file.maxSimplificationError = buf.getFloat();
file.maxEdgeLen = buf.getFloat();
file.minRegionArea = (int)buf.getFloat();
if (!isExportedFromAstar) {
if (!isExportedFromAstar)
{
file.regionMergeArea = buf.getFloat();
file.vertsPerPoly = buf.getInt();
file.buildMeshDetail = buf.get() != 0;
file.detailSampleDistance = buf.getFloat();
file.detailSampleMaxError = buf.getFloat();
} else {
}
else
{
file.regionMergeArea = 6 * file.minRegionArea;
file.vertsPerPoly = 6;
file.buildMeshDetail = true;
file.detailSampleDistance = file.maxEdgeLen * 0.5f;
file.detailSampleMaxError = file.maxSimplificationError * 0.8f;
}
file.useTiles = buf.get() != 0;
file.tileSizeX = buf.getInt();
file.tileSizeZ = buf.getInt();
@ -75,7 +82,8 @@ public class VoxelFileReader {
file.bounds[3] = buf.getFloat();
file.bounds[4] = buf.getFloat();
file.bounds[5] = buf.getFloat();
if (isExportedFromAstar) {
if (isExportedFromAstar)
{
// bounds are saved as center + size
file.bounds[0] -= 0.5f * file.bounds[3];
file.bounds[1] -= 0.5f * file.bounds[4];
@ -84,8 +92,10 @@ public class VoxelFileReader {
file.bounds[4] += file.bounds[1];
file.bounds[5] += file.bounds[2];
}
int tileCount = buf.getInt();
for (int tile = 0; tile < tileCount; tile++) {
for (int tile = 0; tile < tileCount; tile++)
{
int tileX = buf.getInt();
int tileZ = buf.getInt();
int width = buf.getInt();
@ -99,7 +109,8 @@ public class VoxelFileReader {
boundsMax[0] = buf.getFloat();
boundsMax[1] = buf.getFloat();
boundsMax[2] = buf.getFloat();
if (isExportedFromAstar) {
if (isExportedFromAstar)
{
// bounds are local
boundsMin[0] += file.bounds[0];
boundsMin[1] += file.bounds[1];
@ -108,22 +119,24 @@ public class VoxelFileReader {
boundsMax[1] += file.bounds[1];
boundsMax[2] += file.bounds[2];
}
float cellSize = buf.getFloat();
float cellHeight = buf.getFloat();
int voxelSize = buf.getInt();
int position = buf.position();
byte[] bytes = buf.ReadBytes(voxelSize).ToArray();
if (compression) {
if (compression)
{
bytes = compressor.decompress(bytes);
}
ByteBuffer data = new ByteBuffer(bytes);
data.order(buf.order());
file.addTile(new VoxelTile(tileX, tileZ, width, depth, boundsMin, boundsMax, cellSize, cellHeight, borderSize, data));
buf.position(position + voxelSize);
}
return file;
}
}
}

View File

@ -22,17 +22,17 @@ using DotRecast.Detour.Io;
namespace DotRecast.Detour.Dynamic.Io
{
public class VoxelFileWriter : DetourWriter {
public class VoxelFileWriter : DetourWriter
{
private readonly LZ4VoxelTileCompressor compressor = new LZ4VoxelTileCompressor();
public void write(BinaryWriter stream, VoxelFile f, bool compression) {
public void write(BinaryWriter stream, VoxelFile f, bool compression)
{
write(stream, f, VoxelFile.PREFERRED_BYTE_ORDER, compression);
}
public void write(BinaryWriter stream, VoxelFile f, ByteOrder byteOrder, bool compression) {
public void write(BinaryWriter stream, VoxelFile f, ByteOrder byteOrder, bool compression)
{
write(stream, VoxelFile.MAGIC, byteOrder);
write(stream, VoxelFile.VERSION_EXPORTER_RECAST4J | (compression ? VoxelFile.VERSION_COMPRESSION_LZ4 : 0), byteOrder);
write(stream, f.walkableRadius, byteOrder);
@ -61,12 +61,14 @@ public class VoxelFileWriter : DetourWriter {
write(stream, f.bounds[4], byteOrder);
write(stream, f.bounds[5], byteOrder);
write(stream, f.tiles.Count, byteOrder);
foreach (VoxelTile t in f.tiles) {
foreach (VoxelTile t in f.tiles)
{
writeTile(stream, t, byteOrder, compression);
}
}
public void writeTile(BinaryWriter stream, VoxelTile tile, ByteOrder byteOrder, bool compression) {
public void writeTile(BinaryWriter stream, VoxelTile tile, ByteOrder byteOrder, bool compression)
{
write(stream, tile.tileX, byteOrder);
write(stream, tile.tileZ, byteOrder);
write(stream, tile.width, byteOrder);
@ -81,13 +83,13 @@ public class VoxelFileWriter : DetourWriter {
write(stream, tile.cellSize, byteOrder);
write(stream, tile.cellHeight, byteOrder);
byte[] bytes = tile.spanData;
if (compression) {
if (compression)
{
bytes = compressor.compress(bytes);
}
write(stream, bytes.Length, byteOrder);
stream.Write(bytes);
}
}
}

View File

@ -21,11 +21,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Io
{
public class VoxelTile {
public class VoxelTile
{
private const int SERIALIZED_SPAN_COUNT_BYTES = 2;
private const int SERIALIZED_SPAN_BYTES = 12;
public readonly int tileX;
@ -40,7 +37,8 @@ public class VoxelTile {
public readonly byte[] spanData;
public VoxelTile(int tileX, int tileZ, int width, int depth, float[] boundsMin, float[] boundsMax, float cellSize,
float cellHeight, int borderSize, ByteBuffer buffer) {
float cellHeight, int borderSize, ByteBuffer buffer)
{
this.tileX = tileX;
this.tileZ = tileZ;
this.width = width;
@ -53,7 +51,8 @@ public class VoxelTile {
spanData = toByteArray(buffer, width, depth, VoxelFile.PREFERRED_BYTE_ORDER);
}
public VoxelTile(int tileX, int tileZ, Heightfield heightfield) {
public VoxelTile(int tileX, int tileZ, Heightfield heightfield)
{
this.tileX = tileX;
this.tileZ = tileZ;
width = heightfield.width;
@ -66,19 +65,24 @@ public class VoxelTile {
spanData = serializeSpans(heightfield, VoxelFile.PREFERRED_BYTE_ORDER);
}
public Heightfield heightfield() {
public Heightfield heightfield()
{
return VoxelFile.PREFERRED_BYTE_ORDER == ByteOrder.BIG_ENDIAN ? heightfieldBE() : heightfieldLE();
}
private Heightfield heightfieldBE() {
private Heightfield heightfieldBE()
{
Heightfield hf = new Heightfield(width, depth, boundsMin, boundsMax, cellSize, cellHeight, borderSize);
int position = 0;
for (int z = 0, pz = 0; z < depth; z++, pz += width) {
for (int x = 0; x < width; x++) {
for (int z = 0, pz = 0; z < depth; z++, pz += width)
{
for (int x = 0; x < width; x++)
{
Span prev = null;
int spanCount = ByteUtils.getShortBE(spanData, position);
position += 2;
for (int s = 0; s < spanCount; s++) {
for (int s = 0; s < spanCount; s++)
{
Span span = new Span();
span.smin = ByteUtils.getIntBE(spanData, position);
position += 4;
@ -86,27 +90,36 @@ public class VoxelTile {
position += 4;
span.area = ByteUtils.getIntBE(spanData, position);
position += 4;
if (prev == null) {
if (prev == null)
{
hf.spans[pz + x] = span;
} else {
}
else
{
prev.next = span;
}
prev = span;
}
}
}
return hf;
}
private Heightfield heightfieldLE() {
private Heightfield heightfieldLE()
{
Heightfield hf = new Heightfield(width, depth, boundsMin, boundsMax, cellSize, cellHeight, borderSize);
int position = 0;
for (int z = 0, pz = 0; z < depth; z++, pz += width) {
for (int x = 0; x < width; x++) {
for (int z = 0, pz = 0; z < depth; z++, pz += width)
{
for (int x = 0; x < width; x++)
{
Span prev = null;
int spanCount = ByteUtils.getShortLE(spanData, position);
position += 2;
for (int s = 0; s < spanCount; s++) {
for (int s = 0; s < spanCount; s++)
{
Span span = new Span();
span.smin = ByteUtils.getIntLE(spanData, position);
position += 4;
@ -114,38 +127,51 @@ public class VoxelTile {
position += 4;
span.area = ByteUtils.getIntLE(spanData, position);
position += 4;
if (prev == null) {
if (prev == null)
{
hf.spans[pz + x] = span;
} else {
}
else
{
prev.next = span;
}
prev = span;
}
}
}
return hf;
}
private byte[] serializeSpans(Heightfield heightfield, ByteOrder order) {
private byte[] serializeSpans(Heightfield heightfield, ByteOrder order)
{
int[] counts = new int[heightfield.width * heightfield.height];
int totalCount = 0;
for (int z = 0, pz = 0; z < heightfield.height; z++, pz += heightfield.width) {
for (int x = 0; x < heightfield.width; x++) {
for (int z = 0, pz = 0; z < heightfield.height; z++, pz += heightfield.width)
{
for (int x = 0; x < heightfield.width; x++)
{
Span span = heightfield.spans[pz + x];
while (span != null) {
while (span != null)
{
counts[pz + x]++;
totalCount++;
span = span.next;
}
}
}
byte[] data = new byte[totalCount * SERIALIZED_SPAN_BYTES + counts.Length * SERIALIZED_SPAN_COUNT_BYTES];
int position = 0;
for (int z = 0, pz = 0; z < heightfield.height; z++, pz += heightfield.width) {
for (int x = 0; x < heightfield.width; x++) {
for (int z = 0, pz = 0; z < heightfield.height; z++, pz += heightfield.width)
{
for (int x = 0; x < heightfield.width; x++)
{
position = ByteUtils.putShort(counts[pz + x], data, position, order);
Span span = heightfield.spans[pz + x];
while (span != null) {
while (span != null)
{
position = ByteUtils.putInt(span.smin, data, position, order);
position = ByteUtils.putInt(span.smax, data, position, order);
position = ByteUtils.putInt(span.area, data, position, order);
@ -153,22 +179,29 @@ public class VoxelTile {
}
}
}
return data;
}
private byte[] toByteArray(ByteBuffer buf, int width, int height, ByteOrder order) {
private byte[] toByteArray(ByteBuffer buf, int width, int height, ByteOrder order)
{
byte[] data;
if (buf.order() == order) {
if (buf.order() == order)
{
data = buf.ReadBytes(buf.limit()).ToArray();
} else {
}
else
{
data = new byte[buf.limit()];
int l = width * height;
int position = 0;
for (int i = 0; i < l; i++) {
for (int i = 0; i < l; i++)
{
int count = buf.getShort();
ByteUtils.putShort(count, data, position, order);
position += 2;
for (int j = 0; j < count; j++) {
for (int j = 0; j < count; j++)
{
ByteUtils.putInt(buf.getInt(), data, position, order);
position += 4;
ByteUtils.putInt(buf.getInt(), data, position, order);
@ -178,8 +211,8 @@ public class VoxelTile {
}
}
}
return data;
}
}
return data;
}
}
}

View File

@ -20,26 +20,25 @@ using System.Collections.Generic;
namespace DotRecast.Detour.Dynamic
{
public class RemoveColliderQueueItem : UpdateQueueItem {
public class RemoveColliderQueueItem : UpdateQueueItem
{
private readonly long colliderId;
private readonly ICollection<DynamicTile> _affectedTiles;
public RemoveColliderQueueItem(long colliderId, ICollection<DynamicTile> affectedTiles) {
public RemoveColliderQueueItem(long colliderId, ICollection<DynamicTile> affectedTiles)
{
this.colliderId = colliderId;
this._affectedTiles = affectedTiles;
}
public ICollection<DynamicTile> affectedTiles() {
public ICollection<DynamicTile> affectedTiles()
{
return _affectedTiles;
}
public void process(DynamicTile tile) {
public void process(DynamicTile tile)
{
tile.removeCollider(colliderId);
}
}
}

View File

@ -20,14 +20,10 @@ using System.Collections.Generic;
namespace DotRecast.Detour.Dynamic
{
public interface UpdateQueueItem {
public interface UpdateQueueItem
{
ICollection<DynamicTile> affectedTiles();
void process(DynamicTile tile);
}
}

View File

@ -21,22 +21,20 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic
{
/**
* Voxel raycast based on the algorithm described in
*
* "A Fast Voxel Traversal Algorithm for Ray Tracing" by John Amanatides and Andrew Woo
*/
public class VoxelQuery {
public class VoxelQuery
{
private readonly float[] origin;
private readonly float tileWidth;
private readonly float tileDepth;
private readonly Func<int, int, Heightfield> heightfieldProvider;
public VoxelQuery(float[] origin, float tileWidth, float tileDepth, Func<int, int, Heightfield> heightfieldProvider) {
public VoxelQuery(float[] origin, float tileWidth, float tileDepth, Func<int, int, Heightfield> heightfieldProvider)
{
this.origin = origin;
this.tileWidth = tileWidth;
this.tileDepth = tileDepth;
@ -48,11 +46,13 @@ public class VoxelQuery {
*
* @return Optional with hit parameter (t) or empty if no hit found
*/
public float? raycast(float[] start, float[] end) {
public float? raycast(float[] start, float[] end)
{
return traverseTiles(start, end);
}
private float? traverseTiles(float[] start, float[] end) {
private float? traverseTiles(float[] start, float[] end)
{
float relStartX = start[0] - origin[0];
float relStartZ = start[2] - origin[2];
int sx = (int)Math.Floor(relStartX / tileWidth);
@ -76,19 +76,27 @@ public class VoxelQuery {
float tDeltaX = tileWidth / tx;
float tDeltaZ = tileDepth / tz;
float t = 0;
while (true) {
while (true)
{
float? hit = traversHeightfield(sx, sz, start, end, t, Math.Min(1, Math.Min(tMaxX, tMaxZ)));
if (hit.HasValue) {
if (hit.HasValue)
{
return hit;
}
if ((dx > 0 ? sx >= ex : sx <= ex) && (dz > 0 ? sz >= ez : sz <= ez)) {
if ((dx > 0 ? sx >= ex : sx <= ex) && (dz > 0 ? sz >= ez : sz <= ez))
{
break;
}
if (tMaxX < tMaxZ) {
if (tMaxX < tMaxZ)
{
t = tMaxX;
tMaxX += tDeltaX;
sx += stepX;
} else {
}
else
{
t = tMaxZ;
tMaxZ += tDeltaZ;
sz += stepZ;
@ -98,9 +106,11 @@ public class VoxelQuery {
return null;
}
private float? traversHeightfield(int x, int z, float[] start, float[] end, float tMin, float tMax) {
private float? traversHeightfield(int x, int z, float[] start, float[] end, float tMin, float tMax)
{
Heightfield hf = heightfieldProvider.Invoke(x, z);
if (null != hf) {
if (null != hf)
{
float tx = end[0] - start[0];
float ty = end[1] - start[1];
float tz = end[2] - start[2];
@ -127,28 +137,39 @@ public class VoxelQuery {
float tDeltaX = hf.cs / tx;
float tDeltaZ = hf.cs / tz;
float t = 0;
while (true) {
if (sx >= 0 && sx < hf.width && sz >= 0 && sz < hf.height) {
while (true)
{
if (sx >= 0 && sx < hf.width && sz >= 0 && sz < hf.height)
{
float y1 = start[1] + ty * (tMin + t) - hf.bmin[1];
float y2 = start[1] + ty * (tMin + Math.Min(tMaxX, tMaxZ)) - hf.bmin[1];
float ymin = Math.Min(y1, y2) / hf.ch;
float ymax = Math.Max(y1, y2) / hf.ch;
Span span = hf.spans[sx + sz * hf.width];
while (span != null) {
if (span.smin <= ymin && span.smax >= ymax) {
while (span != null)
{
if (span.smin <= ymin && span.smax >= ymax)
{
return Math.Min(1, tMin + t);
}
span = span.next;
}
}
if ((dx > 0 ? sx >= ex : sx <= ex) && (dz > 0 ? sz >= ez : sz <= ez)) {
if ((dx > 0 ? sx >= ex : sx <= ex) && (dz > 0 ? sz >= ez : sz <= ez))
{
break;
}
if (tMaxX < tMaxZ) {
if (tMaxX < tMaxZ)
{
t = tMaxX;
tMaxX += tDeltaX;
sx += stepX;
} else {
}
else
{
t = tMaxZ;
tMaxZ += tDeltaZ;
sz += stepZ;
@ -158,7 +179,5 @@ public class VoxelQuery {
return null;
}
}
}

View File

@ -20,19 +20,21 @@ using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Extras
{
public class BVTreeBuilder {
public void build(MeshData data) {
public class BVTreeBuilder
{
public void build(MeshData data)
{
data.bvTree = new BVNode[data.header.polyCount * 2];
data.header.bvNodeCount = data.bvTree.Length == 0 ? 0
data.header.bvNodeCount = data.bvTree.Length == 0
? 0
: createBVTree(data, data.bvTree, data.header.bvQuantFactor);
}
private static int createBVTree(MeshData data, BVNode[] nodes, float quantFactor) {
private static int createBVTree(MeshData data, BVNode[] nodes, float quantFactor)
{
NavMeshBuilder.BVItem[] items = new NavMeshBuilder.BVItem[data.header.polyCount];
for (int i = 0; i < data.header.polyCount; i++) {
for (int i = 0; i < data.header.polyCount; i++)
{
NavMeshBuilder.BVItem it = new NavMeshBuilder.BVItem();
items[i] = it;
it.i = i;
@ -40,10 +42,12 @@ public class BVTreeBuilder {
float[] bmax = new float[3];
vCopy(bmin, data.verts, data.polys[i].verts[0] * 3);
vCopy(bmax, data.verts, data.polys[i].verts[0] * 3);
for (int j = 1; j < data.polys[i].vertCount; j++) {
for (int j = 1; j < data.polys[i].vertCount; j++)
{
vMin(bmin, data.verts, data.polys[i].verts[j] * 3);
vMax(bmax, data.verts, data.polys[i].verts[j] * 3);
}
it.bmin[0] = clamp((int)((bmin[0] - data.header.bmin[0]) * quantFactor), 0, 0x7fffffff);
it.bmin[1] = clamp((int)((bmin[1] - data.header.bmin[1]) * quantFactor), 0, 0x7fffffff);
it.bmin[2] = clamp((int)((bmin[2] - data.header.bmin[2]) * quantFactor), 0, 0x7fffffff);
@ -51,9 +55,8 @@ public class BVTreeBuilder {
it.bmax[1] = clamp((int)((bmax[1] - data.header.bmin[1]) * quantFactor), 0, 0x7fffffff);
it.bmax[2] = clamp((int)((bmax[2] - data.header.bmin[2]) * quantFactor), 0, 0x7fffffff);
}
return NavMeshBuilder.subdivide(items, data.header.polyCount, 0, data.header.polyCount, 0, nodes);
}
}
}

View File

@ -1,21 +1,20 @@
using System;
using DotRecast.Recast;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Extras.Jumplink
{
public abstract class AbstractGroundSampler : GroundSampler {
public abstract class AbstractGroundSampler : GroundSampler
{
protected void sampleGround(JumpLinkBuilderConfig acfg, EdgeSampler es,
Func<float[], float, Tuple<bool, float>> heightFunc) {
Func<float[], float, Tuple<bool, float>> heightFunc)
{
float cs = acfg.cellSize;
float dist = (float)Math.Sqrt(vDist2DSqr(es.start.p, es.start.q));
int ngsamples = Math.Max(2, (int)Math.Ceiling(dist / cs));
sampleGroundSegment(heightFunc, es.start, ngsamples);
foreach (GroundSegment end in es.end) {
foreach (GroundSegment end in es.end)
{
sampleGroundSegment(heightFunc, end, ngsamples);
}
}
@ -26,10 +25,12 @@ public abstract class AbstractGroundSampler : GroundSampler {
}
protected void sampleGroundSegment(Func<float[], float, Tuple<bool, float>> heightFunc, GroundSegment seg,
int nsamples) {
int nsamples)
{
seg.gsamples = new GroundSample[nsamples];
for (int i = 0; i < nsamples; ++i) {
for (int i = 0; i < nsamples; ++i)
{
float u = i / (float)(nsamples - 1);
GroundSample s = new GroundSample();
@ -40,13 +41,13 @@ public abstract class AbstractGroundSampler : GroundSampler {
s.p[1] = height.Item2;
s.p[2] = pt[2];
if (!height.Item1) {
if (!height.Item1)
{
continue;
}
s.validHeight = true;
}
}
}
}

View File

@ -2,16 +2,16 @@ using System;
namespace DotRecast.Detour.Extras.Jumplink
{
public class ClimbTrajectory : Trajectory {
public override float[] apply(float[] start, float[] end, float u) {
return new float[] { lerp(start[0], end[0], Math.Min(2f * u, 1f)),
public class ClimbTrajectory : Trajectory
{
public override float[] apply(float[] start, float[] end, float u)
{
return new float[]
{
lerp(start[0], end[0], Math.Min(2f * u, 1f)),
lerp(start[1], end[1], Math.Max(0f, 2f * u - 1f)),
lerp(start[2], end[2], Math.Min(2f * u, 1f)) };
lerp(start[2], end[2], Math.Min(2f * u, 1f))
};
}
}
}

View File

@ -1,10 +1,8 @@
namespace DotRecast.Detour.Extras.Jumplink
{
public class Edge {
public class Edge
{
public readonly float[] sp = new float[3];
public readonly float[] sq = new float[3];
}
}

View File

@ -5,39 +5,55 @@ using static DotRecast.Recast.RecastConstants;
namespace DotRecast.Detour.Extras.Jumplink
{
public class EdgeExtractor {
public Edge[] extractEdges(PolyMesh mesh) {
public class EdgeExtractor
{
public Edge[] extractEdges(PolyMesh mesh)
{
List<Edge> edges = new List<Edge>();
if (mesh != null) {
if (mesh != null)
{
float[] orig = mesh.bmin;
float cs = mesh.cs;
float ch = mesh.ch;
for (int i = 0; i < mesh.npolys; i++) {
if (i > 41 || i < 41) {
for (int i = 0; i < mesh.npolys; i++)
{
if (i > 41 || i < 41)
{
// continue;
}
int nvp = mesh.nvp;
int p = i * 2 * nvp;
for (int j = 0; j < nvp; ++j) {
if (j != 1) {
for (int j = 0; j < nvp; ++j)
{
if (j != 1)
{
// continue;
}
if (mesh.polys[p + j] == RC_MESH_NULL_IDX) {
if (mesh.polys[p + j] == RC_MESH_NULL_IDX)
{
break;
}
// Skip connected edges.
if ((mesh.polys[p + nvp + j] & 0x8000) != 0) {
if ((mesh.polys[p + nvp + j] & 0x8000) != 0)
{
int dir = mesh.polys[p + nvp + j] & 0xf;
if (dir == 0xf) {// Border
if (mesh.polys[p + nvp + j] != RC_MESH_NULL_IDX) {
if (dir == 0xf)
{
// Border
if (mesh.polys[p + nvp + j] != RC_MESH_NULL_IDX)
{
continue;
}
int nj = j + 1;
if (nj >= nvp || mesh.polys[p + nj] == RC_MESH_NULL_IDX) {
if (nj >= nvp || mesh.polys[p + nj] == RC_MESH_NULL_IDX)
{
nj = 0;
}
int va = mesh.polys[p + j] * 3;
int vb = mesh.polys[p + nj] * 3;
Edge e = new Edge();
@ -56,7 +72,5 @@ public class EdgeExtractor {
return edges.ToArray();
}
}
}

View File

@ -1,12 +1,10 @@
using System.Collections.Generic;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Extras.Jumplink
{
public class EdgeSampler {
public class EdgeSampler
{
public readonly GroundSegment start = new GroundSegment();
public readonly List<GroundSegment> end = new List<GroundSegment>();
public readonly Trajectory trajectory;
@ -15,7 +13,8 @@ public class EdgeSampler {
public readonly float[] ay = new float[3];
public readonly float[] az = new float[3];
public EdgeSampler(Edge edge, Trajectory trajectory) {
public EdgeSampler(Edge edge, Trajectory trajectory)
{
this.trajectory = trajectory;
vCopy(ax, vSub(edge.sq, edge.sp));
vNormalize(ax);
@ -23,7 +22,5 @@ public class EdgeSampler {
vNormalize(az);
vSet(ay, 0, 1, 0);
}
}
}

View File

@ -2,13 +2,13 @@ using System;
namespace DotRecast.Detour.Extras.Jumplink
{
class EdgeSamplerFactory {
public EdgeSampler get(JumpLinkBuilderConfig acfg, JumpLinkType type, Edge edge) {
class EdgeSamplerFactory
{
public EdgeSampler get(JumpLinkBuilderConfig acfg, JumpLinkType type, Edge edge)
{
EdgeSampler es = null;
switch (type) {
switch (type)
{
case JumpLinkType.EDGE_JUMP:
es = initEdgeJumpSampler(acfg, edge);
break;
@ -19,12 +19,13 @@ class EdgeSamplerFactory {
default:
throw new ArgumentException("Unsupported jump type " + type);
}
return es;
}
private EdgeSampler initEdgeJumpSampler(JumpLinkBuilderConfig acfg, Edge edge) {
private EdgeSampler initEdgeJumpSampler(JumpLinkBuilderConfig acfg, Edge edge)
{
EdgeSampler es = new EdgeSampler(edge, new JumpTrajectory(acfg.jumpHeight));
es.start.height = acfg.agentClimb * 2;
float[] offset = new float[3];
@ -36,7 +37,8 @@ class EdgeSamplerFactory {
float cs = acfg.cellSize;
int nsamples = Math.Max(2, (int)Math.Ceiling(dx / cs));
for (int j = 0; j < nsamples; ++j) {
for (int j = 0; j < nsamples; ++j)
{
float v = (float)j / (float)(nsamples - 1);
float ox = 2 * acfg.agentRadius + dx * v;
trans2d(offset, es.az, es.ay, new float[] { ox, acfg.minHeight });
@ -46,10 +48,12 @@ class EdgeSamplerFactory {
vadd(end.q, edge.sq, offset);
es.end.Add(end);
}
return es;
}
private EdgeSampler initClimbDownSampler(JumpLinkBuilderConfig acfg, Edge edge) {
private EdgeSampler initClimbDownSampler(JumpLinkBuilderConfig acfg, Edge edge)
{
EdgeSampler es = new EdgeSampler(edge, new ClimbTrajectory());
es.start.height = acfg.agentClimb * 2;
float[] offset = new float[3];
@ -66,18 +70,18 @@ class EdgeSamplerFactory {
return es;
}
private void vadd(float[] dest, float[] v1, float[] v2) {
private void vadd(float[] dest, float[] v1, float[] v2)
{
dest[0] = v1[0] + v2[0];
dest[1] = v1[1] + v2[1];
dest[2] = v1[2] + v2[2];
}
private void trans2d(float[] dst, float[] ax, float[] ay, float[] pt) {
private void trans2d(float[] dst, float[] ax, float[] ay, float[] pt)
{
dst[0] = ax[0] * pt[0] + ay[0] * pt[1];
dst[1] = ax[1] * pt[0] + ay[1] * pt[1];
dst[2] = ax[2] * pt[0] + ay[2] * pt[1];
}
}
}

View File

@ -1,11 +1,9 @@
namespace DotRecast.Detour.Extras.Jumplink
{
public class GroundSample {
public class GroundSample
{
public readonly float[] p = new float[3];
public bool validTrajectory;
public bool validHeight;
}
}

View File

@ -2,12 +2,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Extras.Jumplink
{
public interface GroundSampler {
public interface GroundSampler
{
void sample(JumpLinkBuilderConfig acfg, RecastBuilderResult result, EdgeSampler es);
}
}

View File

@ -1,13 +1,10 @@
namespace DotRecast.Detour.Extras.Jumplink
{
public class GroundSegment {
public class GroundSegment
{
public readonly float[] p = new float[3];
public readonly float[] q = new float[3];
public GroundSample[] gsamples;
public float height;
}
}

View File

@ -1,9 +1,7 @@
namespace DotRecast.Detour.Extras.Jumplink
{
public class JumpLink {
public class JumpLink
{
public const int MAX_SPINE = 8;
public readonly int nspine = MAX_SPINE;
public readonly float[] spine0 = new float[MAX_SPINE * 3];
@ -13,7 +11,5 @@ public class JumpLink {
public GroundSegment start;
public GroundSegment end;
public Trajectory trajectory;
}
}

View File

@ -7,10 +7,8 @@ using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Extras.Jumplink
{
public class JumpLinkBuilder {
public class JumpLinkBuilder
{
private readonly EdgeExtractor edgeExtractor = new EdgeExtractor();
private readonly EdgeSamplerFactory edgeSamplerFactory = new EdgeSamplerFactory();
private readonly GroundSampler groundSampler = new NavMeshGroundSampler();
@ -20,23 +18,29 @@ public class JumpLinkBuilder {
private readonly List<Edge[]> edges;
private readonly List<RecastBuilderResult> results;
public JumpLinkBuilder(List<RecastBuilderResult> results) {
public JumpLinkBuilder(List<RecastBuilderResult> results)
{
this.results = results;
edges = results.Select(r => edgeExtractor.extractEdges(r.getMesh())).ToList();
}
public List<JumpLink> build(JumpLinkBuilderConfig acfg, JumpLinkType type) {
public List<JumpLink> build(JumpLinkBuilderConfig acfg, JumpLinkType type)
{
List<JumpLink> links = new List<JumpLink>();
for (int tile = 0; tile < results.Count; tile++) {
for (int tile = 0; tile < results.Count; tile++)
{
Edge[] edges = this.edges[tile];
foreach (Edge edge in edges) {
foreach (Edge edge in edges)
{
links.AddRange(processEdge(acfg, results[tile], type, edge));
}
}
return links;
}
private List<JumpLink> processEdge(JumpLinkBuilderConfig acfg, RecastBuilderResult result, JumpLinkType type, Edge edge) {
private List<JumpLink> processEdge(JumpLinkBuilderConfig acfg, RecastBuilderResult result, JumpLinkType type, Edge edge)
{
EdgeSampler es = edgeSamplerFactory.get(acfg, type, edge);
groundSampler.sample(acfg, result, es);
trajectorySampler.sample(acfg, result.getSolidHeightfield(), es);
@ -45,16 +49,19 @@ public class JumpLinkBuilder {
}
private List<JumpLink> buildJumpLinks(JumpLinkBuilderConfig acfg, EdgeSampler es, JumpSegment[] jumpSegments) {
private List<JumpLink> buildJumpLinks(JumpLinkBuilderConfig acfg, EdgeSampler es, JumpSegment[] jumpSegments)
{
List<JumpLink> links = new List<JumpLink>();
foreach (JumpSegment js in jumpSegments) {
foreach (JumpSegment js in jumpSegments)
{
float[] sp = es.start.gsamples[js.startSample].p;
float[] sq = es.start.gsamples[js.startSample + js.samples - 1].p;
GroundSegment end = es.end[js.groundSegment];
float[] ep = end.gsamples[js.startSample].p;
float[] eq = end.gsamples[js.startSample + js.samples - 1].p;
float d = Math.Min(vDist2DSqr(sp, sq), vDist2DSqr(ep, eq));
if (d >= 4 * acfg.agentRadius * acfg.agentRadius) {
if (d >= 4 * acfg.agentRadius * acfg.agentRadius)
{
JumpLink link = new JumpLink();
links.Add(link);
link.startSamples = ArrayUtils.CopyOf(es.start.gsamples, js.startSample, js.samples - js.startSample);
@ -62,7 +69,8 @@ public class JumpLinkBuilder {
link.start = es.start;
link.end = end;
link.trajectory = es.trajectory;
for (int j = 0; j < link.nspine; ++j) {
for (int j = 0; j < link.nspine; ++j)
{
float u = ((float)j) / (link.nspine - 1);
float[] p = es.trajectory.apply(sp, ep, u);
link.spine0[j * 3] = p[0];
@ -76,13 +84,13 @@ public class JumpLinkBuilder {
}
}
}
return links;
}
public List<Edge[]> getEdges() {
public List<Edge[]> getEdges()
{
return edges;
}
}
}

View File

@ -1,9 +1,7 @@
namespace DotRecast.Detour.Extras.Jumplink
{
public class JumpLinkBuilderConfig {
public class JumpLinkBuilderConfig
{
public readonly float cellSize;
public readonly float cellHeight;
public readonly float agentClimb;
@ -18,7 +16,8 @@ public class JumpLinkBuilderConfig {
public JumpLinkBuilderConfig(float cellSize, float cellHeight, float agentRadius, float agentHeight,
float agentClimb, float groundTolerance, float startDistance, float endDistance, float minHeight,
float maxHeight, float jumpHeight) {
float maxHeight, float jumpHeight)
{
this.cellSize = cellSize;
this.cellHeight = cellHeight;
this.agentRadius = agentRadius;
@ -31,7 +30,5 @@ public class JumpLinkBuilderConfig {
heightRange = maxHeight - minHeight;
this.jumpHeight = jumpHeight;
}
}
}

View File

@ -1,8 +1,9 @@
namespace DotRecast.Detour.Extras.Jumplink
{
public enum JumpLinkType {
EDGE_JUMP, EDGE_CLIMB_DOWN, EDGE_JUMP_OVER
public enum JumpLinkType
{
EDGE_JUMP,
EDGE_CLIMB_DOWN,
EDGE_JUMP_OVER
}
}

View File

@ -1,11 +1,9 @@
namespace DotRecast.Detour.Extras.Jumplink
{
public class JumpSegment {
public class JumpSegment
{
public int groundSegment;
public int startSample;
public int samples;
}
}

View File

@ -1,31 +1,38 @@
using System;
using System.Collections.Generic;
using DotRecast.Core;
namespace DotRecast.Detour.Extras.Jumplink
{
class JumpSegmentBuilder {
public JumpSegment[] build(JumpLinkBuilderConfig acfg, EdgeSampler es) {
class JumpSegmentBuilder
{
public JumpSegment[] build(JumpLinkBuilderConfig acfg, EdgeSampler es)
{
int n = es.end[0].gsamples.Length;
int[][] sampleGrid = ArrayUtils.Of<int>(n, es.end.Count);
for (int j = 0; j < es.end.Count; j++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < es.end.Count; j++)
{
for (int i = 0; i < n; i++)
{
sampleGrid[i][j] = -1;
}
}
// Fill connected regions
int region = 0;
for (int j = 0; j < es.end.Count; j++) {
for (int i = 0; i < n; i++) {
if (sampleGrid[i][j] == -1) {
for (int j = 0; j < es.end.Count; j++)
{
for (int i = 0; i < n; i++)
{
if (sampleGrid[i][j] == -1)
{
GroundSample p = es.end[j].gsamples[i];
if (!p.validTrajectory) {
if (!p.validTrajectory)
{
sampleGrid[i][j] = -2;
} else {
}
else
{
var queue = new Queue<int[]>();
queue.Enqueue(new int[] { i, j });
fill(es, sampleGrid, queue, acfg.agentClimb, region);
@ -34,66 +41,90 @@ class JumpSegmentBuilder {
}
}
}
JumpSegment[] jumpSegments = new JumpSegment[region];
for (int i = 0; i < jumpSegments.Length; i++) {
for (int i = 0; i < jumpSegments.Length; i++)
{
jumpSegments[i] = new JumpSegment();
}
// Find longest segments per region
for (int j = 0; j < es.end.Count; j++) {
for (int j = 0; j < es.end.Count; j++)
{
int l = 0;
int r = -2;
for (int i = 0; i < n + 1; i++) {
if (i == n || sampleGrid[i][j] != r) {
if (r >= 0) {
if (jumpSegments[r].samples < l) {
for (int i = 0; i < n + 1; i++)
{
if (i == n || sampleGrid[i][j] != r)
{
if (r >= 0)
{
if (jumpSegments[r].samples < l)
{
jumpSegments[r].samples = l;
jumpSegments[r].startSample = i - l;
jumpSegments[r].groundSegment = j;
}
}
if (i < n) {
if (i < n)
{
r = sampleGrid[i][j];
}
l = 1;
} else {
}
else
{
l++;
}
}
}
return jumpSegments;
}
private void fill(EdgeSampler es, int[][] sampleGrid, Queue<int[]> queue, float agentClimb, int region) {
while (queue.TryDequeue(out var ij)) {
private void fill(EdgeSampler es, int[][] sampleGrid, Queue<int[]> queue, float agentClimb, int region)
{
while (queue.TryDequeue(out var ij))
{
int i = ij[0];
int j = ij[1];
if (sampleGrid[i][j] == -1) {
if (sampleGrid[i][j] == -1)
{
GroundSample p = es.end[j].gsamples[i];
sampleGrid[i][j] = region;
float h = p.p[1];
if (i < sampleGrid.Length - 1) {
if (i < sampleGrid.Length - 1)
{
addNeighbour(es, queue, agentClimb, h, i + 1, j);
}
if (i > 0) {
if (i > 0)
{
addNeighbour(es, queue, agentClimb, h, i - 1, j);
}
if (j < sampleGrid[0].Length - 1) {
if (j < sampleGrid[0].Length - 1)
{
addNeighbour(es, queue, agentClimb, h, i, j + 1);
}
if (j > 0) {
if (j > 0)
{
addNeighbour(es, queue, agentClimb, h, i, j - 1);
}
}
}
}
private void addNeighbour(EdgeSampler es, Queue<int[]> queue, float agentClimb, float h, int i, int j) {
private void addNeighbour(EdgeSampler es, Queue<int[]> queue, float agentClimb, float h, int i, int j)
{
GroundSample q = es.end[j].gsamples[i];
if (q.validTrajectory && Math.Abs(q.p[1] - h) < agentClimb) {
if (q.validTrajectory && Math.Abs(q.p[1] - h) < agentClimb)
{
queue.Enqueue(new int[] { i, j });
}
}
}
}

View File

@ -2,44 +2,58 @@ using System;
namespace DotRecast.Detour.Extras.Jumplink
{
public class JumpTrajectory : Trajectory {
public class JumpTrajectory : Trajectory
{
private readonly float jumpHeight;
public JumpTrajectory(float jumpHeight) {
public JumpTrajectory(float jumpHeight)
{
this.jumpHeight = jumpHeight;
}
public override float[] apply(float[] start, float[] end, float u) {
return new float[] { lerp(start[0], end[0], u), interpolateHeight(start[1], end[1], u),
lerp(start[2], end[2], u) };
public override float[] apply(float[] start, float[] end, float u)
{
return new float[]
{
lerp(start[0], end[0], u), interpolateHeight(start[1], end[1], u),
lerp(start[2], end[2], u)
};
}
private float interpolateHeight(float ys, float ye, float u) {
if (u == 0f) {
private float interpolateHeight(float ys, float ye, float u)
{
if (u == 0f)
{
return ys;
} else if (u == 1.0f) {
}
else if (u == 1.0f)
{
return ye;
}
float h1, h2;
if (ys >= ye) { // jump down
if (ys >= ye)
{
// jump down
h1 = jumpHeight;
h2 = jumpHeight + ys - ye;
} else { // jump up
}
else
{
// jump up
h1 = jumpHeight + ys - ye;
h2 = jumpHeight;
}
float t = (float)(Math.Sqrt(h1) / (Math.Sqrt(h2) + Math.Sqrt(h1)));
if (u <= t) {
if (u <= t)
{
float v1 = 1.0f - (u / t);
return ys + h1 - h1 * v1 * v1;
}
float v = (u - t) / (1.0f - t);
return ys + h1 - h2 * v * v;
}
}
}

View File

@ -4,31 +4,32 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Extras.Jumplink
{
class NavMeshGroundSampler : AbstractGroundSampler {
class NavMeshGroundSampler : AbstractGroundSampler
{
private readonly QueryFilter filter = new NoOpFilter();
private class NoOpFilter : QueryFilter {
public bool passFilter(long refs, MeshTile tile, Poly poly) {
private class NoOpFilter : QueryFilter
{
public bool passFilter(long refs, MeshTile tile, Poly poly)
{
return true;
}
public float getCost(float[] pa, float[] pb, long prevRef, MeshTile prevTile, Poly prevPoly, long curRef,
MeshTile curTile, Poly curPoly, long nextRef, MeshTile nextTile, Poly nextPoly) {
MeshTile curTile, Poly curPoly, long nextRef, MeshTile nextTile, Poly nextPoly)
{
return 0;
}
}
public void sample(JumpLinkBuilderConfig acfg, RecastBuilderResult result, EdgeSampler es) {
public void sample(JumpLinkBuilderConfig acfg, RecastBuilderResult result, EdgeSampler es)
{
NavMeshQuery navMeshQuery = createNavMesh(result, acfg.agentRadius, acfg.agentHeight, acfg.agentClimb);
sampleGround(acfg, es, (pt, h) => getNavMeshHeight(navMeshQuery, pt, acfg.cellSize, h));
}
private NavMeshQuery createNavMesh(RecastBuilderResult r, float agentRadius, float agentHeight, float agentClimb) {
private NavMeshQuery createNavMesh(RecastBuilderResult r, float agentRadius, float agentHeight, float agentClimb)
{
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = r.getMesh().verts;
option.vertCount = r.getMesh().nverts;
@ -69,27 +70,31 @@ class NavMeshGroundSampler : AbstractGroundSampler {
}
private Tuple<bool, float> getNavMeshHeight(NavMeshQuery navMeshQuery, float[] pt, float cs,
float heightRange) {
float heightRange)
{
float[] halfExtents = new float[] { cs, heightRange, cs };
float maxHeight = pt[1] + heightRange;
AtomicBoolean found = new AtomicBoolean();
AtomicFloat minHeight = new AtomicFloat(pt[1]);
navMeshQuery.queryPolygons(pt, halfExtents, filter, new PolyQueryInvoker((tile, poly, refs) => {
navMeshQuery.queryPolygons(pt, halfExtents, filter, new PolyQueryInvoker((tile, poly, refs) =>
{
Result<float> h = navMeshQuery.getPolyHeight(refs, pt);
if (h.succeeded()) {
if (h.succeeded())
{
float y = h.result;
if (y > minHeight.Get() && y < maxHeight) {
if (y > minHeight.Get() && y < maxHeight)
{
minHeight.Exchange(y);
found.set(true);
}
}
}));
if (found.get()) {
if (found.get())
{
return Tuple.Create(true, minHeight.Get());
}
return Tuple.Create(false, pt[1]);
}
}
}

View File

@ -2,11 +2,10 @@ using System;
namespace DotRecast.Detour.Extras.Jumplink
{
public class Trajectory {
public float lerp(float f, float g, float u) {
public class Trajectory
{
public float lerp(float f, float g, float u)
{
return u * g + (1f - u) * f;
}
@ -14,7 +13,5 @@ public class Trajectory {
{
throw new NotImplementedException();
}
}
}

View File

@ -4,44 +4,53 @@ using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Extras.Jumplink
{
class TrajectorySampler {
public void sample(JumpLinkBuilderConfig acfg, Heightfield heightfield, EdgeSampler es) {
class TrajectorySampler
{
public void sample(JumpLinkBuilderConfig acfg, Heightfield heightfield, EdgeSampler es)
{
int nsamples = es.start.gsamples.Length;
for (int i = 0; i < nsamples; ++i) {
for (int i = 0; i < nsamples; ++i)
{
GroundSample ssmp = es.start.gsamples[i];
foreach (GroundSegment end in es.end) {
foreach (GroundSegment end in es.end)
{
GroundSample esmp = end.gsamples[i];
if (!ssmp.validHeight || !esmp.validHeight) {
if (!ssmp.validHeight || !esmp.validHeight)
{
continue;
}
if (!sampleTrajectory(acfg, heightfield, ssmp.p, esmp.p, es.trajectory)) {
if (!sampleTrajectory(acfg, heightfield, ssmp.p, esmp.p, es.trajectory))
{
continue;
}
ssmp.validTrajectory = true;
esmp.validTrajectory = true;
}
}
}
private bool sampleTrajectory(JumpLinkBuilderConfig acfg, Heightfield solid, float[] pa, float[] pb, Trajectory tra) {
private bool sampleTrajectory(JumpLinkBuilderConfig acfg, Heightfield solid, float[] pa, float[] pb, Trajectory tra)
{
float cs = Math.Min(acfg.cellSize, acfg.cellHeight);
float d = vDist2D(pa, pb) + Math.Abs(pa[1] - pb[1]);
int nsamples = Math.Max(2, (int)Math.Ceiling(d / cs));
for (int i = 0; i < nsamples; ++i) {
for (int i = 0; i < nsamples; ++i)
{
float u = (float)i / (float)(nsamples - 1);
float[] p = tra.apply(pa, pb, u);
if (checkHeightfieldCollision(solid, p[0], p[1] + acfg.groundTolerance, p[1] + acfg.agentHeight, p[2])) {
if (checkHeightfieldCollision(solid, p[0], p[1] + acfg.groundTolerance, p[1] + acfg.agentHeight, p[2]))
{
return false;
}
}
return true;
}
private bool checkHeightfieldCollision(Heightfield solid, float x, float ymin, float ymax, float z) {
private bool checkHeightfieldCollision(Heightfield solid, float x, float ymin, float ymax, float z)
{
int w = solid.width;
int h = solid.height;
float cs = solid.cs;
@ -50,31 +59,35 @@ class TrajectorySampler {
int ix = (int)Math.Floor((x - orig[0]) / cs);
int iz = (int)Math.Floor((z - orig[2]) / cs);
if (ix < 0 || iz < 0 || ix > w || iz > h) {
if (ix < 0 || iz < 0 || ix > w || iz > h)
{
return false;
}
Span s = solid.spans[ix + iz * w];
if (s == null) {
if (s == null)
{
return false;
}
while (s != null) {
while (s != null)
{
float symin = orig[1] + s.smin * ch;
float symax = orig[1] + s.smax * ch;
if (overlapRange(ymin, ymax, symin, symax)) {
if (overlapRange(ymin, ymax, symin, symax))
{
return true;
}
s = s.next;
}
return false;
}
private bool overlapRange(float amin, float amax, float bmin, float bmax) {
private bool overlapRange(float amin, float amax, float bmin, float bmax)
{
return (amin > bmax || amax < bmin) ? false : true;
}
}
}

View File

@ -20,35 +20,44 @@ using System.IO;
namespace DotRecast.Detour.Extras
{
public class ObjExporter {
public void export(NavMesh mesh) {
public class ObjExporter
{
public void export(NavMesh mesh)
{
string filename = Path.Combine(Directory.GetCurrentDirectory(), "Demo", "astar.obj");
using var fs = new FileStream(filename, FileMode.CreateNew);
using var fw = new StreamWriter(fs);
for (int i = 0; i < mesh.getTileCount(); i++) {
for (int i = 0; i < mesh.getTileCount(); i++)
{
MeshTile tile = mesh.getTile(i);
if (tile != null) {
for (int v = 0; v < tile.data.header.vertCount; v++) {
if (tile != null)
{
for (int v = 0; v < tile.data.header.vertCount; v++)
{
fw.Write("v " + tile.data.verts[v * 3] + " " + tile.data.verts[v * 3 + 1] + " "
+ tile.data.verts[v * 3 + 2] + "\n");
}
}
}
int vertexOffset = 1;
for (int i = 0; i < mesh.getTileCount(); i++) {
for (int i = 0; i < mesh.getTileCount(); i++)
{
MeshTile tile = mesh.getTile(i);
if (tile != null) {
for (int p = 0; p < tile.data.header.polyCount; p++) {
if (tile != null)
{
for (int p = 0; p < tile.data.header.polyCount; p++)
{
fw.Write("f ");
Poly poly = tile.data.polys[p];
for (int v = 0; v < poly.vertCount; v++) {
for (int v = 0; v < poly.vertCount; v++)
{
fw.Write(poly.verts[v] + vertexOffset + " ");
}
fw.Write("\n");
}
vertexOffset += tile.data.header.vertCount;
}
}
@ -64,5 +73,4 @@ public class ObjExporter {
*/
}
}

View File

@ -15,70 +15,85 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.Extras
{
public class PolyUtils {
public class PolyUtils
{
/**
* Find edge shared by 2 polygons within the same tile
*/
public static int findEdge(Poly node, Poly neighbour, MeshData tile, MeshData neighbourTile) {
public static int findEdge(Poly node, Poly neighbour, MeshData tile, MeshData neighbourTile)
{
// Compare indices first assuming there are no duplicate vertices
for (int i = 0; i < node.vertCount; i++) {
for (int i = 0; i < node.vertCount; i++)
{
int j = (i + 1) % node.vertCount;
for (int k = 0; k < neighbour.vertCount; k++) {
for (int k = 0; k < neighbour.vertCount; k++)
{
int l = (k + 1) % neighbour.vertCount;
if ((node.verts[i] == neighbour.verts[l] && node.verts[j] == neighbour.verts[k])
|| (node.verts[i] == neighbour.verts[k] && node.verts[j] == neighbour.verts[l])) {
|| (node.verts[i] == neighbour.verts[k] && node.verts[j] == neighbour.verts[l]))
{
return i;
}
}
}
// Fall back to comparing actual positions in case of duplicate vertices
for (int i = 0; i < node.vertCount; i++) {
for (int i = 0; i < node.vertCount; i++)
{
int j = (i + 1) % node.vertCount;
for (int k = 0; k < neighbour.vertCount; k++) {
for (int k = 0; k < neighbour.vertCount; k++)
{
int l = (k + 1) % neighbour.vertCount;
if ((samePosition(tile.verts, node.verts[i], neighbourTile.verts, neighbour.verts[l])
&& samePosition(tile.verts, node.verts[j], neighbourTile.verts, neighbour.verts[k]))
|| (samePosition(tile.verts, node.verts[i], neighbourTile.verts, neighbour.verts[k])
&& samePosition(tile.verts, node.verts[j], neighbourTile.verts, neighbour.verts[l]))) {
&& samePosition(tile.verts, node.verts[j], neighbourTile.verts, neighbour.verts[l])))
{
return i;
}
}
}
return -1;
}
private static bool samePosition(float[] verts, int v, float[] verts2, int v2) {
for (int i = 0; i < 3; i++) {
if (verts[3 * v + i] != verts2[3 * v2 + 1]) {
private static bool samePosition(float[] verts, int v, float[] verts2, int v2)
{
for (int i = 0; i < 3; i++)
{
if (verts[3 * v + i] != verts2[3 * v2 + 1])
{
return false;
}
}
return true;
}
/**
* Find edge closest to the given coordinate
*/
public static int findEdge(Poly node, MeshData tile, float value, int comp) {
public static int findEdge(Poly node, MeshData tile, float value, int comp)
{
float error = float.MaxValue;
int edge = 0;
for (int i = 0; i < node.vertCount; i++) {
for (int i = 0; i < node.vertCount; i++)
{
int j = (i + 1) % node.vertCount;
float v1 = tile.verts[3 * node.verts[i] + comp] - value;
float v2 = tile.verts[3 * node.verts[j] + comp] - value;
float d = v1 * v1 + v2 * v2;
if (d < error) {
if (d < error)
{
error = d;
edge = i;
}
}
return edge;
}
}
}

View File

@ -15,20 +15,19 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.Extras.Unity.Astar
{
public class BVTreeCreator {
public class BVTreeCreator
{
private readonly BVTreeBuilder builder = new BVTreeBuilder();
public void build(GraphMeshData graphData) {
foreach (MeshData d in graphData.tiles) {
public void build(GraphMeshData graphData)
{
foreach (MeshData d in graphData.tiles)
{
builder.build(d);
}
}
}
}

View File

@ -22,30 +22,31 @@ using DotRecast.Core;
namespace DotRecast.Detour.Extras.Unity.Astar
{
class GraphConnectionReader : ZipBinaryReader {
public List<int[]> read(ZipArchive file, string filename, Meta meta, int[] indexToNode) {
class GraphConnectionReader : ZipBinaryReader
{
public List<int[]> read(ZipArchive file, string filename, Meta meta, int[] indexToNode)
{
List<int[]> connections = new List<int[]>();
ByteBuffer buffer = toByteBuffer(file, filename);
while (buffer.remaining() > 0) {
while (buffer.remaining() > 0)
{
int count = buffer.getInt();
int[] nodeConnections = new int[count];
connections.Add(nodeConnections);
for (int i = 0; i < count; i++) {
for (int i = 0; i < count; i++)
{
int nodeIndex = buffer.getInt();
nodeConnections[i] = indexToNode[nodeIndex];
// XXX: Is there anything we can do with the cost?
int cost = buffer.getInt();
if (meta.isVersionAtLeast(Meta.UPDATED_STRUCT_VERSION)) {
if (meta.isVersionAtLeast(Meta.UPDATED_STRUCT_VERSION))
{
byte shapeEdge = buffer.get();
}
}
}
return connections;
}
}
}

View File

@ -20,12 +20,8 @@ using System.Collections.Generic;
namespace DotRecast.Detour.Extras.Unity.Astar
{
public class GraphData {
public class GraphData
{
public readonly Meta meta;
public readonly int[] indexToNode;
public readonly NodeLink2[] nodeLinks2;
@ -34,7 +30,8 @@ public class GraphData {
public readonly List<List<int[]>> graphConnections;
public GraphData(Meta meta, int[] indexToNode, NodeLink2[] nodeLinks2, List<GraphMeta> graphMeta,
List<GraphMeshData> graphMeshData, List<List<int[]>> graphConnections) {
List<GraphMeshData> graphMeshData, List<List<int[]>> graphConnections)
{
this.meta = meta;
this.indexToNode = indexToNode;
this.nodeLinks2 = nodeLinks2;
@ -42,7 +39,5 @@ public class GraphData {
this.graphMeshData = graphMeshData;
this.graphConnections = graphConnections;
}
}
}

View File

@ -15,52 +15,63 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.Extras.Unity.Astar
{
public class GraphMeshData {
public class GraphMeshData
{
public readonly int tileXCount;
public readonly int tileZCount;
public readonly MeshData[] tiles;
public GraphMeshData(int tileXCount, int tileZCount, MeshData[] tiles) {
public GraphMeshData(int tileXCount, int tileZCount, MeshData[] tiles)
{
this.tileXCount = tileXCount;
this.tileZCount = tileZCount;
this.tiles = tiles;
}
public int countNodes() {
public int countNodes()
{
int polyCount = 0;
foreach (MeshData t in tiles) {
foreach (MeshData t in tiles)
{
polyCount += t.header.polyCount;
}
return polyCount;
}
public Poly getNode(int node) {
public Poly getNode(int node)
{
int index = 0;
foreach (MeshData t in tiles) {
if (node - index >= 0 && node - index < t.header.polyCount) {
foreach (MeshData t in tiles)
{
if (node - index >= 0 && node - index < t.header.polyCount)
{
return t.polys[node - index];
}
index += t.header.polyCount;
}
return null;
}
public MeshData getTile(int node) {
public MeshData getTile(int node)
{
int index = 0;
foreach (MeshData t in tiles) {
if (node - index >= 0 && node - index < t.header.polyCount) {
foreach (MeshData t in tiles)
{
if (node - index >= 0 && node - index < t.header.polyCount)
{
return t;
}
index += t.header.polyCount;
}
return null;
}
}
}

View File

@ -23,26 +23,30 @@ using DotRecast.Core;
namespace DotRecast.Detour.Extras.Unity.Astar
{
public class GraphMeshDataReader : ZipBinaryReader {
public class GraphMeshDataReader : ZipBinaryReader
{
public const float INT_PRECISION_FACTOR = 1000f;
public GraphMeshData read(ZipArchive file, string filename, GraphMeta meta, int maxVertPerPoly) {
public GraphMeshData read(ZipArchive file, string filename, GraphMeta meta, int maxVertPerPoly)
{
ByteBuffer buffer = toByteBuffer(file, filename);
int tileXCount = buffer.getInt();
if (tileXCount < 0) {
if (tileXCount < 0)
{
return null;
}
int tileZCount = buffer.getInt();
MeshData[] tiles = new MeshData[tileXCount * tileZCount];
for (int z = 0; z < tileZCount; z++) {
for (int x = 0; x < tileXCount; x++) {
for (int z = 0; z < tileZCount; z++)
{
for (int x = 0; x < tileXCount; x++)
{
int tileIndex = x + z * tileXCount;
int tx = buffer.getInt();
int tz = buffer.getInt();
if (tx != x || tz != z) {
if (tx != x || tz != z)
{
throw new ArgumentException("Inconsistent tile positions");
}
@ -52,18 +56,21 @@ public class GraphMeshDataReader : ZipBinaryReader {
int trisCount = buffer.getInt();
int[] tris = new int[trisCount];
for (int i = 0; i < tris.Length; i++) {
for (int i = 0; i < tris.Length; i++)
{
tris[i] = buffer.getInt();
}
int vertsCount = buffer.getInt();
float[] verts = new float[3 * vertsCount];
for (int i = 0; i < verts.Length; i++) {
for (int i = 0; i < verts.Length; i++)
{
verts[i] = buffer.getInt() / INT_PRECISION_FACTOR;
}
int[] vertsInGraphSpace = new int[3 * buffer.getInt()];
for (int i = 0; i < vertsInGraphSpace.Length; i++) {
for (int i = 0; i < vertsInGraphSpace.Length; i++)
{
vertsInGraphSpace[i] = buffer.getInt();
}
@ -75,7 +82,8 @@ public class GraphMeshDataReader : ZipBinaryReader {
int vertMask = getVertMask(vertsCount);
float ymin = float.PositiveInfinity;
float ymax = float.NegativeInfinity;
for (int i = 0; i < nodes.Length; i++) {
for (int i = 0; i < nodes.Length; i++)
{
nodes[i] = new Poly(i, maxVertPerPoly);
nodes[i].vertCount = 3;
// XXX: What can we do with the penalty?
@ -136,6 +144,7 @@ public class GraphMeshDataReader : ZipBinaryReader {
tiles[tileIndex].header = header;
}
}
return new GraphMeshData(tileXCount, tileZCount, tiles);
}
@ -153,13 +162,13 @@ public class GraphMeshDataReader : ZipBinaryReader {
private int getVertMask(int vertsCount)
{
int vertMask = highestOneBit((uint)vertsCount);
if (vertMask != vertsCount) {
if (vertMask != vertsCount)
{
vertMask *= 2;
}
vertMask--;
return vertMask;
}
}
}

View File

@ -15,13 +15,11 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.Extras.Unity.Astar
{
public class GraphMeta {
public class GraphMeta
{
public float characterRadius { get; set; }
public float contourMaxError { get; set; }
public float cellSize { get; set; }
@ -30,14 +28,16 @@ public class GraphMeta {
public float maxSlope { get; set; }
public float maxEdgeLength { get; set; }
public float minRegionSize { get; set; }
/** Size of tile along X axis in voxels */
public float tileSizeX { get; set; }
/** Size of tile along Z axis in voxels */
public float tileSizeZ { get; set; }
public bool useTiles { get; set; }
public Vector3f rotation { get; set; }
public Vector3f forcedBoundsCenter { get; set; }
public Vector3f forcedBoundsSize { get; set; }
}
}

View File

@ -22,15 +22,15 @@ using System.Text.Json;
namespace DotRecast.Detour.Extras.Unity.Astar
{
public class GraphMetaReader {
public GraphMeta read(ZipArchive file, string filename) {
public class GraphMetaReader
{
public GraphMeta read(ZipArchive file, string filename)
{
ZipArchiveEntry entry = file.GetEntry(filename);
using StreamReader reader = new StreamReader(entry.Open());
JsonSerializerOptions options = new JsonSerializerOptions {
JsonSerializerOptions options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
@ -38,5 +38,4 @@ public class GraphMetaReader {
return JsonSerializer.Deserialize<GraphMeta>(json, options);
}
}
}

View File

@ -21,21 +21,25 @@ using System.Collections.Generic;
namespace DotRecast.Detour.Extras.Unity.Astar
{
public class LinkBuilder {
public class LinkBuilder
{
// Process connections and transform them into recast neighbour flags
public void build(int nodeOffset, GraphMeshData graphData, List<int[]> connections) {
for (int n = 0; n < connections.Count; n++) {
public void build(int nodeOffset, GraphMeshData graphData, List<int[]> connections)
{
for (int n = 0; n < connections.Count; n++)
{
int[] nodeConnections = connections[n];
MeshData tile = graphData.getTile(n);
Poly node = graphData.getNode(n);
foreach (int connection in nodeConnections) {
foreach (int connection in nodeConnections)
{
MeshData neighbourTile = graphData.getTile(connection - nodeOffset);
if (neighbourTile != tile) {
if (neighbourTile != tile)
{
buildExternalLink(tile, node, neighbourTile);
} else {
}
else
{
Poly neighbour = graphData.getNode(connection - nodeOffset);
buildInternalLink(tile, node, neighbourTile, neighbour);
}
@ -43,28 +47,38 @@ public class LinkBuilder {
}
}
private void buildInternalLink(MeshData tile, Poly node, MeshData neighbourTile, Poly neighbour) {
private void buildInternalLink(MeshData tile, Poly node, MeshData neighbourTile, Poly neighbour)
{
int edge = PolyUtils.findEdge(node, neighbour, tile, neighbourTile);
if (edge >= 0) {
if (edge >= 0)
{
node.neis[edge] = neighbour.index + 1;
} else {
}
else
{
throw new ArgumentException();
}
}
// In case of external link to other tiles we must find the direction
private void buildExternalLink(MeshData tile, Poly node, MeshData neighbourTile) {
if (neighbourTile.header.bmin[0] > tile.header.bmin[0]) {
private void buildExternalLink(MeshData tile, Poly node, MeshData neighbourTile)
{
if (neighbourTile.header.bmin[0] > tile.header.bmin[0])
{
node.neis[PolyUtils.findEdge(node, tile, neighbourTile.header.bmin[0], 0)] = NavMesh.DT_EXT_LINK;
} else if (neighbourTile.header.bmin[0] < tile.header.bmin[0]) {
}
else if (neighbourTile.header.bmin[0] < tile.header.bmin[0])
{
node.neis[PolyUtils.findEdge(node, tile, tile.header.bmin[0], 0)] = NavMesh.DT_EXT_LINK | 4;
} else if (neighbourTile.header.bmin[2] > tile.header.bmin[2]) {
}
else if (neighbourTile.header.bmin[2] > tile.header.bmin[2])
{
node.neis[PolyUtils.findEdge(node, tile, neighbourTile.header.bmin[2], 2)] = NavMesh.DT_EXT_LINK | 2;
} else {
}
else
{
node.neis[PolyUtils.findEdge(node, tile, tile.header.bmin[2], 2)] = NavMesh.DT_EXT_LINK | 6;
}
}
}
}

View File

@ -21,10 +21,8 @@ using System.Text.RegularExpressions;
namespace DotRecast.Detour.Extras.Unity.Astar
{
public class Meta {
public class Meta
{
public const string TYPENAME_RECAST_GRAPH = "Pathfinding.RecastGraph";
public const string MIN_SUPPORTED_VERSION = "4.0.6";
public const string UPDATED_STRUCT_VERSION = "4.1.16";
@ -34,44 +32,58 @@ public class Meta {
public string[] guids { get; set; }
public string[] typeNames { get; set; }
public bool isSupportedVersion() {
public bool isSupportedVersion()
{
return isVersionAtLeast(MIN_SUPPORTED_VERSION);
}
public bool isVersionAtLeast(string minVersion) {
public bool isVersionAtLeast(string minVersion)
{
int[] actual = parseVersion(version);
int[] minSupported = parseVersion(minVersion);
for (int i = 0; i < Math.Min(actual.Length, minSupported.Length); i++) {
if (actual[i] > minSupported[i]) {
for (int i = 0; i < Math.Min(actual.Length, minSupported.Length); i++)
{
if (actual[i] > minSupported[i])
{
return true;
} else if (minSupported[i] > actual[i]) {
}
else if (minSupported[i] > actual[i])
{
return false;
}
}
return true;
}
private int[] parseVersion(string version) {
private int[] parseVersion(string version)
{
Match m = VERSION_PATTERN.Match(version);
if (m.Success) {
if (m.Success)
{
int[] v = new int[m.Groups.Count - 1];
for (int i = 0; i < v.Length; i++) {
for (int i = 0; i < v.Length; i++)
{
v[i] = int.Parse(m.Groups[i + 1].Value);
}
return v;
}
throw new ArgumentException("Invalid version format: " + version);
}
public bool isSupportedType() {
foreach (string t in typeNames) {
if (t == TYPENAME_RECAST_GRAPH) {
public bool isSupportedType()
{
foreach (string t in typeNames)
{
if (t == TYPENAME_RECAST_GRAPH)
{
return true;
}
}
return false;
}
}
}

View File

@ -25,16 +25,14 @@ using System.Text.RegularExpressions;
namespace DotRecast.Detour.Extras.Unity.Astar
{
public class MetaReader {
public Meta read(ZipArchive file, string filename) {
public class MetaReader
{
public Meta read(ZipArchive file, string filename)
{
ZipArchiveEntry entry = file.GetEntry(filename);
using StreamReader reader = new StreamReader(entry.Open());
var json = reader.ReadToEnd();
// fixed : version 표기는 문자열이여야 한다
@ -44,15 +42,17 @@ public class MetaReader {
json = regex.Replace(json, replacement);
var meta = JsonSerializer.Deserialize<Meta>(json);
if (!meta.isSupportedType()) {
if (!meta.isSupportedType())
{
throw new ArgumentException("Unsupported graph type " + string.Join(", ", meta.typeNames));
}
if (!meta.isSupportedVersion()) {
if (!meta.isSupportedVersion())
{
throw new ArgumentException("Unsupported version " + meta.version);
}
return meta;
}
}
}

View File

@ -21,22 +21,21 @@ using DotRecast.Core;
namespace DotRecast.Detour.Extras.Unity.Astar
{
class NodeIndexReader : ZipBinaryReader {
public int[] read(ZipArchive file, string filename) {
class NodeIndexReader : ZipBinaryReader
{
public int[] read(ZipArchive file, string filename)
{
ByteBuffer buffer = toByteBuffer(file, filename);
int maxNodeIndex = buffer.getInt();
int[] int2Node = new int[maxNodeIndex + 1];
int node = 0;
while (buffer.remaining() > 0) {
while (buffer.remaining() > 0)
{
int index = buffer.getInt();
int2Node[index] = node++;
}
return int2Node;
}
}
}

View File

@ -15,25 +15,24 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.Extras.Unity.Astar
{
public class NodeLink2 {
public class NodeLink2
{
public readonly long linkID;
public readonly int startNode;
public readonly int endNode;
public readonly Vector3f clamped1;
public readonly Vector3f clamped2;
public NodeLink2(long linkID, int startNode, int endNode, Vector3f clamped1, Vector3f clamped2) : base() {
public NodeLink2(long linkID, int startNode, int endNode, Vector3f clamped1, Vector3f clamped2) : base()
{
this.linkID = linkID;
this.startNode = startNode;
this.endNode = endNode;
this.clamped1 = clamped1;
this.clamped2 = clamped2;
}
}
}

View File

@ -21,15 +21,15 @@ using DotRecast.Core;
namespace DotRecast.Detour.Extras.Unity.Astar
{
public class NodeLink2Reader : ZipBinaryReader {
public NodeLink2[] read(ZipArchive file, string filename, int[] indexToNode) {
public class NodeLink2Reader : ZipBinaryReader
{
public NodeLink2[] read(ZipArchive file, string filename, int[] indexToNode)
{
ByteBuffer buffer = toByteBuffer(file, filename);
int linkCount = buffer.getInt();
NodeLink2[] links = new NodeLink2[linkCount];
for (int i = 0; i < linkCount; i++) {
for (int i = 0; i < linkCount; i++)
{
long linkID = buffer.getLong();
int startNode = indexToNode[buffer.getInt()];
int endNode = indexToNode[buffer.getInt()];
@ -46,8 +46,8 @@ public class NodeLink2Reader : ZipBinaryReader {
bool postScanCalled = buffer.get() != 0;
links[i] = new NodeLink2(linkID, startNode, endNode, clamped1, clamped2);
}
return links;
}
}
}

View File

@ -20,18 +20,20 @@ using DotRecast.Core;
namespace DotRecast.Detour.Extras.Unity.Astar
{
public class OffMeshLinkCreator {
public void build(GraphMeshData graphData, NodeLink2[] links, int nodeOffset) {
if (links.Length > 0) {
foreach (NodeLink2 l in links) {
public class OffMeshLinkCreator
{
public void build(GraphMeshData graphData, NodeLink2[] links, int nodeOffset)
{
if (links.Length > 0)
{
foreach (NodeLink2 l in links)
{
MeshData startTile = graphData.getTile(l.startNode - nodeOffset);
Poly startNode = graphData.getNode(l.startNode - nodeOffset);
MeshData endTile = graphData.getTile(l.endNode - nodeOffset);
Poly endNode = graphData.getNode(l.endNode - nodeOffset);
if (startNode != null && endNode != null) {
if (startNode != null && endNode != null)
{
// FIXME: Optimise
startTile.polys = ArrayUtils.CopyOf(startTile.polys, startTile.polys.Length + 1);
int poly = startTile.header.polyCount;
@ -44,18 +46,26 @@ public class OffMeshLinkCreator {
startTile.header.vertCount += 2;
OffMeshConnection connection = new OffMeshConnection();
connection.poly = poly;
connection.pos = new float[] { l.clamped1.x, l.clamped1.y, l.clamped1.z, l.clamped2.x, l.clamped2.y,
l.clamped2.z };
connection.pos = new float[]
{
l.clamped1.x, l.clamped1.y, l.clamped1.z, l.clamped2.x, l.clamped2.y,
l.clamped2.z
};
connection.rad = 0.1f;
connection.side = startTile == endTile ? 0xFF
connection.side = startTile == endTile
? 0xFF
: NavMeshBuilder.classifyOffMeshPoint(new VectorPtr(connection.pos, 3),
startTile.header.bmin, startTile.header.bmax);
connection.userId = (int)l.linkID;
if (startTile.offMeshCons == null) {
if (startTile.offMeshCons == null)
{
startTile.offMeshCons = new OffMeshConnection[1];
} else {
}
else
{
startTile.offMeshCons = ArrayUtils.CopyOf(startTile.offMeshCons, startTile.offMeshCons.Length + 1);
}
startTile.offMeshCons[startTile.offMeshCons.Length - 1] = connection;
startTile.header.offMeshConCount++;
}
@ -63,5 +73,4 @@ public class OffMeshLinkCreator {
}
}
}
}

View File

@ -22,34 +22,36 @@ using System.IO;
namespace DotRecast.Detour.Extras.Unity.Astar
{
/**
* Import navmeshes created with A* Pathfinding Project Unity plugin (https://arongranberg.com/astar/). Graph data is
* loaded from a zip archive and converted to Recast navmesh objects.
*/
public class UnityAStarPathfindingImporter {
public class UnityAStarPathfindingImporter
{
private readonly UnityAStarPathfindingReader reader = new UnityAStarPathfindingReader();
private readonly BVTreeCreator bvTreeCreator = new BVTreeCreator();
private readonly LinkBuilder linkCreator = new LinkBuilder();
private readonly OffMeshLinkCreator offMeshLinkCreator = new OffMeshLinkCreator();
public NavMesh[] load(FileStream zipFile) {
public NavMesh[] load(FileStream zipFile)
{
GraphData graphData = reader.read(zipFile);
Meta meta = graphData.meta;
NodeLink2[] nodeLinks2 = graphData.nodeLinks2;
NavMesh[] meshes = new NavMesh[meta.graphs];
int nodeOffset = 0;
for (int graphIndex = 0; graphIndex < meta.graphs; graphIndex++) {
for (int graphIndex = 0; graphIndex < meta.graphs; graphIndex++)
{
GraphMeta graphMeta = graphData.graphMeta[graphIndex];
GraphMeshData graphMeshData = graphData.graphMeshData[graphIndex];
List<int[]> connections = graphData.graphConnections[graphIndex];
int nodeCount = graphMeshData.countNodes();
if (connections.Count != nodeCount) {
if (connections.Count != nodeCount)
{
throw new ArgumentException("Inconsistent number of nodes in data file: " + nodeCount
+ " and connecton files: " + connections.Count);
}
// Build BV tree
bvTreeCreator.build(graphMeshData);
// Create links between nodes (both internal and portals between tiles)
@ -65,15 +67,16 @@ public class UnityAStarPathfindingImporter {
option.orig[1] = -0.5f * graphMeta.forcedBoundsSize.y + graphMeta.forcedBoundsCenter.y;
option.orig[2] = -0.5f * graphMeta.forcedBoundsSize.z + graphMeta.forcedBoundsCenter.z;
NavMesh mesh = new NavMesh(option, 3);
foreach (MeshData t in graphMeshData.tiles) {
foreach (MeshData t in graphMeshData.tiles)
{
mesh.addTile(t, 0, 0);
}
meshes[graphIndex] = mesh;
nodeOffset += graphMeshData.countNodes();
}
return meshes;
}
}
}

View File

@ -22,10 +22,8 @@ using System.IO.Compression;
namespace DotRecast.Detour.Extras.Unity.Astar
{
public class UnityAStarPathfindingReader {
public class UnityAStarPathfindingReader
{
private const string META_FILE_NAME = "meta.json";
private const string NODE_INDEX_FILE_NAME = "graph_references.binary";
private const string NODE_LINK_2_FILE_NAME = "node_link2.binary";
@ -40,7 +38,8 @@ public class UnityAStarPathfindingReader {
private readonly GraphConnectionReader graphConnectionReader = new GraphConnectionReader();
private readonly NodeLink2Reader nodeLink2Reader = new NodeLink2Reader();
public GraphData read(FileStream zipFile) {
public GraphData read(FileStream zipFile)
{
using ZipArchive file = new ZipArchive(zipFile);
// Read meta file and check version and graph type
Meta meta = metaReader.read(file, META_FILE_NAME);
@ -52,7 +51,8 @@ public class UnityAStarPathfindingReader {
List<GraphMeta> metaList = new List<GraphMeta>();
List<GraphMeshData> meshDataList = new List<GraphMeshData>();
List<List<int[]>> connectionsList = new List<List<int[]>>();
for (int graphIndex = 0; graphIndex < meta.graphs; graphIndex++) {
for (int graphIndex = 0; graphIndex < meta.graphs; graphIndex++)
{
GraphMeta graphMeta = graphMetaReader.read(file, string.Format(GRAPH_META_FILE_NAME_PATTERN, graphIndex));
// First graph mesh data - vertices and polygons
GraphMeshData graphData = graphDataReader.read(file,
@ -64,8 +64,8 @@ public class UnityAStarPathfindingReader {
meshDataList.Add(graphData);
connectionsList.Add(connections);
}
return new GraphData(meta, indexToNode, nodeLinks2, metaList, meshDataList, connectionsList);
}
}
}

View File

@ -23,11 +23,10 @@ using DotRecast.Detour.Io;
namespace DotRecast.Detour.Extras.Unity.Astar
{
public abstract class ZipBinaryReader {
protected ByteBuffer toByteBuffer(ZipArchive file, string filename) {
public abstract class ZipBinaryReader
{
protected ByteBuffer toByteBuffer(ZipArchive file, string filename)
{
ZipArchiveEntry graphReferences = file.GetEntry(filename);
using var entryStream = graphReferences.Open();
using var bis = new BinaryReader(entryStream);
@ -35,7 +34,5 @@ public abstract class ZipBinaryReader {
buffer.order(ByteOrder.LITTLE_ENDIAN);
return buffer;
}
}
}

View File

@ -15,25 +15,24 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.Extras
{
public class Vector3f {
public class Vector3f
{
public float x { get; set; }
public float y { get; set; }
public float z { get; set; }
public Vector3f() {
public Vector3f()
{
}
public Vector3f(float x, float y, float z) {
public Vector3f(float x, float y, float z)
{
this.x = x;
this.y = y;
this.z = z;
}
}
}

View File

@ -27,34 +27,43 @@ using DotRecast.Core;
namespace DotRecast.Detour.TileCache
{
public abstract class AbstractTileLayersBuilder {
protected List<byte[]> build(ByteOrder order, bool cCompatibility, int threads, int tw, int th) {
if (threads == 1) {
public abstract class AbstractTileLayersBuilder
{
protected List<byte[]> build(ByteOrder order, bool cCompatibility, int threads, int tw, int th)
{
if (threads == 1)
{
return buildSingleThread(order, cCompatibility, tw, th);
}
return buildMultiThread(order, cCompatibility, tw, th, threads);
}
private List<byte[]> buildSingleThread(ByteOrder order, bool cCompatibility, int tw, int th) {
private List<byte[]> buildSingleThread(ByteOrder order, bool cCompatibility, int tw, int th)
{
List<byte[]> layers = new List<byte[]>();
for (int y = 0; y < th; ++y) {
for (int x = 0; x < tw; ++x) {
for (int y = 0; y < th; ++y)
{
for (int x = 0; x < tw; ++x)
{
layers.AddRange(build(x, y, order, cCompatibility));
}
}
return layers;
}
private List<byte[]> buildMultiThread(ByteOrder order, bool cCompatibility, int tw, int th, int threads) {
private List<byte[]> buildMultiThread(ByteOrder order, bool cCompatibility, int tw, int th, int threads)
{
var tasks = new ConcurrentQueue<Task<Tuple<int, int, List<byte[]>>>>();
for (int y = 0; y < th; ++y) {
for (int x = 0; x < tw; ++x) {
for (int y = 0; y < th; ++y)
{
for (int x = 0; x < tw; ++x)
{
int tx = x;
int ty = y;
var task = Task.Run(() => {
var task = Task.Run(() =>
{
var partial = build(tx, ty, order, cCompatibility);
return Tuple.Create(tx, ty, partial);
});
@ -67,16 +76,18 @@ public abstract class AbstractTileLayersBuilder {
.ToDictionary(x => Tuple.Create(x.Item1, x.Item2), x => x.Item3);
List<byte[]> layers = new List<byte[]>();
for (int y = 0; y < th; ++y) {
for (int x = 0; x < tw; ++x) {
for (int y = 0; y < th; ++y)
{
for (int x = 0; x < tw; ++x)
{
var key = Tuple.Create(x, y);
layers.AddRange(partialResults[key]);
}
}
return layers;
}
protected abstract List<byte[]> build(int tx, int ty, ByteOrder order, bool cCompatibility);
}
}

View File

@ -17,23 +17,26 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache
{
public class CompressedTile {
public class CompressedTile
{
public readonly int index;
public int salt; /// < Counter describing modifications to the tile.
public int salt;
/// < Counter describing modifications to the tile.
public TileCacheLayerHeader header;
public byte[] data;
public int compressed; // offset of compressed data
public int flags;
public CompressedTile next;
public CompressedTile(int index) {
public CompressedTile(int index)
{
this.index = index;
salt = 1;
}
}
}

View File

@ -18,8 +18,6 @@ using System;
namespace DotRecast.Detour.TileCache.Io.Compress
{
/**
* Core of FastLZ compression algorithm.
*
@ -29,8 +27,8 @@ namespace DotRecast.Detour.TileCache.Io.Compress
* This is refactored code of <a href="https://code.google.com/p/jfastlz/">jfastlz</a>
* library written by William Kinney.
*/
public class FastLz {
public class FastLz
{
private static readonly int MAX_DISTANCE = 8191;
private static readonly int MAX_FARDISTANCE = 65535 + MAX_DISTANCE - 1;
@ -84,7 +82,8 @@ public class FastLz {
* @param inputLength length of input buffer
* @return Maximum output buffer length
*/
public static int calculateOutputBufferLength(int inputLength) {
public static int calculateOutputBufferLength(int inputLength)
{
int tempOutputLength = (int)(inputLength * 1.06);
return Math.Max(tempOutputLength, 66);
}
@ -96,11 +95,15 @@ public class FastLz {
* If the input is not compressible, the return value might be larger than length (input buffer size).
*/
public static int compress(byte[] input, int inOffset, int inLength,
byte[] output, int outOffset, int proposedLevel) {
byte[] output, int outOffset, int proposedLevel)
{
int level;
if (proposedLevel == LEVEL_AUTO) {
if (proposedLevel == LEVEL_AUTO)
{
level = inLength < MIN_RECOMENDED_LENGTH_FOR_LEVEL_2 ? LEVEL_1 : LEVEL_2;
} else {
}
else
{
level = proposedLevel;
}
@ -122,23 +125,29 @@ public class FastLz {
int copy;
/* sanity check */
if (inLength < 4) {
if (inLength != 0) {
if (inLength < 4)
{
if (inLength != 0)
{
// *op++ = length-1;
output[outOffset + op++] = (byte)(inLength - 1);
ipBound++;
while (ip <= ipBound) {
while (ip <= ipBound)
{
output[outOffset + op++] = input[inOffset + ip++];
}
return inLength + 1;
}
// else
return 0;
}
/* initializes hash table */
// for (hslot = htab; hslot < htab + HASH_SIZE; hslot++)
for (hslot = 0; hslot < HASH_SIZE; hslot++) {
for (hslot = 0; hslot < HASH_SIZE; hslot++)
{
//*hslot = ip;
htab[hslot] = ip;
}
@ -150,7 +159,8 @@ public class FastLz {
output[outOffset + op++] = input[inOffset + ip++];
/* main loop */
while (ip < ipLimit) {
while (ip < ipLimit)
{
int refs = 0;
long distance = 0;
@ -166,10 +176,12 @@ public class FastLz {
bool matchLabel = false;
/* check for a run */
if (level == LEVEL_2) {
if (level == LEVEL_2)
{
//if(ip[0] == ip[-1] && FASTLZ_READU16(ip-1)==FASTLZ_READU16(ip+1))
if (input[inOffset + ip] == input[inOffset + ip - 1] &&
readU16(input, inOffset + ip - 1) == readU16(input, inOffset + ip + 1)) {
readU16(input, inOffset + ip - 1) == readU16(input, inOffset + ip + 1))
{
distance = 1;
ip += 3;
refs = anchor - 1 + 3;
@ -180,7 +192,9 @@ public class FastLz {
matchLabel = true;
}
}
if (!matchLabel) {
if (!matchLabel)
{
/* find potential match */
// HASH_FUNCTION(hval,ip);
hval = hashFunction(input, inOffset + ip);
@ -201,41 +215,51 @@ public class FastLz {
|| (level == LEVEL_1 ? distance >= MAX_DISTANCE : distance >= MAX_FARDISTANCE)
|| input[inOffset + refs++] != input[inOffset + ip++]
|| input[inOffset + refs++] != input[inOffset + ip++]
|| input[inOffset + refs++] != input[inOffset + ip++]) {
|| input[inOffset + refs++] != input[inOffset + ip++])
{
/*
* goto literal;
*/
output[outOffset + op++] = input[inOffset + anchor++];
ip = anchor;
copy++;
if (copy == MAX_COPY) {
if (copy == MAX_COPY)
{
copy = 0;
output[outOffset + op++] = (byte)(MAX_COPY - 1);
}
continue;
}
if (level == LEVEL_2) {
if (level == LEVEL_2)
{
/* far, needs at least 5-byte match */
if (distance >= MAX_DISTANCE) {
if (distance >= MAX_DISTANCE)
{
if (input[inOffset + ip++] != input[inOffset + refs++]
|| input[inOffset + ip++] != input[inOffset + refs++]) {
|| input[inOffset + ip++] != input[inOffset + refs++])
{
/*
* goto literal;
*/
output[outOffset + op++] = input[inOffset + anchor++];
ip = anchor;
copy++;
if (copy == MAX_COPY) {
if (copy == MAX_COPY)
{
copy = 0;
output[outOffset + op++] = (byte)(MAX_COPY - 1);
}
continue;
}
len += 2;
}
}
} // end if(!matchLabel)
/*
* match:
*/
@ -245,59 +269,89 @@ public class FastLz {
/* distance is biased */
distance--;
if (distance == 0) {
if (distance == 0)
{
/* zero distance means a run */
//flzuint8 x = ip[-1];
byte x = input[inOffset + ip - 1];
while (ip < ipBound) {
if (input[inOffset + refs++] != x) {
while (ip < ipBound)
{
if (input[inOffset + refs++] != x)
{
break;
} else {
}
else
{
ip++;
}
}
} else {
for (;;) {
}
else
{
for (;;)
{
/* safe because the outer check against ip limit */
if (input[inOffset + refs++] != input[inOffset + ip++]) {
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++]) {
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++]) {
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++]) {
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++]) {
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++]) {
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++]) {
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
if (input[inOffset + refs++] != input[inOffset + ip++]) {
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
while (ip < ipBound) {
if (input[inOffset + refs++] != input[inOffset + ip++]) {
while (ip < ipBound)
{
if (input[inOffset + refs++] != input[inOffset + ip++])
{
break;
}
}
break;
}
}
/* if we have copied something, adjust the copy count */
if (copy != 0) {
if (copy != 0)
{
/* copy is biased, '0' means 1 byte copy */
// *(op-copy-1) = copy-1;
output[outOffset + op - copy - 1] = (byte)(copy - 1);
} else {
}
else
{
/* back, to overwrite the copy count */
op--;
}
@ -310,42 +364,60 @@ public class FastLz {
len = ip - anchor;
/* encode the match */
if (level == LEVEL_2) {
if (distance < MAX_DISTANCE) {
if (len < 7) {
if (level == LEVEL_2)
{
if (distance < MAX_DISTANCE)
{
if (len < 7)
{
output[outOffset + op++] = (byte)((len << 5) + (int)((ulong)distance >> 8));
output[outOffset + op++] = (byte)(distance & 255);
} else {
}
else
{
output[outOffset + op++] = (byte)((7 << 5) + ((ulong)distance >> 8));
for (len -= 7; len >= 255; len -= 255) {
for (len -= 7; len >= 255; len -= 255)
{
output[outOffset + op++] = (byte)255;
}
output[outOffset + op++] = (byte)len;
output[outOffset + op++] = (byte)(distance & 255);
}
} else {
}
else
{
/* far away, but not yet in the another galaxy... */
if (len < 7) {
if (len < 7)
{
distance -= MAX_DISTANCE;
output[outOffset + op++] = (byte)((len << 5) + 31);
output[outOffset + op++] = (byte)255;
output[outOffset + op++] = (byte)((ulong)distance >> 8);
output[outOffset + op++] = (byte)(distance & 255);
} else {
}
else
{
distance -= MAX_DISTANCE;
output[outOffset + op++] = (byte)((7 << 5) + 31);
for (len -= 7; len >= 255; len -= 255) {
for (len -= 7; len >= 255; len -= 255)
{
output[outOffset + op++] = (byte)255;
}
output[outOffset + op++] = (byte)len;
output[outOffset + op++] = (byte)255;
output[outOffset + op++] = (byte)((ulong)distance >> 8);
output[outOffset + op++] = (byte)(distance & 255);
}
}
} else {
if (len > MAX_LEN - 2) {
while (len > MAX_LEN - 2) {
}
else
{
if (len > MAX_LEN - 2)
{
while (len > MAX_LEN - 2)
{
output[outOffset + op++] = (byte)((7 << 5) + ((ulong)distance >> 8));
output[outOffset + op++] = (byte)(MAX_LEN - 2 - 7 - 2);
output[outOffset + op++] = (byte)(distance & 255);
@ -353,10 +425,13 @@ public class FastLz {
}
}
if (len < 7) {
if (len < 7)
{
output[outOffset + op++] = (byte)((len << 5) + (int)((ulong)distance >> 8));
output[outOffset + op++] = (byte)(distance & 255);
} else {
}
else
{
output[outOffset + op++] = (byte)((7 << 5) + (int)((ulong)distance >> 8));
output[outOffset + op++] = (byte)(len - 7);
output[outOffset + op++] = (byte)(distance & 255);
@ -393,24 +468,30 @@ public class FastLz {
/* left-over as literal copy */
ipBound++;
while (ip <= ipBound) {
while (ip <= ipBound)
{
output[outOffset + op++] = input[inOffset + ip++];
copy++;
if (copy == MAX_COPY) {
if (copy == MAX_COPY)
{
copy = 0;
output[outOffset + op++] = (byte)(MAX_COPY - 1);
}
}
/* if we have copied something, adjust the copy length */
if (copy != 0) {
if (copy != 0)
{
//*(op-copy-1) = copy-1;
output[outOffset + op - copy - 1] = (byte)(copy - 1);
} else {
}
else
{
op--;
}
if (level == LEVEL_2) {
if (level == LEVEL_2)
{
/* marker for fastlz2 */
output[outOffset] |= 1 << 5;
}
@ -427,10 +508,12 @@ public class FastLz {
* more than what is specified in outLength.
*/
public static int decompress(byte[] input, int inOffset, int inLength,
byte[] output, int outOffset, int outLength) {
byte[] output, int outOffset, int outLength)
{
//int level = ((*(const flzuint8*)input) >> 5) + 1;
int level = (input[inOffset] >> 5) + 1;
if (level != LEVEL_1 && level != LEVEL_2) {
if (level != LEVEL_1 && level != LEVEL_2)
{
throw new Exception($"invalid level: {level} (expected: {LEVEL_1} or {LEVEL_2})");
}
@ -442,7 +525,8 @@ public class FastLz {
long ctrl = input[inOffset + ip++] & 31;
int loop = 1;
do {
do
{
// const flzuint8* refs = op;
int refs = op;
// flzuint32 len = ctrl >> 5;
@ -450,34 +534,45 @@ public class FastLz {
// flzuint32 ofs = (ctrl & 31) << 8;
long ofs = (ctrl & 31) << 8;
if (ctrl >= 32) {
if (ctrl >= 32)
{
len--;
// refs -= ofs;
refs -= (int)ofs;
int code;
if (len == 6) {
if (level == LEVEL_1) {
if (len == 6)
{
if (level == LEVEL_1)
{
// len += *ip++;
len += input[inOffset + ip++] & 0xFF;
} else {
do {
}
else
{
do
{
code = input[inOffset + ip++] & 0xFF;
len += code;
} while (code == 255);
}
}
if (level == LEVEL_1) {
if (level == LEVEL_1)
{
// refs -= *ip++;
refs -= input[inOffset + ip++] & 0xFF;
} else {
}
else
{
code = input[inOffset + ip++] & 0xFF;
refs -= code;
/* match from 16-bit distance */
// if(FASTLZ_UNEXPECT_CONDITIONAL(code==255))
// if(FASTLZ_EXPECT_CONDITIONAL(ofs==(31 << 8)))
if (code == 255 && ofs == 31 << 8) {
if (code == 255 && ofs == 31 << 8)
{
ofs = (input[inOffset + ip++] & 0xFF) << 8;
ofs += input[inOffset + ip++] & 0xFF;
@ -486,35 +581,44 @@ public class FastLz {
}
// if the output index + length of block(?) + 3(?) is over the output limit?
if (op + len + 3 > outLength) {
if (op + len + 3 > outLength)
{
return 0;
}
// if (FASTLZ_UNEXPECT_CONDITIONAL(refs-1 < (flzuint8 *)output))
// if the address space of refs-1 is < the address of output?
// if we are still at the beginning of the output address?
if (refs - 1 < 0) {
if (refs - 1 < 0)
{
return 0;
}
if (ip < inLength) {
if (ip < inLength)
{
ctrl = input[inOffset + ip++] & 0xFF;
} else {
}
else
{
loop = 0;
}
if (refs == op) {
if (refs == op)
{
/* optimize copy for a run */
// flzuint8 b = refs[-1];
byte b = output[outOffset + refs - 1];
output[outOffset + op++] = b;
output[outOffset + op++] = b;
output[outOffset + op++] = b;
while (len != 0) {
while (len != 0)
{
output[outOffset + op++] = b;
--len;
}
} else {
}
else
{
/* copy from reference */
refs--;
@ -523,31 +627,39 @@ public class FastLz {
output[outOffset + op++] = output[outOffset + refs++];
output[outOffset + op++] = output[outOffset + refs++];
while (len != 0) {
while (len != 0)
{
output[outOffset + op++] = output[outOffset + refs++];
--len;
}
}
} else {
}
else
{
ctrl++;
if (op + ctrl > outLength) {
if (op + ctrl > outLength)
{
return 0;
}
if (ip + ctrl > inLength) {
if (ip + ctrl > inLength)
{
return 0;
}
//*op++ = *ip++;
output[outOffset + op++] = input[inOffset + ip++];
for (--ctrl; ctrl != 0; ctrl--) {
for (--ctrl; ctrl != 0; ctrl--)
{
// *op++ = *ip++;
output[outOffset + op++] = input[inOffset + ip++];
}
loop = ip < inLength ? 1 : 0;
if (loop != 0) {
if (loop != 0)
{
// ctrl = *ip++;
ctrl = input[inOffset + ip++] & 0xFF;
}
@ -560,20 +672,26 @@ public class FastLz {
return op;
}
private static int hashFunction(byte[] p, int offset) {
private static int hashFunction(byte[] p, int offset)
{
int v = readU16(p, offset);
v ^= readU16(p, offset + 1) ^ v >> 16 - HASH_LOG;
v &= HASH_MASK;
return v;
}
private static int readU16(byte[] data, int offset) {
if (offset + 1 >= data.Length) {
private static int readU16(byte[] data, int offset)
{
if (offset + 1 >= data.Length)
{
return data[offset] & 0xff;
}
return (data[offset + 1] & 0xff) << 8 | data[offset] & 0xff;
}
private FastLz() { }
private FastLz()
{
}
}
}

View File

@ -23,22 +23,20 @@ using K4os.Compression.LZ4;
namespace DotRecast.Detour.TileCache.Io.Compress
{
public class FastLzTileCacheCompressor : TileCacheCompressor {
public byte[] decompress(byte[] buf, int offset, int len, int outputlen) {
public class FastLzTileCacheCompressor : TileCacheCompressor
{
public byte[] decompress(byte[] buf, int offset, int len, int outputlen)
{
byte[] output = new byte[outputlen];
FastLz.decompress(buf, offset, len, output, 0, outputlen);
return output;
}
public byte[] compress(byte[] buf) {
public byte[] compress(byte[] buf)
{
byte[] output = new byte[FastLz.calculateOutputBufferLength(buf.Length)];
int len = FastLz.compress(buf, 0, buf.Length, output, 0, output.Length);
return ArrayUtils.CopyOf(output, len);
}
}
}

View File

@ -22,18 +22,16 @@ using K4os.Compression.LZ4;
namespace DotRecast.Detour.TileCache.Io.Compress
{
public class LZ4TileCacheCompressor : TileCacheCompressor {
public byte[] decompress(byte[] buf, int offset, int len, int outputlen) {
public class LZ4TileCacheCompressor : TileCacheCompressor
{
public byte[] decompress(byte[] buf, int offset, int len, int outputlen)
{
return LZ4Pickler.Unpickle(buf, offset, len);
}
public byte[] compress(byte[] buf) {
public byte[] compress(byte[] buf)
{
return LZ4Pickler.Pickle(buf);
}
}
}

View File

@ -17,14 +17,11 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache.Io.Compress
{
public class TileCacheCompressorFactory {
public class TileCacheCompressorFactory
{
public static TileCacheCompressor get(bool cCompatibility)
{
if (cCompatibility)
@ -33,5 +30,4 @@ public class TileCacheCompressorFactory {
return new LZ4TileCacheCompressor();
}
}
}

View File

@ -23,11 +23,10 @@ using DotRecast.Core;
namespace DotRecast.Detour.TileCache.Io
{
public class TileCacheLayerHeaderReader {
public TileCacheLayerHeader read(ByteBuffer data, bool cCompatibility) {
public class TileCacheLayerHeaderReader
{
public TileCacheLayerHeader read(ByteBuffer data, bool cCompatibility)
{
TileCacheLayerHeader header = new TileCacheLayerHeader();
header.magic = data.getInt();
header.version = data.getInt();
@ -40,12 +39,16 @@ public class TileCacheLayerHeaderReader {
header.tx = data.getInt();
header.ty = data.getInt();
header.tlayer = data.getInt();
for (int j = 0; j < 3; j++) {
for (int j = 0; j < 3; j++)
{
header.bmin[j] = data.getFloat();
}
for (int j = 0; j < 3; j++) {
for (int j = 0; j < 3; j++)
{
header.bmax[j] = data.getFloat();
}
header.hmin = data.getShort() & 0xFFFF;
header.hmax = data.getShort() & 0xFFFF;
header.width = data.get() & 0xFF;
@ -54,12 +57,12 @@ public class TileCacheLayerHeaderReader {
header.maxx = data.get() & 0xFF;
header.miny = data.get() & 0xFF;
header.maxy = data.get() & 0xFF;
if (cCompatibility) {
if (cCompatibility)
{
data.getShort(); // C struct padding
}
return header;
}
}
}

View File

@ -24,22 +24,25 @@ using DotRecast.Detour.Io;
namespace DotRecast.Detour.TileCache.Io
{
public class TileCacheLayerHeaderWriter : DetourWriter {
public void write(BinaryWriter stream, TileCacheLayerHeader header, ByteOrder order, bool cCompatibility) {
public class TileCacheLayerHeaderWriter : DetourWriter
{
public void write(BinaryWriter stream, TileCacheLayerHeader header, ByteOrder order, bool cCompatibility)
{
write(stream, header.magic, order);
write(stream, header.version, order);
write(stream, header.tx, order);
write(stream, header.ty, order);
write(stream, header.tlayer, order);
for (int j = 0; j < 3; j++) {
for (int j = 0; j < 3; j++)
{
write(stream, header.bmin[j], order);
}
for (int j = 0; j < 3; j++) {
for (int j = 0; j < 3; j++)
{
write(stream, header.bmax[j], order);
}
write(stream, (short)header.hmin, order);
write(stream, (short)header.hmax, order);
write(stream, (byte)header.width);
@ -48,11 +51,10 @@ public class TileCacheLayerHeaderWriter : DetourWriter {
write(stream, (byte)header.maxx);
write(stream, (byte)header.miny);
write(stream, (byte)header.maxy);
if (cCompatibility) {
if (cCompatibility)
{
write(stream, (short)0, order); // C struct padding
}
}
}
}

View File

@ -25,33 +25,40 @@ using DotRecast.Detour.TileCache.Io.Compress;
namespace DotRecast.Detour.TileCache.Io
{
public class TileCacheReader {
public class TileCacheReader
{
private readonly NavMeshParamReader paramReader = new NavMeshParamReader();
public TileCache read(BinaryReader @is, int maxVertPerPoly, TileCacheMeshProcess meshProcessor) {
public TileCache read(BinaryReader @is, int maxVertPerPoly, TileCacheMeshProcess meshProcessor)
{
ByteBuffer bb = IOUtils.toByteBuffer(@is);
return read(bb, maxVertPerPoly, meshProcessor);
}
public TileCache read(ByteBuffer bb, int maxVertPerPoly, TileCacheMeshProcess meshProcessor) {
public TileCache read(ByteBuffer bb, int maxVertPerPoly, TileCacheMeshProcess meshProcessor)
{
TileCacheSetHeader header = new TileCacheSetHeader();
header.magic = bb.getInt();
if (header.magic != TileCacheSetHeader.TILECACHESET_MAGIC) {
if (header.magic != TileCacheSetHeader.TILECACHESET_MAGIC)
{
header.magic = IOUtils.swapEndianness(header.magic);
if (header.magic != TileCacheSetHeader.TILECACHESET_MAGIC) {
if (header.magic != TileCacheSetHeader.TILECACHESET_MAGIC)
{
throw new IOException("Invalid magic");
}
bb.order(bb.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
}
header.version = bb.getInt();
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION) {
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION_RECAST4J) {
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION)
{
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION_RECAST4J)
{
throw new IOException("Invalid version");
}
}
bool cCompatibility = header.version == TileCacheSetHeader.TILECACHESET_VERSION;
header.numTiles = bb.getInt();
header.meshParams = paramReader.read(bb);
@ -61,27 +68,34 @@ public class TileCacheReader {
TileCache tc = new TileCache(header.cacheParams, new TileCacheStorageParams(bb.order(), cCompatibility), mesh,
compressor, meshProcessor);
// Read tiles.
for (int i = 0; i < header.numTiles; ++i) {
for (int i = 0; i < header.numTiles; ++i)
{
long tileRef = bb.getInt();
int dataSize = bb.getInt();
if (tileRef == 0 || dataSize == 0) {
if (tileRef == 0 || dataSize == 0)
{
break;
}
byte[] data = bb.ReadBytes(dataSize).ToArray();
long tile = tc.addTile(data, 0);
if (tile != 0) {
if (tile != 0)
{
tc.buildNavMeshTile(tile);
}
}
return tc;
}
private TileCacheParams readCacheParams(ByteBuffer bb, bool cCompatibility) {
private TileCacheParams readCacheParams(ByteBuffer bb, bool cCompatibility)
{
TileCacheParams option = new TileCacheParams();
for (int i = 0; i < 3; i++) {
for (int i = 0; i < 3; i++)
{
option.orig[i] = bb.getFloat();
}
option.cs = bb.getFloat();
option.ch = bb.getFloat();
option.width = bb.getInt();
@ -95,5 +109,4 @@ public class TileCacheReader {
return option;
}
}
}

View File

@ -17,12 +17,11 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache.Io
{
public class TileCacheSetHeader {
public class TileCacheSetHeader
{
public const int TILECACHESET_MAGIC = 'T' << 24 | 'S' << 16 | 'E' << 8 | 'T'; // 'TSET';
public const int TILECACHESET_VERSION = 1;
public const int TILECACHESET_VERSION_RECAST4J = 0x8801;
@ -32,7 +31,5 @@ public class TileCacheSetHeader {
public int numTiles;
public NavMeshParams meshParams = new NavMeshParams();
public TileCacheParams cacheParams = new TileCacheParams();
}
}

View File

@ -24,28 +24,31 @@ using DotRecast.Detour.Io;
namespace DotRecast.Detour.TileCache.Io
{
public class TileCacheWriter : DetourWriter {
public class TileCacheWriter : DetourWriter
{
private readonly NavMeshParamWriter paramWriter = new NavMeshParamWriter();
private readonly TileCacheBuilder builder = new TileCacheBuilder();
public void write(BinaryWriter stream, TileCache cache, ByteOrder order, bool cCompatibility) {
public void write(BinaryWriter stream, TileCache cache, ByteOrder order, bool cCompatibility)
{
write(stream, TileCacheSetHeader.TILECACHESET_MAGIC, order);
write(stream, cCompatibility ? TileCacheSetHeader.TILECACHESET_VERSION
write(stream, cCompatibility
? TileCacheSetHeader.TILECACHESET_VERSION
: TileCacheSetHeader.TILECACHESET_VERSION_RECAST4J, order);
int numTiles = 0;
for (int i = 0; i < cache.getTileCount(); ++i) {
for (int i = 0; i < cache.getTileCount(); ++i)
{
CompressedTile tile = cache.getTile(i);
if (tile == null || tile.data == null)
continue;
numTiles++;
}
write(stream, numTiles, order);
paramWriter.write(stream, cache.getNavMesh().getParams(), order);
writeCacheParams(stream, cache.getParams(), order);
for (int i = 0; i < cache.getTileCount(); i++) {
for (int i = 0; i < cache.getTileCount(); i++)
{
CompressedTile tile = cache.getTile(i);
if (tile == null || tile.data == null)
continue;
@ -58,10 +61,13 @@ public class TileCacheWriter : DetourWriter {
}
}
private void writeCacheParams(BinaryWriter stream, TileCacheParams option, ByteOrder order) {
for (int i = 0; i < 3; i++) {
private void writeCacheParams(BinaryWriter stream, TileCacheParams option, ByteOrder order)
{
for (int i = 0; i < 3; i++)
{
write(stream, option.orig[i], order);
}
write(stream, option.cs, order);
write(stream, option.ch, order);
write(stream, option.width, order);
@ -73,7 +79,5 @@ public class TileCacheWriter : DetourWriter {
write(stream, option.maxTiles, order);
write(stream, option.maxObstacles, order);
}
}
}

View File

@ -17,13 +17,12 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache
{
public class ObstacleRequest {
public class ObstacleRequest
{
public ObstacleRequestAction action;
public long refs;
}
}

View File

@ -17,12 +17,12 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache
{
public enum ObstacleRequestAction {
REQUEST_ADD, REQUEST_REMOVE
public enum ObstacleRequestAction
{
REQUEST_ADD,
REQUEST_REMOVE
}
}

View File

@ -17,13 +17,14 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache
{
public enum ObstacleState {
DT_OBSTACLE_EMPTY, DT_OBSTACLE_PROCESSING, DT_OBSTACLE_PROCESSED, DT_OBSTACLE_REMOVING
public enum ObstacleState
{
DT_OBSTACLE_EMPTY,
DT_OBSTACLE_PROCESSING,
DT_OBSTACLE_PROCESSED,
DT_OBSTACLE_REMOVING
}
}

View File

@ -22,26 +22,35 @@ using System;
using System.Collections.Generic;
using DotRecast.Core;
using DotRecast.Detour.TileCache.Io;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.TileCache
{
public class TileCache
{
int m_tileLutSize;
/// < Tile hash lookup size (must be pot).
int m_tileLutMask;
public class TileCache {
/// < Tile hash lookup mask.
private readonly CompressedTile[] m_posLookup;
int m_tileLutSize; /// < Tile hash lookup size (must be pot).
int m_tileLutMask; /// < Tile hash lookup mask.
/// < Tile hash lookup.
private CompressedTile m_nextFreeTile;
private readonly CompressedTile[] m_posLookup; /// < Tile hash lookup.
private CompressedTile m_nextFreeTile; /// < Freelist of tiles.
private readonly CompressedTile[] m_tiles; /// < List of tiles. // TODO: (PP) replace with list
/// < Freelist of tiles.
private readonly CompressedTile[] m_tiles;
private readonly int m_saltBits; /// < Number of salt bits in the tile ID.
private readonly int m_tileBits; /// < Number of tile bits in the tile ID.
/// < List of tiles. // TODO: (PP) replace with list
private readonly int m_saltBits;
/// < Number of salt bits in the tile ID.
private readonly int m_tileBits;
/// < Number of tile bits in the tile ID.
private readonly NavMesh m_navmesh;
private readonly TileCacheParams m_params;
private readonly TileCacheStorageParams m_storageParams;
@ -57,46 +66,54 @@ public class TileCache {
private readonly TileCacheBuilder builder = new TileCacheBuilder();
private readonly TileCacheLayerHeaderReader tileReader = new TileCacheLayerHeaderReader();
private bool contains(List<long> a, long v) {
private bool contains(List<long> a, long v)
{
return a.Contains(v);
}
/// Encodes a tile id.
private long encodeTileId(int salt, int it) {
private long encodeTileId(int salt, int it)
{
return ((long)salt << m_tileBits) | it;
}
/// Decodes a tile salt.
private int decodeTileIdSalt(long refs) {
private int decodeTileIdSalt(long refs)
{
long saltMask = (1L << m_saltBits) - 1;
return (int)((refs >> m_tileBits) & saltMask);
}
/// Decodes a tile id.
private int decodeTileIdTile(long refs) {
private int decodeTileIdTile(long refs)
{
long tileMask = (1L << m_tileBits) - 1;
return (int)(refs & tileMask);
}
/// Encodes an obstacle id.
private long encodeObstacleId(int salt, int it) {
private long encodeObstacleId(int salt, int it)
{
return ((long)salt << 16) | it;
}
/// Decodes an obstacle salt.
private int decodeObstacleIdSalt(long refs) {
private int decodeObstacleIdSalt(long refs)
{
long saltMask = ((long)1 << 16) - 1;
return (int)((refs >> 16) & saltMask);
}
/// Decodes an obstacle id.
private int decodeObstacleIdObstacle(long refs) {
private int decodeObstacleIdObstacle(long refs)
{
long tileMask = ((long)1 << 16) - 1;
return (int)(refs & tileMask);
}
public TileCache(TileCacheParams option, TileCacheStorageParams storageParams, NavMesh navmesh,
TileCacheCompressor tcomp, TileCacheMeshProcess tmprocs) {
TileCacheCompressor tcomp, TileCacheMeshProcess tmprocs)
{
m_params = option;
m_storageParams = storageParams;
m_navmesh = navmesh;
@ -104,121 +121,159 @@ public class TileCache {
m_tmproc = tmprocs;
m_tileLutSize = nextPow2(m_params.maxTiles / 4);
if (m_tileLutSize == 0) {
if (m_tileLutSize == 0)
{
m_tileLutSize = 1;
}
m_tileLutMask = m_tileLutSize - 1;
m_tiles = new CompressedTile[m_params.maxTiles];
m_posLookup = new CompressedTile[m_tileLutSize];
for (int i = m_params.maxTiles - 1; i >= 0; --i) {
for (int i = m_params.maxTiles - 1; i >= 0; --i)
{
m_tiles[i] = new CompressedTile(i);
m_tiles[i].next = m_nextFreeTile;
m_nextFreeTile = m_tiles[i];
}
m_tileBits = ilog2(nextPow2(m_params.maxTiles));
m_saltBits = Math.Min(31, 32 - m_tileBits);
if (m_saltBits < 10) {
if (m_saltBits < 10)
{
throw new Exception("Too few salt bits: " + m_saltBits);
}
}
public CompressedTile getTileByRef(long refs) {
if (refs == 0) {
public CompressedTile getTileByRef(long refs)
{
if (refs == 0)
{
return null;
}
int tileIndex = decodeTileIdTile(refs);
int tileSalt = decodeTileIdSalt(refs);
if (tileIndex >= m_params.maxTiles) {
if (tileIndex >= m_params.maxTiles)
{
return null;
}
CompressedTile tile = m_tiles[tileIndex];
if (tile.salt != tileSalt) {
if (tile.salt != tileSalt)
{
return null;
}
return tile;
}
public List<long> getTilesAt(int tx, int ty) {
public List<long> getTilesAt(int tx, int ty)
{
List<long> tiles = new List<long>();
// Find tile based on hash.
int h = NavMesh.computeTileHash(tx, ty, m_tileLutMask);
CompressedTile tile = m_posLookup[h];
while (tile != null) {
if (tile.header != null && tile.header.tx == tx && tile.header.ty == ty) {
while (tile != null)
{
if (tile.header != null && tile.header.tx == tx && tile.header.ty == ty)
{
tiles.Add(getTileRef(tile));
}
tile = tile.next;
}
return tiles;
}
CompressedTile getTileAt(int tx, int ty, int tlayer) {
CompressedTile getTileAt(int tx, int ty, int tlayer)
{
// Find tile based on hash.
int h = NavMesh.computeTileHash(tx, ty, m_tileLutMask);
CompressedTile tile = m_posLookup[h];
while (tile != null) {
if (tile.header != null && tile.header.tx == tx && tile.header.ty == ty && tile.header.tlayer == tlayer) {
while (tile != null)
{
if (tile.header != null && tile.header.tx == tx && tile.header.ty == ty && tile.header.tlayer == tlayer)
{
return tile;
}
tile = tile.next;
}
return null;
}
public long getTileRef(CompressedTile tile) {
if (tile == null) {
public long getTileRef(CompressedTile tile)
{
if (tile == null)
{
return 0;
}
int it = tile.index;
return encodeTileId(tile.salt, it);
}
public long getObstacleRef(TileCacheObstacle ob) {
if (ob == null) {
public long getObstacleRef(TileCacheObstacle ob)
{
if (ob == null)
{
return 0;
}
int idx = ob.index;
return encodeObstacleId(ob.salt, idx);
}
public TileCacheObstacle getObstacleByRef(long refs) {
if (refs == 0) {
public TileCacheObstacle getObstacleByRef(long refs)
{
if (refs == 0)
{
return null;
}
int idx = decodeObstacleIdObstacle(refs);
if (idx >= m_obstacles.Count) {
if (idx >= m_obstacles.Count)
{
return null;
}
TileCacheObstacle ob = m_obstacles[idx];
int salt = decodeObstacleIdSalt(refs);
if (ob.salt != salt) {
if (ob.salt != salt)
{
return null;
}
return ob;
}
public long addTile(byte[] data, int flags) {
public long addTile(byte[] data, int flags)
{
// Make sure the data is in right format.
ByteBuffer buf = new ByteBuffer(data);
buf.order(m_storageParams.byteOrder);
TileCacheLayerHeader header = tileReader.read(buf, m_storageParams.cCompatibility);
// Make sure the location is free.
if (getTileAt(header.tx, header.ty, header.tlayer) != null) {
if (getTileAt(header.tx, header.ty, header.tlayer) != null)
{
return 0;
}
// Allocate a tile.
CompressedTile tile = null;
if (m_nextFreeTile != null) {
if (m_nextFreeTile != null)
{
tile = m_nextFreeTile;
m_nextFreeTile = tile.next;
tile.next = null;
}
// Make sure we could allocate a tile.
if (tile == null) {
if (tile == null)
{
throw new Exception("Out of storage");
}
@ -236,21 +291,28 @@ public class TileCache {
return getTileRef(tile);
}
private int align4(int i) {
private int align4(int i)
{
return (i + 3) & (~3);
}
public void removeTile(long refs) {
if (refs == 0) {
public void removeTile(long refs)
{
if (refs == 0)
{
throw new Exception("Invalid tile ref");
}
int tileIndex = decodeTileIdTile(refs);
int tileSalt = decodeTileIdSalt(refs);
if (tileIndex >= m_params.maxTiles) {
if (tileIndex >= m_params.maxTiles)
{
throw new Exception("Invalid tile index");
}
CompressedTile tile = m_tiles[tileIndex];
if (tile.salt != tileSalt) {
if (tile.salt != tileSalt)
{
throw new Exception("Invalid tile salt");
}
@ -258,15 +320,22 @@ public class TileCache {
int h = NavMesh.computeTileHash(tile.header.tx, tile.header.ty, m_tileLutMask);
CompressedTile prev = null;
CompressedTile cur = m_posLookup[h];
while (cur != null) {
if (cur == tile) {
if (prev != null) {
while (cur != null)
{
if (cur == tile)
{
if (prev != null)
{
prev.next = cur.next;
} else {
}
else
{
m_posLookup[h] = cur.next;
}
break;
}
prev = cur;
cur = cur.next;
}
@ -278,18 +347,19 @@ public class TileCache {
// Update salt, salt should never be zero.
tile.salt = (tile.salt + 1) & ((1 << m_saltBits) - 1);
if (tile.salt == 0) {
if (tile.salt == 0)
{
tile.salt++;
}
// Add to free list.
tile.next = m_nextFreeTile;
m_nextFreeTile = tile;
}
// Cylinder obstacle
public long addObstacle(float[] pos, float radius, float height) {
public long addObstacle(float[] pos, float radius, float height)
{
TileCacheObstacle ob = allocObstacle();
ob.type = TileCacheObstacle.TileCacheObstacleType.CYLINDER;
@ -301,7 +371,8 @@ public class TileCache {
}
// Aabb obstacle
public long addBoxObstacle(float[] bmin, float[] bmax) {
public long addBoxObstacle(float[] bmin, float[] bmax)
{
TileCacheObstacle ob = allocObstacle();
ob.type = TileCacheObstacle.TileCacheObstacleType.BOX;
@ -312,7 +383,8 @@ public class TileCache {
}
// Box obstacle: can be rotated in Y
public long addBoxObstacle(float[] center, float[] extents, float yRadians) {
public long addBoxObstacle(float[] center, float[] extents, float yRadians)
{
TileCacheObstacle ob = allocObstacle();
ob.type = TileCacheObstacle.TileCacheObstacleType.ORIENTED_BOX;
vCopy(ob.center, center);
@ -324,7 +396,8 @@ public class TileCache {
return addObstacleRequest(ob).refs;
}
private ObstacleRequest addObstacleRequest(TileCacheObstacle ob) {
private ObstacleRequest addObstacleRequest(TileCacheObstacle ob)
{
ObstacleRequest req = new ObstacleRequest();
req.action = ObstacleRequestAction.REQUEST_ADD;
req.refs = getObstacleRef(ob);
@ -332,8 +405,10 @@ public class TileCache {
return req;
}
public void removeObstacle(long refs) {
if (refs == 0) {
public void removeObstacle(long refs)
{
if (refs == 0)
{
return;
}
@ -343,14 +418,19 @@ public class TileCache {
m_reqs.Add(req);
}
private TileCacheObstacle allocObstacle() {
private TileCacheObstacle allocObstacle()
{
TileCacheObstacle o = m_nextFreeObstacle;
if (o == null) {
if (o == null)
{
o = new TileCacheObstacle(m_obstacles.Count);
m_obstacles.Add(o);
} else {
}
else
{
m_nextFreeObstacle = o.next;
}
o.state = ObstacleState.DT_OBSTACLE_PROCESSING;
o.touched.Clear();
o.pending.Clear();
@ -358,7 +438,8 @@ public class TileCache {
return o;
}
List<long> queryTiles(float[] bmin, float[] bmax) {
List<long> queryTiles(float[] bmin, float[] bmax)
{
List<long> results = new List<long>();
float tw = m_params.width * m_params.cs;
float th = m_params.height * m_params.cs;
@ -366,20 +447,25 @@ public class TileCache {
int tx1 = (int)Math.Floor((bmax[0] - m_params.orig[0]) / tw);
int ty0 = (int)Math.Floor((bmin[2] - m_params.orig[2]) / th);
int ty1 = (int)Math.Floor((bmax[2] - m_params.orig[2]) / th);
for (int ty = ty0; ty <= ty1; ++ty) {
for (int tx = tx0; tx <= tx1; ++tx) {
for (int ty = ty0; ty <= ty1; ++ty)
{
for (int tx = tx0; tx <= tx1; ++tx)
{
List<long> tiles = getTilesAt(tx, ty);
foreach (long i in tiles) {
foreach (long i in tiles)
{
CompressedTile tile = m_tiles[decodeTileIdTile(i)];
float[] tbmin = new float[3];
float[] tbmax = new float[3];
calcTightTileBounds(tile.header, tbmin, tbmax);
if (overlapBounds(bmin, bmax, tbmin, tbmax)) {
if (overlapBounds(bmin, bmax, tbmin, tbmax))
{
results.Add(i);
}
}
}
}
return results;
}
@ -390,21 +476,28 @@ public class TileCache {
* cache is up to date another (immediate) call to update will have no effect; otherwise another call will
* continue processing obstacle requests and tile rebuilds.
*/
public bool update() {
if (0 == m_update.Count) {
public bool update()
{
if (0 == m_update.Count)
{
// Process requests.
foreach (ObstacleRequest req in m_reqs) {
foreach (ObstacleRequest req in m_reqs)
{
int idx = decodeObstacleIdObstacle(req.refs);
if (idx >= m_obstacles.Count) {
continue;
}
TileCacheObstacle ob = m_obstacles[idx];
int salt = decodeObstacleIdSalt(req.refs);
if (ob.salt != salt) {
if (idx >= m_obstacles.Count)
{
continue;
}
if (req.action == ObstacleRequestAction.REQUEST_ADD) {
TileCacheObstacle ob = m_obstacles[idx];
int salt = decodeObstacleIdSalt(req.refs);
if (ob.salt != salt)
{
continue;
}
if (req.action == ObstacleRequestAction.REQUEST_ADD)
{
// Find touched tiles.
float[] bmin = new float[3];
float[] bmax = new float[3];
@ -412,21 +505,29 @@ public class TileCache {
ob.touched = queryTiles(bmin, bmax);
// Add tiles to update list.
ob.pending.Clear();
foreach (long j in ob.touched) {
if (!contains(m_update, j)) {
foreach (long j in ob.touched)
{
if (!contains(m_update, j))
{
m_update.Add(j);
}
ob.pending.Add(j);
}
} else if (req.action == ObstacleRequestAction.REQUEST_REMOVE) {
}
else if (req.action == ObstacleRequestAction.REQUEST_REMOVE)
{
// Prepare to remove obstacle.
ob.state = ObstacleState.DT_OBSTACLE_REMOVING;
// Add tiles to update list.
ob.pending.Clear();
foreach (long j in ob.touched) {
if (!contains(m_update, j)) {
foreach (long j in ob.touched)
{
if (!contains(m_update, j))
{
m_update.Add(j);
}
ob.pending.Add(j);
}
}
@ -436,31 +537,40 @@ public class TileCache {
}
// Process updates
if (0 < m_update.Count) {
if (0 < m_update.Count)
{
long refs = m_update[0];
m_update.RemoveAt(0);
// Build mesh
buildNavMeshTile(refs);
// Update obstacle states.
for (int i = 0; i < m_obstacles.Count; ++i) {
for (int i = 0; i < m_obstacles.Count; ++i)
{
TileCacheObstacle ob = m_obstacles[i];
if (ob.state == ObstacleState.DT_OBSTACLE_PROCESSING
|| ob.state == ObstacleState.DT_OBSTACLE_REMOVING) {
|| ob.state == ObstacleState.DT_OBSTACLE_REMOVING)
{
// Remove handled tile from pending list.
ob.pending.Remove(refs);
// If all pending tiles processed, change state.
if (0 == ob.pending.Count) {
if (ob.state == ObstacleState.DT_OBSTACLE_PROCESSING) {
if (0 == ob.pending.Count)
{
if (ob.state == ObstacleState.DT_OBSTACLE_PROCESSING)
{
ob.state = ObstacleState.DT_OBSTACLE_PROCESSED;
} else if (ob.state == ObstacleState.DT_OBSTACLE_REMOVING) {
}
else if (ob.state == ObstacleState.DT_OBSTACLE_REMOVING)
{
ob.state = ObstacleState.DT_OBSTACLE_EMPTY;
// Update salt, salt should never be zero.
ob.salt = (ob.salt + 1) & ((1 << 16) - 1);
if (ob.salt == 0) {
if (ob.salt == 0)
{
ob.salt++;
}
// Return obstacle to free list.
ob.next = m_nextFreeObstacle;
m_nextFreeObstacle = ob;
@ -473,49 +583,66 @@ public class TileCache {
return 0 == m_update.Count && 0 == m_reqs.Count;
}
public void buildNavMeshTile(long refs) {
public void buildNavMeshTile(long refs)
{
int idx = decodeTileIdTile(refs);
if (idx > m_params.maxTiles) {
if (idx > m_params.maxTiles)
{
throw new Exception("Invalid tile index");
}
CompressedTile tile = m_tiles[idx];
int salt = decodeTileIdSalt(refs);
if (tile.salt != salt) {
if (tile.salt != salt)
{
throw new Exception("Invalid tile salt");
}
int walkableClimbVx = (int)(m_params.walkableClimb / m_params.ch);
// Decompress tile layer data.
TileCacheLayer layer = decompressTile(tile);
// Rasterize obstacles.
for (int i = 0; i < m_obstacles.Count; ++i) {
for (int i = 0; i < m_obstacles.Count; ++i)
{
TileCacheObstacle ob = m_obstacles[i];
if (ob.state == ObstacleState.DT_OBSTACLE_EMPTY || ob.state == ObstacleState.DT_OBSTACLE_REMOVING) {
if (ob.state == ObstacleState.DT_OBSTACLE_EMPTY || ob.state == ObstacleState.DT_OBSTACLE_REMOVING)
{
continue;
}
if (contains(ob.touched, refs)) {
if (ob.type == TileCacheObstacle.TileCacheObstacleType.CYLINDER) {
if (contains(ob.touched, refs))
{
if (ob.type == TileCacheObstacle.TileCacheObstacleType.CYLINDER)
{
builder.markCylinderArea(layer, tile.header.bmin, m_params.cs, m_params.ch, ob.pos, ob.radius,
ob.height, 0);
} else if (ob.type == TileCacheObstacle.TileCacheObstacleType.BOX) {
}
else if (ob.type == TileCacheObstacle.TileCacheObstacleType.BOX)
{
builder.markBoxArea(layer, tile.header.bmin, m_params.cs, m_params.ch, ob.bmin, ob.bmax, 0);
} else if (ob.type == TileCacheObstacle.TileCacheObstacleType.ORIENTED_BOX) {
}
else if (ob.type == TileCacheObstacle.TileCacheObstacleType.ORIENTED_BOX)
{
builder.markBoxArea(layer, tile.header.bmin, m_params.cs, m_params.ch, ob.center, ob.extents,
ob.rotAux, 0);
}
}
}
// Build navmesh
builder.buildTileCacheRegions(layer, walkableClimbVx);
TileCacheContourSet lcset = builder.buildTileCacheContours(layer, walkableClimbVx,
m_params.maxSimplificationError);
TileCachePolyMesh polyMesh = builder.buildTileCachePolyMesh(lcset, m_navmesh.getMaxVertsPerPoly());
// Early out if the mesh tile is empty.
if (polyMesh.npolys == 0) {
if (polyMesh.npolys == 0)
{
m_navmesh.removeTile(m_navmesh.getTileRefAt(tile.header.tx, tile.header.ty, tile.header.tlayer));
return;
}
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = polyMesh.verts;
option.vertCount = polyMesh.nverts;
@ -535,25 +662,30 @@ public class TileCache {
option.buildBvTree = false;
option.bmin = tile.header.bmin;
option.bmax = tile.header.bmax;
if (m_tmproc != null) {
if (m_tmproc != null)
{
m_tmproc.process(option);
}
MeshData meshData = NavMeshBuilder.createNavMeshData(option);
// Remove existing tile.
m_navmesh.removeTile(m_navmesh.getTileRefAt(tile.header.tx, tile.header.ty, tile.header.tlayer));
// Add new tile, or leave the location empty. if (navData) { // Let the
if (meshData != null) {
if (meshData != null)
{
m_navmesh.addTile(meshData, 0, 0);
}
}
public TileCacheLayer decompressTile(CompressedTile tile) {
public TileCacheLayer decompressTile(CompressedTile tile)
{
TileCacheLayer layer = builder.decompressTileCacheLayer(m_tcomp, tile.data, m_storageParams.byteOrder,
m_storageParams.cCompatibility);
return layer;
}
void calcTightTileBounds(TileCacheLayerHeader header, float[] bmin, float[] bmax) {
void calcTightTileBounds(TileCacheLayerHeader header, float[] bmin, float[] bmax)
{
float cs = m_params.cs;
bmin[0] = header.bmin[0] + header.minx * cs;
bmin[1] = header.bmin[1];
@ -563,18 +695,24 @@ public class TileCache {
bmax[2] = header.bmin[2] + (header.maxy + 1) * cs;
}
void getObstacleBounds(TileCacheObstacle ob, float[] bmin, float[] bmax) {
if (ob.type == TileCacheObstacle.TileCacheObstacleType.CYLINDER) {
void getObstacleBounds(TileCacheObstacle ob, float[] bmin, float[] bmax)
{
if (ob.type == TileCacheObstacle.TileCacheObstacleType.CYLINDER)
{
bmin[0] = ob.pos[0] - ob.radius;
bmin[1] = ob.pos[1];
bmin[2] = ob.pos[2] - ob.radius;
bmax[0] = ob.pos[0] + ob.radius;
bmax[1] = ob.pos[1] + ob.height;
bmax[2] = ob.pos[2] + ob.radius;
} else if (ob.type == TileCacheObstacle.TileCacheObstacleType.BOX) {
}
else if (ob.type == TileCacheObstacle.TileCacheObstacleType.BOX)
{
vCopy(bmin, ob.bmin);
vCopy(bmax, ob.bmax);
} else if (ob.type == TileCacheObstacle.TileCacheObstacleType.ORIENTED_BOX) {
}
else if (ob.type == TileCacheObstacle.TileCacheObstacleType.ORIENTED_BOX)
{
float maxr = 1.41f * Math.Max(ob.extents[0], ob.extents[2]);
bmin[0] = ob.center[0] - maxr;
bmax[0] = ob.center[0] + maxr;
@ -585,25 +723,29 @@ public class TileCache {
}
}
public TileCacheParams getParams() {
public TileCacheParams getParams()
{
return m_params;
}
public TileCacheCompressor getCompressor() {
public TileCacheCompressor getCompressor()
{
return m_tcomp;
}
public int getTileCount() {
public int getTileCount()
{
return m_params.maxTiles;
}
public CompressedTile getTile(int i) {
public CompressedTile getTile(int i)
{
return m_tiles[i];
}
public NavMesh getNavMesh() {
public NavMesh getNavMesh()
{
return m_navmesh;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -17,15 +17,13 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache
{
public interface TileCacheCompressor {
public interface TileCacheCompressor
{
byte[] decompress(byte[] buf, int offset, int len, int outputlen);
byte[] compress(byte[] buf);
}
}

View File

@ -17,15 +17,14 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache
{
public class TileCacheContour {
public class TileCacheContour
{
public int nverts;
public int[] verts;
public int reg;
public int area;
}
}

View File

@ -17,13 +17,12 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache
{
public class TileCacheContourSet {
public class TileCacheContourSet
{
public int nconts;
public TileCacheContour[] conts;
}
}

View File

@ -17,17 +17,19 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache
{
public class TileCacheLayer {
public class TileCacheLayer
{
public TileCacheLayerHeader header;
public int regCount; /// < Region count.
public int regCount;
/// < Region count.
public short[] heights; // char
public short[] areas; // char
public short[] cons; // char
public short[] regs; // char
}
}

View File

@ -17,24 +17,32 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache
{
public class TileCacheLayerHeader
{
public const int DT_TILECACHE_MAGIC = 'D' << 24 | 'T' << 16 | 'L' << 8 | 'R';
public class TileCacheLayerHeader {
public const int DT_TILECACHE_MAGIC = 'D' << 24 | 'T' << 16 | 'L' << 8 | 'R'; /// < 'DTLR';
/// < 'DTLR';
public const int DT_TILECACHE_VERSION = 1;
public int magic; /// < Data magic
public int version; /// < Data version
public int magic;
/// < Data magic
public int version;
/// < Data version
public int tx, ty, tlayer;
public float[] bmin = new float[3];
public float[] bmax = new float[3];
public int hmin, hmax; /// < Height min/max range
public int width, height; /// < Dimension of the layer.
public int hmin, hmax;
/// < Height min/max range
public int width, height;
/// < Dimension of the layer.
public int minx, maxx, miny, maxy; /// < Usable sub-region.
}
}

View File

@ -17,13 +17,11 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache
{
public interface TileCacheMeshProcess {
public interface TileCacheMeshProcess
{
void process(NavMeshDataCreateParams option);
}
}

View File

@ -22,12 +22,13 @@ using System.Collections.Generic;
namespace DotRecast.Detour.TileCache
{
public class TileCacheObstacle {
public enum TileCacheObstacleType {
CYLINDER, BOX, ORIENTED_BOX
public class TileCacheObstacle
{
public enum TileCacheObstacleType
{
CYLINDER,
BOX,
ORIENTED_BOX
};
public readonly int index;
@ -45,11 +46,10 @@ public class TileCacheObstacle {
public ObstacleState state = ObstacleState.DT_OBSTACLE_EMPTY;
public TileCacheObstacle next;
public TileCacheObstacle(int index) {
public TileCacheObstacle(int index)
{
salt = 1;
this.index = index;
}
}
}

View File

@ -17,11 +17,11 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache
{
public class TileCacheParams {
public class TileCacheParams
{
public readonly float[] orig = new float[3];
public float cs, ch;
public int width, height;
@ -31,7 +31,5 @@ public class TileCacheParams {
public float maxSimplificationError;
public int maxTiles;
public int maxObstacles;
}
}

View File

@ -17,22 +17,33 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache
{
public class TileCachePolyMesh {
public class TileCachePolyMesh
{
public int nvp;
public int nverts; /// < Number of vertices.
public int npolys; /// < Number of polygons.
public int[] verts; /// < Vertices of the mesh, 3 elements per vertex.
public int[] polys; /// < Polygons of the mesh, nvp*2 elements per polygon.
public int[] flags; /// < Per polygon flags.
public int[] areas; /// < Area ID of polygons.
public int nverts;
public TileCachePolyMesh(int nvp) {
/// < Number of vertices.
public int npolys;
/// < Number of polygons.
public int[] verts;
/// < Vertices of the mesh, 3 elements per vertex.
public int[] polys;
/// < Polygons of the mesh, nvp*2 elements per polygon.
public int[] flags;
/// < Per polygon flags.
public int[] areas;
/// < Area ID of polygons.
public TileCachePolyMesh(int nvp)
{
this.nvp = nvp;
}
}
}

View File

@ -22,18 +22,15 @@ using DotRecast.Core;
namespace DotRecast.Detour.TileCache
{
public class TileCacheStorageParams {
public class TileCacheStorageParams
{
public readonly ByteOrder byteOrder;
public readonly bool cCompatibility;
public TileCacheStorageParams(ByteOrder byteOrder, bool cCompatibility) {
public TileCacheStorageParams(ByteOrder byteOrder, bool cCompatibility)
{
this.byteOrder = byteOrder;
this.cCompatibility = cCompatibility;
}
}
}

View File

@ -17,23 +17,24 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
/**
* Bounding volume node.
*
* @note This structure is rarely if ever used by the end user.
* @see MeshTile
*/
public class BVNode {
public class BVNode
{
/** Minimum bounds of the node's AABB. [(x, y, z)] */
public int[] bmin = new int[3];
/** Maximum bounds of the node's AABB. [(x, y, z)] */
public int[] bmax = new int[3];
/** The node's index. (Negative for escape sequence.) */
public int i;
}
}

View File

@ -17,30 +17,30 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
public class ClosestPointOnPolyResult {
public class ClosestPointOnPolyResult
{
private readonly bool posOverPoly;
private readonly float[] closest;
public ClosestPointOnPolyResult(bool posOverPoly, float[] closest) {
public ClosestPointOnPolyResult(bool posOverPoly, float[] closest)
{
this.posOverPoly = posOverPoly;
this.closest = closest;
}
/** Returns true if the position is over the polygon. */
public bool isPosOverPoly() {
public bool isPosOverPoly()
{
return posOverPoly;
}
/** Returns the closest point on the polygon. [(x, y, z)] */
public float[] getClosest() {
public float[] getClosest()
{
return closest;
}
}
}

View File

@ -20,26 +20,31 @@ using System;
namespace DotRecast.Detour
{
using static DetourCommon;
/**
* Convex-convex intersection based on "Computational Geometry in C" by Joseph O'Rourke
*/
public static class ConvexConvexIntersection {
public static class ConvexConvexIntersection
{
private static readonly float EPSILON = 0.0001f;
private enum InFlag {
Pin, Qin, Unknown,
private enum InFlag
{
Pin,
Qin,
Unknown,
}
private enum Intersection {
None, Single, Overlap,
private enum Intersection
{
None,
Single,
Overlap,
}
public static float[] intersect(float[] p, float[] q) {
public static float[] intersect(float[] p, float[] q)
{
int n = p.Length / 3;
int m = q.Length / 3;
float[] inters = new float[Math.Max(m, n) * 3 * 3];
@ -60,7 +65,8 @@ public static class ConvexConvexIntersection {
float[] ip = new float[3];
float[] iq = new float[3];
do {
do
{
vCopy(a, p, 3 * (ai % n));
vCopy(b, q, 3 * (bi % m));
vCopy(a1, p, 3 * ((ai + n - 1) % n)); // prev a
@ -72,17 +78,22 @@ public static class ConvexConvexIntersection {
float cross = B[0] * A[2] - A[0] * B[2]; // triArea2D({0, 0}, A, B);
float aHB = triArea2D(b1, b, a);
float bHA = triArea2D(a1, a, b);
if (Math.Abs(cross) < EPSILON) {
if (Math.Abs(cross) < EPSILON)
{
cross = 0f;
}
bool parallel = cross == 0f;
Intersection code = parallel ? parallelInt(a1, a, b1, b, ip, iq) : segSegInt(a1, a, b1, b, ip, iq);
if (code == Intersection.Single) {
if (FirstPoint) {
if (code == Intersection.Single)
{
if (FirstPoint)
{
FirstPoint = false;
aa = ba = 0;
}
ii = addVertex(inters, ii, ip);
f = inOut(f, aHB, bHA);
}
@ -90,55 +101,76 @@ public static class ConvexConvexIntersection {
/*-----Advance rules-----*/
/* Special case: A & B overlap and oppositely oriented. */
if (code == Intersection.Overlap && vDot2D(A, B) < 0) {
if (code == Intersection.Overlap && vDot2D(A, B) < 0)
{
ii = addVertex(inters, ii, ip);
ii = addVertex(inters, ii, iq);
break;
}
/* Special case: A & B parallel and separated. */
if (parallel && aHB < 0f && bHA < 0f) {
if (parallel && aHB < 0f && bHA < 0f)
{
return null;
}
/* Special case: A & B collinear. */
else if (parallel && Math.Abs(aHB) < EPSILON && Math.Abs(bHA) < EPSILON) {
else if (parallel && Math.Abs(aHB) < EPSILON && Math.Abs(bHA) < EPSILON)
{
/* Advance but do not output point. */
if (f == InFlag.Pin) {
if (f == InFlag.Pin)
{
ba++;
bi++;
} else {
}
else
{
aa++;
ai++;
}
}
/* Generic cases. */
else if (cross >= 0)
{
if (bHA > 0)
{
if (f == InFlag.Pin)
{
ii = addVertex(inters, ii, a);
}
/* Generic cases. */
else if (cross >= 0) {
if (bHA > 0) {
if (f == InFlag.Pin) {
ii = addVertex(inters, ii, a);
}
aa++;
ai++;
} else {
if (f == InFlag.Qin) {
}
else
{
if (f == InFlag.Qin)
{
ii = addVertex(inters, ii, b);
}
ba++;
bi++;
}
} else {
if (aHB > 0) {
if (f == InFlag.Qin) {
}
else
{
if (aHB > 0)
{
if (f == InFlag.Qin)
{
ii = addVertex(inters, ii, b);
}
ba++;
bi++;
} else {
if (f == InFlag.Pin) {
}
else
{
if (f == InFlag.Pin)
{
ii = addVertex(inters, ii, a);
}
aa++;
ai++;
}
@ -147,7 +179,8 @@ public static class ConvexConvexIntersection {
} while ((aa < n || ba < m) && aa < 2 * n && ba < 2 * m);
/* Deal with special cases: not implemented. */
if (f == InFlag.Unknown) {
if (f == InFlag.Unknown)
{
return null;
}
@ -156,86 +189,117 @@ public static class ConvexConvexIntersection {
return copied;
}
private static int addVertex(float[] inters, int ii, float[] p) {
if (ii > 0) {
if (inters[ii - 3] == p[0] && inters[ii - 2] == p[1] && inters[ii - 1] == p[2]) {
private static int addVertex(float[] inters, int ii, float[] p)
{
if (ii > 0)
{
if (inters[ii - 3] == p[0] && inters[ii - 2] == p[1] && inters[ii - 1] == p[2])
{
return ii;
}
if (inters[0] == p[0] && inters[1] == p[1] && inters[2] == p[2]) {
if (inters[0] == p[0] && inters[1] == p[1] && inters[2] == p[2])
{
return ii;
}
}
inters[ii] = p[0];
inters[ii + 1] = p[1];
inters[ii + 2] = p[2];
return ii + 3;
}
private static InFlag inOut(InFlag inflag, float aHB, float bHA) {
if (aHB > 0) {
private static InFlag inOut(InFlag inflag, float aHB, float bHA)
{
if (aHB > 0)
{
return InFlag.Pin;
} else if (bHA > 0) {
}
else if (bHA > 0)
{
return InFlag.Qin;
}
return inflag;
}
private static Intersection segSegInt(float[] a, float[] b, float[] c, float[] d, float[] p, float[] q) {
private static Intersection segSegInt(float[] a, float[] b, float[] c, float[] d, float[] p, float[] q)
{
var isec = intersectSegSeg2D(a, b, c, d);
if (null != isec) {
if (null != isec)
{
float s = isec.Item1;
float t = isec.Item2;
if (s >= 0.0f && s <= 1.0f && t >= 0.0f && t <= 1.0f) {
if (s >= 0.0f && s <= 1.0f && t >= 0.0f && t <= 1.0f)
{
p[0] = a[0] + (b[0] - a[0]) * s;
p[1] = a[1] + (b[1] - a[1]) * s;
p[2] = a[2] + (b[2] - a[2]) * s;
return Intersection.Single;
}
}
return Intersection.None;
}
private static Intersection parallelInt(float[] a, float[] b, float[] c, float[] d, float[] p, float[] q) {
if (between(a, b, c) && between(a, b, d)) {
private static Intersection parallelInt(float[] a, float[] b, float[] c, float[] d, float[] p, float[] q)
{
if (between(a, b, c) && between(a, b, d))
{
vCopy(p, c);
vCopy(q, d);
return Intersection.Overlap;
}
if (between(c, d, a) && between(c, d, b)) {
if (between(c, d, a) && between(c, d, b))
{
vCopy(p, a);
vCopy(q, b);
return Intersection.Overlap;
}
if (between(a, b, c) && between(c, d, b)) {
if (between(a, b, c) && between(c, d, b))
{
vCopy(p, c);
vCopy(q, b);
return Intersection.Overlap;
}
if (between(a, b, c) && between(c, d, a)) {
if (between(a, b, c) && between(c, d, a))
{
vCopy(p, c);
vCopy(q, a);
return Intersection.Overlap;
}
if (between(a, b, d) && between(c, d, b)) {
if (between(a, b, d) && between(c, d, b))
{
vCopy(p, d);
vCopy(q, b);
return Intersection.Overlap;
}
if (between(a, b, d) && between(c, d, a)) {
if (between(a, b, d) && between(c, d, a))
{
vCopy(p, d);
vCopy(q, a);
return Intersection.Overlap;
}
return Intersection.None;
}
private static bool between(float[] a, float[] b, float[] c) {
if (Math.Abs(a[0] - b[0]) > Math.Abs(a[2] - b[2])) {
private static bool between(float[] a, float[] b, float[] c)
{
if (Math.Abs(a[0] - b[0]) > Math.Abs(a[2] - b[2]))
{
return ((a[0] <= c[0]) && (c[0] <= b[0])) || ((a[0] >= c[0]) && (c[0] >= b[0]));
} else {
}
else
{
return ((a[2] <= c[2]) && (c[2] <= b[2])) || ((a[2] >= c[2]) && (c[2] >= b[2]));
}
}
}
}

View File

@ -22,8 +22,6 @@ using System;
namespace DotRecast.Detour
{
using static DetourCommon;
/**
@ -51,56 +49,66 @@ using static DetourCommon;
*
* @see NavMeshQuery
*/
public class DefaultQueryFilter : QueryFilter {
public class DefaultQueryFilter : QueryFilter
{
private int m_excludeFlags;
private int m_includeFlags;
private readonly float[] m_areaCost = new float[NavMesh.DT_MAX_AREAS];
public DefaultQueryFilter() {
public DefaultQueryFilter()
{
m_includeFlags = 0xffff;
m_excludeFlags = 0;
for (int i = 0; i < NavMesh.DT_MAX_AREAS; ++i) {
for (int i = 0; i < NavMesh.DT_MAX_AREAS; ++i)
{
m_areaCost[i] = 1.0f;
}
}
public DefaultQueryFilter(int includeFlags, int excludeFlags, float[] areaCost) {
public DefaultQueryFilter(int includeFlags, int excludeFlags, float[] areaCost)
{
m_includeFlags = includeFlags;
m_excludeFlags = excludeFlags;
for (int i = 0; i < Math.Min(NavMesh.DT_MAX_AREAS, areaCost.Length); ++i) {
for (int i = 0; i < Math.Min(NavMesh.DT_MAX_AREAS, areaCost.Length); ++i)
{
m_areaCost[i] = areaCost[i];
}
for (int i = areaCost.Length; i < NavMesh.DT_MAX_AREAS; ++i) {
for (int i = areaCost.Length; i < NavMesh.DT_MAX_AREAS; ++i)
{
m_areaCost[i] = 1.0f;
}
}
public bool passFilter(long refs, MeshTile tile, Poly poly) {
public bool passFilter(long refs, MeshTile tile, Poly poly)
{
return (poly.flags & m_includeFlags) != 0 && (poly.flags & m_excludeFlags) == 0;
}
public float getCost(float[] pa, float[] pb, long prevRef, MeshTile prevTile, Poly prevPoly, long curRef,
MeshTile curTile, Poly curPoly, long nextRef, MeshTile nextTile, Poly nextPoly) {
MeshTile curTile, Poly curPoly, long nextRef, MeshTile nextTile, Poly nextPoly)
{
return vDist(pa, pb) * m_areaCost[curPoly.getArea()];
}
public int getIncludeFlags() {
public int getIncludeFlags()
{
return m_includeFlags;
}
public void setIncludeFlags(int flags) {
public void setIncludeFlags(int flags)
{
m_includeFlags = flags;
}
public int getExcludeFlags() {
public int getExcludeFlags()
{
return m_excludeFlags;
}
public void setExcludeFlags(int flags) {
public void setExcludeFlags(int flags)
{
m_excludeFlags = flags;
}
}
}

View File

@ -18,26 +18,24 @@ freely, subject to the following restrictions:
namespace DotRecast.Detour
{
using static DetourCommon;
public class DefaultQueryHeuristic : QueryHeuristic {
public class DefaultQueryHeuristic : QueryHeuristic
{
private readonly float scale;
public DefaultQueryHeuristic() : this(0.999f)
{
}
public DefaultQueryHeuristic(float scale) {
public DefaultQueryHeuristic(float scale)
{
this.scale = scale;
}
public float getCost(float[] neighbourPos, float[] endPos) {
public float getCost(float[] neighbourPos, float[] endPos)
{
return vDist(neighbourPos, endPos) * scale;
}
}
}

View File

@ -17,20 +17,21 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
public class DetourBuilder {
public MeshData build(NavMeshDataCreateParams option, int tileX, int tileY) {
public class DetourBuilder
{
public MeshData build(NavMeshDataCreateParams option, int tileX, int tileY)
{
MeshData data = NavMeshBuilder.createNavMeshData(option);
if (data != null) {
if (data != null)
{
data.header.x = tileX;
data.header.y = tileY;
}
return data;
}
}
}

View File

@ -22,10 +22,8 @@ using System;
namespace DotRecast.Detour
{
public static class DetourCommon {
public static class DetourCommon
{
public const float EPS = 1e-4f;
/// Performs a scaled vector addition. (@p v1 + (@p v2 * @p s))
@ -33,7 +31,8 @@ public static class DetourCommon {
/// @param[in] v1 The base vector. [(x, y, z)]
/// @param[in] v2 The vector to scale and add to @p v1. [(x, y, z)]
/// @param[in] s The amount to scale @p v2 by before adding to @p v1.
public static float[] vMad(float[] v1, float[] v2, float s) {
public static float[] vMad(float[] v1, float[] v2, float s)
{
float[] dest = new float[3];
dest[0] = v1[0] + v2[0] * s;
dest[1] = v1[1] + v2[1] * s;
@ -47,7 +46,8 @@ public static class DetourCommon {
/// @param[in] v1 The starting vector.
/// @param[in] v2 The destination vector.
/// @param[in] t The interpolation factor. [Limits: 0 <= value <= 1.0]
public static float[] vLerp(float[] verts, int v1, int v2, float t) {
public static float[] vLerp(float[] verts, int v1, int v2, float t)
{
float[] dest = new float[3];
dest[0] = verts[v1 + 0] + (verts[v2 + 0] - verts[v1 + 0]) * t;
dest[1] = verts[v1 + 1] + (verts[v2 + 1] - verts[v1 + 1]) * t;
@ -55,7 +55,8 @@ public static class DetourCommon {
return dest;
}
public static float[] vLerp(float[] v1, float[] v2, float t) {
public static float[] vLerp(float[] v1, float[] v2, float t)
{
float[] dest = new float[3];
dest[0] = v1[0] + (v2[0] - v1[0]) * t;
dest[1] = v1[1] + (v2[1] - v1[1]) * t;
@ -63,7 +64,8 @@ public static class DetourCommon {
return dest;
}
public static float[] vSub(VectorPtr v1, VectorPtr v2) {
public static float[] vSub(VectorPtr v1, VectorPtr v2)
{
float[] dest = new float[3];
dest[0] = v1.get(0) - v2.get(0);
dest[1] = v1.get(1) - v2.get(1);
@ -71,7 +73,8 @@ public static class DetourCommon {
return dest;
}
public static float[] vSub(float[] v1, float[] v2) {
public static float[] vSub(float[] v1, float[] v2)
{
float[] dest = new float[3];
dest[0] = v1[0] - v2[0];
dest[1] = v1[1] - v2[1];
@ -79,7 +82,8 @@ public static class DetourCommon {
return dest;
}
public static float[] vAdd(float[] v1, float[] v2) {
public static float[] vAdd(float[] v1, float[] v2)
{
float[] dest = new float[3];
dest[0] = v1[0] + v2[0];
dest[1] = v1[1] + v2[1];
@ -87,7 +91,8 @@ public static class DetourCommon {
return dest;
}
public static float[] vCopy(float[] @in) {
public static float[] vCopy(float[] @in)
{
float[] @out = new float[3];
@out[0] = @in[0];
@out[1] = @in[1];
@ -95,31 +100,36 @@ public static class DetourCommon {
return @out;
}
public static void vSet(float[] @out, float a, float b, float c) {
public static void vSet(float[] @out, float a, float b, float c)
{
@out[0] = a;
@out[1] = b;
@out[2] = c;
}
public static void vCopy(float[] @out, float[] @in) {
public static void vCopy(float[] @out, float[] @in)
{
@out[0] = @in[0];
@out[1] = @in[1];
@out[2] = @in[2];
}
public static void vCopy(float[] @out, float[] @in, int i) {
public static void vCopy(float[] @out, float[] @in, int i)
{
@out[0] = @in[i];
@out[1] = @in[i + 1];
@out[2] = @in[i + 2];
}
public static void vMin(float[] @out, float[] @in, int i) {
public static void vMin(float[] @out, float[] @in, int i)
{
@out[0] = Math.Min(@out[0], @in[i]);
@out[1] = Math.Min(@out[1], @in[i + 1]);
@out[2] = Math.Min(@out[2], @in[i + 2]);
}
public static void vMax(float[] @out, float[] @in, int i) {
public static void vMax(float[] @out, float[] @in, int i)
{
@out[0] = Math.Max(@out[0], @in[i]);
@out[1] = Math.Max(@out[1], @in[i + 1]);
@out[2] = Math.Max(@out[2], @in[i + 2]);
@ -129,7 +139,8 @@ public static class DetourCommon {
/// @param[in] v1 A point. [(x, y, z)]
/// @param[in] v2 A point. [(x, y, z)]
/// @return The distance between the two points.
public static float vDist(float[] v1, float[] v2) {
public static float vDist(float[] v1, float[] v2)
{
float dx = v2[0] - v1[0];
float dy = v2[1] - v1[1];
float dz = v2[2] - v1[2];
@ -140,40 +151,47 @@ public static class DetourCommon {
/// @param[in] v1 A point. [(x, y, z)]
/// @param[in] v2 A point. [(x, y, z)]
/// @return The distance between the two points.
public static float vDistSqr(float[] v1, float[] v2) {
public static float vDistSqr(float[] v1, float[] v2)
{
float dx = v2[0] - v1[0];
float dy = v2[1] - v1[1];
float dz = v2[2] - v1[2];
return dx * dx + dy * dy + dz * dz;
}
public static float sqr(float a) {
public static float sqr(float a)
{
return a * a;
}
/// Derives the square of the scalar length of the vector. (len * len)
/// @param[in] v The vector. [(x, y, z)]
/// @return The square of the scalar length of the vector.
public static float vLenSqr(float[] v) {
public static float vLenSqr(float[] v)
{
return v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
}
public static float vLen(float[] v) {
public static float vLen(float[] v)
{
return (float)Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}
public static float vDist(float[] v1, float[] verts, int i) {
public static float vDist(float[] v1, float[] verts, int i)
{
float dx = verts[i] - v1[0];
float dy = verts[i + 1] - v1[1];
float dz = verts[i + 2] - v1[2];
return (float)Math.Sqrt(dx * dx + dy * dy + dz * dz);
}
public static float clamp(float v, float min, float max) {
public static float clamp(float v, float min, float max)
{
return Math.Max(Math.Min(v, max), min);
}
public static int clamp(int v, int min, int max) {
public static int clamp(int v, int min, int max)
{
return Math.Max(Math.Min(v, max), min);
}
@ -184,19 +202,22 @@ public static class DetourCommon {
///
/// The vectors are projected onto the xz-plane, so the y-values are
/// ignored.
public static float vDist2D(float[] v1, float[] v2) {
public static float vDist2D(float[] v1, float[] v2)
{
float dx = v2[0] - v1[0];
float dz = v2[2] - v1[2];
return (float)Math.Sqrt(dx * dx + dz * dz);
}
public static float vDist2DSqr(float[] v1, float[] v2) {
public static float vDist2DSqr(float[] v1, float[] v2)
{
float dx = v2[0] - v1[0];
float dz = v2[2] - v1[2];
return dx * dx + dz * dz;
}
public static float vDist2DSqr(float[] p, float[] verts, int i) {
public static float vDist2DSqr(float[] p, float[] verts, int i)
{
float dx = verts[i] - p[0];
float dz = verts[i + 2] - p[2];
return dx * dx + dz * dz;
@ -204,9 +225,11 @@ public static class DetourCommon {
/// Normalizes the vector.
/// @param[in,out] v The vector to normalize. [(x, y, z)]
public static void vNormalize(float[] v) {
public static void vNormalize(float[] v)
{
float d = (float)(1.0f / Math.Sqrt(sqr(v[0]) + sqr(v[1]) + sqr(v[2])));
if (d != 0) {
if (d != 0)
{
v[0] *= d;
v[1] *= d;
v[2] *= d;
@ -222,11 +245,13 @@ public static class DetourCommon {
///
/// Basically, this function will return true if the specified points are
/// close enough to eachother to be considered colocated.
public static bool vEqual(float[] p0, float[] p1) {
public static bool vEqual(float[] p0, float[] p1)
{
return vEqual(p0, p1, EQUAL_THRESHOLD);
}
public static bool vEqual(float[] p0, float[] p1, float thresholdSqr) {
public static bool vEqual(float[] p0, float[] p1, float thresholdSqr)
{
float d = vDistSqr(p0, p1);
return d < thresholdSqr;
}
@ -238,11 +263,13 @@ public static class DetourCommon {
///
/// The vectors are projected onto the xz-plane, so the y-values are
/// ignored.
public static float vDot2D(float[] u, float[] v) {
public static float vDot2D(float[] u, float[] v)
{
return u[0] * v[0] + u[2] * v[2];
}
public static float vDot2D(float[] u, float[] v, int vi) {
public static float vDot2D(float[] u, float[] v, int vi)
{
return u[0] * v[vi] + u[2] * v[vi + 2];
}
@ -253,21 +280,22 @@ public static class DetourCommon {
///
/// The vectors are projected onto the xz-plane, so the y-values are
/// ignored.
public static float vPerp2D(float[] u, float[] v) {
public static float vPerp2D(float[] u, float[] v)
{
return u[2] * v[0] - u[0] * v[2];
}
/// @}
/// @name Computational geometry helper functions.
/// @{
/// Derives the signed xz-plane area of the triangle ABC, or the
/// relationship of line AB to point C.
/// @param[in] a Vertex A. [(x, y, z)]
/// @param[in] b Vertex B. [(x, y, z)]
/// @param[in] c Vertex C. [(x, y, z)]
/// @return The signed xz-plane area of the triangle.
public static float triArea2D(float[] verts, int a, int b, int c) {
public static float triArea2D(float[] verts, int a, int b, int c)
{
float abx = verts[b] - verts[a];
float abz = verts[b + 2] - verts[a + 2];
float acx = verts[c] - verts[a];
@ -275,7 +303,8 @@ public static class DetourCommon {
return acx * abz - abx * acz;
}
public static float triArea2D(float[] a, float[] b, float[] c) {
public static float triArea2D(float[] a, float[] b, float[] c)
{
float abx = b[0] - a[0];
float abz = b[2] - a[2];
float acx = c[0] - a[0];
@ -290,7 +319,8 @@ public static class DetourCommon {
/// @param[in] bmax Maximum bounds of box B. [(x, y, z)]
/// @return True if the two AABB's overlap.
/// @see dtOverlapBounds
public static bool overlapQuantBounds(int[] amin, int[] amax, int[] bmin, int[] bmax) {
public static bool overlapQuantBounds(int[] amin, int[] amax, int[] bmin, int[] bmax)
{
bool overlap = true;
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
@ -305,7 +335,8 @@ public static class DetourCommon {
/// @param[in] bmax Maximum bounds of box B. [(x, y, z)]
/// @return True if the two AABB's overlap.
/// @see dtOverlapQuantBounds
public static bool overlapBounds(float[] amin, float[] amax, float[] bmin, float[] bmax) {
public static bool overlapBounds(float[] amin, float[] amax, float[] bmin, float[] bmax)
{
bool overlap = true;
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
@ -313,48 +344,59 @@ public static class DetourCommon {
return overlap;
}
public static Tuple<float, float> distancePtSegSqr2D(float[] pt, float[] p, float[] q) {
public static Tuple<float, float> distancePtSegSqr2D(float[] pt, float[] p, float[] q)
{
float pqx = q[0] - p[0];
float pqz = q[2] - p[2];
float dx = pt[0] - p[0];
float dz = pt[2] - p[2];
float d = pqx * pqx + pqz * pqz;
float t = pqx * dx + pqz * dz;
if (d > 0) {
if (d > 0)
{
t /= d;
}
if (t < 0) {
if (t < 0)
{
t = 0;
} else if (t > 1) {
}
else if (t > 1)
{
t = 1;
}
dx = p[0] + t * pqx - pt[0];
dz = p[2] + t * pqz - pt[2];
return Tuple.Create(dx * dx + dz * dz, t);
}
public static float? closestHeightPointTriangle(float[] p, float[] a, float[] b, float[] c) {
public static float? closestHeightPointTriangle(float[] p, float[] a, float[] b, float[] c)
{
float[] v0 = vSub(c, a);
float[] v1 = vSub(b, a);
float[] v2 = vSub(p, a);
// Compute scaled barycentric coordinates
float denom = v0[0] * v1[2] - v0[2] * v1[0];
if (Math.Abs(denom) < EPS) {
if (Math.Abs(denom) < EPS)
{
return null;
}
float u = v1[2] * v2[0] - v1[0] * v2[2];
float v = v0[0] * v2[2] - v0[2] * v2[0];
if (denom < 0) {
if (denom < 0)
{
denom = -denom;
u = -u;
v = -v;
}
// If point lies inside the triangle, return interpolated ycoord.
if (u >= 0.0f && v >= 0.0f && (u + v) <= denom) {
if (u >= 0.0f && v >= 0.0f && (u + v) <= denom)
{
float h = a[1] + (v0[1] * u + v1[1] * v) / denom;
return h;
}
@ -365,51 +407,64 @@ public static class DetourCommon {
/// @par
///
/// All points are projected onto the xz-plane, so the y-values are ignored.
public static bool pointInPolygon(float[] pt, float[] verts, int nverts) {
public static bool pointInPolygon(float[] pt, float[] verts, int nverts)
{
// TODO: Replace pnpoly with triArea2D tests?
int i, j;
bool c = false;
for (i = 0, j = nverts - 1; i < nverts; j = i++) {
for (i = 0, j = nverts - 1; i < nverts; j = i++)
{
int vi = i * 3;
int vj = j * 3;
if (((verts[vi + 2] > pt[2]) != (verts[vj + 2] > pt[2])) && (pt[0] < (verts[vj + 0] - verts[vi + 0])
* (pt[2] - verts[vi + 2]) / (verts[vj + 2] - verts[vi + 2]) + verts[vi + 0])) {
* (pt[2] - verts[vi + 2]) / (verts[vj + 2] - verts[vi + 2]) + verts[vi + 0]))
{
c = !c;
}
}
return c;
}
public static bool distancePtPolyEdgesSqr(float[] pt, float[] verts, int nverts, float[] ed, float[] et) {
public static bool distancePtPolyEdgesSqr(float[] pt, float[] verts, int nverts, float[] ed, float[] et)
{
// TODO: Replace pnpoly with triArea2D tests?
int i, j;
bool c = false;
for (i = 0, j = nverts - 1; i < nverts; j = i++) {
for (i = 0, j = nverts - 1; i < nverts; j = i++)
{
int vi = i * 3;
int vj = j * 3;
if (((verts[vi + 2] > pt[2]) != (verts[vj + 2] > pt[2])) && (pt[0] < (verts[vj + 0] - verts[vi + 0])
* (pt[2] - verts[vi + 2]) / (verts[vj + 2] - verts[vi + 2]) + verts[vi + 0])) {
* (pt[2] - verts[vi + 2]) / (verts[vj + 2] - verts[vi + 2]) + verts[vi + 0]))
{
c = !c;
}
Tuple<float, float> edet = distancePtSegSqr2D(pt, verts, vj, vi);
ed[j] = edet.Item1;
et[j] = edet.Item2;
}
return c;
}
public static float[] projectPoly(float[] axis, float[] poly, int npoly) {
public static float[] projectPoly(float[] axis, float[] poly, int npoly)
{
float rmin, rmax;
rmin = rmax = vDot2D(axis, poly, 0);
for (int i = 1; i < npoly; ++i) {
for (int i = 1; i < npoly; ++i)
{
float d = vDot2D(axis, poly, i * 3);
rmin = Math.Min(rmin, d);
rmax = Math.Max(rmax, d);
}
return new float[] { rmin, rmax };
}
public static bool overlapRange(float amin, float amax, float bmin, float bmax, float eps) {
public static bool overlapRange(float amin, float amax, float bmin, float bmax, float eps)
{
return ((amin + eps) > bmax || (amax - eps) < bmin) ? false : true;
}
@ -418,9 +473,10 @@ public static class DetourCommon {
/// @par
///
/// All vertices are projected onto the xz-plane, so the y-values are ignored.
public static bool overlapPolyPoly2D(float[] polya, int npolya, float[] polyb, int npolyb) {
for (int i = 0, j = npolya - 1; i < npolya; j = i++) {
public static bool overlapPolyPoly2D(float[] polya, int npolya, float[] polyb, int npolyb)
{
for (int i = 0, j = npolya - 1; i < npolya; j = i++)
{
int va = j * 3;
int vb = i * 3;
@ -428,12 +484,15 @@ public static class DetourCommon {
float[] aminmax = projectPoly(n, polya, npolya);
float[] bminmax = projectPoly(n, polyb, npolyb);
if (!overlapRange(aminmax[0], aminmax[1], bminmax[0], bminmax[1], eps)) {
if (!overlapRange(aminmax[0], aminmax[1], bminmax[0], bminmax[1], eps))
{
// Found separating axis
return false;
}
}
for (int i = 0, j = npolyb - 1; i < npolyb; j = i++) {
for (int i = 0, j = npolyb - 1; i < npolyb; j = i++)
{
int va = j * 3;
int vb = i * 3;
@ -441,35 +500,43 @@ public static class DetourCommon {
float[] aminmax = projectPoly(n, polya, npolya);
float[] bminmax = projectPoly(n, polyb, npolyb);
if (!overlapRange(aminmax[0], aminmax[1], bminmax[0], bminmax[1], eps)) {
if (!overlapRange(aminmax[0], aminmax[1], bminmax[0], bminmax[1], eps))
{
// Found separating axis
return false;
}
}
return true;
}
// Returns a random point in a convex polygon.
// Adapted from Graphics Gems article.
public static float[] randomPointInConvexPoly(float[] pts, int npts, float[] areas, float s, float t) {
public static float[] randomPointInConvexPoly(float[] pts, int npts, float[] areas, float s, float t)
{
// Calc triangle araes
float areasum = 0.0f;
for (int i = 2; i < npts; i++) {
for (int i = 2; i < npts; i++)
{
areas[i] = triArea2D(pts, 0, (i - 1) * 3, i * 3);
areasum += Math.Max(0.001f, areas[i]);
}
// Find sub triangle weighted by area.
float thr = s * areasum;
float acc = 0.0f;
float u = 1.0f;
int tri = npts - 1;
for (int i = 2; i < npts; i++) {
for (int i = 2; i < npts; i++)
{
float dacc = areas[i];
if (thr >= acc && thr < (acc + dacc)) {
if (thr >= acc && thr < (acc + dacc))
{
u = (thr - acc) / dacc;
tri = i;
break;
}
acc += dacc;
}
@ -482,12 +549,16 @@ public static class DetourCommon {
int pb = (tri - 1) * 3;
int pc = tri * 3;
return new float[] { a * pts[pa] + b * pts[pb] + c * pts[pc],
return new float[]
{
a * pts[pa] + b * pts[pb] + c * pts[pc],
a * pts[pa + 1] + b * pts[pb + 1] + c * pts[pc + 1],
a * pts[pa + 2] + b * pts[pb + 2] + c * pts[pc + 2] };
a * pts[pa + 2] + b * pts[pb + 2] + c * pts[pc + 2]
};
}
public static int nextPow2(int v) {
public static int nextPow2(int v)
{
v--;
v |= v >> 1;
v |= v >> 2;
@ -498,7 +569,8 @@ public static class DetourCommon {
return v;
}
public static int ilog2(int v) {
public static int ilog2(int v)
{
int r;
int shift;
r = (v > 0xffff ? 1 : 0) << 4;
@ -516,7 +588,8 @@ public static class DetourCommon {
return r;
}
public class IntersectResult {
public class IntersectResult
{
public bool intersects;
public float tmin;
public float tmax = 1f;
@ -524,83 +597,107 @@ public static class DetourCommon {
public int segMax = -1;
}
public static IntersectResult intersectSegmentPoly2D(float[] p0, float[] p1, float[] verts, int nverts) {
public static IntersectResult intersectSegmentPoly2D(float[] p0, float[] p1, float[] verts, int nverts)
{
IntersectResult result = new IntersectResult();
float EPS = 0.00000001f;
float[] dir = vSub(p1, p0);
VectorPtr p0v = new VectorPtr(p0);
for (int i = 0, j = nverts - 1; i < nverts; j = i++) {
for (int i = 0, j = nverts - 1; i < nverts; j = i++)
{
VectorPtr vpj = new VectorPtr(verts, j * 3);
float[] edge = vSub(new VectorPtr(verts, i * 3), vpj);
float[] diff = vSub(p0v, vpj);
float n = vPerp2D(edge, diff);
float d = vPerp2D(dir, edge);
if (Math.Abs(d) < EPS) {
if (Math.Abs(d) < EPS)
{
// S is nearly parallel to this edge
if (n < 0) {
if (n < 0)
{
return result;
} else {
}
else
{
continue;
}
}
float t = n / d;
if (d < 0) {
if (d < 0)
{
// segment S is entering across this edge
if (t > result.tmin) {
if (t > result.tmin)
{
result.tmin = t;
result.segMin = j;
// S enters after leaving polygon
if (result.tmin > result.tmax) {
if (result.tmin > result.tmax)
{
return result;
}
}
} else {
}
else
{
// segment S is leaving across this edge
if (t < result.tmax) {
if (t < result.tmax)
{
result.tmax = t;
result.segMax = j;
// S leaves before entering polygon
if (result.tmax < result.tmin) {
if (result.tmax < result.tmin)
{
return result;
}
}
}
}
result.intersects = true;
return result;
}
public static Tuple<float, float> distancePtSegSqr2D(float[] pt, float[] verts, int p, int q) {
public static Tuple<float, float> distancePtSegSqr2D(float[] pt, float[] verts, int p, int q)
{
float pqx = verts[q + 0] - verts[p + 0];
float pqz = verts[q + 2] - verts[p + 2];
float dx = pt[0] - verts[p + 0];
float dz = pt[2] - verts[p + 2];
float d = pqx * pqx + pqz * pqz;
float t = pqx * dx + pqz * dz;
if (d > 0) {
if (d > 0)
{
t /= d;
}
if (t < 0) {
if (t < 0)
{
t = 0;
} else if (t > 1) {
}
else if (t > 1)
{
t = 1;
}
dx = verts[p + 0] + t * pqx - pt[0];
dz = verts[p + 2] + t * pqz - pt[2];
return Tuple.Create(dx * dx + dz * dz, t);
}
public static int oppositeTile(int side) {
public static int oppositeTile(int side)
{
return (side + 4) & 0x7;
}
public static float vperpXZ(float[] a, float[] b) {
public static float vperpXZ(float[] a, float[] b)
{
return a[0] * b[2] - a[2] * b[0];
}
public static Tuple<float, float>? intersectSegSeg2D(float[] ap, float[] aq, float[] bp, float[] bq) {
public static Tuple<float, float>? intersectSegSeg2D(float[] ap, float[] aq, float[] bp, float[] bq)
{
float[] u = vSub(aq, ap);
float[] v = vSub(bq, bp);
float[] w = vSub(ap, bp);
@ -609,12 +706,14 @@ public static class DetourCommon {
{
return null;
}
float s = vperpXZ(v, w) / d;
float t = vperpXZ(u, w) / d;
return Tuple.Create(s, t);
}
public static float[] vScale(float[] @in, float scale) {
public static float[] vScale(float[] @in, float scale)
{
float[] @out = new float[3];
@out[0] = @in[0] * scale;
@out[1] = @in[1] * scale;
@ -626,16 +725,16 @@ public static class DetourCommon {
/// @param[in] v A point. [(x, y, z)]
/// @return True if all of the point's components are finite, i.e. not NaN
/// or any of the infinities.
public static bool vIsFinite(float[] v) {
public static bool vIsFinite(float[] v)
{
return float.IsFinite(v[0]) && float.IsFinite(v[1]) && float.IsFinite(v[2]);
}
/// Checks that the specified vector's 2D components are finite.
/// @param[in] v A point. [(x, y, z)]
public static bool vIsFinite2D(float[] v) {
public static bool vIsFinite2D(float[] v)
{
return float.IsFinite(v[0]) && float.IsFinite(v[2]);
}
}
}

View File

@ -17,33 +17,36 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
//TODO: (PP) Add comments
public class FindDistanceToWallResult {
public class FindDistanceToWallResult
{
private readonly float distance;
private readonly float[] position;
private readonly float[] normal;
public FindDistanceToWallResult(float distance, float[] position, float[] normal) {
public FindDistanceToWallResult(float distance, float[] position, float[] normal)
{
this.distance = distance;
this.position = position;
this.normal = normal;
}
public float getDistance() {
public float getDistance()
{
return distance;
}
public float[] getPosition() {
public float[] getPosition()
{
return position;
}
public float[] getNormal() {
public float[] getNormal()
{
return normal;
}
}
}

View File

@ -22,25 +22,26 @@ using System.Collections.Generic;
namespace DotRecast.Detour
{
//TODO: (PP) Add comments
public class FindLocalNeighbourhoodResult {
public class FindLocalNeighbourhoodResult
{
private readonly List<long> refs;
private readonly List<long> parentRefs;
public FindLocalNeighbourhoodResult(List<long> refs, List<long> parentRefs) {
public FindLocalNeighbourhoodResult(List<long> refs, List<long> parentRefs)
{
this.@refs = refs;
this.parentRefs = parentRefs;
}
public List<long> getRefs() {
public List<long> getRefs()
{
return refs;
}
public List<long> getParentRefs() {
public List<long> getParentRefs()
{
return parentRefs;
}
}
}

View File

@ -2,12 +2,10 @@ using System;
namespace DotRecast.Detour
{
using static DetourCommon;
public class FindNearestPolyQuery : PolyQuery {
public class FindNearestPolyQuery : PolyQuery
{
private readonly NavMeshQuery query;
private readonly float[] center;
private long nearestRef;
@ -15,14 +13,16 @@ public class FindNearestPolyQuery : PolyQuery {
private bool overPoly;
private float nearestDistanceSqr;
public FindNearestPolyQuery(NavMeshQuery query, float[] center) {
public FindNearestPolyQuery(NavMeshQuery query, float[] center)
{
this.query = query;
this.center = center;
nearestDistanceSqr = float.MaxValue;
nearestPt = new float[] { center[0], center[1], center[2] };
}
public void process(MeshTile tile, Poly poly, long refs) {
public void process(MeshTile tile, Poly poly, long refs)
{
// Find nearest polygon amongst the nearby polygons.
Result<ClosestPointOnPolyResult> closest = query.closestPointOnPoly(refs, center);
bool posOverPoly = closest.result.isPosOverPoly();
@ -32,26 +32,28 @@ public class FindNearestPolyQuery : PolyQuery {
// climb height, favor that instead of straight line nearest point.
float d = 0;
float[] diff = vSub(center, closestPtPoly);
if (posOverPoly) {
if (posOverPoly)
{
d = Math.Abs(diff[1]) - tile.data.header.walkableClimb;
d = d > 0 ? d * d : 0;
} else {
}
else
{
d = vLenSqr(diff);
}
if (d < nearestDistanceSqr) {
if (d < nearestDistanceSqr)
{
nearestPt = closestPtPoly;
nearestDistanceSqr = d;
nearestRef = refs;
overPoly = posOverPoly;
}
}
public FindNearestPolyResult result() {
public FindNearestPolyResult result()
{
return new FindNearestPolyResult(nearestRef, nearestPt, overPoly);
}
}
}

View File

@ -17,35 +17,37 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
public class FindNearestPolyResult {
public class FindNearestPolyResult
{
private readonly long nearestRef;
private readonly float[] nearestPos;
private readonly bool overPoly;
public FindNearestPolyResult(long nearestRef, float[] nearestPos, bool overPoly) {
public FindNearestPolyResult(long nearestRef, float[] nearestPos, bool overPoly)
{
this.nearestRef = nearestRef;
this.nearestPos = nearestPos;
this.overPoly = overPoly;
}
/** Returns the reference id of the nearest polygon. 0 if no polygon is found. */
public long getNearestRef() {
public long getNearestRef()
{
return nearestRef;
}
/** Returns the nearest point on the polygon. [opt] [(x, y, z)]. Unchanged if no polygon is found. */
public float[] getNearestPos() {
public float[] getNearestPos()
{
return nearestPos;
}
public bool isOverPoly() {
public bool isOverPoly()
{
return overPoly;
}
}
}

View File

@ -22,32 +22,33 @@ using System.Collections.Generic;
namespace DotRecast.Detour
{
// TODO: (PP) Add comments
public class FindPolysAroundResult {
public class FindPolysAroundResult
{
private readonly List<long> refs;
private readonly List<long> parentRefs;
private readonly List<float> costs;
public FindPolysAroundResult(List<long> refs, List<long> parentRefs, List<float> costs) {
public FindPolysAroundResult(List<long> refs, List<long> parentRefs, List<float> costs)
{
this.@refs = refs;
this.parentRefs = parentRefs;
this.costs = costs;
}
public List<long> getRefs() {
public List<long> getRefs()
{
return refs;
}
public List<long> getParentRefs() {
public List<long> getParentRefs()
{
return parentRefs;
}
public List<float> getCosts() {
public List<float> getCosts()
{
return costs;
}
}
}

View File

@ -17,29 +17,31 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
//TODO: (PP) Add comments
public class FindRandomPointResult {
public class FindRandomPointResult
{
private readonly long randomRef;
private readonly float[] randomPt;
public FindRandomPointResult(long randomRef, float[] randomPt) {
public FindRandomPointResult(long randomRef, float[] randomPt)
{
this.randomRef = randomRef;
this.randomPt = randomPt;
}
/// @param[out] randomRef The reference id of the random location.
public long getRandomRef() {
public long getRandomRef()
{
return randomRef;
}
/// @param[out] randomPt The random location.
public float[] getRandomPt() {
public float[] getRandomPt()
{
return randomPt;
}
}
}

View File

@ -22,27 +22,25 @@ using System.Collections.Generic;
namespace DotRecast.Detour
{
public class GetPolyWallSegmentsResult {
public class GetPolyWallSegmentsResult
{
private readonly List<float[]> segmentVerts;
private readonly List<long> segmentRefs;
public GetPolyWallSegmentsResult(List<float[]> segmentVerts, List<long> segmentRefs) {
public GetPolyWallSegmentsResult(List<float[]> segmentVerts, List<long> segmentRefs)
{
this.segmentVerts = segmentVerts;
this.segmentRefs = segmentRefs;
}
public List<float[]> getSegmentVerts() {
public List<float[]> getSegmentVerts()
{
return segmentVerts;
}
public List<long> getSegmentRefs() {
public List<long> getSegmentRefs()
{
return segmentRefs;
}
}
}

View File

@ -22,43 +22,54 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io
{
public abstract class DetourWriter {
protected void write(BinaryWriter stream, float value, ByteOrder order) {
public abstract class DetourWriter
{
protected void write(BinaryWriter stream, float value, ByteOrder order)
{
byte[] bytes = BitConverter.GetBytes(value);
int i = BitConverter.ToInt32(bytes, 0);
write(stream, i, order);
}
protected void write(BinaryWriter stream, short value, ByteOrder order) {
if (order == ByteOrder.BIG_ENDIAN) {
protected void write(BinaryWriter stream, short value, ByteOrder order)
{
if (order == ByteOrder.BIG_ENDIAN)
{
stream.Write((byte)((value >> 8) & 0xFF));
stream.Write((byte)(value & 0xFF));
} else {
}
else
{
stream.Write((byte)(value & 0xFF));
stream.Write((byte)((value >> 8) & 0xFF));
}
}
protected void write(BinaryWriter stream, long value, ByteOrder order) {
if (order == ByteOrder.BIG_ENDIAN) {
protected void write(BinaryWriter stream, long value, ByteOrder order)
{
if (order == ByteOrder.BIG_ENDIAN)
{
write(stream, (int)((ulong)value >> 32), order);
write(stream, (int)(value & 0xFFFFFFFF), order);
} else {
}
else
{
write(stream, (int)(value & 0xFFFFFFFF), order);
write(stream, (int)((ulong)value >> 32), order);
}
}
protected void write(BinaryWriter stream, int value, ByteOrder order) {
if (order == ByteOrder.BIG_ENDIAN) {
protected void write(BinaryWriter stream, int value, ByteOrder order)
{
if (order == ByteOrder.BIG_ENDIAN)
{
stream.Write((byte)((value >> 24) & 0xFF));
stream.Write((byte)((value >> 16) & 0xFF));
stream.Write((byte)((value >> 8) & 0xFF));
stream.Write((byte)(value & 0xFF));
} else {
}
else
{
stream.Write((byte)(value & 0xFF));
stream.Write((byte)((value >> 8) & 0xFF));
stream.Write((byte)((value >> 16) & 0xFF));
@ -66,21 +77,22 @@ public abstract class DetourWriter {
}
}
protected void write(BinaryWriter stream, bool @bool) {
protected void write(BinaryWriter stream, bool @bool)
{
write(stream, (byte)(@bool ? 1 : 0));
}
protected void write(BinaryWriter stream, byte value) {
protected void write(BinaryWriter stream, byte value)
{
stream.Write(value);
}
protected void write(BinaryWriter stream, MemoryStream data) {
protected void write(BinaryWriter stream, MemoryStream data)
{
data.Position = 0;
byte[] buffer = new byte[data.Length];
data.Read(buffer, 0, buffer.Length);
stream.Write(buffer);
}
}
}

View File

@ -22,24 +22,26 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io
{
public static class IOUtils {
public static ByteBuffer toByteBuffer(BinaryReader @is, bool direct) {
public static class IOUtils
{
public static ByteBuffer toByteBuffer(BinaryReader @is, bool direct)
{
byte[] data = toByteArray(@is);
if (direct) {
if (direct)
{
Array.Reverse(data);
}
return new ByteBuffer(data);
}
public static byte[] toByteArray(BinaryReader inputStream) {
public static byte[] toByteArray(BinaryReader inputStream)
{
using var baos = new MemoryStream();
byte[] buffer = new byte[4096];
int l;
while ((l = inputStream.Read(buffer)) > 0) {
while ((l = inputStream.Read(buffer)) > 0)
{
baos.Write(buffer, 0, l);
}
@ -59,5 +61,4 @@ public static class IOUtils {
return (int)s;
}
}
}

View File

@ -21,27 +21,29 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io
{
public class MeshDataReader {
public class MeshDataReader
{
public const int DT_POLY_DETAIL_SIZE = 10;
public MeshData read(BinaryReader stream, int maxVertPerPoly) {
public MeshData read(BinaryReader stream, int maxVertPerPoly)
{
ByteBuffer buf = IOUtils.toByteBuffer(stream);
return read(buf, maxVertPerPoly, false);
}
public MeshData read(ByteBuffer buf, int maxVertPerPoly) {
public MeshData read(ByteBuffer buf, int maxVertPerPoly)
{
return read(buf, maxVertPerPoly, false);
}
public MeshData read32Bit(BinaryReader stream, int maxVertPerPoly) {
public MeshData read32Bit(BinaryReader stream, int maxVertPerPoly)
{
ByteBuffer buf = IOUtils.toByteBuffer(stream);
return read(buf, maxVertPerPoly, true);
}
public MeshData read32Bit(ByteBuffer buf, int maxVertPerPoly) {
public MeshData read32Bit(ByteBuffer buf, int maxVertPerPoly)
{
return read(buf, maxVertPerPoly, true);
}
@ -51,20 +53,27 @@ public class MeshDataReader {
MeshHeader header = new MeshHeader();
data.header = header;
header.magic = buf.getInt();
if (header.magic != MeshHeader.DT_NAVMESH_MAGIC) {
if (header.magic != MeshHeader.DT_NAVMESH_MAGIC)
{
header.magic = IOUtils.swapEndianness(header.magic);
if (header.magic != MeshHeader.DT_NAVMESH_MAGIC) {
if (header.magic != MeshHeader.DT_NAVMESH_MAGIC)
{
throw new IOException("Invalid magic");
}
buf.order(buf.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
}
header.version = buf.getInt();
if (header.version != MeshHeader.DT_NAVMESH_VERSION) {
if (header.version != MeshHeader.DT_NAVMESH_VERSION)
{
if (header.version < MeshHeader.DT_NAVMESH_VERSION_RECAST4J_FIRST
|| header.version > MeshHeader.DT_NAVMESH_VERSION_RECAST4J_LAST) {
|| header.version > MeshHeader.DT_NAVMESH_VERSION_RECAST4J_LAST)
{
throw new IOException("Invalid version " + header.version);
}
}
bool cCompatibility = header.version == MeshHeader.DT_NAVMESH_VERSION;
header.x = buf.getInt();
header.y = buf.getInt();
@ -82,18 +91,24 @@ public class MeshDataReader {
header.walkableHeight = buf.getFloat();
header.walkableRadius = buf.getFloat();
header.walkableClimb = buf.getFloat();
for (int j = 0; j < 3; j++) {
for (int j = 0; j < 3; j++)
{
header.bmin[j] = buf.getFloat();
}
for (int j = 0; j < 3; j++) {
for (int j = 0; j < 3; j++)
{
header.bmax[j] = buf.getFloat();
}
header.bvQuantFactor = buf.getFloat();
data.verts = readVerts(buf, header.vertCount);
data.polys = readPolys(buf, header, maxVertPerPoly);
if (cCompatibility) {
if (cCompatibility)
{
buf.position(buf.position() + header.maxLinkCount * getSizeofLink(is32Bit));
}
data.detailMeshes = readPolyDetails(buf, header, cCompatibility);
data.detailVerts = readVerts(buf, header.detailVertCount);
data.detailTris = readDTris(buf, header);
@ -105,101 +120,137 @@ public class MeshDataReader {
public const int LINK_SIZEOF = 16;
public const int LINK_SIZEOF32BIT = 12;
public static int getSizeofLink(bool is32Bit) {
public static int getSizeofLink(bool is32Bit)
{
return is32Bit ? LINK_SIZEOF32BIT : LINK_SIZEOF;
}
private float[] readVerts(ByteBuffer buf, int count) {
private float[] readVerts(ByteBuffer buf, int count)
{
float[] verts = new float[count * 3];
for (int i = 0; i < verts.Length; i++) {
for (int i = 0; i < verts.Length; i++)
{
verts[i] = buf.getFloat();
}
return verts;
}
private Poly[] readPolys(ByteBuffer buf, MeshHeader header, int maxVertPerPoly) {
private Poly[] readPolys(ByteBuffer buf, MeshHeader header, int maxVertPerPoly)
{
Poly[] polys = new Poly[header.polyCount];
for (int i = 0; i < polys.Length; i++) {
for (int i = 0; i < polys.Length; i++)
{
polys[i] = new Poly(i, maxVertPerPoly);
if (header.version < MeshHeader.DT_NAVMESH_VERSION_RECAST4J_NO_POLY_FIRSTLINK) {
if (header.version < MeshHeader.DT_NAVMESH_VERSION_RECAST4J_NO_POLY_FIRSTLINK)
{
buf.getInt(); // polys[i].firstLink
}
for (int j = 0; j < polys[i].verts.Length; j++) {
for (int j = 0; j < polys[i].verts.Length; j++)
{
polys[i].verts[j] = buf.getShort() & 0xFFFF;
}
for (int j = 0; j < polys[i].neis.Length; j++) {
for (int j = 0; j < polys[i].neis.Length; j++)
{
polys[i].neis[j] = buf.getShort() & 0xFFFF;
}
polys[i].flags = buf.getShort() & 0xFFFF;
polys[i].vertCount = buf.get() & 0xFF;
polys[i].areaAndtype = buf.get() & 0xFF;
}
return polys;
}
private PolyDetail[] readPolyDetails(ByteBuffer buf, MeshHeader header, bool cCompatibility) {
private PolyDetail[] readPolyDetails(ByteBuffer buf, MeshHeader header, bool cCompatibility)
{
PolyDetail[] polys = new PolyDetail[header.detailMeshCount];
for (int i = 0; i < polys.Length; i++) {
for (int i = 0; i < polys.Length; i++)
{
polys[i] = new PolyDetail();
polys[i].vertBase = buf.getInt();
polys[i].triBase = buf.getInt();
polys[i].vertCount = buf.get() & 0xFF;
polys[i].triCount = buf.get() & 0xFF;
if (cCompatibility) {
if (cCompatibility)
{
buf.getShort(); // C struct padding
}
}
return polys;
}
private int[] readDTris(ByteBuffer buf, MeshHeader header) {
private int[] readDTris(ByteBuffer buf, MeshHeader header)
{
int[] tris = new int[4 * header.detailTriCount];
for (int i = 0; i < tris.Length; i++) {
for (int i = 0; i < tris.Length; i++)
{
tris[i] = buf.get() & 0xFF;
}
return tris;
}
private BVNode[] readBVTree(ByteBuffer buf, MeshHeader header) {
private BVNode[] readBVTree(ByteBuffer buf, MeshHeader header)
{
BVNode[] nodes = new BVNode[header.bvNodeCount];
for (int i = 0; i < nodes.Length; i++) {
for (int i = 0; i < nodes.Length; i++)
{
nodes[i] = new BVNode();
if (header.version < MeshHeader.DT_NAVMESH_VERSION_RECAST4J_32BIT_BVTREE) {
for (int j = 0; j < 3; j++) {
if (header.version < MeshHeader.DT_NAVMESH_VERSION_RECAST4J_32BIT_BVTREE)
{
for (int j = 0; j < 3; j++)
{
nodes[i].bmin[j] = buf.getShort() & 0xFFFF;
}
for (int j = 0; j < 3; j++) {
for (int j = 0; j < 3; j++)
{
nodes[i].bmax[j] = buf.getShort() & 0xFFFF;
}
} else {
for (int j = 0; j < 3; j++) {
}
else
{
for (int j = 0; j < 3; j++)
{
nodes[i].bmin[j] = buf.getInt();
}
for (int j = 0; j < 3; j++) {
for (int j = 0; j < 3; j++)
{
nodes[i].bmax[j] = buf.getInt();
}
}
nodes[i].i = buf.getInt();
}
return nodes;
}
private OffMeshConnection[] readOffMeshCons(ByteBuffer buf, MeshHeader header) {
private OffMeshConnection[] readOffMeshCons(ByteBuffer buf, MeshHeader header)
{
OffMeshConnection[] cons = new OffMeshConnection[header.offMeshConCount];
for (int i = 0; i < cons.Length; i++) {
for (int i = 0; i < cons.Length; i++)
{
cons[i] = new OffMeshConnection();
for (int j = 0; j < 6; j++) {
for (int j = 0; j < 6; j++)
{
cons[i].pos[j] = buf.getFloat();
}
cons[i].rad = buf.getFloat();
cons[i].poly = buf.getShort() & 0xFFFF;
cons[i].flags = buf.get() & 0xFF;
cons[i].side = buf.get() & 0xFF;
cons[i].userId = buf.getInt();
}
return cons;
}
}
}

View File

@ -21,11 +21,10 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io
{
public class MeshDataWriter : DetourWriter {
public void write(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility) {
public class MeshDataWriter : DetourWriter
{
public void write(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility)
{
MeshHeader header = data.header;
write(stream, header.magic, order);
write(stream, cCompatibility ? MeshHeader.DT_NAVMESH_VERSION : MeshHeader.DT_NAVMESH_VERSION_RECAST4J_LAST, order);
@ -54,10 +53,12 @@ public class MeshDataWriter : DetourWriter {
write(stream, header.bvQuantFactor, order);
writeVerts(stream, data.verts, header.vertCount, order);
writePolys(stream, data, order, cCompatibility);
if (cCompatibility) {
if (cCompatibility)
{
byte[] linkPlaceholder = new byte[header.maxLinkCount * MeshDataReader.getSizeofLink(false)];
stream.Write(linkPlaceholder);
}
writePolyDetails(stream, data, order, cCompatibility);
writeVerts(stream, data.detailVerts, header.detailVertCount, order);
writeDTris(stream, data);
@ -65,23 +66,33 @@ public class MeshDataWriter : DetourWriter {
writeOffMeshCons(stream, data, order);
}
private void writeVerts(BinaryWriter stream, float[] verts, int count, ByteOrder order) {
for (int i = 0; i < count * 3; i++) {
private void writeVerts(BinaryWriter stream, float[] verts, int count, ByteOrder order)
{
for (int i = 0; i < count * 3; i++)
{
write(stream, verts[i], order);
}
}
private void writePolys(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility) {
for (int i = 0; i < data.header.polyCount; i++) {
if (cCompatibility) {
private void writePolys(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility)
{
for (int i = 0; i < data.header.polyCount; i++)
{
if (cCompatibility)
{
write(stream, 0xFFFF, order);
}
for (int j = 0; j < data.polys[i].verts.Length; j++) {
for (int j = 0; j < data.polys[i].verts.Length; j++)
{
write(stream, (short)data.polys[i].verts[j], order);
}
for (int j = 0; j < data.polys[i].neis.Length; j++) {
for (int j = 0; j < data.polys[i].neis.Length; j++)
{
write(stream, (short)data.polys[i].neis[j], order);
}
write(stream, (short)data.polys[i].flags, order);
write(stream, (byte)data.polys[i].vertCount);
write(stream, (byte)data.polys[i].areaAndtype);
@ -90,49 +101,69 @@ public class MeshDataWriter : DetourWriter {
private void writePolyDetails(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility)
{
for (int i = 0; i < data.header.detailMeshCount; i++) {
for (int i = 0; i < data.header.detailMeshCount; i++)
{
write(stream, data.detailMeshes[i].vertBase, order);
write(stream, data.detailMeshes[i].triBase, order);
write(stream, (byte)data.detailMeshes[i].vertCount);
write(stream, (byte)data.detailMeshes[i].triCount);
if (cCompatibility) {
if (cCompatibility)
{
write(stream, (short)0, order);
}
}
}
private void writeDTris(BinaryWriter stream, MeshData data) {
for (int i = 0; i < data.header.detailTriCount * 4; i++) {
private void writeDTris(BinaryWriter stream, MeshData data)
{
for (int i = 0; i < data.header.detailTriCount * 4; i++)
{
write(stream, (byte)data.detailTris[i]);
}
}
private void writeBVTree(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility) {
for (int i = 0; i < data.header.bvNodeCount; i++) {
if (cCompatibility) {
for (int j = 0; j < 3; j++) {
private void writeBVTree(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility)
{
for (int i = 0; i < data.header.bvNodeCount; i++)
{
if (cCompatibility)
{
for (int j = 0; j < 3; j++)
{
write(stream, (short)data.bvTree[i].bmin[j], order);
}
for (int j = 0; j < 3; j++) {
for (int j = 0; j < 3; j++)
{
write(stream, (short)data.bvTree[i].bmax[j], order);
}
} else {
for (int j = 0; j < 3; j++) {
}
else
{
for (int j = 0; j < 3; j++)
{
write(stream, data.bvTree[i].bmin[j], order);
}
for (int j = 0; j < 3; j++) {
for (int j = 0; j < 3; j++)
{
write(stream, data.bvTree[i].bmax[j], order);
}
}
write(stream, data.bvTree[i].i, order);
}
}
private void writeOffMeshCons(BinaryWriter stream, MeshData data, ByteOrder order) {
for (int i = 0; i < data.header.offMeshConCount; i++) {
for (int j = 0; j < 6; j++) {
private void writeOffMeshCons(BinaryWriter stream, MeshData data, ByteOrder order)
{
for (int i = 0; i < data.header.offMeshConCount; i++)
{
for (int j = 0; j < 6; j++)
{
write(stream, data.offMeshCons[i].pos[j], order);
}
write(stream, data.offMeshCons[i].rad, order);
write(stream, (short)data.offMeshCons[i].poly, order);
write(stream, (byte)data.offMeshCons[i].flags);
@ -140,7 +171,5 @@ public class MeshDataWriter : DetourWriter {
write(stream, data.offMeshCons[i].userId, order);
}
}
}
}

View File

@ -22,98 +22,124 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io
{
using static DetourCommon;
public class MeshSetReader {
public class MeshSetReader
{
private readonly MeshDataReader meshReader = new MeshDataReader();
private readonly NavMeshParamReader paramReader = new NavMeshParamReader();
public NavMesh read(BinaryReader @is, int maxVertPerPoly) {
public NavMesh read(BinaryReader @is, int maxVertPerPoly)
{
return read(IOUtils.toByteBuffer(@is), maxVertPerPoly, false);
}
public NavMesh read(ByteBuffer bb, int maxVertPerPoly) {
public NavMesh read(ByteBuffer bb, int maxVertPerPoly)
{
return read(bb, maxVertPerPoly, false);
}
public NavMesh read32Bit(BinaryReader @is, int maxVertPerPoly) {
public NavMesh read32Bit(BinaryReader @is, int maxVertPerPoly)
{
return read(IOUtils.toByteBuffer(@is), maxVertPerPoly, true);
}
public NavMesh read32Bit(ByteBuffer bb, int maxVertPerPoly) {
public NavMesh read32Bit(ByteBuffer bb, int maxVertPerPoly)
{
return read(bb, maxVertPerPoly, true);
}
public NavMesh read(BinaryReader @is) {
public NavMesh read(BinaryReader @is)
{
return read(IOUtils.toByteBuffer(@is));
}
public NavMesh read(ByteBuffer bb) {
public NavMesh read(ByteBuffer bb)
{
return read(bb, -1, false);
}
NavMesh read(ByteBuffer bb, int maxVertPerPoly, bool is32Bit) {
NavMesh read(ByteBuffer bb, int maxVertPerPoly, bool is32Bit)
{
NavMeshSetHeader header = readHeader(bb, maxVertPerPoly);
if (header.maxVertsPerPoly <= 0) {
if (header.maxVertsPerPoly <= 0)
{
throw new IOException("Invalid number of verts per poly " + header.maxVertsPerPoly);
}
bool cCompatibility = header.version == NavMeshSetHeader.NAVMESHSET_VERSION;
NavMesh mesh = new NavMesh(header.option, header.maxVertsPerPoly);
readTiles(bb, is32Bit, header, cCompatibility, mesh);
return mesh;
}
private NavMeshSetHeader readHeader(ByteBuffer bb, int maxVertsPerPoly) {
private NavMeshSetHeader readHeader(ByteBuffer bb, int maxVertsPerPoly)
{
NavMeshSetHeader header = new NavMeshSetHeader();
header.magic = bb.getInt();
if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC) {
if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC)
{
header.magic = IOUtils.swapEndianness(header.magic);
if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC) {
if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC)
{
throw new IOException("Invalid magic " + header.magic);
}
bb.order(bb.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
}
header.version = bb.getInt();
if (header.version != NavMeshSetHeader.NAVMESHSET_VERSION && header.version != NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J_1
&& header.version != NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J) {
&& header.version != NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J)
{
throw new IOException("Invalid version " + header.version);
}
header.numTiles = bb.getInt();
header.option = paramReader.read(bb);
header.maxVertsPerPoly = maxVertsPerPoly;
if (header.version == NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J) {
if (header.version == NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J)
{
header.maxVertsPerPoly = bb.getInt();
}
return header;
}
private void readTiles(ByteBuffer bb, bool is32Bit, NavMeshSetHeader header, bool cCompatibility, NavMesh mesh)
{
// Read tiles.
for (int i = 0; i < header.numTiles; ++i) {
for (int i = 0; i < header.numTiles; ++i)
{
NavMeshTileHeader tileHeader = new NavMeshTileHeader();
if (is32Bit) {
if (is32Bit)
{
tileHeader.tileRef = convert32BitRef(bb.getInt(), header.option);
} else {
}
else
{
tileHeader.tileRef = bb.getLong();
}
tileHeader.dataSize = bb.getInt();
if (tileHeader.tileRef == 0 || tileHeader.dataSize == 0) {
if (tileHeader.tileRef == 0 || tileHeader.dataSize == 0)
{
break;
}
if (cCompatibility && !is32Bit) {
if (cCompatibility && !is32Bit)
{
bb.getInt(); // C struct padding
}
MeshData data = meshReader.read(bb, mesh.getMaxVertsPerPoly(), is32Bit);
mesh.addTile(data, i, tileHeader.tileRef);
}
}
private long convert32BitRef(int refs, NavMeshParams option) {
private long convert32BitRef(int refs, NavMeshParams option)
{
int m_tileBits = ilog2(nextPow2(option.maxTiles));
int m_polyBits = ilog2(nextPow2(option.maxPolys));
// Only allow 31 salt bits, since the salt mask is calculated using 32bit uint and it will overflow.
@ -127,5 +153,4 @@ public class MeshSetReader {
return NavMesh.encodePolyId(salt, it, ip);
}
}
}

View File

@ -21,40 +21,48 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io
{
public class MeshSetWriter : DetourWriter {
public class MeshSetWriter : DetourWriter
{
private readonly MeshDataWriter writer = new MeshDataWriter();
private readonly NavMeshParamWriter paramWriter = new NavMeshParamWriter();
public void write(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility) {
public void write(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility)
{
writeHeader(stream, mesh, order, cCompatibility);
writeTiles(stream, mesh, order, cCompatibility);
}
private void writeHeader(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility) {
private void writeHeader(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility)
{
write(stream, NavMeshSetHeader.NAVMESHSET_MAGIC, order);
write(stream, cCompatibility ? NavMeshSetHeader.NAVMESHSET_VERSION : NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J, order);
int numTiles = 0;
for (int i = 0; i < mesh.getMaxTiles(); ++i) {
for (int i = 0; i < mesh.getMaxTiles(); ++i)
{
MeshTile tile = mesh.getTile(i);
if (tile == null || tile.data == null || tile.data.header == null) {
if (tile == null || tile.data == null || tile.data.header == null)
{
continue;
}
numTiles++;
}
write(stream, numTiles, order);
paramWriter.write(stream, mesh.getParams(), order);
if (!cCompatibility) {
if (!cCompatibility)
{
write(stream, mesh.getMaxVertsPerPoly(), order);
}
}
private void writeTiles(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility) {
for (int i = 0; i < mesh.getMaxTiles(); ++i) {
private void writeTiles(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility)
{
for (int i = 0; i < mesh.getMaxTiles(); ++i)
{
MeshTile tile = mesh.getTile(i);
if (tile == null || tile.data == null || tile.data.header == null) {
if (tile == null || tile.data == null || tile.data.header == null)
{
continue;
}
@ -70,13 +78,13 @@ public class MeshSetWriter : DetourWriter {
tileHeader.dataSize = ba.Length;
write(stream, tileHeader.tileRef, order);
write(stream, tileHeader.dataSize, order);
if (cCompatibility) {
if (cCompatibility)
{
write(stream, 0, order); // C struct padding
}
stream.Write(ba);
}
}
}
}

View File

@ -2,11 +2,10 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io
{
public class NavMeshParamReader {
public NavMeshParams read(ByteBuffer bb) {
public class NavMeshParamReader
{
public NavMeshParams read(ByteBuffer bb)
{
NavMeshParams option = new NavMeshParams();
option.orig[0] = bb.getFloat();
option.orig[1] = bb.getFloat();
@ -17,7 +16,5 @@ public class NavMeshParamReader {
option.maxPolys = bb.getInt();
return option;
}
}
}

View File

@ -3,11 +3,10 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io
{
public class NavMeshParamWriter : DetourWriter {
public void write(BinaryWriter stream, NavMeshParams option, ByteOrder order) {
public class NavMeshParamWriter : DetourWriter
{
public void write(BinaryWriter stream, NavMeshParams option, ByteOrder order)
{
write(stream, option.orig[0], order);
write(stream, option.orig[1], order);
write(stream, option.orig[2], order);
@ -16,7 +15,5 @@ public class NavMeshParamWriter : DetourWriter {
write(stream, option.maxTiles, order);
write(stream, option.maxPolys, order);
}
}
}

View File

@ -15,12 +15,11 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.Io
{
public class NavMeshSetHeader {
public class NavMeshSetHeader
{
public const int NAVMESHSET_MAGIC = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; // 'MSET';
public const int NAVMESHSET_VERSION = 1;
public const int NAVMESHSET_VERSION_RECAST4J_1 = 0x8801;
@ -31,7 +30,5 @@ public class NavMeshSetHeader {
public int numTiles;
public NavMeshParams option = new NavMeshParams();
public int maxVertsPerPoly;
}
}

View File

@ -1,10 +1,8 @@
namespace DotRecast.Detour.Io
{
public class NavMeshTileHeader {
public class NavMeshTileHeader
{
public long tileRef;
public int dataSize;
}
}

View File

@ -21,32 +21,35 @@ using System.Collections.Generic;
namespace DotRecast.Detour
{
using static DetourCommon;
public class LegacyNavMeshQuery : NavMeshQuery {
public class LegacyNavMeshQuery : NavMeshQuery
{
private static float H_SCALE = 0.999f; // Search heuristic scale.
public LegacyNavMeshQuery(NavMesh nav) : base(nav) {
public LegacyNavMeshQuery(NavMesh nav) : base(nav)
{
}
public override Result<List<long>> findPath(long startRef, long endRef, float[] startPos, float[] endPos, QueryFilter filter,
int options, float raycastLimit) {
int options, float raycastLimit)
{
return findPath(startRef, endRef, startPos, endPos, filter);
}
public override Result<List<long>> findPath(long startRef, long endRef, float[] startPos, float[] endPos,
QueryFilter filter) {
QueryFilter filter)
{
// Validate input
if (!m_nav.isValidPolyRef(startRef) || !m_nav.isValidPolyRef(endRef) || null == startPos
|| !vIsFinite(startPos) || null == endPos || !vIsFinite(endPos) || null == filter) {
|| !vIsFinite(startPos) || null == endPos || !vIsFinite(endPos) || null == filter)
{
return Results.invalidParam<List<long>>();
}
if (startRef == endRef) {
if (startRef == endRef)
{
List<long> singlePath = new List<long>(1);
singlePath.Add(startRef);
return Results.success(singlePath);
@ -69,14 +72,16 @@ public class LegacyNavMeshQuery : NavMeshQuery {
Status status = Status.SUCCSESS;
while (!m_openList.isEmpty()) {
while (!m_openList.isEmpty())
{
// Remove node from open list and put it in closed list.
Node bestNode = m_openList.pop();
bestNode.flags &= ~Node.DT_NODE_OPEN;
bestNode.flags |= Node.DT_NODE_CLOSED;
// Reached the goal, stop searching.
if (bestNode.id == endRef) {
if (bestNode.id == endRef)
{
lastBestNode = bestNode;
break;
}
@ -92,20 +97,25 @@ public class LegacyNavMeshQuery : NavMeshQuery {
long parentRef = 0;
MeshTile parentTile = null;
Poly parentPoly = null;
if (bestNode.pidx != 0) {
if (bestNode.pidx != 0)
{
parentRef = m_nodePool.getNodeAtIdx(bestNode.pidx).id;
}
if (parentRef != 0) {
if (parentRef != 0)
{
tileAndPoly = m_nav.getTileAndPolyByRefUnsafe(parentRef);
parentTile = tileAndPoly.Item1;
parentPoly = tileAndPoly.Item2;
}
for (int i = bestTile.polyLinks[bestPoly.index]; i != NavMesh.DT_NULL_LINK; i = bestTile.links[i].next) {
for (int i = bestTile.polyLinks[bestPoly.index]; i != NavMesh.DT_NULL_LINK; i = bestTile.links[i].next)
{
long neighbourRef = bestTile.links[i].refs;
// Skip invalid ids and do not expand back to where we came from.
if (neighbourRef == 0 || neighbourRef == parentRef) {
if (neighbourRef == 0 || neighbourRef == parentRef)
{
continue;
}
@ -115,13 +125,15 @@ public class LegacyNavMeshQuery : NavMeshQuery {
MeshTile neighbourTile = tileAndPoly.Item1;
Poly neighbourPoly = tileAndPoly.Item2;
if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) {
if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly))
{
continue;
}
// deal explicitly with crossing tile boundaries
int crossSide = 0;
if (bestTile.links[i].side != 0xff) {
if (bestTile.links[i].side != 0xff)
{
crossSide = bestTile.links[i].side >> 1;
}
@ -129,10 +141,12 @@ public class LegacyNavMeshQuery : NavMeshQuery {
Node neighbourNode = m_nodePool.getNode(neighbourRef, crossSide);
// If the node is visited the first time, calculate node position.
if (neighbourNode.flags == 0) {
if (neighbourNode.flags == 0)
{
Result<float[]> midpod = getEdgeMidPoint(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly,
neighbourTile);
if (!midpod.failed()) {
if (!midpod.failed())
{
neighbourNode.pos = midpod.result;
}
}
@ -142,7 +156,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
float heuristic = 0;
// Special case for last node.
if (neighbourRef == endRef) {
if (neighbourRef == endRef)
{
// Cost
float curCost = filter.getCost(bestNode.pos, neighbourNode.pos, parentRef, parentTile, parentPoly,
bestRef, bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly);
@ -151,7 +166,9 @@ public class LegacyNavMeshQuery : NavMeshQuery {
cost = bestNode.cost + curCost + endCost;
heuristic = 0;
} else {
}
else
{
// Cost
float curCost = filter.getCost(bestNode.pos, neighbourNode.pos, parentRef, parentTile, parentPoly,
bestRef, bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly);
@ -162,11 +179,14 @@ public class LegacyNavMeshQuery : NavMeshQuery {
float total = cost + heuristic;
// The node is already in open list and the new result is worse, skip.
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0 && total >= neighbourNode.total) {
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0 && total >= neighbourNode.total)
{
continue;
}
// The node is already visited and process, and the new result is worse, skip.
if ((neighbourNode.flags & Node.DT_NODE_CLOSED) != 0 && total >= neighbourNode.total) {
if ((neighbourNode.flags & Node.DT_NODE_CLOSED) != 0 && total >= neighbourNode.total)
{
continue;
}
@ -177,17 +197,21 @@ public class LegacyNavMeshQuery : NavMeshQuery {
neighbourNode.cost = cost;
neighbourNode.total = total;
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0) {
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0)
{
// Already in open, update node location.
m_openList.modify(neighbourNode);
} else {
}
else
{
// Put the node in open list.
neighbourNode.flags |= Node.DT_NODE_OPEN;
m_openList.push(neighbourNode);
}
// Update nearest node to target so far.
if (heuristic < lastBestNodeCost) {
if (heuristic < lastBestNodeCost)
{
lastBestNodeCost = heuristic;
lastBestNode = neighbourNode;
}
@ -196,7 +220,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
List<long> path = getPathToNode(lastBestNode);
if (lastBestNode.id != endRef) {
if (lastBestNode.id != endRef)
{
status = Status.PARTIAL_RESULT;
}
@ -210,19 +235,23 @@ public class LegacyNavMeshQuery : NavMeshQuery {
* The maximum number of iterations to perform.
* @return The status flags for the query.
*/
public override Result<int> updateSlicedFindPath(int maxIter) {
if (!m_query.status.isInProgress()) {
public override Result<int> updateSlicedFindPath(int maxIter)
{
if (!m_query.status.isInProgress())
{
return Results.of(m_query.status, 0);
}
// Make sure the request is still valid.
if (!m_nav.isValidPolyRef(m_query.startRef) || !m_nav.isValidPolyRef(m_query.endRef)) {
if (!m_nav.isValidPolyRef(m_query.startRef) || !m_nav.isValidPolyRef(m_query.endRef))
{
m_query.status = Status.FAILURE;
return Results.of(m_query.status, 0);
}
int iter = 0;
while (iter < maxIter && !m_openList.isEmpty()) {
while (iter < maxIter && !m_openList.isEmpty())
{
iter++;
// Remove node from open list and put it in closed list.
@ -231,7 +260,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
bestNode.flags |= Node.DT_NODE_CLOSED;
// Reached the goal, stop searching.
if (bestNode.id == m_query.endRef) {
if (bestNode.id == m_query.endRef)
{
m_query.lastBestNode = bestNode;
m_query.status = Status.SUCCSESS;
return Results.of(m_query.status, iter);
@ -242,11 +272,13 @@ public class LegacyNavMeshQuery : NavMeshQuery {
// data.
long bestRef = bestNode.id;
Result<Tuple<MeshTile, Poly>> tileAndPoly = m_nav.getTileAndPolyByRef(bestRef);
if (tileAndPoly.failed()) {
if (tileAndPoly.failed())
{
m_query.status = Status.FAILURE;
// The polygon has disappeared during the sliced query, fail.
return Results.of(m_query.status, iter);
}
MeshTile bestTile = tileAndPoly.result.Item1;
Poly bestPoly = tileAndPoly.result.Item2;
// Get parent and grand parent poly and tile.
@ -254,41 +286,51 @@ public class LegacyNavMeshQuery : NavMeshQuery {
MeshTile parentTile = null;
Poly parentPoly = null;
Node parentNode = null;
if (bestNode.pidx != 0) {
if (bestNode.pidx != 0)
{
parentNode = m_nodePool.getNodeAtIdx(bestNode.pidx);
parentRef = parentNode.id;
if (parentNode.pidx != 0) {
if (parentNode.pidx != 0)
{
grandpaRef = m_nodePool.getNodeAtIdx(parentNode.pidx).id;
}
}
if (parentRef != 0) {
if (parentRef != 0)
{
bool invalidParent = false;
tileAndPoly = m_nav.getTileAndPolyByRef(parentRef);
invalidParent = tileAndPoly.failed();
if (invalidParent || (grandpaRef != 0 && !m_nav.isValidPolyRef(grandpaRef))) {
if (invalidParent || (grandpaRef != 0 && !m_nav.isValidPolyRef(grandpaRef)))
{
// The polygon has disappeared during the sliced query,
// fail.
m_query.status = Status.FAILURE;
return Results.of(m_query.status, iter);
}
parentTile = tileAndPoly.result.Item1;
parentPoly = tileAndPoly.result.Item2;
}
// decide whether to test raycast to previous nodes
bool tryLOS = false;
if ((m_query.options & DT_FINDPATH_ANY_ANGLE) != 0) {
if ((parentRef != 0) && (vDistSqr(parentNode.pos, bestNode.pos) < m_query.raycastLimitSqr)) {
if ((m_query.options & DT_FINDPATH_ANY_ANGLE) != 0)
{
if ((parentRef != 0) && (vDistSqr(parentNode.pos, bestNode.pos) < m_query.raycastLimitSqr))
{
tryLOS = true;
}
}
for (int i = bestTile.polyLinks[bestPoly.index]; i != NavMesh.DT_NULL_LINK; i = bestTile.links[i].next) {
for (int i = bestTile.polyLinks[bestPoly.index]; i != NavMesh.DT_NULL_LINK; i = bestTile.links[i].next)
{
long neighbourRef = bestTile.links[i].refs;
// Skip invalid ids and do not expand back to where we came
// from.
if (neighbourRef == 0 || neighbourRef == parentRef) {
if (neighbourRef == 0 || neighbourRef == parentRef)
{
continue;
}
@ -299,7 +341,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
MeshTile neighbourTile = tileAndPolyUns.Item1;
Poly neighbourPoly = tileAndPolyUns.Item2;
if (!m_query.filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) {
if (!m_query.filter.passFilter(neighbourRef, neighbourTile, neighbourPoly))
{
continue;
}
@ -308,16 +351,19 @@ public class LegacyNavMeshQuery : NavMeshQuery {
// do not expand to nodes that were already visited from the
// same parent
if (neighbourNode.pidx != 0 && neighbourNode.pidx == bestNode.pidx) {
if (neighbourNode.pidx != 0 && neighbourNode.pidx == bestNode.pidx)
{
continue;
}
// If the node is visited the first time, calculate node
// position.
if (neighbourNode.flags == 0) {
if (neighbourNode.flags == 0)
{
Result<float[]> midpod = getEdgeMidPoint(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly,
neighbourTile);
if (!midpod.failed()) {
if (!midpod.failed())
{
neighbourNode.pos = midpod.result;
}
}
@ -328,12 +374,15 @@ public class LegacyNavMeshQuery : NavMeshQuery {
// raycast parent
bool foundShortCut = false;
if (tryLOS) {
if (tryLOS)
{
Result<RaycastHit> rayHit = raycast(parentRef, parentNode.pos, neighbourNode.pos, m_query.filter,
DT_RAYCAST_USE_COSTS, grandpaRef);
if (rayHit.succeeded()) {
if (rayHit.succeeded())
{
foundShortCut = rayHit.result.t >= 1.0f;
if (foundShortCut) {
if (foundShortCut)
{
// shortcut found using raycast. Using shorter cost
// instead
cost = parentNode.cost + rayHit.result.pathCost;
@ -342,7 +391,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
}
// update move cost
if (!foundShortCut) {
if (!foundShortCut)
{
// No shortcut found.
float curCost = m_query.filter.getCost(bestNode.pos, neighbourNode.pos, parentRef, parentTile,
parentPoly, bestRef, bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly);
@ -350,13 +400,16 @@ public class LegacyNavMeshQuery : NavMeshQuery {
}
// Special case for last node.
if (neighbourRef == m_query.endRef) {
if (neighbourRef == m_query.endRef)
{
float endCost = m_query.filter.getCost(neighbourNode.pos, m_query.endPos, bestRef, bestTile,
bestPoly, neighbourRef, neighbourTile, neighbourPoly, 0, null, null);
cost = cost + endCost;
heuristic = 0;
} else {
}
else
{
heuristic = vDist(neighbourNode.pos, m_query.endPos) * H_SCALE;
}
@ -364,12 +417,15 @@ public class LegacyNavMeshQuery : NavMeshQuery {
// The node is already in open list and the new result is worse,
// skip.
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0 && total >= neighbourNode.total) {
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0 && total >= neighbourNode.total)
{
continue;
}
// The node is already visited and process, and the new result
// is worse, skip.
if ((neighbourNode.flags & Node.DT_NODE_CLOSED) != 0 && total >= neighbourNode.total) {
if ((neighbourNode.flags & Node.DT_NODE_CLOSED) != 0 && total >= neighbourNode.total)
{
continue;
}
@ -379,21 +435,26 @@ public class LegacyNavMeshQuery : NavMeshQuery {
neighbourNode.flags = (neighbourNode.flags & ~(Node.DT_NODE_CLOSED | Node.DT_NODE_PARENT_DETACHED));
neighbourNode.cost = cost;
neighbourNode.total = total;
if (foundShortCut) {
if (foundShortCut)
{
neighbourNode.flags = (neighbourNode.flags | Node.DT_NODE_PARENT_DETACHED);
}
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0) {
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0)
{
// Already in open, update node location.
m_openList.modify(neighbourNode);
} else {
}
else
{
// Put the node in open list.
neighbourNode.flags |= Node.DT_NODE_OPEN;
m_openList.push(neighbourNode);
}
// Update nearest node to target so far.
if (heuristic < m_query.lastBestNodeCost) {
if (heuristic < m_query.lastBestNodeCost)
{
m_query.lastBestNodeCost = heuristic;
m_query.lastBestNode = neighbourNode;
}
@ -401,7 +462,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
}
// Exhausted all nodes, but could not find path.
if (m_openList.isEmpty()) {
if (m_openList.isEmpty())
{
m_query.status = Status.PARTIAL_RESULT;
}
@ -412,28 +474,34 @@ public class LegacyNavMeshQuery : NavMeshQuery {
/// @param[out] path An ordered list of polygon references representing the path. (Start to end.)
/// [(polyRef) * @p pathCount]
/// @returns The status flags for the query.
public override Result<List<long>> finalizeSlicedFindPath() {
public override Result<List<long>> finalizeSlicedFindPath()
{
List<long> path = new List<long>(64);
if (m_query.status.isFailed()) {
if (m_query.status.isFailed())
{
// Reset query.
m_query = new QueryData();
return Results.failure(path);
}
if (m_query.startRef == m_query.endRef) {
if (m_query.startRef == m_query.endRef)
{
// Special case: the search starts and ends at same poly.
path.Add(m_query.startRef);
} else {
}
else
{
// Reverse the path.
if (m_query.lastBestNode.id != m_query.endRef) {
if (m_query.lastBestNode.id != m_query.endRef)
{
m_query.status = Status.PARTIAL_RESULT;
}
Node prev = null;
Node node = m_query.lastBestNode;
int prevRay = 0;
do {
do
{
Node next = m_nodePool.getNodeAtIdx(node.pidx);
node.pidx = m_nodePool.getNodeIdx(prev);
prev = node;
@ -447,18 +515,25 @@ public class LegacyNavMeshQuery : NavMeshQuery {
// Store path
node = prev;
do {
do
{
Node next = m_nodePool.getNodeAtIdx(node.pidx);
if ((node.flags & Node.DT_NODE_PARENT_DETACHED) != 0) {
if ((node.flags & Node.DT_NODE_PARENT_DETACHED) != 0)
{
Result<RaycastHit> iresult = raycast(node.id, node.pos, next.pos, m_query.filter, 0, 0);
if (iresult.succeeded()) {
if (iresult.succeeded())
{
path.AddRange(iresult.result.path);
}
// raycast ends on poly boundary and the path might include the next poly boundary.
if (path[path.Count - 1] == next.id) {
if (path[path.Count - 1] == next.id)
{
path.RemoveAt(path.Count - 1); // remove to avoid duplicates
}
} else {
}
else
{
path.Add(node.id);
}
@ -480,39 +555,50 @@ public class LegacyNavMeshQuery : NavMeshQuery {
/// @param[out] path An ordered list of polygon references representing the path. (Start to end.)
/// [(polyRef) * @p pathCount]
/// @returns The status flags for the query.
public override Result<List<long>> finalizeSlicedFindPathPartial(List<long> existing) {
public override Result<List<long>> finalizeSlicedFindPathPartial(List<long> existing)
{
List<long> path = new List<long>(64);
if (null == existing || existing.Count <= 0) {
if (null == existing || existing.Count <= 0)
{
return Results.failure(path);
}
if (m_query.status.isFailed()) {
if (m_query.status.isFailed())
{
// Reset query.
m_query = new QueryData();
return Results.failure(path);
}
if (m_query.startRef == m_query.endRef) {
if (m_query.startRef == m_query.endRef)
{
// Special case: the search starts and ends at same poly.
path.Add(m_query.startRef);
} else {
}
else
{
// Find furthest existing node that was visited.
Node prev = null;
Node node = null;
for (int i = existing.Count - 1; i >= 0; --i) {
for (int i = existing.Count - 1; i >= 0; --i)
{
node = m_nodePool.findNode(existing[i]);
if (node != null) {
if (node != null)
{
break;
}
}
if (node == null) {
if (node == null)
{
m_query.status = Status.PARTIAL_RESULT;
node = m_query.lastBestNode;
}
// Reverse the path.
int prevRay = 0;
do {
do
{
Node next = m_nodePool.getNodeAtIdx(node.pidx);
node.pidx = m_nodePool.getNodeIdx(prev);
prev = node;
@ -526,24 +612,32 @@ public class LegacyNavMeshQuery : NavMeshQuery {
// Store path
node = prev;
do {
do
{
Node next = m_nodePool.getNodeAtIdx(node.pidx);
if ((node.flags & Node.DT_NODE_PARENT_DETACHED) != 0) {
if ((node.flags & Node.DT_NODE_PARENT_DETACHED) != 0)
{
Result<RaycastHit> iresult = raycast(node.id, node.pos, next.pos, m_query.filter, 0, 0);
if (iresult.succeeded()) {
if (iresult.succeeded())
{
path.AddRange(iresult.result.path);
}
// raycast ends on poly boundary and the path might include the next poly boundary.
if (path[path.Count - 1] == next.id) {
if (path[path.Count - 1] == next.id)
{
path.RemoveAt(path.Count - 1); // remove to avoid duplicates
}
} else {
}
else
{
path.Add(node.id);
}
node = next;
} while (node != null);
}
Status status = m_query.status;
// Reset query.
m_query = new QueryData();
@ -552,11 +646,12 @@ public class LegacyNavMeshQuery : NavMeshQuery {
}
public override Result<FindDistanceToWallResult> findDistanceToWall(long startRef, float[] centerPos, float maxRadius,
QueryFilter filter) {
QueryFilter filter)
{
// Validate input
if (!m_nav.isValidPolyRef(startRef) || null == centerPos || !vIsFinite(centerPos) || maxRadius < 0
|| !float.IsFinite(maxRadius) || null == filter) {
|| !float.IsFinite(maxRadius) || null == filter)
{
return Results.invalidParam<FindDistanceToWallResult>();
}
@ -576,7 +671,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
float[] hitPos = new float[3];
VectorPtr bestvj = null;
VectorPtr bestvi = null;
while (!m_openList.isEmpty()) {
while (!m_openList.isEmpty())
{
Node bestNode = m_openList.pop();
bestNode.flags &= ~Node.DT_NODE_OPEN;
bestNode.flags |= Node.DT_NODE_CLOSED;
@ -590,38 +686,51 @@ public class LegacyNavMeshQuery : NavMeshQuery {
// Get parent poly and tile.
long parentRef = 0;
if (bestNode.pidx != 0) {
if (bestNode.pidx != 0)
{
parentRef = m_nodePool.getNodeAtIdx(bestNode.pidx).id;
}
// Hit test walls.
for (int i = 0, j = bestPoly.vertCount - 1; i < bestPoly.vertCount; j = i++) {
for (int i = 0, j = bestPoly.vertCount - 1; i < bestPoly.vertCount; j = i++)
{
// Skip non-solid edges.
if ((bestPoly.neis[j] & NavMesh.DT_EXT_LINK) != 0) {
if ((bestPoly.neis[j] & NavMesh.DT_EXT_LINK) != 0)
{
// Tile border.
bool solid = true;
for (int k = bestTile.polyLinks[bestPoly.index]; k != NavMesh.DT_NULL_LINK; k = bestTile.links[k].next) {
for (int k = bestTile.polyLinks[bestPoly.index]; k != NavMesh.DT_NULL_LINK; k = bestTile.links[k].next)
{
Link link = bestTile.links[k];
if (link.edge == j) {
if (link.refs != 0) {
if (link.edge == j)
{
if (link.refs != 0)
{
Tuple<MeshTile, Poly> linkTileAndPoly = m_nav.getTileAndPolyByRefUnsafe(link.refs);
MeshTile neiTile = linkTileAndPoly.Item1;
Poly neiPoly = linkTileAndPoly.Item2;
if (filter.passFilter(link.refs, neiTile, neiPoly)) {
if (filter.passFilter(link.refs, neiTile, neiPoly))
{
solid = false;
}
}
break;
}
}
if (!solid) {
if (!solid)
{
continue;
}
} else if (bestPoly.neis[j] != 0) {
}
else if (bestPoly.neis[j] != 0)
{
// Internal edge
int idx = (bestPoly.neis[j] - 1);
long refs = m_nav.getPolyRefBase(bestTile) | idx;
if (filter.passFilter(refs, bestTile, bestTile.data.polys[idx])) {
if (filter.passFilter(refs, bestTile, bestTile.data.polys[idx]))
{
continue;
}
}
@ -634,7 +743,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
float tseg = distseg.Item2;
// Edge is too far, skip.
if (distSqr > radiusSqr) {
if (distSqr > radiusSqr)
{
continue;
}
@ -650,11 +760,13 @@ public class LegacyNavMeshQuery : NavMeshQuery {
bestvi = new VectorPtr(bestTile.data.verts, vi);
}
for (int i = bestTile.polyLinks[bestPoly.index]; i != NavMesh.DT_NULL_LINK; i = bestTile.links[i].next) {
for (int i = bestTile.polyLinks[bestPoly.index]; i != NavMesh.DT_NULL_LINK; i = bestTile.links[i].next)
{
Link link = bestTile.links[i];
long neighbourRef = link.refs;
// Skip invalid neighbours and do not follow back to parent.
if (neighbourRef == 0 || neighbourRef == parentRef) {
if (neighbourRef == 0 || neighbourRef == parentRef)
{
continue;
}
@ -664,7 +776,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
Poly neighbourPoly = neighbourTileAndPoly.Item2;
// Skip off-mesh connections.
if (neighbourPoly.getType() == Poly.DT_POLYTYPE_OFFMESH_CONNECTION) {
if (neighbourPoly.getType() == Poly.DT_POLYTYPE_OFFMESH_CONNECTION)
{
continue;
}
@ -674,25 +787,30 @@ public class LegacyNavMeshQuery : NavMeshQuery {
Tuple<float, float> distseg = distancePtSegSqr2D(centerPos, bestTile.data.verts, va, vb);
float distSqr = distseg.Item1;
// If the circle is not touching the next polygon, skip it.
if (distSqr > radiusSqr) {
if (distSqr > radiusSqr)
{
continue;
}
if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) {
if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly))
{
continue;
}
Node neighbourNode = m_nodePool.getNode(neighbourRef);
if ((neighbourNode.flags & Node.DT_NODE_CLOSED) != 0) {
if ((neighbourNode.flags & Node.DT_NODE_CLOSED) != 0)
{
continue;
}
// Cost
if (neighbourNode.flags == 0) {
if (neighbourNode.flags == 0)
{
Result<float[]> midPoint = getEdgeMidPoint(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly,
neighbourTile);
if (midPoint.succeeded()) {
if (midPoint.succeeded())
{
neighbourNode.pos = midPoint.result;
}
}
@ -700,7 +818,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
float total = bestNode.total + vDist(bestNode.pos, neighbourNode.pos);
// The node is already in open list and the new result is worse, skip.
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0 && total >= neighbourNode.total) {
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0 && total >= neighbourNode.total)
{
continue;
}
@ -709,9 +828,12 @@ public class LegacyNavMeshQuery : NavMeshQuery {
neighbourNode.pidx = m_nodePool.getNodeIdx(bestNode);
neighbourNode.total = total;
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0) {
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0)
{
m_openList.modify(neighbourNode);
} else {
}
else
{
neighbourNode.flags |= Node.DT_NODE_OPEN;
m_openList.push(neighbourNode);
}
@ -720,15 +842,16 @@ public class LegacyNavMeshQuery : NavMeshQuery {
// Calc hit normal.
float[] hitNormal = new float[3];
if (bestvi != null && bestvj != null) {
if (bestvi != null && bestvj != null)
{
float[] tangent = vSub(bestvi, bestvj);
hitNormal[0] = tangent[2];
hitNormal[1] = 0;
hitNormal[2] = -tangent[0];
vNormalize(hitNormal);
}
return Results.success(new FindDistanceToWallResult((float)Math.Sqrt(radiusSqr), hitPos, hitNormal));
}
}
}

View File

@ -17,30 +17,33 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
/**
* Defines a link between polygons.
*
* @note This structure is rarely if ever used by the end user.
* @see MeshTile
*/
public class Link {
public class Link
{
/** Neighbour reference. (The neighbor that is linked to.) */
public long refs;
/** Index of the next link. */
public int next;
/** Index of the polygon edge that owns this link. */
public int edge;
/** If a boundary link, defines on which side the link is. */
public int side;
/** If a boundary link, defines the minimum sub-edge area. */
public int bmin;
/** If a boundary link, defines the maximum sub-edge area. */
public int bmax;
}
}

View File

@ -17,33 +17,38 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
public class MeshData {
public class MeshData
{
/** The tile header. */
public MeshHeader header;
/** The tile vertices. [Size: MeshHeader::vertCount] */
public float[] verts;
/** The tile polygons. [Size: MeshHeader::polyCount] */
public Poly[] polys;
/** The tile's detail sub-meshes. [Size: MeshHeader::detailMeshCount] */
public PolyDetail[] detailMeshes;
/** The detail mesh's unique vertices. [(x, y, z) * MeshHeader::detailVertCount] */
public float[] detailVerts;
/**
* The detail mesh's triangles. [(vertA, vertB, vertC) * MeshHeader::detailTriCount] See DetailTriEdgeFlags and
* NavMesh::getDetailTriEdgeFlags.
*/
public int[] detailTris;
/**
* The tile bounding volume nodes. [Size: MeshHeader::bvNodeCount] (Will be null if bounding volumes are disabled.)
*/
public BVNode[] bvTree;
/** The tile off-mesh connections. [Size: MeshHeader::offMeshConCount] */
public OffMeshConnection[] offMeshCons;
}
}

View File

@ -17,67 +17,90 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
/** Provides high level information related to a dtMeshTile object. */
public class MeshHeader {
public class MeshHeader
{
/** A magic number used to detect compatibility of navigation tile data. */
public const int DT_NAVMESH_MAGIC = 'D' << 24 | 'N' << 16 | 'A' << 8 | 'V';
/** A version number used to detect compatibility of navigation tile data. */
public const int DT_NAVMESH_VERSION = 7;
public const int DT_NAVMESH_VERSION_RECAST4J_FIRST = 0x8807;
public const int DT_NAVMESH_VERSION_RECAST4J_NO_POLY_FIRSTLINK = 0x8808;
public const int DT_NAVMESH_VERSION_RECAST4J_32BIT_BVTREE = 0x8809;
public const int DT_NAVMESH_VERSION_RECAST4J_LAST = 0x8809;
/** A magic number used to detect the compatibility of navigation tile states. */
public const int DT_NAVMESH_STATE_MAGIC = 'D' << 24 | 'N' << 16 | 'M' << 8 | 'S';
/** A version number used to detect compatibility of navigation tile states. */
public const int DT_NAVMESH_STATE_VERSION = 1;
/** Tile magic number. (Used to identify the data format.) */
public int magic;
/** Tile data format version number. */
public int version;
/** The x-position of the tile within the dtNavMesh tile grid. (x, y, layer) */
public int x;
/** The y-position of the tile within the dtNavMesh tile grid. (x, y, layer) */
public int y;
/** The layer of the tile within the dtNavMesh tile grid. (x, y, layer) */
public int layer;
/** The user defined id of the tile. */
public int userId;
/** The number of polygons in the tile. */
public int polyCount;
/** The number of vertices in the tile. */
public int vertCount;
/** The number of allocated links. */
public int maxLinkCount;
/** The number of sub-meshes in the detail mesh. */
public int detailMeshCount;
/** The number of unique vertices in the detail mesh. (In addition to the polygon vertices.) */
public int detailVertCount;
/** The number of triangles in the detail mesh. */
public int detailTriCount;
/** The number of bounding volume nodes. (Zero if bounding volumes are disabled.) */
public int bvNodeCount;
/** The number of off-mesh connections. */
public int offMeshConCount;
/** The index of the first polygon which is an off-mesh connection. */
public int offMeshBase;
/** The height of the agents using the tile. */
public float walkableHeight;
/** The radius of the agents using the tile. */
public float walkableRadius;
/** The maximum climb height of the agents using the tile. */
public float walkableClimb;
/** The minimum bounds of the tile's AABB. [(x, y, z)] */
public readonly float[] bmin = new float[3];
/** The maximum bounds of the tile's AABB. [(x, y, z)] */
public readonly float[] bmax = new float[3];
/** The bounding volume quantization factor. */
public float bvQuantFactor;
}
}

View File

@ -22,29 +22,33 @@ using System.Collections.Generic;
namespace DotRecast.Detour
{
/**
* Defines a navigation mesh tile.
*/
public class MeshTile {
public class MeshTile
{
public readonly int index;
/** Counter describing modifications to the tile. */
public int salt;
/** The tile data. */
public MeshData data;
public int[] polyLinks;
/** The tile links. */
public readonly List<Link> links = new List<Link>();
/** Index to the next free link. */
public int linksFreeList = NavMesh.DT_NULL_LINK; // FIXME: Remove
/** Tile flags. (See: #dtTileFlags) */
public int flags;
public MeshTile(int index) {
public MeshTile(int index)
{
this.index = index;
}
}
}

View File

@ -22,29 +22,28 @@ using System.Collections.Generic;
namespace DotRecast.Detour
{
public class MoveAlongSurfaceResult {
public class MoveAlongSurfaceResult
{
/** The result position of the mover. [(x, y, z)] */
private readonly float[] resultPos;
/** The reference ids of the polygons visited during the move. */
private readonly List<long> visited;
public MoveAlongSurfaceResult(float[] resultPos, List<long> visited) {
public MoveAlongSurfaceResult(float[] resultPos, List<long> visited)
{
this.resultPos = resultPos;
this.visited = visited;
}
public float[] getResultPos() {
public float[] getResultPos()
{
return resultPos;
}
public List<long> getVisited() {
public List<long> getVisited()
{
return visited;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -23,45 +23,45 @@ using System.Collections.Generic;
namespace DotRecast.Detour
{
using static DetourCommon;
public class NavMeshBuilder {
public class NavMeshBuilder
{
const int MESH_NULL_IDX = 0xffff;
public class BVItem {
public class BVItem
{
public readonly int[] bmin = new int[3];
public readonly int[] bmax = new int[3];
public int i;
};
private class CompareItemX : IComparer<BVItem> {
public int Compare(BVItem a, BVItem b) {
private class CompareItemX : IComparer<BVItem>
{
public int Compare(BVItem a, BVItem b)
{
return a.bmin[0].CompareTo(b.bmin[0]);
}
}
private class CompareItemY : IComparer<BVItem> {
public int Compare(BVItem a, BVItem b) {
private class CompareItemY : IComparer<BVItem>
{
public int Compare(BVItem a, BVItem b)
{
return a.bmin[1].CompareTo(b.bmin[1]);
}
}
private class CompareItemZ : IComparer<BVItem> {
public int Compare(BVItem a, BVItem b) {
private class CompareItemZ : IComparer<BVItem>
{
public int Compare(BVItem a, BVItem b)
{
return a.bmin[2].CompareTo(b.bmin[2]);
}
}
private static int[][] calcExtends(BVItem[] items, int nitems, int imin, int imax) {
private static int[][] calcExtends(BVItem[] items, int nitems, int imin, int imax)
{
int[] bmin = new int[3];
int[] bmax = new int[3];
bmin[0] = items[imin].bmin[0];
@ -72,7 +72,8 @@ public class NavMeshBuilder {
bmax[1] = items[imin].bmax[1];
bmax[2] = items[imin].bmax[2];
for (int i = imin + 1; i < imax; ++i) {
for (int i = imin + 1; i < imax; ++i)
{
BVItem it = items[i];
if (it.bmin[0] < bmin[0])
bmin[0] = it.bmin[0];
@ -88,31 +89,39 @@ public class NavMeshBuilder {
if (it.bmax[2] > bmax[2])
bmax[2] = it.bmax[2];
}
return new int[][] { bmin, bmax };
}
private static int longestAxis(int x, int y, int z) {
private static int longestAxis(int x, int y, int z)
{
int axis = 0;
int maxVal = x;
if (y > maxVal) {
if (y > maxVal)
{
axis = 1;
maxVal = y;
}
if (z > maxVal) {
if (z > maxVal)
{
axis = 2;
maxVal = z;
}
return axis;
}
public static int subdivide(BVItem[] items, int nitems, int imin, int imax, int curNode, BVNode[] nodes) {
public static int subdivide(BVItem[] items, int nitems, int imin, int imax, int curNode, BVNode[] nodes)
{
int inum = imax - imin;
int icur = curNode;
BVNode node = new BVNode();
nodes[curNode++] = node;
if (inum == 1) {
if (inum == 1)
{
// Leaf
node.bmin[0] = items[imin].bmin[0];
node.bmin[1] = items[imin].bmin[1];
@ -123,7 +132,9 @@ public class NavMeshBuilder {
node.bmax[2] = items[imin].bmax[2];
node.i = items[imin].i;
} else {
}
else
{
// Split
int[][] minmax = calcExtends(items, nitems, imin, imax);
node.bmin = minmax[0];
@ -132,13 +143,18 @@ public class NavMeshBuilder {
int axis = longestAxis(node.bmax[0] - node.bmin[0], node.bmax[1] - node.bmin[1],
node.bmax[2] - node.bmin[2]);
if (axis == 0) {
if (axis == 0)
{
// Sort along x-axis
Array.Sort(items, imin, inum, new CompareItemX());
} else if (axis == 1) {
}
else if (axis == 1)
{
// Sort along y-axis
Array.Sort(items, imin, inum, new CompareItemY());
} else {
}
else
{
// Sort along z-axis
Array.Sort(items, imin, inum, new CompareItemZ());
}
@ -154,19 +170,23 @@ public class NavMeshBuilder {
// Negative index means escape.
node.i = -iescape;
}
return curNode;
}
private static int createBVTree(NavMeshDataCreateParams option, BVNode[] nodes) {
private static int createBVTree(NavMeshDataCreateParams option, BVNode[] nodes)
{
// Build tree
float quantFactor = 1 / option.cs;
BVItem[] items = new BVItem[option.polyCount];
for (int i = 0; i < option.polyCount; i++) {
for (int i = 0; i < option.polyCount; i++)
{
BVItem it = new BVItem();
items[i] = it;
it.i = i;
// Calc polygon bounds. Use detail meshes if available.
if (option.detailMeshes != null) {
if (option.detailMeshes != null)
{
int vb = option.detailMeshes[i * 4 + 0];
int ndv = option.detailMeshes[i * 4 + 1];
float[] bmin = new float[3];
@ -174,7 +194,8 @@ public class NavMeshBuilder {
int dv = vb * 3;
vCopy(bmin, option.detailVerts, dv);
vCopy(bmax, option.detailVerts, dv);
for (int j = 1; j < ndv; j++) {
for (int j = 1; j < ndv; j++)
{
vMin(bmin, option.detailVerts, dv + j * 3);
vMax(bmax, option.detailVerts, dv + j * 3);
}
@ -187,13 +208,16 @@ public class NavMeshBuilder {
it.bmax[0] = clamp((int)((bmax[0] - option.bmin[0]) * quantFactor), 0, int.MaxValue);
it.bmax[1] = clamp((int)((bmax[1] - option.bmin[1]) * quantFactor), 0, int.MaxValue);
it.bmax[2] = clamp((int)((bmax[2] - option.bmin[2]) * quantFactor), 0, int.MaxValue);
} else {
}
else
{
int p = i * option.nvp * 2;
it.bmin[0] = it.bmax[0] = option.verts[option.polys[p] * 3 + 0];
it.bmin[1] = it.bmax[1] = option.verts[option.polys[p] * 3 + 1];
it.bmin[2] = it.bmax[2] = option.verts[option.polys[p] * 3 + 2];
for (int j = 1; j < option.nvp; ++j) {
for (int j = 1; j < option.nvp; ++j)
{
if (option.polys[p + j] == MESH_NULL_IDX)
break;
int x = option.verts[option.polys[p + j] * 3 + 0];
@ -214,6 +238,7 @@ public class NavMeshBuilder {
if (z > it.bmax[2])
it.bmax[2] = z;
}
// Remap y
it.bmin[1] = (int)Math.Floor(it.bmin[1] * option.ch * quantFactor);
it.bmax[1] = (int)Math.Ceiling(it.bmax[1] * option.ch * quantFactor);
@ -228,15 +253,16 @@ public class NavMeshBuilder {
const int XM = 1 << 2;
const int ZM = 1 << 3;
public static int classifyOffMeshPoint(VectorPtr pt, float[] bmin, float[] bmax) {
public static int classifyOffMeshPoint(VectorPtr pt, float[] bmin, float[] bmax)
{
int outcode = 0;
outcode |= (pt.get(0) >= bmax[0]) ? XP : 0;
outcode |= (pt.get(2) >= bmax[2]) ? ZP : 0;
outcode |= (pt.get(0) < bmin[0]) ? XM : 0;
outcode |= (pt.get(2) < bmin[2]) ? ZM : 0;
switch (outcode) {
switch (outcode)
{
case XP:
return 0;
case XP | ZP:
@ -266,7 +292,8 @@ public class NavMeshBuilder {
*
* @return created tile data
*/
public static MeshData createNavMeshData(NavMeshDataCreateParams option) {
public static MeshData createNavMeshData(NavMeshDataCreateParams option)
{
if (option.vertCount >= 0xffff)
return null;
if (option.vertCount == 0 || option.verts == null)
@ -282,7 +309,8 @@ public class NavMeshBuilder {
int storedOffMeshConCount = 0;
int offMeshConLinkCount = 0;
if (option.offMeshConCount > 0) {
if (option.offMeshConCount > 0)
{
offMeshConClass = new int[option.offMeshConCount * 2];
// Find tight heigh bounds, used for culling out off-mesh start
@ -290,20 +318,26 @@ public class NavMeshBuilder {
float hmin = float.MaxValue;
float hmax = -float.MaxValue;
if (option.detailVerts != null && option.detailVertsCount != 0) {
for (int i = 0; i < option.detailVertsCount; ++i) {
if (option.detailVerts != null && option.detailVertsCount != 0)
{
for (int i = 0; i < option.detailVertsCount; ++i)
{
float h = option.detailVerts[i * 3 + 1];
hmin = Math.Min(hmin, h);
hmax = Math.Max(hmax, h);
}
} else {
for (int i = 0; i < option.vertCount; ++i) {
}
else
{
for (int i = 0; i < option.vertCount; ++i)
{
int iv = i * 3;
float h = option.bmin[1] + option.verts[iv + 1] * option.ch;
hmin = Math.Min(hmin, h);
hmax = Math.Max(hmax, h);
}
}
hmin -= option.walkableClimb;
hmax += option.walkableClimb;
float[] bmin = new float[3];
@ -313,7 +347,8 @@ public class NavMeshBuilder {
bmin[1] = hmin;
bmax[1] = hmax;
for (int i = 0; i < option.offMeshConCount; ++i) {
for (int i = 0; i < option.offMeshConCount; ++i)
{
VectorPtr p0 = new VectorPtr(option.offMeshConVerts, (i * 2 + 0) * 3);
VectorPtr p1 = new VectorPtr(option.offMeshConVerts, (i * 2 + 1) * 3);
@ -322,7 +357,8 @@ public class NavMeshBuilder {
// Zero out off-mesh start positions which are not even
// potentially touching the mesh.
if (offMeshConClass[i * 2 + 0] == 0xff) {
if (offMeshConClass[i * 2 + 0] == 0xff)
{
if (p0.get(1) < bmin[1] || p0.get(1) > bmax[1])
offMeshConClass[i * 2 + 0] = 0;
}
@ -346,14 +382,17 @@ public class NavMeshBuilder {
// Find portal edges which are at tile borders.
int edgeCount = 0;
int portalCount = 0;
for (int i = 0; i < option.polyCount; ++i) {
for (int i = 0; i < option.polyCount; ++i)
{
int p = i * 2 * nvp;
for (int j = 0; j < nvp; ++j) {
for (int j = 0; j < nvp; ++j)
{
if (option.polys[p + j] == MESH_NULL_IDX)
break;
edgeCount++;
if ((option.polys[p + nvp + j] & 0x8000) != 0) {
if ((option.polys[p + nvp + j] & 0x8000) != 0)
{
int dir = option.polys[p + nvp + j] & 0xf;
if (dir != 0xf)
portalCount++;
@ -366,34 +405,43 @@ public class NavMeshBuilder {
// Find unique detail vertices.
int uniqueDetailVertCount = 0;
int detailTriCount = 0;
if (option.detailMeshes != null) {
if (option.detailMeshes != null)
{
// Has detail mesh, count unique detail vertex count and use input
// detail tri count.
detailTriCount = option.detailTriCount;
for (int i = 0; i < option.polyCount; ++i) {
for (int i = 0; i < option.polyCount; ++i)
{
int p = i * nvp * 2;
int ndv = option.detailMeshes[i * 4 + 1];
int nv = 0;
for (int j = 0; j < nvp; ++j) {
for (int j = 0; j < nvp; ++j)
{
if (option.polys[p + j] == MESH_NULL_IDX)
break;
nv++;
}
ndv -= nv;
uniqueDetailVertCount += ndv;
}
} else {
}
else
{
// No input detail mesh, build detail mesh from nav polys.
uniqueDetailVertCount = 0; // No extra detail verts.
detailTriCount = 0;
for (int i = 0; i < option.polyCount; ++i) {
for (int i = 0; i < option.polyCount; ++i)
{
int p = i * nvp * 2;
int nv = 0;
for (int j = 0; j < nvp; ++j) {
for (int j = 0; j < nvp; ++j)
{
if (option.polys[p + j] == MESH_NULL_IDX)
break;
nv++;
}
detailTriCount += nv - 2;
}
}
@ -436,18 +484,22 @@ public class NavMeshBuilder {
// Store vertices
// Mesh vertices
for (int i = 0; i < option.vertCount; ++i) {
for (int i = 0; i < option.vertCount; ++i)
{
int iv = i * 3;
int v = i * 3;
navVerts[v] = option.bmin[0] + option.verts[iv] * option.cs;
navVerts[v + 1] = option.bmin[1] + option.verts[iv + 1] * option.ch;
navVerts[v + 2] = option.bmin[2] + option.verts[iv + 2] * option.cs;
}
// Off-mesh link vertices.
int n = 0;
for (int i = 0; i < option.offMeshConCount; ++i) {
for (int i = 0; i < option.offMeshConCount; ++i)
{
// Only store connections which start from this tile.
if (offMeshConClass[i * 2 + 0] == 0xff) {
if (offMeshConClass[i * 2 + 0] == 0xff)
{
int linkv = i * 2 * 3;
int v = (offMeshVertsBase + n * 2) * 3;
Array.Copy(option.offMeshConVerts, linkv, navVerts, v, 6);
@ -458,18 +510,21 @@ public class NavMeshBuilder {
// Store polygons
// Mesh polys
int src = 0;
for (int i = 0; i < option.polyCount; ++i) {
for (int i = 0; i < option.polyCount; ++i)
{
Poly p = new Poly(i, nvp);
navPolys[i] = p;
p.vertCount = 0;
p.flags = option.polyFlags[i];
p.setArea(option.polyAreas[i]);
p.setType(Poly.DT_POLYTYPE_GROUND);
for (int j = 0; j < nvp; ++j) {
for (int j = 0; j < nvp; ++j)
{
if (option.polys[src + j] == MESH_NULL_IDX)
break;
p.verts[j] = option.polys[src + j];
if ((option.polys[src + nvp + j] & 0x8000) != 0) {
if ((option.polys[src + nvp + j] & 0x8000) != 0)
{
// Border or portal edge.
int dir = option.polys[src + nvp + j] & 0xf;
if (dir == 0xf) // Border
@ -482,20 +537,26 @@ public class NavMeshBuilder {
p.neis[j] = NavMesh.DT_EXT_LINK | 0;
else if (dir == 3) // Portal z-
p.neis[j] = NavMesh.DT_EXT_LINK | 6;
} else {
}
else
{
// Normal connection
p.neis[j] = option.polys[src + nvp + j] + 1;
}
p.vertCount++;
}
src += nvp * 2;
}
// Off-mesh connection vertices.
n = 0;
for (int i = 0; i < option.offMeshConCount; ++i) {
for (int i = 0; i < option.offMeshConCount; ++i)
{
// Only store connections which start from this tile.
if (offMeshConClass[i * 2 + 0] == 0xff) {
if (offMeshConClass[i * 2 + 0] == 0xff)
{
Poly p = new Poly(offMeshPolyBase + n, nvp);
navPolys[offMeshPolyBase + n] = p;
p.vertCount = 2;
@ -513,9 +574,11 @@ public class NavMeshBuilder {
// mesh.
// We compress the mesh data by skipping them and using the navmesh
// coordinates.
if (option.detailMeshes != null) {
if (option.detailMeshes != null)
{
int vbase = 0;
for (int i = 0; i < option.polyCount; ++i) {
for (int i = 0; i < option.polyCount; ++i)
{
PolyDetail dtl = new PolyDetail();
navDMeshes[i] = dtl;
int vb = option.detailMeshes[i * 4 + 0];
@ -527,17 +590,22 @@ public class NavMeshBuilder {
dtl.triCount = option.detailMeshes[i * 4 + 3];
// Copy vertices except the first 'nv' verts which are equal to
// nav poly verts.
if (ndv - nv != 0) {
if (ndv - nv != 0)
{
Array.Copy(option.detailVerts, (vb + nv) * 3, navDVerts, vbase * 3, 3 * (ndv - nv));
vbase += ndv - nv;
}
}
// Store triangles.
Array.Copy(option.detailTris, 0, navDTris, 0, 4 * option.detailTriCount);
} else {
}
else
{
// Create dummy detail mesh by triangulating polys.
int tbase = 0;
for (int i = 0; i < option.polyCount; ++i) {
for (int i = 0; i < option.polyCount; ++i)
{
PolyDetail dtl = new PolyDetail();
navDMeshes[i] = dtl;
int nv = navPolys[i].vertCount;
@ -546,7 +614,8 @@ public class NavMeshBuilder {
dtl.triBase = tbase;
dtl.triCount = (nv - 2);
// Triangulate polygon (local indices).
for (int j = 2; j < nv; ++j) {
for (int j = 2; j < nv; ++j)
{
int t = tbase * 4;
navDTris[t + 0] = 0;
navDTris[t + 1] = (j - 1);
@ -564,16 +633,19 @@ public class NavMeshBuilder {
// Store and create BVtree.
// TODO: take detail mesh into account! use byte per bbox extent?
if (option.buildBvTree) {
if (option.buildBvTree)
{
// Do not set header.bvNodeCount set to make it work look exactly the same as in original Detour
header.bvNodeCount = createBVTree(option, navBvtree);
}
// Store Off-Mesh connections.
n = 0;
for (int i = 0; i < option.offMeshConCount; ++i) {
for (int i = 0; i < option.offMeshConCount; ++i)
{
// Only store connections which start from this tile.
if (offMeshConClass[i * 2 + 0] == 0xff) {
if (offMeshConClass[i * 2 + 0] == 0xff)
{
OffMeshConnection con = new OffMeshConnection();
offMeshCons[n] = con;
con.poly = (offMeshPolyBase + n);
@ -600,7 +672,5 @@ public class NavMeshBuilder {
nmd.offMeshCons = offMeshCons;
return nmd;
}
}
}

View File

@ -17,59 +17,83 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
/// Represents the source data used to build an navigation mesh tile.
public class NavMeshDataCreateParams {
public class NavMeshDataCreateParams
{
/// @name Polygon Mesh Attributes
/// Used to create the base navigation graph.
/// See #rcPolyMesh for details related to these attributes.
/// @{
public int[] verts;
public int[] verts; /// < The polygon mesh vertices. [(x, y, z) * #vertCount] [Unit: vx]
public int vertCount; /// < The number vertices in the polygon mesh. [Limit: >= 3]
public int[] polys; /// < The polygon data. [Size: #polyCount * 2 * #nvp]
public int[] polyFlags; /// < The user defined flags assigned to each polygon. [Size: #polyCount]
public int[] polyAreas; /// < The user defined area ids assigned to each polygon. [Size: #polyCount]
public int polyCount; /// < Number of polygons in the mesh. [Limit: >= 1]
public int nvp; /// < Number maximum number of vertices per polygon. [Limit: >= 3]
/// < The polygon mesh vertices. [(x, y, z) * #vertCount] [Unit: vx]
public int vertCount;
/// < The number vertices in the polygon mesh. [Limit: >= 3]
public int[] polys;
/// < The polygon data. [Size: #polyCount * 2 * #nvp]
public int[] polyFlags;
/// < The user defined flags assigned to each polygon. [Size: #polyCount]
public int[] polyAreas;
/// < The user defined area ids assigned to each polygon. [Size: #polyCount]
public int polyCount;
/// < Number of polygons in the mesh. [Limit: >= 1]
public int nvp;
/// < Number maximum number of vertices per polygon. [Limit: >= 3]
/// @}
/// @name Height Detail Attributes (Optional)
/// See #rcPolyMeshDetail for details related to these attributes.
/// @{
public int[] detailMeshes;
public int[] detailMeshes; /// < The height detail sub-mesh data. [Size: 4 * #polyCount]
public float[] detailVerts; /// < The detail mesh vertices. [Size: 3 * #detailVertsCount] [Unit: wu]
public int detailVertsCount; /// < The number of vertices in the detail mesh.
public int[] detailTris; /// < The detail mesh triangles. [Size: 4 * #detailTriCount]
public int detailTriCount; /// < The number of triangles in the detail mesh.
/// < The height detail sub-mesh data. [Size: 4 * #polyCount]
public float[] detailVerts;
/// < The detail mesh vertices. [Size: 3 * #detailVertsCount] [Unit: wu]
public int detailVertsCount;
/// < The number of vertices in the detail mesh.
public int[] detailTris;
/// < The detail mesh triangles. [Size: 4 * #detailTriCount]
public int detailTriCount;
/// < The number of triangles in the detail mesh.
/// @}
/// @name Off-Mesh Connections Attributes (Optional)
/// Used to define a custom point-to-point edge within the navigation graph, an
/// off-mesh connection is a user defined traversable connection made up to two vertices,
/// at least one of which resides within a navigation mesh polygon.
/// @{
/// Off-mesh connection vertices. [(ax, ay, az, bx, by, bz) * #offMeshConCount] [Unit: wu]
public float[] offMeshConVerts;
/// Off-mesh connection radii. [Size: #offMeshConCount] [Unit: wu]
public float[] offMeshConRad;
/// User defined flags assigned to the off-mesh connections. [Size: #offMeshConCount]
public int[] offMeshConFlags;
/// User defined area ids assigned to the off-mesh connections. [Size: #offMeshConCount]
public int[] offMeshConAreas;
/// The permitted travel direction of the off-mesh connections. [Size: #offMeshConCount]
///
/// 0 = Travel only from endpoint A to endpoint B.<br/>
/// #DT_OFFMESH_CON_BIDIR = Bidirectional travel.
public int[] offMeshConDir;
/// The user defined ids of the off-mesh connection. [Size: #offMeshConCount]
public int[] offMeshConUserID;
/// The number of off-mesh connections. [Limit: >= 0]
public int offMeshConCount;
@ -77,29 +101,46 @@ public class NavMeshDataCreateParams {
/// @name Tile Attributes
/// @note The tile grid/layer data can be left at zero if the destination is a single tile mesh.
/// @{
public int userId;
public int userId; /// < The user defined id of the tile.
public int tileX; /// < The tile's x-grid location within the multi-tile destination mesh. (Along the x-axis.)
public int tileZ; /// < The tile's y-grid location within the multi-tile desitation mesh. (Along the z-axis.)
public int tileLayer; /// < The tile's layer within the layered destination mesh. [Limit: >= 0] (Along the y-axis.)
public float[] bmin; /// < The minimum bounds of the tile. [(x, y, z)] [Unit: wu]
public float[] bmax; /// < The maximum bounds of the tile. [(x, y, z)] [Unit: wu]
/// < The user defined id of the tile.
public int tileX;
/// < The tile's x-grid location within the multi-tile destination mesh. (Along the x-axis.)
public int tileZ;
/// < The tile's y-grid location within the multi-tile desitation mesh. (Along the z-axis.)
public int tileLayer;
/// < The tile's layer within the layered destination mesh. [Limit: >= 0] (Along the y-axis.)
public float[] bmin;
/// < The minimum bounds of the tile. [(x, y, z)] [Unit: wu]
public float[] bmax;
/// < The maximum bounds of the tile. [(x, y, z)] [Unit: wu]
/// @}
/// @name General Configuration Attributes
/// @{
public float walkableHeight;
public float walkableHeight; /// < The agent height. [Unit: wu]
public float walkableRadius; /// < The agent radius. [Unit: wu]
public float walkableClimb; /// < The agent maximum traversable ledge. (Up/Down) [Unit: wu]
public float cs; /// < The xz-plane cell size of the polygon mesh. [Limit: > 0] [Unit: wu]
public float ch; /// < The y-axis cell height of the polygon mesh. [Limit: > 0] [Unit: wu]
/// < The agent height. [Unit: wu]
public float walkableRadius;
/// < The agent radius. [Unit: wu]
public float walkableClimb;
/// < The agent maximum traversable ledge. (Up/Down) [Unit: wu]
public float cs;
/// < The xz-plane cell size of the polygon mesh. [Limit: > 0] [Unit: wu]
public float ch;
/// < The y-axis cell height of the polygon mesh. [Limit: > 0] [Unit: wu]
/// True if a bounding volume tree should be built for the tile.
/// @note The BVTree is not normally needed for layered navigation meshes.
public bool buildBvTree;
/// @}
}
}

View File

@ -17,27 +17,30 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
/**
* Configuration parameters used to define multi-tile navigation meshes. The values are used to allocate space during
* the initialization of a navigation mesh.
*
* @see NavMesh
*/
public class NavMeshParams {
public class NavMeshParams
{
/** The world space origin of the navigation mesh's tile space. [(x, y, z)] */
public readonly float[] orig = new float[3];
/** The width of each tile. (Along the x-axis.) */
public float tileWidth;
/** The height of each tile. (Along the z-axis.) */
public float tileHeight;
/** The maximum number of tiles the navigation mesh can contain. */
public int maxTiles;
/** The maximum number of polygons each tile can contain. */
public int maxPolys;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -22,13 +22,11 @@ using System.Collections.Generic;
namespace DotRecast.Detour
{
public class Node {
public class Node
{
public const int DT_NODE_OPEN = 0x01;
public const int DT_NODE_CLOSED = 0x02;
/** parent of the node is not adjacent. Found using raycast. */
public const int DT_NODE_PARENT_DETACHED = 0x04;
@ -36,31 +34,38 @@ public class Node {
/** Position of the node. */
public float[] pos = new float[3];
/** Cost of reaching the given node. */
public float cost;
/** Total cost of reaching the goal via the given node including heuristics. */
public float total;
/** Index to parent node. */
public int pidx;
/**
* extra state information. A polyRef can have multiple nodes with different extra info. see DT_MAX_STATES_PER_NODE
*/
public int state;
/** Node flags. A combination of dtNodeFlags. */
public int flags;
/** Polygon ref the node corresponds to. */
public long id;
/** Shortcut found by raycast. */
public List<long> shortcut;
public Node(int index) {
public Node(int index)
{
this.index = index;
}
public override string ToString() {
public override string ToString()
{
return "Node [id=" + id + "]";
}
}
}

View File

@ -22,82 +22,99 @@ using System.Collections.Generic;
namespace DotRecast.Detour
{
public class NodePool
{
private readonly Dictionary<long, List<Node>> m_map = new Dictionary<long, List<Node>>();
private readonly List<Node> m_nodes = new List<Node>();
public NodePool() {
public NodePool()
{
}
public void clear() {
public void clear()
{
m_nodes.Clear();
m_map.Clear();
}
public List<Node> findNodes(long id) {
var hasNode = m_map.TryGetValue(id, out var nodes);;
if (nodes == null) {
public List<Node> findNodes(long id)
{
var hasNode = m_map.TryGetValue(id, out var nodes);
;
if (nodes == null)
{
nodes = new List<Node>();
}
return nodes;
}
public Node findNode(long id) {
var hasNode = m_map.TryGetValue(id, out var nodes);;
if (nodes != null && 0 != nodes.Count) {
public Node findNode(long id)
{
var hasNode = m_map.TryGetValue(id, out var nodes);
;
if (nodes != null && 0 != nodes.Count)
{
return nodes[0];
}
return null;
}
public Node getNode(long id, int state) {
var hasNode = m_map.TryGetValue(id, out var nodes);;
if (nodes != null) {
foreach (Node node in nodes) {
if (node.state == state) {
public Node getNode(long id, int state)
{
var hasNode = m_map.TryGetValue(id, out var nodes);
;
if (nodes != null)
{
foreach (Node node in nodes)
{
if (node.state == state)
{
return node;
}
}
}
return create(id, state);
}
protected Node create(long id, int state) {
protected Node create(long id, int state)
{
Node node = new Node(m_nodes.Count + 1);
node.id = id;
node.state = state;
m_nodes.Add(node);
var hasNode = m_map.TryGetValue(id, out var nodes);;
if (nodes == null) {
var hasNode = m_map.TryGetValue(id, out var nodes);
;
if (nodes == null)
{
nodes = new List<Node>();
m_map.Add(id, nodes);
}
nodes.Add(node);
return node;
}
public int getNodeIdx(Node node) {
public int getNodeIdx(Node node)
{
return node != null ? node.index : 0;
}
public Node getNodeAtIdx(int idx) {
public Node getNodeAtIdx(int idx)
{
return idx != 0 ? m_nodes[idx - 1] : null;
}
public Node getNode(long refs) {
public Node getNode(long refs)
{
return getNode(refs, 0);
}
public Dictionary<long, List<Node>> getNodeMap() {
public Dictionary<long, List<Node>> getNodeMap()
{
return m_map;
}
}
}

View File

@ -17,14 +17,13 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
using System.Collections.Generic;
public class NodeQueue {
public class NodeQueue
{
private readonly List<Node> m_heap = new List<Node>();
public int count()
@ -32,7 +31,8 @@ public class NodeQueue {
return m_heap.Count;
}
public void clear() {
public void clear()
{
m_heap.Clear();
}
@ -48,12 +48,14 @@ public class NodeQueue {
return node;
}
public void push(Node node) {
public void push(Node node)
{
m_heap.Add(node);
m_heap.Sort((x, y) => x.total.CompareTo(y.total));
}
public void modify(Node node) {
public void modify(Node node)
{
m_heap.Remove(node);
push(node);
}
@ -63,5 +65,4 @@ public class NodeQueue {
return 0 == m_heap.Count;
}
}
}

View File

@ -17,21 +17,24 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
/**
* Defines an navigation mesh off-mesh connection within a dtMeshTile object. An off-mesh connection is a user defined
* traversable connection made up to two vertices.
*/
public class OffMeshConnection {
public class OffMeshConnection
{
/** The endpoints of the connection. [(ax, ay, az, bx, by, bz)] */
public float[] pos = new float[6];
/** The radius of the endpoints. [Limit: >= 0] */
public float rad;
/** The polygon reference of the connection within the tile. */
public int poly;
/**
* Link flags.
*
@ -39,8 +42,10 @@ public class OffMeshConnection {
* These are link flags used for internal purposes.
*/
public int flags;
/** End point side. */
public int side;
/** The id of the offmesh connection. (User assigned when the navigation mesh is built.) */
public int userId;
}

View File

@ -17,26 +17,32 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
/** Defines a polygon within a MeshTile object. */
public class Poly {
public class Poly
{
public readonly int index;
/** The polygon is a standard convex polygon that is part of the surface of the mesh. */
public const int DT_POLYTYPE_GROUND = 0;
/** The polygon is an off-mesh connection consisting of two vertices. */
public const int DT_POLYTYPE_OFFMESH_CONNECTION = 1;
/** The indices of the polygon's vertices. The actual vertices are located in MeshTile::verts. */
public readonly int[] verts;
/** Packed data representing neighbor polygons references and flags for each edge. */
public readonly int[] neis;
/** The user defined polygon flags. */
public int flags;
/** The number of vertices in the polygon. */
public int vertCount;
/**
* The bit packed area id and polygon type.
*
@ -44,32 +50,35 @@ public class Poly {
*/
public int areaAndtype;
public Poly(int index, int maxVertsPerPoly) {
public Poly(int index, int maxVertsPerPoly)
{
this.index = index;
verts = new int[maxVertsPerPoly];
neis = new int[maxVertsPerPoly];
}
/** Sets the user defined area id. [Limit: &lt; {@link org.recast4j.detour.NavMesh#DT_MAX_AREAS}] */
public void setArea(int a) {
public void setArea(int a)
{
areaAndtype = (areaAndtype & 0xc0) | (a & 0x3f);
}
/** Sets the polygon type. (See: #dtPolyTypes.) */
public void setType(int t) {
public void setType(int t)
{
areaAndtype = (areaAndtype & 0x3f) | (t << 6);
}
/** Gets the user defined area id. */
public int getArea() {
public int getArea()
{
return areaAndtype & 0x3f;
}
/** Gets the polygon type. (See: #dtPolyTypes) */
public int getType() {
public int getType()
{
return areaAndtype >> 6;
}
}
}

View File

@ -17,20 +17,22 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
/** Defines the location of detail sub-mesh data within a dtMeshTile. */
public class PolyDetail {
public class PolyDetail
{
/** The offset of the vertices in the MeshTile::detailVerts array. */
public int vertBase;
/** The offset of the triangles in the MeshTile::detailTris array. */
public int triBase;
/** The number of vertices in the sub-mesh. */
public int vertCount;
/** The number of triangles in the sub-mesh. */
public int triCount;
}
}

View File

@ -1,10 +1,7 @@
namespace DotRecast.Detour
{
public interface PolyQuery {
public interface PolyQuery
{
void process(MeshTile tile, Poly poly, long refs);
}
}

View File

@ -20,79 +20,92 @@ using System;
namespace DotRecast.Detour
{
using static DetourCommon;
public interface PolygonByCircleConstraint {
public interface PolygonByCircleConstraint
{
float[] aply(float[] polyVerts, float[] circleCenter, float radius);
public static PolygonByCircleConstraint noop() {
public static PolygonByCircleConstraint noop()
{
return new NoOpPolygonByCircleConstraint();
}
public static PolygonByCircleConstraint strict() {
public static PolygonByCircleConstraint strict()
{
return new StrictPolygonByCircleConstraint();
}
public class NoOpPolygonByCircleConstraint : PolygonByCircleConstraint {
public float[] aply(float[] polyVerts, float[] circleCenter, float radius) {
public class NoOpPolygonByCircleConstraint : PolygonByCircleConstraint
{
public float[] aply(float[] polyVerts, float[] circleCenter, float radius)
{
return polyVerts;
}
}
/**
* Calculate the intersection between a polygon and a circle. A dodecagon is used as an approximation of the circle.
*/
public class StrictPolygonByCircleConstraint : PolygonByCircleConstraint {
public class StrictPolygonByCircleConstraint : PolygonByCircleConstraint
{
private const int CIRCLE_SEGMENTS = 12;
private static float[] unitCircle;
public float[] aply(float[] verts, float[] center, float radius) {
public float[] aply(float[] verts, float[] center, float radius)
{
float radiusSqr = radius * radius;
int outsideVertex = -1;
for (int pv = 0; pv < verts.Length; pv += 3) {
if (vDist2DSqr(center, verts, pv) > radiusSqr) {
for (int pv = 0; pv < verts.Length; pv += 3)
{
if (vDist2DSqr(center, verts, pv) > radiusSqr)
{
outsideVertex = pv;
break;
}
}
if (outsideVertex == -1) {
if (outsideVertex == -1)
{
// polygon inside circle
return verts;
}
float[] qCircle = circle(center, radius);
float[] intersection = ConvexConvexIntersection.intersect(verts, qCircle);
if (intersection == null && pointInPolygon(center, verts, verts.Length / 3)) {
if (intersection == null && pointInPolygon(center, verts, verts.Length / 3))
{
// circle inside polygon
return qCircle;
}
return intersection;
}
private float[] circle(float[] center, float radius) {
if (unitCircle == null) {
private float[] circle(float[] center, float radius)
{
if (unitCircle == null)
{
unitCircle = new float[CIRCLE_SEGMENTS * 3];
for (int i = 0; i < CIRCLE_SEGMENTS; i++) {
for (int i = 0; i < CIRCLE_SEGMENTS; i++)
{
double a = i * Math.PI * 2 / CIRCLE_SEGMENTS;
unitCircle[3 * i] = (float)Math.Cos(a);
unitCircle[3 * i + 1] = 0;
unitCircle[3 * i + 2] = (float)-Math.Sin(a);
}
}
float[] circle = new float[12 * 3];
for (int i = 0; i < CIRCLE_SEGMENTS * 3; i += 3) {
for (int i = 0; i < CIRCLE_SEGMENTS * 3; i += 3)
{
circle[i] = unitCircle[i] * radius + center[0];
circle[i + 1] = center[1];
circle[i + 2] = unitCircle[i + 2] * radius + center[2];
}
return circle;
}
}
}
}

View File

@ -17,11 +17,11 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
public class QueryData {
public class QueryData
{
public Status status;
public Node lastBestNode;
public float lastBestNodeCost;

View File

@ -17,17 +17,14 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
public interface QueryFilter {
public interface QueryFilter
{
bool passFilter(long refs, MeshTile tile, Poly poly);
float getCost(float[] pa, float[] pb, long prevRef, MeshTile prevTile, Poly prevPoly, long curRef, MeshTile curTile,
Poly curPoly, long nextRef, MeshTile nextTile, Poly nextPoly);
}
}

View File

@ -18,12 +18,8 @@ freely, subject to the following restrictions:
namespace DotRecast.Detour
{
public interface QueryHeuristic {
public interface QueryHeuristic
{
float getCost(float[] neighbourPos, float[] endPos);
}
}

View File

@ -22,23 +22,24 @@ using System.Collections.Generic;
namespace DotRecast.Detour
{
/**
* Provides information about raycast hit. Filled by NavMeshQuery::raycast
*/
public class RaycastHit {
public class RaycastHit
{
/** The hit parameter. (float.MaxValue if no wall hit.) */
public float t;
/** hitNormal The normal of the nearest wall hit. [(x, y, z)] */
public readonly float[] hitNormal = new float[3];
/** Visited polygons. */
public readonly List<long> path = new List<long>();
/** The cost of the path until hit. */
public float pathCost;
/** The index of the edge on the readonly polygon where the wall was hit. */
public int hitEdgeIndex;
}
}

View File

@ -17,71 +17,79 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
public static class Results
{
public static Result<T> success<T>(T result) {
public static Result<T> success<T>(T result)
{
return new Result<T>(result, Status.SUCCSESS, null);
}
public static Result<T> failure<T>() {
public static Result<T> failure<T>()
{
return new Result<T>(default, Status.FAILURE, null);
}
public static Result<T> invalidParam<T>() {
public static Result<T> invalidParam<T>()
{
return new Result<T>(default, Status.FAILURE_INVALID_PARAM, null);
}
public static Result<T> failure<T>(string message) {
public static Result<T> failure<T>(string message)
{
return new Result<T>(default, Status.FAILURE, message);
}
public static Result<T> invalidParam<T>(string message) {
public static Result<T> invalidParam<T>(string message)
{
return new Result<T>(default, Status.FAILURE_INVALID_PARAM, message);
}
public static Result<T> failure<T>(T result) {
public static Result<T> failure<T>(T result)
{
return new Result<T>(result, Status.FAILURE, null);
}
public static Result<T> partial<T>(T result) {
public static Result<T> partial<T>(T result)
{
return new Result<T>(default, Status.PARTIAL_RESULT, null);
}
public static Result<T> of<T>(Status status, string message) {
public static Result<T> of<T>(Status status, string message)
{
return new Result<T>(default, status, message);
}
public static Result<T> of<T>(Status status, T result) {
public static Result<T> of<T>(Status status, T result)
{
return new Result<T>(result, status, null);
}
}
public class Result<T> {
public class Result<T>
{
public readonly T result;
public readonly Status status;
public readonly string message;
internal Result(T result, Status status, string message) {
internal Result(T result, Status status, string message)
{
this.result = result;
this.status = status;
this.message = message;
}
public bool failed() {
public bool failed()
{
return status.isFailed();
}
public bool succeeded() {
public bool succeeded()
{
return status.isSuccess();
}
}
}

View File

@ -17,11 +17,11 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
public class Status {
public class Status
{
public static Status FAILURE = new Status(0);
public static Status SUCCSESS = new Status(1);
public static Status IN_PROGRESS = new Status(2);
@ -38,22 +38,24 @@ public class Status {
public static class StatusEx
{
public static bool isFailed(this Status @this) {
public static bool isFailed(this Status @this)
{
return @this == Status.FAILURE || @this == Status.FAILURE_INVALID_PARAM;
}
public static bool isInProgress(this Status @this) {
public static bool isInProgress(this Status @this)
{
return @this == Status.IN_PROGRESS;
}
public static bool isSuccess(this Status @this) {
public static bool isSuccess(this Status @this)
{
return @this == Status.SUCCSESS || @this == Status.PARTIAL_RESULT;
}
public static bool isPartial(this Status @this) {
public static bool isPartial(this Status @this)
{
return @this == Status.PARTIAL_RESULT;
}
}
}

View File

@ -17,35 +17,38 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
using static DetourCommon;
//TODO: (PP) Add comments
public class StraightPathItem {
public class StraightPathItem
{
public float[] pos;
public int flags;
public long refs;
public StraightPathItem(float[] pos, int flags, long refs) {
public StraightPathItem(float[] pos, int flags, long refs)
{
this.pos = vCopy(pos);
this.flags = flags;
this.refs = refs;
}
public float[] getPos() {
public float[] getPos()
{
return pos;
}
public int getFlags() {
public int getFlags()
{
return flags;
}
public long getRef() {
public long getRef()
{
return refs;
}
}
}

View File

@ -20,8 +20,6 @@ freely, subject to the following restrictions:
namespace DotRecast.Detour
{
/**
* Wrapper for 3-element pieces (3D vectors) of a bigger float array.
*

View File

@ -3,17 +3,20 @@ using DotRecast.Recast.Demo.Geom;
namespace DotRecast.Recast.Demo.Builder;
public abstract class AbstractNavMeshBuilder {
public abstract class AbstractNavMeshBuilder
{
protected NavMeshDataCreateParams getNavMeshCreateParams(DemoInputGeomProvider m_geom, float m_cellSize,
float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb,
RecastBuilderResult rcResult) {
RecastBuilderResult rcResult)
{
PolyMesh m_pmesh = rcResult.getMesh();
PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
for (int i = 0; i < m_pmesh.npolys; ++i) {
for (int i = 0; i < m_pmesh.npolys; ++i)
{
m_pmesh.flags[i] = 1;
}
option.verts = m_pmesh.verts;
option.vertCount = m_pmesh.nverts;
option.polys = m_pmesh.polys;
@ -21,13 +24,15 @@ public abstract class AbstractNavMeshBuilder {
option.polyFlags = m_pmesh.flags;
option.polyCount = m_pmesh.npolys;
option.nvp = m_pmesh.nvp;
if (m_dmesh != null) {
if (m_dmesh != null)
{
option.detailMeshes = m_dmesh.meshes;
option.detailVerts = m_dmesh.verts;
option.detailVertsCount = m_dmesh.nverts;
option.detailTris = m_dmesh.tris;
option.detailTriCount = m_dmesh.ntris;
}
option.walkableHeight = m_agentHeight;
option.walkableRadius = m_agentRadius;
option.walkableClimb = m_agentMaxClimb;
@ -44,35 +49,49 @@ public abstract class AbstractNavMeshBuilder {
option.offMeshConAreas = new int[option.offMeshConCount];
option.offMeshConFlags = new int[option.offMeshConCount];
option.offMeshConUserID = new int[option.offMeshConCount];
for (int i = 0; i < option.offMeshConCount; i++) {
for (int i = 0; i < option.offMeshConCount; i++)
{
DemoOffMeshConnection offMeshCon = m_geom.getOffMeshConnections()[i];
for (int j = 0; j < 6; j++) {
for (int j = 0; j < 6; j++)
{
option.offMeshConVerts[6 * i + j] = offMeshCon.verts[j];
}
option.offMeshConRad[i] = offMeshCon.radius;
option.offMeshConDir[i] = offMeshCon.bidir ? 1 : 0;
option.offMeshConAreas[i] = offMeshCon.area;
option.offMeshConFlags[i] = offMeshCon.flags;
}
return option;
}
protected MeshData updateAreaAndFlags(MeshData meshData) {
protected MeshData updateAreaAndFlags(MeshData meshData)
{
// Update poly flags from areas.
for (int i = 0; i < meshData.polys.Length; ++i) {
if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WALKABLE) {
for (int i = 0; i < meshData.polys.Length; ++i)
{
if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WALKABLE)
{
meshData.polys[i].setArea(SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND);
}
if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND
|| meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GRASS
|| meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD) {
|| meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD)
{
meshData.polys[i].flags = SampleAreaModifications.SAMPLE_POLYFLAGS_WALK;
} else if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER) {
}
else if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER)
{
meshData.polys[i].flags = SampleAreaModifications.SAMPLE_POLYFLAGS_SWIM;
} else if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_DOOR) {
}
else if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_DOOR)
{
meshData.polys[i].flags = SampleAreaModifications.SAMPLE_POLYFLAGS_DOOR;
}
}
return meshData;
}
}

View File

@ -17,10 +17,11 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Recast.Demo.Builder;
public class SampleAreaModifications {
public class SampleAreaModifications
{
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x0;
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x1;
public const int SAMPLE_POLYAREA_TYPE_ROAD = 0x2;

View File

@ -24,14 +24,14 @@ using DotRecast.Recast.Demo.Geom;
namespace DotRecast.Recast.Demo.Builder;
public class SoloNavMeshBuilder : AbstractNavMeshBuilder {
public class SoloNavMeshBuilder : AbstractNavMeshBuilder
{
public Tuple<IList<RecastBuilderResult>, NavMesh> build(DemoInputGeomProvider m_geom, PartitionType m_partitionType,
float m_cellSize, float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb,
float m_agentMaxSlope, int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError,
int m_vertsPerPoly, float m_detailSampleDist, float m_detailSampleMaxError, bool filterLowHangingObstacles,
bool filterLedgeSpans, bool filterWalkableLowHeightSpans) {
bool filterLedgeSpans, bool filterWalkableLowHeightSpans)
{
RecastBuilderResult rcResult = buildRecastResult(m_geom, m_partitionType, m_cellSize, m_cellHeight, m_agentHeight,
m_agentRadius, m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, filterLowHangingObstacles, filterLedgeSpans,
@ -42,7 +42,8 @@ public class SoloNavMeshBuilder : AbstractNavMeshBuilder {
m_vertsPerPoly));
}
private NavMesh buildNavMesh(MeshData meshData, int m_vertsPerPoly) {
private NavMesh buildNavMesh(MeshData meshData, int m_vertsPerPoly)
{
return new NavMesh(meshData, m_vertsPerPoly, 0);
}
@ -50,7 +51,8 @@ public class SoloNavMeshBuilder : AbstractNavMeshBuilder {
float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope,
int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly,
float m_detailSampleDist, float m_detailSampleMaxError, bool filterLowHangingObstacles, bool filterLedgeSpans,
bool filterWalkableLowHeightSpans) {
bool filterWalkableLowHeightSpans)
{
RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize, m_cellHeight, m_agentMaxSlope, filterLowHangingObstacles,
filterLedgeSpans, filterWalkableLowHeightSpans, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_regionMinSize,
m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError,
@ -61,10 +63,10 @@ public class SoloNavMeshBuilder : AbstractNavMeshBuilder {
}
private MeshData buildMeshData(DemoInputGeomProvider m_geom, float m_cellSize, float m_cellHeight, float m_agentHeight,
float m_agentRadius, float m_agentMaxClimb, RecastBuilderResult rcResult) {
float m_agentRadius, float m_agentMaxClimb, RecastBuilderResult rcResult)
{
NavMeshDataCreateParams option = getNavMeshCreateParams(m_geom, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius,
m_agentMaxClimb, rcResult);
return updateAreaAndFlags(NavMeshBuilder.createNavMeshData(option));
}
}

View File

@ -25,17 +25,18 @@ using DotRecast.Recast.Demo.Geom;
namespace DotRecast.Recast.Demo.Builder;
public class TileNavMeshBuilder : AbstractNavMeshBuilder {
public TileNavMeshBuilder() {
public class TileNavMeshBuilder : AbstractNavMeshBuilder
{
public TileNavMeshBuilder()
{
}
public Tuple<IList<RecastBuilderResult>, NavMesh> build(DemoInputGeomProvider m_geom, PartitionType m_partitionType,
float m_cellSize, float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb,
float m_agentMaxSlope, int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError,
int m_vertsPerPoly, float m_detailSampleDist, float m_detailSampleMaxError, bool filterLowHangingObstacles,
bool filterLedgeSpans, bool filterWalkableLowHeightSpans, int tileSize) {
bool filterLedgeSpans, bool filterWalkableLowHeightSpans, int tileSize)
{
List<RecastBuilderResult> rcResult = buildRecastResult(m_geom, m_partitionType, m_cellSize, m_cellHeight, m_agentHeight,
m_agentRadius, m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, filterLowHangingObstacles, filterLedgeSpans,
@ -50,7 +51,8 @@ public class TileNavMeshBuilder : AbstractNavMeshBuilder {
float m_cellSize, float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb,
float m_agentMaxSlope, int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError,
int m_vertsPerPoly, float m_detailSampleDist, float m_detailSampleMaxError, bool filterLowHangingObstacles,
bool filterLedgeSpans, bool filterWalkableLowHeightSpans, int tileSize) {
bool filterLedgeSpans, bool filterWalkableLowHeightSpans, int tileSize)
{
RecastConfig cfg = new RecastConfig(true, tileSize, tileSize, RecastConfig.calcBorder(m_agentRadius, m_cellSize),
m_partitionType, m_cellSize, m_cellHeight, m_agentMaxSlope, filterLowHangingObstacles, filterLedgeSpans,
filterWalkableLowHeightSpans, m_agentHeight, m_agentRadius, m_agentMaxClimb,
@ -62,7 +64,8 @@ public class TileNavMeshBuilder : AbstractNavMeshBuilder {
}
private NavMesh buildNavMesh(DemoInputGeomProvider geom, List<MeshData> meshData, float cellSize, int tileSize,
int vertsPerPoly) {
int vertsPerPoly)
{
NavMeshParams navMeshParams = new NavMeshParams();
navMeshParams.orig[0] = geom.getMeshBoundsMin()[0];
navMeshParams.orig[1] = geom.getMeshBoundsMin()[1];
@ -79,17 +82,20 @@ public class TileNavMeshBuilder : AbstractNavMeshBuilder {
return navMesh;
}
public int getMaxTiles(DemoInputGeomProvider geom, float cellSize, int tileSize) {
public int getMaxTiles(DemoInputGeomProvider geom, float cellSize, int tileSize)
{
int tileBits = getTileBits(geom, cellSize, tileSize);
return 1 << tileBits;
}
public int getMaxPolysPerTile(DemoInputGeomProvider geom, float cellSize, int tileSize) {
public int getMaxPolysPerTile(DemoInputGeomProvider geom, float cellSize, int tileSize)
{
int polyBits = 22 - getTileBits(geom, cellSize, tileSize);
return 1 << polyBits;
}
private int getTileBits(DemoInputGeomProvider geom, float cellSize, int tileSize) {
private int getTileBits(DemoInputGeomProvider geom, float cellSize, int tileSize)
{
int[] wh = Recast.calcGridSize(geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), cellSize);
int tw = (wh[0] + tileSize - 1) / tileSize;
int th = (wh[1] + tileSize - 1) / tileSize;
@ -97,7 +103,8 @@ public class TileNavMeshBuilder : AbstractNavMeshBuilder {
return tileBits;
}
public int[] getTiles(DemoInputGeomProvider geom, float cellSize, int tileSize) {
public int[] getTiles(DemoInputGeomProvider geom, float cellSize, int tileSize)
{
int[] wh = Recast.calcGridSize(geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), cellSize);
int tw = (wh[0] + tileSize - 1) / tileSize;
int th = (wh[1] + tileSize - 1) / tileSize;
@ -105,11 +112,12 @@ public class TileNavMeshBuilder : AbstractNavMeshBuilder {
}
private List<MeshData> buildMeshData(DemoInputGeomProvider m_geom, float m_cellSize, float m_cellHeight, float m_agentHeight,
float m_agentRadius, float m_agentMaxClimb, List<RecastBuilderResult> rcResult) {
float m_agentRadius, float m_agentMaxClimb, List<RecastBuilderResult> rcResult)
{
// Add tiles to nav mesh
List<MeshData> meshData = new();
foreach (RecastBuilderResult result in rcResult) {
foreach (RecastBuilderResult result in rcResult)
{
int x = result.tileX;
int z = result.tileZ;
NavMeshDataCreateParams option = getNavMeshCreateParams(m_geom, m_cellSize, m_cellHeight, m_agentHeight,
@ -117,11 +125,12 @@ public class TileNavMeshBuilder : AbstractNavMeshBuilder {
option.tileX = x;
option.tileZ = z;
MeshData md = NavMeshBuilder.createNavMeshData(option);
if (md != null) {
if (md != null)
{
meshData.Add(updateAreaAndFlags(md));
}
}
return meshData;
}
}

View File

@ -25,8 +25,8 @@ using Silk.NET.OpenGL;
namespace DotRecast.Recast.Demo.Draw;
public class DebugDraw {
public class DebugDraw
{
private readonly GLCheckerTexture g_tex = new GLCheckerTexture();
private readonly OpenGLDraw openGlDraw = new ModernOpenGLDraw();
private readonly int[] boxIndices = { 7, 6, 5, 4, 0, 1, 2, 3, 1, 5, 6, 2, 3, 7, 4, 0, 2, 6, 7, 3, 0, 4, 5, 1, };
@ -42,12 +42,14 @@ public class DebugDraw {
};
public void begin(DebugDrawPrimitives prim) {
public void begin(DebugDrawPrimitives prim)
{
begin(prim, 1f);
}
public void debugDrawCylinderWire(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col,
float lineWidth) {
float lineWidth)
{
begin(DebugDrawPrimitives.LINES, lineWidth);
appendCylinderWire(minx, miny, minz, maxx, maxy, maxz, col);
end();
@ -57,10 +59,13 @@ public class DebugDraw {
private readonly float[] cylinderDir = new float[CYLINDER_NUM_SEG * 2];
private bool cylinderInit = false;
private void initCylinder() {
if (!cylinderInit) {
private void initCylinder()
{
if (!cylinderInit)
{
cylinderInit = true;
for (int i = 0; i < CYLINDER_NUM_SEG; ++i) {
for (int i = 0; i < CYLINDER_NUM_SEG; ++i)
{
float a = (float)(i * Math.PI * 2 / CYLINDER_NUM_SEG);
cylinderDir[i * 2] = (float)Math.Cos(a);
cylinderDir[i * 2 + 1] = (float)Math.Sin(a);
@ -68,8 +73,8 @@ public class DebugDraw {
}
}
void appendCylinderWire(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col) {
void appendCylinderWire(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col)
{
initCylinder();
float cx = (maxx + minx) / 2;
@ -77,27 +82,31 @@ public class DebugDraw {
float rx = (maxx - minx) / 2;
float rz = (maxz - minz) / 2;
for (int i = 0, j = CYLINDER_NUM_SEG - 1; i < CYLINDER_NUM_SEG; j = i++) {
for (int i = 0, j = CYLINDER_NUM_SEG - 1; i < CYLINDER_NUM_SEG; j = i++)
{
vertex(cx + cylinderDir[j * 2 + 0] * rx, miny, cz + cylinderDir[j * 2 + 1] * rz, col);
vertex(cx + cylinderDir[i * 2 + 0] * rx, miny, cz + cylinderDir[i * 2 + 1] * rz, col);
vertex(cx + cylinderDir[j * 2 + 0] * rx, maxy, cz + cylinderDir[j * 2 + 1] * rz, col);
vertex(cx + cylinderDir[i * 2 + 0] * rx, maxy, cz + cylinderDir[i * 2 + 1] * rz, col);
}
for (int i = 0; i < CYLINDER_NUM_SEG; i += CYLINDER_NUM_SEG / 4) {
for (int i = 0; i < CYLINDER_NUM_SEG; i += CYLINDER_NUM_SEG / 4)
{
vertex(cx + cylinderDir[i * 2 + 0] * rx, miny, cz + cylinderDir[i * 2 + 1] * rz, col);
vertex(cx + cylinderDir[i * 2 + 0] * rx, maxy, cz + cylinderDir[i * 2 + 1] * rz, col);
}
}
public void debugDrawBoxWire(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col,
float lineWidth) {
float lineWidth)
{
begin(DebugDrawPrimitives.LINES, lineWidth);
appendBoxWire(minx, miny, minz, maxx, maxy, maxz, col);
end();
}
public void appendBoxWire(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col) {
public void appendBoxWire(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col)
{
// Top
vertex(minx, miny, minz, col);
vertex(maxx, miny, minz, col);
@ -129,8 +138,10 @@ public class DebugDraw {
vertex(minx, maxy, maxz, col);
}
public void appendBox(float minx, float miny, float minz, float maxx, float maxy, float maxz, int[] fcol) {
float[][] verts = {
public void appendBox(float minx, float miny, float minz, float maxx, float maxy, float maxz, int[] fcol)
{
float[][] verts =
{
new[] { minx, miny, minz },
new[] { maxx, miny, minz },
new[] { maxx, miny, maxz },
@ -138,10 +149,12 @@ public class DebugDraw {
new[] { minx, maxy, minz },
new[] { maxx, maxy, minz },
new[] { maxx, maxy, maxz },
new[] { minx, maxy, maxz } };
new[] { minx, maxy, maxz }
};
int idx = 0;
for (int i = 0; i < 6; ++i) {
for (int i = 0; i < 6; ++i)
{
vertex(verts[boxIndices[idx]], fcol[i]);
idx++;
vertex(verts[boxIndices[idx]], fcol[i]);
@ -154,37 +167,45 @@ public class DebugDraw {
}
public void debugDrawArc(float x0, float y0, float z0, float x1, float y1, float z1, float h, float as0, float as1, int col,
float lineWidth) {
float lineWidth)
{
begin(DebugDrawPrimitives.LINES, lineWidth);
appendArc(x0, y0, z0, x1, y1, z1, h, as0, as1, col);
end();
}
public void begin(DebugDrawPrimitives prim, float size) {
public void begin(DebugDrawPrimitives prim, float size)
{
getOpenGlDraw().begin(prim, size);
}
public void vertex(float[] pos, int color) {
public void vertex(float[] pos, int color)
{
getOpenGlDraw().vertex(pos, color);
}
public void vertex(float x, float y, float z, int color) {
public void vertex(float x, float y, float z, int color)
{
getOpenGlDraw().vertex(x, y, z, color);
}
public void vertex(float[] pos, int color, float[] uv) {
public void vertex(float[] pos, int color, float[] uv)
{
getOpenGlDraw().vertex(pos, color, uv);
}
public void vertex(float x, float y, float z, int color, float u, float v) {
public void vertex(float x, float y, float z, int color, float u, float v)
{
getOpenGlDraw().vertex(x, y, z, color, u, v);
}
public void end() {
public void end()
{
getOpenGlDraw().end();
}
public void debugDrawCircle(float x, float y, float z, float r, int col, float lineWidth) {
public void debugDrawCircle(float x, float y, float z, float r, int col, float lineWidth)
{
begin(DebugDrawPrimitives.LINES, lineWidth);
appendCircle(x, y, z, r, col);
end();
@ -196,16 +217,21 @@ public class DebugDraw {
private float[] _viewMatrix = new float[16];
private readonly float[] _projectionMatrix = new float[16];
public void appendCircle(float x, float y, float z, float r, int col) {
if (!circleInit) {
public void appendCircle(float x, float y, float z, float r, int col)
{
if (!circleInit)
{
circleInit = true;
for (int i = 0; i < CIRCLE_NUM_SEG; ++i) {
for (int i = 0; i < CIRCLE_NUM_SEG; ++i)
{
float a = (float)(i * Math.PI * 2 / CIRCLE_NUM_SEG);
circeDir[i * 2] = (float)Math.Cos(a);
circeDir[i * 2 + 1] = (float)Math.Sin(a);
}
}
for (int i = 0, j = CIRCLE_NUM_SEG - 1; i < CIRCLE_NUM_SEG; j = i++) {
for (int i = 0, j = CIRCLE_NUM_SEG - 1; i < CIRCLE_NUM_SEG; j = i++)
{
vertex(x + circeDir[j * 2 + 0] * r, y, z + circeDir[j * 2 + 1] * r, col);
vertex(x + circeDir[i * 2 + 0] * r, y, z + circeDir[i * 2 + 1] * r, col);
}
@ -215,14 +241,16 @@ public class DebugDraw {
private static readonly float PAD = 0.05f;
private static readonly float ARC_PTS_SCALE = (1.0f - PAD * 2) / NUM_ARC_PTS;
public void appendArc(float x0, float y0, float z0, float x1, float y1, float z1, float h, float as0, float as1, int col) {
public void appendArc(float x0, float y0, float z0, float x1, float y1, float z1, float h, float as0, float as1, int col)
{
float dx = x1 - x0;
float dy = y1 - y0;
float dz = z1 - z0;
float len = (float)Math.Sqrt(dx * dx + dy * dy + dz * dz);
float[] prev = new float[3];
evalArc(x0, y0, z0, dx, dy, dz, len * h, PAD, prev);
for (int i = 1; i <= NUM_ARC_PTS; ++i) {
for (int i = 1; i <= NUM_ARC_PTS; ++i)
{
float u = PAD + i * ARC_PTS_SCALE;
float[] pt = new float[3];
evalArc(x0, y0, z0, dx, dy, dz, len * h, u, pt);
@ -234,14 +262,16 @@ public class DebugDraw {
}
// End arrows
if (as0 > 0.001f) {
if (as0 > 0.001f)
{
float[] p = new float[3], q = new float[3];
evalArc(x0, y0, z0, dx, dy, dz, len * h, PAD, p);
evalArc(x0, y0, z0, dx, dy, dz, len * h, PAD + 0.05f, q);
appendArrowHead(p, q, as0, col);
}
if (as1 > 0.001f) {
if (as1 > 0.001f)
{
float[] p = new float[3], q = new float[3];
evalArc(x0, y0, z0, dx, dy, dz, len * h, 1 - PAD, p);
evalArc(x0, y0, z0, dx, dy, dz, len * h, 1 - (PAD + 0.05f), q);
@ -249,19 +279,22 @@ public class DebugDraw {
}
}
private void evalArc(float x0, float y0, float z0, float dx, float dy, float dz, float h, float u, float[] res) {
private void evalArc(float x0, float y0, float z0, float dx, float dy, float dz, float h, float u, float[] res)
{
res[0] = x0 + dx * u;
res[1] = y0 + dy * u + h * (1 - (u * 2 - 1) * (u * 2 - 1));
res[2] = z0 + dz * u;
}
public void debugDrawCross(float x, float y, float z, float size, int col, float lineWidth) {
public void debugDrawCross(float x, float y, float z, float size, int col, float lineWidth)
{
begin(DebugDrawPrimitives.LINES, lineWidth);
appendCross(x, y, z, size, col);
end();
}
private void appendCross(float x, float y, float z, float s, int col) {
private void appendCross(float x, float y, float z, float s, int col)
{
vertex(x - s, y, z, col);
vertex(x + s, y, z, col);
vertex(x, y - s, z, col);
@ -270,19 +303,22 @@ public class DebugDraw {
vertex(x, y, z + s, col);
}
public void debugDrawBox(float minx, float miny, float minz, float maxx, float maxy, float maxz, int[] fcol) {
public void debugDrawBox(float minx, float miny, float minz, float maxx, float maxy, float maxz, int[] fcol)
{
begin(DebugDrawPrimitives.QUADS);
appendBox(minx, miny, minz, maxx, maxy, maxz, fcol);
end();
}
public void debugDrawCylinder(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col) {
public void debugDrawCylinder(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col)
{
begin(DebugDrawPrimitives.TRIS);
appendCylinder(minx, miny, minz, maxx, maxy, maxz, col);
end();
}
public void appendCylinder(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col) {
public void appendCylinder(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col)
{
initCylinder();
int col2 = duMultCol(col, 160);
@ -292,19 +328,24 @@ public class DebugDraw {
float rx = (maxx - minx) / 2;
float rz = (maxz - minz) / 2;
for (int i = 2; i < CYLINDER_NUM_SEG; ++i) {
for (int i = 2; i < CYLINDER_NUM_SEG; ++i)
{
int a = 0, b = i - 1, c = i;
vertex(cx + cylinderDir[a * 2 + 0] * rx, miny, cz + cylinderDir[a * 2 + 1] * rz, col2);
vertex(cx + cylinderDir[b * 2 + 0] * rx, miny, cz + cylinderDir[b * 2 + 1] * rz, col2);
vertex(cx + cylinderDir[c * 2 + 0] * rx, miny, cz + cylinderDir[c * 2 + 1] * rz, col2);
}
for (int i = 2; i < CYLINDER_NUM_SEG; ++i) {
for (int i = 2; i < CYLINDER_NUM_SEG; ++i)
{
int a = 0, b = i, c = i - 1;
vertex(cx + cylinderDir[a * 2 + 0] * rx, maxy, cz + cylinderDir[a * 2 + 1] * rz, col);
vertex(cx + cylinderDir[b * 2 + 0] * rx, maxy, cz + cylinderDir[b * 2 + 1] * rz, col);
vertex(cx + cylinderDir[c * 2 + 0] * rx, maxy, cz + cylinderDir[c * 2 + 1] * rz, col);
}
for (int i = 0, j = CYLINDER_NUM_SEG - 1; i < CYLINDER_NUM_SEG; j = i++) {
for (int i = 0, j = CYLINDER_NUM_SEG - 1; i < CYLINDER_NUM_SEG; j = i++)
{
vertex(cx + cylinderDir[i * 2 + 0] * rx, miny, cz + cylinderDir[i * 2 + 1] * rz, col2);
vertex(cx + cylinderDir[j * 2 + 0] * rx, miny, cz + cylinderDir[j * 2 + 1] * rz, col2);
vertex(cx + cylinderDir[j * 2 + 0] * rx, maxy, cz + cylinderDir[j * 2 + 1] * rz, col);
@ -316,15 +357,15 @@ public class DebugDraw {
}
public void debugDrawArrow(float x0, float y0, float z0, float x1, float y1, float z1, float as0, float as1, int col,
float lineWidth) {
float lineWidth)
{
begin(DebugDrawPrimitives.LINES, lineWidth);
appendArrow(x0, y0, z0, x1, y1, z1, as0, as1, col);
end();
}
public void appendArrow(float x0, float y0, float z0, float x1, float y1, float z1, float as0, float as1, int col) {
public void appendArrow(float x0, float y0, float z0, float x1, float y1, float z1, float as0, float as1, int col)
{
vertex(x0, y0, z0, col);
vertex(x1, y1, z1, col);
@ -337,11 +378,14 @@ public class DebugDraw {
appendArrowHead(q, p, as1, col);
}
void appendArrowHead(float[] p, float[] q, float s, int col) {
void appendArrowHead(float[] p, float[] q, float s, int col)
{
float eps = 0.001f;
if (vdistSqr(p, q) < eps * eps) {
if (vdistSqr(p, q) < eps * eps)
{
return;
}
float[] ax = new float[3], ay = { 0, 1, 0 }, az = new float[3];
vsub(az, q, p);
vnormalize(az);
@ -356,29 +400,32 @@ public class DebugDraw {
vertex(p, col);
// vertex(p[0]+az[0]*s-ay[0]*s/2, p[1]+az[1]*s-ay[1]*s/2, p[2]+az[2]*s-ay[2]*s/2, col);
vertex(p[0] + az[0] * s - ax[0] * s / 3, p[1] + az[1] * s - ax[1] * s / 3, p[2] + az[2] * s - ax[2] * s / 3, col);
}
public void vcross(float[] dest, float[] v1, float[] v2) {
public void vcross(float[] dest, float[] v1, float[] v2)
{
dest[0] = v1[1] * v2[2] - v1[2] * v2[1];
dest[1] = v1[2] * v2[0] - v1[0] * v2[2];
dest[2] = v1[0] * v2[1] - v1[1] * v2[0];
}
public void vnormalize(float[] v) {
public void vnormalize(float[] v)
{
float d = (float)(1.0f / Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]));
v[0] *= d;
v[1] *= d;
v[2] *= d;
}
public void vsub(float[] dest, float[] v1, float[] v2) {
public void vsub(float[] dest, float[] v1, float[] v2)
{
dest[0] = v1[0] - v2[0];
dest[1] = v1[1] - v2[1];
dest[2] = v1[2] - v2[2];
}
public float vdistSqr(float[] v1, float[] v2) {
public float vdistSqr(float[] v1, float[] v2)
{
float x = v1[0] - v2[0];
float y = v1[1] - v2[1];
float z = v1[2] - v2[2];
@ -393,8 +440,10 @@ public class DebugDraw {
// }
// }
public static int areaToCol(int area) {
switch (area) {
public static int areaToCol(int area)
{
switch (area)
{
// Ground (0) : light blue
case SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WALKABLE:
case SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND:
@ -420,11 +469,13 @@ public class DebugDraw {
}
}
public static int duRGBA(int r, int g, int b, int a) {
public static int duRGBA(int r, int g, int b, int a)
{
return (r) | (g << 8) | (b << 16) | (a << 24);
}
public static int duLerpCol(int ca, int cb, int u) {
public static int duLerpCol(int ca, int cb, int u)
{
int ra = ca & 0xff;
int ga = (ca >> 8) & 0xff;
int ba = (ca >> 16) & 0xff;
@ -441,18 +492,21 @@ public class DebugDraw {
return duRGBA(r, g, b, a);
}
public static int bit(int a, int b) {
public static int bit(int a, int b)
{
return (a & (1 << b)) >>> b;
}
public static int duIntToCol(int i, int a) {
public static int duIntToCol(int i, int a)
{
int r = bit(i, 1) + bit(i, 3) * 2 + 1;
int g = bit(i, 2) + bit(i, 4) * 2 + 1;
int b = bit(i, 0) + bit(i, 5) * 2 + 1;
return duRGBA(r * 63, g * 63, b * 63, a);
}
public static void duCalcBoxColors(int[] colors, int colTop, int colSide) {
public static void duCalcBoxColors(int[] colors, int colTop, int colSide)
{
colors[0] = duMultCol(colTop, 250);
colors[1] = duMultCol(colSide, 140);
colors[2] = duMultCol(colSide, 165);
@ -461,7 +515,8 @@ public class DebugDraw {
colors[5] = duMultCol(colSide, 217);
}
public static int duMultCol(int col, int d) {
public static int duMultCol(int col, int d)
{
int r = col & 0xff;
int g = (col >> 8) & 0xff;
int b = (col >> 16) & 0xff;
@ -469,46 +524,56 @@ public class DebugDraw {
return duRGBA((r * d) >> 8, (g * d) >> 8, (b * d) >> 8, a);
}
public static int duTransCol(int c, int a) {
public static int duTransCol(int c, int a)
{
return (a << 24) | (c & 0x00ffffff);
}
public static int duDarkenCol(int col) {
public static int duDarkenCol(int col)
{
return (int)(((col >> 1) & 0x007f7f7f) | (col & 0xff000000));
}
public void fog(float start, float end) {
public void fog(float start, float end)
{
getOpenGlDraw().fog(start, end);
}
public void fog(bool state) {
public void fog(bool state)
{
getOpenGlDraw().fog(state);
}
public void depthMask(bool state) {
public void depthMask(bool state)
{
getOpenGlDraw().depthMask(state);
}
public void texture(bool state) {
public void texture(bool state)
{
getOpenGlDraw().texture(g_tex, state);
}
public void init(GL gl, float fogDistance) {
public void init(GL gl, float fogDistance)
{
getOpenGlDraw().init(gl);
}
public void clear() {
public void clear()
{
getOpenGlDraw().clear();
}
public float[] projectionMatrix(float fovy, float aspect, float near, float far) {
public float[] projectionMatrix(float fovy, float aspect, float near, float far)
{
GLU.glhPerspectivef2(_projectionMatrix, fovy, aspect, near, far);
getOpenGlDraw().projectionMatrix(_projectionMatrix);
updateFrustum();
return _projectionMatrix;
}
public float[] viewMatrix(float[] cameraPos, float[] cameraEulers) {
public float[] viewMatrix(float[] cameraPos, float[] cameraEulers)
{
float[] rx = GLU.build_4x4_rotation_matrix(cameraEulers[0], 1, 0, 0);
float[] ry = GLU.build_4x4_rotation_matrix(cameraEulers[1], 0, 1, 0);
float[] r = GLU.mul(rx, ry);
@ -523,11 +588,13 @@ public class DebugDraw {
return _viewMatrix;
}
private OpenGLDraw getOpenGlDraw() {
private OpenGLDraw getOpenGlDraw()
{
return openGlDraw;
}
private void updateFrustum() {
private void updateFrustum()
{
float[] vpm = GLU.mul(_projectionMatrix, _viewMatrix);
// left
frustumPlanes[0] = normalizePlane(vpm[0 + 3] + vpm[0 + 0], vpm[4 + 3] + vpm[4 + 0], vpm[8 + 3] + vpm[8 + 0],
@ -549,56 +616,75 @@ public class DebugDraw {
vpm[12 + 3] - vpm[12 + 2]);
}
private float[] normalizePlane(float px, float py, float pz, float pw) {
private float[] normalizePlane(float px, float py, float pz, float pw)
{
float length = (float)Math.Sqrt(px * px + py * py + pz * pz);
if (length != 0) {
if (length != 0)
{
length = 1f / length;
px *= length;
py *= length;
pz *= length;
pw *= length;
}
return new float[] { px, py, pz, pw };
}
public bool frustumTest(float[] bmin, float[] bmax) {
public bool frustumTest(float[] bmin, float[] bmax)
{
return frustumTest(new float[] { bmin[0], bmin[1], bmin[2], bmax[0], bmax[1], bmax[2] });
}
public bool frustumTest(float[] bounds) {
foreach (float[] plane in frustumPlanes) {
public bool frustumTest(float[] bounds)
{
foreach (float[] plane in frustumPlanes)
{
float p_x;
float p_y;
float p_z;
float n_x;
float n_y;
float n_z;
if (plane[0] >= 0) {
if (plane[0] >= 0)
{
p_x = bounds[3];
n_x = bounds[0];
} else {
}
else
{
p_x = bounds[0];
n_x = bounds[3];
}
if (plane[1] >= 0) {
if (plane[1] >= 0)
{
p_y = bounds[4];
n_y = bounds[1];
} else {
}
else
{
p_y = bounds[1];
n_y = bounds[4];
}
if (plane[2] >= 0) {
if (plane[2] >= 0)
{
p_z = bounds[5];
n_z = bounds[2];
} else {
}
else
{
p_z = bounds[2];
n_z = bounds[5];
}
if (plane[0] * p_x + plane[1] * p_y + plane[2] * p_z + plane[3] < 0) {
if (plane[0] * p_x + plane[1] * p_y + plane[2] * p_z + plane[3] < 0)
{
return false;
}
}
return true;
}
}

View File

@ -17,10 +17,11 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Recast.Demo.Draw;
public enum DebugDrawPrimitives {
public enum DebugDrawPrimitives
{
POINTS,
LINES,
TRIS,

View File

@ -17,9 +17,11 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Recast.Demo.Draw;
public class DrawMode {
public class DrawMode
{
public static readonly DrawMode DRAWMODE_MESH = new("Input Mesh");
public static readonly DrawMode DRAWMODE_NAVMESH = new("Navmesh");
public static readonly DrawMode DRAWMODE_NAVMESH_INVIS = new("Navmesh Invis");
@ -41,11 +43,13 @@ public class DrawMode {
private readonly string text;
private DrawMode(string text) {
private DrawMode(string text)
{
this.text = text;
}
public override string ToString() {
public override string ToString()
{
return text;
}
}

View File

@ -22,17 +22,19 @@ using Silk.NET.OpenGL;
namespace DotRecast.Recast.Demo.Draw;
public class GLCheckerTexture {
public class GLCheckerTexture
{
int m_texId;
public void release() {
public void release()
{
// if (m_texId != 0) {
// glDeleteTextures(m_texId);
// }
}
public void bind() {
public void bind()
{
// if (m_texId == 0) {
// // Create checker pattern.
// int col0 = DebugDraw.duRGBA(215, 215, 215, 255);

View File

@ -21,11 +21,10 @@ using DotRecast.Core;
namespace DotRecast.Recast.Demo.Draw;
public class GLU {
public static float[] gluPerspective(float fovy, float aspect, float near, float far) {
public class GLU
{
public static float[] gluPerspective(float fovy, float aspect, float near, float far)
{
float[] projectionMatrix = new float[16];
glhPerspectivef2(projectionMatrix, fovy, aspect, near, far);
//glLoadMatrixf(projectionMatrix);
@ -33,7 +32,8 @@ public class GLU {
}
public static void glhPerspectivef2(float[] matrix, float fovyInDegrees, float aspectRatio, float znear,
float zfar) {
float zfar)
{
float ymax, xmax;
ymax = (float)(znear * Math.Tan(fovyInDegrees * Math.PI / 360.0));
xmax = ymax * aspectRatio;
@ -41,7 +41,8 @@ public class GLU {
}
private static void glhFrustumf2(float[] matrix, float left, float right, float bottom, float top, float znear,
float zfar) {
float zfar)
{
float temp, temp2, temp3, temp4;
temp = 2.0f * znear;
temp2 = right - left;
@ -66,7 +67,8 @@ public class GLU {
}
public static int glhUnProjectf(float winx, float winy, float winz, float[] modelview, float[] projection,
int[] viewport, float[] objectCoordinate) {
int[] viewport, float[] objectCoordinate)
{
// Transformation matrices
float[] m = new float[16], A = new float[16];
float[] @in = new float[4], @out = new float[4];
@ -92,7 +94,8 @@ public class GLU {
return 1;
}
static void MultiplyMatrices4by4OpenGL_FLOAT(float[] result, float[] matrix1, float[] matrix2) {
static void MultiplyMatrices4by4OpenGL_FLOAT(float[] result, float[] matrix1, float[] matrix2)
{
result[0] = matrix1[0] * matrix2[0] + matrix1[4] * matrix2[1] + matrix1[8] * matrix2[2]
+ matrix1[12] * matrix2[3];
result[4] = matrix1[0] * matrix2[4] + matrix1[4] * matrix2[5] + matrix1[8] * matrix2[6]
@ -127,7 +130,8 @@ public class GLU {
+ matrix1[15] * matrix2[15];
}
static void MultiplyMatrixByVector4by4OpenGL_FLOAT(float[] resultvector, float[] matrix, float[] pvector) {
static void MultiplyMatrixByVector4by4OpenGL_FLOAT(float[] resultvector, float[] matrix, float[] pvector)
{
resultvector[0] = matrix[0] * pvector[0] + matrix[4] * pvector[1] + matrix[8] * pvector[2]
+ matrix[12] * pvector[3];
resultvector[1] = matrix[1] * pvector[0] + matrix[5] * pvector[1] + matrix[9] * pvector[2]
@ -139,7 +143,8 @@ public class GLU {
}
// This code comes directly from GLU except that it is for float
static int glhInvertMatrixf2(float[] m, float[] @out) {
static int glhInvertMatrixf2(float[] m, float[] @out)
{
float[][] wtmp = ArrayUtils.Of<float>(4, 8);
float m0, m1, m2, m3, s;
float[] r0, r1, r2, r3;
@ -172,21 +177,27 @@ public class GLU {
r3[7] = 1.0f;
r3[4] = r3[5] = r3[6] = 0.0f;
/* choose pivot - or die */
if (Math.Abs(r3[0]) > Math.Abs(r2[0])) {
if (Math.Abs(r3[0]) > Math.Abs(r2[0]))
{
float[] r = r2;
r2 = r3;
r3 = r;
}
if (Math.Abs(r2[0]) > Math.Abs(r1[0])) {
if (Math.Abs(r2[0]) > Math.Abs(r1[0]))
{
float[] r = r2;
r2 = r1;
r1 = r;
}
if (Math.Abs(r1[0]) > Math.Abs(r0[0])) {
if (Math.Abs(r1[0]) > Math.Abs(r0[0]))
{
float[] r = r1;
r1 = r0;
r0 = r;
}
if (0.0 == r0[0])
return 0;
/* eliminate first variable */
@ -206,40 +217,52 @@ public class GLU {
r2[3] -= m2 * s;
r3[3] -= m3 * s;
s = r0[4];
if (s != 0.0) {
if (s != 0.0)
{
r1[4] -= m1 * s;
r2[4] -= m2 * s;
r3[4] -= m3 * s;
}
s = r0[5];
if (s != 0.0) {
if (s != 0.0)
{
r1[5] -= m1 * s;
r2[5] -= m2 * s;
r3[5] -= m3 * s;
}
s = r0[6];
if (s != 0.0) {
if (s != 0.0)
{
r1[6] -= m1 * s;
r2[6] -= m2 * s;
r3[6] -= m3 * s;
}
s = r0[7];
if (s != 0.0) {
if (s != 0.0)
{
r1[7] -= m1 * s;
r2[7] -= m2 * s;
r3[7] -= m3 * s;
}
/* choose pivot - or die */
if (Math.Abs(r3[1]) > Math.Abs(r2[1])) {
if (Math.Abs(r3[1]) > Math.Abs(r2[1]))
{
float[] r = r2;
r2 = r3;
r3 = r;
}
if (Math.Abs(r2[1]) > Math.Abs(r1[1])) {
if (Math.Abs(r2[1]) > Math.Abs(r1[1]))
{
float[] r = r2;
r2 = r1;
r1 = r;
}
if (0.0 == r1[1])
return 0;
/* eliminate second variable */
@ -250,31 +273,41 @@ public class GLU {
r2[3] -= m2 * r1[3];
r3[3] -= m3 * r1[3];
s = r1[4];
if (0.0 != s) {
if (0.0 != s)
{
r2[4] -= m2 * s;
r3[4] -= m3 * s;
}
s = r1[5];
if (0.0 != s) {
if (0.0 != s)
{
r2[5] -= m2 * s;
r3[5] -= m3 * s;
}
s = r1[6];
if (0.0 != s) {
if (0.0 != s)
{
r2[6] -= m2 * s;
r3[6] -= m3 * s;
}
s = r1[7];
if (0.0 != s) {
if (0.0 != s)
{
r2[7] -= m2 * s;
r3[7] -= m3 * s;
}
/* choose pivot - or die */
if (Math.Abs(r3[2]) > Math.Abs(r2[2])) {
if (Math.Abs(r3[2]) > Math.Abs(r2[2]))
{
float[] r = r2;
r2 = r3;
r3 = r;
}
if (0.0 == r2[2])
return 0;
/* eliminate third variable */
@ -344,15 +377,18 @@ public class GLU {
return 1;
}
static float MAT(float[] m, int r, int c) {
static float MAT(float[] m, int r, int c)
{
return m[(c) * 4 + (r)];
}
static void MAT(float[] m, int r, int c, float v) {
static void MAT(float[] m, int r, int c, float v)
{
m[(c) * 4 + (r)] = v;
}
public static float[] build_4x4_rotation_matrix(float a, float x, float y, float z) {
public static float[] build_4x4_rotation_matrix(float a, float x, float y, float z)
{
float[] matrix = new float[16];
a = (float)(a * Math.PI / 180.0); // convert to radians
float s = (float)Math.Sin(a);
@ -387,10 +423,10 @@ public class GLU {
matrix[14] = 0;
matrix[15] = 1;
return matrix;
}
public static float[] mul(float[] left, float[] right) {
public static float[] mul(float[] left, float[] right)
{
float m00 = left[0] * right[0] + left[4] * right[1] + left[8] * right[2] + left[12] * right[3];
float m01 = left[1] * right[0] + left[5] * right[1] + left[9] * right[2] + left[13] * right[3];
float m02 = left[2] * right[0] + left[6] * right[1] + left[10] * right[2] + left[14] * right[3];
@ -428,5 +464,4 @@ public class GLU {
return dest;
}
}

View File

@ -4,10 +4,10 @@ namespace DotRecast.Recast.Demo.Draw;
public class LegacyOpenGLDraw : OpenGLDraw
{
private GL _gl;
public void fog(bool state) {
public void fog(bool state)
{
// if (state) {
// _gl.Enable(GL_FOG);
// } else {
@ -30,7 +30,8 @@ public class LegacyOpenGLDraw : OpenGLDraw
// glDepthFunc(GL_LEQUAL);
}
public void clear() {
public void clear()
{
// glClearColor(0.3f, 0.3f, 0.32f, 1.0f);
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// glEnable(GL_BLEND);
@ -40,17 +41,20 @@ public class LegacyOpenGLDraw : OpenGLDraw
// glEnable(GL_CULL_FACE);
}
public void projectionMatrix(float[] matrix) {
public void projectionMatrix(float[] matrix)
{
// glMatrixMode(GL_PROJECTION);
// glLoadMatrixf(matrix);
}
public void viewMatrix(float[] matrix) {
public void viewMatrix(float[] matrix)
{
// glMatrixMode(GL_MODELVIEW);
// glLoadMatrixf(matrix);
}
public void begin(DebugDrawPrimitives prim, float size) {
public void begin(DebugDrawPrimitives prim, float size)
{
// switch (prim) {
// case POINTS:
// glPointSize(size);
@ -69,38 +73,45 @@ public class LegacyOpenGLDraw : OpenGLDraw
// }
}
public void vertex(float[] pos, int color) {
public void vertex(float[] pos, int color)
{
// glColor4ubv(color);
// glVertex3fv(pos);
}
public void vertex(float x, float y, float z, int color) {
public void vertex(float x, float y, float z, int color)
{
// glColor4ubv(color);
// glVertex3f(x, y, z);
}
public void vertex(float[] pos, int color, float[] uv) {
public void vertex(float[] pos, int color, float[] uv)
{
// glColor4ubv(color);
// glTexCoord2fv(uv);
// glVertex3fv(pos);
}
public void vertex(float x, float y, float z, int color, float u, float v) {
public void vertex(float x, float y, float z, int color, float u, float v)
{
// glColor4ubv(color);
// glTexCoord2f(u, v);
// glVertex3f(x, y, z);
}
private void glColor4ubv(int color) {
private void glColor4ubv(int color)
{
// glColor4ub((byte) (color & 0xFF), (byte) ((color >> 8) & 0xFF), (byte) ((color >> 16) & 0xFF),
// (byte) ((color >> 24) & 0xFF));
}
public void depthMask(bool state) {
public void depthMask(bool state)
{
// glDepthMask(state);
}
public void texture(GLCheckerTexture g_tex, bool state) {
public void texture(GLCheckerTexture g_tex, bool state)
{
// if (state) {
// glEnable(GL_TEXTURE_2D);
// g_tex.bind();
@ -109,15 +120,16 @@ public class LegacyOpenGLDraw : OpenGLDraw
// }
}
public void end() {
public void end()
{
// glEnd();
// glLineWidth(1.0f);
// glPointSize(1.0f);
}
public void fog(float start, float end) {
public void fog(float start, float end)
{
// glFogf(GL_FOG_START, start);
// glFogf(GL_FOG_END, end);
}
}

View File

@ -7,7 +7,8 @@ using Silk.NET.Windowing;
namespace DotRecast.Recast.Demo.Draw;
public class ModernOpenGLDraw : OpenGLDraw {
public class ModernOpenGLDraw : OpenGLDraw
{
private GL _gl;
private uint program;
private int uniformTexture;
@ -71,20 +72,26 @@ public class ModernOpenGLDraw : OpenGLDraw {
_gl.CompileShader(vert_shdr);
_gl.CompileShader(frag_shdr);
gl.GetShader(vert_shdr, GLEnum.CompileStatus, out var status);
if (status != (int) GLEnum.True) {
if (status != (int)GLEnum.True)
{
throw new InvalidOperationException();
}
gl.GetShader(frag_shdr, GLEnum.CompileStatus, out status);
if (status != (int) GLEnum.True) {
if (status != (int)GLEnum.True)
{
throw new InvalidOperationException();
}
_gl.AttachShader(program, vert_shdr);
_gl.AttachShader(program, frag_shdr);
_gl.LinkProgram(program);
_gl.GetProgram(program, GLEnum.LinkStatus, out status);
if (status != (int) GLEnum.True) {
if (status != (int)GLEnum.True)
{
throw new InvalidOperationException();
}
uniformTexture = _gl.GetUniformLocation(program, "Texture");
uniformUseTexture = _gl.GetUniformLocation(program, "UseTexture");
uniformFog = _gl.GetUniformLocation(program, "EnableFog");
@ -123,7 +130,8 @@ public class ModernOpenGLDraw : OpenGLDraw {
_gl.BindVertexArray(0);
}
public void clear() {
public void clear()
{
_gl.ClearColor(0.3f, 0.3f, 0.32f, 1.0f);
_gl.Clear((uint)GLEnum.ColorBufferBit | (uint)GLEnum.DepthBufferBit);
_gl.Enable(GLEnum.Blend);
@ -133,14 +141,16 @@ public class ModernOpenGLDraw : OpenGLDraw {
_gl.Enable(GLEnum.CullFace);
}
public void begin(DebugDrawPrimitives prim, float size) {
public void begin(DebugDrawPrimitives prim, float size)
{
currentPrim = prim;
vertices.Clear();
_gl.LineWidth(size);
_gl.PointSize(size);
}
public void end() {
public void end()
{
// if (vertices.isEmpty()) {
// return;
// }
@ -217,48 +227,58 @@ public class ModernOpenGLDraw : OpenGLDraw {
// glPointSize(1.0f);
}
public void vertex(float x, float y, float z, int color) {
public void vertex(float x, float y, float z, int color)
{
vertices.Add(new OpenGLVertex(x, y, z, color));
}
public void vertex(float[] pos, int color) {
public void vertex(float[] pos, int color)
{
vertices.Add(new OpenGLVertex(pos, color));
}
public void vertex(float[] pos, int color, float[] uv) {
public void vertex(float[] pos, int color, float[] uv)
{
vertices.Add(new OpenGLVertex(pos, uv, color));
}
public void vertex(float x, float y, float z, int color, float u, float v) {
public void vertex(float x, float y, float z, int color, float u, float v)
{
vertices.Add(new OpenGLVertex(x, y, z, u, v, color));
}
public void depthMask(bool state) {
public void depthMask(bool state)
{
_gl.DepthMask(state);
}
public void texture(GLCheckerTexture g_tex, bool state) {
public void texture(GLCheckerTexture g_tex, bool state)
{
_texture = state ? g_tex : null;
if (_texture != null) {
if (_texture != null)
{
_texture.bind();
}
}
public void projectionMatrix(float[] projectionMatrix) {
public void projectionMatrix(float[] projectionMatrix)
{
this._projectionMatrix = projectionMatrix;
}
public void viewMatrix(float[] viewMatrix) {
public void viewMatrix(float[] viewMatrix)
{
this._viewMatrix = viewMatrix;
}
public void fog(float start, float end) {
public void fog(float start, float end)
{
fogStart = start;
fogEnd = end;
}
public void fog(bool state) {
public void fog(bool state)
{
fogEnabled = state;
}
}

View File

@ -27,25 +27,30 @@ using DotRecast.Recast.Demo.Settings;
namespace DotRecast.Recast.Demo.Draw;
public class NavMeshRenderer {
public class NavMeshRenderer
{
private readonly RecastDebugDraw debugDraw;
private readonly int navMeshDrawFlags = RecastDebugDraw.DRAWNAVMESH_OFFMESHCONS
| RecastDebugDraw.DRAWNAVMESH_CLOSEDLIST;
public NavMeshRenderer(RecastDebugDraw debugDraw) {
public NavMeshRenderer(RecastDebugDraw debugDraw)
{
this.debugDraw = debugDraw;
}
public RecastDebugDraw getDebugDraw() {
public RecastDebugDraw getDebugDraw()
{
return debugDraw;
}
public void render(Sample sample) {
if (sample == null) {
public void render(Sample sample)
{
if (sample == null)
{
return;
}
NavMeshQuery navQuery = sample.getNavMeshQuery();
DemoInputGeomProvider geom = sample.getInputGeom();
IList<RecastBuilderResult> rcBuilderResults = sample.getRecastResults();
@ -58,9 +63,11 @@ public class NavMeshRenderer {
float texScale = 1.0f / (rcSettingsView.getCellSize() * 10.0f);
float m_agentMaxSlope = rcSettingsView.getAgentMaxSlope();
if (drawMode != DrawMode.DRAWMODE_NAVMESH_TRANS) {
if (drawMode != DrawMode.DRAWMODE_NAVMESH_TRANS)
{
// Draw mesh
if (geom != null) {
if (geom != null)
{
debugDraw.debugDrawTriMeshSlope(geom.vertices, geom.faces, geom.normals, m_agentMaxSlope, texScale);
drawOffMeshConnections(geom, false);
}
@ -68,7 +75,8 @@ public class NavMeshRenderer {
debugDraw.fog(false);
debugDraw.depthMask(false);
if (geom != null) {
if (geom != null)
{
drawGeomBounds(geom);
}
@ -76,17 +84,25 @@ public class NavMeshRenderer {
&& (drawMode == DrawMode.DRAWMODE_NAVMESH || drawMode == DrawMode.DRAWMODE_NAVMESH_TRANS
|| drawMode == DrawMode.DRAWMODE_NAVMESH_BVTREE || drawMode == DrawMode.DRAWMODE_NAVMESH_NODES
|| drawMode == DrawMode.DRAWMODE_NAVMESH_INVIS
|| drawMode == DrawMode.DRAWMODE_NAVMESH_PORTALS)) {
if (drawMode != DrawMode.DRAWMODE_NAVMESH_INVIS) {
|| drawMode == DrawMode.DRAWMODE_NAVMESH_PORTALS))
{
if (drawMode != DrawMode.DRAWMODE_NAVMESH_INVIS)
{
debugDraw.debugDrawNavMeshWithClosedList(navMesh, navQuery, navMeshDrawFlags);
}
if (drawMode == DrawMode.DRAWMODE_NAVMESH_BVTREE) {
if (drawMode == DrawMode.DRAWMODE_NAVMESH_BVTREE)
{
debugDraw.debugDrawNavMeshBVTree(navMesh);
}
if (drawMode == DrawMode.DRAWMODE_NAVMESH_PORTALS) {
if (drawMode == DrawMode.DRAWMODE_NAVMESH_PORTALS)
{
debugDraw.debugDrawNavMeshPortals(navMesh);
}
if (drawMode == DrawMode.DRAWMODE_NAVMESH_NODES) {
if (drawMode == DrawMode.DRAWMODE_NAVMESH_NODES)
{
debugDraw.debugDrawNavMeshNodes(navQuery);
debugDraw.debugDrawNavMeshPolysWithFlags(navMesh, SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED,
DebugDraw.duRGBA(0, 0, 0, 128));
@ -95,68 +111,94 @@ public class NavMeshRenderer {
debugDraw.depthMask(true);
foreach (RecastBuilderResult rcBuilderResult in rcBuilderResults) {
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT) {
foreach (RecastBuilderResult rcBuilderResult in rcBuilderResults)
{
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT)
{
debugDraw.debugDrawCompactHeightfieldSolid(rcBuilderResult.getCompactHeightfield());
}
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT_DISTANCE) {
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT_DISTANCE)
{
debugDraw.debugDrawCompactHeightfieldDistance(rcBuilderResult.getCompactHeightfield());
}
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT_REGIONS) {
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT_REGIONS)
{
debugDraw.debugDrawCompactHeightfieldRegions(rcBuilderResult.getCompactHeightfield());
}
if (rcBuilderResult.getSolidHeightfield() != null && drawMode == DrawMode.DRAWMODE_VOXELS) {
if (rcBuilderResult.getSolidHeightfield() != null && drawMode == DrawMode.DRAWMODE_VOXELS)
{
debugDraw.fog(true);
debugDraw.debugDrawHeightfieldSolid(rcBuilderResult.getSolidHeightfield());
debugDraw.fog(false);
}
if (rcBuilderResult.getSolidHeightfield() != null && drawMode == DrawMode.DRAWMODE_VOXELS_WALKABLE) {
if (rcBuilderResult.getSolidHeightfield() != null && drawMode == DrawMode.DRAWMODE_VOXELS_WALKABLE)
{
debugDraw.fog(true);
debugDraw.debugDrawHeightfieldWalkable(rcBuilderResult.getSolidHeightfield());
debugDraw.fog(false);
}
if (rcBuilderResult.getContourSet() != null && drawMode == DrawMode.DRAWMODE_RAW_CONTOURS) {
if (rcBuilderResult.getContourSet() != null && drawMode == DrawMode.DRAWMODE_RAW_CONTOURS)
{
debugDraw.depthMask(false);
debugDraw.debugDrawRawContours(rcBuilderResult.getContourSet(), 1f);
debugDraw.depthMask(true);
}
if (rcBuilderResult.getContourSet() != null && drawMode == DrawMode.DRAWMODE_BOTH_CONTOURS) {
if (rcBuilderResult.getContourSet() != null && drawMode == DrawMode.DRAWMODE_BOTH_CONTOURS)
{
debugDraw.depthMask(false);
debugDraw.debugDrawRawContours(rcBuilderResult.getContourSet(), 0.5f);
debugDraw.debugDrawContours(rcBuilderResult.getContourSet());
debugDraw.depthMask(true);
}
if (rcBuilderResult.getContourSet() != null && drawMode == DrawMode.DRAWMODE_CONTOURS) {
if (rcBuilderResult.getContourSet() != null && drawMode == DrawMode.DRAWMODE_CONTOURS)
{
debugDraw.depthMask(false);
debugDraw.debugDrawContours(rcBuilderResult.getContourSet());
debugDraw.depthMask(true);
}
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_REGION_CONNECTIONS) {
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_REGION_CONNECTIONS)
{
debugDraw.debugDrawCompactHeightfieldRegions(rcBuilderResult.getCompactHeightfield());
debugDraw.depthMask(false);
if (rcBuilderResult.getContourSet() != null) {
if (rcBuilderResult.getContourSet() != null)
{
debugDraw.debugDrawRegionConnections(rcBuilderResult.getContourSet());
}
debugDraw.depthMask(true);
}
if (rcBuilderResult.getMesh() != null && drawMode == DrawMode.DRAWMODE_POLYMESH) {
if (rcBuilderResult.getMesh() != null && drawMode == DrawMode.DRAWMODE_POLYMESH)
{
debugDraw.depthMask(false);
debugDraw.debugDrawPolyMesh(rcBuilderResult.getMesh());
debugDraw.depthMask(true);
}
if (rcBuilderResult.getMeshDetail() != null && drawMode == DrawMode.DRAWMODE_POLYMESH_DETAIL) {
if (rcBuilderResult.getMeshDetail() != null && drawMode == DrawMode.DRAWMODE_POLYMESH_DETAIL)
{
debugDraw.depthMask(false);
debugDraw.debugDrawPolyMeshDetail(rcBuilderResult.getMeshDetail());
debugDraw.depthMask(true);
}
}
if (geom != null) {
if (geom != null)
{
drawConvexVolumes(geom);
}
}
private void drawGeomBounds(DemoInputGeomProvider geom) {
private void drawGeomBounds(DemoInputGeomProvider geom)
{
// Draw bounds
float[] bmin = geom.getMeshBoundsMin();
float[] bmax = geom.getMeshBoundsMax();
@ -167,14 +209,15 @@ public class NavMeshRenderer {
debugDraw.end();
}
public void drawOffMeshConnections(DemoInputGeomProvider geom, bool hilight) {
public void drawOffMeshConnections(DemoInputGeomProvider geom, bool hilight)
{
int conColor = DebugDraw.duRGBA(192, 0, 128, 192);
int baseColor = DebugDraw.duRGBA(0, 0, 0, 64);
debugDraw.depthMask(false);
debugDraw.begin(DebugDrawPrimitives.LINES, 2.0f);
foreach (DemoOffMeshConnection con in geom.getOffMeshConnections()) {
foreach (DemoOffMeshConnection con in geom.getOffMeshConnections())
{
float[] v = con.verts;
debugDraw.vertex(v[0], v[1], v[2], baseColor);
debugDraw.vertex(v[0], v[1] + 0.2f, v[2], baseColor);
@ -185,23 +228,28 @@ public class NavMeshRenderer {
debugDraw.appendCircle(v[0], v[1] + 0.1f, v[2], con.radius, baseColor);
debugDraw.appendCircle(v[3], v[4] + 0.1f, v[5], con.radius, baseColor);
if (hilight) {
if (hilight)
{
debugDraw.appendArc(v[0], v[1], v[2], v[3], v[4], v[5], 0.25f, con.bidir ? 0.6f : 0.0f, 0.6f, conColor);
}
}
debugDraw.end();
debugDraw.depthMask(true);
}
void drawConvexVolumes(DemoInputGeomProvider geom) {
void drawConvexVolumes(DemoInputGeomProvider geom)
{
debugDraw.depthMask(false);
debugDraw.begin(DebugDrawPrimitives.TRIS);
foreach (ConvexVolume vol in geom.convexVolumes()) {
foreach (ConvexVolume vol in geom.convexVolumes())
{
int col = DebugDraw.duTransCol(DebugDraw.areaToCol(vol.areaMod.getMaskedValue()), 32);
for (int j = 0, k = vol.verts.Length - 3; j < vol.verts.Length; k = j, j += 3) {
for (int j = 0, k = vol.verts.Length - 3; j < vol.verts.Length; k = j, j += 3)
{
float[] va = new float[] { vol.verts[k], vol.verts[k + 1], vol.verts[k + 2] };
float[] vb = new float[] { vol.verts[j], vol.verts[j + 1], vol.verts[j + 2] };
@ -222,9 +270,11 @@ public class NavMeshRenderer {
debugDraw.end();
debugDraw.begin(DebugDrawPrimitives.LINES, 2.0f);
foreach (ConvexVolume vol in geom.convexVolumes()) {
foreach (ConvexVolume vol in geom.convexVolumes())
{
int col = DebugDraw.duTransCol(DebugDraw.areaToCol(vol.areaMod.getMaskedValue()), 220);
for (int j = 0, k = vol.verts.Length - 3; j < vol.verts.Length; k = j, j += 3) {
for (int j = 0, k = vol.verts.Length - 3; j < vol.verts.Length; k = j, j += 3)
{
float[] va = new float[] { vol.verts[k], vol.verts[k + 1], vol.verts[k + 2] };
float[] vb = new float[] { vol.verts[j], vol.verts[j + 1], vol.verts[j + 2] };
debugDraw.vertex(va[0], vol.hmin, va[2], DebugDraw.duDarkenCol(col));
@ -235,21 +285,24 @@ public class NavMeshRenderer {
debugDraw.vertex(va[0], vol.hmax, va[2], col);
}
}
debugDraw.end();
debugDraw.begin(DebugDrawPrimitives.POINTS, 3.0f);
foreach (ConvexVolume vol in geom.convexVolumes()) {
foreach (ConvexVolume vol in geom.convexVolumes())
{
int col = DebugDraw
.duDarkenCol(DebugDraw.duTransCol(DebugDraw.areaToCol(vol.areaMod.getMaskedValue()), 220));
for (int j = 0; j < vol.verts.Length; j += 3) {
for (int j = 0; j < vol.verts.Length; j += 3)
{
debugDraw.vertex(vol.verts[j + 0], vol.verts[j + 1] + 0.1f, vol.verts[j + 2], col);
debugDraw.vertex(vol.verts[j + 0], vol.hmin, vol.verts[j + 2], col);
debugDraw.vertex(vol.verts[j + 0], vol.hmax, vol.verts[j + 2], col);
}
}
debugDraw.end();
debugDraw.depthMask(true);
}
}

View File

@ -2,8 +2,8 @@ using Silk.NET.OpenGL;
namespace DotRecast.Recast.Demo.Draw;
public interface OpenGLDraw {
public interface OpenGLDraw
{
void init(GL gl);
void clear();
@ -31,5 +31,4 @@ public interface OpenGLDraw {
void viewMatrix(float[] viewMatrix);
void fog(float start, float end);
}

View File

@ -2,8 +2,8 @@ using DotRecast.Core;
namespace DotRecast.Recast.Demo.Draw;
public class OpenGLVertex {
public class OpenGLVertex
{
private readonly float x;
private readonly float y;
private readonly float z;
@ -12,18 +12,22 @@ public class OpenGLVertex {
private readonly float v;
public OpenGLVertex(float[] pos, float[] uv, int color) :
this(pos[0], pos[1], pos[2], uv[0], uv[1], color) {
this(pos[0], pos[1], pos[2], uv[0], uv[1], color)
{
}
public OpenGLVertex(float[] pos, int color) :
this(pos[0], pos[1], pos[2], 0f, 0f, color) {
this(pos[0], pos[1], pos[2], 0f, 0f, color)
{
}
public OpenGLVertex(float x, float y, float z, int color) :
this(x, y, z, 0f, 0f, color) {
this(x, y, z, 0f, 0f, color)
{
}
public OpenGLVertex(float x, float y, float z, float u, float v, int color) {
public OpenGLVertex(float x, float y, float z, float u, float v, int color)
{
this.x = x;
this.y = y;
this.z = z;
@ -32,7 +36,8 @@ public class OpenGLVertex {
this.color = color;
}
public void store(ByteBuffer buffer) {
public void store(ByteBuffer buffer)
{
buffer.putFloat(x);
buffer.putFloat(y);
buffer.putFloat(z);

File diff suppressed because it is too large Load Diff

View File

@ -24,22 +24,27 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Recast.Demo.Geom;
public class ChunkyTriMesh {
private class BoundsItem {
public class ChunkyTriMesh
{
private class BoundsItem
{
public readonly float[] bmin = new float[2];
public readonly float[] bmax = new float[2];
public int i;
}
private class CompareItemX : IComparer<BoundsItem> {
public int Compare(BoundsItem a, BoundsItem b) {
private class CompareItemX : IComparer<BoundsItem>
{
public int Compare(BoundsItem a, BoundsItem b)
{
return a.bmin[0].CompareTo(b.bmin[0]);
}
}
private class CompareItemY : IComparer<BoundsItem> {
public int Compare(BoundsItem a, BoundsItem b) {
private class CompareItemY : IComparer<BoundsItem>
{
public int Compare(BoundsItem a, BoundsItem b)
{
return a.bmin[1].CompareTo(b.bmin[1]);
}
}
@ -48,43 +53,54 @@ public class ChunkyTriMesh {
int ntris;
int maxTrisPerChunk;
private void calcExtends(BoundsItem[] items, int imin, int imax, float[] bmin, float[] bmax) {
private void calcExtends(BoundsItem[] items, int imin, int imax, float[] bmin, float[] bmax)
{
bmin[0] = items[imin].bmin[0];
bmin[1] = items[imin].bmin[1];
bmax[0] = items[imin].bmax[0];
bmax[1] = items[imin].bmax[1];
for (int i = imin + 1; i < imax; ++i) {
for (int i = imin + 1; i < imax; ++i)
{
BoundsItem it = items[i];
if (it.bmin[0] < bmin[0]) {
if (it.bmin[0] < bmin[0])
{
bmin[0] = it.bmin[0];
}
if (it.bmin[1] < bmin[1]) {
if (it.bmin[1] < bmin[1])
{
bmin[1] = it.bmin[1];
}
if (it.bmax[0] > bmax[0]) {
if (it.bmax[0] > bmax[0])
{
bmax[0] = it.bmax[0];
}
if (it.bmax[1] > bmax[1]) {
if (it.bmax[1] > bmax[1])
{
bmax[1] = it.bmax[1];
}
}
}
private int longestAxis(float x, float 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<ChunkyTriMeshNode> nodes,
int[] inTris) {
int[] inTris)
{
int inum = imax - imin;
ChunkyTriMeshNode node = new ChunkyTriMeshNode();
nodes.Add(node);
if (inum <= trisPerChunk) {
if (inum <= trisPerChunk)
{
// Leaf
calcExtends(items, imin, imax, node.bmin, node.bmax);
@ -93,22 +109,28 @@ public class ChunkyTriMesh {
node.tris = new int[inum * 3];
int dst = 0;
for (int i = imin; i < imax; ++i) {
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 {
}
else
{
// Split
calcExtends(items, imin, imax, node.bmin, node.bmax);
int axis = longestAxis(node.bmax[0] - node.bmin[0], node.bmax[1] - node.bmin[1]);
if (axis == 0) {
if (axis == 0)
{
Array.Sort(items, imin, imax - imax, new CompareItemX());
// Sort along x-axis
} else if (axis == 1) {
}
else if (axis == 1)
{
Array.Sort(items, imin, imax - imin, new CompareItemY());
// Sort along y-axis
}
@ -125,7 +147,8 @@ public class ChunkyTriMesh {
}
}
public ChunkyTriMesh(float[] verts, int[] tris, int ntris, int trisPerChunk) {
public ChunkyTriMesh(float[] verts, int[] tris, int ntris, int trisPerChunk)
{
int nchunks = (ntris + trisPerChunk - 1) / trisPerChunk;
nodes = new(nchunks);
@ -134,26 +157,34 @@ public class ChunkyTriMesh {
// Build tree
BoundsItem[] items = new BoundsItem[ntris];
for (int i = 0; i < ntris; i++) {
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[0] = it.bmax[0] = verts[tris[t] * 3 + 0];
it.bmin[1] = it.bmax[1] = verts[tris[t] * 3 + 2];
for (int j = 1; j < 3; ++j) {
for (int j = 1; j < 3; ++j)
{
int v = tris[t + j] * 3;
if (verts[v] < it.bmin[0]) {
if (verts[v] < it.bmin[0])
{
it.bmin[0] = verts[v];
}
if (verts[v + 2] < it.bmin[1]) {
if (verts[v + 2] < it.bmin[1])
{
it.bmin[1] = verts[v + 2];
}
if (verts[v] > it.bmax[0]) {
if (verts[v] > it.bmax[0])
{
it.bmax[0] = verts[v];
}
if (verts[v + 2] > it.bmax[1]) {
if (verts[v + 2] > it.bmax[1])
{
it.bmax[1] = verts[v + 2];
}
}
@ -163,70 +194,89 @@ public class ChunkyTriMesh {
// Calc max tris per node.
maxTrisPerChunk = 0;
foreach (ChunkyTriMeshNode node in nodes) {
foreach (ChunkyTriMeshNode node in nodes)
{
bool isLeaf = node.i >= 0;
if (!isLeaf) {
if (!isLeaf)
{
continue;
}
if (node.tris.Length / 3 > maxTrisPerChunk) {
if (node.tris.Length / 3 > maxTrisPerChunk)
{
maxTrisPerChunk = node.tris.Length / 3;
}
}
}
public List<ChunkyTriMeshNode> getChunksOverlappingRect(float[] bmin, float[] bmax) {
public List<ChunkyTriMeshNode> getChunksOverlappingRect(float[] bmin, float[] bmax)
{
// Traverse tree
List<ChunkyTriMeshNode> ids = new();
int i = 0;
while (i < nodes.Count) {
while (i < nodes.Count)
{
ChunkyTriMeshNode node = nodes[i];
bool overlap = checkOverlapRect(bmin, bmax, node.bmin, node.bmax);
bool isLeafNode = node.i >= 0;
if (isLeafNode && overlap) {
if (isLeafNode && overlap)
{
ids.Add(node);
}
if (overlap || isLeafNode) {
if (overlap || isLeafNode)
{
i++;
} else {
}
else
{
i = -node.i;
}
}
return ids;
}
private bool checkOverlapRect(float[] amin, float[] amax, float[] bmin, float[] bmax) {
private bool checkOverlapRect(float[] amin, float[] amax, float[] bmin, float[] bmax)
{
bool overlap = true;
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
return overlap;
}
public List<ChunkyTriMeshNode> getChunksOverlappingSegment(float[] p, float[] q) {
public List<ChunkyTriMeshNode> getChunksOverlappingSegment(float[] p, float[] q)
{
// Traverse tree
List<ChunkyTriMeshNode> ids = new();
int i = 0;
while (i < nodes.Count) {
while (i < nodes.Count)
{
ChunkyTriMeshNode node = nodes[i];
bool overlap = checkOverlapSegment(p, q, node.bmin, node.bmax);
bool isLeafNode = node.i >= 0;
if (isLeafNode && overlap) {
if (isLeafNode && overlap)
{
ids.Add(node);
}
if (overlap || isLeafNode) {
if (overlap || isLeafNode)
{
i++;
} else {
}
else
{
i = -node.i;
}
}
return ids;
}
private bool checkOverlapSegment(float[] p, float[] q, float[] bmin, float[] bmax) {
private bool checkOverlapSegment(float[] p, float[] q, float[] bmin, float[] bmax)
{
float EPSILON = 1e-6f;
float tmin = 0;
@ -235,21 +285,27 @@ public class ChunkyTriMesh {
d[0] = q[0] - p[0];
d[1] = q[1] - p[1];
for (int i = 0; i < 2; i++) {
if (Math.Abs(d[i]) < EPSILON) {
for (int i = 0; i < 2; i++)
{
if (Math.Abs(d[i]) < EPSILON)
{
// Ray is parallel to slab. No hit if origin not within slab
if (p[i] < bmin[i] || p[i] > bmax[i])
return false;
} else {
}
else
{
// Compute intersection t value of ray with near and far plane of slab
float ood = 1.0f / d[i];
float t1 = (bmin[i] - p[i]) * ood;
float t2 = (bmax[i] - p[i]) * ood;
if (t1 > t2) {
if (t1 > t2)
{
float tmp = t1;
t1 = t2;
t2 = tmp;
}
if (t1 > tmin)
tmin = t1;
if (t2 < tmax)
@ -258,7 +314,7 @@ public class ChunkyTriMesh {
return false;
}
}
return true;
}
}

View File

@ -26,8 +26,8 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Recast.Demo.Geom;
public class DemoInputGeomProvider : InputGeomProvider {
public class DemoInputGeomProvider : InputGeomProvider
{
public readonly float[] vertices;
public readonly int[] faces;
public readonly float[] normals;
@ -38,26 +38,34 @@ public class DemoInputGeomProvider : InputGeomProvider {
private readonly ChunkyTriMesh chunkyTriMesh;
public DemoInputGeomProvider(List<float> vertexPositions, List<int> meshFaces) :
this(mapVertices(vertexPositions), mapFaces(meshFaces)) {
this(mapVertices(vertexPositions), mapFaces(meshFaces))
{
}
private static int[] mapFaces(List<int> meshFaces) {
private static int[] mapFaces(List<int> meshFaces)
{
int[] faces = new int[meshFaces.Count];
for (int i = 0; i < faces.Length; i++) {
for (int i = 0; i < faces.Length; i++)
{
faces[i] = meshFaces[i];
}
return faces;
}
private static float[] mapVertices(List<float> vertexPositions) {
private static float[] mapVertices(List<float> vertexPositions)
{
float[] vertices = new float[vertexPositions.Count];
for (int i = 0; i < vertices.Length; i++) {
for (int i = 0; i < vertices.Length; i++)
{
vertices[i] = vertexPositions[i];
}
return vertices;
}
public DemoInputGeomProvider(float[] vertices, int[] faces) {
public DemoInputGeomProvider(float[] vertices, int[] faces)
{
this.vertices = vertices;
this.faces = faces;
normals = new float[faces.Length];
@ -66,36 +74,45 @@ public class DemoInputGeomProvider : InputGeomProvider {
bmax = new float[3];
RecastVectors.copy(bmin, vertices, 0);
RecastVectors.copy(bmax, vertices, 0);
for (int i = 1; i < vertices.Length / 3; i++) {
for (int i = 1; i < vertices.Length / 3; i++)
{
RecastVectors.min(bmin, vertices, i * 3);
RecastVectors.max(bmax, vertices, i * 3);
}
chunkyTriMesh = new ChunkyTriMesh(vertices, faces, faces.Length / 3, 256);
}
public float[] getMeshBoundsMin() {
public float[] getMeshBoundsMin()
{
return bmin;
}
public float[] getMeshBoundsMax() {
public float[] getMeshBoundsMax()
{
return bmax;
}
public void calculateNormals() {
for (int i = 0; i < faces.Length; i += 3) {
public void calculateNormals()
{
for (int i = 0; i < faces.Length; i += 3)
{
int v0 = faces[i] * 3;
int v1 = faces[i + 1] * 3;
int v2 = faces[i + 2] * 3;
float[] e0 = new float[3], e1 = new float[3];
for (int j = 0; j < 3; ++j) {
for (int j = 0; j < 3; ++j)
{
e0[j] = vertices[v1 + j] - vertices[v0 + j];
e1[j] = vertices[v2 + j] - vertices[v0 + j];
}
normals[i] = e0[1] * e1[2] - e0[2] * e1[1];
normals[i + 1] = e0[2] * e1[0] - e0[0] * e1[2];
normals[i + 2] = e0[0] * e1[1] - e0[1] * e1[0];
float d = (float)Math.Sqrt(normals[i] * normals[i] + normals[i + 1] * normals[i + 1] + normals[i + 2] * normals[i + 2]);
if (d > 0) {
if (d > 0)
{
d = 1.0f / d;
normals[i] *= d;
normals[i + 1] *= d;
@ -104,34 +121,41 @@ public class DemoInputGeomProvider : InputGeomProvider {
}
}
public IList<ConvexVolume> convexVolumes() {
public IList<ConvexVolume> convexVolumes()
{
return _convexVolumes;
}
public IEnumerable<TriMesh> meshes() {
public IEnumerable<TriMesh> meshes()
{
return ImmutableArray.Create(new TriMesh(vertices, faces));
}
public List<DemoOffMeshConnection> getOffMeshConnections() {
public List<DemoOffMeshConnection> getOffMeshConnections()
{
return offMeshConnections;
}
public void addOffMeshConnection(float[] start, float[] end, float radius, bool bidir, int area, int flags) {
public void addOffMeshConnection(float[] start, float[] end, float radius, bool bidir, int area, int flags)
{
offMeshConnections.Add(new DemoOffMeshConnection(start, end, radius, bidir, area, flags));
}
public void removeOffMeshConnections(Predicate<DemoOffMeshConnection> filter) {
public void removeOffMeshConnections(Predicate<DemoOffMeshConnection> filter)
{
//offMeshConnections.retainAll(offMeshConnections.stream().filter(c -> !filter.test(c)).collect(toList()));
offMeshConnections.RemoveAll(filter); // TODO : 확인 필요
}
public float? raycastMesh(float[] src, float[] dst) {
public float? raycastMesh(float[] src, float[] dst)
{
// Prune hit ray.
float[] btminmax = Intersections.intersectSegmentAABB(src, dst, bmin, bmax);
if (null == btminmax) {
if (null == btminmax)
{
return null;
}
float btmin = btminmax[0];
float btmax = btminmax[1];
float[] p = new float[2], q = new float[2];
@ -141,26 +165,41 @@ public class DemoInputGeomProvider : InputGeomProvider {
q[1] = src[2] + (dst[2] - src[2]) * btmax;
List<ChunkyTriMeshNode> chunks = chunkyTriMesh.getChunksOverlappingSegment(p, q);
if (0 == chunks.Count) {
if (0 == chunks.Count)
{
return null;
}
float tmin = 1.0f;
bool hit = false;
foreach (ChunkyTriMeshNode chunk in chunks) {
foreach (ChunkyTriMeshNode chunk in chunks)
{
int[] tris = chunk.tris;
for (int j = 0; j < chunk.tris.Length; j += 3) {
float[] v1 = new float[] { vertices[tris[j] * 3], vertices[tris[j] * 3 + 1],
vertices[tris[j] * 3 + 2] };
float[] v2 = new float[] { vertices[tris[j + 1] * 3], vertices[tris[j + 1] * 3 + 1],
vertices[tris[j + 1] * 3 + 2] };
float[] v3 = new float[] { vertices[tris[j + 2] * 3], vertices[tris[j + 2] * 3 + 1],
vertices[tris[j + 2] * 3 + 2] };
for (int j = 0; j < chunk.tris.Length; j += 3)
{
float[] v1 = new float[]
{
vertices[tris[j] * 3], vertices[tris[j] * 3 + 1],
vertices[tris[j] * 3 + 2]
};
float[] v2 = new float[]
{
vertices[tris[j + 1] * 3], vertices[tris[j + 1] * 3 + 1],
vertices[tris[j + 1] * 3 + 2]
};
float[] v3 = new float[]
{
vertices[tris[j + 2] * 3], vertices[tris[j + 2] * 3 + 1],
vertices[tris[j + 2] * 3 + 2]
};
float? t = Intersections.intersectSegmentTriangle(src, dst, v1, v2, v3);
if (null != t) {
if (t.Value < tmin) {
if (null != t)
{
if (t.Value < tmin)
{
tmin = t.Value;
}
hit = true;
}
}
@ -170,7 +209,8 @@ public class DemoInputGeomProvider : InputGeomProvider {
}
public void addConvexVolume(float[] verts, float minh, float maxh, AreaModification areaMod) {
public void addConvexVolume(float[] verts, float minh, float maxh, AreaModification areaMod)
{
ConvexVolume volume = new ConvexVolume();
volume.verts = verts;
volume.hmin = minh;
@ -179,8 +219,8 @@ public class DemoInputGeomProvider : InputGeomProvider {
_convexVolumes.Add(volume);
}
public void clearConvexVolumes() {
public void clearConvexVolumes()
{
_convexVolumes.Clear();
}
}

View File

@ -17,17 +17,19 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Recast.Demo.Geom;
public class DemoOffMeshConnection {
public class DemoOffMeshConnection
{
public readonly float[] verts;
public readonly float radius;
public readonly bool bidir;
public readonly int area;
public readonly int flags;
public DemoOffMeshConnection(float[] start, float[] end, float radius, bool bidir, int area, int flags) {
public DemoOffMeshConnection(float[] start, float[] end, float radius, bool bidir, int area, int flags)
{
verts = new float[6];
verts[0] = start[0];
verts[1] = start[1];
@ -40,5 +42,4 @@ public class DemoOffMeshConnection {
this.area = area;
this.flags = flags;
}
}

View File

@ -22,9 +22,10 @@ using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Recast.Demo.Geom;
public class Intersections {
public static float? intersectSegmentTriangle(float[] sp, float[] sq, float[] a, float[] b, float[] c) {
public class Intersections
{
public static float? intersectSegmentTriangle(float[] sp, float[] sq, float[] a, float[] b, float[] c)
{
float v, w;
float[] ab = vSub(b, a);
float[] ac = vSub(c, a);
@ -37,7 +38,8 @@ public class Intersections {
// Compute denominator d. If d <= 0, segment is parallel to or points
// away from triangle, so exit early
float d = DemoMath.vDot(qp, norm);
if (d <= 0.0f) {
if (d <= 0.0f)
{
return null;
}
@ -46,21 +48,27 @@ public class Intersections {
// dividing by d until intersection has been found to pierce triangle
float[] ap = vSub(sp, a);
float t = DemoMath.vDot(ap, norm);
if (t < 0.0f) {
if (t < 0.0f)
{
return null;
}
if (t > d) {
if (t > d)
{
return null; // For segment; exclude this code line for a ray test
}
// Compute barycentric coordinate components and test if within bounds
float[] e = DemoMath.vCross(qp, ap);
v = DemoMath.vDot(ac, e);
if (v < 0.0f || v > d) {
if (v < 0.0f || v > d)
{
return null;
}
w = -DemoMath.vDot(ab, e);
if (w < 0.0f || v + w > d) {
if (w < 0.0f || v + w > d)
{
return null;
}
@ -70,8 +78,8 @@ public class Intersections {
return t;
}
public static float[] intersectSegmentAABB(float[] sp, float[] sq, float[] amin, float[] amax) {
public static float[] intersectSegmentAABB(float[] sp, float[] sq, float[] amin, float[] amax)
{
float EPS = 1e-6f;
float[] d = new float[3];
@ -81,27 +89,39 @@ public class Intersections {
float tmin = 0.0f;
float tmax = 1.0f;
for (int i = 0; i < 3; i++) {
if (Math.Abs(d[i]) < EPS) {
if (sp[i] < amin[i] || sp[i] > amax[i]) {
for (int i = 0; i < 3; i++)
{
if (Math.Abs(d[i]) < EPS)
{
if (sp[i] < amin[i] || sp[i] > amax[i])
{
return null;
}
} else {
}
else
{
float ood = 1.0f / d[i];
float t1 = (amin[i] - sp[i]) * ood;
float t2 = (amax[i] - sp[i]) * ood;
if (t1 > t2) {
if (t1 > t2)
{
float tmp = t1;
t1 = t2;
t2 = tmp;
}
if (t1 > tmin) {
if (t1 > tmin)
{
tmin = t1;
}
if (t2 < tmax) {
if (t2 < tmax)
{
tmax = t2;
}
if (tmin > tmax) {
if (tmin > tmax)
{
return null;
}
}
@ -109,5 +129,4 @@ public class Intersections {
return new float[] { tmin, tmax };
}
}

View File

@ -24,14 +24,18 @@ namespace DotRecast.Recast.Demo.Geom;
/**
* Simple helper to find an intersection between a ray and a nav mesh
*/
public class NavMeshRaycast {
public static float? raycast(NavMesh mesh, float[] src, float[]dst) {
for (int t = 0; t < mesh.getMaxTiles(); ++t) {
public class NavMeshRaycast
{
public static float? raycast(NavMesh mesh, float[] src, float[] dst)
{
for (int t = 0; t < mesh.getMaxTiles(); ++t)
{
MeshTile tile = mesh.getTile(t);
if (tile != null && tile.data != null) {
if (tile != null && tile.data != null)
{
float? intersection = raycast(tile, src, dst);
if (null != intersection) {
if (null != intersection)
{
return intersection;
}
}
@ -40,36 +44,50 @@ public class NavMeshRaycast {
return null;
}
private static float? raycast(MeshTile tile, float[] sp, float[]sq) {
for (int i = 0; i < tile.data.header.polyCount; ++i) {
private static float? raycast(MeshTile tile, float[] sp, float[] sq)
{
for (int i = 0; i < tile.data.header.polyCount; ++i)
{
Poly p = tile.data.polys[i];
if (p.getType() == Poly.DT_POLYTYPE_OFFMESH_CONNECTION) {
if (p.getType() == Poly.DT_POLYTYPE_OFFMESH_CONNECTION)
{
continue;
}
PolyDetail pd = tile.data.detailMeshes[i];
if (pd != null) {
if (pd != null)
{
float[][] verts = ArrayUtils.Of<float>(3, 3);
for (int j = 0; j < pd.triCount; ++j) {
for (int j = 0; j < pd.triCount; ++j)
{
int t = (pd.triBase + j) * 4;
for (int k = 0; k < 3; ++k) {
for (int k = 0; k < 3; ++k)
{
int v = tile.data.detailTris[t + k];
if (v < p.vertCount) {
if (v < p.vertCount)
{
verts[k][0] = tile.data.verts[p.verts[v] * 3];
verts[k][1] = tile.data.verts[p.verts[v] * 3 + 1];
verts[k][2] = tile.data.verts[p.verts[v] * 3 + 2];
} else {
}
else
{
verts[k][0] = tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3];
verts[k][1] = tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 1];
verts[k][2] = tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 2];
}
}
float? intersection = Intersections.intersectSegmentTriangle(sp, sq, verts[0], verts[1], verts[2]);
if (null != intersection) {
if (null != intersection)
{
return intersection;
}
}
} else {
}
else
{
// FIXME: Use Poly if PolyDetail is unavailable
}
}

View File

@ -21,15 +21,19 @@ using DotRecast.Detour;
namespace DotRecast.Recast.Demo.Geom;
public class NavMeshUtils {
public static float[][] getNavMeshBounds(NavMesh mesh) {
public class NavMeshUtils
{
public static float[][] getNavMeshBounds(NavMesh mesh)
{
float[] bmin = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity };
float[] bmax = new float[] { float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity };
for (int t = 0; t < mesh.getMaxTiles(); ++t) {
for (int t = 0; t < mesh.getMaxTiles(); ++t)
{
MeshTile tile = mesh.getTile(t);
if (tile != null && tile.data != null) {
for (int i = 0; i < tile.data.verts.Length; i += 3) {
if (tile != null && tile.data != null)
{
for (int i = 0; i < tile.data.verts.Length; i += 3)
{
bmin[0] = Math.Min(bmin[0], tile.data.verts[i]);
bmin[1] = Math.Min(bmin[1], tile.data.verts[i + 1]);
bmin[2] = Math.Min(bmin[2], tile.data.verts[i + 2]);
@ -39,6 +43,7 @@ public class NavMeshUtils {
}
}
}
return new float[][] { bmin, bmax };
}
}

View File

@ -21,13 +21,17 @@ using DotRecast.Core;
namespace DotRecast.Recast.Demo.Geom;
public class PolyMeshRaycast {
public static float? raycast(IList<RecastBuilderResult> results, float[] src, float[] dst) {
foreach (RecastBuilderResult result in results) {
if (result.getMeshDetail() != null) {
public class PolyMeshRaycast
{
public static float? raycast(IList<RecastBuilderResult> results, float[] src, float[] dst)
{
foreach (RecastBuilderResult result in results)
{
if (result.getMeshDetail() != null)
{
float? intersection = raycast(result.getMesh(), result.getMeshDetail(), src, dst);
if (null != intersection) {
if (null != intersection)
{
return intersection;
}
}
@ -36,29 +40,38 @@ public class PolyMeshRaycast {
return null;
}
private static float? raycast(PolyMesh poly, PolyMeshDetail meshDetail, float[] sp, float[] sq) {
if (meshDetail != null) {
for (int i = 0; i < meshDetail.nmeshes; ++i) {
private static float? raycast(PolyMesh poly, PolyMeshDetail meshDetail, float[] sp, float[] sq)
{
if (meshDetail != null)
{
for (int i = 0; i < meshDetail.nmeshes; ++i)
{
int m = i * 4;
int bverts = meshDetail.meshes[m];
int btris = meshDetail.meshes[m + 2];
int ntris = meshDetail.meshes[m + 3];
int verts = bverts * 3;
int tris = btris * 4;
for (int j = 0; j < ntris; ++j) {
for (int j = 0; j < ntris; ++j)
{
float[][] vs = ArrayUtils.Of<float>(3, 3);
for (int k = 0; k < 3; ++k) {
for (int k = 0; k < 3; ++k)
{
vs[k][0] = meshDetail.verts[verts + meshDetail.tris[tris + j * 4 + k] * 3];
vs[k][1] = meshDetail.verts[verts + meshDetail.tris[tris + j * 4 + k] * 3 + 1];
vs[k][2] = meshDetail.verts[verts + meshDetail.tris[tris + j * 4 + k] * 3 + 2];
}
float? intersection = Intersections.intersectSegmentTriangle(sp, sq, vs[0], vs[1], vs[2]);
if (null != intersection) {
if (null != intersection)
{
return intersection;
}
}
}
} else {
}
else
{
// TODO: check PolyMesh instead
}

View File

@ -26,8 +26,8 @@ using DotRecast.Recast.Demo.Settings;
namespace DotRecast.Recast.Demo;
public class Sample {
public class Sample
{
private DemoInputGeomProvider inputGeom;
private NavMesh navMesh;
private NavMeshQuery navMeshQuery;
@ -36,7 +36,8 @@ public class Sample {
private bool changed;
public Sample(DemoInputGeomProvider inputGeom, IList<RecastBuilderResult> recastResults, NavMesh navMesh,
RcSettingsView rcSettingsView, RecastDebugDraw debugDraw) {
RcSettingsView rcSettingsView, RecastDebugDraw debugDraw)
{
this.inputGeom = inputGeom;
this.recastResults = recastResults;
this.navMesh = navMesh;
@ -45,39 +46,48 @@ public class Sample {
changed = true;
}
private void setQuery(NavMesh navMesh) {
private void setQuery(NavMesh navMesh)
{
navMeshQuery = navMesh != null ? new NavMeshQuery(navMesh) : null;
}
public DemoInputGeomProvider getInputGeom() {
public DemoInputGeomProvider getInputGeom()
{
return inputGeom;
}
public IList<RecastBuilderResult> getRecastResults() {
public IList<RecastBuilderResult> getRecastResults()
{
return recastResults;
}
public NavMesh getNavMesh() {
public NavMesh getNavMesh()
{
return navMesh;
}
public RcSettingsView getSettingsUI() {
public RcSettingsView getSettingsUI()
{
return _rcSettingsView;
}
public NavMeshQuery getNavMeshQuery() {
public NavMeshQuery getNavMeshQuery()
{
return navMeshQuery;
}
public bool isChanged() {
public bool isChanged()
{
return changed;
}
public void setChanged(bool changed) {
public void setChanged(bool changed)
{
this.changed = changed;
}
public void update(DemoInputGeomProvider geom, IList<RecastBuilderResult> recastResults, NavMesh navMesh) {
public void update(DemoInputGeomProvider geom, IList<RecastBuilderResult> recastResults, NavMesh navMesh)
{
inputGeom = geom;
this.recastResults = recastResults;
this.navMesh = navMesh;

View File

@ -21,19 +21,17 @@ freely, subject to the following restrictions:
using System;
using System.Collections.Generic;
using Silk.NET.Windowing;
using DotRecast.Core;
using DotRecast.Recast.Demo.Builder;
using DotRecast.Recast.Demo.Draw;
using DotRecast.Recast.Demo.Geom;
using static DotRecast.Recast.Demo.Draw.DebugDraw;
using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives;
namespace DotRecast.Recast.Demo.Tools;
public class ConvexVolumeTool : Tool {
public class ConvexVolumeTool : Tool
{
private Sample sample;
private AreaModification areaType = SampleAreaModifications.SAMPLE_AREAMOD_GRASS;
private readonly float[] boxHeight = new[] { 6f };
@ -42,105 +40,137 @@ public class ConvexVolumeTool : Tool {
private readonly List<float> pts = new();
private readonly List<int> hull = new();
public override void setSample(Sample m_sample) {
public override void setSample(Sample m_sample)
{
sample = m_sample;
}
public override void handleClick(float[] s, float[] p, bool shift) {
public override void handleClick(float[] s, float[] p, bool shift)
{
DemoInputGeomProvider geom = sample.getInputGeom();
if (geom == null) {
if (geom == null)
{
return;
}
if (shift) {
if (shift)
{
// Delete
int nearestIndex = -1;
IList<ConvexVolume> vols = geom.convexVolumes();
for (int i = 0; i < vols.Count; ++i) {
for (int i = 0; i < vols.Count; ++i)
{
if (PolyUtils.pointInPoly(vols[i].verts, p) && p[1] >= vols[i].hmin
&& p[1] <= vols[i].hmax) {
&& p[1] <= vols[i].hmax)
{
nearestIndex = i;
}
}
// If end point close enough, delete it.
if (nearestIndex != -1) {
if (nearestIndex != -1)
{
geom.convexVolumes().RemoveAt(nearestIndex);
}
} else {
}
else
{
// Create
// If clicked on that last pt, create the shape.
if (pts.Count > 0 && DemoMath.vDistSqr(p,
new float[] { pts[pts.Count - 3], pts[pts.Count - 2], pts[pts.Count - 1] },
0) < 0.2f * 0.2f) {
if (hull.Count > 2) {
0) < 0.2f * 0.2f)
{
if (hull.Count > 2)
{
// Create shape.
float[] verts = new float[hull.Count * 3];
for (int i = 0; i < hull.Count; ++i) {
for (int i = 0; i < hull.Count; ++i)
{
verts[i * 3] = pts[hull[i] * 3];
verts[i * 3 + 1] = pts[hull[i] * 3 + 1];
verts[i * 3 + 2] = pts[hull[i] * 3 + 2];
}
float minh = float.MaxValue, maxh = 0;
for (int i = 0; i < hull.Count; ++i) {
for (int i = 0; i < hull.Count; ++i)
{
minh = Math.Min(minh, verts[i * 3 + 1]);
}
minh -= boxDescent[0];
maxh = minh + boxHeight[0];
if (polyOffset[0] > 0.01f) {
if (polyOffset[0] > 0.01f)
{
float[] offset = new float[verts.Length * 2];
int noffset = PolyUtils.offsetPoly(verts, hull.Count, polyOffset[0], offset,
offset.Length);
if (noffset > 0) {
if (noffset > 0)
{
geom.addConvexVolume(ArrayUtils.CopyOf(offset, 0, noffset * 3), minh, maxh, areaType);
}
} else {
}
else
{
geom.addConvexVolume(verts, minh, maxh, areaType);
}
}
pts.Clear();
hull.Clear();
} else {
}
else
{
// Add new point
pts.Add(p[0]);
pts.Add(p[1]);
pts.Add(p[2]);
// Update hull.
if (pts.Count > 3) {
if (pts.Count > 3)
{
hull.Clear();
hull.AddRange(ConvexUtils.convexhull(pts));
} else {
}
else
{
hull.Clear();
}
}
}
}
public override void handleRender(NavMeshRenderer renderer) {
public override void handleRender(NavMeshRenderer renderer)
{
RecastDebugDraw dd = renderer.getDebugDraw();
// Find height extent of the shape.
float minh = float.MaxValue, maxh = 0;
for (int i = 0; i < pts.Count; i += 3) {
for (int i = 0; i < pts.Count; i += 3)
{
minh = Math.Min(minh, pts[i + 1]);
}
minh -= boxDescent[0];
maxh = minh + boxHeight[0];
dd.begin(POINTS, 4.0f);
for (int i = 0; i < pts.Count; i += 3) {
for (int i = 0; i < pts.Count; i += 3)
{
int col = duRGBA(255, 255, 255, 255);
if (i == pts.Count - 3) {
if (i == pts.Count - 3)
{
col = duRGBA(240, 32, 16, 255);
}
dd.vertex(pts[i + 0], pts[i + 1] + 0.1f, pts[i + 2], col);
}
dd.end();
dd.begin(LINES, 2.0f);
for (int i = 0, j = hull.Count - 1; i < hull.Count; j = i++) {
for (int i = 0, j = hull.Count - 1; i < hull.Count; j = i++)
{
int vi = hull[j] * 3;
int vj = hull[i] * 3;
dd.vertex(pts[vj + 0], minh, pts[vj + 2], duRGBA(255, 255, 255, 64));
@ -150,10 +180,12 @@ public class ConvexVolumeTool : Tool {
dd.vertex(pts[vj + 0], minh, pts[vj + 2], duRGBA(255, 255, 255, 64));
dd.vertex(pts[vj + 0], maxh, pts[vj + 2], duRGBA(255, 255, 255, 64));
}
dd.end();
}
public override void layout(IWindow ctx) {
public override void layout(IWindow ctx)
{
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Shape Height", 0.1f, boxHeight, 20f, 0.1f, 0.1f);
// nk_layout_row_dynamic(ctx, 20, 1);
@ -194,12 +226,13 @@ public class ConvexVolumeTool : Tool {
// }
}
public override string getName() {
public override string getName()
{
return "Create Convex Volumes";
}
public override void handleUpdate(float dt) {
public override void handleUpdate(float dt)
{
// TODO Auto-generated method stub
}
}

View File

@ -25,13 +25,12 @@ using DotRecast.Detour.Crowd;
using DotRecast.Recast.Demo.Builder;
using DotRecast.Recast.Demo.Draw;
using Silk.NET.Windowing;
using static DotRecast.Recast.Demo.Draw.DebugDraw;
namespace DotRecast.Recast.Demo.Tools;
public class CrowdProfilingTool {
public class CrowdProfilingTool
{
private readonly Func<CrowdAgentParams> agentParamsSupplier;
private readonly int[] expandSimOptions = new[] { 1 };
private readonly int[] expandCrowdOptions = new[] { 1 };
@ -50,11 +49,13 @@ public class CrowdProfilingTool {
private readonly List<FindRandomPointResult> zones = new();
private long crowdUpdateTime;
public CrowdProfilingTool(Func<CrowdAgentParams> agentParamsSupplier) {
public CrowdProfilingTool(Func<CrowdAgentParams> agentParamsSupplier)
{
this.agentParamsSupplier = agentParamsSupplier;
}
public void layout(IWindow ctx) {
public void layout(IWindow ctx)
{
// nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1);
// if (nk_tree_state_push(ctx, 0, "Simulation Options", expandSimOptions)) {
@ -141,43 +142,58 @@ public class CrowdProfilingTool {
// }
}
private float[] getMobPosition(NavMeshQuery navquery, QueryFilter filter, float[] pos) {
private float[] getMobPosition(NavMeshQuery navquery, QueryFilter filter, float[] pos)
{
Result<FindRandomPointResult> result = navquery.findRandomPoint(filter, rnd);
if (result.succeeded()) {
if (result.succeeded())
{
pos = result.result.getRandomPt();
}
return pos;
}
private float[] getVillagerPosition(NavMeshQuery navquery, QueryFilter filter, float[] pos) {
if (0 < zones.Count) {
private float[] getVillagerPosition(NavMeshQuery navquery, QueryFilter filter, float[] pos)
{
if (0 < zones.Count)
{
int zone = (int)(rnd.frand() * zones.Count);
Result<FindRandomPointResult> result = navquery.findRandomPointWithinCircle(zones[zone].getRandomRef(),
zones[zone].getRandomPt(), zoneRadius[0], filter, rnd);
if (result.succeeded()) {
if (result.succeeded())
{
pos = result.result.getRandomPt();
}
}
return pos;
}
private void createZones() {
private void createZones()
{
zones.Clear();
QueryFilter filter = new DefaultQueryFilter();
NavMeshQuery navquery = new NavMeshQuery(navMesh);
for (int i = 0; i < numberOfZones[0]; i++) {
for (int i = 0; i < numberOfZones[0]; i++)
{
float zoneSeparation = zoneRadius[0] * zoneRadius[0] * 16;
for (int k = 0; k < 100; k++) {
for (int k = 0; k < 100; k++)
{
Result<FindRandomPointResult> result = navquery.findRandomPoint(filter, rnd);
if (result.succeeded()) {
if (result.succeeded())
{
bool valid = true;
foreach (FindRandomPointResult zone in zones) {
if (DemoMath.vDistSqr(zone.getRandomPt(), result.result.getRandomPt(), 0) < zoneSeparation) {
foreach (FindRandomPointResult zone in zones)
{
if (DemoMath.vDistSqr(zone.getRandomPt(), result.result.getRandomPt(), 0) < zoneSeparation)
{
valid = false;
break;
}
}
if (valid) {
if (valid)
{
zones.Add(result.result);
break;
}
@ -186,7 +202,8 @@ public class CrowdProfilingTool {
}
}
private void createCrowd() {
private void createCrowd()
{
crowd = new Crowd(config, navMesh, __ => new DefaultQueryFilter(SampleAreaModifications.SAMPLE_POLYFLAGS_ALL,
SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED, new float[] { 1f, 10f, 1f, 1f, 2f, 1.5f }));
@ -217,22 +234,28 @@ public class CrowdProfilingTool {
crowd.setObstacleAvoidanceParams(3, option);
}
public void update(float dt) {
public void update(float dt)
{
long startTime = Stopwatch.GetTimestamp();
if (crowd != null) {
if (crowd != null)
{
crowd.config().pathQueueSize = pathQueueSize[0];
crowd.config().maxFindPathIterations = maxIterations[0];
crowd.update(dt, null);
}
long endTime = Stopwatch.GetTimestamp();
if (crowd != null) {
long endTime = Stopwatch.GetTimestamp();
if (crowd != null)
{
NavMeshQuery navquery = new NavMeshQuery(navMesh);
QueryFilter filter = new DefaultQueryFilter();
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
if (needsNewTarget(ag)) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
if (needsNewTarget(ag))
{
AgentData agentData = (AgentData)ag.option.userData;
switch (agentData.type) {
switch (agentData.type)
{
case AgentType.MOB:
moveMob(navquery, filter, ag, agentData);
break;
@ -246,79 +269,102 @@ public class CrowdProfilingTool {
}
}
}
crowdUpdateTime = (endTime - startTime) / 1_000_000;
}
private void moveMob(NavMeshQuery navquery, QueryFilter filter, CrowdAgent ag, AgentData agentData) {
private void moveMob(NavMeshQuery navquery, QueryFilter filter, CrowdAgent ag, AgentData agentData)
{
// Move somewhere
Result<FindNearestPolyResult> nearestPoly = navquery.findNearestPoly(ag.npos, crowd.getQueryExtents(), filter);
if (nearestPoly.succeeded()) {
if (nearestPoly.succeeded())
{
Result<FindRandomPointResult> result = navquery.findRandomPointAroundCircle(nearestPoly.result.getNearestRef(),
agentData.home, zoneRadius[0] * 2f, filter, rnd);
if (result.succeeded()) {
if (result.succeeded())
{
crowd.requestMoveTarget(ag, result.result.getRandomRef(), result.result.getRandomPt());
}
}
}
private void moveVillager(NavMeshQuery navquery, QueryFilter filter, CrowdAgent ag, AgentData agentData) {
private void moveVillager(NavMeshQuery navquery, QueryFilter filter, CrowdAgent ag, AgentData agentData)
{
// Move somewhere close
Result<FindNearestPolyResult> nearestPoly = navquery.findNearestPoly(ag.npos, crowd.getQueryExtents(), filter);
if (nearestPoly.succeeded()) {
if (nearestPoly.succeeded())
{
Result<FindRandomPointResult> result = navquery.findRandomPointAroundCircle(nearestPoly.result.getNearestRef(),
agentData.home, zoneRadius[0] * 0.2f, filter, rnd);
if (result.succeeded()) {
if (result.succeeded())
{
crowd.requestMoveTarget(ag, result.result.getRandomRef(), result.result.getRandomPt());
}
}
}
private void moveTraveller(NavMeshQuery navquery, QueryFilter filter, CrowdAgent ag, AgentData agentData) {
private void moveTraveller(NavMeshQuery navquery, QueryFilter filter, CrowdAgent ag, AgentData agentData)
{
// Move to another zone
List<FindRandomPointResult> potentialTargets = new();
foreach (FindRandomPointResult zone in zones) {
if (DemoMath.vDistSqr(zone.getRandomPt(), ag.npos, 0) > zoneRadius[0] * zoneRadius[0]) {
foreach (FindRandomPointResult zone in zones)
{
if (DemoMath.vDistSqr(zone.getRandomPt(), ag.npos, 0) > zoneRadius[0] * zoneRadius[0])
{
potentialTargets.Add(zone);
}
}
if (0 < potentialTargets.Count) {
if (0 < potentialTargets.Count)
{
potentialTargets.Shuffle();
crowd.requestMoveTarget(ag, potentialTargets[0].getRandomRef(), potentialTargets[0].getRandomPt());
}
}
private bool needsNewTarget(CrowdAgent ag) {
private bool needsNewTarget(CrowdAgent ag)
{
if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_NONE
|| ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_FAILED) {
|| ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_FAILED)
{
return true;
}
if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_VALID) {
if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_VALID)
{
float dx = ag.targetPos[0] - ag.npos[0];
float dy = ag.targetPos[1] - ag.npos[1];
float dz = ag.targetPos[2] - ag.npos[2];
return dx * dx + dy * dy + dz * dz < 0.3f;
}
return false;
}
public void setup(float maxAgentRadius, NavMesh nav) {
public void setup(float maxAgentRadius, NavMesh nav)
{
navMesh = nav;
if (nav != null) {
if (nav != null)
{
config = new CrowdConfig(maxAgentRadius);
}
}
public void handleRender(NavMeshRenderer renderer) {
public void handleRender(NavMeshRenderer renderer)
{
RecastDebugDraw dd = renderer.getDebugDraw();
dd.depthMask(false);
if (crowd != null) {
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
if (crowd != null)
{
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
float radius = ag.option.radius;
float[] pos = ag.npos;
dd.debugDrawCircle(pos[0], pos[1], pos[2], radius, duRGBA(0, 0, 0, 32), 2.0f);
}
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
AgentData agentData = (AgentData)ag.option.userData;
float height = ag.option.height;
@ -326,12 +372,16 @@ public class CrowdProfilingTool {
float[] pos = ag.npos;
int col = duRGBA(220, 220, 220, 128);
if (agentData.type == AgentType.TRAVELLER) {
if (agentData.type == AgentType.TRAVELLER)
{
col = duRGBA(100, 160, 100, 128);
}
if (agentData.type == AgentType.VILLAGER) {
if (agentData.type == AgentType.VILLAGER)
{
col = duRGBA(120, 80, 160, 128);
}
if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_REQUESTING
|| ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE)
col = duLerpCol(col, duRGBA(255, 255, 32, 128), 128);
@ -350,30 +400,38 @@ public class CrowdProfilingTool {
dd.depthMask(true);
}
private CrowdAgent addAgent(float[] p, AgentType type) {
private CrowdAgent addAgent(float[] p, AgentType type)
{
CrowdAgentParams ap = agentParamsSupplier.Invoke();
ap.userData = new AgentData(type, p);
return crowd.addAgent(p, ap);
}
public enum AgentType {
VILLAGER, TRAVELLER, MOB,
public enum AgentType
{
VILLAGER,
TRAVELLER,
MOB,
}
private class AgentData {
private class AgentData
{
public readonly AgentType type;
public readonly float[] home = new float[3];
public AgentData(AgentType type, float[] home) {
public AgentData(AgentType type, float[] home)
{
this.type = type;
RecastVectors.copy(this.home, home);
}
}
public void updateAgentParams(int updateFlags, int obstacleAvoidanceType, float separationWeight) {
if (crowd != null) {
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
public void updateAgentParams(int updateFlags, int obstacleAvoidanceType, float separationWeight)
{
if (crowd != null)
{
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
CrowdAgentParams option = new CrowdAgentParams();
option.radius = ag.option.radius;
option.height = ag.option.height;

View File

@ -27,16 +27,20 @@ using DotRecast.Detour.Crowd.Tracking;
using DotRecast.Recast.Demo.Builder;
using DotRecast.Recast.Demo.Draw;
using DotRecast.Recast.Demo.Geom;
using static DotRecast.Recast.Demo.Draw.DebugDraw;
using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives;
namespace DotRecast.Recast.Demo.Tools;
public class CrowdTool : Tool {
private enum ToolMode {
CREATE, MOVE_TARGET, SELECT, TOGGLE_POLYS, PROFILING
public class CrowdTool : Tool
{
private enum ToolMode
{
CREATE,
MOVE_TARGET,
SELECT,
TOGGLE_POLYS,
PROFILING
}
private readonly CrowdToolParams toolParams = new CrowdToolParams();
@ -48,7 +52,8 @@ public class CrowdTool : Tool {
private static readonly int AGENT_MAX_TRAIL = 64;
private class AgentTrail {
private class AgentTrail
{
public float[] trail = new float[AGENT_MAX_TRAIL * 3];
public int htrail;
};
@ -59,19 +64,23 @@ public class CrowdTool : Tool {
private ToolMode m_mode = ToolMode.CREATE;
private long crowdUpdateTime;
public CrowdTool() {
public CrowdTool()
{
m_agentDebug.vod = new ObstacleAvoidanceDebugData(2048);
profilingTool = new CrowdProfilingTool(getAgentParams);
}
public override void setSample(Sample psample) {
if (sample != psample) {
public override void setSample(Sample psample)
{
if (sample != psample)
{
sample = psample;
}
NavMesh nav = sample.getNavMesh();
if (nav != null && m_nav != nav) {
if (nav != null && m_nav != nav)
{
m_nav = nav;
CrowdConfig config = new CrowdConfig(sample.getSettingsUI().getAgentRadius());
@ -116,41 +125,60 @@ public class CrowdTool : Tool {
}
}
public override void handleClick(float[] s, float[] p, bool shift) {
if (m_mode == ToolMode.PROFILING) {
public override void handleClick(float[] s, float[] p, bool shift)
{
if (m_mode == ToolMode.PROFILING)
{
return;
}
if (crowd == null) {
if (crowd == null)
{
return;
}
if (m_mode == ToolMode.CREATE) {
if (shift) {
if (m_mode == ToolMode.CREATE)
{
if (shift)
{
// Delete
CrowdAgent ahit = hitTestAgents(s, p);
if (ahit != null) {
if (ahit != null)
{
removeAgent(ahit);
}
} else {
}
else
{
// Add
addAgent(p);
}
} else if (m_mode == ToolMode.MOVE_TARGET) {
}
else if (m_mode == ToolMode.MOVE_TARGET)
{
setMoveTarget(p, shift);
} else if (m_mode == ToolMode.SELECT) {
}
else if (m_mode == ToolMode.SELECT)
{
// Highlight
CrowdAgent ahit = hitTestAgents(s, p);
hilightAgent(ahit);
} else if (m_mode == ToolMode.TOGGLE_POLYS) {
}
else if (m_mode == ToolMode.TOGGLE_POLYS)
{
NavMesh nav = sample.getNavMesh();
NavMeshQuery navquery = sample.getNavMeshQuery();
if (nav != null && navquery != null) {
if (nav != null && navquery != null)
{
QueryFilter filter = new DefaultQueryFilter();
float[] halfExtents = crowd.getQueryExtents();
Result<FindNearestPolyResult> result = navquery.findNearestPoly(p, halfExtents, filter);
long refs = result.result.getNearestRef();
if (refs != 0) {
if (refs != 0)
{
Result<int> flags = nav.getPolyFlags(refs);
if (flags.succeeded()) {
if (flags.succeeded())
{
nav.setPolyFlags(refs, flags.result ^ SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED);
}
}
@ -158,17 +186,21 @@ public class CrowdTool : Tool {
}
}
private void removeAgent(CrowdAgent agent) {
private void removeAgent(CrowdAgent agent)
{
crowd.removeAgent(agent);
if (agent == m_agentDebug.agent) {
if (agent == m_agentDebug.agent)
{
m_agentDebug.agent = null;
}
}
private void addAgent(float[] p) {
private void addAgent(float[] p)
{
CrowdAgentParams ap = getAgentParams();
CrowdAgent ag = crowd.addAgent(p, ap);
if (ag != null) {
if (ag != null)
{
if (m_targetRef != 0)
crowd.requestMoveTarget(ag, m_targetRef, m_targetPos);
@ -178,17 +210,20 @@ public class CrowdTool : Tool {
trail = new AgentTrail();
m_trails.Add(ag.idx, trail);
}
for (int i = 0; i < AGENT_MAX_TRAIL; ++i) {
for (int i = 0; i < AGENT_MAX_TRAIL; ++i)
{
trail.trail[i * 3] = p[0];
trail.trail[i * 3 + 1] = p[1];
trail.trail[i * 3 + 2] = p[2];
}
trail.htrail = 0;
}
}
private CrowdAgentParams getAgentParams() {
private CrowdAgentParams getAgentParams()
{
CrowdAgentParams ap = new CrowdAgentParams();
ap.radius = sample.getSettingsUI().getAgentRadius();
ap.height = sample.getSettingsUI().getAgentHeight();
@ -202,18 +237,21 @@ public class CrowdTool : Tool {
return ap;
}
private CrowdAgent hitTestAgents(float[] s, float[] p) {
private CrowdAgent hitTestAgents(float[] s, float[] p)
{
CrowdAgent isel = null;
float tsel = float.MaxValue;
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
float[] bmin = new float[3], bmax = new float[3];
getAgentBounds(ag, bmin, bmax);
float[] isect = Intersections.intersectSegmentAABB(s, p, bmin, bmax);
if (null != isect) {
if (null != isect)
{
float tmin = isect[0];
if (tmin > 0 && tmin < tsel) {
if (tmin > 0 && tmin < tsel)
{
isel = ag;
tsel = tmin;
}
@ -223,7 +261,8 @@ public class CrowdTool : Tool {
return isel;
}
private void getAgentBounds(CrowdAgent ag, float[] bmin, float[] bmax) {
private void getAgentBounds(CrowdAgent ag, float[] bmin, float[] bmax)
{
float[] p = ag.npos;
float r = ag.option.radius;
float h = ag.option.height;
@ -235,7 +274,8 @@ public class CrowdTool : Tool {
bmax[2] = p[2] + r;
}
private void setMoveTarget(float[] p, bool adjust) {
private void setMoveTarget(float[] p, bool adjust)
{
if (sample == null || crowd == null)
return;
@ -244,65 +284,85 @@ public class CrowdTool : Tool {
QueryFilter filter = crowd.getFilter(0);
float[] halfExtents = crowd.getQueryExtents();
if (adjust) {
if (adjust)
{
// Request velocity
if (m_agentDebug.agent != null) {
if (m_agentDebug.agent != null)
{
float[] vel = calcVel(m_agentDebug.agent.npos, p, m_agentDebug.agent.option.maxSpeed);
crowd.requestMoveVelocity(m_agentDebug.agent, vel);
} else {
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
}
else
{
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
float[] vel = calcVel(ag.npos, p, ag.option.maxSpeed);
crowd.requestMoveVelocity(ag, vel);
}
}
} else {
}
else
{
Result<FindNearestPolyResult> result = navquery.findNearestPoly(p, halfExtents, filter);
m_targetRef = result.result.getNearestRef();
m_targetPos = result.result.getNearestPos();
if (m_agentDebug.agent != null) {
if (m_agentDebug.agent != null)
{
crowd.requestMoveTarget(m_agentDebug.agent, m_targetRef, m_targetPos);
} else {
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
}
else
{
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
crowd.requestMoveTarget(ag, m_targetRef, m_targetPos);
}
}
}
}
private float[] calcVel(float[] pos, float[] tgt, float speed) {
private float[] calcVel(float[] pos, float[] tgt, float speed)
{
float[] vel = DetourCommon.vSub(tgt, pos);
vel[1] = 0.0f;
DetourCommon.vNormalize(vel);
return DetourCommon.vScale(vel, speed);
}
public override void handleRender(NavMeshRenderer renderer) {
if (m_mode == ToolMode.PROFILING) {
public override void handleRender(NavMeshRenderer renderer)
{
if (m_mode == ToolMode.PROFILING)
{
profilingTool.handleRender(renderer);
return;
}
RecastDebugDraw dd = renderer.getDebugDraw();
float rad = sample.getSettingsUI().getAgentRadius();
NavMesh nav = sample.getNavMesh();
if (nav == null || crowd == null)
return;
if (toolParams.m_showNodes && crowd.getPathQueue() != null) {
if (toolParams.m_showNodes && crowd.getPathQueue() != null)
{
// NavMeshQuery navquery = crowd.getPathQueue().getNavQuery();
// if (navquery != null) {
// dd.debugDrawNavMeshNodes(navquery);
// }
}
dd.depthMask(false);
// Draw paths
if (toolParams.m_showPath) {
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
if (toolParams.m_showPath)
{
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
if (!toolParams.m_showDetailAll && ag != m_agentDebug.agent)
continue;
List<long> path = ag.corridor.getPath();
int npath = ag.corridor.getPathCount();
for (int j = 0; j < npath; ++j) {
for (int j = 0; j < npath; ++j)
{
dd.debugDrawNavMeshPoly(nav, path[j], duRGBA(255, 255, 255, 24));
}
}
@ -312,22 +372,27 @@ public class CrowdTool : Tool {
dd.debugDrawCross(m_targetPos[0], m_targetPos[1] + 0.1f, m_targetPos[2], rad, duRGBA(255, 255, 255, 192), 2.0f);
// Occupancy grid.
if (toolParams.m_showGrid) {
if (toolParams.m_showGrid)
{
float gridy = -float.MaxValue;
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
float[] pos = ag.corridor.getPos();
gridy = Math.Max(gridy, pos[1]);
}
gridy += 1.0f;
dd.begin(QUADS);
ProximityGrid grid = crowd.getGrid();
float cs = grid.getCellSize();
foreach (int[] ic in grid.getItemCounts()) {
foreach (int[] ic in grid.getItemCounts())
{
int x = ic[0];
int y = ic[1];
int count = ic[2];
if (count != 0) {
if (count != 0)
{
int col = duRGBA(128, 0, 0, Math.Min(count * 40, 255));
dd.vertex(x * cs, gridy, y * cs, col);
dd.vertex(x * cs, gridy, y * cs + cs, col);
@ -335,12 +400,13 @@ public class CrowdTool : Tool {
dd.vertex(x * cs + cs, gridy, y * cs, col);
}
}
dd.end();
}
// Trail
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
AgentTrail trail = m_trails[ag.idx];
float[] pos = ag.npos;
@ -348,7 +414,8 @@ public class CrowdTool : Tool {
float[] prev = new float[3];
float preva = 1;
DetourCommon.vCopy(prev, pos);
for (int j = 0; j < AGENT_MAX_TRAIL - 1; ++j) {
for (int j = 0; j < AGENT_MAX_TRAIL - 1; ++j)
{
int idx = (trail.htrail + AGENT_MAX_TRAIL - j) % AGENT_MAX_TRAIL;
int v = idx * 3;
float a = 1 - j / (float)AGENT_MAX_TRAIL;
@ -357,29 +424,35 @@ public class CrowdTool : Tool {
preva = a;
DetourCommon.vCopy(prev, trail.trail, v);
}
dd.end();
dd.end();
}
// Corners & co
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
if (toolParams.m_showDetailAll == false && ag != m_agentDebug.agent)
continue;
float radius = ag.option.radius;
float[] pos = ag.npos;
if (toolParams.m_showCorners) {
if (0 < ag.corners.Count) {
if (toolParams.m_showCorners)
{
if (0 < ag.corners.Count)
{
dd.begin(LINES, 2.0f);
for (int j = 0; j < ag.corners.Count; ++j) {
for (int j = 0; j < ag.corners.Count; ++j)
{
float[] va = j == 0 ? pos : ag.corners[j - 1].getPos();
float[] vb = ag.corners[j].getPos();
dd.vertex(va[0], va[1] + radius, va[2], duRGBA(128, 0, 0, 192));
dd.vertex(vb[0], vb[1] + radius, vb[2], duRGBA(128, 0, 0, 192));
}
if ((ag.corners[ag.corners.Count - 1].getFlags()
& NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) {
& NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
{
float[] v = ag.corners[ag.corners.Count - 1].getPos();
dd.vertex(v[0], v[1], v[2], duRGBA(192, 0, 0, 192));
dd.vertex(v[0], v[1] + radius * 2, v[2], duRGBA(192, 0, 0, 192));
@ -387,7 +460,8 @@ public class CrowdTool : Tool {
dd.end();
if (toolParams.m_anticipateTurns) {
if (toolParams.m_anticipateTurns)
{
/* float dvel[3], pos[3];
calcSmoothSteerDirection(ag.pos, ag.cornerVerts, ag.ncorners, dvel);
pos[0] = ag.pos[0] + dvel[0];
@ -411,14 +485,16 @@ public class CrowdTool : Tool {
}
}
if (toolParams.m_showCollisionSegments) {
if (toolParams.m_showCollisionSegments)
{
float[] center = ag.boundary.getCenter();
dd.debugDrawCross(center[0], center[1] + radius, center[2], 0.2f, duRGBA(192, 0, 128, 255), 2.0f);
dd.debugDrawCircle(center[0], center[1] + radius, center[2], ag.option.collisionQueryRange,
duRGBA(192, 0, 128, 128), 2.0f);
dd.begin(LINES, 3.0f);
for (int j = 0; j < ag.boundary.getSegmentCount(); ++j) {
for (int j = 0; j < ag.boundary.getSegmentCount(); ++j)
{
int col = duRGBA(192, 0, 128, 192);
float[] s = ag.boundary.getSegment(j);
float[] s0 = new float[] { s[0], s[1], s[2] };
@ -428,25 +504,31 @@ public class CrowdTool : Tool {
dd.appendArrow(s[0], s[1] + 0.2f, s[2], s[3], s[4] + 0.2f, s[5], 0.0f, 0.3f, col);
}
dd.end();
}
if (toolParams.m_showNeis) {
if (toolParams.m_showNeis)
{
dd.debugDrawCircle(pos[0], pos[1] + radius, pos[2], ag.option.collisionQueryRange, duRGBA(0, 192, 128, 128),
2.0f);
dd.begin(LINES, 2.0f);
for (int j = 0; j < ag.neis.Count; ++j) {
for (int j = 0; j < ag.neis.Count; ++j)
{
CrowdAgent nei = ag.neis[j].agent;
if (nei != null) {
if (nei != null)
{
dd.vertex(pos[0], pos[1] + radius, pos[2], duRGBA(0, 192, 128, 128));
dd.vertex(nei.npos[0], nei.npos[1] + radius, nei.npos[2], duRGBA(0, 192, 128, 128));
}
}
dd.end();
}
if (toolParams.m_showOpt) {
if (toolParams.m_showOpt)
{
dd.begin(LINES, 2.0f);
dd.vertex(m_agentDebug.optStart[0], m_agentDebug.optStart[1] + 0.3f, m_agentDebug.optStart[2],
duRGBA(0, 128, 0, 192));
@ -456,8 +538,8 @@ public class CrowdTool : Tool {
}
// Agent cylinders.
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
float radius = ag.option.radius;
float[] pos = ag.npos;
@ -468,8 +550,8 @@ public class CrowdTool : Tool {
dd.debugDrawCircle(pos[0], pos[1], pos[2], radius, col, 2.0f);
}
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
float height = ag.option.height;
float radius = ag.option.radius;
float[] pos = ag.npos;
@ -489,8 +571,10 @@ public class CrowdTool : Tool {
pos[2] + radius, col);
}
if (toolParams.m_showVO) {
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
if (toolParams.m_showVO)
{
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
if (toolParams.m_showDetailAll == false && ag != m_agentDebug.agent)
continue;
@ -504,7 +588,8 @@ public class CrowdTool : Tool {
dd.debugDrawCircle(dx, dy, dz, ag.option.maxSpeed, duRGBA(255, 255, 255, 64), 2.0f);
dd.begin(QUADS);
for (int j = 0; j < vod.getSampleCount(); ++j) {
for (int j = 0; j < vod.getSampleCount(); ++j)
{
float[] p = vod.getSampleVelocity(j);
float sr = vod.getSampleSize(j);
float pen = vod.getSamplePenalty(j);
@ -516,13 +601,14 @@ public class CrowdTool : Tool {
dd.vertex(dx + p[0] + sr, dy, dz + p[2] + sr, col);
dd.vertex(dx + p[0] + sr, dy, dz + p[2] - sr, col);
}
dd.end();
}
}
// Velocity stuff.
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
float radius = ag.option.radius;
float height = ag.option.height;
float[] pos = ag.npos;
@ -552,15 +638,19 @@ public class CrowdTool : Tool {
dd.depthMask(true);
}
public override void handleUpdate(float dt) {
public override void handleUpdate(float dt)
{
updateTick(dt);
}
private void updateTick(float dt) {
if (m_mode == ToolMode.PROFILING) {
private void updateTick(float dt)
{
if (m_mode == ToolMode.PROFILING)
{
profilingTool.update(dt);
return;
}
if (crowd == null)
return;
NavMesh nav = sample.getNavMesh();
@ -572,7 +662,8 @@ public class CrowdTool : Tool {
long endTime = Stopwatch.GetTimestamp();
// Update agent trails
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
AgentTrail trail = m_trails[ag.idx];
// Update agent movement trail.
trail.htrail = (trail.htrail + 1) % AGENT_MAX_TRAIL;
@ -587,11 +678,13 @@ public class CrowdTool : Tool {
crowdUpdateTime = (endTime - startTime) / 1_000_000;
}
private void hilightAgent(CrowdAgent agent) {
private void hilightAgent(CrowdAgent agent)
{
m_agentDebug.agent = agent;
}
public override void layout(IWindow ctx) {
public override void layout(IWindow ctx)
{
// ToolMode previousToolMode = m_mode;
// nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "Create Agents", m_mode == ToolMode.CREATE)) {
@ -683,14 +776,17 @@ public class CrowdTool : Tool {
// }
}
private void updateAgentParams() {
if (crowd == null) {
private void updateAgentParams()
{
if (crowd == null)
{
return;
}
int updateFlags = getUpdateFlags();
profilingTool.updateAgentParams(updateFlags, toolParams.m_obstacleAvoidanceType[0], toolParams.m_separationWeight[0]);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
CrowdAgentParams option = new CrowdAgentParams();
option.radius = ag.option.radius;
option.height = ag.option.height;
@ -708,28 +804,39 @@ public class CrowdTool : Tool {
}
}
private int getUpdateFlags() {
private int getUpdateFlags()
{
int updateFlags = 0;
if (toolParams.m_anticipateTurns) {
if (toolParams.m_anticipateTurns)
{
updateFlags |= CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS;
}
if (toolParams.m_optimizeVis) {
if (toolParams.m_optimizeVis)
{
updateFlags |= CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS;
}
if (toolParams.m_optimizeTopo) {
if (toolParams.m_optimizeTopo)
{
updateFlags |= CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
}
if (toolParams.m_obstacleAvoidance) {
if (toolParams.m_obstacleAvoidance)
{
updateFlags |= CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
}
if (toolParams.m_separation) {
if (toolParams.m_separation)
{
updateFlags |= CrowdAgentParams.DT_CROWD_SEPARATION;
}
return updateFlags;
}
public override string getName() {
public override string getName()
{
return "Crowd";
}
}

View File

@ -15,11 +15,11 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Recast.Demo.Tools;
public class CrowdToolParams {
public class CrowdToolParams
{
public readonly int[] m_expandSelectedDebugDraw = new[] { 1 };
public bool m_showCorners;
public bool m_showCollisionSegments;

View File

@ -6,9 +6,9 @@ namespace DotRecast.Recast.Demo.Tools;
public static class DemoObjImporter
{
public static DemoInputGeomProvider load(byte[] chunk) {
public static DemoInputGeomProvider load(byte[] chunk)
{
var context = ObjImporter.loadContext(chunk);
return new DemoInputGeomProvider(context.vertexPositions, context.meshFaces);
}
}

View File

@ -30,20 +30,31 @@ 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
public class DynamicUpdateTool : Tool
{
private enum ToolMode
{
BUILD,
COLLIDERS,
RAYCAST
}
private enum ColliderShape {
SPHERE, CAPSULE, BOX, CYLINDER, COMPOSITE, CONVEX, TRIMESH_BRIDGE, TRIMESH_HOUSE
private enum ColliderShape
{
SPHERE,
CAPSULE,
BOX,
CYLINDER,
COMPOSITE,
CONVEX,
TRIMESH_BRIDGE,
TRIMESH_HOUSE
}
private Sample sample;
@ -94,49 +105,78 @@ public class DynamicUpdateTool : Tool {
convexGeom = DemoObjImporter.load(Loader.ToBytes("convex.obj"));
}
public override void setSample(Sample sample) {
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) {
public override void handleClick(float[] s, float[] p, bool shift)
{
if (mode == ToolMode.COLLIDERS)
{
if (!shift)
{
Tuple<Collider, ColliderGizmo> colliderWithGizmo = null;
if (dynaMesh != null) {
if (colliderShape == ColliderShape.SPHERE) {
if (dynaMesh != null)
{
if (colliderShape == ColliderShape.SPHERE)
{
colliderWithGizmo = sphereCollider(p);
} else if (colliderShape == ColliderShape.CAPSULE) {
}
else if (colliderShape == ColliderShape.CAPSULE)
{
colliderWithGizmo = capsuleCollider(p);
} else if (colliderShape == ColliderShape.BOX) {
}
else if (colliderShape == ColliderShape.BOX)
{
colliderWithGizmo = boxCollider(p);
} else if (colliderShape == ColliderShape.CYLINDER) {
}
else if (colliderShape == ColliderShape.CYLINDER)
{
colliderWithGizmo = cylinderCollider(p);
} else if (colliderShape == ColliderShape.COMPOSITE) {
}
else if (colliderShape == ColliderShape.COMPOSITE)
{
colliderWithGizmo = compositeCollider(p);
} else if (colliderShape == ColliderShape.TRIMESH_BRIDGE) {
}
else if (colliderShape == ColliderShape.TRIMESH_BRIDGE)
{
colliderWithGizmo = trimeshBridge(p);
} else if (colliderShape == ColliderShape.TRIMESH_HOUSE) {
}
else if (colliderShape == ColliderShape.TRIMESH_HOUSE)
{
colliderWithGizmo = trimeshHouse(p);
} else if (colliderShape == ColliderShape.CONVEX) {
}
else if (colliderShape == ColliderShape.CONVEX)
{
colliderWithGizmo = convexTrimesh(p);
}
}
if (colliderWithGizmo != null) {
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) {
if (mode == ToolMode.RAYCAST)
{
if (shift)
{
sposSet = true;
spos = ArrayUtils.CopyOf(p, p.Length);
} else {
}
else
{
eposSet = true;
epos = ArrayUtils.CopyOf(p, p.Length);
}
if (sposSet && eposSet && dynaMesh != null) {
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();
@ -151,14 +191,16 @@ public class DynamicUpdateTool : Tool {
}
}
private Tuple<Collider, ColliderGizmo> sphereCollider(float[] p) {
private Tuple<Collider, ColliderGizmo> sphereCollider(float[] p)
{
float radius = 1 + (float)random.NextDouble() * 10;
return Tuple.Create<Collider, ColliderGizmo>(
new SphereCollider(p, radius, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER, dynaMesh.config.walkableClimb),
GizmoFactory.sphere(p, radius));
}
private Tuple<Collider, ColliderGizmo> capsuleCollider(float[] p) {
private Tuple<Collider, ColliderGizmo> 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);
@ -172,9 +214,13 @@ public class DynamicUpdateTool : Tool {
dynaMesh.config.walkableClimb), GizmoFactory.capsule(start, end, radius));
}
private Tuple<Collider, ColliderGizmo> 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 };
private Tuple<Collider, ColliderGizmo> 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);
@ -183,7 +229,8 @@ public class DynamicUpdateTool : Tool {
GizmoFactory.box(p, halfEdges));
}
private Tuple<Collider, ColliderGizmo> cylinderCollider(float[] p) {
private Tuple<Collider, ColliderGizmo> 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);
@ -197,7 +244,8 @@ public class DynamicUpdateTool : Tool {
dynaMesh.config.walkableClimb), GizmoFactory.cylinder(start, end, radius));
}
private Tuple<Collider, ColliderGizmo> compositeCollider(float[] p) {
private Tuple<Collider, ColliderGizmo> 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 };
@ -212,13 +260,19 @@ public class DynamicUpdateTool : Tool {
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[] 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 };
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);
@ -230,36 +284,42 @@ public class DynamicUpdateTool : Tool {
return Tuple.Create<Collider, ColliderGizmo>(collider, gizmo);
}
private Tuple<Collider, ColliderGizmo> trimeshBridge(float[] p) {
private Tuple<Collider, ColliderGizmo> trimeshBridge(float[] p)
{
return trimeshCollider(p, bridgeGeom);
}
private Tuple<Collider, ColliderGizmo> trimeshHouse(float[] p) {
private Tuple<Collider, ColliderGizmo> trimeshHouse(float[] p)
{
return trimeshCollider(p, houseGeom);
}
private Tuple<Collider, ColliderGizmo> convexTrimesh(float[] p) {
private Tuple<Collider, ColliderGizmo> 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, ColliderGizmo>(collider, GizmoFactory.trimesh(verts, convexGeom.faces));
}
private Tuple<Collider, ColliderGizmo> trimeshCollider(float[] p, DemoInputGeomProvider geom) {
private Tuple<Collider, ColliderGizmo> 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, ColliderGizmo>(collider, GizmoFactory.trimesh(verts, geom.faces));
}
private float[] transformVertices(float[] p, DemoInputGeomProvider geom, float ax) {
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) {
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];
@ -271,21 +331,28 @@ public class DynamicUpdateTool : Tool {
verts[i + 1] = vr[1];
verts[i + 2] = vr[2];
}
return verts;
}
private float[] mulMatrixVector(float[] resultvector, float[] matrix, float[] pvector) {
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())) {
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);
@ -296,7 +363,8 @@ public class DynamicUpdateTool : Tool {
}
}
private bool hit(float[] point, float[] dir, float[] bounds) {
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]);
@ -308,46 +376,62 @@ public class DynamicUpdateTool : Tool {
float my = point[1] - cy;
float mz = point[2] - cz;
float c = mx * mx + my * my + mz * mz - rSqr;
if (c <= 0.0f) {
if (c <= 0.0f)
{
return true;
}
float b = mx * dir[0] + my * dir[1] + mz * dir[2];
if (b > 0.0f) {
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) {
public override void handleRender(NavMeshRenderer renderer)
{
if (mode == ToolMode.COLLIDERS)
{
if (showColliders)
{
colliderGizmos.Values.forEach(g => g.render(renderer.getDebugDraw()));
}
}
if (mode == ToolMode.RAYCAST) {
if (mode == ToolMode.RAYCAST)
{
RecastDebugDraw dd = renderer.getDebugDraw();
int startCol = duRGBA(128, 25, 0, 192);
int endCol = duRGBA(51, 102, 0, 129);
if (sposSet) {
if (sposSet)
{
drawAgent(dd, spos, startCol);
}
if (eposSet) {
if (eposSet)
{
drawAgent(dd, epos, endCol);
}
dd.depthMask(false);
if (raycastHitPos != null) {
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) {
private void drawAgent(RecastDebugDraw dd, float[] pos, int col)
{
float r = sample.getSettingsUI().getAgentRadius();
float h = sample.getSettingsUI().getAgentHeight();
float c = sample.getSettingsUI().getAgentMaxClimb();
@ -367,29 +451,35 @@ public class DynamicUpdateTool : Tool {
dd.depthMask(true);
}
public override void handleUpdate(float dt) {
if (dynaMesh != null) {
public override void handleUpdate(float dt)
{
if (dynaMesh != null)
{
updateDynaMesh();
}
}
private void updateDynaMesh() {
private void updateDynaMesh()
{
long t = Stopwatch.GetTimestamp();
try
{
bool updated = dynaMesh.update(executor).Result;
if (updated) {
if (updated)
{
buildTime = (Stopwatch.GetTimestamp() - t) / 1_000_000;
sample.update(null, dynaMesh.recastResults(), dynaMesh.navMesh());
sample.setChanged(false);
}
} catch (Exception e) {
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
public override void layout(IWindow ctx) {
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;
@ -557,10 +647,10 @@ public class DynamicUpdateTool : Tool {
// } else {
// nk_label(ctx, string.format("Build Time: %d ms", buildTime), NK_TEXT_ALIGN_LEFT);
// }
}
private void load() {
private void load()
{
// try (MemoryStack stack = stackPush()) {
// PointerBuffer aFilterPatterns = stack.mallocPointer(1);
// aFilterPatterns.put(stack.UTF8("*.voxels"));
@ -570,10 +660,10 @@ public class DynamicUpdateTool : Tool {
// load(filename);
// }
// }
}
private void load(string filename) {
private void load(string filename)
{
// File file = new File(filename);
// if (file.exists()) {
// VoxelFileReader reader = new VoxelFileReader();
@ -591,7 +681,8 @@ public class DynamicUpdateTool : Tool {
// }
}
private void save() {
private void save()
{
// try (MemoryStack stack = stackPush()) {
// PointerBuffer aFilterPatterns = stack.mallocPointer(1);
// aFilterPatterns.put(stack.UTF8("*.voxels"));
@ -601,10 +692,10 @@ public class DynamicUpdateTool : Tool {
// save(filename);
// }
// }
}
private void save(string filename) {
private void save(string filename)
{
// File file = new File(filename);
// try (FileOutputStream fos = new FileOutputStream(file)) {
// VoxelFile voxelFile = VoxelFile.from(dynaMesh);
@ -613,23 +704,27 @@ public class DynamicUpdateTool : Tool {
// } catch (Exception e) {
// Console.WriteLine(e);
// }
}
private void buildDynaMesh() {
private void buildDynaMesh()
{
configDynaMesh();
long t = Stopwatch.GetTimestamp();
try
{
var _ = dynaMesh.build(executor).Result;
} catch (Exception e) {
}
catch (Exception e)
{
Console.WriteLine(e);
}
buildTime = (Stopwatch.GetTimestamp() - t) / 1_000_000;
sample.update(null, dynaMesh.recastResults(), dynaMesh.navMesh());
}
private void configDynaMesh() {
private void configDynaMesh()
{
dynaMesh.config.partitionType = partitioning;
dynaMesh.config.walkableHeight = walkableHeight[0];
dynaMesh.config.walkableSlopeAngle = walkableSlopeAngle[0];
@ -648,7 +743,8 @@ public class DynamicUpdateTool : Tool {
dynaMesh.config.detailSampleMaxError = detailSampleMaxError[0];
}
private void updateUI() {
private void updateUI()
{
cellSize[0] = dynaMesh.config.cellSize;
partitioning = dynaMesh.config.partitionType;
walkableHeight[0] = dynaMesh.config.walkableHeight;
@ -668,8 +764,8 @@ public class DynamicUpdateTool : Tool {
filterWalkableLowHeightSpans = dynaMesh.config.filterWalkableLowHeightSpans;
}
public override string getName() {
public override string getName()
{
return "Dynamic Updates";
}
}

View File

@ -3,11 +3,16 @@ using DotRecast.Recast.Demo.Draw;
namespace DotRecast.Recast.Demo.Tools.Gizmos;
public class BoxGizmo : ColliderGizmo
{
private static readonly int[] TRIANLGES =
{
0, 1, 2, 0, 2, 3, 4, 7, 6, 4, 6, 5, 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2,
2, 6, 7, 2, 7, 3, 4, 0, 3, 4, 3, 7
};
public class BoxGizmo : ColliderGizmo {
private static readonly int[] TRIANLGES = { 0, 1, 2, 0, 2, 3, 4, 7, 6, 4, 6, 5, 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2,
2, 6, 7, 2, 7, 3, 4, 0, 3, 4, 3, 7 };
private static readonly float[][] VERTS = {
private static readonly float[][] VERTS =
{
new[] { -1f, -1f, -1f, },
new[] { 1f, -1f, -1f, },
new[] { 1f, -1f, 1f, },
@ -27,10 +32,12 @@ public class BoxGizmo : ColliderGizmo {
{
}
public BoxGizmo(float[] center, float[][] halfEdges) {
public BoxGizmo(float[] center, float[][] halfEdges)
{
this.center = center;
this.halfEdges = halfEdges;
for (int i = 0; i < 8; ++i) {
for (int i = 0; i < 8; ++i)
{
float s0 = (i & 1) != 0 ? 1f : -1f;
float s1 = (i & 2) != 0 ? 1f : -1f;
float s2 = (i & 4) != 0 ? 1f : -1f;
@ -40,29 +47,39 @@ public class BoxGizmo : ColliderGizmo {
}
}
public void render(RecastDebugDraw debugDraw) {
public void render(RecastDebugDraw debugDraw)
{
float[] trX = new float[] { halfEdges[0][0], halfEdges[1][0], halfEdges[2][0] };
float[] trY = new float[] { halfEdges[0][1], halfEdges[1][1], halfEdges[2][1] };
float[] trZ = new float[] { halfEdges[0][2], halfEdges[1][2], halfEdges[2][2] };
float[] vertices = new float[8 * 3];
for (int i = 0; i < 8; i++) {
for (int i = 0; i < 8; i++)
{
vertices[i * 3 + 0] = RecastVectors.dot(VERTS[i], trX) + center[0];
vertices[i * 3 + 1] = RecastVectors.dot(VERTS[i], trY) + center[1];
vertices[i * 3 + 2] = RecastVectors.dot(VERTS[i], trZ) + center[2];
}
debugDraw.begin(DebugDrawPrimitives.TRIS);
for (int i = 0; i < 12; i++) {
for (int i = 0; i < 12; i++)
{
int col = DebugDraw.duRGBA(200, 200, 50, 160);
if (i == 4 || i == 5 || i == 8 || i == 9) {
if (i == 4 || i == 5 || i == 8 || i == 9)
{
col = DebugDraw.duRGBA(160, 160, 40, 160);
} else if (i > 4) {
}
else if (i > 4)
{
col = DebugDraw.duRGBA(120, 120, 30, 160);
}
for (int j = 0; j < 3; j++) {
for (int j = 0; j < 3; j++)
{
int v = TRIANLGES[i * 3 + j] * 3;
debugDraw.vertex(vertices[v], vertices[v + 1], vertices[v + 2], col);
}
}
debugDraw.end();
}
}

View File

@ -1,21 +1,24 @@
using DotRecast.Recast.Demo.Draw;
using static DotRecast.Recast.RecastVectors;
using static DotRecast.Detour.DetourCommon;
using static DotRecast.Recast.Demo.Tools.Gizmos.GizmoHelper;
namespace DotRecast.Recast.Demo.Tools.Gizmos;
public class CapsuleGizmo : ColliderGizmo {
public class CapsuleGizmo : ColliderGizmo
{
private readonly float[] vertices;
private readonly int[] triangles;
private readonly float[] center;
private readonly float[] gradient;
public CapsuleGizmo(float[] start, float[] end, float radius) {
center = new float[] { 0.5f * (start[0] + end[0]), 0.5f * (start[1] + end[1]),
0.5f * (start[2] + end[2]) };
public CapsuleGizmo(float[] start, float[] end, float radius)
{
center = new float[]
{
0.5f * (start[0] + end[0]), 0.5f * (start[1] + end[1]),
0.5f * (start[2] + end[2])
};
float[] axis = new float[] { end[0] - start[0], end[1] - start[1], end[2] - start[2] };
float[][] normals = new float[3][];
normals[1] = new float[] { end[0] - start[0], end[1] - start[1], end[2] - start[2] };
@ -33,7 +36,8 @@ public class CapsuleGizmo : ColliderGizmo {
vertices = new float[spVertices.Length];
gradient = new float[spVertices.Length / 3];
float[] v = new float[3];
for (int i = 0; i < spVertices.Length; i += 3) {
for (int i = 0; i < spVertices.Length; i += 3)
{
float offset = (i >= spVertices.Length / 2) ? -halfLength : halfLength;
float x = radius * spVertices[i];
float y = radius * spVertices[i + 1] + offset;
@ -47,14 +51,16 @@ public class CapsuleGizmo : ColliderGizmo {
normalize(v);
gradient[i / 3] = clamp(0.57735026f * (v[0] + v[1] + v[2]), -1, 1);
}
}
private float[] getSideVector(float[] axis) {
private float[] getSideVector(float[] axis)
{
float[] side = { 1, 0, 0 };
if (axis[0] > 0.8) {
if (axis[0] > 0.8)
{
side = new float[] { 0, 0, 1 };
}
float[] forward = new float[3];
cross(forward, side, axis);
cross(side, axis, forward);
@ -62,11 +68,13 @@ public class CapsuleGizmo : ColliderGizmo {
return side;
}
public void render(RecastDebugDraw debugDraw) {
public void render(RecastDebugDraw debugDraw)
{
debugDraw.begin(DebugDrawPrimitives.TRIS);
for (int i = 0; i < triangles.Length; i += 3) {
for (int j = 0; j < 3; j++) {
for (int i = 0; i < triangles.Length; i += 3)
{
for (int j = 0; j < 3; j++)
{
int v = triangles[i + j] * 3;
float c = gradient[triangles[i + j]];
int col = DebugDraw.duLerpCol(DebugDraw.duRGBA(32, 32, 0, 160), DebugDraw.duRGBA(220, 220, 0, 160),
@ -74,7 +82,7 @@ public class CapsuleGizmo : ColliderGizmo {
debugDraw.vertex(vertices[v], vertices[v + 1], vertices[v + 2], col);
}
}
debugDraw.end();
}
}

View File

@ -2,7 +2,7 @@ using DotRecast.Recast.Demo.Draw;
namespace DotRecast.Recast.Demo.Tools.Gizmos;
public interface ColliderGizmo {
public interface ColliderGizmo
{
void render(RecastDebugDraw debugDraw);
}

View File

@ -3,15 +3,17 @@ using DotRecast.Recast.Demo.Draw;
namespace DotRecast.Recast.Demo.Tools.Gizmos;
public class CompositeGizmo : ColliderGizmo {
public class CompositeGizmo : ColliderGizmo
{
private readonly ColliderGizmo[] gizmos;
public CompositeGizmo(params ColliderGizmo[] gizmos) {
public CompositeGizmo(params ColliderGizmo[] gizmos)
{
this.gizmos = gizmos;
}
public void render(RecastDebugDraw debugDraw) {
public void render(RecastDebugDraw debugDraw)
{
gizmos.forEach(g => g.render(debugDraw));
}
}

View File

@ -6,16 +6,20 @@ using static DotRecast.Recast.Demo.Tools.Gizmos.GizmoHelper;
namespace DotRecast.Recast.Demo.Tools.Gizmos;
public class CylinderGizmo : ColliderGizmo {
public class CylinderGizmo : ColliderGizmo
{
private readonly float[] vertices;
private readonly int[] triangles;
private readonly float[] center;
private readonly float[] gradient;
public CylinderGizmo(float[] start, float[] end, float radius) {
center = new float[] { 0.5f * (start[0] + end[0]), 0.5f * (start[1] + end[1]),
0.5f * (start[2] + end[2]) };
public CylinderGizmo(float[] start, float[] end, float radius)
{
center = new float[]
{
0.5f * (start[0] + end[0]), 0.5f * (start[1] + end[1]),
0.5f * (start[2] + end[2])
};
float[] axis = new float[] { end[0] - start[0], end[1] - start[1], end[2] - start[2] };
float[][] normals = new float[3][];
normals[1] = new float[] { end[0] - start[0], end[1] - start[1], end[2] - start[2] };
@ -32,7 +36,8 @@ public class CylinderGizmo : ColliderGizmo {
float halfLength = 0.5f * vLen(axis);
gradient = new float[vertices.Length / 3];
float[] v = new float[3];
for (int i = 0; i < vertices.Length; i += 3) {
for (int i = 0; i < vertices.Length; i += 3)
{
float offset = (i >= vertices.Length / 2) ? -halfLength : halfLength;
float x = radius * vertices[i];
float y = vertices[i + 1] + offset;
@ -40,9 +45,12 @@ public class CylinderGizmo : ColliderGizmo {
vertices[i] = x * trX[0] + y * trX[1] + z * trX[2] + center[0];
vertices[i + 1] = x * trY[0] + y * trY[1] + z * trY[2] + center[1];
vertices[i + 2] = x * trZ[0] + y * trZ[1] + z * trZ[2] + center[2];
if (i < vertices.Length / 4 || i >= 3 * vertices.Length / 4) {
if (i < vertices.Length / 4 || i >= 3 * vertices.Length / 4)
{
gradient[i / 3] = 1;
} else {
}
else
{
v[0] = vertices[i] - center[0];
v[1] = vertices[i + 1] - center[1];
v[2] = vertices[i + 2] - center[2];
@ -52,11 +60,14 @@ public class CylinderGizmo : ColliderGizmo {
}
}
private float[] getSideVector(float[] axis) {
private float[] getSideVector(float[] axis)
{
float[] side = { 1, 0, 0 };
if (axis[0] > 0.8) {
if (axis[0] > 0.8)
{
side = new float[] { 0, 0, 1 };
}
float[] forward = new float[3];
cross(forward, side, axis);
cross(side, axis, forward);
@ -64,11 +75,13 @@ public class CylinderGizmo : ColliderGizmo {
return side;
}
public void render(RecastDebugDraw debugDraw) {
public void render(RecastDebugDraw debugDraw)
{
debugDraw.begin(DebugDrawPrimitives.TRIS);
for (int i = 0; i < triangles.Length; i += 3) {
for (int j = 0; j < 3; j++) {
for (int i = 0; i < triangles.Length; i += 3)
{
for (int j = 0; j < 3; j++)
{
int v = triangles[i + j] * 3;
float c = gradient[triangles[i + j]];
int col = DebugDraw.duLerpCol(DebugDraw.duRGBA(32, 32, 0, 160), DebugDraw.duRGBA(220, 220, 0, 160),
@ -76,7 +89,7 @@ public class CylinderGizmo : ColliderGizmo {
debugDraw.vertex(vertices[v], vertices[v + 1], vertices[v + 2], col);
}
}
debugDraw.end();
}
}

View File

@ -2,27 +2,33 @@
public static class GizmoFactory
{
public static ColliderGizmo box(float[] center, float[][] halfEdges) {
public static ColliderGizmo box(float[] center, float[][] halfEdges)
{
return new BoxGizmo(center, halfEdges);
}
public static ColliderGizmo sphere(float[] center, float radius) {
public static ColliderGizmo sphere(float[] center, float radius)
{
return new SphereGizmo(center, radius);
}
public static ColliderGizmo capsule(float[] start, float[] end, float radius) {
public static ColliderGizmo capsule(float[] start, float[] end, float radius)
{
return new CapsuleGizmo(start, end, radius);
}
public static ColliderGizmo cylinder(float[] start, float[] end, float radius) {
public static ColliderGizmo cylinder(float[] start, float[] end, float radius)
{
return new CylinderGizmo(start, end, radius);
}
public static ColliderGizmo trimesh(float[] verts, int[] faces) {
public static ColliderGizmo trimesh(float[] verts, int[] faces)
{
return new TrimeshGizmo(verts, faces);
}
public static ColliderGizmo composite(params ColliderGizmo[] gizmos) {
public static ColliderGizmo composite(params ColliderGizmo[] gizmos)
{
return new CompositeGizmo(gizmos);
}
}

View File

@ -4,31 +4,37 @@ using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Recast.Demo.Tools.Gizmos;
public class GizmoHelper {
public class GizmoHelper
{
private static readonly int SEGMENTS = 16;
private static readonly int RINGS = 8;
private static float[] sphericalVertices;
public static float[] generateSphericalVertices() {
if (sphericalVertices == null) {
public static float[] generateSphericalVertices()
{
if (sphericalVertices == null)
{
sphericalVertices = generateSphericalVertices(SEGMENTS, RINGS);
}
return sphericalVertices;
}
private static float[] generateSphericalVertices(int segments, int rings) {
private static float[] generateSphericalVertices(int segments, int rings)
{
float[] vertices = new float[6 + 3 * (segments + 1) * (rings + 1)];
// top
int vi = 0;
vertices[vi++] = 0;
vertices[vi++] = 1;
vertices[vi++] = 0;
for (int r = 0; r <= rings; r++) {
for (int r = 0; r <= rings; r++)
{
double theta = Math.PI * (r + 1) / (rings + 2);
vi = generateRingVertices(segments, vertices, vi, theta);
}
// bottom
vertices[vi++] = 0;
vertices[vi++] = -1;
@ -36,24 +42,29 @@ public class GizmoHelper {
return vertices;
}
public static float[] generateCylindricalVertices() {
public static float[] generateCylindricalVertices()
{
return generateCylindricalVertices(SEGMENTS);
}
private static float[] generateCylindricalVertices(int segments) {
private static float[] generateCylindricalVertices(int segments)
{
float[] vertices = new float[3 * (segments + 1) * 4];
int vi = 0;
for (int r = 0; r < 4; r++) {
for (int r = 0; r < 4; r++)
{
vi = generateRingVertices(segments, vertices, vi, Math.PI * 0.5);
}
return vertices;
return vertices;
}
private static int generateRingVertices(int segments, float[] vertices, int vi, double theta) {
private static int generateRingVertices(int segments, float[] vertices, int vi, double theta)
{
double cosTheta = Math.Cos(theta);
double sinTheta = Math.Sin(theta);
for (int p = 0; p <= segments; p++) {
for (int p = 0; p <= segments; p++)
{
double phi = 2 * Math.PI * p / segments;
double cosPhi = Math.Cos(phi);
double sinPhi = Math.Sin(phi);
@ -61,14 +72,17 @@ public class GizmoHelper {
vertices[vi++] = (float)cosTheta;
vertices[vi++] = (float)(sinTheta * sinPhi);
}
return vi;
}
public static int[] generateSphericalTriangles() {
public static int[] generateSphericalTriangles()
{
return generateSphericalTriangles(SEGMENTS, RINGS);
}
private static int[] generateSphericalTriangles(int segments, int rings) {
private static int[] generateSphericalTriangles(int segments, int rings)
{
int[] triangles = new int[6 * (segments + rings * (segments + 1))];
int ti = generateSphereUpperCapTriangles(segments, triangles, 0);
ti = generateRingTriangles(segments, rings, triangles, 1, ti);
@ -76,9 +90,12 @@ public class GizmoHelper {
return triangles;
}
public static int generateRingTriangles(int segments, int rings, int[] triangles, int vertexOffset, int ti) {
for (int r = 0; r < rings; r++) {
for (int p = 0; p < segments; p++) {
public static int generateRingTriangles(int segments, int rings, int[] triangles, int vertexOffset, int ti)
{
for (int r = 0; r < rings; r++)
{
for (int p = 0; p < segments; p++)
{
int current = p + r * (segments + 1) + vertexOffset;
int next = p + 1 + r * (segments + 1) + vertexOffset;
int currentBottom = p + (r + 1) * (segments + 1) + vertexOffset;
@ -91,32 +108,40 @@ public class GizmoHelper {
triangles[ti++] = currentBottom;
}
}
return ti;
}
private static int generateSphereUpperCapTriangles(int segments, int[] triangles, int ti) {
for (int p = 0; p < segments; p++) {
private static int generateSphereUpperCapTriangles(int segments, int[] triangles, int ti)
{
for (int p = 0; p < segments; p++)
{
triangles[ti++] = p + 2;
triangles[ti++] = p + 1;
triangles[ti++] = 0;
}
return ti;
}
private static void generateSphereLowerCapTriangles(int segments, int rings, int[] triangles, int ti) {
private static void generateSphereLowerCapTriangles(int segments, int rings, int[] triangles, int ti)
{
int lastVertex = 1 + (segments + 1) * (rings + 1);
for (int p = 0; p < segments; p++) {
for (int p = 0; p < segments; p++)
{
triangles[ti++] = lastVertex;
triangles[ti++] = lastVertex - (p + 2);
triangles[ti++] = lastVertex - (p + 1);
}
}
public static int[] generateCylindricalTriangles() {
public static int[] generateCylindricalTriangles()
{
return generateCylindricalTriangles(SEGMENTS);
}
private static int[] generateCylindricalTriangles(int segments) {
private static int[] generateCylindricalTriangles(int segments)
{
int circleTriangles = segments - 2;
int[] triangles = new int[6 * (circleTriangles + (segments + 1))];
int vi = 0;
@ -127,28 +152,37 @@ public class GizmoHelper {
return triangles;
}
private static int generateCircleTriangles(int segments, int[] triangles, int vi, int ti, bool invert) {
for (int p = 0; p < segments - 2; p++) {
if (invert) {
private static int generateCircleTriangles(int segments, int[] triangles, int vi, int ti, bool invert)
{
for (int p = 0; p < segments - 2; p++)
{
if (invert)
{
triangles[ti++] = vi;
triangles[ti++] = vi + p + 1;
triangles[ti++] = vi + p + 2;
} else {
}
else
{
triangles[ti++] = vi + p + 2;
triangles[ti++] = vi + p + 1;
triangles[ti++] = vi;
}
}
return ti;
}
public static int getColorByNormal(float[] vertices, int v0, int v1, int v2) {
public static int getColorByNormal(float[] vertices, int v0, int v1, int v2)
{
float[] e0 = new float[3], e1 = new float[3];
float[] normal = new float[3];
for (int j = 0; j < 3; ++j) {
for (int j = 0; j < 3; ++j)
{
e0[j] = vertices[v1 + j] - vertices[v0 + j];
e1[j] = vertices[v2 + j] - vertices[v0 + j];
}
normal[0] = e0[1] * e1[2] - e0[2] * e1[1];
normal[1] = e0[2] * e1[0] - e0[0] * e1[2];
normal[2] = e0[0] * e1[1] - e0[1] * e1[0];
@ -158,5 +192,4 @@ public class GizmoHelper {
(int)(127 * (1 + c)));
return col;
}
}

View File

@ -1,29 +1,32 @@
using DotRecast.Recast.Demo.Draw;
using static DotRecast.Detour.DetourCommon;
using static DotRecast.Recast.Demo.Tools.Gizmos.GizmoHelper;
namespace DotRecast.Recast.Demo.Tools.Gizmos;
public class SphereGizmo : ColliderGizmo {
public class SphereGizmo : ColliderGizmo
{
private readonly float[] vertices;
private readonly int[] triangles;
private readonly float radius;
private readonly float[] center;
public SphereGizmo(float[] center, float radius) {
public SphereGizmo(float[] center, float radius)
{
this.center = center;
this.radius = radius;
vertices = generateSphericalVertices();
triangles = generateSphericalTriangles();
}
public void render(RecastDebugDraw debugDraw) {
public void render(RecastDebugDraw debugDraw)
{
debugDraw.begin(DebugDrawPrimitives.TRIS);
for (int i = 0; i < triangles.Length; i += 3) {
for (int j = 0; j < 3; j++) {
for (int i = 0; i < triangles.Length; i += 3)
{
for (int j = 0; j < 3; j++)
{
int v = triangles[i + j] * 3;
float c = clamp(0.57735026f * (vertices[v] + vertices[v + 1] + vertices[v + 2]), -1, 1);
int col = DebugDraw.duLerpCol(DebugDraw.duRGBA(32, 32, 0, 160), DebugDraw.duRGBA(220, 220, 0, 160),
@ -32,7 +35,7 @@ public class SphereGizmo : ColliderGizmo {
radius * vertices[v + 2] + center[2], col);
}
}
debugDraw.end();
}
}

View File

@ -2,18 +2,22 @@
namespace DotRecast.Recast.Demo.Tools.Gizmos;
public class TrimeshGizmo : ColliderGizmo {
public class TrimeshGizmo : ColliderGizmo
{
private readonly float[] vertices;
private readonly int[] triangles;
public TrimeshGizmo(float[] vertices, int[] triangles) {
public TrimeshGizmo(float[] vertices, int[] triangles)
{
this.vertices = vertices;
this.triangles = triangles;
}
public void render(RecastDebugDraw debugDraw) {
public void render(RecastDebugDraw debugDraw)
{
debugDraw.begin(DebugDrawPrimitives.TRIS);
for (int i = 0; i < triangles.Length; i += 3) {
for (int i = 0; i < triangles.Length; i += 3)
{
int v0 = 3 * triangles[i];
int v1 = 3 * triangles[i + 1];
int v2 = 3 * triangles[i + 2];
@ -22,7 +26,7 @@ public class TrimeshGizmo : ColliderGizmo {
debugDraw.vertex(vertices[v1], vertices[v1 + 1], vertices[v1 + 2], col);
debugDraw.vertex(vertices[v2], vertices[v2 + 1], vertices[v2 + 2], col);
}
debugDraw.end();
}
}

View File

@ -22,60 +22,69 @@ using DotRecast.Detour.Extras.Jumplink;
using DotRecast.Recast.Demo.Builder;
using DotRecast.Recast.Demo.Draw;
using DotRecast.Recast.Demo.Geom;
using static DotRecast.Detour.DetourCommon;
using static DotRecast.Recast.Demo.Draw.DebugDraw;
using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives;
namespace DotRecast.Recast.Demo.Tools;
public class JumpLinkBuilderTool : Tool {
public class JumpLinkBuilderTool : Tool
{
private readonly List<JumpLink> links = new();
private Sample sample;
private JumpLinkBuilder annotationBuilder;
private readonly int selEdge = -1;
private readonly JumpLinkBuilderToolParams option = new JumpLinkBuilderToolParams();
public override void setSample(Sample sample) {
public override void setSample(Sample sample)
{
this.sample = sample;
annotationBuilder = null;
}
public override void handleClick(float[] s, float[] p, bool shift) {
public override void handleClick(float[] s, float[] p, bool shift)
{
}
public override void handleRender(NavMeshRenderer renderer) {
public override void handleRender(NavMeshRenderer renderer)
{
int col0 = duLerpCol(duRGBA(32, 255, 96, 255), duRGBA(255, 255, 255, 255), 200);
int col1 = duRGBA(32, 255, 96, 255);
RecastDebugDraw dd = renderer.getDebugDraw();
dd.depthMask(false);
if ((option.flags & JumpLinkBuilderToolParams.DRAW_WALKABLE_BORDER) != 0) {
if (annotationBuilder != null) {
foreach (Edge[] edges in annotationBuilder.getEdges()) {
if ((option.flags & JumpLinkBuilderToolParams.DRAW_WALKABLE_BORDER) != 0)
{
if (annotationBuilder != null)
{
foreach (Edge[] edges in annotationBuilder.getEdges())
{
dd.begin(LINES, 3.0f);
for (int i = 0; i < edges.Length; ++i) {
for (int i = 0; i < edges.Length; ++i)
{
int col = duRGBA(0, 96, 128, 255);
if (i == selEdge)
continue;
dd.vertex(edges[i].sp, col);
dd.vertex(edges[i].sq, col);
}
dd.end();
dd.begin(POINTS, 8.0f);
for (int i = 0; i < edges.Length; ++i) {
for (int i = 0; i < edges.Length; ++i)
{
int col = duRGBA(0, 96, 128, 255);
if (i == selEdge)
continue;
dd.vertex(edges[i].sp, col);
dd.vertex(edges[i].sq, col);
}
dd.end();
if (selEdge >= 0 && selEdge < edges.Length) {
if (selEdge >= 0 && selEdge < edges.Length)
{
int col = duRGBA(48, 16, 16, 255); // duRGBA(255,192,0,255);
dd.begin(LINES, 3.0f);
dd.vertex(edges[selEdge].sp, col);
@ -88,20 +97,25 @@ public class JumpLinkBuilderTool : Tool {
}
dd.begin(POINTS, 4.0f);
for (int i = 0; i < edges.Length; ++i) {
for (int i = 0; i < edges.Length; ++i)
{
int col = duRGBA(190, 190, 190, 255);
dd.vertex(edges[i].sp, col);
dd.vertex(edges[i].sq, col);
}
dd.end();
}
}
}
if ((option.flags & JumpLinkBuilderToolParams.DRAW_ANNOTATIONS) != 0) {
if ((option.flags & JumpLinkBuilderToolParams.DRAW_ANNOTATIONS) != 0)
{
dd.begin(QUADS);
foreach (JumpLink link in links) {
for (int j = 0; j < link.nspine - 1; ++j) {
foreach (JumpLink link in links)
{
for (int j = 0; j < link.nspine - 1; ++j)
{
int u = (j * 255) / link.nspine;
int col = duTransCol(duLerpCol(col0, col1, u), 128);
dd.vertex(link.spine1[j * 3], link.spine1[j * 3 + 1], link.spine1[j * 3 + 2], col);
@ -112,10 +126,13 @@ public class JumpLinkBuilderTool : Tool {
dd.vertex(link.spine0[j * 3], link.spine0[j * 3 + 1], link.spine0[j * 3 + 2], col);
}
}
dd.end();
dd.begin(LINES, 3.0f);
foreach (JumpLink link in links) {
for (int j = 0; j < link.nspine - 1; ++j) {
foreach (JumpLink link in links)
{
for (int j = 0; j < link.nspine - 1; ++j)
{
// int u = (j*255)/link.nspine;
int col = duTransCol(duDarkenCol(col1) /*duDarkenCol(duLerpCol(col0,col1,u))*/, 128);
@ -135,11 +152,16 @@ public class JumpLinkBuilderTool : Tool {
dd.vertex(link.spine1[(link.nspine - 1) * 3], link.spine1[(link.nspine - 1) * 3 + 1],
link.spine1[(link.nspine - 1) * 3 + 2], duDarkenCol(col1));
}
dd.end();
}
if (annotationBuilder != null) {
foreach (JumpLink link in links) {
if ((option.flags & JumpLinkBuilderToolParams.DRAW_ANIM_TRAJECTORY) != 0) {
if (annotationBuilder != null)
{
foreach (JumpLink link in links)
{
if ((option.flags & JumpLinkBuilderToolParams.DRAW_ANIM_TRAJECTORY) != 0)
{
float r = link.start.height;
int col = duLerpCol(duRGBA(255, 192, 0, 255),
@ -203,84 +225,104 @@ public class JumpLinkBuilderTool : Tool {
dd.vertex(end.q, colm);
dd.end();
}
if ((option.flags & JumpLinkBuilderToolParams.DRAW_LAND_SAMPLES) != 0) {
if ((option.flags & JumpLinkBuilderToolParams.DRAW_LAND_SAMPLES) != 0)
{
dd.begin(POINTS, 8.0f);
for (int i = 0; i < link.start.gsamples.Length; ++i) {
for (int i = 0; i < link.start.gsamples.Length; ++i)
{
GroundSample s = link.start.gsamples[i];
float u = i / (float)(link.start.gsamples.Length - 1);
float[] spt = vLerp(link.start.p, link.start.q, u);
int col = duRGBA(48, 16, 16, 255); // duRGBA(255,(s->flags & 4)?255:0,0,255);
float off = 0.1f;
if (!s.validHeight) {
if (!s.validHeight)
{
off = 0;
col = duRGBA(220, 32, 32, 255);
}
spt[1] = s.p[1] + off;
dd.vertex(spt, col);
}
dd.end();
dd.begin(POINTS, 4.0f);
for (int i = 0; i < link.start.gsamples.Length; ++i) {
for (int i = 0; i < link.start.gsamples.Length; ++i)
{
GroundSample s = link.start.gsamples[i];
float u = i / (float)(link.start.gsamples.Length - 1);
float[] spt = vLerp(link.start.p, link.start.q, u);
int col = duRGBA(255, 255, 255, 255);
float off = 0;
if (s.validHeight) {
if (s.validHeight)
{
off = 0.1f;
}
spt[1] = s.p[1] + off;
dd.vertex(spt, col);
}
dd.end();
{
GroundSegment end = link.end;
dd.begin(POINTS, 8.0f);
for (int i = 0; i < end.gsamples.Length; ++i) {
for (int i = 0; i < end.gsamples.Length; ++i)
{
GroundSample s = end.gsamples[i];
float u = i / (float)(end.gsamples.Length - 1);
float[] spt = vLerp(end.p, end.q, u);
int col = duRGBA(48, 16, 16, 255); // duRGBA(255,(s->flags & 4)?255:0,0,255);
float off = 0.1f;
if (!s.validHeight) {
if (!s.validHeight)
{
off = 0;
col = duRGBA(220, 32, 32, 255);
}
spt[1] = s.p[1] + off;
dd.vertex(spt, col);
}
dd.end();
dd.begin(POINTS, 4.0f);
for (int i = 0; i < end.gsamples.Length; ++i) {
for (int i = 0; i < end.gsamples.Length; ++i)
{
GroundSample s = end.gsamples[i];
float u = i / (float)(end.gsamples.Length - 1);
float[] spt = vLerp(end.p, end.q, u);
int col = duRGBA(255, 255, 255, 255);
float off = 0;
if (s.validHeight) {
if (s.validHeight)
{
off = 0.1f;
}
spt[1] = s.p[1] + off;
dd.vertex(spt, col);
}
dd.end();
}
}
}
}
dd.depthMask(true);
}
private void drawTrajectory(RecastDebugDraw dd, JumpLink link, float[] pa, float[] pb, Trajectory tra, int cola) {
private void drawTrajectory(RecastDebugDraw dd, JumpLink link, float[] pa, float[] pb, Trajectory tra, int cola)
{
}
public override void handleUpdate(float dt) {
public override void handleUpdate(float dt)
{
}
public override void layout(IWindow ctx) {
public override void layout(IWindow ctx)
{
// if (!sample.getRecastResults().isEmpty()) {
//
// nk_layout_row_dynamic(ctx, 18, 1);
@ -403,25 +445,27 @@ public class JumpLinkBuilderTool : Tool {
// : 0;
// params.flags = newFlags;
// }
}
private void addOffMeshLink(JumpLink link, DemoInputGeomProvider geom, float agentRadius) {
private void addOffMeshLink(JumpLink link, DemoInputGeomProvider geom, float agentRadius)
{
int area = SampleAreaModifications.SAMPLE_POLYAREA_TYPE_JUMP_AUTO;
int flags = SampleAreaModifications.SAMPLE_POLYFLAGS_JUMP;
float[] prev = new float[3];
for (int i = 0; i < link.startSamples.Length; i++) {
for (int i = 0; i < link.startSamples.Length; i++)
{
float[] p = link.startSamples[i].p;
float[] q = link.endSamples[i].p;
if (i == 0 || vDist2D(prev, p) > agentRadius) {
if (i == 0 || vDist2D(prev, p) > agentRadius)
{
geom.addOffMeshConnection(p, q, agentRadius, false, area, flags);
prev = p;
}
}
}
public override string getName() {
public override string getName()
{
return "Annotation Builder";
}
}

View File

@ -20,9 +20,8 @@ using DotRecast.Detour.Extras.Jumplink;
namespace DotRecast.Recast.Demo.Tools;
public class JumpLinkBuilderToolParams {
public class JumpLinkBuilderToolParams
{
public const int DRAW_WALKABLE_SURFACE = 1 << 0;
public const int DRAW_WALKABLE_BORDER = 1 << 1;
public const int DRAW_SELECTED_EDGE = 1 << 2;
@ -41,5 +40,4 @@ public class JumpLinkBuilderToolParams {
public readonly float[] edgeJumpDownMaxHeight = new[] { 2.5f };
public readonly float[] edgeJumpUpMaxHeight = new[] { 0.3f };
public int buildTypes = (1 << (int)JumpLinkType.EDGE_CLIMB_DOWN) | (1 << (int)JumpLinkType.EDGE_JUMP);
}

View File

@ -24,87 +24,106 @@ using DotRecast.Core;
using DotRecast.Recast.Demo.Builder;
using DotRecast.Recast.Demo.Draw;
using DotRecast.Recast.Demo.Geom;
using static DotRecast.Recast.Demo.Draw.DebugDraw;
namespace DotRecast.Recast.Demo.Tools;
public class OffMeshConnectionTool : Tool {
public class OffMeshConnectionTool : Tool
{
private Sample sample;
private bool hitPosSet;
private float[] hitPos;
private bool bidir;
public override void setSample(Sample m_sample) {
public override void setSample(Sample m_sample)
{
sample = m_sample;
}
public override void handleClick(float[] s, float[] p, bool shift) {
public override void handleClick(float[] s, float[] p, bool shift)
{
DemoInputGeomProvider geom = sample.getInputGeom();
if (geom == null) {
if (geom == null)
{
return;
}
if (shift) {
if (shift)
{
// Delete
// Find nearest link end-point
float nearestDist = float.MaxValue;
DemoOffMeshConnection nearestConnection = null;
foreach (DemoOffMeshConnection offMeshCon in geom.getOffMeshConnections()) {
foreach (DemoOffMeshConnection offMeshCon in geom.getOffMeshConnections())
{
float d = Math.Min(DemoMath.vDistSqr(p, offMeshCon.verts, 0), DemoMath.vDistSqr(p, offMeshCon.verts, 3));
if (d < nearestDist && Math.Sqrt(d) < sample.getSettingsUI().getAgentRadius()) {
if (d < nearestDist && Math.Sqrt(d) < sample.getSettingsUI().getAgentRadius())
{
nearestDist = d;
nearestConnection = offMeshCon;
}
}
if (nearestConnection != null) {
if (nearestConnection != null)
{
geom.getOffMeshConnections().Remove(nearestConnection);
}
} else {
}
else
{
// Create
if (!hitPosSet) {
if (!hitPosSet)
{
hitPos = ArrayUtils.CopyOf(p, p.Length);
hitPosSet = true;
} else {
}
else
{
int area = SampleAreaModifications.SAMPLE_POLYAREA_TYPE_JUMP;
int flags = SampleAreaModifications.SAMPLE_POLYFLAGS_JUMP;
geom.addOffMeshConnection(hitPos, p, sample.getSettingsUI().getAgentRadius(), bidir, area, flags);
hitPosSet = false;
}
}
}
public override void handleRender(NavMeshRenderer renderer) {
if (sample == null) {
public override void handleRender(NavMeshRenderer renderer)
{
if (sample == null)
{
return;
}
RecastDebugDraw dd = renderer.getDebugDraw();
float s = sample.getSettingsUI().getAgentRadius();
if (hitPosSet) {
if (hitPosSet)
{
dd.debugDrawCross(hitPos[0], hitPos[1] + 0.1f, hitPos[2], s, duRGBA(0, 0, 0, 128), 2.0f);
}
DemoInputGeomProvider geom = sample.getInputGeom();
if (geom != null) {
if (geom != null)
{
renderer.drawOffMeshConnections(geom, true);
}
}
public override void layout(IWindow ctx) {
public override void layout(IWindow ctx)
{
// nk_layout_row_dynamic(ctx, 20, 1);
// bidir = !nk_option_label(ctx, "One Way", !bidir);
// nk_layout_row_dynamic(ctx, 20, 1);
// bidir = nk_option_label(ctx, "Bidirectional", bidir);
}
public override string getName() {
public override string getName()
{
return "Create Off-Mesh Links";
}
public override void handleUpdate(float dt) {
public override void handleUpdate(float dt)
{
// TODO Auto-generated method stub
}
}

View File

@ -24,22 +24,25 @@ using DotRecast.Detour;
namespace DotRecast.Recast.Demo.Tools;
public static class PathUtils {
public static class PathUtils
{
private readonly static int MAX_STEER_POINTS = 3;
public static SteerTarget getSteerTarget(NavMeshQuery navQuery, float[] startPos, float[] endPos,
float minTargetDist, List<long> path) {
float minTargetDist, List<long> path)
{
// Find steer target.
Result<List<StraightPathItem>> result = navQuery.findStraightPath(startPos, endPos, path, MAX_STEER_POINTS, 0);
if (result.failed()) {
if (result.failed())
{
return null;
}
List<StraightPathItem> straightPath = result.result;
float[] steerPoints = new float[straightPath.Count * 3];
for (int i = 0; i < straightPath.Count; i++) {
for (int i = 0; i < straightPath.Count; i++)
{
steerPoints[i * 3] = straightPath[i].getPos()[0];
steerPoints[i * 3 + 1] = straightPath[i].getPos()[1];
steerPoints[i * 3 + 2] = straightPath[i].getPos()[2];
@ -47,48 +50,58 @@ public static class PathUtils {
// Find vertex far enough to steer to.
int ns = 0;
while (ns < straightPath.Count) {
while (ns < straightPath.Count)
{
// Stop at Off-Mesh link or when point is further than slop away.
if (((straightPath[ns].getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
|| !inRange(straightPath[ns].getPos(), startPos, minTargetDist, 1000.0f))
break;
ns++;
}
// Failed to find good point to steer to.
if (ns >= straightPath.Count)
return null;
float[] steerPos = new float[] { straightPath[ns].getPos()[0], startPos[1],
straightPath[ns].getPos()[2] };
float[] steerPos = new float[]
{
straightPath[ns].getPos()[0], startPos[1],
straightPath[ns].getPos()[2]
};
int steerPosFlag = straightPath[ns].getFlags();
long steerPosRef = straightPath[ns].getRef();
SteerTarget target = new SteerTarget(steerPos, steerPosFlag, steerPosRef, steerPoints);
return target;
}
public static bool inRange(float[] v1, float[] v2, float r, float h) {
public static bool inRange(float[] v1, float[] v2, float r, float h)
{
float dx = v2[0] - v1[0];
float dy = v2[1] - v1[1];
float dz = v2[2] - v1[2];
return (dx * dx + dz * dz) < r * r && Math.Abs(dy) < h;
}
public static List<long> fixupCorridor(List<long> path, List<long> visited) {
public static List<long> fixupCorridor(List<long> path, List<long> visited)
{
int furthestPath = -1;
int furthestVisited = -1;
// Find furthest common polygon.
for (int i = path.Count - 1; i >= 0; --i) {
for (int i = path.Count - 1; i >= 0; --i)
{
bool found = false;
for (int j = visited.Count - 1; j >= 0; --j) {
if (path[i] == visited[j]) {
for (int j = visited.Count - 1; j >= 0; --j)
{
if (path[i] == visited[j])
{
furthestPath = i;
furthestVisited = j;
found = true;
}
}
if (found)
break;
}
@ -105,10 +118,13 @@ public static class PathUtils {
int size = Math.Max(0, path.Count - orig);
List<long> fixupPath = new();
// Store visited
for (int i = 0; i < req; ++i) {
for (int i = 0; i < req; ++i)
{
fixupPath.Add(visited[(visited.Count - 1) - i]);
}
for (int i = 0; i < size; i++) {
for (int i = 0; i < size; i++)
{
fixupPath.Add(path[orig + i]);
}
@ -126,8 +142,10 @@ public static class PathUtils {
// +-S-+-T-+
// |:::| | <-- the step can end up in here, resulting U-turn path.
// +---+---+
public static List<long> fixupShortcuts(List<long> path, NavMeshQuery navQuery) {
if (path.Count < 3) {
public static List<long> fixupShortcuts(List<long> path, NavMeshQuery navQuery)
{
if (path.Count < 3)
{
return path;
}
@ -135,15 +153,19 @@ public static class PathUtils {
List<long> neis = new();
Result<Tuple<MeshTile, Poly>> tileAndPoly = navQuery.getAttachedNavMesh().getTileAndPolyByRef(path[0]);
if (tileAndPoly.failed()) {
if (tileAndPoly.failed())
{
return path;
}
MeshTile tile = tileAndPoly.result.Item1;
Poly poly = tileAndPoly.result.Item2;
for (int k = tile.polyLinks[poly.index]; k != NavMesh.DT_NULL_LINK; k = tile.links[k].next) {
for (int k = tile.polyLinks[poly.index]; k != NavMesh.DT_NULL_LINK; k = tile.links[k].next)
{
Link link = tile.links[k];
if (link.refs != 0) {
if (link.refs != 0)
{
neis.Add(link.refs);
}
}
@ -152,21 +174,26 @@ public static class PathUtils {
// in the path, short cut to that polygon directly.
int maxLookAhead = 6;
int cut = 0;
for (int i = Math.Min(maxLookAhead, path.Count) - 1; i > 1 && cut == 0; i--) {
for (int j = 0; j < neis.Count; j++) {
if (path[i] == neis[j]) {
for (int i = Math.Min(maxLookAhead, path.Count) - 1; i > 1 && cut == 0; i--)
{
for (int j = 0; j < neis.Count; j++)
{
if (path[i] == neis[j])
{
cut = i;
break;
}
}
}
if (cut > 1) {
if (cut > 1)
{
List<long> shortcut = new();
shortcut.Add(path[0]);
shortcut.AddRange(path.GetRange(cut, path.Count - cut));
return shortcut;
}
return path;
}
}

View File

@ -21,28 +21,34 @@ using System;
namespace DotRecast.Recast.Demo.Tools;
public class PolyUtils {
public static bool pointInPoly(float[] verts, float[] p) {
public class PolyUtils
{
public static bool pointInPoly(float[] verts, float[] p)
{
int i, j;
bool c = false;
for (i = 0, j = verts.Length / 3 - 1; i < verts.Length / 3; j = i++) {
for (i = 0, j = verts.Length / 3 - 1; i < verts.Length / 3; j = i++)
{
float[] vi = new float[] { verts[i * 3], verts[i * 3 + 1], verts[i * 3 + 2] };
float[] vj = new float[] { verts[j * 3], verts[j * 3 + 1], verts[j * 3 + 2] };
if (((vi[2] > p[2]) != (vj[2] > p[2]))
&& (p[0] < (vj[0] - vi[0]) * (p[2] - vi[2]) / (vj[2] - vi[2]) + vi[0])) {
&& (p[0] < (vj[0] - vi[0]) * (p[2] - vi[2]) / (vj[2] - vi[2]) + vi[0]))
{
c = !c;
}
}
return c;
}
public static int offsetPoly(float[] verts, int nverts, float offset, float[] outVerts, int maxOutVerts) {
public static int offsetPoly(float[] verts, int nverts, float offset, float[] outVerts, int maxOutVerts)
{
float MITER_LIMIT = 1.20f;
int n = 0;
for (int i = 0; i < nverts; i++) {
for (int i = 0; i < nverts; i++)
{
int a = (i + nverts - 1) % nverts;
int b = i;
int c = (i + 1) % nverts;
@ -52,19 +58,23 @@ public class PolyUtils {
float dx0 = verts[vb] - verts[va];
float dy0 = verts[vb + 2] - verts[va + 2];
float d0 = dx0 * dx0 + dy0 * dy0;
if (d0 > 1e-6f) {
if (d0 > 1e-6f)
{
d0 = (float)(1.0f / Math.Sqrt(d0));
dx0 *= d0;
dy0 *= d0;
}
float dx1 = verts[vc] - verts[vb];
float dy1 = verts[vc + 2] - verts[vb + 2];
float d1 = dx1 * dx1 + dy1 * dy1;
if (d1 > 1e-6f) {
if (d1 > 1e-6f)
{
d1 = (float)(1.0f / Math.Sqrt(d1));
dx1 *= d1;
dy1 *= d1;
}
float dlx0 = -dy0;
float dly0 = dx0;
float dlx1 = -dy1;
@ -74,16 +84,20 @@ public class PolyUtils {
float dmy = (dly0 + dly1) * 0.5f;
float dmr2 = dmx * dmx + dmy * dmy;
bool bevel = dmr2 * MITER_LIMIT * MITER_LIMIT < 1.0f;
if (dmr2 > 1e-6f) {
if (dmr2 > 1e-6f)
{
float scale = 1.0f / dmr2;
dmx *= scale;
dmy *= scale;
}
if (bevel && cross < 0.0f) {
if (n + 2 >= maxOutVerts) {
if (bevel && cross < 0.0f)
{
if (n + 2 >= maxOutVerts)
{
return 0;
}
float d = (1.0f - (dx0 * dx1 + dy0 * dy1)) * 0.5f;
outVerts[n * 3 + 0] = verts[vb] + (-dlx0 + dx0 * d) * offset;
outVerts[n * 3 + 1] = verts[vb + 1];
@ -93,10 +107,14 @@ public class PolyUtils {
outVerts[n * 3 + 1] = verts[vb + 1];
outVerts[n * 3 + 2] = verts[vb + 2] + (-dly1 - dy1 * d) * offset;
n++;
} else {
if (n + 1 >= maxOutVerts) {
}
else
{
if (n + 1 >= maxOutVerts)
{
return 0;
}
outVerts[n * 3 + 0] = verts[vb] - dmx * offset;
outVerts[n * 3 + 1] = verts[vb + 1];
outVerts[n * 3 + 2] = verts[vb + 2] - dmy * offset;
@ -106,5 +124,4 @@ public class PolyUtils {
return n;
}
}

View File

@ -1,12 +1,14 @@
namespace DotRecast.Recast.Demo.Tools;
public class SteerTarget {
public class SteerTarget
{
public readonly float[] steerPos;
public readonly int steerPosFlag;
public readonly long steerPosRef;
public readonly float[] steerPoints;
public SteerTarget(float[] steerPos, int steerPosFlag, long steerPosRef, float[] steerPoints) {
public SteerTarget(float[] steerPos, int steerPosFlag, long steerPosRef, float[] steerPoints)
{
this.steerPos = steerPos;
this.steerPosFlag = steerPosFlag;
this.steerPosRef = steerPosRef;

View File

@ -1,21 +1,18 @@
using System;
using System.Collections.Generic;
using Silk.NET.Windowing;
using DotRecast.Core;
using DotRecast.Detour;
using DotRecast.Recast.Demo.Builder;
using DotRecast.Recast.Demo.Draw;
using static DotRecast.Detour.DetourCommon;
using static DotRecast.Recast.Demo.Draw.DebugDraw;
using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives;
namespace DotRecast.Recast.Demo.Tools;
public class TestNavmeshTool : Tool {
public class TestNavmeshTool : Tool
{
private readonly static int MAX_POLYS = 256;
private readonly static int MAX_SMOOTH = 2048;
private Sample m_sample;
@ -44,7 +41,8 @@ public class TestNavmeshTool : Tool {
private readonly List<float[]> randomPoints = new();
private bool constrainByCircle;
private enum ToolMode {
private enum ToolMode
{
PATHFIND_FOLLOW,
PATHFIND_STRAIGHT,
PATHFIND_SLICED,
@ -56,27 +54,35 @@ public class TestNavmeshTool : Tool {
RANDOM_POINTS_IN_CIRCLE
}
public TestNavmeshTool() {
public TestNavmeshTool()
{
m_filter = new DefaultQueryFilter(SampleAreaModifications.SAMPLE_POLYFLAGS_ALL,
SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED, new float[] { 1f, 1f, 1f, 1f, 2f, 1.5f });
}
public override void setSample(Sample m_sample) {
public override void setSample(Sample m_sample)
{
this.m_sample = m_sample;
}
public override void handleClick(float[] s, float[] p, bool shift) {
if (shift) {
public override void handleClick(float[] s, float[] p, bool shift)
{
if (shift)
{
m_sposSet = true;
m_spos = ArrayUtils.CopyOf(p, p.Length);
} else {
}
else
{
m_eposSet = true;
m_epos = ArrayUtils.CopyOf(p, p.Length);
}
recalc();
}
public override void layout(IWindow ctx) {
public override void layout(IWindow ctx)
{
ToolMode previousToolMode = m_toolMode;
int previousStraightPathOptions = m_straightPathOptions;
int previousIncludeFlags = m_filter.getIncludeFlags();
@ -203,31 +209,46 @@ public class TestNavmeshTool : Tool {
// }
}
public override string getName() {
public override string getName()
{
return "Test Navmesh";
}
private void recalc() {
if (m_sample.getNavMesh() == null) {
private void recalc()
{
if (m_sample.getNavMesh() == null)
{
return;
}
NavMeshQuery m_navQuery = m_sample.getNavMeshQuery();
if (m_sposSet) {
if (m_sposSet)
{
m_startRef = m_navQuery.findNearestPoly(m_spos, m_polyPickExt, m_filter).result.getNearestRef();
} else {
}
else
{
m_startRef = 0;
}
if (m_eposSet) {
if (m_eposSet)
{
m_endRef = m_navQuery.findNearestPoly(m_epos, m_polyPickExt, m_filter).result.getNearestRef();
} else {
}
else
{
m_endRef = 0;
}
NavMesh m_navMesh = m_sample.getNavMesh();
if (m_toolMode == ToolMode.PATHFIND_FOLLOW) {
if (m_sposSet && m_eposSet && m_startRef != 0 && m_endRef != 0) {
if (m_toolMode == ToolMode.PATHFIND_FOLLOW)
{
if (m_sposSet && m_eposSet && m_startRef != 0 && m_endRef != 0)
{
m_polys = m_navQuery.findPath(m_startRef, m_endRef, m_spos, m_epos, m_filter,
enableRaycast ? NavMeshQuery.DT_FINDPATH_ANY_ANGLE : 0, float.MaxValue).result;
if (0 < m_polys.Count) {
if (0 < m_polys.Count)
{
List<long> polys = new(m_polys);
// Iterate over the path to find smooth path on the detail mesh surface.
float[] iterPos = m_navQuery.closestPointOnPoly(m_startRef, m_spos).result.getClosest();
@ -242,28 +263,37 @@ public class TestNavmeshTool : Tool {
// Move towards target a small advancement at a time until target reached or
// when ran out of memory to store the path.
while (0 < polys.Count && m_smoothPath.Count < MAX_SMOOTH) {
while (0 < polys.Count && m_smoothPath.Count < MAX_SMOOTH)
{
// Find location to steer towards.
SteerTarget steerTarget = PathUtils.getSteerTarget(m_navQuery, iterPos, targetPos,
SLOP, polys);
if (null == steerTarget) {
if (null == steerTarget)
{
break;
}
bool endOfPath = (steerTarget.steerPosFlag & NavMeshQuery.DT_STRAIGHTPATH_END) != 0
? true
: false;
bool offMeshConnection = (steerTarget.steerPosFlag
& NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0 ? true : false;
& NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0
? true
: false;
// Find movement delta.
float[] delta = vSub(steerTarget.steerPos, iterPos);
float len = (float)Math.Sqrt(DemoMath.vDot(delta, delta));
// If the steer target is end of path or off-mesh link, do not move past the location.
if ((endOfPath || offMeshConnection) && len < STEP_SIZE) {
if ((endOfPath || offMeshConnection) && len < STEP_SIZE)
{
len = 1;
} else {
}
else
{
len = STEP_SIZE / len;
}
float[] moveTgt = vMad(iterPos, delta, len);
// Move
Result<MoveAlongSurfaceResult> result = m_navQuery.moveAlongSurface(polys[0], iterPos,
@ -280,45 +310,57 @@ public class TestNavmeshTool : Tool {
polys = PathUtils.fixupShortcuts(polys, m_navQuery);
Result<float> polyHeight = m_navQuery.getPolyHeight(polys[0], moveAlongSurface.getResultPos());
if (polyHeight.succeeded()) {
if (polyHeight.succeeded())
{
iterPos[1] = polyHeight.result;
}
// Handle end of path and off-mesh links when close enough.
if (endOfPath && PathUtils.inRange(iterPos, steerTarget.steerPos, SLOP, 1.0f)) {
if (endOfPath && PathUtils.inRange(iterPos, steerTarget.steerPos, SLOP, 1.0f))
{
// Reached end of path.
vCopy(iterPos, targetPos);
if (m_smoothPath.Count < MAX_SMOOTH) {
if (m_smoothPath.Count < MAX_SMOOTH)
{
m_smoothPath.Add(iterPos);
}
break;
} else if (offMeshConnection
&& PathUtils.inRange(iterPos, steerTarget.steerPos, SLOP, 1.0f)) {
}
else if (offMeshConnection
&& PathUtils.inRange(iterPos, steerTarget.steerPos, SLOP, 1.0f))
{
// Reached off-mesh connection.
// Advance the path up to and over the off-mesh connection.
long prevRef = 0;
long polyRef = polys[0];
int npos = 0;
while (npos < polys.Count && polyRef != steerTarget.steerPosRef) {
while (npos < polys.Count && polyRef != steerTarget.steerPosRef)
{
prevRef = polyRef;
polyRef = polys[npos];
npos++;
}
polys = polys.GetRange(npos, polys.Count - npos);
// Handle the connection.
Result<Tuple<float[], float[]>> offMeshCon = m_navMesh
.getOffMeshConnectionPolyEndPoints(prevRef, polyRef);
if (offMeshCon.succeeded()) {
if (offMeshCon.succeeded())
{
float[] startPos = offMeshCon.result.Item1;
float[] endPos = offMeshCon.result.Item2;
if (m_smoothPath.Count < MAX_SMOOTH) {
if (m_smoothPath.Count < MAX_SMOOTH)
{
m_smoothPath.Add(startPos);
// Hack to make the dotted path not visible during off-mesh connection.
if ((m_smoothPath.Count & 1) != 0) {
if ((m_smoothPath.Count & 1) != 0)
{
m_smoothPath.Add(startPos);
}
}
// Move position at the other side of the off-mesh link.
vCopy(iterPos, endPos);
iterPos[1] = m_navQuery.getPolyHeight(polys[0], iterPos).result;
@ -326,99 +368,136 @@ public class TestNavmeshTool : Tool {
}
// Store results.
if (m_smoothPath.Count < MAX_SMOOTH) {
if (m_smoothPath.Count < MAX_SMOOTH)
{
m_smoothPath.Add(iterPos);
}
}
}
} else {
}
else
{
m_polys = null;
m_smoothPath = null;
}
} else if (m_toolMode == ToolMode.PATHFIND_STRAIGHT) {
if (m_sposSet && m_eposSet && m_startRef != 0 && m_endRef != 0) {
}
else if (m_toolMode == ToolMode.PATHFIND_STRAIGHT)
{
if (m_sposSet && m_eposSet && m_startRef != 0 && m_endRef != 0)
{
m_polys = m_navQuery.findPath(m_startRef, m_endRef, m_spos, m_epos, m_filter,
enableRaycast ? NavMeshQuery.DT_FINDPATH_ANY_ANGLE : 0, float.MaxValue).result;
if (0 < m_polys.Count) {
if (0 < m_polys.Count)
{
// In case of partial path, make sure the end point is clamped to the last polygon.
float[] epos = new float[] { m_epos[0], m_epos[1], m_epos[2] };
if (m_polys[m_polys.Count - 1] != m_endRef) {
if (m_polys[m_polys.Count - 1] != m_endRef)
{
Result<ClosestPointOnPolyResult> result = m_navQuery
.closestPointOnPoly(m_polys[m_polys.Count - 1], m_epos);
if (result.succeeded()) {
if (result.succeeded())
{
epos = result.result.getClosest();
}
}
m_straightPath = m_navQuery.findStraightPath(m_spos, epos, m_polys, MAX_POLYS,
m_straightPathOptions).result;
}
} else {
}
else
{
m_straightPath = null;
}
} else if (m_toolMode == ToolMode.PATHFIND_SLICED) {
}
else if (m_toolMode == ToolMode.PATHFIND_SLICED)
{
m_polys = null;
m_straightPath = null;
if (m_sposSet && m_eposSet && m_startRef != 0 && m_endRef != 0) {
if (m_sposSet && m_eposSet && m_startRef != 0 && m_endRef != 0)
{
m_pathFindStatus = m_navQuery.initSlicedFindPath(m_startRef, m_endRef, m_spos, m_epos, m_filter,
enableRaycast ? NavMeshQuery.DT_FINDPATH_ANY_ANGLE : 0, float.MaxValue);
}
} else if (m_toolMode == ToolMode.RAYCAST) {
}
else if (m_toolMode == ToolMode.RAYCAST)
{
m_straightPath = null;
if (m_sposSet && m_eposSet && m_startRef != 0) {
if (m_sposSet && m_eposSet && m_startRef != 0)
{
{
Result<RaycastHit> hit = m_navQuery.raycast(m_startRef, m_spos, m_epos, m_filter, 0, 0);
if (hit.succeeded()) {
if (hit.succeeded())
{
m_polys = hit.result.path;
if (hit.result.t > 1) {
if (hit.result.t > 1)
{
// No hit
m_hitPos = ArrayUtils.CopyOf(m_epos, m_epos.Length);
m_hitResult = false;
} else {
}
else
{
// Hit
m_hitPos = vLerp(m_spos, m_epos, hit.result.t);
m_hitNormal = ArrayUtils.CopyOf(hit.result.hitNormal, hit.result.hitNormal.Length);
m_hitResult = true;
}
// Adjust height.
if (hit.result.path.Count > 0) {
if (hit.result.path.Count > 0)
{
Result<float> result = m_navQuery
.getPolyHeight(hit.result.path[hit.result.path.Count - 1], m_hitPos);
if (result.succeeded()) {
if (result.succeeded())
{
m_hitPos[1] = result.result;
}
}
}
m_straightPath = new();
m_straightPath.Add(new StraightPathItem(m_spos, 0, 0));
m_straightPath.Add(new StraightPathItem(m_hitPos, 0, 0));
}
}
} else if (m_toolMode == ToolMode.DISTANCE_TO_WALL) {
}
else if (m_toolMode == ToolMode.DISTANCE_TO_WALL)
{
m_distanceToWall = 0;
if (m_sposSet && m_startRef != 0) {
if (m_sposSet && m_startRef != 0)
{
m_distanceToWall = 0.0f;
Result<FindDistanceToWallResult> result = m_navQuery.findDistanceToWall(m_startRef, m_spos, 100.0f,
m_filter);
if (result.succeeded()) {
if (result.succeeded())
{
m_distanceToWall = result.result.getDistance();
m_hitPos = result.result.getPosition();
m_hitNormal = result.result.getNormal();
}
}
} else if (m_toolMode == ToolMode.FIND_POLYS_IN_CIRCLE) {
if (m_sposSet && m_startRef != 0 && m_eposSet) {
}
else if (m_toolMode == ToolMode.FIND_POLYS_IN_CIRCLE)
{
if (m_sposSet && m_startRef != 0 && m_eposSet)
{
float dx = m_epos[0] - m_spos[0];
float dz = m_epos[2] - m_spos[2];
float dist = (float)Math.Sqrt(dx * dx + dz * dz);
Result<FindPolysAroundResult> result = m_navQuery.findPolysAroundCircle(m_startRef, m_spos, dist,
m_filter);
if (result.succeeded()) {
if (result.succeeded())
{
m_polys = result.result.getRefs();
m_parent = result.result.getParentRefs();
}
}
} else if (m_toolMode == ToolMode.FIND_POLYS_IN_SHAPE) {
if (m_sposSet && m_startRef != 0 && m_eposSet) {
}
else if (m_toolMode == ToolMode.FIND_POLYS_IN_SHAPE)
{
if (m_sposSet && m_startRef != 0 && m_eposSet)
{
float nx = (m_epos[2] - m_spos[2]) * 0.25f;
float nz = -(m_epos[0] - m_spos[0]) * 0.25f;
float agentHeight = m_sample != null ? m_sample.getSettingsUI().getAgentHeight() : 0;
@ -440,33 +519,44 @@ public class TestNavmeshTool : Tool {
m_queryPoly[11] = m_epos[2] + nz;
Result<FindPolysAroundResult> result = m_navQuery.findPolysAroundShape(m_startRef, m_queryPoly, m_filter);
if (result.succeeded()) {
if (result.succeeded())
{
m_polys = result.result.getRefs();
m_parent = result.result.getParentRefs();
}
}
} else if (m_toolMode == ToolMode.FIND_LOCAL_NEIGHBOURHOOD) {
if (m_sposSet && m_startRef != 0) {
}
else if (m_toolMode == ToolMode.FIND_LOCAL_NEIGHBOURHOOD)
{
if (m_sposSet && m_startRef != 0)
{
m_neighbourhoodRadius = m_sample.getSettingsUI().getAgentRadius() * 20.0f;
Result<FindLocalNeighbourhoodResult> result = m_navQuery.findLocalNeighbourhood(m_startRef, m_spos,
m_neighbourhoodRadius, m_filter);
if (result.succeeded()) {
if (result.succeeded())
{
m_polys = result.result.getRefs();
m_parent = result.result.getParentRefs();
}
}
} else if (m_toolMode == ToolMode.RANDOM_POINTS_IN_CIRCLE) {
}
else if (m_toolMode == ToolMode.RANDOM_POINTS_IN_CIRCLE)
{
randomPoints.Clear();
if (m_sposSet && m_startRef != 0 && m_eposSet) {
if (m_sposSet && m_startRef != 0 && m_eposSet)
{
float dx = m_epos[0] - m_spos[0];
float dz = m_epos[2] - m_spos[2];
float dist = (float)Math.Sqrt(dx * dx + dz * dz);
PolygonByCircleConstraint constraint = constrainByCircle ? PolygonByCircleConstraint.strict()
PolygonByCircleConstraint constraint = constrainByCircle
? PolygonByCircleConstraint.strict()
: PolygonByCircleConstraint.noop();
for (int i = 0; i < 200; i++) {
for (int i = 0; i < 200; i++)
{
Result<FindRandomPointResult> result = m_navQuery.findRandomPointAroundCircle(m_startRef, m_spos, dist,
m_filter, new NavMeshQuery.FRand(), constraint);
if (result.succeeded()) {
if (result.succeeded())
{
randomPoints.Add(result.result.getRandomPt());
}
}
@ -474,10 +564,13 @@ public class TestNavmeshTool : Tool {
}
}
public override void handleRender(NavMeshRenderer renderer) {
if (m_sample == null) {
public override void handleRender(NavMeshRenderer renderer)
{
if (m_sample == null)
{
return;
}
RecastDebugDraw dd = renderer.getDebugDraw();
int startCol = duRGBA(128, 25, 0, 192);
int endCol = duRGBA(51, 102, 0, 129);
@ -487,38 +580,52 @@ public class TestNavmeshTool : Tool {
float agentHeight = m_sample.getSettingsUI().getAgentHeight();
float agentClimb = m_sample.getSettingsUI().getAgentMaxClimb();
if (m_sposSet) {
if (m_sposSet)
{
drawAgent(dd, m_spos, startCol);
}
if (m_eposSet) {
if (m_eposSet)
{
drawAgent(dd, m_epos, endCol);
}
dd.depthMask(true);
NavMesh m_navMesh = m_sample.getNavMesh();
if (m_navMesh == null) {
if (m_navMesh == null)
{
return;
}
if (m_toolMode == ToolMode.PATHFIND_FOLLOW) {
if (m_toolMode == ToolMode.PATHFIND_FOLLOW)
{
dd.debugDrawNavMeshPoly(m_navMesh, m_startRef, startCol);
dd.debugDrawNavMeshPoly(m_navMesh, m_endRef, endCol);
if (m_polys != null) {
foreach (long poly in m_polys) {
if (poly == m_startRef || poly == m_endRef) {
if (m_polys != null)
{
foreach (long poly in m_polys)
{
if (poly == m_startRef || poly == m_endRef)
{
continue;
}
dd.debugDrawNavMeshPoly(m_navMesh, poly, pathCol);
}
}
if (m_smoothPath != null) {
if (m_smoothPath != null)
{
dd.depthMask(false);
int spathCol = duRGBA(0, 0, 0, 220);
dd.begin(LINES, 3.0f);
for (int i = 0; i < m_smoothPath.Count; ++i) {
for (int i = 0; i < m_smoothPath.Count; ++i)
{
dd.vertex(m_smoothPath[i][0], m_smoothPath[i][1] + 0.1f, m_smoothPath[i][2], spathCol);
}
dd.end();
dd.depthMask(true);
}
@ -556,60 +663,87 @@ public class TestNavmeshTool : Tool {
dd.depthMask(true);
}
*/
} else if (m_toolMode == ToolMode.PATHFIND_STRAIGHT || m_toolMode == ToolMode.PATHFIND_SLICED) {
}
else if (m_toolMode == ToolMode.PATHFIND_STRAIGHT || m_toolMode == ToolMode.PATHFIND_SLICED)
{
dd.debugDrawNavMeshPoly(m_navMesh, m_startRef, startCol);
dd.debugDrawNavMeshPoly(m_navMesh, m_endRef, endCol);
if (m_polys != null) {
foreach (long poly in m_polys) {
if (m_polys != null)
{
foreach (long poly in m_polys)
{
dd.debugDrawNavMeshPoly(m_navMesh, poly, pathCol);
}
}
if (m_straightPath != null) {
if (m_straightPath != null)
{
dd.depthMask(false);
int spathCol = duRGBA(64, 16, 0, 220);
int offMeshCol = duRGBA(128, 96, 0, 220);
dd.begin(LINES, 2.0f);
for (int i = 0; i < m_straightPath.Count - 1; ++i) {
for (int i = 0; i < m_straightPath.Count - 1; ++i)
{
StraightPathItem straightPathItem = m_straightPath[i];
StraightPathItem straightPathItem2 = m_straightPath[i + 1];
int col;
if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) {
if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
{
col = offMeshCol;
} else {
}
else
{
col = spathCol;
}
dd.vertex(straightPathItem.getPos()[0], straightPathItem.getPos()[1] + 0.4f,
straightPathItem.getPos()[2], col);
dd.vertex(straightPathItem2.getPos()[0], straightPathItem2.getPos()[1] + 0.4f,
straightPathItem2.getPos()[2], col);
}
dd.end();
dd.begin(POINTS, 6.0f);
for (int i = 0; i < m_straightPath.Count; ++i) {
for (int i = 0; i < m_straightPath.Count; ++i)
{
StraightPathItem straightPathItem = m_straightPath[i];
int col;
if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_START) != 0) {
if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_START) != 0)
{
col = startCol;
} else if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_END) != 0) {
}
else if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_END) != 0)
{
col = endCol;
} else if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) {
}
else if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
{
col = offMeshCol;
} else {
}
else
{
col = spathCol;
}
dd.vertex(straightPathItem.getPos()[0], straightPathItem.getPos()[1] + 0.4f,
straightPathItem.getPos()[2], col);
}
dd.end();
dd.depthMask(true);
}
} else if (m_toolMode == ToolMode.RAYCAST) {
}
else if (m_toolMode == ToolMode.RAYCAST)
{
dd.debugDrawNavMeshPoly(m_navMesh, m_startRef, startCol);
if (m_straightPath != null) {
if (m_polys != null) {
foreach (long poly in m_polys) {
if (m_straightPath != null)
{
if (m_polys != null)
{
foreach (long poly in m_polys)
{
dd.debugDrawNavMeshPoly(m_navMesh, poly, pathCol);
}
}
@ -617,7 +751,8 @@ public class TestNavmeshTool : Tool {
dd.depthMask(false);
int spathCol = m_hitResult ? duRGBA(64, 16, 0, 220) : duRGBA(240, 240, 240, 220);
dd.begin(LINES, 2.0f);
for (int i = 0; i < m_straightPath.Count - 1; ++i) {
for (int i = 0; i < m_straightPath.Count - 1; ++i)
{
StraightPathItem straightPathItem = m_straightPath[i];
StraightPathItem straightPathItem2 = m_straightPath[i + 1];
dd.vertex(straightPathItem.getPos()[0], straightPathItem.getPos()[1] + 0.4f,
@ -625,16 +760,20 @@ public class TestNavmeshTool : Tool {
dd.vertex(straightPathItem2.getPos()[0], straightPathItem2.getPos()[1] + 0.4f,
straightPathItem2.getPos()[2], spathCol);
}
dd.end();
dd.begin(POINTS, 4.0f);
for (int i = 0; i < m_straightPath.Count; ++i) {
for (int i = 0; i < m_straightPath.Count; ++i)
{
StraightPathItem straightPathItem = m_straightPath[i];
dd.vertex(straightPathItem.getPos()[0], straightPathItem.getPos()[1] + 0.4f,
straightPathItem.getPos()[2], spathCol);
}
dd.end();
if (m_hitResult) {
if (m_hitResult)
{
int hitCol = duRGBA(0, 0, 0, 128);
dd.begin(LINES, 2.0f);
dd.vertex(m_hitPos[0], m_hitPos[1] + 0.4f, m_hitPos[2], hitCol);
@ -643,28 +782,40 @@ public class TestNavmeshTool : Tool {
m_hitPos[2] + m_hitNormal[2] * agentRadius, hitCol);
dd.end();
}
dd.depthMask(true);
}
} else if (m_toolMode == ToolMode.DISTANCE_TO_WALL) {
}
else if (m_toolMode == ToolMode.DISTANCE_TO_WALL)
{
dd.debugDrawNavMeshPoly(m_navMesh, m_startRef, startCol);
dd.depthMask(false);
if (m_spos != null) {
if (m_spos != null)
{
dd.debugDrawCircle(m_spos[0], m_spos[1] + agentHeight / 2, m_spos[2], m_distanceToWall,
duRGBA(64, 16, 0, 220), 2.0f);
}
if (m_hitPos != null) {
if (m_hitPos != null)
{
dd.begin(LINES, 3.0f);
dd.vertex(m_hitPos[0], m_hitPos[1] + 0.02f, m_hitPos[2], duRGBA(0, 0, 0, 192));
dd.vertex(m_hitPos[0], m_hitPos[1] + agentHeight, m_hitPos[2], duRGBA(0, 0, 0, 192));
dd.end();
}
dd.depthMask(true);
} else if (m_toolMode == ToolMode.FIND_POLYS_IN_CIRCLE) {
if (m_polys != null) {
for (int i = 0; i < m_polys.Count; i++) {
}
else if (m_toolMode == ToolMode.FIND_POLYS_IN_CIRCLE)
{
if (m_polys != null)
{
for (int i = 0; i < m_polys.Count; i++)
{
dd.debugDrawNavMeshPoly(m_navMesh, m_polys[i], pathCol);
dd.depthMask(false);
if (m_parent[i] != 0) {
if (m_parent[i] != 0)
{
dd.depthMask(false);
float[] p0 = getPolyCenter(m_navMesh, m_parent[i]);
float[] p1 = getPolyCenter(m_navMesh, m_polys[i]);
@ -672,11 +823,13 @@ public class TestNavmeshTool : Tool {
duRGBA(0, 0, 0, 128), 2.0f);
dd.depthMask(true);
}
dd.depthMask(true);
}
}
if (m_sposSet && m_eposSet) {
if (m_sposSet && m_eposSet)
{
dd.depthMask(false);
float dx = m_epos[0] - m_spos[0];
float dz = m_epos[2] - m_spos[2];
@ -685,12 +838,17 @@ public class TestNavmeshTool : Tool {
2.0f);
dd.depthMask(true);
}
} else if (m_toolMode == ToolMode.FIND_POLYS_IN_SHAPE) {
if (m_polys != null) {
for (int i = 0; i < m_polys.Count; i++) {
}
else if (m_toolMode == ToolMode.FIND_POLYS_IN_SHAPE)
{
if (m_polys != null)
{
for (int i = 0; i < m_polys.Count; i++)
{
dd.debugDrawNavMeshPoly(m_navMesh, m_polys[i], pathCol);
dd.depthMask(false);
if (m_parent[i] != 0) {
if (m_parent[i] != 0)
{
dd.depthMask(false);
float[] p0 = getPolyCenter(m_navMesh, m_parent[i]);
float[] p1 = getPolyCenter(m_navMesh, m_polys[i]);
@ -698,27 +856,36 @@ public class TestNavmeshTool : Tool {
duRGBA(0, 0, 0, 128), 2.0f);
dd.depthMask(true);
}
dd.depthMask(true);
}
}
if (m_sposSet && m_eposSet) {
if (m_sposSet && m_eposSet)
{
dd.depthMask(false);
int col = duRGBA(64, 16, 0, 220);
dd.begin(LINES, 2.0f);
for (int i = 0, j = 3; i < 4; j = i++) {
for (int i = 0, j = 3; i < 4; j = i++)
{
dd.vertex(m_queryPoly[j * 3], m_queryPoly[j * 3 + 1], m_queryPoly[j * 3 + 2], col);
dd.vertex(m_queryPoly[i * 3], m_queryPoly[i * 3 + 1], m_queryPoly[i * 3 + 2], col);
}
dd.end();
dd.depthMask(true);
}
} else if (m_toolMode == ToolMode.FIND_LOCAL_NEIGHBOURHOOD) {
if (m_polys != null) {
for (int i = 0; i < m_polys.Count; i++) {
}
else if (m_toolMode == ToolMode.FIND_LOCAL_NEIGHBOURHOOD)
{
if (m_polys != null)
{
for (int i = 0; i < m_polys.Count; i++)
{
dd.debugDrawNavMeshPoly(m_navMesh, m_polys[i], pathCol);
dd.depthMask(false);
if (m_parent[i] != 0) {
if (m_parent[i] != 0)
{
dd.depthMask(false);
float[] p0 = getPolyCenter(m_navMesh, m_parent[i]);
float[] p1 = getPolyCenter(m_navMesh, m_polys[i]);
@ -726,36 +893,47 @@ public class TestNavmeshTool : Tool {
duRGBA(0, 0, 0, 128), 2.0f);
dd.depthMask(true);
}
dd.depthMask(true);
if (m_sample.getNavMeshQuery() != null) {
if (m_sample.getNavMeshQuery() != null)
{
Result<GetPolyWallSegmentsResult> result = m_sample.getNavMeshQuery()
.getPolyWallSegments(m_polys[i], false, m_filter);
if (result.succeeded()) {
if (result.succeeded())
{
dd.begin(LINES, 2.0f);
GetPolyWallSegmentsResult wallSegments = result.result;
for (int j = 0; j < wallSegments.getSegmentVerts().Count; ++j) {
for (int j = 0; j < wallSegments.getSegmentVerts().Count; ++j)
{
float[] s = wallSegments.getSegmentVerts()[j];
float[] s3 = new float[] { s[3], s[4], s[5] };
// Skip too distant segments.
Tuple<float, float> distSqr = DetourCommon.distancePtSegSqr2D(m_spos, s, 0, 3);
if (distSqr.Item1 > DemoMath.sqr(m_neighbourhoodRadius)) {
if (distSqr.Item1 > DemoMath.sqr(m_neighbourhoodRadius))
{
continue;
}
float[] delta = vSub(s3, s);
float[] p0 = vMad(s, delta, 0.5f);
float[] norm = new float[] { delta[2], 0, -delta[0] };
vNormalize(norm);
float[] p1 = vMad(p0, norm, agentRadius * 0.5f);
// Skip backfacing segments.
if (wallSegments.getSegmentRefs()[j] != 0) {
if (wallSegments.getSegmentRefs()[j] != 0)
{
int col = duRGBA(255, 255, 255, 32);
dd.vertex(s[0], s[1] + agentClimb, s[2], col);
dd.vertex(s[3], s[4] + agentClimb, s[5], col);
} else {
}
else
{
int col = duRGBA(192, 32, 16, 192);
if (DetourCommon.triArea2D(m_spos, s, s3) < 0.0f) {
if (DetourCommon.triArea2D(m_spos, s, s3) < 0.0f)
{
col = duRGBA(96, 32, 16, 192);
}
dd.vertex(p0[0], p0[1] + agentClimb, p0[2], col);
dd.vertex(p1[0], p1[1] + agentClimb, p1[2], col);
@ -763,6 +941,7 @@ public class TestNavmeshTool : Tool {
dd.vertex(s[3], s[4] + agentClimb, s[5], col);
}
}
dd.end();
}
}
@ -770,22 +949,28 @@ public class TestNavmeshTool : Tool {
dd.depthMask(true);
}
if (m_sposSet) {
if (m_sposSet)
{
dd.depthMask(false);
dd.debugDrawCircle(m_spos[0], m_spos[1] + agentHeight / 2, m_spos[2], m_neighbourhoodRadius,
duRGBA(64, 16, 0, 220), 2.0f);
dd.depthMask(true);
}
}
} else if (m_toolMode == ToolMode.RANDOM_POINTS_IN_CIRCLE) {
}
else if (m_toolMode == ToolMode.RANDOM_POINTS_IN_CIRCLE)
{
dd.depthMask(false);
dd.begin(POINTS, 4.0f);
int col = duRGBA(64, 16, 0, 220);
foreach (float[] point in randomPoints) {
foreach (float[] point in randomPoints)
{
dd.vertex(point[0], point[1] + 0.1f, point[2], col);
}
dd.end();
if (m_sposSet && m_eposSet) {
if (m_sposSet && m_eposSet)
{
dd.depthMask(false);
float dx = m_epos[0] - m_spos[0];
float dz = m_epos[2] - m_spos[2];
@ -794,11 +979,13 @@ public class TestNavmeshTool : Tool {
2.0f);
dd.depthMask(true);
}
dd.depthMask(true);
}
}
private void drawAgent(RecastDebugDraw dd, float[] pos, int col) {
private void drawAgent(RecastDebugDraw dd, float[] pos, int col)
{
float r = m_sample.getSettingsUI().getAgentRadius();
float h = m_sample.getSettingsUI().getAgentHeight();
float c = m_sample.getSettingsUI().getAgentMaxClimb();
@ -818,47 +1005,60 @@ public class TestNavmeshTool : Tool {
dd.depthMask(true);
}
private float[] getPolyCenter(NavMesh navMesh, long refs) {
private float[] getPolyCenter(NavMesh navMesh, long refs)
{
float[] center = new float[3];
center[0] = 0;
center[1] = 0;
center[2] = 0;
Result<Tuple<MeshTile, Poly>> tileAndPoly = navMesh.getTileAndPolyByRef(refs);
if (tileAndPoly.succeeded()) {
if (tileAndPoly.succeeded())
{
MeshTile tile = tileAndPoly.result.Item1;
Poly poly = tileAndPoly.result.Item2;
for (int i = 0; i < poly.vertCount; ++i) {
for (int i = 0; i < poly.vertCount; ++i)
{
int v = poly.verts[i] * 3;
center[0] += tile.data.verts[v];
center[1] += tile.data.verts[v + 1];
center[2] += tile.data.verts[v + 2];
}
float s = 1.0f / poly.vertCount;
center[0] *= s;
center[1] *= s;
center[2] *= s;
}
return center;
}
public override void handleUpdate(float dt) {
public override void handleUpdate(float dt)
{
// TODO Auto-generated method stub
if (m_toolMode == ToolMode.PATHFIND_SLICED) {
if (m_toolMode == ToolMode.PATHFIND_SLICED)
{
NavMeshQuery m_navQuery = m_sample.getNavMeshQuery();
if (m_pathFindStatus.isInProgress()) {
if (m_pathFindStatus.isInProgress())
{
m_pathFindStatus = m_navQuery.updateSlicedFindPath(1).status;
}
if (m_pathFindStatus.isSuccess()) {
if (m_pathFindStatus.isSuccess())
{
m_polys = m_navQuery.finalizeSlicedFindPath().result;
m_straightPath = null;
if (m_polys != null) {
if (m_polys != null)
{
// In case of partial path, make sure the end point is clamped to the last polygon.
float[] epos = new float[3];
DetourCommon.vCopy(epos, m_epos);
if (m_polys[m_polys.Count - 1] != m_endRef) {
if (m_polys[m_polys.Count - 1] != m_endRef)
{
Result<ClosestPointOnPolyResult> result = m_navQuery
.closestPointOnPoly(m_polys[m_polys.Count - 1], m_epos);
if (result.succeeded()) {
if (result.succeeded())
{
epos = result.result.getClosest();
}
}
@ -872,9 +1072,9 @@ public class TestNavmeshTool : Tool {
}
}
}
m_pathFindStatus = Status.FAILURE;
}
}
}
}

View File

@ -23,8 +23,8 @@ using Silk.NET.Windowing;
namespace DotRecast.Recast.Demo.Tools;
public abstract class Tool {
public abstract class Tool
{
public abstract void setSample(Sample m_sample);
public abstract void handleClick(float[] s, float[] p, bool shift);

View File

@ -20,7 +20,7 @@ using Silk.NET.Windowing;
namespace DotRecast.Recast.Demo.Tools;
public interface ToolUIModule {
public interface ToolUIModule
{
void layout(IWindow ctx);
}

View File

@ -23,18 +23,20 @@ using Silk.NET.Windowing;
namespace DotRecast.Recast.Demo.Tools;
public class ToolsUI : IRcView {
public class ToolsUI : IRcView
{
//private readonly NkColor white = NkColor.create();
private Tool currentTool;
private bool enabled;
private readonly Tool[] tools;
public ToolsUI(params Tool[] tools) {
public ToolsUI(params Tool[] tools)
{
this.tools = tools;
}
public bool render(IWindow ctx, int x, int y, int width, int height, int mouseX, int mouseY) {
public bool render(IWindow ctx, int x, int y, int width, int height, int mouseX, int mouseY)
{
bool mouseInside = false;
// nk_rgb(255, 255, 255, white);
// try (MemoryStack stack = stackPush()) {
@ -63,20 +65,23 @@ public class ToolsUI : IRcView {
return mouseInside;
}
public void setEnabled(bool enabled) {
public void setEnabled(bool enabled)
{
this.enabled = enabled;
}
public Tool getTool() {
public Tool getTool()
{
return currentTool;
}
public void setSample(Sample sample) {
public void setSample(Sample sample)
{
tools.forEach(t => t.setSample(sample));
}
public void handleUpdate(float dt) {
public void handleUpdate(float dt)
{
tools.forEach(t => t.handleUpdate(dt));
}
}

View File

@ -20,7 +20,7 @@ using Silk.NET.Windowing;
namespace DotRecast.Recast.Demo.UI;
public interface IRcView {
public interface IRcView
{
bool render(IWindow ctx, int x, int y, int width, int height, int mouseX, int mouseY);
}

View File

@ -22,8 +22,8 @@ using Silk.NET.Windowing;
namespace DotRecast.Recast.Demo.UI;
public class Mouse {
public class Mouse
{
private double x;
private double y;
private double scrollX;
@ -52,80 +52,103 @@ public class Mouse {
// glfwSetScrollCallback(window, (win, x, y) => scroll(x, y));
}
public void cursorPos(double x, double y) {
foreach (MouseListener l in listeners) {
public void cursorPos(double x, double y)
{
foreach (MouseListener l in listeners)
{
l.position(x, y);
}
this.x = x;
this.y = y;
}
public void scroll(double xoffset, double yoffset) {
foreach (MouseListener l in listeners) {
public void scroll(double xoffset, double yoffset)
{
foreach (MouseListener l in listeners)
{
l.scroll(xoffset, yoffset);
}
scrollX += xoffset;
scrollY += yoffset;
}
public double getDX() {
public double getDX()
{
return x - px;
}
public double getDY() {
public double getDY()
{
return y - py;
}
public double getDScrollX() {
public double getDScrollX()
{
return scrollX - pScrollX;
}
public double getDScrollY() {
public double getDScrollY()
{
return scrollY - pScrollY;
}
public double getX() {
public double getX()
{
return x;
}
public void setX(double x) {
public void setX(double x)
{
this.x = x;
}
public double getY() {
public double getY()
{
return y;
}
public void setY(double y) {
public void setY(double y)
{
this.y = y;
}
public void setDelta() {
public void setDelta()
{
px = x;
py = y;
pScrollX = scrollX;
pScrollY = scrollY;
}
public void buttonPress(int button, int mods) {
foreach (MouseListener l in listeners) {
public void buttonPress(int button, int mods)
{
foreach (MouseListener l in listeners)
{
l.button(button, mods, true);
}
pressed.Add(button);
}
public void buttonRelease(int button, int mods) {
foreach (MouseListener l in listeners) {
public void buttonRelease(int button, int mods)
{
foreach (MouseListener l in listeners)
{
l.button(button, mods, false);
}
pressed.Remove(button);
}
public bool isPressed(int button) {
public bool isPressed(int button)
{
return pressed.Contains(button);
}
public void addListener(MouseListener listener) {
public void addListener(MouseListener listener)
{
listeners.Add(listener);
}
}

View File

@ -15,14 +15,14 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Recast.Demo.UI;
public interface MouseListener {
public interface MouseListener
{
void button(int button, int mods, bool down);
void scroll(double xoffset, double yoffset);
void position(double x, double y);
}

View File

@ -23,9 +23,8 @@ using Microsoft.DotNet.PlatformAbstractions;
namespace DotRecast.Recast.Demo.UI;
public class NuklearGL {
public class NuklearGL
{
private static readonly int BUFFER_INITIAL_SIZE = 4 * 1024;
private static readonly int MAX_VERTEX_BUFFER = 512 * 1024;
private static readonly int MAX_ELEMENT_BUFFER = 128 * 1024;
@ -34,6 +33,7 @@ public class NuklearGL {
private static readonly int FONT_HEIGHT = 15;
private readonly RcViewSystem context;
// private readonly NkDrawNullTexture null_texture = NkDrawNullTexture.create();
// private readonly NkBuffer cmds = NkBuffer.create();
// private readonly NkUserFont default_font;
@ -42,10 +42,12 @@ public class NuklearGL {
private readonly int uniform_proj;
private readonly int vbo;
private readonly int ebo;
private readonly int vao;
//private readonly Buffer vertexLayout;
public NuklearGL(RcViewSystem context) {
public NuklearGL(RcViewSystem context)
{
this.context = context;
// nk_buffer_init(cmds, context.allocator, BUFFER_INITIAL_SIZE);
// vertexLayout = NkDrawVertexLayoutElement.create(4)//
@ -128,7 +130,8 @@ public class NuklearGL {
// nk_style_set_font(context.ctx, default_font);
}
private long setupFont() {
private long setupFont()
{
return 0;
// NkUserFont font = NkUserFont.create();
// ByteBuffer ttf;
@ -240,7 +243,8 @@ public class NuklearGL {
// return font;
}
void render(long win, int AA) {
void render(long win, int AA)
{
// int width;
// int height;
// int display_width;

View File

@ -20,8 +20,8 @@ using System.Runtime.InteropServices.JavaScript;
namespace DotRecast.Recast.Demo.UI;
public class NuklearUIHelper {
public class NuklearUIHelper
{
// public static void nk_color_rgb(IWindow ctx, NkColorf color) {
// try (MemoryStack stack = stackPush()) {
// if (nk_combo_begin_color(ctx, nk_rgb_cf(color, NkColor.mallocStack(stack)),

View File

@ -23,11 +23,13 @@ using Silk.NET.Windowing;
namespace DotRecast.Recast.Demo.UI;
public class RcViewSystem {
public class RcViewSystem
{
// readonly NkAllocator allocator;
private readonly IWindow _window;
private readonly GL _gl;
// readonly NkColor background;
// readonly NkColor white;
private readonly IRcView[] _views;
@ -61,7 +63,8 @@ public class RcViewSystem {
_views = views;
}
private void setupMouse(Mouse mouse) {
private void setupMouse(Mouse mouse)
{
// mouse.addListener(new MouseListener() {
//
// @Override
@ -99,7 +102,8 @@ public class RcViewSystem {
// });
}
private void setupClipboard(long window) {
private void setupClipboard(long window)
{
// ctx.clip().copy((handle, text, len) => {
// if (len == 0) {
// return;
@ -120,11 +124,13 @@ public class RcViewSystem {
// });
}
public void inputBegin() {
public void inputBegin()
{
//nk_input_begin(ctx);
}
public void inputEnd(IWindow win) {
public void inputEnd(IWindow win)
{
// NkMouse mouse = ctx.input().mouse();
// if (mouse.grab()) {
// glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
@ -140,11 +146,14 @@ public class RcViewSystem {
// nk_input_end(ctx);
}
public bool render(IWindow ctx, int x, int y, int width, int height, int mouseX, int mouseY) {
public bool render(IWindow ctx, int x, int y, int width, int height, int mouseX, int mouseY)
{
mouseOverUI = false;
foreach (IRcView m in _views) {
foreach (IRcView m in _views)
{
mouseOverUI = m.render(ctx, x, y, width, height, mouseX, mouseY) | mouseOverUI;
}
return mouseOverUI;
}
}

View File

@ -20,8 +20,6 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast
{
public class AreaModification
{
public readonly int RC_AREA_FLAGS_MASK = 0x3F;

View File

@ -20,8 +20,6 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast
{
/** Provides information on the content of a cell column in a compact heightfield. */
public class CompactCell
{

View File

@ -20,8 +20,6 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast
{
/** A compact, static heightfield representing unobstructed space. */
public class CompactHeightfield
{

View File

@ -20,8 +20,6 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast
{
/** Represents a span of unobstructed space within a compact heightfield. */
public class CompactSpan
{

View File

@ -20,8 +20,6 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast
{
/** Represents a simple, non-overlapping contour in field space. */
public class Contour
{

View File

@ -22,8 +22,6 @@ using System.Collections.Generic;
namespace DotRecast.Recast
{
/** Represents a group of related contours. */
public class ContourSet
{

View File

@ -20,8 +20,6 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast
{
public class ConvexVolume
{
public float[] verts;

View File

@ -23,8 +23,6 @@ using System.Collections.Generic;
namespace DotRecast.Recast.Geom
{
public class ChunkyTriMesh
{
private class BoundsItem

View File

@ -1,7 +1,5 @@
namespace DotRecast.Recast.Geom
{
public class ChunkyTriMeshNode
{
public readonly float[] bmin = new float[2];
@ -9,5 +7,4 @@ public class ChunkyTriMeshNode
public int i;
public int[] tris;
}
}

View File

@ -20,8 +20,6 @@ using System.Collections.Generic;
namespace DotRecast.Recast.Geom
{
public interface ConvexVolumeProvider
{
IList<ConvexVolume> convexVolumes();

View File

@ -22,8 +22,6 @@ using System.Collections.Generic;
namespace DotRecast.Recast.Geom
{
public interface InputGeomProvider : ConvexVolumeProvider
{
float[] getMeshBoundsMin();

View File

@ -24,8 +24,6 @@ using System.Collections.Immutable;
namespace DotRecast.Recast.Geom
{
public class SimpleInputGeomProvider : InputGeomProvider
{
public readonly float[] vertices;
@ -104,7 +102,8 @@ public class SimpleInputGeomProvider : InputGeomProvider
volumes.Add(vol);
}
public IEnumerable<TriMesh> meshes() {
public IEnumerable<TriMesh> meshes()
{
return ImmutableArray.Create(new TriMesh(vertices, faces));
}

View File

@ -22,8 +22,6 @@ using System.Collections.Immutable;
namespace DotRecast.Recast.Geom
{
public class SingleTrimeshInputGeomProvider : InputGeomProvider
{
private readonly float[] bmin;

View File

@ -22,8 +22,6 @@ using System.Collections.Generic;
namespace DotRecast.Recast.Geom
{
public class TriMesh
{
private readonly float[] vertices;

View File

@ -20,8 +20,6 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast
{
/** Represents a heightfield layer within a layer set. */
public class Heightfield
{

View File

@ -20,8 +20,6 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast
{
/// Represents a set of heightfield layers.
/// @ingroup recast
/// @see rcAllocHeightfieldLayerSet, rcFreeHeightfieldLayerSet

View File

@ -1,7 +1,5 @@
namespace DotRecast.Recast
{
public class InputGeomReader
{
}

View File

@ -23,8 +23,6 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Recast
{
public static class ObjImporter
{
public class ObjImporterContext

View File

@ -1,7 +1,5 @@
namespace DotRecast.Recast
{
/// < Tessellate edges between areas during contour
/// simplification.
public enum PartitionType

View File

@ -19,8 +19,6 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast
{
/** Represents a polygon mesh suitable for use in building a navigation mesh. */
public class PolyMesh
{

View File

@ -20,8 +20,6 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast
{
/**
* Contains triangle meshes that represent detailed height data associated with the polygons in its associated polygon
* mesh object.

View File

@ -22,8 +22,6 @@ using System;
namespace DotRecast.Recast
{
using static RecastConstants;
public class Recast

View File

@ -22,8 +22,6 @@ using System;
namespace DotRecast.Recast
{
using static RecastConstants;
public class RecastArea

View File

@ -27,8 +27,6 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Recast
{
public class RecastBuilder
{
public interface RecastBuilderProgressListener
@ -48,7 +46,8 @@ public class RecastBuilder
this.progressListener = progressListener;
}
public List<RecastBuilderResult> buildTiles(InputGeomProvider geom, RecastConfig cfg, TaskFactory taskFactory) {
public List<RecastBuilderResult> buildTiles(InputGeomProvider geom, RecastConfig cfg, TaskFactory taskFactory)
{
float[] bmin = geom.getMeshBoundsMin();
float[] bmax = geom.getMeshBoundsMax();
int[] twh = Recast.calcTileCount(bmin, bmax, cfg.cs, cfg.tileSizeX, cfg.tileSizeZ);
@ -58,7 +57,9 @@ public class RecastBuilder
if (null != taskFactory)
{
buildMultiThreadAsync(geom, cfg, bmin, bmax, tw, th, results, taskFactory, default);
} else {
}
else
{
buildSingleThreadAsync(geom, cfg, bmin, bmax, tw, th, results);
}

View File

@ -20,8 +20,6 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast
{
using static RecastVectors;
public class RecastBuilderConfig

View File

@ -1,7 +1,5 @@
namespace DotRecast.Recast
{
public class RecastBuilderResult
{
public readonly int tileX;
@ -56,5 +54,4 @@ public class RecastBuilderResult
return telemetry;
}
}
}

View File

@ -22,8 +22,6 @@ using System;
namespace DotRecast.Recast
{
public class RecastCommon
{
/// Gets neighbor connection data for the specified direction.
@ -82,7 +80,5 @@ public class RecastCommon
{
return Math.Max(Math.Min(max, v), min);
}
}
}

View File

@ -20,8 +20,6 @@ using System;
namespace DotRecast.Recast
{
using static RecastConstants;
using static RecastVectors;

View File

@ -22,8 +22,6 @@ using System;
namespace DotRecast.Recast
{
using static RecastConstants;
public class RecastConfig

View File

@ -20,8 +20,6 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast
{
public static class RecastConstants
{
public const int RC_NULL_AREA = 0;
@ -82,7 +80,6 @@ public static class RecastConstants
public const int RC_CONTOUR_TESS_AREA_EDGES = 0x02;
public const int RC_LOG_WARNING = 1;
}
}

View File

@ -23,8 +23,6 @@ using System.Collections.Generic;
namespace DotRecast.Recast
{
using static RecastConstants;
public class RecastContour

View File

@ -21,8 +21,6 @@ using DotRecast.Core;
namespace DotRecast.Recast
{
using static RecastConstants;
using static RecastVectors;
using static RecastCommon;

View File

@ -22,8 +22,6 @@ using System;
namespace DotRecast.Recast
{
using static RecastConstants;
public class RecastFilter

View File

@ -23,19 +23,18 @@ using System.Collections.Generic;
namespace DotRecast.Recast
{
using static RecastCommon;
using static RecastConstants;
using static RecastVectors;
using static RecastRegion;
public class RecastLayers {
public class RecastLayers
{
const int RC_MAX_LAYERS = RecastConstants.RC_NOT_CONNECTED;
const int RC_MAX_NEIS = 16;
private class LayerRegion {
private class LayerRegion
{
public int id;
public int layerId;
public bool @base;
@ -43,32 +42,36 @@ public class RecastLayers {
public List<int> layers;
public List<int> neis;
public LayerRegion(int i) {
public LayerRegion(int i)
{
id = i;
ymin = 0xFFFF;
layerId = 0xff;
layers = new List<int>();
neis = new List<int>();
}
};
private static void addUnique(List<int> a, int v) {
if (!a.Contains(v)) {
private static void addUnique(List<int> a, int v)
{
if (!a.Contains(v))
{
a.Add(v);
}
}
private static bool contains(List<int> a, int v) {
private static bool contains(List<int> a, int v)
{
return a.Contains(v);
}
private static bool overlapRange(int amin, int amax, int bmin, int bmax) {
private static bool overlapRange(int amin, int amax, int bmin, int bmax)
{
return (amin > bmax || amax < bmin) ? false : true;
}
public static HeightfieldLayerSet buildHeightfieldLayers(Telemetry ctx, CompactHeightfield chf, int walkableHeight) {
public static HeightfieldLayerSet buildHeightfieldLayers(Telemetry ctx, CompactHeightfield chf, int walkableHeight)
{
ctx.startTimer("RC_TIMER_BUILD_LAYERS");
int w = chf.width;
int h = chf.height;
@ -77,29 +80,35 @@ public class RecastLayers {
Array.Fill(srcReg, 0xFF);
int nsweeps = chf.width; // Math.Max(chf.width, chf.height);
SweepSpan[] sweeps = new SweepSpan[nsweeps];
for (int i = 0; i < sweeps.Length; i++) {
for (int i = 0; i < sweeps.Length; i++)
{
sweeps[i] = new SweepSpan();
}
// Partition walkable area into monotone regions.
int[] prevCount = new int[256];
int regId = 0;
// Sweep one line at a time.
for (int y = borderSize; y < h - borderSize; ++y) {
for (int y = borderSize; y < h - borderSize; ++y)
{
// Collect spans from this row.
Array.Fill(prevCount, 0, 0, (regId) - (0));
int sweepId = 0;
for (int x = borderSize; x < w - borderSize; ++x) {
for (int x = borderSize; x < w - borderSize; ++x)
{
CompactCell c = chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) {
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
CompactSpan s = chf.spans[i];
if (chf.areas[i] == RC_NULL_AREA)
continue;
int sid = 0xFF;
// -x
if (GetCon(s, 0) != RC_NOT_CONNECTED) {
if (GetCon(s, 0) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(0);
int ay = y + GetDirOffsetY(0);
int ai = chf.cells[ax + ay * w].index + GetCon(s, 0);
@ -107,29 +116,35 @@ public class RecastLayers {
sid = srcReg[ai];
}
if (sid == 0xff) {
if (sid == 0xff)
{
sid = sweepId++;
sweeps[sid].nei = 0xff;
sweeps[sid].ns = 0;
}
// -y
if (GetCon(s, 3) != RC_NOT_CONNECTED) {
if (GetCon(s, 3) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(3);
int ay = y + GetDirOffsetY(3);
int ai = chf.cells[ax + ay * w].index + GetCon(s, 3);
int nr = srcReg[ai];
if (nr != 0xff) {
if (nr != 0xff)
{
// Set neighbour when first valid neighbour is
// encoutered.
if (sweeps[sid].ns == 0)
sweeps[sid].nei = nr;
if (sweeps[sid].nei == nr) {
if (sweeps[sid].nei == nr)
{
// Update existing neighbour
sweeps[sid].ns++;
prevCount[nr]++;
} else {
}
else
{
// This is hit if there is nore than one
// neighbour.
// Invalidate the neighbour.
@ -143,47 +158,60 @@ public class RecastLayers {
}
// Create unique ID.
for (int i = 0; i < sweepId; ++i) {
for (int i = 0; i < sweepId; ++i)
{
// If the neighbour is set and there is only one continuous
// connection to it,
// the sweep will be merged with the previous one, else new
// region is created.
if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == sweeps[i].ns) {
if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == sweeps[i].ns)
{
sweeps[i].id = sweeps[i].nei;
} else {
if (regId == 255) {
}
else
{
if (regId == 255)
{
throw new Exception("rcBuildHeightfieldLayers: Region ID overflow.");
}
sweeps[i].id = regId++;
}
}
// Remap local sweep ids to region ids.
for (int x = borderSize; x < w - borderSize; ++x) {
for (int x = borderSize; x < w - borderSize; ++x)
{
CompactCell c = chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) {
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
if (srcReg[i] != 0xff)
srcReg[i] = sweeps[srcReg[i]].id;
}
}
}
int nregs = regId;
LayerRegion[] regs = new LayerRegion[nregs];
// Construct regions
for (int i = 0; i < nregs; ++i) {
for (int i = 0; i < nregs; ++i)
{
regs[i] = new LayerRegion(i);
}
// Find region neighbours and overlapping regions.
List<int> lregs = new List<int>();
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
CompactCell c = chf.cells[x + y * w];
lregs.Clear();
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) {
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
CompactSpan s = chf.spans[i];
int ri = srcReg[i];
if (ri == 0xff)
@ -196,8 +224,10 @@ public class RecastLayers {
lregs.Add(ri);
// Update neighbours
for (int dir = 0; dir < 4; ++dir) {
if (GetCon(s, dir) != RC_NOT_CONNECTED) {
for (int dir = 0; dir < 4; ++dir)
{
if (GetCon(s, dir) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(dir);
int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + GetCon(s, dir);
@ -206,13 +236,15 @@ public class RecastLayers {
addUnique(regs[ri].neis, rai);
}
}
}
// Update overlapping regions.
for (int i = 0; i < lregs.Count - 1; ++i) {
for (int j = i + 1; j < lregs.Count; ++j) {
if (lregs[i] != lregs[j]) {
for (int i = 0; i < lregs.Count - 1; ++i)
{
for (int j = i + 1; j < lregs.Count; ++j)
{
if (lregs[i] != lregs[j])
{
LayerRegion ri = regs[lregs[i]];
LayerRegion rj = regs[lregs[j]];
addUnique(ri.layers, lregs[j]);
@ -220,7 +252,6 @@ public class RecastLayers {
}
}
}
}
}
@ -229,7 +260,8 @@ public class RecastLayers {
List<int> stack = new List<int>();
for (int i = 0; i < nregs; ++i) {
for (int i = 0; i < nregs; ++i)
{
LayerRegion root = regs[i];
// Skip already visited.
if (root.layerId != 0xff)
@ -241,13 +273,15 @@ public class RecastLayers {
stack.Add(i);
while (stack.Count != 0) {
while (stack.Count != 0)
{
// Pop front
int pop = stack[0]; // TODO : 여기에 stack 처럼 작동하게 했는데, 스택인지는 모르겠음
stack.RemoveAt(0);
LayerRegion reg = regs[pop];
foreach (int nei in reg.neis) {
foreach (int nei in reg.neis)
{
LayerRegion regn = regs[nei];
// Skip already visited.
if (regn.layerId != 0xff)
@ -280,17 +314,20 @@ public class RecastLayers {
// Merge non-overlapping regions that are close in height.
int mergeHeight = walkableHeight * 4;
for (int i = 0; i < nregs; ++i) {
for (int i = 0; i < nregs; ++i)
{
LayerRegion ri = regs[i];
if (!ri.@base)
continue;
int newId = ri.layerId;
for (;;) {
for (;;)
{
int oldId = 0xff;
for (int j = 0; j < nregs; ++j) {
for (int j = 0; j < nregs; ++j)
{
if (i == j)
continue;
LayerRegion rj = regs[j];
@ -311,16 +348,19 @@ public class RecastLayers {
bool overlap = false;
// Iterate over all regions which have the same layerId as
// 'rj'
for (int k = 0; k < nregs; ++k) {
for (int k = 0; k < nregs; ++k)
{
if (regs[k].layerId != rj.layerId)
continue;
// Check if region 'k' is overlapping region 'ri'
// Index to 'regs' is the same as region id.
if (contains(ri.layers, k)) {
if (contains(ri.layers, k))
{
overlap = true;
break;
}
}
// Cannot merge of regions overlap.
if (overlap)
continue;
@ -335,9 +375,11 @@ public class RecastLayers {
break;
// Merge
for (int j = 0; j < nregs; ++j) {
for (int j = 0; j < nregs; ++j)
{
LayerRegion rj = regs[j];
if (rj.layerId == oldId) {
if (rj.layerId == oldId)
{
rj.@base = false;
// Remap layerIds.
rj.layerId = newId;
@ -359,18 +401,21 @@ public class RecastLayers {
layerId = 0;
for (int i = 0; i < nregs; ++i)
remap[regs[i].layerId] = 1;
for (int i = 0; i < 256; ++i) {
for (int i = 0; i < 256; ++i)
{
if (remap[i] != 0)
remap[i] = layerId++;
else
remap[i] = 0xff;
}
// Remap ids.
for (int i = 0; i < nregs; ++i)
regs[i].layerId = remap[regs[i].layerId];
// No layers, return empty.
if (layerId == 0) {
if (layerId == 0)
{
// ctx.stopTimer(RC_TIMER_BUILD_LAYERS);
return null;
}
@ -393,12 +438,14 @@ public class RecastLayers {
HeightfieldLayerSet lset = new HeightfieldLayerSet();
lset.layers = new HeightfieldLayerSet.HeightfieldLayer[layerId];
for (int i = 0; i < lset.layers.Length; i++) {
for (int i = 0; i < lset.layers.Length; i++)
{
lset.layers[i] = new HeightfieldLayerSet.HeightfieldLayer();
}
// Store layers.
for (int i = 0; i < lset.layers.Length; ++i) {
for (int i = 0; i < lset.layers.Length; ++i)
{
int curId = i;
HeightfieldLayerSet.HeightfieldLayer layer = lset.layers[i];
@ -412,8 +459,10 @@ public class RecastLayers {
// Find layer height bounds.
int hmin = 0, hmax = 0;
for (int j = 0; j < nregs; ++j) {
if (regs[j].@base && regs[j].layerId == curId) {
for (int j = 0; j < nregs; ++j)
{
if (regs[j].@base && regs[j].layerId == curId)
{
hmin = regs[j].ymin;
hmax = regs[j].ymax;
}
@ -439,12 +488,15 @@ public class RecastLayers {
layer.maxy = 0;
// Copy height and area from compact heightfield.
for (int y = 0; y < lh; ++y) {
for (int x = 0; x < lw; ++x) {
for (int y = 0; y < lh; ++y)
{
for (int x = 0; x < lw; ++x)
{
int cx = borderSize + x;
int cy = borderSize + y;
CompactCell c = chf.cells[cx + cy * w];
for (int j = c.index, nj = c.index + c.count; j < nj; ++j) {
for (int j = c.index, nj = c.index + c.count; j < nj; ++j)
{
CompactSpan s = chf.spans[j];
// Skip unassigned regions.
if (srcReg[j] == 0xff)
@ -468,14 +520,17 @@ public class RecastLayers {
// Check connection.
char portal = (char)0;
char con = (char)0;
for (int dir = 0; dir < 4; ++dir) {
if (GetCon(s, dir) != RC_NOT_CONNECTED) {
for (int dir = 0; dir < 4; ++dir)
{
if (GetCon(s, dir) != RC_NOT_CONNECTED)
{
int ax = cx + GetDirOffsetX(dir);
int ay = cy + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + GetCon(s, dir);
int alid = srcReg[ai] != 0xff ? regs[srcReg[ai]].layerId : 0xff;
// Portal mask
if (chf.areas[ai] != RC_NULL_AREA && lid != alid) {
if (chf.areas[ai] != RC_NULL_AREA && lid != alid)
{
portal |= (char)(1 << dir);
// Update height so that it matches on both
// sides of the portal.
@ -483,8 +538,10 @@ public class RecastLayers {
if (@as.y > hmin)
layer.heights[idx] = Math.Max(layer.heights[idx], (char)(@as.y - hmin));
}
// Valid connection mask
if (chf.areas[ai] != RC_NULL_AREA && lid == alid) {
if (chf.areas[ai] != RC_NULL_AREA && lid == alid)
{
int nx = ax - borderSize;
int ny = ay - borderSize;
if (nx >= 0 && ny >= 0 && nx < lw && ny < lh)
@ -492,6 +549,7 @@ public class RecastLayers {
}
}
}
layer.cons[idx] = (portal << 4) | con;
}
}
@ -507,5 +565,4 @@ public class RecastLayers {
return lset;
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -22,8 +22,6 @@ using System;
namespace DotRecast.Recast
{
using static RecastConstants;
public class RecastRasterization

File diff suppressed because it is too large Load Diff

View File

@ -22,8 +22,6 @@ using System;
namespace DotRecast.Recast
{
public static class RecastVectors
{
public static void min(float[] a, float[] b, int i)

View File

@ -21,12 +21,11 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Recast
{
public class RecastVoxelization {
public class RecastVoxelization
{
public static Heightfield buildSolidHeightfield(InputGeomProvider geomProvider, RecastBuilderConfig builderCfg,
Telemetry ctx) {
Telemetry ctx)
{
RecastConfig cfg = builderCfg.cfg;
// Allocate voxel heightfield where we rasterize our input data to.
@ -43,9 +42,11 @@ public class RecastVoxelization {
// If your input data is multiple meshes, you can transform them here,
// calculate
// the are type for each of the meshes and rasterize them.
foreach (TriMesh geom in geomProvider.meshes()) {
foreach (TriMesh geom in geomProvider.meshes())
{
float[] verts = geom.getVerts();
if (cfg.useTiles) {
if (cfg.useTiles)
{
float[] tbmin = new float[2];
float[] tbmax = new float[2];
tbmin[0] = builderCfg.bmin[0];
@ -53,14 +54,17 @@ public class RecastVoxelization {
tbmax[0] = builderCfg.bmax[0];
tbmax[1] = builderCfg.bmax[2];
List<ChunkyTriMeshNode> nodes = geom.getChunksOverlappingRect(tbmin, tbmax);
foreach (ChunkyTriMeshNode node in nodes) {
foreach (ChunkyTriMeshNode node in nodes)
{
int[] tris = node.tris;
int ntris = tris.Length / 3;
int[] m_triareas = Recast.markWalkableTriangles(ctx, cfg.walkableSlopeAngle, verts, tris, ntris,
cfg.walkableAreaMod);
RecastRasterization.rasterizeTriangles(solid, verts, tris, m_triareas, ntris, cfg.walkableClimb, ctx);
}
} else {
}
else
{
int[] tris = geom.getTris();
int ntris = tris.Length / 3;
int[] m_triareas = Recast.markWalkableTriangles(ctx, cfg.walkableSlopeAngle, verts, tris, ntris,
@ -71,7 +75,5 @@ public class RecastVoxelization {
return solid;
}
}
}

View File

@ -17,22 +17,22 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Recast
{
/** Represents a span in a heightfield. */
public class Span {
public class Span
{
/** The lower limit of the span. [Limit: &lt; smax] */
public int smin;
/** The upper limit of the span. [Limit: &lt;= SPAN_MAX_HEIGHT] */
public int smax;
/** The area id assigned to the span. */
public int area;
/** The next span higher up in column. */
public Span next;
}
}

View File

@ -25,8 +25,6 @@ using DotRecast.Core;
namespace DotRecast.Recast
{
public class Telemetry
{
private readonly ThreadLocal<Dictionary<string, AtomicLong>> timerStart = new ThreadLocal<Dictionary<string, AtomicLong>>(() => new Dictionary<string, AtomicLong>());

View File

@ -26,26 +26,33 @@ namespace DotRecast.Detour.Crowd.Test;
using static DetourCommon;
public class AbstractCrowdTest {
protected readonly long[] startRefs = { 281474976710696L, 281474976710773L, 281474976710680L, 281474976710753L,
281474976710733L };
public class AbstractCrowdTest
{
protected readonly long[] startRefs =
{
281474976710696L, 281474976710773L, 281474976710680L, 281474976710753L,
281474976710733L
};
protected readonly long[] endRefs = { 281474976710721L, 281474976710767L, 281474976710758L, 281474976710731L, 281474976710772L };
protected readonly float[][] startPoss = {
protected readonly float[][] startPoss =
{
new[] { 22.60652f, 10.197294f, -45.918674f },
new[] { 22.331268f, 10.197294f, -1.0401875f },
new[] { 18.694363f, 15.803535f, -73.090416f },
new[] { 0.7453353f, 10.197294f, -5.94005f },
new[] { -20.651257f, 5.904126f, -13.712508f } };
new[] { -20.651257f, 5.904126f, -13.712508f }
};
protected readonly float[][] endPoss = {
protected readonly float[][] endPoss =
{
new[] { 6.4576626f, 10.197294f, -18.33406f },
new[] { -5.8023443f, 0.19729415f, 3.008419f },
new[] { 38.423977f, 10.197294f, -0.116066754f },
new[] { 0.8635526f, 10.197294f, -10.31032f },
new[] { 18.784092f, 10.197294f, 3.0543678f } };
new[] { 18.784092f, 10.197294f, 3.0543678f }
};
protected MeshData nmd;
protected NavMeshQuery query;
@ -54,7 +61,8 @@ public class AbstractCrowdTest {
protected List<CrowdAgent> agents;
[SetUp]
public void setUp() {
public void setUp()
{
nmd = new RecastTestMeshBuilder().getMeshData();
navmesh = new NavMesh(nmd, 6, 0);
query = new NavMeshQuery(navmesh);
@ -87,7 +95,8 @@ public class AbstractCrowdTest {
agents = new();
}
protected CrowdAgentParams getAgentParams(int updateFlags, int obstacleAvoidanceType) {
protected CrowdAgentParams getAgentParams(int updateFlags, int obstacleAvoidanceType)
{
CrowdAgentParams ap = new CrowdAgentParams();
ap.radius = 0.6f;
ap.height = 2f;
@ -101,10 +110,13 @@ public class AbstractCrowdTest {
return ap;
}
protected void addAgentGrid(int size, float distance, int updateFlags, int obstacleAvoidanceType, float[] startPos) {
protected void addAgentGrid(int size, float distance, int updateFlags, int obstacleAvoidanceType, float[] startPos)
{
CrowdAgentParams ap = getAgentParams(updateFlags, obstacleAvoidanceType);
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
for (int i = 0; i < size; i++)
{
for (int j = 0; j < size; j++)
{
float[] pos = new float[3];
pos[0] = startPos[0] + i * distance;
pos[1] = startPos[1];
@ -114,23 +126,30 @@ public class AbstractCrowdTest {
}
}
protected void setMoveTarget(float[] pos, bool adjust) {
protected void setMoveTarget(float[] pos, bool adjust)
{
float[] ext = crowd.getQueryExtents();
QueryFilter filter = crowd.getFilter(0);
if (adjust) {
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
if (adjust)
{
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
float[] vel = calcVel(ag.npos, pos, ag.option.maxSpeed);
crowd.requestMoveVelocity(ag, vel);
}
} else {
}
else
{
Result<FindNearestPolyResult> nearest = query.findNearestPoly(pos, ext, filter);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
crowd.requestMoveTarget(ag, nearest.result.getNearestRef(), nearest.result.getNearestPos());
}
}
}
protected float[] calcVel(float[] pos, float[] tgt, float speed) {
protected float[] calcVel(float[] pos, float[] tgt, float speed)
{
float[] vel = vSub(tgt, pos);
vel[1] = 0.0f;
vNormalize(vel);
@ -138,13 +157,14 @@ public class AbstractCrowdTest {
return vel;
}
protected void dumpActiveAgents(int i) {
protected void dumpActiveAgents(int i)
{
Console.WriteLine(crowd.getActiveAgents().Count);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
Console.WriteLine(ag.state + ", " + ag.targetState);
Console.WriteLine(ag.npos[0] + ", " + ag.npos[1] + ", " + ag.npos[2]);
Console.WriteLine(ag.nvel[0] + ", " + ag.nvel[1] + ", " + ag.nvel[2]);
}
}
}

View File

@ -22,9 +22,10 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test;
public class Crowd1Test : AbstractCrowdTest {
static readonly float[][] EXPECTED_A1Q0TVTA = {
public class Crowd1Test : AbstractCrowdTest
{
static readonly float[][] EXPECTED_A1Q0TVTA =
{
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
@ -90,9 +91,11 @@ public class Crowd1Test : AbstractCrowdTest {
new[] { 6.442838f, 10.197294f, -18.328083f, 0.030522f, 0.000000f, -0.012308f },
new[] { 6.447162f, 10.197294f, -18.329826f, 0.021620f, 0.000000f, -0.008717f },
new[] { 6.450224f, 10.197294f, -18.331062f, 0.015314f, 0.000000f, -0.006175f },
new[] { 6.450224f, 10.197294f, -18.331062f, 0.000000f, 0.000000f, 0.000000f } };
new[] { 6.450224f, 10.197294f, -18.331062f, 0.000000f, 0.000000f, 0.000000f }
};
static readonly float[][] EXPECTED_A1Q0TVT = {
static readonly float[][] EXPECTED_A1Q0TVT =
{
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
@ -155,9 +158,11 @@ public class Crowd1Test : AbstractCrowdTest {
new[] { 6.447767f, 10.197294f, -18.358591f, 0.069268f, 0.000000f, 0.171711f },
new[] { 6.453539f, 10.197294f, -18.344282f, 0.028861f, 0.000000f, 0.071547f },
new[] { 6.455945f, 10.197294f, -18.338320f, 0.012026f, 0.000000f, 0.029813f },
new[] { 6.455945f, 10.197294f, -18.338320f, 0.000000f, 0.000000f, 0.000000f } };
new[] { 6.455945f, 10.197294f, -18.338320f, 0.000000f, 0.000000f, 0.000000f }
};
static readonly float[][] EXPECTED_A1Q0TV = {
static readonly float[][] EXPECTED_A1Q0TV =
{
new[] { 22.333418f, 10.197294f, -45.751896f, -2.987050f, 0.000000f, 1.824153f },
new[] { 21.787214f, 10.197294f, -45.418335f, -2.987049f, 0.000000f, 1.824153f },
new[] { 21.189804f, 10.197294f, -45.053505f, -2.987050f, 0.000000f, 1.824153f },
@ -220,9 +225,11 @@ public class Crowd1Test : AbstractCrowdTest {
new[] { 6.447869f, 10.197294f, -18.358244f, 0.068554f, 0.000000f, 0.169280f },
new[] { 6.453582f, 10.197294f, -18.344137f, 0.028564f, 0.000000f, 0.070535f },
new[] { 6.455962f, 10.197294f, -18.338259f, 0.011902f, 0.000000f, 0.029390f },
new[] { 6.455962f, 10.197294f, -18.338259f, 0.000000f, 0.000000f, 0.000000f } };
new[] { 6.455962f, 10.197294f, -18.338259f, 0.000000f, 0.000000f, 0.000000f }
};
static readonly float[][] EXPECTED_A1Q0T = {
static readonly float[][] EXPECTED_A1Q0T =
{
new[] { 22.333418f, 10.197294f, -45.751896f, -2.987050f, 0.000000f, 1.824153f },
new[] { 21.787214f, 10.197294f, -45.418335f, -2.987049f, 0.000000f, 1.824153f },
new[] { 21.189804f, 10.197294f, -45.053505f, -2.987050f, 0.000000f, 1.824153f },
@ -285,9 +292,11 @@ public class Crowd1Test : AbstractCrowdTest {
new[] { 6.447869f, 10.197294f, -18.358244f, 0.068554f, 0.000000f, 0.169280f },
new[] { 6.453582f, 10.197294f, -18.344137f, 0.028564f, 0.000000f, 0.070535f },
new[] { 6.455962f, 10.197294f, -18.338259f, 0.011902f, 0.000000f, 0.029390f },
new[] { 6.455962f, 10.197294f, -18.338259f, 0.000000f, 0.000000f, 0.000000f } };
new[] { 6.455962f, 10.197294f, -18.338259f, 0.000000f, 0.000000f, 0.000000f }
};
static readonly float[][] EXPECTED_A1Q1TVTA = {
static readonly float[][] EXPECTED_A1Q1TVTA =
{
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
@ -351,9 +360,11 @@ public class Crowd1Test : AbstractCrowdTest {
new[] { 6.447618f, 10.197294f, -18.344862f, 0.020681f, 0.000000f, 0.022238f },
new[] { 6.450547f, 10.197294f, -18.341711f, 0.014649f, 0.000000f, 0.015752f },
new[] { 6.452622f, 10.197294f, -18.339479f, 0.010377f, 0.000000f, 0.011157f },
new[] { 6.452622f, 10.197294f, -18.339479f, 0.000000f, 0.000000f, 0.000000f }, };
new[] { 6.452622f, 10.197294f, -18.339479f, 0.000000f, 0.000000f, 0.000000f },
};
static readonly float[][] EXPECTED_A1Q2TVTA = {
static readonly float[][] EXPECTED_A1Q2TVTA =
{
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
@ -417,9 +428,11 @@ public class Crowd1Test : AbstractCrowdTest {
new[] { 6.453821f, 10.197294f, -18.352995f, 0.007909f, 0.000000f, 0.038981f },
new[] { 6.454941f, 10.197294f, -18.347473f, 0.005603f, 0.000000f, 0.027612f },
new[] { 6.455735f, 10.197294f, -18.343561f, 0.003969f, 0.000000f, 0.019560f },
new[] { 6.455735f, 10.197294f, -18.343561f, 0.000000f, 0.000000f, 0.000000f }, };
new[] { 6.455735f, 10.197294f, -18.343561f, 0.000000f, 0.000000f, 0.000000f },
};
static readonly float[][] EXPECTED_A1Q3TVTA = {
static readonly float[][] EXPECTED_A1Q3TVTA =
{
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
@ -480,9 +493,11 @@ public class Crowd1Test : AbstractCrowdTest {
new[] { 6.397289f, 10.197294f, -18.479179f, 0.456429f, 0.000000f, 1.107728f },
new[] { 6.439916f, 10.197294f, -18.384853f, 0.213137f, 0.000000f, 0.471627f },
new[] { 6.454712f, 10.197294f, -18.342505f, 0.073981f, 0.000000f, 0.211745f },
new[] { 6.454712f, 10.197294f, -18.342505f, 0.000000f, 0.000000f, 0.000000f } };
new[] { 6.454712f, 10.197294f, -18.342505f, 0.000000f, 0.000000f, 0.000000f }
};
static readonly float[][] EXPECTED_A1Q3TVTAS = {
static readonly float[][] EXPECTED_A1Q3TVTAS =
{
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
@ -543,18 +558,22 @@ public class Crowd1Test : AbstractCrowdTest {
new[] { 6.397289f, 10.197294f, -18.479179f, 0.456429f, 0.000000f, 1.107728f },
new[] { 6.439916f, 10.197294f, -18.384853f, 0.213137f, 0.000000f, 0.471627f },
new[] { 6.454712f, 10.197294f, -18.342505f, 0.073981f, 0.000000f, 0.211745f },
new[] { 6.454712f, 10.197294f, -18.342505f, 0.000000f, 0.000000f, 0.000000f } };
new[] { 6.454712f, 10.197294f, -18.342505f, 0.000000f, 0.000000f, 0.000000f }
};
[Test]
public void testAgent1Quality0TVTA() {
public void testAgent1Quality0TVTA()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q0TVTA.Length; i++) {
for (int i = 0; i < EXPECTED_A1Q0TVTA.Length; i++)
{
crowd.update(1 / 5f, null);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q0TVTA[i][0]).Within(0.001f));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q0TVTA[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q0TVTA[i][2]).Within(0.001f));
@ -566,15 +585,18 @@ public class Crowd1Test : AbstractCrowdTest {
}
[Test]
public void testAgent1Quality0TVT() {
public void testAgent1Quality0TVT()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q0TVT.Length; i++) {
for (int i = 0; i < EXPECTED_A1Q0TVT.Length; i++)
{
crowd.update(1 / 5f, null);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q0TVT[i][0]).Within(0.001f));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q0TVT[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q0TVT[i][2]).Within(0.001f));
@ -586,14 +608,17 @@ public class Crowd1Test : AbstractCrowdTest {
}
[Test]
public void testAgent1Quality0TV() {
public void testAgent1Quality0TV()
{
int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS;
addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q0TV.Length; i++) {
for (int i = 0; i < EXPECTED_A1Q0TV.Length; i++)
{
crowd.update(1 / 5f, null);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q0TV[i][0]).Within(0.001f));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q0TV[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q0TV[i][2]).Within(0.001f));
@ -605,14 +630,17 @@ public class Crowd1Test : AbstractCrowdTest {
}
[Test]
public void testAgent1Quality0T() {
public void testAgent1Quality0T()
{
int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q0T.Length; i++) {
for (int i = 0; i < EXPECTED_A1Q0T.Length; i++)
{
crowd.update(1 / 5f, null);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q0T[i][0]).Within(0.001));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q0T[i][1]).Within(0.001));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q0T[i][2]).Within(0.001));
@ -624,15 +652,18 @@ public class Crowd1Test : AbstractCrowdTest {
}
[Test]
public void testAgent1Quality1TVTA() {
public void testAgent1Quality1TVTA()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(1, 0.4f, updateFlags, 1, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q1TVTA.Length; i++) {
for (int i = 0; i < EXPECTED_A1Q1TVTA.Length; i++)
{
crowd.update(1 / 5f, null);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q1TVTA[i][0]).Within(0.001f));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q1TVTA[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q1TVTA[i][2]).Within(0.001f));
@ -644,15 +675,18 @@ public class Crowd1Test : AbstractCrowdTest {
}
[Test]
public void testAgent1Quality2TVTA() {
public void testAgent1Quality2TVTA()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(1, 0.4f, updateFlags, 2, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q2TVTA.Length; i++) {
for (int i = 0; i < EXPECTED_A1Q2TVTA.Length; i++)
{
crowd.update(1 / 5f, null);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTA[i][0]).Within(0.001f));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q2TVTA[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q2TVTA[i][2]).Within(0.001f));
@ -664,15 +698,18 @@ public class Crowd1Test : AbstractCrowdTest {
}
[Test]
public void testAgent1Quality3TVTA() {
public void testAgent1Quality3TVTA()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(1, 0.4f, updateFlags, 3, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q3TVTA.Length; i++) {
for (int i = 0; i < EXPECTED_A1Q3TVTA.Length; i++)
{
crowd.update(1 / 5f, null);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q3TVTA[i][0]).Within(0.001f));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q3TVTA[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q3TVTA[i][2]).Within(0.001f));
@ -684,16 +721,19 @@ public class Crowd1Test : AbstractCrowdTest {
}
[Test]
public void testAgent1Quality3TVTAS() {
public void testAgent1Quality3TVTAS()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE
| CrowdAgentParams.DT_CROWD_SEPARATION;
addAgentGrid(1, 0.4f, updateFlags, 3, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q3TVTAS.Length; i++) {
for (int i = 0; i < EXPECTED_A1Q3TVTAS.Length; i++)
{
crowd.update(1 / 5f, null);
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q3TVTAS[i][0]).Within(0.001f));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q3TVTAS[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q3TVTAS[i][2]).Within(0.001f));
@ -703,5 +743,4 @@ public class Crowd1Test : AbstractCrowdTest {
}
}
}
}

View File

@ -24,9 +24,10 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test;
public class Crowd4Test : AbstractCrowdTest {
static readonly float[][] EXPECTED_A1Q2TVTA = {
public class Crowd4Test : AbstractCrowdTest
{
static readonly float[][] EXPECTED_A1Q2TVTA =
{
new[] { 23.275612f, 10.197294f, -46.233074f, 0.061640f, 0.000000f, 0.073828f },
new[] { 23.350517f, 10.197294f, -46.304905f, 0.030557f, 0.000000f, 0.118703f },
new[] { 23.347885f, 10.197294f, -46.331837f, -0.024102f, 0.000000f, -0.093108f },
@ -118,9 +119,11 @@ public class Crowd4Test : AbstractCrowdTest {
new[] { 7.099013f, 10.197294f, -18.064573f, -0.150015f, 0.000000f, 0.647393f },
new[] { 7.085871f, 10.197294f, -17.946556f, -0.065714f, 0.000000f, 0.590090f },
new[] { 7.067722f, 10.197294f, -17.801628f, -0.090745f, 0.000000f, 0.724639f },
new[] { 7.048226f, 10.197294f, -17.673695f, -0.097479f, 0.000000f, 0.639666f } };
new[] { 7.048226f, 10.197294f, -17.673695f, -0.097479f, 0.000000f, 0.639666f }
};
static readonly float[][] EXPECTED_A1Q2TVTAS = {
static readonly float[][] EXPECTED_A1Q2TVTAS =
{
new[] { 23.253357f, 10.197294f, -46.279934f, 0.074597f, 0.000000f, -0.017069f },
new[] { 23.336805f, 10.197294f, -46.374985f, 0.119401f, 0.000000f, -0.031009f },
new[] { 23.351542f, 10.197294f, -46.410728f, 0.053426f, 0.000000f, -0.093556f },
@ -228,9 +231,11 @@ public class Crowd4Test : AbstractCrowdTest {
new[] { 5.764818f, 10.197294f, -19.506109f, -0.043161f, 0.000000f, 0.194928f },
new[] { 5.769928f, 10.197294f, -19.497072f, 0.025550f, 0.000000f, 0.045183f },
new[] { 5.753877f, 10.197294f, -19.524942f, -0.080258f, 0.000000f, -0.139354f },
new[] { 5.731219f, 10.197294f, -19.533819f, -0.113286f, 0.000000f, -0.044384f } };
new[] { 5.731219f, 10.197294f, -19.533819f, -0.113286f, 0.000000f, -0.044384f }
};
static readonly float[][] EXPECTED_A1Q2T = {
static readonly float[][] EXPECTED_A1Q2T =
{
new[] { 22.990597f, 10.197294f, -46.112606f, -2.999564f, 0.000000f, 1.803501f },
new[] { 22.524744f, 10.197294f, -45.867702f, -2.989815f, 0.000000f, 1.819617f },
new[] { 21.946421f, 10.197294f, -45.530769f, -2.987121f, 0.000000f, 1.824035f },
@ -296,16 +301,19 @@ public class Crowd4Test : AbstractCrowdTest {
new[] { 5.947361f, 10.197294f, -19.047245f, 1.574677f, 0.000000f, 2.491868f },
new[] { 5.960892f, 10.197294f, -18.990824f, 1.488381f, 0.000000f, 2.080121f },
new[] { 5.971087f, 10.197294f, -18.963556f, 1.448915f, 0.000000f, 1.915559f },
new[] { 5.977089f, 10.197294f, -18.950905f, 1.419177f, 0.000000f, 1.836029f } };
new[] { 5.977089f, 10.197294f, -18.950905f, 1.419177f, 0.000000f, 1.836029f }
};
[Test]
public void testAgent1Quality2TVTA() {
public void testAgent1Quality2TVTA()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q2TVTA.Length; i++) {
for (int i = 0; i < EXPECTED_A1Q2TVTA.Length; i++)
{
crowd.update(1 / 5f, null);
CrowdAgent ag = agents[2];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTA[i][0]).Within(0.001f), $"{i}");
@ -318,14 +326,16 @@ public class Crowd4Test : AbstractCrowdTest {
}
[Test]
public void testAgent1Quality2TVTAS() {
public void testAgent1Quality2TVTAS()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE
| CrowdAgentParams.DT_CROWD_SEPARATION;
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q2TVTAS.Length; i++) {
for (int i = 0; i < EXPECTED_A1Q2TVTAS.Length; i++)
{
crowd.update(1 / 5f, null);
CrowdAgent ag = agents[2];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTAS[i][0]).Within(0.001f), $"{i}");
@ -338,7 +348,8 @@ public class Crowd4Test : AbstractCrowdTest {
}
[Test]
public void testAgent1Quality2T() {
public void testAgent1Quality2T()
{
int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
@ -352,12 +363,18 @@ public class Crowd4Test : AbstractCrowdTest {
crowd.update(1 / 5f, null);
CrowdAgent ag = agents[2];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2T[i][0]).Within(0.00001f), $"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}"); Console.WriteLine($"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}");
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q2T[i][1]).Within(0.00001f), $"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}"); Console.WriteLine($"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}");
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q2T[i][2]).Within(0.00001f), $"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}"); Console.WriteLine($"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}");
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q2T[i][3]).Within(0.00001f), $"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}"); Console.WriteLine($"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}");
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q2T[i][4]).Within(0.00001f), $"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}"); Console.WriteLine($"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}");
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q2T[i][5]).Within(0.00001f), $"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}"); Console.WriteLine($"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}");
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2T[i][0]).Within(0.00001f), $"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}");
Console.WriteLine($"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}");
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q2T[i][1]).Within(0.00001f), $"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}");
Console.WriteLine($"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}");
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q2T[i][2]).Within(0.00001f), $"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}");
Console.WriteLine($"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}");
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q2T[i][3]).Within(0.00001f), $"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}");
Console.WriteLine($"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}");
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q2T[i][4]).Within(0.00001f), $"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}");
Console.WriteLine($"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}");
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q2T[i][5]).Within(0.00001f), $"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}");
Console.WriteLine($"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}");
Thread.Sleep(1);
}
}

View File

@ -22,10 +22,10 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test;
public class Crowd4VelocityTest : AbstractCrowdTest {
static readonly float[][] EXPECTED_A1Q3TVTA = {
public class Crowd4VelocityTest : AbstractCrowdTest
{
static readonly float[][] EXPECTED_A1Q3TVTA =
{
new[] { 6.101694f, 10.197294f, -17.678480f, 0.000000f, 0.000000f, 0.000000f },
new[] { 6.024141f, 10.197294f, -17.589798f, -0.107331f, 0.000000f, 0.098730f },
new[] { 6.004839f, 10.197294f, -17.554886f, -0.096506f, 0.000000f, 0.174561f },
@ -92,20 +92,25 @@ public class Crowd4VelocityTest : AbstractCrowdTest {
new[] { 4.320761f, 10.197294f, -8.089768f, -0.003438f, 0.000000f, -0.072332f },
new[] { 4.337994f, 10.197294f, -8.081223f, 0.086165f, 0.000000f, 0.042728f },
new[] { 4.328456f, 10.197294f, -8.057701f, -0.047688f, 0.000000f, 0.117607f },
new[] { 4.327769f, 10.197294f, -8.072167f, -0.003438f, 0.000000f, -0.072332f } };
new[] { 4.327769f, 10.197294f, -8.072167f, -0.003438f, 0.000000f, -0.072332f }
};
[Test]
public void testAgent1Quality3TVTA() {
public void testAgent1Quality3TVTA()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(2, 0.3f, updateFlags, 3, endPoss[0]);
setMoveTarget(endPoss[4], false);
for (int i = 0; i < EXPECTED_A1Q3TVTA.Length; i++) {
for (int i = 0; i < EXPECTED_A1Q3TVTA.Length; i++)
{
crowd.update(1 / 5f, null);
if (i == 20) {
if (i == 20)
{
setMoveTarget(startPoss[2], true);
}
CrowdAgent ag = agents[1];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q3TVTA[i][0]).Within(0.001f));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q3TVTA[i][1]).Within(0.001f));
@ -115,5 +120,4 @@ public class Crowd4VelocityTest : AbstractCrowdTest {
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q3TVTA[i][5]).Within(0.001f));
}
}
}

View File

@ -22,18 +22,20 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test;
public class PathCorridorTest {
public class PathCorridorTest
{
private readonly PathCorridor corridor = new PathCorridor();
private readonly QueryFilter filter = new DefaultQueryFilter();
[SetUp]
public void setUp() {
public void setUp()
{
corridor.reset(0, new float[] { 10, 20, 30 });
}
[Test]
public void shouldKeepOriginalPathInFindCornersWhenNothingCanBePruned() {
public void shouldKeepOriginalPathInFindCornersWhenNothingCanBePruned()
{
List<StraightPathItem> straightPath = new();
straightPath.Add(new StraightPathItem(new float[] { 11, 20, 30.00001f }, 0, 0));
straightPath.Add(new StraightPathItem(new float[] { 12, 20, 30.00002f }, 0, 0));
@ -54,7 +56,8 @@ public class PathCorridorTest {
}
[Test]
public void shouldPrunePathInFindCorners() {
public void shouldPrunePathInFindCorners()
{
List<StraightPathItem> straightPath = new();
straightPath.Add(new StraightPathItem(new float[] { 10, 20, 30.00001f }, 0, 0)); // too close
straightPath.Add(new StraightPathItem(new float[] { 10, 20, 30.00002f }, 0, 0)); // too close
@ -76,5 +79,4 @@ public class PathCorridorTest {
Assert.That(path.Count, Is.EqualTo(2));
Assert.That(path, Is.EqualTo(new List<StraightPathItem> { straightPath[2], straightPath[3] }));
}
}

View File

@ -22,8 +22,8 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Detour.Crowd.Test;
public class RecastTestMeshBuilder {
public class RecastTestMeshBuilder
{
private readonly MeshData meshData;
public const float m_cellSize = 0.3f;
public const float m_cellHeight = 0.2f;
@ -49,7 +49,8 @@ public class RecastTestMeshBuilder {
public RecastTestMeshBuilder(InputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize,
float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope,
int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly,
float m_detailSampleDist, float m_detailSampleMaxError) {
float m_detailSampleDist, float m_detailSampleMaxError)
{
RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius,
m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
@ -57,9 +58,11 @@ public class RecastTestMeshBuilder {
RecastBuilder rcBuilder = new RecastBuilder();
RecastBuilderResult rcResult = rcBuilder.build(m_geom, bcfg);
PolyMesh m_pmesh = rcResult.getMesh();
for (int i = 0; i < m_pmesh.npolys; ++i) {
for (int i = 0; i < m_pmesh.npolys; ++i)
{
m_pmesh.flags[i] = 1;
}
PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = m_pmesh.verts;
@ -104,7 +107,8 @@ public class RecastTestMeshBuilder {
meshData = NavMeshBuilder.createNavMeshData(option);
}
public MeshData getMeshData() {
public MeshData getMeshData()
{
return meshData;
}
}

View File

@ -20,8 +20,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Crowd.Test;
public class SampleAreaModifications {
public class SampleAreaModifications
{
public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
@ -32,14 +32,19 @@ public class SampleAreaModifications {
public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR,
SAMPLE_POLYAREA_TYPE_DOOR);
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP,
SAMPLE_POLYAREA_TYPE_JUMP);

View File

@ -8,8 +8,8 @@ using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test;
public class DynamicNavMeshTest {
public class DynamicNavMeshTest
{
private static readonly float[] START_POS = new float[] { 70.87453f, 0.0010070801f, 86.69021f };
private static readonly float[] END_POS = new float[] { -50.22061f, 0.0010070801f, -70.761444f };
private static readonly float[] EXTENT = new float[] { 0.1f, 0.1f, 0.1f };
@ -17,7 +17,8 @@ public class DynamicNavMeshTest {
[Test]
public void e2eTest() {
public void e2eTest()
{
byte[] bytes = Loader.ToBytes("test_tiles.voxels");
using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms);

View File

@ -23,10 +23,11 @@ using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test.Io;
public class VoxelFileReaderTest {
public class VoxelFileReaderTest
{
[Test]
public void shouldReadSingleTileFile() {
public void shouldReadSingleTileFile()
{
byte[] bytes = Loader.ToBytes("test.voxels");
using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms);
@ -51,7 +52,8 @@ public class VoxelFileReaderTest {
}
[Test]
public void shouldReadMultiTileFile() {
public void shouldReadMultiTileFile()
{
byte[] bytes = Loader.ToBytes("test_tiles.voxels");
using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms);

View File

@ -23,11 +23,12 @@ using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test.Io;
public class VoxelFileReaderWriterTest {
public class VoxelFileReaderWriterTest
{
[TestCase(false)]
[TestCase(true)]
public void shouldReadSingleTileFile(bool compression) {
public void shouldReadSingleTileFile(bool compression)
{
byte[] bytes = Loader.ToBytes("test.voxels");
using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms);
@ -54,7 +55,8 @@ public class VoxelFileReaderWriterTest {
[TestCase(false)]
[TestCase(true)]
public void shouldReadMultiTileFile(bool compression) {
public void shouldReadMultiTileFile(bool compression)
{
byte[] bytes = Loader.ToBytes("test_tiles.voxels");
using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms);
@ -80,10 +82,10 @@ public class VoxelFileReaderWriterTest {
Assert.That(f.tiles[18].spanData.Length, Is.EqualTo(113400));
Assert.That(f.tiles[0].boundsMin, Is.EqualTo(new[] { -101.25f, 0f, -101.25f }));
Assert.That(f.tiles[0].boundsMax, Is.EqualTo(new[] { -78.75f, 5.0f, -78.75f }));
}
private VoxelFile readWriteRead(BinaryReader bis, bool compression) {
private VoxelFile readWriteRead(BinaryReader bis, bool compression)
{
VoxelFileReader reader = new VoxelFileReader();
VoxelFile f = reader.read(bis);
@ -96,5 +98,4 @@ public class VoxelFileReaderWriterTest {
using var brIn = new BinaryReader(msIn);
return reader.read(brIn);
}
}

View File

@ -20,8 +20,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Test;
public class SampleAreaModifications {
public class SampleAreaModifications
{
public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
@ -32,14 +32,19 @@ public class SampleAreaModifications {
public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR,
SAMPLE_POLYAREA_TYPE_DOOR);
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP,
SAMPLE_POLYAREA_TYPE_JUMP);

View File

@ -28,8 +28,8 @@ using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test;
public class VoxelQueryTest {
public class VoxelQueryTest
{
private const int TILE_WIDTH = 100;
private const int TILE_DEPTH = 90;
private static readonly float[] ORIGIN = new float[] { 50, 10, 40 };
@ -66,7 +66,8 @@ public class VoxelQueryTest {
}
[Test]
public void shouldHandleRaycastWithoutObstacles() {
public void shouldHandleRaycastWithoutObstacles()
{
DynamicNavMesh mesh = createDynaMesh();
VoxelQuery query = mesh.voxelQuery();
float[] start = { 7.4f, 0.5f, -64.8f };
@ -76,7 +77,8 @@ public class VoxelQueryTest {
}
[Test]
public void shouldHandleRaycastWithObstacles() {
public void shouldHandleRaycastWithObstacles()
{
DynamicNavMesh mesh = createDynaMesh();
VoxelQuery query = mesh.voxelQuery();
float[] start = { 32.3f, 0.5f, 47.9f };
@ -86,7 +88,8 @@ public class VoxelQueryTest {
Assert.That(hit.Value, Is.EqualTo(0.5263836f).Within(1e-7f));
}
private DynamicNavMesh createDynaMesh() {
private DynamicNavMesh createDynaMesh()
{
var bytes = Loader.ToBytes("test_tiles.voxels");
using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms);

View File

@ -26,10 +26,11 @@ using NUnit.Framework;
namespace DotRecast.Detour.Extras.Test.Unity.Astar;
public class UnityAStarPathfindingImporterTest {
public class UnityAStarPathfindingImporterTest
{
[Test]
public void test_v4_0_6() {
public void test_v4_0_6()
{
NavMesh mesh = loadNavMesh("graph.zip");
float[] startPos = new float[] { 8.200293f, 2.155071f, -26.176147f };
float[] endPos = new float[] { 11.971109f, 0.000000f, 8.663261f };
@ -40,7 +41,8 @@ public class UnityAStarPathfindingImporterTest {
}
[Test]
public void test_v4_1_16() {
public void test_v4_1_16()
{
NavMesh mesh = loadNavMesh("graph_v4_1_16.zip");
float[] startPos = new float[] { 22.93f, -2.37f, -5.11f };
float[] endPos = new float[] { 16.81f, -2.37f, 25.52f };
@ -51,7 +53,8 @@ public class UnityAStarPathfindingImporterTest {
}
[Test]
public void testBoundsTree() {
public void testBoundsTree()
{
NavMesh mesh = loadNavMesh("test_boundstree.zip");
float[] position = { 387.52988f, 19.997f, 368.86282f };
@ -72,7 +75,8 @@ public class UnityAStarPathfindingImporterTest {
Assert.That(bvResult.getNearestRef(), Is.EqualTo(clearResult.getNearestRef()));
}
private NavMesh loadNavMesh(string filename) {
private NavMesh loadNavMesh(string filename)
{
var filepath = Loader.ToRPath(filename);
using var fs = new FileStream(filepath, FileMode.Open);
@ -83,7 +87,8 @@ public class UnityAStarPathfindingImporterTest {
return meshes[0];
}
private Result<List<long>> findPath(NavMesh mesh, float[] startPos, float[] endPos) {
private Result<List<long>> findPath(NavMesh mesh, float[] startPos, float[] endPos)
{
// Perform a simple pathfinding
NavMeshQuery query = new NavMeshQuery(mesh);
QueryFilter filter = new DefaultQueryFilter();
@ -92,26 +97,32 @@ public class UnityAStarPathfindingImporterTest {
return query.findPath(polys[0].getNearestRef(), polys[1].getNearestRef(), startPos, endPos, filter);
}
private FindNearestPolyResult[] getNearestPolys(NavMesh mesh, params float[][] positions) {
private FindNearestPolyResult[] getNearestPolys(NavMesh mesh, params float[][] positions)
{
NavMeshQuery query = new NavMeshQuery(mesh);
QueryFilter filter = new DefaultQueryFilter();
float[] extents = new float[] { 0.1f, 0.1f, 0.1f };
FindNearestPolyResult[] results = new FindNearestPolyResult[positions.Length];
for (int i = 0; i < results.Length; i++) {
for (int i = 0; i < results.Length; i++)
{
float[] position = positions[i];
Result<FindNearestPolyResult> result = query.findNearestPoly(position, extents, filter);
Assert.That(result.succeeded(), Is.True);
Assert.That(result.result.getNearestPos(), Is.Not.Null, "Nearest start position is null!");
results[i] = result.result;
}
return results;
}
private void saveMesh(NavMesh mesh, string filePostfix) {
private void saveMesh(NavMesh mesh, string filePostfix)
{
// Set the flag to RecastDemo work properly
for (int i = 0; i < mesh.getTileCount(); i++) {
foreach (Poly p in mesh.getTile(i).data.polys) {
for (int i = 0; i < mesh.getTileCount(); i++)
{
foreach (Poly p in mesh.getTile(i).data.polys)
{
p.flags = 1;
}
}
@ -124,5 +135,4 @@ public class UnityAStarPathfindingImporterTest {
using var os = new BinaryWriter(fs);
writer.write(os, mesh, ByteOrder.LITTLE_ENDIAN, true);
}
}

View File

@ -20,10 +20,11 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class ConvexConvexIntersectionTest {
public class ConvexConvexIntersectionTest
{
[Test]
public void shouldHandleSamePolygonIntersection() {
public void shouldHandleSamePolygonIntersection()
{
float[] p = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
float[] q = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
float[] intersection = ConvexConvexIntersection.intersect(p, q);
@ -32,12 +33,12 @@ public class ConvexConvexIntersectionTest {
}
[Test]
public void shouldHandleIntersection() {
public void shouldHandleIntersection()
{
float[] p = { -5, 0, -5, -5, 0, 4, 1, 0, 4, 1, 0, -5 };
float[] q = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
float[] intersection = ConvexConvexIntersection.intersect(p, q);
Assert.That(intersection.Length, Is.EqualTo(5 * 3));
Assert.That(intersection, Is.EqualTo(new[] { 1, 0, 3, 1, 0, -3.4f, -2, 0, -4, -4, 0, 0, -3, 0, 3 }));
}
}

View File

@ -20,29 +20,33 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class FindNearestPolyTest : AbstractDetourTest {
public class FindNearestPolyTest : AbstractDetourTest
{
private static readonly long[] POLY_REFS = { 281474976710696L, 281474976710773L, 281474976710680L, 281474976710753L, 281474976710733L };
private static readonly float[][] POLY_POS = {
private static readonly float[][] POLY_POS =
{
new[] { 22.606520f, 10.197294f, -45.918674f }, new[] { 22.331268f, 10.197294f, -1.040187f },
new[] { 18.694363f, 15.803535f, -73.090416f }, new[] { 0.745335f, 10.197294f, -5.940050f },
new[] { -20.651257f, 5.904126f, -13.712508f } };
new[] { -20.651257f, 5.904126f, -13.712508f }
};
[Test]
public void testFindNearestPoly() {
public void testFindNearestPoly()
{
QueryFilter filter = new DefaultQueryFilter();
float[] extents = { 2, 4, 2 };
for (int i = 0; i < startRefs.Length; i++) {
for (int i = 0; i < startRefs.Length; i++)
{
float[] startPos = startPoss[i];
Result<FindNearestPolyResult> poly = query.findNearestPoly(startPos, extents, filter);
Assert.That(poly.succeeded(), Is.True);
Assert.That(poly.result.getNearestRef(), Is.EqualTo(POLY_REFS[i]));
for (int v = 0; v < POLY_POS[i].Length; v++) {
for (int v = 0; v < POLY_POS[i].Length; v++)
{
Assert.That(poly.result.getNearestPos()[v], Is.EqualTo(POLY_POS[i][v]).Within(0.001f));
}
}
}
public class EmptyQueryFilter : QueryFilter
@ -60,18 +64,20 @@ public class FindNearestPolyTest : AbstractDetourTest {
}
[Test]
public void shouldReturnStartPosWhenNoPolyIsValid() {
public void shouldReturnStartPosWhenNoPolyIsValid()
{
var filter = new EmptyQueryFilter();
float[] extents = { 2, 4, 2 };
for (int i = 0; i < startRefs.Length; i++) {
for (int i = 0; i < startRefs.Length; i++)
{
float[] startPos = startPoss[i];
Result<FindNearestPolyResult> poly = query.findNearestPoly(startPos, extents, filter);
Assert.That(poly.succeeded(), Is.True);
Assert.That(poly.result.getNearestRef(), Is.EqualTo(0L));
for (int v = 0; v < POLY_POS[i].Length; v++) {
for (int v = 0; v < POLY_POS[i].Length; v++)
{
Assert.That(poly.result.getNearestPos()[v], Is.EqualTo(startPos[v]).Within(0.001f));
}
}
}
}

View File

@ -21,42 +21,65 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class FindPathTest : AbstractDetourTest
{
private static readonly Status[] STATUSES =
{
Status.SUCCSESS, Status.PARTIAL_RESULT, Status.SUCCSESS, Status.SUCCSESS,
Status.SUCCSESS
};
public class FindPathTest : AbstractDetourTest {
private static readonly Status[] STATUSES = { Status.SUCCSESS, Status.PARTIAL_RESULT, Status.SUCCSESS, Status.SUCCSESS,
Status.SUCCSESS };
private static readonly long[][] RESULTS = {
new[] { 281474976710696L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
private static readonly long[][] RESULTS =
{
new[]
{
281474976710696L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
281474976710705L, 281474976710702L, 281474976710701L, 281474976710714L, 281474976710713L,
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710721L },
new[] { 281474976710773L, 281474976710772L, 281474976710768L, 281474976710754L, 281474976710755L,
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710721L
},
new[]
{
281474976710773L, 281474976710772L, 281474976710768L, 281474976710754L, 281474976710755L,
281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L, 281474976710729L,
281474976710717L, 281474976710724L, 281474976710728L, 281474976710737L, 281474976710738L,
281474976710736L, 281474976710733L, 281474976710735L, 281474976710742L, 281474976710740L,
281474976710746L, 281474976710745L, 281474976710744L },
new[] { 281474976710680L, 281474976710684L, 281474976710688L, 281474976710687L, 281474976710686L,
281474976710746L, 281474976710745L, 281474976710744L
},
new[]
{
281474976710680L, 281474976710684L, 281474976710688L, 281474976710687L, 281474976710686L,
281474976710697L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
281474976710705L, 281474976710702L, 281474976710701L, 281474976710714L, 281474976710713L,
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710729L,
281474976710731L, 281474976710752L, 281474976710748L, 281474976710753L, 281474976710755L,
281474976710754L, 281474976710768L, 281474976710772L, 281474976710773L, 281474976710770L,
281474976710757L, 281474976710761L, 281474976710758L },
281474976710757L, 281474976710761L, 281474976710758L
},
new[] { 281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L },
new[] { 281474976710733L, 281474976710736L, 281474976710738L, 281474976710737L, 281474976710728L,
new[]
{
281474976710733L, 281474976710736L, 281474976710738L, 281474976710737L, 281474976710728L,
281474976710724L, 281474976710717L, 281474976710729L, 281474976710731L, 281474976710752L,
281474976710748L, 281474976710753L, 281474976710755L, 281474976710754L, 281474976710768L,
281474976710772L } };
281474976710772L
}
};
private static readonly StraightPathItem[][] STRAIGHT_PATHS = {
new[] { new StraightPathItem(new float[] { 22.606520f, 10.197294f, -45.918674f }, 1, 281474976710696L),
private static readonly StraightPathItem[][] STRAIGHT_PATHS =
{
new[]
{
new StraightPathItem(new float[] { 22.606520f, 10.197294f, -45.918674f }, 1, 281474976710696L),
new StraightPathItem(new float[] { 3.484785f, 10.197294f, -34.241272f }, 0, 281474976710713L),
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -31.241272f }, 0, 281474976710712L),
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -29.741272f }, 0, 281474976710727L),
new StraightPathItem(new float[] { 2.584784f, 10.197294f, -27.941273f }, 0, 281474976710730L),
new StraightPathItem(new float[] { 6.457663f, 10.197294f, -18.334061f }, 2, 0L) },
new StraightPathItem(new float[] { 6.457663f, 10.197294f, -18.334061f }, 2, 0L)
},
new[] { new StraightPathItem(new float[] { 22.331268f, 10.197294f, -1.040187f }, 1, 281474976710773L),
new[]
{
new StraightPathItem(new float[] { 22.331268f, 10.197294f, -1.040187f }, 1, 281474976710773L),
new StraightPathItem(new float[] { 9.784786f, 10.197294f, -2.141273f }, 0, 281474976710755L),
new StraightPathItem(new float[] { 7.984783f, 10.197294f, -2.441269f }, 0, 281474976710753L),
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -8.441269f }, 0, 281474976710752L),
@ -66,9 +89,12 @@ public class FindPathTest : AbstractDetourTest {
new StraightPathItem(new float[] { -11.815216f, 9.997294f, -17.441269f }, 0, 281474976710736L),
new StraightPathItem(new float[] { -17.815216f, 5.197294f, -11.441269f }, 0, 281474976710735L),
new StraightPathItem(new float[] { -17.815216f, 5.197294f, -8.441269f }, 0, 281474976710746L),
new StraightPathItem(new float[] { -11.815216f, 0.197294f, 3.008419f }, 2, 0L) },
new StraightPathItem(new float[] { -11.815216f, 0.197294f, 3.008419f }, 2, 0L)
},
new[] { new StraightPathItem(new float[] { 18.694363f, 15.803535f, -73.090416f }, 1, 281474976710680L),
new[]
{
new StraightPathItem(new float[] { 18.694363f, 15.803535f, -73.090416f }, 1, 281474976710680L),
new StraightPathItem(new float[] { 17.584785f, 10.197294f, -49.841274f }, 0, 281474976710697L),
new StraightPathItem(new float[] { 17.284786f, 10.197294f, -48.041275f }, 0, 281474976710695L),
new StraightPathItem(new float[] { 16.084785f, 10.197294f, -45.341274f }, 0, 281474976710694L),
@ -77,24 +103,34 @@ public class FindPathTest : AbstractDetourTest {
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -8.441269f }, 0, 281474976710753L),
new StraightPathItem(new float[] { 7.984783f, 10.197294f, -2.441269f }, 0, 281474976710755L),
new StraightPathItem(new float[] { 9.784786f, 10.197294f, -2.141273f }, 0, 281474976710768L),
new StraightPathItem(new float[] { 38.423977f, 10.197294f, -0.116067f }, 2, 0L) },
new StraightPathItem(new float[] { 38.423977f, 10.197294f, -0.116067f }, 2, 0L)
},
new[] { new StraightPathItem(new float[] { 0.745335f, 10.197294f, -5.940050f }, 1, 281474976710753L),
new StraightPathItem(new float[] { 0.863553f, 10.197294f, -10.310320f }, 2, 0L) },
new[]
{
new StraightPathItem(new float[] { 0.745335f, 10.197294f, -5.940050f }, 1, 281474976710753L),
new StraightPathItem(new float[] { 0.863553f, 10.197294f, -10.310320f }, 2, 0L)
},
new[] { new StraightPathItem(new float[] { -20.651257f, 5.904126f, -13.712508f }, 1, 281474976710733L),
new[]
{
new StraightPathItem(new float[] { -20.651257f, 5.904126f, -13.712508f }, 1, 281474976710733L),
new StraightPathItem(new float[] { -11.815216f, 9.997294f, -17.441269f }, 0, 281474976710738L),
new StraightPathItem(new float[] { -10.015216f, 10.197294f, -17.741272f }, 0, 281474976710728L),
new StraightPathItem(new float[] { -8.215216f, 10.197294f, -17.441269f }, 0, 281474976710724L),
new StraightPathItem(new float[] { -4.315216f, 10.197294f, -15.341270f }, 0, 281474976710729L),
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -8.441269f }, 0, 281474976710753L),
new StraightPathItem(new float[] { 7.984783f, 10.197294f, -2.441269f }, 0, 281474976710755L),
new StraightPathItem(new float[] { 18.784092f, 10.197294f, 3.054368f }, 2, 0L) } };
new StraightPathItem(new float[] { 18.784092f, 10.197294f, 3.054368f }, 2, 0L)
}
};
[Test]
public void testFindPath() {
public void testFindPath()
{
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
for (int i = 0; i < startRefs.Length; i++)
{
long startRef = startRefs[i];
long endRef = endRefs[i];
float[] startPos = startPoss[i];
@ -102,40 +138,48 @@ public class FindPathTest : AbstractDetourTest {
Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter);
Assert.That(path.status, Is.EqualTo(STATUSES[i]));
Assert.That(path.result.Count, Is.EqualTo(RESULTS[i].Length));
for (int j = 0; j < RESULTS[i].Length; j++) {
for (int j = 0; j < RESULTS[i].Length; j++)
{
Assert.That(path.result[j], Is.EqualTo(RESULTS[i][j]));
}
}
}
[Test]
public void testFindPathSliced() {
public void testFindPathSliced()
{
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
for (int i = 0; i < startRefs.Length; i++)
{
long startRef = startRefs[i];
long endRef = endRefs[i];
float[] startPos = startPoss[i];
float[] endPos = endPoss[i];
query.initSlicedFindPath(startRef, endRef, startPos, endPos, filter, NavMeshQuery.DT_FINDPATH_ANY_ANGLE);
Status status = Status.IN_PROGRESS;
while (status == Status.IN_PROGRESS) {
while (status == Status.IN_PROGRESS)
{
Result<int> res = query.updateSlicedFindPath(10);
status = res.status;
}
Result<List<long>> path = query.finalizeSlicedFindPath();
Assert.That(path.status, Is.EqualTo(STATUSES[i]));
Assert.That(path.result.Count, Is.EqualTo(RESULTS[i].Length));
for (int j = 0; j < RESULTS[i].Length; j++) {
for (int j = 0; j < RESULTS[i].Length; j++)
{
Assert.That(path.result[j], Is.EqualTo(RESULTS[i][j]));
}
}
}
[Test]
public void testFindPathStraight() {
public void testFindPathStraight()
{
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < STRAIGHT_PATHS.Length; i++) {// startRefs.Length; i++) {
for (int i = 0; i < STRAIGHT_PATHS.Length; i++)
{
// startRefs.Length; i++) {
long startRef = startRefs[i];
long endRef = endRefs[i];
float[] startPos = startPoss[i];
@ -145,14 +189,16 @@ public class FindPathTest : AbstractDetourTest {
int.MaxValue, 0);
List<StraightPathItem> straightPath = result.result;
Assert.That(straightPath.Count, Is.EqualTo(STRAIGHT_PATHS[i].Length));
for (int j = 0; j < STRAIGHT_PATHS[i].Length; j++) {
for (int j = 0; j < STRAIGHT_PATHS[i].Length; j++)
{
Assert.That(straightPath[j].refs, Is.EqualTo(STRAIGHT_PATHS[i][j].refs));
for (int v = 0; v < 3; v++) {
for (int v = 0; v < 3; v++)
{
Assert.That(straightPath[j].pos[v], Is.EqualTo(STRAIGHT_PATHS[i][j].pos[v]).Within(0.01f));
}
Assert.That(straightPath[j].flags, Is.EqualTo(STRAIGHT_PATHS[i][j].flags));
}
}
}
}

View File

@ -20,64 +20,108 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class FindPolysAroundCircleTest : AbstractDetourTest {
private static readonly long[][] REFS = {
new[] { 281474976710696L, 281474976710695L, 281474976710694L, 281474976710691L, 281474976710697L, 281474976710693L,
281474976710686L, 281474976710687L, 281474976710692L, 281474976710703L, 281474976710689L },
public class FindPolysAroundCircleTest : AbstractDetourTest
{
private static readonly long[][] REFS =
{
new[]
{
281474976710696L, 281474976710695L, 281474976710694L, 281474976710691L, 281474976710697L, 281474976710693L,
281474976710686L, 281474976710687L, 281474976710692L, 281474976710703L, 281474976710689L
},
new[] { 281474976710773L, 281474976710770L, 281474976710769L, 281474976710772L, 281474976710771L },
new[] { 281474976710680L, 281474976710674L, 281474976710679L, 281474976710684L, 281474976710683L, 281474976710678L,
new[]
{
281474976710680L, 281474976710674L, 281474976710679L, 281474976710684L, 281474976710683L, 281474976710678L,
281474976710682L, 281474976710677L, 281474976710676L, 281474976710688L, 281474976710687L, 281474976710675L,
281474976710685L, 281474976710672L, 281474976710666L, 281474976710668L, 281474976710681L, 281474976710673L },
new[] { 281474976710753L, 281474976710748L, 281474976710755L, 281474976710756L, 281474976710750L, 281474976710752L,
281474976710731L, 281474976710729L, 281474976710749L, 281474976710719L, 281474976710717L, 281474976710726L },
new[] { 281474976710733L, 281474976710735L, 281474976710736L, 281474976710734L, 281474976710739L, 281474976710742L,
281474976710740L, 281474976710746L, 281474976710747L, } };
private static readonly long[][] PARENT_REFS = {
new[] { 0L, 281474976710696L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710697L,
281474976710686L, 281474976710693L, 281474976710694L, 281474976710687L },
281474976710685L, 281474976710672L, 281474976710666L, 281474976710668L, 281474976710681L, 281474976710673L
},
new[]
{
281474976710753L, 281474976710748L, 281474976710755L, 281474976710756L, 281474976710750L, 281474976710752L,
281474976710731L, 281474976710729L, 281474976710749L, 281474976710719L, 281474976710717L, 281474976710726L
},
new[]
{
281474976710733L, 281474976710735L, 281474976710736L, 281474976710734L, 281474976710739L, 281474976710742L,
281474976710740L, 281474976710746L, 281474976710747L,
}
};
private static readonly long[][] PARENT_REFS =
{
new[]
{
0L, 281474976710696L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710697L,
281474976710686L, 281474976710693L, 281474976710694L, 281474976710687L
},
new[] { 0L, 281474976710773L, 281474976710773L, 281474976710773L, 281474976710772L },
new[] { 0L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710679L, 281474976710683L,
new[]
{
0L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710679L, 281474976710683L,
281474976710683L, 281474976710678L, 281474976710684L, 281474976710688L, 281474976710677L, 281474976710687L,
281474976710682L, 281474976710672L, 281474976710672L, 281474976710675L, 281474976710666L },
new[] { 0L, 281474976710753L, 281474976710753L, 281474976710753L, 281474976710753L, 281474976710748L, 281474976710752L,
281474976710731L, 281474976710756L, 281474976710729L, 281474976710729L, 281474976710717L },
new[] { 0L, 281474976710733L, 281474976710733L, 281474976710736L, 281474976710736L, 281474976710735L, 281474976710742L,
281474976710740L, 281474976710746L } };
private static readonly float[][] COSTS = {
new[] { 0.000000f, 0.391453f, 6.764245f, 4.153431f, 3.721995f, 6.109188f, 5.378797f, 7.178796f, 7.009186f, 7.514245f,
12.655564f },
281474976710682L, 281474976710672L, 281474976710672L, 281474976710675L, 281474976710666L
},
new[]
{
0L, 281474976710753L, 281474976710753L, 281474976710753L, 281474976710753L, 281474976710748L, 281474976710752L,
281474976710731L, 281474976710756L, 281474976710729L, 281474976710729L, 281474976710717L
},
new[]
{
0L, 281474976710733L, 281474976710733L, 281474976710736L, 281474976710736L, 281474976710735L, 281474976710742L,
281474976710740L, 281474976710746L
}
};
private static readonly float[][] COSTS =
{
new[]
{
0.000000f, 0.391453f, 6.764245f, 4.153431f, 3.721995f, 6.109188f, 5.378797f, 7.178796f, 7.009186f, 7.514245f,
12.655564f
},
new[] { 0.000000f, 6.161580f, 2.824478f, 2.828730f, 8.035697f },
new[] { 0.000000f, 1.162604f, 1.954029f, 2.776051f, 2.046001f, 2.428367f, 6.429493f, 6.032851f, 2.878368f, 5.333885f,
6.394545f, 9.596563f, 12.457960f, 7.096575f, 10.413582f, 10.362305f, 10.665442f, 10.593861f },
new[] { 0.000000f, 2.483205f, 6.723722f, 5.727250f, 3.126022f, 3.543865f, 5.043865f, 6.843868f, 7.212173f, 10.602858f,
8.793867f, 13.146453f },
new[] { 0.000000f, 2.480514f, 0.823685f, 5.002500f, 8.229258f, 3.983844f, 5.483844f, 6.655379f, 11.996962f } };
new[]
{
0.000000f, 1.162604f, 1.954029f, 2.776051f, 2.046001f, 2.428367f, 6.429493f, 6.032851f, 2.878368f, 5.333885f,
6.394545f, 9.596563f, 12.457960f, 7.096575f, 10.413582f, 10.362305f, 10.665442f, 10.593861f
},
new[]
{
0.000000f, 2.483205f, 6.723722f, 5.727250f, 3.126022f, 3.543865f, 5.043865f, 6.843868f, 7.212173f, 10.602858f,
8.793867f, 13.146453f
},
new[] { 0.000000f, 2.480514f, 0.823685f, 5.002500f, 8.229258f, 3.983844f, 5.483844f, 6.655379f, 11.996962f }
};
[Test]
public void testFindPolysAroundCircle() {
public void testFindPolysAroundCircle()
{
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
for (int i = 0; i < startRefs.Length; i++)
{
long startRef = startRefs[i];
float[] startPos = startPoss[i];
Result<FindPolysAroundResult> result = query.findPolysAroundCircle(startRef, startPos, 7.5f, filter);
Assert.That(result.succeeded(), Is.True);
FindPolysAroundResult polys = result.result;
Assert.That(polys.getRefs().Count, Is.EqualTo(REFS[i].Length));
for (int v = 0; v < REFS[i].Length; v++) {
for (int v = 0; v < REFS[i].Length; v++)
{
bool found = false;
for (int w = 0; w < REFS[i].Length; w++) {
if (REFS[i][v] == polys.getRefs()[w]) {
for (int w = 0; w < REFS[i].Length; w++)
{
if (REFS[i][v] == polys.getRefs()[w])
{
Assert.That(polys.getParentRefs()[w], Is.EqualTo(PARENT_REFS[i][v]));
Assert.That(polys.getCosts()[w], Is.EqualTo(COSTS[i][v]).Within(0.01f));
found = true;
}
}
Assert.That(found, Is.True, $"Ref not found {REFS[i][v]}");
}
}
}
}

View File

@ -20,91 +20,139 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class FindPolysAroundShapeTest : AbstractDetourTest {
private static readonly long[][] REFS = {
new[] { 281474976710696L, 281474976710695L, 281474976710694L, 281474976710691L, 281474976710697L,
public class FindPolysAroundShapeTest : AbstractDetourTest
{
private static readonly long[][] REFS =
{
new[]
{
281474976710696L, 281474976710695L, 281474976710694L, 281474976710691L, 281474976710697L,
281474976710693L, 281474976710692L, 281474976710703L, 281474976710706L, 281474976710699L,
281474976710705L, 281474976710698L, 281474976710700L, 281474976710704L },
new[] { 281474976710773L, 281474976710769L, 281474976710772L, 281474976710768L, 281474976710771L,
281474976710705L, 281474976710698L, 281474976710700L, 281474976710704L
},
new[]
{
281474976710773L, 281474976710769L, 281474976710772L, 281474976710768L, 281474976710771L,
281474976710754L, 281474976710755L, 281474976710753L, 281474976710751L, 281474976710756L,
281474976710749L },
new[] { 281474976710680L, 281474976710679L, 281474976710684L, 281474976710683L, 281474976710688L,
281474976710749L
},
new[]
{
281474976710680L, 281474976710679L, 281474976710684L, 281474976710683L, 281474976710688L,
281474976710678L, 281474976710676L, 281474976710687L, 281474976710690L, 281474976710686L,
281474976710689L, 281474976710685L, 281474976710697L, 281474976710695L, 281474976710694L,
281474976710691L, 281474976710696L, 281474976710693L, 281474976710692L, 281474976710703L,
281474976710706L, 281474976710699L, 281474976710705L, 281474976710700L, 281474976710704L },
281474976710706L, 281474976710699L, 281474976710705L, 281474976710700L, 281474976710704L
},
new[] { 281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L },
new[] { 281474976710733L, 281474976710735L, 281474976710736L, 281474976710742L, 281474976710734L,
new[]
{
281474976710733L, 281474976710735L, 281474976710736L, 281474976710742L, 281474976710734L,
281474976710739L, 281474976710738L, 281474976710740L, 281474976710746L, 281474976710743L,
281474976710745L, 281474976710741L, 281474976710747L, 281474976710737L, 281474976710732L,
281474976710728L, 281474976710724L, 281474976710744L, 281474976710725L, 281474976710717L,
281474976710729L, 281474976710726L, 281474976710721L, 281474976710719L, 281474976710731L,
281474976710720L, 281474976710752L, 281474976710748L, 281474976710753L, 281474976710755L,
281474976710756L, 281474976710750L, 281474976710749L, 281474976710754L, 281474976710751L,
281474976710768L, 281474976710772L, 281474976710773L, 281474976710771L, 281474976710769L } };
private static readonly long[][] PARENT_REFS = {
new[] { 0L, 281474976710696L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710695L,
281474976710768L, 281474976710772L, 281474976710773L, 281474976710771L, 281474976710769L
}
};
private static readonly long[][] PARENT_REFS =
{
new[]
{
0L, 281474976710696L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710695L,
281474976710693L, 281474976710694L, 281474976710703L, 281474976710706L, 281474976710706L,
281474976710705L, 281474976710705L, 281474976710705L },
new[] { 0L, 281474976710773L, 281474976710773L, 281474976710772L, 281474976710772L, 281474976710768L,
281474976710754L, 281474976710755L, 281474976710755L, 281474976710753L, 281474976710756L },
new[] { 0L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710684L, 281474976710679L,
281474976710705L, 281474976710705L, 281474976710705L
},
new[]
{
0L, 281474976710773L, 281474976710773L, 281474976710772L, 281474976710772L, 281474976710768L,
281474976710754L, 281474976710755L, 281474976710755L, 281474976710753L, 281474976710756L
},
new[]
{
0L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710684L, 281474976710679L,
281474976710678L, 281474976710688L, 281474976710687L, 281474976710687L, 281474976710687L,
281474976710687L, 281474976710686L, 281474976710697L, 281474976710695L, 281474976710695L,
281474976710695L, 281474976710695L, 281474976710693L, 281474976710694L, 281474976710703L,
281474976710706L, 281474976710706L, 281474976710705L, 281474976710705L },
281474976710706L, 281474976710706L, 281474976710705L, 281474976710705L
},
new[] { 0L, 281474976710753L, 281474976710748L, 281474976710752L },
new[] { 0L, 281474976710733L, 281474976710733L, 281474976710735L, 281474976710736L, 281474976710736L,
new[]
{
0L, 281474976710733L, 281474976710733L, 281474976710735L, 281474976710736L, 281474976710736L,
281474976710736L, 281474976710742L, 281474976710740L, 281474976710746L, 281474976710746L,
281474976710746L, 281474976710746L, 281474976710738L, 281474976710738L, 281474976710737L,
281474976710728L, 281474976710745L, 281474976710724L, 281474976710724L, 281474976710717L,
281474976710717L, 281474976710717L, 281474976710729L, 281474976710729L, 281474976710721L,
281474976710731L, 281474976710752L, 281474976710748L, 281474976710753L, 281474976710753L,
281474976710753L, 281474976710756L, 281474976710755L, 281474976710755L, 281474976710754L,
281474976710768L, 281474976710772L, 281474976710772L, 281474976710773L } };
private static readonly float[][] COSTS = {
new[] { 0.000000f, 16.188787f, 22.561579f, 19.950766f, 19.519329f, 21.906523f, 22.806520f, 23.311579f, 25.124035f,
28.454576f, 26.084503f, 36.438854f, 30.526634f, 31.942192f },
new[] { 0.000000f, 16.618738f, 12.136283f, 20.387646f, 17.343250f, 22.037645f, 22.787645f, 27.178831f, 26.501472f,
31.691311f, 33.176235f },
new[] { 0.000000f, 36.657764f, 35.197689f, 37.484924f, 37.755524f, 37.132103f, 37.582104f, 38.816185f, 52.426109f,
281474976710768L, 281474976710772L, 281474976710772L, 281474976710773L
}
};
private static readonly float[][] COSTS =
{
new[]
{
0.000000f, 16.188787f, 22.561579f, 19.950766f, 19.519329f, 21.906523f, 22.806520f, 23.311579f, 25.124035f,
28.454576f, 26.084503f, 36.438854f, 30.526634f, 31.942192f
},
new[]
{
0.000000f, 16.618738f, 12.136283f, 20.387646f, 17.343250f, 22.037645f, 22.787645f, 27.178831f, 26.501472f,
31.691311f, 33.176235f
},
new[]
{
0.000000f, 36.657764f, 35.197689f, 37.484924f, 37.755524f, 37.132103f, 37.582104f, 38.816185f, 52.426109f,
55.945839f, 51.882935f, 44.879601f, 57.745838f, 59.402641f, 65.063034f, 64.934372f, 62.733185f,
62.756744f, 63.656742f, 65.813034f, 67.625488f, 70.956032f, 68.585960f, 73.028091f, 74.443649f },
62.756744f, 63.656742f, 65.813034f, 67.625488f, 70.956032f, 68.585960f, 73.028091f, 74.443649f
},
new[] { 0.000000f, 2.097958f, 3.158618f, 4.658618f },
new[] { 0.000000f, 20.495766f, 21.352942f, 21.999096f, 25.531757f, 28.758514f, 30.264732f, 23.499096f, 24.670631f,
new[]
{
0.000000f, 20.495766f, 21.352942f, 21.999096f, 25.531757f, 28.758514f, 30.264732f, 23.499096f, 24.670631f,
33.166218f, 35.651184f, 34.371792f, 30.012215f, 33.886887f, 33.855347f, 34.643524f, 36.300327f,
38.203144f, 40.339203f, 40.203213f, 47.254810f, 50.043945f, 49.054485f, 49.804810f, 49.204811f,
52.813477f, 51.004814f, 52.504814f, 53.565475f, 62.748611f, 61.504147f, 57.915474f, 62.989071f,
67.139801f, 66.507599f, 67.889801f, 69.539803f, 77.791168f, 75.186256f, 83.111412f } };
67.139801f, 66.507599f, 67.889801f, 69.539803f, 77.791168f, 75.186256f, 83.111412f
}
};
[Test]
public void testFindPolysAroundShape() {
public void testFindPolysAroundShape()
{
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
for (int i = 0; i < startRefs.Length; i++)
{
long startRef = startRefs[i];
float[] startPos = startPoss[i];
Result<FindPolysAroundResult> polys = query.findPolysAroundShape(startRef,
getQueryPoly(startPos, endPoss[i]), filter);
Assert.That(polys.result.getRefs().Count, Is.EqualTo(REFS[i].Length));
for (int v = 0; v < REFS[i].Length; v++) {
for (int v = 0; v < REFS[i].Length; v++)
{
bool found = false;
for (int w = 0; w < REFS[i].Length; w++) {
if (REFS[i][v] == polys.result.getRefs()[w]) {
for (int w = 0; w < REFS[i].Length; w++)
{
if (REFS[i][v] == polys.result.getRefs()[w])
{
Assert.That(polys.result.getParentRefs()[w], Is.EqualTo(PARENT_REFS[i][v]));
Assert.That(polys.result.getCosts()[w], Is.EqualTo(COSTS[i][v]).Within(0.01f));
found = true;
}
}
Assert.That(found, Is.True);
}
}
}
private float[] getQueryPoly(float[] m_spos, float[] m_epos) {
private float[] getQueryPoly(float[] m_spos, float[] m_epos)
{
float nx = (m_epos[2] - m_spos[2]) * 0.25f;
float nz = -(m_epos[0] - m_spos[0]) * 0.25f;
float agentHeight = 2.0f;
@ -127,5 +175,4 @@ public class FindPolysAroundShapeTest : AbstractDetourTest {
m_queryPoly[11] = m_epos[2] + nz;
return m_queryPoly;
}
}

View File

@ -20,56 +20,80 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class GetPolyWallSegmentsTest : AbstractDetourTest {
private static readonly float[][] VERTICES = {
new[] { 22.084785f, 10.197294f, -48.341274f, 22.684784f, 10.197294f, -44.141273f, 22.684784f, 10.197294f,
public class GetPolyWallSegmentsTest : AbstractDetourTest
{
private static readonly float[][] VERTICES =
{
new[]
{
22.084785f, 10.197294f, -48.341274f, 22.684784f, 10.197294f, -44.141273f, 22.684784f, 10.197294f,
-44.141273f, 23.884785f, 10.197294f, -48.041275f, 23.884785f, 10.197294f, -48.041275f, 22.084785f,
10.197294f, -48.341274f },
new[] { 27.784786f, 10.197294f, 4.158730f, 28.384785f, 10.197294f, 2.358727f, 28.384785f, 10.197294f, 2.358727f,
10.197294f, -48.341274f
},
new[]
{
27.784786f, 10.197294f, 4.158730f, 28.384785f, 10.197294f, 2.358727f, 28.384785f, 10.197294f, 2.358727f,
28.384785f, 10.197294f, -2.141273f, 28.384785f, 10.197294f, -2.141273f, 27.784786f, 10.197294f,
-2.741272f, 27.784786f, 10.197294f, -2.741272f, 19.684784f, 10.197294f, -4.241272f, 19.684784f,
10.197294f, -4.241272f, 19.684784f, 10.197294f, 4.158730f, 19.684784f, 10.197294f, 4.158730f,
27.784786f, 10.197294f, 4.158730f },
new[] { 22.384785f, 14.997294f, -71.741272f, 19.084785f, 16.597294f, -74.741272f, 19.084785f, 16.597294f,
27.784786f, 10.197294f, 4.158730f
},
new[]
{
22.384785f, 14.997294f, -71.741272f, 19.084785f, 16.597294f, -74.741272f, 19.084785f, 16.597294f,
-74.741272f, 18.184784f, 15.997294f, -73.541275f, 18.184784f, 15.997294f, -73.541275f, 17.884785f,
14.997294f, -72.341278f, 17.884785f, 14.997294f, -72.341278f, 17.584785f, 14.997294f, -70.841278f,
17.584785f, 14.997294f, -70.841278f, 22.084785f, 14.997294f, -70.541275f, 22.084785f, 14.997294f,
-70.541275f, 22.384785f, 14.997294f, -71.741272f },
new[] { 4.684784f, 10.197294f, -6.941269f, 1.984785f, 10.197294f, -8.441269f, 1.984785f, 10.197294f, -8.441269f,
-70.541275f, 22.384785f, 14.997294f, -71.741272f
},
new[]
{
4.684784f, 10.197294f, -6.941269f, 1.984785f, 10.197294f, -8.441269f, 1.984785f, 10.197294f, -8.441269f,
-4.015217f, 10.197294f, -6.941269f, -4.015217f, 10.197294f, -6.941269f, -1.615215f, 10.197294f,
-1.541275f, -1.615215f, 10.197294f, -1.541275f, 1.384785f, 10.197294f, 1.458725f, 1.384785f,
10.197294f, 1.458725f, 7.984783f, 10.197294f, -2.441269f, 7.984783f, 10.197294f, -2.441269f,
4.684784f, 10.197294f, -6.941269f },
new[] { -22.315216f, 6.597294f, -17.141273f, -23.815216f, 5.397294f, -13.841270f, -23.815216f, 5.397294f,
4.684784f, 10.197294f, -6.941269f
},
new[]
{
-22.315216f, 6.597294f, -17.141273f, -23.815216f, 5.397294f, -13.841270f, -23.815216f, 5.397294f,
-13.841270f, -24.115217f, 4.997294f, -12.041275f, -24.115217f, 4.997294f, -12.041275f, -22.315216f,
4.997294f, -11.441269f, -22.315216f, 4.997294f, -11.441269f, -17.815216f, 5.197294f, -11.441269f,
-17.815216f, 5.197294f, -11.441269f, -22.315216f, 6.597294f, -17.141273f } };
private static readonly long[][] REFS = {
-17.815216f, 5.197294f, -11.441269f, -22.315216f, 6.597294f, -17.141273f
}
};
private static readonly long[][] REFS =
{
new[] { 281474976710695L, 0L, 0L },
new[] { 0L, 281474976710770L, 0L, 281474976710769L, 281474976710772L, 0L },
new[] { 281474976710683L, 281474976710674L, 0L, 281474976710679L, 281474976710684L, 0L },
new[] { 281474976710750L, 281474976710748L, 0L, 0L, 281474976710755L, 281474976710756L },
new[] { 0L, 0L, 0L, 281474976710735L, 281474976710736L } };
new[] { 0L, 0L, 0L, 281474976710735L, 281474976710736L }
};
[Test]
public void testFindDistanceToWall() {
public void testFindDistanceToWall()
{
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
for (int i = 0; i < startRefs.Length; i++)
{
Result<GetPolyWallSegmentsResult> result = query.getPolyWallSegments(startRefs[i], true, filter);
GetPolyWallSegmentsResult segments = result.result;
Assert.That(segments.getSegmentVerts().Count, Is.EqualTo(VERTICES[i].Length / 6));
Assert.That(segments.getSegmentRefs().Count, Is.EqualTo(REFS[i].Length));
for (int v = 0; v < VERTICES[i].Length / 6; v++) {
for (int n = 0; n < 6; n++) {
for (int v = 0; v < VERTICES[i].Length / 6; v++)
{
for (int n = 0; n < 6; n++)
{
Assert.That(segments.getSegmentVerts()[v][n], Is.EqualTo(VERTICES[i][v * 6 + n]).Within(0.001f));
}
}
for (int v = 0; v < REFS[i].Length; v++) {
for (int v = 0; v < REFS[i].Length; v++)
{
Assert.That(segments.getSegmentRefs()[v], Is.EqualTo(REFS[i][v]));
}
}
}
}

View File

@ -23,39 +23,44 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test.Io;
public class MeshDataReaderWriterTest {
public class MeshDataReaderWriterTest
{
private const int VERTS_PER_POLYGON = 6;
private MeshData meshData;
[SetUp]
public void setUp() {
public void setUp()
{
RecastTestMeshBuilder rcBuilder = new RecastTestMeshBuilder();
meshData = rcBuilder.getMeshData();
}
[Test]
public void testCCompatibility() {
public void testCCompatibility()
{
test(true, ByteOrder.BIG_ENDIAN);
}
[Test]
public void testCompact() {
public void testCompact()
{
test(false, ByteOrder.BIG_ENDIAN);
}
[Test]
public void testCCompatibilityLE() {
public void testCCompatibilityLE()
{
test(true, ByteOrder.LITTLE_ENDIAN);
}
[Test]
public void testCompactLE() {
public void testCompactLE()
{
test(false, ByteOrder.LITTLE_ENDIAN);
}
public void test(bool cCompatibility, ByteOrder order) {
public void test(bool cCompatibility, ByteOrder order)
{
using var ms = new MemoryStream();
using var bwos = new BinaryWriter(ms);
@ -74,43 +79,59 @@ public class MeshDataReaderWriterTest {
Assert.That(readData.header.detailVertCount, Is.EqualTo(meshData.header.detailVertCount));
Assert.That(readData.header.bvNodeCount, Is.EqualTo(meshData.header.bvNodeCount));
Assert.That(readData.header.offMeshConCount, Is.EqualTo(meshData.header.offMeshConCount));
for (int i = 0; i < meshData.header.vertCount; i++) {
for (int i = 0; i < meshData.header.vertCount; i++)
{
Assert.That(readData.verts[i], Is.EqualTo(meshData.verts[i]));
}
for (int i = 0; i < meshData.header.polyCount; i++) {
for (int i = 0; i < meshData.header.polyCount; i++)
{
Assert.That(readData.polys[i].vertCount, Is.EqualTo(meshData.polys[i].vertCount));
Assert.That(readData.polys[i].areaAndtype, Is.EqualTo(meshData.polys[i].areaAndtype));
for (int j = 0; j < meshData.polys[i].vertCount; j++) {
for (int j = 0; j < meshData.polys[i].vertCount; j++)
{
Assert.That(readData.polys[i].verts[j], Is.EqualTo(meshData.polys[i].verts[j]));
Assert.That(readData.polys[i].neis[j], Is.EqualTo(meshData.polys[i].neis[j]));
}
}
for (int i = 0; i < meshData.header.detailMeshCount; i++) {
for (int i = 0; i < meshData.header.detailMeshCount; i++)
{
Assert.That(readData.detailMeshes[i].vertBase, Is.EqualTo(meshData.detailMeshes[i].vertBase));
Assert.That(readData.detailMeshes[i].vertCount, Is.EqualTo(meshData.detailMeshes[i].vertCount));
Assert.That(readData.detailMeshes[i].triBase, Is.EqualTo(meshData.detailMeshes[i].triBase));
Assert.That(readData.detailMeshes[i].triCount, Is.EqualTo(meshData.detailMeshes[i].triCount));
}
for (int i = 0; i < meshData.header.detailVertCount; i++) {
for (int i = 0; i < meshData.header.detailVertCount; i++)
{
Assert.That(readData.detailVerts[i], Is.EqualTo(meshData.detailVerts[i]));
}
for (int i = 0; i < meshData.header.detailTriCount; i++) {
for (int i = 0; i < meshData.header.detailTriCount; i++)
{
Assert.That(readData.detailTris[i], Is.EqualTo(meshData.detailTris[i]));
}
for (int i = 0; i < meshData.header.bvNodeCount; i++) {
for (int i = 0; i < meshData.header.bvNodeCount; i++)
{
Assert.That(readData.bvTree[i].i, Is.EqualTo(meshData.bvTree[i].i));
for (int j = 0; j < 3; j++) {
for (int j = 0; j < 3; j++)
{
Assert.That(readData.bvTree[i].bmin[j], Is.EqualTo(meshData.bvTree[i].bmin[j]));
Assert.That(readData.bvTree[i].bmax[j], Is.EqualTo(meshData.bvTree[i].bmax[j]));
}
}
for (int i = 0; i < meshData.header.offMeshConCount; i++) {
for (int i = 0; i < meshData.header.offMeshConCount; i++)
{
Assert.That(readData.offMeshCons[i].flags, Is.EqualTo(meshData.offMeshCons[i].flags));
Assert.That(readData.offMeshCons[i].rad, Is.EqualTo(meshData.offMeshCons[i].rad));
Assert.That(readData.offMeshCons[i].poly, Is.EqualTo(meshData.offMeshCons[i].poly));
Assert.That(readData.offMeshCons[i].side, Is.EqualTo(meshData.offMeshCons[i].side));
Assert.That(readData.offMeshCons[i].userId, Is.EqualTo(meshData.offMeshCons[i].userId));
for (int j = 0; j < 6; j++) {
for (int j = 0; j < 6; j++)
{
Assert.That(readData.offMeshCons[i].pos[j], Is.EqualTo(meshData.offMeshCons[i].pos[j]));
}
}

View File

@ -24,12 +24,13 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test.Io;
public class MeshSetReaderTest {
public class MeshSetReaderTest
{
private readonly MeshSetReader reader = new MeshSetReader();
[Test]
public void testNavmesh() {
public void testNavmesh()
{
byte[] @is = Loader.ToBytes("all_tiles_navmesh.bin");
using var ms = new MemoryStream(@is);
using var bris = new BinaryReader(ms);
@ -56,7 +57,8 @@ public class MeshSetReaderTest {
}
[Test]
public void testDungeon() {
public void testDungeon()
{
byte[] @is = Loader.ToBytes("dungeon_all_tiles_navmesh.bin");
using var ms = new MemoryStream(@is);
using var bris = new BinaryReader(ms);
@ -84,7 +86,8 @@ public class MeshSetReaderTest {
}
[Test]
public void testDungeon32Bit() {
public void testDungeon32Bit()
{
byte[] @is = Loader.ToBytes("dungeon_all_tiles_navmesh_32bit.bin");
using var ms = new MemoryStream(@is);
using var bris = new BinaryReader(ms);

View File

@ -23,13 +23,12 @@ using DotRecast.Detour.Io;
using DotRecast.Recast;
using DotRecast.Recast.Geom;
using NUnit.Framework;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Test.Io;
public class MeshSetReaderWriterTest {
public class MeshSetReaderWriterTest
{
private readonly MeshSetWriter writer = new MeshSetWriter();
private readonly MeshSetReader reader = new MeshSetReader();
private const float m_cellSize = 0.3f;
@ -52,8 +51,8 @@ public class MeshSetReaderWriterTest {
private const int m_maxPolysPerTile = 0x8000;
[Test]
public void test() {
public void test()
{
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
NavMeshSetHeader header = new NavMeshSetHeader();
@ -72,8 +71,10 @@ public class MeshSetReaderWriterTest {
int[] twh = DotRecast.Recast.Recast.calcTileCount(bmin, bmax, m_cellSize, m_tileSize, m_tileSize);
int tw = twh[0];
int th = twh[1];
for (int y = 0; y < th; ++y) {
for (int x = 0; x < tw; ++x) {
for (int y = 0; y < th; ++y)
{
for (int x = 0; x < tw; ++x)
{
RecastConfig cfg = new RecastConfig(true, m_tileSize, m_tileSize,
RecastConfig.calcBorder(m_agentRadius, m_cellSize), PartitionType.WATERSHED, m_cellSize, m_cellHeight,
m_agentMaxSlope, true, true, true, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_regionMinArea,
@ -82,7 +83,8 @@ public class MeshSetReaderWriterTest {
RecastBuilderConfig bcfg = new RecastBuilderConfig(cfg, bmin, bmax, x, y);
TestDetourBuilder db = new TestDetourBuilder();
MeshData data = db.build(geom, bcfg, m_agentHeight, m_agentRadius, m_agentMaxClimb, x, y, true);
if (data != null) {
if (data != null)
{
mesh.removeTile(mesh.getTileRefAt(x, y, 0));
mesh.addTile(data, 0, 0);
}
@ -115,6 +117,5 @@ public class MeshSetReaderWriterTest {
Assert.That(tiles.Count, Is.EqualTo(1));
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(5));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(17 * 3));
}
}

View File

@ -20,49 +20,70 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class MoveAlongSurfaceTest : AbstractDetourTest {
private static readonly long[][] VISITED = {
new[] { 281474976710696L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
public class MoveAlongSurfaceTest : AbstractDetourTest
{
private static readonly long[][] VISITED =
{
new[]
{
281474976710696L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
281474976710705L, 281474976710702L, 281474976710701L, 281474976710714L, 281474976710713L,
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710721L },
new[] { 281474976710773L, 281474976710772L, 281474976710768L, 281474976710754L, 281474976710755L,
281474976710753L },
new[] { 281474976710680L, 281474976710684L, 281474976710688L, 281474976710687L, 281474976710686L,
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710721L
},
new[]
{
281474976710773L, 281474976710772L, 281474976710768L, 281474976710754L, 281474976710755L,
281474976710753L
},
new[]
{
281474976710680L, 281474976710684L, 281474976710688L, 281474976710687L, 281474976710686L,
281474976710697L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
281474976710705L, 281474976710702L, 281474976710701L, 281474976710714L, 281474976710713L,
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710721L,
281474976710718L },
281474976710718L
},
new[] { 281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L },
new[] { 281474976710733L, 281474976710736L, 281474976710738L, 281474976710737L, 281474976710728L,
new[]
{
281474976710733L, 281474976710736L, 281474976710738L, 281474976710737L, 281474976710728L,
281474976710724L, 281474976710717L, 281474976710729L, 281474976710731L, 281474976710752L,
281474976710748L, 281474976710753L, 281474976710755L, 281474976710754L, 281474976710768L,
281474976710772L } };
private static readonly float[][] POSITION = {
281474976710772L
}
};
private static readonly float[][] POSITION =
{
new[] { 6.457663f, 10.197294f, -18.334061f },
new[] { -1.433933f, 10.197294f, -1.359993f },
new[] { 12.184784f, 9.997294f, -18.941269f },
new[] { 0.863553f, 10.197294f, -10.310320f },
new[] { 18.784092f, 10.197294f, 3.054368f } };
new[] { 18.784092f, 10.197294f, 3.054368f }
};
[Test]
public void testMoveAlongSurface() {
public void testMoveAlongSurface()
{
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
for (int i = 0; i < startRefs.Length; i++)
{
long startRef = startRefs[i];
float[] startPos = startPoss[i];
float[] endPos = endPoss[i];
Result<MoveAlongSurfaceResult> result = query.moveAlongSurface(startRef, startPos, endPos, filter);
Assert.That(result.succeeded(), Is.True);
MoveAlongSurfaceResult path = result.result;
for (int v = 0; v < 3; v++) {
for (int v = 0; v < 3; v++)
{
Assert.That(path.getResultPos()[v], Is.EqualTo(POSITION[i][v]).Within(0.01f));
}
Assert.That(path.getVisited().Count, Is.EqualTo(VISITED[i].Length));
for (int j = 0; j < POSITION[i].Length; j++) {
for (int j = 0; j < POSITION[i].Length; j++)
{
Assert.That(path.getVisited()[j], Is.EqualTo(VISITED[i][j]));
}
}
}
}

View File

@ -20,18 +20,19 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class NavMeshBuilderTest {
public class NavMeshBuilderTest
{
private MeshData nmd;
[SetUp]
public void setUp() {
public void setUp()
{
nmd = new RecastTestMeshBuilder().getMeshData();
}
[Test]
public void testBVTree() {
public void testBVTree()
{
Assert.That(nmd.verts.Length / 3, Is.EqualTo(225));
Assert.That(nmd.polys.Length, Is.EqualTo(119));
Assert.That(nmd.header.maxLinkCount, Is.EqualTo(457));
@ -42,12 +43,16 @@ public class NavMeshBuilderTest {
Assert.That(nmd.header.offMeshBase, Is.EqualTo(118));
Assert.That(nmd.bvTree.Length, Is.EqualTo(236));
Assert.That(nmd.bvTree.Length, Is.GreaterThanOrEqualTo(nmd.header.bvNodeCount));
for (int i = 0; i < nmd.header.bvNodeCount; i++) {
for (int i = 0; i < nmd.header.bvNodeCount; i++)
{
Assert.That(nmd.bvTree[i], Is.Not.Null);
}
for (int i = 0; i < 6; i++) {
for (int i = 0; i < 6; i++)
{
Assert.That(nmd.verts[223 * 3 + i], Is.EqualTo(nmd.offMeshCons[0].pos[i]));
}
Assert.That(nmd.offMeshCons[0].rad, Is.EqualTo(0.1f));
Assert.That(nmd.offMeshCons[0].poly, Is.EqualTo(118));
Assert.That(nmd.offMeshCons[0].flags, Is.EqualTo(NavMesh.DT_OFFMESH_CON_BIDIR));
@ -59,6 +64,5 @@ public class NavMeshBuilderTest {
Assert.That(nmd.polys[118].flags, Is.EqualTo(12));
Assert.That(nmd.polys[118].getArea(), Is.EqualTo(2));
Assert.That(nmd.polys[118].getType(), Is.EqualTo(Poly.DT_POLYTYPE_OFFMESH_CONNECTION));
}
}

View File

@ -20,13 +20,13 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class PolygonByCircleConstraintTest {
public class PolygonByCircleConstraintTest
{
private readonly PolygonByCircleConstraint constraint = new PolygonByCircleConstraint.StrictPolygonByCircleConstraint();
[Test]
public void shouldHandlePolygonFullyInsideCircle() {
public void shouldHandlePolygonFullyInsideCircle()
{
float[] polygon = { -2, 0, 2, 2, 0, 2, 2, 0, -2, -2, 0, -2 };
float[] center = { 1, 0, 1 };
float[] constrained = constraint.aply(polygon, center, 6);
@ -35,7 +35,8 @@ public class PolygonByCircleConstraintTest {
}
[Test]
public void shouldHandleVerticalSegment() {
public void shouldHandleVerticalSegment()
{
int expectedSize = 21;
float[] polygon = { -2, 0, 2, 2, 0, 2, 2, 0, -2, -2, 0, -2 };
float[] center = { 2, 0, 0 };
@ -46,7 +47,8 @@ public class PolygonByCircleConstraintTest {
}
[Test]
public void shouldHandleCircleFullyInsidePolygon() {
public void shouldHandleCircleFullyInsidePolygon()
{
int expectedSize = 12 * 3;
float[] polygon = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
float[] center = { -1, 0, -1 };
@ -54,7 +56,8 @@ public class PolygonByCircleConstraintTest {
Assert.That(constrained.Length, Is.EqualTo(expectedSize));
for (int i = 0; i < expectedSize; i += 3) {
for (int i = 0; i < expectedSize; i += 3)
{
float x = constrained[i] + 1;
float z = constrained[i + 2] + 1;
Assert.That(x * x + z * z, Is.EqualTo(4).Within(1e-4f));
@ -62,7 +65,8 @@ public class PolygonByCircleConstraintTest {
}
[Test]
public void shouldHandleCircleInsidePolygon() {
public void shouldHandleCircleInsidePolygon()
{
int expectedSize = 9 * 3;
float[] polygon = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
float[] center = { -2, 0, -1 };
@ -83,5 +87,4 @@ public class PolygonByCircleConstraintTest {
Assert.That(constrained.Length, Is.EqualTo(expectedSize));
Assert.That(constrained, Is.SupersetOf(new[] { 1.5358982f, 0f, 3f, 2f, 0f, 3f, 3f, 0f, -3f }));
}
}

View File

@ -19,30 +19,33 @@ freely, subject to the following restrictions:
using System;
using System.Diagnostics;
using NUnit.Framework;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Test;
public class RandomPointTest : AbstractDetourTest {
public class RandomPointTest : AbstractDetourTest
{
[Test]
public void testRandom() {
public void testRandom()
{
NavMeshQuery.FRand f = new NavMeshQuery.FRand(1);
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < 1000; i++) {
for (int i = 0; i < 1000; i++)
{
Result<FindRandomPointResult> point = query.findRandomPoint(filter, f);
Assert.That(point.succeeded(), Is.True);
Tuple<MeshTile, Poly> tileAndPoly = navmesh.getTileAndPolyByRef(point.result.getRandomRef()).result;
float[] bmin = new float[2];
float[] bmax = new float[2];
for (int j = 0; j < tileAndPoly.Item2.vertCount; j++) {
for (int j = 0; j < tileAndPoly.Item2.vertCount; j++)
{
int v = tileAndPoly.Item2.verts[j] * 3;
bmin[0] = j == 0 ? tileAndPoly.Item1.data.verts[v] : Math.Min(bmin[0], tileAndPoly.Item1.data.verts[v]);
bmax[0] = j == 0 ? tileAndPoly.Item1.data.verts[v] : Math.Max(bmax[0], tileAndPoly.Item1.data.verts[v]);
bmin[1] = j == 0 ? tileAndPoly.Item1.data.verts[v + 2] : Math.Min(bmin[1], tileAndPoly.Item1.data.verts[v + 2]);
bmax[1] = j == 0 ? tileAndPoly.Item1.data.verts[v + 2] : Math.Max(bmax[1], tileAndPoly.Item1.data.verts[v + 2]);
}
Assert.That(point.result.getRandomPt()[0] >= bmin[0], Is.True);
Assert.That(point.result.getRandomPt()[0] <= bmax[0], Is.True);
Assert.That(point.result.getRandomPt()[2] >= bmin[1], Is.True);
@ -51,11 +54,13 @@ public class RandomPointTest : AbstractDetourTest {
}
[Test]
public void testRandomAroundCircle() {
public void testRandomAroundCircle()
{
NavMeshQuery.FRand f = new NavMeshQuery.FRand(1);
QueryFilter filter = new DefaultQueryFilter();
FindRandomPointResult point = query.findRandomPoint(filter, f).result;
for (int i = 0; i < 1000; i++) {
for (int i = 0; i < 1000; i++)
{
Result<FindRandomPointResult> result = query.findRandomPointAroundCircle(point.getRandomRef(), point.getRandomPt(),
5f, filter, f);
Assert.That(result.failed(), Is.False);
@ -63,13 +68,15 @@ public class RandomPointTest : AbstractDetourTest {
Tuple<MeshTile, Poly> tileAndPoly = navmesh.getTileAndPolyByRef(point.getRandomRef()).result;
float[] bmin = new float[2];
float[] bmax = new float[2];
for (int j = 0; j < tileAndPoly.Item2.vertCount; j++) {
for (int j = 0; j < tileAndPoly.Item2.vertCount; j++)
{
int v = tileAndPoly.Item2.verts[j] * 3;
bmin[0] = j == 0 ? tileAndPoly.Item1.data.verts[v] : Math.Min(bmin[0], tileAndPoly.Item1.data.verts[v]);
bmax[0] = j == 0 ? tileAndPoly.Item1.data.verts[v] : Math.Max(bmax[0], tileAndPoly.Item1.data.verts[v]);
bmin[1] = j == 0 ? tileAndPoly.Item1.data.verts[v + 2] : Math.Min(bmin[1], tileAndPoly.Item1.data.verts[v + 2]);
bmax[1] = j == 0 ? tileAndPoly.Item1.data.verts[v + 2] : Math.Max(bmax[1], tileAndPoly.Item1.data.verts[v + 2]);
}
Assert.That(point.getRandomPt()[0] >= bmin[0], Is.True);
Assert.That(point.getRandomPt()[0] <= bmax[0], Is.True);
Assert.That(point.getRandomPt()[2] >= bmin[1], Is.True);
@ -78,12 +85,14 @@ public class RandomPointTest : AbstractDetourTest {
}
[Test]
public void testRandomWithinCircle() {
public void testRandomWithinCircle()
{
NavMeshQuery.FRand f = new NavMeshQuery.FRand(1);
QueryFilter filter = new DefaultQueryFilter();
FindRandomPointResult point = query.findRandomPoint(filter, f).result;
float radius = 5f;
for (int i = 0; i < 1000; i++) {
for (int i = 0; i < 1000; i++)
{
Result<FindRandomPointResult> result = query.findRandomPointWithinCircle(point.getRandomRef(), point.getRandomPt(),
radius, filter, f);
Assert.That(result.failed(), Is.False);
@ -94,30 +103,37 @@ public class RandomPointTest : AbstractDetourTest {
}
[Test]
public void testPerformance() {
public void testPerformance()
{
NavMeshQuery.FRand f = new NavMeshQuery.FRand(1);
QueryFilter filter = new DefaultQueryFilter();
FindRandomPointResult point = query.findRandomPoint(filter, f).result;
float radius = 5f;
// jvm warmup
for (int i = 0; i < 1000; i++) {
for (int i = 0; i < 1000; i++)
{
query.findRandomPointAroundCircle(point.getRandomRef(), point.getRandomPt(), radius, filter, f);
}
for (int i = 0; i < 1000; i++) {
for (int i = 0; i < 1000; i++)
{
query.findRandomPointWithinCircle(point.getRandomRef(), point.getRandomPt(), radius, filter, f);
}
long t1 = Stopwatch.GetTimestamp();
for (int i = 0; i < 10000; i++) {
for (int i = 0; i < 10000; i++)
{
query.findRandomPointAroundCircle(point.getRandomRef(), point.getRandomPt(), radius, filter, f);
}
long t2 = Stopwatch.GetTimestamp();
for (int i = 0; i < 10000; i++) {
for (int i = 0; i < 10000; i++)
{
query.findRandomPointWithinCircle(point.getRandomRef(), point.getRandomPt(), radius, filter, f);
}
long t3 = Stopwatch.GetTimestamp();
Console.WriteLine("Random point around circle: " + (t2 - t1) / 1000000 + "ms");
Console.WriteLine("Random point within circle: " + (t3 - t2) / 1000000 + "ms");
}
}

View File

@ -22,8 +22,8 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Detour.Test;
public class RecastTestMeshBuilder {
public class RecastTestMeshBuilder
{
private readonly MeshData meshData;
private const float m_cellSize = 0.3f;
private const float m_cellHeight = 0.2f;
@ -50,7 +50,8 @@ public class RecastTestMeshBuilder {
public RecastTestMeshBuilder(InputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize,
float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope,
int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly,
float m_detailSampleDist, float m_detailSampleMaxError) {
float m_detailSampleDist, float m_detailSampleMaxError)
{
RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius,
m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
@ -58,9 +59,11 @@ public class RecastTestMeshBuilder {
RecastBuilder rcBuilder = new RecastBuilder();
RecastBuilderResult rcResult = rcBuilder.build(m_geom, bcfg);
PolyMesh m_pmesh = rcResult.getMesh();
for (int i = 0; i < m_pmesh.npolys; ++i) {
for (int i = 0; i < m_pmesh.npolys; ++i)
{
m_pmesh.flags[i] = 1;
}
PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = m_pmesh.verts;
@ -105,7 +108,8 @@ public class RecastTestMeshBuilder {
meshData = NavMeshBuilder.createNavMeshData(option);
}
public MeshData getMeshData() {
public MeshData getMeshData()
{
return meshData;
}
}

View File

@ -20,8 +20,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Test;
public class SampleAreaModifications {
public class SampleAreaModifications
{
public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
@ -32,14 +32,19 @@ public class SampleAreaModifications {
public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR,
SAMPLE_POLYAREA_TYPE_DOOR);
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP,
SAMPLE_POLYAREA_TYPE_JUMP);

View File

@ -21,32 +21,43 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Detour.Test;
public class TestDetourBuilder : DetourBuilder {
public class TestDetourBuilder : DetourBuilder
{
public MeshData build(InputGeomProvider geom, RecastBuilderConfig rcConfig, float agentHeight, float agentRadius,
float agentMaxClimb, int x, int y, bool applyRecastDemoFlags) {
float agentMaxClimb, int x, int y, bool applyRecastDemoFlags)
{
RecastBuilder rcBuilder = new RecastBuilder();
RecastBuilderResult rcResult = rcBuilder.build(geom, rcConfig);
PolyMesh pmesh = rcResult.getMesh();
if (applyRecastDemoFlags) {
if (applyRecastDemoFlags)
{
// Update poly flags from areas.
for (int i = 0; i < pmesh.npolys; ++i) {
for (int i = 0; i < pmesh.npolys; ++i)
{
if (pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND
|| pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GRASS
|| pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD) {
|| pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD)
{
pmesh.flags[i] = SampleAreaModifications.SAMPLE_POLYFLAGS_WALK;
} else if (pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER) {
}
else if (pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER)
{
pmesh.flags[i] = SampleAreaModifications.SAMPLE_POLYFLAGS_SWIM;
} else if (pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_DOOR) {
}
else if (pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_DOOR)
{
pmesh.flags[i] = SampleAreaModifications.SAMPLE_POLYFLAGS_WALK
| SampleAreaModifications.SAMPLE_POLYFLAGS_DOOR;
}
if (pmesh.areas[i] > 0) {
if (pmesh.areas[i] > 0)
{
pmesh.areas[i]--;
}
}
}
PolyMeshDetail dmesh = rcResult.getMeshDetail();
NavMeshDataCreateParams option = getNavMeshCreateParams(rcConfig.cfg, pmesh, dmesh, agentHeight, agentRadius,
agentMaxClimb);
@ -54,7 +65,8 @@ public class TestDetourBuilder : DetourBuilder {
}
public NavMeshDataCreateParams getNavMeshCreateParams(RecastConfig rcConfig, PolyMesh pmesh, PolyMeshDetail dmesh,
float agentHeight, float agentRadius, float agentMaxClimb) {
float agentHeight, float agentRadius, float agentMaxClimb)
{
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = pmesh.verts;
option.vertCount = pmesh.nverts;
@ -63,13 +75,15 @@ public class TestDetourBuilder : DetourBuilder {
option.polyFlags = pmesh.flags;
option.polyCount = pmesh.npolys;
option.nvp = pmesh.nvp;
if (dmesh != null) {
if (dmesh != null)
{
option.detailMeshes = dmesh.meshes;
option.detailVerts = dmesh.verts;
option.detailVertsCount = dmesh.nverts;
option.detailTris = dmesh.tris;
option.detailTriCount = dmesh.ntris;
}
option.walkableHeight = agentHeight;
option.walkableRadius = agentRadius;
option.walkableClimb = agentMaxClimb;
@ -86,6 +100,5 @@ public class TestDetourBuilder : DetourBuilder {
* option.offMeshConCount = m_geom->getOffMeshConnectionCount();
*/
return option;
}
}

View File

@ -20,13 +20,12 @@ using System.Collections.Generic;
using DotRecast.Core;
using DotRecast.Recast;
using DotRecast.Recast.Geom;
using static DotRecast.Recast.RecastVectors;
namespace DotRecast.Detour.Test;
public class TestTiledNavMeshBuilder {
public class TestTiledNavMeshBuilder
{
private readonly NavMesh navMesh;
private const float m_cellSize = 0.3f;
private const float m_cellHeight = 0.2f;
@ -56,8 +55,8 @@ public class TestTiledNavMeshBuilder {
public TestTiledNavMeshBuilder(InputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize, float m_cellHeight,
float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope, int m_regionMinSize,
int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly, float m_detailSampleDist,
float m_detailSampleMaxError, int m_tileSize) {
float m_detailSampleMaxError, int m_tileSize)
{
// Create empty nav mesh
NavMeshParams navMeshParams = new NavMeshParams();
copy(navMeshParams.orig, m_geom.getMeshBoundsMin());
@ -77,14 +76,19 @@ public class TestTiledNavMeshBuilder {
// Add tiles to nav mesh
foreach (RecastBuilderResult result in rcResult) {
foreach (RecastBuilderResult result in rcResult)
{
PolyMesh pmesh = result.getMesh();
if (pmesh.npolys == 0) {
if (pmesh.npolys == 0)
{
continue;
}
for (int i = 0; i < pmesh.npolys; ++i) {
for (int i = 0; i < pmesh.npolys; ++i)
{
pmesh.flags[i] = 1;
}
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = pmesh.verts;
option.vertCount = pmesh.nverts;
@ -113,8 +117,8 @@ public class TestTiledNavMeshBuilder {
}
}
public NavMesh getNavMesh() {
public NavMesh getNavMesh()
{
return navMesh;
}
}

View File

@ -21,16 +21,23 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class TiledFindPathTest {
public class TiledFindPathTest
{
private static readonly Status[] STATUSES = { Status.SUCCSESS };
private static readonly long[][] RESULTS = {
new[] { 281475015507969L, 281475014459393L, 281475014459392L, 281475006070784L,
private static readonly long[][] RESULTS =
{
new[]
{
281475015507969L, 281475014459393L, 281475014459392L, 281475006070784L,
281475005022208L, 281475003973636L, 281475012362240L, 281475012362241L, 281475012362242L, 281475003973634L,
281475003973635L, 281475003973633L, 281475002925059L, 281475002925057L, 281475002925056L, 281474998730753L,
281474998730754L, 281474994536450L, 281474994536451L, 281474994536452L, 281474994536448L, 281474990342146L,
281474990342145L, 281474991390723L, 281474991390724L, 281474991390725L, 281474987196418L, 281474987196417L,
281474988244996L, 281474988244995L, 281474988244997L, 281474985099266L } };
281474988244996L, 281474988244995L, 281474988244997L, 281474985099266L
}
};
protected static readonly long[] START_REFS = { 281475015507969L };
protected static readonly long[] END_REFS = { 281474985099266L };
protected static readonly float[][] START_POS = { new[] { 39.447338f, 9.998177f, -0.784811f } };
@ -40,19 +47,23 @@ public class TiledFindPathTest {
protected NavMesh navmesh;
[SetUp]
public void setUp() {
public void setUp()
{
navmesh = createNavMesh();
query = new NavMeshQuery(navmesh);
}
protected NavMesh createNavMesh() {
protected NavMesh createNavMesh()
{
return new TestTiledNavMeshBuilder().getNavMesh();
}
[Test]
public void testFindPath() {
public void testFindPath()
{
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < START_REFS.Length; i++) {
for (int i = 0; i < START_REFS.Length; i++)
{
long startRef = START_REFS[i];
long endRef = END_REFS[i];
float[] startPos = START_POS[i];
@ -60,10 +71,10 @@ public class TiledFindPathTest {
Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter);
Assert.That(path.status, Is.EqualTo(STATUSES[i]));
Assert.That(path.result.Count, Is.EqualTo(RESULTS[i].Length));
for (int j = 0; j < RESULTS[i].Length; j++) {
for (int j = 0; j < RESULTS[i].Length; j++)
{
Assert.That(RESULTS[i][j], Is.EqualTo(path.result[j]));
}
}
}
}

View File

@ -21,14 +21,13 @@ freely, subject to the following restrictions:
using DotRecast.Core;
using DotRecast.Detour.TileCache.Io.Compress;
using DotRecast.Recast.Geom;
using static DotRecast.Detour.DetourCommon;
using static DotRecast.Recast.RecastVectors;
namespace DotRecast.Detour.TileCache.Test;
public class AbstractTileCacheTest {
public class AbstractTileCacheTest
{
private const int EXPECTED_LAYERS_PER_TILE = 4;
private readonly float m_cellSize = 0.3f;
private readonly float m_cellHeight = 0.2f;
@ -38,15 +37,19 @@ public class AbstractTileCacheTest {
private readonly float m_edgeMaxError = 1.3f;
private readonly int m_tileSize = 48;
protected class TestTileCacheMeshProcess : TileCacheMeshProcess {
public void process(NavMeshDataCreateParams option) {
for (int i = 0; i < option.polyCount; ++i) {
protected class TestTileCacheMeshProcess : TileCacheMeshProcess
{
public void process(NavMeshDataCreateParams option)
{
for (int i = 0; i < option.polyCount; ++i)
{
option.polyFlags[i] = 1;
}
}
}
public TileCache getTileCache(InputGeomProvider geom, ByteOrder order, bool cCompatibility) {
public TileCache getTileCache(InputGeomProvider geom, ByteOrder order, bool cCompatibility)
{
TileCacheParams option = new TileCacheParams();
int[] twh = Recast.Recast.calcTileCount(geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), m_cellSize, m_tileSize, m_tileSize);
option.ch = m_cellHeight;
@ -71,5 +74,4 @@ public class AbstractTileCacheTest {
TileCacheCompressorFactory.get(cCompatibility), new TestTileCacheMeshProcess());
return tc;
}
}

View File

@ -26,13 +26,13 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test.Io;
public class TileCacheReaderTest {
public class TileCacheReaderTest
{
private readonly TileCacheReader reader = new TileCacheReader();
[Test]
public void testNavmesh() {
public void testNavmesh()
{
using var ms = new MemoryStream(Loader.ToBytes("all_tiles_tilecache.bin"));
using var @is = new BinaryReader(ms);
TileCache tc = reader.read(@is, 6, null);
@ -129,7 +129,8 @@ public class TileCacheReaderTest {
}
[Test]
public void testDungeon() {
public void testDungeon()
{
using var ms = new MemoryStream(Loader.ToBytes("dungeon_all_tiles_tilecache.bin"));
using var @is = new BinaryReader(ms);
TileCache tc = reader.read(@is, 6, null);
@ -220,5 +221,4 @@ public class TileCacheReaderTest {
Assert.That(data.verts[6], Is.EqualTo(48.484783f).Within(0.0001f));
Assert.That(data.verts[9], Is.EqualTo(48.484783f).Within(0.0001f));
}
}

View File

@ -28,30 +28,33 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test.Io;
public class TileCacheReaderWriterTest : AbstractTileCacheTest {
public class TileCacheReaderWriterTest : AbstractTileCacheTest
{
private readonly TileCacheReader reader = new TileCacheReader();
private readonly TileCacheWriter writer = new TileCacheWriter();
[Test]
public void testFastLz() {
public void testFastLz()
{
testDungeon(false);
testDungeon(true);
}
[Test]
public void testLZ4() {
public void testLZ4()
{
testDungeon(true);
testDungeon(false);
}
private void testDungeon(bool cCompatibility) {
private void testDungeon(bool cCompatibility)
{
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
List<byte[]> layers = layerBuilder.build(ByteOrder.LITTLE_ENDIAN, cCompatibility, 1);
TileCache tc = getTileCache(geom, ByteOrder.LITTLE_ENDIAN, cCompatibility);
foreach (byte[] layer in layers) {
foreach (byte[] layer in layers)
{
long refs = tc.addTile(layer, 0);
tc.buildNavMeshTile(refs);
}
@ -134,5 +137,4 @@ public class TileCacheReaderWriterTest : AbstractTileCacheTest {
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 3));
}
}

View File

@ -22,35 +22,43 @@ using DotRecast.Recast;
namespace DotRecast.Detour.TileCache.Test;
public class SampleAreaModifications {
public class SampleAreaModifications
{
public static int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
/// Value for the kind of ceil "ground"
public static int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
/// Value for the kind of ceil "water"
public static int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
/// Value for the kind of ceil "road"
public static int SAMPLE_POLYAREA_TYPE_ROAD = 0x3;
/// Value for the kind of ceil "grass"
public static int SAMPLE_POLYAREA_TYPE_GRASS = 0x4;
/// Flag for door area. Can be combined with area types and jump flag.
public static int SAMPLE_POLYAREA_FLAG_DOOR = 0x08;
/// Flag for jump area. Can be combined with area types and door flag.
public static int SAMPLE_POLYAREA_FLAG_JUMP = 0x10;
public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_FLAG_DOOR,
SAMPLE_POLYAREA_FLAG_DOOR);
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_FLAG_JUMP,
SAMPLE_POLYAREA_FLAG_JUMP);
}

View File

@ -26,19 +26,22 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test;
public class TempObstaclesTest : AbstractTileCacheTest {
public class TempObstaclesTest : AbstractTileCacheTest
{
[Test]
public void testDungeon() {
public void testDungeon()
{
bool cCompatibility = true;
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
List<byte[]> layers = layerBuilder.build(ByteOrder.LITTLE_ENDIAN, cCompatibility, 1);
TileCache tc = getTileCache(geom, ByteOrder.LITTLE_ENDIAN, cCompatibility);
foreach (byte[] data in layers) {
foreach (byte[] data in layers)
{
long refs = tc.addTile(data, 0);
tc.buildNavMeshTile(refs);
}
List<MeshTile> tiles = tc.getNavMesh().getTilesAt(1, 4);
MeshTile tile = tiles[0];
Assert.That(tile.data.header.vertCount, Is.EqualTo(16));
@ -60,16 +63,19 @@ public class TempObstaclesTest : AbstractTileCacheTest {
}
[Test]
public void testDungeonBox() {
public void testDungeonBox()
{
bool cCompatibility = true;
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
List<byte[]> layers = layerBuilder.build(ByteOrder.LITTLE_ENDIAN, cCompatibility, 1);
TileCache tc = getTileCache(geom, ByteOrder.LITTLE_ENDIAN, cCompatibility);
foreach (byte[] data in layers) {
foreach (byte[] data in layers)
{
long refs = tc.addTile(data, 0);
tc.buildNavMeshTile(refs);
}
List<MeshTile> tiles = tc.getNavMesh().getTilesAt(1, 4);
MeshTile tile = tiles[0];
Assert.That(tile.data.header.vertCount, Is.EqualTo(16));

View File

@ -22,13 +22,12 @@ using System.Collections.Generic;
using DotRecast.Core;
using DotRecast.Recast;
using DotRecast.Recast.Geom;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.TileCache.Test;
public class TestTileLayerBuilder : AbstractTileLayersBuilder {
public class TestTileLayerBuilder : AbstractTileLayersBuilder
{
private const float m_cellSize = 0.3f;
private const float m_cellHeight = 0.2f;
private const float m_agentHeight = 2.0f;
@ -50,7 +49,8 @@ public class TestTileLayerBuilder : AbstractTileLayersBuilder {
private readonly int tw;
private readonly int th;
public TestTileLayerBuilder(InputGeomProvider geom) {
public TestTileLayerBuilder(InputGeomProvider geom)
{
this.geom = geom;
rcConfig = new RecastConfig(true, m_tileSize, m_tileSize, RecastConfig.calcBorder(m_agentRadius, m_cellSize),
PartitionType.WATERSHED, m_cellSize, m_cellHeight, m_agentMaxSlope, true, true, true, m_agentHeight,
@ -63,24 +63,30 @@ public class TestTileLayerBuilder : AbstractTileLayersBuilder {
th = twh[1];
}
public List<byte[]> build(ByteOrder order, bool cCompatibility, int threads) {
public List<byte[]> build(ByteOrder order, bool cCompatibility, int threads)
{
return build(order, cCompatibility, threads, tw, th);
}
public int getTw() {
public int getTw()
{
return tw;
}
public int getTh() {
public int getTh()
{
return th;
}
protected override List<byte[]> build(int tx, int ty, ByteOrder order, bool cCompatibility) {
protected override List<byte[]> build(int tx, int ty, ByteOrder order, bool cCompatibility)
{
HeightfieldLayerSet lset = getHeightfieldSet(tx, ty);
List<byte[]> result = new();
if (lset != null) {
if (lset != null)
{
TileCacheBuilder builder = new TileCacheBuilder();
for (int i = 0; i < lset.layers.Length; ++i) {
for (int i = 0; i < lset.layers.Length; ++i)
{
HeightfieldLayerSet.HeightfieldLayer layer = lset.layers[i];
// Store header
@ -107,10 +113,12 @@ public class TestTileLayerBuilder : AbstractTileLayersBuilder {
result.Add(builder.compressTileCacheLayer(header, layer.heights, layer.areas, layer.cons, order, cCompatibility));
}
}
return result;
}
protected HeightfieldLayerSet getHeightfieldSet(int tx, int ty) {
protected HeightfieldLayerSet getHeightfieldSet(int tx, int ty)
{
RecastBuilder rcBuilder = new RecastBuilder();
float[] bmin = geom.getMeshBoundsMin();
float[] bmax = geom.getMeshBoundsMax();

View File

@ -26,14 +26,15 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test;
public class TileCacheFindPathTest : AbstractTileCacheTest {
public class TileCacheFindPathTest : AbstractTileCacheTest
{
private readonly float[] start = { 39.44734f, 9.998177f, -0.784811f };
private readonly float[] end = { 19.292645f, 11.611748f, -57.750366f };
private readonly NavMesh navmesh;
private readonly NavMeshQuery query;
public TileCacheFindPathTest() {
public TileCacheFindPathTest()
{
using var msis = new MemoryStream(Loader.ToBytes("dungeon_all_tiles_tilecache.bin"));
using var @is = new BinaryReader(msis);
TileCache tcC = new TileCacheReader().read(@is, 6, new TestTileCacheMeshProcess());
@ -42,7 +43,8 @@ public class TileCacheFindPathTest : AbstractTileCacheTest {
}
[Test]
public void testFindPath() {
public void testFindPath()
{
QueryFilter filter = new DefaultQueryFilter();
float[] extents = new float[] { 2f, 4f, 2f };
Result<FindNearestPolyResult> findPolyStart = query.findNearestPoly(start, extents, filter);
@ -58,5 +60,4 @@ public class TileCacheFindPathTest : AbstractTileCacheTest {
options);
Assert.That(pathStr.result.Count, Is.EqualTo(8));
}
}

View File

@ -26,50 +26,64 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test;
public class TileCacheNavigationTest : AbstractTileCacheTest {
public class TileCacheNavigationTest : AbstractTileCacheTest
{
protected readonly long[] startRefs = { 281475006070787L };
protected readonly long[] endRefs = { 281474986147841L };
protected readonly float[][] startPoss = { new[] { 39.447338f, 9.998177f, -0.784811f } };
protected readonly float[][] endPoss = { new[] { 19.292645f, 11.611748f, -57.750366f } };
private readonly Status[] statuses = { Status.SUCCSESS };
private readonly long[][] results = { new[] { 281475006070787L, 281475006070785L, 281475005022208L, 281475005022209L, 281475003973633L,
private readonly long[][] results =
{
new[]
{
281475006070787L, 281475006070785L, 281475005022208L, 281475005022209L, 281475003973633L,
281475003973634L, 281475003973632L, 281474996633604L, 281474996633605L, 281474996633603L, 281474995585027L,
281474995585029L, 281474995585026L, 281474995585028L, 281474995585024L, 281474991390721L, 281474991390722L,
281474991390725L, 281474991390720L, 281474987196418L, 281474987196417L, 281474988244995L, 281474988245001L,
281474988244997L, 281474988244998L, 281474988245002L, 281474988245000L, 281474988244999L, 281474988244994L,
281474985099264L, 281474985099266L, 281474986147841L } };
281474985099264L, 281474985099266L, 281474986147841L
}
};
protected NavMesh navmesh;
protected NavMeshQuery query;
[SetUp]
public void setUp() {
public void setUp()
{
bool cCompatibility = true;
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
List<byte[]> layers = layerBuilder.build(ByteOrder.LITTLE_ENDIAN, cCompatibility, 1);
TileCache tc = getTileCache(geom, ByteOrder.LITTLE_ENDIAN, cCompatibility);
foreach (byte[] data in layers) {
foreach (byte[] data in layers)
{
tc.addTile(data, 0);
}
for (int y = 0; y < layerBuilder.getTh(); ++y) {
for (int x = 0; x < layerBuilder.getTw(); ++x) {
foreach (long refs in tc.getTilesAt(x, y)) {
for (int y = 0; y < layerBuilder.getTh(); ++y)
{
for (int x = 0; x < layerBuilder.getTw(); ++x)
{
foreach (long refs in tc.getTilesAt(x, y))
{
tc.buildNavMeshTile(refs);
}
}
}
navmesh = tc.getNavMesh();
query = new NavMeshQuery(navmesh);
}
[Test]
public void testFindPathWithDefaultHeuristic() {
public void testFindPathWithDefaultHeuristic()
{
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
for (int i = 0; i < startRefs.Length; i++)
{
long startRef = startRefs[i];
long endRef = endRefs[i];
float[] startPos = startPoss[i];
@ -77,16 +91,19 @@ public class TileCacheNavigationTest : AbstractTileCacheTest {
Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter);
Assert.That(path.status, Is.EqualTo(statuses[i]));
Assert.That(path.result.Count, Is.EqualTo(results[i].Length));
for (int j = 0; j < results[i].Length; j++) {
for (int j = 0; j < results[i].Length; j++)
{
Assert.That(path.result[j], Is.EqualTo(results[i][j])); // TODO : 확인 필요
}
}
}
[Test]
public void testFindPathWithNoHeuristic() {
public void testFindPathWithNoHeuristic()
{
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
for (int i = 0; i < startRefs.Length; i++)
{
long startRef = startRefs[i];
long endRef = endRefs[i];
float[] startPos = startPoss[i];
@ -95,10 +112,10 @@ public class TileCacheNavigationTest : AbstractTileCacheTest {
0, 0);
Assert.That(path.status, Is.EqualTo(statuses[i]));
Assert.That(path.result.Count, Is.EqualTo(results[i].Length));
for (int j = 0; j < results[i].Length; j++) {
for (int j = 0; j < results[i].Length; j++)
{
Assert.That(path.result[j], Is.EqualTo(results[i][j])); // TODO : 확인 필요
}
}
}
}

View File

@ -28,10 +28,11 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test;
public class TileCacheTest : AbstractTileCacheTest {
public class TileCacheTest : AbstractTileCacheTest
{
[Test]
public void testFastLz() {
public void testFastLz()
{
testDungeon(ByteOrder.LITTLE_ENDIAN, false);
testDungeon(ByteOrder.LITTLE_ENDIAN, true);
testDungeon(ByteOrder.BIG_ENDIAN, false);
@ -43,7 +44,8 @@ public class TileCacheTest : AbstractTileCacheTest {
}
[Test]
public void testLZ4() {
public void testLZ4()
{
testDungeon(ByteOrder.LITTLE_ENDIAN, false);
testDungeon(ByteOrder.LITTLE_ENDIAN, true);
testDungeon(ByteOrder.BIG_ENDIAN, false);
@ -54,7 +56,8 @@ public class TileCacheTest : AbstractTileCacheTest {
test(ByteOrder.BIG_ENDIAN, true);
}
private void testDungeon(ByteOrder order, bool cCompatibility) {
private void testDungeon(ByteOrder order, bool cCompatibility)
{
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
TileCache tc = getTileCache(geom, order, cCompatibility);
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
@ -62,13 +65,15 @@ public class TileCacheTest : AbstractTileCacheTest {
int cacheLayerCount = 0;
int cacheCompressedSize = 0;
int cacheRawSize = 0;
foreach (byte[] layer in layers) {
foreach (byte[] layer in layers)
{
long refs = tc.addTile(layer, 0);
tc.buildNavMeshTile(refs);
cacheLayerCount++;
cacheCompressedSize += layer.Length;
cacheRawSize += 4 * 48 * 48 + 56; // FIXME
}
Console.WriteLine("Compressor: " + tc.getCompressor().GetType().Name + " C Compatibility: " + cCompatibility
+ " Layers: " + cacheLayerCount + " Raw Size: " + cacheRawSize + " Compressed: " + cacheCompressedSize);
Assert.That(tc.getNavMesh().getMaxTiles(), Is.EqualTo(256));
@ -147,7 +152,8 @@ public class TileCacheTest : AbstractTileCacheTest {
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 3));
}
private void test(ByteOrder order, bool cCompatibility) {
private void test(ByteOrder order, bool cCompatibility)
{
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("nav_test.obj"));
TileCache tc = getTileCache(geom, order, cCompatibility);
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
@ -155,46 +161,56 @@ public class TileCacheTest : AbstractTileCacheTest {
int cacheLayerCount = 0;
int cacheCompressedSize = 0;
int cacheRawSize = 0;
foreach (byte[] layer in layers) {
foreach (byte[] layer in layers)
{
long refs = tc.addTile(layer, 0);
tc.buildNavMeshTile(refs);
cacheLayerCount++;
cacheCompressedSize += layer.Length;
cacheRawSize += 4 * 48 * 48 + 56;
}
Console.WriteLine("Compressor: " + tc.getCompressor().GetType().Name + " C Compatibility: " + cCompatibility
+ " Layers: " + cacheLayerCount + " Raw Size: " + cacheRawSize + " Compressed: " + cacheCompressedSize);
}
[Test]
public void testPerformance() {
public void testPerformance()
{
int threads = 4;
ByteOrder order = ByteOrder.LITTLE_ENDIAN;
bool cCompatibility = false;
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
for (int i = 0; i < 4; i++) {
for (int i = 0; i < 4; i++)
{
layerBuilder.build(order, cCompatibility, 1);
layerBuilder.build(order, cCompatibility, threads);
}
long t1 = Stopwatch.GetTimestamp();
List<byte[]> layers = null;
for (int i = 0; i < 8; i++) {
for (int i = 0; i < 8; i++)
{
layers = layerBuilder.build(order, cCompatibility, 1);
}
long t2 = Stopwatch.GetTimestamp();
for (int i = 0; i < 8; i++) {
for (int i = 0; i < 8; i++)
{
layers = layerBuilder.build(order, cCompatibility, threads);
}
long t3 = Stopwatch.GetTimestamp();
Console.WriteLine(" Time ST : " + (t2 - t1) / 1000000);
Console.WriteLine(" Time MT : " + (t3 - t2) / 1000000);
TileCache tc = getTileCache(geom, order, cCompatibility);
foreach (byte[] layer in layers) {
foreach (byte[] layer in layers)
{
long refs = tc.addTile(layer, 0);
tc.buildNavMeshTile(refs);
}
Assert.That(tc.getNavMesh().getMaxTiles(), Is.EqualTo(256));
Assert.That(tc.getNavMesh().getParams().maxPolys, Is.EqualTo(16384));
Assert.That(tc.getNavMesh().getParams().tileWidth, Is.EqualTo(14.4f).Within(0.001f));
@ -269,5 +285,4 @@ public class TileCacheTest : AbstractTileCacheTest {
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 3));
}
}