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,10 +2,8 @@
namespace DotRecast.Core namespace DotRecast.Core
{ {
public static class ArrayUtils
{
public static class ArrayUtils
{
public static T[] CopyOf<T>(T[] source, int startIdx, int length) public static T[] CopyOf<T>(T[] source, int startIdx, int length)
{ {
var deatArr = new T[length]; var deatArr = new T[length];
@ -40,5 +38,5 @@ public static class ArrayUtils
return temp; return temp;
} }
} }
} }

View File

@ -2,10 +2,8 @@
namespace DotRecast.Core namespace DotRecast.Core
{ {
public class AtomicBoolean
{
public class AtomicBoolean
{
private volatile int _location; private volatile int _location;
public bool set(bool v) public bool set(bool v)
@ -17,5 +15,5 @@ public class AtomicBoolean
{ {
return 0 != _location; return 0 != _location;
} }
} }
} }

View File

@ -2,10 +2,8 @@
namespace DotRecast.Core namespace DotRecast.Core
{ {
public class AtomicFloat
{
public class AtomicFloat
{
private volatile float _location; private volatile float _location;
public AtomicFloat(float location) public AtomicFloat(float location)
@ -27,5 +25,5 @@ public class AtomicFloat
{ {
return Interlocked.CompareExchange(ref _location, value, comparand); return Interlocked.CompareExchange(ref _location, value, comparand);
} }
} }
} }

View File

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

View File

@ -2,10 +2,8 @@
namespace DotRecast.Core namespace DotRecast.Core
{ {
public class AtomicLong
{
public class AtomicLong
{
private long _location; private long _location;
public AtomicLong() : this(0) public AtomicLong() : this(0)
@ -51,5 +49,5 @@ public class AtomicLong
{ {
return Interlocked.Add(ref _location, value); return Interlocked.Add(ref _location, value);
} }
} }
} }

View File

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

View File

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

View File

@ -3,10 +3,8 @@ using System.Collections.Generic;
namespace DotRecast.Core namespace DotRecast.Core
{ {
public static class CollectionExtensions
{
public static class CollectionExtensions
{
public static void forEach<T>(this IEnumerable<T> collection, Action<T> action) public static void forEach<T>(this IEnumerable<T> collection, Action<T> action)
{ {
foreach (var item in collection) foreach (var item in collection)
@ -28,5 +26,5 @@ public static class CollectionExtensions
list[n] = value; list[n] = value;
} }
} }
} }
} }

View File

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

View File

@ -22,17 +22,18 @@ using System;
namespace DotRecast.Core namespace DotRecast.Core
{ {
public class DemoMath
{
public class DemoMath { public static float vDistSqr(float[] v1, float[] v2, int i)
public static float vDistSqr(float[] v1, float[] v2, int i) { {
float dx = v2[i] - v1[0]; float dx = v2[i] - v1[0];
float dy = v2[i + 1] - v1[1]; float dy = v2[i + 1] - v1[1];
float dz = v2[i + 2] - v1[2]; float dz = v2[i + 2] - v1[2];
return dx * dx + dy * dy + dz * dz; 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]; float[] dest = new float[3];
dest[0] = v1[1] * v2[2] - v1[2] * v2[1]; dest[0] = v1[1] * v2[2] - v1[2] * v2[1];
dest[1] = v1[2] * v2[0] - v1[0] * v2[2]; dest[1] = v1[2] * v2[0] - v1[0] * v2[2];
@ -40,44 +41,53 @@ public class DemoMath {
return dest; 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]; 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; return f * f;
} }
public static float getPathLen(float[] path, int npath) { public static float getPathLen(float[] path, int npath)
{
float totd = 0; 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)); totd += (float)Math.Sqrt(vDistSqr(path, i * 3, (i + 1) * 3));
} }
return totd; 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 dx = v[i] - v[j];
float dy = v[i + 1] - v[j + 1]; float dy = v[i + 1] - v[j + 1];
float dz = v[i + 2] - v[j + 2]; float dz = v[i + 2] - v[j + 2];
return dx * dx + dy * dy + dz * dz; 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; 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); 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); 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; return u * g + (1f - u) * f;
} }
} }
} }

View File

@ -5,8 +5,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Collections.Immutable" Version="7.0.0" /> <PackageReference Include="System.Collections.Immutable" Version="7.0.0"/>
<PackageReference Include="System.Text.Json" Version="7.0.2" /> <PackageReference Include="System.Text.Json" Version="7.0.2"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -2,10 +2,8 @@
namespace DotRecast.Core namespace DotRecast.Core
{ {
public static class Loader
{
public static class Loader
{
public static byte[] ToBytes(string filename) public static byte[] ToBytes(string filename)
{ {
var filepath = ToRPath(filename); var filepath = ToRPath(filename);
@ -31,5 +29,5 @@ public static class Loader
return filename; return filename;
} }
} }
} }

View File

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

View File

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

View File

@ -22,33 +22,44 @@ using System;
namespace DotRecast.Detour.Crowd 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;
/// Configuration parameters for a crowd agent. /// < Agent height. [Limit: > 0]
/// @ingroup crowd public float maxAcceleration;
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]
/// < 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] /// Defines how close a collision element must be before it is considered for steering behaviors. [Limits: > 0]
public float collisionQueryRange; 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] /// How aggresive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0]
public float separationWeight; public float separationWeight;
/// Crowd agent update flags. /// Crowd agent update flags.
public const int DT_CROWD_ANTICIPATE_TURNS = 1; public const int DT_CROWD_ANTICIPATE_TURNS = 1;
public const int DT_CROWD_OBSTACLE_AVOIDANCE = 2; public const int DT_CROWD_OBSTACLE_AVOIDANCE = 2;
public const int DT_CROWD_SEPARATION = 4; public const int DT_CROWD_SEPARATION = 4;
public const int DT_CROWD_OPTIMIZE_VIS = 8; /// < Use #dtPathCorridor::optimizePathVisibility() to optimize public const int DT_CROWD_OPTIMIZE_VIS = 8;
/// the agent path.
public const int DT_CROWD_OPTIMIZE_TOPO = 16; /// < Use dtPathCorridor::optimizePathTopology() to optimize
/// the agent path.
/// < 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) /// Flags that impact steering behavior. (See: #UpdateFlags)
public int updateFlags; public int updateFlags;
@ -61,5 +72,5 @@ public class CrowdAgentParams {
/// User defined data attached to the agent. /// User defined data attached to the agent.
public object userData; public object userData;
} }
} }

View File

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

View File

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

View File

@ -6,7 +6,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" /> <ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,21 +20,21 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders namespace DotRecast.Detour.Dynamic.Colliders
{ {
public abstract class AbstractCollider : Collider
{
public abstract class AbstractCollider : Collider {
protected readonly int area; protected readonly int area;
protected readonly float flagMergeThreshold; protected readonly float flagMergeThreshold;
protected readonly float[] _bounds; protected readonly float[] _bounds;
public AbstractCollider(int area, float flagMergeThreshold, float[] bounds) { public AbstractCollider(int area, float flagMergeThreshold, float[] bounds)
{
this.area = area; this.area = area;
this.flagMergeThreshold = flagMergeThreshold; this.flagMergeThreshold = flagMergeThreshold;
this._bounds = bounds; this._bounds = bounds;
} }
public float[] bounds() { public float[] bounds()
{
return _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 namespace DotRecast.Detour.Dynamic.Colliders
{ {
public class BoxCollider : AbstractCollider
{
public class BoxCollider : AbstractCollider {
private readonly float[] center; private readonly float[] center;
private readonly float[][] halfEdges; private readonly float[][] halfEdges;
public BoxCollider(float[] center, float[][] halfEdges, int area, float flagMergeThreshold) : public BoxCollider(float[] center, float[][] halfEdges, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, bounds(center, halfEdges)) base(area, flagMergeThreshold, bounds(center, halfEdges))
{ {
@ -34,10 +33,15 @@ public class BoxCollider : AbstractCollider {
this.halfEdges = halfEdges; this.halfEdges = halfEdges;
} }
private static float[] bounds(float[] center, float[][] 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 }; float[] bounds = new float[]
for (int i = 0; i < 8; ++i) { {
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 s0 = (i & 1) != 0 ? 1f : -1f;
float s1 = (i & 2) != 0 ? 1f : -1f; float s1 = (i & 2) != 0 ? 1f : -1f;
float s2 = (i & 4) != 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[4] = Math.Max(bounds[4], vy);
bounds[5] = Math.Max(bounds[5], vz); bounds[5] = Math.Max(bounds[5], vz);
} }
return bounds; 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), {
RecastFilledVolumeRasterization.rasterizeBox(hf, center, halfEdges, area, (int)Math.Floor(flagMergeThreshold / hf.ch),
telemetry); 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] }; float[][] halfEdges = new float[][] { new float[3], new float[] { up[0], up[1], up[2] }, new float[3] };
RecastVectors.normalize(halfEdges[1]); RecastVectors.normalize(halfEdges[1]);
RecastVectors.cross(halfEdges[0], up, forward); RecastVectors.cross(halfEdges[0], up, forward);
@ -77,7 +84,5 @@ public class BoxCollider : AbstractCollider {
halfEdges[2][2] *= extent[2]; halfEdges[2][2] *= extent[2];
return halfEdges; return halfEdges;
} }
}
}
} }

View File

@ -21,10 +21,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders namespace DotRecast.Detour.Dynamic.Colliders
{ {
public class CapsuleCollider : AbstractCollider
{
public class CapsuleCollider : AbstractCollider {
private readonly float[] start; private readonly float[] start;
private readonly float[] end; private readonly float[] end;
private readonly float radius; private readonly float radius;
@ -37,17 +35,20 @@ public class CapsuleCollider : AbstractCollider {
this.radius = radius; 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), {
RecastFilledVolumeRasterization.rasterizeCapsule(hf, start, end, radius, area, (int)Math.Floor(flagMergeThreshold / hf.ch),
telemetry); telemetry);
} }
private static float[] bounds(float[] start, float[] end, float 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, {
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.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 namespace DotRecast.Detour.Dynamic.Colliders
{ {
public interface Collider
{
public interface Collider {
float[] bounds(); float[] bounds();
void rasterize(Heightfield hf, Telemetry telemetry); void rasterize(Heightfield hf, Telemetry telemetry);
} }
} }

View File

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

View File

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

View File

@ -21,32 +21,34 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders namespace DotRecast.Detour.Dynamic.Colliders
{ {
public class CylinderCollider : AbstractCollider
{
public class CylinderCollider : AbstractCollider {
private readonly float[] start; private readonly float[] start;
private readonly float[] end; private readonly float[] end;
private readonly float radius; private readonly float radius;
public CylinderCollider(float[] start, float[] end, float radius, int area, float flagMergeThreshold) : 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.start = start;
this.end = end; this.end = end;
this.radius = radius; 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), {
RecastFilledVolumeRasterization.rasterizeCylinder(hf, start, end, radius, area, (int)Math.Floor(flagMergeThreshold / hf.ch),
telemetry); telemetry);
} }
private static float[] bounds(float[] start, float[] end, float 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, {
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.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 namespace DotRecast.Detour.Dynamic.Colliders
{ {
public class SphereCollider : AbstractCollider
{
public class SphereCollider : AbstractCollider {
private readonly float[] center; private readonly float[] center;
private readonly float radius; private readonly float radius;
public SphereCollider(float[] center, float radius, int area, float flagMergeThreshold) : 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.center = center;
this.radius = radius; 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), {
RecastFilledVolumeRasterization.rasterizeSphere(hf, center, radius, area, (int)Math.Floor(flagMergeThreshold / hf.ch),
telemetry); telemetry);
} }
private static float[] bounds(float[] center, float 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 }; 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 namespace DotRecast.Detour.Dynamic.Colliders
{ {
public class TrimeshCollider : AbstractCollider
{
public class TrimeshCollider : AbstractCollider {
private readonly float[] vertices; private readonly float[] vertices;
private readonly int[] triangles; private readonly int[] triangles;
public TrimeshCollider(float[] vertices, int[] triangles, int area, float flagMergeThreshold) : public TrimeshCollider(float[] vertices, int[] triangles, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, computeBounds(vertices)) { base(area, flagMergeThreshold, computeBounds(vertices))
{
this.vertices = vertices; this.vertices = vertices;
this.triangles = triangles; this.triangles = triangles;
} }
public TrimeshCollider(float[] vertices, int[] triangles, float[] bounds, int area, float flagMergeThreshold) : public TrimeshCollider(float[] vertices, int[] triangles, float[] bounds, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, bounds) { base(area, flagMergeThreshold, bounds)
{
this.vertices = vertices; this.vertices = vertices;
this.triangles = triangles; 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] }; 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[0] = Math.Min(bounds[0], vertices[i]);
bounds[1] = Math.Min(bounds[1], vertices[i + 1]); bounds[1] = Math.Min(bounds[1], vertices[i + 1]);
bounds[2] = Math.Min(bounds[2], vertices[i + 2]); 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[4] = Math.Max(bounds[4], vertices[i + 1]);
bounds[5] = Math.Max(bounds[5], vertices[i + 2]); bounds[5] = Math.Max(bounds[5], vertices[i + 2]);
} }
return bounds; return bounds;
} }
public void rasterize(Heightfield hf, Telemetry telemetry) { public void rasterize(Heightfield hf, Telemetry telemetry)
for (int i = 0; i < triangles.Length; i += 3) { {
for (int i = 0; i < triangles.Length; i += 3)
{
RecastRasterization.rasterizeTriangle(hf, vertices, triangles[i], triangles[i + 1], triangles[i + 2], area, RecastRasterization.rasterizeTriangle(hf, vertices, triangles[i], triangles[i + 1], triangles[i + 2], area,
(int) Math.Floor(flagMergeThreshold / hf.ch), telemetry); (int)Math.Floor(flagMergeThreshold / hf.ch), telemetry);
}
} }
} }
}
} }

View File

@ -6,12 +6,12 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.5" /> <PackageReference Include="K4os.Compression.LZ4" Version="1.3.5"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" /> <ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj"/>
<ProjectReference Include="..\DotRecast.Recast\DotRecast.Recast.csproj" /> <ProjectReference Include="..\DotRecast.Recast\DotRecast.Recast.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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

View File

@ -20,10 +20,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic namespace DotRecast.Detour.Dynamic
{ {
public class DynamicNavMeshConfig
{
public class DynamicNavMeshConfig {
public readonly bool useTiles; public readonly bool useTiles;
public readonly int tileSizeX; public readonly int tileSizeX;
public readonly int tileSizeZ; public readonly int tileSizeZ;
@ -48,13 +46,12 @@ public class DynamicNavMeshConfig {
public bool enableCheckpoints = true; public bool enableCheckpoints = true;
public bool keepIntermediateResults = false; 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.useTiles = useTiles;
this.tileSizeX = tileSizeX; this.tileSizeX = tileSizeX;
this.tileSizeZ = tileSizeZ; this.tileSizeZ = tileSizeZ;
this.cellSize = cellSize; this.cellSize = cellSize;
} }
}
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,19 +20,21 @@ using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Extras namespace DotRecast.Detour.Extras
{ {
public class BVTreeBuilder
{
public class BVTreeBuilder { public void build(MeshData data)
{
public void build(MeshData data) {
data.bvTree = new BVNode[data.header.polyCount * 2]; 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); : 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]; 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(); NavMeshBuilder.BVItem it = new NavMeshBuilder.BVItem();
items[i] = it; items[i] = it;
it.i = i; it.i = i;
@ -40,20 +42,21 @@ public class BVTreeBuilder {
float[] bmax = new float[3]; float[] bmax = new float[3];
vCopy(bmin, data.verts, data.polys[i].verts[0] * 3); vCopy(bmin, data.verts, data.polys[i].verts[0] * 3);
vCopy(bmax, 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); vMin(bmin, data.verts, data.polys[i].verts[j] * 3);
vMax(bmax, 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[0] = clamp((int)((bmin[0] - data.header.bmin[0]) * quantFactor), 0, 0x7fffffff);
it.bmin[2] = clamp((int) ((bmin[2] - data.header.bmin[2]) * quantFactor), 0, 0x7fffffff); it.bmin[1] = clamp((int)((bmin[1] - data.header.bmin[1]) * quantFactor), 0, 0x7fffffff);
it.bmax[0] = clamp((int) ((bmax[0] - data.header.bmin[0]) * quantFactor), 0, 0x7fffffff); it.bmin[2] = clamp((int)((bmin[2] - data.header.bmin[2]) * quantFactor), 0, 0x7fffffff);
it.bmax[1] = clamp((int) ((bmax[1] - data.header.bmin[1]) * quantFactor), 0, 0x7fffffff); it.bmax[0] = clamp((int)((bmax[0] - data.header.bmin[0]) * quantFactor), 0, 0x7fffffff);
it.bmax[2] = clamp((int) ((bmax[2] - data.header.bmin[2]) * quantFactor), 0, 0x7fffffff); 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); return NavMeshBuilder.subdivide(items, data.header.polyCount, 0, data.header.polyCount, 0, nodes);
} }
}
}
} }

View File

@ -5,8 +5,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DotRecast.Recast\DotRecast.Recast.csproj" /> <ProjectReference Include="..\DotRecast.Recast\DotRecast.Recast.csproj"/>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" /> <ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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

View File

@ -2,16 +2,16 @@ using System;
namespace DotRecast.Detour.Extras.Jumplink namespace DotRecast.Detour.Extras.Jumplink
{ {
public class ClimbTrajectory : Trajectory
{
public class ClimbTrajectory : Trajectory { public override float[] apply(float[] start, float[] end, float u)
{
public override float[] apply(float[] start, float[] end, float u) { return new float[]
return new float[] { lerp(start[0], end[0], Math.Min(2f * u, 1f)), {
lerp(start[0], end[0], Math.Min(2f * u, 1f)),
lerp(start[1], end[1], Math.Max(0f, 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 namespace DotRecast.Detour.Extras.Jumplink
{ {
public class Edge
{
public class Edge {
public readonly float[] sp = new float[3]; public readonly float[] sp = new float[3];
public readonly float[] sq = 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 namespace DotRecast.Detour.Extras.Jumplink
{ {
public class EdgeExtractor
{
public class EdgeExtractor { public Edge[] extractEdges(PolyMesh mesh)
public Edge[] extractEdges(PolyMesh mesh) { {
List<Edge> edges = new List<Edge>(); List<Edge> edges = new List<Edge>();
if (mesh != null) { if (mesh != null)
{
float[] orig = mesh.bmin; float[] orig = mesh.bmin;
float cs = mesh.cs; float cs = mesh.cs;
float ch = mesh.ch; float ch = mesh.ch;
for (int i = 0; i < mesh.npolys; i++) { for (int i = 0; i < mesh.npolys; i++)
if (i > 41 || i < 41) { {
if (i > 41 || i < 41)
{
// continue; // continue;
} }
int nvp = mesh.nvp; int nvp = mesh.nvp;
int p = i * 2 * nvp; int p = i * 2 * nvp;
for (int j = 0; j < nvp; ++j) { for (int j = 0; j < nvp; ++j)
if (j != 1) { {
if (j != 1)
{
// continue; // continue;
} }
if (mesh.polys[p + j] == RC_MESH_NULL_IDX) {
if (mesh.polys[p + j] == RC_MESH_NULL_IDX)
{
break; break;
} }
// Skip connected edges. // 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; int dir = mesh.polys[p + nvp + j] & 0xf;
if (dir == 0xf) {// Border if (dir == 0xf)
if (mesh.polys[p + nvp + j] != RC_MESH_NULL_IDX) { {
// Border
if (mesh.polys[p + nvp + j] != RC_MESH_NULL_IDX)
{
continue; continue;
} }
int nj = j + 1; 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; nj = 0;
} }
int va = mesh.polys[p + j] * 3; int va = mesh.polys[p + j] * 3;
int vb = mesh.polys[p + nj] * 3; int vb = mesh.polys[p + nj] * 3;
Edge e = new Edge(); Edge e = new Edge();
@ -56,7 +72,5 @@ public class EdgeExtractor {
return edges.ToArray(); return edges.ToArray();
} }
}
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,21 +21,25 @@ using System.Collections.Generic;
namespace DotRecast.Detour.Extras.Unity.Astar namespace DotRecast.Detour.Extras.Unity.Astar
{ {
public class LinkBuilder
{
public class LinkBuilder {
// Process connections and transform them into recast neighbour flags // Process connections and transform them into recast neighbour flags
public void build(int nodeOffset, GraphMeshData graphData, List<int[]> connections) { public void build(int nodeOffset, GraphMeshData graphData, List<int[]> connections)
for (int n = 0; n < connections.Count; n++) { {
for (int n = 0; n < connections.Count; n++)
{
int[] nodeConnections = connections[n]; int[] nodeConnections = connections[n];
MeshData tile = graphData.getTile(n); MeshData tile = graphData.getTile(n);
Poly node = graphData.getNode(n); Poly node = graphData.getNode(n);
foreach (int connection in nodeConnections) { foreach (int connection in nodeConnections)
{
MeshData neighbourTile = graphData.getTile(connection - nodeOffset); MeshData neighbourTile = graphData.getTile(connection - nodeOffset);
if (neighbourTile != tile) { if (neighbourTile != tile)
{
buildExternalLink(tile, node, neighbourTile); buildExternalLink(tile, node, neighbourTile);
} else { }
else
{
Poly neighbour = graphData.getNode(connection - nodeOffset); Poly neighbour = graphData.getNode(connection - nodeOffset);
buildInternalLink(tile, node, neighbourTile, neighbour); 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); int edge = PolyUtils.findEdge(node, neighbour, tile, neighbourTile);
if (edge >= 0) { if (edge >= 0)
{
node.neis[edge] = neighbour.index + 1; node.neis[edge] = neighbour.index + 1;
} else { }
else
{
throw new ArgumentException(); throw new ArgumentException();
} }
} }
// In case of external link to other tiles we must find the direction // In case of external link to other tiles we must find the direction
private void buildExternalLink(MeshData tile, Poly node, MeshData neighbourTile) { private void buildExternalLink(MeshData tile, Poly node, MeshData neighbourTile)
if (neighbourTile.header.bmin[0] > tile.header.bmin[0]) { {
if (neighbourTile.header.bmin[0] > tile.header.bmin[0])
{
node.neis[PolyUtils.findEdge(node, tile, neighbourTile.header.bmin[0], 0)] = NavMesh.DT_EXT_LINK; 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; 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; 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; 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 namespace DotRecast.Detour.Extras.Unity.Astar
{ {
public class Meta
{
public class Meta {
public const string TYPENAME_RECAST_GRAPH = "Pathfinding.RecastGraph"; public const string TYPENAME_RECAST_GRAPH = "Pathfinding.RecastGraph";
public const string MIN_SUPPORTED_VERSION = "4.0.6"; public const string MIN_SUPPORTED_VERSION = "4.0.6";
public const string UPDATED_STRUCT_VERSION = "4.1.16"; public const string UPDATED_STRUCT_VERSION = "4.1.16";
@ -34,44 +32,58 @@ public class Meta {
public string[] guids { get; set; } public string[] guids { get; set; }
public string[] typeNames { get; set; } public string[] typeNames { get; set; }
public bool isSupportedVersion() { public bool isSupportedVersion()
{
return isVersionAtLeast(MIN_SUPPORTED_VERSION); return isVersionAtLeast(MIN_SUPPORTED_VERSION);
} }
public bool isVersionAtLeast(string minVersion) { public bool isVersionAtLeast(string minVersion)
{
int[] actual = parseVersion(version); int[] actual = parseVersion(version);
int[] minSupported = parseVersion(minVersion); int[] minSupported = parseVersion(minVersion);
for (int i = 0; i < Math.Min(actual.Length, minSupported.Length); i++) { for (int i = 0; i < Math.Min(actual.Length, minSupported.Length); i++)
if (actual[i] > minSupported[i]) { {
if (actual[i] > minSupported[i])
{
return true; return true;
} else if (minSupported[i] > actual[i]) { }
else if (minSupported[i] > actual[i])
{
return false; return false;
} }
} }
return true; return true;
} }
private int[] parseVersion(string version) { private int[] parseVersion(string version)
{
Match m = VERSION_PATTERN.Match(version); Match m = VERSION_PATTERN.Match(version);
if (m.Success) { if (m.Success)
{
int[] v = new int[m.Groups.Count - 1]; 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); v[i] = int.Parse(m.Groups[i + 1].Value);
} }
return v; return v;
} }
throw new ArgumentException("Invalid version format: " + version); throw new ArgumentException("Invalid version format: " + version);
} }
public bool isSupportedType() { public bool isSupportedType()
foreach (string t in typeNames) { {
if (t == TYPENAME_RECAST_GRAPH) { foreach (string t in typeNames)
{
if (t == TYPENAME_RECAST_GRAPH)
{
return true; return true;
} }
} }
return false; return false;
} }
}
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,10 +22,8 @@ using System.IO.Compression;
namespace DotRecast.Detour.Extras.Unity.Astar namespace DotRecast.Detour.Extras.Unity.Astar
{ {
public class UnityAStarPathfindingReader
{
public class UnityAStarPathfindingReader {
private const string META_FILE_NAME = "meta.json"; private const string META_FILE_NAME = "meta.json";
private const string NODE_INDEX_FILE_NAME = "graph_references.binary"; private const string NODE_INDEX_FILE_NAME = "graph_references.binary";
private const string NODE_LINK_2_FILE_NAME = "node_link2.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 GraphConnectionReader graphConnectionReader = new GraphConnectionReader();
private readonly NodeLink2Reader nodeLink2Reader = new NodeLink2Reader(); private readonly NodeLink2Reader nodeLink2Reader = new NodeLink2Reader();
public GraphData read(FileStream zipFile) { public GraphData read(FileStream zipFile)
{
using ZipArchive file = new ZipArchive(zipFile); using ZipArchive file = new ZipArchive(zipFile);
// Read meta file and check version and graph type // Read meta file and check version and graph type
Meta meta = metaReader.read(file, META_FILE_NAME); Meta meta = metaReader.read(file, META_FILE_NAME);
@ -52,7 +51,8 @@ public class UnityAStarPathfindingReader {
List<GraphMeta> metaList = new List<GraphMeta>(); List<GraphMeta> metaList = new List<GraphMeta>();
List<GraphMeshData> meshDataList = new List<GraphMeshData>(); List<GraphMeshData> meshDataList = new List<GraphMeshData>();
List<List<int[]>> connectionsList = new List<List<int[]>>(); 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)); GraphMeta graphMeta = graphMetaReader.read(file, string.Format(GRAPH_META_FILE_NAME_PATTERN, graphIndex));
// First graph mesh data - vertices and polygons // First graph mesh data - vertices and polygons
GraphMeshData graphData = graphDataReader.read(file, GraphMeshData graphData = graphDataReader.read(file,
@ -64,8 +64,8 @@ public class UnityAStarPathfindingReader {
meshDataList.Add(graphData); meshDataList.Add(graphData);
connectionsList.Add(connections); connectionsList.Add(connections);
} }
return new GraphData(meta, indexToNode, nodeLinks2, metaList, meshDataList, connectionsList); 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 namespace DotRecast.Detour.Extras.Unity.Astar
{ {
public abstract class ZipBinaryReader
{
public abstract class ZipBinaryReader { protected ByteBuffer toByteBuffer(ZipArchive file, string filename)
{
protected ByteBuffer toByteBuffer(ZipArchive file, string filename) {
ZipArchiveEntry graphReferences = file.GetEntry(filename); ZipArchiveEntry graphReferences = file.GetEntry(filename);
using var entryStream = graphReferences.Open(); using var entryStream = graphReferences.Open();
using var bis = new BinaryReader(entryStream); using var bis = new BinaryReader(entryStream);
@ -35,7 +34,5 @@ public abstract class ZipBinaryReader {
buffer.order(ByteOrder.LITTLE_ENDIAN); buffer.order(ByteOrder.LITTLE_ENDIAN);
return buffer; return buffer;
} }
}
}
} }

View File

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

View File

@ -27,35 +27,44 @@ using DotRecast.Core;
namespace DotRecast.Detour.TileCache namespace DotRecast.Detour.TileCache
{ {
public abstract class AbstractTileLayersBuilder
{
public abstract class AbstractTileLayersBuilder { protected List<byte[]> build(ByteOrder order, bool cCompatibility, int threads, int tw, int th)
{
protected List<byte[]> build(ByteOrder order, bool cCompatibility, int threads, int tw, int th) { if (threads == 1)
if (threads == 1) { {
return buildSingleThread(order, cCompatibility, tw, th); return buildSingleThread(order, cCompatibility, tw, th);
} }
return buildMultiThread(order, cCompatibility, tw, th, threads); 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[]>(); List<byte[]> layers = new List<byte[]>();
for (int y = 0; y < th; ++y) { for (int y = 0; y < th; ++y)
for (int x = 0; x < tw; ++x) { {
for (int x = 0; x < tw; ++x)
{
layers.AddRange(build(x, y, order, cCompatibility)); layers.AddRange(build(x, y, order, cCompatibility));
} }
} }
return layers; 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[]>>>>(); var tasks = new ConcurrentQueue<Task<Tuple<int, int, List<byte[]>>>>();
for (int y = 0; y < th; ++y) { for (int y = 0; y < th; ++y)
for (int x = 0; x < tw; ++x) { {
for (int x = 0; x < tw; ++x)
{
int tx = x; int tx = x;
int ty = y; int ty = y;
var task = Task.Run(() => { var task = Task.Run(() =>
var partial= build(tx, ty, order, cCompatibility); {
var partial = build(tx, ty, order, cCompatibility);
return Tuple.Create(tx, ty, partial); return Tuple.Create(tx, ty, partial);
}); });
tasks.Enqueue(task); tasks.Enqueue(task);
@ -67,16 +76,18 @@ public abstract class AbstractTileLayersBuilder {
.ToDictionary(x => Tuple.Create(x.Item1, x.Item2), x => x.Item3); .ToDictionary(x => Tuple.Create(x.Item1, x.Item2), x => x.Item3);
List<byte[]> layers = new List<byte[]>(); List<byte[]> layers = new List<byte[]>();
for (int y = 0; y < th; ++y) { for (int y = 0; y < th; ++y)
for (int x = 0; x < tw; ++x) { {
for (int x = 0; x < tw; ++x)
{
var key = Tuple.Create(x, y); var key = Tuple.Create(x, y);
layers.AddRange(partialResults[key]); layers.AddRange(partialResults[key]);
} }
} }
return layers; return layers;
} }
protected abstract List<byte[]> build(int tx, int ty, ByteOrder order, bool cCompatibility); 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. misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
namespace DotRecast.Detour.TileCache namespace DotRecast.Detour.TileCache
{ {
public class CompressedTile
{
public class CompressedTile {
public readonly int index; 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 TileCacheLayerHeader header;
public byte[] data; public byte[] data;
public int compressed; // offset of compressed data public int compressed; // offset of compressed data
public int flags; public int flags;
public CompressedTile next; public CompressedTile next;
public CompressedTile(int index) { public CompressedTile(int index)
{
this.index = index; this.index = index;
salt = 1; salt = 1;
} }
} }
} }

View File

@ -6,11 +6,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.5" /> <PackageReference Include="K4os.Compression.LZ4" Version="1.3.5"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" /> <ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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

View File

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

View File

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

View File

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

View File

@ -24,35 +24,37 @@ using DotRecast.Detour.Io;
namespace DotRecast.Detour.TileCache.Io namespace DotRecast.Detour.TileCache.Io
{ {
public class TileCacheLayerHeaderWriter : DetourWriter
{
public class TileCacheLayerHeaderWriter : DetourWriter { public void write(BinaryWriter stream, TileCacheLayerHeader header, ByteOrder order, bool cCompatibility)
{
public void write(BinaryWriter stream, TileCacheLayerHeader header, ByteOrder order, bool cCompatibility) {
write(stream, header.magic, order); write(stream, header.magic, order);
write(stream, header.version, order); write(stream, header.version, order);
write(stream, header.tx, order); write(stream, header.tx, order);
write(stream, header.ty, order); write(stream, header.ty, order);
write(stream, header.tlayer, 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); 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, header.bmax[j], order);
} }
write(stream, (short) header.hmin, order);
write(stream, (short) header.hmax, order); write(stream, (short)header.hmin, order);
write(stream, (byte) header.width); write(stream, (short)header.hmax, order);
write(stream, (byte) header.height); write(stream, (byte)header.width);
write(stream, (byte) header.minx); write(stream, (byte)header.height);
write(stream, (byte) header.maxx); write(stream, (byte)header.minx);
write(stream, (byte) header.miny); write(stream, (byte)header.maxx);
write(stream, (byte) header.maxy); write(stream, (byte)header.miny);
if (cCompatibility) { write(stream, (byte)header.maxy);
write(stream, (short) 0, order); // C struct padding 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 namespace DotRecast.Detour.TileCache.Io
{ {
public class TileCacheReader
{
public class TileCacheReader {
private readonly NavMeshParamReader paramReader = new NavMeshParamReader(); 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); ByteBuffer bb = IOUtils.toByteBuffer(@is);
return read(bb, maxVertPerPoly, meshProcessor); 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(); TileCacheSetHeader header = new TileCacheSetHeader();
header.magic = bb.getInt(); header.magic = bb.getInt();
if (header.magic != TileCacheSetHeader.TILECACHESET_MAGIC) { if (header.magic != TileCacheSetHeader.TILECACHESET_MAGIC)
{
header.magic = IOUtils.swapEndianness(header.magic); header.magic = IOUtils.swapEndianness(header.magic);
if (header.magic != TileCacheSetHeader.TILECACHESET_MAGIC) { if (header.magic != TileCacheSetHeader.TILECACHESET_MAGIC)
{
throw new IOException("Invalid magic"); throw new IOException("Invalid magic");
} }
bb.order(bb.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); bb.order(bb.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
} }
header.version = bb.getInt(); header.version = bb.getInt();
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION) { if (header.version != TileCacheSetHeader.TILECACHESET_VERSION)
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION_RECAST4J) { {
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION_RECAST4J)
{
throw new IOException("Invalid version"); throw new IOException("Invalid version");
} }
} }
bool cCompatibility = header.version == TileCacheSetHeader.TILECACHESET_VERSION; bool cCompatibility = header.version == TileCacheSetHeader.TILECACHESET_VERSION;
header.numTiles = bb.getInt(); header.numTiles = bb.getInt();
header.meshParams = paramReader.read(bb); header.meshParams = paramReader.read(bb);
@ -61,27 +68,34 @@ public class TileCacheReader {
TileCache tc = new TileCache(header.cacheParams, new TileCacheStorageParams(bb.order(), cCompatibility), mesh, TileCache tc = new TileCache(header.cacheParams, new TileCacheStorageParams(bb.order(), cCompatibility), mesh,
compressor, meshProcessor); compressor, meshProcessor);
// Read tiles. // Read tiles.
for (int i = 0; i < header.numTiles; ++i) { for (int i = 0; i < header.numTiles; ++i)
{
long tileRef = bb.getInt(); long tileRef = bb.getInt();
int dataSize = bb.getInt(); int dataSize = bb.getInt();
if (tileRef == 0 || dataSize == 0) { if (tileRef == 0 || dataSize == 0)
{
break; break;
} }
byte[] data = bb.ReadBytes(dataSize).ToArray(); byte[] data = bb.ReadBytes(dataSize).ToArray();
long tile = tc.addTile(data, 0); long tile = tc.addTile(data, 0);
if (tile != 0) { if (tile != 0)
{
tc.buildNavMeshTile(tile); tc.buildNavMeshTile(tile);
} }
} }
return tc; return tc;
} }
private TileCacheParams readCacheParams(ByteBuffer bb, bool cCompatibility) { private TileCacheParams readCacheParams(ByteBuffer bb, bool cCompatibility)
{
TileCacheParams option = new TileCacheParams(); TileCacheParams option = new TileCacheParams();
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++)
{
option.orig[i] = bb.getFloat(); option.orig[i] = bb.getFloat();
} }
option.cs = bb.getFloat(); option.cs = bb.getFloat();
option.ch = bb.getFloat(); option.ch = bb.getFloat();
option.width = bb.getInt(); option.width = bb.getInt();
@ -94,6 +108,5 @@ public class TileCacheReader {
option.maxObstacles = bb.getInt(); option.maxObstacles = bb.getInt();
return option; return option;
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,22 +17,33 @@ freely, subject to the following restrictions:
misrepresented as being the original software. misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
namespace DotRecast.Detour.TileCache namespace DotRecast.Detour.TileCache
{ {
public class TileCachePolyMesh
{
public class TileCachePolyMesh {
public int nvp; public int nvp;
public int nverts; /// < Number of vertices. public int nverts;
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) { /// < 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; this.nvp = nvp;
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -22,11 +22,9 @@ using System;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
using static DetourCommon;
/**
using static DetourCommon;
/**
* <b>The Default Implementation</b> * <b>The Default Implementation</b>
* *
* At construction: All area costs default to 1.0. All flags are included and none are excluded. * At construction: All area costs default to 1.0. All flags are included and none are excluded.
@ -51,56 +49,66 @@ using static DetourCommon;
* *
* @see NavMeshQuery * @see NavMeshQuery
*/ */
public class DefaultQueryFilter : QueryFilter { public class DefaultQueryFilter : QueryFilter
{
private int m_excludeFlags; private int m_excludeFlags;
private int m_includeFlags; private int m_includeFlags;
private readonly float[] m_areaCost = new float[NavMesh.DT_MAX_AREAS]; private readonly float[] m_areaCost = new float[NavMesh.DT_MAX_AREAS];
public DefaultQueryFilter() { public DefaultQueryFilter()
{
m_includeFlags = 0xffff; m_includeFlags = 0xffff;
m_excludeFlags = 0; 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; 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_includeFlags = includeFlags;
m_excludeFlags = excludeFlags; 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]; 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; 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; 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, 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()]; return vDist(pa, pb) * m_areaCost[curPoly.getArea()];
} }
public int getIncludeFlags() { public int getIncludeFlags()
{
return m_includeFlags; return m_includeFlags;
} }
public void setIncludeFlags(int flags) { public void setIncludeFlags(int flags)
{
m_includeFlags = flags; m_includeFlags = flags;
} }
public int getExcludeFlags() { public int getExcludeFlags()
{
return m_excludeFlags; return m_excludeFlags;
} }
public void setExcludeFlags(int flags) { public void setExcludeFlags(int flags)
{
m_excludeFlags = flags; m_excludeFlags = flags;
} }
}
}
} }

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DotRecast.Core\DotRecast.Core.csproj" /> <ProjectReference Include="..\DotRecast.Core\DotRecast.Core.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,24 +22,26 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io namespace DotRecast.Detour.Io
{ {
public static class IOUtils
{
public static class IOUtils { public static ByteBuffer toByteBuffer(BinaryReader @is, bool direct)
{
public static ByteBuffer toByteBuffer(BinaryReader @is, bool direct) {
byte[] data = toByteArray(@is); byte[] data = toByteArray(@is);
if (direct) { if (direct)
{
Array.Reverse(data); Array.Reverse(data);
} }
return new ByteBuffer(data); return new ByteBuffer(data);
} }
public static byte[] toByteArray(BinaryReader inputStream) { public static byte[] toByteArray(BinaryReader inputStream)
{
using var baos = new MemoryStream(); using var baos = new MemoryStream();
byte[] buffer = new byte[4096]; byte[] buffer = new byte[4096];
int l; int l;
while ((l = inputStream.Read(buffer)) > 0) { while ((l = inputStream.Read(buffer)) > 0)
{
baos.Write(buffer, 0, l); baos.Write(buffer, 0, l);
} }
@ -55,9 +57,8 @@ public static class IOUtils {
public static int swapEndianness(int i) public static int swapEndianness(int i)
{ {
var s = (((uint)i >> 24) & 0xFF) | (((uint)i>>8) & 0xFF00) | (((uint)i<<8) & 0xFF0000) | ((i << 24) & 0xFF000000); var s = (((uint)i >> 24) & 0xFF) | (((uint)i >> 8) & 0xFF00) | (((uint)i << 8) & 0xFF0000) | ((i << 24) & 0xFF000000);
return (int)s; return (int)s;
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,59 +17,83 @@ freely, subject to the following restrictions:
misrepresented as being the original software. misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/// Represents the source data used to build an navigation mesh tile.
public class NavMeshDataCreateParams
/// Represents the source data used to build an navigation mesh tile. {
public class NavMeshDataCreateParams {
/// @name Polygon Mesh Attributes /// @name Polygon Mesh Attributes
/// Used to create the base navigation graph. /// Used to create the base navigation graph.
/// See #rcPolyMesh for details related to these attributes. /// See #rcPolyMesh for details related to these attributes.
/// @{ /// @{
public int[] verts;
public int[] verts; /// < The polygon mesh vertices. [(x, y, z) * #vertCount] [Unit: vx] /// < The polygon mesh vertices. [(x, y, z) * #vertCount] [Unit: vx]
public int vertCount; /// < The number vertices in the polygon mesh. [Limit: >= 3] public int vertCount;
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 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) /// @name Height Detail Attributes (Optional)
/// See #rcPolyMeshDetail for details related to these attributes. /// See #rcPolyMeshDetail for details related to these attributes.
/// @{ /// @{
public int[] detailMeshes;
public int[] detailMeshes; /// < The height detail sub-mesh data. [Size: 4 * #polyCount] /// < The height detail sub-mesh data. [Size: 4 * #polyCount]
public float[] detailVerts; /// < The detail mesh vertices. [Size: 3 * #detailVertsCount] [Unit: wu] public float[] detailVerts;
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 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) /// @name Off-Mesh Connections Attributes (Optional)
/// Used to define a custom point-to-point edge within the navigation graph, an /// 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, /// 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. /// at least one of which resides within a navigation mesh polygon.
/// @{ /// @{
/// Off-mesh connection vertices. [(ax, ay, az, bx, by, bz) * #offMeshConCount] [Unit: wu] /// Off-mesh connection vertices. [(ax, ay, az, bx, by, bz) * #offMeshConCount] [Unit: wu]
public float[] offMeshConVerts; public float[] offMeshConVerts;
/// Off-mesh connection radii. [Size: #offMeshConCount] [Unit: wu] /// Off-mesh connection radii. [Size: #offMeshConCount] [Unit: wu]
public float[] offMeshConRad; public float[] offMeshConRad;
/// User defined flags assigned to the off-mesh connections. [Size: #offMeshConCount] /// User defined flags assigned to the off-mesh connections. [Size: #offMeshConCount]
public int[] offMeshConFlags; public int[] offMeshConFlags;
/// User defined area ids assigned to the off-mesh connections. [Size: #offMeshConCount] /// User defined area ids assigned to the off-mesh connections. [Size: #offMeshConCount]
public int[] offMeshConAreas; public int[] offMeshConAreas;
/// The permitted travel direction of the off-mesh connections. [Size: #offMeshConCount] /// The permitted travel direction of the off-mesh connections. [Size: #offMeshConCount]
/// ///
/// 0 = Travel only from endpoint A to endpoint B.<br/> /// 0 = Travel only from endpoint A to endpoint B.<br/>
/// #DT_OFFMESH_CON_BIDIR = Bidirectional travel. /// #DT_OFFMESH_CON_BIDIR = Bidirectional travel.
public int[] offMeshConDir; public int[] offMeshConDir;
/// The user defined ids of the off-mesh connection. [Size: #offMeshConCount] /// The user defined ids of the off-mesh connection. [Size: #offMeshConCount]
public int[] offMeshConUserID; public int[] offMeshConUserID;
/// The number of off-mesh connections. [Limit: >= 0] /// The number of off-mesh connections. [Limit: >= 0]
public int offMeshConCount; public int offMeshConCount;
@ -77,29 +101,46 @@ public class NavMeshDataCreateParams {
/// @name Tile Attributes /// @name Tile Attributes
/// @note The tile grid/layer data can be left at zero if the destination is a single tile mesh. /// @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. /// < 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 tileX;
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 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 /// @name General Configuration Attributes
/// @{ /// @{
public float walkableHeight;
public float walkableHeight; /// < The agent height. [Unit: wu] /// < The agent height. [Unit: wu]
public float walkableRadius; /// < The agent radius. [Unit: wu] public float walkableRadius;
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 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. /// True if a bounding volume tree should be built for the tile.
/// @note The BVTree is not normally needed for layered navigation meshes. /// @note The BVTree is not normally needed for layered navigation meshes.
public bool buildBvTree; public bool buildBvTree;
/// @} /// @}
}
}
} }

View File

@ -17,27 +17,30 @@ freely, subject to the following restrictions:
misrepresented as being the original software. misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/**
/**
* Configuration parameters used to define multi-tile navigation meshes. The values are used to allocate space during * Configuration parameters used to define multi-tile navigation meshes. The values are used to allocate space during
* the initialization of a navigation mesh. * the initialization of a navigation mesh.
* *
* @see NavMesh * @see NavMesh
*/ */
public class NavMeshParams { public class NavMeshParams
{
/** The world space origin of the navigation mesh's tile space. [(x, y, z)] */ /** The world space origin of the navigation mesh's tile space. [(x, y, z)] */
public readonly float[] orig = new float[3]; public readonly float[] orig = new float[3];
/** The width of each tile. (Along the x-axis.) */ /** The width of each tile. (Along the x-axis.) */
public float tileWidth; public float tileWidth;
/** The height of each tile. (Along the z-axis.) */ /** The height of each tile. (Along the z-axis.) */
public float tileHeight; public float tileHeight;
/** The maximum number of tiles the navigation mesh can contain. */ /** The maximum number of tiles the navigation mesh can contain. */
public int maxTiles; public int maxTiles;
/** The maximum number of polygons each tile can contain. */ /** The maximum number of polygons each tile can contain. */
public int maxPolys; 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 namespace DotRecast.Detour
{ {
public class Node
{
public class Node {
public const int DT_NODE_OPEN = 0x01; public const int DT_NODE_OPEN = 0x01;
public const int DT_NODE_CLOSED = 0x02; public const int DT_NODE_CLOSED = 0x02;
/** parent of the node is not adjacent. Found using raycast. */ /** parent of the node is not adjacent. Found using raycast. */
public const int DT_NODE_PARENT_DETACHED = 0x04; public const int DT_NODE_PARENT_DETACHED = 0x04;
@ -36,31 +34,38 @@ public class Node {
/** Position of the node. */ /** Position of the node. */
public float[] pos = new float[3]; public float[] pos = new float[3];
/** Cost of reaching the given node. */ /** Cost of reaching the given node. */
public float cost; public float cost;
/** Total cost of reaching the goal via the given node including heuristics. */ /** Total cost of reaching the goal via the given node including heuristics. */
public float total; public float total;
/** Index to parent node. */ /** Index to parent node. */
public int pidx; public int pidx;
/** /**
* extra state information. A polyRef can have multiple nodes with different extra info. see DT_MAX_STATES_PER_NODE * extra state information. A polyRef can have multiple nodes with different extra info. see DT_MAX_STATES_PER_NODE
*/ */
public int state; public int state;
/** Node flags. A combination of dtNodeFlags. */ /** Node flags. A combination of dtNodeFlags. */
public int flags; public int flags;
/** Polygon ref the node corresponds to. */ /** Polygon ref the node corresponds to. */
public long id; public long id;
/** Shortcut found by raycast. */ /** Shortcut found by raycast. */
public List<long> shortcut; public List<long> shortcut;
public Node(int index) { public Node(int index)
{
this.index = index; this.index = index;
} }
public override string ToString() { public override string ToString()
{
return "Node [id=" + id + "]"; return "Node [id=" + id + "]";
} }
}
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,11 +17,11 @@ freely, subject to the following restrictions:
misrepresented as being the original software. misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
public class Status
{
public class Status {
public static Status FAILURE = new Status(0); public static Status FAILURE = new Status(0);
public static Status SUCCSESS = new Status(1); public static Status SUCCSESS = new Status(1);
public static Status IN_PROGRESS = new Status(2); public static Status IN_PROGRESS = new Status(2);
@ -34,26 +34,28 @@ public class Status {
{ {
Value = vlaue; Value = vlaue;
} }
} }
public static class StatusEx 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; 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; 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; 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; return @this == Status.PARTIAL_RESULT;
} }
}
}
} }

View File

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

View File

@ -20,14 +20,12 @@ freely, subject to the following restrictions:
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/**
/**
* Wrapper for 3-element pieces (3D vectors) of a bigger float array. * Wrapper for 3-element pieces (3D vectors) of a bigger float array.
* *
*/ */
public class VectorPtr public class VectorPtr
{ {
private readonly float[] array; private readonly float[] array;
private readonly int index; private readonly int index;
@ -46,5 +44,5 @@ public class VectorPtr
{ {
return array[index + offset]; return array[index + offset];
} }
} }
} }

View File

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

View File

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

View File

@ -24,14 +24,14 @@ using DotRecast.Recast.Demo.Geom;
namespace DotRecast.Recast.Demo.Builder; 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, 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_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, 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, 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, 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_agentRadius, m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, filterLowHangingObstacles, filterLedgeSpans, m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, filterLowHangingObstacles, filterLedgeSpans,
@ -42,7 +42,8 @@ public class SoloNavMeshBuilder : AbstractNavMeshBuilder {
m_vertsPerPoly)); 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); 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, 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, 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, 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, RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize, m_cellHeight, m_agentMaxSlope, filterLowHangingObstacles,
filterLedgeSpans, filterWalkableLowHeightSpans, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_regionMinSize, filterLedgeSpans, filterWalkableLowHeightSpans, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_regionMinSize,
m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, 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, 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, NavMeshDataCreateParams option = getNavMeshCreateParams(m_geom, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius,
m_agentMaxClimb, rcResult); m_agentMaxClimb, rcResult);
return updateAreaAndFlags(NavMeshBuilder.createNavMeshData(option)); return updateAreaAndFlags(NavMeshBuilder.createNavMeshData(option));
} }
} }

View File

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

View File

@ -7,18 +7,18 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Serilog" Version="2.12.0" /> <PackageReference Include="Serilog" Version="2.12.0"/>
<PackageReference Include="Silk.NET" Version="2.16.0" /> <PackageReference Include="Silk.NET" Version="2.16.0"/>
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.16.0" /> <PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.16.0"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DotRecast.Detour.Crowd\DotRecast.Detour.Crowd.csproj" /> <ProjectReference Include="..\DotRecast.Detour.Crowd\DotRecast.Detour.Crowd.csproj"/>
<ProjectReference Include="..\DotRecast.Detour.Dynamic\DotRecast.Detour.Dynamic.csproj" /> <ProjectReference Include="..\DotRecast.Detour.Dynamic\DotRecast.Detour.Dynamic.csproj"/>
<ProjectReference Include="..\DotRecast.Detour.Extras\DotRecast.Detour.Extras.csproj" /> <ProjectReference Include="..\DotRecast.Detour.Extras\DotRecast.Detour.Extras.csproj"/>
<ProjectReference Include="..\DotRecast.Detour.TileCache\DotRecast.Detour.TileCache.csproj" /> <ProjectReference Include="..\DotRecast.Detour.TileCache\DotRecast.Detour.TileCache.csproj"/>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" /> <ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj"/>
<ProjectReference Include="..\DotRecast.Recast\DotRecast.Recast.csproj" /> <ProjectReference Include="..\DotRecast.Recast\DotRecast.Recast.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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

View File

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

View File

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

View File

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

View File

@ -21,11 +21,10 @@ using DotRecast.Core;
namespace DotRecast.Recast.Demo.Draw; namespace DotRecast.Recast.Demo.Draw;
public class GLU
{
public class GLU { public static float[] gluPerspective(float fovy, float aspect, float near, float far)
{
public static float[] gluPerspective(float fovy, float aspect, float near, float far) {
float[] projectionMatrix = new float[16]; float[] projectionMatrix = new float[16];
glhPerspectivef2(projectionMatrix, fovy, aspect, near, far); glhPerspectivef2(projectionMatrix, fovy, aspect, near, far);
//glLoadMatrixf(projectionMatrix); //glLoadMatrixf(projectionMatrix);
@ -33,15 +32,17 @@ public class GLU {
} }
public static void glhPerspectivef2(float[] matrix, float fovyInDegrees, float aspectRatio, float znear, public static void glhPerspectivef2(float[] matrix, float fovyInDegrees, float aspectRatio, float znear,
float zfar) { float zfar)
{
float ymax, xmax; float ymax, xmax;
ymax = (float) (znear * Math.Tan(fovyInDegrees * Math.PI / 360.0)); ymax = (float)(znear * Math.Tan(fovyInDegrees * Math.PI / 360.0));
xmax = ymax * aspectRatio; xmax = ymax * aspectRatio;
glhFrustumf2(matrix, -xmax, xmax, -ymax, ymax, znear, zfar); glhFrustumf2(matrix, -xmax, xmax, -ymax, ymax, znear, zfar);
} }
private static void glhFrustumf2(float[] matrix, float left, float right, float bottom, float top, float znear, 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; float temp, temp2, temp3, temp4;
temp = 2.0f * znear; temp = 2.0f * znear;
temp2 = right - left; temp2 = right - left;
@ -66,7 +67,8 @@ public class GLU {
} }
public static int glhUnProjectf(float winx, float winy, float winz, float[] modelview, float[] projection, public static int glhUnProjectf(float winx, float winy, float winz, float[] modelview, float[] projection,
int[] viewport, float[] objectCoordinate) { int[] viewport, float[] objectCoordinate)
{
// Transformation matrices // Transformation matrices
float[] m = new float[16], A = new float[16]; float[] m = new float[16], A = new float[16];
float[] @in = new float[4], @out = new float[4]; float[] @in = new float[4], @out = new float[4];
@ -92,7 +94,8 @@ public class GLU {
return 1; 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] result[0] = matrix1[0] * matrix2[0] + matrix1[4] * matrix2[1] + matrix1[8] * matrix2[2]
+ matrix1[12] * matrix2[3]; + matrix1[12] * matrix2[3];
result[4] = matrix1[0] * matrix2[4] + matrix1[4] * matrix2[5] + matrix1[8] * matrix2[6] 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]; + 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] resultvector[0] = matrix[0] * pvector[0] + matrix[4] * pvector[1] + matrix[8] * pvector[2]
+ matrix[12] * pvector[3]; + matrix[12] * pvector[3];
resultvector[1] = matrix[1] * pvector[0] + matrix[5] * pvector[1] + matrix[9] * pvector[2] 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 // 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[][] wtmp = ArrayUtils.Of<float>(4, 8);
float m0, m1, m2, m3, s; float m0, m1, m2, m3, s;
float[] r0, r1, r2, r3; float[] r0, r1, r2, r3;
@ -172,21 +177,27 @@ public class GLU {
r3[7] = 1.0f; r3[7] = 1.0f;
r3[4] = r3[5] = r3[6] = 0.0f; r3[4] = r3[5] = r3[6] = 0.0f;
/* choose pivot - or die */ /* 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; float[] r = r2;
r2 = r3; r2 = r3;
r3 = r; r3 = r;
} }
if (Math.Abs(r2[0]) > Math.Abs(r1[0])) {
if (Math.Abs(r2[0]) > Math.Abs(r1[0]))
{
float[] r = r2; float[] r = r2;
r2 = r1; r2 = r1;
r1 = r; r1 = r;
} }
if (Math.Abs(r1[0]) > Math.Abs(r0[0])) {
if (Math.Abs(r1[0]) > Math.Abs(r0[0]))
{
float[] r = r1; float[] r = r1;
r1 = r0; r1 = r0;
r0 = r; r0 = r;
} }
if (0.0 == r0[0]) if (0.0 == r0[0])
return 0; return 0;
/* eliminate first variable */ /* eliminate first variable */
@ -206,40 +217,52 @@ public class GLU {
r2[3] -= m2 * s; r2[3] -= m2 * s;
r3[3] -= m3 * s; r3[3] -= m3 * s;
s = r0[4]; s = r0[4];
if (s != 0.0) { if (s != 0.0)
{
r1[4] -= m1 * s; r1[4] -= m1 * s;
r2[4] -= m2 * s; r2[4] -= m2 * s;
r3[4] -= m3 * s; r3[4] -= m3 * s;
} }
s = r0[5]; s = r0[5];
if (s != 0.0) { if (s != 0.0)
{
r1[5] -= m1 * s; r1[5] -= m1 * s;
r2[5] -= m2 * s; r2[5] -= m2 * s;
r3[5] -= m3 * s; r3[5] -= m3 * s;
} }
s = r0[6]; s = r0[6];
if (s != 0.0) { if (s != 0.0)
{
r1[6] -= m1 * s; r1[6] -= m1 * s;
r2[6] -= m2 * s; r2[6] -= m2 * s;
r3[6] -= m3 * s; r3[6] -= m3 * s;
} }
s = r0[7]; s = r0[7];
if (s != 0.0) { if (s != 0.0)
{
r1[7] -= m1 * s; r1[7] -= m1 * s;
r2[7] -= m2 * s; r2[7] -= m2 * s;
r3[7] -= m3 * s; r3[7] -= m3 * s;
} }
/* choose pivot - or die */ /* 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; float[] r = r2;
r2 = r3; r2 = r3;
r3 = r; r3 = r;
} }
if (Math.Abs(r2[1]) > Math.Abs(r1[1])) {
if (Math.Abs(r2[1]) > Math.Abs(r1[1]))
{
float[] r = r2; float[] r = r2;
r2 = r1; r2 = r1;
r1 = r; r1 = r;
} }
if (0.0 == r1[1]) if (0.0 == r1[1])
return 0; return 0;
/* eliminate second variable */ /* eliminate second variable */
@ -250,31 +273,41 @@ public class GLU {
r2[3] -= m2 * r1[3]; r2[3] -= m2 * r1[3];
r3[3] -= m3 * r1[3]; r3[3] -= m3 * r1[3];
s = r1[4]; s = r1[4];
if (0.0 != s) { if (0.0 != s)
{
r2[4] -= m2 * s; r2[4] -= m2 * s;
r3[4] -= m3 * s; r3[4] -= m3 * s;
} }
s = r1[5]; s = r1[5];
if (0.0 != s) { if (0.0 != s)
{
r2[5] -= m2 * s; r2[5] -= m2 * s;
r3[5] -= m3 * s; r3[5] -= m3 * s;
} }
s = r1[6]; s = r1[6];
if (0.0 != s) { if (0.0 != s)
{
r2[6] -= m2 * s; r2[6] -= m2 * s;
r3[6] -= m3 * s; r3[6] -= m3 * s;
} }
s = r1[7]; s = r1[7];
if (0.0 != s) { if (0.0 != s)
{
r2[7] -= m2 * s; r2[7] -= m2 * s;
r3[7] -= m3 * s; r3[7] -= m3 * s;
} }
/* choose pivot - or die */ /* 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; float[] r = r2;
r2 = r3; r2 = r3;
r3 = r; r3 = r;
} }
if (0.0 == r2[2]) if (0.0 == r2[2])
return 0; return 0;
/* eliminate third variable */ /* eliminate third variable */
@ -344,19 +377,22 @@ public class GLU {
return 1; 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)]; 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; 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]; float[] matrix = new float[16];
a = (float) (a * Math.PI / 180.0); // convert to radians a = (float)(a * Math.PI / 180.0); // convert to radians
float s = (float) Math.Sin(a); float s = (float)Math.Sin(a);
float c = (float) Math.Cos(a); float c = (float)Math.Cos(a);
float t = 1.0f - c; float t = 1.0f - c;
float tx = t * x; float tx = t * x;
@ -387,10 +423,10 @@ public class GLU {
matrix[14] = 0; matrix[14] = 0;
matrix[15] = 1; matrix[15] = 1;
return matrix; 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 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 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]; 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; return dest;
} }
} }

View File

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

View File

@ -7,7 +7,8 @@ using Silk.NET.Windowing;
namespace DotRecast.Recast.Demo.Draw; namespace DotRecast.Recast.Demo.Draw;
public class ModernOpenGLDraw : OpenGLDraw { public class ModernOpenGLDraw : OpenGLDraw
{
private GL _gl; private GL _gl;
private uint program; private uint program;
private int uniformTexture; private int uniformTexture;
@ -33,34 +34,34 @@ public class ModernOpenGLDraw : OpenGLDraw {
{ {
_gl = gl; _gl = gl;
string NK_SHADER_VERSION = PlatformID.MacOSX == Environment.OSVersion.Platform ? "#version 150\n" : "#version 300 es\n"; string NK_SHADER_VERSION = PlatformID.MacOSX == Environment.OSVersion.Platform ? "#version 150\n" : "#version 300 es\n";
string vertex_shader = NK_SHADER_VERSION + "uniform mat4 ProjMtx;\n"// string vertex_shader = NK_SHADER_VERSION + "uniform mat4 ProjMtx;\n" //
+ "uniform mat4 ViewMtx;\n"// + "uniform mat4 ViewMtx;\n" //
+ "in vec3 Position;\n"// + "in vec3 Position;\n" //
+ "in vec2 TexCoord;\n"// + "in vec2 TexCoord;\n" //
+ "in vec4 Color;\n"// + "in vec4 Color;\n" //
+ "out vec2 Frag_UV;\n"// + "out vec2 Frag_UV;\n" //
+ "out vec4 Frag_Color;\n"// + "out vec4 Frag_Color;\n" //
+ "out float Frag_Depth;\n"// + "out float Frag_Depth;\n" //
+ "void main() {\n"// + "void main() {\n" //
+ " Frag_UV = TexCoord;\n"// + " Frag_UV = TexCoord;\n" //
+ " Frag_Color = Color;\n"// + " Frag_Color = Color;\n" //
+ " vec4 VSPosition = ViewMtx * vec4(Position, 1);\n"// + " vec4 VSPosition = ViewMtx * vec4(Position, 1);\n" //
+ " Frag_Depth = -VSPosition.z;\n"// + " Frag_Depth = -VSPosition.z;\n" //
+ " gl_Position = ProjMtx * VSPosition;\n"// + " gl_Position = ProjMtx * VSPosition;\n" //
+ "}\n"; + "}\n";
string fragment_shader = NK_SHADER_VERSION + "precision mediump float;\n"// string fragment_shader = NK_SHADER_VERSION + "precision mediump float;\n" //
+ "uniform sampler2D Texture;\n"// + "uniform sampler2D Texture;\n" //
+ "uniform float UseTexture;\n"// + "uniform float UseTexture;\n" //
+ "uniform float EnableFog;\n"// + "uniform float EnableFog;\n" //
+ "uniform float FogStart;\n"// + "uniform float FogStart;\n" //
+ "uniform float FogEnd;\n"// + "uniform float FogEnd;\n" //
+ "const vec4 FogColor = vec4(0.3f, 0.3f, 0.32f, 1.0f);\n"// + "const vec4 FogColor = vec4(0.3f, 0.3f, 0.32f, 1.0f);\n" //
+ "in vec2 Frag_UV;\n"// + "in vec2 Frag_UV;\n" //
+ "in vec4 Frag_Color;\n"// + "in vec4 Frag_Color;\n" //
+ "in float Frag_Depth;\n"// + "in float Frag_Depth;\n" //
+ "out vec4 Out_Color;\n"// + "out vec4 Out_Color;\n" //
+ "void main(){\n"// + "void main(){\n" //
+ " Out_Color = mix(FogColor, Frag_Color * mix(vec4(1), texture(Texture, Frag_UV.st), UseTexture), 1.0 - EnableFog * clamp( (Frag_Depth - FogStart) / (FogEnd - FogStart), 0.0, 1.0) );\n"// + " Out_Color = mix(FogColor, Frag_Color * mix(vec4(1), texture(Texture, Frag_UV.st), UseTexture), 1.0 - EnableFog * clamp( (Frag_Depth - FogStart) / (FogEnd - FogStart), 0.0, 1.0) );\n" //
+ "}\n"; + "}\n";
program = _gl.CreateProgram(); program = _gl.CreateProgram();
@ -71,20 +72,26 @@ public class ModernOpenGLDraw : OpenGLDraw {
_gl.CompileShader(vert_shdr); _gl.CompileShader(vert_shdr);
_gl.CompileShader(frag_shdr); _gl.CompileShader(frag_shdr);
gl.GetShader(vert_shdr, GLEnum.CompileStatus, out var status); gl.GetShader(vert_shdr, GLEnum.CompileStatus, out var status);
if (status != (int) GLEnum.True) { if (status != (int)GLEnum.True)
{
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
gl.GetShader(frag_shdr, GLEnum.CompileStatus, out status); gl.GetShader(frag_shdr, GLEnum.CompileStatus, out status);
if (status != (int) GLEnum.True) { if (status != (int)GLEnum.True)
{
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
_gl.AttachShader(program, vert_shdr); _gl.AttachShader(program, vert_shdr);
_gl.AttachShader(program, frag_shdr); _gl.AttachShader(program, frag_shdr);
_gl.LinkProgram(program); _gl.LinkProgram(program);
_gl.GetProgram(program, GLEnum.LinkStatus, out status); _gl.GetProgram(program, GLEnum.LinkStatus, out status);
if (status != (int) GLEnum.True) { if (status != (int)GLEnum.True)
{
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
uniformTexture = _gl.GetUniformLocation(program, "Texture"); uniformTexture = _gl.GetUniformLocation(program, "Texture");
uniformUseTexture = _gl.GetUniformLocation(program, "UseTexture"); uniformUseTexture = _gl.GetUniformLocation(program, "UseTexture");
uniformFog = _gl.GetUniformLocation(program, "EnableFog"); uniformFog = _gl.GetUniformLocation(program, "EnableFog");
@ -92,9 +99,9 @@ public class ModernOpenGLDraw : OpenGLDraw {
uniformFogEnd = _gl.GetUniformLocation(program, "FogEnd"); uniformFogEnd = _gl.GetUniformLocation(program, "FogEnd");
uniformProjectionMatrix = _gl.GetUniformLocation(program, "ProjMtx"); uniformProjectionMatrix = _gl.GetUniformLocation(program, "ProjMtx");
uniformViewMatrix = _gl.GetUniformLocation(program, "ViewMtx"); uniformViewMatrix = _gl.GetUniformLocation(program, "ViewMtx");
uint attrib_pos = (uint) _gl.GetAttribLocation(program, "Position"); uint attrib_pos = (uint)_gl.GetAttribLocation(program, "Position");
uint attrib_uv = (uint) _gl.GetAttribLocation(program, "TexCoord"); uint attrib_uv = (uint)_gl.GetAttribLocation(program, "TexCoord");
uint attrib_col = (uint) _gl.GetAttribLocation(program, "Color"); uint attrib_col = (uint)_gl.GetAttribLocation(program, "Color");
// buffer setup // buffer setup
_gl.GenBuffers(1, out vbo); _gl.GenBuffers(1, out vbo);
@ -123,7 +130,8 @@ public class ModernOpenGLDraw : OpenGLDraw {
_gl.BindVertexArray(0); _gl.BindVertexArray(0);
} }
public void clear() { public void clear()
{
_gl.ClearColor(0.3f, 0.3f, 0.32f, 1.0f); _gl.ClearColor(0.3f, 0.3f, 0.32f, 1.0f);
_gl.Clear((uint)GLEnum.ColorBufferBit | (uint)GLEnum.DepthBufferBit); _gl.Clear((uint)GLEnum.ColorBufferBit | (uint)GLEnum.DepthBufferBit);
_gl.Enable(GLEnum.Blend); _gl.Enable(GLEnum.Blend);
@ -133,14 +141,16 @@ public class ModernOpenGLDraw : OpenGLDraw {
_gl.Enable(GLEnum.CullFace); _gl.Enable(GLEnum.CullFace);
} }
public void begin(DebugDrawPrimitives prim, float size) { public void begin(DebugDrawPrimitives prim, float size)
{
currentPrim = prim; currentPrim = prim;
vertices.Clear(); vertices.Clear();
_gl.LineWidth(size); _gl.LineWidth(size);
_gl.PointSize(size); _gl.PointSize(size);
} }
public void end() { public void end()
{
// if (vertices.isEmpty()) { // if (vertices.isEmpty()) {
// return; // return;
// } // }
@ -217,48 +227,58 @@ public class ModernOpenGLDraw : OpenGLDraw {
// glPointSize(1.0f); // 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)); 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)); 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)); 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)); vertices.Add(new OpenGLVertex(x, y, z, u, v, color));
} }
public void depthMask(bool state) { public void depthMask(bool state)
{
_gl.DepthMask(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; _texture = state ? g_tex : null;
if (_texture != null) { if (_texture != null)
{
_texture.bind(); _texture.bind();
} }
} }
public void projectionMatrix(float[] projectionMatrix) { public void projectionMatrix(float[] projectionMatrix)
{
this._projectionMatrix = projectionMatrix; this._projectionMatrix = projectionMatrix;
} }
public void viewMatrix(float[] viewMatrix) { public void viewMatrix(float[] viewMatrix)
{
this._viewMatrix = viewMatrix; this._viewMatrix = viewMatrix;
} }
public void fog(float start, float end) { public void fog(float start, float end)
{
fogStart = start; fogStart = start;
fogEnd = end; fogEnd = end;
} }
public void fog(bool state) { public void fog(bool state)
{
fogEnabled = state; fogEnabled = state;
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,9 +22,10 @@ using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Recast.Demo.Geom; namespace DotRecast.Recast.Demo.Geom;
public class Intersections { public class Intersections
{
public static float? intersectSegmentTriangle(float[] sp, float[] sq, float[] a, float[] b, float[] c) { public static float? intersectSegmentTriangle(float[] sp, float[] sq, float[] a, float[] b, float[] c)
{
float v, w; float v, w;
float[] ab = vSub(b, a); float[] ab = vSub(b, a);
float[] ac = vSub(c, 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 // Compute denominator d. If d <= 0, segment is parallel to or points
// away from triangle, so exit early // away from triangle, so exit early
float d = DemoMath.vDot(qp, norm); float d = DemoMath.vDot(qp, norm);
if (d <= 0.0f) { if (d <= 0.0f)
{
return null; return null;
} }
@ -46,21 +48,27 @@ public class Intersections {
// dividing by d until intersection has been found to pierce triangle // dividing by d until intersection has been found to pierce triangle
float[] ap = vSub(sp, a); float[] ap = vSub(sp, a);
float t = DemoMath.vDot(ap, norm); float t = DemoMath.vDot(ap, norm);
if (t < 0.0f) { if (t < 0.0f)
{
return null; return null;
} }
if (t > d) {
if (t > d)
{
return null; // For segment; exclude this code line for a ray test return null; // For segment; exclude this code line for a ray test
} }
// Compute barycentric coordinate components and test if within bounds // Compute barycentric coordinate components and test if within bounds
float[] e = DemoMath.vCross(qp, ap); float[] e = DemoMath.vCross(qp, ap);
v = DemoMath.vDot(ac, e); v = DemoMath.vDot(ac, e);
if (v < 0.0f || v > d) { if (v < 0.0f || v > d)
{
return null; return null;
} }
w = -DemoMath.vDot(ab, e); w = -DemoMath.vDot(ab, e);
if (w < 0.0f || v + w > d) { if (w < 0.0f || v + w > d)
{
return null; return null;
} }
@ -70,8 +78,8 @@ public class Intersections {
return t; 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 EPS = 1e-6f;
float[] d = new float[3]; float[] d = new float[3];
@ -81,27 +89,39 @@ public class Intersections {
float tmin = 0.0f; float tmin = 0.0f;
float tmax = 1.0f; float tmax = 1.0f;
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++)
if (Math.Abs(d[i]) < EPS) { {
if (sp[i] < amin[i] || sp[i] > amax[i]) { if (Math.Abs(d[i]) < EPS)
{
if (sp[i] < amin[i] || sp[i] > amax[i])
{
return null; return null;
} }
} else { }
else
{
float ood = 1.0f / d[i]; float ood = 1.0f / d[i];
float t1 = (amin[i] - sp[i]) * ood; float t1 = (amin[i] - sp[i]) * ood;
float t2 = (amax[i] - sp[i]) * ood; float t2 = (amax[i] - sp[i]) * ood;
if (t1 > t2) { if (t1 > t2)
{
float tmp = t1; float tmp = t1;
t1 = t2; t1 = t2;
t2 = tmp; t2 = tmp;
} }
if (t1 > tmin) {
if (t1 > tmin)
{
tmin = t1; tmin = t1;
} }
if (t2 < tmax) {
if (t2 < tmax)
{
tmax = t2; tmax = t2;
} }
if (tmin > tmax) {
if (tmin > tmax)
{
return null; return null;
} }
} }
@ -109,5 +129,4 @@ public class Intersections {
return new float[] { tmin, tmax }; 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 * Simple helper to find an intersection between a ray and a nav mesh
*/ */
public class NavMeshRaycast { public class NavMeshRaycast
{
public static float? raycast(NavMesh mesh, float[] src, float[]dst) { public static float? raycast(NavMesh mesh, float[] src, float[] dst)
for (int t = 0; t < mesh.getMaxTiles(); ++t) { {
for (int t = 0; t < mesh.getMaxTiles(); ++t)
{
MeshTile tile = mesh.getTile(t); MeshTile tile = mesh.getTile(t);
if (tile != null && tile.data != null) { if (tile != null && tile.data != null)
{
float? intersection = raycast(tile, src, dst); float? intersection = raycast(tile, src, dst);
if (null != intersection) { if (null != intersection)
{
return intersection; return intersection;
} }
} }
@ -40,36 +44,50 @@ public class NavMeshRaycast {
return null; return null;
} }
private static float? raycast(MeshTile tile, float[] sp, float[]sq) { private static float? raycast(MeshTile tile, float[] sp, float[] sq)
for (int i = 0; i < tile.data.header.polyCount; ++i) { {
for (int i = 0; i < tile.data.header.polyCount; ++i)
{
Poly p = tile.data.polys[i]; Poly p = tile.data.polys[i];
if (p.getType() == Poly.DT_POLYTYPE_OFFMESH_CONNECTION) { if (p.getType() == Poly.DT_POLYTYPE_OFFMESH_CONNECTION)
{
continue; continue;
} }
PolyDetail pd = tile.data.detailMeshes[i]; PolyDetail pd = tile.data.detailMeshes[i];
if (pd != null) { if (pd != null)
{
float[][] verts = ArrayUtils.Of<float>(3, 3); 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; 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]; 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][0] = tile.data.verts[p.verts[v] * 3];
verts[k][1] = tile.data.verts[p.verts[v] * 3 + 1]; verts[k][1] = tile.data.verts[p.verts[v] * 3 + 1];
verts[k][2] = tile.data.verts[p.verts[v] * 3 + 2]; 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][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][1] = tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 1];
verts[k][2] = tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 2]; 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]); float? intersection = Intersections.intersectSegmentTriangle(sp, sq, verts[0], verts[1], verts[2]);
if (null != intersection) { if (null != intersection)
{
return intersection; return intersection;
} }
} }
} else { }
else
{
// FIXME: Use Poly if PolyDetail is unavailable // FIXME: Use Poly if PolyDetail is unavailable
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -25,13 +25,12 @@ using DotRecast.Detour.Crowd;
using DotRecast.Recast.Demo.Builder; using DotRecast.Recast.Demo.Builder;
using DotRecast.Recast.Demo.Draw; using DotRecast.Recast.Demo.Draw;
using Silk.NET.Windowing; using Silk.NET.Windowing;
using static DotRecast.Recast.Demo.Draw.DebugDraw; using static DotRecast.Recast.Demo.Draw.DebugDraw;
namespace DotRecast.Recast.Demo.Tools; namespace DotRecast.Recast.Demo.Tools;
public class CrowdProfilingTool { public class CrowdProfilingTool
{
private readonly Func<CrowdAgentParams> agentParamsSupplier; private readonly Func<CrowdAgentParams> agentParamsSupplier;
private readonly int[] expandSimOptions = new[] { 1 }; private readonly int[] expandSimOptions = new[] { 1 };
private readonly int[] expandCrowdOptions = new[] { 1 }; private readonly int[] expandCrowdOptions = new[] { 1 };
@ -50,11 +49,13 @@ public class CrowdProfilingTool {
private readonly List<FindRandomPointResult> zones = new(); private readonly List<FindRandomPointResult> zones = new();
private long crowdUpdateTime; private long crowdUpdateTime;
public CrowdProfilingTool(Func<CrowdAgentParams> agentParamsSupplier) { public CrowdProfilingTool(Func<CrowdAgentParams> agentParamsSupplier)
{
this.agentParamsSupplier = agentParamsSupplier; this.agentParamsSupplier = agentParamsSupplier;
} }
public void layout(IWindow ctx) { public void layout(IWindow ctx)
{
// nk_layout_row_dynamic(ctx, 1, 1); // nk_layout_row_dynamic(ctx, 1, 1);
// nk_spacing(ctx, 1); // nk_spacing(ctx, 1);
// if (nk_tree_state_push(ctx, 0, "Simulation Options", expandSimOptions)) { // 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); Result<FindRandomPointResult> result = navquery.findRandomPoint(filter, rnd);
if (result.succeeded()) { if (result.succeeded())
{
pos = result.result.getRandomPt(); pos = result.result.getRandomPt();
} }
return pos; return pos;
} }
private float[] getVillagerPosition(NavMeshQuery navquery, QueryFilter filter, float[] pos) { private float[] getVillagerPosition(NavMeshQuery navquery, QueryFilter filter, float[] pos)
if (0 < zones.Count) { {
int zone = (int) (rnd.frand() * zones.Count); if (0 < zones.Count)
{
int zone = (int)(rnd.frand() * zones.Count);
Result<FindRandomPointResult> result = navquery.findRandomPointWithinCircle(zones[zone].getRandomRef(), Result<FindRandomPointResult> result = navquery.findRandomPointWithinCircle(zones[zone].getRandomRef(),
zones[zone].getRandomPt(), zoneRadius[0], filter, rnd); zones[zone].getRandomPt(), zoneRadius[0], filter, rnd);
if (result.succeeded()) { if (result.succeeded())
{
pos = result.result.getRandomPt(); pos = result.result.getRandomPt();
} }
} }
return pos; return pos;
} }
private void createZones() { private void createZones()
{
zones.Clear(); zones.Clear();
QueryFilter filter = new DefaultQueryFilter(); QueryFilter filter = new DefaultQueryFilter();
NavMeshQuery navquery = new NavMeshQuery(navMesh); 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; 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); Result<FindRandomPointResult> result = navquery.findRandomPoint(filter, rnd);
if (result.succeeded()) { if (result.succeeded())
{
bool valid = true; bool valid = true;
foreach (FindRandomPointResult zone in zones) { foreach (FindRandomPointResult zone in zones)
if (DemoMath.vDistSqr(zone.getRandomPt(), result.result.getRandomPt(), 0) < zoneSeparation) { {
if (DemoMath.vDistSqr(zone.getRandomPt(), result.result.getRandomPt(), 0) < zoneSeparation)
{
valid = false; valid = false;
break; break;
} }
} }
if (valid) {
if (valid)
{
zones.Add(result.result); zones.Add(result.result);
break; 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, crowd = new Crowd(config, navMesh, __ => new DefaultQueryFilter(SampleAreaModifications.SAMPLE_POLYFLAGS_ALL,
SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED, new float[] { 1f, 10f, 1f, 1f, 2f, 1.5f })); SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED, new float[] { 1f, 10f, 1f, 1f, 2f, 1.5f }));
@ -217,22 +234,28 @@ public class CrowdProfilingTool {
crowd.setObstacleAvoidanceParams(3, option); crowd.setObstacleAvoidanceParams(3, option);
} }
public void update(float dt) { public void update(float dt)
{
long startTime = Stopwatch.GetTimestamp(); long startTime = Stopwatch.GetTimestamp();
if (crowd != null) { if (crowd != null)
{
crowd.config().pathQueueSize = pathQueueSize[0]; crowd.config().pathQueueSize = pathQueueSize[0];
crowd.config().maxFindPathIterations = maxIterations[0]; crowd.config().maxFindPathIterations = maxIterations[0];
crowd.update(dt, null); crowd.update(dt, null);
} }
long endTime = Stopwatch.GetTimestamp();
if (crowd != null) {
long endTime = Stopwatch.GetTimestamp();
if (crowd != null)
{
NavMeshQuery navquery = new NavMeshQuery(navMesh); NavMeshQuery navquery = new NavMeshQuery(navMesh);
QueryFilter filter = new DefaultQueryFilter(); QueryFilter filter = new DefaultQueryFilter();
foreach (CrowdAgent ag in crowd.getActiveAgents()) { foreach (CrowdAgent ag in crowd.getActiveAgents())
if (needsNewTarget(ag)) { {
AgentData agentData = (AgentData) ag.option.userData; if (needsNewTarget(ag))
switch (agentData.type) { {
AgentData agentData = (AgentData)ag.option.userData;
switch (agentData.type)
{
case AgentType.MOB: case AgentType.MOB:
moveMob(navquery, filter, ag, agentData); moveMob(navquery, filter, ag, agentData);
break; break;
@ -246,92 +269,119 @@ public class CrowdProfilingTool {
} }
} }
} }
crowdUpdateTime = (endTime - startTime) / 1_000_000; 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 // Move somewhere
Result<FindNearestPolyResult> nearestPoly = navquery.findNearestPoly(ag.npos, crowd.getQueryExtents(), filter); Result<FindNearestPolyResult> nearestPoly = navquery.findNearestPoly(ag.npos, crowd.getQueryExtents(), filter);
if (nearestPoly.succeeded()) { if (nearestPoly.succeeded())
{
Result<FindRandomPointResult> result = navquery.findRandomPointAroundCircle(nearestPoly.result.getNearestRef(), Result<FindRandomPointResult> result = navquery.findRandomPointAroundCircle(nearestPoly.result.getNearestRef(),
agentData.home, zoneRadius[0] * 2f, filter, rnd); agentData.home, zoneRadius[0] * 2f, filter, rnd);
if (result.succeeded()) { if (result.succeeded())
{
crowd.requestMoveTarget(ag, result.result.getRandomRef(), result.result.getRandomPt()); 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 // Move somewhere close
Result<FindNearestPolyResult> nearestPoly = navquery.findNearestPoly(ag.npos, crowd.getQueryExtents(), filter); Result<FindNearestPolyResult> nearestPoly = navquery.findNearestPoly(ag.npos, crowd.getQueryExtents(), filter);
if (nearestPoly.succeeded()) { if (nearestPoly.succeeded())
{
Result<FindRandomPointResult> result = navquery.findRandomPointAroundCircle(nearestPoly.result.getNearestRef(), Result<FindRandomPointResult> result = navquery.findRandomPointAroundCircle(nearestPoly.result.getNearestRef(),
agentData.home, zoneRadius[0] * 0.2f, filter, rnd); agentData.home, zoneRadius[0] * 0.2f, filter, rnd);
if (result.succeeded()) { if (result.succeeded())
{
crowd.requestMoveTarget(ag, result.result.getRandomRef(), result.result.getRandomPt()); 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 // Move to another zone
List<FindRandomPointResult> potentialTargets = new(); List<FindRandomPointResult> potentialTargets = new();
foreach (FindRandomPointResult zone in zones) { foreach (FindRandomPointResult zone in zones)
if (DemoMath.vDistSqr(zone.getRandomPt(), ag.npos, 0) > zoneRadius[0] * zoneRadius[0]) { {
if (DemoMath.vDistSqr(zone.getRandomPt(), ag.npos, 0) > zoneRadius[0] * zoneRadius[0])
{
potentialTargets.Add(zone); potentialTargets.Add(zone);
} }
} }
if (0 < potentialTargets.Count) {
if (0 < potentialTargets.Count)
{
potentialTargets.Shuffle(); potentialTargets.Shuffle();
crowd.requestMoveTarget(ag, potentialTargets[0].getRandomRef(), potentialTargets[0].getRandomPt()); 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 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; 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 dx = ag.targetPos[0] - ag.npos[0];
float dy = ag.targetPos[1] - ag.npos[1]; float dy = ag.targetPos[1] - ag.npos[1];
float dz = ag.targetPos[2] - ag.npos[2]; float dz = ag.targetPos[2] - ag.npos[2];
return dx * dx + dy * dy + dz * dz < 0.3f; return dx * dx + dy * dy + dz * dz < 0.3f;
} }
return false; return false;
} }
public void setup(float maxAgentRadius, NavMesh nav) { public void setup(float maxAgentRadius, NavMesh nav)
{
navMesh = nav; navMesh = nav;
if (nav != null) { if (nav != null)
{
config = new CrowdConfig(maxAgentRadius); config = new CrowdConfig(maxAgentRadius);
} }
} }
public void handleRender(NavMeshRenderer renderer) { public void handleRender(NavMeshRenderer renderer)
{
RecastDebugDraw dd = renderer.getDebugDraw(); RecastDebugDraw dd = renderer.getDebugDraw();
dd.depthMask(false); dd.depthMask(false);
if (crowd != null) { if (crowd != null)
foreach (CrowdAgent ag in crowd.getActiveAgents()) { {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
float radius = ag.option.radius; float radius = ag.option.radius;
float[] pos = ag.npos; float[] pos = ag.npos;
dd.debugDrawCircle(pos[0], pos[1], pos[2], radius, duRGBA(0, 0, 0, 32), 2.0f); 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; {
AgentData agentData = (AgentData)ag.option.userData;
float height = ag.option.height; float height = ag.option.height;
float radius = ag.option.radius; float radius = ag.option.radius;
float[] pos = ag.npos; float[] pos = ag.npos;
int col = duRGBA(220, 220, 220, 128); int col = duRGBA(220, 220, 220, 128);
if (agentData.type == AgentType.TRAVELLER) { if (agentData.type == AgentType.TRAVELLER)
{
col = duRGBA(100, 160, 100, 128); col = duRGBA(100, 160, 100, 128);
} }
if (agentData.type == AgentType.VILLAGER) {
if (agentData.type == AgentType.VILLAGER)
{
col = duRGBA(120, 80, 160, 128); col = duRGBA(120, 80, 160, 128);
} }
if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_REQUESTING if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_REQUESTING
|| ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE) || ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE)
col = duLerpCol(col, duRGBA(255, 255, 32, 128), 128); col = duLerpCol(col, duRGBA(255, 255, 32, 128), 128);
@ -350,30 +400,38 @@ public class CrowdProfilingTool {
dd.depthMask(true); dd.depthMask(true);
} }
private CrowdAgent addAgent(float[] p, AgentType type) { private CrowdAgent addAgent(float[] p, AgentType type)
{
CrowdAgentParams ap = agentParamsSupplier.Invoke(); CrowdAgentParams ap = agentParamsSupplier.Invoke();
ap.userData = new AgentData(type, p); ap.userData = new AgentData(type, p);
return crowd.addAgent(p, ap); return crowd.addAgent(p, ap);
} }
public enum AgentType { public enum AgentType
VILLAGER, TRAVELLER, MOB, {
VILLAGER,
TRAVELLER,
MOB,
} }
private class AgentData { private class AgentData
{
public readonly AgentType type; public readonly AgentType type;
public readonly float[] home = new float[3]; public readonly float[] home = new float[3];
public AgentData(AgentType type, float[] home) { public AgentData(AgentType type, float[] home)
{
this.type = type; this.type = type;
RecastVectors.copy(this.home, home); RecastVectors.copy(this.home, home);
} }
} }
public void updateAgentParams(int updateFlags, int obstacleAvoidanceType, float separationWeight) { public void updateAgentParams(int updateFlags, int obstacleAvoidanceType, float separationWeight)
if (crowd != null) { {
foreach (CrowdAgent ag in crowd.getActiveAgents()) { if (crowd != null)
{
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
CrowdAgentParams option = new CrowdAgentParams(); CrowdAgentParams option = new CrowdAgentParams();
option.radius = ag.option.radius; option.radius = ag.option.radius;
option.height = ag.option.height; 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.Builder;
using DotRecast.Recast.Demo.Draw; using DotRecast.Recast.Demo.Draw;
using DotRecast.Recast.Demo.Geom; using DotRecast.Recast.Demo.Geom;
using static DotRecast.Recast.Demo.Draw.DebugDraw; using static DotRecast.Recast.Demo.Draw.DebugDraw;
using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives; using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives;
namespace DotRecast.Recast.Demo.Tools; namespace DotRecast.Recast.Demo.Tools;
public class CrowdTool : Tool { public class CrowdTool : Tool
{
private enum ToolMode { private enum ToolMode
CREATE, MOVE_TARGET, SELECT, TOGGLE_POLYS, PROFILING {
CREATE,
MOVE_TARGET,
SELECT,
TOGGLE_POLYS,
PROFILING
} }
private readonly CrowdToolParams toolParams = new CrowdToolParams(); private readonly CrowdToolParams toolParams = new CrowdToolParams();
@ -48,7 +52,8 @@ public class CrowdTool : Tool {
private static readonly int AGENT_MAX_TRAIL = 64; private static readonly int AGENT_MAX_TRAIL = 64;
private class AgentTrail { private class AgentTrail
{
public float[] trail = new float[AGENT_MAX_TRAIL * 3]; public float[] trail = new float[AGENT_MAX_TRAIL * 3];
public int htrail; public int htrail;
}; };
@ -59,19 +64,23 @@ public class CrowdTool : Tool {
private ToolMode m_mode = ToolMode.CREATE; private ToolMode m_mode = ToolMode.CREATE;
private long crowdUpdateTime; private long crowdUpdateTime;
public CrowdTool() { public CrowdTool()
{
m_agentDebug.vod = new ObstacleAvoidanceDebugData(2048); m_agentDebug.vod = new ObstacleAvoidanceDebugData(2048);
profilingTool = new CrowdProfilingTool(getAgentParams); profilingTool = new CrowdProfilingTool(getAgentParams);
} }
public override void setSample(Sample psample) { public override void setSample(Sample psample)
if (sample != psample) { {
if (sample != psample)
{
sample = psample; sample = psample;
} }
NavMesh nav = sample.getNavMesh(); NavMesh nav = sample.getNavMesh();
if (nav != null && m_nav != nav) { if (nav != null && m_nav != nav)
{
m_nav = nav; m_nav = nav;
CrowdConfig config = new CrowdConfig(sample.getSettingsUI().getAgentRadius()); 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) { public override void handleClick(float[] s, float[] p, bool shift)
if (m_mode == ToolMode.PROFILING) { {
if (m_mode == ToolMode.PROFILING)
{
return; return;
} }
if (crowd == null) {
if (crowd == null)
{
return; return;
} }
if (m_mode == ToolMode.CREATE) {
if (shift) { if (m_mode == ToolMode.CREATE)
{
if (shift)
{
// Delete // Delete
CrowdAgent ahit = hitTestAgents(s, p); CrowdAgent ahit = hitTestAgents(s, p);
if (ahit != null) { if (ahit != null)
{
removeAgent(ahit); removeAgent(ahit);
} }
} else { }
else
{
// Add // Add
addAgent(p); addAgent(p);
} }
} else if (m_mode == ToolMode.MOVE_TARGET) { }
else if (m_mode == ToolMode.MOVE_TARGET)
{
setMoveTarget(p, shift); setMoveTarget(p, shift);
} else if (m_mode == ToolMode.SELECT) { }
else if (m_mode == ToolMode.SELECT)
{
// Highlight // Highlight
CrowdAgent ahit = hitTestAgents(s, p); CrowdAgent ahit = hitTestAgents(s, p);
hilightAgent(ahit); hilightAgent(ahit);
} else if (m_mode == ToolMode.TOGGLE_POLYS) { }
else if (m_mode == ToolMode.TOGGLE_POLYS)
{
NavMesh nav = sample.getNavMesh(); NavMesh nav = sample.getNavMesh();
NavMeshQuery navquery = sample.getNavMeshQuery(); NavMeshQuery navquery = sample.getNavMeshQuery();
if (nav != null && navquery != null) { if (nav != null && navquery != null)
{
QueryFilter filter = new DefaultQueryFilter(); QueryFilter filter = new DefaultQueryFilter();
float[] halfExtents = crowd.getQueryExtents(); float[] halfExtents = crowd.getQueryExtents();
Result<FindNearestPolyResult> result = navquery.findNearestPoly(p, halfExtents, filter); Result<FindNearestPolyResult> result = navquery.findNearestPoly(p, halfExtents, filter);
long refs = result.result.getNearestRef(); long refs = result.result.getNearestRef();
if (refs != 0) { if (refs != 0)
{
Result<int> flags = nav.getPolyFlags(refs); Result<int> flags = nav.getPolyFlags(refs);
if (flags.succeeded()) { if (flags.succeeded())
{
nav.setPolyFlags(refs, flags.result ^ SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED); 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); crowd.removeAgent(agent);
if (agent == m_agentDebug.agent) { if (agent == m_agentDebug.agent)
{
m_agentDebug.agent = null; m_agentDebug.agent = null;
} }
} }
private void addAgent(float[] p) { private void addAgent(float[] p)
{
CrowdAgentParams ap = getAgentParams(); CrowdAgentParams ap = getAgentParams();
CrowdAgent ag = crowd.addAgent(p, ap); CrowdAgent ag = crowd.addAgent(p, ap);
if (ag != null) { if (ag != null)
{
if (m_targetRef != 0) if (m_targetRef != 0)
crowd.requestMoveTarget(ag, m_targetRef, m_targetPos); crowd.requestMoveTarget(ag, m_targetRef, m_targetPos);
@ -178,17 +210,20 @@ public class CrowdTool : Tool {
trail = new AgentTrail(); trail = new AgentTrail();
m_trails.Add(ag.idx, trail); 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] = p[0];
trail.trail[i * 3 + 1] = p[1]; trail.trail[i * 3 + 1] = p[1];
trail.trail[i * 3 + 2] = p[2]; trail.trail[i * 3 + 2] = p[2];
} }
trail.htrail = 0; trail.htrail = 0;
} }
} }
private CrowdAgentParams getAgentParams() { private CrowdAgentParams getAgentParams()
{
CrowdAgentParams ap = new CrowdAgentParams(); CrowdAgentParams ap = new CrowdAgentParams();
ap.radius = sample.getSettingsUI().getAgentRadius(); ap.radius = sample.getSettingsUI().getAgentRadius();
ap.height = sample.getSettingsUI().getAgentHeight(); ap.height = sample.getSettingsUI().getAgentHeight();
@ -202,18 +237,21 @@ public class CrowdTool : Tool {
return ap; return ap;
} }
private CrowdAgent hitTestAgents(float[] s, float[] p) { private CrowdAgent hitTestAgents(float[] s, float[] p)
{
CrowdAgent isel = null; CrowdAgent isel = null;
float tsel = float.MaxValue; 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]; float[] bmin = new float[3], bmax = new float[3];
getAgentBounds(ag, bmin, bmax); getAgentBounds(ag, bmin, bmax);
float[] isect = Intersections.intersectSegmentAABB(s, p, bmin, bmax); float[] isect = Intersections.intersectSegmentAABB(s, p, bmin, bmax);
if (null != isect) { if (null != isect)
{
float tmin = isect[0]; float tmin = isect[0];
if (tmin > 0 && tmin < tsel) { if (tmin > 0 && tmin < tsel)
{
isel = ag; isel = ag;
tsel = tmin; tsel = tmin;
} }
@ -223,7 +261,8 @@ public class CrowdTool : Tool {
return isel; 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[] p = ag.npos;
float r = ag.option.radius; float r = ag.option.radius;
float h = ag.option.height; float h = ag.option.height;
@ -235,7 +274,8 @@ public class CrowdTool : Tool {
bmax[2] = p[2] + r; bmax[2] = p[2] + r;
} }
private void setMoveTarget(float[] p, bool adjust) { private void setMoveTarget(float[] p, bool adjust)
{
if (sample == null || crowd == null) if (sample == null || crowd == null)
return; return;
@ -244,65 +284,85 @@ public class CrowdTool : Tool {
QueryFilter filter = crowd.getFilter(0); QueryFilter filter = crowd.getFilter(0);
float[] halfExtents = crowd.getQueryExtents(); float[] halfExtents = crowd.getQueryExtents();
if (adjust) { if (adjust)
{
// Request velocity // 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); float[] vel = calcVel(m_agentDebug.agent.npos, p, m_agentDebug.agent.option.maxSpeed);
crowd.requestMoveVelocity(m_agentDebug.agent, vel); 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); float[] vel = calcVel(ag.npos, p, ag.option.maxSpeed);
crowd.requestMoveVelocity(ag, vel); crowd.requestMoveVelocity(ag, vel);
} }
} }
} else { }
else
{
Result<FindNearestPolyResult> result = navquery.findNearestPoly(p, halfExtents, filter); Result<FindNearestPolyResult> result = navquery.findNearestPoly(p, halfExtents, filter);
m_targetRef = result.result.getNearestRef(); m_targetRef = result.result.getNearestRef();
m_targetPos = result.result.getNearestPos(); m_targetPos = result.result.getNearestPos();
if (m_agentDebug.agent != null) { if (m_agentDebug.agent != null)
{
crowd.requestMoveTarget(m_agentDebug.agent, m_targetRef, m_targetPos); 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); 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); float[] vel = DetourCommon.vSub(tgt, pos);
vel[1] = 0.0f; vel[1] = 0.0f;
DetourCommon.vNormalize(vel); DetourCommon.vNormalize(vel);
return DetourCommon.vScale(vel, speed); return DetourCommon.vScale(vel, speed);
} }
public override void handleRender(NavMeshRenderer renderer) { public override void handleRender(NavMeshRenderer renderer)
if (m_mode == ToolMode.PROFILING) { {
if (m_mode == ToolMode.PROFILING)
{
profilingTool.handleRender(renderer); profilingTool.handleRender(renderer);
return; return;
} }
RecastDebugDraw dd = renderer.getDebugDraw(); RecastDebugDraw dd = renderer.getDebugDraw();
float rad = sample.getSettingsUI().getAgentRadius(); float rad = sample.getSettingsUI().getAgentRadius();
NavMesh nav = sample.getNavMesh(); NavMesh nav = sample.getNavMesh();
if (nav == null || crowd == null) if (nav == null || crowd == null)
return; return;
if (toolParams.m_showNodes && crowd.getPathQueue() != null) { if (toolParams.m_showNodes && crowd.getPathQueue() != null)
{
// NavMeshQuery navquery = crowd.getPathQueue().getNavQuery(); // NavMeshQuery navquery = crowd.getPathQueue().getNavQuery();
// if (navquery != null) { // if (navquery != null) {
// dd.debugDrawNavMeshNodes(navquery); // dd.debugDrawNavMeshNodes(navquery);
// } // }
} }
dd.depthMask(false); dd.depthMask(false);
// Draw paths // Draw paths
if (toolParams.m_showPath) { if (toolParams.m_showPath)
foreach (CrowdAgent ag in crowd.getActiveAgents()) { {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
if (!toolParams.m_showDetailAll && ag != m_agentDebug.agent) if (!toolParams.m_showDetailAll && ag != m_agentDebug.agent)
continue; continue;
List<long> path = ag.corridor.getPath(); List<long> path = ag.corridor.getPath();
int npath = ag.corridor.getPathCount(); 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)); 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); dd.debugDrawCross(m_targetPos[0], m_targetPos[1] + 0.1f, m_targetPos[2], rad, duRGBA(255, 255, 255, 192), 2.0f);
// Occupancy grid. // Occupancy grid.
if (toolParams.m_showGrid) { if (toolParams.m_showGrid)
{
float gridy = -float.MaxValue; float gridy = -float.MaxValue;
foreach (CrowdAgent ag in crowd.getActiveAgents()) { foreach (CrowdAgent ag in crowd.getActiveAgents())
{
float[] pos = ag.corridor.getPos(); float[] pos = ag.corridor.getPos();
gridy = Math.Max(gridy, pos[1]); gridy = Math.Max(gridy, pos[1]);
} }
gridy += 1.0f; gridy += 1.0f;
dd.begin(QUADS); dd.begin(QUADS);
ProximityGrid grid = crowd.getGrid(); ProximityGrid grid = crowd.getGrid();
float cs = grid.getCellSize(); float cs = grid.getCellSize();
foreach (int[] ic in grid.getItemCounts()) { foreach (int[] ic in grid.getItemCounts())
{
int x = ic[0]; int x = ic[0];
int y = ic[1]; int y = ic[1];
int count = ic[2]; int count = ic[2];
if (count != 0) { if (count != 0)
{
int col = duRGBA(128, 0, 0, Math.Min(count * 40, 255)); 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, col);
dd.vertex(x * cs, gridy, y * cs + 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.vertex(x * cs + cs, gridy, y * cs, col);
} }
} }
dd.end(); dd.end();
} }
// Trail // Trail
foreach (CrowdAgent ag in crowd.getActiveAgents()) { foreach (CrowdAgent ag in crowd.getActiveAgents())
{
AgentTrail trail = m_trails[ag.idx]; AgentTrail trail = m_trails[ag.idx];
float[] pos = ag.npos; float[] pos = ag.npos;
@ -348,38 +414,45 @@ public class CrowdTool : Tool {
float[] prev = new float[3]; float[] prev = new float[3];
float preva = 1; float preva = 1;
DetourCommon.vCopy(prev, pos); 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 idx = (trail.htrail + AGENT_MAX_TRAIL - j) % AGENT_MAX_TRAIL;
int v = idx * 3; int v = idx * 3;
float a = 1 - j / (float) AGENT_MAX_TRAIL; float a = 1 - j / (float)AGENT_MAX_TRAIL;
dd.vertex(prev[0], prev[1] + 0.1f, prev[2], duRGBA(0, 0, 0, (int) (128 * preva))); dd.vertex(prev[0], prev[1] + 0.1f, prev[2], duRGBA(0, 0, 0, (int)(128 * preva)));
dd.vertex(trail.trail[v], trail.trail[v + 1] + 0.1f, trail.trail[v + 2], duRGBA(0, 0, 0, (int) (128 * a))); dd.vertex(trail.trail[v], trail.trail[v + 1] + 0.1f, trail.trail[v + 2], duRGBA(0, 0, 0, (int)(128 * a)));
preva = a; preva = a;
DetourCommon.vCopy(prev, trail.trail, v); DetourCommon.vCopy(prev, trail.trail, v);
} }
dd.end();
dd.end();
} }
// Corners & co // Corners & co
foreach (CrowdAgent ag in crowd.getActiveAgents()) { foreach (CrowdAgent ag in crowd.getActiveAgents())
{
if (toolParams.m_showDetailAll == false && ag != m_agentDebug.agent) if (toolParams.m_showDetailAll == false && ag != m_agentDebug.agent)
continue; continue;
float radius = ag.option.radius; float radius = ag.option.radius;
float[] pos = ag.npos; float[] pos = ag.npos;
if (toolParams.m_showCorners) { if (toolParams.m_showCorners)
if (0 < ag.corners.Count) { {
if (0 < ag.corners.Count)
{
dd.begin(LINES, 2.0f); 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[] va = j == 0 ? pos : ag.corners[j - 1].getPos();
float[] vb = ag.corners[j].getPos(); float[] vb = ag.corners[j].getPos();
dd.vertex(va[0], va[1] + radius, va[2], duRGBA(128, 0, 0, 192)); 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)); dd.vertex(vb[0], vb[1] + radius, vb[2], duRGBA(128, 0, 0, 192));
} }
if ((ag.corners[ag.corners.Count - 1].getFlags() 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(); 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], v[2], duRGBA(192, 0, 0, 192));
dd.vertex(v[0], v[1] + radius * 2, 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(); dd.end();
if (toolParams.m_anticipateTurns) { if (toolParams.m_anticipateTurns)
{
/* float dvel[3], pos[3]; /* float dvel[3], pos[3];
calcSmoothSteerDirection(ag.pos, ag.cornerVerts, ag.ncorners, dvel); calcSmoothSteerDirection(ag.pos, ag.cornerVerts, ag.ncorners, dvel);
pos[0] = ag.pos[0] + dvel[0]; 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(); float[] center = ag.boundary.getCenter();
dd.debugDrawCross(center[0], center[1] + radius, center[2], 0.2f, duRGBA(192, 0, 128, 255), 2.0f); 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, dd.debugDrawCircle(center[0], center[1] + radius, center[2], ag.option.collisionQueryRange,
duRGBA(192, 0, 128, 128), 2.0f); duRGBA(192, 0, 128, 128), 2.0f);
dd.begin(LINES, 3.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); int col = duRGBA(192, 0, 128, 192);
float[] s = ag.boundary.getSegment(j); float[] s = ag.boundary.getSegment(j);
float[] s0 = new float[] { s[0], s[1], s[2] }; 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.appendArrow(s[0], s[1] + 0.2f, s[2], s[3], s[4] + 0.2f, s[5], 0.0f, 0.3f, col);
} }
dd.end(); 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), dd.debugDrawCircle(pos[0], pos[1] + radius, pos[2], ag.option.collisionQueryRange, duRGBA(0, 192, 128, 128),
2.0f); 2.0f);
dd.begin(LINES, 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; 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(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.vertex(nei.npos[0], nei.npos[1] + radius, nei.npos[2], duRGBA(0, 192, 128, 128));
} }
} }
dd.end(); dd.end();
} }
if (toolParams.m_showOpt) { if (toolParams.m_showOpt)
{
dd.begin(LINES, 2.0f); dd.begin(LINES, 2.0f);
dd.vertex(m_agentDebug.optStart[0], m_agentDebug.optStart[1] + 0.3f, m_agentDebug.optStart[2], dd.vertex(m_agentDebug.optStart[0], m_agentDebug.optStart[1] + 0.3f, m_agentDebug.optStart[2],
duRGBA(0, 128, 0, 192)); duRGBA(0, 128, 0, 192));
@ -456,8 +538,8 @@ public class CrowdTool : Tool {
} }
// Agent cylinders. // Agent cylinders.
foreach (CrowdAgent ag in crowd.getActiveAgents()) { foreach (CrowdAgent ag in crowd.getActiveAgents())
{
float radius = ag.option.radius; float radius = ag.option.radius;
float[] pos = ag.npos; float[] pos = ag.npos;
@ -468,8 +550,8 @@ public class CrowdTool : Tool {
dd.debugDrawCircle(pos[0], pos[1], pos[2], radius, col, 2.0f); 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 height = ag.option.height;
float radius = ag.option.radius; float radius = ag.option.radius;
float[] pos = ag.npos; float[] pos = ag.npos;
@ -489,8 +571,10 @@ public class CrowdTool : Tool {
pos[2] + radius, col); pos[2] + radius, col);
} }
if (toolParams.m_showVO) { if (toolParams.m_showVO)
foreach (CrowdAgent ag in crowd.getActiveAgents()) { {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
if (toolParams.m_showDetailAll == false && ag != m_agentDebug.agent) if (toolParams.m_showDetailAll == false && ag != m_agentDebug.agent)
continue; continue;
@ -504,25 +588,27 @@ public class CrowdTool : Tool {
dd.debugDrawCircle(dx, dy, dz, ag.option.maxSpeed, duRGBA(255, 255, 255, 64), 2.0f); dd.debugDrawCircle(dx, dy, dz, ag.option.maxSpeed, duRGBA(255, 255, 255, 64), 2.0f);
dd.begin(QUADS); 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[] p = vod.getSampleVelocity(j);
float sr = vod.getSampleSize(j); float sr = vod.getSampleSize(j);
float pen = vod.getSamplePenalty(j); float pen = vod.getSamplePenalty(j);
float pen2 = vod.getSamplePreferredSidePenalty(j); float pen2 = vod.getSamplePreferredSidePenalty(j);
int col = duLerpCol(duRGBA(255, 255, 255, 220), duRGBA(128, 96, 0, 220), (int) (pen * 255)); int col = duLerpCol(duRGBA(255, 255, 255, 220), duRGBA(128, 96, 0, 220), (int)(pen * 255));
col = duLerpCol(col, duRGBA(128, 0, 0, 220), (int) (pen2 * 128)); col = duLerpCol(col, duRGBA(128, 0, 0, 220), (int)(pen2 * 128));
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.vertex(dx + p[0] - sr, dy, dz + p[2] + sr, col); 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.vertex(dx + p[0] + sr, dy, dz + p[2] + sr, col);
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(); dd.end();
} }
} }
// Velocity stuff. // Velocity stuff.
foreach (CrowdAgent ag in crowd.getActiveAgents()) { foreach (CrowdAgent ag in crowd.getActiveAgents())
{
float radius = ag.option.radius; float radius = ag.option.radius;
float height = ag.option.height; float height = ag.option.height;
float[] pos = ag.npos; float[] pos = ag.npos;
@ -552,15 +638,19 @@ public class CrowdTool : Tool {
dd.depthMask(true); dd.depthMask(true);
} }
public override void handleUpdate(float dt) { public override void handleUpdate(float dt)
{
updateTick(dt); updateTick(dt);
} }
private void updateTick(float dt) { private void updateTick(float dt)
if (m_mode == ToolMode.PROFILING) { {
if (m_mode == ToolMode.PROFILING)
{
profilingTool.update(dt); profilingTool.update(dt);
return; return;
} }
if (crowd == null) if (crowd == null)
return; return;
NavMesh nav = sample.getNavMesh(); NavMesh nav = sample.getNavMesh();
@ -572,7 +662,8 @@ public class CrowdTool : Tool {
long endTime = Stopwatch.GetTimestamp(); long endTime = Stopwatch.GetTimestamp();
// Update agent trails // Update agent trails
foreach (CrowdAgent ag in crowd.getActiveAgents()) { foreach (CrowdAgent ag in crowd.getActiveAgents())
{
AgentTrail trail = m_trails[ag.idx]; AgentTrail trail = m_trails[ag.idx];
// Update agent movement trail. // Update agent movement trail.
trail.htrail = (trail.htrail + 1) % AGENT_MAX_TRAIL; trail.htrail = (trail.htrail + 1) % AGENT_MAX_TRAIL;
@ -587,11 +678,13 @@ public class CrowdTool : Tool {
crowdUpdateTime = (endTime - startTime) / 1_000_000; crowdUpdateTime = (endTime - startTime) / 1_000_000;
} }
private void hilightAgent(CrowdAgent agent) { private void hilightAgent(CrowdAgent agent)
{
m_agentDebug.agent = agent; m_agentDebug.agent = agent;
} }
public override void layout(IWindow ctx) { public override void layout(IWindow ctx)
{
// ToolMode previousToolMode = m_mode; // ToolMode previousToolMode = m_mode;
// nk_layout_row_dynamic(ctx, 20, 1); // nk_layout_row_dynamic(ctx, 20, 1);
// if (nk_option_label(ctx, "Create Agents", m_mode == ToolMode.CREATE)) { // if (nk_option_label(ctx, "Create Agents", m_mode == ToolMode.CREATE)) {
@ -683,14 +776,17 @@ public class CrowdTool : Tool {
// } // }
} }
private void updateAgentParams() { private void updateAgentParams()
if (crowd == null) { {
if (crowd == null)
{
return; return;
} }
int updateFlags = getUpdateFlags(); int updateFlags = getUpdateFlags();
profilingTool.updateAgentParams(updateFlags, toolParams.m_obstacleAvoidanceType[0], toolParams.m_separationWeight[0]); 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(); CrowdAgentParams option = new CrowdAgentParams();
option.radius = ag.option.radius; option.radius = ag.option.radius;
option.height = ag.option.height; option.height = ag.option.height;
@ -708,28 +804,39 @@ public class CrowdTool : Tool {
} }
} }
private int getUpdateFlags() { private int getUpdateFlags()
{
int updateFlags = 0; int updateFlags = 0;
if (toolParams.m_anticipateTurns) { if (toolParams.m_anticipateTurns)
{
updateFlags |= CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS; updateFlags |= CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS;
} }
if (toolParams.m_optimizeVis) {
if (toolParams.m_optimizeVis)
{
updateFlags |= CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS; updateFlags |= CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS;
} }
if (toolParams.m_optimizeTopo) {
if (toolParams.m_optimizeTopo)
{
updateFlags |= CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO; updateFlags |= CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
} }
if (toolParams.m_obstacleAvoidance) {
if (toolParams.m_obstacleAvoidance)
{
updateFlags |= CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE; updateFlags |= CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
} }
if (toolParams.m_separation) {
if (toolParams.m_separation)
{
updateFlags |= CrowdAgentParams.DT_CROWD_SEPARATION; updateFlags |= CrowdAgentParams.DT_CROWD_SEPARATION;
} }
return updateFlags; return updateFlags;
} }
public override string getName() { public override string getName()
{
return "Crowd"; return "Crowd";
} }
} }

View File

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

View File

@ -6,9 +6,9 @@ namespace DotRecast.Recast.Demo.Tools;
public static class DemoObjImporter public static class DemoObjImporter
{ {
public static DemoInputGeomProvider load(byte[] chunk) { public static DemoInputGeomProvider load(byte[] chunk)
{
var context = ObjImporter.loadContext(chunk); var context = ObjImporter.loadContext(chunk);
return new DemoInputGeomProvider(context.vertexPositions, context.meshFaces); 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.Tools.Gizmos;
using DotRecast.Recast.Demo.UI; using DotRecast.Recast.Demo.UI;
using Silk.NET.Windowing; using Silk.NET.Windowing;
using static DotRecast.Recast.Demo.Draw.DebugDraw; using static DotRecast.Recast.Demo.Draw.DebugDraw;
using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives; using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives;
using static DotRecast.Detour.DetourCommon; using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Recast.Demo.Tools; namespace DotRecast.Recast.Demo.Tools;
public class DynamicUpdateTool : Tool {
private enum ToolMode { public class DynamicUpdateTool : Tool
BUILD, COLLIDERS, RAYCAST {
private enum ToolMode
{
BUILD,
COLLIDERS,
RAYCAST
} }
private enum ColliderShape { private enum ColliderShape
SPHERE, CAPSULE, BOX, CYLINDER, COMPOSITE, CONVEX, TRIMESH_BRIDGE, TRIMESH_HOUSE {
SPHERE,
CAPSULE,
BOX,
CYLINDER,
COMPOSITE,
CONVEX,
TRIMESH_BRIDGE,
TRIMESH_HOUSE
} }
private Sample sample; private Sample sample;
@ -94,49 +105,78 @@ public class DynamicUpdateTool : Tool {
convexGeom = DemoObjImporter.load(Loader.ToBytes("convex.obj")); convexGeom = DemoObjImporter.load(Loader.ToBytes("convex.obj"));
} }
public override void setSample(Sample sample) { public override void setSample(Sample sample)
{
this.sample = sample; this.sample = sample;
} }
public override void handleClick(float[] s, float[] p, bool shift) { public override void handleClick(float[] s, float[] p, bool shift)
if (mode == ToolMode.COLLIDERS) { {
if (!shift) { if (mode == ToolMode.COLLIDERS)
{
if (!shift)
{
Tuple<Collider, ColliderGizmo> colliderWithGizmo = null; Tuple<Collider, ColliderGizmo> colliderWithGizmo = null;
if (dynaMesh != null) { if (dynaMesh != null)
if (colliderShape == ColliderShape.SPHERE) { {
if (colliderShape == ColliderShape.SPHERE)
{
colliderWithGizmo = sphereCollider(p); colliderWithGizmo = sphereCollider(p);
} else if (colliderShape == ColliderShape.CAPSULE) { }
else if (colliderShape == ColliderShape.CAPSULE)
{
colliderWithGizmo = capsuleCollider(p); colliderWithGizmo = capsuleCollider(p);
} else if (colliderShape == ColliderShape.BOX) { }
else if (colliderShape == ColliderShape.BOX)
{
colliderWithGizmo = boxCollider(p); colliderWithGizmo = boxCollider(p);
} else if (colliderShape == ColliderShape.CYLINDER) { }
else if (colliderShape == ColliderShape.CYLINDER)
{
colliderWithGizmo = cylinderCollider(p); colliderWithGizmo = cylinderCollider(p);
} else if (colliderShape == ColliderShape.COMPOSITE) { }
else if (colliderShape == ColliderShape.COMPOSITE)
{
colliderWithGizmo = compositeCollider(p); colliderWithGizmo = compositeCollider(p);
} else if (colliderShape == ColliderShape.TRIMESH_BRIDGE) { }
else if (colliderShape == ColliderShape.TRIMESH_BRIDGE)
{
colliderWithGizmo = trimeshBridge(p); colliderWithGizmo = trimeshBridge(p);
} else if (colliderShape == ColliderShape.TRIMESH_HOUSE) { }
else if (colliderShape == ColliderShape.TRIMESH_HOUSE)
{
colliderWithGizmo = trimeshHouse(p); colliderWithGizmo = trimeshHouse(p);
} else if (colliderShape == ColliderShape.CONVEX) { }
else if (colliderShape == ColliderShape.CONVEX)
{
colliderWithGizmo = convexTrimesh(p); colliderWithGizmo = convexTrimesh(p);
} }
} }
if (colliderWithGizmo != null) {
if (colliderWithGizmo != null)
{
long id = dynaMesh.addCollider(colliderWithGizmo.Item1); long id = dynaMesh.addCollider(colliderWithGizmo.Item1);
colliders.Add(id, colliderWithGizmo.Item1); colliders.Add(id, colliderWithGizmo.Item1);
colliderGizmos.Add(id, colliderWithGizmo.Item2); colliderGizmos.Add(id, colliderWithGizmo.Item2);
} }
} }
} }
if (mode == ToolMode.RAYCAST) {
if (shift) { if (mode == ToolMode.RAYCAST)
{
if (shift)
{
sposSet = true; sposSet = true;
spos = ArrayUtils.CopyOf(p, p.Length); spos = ArrayUtils.CopyOf(p, p.Length);
} else { }
else
{
eposSet = true; eposSet = true;
epos = ArrayUtils.CopyOf(p, p.Length); 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[] sp = { spos[0], spos[1] + 1.3f, spos[2] };
float[] ep = { epos[0], epos[1] + 1.3f, epos[2] }; float[] ep = { epos[0], epos[1] + 1.3f, epos[2] };
long t1 = Stopwatch.GetTimestamp(); 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; float radius = 1 + (float)random.NextDouble() * 10;
return Tuple.Create<Collider, ColliderGizmo>( return Tuple.Create<Collider, ColliderGizmo>(
new SphereCollider(p, radius, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER, dynaMesh.config.walkableClimb), new SphereCollider(p, radius, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER, dynaMesh.config.walkableClimb),
GizmoFactory.sphere(p, radius)); 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 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()) }; float[] a = new float[] { (1f - 2 * (float)random.NextDouble()), 0.01f + (float)random.NextDouble(), (1f - 2 * (float)random.NextDouble()) };
vNormalize(a); vNormalize(a);
@ -172,9 +214,13 @@ public class DynamicUpdateTool : Tool {
dynaMesh.config.walkableClimb), GizmoFactory.capsule(start, end, radius)); dynaMesh.config.walkableClimb), GizmoFactory.capsule(start, end, radius));
} }
private Tuple<Collider, ColliderGizmo> boxCollider(float[] p) { 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[] 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[] 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[] 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); float[][] halfEdges = BoxCollider.getHalfEdges(up, forward, extent);
@ -183,7 +229,8 @@ public class DynamicUpdateTool : Tool {
GizmoFactory.box(p, halfEdges)); 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 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()) }; float[] a = new float[] { (1f - 2 * (float)random.NextDouble()), 0.01f + (float)random.NextDouble(), (1f - 2 * (float)random.NextDouble()) };
vNormalize(a); vNormalize(a);
@ -197,7 +244,8 @@ public class DynamicUpdateTool : Tool {
dynaMesh.config.walkableClimb), GizmoFactory.cylinder(start, end, radius)); 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[] baseExtent = new float[] { 5, 3, 8 };
float[] baseCenter = new float[] { p[0], p[1] + 3, p[2] }; float[] baseCenter = new float[] { p[0], p[1] + 3, p[2] };
float[] baseUp = new float[] { 0, 1, 0 }; 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] }; float[] roofCenter = new float[] { p[0], p[1] + 6, p[2] };
BoxCollider roof = new BoxCollider(roofCenter, BoxCollider.getHalfEdges(roofUp, forward, roofExtent), BoxCollider roof = new BoxCollider(roofCenter, BoxCollider.getHalfEdges(roofUp, forward, roofExtent),
SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD, dynaMesh.config.walkableClimb); SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD, dynaMesh.config.walkableClimb);
float[] trunkStart = new float[] { baseCenter[0] - forward[0] * 15 + side[0] * 6, p[1], float[] trunkStart = new float[]
baseCenter[2] - forward[2] * 15 + side[2] * 6 }; {
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] }; float[] trunkEnd = new float[] { trunkStart[0], trunkStart[1] + 10, trunkStart[2] };
CapsuleCollider trunk = new CapsuleCollider(trunkStart, trunkEnd, 0.5f, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD, CapsuleCollider trunk = new CapsuleCollider(trunkStart, trunkEnd, 0.5f, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD,
dynaMesh.config.walkableClimb); dynaMesh.config.walkableClimb);
float[] crownCenter = new float[] { baseCenter[0] - forward[0] * 15 + side[0] * 6, p[1] + 10, float[] crownCenter = new float[]
baseCenter[2] - forward[2] * 15 + side[2] * 6 }; {
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, SphereCollider crown = new SphereCollider(crownCenter, 4f, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GRASS,
dynaMesh.config.walkableClimb); dynaMesh.config.walkableClimb);
CompositeCollider collider = new CompositeCollider(@base, roof, trunk, crown); CompositeCollider collider = new CompositeCollider(@base, roof, trunk, crown);
@ -230,36 +284,42 @@ public class DynamicUpdateTool : Tool {
return Tuple.Create<Collider, ColliderGizmo>(collider, gizmo); 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); return trimeshCollider(p, bridgeGeom);
} }
private Tuple<Collider, ColliderGizmo> trimeshHouse(float[] p) { private Tuple<Collider, ColliderGizmo> trimeshHouse(float[] p)
{
return trimeshCollider(p, houseGeom); return trimeshCollider(p, houseGeom);
} }
private Tuple<Collider, ColliderGizmo> convexTrimesh(float[] p) { private Tuple<Collider, ColliderGizmo> convexTrimesh(float[] p)
{
float[] verts = transformVertices(p, convexGeom, 360); float[] verts = transformVertices(p, convexGeom, 360);
ConvexTrimeshCollider collider = new ConvexTrimeshCollider(verts, convexGeom.faces, ConvexTrimeshCollider collider = new ConvexTrimeshCollider(verts, convexGeom.faces,
SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD, dynaMesh.config.walkableClimb * 10); SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD, dynaMesh.config.walkableClimb * 10);
return Tuple.Create<Collider, ColliderGizmo>(collider, GizmoFactory.trimesh(verts, convexGeom.faces)); 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); float[] verts = transformVertices(p, geom, 0);
TrimeshCollider collider = new TrimeshCollider(verts, geom.faces, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD, TrimeshCollider collider = new TrimeshCollider(verts, geom.faces, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD,
dynaMesh.config.walkableClimb * 10); dynaMesh.config.walkableClimb * 10);
return Tuple.Create<Collider, ColliderGizmo>(collider, GizmoFactory.trimesh(verts, geom.faces)); 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[] 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[] ry = GLU.build_4x4_rotation_matrix((float)random.NextDouble() * 360, 0, 1, 0);
float[] m = GLU.mul(rx, ry); float[] m = GLU.mul(rx, ry);
float[] verts = new float[geom.vertices.Length]; float[] verts = new float[geom.vertices.Length];
float[] v = new float[3]; float[] v = new float[3];
float[] vr = 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[0] = geom.vertices[i];
v[1] = geom.vertices[i + 1]; v[1] = geom.vertices[i + 1];
v[2] = geom.vertices[i + 2]; v[2] = geom.vertices[i + 2];
@ -271,21 +331,28 @@ public class DynamicUpdateTool : Tool {
verts[i + 1] = vr[1]; verts[i + 1] = vr[1];
verts[i + 2] = vr[2]; verts[i + 2] = vr[2];
} }
return verts; 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[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[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]; resultvector[2] = matrix[2] * pvector[0] + matrix[6] * pvector[1] + matrix[10] * pvector[2];
return resultvector; return resultvector;
} }
public override void handleClickRay(float[] start, float[] dir, bool shift) { public override void handleClickRay(float[] start, float[] dir, bool shift)
if (mode == ToolMode.COLLIDERS) { {
if (shift) { if (mode == ToolMode.COLLIDERS)
foreach (var e in colliders) { {
if (hit(start, dir, e.Value.bounds())) { if (shift)
{
foreach (var e in colliders)
{
if (hit(start, dir, e.Value.bounds()))
{
dynaMesh.removeCollider(e.Key); dynaMesh.removeCollider(e.Key);
colliders.Remove(e.Key); colliders.Remove(e.Key);
colliderGizmos.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 cx = 0.5f * (bounds[0] + bounds[3]);
float cy = 0.5f * (bounds[1] + bounds[4]); float cy = 0.5f * (bounds[1] + bounds[4]);
float cz = 0.5f * (bounds[2] + bounds[5]); float cz = 0.5f * (bounds[2] + bounds[5]);
@ -308,46 +376,62 @@ public class DynamicUpdateTool : Tool {
float my = point[1] - cy; float my = point[1] - cy;
float mz = point[2] - cz; float mz = point[2] - cz;
float c = mx * mx + my * my + mz * mz - rSqr; float c = mx * mx + my * my + mz * mz - rSqr;
if (c <= 0.0f) { if (c <= 0.0f)
{
return true; return true;
} }
float b = mx * dir[0] + my * dir[1] + mz * dir[2]; float b = mx * dir[0] + my * dir[1] + mz * dir[2];
if (b > 0.0f) { if (b > 0.0f)
{
return false; return false;
} }
float disc = b * b - c; float disc = b * b - c;
return disc >= 0.0f; return disc >= 0.0f;
} }
public override void handleRender(NavMeshRenderer renderer) { public override void handleRender(NavMeshRenderer renderer)
if (mode == ToolMode.COLLIDERS) { {
if (showColliders) { if (mode == ToolMode.COLLIDERS)
{
if (showColliders)
{
colliderGizmos.Values.forEach(g => g.render(renderer.getDebugDraw())); colliderGizmos.Values.forEach(g => g.render(renderer.getDebugDraw()));
} }
} }
if (mode == ToolMode.RAYCAST) {
if (mode == ToolMode.RAYCAST)
{
RecastDebugDraw dd = renderer.getDebugDraw(); RecastDebugDraw dd = renderer.getDebugDraw();
int startCol = duRGBA(128, 25, 0, 192); int startCol = duRGBA(128, 25, 0, 192);
int endCol = duRGBA(51, 102, 0, 129); int endCol = duRGBA(51, 102, 0, 129);
if (sposSet) { if (sposSet)
{
drawAgent(dd, spos, startCol); drawAgent(dd, spos, startCol);
} }
if (eposSet) {
if (eposSet)
{
drawAgent(dd, epos, endCol); drawAgent(dd, epos, endCol);
} }
dd.depthMask(false); dd.depthMask(false);
if (raycastHitPos != null) { if (raycastHitPos != null)
{
int spathCol = raycastHit ? duRGBA(128, 32, 16, 220) : duRGBA(64, 128, 240, 220); int spathCol = raycastHit ? duRGBA(128, 32, 16, 220) : duRGBA(64, 128, 240, 220);
dd.begin(LINES, 2.0f); dd.begin(LINES, 2.0f);
dd.vertex(spos[0], spos[1] + 1.3f, spos[2], spathCol); dd.vertex(spos[0], spos[1] + 1.3f, spos[2], spathCol);
dd.vertex(raycastHitPos[0], raycastHitPos[1], raycastHitPos[2], spathCol); dd.vertex(raycastHitPos[0], raycastHitPos[1], raycastHitPos[2], spathCol);
dd.end(); dd.end();
} }
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 = sample.getSettingsUI().getAgentRadius(); float r = sample.getSettingsUI().getAgentRadius();
float h = sample.getSettingsUI().getAgentHeight(); float h = sample.getSettingsUI().getAgentHeight();
float c = sample.getSettingsUI().getAgentMaxClimb(); float c = sample.getSettingsUI().getAgentMaxClimb();
@ -367,29 +451,35 @@ public class DynamicUpdateTool : Tool {
dd.depthMask(true); dd.depthMask(true);
} }
public override void handleUpdate(float dt) { public override void handleUpdate(float dt)
if (dynaMesh != null) { {
if (dynaMesh != null)
{
updateDynaMesh(); updateDynaMesh();
} }
} }
private void updateDynaMesh() { private void updateDynaMesh()
{
long t = Stopwatch.GetTimestamp(); long t = Stopwatch.GetTimestamp();
try try
{ {
bool updated = dynaMesh.update(executor).Result; bool updated = dynaMesh.update(executor).Result;
if (updated) { if (updated)
{
buildTime = (Stopwatch.GetTimestamp() - t) / 1_000_000; buildTime = (Stopwatch.GetTimestamp() - t) / 1_000_000;
sample.update(null, dynaMesh.recastResults(), dynaMesh.navMesh()); sample.update(null, dynaMesh.recastResults(), dynaMesh.navMesh());
sample.setChanged(false); sample.setChanged(false);
} }
} catch (Exception e) { }
catch (Exception e)
{
Console.WriteLine(e); Console.WriteLine(e);
} }
} }
public override void layout(IWindow ctx) { public override void layout(IWindow ctx)
{
// nk_layout_row_dynamic(ctx, 18, 1); // nk_layout_row_dynamic(ctx, 18, 1);
// if (nk_option_label(ctx, "Build", mode == ToolMode.BUILD)) { // if (nk_option_label(ctx, "Build", mode == ToolMode.BUILD)) {
// mode = ToolMode.BUILD; // mode = ToolMode.BUILD;
@ -557,10 +647,10 @@ public class DynamicUpdateTool : Tool {
// } else { // } else {
// nk_label(ctx, string.format("Build Time: %d ms", buildTime), NK_TEXT_ALIGN_LEFT); // nk_label(ctx, string.format("Build Time: %d ms", buildTime), NK_TEXT_ALIGN_LEFT);
// } // }
} }
private void load() { private void load()
{
// try (MemoryStack stack = stackPush()) { // try (MemoryStack stack = stackPush()) {
// PointerBuffer aFilterPatterns = stack.mallocPointer(1); // PointerBuffer aFilterPatterns = stack.mallocPointer(1);
// aFilterPatterns.put(stack.UTF8("*.voxels")); // aFilterPatterns.put(stack.UTF8("*.voxels"));
@ -570,10 +660,10 @@ public class DynamicUpdateTool : Tool {
// load(filename); // load(filename);
// } // }
// } // }
} }
private void load(string filename) { private void load(string filename)
{
// File file = new File(filename); // File file = new File(filename);
// if (file.exists()) { // if (file.exists()) {
// VoxelFileReader reader = new VoxelFileReader(); // VoxelFileReader reader = new VoxelFileReader();
@ -591,7 +681,8 @@ public class DynamicUpdateTool : Tool {
// } // }
} }
private void save() { private void save()
{
// try (MemoryStack stack = stackPush()) { // try (MemoryStack stack = stackPush()) {
// PointerBuffer aFilterPatterns = stack.mallocPointer(1); // PointerBuffer aFilterPatterns = stack.mallocPointer(1);
// aFilterPatterns.put(stack.UTF8("*.voxels")); // aFilterPatterns.put(stack.UTF8("*.voxels"));
@ -601,10 +692,10 @@ public class DynamicUpdateTool : Tool {
// save(filename); // save(filename);
// } // }
// } // }
} }
private void save(string filename) { private void save(string filename)
{
// File file = new File(filename); // File file = new File(filename);
// try (FileOutputStream fos = new FileOutputStream(file)) { // try (FileOutputStream fos = new FileOutputStream(file)) {
// VoxelFile voxelFile = VoxelFile.from(dynaMesh); // VoxelFile voxelFile = VoxelFile.from(dynaMesh);
@ -613,23 +704,27 @@ public class DynamicUpdateTool : Tool {
// } catch (Exception e) { // } catch (Exception e) {
// Console.WriteLine(e); // Console.WriteLine(e);
// } // }
} }
private void buildDynaMesh() { private void buildDynaMesh()
{
configDynaMesh(); configDynaMesh();
long t = Stopwatch.GetTimestamp(); long t = Stopwatch.GetTimestamp();
try try
{ {
var _ = dynaMesh.build(executor).Result; var _ = dynaMesh.build(executor).Result;
} catch (Exception e) { }
catch (Exception e)
{
Console.WriteLine(e); Console.WriteLine(e);
} }
buildTime = (Stopwatch.GetTimestamp() - t) / 1_000_000; buildTime = (Stopwatch.GetTimestamp() - t) / 1_000_000;
sample.update(null, dynaMesh.recastResults(), dynaMesh.navMesh()); sample.update(null, dynaMesh.recastResults(), dynaMesh.navMesh());
} }
private void configDynaMesh() { private void configDynaMesh()
{
dynaMesh.config.partitionType = partitioning; dynaMesh.config.partitionType = partitioning;
dynaMesh.config.walkableHeight = walkableHeight[0]; dynaMesh.config.walkableHeight = walkableHeight[0];
dynaMesh.config.walkableSlopeAngle = walkableSlopeAngle[0]; dynaMesh.config.walkableSlopeAngle = walkableSlopeAngle[0];
@ -648,7 +743,8 @@ public class DynamicUpdateTool : Tool {
dynaMesh.config.detailSampleMaxError = detailSampleMaxError[0]; dynaMesh.config.detailSampleMaxError = detailSampleMaxError[0];
} }
private void updateUI() { private void updateUI()
{
cellSize[0] = dynaMesh.config.cellSize; cellSize[0] = dynaMesh.config.cellSize;
partitioning = dynaMesh.config.partitionType; partitioning = dynaMesh.config.partitionType;
walkableHeight[0] = dynaMesh.config.walkableHeight; walkableHeight[0] = dynaMesh.config.walkableHeight;
@ -668,8 +764,8 @@ public class DynamicUpdateTool : Tool {
filterWalkableLowHeightSpans = dynaMesh.config.filterWalkableLowHeightSpans; filterWalkableLowHeightSpans = dynaMesh.config.filterWalkableLowHeightSpans;
} }
public override string getName() { public override string getName()
{
return "Dynamic Updates"; return "Dynamic Updates";
} }
} }

View File

@ -3,11 +3,16 @@ using DotRecast.Recast.Demo.Draw;
namespace DotRecast.Recast.Demo.Tools.Gizmos; 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 float[][] VERTS =
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 = {
new[] { -1f, -1f, -1f, }, new[] { -1f, -1f, -1f, },
new[] { 1f, -1f, -1f, }, 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.center = center;
this.halfEdges = halfEdges; 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 s0 = (i & 1) != 0 ? 1f : -1f;
float s1 = (i & 2) != 0 ? 1f : -1f; float s1 = (i & 2) != 0 ? 1f : -1f;
float s2 = (i & 4) != 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[] 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[] 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[] trZ = new float[] { halfEdges[0][2], halfEdges[1][2], halfEdges[2][2] };
float[] vertices = new float[8 * 3]; 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 + 0] = RecastVectors.dot(VERTS[i], trX) + center[0];
vertices[i * 3 + 1] = RecastVectors.dot(VERTS[i], trY) + center[1]; vertices[i * 3 + 1] = RecastVectors.dot(VERTS[i], trY) + center[1];
vertices[i * 3 + 2] = RecastVectors.dot(VERTS[i], trZ) + center[2]; vertices[i * 3 + 2] = RecastVectors.dot(VERTS[i], trZ) + center[2];
} }
debugDraw.begin(DebugDrawPrimitives.TRIS); 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); 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); col = DebugDraw.duRGBA(160, 160, 40, 160);
} else if (i > 4) { }
else if (i > 4)
{
col = DebugDraw.duRGBA(120, 120, 30, 160); 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; int v = TRIANLGES[i * 3 + j] * 3;
debugDraw.vertex(vertices[v], vertices[v + 1], vertices[v + 2], col); debugDraw.vertex(vertices[v], vertices[v + 1], vertices[v + 2], col);
} }
} }
debugDraw.end(); debugDraw.end();
} }
} }

View File

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

View File

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

View File

@ -3,15 +3,17 @@ using DotRecast.Recast.Demo.Draw;
namespace DotRecast.Recast.Demo.Tools.Gizmos; namespace DotRecast.Recast.Demo.Tools.Gizmos;
public class CompositeGizmo : ColliderGizmo { public class CompositeGizmo : ColliderGizmo
{
private readonly ColliderGizmo[] gizmos; private readonly ColliderGizmo[] gizmos;
public CompositeGizmo(params ColliderGizmo[] gizmos) { public CompositeGizmo(params ColliderGizmo[] gizmos)
{
this.gizmos = gizmos; this.gizmos = gizmos;
} }
public void render(RecastDebugDraw debugDraw) { public void render(RecastDebugDraw debugDraw)
{
gizmos.forEach(g => g.render(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; namespace DotRecast.Recast.Demo.Tools.Gizmos;
public class CylinderGizmo : ColliderGizmo
public class CylinderGizmo : ColliderGizmo { {
private readonly float[] vertices; private readonly float[] vertices;
private readonly int[] triangles; private readonly int[] triangles;
private readonly float[] center; private readonly float[] center;
private readonly float[] gradient; private readonly float[] gradient;
public CylinderGizmo(float[] start, float[] end, float radius) { 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]) }; 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[] axis = new float[] { end[0] - start[0], end[1] - start[1], end[2] - start[2] };
float[][] normals = new float[3][]; float[][] normals = new float[3][];
normals[1] = new float[] { end[0] - start[0], end[1] - start[1], end[2] - start[2] }; 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); float halfLength = 0.5f * vLen(axis);
gradient = new float[vertices.Length / 3]; gradient = new float[vertices.Length / 3];
float[] v = new float[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 offset = (i >= vertices.Length / 2) ? -halfLength : halfLength;
float x = radius * vertices[i]; float x = radius * vertices[i];
float y = vertices[i + 1] + offset; 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] = 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 + 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]; 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; gradient[i / 3] = 1;
} else { }
else
{
v[0] = vertices[i] - center[0]; v[0] = vertices[i] - center[0];
v[1] = vertices[i + 1] - center[1]; v[1] = vertices[i + 1] - center[1];
v[2] = vertices[i + 2] - center[2]; 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 }; float[] side = { 1, 0, 0 };
if (axis[0] > 0.8) { if (axis[0] > 0.8)
{
side = new float[] { 0, 0, 1 }; side = new float[] { 0, 0, 1 };
} }
float[] forward = new float[3]; float[] forward = new float[3];
cross(forward, side, axis); cross(forward, side, axis);
cross(side, axis, forward); cross(side, axis, forward);
@ -64,19 +75,21 @@ public class CylinderGizmo : ColliderGizmo {
return side; return side;
} }
public void render(RecastDebugDraw debugDraw) { public void render(RecastDebugDraw debugDraw)
{
debugDraw.begin(DebugDrawPrimitives.TRIS); debugDraw.begin(DebugDrawPrimitives.TRIS);
for (int i = 0; i < triangles.Length; i += 3) { for (int i = 0; i < triangles.Length; i += 3)
for (int j = 0; j < 3; j++) { {
for (int j = 0; j < 3; j++)
{
int v = triangles[i + j] * 3; int v = triangles[i + j] * 3;
float c = gradient[triangles[i + j]]; float c = gradient[triangles[i + j]];
int col = DebugDraw.duLerpCol(DebugDraw.duRGBA(32, 32, 0, 160), DebugDraw.duRGBA(220, 220, 0, 160), int col = DebugDraw.duLerpCol(DebugDraw.duRGBA(32, 32, 0, 160), DebugDraw.duRGBA(220, 220, 0, 160),
(int) (127 * (1 + c))); (int)(127 * (1 + c)));
debugDraw.vertex(vertices[v], vertices[v + 1], vertices[v + 2], col); debugDraw.vertex(vertices[v], vertices[v + 1], vertices[v + 2], col);
} }
} }
debugDraw.end(); debugDraw.end();
} }
} }

View File

@ -2,27 +2,33 @@
public static class GizmoFactory 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); 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); 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); 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); 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); return new TrimeshGizmo(verts, faces);
} }
public static ColliderGizmo composite(params ColliderGizmo[] gizmos) { public static ColliderGizmo composite(params ColliderGizmo[] gizmos)
{
return new CompositeGizmo(gizmos); return new CompositeGizmo(gizmos);
} }
} }

View File

@ -4,31 +4,37 @@ using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Recast.Demo.Tools.Gizmos; namespace DotRecast.Recast.Demo.Tools.Gizmos;
public class GizmoHelper { public class GizmoHelper
{
private static readonly int SEGMENTS = 16; private static readonly int SEGMENTS = 16;
private static readonly int RINGS = 8; private static readonly int RINGS = 8;
private static float[] sphericalVertices; private static float[] sphericalVertices;
public static float[] generateSphericalVertices() { public static float[] generateSphericalVertices()
if (sphericalVertices == null) { {
if (sphericalVertices == null)
{
sphericalVertices = generateSphericalVertices(SEGMENTS, RINGS); sphericalVertices = generateSphericalVertices(SEGMENTS, RINGS);
} }
return sphericalVertices; 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)]; float[] vertices = new float[6 + 3 * (segments + 1) * (rings + 1)];
// top // top
int vi = 0; int vi = 0;
vertices[vi++] = 0; vertices[vi++] = 0;
vertices[vi++] = 1; vertices[vi++] = 1;
vertices[vi++] = 0; 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); double theta = Math.PI * (r + 1) / (rings + 2);
vi = generateRingVertices(segments, vertices, vi, theta); vi = generateRingVertices(segments, vertices, vi, theta);
} }
// bottom // bottom
vertices[vi++] = 0; vertices[vi++] = 0;
vertices[vi++] = -1; vertices[vi++] = -1;
@ -36,39 +42,47 @@ public class GizmoHelper {
return vertices; return vertices;
} }
public static float[] generateCylindricalVertices() { public static float[] generateCylindricalVertices()
{
return generateCylindricalVertices(SEGMENTS); return generateCylindricalVertices(SEGMENTS);
} }
private static float[] generateCylindricalVertices(int segments) { private static float[] generateCylindricalVertices(int segments)
{
float[] vertices = new float[3 * (segments + 1) * 4]; float[] vertices = new float[3 * (segments + 1) * 4];
int vi = 0; 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); 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 cosTheta = Math.Cos(theta);
double sinTheta = Math.Sin(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 phi = 2 * Math.PI * p / segments;
double cosPhi = Math.Cos(phi); double cosPhi = Math.Cos(phi);
double sinPhi = Math.Sin(phi); double sinPhi = Math.Sin(phi);
vertices[vi++] = (float) (sinTheta * cosPhi); vertices[vi++] = (float)(sinTheta * cosPhi);
vertices[vi++] = (float) cosTheta; vertices[vi++] = (float)cosTheta;
vertices[vi++] = (float) (sinTheta * sinPhi); vertices[vi++] = (float)(sinTheta * sinPhi);
} }
return vi; return vi;
} }
public static int[] generateSphericalTriangles() { public static int[] generateSphericalTriangles()
{
return generateSphericalTriangles(SEGMENTS, RINGS); 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[] triangles = new int[6 * (segments + rings * (segments + 1))];
int ti = generateSphereUpperCapTriangles(segments, triangles, 0); int ti = generateSphereUpperCapTriangles(segments, triangles, 0);
ti = generateRingTriangles(segments, rings, triangles, 1, ti); ti = generateRingTriangles(segments, rings, triangles, 1, ti);
@ -76,9 +90,12 @@ public class GizmoHelper {
return triangles; return triangles;
} }
public static int generateRingTriangles(int segments, int rings, int[] triangles, int vertexOffset, int ti) { 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++) { for (int r = 0; r < rings; r++)
{
for (int p = 0; p < segments; p++)
{
int current = p + r * (segments + 1) + vertexOffset; int current = p + r * (segments + 1) + vertexOffset;
int next = p + 1 + r * (segments + 1) + vertexOffset; int next = p + 1 + r * (segments + 1) + vertexOffset;
int currentBottom = p + (r + 1) * (segments + 1) + vertexOffset; int currentBottom = p + (r + 1) * (segments + 1) + vertexOffset;
@ -91,32 +108,40 @@ public class GizmoHelper {
triangles[ti++] = currentBottom; triangles[ti++] = currentBottom;
} }
} }
return ti; return ti;
} }
private static int generateSphereUpperCapTriangles(int segments, int[] triangles, int ti) { private static int generateSphereUpperCapTriangles(int segments, int[] triangles, int ti)
for (int p = 0; p < segments; p++) { {
for (int p = 0; p < segments; p++)
{
triangles[ti++] = p + 2; triangles[ti++] = p + 2;
triangles[ti++] = p + 1; triangles[ti++] = p + 1;
triangles[ti++] = 0; triangles[ti++] = 0;
} }
return ti; 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); 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;
triangles[ti++] = lastVertex - (p + 2); triangles[ti++] = lastVertex - (p + 2);
triangles[ti++] = lastVertex - (p + 1); triangles[ti++] = lastVertex - (p + 1);
} }
} }
public static int[] generateCylindricalTriangles() { public static int[] generateCylindricalTriangles()
{
return generateCylindricalTriangles(SEGMENTS); return generateCylindricalTriangles(SEGMENTS);
} }
private static int[] generateCylindricalTriangles(int segments) { private static int[] generateCylindricalTriangles(int segments)
{
int circleTriangles = segments - 2; int circleTriangles = segments - 2;
int[] triangles = new int[6 * (circleTriangles + (segments + 1))]; int[] triangles = new int[6 * (circleTriangles + (segments + 1))];
int vi = 0; int vi = 0;
@ -127,36 +152,44 @@ public class GizmoHelper {
return triangles; return triangles;
} }
private static int generateCircleTriangles(int segments, int[] triangles, int vi, int ti, bool 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) { for (int p = 0; p < segments - 2; p++)
{
if (invert)
{
triangles[ti++] = vi; triangles[ti++] = vi;
triangles[ti++] = vi + p + 1; triangles[ti++] = vi + p + 1;
triangles[ti++] = vi + p + 2; triangles[ti++] = vi + p + 2;
} else { }
else
{
triangles[ti++] = vi + p + 2; triangles[ti++] = vi + p + 2;
triangles[ti++] = vi + p + 1; triangles[ti++] = vi + p + 1;
triangles[ti++] = vi; triangles[ti++] = vi;
} }
} }
return ti; 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[] e0 = new float[3], e1 = new float[3];
float[] normal = 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]; e0[j] = vertices[v1 + j] - vertices[v0 + j];
e1[j] = vertices[v2 + j] - vertices[v0 + j]; e1[j] = vertices[v2 + j] - vertices[v0 + j];
} }
normal[0] = e0[1] * e1[2] - e0[2] * e1[1]; normal[0] = e0[1] * e1[2] - e0[2] * e1[1];
normal[1] = e0[2] * e1[0] - e0[0] * e1[2]; normal[1] = e0[2] * e1[0] - e0[0] * e1[2];
normal[2] = e0[0] * e1[1] - e0[1] * e1[0]; normal[2] = e0[0] * e1[1] - e0[1] * e1[0];
RecastVectors.normalize(normal); RecastVectors.normalize(normal);
float c = clamp(0.57735026f * (normal[0] + normal[1] + normal[2]), -1, 1); float c = clamp(0.57735026f * (normal[0] + normal[1] + normal[2]), -1, 1);
int col = DebugDraw.duLerpCol(DebugDraw.duRGBA(32, 32, 0, 160), DebugDraw.duRGBA(220, 220, 0, 160), int col = DebugDraw.duLerpCol(DebugDraw.duRGBA(32, 32, 0, 160), DebugDraw.duRGBA(220, 220, 0, 160),
(int) (127 * (1 + c))); (int)(127 * (1 + c)));
return col; return col;
} }
} }

View File

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

View File

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

View File

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

View File

@ -20,9 +20,8 @@ using DotRecast.Detour.Extras.Jumplink;
namespace DotRecast.Recast.Demo.Tools; namespace DotRecast.Recast.Demo.Tools;
public class JumpLinkBuilderToolParams
public class JumpLinkBuilderToolParams { {
public const int DRAW_WALKABLE_SURFACE = 1 << 0; public const int DRAW_WALKABLE_SURFACE = 1 << 0;
public const int DRAW_WALKABLE_BORDER = 1 << 1; public const int DRAW_WALKABLE_BORDER = 1 << 1;
public const int DRAW_SELECTED_EDGE = 1 << 2; 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[] edgeJumpDownMaxHeight = new[] { 2.5f };
public readonly float[] edgeJumpUpMaxHeight = new[] { 0.3f }; public readonly float[] edgeJumpUpMaxHeight = new[] { 0.3f };
public int buildTypes = (1 << (int)JumpLinkType.EDGE_CLIMB_DOWN) | (1 << (int)JumpLinkType.EDGE_JUMP); 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.Builder;
using DotRecast.Recast.Demo.Draw; using DotRecast.Recast.Demo.Draw;
using DotRecast.Recast.Demo.Geom; using DotRecast.Recast.Demo.Geom;
using static DotRecast.Recast.Demo.Draw.DebugDraw; using static DotRecast.Recast.Demo.Draw.DebugDraw;
namespace DotRecast.Recast.Demo.Tools; namespace DotRecast.Recast.Demo.Tools;
public class OffMeshConnectionTool : Tool {
public class OffMeshConnectionTool : Tool
{
private Sample sample; private Sample sample;
private bool hitPosSet; private bool hitPosSet;
private float[] hitPos; private float[] hitPos;
private bool bidir; private bool bidir;
public override void setSample(Sample m_sample) { public override void setSample(Sample m_sample)
{
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(); DemoInputGeomProvider geom = sample.getInputGeom();
if (geom == null) { if (geom == null)
{
return; return;
} }
if (shift) { if (shift)
{
// Delete // Delete
// Find nearest link end-point // Find nearest link end-point
float nearestDist = float.MaxValue; float nearestDist = float.MaxValue;
DemoOffMeshConnection nearestConnection = null; 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)); 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; nearestDist = d;
nearestConnection = offMeshCon; nearestConnection = offMeshCon;
} }
} }
if (nearestConnection != null) {
if (nearestConnection != null)
{
geom.getOffMeshConnections().Remove(nearestConnection); geom.getOffMeshConnections().Remove(nearestConnection);
} }
} else { }
else
{
// Create // Create
if (!hitPosSet) { if (!hitPosSet)
{
hitPos = ArrayUtils.CopyOf(p, p.Length); hitPos = ArrayUtils.CopyOf(p, p.Length);
hitPosSet = true; hitPosSet = true;
} else { }
else
{
int area = SampleAreaModifications.SAMPLE_POLYAREA_TYPE_JUMP; int area = SampleAreaModifications.SAMPLE_POLYAREA_TYPE_JUMP;
int flags = SampleAreaModifications.SAMPLE_POLYFLAGS_JUMP; int flags = SampleAreaModifications.SAMPLE_POLYFLAGS_JUMP;
geom.addOffMeshConnection(hitPos, p, sample.getSettingsUI().getAgentRadius(), bidir, area, flags); geom.addOffMeshConnection(hitPos, p, sample.getSettingsUI().getAgentRadius(), bidir, area, flags);
hitPosSet = false; hitPosSet = false;
} }
} }
} }
public override void handleRender(NavMeshRenderer renderer) { public override void handleRender(NavMeshRenderer renderer)
if (sample == null) { {
if (sample == null)
{
return; return;
} }
RecastDebugDraw dd = renderer.getDebugDraw(); RecastDebugDraw dd = renderer.getDebugDraw();
float s = sample.getSettingsUI().getAgentRadius(); 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); dd.debugDrawCross(hitPos[0], hitPos[1] + 0.1f, hitPos[2], s, duRGBA(0, 0, 0, 128), 2.0f);
} }
DemoInputGeomProvider geom = sample.getInputGeom(); DemoInputGeomProvider geom = sample.getInputGeom();
if (geom != null) { if (geom != null)
{
renderer.drawOffMeshConnections(geom, true); renderer.drawOffMeshConnections(geom, true);
} }
} }
public override void layout(IWindow ctx) { public override void layout(IWindow ctx)
{
// nk_layout_row_dynamic(ctx, 20, 1); // nk_layout_row_dynamic(ctx, 20, 1);
// bidir = !nk_option_label(ctx, "One Way", !bidir); // bidir = !nk_option_label(ctx, "One Way", !bidir);
// nk_layout_row_dynamic(ctx, 20, 1); // nk_layout_row_dynamic(ctx, 20, 1);
// bidir = nk_option_label(ctx, "Bidirectional", bidir); // bidir = nk_option_label(ctx, "Bidirectional", bidir);
} }
public override string getName() { public override string getName()
{
return "Create Off-Mesh Links"; return "Create Off-Mesh Links";
} }
public override void handleUpdate(float dt) { public override void handleUpdate(float dt)
{
// TODO Auto-generated method stub // TODO Auto-generated method stub
} }
} }

View File

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

View File

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

View File

@ -1,12 +1,14 @@
namespace DotRecast.Recast.Demo.Tools; namespace DotRecast.Recast.Demo.Tools;
public class SteerTarget { public class SteerTarget
{
public readonly float[] steerPos; public readonly float[] steerPos;
public readonly int steerPosFlag; public readonly int steerPosFlag;
public readonly long steerPosRef; public readonly long steerPosRef;
public readonly float[] steerPoints; 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.steerPos = steerPos;
this.steerPosFlag = steerPosFlag; this.steerPosFlag = steerPosFlag;
this.steerPosRef = steerPosRef; this.steerPosRef = steerPosRef;

View File

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

View File

@ -23,8 +23,8 @@ using Silk.NET.Windowing;
namespace DotRecast.Recast.Demo.Tools; namespace DotRecast.Recast.Demo.Tools;
public abstract class Tool { public abstract class Tool
{
public abstract void setSample(Sample m_sample); public abstract void setSample(Sample m_sample);
public abstract void handleClick(float[] s, float[] p, bool shift); 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; namespace DotRecast.Recast.Demo.Tools;
public interface ToolUIModule { public interface ToolUIModule
{
void layout(IWindow ctx); void layout(IWindow ctx);
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -20,8 +20,8 @@ using System.Runtime.InteropServices.JavaScript;
namespace DotRecast.Recast.Demo.UI; namespace DotRecast.Recast.Demo.UI;
public class NuklearUIHelper { public class NuklearUIHelper
{
// public static void nk_color_rgb(IWindow ctx, NkColorf color) { // public static void nk_color_rgb(IWindow ctx, NkColorf color) {
// try (MemoryStack stack = stackPush()) { // try (MemoryStack stack = stackPush()) {
// if (nk_combo_begin_color(ctx, nk_rgb_cf(color, NkColor.mallocStack(stack)), // 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; namespace DotRecast.Recast.Demo.UI;
public class RcViewSystem { public class RcViewSystem
{
// readonly NkAllocator allocator; // readonly NkAllocator allocator;
private readonly IWindow _window; private readonly IWindow _window;
private readonly GL _gl; private readonly GL _gl;
// readonly NkColor background; // readonly NkColor background;
// readonly NkColor white; // readonly NkColor white;
private readonly IRcView[] _views; private readonly IRcView[] _views;
@ -61,7 +63,8 @@ public class RcViewSystem {
_views = views; _views = views;
} }
private void setupMouse(Mouse mouse) { private void setupMouse(Mouse mouse)
{
// mouse.addListener(new MouseListener() { // mouse.addListener(new MouseListener() {
// //
// @Override // @Override
@ -99,7 +102,8 @@ public class RcViewSystem {
// }); // });
} }
private void setupClipboard(long window) { private void setupClipboard(long window)
{
// ctx.clip().copy((handle, text, len) => { // ctx.clip().copy((handle, text, len) => {
// if (len == 0) { // if (len == 0) {
// return; // return;
@ -120,11 +124,13 @@ public class RcViewSystem {
// }); // });
} }
public void inputBegin() { public void inputBegin()
{
//nk_input_begin(ctx); //nk_input_begin(ctx);
} }
public void inputEnd(IWindow win) { public void inputEnd(IWindow win)
{
// NkMouse mouse = ctx.input().mouse(); // NkMouse mouse = ctx.input().mouse();
// if (mouse.grab()) { // if (mouse.grab()) {
// glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); // glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
@ -140,11 +146,14 @@ public class RcViewSystem {
// nk_input_end(ctx); // 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; mouseOverUI = false;
foreach (IRcView m in _views) { foreach (IRcView m in _views)
{
mouseOverUI = m.render(ctx, x, y, width, height, mouseX, mouseY) | mouseOverUI; mouseOverUI = m.render(ctx, x, y, width, height, mouseX, mouseY) | mouseOverUI;
} }
return mouseOverUI; return mouseOverUI;
} }
} }

View File

@ -20,10 +20,8 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class AreaModification
{
public class AreaModification
{
public readonly int RC_AREA_FLAGS_MASK = 0x3F; public readonly int RC_AREA_FLAGS_MASK = 0x3F;
private readonly int value; private readonly int value;
@ -69,5 +67,5 @@ public class AreaModification
{ {
return ((value & mask) | (area & ~mask)); return ((value & mask) | (area & ~mask));
} }
} }
} }

View File

@ -20,15 +20,13 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/** Provides information on the content of a cell column in a compact heightfield. */
public class CompactCell
/** Provides information on the content of a cell column in a compact heightfield. */ {
public class CompactCell
{
/** Index to the first span in the column. */ /** Index to the first span in the column. */
public int index; public int index;
/** Number of spans in the column. */ /** Number of spans in the column. */
public int count; public int count;
} }
} }

View File

@ -20,11 +20,9 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/** A compact, static heightfield representing unobstructed space. */
public class CompactHeightfield
/** A compact, static heightfield representing unobstructed space. */ {
public class CompactHeightfield
{
/** The width of the heightfield. (Along the x-axis in cell units.) */ /** The width of the heightfield. (Along the x-axis in cell units.) */
public int width; public int width;
@ -72,5 +70,5 @@ public class CompactHeightfield
/** Array containing area id data. [Size: #spanCount] */ /** Array containing area id data. [Size: #spanCount] */
public int[] areas; public int[] areas;
} }
} }

View File

@ -20,11 +20,9 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/** Represents a span of unobstructed space within a compact heightfield. */
public class CompactSpan
/** Represents a span of unobstructed space within a compact heightfield. */ {
public class CompactSpan
{
/** The lower extent of the span. (Measured from the heightfield's base.) */ /** The lower extent of the span. (Measured from the heightfield's base.) */
public int y; public int y;
@ -36,5 +34,5 @@ public class CompactSpan
/** The height of the span. (Measured from #y.) */ /** The height of the span. (Measured from #y.) */
public int h; public int h;
} }
} }

View File

@ -20,11 +20,9 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/** Represents a simple, non-overlapping contour in field space. */
public class Contour
/** Represents a simple, non-overlapping contour in field space. */ {
public class Contour
{
/** Simplified contour vertex and connection data. [Size: 4 * #nverts] */ /** Simplified contour vertex and connection data. [Size: 4 * #nverts] */
public int[] verts; public int[] verts;
@ -42,5 +40,5 @@ public class Contour
/** The area id of the contour. */ /** The area id of the contour. */
public int reg; public int reg;
} }
} }

View File

@ -22,11 +22,9 @@ using System.Collections.Generic;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/** Represents a group of related contours. */
public class ContourSet
/** Represents a group of related contours. */ {
public class ContourSet
{
/** A list of the contours in the set. */ /** A list of the contours in the set. */
public List<Contour> conts = new List<Contour>(); public List<Contour> conts = new List<Contour>();
@ -53,5 +51,5 @@ public class ContourSet
/** The max edge error that this contour set was simplified with. */ /** The max edge error that this contour set was simplified with. */
public float maxError; public float maxError;
} }
} }

View File

@ -20,13 +20,11 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class ConvexVolume
{
public class ConvexVolume
{
public float[] verts; public float[] verts;
public float hmin; public float hmin;
public float hmax; public float hmax;
public AreaModification areaMod; public AreaModification areaMod;
} }
} }

View File

@ -5,7 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DotRecast.Core\DotRecast.Core.csproj" /> <ProjectReference Include="..\DotRecast.Core\DotRecast.Core.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -23,10 +23,8 @@ using System.Collections.Generic;
namespace DotRecast.Recast.Geom namespace DotRecast.Recast.Geom
{ {
public class ChunkyTriMesh
{
public class ChunkyTriMesh
{
private class BoundsItem private class BoundsItem
{ {
public readonly float[] bmin = new float[2]; public readonly float[] bmin = new float[2];
@ -246,5 +244,5 @@ public class ChunkyTriMesh
return ids; return ids;
} }
} }
} }

View File

@ -1,13 +1,10 @@
namespace DotRecast.Recast.Geom namespace DotRecast.Recast.Geom
{ {
public class ChunkyTriMeshNode
{
public class ChunkyTriMeshNode
{
public readonly float[] bmin = new float[2]; public readonly float[] bmin = new float[2];
public readonly float[] bmax = new float[2]; public readonly float[] bmax = new float[2];
public int i; public int i;
public int[] tris; public int[] tris;
} }
} }

View File

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

View File

@ -22,14 +22,12 @@ using System.Collections.Generic;
namespace DotRecast.Recast.Geom namespace DotRecast.Recast.Geom
{ {
public interface InputGeomProvider : ConvexVolumeProvider
{
public interface InputGeomProvider : ConvexVolumeProvider
{
float[] getMeshBoundsMin(); float[] getMeshBoundsMin();
float[] getMeshBoundsMax(); float[] getMeshBoundsMax();
IEnumerable<TriMesh> meshes(); IEnumerable<TriMesh> meshes();
} }
} }

View File

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

View File

@ -22,10 +22,8 @@ using System.Collections.Immutable;
namespace DotRecast.Recast.Geom namespace DotRecast.Recast.Geom
{ {
public class SingleTrimeshInputGeomProvider : InputGeomProvider
{
public class SingleTrimeshInputGeomProvider : InputGeomProvider
{
private readonly float[] bmin; private readonly float[] bmin;
private readonly float[] bmax; private readonly float[] bmax;
private readonly TriMesh[] _meshes; private readonly TriMesh[] _meshes;
@ -64,5 +62,5 @@ public class SingleTrimeshInputGeomProvider : InputGeomProvider
{ {
return ImmutableArray<ConvexVolume>.Empty; return ImmutableArray<ConvexVolume>.Empty;
} }
} }
} }

View File

@ -22,10 +22,8 @@ using System.Collections.Generic;
namespace DotRecast.Recast.Geom namespace DotRecast.Recast.Geom
{ {
public class TriMesh
{
public class TriMesh
{
private readonly float[] vertices; private readonly float[] vertices;
private readonly int[] faces; private readonly int[] faces;
private readonly ChunkyTriMesh chunkyTriMesh; private readonly ChunkyTriMesh chunkyTriMesh;
@ -51,5 +49,5 @@ public class TriMesh
{ {
return chunkyTriMesh.getChunksOverlappingRect(bmin, bmax); return chunkyTriMesh.getChunksOverlappingRect(bmin, bmax);
} }
} }
} }

View File

@ -20,11 +20,9 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/** Represents a heightfield layer within a layer set. */
public class Heightfield
/** Represents a heightfield layer within a layer set. */ {
public class Heightfield
{
/** The width of the heightfield. (Along the x-axis in cell units.) */ /** The width of the heightfield. (Along the x-axis in cell units.) */
public readonly int width; public readonly int width;
@ -60,5 +58,5 @@ public class Heightfield
this.borderSize = borderSize; this.borderSize = borderSize;
spans = new Span[width * height]; spans = new Span[width * height];
} }
} }
} }

View File

@ -20,13 +20,11 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/// Represents a set of heightfield layers.
/// @ingroup recast
/// Represents a set of heightfield layers. /// @see rcAllocHeightfieldLayerSet, rcFreeHeightfieldLayerSet
/// @ingroup recast public class HeightfieldLayerSet
/// @see rcAllocHeightfieldLayerSet, rcFreeHeightfieldLayerSet {
public class HeightfieldLayerSet
{
/// Represents a heightfield layer within a layer set. /// Represents a heightfield layer within a layer set.
/// @see rcHeightfieldLayerSet /// @see rcHeightfieldLayerSet
public class HeightfieldLayer public class HeightfieldLayer
@ -77,5 +75,5 @@ public class HeightfieldLayerSet
} }
public HeightfieldLayer[] layers; /// < The layers in the set. [Size: #nlayers] public HeightfieldLayer[] layers; /// < The layers in the set. [Size: #nlayers]
} }
} }

View File

@ -1,8 +1,6 @@
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class InputGeomReader
{
public class InputGeomReader }
{
}
} }

View File

@ -23,10 +23,8 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public static class ObjImporter
{
public static class ObjImporter
{
public class ObjImporterContext public class ObjImporterContext
{ {
public List<float> vertexPositions = new List<float>(); public List<float> vertexPositions = new List<float>();
@ -137,5 +135,5 @@ public static class ObjImporter
return posi; return posi;
} }
} }
} }

View File

@ -1,13 +1,11 @@
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/// < Tessellate edges between areas during contour
/// simplification.
/// < Tessellate edges between areas during contour public enum PartitionType
/// simplification. {
public enum PartitionType
{
WATERSHED, WATERSHED,
MONOTONE, MONOTONE,
LAYERS LAYERS
} }
} }

View File

@ -19,11 +19,9 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/** Represents a polygon mesh suitable for use in building a navigation mesh. */
public class PolyMesh
/** Represents a polygon mesh suitable for use in building a navigation mesh. */ {
public class PolyMesh
{
/** The mesh vertices. [Form: (x, y, z) coordinates * #nverts] */ /** The mesh vertices. [Form: (x, y, z) coordinates * #nverts] */
public int[] verts; public int[] verts;
@ -68,5 +66,5 @@ public class PolyMesh
/** The max error of the polygon edges in the mesh. */ /** The max error of the polygon edges in the mesh. */
public float maxEdgeError; public float maxEdgeError;
} }
} }

View File

@ -20,14 +20,12 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/**
/**
* Contains triangle meshes that represent detailed height data associated with the polygons in its associated polygon * Contains triangle meshes that represent detailed height data associated with the polygons in its associated polygon
* mesh object. * mesh object.
*/ */
public class PolyMeshDetail public class PolyMeshDetail
{ {
/** The sub-mesh data. [Size: 4*#nmeshes] */ /** The sub-mesh data. [Size: 4*#nmeshes] */
public int[] meshes; public int[] meshes;
@ -45,5 +43,5 @@ public class PolyMeshDetail
/** The number of triangles in #tris. */ /** The number of triangles in #tris. */
public int ntris; public int ntris;
} }
} }

View File

@ -22,12 +22,10 @@ using System;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RecastConstants;
public class Recast
using static RecastConstants; {
public class Recast
{
void calcBounds(float[] verts, int nv, float[] bmin, float[] bmax) void calcBounds(float[] verts, int nv, float[] bmin, float[] bmax)
{ {
for (int i = 0; i < 3; i++) for (int i = 0; i < 3; i++)
@ -121,5 +119,5 @@ public class Recast
areas[i] = RC_NULL_AREA; areas[i] = RC_NULL_AREA;
} }
} }
} }
} }

View File

@ -22,12 +22,10 @@ using System;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RecastConstants;
public class RecastArea
using static RecastConstants; {
public class RecastArea
{
/// @par /// @par
/// ///
/// Basically, any spans that are closer to a boundary or obstruction than the specified radius /// Basically, any spans that are closer to a boundary or obstruction than the specified radius
@ -584,5 +582,5 @@ public class RecastArea
ctx.stopTimer("MARK_CYLINDER_AREA"); ctx.stopTimer("MARK_CYLINDER_AREA");
} }
} }
} }

View File

@ -27,10 +27,8 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class RecastBuilder
{
public class RecastBuilder
{
public interface RecastBuilderProgressListener public interface RecastBuilderProgressListener
{ {
void onProgress(int completed, int total); void onProgress(int completed, int total);
@ -48,7 +46,8 @@ public class RecastBuilder
this.progressListener = progressListener; 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[] bmin = geom.getMeshBoundsMin();
float[] bmax = geom.getMeshBoundsMax(); float[] bmax = geom.getMeshBoundsMax();
int[] twh = Recast.calcTileCount(bmin, bmax, cfg.cs, cfg.tileSizeX, cfg.tileSizeZ); int[] twh = Recast.calcTileCount(bmin, bmax, cfg.cs, cfg.tileSizeX, cfg.tileSizeZ);
@ -58,7 +57,9 @@ public class RecastBuilder
if (null != taskFactory) if (null != taskFactory)
{ {
buildMultiThreadAsync(geom, cfg, bmin, bmax, tw, th, results, taskFactory, default); buildMultiThreadAsync(geom, cfg, bmin, bmax, tw, th, results, taskFactory, default);
} else { }
else
{
buildSingleThreadAsync(geom, cfg, bmin, bmax, tw, th, results); buildSingleThreadAsync(geom, cfg, bmin, bmax, tw, th, results);
} }
@ -319,5 +320,5 @@ public class RecastBuilder
CompactHeightfield chf = buildCompactHeightfield(geom, builderCfg.cfg, ctx, solid); CompactHeightfield chf = buildCompactHeightfield(geom, builderCfg.cfg, ctx, solid);
return RecastLayers.buildHeightfieldLayers(ctx, chf, builderCfg.cfg.walkableHeight); return RecastLayers.buildHeightfieldLayers(ctx, chf, builderCfg.cfg.walkableHeight);
} }
} }
} }

View File

@ -20,12 +20,10 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RecastVectors;
public class RecastBuilderConfig
using static RecastVectors; {
public class RecastBuilderConfig
{
public readonly RecastConfig cfg; public readonly RecastConfig cfg;
public readonly int tileX; public readonly int tileX;
@ -99,5 +97,5 @@ public class RecastBuilderConfig
height = wh[1]; height = wh[1];
} }
} }
} }
} }

View File

@ -1,9 +1,7 @@
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class RecastBuilderResult
{
public class RecastBuilderResult
{
public readonly int tileX; public readonly int tileX;
public readonly int tileZ; public readonly int tileZ;
private readonly CompactHeightfield chf; private readonly CompactHeightfield chf;
@ -55,6 +53,5 @@ public class RecastBuilderResult
{ {
return telemetry; return telemetry;
} }
} }
} }

View File

@ -22,10 +22,8 @@ using System;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class RecastCommon
{
public class RecastCommon
{
/// Gets neighbor connection data for the specified direction. /// Gets neighbor connection data for the specified direction.
/// @param[in] s The span to check. /// @param[in] s The span to check.
/// @param[in] dir The direction to check. [Limits: 0 <= value < 4] /// @param[in] dir The direction to check. [Limits: 0 <= value < 4]
@ -82,7 +80,5 @@ public class RecastCommon
{ {
return Math.Max(Math.Min(max, v), min); return Math.Max(Math.Min(max, v), min);
} }
}
}
} }

View File

@ -20,13 +20,11 @@ using System;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RecastConstants;
using static RecastVectors;
public class RecastCompact
using static RecastConstants; {
using static RecastVectors;
public class RecastCompact
{
private const int MAX_LAYERS = RC_NOT_CONNECTED - 1; private const int MAX_LAYERS = RC_NOT_CONNECTED - 1;
private const int MAX_HEIGHT = RecastConstants.SPAN_MAX_HEIGHT; private const int MAX_HEIGHT = RecastConstants.SPAN_MAX_HEIGHT;
@ -185,5 +183,5 @@ public class RecastCompact
return spanCount; return spanCount;
} }
} }
} }

View File

@ -22,12 +22,10 @@ using System;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RecastConstants;
public class RecastConfig
using static RecastConstants; {
public class RecastConfig
{
public readonly PartitionType partitionType; public readonly PartitionType partitionType;
public readonly bool useTiles; public readonly bool useTiles;
@ -185,5 +183,5 @@ public class RecastConfig
{ {
return 3 + (int)Math.Ceiling(agentRadius / cs); return 3 + (int)Math.Ceiling(agentRadius / cs);
} }
} }
} }

View File

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

View File

@ -23,12 +23,10 @@ using System.Collections.Generic;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RecastConstants;
public class RecastContour
using static RecastConstants; {
public class RecastContour
{
private class ContourRegion private class ContourRegion
{ {
public Contour outline; public Contour outline;
@ -1019,5 +1017,5 @@ public class RecastContour
ctx.stopTimer("CONTOURS"); ctx.stopTimer("CONTOURS");
return cset; return cset;
} }
} }
} }

View File

@ -21,14 +21,12 @@ using DotRecast.Core;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RecastConstants;
using static RecastVectors;
using static RecastCommon;
public class RecastFilledVolumeRasterization
using static RecastConstants; {
using static RecastVectors;
using static RecastCommon;
public class RecastFilledVolumeRasterization
{
private const float EPSILON = 0.00001f; private const float EPSILON = 0.00001f;
private static readonly int[] BOX_EDGES = new[] { 0, 1, 0, 2, 0, 4, 1, 3, 1, 5, 2, 3, 2, 6, 3, 7, 4, 5, 4, 6, 5, 7, 6, 7 }; private static readonly int[] BOX_EDGES = new[] { 0, 1, 0, 2, 0, 4, 1, 3, 1, 5, 2, 3, 2, 6, 3, 7, 4, 5, 4, 6, 5, 7, 6, 7 };
@ -801,5 +799,5 @@ public class RecastFilledVolumeRasterization
overlap = (amin[2] > bounds[5] || amax[2] < bounds[2]) ? false : overlap; overlap = (amin[2] > bounds[5] || amax[2] < bounds[2]) ? false : overlap;
return overlap; return overlap;
} }
} }
} }

View File

@ -22,12 +22,10 @@ using System;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RecastConstants;
public class RecastFilter
using static RecastConstants; {
public class RecastFilter
{
/// @par /// @par
/// ///
/// Allows the formation of walkable regions that will flow over low lying /// Allows the formation of walkable regions that will flow over low lying
@ -204,5 +202,5 @@ public class RecastFilter
ctx.stopTimer("FILTER_WALKABLE"); ctx.stopTimer("FILTER_WALKABLE");
} }
} }
} }

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -22,10 +22,8 @@ using System;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public static class RecastVectors
{
public static class RecastVectors
{
public static void min(float[] a, float[] b, int i) public static void min(float[] a, float[] b, int i)
{ {
a[0] = Math.Min(a[0], b[i + 0]); a[0] = Math.Min(a[0], b[i + 0]);
@ -97,5 +95,5 @@ public static class RecastVectors
{ {
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
} }
} }
} }

View File

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

View File

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

View File

@ -25,10 +25,8 @@ using DotRecast.Core;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class Telemetry
{
public class Telemetry
{
private readonly ThreadLocal<Dictionary<string, AtomicLong>> timerStart = new ThreadLocal<Dictionary<string, AtomicLong>>(() => new Dictionary<string, AtomicLong>()); private readonly ThreadLocal<Dictionary<string, AtomicLong>> timerStart = new ThreadLocal<Dictionary<string, AtomicLong>>(() => new Dictionary<string, AtomicLong>());
private readonly ConcurrentDictionary<string, AtomicLong> timerAccum = new ConcurrentDictionary<string, AtomicLong>(); private readonly ConcurrentDictionary<string, AtomicLong> timerAccum = new ConcurrentDictionary<string, AtomicLong>();
@ -56,5 +54,5 @@ public class Telemetry
Console.WriteLine(n + ": " + v.Read() / 1000000); Console.WriteLine(n + ": " + v.Read() / 1000000);
} }
} }
} }
} }

View File

@ -26,26 +26,33 @@ namespace DotRecast.Detour.Crowd.Test;
using static DetourCommon; using static DetourCommon;
public class AbstractCrowdTest { public class AbstractCrowdTest
{
protected readonly long[] startRefs = { 281474976710696L, 281474976710773L, 281474976710680L, 281474976710753L, protected readonly long[] startRefs =
281474976710733L }; {
281474976710696L, 281474976710773L, 281474976710680L, 281474976710753L,
281474976710733L
};
protected readonly long[] endRefs = { 281474976710721L, 281474976710767L, 281474976710758L, 281474976710731L, 281474976710772L }; 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.60652f, 10.197294f, -45.918674f },
new[] { 22.331268f, 10.197294f, -1.0401875f }, new[] { 22.331268f, 10.197294f, -1.0401875f },
new[] { 18.694363f, 15.803535f, -73.090416f }, new[] { 18.694363f, 15.803535f, -73.090416f },
new[] { 0.7453353f, 10.197294f, -5.94005f }, 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[] { 6.4576626f, 10.197294f, -18.33406f },
new[] { -5.8023443f, 0.19729415f, 3.008419f }, new[] { -5.8023443f, 0.19729415f, 3.008419f },
new[] { 38.423977f, 10.197294f, -0.116066754f }, new[] { 38.423977f, 10.197294f, -0.116066754f },
new[] { 0.8635526f, 10.197294f, -10.31032f }, 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 MeshData nmd;
protected NavMeshQuery query; protected NavMeshQuery query;
@ -54,7 +61,8 @@ public class AbstractCrowdTest {
protected List<CrowdAgent> agents; protected List<CrowdAgent> agents;
[SetUp] [SetUp]
public void setUp() { public void setUp()
{
nmd = new RecastTestMeshBuilder().getMeshData(); nmd = new RecastTestMeshBuilder().getMeshData();
navmesh = new NavMesh(nmd, 6, 0); navmesh = new NavMesh(nmd, 6, 0);
query = new NavMeshQuery(navmesh); query = new NavMeshQuery(navmesh);
@ -87,7 +95,8 @@ public class AbstractCrowdTest {
agents = new(); agents = new();
} }
protected CrowdAgentParams getAgentParams(int updateFlags, int obstacleAvoidanceType) { protected CrowdAgentParams getAgentParams(int updateFlags, int obstacleAvoidanceType)
{
CrowdAgentParams ap = new CrowdAgentParams(); CrowdAgentParams ap = new CrowdAgentParams();
ap.radius = 0.6f; ap.radius = 0.6f;
ap.height = 2f; ap.height = 2f;
@ -101,10 +110,13 @@ public class AbstractCrowdTest {
return ap; 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); CrowdAgentParams ap = getAgentParams(updateFlags, obstacleAvoidanceType);
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++)
for (int j = 0; j < size; j++) { {
for (int j = 0; j < size; j++)
{
float[] pos = new float[3]; float[] pos = new float[3];
pos[0] = startPos[0] + i * distance; pos[0] = startPos[0] + i * distance;
pos[1] = startPos[1]; 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(); float[] ext = crowd.getQueryExtents();
QueryFilter filter = crowd.getFilter(0); QueryFilter filter = crowd.getFilter(0);
if (adjust) { if (adjust)
foreach (CrowdAgent ag in crowd.getActiveAgents()) { {
foreach (CrowdAgent ag in crowd.getActiveAgents())
{
float[] vel = calcVel(ag.npos, pos, ag.option.maxSpeed); float[] vel = calcVel(ag.npos, pos, ag.option.maxSpeed);
crowd.requestMoveVelocity(ag, vel); crowd.requestMoveVelocity(ag, vel);
} }
} else { }
else
{
Result<FindNearestPolyResult> nearest = query.findNearestPoly(pos, ext, filter); 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()); 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); float[] vel = vSub(tgt, pos);
vel[1] = 0.0f; vel[1] = 0.0f;
vNormalize(vel); vNormalize(vel);
@ -138,13 +157,14 @@ public class AbstractCrowdTest {
return vel; return vel;
} }
protected void dumpActiveAgents(int i) { protected void dumpActiveAgents(int i)
{
Console.WriteLine(crowd.getActiveAgents().Count); 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.state + ", " + ag.targetState);
Console.WriteLine(ag.npos[0] + ", " + ag.npos[1] + ", " + ag.npos[2]); Console.WriteLine(ag.npos[0] + ", " + ag.npos[1] + ", " + ag.npos[2]);
Console.WriteLine(ag.nvel[0] + ", " + ag.nvel[1] + ", " + ag.nvel[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; namespace DotRecast.Detour.Crowd.Test;
public class Crowd1Test : AbstractCrowdTest { public class Crowd1Test : AbstractCrowdTest
{
static readonly float[][] EXPECTED_A1Q0TVTA = { static readonly float[][] EXPECTED_A1Q0TVTA =
{
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f }, 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.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f }, 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.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.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.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[] { 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.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f }, 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.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.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.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[] { 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.787214f, 10.197294f, -45.418335f, -2.987049f, 0.000000f, 1.824153f },
new[] { 21.189804f, 10.197294f, -45.053505f, -2.987050f, 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.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.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.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[] { 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.787214f, 10.197294f, -45.418335f, -2.987049f, 0.000000f, 1.824153f },
new[] { 21.189804f, 10.197294f, -45.053505f, -2.987050f, 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.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.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.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[] { 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.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f }, 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.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.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.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[] { 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.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f }, 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.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.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.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[] { 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.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f }, 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.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.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.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[] { 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.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f }, 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.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.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.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] [Test]
public void testAgent1Quality0TVTA() { public void testAgent1Quality0TVTA()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS 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_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]); addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]);
setMoveTarget(endPoss[0], false); 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); 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[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[1], Is.EqualTo(EXPECTED_A1Q0TVTA[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q0TVTA[i][2]).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] [Test]
public void testAgent1Quality0TVT() { public void testAgent1Quality0TVT()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO; | CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]); addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]);
setMoveTarget(endPoss[0], false); 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); 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[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[1], Is.EqualTo(EXPECTED_A1Q0TVT[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q0TVT[i][2]).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] [Test]
public void testAgent1Quality0TV() { public void testAgent1Quality0TV()
{
int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS; int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS;
addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]); addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]);
setMoveTarget(endPoss[0], false); 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); 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[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[1], Is.EqualTo(EXPECTED_A1Q0TV[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q0TV[i][2]).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] [Test]
public void testAgent1Quality0T() { public void testAgent1Quality0T()
{
int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO; int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]); addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]);
setMoveTarget(endPoss[0], false); 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); 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[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[1], Is.EqualTo(EXPECTED_A1Q0T[i][1]).Within(0.001));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q0T[i][2]).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] [Test]
public void testAgent1Quality1TVTA() { public void testAgent1Quality1TVTA()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS 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_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(1, 0.4f, updateFlags, 1, startPoss[0]); addAgentGrid(1, 0.4f, updateFlags, 1, startPoss[0]);
setMoveTarget(endPoss[0], false); 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); 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[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[1], Is.EqualTo(EXPECTED_A1Q1TVTA[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q1TVTA[i][2]).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] [Test]
public void testAgent1Quality2TVTA() { public void testAgent1Quality2TVTA()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS 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_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(1, 0.4f, updateFlags, 2, startPoss[0]); addAgentGrid(1, 0.4f, updateFlags, 2, startPoss[0]);
setMoveTarget(endPoss[0], false); 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); 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[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[1], Is.EqualTo(EXPECTED_A1Q2TVTA[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q2TVTA[i][2]).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] [Test]
public void testAgent1Quality3TVTA() { public void testAgent1Quality3TVTA()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS 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_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(1, 0.4f, updateFlags, 3, startPoss[0]); addAgentGrid(1, 0.4f, updateFlags, 3, startPoss[0]);
setMoveTarget(endPoss[0], false); 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); 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[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[1], Is.EqualTo(EXPECTED_A1Q3TVTA[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q3TVTA[i][2]).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] [Test]
public void testAgent1Quality3TVTAS() { public void testAgent1Quality3TVTAS()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS 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_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE
| CrowdAgentParams.DT_CROWD_SEPARATION; | CrowdAgentParams.DT_CROWD_SEPARATION;
addAgentGrid(1, 0.4f, updateFlags, 3, startPoss[0]); addAgentGrid(1, 0.4f, updateFlags, 3, startPoss[0]);
setMoveTarget(endPoss[0], false); 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); 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[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[1], Is.EqualTo(EXPECTED_A1Q3TVTAS[i][1]).Within(0.001f));
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q3TVTAS[i][2]).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; namespace DotRecast.Detour.Crowd.Test;
public class Crowd4Test : AbstractCrowdTest { public class Crowd4Test : AbstractCrowdTest
{
static readonly float[][] EXPECTED_A1Q2TVTA = { static readonly float[][] EXPECTED_A1Q2TVTA =
{
new[] { 23.275612f, 10.197294f, -46.233074f, 0.061640f, 0.000000f, 0.073828f }, 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.350517f, 10.197294f, -46.304905f, 0.030557f, 0.000000f, 0.118703f },
new[] { 23.347885f, 10.197294f, -46.331837f, -0.024102f, 0.000000f, -0.093108f }, 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.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.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.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.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.336805f, 10.197294f, -46.374985f, 0.119401f, 0.000000f, -0.031009f },
new[] { 23.351542f, 10.197294f, -46.410728f, 0.053426f, 0.000000f, -0.093556f }, 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.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.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.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.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[] { 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 }, 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.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.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.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] [Test]
public void testAgent1Quality2TVTA() { public void testAgent1Quality2TVTA()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS 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_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]); addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
setMoveTarget(endPoss[0], false); 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); crowd.update(1 / 5f, null);
CrowdAgent ag = agents[2]; CrowdAgent ag = agents[2];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTA[i][0]).Within(0.001f), $"{i}"); Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTA[i][0]).Within(0.001f), $"{i}");
@ -318,14 +326,16 @@ public class Crowd4Test : AbstractCrowdTest {
} }
[Test] [Test]
public void testAgent1Quality2TVTAS() { public void testAgent1Quality2TVTAS()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS 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_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE
| CrowdAgentParams.DT_CROWD_SEPARATION; | CrowdAgentParams.DT_CROWD_SEPARATION;
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]); addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
setMoveTarget(endPoss[0], false); 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); crowd.update(1 / 5f, null);
CrowdAgent ag = agents[2]; CrowdAgent ag = agents[2];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTAS[i][0]).Within(0.001f), $"{i}"); Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTAS[i][0]).Within(0.001f), $"{i}");
@ -338,7 +348,8 @@ public class Crowd4Test : AbstractCrowdTest {
} }
[Test] [Test]
public void testAgent1Quality2T() { public void testAgent1Quality2T()
{
int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO; int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]); addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
@ -352,12 +363,18 @@ public class Crowd4Test : AbstractCrowdTest {
crowd.update(1 / 5f, null); crowd.update(1 / 5f, null);
CrowdAgent ag = agents[2]; 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[0], Is.EqualTo(EXPECTED_A1Q2T[i][0]).Within(0.00001f), $"{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]}"); Console.WriteLine($"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}");
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.npos[1], Is.EqualTo(EXPECTED_A1Q2T[i][1]).Within(0.00001f), $"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}");
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]}"); Console.WriteLine($"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}");
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.npos[2], Is.EqualTo(EXPECTED_A1Q2T[i][2]).Within(0.00001f), $"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}");
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]}"); 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); Thread.Sleep(1);
} }
} }

View File

@ -22,10 +22,10 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test; namespace DotRecast.Detour.Crowd.Test;
public class Crowd4VelocityTest : AbstractCrowdTest { public class Crowd4VelocityTest : AbstractCrowdTest
{
static readonly float[][] EXPECTED_A1Q3TVTA = { static readonly float[][] EXPECTED_A1Q3TVTA =
{
new[] { 6.101694f, 10.197294f, -17.678480f, 0.000000f, 0.000000f, 0.000000f }, 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.024141f, 10.197294f, -17.589798f, -0.107331f, 0.000000f, 0.098730f },
new[] { 6.004839f, 10.197294f, -17.554886f, -0.096506f, 0.000000f, 0.174561f }, 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.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.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.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] [Test]
public void testAgent1Quality3TVTA() { public void testAgent1Quality3TVTA()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS 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_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(2, 0.3f, updateFlags, 3, endPoss[0]); addAgentGrid(2, 0.3f, updateFlags, 3, endPoss[0]);
setMoveTarget(endPoss[4], false); 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); crowd.update(1 / 5f, null);
if (i == 20) { if (i == 20)
{
setMoveTarget(startPoss[2], true); setMoveTarget(startPoss[2], true);
} }
CrowdAgent ag = agents[1]; CrowdAgent ag = agents[1];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q3TVTA[i][0]).Within(0.001f)); 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[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)); Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q3TVTA[i][5]).Within(0.001f));
} }
} }
} }

View File

@ -8,11 +8,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.4.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.4.0"/>
<PackageReference Include="NUnit.Analyzers" Version="3.5.0" /> <PackageReference Include="NUnit.Analyzers" Version="3.5.0"/>
<PackageReference Include="Moq" Version="4.18.4" /> <PackageReference Include="Moq" Version="4.18.4"/>
<PackageReference Include="coverlet.collector" Version="3.2.0"> <PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -20,9 +20,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Detour.Crowd\DotRecast.Detour.Crowd.csproj" /> <ProjectReference Include="..\..\src\DotRecast.Detour.Crowd\DotRecast.Detour.Crowd.csproj"/>
<ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj" /> <ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj"/>
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj" /> <ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -22,18 +22,20 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test; namespace DotRecast.Detour.Crowd.Test;
public class PathCorridorTest { public class PathCorridorTest
{
private readonly PathCorridor corridor = new PathCorridor(); private readonly PathCorridor corridor = new PathCorridor();
private readonly QueryFilter filter = new DefaultQueryFilter(); private readonly QueryFilter filter = new DefaultQueryFilter();
[SetUp] [SetUp]
public void setUp() { public void setUp()
corridor.reset(0, new float[] {10,20,30}); {
corridor.reset(0, new float[] { 10, 20, 30 });
} }
[Test] [Test]
public void shouldKeepOriginalPathInFindCornersWhenNothingCanBePruned() { public void shouldKeepOriginalPathInFindCornersWhenNothingCanBePruned()
{
List<StraightPathItem> straightPath = new(); List<StraightPathItem> straightPath = new();
straightPath.Add(new StraightPathItem(new float[] { 11, 20, 30.00001f }, 0, 0)); straightPath.Add(new StraightPathItem(new float[] { 11, 20, 30.00001f }, 0, 0));
straightPath.Add(new StraightPathItem(new float[] { 12, 20, 30.00002f }, 0, 0)); straightPath.Add(new StraightPathItem(new float[] { 12, 20, 30.00002f }, 0, 0));
@ -54,7 +56,8 @@ public class PathCorridorTest {
} }
[Test] [Test]
public void shouldPrunePathInFindCorners() { public void shouldPrunePathInFindCorners()
{
List<StraightPathItem> straightPath = new(); 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.00001f }, 0, 0)); // too close
straightPath.Add(new StraightPathItem(new float[] { 10, 20, 30.00002f }, 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.Count, Is.EqualTo(2));
Assert.That(path, Is.EqualTo(new List<StraightPathItem> { straightPath[2], straightPath[3] })); 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; namespace DotRecast.Detour.Crowd.Test;
public class RecastTestMeshBuilder { public class RecastTestMeshBuilder
{
private readonly MeshData meshData; private readonly MeshData meshData;
public const float m_cellSize = 0.3f; public const float m_cellSize = 0.3f;
public const float m_cellHeight = 0.2f; 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, 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, 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, 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, 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_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND); m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
@ -57,9 +58,11 @@ public class RecastTestMeshBuilder {
RecastBuilder rcBuilder = new RecastBuilder(); RecastBuilder rcBuilder = new RecastBuilder();
RecastBuilderResult rcResult = rcBuilder.build(m_geom, bcfg); RecastBuilderResult rcResult = rcBuilder.build(m_geom, bcfg);
PolyMesh m_pmesh = rcResult.getMesh(); 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; m_pmesh.flags[i] = 1;
} }
PolyMeshDetail m_dmesh = rcResult.getMeshDetail(); PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
NavMeshDataCreateParams option = new NavMeshDataCreateParams(); NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = m_pmesh.verts; option.verts = m_pmesh.verts;
@ -104,7 +107,8 @@ public class RecastTestMeshBuilder {
meshData = NavMeshBuilder.createNavMeshData(option); meshData = NavMeshBuilder.createNavMeshData(option);
} }
public MeshData getMeshData() { public MeshData getMeshData()
{
return meshData; return meshData;
} }
} }

View File

@ -20,8 +20,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Crowd.Test; 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_MASK = 0x07;
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1; public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2; 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, public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER, public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD, public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS, public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR, public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR,
SAMPLE_POLYAREA_TYPE_DOOR); SAMPLE_POLYAREA_TYPE_DOOR);
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP, public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP,
SAMPLE_POLYAREA_TYPE_JUMP); SAMPLE_POLYAREA_TYPE_JUMP);

View File

@ -6,11 +6,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.4.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.4.0"/>
<PackageReference Include="NUnit.Analyzers" Version="3.5.0" /> <PackageReference Include="NUnit.Analyzers" Version="3.5.0"/>
<PackageReference Include="Moq" Version="4.18.4" /> <PackageReference Include="Moq" Version="4.18.4"/>
<PackageReference Include="coverlet.collector" Version="3.2.0"> <PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -18,9 +18,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Detour.Dynamic\DotRecast.Detour.Dynamic.csproj" /> <ProjectReference Include="..\..\src\DotRecast.Detour.Dynamic\DotRecast.Detour.Dynamic.csproj"/>
<ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj" /> <ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj"/>
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj" /> <ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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

View File

@ -23,10 +23,11 @@ using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test.Io; namespace DotRecast.Detour.Dynamic.Test.Io;
public class VoxelFileReaderTest { public class VoxelFileReaderTest
{
[Test] [Test]
public void shouldReadSingleTileFile() { public void shouldReadSingleTileFile()
{
byte[] bytes = Loader.ToBytes("test.voxels"); byte[] bytes = Loader.ToBytes("test.voxels");
using var ms = new MemoryStream(bytes); using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms); using var bis = new BinaryReader(ms);
@ -34,7 +35,7 @@ public class VoxelFileReaderTest {
VoxelFileReader reader = new VoxelFileReader(); VoxelFileReader reader = new VoxelFileReader();
VoxelFile f = reader.read(bis); VoxelFile f = reader.read(bis);
Assert.That(f.useTiles, Is.False); Assert.That(f.useTiles, Is.False);
Assert.That(f.bounds, Is.EqualTo(new float[] {-100.0f, 0f, -100f, 100f, 5f, 100f})); Assert.That(f.bounds, Is.EqualTo(new float[] { -100.0f, 0f, -100f, 100f, 5f, 100f }));
Assert.That(f.cellSize, Is.EqualTo(0.25f)); Assert.That(f.cellSize, Is.EqualTo(0.25f));
Assert.That(f.walkableRadius, Is.EqualTo(0.5f)); Assert.That(f.walkableRadius, Is.EqualTo(0.5f));
Assert.That(f.walkableHeight, Is.EqualTo(2f)); Assert.That(f.walkableHeight, Is.EqualTo(2f));
@ -46,12 +47,13 @@ public class VoxelFileReaderTest {
Assert.That(f.tiles[0].cellHeight, Is.EqualTo(0.001f)); Assert.That(f.tiles[0].cellHeight, Is.EqualTo(0.001f));
Assert.That(f.tiles[0].width, Is.EqualTo(810)); Assert.That(f.tiles[0].width, Is.EqualTo(810));
Assert.That(f.tiles[0].depth, Is.EqualTo(810)); Assert.That(f.tiles[0].depth, Is.EqualTo(810));
Assert.That(f.tiles[0].boundsMin, Is.EqualTo(new float[] {-101.25f, 0f, -101.25f})); Assert.That(f.tiles[0].boundsMin, Is.EqualTo(new float[] { -101.25f, 0f, -101.25f }));
Assert.That(f.tiles[0].boundsMax, Is.EqualTo(new float[] {101.25f, 5.0f, 101.25f})); Assert.That(f.tiles[0].boundsMax, Is.EqualTo(new float[] { 101.25f, 5.0f, 101.25f }));
} }
[Test] [Test]
public void shouldReadMultiTileFile() { public void shouldReadMultiTileFile()
{
byte[] bytes = Loader.ToBytes("test_tiles.voxels"); byte[] bytes = Loader.ToBytes("test_tiles.voxels");
using var ms = new MemoryStream(bytes); using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms); using var bis = new BinaryReader(ms);

View File

@ -23,11 +23,12 @@ using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test.Io; namespace DotRecast.Detour.Dynamic.Test.Io;
public class VoxelFileReaderWriterTest { public class VoxelFileReaderWriterTest
{
[TestCase(false)] [TestCase(false)]
[TestCase(true)] [TestCase(true)]
public void shouldReadSingleTileFile(bool compression) { public void shouldReadSingleTileFile(bool compression)
{
byte[] bytes = Loader.ToBytes("test.voxels"); byte[] bytes = Loader.ToBytes("test.voxels");
using var ms = new MemoryStream(bytes); using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms); using var bis = new BinaryReader(ms);
@ -54,7 +55,8 @@ public class VoxelFileReaderWriterTest {
[TestCase(false)] [TestCase(false)]
[TestCase(true)] [TestCase(true)]
public void shouldReadMultiTileFile(bool compression) { public void shouldReadMultiTileFile(bool compression)
{
byte[] bytes = Loader.ToBytes("test_tiles.voxels"); byte[] bytes = Loader.ToBytes("test_tiles.voxels");
using var ms = new MemoryStream(bytes); using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms); using var bis = new BinaryReader(ms);
@ -62,7 +64,7 @@ public class VoxelFileReaderWriterTest {
VoxelFile f = readWriteRead(bis, compression); VoxelFile f = readWriteRead(bis, compression);
Assert.That(f.useTiles, Is.True); Assert.That(f.useTiles, Is.True);
Assert.That(f.bounds, Is.EqualTo(new[] {-100.0f, 0f, -100f, 100f, 5f, 100f})); Assert.That(f.bounds, Is.EqualTo(new[] { -100.0f, 0f, -100f, 100f, 5f, 100f }));
Assert.That(f.cellSize, Is.EqualTo(0.25f)); Assert.That(f.cellSize, Is.EqualTo(0.25f));
Assert.That(f.walkableRadius, Is.EqualTo(0.5f)); Assert.That(f.walkableRadius, Is.EqualTo(0.5f));
Assert.That(f.walkableHeight, Is.EqualTo(2f)); Assert.That(f.walkableHeight, Is.EqualTo(2f));
@ -78,12 +80,12 @@ public class VoxelFileReaderWriterTest {
Assert.That(f.tiles[0].spanData.Length, Is.EqualTo(104952)); Assert.That(f.tiles[0].spanData.Length, Is.EqualTo(104952));
Assert.That(f.tiles[5].spanData.Length, Is.EqualTo(109080)); Assert.That(f.tiles[5].spanData.Length, Is.EqualTo(109080));
Assert.That(f.tiles[18].spanData.Length, Is.EqualTo(113400)); 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].boundsMin, Is.EqualTo(new[] { -101.25f, 0f, -101.25f }));
Assert.That(f.tiles[0].boundsMax, Is.EqualTo(new[] {-78.75f, 5.0f, -78.75f})); 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(); VoxelFileReader reader = new VoxelFileReader();
VoxelFile f = reader.read(bis); VoxelFile f = reader.read(bis);
@ -96,5 +98,4 @@ public class VoxelFileReaderWriterTest {
using var brIn = new BinaryReader(msIn); using var brIn = new BinaryReader(msIn);
return reader.read(brIn); return reader.read(brIn);
} }
} }

View File

@ -20,8 +20,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Test; 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_MASK = 0x07;
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1; public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2; 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, public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER, public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD, public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS, public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR, public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR,
SAMPLE_POLYAREA_TYPE_DOOR); SAMPLE_POLYAREA_TYPE_DOOR);
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP, public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP,
SAMPLE_POLYAREA_TYPE_JUMP); SAMPLE_POLYAREA_TYPE_JUMP);

View File

@ -28,8 +28,8 @@ using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test; namespace DotRecast.Detour.Dynamic.Test;
public class VoxelQueryTest { public class VoxelQueryTest
{
private const int TILE_WIDTH = 100; private const int TILE_WIDTH = 100;
private const int TILE_DEPTH = 90; private const int TILE_DEPTH = 90;
private static readonly float[] ORIGIN = new float[] { 50, 10, 40 }; private static readonly float[] ORIGIN = new float[] { 50, 10, 40 };
@ -61,12 +61,13 @@ public class VoxelQueryTest {
query.raycast(start, end); query.raycast(start, end);
// Then // Then
hfProvider.Verify(mock => mock.Invoke(It.IsAny<int>(), It.IsAny<int>()), Times.Exactly(6)); hfProvider.Verify(mock => mock.Invoke(It.IsAny<int>(), It.IsAny<int>()), Times.Exactly(6));
Assert.That(captorX, Is.EqualTo(new[] { 0, 1, 1, 1, 2, 2})); Assert.That(captorX, Is.EqualTo(new[] { 0, 1, 1, 1, 2, 2 }));
Assert.That(captorZ, Is.EqualTo(new[] { 3, 3, 2, 1, 1, 0})); Assert.That(captorZ, Is.EqualTo(new[] { 3, 3, 2, 1, 1, 0 }));
} }
[Test] [Test]
public void shouldHandleRaycastWithoutObstacles() { public void shouldHandleRaycastWithoutObstacles()
{
DynamicNavMesh mesh = createDynaMesh(); DynamicNavMesh mesh = createDynaMesh();
VoxelQuery query = mesh.voxelQuery(); VoxelQuery query = mesh.voxelQuery();
float[] start = { 7.4f, 0.5f, -64.8f }; float[] start = { 7.4f, 0.5f, -64.8f };
@ -76,7 +77,8 @@ public class VoxelQueryTest {
} }
[Test] [Test]
public void shouldHandleRaycastWithObstacles() { public void shouldHandleRaycastWithObstacles()
{
DynamicNavMesh mesh = createDynaMesh(); DynamicNavMesh mesh = createDynaMesh();
VoxelQuery query = mesh.voxelQuery(); VoxelQuery query = mesh.voxelQuery();
float[] start = { 32.3f, 0.5f, 47.9f }; 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)); Assert.That(hit.Value, Is.EqualTo(0.5263836f).Within(1e-7f));
} }
private DynamicNavMesh createDynaMesh() { private DynamicNavMesh createDynaMesh()
{
var bytes = Loader.ToBytes("test_tiles.voxels"); var bytes = Loader.ToBytes("test_tiles.voxels");
using var ms = new MemoryStream(bytes); using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms); using var bis = new BinaryReader(ms);

View File

@ -6,11 +6,11 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.4.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.4.0"/>
<PackageReference Include="NUnit.Analyzers" Version="3.5.0" /> <PackageReference Include="NUnit.Analyzers" Version="3.5.0"/>
<PackageReference Include="Moq" Version="4.18.4" /> <PackageReference Include="Moq" Version="4.18.4"/>
<PackageReference Include="coverlet.collector" Version="3.2.0"> <PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -18,9 +18,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Detour.Extras\DotRecast.Detour.Extras.csproj" /> <ProjectReference Include="..\..\src\DotRecast.Detour.Extras\DotRecast.Detour.Extras.csproj"/>
<ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj" /> <ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj"/>
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj" /> <ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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

View File

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

View File

@ -8,10 +8,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.4.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.4.0"/>
<PackageReference Include="NUnit.Analyzers" Version="3.5.0" /> <PackageReference Include="NUnit.Analyzers" Version="3.5.0"/>
<PackageReference Include="coverlet.collector" Version="3.2.0"> <PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -19,8 +19,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj" /> <ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj"/>
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj" /> <ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -20,29 +20,33 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test; 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 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[] { 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[] { 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] [Test]
public void testFindNearestPoly() { public void testFindNearestPoly()
{
QueryFilter filter = new DefaultQueryFilter(); QueryFilter filter = new DefaultQueryFilter();
float[] extents = { 2, 4, 2 }; 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]; float[] startPos = startPoss[i];
Result<FindNearestPolyResult> poly = query.findNearestPoly(startPos, extents, filter); Result<FindNearestPolyResult> poly = query.findNearestPoly(startPos, extents, filter);
Assert.That(poly.succeeded(), Is.True); Assert.That(poly.succeeded(), Is.True);
Assert.That(poly.result.getNearestRef(), Is.EqualTo(POLY_REFS[i])); 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)); Assert.That(poly.result.getNearestPos()[v], Is.EqualTo(POLY_POS[i][v]).Within(0.001f));
} }
} }
} }
public class EmptyQueryFilter : QueryFilter public class EmptyQueryFilter : QueryFilter
@ -60,18 +64,20 @@ public class FindNearestPolyTest : AbstractDetourTest {
} }
[Test] [Test]
public void shouldReturnStartPosWhenNoPolyIsValid() { public void shouldReturnStartPosWhenNoPolyIsValid()
{
var filter = new EmptyQueryFilter(); var filter = new EmptyQueryFilter();
float[] extents = { 2, 4, 2 }; 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]; float[] startPos = startPoss[i];
Result<FindNearestPolyResult> poly = query.findNearestPoly(startPos, extents, filter); Result<FindNearestPolyResult> poly = query.findNearestPoly(startPos, extents, filter);
Assert.That(poly.succeeded(), Is.True); Assert.That(poly.succeeded(), Is.True);
Assert.That(poly.result.getNearestRef(), Is.EqualTo(0L)); 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)); 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; 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 long[][] RESULTS =
{
private static readonly Status[] STATUSES = { Status.SUCCSESS, Status.PARTIAL_RESULT, Status.SUCCSESS, Status.SUCCSESS, new[]
Status.SUCCSESS }; {
private static readonly long[][] RESULTS = { 281474976710696L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
new[] { 281474976710696L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
281474976710705L, 281474976710702L, 281474976710701L, 281474976710714L, 281474976710713L, 281474976710705L, 281474976710702L, 281474976710701L, 281474976710714L, 281474976710713L,
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710721L }, 281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710721L
new[] { 281474976710773L, 281474976710772L, 281474976710768L, 281474976710754L, 281474976710755L, },
new[]
{
281474976710773L, 281474976710772L, 281474976710768L, 281474976710754L, 281474976710755L,
281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L, 281474976710729L, 281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L, 281474976710729L,
281474976710717L, 281474976710724L, 281474976710728L, 281474976710737L, 281474976710738L, 281474976710717L, 281474976710724L, 281474976710728L, 281474976710737L, 281474976710738L,
281474976710736L, 281474976710733L, 281474976710735L, 281474976710742L, 281474976710740L, 281474976710736L, 281474976710733L, 281474976710735L, 281474976710742L, 281474976710740L,
281474976710746L, 281474976710745L, 281474976710744L }, 281474976710746L, 281474976710745L, 281474976710744L
new[] { 281474976710680L, 281474976710684L, 281474976710688L, 281474976710687L, 281474976710686L, },
new[]
{
281474976710680L, 281474976710684L, 281474976710688L, 281474976710687L, 281474976710686L,
281474976710697L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L, 281474976710697L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
281474976710705L, 281474976710702L, 281474976710701L, 281474976710714L, 281474976710713L, 281474976710705L, 281474976710702L, 281474976710701L, 281474976710714L, 281474976710713L,
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710729L, 281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710729L,
281474976710731L, 281474976710752L, 281474976710748L, 281474976710753L, 281474976710755L, 281474976710731L, 281474976710752L, 281474976710748L, 281474976710753L, 281474976710755L,
281474976710754L, 281474976710768L, 281474976710772L, 281474976710773L, 281474976710770L, 281474976710754L, 281474976710768L, 281474976710772L, 281474976710773L, 281474976710770L,
281474976710757L, 281474976710761L, 281474976710758L }, 281474976710757L, 281474976710761L, 281474976710758L
},
new[] { 281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L }, new[] { 281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L },
new[] { 281474976710733L, 281474976710736L, 281474976710738L, 281474976710737L, 281474976710728L, new[]
{
281474976710733L, 281474976710736L, 281474976710738L, 281474976710737L, 281474976710728L,
281474976710724L, 281474976710717L, 281474976710729L, 281474976710731L, 281474976710752L, 281474976710724L, 281474976710717L, 281474976710729L, 281474976710731L, 281474976710752L,
281474976710748L, 281474976710753L, 281474976710755L, 281474976710754L, 281474976710768L, 281474976710748L, 281474976710753L, 281474976710755L, 281474976710754L, 281474976710768L,
281474976710772L } }; 281474976710772L
}
};
private static readonly StraightPathItem[][] STRAIGHT_PATHS = { private static readonly StraightPathItem[][] STRAIGHT_PATHS =
new[] { new StraightPathItem(new float[] { 22.606520f, 10.197294f, -45.918674f }, 1, 281474976710696L), {
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[] { 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, -31.241272f }, 0, 281474976710712L),
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -29.741272f }, 0, 281474976710727L), 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[] { 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[] { 9.784786f, 10.197294f, -2.141273f }, 0, 281474976710755L),
new StraightPathItem(new float[] { 7.984783f, 10.197294f, -2.441269f }, 0, 281474976710753L), new StraightPathItem(new float[] { 7.984783f, 10.197294f, -2.441269f }, 0, 281474976710753L),
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -8.441269f }, 0, 281474976710752L), 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[] { -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, -11.441269f }, 0, 281474976710735L),
new StraightPathItem(new float[] { -17.815216f, 5.197294f, -8.441269f }, 0, 281474976710746L), 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.584785f, 10.197294f, -49.841274f }, 0, 281474976710697L),
new StraightPathItem(new float[] { 17.284786f, 10.197294f, -48.041275f }, 0, 281474976710695L), new StraightPathItem(new float[] { 17.284786f, 10.197294f, -48.041275f }, 0, 281474976710695L),
new StraightPathItem(new float[] { 16.084785f, 10.197294f, -45.341274f }, 0, 281474976710694L), 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[] { 1.984785f, 10.197294f, -8.441269f }, 0, 281474976710753L),
new StraightPathItem(new float[] { 7.984783f, 10.197294f, -2.441269f }, 0, 281474976710755L), 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[] { 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[]
new StraightPathItem(new float[] { 0.863553f, 10.197294f, -10.310320f }, 2, 0L) }, {
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[] { -11.815216f, 9.997294f, -17.441269f }, 0, 281474976710738L),
new StraightPathItem(new float[] { -10.015216f, 10.197294f, -17.741272f }, 0, 281474976710728L), 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[] { -8.215216f, 10.197294f, -17.441269f }, 0, 281474976710724L),
new StraightPathItem(new float[] { -4.315216f, 10.197294f, -15.341270f }, 0, 281474976710729L), 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[] { 1.984785f, 10.197294f, -8.441269f }, 0, 281474976710753L),
new StraightPathItem(new float[] { 7.984783f, 10.197294f, -2.441269f }, 0, 281474976710755L), 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] [Test]
public void testFindPath() { public void testFindPath()
{
QueryFilter filter = new DefaultQueryFilter(); 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 startRef = startRefs[i];
long endRef = endRefs[i]; long endRef = endRefs[i];
float[] startPos = startPoss[i]; float[] startPos = startPoss[i];
@ -102,40 +138,48 @@ public class FindPathTest : AbstractDetourTest {
Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter); Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter);
Assert.That(path.status, Is.EqualTo(STATUSES[i])); Assert.That(path.status, Is.EqualTo(STATUSES[i]));
Assert.That(path.result.Count, Is.EqualTo(RESULTS[i].Length)); 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])); Assert.That(path.result[j], Is.EqualTo(RESULTS[i][j]));
} }
} }
} }
[Test] [Test]
public void testFindPathSliced() { public void testFindPathSliced()
{
QueryFilter filter = new DefaultQueryFilter(); 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 startRef = startRefs[i];
long endRef = endRefs[i]; long endRef = endRefs[i];
float[] startPos = startPoss[i]; float[] startPos = startPoss[i];
float[] endPos = endPoss[i]; float[] endPos = endPoss[i];
query.initSlicedFindPath(startRef, endRef, startPos, endPos, filter, NavMeshQuery.DT_FINDPATH_ANY_ANGLE); query.initSlicedFindPath(startRef, endRef, startPos, endPos, filter, NavMeshQuery.DT_FINDPATH_ANY_ANGLE);
Status status = Status.IN_PROGRESS; Status status = Status.IN_PROGRESS;
while (status == Status.IN_PROGRESS) { while (status == Status.IN_PROGRESS)
{
Result<int> res = query.updateSlicedFindPath(10); Result<int> res = query.updateSlicedFindPath(10);
status = res.status; status = res.status;
} }
Result<List<long>> path = query.finalizeSlicedFindPath(); Result<List<long>> path = query.finalizeSlicedFindPath();
Assert.That(path.status, Is.EqualTo(STATUSES[i])); Assert.That(path.status, Is.EqualTo(STATUSES[i]));
Assert.That(path.result.Count, Is.EqualTo(RESULTS[i].Length)); 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])); Assert.That(path.result[j], Is.EqualTo(RESULTS[i][j]));
} }
} }
} }
[Test] [Test]
public void testFindPathStraight() { public void testFindPathStraight()
{
QueryFilter filter = new DefaultQueryFilter(); 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 startRef = startRefs[i];
long endRef = endRefs[i]; long endRef = endRefs[i];
float[] startPos = startPoss[i]; float[] startPos = startPoss[i];
@ -145,14 +189,16 @@ public class FindPathTest : AbstractDetourTest {
int.MaxValue, 0); int.MaxValue, 0);
List<StraightPathItem> straightPath = result.result; List<StraightPathItem> straightPath = result.result;
Assert.That(straightPath.Count, Is.EqualTo(STRAIGHT_PATHS[i].Length)); 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)); 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].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)); 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; namespace DotRecast.Detour.Test;
public class FindPolysAroundCircleTest : AbstractDetourTest
public class FindPolysAroundCircleTest : AbstractDetourTest { {
private static readonly long[][] REFS =
private static readonly long[][] REFS = { {
new[] { 281474976710696L, 281474976710695L, 281474976710694L, 281474976710691L, 281474976710697L, 281474976710693L, new[]
281474976710686L, 281474976710687L, 281474976710692L, 281474976710703L, 281474976710689L }, {
281474976710696L, 281474976710695L, 281474976710694L, 281474976710691L, 281474976710697L, 281474976710693L,
281474976710686L, 281474976710687L, 281474976710692L, 281474976710703L, 281474976710689L
},
new[] { 281474976710773L, 281474976710770L, 281474976710769L, 281474976710772L, 281474976710771L }, 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, 281474976710682L, 281474976710677L, 281474976710676L, 281474976710688L, 281474976710687L, 281474976710675L,
281474976710685L, 281474976710672L, 281474976710666L, 281474976710668L, 281474976710681L, 281474976710673L }, 281474976710685L, 281474976710672L, 281474976710666L, 281474976710668L, 281474976710681L, 281474976710673L
new[] { 281474976710753L, 281474976710748L, 281474976710755L, 281474976710756L, 281474976710750L, 281474976710752L, },
281474976710731L, 281474976710729L, 281474976710749L, 281474976710719L, 281474976710717L, 281474976710726L }, new[]
new[] { 281474976710733L, 281474976710735L, 281474976710736L, 281474976710734L, 281474976710739L, 281474976710742L, {
281474976710740L, 281474976710746L, 281474976710747L, } }; 281474976710753L, 281474976710748L, 281474976710755L, 281474976710756L, 281474976710750L, 281474976710752L,
private static readonly long[][] PARENT_REFS = { 281474976710731L, 281474976710729L, 281474976710749L, 281474976710719L, 281474976710717L, 281474976710726L
new[] { 0L, 281474976710696L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710697L, },
281474976710686L, 281474976710693L, 281474976710694L, 281474976710687L }, 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, 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, 281474976710683L, 281474976710678L, 281474976710684L, 281474976710688L, 281474976710677L, 281474976710687L,
281474976710682L, 281474976710672L, 281474976710672L, 281474976710675L, 281474976710666L }, 281474976710682L, 281474976710672L, 281474976710672L, 281474976710675L, 281474976710666L
new[] { 0L, 281474976710753L, 281474976710753L, 281474976710753L, 281474976710753L, 281474976710748L, 281474976710752L, },
281474976710731L, 281474976710756L, 281474976710729L, 281474976710729L, 281474976710717L }, new[]
new[] { 0L, 281474976710733L, 281474976710733L, 281474976710736L, 281474976710736L, 281474976710735L, 281474976710742L, {
281474976710740L, 281474976710746L } }; 0L, 281474976710753L, 281474976710753L, 281474976710753L, 281474976710753L, 281474976710748L, 281474976710752L,
private static readonly float[][] COSTS = { 281474976710731L, 281474976710756L, 281474976710729L, 281474976710729L, 281474976710717L
new[] { 0.000000f, 0.391453f, 6.764245f, 4.153431f, 3.721995f, 6.109188f, 5.378797f, 7.178796f, 7.009186f, 7.514245f, },
12.655564f }, 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, 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, new[]
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, 0.000000f, 1.162604f, 1.954029f, 2.776051f, 2.046001f, 2.428367f, 6.429493f, 6.032851f, 2.878368f, 5.333885f,
8.793867f, 13.146453f }, 6.394545f, 9.596563f, 12.457960f, 7.096575f, 10.413582f, 10.362305f, 10.665442f, 10.593861f
new[] { 0.000000f, 2.480514f, 0.823685f, 5.002500f, 8.229258f, 3.983844f, 5.483844f, 6.655379f, 11.996962f } }; },
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] [Test]
public void testFindPolysAroundCircle() { public void testFindPolysAroundCircle()
{
QueryFilter filter = new DefaultQueryFilter(); 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 startRef = startRefs[i];
float[] startPos = startPoss[i]; float[] startPos = startPoss[i];
Result<FindPolysAroundResult> result = query.findPolysAroundCircle(startRef, startPos, 7.5f, filter); Result<FindPolysAroundResult> result = query.findPolysAroundCircle(startRef, startPos, 7.5f, filter);
Assert.That(result.succeeded(), Is.True); Assert.That(result.succeeded(), Is.True);
FindPolysAroundResult polys = result.result; FindPolysAroundResult polys = result.result;
Assert.That(polys.getRefs().Count, Is.EqualTo(REFS[i].Length)); 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; bool found = false;
for (int w = 0; w < REFS[i].Length; w++) { for (int w = 0; w < REFS[i].Length; w++)
if (REFS[i][v] == polys.getRefs()[w]) { {
if (REFS[i][v] == polys.getRefs()[w])
{
Assert.That(polys.getParentRefs()[w], Is.EqualTo(PARENT_REFS[i][v])); Assert.That(polys.getParentRefs()[w], Is.EqualTo(PARENT_REFS[i][v]));
Assert.That(polys.getCosts()[w], Is.EqualTo(COSTS[i][v]).Within(0.01f)); Assert.That(polys.getCosts()[w], Is.EqualTo(COSTS[i][v]).Within(0.01f));
found = true; found = true;
} }
} }
Assert.That(found, Is.True, $"Ref not found {REFS[i][v]}"); Assert.That(found, Is.True, $"Ref not found {REFS[i][v]}");
} }
} }
} }
} }

View File

@ -20,91 +20,139 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
public class FindPolysAroundShapeTest : AbstractDetourTest
public class FindPolysAroundShapeTest : AbstractDetourTest { {
private static readonly long[][] REFS =
private static readonly long[][] REFS = { {
new[] { 281474976710696L, 281474976710695L, 281474976710694L, 281474976710691L, 281474976710697L, new[]
{
281474976710696L, 281474976710695L, 281474976710694L, 281474976710691L, 281474976710697L,
281474976710693L, 281474976710692L, 281474976710703L, 281474976710706L, 281474976710699L, 281474976710693L, 281474976710692L, 281474976710703L, 281474976710706L, 281474976710699L,
281474976710705L, 281474976710698L, 281474976710700L, 281474976710704L }, 281474976710705L, 281474976710698L, 281474976710700L, 281474976710704L
new[] { 281474976710773L, 281474976710769L, 281474976710772L, 281474976710768L, 281474976710771L, },
new[]
{
281474976710773L, 281474976710769L, 281474976710772L, 281474976710768L, 281474976710771L,
281474976710754L, 281474976710755L, 281474976710753L, 281474976710751L, 281474976710756L, 281474976710754L, 281474976710755L, 281474976710753L, 281474976710751L, 281474976710756L,
281474976710749L }, 281474976710749L
new[] { 281474976710680L, 281474976710679L, 281474976710684L, 281474976710683L, 281474976710688L, },
new[]
{
281474976710680L, 281474976710679L, 281474976710684L, 281474976710683L, 281474976710688L,
281474976710678L, 281474976710676L, 281474976710687L, 281474976710690L, 281474976710686L, 281474976710678L, 281474976710676L, 281474976710687L, 281474976710690L, 281474976710686L,
281474976710689L, 281474976710685L, 281474976710697L, 281474976710695L, 281474976710694L, 281474976710689L, 281474976710685L, 281474976710697L, 281474976710695L, 281474976710694L,
281474976710691L, 281474976710696L, 281474976710693L, 281474976710692L, 281474976710703L, 281474976710691L, 281474976710696L, 281474976710693L, 281474976710692L, 281474976710703L,
281474976710706L, 281474976710699L, 281474976710705L, 281474976710700L, 281474976710704L }, 281474976710706L, 281474976710699L, 281474976710705L, 281474976710700L, 281474976710704L
},
new[] { 281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L }, new[] { 281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L },
new[] { 281474976710733L, 281474976710735L, 281474976710736L, 281474976710742L, 281474976710734L, new[]
{
281474976710733L, 281474976710735L, 281474976710736L, 281474976710742L, 281474976710734L,
281474976710739L, 281474976710738L, 281474976710740L, 281474976710746L, 281474976710743L, 281474976710739L, 281474976710738L, 281474976710740L, 281474976710746L, 281474976710743L,
281474976710745L, 281474976710741L, 281474976710747L, 281474976710737L, 281474976710732L, 281474976710745L, 281474976710741L, 281474976710747L, 281474976710737L, 281474976710732L,
281474976710728L, 281474976710724L, 281474976710744L, 281474976710725L, 281474976710717L, 281474976710728L, 281474976710724L, 281474976710744L, 281474976710725L, 281474976710717L,
281474976710729L, 281474976710726L, 281474976710721L, 281474976710719L, 281474976710731L, 281474976710729L, 281474976710726L, 281474976710721L, 281474976710719L, 281474976710731L,
281474976710720L, 281474976710752L, 281474976710748L, 281474976710753L, 281474976710755L, 281474976710720L, 281474976710752L, 281474976710748L, 281474976710753L, 281474976710755L,
281474976710756L, 281474976710750L, 281474976710749L, 281474976710754L, 281474976710751L, 281474976710756L, 281474976710750L, 281474976710749L, 281474976710754L, 281474976710751L,
281474976710768L, 281474976710772L, 281474976710773L, 281474976710771L, 281474976710769L } }; 281474976710768L, 281474976710772L, 281474976710773L, 281474976710771L, 281474976710769L
private static readonly long[][] PARENT_REFS = { }
new[] { 0L, 281474976710696L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710695L, };
private static readonly long[][] PARENT_REFS =
{
new[]
{
0L, 281474976710696L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710695L,
281474976710693L, 281474976710694L, 281474976710703L, 281474976710706L, 281474976710706L, 281474976710693L, 281474976710694L, 281474976710703L, 281474976710706L, 281474976710706L,
281474976710705L, 281474976710705L, 281474976710705L }, 281474976710705L, 281474976710705L, 281474976710705L
new[] { 0L, 281474976710773L, 281474976710773L, 281474976710772L, 281474976710772L, 281474976710768L, },
281474976710754L, 281474976710755L, 281474976710755L, 281474976710753L, 281474976710756L }, new[]
new[] { 0L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710684L, 281474976710679L, {
0L, 281474976710773L, 281474976710773L, 281474976710772L, 281474976710772L, 281474976710768L,
281474976710754L, 281474976710755L, 281474976710755L, 281474976710753L, 281474976710756L
},
new[]
{
0L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710684L, 281474976710679L,
281474976710678L, 281474976710688L, 281474976710687L, 281474976710687L, 281474976710687L, 281474976710678L, 281474976710688L, 281474976710687L, 281474976710687L, 281474976710687L,
281474976710687L, 281474976710686L, 281474976710697L, 281474976710695L, 281474976710695L, 281474976710687L, 281474976710686L, 281474976710697L, 281474976710695L, 281474976710695L,
281474976710695L, 281474976710695L, 281474976710693L, 281474976710694L, 281474976710703L, 281474976710695L, 281474976710695L, 281474976710693L, 281474976710694L, 281474976710703L,
281474976710706L, 281474976710706L, 281474976710705L, 281474976710705L }, 281474976710706L, 281474976710706L, 281474976710705L, 281474976710705L
},
new[] { 0L, 281474976710753L, 281474976710748L, 281474976710752L }, new[] { 0L, 281474976710753L, 281474976710748L, 281474976710752L },
new[] { 0L, 281474976710733L, 281474976710733L, 281474976710735L, 281474976710736L, 281474976710736L, new[]
{
0L, 281474976710733L, 281474976710733L, 281474976710735L, 281474976710736L, 281474976710736L,
281474976710736L, 281474976710742L, 281474976710740L, 281474976710746L, 281474976710746L, 281474976710736L, 281474976710742L, 281474976710740L, 281474976710746L, 281474976710746L,
281474976710746L, 281474976710746L, 281474976710738L, 281474976710738L, 281474976710737L, 281474976710746L, 281474976710746L, 281474976710738L, 281474976710738L, 281474976710737L,
281474976710728L, 281474976710745L, 281474976710724L, 281474976710724L, 281474976710717L, 281474976710728L, 281474976710745L, 281474976710724L, 281474976710724L, 281474976710717L,
281474976710717L, 281474976710717L, 281474976710729L, 281474976710729L, 281474976710721L, 281474976710717L, 281474976710717L, 281474976710729L, 281474976710729L, 281474976710721L,
281474976710731L, 281474976710752L, 281474976710748L, 281474976710753L, 281474976710753L, 281474976710731L, 281474976710752L, 281474976710748L, 281474976710753L, 281474976710753L,
281474976710753L, 281474976710756L, 281474976710755L, 281474976710755L, 281474976710754L, 281474976710753L, 281474976710756L, 281474976710755L, 281474976710755L, 281474976710754L,
281474976710768L, 281474976710772L, 281474976710772L, 281474976710773L } }; 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, private static readonly float[][] COSTS =
31.691311f, 33.176235f }, {
new[] { 0.000000f, 36.657764f, 35.197689f, 37.484924f, 37.755524f, 37.132103f, 37.582104f, 38.816185f, 52.426109f, 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, 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, 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, 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, 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, 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] [Test]
public void testFindPolysAroundShape() { public void testFindPolysAroundShape()
{
QueryFilter filter = new DefaultQueryFilter(); 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 startRef = startRefs[i];
float[] startPos = startPoss[i]; float[] startPos = startPoss[i];
Result<FindPolysAroundResult> polys = query.findPolysAroundShape(startRef, Result<FindPolysAroundResult> polys = query.findPolysAroundShape(startRef,
getQueryPoly(startPos, endPoss[i]), filter); getQueryPoly(startPos, endPoss[i]), filter);
Assert.That(polys.result.getRefs().Count, Is.EqualTo(REFS[i].Length)); 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; bool found = false;
for (int w = 0; w < REFS[i].Length; w++) { for (int w = 0; w < REFS[i].Length; w++)
if (REFS[i][v] == polys.result.getRefs()[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.getParentRefs()[w], Is.EqualTo(PARENT_REFS[i][v]));
Assert.That(polys.result.getCosts()[w], Is.EqualTo(COSTS[i][v]).Within(0.01f)); Assert.That(polys.result.getCosts()[w], Is.EqualTo(COSTS[i][v]).Within(0.01f));
found = true; found = true;
} }
} }
Assert.That(found, Is.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 nx = (m_epos[2] - m_spos[2]) * 0.25f;
float nz = -(m_epos[0] - m_spos[0]) * 0.25f; float nz = -(m_epos[0] - m_spos[0]) * 0.25f;
float agentHeight = 2.0f; float agentHeight = 2.0f;
@ -127,5 +175,4 @@ public class FindPolysAroundShapeTest : AbstractDetourTest {
m_queryPoly[11] = m_epos[2] + nz; m_queryPoly[11] = m_epos[2] + nz;
return m_queryPoly; return m_queryPoly;
} }
} }

View File

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

View File

@ -23,39 +23,44 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test.Io; namespace DotRecast.Detour.Test.Io;
public class MeshDataReaderWriterTest
public class MeshDataReaderWriterTest { {
private const int VERTS_PER_POLYGON = 6; private const int VERTS_PER_POLYGON = 6;
private MeshData meshData; private MeshData meshData;
[SetUp] [SetUp]
public void setUp() { public void setUp()
{
RecastTestMeshBuilder rcBuilder = new RecastTestMeshBuilder(); RecastTestMeshBuilder rcBuilder = new RecastTestMeshBuilder();
meshData = rcBuilder.getMeshData(); meshData = rcBuilder.getMeshData();
} }
[Test] [Test]
public void testCCompatibility() { public void testCCompatibility()
{
test(true, ByteOrder.BIG_ENDIAN); test(true, ByteOrder.BIG_ENDIAN);
} }
[Test] [Test]
public void testCompact() { public void testCompact()
{
test(false, ByteOrder.BIG_ENDIAN); test(false, ByteOrder.BIG_ENDIAN);
} }
[Test] [Test]
public void testCCompatibilityLE() { public void testCCompatibilityLE()
{
test(true, ByteOrder.LITTLE_ENDIAN); test(true, ByteOrder.LITTLE_ENDIAN);
} }
[Test] [Test]
public void testCompactLE() { public void testCompactLE()
{
test(false, ByteOrder.LITTLE_ENDIAN); 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 ms = new MemoryStream();
using var bwos = new BinaryWriter(ms); 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.detailVertCount, Is.EqualTo(meshData.header.detailVertCount));
Assert.That(readData.header.bvNodeCount, Is.EqualTo(meshData.header.bvNodeCount)); Assert.That(readData.header.bvNodeCount, Is.EqualTo(meshData.header.bvNodeCount));
Assert.That(readData.header.offMeshConCount, Is.EqualTo(meshData.header.offMeshConCount)); 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])); 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].vertCount, Is.EqualTo(meshData.polys[i].vertCount));
Assert.That(readData.polys[i].areaAndtype, Is.EqualTo(meshData.polys[i].areaAndtype)); 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].verts[j], Is.EqualTo(meshData.polys[i].verts[j]));
Assert.That(readData.polys[i].neis[j], Is.EqualTo(meshData.polys[i].neis[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].vertBase, Is.EqualTo(meshData.detailMeshes[i].vertBase));
Assert.That(readData.detailMeshes[i].vertCount, Is.EqualTo(meshData.detailMeshes[i].vertCount)); 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].triBase, Is.EqualTo(meshData.detailMeshes[i].triBase));
Assert.That(readData.detailMeshes[i].triCount, Is.EqualTo(meshData.detailMeshes[i].triCount)); 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])); 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])); 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)); 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].bmin[j], Is.EqualTo(meshData.bvTree[i].bmin[j]));
Assert.That(readData.bvTree[i].bmax[j], Is.EqualTo(meshData.bvTree[i].bmax[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].flags, Is.EqualTo(meshData.offMeshCons[i].flags));
Assert.That(readData.offMeshCons[i].rad, Is.EqualTo(meshData.offMeshCons[i].rad)); 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].poly, Is.EqualTo(meshData.offMeshCons[i].poly));
Assert.That(readData.offMeshCons[i].side, Is.EqualTo(meshData.offMeshCons[i].side)); Assert.That(readData.offMeshCons[i].side, Is.EqualTo(meshData.offMeshCons[i].side));
Assert.That(readData.offMeshCons[i].userId, Is.EqualTo(meshData.offMeshCons[i].userId)); 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])); 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; namespace DotRecast.Detour.Test.Io;
public class MeshSetReaderTest { public class MeshSetReaderTest
{
private readonly MeshSetReader reader = new MeshSetReader(); private readonly MeshSetReader reader = new MeshSetReader();
[Test] [Test]
public void testNavmesh() { public void testNavmesh()
{
byte[] @is = Loader.ToBytes("all_tiles_navmesh.bin"); byte[] @is = Loader.ToBytes("all_tiles_navmesh.bin");
using var ms = new MemoryStream(@is); using var ms = new MemoryStream(@is);
using var bris = new BinaryReader(ms); using var bris = new BinaryReader(ms);
@ -56,7 +57,8 @@ public class MeshSetReaderTest {
} }
[Test] [Test]
public void testDungeon() { public void testDungeon()
{
byte[] @is = Loader.ToBytes("dungeon_all_tiles_navmesh.bin"); byte[] @is = Loader.ToBytes("dungeon_all_tiles_navmesh.bin");
using var ms = new MemoryStream(@is); using var ms = new MemoryStream(@is);
using var bris = new BinaryReader(ms); using var bris = new BinaryReader(ms);
@ -84,7 +86,8 @@ public class MeshSetReaderTest {
} }
[Test] [Test]
public void testDungeon32Bit() { public void testDungeon32Bit()
{
byte[] @is = Loader.ToBytes("dungeon_all_tiles_navmesh_32bit.bin"); byte[] @is = Loader.ToBytes("dungeon_all_tiles_navmesh_32bit.bin");
using var ms = new MemoryStream(@is); using var ms = new MemoryStream(@is);
using var bris = new BinaryReader(ms); using var bris = new BinaryReader(ms);

View File

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

View File

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

View File

@ -20,18 +20,19 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
public class NavMeshBuilderTest
public class NavMeshBuilderTest { {
private MeshData nmd; private MeshData nmd;
[SetUp] [SetUp]
public void setUp() { public void setUp()
{
nmd = new RecastTestMeshBuilder().getMeshData(); nmd = new RecastTestMeshBuilder().getMeshData();
} }
[Test] [Test]
public void testBVTree() { public void testBVTree()
{
Assert.That(nmd.verts.Length / 3, Is.EqualTo(225)); Assert.That(nmd.verts.Length / 3, Is.EqualTo(225));
Assert.That(nmd.polys.Length, Is.EqualTo(119)); Assert.That(nmd.polys.Length, Is.EqualTo(119));
Assert.That(nmd.header.maxLinkCount, Is.EqualTo(457)); 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.header.offMeshBase, Is.EqualTo(118));
Assert.That(nmd.bvTree.Length, Is.EqualTo(236)); Assert.That(nmd.bvTree.Length, Is.EqualTo(236));
Assert.That(nmd.bvTree.Length, Is.GreaterThanOrEqualTo(nmd.header.bvNodeCount)); 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); 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.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].rad, Is.EqualTo(0.1f));
Assert.That(nmd.offMeshCons[0].poly, Is.EqualTo(118)); Assert.That(nmd.offMeshCons[0].poly, Is.EqualTo(118));
Assert.That(nmd.offMeshCons[0].flags, Is.EqualTo(NavMesh.DT_OFFMESH_CON_BIDIR)); 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].flags, Is.EqualTo(12));
Assert.That(nmd.polys[118].getArea(), Is.EqualTo(2)); Assert.That(nmd.polys[118].getArea(), Is.EqualTo(2));
Assert.That(nmd.polys[118].getType(), Is.EqualTo(Poly.DT_POLYTYPE_OFFMESH_CONNECTION)); 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; namespace DotRecast.Detour.Test;
public class PolygonByCircleConstraintTest
public class PolygonByCircleConstraintTest { {
private readonly PolygonByCircleConstraint constraint = new PolygonByCircleConstraint.StrictPolygonByCircleConstraint(); private readonly PolygonByCircleConstraint constraint = new PolygonByCircleConstraint.StrictPolygonByCircleConstraint();
[Test] [Test]
public void shouldHandlePolygonFullyInsideCircle() { public void shouldHandlePolygonFullyInsideCircle()
{
float[] polygon = { -2, 0, 2, 2, 0, 2, 2, 0, -2, -2, 0, -2 }; float[] polygon = { -2, 0, 2, 2, 0, 2, 2, 0, -2, -2, 0, -2 };
float[] center = { 1, 0, 1 }; float[] center = { 1, 0, 1 };
float[] constrained = constraint.aply(polygon, center, 6); float[] constrained = constraint.aply(polygon, center, 6);
@ -35,18 +35,20 @@ public class PolygonByCircleConstraintTest {
} }
[Test] [Test]
public void shouldHandleVerticalSegment() { public void shouldHandleVerticalSegment()
{
int expectedSize = 21; int expectedSize = 21;
float[] polygon = { -2, 0, 2, 2, 0, 2, 2, 0, -2, -2, 0, -2 }; float[] polygon = { -2, 0, 2, 2, 0, 2, 2, 0, -2, -2, 0, -2 };
float[] center = { 2, 0, 0 }; float[] center = { 2, 0, 0 };
float[] constrained = constraint.aply(polygon, center, 3); float[] constrained = constraint.aply(polygon, center, 3);
Assert.That(constrained.Length, Is.EqualTo(expectedSize)); Assert.That(constrained.Length, Is.EqualTo(expectedSize));
Assert.That(constrained, Is.SupersetOf(new[] {2f, 0f, 2f, 2f, 0f, -2f})); Assert.That(constrained, Is.SupersetOf(new[] { 2f, 0f, 2f, 2f, 0f, -2f }));
} }
[Test] [Test]
public void shouldHandleCircleFullyInsidePolygon() { public void shouldHandleCircleFullyInsidePolygon()
{
int expectedSize = 12 * 3; int expectedSize = 12 * 3;
float[] polygon = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 }; float[] polygon = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
float[] center = { -1, 0, -1 }; float[] center = { -1, 0, -1 };
@ -54,7 +56,8 @@ public class PolygonByCircleConstraintTest {
Assert.That(constrained.Length, Is.EqualTo(expectedSize)); 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 x = constrained[i] + 1;
float z = constrained[i + 2] + 1; float z = constrained[i + 2] + 1;
Assert.That(x * x + z * z, Is.EqualTo(4).Within(1e-4f)); Assert.That(x * x + z * z, Is.EqualTo(4).Within(1e-4f));
@ -62,7 +65,8 @@ public class PolygonByCircleConstraintTest {
} }
[Test] [Test]
public void shouldHandleCircleInsidePolygon() { public void shouldHandleCircleInsidePolygon()
{
int expectedSize = 9 * 3; int expectedSize = 9 * 3;
float[] polygon = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 }; float[] polygon = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
float[] center = { -2, 0, -1 }; float[] center = { -2, 0, -1 };
@ -82,6 +86,5 @@ public class PolygonByCircleConstraintTest {
Assert.That(constrained.Length, Is.EqualTo(expectedSize)); Assert.That(constrained.Length, Is.EqualTo(expectedSize));
Assert.That(constrained, Is.SupersetOf(new[] { 1.5358982f, 0f, 3f, 2f, 0f, 3f, 3f, 0f, -3f })); 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;
using System.Diagnostics; using System.Diagnostics;
using NUnit.Framework; using NUnit.Framework;
using static DotRecast.Detour.DetourCommon; using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
public class RandomPointTest : AbstractDetourTest { public class RandomPointTest : AbstractDetourTest
{
[Test] [Test]
public void testRandom() { public void testRandom()
{
NavMeshQuery.FRand f = new NavMeshQuery.FRand(1); NavMeshQuery.FRand f = new NavMeshQuery.FRand(1);
QueryFilter filter = new DefaultQueryFilter(); 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); Result<FindRandomPointResult> point = query.findRandomPoint(filter, f);
Assert.That(point.succeeded(), Is.True); Assert.That(point.succeeded(), Is.True);
Tuple<MeshTile, Poly> tileAndPoly = navmesh.getTileAndPolyByRef(point.result.getRandomRef()).result; Tuple<MeshTile, Poly> tileAndPoly = navmesh.getTileAndPolyByRef(point.result.getRandomRef()).result;
float[] bmin = new float[2]; float[] bmin = new float[2];
float[] bmax = 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; 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]); 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]); 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]); 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]); 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] >= bmin[0], Is.True);
Assert.That(point.result.getRandomPt()[0] <= bmax[0], Is.True); Assert.That(point.result.getRandomPt()[0] <= bmax[0], Is.True);
Assert.That(point.result.getRandomPt()[2] >= bmin[1], Is.True); Assert.That(point.result.getRandomPt()[2] >= bmin[1], Is.True);
@ -51,11 +54,13 @@ public class RandomPointTest : AbstractDetourTest {
} }
[Test] [Test]
public void testRandomAroundCircle() { public void testRandomAroundCircle()
{
NavMeshQuery.FRand f = new NavMeshQuery.FRand(1); NavMeshQuery.FRand f = new NavMeshQuery.FRand(1);
QueryFilter filter = new DefaultQueryFilter(); QueryFilter filter = new DefaultQueryFilter();
FindRandomPointResult point = query.findRandomPoint(filter, f).result; 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(), Result<FindRandomPointResult> result = query.findRandomPointAroundCircle(point.getRandomRef(), point.getRandomPt(),
5f, filter, f); 5f, filter, f);
Assert.That(result.failed(), Is.False); Assert.That(result.failed(), Is.False);
@ -63,13 +68,15 @@ public class RandomPointTest : AbstractDetourTest {
Tuple<MeshTile, Poly> tileAndPoly = navmesh.getTileAndPolyByRef(point.getRandomRef()).result; Tuple<MeshTile, Poly> tileAndPoly = navmesh.getTileAndPolyByRef(point.getRandomRef()).result;
float[] bmin = new float[2]; float[] bmin = new float[2];
float[] bmax = 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; 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]); 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]); 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]); 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]); 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] >= bmin[0], Is.True);
Assert.That(point.getRandomPt()[0] <= bmax[0], Is.True); Assert.That(point.getRandomPt()[0] <= bmax[0], Is.True);
Assert.That(point.getRandomPt()[2] >= bmin[1], Is.True); Assert.That(point.getRandomPt()[2] >= bmin[1], Is.True);
@ -78,12 +85,14 @@ public class RandomPointTest : AbstractDetourTest {
} }
[Test] [Test]
public void testRandomWithinCircle() { public void testRandomWithinCircle()
{
NavMeshQuery.FRand f = new NavMeshQuery.FRand(1); NavMeshQuery.FRand f = new NavMeshQuery.FRand(1);
QueryFilter filter = new DefaultQueryFilter(); QueryFilter filter = new DefaultQueryFilter();
FindRandomPointResult point = query.findRandomPoint(filter, f).result; FindRandomPointResult point = query.findRandomPoint(filter, f).result;
float radius = 5f; 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(), Result<FindRandomPointResult> result = query.findRandomPointWithinCircle(point.getRandomRef(), point.getRandomPt(),
radius, filter, f); radius, filter, f);
Assert.That(result.failed(), Is.False); Assert.That(result.failed(), Is.False);
@ -94,30 +103,37 @@ public class RandomPointTest : AbstractDetourTest {
} }
[Test] [Test]
public void testPerformance() { public void testPerformance()
{
NavMeshQuery.FRand f = new NavMeshQuery.FRand(1); NavMeshQuery.FRand f = new NavMeshQuery.FRand(1);
QueryFilter filter = new DefaultQueryFilter(); QueryFilter filter = new DefaultQueryFilter();
FindRandomPointResult point = query.findRandomPoint(filter, f).result; FindRandomPointResult point = query.findRandomPoint(filter, f).result;
float radius = 5f; float radius = 5f;
// jvm warmup // 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); 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); query.findRandomPointWithinCircle(point.getRandomRef(), point.getRandomPt(), radius, filter, f);
} }
long t1 = Stopwatch.GetTimestamp(); 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); query.findRandomPointAroundCircle(point.getRandomRef(), point.getRandomPt(), radius, filter, f);
} }
long t2 = Stopwatch.GetTimestamp(); 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); query.findRandomPointWithinCircle(point.getRandomRef(), point.getRandomPt(), radius, filter, f);
} }
long t3 = Stopwatch.GetTimestamp(); long t3 = Stopwatch.GetTimestamp();
Console.WriteLine("Random point around circle: " + (t2 - t1) / 1000000 + "ms"); Console.WriteLine("Random point around circle: " + (t2 - t1) / 1000000 + "ms");
Console.WriteLine("Random point within circle: " + (t3 - t2) / 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; namespace DotRecast.Detour.Test;
public class RecastTestMeshBuilder { public class RecastTestMeshBuilder
{
private readonly MeshData meshData; private readonly MeshData meshData;
private const float m_cellSize = 0.3f; private const float m_cellSize = 0.3f;
private const float m_cellHeight = 0.2f; 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, 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, 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, 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, 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_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND); m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
@ -58,9 +59,11 @@ public class RecastTestMeshBuilder {
RecastBuilder rcBuilder = new RecastBuilder(); RecastBuilder rcBuilder = new RecastBuilder();
RecastBuilderResult rcResult = rcBuilder.build(m_geom, bcfg); RecastBuilderResult rcResult = rcBuilder.build(m_geom, bcfg);
PolyMesh m_pmesh = rcResult.getMesh(); 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; m_pmesh.flags[i] = 1;
} }
PolyMeshDetail m_dmesh = rcResult.getMeshDetail(); PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
NavMeshDataCreateParams option = new NavMeshDataCreateParams(); NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = m_pmesh.verts; option.verts = m_pmesh.verts;
@ -105,7 +108,8 @@ public class RecastTestMeshBuilder {
meshData = NavMeshBuilder.createNavMeshData(option); meshData = NavMeshBuilder.createNavMeshData(option);
} }
public MeshData getMeshData() { public MeshData getMeshData()
{
return meshData; return meshData;
} }
} }

View File

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

View File

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

View File

@ -20,13 +20,12 @@ using System.Collections.Generic;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Recast; using DotRecast.Recast;
using DotRecast.Recast.Geom; using DotRecast.Recast.Geom;
using static DotRecast.Recast.RecastVectors; using static DotRecast.Recast.RecastVectors;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
public class TestTiledNavMeshBuilder { public class TestTiledNavMeshBuilder
{
private readonly NavMesh navMesh; private readonly NavMesh navMesh;
private const float m_cellSize = 0.3f; private const float m_cellSize = 0.3f;
private const float m_cellHeight = 0.2f; 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, 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, 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, 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 // Create empty nav mesh
NavMeshParams navMeshParams = new NavMeshParams(); NavMeshParams navMeshParams = new NavMeshParams();
copy(navMeshParams.orig, m_geom.getMeshBoundsMin()); copy(navMeshParams.orig, m_geom.getMeshBoundsMin());
@ -77,14 +76,19 @@ public class TestTiledNavMeshBuilder {
// Add tiles to nav mesh // Add tiles to nav mesh
foreach (RecastBuilderResult result in rcResult) { foreach (RecastBuilderResult result in rcResult)
{
PolyMesh pmesh = result.getMesh(); PolyMesh pmesh = result.getMesh();
if (pmesh.npolys == 0) { if (pmesh.npolys == 0)
{
continue; continue;
} }
for (int i = 0; i < pmesh.npolys; ++i) {
for (int i = 0; i < pmesh.npolys; ++i)
{
pmesh.flags[i] = 1; pmesh.flags[i] = 1;
} }
NavMeshDataCreateParams option = new NavMeshDataCreateParams(); NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = pmesh.verts; option.verts = pmesh.verts;
option.vertCount = pmesh.nverts; option.vertCount = pmesh.nverts;
@ -113,8 +117,8 @@ public class TestTiledNavMeshBuilder {
} }
} }
public NavMesh getNavMesh() { public NavMesh getNavMesh()
{
return navMesh; return navMesh;
} }
} }

View File

@ -21,16 +21,23 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
public class TiledFindPathTest { public class TiledFindPathTest
{
private static readonly Status[] STATUSES = { Status.SUCCSESS }; 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, 281475005022208L, 281475003973636L, 281475012362240L, 281475012362241L, 281475012362242L, 281475003973634L,
281475003973635L, 281475003973633L, 281475002925059L, 281475002925057L, 281475002925056L, 281474998730753L, 281475003973635L, 281475003973633L, 281475002925059L, 281475002925057L, 281475002925056L, 281474998730753L,
281474998730754L, 281474994536450L, 281474994536451L, 281474994536452L, 281474994536448L, 281474990342146L, 281474998730754L, 281474994536450L, 281474994536451L, 281474994536452L, 281474994536448L, 281474990342146L,
281474990342145L, 281474991390723L, 281474991390724L, 281474991390725L, 281474987196418L, 281474987196417L, 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[] START_REFS = { 281475015507969L };
protected static readonly long[] END_REFS = { 281474985099266L }; protected static readonly long[] END_REFS = { 281474985099266L };
protected static readonly float[][] START_POS = { new[] { 39.447338f, 9.998177f, -0.784811f } }; protected static readonly float[][] START_POS = { new[] { 39.447338f, 9.998177f, -0.784811f } };
@ -40,19 +47,23 @@ public class TiledFindPathTest {
protected NavMesh navmesh; protected NavMesh navmesh;
[SetUp] [SetUp]
public void setUp() { public void setUp()
{
navmesh = createNavMesh(); navmesh = createNavMesh();
query = new NavMeshQuery(navmesh); query = new NavMeshQuery(navmesh);
} }
protected NavMesh createNavMesh() { protected NavMesh createNavMesh()
{
return new TestTiledNavMeshBuilder().getNavMesh(); return new TestTiledNavMeshBuilder().getNavMesh();
} }
[Test] [Test]
public void testFindPath() { public void testFindPath()
{
QueryFilter filter = new DefaultQueryFilter(); 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 startRef = START_REFS[i];
long endRef = END_REFS[i]; long endRef = END_REFS[i];
float[] startPos = START_POS[i]; float[] startPos = START_POS[i];
@ -60,10 +71,10 @@ public class TiledFindPathTest {
Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter); Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter);
Assert.That(path.status, Is.EqualTo(STATUSES[i])); Assert.That(path.status, Is.EqualTo(STATUSES[i]));
Assert.That(path.result.Count, Is.EqualTo(RESULTS[i].Length)); 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])); 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.Core;
using DotRecast.Detour.TileCache.Io.Compress; using DotRecast.Detour.TileCache.Io.Compress;
using DotRecast.Recast.Geom; using DotRecast.Recast.Geom;
using static DotRecast.Detour.DetourCommon; using static DotRecast.Detour.DetourCommon;
using static DotRecast.Recast.RecastVectors; using static DotRecast.Recast.RecastVectors;
namespace DotRecast.Detour.TileCache.Test; namespace DotRecast.Detour.TileCache.Test;
public class AbstractTileCacheTest { public class AbstractTileCacheTest
{
private const int EXPECTED_LAYERS_PER_TILE = 4; private const int EXPECTED_LAYERS_PER_TILE = 4;
private readonly float m_cellSize = 0.3f; private readonly float m_cellSize = 0.3f;
private readonly float m_cellHeight = 0.2f; private readonly float m_cellHeight = 0.2f;
@ -38,15 +37,19 @@ public class AbstractTileCacheTest {
private readonly float m_edgeMaxError = 1.3f; private readonly float m_edgeMaxError = 1.3f;
private readonly int m_tileSize = 48; private readonly int m_tileSize = 48;
protected class TestTileCacheMeshProcess : TileCacheMeshProcess { protected class TestTileCacheMeshProcess : TileCacheMeshProcess
public void process(NavMeshDataCreateParams option) { {
for (int i = 0; i < option.polyCount; ++i) { public void process(NavMeshDataCreateParams option)
{
for (int i = 0; i < option.polyCount; ++i)
{
option.polyFlags[i] = 1; 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(); TileCacheParams option = new TileCacheParams();
int[] twh = Recast.Recast.calcTileCount(geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), m_cellSize, m_tileSize, m_tileSize); int[] twh = Recast.Recast.calcTileCount(geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), m_cellSize, m_tileSize, m_tileSize);
option.ch = m_cellHeight; option.ch = m_cellHeight;
@ -71,5 +74,4 @@ public class AbstractTileCacheTest {
TileCacheCompressorFactory.get(cCompatibility), new TestTileCacheMeshProcess()); TileCacheCompressorFactory.get(cCompatibility), new TestTileCacheMeshProcess());
return tc; return tc;
} }
} }

View File

@ -6,10 +6,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.4.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.4.0"/>
<PackageReference Include="NUnit.Analyzers" Version="3.5.0" /> <PackageReference Include="NUnit.Analyzers" Version="3.5.0"/>
<PackageReference Include="coverlet.collector" Version="3.2.0"> <PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -17,9 +17,9 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Detour.TileCache\DotRecast.Detour.TileCache.csproj" /> <ProjectReference Include="..\..\src\DotRecast.Detour.TileCache\DotRecast.Detour.TileCache.csproj"/>
<ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj" /> <ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj"/>
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj" /> <ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -26,13 +26,13 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test.Io; namespace DotRecast.Detour.TileCache.Test.Io;
public class TileCacheReaderTest { public class TileCacheReaderTest
{
private readonly TileCacheReader reader = new TileCacheReader(); private readonly TileCacheReader reader = new TileCacheReader();
[Test] [Test]
public void testNavmesh() { public void testNavmesh()
{
using var ms = new MemoryStream(Loader.ToBytes("all_tiles_tilecache.bin")); using var ms = new MemoryStream(Loader.ToBytes("all_tiles_tilecache.bin"));
using var @is = new BinaryReader(ms); using var @is = new BinaryReader(ms);
TileCache tc = reader.read(@is, 6, null); TileCache tc = reader.read(@is, 6, null);
@ -129,7 +129,8 @@ public class TileCacheReaderTest {
} }
[Test] [Test]
public void testDungeon() { public void testDungeon()
{
using var ms = new MemoryStream(Loader.ToBytes("dungeon_all_tiles_tilecache.bin")); using var ms = new MemoryStream(Loader.ToBytes("dungeon_all_tiles_tilecache.bin"));
using var @is = new BinaryReader(ms); using var @is = new BinaryReader(ms);
TileCache tc = reader.read(@is, 6, null); 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[6], Is.EqualTo(48.484783f).Within(0.0001f));
Assert.That(data.verts[9], 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; namespace DotRecast.Detour.TileCache.Test.Io;
public class TileCacheReaderWriterTest : AbstractTileCacheTest
public class TileCacheReaderWriterTest : AbstractTileCacheTest { {
private readonly TileCacheReader reader = new TileCacheReader(); private readonly TileCacheReader reader = new TileCacheReader();
private readonly TileCacheWriter writer = new TileCacheWriter(); private readonly TileCacheWriter writer = new TileCacheWriter();
[Test] [Test]
public void testFastLz() { public void testFastLz()
{
testDungeon(false); testDungeon(false);
testDungeon(true); testDungeon(true);
} }
[Test] [Test]
public void testLZ4() { public void testLZ4()
{
testDungeon(true); testDungeon(true);
testDungeon(false); testDungeon(false);
} }
private void testDungeon(bool cCompatibility) { private void testDungeon(bool cCompatibility)
{
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj")); InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom); TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
List<byte[]> layers = layerBuilder.build(ByteOrder.LITTLE_ENDIAN, cCompatibility, 1); List<byte[]> layers = layerBuilder.build(ByteOrder.LITTLE_ENDIAN, cCompatibility, 1);
TileCache tc = getTileCache(geom, ByteOrder.LITTLE_ENDIAN, cCompatibility); TileCache tc = getTileCache(geom, ByteOrder.LITTLE_ENDIAN, cCompatibility);
foreach (byte[] layer in layers) { foreach (byte[] layer in layers)
{
long refs = tc.addTile(layer, 0); long refs = tc.addTile(layer, 0);
tc.buildNavMeshTile(refs); tc.buildNavMeshTile(refs);
} }
@ -134,5 +137,4 @@ public class TileCacheReaderWriterTest : AbstractTileCacheTest {
Assert.That(data.detailVerts.Length, Is.EqualTo(0)); Assert.That(data.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 3)); Assert.That(data.detailTris.Length, Is.EqualTo(4 * 3));
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -26,50 +26,64 @@ using NUnit.Framework;
namespace DotRecast.Detour.TileCache.Test; namespace DotRecast.Detour.TileCache.Test;
public class TileCacheNavigationTest : AbstractTileCacheTest { public class TileCacheNavigationTest : AbstractTileCacheTest
{
protected readonly long[] startRefs = { 281475006070787L }; protected readonly long[] startRefs = { 281475006070787L };
protected readonly long[] endRefs = { 281474986147841L }; protected readonly long[] endRefs = { 281474986147841L };
protected readonly float[][] startPoss = { new[] { 39.447338f, 9.998177f, -0.784811f } }; protected readonly float[][] startPoss = { new[] { 39.447338f, 9.998177f, -0.784811f } };
protected readonly float[][] endPoss = { new[] { 19.292645f, 11.611748f, -57.750366f } }; protected readonly float[][] endPoss = { new[] { 19.292645f, 11.611748f, -57.750366f } };
private readonly Status[] statuses = { Status.SUCCSESS }; 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, 281475003973634L, 281475003973632L, 281474996633604L, 281474996633605L, 281474996633603L, 281474995585027L,
281474995585029L, 281474995585026L, 281474995585028L, 281474995585024L, 281474991390721L, 281474991390722L, 281474995585029L, 281474995585026L, 281474995585028L, 281474995585024L, 281474991390721L, 281474991390722L,
281474991390725L, 281474991390720L, 281474987196418L, 281474987196417L, 281474988244995L, 281474988245001L, 281474991390725L, 281474991390720L, 281474987196418L, 281474987196417L, 281474988244995L, 281474988245001L,
281474988244997L, 281474988244998L, 281474988245002L, 281474988245000L, 281474988244999L, 281474988244994L, 281474988244997L, 281474988244998L, 281474988245002L, 281474988245000L, 281474988244999L, 281474988244994L,
281474985099264L, 281474985099266L, 281474986147841L } }; 281474985099264L, 281474985099266L, 281474986147841L
}
};
protected NavMesh navmesh; protected NavMesh navmesh;
protected NavMeshQuery query; protected NavMeshQuery query;
[SetUp] [SetUp]
public void setUp() { public void setUp()
{
bool cCompatibility = true; bool cCompatibility = true;
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj")); InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom); TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
List<byte[]> layers = layerBuilder.build(ByteOrder.LITTLE_ENDIAN, cCompatibility, 1); List<byte[]> layers = layerBuilder.build(ByteOrder.LITTLE_ENDIAN, cCompatibility, 1);
TileCache tc = getTileCache(geom, ByteOrder.LITTLE_ENDIAN, cCompatibility); TileCache tc = getTileCache(geom, ByteOrder.LITTLE_ENDIAN, cCompatibility);
foreach (byte[] data in layers) { foreach (byte[] data in layers)
{
tc.addTile(data, 0); tc.addTile(data, 0);
} }
for (int y = 0; y < layerBuilder.getTh(); ++y) {
for (int x = 0; x < layerBuilder.getTw(); ++x) { for (int y = 0; y < layerBuilder.getTh(); ++y)
foreach (long refs in tc.getTilesAt(x, y)) { {
for (int x = 0; x < layerBuilder.getTw(); ++x)
{
foreach (long refs in tc.getTilesAt(x, y))
{
tc.buildNavMeshTile(refs); tc.buildNavMeshTile(refs);
} }
} }
} }
navmesh = tc.getNavMesh(); navmesh = tc.getNavMesh();
query = new NavMeshQuery(navmesh); query = new NavMeshQuery(navmesh);
} }
[Test] [Test]
public void testFindPathWithDefaultHeuristic() { public void testFindPathWithDefaultHeuristic()
{
QueryFilter filter = new DefaultQueryFilter(); 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 startRef = startRefs[i];
long endRef = endRefs[i]; long endRef = endRefs[i];
float[] startPos = startPoss[i]; float[] startPos = startPoss[i];
@ -77,16 +91,19 @@ public class TileCacheNavigationTest : AbstractTileCacheTest {
Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter); Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter);
Assert.That(path.status, Is.EqualTo(statuses[i])); Assert.That(path.status, Is.EqualTo(statuses[i]));
Assert.That(path.result.Count, Is.EqualTo(results[i].Length)); 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 : 확인 필요 Assert.That(path.result[j], Is.EqualTo(results[i][j])); // TODO : 확인 필요
} }
} }
} }
[Test] [Test]
public void testFindPathWithNoHeuristic() { public void testFindPathWithNoHeuristic()
{
QueryFilter filter = new DefaultQueryFilter(); 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 startRef = startRefs[i];
long endRef = endRefs[i]; long endRef = endRefs[i];
float[] startPos = startPoss[i]; float[] startPos = startPoss[i];
@ -95,10 +112,10 @@ public class TileCacheNavigationTest : AbstractTileCacheTest {
0, 0); 0, 0);
Assert.That(path.status, Is.EqualTo(statuses[i])); Assert.That(path.status, Is.EqualTo(statuses[i]));
Assert.That(path.result.Count, Is.EqualTo(results[i].Length)); 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 : 확인 필요 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; namespace DotRecast.Detour.TileCache.Test;
public class TileCacheTest : AbstractTileCacheTest { public class TileCacheTest : AbstractTileCacheTest
{
[Test] [Test]
public void testFastLz() { public void testFastLz()
{
testDungeon(ByteOrder.LITTLE_ENDIAN, false); testDungeon(ByteOrder.LITTLE_ENDIAN, false);
testDungeon(ByteOrder.LITTLE_ENDIAN, true); testDungeon(ByteOrder.LITTLE_ENDIAN, true);
testDungeon(ByteOrder.BIG_ENDIAN, false); testDungeon(ByteOrder.BIG_ENDIAN, false);
@ -43,7 +44,8 @@ public class TileCacheTest : AbstractTileCacheTest {
} }
[Test] [Test]
public void testLZ4() { public void testLZ4()
{
testDungeon(ByteOrder.LITTLE_ENDIAN, false); testDungeon(ByteOrder.LITTLE_ENDIAN, false);
testDungeon(ByteOrder.LITTLE_ENDIAN, true); testDungeon(ByteOrder.LITTLE_ENDIAN, true);
testDungeon(ByteOrder.BIG_ENDIAN, false); testDungeon(ByteOrder.BIG_ENDIAN, false);
@ -54,7 +56,8 @@ public class TileCacheTest : AbstractTileCacheTest {
test(ByteOrder.BIG_ENDIAN, true); 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")); InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
TileCache tc = getTileCache(geom, order, cCompatibility); TileCache tc = getTileCache(geom, order, cCompatibility);
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom); TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
@ -62,13 +65,15 @@ public class TileCacheTest : AbstractTileCacheTest {
int cacheLayerCount = 0; int cacheLayerCount = 0;
int cacheCompressedSize = 0; int cacheCompressedSize = 0;
int cacheRawSize = 0; int cacheRawSize = 0;
foreach (byte[] layer in layers) { foreach (byte[] layer in layers)
{
long refs = tc.addTile(layer, 0); long refs = tc.addTile(layer, 0);
tc.buildNavMeshTile(refs); tc.buildNavMeshTile(refs);
cacheLayerCount++; cacheLayerCount++;
cacheCompressedSize += layer.Length; cacheCompressedSize += layer.Length;
cacheRawSize += 4 * 48 * 48 + 56; // FIXME cacheRawSize += 4 * 48 * 48 + 56; // FIXME
} }
Console.WriteLine("Compressor: " + tc.getCompressor().GetType().Name + " C Compatibility: " + cCompatibility Console.WriteLine("Compressor: " + tc.getCompressor().GetType().Name + " C Compatibility: " + cCompatibility
+ " Layers: " + cacheLayerCount + " Raw Size: " + cacheRawSize + " Compressed: " + cacheCompressedSize); + " Layers: " + cacheLayerCount + " Raw Size: " + cacheRawSize + " Compressed: " + cacheCompressedSize);
Assert.That(tc.getNavMesh().getMaxTiles(), Is.EqualTo(256)); 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)); 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")); InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("nav_test.obj"));
TileCache tc = getTileCache(geom, order, cCompatibility); TileCache tc = getTileCache(geom, order, cCompatibility);
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom); TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
@ -155,46 +161,56 @@ public class TileCacheTest : AbstractTileCacheTest {
int cacheLayerCount = 0; int cacheLayerCount = 0;
int cacheCompressedSize = 0; int cacheCompressedSize = 0;
int cacheRawSize = 0; int cacheRawSize = 0;
foreach (byte[] layer in layers) { foreach (byte[] layer in layers)
{
long refs = tc.addTile(layer, 0); long refs = tc.addTile(layer, 0);
tc.buildNavMeshTile(refs); tc.buildNavMeshTile(refs);
cacheLayerCount++; cacheLayerCount++;
cacheCompressedSize += layer.Length; cacheCompressedSize += layer.Length;
cacheRawSize += 4 * 48 * 48 + 56; cacheRawSize += 4 * 48 * 48 + 56;
} }
Console.WriteLine("Compressor: " + tc.getCompressor().GetType().Name + " C Compatibility: " + cCompatibility Console.WriteLine("Compressor: " + tc.getCompressor().GetType().Name + " C Compatibility: " + cCompatibility
+ " Layers: " + cacheLayerCount + " Raw Size: " + cacheRawSize + " Compressed: " + cacheCompressedSize); + " Layers: " + cacheLayerCount + " Raw Size: " + cacheRawSize + " Compressed: " + cacheCompressedSize);
} }
[Test] [Test]
public void testPerformance() { public void testPerformance()
{
int threads = 4; int threads = 4;
ByteOrder order = ByteOrder.LITTLE_ENDIAN; ByteOrder order = ByteOrder.LITTLE_ENDIAN;
bool cCompatibility = false; bool cCompatibility = false;
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj")); InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom); 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, 1);
layerBuilder.build(order, cCompatibility, threads); layerBuilder.build(order, cCompatibility, threads);
} }
long t1 = Stopwatch.GetTimestamp(); long t1 = Stopwatch.GetTimestamp();
List<byte[]> layers = null; List<byte[]> layers = null;
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++)
{
layers = layerBuilder.build(order, cCompatibility, 1); layers = layerBuilder.build(order, cCompatibility, 1);
} }
long t2 = Stopwatch.GetTimestamp(); long t2 = Stopwatch.GetTimestamp();
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++)
{
layers = layerBuilder.build(order, cCompatibility, threads); layers = layerBuilder.build(order, cCompatibility, threads);
} }
long t3 = Stopwatch.GetTimestamp(); long t3 = Stopwatch.GetTimestamp();
Console.WriteLine(" Time ST : " + (t2 - t1) / 1000000); Console.WriteLine(" Time ST : " + (t2 - t1) / 1000000);
Console.WriteLine(" Time MT : " + (t3 - t2) / 1000000); Console.WriteLine(" Time MT : " + (t3 - t2) / 1000000);
TileCache tc = getTileCache(geom, order, cCompatibility); TileCache tc = getTileCache(geom, order, cCompatibility);
foreach (byte[] layer in layers) { foreach (byte[] layer in layers)
{
long refs = tc.addTile(layer, 0); long refs = tc.addTile(layer, 0);
tc.buildNavMeshTile(refs); tc.buildNavMeshTile(refs);
} }
Assert.That(tc.getNavMesh().getMaxTiles(), Is.EqualTo(256)); Assert.That(tc.getNavMesh().getMaxTiles(), Is.EqualTo(256));
Assert.That(tc.getNavMesh().getParams().maxPolys, Is.EqualTo(16384)); Assert.That(tc.getNavMesh().getParams().maxPolys, Is.EqualTo(16384));
Assert.That(tc.getNavMesh().getParams().tileWidth, Is.EqualTo(14.4f).Within(0.001f)); 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.detailVerts.Length, Is.EqualTo(0));
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 3)); Assert.That(data.detailTris.Length, Is.EqualTo(4 * 3));
} }
} }

View File

@ -8,10 +8,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit" Version="3.13.3"/>
<PackageReference Include="NUnit3TestAdapter" Version="4.4.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.4.0"/>
<PackageReference Include="NUnit.Analyzers" Version="3.5.0" /> <PackageReference Include="NUnit.Analyzers" Version="3.5.0"/>
<PackageReference Include="coverlet.collector" Version="3.2.0"> <PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -19,7 +19,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj" /> <ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>