forked from mirror/DotRecast
reformat
This commit is contained in:
parent
41c7b777ab
commit
06e6f53101
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace DotRecast.Core
|
||||
{
|
||||
|
||||
|
||||
public static class ArrayUtils
|
||||
{
|
||||
public static T[] CopyOf<T>(T[] source, int startIdx, int length)
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace DotRecast.Core
|
||||
{
|
||||
|
||||
|
||||
public class AtomicBoolean
|
||||
{
|
||||
private volatile int _location;
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace DotRecast.Core
|
||||
{
|
||||
|
||||
|
||||
public class AtomicFloat
|
||||
{
|
||||
private volatile float _location;
|
||||
|
|
|
@ -2,15 +2,12 @@
|
|||
|
||||
namespace DotRecast.Core
|
||||
{
|
||||
|
||||
|
||||
public class AtomicInteger
|
||||
{
|
||||
private volatile int _location;
|
||||
|
||||
public AtomicInteger() : this(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public AtomicInteger(int location)
|
||||
|
@ -64,6 +61,5 @@ public class AtomicInteger
|
|||
{
|
||||
return Interlocked.Add(ref _location, value);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace DotRecast.Core
|
||||
{
|
||||
|
||||
|
||||
public class AtomicLong
|
||||
{
|
||||
private long _location;
|
||||
|
|
|
@ -3,8 +3,6 @@ using System.Buffers.Binary;
|
|||
|
||||
namespace DotRecast.Core
|
||||
{
|
||||
|
||||
|
||||
public class ByteBuffer
|
||||
{
|
||||
private ByteOrder _order;
|
||||
|
@ -31,11 +29,13 @@ public class ByteBuffer
|
|||
_order = order;
|
||||
}
|
||||
|
||||
public int limit() {
|
||||
public int limit()
|
||||
{
|
||||
return _bytes.Length - _position;
|
||||
}
|
||||
|
||||
public int remaining() {
|
||||
public int remaining()
|
||||
{
|
||||
int rem = limit();
|
||||
return rem > 0 ? rem : 0;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
namespace DotRecast.Core
|
||||
{
|
||||
|
||||
|
||||
public enum ByteOrder
|
||||
{
|
||||
/// <summary>Default on most Windows systems</summary>
|
||||
LITTLE_ENDIAN,
|
||||
BIG_ENDIAN,
|
||||
}
|
||||
|
||||
}
|
|
@ -3,8 +3,6 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Core
|
||||
{
|
||||
|
||||
|
||||
public static class CollectionExtensions
|
||||
{
|
||||
public static void forEach<T>(this IEnumerable<T> collection, Action<T> action)
|
||||
|
|
|
@ -20,38 +20,44 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Core
|
||||
{
|
||||
|
||||
|
||||
public static class ConvexUtils {
|
||||
|
||||
public static class ConvexUtils
|
||||
{
|
||||
// Calculates convex hull on xz-plane of points on 'pts',
|
||||
// stores the indices of the resulting hull in 'out' and
|
||||
// returns number of points on hull.
|
||||
public static List<int> convexhull(List<float> pts) {
|
||||
public static List<int> convexhull(List<float> pts)
|
||||
{
|
||||
int npts = pts.Count / 3;
|
||||
List<int> @out = new List<int>();
|
||||
// Find lower-leftmost point.
|
||||
int hull = 0;
|
||||
for (int i = 1; i < npts; ++i) {
|
||||
for (int i = 1; i < npts; ++i)
|
||||
{
|
||||
float[] a = new float[] { pts[i * 3], pts[i * 3 + 1], pts[i * 3 + 2] };
|
||||
float[] b = new float[] { pts[hull * 3], pts[hull * 3 + 1], pts[hull * 3 + 2] };
|
||||
if (cmppt(a, b)) {
|
||||
if (cmppt(a, b))
|
||||
{
|
||||
hull = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Gift wrap hull.
|
||||
int endpt = 0;
|
||||
do {
|
||||
do
|
||||
{
|
||||
@out.Add(hull);
|
||||
endpt = 0;
|
||||
for (int j = 1; j < npts; ++j) {
|
||||
for (int j = 1; j < npts; ++j)
|
||||
{
|
||||
float[] a = new float[] { pts[hull * 3], pts[hull * 3 + 1], pts[hull * 3 + 2] };
|
||||
float[] b = new float[] { pts[endpt * 3], pts[endpt * 3 + 1], pts[endpt * 3 + 2] };
|
||||
float[] c = new float[] { pts[j * 3], pts[j * 3 + 1], pts[j * 3 + 2] };
|
||||
if (hull == endpt || left(a, b, c)) {
|
||||
if (hull == endpt || left(a, b, c))
|
||||
{
|
||||
endpt = j;
|
||||
}
|
||||
}
|
||||
|
||||
hull = endpt;
|
||||
} while (endpt != @out[0]);
|
||||
|
||||
|
@ -59,31 +65,39 @@ public static class ConvexUtils {
|
|||
}
|
||||
|
||||
// Returns true if 'a' is more lower-left than 'b'.
|
||||
private static bool cmppt(float[] a, float[] b) {
|
||||
if (a[0] < b[0]) {
|
||||
private static bool cmppt(float[] a, float[] b)
|
||||
{
|
||||
if (a[0] < b[0])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (a[0] > b[0]) {
|
||||
|
||||
if (a[0] > b[0])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (a[2] < b[2]) {
|
||||
|
||||
if (a[2] < b[2])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (a[2] > b[2]) {
|
||||
|
||||
if (a[2] > b[2])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns true if 'c' is left of line 'a'-'b'.
|
||||
private static bool left(float[] a, float[] b, float[] c) {
|
||||
private static bool left(float[] a, float[] b, float[] c)
|
||||
{
|
||||
float u1 = b[0] - a[0];
|
||||
float v1 = b[2] - a[2];
|
||||
float u2 = c[0] - a[0];
|
||||
float v2 = c[2] - a[2];
|
||||
return u1 * v2 - v1 * u2 < 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,17 +22,18 @@ using System;
|
|||
|
||||
namespace DotRecast.Core
|
||||
{
|
||||
|
||||
|
||||
public class DemoMath {
|
||||
public static float vDistSqr(float[] v1, float[] v2, int i) {
|
||||
public class DemoMath
|
||||
{
|
||||
public static float vDistSqr(float[] v1, float[] v2, int i)
|
||||
{
|
||||
float dx = v2[i] - v1[0];
|
||||
float dy = v2[i + 1] - v1[1];
|
||||
float dz = v2[i + 2] - v1[2];
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
public static float[] vCross(float[] v1, float[] v2) {
|
||||
public static float[] vCross(float[] v1, float[] v2)
|
||||
{
|
||||
float[] dest = new float[3];
|
||||
dest[0] = v1[1] * v2[2] - v1[2] * v2[1];
|
||||
dest[1] = v1[2] * v2[0] - v1[0] * v2[2];
|
||||
|
@ -40,44 +41,53 @@ public class DemoMath {
|
|||
return dest;
|
||||
}
|
||||
|
||||
public static float vDot(float[] v1, float[] v2) {
|
||||
public static float vDot(float[] v1, float[] v2)
|
||||
{
|
||||
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
|
||||
}
|
||||
|
||||
public static float sqr(float f) {
|
||||
public static float sqr(float f)
|
||||
{
|
||||
return f * f;
|
||||
}
|
||||
|
||||
public static float getPathLen(float[] path, int npath) {
|
||||
public static float getPathLen(float[] path, int npath)
|
||||
{
|
||||
float totd = 0;
|
||||
for (int i = 0; i < npath - 1; ++i) {
|
||||
for (int i = 0; i < npath - 1; ++i)
|
||||
{
|
||||
totd += (float)Math.Sqrt(vDistSqr(path, i * 3, (i + 1) * 3));
|
||||
}
|
||||
|
||||
return totd;
|
||||
}
|
||||
|
||||
public static float vDistSqr(float[] v, int i, int j) {
|
||||
public static float vDistSqr(float[] v, int i, int j)
|
||||
{
|
||||
float dx = v[i] - v[j];
|
||||
float dy = v[i + 1] - v[j + 1];
|
||||
float dz = v[i + 2] - v[j + 2];
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
public static float step(float threshold, float v) {
|
||||
public static float step(float threshold, float v)
|
||||
{
|
||||
return v < threshold ? 0.0f : 1.0f;
|
||||
}
|
||||
|
||||
public static int clamp(int v, int min, int max) {
|
||||
public static int clamp(int v, int min, int max)
|
||||
{
|
||||
return Math.Max(Math.Min(v, max), min);
|
||||
}
|
||||
|
||||
public static float clamp(float v, float min, float max) {
|
||||
public static float clamp(float v, float min, float max)
|
||||
{
|
||||
return Math.Max(Math.Min(v, max), min);
|
||||
}
|
||||
|
||||
public static float lerp(float f, float g, float u) {
|
||||
public static float lerp(float f, float g, float u)
|
||||
{
|
||||
return u * g + (1f - u) * f;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
namespace DotRecast.Core
|
||||
{
|
||||
|
||||
|
||||
public static class Loader
|
||||
{
|
||||
public static byte[] ToBytes(string filename)
|
||||
|
|
|
@ -22,13 +22,10 @@ using System;
|
|||
|
||||
namespace DotRecast.Core
|
||||
{
|
||||
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class OrderedQueue<T>
|
||||
{
|
||||
|
||||
private readonly List<T> _items;
|
||||
private readonly Comparison<T> _comparison;
|
||||
|
||||
|
@ -43,7 +40,8 @@ public class OrderedQueue<T>
|
|||
return _items.Count;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
public void clear()
|
||||
{
|
||||
_items.Clear();
|
||||
}
|
||||
|
||||
|
@ -59,12 +57,14 @@ public class OrderedQueue<T>
|
|||
return node;
|
||||
}
|
||||
|
||||
public void Enqueue(T item) {
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
_items.Add(item);
|
||||
_items.Sort(_comparison);
|
||||
}
|
||||
|
||||
public void Remove(T item) {
|
||||
public void Remove(T item)
|
||||
{
|
||||
_items.Remove(item);
|
||||
}
|
||||
|
||||
|
@ -73,5 +73,4 @@ public class OrderedQueue<T>
|
|||
return 0 == _items.Count;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -23,23 +23,27 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour.Crowd
|
||||
{
|
||||
|
||||
|
||||
using static DetourCommon;
|
||||
|
||||
/// Represents an agent managed by a #dtCrowd object.
|
||||
/// @ingroup crowd
|
||||
public class CrowdAgent {
|
||||
|
||||
public class CrowdAgent
|
||||
{
|
||||
/// The type of navigation mesh polygon the agent is currently traversing.
|
||||
/// @ingroup crowd
|
||||
public enum CrowdAgentState {
|
||||
DT_CROWDAGENT_STATE_INVALID, /// < The agent is not in a valid state.
|
||||
DT_CROWDAGENT_STATE_WALKING, /// < The agent is traversing a normal navigation mesh polygon.
|
||||
public enum CrowdAgentState
|
||||
{
|
||||
DT_CROWDAGENT_STATE_INVALID,
|
||||
|
||||
/// < The agent is not in a valid state.
|
||||
DT_CROWDAGENT_STATE_WALKING,
|
||||
|
||||
/// < The agent is traversing a normal navigation mesh polygon.
|
||||
DT_CROWDAGENT_STATE_OFFMESH, /// < The agent is traversing an off-mesh connection.
|
||||
};
|
||||
|
||||
public enum MoveRequestState {
|
||||
public enum MoveRequestState
|
||||
{
|
||||
DT_CROWDAGENT_TARGET_NONE,
|
||||
DT_CROWDAGENT_TARGET_FAILED,
|
||||
DT_CROWDAGENT_TARGET_VALID,
|
||||
|
@ -50,57 +54,88 @@ public class CrowdAgent {
|
|||
};
|
||||
|
||||
public readonly long idx;
|
||||
|
||||
/// The type of mesh polygon the agent is traversing. (See: #CrowdAgentState)
|
||||
public CrowdAgentState state;
|
||||
|
||||
/// True if the agent has valid path (targetState == DT_CROWDAGENT_TARGET_VALID) and the path does not lead to the
|
||||
/// requested position, else false.
|
||||
public bool partial;
|
||||
|
||||
/// The path corridor the agent is using.
|
||||
public PathCorridor corridor;
|
||||
|
||||
/// The local boundary data for the agent.
|
||||
public LocalBoundary boundary;
|
||||
|
||||
/// Time since the agent's path corridor was optimized.
|
||||
public float topologyOptTime;
|
||||
|
||||
/// The known neighbors of the agent.
|
||||
public List<Crowd.CrowdNeighbour> neis = new List<Crowd.CrowdNeighbour>();
|
||||
|
||||
/// The desired speed.
|
||||
public float desiredSpeed;
|
||||
|
||||
public float[] npos = new float[3]; /// < The current agent position. [(x, y, z)]
|
||||
public float[] disp = new float[3]; /// < A temporary value used to accumulate agent displacement during iterative
|
||||
public float[] npos = new float[3];
|
||||
|
||||
/// < The current agent position. [(x, y, z)]
|
||||
public float[] disp = new float[3];
|
||||
|
||||
/// < A temporary value used to accumulate agent displacement during iterative
|
||||
/// collision resolution. [(x, y, z)]
|
||||
public float[] dvel = new float[3]; /// < The desired velocity of the agent. Based on the current path, calculated
|
||||
public float[] dvel = new float[3];
|
||||
|
||||
/// < The desired velocity of the agent. Based on the current path, calculated
|
||||
/// from
|
||||
/// scratch each frame. [(x, y, z)]
|
||||
public float[] nvel = new float[3]; /// < The desired velocity adjusted by obstacle avoidance, calculated from scratch each
|
||||
/// frame. [(x, y, z)]
|
||||
public float[] vel = new float[3]; /// < The actual velocity of the agent. The change from nvel -> vel is
|
||||
/// constrained by max acceleration. [(x, y, z)]
|
||||
public float[] nvel = new float[3];
|
||||
|
||||
/// < The desired velocity adjusted by obstacle avoidance, calculated from scratch each
|
||||
/// frame. [(x, y, z)]
|
||||
public float[] vel = new float[3];
|
||||
|
||||
/// < The actual velocity of the agent. The change from nvel -> vel is
|
||||
/// constrained by max acceleration. [(x, y, z)]
|
||||
/// The agent's configuration parameters.
|
||||
public CrowdAgentParams option;
|
||||
|
||||
/// The local path corridor corners for the agent.
|
||||
public List<StraightPathItem> corners = new List<StraightPathItem>();
|
||||
|
||||
public MoveRequestState targetState; /// < State of the movement request.
|
||||
public long targetRef; /// < Target polyref of the movement request.
|
||||
public float[] targetPos = new float[3]; /// < Target position of the movement request (or velocity in case of
|
||||
public MoveRequestState targetState;
|
||||
|
||||
/// < State of the movement request.
|
||||
public long targetRef;
|
||||
|
||||
/// < Target polyref of the movement request.
|
||||
public float[] targetPos = new float[3];
|
||||
|
||||
/// < Target position of the movement request (or velocity in case of
|
||||
/// DT_CROWDAGENT_TARGET_VELOCITY).
|
||||
public PathQueryResult targetPathQueryResult; /// < Path finder query
|
||||
public bool targetReplan; /// < Flag indicating that the current path is being replanned.
|
||||
public float targetReplanTime; /// <Time since the agent's target was replanned.
|
||||
public PathQueryResult targetPathQueryResult;
|
||||
|
||||
/// < Path finder query
|
||||
public bool targetReplan;
|
||||
|
||||
/// < Flag indicating that the current path is being replanned.
|
||||
public float targetReplanTime;
|
||||
|
||||
/// <Time since the agent's target was replanned.
|
||||
public float targetReplanWaitTime;
|
||||
|
||||
public CrowdAgentAnimation animation;
|
||||
|
||||
public CrowdAgent(int idx) {
|
||||
public CrowdAgent(int idx)
|
||||
{
|
||||
this.idx = idx;
|
||||
corridor = new PathCorridor();
|
||||
boundary = new LocalBoundary();
|
||||
animation = new CrowdAgentAnimation();
|
||||
}
|
||||
|
||||
public void integrate(float dt) {
|
||||
public void integrate(float dt)
|
||||
{
|
||||
// Fake dynamic constraint.
|
||||
float maxDelta = option.maxAcceleration * dt;
|
||||
float[] dv = vSub(nvel, vel);
|
||||
|
@ -116,13 +151,17 @@ public class CrowdAgent {
|
|||
vSet(vel, 0, 0, 0);
|
||||
}
|
||||
|
||||
public bool overOffmeshConnection(float radius) {
|
||||
public bool overOffmeshConnection(float radius)
|
||||
{
|
||||
if (0 == corners.Count)
|
||||
return false;
|
||||
|
||||
bool offMeshConnection = ((corners[corners.Count - 1].getFlags()
|
||||
& NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) ? true : false;
|
||||
if (offMeshConnection) {
|
||||
& NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
|
||||
? true
|
||||
: false;
|
||||
if (offMeshConnection)
|
||||
{
|
||||
float distSq = vDist2DSqr(npos, corners[corners.Count - 1].getPos());
|
||||
if (distSq < radius * radius)
|
||||
return true;
|
||||
|
@ -131,7 +170,8 @@ public class CrowdAgent {
|
|||
return false;
|
||||
}
|
||||
|
||||
public float getDistanceToGoal(float range) {
|
||||
public float getDistanceToGoal(float range)
|
||||
{
|
||||
if (0 == corners.Count)
|
||||
return range;
|
||||
|
||||
|
@ -142,10 +182,11 @@ public class CrowdAgent {
|
|||
return range;
|
||||
}
|
||||
|
||||
public float[] calcSmoothSteerDirection() {
|
||||
public float[] calcSmoothSteerDirection()
|
||||
{
|
||||
float[] dir = new float[3];
|
||||
if (0 < corners.Count) {
|
||||
|
||||
if (0 < corners.Count)
|
||||
{
|
||||
int ip0 = 0;
|
||||
int ip1 = Math.Min(1, corners.Count - 1);
|
||||
float[] p0 = corners[ip0].getPos();
|
||||
|
@ -167,29 +208,36 @@ public class CrowdAgent {
|
|||
|
||||
vNormalize(dir);
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
public float[] calcStraightSteerDirection() {
|
||||
public float[] calcStraightSteerDirection()
|
||||
{
|
||||
float[] dir = new float[3];
|
||||
if (0 < corners.Count) {
|
||||
if (0 < corners.Count)
|
||||
{
|
||||
dir = vSub(corners[0].getPos(), npos);
|
||||
dir[1] = 0;
|
||||
vNormalize(dir);
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
public void setTarget(long refs, float[] pos) {
|
||||
public void setTarget(long refs, float[] pos)
|
||||
{
|
||||
targetRef = refs;
|
||||
vCopy(targetPos, pos);
|
||||
targetPathQueryResult = null;
|
||||
if (targetRef != 0) {
|
||||
if (targetRef != 0)
|
||||
{
|
||||
targetState = MoveRequestState.DT_CROWDAGENT_TARGET_REQUESTING;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
targetState = MoveRequestState.DT_CROWDAGENT_TARGET_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -17,18 +17,16 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.Crowd
|
||||
{
|
||||
|
||||
|
||||
public class CrowdAgentAnimation {
|
||||
public class CrowdAgentAnimation
|
||||
{
|
||||
public bool active;
|
||||
public float[] initPos = new float[3];
|
||||
public float[] startPos = new float[3];
|
||||
public float[] endPos = new float[3];
|
||||
public long polyRef;
|
||||
public float t, tmax;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,33 +22,44 @@ using System;
|
|||
|
||||
namespace DotRecast.Detour.Crowd
|
||||
{
|
||||
|
||||
|
||||
/// Configuration parameters for a crowd agent.
|
||||
/// @ingroup crowd
|
||||
public class CrowdAgentParams {
|
||||
public float radius; /// < Agent radius. [Limit: >= 0]
|
||||
public float height; /// < Agent height. [Limit: > 0]
|
||||
public float maxAcceleration; /// < Maximum allowed acceleration. [Limit: >= 0]
|
||||
public float maxSpeed; /// < Maximum allowed speed. [Limit: >= 0]
|
||||
public class CrowdAgentParams
|
||||
{
|
||||
public float radius;
|
||||
|
||||
/// < Agent radius. [Limit: >= 0]
|
||||
public float height;
|
||||
|
||||
/// < Agent height. [Limit: > 0]
|
||||
public float maxAcceleration;
|
||||
|
||||
/// < Maximum allowed acceleration. [Limit: >= 0]
|
||||
public float maxSpeed;
|
||||
|
||||
/// < Maximum allowed speed. [Limit: >= 0]
|
||||
/// Defines how close a collision element must be before it is considered for steering behaviors. [Limits: > 0]
|
||||
public float collisionQueryRange;
|
||||
|
||||
public float pathOptimizationRange; /// < The path visibility optimization range. [Limit: > 0]
|
||||
public float pathOptimizationRange;
|
||||
|
||||
/// < The path visibility optimization range. [Limit: > 0]
|
||||
/// How aggresive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0]
|
||||
public float separationWeight;
|
||||
|
||||
/// Crowd agent update flags.
|
||||
public const int DT_CROWD_ANTICIPATE_TURNS = 1;
|
||||
|
||||
public const int DT_CROWD_OBSTACLE_AVOIDANCE = 2;
|
||||
public const int DT_CROWD_SEPARATION = 4;
|
||||
public const int DT_CROWD_OPTIMIZE_VIS = 8; /// < Use #dtPathCorridor::optimizePathVisibility() to optimize
|
||||
/// the agent path.
|
||||
public const int DT_CROWD_OPTIMIZE_TOPO = 16; /// < Use dtPathCorridor::optimizePathTopology() to optimize
|
||||
/// the agent path.
|
||||
public const int DT_CROWD_OPTIMIZE_VIS = 8;
|
||||
|
||||
/// < Use #dtPathCorridor::optimizePathVisibility() to optimize
|
||||
/// the agent path.
|
||||
public const int DT_CROWD_OPTIMIZE_TOPO = 16;
|
||||
|
||||
/// < Use dtPathCorridor::optimizePathTopology() to optimize
|
||||
/// the agent path.
|
||||
/// Flags that impact steering behavior. (See: #UpdateFlags)
|
||||
public int updateFlags;
|
||||
|
||||
|
|
|
@ -18,53 +18,60 @@ freely, subject to the following restrictions:
|
|||
|
||||
namespace DotRecast.Detour.Crowd
|
||||
{
|
||||
|
||||
|
||||
public class CrowdConfig {
|
||||
|
||||
public class CrowdConfig
|
||||
{
|
||||
public readonly float maxAgentRadius;
|
||||
|
||||
/**
|
||||
* Max number of path requests in the queue
|
||||
*/
|
||||
public int pathQueueSize = 32;
|
||||
|
||||
/**
|
||||
* Max number of sliced path finding iterations executed per update (used to handle longer paths and replans)
|
||||
*/
|
||||
public int maxFindPathIterations = 100;
|
||||
|
||||
/**
|
||||
* Max number of sliced path finding iterations executed per agent to find the initial path to target
|
||||
*/
|
||||
public int maxTargetFindPathIterations = 20;
|
||||
|
||||
/**
|
||||
* Min time between topology optimizations (in seconds)
|
||||
*/
|
||||
public float topologyOptimizationTimeThreshold = 0.5f;
|
||||
|
||||
/**
|
||||
* The number of polygons from the beginning of the corridor to check to ensure path validity
|
||||
*/
|
||||
public int checkLookAhead = 10;
|
||||
|
||||
/**
|
||||
* Min time between target re-planning (in seconds)
|
||||
*/
|
||||
public float targetReplanDelay = 1.0f;
|
||||
|
||||
/**
|
||||
* Max number of sliced path finding iterations executed per topology optimization per agent
|
||||
*/
|
||||
public int maxTopologyOptimizationIterations = 32;
|
||||
|
||||
public float collisionResolveFactor = 0.7f;
|
||||
|
||||
/**
|
||||
* Max number of neighbour agents to consider in obstacle avoidance processing
|
||||
*/
|
||||
public int maxObstacleAvoidanceCircles = 6;
|
||||
|
||||
/**
|
||||
* Max number of neighbour segments to consider in obstacle avoidance processing
|
||||
*/
|
||||
public int maxObstacleAvoidanceSegments = 8;
|
||||
|
||||
public CrowdConfig(float maxAgentRadius) {
|
||||
public CrowdConfig(float maxAgentRadius)
|
||||
{
|
||||
this.maxAgentRadius = maxAgentRadius;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -23,48 +23,53 @@ using System.Linq;
|
|||
|
||||
namespace DotRecast.Detour.Crowd
|
||||
{
|
||||
|
||||
|
||||
|
||||
public class CrowdTelemetry {
|
||||
|
||||
public class CrowdTelemetry
|
||||
{
|
||||
public const int TIMING_SAMPLES = 10;
|
||||
private float _maxTimeToEnqueueRequest;
|
||||
private float _maxTimeToFindPath;
|
||||
private readonly Dictionary<string, long> _executionTimings = new Dictionary<string, long>();
|
||||
private readonly Dictionary<string, List<long>> _executionTimingSamples = new Dictionary<string, List<long>>();
|
||||
|
||||
public float maxTimeToEnqueueRequest() {
|
||||
public float maxTimeToEnqueueRequest()
|
||||
{
|
||||
return _maxTimeToEnqueueRequest;
|
||||
}
|
||||
|
||||
public float maxTimeToFindPath() {
|
||||
public float maxTimeToFindPath()
|
||||
{
|
||||
return _maxTimeToFindPath;
|
||||
}
|
||||
|
||||
public Dictionary<string, long> executionTimings() {
|
||||
public Dictionary<string, long> executionTimings()
|
||||
{
|
||||
return _executionTimings;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
public void start()
|
||||
{
|
||||
_maxTimeToEnqueueRequest = 0;
|
||||
_maxTimeToFindPath = 0;
|
||||
_executionTimings.Clear();
|
||||
}
|
||||
|
||||
public void recordMaxTimeToEnqueueRequest(float time) {
|
||||
public void recordMaxTimeToEnqueueRequest(float time)
|
||||
{
|
||||
_maxTimeToEnqueueRequest = Math.Max(_maxTimeToEnqueueRequest, time);
|
||||
}
|
||||
|
||||
public void recordMaxTimeToFindPath(float time) {
|
||||
public void recordMaxTimeToFindPath(float time)
|
||||
{
|
||||
_maxTimeToFindPath = Math.Max(_maxTimeToFindPath, time);
|
||||
}
|
||||
|
||||
public void start(string name) {
|
||||
public void start(string name)
|
||||
{
|
||||
_executionTimings.Add(name, Stopwatch.GetTimestamp());
|
||||
}
|
||||
|
||||
public void stop(string name) {
|
||||
public void stop(string name)
|
||||
{
|
||||
long duration = Stopwatch.GetTimestamp() - _executionTimings[name];
|
||||
if (!_executionTimingSamples.TryGetValue(name, out var s))
|
||||
{
|
||||
|
@ -72,12 +77,13 @@ public class CrowdTelemetry {
|
|||
_executionTimingSamples.Add(name, s);
|
||||
}
|
||||
|
||||
if (s.Count == TIMING_SAMPLES) {
|
||||
if (s.Count == TIMING_SAMPLES)
|
||||
{
|
||||
s.RemoveAt(0);
|
||||
}
|
||||
|
||||
s.Add(duration);
|
||||
_executionTimings[name] = (long)s.Average();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -23,17 +23,17 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour.Crowd
|
||||
{
|
||||
|
||||
|
||||
using static DetourCommon;
|
||||
|
||||
public class LocalBoundary {
|
||||
|
||||
public class LocalBoundary
|
||||
{
|
||||
public const int MAX_LOCAL_SEGS = 8;
|
||||
|
||||
private class Segment {
|
||||
private class Segment
|
||||
{
|
||||
/** Segment start/end */
|
||||
public float[] s = new float[6];
|
||||
|
||||
/** Distance for pruning. */
|
||||
public float d;
|
||||
}
|
||||
|
@ -42,67 +42,91 @@ public class LocalBoundary {
|
|||
List<Segment> m_segs = new List<Segment>();
|
||||
List<long> m_polys = new List<long>();
|
||||
|
||||
public LocalBoundary() {
|
||||
public LocalBoundary()
|
||||
{
|
||||
m_center[0] = m_center[1] = m_center[2] = float.MaxValue;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
public void reset()
|
||||
{
|
||||
m_center[0] = m_center[1] = m_center[2] = float.MaxValue;
|
||||
m_polys.Clear();
|
||||
m_segs.Clear();
|
||||
}
|
||||
|
||||
protected void addSegment(float dist, float[] s) {
|
||||
protected void addSegment(float dist, float[] s)
|
||||
{
|
||||
// Insert neighbour based on the distance.
|
||||
Segment seg = new Segment();
|
||||
Array.Copy(s, seg.s, 6);
|
||||
seg.d = dist;
|
||||
if (0 == m_segs.Count) {
|
||||
if (0 == m_segs.Count)
|
||||
{
|
||||
m_segs.Add(seg);
|
||||
} else if (dist >= m_segs[m_segs.Count - 1].d) {
|
||||
if (m_segs.Count >= MAX_LOCAL_SEGS) {
|
||||
}
|
||||
else if (dist >= m_segs[m_segs.Count - 1].d)
|
||||
{
|
||||
if (m_segs.Count >= MAX_LOCAL_SEGS)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_segs.Add(seg);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Insert inbetween.
|
||||
int i;
|
||||
for (i = 0; i < m_segs.Count; ++i) {
|
||||
if (dist <= m_segs[i].d) {
|
||||
for (i = 0; i < m_segs.Count; ++i)
|
||||
{
|
||||
if (dist <= m_segs[i].d)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_segs.Insert(i, seg);
|
||||
}
|
||||
while (m_segs.Count > MAX_LOCAL_SEGS) {
|
||||
|
||||
while (m_segs.Count > MAX_LOCAL_SEGS)
|
||||
{
|
||||
m_segs.RemoveAt(m_segs.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void update(long refs, float[] pos, float collisionQueryRange, NavMeshQuery navquery, QueryFilter filter) {
|
||||
if (refs == 0) {
|
||||
public void update(long refs, float[] pos, float collisionQueryRange, NavMeshQuery navquery, QueryFilter filter)
|
||||
{
|
||||
if (refs == 0)
|
||||
{
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
|
||||
vCopy(m_center, pos);
|
||||
// First query non-overlapping polygons.
|
||||
Result<FindLocalNeighbourhoodResult> res = navquery.findLocalNeighbourhood(refs, pos, collisionQueryRange,
|
||||
filter);
|
||||
if (res.succeeded()) {
|
||||
if (res.succeeded())
|
||||
{
|
||||
m_polys = res.result.getRefs();
|
||||
m_segs.Clear();
|
||||
// Secondly, store all polygon edges.
|
||||
for (int j = 0; j < m_polys.Count; ++j) {
|
||||
for (int j = 0; j < m_polys.Count; ++j)
|
||||
{
|
||||
Result<GetPolyWallSegmentsResult> result = navquery.getPolyWallSegments(m_polys[j], false, filter);
|
||||
if (result.succeeded()) {
|
||||
if (result.succeeded())
|
||||
{
|
||||
GetPolyWallSegmentsResult gpws = result.result;
|
||||
for (int k = 0; k < gpws.getSegmentRefs().Count; ++k) {
|
||||
for (int k = 0; k < gpws.getSegmentRefs().Count; ++k)
|
||||
{
|
||||
float[] s = gpws.getSegmentVerts()[k];
|
||||
// Skip too distant segments.
|
||||
Tuple<float, float> distseg = distancePtSegSqr2D(pos, s, 0, 3);
|
||||
if (distseg.Item1 > sqr(collisionQueryRange)) {
|
||||
if (distseg.Item1 > sqr(collisionQueryRange))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
addSegment(distseg.Item1, s);
|
||||
}
|
||||
}
|
||||
|
@ -110,14 +134,18 @@ public class LocalBoundary {
|
|||
}
|
||||
}
|
||||
|
||||
public bool isValid(NavMeshQuery navquery, QueryFilter filter) {
|
||||
if (m_polys.Count == 0) {
|
||||
public bool isValid(NavMeshQuery navquery, QueryFilter filter)
|
||||
{
|
||||
if (m_polys.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that all polygons still pass query filter.
|
||||
foreach (long refs in m_polys) {
|
||||
if (!navquery.isValidPolyRef(refs, filter)) {
|
||||
foreach (long refs in m_polys)
|
||||
{
|
||||
if (!navquery.isValidPolyRef(refs, filter))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -125,17 +153,19 @@ public class LocalBoundary {
|
|||
return true;
|
||||
}
|
||||
|
||||
public float[] getCenter() {
|
||||
public float[] getCenter()
|
||||
{
|
||||
return m_center;
|
||||
}
|
||||
|
||||
public float[] getSegment(int j) {
|
||||
public float[] getSegment(int j)
|
||||
{
|
||||
return m_segs[j].s;
|
||||
}
|
||||
|
||||
public int getSegmentCount() {
|
||||
public int getSegmentCount()
|
||||
{
|
||||
return m_segs.Count;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -23,51 +23,70 @@ using DotRecast.Detour.Crowd.Tracking;
|
|||
|
||||
namespace DotRecast.Detour.Crowd
|
||||
{
|
||||
|
||||
|
||||
using static DetourCommon;
|
||||
|
||||
public class ObstacleAvoidanceQuery {
|
||||
public class ObstacleAvoidanceQuery
|
||||
{
|
||||
public const int DT_MAX_PATTERN_DIVS = 32;
|
||||
|
||||
public const int DT_MAX_PATTERN_DIVS = 32; /// < Max numver of adaptive divs.
|
||||
public const int DT_MAX_PATTERN_RINGS = 4; /// < Max number of adaptive rings.
|
||||
/// < Max numver of adaptive divs.
|
||||
public const int DT_MAX_PATTERN_RINGS = 4;
|
||||
|
||||
public class ObstacleCircle {
|
||||
/// < Max number of adaptive rings.
|
||||
public class ObstacleCircle
|
||||
{
|
||||
/** Position of the obstacle */
|
||||
public readonly float[] p = new float[3];
|
||||
|
||||
/** Velocity of the obstacle */
|
||||
public readonly float[] vel = new float[3];
|
||||
|
||||
/** Velocity of the obstacle */
|
||||
public readonly float[] dvel = new float[3];
|
||||
|
||||
/** Radius of the obstacle */
|
||||
public float rad;
|
||||
|
||||
/** Use for side selection during sampling. */
|
||||
public readonly float[] dp = new float[3];
|
||||
|
||||
/** Use for side selection during sampling. */
|
||||
public readonly float[] np = new float[3];
|
||||
}
|
||||
|
||||
public class ObstacleSegment {
|
||||
public class ObstacleSegment
|
||||
{
|
||||
/** End points of the obstacle segment */
|
||||
public readonly float[] p = new float[3];
|
||||
|
||||
/** End points of the obstacle segment */
|
||||
public readonly float[] q = new float[3];
|
||||
|
||||
public bool touch;
|
||||
}
|
||||
|
||||
public class ObstacleAvoidanceParams {
|
||||
public class ObstacleAvoidanceParams
|
||||
{
|
||||
public float velBias;
|
||||
public float weightDesVel;
|
||||
public float weightCurVel;
|
||||
public float weightSide;
|
||||
public float weightToi;
|
||||
public float horizTime;
|
||||
public int gridSize; /// < grid
|
||||
public int adaptiveDivs; /// < adaptive
|
||||
public int adaptiveRings; /// < adaptive
|
||||
public int adaptiveDepth; /// < adaptive
|
||||
public int gridSize;
|
||||
|
||||
public ObstacleAvoidanceParams() {
|
||||
/// < grid
|
||||
public int adaptiveDivs;
|
||||
|
||||
/// < adaptive
|
||||
public int adaptiveRings;
|
||||
|
||||
/// < adaptive
|
||||
public int adaptiveDepth;
|
||||
|
||||
/// < adaptive
|
||||
public ObstacleAvoidanceParams()
|
||||
{
|
||||
velBias = 0.4f;
|
||||
weightDesVel = 2.0f;
|
||||
weightCurVel = 0.75f;
|
||||
|
@ -80,7 +99,8 @@ public class ObstacleAvoidanceQuery {
|
|||
adaptiveDepth = 5;
|
||||
}
|
||||
|
||||
public ObstacleAvoidanceParams(ObstacleAvoidanceParams option) {
|
||||
public ObstacleAvoidanceParams(ObstacleAvoidanceParams option)
|
||||
{
|
||||
velBias = option.velBias;
|
||||
weightDesVel = option.weightDesVel;
|
||||
weightCurVel = option.weightCurVel;
|
||||
|
@ -107,27 +127,33 @@ public class ObstacleAvoidanceQuery {
|
|||
private readonly ObstacleSegment[] m_segments;
|
||||
private int m_nsegments;
|
||||
|
||||
public ObstacleAvoidanceQuery(int maxCircles, int maxSegments) {
|
||||
public ObstacleAvoidanceQuery(int maxCircles, int maxSegments)
|
||||
{
|
||||
m_maxCircles = maxCircles;
|
||||
m_ncircles = 0;
|
||||
m_circles = new ObstacleCircle[m_maxCircles];
|
||||
for (int i = 0; i < m_maxCircles; i++) {
|
||||
for (int i = 0; i < m_maxCircles; i++)
|
||||
{
|
||||
m_circles[i] = new ObstacleCircle();
|
||||
}
|
||||
|
||||
m_maxSegments = maxSegments;
|
||||
m_nsegments = 0;
|
||||
m_segments = new ObstacleSegment[m_maxSegments];
|
||||
for (int i = 0; i < m_maxSegments; i++) {
|
||||
for (int i = 0; i < m_maxSegments; i++)
|
||||
{
|
||||
m_segments[i] = new ObstacleSegment();
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
public void reset()
|
||||
{
|
||||
m_ncircles = 0;
|
||||
m_nsegments = 0;
|
||||
}
|
||||
|
||||
public void addCircle(float[] pos, float rad, float[] vel, float[] dvel) {
|
||||
public void addCircle(float[] pos, float rad, float[] vel, float[] dvel)
|
||||
{
|
||||
if (m_ncircles >= m_maxCircles)
|
||||
return;
|
||||
|
||||
|
@ -138,7 +164,8 @@ public class ObstacleAvoidanceQuery {
|
|||
vCopy(cir.dvel, dvel);
|
||||
}
|
||||
|
||||
public void addSegment(float[] p, float[] q) {
|
||||
public void addSegment(float[] p, float[] q)
|
||||
{
|
||||
if (m_nsegments >= m_maxSegments)
|
||||
return;
|
||||
ObstacleSegment seg = m_segments[m_nsegments++];
|
||||
|
@ -146,25 +173,31 @@ public class ObstacleAvoidanceQuery {
|
|||
vCopy(seg.q, q);
|
||||
}
|
||||
|
||||
public int getObstacleCircleCount() {
|
||||
public int getObstacleCircleCount()
|
||||
{
|
||||
return m_ncircles;
|
||||
}
|
||||
|
||||
public ObstacleCircle getObstacleCircle(int i) {
|
||||
public ObstacleCircle getObstacleCircle(int i)
|
||||
{
|
||||
return m_circles[i];
|
||||
}
|
||||
|
||||
public int getObstacleSegmentCount() {
|
||||
public int getObstacleSegmentCount()
|
||||
{
|
||||
return m_nsegments;
|
||||
}
|
||||
|
||||
public ObstacleSegment getObstacleSegment(int i) {
|
||||
public ObstacleSegment getObstacleSegment(int i)
|
||||
{
|
||||
return m_segments[i];
|
||||
}
|
||||
|
||||
private void prepare(float[] pos, float[] dvel) {
|
||||
private void prepare(float[] pos, float[] dvel)
|
||||
{
|
||||
// Prepare obstacles
|
||||
for (int i = 0; i < m_ncircles; ++i) {
|
||||
for (int i = 0; i < m_ncircles; ++i)
|
||||
{
|
||||
ObstacleCircle cir = m_circles[i];
|
||||
|
||||
// Side
|
||||
|
@ -178,16 +211,20 @@ public class ObstacleAvoidanceQuery {
|
|||
dv = vSub(cir.dvel, dvel);
|
||||
|
||||
float a = triArea2D(orig, cir.dp, dv);
|
||||
if (a < 0.01f) {
|
||||
if (a < 0.01f)
|
||||
{
|
||||
cir.np[0] = -cir.dp[2];
|
||||
cir.np[2] = cir.dp[0];
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
cir.np[0] = cir.dp[2];
|
||||
cir.np[2] = -cir.dp[0];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_nsegments; ++i) {
|
||||
for (int i = 0; i < m_nsegments; ++i)
|
||||
{
|
||||
ObstacleSegment seg = m_segments[i];
|
||||
|
||||
// Precalc if the agent is really close to the segment.
|
||||
|
@ -197,7 +234,8 @@ public class ObstacleAvoidanceQuery {
|
|||
}
|
||||
}
|
||||
|
||||
SweepCircleCircleResult sweepCircleCircle(float[] c0, float r0, float[] v, float[] c1, float r1) {
|
||||
SweepCircleCircleResult sweepCircleCircle(float[] c0, float r0, float[] v, float[] c1, float r1)
|
||||
{
|
||||
const float EPS = 0.0001f;
|
||||
float[] s = vSub(c1, c0);
|
||||
float r = r0 + r1;
|
||||
|
@ -216,7 +254,8 @@ public class ObstacleAvoidanceQuery {
|
|||
return new SweepCircleCircleResult(true, (b - rd) * a, (b + rd) * a);
|
||||
}
|
||||
|
||||
Tuple<bool, float> isectRaySeg(float[] ap, float[] u, float[] bp, float[] bq) {
|
||||
Tuple<bool, float> isectRaySeg(float[] ap, float[] u, float[] bp, float[] bq)
|
||||
{
|
||||
float[] v = vSub(bq, bp);
|
||||
float[] w = vSub(ap, bp);
|
||||
float d = vPerp2D(u, v);
|
||||
|
@ -243,7 +282,8 @@ public class ObstacleAvoidanceQuery {
|
|||
* threshold penalty for early out
|
||||
*/
|
||||
private float processSample(float[] vcand, float cs, float[] pos, float rad, float[] vel, float[] dvel,
|
||||
float minPenalty, ObstacleAvoidanceDebugData debug) {
|
||||
float minPenalty, ObstacleAvoidanceDebugData debug)
|
||||
{
|
||||
// penalty for straying away from the desired and current velocities
|
||||
float vpen = m_params.weightDesVel * (vDist2D(vcand, dvel) * m_invVmax);
|
||||
float vcpen = m_params.weightCurVel * (vDist2D(vcand, vel) * m_invVmax);
|
||||
|
@ -260,7 +300,8 @@ public class ObstacleAvoidanceQuery {
|
|||
float side = 0;
|
||||
int nside = 0;
|
||||
|
||||
for (int i = 0; i < m_ncircles; ++i) {
|
||||
for (int i = 0; i < m_ncircles; ++i)
|
||||
{
|
||||
ObstacleCircle cir = m_circles[i];
|
||||
|
||||
// RVO
|
||||
|
@ -278,14 +319,17 @@ public class ObstacleAvoidanceQuery {
|
|||
float htmin = sres.htmin, htmax = sres.htmax;
|
||||
|
||||
// Handle overlapping obstacles.
|
||||
if (htmin < 0.0f && htmax > 0.0f) {
|
||||
if (htmin < 0.0f && htmax > 0.0f)
|
||||
{
|
||||
// Avoid more when overlapped.
|
||||
htmin = -htmin * 0.5f;
|
||||
}
|
||||
|
||||
if (htmin >= 0.0f) {
|
||||
if (htmin >= 0.0f)
|
||||
{
|
||||
// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
|
||||
if (htmin < tmin) {
|
||||
if (htmin < tmin)
|
||||
{
|
||||
tmin = htmin;
|
||||
if (tmin < tThresold)
|
||||
return minPenalty;
|
||||
|
@ -293,11 +337,13 @@ public class ObstacleAvoidanceQuery {
|
|||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_nsegments; ++i) {
|
||||
for (int i = 0; i < m_nsegments; ++i)
|
||||
{
|
||||
ObstacleSegment seg = m_segments[i];
|
||||
float htmin = 0;
|
||||
|
||||
if (seg.touch) {
|
||||
if (seg.touch)
|
||||
{
|
||||
// Special case when the agent is very close to the segment.
|
||||
float[] sdir = vSub(seg.q, seg.p);
|
||||
float[] snorm = new float[3];
|
||||
|
@ -308,7 +354,9 @@ public class ObstacleAvoidanceQuery {
|
|||
continue;
|
||||
// Else immediate collision.
|
||||
htmin = 0.0f;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
Tuple<bool, float> ires = isectRaySeg(pos, vcand, seg.p, seg.q);
|
||||
if (!ires.Item1)
|
||||
continue;
|
||||
|
@ -319,7 +367,8 @@ public class ObstacleAvoidanceQuery {
|
|||
htmin *= 2.0f;
|
||||
|
||||
// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
|
||||
if (htmin < tmin) {
|
||||
if (htmin < tmin)
|
||||
{
|
||||
tmin = htmin;
|
||||
if (tmin < tThresold)
|
||||
return minPenalty;
|
||||
|
@ -342,7 +391,8 @@ public class ObstacleAvoidanceQuery {
|
|||
}
|
||||
|
||||
public Tuple<int, float[]> sampleVelocityGrid(float[] pos, float rad, float vmax, float[] vel, float[] dvel,
|
||||
ObstacleAvoidanceParams option, ObstacleAvoidanceDebugData debug) {
|
||||
ObstacleAvoidanceParams option, ObstacleAvoidanceDebugData debug)
|
||||
{
|
||||
prepare(pos, dvel);
|
||||
m_params = option;
|
||||
m_invHorizTime = 1.0f / m_params.horizTime;
|
||||
|
@ -363,8 +413,10 @@ public class ObstacleAvoidanceQuery {
|
|||
float minPenalty = float.MaxValue;
|
||||
int ns = 0;
|
||||
|
||||
for (int y = 0; y < m_params.gridSize; ++y) {
|
||||
for (int x = 0; x < m_params.gridSize; ++x) {
|
||||
for (int y = 0; y < m_params.gridSize; ++y)
|
||||
{
|
||||
for (int x = 0; x < m_params.gridSize; ++x)
|
||||
{
|
||||
float[] vcand = new float[3];
|
||||
vSet(vcand, cvx + x * cs - half, 0f, cvz + y * cs - half);
|
||||
|
||||
|
@ -373,7 +425,8 @@ public class ObstacleAvoidanceQuery {
|
|||
|
||||
float penalty = processSample(vcand, cs, pos, rad, vel, dvel, minPenalty, debug);
|
||||
ns++;
|
||||
if (penalty < minPenalty) {
|
||||
if (penalty < minPenalty)
|
||||
{
|
||||
minPenalty = penalty;
|
||||
vCopy(nvel, vcand);
|
||||
}
|
||||
|
@ -384,7 +437,8 @@ public class ObstacleAvoidanceQuery {
|
|||
}
|
||||
|
||||
// vector normalization that ignores the y-component.
|
||||
void dtNormalize2D(float[] v) {
|
||||
void dtNormalize2D(float[] v)
|
||||
{
|
||||
float d = (float)Math.Sqrt(v[0] * v[0] + v[2] * v[2]);
|
||||
if (d == 0)
|
||||
return;
|
||||
|
@ -394,7 +448,8 @@ public class ObstacleAvoidanceQuery {
|
|||
}
|
||||
|
||||
// vector normalization that ignores the y-component.
|
||||
float[] dtRotate2D(float[] v, float ang) {
|
||||
float[] dtRotate2D(float[] v, float ang)
|
||||
{
|
||||
float[] dest = new float[3];
|
||||
float c = (float)Math.Cos(ang);
|
||||
float s = (float)Math.Sin(ang);
|
||||
|
@ -407,7 +462,8 @@ public class ObstacleAvoidanceQuery {
|
|||
static readonly float DT_PI = 3.14159265f;
|
||||
|
||||
public Tuple<int, float[]> sampleVelocityAdaptive(float[] pos, float rad, float vmax, float[] vel,
|
||||
float[] dvel, ObstacleAvoidanceParams option, ObstacleAvoidanceDebugData debug) {
|
||||
float[] dvel, ObstacleAvoidanceParams option, ObstacleAvoidanceDebugData debug)
|
||||
{
|
||||
prepare(pos, dvel);
|
||||
m_params = option;
|
||||
m_invHorizTime = 1.0f / m_params.horizTime;
|
||||
|
@ -448,7 +504,8 @@ public class ObstacleAvoidanceQuery {
|
|||
pat[npat * 2 + 1] = 0;
|
||||
npat++;
|
||||
|
||||
for (int j = 0; j < nr; ++j) {
|
||||
for (int j = 0; j < nr; ++j)
|
||||
{
|
||||
float r = (float)(nr - j) / (float)nr;
|
||||
pat[npat * 2 + 0] = ddir[(j % 2) * 3] * r;
|
||||
pat[npat * 2 + 1] = ddir[(j % 2) * 3 + 2] * r;
|
||||
|
@ -456,7 +513,8 @@ public class ObstacleAvoidanceQuery {
|
|||
int last2 = last1;
|
||||
npat++;
|
||||
|
||||
for (int i = 1; i < nd - 1; i += 2) {
|
||||
for (int i = 1; i < nd - 1; i += 2)
|
||||
{
|
||||
// get next point on the "right" (rotate CW)
|
||||
pat[npat * 2 + 0] = pat[last1] * ca + pat[last1 + 1] * sa;
|
||||
pat[npat * 2 + 1] = -pat[last1] * sa + pat[last1 + 1] * ca;
|
||||
|
@ -469,7 +527,8 @@ public class ObstacleAvoidanceQuery {
|
|||
npat += 2;
|
||||
}
|
||||
|
||||
if ((nd & 1) == 0) {
|
||||
if ((nd & 1) == 0)
|
||||
{
|
||||
pat[npat * 2 + 2] = pat[last2] * ca - pat[last2 + 1] * sa;
|
||||
pat[npat * 2 + 3] = pat[last2] * sa + pat[last2 + 1] * ca;
|
||||
npat++;
|
||||
|
@ -481,12 +540,14 @@ public class ObstacleAvoidanceQuery {
|
|||
float[] res = new float[3];
|
||||
vSet(res, dvel[0] * m_params.velBias, 0, dvel[2] * m_params.velBias);
|
||||
int ns = 0;
|
||||
for (int k = 0; k < depth; ++k) {
|
||||
for (int k = 0; k < depth; ++k)
|
||||
{
|
||||
float minPenalty = float.MaxValue;
|
||||
float[] bvel = new float[3];
|
||||
vSet(bvel, 0, 0, 0);
|
||||
|
||||
for (int i = 0; i < npat; ++i) {
|
||||
for (int i = 0; i < npat; ++i)
|
||||
{
|
||||
float[] vcand = new float[3];
|
||||
vSet(vcand, res[0] + pat[i * 2 + 0] * cr, 0f, res[2] + pat[i * 2 + 1] * cr);
|
||||
if (sqr(vcand[0]) + sqr(vcand[2]) > sqr(vmax + 0.001f))
|
||||
|
@ -494,7 +555,8 @@ public class ObstacleAvoidanceQuery {
|
|||
|
||||
float penalty = processSample(vcand, cr / 10, pos, rad, vel, dvel, minPenalty, debug);
|
||||
ns++;
|
||||
if (penalty < minPenalty) {
|
||||
if (penalty < minPenalty)
|
||||
{
|
||||
minPenalty = penalty;
|
||||
vCopy(bvel, vcand);
|
||||
}
|
||||
|
@ -504,10 +566,10 @@ public class ObstacleAvoidanceQuery {
|
|||
|
||||
cr *= 0.5f;
|
||||
}
|
||||
|
||||
vCopy(nvel, res);
|
||||
|
||||
return Tuple.Create(ns, nvel);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -23,8 +23,6 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour.Crowd
|
||||
{
|
||||
|
||||
|
||||
using static DetourCommon;
|
||||
|
||||
/**
|
||||
|
@ -64,33 +62,40 @@ using static DetourCommon;
|
|||
* replanning may be needed. E.g. If you move the target, check #getLastPoly() to see if it is the expected polygon.
|
||||
*
|
||||
*/
|
||||
public class PathCorridor {
|
||||
|
||||
public class PathCorridor
|
||||
{
|
||||
private readonly float[] m_pos = new float[3];
|
||||
private readonly float[] m_target = new float[3];
|
||||
private List<long> m_path;
|
||||
|
||||
protected List<long> mergeCorridorStartMoved(List<long> path, List<long> visited) {
|
||||
protected List<long> mergeCorridorStartMoved(List<long> path, List<long> visited)
|
||||
{
|
||||
int furthestPath = -1;
|
||||
int furthestVisited = -1;
|
||||
|
||||
// Find furthest common polygon.
|
||||
for (int i = path.Count - 1; i >= 0; --i) {
|
||||
for (int i = path.Count - 1; i >= 0; --i)
|
||||
{
|
||||
bool found = false;
|
||||
for (int j = visited.Count - 1; j >= 0; --j) {
|
||||
if (path[i] == visited[j]) {
|
||||
for (int j = visited.Count - 1; j >= 0; --j)
|
||||
{
|
||||
if (path[i] == visited[j])
|
||||
{
|
||||
furthestPath = i;
|
||||
furthestVisited = j;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
|
||||
if (found)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no intersection found just return current path.
|
||||
if (furthestPath == -1 || furthestVisited == -1) {
|
||||
if (furthestPath == -1 || furthestVisited == -1)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
|
@ -99,34 +104,43 @@ public class PathCorridor {
|
|||
// Adjust beginning of the buffer to include the visited.
|
||||
List<long> result = new List<long>();
|
||||
// Store visited
|
||||
for (int i = visited.Count - 1; i > furthestVisited; --i) {
|
||||
for (int i = visited.Count - 1; i > furthestVisited; --i)
|
||||
{
|
||||
result.Add(visited[i]);
|
||||
}
|
||||
|
||||
result.AddRange(path.GetRange(furthestPath, path.Count - furthestPath));
|
||||
return result;
|
||||
}
|
||||
|
||||
protected List<long> mergeCorridorEndMoved(List<long> path, List<long> visited) {
|
||||
protected List<long> mergeCorridorEndMoved(List<long> path, List<long> visited)
|
||||
{
|
||||
int furthestPath = -1;
|
||||
int furthestVisited = -1;
|
||||
|
||||
// Find furthest common polygon.
|
||||
for (int i = 0; i < path.Count; ++i) {
|
||||
for (int i = 0; i < path.Count; ++i)
|
||||
{
|
||||
bool found = false;
|
||||
for (int j = visited.Count - 1; j >= 0; --j) {
|
||||
if (path[i] == visited[j]) {
|
||||
for (int j = visited.Count - 1; j >= 0; --j)
|
||||
{
|
||||
if (path[i] == visited[j])
|
||||
{
|
||||
furthestPath = i;
|
||||
furthestVisited = j;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
|
||||
if (found)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no intersection found just return current path.
|
||||
if (furthestPath == -1 || furthestVisited == -1) {
|
||||
if (furthestPath == -1 || furthestVisited == -1)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
|
@ -136,28 +150,34 @@ public class PathCorridor {
|
|||
return result;
|
||||
}
|
||||
|
||||
protected List<long> mergeCorridorStartShortcut(List<long> path, List<long> visited) {
|
||||
|
||||
protected List<long> mergeCorridorStartShortcut(List<long> path, List<long> visited)
|
||||
{
|
||||
int furthestPath = -1;
|
||||
int furthestVisited = -1;
|
||||
|
||||
// Find furthest common polygon.
|
||||
for (int i = path.Count - 1; i >= 0; --i) {
|
||||
for (int i = path.Count - 1; i >= 0; --i)
|
||||
{
|
||||
bool found = false;
|
||||
for (int j = visited.Count - 1; j >= 0; --j) {
|
||||
if (path[i] == visited[j]) {
|
||||
for (int j = visited.Count - 1; j >= 0; --j)
|
||||
{
|
||||
if (path[i] == visited[j])
|
||||
{
|
||||
furthestPath = i;
|
||||
furthestVisited = j;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
|
||||
if (found)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no intersection found just return current path.
|
||||
if (furthestPath == -1 || furthestVisited <= 0) {
|
||||
if (furthestPath == -1 || furthestVisited <= 0)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
|
@ -172,7 +192,8 @@ public class PathCorridor {
|
|||
/**
|
||||
* Allocates the corridor's path buffer.
|
||||
*/
|
||||
public PathCorridor() {
|
||||
public PathCorridor()
|
||||
{
|
||||
m_path = new List<long>();
|
||||
}
|
||||
|
||||
|
@ -184,7 +205,8 @@ public class PathCorridor {
|
|||
* @param pos
|
||||
* The new position in the corridor. [(x, y, z)]
|
||||
*/
|
||||
public void reset(long refs, float[] pos) {
|
||||
public void reset(long refs, float[] pos)
|
||||
{
|
||||
m_path.Clear();
|
||||
m_path.Add(refs);
|
||||
vCopy(m_pos, pos);
|
||||
|
@ -210,31 +232,41 @@ public class PathCorridor {
|
|||
* @param[in] navquery The query object used to build the corridor.
|
||||
* @return Corners
|
||||
*/
|
||||
public List<StraightPathItem> findCorners(int maxCorners, NavMeshQuery navquery, QueryFilter filter) {
|
||||
public List<StraightPathItem> findCorners(int maxCorners, NavMeshQuery navquery, QueryFilter filter)
|
||||
{
|
||||
List<StraightPathItem> path = new List<StraightPathItem>();
|
||||
Result<List<StraightPathItem>> result = navquery.findStraightPath(m_pos, m_target, m_path, maxCorners, 0);
|
||||
if (result.succeeded()) {
|
||||
if (result.succeeded())
|
||||
{
|
||||
path = result.result;
|
||||
// Prune points in the beginning of the path which are too close.
|
||||
int start = 0;
|
||||
foreach (StraightPathItem spi in path) {
|
||||
foreach (StraightPathItem spi in path)
|
||||
{
|
||||
if ((spi.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0
|
||||
|| vDist2DSqr(spi.getPos(), m_pos) > MIN_TARGET_DIST) {
|
||||
|| vDist2DSqr(spi.getPos(), m_pos) > MIN_TARGET_DIST)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
start++;
|
||||
}
|
||||
|
||||
int end = path.Count;
|
||||
// Prune points after an off-mesh connection.
|
||||
for (int i = start; i < path.Count; i++) {
|
||||
for (int i = start; i < path.Count; i++)
|
||||
{
|
||||
StraightPathItem spi = path[i];
|
||||
if ((spi.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) {
|
||||
if ((spi.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
|
||||
{
|
||||
end = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
path = path.GetRange(start, end - start);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
@ -265,14 +297,15 @@ public class PathCorridor {
|
|||
* @param filter
|
||||
* The filter to apply to the operation.
|
||||
*/
|
||||
|
||||
public void optimizePathVisibility(float[] next, float pathOptimizationRange, NavMeshQuery navquery,
|
||||
QueryFilter filter) {
|
||||
QueryFilter filter)
|
||||
{
|
||||
// Clamp the ray to max distance.
|
||||
float dist = vDist2D(m_pos, next);
|
||||
|
||||
// If too close to the goal, do not try to optimize.
|
||||
if (dist < 0.01f) {
|
||||
if (dist < 0.01f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -285,8 +318,10 @@ public class PathCorridor {
|
|||
float[] goal = vMad(m_pos, delta, pathOptimizationRange / dist);
|
||||
|
||||
Result<RaycastHit> rc = navquery.raycast(m_path[0], m_pos, goal, filter, 0, 0);
|
||||
if (rc.succeeded()) {
|
||||
if (rc.result.path.Count > 1 && rc.result.t > 0.99f) {
|
||||
if (rc.succeeded())
|
||||
{
|
||||
if (rc.result.path.Count > 1 && rc.result.t > 0.99f)
|
||||
{
|
||||
m_path = mergeCorridorStartShortcut(m_path, rc.result.path);
|
||||
}
|
||||
}
|
||||
|
@ -308,8 +343,10 @@ public class PathCorridor {
|
|||
* The filter to apply to the operation.
|
||||
*
|
||||
*/
|
||||
public bool optimizePathTopology(NavMeshQuery navquery, QueryFilter filter, int maxIterations) {
|
||||
if (m_path.Count < 3) {
|
||||
public bool optimizePathTopology(NavMeshQuery navquery, QueryFilter filter, int maxIterations)
|
||||
{
|
||||
if (m_path.Count < 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -317,7 +354,8 @@ public class PathCorridor {
|
|||
navquery.updateSlicedFindPath(maxIterations);
|
||||
Result<List<long>> fpr = navquery.finalizeSlicedFindPathPartial(m_path);
|
||||
|
||||
if (fpr.succeeded() && fpr.result.Count > 0) {
|
||||
if (fpr.succeeded() && fpr.result.Count > 0)
|
||||
{
|
||||
m_path = mergeCorridorStartShortcut(m_path, fpr.result);
|
||||
return true;
|
||||
}
|
||||
|
@ -326,16 +364,20 @@ public class PathCorridor {
|
|||
}
|
||||
|
||||
public bool moveOverOffmeshConnection(long offMeshConRef, long[] refs, float[] start, float[] end,
|
||||
NavMeshQuery navquery) {
|
||||
NavMeshQuery navquery)
|
||||
{
|
||||
// Advance the path up to and over the off-mesh connection.
|
||||
long prevRef = 0, polyRef = m_path[0];
|
||||
int npos = 0;
|
||||
while (npos < m_path.Count && polyRef != offMeshConRef) {
|
||||
while (npos < m_path.Count && polyRef != offMeshConRef)
|
||||
{
|
||||
prevRef = polyRef;
|
||||
polyRef = m_path[npos];
|
||||
npos++;
|
||||
}
|
||||
if (npos == m_path.Count) {
|
||||
|
||||
if (npos == m_path.Count)
|
||||
{
|
||||
// Could not find offMeshConRef
|
||||
return false;
|
||||
}
|
||||
|
@ -347,12 +389,14 @@ public class PathCorridor {
|
|||
|
||||
NavMesh nav = navquery.getAttachedNavMesh();
|
||||
Result<Tuple<float[], float[]>> startEnd = nav.getOffMeshConnectionPolyEndPoints(refs[0], refs[1]);
|
||||
if (startEnd.succeeded()) {
|
||||
if (startEnd.succeeded())
|
||||
{
|
||||
vCopy(m_pos, startEnd.result.Item2);
|
||||
vCopy(start, startEnd.result.Item1);
|
||||
vCopy(end, startEnd.result.Item2);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -379,19 +423,24 @@ public class PathCorridor {
|
|||
* @param filter
|
||||
* The filter to apply to the operation.
|
||||
*/
|
||||
public bool movePosition(float[] npos, NavMeshQuery navquery, QueryFilter filter) {
|
||||
public bool movePosition(float[] npos, NavMeshQuery navquery, QueryFilter filter)
|
||||
{
|
||||
// Move along navmesh and update new position.
|
||||
Result<MoveAlongSurfaceResult> masResult = navquery.moveAlongSurface(m_path[0], m_pos, npos, filter);
|
||||
if (masResult.succeeded()) {
|
||||
if (masResult.succeeded())
|
||||
{
|
||||
m_path = mergeCorridorStartMoved(m_path, masResult.result.getVisited());
|
||||
// Adjust the position to stay on top of the navmesh.
|
||||
vCopy(m_pos, masResult.result.getResultPos());
|
||||
Result<float> hr = navquery.getPolyHeight(m_path[0], masResult.result.getResultPos());
|
||||
if (hr.succeeded()) {
|
||||
if (hr.succeeded())
|
||||
{
|
||||
m_pos[1] = hr.result;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -412,11 +461,13 @@ public class PathCorridor {
|
|||
* @param filter
|
||||
* The filter to apply to the operation.
|
||||
*/
|
||||
public bool moveTargetPosition(float[] npos, NavMeshQuery navquery, QueryFilter filter) {
|
||||
public bool moveTargetPosition(float[] npos, NavMeshQuery navquery, QueryFilter filter)
|
||||
{
|
||||
// Move along navmesh and update new position.
|
||||
Result<MoveAlongSurfaceResult> masResult = navquery.moveAlongSurface(m_path[m_path.Count - 1], m_target,
|
||||
npos, filter);
|
||||
if (masResult.succeeded()) {
|
||||
if (masResult.succeeded())
|
||||
{
|
||||
m_path = mergeCorridorEndMoved(m_path, masResult.result.getVisited());
|
||||
// TODO: should we do that?
|
||||
// Adjust the position to stay on top of the navmesh.
|
||||
|
@ -427,6 +478,7 @@ public class PathCorridor {
|
|||
vCopy(m_target, masResult.result.getResultPos());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -440,47 +492,57 @@ public class PathCorridor {
|
|||
* @param path
|
||||
* The path corridor.
|
||||
*/
|
||||
|
||||
public void setCorridor(float[] target, List<long> path) {
|
||||
public void setCorridor(float[] target, List<long> path)
|
||||
{
|
||||
vCopy(m_target, target);
|
||||
m_path = new List<long>(path);
|
||||
}
|
||||
|
||||
public void fixPathStart(long safeRef, float[] safePos) {
|
||||
public void fixPathStart(long safeRef, float[] safePos)
|
||||
{
|
||||
vCopy(m_pos, safePos);
|
||||
if (m_path.Count < 3 && m_path.Count > 0) {
|
||||
if (m_path.Count < 3 && m_path.Count > 0)
|
||||
{
|
||||
long p = m_path[m_path.Count - 1];
|
||||
m_path.Clear();
|
||||
m_path.Add(safeRef);
|
||||
m_path.Add(0L);
|
||||
m_path.Add(p);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
m_path.Clear();
|
||||
m_path.Add(safeRef);
|
||||
m_path.Add(0L);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void trimInvalidPath(long safeRef, float[] safePos, NavMeshQuery navquery, QueryFilter filter) {
|
||||
public void trimInvalidPath(long safeRef, float[] safePos, NavMeshQuery navquery, QueryFilter filter)
|
||||
{
|
||||
// Keep valid path as far as possible.
|
||||
int n = 0;
|
||||
while (n < m_path.Count && navquery.isValidPolyRef(m_path[n], filter)) {
|
||||
while (n < m_path.Count && navquery.isValidPolyRef(m_path[n], filter))
|
||||
{
|
||||
n++;
|
||||
}
|
||||
|
||||
if (n == 0) {
|
||||
if (n == 0)
|
||||
{
|
||||
// The first polyref is bad, use current safe values.
|
||||
vCopy(m_pos, safePos);
|
||||
m_path.Clear();
|
||||
m_path.Add(safeRef);
|
||||
} else if (n < m_path.Count) {
|
||||
}
|
||||
else if (n < m_path.Count)
|
||||
{
|
||||
m_path = m_path.GetRange(0, n);
|
||||
// The path is partially usable.
|
||||
}
|
||||
|
||||
// Clamp target pos to last poly
|
||||
Result<float[]> result = navquery.closestPointOnPolyBoundary(m_path[m_path.Count - 1], m_target);
|
||||
if (result.succeeded()) {
|
||||
if (result.succeeded())
|
||||
{
|
||||
vCopy(m_target, result.result);
|
||||
}
|
||||
}
|
||||
|
@ -498,11 +560,14 @@ public class PathCorridor {
|
|||
* The filter to apply to the operation.
|
||||
* @return
|
||||
*/
|
||||
public bool isValid(int maxLookAhead, NavMeshQuery navquery, QueryFilter filter) {
|
||||
public bool isValid(int maxLookAhead, NavMeshQuery navquery, QueryFilter filter)
|
||||
{
|
||||
// Check that all polygons still pass query filter.
|
||||
int n = Math.Min(m_path.Count, maxLookAhead);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (!navquery.isValidPolyRef(m_path[i], filter)) {
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
if (!navquery.isValidPolyRef(m_path[i], filter))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -515,7 +580,8 @@ public class PathCorridor {
|
|||
*
|
||||
* @return The current position within the corridor.
|
||||
*/
|
||||
public float[] getPos() {
|
||||
public float[] getPos()
|
||||
{
|
||||
return m_pos;
|
||||
}
|
||||
|
||||
|
@ -524,7 +590,8 @@ public class PathCorridor {
|
|||
*
|
||||
* @return The current target within the corridor.
|
||||
*/
|
||||
public float[] getTarget() {
|
||||
public float[] getTarget()
|
||||
{
|
||||
return m_target;
|
||||
}
|
||||
|
||||
|
@ -533,7 +600,8 @@ public class PathCorridor {
|
|||
*
|
||||
* @return The polygon reference id of the first polygon in the corridor. (Or zero if there is no path.)
|
||||
*/
|
||||
public long getFirstPoly() {
|
||||
public long getFirstPoly()
|
||||
{
|
||||
return 0 == m_path.Count ? 0 : m_path[0];
|
||||
}
|
||||
|
||||
|
@ -542,14 +610,16 @@ public class PathCorridor {
|
|||
*
|
||||
* @return The polygon reference id of the last polygon in the corridor. (Or zero if there is no path.)
|
||||
*/
|
||||
public long getLastPoly() {
|
||||
public long getLastPoly()
|
||||
{
|
||||
return 0 == m_path.Count ? 0 : m_path[m_path.Count - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* The corridor's path.
|
||||
*/
|
||||
public List<long> getPath() {
|
||||
public List<long> getPath()
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
|
||||
|
@ -558,9 +628,9 @@ public class PathCorridor {
|
|||
*
|
||||
* @return The number of polygons in the current corridor path.
|
||||
*/
|
||||
public int getPathCount() {
|
||||
public int getPathCount()
|
||||
{
|
||||
return m_path.Count;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -17,20 +17,22 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.Crowd
|
||||
{
|
||||
|
||||
|
||||
public class PathQuery {
|
||||
public class PathQuery
|
||||
{
|
||||
/// Path find start and end location.
|
||||
public float[] startPos = new float[3];
|
||||
|
||||
public float[] endPos = new float[3];
|
||||
public long startRef;
|
||||
public long endRef;
|
||||
public QueryFilter filter; /// < TODO: This is potentially dangerous!
|
||||
public QueryFilter filter;
|
||||
|
||||
/// < TODO: This is potentially dangerous!
|
||||
public readonly PathQueryResult result = new PathQueryResult();
|
||||
|
||||
public NavMeshQuery navQuery;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -20,11 +20,9 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour.Crowd
|
||||
{
|
||||
|
||||
|
||||
public class PathQueryResult {
|
||||
public class PathQueryResult
|
||||
{
|
||||
public Status status;
|
||||
public List<long> path = new List<long>();
|
||||
}
|
||||
|
||||
}
|
|
@ -22,55 +22,67 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour.Crowd
|
||||
{
|
||||
|
||||
|
||||
using static DetourCommon;
|
||||
|
||||
public class PathQueue {
|
||||
|
||||
public class PathQueue
|
||||
{
|
||||
private readonly CrowdConfig config;
|
||||
private readonly LinkedList<PathQuery> queue = new LinkedList<PathQuery>();
|
||||
|
||||
public PathQueue(CrowdConfig config) {
|
||||
public PathQueue(CrowdConfig config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public void update(NavMesh navMesh) {
|
||||
public void update(NavMesh navMesh)
|
||||
{
|
||||
// Update path request until there is nothing to update or up to maxIters pathfinder iterations has been
|
||||
// consumed.
|
||||
int iterCount = config.maxFindPathIterations;
|
||||
while (iterCount > 0) {
|
||||
while (iterCount > 0)
|
||||
{
|
||||
PathQuery? q = queue.First?.Value;
|
||||
if (q == null) {
|
||||
if (q == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle query start.
|
||||
if (q.result.status == null) {
|
||||
if (q.result.status == null)
|
||||
{
|
||||
q.navQuery = new NavMeshQuery(navMesh);
|
||||
q.result.status = q.navQuery.initSlicedFindPath(q.startRef, q.endRef, q.startPos, q.endPos, q.filter, 0);
|
||||
}
|
||||
|
||||
// Handle query in progress.
|
||||
if (q.result.status.isInProgress()) {
|
||||
if (q.result.status.isInProgress())
|
||||
{
|
||||
Result<int> res = q.navQuery.updateSlicedFindPath(iterCount);
|
||||
q.result.status = res.status;
|
||||
iterCount -= res.result;
|
||||
}
|
||||
if (q.result.status.isSuccess()) {
|
||||
|
||||
if (q.result.status.isSuccess())
|
||||
{
|
||||
Result<List<long>> path = q.navQuery.finalizeSlicedFindPath();
|
||||
q.result.status = path.status;
|
||||
q.result.path = path.result;
|
||||
}
|
||||
if (!(q.result.status.isFailed() || q.result.status.isSuccess())) {
|
||||
|
||||
if (!(q.result.status.isFailed() || q.result.status.isSuccess()))
|
||||
{
|
||||
queue.AddFirst(q);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public PathQueryResult request(long startRef, long endRef, float[] startPos, float[] endPos, QueryFilter filter) {
|
||||
if (queue.Count >= config.pathQueueSize) {
|
||||
public PathQueryResult request(long startRef, long endRef, float[] startPos, float[] endPos, QueryFilter filter)
|
||||
{
|
||||
if (queue.Count >= config.pathQueueSize)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
PathQuery q = new PathQuery();
|
||||
vCopy(q.startPos, startPos);
|
||||
q.startRef = startRef;
|
||||
|
@ -81,7 +93,5 @@ public class PathQueue {
|
|||
queue.AddLast(q);
|
||||
return q.result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -24,53 +24,62 @@ using System.Linq;
|
|||
|
||||
namespace DotRecast.Detour.Crowd
|
||||
{
|
||||
|
||||
|
||||
public class ProximityGrid {
|
||||
|
||||
public class ProximityGrid
|
||||
{
|
||||
private readonly float m_cellSize;
|
||||
private readonly float m_invCellSize;
|
||||
private readonly Dictionary<ItemKey, List<CrowdAgent>> items;
|
||||
|
||||
public ProximityGrid(float m_cellSize) {
|
||||
public ProximityGrid(float m_cellSize)
|
||||
{
|
||||
this.m_cellSize = m_cellSize;
|
||||
m_invCellSize = 1.0f / m_cellSize;
|
||||
items = new Dictionary<ItemKey, List<CrowdAgent>>();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
void clear()
|
||||
{
|
||||
items.Clear();
|
||||
}
|
||||
|
||||
public void addItem(CrowdAgent agent, float minx, float miny, float maxx, float maxy) {
|
||||
public void addItem(CrowdAgent agent, float minx, float miny, float maxx, float maxy)
|
||||
{
|
||||
int iminx = (int)Math.Floor(minx * m_invCellSize);
|
||||
int iminy = (int)Math.Floor(miny * m_invCellSize);
|
||||
int imaxx = (int)Math.Floor(maxx * m_invCellSize);
|
||||
int imaxy = (int)Math.Floor(maxy * m_invCellSize);
|
||||
|
||||
for (int y = iminy; y <= imaxy; ++y) {
|
||||
for (int x = iminx; x <= imaxx; ++x) {
|
||||
for (int y = iminy; y <= imaxy; ++y)
|
||||
{
|
||||
for (int x = iminx; x <= imaxx; ++x)
|
||||
{
|
||||
ItemKey key = new ItemKey(x, y);
|
||||
if (!items.TryGetValue(key, out var ids)) {
|
||||
if (!items.TryGetValue(key, out var ids))
|
||||
{
|
||||
ids = new List<CrowdAgent>();
|
||||
items.Add(key, ids);
|
||||
}
|
||||
|
||||
ids.Add(agent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HashSet<CrowdAgent> queryItems(float minx, float miny, float maxx, float maxy) {
|
||||
public HashSet<CrowdAgent> queryItems(float minx, float miny, float maxx, float maxy)
|
||||
{
|
||||
int iminx = (int)Math.Floor(minx * m_invCellSize);
|
||||
int iminy = (int)Math.Floor(miny * m_invCellSize);
|
||||
int imaxx = (int)Math.Floor(maxx * m_invCellSize);
|
||||
int imaxy = (int)Math.Floor(maxy * m_invCellSize);
|
||||
|
||||
HashSet<CrowdAgent> result = new HashSet<CrowdAgent>();
|
||||
for (int y = iminy; y <= imaxy; ++y) {
|
||||
for (int x = iminx; x <= imaxx; ++x) {
|
||||
for (int y = iminy; y <= imaxy; ++y)
|
||||
{
|
||||
for (int x = iminx; x <= imaxx; ++x)
|
||||
{
|
||||
ItemKey key = new ItemKey(x, y);
|
||||
if (items.TryGetValue(key, out var ids)) {
|
||||
if (items.TryGetValue(key, out var ids))
|
||||
{
|
||||
result.UnionWith(ids);
|
||||
}
|
||||
}
|
||||
|
@ -79,28 +88,32 @@ public class ProximityGrid {
|
|||
return result;
|
||||
}
|
||||
|
||||
public List<int[]> getItemCounts() {
|
||||
public List<int[]> getItemCounts()
|
||||
{
|
||||
return items
|
||||
.Where(e => e.Value.Count > 0)
|
||||
.Select(e => new int[] { e.Key.x, e.Key.y, e.Value.Count })
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public float getCellSize() {
|
||||
public float getCellSize()
|
||||
{
|
||||
return m_cellSize;
|
||||
}
|
||||
|
||||
private class ItemKey {
|
||||
|
||||
private class ItemKey
|
||||
{
|
||||
public readonly int x;
|
||||
public readonly int y;
|
||||
|
||||
public ItemKey(int x, int y) {
|
||||
public ItemKey(int x, int y)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public override int GetHashCode() {
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + x;
|
||||
|
@ -108,7 +121,8 @@ public class ProximityGrid {
|
|||
return result;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) {
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
|
||||
|
@ -127,8 +141,6 @@ public class ProximityGrid {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -17,22 +17,20 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.Crowd
|
||||
{
|
||||
|
||||
|
||||
public class SweepCircleCircleResult {
|
||||
|
||||
public class SweepCircleCircleResult
|
||||
{
|
||||
public readonly bool intersection;
|
||||
public readonly float htmin;
|
||||
public readonly float htmax;
|
||||
|
||||
public SweepCircleCircleResult(bool intersection, float htmin, float htmax) {
|
||||
public SweepCircleCircleResult(bool intersection, float htmin, float htmax)
|
||||
{
|
||||
this.intersection = intersection;
|
||||
this.htmin = htmin;
|
||||
this.htmax = htmax;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,17 +17,14 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.Crowd.Tracking
|
||||
{
|
||||
|
||||
|
||||
public class CrowdAgentDebugInfo {
|
||||
|
||||
public class CrowdAgentDebugInfo
|
||||
{
|
||||
public CrowdAgent agent;
|
||||
public float[] optStart = new float[3];
|
||||
public float[] optEnd = new float[3];
|
||||
public ObstacleAvoidanceDebugData vod;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,11 +22,10 @@ using System;
|
|||
|
||||
namespace DotRecast.Detour.Crowd.Tracking
|
||||
{
|
||||
|
||||
|
||||
using static DetourCommon;
|
||||
|
||||
public class ObstacleAvoidanceDebugData {
|
||||
public class ObstacleAvoidanceDebugData
|
||||
{
|
||||
int m_nsamples;
|
||||
int m_maxSamples;
|
||||
float[] m_vel;
|
||||
|
@ -37,7 +36,8 @@ public class ObstacleAvoidanceDebugData {
|
|||
float[] m_spen;
|
||||
float[] m_tpen;
|
||||
|
||||
public ObstacleAvoidanceDebugData(int maxSamples) {
|
||||
public ObstacleAvoidanceDebugData(int maxSamples)
|
||||
{
|
||||
m_maxSamples = maxSamples;
|
||||
m_vel = new float[3 * m_maxSamples];
|
||||
m_pen = new float[m_maxSamples];
|
||||
|
@ -48,25 +48,30 @@ public class ObstacleAvoidanceDebugData {
|
|||
m_tpen = new float[m_maxSamples];
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
public void reset()
|
||||
{
|
||||
m_nsamples = 0;
|
||||
}
|
||||
|
||||
void normalizeArray(float[] arr, int n) {
|
||||
void normalizeArray(float[] arr, int n)
|
||||
{
|
||||
// Normalize penaly range.
|
||||
float minPen = float.MaxValue;
|
||||
float maxPen = -float.MaxValue;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
minPen = Math.Min(minPen, arr[i]);
|
||||
maxPen = Math.Max(maxPen, arr[i]);
|
||||
}
|
||||
|
||||
float penRange = maxPen - minPen;
|
||||
float s = penRange > 0.001f ? (1.0f / penRange) : 1;
|
||||
for (int i = 0; i < n; ++i)
|
||||
arr[i] = clamp((arr[i] - minPen) * s, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
public void normalizeSamples() {
|
||||
public void normalizeSamples()
|
||||
{
|
||||
normalizeArray(m_pen, m_nsamples);
|
||||
normalizeArray(m_vpen, m_nsamples);
|
||||
normalizeArray(m_vcpen, m_nsamples);
|
||||
|
@ -74,7 +79,8 @@ public class ObstacleAvoidanceDebugData {
|
|||
normalizeArray(m_tpen, m_nsamples);
|
||||
}
|
||||
|
||||
public void addSample(float[] vel, float ssize, float pen, float vpen, float vcpen, float spen, float tpen) {
|
||||
public void addSample(float[] vel, float ssize, float pen, float vpen, float vcpen, float spen, float tpen)
|
||||
{
|
||||
if (m_nsamples >= m_maxSamples)
|
||||
return;
|
||||
m_vel[m_nsamples * 3] = vel[0];
|
||||
|
@ -89,11 +95,13 @@ public class ObstacleAvoidanceDebugData {
|
|||
m_nsamples++;
|
||||
}
|
||||
|
||||
public int getSampleCount() {
|
||||
public int getSampleCount()
|
||||
{
|
||||
return m_nsamples;
|
||||
}
|
||||
|
||||
public float[] getSampleVelocity(int i) {
|
||||
public float[] getSampleVelocity(int i)
|
||||
{
|
||||
float[] vel = new float[3];
|
||||
vel[0] = m_vel[i * 3];
|
||||
vel[1] = m_vel[i * 3 + 1];
|
||||
|
@ -101,27 +109,33 @@ public class ObstacleAvoidanceDebugData {
|
|||
return vel;
|
||||
}
|
||||
|
||||
public float getSampleSize(int i) {
|
||||
public float getSampleSize(int i)
|
||||
{
|
||||
return m_ssize[i];
|
||||
}
|
||||
|
||||
public float getSamplePenalty(int i) {
|
||||
public float getSamplePenalty(int i)
|
||||
{
|
||||
return m_pen[i];
|
||||
}
|
||||
|
||||
public float getSampleDesiredVelocityPenalty(int i) {
|
||||
public float getSampleDesiredVelocityPenalty(int i)
|
||||
{
|
||||
return m_vpen[i];
|
||||
}
|
||||
|
||||
public float getSampleCurrentVelocityPenalty(int i) {
|
||||
public float getSampleCurrentVelocityPenalty(int i)
|
||||
{
|
||||
return m_vcpen[i];
|
||||
}
|
||||
|
||||
public float getSamplePreferredSidePenalty(int i) {
|
||||
public float getSamplePreferredSidePenalty(int i)
|
||||
{
|
||||
return m_spen[i];
|
||||
}
|
||||
|
||||
public float getSampleCollisionTimePenalty(int i) {
|
||||
public float getSampleCollisionTimePenalty(int i)
|
||||
{
|
||||
return m_tpen[i];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,28 +21,27 @@ using DotRecast.Detour.Dynamic.Colliders;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic
|
||||
{
|
||||
|
||||
|
||||
public class AddColliderQueueItem : UpdateQueueItem {
|
||||
|
||||
public class AddColliderQueueItem : UpdateQueueItem
|
||||
{
|
||||
private readonly long colliderId;
|
||||
private readonly Collider collider;
|
||||
private readonly ICollection<DynamicTile> _affectedTiles;
|
||||
|
||||
public AddColliderQueueItem(long colliderId, Collider collider, ICollection<DynamicTile> affectedTiles) {
|
||||
public AddColliderQueueItem(long colliderId, Collider collider, ICollection<DynamicTile> affectedTiles)
|
||||
{
|
||||
this.colliderId = colliderId;
|
||||
this.collider = collider;
|
||||
_affectedTiles = affectedTiles;
|
||||
}
|
||||
|
||||
public ICollection<DynamicTile> affectedTiles() {
|
||||
public ICollection<DynamicTile> affectedTiles()
|
||||
{
|
||||
return _affectedTiles;
|
||||
}
|
||||
|
||||
public void process(DynamicTile tile) {
|
||||
public void process(DynamicTile tile)
|
||||
{
|
||||
tile.addCollider(colliderId, collider);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -20,21 +20,21 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Colliders
|
||||
{
|
||||
|
||||
|
||||
public abstract class AbstractCollider : Collider {
|
||||
|
||||
public abstract class AbstractCollider : Collider
|
||||
{
|
||||
protected readonly int area;
|
||||
protected readonly float flagMergeThreshold;
|
||||
protected readonly float[] _bounds;
|
||||
|
||||
public AbstractCollider(int area, float flagMergeThreshold, float[] bounds) {
|
||||
public AbstractCollider(int area, float flagMergeThreshold, float[] bounds)
|
||||
{
|
||||
this.area = area;
|
||||
this.flagMergeThreshold = flagMergeThreshold;
|
||||
this._bounds = bounds;
|
||||
}
|
||||
|
||||
public float[] bounds() {
|
||||
public float[] bounds()
|
||||
{
|
||||
return _bounds;
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,5 @@ public abstract class AbstractCollider : Collider {
|
|||
{
|
||||
///?
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -21,12 +21,11 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Colliders
|
||||
{
|
||||
|
||||
|
||||
public class BoxCollider : AbstractCollider {
|
||||
|
||||
public class BoxCollider : AbstractCollider
|
||||
{
|
||||
private readonly float[] center;
|
||||
private readonly float[][] halfEdges;
|
||||
|
||||
public BoxCollider(float[] center, float[][] halfEdges, int area, float flagMergeThreshold) :
|
||||
base(area, flagMergeThreshold, bounds(center, halfEdges))
|
||||
{
|
||||
|
@ -34,10 +33,15 @@ public class BoxCollider : AbstractCollider {
|
|||
this.halfEdges = halfEdges;
|
||||
}
|
||||
|
||||
private static float[] bounds(float[] center, float[][] halfEdges) {
|
||||
float[] bounds = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
|
||||
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity };
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
private static float[] bounds(float[] center, float[][] halfEdges)
|
||||
{
|
||||
float[] bounds = new float[]
|
||||
{
|
||||
float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
|
||||
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity
|
||||
};
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
float s0 = (i & 1) != 0 ? 1f : -1f;
|
||||
float s1 = (i & 2) != 0 ? 1f : -1f;
|
||||
float s2 = (i & 4) != 0 ? 1f : -1f;
|
||||
|
@ -51,15 +55,18 @@ public class BoxCollider : AbstractCollider {
|
|||
bounds[4] = Math.Max(bounds[4], vy);
|
||||
bounds[5] = Math.Max(bounds[5], vz);
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
public void rasterize(Heightfield hf, Telemetry telemetry) {
|
||||
public void rasterize(Heightfield hf, Telemetry telemetry)
|
||||
{
|
||||
RecastFilledVolumeRasterization.rasterizeBox(hf, center, halfEdges, area, (int)Math.Floor(flagMergeThreshold / hf.ch),
|
||||
telemetry);
|
||||
}
|
||||
|
||||
public static float[][] getHalfEdges(float[] up, float[] forward, float[] extent) {
|
||||
public static float[][] getHalfEdges(float[] up, float[] forward, float[] extent)
|
||||
{
|
||||
float[][] halfEdges = new float[][] { new float[3], new float[] { up[0], up[1], up[2] }, new float[3] };
|
||||
RecastVectors.normalize(halfEdges[1]);
|
||||
RecastVectors.cross(halfEdges[0], up, forward);
|
||||
|
@ -77,7 +84,5 @@ public class BoxCollider : AbstractCollider {
|
|||
halfEdges[2][2] *= extent[2];
|
||||
return halfEdges;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -21,10 +21,8 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Colliders
|
||||
{
|
||||
|
||||
|
||||
public class CapsuleCollider : AbstractCollider {
|
||||
|
||||
public class CapsuleCollider : AbstractCollider
|
||||
{
|
||||
private readonly float[] start;
|
||||
private readonly float[] end;
|
||||
private readonly float radius;
|
||||
|
@ -37,17 +35,20 @@ public class CapsuleCollider : AbstractCollider {
|
|||
this.radius = radius;
|
||||
}
|
||||
|
||||
public void rasterize(Heightfield hf, Telemetry telemetry) {
|
||||
public void rasterize(Heightfield hf, Telemetry telemetry)
|
||||
{
|
||||
RecastFilledVolumeRasterization.rasterizeCapsule(hf, start, end, radius, area, (int)Math.Floor(flagMergeThreshold / hf.ch),
|
||||
telemetry);
|
||||
}
|
||||
|
||||
private static float[] bounds(float[] start, float[] end, float radius) {
|
||||
return new float[] { Math.Min(start[0], end[0]) - radius, Math.Min(start[1], end[1]) - radius,
|
||||
private static float[] bounds(float[] start, float[] end, float radius)
|
||||
{
|
||||
return new float[]
|
||||
{
|
||||
Math.Min(start[0], end[0]) - radius, Math.Min(start[1], end[1]) - radius,
|
||||
Math.Min(start[2], end[2]) - radius, Math.Max(start[0], end[0]) + radius, Math.Max(start[1], end[1]) + radius,
|
||||
Math.Max(start[2], end[2]) + radius };
|
||||
Math.Max(start[2], end[2]) + radius
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -20,12 +20,9 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Colliders
|
||||
{
|
||||
|
||||
|
||||
public interface Collider {
|
||||
|
||||
public interface Collider
|
||||
{
|
||||
float[] bounds();
|
||||
void rasterize(Heightfield hf, Telemetry telemetry);
|
||||
}
|
||||
|
||||
}
|
|
@ -23,31 +23,37 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Colliders
|
||||
{
|
||||
|
||||
|
||||
public class CompositeCollider : Collider {
|
||||
|
||||
public class CompositeCollider : Collider
|
||||
{
|
||||
private readonly List<Collider> colliders;
|
||||
private readonly float[] _bounds;
|
||||
|
||||
public CompositeCollider(List<Collider> colliders) {
|
||||
public CompositeCollider(List<Collider> colliders)
|
||||
{
|
||||
this.colliders = colliders;
|
||||
_bounds = bounds(colliders);
|
||||
}
|
||||
|
||||
public CompositeCollider(params Collider[] colliders) {
|
||||
public CompositeCollider(params Collider[] colliders)
|
||||
{
|
||||
this.colliders = colliders.ToList();
|
||||
_bounds = bounds(this.colliders);
|
||||
}
|
||||
|
||||
public float[] bounds() {
|
||||
public float[] bounds()
|
||||
{
|
||||
return _bounds;
|
||||
}
|
||||
|
||||
private static float[] bounds(List<Collider> colliders) {
|
||||
float[] bounds = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
|
||||
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity };
|
||||
foreach (Collider collider in colliders) {
|
||||
private static float[] bounds(List<Collider> colliders)
|
||||
{
|
||||
float[] bounds = new float[]
|
||||
{
|
||||
float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
|
||||
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity
|
||||
};
|
||||
foreach (Collider collider in colliders)
|
||||
{
|
||||
float[] b = collider.bounds();
|
||||
bounds[0] = Math.Min(bounds[0], b[0]);
|
||||
bounds[1] = Math.Min(bounds[1], b[1]);
|
||||
|
@ -56,14 +62,14 @@ public class CompositeCollider : Collider {
|
|||
bounds[4] = Math.Max(bounds[4], b[4]);
|
||||
bounds[5] = Math.Max(bounds[5], b[5]);
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
public void rasterize(Heightfield hf, Telemetry telemetry) {
|
||||
public void rasterize(Heightfield hf, Telemetry telemetry)
|
||||
{
|
||||
foreach (var c in colliders)
|
||||
c.rasterize(hf, telemetry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -21,30 +21,29 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Colliders
|
||||
{
|
||||
|
||||
|
||||
public class ConvexTrimeshCollider : AbstractCollider {
|
||||
|
||||
public class ConvexTrimeshCollider : AbstractCollider
|
||||
{
|
||||
private readonly float[] vertices;
|
||||
private readonly int[] triangles;
|
||||
|
||||
public ConvexTrimeshCollider(float[] vertices, int[] triangles, int area, float flagMergeThreshold) :
|
||||
base(area, flagMergeThreshold, TrimeshCollider.computeBounds(vertices)) {
|
||||
base(area, flagMergeThreshold, TrimeshCollider.computeBounds(vertices))
|
||||
{
|
||||
this.vertices = vertices;
|
||||
this.triangles = triangles;
|
||||
}
|
||||
|
||||
public ConvexTrimeshCollider(float[] vertices, int[] triangles, float[] bounds, int area, float flagMergeThreshold) :
|
||||
base(area, flagMergeThreshold, bounds) {
|
||||
base(area, flagMergeThreshold, bounds)
|
||||
{
|
||||
this.vertices = vertices;
|
||||
this.triangles = triangles;
|
||||
}
|
||||
|
||||
public void rasterize(Heightfield hf, Telemetry telemetry) {
|
||||
public void rasterize(Heightfield hf, Telemetry telemetry)
|
||||
{
|
||||
RecastFilledVolumeRasterization.rasterizeConvex(hf, vertices, triangles, area,
|
||||
(int)Math.Floor(flagMergeThreshold / hf.ch), telemetry);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -21,32 +21,34 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Colliders
|
||||
{
|
||||
|
||||
|
||||
public class CylinderCollider : AbstractCollider {
|
||||
|
||||
public class CylinderCollider : AbstractCollider
|
||||
{
|
||||
private readonly float[] start;
|
||||
private readonly float[] end;
|
||||
private readonly float radius;
|
||||
|
||||
public CylinderCollider(float[] start, float[] end, float radius, int area, float flagMergeThreshold) :
|
||||
base(area, flagMergeThreshold, bounds(start, end, radius)) {
|
||||
base(area, flagMergeThreshold, bounds(start, end, radius))
|
||||
{
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
public void rasterize(Heightfield hf, Telemetry telemetry) {
|
||||
public void rasterize(Heightfield hf, Telemetry telemetry)
|
||||
{
|
||||
RecastFilledVolumeRasterization.rasterizeCylinder(hf, start, end, radius, area, (int)Math.Floor(flagMergeThreshold / hf.ch),
|
||||
telemetry);
|
||||
}
|
||||
|
||||
private static float[] bounds(float[] start, float[] end, float radius) {
|
||||
return new float[] { Math.Min(start[0], end[0]) - radius, Math.Min(start[1], end[1]) - radius,
|
||||
private static float[] bounds(float[] start, float[] end, float radius)
|
||||
{
|
||||
return new float[]
|
||||
{
|
||||
Math.Min(start[0], end[0]) - radius, Math.Min(start[1], end[1]) - radius,
|
||||
Math.Min(start[2], end[2]) - radius, Math.Max(start[0], end[0]) + radius, Math.Max(start[1], end[1]) + radius,
|
||||
Math.Max(start[2], end[2]) + radius };
|
||||
Math.Max(start[2], end[2]) + radius
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -21,29 +21,31 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Colliders
|
||||
{
|
||||
|
||||
|
||||
public class SphereCollider : AbstractCollider {
|
||||
|
||||
public class SphereCollider : AbstractCollider
|
||||
{
|
||||
private readonly float[] center;
|
||||
private readonly float radius;
|
||||
|
||||
public SphereCollider(float[] center, float radius, int area, float flagMergeThreshold) :
|
||||
base(area, flagMergeThreshold, bounds(center, radius)) {
|
||||
base(area, flagMergeThreshold, bounds(center, radius))
|
||||
{
|
||||
this.center = center;
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
public void rasterize(Heightfield hf, Telemetry telemetry) {
|
||||
public void rasterize(Heightfield hf, Telemetry telemetry)
|
||||
{
|
||||
RecastFilledVolumeRasterization.rasterizeSphere(hf, center, radius, area, (int)Math.Floor(flagMergeThreshold / hf.ch),
|
||||
telemetry);
|
||||
}
|
||||
|
||||
private static float[] bounds(float[] center, float radius) {
|
||||
return new float[] { center[0] - radius, center[1] - radius, center[2] - radius, center[0] + radius, center[1] + radius,
|
||||
center[2] + radius };
|
||||
private static float[] bounds(float[] center, float radius)
|
||||
{
|
||||
return new float[]
|
||||
{
|
||||
center[0] - radius, center[1] - radius, center[2] - radius, center[0] + radius, center[1] + radius,
|
||||
center[2] + radius
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -21,28 +21,30 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Colliders
|
||||
{
|
||||
|
||||
|
||||
public class TrimeshCollider : AbstractCollider {
|
||||
|
||||
public class TrimeshCollider : AbstractCollider
|
||||
{
|
||||
private readonly float[] vertices;
|
||||
private readonly int[] triangles;
|
||||
|
||||
public TrimeshCollider(float[] vertices, int[] triangles, int area, float flagMergeThreshold) :
|
||||
base(area, flagMergeThreshold, computeBounds(vertices)) {
|
||||
base(area, flagMergeThreshold, computeBounds(vertices))
|
||||
{
|
||||
this.vertices = vertices;
|
||||
this.triangles = triangles;
|
||||
}
|
||||
|
||||
public TrimeshCollider(float[] vertices, int[] triangles, float[] bounds, int area, float flagMergeThreshold) :
|
||||
base(area, flagMergeThreshold, bounds) {
|
||||
base(area, flagMergeThreshold, bounds)
|
||||
{
|
||||
this.vertices = vertices;
|
||||
this.triangles = triangles;
|
||||
}
|
||||
|
||||
public static float[] computeBounds(float[] vertices) {
|
||||
public static float[] computeBounds(float[] vertices)
|
||||
{
|
||||
float[] bounds = new float[] { vertices[0], vertices[1], vertices[2], vertices[0], vertices[1], vertices[2] };
|
||||
for (int i = 3; i < vertices.Length; i += 3) {
|
||||
for (int i = 3; i < vertices.Length; i += 3)
|
||||
{
|
||||
bounds[0] = Math.Min(bounds[0], vertices[i]);
|
||||
bounds[1] = Math.Min(bounds[1], vertices[i + 1]);
|
||||
bounds[2] = Math.Min(bounds[2], vertices[i + 2]);
|
||||
|
@ -50,16 +52,17 @@ public class TrimeshCollider : AbstractCollider {
|
|||
bounds[4] = Math.Max(bounds[4], vertices[i + 1]);
|
||||
bounds[5] = Math.Max(bounds[5], vertices[i + 2]);
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
public void rasterize(Heightfield hf, Telemetry telemetry) {
|
||||
for (int i = 0; i < triangles.Length; i += 3) {
|
||||
public void rasterize(Heightfield hf, Telemetry telemetry)
|
||||
{
|
||||
for (int i = 0; i < triangles.Length; i += 3)
|
||||
{
|
||||
RecastRasterization.rasterizeTriangle(hf, vertices, triangles[i], triangles[i + 1], triangles[i + 2], area,
|
||||
(int)Math.Floor(flagMergeThreshold / hf.ch), telemetry);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -29,10 +29,8 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic
|
||||
{
|
||||
|
||||
|
||||
public class DynamicNavMesh {
|
||||
|
||||
public class DynamicNavMesh
|
||||
{
|
||||
public const int MAX_VERTS_PER_POLY = 6;
|
||||
public readonly DynamicNavMeshConfig config;
|
||||
private readonly RecastBuilder builder;
|
||||
|
@ -44,7 +42,8 @@ public class DynamicNavMesh {
|
|||
private NavMesh _navMesh;
|
||||
private bool dirty = true;
|
||||
|
||||
public DynamicNavMesh(VoxelFile voxelFile) {
|
||||
public DynamicNavMesh(VoxelFile voxelFile)
|
||||
{
|
||||
config = new DynamicNavMeshConfig(voxelFile.useTiles, voxelFile.tileSizeX, voxelFile.tileSizeZ, voxelFile.cellSize);
|
||||
config.walkableHeight = voxelFile.walkableHeight;
|
||||
config.walkableRadius = voxelFile.walkableRadius;
|
||||
|
@ -67,41 +66,50 @@ public class DynamicNavMesh {
|
|||
navMeshParams.tileHeight = voxelFile.cellSize * voxelFile.tileSizeZ;
|
||||
navMeshParams.maxTiles = voxelFile.tiles.Count;
|
||||
navMeshParams.maxPolys = 0x8000;
|
||||
foreach (var t in voxelFile.tiles) {
|
||||
foreach (var t in voxelFile.tiles)
|
||||
{
|
||||
_tiles.Add(lookupKey(t.tileX, t.tileZ), new DynamicTile(t));
|
||||
};
|
||||
}
|
||||
|
||||
;
|
||||
telemetry = new Telemetry();
|
||||
}
|
||||
|
||||
public NavMesh navMesh() {
|
||||
public NavMesh navMesh()
|
||||
{
|
||||
return _navMesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Voxel queries require checkpoints to be enabled in {@link DynamicNavMeshConfig}
|
||||
*/
|
||||
public VoxelQuery voxelQuery() {
|
||||
public VoxelQuery voxelQuery()
|
||||
{
|
||||
return new VoxelQuery(navMeshParams.orig, navMeshParams.tileWidth, navMeshParams.tileHeight, lookupHeightfield);
|
||||
}
|
||||
|
||||
private Heightfield lookupHeightfield(int x, int z) {
|
||||
private Heightfield lookupHeightfield(int x, int z)
|
||||
{
|
||||
return getTileAt(x, z)?.checkpoint.heightfield;
|
||||
}
|
||||
|
||||
public long addCollider(Collider collider) {
|
||||
public long addCollider(Collider collider)
|
||||
{
|
||||
long cid = currentColliderId.IncrementAndGet();
|
||||
updateQueue.Add(new AddColliderQueueItem(cid, collider, getTiles(collider.bounds())));
|
||||
return cid;
|
||||
}
|
||||
|
||||
public void removeCollider(long colliderId) {
|
||||
public void removeCollider(long colliderId)
|
||||
{
|
||||
updateQueue.Add(new RemoveColliderQueueItem(colliderId, getTilesByCollider(colliderId)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform full build of the nav mesh
|
||||
*/
|
||||
public void build() {
|
||||
public void build()
|
||||
{
|
||||
processQueue();
|
||||
rebuild(_tiles.Values);
|
||||
}
|
||||
|
@ -109,34 +117,42 @@ public class DynamicNavMesh {
|
|||
/**
|
||||
* Perform incremental update of the nav mesh
|
||||
*/
|
||||
public bool update() {
|
||||
public bool update()
|
||||
{
|
||||
return rebuild(processQueue());
|
||||
}
|
||||
|
||||
private bool rebuild(ICollection<DynamicTile> stream) {
|
||||
private bool rebuild(ICollection<DynamicTile> stream)
|
||||
{
|
||||
foreach (var dynamicTile in stream)
|
||||
rebuild(dynamicTile);
|
||||
return updateNavMesh();
|
||||
}
|
||||
|
||||
private HashSet<DynamicTile> processQueue() {
|
||||
private HashSet<DynamicTile> processQueue()
|
||||
{
|
||||
var items = consumeQueue();
|
||||
foreach (var item in items) {
|
||||
foreach (var item in items)
|
||||
{
|
||||
process(item);
|
||||
}
|
||||
|
||||
return items.SelectMany(i => i.affectedTiles()).ToHashSet();
|
||||
}
|
||||
|
||||
private List<UpdateQueueItem> consumeQueue() {
|
||||
private List<UpdateQueueItem> consumeQueue()
|
||||
{
|
||||
List<UpdateQueueItem> items = new List<UpdateQueueItem>();
|
||||
while (updateQueue.TryTake(out var item)) {
|
||||
while (updateQueue.TryTake(out var item))
|
||||
{
|
||||
items.Add(item);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private void process(UpdateQueueItem item) {
|
||||
private void process(UpdateQueueItem item)
|
||||
{
|
||||
foreach (var tile in item.affectedTiles())
|
||||
{
|
||||
item.process(tile);
|
||||
|
@ -146,7 +162,8 @@ public class DynamicNavMesh {
|
|||
/**
|
||||
* Perform full build concurrently using the given {@link ExecutorService}
|
||||
*/
|
||||
public Task<bool> build(TaskFactory executor) {
|
||||
public Task<bool> build(TaskFactory executor)
|
||||
{
|
||||
processQueue();
|
||||
return rebuild(_tiles.Values, executor);
|
||||
}
|
||||
|
@ -154,7 +171,8 @@ public class DynamicNavMesh {
|
|||
/**
|
||||
* Perform incremental update concurrently using the given {@link ExecutorService}
|
||||
*/
|
||||
public Task<bool> update(TaskFactory executor) {
|
||||
public Task<bool> update(TaskFactory executor)
|
||||
{
|
||||
return rebuild(processQueue(), executor);
|
||||
}
|
||||
|
||||
|
@ -164,38 +182,49 @@ public class DynamicNavMesh {
|
|||
return Task.WhenAll(tasks).ContinueWith(k => updateNavMesh());
|
||||
}
|
||||
|
||||
private ICollection<DynamicTile> getTiles(float[] bounds) {
|
||||
if (bounds == null) {
|
||||
private ICollection<DynamicTile> getTiles(float[] bounds)
|
||||
{
|
||||
if (bounds == null)
|
||||
{
|
||||
return _tiles.Values;
|
||||
}
|
||||
|
||||
int minx = (int)Math.Floor((bounds[0] - navMeshParams.orig[0]) / navMeshParams.tileWidth);
|
||||
int minz = (int)Math.Floor((bounds[2] - navMeshParams.orig[2]) / navMeshParams.tileHeight);
|
||||
int maxx = (int)Math.Floor((bounds[3] - navMeshParams.orig[0]) / navMeshParams.tileWidth);
|
||||
int maxz = (int)Math.Floor((bounds[5] - navMeshParams.orig[2]) / navMeshParams.tileHeight);
|
||||
List<DynamicTile> tiles = new List<DynamicTile>();
|
||||
for (int z = minz; z <= maxz; ++z) {
|
||||
for (int x = minx; x <= maxx; ++x) {
|
||||
for (int z = minz; z <= maxz; ++z)
|
||||
{
|
||||
for (int x = minx; x <= maxx; ++x)
|
||||
{
|
||||
DynamicTile tile = getTileAt(x, z);
|
||||
if (tile != null) {
|
||||
if (tile != null)
|
||||
{
|
||||
tiles.Add(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
private List<DynamicTile> getTilesByCollider(long cid) {
|
||||
private List<DynamicTile> getTilesByCollider(long cid)
|
||||
{
|
||||
return _tiles.Values.Where(t => t.containsCollider(cid)).ToList();
|
||||
}
|
||||
|
||||
private void rebuild(DynamicTile tile) {
|
||||
private void rebuild(DynamicTile tile)
|
||||
{
|
||||
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
|
||||
option.walkableHeight = config.walkableHeight;
|
||||
dirty = dirty | tile.build(builder, config, telemetry);
|
||||
}
|
||||
|
||||
private bool updateNavMesh() {
|
||||
if (dirty) {
|
||||
private bool updateNavMesh()
|
||||
{
|
||||
if (dirty)
|
||||
{
|
||||
NavMesh navMesh = new NavMesh(navMeshParams, MAX_VERTS_PER_POLY);
|
||||
foreach (var t in _tiles.Values)
|
||||
t.addTo(navMesh);
|
||||
|
@ -204,27 +233,30 @@ public class DynamicNavMesh {
|
|||
dirty = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private DynamicTile getTileAt(int x, int z) {
|
||||
private DynamicTile getTileAt(int x, int z)
|
||||
{
|
||||
return _tiles.TryGetValue(lookupKey(x, z), out var tile)
|
||||
? tile
|
||||
: null;
|
||||
}
|
||||
|
||||
private long lookupKey(long x, long z) {
|
||||
private long lookupKey(long x, long z)
|
||||
{
|
||||
return (z << 32) | x;
|
||||
}
|
||||
|
||||
public List<VoxelTile> voxelTiles() {
|
||||
public List<VoxelTile> voxelTiles()
|
||||
{
|
||||
return _tiles.Values.Select(t => t.voxelTile).ToList();
|
||||
}
|
||||
|
||||
public List<RecastBuilderResult> recastResults() {
|
||||
public List<RecastBuilderResult> recastResults()
|
||||
{
|
||||
return _tiles.Values.Select(t => t.recastResult).ToList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -20,10 +20,8 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic
|
||||
{
|
||||
|
||||
|
||||
public class DynamicNavMeshConfig {
|
||||
|
||||
public class DynamicNavMeshConfig
|
||||
{
|
||||
public readonly bool useTiles;
|
||||
public readonly int tileSizeX;
|
||||
public readonly int tileSizeZ;
|
||||
|
@ -48,13 +46,12 @@ public class DynamicNavMeshConfig {
|
|||
public bool enableCheckpoints = true;
|
||||
public bool keepIntermediateResults = false;
|
||||
|
||||
public DynamicNavMeshConfig(bool useTiles, int tileSizeX, int tileSizeZ, float cellSize) {
|
||||
public DynamicNavMeshConfig(bool useTiles, int tileSizeX, int tileSizeZ, float cellSize)
|
||||
{
|
||||
this.useTiles = useTiles;
|
||||
this.tileSizeX = tileSizeX;
|
||||
this.tileSizeZ = tileSizeZ;
|
||||
this.cellSize = cellSize;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -27,10 +27,8 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic
|
||||
{
|
||||
|
||||
|
||||
public class DynamicTile {
|
||||
|
||||
public class DynamicTile
|
||||
{
|
||||
public readonly VoxelTile voxelTile;
|
||||
public DynamicTileCheckpoint checkpoint;
|
||||
public RecastBuilderResult recastResult;
|
||||
|
@ -39,12 +37,15 @@ public class DynamicTile {
|
|||
private bool dirty = true;
|
||||
private long id;
|
||||
|
||||
public DynamicTile(VoxelTile voxelTile) {
|
||||
public DynamicTile(VoxelTile voxelTile)
|
||||
{
|
||||
this.voxelTile = voxelTile;
|
||||
}
|
||||
|
||||
public bool build(RecastBuilder builder, DynamicNavMeshConfig config, Telemetry telemetry) {
|
||||
if (dirty) {
|
||||
public bool build(RecastBuilder builder, DynamicNavMeshConfig config, Telemetry telemetry)
|
||||
{
|
||||
if (dirty)
|
||||
{
|
||||
Heightfield heightfield = buildHeightfield(config, telemetry);
|
||||
RecastBuilderResult r = buildRecast(builder, config, voxelTile, heightfield, telemetry);
|
||||
NavMeshDataCreateParams option = navMeshCreateParams(voxelTile.tileX, voxelTile.tileZ, voxelTile.cellSize,
|
||||
|
@ -52,26 +53,34 @@ public class DynamicTile {
|
|||
meshData = NavMeshBuilder.createNavMeshData(option);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Heightfield buildHeightfield(DynamicNavMeshConfig config, Telemetry telemetry) {
|
||||
private Heightfield buildHeightfield(DynamicNavMeshConfig config, Telemetry telemetry)
|
||||
{
|
||||
ICollection<long> rasterizedColliders = checkpoint != null ? checkpoint.colliders : ImmutableHashSet<long>.Empty;
|
||||
Heightfield heightfield = checkpoint != null ? checkpoint.heightfield : voxelTile.heightfield();
|
||||
foreach (var (cid, c) in colliders) {
|
||||
if (!rasterizedColliders.Contains(cid)) {
|
||||
foreach (var (cid, c) in colliders)
|
||||
{
|
||||
if (!rasterizedColliders.Contains(cid))
|
||||
{
|
||||
heightfield.bmax[1] = Math.Max(heightfield.bmax[1], c.bounds()[4] + heightfield.ch * 2);
|
||||
c.rasterize(heightfield, telemetry);
|
||||
}
|
||||
}
|
||||
if (config.enableCheckpoints) {
|
||||
|
||||
if (config.enableCheckpoints)
|
||||
{
|
||||
checkpoint = new DynamicTileCheckpoint(heightfield, colliders.Keys.ToHashSet());
|
||||
}
|
||||
|
||||
return heightfield;
|
||||
}
|
||||
|
||||
private RecastBuilderResult buildRecast(RecastBuilder builder, DynamicNavMeshConfig config, VoxelTile vt,
|
||||
Heightfield heightfield, Telemetry telemetry) {
|
||||
Heightfield heightfield, Telemetry telemetry)
|
||||
{
|
||||
RecastConfig rcConfig = new RecastConfig(config.useTiles, config.tileSizeX, config.tileSizeZ, vt.borderSize,
|
||||
config.partitionType, vt.cellSize, vt.cellHeight, config.walkableSlopeAngle, true, true, true,
|
||||
config.walkableHeight, config.walkableRadius, config.walkableClimb, config.minRegionArea, config.regionMergeArea,
|
||||
|
@ -79,36 +88,45 @@ public class DynamicTile {
|
|||
Math.Min(DynamicNavMesh.MAX_VERTS_PER_POLY, config.vertsPerPoly), true, config.detailSampleDistance,
|
||||
config.detailSampleMaxError, null);
|
||||
RecastBuilderResult r = builder.build(vt.tileX, vt.tileZ, null, rcConfig, heightfield, telemetry);
|
||||
if (config.keepIntermediateResults) {
|
||||
if (config.keepIntermediateResults)
|
||||
{
|
||||
recastResult = r;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public void addCollider(long cid, Collider collider) {
|
||||
public void addCollider(long cid, Collider collider)
|
||||
{
|
||||
colliders[cid] = collider;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
public bool containsCollider(long cid) {
|
||||
public bool containsCollider(long cid)
|
||||
{
|
||||
return colliders.ContainsKey(cid);
|
||||
}
|
||||
|
||||
public void removeCollider(long colliderId) {
|
||||
if (colliders.TryRemove(colliderId, out var collider)) {
|
||||
public void removeCollider(long colliderId)
|
||||
{
|
||||
if (colliders.TryRemove(colliderId, out var collider))
|
||||
{
|
||||
dirty = true;
|
||||
checkpoint = null;
|
||||
}
|
||||
}
|
||||
|
||||
private NavMeshDataCreateParams navMeshCreateParams(int tilex, int tileZ, float cellSize, float cellHeight,
|
||||
DynamicNavMeshConfig config, RecastBuilderResult rcResult) {
|
||||
DynamicNavMeshConfig config, RecastBuilderResult rcResult)
|
||||
{
|
||||
PolyMesh m_pmesh = rcResult.getMesh();
|
||||
PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
|
||||
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
|
||||
for (int i = 0; i < m_pmesh.npolys; ++i) {
|
||||
for (int i = 0; i < m_pmesh.npolys; ++i)
|
||||
{
|
||||
m_pmesh.flags[i] = 1;
|
||||
}
|
||||
|
||||
option.tileX = tilex;
|
||||
option.tileZ = tileZ;
|
||||
option.verts = m_pmesh.verts;
|
||||
|
@ -118,13 +136,15 @@ public class DynamicTile {
|
|||
option.polyFlags = m_pmesh.flags;
|
||||
option.polyCount = m_pmesh.npolys;
|
||||
option.nvp = m_pmesh.nvp;
|
||||
if (m_dmesh != null) {
|
||||
if (m_dmesh != null)
|
||||
{
|
||||
option.detailMeshes = m_dmesh.meshes;
|
||||
option.detailVerts = m_dmesh.verts;
|
||||
option.detailVertsCount = m_dmesh.nverts;
|
||||
option.detailTris = m_dmesh.tris;
|
||||
option.detailTriCount = m_dmesh.ntris;
|
||||
}
|
||||
|
||||
option.walkableHeight = config.walkableHeight;
|
||||
option.walkableRadius = config.walkableRadius;
|
||||
option.walkableClimb = config.walkableClimb;
|
||||
|
@ -144,14 +164,17 @@ public class DynamicTile {
|
|||
return option;
|
||||
}
|
||||
|
||||
public void addTo(NavMesh navMesh) {
|
||||
if (meshData != null) {
|
||||
public void addTo(NavMesh navMesh)
|
||||
{
|
||||
if (meshData != null)
|
||||
{
|
||||
id = navMesh.addTile(meshData, 0, 0);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
navMesh.removeTile(id);
|
||||
id = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -18,48 +18,53 @@ freely, subject to the following restrictions:
|
|||
|
||||
using System.Collections.Generic;
|
||||
using DotRecast.Recast;
|
||||
|
||||
using static DotRecast.Detour.DetourCommon;
|
||||
|
||||
namespace DotRecast.Detour.Dynamic
|
||||
{
|
||||
|
||||
|
||||
public class DynamicTileCheckpoint {
|
||||
|
||||
public class DynamicTileCheckpoint
|
||||
{
|
||||
public readonly Heightfield heightfield;
|
||||
public readonly ISet<long> colliders;
|
||||
|
||||
public DynamicTileCheckpoint(Heightfield heightfield, ISet<long> colliders) {
|
||||
public DynamicTileCheckpoint(Heightfield heightfield, ISet<long> colliders)
|
||||
{
|
||||
this.colliders = colliders;
|
||||
this.heightfield = clone(heightfield);
|
||||
}
|
||||
|
||||
private Heightfield clone(Heightfield source) {
|
||||
private Heightfield clone(Heightfield source)
|
||||
{
|
||||
Heightfield clone = new Heightfield(source.width, source.height, vCopy(source.bmin), vCopy(source.bmax), source.cs,
|
||||
source.ch, source.borderSize);
|
||||
for (int z = 0, pz = 0; z < source.height; z++, pz += source.width) {
|
||||
for (int x = 0; x < source.width; x++) {
|
||||
for (int z = 0, pz = 0; z < source.height; z++, pz += source.width)
|
||||
{
|
||||
for (int x = 0; x < source.width; x++)
|
||||
{
|
||||
Span span = source.spans[pz + x];
|
||||
Span prevCopy = null;
|
||||
while (span != null) {
|
||||
while (span != null)
|
||||
{
|
||||
Span copy = new Span();
|
||||
copy.smin = span.smin;
|
||||
copy.smax = span.smax;
|
||||
copy.area = span.area;
|
||||
if (prevCopy == null) {
|
||||
if (prevCopy == null)
|
||||
{
|
||||
clone.spans[pz + x] = copy;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
prevCopy.next = copy;
|
||||
}
|
||||
|
||||
prevCopy = copy;
|
||||
span = span.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -20,62 +20,74 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Io
|
||||
{
|
||||
|
||||
|
||||
public static class ByteUtils {
|
||||
|
||||
public static int getInt(byte[] data, int position, ByteOrder order) {
|
||||
public static class ByteUtils
|
||||
{
|
||||
public static int getInt(byte[] data, int position, ByteOrder order)
|
||||
{
|
||||
return order == ByteOrder.BIG_ENDIAN ? getIntBE(data, position) : getIntLE(data, position);
|
||||
}
|
||||
|
||||
public static int getIntBE(byte[] data, int position) {
|
||||
public static int getIntBE(byte[] data, int position)
|
||||
{
|
||||
return ((data[position] & 0xff) << 24) | ((data[position + 1] & 0xff) << 16) | ((data[position + 2] & 0xff) << 8)
|
||||
| (data[position + 3] & 0xff);
|
||||
}
|
||||
|
||||
public static int getIntLE(byte[] data, int position) {
|
||||
public static int getIntLE(byte[] data, int position)
|
||||
{
|
||||
return ((data[position + 3] & 0xff) << 24) | ((data[position + 2] & 0xff) << 16) | ((data[position + 1] & 0xff) << 8)
|
||||
| (data[position] & 0xff);
|
||||
}
|
||||
|
||||
public static int getShort(byte[] data, int position, ByteOrder order) {
|
||||
public static int getShort(byte[] data, int position, ByteOrder order)
|
||||
{
|
||||
return order == ByteOrder.BIG_ENDIAN ? getShortBE(data, position) : getShortLE(data, position);
|
||||
}
|
||||
|
||||
public static int getShortBE(byte[] data, int position) {
|
||||
public static int getShortBE(byte[] data, int position)
|
||||
{
|
||||
return ((data[position] & 0xff) << 8) | (data[position + 1] & 0xff);
|
||||
}
|
||||
|
||||
public static int getShortLE(byte[] data, int position) {
|
||||
public static int getShortLE(byte[] data, int position)
|
||||
{
|
||||
return ((data[position + 1] & 0xff) << 8) | (data[position] & 0xff);
|
||||
}
|
||||
|
||||
public static int putInt(int value, byte[] data, int position, ByteOrder order) {
|
||||
if (order == ByteOrder.BIG_ENDIAN) {
|
||||
public static int putInt(int value, byte[] data, int position, ByteOrder order)
|
||||
{
|
||||
if (order == ByteOrder.BIG_ENDIAN)
|
||||
{
|
||||
data[position] = (byte)((uint)value >> 24);
|
||||
data[position + 1] = (byte)((uint)value >> 16);
|
||||
data[position + 2] = (byte)((uint)value >> 8);
|
||||
data[position + 3] = (byte)(value & 0xFF);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
data[position] = (byte)(value & 0xFF);
|
||||
data[position + 1] = (byte)((uint)value >> 8);
|
||||
data[position + 2] = (byte)((uint)value >> 16);
|
||||
data[position + 3] = (byte)((uint)value >> 24);
|
||||
}
|
||||
|
||||
return position + 4;
|
||||
}
|
||||
|
||||
public static int putShort(int value, byte[] data, int position, ByteOrder order) {
|
||||
if (order == ByteOrder.BIG_ENDIAN) {
|
||||
public static int putShort(int value, byte[] data, int position, ByteOrder order)
|
||||
{
|
||||
if (order == ByteOrder.BIG_ENDIAN)
|
||||
{
|
||||
data[position] = (byte)((uint)value >> 8);
|
||||
data[position + 1] = (byte)(value & 0xFF);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
data[position] = (byte)(value & 0xFF);
|
||||
data[position + 1] = (byte)((uint)value >> 8);
|
||||
}
|
||||
|
||||
return position + 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,23 +22,21 @@ using K4os.Compression.LZ4;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Io
|
||||
{
|
||||
|
||||
|
||||
public class LZ4VoxelTileCompressor {
|
||||
|
||||
public byte[] decompress(byte[] data) {
|
||||
public class LZ4VoxelTileCompressor
|
||||
{
|
||||
public byte[] decompress(byte[] data)
|
||||
{
|
||||
int compressedSize = ByteUtils.getIntBE(data, 0);
|
||||
return LZ4Pickler.Unpickle(data.AsSpan(4, compressedSize));
|
||||
}
|
||||
|
||||
public byte[] compress(byte[] data) {
|
||||
public byte[] compress(byte[] data)
|
||||
{
|
||||
byte[] compressed = LZ4Pickler.Pickle(data, LZ4Level.L12_MAX);
|
||||
byte[] result = new byte[4 + compressed.Length];
|
||||
ByteUtils.putInt(compressed.Length, result, 0, ByteOrder.BIG_ENDIAN);
|
||||
Array.Copy(compressed, 0, result, 4, compressed.Length);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -23,10 +23,8 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Io
|
||||
{
|
||||
|
||||
|
||||
public class VoxelFile {
|
||||
|
||||
public class VoxelFile
|
||||
{
|
||||
public static readonly ByteOrder PREFERRED_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
|
||||
public const int MAGIC = 'V' << 24 | 'O' << 16 | 'X' << 8 | 'L';
|
||||
public const int VERSION_EXPORTER_MASK = 0xF000;
|
||||
|
@ -58,20 +56,23 @@ public class VoxelFile {
|
|||
public float[] bounds = new float[6];
|
||||
public readonly List<VoxelTile> tiles = new List<VoxelTile>();
|
||||
|
||||
public void addTile(VoxelTile tile) {
|
||||
public void addTile(VoxelTile tile)
|
||||
{
|
||||
tiles.Add(tile);
|
||||
}
|
||||
|
||||
public RecastConfig getConfig(VoxelTile tile, PartitionType partitionType, int maxPolyVerts, int regionMergeSize,
|
||||
bool filterLowHangingObstacles, bool filterLedgeSpans, bool filterWalkableLowHeightSpans,
|
||||
AreaModification walkbableAreaMod, bool buildMeshDetail, float detailSampleDist, float detailSampleMaxError) {
|
||||
AreaModification walkbableAreaMod, bool buildMeshDetail, float detailSampleDist, float detailSampleMaxError)
|
||||
{
|
||||
return new RecastConfig(useTiles, tileSizeX, tileSizeZ, tile.borderSize, partitionType, cellSize, tile.cellHeight,
|
||||
walkableSlopeAngle, filterLowHangingObstacles, filterLedgeSpans, filterWalkableLowHeightSpans, walkableHeight,
|
||||
walkableRadius, walkableClimb, minRegionArea, regionMergeArea, maxEdgeLen, maxSimplificationError, maxPolyVerts,
|
||||
buildMeshDetail, detailSampleDist, detailSampleMaxError, walkbableAreaMod);
|
||||
}
|
||||
|
||||
public static VoxelFile from(RecastConfig config, List<RecastBuilderResult> results) {
|
||||
public static VoxelFile from(RecastConfig config, List<RecastBuilderResult> results)
|
||||
{
|
||||
VoxelFile f = new VoxelFile();
|
||||
f.version = 1;
|
||||
f.partitionType = config.partitionType;
|
||||
|
@ -94,9 +95,13 @@ public class VoxelFile {
|
|||
f.useTiles = config.useTiles;
|
||||
f.tileSizeX = config.tileSizeX;
|
||||
f.tileSizeZ = config.tileSizeZ;
|
||||
f.bounds = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
|
||||
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity };
|
||||
foreach (RecastBuilderResult r in results) {
|
||||
f.bounds = new float[]
|
||||
{
|
||||
float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
|
||||
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity
|
||||
};
|
||||
foreach (RecastBuilderResult r in results)
|
||||
{
|
||||
f.tiles.Add(new VoxelTile(r.tileX, r.tileZ, r.getSolidHeightfield()));
|
||||
f.bounds[0] = Math.Min(f.bounds[0], r.getSolidHeightfield().bmin[0]);
|
||||
f.bounds[1] = Math.Min(f.bounds[1], r.getSolidHeightfield().bmin[1]);
|
||||
|
@ -105,10 +110,12 @@ public class VoxelFile {
|
|||
f.bounds[4] = Math.Max(f.bounds[4], r.getSolidHeightfield().bmax[1]);
|
||||
f.bounds[5] = Math.Max(f.bounds[5], r.getSolidHeightfield().bmax[2]);
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
public static VoxelFile from(DynamicNavMesh mesh) {
|
||||
public static VoxelFile from(DynamicNavMesh mesh)
|
||||
{
|
||||
VoxelFile f = new VoxelFile();
|
||||
f.version = 1;
|
||||
DynamicNavMeshConfig config = mesh.config;
|
||||
|
@ -132,9 +139,13 @@ public class VoxelFile {
|
|||
f.useTiles = config.useTiles;
|
||||
f.tileSizeX = config.tileSizeX;
|
||||
f.tileSizeZ = config.tileSizeZ;
|
||||
f.bounds = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
|
||||
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity };
|
||||
foreach (VoxelTile vt in mesh.voxelTiles()) {
|
||||
f.bounds = new float[]
|
||||
{
|
||||
float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
|
||||
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity
|
||||
};
|
||||
foreach (VoxelTile vt in mesh.voxelTiles())
|
||||
{
|
||||
Heightfield heightfield = vt.heightfield();
|
||||
f.tiles.Add(new VoxelTile(vt.tileX, vt.tileZ, heightfield));
|
||||
f.bounds[0] = Math.Min(f.bounds[0], vt.boundsMin[0]);
|
||||
|
@ -144,9 +155,8 @@ public class VoxelFile {
|
|||
f.bounds[4] = Math.Max(f.bounds[4], vt.boundsMax[1]);
|
||||
f.bounds[5] = Math.Max(f.bounds[5], vt.boundsMax[2]);
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,23 +22,26 @@ using DotRecast.Detour.Io;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Io
|
||||
{
|
||||
|
||||
|
||||
public class VoxelFileReader {
|
||||
|
||||
public class VoxelFileReader
|
||||
{
|
||||
private readonly LZ4VoxelTileCompressor compressor = new LZ4VoxelTileCompressor();
|
||||
|
||||
public VoxelFile read(BinaryReader stream) {
|
||||
public VoxelFile read(BinaryReader stream)
|
||||
{
|
||||
ByteBuffer buf = IOUtils.toByteBuffer(stream);
|
||||
VoxelFile file = new VoxelFile();
|
||||
int magic = buf.getInt();
|
||||
if (magic != VoxelFile.MAGIC) {
|
||||
if (magic != VoxelFile.MAGIC)
|
||||
{
|
||||
magic = IOUtils.swapEndianness(magic);
|
||||
if (magic != VoxelFile.MAGIC) {
|
||||
if (magic != VoxelFile.MAGIC)
|
||||
{
|
||||
throw new IOException("Invalid magic");
|
||||
}
|
||||
|
||||
buf.order(buf.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
|
||||
file.version = buf.getInt();
|
||||
bool isExportedFromAstar = (file.version & VoxelFile.VERSION_EXPORTER_MASK) == 0;
|
||||
bool compression = (file.version & VoxelFile.VERSION_COMPRESSION_MASK) == VoxelFile.VERSION_COMPRESSION_LZ4;
|
||||
|
@ -50,19 +53,23 @@ public class VoxelFileReader {
|
|||
file.maxSimplificationError = buf.getFloat();
|
||||
file.maxEdgeLen = buf.getFloat();
|
||||
file.minRegionArea = (int)buf.getFloat();
|
||||
if (!isExportedFromAstar) {
|
||||
if (!isExportedFromAstar)
|
||||
{
|
||||
file.regionMergeArea = buf.getFloat();
|
||||
file.vertsPerPoly = buf.getInt();
|
||||
file.buildMeshDetail = buf.get() != 0;
|
||||
file.detailSampleDistance = buf.getFloat();
|
||||
file.detailSampleMaxError = buf.getFloat();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
file.regionMergeArea = 6 * file.minRegionArea;
|
||||
file.vertsPerPoly = 6;
|
||||
file.buildMeshDetail = true;
|
||||
file.detailSampleDistance = file.maxEdgeLen * 0.5f;
|
||||
file.detailSampleMaxError = file.maxSimplificationError * 0.8f;
|
||||
}
|
||||
|
||||
file.useTiles = buf.get() != 0;
|
||||
file.tileSizeX = buf.getInt();
|
||||
file.tileSizeZ = buf.getInt();
|
||||
|
@ -75,7 +82,8 @@ public class VoxelFileReader {
|
|||
file.bounds[3] = buf.getFloat();
|
||||
file.bounds[4] = buf.getFloat();
|
||||
file.bounds[5] = buf.getFloat();
|
||||
if (isExportedFromAstar) {
|
||||
if (isExportedFromAstar)
|
||||
{
|
||||
// bounds are saved as center + size
|
||||
file.bounds[0] -= 0.5f * file.bounds[3];
|
||||
file.bounds[1] -= 0.5f * file.bounds[4];
|
||||
|
@ -84,8 +92,10 @@ public class VoxelFileReader {
|
|||
file.bounds[4] += file.bounds[1];
|
||||
file.bounds[5] += file.bounds[2];
|
||||
}
|
||||
|
||||
int tileCount = buf.getInt();
|
||||
for (int tile = 0; tile < tileCount; tile++) {
|
||||
for (int tile = 0; tile < tileCount; tile++)
|
||||
{
|
||||
int tileX = buf.getInt();
|
||||
int tileZ = buf.getInt();
|
||||
int width = buf.getInt();
|
||||
|
@ -99,7 +109,8 @@ public class VoxelFileReader {
|
|||
boundsMax[0] = buf.getFloat();
|
||||
boundsMax[1] = buf.getFloat();
|
||||
boundsMax[2] = buf.getFloat();
|
||||
if (isExportedFromAstar) {
|
||||
if (isExportedFromAstar)
|
||||
{
|
||||
// bounds are local
|
||||
boundsMin[0] += file.bounds[0];
|
||||
boundsMin[1] += file.bounds[1];
|
||||
|
@ -108,22 +119,24 @@ public class VoxelFileReader {
|
|||
boundsMax[1] += file.bounds[1];
|
||||
boundsMax[2] += file.bounds[2];
|
||||
}
|
||||
|
||||
float cellSize = buf.getFloat();
|
||||
float cellHeight = buf.getFloat();
|
||||
int voxelSize = buf.getInt();
|
||||
int position = buf.position();
|
||||
byte[] bytes = buf.ReadBytes(voxelSize).ToArray();
|
||||
if (compression) {
|
||||
if (compression)
|
||||
{
|
||||
bytes = compressor.decompress(bytes);
|
||||
}
|
||||
|
||||
ByteBuffer data = new ByteBuffer(bytes);
|
||||
data.order(buf.order());
|
||||
file.addTile(new VoxelTile(tileX, tileZ, width, depth, boundsMin, boundsMax, cellSize, cellHeight, borderSize, data));
|
||||
buf.position(position + voxelSize);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,17 +22,17 @@ using DotRecast.Detour.Io;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Io
|
||||
{
|
||||
|
||||
|
||||
public class VoxelFileWriter : DetourWriter {
|
||||
|
||||
public class VoxelFileWriter : DetourWriter
|
||||
{
|
||||
private readonly LZ4VoxelTileCompressor compressor = new LZ4VoxelTileCompressor();
|
||||
|
||||
public void write(BinaryWriter stream, VoxelFile f, bool compression) {
|
||||
public void write(BinaryWriter stream, VoxelFile f, bool compression)
|
||||
{
|
||||
write(stream, f, VoxelFile.PREFERRED_BYTE_ORDER, compression);
|
||||
}
|
||||
|
||||
public void write(BinaryWriter stream, VoxelFile f, ByteOrder byteOrder, bool compression) {
|
||||
public void write(BinaryWriter stream, VoxelFile f, ByteOrder byteOrder, bool compression)
|
||||
{
|
||||
write(stream, VoxelFile.MAGIC, byteOrder);
|
||||
write(stream, VoxelFile.VERSION_EXPORTER_RECAST4J | (compression ? VoxelFile.VERSION_COMPRESSION_LZ4 : 0), byteOrder);
|
||||
write(stream, f.walkableRadius, byteOrder);
|
||||
|
@ -61,12 +61,14 @@ public class VoxelFileWriter : DetourWriter {
|
|||
write(stream, f.bounds[4], byteOrder);
|
||||
write(stream, f.bounds[5], byteOrder);
|
||||
write(stream, f.tiles.Count, byteOrder);
|
||||
foreach (VoxelTile t in f.tiles) {
|
||||
foreach (VoxelTile t in f.tiles)
|
||||
{
|
||||
writeTile(stream, t, byteOrder, compression);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeTile(BinaryWriter stream, VoxelTile tile, ByteOrder byteOrder, bool compression) {
|
||||
public void writeTile(BinaryWriter stream, VoxelTile tile, ByteOrder byteOrder, bool compression)
|
||||
{
|
||||
write(stream, tile.tileX, byteOrder);
|
||||
write(stream, tile.tileZ, byteOrder);
|
||||
write(stream, tile.width, byteOrder);
|
||||
|
@ -81,13 +83,13 @@ public class VoxelFileWriter : DetourWriter {
|
|||
write(stream, tile.cellSize, byteOrder);
|
||||
write(stream, tile.cellHeight, byteOrder);
|
||||
byte[] bytes = tile.spanData;
|
||||
if (compression) {
|
||||
if (compression)
|
||||
{
|
||||
bytes = compressor.compress(bytes);
|
||||
}
|
||||
|
||||
write(stream, bytes.Length, byteOrder);
|
||||
stream.Write(bytes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -21,11 +21,8 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Io
|
||||
{
|
||||
|
||||
|
||||
|
||||
public class VoxelTile {
|
||||
|
||||
public class VoxelTile
|
||||
{
|
||||
private const int SERIALIZED_SPAN_COUNT_BYTES = 2;
|
||||
private const int SERIALIZED_SPAN_BYTES = 12;
|
||||
public readonly int tileX;
|
||||
|
@ -40,7 +37,8 @@ public class VoxelTile {
|
|||
public readonly byte[] spanData;
|
||||
|
||||
public VoxelTile(int tileX, int tileZ, int width, int depth, float[] boundsMin, float[] boundsMax, float cellSize,
|
||||
float cellHeight, int borderSize, ByteBuffer buffer) {
|
||||
float cellHeight, int borderSize, ByteBuffer buffer)
|
||||
{
|
||||
this.tileX = tileX;
|
||||
this.tileZ = tileZ;
|
||||
this.width = width;
|
||||
|
@ -53,7 +51,8 @@ public class VoxelTile {
|
|||
spanData = toByteArray(buffer, width, depth, VoxelFile.PREFERRED_BYTE_ORDER);
|
||||
}
|
||||
|
||||
public VoxelTile(int tileX, int tileZ, Heightfield heightfield) {
|
||||
public VoxelTile(int tileX, int tileZ, Heightfield heightfield)
|
||||
{
|
||||
this.tileX = tileX;
|
||||
this.tileZ = tileZ;
|
||||
width = heightfield.width;
|
||||
|
@ -66,19 +65,24 @@ public class VoxelTile {
|
|||
spanData = serializeSpans(heightfield, VoxelFile.PREFERRED_BYTE_ORDER);
|
||||
}
|
||||
|
||||
public Heightfield heightfield() {
|
||||
public Heightfield heightfield()
|
||||
{
|
||||
return VoxelFile.PREFERRED_BYTE_ORDER == ByteOrder.BIG_ENDIAN ? heightfieldBE() : heightfieldLE();
|
||||
}
|
||||
|
||||
private Heightfield heightfieldBE() {
|
||||
private Heightfield heightfieldBE()
|
||||
{
|
||||
Heightfield hf = new Heightfield(width, depth, boundsMin, boundsMax, cellSize, cellHeight, borderSize);
|
||||
int position = 0;
|
||||
for (int z = 0, pz = 0; z < depth; z++, pz += width) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int z = 0, pz = 0; z < depth; z++, pz += width)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
Span prev = null;
|
||||
int spanCount = ByteUtils.getShortBE(spanData, position);
|
||||
position += 2;
|
||||
for (int s = 0; s < spanCount; s++) {
|
||||
for (int s = 0; s < spanCount; s++)
|
||||
{
|
||||
Span span = new Span();
|
||||
span.smin = ByteUtils.getIntBE(spanData, position);
|
||||
position += 4;
|
||||
|
@ -86,27 +90,36 @@ public class VoxelTile {
|
|||
position += 4;
|
||||
span.area = ByteUtils.getIntBE(spanData, position);
|
||||
position += 4;
|
||||
if (prev == null) {
|
||||
if (prev == null)
|
||||
{
|
||||
hf.spans[pz + x] = span;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = span;
|
||||
}
|
||||
|
||||
prev = span;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hf;
|
||||
}
|
||||
|
||||
private Heightfield heightfieldLE() {
|
||||
private Heightfield heightfieldLE()
|
||||
{
|
||||
Heightfield hf = new Heightfield(width, depth, boundsMin, boundsMax, cellSize, cellHeight, borderSize);
|
||||
int position = 0;
|
||||
for (int z = 0, pz = 0; z < depth; z++, pz += width) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int z = 0, pz = 0; z < depth; z++, pz += width)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
Span prev = null;
|
||||
int spanCount = ByteUtils.getShortLE(spanData, position);
|
||||
position += 2;
|
||||
for (int s = 0; s < spanCount; s++) {
|
||||
for (int s = 0; s < spanCount; s++)
|
||||
{
|
||||
Span span = new Span();
|
||||
span.smin = ByteUtils.getIntLE(spanData, position);
|
||||
position += 4;
|
||||
|
@ -114,38 +127,51 @@ public class VoxelTile {
|
|||
position += 4;
|
||||
span.area = ByteUtils.getIntLE(spanData, position);
|
||||
position += 4;
|
||||
if (prev == null) {
|
||||
if (prev == null)
|
||||
{
|
||||
hf.spans[pz + x] = span;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
prev.next = span;
|
||||
}
|
||||
|
||||
prev = span;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hf;
|
||||
}
|
||||
|
||||
private byte[] serializeSpans(Heightfield heightfield, ByteOrder order) {
|
||||
private byte[] serializeSpans(Heightfield heightfield, ByteOrder order)
|
||||
{
|
||||
int[] counts = new int[heightfield.width * heightfield.height];
|
||||
int totalCount = 0;
|
||||
for (int z = 0, pz = 0; z < heightfield.height; z++, pz += heightfield.width) {
|
||||
for (int x = 0; x < heightfield.width; x++) {
|
||||
for (int z = 0, pz = 0; z < heightfield.height; z++, pz += heightfield.width)
|
||||
{
|
||||
for (int x = 0; x < heightfield.width; x++)
|
||||
{
|
||||
Span span = heightfield.spans[pz + x];
|
||||
while (span != null) {
|
||||
while (span != null)
|
||||
{
|
||||
counts[pz + x]++;
|
||||
totalCount++;
|
||||
span = span.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
byte[] data = new byte[totalCount * SERIALIZED_SPAN_BYTES + counts.Length * SERIALIZED_SPAN_COUNT_BYTES];
|
||||
int position = 0;
|
||||
for (int z = 0, pz = 0; z < heightfield.height; z++, pz += heightfield.width) {
|
||||
for (int x = 0; x < heightfield.width; x++) {
|
||||
for (int z = 0, pz = 0; z < heightfield.height; z++, pz += heightfield.width)
|
||||
{
|
||||
for (int x = 0; x < heightfield.width; x++)
|
||||
{
|
||||
position = ByteUtils.putShort(counts[pz + x], data, position, order);
|
||||
Span span = heightfield.spans[pz + x];
|
||||
while (span != null) {
|
||||
while (span != null)
|
||||
{
|
||||
position = ByteUtils.putInt(span.smin, data, position, order);
|
||||
position = ByteUtils.putInt(span.smax, data, position, order);
|
||||
position = ByteUtils.putInt(span.area, data, position, order);
|
||||
|
@ -153,22 +179,29 @@ public class VoxelTile {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private byte[] toByteArray(ByteBuffer buf, int width, int height, ByteOrder order) {
|
||||
private byte[] toByteArray(ByteBuffer buf, int width, int height, ByteOrder order)
|
||||
{
|
||||
byte[] data;
|
||||
if (buf.order() == order) {
|
||||
if (buf.order() == order)
|
||||
{
|
||||
data = buf.ReadBytes(buf.limit()).ToArray();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
data = new byte[buf.limit()];
|
||||
int l = width * height;
|
||||
int position = 0;
|
||||
for (int i = 0; i < l; i++) {
|
||||
for (int i = 0; i < l; i++)
|
||||
{
|
||||
int count = buf.getShort();
|
||||
ByteUtils.putShort(count, data, position, order);
|
||||
position += 2;
|
||||
for (int j = 0; j < count; j++) {
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
ByteUtils.putInt(buf.getInt(), data, position, order);
|
||||
position += 4;
|
||||
ByteUtils.putInt(buf.getInt(), data, position, order);
|
||||
|
@ -178,8 +211,8 @@ public class VoxelTile {
|
|||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,26 +20,25 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic
|
||||
{
|
||||
|
||||
|
||||
public class RemoveColliderQueueItem : UpdateQueueItem {
|
||||
|
||||
public class RemoveColliderQueueItem : UpdateQueueItem
|
||||
{
|
||||
private readonly long colliderId;
|
||||
private readonly ICollection<DynamicTile> _affectedTiles;
|
||||
|
||||
public RemoveColliderQueueItem(long colliderId, ICollection<DynamicTile> affectedTiles) {
|
||||
public RemoveColliderQueueItem(long colliderId, ICollection<DynamicTile> affectedTiles)
|
||||
{
|
||||
this.colliderId = colliderId;
|
||||
this._affectedTiles = affectedTiles;
|
||||
}
|
||||
|
||||
public ICollection<DynamicTile> affectedTiles() {
|
||||
public ICollection<DynamicTile> affectedTiles()
|
||||
{
|
||||
return _affectedTiles;
|
||||
}
|
||||
|
||||
public void process(DynamicTile tile) {
|
||||
public void process(DynamicTile tile)
|
||||
{
|
||||
tile.removeCollider(colliderId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -20,14 +20,10 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic
|
||||
{
|
||||
|
||||
|
||||
public interface UpdateQueueItem {
|
||||
|
||||
public interface UpdateQueueItem
|
||||
{
|
||||
ICollection<DynamicTile> affectedTiles();
|
||||
|
||||
void process(DynamicTile tile);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -21,22 +21,20 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic
|
||||
{
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Voxel raycast based on the algorithm described in
|
||||
*
|
||||
* "A Fast Voxel Traversal Algorithm for Ray Tracing" by John Amanatides and Andrew Woo
|
||||
*/
|
||||
public class VoxelQuery {
|
||||
|
||||
public class VoxelQuery
|
||||
{
|
||||
private readonly float[] origin;
|
||||
private readonly float tileWidth;
|
||||
private readonly float tileDepth;
|
||||
private readonly Func<int, int, Heightfield> heightfieldProvider;
|
||||
|
||||
public VoxelQuery(float[] origin, float tileWidth, float tileDepth, Func<int, int, Heightfield> heightfieldProvider) {
|
||||
public VoxelQuery(float[] origin, float tileWidth, float tileDepth, Func<int, int, Heightfield> heightfieldProvider)
|
||||
{
|
||||
this.origin = origin;
|
||||
this.tileWidth = tileWidth;
|
||||
this.tileDepth = tileDepth;
|
||||
|
@ -48,11 +46,13 @@ public class VoxelQuery {
|
|||
*
|
||||
* @return Optional with hit parameter (t) or empty if no hit found
|
||||
*/
|
||||
public float? raycast(float[] start, float[] end) {
|
||||
public float? raycast(float[] start, float[] end)
|
||||
{
|
||||
return traverseTiles(start, end);
|
||||
}
|
||||
|
||||
private float? traverseTiles(float[] start, float[] end) {
|
||||
private float? traverseTiles(float[] start, float[] end)
|
||||
{
|
||||
float relStartX = start[0] - origin[0];
|
||||
float relStartZ = start[2] - origin[2];
|
||||
int sx = (int)Math.Floor(relStartX / tileWidth);
|
||||
|
@ -76,19 +76,27 @@ public class VoxelQuery {
|
|||
float tDeltaX = tileWidth / tx;
|
||||
float tDeltaZ = tileDepth / tz;
|
||||
float t = 0;
|
||||
while (true) {
|
||||
while (true)
|
||||
{
|
||||
float? hit = traversHeightfield(sx, sz, start, end, t, Math.Min(1, Math.Min(tMaxX, tMaxZ)));
|
||||
if (hit.HasValue) {
|
||||
if (hit.HasValue)
|
||||
{
|
||||
return hit;
|
||||
}
|
||||
if ((dx > 0 ? sx >= ex : sx <= ex) && (dz > 0 ? sz >= ez : sz <= ez)) {
|
||||
|
||||
if ((dx > 0 ? sx >= ex : sx <= ex) && (dz > 0 ? sz >= ez : sz <= ez))
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (tMaxX < tMaxZ) {
|
||||
|
||||
if (tMaxX < tMaxZ)
|
||||
{
|
||||
t = tMaxX;
|
||||
tMaxX += tDeltaX;
|
||||
sx += stepX;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
t = tMaxZ;
|
||||
tMaxZ += tDeltaZ;
|
||||
sz += stepZ;
|
||||
|
@ -98,9 +106,11 @@ public class VoxelQuery {
|
|||
return null;
|
||||
}
|
||||
|
||||
private float? traversHeightfield(int x, int z, float[] start, float[] end, float tMin, float tMax) {
|
||||
private float? traversHeightfield(int x, int z, float[] start, float[] end, float tMin, float tMax)
|
||||
{
|
||||
Heightfield hf = heightfieldProvider.Invoke(x, z);
|
||||
if (null != hf) {
|
||||
if (null != hf)
|
||||
{
|
||||
float tx = end[0] - start[0];
|
||||
float ty = end[1] - start[1];
|
||||
float tz = end[2] - start[2];
|
||||
|
@ -127,28 +137,39 @@ public class VoxelQuery {
|
|||
float tDeltaX = hf.cs / tx;
|
||||
float tDeltaZ = hf.cs / tz;
|
||||
float t = 0;
|
||||
while (true) {
|
||||
if (sx >= 0 && sx < hf.width && sz >= 0 && sz < hf.height) {
|
||||
while (true)
|
||||
{
|
||||
if (sx >= 0 && sx < hf.width && sz >= 0 && sz < hf.height)
|
||||
{
|
||||
float y1 = start[1] + ty * (tMin + t) - hf.bmin[1];
|
||||
float y2 = start[1] + ty * (tMin + Math.Min(tMaxX, tMaxZ)) - hf.bmin[1];
|
||||
float ymin = Math.Min(y1, y2) / hf.ch;
|
||||
float ymax = Math.Max(y1, y2) / hf.ch;
|
||||
Span span = hf.spans[sx + sz * hf.width];
|
||||
while (span != null) {
|
||||
if (span.smin <= ymin && span.smax >= ymax) {
|
||||
while (span != null)
|
||||
{
|
||||
if (span.smin <= ymin && span.smax >= ymax)
|
||||
{
|
||||
return Math.Min(1, tMin + t);
|
||||
}
|
||||
|
||||
span = span.next;
|
||||
}
|
||||
}
|
||||
if ((dx > 0 ? sx >= ex : sx <= ex) && (dz > 0 ? sz >= ez : sz <= ez)) {
|
||||
|
||||
if ((dx > 0 ? sx >= ex : sx <= ex) && (dz > 0 ? sz >= ez : sz <= ez))
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (tMaxX < tMaxZ) {
|
||||
|
||||
if (tMaxX < tMaxZ)
|
||||
{
|
||||
t = tMaxX;
|
||||
tMaxX += tDeltaX;
|
||||
sx += stepX;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
t = tMaxZ;
|
||||
tMaxZ += tDeltaZ;
|
||||
sz += stepZ;
|
||||
|
@ -158,7 +179,5 @@ public class VoxelQuery {
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -20,19 +20,21 @@ using static DotRecast.Detour.DetourCommon;
|
|||
|
||||
namespace DotRecast.Detour.Extras
|
||||
{
|
||||
|
||||
|
||||
public class BVTreeBuilder {
|
||||
|
||||
public void build(MeshData data) {
|
||||
public class BVTreeBuilder
|
||||
{
|
||||
public void build(MeshData data)
|
||||
{
|
||||
data.bvTree = new BVNode[data.header.polyCount * 2];
|
||||
data.header.bvNodeCount = data.bvTree.Length == 0 ? 0
|
||||
data.header.bvNodeCount = data.bvTree.Length == 0
|
||||
? 0
|
||||
: createBVTree(data, data.bvTree, data.header.bvQuantFactor);
|
||||
}
|
||||
|
||||
private static int createBVTree(MeshData data, BVNode[] nodes, float quantFactor) {
|
||||
private static int createBVTree(MeshData data, BVNode[] nodes, float quantFactor)
|
||||
{
|
||||
NavMeshBuilder.BVItem[] items = new NavMeshBuilder.BVItem[data.header.polyCount];
|
||||
for (int i = 0; i < data.header.polyCount; i++) {
|
||||
for (int i = 0; i < data.header.polyCount; i++)
|
||||
{
|
||||
NavMeshBuilder.BVItem it = new NavMeshBuilder.BVItem();
|
||||
items[i] = it;
|
||||
it.i = i;
|
||||
|
@ -40,10 +42,12 @@ public class BVTreeBuilder {
|
|||
float[] bmax = new float[3];
|
||||
vCopy(bmin, data.verts, data.polys[i].verts[0] * 3);
|
||||
vCopy(bmax, data.verts, data.polys[i].verts[0] * 3);
|
||||
for (int j = 1; j < data.polys[i].vertCount; j++) {
|
||||
for (int j = 1; j < data.polys[i].vertCount; j++)
|
||||
{
|
||||
vMin(bmin, data.verts, data.polys[i].verts[j] * 3);
|
||||
vMax(bmax, data.verts, data.polys[i].verts[j] * 3);
|
||||
}
|
||||
|
||||
it.bmin[0] = clamp((int)((bmin[0] - data.header.bmin[0]) * quantFactor), 0, 0x7fffffff);
|
||||
it.bmin[1] = clamp((int)((bmin[1] - data.header.bmin[1]) * quantFactor), 0, 0x7fffffff);
|
||||
it.bmin[2] = clamp((int)((bmin[2] - data.header.bmin[2]) * quantFactor), 0, 0x7fffffff);
|
||||
|
@ -51,9 +55,8 @@ public class BVTreeBuilder {
|
|||
it.bmax[1] = clamp((int)((bmax[1] - data.header.bmin[1]) * quantFactor), 0, 0x7fffffff);
|
||||
it.bmax[2] = clamp((int)((bmax[2] - data.header.bmin[2]) * quantFactor), 0, 0x7fffffff);
|
||||
}
|
||||
|
||||
return NavMeshBuilder.subdivide(items, data.header.polyCount, 0, data.header.polyCount, 0, nodes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,21 +1,20 @@
|
|||
|
||||
using System;
|
||||
using DotRecast.Recast;
|
||||
using static DotRecast.Detour.DetourCommon;
|
||||
|
||||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
public abstract class AbstractGroundSampler : GroundSampler {
|
||||
|
||||
public abstract class AbstractGroundSampler : GroundSampler
|
||||
{
|
||||
protected void sampleGround(JumpLinkBuilderConfig acfg, EdgeSampler es,
|
||||
Func<float[], float, Tuple<bool, float>> heightFunc) {
|
||||
Func<float[], float, Tuple<bool, float>> heightFunc)
|
||||
{
|
||||
float cs = acfg.cellSize;
|
||||
float dist = (float)Math.Sqrt(vDist2DSqr(es.start.p, es.start.q));
|
||||
int ngsamples = Math.Max(2, (int)Math.Ceiling(dist / cs));
|
||||
sampleGroundSegment(heightFunc, es.start, ngsamples);
|
||||
foreach (GroundSegment end in es.end) {
|
||||
foreach (GroundSegment end in es.end)
|
||||
{
|
||||
sampleGroundSegment(heightFunc, end, ngsamples);
|
||||
}
|
||||
}
|
||||
|
@ -26,10 +25,12 @@ public abstract class AbstractGroundSampler : GroundSampler {
|
|||
}
|
||||
|
||||
protected void sampleGroundSegment(Func<float[], float, Tuple<bool, float>> heightFunc, GroundSegment seg,
|
||||
int nsamples) {
|
||||
int nsamples)
|
||||
{
|
||||
seg.gsamples = new GroundSample[nsamples];
|
||||
|
||||
for (int i = 0; i < nsamples; ++i) {
|
||||
for (int i = 0; i < nsamples; ++i)
|
||||
{
|
||||
float u = i / (float)(nsamples - 1);
|
||||
|
||||
GroundSample s = new GroundSample();
|
||||
|
@ -40,13 +41,13 @@ public abstract class AbstractGroundSampler : GroundSampler {
|
|||
s.p[1] = height.Item2;
|
||||
s.p[2] = pt[2];
|
||||
|
||||
if (!height.Item1) {
|
||||
if (!height.Item1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
s.validHeight = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -2,16 +2,16 @@ using System;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
public class ClimbTrajectory : Trajectory {
|
||||
|
||||
public override float[] apply(float[] start, float[] end, float u) {
|
||||
return new float[] { lerp(start[0], end[0], Math.Min(2f * u, 1f)),
|
||||
public class ClimbTrajectory : Trajectory
|
||||
{
|
||||
public override float[] apply(float[] start, float[] end, float u)
|
||||
{
|
||||
return new float[]
|
||||
{
|
||||
lerp(start[0], end[0], Math.Min(2f * u, 1f)),
|
||||
lerp(start[1], end[1], Math.Max(0f, 2f * u - 1f)),
|
||||
lerp(start[2], end[2], Math.Min(2f * u, 1f)) };
|
||||
lerp(start[2], end[2], Math.Min(2f * u, 1f))
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,10 +1,8 @@
|
|||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
public class Edge {
|
||||
public class Edge
|
||||
{
|
||||
public readonly float[] sp = new float[3];
|
||||
public readonly float[] sq = new float[3];
|
||||
}
|
||||
|
||||
}
|
|
@ -5,39 +5,55 @@ using static DotRecast.Recast.RecastConstants;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
public class EdgeExtractor {
|
||||
public Edge[] extractEdges(PolyMesh mesh) {
|
||||
public class EdgeExtractor
|
||||
{
|
||||
public Edge[] extractEdges(PolyMesh mesh)
|
||||
{
|
||||
List<Edge> edges = new List<Edge>();
|
||||
if (mesh != null) {
|
||||
if (mesh != null)
|
||||
{
|
||||
float[] orig = mesh.bmin;
|
||||
float cs = mesh.cs;
|
||||
float ch = mesh.ch;
|
||||
for (int i = 0; i < mesh.npolys; i++) {
|
||||
if (i > 41 || i < 41) {
|
||||
for (int i = 0; i < mesh.npolys; i++)
|
||||
{
|
||||
if (i > 41 || i < 41)
|
||||
{
|
||||
// continue;
|
||||
}
|
||||
|
||||
int nvp = mesh.nvp;
|
||||
int p = i * 2 * nvp;
|
||||
for (int j = 0; j < nvp; ++j) {
|
||||
if (j != 1) {
|
||||
for (int j = 0; j < nvp; ++j)
|
||||
{
|
||||
if (j != 1)
|
||||
{
|
||||
// continue;
|
||||
}
|
||||
if (mesh.polys[p + j] == RC_MESH_NULL_IDX) {
|
||||
|
||||
if (mesh.polys[p + j] == RC_MESH_NULL_IDX)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip connected edges.
|
||||
if ((mesh.polys[p + nvp + j] & 0x8000) != 0) {
|
||||
if ((mesh.polys[p + nvp + j] & 0x8000) != 0)
|
||||
{
|
||||
int dir = mesh.polys[p + nvp + j] & 0xf;
|
||||
if (dir == 0xf) {// Border
|
||||
if (mesh.polys[p + nvp + j] != RC_MESH_NULL_IDX) {
|
||||
if (dir == 0xf)
|
||||
{
|
||||
// Border
|
||||
if (mesh.polys[p + nvp + j] != RC_MESH_NULL_IDX)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int nj = j + 1;
|
||||
if (nj >= nvp || mesh.polys[p + nj] == RC_MESH_NULL_IDX) {
|
||||
if (nj >= nvp || mesh.polys[p + nj] == RC_MESH_NULL_IDX)
|
||||
{
|
||||
nj = 0;
|
||||
}
|
||||
|
||||
int va = mesh.polys[p + j] * 3;
|
||||
int vb = mesh.polys[p + nj] * 3;
|
||||
Edge e = new Edge();
|
||||
|
@ -56,7 +72,5 @@ public class EdgeExtractor {
|
|||
|
||||
return edges.ToArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,12 +1,10 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using static DotRecast.Detour.DetourCommon;
|
||||
|
||||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
public class EdgeSampler {
|
||||
public class EdgeSampler
|
||||
{
|
||||
public readonly GroundSegment start = new GroundSegment();
|
||||
public readonly List<GroundSegment> end = new List<GroundSegment>();
|
||||
public readonly Trajectory trajectory;
|
||||
|
@ -15,7 +13,8 @@ public class EdgeSampler {
|
|||
public readonly float[] ay = new float[3];
|
||||
public readonly float[] az = new float[3];
|
||||
|
||||
public EdgeSampler(Edge edge, Trajectory trajectory) {
|
||||
public EdgeSampler(Edge edge, Trajectory trajectory)
|
||||
{
|
||||
this.trajectory = trajectory;
|
||||
vCopy(ax, vSub(edge.sq, edge.sp));
|
||||
vNormalize(ax);
|
||||
|
@ -23,7 +22,5 @@ public class EdgeSampler {
|
|||
vNormalize(az);
|
||||
vSet(ay, 0, 1, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -2,13 +2,13 @@ using System;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
class EdgeSamplerFactory {
|
||||
|
||||
public EdgeSampler get(JumpLinkBuilderConfig acfg, JumpLinkType type, Edge edge) {
|
||||
class EdgeSamplerFactory
|
||||
{
|
||||
public EdgeSampler get(JumpLinkBuilderConfig acfg, JumpLinkType type, Edge edge)
|
||||
{
|
||||
EdgeSampler es = null;
|
||||
switch (type) {
|
||||
switch (type)
|
||||
{
|
||||
case JumpLinkType.EDGE_JUMP:
|
||||
es = initEdgeJumpSampler(acfg, edge);
|
||||
break;
|
||||
|
@ -19,12 +19,13 @@ class EdgeSamplerFactory {
|
|||
default:
|
||||
throw new ArgumentException("Unsupported jump type " + type);
|
||||
}
|
||||
|
||||
return es;
|
||||
}
|
||||
|
||||
|
||||
private EdgeSampler initEdgeJumpSampler(JumpLinkBuilderConfig acfg, Edge edge) {
|
||||
|
||||
private EdgeSampler initEdgeJumpSampler(JumpLinkBuilderConfig acfg, Edge edge)
|
||||
{
|
||||
EdgeSampler es = new EdgeSampler(edge, new JumpTrajectory(acfg.jumpHeight));
|
||||
es.start.height = acfg.agentClimb * 2;
|
||||
float[] offset = new float[3];
|
||||
|
@ -36,7 +37,8 @@ class EdgeSamplerFactory {
|
|||
float cs = acfg.cellSize;
|
||||
int nsamples = Math.Max(2, (int)Math.Ceiling(dx / cs));
|
||||
|
||||
for (int j = 0; j < nsamples; ++j) {
|
||||
for (int j = 0; j < nsamples; ++j)
|
||||
{
|
||||
float v = (float)j / (float)(nsamples - 1);
|
||||
float ox = 2 * acfg.agentRadius + dx * v;
|
||||
trans2d(offset, es.az, es.ay, new float[] { ox, acfg.minHeight });
|
||||
|
@ -46,10 +48,12 @@ class EdgeSamplerFactory {
|
|||
vadd(end.q, edge.sq, offset);
|
||||
es.end.Add(end);
|
||||
}
|
||||
|
||||
return es;
|
||||
}
|
||||
|
||||
private EdgeSampler initClimbDownSampler(JumpLinkBuilderConfig acfg, Edge edge) {
|
||||
private EdgeSampler initClimbDownSampler(JumpLinkBuilderConfig acfg, Edge edge)
|
||||
{
|
||||
EdgeSampler es = new EdgeSampler(edge, new ClimbTrajectory());
|
||||
es.start.height = acfg.agentClimb * 2;
|
||||
float[] offset = new float[3];
|
||||
|
@ -66,18 +70,18 @@ class EdgeSamplerFactory {
|
|||
return es;
|
||||
}
|
||||
|
||||
private void vadd(float[] dest, float[] v1, float[] v2) {
|
||||
private void vadd(float[] dest, float[] v1, float[] v2)
|
||||
{
|
||||
dest[0] = v1[0] + v2[0];
|
||||
dest[1] = v1[1] + v2[1];
|
||||
dest[2] = v1[2] + v2[2];
|
||||
}
|
||||
|
||||
private void trans2d(float[] dst, float[] ax, float[] ay, float[] pt) {
|
||||
private void trans2d(float[] dst, float[] ax, float[] ay, float[] pt)
|
||||
{
|
||||
dst[0] = ax[0] * pt[0] + ay[0] * pt[1];
|
||||
dst[1] = ax[1] * pt[0] + ay[1] * pt[1];
|
||||
dst[2] = ax[2] * pt[0] + ay[2] * pt[1];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
public class GroundSample {
|
||||
public class GroundSample
|
||||
{
|
||||
public readonly float[] p = new float[3];
|
||||
public bool validTrajectory;
|
||||
public bool validHeight;
|
||||
}
|
||||
|
||||
}
|
|
@ -2,12 +2,8 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
public interface GroundSampler {
|
||||
|
||||
public interface GroundSampler
|
||||
{
|
||||
void sample(JumpLinkBuilderConfig acfg, RecastBuilderResult result, EdgeSampler es);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,13 +1,10 @@
|
|||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
public class GroundSegment {
|
||||
public class GroundSegment
|
||||
{
|
||||
public readonly float[] p = new float[3];
|
||||
public readonly float[] q = new float[3];
|
||||
public GroundSample[] gsamples;
|
||||
public float height;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
public class JumpLink {
|
||||
|
||||
public class JumpLink
|
||||
{
|
||||
public const int MAX_SPINE = 8;
|
||||
public readonly int nspine = MAX_SPINE;
|
||||
public readonly float[] spine0 = new float[MAX_SPINE * 3];
|
||||
|
@ -13,7 +11,5 @@ public class JumpLink {
|
|||
public GroundSegment start;
|
||||
public GroundSegment end;
|
||||
public Trajectory trajectory;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -7,10 +7,8 @@ using static DotRecast.Detour.DetourCommon;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
public class JumpLinkBuilder {
|
||||
|
||||
public class JumpLinkBuilder
|
||||
{
|
||||
private readonly EdgeExtractor edgeExtractor = new EdgeExtractor();
|
||||
private readonly EdgeSamplerFactory edgeSamplerFactory = new EdgeSamplerFactory();
|
||||
private readonly GroundSampler groundSampler = new NavMeshGroundSampler();
|
||||
|
@ -20,23 +18,29 @@ public class JumpLinkBuilder {
|
|||
private readonly List<Edge[]> edges;
|
||||
private readonly List<RecastBuilderResult> results;
|
||||
|
||||
public JumpLinkBuilder(List<RecastBuilderResult> results) {
|
||||
public JumpLinkBuilder(List<RecastBuilderResult> results)
|
||||
{
|
||||
this.results = results;
|
||||
edges = results.Select(r => edgeExtractor.extractEdges(r.getMesh())).ToList();
|
||||
}
|
||||
|
||||
public List<JumpLink> build(JumpLinkBuilderConfig acfg, JumpLinkType type) {
|
||||
public List<JumpLink> build(JumpLinkBuilderConfig acfg, JumpLinkType type)
|
||||
{
|
||||
List<JumpLink> links = new List<JumpLink>();
|
||||
for (int tile = 0; tile < results.Count; tile++) {
|
||||
for (int tile = 0; tile < results.Count; tile++)
|
||||
{
|
||||
Edge[] edges = this.edges[tile];
|
||||
foreach (Edge edge in edges) {
|
||||
foreach (Edge edge in edges)
|
||||
{
|
||||
links.AddRange(processEdge(acfg, results[tile], type, edge));
|
||||
}
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
private List<JumpLink> processEdge(JumpLinkBuilderConfig acfg, RecastBuilderResult result, JumpLinkType type, Edge edge) {
|
||||
private List<JumpLink> processEdge(JumpLinkBuilderConfig acfg, RecastBuilderResult result, JumpLinkType type, Edge edge)
|
||||
{
|
||||
EdgeSampler es = edgeSamplerFactory.get(acfg, type, edge);
|
||||
groundSampler.sample(acfg, result, es);
|
||||
trajectorySampler.sample(acfg, result.getSolidHeightfield(), es);
|
||||
|
@ -45,16 +49,19 @@ public class JumpLinkBuilder {
|
|||
}
|
||||
|
||||
|
||||
private List<JumpLink> buildJumpLinks(JumpLinkBuilderConfig acfg, EdgeSampler es, JumpSegment[] jumpSegments) {
|
||||
private List<JumpLink> buildJumpLinks(JumpLinkBuilderConfig acfg, EdgeSampler es, JumpSegment[] jumpSegments)
|
||||
{
|
||||
List<JumpLink> links = new List<JumpLink>();
|
||||
foreach (JumpSegment js in jumpSegments) {
|
||||
foreach (JumpSegment js in jumpSegments)
|
||||
{
|
||||
float[] sp = es.start.gsamples[js.startSample].p;
|
||||
float[] sq = es.start.gsamples[js.startSample + js.samples - 1].p;
|
||||
GroundSegment end = es.end[js.groundSegment];
|
||||
float[] ep = end.gsamples[js.startSample].p;
|
||||
float[] eq = end.gsamples[js.startSample + js.samples - 1].p;
|
||||
float d = Math.Min(vDist2DSqr(sp, sq), vDist2DSqr(ep, eq));
|
||||
if (d >= 4 * acfg.agentRadius * acfg.agentRadius) {
|
||||
if (d >= 4 * acfg.agentRadius * acfg.agentRadius)
|
||||
{
|
||||
JumpLink link = new JumpLink();
|
||||
links.Add(link);
|
||||
link.startSamples = ArrayUtils.CopyOf(es.start.gsamples, js.startSample, js.samples - js.startSample);
|
||||
|
@ -62,7 +69,8 @@ public class JumpLinkBuilder {
|
|||
link.start = es.start;
|
||||
link.end = end;
|
||||
link.trajectory = es.trajectory;
|
||||
for (int j = 0; j < link.nspine; ++j) {
|
||||
for (int j = 0; j < link.nspine; ++j)
|
||||
{
|
||||
float u = ((float)j) / (link.nspine - 1);
|
||||
float[] p = es.trajectory.apply(sp, ep, u);
|
||||
link.spine0[j * 3] = p[0];
|
||||
|
@ -76,13 +84,13 @@ public class JumpLinkBuilder {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
public List<Edge[]> getEdges() {
|
||||
public List<Edge[]> getEdges()
|
||||
{
|
||||
return edges;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
public class JumpLinkBuilderConfig {
|
||||
|
||||
public class JumpLinkBuilderConfig
|
||||
{
|
||||
public readonly float cellSize;
|
||||
public readonly float cellHeight;
|
||||
public readonly float agentClimb;
|
||||
|
@ -18,7 +16,8 @@ public class JumpLinkBuilderConfig {
|
|||
|
||||
public JumpLinkBuilderConfig(float cellSize, float cellHeight, float agentRadius, float agentHeight,
|
||||
float agentClimb, float groundTolerance, float startDistance, float endDistance, float minHeight,
|
||||
float maxHeight, float jumpHeight) {
|
||||
float maxHeight, float jumpHeight)
|
||||
{
|
||||
this.cellSize = cellSize;
|
||||
this.cellHeight = cellHeight;
|
||||
this.agentRadius = agentRadius;
|
||||
|
@ -31,7 +30,5 @@ public class JumpLinkBuilderConfig {
|
|||
heightRange = maxHeight - minHeight;
|
||||
this.jumpHeight = jumpHeight;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
public enum JumpLinkType {
|
||||
EDGE_JUMP, EDGE_CLIMB_DOWN, EDGE_JUMP_OVER
|
||||
public enum JumpLinkType
|
||||
{
|
||||
EDGE_JUMP,
|
||||
EDGE_CLIMB_DOWN,
|
||||
EDGE_JUMP_OVER
|
||||
}
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
public class JumpSegment {
|
||||
public class JumpSegment
|
||||
{
|
||||
public int groundSegment;
|
||||
public int startSample;
|
||||
public int samples;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,31 +1,38 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using DotRecast.Core;
|
||||
|
||||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
class JumpSegmentBuilder {
|
||||
|
||||
public JumpSegment[] build(JumpLinkBuilderConfig acfg, EdgeSampler es) {
|
||||
class JumpSegmentBuilder
|
||||
{
|
||||
public JumpSegment[] build(JumpLinkBuilderConfig acfg, EdgeSampler es)
|
||||
{
|
||||
int n = es.end[0].gsamples.Length;
|
||||
int[][] sampleGrid = ArrayUtils.Of<int>(n, es.end.Count);
|
||||
for (int j = 0; j < es.end.Count; j++) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
for (int j = 0; j < es.end.Count; j++)
|
||||
{
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
sampleGrid[i][j] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill connected regions
|
||||
int region = 0;
|
||||
for (int j = 0; j < es.end.Count; j++) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (sampleGrid[i][j] == -1) {
|
||||
for (int j = 0; j < es.end.Count; j++)
|
||||
{
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
if (sampleGrid[i][j] == -1)
|
||||
{
|
||||
GroundSample p = es.end[j].gsamples[i];
|
||||
if (!p.validTrajectory) {
|
||||
if (!p.validTrajectory)
|
||||
{
|
||||
sampleGrid[i][j] = -2;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
var queue = new Queue<int[]>();
|
||||
queue.Enqueue(new int[] { i, j });
|
||||
fill(es, sampleGrid, queue, acfg.agentClimb, region);
|
||||
|
@ -34,66 +41,90 @@ class JumpSegmentBuilder {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
JumpSegment[] jumpSegments = new JumpSegment[region];
|
||||
for (int i = 0; i < jumpSegments.Length; i++) {
|
||||
for (int i = 0; i < jumpSegments.Length; i++)
|
||||
{
|
||||
jumpSegments[i] = new JumpSegment();
|
||||
}
|
||||
|
||||
// Find longest segments per region
|
||||
for (int j = 0; j < es.end.Count; j++) {
|
||||
for (int j = 0; j < es.end.Count; j++)
|
||||
{
|
||||
int l = 0;
|
||||
int r = -2;
|
||||
for (int i = 0; i < n + 1; i++) {
|
||||
if (i == n || sampleGrid[i][j] != r) {
|
||||
if (r >= 0) {
|
||||
if (jumpSegments[r].samples < l) {
|
||||
for (int i = 0; i < n + 1; i++)
|
||||
{
|
||||
if (i == n || sampleGrid[i][j] != r)
|
||||
{
|
||||
if (r >= 0)
|
||||
{
|
||||
if (jumpSegments[r].samples < l)
|
||||
{
|
||||
jumpSegments[r].samples = l;
|
||||
jumpSegments[r].startSample = i - l;
|
||||
jumpSegments[r].groundSegment = j;
|
||||
}
|
||||
}
|
||||
if (i < n) {
|
||||
|
||||
if (i < n)
|
||||
{
|
||||
r = sampleGrid[i][j];
|
||||
}
|
||||
|
||||
l = 1;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
l++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return jumpSegments;
|
||||
}
|
||||
|
||||
private void fill(EdgeSampler es, int[][] sampleGrid, Queue<int[]> queue, float agentClimb, int region) {
|
||||
while (queue.TryDequeue(out var ij)) {
|
||||
private void fill(EdgeSampler es, int[][] sampleGrid, Queue<int[]> queue, float agentClimb, int region)
|
||||
{
|
||||
while (queue.TryDequeue(out var ij))
|
||||
{
|
||||
int i = ij[0];
|
||||
int j = ij[1];
|
||||
if (sampleGrid[i][j] == -1) {
|
||||
if (sampleGrid[i][j] == -1)
|
||||
{
|
||||
GroundSample p = es.end[j].gsamples[i];
|
||||
sampleGrid[i][j] = region;
|
||||
float h = p.p[1];
|
||||
if (i < sampleGrid.Length - 1) {
|
||||
if (i < sampleGrid.Length - 1)
|
||||
{
|
||||
addNeighbour(es, queue, agentClimb, h, i + 1, j);
|
||||
}
|
||||
if (i > 0) {
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
addNeighbour(es, queue, agentClimb, h, i - 1, j);
|
||||
}
|
||||
if (j < sampleGrid[0].Length - 1) {
|
||||
|
||||
if (j < sampleGrid[0].Length - 1)
|
||||
{
|
||||
addNeighbour(es, queue, agentClimb, h, i, j + 1);
|
||||
}
|
||||
if (j > 0) {
|
||||
|
||||
if (j > 0)
|
||||
{
|
||||
addNeighbour(es, queue, agentClimb, h, i, j - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addNeighbour(EdgeSampler es, Queue<int[]> queue, float agentClimb, float h, int i, int j) {
|
||||
private void addNeighbour(EdgeSampler es, Queue<int[]> queue, float agentClimb, float h, int i, int j)
|
||||
{
|
||||
GroundSample q = es.end[j].gsamples[i];
|
||||
if (q.validTrajectory && Math.Abs(q.p[1] - h) < agentClimb) {
|
||||
if (q.validTrajectory && Math.Abs(q.p[1] - h) < agentClimb)
|
||||
{
|
||||
queue.Enqueue(new int[] { i, j });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -2,44 +2,58 @@ using System;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
public class JumpTrajectory : Trajectory {
|
||||
|
||||
public class JumpTrajectory : Trajectory
|
||||
{
|
||||
private readonly float jumpHeight;
|
||||
|
||||
public JumpTrajectory(float jumpHeight) {
|
||||
public JumpTrajectory(float jumpHeight)
|
||||
{
|
||||
this.jumpHeight = jumpHeight;
|
||||
}
|
||||
|
||||
public override float[] apply(float[] start, float[] end, float u) {
|
||||
return new float[] { lerp(start[0], end[0], u), interpolateHeight(start[1], end[1], u),
|
||||
lerp(start[2], end[2], u) };
|
||||
public override float[] apply(float[] start, float[] end, float u)
|
||||
{
|
||||
return new float[]
|
||||
{
|
||||
lerp(start[0], end[0], u), interpolateHeight(start[1], end[1], u),
|
||||
lerp(start[2], end[2], u)
|
||||
};
|
||||
}
|
||||
|
||||
private float interpolateHeight(float ys, float ye, float u) {
|
||||
if (u == 0f) {
|
||||
private float interpolateHeight(float ys, float ye, float u)
|
||||
{
|
||||
if (u == 0f)
|
||||
{
|
||||
return ys;
|
||||
} else if (u == 1.0f) {
|
||||
}
|
||||
else if (u == 1.0f)
|
||||
{
|
||||
return ye;
|
||||
}
|
||||
|
||||
float h1, h2;
|
||||
if (ys >= ye) { // jump down
|
||||
if (ys >= ye)
|
||||
{
|
||||
// jump down
|
||||
h1 = jumpHeight;
|
||||
h2 = jumpHeight + ys - ye;
|
||||
} else { // jump up
|
||||
}
|
||||
else
|
||||
{
|
||||
// jump up
|
||||
h1 = jumpHeight + ys - ye;
|
||||
h2 = jumpHeight;
|
||||
}
|
||||
|
||||
float t = (float)(Math.Sqrt(h1) / (Math.Sqrt(h2) + Math.Sqrt(h1)));
|
||||
if (u <= t) {
|
||||
if (u <= t)
|
||||
{
|
||||
float v1 = 1.0f - (u / t);
|
||||
return ys + h1 - h1 * v1 * v1;
|
||||
}
|
||||
|
||||
float v = (u - t) / (1.0f - t);
|
||||
return ys + h1 - h2 * v * v;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -4,31 +4,32 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
class NavMeshGroundSampler : AbstractGroundSampler {
|
||||
|
||||
class NavMeshGroundSampler : AbstractGroundSampler
|
||||
{
|
||||
private readonly QueryFilter filter = new NoOpFilter();
|
||||
|
||||
private class NoOpFilter : QueryFilter {
|
||||
|
||||
public bool passFilter(long refs, MeshTile tile, Poly poly) {
|
||||
private class NoOpFilter : QueryFilter
|
||||
{
|
||||
public bool passFilter(long refs, MeshTile tile, Poly poly)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public float getCost(float[] pa, float[] pb, long prevRef, MeshTile prevTile, Poly prevPoly, long curRef,
|
||||
MeshTile curTile, Poly curPoly, long nextRef, MeshTile nextTile, Poly nextPoly) {
|
||||
MeshTile curTile, Poly curPoly, long nextRef, MeshTile nextTile, Poly nextPoly)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void sample(JumpLinkBuilderConfig acfg, RecastBuilderResult result, EdgeSampler es) {
|
||||
public void sample(JumpLinkBuilderConfig acfg, RecastBuilderResult result, EdgeSampler es)
|
||||
{
|
||||
NavMeshQuery navMeshQuery = createNavMesh(result, acfg.agentRadius, acfg.agentHeight, acfg.agentClimb);
|
||||
sampleGround(acfg, es, (pt, h) => getNavMeshHeight(navMeshQuery, pt, acfg.cellSize, h));
|
||||
}
|
||||
|
||||
private NavMeshQuery createNavMesh(RecastBuilderResult r, float agentRadius, float agentHeight, float agentClimb) {
|
||||
private NavMeshQuery createNavMesh(RecastBuilderResult r, float agentRadius, float agentHeight, float agentClimb)
|
||||
{
|
||||
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
|
||||
option.verts = r.getMesh().verts;
|
||||
option.vertCount = r.getMesh().nverts;
|
||||
|
@ -69,27 +70,31 @@ class NavMeshGroundSampler : AbstractGroundSampler {
|
|||
}
|
||||
|
||||
private Tuple<bool, float> getNavMeshHeight(NavMeshQuery navMeshQuery, float[] pt, float cs,
|
||||
float heightRange) {
|
||||
float heightRange)
|
||||
{
|
||||
float[] halfExtents = new float[] { cs, heightRange, cs };
|
||||
float maxHeight = pt[1] + heightRange;
|
||||
AtomicBoolean found = new AtomicBoolean();
|
||||
AtomicFloat minHeight = new AtomicFloat(pt[1]);
|
||||
navMeshQuery.queryPolygons(pt, halfExtents, filter, new PolyQueryInvoker((tile, poly, refs) => {
|
||||
navMeshQuery.queryPolygons(pt, halfExtents, filter, new PolyQueryInvoker((tile, poly, refs) =>
|
||||
{
|
||||
Result<float> h = navMeshQuery.getPolyHeight(refs, pt);
|
||||
if (h.succeeded()) {
|
||||
if (h.succeeded())
|
||||
{
|
||||
float y = h.result;
|
||||
if (y > minHeight.Get() && y < maxHeight) {
|
||||
if (y > minHeight.Get() && y < maxHeight)
|
||||
{
|
||||
minHeight.Exchange(y);
|
||||
found.set(true);
|
||||
}
|
||||
}
|
||||
}));
|
||||
if (found.get()) {
|
||||
if (found.get())
|
||||
{
|
||||
return Tuple.Create(true, minHeight.Get());
|
||||
}
|
||||
|
||||
return Tuple.Create(false, pt[1]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -2,11 +2,10 @@ using System;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
public class Trajectory {
|
||||
|
||||
public float lerp(float f, float g, float u) {
|
||||
public class Trajectory
|
||||
{
|
||||
public float lerp(float f, float g, float u)
|
||||
{
|
||||
return u * g + (1f - u) * f;
|
||||
}
|
||||
|
||||
|
@ -14,7 +13,5 @@ public class Trajectory {
|
|||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -4,44 +4,53 @@ using static DotRecast.Detour.DetourCommon;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Jumplink
|
||||
{
|
||||
|
||||
|
||||
class TrajectorySampler {
|
||||
|
||||
public void sample(JumpLinkBuilderConfig acfg, Heightfield heightfield, EdgeSampler es) {
|
||||
class TrajectorySampler
|
||||
{
|
||||
public void sample(JumpLinkBuilderConfig acfg, Heightfield heightfield, EdgeSampler es)
|
||||
{
|
||||
int nsamples = es.start.gsamples.Length;
|
||||
for (int i = 0; i < nsamples; ++i) {
|
||||
for (int i = 0; i < nsamples; ++i)
|
||||
{
|
||||
GroundSample ssmp = es.start.gsamples[i];
|
||||
foreach (GroundSegment end in es.end) {
|
||||
foreach (GroundSegment end in es.end)
|
||||
{
|
||||
GroundSample esmp = end.gsamples[i];
|
||||
if (!ssmp.validHeight || !esmp.validHeight) {
|
||||
if (!ssmp.validHeight || !esmp.validHeight)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!sampleTrajectory(acfg, heightfield, ssmp.p, esmp.p, es.trajectory)) {
|
||||
if (!sampleTrajectory(acfg, heightfield, ssmp.p, esmp.p, es.trajectory))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ssmp.validTrajectory = true;
|
||||
esmp.validTrajectory = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool sampleTrajectory(JumpLinkBuilderConfig acfg, Heightfield solid, float[] pa, float[] pb, Trajectory tra) {
|
||||
private bool sampleTrajectory(JumpLinkBuilderConfig acfg, Heightfield solid, float[] pa, float[] pb, Trajectory tra)
|
||||
{
|
||||
float cs = Math.Min(acfg.cellSize, acfg.cellHeight);
|
||||
float d = vDist2D(pa, pb) + Math.Abs(pa[1] - pb[1]);
|
||||
int nsamples = Math.Max(2, (int)Math.Ceiling(d / cs));
|
||||
for (int i = 0; i < nsamples; ++i) {
|
||||
for (int i = 0; i < nsamples; ++i)
|
||||
{
|
||||
float u = (float)i / (float)(nsamples - 1);
|
||||
float[] p = tra.apply(pa, pb, u);
|
||||
if (checkHeightfieldCollision(solid, p[0], p[1] + acfg.groundTolerance, p[1] + acfg.agentHeight, p[2])) {
|
||||
if (checkHeightfieldCollision(solid, p[0], p[1] + acfg.groundTolerance, p[1] + acfg.agentHeight, p[2]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool checkHeightfieldCollision(Heightfield solid, float x, float ymin, float ymax, float z) {
|
||||
private bool checkHeightfieldCollision(Heightfield solid, float x, float ymin, float ymax, float z)
|
||||
{
|
||||
int w = solid.width;
|
||||
int h = solid.height;
|
||||
float cs = solid.cs;
|
||||
|
@ -50,31 +59,35 @@ class TrajectorySampler {
|
|||
int ix = (int)Math.Floor((x - orig[0]) / cs);
|
||||
int iz = (int)Math.Floor((z - orig[2]) / cs);
|
||||
|
||||
if (ix < 0 || iz < 0 || ix > w || iz > h) {
|
||||
if (ix < 0 || iz < 0 || ix > w || iz > h)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Span s = solid.spans[ix + iz * w];
|
||||
if (s == null) {
|
||||
if (s == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
while (s != null) {
|
||||
while (s != null)
|
||||
{
|
||||
float symin = orig[1] + s.smin * ch;
|
||||
float symax = orig[1] + s.smax * ch;
|
||||
if (overlapRange(ymin, ymax, symin, symax)) {
|
||||
if (overlapRange(ymin, ymax, symin, symax))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
s = s.next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool overlapRange(float amin, float amax, float bmin, float bmax) {
|
||||
private bool overlapRange(float amin, float amax, float bmin, float bmax)
|
||||
{
|
||||
return (amin > bmax || amax < bmin) ? false : true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -20,35 +20,44 @@ using System.IO;
|
|||
|
||||
namespace DotRecast.Detour.Extras
|
||||
{
|
||||
|
||||
|
||||
public class ObjExporter {
|
||||
|
||||
public void export(NavMesh mesh) {
|
||||
public class ObjExporter
|
||||
{
|
||||
public void export(NavMesh mesh)
|
||||
{
|
||||
string filename = Path.Combine(Directory.GetCurrentDirectory(), "Demo", "astar.obj");
|
||||
using var fs = new FileStream(filename, FileMode.CreateNew);
|
||||
using var fw = new StreamWriter(fs);
|
||||
for (int i = 0; i < mesh.getTileCount(); i++) {
|
||||
for (int i = 0; i < mesh.getTileCount(); i++)
|
||||
{
|
||||
MeshTile tile = mesh.getTile(i);
|
||||
if (tile != null) {
|
||||
for (int v = 0; v < tile.data.header.vertCount; v++) {
|
||||
if (tile != null)
|
||||
{
|
||||
for (int v = 0; v < tile.data.header.vertCount; v++)
|
||||
{
|
||||
fw.Write("v " + tile.data.verts[v * 3] + " " + tile.data.verts[v * 3 + 1] + " "
|
||||
+ tile.data.verts[v * 3 + 2] + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int vertexOffset = 1;
|
||||
for (int i = 0; i < mesh.getTileCount(); i++) {
|
||||
for (int i = 0; i < mesh.getTileCount(); i++)
|
||||
{
|
||||
MeshTile tile = mesh.getTile(i);
|
||||
if (tile != null) {
|
||||
for (int p = 0; p < tile.data.header.polyCount; p++) {
|
||||
if (tile != null)
|
||||
{
|
||||
for (int p = 0; p < tile.data.header.polyCount; p++)
|
||||
{
|
||||
fw.Write("f ");
|
||||
Poly poly = tile.data.polys[p];
|
||||
for (int v = 0; v < poly.vertCount; v++) {
|
||||
for (int v = 0; v < poly.vertCount; v++)
|
||||
{
|
||||
fw.Write(poly.verts[v] + vertexOffset + " ");
|
||||
}
|
||||
|
||||
fw.Write("\n");
|
||||
}
|
||||
|
||||
vertexOffset += tile.data.header.vertCount;
|
||||
}
|
||||
}
|
||||
|
@ -64,5 +73,4 @@ public class ObjExporter {
|
|||
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
|
@ -15,70 +15,85 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.Extras
|
||||
{
|
||||
|
||||
|
||||
public class PolyUtils {
|
||||
|
||||
public class PolyUtils
|
||||
{
|
||||
/**
|
||||
* Find edge shared by 2 polygons within the same tile
|
||||
*/
|
||||
public static int findEdge(Poly node, Poly neighbour, MeshData tile, MeshData neighbourTile) {
|
||||
public static int findEdge(Poly node, Poly neighbour, MeshData tile, MeshData neighbourTile)
|
||||
{
|
||||
// Compare indices first assuming there are no duplicate vertices
|
||||
for (int i = 0; i < node.vertCount; i++) {
|
||||
for (int i = 0; i < node.vertCount; i++)
|
||||
{
|
||||
int j = (i + 1) % node.vertCount;
|
||||
for (int k = 0; k < neighbour.vertCount; k++) {
|
||||
for (int k = 0; k < neighbour.vertCount; k++)
|
||||
{
|
||||
int l = (k + 1) % neighbour.vertCount;
|
||||
if ((node.verts[i] == neighbour.verts[l] && node.verts[j] == neighbour.verts[k])
|
||||
|| (node.verts[i] == neighbour.verts[k] && node.verts[j] == neighbour.verts[l])) {
|
||||
|| (node.verts[i] == neighbour.verts[k] && node.verts[j] == neighbour.verts[l]))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to comparing actual positions in case of duplicate vertices
|
||||
for (int i = 0; i < node.vertCount; i++) {
|
||||
for (int i = 0; i < node.vertCount; i++)
|
||||
{
|
||||
int j = (i + 1) % node.vertCount;
|
||||
for (int k = 0; k < neighbour.vertCount; k++) {
|
||||
for (int k = 0; k < neighbour.vertCount; k++)
|
||||
{
|
||||
int l = (k + 1) % neighbour.vertCount;
|
||||
if ((samePosition(tile.verts, node.verts[i], neighbourTile.verts, neighbour.verts[l])
|
||||
&& samePosition(tile.verts, node.verts[j], neighbourTile.verts, neighbour.verts[k]))
|
||||
|| (samePosition(tile.verts, node.verts[i], neighbourTile.verts, neighbour.verts[k])
|
||||
&& samePosition(tile.verts, node.verts[j], neighbourTile.verts, neighbour.verts[l]))) {
|
||||
&& samePosition(tile.verts, node.verts[j], neighbourTile.verts, neighbour.verts[l])))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static bool samePosition(float[] verts, int v, float[] verts2, int v2) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (verts[3 * v + i] != verts2[3 * v2 + 1]) {
|
||||
private static bool samePosition(float[] verts, int v, float[] verts2, int v2)
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (verts[3 * v + i] != verts2[3 * v2 + 1])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find edge closest to the given coordinate
|
||||
*/
|
||||
public static int findEdge(Poly node, MeshData tile, float value, int comp) {
|
||||
public static int findEdge(Poly node, MeshData tile, float value, int comp)
|
||||
{
|
||||
float error = float.MaxValue;
|
||||
int edge = 0;
|
||||
for (int i = 0; i < node.vertCount; i++) {
|
||||
for (int i = 0; i < node.vertCount; i++)
|
||||
{
|
||||
int j = (i + 1) % node.vertCount;
|
||||
float v1 = tile.verts[3 * node.verts[i] + comp] - value;
|
||||
float v2 = tile.verts[3 * node.verts[j] + comp] - value;
|
||||
float d = v1 * v1 + v2 * v2;
|
||||
if (d < error) {
|
||||
if (d < error)
|
||||
{
|
||||
error = d;
|
||||
edge = i;
|
||||
}
|
||||
}
|
||||
|
||||
return edge;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -15,20 +15,19 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.Extras.Unity.Astar
|
||||
{
|
||||
|
||||
|
||||
public class BVTreeCreator {
|
||||
|
||||
public class BVTreeCreator
|
||||
{
|
||||
private readonly BVTreeBuilder builder = new BVTreeBuilder();
|
||||
|
||||
public void build(GraphMeshData graphData) {
|
||||
foreach (MeshData d in graphData.tiles) {
|
||||
public void build(GraphMeshData graphData)
|
||||
{
|
||||
foreach (MeshData d in graphData.tiles)
|
||||
{
|
||||
builder.build(d);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,30 +22,31 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Unity.Astar
|
||||
{
|
||||
|
||||
|
||||
class GraphConnectionReader : ZipBinaryReader {
|
||||
|
||||
public List<int[]> read(ZipArchive file, string filename, Meta meta, int[] indexToNode) {
|
||||
class GraphConnectionReader : ZipBinaryReader
|
||||
{
|
||||
public List<int[]> read(ZipArchive file, string filename, Meta meta, int[] indexToNode)
|
||||
{
|
||||
List<int[]> connections = new List<int[]>();
|
||||
ByteBuffer buffer = toByteBuffer(file, filename);
|
||||
while (buffer.remaining() > 0) {
|
||||
while (buffer.remaining() > 0)
|
||||
{
|
||||
int count = buffer.getInt();
|
||||
int[] nodeConnections = new int[count];
|
||||
connections.Add(nodeConnections);
|
||||
for (int i = 0; i < count; i++) {
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int nodeIndex = buffer.getInt();
|
||||
nodeConnections[i] = indexToNode[nodeIndex];
|
||||
// XXX: Is there anything we can do with the cost?
|
||||
int cost = buffer.getInt();
|
||||
if (meta.isVersionAtLeast(Meta.UPDATED_STRUCT_VERSION)) {
|
||||
if (meta.isVersionAtLeast(Meta.UPDATED_STRUCT_VERSION))
|
||||
{
|
||||
byte shapeEdge = buffer.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return connections;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -20,12 +20,8 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Unity.Astar
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
public class GraphData {
|
||||
|
||||
public class GraphData
|
||||
{
|
||||
public readonly Meta meta;
|
||||
public readonly int[] indexToNode;
|
||||
public readonly NodeLink2[] nodeLinks2;
|
||||
|
@ -34,7 +30,8 @@ public class GraphData {
|
|||
public readonly List<List<int[]>> graphConnections;
|
||||
|
||||
public GraphData(Meta meta, int[] indexToNode, NodeLink2[] nodeLinks2, List<GraphMeta> graphMeta,
|
||||
List<GraphMeshData> graphMeshData, List<List<int[]>> graphConnections) {
|
||||
List<GraphMeshData> graphMeshData, List<List<int[]>> graphConnections)
|
||||
{
|
||||
this.meta = meta;
|
||||
this.indexToNode = indexToNode;
|
||||
this.nodeLinks2 = nodeLinks2;
|
||||
|
@ -42,7 +39,5 @@ public class GraphData {
|
|||
this.graphMeshData = graphMeshData;
|
||||
this.graphConnections = graphConnections;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -15,52 +15,63 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.Extras.Unity.Astar
|
||||
{
|
||||
|
||||
|
||||
public class GraphMeshData {
|
||||
|
||||
public class GraphMeshData
|
||||
{
|
||||
public readonly int tileXCount;
|
||||
public readonly int tileZCount;
|
||||
public readonly MeshData[] tiles;
|
||||
|
||||
public GraphMeshData(int tileXCount, int tileZCount, MeshData[] tiles) {
|
||||
public GraphMeshData(int tileXCount, int tileZCount, MeshData[] tiles)
|
||||
{
|
||||
this.tileXCount = tileXCount;
|
||||
this.tileZCount = tileZCount;
|
||||
this.tiles = tiles;
|
||||
}
|
||||
|
||||
public int countNodes() {
|
||||
public int countNodes()
|
||||
{
|
||||
int polyCount = 0;
|
||||
foreach (MeshData t in tiles) {
|
||||
foreach (MeshData t in tiles)
|
||||
{
|
||||
polyCount += t.header.polyCount;
|
||||
}
|
||||
|
||||
return polyCount;
|
||||
}
|
||||
|
||||
public Poly getNode(int node) {
|
||||
public Poly getNode(int node)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (MeshData t in tiles) {
|
||||
if (node - index >= 0 && node - index < t.header.polyCount) {
|
||||
foreach (MeshData t in tiles)
|
||||
{
|
||||
if (node - index >= 0 && node - index < t.header.polyCount)
|
||||
{
|
||||
return t.polys[node - index];
|
||||
}
|
||||
|
||||
index += t.header.polyCount;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public MeshData getTile(int node) {
|
||||
public MeshData getTile(int node)
|
||||
{
|
||||
int index = 0;
|
||||
foreach (MeshData t in tiles) {
|
||||
if (node - index >= 0 && node - index < t.header.polyCount) {
|
||||
foreach (MeshData t in tiles)
|
||||
{
|
||||
if (node - index >= 0 && node - index < t.header.polyCount)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
|
||||
index += t.header.polyCount;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -23,26 +23,30 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Unity.Astar
|
||||
{
|
||||
|
||||
|
||||
public class GraphMeshDataReader : ZipBinaryReader {
|
||||
|
||||
public class GraphMeshDataReader : ZipBinaryReader
|
||||
{
|
||||
public const float INT_PRECISION_FACTOR = 1000f;
|
||||
|
||||
public GraphMeshData read(ZipArchive file, string filename, GraphMeta meta, int maxVertPerPoly) {
|
||||
public GraphMeshData read(ZipArchive file, string filename, GraphMeta meta, int maxVertPerPoly)
|
||||
{
|
||||
ByteBuffer buffer = toByteBuffer(file, filename);
|
||||
int tileXCount = buffer.getInt();
|
||||
if (tileXCount < 0) {
|
||||
if (tileXCount < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int tileZCount = buffer.getInt();
|
||||
MeshData[] tiles = new MeshData[tileXCount * tileZCount];
|
||||
for (int z = 0; z < tileZCount; z++) {
|
||||
for (int x = 0; x < tileXCount; x++) {
|
||||
for (int z = 0; z < tileZCount; z++)
|
||||
{
|
||||
for (int x = 0; x < tileXCount; x++)
|
||||
{
|
||||
int tileIndex = x + z * tileXCount;
|
||||
int tx = buffer.getInt();
|
||||
int tz = buffer.getInt();
|
||||
if (tx != x || tz != z) {
|
||||
if (tx != x || tz != z)
|
||||
{
|
||||
throw new ArgumentException("Inconsistent tile positions");
|
||||
}
|
||||
|
||||
|
@ -52,18 +56,21 @@ public class GraphMeshDataReader : ZipBinaryReader {
|
|||
|
||||
int trisCount = buffer.getInt();
|
||||
int[] tris = new int[trisCount];
|
||||
for (int i = 0; i < tris.Length; i++) {
|
||||
for (int i = 0; i < tris.Length; i++)
|
||||
{
|
||||
tris[i] = buffer.getInt();
|
||||
}
|
||||
|
||||
int vertsCount = buffer.getInt();
|
||||
float[] verts = new float[3 * vertsCount];
|
||||
for (int i = 0; i < verts.Length; i++) {
|
||||
for (int i = 0; i < verts.Length; i++)
|
||||
{
|
||||
verts[i] = buffer.getInt() / INT_PRECISION_FACTOR;
|
||||
}
|
||||
|
||||
int[] vertsInGraphSpace = new int[3 * buffer.getInt()];
|
||||
for (int i = 0; i < vertsInGraphSpace.Length; i++) {
|
||||
for (int i = 0; i < vertsInGraphSpace.Length; i++)
|
||||
{
|
||||
vertsInGraphSpace[i] = buffer.getInt();
|
||||
}
|
||||
|
||||
|
@ -75,7 +82,8 @@ public class GraphMeshDataReader : ZipBinaryReader {
|
|||
int vertMask = getVertMask(vertsCount);
|
||||
float ymin = float.PositiveInfinity;
|
||||
float ymax = float.NegativeInfinity;
|
||||
for (int i = 0; i < nodes.Length; i++) {
|
||||
for (int i = 0; i < nodes.Length; i++)
|
||||
{
|
||||
nodes[i] = new Poly(i, maxVertPerPoly);
|
||||
nodes[i].vertCount = 3;
|
||||
// XXX: What can we do with the penalty?
|
||||
|
@ -136,6 +144,7 @@ public class GraphMeshDataReader : ZipBinaryReader {
|
|||
tiles[tileIndex].header = header;
|
||||
}
|
||||
}
|
||||
|
||||
return new GraphMeshData(tileXCount, tileZCount, tiles);
|
||||
}
|
||||
|
||||
|
@ -153,13 +162,13 @@ public class GraphMeshDataReader : ZipBinaryReader {
|
|||
private int getVertMask(int vertsCount)
|
||||
{
|
||||
int vertMask = highestOneBit((uint)vertsCount);
|
||||
if (vertMask != vertsCount) {
|
||||
if (vertMask != vertsCount)
|
||||
{
|
||||
vertMask *= 2;
|
||||
}
|
||||
|
||||
vertMask--;
|
||||
return vertMask;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -15,13 +15,11 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.Extras.Unity.Astar
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
public class GraphMeta {
|
||||
public class GraphMeta
|
||||
{
|
||||
public float characterRadius { get; set; }
|
||||
public float contourMaxError { get; set; }
|
||||
public float cellSize { get; set; }
|
||||
|
@ -30,14 +28,16 @@ public class GraphMeta {
|
|||
public float maxSlope { get; set; }
|
||||
public float maxEdgeLength { get; set; }
|
||||
public float minRegionSize { get; set; }
|
||||
|
||||
/** Size of tile along X axis in voxels */
|
||||
public float tileSizeX { get; set; }
|
||||
|
||||
/** Size of tile along Z axis in voxels */
|
||||
public float tileSizeZ { get; set; }
|
||||
|
||||
public bool useTiles { get; set; }
|
||||
public Vector3f rotation { get; set; }
|
||||
public Vector3f forcedBoundsCenter { get; set; }
|
||||
public Vector3f forcedBoundsSize { get; set; }
|
||||
}
|
||||
|
||||
}
|
|
@ -22,15 +22,15 @@ using System.Text.Json;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Unity.Astar
|
||||
{
|
||||
|
||||
|
||||
public class GraphMetaReader {
|
||||
|
||||
public GraphMeta read(ZipArchive file, string filename) {
|
||||
public class GraphMetaReader
|
||||
{
|
||||
public GraphMeta read(ZipArchive file, string filename)
|
||||
{
|
||||
ZipArchiveEntry entry = file.GetEntry(filename);
|
||||
using StreamReader reader = new StreamReader(entry.Open());
|
||||
|
||||
JsonSerializerOptions options = new JsonSerializerOptions {
|
||||
JsonSerializerOptions options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
|
@ -38,5 +38,4 @@ public class GraphMetaReader {
|
|||
return JsonSerializer.Deserialize<GraphMeta>(json, options);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -21,21 +21,25 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Unity.Astar
|
||||
{
|
||||
|
||||
|
||||
public class LinkBuilder {
|
||||
|
||||
public class LinkBuilder
|
||||
{
|
||||
// Process connections and transform them into recast neighbour flags
|
||||
public void build(int nodeOffset, GraphMeshData graphData, List<int[]> connections) {
|
||||
for (int n = 0; n < connections.Count; n++) {
|
||||
public void build(int nodeOffset, GraphMeshData graphData, List<int[]> connections)
|
||||
{
|
||||
for (int n = 0; n < connections.Count; n++)
|
||||
{
|
||||
int[] nodeConnections = connections[n];
|
||||
MeshData tile = graphData.getTile(n);
|
||||
Poly node = graphData.getNode(n);
|
||||
foreach (int connection in nodeConnections) {
|
||||
foreach (int connection in nodeConnections)
|
||||
{
|
||||
MeshData neighbourTile = graphData.getTile(connection - nodeOffset);
|
||||
if (neighbourTile != tile) {
|
||||
if (neighbourTile != tile)
|
||||
{
|
||||
buildExternalLink(tile, node, neighbourTile);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
Poly neighbour = graphData.getNode(connection - nodeOffset);
|
||||
buildInternalLink(tile, node, neighbourTile, neighbour);
|
||||
}
|
||||
|
@ -43,28 +47,38 @@ public class LinkBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
private void buildInternalLink(MeshData tile, Poly node, MeshData neighbourTile, Poly neighbour) {
|
||||
private void buildInternalLink(MeshData tile, Poly node, MeshData neighbourTile, Poly neighbour)
|
||||
{
|
||||
int edge = PolyUtils.findEdge(node, neighbour, tile, neighbourTile);
|
||||
if (edge >= 0) {
|
||||
if (edge >= 0)
|
||||
{
|
||||
node.neis[edge] = neighbour.index + 1;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
// In case of external link to other tiles we must find the direction
|
||||
private void buildExternalLink(MeshData tile, Poly node, MeshData neighbourTile) {
|
||||
if (neighbourTile.header.bmin[0] > tile.header.bmin[0]) {
|
||||
private void buildExternalLink(MeshData tile, Poly node, MeshData neighbourTile)
|
||||
{
|
||||
if (neighbourTile.header.bmin[0] > tile.header.bmin[0])
|
||||
{
|
||||
node.neis[PolyUtils.findEdge(node, tile, neighbourTile.header.bmin[0], 0)] = NavMesh.DT_EXT_LINK;
|
||||
} else if (neighbourTile.header.bmin[0] < tile.header.bmin[0]) {
|
||||
}
|
||||
else if (neighbourTile.header.bmin[0] < tile.header.bmin[0])
|
||||
{
|
||||
node.neis[PolyUtils.findEdge(node, tile, tile.header.bmin[0], 0)] = NavMesh.DT_EXT_LINK | 4;
|
||||
} else if (neighbourTile.header.bmin[2] > tile.header.bmin[2]) {
|
||||
}
|
||||
else if (neighbourTile.header.bmin[2] > tile.header.bmin[2])
|
||||
{
|
||||
node.neis[PolyUtils.findEdge(node, tile, neighbourTile.header.bmin[2], 2)] = NavMesh.DT_EXT_LINK | 2;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
node.neis[PolyUtils.findEdge(node, tile, tile.header.bmin[2], 2)] = NavMesh.DT_EXT_LINK | 6;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -21,10 +21,8 @@ using System.Text.RegularExpressions;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Unity.Astar
|
||||
{
|
||||
|
||||
|
||||
public class Meta {
|
||||
|
||||
public class Meta
|
||||
{
|
||||
public const string TYPENAME_RECAST_GRAPH = "Pathfinding.RecastGraph";
|
||||
public const string MIN_SUPPORTED_VERSION = "4.0.6";
|
||||
public const string UPDATED_STRUCT_VERSION = "4.1.16";
|
||||
|
@ -34,44 +32,58 @@ public class Meta {
|
|||
public string[] guids { get; set; }
|
||||
public string[] typeNames { get; set; }
|
||||
|
||||
public bool isSupportedVersion() {
|
||||
public bool isSupportedVersion()
|
||||
{
|
||||
return isVersionAtLeast(MIN_SUPPORTED_VERSION);
|
||||
}
|
||||
|
||||
public bool isVersionAtLeast(string minVersion) {
|
||||
public bool isVersionAtLeast(string minVersion)
|
||||
{
|
||||
int[] actual = parseVersion(version);
|
||||
int[] minSupported = parseVersion(minVersion);
|
||||
for (int i = 0; i < Math.Min(actual.Length, minSupported.Length); i++) {
|
||||
if (actual[i] > minSupported[i]) {
|
||||
for (int i = 0; i < Math.Min(actual.Length, minSupported.Length); i++)
|
||||
{
|
||||
if (actual[i] > minSupported[i])
|
||||
{
|
||||
return true;
|
||||
} else if (minSupported[i] > actual[i]) {
|
||||
}
|
||||
else if (minSupported[i] > actual[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private int[] parseVersion(string version) {
|
||||
private int[] parseVersion(string version)
|
||||
{
|
||||
Match m = VERSION_PATTERN.Match(version);
|
||||
if (m.Success) {
|
||||
if (m.Success)
|
||||
{
|
||||
int[] v = new int[m.Groups.Count - 1];
|
||||
for (int i = 0; i < v.Length; i++) {
|
||||
for (int i = 0; i < v.Length; i++)
|
||||
{
|
||||
v[i] = int.Parse(m.Groups[i + 1].Value);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Invalid version format: " + version);
|
||||
}
|
||||
|
||||
public bool isSupportedType() {
|
||||
foreach (string t in typeNames) {
|
||||
if (t == TYPENAME_RECAST_GRAPH) {
|
||||
public bool isSupportedType()
|
||||
{
|
||||
foreach (string t in typeNames)
|
||||
{
|
||||
if (t == TYPENAME_RECAST_GRAPH)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -25,16 +25,14 @@ using System.Text.RegularExpressions;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Unity.Astar
|
||||
{
|
||||
|
||||
|
||||
public class MetaReader {
|
||||
|
||||
public Meta read(ZipArchive file, string filename) {
|
||||
public class MetaReader
|
||||
{
|
||||
public Meta read(ZipArchive file, string filename)
|
||||
{
|
||||
ZipArchiveEntry entry = file.GetEntry(filename);
|
||||
using StreamReader reader = new StreamReader(entry.Open());
|
||||
|
||||
|
||||
|
||||
var json = reader.ReadToEnd();
|
||||
|
||||
// fixed : version 표기는 문자열이여야 한다
|
||||
|
@ -44,15 +42,17 @@ public class MetaReader {
|
|||
json = regex.Replace(json, replacement);
|
||||
|
||||
var meta = JsonSerializer.Deserialize<Meta>(json);
|
||||
if (!meta.isSupportedType()) {
|
||||
if (!meta.isSupportedType())
|
||||
{
|
||||
throw new ArgumentException("Unsupported graph type " + string.Join(", ", meta.typeNames));
|
||||
}
|
||||
if (!meta.isSupportedVersion()) {
|
||||
|
||||
if (!meta.isSupportedVersion())
|
||||
{
|
||||
throw new ArgumentException("Unsupported version " + meta.version);
|
||||
}
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -21,22 +21,21 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Unity.Astar
|
||||
{
|
||||
|
||||
|
||||
class NodeIndexReader : ZipBinaryReader {
|
||||
|
||||
public int[] read(ZipArchive file, string filename) {
|
||||
class NodeIndexReader : ZipBinaryReader
|
||||
{
|
||||
public int[] read(ZipArchive file, string filename)
|
||||
{
|
||||
ByteBuffer buffer = toByteBuffer(file, filename);
|
||||
int maxNodeIndex = buffer.getInt();
|
||||
int[] int2Node = new int[maxNodeIndex + 1];
|
||||
int node = 0;
|
||||
while (buffer.remaining() > 0) {
|
||||
while (buffer.remaining() > 0)
|
||||
{
|
||||
int index = buffer.getInt();
|
||||
int2Node[index] = node++;
|
||||
}
|
||||
|
||||
return int2Node;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -15,25 +15,24 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.Extras.Unity.Astar
|
||||
{
|
||||
|
||||
|
||||
public class NodeLink2 {
|
||||
public class NodeLink2
|
||||
{
|
||||
public readonly long linkID;
|
||||
public readonly int startNode;
|
||||
public readonly int endNode;
|
||||
public readonly Vector3f clamped1;
|
||||
public readonly Vector3f clamped2;
|
||||
|
||||
public NodeLink2(long linkID, int startNode, int endNode, Vector3f clamped1, Vector3f clamped2) : base() {
|
||||
public NodeLink2(long linkID, int startNode, int endNode, Vector3f clamped1, Vector3f clamped2) : base()
|
||||
{
|
||||
this.linkID = linkID;
|
||||
this.startNode = startNode;
|
||||
this.endNode = endNode;
|
||||
this.clamped1 = clamped1;
|
||||
this.clamped2 = clamped2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -21,15 +21,15 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Unity.Astar
|
||||
{
|
||||
|
||||
|
||||
public class NodeLink2Reader : ZipBinaryReader {
|
||||
|
||||
public NodeLink2[] read(ZipArchive file, string filename, int[] indexToNode) {
|
||||
public class NodeLink2Reader : ZipBinaryReader
|
||||
{
|
||||
public NodeLink2[] read(ZipArchive file, string filename, int[] indexToNode)
|
||||
{
|
||||
ByteBuffer buffer = toByteBuffer(file, filename);
|
||||
int linkCount = buffer.getInt();
|
||||
NodeLink2[] links = new NodeLink2[linkCount];
|
||||
for (int i = 0; i < linkCount; i++) {
|
||||
for (int i = 0; i < linkCount; i++)
|
||||
{
|
||||
long linkID = buffer.getLong();
|
||||
int startNode = indexToNode[buffer.getInt()];
|
||||
int endNode = indexToNode[buffer.getInt()];
|
||||
|
@ -46,8 +46,8 @@ public class NodeLink2Reader : ZipBinaryReader {
|
|||
bool postScanCalled = buffer.get() != 0;
|
||||
links[i] = new NodeLink2(linkID, startNode, endNode, clamped1, clamped2);
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -20,18 +20,20 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Unity.Astar
|
||||
{
|
||||
|
||||
|
||||
public class OffMeshLinkCreator {
|
||||
|
||||
public void build(GraphMeshData graphData, NodeLink2[] links, int nodeOffset) {
|
||||
if (links.Length > 0) {
|
||||
foreach (NodeLink2 l in links) {
|
||||
public class OffMeshLinkCreator
|
||||
{
|
||||
public void build(GraphMeshData graphData, NodeLink2[] links, int nodeOffset)
|
||||
{
|
||||
if (links.Length > 0)
|
||||
{
|
||||
foreach (NodeLink2 l in links)
|
||||
{
|
||||
MeshData startTile = graphData.getTile(l.startNode - nodeOffset);
|
||||
Poly startNode = graphData.getNode(l.startNode - nodeOffset);
|
||||
MeshData endTile = graphData.getTile(l.endNode - nodeOffset);
|
||||
Poly endNode = graphData.getNode(l.endNode - nodeOffset);
|
||||
if (startNode != null && endNode != null) {
|
||||
if (startNode != null && endNode != null)
|
||||
{
|
||||
// FIXME: Optimise
|
||||
startTile.polys = ArrayUtils.CopyOf(startTile.polys, startTile.polys.Length + 1);
|
||||
int poly = startTile.header.polyCount;
|
||||
|
@ -44,18 +46,26 @@ public class OffMeshLinkCreator {
|
|||
startTile.header.vertCount += 2;
|
||||
OffMeshConnection connection = new OffMeshConnection();
|
||||
connection.poly = poly;
|
||||
connection.pos = new float[] { l.clamped1.x, l.clamped1.y, l.clamped1.z, l.clamped2.x, l.clamped2.y,
|
||||
l.clamped2.z };
|
||||
connection.pos = new float[]
|
||||
{
|
||||
l.clamped1.x, l.clamped1.y, l.clamped1.z, l.clamped2.x, l.clamped2.y,
|
||||
l.clamped2.z
|
||||
};
|
||||
connection.rad = 0.1f;
|
||||
connection.side = startTile == endTile ? 0xFF
|
||||
connection.side = startTile == endTile
|
||||
? 0xFF
|
||||
: NavMeshBuilder.classifyOffMeshPoint(new VectorPtr(connection.pos, 3),
|
||||
startTile.header.bmin, startTile.header.bmax);
|
||||
connection.userId = (int)l.linkID;
|
||||
if (startTile.offMeshCons == null) {
|
||||
if (startTile.offMeshCons == null)
|
||||
{
|
||||
startTile.offMeshCons = new OffMeshConnection[1];
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
startTile.offMeshCons = ArrayUtils.CopyOf(startTile.offMeshCons, startTile.offMeshCons.Length + 1);
|
||||
}
|
||||
|
||||
startTile.offMeshCons[startTile.offMeshCons.Length - 1] = connection;
|
||||
startTile.header.offMeshConCount++;
|
||||
}
|
||||
|
@ -63,5 +73,4 @@ public class OffMeshLinkCreator {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -22,34 +22,36 @@ using System.IO;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Unity.Astar
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Import navmeshes created with A* Pathfinding Project Unity plugin (https://arongranberg.com/astar/). Graph data is
|
||||
* loaded from a zip archive and converted to Recast navmesh objects.
|
||||
*/
|
||||
public class UnityAStarPathfindingImporter {
|
||||
|
||||
public class UnityAStarPathfindingImporter
|
||||
{
|
||||
private readonly UnityAStarPathfindingReader reader = new UnityAStarPathfindingReader();
|
||||
private readonly BVTreeCreator bvTreeCreator = new BVTreeCreator();
|
||||
private readonly LinkBuilder linkCreator = new LinkBuilder();
|
||||
private readonly OffMeshLinkCreator offMeshLinkCreator = new OffMeshLinkCreator();
|
||||
|
||||
public NavMesh[] load(FileStream zipFile) {
|
||||
public NavMesh[] load(FileStream zipFile)
|
||||
{
|
||||
GraphData graphData = reader.read(zipFile);
|
||||
Meta meta = graphData.meta;
|
||||
NodeLink2[] nodeLinks2 = graphData.nodeLinks2;
|
||||
NavMesh[] meshes = new NavMesh[meta.graphs];
|
||||
int nodeOffset = 0;
|
||||
for (int graphIndex = 0; graphIndex < meta.graphs; graphIndex++) {
|
||||
for (int graphIndex = 0; graphIndex < meta.graphs; graphIndex++)
|
||||
{
|
||||
GraphMeta graphMeta = graphData.graphMeta[graphIndex];
|
||||
GraphMeshData graphMeshData = graphData.graphMeshData[graphIndex];
|
||||
List<int[]> connections = graphData.graphConnections[graphIndex];
|
||||
int nodeCount = graphMeshData.countNodes();
|
||||
if (connections.Count != nodeCount) {
|
||||
if (connections.Count != nodeCount)
|
||||
{
|
||||
throw new ArgumentException("Inconsistent number of nodes in data file: " + nodeCount
|
||||
+ " and connecton files: " + connections.Count);
|
||||
}
|
||||
|
||||
// Build BV tree
|
||||
bvTreeCreator.build(graphMeshData);
|
||||
// Create links between nodes (both internal and portals between tiles)
|
||||
|
@ -65,15 +67,16 @@ public class UnityAStarPathfindingImporter {
|
|||
option.orig[1] = -0.5f * graphMeta.forcedBoundsSize.y + graphMeta.forcedBoundsCenter.y;
|
||||
option.orig[2] = -0.5f * graphMeta.forcedBoundsSize.z + graphMeta.forcedBoundsCenter.z;
|
||||
NavMesh mesh = new NavMesh(option, 3);
|
||||
foreach (MeshData t in graphMeshData.tiles) {
|
||||
foreach (MeshData t in graphMeshData.tiles)
|
||||
{
|
||||
mesh.addTile(t, 0, 0);
|
||||
}
|
||||
|
||||
meshes[graphIndex] = mesh;
|
||||
nodeOffset += graphMeshData.countNodes();
|
||||
}
|
||||
|
||||
return meshes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,10 +22,8 @@ using System.IO.Compression;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Unity.Astar
|
||||
{
|
||||
|
||||
|
||||
public class UnityAStarPathfindingReader {
|
||||
|
||||
public class UnityAStarPathfindingReader
|
||||
{
|
||||
private const string META_FILE_NAME = "meta.json";
|
||||
private const string NODE_INDEX_FILE_NAME = "graph_references.binary";
|
||||
private const string NODE_LINK_2_FILE_NAME = "node_link2.binary";
|
||||
|
@ -40,7 +38,8 @@ public class UnityAStarPathfindingReader {
|
|||
private readonly GraphConnectionReader graphConnectionReader = new GraphConnectionReader();
|
||||
private readonly NodeLink2Reader nodeLink2Reader = new NodeLink2Reader();
|
||||
|
||||
public GraphData read(FileStream zipFile) {
|
||||
public GraphData read(FileStream zipFile)
|
||||
{
|
||||
using ZipArchive file = new ZipArchive(zipFile);
|
||||
// Read meta file and check version and graph type
|
||||
Meta meta = metaReader.read(file, META_FILE_NAME);
|
||||
|
@ -52,7 +51,8 @@ public class UnityAStarPathfindingReader {
|
|||
List<GraphMeta> metaList = new List<GraphMeta>();
|
||||
List<GraphMeshData> meshDataList = new List<GraphMeshData>();
|
||||
List<List<int[]>> connectionsList = new List<List<int[]>>();
|
||||
for (int graphIndex = 0; graphIndex < meta.graphs; graphIndex++) {
|
||||
for (int graphIndex = 0; graphIndex < meta.graphs; graphIndex++)
|
||||
{
|
||||
GraphMeta graphMeta = graphMetaReader.read(file, string.Format(GRAPH_META_FILE_NAME_PATTERN, graphIndex));
|
||||
// First graph mesh data - vertices and polygons
|
||||
GraphMeshData graphData = graphDataReader.read(file,
|
||||
|
@ -64,8 +64,8 @@ public class UnityAStarPathfindingReader {
|
|||
meshDataList.Add(graphData);
|
||||
connectionsList.Add(connections);
|
||||
}
|
||||
|
||||
return new GraphData(meta, indexToNode, nodeLinks2, metaList, meshDataList, connectionsList);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -23,11 +23,10 @@ using DotRecast.Detour.Io;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Unity.Astar
|
||||
{
|
||||
|
||||
|
||||
public abstract class ZipBinaryReader {
|
||||
|
||||
protected ByteBuffer toByteBuffer(ZipArchive file, string filename) {
|
||||
public abstract class ZipBinaryReader
|
||||
{
|
||||
protected ByteBuffer toByteBuffer(ZipArchive file, string filename)
|
||||
{
|
||||
ZipArchiveEntry graphReferences = file.GetEntry(filename);
|
||||
using var entryStream = graphReferences.Open();
|
||||
using var bis = new BinaryReader(entryStream);
|
||||
|
@ -35,7 +34,5 @@ public abstract class ZipBinaryReader {
|
|||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -15,25 +15,24 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.Extras
|
||||
{
|
||||
|
||||
|
||||
public class Vector3f {
|
||||
|
||||
public class Vector3f
|
||||
{
|
||||
public float x { get; set; }
|
||||
public float y { get; set; }
|
||||
public float z { get; set; }
|
||||
|
||||
public Vector3f() {
|
||||
public Vector3f()
|
||||
{
|
||||
}
|
||||
|
||||
public Vector3f(float x, float y, float z) {
|
||||
public Vector3f(float x, float y, float z)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -27,34 +27,43 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Detour.TileCache
|
||||
{
|
||||
|
||||
|
||||
public abstract class AbstractTileLayersBuilder {
|
||||
|
||||
protected List<byte[]> build(ByteOrder order, bool cCompatibility, int threads, int tw, int th) {
|
||||
if (threads == 1) {
|
||||
public abstract class AbstractTileLayersBuilder
|
||||
{
|
||||
protected List<byte[]> build(ByteOrder order, bool cCompatibility, int threads, int tw, int th)
|
||||
{
|
||||
if (threads == 1)
|
||||
{
|
||||
return buildSingleThread(order, cCompatibility, tw, th);
|
||||
}
|
||||
|
||||
return buildMultiThread(order, cCompatibility, tw, th, threads);
|
||||
}
|
||||
|
||||
private List<byte[]> buildSingleThread(ByteOrder order, bool cCompatibility, int tw, int th) {
|
||||
private List<byte[]> buildSingleThread(ByteOrder order, bool cCompatibility, int tw, int th)
|
||||
{
|
||||
List<byte[]> layers = new List<byte[]>();
|
||||
for (int y = 0; y < th; ++y) {
|
||||
for (int x = 0; x < tw; ++x) {
|
||||
for (int y = 0; y < th; ++y)
|
||||
{
|
||||
for (int x = 0; x < tw; ++x)
|
||||
{
|
||||
layers.AddRange(build(x, y, order, cCompatibility));
|
||||
}
|
||||
}
|
||||
|
||||
return layers;
|
||||
}
|
||||
|
||||
private List<byte[]> buildMultiThread(ByteOrder order, bool cCompatibility, int tw, int th, int threads) {
|
||||
private List<byte[]> buildMultiThread(ByteOrder order, bool cCompatibility, int tw, int th, int threads)
|
||||
{
|
||||
var tasks = new ConcurrentQueue<Task<Tuple<int, int, List<byte[]>>>>();
|
||||
for (int y = 0; y < th; ++y) {
|
||||
for (int x = 0; x < tw; ++x) {
|
||||
for (int y = 0; y < th; ++y)
|
||||
{
|
||||
for (int x = 0; x < tw; ++x)
|
||||
{
|
||||
int tx = x;
|
||||
int ty = y;
|
||||
var task = Task.Run(() => {
|
||||
var task = Task.Run(() =>
|
||||
{
|
||||
var partial = build(tx, ty, order, cCompatibility);
|
||||
return Tuple.Create(tx, ty, partial);
|
||||
});
|
||||
|
@ -67,16 +76,18 @@ public abstract class AbstractTileLayersBuilder {
|
|||
.ToDictionary(x => Tuple.Create(x.Item1, x.Item2), x => x.Item3);
|
||||
|
||||
List<byte[]> layers = new List<byte[]>();
|
||||
for (int y = 0; y < th; ++y) {
|
||||
for (int x = 0; x < tw; ++x) {
|
||||
for (int y = 0; y < th; ++y)
|
||||
{
|
||||
for (int x = 0; x < tw; ++x)
|
||||
{
|
||||
var key = Tuple.Create(x, y);
|
||||
layers.AddRange(partialResults[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return layers;
|
||||
}
|
||||
|
||||
protected abstract List<byte[]> build(int tx, int ty, ByteOrder order, bool cCompatibility);
|
||||
}
|
||||
|
||||
}
|
|
@ -17,23 +17,26 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.TileCache
|
||||
{
|
||||
|
||||
|
||||
public class CompressedTile {
|
||||
public class CompressedTile
|
||||
{
|
||||
public readonly int index;
|
||||
public int salt; /// < Counter describing modifications to the tile.
|
||||
public int salt;
|
||||
|
||||
/// < Counter describing modifications to the tile.
|
||||
public TileCacheLayerHeader header;
|
||||
|
||||
public byte[] data;
|
||||
public int compressed; // offset of compressed data
|
||||
public int flags;
|
||||
public CompressedTile next;
|
||||
|
||||
public CompressedTile(int index) {
|
||||
public CompressedTile(int index)
|
||||
{
|
||||
this.index = index;
|
||||
salt = 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -18,8 +18,6 @@ using System;
|
|||
|
||||
namespace DotRecast.Detour.TileCache.Io.Compress
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Core of FastLZ compression algorithm.
|
||||
*
|
||||
|
@ -29,8 +27,8 @@ namespace DotRecast.Detour.TileCache.Io.Compress
|
|||
* This is refactored code of <a href="https://code.google.com/p/jfastlz/">jfastlz</a>
|
||||
* library written by William Kinney.
|
||||
*/
|
||||
public class FastLz {
|
||||
|
||||
public class FastLz
|
||||
{
|
||||
private static readonly int MAX_DISTANCE = 8191;
|
||||
private static readonly int MAX_FARDISTANCE = 65535 + MAX_DISTANCE - 1;
|
||||
|
||||
|
@ -84,7 +82,8 @@ public class FastLz {
|
|||
* @param inputLength length of input buffer
|
||||
* @return Maximum output buffer length
|
||||
*/
|
||||
public static int calculateOutputBufferLength(int inputLength) {
|
||||
public static int calculateOutputBufferLength(int inputLength)
|
||||
{
|
||||
int tempOutputLength = (int)(inputLength * 1.06);
|
||||
return Math.Max(tempOutputLength, 66);
|
||||
}
|
||||
|
@ -96,11 +95,15 @@ public class FastLz {
|
|||
* If the input is not compressible, the return value might be larger than length (input buffer size).
|
||||
*/
|
||||
public static int compress(byte[] input, int inOffset, int inLength,
|
||||
byte[] output, int outOffset, int proposedLevel) {
|
||||
byte[] output, int outOffset, int proposedLevel)
|
||||
{
|
||||
int level;
|
||||
if (proposedLevel == LEVEL_AUTO) {
|
||||
if (proposedLevel == LEVEL_AUTO)
|
||||
{
|
||||
level = inLength < MIN_RECOMENDED_LENGTH_FOR_LEVEL_2 ? LEVEL_1 : LEVEL_2;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
level = proposedLevel;
|
||||
}
|
||||
|
||||
|
@ -122,23 +125,29 @@ public class FastLz {
|
|||
int copy;
|
||||
|
||||
/* sanity check */
|
||||
if (inLength < 4) {
|
||||
if (inLength != 0) {
|
||||
if (inLength < 4)
|
||||
{
|
||||
if (inLength != 0)
|
||||
{
|
||||
// *op++ = length-1;
|
||||
output[outOffset + op++] = (byte)(inLength - 1);
|
||||
ipBound++;
|
||||
while (ip <= ipBound) {
|
||||
while (ip <= ipBound)
|
||||
{
|
||||
output[outOffset + op++] = input[inOffset + ip++];
|
||||
}
|
||||
|
||||
return inLength + 1;
|
||||
}
|
||||
|
||||
// else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* initializes hash table */
|
||||
// for (hslot = htab; hslot < htab + HASH_SIZE; hslot++)
|
||||
for (hslot = 0; hslot < HASH_SIZE; hslot++) {
|
||||
for (hslot = 0; hslot < HASH_SIZE; hslot++)
|
||||
{
|
||||
//*hslot = ip;
|
||||
htab[hslot] = ip;
|
||||
}
|
||||
|
@ -150,7 +159,8 @@ public class FastLz {
|
|||
output[outOffset + op++] = input[inOffset + ip++];
|
||||
|
||||
/* main loop */
|
||||
while (ip < ipLimit) {
|
||||
while (ip < ipLimit)
|
||||
{
|
||||
int refs = 0;
|
||||
|
||||
long distance = 0;
|
||||
|
@ -166,10 +176,12 @@ public class FastLz {
|
|||
bool matchLabel = false;
|
||||
|
||||
/* check for a run */
|
||||
if (level == LEVEL_2) {
|
||||
if (level == LEVEL_2)
|
||||
{
|
||||
//if(ip[0] == ip[-1] && FASTLZ_READU16(ip-1)==FASTLZ_READU16(ip+1))
|
||||
if (input[inOffset + ip] == input[inOffset + ip - 1] &&
|
||||
readU16(input, inOffset + ip - 1) == readU16(input, inOffset + ip + 1)) {
|
||||
readU16(input, inOffset + ip - 1) == readU16(input, inOffset + ip + 1))
|
||||
{
|
||||
distance = 1;
|
||||
ip += 3;
|
||||
refs = anchor - 1 + 3;
|
||||
|
@ -180,7 +192,9 @@ public class FastLz {
|
|||
matchLabel = true;
|
||||
}
|
||||
}
|
||||
if (!matchLabel) {
|
||||
|
||||
if (!matchLabel)
|
||||
{
|
||||
/* find potential match */
|
||||
// HASH_FUNCTION(hval,ip);
|
||||
hval = hashFunction(input, inOffset + ip);
|
||||
|
@ -201,41 +215,51 @@ public class FastLz {
|
|||
|| (level == LEVEL_1 ? distance >= MAX_DISTANCE : distance >= MAX_FARDISTANCE)
|
||||
|| input[inOffset + refs++] != input[inOffset + ip++]
|
||||
|| input[inOffset + refs++] != input[inOffset + ip++]
|
||||
|| input[inOffset + refs++] != input[inOffset + ip++]) {
|
||||
|| input[inOffset + refs++] != input[inOffset + ip++])
|
||||
{
|
||||
/*
|
||||
* goto literal;
|
||||
*/
|
||||
output[outOffset + op++] = input[inOffset + anchor++];
|
||||
ip = anchor;
|
||||
copy++;
|
||||
if (copy == MAX_COPY) {
|
||||
if (copy == MAX_COPY)
|
||||
{
|
||||
copy = 0;
|
||||
output[outOffset + op++] = (byte)(MAX_COPY - 1);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (level == LEVEL_2) {
|
||||
if (level == LEVEL_2)
|
||||
{
|
||||
/* far, needs at least 5-byte match */
|
||||
if (distance >= MAX_DISTANCE) {
|
||||
if (distance >= MAX_DISTANCE)
|
||||
{
|
||||
if (input[inOffset + ip++] != input[inOffset + refs++]
|
||||
|| input[inOffset + ip++] != input[inOffset + refs++]) {
|
||||
|| input[inOffset + ip++] != input[inOffset + refs++])
|
||||
{
|
||||
/*
|
||||
* goto literal;
|
||||
*/
|
||||
output[outOffset + op++] = input[inOffset + anchor++];
|
||||
ip = anchor;
|
||||
copy++;
|
||||
if (copy == MAX_COPY) {
|
||||
if (copy == MAX_COPY)
|
||||
{
|
||||
copy = 0;
|
||||
output[outOffset + op++] = (byte)(MAX_COPY - 1);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
len += 2;
|
||||
}
|
||||
}
|
||||
} // end if(!matchLabel)
|
||||
|
||||
/*
|
||||
* match:
|
||||
*/
|
||||
|
@ -245,59 +269,89 @@ public class FastLz {
|
|||
/* distance is biased */
|
||||
distance--;
|
||||
|
||||
if (distance == 0) {
|
||||
if (distance == 0)
|
||||
{
|
||||
/* zero distance means a run */
|
||||
//flzuint8 x = ip[-1];
|
||||
byte x = input[inOffset + ip - 1];
|
||||
while (ip < ipBound) {
|
||||
if (input[inOffset + refs++] != x) {
|
||||
while (ip < ipBound)
|
||||
{
|
||||
if (input[inOffset + refs++] != x)
|
||||
{
|
||||
break;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
ip++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (;;) {
|
||||
}
|
||||
else
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
/* safe because the outer check against ip limit */
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++]) {
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++])
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++]) {
|
||||
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++])
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++]) {
|
||||
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++])
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++]) {
|
||||
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++])
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++]) {
|
||||
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++])
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++]) {
|
||||
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++])
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++]) {
|
||||
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++])
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++]) {
|
||||
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++])
|
||||
{
|
||||
break;
|
||||
}
|
||||
while (ip < ipBound) {
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++]) {
|
||||
|
||||
while (ip < ipBound)
|
||||
{
|
||||
if (input[inOffset + refs++] != input[inOffset + ip++])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* if we have copied something, adjust the copy count */
|
||||
if (copy != 0) {
|
||||
if (copy != 0)
|
||||
{
|
||||
/* copy is biased, '0' means 1 byte copy */
|
||||
// *(op-copy-1) = copy-1;
|
||||
output[outOffset + op - copy - 1] = (byte)(copy - 1);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* back, to overwrite the copy count */
|
||||
op--;
|
||||
}
|
||||
|
@ -310,42 +364,60 @@ public class FastLz {
|
|||
len = ip - anchor;
|
||||
|
||||
/* encode the match */
|
||||
if (level == LEVEL_2) {
|
||||
if (distance < MAX_DISTANCE) {
|
||||
if (len < 7) {
|
||||
if (level == LEVEL_2)
|
||||
{
|
||||
if (distance < MAX_DISTANCE)
|
||||
{
|
||||
if (len < 7)
|
||||
{
|
||||
output[outOffset + op++] = (byte)((len << 5) + (int)((ulong)distance >> 8));
|
||||
output[outOffset + op++] = (byte)(distance & 255);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
output[outOffset + op++] = (byte)((7 << 5) + ((ulong)distance >> 8));
|
||||
for (len -= 7; len >= 255; len -= 255) {
|
||||
for (len -= 7; len >= 255; len -= 255)
|
||||
{
|
||||
output[outOffset + op++] = (byte)255;
|
||||
}
|
||||
|
||||
output[outOffset + op++] = (byte)len;
|
||||
output[outOffset + op++] = (byte)(distance & 255);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* far away, but not yet in the another galaxy... */
|
||||
if (len < 7) {
|
||||
if (len < 7)
|
||||
{
|
||||
distance -= MAX_DISTANCE;
|
||||
output[outOffset + op++] = (byte)((len << 5) + 31);
|
||||
output[outOffset + op++] = (byte)255;
|
||||
output[outOffset + op++] = (byte)((ulong)distance >> 8);
|
||||
output[outOffset + op++] = (byte)(distance & 255);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
distance -= MAX_DISTANCE;
|
||||
output[outOffset + op++] = (byte)((7 << 5) + 31);
|
||||
for (len -= 7; len >= 255; len -= 255) {
|
||||
for (len -= 7; len >= 255; len -= 255)
|
||||
{
|
||||
output[outOffset + op++] = (byte)255;
|
||||
}
|
||||
|
||||
output[outOffset + op++] = (byte)len;
|
||||
output[outOffset + op++] = (byte)255;
|
||||
output[outOffset + op++] = (byte)((ulong)distance >> 8);
|
||||
output[outOffset + op++] = (byte)(distance & 255);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (len > MAX_LEN - 2) {
|
||||
while (len > MAX_LEN - 2) {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (len > MAX_LEN - 2)
|
||||
{
|
||||
while (len > MAX_LEN - 2)
|
||||
{
|
||||
output[outOffset + op++] = (byte)((7 << 5) + ((ulong)distance >> 8));
|
||||
output[outOffset + op++] = (byte)(MAX_LEN - 2 - 7 - 2);
|
||||
output[outOffset + op++] = (byte)(distance & 255);
|
||||
|
@ -353,10 +425,13 @@ public class FastLz {
|
|||
}
|
||||
}
|
||||
|
||||
if (len < 7) {
|
||||
if (len < 7)
|
||||
{
|
||||
output[outOffset + op++] = (byte)((len << 5) + (int)((ulong)distance >> 8));
|
||||
output[outOffset + op++] = (byte)(distance & 255);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
output[outOffset + op++] = (byte)((7 << 5) + (int)((ulong)distance >> 8));
|
||||
output[outOffset + op++] = (byte)(len - 7);
|
||||
output[outOffset + op++] = (byte)(distance & 255);
|
||||
|
@ -393,24 +468,30 @@ public class FastLz {
|
|||
|
||||
/* left-over as literal copy */
|
||||
ipBound++;
|
||||
while (ip <= ipBound) {
|
||||
while (ip <= ipBound)
|
||||
{
|
||||
output[outOffset + op++] = input[inOffset + ip++];
|
||||
copy++;
|
||||
if (copy == MAX_COPY) {
|
||||
if (copy == MAX_COPY)
|
||||
{
|
||||
copy = 0;
|
||||
output[outOffset + op++] = (byte)(MAX_COPY - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* if we have copied something, adjust the copy length */
|
||||
if (copy != 0) {
|
||||
if (copy != 0)
|
||||
{
|
||||
//*(op-copy-1) = copy-1;
|
||||
output[outOffset + op - copy - 1] = (byte)(copy - 1);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
op--;
|
||||
}
|
||||
|
||||
if (level == LEVEL_2) {
|
||||
if (level == LEVEL_2)
|
||||
{
|
||||
/* marker for fastlz2 */
|
||||
output[outOffset] |= 1 << 5;
|
||||
}
|
||||
|
@ -427,10 +508,12 @@ public class FastLz {
|
|||
* more than what is specified in outLength.
|
||||
*/
|
||||
public static int decompress(byte[] input, int inOffset, int inLength,
|
||||
byte[] output, int outOffset, int outLength) {
|
||||
byte[] output, int outOffset, int outLength)
|
||||
{
|
||||
//int level = ((*(const flzuint8*)input) >> 5) + 1;
|
||||
int level = (input[inOffset] >> 5) + 1;
|
||||
if (level != LEVEL_1 && level != LEVEL_2) {
|
||||
if (level != LEVEL_1 && level != LEVEL_2)
|
||||
{
|
||||
throw new Exception($"invalid level: {level} (expected: {LEVEL_1} or {LEVEL_2})");
|
||||
}
|
||||
|
||||
|
@ -442,7 +525,8 @@ public class FastLz {
|
|||
long ctrl = input[inOffset + ip++] & 31;
|
||||
|
||||
int loop = 1;
|
||||
do {
|
||||
do
|
||||
{
|
||||
// const flzuint8* refs = op;
|
||||
int refs = op;
|
||||
// flzuint32 len = ctrl >> 5;
|
||||
|
@ -450,34 +534,45 @@ public class FastLz {
|
|||
// flzuint32 ofs = (ctrl & 31) << 8;
|
||||
long ofs = (ctrl & 31) << 8;
|
||||
|
||||
if (ctrl >= 32) {
|
||||
if (ctrl >= 32)
|
||||
{
|
||||
len--;
|
||||
// refs -= ofs;
|
||||
refs -= (int)ofs;
|
||||
|
||||
int code;
|
||||
if (len == 6) {
|
||||
if (level == LEVEL_1) {
|
||||
if (len == 6)
|
||||
{
|
||||
if (level == LEVEL_1)
|
||||
{
|
||||
// len += *ip++;
|
||||
len += input[inOffset + ip++] & 0xFF;
|
||||
} else {
|
||||
do {
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
code = input[inOffset + ip++] & 0xFF;
|
||||
len += code;
|
||||
} while (code == 255);
|
||||
}
|
||||
}
|
||||
if (level == LEVEL_1) {
|
||||
|
||||
if (level == LEVEL_1)
|
||||
{
|
||||
// refs -= *ip++;
|
||||
refs -= input[inOffset + ip++] & 0xFF;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
code = input[inOffset + ip++] & 0xFF;
|
||||
refs -= code;
|
||||
|
||||
/* match from 16-bit distance */
|
||||
// if(FASTLZ_UNEXPECT_CONDITIONAL(code==255))
|
||||
// if(FASTLZ_EXPECT_CONDITIONAL(ofs==(31 << 8)))
|
||||
if (code == 255 && ofs == 31 << 8) {
|
||||
if (code == 255 && ofs == 31 << 8)
|
||||
{
|
||||
ofs = (input[inOffset + ip++] & 0xFF) << 8;
|
||||
ofs += input[inOffset + ip++] & 0xFF;
|
||||
|
||||
|
@ -486,35 +581,44 @@ public class FastLz {
|
|||
}
|
||||
|
||||
// if the output index + length of block(?) + 3(?) is over the output limit?
|
||||
if (op + len + 3 > outLength) {
|
||||
if (op + len + 3 > outLength)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// if (FASTLZ_UNEXPECT_CONDITIONAL(refs-1 < (flzuint8 *)output))
|
||||
// if the address space of refs-1 is < the address of output?
|
||||
// if we are still at the beginning of the output address?
|
||||
if (refs - 1 < 0) {
|
||||
if (refs - 1 < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ip < inLength) {
|
||||
if (ip < inLength)
|
||||
{
|
||||
ctrl = input[inOffset + ip++] & 0xFF;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
loop = 0;
|
||||
}
|
||||
|
||||
if (refs == op) {
|
||||
if (refs == op)
|
||||
{
|
||||
/* optimize copy for a run */
|
||||
// flzuint8 b = refs[-1];
|
||||
byte b = output[outOffset + refs - 1];
|
||||
output[outOffset + op++] = b;
|
||||
output[outOffset + op++] = b;
|
||||
output[outOffset + op++] = b;
|
||||
while (len != 0) {
|
||||
while (len != 0)
|
||||
{
|
||||
output[outOffset + op++] = b;
|
||||
--len;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
/* copy from reference */
|
||||
refs--;
|
||||
|
||||
|
@ -523,31 +627,39 @@ public class FastLz {
|
|||
output[outOffset + op++] = output[outOffset + refs++];
|
||||
output[outOffset + op++] = output[outOffset + refs++];
|
||||
|
||||
while (len != 0) {
|
||||
while (len != 0)
|
||||
{
|
||||
output[outOffset + op++] = output[outOffset + refs++];
|
||||
--len;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
ctrl++;
|
||||
|
||||
if (op + ctrl > outLength) {
|
||||
if (op + ctrl > outLength)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (ip + ctrl > inLength) {
|
||||
|
||||
if (ip + ctrl > inLength)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
//*op++ = *ip++;
|
||||
output[outOffset + op++] = input[inOffset + ip++];
|
||||
|
||||
for (--ctrl; ctrl != 0; ctrl--) {
|
||||
for (--ctrl; ctrl != 0; ctrl--)
|
||||
{
|
||||
// *op++ = *ip++;
|
||||
output[outOffset + op++] = input[inOffset + ip++];
|
||||
}
|
||||
|
||||
loop = ip < inLength ? 1 : 0;
|
||||
if (loop != 0) {
|
||||
if (loop != 0)
|
||||
{
|
||||
// ctrl = *ip++;
|
||||
ctrl = input[inOffset + ip++] & 0xFF;
|
||||
}
|
||||
|
@ -560,20 +672,26 @@ public class FastLz {
|
|||
return op;
|
||||
}
|
||||
|
||||
private static int hashFunction(byte[] p, int offset) {
|
||||
private static int hashFunction(byte[] p, int offset)
|
||||
{
|
||||
int v = readU16(p, offset);
|
||||
v ^= readU16(p, offset + 1) ^ v >> 16 - HASH_LOG;
|
||||
v &= HASH_MASK;
|
||||
return v;
|
||||
}
|
||||
|
||||
private static int readU16(byte[] data, int offset) {
|
||||
if (offset + 1 >= data.Length) {
|
||||
private static int readU16(byte[] data, int offset)
|
||||
{
|
||||
if (offset + 1 >= data.Length)
|
||||
{
|
||||
return data[offset] & 0xff;
|
||||
}
|
||||
|
||||
return (data[offset + 1] & 0xff) << 8 | data[offset] & 0xff;
|
||||
}
|
||||
|
||||
private FastLz() { }
|
||||
private FastLz()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,22 +23,20 @@ using K4os.Compression.LZ4;
|
|||
|
||||
namespace DotRecast.Detour.TileCache.Io.Compress
|
||||
{
|
||||
|
||||
|
||||
public class FastLzTileCacheCompressor : TileCacheCompressor {
|
||||
|
||||
public byte[] decompress(byte[] buf, int offset, int len, int outputlen) {
|
||||
public class FastLzTileCacheCompressor : TileCacheCompressor
|
||||
{
|
||||
public byte[] decompress(byte[] buf, int offset, int len, int outputlen)
|
||||
{
|
||||
byte[] output = new byte[outputlen];
|
||||
FastLz.decompress(buf, offset, len, output, 0, outputlen);
|
||||
return output;
|
||||
}
|
||||
|
||||
public byte[] compress(byte[] buf) {
|
||||
public byte[] compress(byte[] buf)
|
||||
{
|
||||
byte[] output = new byte[FastLz.calculateOutputBufferLength(buf.Length)];
|
||||
int len = FastLz.compress(buf, 0, buf.Length, output, 0, output.Length);
|
||||
return ArrayUtils.CopyOf(output, len);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,18 +22,16 @@ using K4os.Compression.LZ4;
|
|||
|
||||
namespace DotRecast.Detour.TileCache.Io.Compress
|
||||
{
|
||||
|
||||
|
||||
public class LZ4TileCacheCompressor : TileCacheCompressor {
|
||||
|
||||
public byte[] decompress(byte[] buf, int offset, int len, int outputlen) {
|
||||
public class LZ4TileCacheCompressor : TileCacheCompressor
|
||||
{
|
||||
public byte[] decompress(byte[] buf, int offset, int len, int outputlen)
|
||||
{
|
||||
return LZ4Pickler.Unpickle(buf, offset, len);
|
||||
}
|
||||
|
||||
public byte[] compress(byte[] buf) {
|
||||
public byte[] compress(byte[] buf)
|
||||
{
|
||||
return LZ4Pickler.Pickle(buf);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,14 +17,11 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.TileCache.Io.Compress
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
public class TileCacheCompressorFactory {
|
||||
|
||||
public class TileCacheCompressorFactory
|
||||
{
|
||||
public static TileCacheCompressor get(bool cCompatibility)
|
||||
{
|
||||
if (cCompatibility)
|
||||
|
@ -33,5 +30,4 @@ public class TileCacheCompressorFactory {
|
|||
return new LZ4TileCacheCompressor();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -23,11 +23,10 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Detour.TileCache.Io
|
||||
{
|
||||
|
||||
|
||||
public class TileCacheLayerHeaderReader {
|
||||
|
||||
public TileCacheLayerHeader read(ByteBuffer data, bool cCompatibility) {
|
||||
public class TileCacheLayerHeaderReader
|
||||
{
|
||||
public TileCacheLayerHeader read(ByteBuffer data, bool cCompatibility)
|
||||
{
|
||||
TileCacheLayerHeader header = new TileCacheLayerHeader();
|
||||
header.magic = data.getInt();
|
||||
header.version = data.getInt();
|
||||
|
@ -40,12 +39,16 @@ public class TileCacheLayerHeaderReader {
|
|||
header.tx = data.getInt();
|
||||
header.ty = data.getInt();
|
||||
header.tlayer = data.getInt();
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
header.bmin[j] = data.getFloat();
|
||||
}
|
||||
for (int j = 0; j < 3; j++) {
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
header.bmax[j] = data.getFloat();
|
||||
}
|
||||
|
||||
header.hmin = data.getShort() & 0xFFFF;
|
||||
header.hmax = data.getShort() & 0xFFFF;
|
||||
header.width = data.get() & 0xFF;
|
||||
|
@ -54,12 +57,12 @@ public class TileCacheLayerHeaderReader {
|
|||
header.maxx = data.get() & 0xFF;
|
||||
header.miny = data.get() & 0xFF;
|
||||
header.maxy = data.get() & 0xFF;
|
||||
if (cCompatibility) {
|
||||
if (cCompatibility)
|
||||
{
|
||||
data.getShort(); // C struct padding
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -24,22 +24,25 @@ using DotRecast.Detour.Io;
|
|||
|
||||
namespace DotRecast.Detour.TileCache.Io
|
||||
{
|
||||
|
||||
|
||||
public class TileCacheLayerHeaderWriter : DetourWriter {
|
||||
|
||||
public void write(BinaryWriter stream, TileCacheLayerHeader header, ByteOrder order, bool cCompatibility) {
|
||||
public class TileCacheLayerHeaderWriter : DetourWriter
|
||||
{
|
||||
public void write(BinaryWriter stream, TileCacheLayerHeader header, ByteOrder order, bool cCompatibility)
|
||||
{
|
||||
write(stream, header.magic, order);
|
||||
write(stream, header.version, order);
|
||||
write(stream, header.tx, order);
|
||||
write(stream, header.ty, order);
|
||||
write(stream, header.tlayer, order);
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
write(stream, header.bmin[j], order);
|
||||
}
|
||||
for (int j = 0; j < 3; j++) {
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
write(stream, header.bmax[j], order);
|
||||
}
|
||||
|
||||
write(stream, (short)header.hmin, order);
|
||||
write(stream, (short)header.hmax, order);
|
||||
write(stream, (byte)header.width);
|
||||
|
@ -48,11 +51,10 @@ public class TileCacheLayerHeaderWriter : DetourWriter {
|
|||
write(stream, (byte)header.maxx);
|
||||
write(stream, (byte)header.miny);
|
||||
write(stream, (byte)header.maxy);
|
||||
if (cCompatibility) {
|
||||
if (cCompatibility)
|
||||
{
|
||||
write(stream, (short)0, order); // C struct padding
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -25,33 +25,40 @@ using DotRecast.Detour.TileCache.Io.Compress;
|
|||
|
||||
namespace DotRecast.Detour.TileCache.Io
|
||||
{
|
||||
|
||||
|
||||
public class TileCacheReader {
|
||||
|
||||
public class TileCacheReader
|
||||
{
|
||||
private readonly NavMeshParamReader paramReader = new NavMeshParamReader();
|
||||
|
||||
public TileCache read(BinaryReader @is, int maxVertPerPoly, TileCacheMeshProcess meshProcessor) {
|
||||
public TileCache read(BinaryReader @is, int maxVertPerPoly, TileCacheMeshProcess meshProcessor)
|
||||
{
|
||||
ByteBuffer bb = IOUtils.toByteBuffer(@is);
|
||||
return read(bb, maxVertPerPoly, meshProcessor);
|
||||
}
|
||||
|
||||
public TileCache read(ByteBuffer bb, int maxVertPerPoly, TileCacheMeshProcess meshProcessor) {
|
||||
public TileCache read(ByteBuffer bb, int maxVertPerPoly, TileCacheMeshProcess meshProcessor)
|
||||
{
|
||||
TileCacheSetHeader header = new TileCacheSetHeader();
|
||||
header.magic = bb.getInt();
|
||||
if (header.magic != TileCacheSetHeader.TILECACHESET_MAGIC) {
|
||||
if (header.magic != TileCacheSetHeader.TILECACHESET_MAGIC)
|
||||
{
|
||||
header.magic = IOUtils.swapEndianness(header.magic);
|
||||
if (header.magic != TileCacheSetHeader.TILECACHESET_MAGIC) {
|
||||
if (header.magic != TileCacheSetHeader.TILECACHESET_MAGIC)
|
||||
{
|
||||
throw new IOException("Invalid magic");
|
||||
}
|
||||
|
||||
bb.order(bb.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
|
||||
header.version = bb.getInt();
|
||||
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION) {
|
||||
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION_RECAST4J) {
|
||||
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION)
|
||||
{
|
||||
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION_RECAST4J)
|
||||
{
|
||||
throw new IOException("Invalid version");
|
||||
}
|
||||
}
|
||||
|
||||
bool cCompatibility = header.version == TileCacheSetHeader.TILECACHESET_VERSION;
|
||||
header.numTiles = bb.getInt();
|
||||
header.meshParams = paramReader.read(bb);
|
||||
|
@ -61,27 +68,34 @@ public class TileCacheReader {
|
|||
TileCache tc = new TileCache(header.cacheParams, new TileCacheStorageParams(bb.order(), cCompatibility), mesh,
|
||||
compressor, meshProcessor);
|
||||
// Read tiles.
|
||||
for (int i = 0; i < header.numTiles; ++i) {
|
||||
for (int i = 0; i < header.numTiles; ++i)
|
||||
{
|
||||
long tileRef = bb.getInt();
|
||||
int dataSize = bb.getInt();
|
||||
if (tileRef == 0 || dataSize == 0) {
|
||||
if (tileRef == 0 || dataSize == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
byte[] data = bb.ReadBytes(dataSize).ToArray();
|
||||
long tile = tc.addTile(data, 0);
|
||||
if (tile != 0) {
|
||||
if (tile != 0)
|
||||
{
|
||||
tc.buildNavMeshTile(tile);
|
||||
}
|
||||
}
|
||||
|
||||
return tc;
|
||||
}
|
||||
|
||||
private TileCacheParams readCacheParams(ByteBuffer bb, bool cCompatibility) {
|
||||
private TileCacheParams readCacheParams(ByteBuffer bb, bool cCompatibility)
|
||||
{
|
||||
TileCacheParams option = new TileCacheParams();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
option.orig[i] = bb.getFloat();
|
||||
}
|
||||
|
||||
option.cs = bb.getFloat();
|
||||
option.ch = bb.getFloat();
|
||||
option.width = bb.getInt();
|
||||
|
@ -95,5 +109,4 @@ public class TileCacheReader {
|
|||
return option;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -17,12 +17,11 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.TileCache.Io
|
||||
{
|
||||
|
||||
|
||||
public class TileCacheSetHeader {
|
||||
|
||||
public class TileCacheSetHeader
|
||||
{
|
||||
public const int TILECACHESET_MAGIC = 'T' << 24 | 'S' << 16 | 'E' << 8 | 'T'; // 'TSET';
|
||||
public const int TILECACHESET_VERSION = 1;
|
||||
public const int TILECACHESET_VERSION_RECAST4J = 0x8801;
|
||||
|
@ -32,7 +31,5 @@ public class TileCacheSetHeader {
|
|||
public int numTiles;
|
||||
public NavMeshParams meshParams = new NavMeshParams();
|
||||
public TileCacheParams cacheParams = new TileCacheParams();
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -24,28 +24,31 @@ using DotRecast.Detour.Io;
|
|||
|
||||
namespace DotRecast.Detour.TileCache.Io
|
||||
{
|
||||
|
||||
|
||||
public class TileCacheWriter : DetourWriter {
|
||||
|
||||
public class TileCacheWriter : DetourWriter
|
||||
{
|
||||
private readonly NavMeshParamWriter paramWriter = new NavMeshParamWriter();
|
||||
private readonly TileCacheBuilder builder = new TileCacheBuilder();
|
||||
|
||||
public void write(BinaryWriter stream, TileCache cache, ByteOrder order, bool cCompatibility) {
|
||||
public void write(BinaryWriter stream, TileCache cache, ByteOrder order, bool cCompatibility)
|
||||
{
|
||||
write(stream, TileCacheSetHeader.TILECACHESET_MAGIC, order);
|
||||
write(stream, cCompatibility ? TileCacheSetHeader.TILECACHESET_VERSION
|
||||
write(stream, cCompatibility
|
||||
? TileCacheSetHeader.TILECACHESET_VERSION
|
||||
: TileCacheSetHeader.TILECACHESET_VERSION_RECAST4J, order);
|
||||
int numTiles = 0;
|
||||
for (int i = 0; i < cache.getTileCount(); ++i) {
|
||||
for (int i = 0; i < cache.getTileCount(); ++i)
|
||||
{
|
||||
CompressedTile tile = cache.getTile(i);
|
||||
if (tile == null || tile.data == null)
|
||||
continue;
|
||||
numTiles++;
|
||||
}
|
||||
|
||||
write(stream, numTiles, order);
|
||||
paramWriter.write(stream, cache.getNavMesh().getParams(), order);
|
||||
writeCacheParams(stream, cache.getParams(), order);
|
||||
for (int i = 0; i < cache.getTileCount(); i++) {
|
||||
for (int i = 0; i < cache.getTileCount(); i++)
|
||||
{
|
||||
CompressedTile tile = cache.getTile(i);
|
||||
if (tile == null || tile.data == null)
|
||||
continue;
|
||||
|
@ -58,10 +61,13 @@ public class TileCacheWriter : DetourWriter {
|
|||
}
|
||||
}
|
||||
|
||||
private void writeCacheParams(BinaryWriter stream, TileCacheParams option, ByteOrder order) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
private void writeCacheParams(BinaryWriter stream, TileCacheParams option, ByteOrder order)
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
write(stream, option.orig[i], order);
|
||||
}
|
||||
|
||||
write(stream, option.cs, order);
|
||||
write(stream, option.ch, order);
|
||||
write(stream, option.width, order);
|
||||
|
@ -73,7 +79,5 @@ public class TileCacheWriter : DetourWriter {
|
|||
write(stream, option.maxTiles, order);
|
||||
write(stream, option.maxObstacles, order);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,13 +17,12 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.TileCache
|
||||
{
|
||||
|
||||
|
||||
public class ObstacleRequest {
|
||||
public class ObstacleRequest
|
||||
{
|
||||
public ObstacleRequestAction action;
|
||||
public long refs;
|
||||
}
|
||||
|
||||
}
|
|
@ -17,12 +17,12 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.TileCache
|
||||
{
|
||||
|
||||
|
||||
public enum ObstacleRequestAction {
|
||||
REQUEST_ADD, REQUEST_REMOVE
|
||||
public enum ObstacleRequestAction
|
||||
{
|
||||
REQUEST_ADD,
|
||||
REQUEST_REMOVE
|
||||
}
|
||||
|
||||
}
|
|
@ -17,13 +17,14 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.TileCache
|
||||
{
|
||||
|
||||
|
||||
public enum ObstacleState {
|
||||
|
||||
DT_OBSTACLE_EMPTY, DT_OBSTACLE_PROCESSING, DT_OBSTACLE_PROCESSED, DT_OBSTACLE_REMOVING
|
||||
public enum ObstacleState
|
||||
{
|
||||
DT_OBSTACLE_EMPTY,
|
||||
DT_OBSTACLE_PROCESSING,
|
||||
DT_OBSTACLE_PROCESSED,
|
||||
DT_OBSTACLE_REMOVING
|
||||
}
|
||||
|
||||
}
|
|
@ -22,26 +22,35 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using DotRecast.Core;
|
||||
using DotRecast.Detour.TileCache.Io;
|
||||
|
||||
using static DotRecast.Detour.DetourCommon;
|
||||
|
||||
namespace DotRecast.Detour.TileCache
|
||||
{
|
||||
public class TileCache
|
||||
{
|
||||
int m_tileLutSize;
|
||||
|
||||
/// < Tile hash lookup size (must be pot).
|
||||
int m_tileLutMask;
|
||||
|
||||
public class TileCache {
|
||||
/// < Tile hash lookup mask.
|
||||
private readonly CompressedTile[] m_posLookup;
|
||||
|
||||
int m_tileLutSize; /// < Tile hash lookup size (must be pot).
|
||||
int m_tileLutMask; /// < Tile hash lookup mask.
|
||||
/// < Tile hash lookup.
|
||||
private CompressedTile m_nextFreeTile;
|
||||
|
||||
private readonly CompressedTile[] m_posLookup; /// < Tile hash lookup.
|
||||
private CompressedTile m_nextFreeTile; /// < Freelist of tiles.
|
||||
private readonly CompressedTile[] m_tiles; /// < List of tiles. // TODO: (PP) replace with list
|
||||
/// < Freelist of tiles.
|
||||
private readonly CompressedTile[] m_tiles;
|
||||
|
||||
private readonly int m_saltBits; /// < Number of salt bits in the tile ID.
|
||||
private readonly int m_tileBits; /// < Number of tile bits in the tile ID.
|
||||
/// < List of tiles. // TODO: (PP) replace with list
|
||||
private readonly int m_saltBits;
|
||||
|
||||
/// < Number of salt bits in the tile ID.
|
||||
private readonly int m_tileBits;
|
||||
|
||||
/// < Number of tile bits in the tile ID.
|
||||
private readonly NavMesh m_navmesh;
|
||||
|
||||
private readonly TileCacheParams m_params;
|
||||
private readonly TileCacheStorageParams m_storageParams;
|
||||
|
||||
|
@ -57,46 +66,54 @@ public class TileCache {
|
|||
private readonly TileCacheBuilder builder = new TileCacheBuilder();
|
||||
private readonly TileCacheLayerHeaderReader tileReader = new TileCacheLayerHeaderReader();
|
||||
|
||||
private bool contains(List<long> a, long v) {
|
||||
private bool contains(List<long> a, long v)
|
||||
{
|
||||
return a.Contains(v);
|
||||
}
|
||||
|
||||
/// Encodes a tile id.
|
||||
private long encodeTileId(int salt, int it) {
|
||||
private long encodeTileId(int salt, int it)
|
||||
{
|
||||
return ((long)salt << m_tileBits) | it;
|
||||
}
|
||||
|
||||
/// Decodes a tile salt.
|
||||
private int decodeTileIdSalt(long refs) {
|
||||
private int decodeTileIdSalt(long refs)
|
||||
{
|
||||
long saltMask = (1L << m_saltBits) - 1;
|
||||
return (int)((refs >> m_tileBits) & saltMask);
|
||||
}
|
||||
|
||||
/// Decodes a tile id.
|
||||
private int decodeTileIdTile(long refs) {
|
||||
private int decodeTileIdTile(long refs)
|
||||
{
|
||||
long tileMask = (1L << m_tileBits) - 1;
|
||||
return (int)(refs & tileMask);
|
||||
}
|
||||
|
||||
/// Encodes an obstacle id.
|
||||
private long encodeObstacleId(int salt, int it) {
|
||||
private long encodeObstacleId(int salt, int it)
|
||||
{
|
||||
return ((long)salt << 16) | it;
|
||||
}
|
||||
|
||||
/// Decodes an obstacle salt.
|
||||
private int decodeObstacleIdSalt(long refs) {
|
||||
private int decodeObstacleIdSalt(long refs)
|
||||
{
|
||||
long saltMask = ((long)1 << 16) - 1;
|
||||
return (int)((refs >> 16) & saltMask);
|
||||
}
|
||||
|
||||
/// Decodes an obstacle id.
|
||||
private int decodeObstacleIdObstacle(long refs) {
|
||||
private int decodeObstacleIdObstacle(long refs)
|
||||
{
|
||||
long tileMask = ((long)1 << 16) - 1;
|
||||
return (int)(refs & tileMask);
|
||||
}
|
||||
|
||||
public TileCache(TileCacheParams option, TileCacheStorageParams storageParams, NavMesh navmesh,
|
||||
TileCacheCompressor tcomp, TileCacheMeshProcess tmprocs) {
|
||||
TileCacheCompressor tcomp, TileCacheMeshProcess tmprocs)
|
||||
{
|
||||
m_params = option;
|
||||
m_storageParams = storageParams;
|
||||
m_navmesh = navmesh;
|
||||
|
@ -104,121 +121,159 @@ public class TileCache {
|
|||
m_tmproc = tmprocs;
|
||||
|
||||
m_tileLutSize = nextPow2(m_params.maxTiles / 4);
|
||||
if (m_tileLutSize == 0) {
|
||||
if (m_tileLutSize == 0)
|
||||
{
|
||||
m_tileLutSize = 1;
|
||||
}
|
||||
|
||||
m_tileLutMask = m_tileLutSize - 1;
|
||||
m_tiles = new CompressedTile[m_params.maxTiles];
|
||||
m_posLookup = new CompressedTile[m_tileLutSize];
|
||||
for (int i = m_params.maxTiles - 1; i >= 0; --i) {
|
||||
for (int i = m_params.maxTiles - 1; i >= 0; --i)
|
||||
{
|
||||
m_tiles[i] = new CompressedTile(i);
|
||||
m_tiles[i].next = m_nextFreeTile;
|
||||
m_nextFreeTile = m_tiles[i];
|
||||
}
|
||||
|
||||
m_tileBits = ilog2(nextPow2(m_params.maxTiles));
|
||||
m_saltBits = Math.Min(31, 32 - m_tileBits);
|
||||
if (m_saltBits < 10) {
|
||||
if (m_saltBits < 10)
|
||||
{
|
||||
throw new Exception("Too few salt bits: " + m_saltBits);
|
||||
}
|
||||
}
|
||||
|
||||
public CompressedTile getTileByRef(long refs) {
|
||||
if (refs == 0) {
|
||||
public CompressedTile getTileByRef(long refs)
|
||||
{
|
||||
if (refs == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int tileIndex = decodeTileIdTile(refs);
|
||||
int tileSalt = decodeTileIdSalt(refs);
|
||||
if (tileIndex >= m_params.maxTiles) {
|
||||
if (tileIndex >= m_params.maxTiles)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
CompressedTile tile = m_tiles[tileIndex];
|
||||
if (tile.salt != tileSalt) {
|
||||
if (tile.salt != tileSalt)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return tile;
|
||||
}
|
||||
|
||||
public List<long> getTilesAt(int tx, int ty) {
|
||||
public List<long> getTilesAt(int tx, int ty)
|
||||
{
|
||||
List<long> tiles = new List<long>();
|
||||
|
||||
// Find tile based on hash.
|
||||
int h = NavMesh.computeTileHash(tx, ty, m_tileLutMask);
|
||||
CompressedTile tile = m_posLookup[h];
|
||||
while (tile != null) {
|
||||
if (tile.header != null && tile.header.tx == tx && tile.header.ty == ty) {
|
||||
while (tile != null)
|
||||
{
|
||||
if (tile.header != null && tile.header.tx == tx && tile.header.ty == ty)
|
||||
{
|
||||
tiles.Add(getTileRef(tile));
|
||||
}
|
||||
|
||||
tile = tile.next;
|
||||
}
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
CompressedTile getTileAt(int tx, int ty, int tlayer) {
|
||||
CompressedTile getTileAt(int tx, int ty, int tlayer)
|
||||
{
|
||||
// Find tile based on hash.
|
||||
int h = NavMesh.computeTileHash(tx, ty, m_tileLutMask);
|
||||
CompressedTile tile = m_posLookup[h];
|
||||
while (tile != null) {
|
||||
if (tile.header != null && tile.header.tx == tx && tile.header.ty == ty && tile.header.tlayer == tlayer) {
|
||||
while (tile != null)
|
||||
{
|
||||
if (tile.header != null && tile.header.tx == tx && tile.header.ty == ty && tile.header.tlayer == tlayer)
|
||||
{
|
||||
return tile;
|
||||
}
|
||||
|
||||
tile = tile.next;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public long getTileRef(CompressedTile tile) {
|
||||
if (tile == null) {
|
||||
public long getTileRef(CompressedTile tile)
|
||||
{
|
||||
if (tile == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int it = tile.index;
|
||||
return encodeTileId(tile.salt, it);
|
||||
}
|
||||
|
||||
public long getObstacleRef(TileCacheObstacle ob) {
|
||||
if (ob == null) {
|
||||
public long getObstacleRef(TileCacheObstacle ob)
|
||||
{
|
||||
if (ob == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int idx = ob.index;
|
||||
return encodeObstacleId(ob.salt, idx);
|
||||
}
|
||||
|
||||
public TileCacheObstacle getObstacleByRef(long refs) {
|
||||
if (refs == 0) {
|
||||
public TileCacheObstacle getObstacleByRef(long refs)
|
||||
{
|
||||
if (refs == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int idx = decodeObstacleIdObstacle(refs);
|
||||
if (idx >= m_obstacles.Count) {
|
||||
if (idx >= m_obstacles.Count)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
TileCacheObstacle ob = m_obstacles[idx];
|
||||
int salt = decodeObstacleIdSalt(refs);
|
||||
if (ob.salt != salt) {
|
||||
if (ob.salt != salt)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ob;
|
||||
}
|
||||
|
||||
public long addTile(byte[] data, int flags) {
|
||||
public long addTile(byte[] data, int flags)
|
||||
{
|
||||
// Make sure the data is in right format.
|
||||
ByteBuffer buf = new ByteBuffer(data);
|
||||
buf.order(m_storageParams.byteOrder);
|
||||
TileCacheLayerHeader header = tileReader.read(buf, m_storageParams.cCompatibility);
|
||||
// Make sure the location is free.
|
||||
if (getTileAt(header.tx, header.ty, header.tlayer) != null) {
|
||||
if (getTileAt(header.tx, header.ty, header.tlayer) != null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate a tile.
|
||||
CompressedTile tile = null;
|
||||
if (m_nextFreeTile != null) {
|
||||
if (m_nextFreeTile != null)
|
||||
{
|
||||
tile = m_nextFreeTile;
|
||||
m_nextFreeTile = tile.next;
|
||||
tile.next = null;
|
||||
}
|
||||
|
||||
// Make sure we could allocate a tile.
|
||||
if (tile == null) {
|
||||
if (tile == null)
|
||||
{
|
||||
throw new Exception("Out of storage");
|
||||
}
|
||||
|
||||
|
@ -236,21 +291,28 @@ public class TileCache {
|
|||
return getTileRef(tile);
|
||||
}
|
||||
|
||||
private int align4(int i) {
|
||||
private int align4(int i)
|
||||
{
|
||||
return (i + 3) & (~3);
|
||||
}
|
||||
|
||||
public void removeTile(long refs) {
|
||||
if (refs == 0) {
|
||||
public void removeTile(long refs)
|
||||
{
|
||||
if (refs == 0)
|
||||
{
|
||||
throw new Exception("Invalid tile ref");
|
||||
}
|
||||
|
||||
int tileIndex = decodeTileIdTile(refs);
|
||||
int tileSalt = decodeTileIdSalt(refs);
|
||||
if (tileIndex >= m_params.maxTiles) {
|
||||
if (tileIndex >= m_params.maxTiles)
|
||||
{
|
||||
throw new Exception("Invalid tile index");
|
||||
}
|
||||
|
||||
CompressedTile tile = m_tiles[tileIndex];
|
||||
if (tile.salt != tileSalt) {
|
||||
if (tile.salt != tileSalt)
|
||||
{
|
||||
throw new Exception("Invalid tile salt");
|
||||
}
|
||||
|
||||
|
@ -258,15 +320,22 @@ public class TileCache {
|
|||
int h = NavMesh.computeTileHash(tile.header.tx, tile.header.ty, m_tileLutMask);
|
||||
CompressedTile prev = null;
|
||||
CompressedTile cur = m_posLookup[h];
|
||||
while (cur != null) {
|
||||
if (cur == tile) {
|
||||
if (prev != null) {
|
||||
while (cur != null)
|
||||
{
|
||||
if (cur == tile)
|
||||
{
|
||||
if (prev != null)
|
||||
{
|
||||
prev.next = cur.next;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
m_posLookup[h] = cur.next;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
prev = cur;
|
||||
cur = cur.next;
|
||||
}
|
||||
|
@ -278,18 +347,19 @@ public class TileCache {
|
|||
|
||||
// Update salt, salt should never be zero.
|
||||
tile.salt = (tile.salt + 1) & ((1 << m_saltBits) - 1);
|
||||
if (tile.salt == 0) {
|
||||
if (tile.salt == 0)
|
||||
{
|
||||
tile.salt++;
|
||||
}
|
||||
|
||||
// Add to free list.
|
||||
tile.next = m_nextFreeTile;
|
||||
m_nextFreeTile = tile;
|
||||
|
||||
}
|
||||
|
||||
// Cylinder obstacle
|
||||
public long addObstacle(float[] pos, float radius, float height) {
|
||||
public long addObstacle(float[] pos, float radius, float height)
|
||||
{
|
||||
TileCacheObstacle ob = allocObstacle();
|
||||
ob.type = TileCacheObstacle.TileCacheObstacleType.CYLINDER;
|
||||
|
||||
|
@ -301,7 +371,8 @@ public class TileCache {
|
|||
}
|
||||
|
||||
// Aabb obstacle
|
||||
public long addBoxObstacle(float[] bmin, float[] bmax) {
|
||||
public long addBoxObstacle(float[] bmin, float[] bmax)
|
||||
{
|
||||
TileCacheObstacle ob = allocObstacle();
|
||||
ob.type = TileCacheObstacle.TileCacheObstacleType.BOX;
|
||||
|
||||
|
@ -312,7 +383,8 @@ public class TileCache {
|
|||
}
|
||||
|
||||
// Box obstacle: can be rotated in Y
|
||||
public long addBoxObstacle(float[] center, float[] extents, float yRadians) {
|
||||
public long addBoxObstacle(float[] center, float[] extents, float yRadians)
|
||||
{
|
||||
TileCacheObstacle ob = allocObstacle();
|
||||
ob.type = TileCacheObstacle.TileCacheObstacleType.ORIENTED_BOX;
|
||||
vCopy(ob.center, center);
|
||||
|
@ -324,7 +396,8 @@ public class TileCache {
|
|||
return addObstacleRequest(ob).refs;
|
||||
}
|
||||
|
||||
private ObstacleRequest addObstacleRequest(TileCacheObstacle ob) {
|
||||
private ObstacleRequest addObstacleRequest(TileCacheObstacle ob)
|
||||
{
|
||||
ObstacleRequest req = new ObstacleRequest();
|
||||
req.action = ObstacleRequestAction.REQUEST_ADD;
|
||||
req.refs = getObstacleRef(ob);
|
||||
|
@ -332,8 +405,10 @@ public class TileCache {
|
|||
return req;
|
||||
}
|
||||
|
||||
public void removeObstacle(long refs) {
|
||||
if (refs == 0) {
|
||||
public void removeObstacle(long refs)
|
||||
{
|
||||
if (refs == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -343,14 +418,19 @@ public class TileCache {
|
|||
m_reqs.Add(req);
|
||||
}
|
||||
|
||||
private TileCacheObstacle allocObstacle() {
|
||||
private TileCacheObstacle allocObstacle()
|
||||
{
|
||||
TileCacheObstacle o = m_nextFreeObstacle;
|
||||
if (o == null) {
|
||||
if (o == null)
|
||||
{
|
||||
o = new TileCacheObstacle(m_obstacles.Count);
|
||||
m_obstacles.Add(o);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
m_nextFreeObstacle = o.next;
|
||||
}
|
||||
|
||||
o.state = ObstacleState.DT_OBSTACLE_PROCESSING;
|
||||
o.touched.Clear();
|
||||
o.pending.Clear();
|
||||
|
@ -358,7 +438,8 @@ public class TileCache {
|
|||
return o;
|
||||
}
|
||||
|
||||
List<long> queryTiles(float[] bmin, float[] bmax) {
|
||||
List<long> queryTiles(float[] bmin, float[] bmax)
|
||||
{
|
||||
List<long> results = new List<long>();
|
||||
float tw = m_params.width * m_params.cs;
|
||||
float th = m_params.height * m_params.cs;
|
||||
|
@ -366,20 +447,25 @@ public class TileCache {
|
|||
int tx1 = (int)Math.Floor((bmax[0] - m_params.orig[0]) / tw);
|
||||
int ty0 = (int)Math.Floor((bmin[2] - m_params.orig[2]) / th);
|
||||
int ty1 = (int)Math.Floor((bmax[2] - m_params.orig[2]) / th);
|
||||
for (int ty = ty0; ty <= ty1; ++ty) {
|
||||
for (int tx = tx0; tx <= tx1; ++tx) {
|
||||
for (int ty = ty0; ty <= ty1; ++ty)
|
||||
{
|
||||
for (int tx = tx0; tx <= tx1; ++tx)
|
||||
{
|
||||
List<long> tiles = getTilesAt(tx, ty);
|
||||
foreach (long i in tiles) {
|
||||
foreach (long i in tiles)
|
||||
{
|
||||
CompressedTile tile = m_tiles[decodeTileIdTile(i)];
|
||||
float[] tbmin = new float[3];
|
||||
float[] tbmax = new float[3];
|
||||
calcTightTileBounds(tile.header, tbmin, tbmax);
|
||||
if (overlapBounds(bmin, bmax, tbmin, tbmax)) {
|
||||
if (overlapBounds(bmin, bmax, tbmin, tbmax))
|
||||
{
|
||||
results.Add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
@ -390,21 +476,28 @@ public class TileCache {
|
|||
* cache is up to date another (immediate) call to update will have no effect; otherwise another call will
|
||||
* continue processing obstacle requests and tile rebuilds.
|
||||
*/
|
||||
public bool update() {
|
||||
if (0 == m_update.Count) {
|
||||
public bool update()
|
||||
{
|
||||
if (0 == m_update.Count)
|
||||
{
|
||||
// Process requests.
|
||||
foreach (ObstacleRequest req in m_reqs) {
|
||||
foreach (ObstacleRequest req in m_reqs)
|
||||
{
|
||||
int idx = decodeObstacleIdObstacle(req.refs);
|
||||
if (idx >= m_obstacles.Count) {
|
||||
continue;
|
||||
}
|
||||
TileCacheObstacle ob = m_obstacles[idx];
|
||||
int salt = decodeObstacleIdSalt(req.refs);
|
||||
if (ob.salt != salt) {
|
||||
if (idx >= m_obstacles.Count)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (req.action == ObstacleRequestAction.REQUEST_ADD) {
|
||||
TileCacheObstacle ob = m_obstacles[idx];
|
||||
int salt = decodeObstacleIdSalt(req.refs);
|
||||
if (ob.salt != salt)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (req.action == ObstacleRequestAction.REQUEST_ADD)
|
||||
{
|
||||
// Find touched tiles.
|
||||
float[] bmin = new float[3];
|
||||
float[] bmax = new float[3];
|
||||
|
@ -412,21 +505,29 @@ public class TileCache {
|
|||
ob.touched = queryTiles(bmin, bmax);
|
||||
// Add tiles to update list.
|
||||
ob.pending.Clear();
|
||||
foreach (long j in ob.touched) {
|
||||
if (!contains(m_update, j)) {
|
||||
foreach (long j in ob.touched)
|
||||
{
|
||||
if (!contains(m_update, j))
|
||||
{
|
||||
m_update.Add(j);
|
||||
}
|
||||
|
||||
ob.pending.Add(j);
|
||||
}
|
||||
} else if (req.action == ObstacleRequestAction.REQUEST_REMOVE) {
|
||||
}
|
||||
else if (req.action == ObstacleRequestAction.REQUEST_REMOVE)
|
||||
{
|
||||
// Prepare to remove obstacle.
|
||||
ob.state = ObstacleState.DT_OBSTACLE_REMOVING;
|
||||
// Add tiles to update list.
|
||||
ob.pending.Clear();
|
||||
foreach (long j in ob.touched) {
|
||||
if (!contains(m_update, j)) {
|
||||
foreach (long j in ob.touched)
|
||||
{
|
||||
if (!contains(m_update, j))
|
||||
{
|
||||
m_update.Add(j);
|
||||
}
|
||||
|
||||
ob.pending.Add(j);
|
||||
}
|
||||
}
|
||||
|
@ -436,31 +537,40 @@ public class TileCache {
|
|||
}
|
||||
|
||||
// Process updates
|
||||
if (0 < m_update.Count) {
|
||||
if (0 < m_update.Count)
|
||||
{
|
||||
long refs = m_update[0];
|
||||
m_update.RemoveAt(0);
|
||||
// Build mesh
|
||||
buildNavMeshTile(refs);
|
||||
|
||||
// Update obstacle states.
|
||||
for (int i = 0; i < m_obstacles.Count; ++i) {
|
||||
for (int i = 0; i < m_obstacles.Count; ++i)
|
||||
{
|
||||
TileCacheObstacle ob = m_obstacles[i];
|
||||
if (ob.state == ObstacleState.DT_OBSTACLE_PROCESSING
|
||||
|| ob.state == ObstacleState.DT_OBSTACLE_REMOVING) {
|
||||
|| ob.state == ObstacleState.DT_OBSTACLE_REMOVING)
|
||||
{
|
||||
// Remove handled tile from pending list.
|
||||
ob.pending.Remove(refs);
|
||||
|
||||
// If all pending tiles processed, change state.
|
||||
if (0 == ob.pending.Count) {
|
||||
if (ob.state == ObstacleState.DT_OBSTACLE_PROCESSING) {
|
||||
if (0 == ob.pending.Count)
|
||||
{
|
||||
if (ob.state == ObstacleState.DT_OBSTACLE_PROCESSING)
|
||||
{
|
||||
ob.state = ObstacleState.DT_OBSTACLE_PROCESSED;
|
||||
} else if (ob.state == ObstacleState.DT_OBSTACLE_REMOVING) {
|
||||
}
|
||||
else if (ob.state == ObstacleState.DT_OBSTACLE_REMOVING)
|
||||
{
|
||||
ob.state = ObstacleState.DT_OBSTACLE_EMPTY;
|
||||
// Update salt, salt should never be zero.
|
||||
ob.salt = (ob.salt + 1) & ((1 << 16) - 1);
|
||||
if (ob.salt == 0) {
|
||||
if (ob.salt == 0)
|
||||
{
|
||||
ob.salt++;
|
||||
}
|
||||
|
||||
// Return obstacle to free list.
|
||||
ob.next = m_nextFreeObstacle;
|
||||
m_nextFreeObstacle = ob;
|
||||
|
@ -473,49 +583,66 @@ public class TileCache {
|
|||
return 0 == m_update.Count && 0 == m_reqs.Count;
|
||||
}
|
||||
|
||||
public void buildNavMeshTile(long refs) {
|
||||
public void buildNavMeshTile(long refs)
|
||||
{
|
||||
int idx = decodeTileIdTile(refs);
|
||||
if (idx > m_params.maxTiles) {
|
||||
if (idx > m_params.maxTiles)
|
||||
{
|
||||
throw new Exception("Invalid tile index");
|
||||
}
|
||||
|
||||
CompressedTile tile = m_tiles[idx];
|
||||
int salt = decodeTileIdSalt(refs);
|
||||
if (tile.salt != salt) {
|
||||
if (tile.salt != salt)
|
||||
{
|
||||
throw new Exception("Invalid tile salt");
|
||||
}
|
||||
|
||||
int walkableClimbVx = (int)(m_params.walkableClimb / m_params.ch);
|
||||
|
||||
// Decompress tile layer data.
|
||||
TileCacheLayer layer = decompressTile(tile);
|
||||
|
||||
// Rasterize obstacles.
|
||||
for (int i = 0; i < m_obstacles.Count; ++i) {
|
||||
for (int i = 0; i < m_obstacles.Count; ++i)
|
||||
{
|
||||
TileCacheObstacle ob = m_obstacles[i];
|
||||
if (ob.state == ObstacleState.DT_OBSTACLE_EMPTY || ob.state == ObstacleState.DT_OBSTACLE_REMOVING) {
|
||||
if (ob.state == ObstacleState.DT_OBSTACLE_EMPTY || ob.state == ObstacleState.DT_OBSTACLE_REMOVING)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (contains(ob.touched, refs)) {
|
||||
if (ob.type == TileCacheObstacle.TileCacheObstacleType.CYLINDER) {
|
||||
|
||||
if (contains(ob.touched, refs))
|
||||
{
|
||||
if (ob.type == TileCacheObstacle.TileCacheObstacleType.CYLINDER)
|
||||
{
|
||||
builder.markCylinderArea(layer, tile.header.bmin, m_params.cs, m_params.ch, ob.pos, ob.radius,
|
||||
ob.height, 0);
|
||||
} else if (ob.type == TileCacheObstacle.TileCacheObstacleType.BOX) {
|
||||
}
|
||||
else if (ob.type == TileCacheObstacle.TileCacheObstacleType.BOX)
|
||||
{
|
||||
builder.markBoxArea(layer, tile.header.bmin, m_params.cs, m_params.ch, ob.bmin, ob.bmax, 0);
|
||||
} else if (ob.type == TileCacheObstacle.TileCacheObstacleType.ORIENTED_BOX) {
|
||||
}
|
||||
else if (ob.type == TileCacheObstacle.TileCacheObstacleType.ORIENTED_BOX)
|
||||
{
|
||||
builder.markBoxArea(layer, tile.header.bmin, m_params.cs, m_params.ch, ob.center, ob.extents,
|
||||
ob.rotAux, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build navmesh
|
||||
builder.buildTileCacheRegions(layer, walkableClimbVx);
|
||||
TileCacheContourSet lcset = builder.buildTileCacheContours(layer, walkableClimbVx,
|
||||
m_params.maxSimplificationError);
|
||||
TileCachePolyMesh polyMesh = builder.buildTileCachePolyMesh(lcset, m_navmesh.getMaxVertsPerPoly());
|
||||
// Early out if the mesh tile is empty.
|
||||
if (polyMesh.npolys == 0) {
|
||||
if (polyMesh.npolys == 0)
|
||||
{
|
||||
m_navmesh.removeTile(m_navmesh.getTileRefAt(tile.header.tx, tile.header.ty, tile.header.tlayer));
|
||||
return;
|
||||
}
|
||||
|
||||
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
|
||||
option.verts = polyMesh.verts;
|
||||
option.vertCount = polyMesh.nverts;
|
||||
|
@ -535,25 +662,30 @@ public class TileCache {
|
|||
option.buildBvTree = false;
|
||||
option.bmin = tile.header.bmin;
|
||||
option.bmax = tile.header.bmax;
|
||||
if (m_tmproc != null) {
|
||||
if (m_tmproc != null)
|
||||
{
|
||||
m_tmproc.process(option);
|
||||
}
|
||||
|
||||
MeshData meshData = NavMeshBuilder.createNavMeshData(option);
|
||||
// Remove existing tile.
|
||||
m_navmesh.removeTile(m_navmesh.getTileRefAt(tile.header.tx, tile.header.ty, tile.header.tlayer));
|
||||
// Add new tile, or leave the location empty. if (navData) { // Let the
|
||||
if (meshData != null) {
|
||||
if (meshData != null)
|
||||
{
|
||||
m_navmesh.addTile(meshData, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public TileCacheLayer decompressTile(CompressedTile tile) {
|
||||
public TileCacheLayer decompressTile(CompressedTile tile)
|
||||
{
|
||||
TileCacheLayer layer = builder.decompressTileCacheLayer(m_tcomp, tile.data, m_storageParams.byteOrder,
|
||||
m_storageParams.cCompatibility);
|
||||
return layer;
|
||||
}
|
||||
|
||||
void calcTightTileBounds(TileCacheLayerHeader header, float[] bmin, float[] bmax) {
|
||||
void calcTightTileBounds(TileCacheLayerHeader header, float[] bmin, float[] bmax)
|
||||
{
|
||||
float cs = m_params.cs;
|
||||
bmin[0] = header.bmin[0] + header.minx * cs;
|
||||
bmin[1] = header.bmin[1];
|
||||
|
@ -563,18 +695,24 @@ public class TileCache {
|
|||
bmax[2] = header.bmin[2] + (header.maxy + 1) * cs;
|
||||
}
|
||||
|
||||
void getObstacleBounds(TileCacheObstacle ob, float[] bmin, float[] bmax) {
|
||||
if (ob.type == TileCacheObstacle.TileCacheObstacleType.CYLINDER) {
|
||||
void getObstacleBounds(TileCacheObstacle ob, float[] bmin, float[] bmax)
|
||||
{
|
||||
if (ob.type == TileCacheObstacle.TileCacheObstacleType.CYLINDER)
|
||||
{
|
||||
bmin[0] = ob.pos[0] - ob.radius;
|
||||
bmin[1] = ob.pos[1];
|
||||
bmin[2] = ob.pos[2] - ob.radius;
|
||||
bmax[0] = ob.pos[0] + ob.radius;
|
||||
bmax[1] = ob.pos[1] + ob.height;
|
||||
bmax[2] = ob.pos[2] + ob.radius;
|
||||
} else if (ob.type == TileCacheObstacle.TileCacheObstacleType.BOX) {
|
||||
}
|
||||
else if (ob.type == TileCacheObstacle.TileCacheObstacleType.BOX)
|
||||
{
|
||||
vCopy(bmin, ob.bmin);
|
||||
vCopy(bmax, ob.bmax);
|
||||
} else if (ob.type == TileCacheObstacle.TileCacheObstacleType.ORIENTED_BOX) {
|
||||
}
|
||||
else if (ob.type == TileCacheObstacle.TileCacheObstacleType.ORIENTED_BOX)
|
||||
{
|
||||
float maxr = 1.41f * Math.Max(ob.extents[0], ob.extents[2]);
|
||||
bmin[0] = ob.center[0] - maxr;
|
||||
bmax[0] = ob.center[0] + maxr;
|
||||
|
@ -585,25 +723,29 @@ public class TileCache {
|
|||
}
|
||||
}
|
||||
|
||||
public TileCacheParams getParams() {
|
||||
public TileCacheParams getParams()
|
||||
{
|
||||
return m_params;
|
||||
}
|
||||
|
||||
public TileCacheCompressor getCompressor() {
|
||||
public TileCacheCompressor getCompressor()
|
||||
{
|
||||
return m_tcomp;
|
||||
}
|
||||
|
||||
public int getTileCount() {
|
||||
public int getTileCount()
|
||||
{
|
||||
return m_params.maxTiles;
|
||||
}
|
||||
|
||||
public CompressedTile getTile(int i) {
|
||||
public CompressedTile getTile(int i)
|
||||
{
|
||||
return m_tiles[i];
|
||||
}
|
||||
|
||||
public NavMesh getNavMesh() {
|
||||
public NavMesh getNavMesh()
|
||||
{
|
||||
return m_navmesh;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -17,15 +17,13 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.TileCache
|
||||
{
|
||||
|
||||
|
||||
public interface TileCacheCompressor {
|
||||
|
||||
public interface TileCacheCompressor
|
||||
{
|
||||
byte[] decompress(byte[] buf, int offset, int len, int outputlen);
|
||||
|
||||
byte[] compress(byte[] buf);
|
||||
}
|
||||
|
||||
}
|
|
@ -17,15 +17,14 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.TileCache
|
||||
{
|
||||
|
||||
|
||||
public class TileCacheContour {
|
||||
public class TileCacheContour
|
||||
{
|
||||
public int nverts;
|
||||
public int[] verts;
|
||||
public int reg;
|
||||
public int area;
|
||||
}
|
||||
|
||||
}
|
|
@ -17,13 +17,12 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.TileCache
|
||||
{
|
||||
|
||||
|
||||
public class TileCacheContourSet {
|
||||
public class TileCacheContourSet
|
||||
{
|
||||
public int nconts;
|
||||
public TileCacheContour[] conts;
|
||||
}
|
||||
|
||||
}
|
|
@ -17,17 +17,19 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.TileCache
|
||||
{
|
||||
|
||||
|
||||
public class TileCacheLayer {
|
||||
public class TileCacheLayer
|
||||
{
|
||||
public TileCacheLayerHeader header;
|
||||
public int regCount; /// < Region count.
|
||||
public int regCount;
|
||||
|
||||
/// < Region count.
|
||||
public short[] heights; // char
|
||||
|
||||
public short[] areas; // char
|
||||
public short[] cons; // char
|
||||
public short[] regs; // char
|
||||
}
|
||||
|
||||
}
|
|
@ -17,24 +17,32 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.TileCache
|
||||
{
|
||||
public class TileCacheLayerHeader
|
||||
{
|
||||
public const int DT_TILECACHE_MAGIC = 'D' << 24 | 'T' << 16 | 'L' << 8 | 'R';
|
||||
|
||||
|
||||
public class TileCacheLayerHeader {
|
||||
|
||||
public const int DT_TILECACHE_MAGIC = 'D' << 24 | 'T' << 16 | 'L' << 8 | 'R'; /// < 'DTLR';
|
||||
/// < 'DTLR';
|
||||
public const int DT_TILECACHE_VERSION = 1;
|
||||
|
||||
public int magic; /// < Data magic
|
||||
public int version; /// < Data version
|
||||
public int magic;
|
||||
|
||||
/// < Data magic
|
||||
public int version;
|
||||
|
||||
/// < Data version
|
||||
public int tx, ty, tlayer;
|
||||
|
||||
public float[] bmin = new float[3];
|
||||
public float[] bmax = new float[3];
|
||||
public int hmin, hmax; /// < Height min/max range
|
||||
public int width, height; /// < Dimension of the layer.
|
||||
public int hmin, hmax;
|
||||
|
||||
/// < Height min/max range
|
||||
public int width, height;
|
||||
|
||||
/// < Dimension of the layer.
|
||||
public int minx, maxx, miny, maxy; /// < Usable sub-region.
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,13 +17,11 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.TileCache
|
||||
{
|
||||
|
||||
|
||||
public interface TileCacheMeshProcess {
|
||||
|
||||
public interface TileCacheMeshProcess
|
||||
{
|
||||
void process(NavMeshDataCreateParams option);
|
||||
}
|
||||
|
||||
}
|
|
@ -22,12 +22,13 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour.TileCache
|
||||
{
|
||||
|
||||
|
||||
public class TileCacheObstacle {
|
||||
|
||||
public enum TileCacheObstacleType {
|
||||
CYLINDER, BOX, ORIENTED_BOX
|
||||
public class TileCacheObstacle
|
||||
{
|
||||
public enum TileCacheObstacleType
|
||||
{
|
||||
CYLINDER,
|
||||
BOX,
|
||||
ORIENTED_BOX
|
||||
};
|
||||
|
||||
public readonly int index;
|
||||
|
@ -45,11 +46,10 @@ public class TileCacheObstacle {
|
|||
public ObstacleState state = ObstacleState.DT_OBSTACLE_EMPTY;
|
||||
public TileCacheObstacle next;
|
||||
|
||||
public TileCacheObstacle(int index) {
|
||||
public TileCacheObstacle(int index)
|
||||
{
|
||||
salt = 1;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,11 +17,11 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.TileCache
|
||||
{
|
||||
|
||||
|
||||
public class TileCacheParams {
|
||||
public class TileCacheParams
|
||||
{
|
||||
public readonly float[] orig = new float[3];
|
||||
public float cs, ch;
|
||||
public int width, height;
|
||||
|
@ -31,7 +31,5 @@ public class TileCacheParams {
|
|||
public float maxSimplificationError;
|
||||
public int maxTiles;
|
||||
public int maxObstacles;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,22 +17,33 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.TileCache
|
||||
{
|
||||
|
||||
|
||||
public class TileCachePolyMesh {
|
||||
public class TileCachePolyMesh
|
||||
{
|
||||
public int nvp;
|
||||
public int nverts; /// < Number of vertices.
|
||||
public int npolys; /// < Number of polygons.
|
||||
public int[] verts; /// < Vertices of the mesh, 3 elements per vertex.
|
||||
public int[] polys; /// < Polygons of the mesh, nvp*2 elements per polygon.
|
||||
public int[] flags; /// < Per polygon flags.
|
||||
public int[] areas; /// < Area ID of polygons.
|
||||
public int nverts;
|
||||
|
||||
public TileCachePolyMesh(int nvp) {
|
||||
/// < Number of vertices.
|
||||
public int npolys;
|
||||
|
||||
/// < Number of polygons.
|
||||
public int[] verts;
|
||||
|
||||
/// < Vertices of the mesh, 3 elements per vertex.
|
||||
public int[] polys;
|
||||
|
||||
/// < Polygons of the mesh, nvp*2 elements per polygon.
|
||||
public int[] flags;
|
||||
|
||||
/// < Per polygon flags.
|
||||
public int[] areas;
|
||||
|
||||
/// < Area ID of polygons.
|
||||
public TileCachePolyMesh(int nvp)
|
||||
{
|
||||
this.nvp = nvp;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -22,18 +22,15 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Detour.TileCache
|
||||
{
|
||||
|
||||
|
||||
public class TileCacheStorageParams {
|
||||
|
||||
public class TileCacheStorageParams
|
||||
{
|
||||
public readonly ByteOrder byteOrder;
|
||||
public readonly bool cCompatibility;
|
||||
|
||||
public TileCacheStorageParams(ByteOrder byteOrder, bool cCompatibility) {
|
||||
public TileCacheStorageParams(ByteOrder byteOrder, bool cCompatibility)
|
||||
{
|
||||
this.byteOrder = byteOrder;
|
||||
this.cCompatibility = cCompatibility;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,23 +17,24 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Bounding volume node.
|
||||
*
|
||||
* @note This structure is rarely if ever used by the end user.
|
||||
* @see MeshTile
|
||||
*/
|
||||
public class BVNode {
|
||||
public class BVNode
|
||||
{
|
||||
/** Minimum bounds of the node's AABB. [(x, y, z)] */
|
||||
public int[] bmin = new int[3];
|
||||
|
||||
/** Maximum bounds of the node's AABB. [(x, y, z)] */
|
||||
public int[] bmax = new int[3];
|
||||
|
||||
/** The node's index. (Negative for escape sequence.) */
|
||||
public int i;
|
||||
}
|
||||
|
||||
}
|
|
@ -17,30 +17,30 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
public class ClosestPointOnPolyResult {
|
||||
|
||||
public class ClosestPointOnPolyResult
|
||||
{
|
||||
private readonly bool posOverPoly;
|
||||
private readonly float[] closest;
|
||||
|
||||
public ClosestPointOnPolyResult(bool posOverPoly, float[] closest) {
|
||||
public ClosestPointOnPolyResult(bool posOverPoly, float[] closest)
|
||||
{
|
||||
this.posOverPoly = posOverPoly;
|
||||
this.closest = closest;
|
||||
}
|
||||
|
||||
/** Returns true if the position is over the polygon. */
|
||||
public bool isPosOverPoly() {
|
||||
public bool isPosOverPoly()
|
||||
{
|
||||
return posOverPoly;
|
||||
}
|
||||
|
||||
/** Returns the closest point on the polygon. [(x, y, z)] */
|
||||
public float[] getClosest() {
|
||||
public float[] getClosest()
|
||||
{
|
||||
return closest;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -20,26 +20,31 @@ using System;
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
using static DetourCommon;
|
||||
|
||||
/**
|
||||
* Convex-convex intersection based on "Computational Geometry in C" by Joseph O'Rourke
|
||||
*/
|
||||
public static class ConvexConvexIntersection {
|
||||
|
||||
public static class ConvexConvexIntersection
|
||||
{
|
||||
private static readonly float EPSILON = 0.0001f;
|
||||
|
||||
private enum InFlag {
|
||||
Pin, Qin, Unknown,
|
||||
private enum InFlag
|
||||
{
|
||||
Pin,
|
||||
Qin,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
private enum Intersection {
|
||||
None, Single, Overlap,
|
||||
private enum Intersection
|
||||
{
|
||||
None,
|
||||
Single,
|
||||
Overlap,
|
||||
}
|
||||
|
||||
public static float[] intersect(float[] p, float[] q) {
|
||||
public static float[] intersect(float[] p, float[] q)
|
||||
{
|
||||
int n = p.Length / 3;
|
||||
int m = q.Length / 3;
|
||||
float[] inters = new float[Math.Max(m, n) * 3 * 3];
|
||||
|
@ -60,7 +65,8 @@ public static class ConvexConvexIntersection {
|
|||
float[] ip = new float[3];
|
||||
float[] iq = new float[3];
|
||||
|
||||
do {
|
||||
do
|
||||
{
|
||||
vCopy(a, p, 3 * (ai % n));
|
||||
vCopy(b, q, 3 * (bi % m));
|
||||
vCopy(a1, p, 3 * ((ai + n - 1) % n)); // prev a
|
||||
|
@ -72,17 +78,22 @@ public static class ConvexConvexIntersection {
|
|||
float cross = B[0] * A[2] - A[0] * B[2]; // triArea2D({0, 0}, A, B);
|
||||
float aHB = triArea2D(b1, b, a);
|
||||
float bHA = triArea2D(a1, a, b);
|
||||
if (Math.Abs(cross) < EPSILON) {
|
||||
if (Math.Abs(cross) < EPSILON)
|
||||
{
|
||||
cross = 0f;
|
||||
}
|
||||
|
||||
bool parallel = cross == 0f;
|
||||
Intersection code = parallel ? parallelInt(a1, a, b1, b, ip, iq) : segSegInt(a1, a, b1, b, ip, iq);
|
||||
|
||||
if (code == Intersection.Single) {
|
||||
if (FirstPoint) {
|
||||
if (code == Intersection.Single)
|
||||
{
|
||||
if (FirstPoint)
|
||||
{
|
||||
FirstPoint = false;
|
||||
aa = ba = 0;
|
||||
}
|
||||
|
||||
ii = addVertex(inters, ii, ip);
|
||||
f = inOut(f, aHB, bHA);
|
||||
}
|
||||
|
@ -90,55 +101,76 @@ public static class ConvexConvexIntersection {
|
|||
/*-----Advance rules-----*/
|
||||
|
||||
/* Special case: A & B overlap and oppositely oriented. */
|
||||
if (code == Intersection.Overlap && vDot2D(A, B) < 0) {
|
||||
if (code == Intersection.Overlap && vDot2D(A, B) < 0)
|
||||
{
|
||||
ii = addVertex(inters, ii, ip);
|
||||
ii = addVertex(inters, ii, iq);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Special case: A & B parallel and separated. */
|
||||
if (parallel && aHB < 0f && bHA < 0f) {
|
||||
if (parallel && aHB < 0f && bHA < 0f)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/* Special case: A & B collinear. */
|
||||
else if (parallel && Math.Abs(aHB) < EPSILON && Math.Abs(bHA) < EPSILON) {
|
||||
else if (parallel && Math.Abs(aHB) < EPSILON && Math.Abs(bHA) < EPSILON)
|
||||
{
|
||||
/* Advance but do not output point. */
|
||||
if (f == InFlag.Pin) {
|
||||
if (f == InFlag.Pin)
|
||||
{
|
||||
ba++;
|
||||
bi++;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
aa++;
|
||||
ai++;
|
||||
}
|
||||
}
|
||||
/* Generic cases. */
|
||||
else if (cross >= 0)
|
||||
{
|
||||
if (bHA > 0)
|
||||
{
|
||||
if (f == InFlag.Pin)
|
||||
{
|
||||
ii = addVertex(inters, ii, a);
|
||||
}
|
||||
|
||||
/* Generic cases. */
|
||||
else if (cross >= 0) {
|
||||
if (bHA > 0) {
|
||||
if (f == InFlag.Pin) {
|
||||
ii = addVertex(inters, ii, a);
|
||||
}
|
||||
aa++;
|
||||
ai++;
|
||||
} else {
|
||||
if (f == InFlag.Qin) {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (f == InFlag.Qin)
|
||||
{
|
||||
ii = addVertex(inters, ii, b);
|
||||
}
|
||||
|
||||
ba++;
|
||||
bi++;
|
||||
}
|
||||
} else {
|
||||
if (aHB > 0) {
|
||||
if (f == InFlag.Qin) {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (aHB > 0)
|
||||
{
|
||||
if (f == InFlag.Qin)
|
||||
{
|
||||
ii = addVertex(inters, ii, b);
|
||||
}
|
||||
|
||||
ba++;
|
||||
bi++;
|
||||
} else {
|
||||
if (f == InFlag.Pin) {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (f == InFlag.Pin)
|
||||
{
|
||||
ii = addVertex(inters, ii, a);
|
||||
}
|
||||
|
||||
aa++;
|
||||
ai++;
|
||||
}
|
||||
|
@ -147,7 +179,8 @@ public static class ConvexConvexIntersection {
|
|||
} while ((aa < n || ba < m) && aa < 2 * n && ba < 2 * m);
|
||||
|
||||
/* Deal with special cases: not implemented. */
|
||||
if (f == InFlag.Unknown) {
|
||||
if (f == InFlag.Unknown)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -156,86 +189,117 @@ public static class ConvexConvexIntersection {
|
|||
return copied;
|
||||
}
|
||||
|
||||
private static int addVertex(float[] inters, int ii, float[] p) {
|
||||
if (ii > 0) {
|
||||
if (inters[ii - 3] == p[0] && inters[ii - 2] == p[1] && inters[ii - 1] == p[2]) {
|
||||
private static int addVertex(float[] inters, int ii, float[] p)
|
||||
{
|
||||
if (ii > 0)
|
||||
{
|
||||
if (inters[ii - 3] == p[0] && inters[ii - 2] == p[1] && inters[ii - 1] == p[2])
|
||||
{
|
||||
return ii;
|
||||
}
|
||||
if (inters[0] == p[0] && inters[1] == p[1] && inters[2] == p[2]) {
|
||||
|
||||
if (inters[0] == p[0] && inters[1] == p[1] && inters[2] == p[2])
|
||||
{
|
||||
return ii;
|
||||
}
|
||||
}
|
||||
|
||||
inters[ii] = p[0];
|
||||
inters[ii + 1] = p[1];
|
||||
inters[ii + 2] = p[2];
|
||||
return ii + 3;
|
||||
}
|
||||
|
||||
private static InFlag inOut(InFlag inflag, float aHB, float bHA) {
|
||||
if (aHB > 0) {
|
||||
private static InFlag inOut(InFlag inflag, float aHB, float bHA)
|
||||
{
|
||||
if (aHB > 0)
|
||||
{
|
||||
return InFlag.Pin;
|
||||
} else if (bHA > 0) {
|
||||
}
|
||||
else if (bHA > 0)
|
||||
{
|
||||
return InFlag.Qin;
|
||||
}
|
||||
|
||||
return inflag;
|
||||
}
|
||||
|
||||
private static Intersection segSegInt(float[] a, float[] b, float[] c, float[] d, float[] p, float[] q) {
|
||||
private static Intersection segSegInt(float[] a, float[] b, float[] c, float[] d, float[] p, float[] q)
|
||||
{
|
||||
var isec = intersectSegSeg2D(a, b, c, d);
|
||||
if (null != isec) {
|
||||
if (null != isec)
|
||||
{
|
||||
float s = isec.Item1;
|
||||
float t = isec.Item2;
|
||||
if (s >= 0.0f && s <= 1.0f && t >= 0.0f && t <= 1.0f) {
|
||||
if (s >= 0.0f && s <= 1.0f && t >= 0.0f && t <= 1.0f)
|
||||
{
|
||||
p[0] = a[0] + (b[0] - a[0]) * s;
|
||||
p[1] = a[1] + (b[1] - a[1]) * s;
|
||||
p[2] = a[2] + (b[2] - a[2]) * s;
|
||||
return Intersection.Single;
|
||||
}
|
||||
}
|
||||
|
||||
return Intersection.None;
|
||||
}
|
||||
|
||||
private static Intersection parallelInt(float[] a, float[] b, float[] c, float[] d, float[] p, float[] q) {
|
||||
if (between(a, b, c) && between(a, b, d)) {
|
||||
private static Intersection parallelInt(float[] a, float[] b, float[] c, float[] d, float[] p, float[] q)
|
||||
{
|
||||
if (between(a, b, c) && between(a, b, d))
|
||||
{
|
||||
vCopy(p, c);
|
||||
vCopy(q, d);
|
||||
return Intersection.Overlap;
|
||||
}
|
||||
if (between(c, d, a) && between(c, d, b)) {
|
||||
|
||||
if (between(c, d, a) && between(c, d, b))
|
||||
{
|
||||
vCopy(p, a);
|
||||
vCopy(q, b);
|
||||
return Intersection.Overlap;
|
||||
}
|
||||
if (between(a, b, c) && between(c, d, b)) {
|
||||
|
||||
if (between(a, b, c) && between(c, d, b))
|
||||
{
|
||||
vCopy(p, c);
|
||||
vCopy(q, b);
|
||||
return Intersection.Overlap;
|
||||
}
|
||||
if (between(a, b, c) && between(c, d, a)) {
|
||||
|
||||
if (between(a, b, c) && between(c, d, a))
|
||||
{
|
||||
vCopy(p, c);
|
||||
vCopy(q, a);
|
||||
return Intersection.Overlap;
|
||||
}
|
||||
if (between(a, b, d) && between(c, d, b)) {
|
||||
|
||||
if (between(a, b, d) && between(c, d, b))
|
||||
{
|
||||
vCopy(p, d);
|
||||
vCopy(q, b);
|
||||
return Intersection.Overlap;
|
||||
}
|
||||
if (between(a, b, d) && between(c, d, a)) {
|
||||
|
||||
if (between(a, b, d) && between(c, d, a))
|
||||
{
|
||||
vCopy(p, d);
|
||||
vCopy(q, a);
|
||||
return Intersection.Overlap;
|
||||
}
|
||||
|
||||
return Intersection.None;
|
||||
}
|
||||
|
||||
private static bool between(float[] a, float[] b, float[] c) {
|
||||
if (Math.Abs(a[0] - b[0]) > Math.Abs(a[2] - b[2])) {
|
||||
private static bool between(float[] a, float[] b, float[] c)
|
||||
{
|
||||
if (Math.Abs(a[0] - b[0]) > Math.Abs(a[2] - b[2]))
|
||||
{
|
||||
return ((a[0] <= c[0]) && (c[0] <= b[0])) || ((a[0] >= c[0]) && (c[0] >= b[0]));
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((a[2] <= c[2]) && (c[2] <= b[2])) || ((a[2] >= c[2]) && (c[2] >= b[2]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -22,8 +22,6 @@ using System;
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
using static DetourCommon;
|
||||
|
||||
/**
|
||||
|
@ -51,56 +49,66 @@ using static DetourCommon;
|
|||
*
|
||||
* @see NavMeshQuery
|
||||
*/
|
||||
public class DefaultQueryFilter : QueryFilter {
|
||||
|
||||
public class DefaultQueryFilter : QueryFilter
|
||||
{
|
||||
private int m_excludeFlags;
|
||||
private int m_includeFlags;
|
||||
private readonly float[] m_areaCost = new float[NavMesh.DT_MAX_AREAS];
|
||||
|
||||
public DefaultQueryFilter() {
|
||||
public DefaultQueryFilter()
|
||||
{
|
||||
m_includeFlags = 0xffff;
|
||||
m_excludeFlags = 0;
|
||||
for (int i = 0; i < NavMesh.DT_MAX_AREAS; ++i) {
|
||||
for (int i = 0; i < NavMesh.DT_MAX_AREAS; ++i)
|
||||
{
|
||||
m_areaCost[i] = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public DefaultQueryFilter(int includeFlags, int excludeFlags, float[] areaCost) {
|
||||
public DefaultQueryFilter(int includeFlags, int excludeFlags, float[] areaCost)
|
||||
{
|
||||
m_includeFlags = includeFlags;
|
||||
m_excludeFlags = excludeFlags;
|
||||
for (int i = 0; i < Math.Min(NavMesh.DT_MAX_AREAS, areaCost.Length); ++i) {
|
||||
for (int i = 0; i < Math.Min(NavMesh.DT_MAX_AREAS, areaCost.Length); ++i)
|
||||
{
|
||||
m_areaCost[i] = areaCost[i];
|
||||
}
|
||||
for (int i = areaCost.Length; i < NavMesh.DT_MAX_AREAS; ++i) {
|
||||
|
||||
for (int i = areaCost.Length; i < NavMesh.DT_MAX_AREAS; ++i)
|
||||
{
|
||||
m_areaCost[i] = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public bool passFilter(long refs, MeshTile tile, Poly poly) {
|
||||
public bool passFilter(long refs, MeshTile tile, Poly poly)
|
||||
{
|
||||
return (poly.flags & m_includeFlags) != 0 && (poly.flags & m_excludeFlags) == 0;
|
||||
}
|
||||
|
||||
public float getCost(float[] pa, float[] pb, long prevRef, MeshTile prevTile, Poly prevPoly, long curRef,
|
||||
MeshTile curTile, Poly curPoly, long nextRef, MeshTile nextTile, Poly nextPoly) {
|
||||
MeshTile curTile, Poly curPoly, long nextRef, MeshTile nextTile, Poly nextPoly)
|
||||
{
|
||||
return vDist(pa, pb) * m_areaCost[curPoly.getArea()];
|
||||
}
|
||||
|
||||
public int getIncludeFlags() {
|
||||
public int getIncludeFlags()
|
||||
{
|
||||
return m_includeFlags;
|
||||
}
|
||||
|
||||
public void setIncludeFlags(int flags) {
|
||||
public void setIncludeFlags(int flags)
|
||||
{
|
||||
m_includeFlags = flags;
|
||||
}
|
||||
|
||||
public int getExcludeFlags() {
|
||||
public int getExcludeFlags()
|
||||
{
|
||||
return m_excludeFlags;
|
||||
}
|
||||
|
||||
public void setExcludeFlags(int flags) {
|
||||
public void setExcludeFlags(int flags)
|
||||
{
|
||||
m_excludeFlags = flags;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -18,26 +18,24 @@ freely, subject to the following restrictions:
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
using static DetourCommon;
|
||||
|
||||
public class DefaultQueryHeuristic : QueryHeuristic {
|
||||
|
||||
public class DefaultQueryHeuristic : QueryHeuristic
|
||||
{
|
||||
private readonly float scale;
|
||||
|
||||
public DefaultQueryHeuristic() : this(0.999f)
|
||||
{
|
||||
}
|
||||
|
||||
public DefaultQueryHeuristic(float scale) {
|
||||
public DefaultQueryHeuristic(float scale)
|
||||
{
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
public float getCost(float[] neighbourPos, float[] endPos) {
|
||||
public float getCost(float[] neighbourPos, float[] endPos)
|
||||
{
|
||||
return vDist(neighbourPos, endPos) * scale;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,20 +17,21 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
public class DetourBuilder {
|
||||
|
||||
public MeshData build(NavMeshDataCreateParams option, int tileX, int tileY) {
|
||||
public class DetourBuilder
|
||||
{
|
||||
public MeshData build(NavMeshDataCreateParams option, int tileX, int tileY)
|
||||
{
|
||||
MeshData data = NavMeshBuilder.createNavMeshData(option);
|
||||
if (data != null) {
|
||||
if (data != null)
|
||||
{
|
||||
data.header.x = tileX;
|
||||
data.header.y = tileY;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -22,10 +22,8 @@ using System;
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
public static class DetourCommon {
|
||||
|
||||
public static class DetourCommon
|
||||
{
|
||||
public const float EPS = 1e-4f;
|
||||
|
||||
/// Performs a scaled vector addition. (@p v1 + (@p v2 * @p s))
|
||||
|
@ -33,7 +31,8 @@ public static class DetourCommon {
|
|||
/// @param[in] v1 The base vector. [(x, y, z)]
|
||||
/// @param[in] v2 The vector to scale and add to @p v1. [(x, y, z)]
|
||||
/// @param[in] s The amount to scale @p v2 by before adding to @p v1.
|
||||
public static float[] vMad(float[] v1, float[] v2, float s) {
|
||||
public static float[] vMad(float[] v1, float[] v2, float s)
|
||||
{
|
||||
float[] dest = new float[3];
|
||||
dest[0] = v1[0] + v2[0] * s;
|
||||
dest[1] = v1[1] + v2[1] * s;
|
||||
|
@ -47,7 +46,8 @@ public static class DetourCommon {
|
|||
/// @param[in] v1 The starting vector.
|
||||
/// @param[in] v2 The destination vector.
|
||||
/// @param[in] t The interpolation factor. [Limits: 0 <= value <= 1.0]
|
||||
public static float[] vLerp(float[] verts, int v1, int v2, float t) {
|
||||
public static float[] vLerp(float[] verts, int v1, int v2, float t)
|
||||
{
|
||||
float[] dest = new float[3];
|
||||
dest[0] = verts[v1 + 0] + (verts[v2 + 0] - verts[v1 + 0]) * t;
|
||||
dest[1] = verts[v1 + 1] + (verts[v2 + 1] - verts[v1 + 1]) * t;
|
||||
|
@ -55,7 +55,8 @@ public static class DetourCommon {
|
|||
return dest;
|
||||
}
|
||||
|
||||
public static float[] vLerp(float[] v1, float[] v2, float t) {
|
||||
public static float[] vLerp(float[] v1, float[] v2, float t)
|
||||
{
|
||||
float[] dest = new float[3];
|
||||
dest[0] = v1[0] + (v2[0] - v1[0]) * t;
|
||||
dest[1] = v1[1] + (v2[1] - v1[1]) * t;
|
||||
|
@ -63,7 +64,8 @@ public static class DetourCommon {
|
|||
return dest;
|
||||
}
|
||||
|
||||
public static float[] vSub(VectorPtr v1, VectorPtr v2) {
|
||||
public static float[] vSub(VectorPtr v1, VectorPtr v2)
|
||||
{
|
||||
float[] dest = new float[3];
|
||||
dest[0] = v1.get(0) - v2.get(0);
|
||||
dest[1] = v1.get(1) - v2.get(1);
|
||||
|
@ -71,7 +73,8 @@ public static class DetourCommon {
|
|||
return dest;
|
||||
}
|
||||
|
||||
public static float[] vSub(float[] v1, float[] v2) {
|
||||
public static float[] vSub(float[] v1, float[] v2)
|
||||
{
|
||||
float[] dest = new float[3];
|
||||
dest[0] = v1[0] - v2[0];
|
||||
dest[1] = v1[1] - v2[1];
|
||||
|
@ -79,7 +82,8 @@ public static class DetourCommon {
|
|||
return dest;
|
||||
}
|
||||
|
||||
public static float[] vAdd(float[] v1, float[] v2) {
|
||||
public static float[] vAdd(float[] v1, float[] v2)
|
||||
{
|
||||
float[] dest = new float[3];
|
||||
dest[0] = v1[0] + v2[0];
|
||||
dest[1] = v1[1] + v2[1];
|
||||
|
@ -87,7 +91,8 @@ public static class DetourCommon {
|
|||
return dest;
|
||||
}
|
||||
|
||||
public static float[] vCopy(float[] @in) {
|
||||
public static float[] vCopy(float[] @in)
|
||||
{
|
||||
float[] @out = new float[3];
|
||||
@out[0] = @in[0];
|
||||
@out[1] = @in[1];
|
||||
|
@ -95,31 +100,36 @@ public static class DetourCommon {
|
|||
return @out;
|
||||
}
|
||||
|
||||
public static void vSet(float[] @out, float a, float b, float c) {
|
||||
public static void vSet(float[] @out, float a, float b, float c)
|
||||
{
|
||||
@out[0] = a;
|
||||
@out[1] = b;
|
||||
@out[2] = c;
|
||||
}
|
||||
|
||||
public static void vCopy(float[] @out, float[] @in) {
|
||||
public static void vCopy(float[] @out, float[] @in)
|
||||
{
|
||||
@out[0] = @in[0];
|
||||
@out[1] = @in[1];
|
||||
@out[2] = @in[2];
|
||||
}
|
||||
|
||||
public static void vCopy(float[] @out, float[] @in, int i) {
|
||||
public static void vCopy(float[] @out, float[] @in, int i)
|
||||
{
|
||||
@out[0] = @in[i];
|
||||
@out[1] = @in[i + 1];
|
||||
@out[2] = @in[i + 2];
|
||||
}
|
||||
|
||||
public static void vMin(float[] @out, float[] @in, int i) {
|
||||
public static void vMin(float[] @out, float[] @in, int i)
|
||||
{
|
||||
@out[0] = Math.Min(@out[0], @in[i]);
|
||||
@out[1] = Math.Min(@out[1], @in[i + 1]);
|
||||
@out[2] = Math.Min(@out[2], @in[i + 2]);
|
||||
}
|
||||
|
||||
public static void vMax(float[] @out, float[] @in, int i) {
|
||||
public static void vMax(float[] @out, float[] @in, int i)
|
||||
{
|
||||
@out[0] = Math.Max(@out[0], @in[i]);
|
||||
@out[1] = Math.Max(@out[1], @in[i + 1]);
|
||||
@out[2] = Math.Max(@out[2], @in[i + 2]);
|
||||
|
@ -129,7 +139,8 @@ public static class DetourCommon {
|
|||
/// @param[in] v1 A point. [(x, y, z)]
|
||||
/// @param[in] v2 A point. [(x, y, z)]
|
||||
/// @return The distance between the two points.
|
||||
public static float vDist(float[] v1, float[] v2) {
|
||||
public static float vDist(float[] v1, float[] v2)
|
||||
{
|
||||
float dx = v2[0] - v1[0];
|
||||
float dy = v2[1] - v1[1];
|
||||
float dz = v2[2] - v1[2];
|
||||
|
@ -140,40 +151,47 @@ public static class DetourCommon {
|
|||
/// @param[in] v1 A point. [(x, y, z)]
|
||||
/// @param[in] v2 A point. [(x, y, z)]
|
||||
/// @return The distance between the two points.
|
||||
public static float vDistSqr(float[] v1, float[] v2) {
|
||||
public static float vDistSqr(float[] v1, float[] v2)
|
||||
{
|
||||
float dx = v2[0] - v1[0];
|
||||
float dy = v2[1] - v1[1];
|
||||
float dz = v2[2] - v1[2];
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
public static float sqr(float a) {
|
||||
public static float sqr(float a)
|
||||
{
|
||||
return a * a;
|
||||
}
|
||||
|
||||
/// Derives the square of the scalar length of the vector. (len * len)
|
||||
/// @param[in] v The vector. [(x, y, z)]
|
||||
/// @return The square of the scalar length of the vector.
|
||||
public static float vLenSqr(float[] v) {
|
||||
public static float vLenSqr(float[] v)
|
||||
{
|
||||
return v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
|
||||
}
|
||||
|
||||
public static float vLen(float[] v) {
|
||||
public static float vLen(float[] v)
|
||||
{
|
||||
return (float)Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
||||
}
|
||||
|
||||
public static float vDist(float[] v1, float[] verts, int i) {
|
||||
public static float vDist(float[] v1, float[] verts, int i)
|
||||
{
|
||||
float dx = verts[i] - v1[0];
|
||||
float dy = verts[i + 1] - v1[1];
|
||||
float dz = verts[i + 2] - v1[2];
|
||||
return (float)Math.Sqrt(dx * dx + dy * dy + dz * dz);
|
||||
}
|
||||
|
||||
public static float clamp(float v, float min, float max) {
|
||||
public static float clamp(float v, float min, float max)
|
||||
{
|
||||
return Math.Max(Math.Min(v, max), min);
|
||||
}
|
||||
|
||||
public static int clamp(int v, int min, int max) {
|
||||
public static int clamp(int v, int min, int max)
|
||||
{
|
||||
return Math.Max(Math.Min(v, max), min);
|
||||
}
|
||||
|
||||
|
@ -184,19 +202,22 @@ public static class DetourCommon {
|
|||
///
|
||||
/// The vectors are projected onto the xz-plane, so the y-values are
|
||||
/// ignored.
|
||||
public static float vDist2D(float[] v1, float[] v2) {
|
||||
public static float vDist2D(float[] v1, float[] v2)
|
||||
{
|
||||
float dx = v2[0] - v1[0];
|
||||
float dz = v2[2] - v1[2];
|
||||
return (float)Math.Sqrt(dx * dx + dz * dz);
|
||||
}
|
||||
|
||||
public static float vDist2DSqr(float[] v1, float[] v2) {
|
||||
public static float vDist2DSqr(float[] v1, float[] v2)
|
||||
{
|
||||
float dx = v2[0] - v1[0];
|
||||
float dz = v2[2] - v1[2];
|
||||
return dx * dx + dz * dz;
|
||||
}
|
||||
|
||||
public static float vDist2DSqr(float[] p, float[] verts, int i) {
|
||||
public static float vDist2DSqr(float[] p, float[] verts, int i)
|
||||
{
|
||||
float dx = verts[i] - p[0];
|
||||
float dz = verts[i + 2] - p[2];
|
||||
return dx * dx + dz * dz;
|
||||
|
@ -204,9 +225,11 @@ public static class DetourCommon {
|
|||
|
||||
/// Normalizes the vector.
|
||||
/// @param[in,out] v The vector to normalize. [(x, y, z)]
|
||||
public static void vNormalize(float[] v) {
|
||||
public static void vNormalize(float[] v)
|
||||
{
|
||||
float d = (float)(1.0f / Math.Sqrt(sqr(v[0]) + sqr(v[1]) + sqr(v[2])));
|
||||
if (d != 0) {
|
||||
if (d != 0)
|
||||
{
|
||||
v[0] *= d;
|
||||
v[1] *= d;
|
||||
v[2] *= d;
|
||||
|
@ -222,11 +245,13 @@ public static class DetourCommon {
|
|||
///
|
||||
/// Basically, this function will return true if the specified points are
|
||||
/// close enough to eachother to be considered colocated.
|
||||
public static bool vEqual(float[] p0, float[] p1) {
|
||||
public static bool vEqual(float[] p0, float[] p1)
|
||||
{
|
||||
return vEqual(p0, p1, EQUAL_THRESHOLD);
|
||||
}
|
||||
|
||||
public static bool vEqual(float[] p0, float[] p1, float thresholdSqr) {
|
||||
public static bool vEqual(float[] p0, float[] p1, float thresholdSqr)
|
||||
{
|
||||
float d = vDistSqr(p0, p1);
|
||||
return d < thresholdSqr;
|
||||
}
|
||||
|
@ -238,11 +263,13 @@ public static class DetourCommon {
|
|||
///
|
||||
/// The vectors are projected onto the xz-plane, so the y-values are
|
||||
/// ignored.
|
||||
public static float vDot2D(float[] u, float[] v) {
|
||||
public static float vDot2D(float[] u, float[] v)
|
||||
{
|
||||
return u[0] * v[0] + u[2] * v[2];
|
||||
}
|
||||
|
||||
public static float vDot2D(float[] u, float[] v, int vi) {
|
||||
public static float vDot2D(float[] u, float[] v, int vi)
|
||||
{
|
||||
return u[0] * v[vi] + u[2] * v[vi + 2];
|
||||
}
|
||||
|
||||
|
@ -253,21 +280,22 @@ public static class DetourCommon {
|
|||
///
|
||||
/// The vectors are projected onto the xz-plane, so the y-values are
|
||||
/// ignored.
|
||||
public static float vPerp2D(float[] u, float[] v) {
|
||||
public static float vPerp2D(float[] u, float[] v)
|
||||
{
|
||||
return u[2] * v[0] - u[0] * v[2];
|
||||
}
|
||||
|
||||
/// @}
|
||||
/// @name Computational geometry helper functions.
|
||||
/// @{
|
||||
|
||||
/// Derives the signed xz-plane area of the triangle ABC, or the
|
||||
/// relationship of line AB to point C.
|
||||
/// @param[in] a Vertex A. [(x, y, z)]
|
||||
/// @param[in] b Vertex B. [(x, y, z)]
|
||||
/// @param[in] c Vertex C. [(x, y, z)]
|
||||
/// @return The signed xz-plane area of the triangle.
|
||||
public static float triArea2D(float[] verts, int a, int b, int c) {
|
||||
public static float triArea2D(float[] verts, int a, int b, int c)
|
||||
{
|
||||
float abx = verts[b] - verts[a];
|
||||
float abz = verts[b + 2] - verts[a + 2];
|
||||
float acx = verts[c] - verts[a];
|
||||
|
@ -275,7 +303,8 @@ public static class DetourCommon {
|
|||
return acx * abz - abx * acz;
|
||||
}
|
||||
|
||||
public static float triArea2D(float[] a, float[] b, float[] c) {
|
||||
public static float triArea2D(float[] a, float[] b, float[] c)
|
||||
{
|
||||
float abx = b[0] - a[0];
|
||||
float abz = b[2] - a[2];
|
||||
float acx = c[0] - a[0];
|
||||
|
@ -290,7 +319,8 @@ public static class DetourCommon {
|
|||
/// @param[in] bmax Maximum bounds of box B. [(x, y, z)]
|
||||
/// @return True if the two AABB's overlap.
|
||||
/// @see dtOverlapBounds
|
||||
public static bool overlapQuantBounds(int[] amin, int[] amax, int[] bmin, int[] bmax) {
|
||||
public static bool overlapQuantBounds(int[] amin, int[] amax, int[] bmin, int[] bmax)
|
||||
{
|
||||
bool overlap = true;
|
||||
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
|
||||
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
|
||||
|
@ -305,7 +335,8 @@ public static class DetourCommon {
|
|||
/// @param[in] bmax Maximum bounds of box B. [(x, y, z)]
|
||||
/// @return True if the two AABB's overlap.
|
||||
/// @see dtOverlapQuantBounds
|
||||
public static bool overlapBounds(float[] amin, float[] amax, float[] bmin, float[] bmax) {
|
||||
public static bool overlapBounds(float[] amin, float[] amax, float[] bmin, float[] bmax)
|
||||
{
|
||||
bool overlap = true;
|
||||
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
|
||||
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
|
||||
|
@ -313,48 +344,59 @@ public static class DetourCommon {
|
|||
return overlap;
|
||||
}
|
||||
|
||||
public static Tuple<float, float> distancePtSegSqr2D(float[] pt, float[] p, float[] q) {
|
||||
public static Tuple<float, float> distancePtSegSqr2D(float[] pt, float[] p, float[] q)
|
||||
{
|
||||
float pqx = q[0] - p[0];
|
||||
float pqz = q[2] - p[2];
|
||||
float dx = pt[0] - p[0];
|
||||
float dz = pt[2] - p[2];
|
||||
float d = pqx * pqx + pqz * pqz;
|
||||
float t = pqx * dx + pqz * dz;
|
||||
if (d > 0) {
|
||||
if (d > 0)
|
||||
{
|
||||
t /= d;
|
||||
}
|
||||
if (t < 0) {
|
||||
|
||||
if (t < 0)
|
||||
{
|
||||
t = 0;
|
||||
} else if (t > 1) {
|
||||
}
|
||||
else if (t > 1)
|
||||
{
|
||||
t = 1;
|
||||
}
|
||||
|
||||
dx = p[0] + t * pqx - pt[0];
|
||||
dz = p[2] + t * pqz - pt[2];
|
||||
return Tuple.Create(dx * dx + dz * dz, t);
|
||||
}
|
||||
|
||||
public static float? closestHeightPointTriangle(float[] p, float[] a, float[] b, float[] c) {
|
||||
public static float? closestHeightPointTriangle(float[] p, float[] a, float[] b, float[] c)
|
||||
{
|
||||
float[] v0 = vSub(c, a);
|
||||
float[] v1 = vSub(b, a);
|
||||
float[] v2 = vSub(p, a);
|
||||
|
||||
// Compute scaled barycentric coordinates
|
||||
float denom = v0[0] * v1[2] - v0[2] * v1[0];
|
||||
if (Math.Abs(denom) < EPS) {
|
||||
if (Math.Abs(denom) < EPS)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
float u = v1[2] * v2[0] - v1[0] * v2[2];
|
||||
float v = v0[0] * v2[2] - v0[2] * v2[0];
|
||||
|
||||
if (denom < 0) {
|
||||
if (denom < 0)
|
||||
{
|
||||
denom = -denom;
|
||||
u = -u;
|
||||
v = -v;
|
||||
}
|
||||
|
||||
// If point lies inside the triangle, return interpolated ycoord.
|
||||
if (u >= 0.0f && v >= 0.0f && (u + v) <= denom) {
|
||||
if (u >= 0.0f && v >= 0.0f && (u + v) <= denom)
|
||||
{
|
||||
float h = a[1] + (v0[1] * u + v1[1] * v) / denom;
|
||||
return h;
|
||||
}
|
||||
|
@ -365,51 +407,64 @@ public static class DetourCommon {
|
|||
/// @par
|
||||
///
|
||||
/// All points are projected onto the xz-plane, so the y-values are ignored.
|
||||
public static bool pointInPolygon(float[] pt, float[] verts, int nverts) {
|
||||
public static bool pointInPolygon(float[] pt, float[] verts, int nverts)
|
||||
{
|
||||
// TODO: Replace pnpoly with triArea2D tests?
|
||||
int i, j;
|
||||
bool c = false;
|
||||
for (i = 0, j = nverts - 1; i < nverts; j = i++) {
|
||||
for (i = 0, j = nverts - 1; i < nverts; j = i++)
|
||||
{
|
||||
int vi = i * 3;
|
||||
int vj = j * 3;
|
||||
if (((verts[vi + 2] > pt[2]) != (verts[vj + 2] > pt[2])) && (pt[0] < (verts[vj + 0] - verts[vi + 0])
|
||||
* (pt[2] - verts[vi + 2]) / (verts[vj + 2] - verts[vi + 2]) + verts[vi + 0])) {
|
||||
* (pt[2] - verts[vi + 2]) / (verts[vj + 2] - verts[vi + 2]) + verts[vi + 0]))
|
||||
{
|
||||
c = !c;
|
||||
}
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
public static bool distancePtPolyEdgesSqr(float[] pt, float[] verts, int nverts, float[] ed, float[] et) {
|
||||
public static bool distancePtPolyEdgesSqr(float[] pt, float[] verts, int nverts, float[] ed, float[] et)
|
||||
{
|
||||
// TODO: Replace pnpoly with triArea2D tests?
|
||||
int i, j;
|
||||
bool c = false;
|
||||
for (i = 0, j = nverts - 1; i < nverts; j = i++) {
|
||||
for (i = 0, j = nverts - 1; i < nverts; j = i++)
|
||||
{
|
||||
int vi = i * 3;
|
||||
int vj = j * 3;
|
||||
if (((verts[vi + 2] > pt[2]) != (verts[vj + 2] > pt[2])) && (pt[0] < (verts[vj + 0] - verts[vi + 0])
|
||||
* (pt[2] - verts[vi + 2]) / (verts[vj + 2] - verts[vi + 2]) + verts[vi + 0])) {
|
||||
* (pt[2] - verts[vi + 2]) / (verts[vj + 2] - verts[vi + 2]) + verts[vi + 0]))
|
||||
{
|
||||
c = !c;
|
||||
}
|
||||
|
||||
Tuple<float, float> edet = distancePtSegSqr2D(pt, verts, vj, vi);
|
||||
ed[j] = edet.Item1;
|
||||
et[j] = edet.Item2;
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
public static float[] projectPoly(float[] axis, float[] poly, int npoly) {
|
||||
public static float[] projectPoly(float[] axis, float[] poly, int npoly)
|
||||
{
|
||||
float rmin, rmax;
|
||||
rmin = rmax = vDot2D(axis, poly, 0);
|
||||
for (int i = 1; i < npoly; ++i) {
|
||||
for (int i = 1; i < npoly; ++i)
|
||||
{
|
||||
float d = vDot2D(axis, poly, i * 3);
|
||||
rmin = Math.Min(rmin, d);
|
||||
rmax = Math.Max(rmax, d);
|
||||
}
|
||||
|
||||
return new float[] { rmin, rmax };
|
||||
}
|
||||
|
||||
public static bool overlapRange(float amin, float amax, float bmin, float bmax, float eps) {
|
||||
public static bool overlapRange(float amin, float amax, float bmin, float bmax, float eps)
|
||||
{
|
||||
return ((amin + eps) > bmax || (amax - eps) < bmin) ? false : true;
|
||||
}
|
||||
|
||||
|
@ -418,9 +473,10 @@ public static class DetourCommon {
|
|||
/// @par
|
||||
///
|
||||
/// All vertices are projected onto the xz-plane, so the y-values are ignored.
|
||||
public static bool overlapPolyPoly2D(float[] polya, int npolya, float[] polyb, int npolyb) {
|
||||
|
||||
for (int i = 0, j = npolya - 1; i < npolya; j = i++) {
|
||||
public static bool overlapPolyPoly2D(float[] polya, int npolya, float[] polyb, int npolyb)
|
||||
{
|
||||
for (int i = 0, j = npolya - 1; i < npolya; j = i++)
|
||||
{
|
||||
int va = j * 3;
|
||||
int vb = i * 3;
|
||||
|
||||
|
@ -428,12 +484,15 @@ public static class DetourCommon {
|
|||
|
||||
float[] aminmax = projectPoly(n, polya, npolya);
|
||||
float[] bminmax = projectPoly(n, polyb, npolyb);
|
||||
if (!overlapRange(aminmax[0], aminmax[1], bminmax[0], bminmax[1], eps)) {
|
||||
if (!overlapRange(aminmax[0], aminmax[1], bminmax[0], bminmax[1], eps))
|
||||
{
|
||||
// Found separating axis
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (int i = 0, j = npolyb - 1; i < npolyb; j = i++) {
|
||||
|
||||
for (int i = 0, j = npolyb - 1; i < npolyb; j = i++)
|
||||
{
|
||||
int va = j * 3;
|
||||
int vb = i * 3;
|
||||
|
||||
|
@ -441,35 +500,43 @@ public static class DetourCommon {
|
|||
|
||||
float[] aminmax = projectPoly(n, polya, npolya);
|
||||
float[] bminmax = projectPoly(n, polyb, npolyb);
|
||||
if (!overlapRange(aminmax[0], aminmax[1], bminmax[0], bminmax[1], eps)) {
|
||||
if (!overlapRange(aminmax[0], aminmax[1], bminmax[0], bminmax[1], eps))
|
||||
{
|
||||
// Found separating axis
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns a random point in a convex polygon.
|
||||
// Adapted from Graphics Gems article.
|
||||
public static float[] randomPointInConvexPoly(float[] pts, int npts, float[] areas, float s, float t) {
|
||||
public static float[] randomPointInConvexPoly(float[] pts, int npts, float[] areas, float s, float t)
|
||||
{
|
||||
// Calc triangle araes
|
||||
float areasum = 0.0f;
|
||||
for (int i = 2; i < npts; i++) {
|
||||
for (int i = 2; i < npts; i++)
|
||||
{
|
||||
areas[i] = triArea2D(pts, 0, (i - 1) * 3, i * 3);
|
||||
areasum += Math.Max(0.001f, areas[i]);
|
||||
}
|
||||
|
||||
// Find sub triangle weighted by area.
|
||||
float thr = s * areasum;
|
||||
float acc = 0.0f;
|
||||
float u = 1.0f;
|
||||
int tri = npts - 1;
|
||||
for (int i = 2; i < npts; i++) {
|
||||
for (int i = 2; i < npts; i++)
|
||||
{
|
||||
float dacc = areas[i];
|
||||
if (thr >= acc && thr < (acc + dacc)) {
|
||||
if (thr >= acc && thr < (acc + dacc))
|
||||
{
|
||||
u = (thr - acc) / dacc;
|
||||
tri = i;
|
||||
break;
|
||||
}
|
||||
|
||||
acc += dacc;
|
||||
}
|
||||
|
||||
|
@ -482,12 +549,16 @@ public static class DetourCommon {
|
|||
int pb = (tri - 1) * 3;
|
||||
int pc = tri * 3;
|
||||
|
||||
return new float[] { a * pts[pa] + b * pts[pb] + c * pts[pc],
|
||||
return new float[]
|
||||
{
|
||||
a * pts[pa] + b * pts[pb] + c * pts[pc],
|
||||
a * pts[pa + 1] + b * pts[pb + 1] + c * pts[pc + 1],
|
||||
a * pts[pa + 2] + b * pts[pb + 2] + c * pts[pc + 2] };
|
||||
a * pts[pa + 2] + b * pts[pb + 2] + c * pts[pc + 2]
|
||||
};
|
||||
}
|
||||
|
||||
public static int nextPow2(int v) {
|
||||
public static int nextPow2(int v)
|
||||
{
|
||||
v--;
|
||||
v |= v >> 1;
|
||||
v |= v >> 2;
|
||||
|
@ -498,7 +569,8 @@ public static class DetourCommon {
|
|||
return v;
|
||||
}
|
||||
|
||||
public static int ilog2(int v) {
|
||||
public static int ilog2(int v)
|
||||
{
|
||||
int r;
|
||||
int shift;
|
||||
r = (v > 0xffff ? 1 : 0) << 4;
|
||||
|
@ -516,7 +588,8 @@ public static class DetourCommon {
|
|||
return r;
|
||||
}
|
||||
|
||||
public class IntersectResult {
|
||||
public class IntersectResult
|
||||
{
|
||||
public bool intersects;
|
||||
public float tmin;
|
||||
public float tmax = 1f;
|
||||
|
@ -524,83 +597,107 @@ public static class DetourCommon {
|
|||
public int segMax = -1;
|
||||
}
|
||||
|
||||
public static IntersectResult intersectSegmentPoly2D(float[] p0, float[] p1, float[] verts, int nverts) {
|
||||
|
||||
public static IntersectResult intersectSegmentPoly2D(float[] p0, float[] p1, float[] verts, int nverts)
|
||||
{
|
||||
IntersectResult result = new IntersectResult();
|
||||
float EPS = 0.00000001f;
|
||||
float[] dir = vSub(p1, p0);
|
||||
|
||||
VectorPtr p0v = new VectorPtr(p0);
|
||||
for (int i = 0, j = nverts - 1; i < nverts; j = i++) {
|
||||
for (int i = 0, j = nverts - 1; i < nverts; j = i++)
|
||||
{
|
||||
VectorPtr vpj = new VectorPtr(verts, j * 3);
|
||||
float[] edge = vSub(new VectorPtr(verts, i * 3), vpj);
|
||||
float[] diff = vSub(p0v, vpj);
|
||||
float n = vPerp2D(edge, diff);
|
||||
float d = vPerp2D(dir, edge);
|
||||
if (Math.Abs(d) < EPS) {
|
||||
if (Math.Abs(d) < EPS)
|
||||
{
|
||||
// S is nearly parallel to this edge
|
||||
if (n < 0) {
|
||||
if (n < 0)
|
||||
{
|
||||
return result;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
float t = n / d;
|
||||
if (d < 0) {
|
||||
if (d < 0)
|
||||
{
|
||||
// segment S is entering across this edge
|
||||
if (t > result.tmin) {
|
||||
if (t > result.tmin)
|
||||
{
|
||||
result.tmin = t;
|
||||
result.segMin = j;
|
||||
// S enters after leaving polygon
|
||||
if (result.tmin > result.tmax) {
|
||||
if (result.tmin > result.tmax)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// segment S is leaving across this edge
|
||||
if (t < result.tmax) {
|
||||
if (t < result.tmax)
|
||||
{
|
||||
result.tmax = t;
|
||||
result.segMax = j;
|
||||
// S leaves before entering polygon
|
||||
if (result.tmax < result.tmin) {
|
||||
if (result.tmax < result.tmin)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.intersects = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Tuple<float, float> distancePtSegSqr2D(float[] pt, float[] verts, int p, int q) {
|
||||
public static Tuple<float, float> distancePtSegSqr2D(float[] pt, float[] verts, int p, int q)
|
||||
{
|
||||
float pqx = verts[q + 0] - verts[p + 0];
|
||||
float pqz = verts[q + 2] - verts[p + 2];
|
||||
float dx = pt[0] - verts[p + 0];
|
||||
float dz = pt[2] - verts[p + 2];
|
||||
float d = pqx * pqx + pqz * pqz;
|
||||
float t = pqx * dx + pqz * dz;
|
||||
if (d > 0) {
|
||||
if (d > 0)
|
||||
{
|
||||
t /= d;
|
||||
}
|
||||
if (t < 0) {
|
||||
|
||||
if (t < 0)
|
||||
{
|
||||
t = 0;
|
||||
} else if (t > 1) {
|
||||
}
|
||||
else if (t > 1)
|
||||
{
|
||||
t = 1;
|
||||
}
|
||||
|
||||
dx = verts[p + 0] + t * pqx - pt[0];
|
||||
dz = verts[p + 2] + t * pqz - pt[2];
|
||||
return Tuple.Create(dx * dx + dz * dz, t);
|
||||
}
|
||||
|
||||
public static int oppositeTile(int side) {
|
||||
public static int oppositeTile(int side)
|
||||
{
|
||||
return (side + 4) & 0x7;
|
||||
}
|
||||
|
||||
public static float vperpXZ(float[] a, float[] b) {
|
||||
public static float vperpXZ(float[] a, float[] b)
|
||||
{
|
||||
return a[0] * b[2] - a[2] * b[0];
|
||||
}
|
||||
|
||||
public static Tuple<float, float>? intersectSegSeg2D(float[] ap, float[] aq, float[] bp, float[] bq) {
|
||||
public static Tuple<float, float>? intersectSegSeg2D(float[] ap, float[] aq, float[] bp, float[] bq)
|
||||
{
|
||||
float[] u = vSub(aq, ap);
|
||||
float[] v = vSub(bq, bp);
|
||||
float[] w = vSub(ap, bp);
|
||||
|
@ -609,12 +706,14 @@ public static class DetourCommon {
|
|||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
float s = vperpXZ(v, w) / d;
|
||||
float t = vperpXZ(u, w) / d;
|
||||
return Tuple.Create(s, t);
|
||||
}
|
||||
|
||||
public static float[] vScale(float[] @in, float scale) {
|
||||
public static float[] vScale(float[] @in, float scale)
|
||||
{
|
||||
float[] @out = new float[3];
|
||||
@out[0] = @in[0] * scale;
|
||||
@out[1] = @in[1] * scale;
|
||||
|
@ -626,16 +725,16 @@ public static class DetourCommon {
|
|||
/// @param[in] v A point. [(x, y, z)]
|
||||
/// @return True if all of the point's components are finite, i.e. not NaN
|
||||
/// or any of the infinities.
|
||||
public static bool vIsFinite(float[] v) {
|
||||
public static bool vIsFinite(float[] v)
|
||||
{
|
||||
return float.IsFinite(v[0]) && float.IsFinite(v[1]) && float.IsFinite(v[2]);
|
||||
}
|
||||
|
||||
/// Checks that the specified vector's 2D components are finite.
|
||||
/// @param[in] v A point. [(x, y, z)]
|
||||
public static bool vIsFinite2D(float[] v) {
|
||||
public static bool vIsFinite2D(float[] v)
|
||||
{
|
||||
return float.IsFinite(v[0]) && float.IsFinite(v[2]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,33 +17,36 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
//TODO: (PP) Add comments
|
||||
public class FindDistanceToWallResult {
|
||||
public class FindDistanceToWallResult
|
||||
{
|
||||
private readonly float distance;
|
||||
private readonly float[] position;
|
||||
private readonly float[] normal;
|
||||
|
||||
public FindDistanceToWallResult(float distance, float[] position, float[] normal) {
|
||||
public FindDistanceToWallResult(float distance, float[] position, float[] normal)
|
||||
{
|
||||
this.distance = distance;
|
||||
this.position = position;
|
||||
this.normal = normal;
|
||||
}
|
||||
|
||||
public float getDistance() {
|
||||
public float getDistance()
|
||||
{
|
||||
return distance;
|
||||
}
|
||||
|
||||
public float[] getPosition() {
|
||||
public float[] getPosition()
|
||||
{
|
||||
return position;
|
||||
}
|
||||
|
||||
public float[] getNormal() {
|
||||
public float[] getNormal()
|
||||
{
|
||||
return normal;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -22,25 +22,26 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
//TODO: (PP) Add comments
|
||||
public class FindLocalNeighbourhoodResult {
|
||||
public class FindLocalNeighbourhoodResult
|
||||
{
|
||||
private readonly List<long> refs;
|
||||
private readonly List<long> parentRefs;
|
||||
|
||||
public FindLocalNeighbourhoodResult(List<long> refs, List<long> parentRefs) {
|
||||
public FindLocalNeighbourhoodResult(List<long> refs, List<long> parentRefs)
|
||||
{
|
||||
this.@refs = refs;
|
||||
this.parentRefs = parentRefs;
|
||||
}
|
||||
|
||||
public List<long> getRefs() {
|
||||
public List<long> getRefs()
|
||||
{
|
||||
return refs;
|
||||
}
|
||||
|
||||
public List<long> getParentRefs() {
|
||||
public List<long> getParentRefs()
|
||||
{
|
||||
return parentRefs;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -2,12 +2,10 @@ using System;
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
using static DetourCommon;
|
||||
|
||||
public class FindNearestPolyQuery : PolyQuery {
|
||||
|
||||
public class FindNearestPolyQuery : PolyQuery
|
||||
{
|
||||
private readonly NavMeshQuery query;
|
||||
private readonly float[] center;
|
||||
private long nearestRef;
|
||||
|
@ -15,14 +13,16 @@ public class FindNearestPolyQuery : PolyQuery {
|
|||
private bool overPoly;
|
||||
private float nearestDistanceSqr;
|
||||
|
||||
public FindNearestPolyQuery(NavMeshQuery query, float[] center) {
|
||||
public FindNearestPolyQuery(NavMeshQuery query, float[] center)
|
||||
{
|
||||
this.query = query;
|
||||
this.center = center;
|
||||
nearestDistanceSqr = float.MaxValue;
|
||||
nearestPt = new float[] { center[0], center[1], center[2] };
|
||||
}
|
||||
|
||||
public void process(MeshTile tile, Poly poly, long refs) {
|
||||
public void process(MeshTile tile, Poly poly, long refs)
|
||||
{
|
||||
// Find nearest polygon amongst the nearby polygons.
|
||||
Result<ClosestPointOnPolyResult> closest = query.closestPointOnPoly(refs, center);
|
||||
bool posOverPoly = closest.result.isPosOverPoly();
|
||||
|
@ -32,26 +32,28 @@ public class FindNearestPolyQuery : PolyQuery {
|
|||
// climb height, favor that instead of straight line nearest point.
|
||||
float d = 0;
|
||||
float[] diff = vSub(center, closestPtPoly);
|
||||
if (posOverPoly) {
|
||||
if (posOverPoly)
|
||||
{
|
||||
d = Math.Abs(diff[1]) - tile.data.header.walkableClimb;
|
||||
d = d > 0 ? d * d : 0;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
d = vLenSqr(diff);
|
||||
}
|
||||
|
||||
if (d < nearestDistanceSqr) {
|
||||
if (d < nearestDistanceSqr)
|
||||
{
|
||||
nearestPt = closestPtPoly;
|
||||
nearestDistanceSqr = d;
|
||||
nearestRef = refs;
|
||||
overPoly = posOverPoly;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public FindNearestPolyResult result() {
|
||||
public FindNearestPolyResult result()
|
||||
{
|
||||
return new FindNearestPolyResult(nearestRef, nearestPt, overPoly);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,35 +17,37 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
public class FindNearestPolyResult {
|
||||
public class FindNearestPolyResult
|
||||
{
|
||||
private readonly long nearestRef;
|
||||
private readonly float[] nearestPos;
|
||||
private readonly bool overPoly;
|
||||
|
||||
public FindNearestPolyResult(long nearestRef, float[] nearestPos, bool overPoly) {
|
||||
public FindNearestPolyResult(long nearestRef, float[] nearestPos, bool overPoly)
|
||||
{
|
||||
this.nearestRef = nearestRef;
|
||||
this.nearestPos = nearestPos;
|
||||
this.overPoly = overPoly;
|
||||
}
|
||||
|
||||
/** Returns the reference id of the nearest polygon. 0 if no polygon is found. */
|
||||
public long getNearestRef() {
|
||||
public long getNearestRef()
|
||||
{
|
||||
return nearestRef;
|
||||
}
|
||||
|
||||
/** Returns the nearest point on the polygon. [opt] [(x, y, z)]. Unchanged if no polygon is found. */
|
||||
public float[] getNearestPos() {
|
||||
public float[] getNearestPos()
|
||||
{
|
||||
return nearestPos;
|
||||
}
|
||||
|
||||
public bool isOverPoly() {
|
||||
public bool isOverPoly()
|
||||
{
|
||||
return overPoly;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,32 +22,33 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
|
||||
// TODO: (PP) Add comments
|
||||
public class FindPolysAroundResult {
|
||||
public class FindPolysAroundResult
|
||||
{
|
||||
private readonly List<long> refs;
|
||||
private readonly List<long> parentRefs;
|
||||
private readonly List<float> costs;
|
||||
|
||||
public FindPolysAroundResult(List<long> refs, List<long> parentRefs, List<float> costs) {
|
||||
public FindPolysAroundResult(List<long> refs, List<long> parentRefs, List<float> costs)
|
||||
{
|
||||
this.@refs = refs;
|
||||
this.parentRefs = parentRefs;
|
||||
this.costs = costs;
|
||||
}
|
||||
|
||||
public List<long> getRefs() {
|
||||
public List<long> getRefs()
|
||||
{
|
||||
return refs;
|
||||
}
|
||||
|
||||
public List<long> getParentRefs() {
|
||||
public List<long> getParentRefs()
|
||||
{
|
||||
return parentRefs;
|
||||
}
|
||||
|
||||
public List<float> getCosts() {
|
||||
public List<float> getCosts()
|
||||
{
|
||||
return costs;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -17,29 +17,31 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
//TODO: (PP) Add comments
|
||||
public class FindRandomPointResult {
|
||||
public class FindRandomPointResult
|
||||
{
|
||||
private readonly long randomRef;
|
||||
private readonly float[] randomPt;
|
||||
|
||||
public FindRandomPointResult(long randomRef, float[] randomPt) {
|
||||
public FindRandomPointResult(long randomRef, float[] randomPt)
|
||||
{
|
||||
this.randomRef = randomRef;
|
||||
this.randomPt = randomPt;
|
||||
}
|
||||
|
||||
/// @param[out] randomRef The reference id of the random location.
|
||||
public long getRandomRef() {
|
||||
public long getRandomRef()
|
||||
{
|
||||
return randomRef;
|
||||
}
|
||||
|
||||
/// @param[out] randomPt The random location.
|
||||
public float[] getRandomPt() {
|
||||
public float[] getRandomPt()
|
||||
{
|
||||
return randomPt;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -22,27 +22,25 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
|
||||
public class GetPolyWallSegmentsResult {
|
||||
|
||||
public class GetPolyWallSegmentsResult
|
||||
{
|
||||
private readonly List<float[]> segmentVerts;
|
||||
private readonly List<long> segmentRefs;
|
||||
|
||||
public GetPolyWallSegmentsResult(List<float[]> segmentVerts, List<long> segmentRefs) {
|
||||
public GetPolyWallSegmentsResult(List<float[]> segmentVerts, List<long> segmentRefs)
|
||||
{
|
||||
this.segmentVerts = segmentVerts;
|
||||
this.segmentRefs = segmentRefs;
|
||||
}
|
||||
|
||||
public List<float[]> getSegmentVerts() {
|
||||
public List<float[]> getSegmentVerts()
|
||||
{
|
||||
return segmentVerts;
|
||||
}
|
||||
|
||||
public List<long> getSegmentRefs() {
|
||||
public List<long> getSegmentRefs()
|
||||
{
|
||||
return segmentRefs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,43 +22,54 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Detour.Io
|
||||
{
|
||||
|
||||
|
||||
public abstract class DetourWriter {
|
||||
|
||||
protected void write(BinaryWriter stream, float value, ByteOrder order) {
|
||||
public abstract class DetourWriter
|
||||
{
|
||||
protected void write(BinaryWriter stream, float value, ByteOrder order)
|
||||
{
|
||||
byte[] bytes = BitConverter.GetBytes(value);
|
||||
int i = BitConverter.ToInt32(bytes, 0);
|
||||
write(stream, i, order);
|
||||
}
|
||||
|
||||
protected void write(BinaryWriter stream, short value, ByteOrder order) {
|
||||
if (order == ByteOrder.BIG_ENDIAN) {
|
||||
protected void write(BinaryWriter stream, short value, ByteOrder order)
|
||||
{
|
||||
if (order == ByteOrder.BIG_ENDIAN)
|
||||
{
|
||||
stream.Write((byte)((value >> 8) & 0xFF));
|
||||
stream.Write((byte)(value & 0xFF));
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.Write((byte)(value & 0xFF));
|
||||
stream.Write((byte)((value >> 8) & 0xFF));
|
||||
}
|
||||
}
|
||||
|
||||
protected void write(BinaryWriter stream, long value, ByteOrder order) {
|
||||
if (order == ByteOrder.BIG_ENDIAN) {
|
||||
protected void write(BinaryWriter stream, long value, ByteOrder order)
|
||||
{
|
||||
if (order == ByteOrder.BIG_ENDIAN)
|
||||
{
|
||||
write(stream, (int)((ulong)value >> 32), order);
|
||||
write(stream, (int)(value & 0xFFFFFFFF), order);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
write(stream, (int)(value & 0xFFFFFFFF), order);
|
||||
write(stream, (int)((ulong)value >> 32), order);
|
||||
}
|
||||
}
|
||||
|
||||
protected void write(BinaryWriter stream, int value, ByteOrder order) {
|
||||
if (order == ByteOrder.BIG_ENDIAN) {
|
||||
protected void write(BinaryWriter stream, int value, ByteOrder order)
|
||||
{
|
||||
if (order == ByteOrder.BIG_ENDIAN)
|
||||
{
|
||||
stream.Write((byte)((value >> 24) & 0xFF));
|
||||
stream.Write((byte)((value >> 16) & 0xFF));
|
||||
stream.Write((byte)((value >> 8) & 0xFF));
|
||||
stream.Write((byte)(value & 0xFF));
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.Write((byte)(value & 0xFF));
|
||||
stream.Write((byte)((value >> 8) & 0xFF));
|
||||
stream.Write((byte)((value >> 16) & 0xFF));
|
||||
|
@ -66,21 +77,22 @@ public abstract class DetourWriter {
|
|||
}
|
||||
}
|
||||
|
||||
protected void write(BinaryWriter stream, bool @bool) {
|
||||
protected void write(BinaryWriter stream, bool @bool)
|
||||
{
|
||||
write(stream, (byte)(@bool ? 1 : 0));
|
||||
}
|
||||
|
||||
protected void write(BinaryWriter stream, byte value) {
|
||||
protected void write(BinaryWriter stream, byte value)
|
||||
{
|
||||
stream.Write(value);
|
||||
}
|
||||
|
||||
protected void write(BinaryWriter stream, MemoryStream data) {
|
||||
protected void write(BinaryWriter stream, MemoryStream data)
|
||||
{
|
||||
data.Position = 0;
|
||||
byte[] buffer = new byte[data.Length];
|
||||
data.Read(buffer, 0, buffer.Length);
|
||||
stream.Write(buffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,24 +22,26 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Detour.Io
|
||||
{
|
||||
|
||||
|
||||
public static class IOUtils {
|
||||
|
||||
public static ByteBuffer toByteBuffer(BinaryReader @is, bool direct) {
|
||||
public static class IOUtils
|
||||
{
|
||||
public static ByteBuffer toByteBuffer(BinaryReader @is, bool direct)
|
||||
{
|
||||
byte[] data = toByteArray(@is);
|
||||
if (direct) {
|
||||
if (direct)
|
||||
{
|
||||
Array.Reverse(data);
|
||||
}
|
||||
|
||||
return new ByteBuffer(data);
|
||||
}
|
||||
|
||||
public static byte[] toByteArray(BinaryReader inputStream) {
|
||||
public static byte[] toByteArray(BinaryReader inputStream)
|
||||
{
|
||||
using var baos = new MemoryStream();
|
||||
byte[] buffer = new byte[4096];
|
||||
int l;
|
||||
while ((l = inputStream.Read(buffer)) > 0) {
|
||||
while ((l = inputStream.Read(buffer)) > 0)
|
||||
{
|
||||
baos.Write(buffer, 0, l);
|
||||
}
|
||||
|
||||
|
@ -59,5 +61,4 @@ public static class IOUtils {
|
|||
return (int)s;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -21,27 +21,29 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Detour.Io
|
||||
{
|
||||
|
||||
|
||||
public class MeshDataReader {
|
||||
|
||||
public class MeshDataReader
|
||||
{
|
||||
public const int DT_POLY_DETAIL_SIZE = 10;
|
||||
|
||||
public MeshData read(BinaryReader stream, int maxVertPerPoly) {
|
||||
public MeshData read(BinaryReader stream, int maxVertPerPoly)
|
||||
{
|
||||
ByteBuffer buf = IOUtils.toByteBuffer(stream);
|
||||
return read(buf, maxVertPerPoly, false);
|
||||
}
|
||||
|
||||
public MeshData read(ByteBuffer buf, int maxVertPerPoly) {
|
||||
public MeshData read(ByteBuffer buf, int maxVertPerPoly)
|
||||
{
|
||||
return read(buf, maxVertPerPoly, false);
|
||||
}
|
||||
|
||||
public MeshData read32Bit(BinaryReader stream, int maxVertPerPoly) {
|
||||
public MeshData read32Bit(BinaryReader stream, int maxVertPerPoly)
|
||||
{
|
||||
ByteBuffer buf = IOUtils.toByteBuffer(stream);
|
||||
return read(buf, maxVertPerPoly, true);
|
||||
}
|
||||
|
||||
public MeshData read32Bit(ByteBuffer buf, int maxVertPerPoly) {
|
||||
public MeshData read32Bit(ByteBuffer buf, int maxVertPerPoly)
|
||||
{
|
||||
return read(buf, maxVertPerPoly, true);
|
||||
}
|
||||
|
||||
|
@ -51,20 +53,27 @@ public class MeshDataReader {
|
|||
MeshHeader header = new MeshHeader();
|
||||
data.header = header;
|
||||
header.magic = buf.getInt();
|
||||
if (header.magic != MeshHeader.DT_NAVMESH_MAGIC) {
|
||||
if (header.magic != MeshHeader.DT_NAVMESH_MAGIC)
|
||||
{
|
||||
header.magic = IOUtils.swapEndianness(header.magic);
|
||||
if (header.magic != MeshHeader.DT_NAVMESH_MAGIC) {
|
||||
if (header.magic != MeshHeader.DT_NAVMESH_MAGIC)
|
||||
{
|
||||
throw new IOException("Invalid magic");
|
||||
}
|
||||
|
||||
buf.order(buf.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
|
||||
header.version = buf.getInt();
|
||||
if (header.version != MeshHeader.DT_NAVMESH_VERSION) {
|
||||
if (header.version != MeshHeader.DT_NAVMESH_VERSION)
|
||||
{
|
||||
if (header.version < MeshHeader.DT_NAVMESH_VERSION_RECAST4J_FIRST
|
||||
|| header.version > MeshHeader.DT_NAVMESH_VERSION_RECAST4J_LAST) {
|
||||
|| header.version > MeshHeader.DT_NAVMESH_VERSION_RECAST4J_LAST)
|
||||
{
|
||||
throw new IOException("Invalid version " + header.version);
|
||||
}
|
||||
}
|
||||
|
||||
bool cCompatibility = header.version == MeshHeader.DT_NAVMESH_VERSION;
|
||||
header.x = buf.getInt();
|
||||
header.y = buf.getInt();
|
||||
|
@ -82,18 +91,24 @@ public class MeshDataReader {
|
|||
header.walkableHeight = buf.getFloat();
|
||||
header.walkableRadius = buf.getFloat();
|
||||
header.walkableClimb = buf.getFloat();
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
header.bmin[j] = buf.getFloat();
|
||||
}
|
||||
for (int j = 0; j < 3; j++) {
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
header.bmax[j] = buf.getFloat();
|
||||
}
|
||||
|
||||
header.bvQuantFactor = buf.getFloat();
|
||||
data.verts = readVerts(buf, header.vertCount);
|
||||
data.polys = readPolys(buf, header, maxVertPerPoly);
|
||||
if (cCompatibility) {
|
||||
if (cCompatibility)
|
||||
{
|
||||
buf.position(buf.position() + header.maxLinkCount * getSizeofLink(is32Bit));
|
||||
}
|
||||
|
||||
data.detailMeshes = readPolyDetails(buf, header, cCompatibility);
|
||||
data.detailVerts = readVerts(buf, header.detailVertCount);
|
||||
data.detailTris = readDTris(buf, header);
|
||||
|
@ -105,101 +120,137 @@ public class MeshDataReader {
|
|||
public const int LINK_SIZEOF = 16;
|
||||
public const int LINK_SIZEOF32BIT = 12;
|
||||
|
||||
public static int getSizeofLink(bool is32Bit) {
|
||||
public static int getSizeofLink(bool is32Bit)
|
||||
{
|
||||
return is32Bit ? LINK_SIZEOF32BIT : LINK_SIZEOF;
|
||||
}
|
||||
|
||||
private float[] readVerts(ByteBuffer buf, int count) {
|
||||
private float[] readVerts(ByteBuffer buf, int count)
|
||||
{
|
||||
float[] verts = new float[count * 3];
|
||||
for (int i = 0; i < verts.Length; i++) {
|
||||
for (int i = 0; i < verts.Length; i++)
|
||||
{
|
||||
verts[i] = buf.getFloat();
|
||||
}
|
||||
|
||||
return verts;
|
||||
}
|
||||
|
||||
private Poly[] readPolys(ByteBuffer buf, MeshHeader header, int maxVertPerPoly) {
|
||||
private Poly[] readPolys(ByteBuffer buf, MeshHeader header, int maxVertPerPoly)
|
||||
{
|
||||
Poly[] polys = new Poly[header.polyCount];
|
||||
for (int i = 0; i < polys.Length; i++) {
|
||||
for (int i = 0; i < polys.Length; i++)
|
||||
{
|
||||
polys[i] = new Poly(i, maxVertPerPoly);
|
||||
if (header.version < MeshHeader.DT_NAVMESH_VERSION_RECAST4J_NO_POLY_FIRSTLINK) {
|
||||
if (header.version < MeshHeader.DT_NAVMESH_VERSION_RECAST4J_NO_POLY_FIRSTLINK)
|
||||
{
|
||||
buf.getInt(); // polys[i].firstLink
|
||||
}
|
||||
for (int j = 0; j < polys[i].verts.Length; j++) {
|
||||
|
||||
for (int j = 0; j < polys[i].verts.Length; j++)
|
||||
{
|
||||
polys[i].verts[j] = buf.getShort() & 0xFFFF;
|
||||
}
|
||||
for (int j = 0; j < polys[i].neis.Length; j++) {
|
||||
|
||||
for (int j = 0; j < polys[i].neis.Length; j++)
|
||||
{
|
||||
polys[i].neis[j] = buf.getShort() & 0xFFFF;
|
||||
}
|
||||
|
||||
polys[i].flags = buf.getShort() & 0xFFFF;
|
||||
polys[i].vertCount = buf.get() & 0xFF;
|
||||
polys[i].areaAndtype = buf.get() & 0xFF;
|
||||
}
|
||||
|
||||
return polys;
|
||||
}
|
||||
|
||||
private PolyDetail[] readPolyDetails(ByteBuffer buf, MeshHeader header, bool cCompatibility) {
|
||||
private PolyDetail[] readPolyDetails(ByteBuffer buf, MeshHeader header, bool cCompatibility)
|
||||
{
|
||||
PolyDetail[] polys = new PolyDetail[header.detailMeshCount];
|
||||
for (int i = 0; i < polys.Length; i++) {
|
||||
for (int i = 0; i < polys.Length; i++)
|
||||
{
|
||||
polys[i] = new PolyDetail();
|
||||
polys[i].vertBase = buf.getInt();
|
||||
polys[i].triBase = buf.getInt();
|
||||
polys[i].vertCount = buf.get() & 0xFF;
|
||||
polys[i].triCount = buf.get() & 0xFF;
|
||||
if (cCompatibility) {
|
||||
if (cCompatibility)
|
||||
{
|
||||
buf.getShort(); // C struct padding
|
||||
}
|
||||
}
|
||||
|
||||
return polys;
|
||||
}
|
||||
|
||||
private int[] readDTris(ByteBuffer buf, MeshHeader header) {
|
||||
private int[] readDTris(ByteBuffer buf, MeshHeader header)
|
||||
{
|
||||
int[] tris = new int[4 * header.detailTriCount];
|
||||
for (int i = 0; i < tris.Length; i++) {
|
||||
for (int i = 0; i < tris.Length; i++)
|
||||
{
|
||||
tris[i] = buf.get() & 0xFF;
|
||||
}
|
||||
|
||||
return tris;
|
||||
}
|
||||
|
||||
private BVNode[] readBVTree(ByteBuffer buf, MeshHeader header) {
|
||||
private BVNode[] readBVTree(ByteBuffer buf, MeshHeader header)
|
||||
{
|
||||
BVNode[] nodes = new BVNode[header.bvNodeCount];
|
||||
for (int i = 0; i < nodes.Length; i++) {
|
||||
for (int i = 0; i < nodes.Length; i++)
|
||||
{
|
||||
nodes[i] = new BVNode();
|
||||
if (header.version < MeshHeader.DT_NAVMESH_VERSION_RECAST4J_32BIT_BVTREE) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (header.version < MeshHeader.DT_NAVMESH_VERSION_RECAST4J_32BIT_BVTREE)
|
||||
{
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
nodes[i].bmin[j] = buf.getShort() & 0xFFFF;
|
||||
}
|
||||
for (int j = 0; j < 3; j++) {
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
nodes[i].bmax[j] = buf.getShort() & 0xFFFF;
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
nodes[i].bmin[j] = buf.getInt();
|
||||
}
|
||||
for (int j = 0; j < 3; j++) {
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
nodes[i].bmax[j] = buf.getInt();
|
||||
}
|
||||
}
|
||||
|
||||
nodes[i].i = buf.getInt();
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
private OffMeshConnection[] readOffMeshCons(ByteBuffer buf, MeshHeader header) {
|
||||
private OffMeshConnection[] readOffMeshCons(ByteBuffer buf, MeshHeader header)
|
||||
{
|
||||
OffMeshConnection[] cons = new OffMeshConnection[header.offMeshConCount];
|
||||
for (int i = 0; i < cons.Length; i++) {
|
||||
for (int i = 0; i < cons.Length; i++)
|
||||
{
|
||||
cons[i] = new OffMeshConnection();
|
||||
for (int j = 0; j < 6; j++) {
|
||||
for (int j = 0; j < 6; j++)
|
||||
{
|
||||
cons[i].pos[j] = buf.getFloat();
|
||||
}
|
||||
|
||||
cons[i].rad = buf.getFloat();
|
||||
cons[i].poly = buf.getShort() & 0xFFFF;
|
||||
cons[i].flags = buf.get() & 0xFF;
|
||||
cons[i].side = buf.get() & 0xFF;
|
||||
cons[i].userId = buf.getInt();
|
||||
}
|
||||
|
||||
return cons;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -21,11 +21,10 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Detour.Io
|
||||
{
|
||||
|
||||
|
||||
public class MeshDataWriter : DetourWriter {
|
||||
|
||||
public void write(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility) {
|
||||
public class MeshDataWriter : DetourWriter
|
||||
{
|
||||
public void write(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility)
|
||||
{
|
||||
MeshHeader header = data.header;
|
||||
write(stream, header.magic, order);
|
||||
write(stream, cCompatibility ? MeshHeader.DT_NAVMESH_VERSION : MeshHeader.DT_NAVMESH_VERSION_RECAST4J_LAST, order);
|
||||
|
@ -54,10 +53,12 @@ public class MeshDataWriter : DetourWriter {
|
|||
write(stream, header.bvQuantFactor, order);
|
||||
writeVerts(stream, data.verts, header.vertCount, order);
|
||||
writePolys(stream, data, order, cCompatibility);
|
||||
if (cCompatibility) {
|
||||
if (cCompatibility)
|
||||
{
|
||||
byte[] linkPlaceholder = new byte[header.maxLinkCount * MeshDataReader.getSizeofLink(false)];
|
||||
stream.Write(linkPlaceholder);
|
||||
}
|
||||
|
||||
writePolyDetails(stream, data, order, cCompatibility);
|
||||
writeVerts(stream, data.detailVerts, header.detailVertCount, order);
|
||||
writeDTris(stream, data);
|
||||
|
@ -65,23 +66,33 @@ public class MeshDataWriter : DetourWriter {
|
|||
writeOffMeshCons(stream, data, order);
|
||||
}
|
||||
|
||||
private void writeVerts(BinaryWriter stream, float[] verts, int count, ByteOrder order) {
|
||||
for (int i = 0; i < count * 3; i++) {
|
||||
private void writeVerts(BinaryWriter stream, float[] verts, int count, ByteOrder order)
|
||||
{
|
||||
for (int i = 0; i < count * 3; i++)
|
||||
{
|
||||
write(stream, verts[i], order);
|
||||
}
|
||||
}
|
||||
|
||||
private void writePolys(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility) {
|
||||
for (int i = 0; i < data.header.polyCount; i++) {
|
||||
if (cCompatibility) {
|
||||
private void writePolys(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility)
|
||||
{
|
||||
for (int i = 0; i < data.header.polyCount; i++)
|
||||
{
|
||||
if (cCompatibility)
|
||||
{
|
||||
write(stream, 0xFFFF, order);
|
||||
}
|
||||
for (int j = 0; j < data.polys[i].verts.Length; j++) {
|
||||
|
||||
for (int j = 0; j < data.polys[i].verts.Length; j++)
|
||||
{
|
||||
write(stream, (short)data.polys[i].verts[j], order);
|
||||
}
|
||||
for (int j = 0; j < data.polys[i].neis.Length; j++) {
|
||||
|
||||
for (int j = 0; j < data.polys[i].neis.Length; j++)
|
||||
{
|
||||
write(stream, (short)data.polys[i].neis[j], order);
|
||||
}
|
||||
|
||||
write(stream, (short)data.polys[i].flags, order);
|
||||
write(stream, (byte)data.polys[i].vertCount);
|
||||
write(stream, (byte)data.polys[i].areaAndtype);
|
||||
|
@ -90,49 +101,69 @@ public class MeshDataWriter : DetourWriter {
|
|||
|
||||
private void writePolyDetails(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility)
|
||||
{
|
||||
for (int i = 0; i < data.header.detailMeshCount; i++) {
|
||||
for (int i = 0; i < data.header.detailMeshCount; i++)
|
||||
{
|
||||
write(stream, data.detailMeshes[i].vertBase, order);
|
||||
write(stream, data.detailMeshes[i].triBase, order);
|
||||
write(stream, (byte)data.detailMeshes[i].vertCount);
|
||||
write(stream, (byte)data.detailMeshes[i].triCount);
|
||||
if (cCompatibility) {
|
||||
if (cCompatibility)
|
||||
{
|
||||
write(stream, (short)0, order);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeDTris(BinaryWriter stream, MeshData data) {
|
||||
for (int i = 0; i < data.header.detailTriCount * 4; i++) {
|
||||
private void writeDTris(BinaryWriter stream, MeshData data)
|
||||
{
|
||||
for (int i = 0; i < data.header.detailTriCount * 4; i++)
|
||||
{
|
||||
write(stream, (byte)data.detailTris[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeBVTree(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility) {
|
||||
for (int i = 0; i < data.header.bvNodeCount; i++) {
|
||||
if (cCompatibility) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
private void writeBVTree(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility)
|
||||
{
|
||||
for (int i = 0; i < data.header.bvNodeCount; i++)
|
||||
{
|
||||
if (cCompatibility)
|
||||
{
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
write(stream, (short)data.bvTree[i].bmin[j], order);
|
||||
}
|
||||
for (int j = 0; j < 3; j++) {
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
write(stream, (short)data.bvTree[i].bmax[j], order);
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
write(stream, data.bvTree[i].bmin[j], order);
|
||||
}
|
||||
for (int j = 0; j < 3; j++) {
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
write(stream, data.bvTree[i].bmax[j], order);
|
||||
}
|
||||
}
|
||||
|
||||
write(stream, data.bvTree[i].i, order);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeOffMeshCons(BinaryWriter stream, MeshData data, ByteOrder order) {
|
||||
for (int i = 0; i < data.header.offMeshConCount; i++) {
|
||||
for (int j = 0; j < 6; j++) {
|
||||
private void writeOffMeshCons(BinaryWriter stream, MeshData data, ByteOrder order)
|
||||
{
|
||||
for (int i = 0; i < data.header.offMeshConCount; i++)
|
||||
{
|
||||
for (int j = 0; j < 6; j++)
|
||||
{
|
||||
write(stream, data.offMeshCons[i].pos[j], order);
|
||||
}
|
||||
|
||||
write(stream, data.offMeshCons[i].rad, order);
|
||||
write(stream, (short)data.offMeshCons[i].poly, order);
|
||||
write(stream, (byte)data.offMeshCons[i].flags);
|
||||
|
@ -140,7 +171,5 @@ public class MeshDataWriter : DetourWriter {
|
|||
write(stream, data.offMeshCons[i].userId, order);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,98 +22,124 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Detour.Io
|
||||
{
|
||||
|
||||
|
||||
using static DetourCommon;
|
||||
|
||||
|
||||
public class MeshSetReader {
|
||||
|
||||
public class MeshSetReader
|
||||
{
|
||||
private readonly MeshDataReader meshReader = new MeshDataReader();
|
||||
private readonly NavMeshParamReader paramReader = new NavMeshParamReader();
|
||||
|
||||
public NavMesh read(BinaryReader @is, int maxVertPerPoly) {
|
||||
public NavMesh read(BinaryReader @is, int maxVertPerPoly)
|
||||
{
|
||||
return read(IOUtils.toByteBuffer(@is), maxVertPerPoly, false);
|
||||
}
|
||||
|
||||
public NavMesh read(ByteBuffer bb, int maxVertPerPoly) {
|
||||
public NavMesh read(ByteBuffer bb, int maxVertPerPoly)
|
||||
{
|
||||
return read(bb, maxVertPerPoly, false);
|
||||
}
|
||||
|
||||
public NavMesh read32Bit(BinaryReader @is, int maxVertPerPoly) {
|
||||
public NavMesh read32Bit(BinaryReader @is, int maxVertPerPoly)
|
||||
{
|
||||
return read(IOUtils.toByteBuffer(@is), maxVertPerPoly, true);
|
||||
}
|
||||
|
||||
public NavMesh read32Bit(ByteBuffer bb, int maxVertPerPoly) {
|
||||
public NavMesh read32Bit(ByteBuffer bb, int maxVertPerPoly)
|
||||
{
|
||||
return read(bb, maxVertPerPoly, true);
|
||||
}
|
||||
|
||||
public NavMesh read(BinaryReader @is) {
|
||||
public NavMesh read(BinaryReader @is)
|
||||
{
|
||||
return read(IOUtils.toByteBuffer(@is));
|
||||
}
|
||||
|
||||
public NavMesh read(ByteBuffer bb) {
|
||||
public NavMesh read(ByteBuffer bb)
|
||||
{
|
||||
return read(bb, -1, false);
|
||||
}
|
||||
|
||||
NavMesh read(ByteBuffer bb, int maxVertPerPoly, bool is32Bit) {
|
||||
NavMesh read(ByteBuffer bb, int maxVertPerPoly, bool is32Bit)
|
||||
{
|
||||
NavMeshSetHeader header = readHeader(bb, maxVertPerPoly);
|
||||
if (header.maxVertsPerPoly <= 0) {
|
||||
if (header.maxVertsPerPoly <= 0)
|
||||
{
|
||||
throw new IOException("Invalid number of verts per poly " + header.maxVertsPerPoly);
|
||||
}
|
||||
|
||||
bool cCompatibility = header.version == NavMeshSetHeader.NAVMESHSET_VERSION;
|
||||
NavMesh mesh = new NavMesh(header.option, header.maxVertsPerPoly);
|
||||
readTiles(bb, is32Bit, header, cCompatibility, mesh);
|
||||
return mesh;
|
||||
}
|
||||
|
||||
private NavMeshSetHeader readHeader(ByteBuffer bb, int maxVertsPerPoly) {
|
||||
private NavMeshSetHeader readHeader(ByteBuffer bb, int maxVertsPerPoly)
|
||||
{
|
||||
NavMeshSetHeader header = new NavMeshSetHeader();
|
||||
header.magic = bb.getInt();
|
||||
if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC) {
|
||||
if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC)
|
||||
{
|
||||
header.magic = IOUtils.swapEndianness(header.magic);
|
||||
if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC) {
|
||||
if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC)
|
||||
{
|
||||
throw new IOException("Invalid magic " + header.magic);
|
||||
}
|
||||
|
||||
bb.order(bb.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
|
||||
header.version = bb.getInt();
|
||||
if (header.version != NavMeshSetHeader.NAVMESHSET_VERSION && header.version != NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J_1
|
||||
&& header.version != NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J) {
|
||||
&& header.version != NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J)
|
||||
{
|
||||
throw new IOException("Invalid version " + header.version);
|
||||
}
|
||||
|
||||
header.numTiles = bb.getInt();
|
||||
header.option = paramReader.read(bb);
|
||||
header.maxVertsPerPoly = maxVertsPerPoly;
|
||||
if (header.version == NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J) {
|
||||
if (header.version == NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J)
|
||||
{
|
||||
header.maxVertsPerPoly = bb.getInt();
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
private void readTiles(ByteBuffer bb, bool is32Bit, NavMeshSetHeader header, bool cCompatibility, NavMesh mesh)
|
||||
{
|
||||
// Read tiles.
|
||||
for (int i = 0; i < header.numTiles; ++i) {
|
||||
for (int i = 0; i < header.numTiles; ++i)
|
||||
{
|
||||
NavMeshTileHeader tileHeader = new NavMeshTileHeader();
|
||||
if (is32Bit) {
|
||||
if (is32Bit)
|
||||
{
|
||||
tileHeader.tileRef = convert32BitRef(bb.getInt(), header.option);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
tileHeader.tileRef = bb.getLong();
|
||||
}
|
||||
|
||||
tileHeader.dataSize = bb.getInt();
|
||||
if (tileHeader.tileRef == 0 || tileHeader.dataSize == 0) {
|
||||
if (tileHeader.tileRef == 0 || tileHeader.dataSize == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (cCompatibility && !is32Bit) {
|
||||
|
||||
if (cCompatibility && !is32Bit)
|
||||
{
|
||||
bb.getInt(); // C struct padding
|
||||
}
|
||||
|
||||
MeshData data = meshReader.read(bb, mesh.getMaxVertsPerPoly(), is32Bit);
|
||||
mesh.addTile(data, i, tileHeader.tileRef);
|
||||
}
|
||||
}
|
||||
|
||||
private long convert32BitRef(int refs, NavMeshParams option) {
|
||||
private long convert32BitRef(int refs, NavMeshParams option)
|
||||
{
|
||||
int m_tileBits = ilog2(nextPow2(option.maxTiles));
|
||||
int m_polyBits = ilog2(nextPow2(option.maxPolys));
|
||||
// Only allow 31 salt bits, since the salt mask is calculated using 32bit uint and it will overflow.
|
||||
|
@ -127,5 +153,4 @@ public class MeshSetReader {
|
|||
return NavMesh.encodePolyId(salt, it, ip);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -21,40 +21,48 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Detour.Io
|
||||
{
|
||||
|
||||
|
||||
public class MeshSetWriter : DetourWriter {
|
||||
|
||||
public class MeshSetWriter : DetourWriter
|
||||
{
|
||||
private readonly MeshDataWriter writer = new MeshDataWriter();
|
||||
private readonly NavMeshParamWriter paramWriter = new NavMeshParamWriter();
|
||||
|
||||
public void write(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility) {
|
||||
public void write(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility)
|
||||
{
|
||||
writeHeader(stream, mesh, order, cCompatibility);
|
||||
writeTiles(stream, mesh, order, cCompatibility);
|
||||
}
|
||||
|
||||
private void writeHeader(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility) {
|
||||
private void writeHeader(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility)
|
||||
{
|
||||
write(stream, NavMeshSetHeader.NAVMESHSET_MAGIC, order);
|
||||
write(stream, cCompatibility ? NavMeshSetHeader.NAVMESHSET_VERSION : NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J, order);
|
||||
int numTiles = 0;
|
||||
for (int i = 0; i < mesh.getMaxTiles(); ++i) {
|
||||
for (int i = 0; i < mesh.getMaxTiles(); ++i)
|
||||
{
|
||||
MeshTile tile = mesh.getTile(i);
|
||||
if (tile == null || tile.data == null || tile.data.header == null) {
|
||||
if (tile == null || tile.data == null || tile.data.header == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
numTiles++;
|
||||
}
|
||||
|
||||
write(stream, numTiles, order);
|
||||
paramWriter.write(stream, mesh.getParams(), order);
|
||||
if (!cCompatibility) {
|
||||
if (!cCompatibility)
|
||||
{
|
||||
write(stream, mesh.getMaxVertsPerPoly(), order);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeTiles(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility) {
|
||||
for (int i = 0; i < mesh.getMaxTiles(); ++i) {
|
||||
private void writeTiles(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility)
|
||||
{
|
||||
for (int i = 0; i < mesh.getMaxTiles(); ++i)
|
||||
{
|
||||
MeshTile tile = mesh.getTile(i);
|
||||
if (tile == null || tile.data == null || tile.data.header == null) {
|
||||
if (tile == null || tile.data == null || tile.data.header == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -70,13 +78,13 @@ public class MeshSetWriter : DetourWriter {
|
|||
tileHeader.dataSize = ba.Length;
|
||||
write(stream, tileHeader.tileRef, order);
|
||||
write(stream, tileHeader.dataSize, order);
|
||||
if (cCompatibility) {
|
||||
if (cCompatibility)
|
||||
{
|
||||
write(stream, 0, order); // C struct padding
|
||||
}
|
||||
|
||||
stream.Write(ba);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -2,11 +2,10 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Detour.Io
|
||||
{
|
||||
|
||||
|
||||
public class NavMeshParamReader {
|
||||
|
||||
public NavMeshParams read(ByteBuffer bb) {
|
||||
public class NavMeshParamReader
|
||||
{
|
||||
public NavMeshParams read(ByteBuffer bb)
|
||||
{
|
||||
NavMeshParams option = new NavMeshParams();
|
||||
option.orig[0] = bb.getFloat();
|
||||
option.orig[1] = bb.getFloat();
|
||||
|
@ -17,7 +16,5 @@ public class NavMeshParamReader {
|
|||
option.maxPolys = bb.getInt();
|
||||
return option;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -3,11 +3,10 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Detour.Io
|
||||
{
|
||||
|
||||
|
||||
public class NavMeshParamWriter : DetourWriter {
|
||||
|
||||
public void write(BinaryWriter stream, NavMeshParams option, ByteOrder order) {
|
||||
public class NavMeshParamWriter : DetourWriter
|
||||
{
|
||||
public void write(BinaryWriter stream, NavMeshParams option, ByteOrder order)
|
||||
{
|
||||
write(stream, option.orig[0], order);
|
||||
write(stream, option.orig[1], order);
|
||||
write(stream, option.orig[2], order);
|
||||
|
@ -16,7 +15,5 @@ public class NavMeshParamWriter : DetourWriter {
|
|||
write(stream, option.maxTiles, order);
|
||||
write(stream, option.maxPolys, order);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -15,12 +15,11 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour.Io
|
||||
{
|
||||
|
||||
|
||||
public class NavMeshSetHeader {
|
||||
|
||||
public class NavMeshSetHeader
|
||||
{
|
||||
public const int NAVMESHSET_MAGIC = 'M' << 24 | 'S' << 16 | 'E' << 8 | 'T'; // 'MSET';
|
||||
public const int NAVMESHSET_VERSION = 1;
|
||||
public const int NAVMESHSET_VERSION_RECAST4J_1 = 0x8801;
|
||||
|
@ -31,7 +30,5 @@ public class NavMeshSetHeader {
|
|||
public int numTiles;
|
||||
public NavMeshParams option = new NavMeshParams();
|
||||
public int maxVertsPerPoly;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,10 +1,8 @@
|
|||
namespace DotRecast.Detour.Io
|
||||
{
|
||||
|
||||
|
||||
public class NavMeshTileHeader {
|
||||
public class NavMeshTileHeader
|
||||
{
|
||||
public long tileRef;
|
||||
public int dataSize;
|
||||
}
|
||||
|
||||
}
|
|
@ -21,32 +21,35 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
using static DetourCommon;
|
||||
|
||||
|
||||
public class LegacyNavMeshQuery : NavMeshQuery {
|
||||
|
||||
public class LegacyNavMeshQuery : NavMeshQuery
|
||||
{
|
||||
private static float H_SCALE = 0.999f; // Search heuristic scale.
|
||||
|
||||
public LegacyNavMeshQuery(NavMesh nav) : base(nav) {
|
||||
public LegacyNavMeshQuery(NavMesh nav) : base(nav)
|
||||
{
|
||||
}
|
||||
|
||||
public override Result<List<long>> findPath(long startRef, long endRef, float[] startPos, float[] endPos, QueryFilter filter,
|
||||
int options, float raycastLimit) {
|
||||
int options, float raycastLimit)
|
||||
{
|
||||
return findPath(startRef, endRef, startPos, endPos, filter);
|
||||
}
|
||||
|
||||
public override Result<List<long>> findPath(long startRef, long endRef, float[] startPos, float[] endPos,
|
||||
QueryFilter filter) {
|
||||
QueryFilter filter)
|
||||
{
|
||||
// Validate input
|
||||
if (!m_nav.isValidPolyRef(startRef) || !m_nav.isValidPolyRef(endRef) || null == startPos
|
||||
|| !vIsFinite(startPos) || null == endPos || !vIsFinite(endPos) || null == filter) {
|
||||
|| !vIsFinite(startPos) || null == endPos || !vIsFinite(endPos) || null == filter)
|
||||
{
|
||||
return Results.invalidParam<List<long>>();
|
||||
}
|
||||
|
||||
if (startRef == endRef) {
|
||||
if (startRef == endRef)
|
||||
{
|
||||
List<long> singlePath = new List<long>(1);
|
||||
singlePath.Add(startRef);
|
||||
return Results.success(singlePath);
|
||||
|
@ -69,14 +72,16 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
|
||||
Status status = Status.SUCCSESS;
|
||||
|
||||
while (!m_openList.isEmpty()) {
|
||||
while (!m_openList.isEmpty())
|
||||
{
|
||||
// Remove node from open list and put it in closed list.
|
||||
Node bestNode = m_openList.pop();
|
||||
bestNode.flags &= ~Node.DT_NODE_OPEN;
|
||||
bestNode.flags |= Node.DT_NODE_CLOSED;
|
||||
|
||||
// Reached the goal, stop searching.
|
||||
if (bestNode.id == endRef) {
|
||||
if (bestNode.id == endRef)
|
||||
{
|
||||
lastBestNode = bestNode;
|
||||
break;
|
||||
}
|
||||
|
@ -92,20 +97,25 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
long parentRef = 0;
|
||||
MeshTile parentTile = null;
|
||||
Poly parentPoly = null;
|
||||
if (bestNode.pidx != 0) {
|
||||
if (bestNode.pidx != 0)
|
||||
{
|
||||
parentRef = m_nodePool.getNodeAtIdx(bestNode.pidx).id;
|
||||
}
|
||||
if (parentRef != 0) {
|
||||
|
||||
if (parentRef != 0)
|
||||
{
|
||||
tileAndPoly = m_nav.getTileAndPolyByRefUnsafe(parentRef);
|
||||
parentTile = tileAndPoly.Item1;
|
||||
parentPoly = tileAndPoly.Item2;
|
||||
}
|
||||
|
||||
for (int i = bestTile.polyLinks[bestPoly.index]; i != NavMesh.DT_NULL_LINK; i = bestTile.links[i].next) {
|
||||
for (int i = bestTile.polyLinks[bestPoly.index]; i != NavMesh.DT_NULL_LINK; i = bestTile.links[i].next)
|
||||
{
|
||||
long neighbourRef = bestTile.links[i].refs;
|
||||
|
||||
// Skip invalid ids and do not expand back to where we came from.
|
||||
if (neighbourRef == 0 || neighbourRef == parentRef) {
|
||||
if (neighbourRef == 0 || neighbourRef == parentRef)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -115,13 +125,15 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
MeshTile neighbourTile = tileAndPoly.Item1;
|
||||
Poly neighbourPoly = tileAndPoly.Item2;
|
||||
|
||||
if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) {
|
||||
if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// deal explicitly with crossing tile boundaries
|
||||
int crossSide = 0;
|
||||
if (bestTile.links[i].side != 0xff) {
|
||||
if (bestTile.links[i].side != 0xff)
|
||||
{
|
||||
crossSide = bestTile.links[i].side >> 1;
|
||||
}
|
||||
|
||||
|
@ -129,10 +141,12 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
Node neighbourNode = m_nodePool.getNode(neighbourRef, crossSide);
|
||||
|
||||
// If the node is visited the first time, calculate node position.
|
||||
if (neighbourNode.flags == 0) {
|
||||
if (neighbourNode.flags == 0)
|
||||
{
|
||||
Result<float[]> midpod = getEdgeMidPoint(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly,
|
||||
neighbourTile);
|
||||
if (!midpod.failed()) {
|
||||
if (!midpod.failed())
|
||||
{
|
||||
neighbourNode.pos = midpod.result;
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +156,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
float heuristic = 0;
|
||||
|
||||
// Special case for last node.
|
||||
if (neighbourRef == endRef) {
|
||||
if (neighbourRef == endRef)
|
||||
{
|
||||
// Cost
|
||||
float curCost = filter.getCost(bestNode.pos, neighbourNode.pos, parentRef, parentTile, parentPoly,
|
||||
bestRef, bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly);
|
||||
|
@ -151,7 +166,9 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
|
||||
cost = bestNode.cost + curCost + endCost;
|
||||
heuristic = 0;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cost
|
||||
float curCost = filter.getCost(bestNode.pos, neighbourNode.pos, parentRef, parentTile, parentPoly,
|
||||
bestRef, bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly);
|
||||
|
@ -162,11 +179,14 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
float total = cost + heuristic;
|
||||
|
||||
// The node is already in open list and the new result is worse, skip.
|
||||
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0 && total >= neighbourNode.total) {
|
||||
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0 && total >= neighbourNode.total)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// The node is already visited and process, and the new result is worse, skip.
|
||||
if ((neighbourNode.flags & Node.DT_NODE_CLOSED) != 0 && total >= neighbourNode.total) {
|
||||
if ((neighbourNode.flags & Node.DT_NODE_CLOSED) != 0 && total >= neighbourNode.total)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -177,17 +197,21 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
neighbourNode.cost = cost;
|
||||
neighbourNode.total = total;
|
||||
|
||||
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0) {
|
||||
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0)
|
||||
{
|
||||
// Already in open, update node location.
|
||||
m_openList.modify(neighbourNode);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Put the node in open list.
|
||||
neighbourNode.flags |= Node.DT_NODE_OPEN;
|
||||
m_openList.push(neighbourNode);
|
||||
}
|
||||
|
||||
// Update nearest node to target so far.
|
||||
if (heuristic < lastBestNodeCost) {
|
||||
if (heuristic < lastBestNodeCost)
|
||||
{
|
||||
lastBestNodeCost = heuristic;
|
||||
lastBestNode = neighbourNode;
|
||||
}
|
||||
|
@ -196,7 +220,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
|
||||
List<long> path = getPathToNode(lastBestNode);
|
||||
|
||||
if (lastBestNode.id != endRef) {
|
||||
if (lastBestNode.id != endRef)
|
||||
{
|
||||
status = Status.PARTIAL_RESULT;
|
||||
}
|
||||
|
||||
|
@ -210,19 +235,23 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
* The maximum number of iterations to perform.
|
||||
* @return The status flags for the query.
|
||||
*/
|
||||
public override Result<int> updateSlicedFindPath(int maxIter) {
|
||||
if (!m_query.status.isInProgress()) {
|
||||
public override Result<int> updateSlicedFindPath(int maxIter)
|
||||
{
|
||||
if (!m_query.status.isInProgress())
|
||||
{
|
||||
return Results.of(m_query.status, 0);
|
||||
}
|
||||
|
||||
// Make sure the request is still valid.
|
||||
if (!m_nav.isValidPolyRef(m_query.startRef) || !m_nav.isValidPolyRef(m_query.endRef)) {
|
||||
if (!m_nav.isValidPolyRef(m_query.startRef) || !m_nav.isValidPolyRef(m_query.endRef))
|
||||
{
|
||||
m_query.status = Status.FAILURE;
|
||||
return Results.of(m_query.status, 0);
|
||||
}
|
||||
|
||||
int iter = 0;
|
||||
while (iter < maxIter && !m_openList.isEmpty()) {
|
||||
while (iter < maxIter && !m_openList.isEmpty())
|
||||
{
|
||||
iter++;
|
||||
|
||||
// Remove node from open list and put it in closed list.
|
||||
|
@ -231,7 +260,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
bestNode.flags |= Node.DT_NODE_CLOSED;
|
||||
|
||||
// Reached the goal, stop searching.
|
||||
if (bestNode.id == m_query.endRef) {
|
||||
if (bestNode.id == m_query.endRef)
|
||||
{
|
||||
m_query.lastBestNode = bestNode;
|
||||
m_query.status = Status.SUCCSESS;
|
||||
return Results.of(m_query.status, iter);
|
||||
|
@ -242,11 +272,13 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
// data.
|
||||
long bestRef = bestNode.id;
|
||||
Result<Tuple<MeshTile, Poly>> tileAndPoly = m_nav.getTileAndPolyByRef(bestRef);
|
||||
if (tileAndPoly.failed()) {
|
||||
if (tileAndPoly.failed())
|
||||
{
|
||||
m_query.status = Status.FAILURE;
|
||||
// The polygon has disappeared during the sliced query, fail.
|
||||
return Results.of(m_query.status, iter);
|
||||
}
|
||||
|
||||
MeshTile bestTile = tileAndPoly.result.Item1;
|
||||
Poly bestPoly = tileAndPoly.result.Item2;
|
||||
// Get parent and grand parent poly and tile.
|
||||
|
@ -254,41 +286,51 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
MeshTile parentTile = null;
|
||||
Poly parentPoly = null;
|
||||
Node parentNode = null;
|
||||
if (bestNode.pidx != 0) {
|
||||
if (bestNode.pidx != 0)
|
||||
{
|
||||
parentNode = m_nodePool.getNodeAtIdx(bestNode.pidx);
|
||||
parentRef = parentNode.id;
|
||||
if (parentNode.pidx != 0) {
|
||||
if (parentNode.pidx != 0)
|
||||
{
|
||||
grandpaRef = m_nodePool.getNodeAtIdx(parentNode.pidx).id;
|
||||
}
|
||||
}
|
||||
if (parentRef != 0) {
|
||||
|
||||
if (parentRef != 0)
|
||||
{
|
||||
bool invalidParent = false;
|
||||
tileAndPoly = m_nav.getTileAndPolyByRef(parentRef);
|
||||
invalidParent = tileAndPoly.failed();
|
||||
if (invalidParent || (grandpaRef != 0 && !m_nav.isValidPolyRef(grandpaRef))) {
|
||||
if (invalidParent || (grandpaRef != 0 && !m_nav.isValidPolyRef(grandpaRef)))
|
||||
{
|
||||
// The polygon has disappeared during the sliced query,
|
||||
// fail.
|
||||
m_query.status = Status.FAILURE;
|
||||
return Results.of(m_query.status, iter);
|
||||
}
|
||||
|
||||
parentTile = tileAndPoly.result.Item1;
|
||||
parentPoly = tileAndPoly.result.Item2;
|
||||
}
|
||||
|
||||
// decide whether to test raycast to previous nodes
|
||||
bool tryLOS = false;
|
||||
if ((m_query.options & DT_FINDPATH_ANY_ANGLE) != 0) {
|
||||
if ((parentRef != 0) && (vDistSqr(parentNode.pos, bestNode.pos) < m_query.raycastLimitSqr)) {
|
||||
if ((m_query.options & DT_FINDPATH_ANY_ANGLE) != 0)
|
||||
{
|
||||
if ((parentRef != 0) && (vDistSqr(parentNode.pos, bestNode.pos) < m_query.raycastLimitSqr))
|
||||
{
|
||||
tryLOS = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = bestTile.polyLinks[bestPoly.index]; i != NavMesh.DT_NULL_LINK; i = bestTile.links[i].next) {
|
||||
for (int i = bestTile.polyLinks[bestPoly.index]; i != NavMesh.DT_NULL_LINK; i = bestTile.links[i].next)
|
||||
{
|
||||
long neighbourRef = bestTile.links[i].refs;
|
||||
|
||||
// Skip invalid ids and do not expand back to where we came
|
||||
// from.
|
||||
if (neighbourRef == 0 || neighbourRef == parentRef) {
|
||||
if (neighbourRef == 0 || neighbourRef == parentRef)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -299,7 +341,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
MeshTile neighbourTile = tileAndPolyUns.Item1;
|
||||
Poly neighbourPoly = tileAndPolyUns.Item2;
|
||||
|
||||
if (!m_query.filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) {
|
||||
if (!m_query.filter.passFilter(neighbourRef, neighbourTile, neighbourPoly))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -308,16 +351,19 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
|
||||
// do not expand to nodes that were already visited from the
|
||||
// same parent
|
||||
if (neighbourNode.pidx != 0 && neighbourNode.pidx == bestNode.pidx) {
|
||||
if (neighbourNode.pidx != 0 && neighbourNode.pidx == bestNode.pidx)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the node is visited the first time, calculate node
|
||||
// position.
|
||||
if (neighbourNode.flags == 0) {
|
||||
if (neighbourNode.flags == 0)
|
||||
{
|
||||
Result<float[]> midpod = getEdgeMidPoint(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly,
|
||||
neighbourTile);
|
||||
if (!midpod.failed()) {
|
||||
if (!midpod.failed())
|
||||
{
|
||||
neighbourNode.pos = midpod.result;
|
||||
}
|
||||
}
|
||||
|
@ -328,12 +374,15 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
|
||||
// raycast parent
|
||||
bool foundShortCut = false;
|
||||
if (tryLOS) {
|
||||
if (tryLOS)
|
||||
{
|
||||
Result<RaycastHit> rayHit = raycast(parentRef, parentNode.pos, neighbourNode.pos, m_query.filter,
|
||||
DT_RAYCAST_USE_COSTS, grandpaRef);
|
||||
if (rayHit.succeeded()) {
|
||||
if (rayHit.succeeded())
|
||||
{
|
||||
foundShortCut = rayHit.result.t >= 1.0f;
|
||||
if (foundShortCut) {
|
||||
if (foundShortCut)
|
||||
{
|
||||
// shortcut found using raycast. Using shorter cost
|
||||
// instead
|
||||
cost = parentNode.cost + rayHit.result.pathCost;
|
||||
|
@ -342,7 +391,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
}
|
||||
|
||||
// update move cost
|
||||
if (!foundShortCut) {
|
||||
if (!foundShortCut)
|
||||
{
|
||||
// No shortcut found.
|
||||
float curCost = m_query.filter.getCost(bestNode.pos, neighbourNode.pos, parentRef, parentTile,
|
||||
parentPoly, bestRef, bestTile, bestPoly, neighbourRef, neighbourTile, neighbourPoly);
|
||||
|
@ -350,13 +400,16 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
}
|
||||
|
||||
// Special case for last node.
|
||||
if (neighbourRef == m_query.endRef) {
|
||||
if (neighbourRef == m_query.endRef)
|
||||
{
|
||||
float endCost = m_query.filter.getCost(neighbourNode.pos, m_query.endPos, bestRef, bestTile,
|
||||
bestPoly, neighbourRef, neighbourTile, neighbourPoly, 0, null, null);
|
||||
|
||||
cost = cost + endCost;
|
||||
heuristic = 0;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
heuristic = vDist(neighbourNode.pos, m_query.endPos) * H_SCALE;
|
||||
}
|
||||
|
||||
|
@ -364,12 +417,15 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
|
||||
// The node is already in open list and the new result is worse,
|
||||
// skip.
|
||||
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0 && total >= neighbourNode.total) {
|
||||
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0 && total >= neighbourNode.total)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// The node is already visited and process, and the new result
|
||||
// is worse, skip.
|
||||
if ((neighbourNode.flags & Node.DT_NODE_CLOSED) != 0 && total >= neighbourNode.total) {
|
||||
if ((neighbourNode.flags & Node.DT_NODE_CLOSED) != 0 && total >= neighbourNode.total)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -379,21 +435,26 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
neighbourNode.flags = (neighbourNode.flags & ~(Node.DT_NODE_CLOSED | Node.DT_NODE_PARENT_DETACHED));
|
||||
neighbourNode.cost = cost;
|
||||
neighbourNode.total = total;
|
||||
if (foundShortCut) {
|
||||
if (foundShortCut)
|
||||
{
|
||||
neighbourNode.flags = (neighbourNode.flags | Node.DT_NODE_PARENT_DETACHED);
|
||||
}
|
||||
|
||||
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0) {
|
||||
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0)
|
||||
{
|
||||
// Already in open, update node location.
|
||||
m_openList.modify(neighbourNode);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Put the node in open list.
|
||||
neighbourNode.flags |= Node.DT_NODE_OPEN;
|
||||
m_openList.push(neighbourNode);
|
||||
}
|
||||
|
||||
// Update nearest node to target so far.
|
||||
if (heuristic < m_query.lastBestNodeCost) {
|
||||
if (heuristic < m_query.lastBestNodeCost)
|
||||
{
|
||||
m_query.lastBestNodeCost = heuristic;
|
||||
m_query.lastBestNode = neighbourNode;
|
||||
}
|
||||
|
@ -401,7 +462,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
}
|
||||
|
||||
// Exhausted all nodes, but could not find path.
|
||||
if (m_openList.isEmpty()) {
|
||||
if (m_openList.isEmpty())
|
||||
{
|
||||
m_query.status = Status.PARTIAL_RESULT;
|
||||
}
|
||||
|
||||
|
@ -412,28 +474,34 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
/// @param[out] path An ordered list of polygon references representing the path. (Start to end.)
|
||||
/// [(polyRef) * @p pathCount]
|
||||
/// @returns The status flags for the query.
|
||||
public override Result<List<long>> finalizeSlicedFindPath() {
|
||||
|
||||
public override Result<List<long>> finalizeSlicedFindPath()
|
||||
{
|
||||
List<long> path = new List<long>(64);
|
||||
if (m_query.status.isFailed()) {
|
||||
if (m_query.status.isFailed())
|
||||
{
|
||||
// Reset query.
|
||||
m_query = new QueryData();
|
||||
return Results.failure(path);
|
||||
}
|
||||
|
||||
if (m_query.startRef == m_query.endRef) {
|
||||
if (m_query.startRef == m_query.endRef)
|
||||
{
|
||||
// Special case: the search starts and ends at same poly.
|
||||
path.Add(m_query.startRef);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reverse the path.
|
||||
if (m_query.lastBestNode.id != m_query.endRef) {
|
||||
if (m_query.lastBestNode.id != m_query.endRef)
|
||||
{
|
||||
m_query.status = Status.PARTIAL_RESULT;
|
||||
}
|
||||
|
||||
Node prev = null;
|
||||
Node node = m_query.lastBestNode;
|
||||
int prevRay = 0;
|
||||
do {
|
||||
do
|
||||
{
|
||||
Node next = m_nodePool.getNodeAtIdx(node.pidx);
|
||||
node.pidx = m_nodePool.getNodeIdx(prev);
|
||||
prev = node;
|
||||
|
@ -447,18 +515,25 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
|
||||
// Store path
|
||||
node = prev;
|
||||
do {
|
||||
do
|
||||
{
|
||||
Node next = m_nodePool.getNodeAtIdx(node.pidx);
|
||||
if ((node.flags & Node.DT_NODE_PARENT_DETACHED) != 0) {
|
||||
if ((node.flags & Node.DT_NODE_PARENT_DETACHED) != 0)
|
||||
{
|
||||
Result<RaycastHit> iresult = raycast(node.id, node.pos, next.pos, m_query.filter, 0, 0);
|
||||
if (iresult.succeeded()) {
|
||||
if (iresult.succeeded())
|
||||
{
|
||||
path.AddRange(iresult.result.path);
|
||||
}
|
||||
|
||||
// raycast ends on poly boundary and the path might include the next poly boundary.
|
||||
if (path[path.Count - 1] == next.id) {
|
||||
if (path[path.Count - 1] == next.id)
|
||||
{
|
||||
path.RemoveAt(path.Count - 1); // remove to avoid duplicates
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
path.Add(node.id);
|
||||
}
|
||||
|
||||
|
@ -480,39 +555,50 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
/// @param[out] path An ordered list of polygon references representing the path. (Start to end.)
|
||||
/// [(polyRef) * @p pathCount]
|
||||
/// @returns The status flags for the query.
|
||||
public override Result<List<long>> finalizeSlicedFindPathPartial(List<long> existing) {
|
||||
|
||||
public override Result<List<long>> finalizeSlicedFindPathPartial(List<long> existing)
|
||||
{
|
||||
List<long> path = new List<long>(64);
|
||||
if (null == existing || existing.Count <= 0) {
|
||||
if (null == existing || existing.Count <= 0)
|
||||
{
|
||||
return Results.failure(path);
|
||||
}
|
||||
if (m_query.status.isFailed()) {
|
||||
|
||||
if (m_query.status.isFailed())
|
||||
{
|
||||
// Reset query.
|
||||
m_query = new QueryData();
|
||||
return Results.failure(path);
|
||||
}
|
||||
if (m_query.startRef == m_query.endRef) {
|
||||
|
||||
if (m_query.startRef == m_query.endRef)
|
||||
{
|
||||
// Special case: the search starts and ends at same poly.
|
||||
path.Add(m_query.startRef);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find furthest existing node that was visited.
|
||||
Node prev = null;
|
||||
Node node = null;
|
||||
for (int i = existing.Count - 1; i >= 0; --i) {
|
||||
for (int i = existing.Count - 1; i >= 0; --i)
|
||||
{
|
||||
node = m_nodePool.findNode(existing[i]);
|
||||
if (node != null) {
|
||||
if (node != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (node == null) {
|
||||
if (node == null)
|
||||
{
|
||||
m_query.status = Status.PARTIAL_RESULT;
|
||||
node = m_query.lastBestNode;
|
||||
}
|
||||
|
||||
// Reverse the path.
|
||||
int prevRay = 0;
|
||||
do {
|
||||
do
|
||||
{
|
||||
Node next = m_nodePool.getNodeAtIdx(node.pidx);
|
||||
node.pidx = m_nodePool.getNodeIdx(prev);
|
||||
prev = node;
|
||||
|
@ -526,24 +612,32 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
|
||||
// Store path
|
||||
node = prev;
|
||||
do {
|
||||
do
|
||||
{
|
||||
Node next = m_nodePool.getNodeAtIdx(node.pidx);
|
||||
if ((node.flags & Node.DT_NODE_PARENT_DETACHED) != 0) {
|
||||
if ((node.flags & Node.DT_NODE_PARENT_DETACHED) != 0)
|
||||
{
|
||||
Result<RaycastHit> iresult = raycast(node.id, node.pos, next.pos, m_query.filter, 0, 0);
|
||||
if (iresult.succeeded()) {
|
||||
if (iresult.succeeded())
|
||||
{
|
||||
path.AddRange(iresult.result.path);
|
||||
}
|
||||
|
||||
// raycast ends on poly boundary and the path might include the next poly boundary.
|
||||
if (path[path.Count - 1] == next.id) {
|
||||
if (path[path.Count - 1] == next.id)
|
||||
{
|
||||
path.RemoveAt(path.Count - 1); // remove to avoid duplicates
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
path.Add(node.id);
|
||||
}
|
||||
|
||||
node = next;
|
||||
} while (node != null);
|
||||
}
|
||||
|
||||
Status status = m_query.status;
|
||||
// Reset query.
|
||||
m_query = new QueryData();
|
||||
|
@ -552,11 +646,12 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
}
|
||||
|
||||
public override Result<FindDistanceToWallResult> findDistanceToWall(long startRef, float[] centerPos, float maxRadius,
|
||||
QueryFilter filter) {
|
||||
|
||||
QueryFilter filter)
|
||||
{
|
||||
// Validate input
|
||||
if (!m_nav.isValidPolyRef(startRef) || null == centerPos || !vIsFinite(centerPos) || maxRadius < 0
|
||||
|| !float.IsFinite(maxRadius) || null == filter) {
|
||||
|| !float.IsFinite(maxRadius) || null == filter)
|
||||
{
|
||||
return Results.invalidParam<FindDistanceToWallResult>();
|
||||
}
|
||||
|
||||
|
@ -576,7 +671,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
float[] hitPos = new float[3];
|
||||
VectorPtr bestvj = null;
|
||||
VectorPtr bestvi = null;
|
||||
while (!m_openList.isEmpty()) {
|
||||
while (!m_openList.isEmpty())
|
||||
{
|
||||
Node bestNode = m_openList.pop();
|
||||
bestNode.flags &= ~Node.DT_NODE_OPEN;
|
||||
bestNode.flags |= Node.DT_NODE_CLOSED;
|
||||
|
@ -590,38 +686,51 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
|
||||
// Get parent poly and tile.
|
||||
long parentRef = 0;
|
||||
if (bestNode.pidx != 0) {
|
||||
if (bestNode.pidx != 0)
|
||||
{
|
||||
parentRef = m_nodePool.getNodeAtIdx(bestNode.pidx).id;
|
||||
}
|
||||
|
||||
// Hit test walls.
|
||||
for (int i = 0, j = bestPoly.vertCount - 1; i < bestPoly.vertCount; j = i++) {
|
||||
for (int i = 0, j = bestPoly.vertCount - 1; i < bestPoly.vertCount; j = i++)
|
||||
{
|
||||
// Skip non-solid edges.
|
||||
if ((bestPoly.neis[j] & NavMesh.DT_EXT_LINK) != 0) {
|
||||
if ((bestPoly.neis[j] & NavMesh.DT_EXT_LINK) != 0)
|
||||
{
|
||||
// Tile border.
|
||||
bool solid = true;
|
||||
for (int k = bestTile.polyLinks[bestPoly.index]; k != NavMesh.DT_NULL_LINK; k = bestTile.links[k].next) {
|
||||
for (int k = bestTile.polyLinks[bestPoly.index]; k != NavMesh.DT_NULL_LINK; k = bestTile.links[k].next)
|
||||
{
|
||||
Link link = bestTile.links[k];
|
||||
if (link.edge == j) {
|
||||
if (link.refs != 0) {
|
||||
if (link.edge == j)
|
||||
{
|
||||
if (link.refs != 0)
|
||||
{
|
||||
Tuple<MeshTile, Poly> linkTileAndPoly = m_nav.getTileAndPolyByRefUnsafe(link.refs);
|
||||
MeshTile neiTile = linkTileAndPoly.Item1;
|
||||
Poly neiPoly = linkTileAndPoly.Item2;
|
||||
if (filter.passFilter(link.refs, neiTile, neiPoly)) {
|
||||
if (filter.passFilter(link.refs, neiTile, neiPoly))
|
||||
{
|
||||
solid = false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!solid) {
|
||||
|
||||
if (!solid)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
} else if (bestPoly.neis[j] != 0) {
|
||||
}
|
||||
else if (bestPoly.neis[j] != 0)
|
||||
{
|
||||
// Internal edge
|
||||
int idx = (bestPoly.neis[j] - 1);
|
||||
long refs = m_nav.getPolyRefBase(bestTile) | idx;
|
||||
if (filter.passFilter(refs, bestTile, bestTile.data.polys[idx])) {
|
||||
if (filter.passFilter(refs, bestTile, bestTile.data.polys[idx]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -634,7 +743,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
float tseg = distseg.Item2;
|
||||
|
||||
// Edge is too far, skip.
|
||||
if (distSqr > radiusSqr) {
|
||||
if (distSqr > radiusSqr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -650,11 +760,13 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
bestvi = new VectorPtr(bestTile.data.verts, vi);
|
||||
}
|
||||
|
||||
for (int i = bestTile.polyLinks[bestPoly.index]; i != NavMesh.DT_NULL_LINK; i = bestTile.links[i].next) {
|
||||
for (int i = bestTile.polyLinks[bestPoly.index]; i != NavMesh.DT_NULL_LINK; i = bestTile.links[i].next)
|
||||
{
|
||||
Link link = bestTile.links[i];
|
||||
long neighbourRef = link.refs;
|
||||
// Skip invalid neighbours and do not follow back to parent.
|
||||
if (neighbourRef == 0 || neighbourRef == parentRef) {
|
||||
if (neighbourRef == 0 || neighbourRef == parentRef)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -664,7 +776,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
Poly neighbourPoly = neighbourTileAndPoly.Item2;
|
||||
|
||||
// Skip off-mesh connections.
|
||||
if (neighbourPoly.getType() == Poly.DT_POLYTYPE_OFFMESH_CONNECTION) {
|
||||
if (neighbourPoly.getType() == Poly.DT_POLYTYPE_OFFMESH_CONNECTION)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -674,25 +787,30 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
Tuple<float, float> distseg = distancePtSegSqr2D(centerPos, bestTile.data.verts, va, vb);
|
||||
float distSqr = distseg.Item1;
|
||||
// If the circle is not touching the next polygon, skip it.
|
||||
if (distSqr > radiusSqr) {
|
||||
if (distSqr > radiusSqr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly)) {
|
||||
if (!filter.passFilter(neighbourRef, neighbourTile, neighbourPoly))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Node neighbourNode = m_nodePool.getNode(neighbourRef);
|
||||
|
||||
if ((neighbourNode.flags & Node.DT_NODE_CLOSED) != 0) {
|
||||
if ((neighbourNode.flags & Node.DT_NODE_CLOSED) != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cost
|
||||
if (neighbourNode.flags == 0) {
|
||||
if (neighbourNode.flags == 0)
|
||||
{
|
||||
Result<float[]> midPoint = getEdgeMidPoint(bestRef, bestPoly, bestTile, neighbourRef, neighbourPoly,
|
||||
neighbourTile);
|
||||
if (midPoint.succeeded()) {
|
||||
if (midPoint.succeeded())
|
||||
{
|
||||
neighbourNode.pos = midPoint.result;
|
||||
}
|
||||
}
|
||||
|
@ -700,7 +818,8 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
float total = bestNode.total + vDist(bestNode.pos, neighbourNode.pos);
|
||||
|
||||
// The node is already in open list and the new result is worse, skip.
|
||||
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0 && total >= neighbourNode.total) {
|
||||
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0 && total >= neighbourNode.total)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -709,9 +828,12 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
neighbourNode.pidx = m_nodePool.getNodeIdx(bestNode);
|
||||
neighbourNode.total = total;
|
||||
|
||||
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0) {
|
||||
if ((neighbourNode.flags & Node.DT_NODE_OPEN) != 0)
|
||||
{
|
||||
m_openList.modify(neighbourNode);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
neighbourNode.flags |= Node.DT_NODE_OPEN;
|
||||
m_openList.push(neighbourNode);
|
||||
}
|
||||
|
@ -720,15 +842,16 @@ public class LegacyNavMeshQuery : NavMeshQuery {
|
|||
|
||||
// Calc hit normal.
|
||||
float[] hitNormal = new float[3];
|
||||
if (bestvi != null && bestvj != null) {
|
||||
if (bestvi != null && bestvj != null)
|
||||
{
|
||||
float[] tangent = vSub(bestvi, bestvj);
|
||||
hitNormal[0] = tangent[2];
|
||||
hitNormal[1] = 0;
|
||||
hitNormal[2] = -tangent[0];
|
||||
vNormalize(hitNormal);
|
||||
}
|
||||
|
||||
return Results.success(new FindDistanceToWallResult((float)Math.Sqrt(radiusSqr), hitPos, hitNormal));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -17,30 +17,33 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Defines a link between polygons.
|
||||
*
|
||||
* @note This structure is rarely if ever used by the end user.
|
||||
* @see MeshTile
|
||||
*/
|
||||
public class Link {
|
||||
public class Link
|
||||
{
|
||||
/** Neighbour reference. (The neighbor that is linked to.) */
|
||||
public long refs;
|
||||
|
||||
/** Index of the next link. */
|
||||
public int next;
|
||||
|
||||
/** Index of the polygon edge that owns this link. */
|
||||
public int edge;
|
||||
|
||||
/** If a boundary link, defines on which side the link is. */
|
||||
public int side;
|
||||
|
||||
/** If a boundary link, defines the minimum sub-edge area. */
|
||||
public int bmin;
|
||||
|
||||
/** If a boundary link, defines the maximum sub-edge area. */
|
||||
public int bmax;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,33 +17,38 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
public class MeshData {
|
||||
|
||||
public class MeshData
|
||||
{
|
||||
/** The tile header. */
|
||||
public MeshHeader header;
|
||||
|
||||
/** The tile vertices. [Size: MeshHeader::vertCount] */
|
||||
public float[] verts;
|
||||
|
||||
/** The tile polygons. [Size: MeshHeader::polyCount] */
|
||||
public Poly[] polys;
|
||||
|
||||
/** The tile's detail sub-meshes. [Size: MeshHeader::detailMeshCount] */
|
||||
public PolyDetail[] detailMeshes;
|
||||
|
||||
/** The detail mesh's unique vertices. [(x, y, z) * MeshHeader::detailVertCount] */
|
||||
public float[] detailVerts;
|
||||
|
||||
/**
|
||||
* The detail mesh's triangles. [(vertA, vertB, vertC) * MeshHeader::detailTriCount] See DetailTriEdgeFlags and
|
||||
* NavMesh::getDetailTriEdgeFlags.
|
||||
*/
|
||||
public int[] detailTris;
|
||||
|
||||
/**
|
||||
* The tile bounding volume nodes. [Size: MeshHeader::bvNodeCount] (Will be null if bounding volumes are disabled.)
|
||||
*/
|
||||
public BVNode[] bvTree;
|
||||
|
||||
/** The tile off-mesh connections. [Size: MeshHeader::offMeshConCount] */
|
||||
public OffMeshConnection[] offMeshCons;
|
||||
|
||||
}
|
||||
}
|
|
@ -17,67 +17,90 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
/** Provides high level information related to a dtMeshTile object. */
|
||||
public class MeshHeader {
|
||||
public class MeshHeader
|
||||
{
|
||||
/** A magic number used to detect compatibility of navigation tile data. */
|
||||
public const int DT_NAVMESH_MAGIC = 'D' << 24 | 'N' << 16 | 'A' << 8 | 'V';
|
||||
|
||||
/** A version number used to detect compatibility of navigation tile data. */
|
||||
public const int DT_NAVMESH_VERSION = 7;
|
||||
|
||||
public const int DT_NAVMESH_VERSION_RECAST4J_FIRST = 0x8807;
|
||||
public const int DT_NAVMESH_VERSION_RECAST4J_NO_POLY_FIRSTLINK = 0x8808;
|
||||
public const int DT_NAVMESH_VERSION_RECAST4J_32BIT_BVTREE = 0x8809;
|
||||
public const int DT_NAVMESH_VERSION_RECAST4J_LAST = 0x8809;
|
||||
|
||||
/** A magic number used to detect the compatibility of navigation tile states. */
|
||||
public const int DT_NAVMESH_STATE_MAGIC = 'D' << 24 | 'N' << 16 | 'M' << 8 | 'S';
|
||||
|
||||
/** A version number used to detect compatibility of navigation tile states. */
|
||||
public const int DT_NAVMESH_STATE_VERSION = 1;
|
||||
|
||||
/** Tile magic number. (Used to identify the data format.) */
|
||||
public int magic;
|
||||
|
||||
/** Tile data format version number. */
|
||||
public int version;
|
||||
|
||||
/** The x-position of the tile within the dtNavMesh tile grid. (x, y, layer) */
|
||||
public int x;
|
||||
|
||||
/** The y-position of the tile within the dtNavMesh tile grid. (x, y, layer) */
|
||||
public int y;
|
||||
|
||||
/** The layer of the tile within the dtNavMesh tile grid. (x, y, layer) */
|
||||
public int layer;
|
||||
|
||||
/** The user defined id of the tile. */
|
||||
public int userId;
|
||||
|
||||
/** The number of polygons in the tile. */
|
||||
public int polyCount;
|
||||
|
||||
/** The number of vertices in the tile. */
|
||||
public int vertCount;
|
||||
|
||||
/** The number of allocated links. */
|
||||
public int maxLinkCount;
|
||||
|
||||
/** The number of sub-meshes in the detail mesh. */
|
||||
public int detailMeshCount;
|
||||
|
||||
/** The number of unique vertices in the detail mesh. (In addition to the polygon vertices.) */
|
||||
public int detailVertCount;
|
||||
|
||||
/** The number of triangles in the detail mesh. */
|
||||
public int detailTriCount;
|
||||
|
||||
/** The number of bounding volume nodes. (Zero if bounding volumes are disabled.) */
|
||||
public int bvNodeCount;
|
||||
|
||||
/** The number of off-mesh connections. */
|
||||
public int offMeshConCount;
|
||||
|
||||
/** The index of the first polygon which is an off-mesh connection. */
|
||||
public int offMeshBase;
|
||||
|
||||
/** The height of the agents using the tile. */
|
||||
public float walkableHeight;
|
||||
|
||||
/** The radius of the agents using the tile. */
|
||||
public float walkableRadius;
|
||||
|
||||
/** The maximum climb height of the agents using the tile. */
|
||||
public float walkableClimb;
|
||||
|
||||
/** The minimum bounds of the tile's AABB. [(x, y, z)] */
|
||||
public readonly float[] bmin = new float[3];
|
||||
|
||||
/** The maximum bounds of the tile's AABB. [(x, y, z)] */
|
||||
public readonly float[] bmax = new float[3];
|
||||
|
||||
/** The bounding volume quantization factor. */
|
||||
public float bvQuantFactor;
|
||||
}
|
||||
|
||||
}
|
|
@ -22,29 +22,33 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Defines a navigation mesh tile.
|
||||
*/
|
||||
public class MeshTile {
|
||||
public class MeshTile
|
||||
{
|
||||
public readonly int index;
|
||||
|
||||
/** Counter describing modifications to the tile. */
|
||||
public int salt;
|
||||
|
||||
/** The tile data. */
|
||||
public MeshData data;
|
||||
|
||||
public int[] polyLinks;
|
||||
|
||||
/** The tile links. */
|
||||
public readonly List<Link> links = new List<Link>();
|
||||
|
||||
/** Index to the next free link. */
|
||||
public int linksFreeList = NavMesh.DT_NULL_LINK; // FIXME: Remove
|
||||
|
||||
/** Tile flags. (See: #dtTileFlags) */
|
||||
public int flags;
|
||||
|
||||
public MeshTile(int index) {
|
||||
public MeshTile(int index)
|
||||
{
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,29 +22,28 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
|
||||
public class MoveAlongSurfaceResult {
|
||||
|
||||
public class MoveAlongSurfaceResult
|
||||
{
|
||||
/** The result position of the mover. [(x, y, z)] */
|
||||
private readonly float[] resultPos;
|
||||
|
||||
/** The reference ids of the polygons visited during the move. */
|
||||
private readonly List<long> visited;
|
||||
|
||||
public MoveAlongSurfaceResult(float[] resultPos, List<long> visited) {
|
||||
public MoveAlongSurfaceResult(float[] resultPos, List<long> visited)
|
||||
{
|
||||
this.resultPos = resultPos;
|
||||
this.visited = visited;
|
||||
}
|
||||
|
||||
public float[] getResultPos() {
|
||||
public float[] getResultPos()
|
||||
{
|
||||
return resultPos;
|
||||
}
|
||||
|
||||
public List<long> getVisited() {
|
||||
public List<long> getVisited()
|
||||
{
|
||||
return visited;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -23,45 +23,45 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
using static DetourCommon;
|
||||
|
||||
public class NavMeshBuilder {
|
||||
|
||||
public class NavMeshBuilder
|
||||
{
|
||||
const int MESH_NULL_IDX = 0xffff;
|
||||
|
||||
public class BVItem {
|
||||
public class BVItem
|
||||
{
|
||||
public readonly int[] bmin = new int[3];
|
||||
public readonly int[] bmax = new int[3];
|
||||
public int i;
|
||||
};
|
||||
|
||||
private class CompareItemX : IComparer<BVItem> {
|
||||
|
||||
public int Compare(BVItem a, BVItem b) {
|
||||
private class CompareItemX : IComparer<BVItem>
|
||||
{
|
||||
public int Compare(BVItem a, BVItem b)
|
||||
{
|
||||
return a.bmin[0].CompareTo(b.bmin[0]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class CompareItemY : IComparer<BVItem> {
|
||||
|
||||
public int Compare(BVItem a, BVItem b) {
|
||||
private class CompareItemY : IComparer<BVItem>
|
||||
{
|
||||
public int Compare(BVItem a, BVItem b)
|
||||
{
|
||||
return a.bmin[1].CompareTo(b.bmin[1]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class CompareItemZ : IComparer<BVItem> {
|
||||
|
||||
public int Compare(BVItem a, BVItem b) {
|
||||
private class CompareItemZ : IComparer<BVItem>
|
||||
{
|
||||
public int Compare(BVItem a, BVItem b)
|
||||
{
|
||||
return a.bmin[2].CompareTo(b.bmin[2]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static int[][] calcExtends(BVItem[] items, int nitems, int imin, int imax) {
|
||||
private static int[][] calcExtends(BVItem[] items, int nitems, int imin, int imax)
|
||||
{
|
||||
int[] bmin = new int[3];
|
||||
int[] bmax = new int[3];
|
||||
bmin[0] = items[imin].bmin[0];
|
||||
|
@ -72,7 +72,8 @@ public class NavMeshBuilder {
|
|||
bmax[1] = items[imin].bmax[1];
|
||||
bmax[2] = items[imin].bmax[2];
|
||||
|
||||
for (int i = imin + 1; i < imax; ++i) {
|
||||
for (int i = imin + 1; i < imax; ++i)
|
||||
{
|
||||
BVItem it = items[i];
|
||||
if (it.bmin[0] < bmin[0])
|
||||
bmin[0] = it.bmin[0];
|
||||
|
@ -88,31 +89,39 @@ public class NavMeshBuilder {
|
|||
if (it.bmax[2] > bmax[2])
|
||||
bmax[2] = it.bmax[2];
|
||||
}
|
||||
|
||||
return new int[][] { bmin, bmax };
|
||||
}
|
||||
|
||||
private static int longestAxis(int x, int y, int z) {
|
||||
private static int longestAxis(int x, int y, int z)
|
||||
{
|
||||
int axis = 0;
|
||||
int maxVal = x;
|
||||
if (y > maxVal) {
|
||||
if (y > maxVal)
|
||||
{
|
||||
axis = 1;
|
||||
maxVal = y;
|
||||
}
|
||||
if (z > maxVal) {
|
||||
|
||||
if (z > maxVal)
|
||||
{
|
||||
axis = 2;
|
||||
maxVal = z;
|
||||
}
|
||||
|
||||
return axis;
|
||||
}
|
||||
|
||||
public static int subdivide(BVItem[] items, int nitems, int imin, int imax, int curNode, BVNode[] nodes) {
|
||||
public static int subdivide(BVItem[] items, int nitems, int imin, int imax, int curNode, BVNode[] nodes)
|
||||
{
|
||||
int inum = imax - imin;
|
||||
int icur = curNode;
|
||||
|
||||
BVNode node = new BVNode();
|
||||
nodes[curNode++] = node;
|
||||
|
||||
if (inum == 1) {
|
||||
if (inum == 1)
|
||||
{
|
||||
// Leaf
|
||||
node.bmin[0] = items[imin].bmin[0];
|
||||
node.bmin[1] = items[imin].bmin[1];
|
||||
|
@ -123,7 +132,9 @@ public class NavMeshBuilder {
|
|||
node.bmax[2] = items[imin].bmax[2];
|
||||
|
||||
node.i = items[imin].i;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Split
|
||||
int[][] minmax = calcExtends(items, nitems, imin, imax);
|
||||
node.bmin = minmax[0];
|
||||
|
@ -132,13 +143,18 @@ public class NavMeshBuilder {
|
|||
int axis = longestAxis(node.bmax[0] - node.bmin[0], node.bmax[1] - node.bmin[1],
|
||||
node.bmax[2] - node.bmin[2]);
|
||||
|
||||
if (axis == 0) {
|
||||
if (axis == 0)
|
||||
{
|
||||
// Sort along x-axis
|
||||
Array.Sort(items, imin, inum, new CompareItemX());
|
||||
} else if (axis == 1) {
|
||||
}
|
||||
else if (axis == 1)
|
||||
{
|
||||
// Sort along y-axis
|
||||
Array.Sort(items, imin, inum, new CompareItemY());
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sort along z-axis
|
||||
Array.Sort(items, imin, inum, new CompareItemZ());
|
||||
}
|
||||
|
@ -154,19 +170,23 @@ public class NavMeshBuilder {
|
|||
// Negative index means escape.
|
||||
node.i = -iescape;
|
||||
}
|
||||
|
||||
return curNode;
|
||||
}
|
||||
|
||||
private static int createBVTree(NavMeshDataCreateParams option, BVNode[] nodes) {
|
||||
private static int createBVTree(NavMeshDataCreateParams option, BVNode[] nodes)
|
||||
{
|
||||
// Build tree
|
||||
float quantFactor = 1 / option.cs;
|
||||
BVItem[] items = new BVItem[option.polyCount];
|
||||
for (int i = 0; i < option.polyCount; i++) {
|
||||
for (int i = 0; i < option.polyCount; i++)
|
||||
{
|
||||
BVItem it = new BVItem();
|
||||
items[i] = it;
|
||||
it.i = i;
|
||||
// Calc polygon bounds. Use detail meshes if available.
|
||||
if (option.detailMeshes != null) {
|
||||
if (option.detailMeshes != null)
|
||||
{
|
||||
int vb = option.detailMeshes[i * 4 + 0];
|
||||
int ndv = option.detailMeshes[i * 4 + 1];
|
||||
float[] bmin = new float[3];
|
||||
|
@ -174,7 +194,8 @@ public class NavMeshBuilder {
|
|||
int dv = vb * 3;
|
||||
vCopy(bmin, option.detailVerts, dv);
|
||||
vCopy(bmax, option.detailVerts, dv);
|
||||
for (int j = 1; j < ndv; j++) {
|
||||
for (int j = 1; j < ndv; j++)
|
||||
{
|
||||
vMin(bmin, option.detailVerts, dv + j * 3);
|
||||
vMax(bmax, option.detailVerts, dv + j * 3);
|
||||
}
|
||||
|
@ -187,13 +208,16 @@ public class NavMeshBuilder {
|
|||
it.bmax[0] = clamp((int)((bmax[0] - option.bmin[0]) * quantFactor), 0, int.MaxValue);
|
||||
it.bmax[1] = clamp((int)((bmax[1] - option.bmin[1]) * quantFactor), 0, int.MaxValue);
|
||||
it.bmax[2] = clamp((int)((bmax[2] - option.bmin[2]) * quantFactor), 0, int.MaxValue);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
int p = i * option.nvp * 2;
|
||||
it.bmin[0] = it.bmax[0] = option.verts[option.polys[p] * 3 + 0];
|
||||
it.bmin[1] = it.bmax[1] = option.verts[option.polys[p] * 3 + 1];
|
||||
it.bmin[2] = it.bmax[2] = option.verts[option.polys[p] * 3 + 2];
|
||||
|
||||
for (int j = 1; j < option.nvp; ++j) {
|
||||
for (int j = 1; j < option.nvp; ++j)
|
||||
{
|
||||
if (option.polys[p + j] == MESH_NULL_IDX)
|
||||
break;
|
||||
int x = option.verts[option.polys[p + j] * 3 + 0];
|
||||
|
@ -214,6 +238,7 @@ public class NavMeshBuilder {
|
|||
if (z > it.bmax[2])
|
||||
it.bmax[2] = z;
|
||||
}
|
||||
|
||||
// Remap y
|
||||
it.bmin[1] = (int)Math.Floor(it.bmin[1] * option.ch * quantFactor);
|
||||
it.bmax[1] = (int)Math.Ceiling(it.bmax[1] * option.ch * quantFactor);
|
||||
|
@ -228,15 +253,16 @@ public class NavMeshBuilder {
|
|||
const int XM = 1 << 2;
|
||||
const int ZM = 1 << 3;
|
||||
|
||||
public static int classifyOffMeshPoint(VectorPtr pt, float[] bmin, float[] bmax) {
|
||||
|
||||
public static int classifyOffMeshPoint(VectorPtr pt, float[] bmin, float[] bmax)
|
||||
{
|
||||
int outcode = 0;
|
||||
outcode |= (pt.get(0) >= bmax[0]) ? XP : 0;
|
||||
outcode |= (pt.get(2) >= bmax[2]) ? ZP : 0;
|
||||
outcode |= (pt.get(0) < bmin[0]) ? XM : 0;
|
||||
outcode |= (pt.get(2) < bmin[2]) ? ZM : 0;
|
||||
|
||||
switch (outcode) {
|
||||
switch (outcode)
|
||||
{
|
||||
case XP:
|
||||
return 0;
|
||||
case XP | ZP:
|
||||
|
@ -266,7 +292,8 @@ public class NavMeshBuilder {
|
|||
*
|
||||
* @return created tile data
|
||||
*/
|
||||
public static MeshData createNavMeshData(NavMeshDataCreateParams option) {
|
||||
public static MeshData createNavMeshData(NavMeshDataCreateParams option)
|
||||
{
|
||||
if (option.vertCount >= 0xffff)
|
||||
return null;
|
||||
if (option.vertCount == 0 || option.verts == null)
|
||||
|
@ -282,7 +309,8 @@ public class NavMeshBuilder {
|
|||
int storedOffMeshConCount = 0;
|
||||
int offMeshConLinkCount = 0;
|
||||
|
||||
if (option.offMeshConCount > 0) {
|
||||
if (option.offMeshConCount > 0)
|
||||
{
|
||||
offMeshConClass = new int[option.offMeshConCount * 2];
|
||||
|
||||
// Find tight heigh bounds, used for culling out off-mesh start
|
||||
|
@ -290,20 +318,26 @@ public class NavMeshBuilder {
|
|||
float hmin = float.MaxValue;
|
||||
float hmax = -float.MaxValue;
|
||||
|
||||
if (option.detailVerts != null && option.detailVertsCount != 0) {
|
||||
for (int i = 0; i < option.detailVertsCount; ++i) {
|
||||
if (option.detailVerts != null && option.detailVertsCount != 0)
|
||||
{
|
||||
for (int i = 0; i < option.detailVertsCount; ++i)
|
||||
{
|
||||
float h = option.detailVerts[i * 3 + 1];
|
||||
hmin = Math.Min(hmin, h);
|
||||
hmax = Math.Max(hmax, h);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < option.vertCount; ++i) {
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < option.vertCount; ++i)
|
||||
{
|
||||
int iv = i * 3;
|
||||
float h = option.bmin[1] + option.verts[iv + 1] * option.ch;
|
||||
hmin = Math.Min(hmin, h);
|
||||
hmax = Math.Max(hmax, h);
|
||||
}
|
||||
}
|
||||
|
||||
hmin -= option.walkableClimb;
|
||||
hmax += option.walkableClimb;
|
||||
float[] bmin = new float[3];
|
||||
|
@ -313,7 +347,8 @@ public class NavMeshBuilder {
|
|||
bmin[1] = hmin;
|
||||
bmax[1] = hmax;
|
||||
|
||||
for (int i = 0; i < option.offMeshConCount; ++i) {
|
||||
for (int i = 0; i < option.offMeshConCount; ++i)
|
||||
{
|
||||
VectorPtr p0 = new VectorPtr(option.offMeshConVerts, (i * 2 + 0) * 3);
|
||||
VectorPtr p1 = new VectorPtr(option.offMeshConVerts, (i * 2 + 1) * 3);
|
||||
|
||||
|
@ -322,7 +357,8 @@ public class NavMeshBuilder {
|
|||
|
||||
// Zero out off-mesh start positions which are not even
|
||||
// potentially touching the mesh.
|
||||
if (offMeshConClass[i * 2 + 0] == 0xff) {
|
||||
if (offMeshConClass[i * 2 + 0] == 0xff)
|
||||
{
|
||||
if (p0.get(1) < bmin[1] || p0.get(1) > bmax[1])
|
||||
offMeshConClass[i * 2 + 0] = 0;
|
||||
}
|
||||
|
@ -346,14 +382,17 @@ public class NavMeshBuilder {
|
|||
// Find portal edges which are at tile borders.
|
||||
int edgeCount = 0;
|
||||
int portalCount = 0;
|
||||
for (int i = 0; i < option.polyCount; ++i) {
|
||||
for (int i = 0; i < option.polyCount; ++i)
|
||||
{
|
||||
int p = i * 2 * nvp;
|
||||
for (int j = 0; j < nvp; ++j) {
|
||||
for (int j = 0; j < nvp; ++j)
|
||||
{
|
||||
if (option.polys[p + j] == MESH_NULL_IDX)
|
||||
break;
|
||||
edgeCount++;
|
||||
|
||||
if ((option.polys[p + nvp + j] & 0x8000) != 0) {
|
||||
if ((option.polys[p + nvp + j] & 0x8000) != 0)
|
||||
{
|
||||
int dir = option.polys[p + nvp + j] & 0xf;
|
||||
if (dir != 0xf)
|
||||
portalCount++;
|
||||
|
@ -366,34 +405,43 @@ public class NavMeshBuilder {
|
|||
// Find unique detail vertices.
|
||||
int uniqueDetailVertCount = 0;
|
||||
int detailTriCount = 0;
|
||||
if (option.detailMeshes != null) {
|
||||
if (option.detailMeshes != null)
|
||||
{
|
||||
// Has detail mesh, count unique detail vertex count and use input
|
||||
// detail tri count.
|
||||
detailTriCount = option.detailTriCount;
|
||||
for (int i = 0; i < option.polyCount; ++i) {
|
||||
for (int i = 0; i < option.polyCount; ++i)
|
||||
{
|
||||
int p = i * nvp * 2;
|
||||
int ndv = option.detailMeshes[i * 4 + 1];
|
||||
int nv = 0;
|
||||
for (int j = 0; j < nvp; ++j) {
|
||||
for (int j = 0; j < nvp; ++j)
|
||||
{
|
||||
if (option.polys[p + j] == MESH_NULL_IDX)
|
||||
break;
|
||||
nv++;
|
||||
}
|
||||
|
||||
ndv -= nv;
|
||||
uniqueDetailVertCount += ndv;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// No input detail mesh, build detail mesh from nav polys.
|
||||
uniqueDetailVertCount = 0; // No extra detail verts.
|
||||
detailTriCount = 0;
|
||||
for (int i = 0; i < option.polyCount; ++i) {
|
||||
for (int i = 0; i < option.polyCount; ++i)
|
||||
{
|
||||
int p = i * nvp * 2;
|
||||
int nv = 0;
|
||||
for (int j = 0; j < nvp; ++j) {
|
||||
for (int j = 0; j < nvp; ++j)
|
||||
{
|
||||
if (option.polys[p + j] == MESH_NULL_IDX)
|
||||
break;
|
||||
nv++;
|
||||
}
|
||||
|
||||
detailTriCount += nv - 2;
|
||||
}
|
||||
}
|
||||
|
@ -436,18 +484,22 @@ public class NavMeshBuilder {
|
|||
|
||||
// Store vertices
|
||||
// Mesh vertices
|
||||
for (int i = 0; i < option.vertCount; ++i) {
|
||||
for (int i = 0; i < option.vertCount; ++i)
|
||||
{
|
||||
int iv = i * 3;
|
||||
int v = i * 3;
|
||||
navVerts[v] = option.bmin[0] + option.verts[iv] * option.cs;
|
||||
navVerts[v + 1] = option.bmin[1] + option.verts[iv + 1] * option.ch;
|
||||
navVerts[v + 2] = option.bmin[2] + option.verts[iv + 2] * option.cs;
|
||||
}
|
||||
|
||||
// Off-mesh link vertices.
|
||||
int n = 0;
|
||||
for (int i = 0; i < option.offMeshConCount; ++i) {
|
||||
for (int i = 0; i < option.offMeshConCount; ++i)
|
||||
{
|
||||
// Only store connections which start from this tile.
|
||||
if (offMeshConClass[i * 2 + 0] == 0xff) {
|
||||
if (offMeshConClass[i * 2 + 0] == 0xff)
|
||||
{
|
||||
int linkv = i * 2 * 3;
|
||||
int v = (offMeshVertsBase + n * 2) * 3;
|
||||
Array.Copy(option.offMeshConVerts, linkv, navVerts, v, 6);
|
||||
|
@ -458,18 +510,21 @@ public class NavMeshBuilder {
|
|||
// Store polygons
|
||||
// Mesh polys
|
||||
int src = 0;
|
||||
for (int i = 0; i < option.polyCount; ++i) {
|
||||
for (int i = 0; i < option.polyCount; ++i)
|
||||
{
|
||||
Poly p = new Poly(i, nvp);
|
||||
navPolys[i] = p;
|
||||
p.vertCount = 0;
|
||||
p.flags = option.polyFlags[i];
|
||||
p.setArea(option.polyAreas[i]);
|
||||
p.setType(Poly.DT_POLYTYPE_GROUND);
|
||||
for (int j = 0; j < nvp; ++j) {
|
||||
for (int j = 0; j < nvp; ++j)
|
||||
{
|
||||
if (option.polys[src + j] == MESH_NULL_IDX)
|
||||
break;
|
||||
p.verts[j] = option.polys[src + j];
|
||||
if ((option.polys[src + nvp + j] & 0x8000) != 0) {
|
||||
if ((option.polys[src + nvp + j] & 0x8000) != 0)
|
||||
{
|
||||
// Border or portal edge.
|
||||
int dir = option.polys[src + nvp + j] & 0xf;
|
||||
if (dir == 0xf) // Border
|
||||
|
@ -482,20 +537,26 @@ public class NavMeshBuilder {
|
|||
p.neis[j] = NavMesh.DT_EXT_LINK | 0;
|
||||
else if (dir == 3) // Portal z-
|
||||
p.neis[j] = NavMesh.DT_EXT_LINK | 6;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal connection
|
||||
p.neis[j] = option.polys[src + nvp + j] + 1;
|
||||
}
|
||||
|
||||
p.vertCount++;
|
||||
}
|
||||
|
||||
src += nvp * 2;
|
||||
}
|
||||
|
||||
// Off-mesh connection vertices.
|
||||
n = 0;
|
||||
for (int i = 0; i < option.offMeshConCount; ++i) {
|
||||
for (int i = 0; i < option.offMeshConCount; ++i)
|
||||
{
|
||||
// Only store connections which start from this tile.
|
||||
if (offMeshConClass[i * 2 + 0] == 0xff) {
|
||||
if (offMeshConClass[i * 2 + 0] == 0xff)
|
||||
{
|
||||
Poly p = new Poly(offMeshPolyBase + n, nvp);
|
||||
navPolys[offMeshPolyBase + n] = p;
|
||||
p.vertCount = 2;
|
||||
|
@ -513,9 +574,11 @@ public class NavMeshBuilder {
|
|||
// mesh.
|
||||
// We compress the mesh data by skipping them and using the navmesh
|
||||
// coordinates.
|
||||
if (option.detailMeshes != null) {
|
||||
if (option.detailMeshes != null)
|
||||
{
|
||||
int vbase = 0;
|
||||
for (int i = 0; i < option.polyCount; ++i) {
|
||||
for (int i = 0; i < option.polyCount; ++i)
|
||||
{
|
||||
PolyDetail dtl = new PolyDetail();
|
||||
navDMeshes[i] = dtl;
|
||||
int vb = option.detailMeshes[i * 4 + 0];
|
||||
|
@ -527,17 +590,22 @@ public class NavMeshBuilder {
|
|||
dtl.triCount = option.detailMeshes[i * 4 + 3];
|
||||
// Copy vertices except the first 'nv' verts which are equal to
|
||||
// nav poly verts.
|
||||
if (ndv - nv != 0) {
|
||||
if (ndv - nv != 0)
|
||||
{
|
||||
Array.Copy(option.detailVerts, (vb + nv) * 3, navDVerts, vbase * 3, 3 * (ndv - nv));
|
||||
vbase += ndv - nv;
|
||||
}
|
||||
}
|
||||
|
||||
// Store triangles.
|
||||
Array.Copy(option.detailTris, 0, navDTris, 0, 4 * option.detailTriCount);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create dummy detail mesh by triangulating polys.
|
||||
int tbase = 0;
|
||||
for (int i = 0; i < option.polyCount; ++i) {
|
||||
for (int i = 0; i < option.polyCount; ++i)
|
||||
{
|
||||
PolyDetail dtl = new PolyDetail();
|
||||
navDMeshes[i] = dtl;
|
||||
int nv = navPolys[i].vertCount;
|
||||
|
@ -546,7 +614,8 @@ public class NavMeshBuilder {
|
|||
dtl.triBase = tbase;
|
||||
dtl.triCount = (nv - 2);
|
||||
// Triangulate polygon (local indices).
|
||||
for (int j = 2; j < nv; ++j) {
|
||||
for (int j = 2; j < nv; ++j)
|
||||
{
|
||||
int t = tbase * 4;
|
||||
navDTris[t + 0] = 0;
|
||||
navDTris[t + 1] = (j - 1);
|
||||
|
@ -564,16 +633,19 @@ public class NavMeshBuilder {
|
|||
|
||||
// Store and create BVtree.
|
||||
// TODO: take detail mesh into account! use byte per bbox extent?
|
||||
if (option.buildBvTree) {
|
||||
if (option.buildBvTree)
|
||||
{
|
||||
// Do not set header.bvNodeCount set to make it work look exactly the same as in original Detour
|
||||
header.bvNodeCount = createBVTree(option, navBvtree);
|
||||
}
|
||||
|
||||
// Store Off-Mesh connections.
|
||||
n = 0;
|
||||
for (int i = 0; i < option.offMeshConCount; ++i) {
|
||||
for (int i = 0; i < option.offMeshConCount; ++i)
|
||||
{
|
||||
// Only store connections which start from this tile.
|
||||
if (offMeshConClass[i * 2 + 0] == 0xff) {
|
||||
if (offMeshConClass[i * 2 + 0] == 0xff)
|
||||
{
|
||||
OffMeshConnection con = new OffMeshConnection();
|
||||
offMeshCons[n] = con;
|
||||
con.poly = (offMeshPolyBase + n);
|
||||
|
@ -600,7 +672,5 @@ public class NavMeshBuilder {
|
|||
nmd.offMeshCons = offMeshCons;
|
||||
return nmd;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,59 +17,83 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
/// Represents the source data used to build an navigation mesh tile.
|
||||
public class NavMeshDataCreateParams {
|
||||
|
||||
public class NavMeshDataCreateParams
|
||||
{
|
||||
/// @name Polygon Mesh Attributes
|
||||
/// Used to create the base navigation graph.
|
||||
/// See #rcPolyMesh for details related to these attributes.
|
||||
/// @{
|
||||
public int[] verts;
|
||||
|
||||
public int[] verts; /// < The polygon mesh vertices. [(x, y, z) * #vertCount] [Unit: vx]
|
||||
public int vertCount; /// < The number vertices in the polygon mesh. [Limit: >= 3]
|
||||
public int[] polys; /// < The polygon data. [Size: #polyCount * 2 * #nvp]
|
||||
public int[] polyFlags; /// < The user defined flags assigned to each polygon. [Size: #polyCount]
|
||||
public int[] polyAreas; /// < The user defined area ids assigned to each polygon. [Size: #polyCount]
|
||||
public int polyCount; /// < Number of polygons in the mesh. [Limit: >= 1]
|
||||
public int nvp; /// < Number maximum number of vertices per polygon. [Limit: >= 3]
|
||||
/// < The polygon mesh vertices. [(x, y, z) * #vertCount] [Unit: vx]
|
||||
public int vertCount;
|
||||
|
||||
/// < The number vertices in the polygon mesh. [Limit: >= 3]
|
||||
public int[] polys;
|
||||
|
||||
/// < The polygon data. [Size: #polyCount * 2 * #nvp]
|
||||
public int[] polyFlags;
|
||||
|
||||
/// < The user defined flags assigned to each polygon. [Size: #polyCount]
|
||||
public int[] polyAreas;
|
||||
|
||||
/// < The user defined area ids assigned to each polygon. [Size: #polyCount]
|
||||
public int polyCount;
|
||||
|
||||
/// < Number of polygons in the mesh. [Limit: >= 1]
|
||||
public int nvp;
|
||||
|
||||
/// < Number maximum number of vertices per polygon. [Limit: >= 3]
|
||||
/// @}
|
||||
/// @name Height Detail Attributes (Optional)
|
||||
/// See #rcPolyMeshDetail for details related to these attributes.
|
||||
/// @{
|
||||
public int[] detailMeshes;
|
||||
|
||||
public int[] detailMeshes; /// < The height detail sub-mesh data. [Size: 4 * #polyCount]
|
||||
public float[] detailVerts; /// < The detail mesh vertices. [Size: 3 * #detailVertsCount] [Unit: wu]
|
||||
public int detailVertsCount; /// < The number of vertices in the detail mesh.
|
||||
public int[] detailTris; /// < The detail mesh triangles. [Size: 4 * #detailTriCount]
|
||||
public int detailTriCount; /// < The number of triangles in the detail mesh.
|
||||
/// < The height detail sub-mesh data. [Size: 4 * #polyCount]
|
||||
public float[] detailVerts;
|
||||
|
||||
/// < The detail mesh vertices. [Size: 3 * #detailVertsCount] [Unit: wu]
|
||||
public int detailVertsCount;
|
||||
|
||||
/// < The number of vertices in the detail mesh.
|
||||
public int[] detailTris;
|
||||
|
||||
/// < The detail mesh triangles. [Size: 4 * #detailTriCount]
|
||||
public int detailTriCount;
|
||||
|
||||
/// < The number of triangles in the detail mesh.
|
||||
/// @}
|
||||
/// @name Off-Mesh Connections Attributes (Optional)
|
||||
/// Used to define a custom point-to-point edge within the navigation graph, an
|
||||
/// off-mesh connection is a user defined traversable connection made up to two vertices,
|
||||
/// at least one of which resides within a navigation mesh polygon.
|
||||
/// @{
|
||||
|
||||
/// Off-mesh connection vertices. [(ax, ay, az, bx, by, bz) * #offMeshConCount] [Unit: wu]
|
||||
public float[] offMeshConVerts;
|
||||
|
||||
/// Off-mesh connection radii. [Size: #offMeshConCount] [Unit: wu]
|
||||
public float[] offMeshConRad;
|
||||
|
||||
/// User defined flags assigned to the off-mesh connections. [Size: #offMeshConCount]
|
||||
public int[] offMeshConFlags;
|
||||
|
||||
/// User defined area ids assigned to the off-mesh connections. [Size: #offMeshConCount]
|
||||
public int[] offMeshConAreas;
|
||||
|
||||
/// The permitted travel direction of the off-mesh connections. [Size: #offMeshConCount]
|
||||
///
|
||||
/// 0 = Travel only from endpoint A to endpoint B.<br/>
|
||||
/// #DT_OFFMESH_CON_BIDIR = Bidirectional travel.
|
||||
public int[] offMeshConDir;
|
||||
|
||||
/// The user defined ids of the off-mesh connection. [Size: #offMeshConCount]
|
||||
public int[] offMeshConUserID;
|
||||
|
||||
/// The number of off-mesh connections. [Limit: >= 0]
|
||||
public int offMeshConCount;
|
||||
|
||||
|
@ -77,29 +101,46 @@ public class NavMeshDataCreateParams {
|
|||
/// @name Tile Attributes
|
||||
/// @note The tile grid/layer data can be left at zero if the destination is a single tile mesh.
|
||||
/// @{
|
||||
public int userId;
|
||||
|
||||
public int userId; /// < The user defined id of the tile.
|
||||
public int tileX; /// < The tile's x-grid location within the multi-tile destination mesh. (Along the x-axis.)
|
||||
public int tileZ; /// < The tile's y-grid location within the multi-tile desitation mesh. (Along the z-axis.)
|
||||
public int tileLayer; /// < The tile's layer within the layered destination mesh. [Limit: >= 0] (Along the y-axis.)
|
||||
public float[] bmin; /// < The minimum bounds of the tile. [(x, y, z)] [Unit: wu]
|
||||
public float[] bmax; /// < The maximum bounds of the tile. [(x, y, z)] [Unit: wu]
|
||||
/// < The user defined id of the tile.
|
||||
public int tileX;
|
||||
|
||||
/// < The tile's x-grid location within the multi-tile destination mesh. (Along the x-axis.)
|
||||
public int tileZ;
|
||||
|
||||
/// < The tile's y-grid location within the multi-tile desitation mesh. (Along the z-axis.)
|
||||
public int tileLayer;
|
||||
|
||||
/// < The tile's layer within the layered destination mesh. [Limit: >= 0] (Along the y-axis.)
|
||||
public float[] bmin;
|
||||
|
||||
/// < The minimum bounds of the tile. [(x, y, z)] [Unit: wu]
|
||||
public float[] bmax;
|
||||
|
||||
/// < The maximum bounds of the tile. [(x, y, z)] [Unit: wu]
|
||||
/// @}
|
||||
/// @name General Configuration Attributes
|
||||
/// @{
|
||||
public float walkableHeight;
|
||||
|
||||
public float walkableHeight; /// < The agent height. [Unit: wu]
|
||||
public float walkableRadius; /// < The agent radius. [Unit: wu]
|
||||
public float walkableClimb; /// < The agent maximum traversable ledge. (Up/Down) [Unit: wu]
|
||||
public float cs; /// < The xz-plane cell size of the polygon mesh. [Limit: > 0] [Unit: wu]
|
||||
public float ch; /// < The y-axis cell height of the polygon mesh. [Limit: > 0] [Unit: wu]
|
||||
/// < The agent height. [Unit: wu]
|
||||
public float walkableRadius;
|
||||
|
||||
/// < The agent radius. [Unit: wu]
|
||||
public float walkableClimb;
|
||||
|
||||
/// < The agent maximum traversable ledge. (Up/Down) [Unit: wu]
|
||||
public float cs;
|
||||
|
||||
/// < The xz-plane cell size of the polygon mesh. [Limit: > 0] [Unit: wu]
|
||||
public float ch;
|
||||
|
||||
/// < The y-axis cell height of the polygon mesh. [Limit: > 0] [Unit: wu]
|
||||
/// True if a bounding volume tree should be built for the tile.
|
||||
/// @note The BVTree is not normally needed for layered navigation meshes.
|
||||
public bool buildBvTree;
|
||||
|
||||
/// @}
|
||||
|
||||
}
|
||||
}
|
|
@ -17,27 +17,30 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Configuration parameters used to define multi-tile navigation meshes. The values are used to allocate space during
|
||||
* the initialization of a navigation mesh.
|
||||
*
|
||||
* @see NavMesh
|
||||
*/
|
||||
public class NavMeshParams {
|
||||
public class NavMeshParams
|
||||
{
|
||||
/** The world space origin of the navigation mesh's tile space. [(x, y, z)] */
|
||||
public readonly float[] orig = new float[3];
|
||||
|
||||
/** The width of each tile. (Along the x-axis.) */
|
||||
public float tileWidth;
|
||||
|
||||
/** The height of each tile. (Along the z-axis.) */
|
||||
public float tileHeight;
|
||||
|
||||
/** The maximum number of tiles the navigation mesh can contain. */
|
||||
public int maxTiles;
|
||||
|
||||
/** The maximum number of polygons each tile can contain. */
|
||||
public int maxPolys;
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -22,13 +22,11 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
|
||||
public class Node {
|
||||
|
||||
public class Node
|
||||
{
|
||||
public const int DT_NODE_OPEN = 0x01;
|
||||
public const int DT_NODE_CLOSED = 0x02;
|
||||
|
||||
/** parent of the node is not adjacent. Found using raycast. */
|
||||
public const int DT_NODE_PARENT_DETACHED = 0x04;
|
||||
|
||||
|
@ -36,31 +34,38 @@ public class Node {
|
|||
|
||||
/** Position of the node. */
|
||||
public float[] pos = new float[3];
|
||||
|
||||
/** Cost of reaching the given node. */
|
||||
public float cost;
|
||||
|
||||
/** Total cost of reaching the goal via the given node including heuristics. */
|
||||
public float total;
|
||||
|
||||
/** Index to parent node. */
|
||||
public int pidx;
|
||||
|
||||
/**
|
||||
* extra state information. A polyRef can have multiple nodes with different extra info. see DT_MAX_STATES_PER_NODE
|
||||
*/
|
||||
public int state;
|
||||
|
||||
/** Node flags. A combination of dtNodeFlags. */
|
||||
public int flags;
|
||||
|
||||
/** Polygon ref the node corresponds to. */
|
||||
public long id;
|
||||
|
||||
/** Shortcut found by raycast. */
|
||||
public List<long> shortcut;
|
||||
|
||||
public Node(int index) {
|
||||
public Node(int index)
|
||||
{
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
public override string ToString()
|
||||
{
|
||||
return "Node [id=" + id + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,82 +22,99 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
|
||||
public class NodePool
|
||||
{
|
||||
|
||||
private readonly Dictionary<long, List<Node>> m_map = new Dictionary<long, List<Node>>();
|
||||
private readonly List<Node> m_nodes = new List<Node>();
|
||||
|
||||
public NodePool() {
|
||||
|
||||
public NodePool()
|
||||
{
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
public void clear()
|
||||
{
|
||||
m_nodes.Clear();
|
||||
m_map.Clear();
|
||||
}
|
||||
|
||||
public List<Node> findNodes(long id) {
|
||||
var hasNode = m_map.TryGetValue(id, out var nodes);;
|
||||
if (nodes == null) {
|
||||
public List<Node> findNodes(long id)
|
||||
{
|
||||
var hasNode = m_map.TryGetValue(id, out var nodes);
|
||||
;
|
||||
if (nodes == null)
|
||||
{
|
||||
nodes = new List<Node>();
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public Node findNode(long id) {
|
||||
var hasNode = m_map.TryGetValue(id, out var nodes);;
|
||||
if (nodes != null && 0 != nodes.Count) {
|
||||
public Node findNode(long id)
|
||||
{
|
||||
var hasNode = m_map.TryGetValue(id, out var nodes);
|
||||
;
|
||||
if (nodes != null && 0 != nodes.Count)
|
||||
{
|
||||
return nodes[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Node getNode(long id, int state) {
|
||||
var hasNode = m_map.TryGetValue(id, out var nodes);;
|
||||
if (nodes != null) {
|
||||
foreach (Node node in nodes) {
|
||||
if (node.state == state) {
|
||||
public Node getNode(long id, int state)
|
||||
{
|
||||
var hasNode = m_map.TryGetValue(id, out var nodes);
|
||||
;
|
||||
if (nodes != null)
|
||||
{
|
||||
foreach (Node node in nodes)
|
||||
{
|
||||
if (node.state == state)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return create(id, state);
|
||||
}
|
||||
|
||||
protected Node create(long id, int state) {
|
||||
protected Node create(long id, int state)
|
||||
{
|
||||
Node node = new Node(m_nodes.Count + 1);
|
||||
node.id = id;
|
||||
node.state = state;
|
||||
m_nodes.Add(node);
|
||||
var hasNode = m_map.TryGetValue(id, out var nodes);;
|
||||
if (nodes == null) {
|
||||
var hasNode = m_map.TryGetValue(id, out var nodes);
|
||||
;
|
||||
if (nodes == null)
|
||||
{
|
||||
nodes = new List<Node>();
|
||||
m_map.Add(id, nodes);
|
||||
}
|
||||
|
||||
nodes.Add(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
public int getNodeIdx(Node node) {
|
||||
public int getNodeIdx(Node node)
|
||||
{
|
||||
return node != null ? node.index : 0;
|
||||
}
|
||||
|
||||
public Node getNodeAtIdx(int idx) {
|
||||
public Node getNodeAtIdx(int idx)
|
||||
{
|
||||
return idx != 0 ? m_nodes[idx - 1] : null;
|
||||
}
|
||||
|
||||
public Node getNode(long refs) {
|
||||
public Node getNode(long refs)
|
||||
{
|
||||
return getNode(refs, 0);
|
||||
}
|
||||
|
||||
public Dictionary<long, List<Node>> getNodeMap() {
|
||||
public Dictionary<long, List<Node>> getNodeMap()
|
||||
{
|
||||
return m_map;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,14 +17,13 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class NodeQueue {
|
||||
|
||||
public class NodeQueue
|
||||
{
|
||||
private readonly List<Node> m_heap = new List<Node>();
|
||||
|
||||
public int count()
|
||||
|
@ -32,7 +31,8 @@ public class NodeQueue {
|
|||
return m_heap.Count;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
public void clear()
|
||||
{
|
||||
m_heap.Clear();
|
||||
}
|
||||
|
||||
|
@ -48,12 +48,14 @@ public class NodeQueue {
|
|||
return node;
|
||||
}
|
||||
|
||||
public void push(Node node) {
|
||||
public void push(Node node)
|
||||
{
|
||||
m_heap.Add(node);
|
||||
m_heap.Sort((x, y) => x.total.CompareTo(y.total));
|
||||
}
|
||||
|
||||
public void modify(Node node) {
|
||||
public void modify(Node node)
|
||||
{
|
||||
m_heap.Remove(node);
|
||||
push(node);
|
||||
}
|
||||
|
@ -63,5 +65,4 @@ public class NodeQueue {
|
|||
return 0 == m_heap.Count;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -17,21 +17,24 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Defines an navigation mesh off-mesh connection within a dtMeshTile object. An off-mesh connection is a user defined
|
||||
* traversable connection made up to two vertices.
|
||||
*/
|
||||
public class OffMeshConnection {
|
||||
public class OffMeshConnection
|
||||
{
|
||||
/** The endpoints of the connection. [(ax, ay, az, bx, by, bz)] */
|
||||
public float[] pos = new float[6];
|
||||
|
||||
/** The radius of the endpoints. [Limit: >= 0] */
|
||||
public float rad;
|
||||
|
||||
/** The polygon reference of the connection within the tile. */
|
||||
public int poly;
|
||||
|
||||
/**
|
||||
* Link flags.
|
||||
*
|
||||
|
@ -39,8 +42,10 @@ public class OffMeshConnection {
|
|||
* These are link flags used for internal purposes.
|
||||
*/
|
||||
public int flags;
|
||||
|
||||
/** End point side. */
|
||||
public int side;
|
||||
|
||||
/** The id of the offmesh connection. (User assigned when the navigation mesh is built.) */
|
||||
public int userId;
|
||||
}
|
||||
|
|
|
@ -17,26 +17,32 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
/** Defines a polygon within a MeshTile object. */
|
||||
public class Poly {
|
||||
|
||||
public class Poly
|
||||
{
|
||||
public readonly int index;
|
||||
|
||||
/** The polygon is a standard convex polygon that is part of the surface of the mesh. */
|
||||
public const int DT_POLYTYPE_GROUND = 0;
|
||||
|
||||
/** The polygon is an off-mesh connection consisting of two vertices. */
|
||||
public const int DT_POLYTYPE_OFFMESH_CONNECTION = 1;
|
||||
|
||||
/** The indices of the polygon's vertices. The actual vertices are located in MeshTile::verts. */
|
||||
public readonly int[] verts;
|
||||
|
||||
/** Packed data representing neighbor polygons references and flags for each edge. */
|
||||
public readonly int[] neis;
|
||||
|
||||
/** The user defined polygon flags. */
|
||||
public int flags;
|
||||
|
||||
/** The number of vertices in the polygon. */
|
||||
public int vertCount;
|
||||
|
||||
/**
|
||||
* The bit packed area id and polygon type.
|
||||
*
|
||||
|
@ -44,32 +50,35 @@ public class Poly {
|
|||
*/
|
||||
public int areaAndtype;
|
||||
|
||||
public Poly(int index, int maxVertsPerPoly) {
|
||||
public Poly(int index, int maxVertsPerPoly)
|
||||
{
|
||||
this.index = index;
|
||||
verts = new int[maxVertsPerPoly];
|
||||
neis = new int[maxVertsPerPoly];
|
||||
}
|
||||
|
||||
/** Sets the user defined area id. [Limit: < {@link org.recast4j.detour.NavMesh#DT_MAX_AREAS}] */
|
||||
public void setArea(int a) {
|
||||
public void setArea(int a)
|
||||
{
|
||||
areaAndtype = (areaAndtype & 0xc0) | (a & 0x3f);
|
||||
}
|
||||
|
||||
/** Sets the polygon type. (See: #dtPolyTypes.) */
|
||||
public void setType(int t) {
|
||||
public void setType(int t)
|
||||
{
|
||||
areaAndtype = (areaAndtype & 0x3f) | (t << 6);
|
||||
}
|
||||
|
||||
/** Gets the user defined area id. */
|
||||
public int getArea() {
|
||||
public int getArea()
|
||||
{
|
||||
return areaAndtype & 0x3f;
|
||||
}
|
||||
|
||||
/** Gets the polygon type. (See: #dtPolyTypes) */
|
||||
public int getType() {
|
||||
public int getType()
|
||||
{
|
||||
return areaAndtype >> 6;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,20 +17,22 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
/** Defines the location of detail sub-mesh data within a dtMeshTile. */
|
||||
public class PolyDetail {
|
||||
public class PolyDetail
|
||||
{
|
||||
/** The offset of the vertices in the MeshTile::detailVerts array. */
|
||||
public int vertBase;
|
||||
|
||||
/** The offset of the triangles in the MeshTile::detailTris array. */
|
||||
public int triBase;
|
||||
|
||||
/** The number of vertices in the sub-mesh. */
|
||||
public int vertCount;
|
||||
|
||||
/** The number of triangles in the sub-mesh. */
|
||||
public int triCount;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,10 +1,7 @@
|
|||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
public interface PolyQuery {
|
||||
|
||||
public interface PolyQuery
|
||||
{
|
||||
void process(MeshTile tile, Poly poly, long refs);
|
||||
}
|
||||
|
||||
}
|
|
@ -20,79 +20,92 @@ using System;
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
using static DetourCommon;
|
||||
|
||||
public interface PolygonByCircleConstraint {
|
||||
|
||||
public interface PolygonByCircleConstraint
|
||||
{
|
||||
float[] aply(float[] polyVerts, float[] circleCenter, float radius);
|
||||
|
||||
public static PolygonByCircleConstraint noop() {
|
||||
public static PolygonByCircleConstraint noop()
|
||||
{
|
||||
return new NoOpPolygonByCircleConstraint();
|
||||
}
|
||||
|
||||
public static PolygonByCircleConstraint strict() {
|
||||
public static PolygonByCircleConstraint strict()
|
||||
{
|
||||
return new StrictPolygonByCircleConstraint();
|
||||
}
|
||||
|
||||
public class NoOpPolygonByCircleConstraint : PolygonByCircleConstraint {
|
||||
|
||||
public float[] aply(float[] polyVerts, float[] circleCenter, float radius) {
|
||||
public class NoOpPolygonByCircleConstraint : PolygonByCircleConstraint
|
||||
{
|
||||
public float[] aply(float[] polyVerts, float[] circleCenter, float radius)
|
||||
{
|
||||
return polyVerts;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the intersection between a polygon and a circle. A dodecagon is used as an approximation of the circle.
|
||||
*/
|
||||
public class StrictPolygonByCircleConstraint : PolygonByCircleConstraint {
|
||||
|
||||
public class StrictPolygonByCircleConstraint : PolygonByCircleConstraint
|
||||
{
|
||||
private const int CIRCLE_SEGMENTS = 12;
|
||||
private static float[] unitCircle;
|
||||
|
||||
public float[] aply(float[] verts, float[] center, float radius) {
|
||||
public float[] aply(float[] verts, float[] center, float radius)
|
||||
{
|
||||
float radiusSqr = radius * radius;
|
||||
int outsideVertex = -1;
|
||||
for (int pv = 0; pv < verts.Length; pv += 3) {
|
||||
if (vDist2DSqr(center, verts, pv) > radiusSqr) {
|
||||
for (int pv = 0; pv < verts.Length; pv += 3)
|
||||
{
|
||||
if (vDist2DSqr(center, verts, pv) > radiusSqr)
|
||||
{
|
||||
outsideVertex = pv;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (outsideVertex == -1) {
|
||||
|
||||
if (outsideVertex == -1)
|
||||
{
|
||||
// polygon inside circle
|
||||
return verts;
|
||||
}
|
||||
|
||||
float[] qCircle = circle(center, radius);
|
||||
float[] intersection = ConvexConvexIntersection.intersect(verts, qCircle);
|
||||
if (intersection == null && pointInPolygon(center, verts, verts.Length / 3)) {
|
||||
if (intersection == null && pointInPolygon(center, verts, verts.Length / 3))
|
||||
{
|
||||
// circle inside polygon
|
||||
return qCircle;
|
||||
}
|
||||
|
||||
return intersection;
|
||||
}
|
||||
|
||||
private float[] circle(float[] center, float radius) {
|
||||
if (unitCircle == null) {
|
||||
private float[] circle(float[] center, float radius)
|
||||
{
|
||||
if (unitCircle == null)
|
||||
{
|
||||
unitCircle = new float[CIRCLE_SEGMENTS * 3];
|
||||
for (int i = 0; i < CIRCLE_SEGMENTS; i++) {
|
||||
for (int i = 0; i < CIRCLE_SEGMENTS; i++)
|
||||
{
|
||||
double a = i * Math.PI * 2 / CIRCLE_SEGMENTS;
|
||||
unitCircle[3 * i] = (float)Math.Cos(a);
|
||||
unitCircle[3 * i + 1] = 0;
|
||||
unitCircle[3 * i + 2] = (float)-Math.Sin(a);
|
||||
}
|
||||
}
|
||||
|
||||
float[] circle = new float[12 * 3];
|
||||
for (int i = 0; i < CIRCLE_SEGMENTS * 3; i += 3) {
|
||||
for (int i = 0; i < CIRCLE_SEGMENTS * 3; i += 3)
|
||||
{
|
||||
circle[i] = unitCircle[i] * radius + center[0];
|
||||
circle[i + 1] = center[1];
|
||||
circle[i + 2] = unitCircle[i + 2] * radius + center[2];
|
||||
}
|
||||
|
||||
return circle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -17,11 +17,11 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
public class QueryData {
|
||||
public class QueryData
|
||||
{
|
||||
public Status status;
|
||||
public Node lastBestNode;
|
||||
public float lastBestNodeCost;
|
||||
|
|
|
@ -17,17 +17,14 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
public interface QueryFilter {
|
||||
|
||||
public interface QueryFilter
|
||||
{
|
||||
bool passFilter(long refs, MeshTile tile, Poly poly);
|
||||
|
||||
float getCost(float[] pa, float[] pb, long prevRef, MeshTile prevTile, Poly prevPoly, long curRef, MeshTile curTile,
|
||||
Poly curPoly, long nextRef, MeshTile nextTile, Poly nextPoly);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -18,12 +18,8 @@ freely, subject to the following restrictions:
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
public interface QueryHeuristic {
|
||||
|
||||
public interface QueryHeuristic
|
||||
{
|
||||
float getCost(float[] neighbourPos, float[] endPos);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -22,23 +22,24 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Provides information about raycast hit. Filled by NavMeshQuery::raycast
|
||||
*/
|
||||
public class RaycastHit {
|
||||
public class RaycastHit
|
||||
{
|
||||
/** The hit parameter. (float.MaxValue if no wall hit.) */
|
||||
public float t;
|
||||
|
||||
/** hitNormal The normal of the nearest wall hit. [(x, y, z)] */
|
||||
public readonly float[] hitNormal = new float[3];
|
||||
|
||||
/** Visited polygons. */
|
||||
public readonly List<long> path = new List<long>();
|
||||
|
||||
/** The cost of the path until hit. */
|
||||
public float pathCost;
|
||||
|
||||
/** The index of the edge on the readonly polygon where the wall was hit. */
|
||||
public int hitEdgeIndex;
|
||||
}
|
||||
|
||||
}
|
|
@ -17,71 +17,79 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
public static class Results
|
||||
{
|
||||
public static Result<T> success<T>(T result) {
|
||||
public static Result<T> success<T>(T result)
|
||||
{
|
||||
return new Result<T>(result, Status.SUCCSESS, null);
|
||||
}
|
||||
|
||||
public static Result<T> failure<T>() {
|
||||
public static Result<T> failure<T>()
|
||||
{
|
||||
return new Result<T>(default, Status.FAILURE, null);
|
||||
}
|
||||
|
||||
public static Result<T> invalidParam<T>() {
|
||||
public static Result<T> invalidParam<T>()
|
||||
{
|
||||
return new Result<T>(default, Status.FAILURE_INVALID_PARAM, null);
|
||||
}
|
||||
|
||||
public static Result<T> failure<T>(string message) {
|
||||
public static Result<T> failure<T>(string message)
|
||||
{
|
||||
return new Result<T>(default, Status.FAILURE, message);
|
||||
}
|
||||
|
||||
public static Result<T> invalidParam<T>(string message) {
|
||||
public static Result<T> invalidParam<T>(string message)
|
||||
{
|
||||
return new Result<T>(default, Status.FAILURE_INVALID_PARAM, message);
|
||||
}
|
||||
|
||||
public static Result<T> failure<T>(T result) {
|
||||
public static Result<T> failure<T>(T result)
|
||||
{
|
||||
return new Result<T>(result, Status.FAILURE, null);
|
||||
}
|
||||
|
||||
public static Result<T> partial<T>(T result) {
|
||||
public static Result<T> partial<T>(T result)
|
||||
{
|
||||
return new Result<T>(default, Status.PARTIAL_RESULT, null);
|
||||
}
|
||||
|
||||
public static Result<T> of<T>(Status status, string message) {
|
||||
public static Result<T> of<T>(Status status, string message)
|
||||
{
|
||||
return new Result<T>(default, status, message);
|
||||
}
|
||||
|
||||
public static Result<T> of<T>(Status status, T result) {
|
||||
public static Result<T> of<T>(Status status, T result)
|
||||
{
|
||||
return new Result<T>(result, status, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class Result<T> {
|
||||
|
||||
public class Result<T>
|
||||
{
|
||||
public readonly T result;
|
||||
public readonly Status status;
|
||||
public readonly string message;
|
||||
|
||||
internal Result(T result, Status status, string message) {
|
||||
internal Result(T result, Status status, string message)
|
||||
{
|
||||
this.result = result;
|
||||
this.status = status;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
|
||||
public bool failed() {
|
||||
public bool failed()
|
||||
{
|
||||
return status.isFailed();
|
||||
}
|
||||
|
||||
public bool succeeded() {
|
||||
public bool succeeded()
|
||||
{
|
||||
return status.isSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,11 +17,11 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
public class Status {
|
||||
public class Status
|
||||
{
|
||||
public static Status FAILURE = new Status(0);
|
||||
public static Status SUCCSESS = new Status(1);
|
||||
public static Status IN_PROGRESS = new Status(2);
|
||||
|
@ -38,22 +38,24 @@ public class Status {
|
|||
|
||||
public static class StatusEx
|
||||
{
|
||||
public static bool isFailed(this Status @this) {
|
||||
public static bool isFailed(this Status @this)
|
||||
{
|
||||
return @this == Status.FAILURE || @this == Status.FAILURE_INVALID_PARAM;
|
||||
}
|
||||
|
||||
public static bool isInProgress(this Status @this) {
|
||||
public static bool isInProgress(this Status @this)
|
||||
{
|
||||
return @this == Status.IN_PROGRESS;
|
||||
}
|
||||
|
||||
public static bool isSuccess(this Status @this) {
|
||||
public static bool isSuccess(this Status @this)
|
||||
{
|
||||
return @this == Status.SUCCSESS || @this == Status.PARTIAL_RESULT;
|
||||
}
|
||||
|
||||
public static bool isPartial(this Status @this) {
|
||||
public static bool isPartial(this Status @this)
|
||||
{
|
||||
return @this == Status.PARTIAL_RESULT;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,35 +17,38 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
using static DetourCommon;
|
||||
|
||||
//TODO: (PP) Add comments
|
||||
public class StraightPathItem {
|
||||
public class StraightPathItem
|
||||
{
|
||||
public float[] pos;
|
||||
public int flags;
|
||||
public long refs;
|
||||
|
||||
public StraightPathItem(float[] pos, int flags, long refs) {
|
||||
public StraightPathItem(float[] pos, int flags, long refs)
|
||||
{
|
||||
this.pos = vCopy(pos);
|
||||
this.flags = flags;
|
||||
this.refs = refs;
|
||||
}
|
||||
|
||||
public float[] getPos() {
|
||||
public float[] getPos()
|
||||
{
|
||||
return pos;
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
public int getFlags()
|
||||
{
|
||||
return flags;
|
||||
}
|
||||
|
||||
public long getRef() {
|
||||
public long getRef()
|
||||
{
|
||||
return refs;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -20,8 +20,6 @@ freely, subject to the following restrictions:
|
|||
|
||||
namespace DotRecast.Detour
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper for 3-element pieces (3D vectors) of a bigger float array.
|
||||
*
|
||||
|
|
|
@ -3,17 +3,20 @@ using DotRecast.Recast.Demo.Geom;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Builder;
|
||||
|
||||
public abstract class AbstractNavMeshBuilder {
|
||||
|
||||
public abstract class AbstractNavMeshBuilder
|
||||
{
|
||||
protected NavMeshDataCreateParams getNavMeshCreateParams(DemoInputGeomProvider m_geom, float m_cellSize,
|
||||
float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb,
|
||||
RecastBuilderResult rcResult) {
|
||||
RecastBuilderResult rcResult)
|
||||
{
|
||||
PolyMesh m_pmesh = rcResult.getMesh();
|
||||
PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
|
||||
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
|
||||
for (int i = 0; i < m_pmesh.npolys; ++i) {
|
||||
for (int i = 0; i < m_pmesh.npolys; ++i)
|
||||
{
|
||||
m_pmesh.flags[i] = 1;
|
||||
}
|
||||
|
||||
option.verts = m_pmesh.verts;
|
||||
option.vertCount = m_pmesh.nverts;
|
||||
option.polys = m_pmesh.polys;
|
||||
|
@ -21,13 +24,15 @@ public abstract class AbstractNavMeshBuilder {
|
|||
option.polyFlags = m_pmesh.flags;
|
||||
option.polyCount = m_pmesh.npolys;
|
||||
option.nvp = m_pmesh.nvp;
|
||||
if (m_dmesh != null) {
|
||||
if (m_dmesh != null)
|
||||
{
|
||||
option.detailMeshes = m_dmesh.meshes;
|
||||
option.detailVerts = m_dmesh.verts;
|
||||
option.detailVertsCount = m_dmesh.nverts;
|
||||
option.detailTris = m_dmesh.tris;
|
||||
option.detailTriCount = m_dmesh.ntris;
|
||||
}
|
||||
|
||||
option.walkableHeight = m_agentHeight;
|
||||
option.walkableRadius = m_agentRadius;
|
||||
option.walkableClimb = m_agentMaxClimb;
|
||||
|
@ -44,35 +49,49 @@ public abstract class AbstractNavMeshBuilder {
|
|||
option.offMeshConAreas = new int[option.offMeshConCount];
|
||||
option.offMeshConFlags = new int[option.offMeshConCount];
|
||||
option.offMeshConUserID = new int[option.offMeshConCount];
|
||||
for (int i = 0; i < option.offMeshConCount; i++) {
|
||||
for (int i = 0; i < option.offMeshConCount; i++)
|
||||
{
|
||||
DemoOffMeshConnection offMeshCon = m_geom.getOffMeshConnections()[i];
|
||||
for (int j = 0; j < 6; j++) {
|
||||
for (int j = 0; j < 6; j++)
|
||||
{
|
||||
option.offMeshConVerts[6 * i + j] = offMeshCon.verts[j];
|
||||
}
|
||||
|
||||
option.offMeshConRad[i] = offMeshCon.radius;
|
||||
option.offMeshConDir[i] = offMeshCon.bidir ? 1 : 0;
|
||||
option.offMeshConAreas[i] = offMeshCon.area;
|
||||
option.offMeshConFlags[i] = offMeshCon.flags;
|
||||
}
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
protected MeshData updateAreaAndFlags(MeshData meshData) {
|
||||
protected MeshData updateAreaAndFlags(MeshData meshData)
|
||||
{
|
||||
// Update poly flags from areas.
|
||||
for (int i = 0; i < meshData.polys.Length; ++i) {
|
||||
if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WALKABLE) {
|
||||
for (int i = 0; i < meshData.polys.Length; ++i)
|
||||
{
|
||||
if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WALKABLE)
|
||||
{
|
||||
meshData.polys[i].setArea(SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND);
|
||||
}
|
||||
|
||||
if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND
|
||||
|| meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GRASS
|
||||
|| meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD) {
|
||||
|| meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD)
|
||||
{
|
||||
meshData.polys[i].flags = SampleAreaModifications.SAMPLE_POLYFLAGS_WALK;
|
||||
} else if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER) {
|
||||
}
|
||||
else if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER)
|
||||
{
|
||||
meshData.polys[i].flags = SampleAreaModifications.SAMPLE_POLYFLAGS_SWIM;
|
||||
} else if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_DOOR) {
|
||||
}
|
||||
else if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_DOOR)
|
||||
{
|
||||
meshData.polys[i].flags = SampleAreaModifications.SAMPLE_POLYFLAGS_DOOR;
|
||||
}
|
||||
}
|
||||
|
||||
return meshData;
|
||||
}
|
||||
}
|
|
@ -17,10 +17,11 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Recast.Demo.Builder;
|
||||
|
||||
public class SampleAreaModifications {
|
||||
|
||||
public class SampleAreaModifications
|
||||
{
|
||||
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x0;
|
||||
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x1;
|
||||
public const int SAMPLE_POLYAREA_TYPE_ROAD = 0x2;
|
||||
|
|
|
@ -24,14 +24,14 @@ using DotRecast.Recast.Demo.Geom;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Builder;
|
||||
|
||||
public class SoloNavMeshBuilder : AbstractNavMeshBuilder {
|
||||
|
||||
public class SoloNavMeshBuilder : AbstractNavMeshBuilder
|
||||
{
|
||||
public Tuple<IList<RecastBuilderResult>, NavMesh> build(DemoInputGeomProvider m_geom, PartitionType m_partitionType,
|
||||
float m_cellSize, float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb,
|
||||
float m_agentMaxSlope, int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError,
|
||||
int m_vertsPerPoly, float m_detailSampleDist, float m_detailSampleMaxError, bool filterLowHangingObstacles,
|
||||
bool filterLedgeSpans, bool filterWalkableLowHeightSpans) {
|
||||
|
||||
bool filterLedgeSpans, bool filterWalkableLowHeightSpans)
|
||||
{
|
||||
RecastBuilderResult rcResult = buildRecastResult(m_geom, m_partitionType, m_cellSize, m_cellHeight, m_agentHeight,
|
||||
m_agentRadius, m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
|
||||
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, filterLowHangingObstacles, filterLedgeSpans,
|
||||
|
@ -42,7 +42,8 @@ public class SoloNavMeshBuilder : AbstractNavMeshBuilder {
|
|||
m_vertsPerPoly));
|
||||
}
|
||||
|
||||
private NavMesh buildNavMesh(MeshData meshData, int m_vertsPerPoly) {
|
||||
private NavMesh buildNavMesh(MeshData meshData, int m_vertsPerPoly)
|
||||
{
|
||||
return new NavMesh(meshData, m_vertsPerPoly, 0);
|
||||
}
|
||||
|
||||
|
@ -50,7 +51,8 @@ public class SoloNavMeshBuilder : AbstractNavMeshBuilder {
|
|||
float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope,
|
||||
int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly,
|
||||
float m_detailSampleDist, float m_detailSampleMaxError, bool filterLowHangingObstacles, bool filterLedgeSpans,
|
||||
bool filterWalkableLowHeightSpans) {
|
||||
bool filterWalkableLowHeightSpans)
|
||||
{
|
||||
RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize, m_cellHeight, m_agentMaxSlope, filterLowHangingObstacles,
|
||||
filterLedgeSpans, filterWalkableLowHeightSpans, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_regionMinSize,
|
||||
m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError,
|
||||
|
@ -61,10 +63,10 @@ public class SoloNavMeshBuilder : AbstractNavMeshBuilder {
|
|||
}
|
||||
|
||||
private MeshData buildMeshData(DemoInputGeomProvider m_geom, float m_cellSize, float m_cellHeight, float m_agentHeight,
|
||||
float m_agentRadius, float m_agentMaxClimb, RecastBuilderResult rcResult) {
|
||||
float m_agentRadius, float m_agentMaxClimb, RecastBuilderResult rcResult)
|
||||
{
|
||||
NavMeshDataCreateParams option = getNavMeshCreateParams(m_geom, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius,
|
||||
m_agentMaxClimb, rcResult);
|
||||
return updateAreaAndFlags(NavMeshBuilder.createNavMeshData(option));
|
||||
}
|
||||
|
||||
}
|
|
@ -25,17 +25,18 @@ using DotRecast.Recast.Demo.Geom;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Builder;
|
||||
|
||||
public class TileNavMeshBuilder : AbstractNavMeshBuilder {
|
||||
|
||||
public TileNavMeshBuilder() {
|
||||
public class TileNavMeshBuilder : AbstractNavMeshBuilder
|
||||
{
|
||||
public TileNavMeshBuilder()
|
||||
{
|
||||
}
|
||||
|
||||
public Tuple<IList<RecastBuilderResult>, NavMesh> build(DemoInputGeomProvider m_geom, PartitionType m_partitionType,
|
||||
float m_cellSize, float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb,
|
||||
float m_agentMaxSlope, int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError,
|
||||
int m_vertsPerPoly, float m_detailSampleDist, float m_detailSampleMaxError, bool filterLowHangingObstacles,
|
||||
bool filterLedgeSpans, bool filterWalkableLowHeightSpans, int tileSize) {
|
||||
|
||||
bool filterLedgeSpans, bool filterWalkableLowHeightSpans, int tileSize)
|
||||
{
|
||||
List<RecastBuilderResult> rcResult = buildRecastResult(m_geom, m_partitionType, m_cellSize, m_cellHeight, m_agentHeight,
|
||||
m_agentRadius, m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
|
||||
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, filterLowHangingObstacles, filterLedgeSpans,
|
||||
|
@ -50,7 +51,8 @@ public class TileNavMeshBuilder : AbstractNavMeshBuilder {
|
|||
float m_cellSize, float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb,
|
||||
float m_agentMaxSlope, int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError,
|
||||
int m_vertsPerPoly, float m_detailSampleDist, float m_detailSampleMaxError, bool filterLowHangingObstacles,
|
||||
bool filterLedgeSpans, bool filterWalkableLowHeightSpans, int tileSize) {
|
||||
bool filterLedgeSpans, bool filterWalkableLowHeightSpans, int tileSize)
|
||||
{
|
||||
RecastConfig cfg = new RecastConfig(true, tileSize, tileSize, RecastConfig.calcBorder(m_agentRadius, m_cellSize),
|
||||
m_partitionType, m_cellSize, m_cellHeight, m_agentMaxSlope, filterLowHangingObstacles, filterLedgeSpans,
|
||||
filterWalkableLowHeightSpans, m_agentHeight, m_agentRadius, m_agentMaxClimb,
|
||||
|
@ -62,7 +64,8 @@ public class TileNavMeshBuilder : AbstractNavMeshBuilder {
|
|||
}
|
||||
|
||||
private NavMesh buildNavMesh(DemoInputGeomProvider geom, List<MeshData> meshData, float cellSize, int tileSize,
|
||||
int vertsPerPoly) {
|
||||
int vertsPerPoly)
|
||||
{
|
||||
NavMeshParams navMeshParams = new NavMeshParams();
|
||||
navMeshParams.orig[0] = geom.getMeshBoundsMin()[0];
|
||||
navMeshParams.orig[1] = geom.getMeshBoundsMin()[1];
|
||||
|
@ -79,17 +82,20 @@ public class TileNavMeshBuilder : AbstractNavMeshBuilder {
|
|||
return navMesh;
|
||||
}
|
||||
|
||||
public int getMaxTiles(DemoInputGeomProvider geom, float cellSize, int tileSize) {
|
||||
public int getMaxTiles(DemoInputGeomProvider geom, float cellSize, int tileSize)
|
||||
{
|
||||
int tileBits = getTileBits(geom, cellSize, tileSize);
|
||||
return 1 << tileBits;
|
||||
}
|
||||
|
||||
public int getMaxPolysPerTile(DemoInputGeomProvider geom, float cellSize, int tileSize) {
|
||||
public int getMaxPolysPerTile(DemoInputGeomProvider geom, float cellSize, int tileSize)
|
||||
{
|
||||
int polyBits = 22 - getTileBits(geom, cellSize, tileSize);
|
||||
return 1 << polyBits;
|
||||
}
|
||||
|
||||
private int getTileBits(DemoInputGeomProvider geom, float cellSize, int tileSize) {
|
||||
private int getTileBits(DemoInputGeomProvider geom, float cellSize, int tileSize)
|
||||
{
|
||||
int[] wh = Recast.calcGridSize(geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), cellSize);
|
||||
int tw = (wh[0] + tileSize - 1) / tileSize;
|
||||
int th = (wh[1] + tileSize - 1) / tileSize;
|
||||
|
@ -97,7 +103,8 @@ public class TileNavMeshBuilder : AbstractNavMeshBuilder {
|
|||
return tileBits;
|
||||
}
|
||||
|
||||
public int[] getTiles(DemoInputGeomProvider geom, float cellSize, int tileSize) {
|
||||
public int[] getTiles(DemoInputGeomProvider geom, float cellSize, int tileSize)
|
||||
{
|
||||
int[] wh = Recast.calcGridSize(geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), cellSize);
|
||||
int tw = (wh[0] + tileSize - 1) / tileSize;
|
||||
int th = (wh[1] + tileSize - 1) / tileSize;
|
||||
|
@ -105,11 +112,12 @@ public class TileNavMeshBuilder : AbstractNavMeshBuilder {
|
|||
}
|
||||
|
||||
private List<MeshData> buildMeshData(DemoInputGeomProvider m_geom, float m_cellSize, float m_cellHeight, float m_agentHeight,
|
||||
float m_agentRadius, float m_agentMaxClimb, List<RecastBuilderResult> rcResult) {
|
||||
|
||||
float m_agentRadius, float m_agentMaxClimb, List<RecastBuilderResult> rcResult)
|
||||
{
|
||||
// Add tiles to nav mesh
|
||||
List<MeshData> meshData = new();
|
||||
foreach (RecastBuilderResult result in rcResult) {
|
||||
foreach (RecastBuilderResult result in rcResult)
|
||||
{
|
||||
int x = result.tileX;
|
||||
int z = result.tileZ;
|
||||
NavMeshDataCreateParams option = getNavMeshCreateParams(m_geom, m_cellSize, m_cellHeight, m_agentHeight,
|
||||
|
@ -117,11 +125,12 @@ public class TileNavMeshBuilder : AbstractNavMeshBuilder {
|
|||
option.tileX = x;
|
||||
option.tileZ = z;
|
||||
MeshData md = NavMeshBuilder.createNavMeshData(option);
|
||||
if (md != null) {
|
||||
if (md != null)
|
||||
{
|
||||
meshData.Add(updateAreaAndFlags(md));
|
||||
}
|
||||
}
|
||||
|
||||
return meshData;
|
||||
}
|
||||
|
||||
}
|
|
@ -25,8 +25,8 @@ using Silk.NET.OpenGL;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Draw;
|
||||
|
||||
public class DebugDraw {
|
||||
|
||||
public class DebugDraw
|
||||
{
|
||||
private readonly GLCheckerTexture g_tex = new GLCheckerTexture();
|
||||
private readonly OpenGLDraw openGlDraw = new ModernOpenGLDraw();
|
||||
private readonly int[] boxIndices = { 7, 6, 5, 4, 0, 1, 2, 3, 1, 5, 6, 2, 3, 7, 4, 0, 2, 6, 7, 3, 0, 4, 5, 1, };
|
||||
|
@ -42,12 +42,14 @@ public class DebugDraw {
|
|||
};
|
||||
|
||||
|
||||
public void begin(DebugDrawPrimitives prim) {
|
||||
public void begin(DebugDrawPrimitives prim)
|
||||
{
|
||||
begin(prim, 1f);
|
||||
}
|
||||
|
||||
public void debugDrawCylinderWire(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col,
|
||||
float lineWidth) {
|
||||
float lineWidth)
|
||||
{
|
||||
begin(DebugDrawPrimitives.LINES, lineWidth);
|
||||
appendCylinderWire(minx, miny, minz, maxx, maxy, maxz, col);
|
||||
end();
|
||||
|
@ -57,10 +59,13 @@ public class DebugDraw {
|
|||
private readonly float[] cylinderDir = new float[CYLINDER_NUM_SEG * 2];
|
||||
private bool cylinderInit = false;
|
||||
|
||||
private void initCylinder() {
|
||||
if (!cylinderInit) {
|
||||
private void initCylinder()
|
||||
{
|
||||
if (!cylinderInit)
|
||||
{
|
||||
cylinderInit = true;
|
||||
for (int i = 0; i < CYLINDER_NUM_SEG; ++i) {
|
||||
for (int i = 0; i < CYLINDER_NUM_SEG; ++i)
|
||||
{
|
||||
float a = (float)(i * Math.PI * 2 / CYLINDER_NUM_SEG);
|
||||
cylinderDir[i * 2] = (float)Math.Cos(a);
|
||||
cylinderDir[i * 2 + 1] = (float)Math.Sin(a);
|
||||
|
@ -68,8 +73,8 @@ public class DebugDraw {
|
|||
}
|
||||
}
|
||||
|
||||
void appendCylinderWire(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col) {
|
||||
|
||||
void appendCylinderWire(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col)
|
||||
{
|
||||
initCylinder();
|
||||
|
||||
float cx = (maxx + minx) / 2;
|
||||
|
@ -77,27 +82,31 @@ public class DebugDraw {
|
|||
float rx = (maxx - minx) / 2;
|
||||
float rz = (maxz - minz) / 2;
|
||||
|
||||
for (int i = 0, j = CYLINDER_NUM_SEG - 1; i < CYLINDER_NUM_SEG; j = i++) {
|
||||
for (int i = 0, j = CYLINDER_NUM_SEG - 1; i < CYLINDER_NUM_SEG; j = i++)
|
||||
{
|
||||
vertex(cx + cylinderDir[j * 2 + 0] * rx, miny, cz + cylinderDir[j * 2 + 1] * rz, col);
|
||||
vertex(cx + cylinderDir[i * 2 + 0] * rx, miny, cz + cylinderDir[i * 2 + 1] * rz, col);
|
||||
vertex(cx + cylinderDir[j * 2 + 0] * rx, maxy, cz + cylinderDir[j * 2 + 1] * rz, col);
|
||||
vertex(cx + cylinderDir[i * 2 + 0] * rx, maxy, cz + cylinderDir[i * 2 + 1] * rz, col);
|
||||
}
|
||||
for (int i = 0; i < CYLINDER_NUM_SEG; i += CYLINDER_NUM_SEG / 4) {
|
||||
|
||||
for (int i = 0; i < CYLINDER_NUM_SEG; i += CYLINDER_NUM_SEG / 4)
|
||||
{
|
||||
vertex(cx + cylinderDir[i * 2 + 0] * rx, miny, cz + cylinderDir[i * 2 + 1] * rz, col);
|
||||
vertex(cx + cylinderDir[i * 2 + 0] * rx, maxy, cz + cylinderDir[i * 2 + 1] * rz, col);
|
||||
}
|
||||
}
|
||||
|
||||
public void debugDrawBoxWire(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col,
|
||||
float lineWidth) {
|
||||
|
||||
float lineWidth)
|
||||
{
|
||||
begin(DebugDrawPrimitives.LINES, lineWidth);
|
||||
appendBoxWire(minx, miny, minz, maxx, maxy, maxz, col);
|
||||
end();
|
||||
}
|
||||
|
||||
public void appendBoxWire(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col) {
|
||||
public void appendBoxWire(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col)
|
||||
{
|
||||
// Top
|
||||
vertex(minx, miny, minz, col);
|
||||
vertex(maxx, miny, minz, col);
|
||||
|
@ -129,8 +138,10 @@ public class DebugDraw {
|
|||
vertex(minx, maxy, maxz, col);
|
||||
}
|
||||
|
||||
public void appendBox(float minx, float miny, float minz, float maxx, float maxy, float maxz, int[] fcol) {
|
||||
float[][] verts = {
|
||||
public void appendBox(float minx, float miny, float minz, float maxx, float maxy, float maxz, int[] fcol)
|
||||
{
|
||||
float[][] verts =
|
||||
{
|
||||
new[] { minx, miny, minz },
|
||||
new[] { maxx, miny, minz },
|
||||
new[] { maxx, miny, maxz },
|
||||
|
@ -138,10 +149,12 @@ public class DebugDraw {
|
|||
new[] { minx, maxy, minz },
|
||||
new[] { maxx, maxy, minz },
|
||||
new[] { maxx, maxy, maxz },
|
||||
new[] { minx, maxy, maxz } };
|
||||
new[] { minx, maxy, maxz }
|
||||
};
|
||||
|
||||
int idx = 0;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
for (int i = 0; i < 6; ++i)
|
||||
{
|
||||
vertex(verts[boxIndices[idx]], fcol[i]);
|
||||
idx++;
|
||||
vertex(verts[boxIndices[idx]], fcol[i]);
|
||||
|
@ -154,37 +167,45 @@ public class DebugDraw {
|
|||
}
|
||||
|
||||
public void debugDrawArc(float x0, float y0, float z0, float x1, float y1, float z1, float h, float as0, float as1, int col,
|
||||
float lineWidth) {
|
||||
float lineWidth)
|
||||
{
|
||||
begin(DebugDrawPrimitives.LINES, lineWidth);
|
||||
appendArc(x0, y0, z0, x1, y1, z1, h, as0, as1, col);
|
||||
end();
|
||||
}
|
||||
|
||||
public void begin(DebugDrawPrimitives prim, float size) {
|
||||
public void begin(DebugDrawPrimitives prim, float size)
|
||||
{
|
||||
getOpenGlDraw().begin(prim, size);
|
||||
}
|
||||
|
||||
public void vertex(float[] pos, int color) {
|
||||
public void vertex(float[] pos, int color)
|
||||
{
|
||||
getOpenGlDraw().vertex(pos, color);
|
||||
}
|
||||
|
||||
public void vertex(float x, float y, float z, int color) {
|
||||
public void vertex(float x, float y, float z, int color)
|
||||
{
|
||||
getOpenGlDraw().vertex(x, y, z, color);
|
||||
}
|
||||
|
||||
public void vertex(float[] pos, int color, float[] uv) {
|
||||
public void vertex(float[] pos, int color, float[] uv)
|
||||
{
|
||||
getOpenGlDraw().vertex(pos, color, uv);
|
||||
}
|
||||
|
||||
public void vertex(float x, float y, float z, int color, float u, float v) {
|
||||
public void vertex(float x, float y, float z, int color, float u, float v)
|
||||
{
|
||||
getOpenGlDraw().vertex(x, y, z, color, u, v);
|
||||
}
|
||||
|
||||
public void end() {
|
||||
public void end()
|
||||
{
|
||||
getOpenGlDraw().end();
|
||||
}
|
||||
|
||||
public void debugDrawCircle(float x, float y, float z, float r, int col, float lineWidth) {
|
||||
public void debugDrawCircle(float x, float y, float z, float r, int col, float lineWidth)
|
||||
{
|
||||
begin(DebugDrawPrimitives.LINES, lineWidth);
|
||||
appendCircle(x, y, z, r, col);
|
||||
end();
|
||||
|
@ -196,16 +217,21 @@ public class DebugDraw {
|
|||
private float[] _viewMatrix = new float[16];
|
||||
private readonly float[] _projectionMatrix = new float[16];
|
||||
|
||||
public void appendCircle(float x, float y, float z, float r, int col) {
|
||||
if (!circleInit) {
|
||||
public void appendCircle(float x, float y, float z, float r, int col)
|
||||
{
|
||||
if (!circleInit)
|
||||
{
|
||||
circleInit = true;
|
||||
for (int i = 0; i < CIRCLE_NUM_SEG; ++i) {
|
||||
for (int i = 0; i < CIRCLE_NUM_SEG; ++i)
|
||||
{
|
||||
float a = (float)(i * Math.PI * 2 / CIRCLE_NUM_SEG);
|
||||
circeDir[i * 2] = (float)Math.Cos(a);
|
||||
circeDir[i * 2 + 1] = (float)Math.Sin(a);
|
||||
}
|
||||
}
|
||||
for (int i = 0, j = CIRCLE_NUM_SEG - 1; i < CIRCLE_NUM_SEG; j = i++) {
|
||||
|
||||
for (int i = 0, j = CIRCLE_NUM_SEG - 1; i < CIRCLE_NUM_SEG; j = i++)
|
||||
{
|
||||
vertex(x + circeDir[j * 2 + 0] * r, y, z + circeDir[j * 2 + 1] * r, col);
|
||||
vertex(x + circeDir[i * 2 + 0] * r, y, z + circeDir[i * 2 + 1] * r, col);
|
||||
}
|
||||
|
@ -215,14 +241,16 @@ public class DebugDraw {
|
|||
private static readonly float PAD = 0.05f;
|
||||
private static readonly float ARC_PTS_SCALE = (1.0f - PAD * 2) / NUM_ARC_PTS;
|
||||
|
||||
public void appendArc(float x0, float y0, float z0, float x1, float y1, float z1, float h, float as0, float as1, int col) {
|
||||
public void appendArc(float x0, float y0, float z0, float x1, float y1, float z1, float h, float as0, float as1, int col)
|
||||
{
|
||||
float dx = x1 - x0;
|
||||
float dy = y1 - y0;
|
||||
float dz = z1 - z0;
|
||||
float len = (float)Math.Sqrt(dx * dx + dy * dy + dz * dz);
|
||||
float[] prev = new float[3];
|
||||
evalArc(x0, y0, z0, dx, dy, dz, len * h, PAD, prev);
|
||||
for (int i = 1; i <= NUM_ARC_PTS; ++i) {
|
||||
for (int i = 1; i <= NUM_ARC_PTS; ++i)
|
||||
{
|
||||
float u = PAD + i * ARC_PTS_SCALE;
|
||||
float[] pt = new float[3];
|
||||
evalArc(x0, y0, z0, dx, dy, dz, len * h, u, pt);
|
||||
|
@ -234,14 +262,16 @@ public class DebugDraw {
|
|||
}
|
||||
|
||||
// End arrows
|
||||
if (as0 > 0.001f) {
|
||||
if (as0 > 0.001f)
|
||||
{
|
||||
float[] p = new float[3], q = new float[3];
|
||||
evalArc(x0, y0, z0, dx, dy, dz, len * h, PAD, p);
|
||||
evalArc(x0, y0, z0, dx, dy, dz, len * h, PAD + 0.05f, q);
|
||||
appendArrowHead(p, q, as0, col);
|
||||
}
|
||||
|
||||
if (as1 > 0.001f) {
|
||||
if (as1 > 0.001f)
|
||||
{
|
||||
float[] p = new float[3], q = new float[3];
|
||||
evalArc(x0, y0, z0, dx, dy, dz, len * h, 1 - PAD, p);
|
||||
evalArc(x0, y0, z0, dx, dy, dz, len * h, 1 - (PAD + 0.05f), q);
|
||||
|
@ -249,19 +279,22 @@ public class DebugDraw {
|
|||
}
|
||||
}
|
||||
|
||||
private void evalArc(float x0, float y0, float z0, float dx, float dy, float dz, float h, float u, float[] res) {
|
||||
private void evalArc(float x0, float y0, float z0, float dx, float dy, float dz, float h, float u, float[] res)
|
||||
{
|
||||
res[0] = x0 + dx * u;
|
||||
res[1] = y0 + dy * u + h * (1 - (u * 2 - 1) * (u * 2 - 1));
|
||||
res[2] = z0 + dz * u;
|
||||
}
|
||||
|
||||
public void debugDrawCross(float x, float y, float z, float size, int col, float lineWidth) {
|
||||
public void debugDrawCross(float x, float y, float z, float size, int col, float lineWidth)
|
||||
{
|
||||
begin(DebugDrawPrimitives.LINES, lineWidth);
|
||||
appendCross(x, y, z, size, col);
|
||||
end();
|
||||
}
|
||||
|
||||
private void appendCross(float x, float y, float z, float s, int col) {
|
||||
private void appendCross(float x, float y, float z, float s, int col)
|
||||
{
|
||||
vertex(x - s, y, z, col);
|
||||
vertex(x + s, y, z, col);
|
||||
vertex(x, y - s, z, col);
|
||||
|
@ -270,19 +303,22 @@ public class DebugDraw {
|
|||
vertex(x, y, z + s, col);
|
||||
}
|
||||
|
||||
public void debugDrawBox(float minx, float miny, float minz, float maxx, float maxy, float maxz, int[] fcol) {
|
||||
public void debugDrawBox(float minx, float miny, float minz, float maxx, float maxy, float maxz, int[] fcol)
|
||||
{
|
||||
begin(DebugDrawPrimitives.QUADS);
|
||||
appendBox(minx, miny, minz, maxx, maxy, maxz, fcol);
|
||||
end();
|
||||
}
|
||||
|
||||
public void debugDrawCylinder(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col) {
|
||||
public void debugDrawCylinder(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col)
|
||||
{
|
||||
begin(DebugDrawPrimitives.TRIS);
|
||||
appendCylinder(minx, miny, minz, maxx, maxy, maxz, col);
|
||||
end();
|
||||
}
|
||||
|
||||
public void appendCylinder(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col) {
|
||||
public void appendCylinder(float minx, float miny, float minz, float maxx, float maxy, float maxz, int col)
|
||||
{
|
||||
initCylinder();
|
||||
|
||||
int col2 = duMultCol(col, 160);
|
||||
|
@ -292,19 +328,24 @@ public class DebugDraw {
|
|||
float rx = (maxx - minx) / 2;
|
||||
float rz = (maxz - minz) / 2;
|
||||
|
||||
for (int i = 2; i < CYLINDER_NUM_SEG; ++i) {
|
||||
for (int i = 2; i < CYLINDER_NUM_SEG; ++i)
|
||||
{
|
||||
int a = 0, b = i - 1, c = i;
|
||||
vertex(cx + cylinderDir[a * 2 + 0] * rx, miny, cz + cylinderDir[a * 2 + 1] * rz, col2);
|
||||
vertex(cx + cylinderDir[b * 2 + 0] * rx, miny, cz + cylinderDir[b * 2 + 1] * rz, col2);
|
||||
vertex(cx + cylinderDir[c * 2 + 0] * rx, miny, cz + cylinderDir[c * 2 + 1] * rz, col2);
|
||||
}
|
||||
for (int i = 2; i < CYLINDER_NUM_SEG; ++i) {
|
||||
|
||||
for (int i = 2; i < CYLINDER_NUM_SEG; ++i)
|
||||
{
|
||||
int a = 0, b = i, c = i - 1;
|
||||
vertex(cx + cylinderDir[a * 2 + 0] * rx, maxy, cz + cylinderDir[a * 2 + 1] * rz, col);
|
||||
vertex(cx + cylinderDir[b * 2 + 0] * rx, maxy, cz + cylinderDir[b * 2 + 1] * rz, col);
|
||||
vertex(cx + cylinderDir[c * 2 + 0] * rx, maxy, cz + cylinderDir[c * 2 + 1] * rz, col);
|
||||
}
|
||||
for (int i = 0, j = CYLINDER_NUM_SEG - 1; i < CYLINDER_NUM_SEG; j = i++) {
|
||||
|
||||
for (int i = 0, j = CYLINDER_NUM_SEG - 1; i < CYLINDER_NUM_SEG; j = i++)
|
||||
{
|
||||
vertex(cx + cylinderDir[i * 2 + 0] * rx, miny, cz + cylinderDir[i * 2 + 1] * rz, col2);
|
||||
vertex(cx + cylinderDir[j * 2 + 0] * rx, miny, cz + cylinderDir[j * 2 + 1] * rz, col2);
|
||||
vertex(cx + cylinderDir[j * 2 + 0] * rx, maxy, cz + cylinderDir[j * 2 + 1] * rz, col);
|
||||
|
@ -316,15 +357,15 @@ public class DebugDraw {
|
|||
}
|
||||
|
||||
public void debugDrawArrow(float x0, float y0, float z0, float x1, float y1, float z1, float as0, float as1, int col,
|
||||
float lineWidth) {
|
||||
|
||||
float lineWidth)
|
||||
{
|
||||
begin(DebugDrawPrimitives.LINES, lineWidth);
|
||||
appendArrow(x0, y0, z0, x1, y1, z1, as0, as1, col);
|
||||
end();
|
||||
}
|
||||
|
||||
public void appendArrow(float x0, float y0, float z0, float x1, float y1, float z1, float as0, float as1, int col) {
|
||||
|
||||
public void appendArrow(float x0, float y0, float z0, float x1, float y1, float z1, float as0, float as1, int col)
|
||||
{
|
||||
vertex(x0, y0, z0, col);
|
||||
vertex(x1, y1, z1, col);
|
||||
|
||||
|
@ -337,11 +378,14 @@ public class DebugDraw {
|
|||
appendArrowHead(q, p, as1, col);
|
||||
}
|
||||
|
||||
void appendArrowHead(float[] p, float[] q, float s, int col) {
|
||||
void appendArrowHead(float[] p, float[] q, float s, int col)
|
||||
{
|
||||
float eps = 0.001f;
|
||||
if (vdistSqr(p, q) < eps * eps) {
|
||||
if (vdistSqr(p, q) < eps * eps)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float[] ax = new float[3], ay = { 0, 1, 0 }, az = new float[3];
|
||||
vsub(az, q, p);
|
||||
vnormalize(az);
|
||||
|
@ -356,29 +400,32 @@ public class DebugDraw {
|
|||
vertex(p, col);
|
||||
// vertex(p[0]+az[0]*s-ay[0]*s/2, p[1]+az[1]*s-ay[1]*s/2, p[2]+az[2]*s-ay[2]*s/2, col);
|
||||
vertex(p[0] + az[0] * s - ax[0] * s / 3, p[1] + az[1] * s - ax[1] * s / 3, p[2] + az[2] * s - ax[2] * s / 3, col);
|
||||
|
||||
}
|
||||
|
||||
public void vcross(float[] dest, float[] v1, float[] v2) {
|
||||
public void vcross(float[] dest, float[] v1, float[] v2)
|
||||
{
|
||||
dest[0] = v1[1] * v2[2] - v1[2] * v2[1];
|
||||
dest[1] = v1[2] * v2[0] - v1[0] * v2[2];
|
||||
dest[2] = v1[0] * v2[1] - v1[1] * v2[0];
|
||||
}
|
||||
|
||||
public void vnormalize(float[] v) {
|
||||
public void vnormalize(float[] v)
|
||||
{
|
||||
float d = (float)(1.0f / Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]));
|
||||
v[0] *= d;
|
||||
v[1] *= d;
|
||||
v[2] *= d;
|
||||
}
|
||||
|
||||
public void vsub(float[] dest, float[] v1, float[] v2) {
|
||||
public void vsub(float[] dest, float[] v1, float[] v2)
|
||||
{
|
||||
dest[0] = v1[0] - v2[0];
|
||||
dest[1] = v1[1] - v2[1];
|
||||
dest[2] = v1[2] - v2[2];
|
||||
}
|
||||
|
||||
public float vdistSqr(float[] v1, float[] v2) {
|
||||
public float vdistSqr(float[] v1, float[] v2)
|
||||
{
|
||||
float x = v1[0] - v2[0];
|
||||
float y = v1[1] - v2[1];
|
||||
float z = v1[2] - v2[2];
|
||||
|
@ -393,8 +440,10 @@ public class DebugDraw {
|
|||
// }
|
||||
// }
|
||||
|
||||
public static int areaToCol(int area) {
|
||||
switch (area) {
|
||||
public static int areaToCol(int area)
|
||||
{
|
||||
switch (area)
|
||||
{
|
||||
// Ground (0) : light blue
|
||||
case SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WALKABLE:
|
||||
case SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND:
|
||||
|
@ -420,11 +469,13 @@ public class DebugDraw {
|
|||
}
|
||||
}
|
||||
|
||||
public static int duRGBA(int r, int g, int b, int a) {
|
||||
public static int duRGBA(int r, int g, int b, int a)
|
||||
{
|
||||
return (r) | (g << 8) | (b << 16) | (a << 24);
|
||||
}
|
||||
|
||||
public static int duLerpCol(int ca, int cb, int u) {
|
||||
public static int duLerpCol(int ca, int cb, int u)
|
||||
{
|
||||
int ra = ca & 0xff;
|
||||
int ga = (ca >> 8) & 0xff;
|
||||
int ba = (ca >> 16) & 0xff;
|
||||
|
@ -441,18 +492,21 @@ public class DebugDraw {
|
|||
return duRGBA(r, g, b, a);
|
||||
}
|
||||
|
||||
public static int bit(int a, int b) {
|
||||
public static int bit(int a, int b)
|
||||
{
|
||||
return (a & (1 << b)) >>> b;
|
||||
}
|
||||
|
||||
public static int duIntToCol(int i, int a) {
|
||||
public static int duIntToCol(int i, int a)
|
||||
{
|
||||
int r = bit(i, 1) + bit(i, 3) * 2 + 1;
|
||||
int g = bit(i, 2) + bit(i, 4) * 2 + 1;
|
||||
int b = bit(i, 0) + bit(i, 5) * 2 + 1;
|
||||
return duRGBA(r * 63, g * 63, b * 63, a);
|
||||
}
|
||||
|
||||
public static void duCalcBoxColors(int[] colors, int colTop, int colSide) {
|
||||
public static void duCalcBoxColors(int[] colors, int colTop, int colSide)
|
||||
{
|
||||
colors[0] = duMultCol(colTop, 250);
|
||||
colors[1] = duMultCol(colSide, 140);
|
||||
colors[2] = duMultCol(colSide, 165);
|
||||
|
@ -461,7 +515,8 @@ public class DebugDraw {
|
|||
colors[5] = duMultCol(colSide, 217);
|
||||
}
|
||||
|
||||
public static int duMultCol(int col, int d) {
|
||||
public static int duMultCol(int col, int d)
|
||||
{
|
||||
int r = col & 0xff;
|
||||
int g = (col >> 8) & 0xff;
|
||||
int b = (col >> 16) & 0xff;
|
||||
|
@ -469,46 +524,56 @@ public class DebugDraw {
|
|||
return duRGBA((r * d) >> 8, (g * d) >> 8, (b * d) >> 8, a);
|
||||
}
|
||||
|
||||
public static int duTransCol(int c, int a) {
|
||||
public static int duTransCol(int c, int a)
|
||||
{
|
||||
return (a << 24) | (c & 0x00ffffff);
|
||||
}
|
||||
|
||||
public static int duDarkenCol(int col) {
|
||||
public static int duDarkenCol(int col)
|
||||
{
|
||||
return (int)(((col >> 1) & 0x007f7f7f) | (col & 0xff000000));
|
||||
}
|
||||
|
||||
public void fog(float start, float end) {
|
||||
public void fog(float start, float end)
|
||||
{
|
||||
getOpenGlDraw().fog(start, end);
|
||||
}
|
||||
|
||||
public void fog(bool state) {
|
||||
public void fog(bool state)
|
||||
{
|
||||
getOpenGlDraw().fog(state);
|
||||
}
|
||||
|
||||
public void depthMask(bool state) {
|
||||
public void depthMask(bool state)
|
||||
{
|
||||
getOpenGlDraw().depthMask(state);
|
||||
}
|
||||
|
||||
public void texture(bool state) {
|
||||
public void texture(bool state)
|
||||
{
|
||||
getOpenGlDraw().texture(g_tex, state);
|
||||
}
|
||||
|
||||
public void init(GL gl, float fogDistance) {
|
||||
public void init(GL gl, float fogDistance)
|
||||
{
|
||||
getOpenGlDraw().init(gl);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
public void clear()
|
||||
{
|
||||
getOpenGlDraw().clear();
|
||||
}
|
||||
|
||||
public float[] projectionMatrix(float fovy, float aspect, float near, float far) {
|
||||
public float[] projectionMatrix(float fovy, float aspect, float near, float far)
|
||||
{
|
||||
GLU.glhPerspectivef2(_projectionMatrix, fovy, aspect, near, far);
|
||||
getOpenGlDraw().projectionMatrix(_projectionMatrix);
|
||||
updateFrustum();
|
||||
return _projectionMatrix;
|
||||
}
|
||||
|
||||
public float[] viewMatrix(float[] cameraPos, float[] cameraEulers) {
|
||||
public float[] viewMatrix(float[] cameraPos, float[] cameraEulers)
|
||||
{
|
||||
float[] rx = GLU.build_4x4_rotation_matrix(cameraEulers[0], 1, 0, 0);
|
||||
float[] ry = GLU.build_4x4_rotation_matrix(cameraEulers[1], 0, 1, 0);
|
||||
float[] r = GLU.mul(rx, ry);
|
||||
|
@ -523,11 +588,13 @@ public class DebugDraw {
|
|||
return _viewMatrix;
|
||||
}
|
||||
|
||||
private OpenGLDraw getOpenGlDraw() {
|
||||
private OpenGLDraw getOpenGlDraw()
|
||||
{
|
||||
return openGlDraw;
|
||||
}
|
||||
|
||||
private void updateFrustum() {
|
||||
private void updateFrustum()
|
||||
{
|
||||
float[] vpm = GLU.mul(_projectionMatrix, _viewMatrix);
|
||||
// left
|
||||
frustumPlanes[0] = normalizePlane(vpm[0 + 3] + vpm[0 + 0], vpm[4 + 3] + vpm[4 + 0], vpm[8 + 3] + vpm[8 + 0],
|
||||
|
@ -549,56 +616,75 @@ public class DebugDraw {
|
|||
vpm[12 + 3] - vpm[12 + 2]);
|
||||
}
|
||||
|
||||
private float[] normalizePlane(float px, float py, float pz, float pw) {
|
||||
private float[] normalizePlane(float px, float py, float pz, float pw)
|
||||
{
|
||||
float length = (float)Math.Sqrt(px * px + py * py + pz * pz);
|
||||
if (length != 0) {
|
||||
if (length != 0)
|
||||
{
|
||||
length = 1f / length;
|
||||
px *= length;
|
||||
py *= length;
|
||||
pz *= length;
|
||||
pw *= length;
|
||||
}
|
||||
|
||||
return new float[] { px, py, pz, pw };
|
||||
}
|
||||
|
||||
public bool frustumTest(float[] bmin, float[] bmax) {
|
||||
public bool frustumTest(float[] bmin, float[] bmax)
|
||||
{
|
||||
return frustumTest(new float[] { bmin[0], bmin[1], bmin[2], bmax[0], bmax[1], bmax[2] });
|
||||
}
|
||||
|
||||
public bool frustumTest(float[] bounds) {
|
||||
foreach (float[] plane in frustumPlanes) {
|
||||
public bool frustumTest(float[] bounds)
|
||||
{
|
||||
foreach (float[] plane in frustumPlanes)
|
||||
{
|
||||
float p_x;
|
||||
float p_y;
|
||||
float p_z;
|
||||
float n_x;
|
||||
float n_y;
|
||||
float n_z;
|
||||
if (plane[0] >= 0) {
|
||||
if (plane[0] >= 0)
|
||||
{
|
||||
p_x = bounds[3];
|
||||
n_x = bounds[0];
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
p_x = bounds[0];
|
||||
n_x = bounds[3];
|
||||
}
|
||||
if (plane[1] >= 0) {
|
||||
|
||||
if (plane[1] >= 0)
|
||||
{
|
||||
p_y = bounds[4];
|
||||
n_y = bounds[1];
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
p_y = bounds[1];
|
||||
n_y = bounds[4];
|
||||
}
|
||||
if (plane[2] >= 0) {
|
||||
|
||||
if (plane[2] >= 0)
|
||||
{
|
||||
p_z = bounds[5];
|
||||
n_z = bounds[2];
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
p_z = bounds[2];
|
||||
n_z = bounds[5];
|
||||
}
|
||||
if (plane[0] * p_x + plane[1] * p_y + plane[2] * p_z + plane[3] < 0) {
|
||||
|
||||
if (plane[0] * p_x + plane[1] * p_y + plane[2] * p_z + plane[3] < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -17,10 +17,11 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Recast.Demo.Draw;
|
||||
|
||||
public enum DebugDrawPrimitives {
|
||||
|
||||
public enum DebugDrawPrimitives
|
||||
{
|
||||
POINTS,
|
||||
LINES,
|
||||
TRIS,
|
||||
|
|
|
@ -17,9 +17,11 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Recast.Demo.Draw;
|
||||
|
||||
public class DrawMode {
|
||||
public class DrawMode
|
||||
{
|
||||
public static readonly DrawMode DRAWMODE_MESH = new("Input Mesh");
|
||||
public static readonly DrawMode DRAWMODE_NAVMESH = new("Navmesh");
|
||||
public static readonly DrawMode DRAWMODE_NAVMESH_INVIS = new("Navmesh Invis");
|
||||
|
@ -41,11 +43,13 @@ public class DrawMode {
|
|||
|
||||
private readonly string text;
|
||||
|
||||
private DrawMode(string text) {
|
||||
private DrawMode(string text)
|
||||
{
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
public override string ToString()
|
||||
{
|
||||
return text;
|
||||
}
|
||||
}
|
|
@ -22,17 +22,19 @@ using Silk.NET.OpenGL;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Draw;
|
||||
|
||||
public class GLCheckerTexture {
|
||||
|
||||
public class GLCheckerTexture
|
||||
{
|
||||
int m_texId;
|
||||
|
||||
public void release() {
|
||||
public void release()
|
||||
{
|
||||
// if (m_texId != 0) {
|
||||
// glDeleteTextures(m_texId);
|
||||
// }
|
||||
}
|
||||
|
||||
public void bind() {
|
||||
public void bind()
|
||||
{
|
||||
// if (m_texId == 0) {
|
||||
// // Create checker pattern.
|
||||
// int col0 = DebugDraw.duRGBA(215, 215, 215, 255);
|
||||
|
|
|
@ -21,11 +21,10 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Draw;
|
||||
|
||||
|
||||
|
||||
public class GLU {
|
||||
|
||||
public static float[] gluPerspective(float fovy, float aspect, float near, float far) {
|
||||
public class GLU
|
||||
{
|
||||
public static float[] gluPerspective(float fovy, float aspect, float near, float far)
|
||||
{
|
||||
float[] projectionMatrix = new float[16];
|
||||
glhPerspectivef2(projectionMatrix, fovy, aspect, near, far);
|
||||
//glLoadMatrixf(projectionMatrix);
|
||||
|
@ -33,7 +32,8 @@ public class GLU {
|
|||
}
|
||||
|
||||
public static void glhPerspectivef2(float[] matrix, float fovyInDegrees, float aspectRatio, float znear,
|
||||
float zfar) {
|
||||
float zfar)
|
||||
{
|
||||
float ymax, xmax;
|
||||
ymax = (float)(znear * Math.Tan(fovyInDegrees * Math.PI / 360.0));
|
||||
xmax = ymax * aspectRatio;
|
||||
|
@ -41,7 +41,8 @@ public class GLU {
|
|||
}
|
||||
|
||||
private static void glhFrustumf2(float[] matrix, float left, float right, float bottom, float top, float znear,
|
||||
float zfar) {
|
||||
float zfar)
|
||||
{
|
||||
float temp, temp2, temp3, temp4;
|
||||
temp = 2.0f * znear;
|
||||
temp2 = right - left;
|
||||
|
@ -66,7 +67,8 @@ public class GLU {
|
|||
}
|
||||
|
||||
public static int glhUnProjectf(float winx, float winy, float winz, float[] modelview, float[] projection,
|
||||
int[] viewport, float[] objectCoordinate) {
|
||||
int[] viewport, float[] objectCoordinate)
|
||||
{
|
||||
// Transformation matrices
|
||||
float[] m = new float[16], A = new float[16];
|
||||
float[] @in = new float[4], @out = new float[4];
|
||||
|
@ -92,7 +94,8 @@ public class GLU {
|
|||
return 1;
|
||||
}
|
||||
|
||||
static void MultiplyMatrices4by4OpenGL_FLOAT(float[] result, float[] matrix1, float[] matrix2) {
|
||||
static void MultiplyMatrices4by4OpenGL_FLOAT(float[] result, float[] matrix1, float[] matrix2)
|
||||
{
|
||||
result[0] = matrix1[0] * matrix2[0] + matrix1[4] * matrix2[1] + matrix1[8] * matrix2[2]
|
||||
+ matrix1[12] * matrix2[3];
|
||||
result[4] = matrix1[0] * matrix2[4] + matrix1[4] * matrix2[5] + matrix1[8] * matrix2[6]
|
||||
|
@ -127,7 +130,8 @@ public class GLU {
|
|||
+ matrix1[15] * matrix2[15];
|
||||
}
|
||||
|
||||
static void MultiplyMatrixByVector4by4OpenGL_FLOAT(float[] resultvector, float[] matrix, float[] pvector) {
|
||||
static void MultiplyMatrixByVector4by4OpenGL_FLOAT(float[] resultvector, float[] matrix, float[] pvector)
|
||||
{
|
||||
resultvector[0] = matrix[0] * pvector[0] + matrix[4] * pvector[1] + matrix[8] * pvector[2]
|
||||
+ matrix[12] * pvector[3];
|
||||
resultvector[1] = matrix[1] * pvector[0] + matrix[5] * pvector[1] + matrix[9] * pvector[2]
|
||||
|
@ -139,7 +143,8 @@ public class GLU {
|
|||
}
|
||||
|
||||
// This code comes directly from GLU except that it is for float
|
||||
static int glhInvertMatrixf2(float[] m, float[] @out) {
|
||||
static int glhInvertMatrixf2(float[] m, float[] @out)
|
||||
{
|
||||
float[][] wtmp = ArrayUtils.Of<float>(4, 8);
|
||||
float m0, m1, m2, m3, s;
|
||||
float[] r0, r1, r2, r3;
|
||||
|
@ -172,21 +177,27 @@ public class GLU {
|
|||
r3[7] = 1.0f;
|
||||
r3[4] = r3[5] = r3[6] = 0.0f;
|
||||
/* choose pivot - or die */
|
||||
if (Math.Abs(r3[0]) > Math.Abs(r2[0])) {
|
||||
if (Math.Abs(r3[0]) > Math.Abs(r2[0]))
|
||||
{
|
||||
float[] r = r2;
|
||||
r2 = r3;
|
||||
r3 = r;
|
||||
}
|
||||
if (Math.Abs(r2[0]) > Math.Abs(r1[0])) {
|
||||
|
||||
if (Math.Abs(r2[0]) > Math.Abs(r1[0]))
|
||||
{
|
||||
float[] r = r2;
|
||||
r2 = r1;
|
||||
r1 = r;
|
||||
}
|
||||
if (Math.Abs(r1[0]) > Math.Abs(r0[0])) {
|
||||
|
||||
if (Math.Abs(r1[0]) > Math.Abs(r0[0]))
|
||||
{
|
||||
float[] r = r1;
|
||||
r1 = r0;
|
||||
r0 = r;
|
||||
}
|
||||
|
||||
if (0.0 == r0[0])
|
||||
return 0;
|
||||
/* eliminate first variable */
|
||||
|
@ -206,40 +217,52 @@ public class GLU {
|
|||
r2[3] -= m2 * s;
|
||||
r3[3] -= m3 * s;
|
||||
s = r0[4];
|
||||
if (s != 0.0) {
|
||||
if (s != 0.0)
|
||||
{
|
||||
r1[4] -= m1 * s;
|
||||
r2[4] -= m2 * s;
|
||||
r3[4] -= m3 * s;
|
||||
}
|
||||
|
||||
s = r0[5];
|
||||
if (s != 0.0) {
|
||||
if (s != 0.0)
|
||||
{
|
||||
r1[5] -= m1 * s;
|
||||
r2[5] -= m2 * s;
|
||||
r3[5] -= m3 * s;
|
||||
}
|
||||
|
||||
s = r0[6];
|
||||
if (s != 0.0) {
|
||||
if (s != 0.0)
|
||||
{
|
||||
r1[6] -= m1 * s;
|
||||
r2[6] -= m2 * s;
|
||||
r3[6] -= m3 * s;
|
||||
}
|
||||
|
||||
s = r0[7];
|
||||
if (s != 0.0) {
|
||||
if (s != 0.0)
|
||||
{
|
||||
r1[7] -= m1 * s;
|
||||
r2[7] -= m2 * s;
|
||||
r3[7] -= m3 * s;
|
||||
}
|
||||
|
||||
/* choose pivot - or die */
|
||||
if (Math.Abs(r3[1]) > Math.Abs(r2[1])) {
|
||||
if (Math.Abs(r3[1]) > Math.Abs(r2[1]))
|
||||
{
|
||||
float[] r = r2;
|
||||
r2 = r3;
|
||||
r3 = r;
|
||||
}
|
||||
if (Math.Abs(r2[1]) > Math.Abs(r1[1])) {
|
||||
|
||||
if (Math.Abs(r2[1]) > Math.Abs(r1[1]))
|
||||
{
|
||||
float[] r = r2;
|
||||
r2 = r1;
|
||||
r1 = r;
|
||||
}
|
||||
|
||||
if (0.0 == r1[1])
|
||||
return 0;
|
||||
/* eliminate second variable */
|
||||
|
@ -250,31 +273,41 @@ public class GLU {
|
|||
r2[3] -= m2 * r1[3];
|
||||
r3[3] -= m3 * r1[3];
|
||||
s = r1[4];
|
||||
if (0.0 != s) {
|
||||
if (0.0 != s)
|
||||
{
|
||||
r2[4] -= m2 * s;
|
||||
r3[4] -= m3 * s;
|
||||
}
|
||||
|
||||
s = r1[5];
|
||||
if (0.0 != s) {
|
||||
if (0.0 != s)
|
||||
{
|
||||
r2[5] -= m2 * s;
|
||||
r3[5] -= m3 * s;
|
||||
}
|
||||
|
||||
s = r1[6];
|
||||
if (0.0 != s) {
|
||||
if (0.0 != s)
|
||||
{
|
||||
r2[6] -= m2 * s;
|
||||
r3[6] -= m3 * s;
|
||||
}
|
||||
|
||||
s = r1[7];
|
||||
if (0.0 != s) {
|
||||
if (0.0 != s)
|
||||
{
|
||||
r2[7] -= m2 * s;
|
||||
r3[7] -= m3 * s;
|
||||
}
|
||||
|
||||
/* choose pivot - or die */
|
||||
if (Math.Abs(r3[2]) > Math.Abs(r2[2])) {
|
||||
if (Math.Abs(r3[2]) > Math.Abs(r2[2]))
|
||||
{
|
||||
float[] r = r2;
|
||||
r2 = r3;
|
||||
r3 = r;
|
||||
}
|
||||
|
||||
if (0.0 == r2[2])
|
||||
return 0;
|
||||
/* eliminate third variable */
|
||||
|
@ -344,15 +377,18 @@ public class GLU {
|
|||
return 1;
|
||||
}
|
||||
|
||||
static float MAT(float[] m, int r, int c) {
|
||||
static float MAT(float[] m, int r, int c)
|
||||
{
|
||||
return m[(c) * 4 + (r)];
|
||||
}
|
||||
|
||||
static void MAT(float[] m, int r, int c, float v) {
|
||||
static void MAT(float[] m, int r, int c, float v)
|
||||
{
|
||||
m[(c) * 4 + (r)] = v;
|
||||
}
|
||||
|
||||
public static float[] build_4x4_rotation_matrix(float a, float x, float y, float z) {
|
||||
public static float[] build_4x4_rotation_matrix(float a, float x, float y, float z)
|
||||
{
|
||||
float[] matrix = new float[16];
|
||||
a = (float)(a * Math.PI / 180.0); // convert to radians
|
||||
float s = (float)Math.Sin(a);
|
||||
|
@ -387,10 +423,10 @@ public class GLU {
|
|||
matrix[14] = 0;
|
||||
matrix[15] = 1;
|
||||
return matrix;
|
||||
|
||||
}
|
||||
|
||||
public static float[] mul(float[] left, float[] right) {
|
||||
public static float[] mul(float[] left, float[] right)
|
||||
{
|
||||
float m00 = left[0] * right[0] + left[4] * right[1] + left[8] * right[2] + left[12] * right[3];
|
||||
float m01 = left[1] * right[0] + left[5] * right[1] + left[9] * right[2] + left[13] * right[3];
|
||||
float m02 = left[2] * right[0] + left[6] * right[1] + left[10] * right[2] + left[14] * right[3];
|
||||
|
@ -428,5 +464,4 @@ public class GLU {
|
|||
|
||||
return dest;
|
||||
}
|
||||
|
||||
}
|
|
@ -4,10 +4,10 @@ namespace DotRecast.Recast.Demo.Draw;
|
|||
|
||||
public class LegacyOpenGLDraw : OpenGLDraw
|
||||
{
|
||||
|
||||
private GL _gl;
|
||||
|
||||
public void fog(bool state) {
|
||||
public void fog(bool state)
|
||||
{
|
||||
// if (state) {
|
||||
// _gl.Enable(GL_FOG);
|
||||
// } else {
|
||||
|
@ -30,7 +30,8 @@ public class LegacyOpenGLDraw : OpenGLDraw
|
|||
// glDepthFunc(GL_LEQUAL);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
public void clear()
|
||||
{
|
||||
// glClearColor(0.3f, 0.3f, 0.32f, 1.0f);
|
||||
// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
// glEnable(GL_BLEND);
|
||||
|
@ -40,17 +41,20 @@ public class LegacyOpenGLDraw : OpenGLDraw
|
|||
// glEnable(GL_CULL_FACE);
|
||||
}
|
||||
|
||||
public void projectionMatrix(float[] matrix) {
|
||||
public void projectionMatrix(float[] matrix)
|
||||
{
|
||||
// glMatrixMode(GL_PROJECTION);
|
||||
// glLoadMatrixf(matrix);
|
||||
}
|
||||
|
||||
public void viewMatrix(float[] matrix) {
|
||||
public void viewMatrix(float[] matrix)
|
||||
{
|
||||
// glMatrixMode(GL_MODELVIEW);
|
||||
// glLoadMatrixf(matrix);
|
||||
}
|
||||
|
||||
public void begin(DebugDrawPrimitives prim, float size) {
|
||||
public void begin(DebugDrawPrimitives prim, float size)
|
||||
{
|
||||
// switch (prim) {
|
||||
// case POINTS:
|
||||
// glPointSize(size);
|
||||
|
@ -69,38 +73,45 @@ public class LegacyOpenGLDraw : OpenGLDraw
|
|||
// }
|
||||
}
|
||||
|
||||
public void vertex(float[] pos, int color) {
|
||||
public void vertex(float[] pos, int color)
|
||||
{
|
||||
// glColor4ubv(color);
|
||||
// glVertex3fv(pos);
|
||||
}
|
||||
|
||||
public void vertex(float x, float y, float z, int color) {
|
||||
public void vertex(float x, float y, float z, int color)
|
||||
{
|
||||
// glColor4ubv(color);
|
||||
// glVertex3f(x, y, z);
|
||||
}
|
||||
|
||||
public void vertex(float[] pos, int color, float[] uv) {
|
||||
public void vertex(float[] pos, int color, float[] uv)
|
||||
{
|
||||
// glColor4ubv(color);
|
||||
// glTexCoord2fv(uv);
|
||||
// glVertex3fv(pos);
|
||||
}
|
||||
|
||||
public void vertex(float x, float y, float z, int color, float u, float v) {
|
||||
public void vertex(float x, float y, float z, int color, float u, float v)
|
||||
{
|
||||
// glColor4ubv(color);
|
||||
// glTexCoord2f(u, v);
|
||||
// glVertex3f(x, y, z);
|
||||
}
|
||||
|
||||
private void glColor4ubv(int color) {
|
||||
private void glColor4ubv(int color)
|
||||
{
|
||||
// glColor4ub((byte) (color & 0xFF), (byte) ((color >> 8) & 0xFF), (byte) ((color >> 16) & 0xFF),
|
||||
// (byte) ((color >> 24) & 0xFF));
|
||||
}
|
||||
|
||||
public void depthMask(bool state) {
|
||||
public void depthMask(bool state)
|
||||
{
|
||||
// glDepthMask(state);
|
||||
}
|
||||
|
||||
public void texture(GLCheckerTexture g_tex, bool state) {
|
||||
public void texture(GLCheckerTexture g_tex, bool state)
|
||||
{
|
||||
// if (state) {
|
||||
// glEnable(GL_TEXTURE_2D);
|
||||
// g_tex.bind();
|
||||
|
@ -109,15 +120,16 @@ public class LegacyOpenGLDraw : OpenGLDraw
|
|||
// }
|
||||
}
|
||||
|
||||
public void end() {
|
||||
public void end()
|
||||
{
|
||||
// glEnd();
|
||||
// glLineWidth(1.0f);
|
||||
// glPointSize(1.0f);
|
||||
}
|
||||
|
||||
public void fog(float start, float end) {
|
||||
public void fog(float start, float end)
|
||||
{
|
||||
// glFogf(GL_FOG_START, start);
|
||||
// glFogf(GL_FOG_END, end);
|
||||
}
|
||||
|
||||
}
|
|
@ -7,7 +7,8 @@ using Silk.NET.Windowing;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Draw;
|
||||
|
||||
public class ModernOpenGLDraw : OpenGLDraw {
|
||||
public class ModernOpenGLDraw : OpenGLDraw
|
||||
{
|
||||
private GL _gl;
|
||||
private uint program;
|
||||
private int uniformTexture;
|
||||
|
@ -71,20 +72,26 @@ public class ModernOpenGLDraw : OpenGLDraw {
|
|||
_gl.CompileShader(vert_shdr);
|
||||
_gl.CompileShader(frag_shdr);
|
||||
gl.GetShader(vert_shdr, GLEnum.CompileStatus, out var status);
|
||||
if (status != (int) GLEnum.True) {
|
||||
if (status != (int)GLEnum.True)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
gl.GetShader(frag_shdr, GLEnum.CompileStatus, out status);
|
||||
if (status != (int) GLEnum.True) {
|
||||
if (status != (int)GLEnum.True)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
_gl.AttachShader(program, vert_shdr);
|
||||
_gl.AttachShader(program, frag_shdr);
|
||||
_gl.LinkProgram(program);
|
||||
_gl.GetProgram(program, GLEnum.LinkStatus, out status);
|
||||
if (status != (int) GLEnum.True) {
|
||||
if (status != (int)GLEnum.True)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
uniformTexture = _gl.GetUniformLocation(program, "Texture");
|
||||
uniformUseTexture = _gl.GetUniformLocation(program, "UseTexture");
|
||||
uniformFog = _gl.GetUniformLocation(program, "EnableFog");
|
||||
|
@ -123,7 +130,8 @@ public class ModernOpenGLDraw : OpenGLDraw {
|
|||
_gl.BindVertexArray(0);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
public void clear()
|
||||
{
|
||||
_gl.ClearColor(0.3f, 0.3f, 0.32f, 1.0f);
|
||||
_gl.Clear((uint)GLEnum.ColorBufferBit | (uint)GLEnum.DepthBufferBit);
|
||||
_gl.Enable(GLEnum.Blend);
|
||||
|
@ -133,14 +141,16 @@ public class ModernOpenGLDraw : OpenGLDraw {
|
|||
_gl.Enable(GLEnum.CullFace);
|
||||
}
|
||||
|
||||
public void begin(DebugDrawPrimitives prim, float size) {
|
||||
public void begin(DebugDrawPrimitives prim, float size)
|
||||
{
|
||||
currentPrim = prim;
|
||||
vertices.Clear();
|
||||
_gl.LineWidth(size);
|
||||
_gl.PointSize(size);
|
||||
}
|
||||
|
||||
public void end() {
|
||||
public void end()
|
||||
{
|
||||
// if (vertices.isEmpty()) {
|
||||
// return;
|
||||
// }
|
||||
|
@ -217,48 +227,58 @@ public class ModernOpenGLDraw : OpenGLDraw {
|
|||
// glPointSize(1.0f);
|
||||
}
|
||||
|
||||
public void vertex(float x, float y, float z, int color) {
|
||||
public void vertex(float x, float y, float z, int color)
|
||||
{
|
||||
vertices.Add(new OpenGLVertex(x, y, z, color));
|
||||
}
|
||||
|
||||
public void vertex(float[] pos, int color) {
|
||||
public void vertex(float[] pos, int color)
|
||||
{
|
||||
vertices.Add(new OpenGLVertex(pos, color));
|
||||
}
|
||||
|
||||
public void vertex(float[] pos, int color, float[] uv) {
|
||||
public void vertex(float[] pos, int color, float[] uv)
|
||||
{
|
||||
vertices.Add(new OpenGLVertex(pos, uv, color));
|
||||
}
|
||||
|
||||
public void vertex(float x, float y, float z, int color, float u, float v) {
|
||||
public void vertex(float x, float y, float z, int color, float u, float v)
|
||||
{
|
||||
vertices.Add(new OpenGLVertex(x, y, z, u, v, color));
|
||||
}
|
||||
|
||||
public void depthMask(bool state) {
|
||||
public void depthMask(bool state)
|
||||
{
|
||||
_gl.DepthMask(state);
|
||||
}
|
||||
|
||||
public void texture(GLCheckerTexture g_tex, bool state) {
|
||||
public void texture(GLCheckerTexture g_tex, bool state)
|
||||
{
|
||||
_texture = state ? g_tex : null;
|
||||
if (_texture != null) {
|
||||
if (_texture != null)
|
||||
{
|
||||
_texture.bind();
|
||||
}
|
||||
}
|
||||
|
||||
public void projectionMatrix(float[] projectionMatrix) {
|
||||
public void projectionMatrix(float[] projectionMatrix)
|
||||
{
|
||||
this._projectionMatrix = projectionMatrix;
|
||||
}
|
||||
|
||||
public void viewMatrix(float[] viewMatrix) {
|
||||
public void viewMatrix(float[] viewMatrix)
|
||||
{
|
||||
this._viewMatrix = viewMatrix;
|
||||
}
|
||||
|
||||
public void fog(float start, float end) {
|
||||
public void fog(float start, float end)
|
||||
{
|
||||
fogStart = start;
|
||||
fogEnd = end;
|
||||
}
|
||||
|
||||
public void fog(bool state) {
|
||||
public void fog(bool state)
|
||||
{
|
||||
fogEnabled = state;
|
||||
}
|
||||
|
||||
}
|
|
@ -27,25 +27,30 @@ using DotRecast.Recast.Demo.Settings;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Draw;
|
||||
|
||||
public class NavMeshRenderer {
|
||||
|
||||
public class NavMeshRenderer
|
||||
{
|
||||
private readonly RecastDebugDraw debugDraw;
|
||||
|
||||
private readonly int navMeshDrawFlags = RecastDebugDraw.DRAWNAVMESH_OFFMESHCONS
|
||||
| RecastDebugDraw.DRAWNAVMESH_CLOSEDLIST;
|
||||
|
||||
public NavMeshRenderer(RecastDebugDraw debugDraw) {
|
||||
public NavMeshRenderer(RecastDebugDraw debugDraw)
|
||||
{
|
||||
this.debugDraw = debugDraw;
|
||||
}
|
||||
|
||||
public RecastDebugDraw getDebugDraw() {
|
||||
public RecastDebugDraw getDebugDraw()
|
||||
{
|
||||
return debugDraw;
|
||||
}
|
||||
|
||||
public void render(Sample sample) {
|
||||
if (sample == null) {
|
||||
public void render(Sample sample)
|
||||
{
|
||||
if (sample == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NavMeshQuery navQuery = sample.getNavMeshQuery();
|
||||
DemoInputGeomProvider geom = sample.getInputGeom();
|
||||
IList<RecastBuilderResult> rcBuilderResults = sample.getRecastResults();
|
||||
|
@ -58,9 +63,11 @@ public class NavMeshRenderer {
|
|||
float texScale = 1.0f / (rcSettingsView.getCellSize() * 10.0f);
|
||||
float m_agentMaxSlope = rcSettingsView.getAgentMaxSlope();
|
||||
|
||||
if (drawMode != DrawMode.DRAWMODE_NAVMESH_TRANS) {
|
||||
if (drawMode != DrawMode.DRAWMODE_NAVMESH_TRANS)
|
||||
{
|
||||
// Draw mesh
|
||||
if (geom != null) {
|
||||
if (geom != null)
|
||||
{
|
||||
debugDraw.debugDrawTriMeshSlope(geom.vertices, geom.faces, geom.normals, m_agentMaxSlope, texScale);
|
||||
drawOffMeshConnections(geom, false);
|
||||
}
|
||||
|
@ -68,7 +75,8 @@ public class NavMeshRenderer {
|
|||
|
||||
debugDraw.fog(false);
|
||||
debugDraw.depthMask(false);
|
||||
if (geom != null) {
|
||||
if (geom != null)
|
||||
{
|
||||
drawGeomBounds(geom);
|
||||
}
|
||||
|
||||
|
@ -76,17 +84,25 @@ public class NavMeshRenderer {
|
|||
&& (drawMode == DrawMode.DRAWMODE_NAVMESH || drawMode == DrawMode.DRAWMODE_NAVMESH_TRANS
|
||||
|| drawMode == DrawMode.DRAWMODE_NAVMESH_BVTREE || drawMode == DrawMode.DRAWMODE_NAVMESH_NODES
|
||||
|| drawMode == DrawMode.DRAWMODE_NAVMESH_INVIS
|
||||
|| drawMode == DrawMode.DRAWMODE_NAVMESH_PORTALS)) {
|
||||
if (drawMode != DrawMode.DRAWMODE_NAVMESH_INVIS) {
|
||||
|| drawMode == DrawMode.DRAWMODE_NAVMESH_PORTALS))
|
||||
{
|
||||
if (drawMode != DrawMode.DRAWMODE_NAVMESH_INVIS)
|
||||
{
|
||||
debugDraw.debugDrawNavMeshWithClosedList(navMesh, navQuery, navMeshDrawFlags);
|
||||
}
|
||||
if (drawMode == DrawMode.DRAWMODE_NAVMESH_BVTREE) {
|
||||
|
||||
if (drawMode == DrawMode.DRAWMODE_NAVMESH_BVTREE)
|
||||
{
|
||||
debugDraw.debugDrawNavMeshBVTree(navMesh);
|
||||
}
|
||||
if (drawMode == DrawMode.DRAWMODE_NAVMESH_PORTALS) {
|
||||
|
||||
if (drawMode == DrawMode.DRAWMODE_NAVMESH_PORTALS)
|
||||
{
|
||||
debugDraw.debugDrawNavMeshPortals(navMesh);
|
||||
}
|
||||
if (drawMode == DrawMode.DRAWMODE_NAVMESH_NODES) {
|
||||
|
||||
if (drawMode == DrawMode.DRAWMODE_NAVMESH_NODES)
|
||||
{
|
||||
debugDraw.debugDrawNavMeshNodes(navQuery);
|
||||
debugDraw.debugDrawNavMeshPolysWithFlags(navMesh, SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED,
|
||||
DebugDraw.duRGBA(0, 0, 0, 128));
|
||||
|
@ -95,68 +111,94 @@ public class NavMeshRenderer {
|
|||
|
||||
debugDraw.depthMask(true);
|
||||
|
||||
foreach (RecastBuilderResult rcBuilderResult in rcBuilderResults) {
|
||||
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT) {
|
||||
foreach (RecastBuilderResult rcBuilderResult in rcBuilderResults)
|
||||
{
|
||||
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT)
|
||||
{
|
||||
debugDraw.debugDrawCompactHeightfieldSolid(rcBuilderResult.getCompactHeightfield());
|
||||
}
|
||||
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT_DISTANCE) {
|
||||
|
||||
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT_DISTANCE)
|
||||
{
|
||||
debugDraw.debugDrawCompactHeightfieldDistance(rcBuilderResult.getCompactHeightfield());
|
||||
}
|
||||
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT_REGIONS) {
|
||||
|
||||
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_COMPACT_REGIONS)
|
||||
{
|
||||
debugDraw.debugDrawCompactHeightfieldRegions(rcBuilderResult.getCompactHeightfield());
|
||||
}
|
||||
if (rcBuilderResult.getSolidHeightfield() != null && drawMode == DrawMode.DRAWMODE_VOXELS) {
|
||||
|
||||
if (rcBuilderResult.getSolidHeightfield() != null && drawMode == DrawMode.DRAWMODE_VOXELS)
|
||||
{
|
||||
debugDraw.fog(true);
|
||||
debugDraw.debugDrawHeightfieldSolid(rcBuilderResult.getSolidHeightfield());
|
||||
debugDraw.fog(false);
|
||||
}
|
||||
if (rcBuilderResult.getSolidHeightfield() != null && drawMode == DrawMode.DRAWMODE_VOXELS_WALKABLE) {
|
||||
|
||||
if (rcBuilderResult.getSolidHeightfield() != null && drawMode == DrawMode.DRAWMODE_VOXELS_WALKABLE)
|
||||
{
|
||||
debugDraw.fog(true);
|
||||
debugDraw.debugDrawHeightfieldWalkable(rcBuilderResult.getSolidHeightfield());
|
||||
debugDraw.fog(false);
|
||||
}
|
||||
if (rcBuilderResult.getContourSet() != null && drawMode == DrawMode.DRAWMODE_RAW_CONTOURS) {
|
||||
|
||||
if (rcBuilderResult.getContourSet() != null && drawMode == DrawMode.DRAWMODE_RAW_CONTOURS)
|
||||
{
|
||||
debugDraw.depthMask(false);
|
||||
debugDraw.debugDrawRawContours(rcBuilderResult.getContourSet(), 1f);
|
||||
debugDraw.depthMask(true);
|
||||
}
|
||||
if (rcBuilderResult.getContourSet() != null && drawMode == DrawMode.DRAWMODE_BOTH_CONTOURS) {
|
||||
|
||||
if (rcBuilderResult.getContourSet() != null && drawMode == DrawMode.DRAWMODE_BOTH_CONTOURS)
|
||||
{
|
||||
debugDraw.depthMask(false);
|
||||
debugDraw.debugDrawRawContours(rcBuilderResult.getContourSet(), 0.5f);
|
||||
debugDraw.debugDrawContours(rcBuilderResult.getContourSet());
|
||||
debugDraw.depthMask(true);
|
||||
}
|
||||
if (rcBuilderResult.getContourSet() != null && drawMode == DrawMode.DRAWMODE_CONTOURS) {
|
||||
|
||||
if (rcBuilderResult.getContourSet() != null && drawMode == DrawMode.DRAWMODE_CONTOURS)
|
||||
{
|
||||
debugDraw.depthMask(false);
|
||||
debugDraw.debugDrawContours(rcBuilderResult.getContourSet());
|
||||
debugDraw.depthMask(true);
|
||||
}
|
||||
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_REGION_CONNECTIONS) {
|
||||
|
||||
if (rcBuilderResult.getCompactHeightfield() != null && drawMode == DrawMode.DRAWMODE_REGION_CONNECTIONS)
|
||||
{
|
||||
debugDraw.debugDrawCompactHeightfieldRegions(rcBuilderResult.getCompactHeightfield());
|
||||
debugDraw.depthMask(false);
|
||||
if (rcBuilderResult.getContourSet() != null) {
|
||||
if (rcBuilderResult.getContourSet() != null)
|
||||
{
|
||||
debugDraw.debugDrawRegionConnections(rcBuilderResult.getContourSet());
|
||||
}
|
||||
|
||||
debugDraw.depthMask(true);
|
||||
}
|
||||
if (rcBuilderResult.getMesh() != null && drawMode == DrawMode.DRAWMODE_POLYMESH) {
|
||||
|
||||
if (rcBuilderResult.getMesh() != null && drawMode == DrawMode.DRAWMODE_POLYMESH)
|
||||
{
|
||||
debugDraw.depthMask(false);
|
||||
debugDraw.debugDrawPolyMesh(rcBuilderResult.getMesh());
|
||||
debugDraw.depthMask(true);
|
||||
}
|
||||
if (rcBuilderResult.getMeshDetail() != null && drawMode == DrawMode.DRAWMODE_POLYMESH_DETAIL) {
|
||||
|
||||
if (rcBuilderResult.getMeshDetail() != null && drawMode == DrawMode.DRAWMODE_POLYMESH_DETAIL)
|
||||
{
|
||||
debugDraw.depthMask(false);
|
||||
debugDraw.debugDrawPolyMeshDetail(rcBuilderResult.getMeshDetail());
|
||||
debugDraw.depthMask(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (geom != null) {
|
||||
if (geom != null)
|
||||
{
|
||||
drawConvexVolumes(geom);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawGeomBounds(DemoInputGeomProvider geom) {
|
||||
private void drawGeomBounds(DemoInputGeomProvider geom)
|
||||
{
|
||||
// Draw bounds
|
||||
float[] bmin = geom.getMeshBoundsMin();
|
||||
float[] bmax = geom.getMeshBoundsMax();
|
||||
|
@ -167,14 +209,15 @@ public class NavMeshRenderer {
|
|||
debugDraw.end();
|
||||
}
|
||||
|
||||
public void drawOffMeshConnections(DemoInputGeomProvider geom, bool hilight) {
|
||||
public void drawOffMeshConnections(DemoInputGeomProvider geom, bool hilight)
|
||||
{
|
||||
int conColor = DebugDraw.duRGBA(192, 0, 128, 192);
|
||||
int baseColor = DebugDraw.duRGBA(0, 0, 0, 64);
|
||||
debugDraw.depthMask(false);
|
||||
|
||||
debugDraw.begin(DebugDrawPrimitives.LINES, 2.0f);
|
||||
foreach (DemoOffMeshConnection con in geom.getOffMeshConnections()) {
|
||||
|
||||
foreach (DemoOffMeshConnection con in geom.getOffMeshConnections())
|
||||
{
|
||||
float[] v = con.verts;
|
||||
debugDraw.vertex(v[0], v[1], v[2], baseColor);
|
||||
debugDraw.vertex(v[0], v[1] + 0.2f, v[2], baseColor);
|
||||
|
@ -185,23 +228,28 @@ public class NavMeshRenderer {
|
|||
debugDraw.appendCircle(v[0], v[1] + 0.1f, v[2], con.radius, baseColor);
|
||||
debugDraw.appendCircle(v[3], v[4] + 0.1f, v[5], con.radius, baseColor);
|
||||
|
||||
if (hilight) {
|
||||
if (hilight)
|
||||
{
|
||||
debugDraw.appendArc(v[0], v[1], v[2], v[3], v[4], v[5], 0.25f, con.bidir ? 0.6f : 0.0f, 0.6f, conColor);
|
||||
}
|
||||
}
|
||||
|
||||
debugDraw.end();
|
||||
|
||||
debugDraw.depthMask(true);
|
||||
}
|
||||
|
||||
void drawConvexVolumes(DemoInputGeomProvider geom) {
|
||||
void drawConvexVolumes(DemoInputGeomProvider geom)
|
||||
{
|
||||
debugDraw.depthMask(false);
|
||||
|
||||
debugDraw.begin(DebugDrawPrimitives.TRIS);
|
||||
|
||||
foreach (ConvexVolume vol in geom.convexVolumes()) {
|
||||
foreach (ConvexVolume vol in geom.convexVolumes())
|
||||
{
|
||||
int col = DebugDraw.duTransCol(DebugDraw.areaToCol(vol.areaMod.getMaskedValue()), 32);
|
||||
for (int j = 0, k = vol.verts.Length - 3; j < vol.verts.Length; k = j, j += 3) {
|
||||
for (int j = 0, k = vol.verts.Length - 3; j < vol.verts.Length; k = j, j += 3)
|
||||
{
|
||||
float[] va = new float[] { vol.verts[k], vol.verts[k + 1], vol.verts[k + 2] };
|
||||
float[] vb = new float[] { vol.verts[j], vol.verts[j + 1], vol.verts[j + 2] };
|
||||
|
||||
|
@ -222,9 +270,11 @@ public class NavMeshRenderer {
|
|||
debugDraw.end();
|
||||
|
||||
debugDraw.begin(DebugDrawPrimitives.LINES, 2.0f);
|
||||
foreach (ConvexVolume vol in geom.convexVolumes()) {
|
||||
foreach (ConvexVolume vol in geom.convexVolumes())
|
||||
{
|
||||
int col = DebugDraw.duTransCol(DebugDraw.areaToCol(vol.areaMod.getMaskedValue()), 220);
|
||||
for (int j = 0, k = vol.verts.Length - 3; j < vol.verts.Length; k = j, j += 3) {
|
||||
for (int j = 0, k = vol.verts.Length - 3; j < vol.verts.Length; k = j, j += 3)
|
||||
{
|
||||
float[] va = new float[] { vol.verts[k], vol.verts[k + 1], vol.verts[k + 2] };
|
||||
float[] vb = new float[] { vol.verts[j], vol.verts[j + 1], vol.verts[j + 2] };
|
||||
debugDraw.vertex(va[0], vol.hmin, va[2], DebugDraw.duDarkenCol(col));
|
||||
|
@ -235,21 +285,24 @@ public class NavMeshRenderer {
|
|||
debugDraw.vertex(va[0], vol.hmax, va[2], col);
|
||||
}
|
||||
}
|
||||
|
||||
debugDraw.end();
|
||||
|
||||
debugDraw.begin(DebugDrawPrimitives.POINTS, 3.0f);
|
||||
foreach (ConvexVolume vol in geom.convexVolumes()) {
|
||||
foreach (ConvexVolume vol in geom.convexVolumes())
|
||||
{
|
||||
int col = DebugDraw
|
||||
.duDarkenCol(DebugDraw.duTransCol(DebugDraw.areaToCol(vol.areaMod.getMaskedValue()), 220));
|
||||
for (int j = 0; j < vol.verts.Length; j += 3) {
|
||||
for (int j = 0; j < vol.verts.Length; j += 3)
|
||||
{
|
||||
debugDraw.vertex(vol.verts[j + 0], vol.verts[j + 1] + 0.1f, vol.verts[j + 2], col);
|
||||
debugDraw.vertex(vol.verts[j + 0], vol.hmin, vol.verts[j + 2], col);
|
||||
debugDraw.vertex(vol.verts[j + 0], vol.hmax, vol.verts[j + 2], col);
|
||||
}
|
||||
}
|
||||
|
||||
debugDraw.end();
|
||||
|
||||
debugDraw.depthMask(true);
|
||||
}
|
||||
|
||||
}
|
|
@ -2,8 +2,8 @@ using Silk.NET.OpenGL;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Draw;
|
||||
|
||||
public interface OpenGLDraw {
|
||||
|
||||
public interface OpenGLDraw
|
||||
{
|
||||
void init(GL gl);
|
||||
|
||||
void clear();
|
||||
|
@ -31,5 +31,4 @@ public interface OpenGLDraw {
|
|||
void viewMatrix(float[] viewMatrix);
|
||||
|
||||
void fog(float start, float end);
|
||||
|
||||
}
|
|
@ -2,8 +2,8 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Draw;
|
||||
|
||||
public class OpenGLVertex {
|
||||
|
||||
public class OpenGLVertex
|
||||
{
|
||||
private readonly float x;
|
||||
private readonly float y;
|
||||
private readonly float z;
|
||||
|
@ -12,18 +12,22 @@ public class OpenGLVertex {
|
|||
private readonly float v;
|
||||
|
||||
public OpenGLVertex(float[] pos, float[] uv, int color) :
|
||||
this(pos[0], pos[1], pos[2], uv[0], uv[1], color) {
|
||||
this(pos[0], pos[1], pos[2], uv[0], uv[1], color)
|
||||
{
|
||||
}
|
||||
|
||||
public OpenGLVertex(float[] pos, int color) :
|
||||
this(pos[0], pos[1], pos[2], 0f, 0f, color) {
|
||||
this(pos[0], pos[1], pos[2], 0f, 0f, color)
|
||||
{
|
||||
}
|
||||
|
||||
public OpenGLVertex(float x, float y, float z, int color) :
|
||||
this(x, y, z, 0f, 0f, color) {
|
||||
this(x, y, z, 0f, 0f, color)
|
||||
{
|
||||
}
|
||||
|
||||
public OpenGLVertex(float x, float y, float z, float u, float v, int color) {
|
||||
public OpenGLVertex(float x, float y, float z, float u, float v, int color)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
|
@ -32,7 +36,8 @@ public class OpenGLVertex {
|
|||
this.color = color;
|
||||
}
|
||||
|
||||
public void store(ByteBuffer buffer) {
|
||||
public void store(ByteBuffer buffer)
|
||||
{
|
||||
buffer.putFloat(x);
|
||||
buffer.putFloat(y);
|
||||
buffer.putFloat(z);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -24,22 +24,27 @@ using DotRecast.Recast.Geom;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Geom;
|
||||
|
||||
public class ChunkyTriMesh {
|
||||
|
||||
private class BoundsItem {
|
||||
public class ChunkyTriMesh
|
||||
{
|
||||
private class BoundsItem
|
||||
{
|
||||
public readonly float[] bmin = new float[2];
|
||||
public readonly float[] bmax = new float[2];
|
||||
public int i;
|
||||
}
|
||||
|
||||
private class CompareItemX : IComparer<BoundsItem> {
|
||||
public int Compare(BoundsItem a, BoundsItem b) {
|
||||
private class CompareItemX : IComparer<BoundsItem>
|
||||
{
|
||||
public int Compare(BoundsItem a, BoundsItem b)
|
||||
{
|
||||
return a.bmin[0].CompareTo(b.bmin[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private class CompareItemY : IComparer<BoundsItem> {
|
||||
public int Compare(BoundsItem a, BoundsItem b) {
|
||||
private class CompareItemY : IComparer<BoundsItem>
|
||||
{
|
||||
public int Compare(BoundsItem a, BoundsItem b)
|
||||
{
|
||||
return a.bmin[1].CompareTo(b.bmin[1]);
|
||||
}
|
||||
}
|
||||
|
@ -48,43 +53,54 @@ public class ChunkyTriMesh {
|
|||
int ntris;
|
||||
int maxTrisPerChunk;
|
||||
|
||||
private void calcExtends(BoundsItem[] items, int imin, int imax, float[] bmin, float[] bmax) {
|
||||
private void calcExtends(BoundsItem[] items, int imin, int imax, float[] bmin, float[] bmax)
|
||||
{
|
||||
bmin[0] = items[imin].bmin[0];
|
||||
bmin[1] = items[imin].bmin[1];
|
||||
|
||||
bmax[0] = items[imin].bmax[0];
|
||||
bmax[1] = items[imin].bmax[1];
|
||||
|
||||
for (int i = imin + 1; i < imax; ++i) {
|
||||
for (int i = imin + 1; i < imax; ++i)
|
||||
{
|
||||
BoundsItem it = items[i];
|
||||
if (it.bmin[0] < bmin[0]) {
|
||||
if (it.bmin[0] < bmin[0])
|
||||
{
|
||||
bmin[0] = it.bmin[0];
|
||||
}
|
||||
if (it.bmin[1] < bmin[1]) {
|
||||
|
||||
if (it.bmin[1] < bmin[1])
|
||||
{
|
||||
bmin[1] = it.bmin[1];
|
||||
}
|
||||
|
||||
if (it.bmax[0] > bmax[0]) {
|
||||
if (it.bmax[0] > bmax[0])
|
||||
{
|
||||
bmax[0] = it.bmax[0];
|
||||
}
|
||||
if (it.bmax[1] > bmax[1]) {
|
||||
|
||||
if (it.bmax[1] > bmax[1])
|
||||
{
|
||||
bmax[1] = it.bmax[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int longestAxis(float x, float y) {
|
||||
private int longestAxis(float x, float y)
|
||||
{
|
||||
return y > x ? 1 : 0;
|
||||
}
|
||||
|
||||
private void subdivide(BoundsItem[] items, int imin, int imax, int trisPerChunk, List<ChunkyTriMeshNode> nodes,
|
||||
int[] inTris) {
|
||||
int[] inTris)
|
||||
{
|
||||
int inum = imax - imin;
|
||||
|
||||
ChunkyTriMeshNode node = new ChunkyTriMeshNode();
|
||||
nodes.Add(node);
|
||||
|
||||
if (inum <= trisPerChunk) {
|
||||
if (inum <= trisPerChunk)
|
||||
{
|
||||
// Leaf
|
||||
calcExtends(items, imin, imax, node.bmin, node.bmax);
|
||||
|
||||
|
@ -93,22 +109,28 @@ public class ChunkyTriMesh {
|
|||
node.tris = new int[inum * 3];
|
||||
|
||||
int dst = 0;
|
||||
for (int i = imin; i < imax; ++i) {
|
||||
for (int i = imin; i < imax; ++i)
|
||||
{
|
||||
int src = items[i].i * 3;
|
||||
node.tris[dst++] = inTris[src];
|
||||
node.tris[dst++] = inTris[src + 1];
|
||||
node.tris[dst++] = inTris[src + 2];
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Split
|
||||
calcExtends(items, imin, imax, node.bmin, node.bmax);
|
||||
|
||||
int axis = longestAxis(node.bmax[0] - node.bmin[0], node.bmax[1] - node.bmin[1]);
|
||||
|
||||
if (axis == 0) {
|
||||
if (axis == 0)
|
||||
{
|
||||
Array.Sort(items, imin, imax - imax, new CompareItemX());
|
||||
// Sort along x-axis
|
||||
} else if (axis == 1) {
|
||||
}
|
||||
else if (axis == 1)
|
||||
{
|
||||
Array.Sort(items, imin, imax - imin, new CompareItemY());
|
||||
// Sort along y-axis
|
||||
}
|
||||
|
@ -125,7 +147,8 @@ public class ChunkyTriMesh {
|
|||
}
|
||||
}
|
||||
|
||||
public ChunkyTriMesh(float[] verts, int[] tris, int ntris, int trisPerChunk) {
|
||||
public ChunkyTriMesh(float[] verts, int[] tris, int ntris, int trisPerChunk)
|
||||
{
|
||||
int nchunks = (ntris + trisPerChunk - 1) / trisPerChunk;
|
||||
|
||||
nodes = new(nchunks);
|
||||
|
@ -134,26 +157,34 @@ public class ChunkyTriMesh {
|
|||
// Build tree
|
||||
BoundsItem[] items = new BoundsItem[ntris];
|
||||
|
||||
for (int i = 0; i < ntris; i++) {
|
||||
for (int i = 0; i < ntris; i++)
|
||||
{
|
||||
int t = i * 3;
|
||||
BoundsItem it = items[i] = new BoundsItem();
|
||||
it.i = i;
|
||||
// Calc triangle XZ bounds.
|
||||
it.bmin[0] = it.bmax[0] = verts[tris[t] * 3 + 0];
|
||||
it.bmin[1] = it.bmax[1] = verts[tris[t] * 3 + 2];
|
||||
for (int j = 1; j < 3; ++j) {
|
||||
for (int j = 1; j < 3; ++j)
|
||||
{
|
||||
int v = tris[t + j] * 3;
|
||||
if (verts[v] < it.bmin[0]) {
|
||||
if (verts[v] < it.bmin[0])
|
||||
{
|
||||
it.bmin[0] = verts[v];
|
||||
}
|
||||
if (verts[v + 2] < it.bmin[1]) {
|
||||
|
||||
if (verts[v + 2] < it.bmin[1])
|
||||
{
|
||||
it.bmin[1] = verts[v + 2];
|
||||
}
|
||||
|
||||
if (verts[v] > it.bmax[0]) {
|
||||
if (verts[v] > it.bmax[0])
|
||||
{
|
||||
it.bmax[0] = verts[v];
|
||||
}
|
||||
if (verts[v + 2] > it.bmax[1]) {
|
||||
|
||||
if (verts[v + 2] > it.bmax[1])
|
||||
{
|
||||
it.bmax[1] = verts[v + 2];
|
||||
}
|
||||
}
|
||||
|
@ -163,70 +194,89 @@ public class ChunkyTriMesh {
|
|||
|
||||
// Calc max tris per node.
|
||||
maxTrisPerChunk = 0;
|
||||
foreach (ChunkyTriMeshNode node in nodes) {
|
||||
foreach (ChunkyTriMeshNode node in nodes)
|
||||
{
|
||||
bool isLeaf = node.i >= 0;
|
||||
if (!isLeaf) {
|
||||
if (!isLeaf)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (node.tris.Length / 3 > maxTrisPerChunk) {
|
||||
|
||||
if (node.tris.Length / 3 > maxTrisPerChunk)
|
||||
{
|
||||
maxTrisPerChunk = node.tris.Length / 3;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public List<ChunkyTriMeshNode> getChunksOverlappingRect(float[] bmin, float[] bmax) {
|
||||
public List<ChunkyTriMeshNode> getChunksOverlappingRect(float[] bmin, float[] bmax)
|
||||
{
|
||||
// Traverse tree
|
||||
List<ChunkyTriMeshNode> ids = new();
|
||||
int i = 0;
|
||||
while (i < nodes.Count) {
|
||||
while (i < nodes.Count)
|
||||
{
|
||||
ChunkyTriMeshNode node = nodes[i];
|
||||
bool overlap = checkOverlapRect(bmin, bmax, node.bmin, node.bmax);
|
||||
bool isLeafNode = node.i >= 0;
|
||||
|
||||
if (isLeafNode && overlap) {
|
||||
if (isLeafNode && overlap)
|
||||
{
|
||||
ids.Add(node);
|
||||
}
|
||||
|
||||
if (overlap || isLeafNode) {
|
||||
if (overlap || isLeafNode)
|
||||
{
|
||||
i++;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
i = -node.i;
|
||||
}
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
private bool checkOverlapRect(float[] amin, float[] amax, float[] bmin, float[] bmax) {
|
||||
private bool checkOverlapRect(float[] amin, float[] amax, float[] bmin, float[] bmax)
|
||||
{
|
||||
bool overlap = true;
|
||||
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
|
||||
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
|
||||
return overlap;
|
||||
}
|
||||
|
||||
public List<ChunkyTriMeshNode> getChunksOverlappingSegment(float[] p, float[] q) {
|
||||
public List<ChunkyTriMeshNode> getChunksOverlappingSegment(float[] p, float[] q)
|
||||
{
|
||||
// Traverse tree
|
||||
List<ChunkyTriMeshNode> ids = new();
|
||||
int i = 0;
|
||||
while (i < nodes.Count) {
|
||||
while (i < nodes.Count)
|
||||
{
|
||||
ChunkyTriMeshNode node = nodes[i];
|
||||
bool overlap = checkOverlapSegment(p, q, node.bmin, node.bmax);
|
||||
bool isLeafNode = node.i >= 0;
|
||||
|
||||
if (isLeafNode && overlap) {
|
||||
if (isLeafNode && overlap)
|
||||
{
|
||||
ids.Add(node);
|
||||
}
|
||||
|
||||
if (overlap || isLeafNode) {
|
||||
if (overlap || isLeafNode)
|
||||
{
|
||||
i++;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
i = -node.i;
|
||||
}
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
private bool checkOverlapSegment(float[] p, float[] q, float[] bmin, float[] bmax) {
|
||||
private bool checkOverlapSegment(float[] p, float[] q, float[] bmin, float[] bmax)
|
||||
{
|
||||
float EPSILON = 1e-6f;
|
||||
|
||||
float tmin = 0;
|
||||
|
@ -235,21 +285,27 @@ public class ChunkyTriMesh {
|
|||
d[0] = q[0] - p[0];
|
||||
d[1] = q[1] - p[1];
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (Math.Abs(d[i]) < EPSILON) {
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (Math.Abs(d[i]) < EPSILON)
|
||||
{
|
||||
// Ray is parallel to slab. No hit if origin not within slab
|
||||
if (p[i] < bmin[i] || p[i] > bmax[i])
|
||||
return false;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Compute intersection t value of ray with near and far plane of slab
|
||||
float ood = 1.0f / d[i];
|
||||
float t1 = (bmin[i] - p[i]) * ood;
|
||||
float t2 = (bmax[i] - p[i]) * ood;
|
||||
if (t1 > t2) {
|
||||
if (t1 > t2)
|
||||
{
|
||||
float tmp = t1;
|
||||
t1 = t2;
|
||||
t2 = tmp;
|
||||
}
|
||||
|
||||
if (t1 > tmin)
|
||||
tmin = t1;
|
||||
if (t2 < tmax)
|
||||
|
@ -258,7 +314,7 @@ public class ChunkyTriMesh {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -26,8 +26,8 @@ using DotRecast.Recast.Geom;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Geom;
|
||||
|
||||
public class DemoInputGeomProvider : InputGeomProvider {
|
||||
|
||||
public class DemoInputGeomProvider : InputGeomProvider
|
||||
{
|
||||
public readonly float[] vertices;
|
||||
public readonly int[] faces;
|
||||
public readonly float[] normals;
|
||||
|
@ -38,26 +38,34 @@ public class DemoInputGeomProvider : InputGeomProvider {
|
|||
private readonly ChunkyTriMesh chunkyTriMesh;
|
||||
|
||||
public DemoInputGeomProvider(List<float> vertexPositions, List<int> meshFaces) :
|
||||
this(mapVertices(vertexPositions), mapFaces(meshFaces)) {
|
||||
this(mapVertices(vertexPositions), mapFaces(meshFaces))
|
||||
{
|
||||
}
|
||||
|
||||
private static int[] mapFaces(List<int> meshFaces) {
|
||||
private static int[] mapFaces(List<int> meshFaces)
|
||||
{
|
||||
int[] faces = new int[meshFaces.Count];
|
||||
for (int i = 0; i < faces.Length; i++) {
|
||||
for (int i = 0; i < faces.Length; i++)
|
||||
{
|
||||
faces[i] = meshFaces[i];
|
||||
}
|
||||
|
||||
return faces;
|
||||
}
|
||||
|
||||
private static float[] mapVertices(List<float> vertexPositions) {
|
||||
private static float[] mapVertices(List<float> vertexPositions)
|
||||
{
|
||||
float[] vertices = new float[vertexPositions.Count];
|
||||
for (int i = 0; i < vertices.Length; i++) {
|
||||
for (int i = 0; i < vertices.Length; i++)
|
||||
{
|
||||
vertices[i] = vertexPositions[i];
|
||||
}
|
||||
|
||||
return vertices;
|
||||
}
|
||||
|
||||
public DemoInputGeomProvider(float[] vertices, int[] faces) {
|
||||
public DemoInputGeomProvider(float[] vertices, int[] faces)
|
||||
{
|
||||
this.vertices = vertices;
|
||||
this.faces = faces;
|
||||
normals = new float[faces.Length];
|
||||
|
@ -66,36 +74,45 @@ public class DemoInputGeomProvider : InputGeomProvider {
|
|||
bmax = new float[3];
|
||||
RecastVectors.copy(bmin, vertices, 0);
|
||||
RecastVectors.copy(bmax, vertices, 0);
|
||||
for (int i = 1; i < vertices.Length / 3; i++) {
|
||||
for (int i = 1; i < vertices.Length / 3; i++)
|
||||
{
|
||||
RecastVectors.min(bmin, vertices, i * 3);
|
||||
RecastVectors.max(bmax, vertices, i * 3);
|
||||
}
|
||||
|
||||
chunkyTriMesh = new ChunkyTriMesh(vertices, faces, faces.Length / 3, 256);
|
||||
}
|
||||
|
||||
public float[] getMeshBoundsMin() {
|
||||
public float[] getMeshBoundsMin()
|
||||
{
|
||||
return bmin;
|
||||
}
|
||||
|
||||
public float[] getMeshBoundsMax() {
|
||||
public float[] getMeshBoundsMax()
|
||||
{
|
||||
return bmax;
|
||||
}
|
||||
|
||||
public void calculateNormals() {
|
||||
for (int i = 0; i < faces.Length; i += 3) {
|
||||
public void calculateNormals()
|
||||
{
|
||||
for (int i = 0; i < faces.Length; i += 3)
|
||||
{
|
||||
int v0 = faces[i] * 3;
|
||||
int v1 = faces[i + 1] * 3;
|
||||
int v2 = faces[i + 2] * 3;
|
||||
float[] e0 = new float[3], e1 = new float[3];
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
for (int j = 0; j < 3; ++j)
|
||||
{
|
||||
e0[j] = vertices[v1 + j] - vertices[v0 + j];
|
||||
e1[j] = vertices[v2 + j] - vertices[v0 + j];
|
||||
}
|
||||
|
||||
normals[i] = e0[1] * e1[2] - e0[2] * e1[1];
|
||||
normals[i + 1] = e0[2] * e1[0] - e0[0] * e1[2];
|
||||
normals[i + 2] = e0[0] * e1[1] - e0[1] * e1[0];
|
||||
float d = (float)Math.Sqrt(normals[i] * normals[i] + normals[i + 1] * normals[i + 1] + normals[i + 2] * normals[i + 2]);
|
||||
if (d > 0) {
|
||||
if (d > 0)
|
||||
{
|
||||
d = 1.0f / d;
|
||||
normals[i] *= d;
|
||||
normals[i + 1] *= d;
|
||||
|
@ -104,34 +121,41 @@ public class DemoInputGeomProvider : InputGeomProvider {
|
|||
}
|
||||
}
|
||||
|
||||
public IList<ConvexVolume> convexVolumes() {
|
||||
public IList<ConvexVolume> convexVolumes()
|
||||
{
|
||||
return _convexVolumes;
|
||||
}
|
||||
|
||||
public IEnumerable<TriMesh> meshes() {
|
||||
public IEnumerable<TriMesh> meshes()
|
||||
{
|
||||
return ImmutableArray.Create(new TriMesh(vertices, faces));
|
||||
}
|
||||
|
||||
public List<DemoOffMeshConnection> getOffMeshConnections() {
|
||||
public List<DemoOffMeshConnection> getOffMeshConnections()
|
||||
{
|
||||
return offMeshConnections;
|
||||
}
|
||||
|
||||
public void addOffMeshConnection(float[] start, float[] end, float radius, bool bidir, int area, int flags) {
|
||||
public void addOffMeshConnection(float[] start, float[] end, float radius, bool bidir, int area, int flags)
|
||||
{
|
||||
offMeshConnections.Add(new DemoOffMeshConnection(start, end, radius, bidir, area, flags));
|
||||
}
|
||||
|
||||
public void removeOffMeshConnections(Predicate<DemoOffMeshConnection> filter) {
|
||||
public void removeOffMeshConnections(Predicate<DemoOffMeshConnection> filter)
|
||||
{
|
||||
//offMeshConnections.retainAll(offMeshConnections.stream().filter(c -> !filter.test(c)).collect(toList()));
|
||||
offMeshConnections.RemoveAll(filter); // TODO : 확인 필요
|
||||
}
|
||||
|
||||
public float? raycastMesh(float[] src, float[] dst) {
|
||||
|
||||
public float? raycastMesh(float[] src, float[] dst)
|
||||
{
|
||||
// Prune hit ray.
|
||||
float[] btminmax = Intersections.intersectSegmentAABB(src, dst, bmin, bmax);
|
||||
if (null == btminmax) {
|
||||
if (null == btminmax)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
float btmin = btminmax[0];
|
||||
float btmax = btminmax[1];
|
||||
float[] p = new float[2], q = new float[2];
|
||||
|
@ -141,26 +165,41 @@ public class DemoInputGeomProvider : InputGeomProvider {
|
|||
q[1] = src[2] + (dst[2] - src[2]) * btmax;
|
||||
|
||||
List<ChunkyTriMeshNode> chunks = chunkyTriMesh.getChunksOverlappingSegment(p, q);
|
||||
if (0 == chunks.Count) {
|
||||
if (0 == chunks.Count)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
float tmin = 1.0f;
|
||||
bool hit = false;
|
||||
foreach (ChunkyTriMeshNode chunk in chunks) {
|
||||
foreach (ChunkyTriMeshNode chunk in chunks)
|
||||
{
|
||||
int[] tris = chunk.tris;
|
||||
for (int j = 0; j < chunk.tris.Length; j += 3) {
|
||||
float[] v1 = new float[] { vertices[tris[j] * 3], vertices[tris[j] * 3 + 1],
|
||||
vertices[tris[j] * 3 + 2] };
|
||||
float[] v2 = new float[] { vertices[tris[j + 1] * 3], vertices[tris[j + 1] * 3 + 1],
|
||||
vertices[tris[j + 1] * 3 + 2] };
|
||||
float[] v3 = new float[] { vertices[tris[j + 2] * 3], vertices[tris[j + 2] * 3 + 1],
|
||||
vertices[tris[j + 2] * 3 + 2] };
|
||||
for (int j = 0; j < chunk.tris.Length; j += 3)
|
||||
{
|
||||
float[] v1 = new float[]
|
||||
{
|
||||
vertices[tris[j] * 3], vertices[tris[j] * 3 + 1],
|
||||
vertices[tris[j] * 3 + 2]
|
||||
};
|
||||
float[] v2 = new float[]
|
||||
{
|
||||
vertices[tris[j + 1] * 3], vertices[tris[j + 1] * 3 + 1],
|
||||
vertices[tris[j + 1] * 3 + 2]
|
||||
};
|
||||
float[] v3 = new float[]
|
||||
{
|
||||
vertices[tris[j + 2] * 3], vertices[tris[j + 2] * 3 + 1],
|
||||
vertices[tris[j + 2] * 3 + 2]
|
||||
};
|
||||
float? t = Intersections.intersectSegmentTriangle(src, dst, v1, v2, v3);
|
||||
if (null != t) {
|
||||
if (t.Value < tmin) {
|
||||
if (null != t)
|
||||
{
|
||||
if (t.Value < tmin)
|
||||
{
|
||||
tmin = t.Value;
|
||||
}
|
||||
|
||||
hit = true;
|
||||
}
|
||||
}
|
||||
|
@ -170,7 +209,8 @@ public class DemoInputGeomProvider : InputGeomProvider {
|
|||
}
|
||||
|
||||
|
||||
public void addConvexVolume(float[] verts, float minh, float maxh, AreaModification areaMod) {
|
||||
public void addConvexVolume(float[] verts, float minh, float maxh, AreaModification areaMod)
|
||||
{
|
||||
ConvexVolume volume = new ConvexVolume();
|
||||
volume.verts = verts;
|
||||
volume.hmin = minh;
|
||||
|
@ -179,8 +219,8 @@ public class DemoInputGeomProvider : InputGeomProvider {
|
|||
_convexVolumes.Add(volume);
|
||||
}
|
||||
|
||||
public void clearConvexVolumes() {
|
||||
public void clearConvexVolumes()
|
||||
{
|
||||
_convexVolumes.Clear();
|
||||
}
|
||||
|
||||
}
|
|
@ -17,17 +17,19 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Recast.Demo.Geom;
|
||||
|
||||
public class DemoOffMeshConnection {
|
||||
|
||||
public class DemoOffMeshConnection
|
||||
{
|
||||
public readonly float[] verts;
|
||||
public readonly float radius;
|
||||
public readonly bool bidir;
|
||||
public readonly int area;
|
||||
public readonly int flags;
|
||||
|
||||
public DemoOffMeshConnection(float[] start, float[] end, float radius, bool bidir, int area, int flags) {
|
||||
public DemoOffMeshConnection(float[] start, float[] end, float radius, bool bidir, int area, int flags)
|
||||
{
|
||||
verts = new float[6];
|
||||
verts[0] = start[0];
|
||||
verts[1] = start[1];
|
||||
|
@ -40,5 +42,4 @@ public class DemoOffMeshConnection {
|
|||
this.area = area;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
}
|
|
@ -22,9 +22,10 @@ using static DotRecast.Detour.DetourCommon;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Geom;
|
||||
|
||||
public class Intersections {
|
||||
|
||||
public static float? intersectSegmentTriangle(float[] sp, float[] sq, float[] a, float[] b, float[] c) {
|
||||
public class Intersections
|
||||
{
|
||||
public static float? intersectSegmentTriangle(float[] sp, float[] sq, float[] a, float[] b, float[] c)
|
||||
{
|
||||
float v, w;
|
||||
float[] ab = vSub(b, a);
|
||||
float[] ac = vSub(c, a);
|
||||
|
@ -37,7 +38,8 @@ public class Intersections {
|
|||
// Compute denominator d. If d <= 0, segment is parallel to or points
|
||||
// away from triangle, so exit early
|
||||
float d = DemoMath.vDot(qp, norm);
|
||||
if (d <= 0.0f) {
|
||||
if (d <= 0.0f)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -46,21 +48,27 @@ public class Intersections {
|
|||
// dividing by d until intersection has been found to pierce triangle
|
||||
float[] ap = vSub(sp, a);
|
||||
float t = DemoMath.vDot(ap, norm);
|
||||
if (t < 0.0f) {
|
||||
if (t < 0.0f)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (t > d) {
|
||||
|
||||
if (t > d)
|
||||
{
|
||||
return null; // For segment; exclude this code line for a ray test
|
||||
}
|
||||
|
||||
// Compute barycentric coordinate components and test if within bounds
|
||||
float[] e = DemoMath.vCross(qp, ap);
|
||||
v = DemoMath.vDot(ac, e);
|
||||
if (v < 0.0f || v > d) {
|
||||
if (v < 0.0f || v > d)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
w = -DemoMath.vDot(ab, e);
|
||||
if (w < 0.0f || v + w > d) {
|
||||
if (w < 0.0f || v + w > d)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -70,8 +78,8 @@ public class Intersections {
|
|||
return t;
|
||||
}
|
||||
|
||||
public static float[] intersectSegmentAABB(float[] sp, float[] sq, float[] amin, float[] amax) {
|
||||
|
||||
public static float[] intersectSegmentAABB(float[] sp, float[] sq, float[] amin, float[] amax)
|
||||
{
|
||||
float EPS = 1e-6f;
|
||||
|
||||
float[] d = new float[3];
|
||||
|
@ -81,27 +89,39 @@ public class Intersections {
|
|||
float tmin = 0.0f;
|
||||
float tmax = 1.0f;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (Math.Abs(d[i]) < EPS) {
|
||||
if (sp[i] < amin[i] || sp[i] > amax[i]) {
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (Math.Abs(d[i]) < EPS)
|
||||
{
|
||||
if (sp[i] < amin[i] || sp[i] > amax[i])
|
||||
{
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
float ood = 1.0f / d[i];
|
||||
float t1 = (amin[i] - sp[i]) * ood;
|
||||
float t2 = (amax[i] - sp[i]) * ood;
|
||||
if (t1 > t2) {
|
||||
if (t1 > t2)
|
||||
{
|
||||
float tmp = t1;
|
||||
t1 = t2;
|
||||
t2 = tmp;
|
||||
}
|
||||
if (t1 > tmin) {
|
||||
|
||||
if (t1 > tmin)
|
||||
{
|
||||
tmin = t1;
|
||||
}
|
||||
if (t2 < tmax) {
|
||||
|
||||
if (t2 < tmax)
|
||||
{
|
||||
tmax = t2;
|
||||
}
|
||||
if (tmin > tmax) {
|
||||
|
||||
if (tmin > tmax)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -109,5 +129,4 @@ public class Intersections {
|
|||
|
||||
return new float[] { tmin, tmax };
|
||||
}
|
||||
|
||||
}
|
|
@ -24,14 +24,18 @@ namespace DotRecast.Recast.Demo.Geom;
|
|||
/**
|
||||
* Simple helper to find an intersection between a ray and a nav mesh
|
||||
*/
|
||||
public class NavMeshRaycast {
|
||||
|
||||
public static float? raycast(NavMesh mesh, float[] src, float[]dst) {
|
||||
for (int t = 0; t < mesh.getMaxTiles(); ++t) {
|
||||
public class NavMeshRaycast
|
||||
{
|
||||
public static float? raycast(NavMesh mesh, float[] src, float[] dst)
|
||||
{
|
||||
for (int t = 0; t < mesh.getMaxTiles(); ++t)
|
||||
{
|
||||
MeshTile tile = mesh.getTile(t);
|
||||
if (tile != null && tile.data != null) {
|
||||
if (tile != null && tile.data != null)
|
||||
{
|
||||
float? intersection = raycast(tile, src, dst);
|
||||
if (null != intersection) {
|
||||
if (null != intersection)
|
||||
{
|
||||
return intersection;
|
||||
}
|
||||
}
|
||||
|
@ -40,36 +44,50 @@ public class NavMeshRaycast {
|
|||
return null;
|
||||
}
|
||||
|
||||
private static float? raycast(MeshTile tile, float[] sp, float[]sq) {
|
||||
for (int i = 0; i < tile.data.header.polyCount; ++i) {
|
||||
private static float? raycast(MeshTile tile, float[] sp, float[] sq)
|
||||
{
|
||||
for (int i = 0; i < tile.data.header.polyCount; ++i)
|
||||
{
|
||||
Poly p = tile.data.polys[i];
|
||||
if (p.getType() == Poly.DT_POLYTYPE_OFFMESH_CONNECTION) {
|
||||
if (p.getType() == Poly.DT_POLYTYPE_OFFMESH_CONNECTION)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PolyDetail pd = tile.data.detailMeshes[i];
|
||||
|
||||
if (pd != null) {
|
||||
if (pd != null)
|
||||
{
|
||||
float[][] verts = ArrayUtils.Of<float>(3, 3);
|
||||
for (int j = 0; j < pd.triCount; ++j) {
|
||||
for (int j = 0; j < pd.triCount; ++j)
|
||||
{
|
||||
int t = (pd.triBase + j) * 4;
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
for (int k = 0; k < 3; ++k)
|
||||
{
|
||||
int v = tile.data.detailTris[t + k];
|
||||
if (v < p.vertCount) {
|
||||
if (v < p.vertCount)
|
||||
{
|
||||
verts[k][0] = tile.data.verts[p.verts[v] * 3];
|
||||
verts[k][1] = tile.data.verts[p.verts[v] * 3 + 1];
|
||||
verts[k][2] = tile.data.verts[p.verts[v] * 3 + 2];
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
verts[k][0] = tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3];
|
||||
verts[k][1] = tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 1];
|
||||
verts[k][2] = tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 2];
|
||||
}
|
||||
}
|
||||
|
||||
float? intersection = Intersections.intersectSegmentTriangle(sp, sq, verts[0], verts[1], verts[2]);
|
||||
if (null != intersection) {
|
||||
if (null != intersection)
|
||||
{
|
||||
return intersection;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME: Use Poly if PolyDetail is unavailable
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,15 +21,19 @@ using DotRecast.Detour;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Geom;
|
||||
|
||||
public class NavMeshUtils {
|
||||
|
||||
public static float[][] getNavMeshBounds(NavMesh mesh) {
|
||||
public class NavMeshUtils
|
||||
{
|
||||
public static float[][] getNavMeshBounds(NavMesh mesh)
|
||||
{
|
||||
float[] bmin = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity };
|
||||
float[] bmax = new float[] { float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity };
|
||||
for (int t = 0; t < mesh.getMaxTiles(); ++t) {
|
||||
for (int t = 0; t < mesh.getMaxTiles(); ++t)
|
||||
{
|
||||
MeshTile tile = mesh.getTile(t);
|
||||
if (tile != null && tile.data != null) {
|
||||
for (int i = 0; i < tile.data.verts.Length; i += 3) {
|
||||
if (tile != null && tile.data != null)
|
||||
{
|
||||
for (int i = 0; i < tile.data.verts.Length; i += 3)
|
||||
{
|
||||
bmin[0] = Math.Min(bmin[0], tile.data.verts[i]);
|
||||
bmin[1] = Math.Min(bmin[1], tile.data.verts[i + 1]);
|
||||
bmin[2] = Math.Min(bmin[2], tile.data.verts[i + 2]);
|
||||
|
@ -39,6 +43,7 @@ public class NavMeshUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new float[][] { bmin, bmax };
|
||||
}
|
||||
}
|
|
@ -21,13 +21,17 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Geom;
|
||||
|
||||
public class PolyMeshRaycast {
|
||||
|
||||
public static float? raycast(IList<RecastBuilderResult> results, float[] src, float[] dst) {
|
||||
foreach (RecastBuilderResult result in results) {
|
||||
if (result.getMeshDetail() != null) {
|
||||
public class PolyMeshRaycast
|
||||
{
|
||||
public static float? raycast(IList<RecastBuilderResult> results, float[] src, float[] dst)
|
||||
{
|
||||
foreach (RecastBuilderResult result in results)
|
||||
{
|
||||
if (result.getMeshDetail() != null)
|
||||
{
|
||||
float? intersection = raycast(result.getMesh(), result.getMeshDetail(), src, dst);
|
||||
if (null != intersection) {
|
||||
if (null != intersection)
|
||||
{
|
||||
return intersection;
|
||||
}
|
||||
}
|
||||
|
@ -36,29 +40,38 @@ public class PolyMeshRaycast {
|
|||
return null;
|
||||
}
|
||||
|
||||
private static float? raycast(PolyMesh poly, PolyMeshDetail meshDetail, float[] sp, float[] sq) {
|
||||
if (meshDetail != null) {
|
||||
for (int i = 0; i < meshDetail.nmeshes; ++i) {
|
||||
private static float? raycast(PolyMesh poly, PolyMeshDetail meshDetail, float[] sp, float[] sq)
|
||||
{
|
||||
if (meshDetail != null)
|
||||
{
|
||||
for (int i = 0; i < meshDetail.nmeshes; ++i)
|
||||
{
|
||||
int m = i * 4;
|
||||
int bverts = meshDetail.meshes[m];
|
||||
int btris = meshDetail.meshes[m + 2];
|
||||
int ntris = meshDetail.meshes[m + 3];
|
||||
int verts = bverts * 3;
|
||||
int tris = btris * 4;
|
||||
for (int j = 0; j < ntris; ++j) {
|
||||
for (int j = 0; j < ntris; ++j)
|
||||
{
|
||||
float[][] vs = ArrayUtils.Of<float>(3, 3);
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
for (int k = 0; k < 3; ++k)
|
||||
{
|
||||
vs[k][0] = meshDetail.verts[verts + meshDetail.tris[tris + j * 4 + k] * 3];
|
||||
vs[k][1] = meshDetail.verts[verts + meshDetail.tris[tris + j * 4 + k] * 3 + 1];
|
||||
vs[k][2] = meshDetail.verts[verts + meshDetail.tris[tris + j * 4 + k] * 3 + 2];
|
||||
}
|
||||
|
||||
float? intersection = Intersections.intersectSegmentTriangle(sp, sq, vs[0], vs[1], vs[2]);
|
||||
if (null != intersection) {
|
||||
if (null != intersection)
|
||||
{
|
||||
return intersection;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: check PolyMesh instead
|
||||
}
|
||||
|
||||
|
|
|
@ -26,8 +26,8 @@ using DotRecast.Recast.Demo.Settings;
|
|||
|
||||
namespace DotRecast.Recast.Demo;
|
||||
|
||||
public class Sample {
|
||||
|
||||
public class Sample
|
||||
{
|
||||
private DemoInputGeomProvider inputGeom;
|
||||
private NavMesh navMesh;
|
||||
private NavMeshQuery navMeshQuery;
|
||||
|
@ -36,7 +36,8 @@ public class Sample {
|
|||
private bool changed;
|
||||
|
||||
public Sample(DemoInputGeomProvider inputGeom, IList<RecastBuilderResult> recastResults, NavMesh navMesh,
|
||||
RcSettingsView rcSettingsView, RecastDebugDraw debugDraw) {
|
||||
RcSettingsView rcSettingsView, RecastDebugDraw debugDraw)
|
||||
{
|
||||
this.inputGeom = inputGeom;
|
||||
this.recastResults = recastResults;
|
||||
this.navMesh = navMesh;
|
||||
|
@ -45,39 +46,48 @@ public class Sample {
|
|||
changed = true;
|
||||
}
|
||||
|
||||
private void setQuery(NavMesh navMesh) {
|
||||
private void setQuery(NavMesh navMesh)
|
||||
{
|
||||
navMeshQuery = navMesh != null ? new NavMeshQuery(navMesh) : null;
|
||||
}
|
||||
|
||||
public DemoInputGeomProvider getInputGeom() {
|
||||
public DemoInputGeomProvider getInputGeom()
|
||||
{
|
||||
return inputGeom;
|
||||
}
|
||||
|
||||
public IList<RecastBuilderResult> getRecastResults() {
|
||||
public IList<RecastBuilderResult> getRecastResults()
|
||||
{
|
||||
return recastResults;
|
||||
}
|
||||
|
||||
public NavMesh getNavMesh() {
|
||||
public NavMesh getNavMesh()
|
||||
{
|
||||
return navMesh;
|
||||
}
|
||||
|
||||
public RcSettingsView getSettingsUI() {
|
||||
public RcSettingsView getSettingsUI()
|
||||
{
|
||||
return _rcSettingsView;
|
||||
}
|
||||
|
||||
public NavMeshQuery getNavMeshQuery() {
|
||||
public NavMeshQuery getNavMeshQuery()
|
||||
{
|
||||
return navMeshQuery;
|
||||
}
|
||||
|
||||
public bool isChanged() {
|
||||
public bool isChanged()
|
||||
{
|
||||
return changed;
|
||||
}
|
||||
|
||||
public void setChanged(bool changed) {
|
||||
public void setChanged(bool changed)
|
||||
{
|
||||
this.changed = changed;
|
||||
}
|
||||
|
||||
public void update(DemoInputGeomProvider geom, IList<RecastBuilderResult> recastResults, NavMesh navMesh) {
|
||||
public void update(DemoInputGeomProvider geom, IList<RecastBuilderResult> recastResults, NavMesh navMesh)
|
||||
{
|
||||
inputGeom = geom;
|
||||
this.recastResults = recastResults;
|
||||
this.navMesh = navMesh;
|
||||
|
|
|
@ -21,19 +21,17 @@ freely, subject to the following restrictions:
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Silk.NET.Windowing;
|
||||
|
||||
using DotRecast.Core;
|
||||
using DotRecast.Recast.Demo.Builder;
|
||||
using DotRecast.Recast.Demo.Draw;
|
||||
using DotRecast.Recast.Demo.Geom;
|
||||
|
||||
using static DotRecast.Recast.Demo.Draw.DebugDraw;
|
||||
using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives;
|
||||
|
||||
namespace DotRecast.Recast.Demo.Tools;
|
||||
|
||||
public class ConvexVolumeTool : Tool {
|
||||
|
||||
public class ConvexVolumeTool : Tool
|
||||
{
|
||||
private Sample sample;
|
||||
private AreaModification areaType = SampleAreaModifications.SAMPLE_AREAMOD_GRASS;
|
||||
private readonly float[] boxHeight = new[] { 6f };
|
||||
|
@ -42,105 +40,137 @@ public class ConvexVolumeTool : Tool {
|
|||
private readonly List<float> pts = new();
|
||||
private readonly List<int> hull = new();
|
||||
|
||||
public override void setSample(Sample m_sample) {
|
||||
public override void setSample(Sample m_sample)
|
||||
{
|
||||
sample = m_sample;
|
||||
}
|
||||
|
||||
public override void handleClick(float[] s, float[] p, bool shift) {
|
||||
public override void handleClick(float[] s, float[] p, bool shift)
|
||||
{
|
||||
DemoInputGeomProvider geom = sample.getInputGeom();
|
||||
if (geom == null) {
|
||||
if (geom == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (shift) {
|
||||
if (shift)
|
||||
{
|
||||
// Delete
|
||||
int nearestIndex = -1;
|
||||
IList<ConvexVolume> vols = geom.convexVolumes();
|
||||
for (int i = 0; i < vols.Count; ++i) {
|
||||
for (int i = 0; i < vols.Count; ++i)
|
||||
{
|
||||
if (PolyUtils.pointInPoly(vols[i].verts, p) && p[1] >= vols[i].hmin
|
||||
&& p[1] <= vols[i].hmax) {
|
||||
&& p[1] <= vols[i].hmax)
|
||||
{
|
||||
nearestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// If end point close enough, delete it.
|
||||
if (nearestIndex != -1) {
|
||||
if (nearestIndex != -1)
|
||||
{
|
||||
geom.convexVolumes().RemoveAt(nearestIndex);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create
|
||||
|
||||
// If clicked on that last pt, create the shape.
|
||||
if (pts.Count > 0 && DemoMath.vDistSqr(p,
|
||||
new float[] { pts[pts.Count - 3], pts[pts.Count - 2], pts[pts.Count - 1] },
|
||||
0) < 0.2f * 0.2f) {
|
||||
if (hull.Count > 2) {
|
||||
0) < 0.2f * 0.2f)
|
||||
{
|
||||
if (hull.Count > 2)
|
||||
{
|
||||
// Create shape.
|
||||
float[] verts = new float[hull.Count * 3];
|
||||
for (int i = 0; i < hull.Count; ++i) {
|
||||
for (int i = 0; i < hull.Count; ++i)
|
||||
{
|
||||
verts[i * 3] = pts[hull[i] * 3];
|
||||
verts[i * 3 + 1] = pts[hull[i] * 3 + 1];
|
||||
verts[i * 3 + 2] = pts[hull[i] * 3 + 2];
|
||||
}
|
||||
|
||||
float minh = float.MaxValue, maxh = 0;
|
||||
for (int i = 0; i < hull.Count; ++i) {
|
||||
for (int i = 0; i < hull.Count; ++i)
|
||||
{
|
||||
minh = Math.Min(minh, verts[i * 3 + 1]);
|
||||
}
|
||||
|
||||
minh -= boxDescent[0];
|
||||
maxh = minh + boxHeight[0];
|
||||
|
||||
if (polyOffset[0] > 0.01f) {
|
||||
if (polyOffset[0] > 0.01f)
|
||||
{
|
||||
float[] offset = new float[verts.Length * 2];
|
||||
int noffset = PolyUtils.offsetPoly(verts, hull.Count, polyOffset[0], offset,
|
||||
offset.Length);
|
||||
if (noffset > 0) {
|
||||
if (noffset > 0)
|
||||
{
|
||||
geom.addConvexVolume(ArrayUtils.CopyOf(offset, 0, noffset * 3), minh, maxh, areaType);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
geom.addConvexVolume(verts, minh, maxh, areaType);
|
||||
}
|
||||
}
|
||||
|
||||
pts.Clear();
|
||||
hull.Clear();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add new point
|
||||
pts.Add(p[0]);
|
||||
pts.Add(p[1]);
|
||||
pts.Add(p[2]);
|
||||
// Update hull.
|
||||
if (pts.Count > 3) {
|
||||
if (pts.Count > 3)
|
||||
{
|
||||
hull.Clear();
|
||||
hull.AddRange(ConvexUtils.convexhull(pts));
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
hull.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void handleRender(NavMeshRenderer renderer) {
|
||||
public override void handleRender(NavMeshRenderer renderer)
|
||||
{
|
||||
RecastDebugDraw dd = renderer.getDebugDraw();
|
||||
// Find height extent of the shape.
|
||||
float minh = float.MaxValue, maxh = 0;
|
||||
for (int i = 0; i < pts.Count; i += 3) {
|
||||
for (int i = 0; i < pts.Count; i += 3)
|
||||
{
|
||||
minh = Math.Min(minh, pts[i + 1]);
|
||||
}
|
||||
|
||||
minh -= boxDescent[0];
|
||||
maxh = minh + boxHeight[0];
|
||||
|
||||
dd.begin(POINTS, 4.0f);
|
||||
for (int i = 0; i < pts.Count; i += 3) {
|
||||
for (int i = 0; i < pts.Count; i += 3)
|
||||
{
|
||||
int col = duRGBA(255, 255, 255, 255);
|
||||
if (i == pts.Count - 3) {
|
||||
if (i == pts.Count - 3)
|
||||
{
|
||||
col = duRGBA(240, 32, 16, 255);
|
||||
}
|
||||
|
||||
dd.vertex(pts[i + 0], pts[i + 1] + 0.1f, pts[i + 2], col);
|
||||
}
|
||||
|
||||
dd.end();
|
||||
|
||||
dd.begin(LINES, 2.0f);
|
||||
for (int i = 0, j = hull.Count - 1; i < hull.Count; j = i++) {
|
||||
for (int i = 0, j = hull.Count - 1; i < hull.Count; j = i++)
|
||||
{
|
||||
int vi = hull[j] * 3;
|
||||
int vj = hull[i] * 3;
|
||||
dd.vertex(pts[vj + 0], minh, pts[vj + 2], duRGBA(255, 255, 255, 64));
|
||||
|
@ -150,10 +180,12 @@ public class ConvexVolumeTool : Tool {
|
|||
dd.vertex(pts[vj + 0], minh, pts[vj + 2], duRGBA(255, 255, 255, 64));
|
||||
dd.vertex(pts[vj + 0], maxh, pts[vj + 2], duRGBA(255, 255, 255, 64));
|
||||
}
|
||||
|
||||
dd.end();
|
||||
}
|
||||
|
||||
public override void layout(IWindow ctx) {
|
||||
public override void layout(IWindow ctx)
|
||||
{
|
||||
// nk_layout_row_dynamic(ctx, 20, 1);
|
||||
// nk_property_float(ctx, "Shape Height", 0.1f, boxHeight, 20f, 0.1f, 0.1f);
|
||||
// nk_layout_row_dynamic(ctx, 20, 1);
|
||||
|
@ -194,12 +226,13 @@ public class ConvexVolumeTool : Tool {
|
|||
// }
|
||||
}
|
||||
|
||||
public override string getName() {
|
||||
public override string getName()
|
||||
{
|
||||
return "Create Convex Volumes";
|
||||
}
|
||||
|
||||
public override void handleUpdate(float dt) {
|
||||
public override void handleUpdate(float dt)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
}
|
|
@ -25,13 +25,12 @@ using DotRecast.Detour.Crowd;
|
|||
using DotRecast.Recast.Demo.Builder;
|
||||
using DotRecast.Recast.Demo.Draw;
|
||||
using Silk.NET.Windowing;
|
||||
|
||||
using static DotRecast.Recast.Demo.Draw.DebugDraw;
|
||||
|
||||
namespace DotRecast.Recast.Demo.Tools;
|
||||
|
||||
public class CrowdProfilingTool {
|
||||
|
||||
public class CrowdProfilingTool
|
||||
{
|
||||
private readonly Func<CrowdAgentParams> agentParamsSupplier;
|
||||
private readonly int[] expandSimOptions = new[] { 1 };
|
||||
private readonly int[] expandCrowdOptions = new[] { 1 };
|
||||
|
@ -50,11 +49,13 @@ public class CrowdProfilingTool {
|
|||
private readonly List<FindRandomPointResult> zones = new();
|
||||
private long crowdUpdateTime;
|
||||
|
||||
public CrowdProfilingTool(Func<CrowdAgentParams> agentParamsSupplier) {
|
||||
public CrowdProfilingTool(Func<CrowdAgentParams> agentParamsSupplier)
|
||||
{
|
||||
this.agentParamsSupplier = agentParamsSupplier;
|
||||
}
|
||||
|
||||
public void layout(IWindow ctx) {
|
||||
public void layout(IWindow ctx)
|
||||
{
|
||||
// nk_layout_row_dynamic(ctx, 1, 1);
|
||||
// nk_spacing(ctx, 1);
|
||||
// if (nk_tree_state_push(ctx, 0, "Simulation Options", expandSimOptions)) {
|
||||
|
@ -141,43 +142,58 @@ public class CrowdProfilingTool {
|
|||
// }
|
||||
}
|
||||
|
||||
private float[] getMobPosition(NavMeshQuery navquery, QueryFilter filter, float[] pos) {
|
||||
private float[] getMobPosition(NavMeshQuery navquery, QueryFilter filter, float[] pos)
|
||||
{
|
||||
Result<FindRandomPointResult> result = navquery.findRandomPoint(filter, rnd);
|
||||
if (result.succeeded()) {
|
||||
if (result.succeeded())
|
||||
{
|
||||
pos = result.result.getRandomPt();
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
private float[] getVillagerPosition(NavMeshQuery navquery, QueryFilter filter, float[] pos) {
|
||||
if (0 < zones.Count) {
|
||||
private float[] getVillagerPosition(NavMeshQuery navquery, QueryFilter filter, float[] pos)
|
||||
{
|
||||
if (0 < zones.Count)
|
||||
{
|
||||
int zone = (int)(rnd.frand() * zones.Count);
|
||||
Result<FindRandomPointResult> result = navquery.findRandomPointWithinCircle(zones[zone].getRandomRef(),
|
||||
zones[zone].getRandomPt(), zoneRadius[0], filter, rnd);
|
||||
if (result.succeeded()) {
|
||||
if (result.succeeded())
|
||||
{
|
||||
pos = result.result.getRandomPt();
|
||||
}
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
private void createZones() {
|
||||
private void createZones()
|
||||
{
|
||||
zones.Clear();
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
NavMeshQuery navquery = new NavMeshQuery(navMesh);
|
||||
for (int i = 0; i < numberOfZones[0]; i++) {
|
||||
for (int i = 0; i < numberOfZones[0]; i++)
|
||||
{
|
||||
float zoneSeparation = zoneRadius[0] * zoneRadius[0] * 16;
|
||||
for (int k = 0; k < 100; k++) {
|
||||
for (int k = 0; k < 100; k++)
|
||||
{
|
||||
Result<FindRandomPointResult> result = navquery.findRandomPoint(filter, rnd);
|
||||
if (result.succeeded()) {
|
||||
if (result.succeeded())
|
||||
{
|
||||
bool valid = true;
|
||||
foreach (FindRandomPointResult zone in zones) {
|
||||
if (DemoMath.vDistSqr(zone.getRandomPt(), result.result.getRandomPt(), 0) < zoneSeparation) {
|
||||
foreach (FindRandomPointResult zone in zones)
|
||||
{
|
||||
if (DemoMath.vDistSqr(zone.getRandomPt(), result.result.getRandomPt(), 0) < zoneSeparation)
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (valid) {
|
||||
|
||||
if (valid)
|
||||
{
|
||||
zones.Add(result.result);
|
||||
break;
|
||||
}
|
||||
|
@ -186,7 +202,8 @@ public class CrowdProfilingTool {
|
|||
}
|
||||
}
|
||||
|
||||
private void createCrowd() {
|
||||
private void createCrowd()
|
||||
{
|
||||
crowd = new Crowd(config, navMesh, __ => new DefaultQueryFilter(SampleAreaModifications.SAMPLE_POLYFLAGS_ALL,
|
||||
SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED, new float[] { 1f, 10f, 1f, 1f, 2f, 1.5f }));
|
||||
|
||||
|
@ -217,22 +234,28 @@ public class CrowdProfilingTool {
|
|||
crowd.setObstacleAvoidanceParams(3, option);
|
||||
}
|
||||
|
||||
public void update(float dt) {
|
||||
public void update(float dt)
|
||||
{
|
||||
long startTime = Stopwatch.GetTimestamp();
|
||||
if (crowd != null) {
|
||||
if (crowd != null)
|
||||
{
|
||||
crowd.config().pathQueueSize = pathQueueSize[0];
|
||||
crowd.config().maxFindPathIterations = maxIterations[0];
|
||||
crowd.update(dt, null);
|
||||
}
|
||||
long endTime = Stopwatch.GetTimestamp();
|
||||
if (crowd != null) {
|
||||
|
||||
long endTime = Stopwatch.GetTimestamp();
|
||||
if (crowd != null)
|
||||
{
|
||||
NavMeshQuery navquery = new NavMeshQuery(navMesh);
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
if (needsNewTarget(ag)) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
if (needsNewTarget(ag))
|
||||
{
|
||||
AgentData agentData = (AgentData)ag.option.userData;
|
||||
switch (agentData.type) {
|
||||
switch (agentData.type)
|
||||
{
|
||||
case AgentType.MOB:
|
||||
moveMob(navquery, filter, ag, agentData);
|
||||
break;
|
||||
|
@ -246,79 +269,102 @@ public class CrowdProfilingTool {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
crowdUpdateTime = (endTime - startTime) / 1_000_000;
|
||||
}
|
||||
|
||||
private void moveMob(NavMeshQuery navquery, QueryFilter filter, CrowdAgent ag, AgentData agentData) {
|
||||
private void moveMob(NavMeshQuery navquery, QueryFilter filter, CrowdAgent ag, AgentData agentData)
|
||||
{
|
||||
// Move somewhere
|
||||
Result<FindNearestPolyResult> nearestPoly = navquery.findNearestPoly(ag.npos, crowd.getQueryExtents(), filter);
|
||||
if (nearestPoly.succeeded()) {
|
||||
if (nearestPoly.succeeded())
|
||||
{
|
||||
Result<FindRandomPointResult> result = navquery.findRandomPointAroundCircle(nearestPoly.result.getNearestRef(),
|
||||
agentData.home, zoneRadius[0] * 2f, filter, rnd);
|
||||
if (result.succeeded()) {
|
||||
if (result.succeeded())
|
||||
{
|
||||
crowd.requestMoveTarget(ag, result.result.getRandomRef(), result.result.getRandomPt());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void moveVillager(NavMeshQuery navquery, QueryFilter filter, CrowdAgent ag, AgentData agentData) {
|
||||
private void moveVillager(NavMeshQuery navquery, QueryFilter filter, CrowdAgent ag, AgentData agentData)
|
||||
{
|
||||
// Move somewhere close
|
||||
Result<FindNearestPolyResult> nearestPoly = navquery.findNearestPoly(ag.npos, crowd.getQueryExtents(), filter);
|
||||
if (nearestPoly.succeeded()) {
|
||||
if (nearestPoly.succeeded())
|
||||
{
|
||||
Result<FindRandomPointResult> result = navquery.findRandomPointAroundCircle(nearestPoly.result.getNearestRef(),
|
||||
agentData.home, zoneRadius[0] * 0.2f, filter, rnd);
|
||||
if (result.succeeded()) {
|
||||
if (result.succeeded())
|
||||
{
|
||||
crowd.requestMoveTarget(ag, result.result.getRandomRef(), result.result.getRandomPt());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void moveTraveller(NavMeshQuery navquery, QueryFilter filter, CrowdAgent ag, AgentData agentData) {
|
||||
private void moveTraveller(NavMeshQuery navquery, QueryFilter filter, CrowdAgent ag, AgentData agentData)
|
||||
{
|
||||
// Move to another zone
|
||||
List<FindRandomPointResult> potentialTargets = new();
|
||||
foreach (FindRandomPointResult zone in zones) {
|
||||
if (DemoMath.vDistSqr(zone.getRandomPt(), ag.npos, 0) > zoneRadius[0] * zoneRadius[0]) {
|
||||
foreach (FindRandomPointResult zone in zones)
|
||||
{
|
||||
if (DemoMath.vDistSqr(zone.getRandomPt(), ag.npos, 0) > zoneRadius[0] * zoneRadius[0])
|
||||
{
|
||||
potentialTargets.Add(zone);
|
||||
}
|
||||
}
|
||||
if (0 < potentialTargets.Count) {
|
||||
|
||||
if (0 < potentialTargets.Count)
|
||||
{
|
||||
potentialTargets.Shuffle();
|
||||
crowd.requestMoveTarget(ag, potentialTargets[0].getRandomRef(), potentialTargets[0].getRandomPt());
|
||||
}
|
||||
}
|
||||
|
||||
private bool needsNewTarget(CrowdAgent ag) {
|
||||
private bool needsNewTarget(CrowdAgent ag)
|
||||
{
|
||||
if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_NONE
|
||||
|| ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_FAILED) {
|
||||
|| ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_FAILED)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_VALID) {
|
||||
|
||||
if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_VALID)
|
||||
{
|
||||
float dx = ag.targetPos[0] - ag.npos[0];
|
||||
float dy = ag.targetPos[1] - ag.npos[1];
|
||||
float dz = ag.targetPos[2] - ag.npos[2];
|
||||
return dx * dx + dy * dy + dz * dz < 0.3f;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setup(float maxAgentRadius, NavMesh nav) {
|
||||
public void setup(float maxAgentRadius, NavMesh nav)
|
||||
{
|
||||
navMesh = nav;
|
||||
if (nav != null) {
|
||||
if (nav != null)
|
||||
{
|
||||
config = new CrowdConfig(maxAgentRadius);
|
||||
}
|
||||
}
|
||||
|
||||
public void handleRender(NavMeshRenderer renderer) {
|
||||
public void handleRender(NavMeshRenderer renderer)
|
||||
{
|
||||
RecastDebugDraw dd = renderer.getDebugDraw();
|
||||
dd.depthMask(false);
|
||||
if (crowd != null) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
if (crowd != null)
|
||||
{
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
float radius = ag.option.radius;
|
||||
float[] pos = ag.npos;
|
||||
dd.debugDrawCircle(pos[0], pos[1], pos[2], radius, duRGBA(0, 0, 0, 32), 2.0f);
|
||||
}
|
||||
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
AgentData agentData = (AgentData)ag.option.userData;
|
||||
|
||||
float height = ag.option.height;
|
||||
|
@ -326,12 +372,16 @@ public class CrowdProfilingTool {
|
|||
float[] pos = ag.npos;
|
||||
|
||||
int col = duRGBA(220, 220, 220, 128);
|
||||
if (agentData.type == AgentType.TRAVELLER) {
|
||||
if (agentData.type == AgentType.TRAVELLER)
|
||||
{
|
||||
col = duRGBA(100, 160, 100, 128);
|
||||
}
|
||||
if (agentData.type == AgentType.VILLAGER) {
|
||||
|
||||
if (agentData.type == AgentType.VILLAGER)
|
||||
{
|
||||
col = duRGBA(120, 80, 160, 128);
|
||||
}
|
||||
|
||||
if (ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_REQUESTING
|
||||
|| ag.targetState == CrowdAgent.MoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE)
|
||||
col = duLerpCol(col, duRGBA(255, 255, 32, 128), 128);
|
||||
|
@ -350,30 +400,38 @@ public class CrowdProfilingTool {
|
|||
dd.depthMask(true);
|
||||
}
|
||||
|
||||
private CrowdAgent addAgent(float[] p, AgentType type) {
|
||||
private CrowdAgent addAgent(float[] p, AgentType type)
|
||||
{
|
||||
CrowdAgentParams ap = agentParamsSupplier.Invoke();
|
||||
ap.userData = new AgentData(type, p);
|
||||
return crowd.addAgent(p, ap);
|
||||
}
|
||||
|
||||
public enum AgentType {
|
||||
VILLAGER, TRAVELLER, MOB,
|
||||
public enum AgentType
|
||||
{
|
||||
VILLAGER,
|
||||
TRAVELLER,
|
||||
MOB,
|
||||
}
|
||||
|
||||
private class AgentData {
|
||||
private class AgentData
|
||||
{
|
||||
public readonly AgentType type;
|
||||
public readonly float[] home = new float[3];
|
||||
|
||||
public AgentData(AgentType type, float[] home) {
|
||||
public AgentData(AgentType type, float[] home)
|
||||
{
|
||||
this.type = type;
|
||||
RecastVectors.copy(this.home, home);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void updateAgentParams(int updateFlags, int obstacleAvoidanceType, float separationWeight) {
|
||||
if (crowd != null) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
public void updateAgentParams(int updateFlags, int obstacleAvoidanceType, float separationWeight)
|
||||
{
|
||||
if (crowd != null)
|
||||
{
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
CrowdAgentParams option = new CrowdAgentParams();
|
||||
option.radius = ag.option.radius;
|
||||
option.height = ag.option.height;
|
||||
|
|
|
@ -27,16 +27,20 @@ using DotRecast.Detour.Crowd.Tracking;
|
|||
using DotRecast.Recast.Demo.Builder;
|
||||
using DotRecast.Recast.Demo.Draw;
|
||||
using DotRecast.Recast.Demo.Geom;
|
||||
|
||||
using static DotRecast.Recast.Demo.Draw.DebugDraw;
|
||||
using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives;
|
||||
|
||||
namespace DotRecast.Recast.Demo.Tools;
|
||||
|
||||
public class CrowdTool : Tool {
|
||||
|
||||
private enum ToolMode {
|
||||
CREATE, MOVE_TARGET, SELECT, TOGGLE_POLYS, PROFILING
|
||||
public class CrowdTool : Tool
|
||||
{
|
||||
private enum ToolMode
|
||||
{
|
||||
CREATE,
|
||||
MOVE_TARGET,
|
||||
SELECT,
|
||||
TOGGLE_POLYS,
|
||||
PROFILING
|
||||
}
|
||||
|
||||
private readonly CrowdToolParams toolParams = new CrowdToolParams();
|
||||
|
@ -48,7 +52,8 @@ public class CrowdTool : Tool {
|
|||
|
||||
private static readonly int AGENT_MAX_TRAIL = 64;
|
||||
|
||||
private class AgentTrail {
|
||||
private class AgentTrail
|
||||
{
|
||||
public float[] trail = new float[AGENT_MAX_TRAIL * 3];
|
||||
public int htrail;
|
||||
};
|
||||
|
@ -59,19 +64,23 @@ public class CrowdTool : Tool {
|
|||
private ToolMode m_mode = ToolMode.CREATE;
|
||||
private long crowdUpdateTime;
|
||||
|
||||
public CrowdTool() {
|
||||
public CrowdTool()
|
||||
{
|
||||
m_agentDebug.vod = new ObstacleAvoidanceDebugData(2048);
|
||||
profilingTool = new CrowdProfilingTool(getAgentParams);
|
||||
}
|
||||
|
||||
public override void setSample(Sample psample) {
|
||||
if (sample != psample) {
|
||||
public override void setSample(Sample psample)
|
||||
{
|
||||
if (sample != psample)
|
||||
{
|
||||
sample = psample;
|
||||
}
|
||||
|
||||
NavMesh nav = sample.getNavMesh();
|
||||
|
||||
if (nav != null && m_nav != nav) {
|
||||
if (nav != null && m_nav != nav)
|
||||
{
|
||||
m_nav = nav;
|
||||
|
||||
CrowdConfig config = new CrowdConfig(sample.getSettingsUI().getAgentRadius());
|
||||
|
@ -116,41 +125,60 @@ public class CrowdTool : Tool {
|
|||
}
|
||||
}
|
||||
|
||||
public override void handleClick(float[] s, float[] p, bool shift) {
|
||||
if (m_mode == ToolMode.PROFILING) {
|
||||
public override void handleClick(float[] s, float[] p, bool shift)
|
||||
{
|
||||
if (m_mode == ToolMode.PROFILING)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (crowd == null) {
|
||||
|
||||
if (crowd == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (m_mode == ToolMode.CREATE) {
|
||||
if (shift) {
|
||||
|
||||
if (m_mode == ToolMode.CREATE)
|
||||
{
|
||||
if (shift)
|
||||
{
|
||||
// Delete
|
||||
CrowdAgent ahit = hitTestAgents(s, p);
|
||||
if (ahit != null) {
|
||||
if (ahit != null)
|
||||
{
|
||||
removeAgent(ahit);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add
|
||||
addAgent(p);
|
||||
}
|
||||
} else if (m_mode == ToolMode.MOVE_TARGET) {
|
||||
}
|
||||
else if (m_mode == ToolMode.MOVE_TARGET)
|
||||
{
|
||||
setMoveTarget(p, shift);
|
||||
} else if (m_mode == ToolMode.SELECT) {
|
||||
}
|
||||
else if (m_mode == ToolMode.SELECT)
|
||||
{
|
||||
// Highlight
|
||||
CrowdAgent ahit = hitTestAgents(s, p);
|
||||
hilightAgent(ahit);
|
||||
} else if (m_mode == ToolMode.TOGGLE_POLYS) {
|
||||
}
|
||||
else if (m_mode == ToolMode.TOGGLE_POLYS)
|
||||
{
|
||||
NavMesh nav = sample.getNavMesh();
|
||||
NavMeshQuery navquery = sample.getNavMeshQuery();
|
||||
if (nav != null && navquery != null) {
|
||||
if (nav != null && navquery != null)
|
||||
{
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
float[] halfExtents = crowd.getQueryExtents();
|
||||
Result<FindNearestPolyResult> result = navquery.findNearestPoly(p, halfExtents, filter);
|
||||
long refs = result.result.getNearestRef();
|
||||
if (refs != 0) {
|
||||
if (refs != 0)
|
||||
{
|
||||
Result<int> flags = nav.getPolyFlags(refs);
|
||||
if (flags.succeeded()) {
|
||||
if (flags.succeeded())
|
||||
{
|
||||
nav.setPolyFlags(refs, flags.result ^ SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED);
|
||||
}
|
||||
}
|
||||
|
@ -158,17 +186,21 @@ public class CrowdTool : Tool {
|
|||
}
|
||||
}
|
||||
|
||||
private void removeAgent(CrowdAgent agent) {
|
||||
private void removeAgent(CrowdAgent agent)
|
||||
{
|
||||
crowd.removeAgent(agent);
|
||||
if (agent == m_agentDebug.agent) {
|
||||
if (agent == m_agentDebug.agent)
|
||||
{
|
||||
m_agentDebug.agent = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void addAgent(float[] p) {
|
||||
private void addAgent(float[] p)
|
||||
{
|
||||
CrowdAgentParams ap = getAgentParams();
|
||||
CrowdAgent ag = crowd.addAgent(p, ap);
|
||||
if (ag != null) {
|
||||
if (ag != null)
|
||||
{
|
||||
if (m_targetRef != 0)
|
||||
crowd.requestMoveTarget(ag, m_targetRef, m_targetPos);
|
||||
|
||||
|
@ -178,17 +210,20 @@ public class CrowdTool : Tool {
|
|||
trail = new AgentTrail();
|
||||
m_trails.Add(ag.idx, trail);
|
||||
}
|
||||
for (int i = 0; i < AGENT_MAX_TRAIL; ++i) {
|
||||
|
||||
for (int i = 0; i < AGENT_MAX_TRAIL; ++i)
|
||||
{
|
||||
trail.trail[i * 3] = p[0];
|
||||
trail.trail[i * 3 + 1] = p[1];
|
||||
trail.trail[i * 3 + 2] = p[2];
|
||||
}
|
||||
|
||||
trail.htrail = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private CrowdAgentParams getAgentParams() {
|
||||
private CrowdAgentParams getAgentParams()
|
||||
{
|
||||
CrowdAgentParams ap = new CrowdAgentParams();
|
||||
ap.radius = sample.getSettingsUI().getAgentRadius();
|
||||
ap.height = sample.getSettingsUI().getAgentHeight();
|
||||
|
@ -202,18 +237,21 @@ public class CrowdTool : Tool {
|
|||
return ap;
|
||||
}
|
||||
|
||||
private CrowdAgent hitTestAgents(float[] s, float[] p) {
|
||||
|
||||
private CrowdAgent hitTestAgents(float[] s, float[] p)
|
||||
{
|
||||
CrowdAgent isel = null;
|
||||
float tsel = float.MaxValue;
|
||||
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
float[] bmin = new float[3], bmax = new float[3];
|
||||
getAgentBounds(ag, bmin, bmax);
|
||||
float[] isect = Intersections.intersectSegmentAABB(s, p, bmin, bmax);
|
||||
if (null != isect) {
|
||||
if (null != isect)
|
||||
{
|
||||
float tmin = isect[0];
|
||||
if (tmin > 0 && tmin < tsel) {
|
||||
if (tmin > 0 && tmin < tsel)
|
||||
{
|
||||
isel = ag;
|
||||
tsel = tmin;
|
||||
}
|
||||
|
@ -223,7 +261,8 @@ public class CrowdTool : Tool {
|
|||
return isel;
|
||||
}
|
||||
|
||||
private void getAgentBounds(CrowdAgent ag, float[] bmin, float[] bmax) {
|
||||
private void getAgentBounds(CrowdAgent ag, float[] bmin, float[] bmax)
|
||||
{
|
||||
float[] p = ag.npos;
|
||||
float r = ag.option.radius;
|
||||
float h = ag.option.height;
|
||||
|
@ -235,7 +274,8 @@ public class CrowdTool : Tool {
|
|||
bmax[2] = p[2] + r;
|
||||
}
|
||||
|
||||
private void setMoveTarget(float[] p, bool adjust) {
|
||||
private void setMoveTarget(float[] p, bool adjust)
|
||||
{
|
||||
if (sample == null || crowd == null)
|
||||
return;
|
||||
|
||||
|
@ -244,65 +284,85 @@ public class CrowdTool : Tool {
|
|||
QueryFilter filter = crowd.getFilter(0);
|
||||
float[] halfExtents = crowd.getQueryExtents();
|
||||
|
||||
if (adjust) {
|
||||
if (adjust)
|
||||
{
|
||||
// Request velocity
|
||||
if (m_agentDebug.agent != null) {
|
||||
if (m_agentDebug.agent != null)
|
||||
{
|
||||
float[] vel = calcVel(m_agentDebug.agent.npos, p, m_agentDebug.agent.option.maxSpeed);
|
||||
crowd.requestMoveVelocity(m_agentDebug.agent, vel);
|
||||
} else {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
float[] vel = calcVel(ag.npos, p, ag.option.maxSpeed);
|
||||
crowd.requestMoveVelocity(ag, vel);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
Result<FindNearestPolyResult> result = navquery.findNearestPoly(p, halfExtents, filter);
|
||||
m_targetRef = result.result.getNearestRef();
|
||||
m_targetPos = result.result.getNearestPos();
|
||||
if (m_agentDebug.agent != null) {
|
||||
if (m_agentDebug.agent != null)
|
||||
{
|
||||
crowd.requestMoveTarget(m_agentDebug.agent, m_targetRef, m_targetPos);
|
||||
} else {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
crowd.requestMoveTarget(ag, m_targetRef, m_targetPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float[] calcVel(float[] pos, float[] tgt, float speed) {
|
||||
private float[] calcVel(float[] pos, float[] tgt, float speed)
|
||||
{
|
||||
float[] vel = DetourCommon.vSub(tgt, pos);
|
||||
vel[1] = 0.0f;
|
||||
DetourCommon.vNormalize(vel);
|
||||
return DetourCommon.vScale(vel, speed);
|
||||
}
|
||||
|
||||
public override void handleRender(NavMeshRenderer renderer) {
|
||||
if (m_mode == ToolMode.PROFILING) {
|
||||
public override void handleRender(NavMeshRenderer renderer)
|
||||
{
|
||||
if (m_mode == ToolMode.PROFILING)
|
||||
{
|
||||
profilingTool.handleRender(renderer);
|
||||
return;
|
||||
}
|
||||
|
||||
RecastDebugDraw dd = renderer.getDebugDraw();
|
||||
float rad = sample.getSettingsUI().getAgentRadius();
|
||||
NavMesh nav = sample.getNavMesh();
|
||||
if (nav == null || crowd == null)
|
||||
return;
|
||||
|
||||
if (toolParams.m_showNodes && crowd.getPathQueue() != null) {
|
||||
if (toolParams.m_showNodes && crowd.getPathQueue() != null)
|
||||
{
|
||||
// NavMeshQuery navquery = crowd.getPathQueue().getNavQuery();
|
||||
// if (navquery != null) {
|
||||
// dd.debugDrawNavMeshNodes(navquery);
|
||||
// }
|
||||
}
|
||||
|
||||
dd.depthMask(false);
|
||||
|
||||
// Draw paths
|
||||
if (toolParams.m_showPath) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
if (toolParams.m_showPath)
|
||||
{
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
if (!toolParams.m_showDetailAll && ag != m_agentDebug.agent)
|
||||
continue;
|
||||
List<long> path = ag.corridor.getPath();
|
||||
int npath = ag.corridor.getPathCount();
|
||||
for (int j = 0; j < npath; ++j) {
|
||||
for (int j = 0; j < npath; ++j)
|
||||
{
|
||||
dd.debugDrawNavMeshPoly(nav, path[j], duRGBA(255, 255, 255, 24));
|
||||
}
|
||||
}
|
||||
|
@ -312,22 +372,27 @@ public class CrowdTool : Tool {
|
|||
dd.debugDrawCross(m_targetPos[0], m_targetPos[1] + 0.1f, m_targetPos[2], rad, duRGBA(255, 255, 255, 192), 2.0f);
|
||||
|
||||
// Occupancy grid.
|
||||
if (toolParams.m_showGrid) {
|
||||
if (toolParams.m_showGrid)
|
||||
{
|
||||
float gridy = -float.MaxValue;
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
float[] pos = ag.corridor.getPos();
|
||||
gridy = Math.Max(gridy, pos[1]);
|
||||
}
|
||||
|
||||
gridy += 1.0f;
|
||||
|
||||
dd.begin(QUADS);
|
||||
ProximityGrid grid = crowd.getGrid();
|
||||
float cs = grid.getCellSize();
|
||||
foreach (int[] ic in grid.getItemCounts()) {
|
||||
foreach (int[] ic in grid.getItemCounts())
|
||||
{
|
||||
int x = ic[0];
|
||||
int y = ic[1];
|
||||
int count = ic[2];
|
||||
if (count != 0) {
|
||||
if (count != 0)
|
||||
{
|
||||
int col = duRGBA(128, 0, 0, Math.Min(count * 40, 255));
|
||||
dd.vertex(x * cs, gridy, y * cs, col);
|
||||
dd.vertex(x * cs, gridy, y * cs + cs, col);
|
||||
|
@ -335,12 +400,13 @@ public class CrowdTool : Tool {
|
|||
dd.vertex(x * cs + cs, gridy, y * cs, col);
|
||||
}
|
||||
}
|
||||
|
||||
dd.end();
|
||||
}
|
||||
|
||||
// Trail
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
AgentTrail trail = m_trails[ag.idx];
|
||||
float[] pos = ag.npos;
|
||||
|
||||
|
@ -348,7 +414,8 @@ public class CrowdTool : Tool {
|
|||
float[] prev = new float[3];
|
||||
float preva = 1;
|
||||
DetourCommon.vCopy(prev, pos);
|
||||
for (int j = 0; j < AGENT_MAX_TRAIL - 1; ++j) {
|
||||
for (int j = 0; j < AGENT_MAX_TRAIL - 1; ++j)
|
||||
{
|
||||
int idx = (trail.htrail + AGENT_MAX_TRAIL - j) % AGENT_MAX_TRAIL;
|
||||
int v = idx * 3;
|
||||
float a = 1 - j / (float)AGENT_MAX_TRAIL;
|
||||
|
@ -357,29 +424,35 @@ public class CrowdTool : Tool {
|
|||
preva = a;
|
||||
DetourCommon.vCopy(prev, trail.trail, v);
|
||||
}
|
||||
dd.end();
|
||||
|
||||
dd.end();
|
||||
}
|
||||
|
||||
// Corners & co
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
if (toolParams.m_showDetailAll == false && ag != m_agentDebug.agent)
|
||||
continue;
|
||||
|
||||
float radius = ag.option.radius;
|
||||
float[] pos = ag.npos;
|
||||
|
||||
if (toolParams.m_showCorners) {
|
||||
if (0 < ag.corners.Count) {
|
||||
if (toolParams.m_showCorners)
|
||||
{
|
||||
if (0 < ag.corners.Count)
|
||||
{
|
||||
dd.begin(LINES, 2.0f);
|
||||
for (int j = 0; j < ag.corners.Count; ++j) {
|
||||
for (int j = 0; j < ag.corners.Count; ++j)
|
||||
{
|
||||
float[] va = j == 0 ? pos : ag.corners[j - 1].getPos();
|
||||
float[] vb = ag.corners[j].getPos();
|
||||
dd.vertex(va[0], va[1] + radius, va[2], duRGBA(128, 0, 0, 192));
|
||||
dd.vertex(vb[0], vb[1] + radius, vb[2], duRGBA(128, 0, 0, 192));
|
||||
}
|
||||
|
||||
if ((ag.corners[ag.corners.Count - 1].getFlags()
|
||||
& NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) {
|
||||
& NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
|
||||
{
|
||||
float[] v = ag.corners[ag.corners.Count - 1].getPos();
|
||||
dd.vertex(v[0], v[1], v[2], duRGBA(192, 0, 0, 192));
|
||||
dd.vertex(v[0], v[1] + radius * 2, v[2], duRGBA(192, 0, 0, 192));
|
||||
|
@ -387,7 +460,8 @@ public class CrowdTool : Tool {
|
|||
|
||||
dd.end();
|
||||
|
||||
if (toolParams.m_anticipateTurns) {
|
||||
if (toolParams.m_anticipateTurns)
|
||||
{
|
||||
/* float dvel[3], pos[3];
|
||||
calcSmoothSteerDirection(ag.pos, ag.cornerVerts, ag.ncorners, dvel);
|
||||
pos[0] = ag.pos[0] + dvel[0];
|
||||
|
@ -411,14 +485,16 @@ public class CrowdTool : Tool {
|
|||
}
|
||||
}
|
||||
|
||||
if (toolParams.m_showCollisionSegments) {
|
||||
if (toolParams.m_showCollisionSegments)
|
||||
{
|
||||
float[] center = ag.boundary.getCenter();
|
||||
dd.debugDrawCross(center[0], center[1] + radius, center[2], 0.2f, duRGBA(192, 0, 128, 255), 2.0f);
|
||||
dd.debugDrawCircle(center[0], center[1] + radius, center[2], ag.option.collisionQueryRange,
|
||||
duRGBA(192, 0, 128, 128), 2.0f);
|
||||
|
||||
dd.begin(LINES, 3.0f);
|
||||
for (int j = 0; j < ag.boundary.getSegmentCount(); ++j) {
|
||||
for (int j = 0; j < ag.boundary.getSegmentCount(); ++j)
|
||||
{
|
||||
int col = duRGBA(192, 0, 128, 192);
|
||||
float[] s = ag.boundary.getSegment(j);
|
||||
float[] s0 = new float[] { s[0], s[1], s[2] };
|
||||
|
@ -428,25 +504,31 @@ public class CrowdTool : Tool {
|
|||
|
||||
dd.appendArrow(s[0], s[1] + 0.2f, s[2], s[3], s[4] + 0.2f, s[5], 0.0f, 0.3f, col);
|
||||
}
|
||||
|
||||
dd.end();
|
||||
}
|
||||
|
||||
if (toolParams.m_showNeis) {
|
||||
if (toolParams.m_showNeis)
|
||||
{
|
||||
dd.debugDrawCircle(pos[0], pos[1] + radius, pos[2], ag.option.collisionQueryRange, duRGBA(0, 192, 128, 128),
|
||||
2.0f);
|
||||
|
||||
dd.begin(LINES, 2.0f);
|
||||
for (int j = 0; j < ag.neis.Count; ++j) {
|
||||
for (int j = 0; j < ag.neis.Count; ++j)
|
||||
{
|
||||
CrowdAgent nei = ag.neis[j].agent;
|
||||
if (nei != null) {
|
||||
if (nei != null)
|
||||
{
|
||||
dd.vertex(pos[0], pos[1] + radius, pos[2], duRGBA(0, 192, 128, 128));
|
||||
dd.vertex(nei.npos[0], nei.npos[1] + radius, nei.npos[2], duRGBA(0, 192, 128, 128));
|
||||
}
|
||||
}
|
||||
|
||||
dd.end();
|
||||
}
|
||||
|
||||
if (toolParams.m_showOpt) {
|
||||
if (toolParams.m_showOpt)
|
||||
{
|
||||
dd.begin(LINES, 2.0f);
|
||||
dd.vertex(m_agentDebug.optStart[0], m_agentDebug.optStart[1] + 0.3f, m_agentDebug.optStart[2],
|
||||
duRGBA(0, 128, 0, 192));
|
||||
|
@ -456,8 +538,8 @@ public class CrowdTool : Tool {
|
|||
}
|
||||
|
||||
// Agent cylinders.
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
float radius = ag.option.radius;
|
||||
float[] pos = ag.npos;
|
||||
|
||||
|
@ -468,8 +550,8 @@ public class CrowdTool : Tool {
|
|||
dd.debugDrawCircle(pos[0], pos[1], pos[2], radius, col, 2.0f);
|
||||
}
|
||||
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
float height = ag.option.height;
|
||||
float radius = ag.option.radius;
|
||||
float[] pos = ag.npos;
|
||||
|
@ -489,8 +571,10 @@ public class CrowdTool : Tool {
|
|||
pos[2] + radius, col);
|
||||
}
|
||||
|
||||
if (toolParams.m_showVO) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
if (toolParams.m_showVO)
|
||||
{
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
if (toolParams.m_showDetailAll == false && ag != m_agentDebug.agent)
|
||||
continue;
|
||||
|
||||
|
@ -504,7 +588,8 @@ public class CrowdTool : Tool {
|
|||
dd.debugDrawCircle(dx, dy, dz, ag.option.maxSpeed, duRGBA(255, 255, 255, 64), 2.0f);
|
||||
|
||||
dd.begin(QUADS);
|
||||
for (int j = 0; j < vod.getSampleCount(); ++j) {
|
||||
for (int j = 0; j < vod.getSampleCount(); ++j)
|
||||
{
|
||||
float[] p = vod.getSampleVelocity(j);
|
||||
float sr = vod.getSampleSize(j);
|
||||
float pen = vod.getSamplePenalty(j);
|
||||
|
@ -516,13 +601,14 @@ public class CrowdTool : Tool {
|
|||
dd.vertex(dx + p[0] + sr, dy, dz + p[2] + sr, col);
|
||||
dd.vertex(dx + p[0] + sr, dy, dz + p[2] - sr, col);
|
||||
}
|
||||
|
||||
dd.end();
|
||||
}
|
||||
}
|
||||
|
||||
// Velocity stuff.
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
float radius = ag.option.radius;
|
||||
float height = ag.option.height;
|
||||
float[] pos = ag.npos;
|
||||
|
@ -552,15 +638,19 @@ public class CrowdTool : Tool {
|
|||
dd.depthMask(true);
|
||||
}
|
||||
|
||||
public override void handleUpdate(float dt) {
|
||||
public override void handleUpdate(float dt)
|
||||
{
|
||||
updateTick(dt);
|
||||
}
|
||||
|
||||
private void updateTick(float dt) {
|
||||
if (m_mode == ToolMode.PROFILING) {
|
||||
private void updateTick(float dt)
|
||||
{
|
||||
if (m_mode == ToolMode.PROFILING)
|
||||
{
|
||||
profilingTool.update(dt);
|
||||
return;
|
||||
}
|
||||
|
||||
if (crowd == null)
|
||||
return;
|
||||
NavMesh nav = sample.getNavMesh();
|
||||
|
@ -572,7 +662,8 @@ public class CrowdTool : Tool {
|
|||
long endTime = Stopwatch.GetTimestamp();
|
||||
|
||||
// Update agent trails
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
AgentTrail trail = m_trails[ag.idx];
|
||||
// Update agent movement trail.
|
||||
trail.htrail = (trail.htrail + 1) % AGENT_MAX_TRAIL;
|
||||
|
@ -587,11 +678,13 @@ public class CrowdTool : Tool {
|
|||
crowdUpdateTime = (endTime - startTime) / 1_000_000;
|
||||
}
|
||||
|
||||
private void hilightAgent(CrowdAgent agent) {
|
||||
private void hilightAgent(CrowdAgent agent)
|
||||
{
|
||||
m_agentDebug.agent = agent;
|
||||
}
|
||||
|
||||
public override void layout(IWindow ctx) {
|
||||
public override void layout(IWindow ctx)
|
||||
{
|
||||
// ToolMode previousToolMode = m_mode;
|
||||
// nk_layout_row_dynamic(ctx, 20, 1);
|
||||
// if (nk_option_label(ctx, "Create Agents", m_mode == ToolMode.CREATE)) {
|
||||
|
@ -683,14 +776,17 @@ public class CrowdTool : Tool {
|
|||
// }
|
||||
}
|
||||
|
||||
private void updateAgentParams() {
|
||||
if (crowd == null) {
|
||||
private void updateAgentParams()
|
||||
{
|
||||
if (crowd == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int updateFlags = getUpdateFlags();
|
||||
profilingTool.updateAgentParams(updateFlags, toolParams.m_obstacleAvoidanceType[0], toolParams.m_separationWeight[0]);
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
CrowdAgentParams option = new CrowdAgentParams();
|
||||
option.radius = ag.option.radius;
|
||||
option.height = ag.option.height;
|
||||
|
@ -708,28 +804,39 @@ public class CrowdTool : Tool {
|
|||
}
|
||||
}
|
||||
|
||||
private int getUpdateFlags() {
|
||||
private int getUpdateFlags()
|
||||
{
|
||||
int updateFlags = 0;
|
||||
if (toolParams.m_anticipateTurns) {
|
||||
if (toolParams.m_anticipateTurns)
|
||||
{
|
||||
updateFlags |= CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS;
|
||||
}
|
||||
if (toolParams.m_optimizeVis) {
|
||||
|
||||
if (toolParams.m_optimizeVis)
|
||||
{
|
||||
updateFlags |= CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS;
|
||||
}
|
||||
if (toolParams.m_optimizeTopo) {
|
||||
|
||||
if (toolParams.m_optimizeTopo)
|
||||
{
|
||||
updateFlags |= CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
|
||||
}
|
||||
if (toolParams.m_obstacleAvoidance) {
|
||||
|
||||
if (toolParams.m_obstacleAvoidance)
|
||||
{
|
||||
updateFlags |= CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
|
||||
}
|
||||
if (toolParams.m_separation) {
|
||||
|
||||
if (toolParams.m_separation)
|
||||
{
|
||||
updateFlags |= CrowdAgentParams.DT_CROWD_SEPARATION;
|
||||
}
|
||||
|
||||
return updateFlags;
|
||||
}
|
||||
|
||||
public override string getName() {
|
||||
public override string getName()
|
||||
{
|
||||
return "Crowd";
|
||||
}
|
||||
|
||||
}
|
|
@ -15,11 +15,11 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Recast.Demo.Tools;
|
||||
|
||||
|
||||
public class CrowdToolParams {
|
||||
|
||||
public class CrowdToolParams
|
||||
{
|
||||
public readonly int[] m_expandSelectedDebugDraw = new[] { 1 };
|
||||
public bool m_showCorners;
|
||||
public bool m_showCollisionSegments;
|
||||
|
|
|
@ -6,9 +6,9 @@ namespace DotRecast.Recast.Demo.Tools;
|
|||
|
||||
public static class DemoObjImporter
|
||||
{
|
||||
public static DemoInputGeomProvider load(byte[] chunk) {
|
||||
public static DemoInputGeomProvider load(byte[] chunk)
|
||||
{
|
||||
var context = ObjImporter.loadContext(chunk);
|
||||
return new DemoInputGeomProvider(context.vertexPositions, context.meshFaces);
|
||||
|
||||
}
|
||||
}
|
|
@ -30,20 +30,31 @@ using DotRecast.Recast.Demo.Geom;
|
|||
using DotRecast.Recast.Demo.Tools.Gizmos;
|
||||
using DotRecast.Recast.Demo.UI;
|
||||
using Silk.NET.Windowing;
|
||||
|
||||
using static DotRecast.Recast.Demo.Draw.DebugDraw;
|
||||
using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives;
|
||||
using static DotRecast.Detour.DetourCommon;
|
||||
|
||||
namespace DotRecast.Recast.Demo.Tools;
|
||||
public class DynamicUpdateTool : Tool {
|
||||
|
||||
private enum ToolMode {
|
||||
BUILD, COLLIDERS, RAYCAST
|
||||
public class DynamicUpdateTool : Tool
|
||||
{
|
||||
private enum ToolMode
|
||||
{
|
||||
BUILD,
|
||||
COLLIDERS,
|
||||
RAYCAST
|
||||
}
|
||||
|
||||
private enum ColliderShape {
|
||||
SPHERE, CAPSULE, BOX, CYLINDER, COMPOSITE, CONVEX, TRIMESH_BRIDGE, TRIMESH_HOUSE
|
||||
private enum ColliderShape
|
||||
{
|
||||
SPHERE,
|
||||
CAPSULE,
|
||||
BOX,
|
||||
CYLINDER,
|
||||
COMPOSITE,
|
||||
CONVEX,
|
||||
TRIMESH_BRIDGE,
|
||||
TRIMESH_HOUSE
|
||||
}
|
||||
|
||||
private Sample sample;
|
||||
|
@ -94,49 +105,78 @@ public class DynamicUpdateTool : Tool {
|
|||
convexGeom = DemoObjImporter.load(Loader.ToBytes("convex.obj"));
|
||||
}
|
||||
|
||||
public override void setSample(Sample sample) {
|
||||
public override void setSample(Sample sample)
|
||||
{
|
||||
this.sample = sample;
|
||||
}
|
||||
|
||||
public override void handleClick(float[] s, float[] p, bool shift) {
|
||||
if (mode == ToolMode.COLLIDERS) {
|
||||
if (!shift) {
|
||||
public override void handleClick(float[] s, float[] p, bool shift)
|
||||
{
|
||||
if (mode == ToolMode.COLLIDERS)
|
||||
{
|
||||
if (!shift)
|
||||
{
|
||||
Tuple<Collider, ColliderGizmo> colliderWithGizmo = null;
|
||||
if (dynaMesh != null) {
|
||||
if (colliderShape == ColliderShape.SPHERE) {
|
||||
if (dynaMesh != null)
|
||||
{
|
||||
if (colliderShape == ColliderShape.SPHERE)
|
||||
{
|
||||
colliderWithGizmo = sphereCollider(p);
|
||||
} else if (colliderShape == ColliderShape.CAPSULE) {
|
||||
}
|
||||
else if (colliderShape == ColliderShape.CAPSULE)
|
||||
{
|
||||
colliderWithGizmo = capsuleCollider(p);
|
||||
} else if (colliderShape == ColliderShape.BOX) {
|
||||
}
|
||||
else if (colliderShape == ColliderShape.BOX)
|
||||
{
|
||||
colliderWithGizmo = boxCollider(p);
|
||||
} else if (colliderShape == ColliderShape.CYLINDER) {
|
||||
}
|
||||
else if (colliderShape == ColliderShape.CYLINDER)
|
||||
{
|
||||
colliderWithGizmo = cylinderCollider(p);
|
||||
} else if (colliderShape == ColliderShape.COMPOSITE) {
|
||||
}
|
||||
else if (colliderShape == ColliderShape.COMPOSITE)
|
||||
{
|
||||
colliderWithGizmo = compositeCollider(p);
|
||||
} else if (colliderShape == ColliderShape.TRIMESH_BRIDGE) {
|
||||
}
|
||||
else if (colliderShape == ColliderShape.TRIMESH_BRIDGE)
|
||||
{
|
||||
colliderWithGizmo = trimeshBridge(p);
|
||||
} else if (colliderShape == ColliderShape.TRIMESH_HOUSE) {
|
||||
}
|
||||
else if (colliderShape == ColliderShape.TRIMESH_HOUSE)
|
||||
{
|
||||
colliderWithGizmo = trimeshHouse(p);
|
||||
} else if (colliderShape == ColliderShape.CONVEX) {
|
||||
}
|
||||
else if (colliderShape == ColliderShape.CONVEX)
|
||||
{
|
||||
colliderWithGizmo = convexTrimesh(p);
|
||||
}
|
||||
}
|
||||
if (colliderWithGizmo != null) {
|
||||
|
||||
if (colliderWithGizmo != null)
|
||||
{
|
||||
long id = dynaMesh.addCollider(colliderWithGizmo.Item1);
|
||||
colliders.Add(id, colliderWithGizmo.Item1);
|
||||
colliderGizmos.Add(id, colliderWithGizmo.Item2);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mode == ToolMode.RAYCAST) {
|
||||
if (shift) {
|
||||
|
||||
if (mode == ToolMode.RAYCAST)
|
||||
{
|
||||
if (shift)
|
||||
{
|
||||
sposSet = true;
|
||||
spos = ArrayUtils.CopyOf(p, p.Length);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
eposSet = true;
|
||||
epos = ArrayUtils.CopyOf(p, p.Length);
|
||||
}
|
||||
if (sposSet && eposSet && dynaMesh != null) {
|
||||
|
||||
if (sposSet && eposSet && dynaMesh != null)
|
||||
{
|
||||
float[] sp = { spos[0], spos[1] + 1.3f, spos[2] };
|
||||
float[] ep = { epos[0], epos[1] + 1.3f, epos[2] };
|
||||
long t1 = Stopwatch.GetTimestamp();
|
||||
|
@ -151,14 +191,16 @@ public class DynamicUpdateTool : Tool {
|
|||
}
|
||||
}
|
||||
|
||||
private Tuple<Collider, ColliderGizmo> sphereCollider(float[] p) {
|
||||
private Tuple<Collider, ColliderGizmo> sphereCollider(float[] p)
|
||||
{
|
||||
float radius = 1 + (float)random.NextDouble() * 10;
|
||||
return Tuple.Create<Collider, ColliderGizmo>(
|
||||
new SphereCollider(p, radius, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER, dynaMesh.config.walkableClimb),
|
||||
GizmoFactory.sphere(p, radius));
|
||||
}
|
||||
|
||||
private Tuple<Collider, ColliderGizmo> capsuleCollider(float[] p) {
|
||||
private Tuple<Collider, ColliderGizmo> capsuleCollider(float[] p)
|
||||
{
|
||||
float radius = 0.4f + (float)random.NextDouble() * 4f;
|
||||
float[] a = new float[] { (1f - 2 * (float)random.NextDouble()), 0.01f + (float)random.NextDouble(), (1f - 2 * (float)random.NextDouble()) };
|
||||
vNormalize(a);
|
||||
|
@ -172,9 +214,13 @@ public class DynamicUpdateTool : Tool {
|
|||
dynaMesh.config.walkableClimb), GizmoFactory.capsule(start, end, radius));
|
||||
}
|
||||
|
||||
private Tuple<Collider, ColliderGizmo> boxCollider(float[] p) {
|
||||
float[] extent = new float[] { 0.5f + (float)random.NextDouble() * 6f, 0.5f + (float)random.NextDouble() * 6f,
|
||||
0.5f + (float)random.NextDouble() * 6f };
|
||||
private Tuple<Collider, ColliderGizmo> boxCollider(float[] p)
|
||||
{
|
||||
float[] extent = new float[]
|
||||
{
|
||||
0.5f + (float)random.NextDouble() * 6f, 0.5f + (float)random.NextDouble() * 6f,
|
||||
0.5f + (float)random.NextDouble() * 6f
|
||||
};
|
||||
float[] forward = new float[] { (1f - 2 * (float)random.NextDouble()), 0, (1f - 2 * (float)random.NextDouble()) };
|
||||
float[] up = new float[] { (1f - 2 * (float)random.NextDouble()), 0.01f + (float)random.NextDouble(), (1f - 2 * (float)random.NextDouble()) };
|
||||
float[][] halfEdges = BoxCollider.getHalfEdges(up, forward, extent);
|
||||
|
@ -183,7 +229,8 @@ public class DynamicUpdateTool : Tool {
|
|||
GizmoFactory.box(p, halfEdges));
|
||||
}
|
||||
|
||||
private Tuple<Collider, ColliderGizmo> cylinderCollider(float[] p) {
|
||||
private Tuple<Collider, ColliderGizmo> cylinderCollider(float[] p)
|
||||
{
|
||||
float radius = 0.7f + (float)random.NextDouble() * 4f;
|
||||
float[] a = new float[] { (1f - 2 * (float)random.NextDouble()), 0.01f + (float)random.NextDouble(), (1f - 2 * (float)random.NextDouble()) };
|
||||
vNormalize(a);
|
||||
|
@ -197,7 +244,8 @@ public class DynamicUpdateTool : Tool {
|
|||
dynaMesh.config.walkableClimb), GizmoFactory.cylinder(start, end, radius));
|
||||
}
|
||||
|
||||
private Tuple<Collider, ColliderGizmo> compositeCollider(float[] p) {
|
||||
private Tuple<Collider, ColliderGizmo> compositeCollider(float[] p)
|
||||
{
|
||||
float[] baseExtent = new float[] { 5, 3, 8 };
|
||||
float[] baseCenter = new float[] { p[0], p[1] + 3, p[2] };
|
||||
float[] baseUp = new float[] { 0, 1, 0 };
|
||||
|
@ -212,13 +260,19 @@ public class DynamicUpdateTool : Tool {
|
|||
float[] roofCenter = new float[] { p[0], p[1] + 6, p[2] };
|
||||
BoxCollider roof = new BoxCollider(roofCenter, BoxCollider.getHalfEdges(roofUp, forward, roofExtent),
|
||||
SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD, dynaMesh.config.walkableClimb);
|
||||
float[] trunkStart = new float[] { baseCenter[0] - forward[0] * 15 + side[0] * 6, p[1],
|
||||
baseCenter[2] - forward[2] * 15 + side[2] * 6 };
|
||||
float[] trunkStart = new float[]
|
||||
{
|
||||
baseCenter[0] - forward[0] * 15 + side[0] * 6, p[1],
|
||||
baseCenter[2] - forward[2] * 15 + side[2] * 6
|
||||
};
|
||||
float[] trunkEnd = new float[] { trunkStart[0], trunkStart[1] + 10, trunkStart[2] };
|
||||
CapsuleCollider trunk = new CapsuleCollider(trunkStart, trunkEnd, 0.5f, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD,
|
||||
dynaMesh.config.walkableClimb);
|
||||
float[] crownCenter = new float[] { baseCenter[0] - forward[0] * 15 + side[0] * 6, p[1] + 10,
|
||||
baseCenter[2] - forward[2] * 15 + side[2] * 6 };
|
||||
float[] crownCenter = new float[]
|
||||
{
|
||||
baseCenter[0] - forward[0] * 15 + side[0] * 6, p[1] + 10,
|
||||
baseCenter[2] - forward[2] * 15 + side[2] * 6
|
||||
};
|
||||
SphereCollider crown = new SphereCollider(crownCenter, 4f, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GRASS,
|
||||
dynaMesh.config.walkableClimb);
|
||||
CompositeCollider collider = new CompositeCollider(@base, roof, trunk, crown);
|
||||
|
@ -230,36 +284,42 @@ public class DynamicUpdateTool : Tool {
|
|||
return Tuple.Create<Collider, ColliderGizmo>(collider, gizmo);
|
||||
}
|
||||
|
||||
private Tuple<Collider, ColliderGizmo> trimeshBridge(float[] p) {
|
||||
private Tuple<Collider, ColliderGizmo> trimeshBridge(float[] p)
|
||||
{
|
||||
return trimeshCollider(p, bridgeGeom);
|
||||
}
|
||||
|
||||
private Tuple<Collider, ColliderGizmo> trimeshHouse(float[] p) {
|
||||
private Tuple<Collider, ColliderGizmo> trimeshHouse(float[] p)
|
||||
{
|
||||
return trimeshCollider(p, houseGeom);
|
||||
}
|
||||
|
||||
private Tuple<Collider, ColliderGizmo> convexTrimesh(float[] p) {
|
||||
private Tuple<Collider, ColliderGizmo> convexTrimesh(float[] p)
|
||||
{
|
||||
float[] verts = transformVertices(p, convexGeom, 360);
|
||||
ConvexTrimeshCollider collider = new ConvexTrimeshCollider(verts, convexGeom.faces,
|
||||
SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD, dynaMesh.config.walkableClimb * 10);
|
||||
return Tuple.Create<Collider, ColliderGizmo>(collider, GizmoFactory.trimesh(verts, convexGeom.faces));
|
||||
}
|
||||
|
||||
private Tuple<Collider, ColliderGizmo> trimeshCollider(float[] p, DemoInputGeomProvider geom) {
|
||||
private Tuple<Collider, ColliderGizmo> trimeshCollider(float[] p, DemoInputGeomProvider geom)
|
||||
{
|
||||
float[] verts = transformVertices(p, geom, 0);
|
||||
TrimeshCollider collider = new TrimeshCollider(verts, geom.faces, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD,
|
||||
dynaMesh.config.walkableClimb * 10);
|
||||
return Tuple.Create<Collider, ColliderGizmo>(collider, GizmoFactory.trimesh(verts, geom.faces));
|
||||
}
|
||||
|
||||
private float[] transformVertices(float[] p, DemoInputGeomProvider geom, float ax) {
|
||||
private float[] transformVertices(float[] p, DemoInputGeomProvider geom, float ax)
|
||||
{
|
||||
float[] rx = GLU.build_4x4_rotation_matrix((float)random.NextDouble() * ax, 1, 0, 0);
|
||||
float[] ry = GLU.build_4x4_rotation_matrix((float)random.NextDouble() * 360, 0, 1, 0);
|
||||
float[] m = GLU.mul(rx, ry);
|
||||
float[] verts = new float[geom.vertices.Length];
|
||||
float[] v = new float[3];
|
||||
float[] vr = new float[3];
|
||||
for (int i = 0; i < geom.vertices.Length; i += 3) {
|
||||
for (int i = 0; i < geom.vertices.Length; i += 3)
|
||||
{
|
||||
v[0] = geom.vertices[i];
|
||||
v[1] = geom.vertices[i + 1];
|
||||
v[2] = geom.vertices[i + 2];
|
||||
|
@ -271,21 +331,28 @@ public class DynamicUpdateTool : Tool {
|
|||
verts[i + 1] = vr[1];
|
||||
verts[i + 2] = vr[2];
|
||||
}
|
||||
|
||||
return verts;
|
||||
}
|
||||
|
||||
private float[] mulMatrixVector(float[] resultvector, float[] matrix, float[] pvector) {
|
||||
private float[] mulMatrixVector(float[] resultvector, float[] matrix, float[] pvector)
|
||||
{
|
||||
resultvector[0] = matrix[0] * pvector[0] + matrix[4] * pvector[1] + matrix[8] * pvector[2];
|
||||
resultvector[1] = matrix[1] * pvector[0] + matrix[5] * pvector[1] + matrix[9] * pvector[2];
|
||||
resultvector[2] = matrix[2] * pvector[0] + matrix[6] * pvector[1] + matrix[10] * pvector[2];
|
||||
return resultvector;
|
||||
}
|
||||
|
||||
public override void handleClickRay(float[] start, float[] dir, bool shift) {
|
||||
if (mode == ToolMode.COLLIDERS) {
|
||||
if (shift) {
|
||||
foreach (var e in colliders) {
|
||||
if (hit(start, dir, e.Value.bounds())) {
|
||||
public override void handleClickRay(float[] start, float[] dir, bool shift)
|
||||
{
|
||||
if (mode == ToolMode.COLLIDERS)
|
||||
{
|
||||
if (shift)
|
||||
{
|
||||
foreach (var e in colliders)
|
||||
{
|
||||
if (hit(start, dir, e.Value.bounds()))
|
||||
{
|
||||
dynaMesh.removeCollider(e.Key);
|
||||
colliders.Remove(e.Key);
|
||||
colliderGizmos.Remove(e.Key);
|
||||
|
@ -296,7 +363,8 @@ public class DynamicUpdateTool : Tool {
|
|||
}
|
||||
}
|
||||
|
||||
private bool hit(float[] point, float[] dir, float[] bounds) {
|
||||
private bool hit(float[] point, float[] dir, float[] bounds)
|
||||
{
|
||||
float cx = 0.5f * (bounds[0] + bounds[3]);
|
||||
float cy = 0.5f * (bounds[1] + bounds[4]);
|
||||
float cz = 0.5f * (bounds[2] + bounds[5]);
|
||||
|
@ -308,46 +376,62 @@ public class DynamicUpdateTool : Tool {
|
|||
float my = point[1] - cy;
|
||||
float mz = point[2] - cz;
|
||||
float c = mx * mx + my * my + mz * mz - rSqr;
|
||||
if (c <= 0.0f) {
|
||||
if (c <= 0.0f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
float b = mx * dir[0] + my * dir[1] + mz * dir[2];
|
||||
if (b > 0.0f) {
|
||||
if (b > 0.0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
float disc = b * b - c;
|
||||
return disc >= 0.0f;
|
||||
}
|
||||
|
||||
public override void handleRender(NavMeshRenderer renderer) {
|
||||
if (mode == ToolMode.COLLIDERS) {
|
||||
if (showColliders) {
|
||||
public override void handleRender(NavMeshRenderer renderer)
|
||||
{
|
||||
if (mode == ToolMode.COLLIDERS)
|
||||
{
|
||||
if (showColliders)
|
||||
{
|
||||
colliderGizmos.Values.forEach(g => g.render(renderer.getDebugDraw()));
|
||||
}
|
||||
}
|
||||
if (mode == ToolMode.RAYCAST) {
|
||||
|
||||
if (mode == ToolMode.RAYCAST)
|
||||
{
|
||||
RecastDebugDraw dd = renderer.getDebugDraw();
|
||||
int startCol = duRGBA(128, 25, 0, 192);
|
||||
int endCol = duRGBA(51, 102, 0, 129);
|
||||
if (sposSet) {
|
||||
if (sposSet)
|
||||
{
|
||||
drawAgent(dd, spos, startCol);
|
||||
}
|
||||
if (eposSet) {
|
||||
|
||||
if (eposSet)
|
||||
{
|
||||
drawAgent(dd, epos, endCol);
|
||||
}
|
||||
|
||||
dd.depthMask(false);
|
||||
if (raycastHitPos != null) {
|
||||
if (raycastHitPos != null)
|
||||
{
|
||||
int spathCol = raycastHit ? duRGBA(128, 32, 16, 220) : duRGBA(64, 128, 240, 220);
|
||||
dd.begin(LINES, 2.0f);
|
||||
dd.vertex(spos[0], spos[1] + 1.3f, spos[2], spathCol);
|
||||
dd.vertex(raycastHitPos[0], raycastHitPos[1], raycastHitPos[2], spathCol);
|
||||
dd.end();
|
||||
}
|
||||
|
||||
dd.depthMask(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawAgent(RecastDebugDraw dd, float[] pos, int col) {
|
||||
private void drawAgent(RecastDebugDraw dd, float[] pos, int col)
|
||||
{
|
||||
float r = sample.getSettingsUI().getAgentRadius();
|
||||
float h = sample.getSettingsUI().getAgentHeight();
|
||||
float c = sample.getSettingsUI().getAgentMaxClimb();
|
||||
|
@ -367,29 +451,35 @@ public class DynamicUpdateTool : Tool {
|
|||
dd.depthMask(true);
|
||||
}
|
||||
|
||||
public override void handleUpdate(float dt) {
|
||||
if (dynaMesh != null) {
|
||||
public override void handleUpdate(float dt)
|
||||
{
|
||||
if (dynaMesh != null)
|
||||
{
|
||||
updateDynaMesh();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDynaMesh() {
|
||||
private void updateDynaMesh()
|
||||
{
|
||||
long t = Stopwatch.GetTimestamp();
|
||||
try
|
||||
{
|
||||
bool updated = dynaMesh.update(executor).Result;
|
||||
if (updated) {
|
||||
if (updated)
|
||||
{
|
||||
buildTime = (Stopwatch.GetTimestamp() - t) / 1_000_000;
|
||||
sample.update(null, dynaMesh.recastResults(), dynaMesh.navMesh());
|
||||
sample.setChanged(false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
}
|
||||
|
||||
public override void layout(IWindow ctx) {
|
||||
|
||||
public override void layout(IWindow ctx)
|
||||
{
|
||||
// nk_layout_row_dynamic(ctx, 18, 1);
|
||||
// if (nk_option_label(ctx, "Build", mode == ToolMode.BUILD)) {
|
||||
// mode = ToolMode.BUILD;
|
||||
|
@ -557,10 +647,10 @@ public class DynamicUpdateTool : Tool {
|
|||
// } else {
|
||||
// nk_label(ctx, string.format("Build Time: %d ms", buildTime), NK_TEXT_ALIGN_LEFT);
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
private void load() {
|
||||
private void load()
|
||||
{
|
||||
// try (MemoryStack stack = stackPush()) {
|
||||
// PointerBuffer aFilterPatterns = stack.mallocPointer(1);
|
||||
// aFilterPatterns.put(stack.UTF8("*.voxels"));
|
||||
|
@ -570,10 +660,10 @@ public class DynamicUpdateTool : Tool {
|
|||
// load(filename);
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
private void load(string filename) {
|
||||
private void load(string filename)
|
||||
{
|
||||
// File file = new File(filename);
|
||||
// if (file.exists()) {
|
||||
// VoxelFileReader reader = new VoxelFileReader();
|
||||
|
@ -591,7 +681,8 @@ public class DynamicUpdateTool : Tool {
|
|||
// }
|
||||
}
|
||||
|
||||
private void save() {
|
||||
private void save()
|
||||
{
|
||||
// try (MemoryStack stack = stackPush()) {
|
||||
// PointerBuffer aFilterPatterns = stack.mallocPointer(1);
|
||||
// aFilterPatterns.put(stack.UTF8("*.voxels"));
|
||||
|
@ -601,10 +692,10 @@ public class DynamicUpdateTool : Tool {
|
|||
// save(filename);
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
private void save(string filename) {
|
||||
private void save(string filename)
|
||||
{
|
||||
// File file = new File(filename);
|
||||
// try (FileOutputStream fos = new FileOutputStream(file)) {
|
||||
// VoxelFile voxelFile = VoxelFile.from(dynaMesh);
|
||||
|
@ -613,23 +704,27 @@ public class DynamicUpdateTool : Tool {
|
|||
// } catch (Exception e) {
|
||||
// Console.WriteLine(e);
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
private void buildDynaMesh() {
|
||||
private void buildDynaMesh()
|
||||
{
|
||||
configDynaMesh();
|
||||
long t = Stopwatch.GetTimestamp();
|
||||
try
|
||||
{
|
||||
var _ = dynaMesh.build(executor).Result;
|
||||
} catch (Exception e) {
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
|
||||
buildTime = (Stopwatch.GetTimestamp() - t) / 1_000_000;
|
||||
sample.update(null, dynaMesh.recastResults(), dynaMesh.navMesh());
|
||||
}
|
||||
|
||||
private void configDynaMesh() {
|
||||
private void configDynaMesh()
|
||||
{
|
||||
dynaMesh.config.partitionType = partitioning;
|
||||
dynaMesh.config.walkableHeight = walkableHeight[0];
|
||||
dynaMesh.config.walkableSlopeAngle = walkableSlopeAngle[0];
|
||||
|
@ -648,7 +743,8 @@ public class DynamicUpdateTool : Tool {
|
|||
dynaMesh.config.detailSampleMaxError = detailSampleMaxError[0];
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
private void updateUI()
|
||||
{
|
||||
cellSize[0] = dynaMesh.config.cellSize;
|
||||
partitioning = dynaMesh.config.partitionType;
|
||||
walkableHeight[0] = dynaMesh.config.walkableHeight;
|
||||
|
@ -668,8 +764,8 @@ public class DynamicUpdateTool : Tool {
|
|||
filterWalkableLowHeightSpans = dynaMesh.config.filterWalkableLowHeightSpans;
|
||||
}
|
||||
|
||||
public override string getName() {
|
||||
public override string getName()
|
||||
{
|
||||
return "Dynamic Updates";
|
||||
}
|
||||
|
||||
}
|
|
@ -3,11 +3,16 @@ using DotRecast.Recast.Demo.Draw;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Tools.Gizmos;
|
||||
|
||||
public class BoxGizmo : ColliderGizmo
|
||||
{
|
||||
private static readonly int[] TRIANLGES =
|
||||
{
|
||||
0, 1, 2, 0, 2, 3, 4, 7, 6, 4, 6, 5, 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2,
|
||||
2, 6, 7, 2, 7, 3, 4, 0, 3, 4, 3, 7
|
||||
};
|
||||
|
||||
public class BoxGizmo : ColliderGizmo {
|
||||
private static readonly int[] TRIANLGES = { 0, 1, 2, 0, 2, 3, 4, 7, 6, 4, 6, 5, 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2,
|
||||
2, 6, 7, 2, 7, 3, 4, 0, 3, 4, 3, 7 };
|
||||
private static readonly float[][] VERTS = {
|
||||
private static readonly float[][] VERTS =
|
||||
{
|
||||
new[] { -1f, -1f, -1f, },
|
||||
new[] { 1f, -1f, -1f, },
|
||||
new[] { 1f, -1f, 1f, },
|
||||
|
@ -27,10 +32,12 @@ public class BoxGizmo : ColliderGizmo {
|
|||
{
|
||||
}
|
||||
|
||||
public BoxGizmo(float[] center, float[][] halfEdges) {
|
||||
public BoxGizmo(float[] center, float[][] halfEdges)
|
||||
{
|
||||
this.center = center;
|
||||
this.halfEdges = halfEdges;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
float s0 = (i & 1) != 0 ? 1f : -1f;
|
||||
float s1 = (i & 2) != 0 ? 1f : -1f;
|
||||
float s2 = (i & 4) != 0 ? 1f : -1f;
|
||||
|
@ -40,29 +47,39 @@ public class BoxGizmo : ColliderGizmo {
|
|||
}
|
||||
}
|
||||
|
||||
public void render(RecastDebugDraw debugDraw) {
|
||||
public void render(RecastDebugDraw debugDraw)
|
||||
{
|
||||
float[] trX = new float[] { halfEdges[0][0], halfEdges[1][0], halfEdges[2][0] };
|
||||
float[] trY = new float[] { halfEdges[0][1], halfEdges[1][1], halfEdges[2][1] };
|
||||
float[] trZ = new float[] { halfEdges[0][2], halfEdges[1][2], halfEdges[2][2] };
|
||||
float[] vertices = new float[8 * 3];
|
||||
for (int i = 0; i < 8; i++) {
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
vertices[i * 3 + 0] = RecastVectors.dot(VERTS[i], trX) + center[0];
|
||||
vertices[i * 3 + 1] = RecastVectors.dot(VERTS[i], trY) + center[1];
|
||||
vertices[i * 3 + 2] = RecastVectors.dot(VERTS[i], trZ) + center[2];
|
||||
}
|
||||
|
||||
debugDraw.begin(DebugDrawPrimitives.TRIS);
|
||||
for (int i = 0; i < 12; i++) {
|
||||
for (int i = 0; i < 12; i++)
|
||||
{
|
||||
int col = DebugDraw.duRGBA(200, 200, 50, 160);
|
||||
if (i == 4 || i == 5 || i == 8 || i == 9) {
|
||||
if (i == 4 || i == 5 || i == 8 || i == 9)
|
||||
{
|
||||
col = DebugDraw.duRGBA(160, 160, 40, 160);
|
||||
} else if (i > 4) {
|
||||
}
|
||||
else if (i > 4)
|
||||
{
|
||||
col = DebugDraw.duRGBA(120, 120, 30, 160);
|
||||
}
|
||||
for (int j = 0; j < 3; j++) {
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
int v = TRIANLGES[i * 3 + j] * 3;
|
||||
debugDraw.vertex(vertices[v], vertices[v + 1], vertices[v + 2], col);
|
||||
}
|
||||
}
|
||||
|
||||
debugDraw.end();
|
||||
}
|
||||
}
|
|
@ -1,21 +1,24 @@
|
|||
using DotRecast.Recast.Demo.Draw;
|
||||
|
||||
using static DotRecast.Recast.RecastVectors;
|
||||
using static DotRecast.Detour.DetourCommon;
|
||||
using static DotRecast.Recast.Demo.Tools.Gizmos.GizmoHelper;
|
||||
|
||||
namespace DotRecast.Recast.Demo.Tools.Gizmos;
|
||||
|
||||
|
||||
public class CapsuleGizmo : ColliderGizmo {
|
||||
public class CapsuleGizmo : ColliderGizmo
|
||||
{
|
||||
private readonly float[] vertices;
|
||||
private readonly int[] triangles;
|
||||
private readonly float[] center;
|
||||
private readonly float[] gradient;
|
||||
|
||||
public CapsuleGizmo(float[] start, float[] end, float radius) {
|
||||
center = new float[] { 0.5f * (start[0] + end[0]), 0.5f * (start[1] + end[1]),
|
||||
0.5f * (start[2] + end[2]) };
|
||||
public CapsuleGizmo(float[] start, float[] end, float radius)
|
||||
{
|
||||
center = new float[]
|
||||
{
|
||||
0.5f * (start[0] + end[0]), 0.5f * (start[1] + end[1]),
|
||||
0.5f * (start[2] + end[2])
|
||||
};
|
||||
float[] axis = new float[] { end[0] - start[0], end[1] - start[1], end[2] - start[2] };
|
||||
float[][] normals = new float[3][];
|
||||
normals[1] = new float[] { end[0] - start[0], end[1] - start[1], end[2] - start[2] };
|
||||
|
@ -33,7 +36,8 @@ public class CapsuleGizmo : ColliderGizmo {
|
|||
vertices = new float[spVertices.Length];
|
||||
gradient = new float[spVertices.Length / 3];
|
||||
float[] v = new float[3];
|
||||
for (int i = 0; i < spVertices.Length; i += 3) {
|
||||
for (int i = 0; i < spVertices.Length; i += 3)
|
||||
{
|
||||
float offset = (i >= spVertices.Length / 2) ? -halfLength : halfLength;
|
||||
float x = radius * spVertices[i];
|
||||
float y = radius * spVertices[i + 1] + offset;
|
||||
|
@ -47,14 +51,16 @@ public class CapsuleGizmo : ColliderGizmo {
|
|||
normalize(v);
|
||||
gradient[i / 3] = clamp(0.57735026f * (v[0] + v[1] + v[2]), -1, 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private float[] getSideVector(float[] axis) {
|
||||
private float[] getSideVector(float[] axis)
|
||||
{
|
||||
float[] side = { 1, 0, 0 };
|
||||
if (axis[0] > 0.8) {
|
||||
if (axis[0] > 0.8)
|
||||
{
|
||||
side = new float[] { 0, 0, 1 };
|
||||
}
|
||||
|
||||
float[] forward = new float[3];
|
||||
cross(forward, side, axis);
|
||||
cross(side, axis, forward);
|
||||
|
@ -62,11 +68,13 @@ public class CapsuleGizmo : ColliderGizmo {
|
|||
return side;
|
||||
}
|
||||
|
||||
public void render(RecastDebugDraw debugDraw) {
|
||||
|
||||
public void render(RecastDebugDraw debugDraw)
|
||||
{
|
||||
debugDraw.begin(DebugDrawPrimitives.TRIS);
|
||||
for (int i = 0; i < triangles.Length; i += 3) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < triangles.Length; i += 3)
|
||||
{
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
int v = triangles[i + j] * 3;
|
||||
float c = gradient[triangles[i + j]];
|
||||
int col = DebugDraw.duLerpCol(DebugDraw.duRGBA(32, 32, 0, 160), DebugDraw.duRGBA(220, 220, 0, 160),
|
||||
|
@ -74,7 +82,7 @@ public class CapsuleGizmo : ColliderGizmo {
|
|||
debugDraw.vertex(vertices[v], vertices[v + 1], vertices[v + 2], col);
|
||||
}
|
||||
}
|
||||
|
||||
debugDraw.end();
|
||||
}
|
||||
|
||||
}
|
|
@ -2,7 +2,7 @@ using DotRecast.Recast.Demo.Draw;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Tools.Gizmos;
|
||||
|
||||
public interface ColliderGizmo {
|
||||
|
||||
public interface ColliderGizmo
|
||||
{
|
||||
void render(RecastDebugDraw debugDraw);
|
||||
}
|
|
@ -3,15 +3,17 @@ using DotRecast.Recast.Demo.Draw;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Tools.Gizmos;
|
||||
|
||||
public class CompositeGizmo : ColliderGizmo {
|
||||
|
||||
public class CompositeGizmo : ColliderGizmo
|
||||
{
|
||||
private readonly ColliderGizmo[] gizmos;
|
||||
|
||||
public CompositeGizmo(params ColliderGizmo[] gizmos) {
|
||||
public CompositeGizmo(params ColliderGizmo[] gizmos)
|
||||
{
|
||||
this.gizmos = gizmos;
|
||||
}
|
||||
|
||||
public void render(RecastDebugDraw debugDraw) {
|
||||
public void render(RecastDebugDraw debugDraw)
|
||||
{
|
||||
gizmos.forEach(g => g.render(debugDraw));
|
||||
}
|
||||
}
|
|
@ -6,16 +6,20 @@ using static DotRecast.Recast.Demo.Tools.Gizmos.GizmoHelper;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Tools.Gizmos;
|
||||
|
||||
|
||||
public class CylinderGizmo : ColliderGizmo {
|
||||
public class CylinderGizmo : ColliderGizmo
|
||||
{
|
||||
private readonly float[] vertices;
|
||||
private readonly int[] triangles;
|
||||
private readonly float[] center;
|
||||
private readonly float[] gradient;
|
||||
|
||||
public CylinderGizmo(float[] start, float[] end, float radius) {
|
||||
center = new float[] { 0.5f * (start[0] + end[0]), 0.5f * (start[1] + end[1]),
|
||||
0.5f * (start[2] + end[2]) };
|
||||
public CylinderGizmo(float[] start, float[] end, float radius)
|
||||
{
|
||||
center = new float[]
|
||||
{
|
||||
0.5f * (start[0] + end[0]), 0.5f * (start[1] + end[1]),
|
||||
0.5f * (start[2] + end[2])
|
||||
};
|
||||
float[] axis = new float[] { end[0] - start[0], end[1] - start[1], end[2] - start[2] };
|
||||
float[][] normals = new float[3][];
|
||||
normals[1] = new float[] { end[0] - start[0], end[1] - start[1], end[2] - start[2] };
|
||||
|
@ -32,7 +36,8 @@ public class CylinderGizmo : ColliderGizmo {
|
|||
float halfLength = 0.5f * vLen(axis);
|
||||
gradient = new float[vertices.Length / 3];
|
||||
float[] v = new float[3];
|
||||
for (int i = 0; i < vertices.Length; i += 3) {
|
||||
for (int i = 0; i < vertices.Length; i += 3)
|
||||
{
|
||||
float offset = (i >= vertices.Length / 2) ? -halfLength : halfLength;
|
||||
float x = radius * vertices[i];
|
||||
float y = vertices[i + 1] + offset;
|
||||
|
@ -40,9 +45,12 @@ public class CylinderGizmo : ColliderGizmo {
|
|||
vertices[i] = x * trX[0] + y * trX[1] + z * trX[2] + center[0];
|
||||
vertices[i + 1] = x * trY[0] + y * trY[1] + z * trY[2] + center[1];
|
||||
vertices[i + 2] = x * trZ[0] + y * trZ[1] + z * trZ[2] + center[2];
|
||||
if (i < vertices.Length / 4 || i >= 3 * vertices.Length / 4) {
|
||||
if (i < vertices.Length / 4 || i >= 3 * vertices.Length / 4)
|
||||
{
|
||||
gradient[i / 3] = 1;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
v[0] = vertices[i] - center[0];
|
||||
v[1] = vertices[i + 1] - center[1];
|
||||
v[2] = vertices[i + 2] - center[2];
|
||||
|
@ -52,11 +60,14 @@ public class CylinderGizmo : ColliderGizmo {
|
|||
}
|
||||
}
|
||||
|
||||
private float[] getSideVector(float[] axis) {
|
||||
private float[] getSideVector(float[] axis)
|
||||
{
|
||||
float[] side = { 1, 0, 0 };
|
||||
if (axis[0] > 0.8) {
|
||||
if (axis[0] > 0.8)
|
||||
{
|
||||
side = new float[] { 0, 0, 1 };
|
||||
}
|
||||
|
||||
float[] forward = new float[3];
|
||||
cross(forward, side, axis);
|
||||
cross(side, axis, forward);
|
||||
|
@ -64,11 +75,13 @@ public class CylinderGizmo : ColliderGizmo {
|
|||
return side;
|
||||
}
|
||||
|
||||
public void render(RecastDebugDraw debugDraw) {
|
||||
|
||||
public void render(RecastDebugDraw debugDraw)
|
||||
{
|
||||
debugDraw.begin(DebugDrawPrimitives.TRIS);
|
||||
for (int i = 0; i < triangles.Length; i += 3) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < triangles.Length; i += 3)
|
||||
{
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
int v = triangles[i + j] * 3;
|
||||
float c = gradient[triangles[i + j]];
|
||||
int col = DebugDraw.duLerpCol(DebugDraw.duRGBA(32, 32, 0, 160), DebugDraw.duRGBA(220, 220, 0, 160),
|
||||
|
@ -76,7 +89,7 @@ public class CylinderGizmo : ColliderGizmo {
|
|||
debugDraw.vertex(vertices[v], vertices[v + 1], vertices[v + 2], col);
|
||||
}
|
||||
}
|
||||
|
||||
debugDraw.end();
|
||||
}
|
||||
|
||||
}
|
|
@ -2,27 +2,33 @@
|
|||
|
||||
public static class GizmoFactory
|
||||
{
|
||||
public static ColliderGizmo box(float[] center, float[][] halfEdges) {
|
||||
public static ColliderGizmo box(float[] center, float[][] halfEdges)
|
||||
{
|
||||
return new BoxGizmo(center, halfEdges);
|
||||
}
|
||||
|
||||
public static ColliderGizmo sphere(float[] center, float radius) {
|
||||
public static ColliderGizmo sphere(float[] center, float radius)
|
||||
{
|
||||
return new SphereGizmo(center, radius);
|
||||
}
|
||||
|
||||
public static ColliderGizmo capsule(float[] start, float[] end, float radius) {
|
||||
public static ColliderGizmo capsule(float[] start, float[] end, float radius)
|
||||
{
|
||||
return new CapsuleGizmo(start, end, radius);
|
||||
}
|
||||
|
||||
public static ColliderGizmo cylinder(float[] start, float[] end, float radius) {
|
||||
public static ColliderGizmo cylinder(float[] start, float[] end, float radius)
|
||||
{
|
||||
return new CylinderGizmo(start, end, radius);
|
||||
}
|
||||
|
||||
public static ColliderGizmo trimesh(float[] verts, int[] faces) {
|
||||
public static ColliderGizmo trimesh(float[] verts, int[] faces)
|
||||
{
|
||||
return new TrimeshGizmo(verts, faces);
|
||||
}
|
||||
|
||||
public static ColliderGizmo composite(params ColliderGizmo[] gizmos) {
|
||||
public static ColliderGizmo composite(params ColliderGizmo[] gizmos)
|
||||
{
|
||||
return new CompositeGizmo(gizmos);
|
||||
}
|
||||
}
|
|
@ -4,31 +4,37 @@ using static DotRecast.Detour.DetourCommon;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Tools.Gizmos;
|
||||
|
||||
public class GizmoHelper {
|
||||
|
||||
public class GizmoHelper
|
||||
{
|
||||
private static readonly int SEGMENTS = 16;
|
||||
private static readonly int RINGS = 8;
|
||||
|
||||
private static float[] sphericalVertices;
|
||||
|
||||
public static float[] generateSphericalVertices() {
|
||||
if (sphericalVertices == null) {
|
||||
public static float[] generateSphericalVertices()
|
||||
{
|
||||
if (sphericalVertices == null)
|
||||
{
|
||||
sphericalVertices = generateSphericalVertices(SEGMENTS, RINGS);
|
||||
}
|
||||
|
||||
return sphericalVertices;
|
||||
}
|
||||
|
||||
private static float[] generateSphericalVertices(int segments, int rings) {
|
||||
private static float[] generateSphericalVertices(int segments, int rings)
|
||||
{
|
||||
float[] vertices = new float[6 + 3 * (segments + 1) * (rings + 1)];
|
||||
// top
|
||||
int vi = 0;
|
||||
vertices[vi++] = 0;
|
||||
vertices[vi++] = 1;
|
||||
vertices[vi++] = 0;
|
||||
for (int r = 0; r <= rings; r++) {
|
||||
for (int r = 0; r <= rings; r++)
|
||||
{
|
||||
double theta = Math.PI * (r + 1) / (rings + 2);
|
||||
vi = generateRingVertices(segments, vertices, vi, theta);
|
||||
}
|
||||
|
||||
// bottom
|
||||
vertices[vi++] = 0;
|
||||
vertices[vi++] = -1;
|
||||
|
@ -36,24 +42,29 @@ public class GizmoHelper {
|
|||
return vertices;
|
||||
}
|
||||
|
||||
public static float[] generateCylindricalVertices() {
|
||||
public static float[] generateCylindricalVertices()
|
||||
{
|
||||
return generateCylindricalVertices(SEGMENTS);
|
||||
}
|
||||
|
||||
private static float[] generateCylindricalVertices(int segments) {
|
||||
private static float[] generateCylindricalVertices(int segments)
|
||||
{
|
||||
float[] vertices = new float[3 * (segments + 1) * 4];
|
||||
int vi = 0;
|
||||
for (int r = 0; r < 4; r++) {
|
||||
for (int r = 0; r < 4; r++)
|
||||
{
|
||||
vi = generateRingVertices(segments, vertices, vi, Math.PI * 0.5);
|
||||
}
|
||||
return vertices;
|
||||
|
||||
return vertices;
|
||||
}
|
||||
|
||||
private static int generateRingVertices(int segments, float[] vertices, int vi, double theta) {
|
||||
private static int generateRingVertices(int segments, float[] vertices, int vi, double theta)
|
||||
{
|
||||
double cosTheta = Math.Cos(theta);
|
||||
double sinTheta = Math.Sin(theta);
|
||||
for (int p = 0; p <= segments; p++) {
|
||||
for (int p = 0; p <= segments; p++)
|
||||
{
|
||||
double phi = 2 * Math.PI * p / segments;
|
||||
double cosPhi = Math.Cos(phi);
|
||||
double sinPhi = Math.Sin(phi);
|
||||
|
@ -61,14 +72,17 @@ public class GizmoHelper {
|
|||
vertices[vi++] = (float)cosTheta;
|
||||
vertices[vi++] = (float)(sinTheta * sinPhi);
|
||||
}
|
||||
|
||||
return vi;
|
||||
}
|
||||
|
||||
public static int[] generateSphericalTriangles() {
|
||||
public static int[] generateSphericalTriangles()
|
||||
{
|
||||
return generateSphericalTriangles(SEGMENTS, RINGS);
|
||||
}
|
||||
|
||||
private static int[] generateSphericalTriangles(int segments, int rings) {
|
||||
private static int[] generateSphericalTriangles(int segments, int rings)
|
||||
{
|
||||
int[] triangles = new int[6 * (segments + rings * (segments + 1))];
|
||||
int ti = generateSphereUpperCapTriangles(segments, triangles, 0);
|
||||
ti = generateRingTriangles(segments, rings, triangles, 1, ti);
|
||||
|
@ -76,9 +90,12 @@ public class GizmoHelper {
|
|||
return triangles;
|
||||
}
|
||||
|
||||
public static int generateRingTriangles(int segments, int rings, int[] triangles, int vertexOffset, int ti) {
|
||||
for (int r = 0; r < rings; r++) {
|
||||
for (int p = 0; p < segments; p++) {
|
||||
public static int generateRingTriangles(int segments, int rings, int[] triangles, int vertexOffset, int ti)
|
||||
{
|
||||
for (int r = 0; r < rings; r++)
|
||||
{
|
||||
for (int p = 0; p < segments; p++)
|
||||
{
|
||||
int current = p + r * (segments + 1) + vertexOffset;
|
||||
int next = p + 1 + r * (segments + 1) + vertexOffset;
|
||||
int currentBottom = p + (r + 1) * (segments + 1) + vertexOffset;
|
||||
|
@ -91,32 +108,40 @@ public class GizmoHelper {
|
|||
triangles[ti++] = currentBottom;
|
||||
}
|
||||
}
|
||||
|
||||
return ti;
|
||||
}
|
||||
|
||||
private static int generateSphereUpperCapTriangles(int segments, int[] triangles, int ti) {
|
||||
for (int p = 0; p < segments; p++) {
|
||||
private static int generateSphereUpperCapTriangles(int segments, int[] triangles, int ti)
|
||||
{
|
||||
for (int p = 0; p < segments; p++)
|
||||
{
|
||||
triangles[ti++] = p + 2;
|
||||
triangles[ti++] = p + 1;
|
||||
triangles[ti++] = 0;
|
||||
}
|
||||
|
||||
return ti;
|
||||
}
|
||||
|
||||
private static void generateSphereLowerCapTriangles(int segments, int rings, int[] triangles, int ti) {
|
||||
private static void generateSphereLowerCapTriangles(int segments, int rings, int[] triangles, int ti)
|
||||
{
|
||||
int lastVertex = 1 + (segments + 1) * (rings + 1);
|
||||
for (int p = 0; p < segments; p++) {
|
||||
for (int p = 0; p < segments; p++)
|
||||
{
|
||||
triangles[ti++] = lastVertex;
|
||||
triangles[ti++] = lastVertex - (p + 2);
|
||||
triangles[ti++] = lastVertex - (p + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static int[] generateCylindricalTriangles() {
|
||||
public static int[] generateCylindricalTriangles()
|
||||
{
|
||||
return generateCylindricalTriangles(SEGMENTS);
|
||||
}
|
||||
|
||||
private static int[] generateCylindricalTriangles(int segments) {
|
||||
private static int[] generateCylindricalTriangles(int segments)
|
||||
{
|
||||
int circleTriangles = segments - 2;
|
||||
int[] triangles = new int[6 * (circleTriangles + (segments + 1))];
|
||||
int vi = 0;
|
||||
|
@ -127,28 +152,37 @@ public class GizmoHelper {
|
|||
return triangles;
|
||||
}
|
||||
|
||||
private static int generateCircleTriangles(int segments, int[] triangles, int vi, int ti, bool invert) {
|
||||
for (int p = 0; p < segments - 2; p++) {
|
||||
if (invert) {
|
||||
private static int generateCircleTriangles(int segments, int[] triangles, int vi, int ti, bool invert)
|
||||
{
|
||||
for (int p = 0; p < segments - 2; p++)
|
||||
{
|
||||
if (invert)
|
||||
{
|
||||
triangles[ti++] = vi;
|
||||
triangles[ti++] = vi + p + 1;
|
||||
triangles[ti++] = vi + p + 2;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
triangles[ti++] = vi + p + 2;
|
||||
triangles[ti++] = vi + p + 1;
|
||||
triangles[ti++] = vi;
|
||||
}
|
||||
}
|
||||
|
||||
return ti;
|
||||
}
|
||||
|
||||
public static int getColorByNormal(float[] vertices, int v0, int v1, int v2) {
|
||||
public static int getColorByNormal(float[] vertices, int v0, int v1, int v2)
|
||||
{
|
||||
float[] e0 = new float[3], e1 = new float[3];
|
||||
float[] normal = new float[3];
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
for (int j = 0; j < 3; ++j)
|
||||
{
|
||||
e0[j] = vertices[v1 + j] - vertices[v0 + j];
|
||||
e1[j] = vertices[v2 + j] - vertices[v0 + j];
|
||||
}
|
||||
|
||||
normal[0] = e0[1] * e1[2] - e0[2] * e1[1];
|
||||
normal[1] = e0[2] * e1[0] - e0[0] * e1[2];
|
||||
normal[2] = e0[0] * e1[1] - e0[1] * e1[0];
|
||||
|
@ -158,5 +192,4 @@ public class GizmoHelper {
|
|||
(int)(127 * (1 + c)));
|
||||
return col;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,29 +1,32 @@
|
|||
using DotRecast.Recast.Demo.Draw;
|
||||
|
||||
using static DotRecast.Detour.DetourCommon;
|
||||
using static DotRecast.Recast.Demo.Tools.Gizmos.GizmoHelper;
|
||||
|
||||
|
||||
namespace DotRecast.Recast.Demo.Tools.Gizmos;
|
||||
|
||||
|
||||
public class SphereGizmo : ColliderGizmo {
|
||||
public class SphereGizmo : ColliderGizmo
|
||||
{
|
||||
private readonly float[] vertices;
|
||||
private readonly int[] triangles;
|
||||
private readonly float radius;
|
||||
private readonly float[] center;
|
||||
|
||||
public SphereGizmo(float[] center, float radius) {
|
||||
public SphereGizmo(float[] center, float radius)
|
||||
{
|
||||
this.center = center;
|
||||
this.radius = radius;
|
||||
vertices = generateSphericalVertices();
|
||||
triangles = generateSphericalTriangles();
|
||||
}
|
||||
|
||||
public void render(RecastDebugDraw debugDraw) {
|
||||
public void render(RecastDebugDraw debugDraw)
|
||||
{
|
||||
debugDraw.begin(DebugDrawPrimitives.TRIS);
|
||||
for (int i = 0; i < triangles.Length; i += 3) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int i = 0; i < triangles.Length; i += 3)
|
||||
{
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
int v = triangles[i + j] * 3;
|
||||
float c = clamp(0.57735026f * (vertices[v] + vertices[v + 1] + vertices[v + 2]), -1, 1);
|
||||
int col = DebugDraw.duLerpCol(DebugDraw.duRGBA(32, 32, 0, 160), DebugDraw.duRGBA(220, 220, 0, 160),
|
||||
|
@ -32,7 +35,7 @@ public class SphereGizmo : ColliderGizmo {
|
|||
radius * vertices[v + 2] + center[2], col);
|
||||
}
|
||||
}
|
||||
|
||||
debugDraw.end();
|
||||
}
|
||||
|
||||
}
|
|
@ -2,18 +2,22 @@
|
|||
|
||||
namespace DotRecast.Recast.Demo.Tools.Gizmos;
|
||||
|
||||
public class TrimeshGizmo : ColliderGizmo {
|
||||
public class TrimeshGizmo : ColliderGizmo
|
||||
{
|
||||
private readonly float[] vertices;
|
||||
private readonly int[] triangles;
|
||||
|
||||
public TrimeshGizmo(float[] vertices, int[] triangles) {
|
||||
public TrimeshGizmo(float[] vertices, int[] triangles)
|
||||
{
|
||||
this.vertices = vertices;
|
||||
this.triangles = triangles;
|
||||
}
|
||||
|
||||
public void render(RecastDebugDraw debugDraw) {
|
||||
public void render(RecastDebugDraw debugDraw)
|
||||
{
|
||||
debugDraw.begin(DebugDrawPrimitives.TRIS);
|
||||
for (int i = 0; i < triangles.Length; i += 3) {
|
||||
for (int i = 0; i < triangles.Length; i += 3)
|
||||
{
|
||||
int v0 = 3 * triangles[i];
|
||||
int v1 = 3 * triangles[i + 1];
|
||||
int v2 = 3 * triangles[i + 2];
|
||||
|
@ -22,7 +26,7 @@ public class TrimeshGizmo : ColliderGizmo {
|
|||
debugDraw.vertex(vertices[v1], vertices[v1 + 1], vertices[v1 + 2], col);
|
||||
debugDraw.vertex(vertices[v2], vertices[v2 + 1], vertices[v2 + 2], col);
|
||||
}
|
||||
|
||||
debugDraw.end();
|
||||
}
|
||||
|
||||
}
|
|
@ -22,60 +22,69 @@ using DotRecast.Detour.Extras.Jumplink;
|
|||
using DotRecast.Recast.Demo.Builder;
|
||||
using DotRecast.Recast.Demo.Draw;
|
||||
using DotRecast.Recast.Demo.Geom;
|
||||
|
||||
using static DotRecast.Detour.DetourCommon;
|
||||
using static DotRecast.Recast.Demo.Draw.DebugDraw;
|
||||
using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives;
|
||||
|
||||
namespace DotRecast.Recast.Demo.Tools;
|
||||
|
||||
public class JumpLinkBuilderTool : Tool {
|
||||
|
||||
public class JumpLinkBuilderTool : Tool
|
||||
{
|
||||
private readonly List<JumpLink> links = new();
|
||||
private Sample sample;
|
||||
private JumpLinkBuilder annotationBuilder;
|
||||
private readonly int selEdge = -1;
|
||||
private readonly JumpLinkBuilderToolParams option = new JumpLinkBuilderToolParams();
|
||||
|
||||
public override void setSample(Sample sample) {
|
||||
public override void setSample(Sample sample)
|
||||
{
|
||||
this.sample = sample;
|
||||
annotationBuilder = null;
|
||||
}
|
||||
|
||||
public override void handleClick(float[] s, float[] p, bool shift) {
|
||||
|
||||
public override void handleClick(float[] s, float[] p, bool shift)
|
||||
{
|
||||
}
|
||||
|
||||
public override void handleRender(NavMeshRenderer renderer) {
|
||||
public override void handleRender(NavMeshRenderer renderer)
|
||||
{
|
||||
int col0 = duLerpCol(duRGBA(32, 255, 96, 255), duRGBA(255, 255, 255, 255), 200);
|
||||
int col1 = duRGBA(32, 255, 96, 255);
|
||||
RecastDebugDraw dd = renderer.getDebugDraw();
|
||||
dd.depthMask(false);
|
||||
|
||||
if ((option.flags & JumpLinkBuilderToolParams.DRAW_WALKABLE_BORDER) != 0) {
|
||||
if (annotationBuilder != null) {
|
||||
foreach (Edge[] edges in annotationBuilder.getEdges()) {
|
||||
if ((option.flags & JumpLinkBuilderToolParams.DRAW_WALKABLE_BORDER) != 0)
|
||||
{
|
||||
if (annotationBuilder != null)
|
||||
{
|
||||
foreach (Edge[] edges in annotationBuilder.getEdges())
|
||||
{
|
||||
dd.begin(LINES, 3.0f);
|
||||
for (int i = 0; i < edges.Length; ++i) {
|
||||
for (int i = 0; i < edges.Length; ++i)
|
||||
{
|
||||
int col = duRGBA(0, 96, 128, 255);
|
||||
if (i == selEdge)
|
||||
continue;
|
||||
dd.vertex(edges[i].sp, col);
|
||||
dd.vertex(edges[i].sq, col);
|
||||
}
|
||||
|
||||
dd.end();
|
||||
|
||||
dd.begin(POINTS, 8.0f);
|
||||
for (int i = 0; i < edges.Length; ++i) {
|
||||
for (int i = 0; i < edges.Length; ++i)
|
||||
{
|
||||
int col = duRGBA(0, 96, 128, 255);
|
||||
if (i == selEdge)
|
||||
continue;
|
||||
dd.vertex(edges[i].sp, col);
|
||||
dd.vertex(edges[i].sq, col);
|
||||
}
|
||||
|
||||
dd.end();
|
||||
|
||||
if (selEdge >= 0 && selEdge < edges.Length) {
|
||||
if (selEdge >= 0 && selEdge < edges.Length)
|
||||
{
|
||||
int col = duRGBA(48, 16, 16, 255); // duRGBA(255,192,0,255);
|
||||
dd.begin(LINES, 3.0f);
|
||||
dd.vertex(edges[selEdge].sp, col);
|
||||
|
@ -88,20 +97,25 @@ public class JumpLinkBuilderTool : Tool {
|
|||
}
|
||||
|
||||
dd.begin(POINTS, 4.0f);
|
||||
for (int i = 0; i < edges.Length; ++i) {
|
||||
for (int i = 0; i < edges.Length; ++i)
|
||||
{
|
||||
int col = duRGBA(190, 190, 190, 255);
|
||||
dd.vertex(edges[i].sp, col);
|
||||
dd.vertex(edges[i].sq, col);
|
||||
}
|
||||
|
||||
dd.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((option.flags & JumpLinkBuilderToolParams.DRAW_ANNOTATIONS) != 0) {
|
||||
if ((option.flags & JumpLinkBuilderToolParams.DRAW_ANNOTATIONS) != 0)
|
||||
{
|
||||
dd.begin(QUADS);
|
||||
foreach (JumpLink link in links) {
|
||||
for (int j = 0; j < link.nspine - 1; ++j) {
|
||||
foreach (JumpLink link in links)
|
||||
{
|
||||
for (int j = 0; j < link.nspine - 1; ++j)
|
||||
{
|
||||
int u = (j * 255) / link.nspine;
|
||||
int col = duTransCol(duLerpCol(col0, col1, u), 128);
|
||||
dd.vertex(link.spine1[j * 3], link.spine1[j * 3 + 1], link.spine1[j * 3 + 2], col);
|
||||
|
@ -112,10 +126,13 @@ public class JumpLinkBuilderTool : Tool {
|
|||
dd.vertex(link.spine0[j * 3], link.spine0[j * 3 + 1], link.spine0[j * 3 + 2], col);
|
||||
}
|
||||
}
|
||||
|
||||
dd.end();
|
||||
dd.begin(LINES, 3.0f);
|
||||
foreach (JumpLink link in links) {
|
||||
for (int j = 0; j < link.nspine - 1; ++j) {
|
||||
foreach (JumpLink link in links)
|
||||
{
|
||||
for (int j = 0; j < link.nspine - 1; ++j)
|
||||
{
|
||||
// int u = (j*255)/link.nspine;
|
||||
int col = duTransCol(duDarkenCol(col1) /*duDarkenCol(duLerpCol(col0,col1,u))*/, 128);
|
||||
|
||||
|
@ -135,11 +152,16 @@ public class JumpLinkBuilderTool : Tool {
|
|||
dd.vertex(link.spine1[(link.nspine - 1) * 3], link.spine1[(link.nspine - 1) * 3 + 1],
|
||||
link.spine1[(link.nspine - 1) * 3 + 2], duDarkenCol(col1));
|
||||
}
|
||||
|
||||
dd.end();
|
||||
}
|
||||
if (annotationBuilder != null) {
|
||||
foreach (JumpLink link in links) {
|
||||
if ((option.flags & JumpLinkBuilderToolParams.DRAW_ANIM_TRAJECTORY) != 0) {
|
||||
|
||||
if (annotationBuilder != null)
|
||||
{
|
||||
foreach (JumpLink link in links)
|
||||
{
|
||||
if ((option.flags & JumpLinkBuilderToolParams.DRAW_ANIM_TRAJECTORY) != 0)
|
||||
{
|
||||
float r = link.start.height;
|
||||
|
||||
int col = duLerpCol(duRGBA(255, 192, 0, 255),
|
||||
|
@ -203,84 +225,104 @@ public class JumpLinkBuilderTool : Tool {
|
|||
dd.vertex(end.q, colm);
|
||||
dd.end();
|
||||
}
|
||||
if ((option.flags & JumpLinkBuilderToolParams.DRAW_LAND_SAMPLES) != 0) {
|
||||
|
||||
if ((option.flags & JumpLinkBuilderToolParams.DRAW_LAND_SAMPLES) != 0)
|
||||
{
|
||||
dd.begin(POINTS, 8.0f);
|
||||
for (int i = 0; i < link.start.gsamples.Length; ++i) {
|
||||
for (int i = 0; i < link.start.gsamples.Length; ++i)
|
||||
{
|
||||
GroundSample s = link.start.gsamples[i];
|
||||
float u = i / (float)(link.start.gsamples.Length - 1);
|
||||
float[] spt = vLerp(link.start.p, link.start.q, u);
|
||||
int col = duRGBA(48, 16, 16, 255); // duRGBA(255,(s->flags & 4)?255:0,0,255);
|
||||
float off = 0.1f;
|
||||
if (!s.validHeight) {
|
||||
if (!s.validHeight)
|
||||
{
|
||||
off = 0;
|
||||
col = duRGBA(220, 32, 32, 255);
|
||||
}
|
||||
|
||||
spt[1] = s.p[1] + off;
|
||||
dd.vertex(spt, col);
|
||||
}
|
||||
|
||||
dd.end();
|
||||
|
||||
dd.begin(POINTS, 4.0f);
|
||||
for (int i = 0; i < link.start.gsamples.Length; ++i) {
|
||||
for (int i = 0; i < link.start.gsamples.Length; ++i)
|
||||
{
|
||||
GroundSample s = link.start.gsamples[i];
|
||||
float u = i / (float)(link.start.gsamples.Length - 1);
|
||||
float[] spt = vLerp(link.start.p, link.start.q, u);
|
||||
int col = duRGBA(255, 255, 255, 255);
|
||||
float off = 0;
|
||||
if (s.validHeight) {
|
||||
if (s.validHeight)
|
||||
{
|
||||
off = 0.1f;
|
||||
}
|
||||
|
||||
spt[1] = s.p[1] + off;
|
||||
dd.vertex(spt, col);
|
||||
}
|
||||
|
||||
dd.end();
|
||||
{
|
||||
GroundSegment end = link.end;
|
||||
dd.begin(POINTS, 8.0f);
|
||||
for (int i = 0; i < end.gsamples.Length; ++i) {
|
||||
for (int i = 0; i < end.gsamples.Length; ++i)
|
||||
{
|
||||
GroundSample s = end.gsamples[i];
|
||||
float u = i / (float)(end.gsamples.Length - 1);
|
||||
float[] spt = vLerp(end.p, end.q, u);
|
||||
int col = duRGBA(48, 16, 16, 255); // duRGBA(255,(s->flags & 4)?255:0,0,255);
|
||||
float off = 0.1f;
|
||||
if (!s.validHeight) {
|
||||
if (!s.validHeight)
|
||||
{
|
||||
off = 0;
|
||||
col = duRGBA(220, 32, 32, 255);
|
||||
}
|
||||
|
||||
spt[1] = s.p[1] + off;
|
||||
dd.vertex(spt, col);
|
||||
}
|
||||
|
||||
dd.end();
|
||||
dd.begin(POINTS, 4.0f);
|
||||
for (int i = 0; i < end.gsamples.Length; ++i) {
|
||||
for (int i = 0; i < end.gsamples.Length; ++i)
|
||||
{
|
||||
GroundSample s = end.gsamples[i];
|
||||
float u = i / (float)(end.gsamples.Length - 1);
|
||||
float[] spt = vLerp(end.p, end.q, u);
|
||||
int col = duRGBA(255, 255, 255, 255);
|
||||
float off = 0;
|
||||
if (s.validHeight) {
|
||||
if (s.validHeight)
|
||||
{
|
||||
off = 0.1f;
|
||||
}
|
||||
|
||||
spt[1] = s.p[1] + off;
|
||||
dd.vertex(spt, col);
|
||||
}
|
||||
|
||||
dd.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dd.depthMask(true);
|
||||
}
|
||||
|
||||
private void drawTrajectory(RecastDebugDraw dd, JumpLink link, float[] pa, float[] pb, Trajectory tra, int cola) {
|
||||
|
||||
private void drawTrajectory(RecastDebugDraw dd, JumpLink link, float[] pa, float[] pb, Trajectory tra, int cola)
|
||||
{
|
||||
}
|
||||
|
||||
public override void handleUpdate(float dt) {
|
||||
|
||||
public override void handleUpdate(float dt)
|
||||
{
|
||||
}
|
||||
|
||||
public override void layout(IWindow ctx) {
|
||||
public override void layout(IWindow ctx)
|
||||
{
|
||||
// if (!sample.getRecastResults().isEmpty()) {
|
||||
//
|
||||
// nk_layout_row_dynamic(ctx, 18, 1);
|
||||
|
@ -403,25 +445,27 @@ public class JumpLinkBuilderTool : Tool {
|
|||
// : 0;
|
||||
// params.flags = newFlags;
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
private void addOffMeshLink(JumpLink link, DemoInputGeomProvider geom, float agentRadius) {
|
||||
private void addOffMeshLink(JumpLink link, DemoInputGeomProvider geom, float agentRadius)
|
||||
{
|
||||
int area = SampleAreaModifications.SAMPLE_POLYAREA_TYPE_JUMP_AUTO;
|
||||
int flags = SampleAreaModifications.SAMPLE_POLYFLAGS_JUMP;
|
||||
float[] prev = new float[3];
|
||||
for (int i = 0; i < link.startSamples.Length; i++) {
|
||||
for (int i = 0; i < link.startSamples.Length; i++)
|
||||
{
|
||||
float[] p = link.startSamples[i].p;
|
||||
float[] q = link.endSamples[i].p;
|
||||
if (i == 0 || vDist2D(prev, p) > agentRadius) {
|
||||
if (i == 0 || vDist2D(prev, p) > agentRadius)
|
||||
{
|
||||
geom.addOffMeshConnection(p, q, agentRadius, false, area, flags);
|
||||
prev = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string getName() {
|
||||
public override string getName()
|
||||
{
|
||||
return "Annotation Builder";
|
||||
}
|
||||
|
||||
}
|
|
@ -20,9 +20,8 @@ using DotRecast.Detour.Extras.Jumplink;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Tools;
|
||||
|
||||
|
||||
public class JumpLinkBuilderToolParams {
|
||||
|
||||
public class JumpLinkBuilderToolParams
|
||||
{
|
||||
public const int DRAW_WALKABLE_SURFACE = 1 << 0;
|
||||
public const int DRAW_WALKABLE_BORDER = 1 << 1;
|
||||
public const int DRAW_SELECTED_EDGE = 1 << 2;
|
||||
|
@ -41,5 +40,4 @@ public class JumpLinkBuilderToolParams {
|
|||
public readonly float[] edgeJumpDownMaxHeight = new[] { 2.5f };
|
||||
public readonly float[] edgeJumpUpMaxHeight = new[] { 0.3f };
|
||||
public int buildTypes = (1 << (int)JumpLinkType.EDGE_CLIMB_DOWN) | (1 << (int)JumpLinkType.EDGE_JUMP);
|
||||
|
||||
}
|
|
@ -24,87 +24,106 @@ using DotRecast.Core;
|
|||
using DotRecast.Recast.Demo.Builder;
|
||||
using DotRecast.Recast.Demo.Draw;
|
||||
using DotRecast.Recast.Demo.Geom;
|
||||
|
||||
using static DotRecast.Recast.Demo.Draw.DebugDraw;
|
||||
|
||||
namespace DotRecast.Recast.Demo.Tools;
|
||||
public class OffMeshConnectionTool : Tool {
|
||||
|
||||
public class OffMeshConnectionTool : Tool
|
||||
{
|
||||
private Sample sample;
|
||||
private bool hitPosSet;
|
||||
private float[] hitPos;
|
||||
private bool bidir;
|
||||
|
||||
public override void setSample(Sample m_sample) {
|
||||
public override void setSample(Sample m_sample)
|
||||
{
|
||||
sample = m_sample;
|
||||
}
|
||||
|
||||
public override void handleClick(float[] s, float[] p, bool shift) {
|
||||
public override void handleClick(float[] s, float[] p, bool shift)
|
||||
{
|
||||
DemoInputGeomProvider geom = sample.getInputGeom();
|
||||
if (geom == null) {
|
||||
if (geom == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (shift) {
|
||||
if (shift)
|
||||
{
|
||||
// Delete
|
||||
// Find nearest link end-point
|
||||
float nearestDist = float.MaxValue;
|
||||
DemoOffMeshConnection nearestConnection = null;
|
||||
foreach (DemoOffMeshConnection offMeshCon in geom.getOffMeshConnections()) {
|
||||
foreach (DemoOffMeshConnection offMeshCon in geom.getOffMeshConnections())
|
||||
{
|
||||
float d = Math.Min(DemoMath.vDistSqr(p, offMeshCon.verts, 0), DemoMath.vDistSqr(p, offMeshCon.verts, 3));
|
||||
if (d < nearestDist && Math.Sqrt(d) < sample.getSettingsUI().getAgentRadius()) {
|
||||
if (d < nearestDist && Math.Sqrt(d) < sample.getSettingsUI().getAgentRadius())
|
||||
{
|
||||
nearestDist = d;
|
||||
nearestConnection = offMeshCon;
|
||||
}
|
||||
}
|
||||
if (nearestConnection != null) {
|
||||
|
||||
if (nearestConnection != null)
|
||||
{
|
||||
geom.getOffMeshConnections().Remove(nearestConnection);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create
|
||||
if (!hitPosSet) {
|
||||
if (!hitPosSet)
|
||||
{
|
||||
hitPos = ArrayUtils.CopyOf(p, p.Length);
|
||||
hitPosSet = true;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
int area = SampleAreaModifications.SAMPLE_POLYAREA_TYPE_JUMP;
|
||||
int flags = SampleAreaModifications.SAMPLE_POLYFLAGS_JUMP;
|
||||
geom.addOffMeshConnection(hitPos, p, sample.getSettingsUI().getAgentRadius(), bidir, area, flags);
|
||||
hitPosSet = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void handleRender(NavMeshRenderer renderer) {
|
||||
if (sample == null) {
|
||||
public override void handleRender(NavMeshRenderer renderer)
|
||||
{
|
||||
if (sample == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RecastDebugDraw dd = renderer.getDebugDraw();
|
||||
float s = sample.getSettingsUI().getAgentRadius();
|
||||
|
||||
if (hitPosSet) {
|
||||
if (hitPosSet)
|
||||
{
|
||||
dd.debugDrawCross(hitPos[0], hitPos[1] + 0.1f, hitPos[2], s, duRGBA(0, 0, 0, 128), 2.0f);
|
||||
}
|
||||
|
||||
DemoInputGeomProvider geom = sample.getInputGeom();
|
||||
if (geom != null) {
|
||||
if (geom != null)
|
||||
{
|
||||
renderer.drawOffMeshConnections(geom, true);
|
||||
}
|
||||
}
|
||||
|
||||
public override void layout(IWindow ctx) {
|
||||
public override void layout(IWindow ctx)
|
||||
{
|
||||
// nk_layout_row_dynamic(ctx, 20, 1);
|
||||
// bidir = !nk_option_label(ctx, "One Way", !bidir);
|
||||
// nk_layout_row_dynamic(ctx, 20, 1);
|
||||
// bidir = nk_option_label(ctx, "Bidirectional", bidir);
|
||||
}
|
||||
|
||||
public override string getName() {
|
||||
public override string getName()
|
||||
{
|
||||
return "Create Off-Mesh Links";
|
||||
}
|
||||
|
||||
public override void handleUpdate(float dt) {
|
||||
public override void handleUpdate(float dt)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -24,22 +24,25 @@ using DotRecast.Detour;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Tools;
|
||||
|
||||
|
||||
public static class PathUtils {
|
||||
|
||||
public static class PathUtils
|
||||
{
|
||||
private readonly static int MAX_STEER_POINTS = 3;
|
||||
|
||||
|
||||
public static SteerTarget getSteerTarget(NavMeshQuery navQuery, float[] startPos, float[] endPos,
|
||||
float minTargetDist, List<long> path) {
|
||||
float minTargetDist, List<long> path)
|
||||
{
|
||||
// Find steer target.
|
||||
Result<List<StraightPathItem>> result = navQuery.findStraightPath(startPos, endPos, path, MAX_STEER_POINTS, 0);
|
||||
if (result.failed()) {
|
||||
if (result.failed())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
List<StraightPathItem> straightPath = result.result;
|
||||
float[] steerPoints = new float[straightPath.Count * 3];
|
||||
for (int i = 0; i < straightPath.Count; i++) {
|
||||
for (int i = 0; i < straightPath.Count; i++)
|
||||
{
|
||||
steerPoints[i * 3] = straightPath[i].getPos()[0];
|
||||
steerPoints[i * 3 + 1] = straightPath[i].getPos()[1];
|
||||
steerPoints[i * 3 + 2] = straightPath[i].getPos()[2];
|
||||
|
@ -47,48 +50,58 @@ public static class PathUtils {
|
|||
|
||||
// Find vertex far enough to steer to.
|
||||
int ns = 0;
|
||||
while (ns < straightPath.Count) {
|
||||
while (ns < straightPath.Count)
|
||||
{
|
||||
// Stop at Off-Mesh link or when point is further than slop away.
|
||||
if (((straightPath[ns].getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
|
||||
|| !inRange(straightPath[ns].getPos(), startPos, minTargetDist, 1000.0f))
|
||||
break;
|
||||
ns++;
|
||||
}
|
||||
|
||||
// Failed to find good point to steer to.
|
||||
if (ns >= straightPath.Count)
|
||||
return null;
|
||||
|
||||
float[] steerPos = new float[] { straightPath[ns].getPos()[0], startPos[1],
|
||||
straightPath[ns].getPos()[2] };
|
||||
float[] steerPos = new float[]
|
||||
{
|
||||
straightPath[ns].getPos()[0], startPos[1],
|
||||
straightPath[ns].getPos()[2]
|
||||
};
|
||||
int steerPosFlag = straightPath[ns].getFlags();
|
||||
long steerPosRef = straightPath[ns].getRef();
|
||||
|
||||
SteerTarget target = new SteerTarget(steerPos, steerPosFlag, steerPosRef, steerPoints);
|
||||
return target;
|
||||
|
||||
}
|
||||
|
||||
public static bool inRange(float[] v1, float[] v2, float r, float h) {
|
||||
public static bool inRange(float[] v1, float[] v2, float r, float h)
|
||||
{
|
||||
float dx = v2[0] - v1[0];
|
||||
float dy = v2[1] - v1[1];
|
||||
float dz = v2[2] - v1[2];
|
||||
return (dx * dx + dz * dz) < r * r && Math.Abs(dy) < h;
|
||||
}
|
||||
|
||||
public static List<long> fixupCorridor(List<long> path, List<long> visited) {
|
||||
public static List<long> fixupCorridor(List<long> path, List<long> visited)
|
||||
{
|
||||
int furthestPath = -1;
|
||||
int furthestVisited = -1;
|
||||
|
||||
// Find furthest common polygon.
|
||||
for (int i = path.Count - 1; i >= 0; --i) {
|
||||
for (int i = path.Count - 1; i >= 0; --i)
|
||||
{
|
||||
bool found = false;
|
||||
for (int j = visited.Count - 1; j >= 0; --j) {
|
||||
if (path[i] == visited[j]) {
|
||||
for (int j = visited.Count - 1; j >= 0; --j)
|
||||
{
|
||||
if (path[i] == visited[j])
|
||||
{
|
||||
furthestPath = i;
|
||||
furthestVisited = j;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
|
@ -105,10 +118,13 @@ public static class PathUtils {
|
|||
int size = Math.Max(0, path.Count - orig);
|
||||
List<long> fixupPath = new();
|
||||
// Store visited
|
||||
for (int i = 0; i < req; ++i) {
|
||||
for (int i = 0; i < req; ++i)
|
||||
{
|
||||
fixupPath.Add(visited[(visited.Count - 1) - i]);
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
fixupPath.Add(path[orig + i]);
|
||||
}
|
||||
|
||||
|
@ -126,8 +142,10 @@ public static class PathUtils {
|
|||
// +-S-+-T-+
|
||||
// |:::| | <-- the step can end up in here, resulting U-turn path.
|
||||
// +---+---+
|
||||
public static List<long> fixupShortcuts(List<long> path, NavMeshQuery navQuery) {
|
||||
if (path.Count < 3) {
|
||||
public static List<long> fixupShortcuts(List<long> path, NavMeshQuery navQuery)
|
||||
{
|
||||
if (path.Count < 3)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
|
@ -135,15 +153,19 @@ public static class PathUtils {
|
|||
List<long> neis = new();
|
||||
|
||||
Result<Tuple<MeshTile, Poly>> tileAndPoly = navQuery.getAttachedNavMesh().getTileAndPolyByRef(path[0]);
|
||||
if (tileAndPoly.failed()) {
|
||||
if (tileAndPoly.failed())
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
MeshTile tile = tileAndPoly.result.Item1;
|
||||
Poly poly = tileAndPoly.result.Item2;
|
||||
|
||||
for (int k = tile.polyLinks[poly.index]; k != NavMesh.DT_NULL_LINK; k = tile.links[k].next) {
|
||||
for (int k = tile.polyLinks[poly.index]; k != NavMesh.DT_NULL_LINK; k = tile.links[k].next)
|
||||
{
|
||||
Link link = tile.links[k];
|
||||
if (link.refs != 0) {
|
||||
if (link.refs != 0)
|
||||
{
|
||||
neis.Add(link.refs);
|
||||
}
|
||||
}
|
||||
|
@ -152,21 +174,26 @@ public static class PathUtils {
|
|||
// in the path, short cut to that polygon directly.
|
||||
int maxLookAhead = 6;
|
||||
int cut = 0;
|
||||
for (int i = Math.Min(maxLookAhead, path.Count) - 1; i > 1 && cut == 0; i--) {
|
||||
for (int j = 0; j < neis.Count; j++) {
|
||||
if (path[i] == neis[j]) {
|
||||
for (int i = Math.Min(maxLookAhead, path.Count) - 1; i > 1 && cut == 0; i--)
|
||||
{
|
||||
for (int j = 0; j < neis.Count; j++)
|
||||
{
|
||||
if (path[i] == neis[j])
|
||||
{
|
||||
cut = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cut > 1) {
|
||||
|
||||
if (cut > 1)
|
||||
{
|
||||
List<long> shortcut = new();
|
||||
shortcut.Add(path[0]);
|
||||
shortcut.AddRange(path.GetRange(cut, path.Count - cut));
|
||||
return shortcut;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
}
|
|
@ -21,28 +21,34 @@ using System;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Tools;
|
||||
|
||||
public class PolyUtils {
|
||||
|
||||
public static bool pointInPoly(float[] verts, float[] p) {
|
||||
public class PolyUtils
|
||||
{
|
||||
public static bool pointInPoly(float[] verts, float[] p)
|
||||
{
|
||||
int i, j;
|
||||
bool c = false;
|
||||
for (i = 0, j = verts.Length / 3 - 1; i < verts.Length / 3; j = i++) {
|
||||
for (i = 0, j = verts.Length / 3 - 1; i < verts.Length / 3; j = i++)
|
||||
{
|
||||
float[] vi = new float[] { verts[i * 3], verts[i * 3 + 1], verts[i * 3 + 2] };
|
||||
float[] vj = new float[] { verts[j * 3], verts[j * 3 + 1], verts[j * 3 + 2] };
|
||||
if (((vi[2] > p[2]) != (vj[2] > p[2]))
|
||||
&& (p[0] < (vj[0] - vi[0]) * (p[2] - vi[2]) / (vj[2] - vi[2]) + vi[0])) {
|
||||
&& (p[0] < (vj[0] - vi[0]) * (p[2] - vi[2]) / (vj[2] - vi[2]) + vi[0]))
|
||||
{
|
||||
c = !c;
|
||||
}
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
public static int offsetPoly(float[] verts, int nverts, float offset, float[] outVerts, int maxOutVerts) {
|
||||
public static int offsetPoly(float[] verts, int nverts, float offset, float[] outVerts, int maxOutVerts)
|
||||
{
|
||||
float MITER_LIMIT = 1.20f;
|
||||
|
||||
int n = 0;
|
||||
|
||||
for (int i = 0; i < nverts; i++) {
|
||||
for (int i = 0; i < nverts; i++)
|
||||
{
|
||||
int a = (i + nverts - 1) % nverts;
|
||||
int b = i;
|
||||
int c = (i + 1) % nverts;
|
||||
|
@ -52,19 +58,23 @@ public class PolyUtils {
|
|||
float dx0 = verts[vb] - verts[va];
|
||||
float dy0 = verts[vb + 2] - verts[va + 2];
|
||||
float d0 = dx0 * dx0 + dy0 * dy0;
|
||||
if (d0 > 1e-6f) {
|
||||
if (d0 > 1e-6f)
|
||||
{
|
||||
d0 = (float)(1.0f / Math.Sqrt(d0));
|
||||
dx0 *= d0;
|
||||
dy0 *= d0;
|
||||
}
|
||||
|
||||
float dx1 = verts[vc] - verts[vb];
|
||||
float dy1 = verts[vc + 2] - verts[vb + 2];
|
||||
float d1 = dx1 * dx1 + dy1 * dy1;
|
||||
if (d1 > 1e-6f) {
|
||||
if (d1 > 1e-6f)
|
||||
{
|
||||
d1 = (float)(1.0f / Math.Sqrt(d1));
|
||||
dx1 *= d1;
|
||||
dy1 *= d1;
|
||||
}
|
||||
|
||||
float dlx0 = -dy0;
|
||||
float dly0 = dx0;
|
||||
float dlx1 = -dy1;
|
||||
|
@ -74,16 +84,20 @@ public class PolyUtils {
|
|||
float dmy = (dly0 + dly1) * 0.5f;
|
||||
float dmr2 = dmx * dmx + dmy * dmy;
|
||||
bool bevel = dmr2 * MITER_LIMIT * MITER_LIMIT < 1.0f;
|
||||
if (dmr2 > 1e-6f) {
|
||||
if (dmr2 > 1e-6f)
|
||||
{
|
||||
float scale = 1.0f / dmr2;
|
||||
dmx *= scale;
|
||||
dmy *= scale;
|
||||
}
|
||||
|
||||
if (bevel && cross < 0.0f) {
|
||||
if (n + 2 >= maxOutVerts) {
|
||||
if (bevel && cross < 0.0f)
|
||||
{
|
||||
if (n + 2 >= maxOutVerts)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
float d = (1.0f - (dx0 * dx1 + dy0 * dy1)) * 0.5f;
|
||||
outVerts[n * 3 + 0] = verts[vb] + (-dlx0 + dx0 * d) * offset;
|
||||
outVerts[n * 3 + 1] = verts[vb + 1];
|
||||
|
@ -93,10 +107,14 @@ public class PolyUtils {
|
|||
outVerts[n * 3 + 1] = verts[vb + 1];
|
||||
outVerts[n * 3 + 2] = verts[vb + 2] + (-dly1 - dy1 * d) * offset;
|
||||
n++;
|
||||
} else {
|
||||
if (n + 1 >= maxOutVerts) {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (n + 1 >= maxOutVerts)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
outVerts[n * 3 + 0] = verts[vb] - dmx * offset;
|
||||
outVerts[n * 3 + 1] = verts[vb + 1];
|
||||
outVerts[n * 3 + 2] = verts[vb + 2] - dmy * offset;
|
||||
|
@ -106,5 +124,4 @@ public class PolyUtils {
|
|||
|
||||
return n;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,12 +1,14 @@
|
|||
namespace DotRecast.Recast.Demo.Tools;
|
||||
|
||||
public class SteerTarget {
|
||||
public class SteerTarget
|
||||
{
|
||||
public readonly float[] steerPos;
|
||||
public readonly int steerPosFlag;
|
||||
public readonly long steerPosRef;
|
||||
public readonly float[] steerPoints;
|
||||
|
||||
public SteerTarget(float[] steerPos, int steerPosFlag, long steerPosRef, float[] steerPoints) {
|
||||
public SteerTarget(float[] steerPos, int steerPosFlag, long steerPosRef, float[] steerPoints)
|
||||
{
|
||||
this.steerPos = steerPos;
|
||||
this.steerPosFlag = steerPosFlag;
|
||||
this.steerPosRef = steerPosRef;
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Silk.NET.Windowing;
|
||||
|
||||
using DotRecast.Core;
|
||||
using DotRecast.Detour;
|
||||
using DotRecast.Recast.Demo.Builder;
|
||||
using DotRecast.Recast.Demo.Draw;
|
||||
|
||||
using static DotRecast.Detour.DetourCommon;
|
||||
using static DotRecast.Recast.Demo.Draw.DebugDraw;
|
||||
using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives;
|
||||
|
||||
namespace DotRecast.Recast.Demo.Tools;
|
||||
|
||||
|
||||
public class TestNavmeshTool : Tool {
|
||||
|
||||
public class TestNavmeshTool : Tool
|
||||
{
|
||||
private readonly static int MAX_POLYS = 256;
|
||||
private readonly static int MAX_SMOOTH = 2048;
|
||||
private Sample m_sample;
|
||||
|
@ -44,7 +41,8 @@ public class TestNavmeshTool : Tool {
|
|||
private readonly List<float[]> randomPoints = new();
|
||||
private bool constrainByCircle;
|
||||
|
||||
private enum ToolMode {
|
||||
private enum ToolMode
|
||||
{
|
||||
PATHFIND_FOLLOW,
|
||||
PATHFIND_STRAIGHT,
|
||||
PATHFIND_SLICED,
|
||||
|
@ -56,27 +54,35 @@ public class TestNavmeshTool : Tool {
|
|||
RANDOM_POINTS_IN_CIRCLE
|
||||
}
|
||||
|
||||
public TestNavmeshTool() {
|
||||
public TestNavmeshTool()
|
||||
{
|
||||
m_filter = new DefaultQueryFilter(SampleAreaModifications.SAMPLE_POLYFLAGS_ALL,
|
||||
SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED, new float[] { 1f, 1f, 1f, 1f, 2f, 1.5f });
|
||||
}
|
||||
|
||||
public override void setSample(Sample m_sample) {
|
||||
public override void setSample(Sample m_sample)
|
||||
{
|
||||
this.m_sample = m_sample;
|
||||
}
|
||||
|
||||
public override void handleClick(float[] s, float[] p, bool shift) {
|
||||
if (shift) {
|
||||
public override void handleClick(float[] s, float[] p, bool shift)
|
||||
{
|
||||
if (shift)
|
||||
{
|
||||
m_sposSet = true;
|
||||
m_spos = ArrayUtils.CopyOf(p, p.Length);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
m_eposSet = true;
|
||||
m_epos = ArrayUtils.CopyOf(p, p.Length);
|
||||
}
|
||||
|
||||
recalc();
|
||||
}
|
||||
|
||||
public override void layout(IWindow ctx) {
|
||||
public override void layout(IWindow ctx)
|
||||
{
|
||||
ToolMode previousToolMode = m_toolMode;
|
||||
int previousStraightPathOptions = m_straightPathOptions;
|
||||
int previousIncludeFlags = m_filter.getIncludeFlags();
|
||||
|
@ -203,31 +209,46 @@ public class TestNavmeshTool : Tool {
|
|||
// }
|
||||
}
|
||||
|
||||
public override string getName() {
|
||||
public override string getName()
|
||||
{
|
||||
return "Test Navmesh";
|
||||
}
|
||||
|
||||
private void recalc() {
|
||||
if (m_sample.getNavMesh() == null) {
|
||||
private void recalc()
|
||||
{
|
||||
if (m_sample.getNavMesh() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
NavMeshQuery m_navQuery = m_sample.getNavMeshQuery();
|
||||
if (m_sposSet) {
|
||||
if (m_sposSet)
|
||||
{
|
||||
m_startRef = m_navQuery.findNearestPoly(m_spos, m_polyPickExt, m_filter).result.getNearestRef();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
m_startRef = 0;
|
||||
}
|
||||
if (m_eposSet) {
|
||||
|
||||
if (m_eposSet)
|
||||
{
|
||||
m_endRef = m_navQuery.findNearestPoly(m_epos, m_polyPickExt, m_filter).result.getNearestRef();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
m_endRef = 0;
|
||||
}
|
||||
|
||||
NavMesh m_navMesh = m_sample.getNavMesh();
|
||||
if (m_toolMode == ToolMode.PATHFIND_FOLLOW) {
|
||||
if (m_sposSet && m_eposSet && m_startRef != 0 && m_endRef != 0) {
|
||||
if (m_toolMode == ToolMode.PATHFIND_FOLLOW)
|
||||
{
|
||||
if (m_sposSet && m_eposSet && m_startRef != 0 && m_endRef != 0)
|
||||
{
|
||||
m_polys = m_navQuery.findPath(m_startRef, m_endRef, m_spos, m_epos, m_filter,
|
||||
enableRaycast ? NavMeshQuery.DT_FINDPATH_ANY_ANGLE : 0, float.MaxValue).result;
|
||||
if (0 < m_polys.Count) {
|
||||
if (0 < m_polys.Count)
|
||||
{
|
||||
List<long> polys = new(m_polys);
|
||||
// Iterate over the path to find smooth path on the detail mesh surface.
|
||||
float[] iterPos = m_navQuery.closestPointOnPoly(m_startRef, m_spos).result.getClosest();
|
||||
|
@ -242,28 +263,37 @@ public class TestNavmeshTool : Tool {
|
|||
|
||||
// Move towards target a small advancement at a time until target reached or
|
||||
// when ran out of memory to store the path.
|
||||
while (0 < polys.Count && m_smoothPath.Count < MAX_SMOOTH) {
|
||||
while (0 < polys.Count && m_smoothPath.Count < MAX_SMOOTH)
|
||||
{
|
||||
// Find location to steer towards.
|
||||
SteerTarget steerTarget = PathUtils.getSteerTarget(m_navQuery, iterPos, targetPos,
|
||||
SLOP, polys);
|
||||
if (null == steerTarget) {
|
||||
if (null == steerTarget)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bool endOfPath = (steerTarget.steerPosFlag & NavMeshQuery.DT_STRAIGHTPATH_END) != 0
|
||||
? true
|
||||
: false;
|
||||
bool offMeshConnection = (steerTarget.steerPosFlag
|
||||
& NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0 ? true : false;
|
||||
& NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0
|
||||
? true
|
||||
: false;
|
||||
|
||||
// Find movement delta.
|
||||
float[] delta = vSub(steerTarget.steerPos, iterPos);
|
||||
float len = (float)Math.Sqrt(DemoMath.vDot(delta, delta));
|
||||
// If the steer target is end of path or off-mesh link, do not move past the location.
|
||||
if ((endOfPath || offMeshConnection) && len < STEP_SIZE) {
|
||||
if ((endOfPath || offMeshConnection) && len < STEP_SIZE)
|
||||
{
|
||||
len = 1;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
len = STEP_SIZE / len;
|
||||
}
|
||||
|
||||
float[] moveTgt = vMad(iterPos, delta, len);
|
||||
// Move
|
||||
Result<MoveAlongSurfaceResult> result = m_navQuery.moveAlongSurface(polys[0], iterPos,
|
||||
|
@ -280,45 +310,57 @@ public class TestNavmeshTool : Tool {
|
|||
polys = PathUtils.fixupShortcuts(polys, m_navQuery);
|
||||
|
||||
Result<float> polyHeight = m_navQuery.getPolyHeight(polys[0], moveAlongSurface.getResultPos());
|
||||
if (polyHeight.succeeded()) {
|
||||
if (polyHeight.succeeded())
|
||||
{
|
||||
iterPos[1] = polyHeight.result;
|
||||
}
|
||||
|
||||
// Handle end of path and off-mesh links when close enough.
|
||||
if (endOfPath && PathUtils.inRange(iterPos, steerTarget.steerPos, SLOP, 1.0f)) {
|
||||
if (endOfPath && PathUtils.inRange(iterPos, steerTarget.steerPos, SLOP, 1.0f))
|
||||
{
|
||||
// Reached end of path.
|
||||
vCopy(iterPos, targetPos);
|
||||
if (m_smoothPath.Count < MAX_SMOOTH) {
|
||||
if (m_smoothPath.Count < MAX_SMOOTH)
|
||||
{
|
||||
m_smoothPath.Add(iterPos);
|
||||
}
|
||||
|
||||
break;
|
||||
} else if (offMeshConnection
|
||||
&& PathUtils.inRange(iterPos, steerTarget.steerPos, SLOP, 1.0f)) {
|
||||
}
|
||||
else if (offMeshConnection
|
||||
&& PathUtils.inRange(iterPos, steerTarget.steerPos, SLOP, 1.0f))
|
||||
{
|
||||
// Reached off-mesh connection.
|
||||
// Advance the path up to and over the off-mesh connection.
|
||||
long prevRef = 0;
|
||||
long polyRef = polys[0];
|
||||
int npos = 0;
|
||||
while (npos < polys.Count && polyRef != steerTarget.steerPosRef) {
|
||||
while (npos < polys.Count && polyRef != steerTarget.steerPosRef)
|
||||
{
|
||||
prevRef = polyRef;
|
||||
polyRef = polys[npos];
|
||||
npos++;
|
||||
}
|
||||
|
||||
polys = polys.GetRange(npos, polys.Count - npos);
|
||||
|
||||
// Handle the connection.
|
||||
Result<Tuple<float[], float[]>> offMeshCon = m_navMesh
|
||||
.getOffMeshConnectionPolyEndPoints(prevRef, polyRef);
|
||||
if (offMeshCon.succeeded()) {
|
||||
if (offMeshCon.succeeded())
|
||||
{
|
||||
float[] startPos = offMeshCon.result.Item1;
|
||||
float[] endPos = offMeshCon.result.Item2;
|
||||
if (m_smoothPath.Count < MAX_SMOOTH) {
|
||||
if (m_smoothPath.Count < MAX_SMOOTH)
|
||||
{
|
||||
m_smoothPath.Add(startPos);
|
||||
// Hack to make the dotted path not visible during off-mesh connection.
|
||||
if ((m_smoothPath.Count & 1) != 0) {
|
||||
if ((m_smoothPath.Count & 1) != 0)
|
||||
{
|
||||
m_smoothPath.Add(startPos);
|
||||
}
|
||||
}
|
||||
|
||||
// Move position at the other side of the off-mesh link.
|
||||
vCopy(iterPos, endPos);
|
||||
iterPos[1] = m_navQuery.getPolyHeight(polys[0], iterPos).result;
|
||||
|
@ -326,99 +368,136 @@ public class TestNavmeshTool : Tool {
|
|||
}
|
||||
|
||||
// Store results.
|
||||
if (m_smoothPath.Count < MAX_SMOOTH) {
|
||||
if (m_smoothPath.Count < MAX_SMOOTH)
|
||||
{
|
||||
m_smoothPath.Add(iterPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
m_polys = null;
|
||||
m_smoothPath = null;
|
||||
}
|
||||
} else if (m_toolMode == ToolMode.PATHFIND_STRAIGHT) {
|
||||
if (m_sposSet && m_eposSet && m_startRef != 0 && m_endRef != 0) {
|
||||
}
|
||||
else if (m_toolMode == ToolMode.PATHFIND_STRAIGHT)
|
||||
{
|
||||
if (m_sposSet && m_eposSet && m_startRef != 0 && m_endRef != 0)
|
||||
{
|
||||
m_polys = m_navQuery.findPath(m_startRef, m_endRef, m_spos, m_epos, m_filter,
|
||||
enableRaycast ? NavMeshQuery.DT_FINDPATH_ANY_ANGLE : 0, float.MaxValue).result;
|
||||
if (0 < m_polys.Count) {
|
||||
if (0 < m_polys.Count)
|
||||
{
|
||||
// In case of partial path, make sure the end point is clamped to the last polygon.
|
||||
float[] epos = new float[] { m_epos[0], m_epos[1], m_epos[2] };
|
||||
if (m_polys[m_polys.Count - 1] != m_endRef) {
|
||||
if (m_polys[m_polys.Count - 1] != m_endRef)
|
||||
{
|
||||
Result<ClosestPointOnPolyResult> result = m_navQuery
|
||||
.closestPointOnPoly(m_polys[m_polys.Count - 1], m_epos);
|
||||
if (result.succeeded()) {
|
||||
if (result.succeeded())
|
||||
{
|
||||
epos = result.result.getClosest();
|
||||
}
|
||||
}
|
||||
|
||||
m_straightPath = m_navQuery.findStraightPath(m_spos, epos, m_polys, MAX_POLYS,
|
||||
m_straightPathOptions).result;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
m_straightPath = null;
|
||||
}
|
||||
} else if (m_toolMode == ToolMode.PATHFIND_SLICED) {
|
||||
}
|
||||
else if (m_toolMode == ToolMode.PATHFIND_SLICED)
|
||||
{
|
||||
m_polys = null;
|
||||
m_straightPath = null;
|
||||
if (m_sposSet && m_eposSet && m_startRef != 0 && m_endRef != 0) {
|
||||
if (m_sposSet && m_eposSet && m_startRef != 0 && m_endRef != 0)
|
||||
{
|
||||
m_pathFindStatus = m_navQuery.initSlicedFindPath(m_startRef, m_endRef, m_spos, m_epos, m_filter,
|
||||
enableRaycast ? NavMeshQuery.DT_FINDPATH_ANY_ANGLE : 0, float.MaxValue);
|
||||
}
|
||||
} else if (m_toolMode == ToolMode.RAYCAST) {
|
||||
}
|
||||
else if (m_toolMode == ToolMode.RAYCAST)
|
||||
{
|
||||
m_straightPath = null;
|
||||
if (m_sposSet && m_eposSet && m_startRef != 0) {
|
||||
if (m_sposSet && m_eposSet && m_startRef != 0)
|
||||
{
|
||||
{
|
||||
Result<RaycastHit> hit = m_navQuery.raycast(m_startRef, m_spos, m_epos, m_filter, 0, 0);
|
||||
if (hit.succeeded()) {
|
||||
if (hit.succeeded())
|
||||
{
|
||||
m_polys = hit.result.path;
|
||||
if (hit.result.t > 1) {
|
||||
if (hit.result.t > 1)
|
||||
{
|
||||
// No hit
|
||||
m_hitPos = ArrayUtils.CopyOf(m_epos, m_epos.Length);
|
||||
m_hitResult = false;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Hit
|
||||
m_hitPos = vLerp(m_spos, m_epos, hit.result.t);
|
||||
m_hitNormal = ArrayUtils.CopyOf(hit.result.hitNormal, hit.result.hitNormal.Length);
|
||||
m_hitResult = true;
|
||||
}
|
||||
|
||||
// Adjust height.
|
||||
if (hit.result.path.Count > 0) {
|
||||
if (hit.result.path.Count > 0)
|
||||
{
|
||||
Result<float> result = m_navQuery
|
||||
.getPolyHeight(hit.result.path[hit.result.path.Count - 1], m_hitPos);
|
||||
if (result.succeeded()) {
|
||||
if (result.succeeded())
|
||||
{
|
||||
m_hitPos[1] = result.result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_straightPath = new();
|
||||
m_straightPath.Add(new StraightPathItem(m_spos, 0, 0));
|
||||
m_straightPath.Add(new StraightPathItem(m_hitPos, 0, 0));
|
||||
}
|
||||
}
|
||||
} else if (m_toolMode == ToolMode.DISTANCE_TO_WALL) {
|
||||
}
|
||||
else if (m_toolMode == ToolMode.DISTANCE_TO_WALL)
|
||||
{
|
||||
m_distanceToWall = 0;
|
||||
if (m_sposSet && m_startRef != 0) {
|
||||
if (m_sposSet && m_startRef != 0)
|
||||
{
|
||||
m_distanceToWall = 0.0f;
|
||||
Result<FindDistanceToWallResult> result = m_navQuery.findDistanceToWall(m_startRef, m_spos, 100.0f,
|
||||
m_filter);
|
||||
if (result.succeeded()) {
|
||||
if (result.succeeded())
|
||||
{
|
||||
m_distanceToWall = result.result.getDistance();
|
||||
m_hitPos = result.result.getPosition();
|
||||
m_hitNormal = result.result.getNormal();
|
||||
}
|
||||
}
|
||||
} else if (m_toolMode == ToolMode.FIND_POLYS_IN_CIRCLE) {
|
||||
if (m_sposSet && m_startRef != 0 && m_eposSet) {
|
||||
}
|
||||
else if (m_toolMode == ToolMode.FIND_POLYS_IN_CIRCLE)
|
||||
{
|
||||
if (m_sposSet && m_startRef != 0 && m_eposSet)
|
||||
{
|
||||
float dx = m_epos[0] - m_spos[0];
|
||||
float dz = m_epos[2] - m_spos[2];
|
||||
float dist = (float)Math.Sqrt(dx * dx + dz * dz);
|
||||
Result<FindPolysAroundResult> result = m_navQuery.findPolysAroundCircle(m_startRef, m_spos, dist,
|
||||
m_filter);
|
||||
if (result.succeeded()) {
|
||||
if (result.succeeded())
|
||||
{
|
||||
m_polys = result.result.getRefs();
|
||||
m_parent = result.result.getParentRefs();
|
||||
}
|
||||
}
|
||||
} else if (m_toolMode == ToolMode.FIND_POLYS_IN_SHAPE) {
|
||||
if (m_sposSet && m_startRef != 0 && m_eposSet) {
|
||||
}
|
||||
else if (m_toolMode == ToolMode.FIND_POLYS_IN_SHAPE)
|
||||
{
|
||||
if (m_sposSet && m_startRef != 0 && m_eposSet)
|
||||
{
|
||||
float nx = (m_epos[2] - m_spos[2]) * 0.25f;
|
||||
float nz = -(m_epos[0] - m_spos[0]) * 0.25f;
|
||||
float agentHeight = m_sample != null ? m_sample.getSettingsUI().getAgentHeight() : 0;
|
||||
|
@ -440,33 +519,44 @@ public class TestNavmeshTool : Tool {
|
|||
m_queryPoly[11] = m_epos[2] + nz;
|
||||
|
||||
Result<FindPolysAroundResult> result = m_navQuery.findPolysAroundShape(m_startRef, m_queryPoly, m_filter);
|
||||
if (result.succeeded()) {
|
||||
if (result.succeeded())
|
||||
{
|
||||
m_polys = result.result.getRefs();
|
||||
m_parent = result.result.getParentRefs();
|
||||
}
|
||||
}
|
||||
} else if (m_toolMode == ToolMode.FIND_LOCAL_NEIGHBOURHOOD) {
|
||||
if (m_sposSet && m_startRef != 0) {
|
||||
}
|
||||
else if (m_toolMode == ToolMode.FIND_LOCAL_NEIGHBOURHOOD)
|
||||
{
|
||||
if (m_sposSet && m_startRef != 0)
|
||||
{
|
||||
m_neighbourhoodRadius = m_sample.getSettingsUI().getAgentRadius() * 20.0f;
|
||||
Result<FindLocalNeighbourhoodResult> result = m_navQuery.findLocalNeighbourhood(m_startRef, m_spos,
|
||||
m_neighbourhoodRadius, m_filter);
|
||||
if (result.succeeded()) {
|
||||
if (result.succeeded())
|
||||
{
|
||||
m_polys = result.result.getRefs();
|
||||
m_parent = result.result.getParentRefs();
|
||||
}
|
||||
}
|
||||
} else if (m_toolMode == ToolMode.RANDOM_POINTS_IN_CIRCLE) {
|
||||
}
|
||||
else if (m_toolMode == ToolMode.RANDOM_POINTS_IN_CIRCLE)
|
||||
{
|
||||
randomPoints.Clear();
|
||||
if (m_sposSet && m_startRef != 0 && m_eposSet) {
|
||||
if (m_sposSet && m_startRef != 0 && m_eposSet)
|
||||
{
|
||||
float dx = m_epos[0] - m_spos[0];
|
||||
float dz = m_epos[2] - m_spos[2];
|
||||
float dist = (float)Math.Sqrt(dx * dx + dz * dz);
|
||||
PolygonByCircleConstraint constraint = constrainByCircle ? PolygonByCircleConstraint.strict()
|
||||
PolygonByCircleConstraint constraint = constrainByCircle
|
||||
? PolygonByCircleConstraint.strict()
|
||||
: PolygonByCircleConstraint.noop();
|
||||
for (int i = 0; i < 200; i++) {
|
||||
for (int i = 0; i < 200; i++)
|
||||
{
|
||||
Result<FindRandomPointResult> result = m_navQuery.findRandomPointAroundCircle(m_startRef, m_spos, dist,
|
||||
m_filter, new NavMeshQuery.FRand(), constraint);
|
||||
if (result.succeeded()) {
|
||||
if (result.succeeded())
|
||||
{
|
||||
randomPoints.Add(result.result.getRandomPt());
|
||||
}
|
||||
}
|
||||
|
@ -474,10 +564,13 @@ public class TestNavmeshTool : Tool {
|
|||
}
|
||||
}
|
||||
|
||||
public override void handleRender(NavMeshRenderer renderer) {
|
||||
if (m_sample == null) {
|
||||
public override void handleRender(NavMeshRenderer renderer)
|
||||
{
|
||||
if (m_sample == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RecastDebugDraw dd = renderer.getDebugDraw();
|
||||
int startCol = duRGBA(128, 25, 0, 192);
|
||||
int endCol = duRGBA(51, 102, 0, 129);
|
||||
|
@ -487,38 +580,52 @@ public class TestNavmeshTool : Tool {
|
|||
float agentHeight = m_sample.getSettingsUI().getAgentHeight();
|
||||
float agentClimb = m_sample.getSettingsUI().getAgentMaxClimb();
|
||||
|
||||
if (m_sposSet) {
|
||||
if (m_sposSet)
|
||||
{
|
||||
drawAgent(dd, m_spos, startCol);
|
||||
}
|
||||
if (m_eposSet) {
|
||||
|
||||
if (m_eposSet)
|
||||
{
|
||||
drawAgent(dd, m_epos, endCol);
|
||||
}
|
||||
|
||||
dd.depthMask(true);
|
||||
|
||||
NavMesh m_navMesh = m_sample.getNavMesh();
|
||||
if (m_navMesh == null) {
|
||||
if (m_navMesh == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_toolMode == ToolMode.PATHFIND_FOLLOW) {
|
||||
if (m_toolMode == ToolMode.PATHFIND_FOLLOW)
|
||||
{
|
||||
dd.debugDrawNavMeshPoly(m_navMesh, m_startRef, startCol);
|
||||
dd.debugDrawNavMeshPoly(m_navMesh, m_endRef, endCol);
|
||||
|
||||
if (m_polys != null) {
|
||||
foreach (long poly in m_polys) {
|
||||
if (poly == m_startRef || poly == m_endRef) {
|
||||
if (m_polys != null)
|
||||
{
|
||||
foreach (long poly in m_polys)
|
||||
{
|
||||
if (poly == m_startRef || poly == m_endRef)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
dd.debugDrawNavMeshPoly(m_navMesh, poly, pathCol);
|
||||
}
|
||||
}
|
||||
if (m_smoothPath != null) {
|
||||
|
||||
if (m_smoothPath != null)
|
||||
{
|
||||
dd.depthMask(false);
|
||||
int spathCol = duRGBA(0, 0, 0, 220);
|
||||
dd.begin(LINES, 3.0f);
|
||||
for (int i = 0; i < m_smoothPath.Count; ++i) {
|
||||
for (int i = 0; i < m_smoothPath.Count; ++i)
|
||||
{
|
||||
dd.vertex(m_smoothPath[i][0], m_smoothPath[i][1] + 0.1f, m_smoothPath[i][2], spathCol);
|
||||
}
|
||||
|
||||
dd.end();
|
||||
dd.depthMask(true);
|
||||
}
|
||||
|
@ -556,60 +663,87 @@ public class TestNavmeshTool : Tool {
|
|||
dd.depthMask(true);
|
||||
}
|
||||
*/
|
||||
} else if (m_toolMode == ToolMode.PATHFIND_STRAIGHT || m_toolMode == ToolMode.PATHFIND_SLICED) {
|
||||
}
|
||||
else if (m_toolMode == ToolMode.PATHFIND_STRAIGHT || m_toolMode == ToolMode.PATHFIND_SLICED)
|
||||
{
|
||||
dd.debugDrawNavMeshPoly(m_navMesh, m_startRef, startCol);
|
||||
dd.debugDrawNavMeshPoly(m_navMesh, m_endRef, endCol);
|
||||
|
||||
if (m_polys != null) {
|
||||
foreach (long poly in m_polys) {
|
||||
if (m_polys != null)
|
||||
{
|
||||
foreach (long poly in m_polys)
|
||||
{
|
||||
dd.debugDrawNavMeshPoly(m_navMesh, poly, pathCol);
|
||||
}
|
||||
}
|
||||
if (m_straightPath != null) {
|
||||
|
||||
if (m_straightPath != null)
|
||||
{
|
||||
dd.depthMask(false);
|
||||
int spathCol = duRGBA(64, 16, 0, 220);
|
||||
int offMeshCol = duRGBA(128, 96, 0, 220);
|
||||
dd.begin(LINES, 2.0f);
|
||||
for (int i = 0; i < m_straightPath.Count - 1; ++i) {
|
||||
for (int i = 0; i < m_straightPath.Count - 1; ++i)
|
||||
{
|
||||
StraightPathItem straightPathItem = m_straightPath[i];
|
||||
StraightPathItem straightPathItem2 = m_straightPath[i + 1];
|
||||
int col;
|
||||
if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) {
|
||||
if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
|
||||
{
|
||||
col = offMeshCol;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
col = spathCol;
|
||||
}
|
||||
|
||||
dd.vertex(straightPathItem.getPos()[0], straightPathItem.getPos()[1] + 0.4f,
|
||||
straightPathItem.getPos()[2], col);
|
||||
dd.vertex(straightPathItem2.getPos()[0], straightPathItem2.getPos()[1] + 0.4f,
|
||||
straightPathItem2.getPos()[2], col);
|
||||
}
|
||||
|
||||
dd.end();
|
||||
dd.begin(POINTS, 6.0f);
|
||||
for (int i = 0; i < m_straightPath.Count; ++i) {
|
||||
for (int i = 0; i < m_straightPath.Count; ++i)
|
||||
{
|
||||
StraightPathItem straightPathItem = m_straightPath[i];
|
||||
int col;
|
||||
if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_START) != 0) {
|
||||
if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_START) != 0)
|
||||
{
|
||||
col = startCol;
|
||||
} else if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_END) != 0) {
|
||||
}
|
||||
else if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_END) != 0)
|
||||
{
|
||||
col = endCol;
|
||||
} else if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) {
|
||||
}
|
||||
else if ((straightPathItem.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
|
||||
{
|
||||
col = offMeshCol;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
col = spathCol;
|
||||
}
|
||||
|
||||
dd.vertex(straightPathItem.getPos()[0], straightPathItem.getPos()[1] + 0.4f,
|
||||
straightPathItem.getPos()[2], col);
|
||||
}
|
||||
|
||||
dd.end();
|
||||
dd.depthMask(true);
|
||||
}
|
||||
} else if (m_toolMode == ToolMode.RAYCAST) {
|
||||
}
|
||||
else if (m_toolMode == ToolMode.RAYCAST)
|
||||
{
|
||||
dd.debugDrawNavMeshPoly(m_navMesh, m_startRef, startCol);
|
||||
|
||||
if (m_straightPath != null) {
|
||||
if (m_polys != null) {
|
||||
foreach (long poly in m_polys) {
|
||||
if (m_straightPath != null)
|
||||
{
|
||||
if (m_polys != null)
|
||||
{
|
||||
foreach (long poly in m_polys)
|
||||
{
|
||||
dd.debugDrawNavMeshPoly(m_navMesh, poly, pathCol);
|
||||
}
|
||||
}
|
||||
|
@ -617,7 +751,8 @@ public class TestNavmeshTool : Tool {
|
|||
dd.depthMask(false);
|
||||
int spathCol = m_hitResult ? duRGBA(64, 16, 0, 220) : duRGBA(240, 240, 240, 220);
|
||||
dd.begin(LINES, 2.0f);
|
||||
for (int i = 0; i < m_straightPath.Count - 1; ++i) {
|
||||
for (int i = 0; i < m_straightPath.Count - 1; ++i)
|
||||
{
|
||||
StraightPathItem straightPathItem = m_straightPath[i];
|
||||
StraightPathItem straightPathItem2 = m_straightPath[i + 1];
|
||||
dd.vertex(straightPathItem.getPos()[0], straightPathItem.getPos()[1] + 0.4f,
|
||||
|
@ -625,16 +760,20 @@ public class TestNavmeshTool : Tool {
|
|||
dd.vertex(straightPathItem2.getPos()[0], straightPathItem2.getPos()[1] + 0.4f,
|
||||
straightPathItem2.getPos()[2], spathCol);
|
||||
}
|
||||
|
||||
dd.end();
|
||||
dd.begin(POINTS, 4.0f);
|
||||
for (int i = 0; i < m_straightPath.Count; ++i) {
|
||||
for (int i = 0; i < m_straightPath.Count; ++i)
|
||||
{
|
||||
StraightPathItem straightPathItem = m_straightPath[i];
|
||||
dd.vertex(straightPathItem.getPos()[0], straightPathItem.getPos()[1] + 0.4f,
|
||||
straightPathItem.getPos()[2], spathCol);
|
||||
}
|
||||
|
||||
dd.end();
|
||||
|
||||
if (m_hitResult) {
|
||||
if (m_hitResult)
|
||||
{
|
||||
int hitCol = duRGBA(0, 0, 0, 128);
|
||||
dd.begin(LINES, 2.0f);
|
||||
dd.vertex(m_hitPos[0], m_hitPos[1] + 0.4f, m_hitPos[2], hitCol);
|
||||
|
@ -643,28 +782,40 @@ public class TestNavmeshTool : Tool {
|
|||
m_hitPos[2] + m_hitNormal[2] * agentRadius, hitCol);
|
||||
dd.end();
|
||||
}
|
||||
|
||||
dd.depthMask(true);
|
||||
}
|
||||
} else if (m_toolMode == ToolMode.DISTANCE_TO_WALL) {
|
||||
}
|
||||
else if (m_toolMode == ToolMode.DISTANCE_TO_WALL)
|
||||
{
|
||||
dd.debugDrawNavMeshPoly(m_navMesh, m_startRef, startCol);
|
||||
dd.depthMask(false);
|
||||
if (m_spos != null) {
|
||||
if (m_spos != null)
|
||||
{
|
||||
dd.debugDrawCircle(m_spos[0], m_spos[1] + agentHeight / 2, m_spos[2], m_distanceToWall,
|
||||
duRGBA(64, 16, 0, 220), 2.0f);
|
||||
}
|
||||
if (m_hitPos != null) {
|
||||
|
||||
if (m_hitPos != null)
|
||||
{
|
||||
dd.begin(LINES, 3.0f);
|
||||
dd.vertex(m_hitPos[0], m_hitPos[1] + 0.02f, m_hitPos[2], duRGBA(0, 0, 0, 192));
|
||||
dd.vertex(m_hitPos[0], m_hitPos[1] + agentHeight, m_hitPos[2], duRGBA(0, 0, 0, 192));
|
||||
dd.end();
|
||||
}
|
||||
|
||||
dd.depthMask(true);
|
||||
} else if (m_toolMode == ToolMode.FIND_POLYS_IN_CIRCLE) {
|
||||
if (m_polys != null) {
|
||||
for (int i = 0; i < m_polys.Count; i++) {
|
||||
}
|
||||
else if (m_toolMode == ToolMode.FIND_POLYS_IN_CIRCLE)
|
||||
{
|
||||
if (m_polys != null)
|
||||
{
|
||||
for (int i = 0; i < m_polys.Count; i++)
|
||||
{
|
||||
dd.debugDrawNavMeshPoly(m_navMesh, m_polys[i], pathCol);
|
||||
dd.depthMask(false);
|
||||
if (m_parent[i] != 0) {
|
||||
if (m_parent[i] != 0)
|
||||
{
|
||||
dd.depthMask(false);
|
||||
float[] p0 = getPolyCenter(m_navMesh, m_parent[i]);
|
||||
float[] p1 = getPolyCenter(m_navMesh, m_polys[i]);
|
||||
|
@ -672,11 +823,13 @@ public class TestNavmeshTool : Tool {
|
|||
duRGBA(0, 0, 0, 128), 2.0f);
|
||||
dd.depthMask(true);
|
||||
}
|
||||
|
||||
dd.depthMask(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_sposSet && m_eposSet) {
|
||||
if (m_sposSet && m_eposSet)
|
||||
{
|
||||
dd.depthMask(false);
|
||||
float dx = m_epos[0] - m_spos[0];
|
||||
float dz = m_epos[2] - m_spos[2];
|
||||
|
@ -685,12 +838,17 @@ public class TestNavmeshTool : Tool {
|
|||
2.0f);
|
||||
dd.depthMask(true);
|
||||
}
|
||||
} else if (m_toolMode == ToolMode.FIND_POLYS_IN_SHAPE) {
|
||||
if (m_polys != null) {
|
||||
for (int i = 0; i < m_polys.Count; i++) {
|
||||
}
|
||||
else if (m_toolMode == ToolMode.FIND_POLYS_IN_SHAPE)
|
||||
{
|
||||
if (m_polys != null)
|
||||
{
|
||||
for (int i = 0; i < m_polys.Count; i++)
|
||||
{
|
||||
dd.debugDrawNavMeshPoly(m_navMesh, m_polys[i], pathCol);
|
||||
dd.depthMask(false);
|
||||
if (m_parent[i] != 0) {
|
||||
if (m_parent[i] != 0)
|
||||
{
|
||||
dd.depthMask(false);
|
||||
float[] p0 = getPolyCenter(m_navMesh, m_parent[i]);
|
||||
float[] p1 = getPolyCenter(m_navMesh, m_polys[i]);
|
||||
|
@ -698,27 +856,36 @@ public class TestNavmeshTool : Tool {
|
|||
duRGBA(0, 0, 0, 128), 2.0f);
|
||||
dd.depthMask(true);
|
||||
}
|
||||
|
||||
dd.depthMask(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_sposSet && m_eposSet) {
|
||||
if (m_sposSet && m_eposSet)
|
||||
{
|
||||
dd.depthMask(false);
|
||||
int col = duRGBA(64, 16, 0, 220);
|
||||
dd.begin(LINES, 2.0f);
|
||||
for (int i = 0, j = 3; i < 4; j = i++) {
|
||||
for (int i = 0, j = 3; i < 4; j = i++)
|
||||
{
|
||||
dd.vertex(m_queryPoly[j * 3], m_queryPoly[j * 3 + 1], m_queryPoly[j * 3 + 2], col);
|
||||
dd.vertex(m_queryPoly[i * 3], m_queryPoly[i * 3 + 1], m_queryPoly[i * 3 + 2], col);
|
||||
}
|
||||
|
||||
dd.end();
|
||||
dd.depthMask(true);
|
||||
}
|
||||
} else if (m_toolMode == ToolMode.FIND_LOCAL_NEIGHBOURHOOD) {
|
||||
if (m_polys != null) {
|
||||
for (int i = 0; i < m_polys.Count; i++) {
|
||||
}
|
||||
else if (m_toolMode == ToolMode.FIND_LOCAL_NEIGHBOURHOOD)
|
||||
{
|
||||
if (m_polys != null)
|
||||
{
|
||||
for (int i = 0; i < m_polys.Count; i++)
|
||||
{
|
||||
dd.debugDrawNavMeshPoly(m_navMesh, m_polys[i], pathCol);
|
||||
dd.depthMask(false);
|
||||
if (m_parent[i] != 0) {
|
||||
if (m_parent[i] != 0)
|
||||
{
|
||||
dd.depthMask(false);
|
||||
float[] p0 = getPolyCenter(m_navMesh, m_parent[i]);
|
||||
float[] p1 = getPolyCenter(m_navMesh, m_polys[i]);
|
||||
|
@ -726,36 +893,47 @@ public class TestNavmeshTool : Tool {
|
|||
duRGBA(0, 0, 0, 128), 2.0f);
|
||||
dd.depthMask(true);
|
||||
}
|
||||
|
||||
dd.depthMask(true);
|
||||
if (m_sample.getNavMeshQuery() != null) {
|
||||
if (m_sample.getNavMeshQuery() != null)
|
||||
{
|
||||
Result<GetPolyWallSegmentsResult> result = m_sample.getNavMeshQuery()
|
||||
.getPolyWallSegments(m_polys[i], false, m_filter);
|
||||
if (result.succeeded()) {
|
||||
if (result.succeeded())
|
||||
{
|
||||
dd.begin(LINES, 2.0f);
|
||||
GetPolyWallSegmentsResult wallSegments = result.result;
|
||||
for (int j = 0; j < wallSegments.getSegmentVerts().Count; ++j) {
|
||||
for (int j = 0; j < wallSegments.getSegmentVerts().Count; ++j)
|
||||
{
|
||||
float[] s = wallSegments.getSegmentVerts()[j];
|
||||
float[] s3 = new float[] { s[3], s[4], s[5] };
|
||||
// Skip too distant segments.
|
||||
Tuple<float, float> distSqr = DetourCommon.distancePtSegSqr2D(m_spos, s, 0, 3);
|
||||
if (distSqr.Item1 > DemoMath.sqr(m_neighbourhoodRadius)) {
|
||||
if (distSqr.Item1 > DemoMath.sqr(m_neighbourhoodRadius))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float[] delta = vSub(s3, s);
|
||||
float[] p0 = vMad(s, delta, 0.5f);
|
||||
float[] norm = new float[] { delta[2], 0, -delta[0] };
|
||||
vNormalize(norm);
|
||||
float[] p1 = vMad(p0, norm, agentRadius * 0.5f);
|
||||
// Skip backfacing segments.
|
||||
if (wallSegments.getSegmentRefs()[j] != 0) {
|
||||
if (wallSegments.getSegmentRefs()[j] != 0)
|
||||
{
|
||||
int col = duRGBA(255, 255, 255, 32);
|
||||
dd.vertex(s[0], s[1] + agentClimb, s[2], col);
|
||||
dd.vertex(s[3], s[4] + agentClimb, s[5], col);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
int col = duRGBA(192, 32, 16, 192);
|
||||
if (DetourCommon.triArea2D(m_spos, s, s3) < 0.0f) {
|
||||
if (DetourCommon.triArea2D(m_spos, s, s3) < 0.0f)
|
||||
{
|
||||
col = duRGBA(96, 32, 16, 192);
|
||||
}
|
||||
|
||||
dd.vertex(p0[0], p0[1] + agentClimb, p0[2], col);
|
||||
dd.vertex(p1[0], p1[1] + agentClimb, p1[2], col);
|
||||
|
||||
|
@ -763,6 +941,7 @@ public class TestNavmeshTool : Tool {
|
|||
dd.vertex(s[3], s[4] + agentClimb, s[5], col);
|
||||
}
|
||||
}
|
||||
|
||||
dd.end();
|
||||
}
|
||||
}
|
||||
|
@ -770,22 +949,28 @@ public class TestNavmeshTool : Tool {
|
|||
dd.depthMask(true);
|
||||
}
|
||||
|
||||
if (m_sposSet) {
|
||||
if (m_sposSet)
|
||||
{
|
||||
dd.depthMask(false);
|
||||
dd.debugDrawCircle(m_spos[0], m_spos[1] + agentHeight / 2, m_spos[2], m_neighbourhoodRadius,
|
||||
duRGBA(64, 16, 0, 220), 2.0f);
|
||||
dd.depthMask(true);
|
||||
}
|
||||
}
|
||||
} else if (m_toolMode == ToolMode.RANDOM_POINTS_IN_CIRCLE) {
|
||||
}
|
||||
else if (m_toolMode == ToolMode.RANDOM_POINTS_IN_CIRCLE)
|
||||
{
|
||||
dd.depthMask(false);
|
||||
dd.begin(POINTS, 4.0f);
|
||||
int col = duRGBA(64, 16, 0, 220);
|
||||
foreach (float[] point in randomPoints) {
|
||||
foreach (float[] point in randomPoints)
|
||||
{
|
||||
dd.vertex(point[0], point[1] + 0.1f, point[2], col);
|
||||
}
|
||||
|
||||
dd.end();
|
||||
if (m_sposSet && m_eposSet) {
|
||||
if (m_sposSet && m_eposSet)
|
||||
{
|
||||
dd.depthMask(false);
|
||||
float dx = m_epos[0] - m_spos[0];
|
||||
float dz = m_epos[2] - m_spos[2];
|
||||
|
@ -794,11 +979,13 @@ public class TestNavmeshTool : Tool {
|
|||
2.0f);
|
||||
dd.depthMask(true);
|
||||
}
|
||||
|
||||
dd.depthMask(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawAgent(RecastDebugDraw dd, float[] pos, int col) {
|
||||
private void drawAgent(RecastDebugDraw dd, float[] pos, int col)
|
||||
{
|
||||
float r = m_sample.getSettingsUI().getAgentRadius();
|
||||
float h = m_sample.getSettingsUI().getAgentHeight();
|
||||
float c = m_sample.getSettingsUI().getAgentMaxClimb();
|
||||
|
@ -818,47 +1005,60 @@ public class TestNavmeshTool : Tool {
|
|||
dd.depthMask(true);
|
||||
}
|
||||
|
||||
private float[] getPolyCenter(NavMesh navMesh, long refs) {
|
||||
private float[] getPolyCenter(NavMesh navMesh, long refs)
|
||||
{
|
||||
float[] center = new float[3];
|
||||
center[0] = 0;
|
||||
center[1] = 0;
|
||||
center[2] = 0;
|
||||
Result<Tuple<MeshTile, Poly>> tileAndPoly = navMesh.getTileAndPolyByRef(refs);
|
||||
if (tileAndPoly.succeeded()) {
|
||||
if (tileAndPoly.succeeded())
|
||||
{
|
||||
MeshTile tile = tileAndPoly.result.Item1;
|
||||
Poly poly = tileAndPoly.result.Item2;
|
||||
for (int i = 0; i < poly.vertCount; ++i) {
|
||||
for (int i = 0; i < poly.vertCount; ++i)
|
||||
{
|
||||
int v = poly.verts[i] * 3;
|
||||
center[0] += tile.data.verts[v];
|
||||
center[1] += tile.data.verts[v + 1];
|
||||
center[2] += tile.data.verts[v + 2];
|
||||
}
|
||||
|
||||
float s = 1.0f / poly.vertCount;
|
||||
center[0] *= s;
|
||||
center[1] *= s;
|
||||
center[2] *= s;
|
||||
}
|
||||
|
||||
return center;
|
||||
}
|
||||
|
||||
public override void handleUpdate(float dt) {
|
||||
public override void handleUpdate(float dt)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
if (m_toolMode == ToolMode.PATHFIND_SLICED) {
|
||||
if (m_toolMode == ToolMode.PATHFIND_SLICED)
|
||||
{
|
||||
NavMeshQuery m_navQuery = m_sample.getNavMeshQuery();
|
||||
if (m_pathFindStatus.isInProgress()) {
|
||||
if (m_pathFindStatus.isInProgress())
|
||||
{
|
||||
m_pathFindStatus = m_navQuery.updateSlicedFindPath(1).status;
|
||||
}
|
||||
if (m_pathFindStatus.isSuccess()) {
|
||||
|
||||
if (m_pathFindStatus.isSuccess())
|
||||
{
|
||||
m_polys = m_navQuery.finalizeSlicedFindPath().result;
|
||||
m_straightPath = null;
|
||||
if (m_polys != null) {
|
||||
if (m_polys != null)
|
||||
{
|
||||
// In case of partial path, make sure the end point is clamped to the last polygon.
|
||||
float[] epos = new float[3];
|
||||
DetourCommon.vCopy(epos, m_epos);
|
||||
if (m_polys[m_polys.Count - 1] != m_endRef) {
|
||||
if (m_polys[m_polys.Count - 1] != m_endRef)
|
||||
{
|
||||
Result<ClosestPointOnPolyResult> result = m_navQuery
|
||||
.closestPointOnPoly(m_polys[m_polys.Count - 1], m_epos);
|
||||
if (result.succeeded()) {
|
||||
if (result.succeeded())
|
||||
{
|
||||
epos = result.result.getClosest();
|
||||
}
|
||||
}
|
||||
|
@ -872,9 +1072,9 @@ public class TestNavmeshTool : Tool {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_pathFindStatus = Status.FAILURE;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,8 +23,8 @@ using Silk.NET.Windowing;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Tools;
|
||||
|
||||
public abstract class Tool {
|
||||
|
||||
public abstract class Tool
|
||||
{
|
||||
public abstract void setSample(Sample m_sample);
|
||||
|
||||
public abstract void handleClick(float[] s, float[] p, bool shift);
|
||||
|
|
|
@ -20,7 +20,7 @@ using Silk.NET.Windowing;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Tools;
|
||||
|
||||
public interface ToolUIModule {
|
||||
|
||||
public interface ToolUIModule
|
||||
{
|
||||
void layout(IWindow ctx);
|
||||
}
|
|
@ -23,18 +23,20 @@ using Silk.NET.Windowing;
|
|||
|
||||
namespace DotRecast.Recast.Demo.Tools;
|
||||
|
||||
public class ToolsUI : IRcView {
|
||||
|
||||
public class ToolsUI : IRcView
|
||||
{
|
||||
//private readonly NkColor white = NkColor.create();
|
||||
private Tool currentTool;
|
||||
private bool enabled;
|
||||
private readonly Tool[] tools;
|
||||
|
||||
public ToolsUI(params Tool[] tools) {
|
||||
public ToolsUI(params Tool[] tools)
|
||||
{
|
||||
this.tools = tools;
|
||||
}
|
||||
|
||||
public bool render(IWindow ctx, int x, int y, int width, int height, int mouseX, int mouseY) {
|
||||
public bool render(IWindow ctx, int x, int y, int width, int height, int mouseX, int mouseY)
|
||||
{
|
||||
bool mouseInside = false;
|
||||
// nk_rgb(255, 255, 255, white);
|
||||
// try (MemoryStack stack = stackPush()) {
|
||||
|
@ -63,20 +65,23 @@ public class ToolsUI : IRcView {
|
|||
return mouseInside;
|
||||
}
|
||||
|
||||
public void setEnabled(bool enabled) {
|
||||
public void setEnabled(bool enabled)
|
||||
{
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Tool getTool() {
|
||||
public Tool getTool()
|
||||
{
|
||||
return currentTool;
|
||||
}
|
||||
|
||||
public void setSample(Sample sample) {
|
||||
public void setSample(Sample sample)
|
||||
{
|
||||
tools.forEach(t => t.setSample(sample));
|
||||
}
|
||||
|
||||
public void handleUpdate(float dt) {
|
||||
public void handleUpdate(float dt)
|
||||
{
|
||||
tools.forEach(t => t.handleUpdate(dt));
|
||||
}
|
||||
|
||||
}
|
|
@ -20,7 +20,7 @@ using Silk.NET.Windowing;
|
|||
|
||||
namespace DotRecast.Recast.Demo.UI;
|
||||
|
||||
public interface IRcView {
|
||||
|
||||
public interface IRcView
|
||||
{
|
||||
bool render(IWindow ctx, int x, int y, int width, int height, int mouseX, int mouseY);
|
||||
}
|
|
@ -22,8 +22,8 @@ using Silk.NET.Windowing;
|
|||
|
||||
namespace DotRecast.Recast.Demo.UI;
|
||||
|
||||
public class Mouse {
|
||||
|
||||
public class Mouse
|
||||
{
|
||||
private double x;
|
||||
private double y;
|
||||
private double scrollX;
|
||||
|
@ -52,80 +52,103 @@ public class Mouse {
|
|||
// glfwSetScrollCallback(window, (win, x, y) => scroll(x, y));
|
||||
}
|
||||
|
||||
public void cursorPos(double x, double y) {
|
||||
foreach (MouseListener l in listeners) {
|
||||
public void cursorPos(double x, double y)
|
||||
{
|
||||
foreach (MouseListener l in listeners)
|
||||
{
|
||||
l.position(x, y);
|
||||
}
|
||||
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public void scroll(double xoffset, double yoffset) {
|
||||
foreach (MouseListener l in listeners) {
|
||||
public void scroll(double xoffset, double yoffset)
|
||||
{
|
||||
foreach (MouseListener l in listeners)
|
||||
{
|
||||
l.scroll(xoffset, yoffset);
|
||||
}
|
||||
|
||||
scrollX += xoffset;
|
||||
scrollY += yoffset;
|
||||
}
|
||||
|
||||
public double getDX() {
|
||||
public double getDX()
|
||||
{
|
||||
return x - px;
|
||||
}
|
||||
|
||||
public double getDY() {
|
||||
public double getDY()
|
||||
{
|
||||
return y - py;
|
||||
}
|
||||
|
||||
public double getDScrollX() {
|
||||
public double getDScrollX()
|
||||
{
|
||||
return scrollX - pScrollX;
|
||||
}
|
||||
|
||||
public double getDScrollY() {
|
||||
public double getDScrollY()
|
||||
{
|
||||
return scrollY - pScrollY;
|
||||
}
|
||||
|
||||
public double getX() {
|
||||
public double getX()
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
public void setX(double x) {
|
||||
public void setX(double x)
|
||||
{
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
public double getY() {
|
||||
public double getY()
|
||||
{
|
||||
return y;
|
||||
}
|
||||
|
||||
public void setY(double y) {
|
||||
public void setY(double y)
|
||||
{
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public void setDelta() {
|
||||
public void setDelta()
|
||||
{
|
||||
px = x;
|
||||
py = y;
|
||||
pScrollX = scrollX;
|
||||
pScrollY = scrollY;
|
||||
}
|
||||
|
||||
public void buttonPress(int button, int mods) {
|
||||
foreach (MouseListener l in listeners) {
|
||||
public void buttonPress(int button, int mods)
|
||||
{
|
||||
foreach (MouseListener l in listeners)
|
||||
{
|
||||
l.button(button, mods, true);
|
||||
}
|
||||
|
||||
pressed.Add(button);
|
||||
}
|
||||
|
||||
public void buttonRelease(int button, int mods) {
|
||||
foreach (MouseListener l in listeners) {
|
||||
public void buttonRelease(int button, int mods)
|
||||
{
|
||||
foreach (MouseListener l in listeners)
|
||||
{
|
||||
l.button(button, mods, false);
|
||||
}
|
||||
|
||||
pressed.Remove(button);
|
||||
}
|
||||
|
||||
public bool isPressed(int button) {
|
||||
public bool isPressed(int button)
|
||||
{
|
||||
return pressed.Contains(button);
|
||||
}
|
||||
|
||||
public void addListener(MouseListener listener) {
|
||||
public void addListener(MouseListener listener)
|
||||
{
|
||||
listeners.Add(listener);
|
||||
}
|
||||
}
|
|
@ -15,14 +15,14 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Recast.Demo.UI;
|
||||
|
||||
public interface MouseListener {
|
||||
|
||||
public interface MouseListener
|
||||
{
|
||||
void button(int button, int mods, bool down);
|
||||
|
||||
void scroll(double xoffset, double yoffset);
|
||||
|
||||
void position(double x, double y);
|
||||
|
||||
}
|
|
@ -23,9 +23,8 @@ using Microsoft.DotNet.PlatformAbstractions;
|
|||
|
||||
namespace DotRecast.Recast.Demo.UI;
|
||||
|
||||
|
||||
public class NuklearGL {
|
||||
|
||||
public class NuklearGL
|
||||
{
|
||||
private static readonly int BUFFER_INITIAL_SIZE = 4 * 1024;
|
||||
private static readonly int MAX_VERTEX_BUFFER = 512 * 1024;
|
||||
private static readonly int MAX_ELEMENT_BUFFER = 128 * 1024;
|
||||
|
@ -34,6 +33,7 @@ public class NuklearGL {
|
|||
private static readonly int FONT_HEIGHT = 15;
|
||||
|
||||
private readonly RcViewSystem context;
|
||||
|
||||
// private readonly NkDrawNullTexture null_texture = NkDrawNullTexture.create();
|
||||
// private readonly NkBuffer cmds = NkBuffer.create();
|
||||
// private readonly NkUserFont default_font;
|
||||
|
@ -42,10 +42,12 @@ public class NuklearGL {
|
|||
private readonly int uniform_proj;
|
||||
private readonly int vbo;
|
||||
private readonly int ebo;
|
||||
|
||||
private readonly int vao;
|
||||
//private readonly Buffer vertexLayout;
|
||||
|
||||
public NuklearGL(RcViewSystem context) {
|
||||
public NuklearGL(RcViewSystem context)
|
||||
{
|
||||
this.context = context;
|
||||
// nk_buffer_init(cmds, context.allocator, BUFFER_INITIAL_SIZE);
|
||||
// vertexLayout = NkDrawVertexLayoutElement.create(4)//
|
||||
|
@ -128,7 +130,8 @@ public class NuklearGL {
|
|||
// nk_style_set_font(context.ctx, default_font);
|
||||
}
|
||||
|
||||
private long setupFont() {
|
||||
private long setupFont()
|
||||
{
|
||||
return 0;
|
||||
// NkUserFont font = NkUserFont.create();
|
||||
// ByteBuffer ttf;
|
||||
|
@ -240,7 +243,8 @@ public class NuklearGL {
|
|||
// return font;
|
||||
}
|
||||
|
||||
void render(long win, int AA) {
|
||||
void render(long win, int AA)
|
||||
{
|
||||
// int width;
|
||||
// int height;
|
||||
// int display_width;
|
||||
|
|
|
@ -20,8 +20,8 @@ using System.Runtime.InteropServices.JavaScript;
|
|||
|
||||
namespace DotRecast.Recast.Demo.UI;
|
||||
|
||||
public class NuklearUIHelper {
|
||||
|
||||
public class NuklearUIHelper
|
||||
{
|
||||
// public static void nk_color_rgb(IWindow ctx, NkColorf color) {
|
||||
// try (MemoryStack stack = stackPush()) {
|
||||
// if (nk_combo_begin_color(ctx, nk_rgb_cf(color, NkColor.mallocStack(stack)),
|
||||
|
|
|
@ -23,11 +23,13 @@ using Silk.NET.Windowing;
|
|||
|
||||
namespace DotRecast.Recast.Demo.UI;
|
||||
|
||||
public class RcViewSystem {
|
||||
|
||||
public class RcViewSystem
|
||||
{
|
||||
// readonly NkAllocator allocator;
|
||||
private readonly IWindow _window;
|
||||
|
||||
private readonly GL _gl;
|
||||
|
||||
// readonly NkColor background;
|
||||
// readonly NkColor white;
|
||||
private readonly IRcView[] _views;
|
||||
|
@ -61,7 +63,8 @@ public class RcViewSystem {
|
|||
_views = views;
|
||||
}
|
||||
|
||||
private void setupMouse(Mouse mouse) {
|
||||
private void setupMouse(Mouse mouse)
|
||||
{
|
||||
// mouse.addListener(new MouseListener() {
|
||||
//
|
||||
// @Override
|
||||
|
@ -99,7 +102,8 @@ public class RcViewSystem {
|
|||
// });
|
||||
}
|
||||
|
||||
private void setupClipboard(long window) {
|
||||
private void setupClipboard(long window)
|
||||
{
|
||||
// ctx.clip().copy((handle, text, len) => {
|
||||
// if (len == 0) {
|
||||
// return;
|
||||
|
@ -120,11 +124,13 @@ public class RcViewSystem {
|
|||
// });
|
||||
}
|
||||
|
||||
public void inputBegin() {
|
||||
public void inputBegin()
|
||||
{
|
||||
//nk_input_begin(ctx);
|
||||
}
|
||||
|
||||
public void inputEnd(IWindow win) {
|
||||
public void inputEnd(IWindow win)
|
||||
{
|
||||
// NkMouse mouse = ctx.input().mouse();
|
||||
// if (mouse.grab()) {
|
||||
// glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
|
||||
|
@ -140,11 +146,14 @@ public class RcViewSystem {
|
|||
// nk_input_end(ctx);
|
||||
}
|
||||
|
||||
public bool render(IWindow ctx, int x, int y, int width, int height, int mouseX, int mouseY) {
|
||||
public bool render(IWindow ctx, int x, int y, int width, int height, int mouseX, int mouseY)
|
||||
{
|
||||
mouseOverUI = false;
|
||||
foreach (IRcView m in _views) {
|
||||
foreach (IRcView m in _views)
|
||||
{
|
||||
mouseOverUI = m.render(ctx, x, y, width, height, mouseX, mouseY) | mouseOverUI;
|
||||
}
|
||||
|
||||
return mouseOverUI;
|
||||
}
|
||||
}
|
|
@ -20,8 +20,6 @@ freely, subject to the following restrictions:
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
public class AreaModification
|
||||
{
|
||||
public readonly int RC_AREA_FLAGS_MASK = 0x3F;
|
||||
|
|
|
@ -20,8 +20,6 @@ freely, subject to the following restrictions:
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
/** Provides information on the content of a cell column in a compact heightfield. */
|
||||
public class CompactCell
|
||||
{
|
||||
|
|
|
@ -20,8 +20,6 @@ freely, subject to the following restrictions:
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
/** A compact, static heightfield representing unobstructed space. */
|
||||
public class CompactHeightfield
|
||||
{
|
||||
|
|
|
@ -20,8 +20,6 @@ freely, subject to the following restrictions:
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
/** Represents a span of unobstructed space within a compact heightfield. */
|
||||
public class CompactSpan
|
||||
{
|
||||
|
|
|
@ -20,8 +20,6 @@ freely, subject to the following restrictions:
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
/** Represents a simple, non-overlapping contour in field space. */
|
||||
public class Contour
|
||||
{
|
||||
|
|
|
@ -22,8 +22,6 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
/** Represents a group of related contours. */
|
||||
public class ContourSet
|
||||
{
|
||||
|
|
|
@ -20,8 +20,6 @@ freely, subject to the following restrictions:
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
public class ConvexVolume
|
||||
{
|
||||
public float[] verts;
|
||||
|
|
|
@ -23,8 +23,6 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Recast.Geom
|
||||
{
|
||||
|
||||
|
||||
public class ChunkyTriMesh
|
||||
{
|
||||
private class BoundsItem
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
namespace DotRecast.Recast.Geom
|
||||
{
|
||||
|
||||
|
||||
public class ChunkyTriMeshNode
|
||||
{
|
||||
public readonly float[] bmin = new float[2];
|
||||
|
@ -9,5 +7,4 @@ public class ChunkyTriMeshNode
|
|||
public int i;
|
||||
public int[] tris;
|
||||
}
|
||||
|
||||
}
|
|
@ -20,8 +20,6 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Recast.Geom
|
||||
{
|
||||
|
||||
|
||||
public interface ConvexVolumeProvider
|
||||
{
|
||||
IList<ConvexVolume> convexVolumes();
|
||||
|
|
|
@ -22,8 +22,6 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Recast.Geom
|
||||
{
|
||||
|
||||
|
||||
public interface InputGeomProvider : ConvexVolumeProvider
|
||||
{
|
||||
float[] getMeshBoundsMin();
|
||||
|
|
|
@ -24,8 +24,6 @@ using System.Collections.Immutable;
|
|||
|
||||
namespace DotRecast.Recast.Geom
|
||||
{
|
||||
|
||||
|
||||
public class SimpleInputGeomProvider : InputGeomProvider
|
||||
{
|
||||
public readonly float[] vertices;
|
||||
|
@ -104,7 +102,8 @@ public class SimpleInputGeomProvider : InputGeomProvider
|
|||
volumes.Add(vol);
|
||||
}
|
||||
|
||||
public IEnumerable<TriMesh> meshes() {
|
||||
public IEnumerable<TriMesh> meshes()
|
||||
{
|
||||
return ImmutableArray.Create(new TriMesh(vertices, faces));
|
||||
}
|
||||
|
||||
|
|
|
@ -22,8 +22,6 @@ using System.Collections.Immutable;
|
|||
|
||||
namespace DotRecast.Recast.Geom
|
||||
{
|
||||
|
||||
|
||||
public class SingleTrimeshInputGeomProvider : InputGeomProvider
|
||||
{
|
||||
private readonly float[] bmin;
|
||||
|
|
|
@ -22,8 +22,6 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Recast.Geom
|
||||
{
|
||||
|
||||
|
||||
public class TriMesh
|
||||
{
|
||||
private readonly float[] vertices;
|
||||
|
|
|
@ -20,8 +20,6 @@ freely, subject to the following restrictions:
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
/** Represents a heightfield layer within a layer set. */
|
||||
public class Heightfield
|
||||
{
|
||||
|
|
|
@ -20,8 +20,6 @@ freely, subject to the following restrictions:
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
/// Represents a set of heightfield layers.
|
||||
/// @ingroup recast
|
||||
/// @see rcAllocHeightfieldLayerSet, rcFreeHeightfieldLayerSet
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
public class InputGeomReader
|
||||
{
|
||||
}
|
||||
|
|
|
@ -23,8 +23,6 @@ using DotRecast.Recast.Geom;
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
public static class ObjImporter
|
||||
{
|
||||
public class ObjImporterContext
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
/// < Tessellate edges between areas during contour
|
||||
/// simplification.
|
||||
public enum PartitionType
|
||||
|
|
|
@ -19,8 +19,6 @@ freely, subject to the following restrictions:
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
/** Represents a polygon mesh suitable for use in building a navigation mesh. */
|
||||
public class PolyMesh
|
||||
{
|
||||
|
|
|
@ -20,8 +20,6 @@ freely, subject to the following restrictions:
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Contains triangle meshes that represent detailed height data associated with the polygons in its associated polygon
|
||||
* mesh object.
|
||||
|
|
|
@ -22,8 +22,6 @@ using System;
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
using static RecastConstants;
|
||||
|
||||
public class Recast
|
||||
|
|
|
@ -22,8 +22,6 @@ using System;
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
using static RecastConstants;
|
||||
|
||||
public class RecastArea
|
||||
|
|
|
@ -27,8 +27,6 @@ using DotRecast.Recast.Geom;
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
public class RecastBuilder
|
||||
{
|
||||
public interface RecastBuilderProgressListener
|
||||
|
@ -48,7 +46,8 @@ public class RecastBuilder
|
|||
this.progressListener = progressListener;
|
||||
}
|
||||
|
||||
public List<RecastBuilderResult> buildTiles(InputGeomProvider geom, RecastConfig cfg, TaskFactory taskFactory) {
|
||||
public List<RecastBuilderResult> buildTiles(InputGeomProvider geom, RecastConfig cfg, TaskFactory taskFactory)
|
||||
{
|
||||
float[] bmin = geom.getMeshBoundsMin();
|
||||
float[] bmax = geom.getMeshBoundsMax();
|
||||
int[] twh = Recast.calcTileCount(bmin, bmax, cfg.cs, cfg.tileSizeX, cfg.tileSizeZ);
|
||||
|
@ -58,7 +57,9 @@ public class RecastBuilder
|
|||
if (null != taskFactory)
|
||||
{
|
||||
buildMultiThreadAsync(geom, cfg, bmin, bmax, tw, th, results, taskFactory, default);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
buildSingleThreadAsync(geom, cfg, bmin, bmax, tw, th, results);
|
||||
}
|
||||
|
||||
|
|
|
@ -20,8 +20,6 @@ freely, subject to the following restrictions:
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
using static RecastVectors;
|
||||
|
||||
public class RecastBuilderConfig
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
public class RecastBuilderResult
|
||||
{
|
||||
public readonly int tileX;
|
||||
|
@ -56,5 +54,4 @@ public class RecastBuilderResult
|
|||
return telemetry;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -22,8 +22,6 @@ using System;
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
public class RecastCommon
|
||||
{
|
||||
/// Gets neighbor connection data for the specified direction.
|
||||
|
@ -82,7 +80,5 @@ public class RecastCommon
|
|||
{
|
||||
return Math.Max(Math.Min(max, v), min);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -20,8 +20,6 @@ using System;
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
using static RecastConstants;
|
||||
using static RecastVectors;
|
||||
|
||||
|
|
|
@ -22,8 +22,6 @@ using System;
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
using static RecastConstants;
|
||||
|
||||
public class RecastConfig
|
||||
|
|
|
@ -20,8 +20,6 @@ freely, subject to the following restrictions:
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
public static class RecastConstants
|
||||
{
|
||||
public const int RC_NULL_AREA = 0;
|
||||
|
@ -82,7 +80,6 @@ public static class RecastConstants
|
|||
public const int RC_CONTOUR_TESS_AREA_EDGES = 0x02;
|
||||
|
||||
|
||||
|
||||
public const int RC_LOG_WARNING = 1;
|
||||
}
|
||||
}
|
|
@ -23,8 +23,6 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
using static RecastConstants;
|
||||
|
||||
public class RecastContour
|
||||
|
|
|
@ -21,8 +21,6 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
using static RecastConstants;
|
||||
using static RecastVectors;
|
||||
using static RecastCommon;
|
||||
|
|
|
@ -22,8 +22,6 @@ using System;
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
using static RecastConstants;
|
||||
|
||||
public class RecastFilter
|
||||
|
|
|
@ -23,19 +23,18 @@ using System.Collections.Generic;
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
using static RecastCommon;
|
||||
using static RecastConstants;
|
||||
using static RecastVectors;
|
||||
using static RecastRegion;
|
||||
|
||||
public class RecastLayers {
|
||||
|
||||
public class RecastLayers
|
||||
{
|
||||
const int RC_MAX_LAYERS = RecastConstants.RC_NOT_CONNECTED;
|
||||
const int RC_MAX_NEIS = 16;
|
||||
|
||||
private class LayerRegion {
|
||||
private class LayerRegion
|
||||
{
|
||||
public int id;
|
||||
public int layerId;
|
||||
public bool @base;
|
||||
|
@ -43,32 +42,36 @@ public class RecastLayers {
|
|||
public List<int> layers;
|
||||
public List<int> neis;
|
||||
|
||||
public LayerRegion(int i) {
|
||||
public LayerRegion(int i)
|
||||
{
|
||||
id = i;
|
||||
ymin = 0xFFFF;
|
||||
layerId = 0xff;
|
||||
layers = new List<int>();
|
||||
neis = new List<int>();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private static void addUnique(List<int> a, int v) {
|
||||
if (!a.Contains(v)) {
|
||||
private static void addUnique(List<int> a, int v)
|
||||
{
|
||||
if (!a.Contains(v))
|
||||
{
|
||||
a.Add(v);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool contains(List<int> a, int v) {
|
||||
private static bool contains(List<int> a, int v)
|
||||
{
|
||||
return a.Contains(v);
|
||||
}
|
||||
|
||||
private static bool overlapRange(int amin, int amax, int bmin, int bmax) {
|
||||
private static bool overlapRange(int amin, int amax, int bmin, int bmax)
|
||||
{
|
||||
return (amin > bmax || amax < bmin) ? false : true;
|
||||
}
|
||||
|
||||
public static HeightfieldLayerSet buildHeightfieldLayers(Telemetry ctx, CompactHeightfield chf, int walkableHeight) {
|
||||
|
||||
public static HeightfieldLayerSet buildHeightfieldLayers(Telemetry ctx, CompactHeightfield chf, int walkableHeight)
|
||||
{
|
||||
ctx.startTimer("RC_TIMER_BUILD_LAYERS");
|
||||
int w = chf.width;
|
||||
int h = chf.height;
|
||||
|
@ -77,29 +80,35 @@ public class RecastLayers {
|
|||
Array.Fill(srcReg, 0xFF);
|
||||
int nsweeps = chf.width; // Math.Max(chf.width, chf.height);
|
||||
SweepSpan[] sweeps = new SweepSpan[nsweeps];
|
||||
for (int i = 0; i < sweeps.Length; i++) {
|
||||
for (int i = 0; i < sweeps.Length; i++)
|
||||
{
|
||||
sweeps[i] = new SweepSpan();
|
||||
}
|
||||
|
||||
// Partition walkable area into monotone regions.
|
||||
int[] prevCount = new int[256];
|
||||
int regId = 0;
|
||||
// Sweep one line at a time.
|
||||
for (int y = borderSize; y < h - borderSize; ++y) {
|
||||
for (int y = borderSize; y < h - borderSize; ++y)
|
||||
{
|
||||
// Collect spans from this row.
|
||||
Array.Fill(prevCount, 0, 0, (regId) - (0));
|
||||
int sweepId = 0;
|
||||
|
||||
for (int x = borderSize; x < w - borderSize; ++x) {
|
||||
for (int x = borderSize; x < w - borderSize; ++x)
|
||||
{
|
||||
CompactCell c = chf.cells[x + y * w];
|
||||
|
||||
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) {
|
||||
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
|
||||
{
|
||||
CompactSpan s = chf.spans[i];
|
||||
if (chf.areas[i] == RC_NULL_AREA)
|
||||
continue;
|
||||
int sid = 0xFF;
|
||||
// -x
|
||||
|
||||
if (GetCon(s, 0) != RC_NOT_CONNECTED) {
|
||||
if (GetCon(s, 0) != RC_NOT_CONNECTED)
|
||||
{
|
||||
int ax = x + GetDirOffsetX(0);
|
||||
int ay = y + GetDirOffsetY(0);
|
||||
int ai = chf.cells[ax + ay * w].index + GetCon(s, 0);
|
||||
|
@ -107,29 +116,35 @@ public class RecastLayers {
|
|||
sid = srcReg[ai];
|
||||
}
|
||||
|
||||
if (sid == 0xff) {
|
||||
if (sid == 0xff)
|
||||
{
|
||||
sid = sweepId++;
|
||||
sweeps[sid].nei = 0xff;
|
||||
sweeps[sid].ns = 0;
|
||||
}
|
||||
|
||||
// -y
|
||||
if (GetCon(s, 3) != RC_NOT_CONNECTED) {
|
||||
if (GetCon(s, 3) != RC_NOT_CONNECTED)
|
||||
{
|
||||
int ax = x + GetDirOffsetX(3);
|
||||
int ay = y + GetDirOffsetY(3);
|
||||
int ai = chf.cells[ax + ay * w].index + GetCon(s, 3);
|
||||
int nr = srcReg[ai];
|
||||
if (nr != 0xff) {
|
||||
if (nr != 0xff)
|
||||
{
|
||||
// Set neighbour when first valid neighbour is
|
||||
// encoutered.
|
||||
if (sweeps[sid].ns == 0)
|
||||
sweeps[sid].nei = nr;
|
||||
|
||||
if (sweeps[sid].nei == nr) {
|
||||
if (sweeps[sid].nei == nr)
|
||||
{
|
||||
// Update existing neighbour
|
||||
sweeps[sid].ns++;
|
||||
prevCount[nr]++;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is hit if there is nore than one
|
||||
// neighbour.
|
||||
// Invalidate the neighbour.
|
||||
|
@ -143,47 +158,60 @@ public class RecastLayers {
|
|||
}
|
||||
|
||||
// Create unique ID.
|
||||
for (int i = 0; i < sweepId; ++i) {
|
||||
for (int i = 0; i < sweepId; ++i)
|
||||
{
|
||||
// If the neighbour is set and there is only one continuous
|
||||
// connection to it,
|
||||
// the sweep will be merged with the previous one, else new
|
||||
// region is created.
|
||||
if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == sweeps[i].ns) {
|
||||
if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == sweeps[i].ns)
|
||||
{
|
||||
sweeps[i].id = sweeps[i].nei;
|
||||
} else {
|
||||
if (regId == 255) {
|
||||
}
|
||||
else
|
||||
{
|
||||
if (regId == 255)
|
||||
{
|
||||
throw new Exception("rcBuildHeightfieldLayers: Region ID overflow.");
|
||||
}
|
||||
|
||||
sweeps[i].id = regId++;
|
||||
}
|
||||
}
|
||||
|
||||
// Remap local sweep ids to region ids.
|
||||
for (int x = borderSize; x < w - borderSize; ++x) {
|
||||
for (int x = borderSize; x < w - borderSize; ++x)
|
||||
{
|
||||
CompactCell c = chf.cells[x + y * w];
|
||||
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) {
|
||||
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
|
||||
{
|
||||
if (srcReg[i] != 0xff)
|
||||
srcReg[i] = sweeps[srcReg[i]].id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int nregs = regId;
|
||||
LayerRegion[] regs = new LayerRegion[nregs];
|
||||
|
||||
// Construct regions
|
||||
for (int i = 0; i < nregs; ++i) {
|
||||
for (int i = 0; i < nregs; ++i)
|
||||
{
|
||||
regs[i] = new LayerRegion(i);
|
||||
}
|
||||
|
||||
// Find region neighbours and overlapping regions.
|
||||
List<int> lregs = new List<int>();
|
||||
for (int y = 0; y < h; ++y) {
|
||||
for (int x = 0; x < w; ++x) {
|
||||
for (int y = 0; y < h; ++y)
|
||||
{
|
||||
for (int x = 0; x < w; ++x)
|
||||
{
|
||||
CompactCell c = chf.cells[x + y * w];
|
||||
|
||||
lregs.Clear();
|
||||
|
||||
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) {
|
||||
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
|
||||
{
|
||||
CompactSpan s = chf.spans[i];
|
||||
int ri = srcReg[i];
|
||||
if (ri == 0xff)
|
||||
|
@ -196,8 +224,10 @@ public class RecastLayers {
|
|||
lregs.Add(ri);
|
||||
|
||||
// Update neighbours
|
||||
for (int dir = 0; dir < 4; ++dir) {
|
||||
if (GetCon(s, dir) != RC_NOT_CONNECTED) {
|
||||
for (int dir = 0; dir < 4; ++dir)
|
||||
{
|
||||
if (GetCon(s, dir) != RC_NOT_CONNECTED)
|
||||
{
|
||||
int ax = x + GetDirOffsetX(dir);
|
||||
int ay = y + GetDirOffsetY(dir);
|
||||
int ai = chf.cells[ax + ay * w].index + GetCon(s, dir);
|
||||
|
@ -206,13 +236,15 @@ public class RecastLayers {
|
|||
addUnique(regs[ri].neis, rai);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Update overlapping regions.
|
||||
for (int i = 0; i < lregs.Count - 1; ++i) {
|
||||
for (int j = i + 1; j < lregs.Count; ++j) {
|
||||
if (lregs[i] != lregs[j]) {
|
||||
for (int i = 0; i < lregs.Count - 1; ++i)
|
||||
{
|
||||
for (int j = i + 1; j < lregs.Count; ++j)
|
||||
{
|
||||
if (lregs[i] != lregs[j])
|
||||
{
|
||||
LayerRegion ri = regs[lregs[i]];
|
||||
LayerRegion rj = regs[lregs[j]];
|
||||
addUnique(ri.layers, lregs[j]);
|
||||
|
@ -220,7 +252,6 @@ public class RecastLayers {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -229,7 +260,8 @@ public class RecastLayers {
|
|||
|
||||
List<int> stack = new List<int>();
|
||||
|
||||
for (int i = 0; i < nregs; ++i) {
|
||||
for (int i = 0; i < nregs; ++i)
|
||||
{
|
||||
LayerRegion root = regs[i];
|
||||
// Skip already visited.
|
||||
if (root.layerId != 0xff)
|
||||
|
@ -241,13 +273,15 @@ public class RecastLayers {
|
|||
|
||||
stack.Add(i);
|
||||
|
||||
while (stack.Count != 0) {
|
||||
while (stack.Count != 0)
|
||||
{
|
||||
// Pop front
|
||||
int pop = stack[0]; // TODO : 여기에 stack 처럼 작동하게 했는데, 스택인지는 모르겠음
|
||||
stack.RemoveAt(0);
|
||||
LayerRegion reg = regs[pop];
|
||||
|
||||
foreach (int nei in reg.neis) {
|
||||
foreach (int nei in reg.neis)
|
||||
{
|
||||
LayerRegion regn = regs[nei];
|
||||
// Skip already visited.
|
||||
if (regn.layerId != 0xff)
|
||||
|
@ -280,17 +314,20 @@ public class RecastLayers {
|
|||
// Merge non-overlapping regions that are close in height.
|
||||
int mergeHeight = walkableHeight * 4;
|
||||
|
||||
for (int i = 0; i < nregs; ++i) {
|
||||
for (int i = 0; i < nregs; ++i)
|
||||
{
|
||||
LayerRegion ri = regs[i];
|
||||
if (!ri.@base)
|
||||
continue;
|
||||
|
||||
int newId = ri.layerId;
|
||||
|
||||
for (;;) {
|
||||
for (;;)
|
||||
{
|
||||
int oldId = 0xff;
|
||||
|
||||
for (int j = 0; j < nregs; ++j) {
|
||||
for (int j = 0; j < nregs; ++j)
|
||||
{
|
||||
if (i == j)
|
||||
continue;
|
||||
LayerRegion rj = regs[j];
|
||||
|
@ -311,16 +348,19 @@ public class RecastLayers {
|
|||
bool overlap = false;
|
||||
// Iterate over all regions which have the same layerId as
|
||||
// 'rj'
|
||||
for (int k = 0; k < nregs; ++k) {
|
||||
for (int k = 0; k < nregs; ++k)
|
||||
{
|
||||
if (regs[k].layerId != rj.layerId)
|
||||
continue;
|
||||
// Check if region 'k' is overlapping region 'ri'
|
||||
// Index to 'regs' is the same as region id.
|
||||
if (contains(ri.layers, k)) {
|
||||
if (contains(ri.layers, k))
|
||||
{
|
||||
overlap = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Cannot merge of regions overlap.
|
||||
if (overlap)
|
||||
continue;
|
||||
|
@ -335,9 +375,11 @@ public class RecastLayers {
|
|||
break;
|
||||
|
||||
// Merge
|
||||
for (int j = 0; j < nregs; ++j) {
|
||||
for (int j = 0; j < nregs; ++j)
|
||||
{
|
||||
LayerRegion rj = regs[j];
|
||||
if (rj.layerId == oldId) {
|
||||
if (rj.layerId == oldId)
|
||||
{
|
||||
rj.@base = false;
|
||||
// Remap layerIds.
|
||||
rj.layerId = newId;
|
||||
|
@ -359,18 +401,21 @@ public class RecastLayers {
|
|||
layerId = 0;
|
||||
for (int i = 0; i < nregs; ++i)
|
||||
remap[regs[i].layerId] = 1;
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
if (remap[i] != 0)
|
||||
remap[i] = layerId++;
|
||||
else
|
||||
remap[i] = 0xff;
|
||||
}
|
||||
|
||||
// Remap ids.
|
||||
for (int i = 0; i < nregs; ++i)
|
||||
regs[i].layerId = remap[regs[i].layerId];
|
||||
|
||||
// No layers, return empty.
|
||||
if (layerId == 0) {
|
||||
if (layerId == 0)
|
||||
{
|
||||
// ctx.stopTimer(RC_TIMER_BUILD_LAYERS);
|
||||
return null;
|
||||
}
|
||||
|
@ -393,12 +438,14 @@ public class RecastLayers {
|
|||
|
||||
HeightfieldLayerSet lset = new HeightfieldLayerSet();
|
||||
lset.layers = new HeightfieldLayerSet.HeightfieldLayer[layerId];
|
||||
for (int i = 0; i < lset.layers.Length; i++) {
|
||||
for (int i = 0; i < lset.layers.Length; i++)
|
||||
{
|
||||
lset.layers[i] = new HeightfieldLayerSet.HeightfieldLayer();
|
||||
}
|
||||
|
||||
// Store layers.
|
||||
for (int i = 0; i < lset.layers.Length; ++i) {
|
||||
for (int i = 0; i < lset.layers.Length; ++i)
|
||||
{
|
||||
int curId = i;
|
||||
|
||||
HeightfieldLayerSet.HeightfieldLayer layer = lset.layers[i];
|
||||
|
@ -412,8 +459,10 @@ public class RecastLayers {
|
|||
|
||||
// Find layer height bounds.
|
||||
int hmin = 0, hmax = 0;
|
||||
for (int j = 0; j < nregs; ++j) {
|
||||
if (regs[j].@base && regs[j].layerId == curId) {
|
||||
for (int j = 0; j < nregs; ++j)
|
||||
{
|
||||
if (regs[j].@base && regs[j].layerId == curId)
|
||||
{
|
||||
hmin = regs[j].ymin;
|
||||
hmax = regs[j].ymax;
|
||||
}
|
||||
|
@ -439,12 +488,15 @@ public class RecastLayers {
|
|||
layer.maxy = 0;
|
||||
|
||||
// Copy height and area from compact heightfield.
|
||||
for (int y = 0; y < lh; ++y) {
|
||||
for (int x = 0; x < lw; ++x) {
|
||||
for (int y = 0; y < lh; ++y)
|
||||
{
|
||||
for (int x = 0; x < lw; ++x)
|
||||
{
|
||||
int cx = borderSize + x;
|
||||
int cy = borderSize + y;
|
||||
CompactCell c = chf.cells[cx + cy * w];
|
||||
for (int j = c.index, nj = c.index + c.count; j < nj; ++j) {
|
||||
for (int j = c.index, nj = c.index + c.count; j < nj; ++j)
|
||||
{
|
||||
CompactSpan s = chf.spans[j];
|
||||
// Skip unassigned regions.
|
||||
if (srcReg[j] == 0xff)
|
||||
|
@ -468,14 +520,17 @@ public class RecastLayers {
|
|||
// Check connection.
|
||||
char portal = (char)0;
|
||||
char con = (char)0;
|
||||
for (int dir = 0; dir < 4; ++dir) {
|
||||
if (GetCon(s, dir) != RC_NOT_CONNECTED) {
|
||||
for (int dir = 0; dir < 4; ++dir)
|
||||
{
|
||||
if (GetCon(s, dir) != RC_NOT_CONNECTED)
|
||||
{
|
||||
int ax = cx + GetDirOffsetX(dir);
|
||||
int ay = cy + GetDirOffsetY(dir);
|
||||
int ai = chf.cells[ax + ay * w].index + GetCon(s, dir);
|
||||
int alid = srcReg[ai] != 0xff ? regs[srcReg[ai]].layerId : 0xff;
|
||||
// Portal mask
|
||||
if (chf.areas[ai] != RC_NULL_AREA && lid != alid) {
|
||||
if (chf.areas[ai] != RC_NULL_AREA && lid != alid)
|
||||
{
|
||||
portal |= (char)(1 << dir);
|
||||
// Update height so that it matches on both
|
||||
// sides of the portal.
|
||||
|
@ -483,8 +538,10 @@ public class RecastLayers {
|
|||
if (@as.y > hmin)
|
||||
layer.heights[idx] = Math.Max(layer.heights[idx], (char)(@as.y - hmin));
|
||||
}
|
||||
|
||||
// Valid connection mask
|
||||
if (chf.areas[ai] != RC_NULL_AREA && lid == alid) {
|
||||
if (chf.areas[ai] != RC_NULL_AREA && lid == alid)
|
||||
{
|
||||
int nx = ax - borderSize;
|
||||
int ny = ay - borderSize;
|
||||
if (nx >= 0 && ny >= 0 && nx < lw && ny < lh)
|
||||
|
@ -492,6 +549,7 @@ public class RecastLayers {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
layer.cons[idx] = (portal << 4) | con;
|
||||
}
|
||||
}
|
||||
|
@ -507,5 +565,4 @@ public class RecastLayers {
|
|||
return lset;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -22,8 +22,6 @@ using System;
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
using static RecastConstants;
|
||||
|
||||
public class RecastRasterization
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -22,8 +22,6 @@ using System;
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
public static class RecastVectors
|
||||
{
|
||||
public static void min(float[] a, float[] b, int i)
|
||||
|
|
|
@ -21,12 +21,11 @@ using DotRecast.Recast.Geom;
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
public class RecastVoxelization {
|
||||
|
||||
public class RecastVoxelization
|
||||
{
|
||||
public static Heightfield buildSolidHeightfield(InputGeomProvider geomProvider, RecastBuilderConfig builderCfg,
|
||||
Telemetry ctx) {
|
||||
Telemetry ctx)
|
||||
{
|
||||
RecastConfig cfg = builderCfg.cfg;
|
||||
|
||||
// Allocate voxel heightfield where we rasterize our input data to.
|
||||
|
@ -43,9 +42,11 @@ public class RecastVoxelization {
|
|||
// If your input data is multiple meshes, you can transform them here,
|
||||
// calculate
|
||||
// the are type for each of the meshes and rasterize them.
|
||||
foreach (TriMesh geom in geomProvider.meshes()) {
|
||||
foreach (TriMesh geom in geomProvider.meshes())
|
||||
{
|
||||
float[] verts = geom.getVerts();
|
||||
if (cfg.useTiles) {
|
||||
if (cfg.useTiles)
|
||||
{
|
||||
float[] tbmin = new float[2];
|
||||
float[] tbmax = new float[2];
|
||||
tbmin[0] = builderCfg.bmin[0];
|
||||
|
@ -53,14 +54,17 @@ public class RecastVoxelization {
|
|||
tbmax[0] = builderCfg.bmax[0];
|
||||
tbmax[1] = builderCfg.bmax[2];
|
||||
List<ChunkyTriMeshNode> nodes = geom.getChunksOverlappingRect(tbmin, tbmax);
|
||||
foreach (ChunkyTriMeshNode node in nodes) {
|
||||
foreach (ChunkyTriMeshNode node in nodes)
|
||||
{
|
||||
int[] tris = node.tris;
|
||||
int ntris = tris.Length / 3;
|
||||
int[] m_triareas = Recast.markWalkableTriangles(ctx, cfg.walkableSlopeAngle, verts, tris, ntris,
|
||||
cfg.walkableAreaMod);
|
||||
RecastRasterization.rasterizeTriangles(solid, verts, tris, m_triareas, ntris, cfg.walkableClimb, ctx);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
int[] tris = geom.getTris();
|
||||
int ntris = tris.Length / 3;
|
||||
int[] m_triareas = Recast.markWalkableTriangles(ctx, cfg.walkableSlopeAngle, verts, tris, ntris,
|
||||
|
@ -71,7 +75,5 @@ public class RecastVoxelization {
|
|||
|
||||
return solid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,22 +17,22 @@ freely, subject to the following restrictions:
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
/** Represents a span in a heightfield. */
|
||||
public class Span {
|
||||
|
||||
public class Span
|
||||
{
|
||||
/** The lower limit of the span. [Limit: < smax] */
|
||||
public int smin;
|
||||
|
||||
/** The upper limit of the span. [Limit: <= SPAN_MAX_HEIGHT] */
|
||||
public int smax;
|
||||
|
||||
/** The area id assigned to the span. */
|
||||
public int area;
|
||||
|
||||
/** The next span higher up in column. */
|
||||
public Span next;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -25,8 +25,6 @@ using DotRecast.Core;
|
|||
|
||||
namespace DotRecast.Recast
|
||||
{
|
||||
|
||||
|
||||
public class Telemetry
|
||||
{
|
||||
private readonly ThreadLocal<Dictionary<string, AtomicLong>> timerStart = new ThreadLocal<Dictionary<string, AtomicLong>>(() => new Dictionary<string, AtomicLong>());
|
||||
|
|
|
@ -26,26 +26,33 @@ namespace DotRecast.Detour.Crowd.Test;
|
|||
|
||||
using static DetourCommon;
|
||||
|
||||
public class AbstractCrowdTest {
|
||||
|
||||
protected readonly long[] startRefs = { 281474976710696L, 281474976710773L, 281474976710680L, 281474976710753L,
|
||||
281474976710733L };
|
||||
public class AbstractCrowdTest
|
||||
{
|
||||
protected readonly long[] startRefs =
|
||||
{
|
||||
281474976710696L, 281474976710773L, 281474976710680L, 281474976710753L,
|
||||
281474976710733L
|
||||
};
|
||||
|
||||
protected readonly long[] endRefs = { 281474976710721L, 281474976710767L, 281474976710758L, 281474976710731L, 281474976710772L };
|
||||
|
||||
protected readonly float[][] startPoss = {
|
||||
protected readonly float[][] startPoss =
|
||||
{
|
||||
new[] { 22.60652f, 10.197294f, -45.918674f },
|
||||
new[] { 22.331268f, 10.197294f, -1.0401875f },
|
||||
new[] { 18.694363f, 15.803535f, -73.090416f },
|
||||
new[] { 0.7453353f, 10.197294f, -5.94005f },
|
||||
new[] { -20.651257f, 5.904126f, -13.712508f } };
|
||||
new[] { -20.651257f, 5.904126f, -13.712508f }
|
||||
};
|
||||
|
||||
protected readonly float[][] endPoss = {
|
||||
protected readonly float[][] endPoss =
|
||||
{
|
||||
new[] { 6.4576626f, 10.197294f, -18.33406f },
|
||||
new[] { -5.8023443f, 0.19729415f, 3.008419f },
|
||||
new[] { 38.423977f, 10.197294f, -0.116066754f },
|
||||
new[] { 0.8635526f, 10.197294f, -10.31032f },
|
||||
new[] { 18.784092f, 10.197294f, 3.0543678f } };
|
||||
new[] { 18.784092f, 10.197294f, 3.0543678f }
|
||||
};
|
||||
|
||||
protected MeshData nmd;
|
||||
protected NavMeshQuery query;
|
||||
|
@ -54,7 +61,8 @@ public class AbstractCrowdTest {
|
|||
protected List<CrowdAgent> agents;
|
||||
|
||||
[SetUp]
|
||||
public void setUp() {
|
||||
public void setUp()
|
||||
{
|
||||
nmd = new RecastTestMeshBuilder().getMeshData();
|
||||
navmesh = new NavMesh(nmd, 6, 0);
|
||||
query = new NavMeshQuery(navmesh);
|
||||
|
@ -87,7 +95,8 @@ public class AbstractCrowdTest {
|
|||
agents = new();
|
||||
}
|
||||
|
||||
protected CrowdAgentParams getAgentParams(int updateFlags, int obstacleAvoidanceType) {
|
||||
protected CrowdAgentParams getAgentParams(int updateFlags, int obstacleAvoidanceType)
|
||||
{
|
||||
CrowdAgentParams ap = new CrowdAgentParams();
|
||||
ap.radius = 0.6f;
|
||||
ap.height = 2f;
|
||||
|
@ -101,10 +110,13 @@ public class AbstractCrowdTest {
|
|||
return ap;
|
||||
}
|
||||
|
||||
protected void addAgentGrid(int size, float distance, int updateFlags, int obstacleAvoidanceType, float[] startPos) {
|
||||
protected void addAgentGrid(int size, float distance, int updateFlags, int obstacleAvoidanceType, float[] startPos)
|
||||
{
|
||||
CrowdAgentParams ap = getAgentParams(updateFlags, obstacleAvoidanceType);
|
||||
for (int i = 0; i < size; i++) {
|
||||
for (int j = 0; j < size; j++) {
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
for (int j = 0; j < size; j++)
|
||||
{
|
||||
float[] pos = new float[3];
|
||||
pos[0] = startPos[0] + i * distance;
|
||||
pos[1] = startPos[1];
|
||||
|
@ -114,23 +126,30 @@ public class AbstractCrowdTest {
|
|||
}
|
||||
}
|
||||
|
||||
protected void setMoveTarget(float[] pos, bool adjust) {
|
||||
protected void setMoveTarget(float[] pos, bool adjust)
|
||||
{
|
||||
float[] ext = crowd.getQueryExtents();
|
||||
QueryFilter filter = crowd.getFilter(0);
|
||||
if (adjust) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
if (adjust)
|
||||
{
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
float[] vel = calcVel(ag.npos, pos, ag.option.maxSpeed);
|
||||
crowd.requestMoveVelocity(ag, vel);
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
Result<FindNearestPolyResult> nearest = query.findNearestPoly(pos, ext, filter);
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
crowd.requestMoveTarget(ag, nearest.result.getNearestRef(), nearest.result.getNearestPos());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected float[] calcVel(float[] pos, float[] tgt, float speed) {
|
||||
protected float[] calcVel(float[] pos, float[] tgt, float speed)
|
||||
{
|
||||
float[] vel = vSub(tgt, pos);
|
||||
vel[1] = 0.0f;
|
||||
vNormalize(vel);
|
||||
|
@ -138,13 +157,14 @@ public class AbstractCrowdTest {
|
|||
return vel;
|
||||
}
|
||||
|
||||
protected void dumpActiveAgents(int i) {
|
||||
protected void dumpActiveAgents(int i)
|
||||
{
|
||||
Console.WriteLine(crowd.getActiveAgents().Count);
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
Console.WriteLine(ag.state + ", " + ag.targetState);
|
||||
Console.WriteLine(ag.npos[0] + ", " + ag.npos[1] + ", " + ag.npos[2]);
|
||||
Console.WriteLine(ag.nvel[0] + ", " + ag.nvel[1] + ", " + ag.nvel[2]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -22,9 +22,10 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Crowd.Test;
|
||||
|
||||
public class Crowd1Test : AbstractCrowdTest {
|
||||
|
||||
static readonly float[][] EXPECTED_A1Q0TVTA = {
|
||||
public class Crowd1Test : AbstractCrowdTest
|
||||
{
|
||||
static readonly float[][] EXPECTED_A1Q0TVTA =
|
||||
{
|
||||
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
|
||||
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
|
||||
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
|
||||
|
@ -90,9 +91,11 @@ public class Crowd1Test : AbstractCrowdTest {
|
|||
new[] { 6.442838f, 10.197294f, -18.328083f, 0.030522f, 0.000000f, -0.012308f },
|
||||
new[] { 6.447162f, 10.197294f, -18.329826f, 0.021620f, 0.000000f, -0.008717f },
|
||||
new[] { 6.450224f, 10.197294f, -18.331062f, 0.015314f, 0.000000f, -0.006175f },
|
||||
new[] { 6.450224f, 10.197294f, -18.331062f, 0.000000f, 0.000000f, 0.000000f } };
|
||||
new[] { 6.450224f, 10.197294f, -18.331062f, 0.000000f, 0.000000f, 0.000000f }
|
||||
};
|
||||
|
||||
static readonly float[][] EXPECTED_A1Q0TVT = {
|
||||
static readonly float[][] EXPECTED_A1Q0TVT =
|
||||
{
|
||||
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
|
||||
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
|
||||
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
|
||||
|
@ -155,9 +158,11 @@ public class Crowd1Test : AbstractCrowdTest {
|
|||
new[] { 6.447767f, 10.197294f, -18.358591f, 0.069268f, 0.000000f, 0.171711f },
|
||||
new[] { 6.453539f, 10.197294f, -18.344282f, 0.028861f, 0.000000f, 0.071547f },
|
||||
new[] { 6.455945f, 10.197294f, -18.338320f, 0.012026f, 0.000000f, 0.029813f },
|
||||
new[] { 6.455945f, 10.197294f, -18.338320f, 0.000000f, 0.000000f, 0.000000f } };
|
||||
new[] { 6.455945f, 10.197294f, -18.338320f, 0.000000f, 0.000000f, 0.000000f }
|
||||
};
|
||||
|
||||
static readonly float[][] EXPECTED_A1Q0TV = {
|
||||
static readonly float[][] EXPECTED_A1Q0TV =
|
||||
{
|
||||
new[] { 22.333418f, 10.197294f, -45.751896f, -2.987050f, 0.000000f, 1.824153f },
|
||||
new[] { 21.787214f, 10.197294f, -45.418335f, -2.987049f, 0.000000f, 1.824153f },
|
||||
new[] { 21.189804f, 10.197294f, -45.053505f, -2.987050f, 0.000000f, 1.824153f },
|
||||
|
@ -220,9 +225,11 @@ public class Crowd1Test : AbstractCrowdTest {
|
|||
new[] { 6.447869f, 10.197294f, -18.358244f, 0.068554f, 0.000000f, 0.169280f },
|
||||
new[] { 6.453582f, 10.197294f, -18.344137f, 0.028564f, 0.000000f, 0.070535f },
|
||||
new[] { 6.455962f, 10.197294f, -18.338259f, 0.011902f, 0.000000f, 0.029390f },
|
||||
new[] { 6.455962f, 10.197294f, -18.338259f, 0.000000f, 0.000000f, 0.000000f } };
|
||||
new[] { 6.455962f, 10.197294f, -18.338259f, 0.000000f, 0.000000f, 0.000000f }
|
||||
};
|
||||
|
||||
static readonly float[][] EXPECTED_A1Q0T = {
|
||||
static readonly float[][] EXPECTED_A1Q0T =
|
||||
{
|
||||
new[] { 22.333418f, 10.197294f, -45.751896f, -2.987050f, 0.000000f, 1.824153f },
|
||||
new[] { 21.787214f, 10.197294f, -45.418335f, -2.987049f, 0.000000f, 1.824153f },
|
||||
new[] { 21.189804f, 10.197294f, -45.053505f, -2.987050f, 0.000000f, 1.824153f },
|
||||
|
@ -285,9 +292,11 @@ public class Crowd1Test : AbstractCrowdTest {
|
|||
new[] { 6.447869f, 10.197294f, -18.358244f, 0.068554f, 0.000000f, 0.169280f },
|
||||
new[] { 6.453582f, 10.197294f, -18.344137f, 0.028564f, 0.000000f, 0.070535f },
|
||||
new[] { 6.455962f, 10.197294f, -18.338259f, 0.011902f, 0.000000f, 0.029390f },
|
||||
new[] { 6.455962f, 10.197294f, -18.338259f, 0.000000f, 0.000000f, 0.000000f } };
|
||||
new[] { 6.455962f, 10.197294f, -18.338259f, 0.000000f, 0.000000f, 0.000000f }
|
||||
};
|
||||
|
||||
static readonly float[][] EXPECTED_A1Q1TVTA = {
|
||||
static readonly float[][] EXPECTED_A1Q1TVTA =
|
||||
{
|
||||
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
|
||||
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
|
||||
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
|
||||
|
@ -351,9 +360,11 @@ public class Crowd1Test : AbstractCrowdTest {
|
|||
new[] { 6.447618f, 10.197294f, -18.344862f, 0.020681f, 0.000000f, 0.022238f },
|
||||
new[] { 6.450547f, 10.197294f, -18.341711f, 0.014649f, 0.000000f, 0.015752f },
|
||||
new[] { 6.452622f, 10.197294f, -18.339479f, 0.010377f, 0.000000f, 0.011157f },
|
||||
new[] { 6.452622f, 10.197294f, -18.339479f, 0.000000f, 0.000000f, 0.000000f }, };
|
||||
new[] { 6.452622f, 10.197294f, -18.339479f, 0.000000f, 0.000000f, 0.000000f },
|
||||
};
|
||||
|
||||
static readonly float[][] EXPECTED_A1Q2TVTA = {
|
||||
static readonly float[][] EXPECTED_A1Q2TVTA =
|
||||
{
|
||||
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
|
||||
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
|
||||
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
|
||||
|
@ -417,9 +428,11 @@ public class Crowd1Test : AbstractCrowdTest {
|
|||
new[] { 6.453821f, 10.197294f, -18.352995f, 0.007909f, 0.000000f, 0.038981f },
|
||||
new[] { 6.454941f, 10.197294f, -18.347473f, 0.005603f, 0.000000f, 0.027612f },
|
||||
new[] { 6.455735f, 10.197294f, -18.343561f, 0.003969f, 0.000000f, 0.019560f },
|
||||
new[] { 6.455735f, 10.197294f, -18.343561f, 0.000000f, 0.000000f, 0.000000f }, };
|
||||
new[] { 6.455735f, 10.197294f, -18.343561f, 0.000000f, 0.000000f, 0.000000f },
|
||||
};
|
||||
|
||||
static readonly float[][] EXPECTED_A1Q3TVTA = {
|
||||
static readonly float[][] EXPECTED_A1Q3TVTA =
|
||||
{
|
||||
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
|
||||
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
|
||||
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
|
||||
|
@ -480,9 +493,11 @@ public class Crowd1Test : AbstractCrowdTest {
|
|||
new[] { 6.397289f, 10.197294f, -18.479179f, 0.456429f, 0.000000f, 1.107728f },
|
||||
new[] { 6.439916f, 10.197294f, -18.384853f, 0.213137f, 0.000000f, 0.471627f },
|
||||
new[] { 6.454712f, 10.197294f, -18.342505f, 0.073981f, 0.000000f, 0.211745f },
|
||||
new[] { 6.454712f, 10.197294f, -18.342505f, 0.000000f, 0.000000f, 0.000000f } };
|
||||
new[] { 6.454712f, 10.197294f, -18.342505f, 0.000000f, 0.000000f, 0.000000f }
|
||||
};
|
||||
|
||||
static readonly float[][] EXPECTED_A1Q3TVTAS = {
|
||||
static readonly float[][] EXPECTED_A1Q3TVTAS =
|
||||
{
|
||||
new[] { 22.322426f, 10.197294f, -45.771397f, -3.107285f, 0.000000f, 1.610831f },
|
||||
new[] { 21.754303f, 10.197294f, -45.476715f, -3.106887f, 0.000000f, 1.611599f },
|
||||
new[] { 21.133097f, 10.197294f, -45.154068f, -3.106033f, 0.000000f, 1.613245f },
|
||||
|
@ -543,18 +558,22 @@ public class Crowd1Test : AbstractCrowdTest {
|
|||
new[] { 6.397289f, 10.197294f, -18.479179f, 0.456429f, 0.000000f, 1.107728f },
|
||||
new[] { 6.439916f, 10.197294f, -18.384853f, 0.213137f, 0.000000f, 0.471627f },
|
||||
new[] { 6.454712f, 10.197294f, -18.342505f, 0.073981f, 0.000000f, 0.211745f },
|
||||
new[] { 6.454712f, 10.197294f, -18.342505f, 0.000000f, 0.000000f, 0.000000f } };
|
||||
new[] { 6.454712f, 10.197294f, -18.342505f, 0.000000f, 0.000000f, 0.000000f }
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void testAgent1Quality0TVTA() {
|
||||
public void testAgent1Quality0TVTA()
|
||||
{
|
||||
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
|
||||
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
|
||||
|
||||
addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]);
|
||||
setMoveTarget(endPoss[0], false);
|
||||
for (int i = 0; i < EXPECTED_A1Q0TVTA.Length; i++) {
|
||||
for (int i = 0; i < EXPECTED_A1Q0TVTA.Length; i++)
|
||||
{
|
||||
crowd.update(1 / 5f, null);
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q0TVTA[i][0]).Within(0.001f));
|
||||
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q0TVTA[i][1]).Within(0.001f));
|
||||
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q0TVTA[i][2]).Within(0.001f));
|
||||
|
@ -566,15 +585,18 @@ public class Crowd1Test : AbstractCrowdTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testAgent1Quality0TVT() {
|
||||
public void testAgent1Quality0TVT()
|
||||
{
|
||||
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
|
||||
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
|
||||
|
||||
addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]);
|
||||
setMoveTarget(endPoss[0], false);
|
||||
for (int i = 0; i < EXPECTED_A1Q0TVT.Length; i++) {
|
||||
for (int i = 0; i < EXPECTED_A1Q0TVT.Length; i++)
|
||||
{
|
||||
crowd.update(1 / 5f, null);
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q0TVT[i][0]).Within(0.001f));
|
||||
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q0TVT[i][1]).Within(0.001f));
|
||||
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q0TVT[i][2]).Within(0.001f));
|
||||
|
@ -586,14 +608,17 @@ public class Crowd1Test : AbstractCrowdTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testAgent1Quality0TV() {
|
||||
public void testAgent1Quality0TV()
|
||||
{
|
||||
int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS;
|
||||
|
||||
addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]);
|
||||
setMoveTarget(endPoss[0], false);
|
||||
for (int i = 0; i < EXPECTED_A1Q0TV.Length; i++) {
|
||||
for (int i = 0; i < EXPECTED_A1Q0TV.Length; i++)
|
||||
{
|
||||
crowd.update(1 / 5f, null);
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q0TV[i][0]).Within(0.001f));
|
||||
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q0TV[i][1]).Within(0.001f));
|
||||
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q0TV[i][2]).Within(0.001f));
|
||||
|
@ -605,14 +630,17 @@ public class Crowd1Test : AbstractCrowdTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testAgent1Quality0T() {
|
||||
public void testAgent1Quality0T()
|
||||
{
|
||||
int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
|
||||
|
||||
addAgentGrid(1, 0.4f, updateFlags, 0, startPoss[0]);
|
||||
setMoveTarget(endPoss[0], false);
|
||||
for (int i = 0; i < EXPECTED_A1Q0T.Length; i++) {
|
||||
for (int i = 0; i < EXPECTED_A1Q0T.Length; i++)
|
||||
{
|
||||
crowd.update(1 / 5f, null);
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q0T[i][0]).Within(0.001));
|
||||
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q0T[i][1]).Within(0.001));
|
||||
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q0T[i][2]).Within(0.001));
|
||||
|
@ -624,15 +652,18 @@ public class Crowd1Test : AbstractCrowdTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testAgent1Quality1TVTA() {
|
||||
public void testAgent1Quality1TVTA()
|
||||
{
|
||||
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
|
||||
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
|
||||
|
||||
addAgentGrid(1, 0.4f, updateFlags, 1, startPoss[0]);
|
||||
setMoveTarget(endPoss[0], false);
|
||||
for (int i = 0; i < EXPECTED_A1Q1TVTA.Length; i++) {
|
||||
for (int i = 0; i < EXPECTED_A1Q1TVTA.Length; i++)
|
||||
{
|
||||
crowd.update(1 / 5f, null);
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q1TVTA[i][0]).Within(0.001f));
|
||||
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q1TVTA[i][1]).Within(0.001f));
|
||||
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q1TVTA[i][2]).Within(0.001f));
|
||||
|
@ -644,15 +675,18 @@ public class Crowd1Test : AbstractCrowdTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testAgent1Quality2TVTA() {
|
||||
public void testAgent1Quality2TVTA()
|
||||
{
|
||||
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
|
||||
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
|
||||
|
||||
addAgentGrid(1, 0.4f, updateFlags, 2, startPoss[0]);
|
||||
setMoveTarget(endPoss[0], false);
|
||||
for (int i = 0; i < EXPECTED_A1Q2TVTA.Length; i++) {
|
||||
for (int i = 0; i < EXPECTED_A1Q2TVTA.Length; i++)
|
||||
{
|
||||
crowd.update(1 / 5f, null);
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTA[i][0]).Within(0.001f));
|
||||
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q2TVTA[i][1]).Within(0.001f));
|
||||
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q2TVTA[i][2]).Within(0.001f));
|
||||
|
@ -664,15 +698,18 @@ public class Crowd1Test : AbstractCrowdTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testAgent1Quality3TVTA() {
|
||||
public void testAgent1Quality3TVTA()
|
||||
{
|
||||
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
|
||||
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
|
||||
|
||||
addAgentGrid(1, 0.4f, updateFlags, 3, startPoss[0]);
|
||||
setMoveTarget(endPoss[0], false);
|
||||
for (int i = 0; i < EXPECTED_A1Q3TVTA.Length; i++) {
|
||||
for (int i = 0; i < EXPECTED_A1Q3TVTA.Length; i++)
|
||||
{
|
||||
crowd.update(1 / 5f, null);
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q3TVTA[i][0]).Within(0.001f));
|
||||
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q3TVTA[i][1]).Within(0.001f));
|
||||
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q3TVTA[i][2]).Within(0.001f));
|
||||
|
@ -684,16 +721,19 @@ public class Crowd1Test : AbstractCrowdTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testAgent1Quality3TVTAS() {
|
||||
public void testAgent1Quality3TVTAS()
|
||||
{
|
||||
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
|
||||
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE
|
||||
| CrowdAgentParams.DT_CROWD_SEPARATION;
|
||||
|
||||
addAgentGrid(1, 0.4f, updateFlags, 3, startPoss[0]);
|
||||
setMoveTarget(endPoss[0], false);
|
||||
for (int i = 0; i < EXPECTED_A1Q3TVTAS.Length; i++) {
|
||||
for (int i = 0; i < EXPECTED_A1Q3TVTAS.Length; i++)
|
||||
{
|
||||
crowd.update(1 / 5f, null);
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents()) {
|
||||
foreach (CrowdAgent ag in crowd.getActiveAgents())
|
||||
{
|
||||
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q3TVTAS[i][0]).Within(0.001f));
|
||||
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q3TVTAS[i][1]).Within(0.001f));
|
||||
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q3TVTAS[i][2]).Within(0.001f));
|
||||
|
@ -703,5 +743,4 @@ public class Crowd1Test : AbstractCrowdTest {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -24,9 +24,10 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Crowd.Test;
|
||||
|
||||
public class Crowd4Test : AbstractCrowdTest {
|
||||
|
||||
static readonly float[][] EXPECTED_A1Q2TVTA = {
|
||||
public class Crowd4Test : AbstractCrowdTest
|
||||
{
|
||||
static readonly float[][] EXPECTED_A1Q2TVTA =
|
||||
{
|
||||
new[] { 23.275612f, 10.197294f, -46.233074f, 0.061640f, 0.000000f, 0.073828f },
|
||||
new[] { 23.350517f, 10.197294f, -46.304905f, 0.030557f, 0.000000f, 0.118703f },
|
||||
new[] { 23.347885f, 10.197294f, -46.331837f, -0.024102f, 0.000000f, -0.093108f },
|
||||
|
@ -118,9 +119,11 @@ public class Crowd4Test : AbstractCrowdTest {
|
|||
new[] { 7.099013f, 10.197294f, -18.064573f, -0.150015f, 0.000000f, 0.647393f },
|
||||
new[] { 7.085871f, 10.197294f, -17.946556f, -0.065714f, 0.000000f, 0.590090f },
|
||||
new[] { 7.067722f, 10.197294f, -17.801628f, -0.090745f, 0.000000f, 0.724639f },
|
||||
new[] { 7.048226f, 10.197294f, -17.673695f, -0.097479f, 0.000000f, 0.639666f } };
|
||||
new[] { 7.048226f, 10.197294f, -17.673695f, -0.097479f, 0.000000f, 0.639666f }
|
||||
};
|
||||
|
||||
static readonly float[][] EXPECTED_A1Q2TVTAS = {
|
||||
static readonly float[][] EXPECTED_A1Q2TVTAS =
|
||||
{
|
||||
new[] { 23.253357f, 10.197294f, -46.279934f, 0.074597f, 0.000000f, -0.017069f },
|
||||
new[] { 23.336805f, 10.197294f, -46.374985f, 0.119401f, 0.000000f, -0.031009f },
|
||||
new[] { 23.351542f, 10.197294f, -46.410728f, 0.053426f, 0.000000f, -0.093556f },
|
||||
|
@ -228,9 +231,11 @@ public class Crowd4Test : AbstractCrowdTest {
|
|||
new[] { 5.764818f, 10.197294f, -19.506109f, -0.043161f, 0.000000f, 0.194928f },
|
||||
new[] { 5.769928f, 10.197294f, -19.497072f, 0.025550f, 0.000000f, 0.045183f },
|
||||
new[] { 5.753877f, 10.197294f, -19.524942f, -0.080258f, 0.000000f, -0.139354f },
|
||||
new[] { 5.731219f, 10.197294f, -19.533819f, -0.113286f, 0.000000f, -0.044384f } };
|
||||
new[] { 5.731219f, 10.197294f, -19.533819f, -0.113286f, 0.000000f, -0.044384f }
|
||||
};
|
||||
|
||||
static readonly float[][] EXPECTED_A1Q2T = {
|
||||
static readonly float[][] EXPECTED_A1Q2T =
|
||||
{
|
||||
new[] { 22.990597f, 10.197294f, -46.112606f, -2.999564f, 0.000000f, 1.803501f },
|
||||
new[] { 22.524744f, 10.197294f, -45.867702f, -2.989815f, 0.000000f, 1.819617f },
|
||||
new[] { 21.946421f, 10.197294f, -45.530769f, -2.987121f, 0.000000f, 1.824035f },
|
||||
|
@ -296,16 +301,19 @@ public class Crowd4Test : AbstractCrowdTest {
|
|||
new[] { 5.947361f, 10.197294f, -19.047245f, 1.574677f, 0.000000f, 2.491868f },
|
||||
new[] { 5.960892f, 10.197294f, -18.990824f, 1.488381f, 0.000000f, 2.080121f },
|
||||
new[] { 5.971087f, 10.197294f, -18.963556f, 1.448915f, 0.000000f, 1.915559f },
|
||||
new[] { 5.977089f, 10.197294f, -18.950905f, 1.419177f, 0.000000f, 1.836029f } };
|
||||
new[] { 5.977089f, 10.197294f, -18.950905f, 1.419177f, 0.000000f, 1.836029f }
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void testAgent1Quality2TVTA() {
|
||||
public void testAgent1Quality2TVTA()
|
||||
{
|
||||
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
|
||||
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
|
||||
|
||||
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
|
||||
setMoveTarget(endPoss[0], false);
|
||||
for (int i = 0; i < EXPECTED_A1Q2TVTA.Length; i++) {
|
||||
for (int i = 0; i < EXPECTED_A1Q2TVTA.Length; i++)
|
||||
{
|
||||
crowd.update(1 / 5f, null);
|
||||
CrowdAgent ag = agents[2];
|
||||
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTA[i][0]).Within(0.001f), $"{i}");
|
||||
|
@ -318,14 +326,16 @@ public class Crowd4Test : AbstractCrowdTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testAgent1Quality2TVTAS() {
|
||||
public void testAgent1Quality2TVTAS()
|
||||
{
|
||||
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
|
||||
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE
|
||||
| CrowdAgentParams.DT_CROWD_SEPARATION;
|
||||
|
||||
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
|
||||
setMoveTarget(endPoss[0], false);
|
||||
for (int i = 0; i < EXPECTED_A1Q2TVTAS.Length; i++) {
|
||||
for (int i = 0; i < EXPECTED_A1Q2TVTAS.Length; i++)
|
||||
{
|
||||
crowd.update(1 / 5f, null);
|
||||
CrowdAgent ag = agents[2];
|
||||
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTAS[i][0]).Within(0.001f), $"{i}");
|
||||
|
@ -338,7 +348,8 @@ public class Crowd4Test : AbstractCrowdTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testAgent1Quality2T() {
|
||||
public void testAgent1Quality2T()
|
||||
{
|
||||
int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
|
||||
|
||||
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
|
||||
|
@ -352,12 +363,18 @@ public class Crowd4Test : AbstractCrowdTest {
|
|||
|
||||
crowd.update(1 / 5f, null);
|
||||
CrowdAgent ag = agents[2];
|
||||
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2T[i][0]).Within(0.00001f), $"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}"); Console.WriteLine($"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}");
|
||||
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q2T[i][1]).Within(0.00001f), $"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}"); Console.WriteLine($"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}");
|
||||
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q2T[i][2]).Within(0.00001f), $"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}"); Console.WriteLine($"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}");
|
||||
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q2T[i][3]).Within(0.00001f), $"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}"); Console.WriteLine($"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}");
|
||||
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q2T[i][4]).Within(0.00001f), $"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}"); Console.WriteLine($"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}");
|
||||
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q2T[i][5]).Within(0.00001f), $"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}"); Console.WriteLine($"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}");
|
||||
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2T[i][0]).Within(0.00001f), $"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}");
|
||||
Console.WriteLine($"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}");
|
||||
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q2T[i][1]).Within(0.00001f), $"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}");
|
||||
Console.WriteLine($"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}");
|
||||
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q2T[i][2]).Within(0.00001f), $"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}");
|
||||
Console.WriteLine($"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}");
|
||||
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q2T[i][3]).Within(0.00001f), $"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}");
|
||||
Console.WriteLine($"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}");
|
||||
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q2T[i][4]).Within(0.00001f), $"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}");
|
||||
Console.WriteLine($"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}");
|
||||
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q2T[i][5]).Within(0.00001f), $"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}");
|
||||
Console.WriteLine($"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}");
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,10 +22,10 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Crowd.Test;
|
||||
|
||||
public class Crowd4VelocityTest : AbstractCrowdTest {
|
||||
|
||||
static readonly float[][] EXPECTED_A1Q3TVTA = {
|
||||
|
||||
public class Crowd4VelocityTest : AbstractCrowdTest
|
||||
{
|
||||
static readonly float[][] EXPECTED_A1Q3TVTA =
|
||||
{
|
||||
new[] { 6.101694f, 10.197294f, -17.678480f, 0.000000f, 0.000000f, 0.000000f },
|
||||
new[] { 6.024141f, 10.197294f, -17.589798f, -0.107331f, 0.000000f, 0.098730f },
|
||||
new[] { 6.004839f, 10.197294f, -17.554886f, -0.096506f, 0.000000f, 0.174561f },
|
||||
|
@ -92,20 +92,25 @@ public class Crowd4VelocityTest : AbstractCrowdTest {
|
|||
new[] { 4.320761f, 10.197294f, -8.089768f, -0.003438f, 0.000000f, -0.072332f },
|
||||
new[] { 4.337994f, 10.197294f, -8.081223f, 0.086165f, 0.000000f, 0.042728f },
|
||||
new[] { 4.328456f, 10.197294f, -8.057701f, -0.047688f, 0.000000f, 0.117607f },
|
||||
new[] { 4.327769f, 10.197294f, -8.072167f, -0.003438f, 0.000000f, -0.072332f } };
|
||||
new[] { 4.327769f, 10.197294f, -8.072167f, -0.003438f, 0.000000f, -0.072332f }
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void testAgent1Quality3TVTA() {
|
||||
public void testAgent1Quality3TVTA()
|
||||
{
|
||||
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
|
||||
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
|
||||
|
||||
addAgentGrid(2, 0.3f, updateFlags, 3, endPoss[0]);
|
||||
setMoveTarget(endPoss[4], false);
|
||||
for (int i = 0; i < EXPECTED_A1Q3TVTA.Length; i++) {
|
||||
for (int i = 0; i < EXPECTED_A1Q3TVTA.Length; i++)
|
||||
{
|
||||
crowd.update(1 / 5f, null);
|
||||
if (i == 20) {
|
||||
if (i == 20)
|
||||
{
|
||||
setMoveTarget(startPoss[2], true);
|
||||
}
|
||||
|
||||
CrowdAgent ag = agents[1];
|
||||
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q3TVTA[i][0]).Within(0.001f));
|
||||
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q3TVTA[i][1]).Within(0.001f));
|
||||
|
@ -115,5 +120,4 @@ public class Crowd4VelocityTest : AbstractCrowdTest {
|
|||
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q3TVTA[i][5]).Within(0.001f));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -22,18 +22,20 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Crowd.Test;
|
||||
|
||||
public class PathCorridorTest {
|
||||
|
||||
public class PathCorridorTest
|
||||
{
|
||||
private readonly PathCorridor corridor = new PathCorridor();
|
||||
private readonly QueryFilter filter = new DefaultQueryFilter();
|
||||
|
||||
[SetUp]
|
||||
public void setUp() {
|
||||
public void setUp()
|
||||
{
|
||||
corridor.reset(0, new float[] { 10, 20, 30 });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void shouldKeepOriginalPathInFindCornersWhenNothingCanBePruned() {
|
||||
public void shouldKeepOriginalPathInFindCornersWhenNothingCanBePruned()
|
||||
{
|
||||
List<StraightPathItem> straightPath = new();
|
||||
straightPath.Add(new StraightPathItem(new float[] { 11, 20, 30.00001f }, 0, 0));
|
||||
straightPath.Add(new StraightPathItem(new float[] { 12, 20, 30.00002f }, 0, 0));
|
||||
|
@ -54,7 +56,8 @@ public class PathCorridorTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void shouldPrunePathInFindCorners() {
|
||||
public void shouldPrunePathInFindCorners()
|
||||
{
|
||||
List<StraightPathItem> straightPath = new();
|
||||
straightPath.Add(new StraightPathItem(new float[] { 10, 20, 30.00001f }, 0, 0)); // too close
|
||||
straightPath.Add(new StraightPathItem(new float[] { 10, 20, 30.00002f }, 0, 0)); // too close
|
||||
|
@ -76,5 +79,4 @@ public class PathCorridorTest {
|
|||
Assert.That(path.Count, Is.EqualTo(2));
|
||||
Assert.That(path, Is.EqualTo(new List<StraightPathItem> { straightPath[2], straightPath[3] }));
|
||||
}
|
||||
|
||||
}
|
|
@ -22,8 +22,8 @@ using DotRecast.Recast.Geom;
|
|||
|
||||
namespace DotRecast.Detour.Crowd.Test;
|
||||
|
||||
public class RecastTestMeshBuilder {
|
||||
|
||||
public class RecastTestMeshBuilder
|
||||
{
|
||||
private readonly MeshData meshData;
|
||||
public const float m_cellSize = 0.3f;
|
||||
public const float m_cellHeight = 0.2f;
|
||||
|
@ -49,7 +49,8 @@ public class RecastTestMeshBuilder {
|
|||
public RecastTestMeshBuilder(InputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize,
|
||||
float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope,
|
||||
int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly,
|
||||
float m_detailSampleDist, float m_detailSampleMaxError) {
|
||||
float m_detailSampleDist, float m_detailSampleMaxError)
|
||||
{
|
||||
RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius,
|
||||
m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
|
||||
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
|
||||
|
@ -57,9 +58,11 @@ public class RecastTestMeshBuilder {
|
|||
RecastBuilder rcBuilder = new RecastBuilder();
|
||||
RecastBuilderResult rcResult = rcBuilder.build(m_geom, bcfg);
|
||||
PolyMesh m_pmesh = rcResult.getMesh();
|
||||
for (int i = 0; i < m_pmesh.npolys; ++i) {
|
||||
for (int i = 0; i < m_pmesh.npolys; ++i)
|
||||
{
|
||||
m_pmesh.flags[i] = 1;
|
||||
}
|
||||
|
||||
PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
|
||||
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
|
||||
option.verts = m_pmesh.verts;
|
||||
|
@ -104,7 +107,8 @@ public class RecastTestMeshBuilder {
|
|||
meshData = NavMeshBuilder.createNavMeshData(option);
|
||||
}
|
||||
|
||||
public MeshData getMeshData() {
|
||||
public MeshData getMeshData()
|
||||
{
|
||||
return meshData;
|
||||
}
|
||||
}
|
|
@ -20,8 +20,8 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Crowd.Test;
|
||||
|
||||
public class SampleAreaModifications {
|
||||
|
||||
public class SampleAreaModifications
|
||||
{
|
||||
public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
|
||||
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
|
||||
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
|
||||
|
@ -32,14 +32,19 @@ public class SampleAreaModifications {
|
|||
|
||||
public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
|
||||
SAMPLE_POLYAREA_TYPE_MASK);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
|
||||
SAMPLE_POLYAREA_TYPE_MASK);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
|
||||
SAMPLE_POLYAREA_TYPE_MASK);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
|
||||
SAMPLE_POLYAREA_TYPE_MASK);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR,
|
||||
SAMPLE_POLYAREA_TYPE_DOOR);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP,
|
||||
SAMPLE_POLYAREA_TYPE_JUMP);
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Test;
|
||||
|
||||
public class DynamicNavMeshTest {
|
||||
|
||||
public class DynamicNavMeshTest
|
||||
{
|
||||
private static readonly float[] START_POS = new float[] { 70.87453f, 0.0010070801f, 86.69021f };
|
||||
private static readonly float[] END_POS = new float[] { -50.22061f, 0.0010070801f, -70.761444f };
|
||||
private static readonly float[] EXTENT = new float[] { 0.1f, 0.1f, 0.1f };
|
||||
|
@ -17,7 +17,8 @@ public class DynamicNavMeshTest {
|
|||
|
||||
|
||||
[Test]
|
||||
public void e2eTest() {
|
||||
public void e2eTest()
|
||||
{
|
||||
byte[] bytes = Loader.ToBytes("test_tiles.voxels");
|
||||
using var ms = new MemoryStream(bytes);
|
||||
using var bis = new BinaryReader(ms);
|
||||
|
|
|
@ -23,10 +23,11 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Test.Io;
|
||||
|
||||
public class VoxelFileReaderTest {
|
||||
|
||||
public class VoxelFileReaderTest
|
||||
{
|
||||
[Test]
|
||||
public void shouldReadSingleTileFile() {
|
||||
public void shouldReadSingleTileFile()
|
||||
{
|
||||
byte[] bytes = Loader.ToBytes("test.voxels");
|
||||
using var ms = new MemoryStream(bytes);
|
||||
using var bis = new BinaryReader(ms);
|
||||
|
@ -51,7 +52,8 @@ public class VoxelFileReaderTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void shouldReadMultiTileFile() {
|
||||
public void shouldReadMultiTileFile()
|
||||
{
|
||||
byte[] bytes = Loader.ToBytes("test_tiles.voxels");
|
||||
using var ms = new MemoryStream(bytes);
|
||||
using var bis = new BinaryReader(ms);
|
||||
|
|
|
@ -23,11 +23,12 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Test.Io;
|
||||
|
||||
public class VoxelFileReaderWriterTest {
|
||||
|
||||
public class VoxelFileReaderWriterTest
|
||||
{
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void shouldReadSingleTileFile(bool compression) {
|
||||
public void shouldReadSingleTileFile(bool compression)
|
||||
{
|
||||
byte[] bytes = Loader.ToBytes("test.voxels");
|
||||
using var ms = new MemoryStream(bytes);
|
||||
using var bis = new BinaryReader(ms);
|
||||
|
@ -54,7 +55,8 @@ public class VoxelFileReaderWriterTest {
|
|||
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void shouldReadMultiTileFile(bool compression) {
|
||||
public void shouldReadMultiTileFile(bool compression)
|
||||
{
|
||||
byte[] bytes = Loader.ToBytes("test_tiles.voxels");
|
||||
using var ms = new MemoryStream(bytes);
|
||||
using var bis = new BinaryReader(ms);
|
||||
|
@ -80,10 +82,10 @@ public class VoxelFileReaderWriterTest {
|
|||
Assert.That(f.tiles[18].spanData.Length, Is.EqualTo(113400));
|
||||
Assert.That(f.tiles[0].boundsMin, Is.EqualTo(new[] { -101.25f, 0f, -101.25f }));
|
||||
Assert.That(f.tiles[0].boundsMax, Is.EqualTo(new[] { -78.75f, 5.0f, -78.75f }));
|
||||
|
||||
}
|
||||
|
||||
private VoxelFile readWriteRead(BinaryReader bis, bool compression) {
|
||||
private VoxelFile readWriteRead(BinaryReader bis, bool compression)
|
||||
{
|
||||
VoxelFileReader reader = new VoxelFileReader();
|
||||
VoxelFile f = reader.read(bis);
|
||||
|
||||
|
@ -96,5 +98,4 @@ public class VoxelFileReaderWriterTest {
|
|||
using var brIn = new BinaryReader(msIn);
|
||||
return reader.read(brIn);
|
||||
}
|
||||
|
||||
}
|
|
@ -20,8 +20,8 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Test;
|
||||
|
||||
public class SampleAreaModifications {
|
||||
|
||||
public class SampleAreaModifications
|
||||
{
|
||||
public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
|
||||
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
|
||||
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
|
||||
|
@ -32,14 +32,19 @@ public class SampleAreaModifications {
|
|||
|
||||
public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
|
||||
SAMPLE_POLYAREA_TYPE_MASK);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
|
||||
SAMPLE_POLYAREA_TYPE_MASK);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
|
||||
SAMPLE_POLYAREA_TYPE_MASK);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
|
||||
SAMPLE_POLYAREA_TYPE_MASK);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR,
|
||||
SAMPLE_POLYAREA_TYPE_DOOR);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP,
|
||||
SAMPLE_POLYAREA_TYPE_JUMP);
|
||||
|
||||
|
|
|
@ -28,8 +28,8 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Dynamic.Test;
|
||||
|
||||
public class VoxelQueryTest {
|
||||
|
||||
public class VoxelQueryTest
|
||||
{
|
||||
private const int TILE_WIDTH = 100;
|
||||
private const int TILE_DEPTH = 90;
|
||||
private static readonly float[] ORIGIN = new float[] { 50, 10, 40 };
|
||||
|
@ -66,7 +66,8 @@ public class VoxelQueryTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void shouldHandleRaycastWithoutObstacles() {
|
||||
public void shouldHandleRaycastWithoutObstacles()
|
||||
{
|
||||
DynamicNavMesh mesh = createDynaMesh();
|
||||
VoxelQuery query = mesh.voxelQuery();
|
||||
float[] start = { 7.4f, 0.5f, -64.8f };
|
||||
|
@ -76,7 +77,8 @@ public class VoxelQueryTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void shouldHandleRaycastWithObstacles() {
|
||||
public void shouldHandleRaycastWithObstacles()
|
||||
{
|
||||
DynamicNavMesh mesh = createDynaMesh();
|
||||
VoxelQuery query = mesh.voxelQuery();
|
||||
float[] start = { 32.3f, 0.5f, 47.9f };
|
||||
|
@ -86,7 +88,8 @@ public class VoxelQueryTest {
|
|||
Assert.That(hit.Value, Is.EqualTo(0.5263836f).Within(1e-7f));
|
||||
}
|
||||
|
||||
private DynamicNavMesh createDynaMesh() {
|
||||
private DynamicNavMesh createDynaMesh()
|
||||
{
|
||||
var bytes = Loader.ToBytes("test_tiles.voxels");
|
||||
using var ms = new MemoryStream(bytes);
|
||||
using var bis = new BinaryReader(ms);
|
||||
|
|
|
@ -26,10 +26,11 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Extras.Test.Unity.Astar;
|
||||
|
||||
public class UnityAStarPathfindingImporterTest {
|
||||
|
||||
public class UnityAStarPathfindingImporterTest
|
||||
{
|
||||
[Test]
|
||||
public void test_v4_0_6() {
|
||||
public void test_v4_0_6()
|
||||
{
|
||||
NavMesh mesh = loadNavMesh("graph.zip");
|
||||
float[] startPos = new float[] { 8.200293f, 2.155071f, -26.176147f };
|
||||
float[] endPos = new float[] { 11.971109f, 0.000000f, 8.663261f };
|
||||
|
@ -40,7 +41,8 @@ public class UnityAStarPathfindingImporterTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void test_v4_1_16() {
|
||||
public void test_v4_1_16()
|
||||
{
|
||||
NavMesh mesh = loadNavMesh("graph_v4_1_16.zip");
|
||||
float[] startPos = new float[] { 22.93f, -2.37f, -5.11f };
|
||||
float[] endPos = new float[] { 16.81f, -2.37f, 25.52f };
|
||||
|
@ -51,7 +53,8 @@ public class UnityAStarPathfindingImporterTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testBoundsTree() {
|
||||
public void testBoundsTree()
|
||||
{
|
||||
NavMesh mesh = loadNavMesh("test_boundstree.zip");
|
||||
float[] position = { 387.52988f, 19.997f, 368.86282f };
|
||||
|
||||
|
@ -72,7 +75,8 @@ public class UnityAStarPathfindingImporterTest {
|
|||
Assert.That(bvResult.getNearestRef(), Is.EqualTo(clearResult.getNearestRef()));
|
||||
}
|
||||
|
||||
private NavMesh loadNavMesh(string filename) {
|
||||
private NavMesh loadNavMesh(string filename)
|
||||
{
|
||||
var filepath = Loader.ToRPath(filename);
|
||||
using var fs = new FileStream(filepath, FileMode.Open);
|
||||
|
||||
|
@ -83,7 +87,8 @@ public class UnityAStarPathfindingImporterTest {
|
|||
return meshes[0];
|
||||
}
|
||||
|
||||
private Result<List<long>> findPath(NavMesh mesh, float[] startPos, float[] endPos) {
|
||||
private Result<List<long>> findPath(NavMesh mesh, float[] startPos, float[] endPos)
|
||||
{
|
||||
// Perform a simple pathfinding
|
||||
NavMeshQuery query = new NavMeshQuery(mesh);
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
|
@ -92,26 +97,32 @@ public class UnityAStarPathfindingImporterTest {
|
|||
return query.findPath(polys[0].getNearestRef(), polys[1].getNearestRef(), startPos, endPos, filter);
|
||||
}
|
||||
|
||||
private FindNearestPolyResult[] getNearestPolys(NavMesh mesh, params float[][] positions) {
|
||||
private FindNearestPolyResult[] getNearestPolys(NavMesh mesh, params float[][] positions)
|
||||
{
|
||||
NavMeshQuery query = new NavMeshQuery(mesh);
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
float[] extents = new float[] { 0.1f, 0.1f, 0.1f };
|
||||
|
||||
FindNearestPolyResult[] results = new FindNearestPolyResult[positions.Length];
|
||||
for (int i = 0; i < results.Length; i++) {
|
||||
for (int i = 0; i < results.Length; i++)
|
||||
{
|
||||
float[] position = positions[i];
|
||||
Result<FindNearestPolyResult> result = query.findNearestPoly(position, extents, filter);
|
||||
Assert.That(result.succeeded(), Is.True);
|
||||
Assert.That(result.result.getNearestPos(), Is.Not.Null, "Nearest start position is null!");
|
||||
results[i] = result.result;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private void saveMesh(NavMesh mesh, string filePostfix) {
|
||||
private void saveMesh(NavMesh mesh, string filePostfix)
|
||||
{
|
||||
// Set the flag to RecastDemo work properly
|
||||
for (int i = 0; i < mesh.getTileCount(); i++) {
|
||||
foreach (Poly p in mesh.getTile(i).data.polys) {
|
||||
for (int i = 0; i < mesh.getTileCount(); i++)
|
||||
{
|
||||
foreach (Poly p in mesh.getTile(i).data.polys)
|
||||
{
|
||||
p.flags = 1;
|
||||
}
|
||||
}
|
||||
|
@ -124,5 +135,4 @@ public class UnityAStarPathfindingImporterTest {
|
|||
using var os = new BinaryWriter(fs);
|
||||
writer.write(os, mesh, ByteOrder.LITTLE_ENDIAN, true);
|
||||
}
|
||||
|
||||
}
|
|
@ -20,10 +20,11 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Test;
|
||||
|
||||
public class ConvexConvexIntersectionTest {
|
||||
|
||||
public class ConvexConvexIntersectionTest
|
||||
{
|
||||
[Test]
|
||||
public void shouldHandleSamePolygonIntersection() {
|
||||
public void shouldHandleSamePolygonIntersection()
|
||||
{
|
||||
float[] p = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
|
||||
float[] q = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
|
||||
float[] intersection = ConvexConvexIntersection.intersect(p, q);
|
||||
|
@ -32,12 +33,12 @@ public class ConvexConvexIntersectionTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void shouldHandleIntersection() {
|
||||
public void shouldHandleIntersection()
|
||||
{
|
||||
float[] p = { -5, 0, -5, -5, 0, 4, 1, 0, 4, 1, 0, -5 };
|
||||
float[] q = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
|
||||
float[] intersection = ConvexConvexIntersection.intersect(p, q);
|
||||
Assert.That(intersection.Length, Is.EqualTo(5 * 3));
|
||||
Assert.That(intersection, Is.EqualTo(new[] { 1, 0, 3, 1, 0, -3.4f, -2, 0, -4, -4, 0, 0, -3, 0, 3 }));
|
||||
}
|
||||
|
||||
}
|
|
@ -20,29 +20,33 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Test;
|
||||
|
||||
|
||||
public class FindNearestPolyTest : AbstractDetourTest {
|
||||
|
||||
public class FindNearestPolyTest : AbstractDetourTest
|
||||
{
|
||||
private static readonly long[] POLY_REFS = { 281474976710696L, 281474976710773L, 281474976710680L, 281474976710753L, 281474976710733L };
|
||||
private static readonly float[][] POLY_POS = {
|
||||
|
||||
private static readonly float[][] POLY_POS =
|
||||
{
|
||||
new[] { 22.606520f, 10.197294f, -45.918674f }, new[] { 22.331268f, 10.197294f, -1.040187f },
|
||||
new[] { 18.694363f, 15.803535f, -73.090416f }, new[] { 0.745335f, 10.197294f, -5.940050f },
|
||||
new[] { -20.651257f, 5.904126f, -13.712508f } };
|
||||
new[] { -20.651257f, 5.904126f, -13.712508f }
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void testFindNearestPoly() {
|
||||
public void testFindNearestPoly()
|
||||
{
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
float[] extents = { 2, 4, 2 };
|
||||
for (int i = 0; i < startRefs.Length; i++) {
|
||||
for (int i = 0; i < startRefs.Length; i++)
|
||||
{
|
||||
float[] startPos = startPoss[i];
|
||||
Result<FindNearestPolyResult> poly = query.findNearestPoly(startPos, extents, filter);
|
||||
Assert.That(poly.succeeded(), Is.True);
|
||||
Assert.That(poly.result.getNearestRef(), Is.EqualTo(POLY_REFS[i]));
|
||||
for (int v = 0; v < POLY_POS[i].Length; v++) {
|
||||
for (int v = 0; v < POLY_POS[i].Length; v++)
|
||||
{
|
||||
Assert.That(poly.result.getNearestPos()[v], Is.EqualTo(POLY_POS[i][v]).Within(0.001f));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class EmptyQueryFilter : QueryFilter
|
||||
|
@ -60,18 +64,20 @@ public class FindNearestPolyTest : AbstractDetourTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void shouldReturnStartPosWhenNoPolyIsValid() {
|
||||
public void shouldReturnStartPosWhenNoPolyIsValid()
|
||||
{
|
||||
var filter = new EmptyQueryFilter();
|
||||
float[] extents = { 2, 4, 2 };
|
||||
for (int i = 0; i < startRefs.Length; i++) {
|
||||
for (int i = 0; i < startRefs.Length; i++)
|
||||
{
|
||||
float[] startPos = startPoss[i];
|
||||
Result<FindNearestPolyResult> poly = query.findNearestPoly(startPos, extents, filter);
|
||||
Assert.That(poly.succeeded(), Is.True);
|
||||
Assert.That(poly.result.getNearestRef(), Is.EqualTo(0L));
|
||||
for (int v = 0; v < POLY_POS[i].Length; v++) {
|
||||
for (int v = 0; v < POLY_POS[i].Length; v++)
|
||||
{
|
||||
Assert.That(poly.result.getNearestPos()[v], Is.EqualTo(startPos[v]).Within(0.001f));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -21,42 +21,65 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Test;
|
||||
|
||||
public class FindPathTest : AbstractDetourTest
|
||||
{
|
||||
private static readonly Status[] STATUSES =
|
||||
{
|
||||
Status.SUCCSESS, Status.PARTIAL_RESULT, Status.SUCCSESS, Status.SUCCSESS,
|
||||
Status.SUCCSESS
|
||||
};
|
||||
|
||||
public class FindPathTest : AbstractDetourTest {
|
||||
|
||||
private static readonly Status[] STATUSES = { Status.SUCCSESS, Status.PARTIAL_RESULT, Status.SUCCSESS, Status.SUCCSESS,
|
||||
Status.SUCCSESS };
|
||||
private static readonly long[][] RESULTS = {
|
||||
new[] { 281474976710696L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
|
||||
private static readonly long[][] RESULTS =
|
||||
{
|
||||
new[]
|
||||
{
|
||||
281474976710696L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
|
||||
281474976710705L, 281474976710702L, 281474976710701L, 281474976710714L, 281474976710713L,
|
||||
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710721L },
|
||||
new[] { 281474976710773L, 281474976710772L, 281474976710768L, 281474976710754L, 281474976710755L,
|
||||
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710721L
|
||||
},
|
||||
new[]
|
||||
{
|
||||
281474976710773L, 281474976710772L, 281474976710768L, 281474976710754L, 281474976710755L,
|
||||
281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L, 281474976710729L,
|
||||
281474976710717L, 281474976710724L, 281474976710728L, 281474976710737L, 281474976710738L,
|
||||
281474976710736L, 281474976710733L, 281474976710735L, 281474976710742L, 281474976710740L,
|
||||
281474976710746L, 281474976710745L, 281474976710744L },
|
||||
new[] { 281474976710680L, 281474976710684L, 281474976710688L, 281474976710687L, 281474976710686L,
|
||||
281474976710746L, 281474976710745L, 281474976710744L
|
||||
},
|
||||
new[]
|
||||
{
|
||||
281474976710680L, 281474976710684L, 281474976710688L, 281474976710687L, 281474976710686L,
|
||||
281474976710697L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
|
||||
281474976710705L, 281474976710702L, 281474976710701L, 281474976710714L, 281474976710713L,
|
||||
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710729L,
|
||||
281474976710731L, 281474976710752L, 281474976710748L, 281474976710753L, 281474976710755L,
|
||||
281474976710754L, 281474976710768L, 281474976710772L, 281474976710773L, 281474976710770L,
|
||||
281474976710757L, 281474976710761L, 281474976710758L },
|
||||
281474976710757L, 281474976710761L, 281474976710758L
|
||||
},
|
||||
new[] { 281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L },
|
||||
new[] { 281474976710733L, 281474976710736L, 281474976710738L, 281474976710737L, 281474976710728L,
|
||||
new[]
|
||||
{
|
||||
281474976710733L, 281474976710736L, 281474976710738L, 281474976710737L, 281474976710728L,
|
||||
281474976710724L, 281474976710717L, 281474976710729L, 281474976710731L, 281474976710752L,
|
||||
281474976710748L, 281474976710753L, 281474976710755L, 281474976710754L, 281474976710768L,
|
||||
281474976710772L } };
|
||||
281474976710772L
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly StraightPathItem[][] STRAIGHT_PATHS = {
|
||||
new[] { new StraightPathItem(new float[] { 22.606520f, 10.197294f, -45.918674f }, 1, 281474976710696L),
|
||||
private static readonly StraightPathItem[][] STRAIGHT_PATHS =
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new StraightPathItem(new float[] { 22.606520f, 10.197294f, -45.918674f }, 1, 281474976710696L),
|
||||
new StraightPathItem(new float[] { 3.484785f, 10.197294f, -34.241272f }, 0, 281474976710713L),
|
||||
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -31.241272f }, 0, 281474976710712L),
|
||||
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -29.741272f }, 0, 281474976710727L),
|
||||
new StraightPathItem(new float[] { 2.584784f, 10.197294f, -27.941273f }, 0, 281474976710730L),
|
||||
new StraightPathItem(new float[] { 6.457663f, 10.197294f, -18.334061f }, 2, 0L) },
|
||||
new StraightPathItem(new float[] { 6.457663f, 10.197294f, -18.334061f }, 2, 0L)
|
||||
},
|
||||
|
||||
new[] { new StraightPathItem(new float[] { 22.331268f, 10.197294f, -1.040187f }, 1, 281474976710773L),
|
||||
new[]
|
||||
{
|
||||
new StraightPathItem(new float[] { 22.331268f, 10.197294f, -1.040187f }, 1, 281474976710773L),
|
||||
new StraightPathItem(new float[] { 9.784786f, 10.197294f, -2.141273f }, 0, 281474976710755L),
|
||||
new StraightPathItem(new float[] { 7.984783f, 10.197294f, -2.441269f }, 0, 281474976710753L),
|
||||
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -8.441269f }, 0, 281474976710752L),
|
||||
|
@ -66,9 +89,12 @@ public class FindPathTest : AbstractDetourTest {
|
|||
new StraightPathItem(new float[] { -11.815216f, 9.997294f, -17.441269f }, 0, 281474976710736L),
|
||||
new StraightPathItem(new float[] { -17.815216f, 5.197294f, -11.441269f }, 0, 281474976710735L),
|
||||
new StraightPathItem(new float[] { -17.815216f, 5.197294f, -8.441269f }, 0, 281474976710746L),
|
||||
new StraightPathItem(new float[] { -11.815216f, 0.197294f, 3.008419f }, 2, 0L) },
|
||||
new StraightPathItem(new float[] { -11.815216f, 0.197294f, 3.008419f }, 2, 0L)
|
||||
},
|
||||
|
||||
new[] { new StraightPathItem(new float[] { 18.694363f, 15.803535f, -73.090416f }, 1, 281474976710680L),
|
||||
new[]
|
||||
{
|
||||
new StraightPathItem(new float[] { 18.694363f, 15.803535f, -73.090416f }, 1, 281474976710680L),
|
||||
new StraightPathItem(new float[] { 17.584785f, 10.197294f, -49.841274f }, 0, 281474976710697L),
|
||||
new StraightPathItem(new float[] { 17.284786f, 10.197294f, -48.041275f }, 0, 281474976710695L),
|
||||
new StraightPathItem(new float[] { 16.084785f, 10.197294f, -45.341274f }, 0, 281474976710694L),
|
||||
|
@ -77,24 +103,34 @@ public class FindPathTest : AbstractDetourTest {
|
|||
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -8.441269f }, 0, 281474976710753L),
|
||||
new StraightPathItem(new float[] { 7.984783f, 10.197294f, -2.441269f }, 0, 281474976710755L),
|
||||
new StraightPathItem(new float[] { 9.784786f, 10.197294f, -2.141273f }, 0, 281474976710768L),
|
||||
new StraightPathItem(new float[] { 38.423977f, 10.197294f, -0.116067f }, 2, 0L) },
|
||||
new StraightPathItem(new float[] { 38.423977f, 10.197294f, -0.116067f }, 2, 0L)
|
||||
},
|
||||
|
||||
new[] { new StraightPathItem(new float[] { 0.745335f, 10.197294f, -5.940050f }, 1, 281474976710753L),
|
||||
new StraightPathItem(new float[] { 0.863553f, 10.197294f, -10.310320f }, 2, 0L) },
|
||||
new[]
|
||||
{
|
||||
new StraightPathItem(new float[] { 0.745335f, 10.197294f, -5.940050f }, 1, 281474976710753L),
|
||||
new StraightPathItem(new float[] { 0.863553f, 10.197294f, -10.310320f }, 2, 0L)
|
||||
},
|
||||
|
||||
new[] { new StraightPathItem(new float[] { -20.651257f, 5.904126f, -13.712508f }, 1, 281474976710733L),
|
||||
new[]
|
||||
{
|
||||
new StraightPathItem(new float[] { -20.651257f, 5.904126f, -13.712508f }, 1, 281474976710733L),
|
||||
new StraightPathItem(new float[] { -11.815216f, 9.997294f, -17.441269f }, 0, 281474976710738L),
|
||||
new StraightPathItem(new float[] { -10.015216f, 10.197294f, -17.741272f }, 0, 281474976710728L),
|
||||
new StraightPathItem(new float[] { -8.215216f, 10.197294f, -17.441269f }, 0, 281474976710724L),
|
||||
new StraightPathItem(new float[] { -4.315216f, 10.197294f, -15.341270f }, 0, 281474976710729L),
|
||||
new StraightPathItem(new float[] { 1.984785f, 10.197294f, -8.441269f }, 0, 281474976710753L),
|
||||
new StraightPathItem(new float[] { 7.984783f, 10.197294f, -2.441269f }, 0, 281474976710755L),
|
||||
new StraightPathItem(new float[] { 18.784092f, 10.197294f, 3.054368f }, 2, 0L) } };
|
||||
new StraightPathItem(new float[] { 18.784092f, 10.197294f, 3.054368f }, 2, 0L)
|
||||
}
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void testFindPath() {
|
||||
public void testFindPath()
|
||||
{
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
for (int i = 0; i < startRefs.Length; i++) {
|
||||
for (int i = 0; i < startRefs.Length; i++)
|
||||
{
|
||||
long startRef = startRefs[i];
|
||||
long endRef = endRefs[i];
|
||||
float[] startPos = startPoss[i];
|
||||
|
@ -102,40 +138,48 @@ public class FindPathTest : AbstractDetourTest {
|
|||
Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter);
|
||||
Assert.That(path.status, Is.EqualTo(STATUSES[i]));
|
||||
Assert.That(path.result.Count, Is.EqualTo(RESULTS[i].Length));
|
||||
for (int j = 0; j < RESULTS[i].Length; j++) {
|
||||
for (int j = 0; j < RESULTS[i].Length; j++)
|
||||
{
|
||||
Assert.That(path.result[j], Is.EqualTo(RESULTS[i][j]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void testFindPathSliced() {
|
||||
public void testFindPathSliced()
|
||||
{
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
for (int i = 0; i < startRefs.Length; i++) {
|
||||
for (int i = 0; i < startRefs.Length; i++)
|
||||
{
|
||||
long startRef = startRefs[i];
|
||||
long endRef = endRefs[i];
|
||||
float[] startPos = startPoss[i];
|
||||
float[] endPos = endPoss[i];
|
||||
query.initSlicedFindPath(startRef, endRef, startPos, endPos, filter, NavMeshQuery.DT_FINDPATH_ANY_ANGLE);
|
||||
Status status = Status.IN_PROGRESS;
|
||||
while (status == Status.IN_PROGRESS) {
|
||||
while (status == Status.IN_PROGRESS)
|
||||
{
|
||||
Result<int> res = query.updateSlicedFindPath(10);
|
||||
status = res.status;
|
||||
}
|
||||
|
||||
Result<List<long>> path = query.finalizeSlicedFindPath();
|
||||
Assert.That(path.status, Is.EqualTo(STATUSES[i]));
|
||||
Assert.That(path.result.Count, Is.EqualTo(RESULTS[i].Length));
|
||||
for (int j = 0; j < RESULTS[i].Length; j++) {
|
||||
for (int j = 0; j < RESULTS[i].Length; j++)
|
||||
{
|
||||
Assert.That(path.result[j], Is.EqualTo(RESULTS[i][j]));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void testFindPathStraight() {
|
||||
public void testFindPathStraight()
|
||||
{
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
for (int i = 0; i < STRAIGHT_PATHS.Length; i++) {// startRefs.Length; i++) {
|
||||
for (int i = 0; i < STRAIGHT_PATHS.Length; i++)
|
||||
{
|
||||
// startRefs.Length; i++) {
|
||||
long startRef = startRefs[i];
|
||||
long endRef = endRefs[i];
|
||||
float[] startPos = startPoss[i];
|
||||
|
@ -145,14 +189,16 @@ public class FindPathTest : AbstractDetourTest {
|
|||
int.MaxValue, 0);
|
||||
List<StraightPathItem> straightPath = result.result;
|
||||
Assert.That(straightPath.Count, Is.EqualTo(STRAIGHT_PATHS[i].Length));
|
||||
for (int j = 0; j < STRAIGHT_PATHS[i].Length; j++) {
|
||||
for (int j = 0; j < STRAIGHT_PATHS[i].Length; j++)
|
||||
{
|
||||
Assert.That(straightPath[j].refs, Is.EqualTo(STRAIGHT_PATHS[i][j].refs));
|
||||
for (int v = 0; v < 3; v++) {
|
||||
for (int v = 0; v < 3; v++)
|
||||
{
|
||||
Assert.That(straightPath[j].pos[v], Is.EqualTo(STRAIGHT_PATHS[i][j].pos[v]).Within(0.01f));
|
||||
}
|
||||
|
||||
Assert.That(straightPath[j].flags, Is.EqualTo(STRAIGHT_PATHS[i][j].flags));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -20,64 +20,108 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Test;
|
||||
|
||||
|
||||
public class FindPolysAroundCircleTest : AbstractDetourTest {
|
||||
|
||||
private static readonly long[][] REFS = {
|
||||
new[] { 281474976710696L, 281474976710695L, 281474976710694L, 281474976710691L, 281474976710697L, 281474976710693L,
|
||||
281474976710686L, 281474976710687L, 281474976710692L, 281474976710703L, 281474976710689L },
|
||||
public class FindPolysAroundCircleTest : AbstractDetourTest
|
||||
{
|
||||
private static readonly long[][] REFS =
|
||||
{
|
||||
new[]
|
||||
{
|
||||
281474976710696L, 281474976710695L, 281474976710694L, 281474976710691L, 281474976710697L, 281474976710693L,
|
||||
281474976710686L, 281474976710687L, 281474976710692L, 281474976710703L, 281474976710689L
|
||||
},
|
||||
new[] { 281474976710773L, 281474976710770L, 281474976710769L, 281474976710772L, 281474976710771L },
|
||||
new[] { 281474976710680L, 281474976710674L, 281474976710679L, 281474976710684L, 281474976710683L, 281474976710678L,
|
||||
new[]
|
||||
{
|
||||
281474976710680L, 281474976710674L, 281474976710679L, 281474976710684L, 281474976710683L, 281474976710678L,
|
||||
281474976710682L, 281474976710677L, 281474976710676L, 281474976710688L, 281474976710687L, 281474976710675L,
|
||||
281474976710685L, 281474976710672L, 281474976710666L, 281474976710668L, 281474976710681L, 281474976710673L },
|
||||
new[] { 281474976710753L, 281474976710748L, 281474976710755L, 281474976710756L, 281474976710750L, 281474976710752L,
|
||||
281474976710731L, 281474976710729L, 281474976710749L, 281474976710719L, 281474976710717L, 281474976710726L },
|
||||
new[] { 281474976710733L, 281474976710735L, 281474976710736L, 281474976710734L, 281474976710739L, 281474976710742L,
|
||||
281474976710740L, 281474976710746L, 281474976710747L, } };
|
||||
private static readonly long[][] PARENT_REFS = {
|
||||
new[] { 0L, 281474976710696L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710697L,
|
||||
281474976710686L, 281474976710693L, 281474976710694L, 281474976710687L },
|
||||
281474976710685L, 281474976710672L, 281474976710666L, 281474976710668L, 281474976710681L, 281474976710673L
|
||||
},
|
||||
new[]
|
||||
{
|
||||
281474976710753L, 281474976710748L, 281474976710755L, 281474976710756L, 281474976710750L, 281474976710752L,
|
||||
281474976710731L, 281474976710729L, 281474976710749L, 281474976710719L, 281474976710717L, 281474976710726L
|
||||
},
|
||||
new[]
|
||||
{
|
||||
281474976710733L, 281474976710735L, 281474976710736L, 281474976710734L, 281474976710739L, 281474976710742L,
|
||||
281474976710740L, 281474976710746L, 281474976710747L,
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly long[][] PARENT_REFS =
|
||||
{
|
||||
new[]
|
||||
{
|
||||
0L, 281474976710696L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710697L,
|
||||
281474976710686L, 281474976710693L, 281474976710694L, 281474976710687L
|
||||
},
|
||||
new[] { 0L, 281474976710773L, 281474976710773L, 281474976710773L, 281474976710772L },
|
||||
new[] { 0L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710679L, 281474976710683L,
|
||||
new[]
|
||||
{
|
||||
0L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710679L, 281474976710683L,
|
||||
281474976710683L, 281474976710678L, 281474976710684L, 281474976710688L, 281474976710677L, 281474976710687L,
|
||||
281474976710682L, 281474976710672L, 281474976710672L, 281474976710675L, 281474976710666L },
|
||||
new[] { 0L, 281474976710753L, 281474976710753L, 281474976710753L, 281474976710753L, 281474976710748L, 281474976710752L,
|
||||
281474976710731L, 281474976710756L, 281474976710729L, 281474976710729L, 281474976710717L },
|
||||
new[] { 0L, 281474976710733L, 281474976710733L, 281474976710736L, 281474976710736L, 281474976710735L, 281474976710742L,
|
||||
281474976710740L, 281474976710746L } };
|
||||
private static readonly float[][] COSTS = {
|
||||
new[] { 0.000000f, 0.391453f, 6.764245f, 4.153431f, 3.721995f, 6.109188f, 5.378797f, 7.178796f, 7.009186f, 7.514245f,
|
||||
12.655564f },
|
||||
281474976710682L, 281474976710672L, 281474976710672L, 281474976710675L, 281474976710666L
|
||||
},
|
||||
new[]
|
||||
{
|
||||
0L, 281474976710753L, 281474976710753L, 281474976710753L, 281474976710753L, 281474976710748L, 281474976710752L,
|
||||
281474976710731L, 281474976710756L, 281474976710729L, 281474976710729L, 281474976710717L
|
||||
},
|
||||
new[]
|
||||
{
|
||||
0L, 281474976710733L, 281474976710733L, 281474976710736L, 281474976710736L, 281474976710735L, 281474976710742L,
|
||||
281474976710740L, 281474976710746L
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly float[][] COSTS =
|
||||
{
|
||||
new[]
|
||||
{
|
||||
0.000000f, 0.391453f, 6.764245f, 4.153431f, 3.721995f, 6.109188f, 5.378797f, 7.178796f, 7.009186f, 7.514245f,
|
||||
12.655564f
|
||||
},
|
||||
new[] { 0.000000f, 6.161580f, 2.824478f, 2.828730f, 8.035697f },
|
||||
new[] { 0.000000f, 1.162604f, 1.954029f, 2.776051f, 2.046001f, 2.428367f, 6.429493f, 6.032851f, 2.878368f, 5.333885f,
|
||||
6.394545f, 9.596563f, 12.457960f, 7.096575f, 10.413582f, 10.362305f, 10.665442f, 10.593861f },
|
||||
new[] { 0.000000f, 2.483205f, 6.723722f, 5.727250f, 3.126022f, 3.543865f, 5.043865f, 6.843868f, 7.212173f, 10.602858f,
|
||||
8.793867f, 13.146453f },
|
||||
new[] { 0.000000f, 2.480514f, 0.823685f, 5.002500f, 8.229258f, 3.983844f, 5.483844f, 6.655379f, 11.996962f } };
|
||||
new[]
|
||||
{
|
||||
0.000000f, 1.162604f, 1.954029f, 2.776051f, 2.046001f, 2.428367f, 6.429493f, 6.032851f, 2.878368f, 5.333885f,
|
||||
6.394545f, 9.596563f, 12.457960f, 7.096575f, 10.413582f, 10.362305f, 10.665442f, 10.593861f
|
||||
},
|
||||
new[]
|
||||
{
|
||||
0.000000f, 2.483205f, 6.723722f, 5.727250f, 3.126022f, 3.543865f, 5.043865f, 6.843868f, 7.212173f, 10.602858f,
|
||||
8.793867f, 13.146453f
|
||||
},
|
||||
new[] { 0.000000f, 2.480514f, 0.823685f, 5.002500f, 8.229258f, 3.983844f, 5.483844f, 6.655379f, 11.996962f }
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void testFindPolysAroundCircle() {
|
||||
public void testFindPolysAroundCircle()
|
||||
{
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
for (int i = 0; i < startRefs.Length; i++) {
|
||||
for (int i = 0; i < startRefs.Length; i++)
|
||||
{
|
||||
long startRef = startRefs[i];
|
||||
float[] startPos = startPoss[i];
|
||||
Result<FindPolysAroundResult> result = query.findPolysAroundCircle(startRef, startPos, 7.5f, filter);
|
||||
Assert.That(result.succeeded(), Is.True);
|
||||
FindPolysAroundResult polys = result.result;
|
||||
Assert.That(polys.getRefs().Count, Is.EqualTo(REFS[i].Length));
|
||||
for (int v = 0; v < REFS[i].Length; v++) {
|
||||
for (int v = 0; v < REFS[i].Length; v++)
|
||||
{
|
||||
bool found = false;
|
||||
for (int w = 0; w < REFS[i].Length; w++) {
|
||||
if (REFS[i][v] == polys.getRefs()[w]) {
|
||||
for (int w = 0; w < REFS[i].Length; w++)
|
||||
{
|
||||
if (REFS[i][v] == polys.getRefs()[w])
|
||||
{
|
||||
Assert.That(polys.getParentRefs()[w], Is.EqualTo(PARENT_REFS[i][v]));
|
||||
Assert.That(polys.getCosts()[w], Is.EqualTo(COSTS[i][v]).Within(0.01f));
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.That(found, Is.True, $"Ref not found {REFS[i][v]}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -20,91 +20,139 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Test;
|
||||
|
||||
|
||||
public class FindPolysAroundShapeTest : AbstractDetourTest {
|
||||
|
||||
private static readonly long[][] REFS = {
|
||||
new[] { 281474976710696L, 281474976710695L, 281474976710694L, 281474976710691L, 281474976710697L,
|
||||
public class FindPolysAroundShapeTest : AbstractDetourTest
|
||||
{
|
||||
private static readonly long[][] REFS =
|
||||
{
|
||||
new[]
|
||||
{
|
||||
281474976710696L, 281474976710695L, 281474976710694L, 281474976710691L, 281474976710697L,
|
||||
281474976710693L, 281474976710692L, 281474976710703L, 281474976710706L, 281474976710699L,
|
||||
281474976710705L, 281474976710698L, 281474976710700L, 281474976710704L },
|
||||
new[] { 281474976710773L, 281474976710769L, 281474976710772L, 281474976710768L, 281474976710771L,
|
||||
281474976710705L, 281474976710698L, 281474976710700L, 281474976710704L
|
||||
},
|
||||
new[]
|
||||
{
|
||||
281474976710773L, 281474976710769L, 281474976710772L, 281474976710768L, 281474976710771L,
|
||||
281474976710754L, 281474976710755L, 281474976710753L, 281474976710751L, 281474976710756L,
|
||||
281474976710749L },
|
||||
new[] { 281474976710680L, 281474976710679L, 281474976710684L, 281474976710683L, 281474976710688L,
|
||||
281474976710749L
|
||||
},
|
||||
new[]
|
||||
{
|
||||
281474976710680L, 281474976710679L, 281474976710684L, 281474976710683L, 281474976710688L,
|
||||
281474976710678L, 281474976710676L, 281474976710687L, 281474976710690L, 281474976710686L,
|
||||
281474976710689L, 281474976710685L, 281474976710697L, 281474976710695L, 281474976710694L,
|
||||
281474976710691L, 281474976710696L, 281474976710693L, 281474976710692L, 281474976710703L,
|
||||
281474976710706L, 281474976710699L, 281474976710705L, 281474976710700L, 281474976710704L },
|
||||
281474976710706L, 281474976710699L, 281474976710705L, 281474976710700L, 281474976710704L
|
||||
},
|
||||
new[] { 281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L },
|
||||
new[] { 281474976710733L, 281474976710735L, 281474976710736L, 281474976710742L, 281474976710734L,
|
||||
new[]
|
||||
{
|
||||
281474976710733L, 281474976710735L, 281474976710736L, 281474976710742L, 281474976710734L,
|
||||
281474976710739L, 281474976710738L, 281474976710740L, 281474976710746L, 281474976710743L,
|
||||
281474976710745L, 281474976710741L, 281474976710747L, 281474976710737L, 281474976710732L,
|
||||
281474976710728L, 281474976710724L, 281474976710744L, 281474976710725L, 281474976710717L,
|
||||
281474976710729L, 281474976710726L, 281474976710721L, 281474976710719L, 281474976710731L,
|
||||
281474976710720L, 281474976710752L, 281474976710748L, 281474976710753L, 281474976710755L,
|
||||
281474976710756L, 281474976710750L, 281474976710749L, 281474976710754L, 281474976710751L,
|
||||
281474976710768L, 281474976710772L, 281474976710773L, 281474976710771L, 281474976710769L } };
|
||||
private static readonly long[][] PARENT_REFS = {
|
||||
new[] { 0L, 281474976710696L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710695L,
|
||||
281474976710768L, 281474976710772L, 281474976710773L, 281474976710771L, 281474976710769L
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly long[][] PARENT_REFS =
|
||||
{
|
||||
new[]
|
||||
{
|
||||
0L, 281474976710696L, 281474976710695L, 281474976710695L, 281474976710695L, 281474976710695L,
|
||||
281474976710693L, 281474976710694L, 281474976710703L, 281474976710706L, 281474976710706L,
|
||||
281474976710705L, 281474976710705L, 281474976710705L },
|
||||
new[] { 0L, 281474976710773L, 281474976710773L, 281474976710772L, 281474976710772L, 281474976710768L,
|
||||
281474976710754L, 281474976710755L, 281474976710755L, 281474976710753L, 281474976710756L },
|
||||
new[] { 0L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710684L, 281474976710679L,
|
||||
281474976710705L, 281474976710705L, 281474976710705L
|
||||
},
|
||||
new[]
|
||||
{
|
||||
0L, 281474976710773L, 281474976710773L, 281474976710772L, 281474976710772L, 281474976710768L,
|
||||
281474976710754L, 281474976710755L, 281474976710755L, 281474976710753L, 281474976710756L
|
||||
},
|
||||
new[]
|
||||
{
|
||||
0L, 281474976710680L, 281474976710680L, 281474976710680L, 281474976710684L, 281474976710679L,
|
||||
281474976710678L, 281474976710688L, 281474976710687L, 281474976710687L, 281474976710687L,
|
||||
281474976710687L, 281474976710686L, 281474976710697L, 281474976710695L, 281474976710695L,
|
||||
281474976710695L, 281474976710695L, 281474976710693L, 281474976710694L, 281474976710703L,
|
||||
281474976710706L, 281474976710706L, 281474976710705L, 281474976710705L },
|
||||
281474976710706L, 281474976710706L, 281474976710705L, 281474976710705L
|
||||
},
|
||||
new[] { 0L, 281474976710753L, 281474976710748L, 281474976710752L },
|
||||
new[] { 0L, 281474976710733L, 281474976710733L, 281474976710735L, 281474976710736L, 281474976710736L,
|
||||
new[]
|
||||
{
|
||||
0L, 281474976710733L, 281474976710733L, 281474976710735L, 281474976710736L, 281474976710736L,
|
||||
281474976710736L, 281474976710742L, 281474976710740L, 281474976710746L, 281474976710746L,
|
||||
281474976710746L, 281474976710746L, 281474976710738L, 281474976710738L, 281474976710737L,
|
||||
281474976710728L, 281474976710745L, 281474976710724L, 281474976710724L, 281474976710717L,
|
||||
281474976710717L, 281474976710717L, 281474976710729L, 281474976710729L, 281474976710721L,
|
||||
281474976710731L, 281474976710752L, 281474976710748L, 281474976710753L, 281474976710753L,
|
||||
281474976710753L, 281474976710756L, 281474976710755L, 281474976710755L, 281474976710754L,
|
||||
281474976710768L, 281474976710772L, 281474976710772L, 281474976710773L } };
|
||||
private static readonly float[][] COSTS = {
|
||||
new[] { 0.000000f, 16.188787f, 22.561579f, 19.950766f, 19.519329f, 21.906523f, 22.806520f, 23.311579f, 25.124035f,
|
||||
28.454576f, 26.084503f, 36.438854f, 30.526634f, 31.942192f },
|
||||
new[] { 0.000000f, 16.618738f, 12.136283f, 20.387646f, 17.343250f, 22.037645f, 22.787645f, 27.178831f, 26.501472f,
|
||||
31.691311f, 33.176235f },
|
||||
new[] { 0.000000f, 36.657764f, 35.197689f, 37.484924f, 37.755524f, 37.132103f, 37.582104f, 38.816185f, 52.426109f,
|
||||
281474976710768L, 281474976710772L, 281474976710772L, 281474976710773L
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly float[][] COSTS =
|
||||
{
|
||||
new[]
|
||||
{
|
||||
0.000000f, 16.188787f, 22.561579f, 19.950766f, 19.519329f, 21.906523f, 22.806520f, 23.311579f, 25.124035f,
|
||||
28.454576f, 26.084503f, 36.438854f, 30.526634f, 31.942192f
|
||||
},
|
||||
new[]
|
||||
{
|
||||
0.000000f, 16.618738f, 12.136283f, 20.387646f, 17.343250f, 22.037645f, 22.787645f, 27.178831f, 26.501472f,
|
||||
31.691311f, 33.176235f
|
||||
},
|
||||
new[]
|
||||
{
|
||||
0.000000f, 36.657764f, 35.197689f, 37.484924f, 37.755524f, 37.132103f, 37.582104f, 38.816185f, 52.426109f,
|
||||
55.945839f, 51.882935f, 44.879601f, 57.745838f, 59.402641f, 65.063034f, 64.934372f, 62.733185f,
|
||||
62.756744f, 63.656742f, 65.813034f, 67.625488f, 70.956032f, 68.585960f, 73.028091f, 74.443649f },
|
||||
62.756744f, 63.656742f, 65.813034f, 67.625488f, 70.956032f, 68.585960f, 73.028091f, 74.443649f
|
||||
},
|
||||
new[] { 0.000000f, 2.097958f, 3.158618f, 4.658618f },
|
||||
new[] { 0.000000f, 20.495766f, 21.352942f, 21.999096f, 25.531757f, 28.758514f, 30.264732f, 23.499096f, 24.670631f,
|
||||
new[]
|
||||
{
|
||||
0.000000f, 20.495766f, 21.352942f, 21.999096f, 25.531757f, 28.758514f, 30.264732f, 23.499096f, 24.670631f,
|
||||
33.166218f, 35.651184f, 34.371792f, 30.012215f, 33.886887f, 33.855347f, 34.643524f, 36.300327f,
|
||||
38.203144f, 40.339203f, 40.203213f, 47.254810f, 50.043945f, 49.054485f, 49.804810f, 49.204811f,
|
||||
52.813477f, 51.004814f, 52.504814f, 53.565475f, 62.748611f, 61.504147f, 57.915474f, 62.989071f,
|
||||
67.139801f, 66.507599f, 67.889801f, 69.539803f, 77.791168f, 75.186256f, 83.111412f } };
|
||||
67.139801f, 66.507599f, 67.889801f, 69.539803f, 77.791168f, 75.186256f, 83.111412f
|
||||
}
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void testFindPolysAroundShape() {
|
||||
public void testFindPolysAroundShape()
|
||||
{
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
for (int i = 0; i < startRefs.Length; i++) {
|
||||
for (int i = 0; i < startRefs.Length; i++)
|
||||
{
|
||||
long startRef = startRefs[i];
|
||||
float[] startPos = startPoss[i];
|
||||
Result<FindPolysAroundResult> polys = query.findPolysAroundShape(startRef,
|
||||
getQueryPoly(startPos, endPoss[i]), filter);
|
||||
Assert.That(polys.result.getRefs().Count, Is.EqualTo(REFS[i].Length));
|
||||
for (int v = 0; v < REFS[i].Length; v++) {
|
||||
for (int v = 0; v < REFS[i].Length; v++)
|
||||
{
|
||||
bool found = false;
|
||||
for (int w = 0; w < REFS[i].Length; w++) {
|
||||
if (REFS[i][v] == polys.result.getRefs()[w]) {
|
||||
for (int w = 0; w < REFS[i].Length; w++)
|
||||
{
|
||||
if (REFS[i][v] == polys.result.getRefs()[w])
|
||||
{
|
||||
Assert.That(polys.result.getParentRefs()[w], Is.EqualTo(PARENT_REFS[i][v]));
|
||||
Assert.That(polys.result.getCosts()[w], Is.EqualTo(COSTS[i][v]).Within(0.01f));
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.That(found, Is.True);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private float[] getQueryPoly(float[] m_spos, float[] m_epos) {
|
||||
|
||||
private float[] getQueryPoly(float[] m_spos, float[] m_epos)
|
||||
{
|
||||
float nx = (m_epos[2] - m_spos[2]) * 0.25f;
|
||||
float nz = -(m_epos[0] - m_spos[0]) * 0.25f;
|
||||
float agentHeight = 2.0f;
|
||||
|
@ -127,5 +175,4 @@ public class FindPolysAroundShapeTest : AbstractDetourTest {
|
|||
m_queryPoly[11] = m_epos[2] + nz;
|
||||
return m_queryPoly;
|
||||
}
|
||||
|
||||
}
|
|
@ -20,56 +20,80 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Test;
|
||||
|
||||
|
||||
public class GetPolyWallSegmentsTest : AbstractDetourTest {
|
||||
|
||||
private static readonly float[][] VERTICES = {
|
||||
new[] { 22.084785f, 10.197294f, -48.341274f, 22.684784f, 10.197294f, -44.141273f, 22.684784f, 10.197294f,
|
||||
public class GetPolyWallSegmentsTest : AbstractDetourTest
|
||||
{
|
||||
private static readonly float[][] VERTICES =
|
||||
{
|
||||
new[]
|
||||
{
|
||||
22.084785f, 10.197294f, -48.341274f, 22.684784f, 10.197294f, -44.141273f, 22.684784f, 10.197294f,
|
||||
-44.141273f, 23.884785f, 10.197294f, -48.041275f, 23.884785f, 10.197294f, -48.041275f, 22.084785f,
|
||||
10.197294f, -48.341274f },
|
||||
new[] { 27.784786f, 10.197294f, 4.158730f, 28.384785f, 10.197294f, 2.358727f, 28.384785f, 10.197294f, 2.358727f,
|
||||
10.197294f, -48.341274f
|
||||
},
|
||||
new[]
|
||||
{
|
||||
27.784786f, 10.197294f, 4.158730f, 28.384785f, 10.197294f, 2.358727f, 28.384785f, 10.197294f, 2.358727f,
|
||||
28.384785f, 10.197294f, -2.141273f, 28.384785f, 10.197294f, -2.141273f, 27.784786f, 10.197294f,
|
||||
-2.741272f, 27.784786f, 10.197294f, -2.741272f, 19.684784f, 10.197294f, -4.241272f, 19.684784f,
|
||||
10.197294f, -4.241272f, 19.684784f, 10.197294f, 4.158730f, 19.684784f, 10.197294f, 4.158730f,
|
||||
27.784786f, 10.197294f, 4.158730f },
|
||||
new[] { 22.384785f, 14.997294f, -71.741272f, 19.084785f, 16.597294f, -74.741272f, 19.084785f, 16.597294f,
|
||||
27.784786f, 10.197294f, 4.158730f
|
||||
},
|
||||
new[]
|
||||
{
|
||||
22.384785f, 14.997294f, -71.741272f, 19.084785f, 16.597294f, -74.741272f, 19.084785f, 16.597294f,
|
||||
-74.741272f, 18.184784f, 15.997294f, -73.541275f, 18.184784f, 15.997294f, -73.541275f, 17.884785f,
|
||||
14.997294f, -72.341278f, 17.884785f, 14.997294f, -72.341278f, 17.584785f, 14.997294f, -70.841278f,
|
||||
17.584785f, 14.997294f, -70.841278f, 22.084785f, 14.997294f, -70.541275f, 22.084785f, 14.997294f,
|
||||
-70.541275f, 22.384785f, 14.997294f, -71.741272f },
|
||||
new[] { 4.684784f, 10.197294f, -6.941269f, 1.984785f, 10.197294f, -8.441269f, 1.984785f, 10.197294f, -8.441269f,
|
||||
-70.541275f, 22.384785f, 14.997294f, -71.741272f
|
||||
},
|
||||
new[]
|
||||
{
|
||||
4.684784f, 10.197294f, -6.941269f, 1.984785f, 10.197294f, -8.441269f, 1.984785f, 10.197294f, -8.441269f,
|
||||
-4.015217f, 10.197294f, -6.941269f, -4.015217f, 10.197294f, -6.941269f, -1.615215f, 10.197294f,
|
||||
-1.541275f, -1.615215f, 10.197294f, -1.541275f, 1.384785f, 10.197294f, 1.458725f, 1.384785f,
|
||||
10.197294f, 1.458725f, 7.984783f, 10.197294f, -2.441269f, 7.984783f, 10.197294f, -2.441269f,
|
||||
4.684784f, 10.197294f, -6.941269f },
|
||||
new[] { -22.315216f, 6.597294f, -17.141273f, -23.815216f, 5.397294f, -13.841270f, -23.815216f, 5.397294f,
|
||||
4.684784f, 10.197294f, -6.941269f
|
||||
},
|
||||
new[]
|
||||
{
|
||||
-22.315216f, 6.597294f, -17.141273f, -23.815216f, 5.397294f, -13.841270f, -23.815216f, 5.397294f,
|
||||
-13.841270f, -24.115217f, 4.997294f, -12.041275f, -24.115217f, 4.997294f, -12.041275f, -22.315216f,
|
||||
4.997294f, -11.441269f, -22.315216f, 4.997294f, -11.441269f, -17.815216f, 5.197294f, -11.441269f,
|
||||
-17.815216f, 5.197294f, -11.441269f, -22.315216f, 6.597294f, -17.141273f } };
|
||||
private static readonly long[][] REFS = {
|
||||
-17.815216f, 5.197294f, -11.441269f, -22.315216f, 6.597294f, -17.141273f
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly long[][] REFS =
|
||||
{
|
||||
new[] { 281474976710695L, 0L, 0L },
|
||||
new[] { 0L, 281474976710770L, 0L, 281474976710769L, 281474976710772L, 0L },
|
||||
new[] { 281474976710683L, 281474976710674L, 0L, 281474976710679L, 281474976710684L, 0L },
|
||||
new[] { 281474976710750L, 281474976710748L, 0L, 0L, 281474976710755L, 281474976710756L },
|
||||
new[] { 0L, 0L, 0L, 281474976710735L, 281474976710736L } };
|
||||
new[] { 0L, 0L, 0L, 281474976710735L, 281474976710736L }
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void testFindDistanceToWall() {
|
||||
public void testFindDistanceToWall()
|
||||
{
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
for (int i = 0; i < startRefs.Length; i++) {
|
||||
for (int i = 0; i < startRefs.Length; i++)
|
||||
{
|
||||
Result<GetPolyWallSegmentsResult> result = query.getPolyWallSegments(startRefs[i], true, filter);
|
||||
GetPolyWallSegmentsResult segments = result.result;
|
||||
Assert.That(segments.getSegmentVerts().Count, Is.EqualTo(VERTICES[i].Length / 6));
|
||||
Assert.That(segments.getSegmentRefs().Count, Is.EqualTo(REFS[i].Length));
|
||||
for (int v = 0; v < VERTICES[i].Length / 6; v++) {
|
||||
for (int n = 0; n < 6; n++) {
|
||||
for (int v = 0; v < VERTICES[i].Length / 6; v++)
|
||||
{
|
||||
for (int n = 0; n < 6; n++)
|
||||
{
|
||||
Assert.That(segments.getSegmentVerts()[v][n], Is.EqualTo(VERTICES[i][v * 6 + n]).Within(0.001f));
|
||||
}
|
||||
}
|
||||
for (int v = 0; v < REFS[i].Length; v++) {
|
||||
|
||||
for (int v = 0; v < REFS[i].Length; v++)
|
||||
{
|
||||
Assert.That(segments.getSegmentRefs()[v], Is.EqualTo(REFS[i][v]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -23,39 +23,44 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Test.Io;
|
||||
|
||||
|
||||
public class MeshDataReaderWriterTest {
|
||||
|
||||
public class MeshDataReaderWriterTest
|
||||
{
|
||||
private const int VERTS_PER_POLYGON = 6;
|
||||
private MeshData meshData;
|
||||
|
||||
[SetUp]
|
||||
public void setUp() {
|
||||
public void setUp()
|
||||
{
|
||||
RecastTestMeshBuilder rcBuilder = new RecastTestMeshBuilder();
|
||||
meshData = rcBuilder.getMeshData();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void testCCompatibility() {
|
||||
public void testCCompatibility()
|
||||
{
|
||||
test(true, ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void testCompact() {
|
||||
public void testCompact()
|
||||
{
|
||||
test(false, ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void testCCompatibilityLE() {
|
||||
public void testCCompatibilityLE()
|
||||
{
|
||||
test(true, ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void testCompactLE() {
|
||||
public void testCompactLE()
|
||||
{
|
||||
test(false, ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
public void test(bool cCompatibility, ByteOrder order) {
|
||||
public void test(bool cCompatibility, ByteOrder order)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
using var bwos = new BinaryWriter(ms);
|
||||
|
||||
|
@ -74,43 +79,59 @@ public class MeshDataReaderWriterTest {
|
|||
Assert.That(readData.header.detailVertCount, Is.EqualTo(meshData.header.detailVertCount));
|
||||
Assert.That(readData.header.bvNodeCount, Is.EqualTo(meshData.header.bvNodeCount));
|
||||
Assert.That(readData.header.offMeshConCount, Is.EqualTo(meshData.header.offMeshConCount));
|
||||
for (int i = 0; i < meshData.header.vertCount; i++) {
|
||||
for (int i = 0; i < meshData.header.vertCount; i++)
|
||||
{
|
||||
Assert.That(readData.verts[i], Is.EqualTo(meshData.verts[i]));
|
||||
}
|
||||
for (int i = 0; i < meshData.header.polyCount; i++) {
|
||||
|
||||
for (int i = 0; i < meshData.header.polyCount; i++)
|
||||
{
|
||||
Assert.That(readData.polys[i].vertCount, Is.EqualTo(meshData.polys[i].vertCount));
|
||||
Assert.That(readData.polys[i].areaAndtype, Is.EqualTo(meshData.polys[i].areaAndtype));
|
||||
for (int j = 0; j < meshData.polys[i].vertCount; j++) {
|
||||
for (int j = 0; j < meshData.polys[i].vertCount; j++)
|
||||
{
|
||||
Assert.That(readData.polys[i].verts[j], Is.EqualTo(meshData.polys[i].verts[j]));
|
||||
Assert.That(readData.polys[i].neis[j], Is.EqualTo(meshData.polys[i].neis[j]));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < meshData.header.detailMeshCount; i++) {
|
||||
|
||||
for (int i = 0; i < meshData.header.detailMeshCount; i++)
|
||||
{
|
||||
Assert.That(readData.detailMeshes[i].vertBase, Is.EqualTo(meshData.detailMeshes[i].vertBase));
|
||||
Assert.That(readData.detailMeshes[i].vertCount, Is.EqualTo(meshData.detailMeshes[i].vertCount));
|
||||
Assert.That(readData.detailMeshes[i].triBase, Is.EqualTo(meshData.detailMeshes[i].triBase));
|
||||
Assert.That(readData.detailMeshes[i].triCount, Is.EqualTo(meshData.detailMeshes[i].triCount));
|
||||
}
|
||||
for (int i = 0; i < meshData.header.detailVertCount; i++) {
|
||||
|
||||
for (int i = 0; i < meshData.header.detailVertCount; i++)
|
||||
{
|
||||
Assert.That(readData.detailVerts[i], Is.EqualTo(meshData.detailVerts[i]));
|
||||
}
|
||||
for (int i = 0; i < meshData.header.detailTriCount; i++) {
|
||||
|
||||
for (int i = 0; i < meshData.header.detailTriCount; i++)
|
||||
{
|
||||
Assert.That(readData.detailTris[i], Is.EqualTo(meshData.detailTris[i]));
|
||||
}
|
||||
for (int i = 0; i < meshData.header.bvNodeCount; i++) {
|
||||
|
||||
for (int i = 0; i < meshData.header.bvNodeCount; i++)
|
||||
{
|
||||
Assert.That(readData.bvTree[i].i, Is.EqualTo(meshData.bvTree[i].i));
|
||||
for (int j = 0; j < 3; j++) {
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
Assert.That(readData.bvTree[i].bmin[j], Is.EqualTo(meshData.bvTree[i].bmin[j]));
|
||||
Assert.That(readData.bvTree[i].bmax[j], Is.EqualTo(meshData.bvTree[i].bmax[j]));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < meshData.header.offMeshConCount; i++) {
|
||||
|
||||
for (int i = 0; i < meshData.header.offMeshConCount; i++)
|
||||
{
|
||||
Assert.That(readData.offMeshCons[i].flags, Is.EqualTo(meshData.offMeshCons[i].flags));
|
||||
Assert.That(readData.offMeshCons[i].rad, Is.EqualTo(meshData.offMeshCons[i].rad));
|
||||
Assert.That(readData.offMeshCons[i].poly, Is.EqualTo(meshData.offMeshCons[i].poly));
|
||||
Assert.That(readData.offMeshCons[i].side, Is.EqualTo(meshData.offMeshCons[i].side));
|
||||
Assert.That(readData.offMeshCons[i].userId, Is.EqualTo(meshData.offMeshCons[i].userId));
|
||||
for (int j = 0; j < 6; j++) {
|
||||
for (int j = 0; j < 6; j++)
|
||||
{
|
||||
Assert.That(readData.offMeshCons[i].pos[j], Is.EqualTo(meshData.offMeshCons[i].pos[j]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,12 +24,13 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Test.Io;
|
||||
|
||||
public class MeshSetReaderTest {
|
||||
|
||||
public class MeshSetReaderTest
|
||||
{
|
||||
private readonly MeshSetReader reader = new MeshSetReader();
|
||||
|
||||
[Test]
|
||||
public void testNavmesh() {
|
||||
public void testNavmesh()
|
||||
{
|
||||
byte[] @is = Loader.ToBytes("all_tiles_navmesh.bin");
|
||||
using var ms = new MemoryStream(@is);
|
||||
using var bris = new BinaryReader(ms);
|
||||
|
@ -56,7 +57,8 @@ public class MeshSetReaderTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testDungeon() {
|
||||
public void testDungeon()
|
||||
{
|
||||
byte[] @is = Loader.ToBytes("dungeon_all_tiles_navmesh.bin");
|
||||
using var ms = new MemoryStream(@is);
|
||||
using var bris = new BinaryReader(ms);
|
||||
|
@ -84,7 +86,8 @@ public class MeshSetReaderTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testDungeon32Bit() {
|
||||
public void testDungeon32Bit()
|
||||
{
|
||||
byte[] @is = Loader.ToBytes("dungeon_all_tiles_navmesh_32bit.bin");
|
||||
using var ms = new MemoryStream(@is);
|
||||
using var bris = new BinaryReader(ms);
|
||||
|
|
|
@ -23,13 +23,12 @@ using DotRecast.Detour.Io;
|
|||
using DotRecast.Recast;
|
||||
using DotRecast.Recast.Geom;
|
||||
using NUnit.Framework;
|
||||
|
||||
using static DotRecast.Detour.DetourCommon;
|
||||
|
||||
namespace DotRecast.Detour.Test.Io;
|
||||
|
||||
public class MeshSetReaderWriterTest {
|
||||
|
||||
public class MeshSetReaderWriterTest
|
||||
{
|
||||
private readonly MeshSetWriter writer = new MeshSetWriter();
|
||||
private readonly MeshSetReader reader = new MeshSetReader();
|
||||
private const float m_cellSize = 0.3f;
|
||||
|
@ -52,8 +51,8 @@ public class MeshSetReaderWriterTest {
|
|||
private const int m_maxPolysPerTile = 0x8000;
|
||||
|
||||
[Test]
|
||||
public void test() {
|
||||
|
||||
public void test()
|
||||
{
|
||||
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
|
||||
|
||||
NavMeshSetHeader header = new NavMeshSetHeader();
|
||||
|
@ -72,8 +71,10 @@ public class MeshSetReaderWriterTest {
|
|||
int[] twh = DotRecast.Recast.Recast.calcTileCount(bmin, bmax, m_cellSize, m_tileSize, m_tileSize);
|
||||
int tw = twh[0];
|
||||
int th = twh[1];
|
||||
for (int y = 0; y < th; ++y) {
|
||||
for (int x = 0; x < tw; ++x) {
|
||||
for (int y = 0; y < th; ++y)
|
||||
{
|
||||
for (int x = 0; x < tw; ++x)
|
||||
{
|
||||
RecastConfig cfg = new RecastConfig(true, m_tileSize, m_tileSize,
|
||||
RecastConfig.calcBorder(m_agentRadius, m_cellSize), PartitionType.WATERSHED, m_cellSize, m_cellHeight,
|
||||
m_agentMaxSlope, true, true, true, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_regionMinArea,
|
||||
|
@ -82,7 +83,8 @@ public class MeshSetReaderWriterTest {
|
|||
RecastBuilderConfig bcfg = new RecastBuilderConfig(cfg, bmin, bmax, x, y);
|
||||
TestDetourBuilder db = new TestDetourBuilder();
|
||||
MeshData data = db.build(geom, bcfg, m_agentHeight, m_agentRadius, m_agentMaxClimb, x, y, true);
|
||||
if (data != null) {
|
||||
if (data != null)
|
||||
{
|
||||
mesh.removeTile(mesh.getTileRefAt(x, y, 0));
|
||||
mesh.addTile(data, 0, 0);
|
||||
}
|
||||
|
@ -115,6 +117,5 @@ public class MeshSetReaderWriterTest {
|
|||
Assert.That(tiles.Count, Is.EqualTo(1));
|
||||
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(5));
|
||||
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(17 * 3));
|
||||
|
||||
}
|
||||
}
|
|
@ -20,49 +20,70 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Test;
|
||||
|
||||
public class MoveAlongSurfaceTest : AbstractDetourTest {
|
||||
|
||||
private static readonly long[][] VISITED = {
|
||||
new[] { 281474976710696L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
|
||||
public class MoveAlongSurfaceTest : AbstractDetourTest
|
||||
{
|
||||
private static readonly long[][] VISITED =
|
||||
{
|
||||
new[]
|
||||
{
|
||||
281474976710696L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
|
||||
281474976710705L, 281474976710702L, 281474976710701L, 281474976710714L, 281474976710713L,
|
||||
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710721L },
|
||||
new[] { 281474976710773L, 281474976710772L, 281474976710768L, 281474976710754L, 281474976710755L,
|
||||
281474976710753L },
|
||||
new[] { 281474976710680L, 281474976710684L, 281474976710688L, 281474976710687L, 281474976710686L,
|
||||
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710721L
|
||||
},
|
||||
new[]
|
||||
{
|
||||
281474976710773L, 281474976710772L, 281474976710768L, 281474976710754L, 281474976710755L,
|
||||
281474976710753L
|
||||
},
|
||||
new[]
|
||||
{
|
||||
281474976710680L, 281474976710684L, 281474976710688L, 281474976710687L, 281474976710686L,
|
||||
281474976710697L, 281474976710695L, 281474976710694L, 281474976710703L, 281474976710706L,
|
||||
281474976710705L, 281474976710702L, 281474976710701L, 281474976710714L, 281474976710713L,
|
||||
281474976710712L, 281474976710727L, 281474976710730L, 281474976710717L, 281474976710721L,
|
||||
281474976710718L },
|
||||
281474976710718L
|
||||
},
|
||||
new[] { 281474976710753L, 281474976710748L, 281474976710752L, 281474976710731L },
|
||||
new[] { 281474976710733L, 281474976710736L, 281474976710738L, 281474976710737L, 281474976710728L,
|
||||
new[]
|
||||
{
|
||||
281474976710733L, 281474976710736L, 281474976710738L, 281474976710737L, 281474976710728L,
|
||||
281474976710724L, 281474976710717L, 281474976710729L, 281474976710731L, 281474976710752L,
|
||||
281474976710748L, 281474976710753L, 281474976710755L, 281474976710754L, 281474976710768L,
|
||||
281474976710772L } };
|
||||
private static readonly float[][] POSITION = {
|
||||
281474976710772L
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly float[][] POSITION =
|
||||
{
|
||||
new[] { 6.457663f, 10.197294f, -18.334061f },
|
||||
new[] { -1.433933f, 10.197294f, -1.359993f },
|
||||
new[] { 12.184784f, 9.997294f, -18.941269f },
|
||||
new[] { 0.863553f, 10.197294f, -10.310320f },
|
||||
new[] { 18.784092f, 10.197294f, 3.054368f } };
|
||||
new[] { 18.784092f, 10.197294f, 3.054368f }
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void testMoveAlongSurface() {
|
||||
public void testMoveAlongSurface()
|
||||
{
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
for (int i = 0; i < startRefs.Length; i++) {
|
||||
for (int i = 0; i < startRefs.Length; i++)
|
||||
{
|
||||
long startRef = startRefs[i];
|
||||
float[] startPos = startPoss[i];
|
||||
float[] endPos = endPoss[i];
|
||||
Result<MoveAlongSurfaceResult> result = query.moveAlongSurface(startRef, startPos, endPos, filter);
|
||||
Assert.That(result.succeeded(), Is.True);
|
||||
MoveAlongSurfaceResult path = result.result;
|
||||
for (int v = 0; v < 3; v++) {
|
||||
for (int v = 0; v < 3; v++)
|
||||
{
|
||||
Assert.That(path.getResultPos()[v], Is.EqualTo(POSITION[i][v]).Within(0.01f));
|
||||
}
|
||||
|
||||
Assert.That(path.getVisited().Count, Is.EqualTo(VISITED[i].Length));
|
||||
for (int j = 0; j < POSITION[i].Length; j++) {
|
||||
for (int j = 0; j < POSITION[i].Length; j++)
|
||||
{
|
||||
Assert.That(path.getVisited()[j], Is.EqualTo(VISITED[i][j]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -20,18 +20,19 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Test;
|
||||
|
||||
|
||||
public class NavMeshBuilderTest {
|
||||
|
||||
public class NavMeshBuilderTest
|
||||
{
|
||||
private MeshData nmd;
|
||||
|
||||
[SetUp]
|
||||
public void setUp() {
|
||||
public void setUp()
|
||||
{
|
||||
nmd = new RecastTestMeshBuilder().getMeshData();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void testBVTree() {
|
||||
public void testBVTree()
|
||||
{
|
||||
Assert.That(nmd.verts.Length / 3, Is.EqualTo(225));
|
||||
Assert.That(nmd.polys.Length, Is.EqualTo(119));
|
||||
Assert.That(nmd.header.maxLinkCount, Is.EqualTo(457));
|
||||
|
@ -42,12 +43,16 @@ public class NavMeshBuilderTest {
|
|||
Assert.That(nmd.header.offMeshBase, Is.EqualTo(118));
|
||||
Assert.That(nmd.bvTree.Length, Is.EqualTo(236));
|
||||
Assert.That(nmd.bvTree.Length, Is.GreaterThanOrEqualTo(nmd.header.bvNodeCount));
|
||||
for (int i = 0; i < nmd.header.bvNodeCount; i++) {
|
||||
for (int i = 0; i < nmd.header.bvNodeCount; i++)
|
||||
{
|
||||
Assert.That(nmd.bvTree[i], Is.Not.Null);
|
||||
}
|
||||
for (int i = 0; i < 6; i++) {
|
||||
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
Assert.That(nmd.verts[223 * 3 + i], Is.EqualTo(nmd.offMeshCons[0].pos[i]));
|
||||
}
|
||||
|
||||
Assert.That(nmd.offMeshCons[0].rad, Is.EqualTo(0.1f));
|
||||
Assert.That(nmd.offMeshCons[0].poly, Is.EqualTo(118));
|
||||
Assert.That(nmd.offMeshCons[0].flags, Is.EqualTo(NavMesh.DT_OFFMESH_CON_BIDIR));
|
||||
|
@ -59,6 +64,5 @@ public class NavMeshBuilderTest {
|
|||
Assert.That(nmd.polys[118].flags, Is.EqualTo(12));
|
||||
Assert.That(nmd.polys[118].getArea(), Is.EqualTo(2));
|
||||
Assert.That(nmd.polys[118].getType(), Is.EqualTo(Poly.DT_POLYTYPE_OFFMESH_CONNECTION));
|
||||
|
||||
}
|
||||
}
|
|
@ -20,13 +20,13 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Test;
|
||||
|
||||
|
||||
public class PolygonByCircleConstraintTest {
|
||||
|
||||
public class PolygonByCircleConstraintTest
|
||||
{
|
||||
private readonly PolygonByCircleConstraint constraint = new PolygonByCircleConstraint.StrictPolygonByCircleConstraint();
|
||||
|
||||
[Test]
|
||||
public void shouldHandlePolygonFullyInsideCircle() {
|
||||
public void shouldHandlePolygonFullyInsideCircle()
|
||||
{
|
||||
float[] polygon = { -2, 0, 2, 2, 0, 2, 2, 0, -2, -2, 0, -2 };
|
||||
float[] center = { 1, 0, 1 };
|
||||
float[] constrained = constraint.aply(polygon, center, 6);
|
||||
|
@ -35,7 +35,8 @@ public class PolygonByCircleConstraintTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void shouldHandleVerticalSegment() {
|
||||
public void shouldHandleVerticalSegment()
|
||||
{
|
||||
int expectedSize = 21;
|
||||
float[] polygon = { -2, 0, 2, 2, 0, 2, 2, 0, -2, -2, 0, -2 };
|
||||
float[] center = { 2, 0, 0 };
|
||||
|
@ -46,7 +47,8 @@ public class PolygonByCircleConstraintTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void shouldHandleCircleFullyInsidePolygon() {
|
||||
public void shouldHandleCircleFullyInsidePolygon()
|
||||
{
|
||||
int expectedSize = 12 * 3;
|
||||
float[] polygon = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
|
||||
float[] center = { -1, 0, -1 };
|
||||
|
@ -54,7 +56,8 @@ public class PolygonByCircleConstraintTest {
|
|||
|
||||
Assert.That(constrained.Length, Is.EqualTo(expectedSize));
|
||||
|
||||
for (int i = 0; i < expectedSize; i += 3) {
|
||||
for (int i = 0; i < expectedSize; i += 3)
|
||||
{
|
||||
float x = constrained[i] + 1;
|
||||
float z = constrained[i + 2] + 1;
|
||||
Assert.That(x * x + z * z, Is.EqualTo(4).Within(1e-4f));
|
||||
|
@ -62,7 +65,8 @@ public class PolygonByCircleConstraintTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void shouldHandleCircleInsidePolygon() {
|
||||
public void shouldHandleCircleInsidePolygon()
|
||||
{
|
||||
int expectedSize = 9 * 3;
|
||||
float[] polygon = { -4, 0, 0, -3, 0, 3, 2, 0, 3, 3, 0, -3, -2, 0, -4 };
|
||||
float[] center = { -2, 0, -1 };
|
||||
|
@ -83,5 +87,4 @@ public class PolygonByCircleConstraintTest {
|
|||
Assert.That(constrained.Length, Is.EqualTo(expectedSize));
|
||||
Assert.That(constrained, Is.SupersetOf(new[] { 1.5358982f, 0f, 3f, 2f, 0f, 3f, 3f, 0f, -3f }));
|
||||
}
|
||||
|
||||
}
|
|
@ -19,30 +19,33 @@ freely, subject to the following restrictions:
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using NUnit.Framework;
|
||||
|
||||
using static DotRecast.Detour.DetourCommon;
|
||||
|
||||
namespace DotRecast.Detour.Test;
|
||||
|
||||
public class RandomPointTest : AbstractDetourTest {
|
||||
|
||||
public class RandomPointTest : AbstractDetourTest
|
||||
{
|
||||
[Test]
|
||||
public void testRandom() {
|
||||
public void testRandom()
|
||||
{
|
||||
NavMeshQuery.FRand f = new NavMeshQuery.FRand(1);
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
Result<FindRandomPointResult> point = query.findRandomPoint(filter, f);
|
||||
Assert.That(point.succeeded(), Is.True);
|
||||
Tuple<MeshTile, Poly> tileAndPoly = navmesh.getTileAndPolyByRef(point.result.getRandomRef()).result;
|
||||
float[] bmin = new float[2];
|
||||
float[] bmax = new float[2];
|
||||
for (int j = 0; j < tileAndPoly.Item2.vertCount; j++) {
|
||||
for (int j = 0; j < tileAndPoly.Item2.vertCount; j++)
|
||||
{
|
||||
int v = tileAndPoly.Item2.verts[j] * 3;
|
||||
bmin[0] = j == 0 ? tileAndPoly.Item1.data.verts[v] : Math.Min(bmin[0], tileAndPoly.Item1.data.verts[v]);
|
||||
bmax[0] = j == 0 ? tileAndPoly.Item1.data.verts[v] : Math.Max(bmax[0], tileAndPoly.Item1.data.verts[v]);
|
||||
bmin[1] = j == 0 ? tileAndPoly.Item1.data.verts[v + 2] : Math.Min(bmin[1], tileAndPoly.Item1.data.verts[v + 2]);
|
||||
bmax[1] = j == 0 ? tileAndPoly.Item1.data.verts[v + 2] : Math.Max(bmax[1], tileAndPoly.Item1.data.verts[v + 2]);
|
||||
}
|
||||
|
||||
Assert.That(point.result.getRandomPt()[0] >= bmin[0], Is.True);
|
||||
Assert.That(point.result.getRandomPt()[0] <= bmax[0], Is.True);
|
||||
Assert.That(point.result.getRandomPt()[2] >= bmin[1], Is.True);
|
||||
|
@ -51,11 +54,13 @@ public class RandomPointTest : AbstractDetourTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testRandomAroundCircle() {
|
||||
public void testRandomAroundCircle()
|
||||
{
|
||||
NavMeshQuery.FRand f = new NavMeshQuery.FRand(1);
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
FindRandomPointResult point = query.findRandomPoint(filter, f).result;
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
Result<FindRandomPointResult> result = query.findRandomPointAroundCircle(point.getRandomRef(), point.getRandomPt(),
|
||||
5f, filter, f);
|
||||
Assert.That(result.failed(), Is.False);
|
||||
|
@ -63,13 +68,15 @@ public class RandomPointTest : AbstractDetourTest {
|
|||
Tuple<MeshTile, Poly> tileAndPoly = navmesh.getTileAndPolyByRef(point.getRandomRef()).result;
|
||||
float[] bmin = new float[2];
|
||||
float[] bmax = new float[2];
|
||||
for (int j = 0; j < tileAndPoly.Item2.vertCount; j++) {
|
||||
for (int j = 0; j < tileAndPoly.Item2.vertCount; j++)
|
||||
{
|
||||
int v = tileAndPoly.Item2.verts[j] * 3;
|
||||
bmin[0] = j == 0 ? tileAndPoly.Item1.data.verts[v] : Math.Min(bmin[0], tileAndPoly.Item1.data.verts[v]);
|
||||
bmax[0] = j == 0 ? tileAndPoly.Item1.data.verts[v] : Math.Max(bmax[0], tileAndPoly.Item1.data.verts[v]);
|
||||
bmin[1] = j == 0 ? tileAndPoly.Item1.data.verts[v + 2] : Math.Min(bmin[1], tileAndPoly.Item1.data.verts[v + 2]);
|
||||
bmax[1] = j == 0 ? tileAndPoly.Item1.data.verts[v + 2] : Math.Max(bmax[1], tileAndPoly.Item1.data.verts[v + 2]);
|
||||
}
|
||||
|
||||
Assert.That(point.getRandomPt()[0] >= bmin[0], Is.True);
|
||||
Assert.That(point.getRandomPt()[0] <= bmax[0], Is.True);
|
||||
Assert.That(point.getRandomPt()[2] >= bmin[1], Is.True);
|
||||
|
@ -78,12 +85,14 @@ public class RandomPointTest : AbstractDetourTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testRandomWithinCircle() {
|
||||
public void testRandomWithinCircle()
|
||||
{
|
||||
NavMeshQuery.FRand f = new NavMeshQuery.FRand(1);
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
FindRandomPointResult point = query.findRandomPoint(filter, f).result;
|
||||
float radius = 5f;
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
Result<FindRandomPointResult> result = query.findRandomPointWithinCircle(point.getRandomRef(), point.getRandomPt(),
|
||||
radius, filter, f);
|
||||
Assert.That(result.failed(), Is.False);
|
||||
|
@ -94,30 +103,37 @@ public class RandomPointTest : AbstractDetourTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testPerformance() {
|
||||
public void testPerformance()
|
||||
{
|
||||
NavMeshQuery.FRand f = new NavMeshQuery.FRand(1);
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
FindRandomPointResult point = query.findRandomPoint(filter, f).result;
|
||||
float radius = 5f;
|
||||
// jvm warmup
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
query.findRandomPointAroundCircle(point.getRandomRef(), point.getRandomPt(), radius, filter, f);
|
||||
}
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
|
||||
for (int i = 0; i < 1000; i++)
|
||||
{
|
||||
query.findRandomPointWithinCircle(point.getRandomRef(), point.getRandomPt(), radius, filter, f);
|
||||
}
|
||||
|
||||
long t1 = Stopwatch.GetTimestamp();
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
for (int i = 0; i < 10000; i++)
|
||||
{
|
||||
query.findRandomPointAroundCircle(point.getRandomRef(), point.getRandomPt(), radius, filter, f);
|
||||
}
|
||||
|
||||
long t2 = Stopwatch.GetTimestamp();
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
for (int i = 0; i < 10000; i++)
|
||||
{
|
||||
query.findRandomPointWithinCircle(point.getRandomRef(), point.getRandomPt(), radius, filter, f);
|
||||
}
|
||||
|
||||
long t3 = Stopwatch.GetTimestamp();
|
||||
Console.WriteLine("Random point around circle: " + (t2 - t1) / 1000000 + "ms");
|
||||
Console.WriteLine("Random point within circle: " + (t3 - t2) / 1000000 + "ms");
|
||||
}
|
||||
|
||||
}
|
|
@ -22,8 +22,8 @@ using DotRecast.Recast.Geom;
|
|||
|
||||
namespace DotRecast.Detour.Test;
|
||||
|
||||
public class RecastTestMeshBuilder {
|
||||
|
||||
public class RecastTestMeshBuilder
|
||||
{
|
||||
private readonly MeshData meshData;
|
||||
private const float m_cellSize = 0.3f;
|
||||
private const float m_cellHeight = 0.2f;
|
||||
|
@ -50,7 +50,8 @@ public class RecastTestMeshBuilder {
|
|||
public RecastTestMeshBuilder(InputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize,
|
||||
float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope,
|
||||
int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly,
|
||||
float m_detailSampleDist, float m_detailSampleMaxError) {
|
||||
float m_detailSampleDist, float m_detailSampleMaxError)
|
||||
{
|
||||
RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius,
|
||||
m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
|
||||
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
|
||||
|
@ -58,9 +59,11 @@ public class RecastTestMeshBuilder {
|
|||
RecastBuilder rcBuilder = new RecastBuilder();
|
||||
RecastBuilderResult rcResult = rcBuilder.build(m_geom, bcfg);
|
||||
PolyMesh m_pmesh = rcResult.getMesh();
|
||||
for (int i = 0; i < m_pmesh.npolys; ++i) {
|
||||
for (int i = 0; i < m_pmesh.npolys; ++i)
|
||||
{
|
||||
m_pmesh.flags[i] = 1;
|
||||
}
|
||||
|
||||
PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
|
||||
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
|
||||
option.verts = m_pmesh.verts;
|
||||
|
@ -105,7 +108,8 @@ public class RecastTestMeshBuilder {
|
|||
meshData = NavMeshBuilder.createNavMeshData(option);
|
||||
}
|
||||
|
||||
public MeshData getMeshData() {
|
||||
public MeshData getMeshData()
|
||||
{
|
||||
return meshData;
|
||||
}
|
||||
}
|
|
@ -20,8 +20,8 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.Test;
|
||||
|
||||
public class SampleAreaModifications {
|
||||
|
||||
public class SampleAreaModifications
|
||||
{
|
||||
public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
|
||||
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
|
||||
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
|
||||
|
@ -32,14 +32,19 @@ public class SampleAreaModifications {
|
|||
|
||||
public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
|
||||
SAMPLE_POLYAREA_TYPE_MASK);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
|
||||
SAMPLE_POLYAREA_TYPE_MASK);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
|
||||
SAMPLE_POLYAREA_TYPE_MASK);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
|
||||
SAMPLE_POLYAREA_TYPE_MASK);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR,
|
||||
SAMPLE_POLYAREA_TYPE_DOOR);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP,
|
||||
SAMPLE_POLYAREA_TYPE_JUMP);
|
||||
|
||||
|
|
|
@ -21,32 +21,43 @@ using DotRecast.Recast.Geom;
|
|||
|
||||
namespace DotRecast.Detour.Test;
|
||||
|
||||
public class TestDetourBuilder : DetourBuilder {
|
||||
|
||||
public class TestDetourBuilder : DetourBuilder
|
||||
{
|
||||
public MeshData build(InputGeomProvider geom, RecastBuilderConfig rcConfig, float agentHeight, float agentRadius,
|
||||
float agentMaxClimb, int x, int y, bool applyRecastDemoFlags) {
|
||||
float agentMaxClimb, int x, int y, bool applyRecastDemoFlags)
|
||||
{
|
||||
RecastBuilder rcBuilder = new RecastBuilder();
|
||||
RecastBuilderResult rcResult = rcBuilder.build(geom, rcConfig);
|
||||
PolyMesh pmesh = rcResult.getMesh();
|
||||
|
||||
if (applyRecastDemoFlags) {
|
||||
if (applyRecastDemoFlags)
|
||||
{
|
||||
// Update poly flags from areas.
|
||||
for (int i = 0; i < pmesh.npolys; ++i) {
|
||||
for (int i = 0; i < pmesh.npolys; ++i)
|
||||
{
|
||||
if (pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND
|
||||
|| pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GRASS
|
||||
|| pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD) {
|
||||
|| pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD)
|
||||
{
|
||||
pmesh.flags[i] = SampleAreaModifications.SAMPLE_POLYFLAGS_WALK;
|
||||
} else if (pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER) {
|
||||
}
|
||||
else if (pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER)
|
||||
{
|
||||
pmesh.flags[i] = SampleAreaModifications.SAMPLE_POLYFLAGS_SWIM;
|
||||
} else if (pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_DOOR) {
|
||||
}
|
||||
else if (pmesh.areas[i] == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_DOOR)
|
||||
{
|
||||
pmesh.flags[i] = SampleAreaModifications.SAMPLE_POLYFLAGS_WALK
|
||||
| SampleAreaModifications.SAMPLE_POLYFLAGS_DOOR;
|
||||
}
|
||||
if (pmesh.areas[i] > 0) {
|
||||
|
||||
if (pmesh.areas[i] > 0)
|
||||
{
|
||||
pmesh.areas[i]--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PolyMeshDetail dmesh = rcResult.getMeshDetail();
|
||||
NavMeshDataCreateParams option = getNavMeshCreateParams(rcConfig.cfg, pmesh, dmesh, agentHeight, agentRadius,
|
||||
agentMaxClimb);
|
||||
|
@ -54,7 +65,8 @@ public class TestDetourBuilder : DetourBuilder {
|
|||
}
|
||||
|
||||
public NavMeshDataCreateParams getNavMeshCreateParams(RecastConfig rcConfig, PolyMesh pmesh, PolyMeshDetail dmesh,
|
||||
float agentHeight, float agentRadius, float agentMaxClimb) {
|
||||
float agentHeight, float agentRadius, float agentMaxClimb)
|
||||
{
|
||||
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
|
||||
option.verts = pmesh.verts;
|
||||
option.vertCount = pmesh.nverts;
|
||||
|
@ -63,13 +75,15 @@ public class TestDetourBuilder : DetourBuilder {
|
|||
option.polyFlags = pmesh.flags;
|
||||
option.polyCount = pmesh.npolys;
|
||||
option.nvp = pmesh.nvp;
|
||||
if (dmesh != null) {
|
||||
if (dmesh != null)
|
||||
{
|
||||
option.detailMeshes = dmesh.meshes;
|
||||
option.detailVerts = dmesh.verts;
|
||||
option.detailVertsCount = dmesh.nverts;
|
||||
option.detailTris = dmesh.tris;
|
||||
option.detailTriCount = dmesh.ntris;
|
||||
}
|
||||
|
||||
option.walkableHeight = agentHeight;
|
||||
option.walkableRadius = agentRadius;
|
||||
option.walkableClimb = agentMaxClimb;
|
||||
|
@ -86,6 +100,5 @@ public class TestDetourBuilder : DetourBuilder {
|
|||
* option.offMeshConCount = m_geom->getOffMeshConnectionCount();
|
||||
*/
|
||||
return option;
|
||||
|
||||
}
|
||||
}
|
|
@ -20,13 +20,12 @@ using System.Collections.Generic;
|
|||
using DotRecast.Core;
|
||||
using DotRecast.Recast;
|
||||
using DotRecast.Recast.Geom;
|
||||
|
||||
using static DotRecast.Recast.RecastVectors;
|
||||
|
||||
namespace DotRecast.Detour.Test;
|
||||
|
||||
public class TestTiledNavMeshBuilder {
|
||||
|
||||
public class TestTiledNavMeshBuilder
|
||||
{
|
||||
private readonly NavMesh navMesh;
|
||||
private const float m_cellSize = 0.3f;
|
||||
private const float m_cellHeight = 0.2f;
|
||||
|
@ -56,8 +55,8 @@ public class TestTiledNavMeshBuilder {
|
|||
public TestTiledNavMeshBuilder(InputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize, float m_cellHeight,
|
||||
float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope, int m_regionMinSize,
|
||||
int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly, float m_detailSampleDist,
|
||||
float m_detailSampleMaxError, int m_tileSize) {
|
||||
|
||||
float m_detailSampleMaxError, int m_tileSize)
|
||||
{
|
||||
// Create empty nav mesh
|
||||
NavMeshParams navMeshParams = new NavMeshParams();
|
||||
copy(navMeshParams.orig, m_geom.getMeshBoundsMin());
|
||||
|
@ -77,14 +76,19 @@ public class TestTiledNavMeshBuilder {
|
|||
|
||||
// Add tiles to nav mesh
|
||||
|
||||
foreach (RecastBuilderResult result in rcResult) {
|
||||
foreach (RecastBuilderResult result in rcResult)
|
||||
{
|
||||
PolyMesh pmesh = result.getMesh();
|
||||
if (pmesh.npolys == 0) {
|
||||
if (pmesh.npolys == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < pmesh.npolys; ++i) {
|
||||
|
||||
for (int i = 0; i < pmesh.npolys; ++i)
|
||||
{
|
||||
pmesh.flags[i] = 1;
|
||||
}
|
||||
|
||||
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
|
||||
option.verts = pmesh.verts;
|
||||
option.vertCount = pmesh.nverts;
|
||||
|
@ -113,8 +117,8 @@ public class TestTiledNavMeshBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
public NavMesh getNavMesh() {
|
||||
public NavMesh getNavMesh()
|
||||
{
|
||||
return navMesh;
|
||||
}
|
||||
|
||||
}
|
|
@ -21,16 +21,23 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.Test;
|
||||
|
||||
public class TiledFindPathTest {
|
||||
|
||||
public class TiledFindPathTest
|
||||
{
|
||||
private static readonly Status[] STATUSES = { Status.SUCCSESS };
|
||||
private static readonly long[][] RESULTS = {
|
||||
new[] { 281475015507969L, 281475014459393L, 281475014459392L, 281475006070784L,
|
||||
|
||||
private static readonly long[][] RESULTS =
|
||||
{
|
||||
new[]
|
||||
{
|
||||
281475015507969L, 281475014459393L, 281475014459392L, 281475006070784L,
|
||||
281475005022208L, 281475003973636L, 281475012362240L, 281475012362241L, 281475012362242L, 281475003973634L,
|
||||
281475003973635L, 281475003973633L, 281475002925059L, 281475002925057L, 281475002925056L, 281474998730753L,
|
||||
281474998730754L, 281474994536450L, 281474994536451L, 281474994536452L, 281474994536448L, 281474990342146L,
|
||||
281474990342145L, 281474991390723L, 281474991390724L, 281474991390725L, 281474987196418L, 281474987196417L,
|
||||
281474988244996L, 281474988244995L, 281474988244997L, 281474985099266L } };
|
||||
281474988244996L, 281474988244995L, 281474988244997L, 281474985099266L
|
||||
}
|
||||
};
|
||||
|
||||
protected static readonly long[] START_REFS = { 281475015507969L };
|
||||
protected static readonly long[] END_REFS = { 281474985099266L };
|
||||
protected static readonly float[][] START_POS = { new[] { 39.447338f, 9.998177f, -0.784811f } };
|
||||
|
@ -40,19 +47,23 @@ public class TiledFindPathTest {
|
|||
protected NavMesh navmesh;
|
||||
|
||||
[SetUp]
|
||||
public void setUp() {
|
||||
public void setUp()
|
||||
{
|
||||
navmesh = createNavMesh();
|
||||
query = new NavMeshQuery(navmesh);
|
||||
}
|
||||
|
||||
protected NavMesh createNavMesh() {
|
||||
protected NavMesh createNavMesh()
|
||||
{
|
||||
return new TestTiledNavMeshBuilder().getNavMesh();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void testFindPath() {
|
||||
public void testFindPath()
|
||||
{
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
for (int i = 0; i < START_REFS.Length; i++) {
|
||||
for (int i = 0; i < START_REFS.Length; i++)
|
||||
{
|
||||
long startRef = START_REFS[i];
|
||||
long endRef = END_REFS[i];
|
||||
float[] startPos = START_POS[i];
|
||||
|
@ -60,10 +71,10 @@ public class TiledFindPathTest {
|
|||
Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter);
|
||||
Assert.That(path.status, Is.EqualTo(STATUSES[i]));
|
||||
Assert.That(path.result.Count, Is.EqualTo(RESULTS[i].Length));
|
||||
for (int j = 0; j < RESULTS[i].Length; j++) {
|
||||
for (int j = 0; j < RESULTS[i].Length; j++)
|
||||
{
|
||||
Assert.That(RESULTS[i][j], Is.EqualTo(path.result[j]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -21,14 +21,13 @@ freely, subject to the following restrictions:
|
|||
using DotRecast.Core;
|
||||
using DotRecast.Detour.TileCache.Io.Compress;
|
||||
using DotRecast.Recast.Geom;
|
||||
|
||||
using static DotRecast.Detour.DetourCommon;
|
||||
using static DotRecast.Recast.RecastVectors;
|
||||
|
||||
namespace DotRecast.Detour.TileCache.Test;
|
||||
|
||||
public class AbstractTileCacheTest {
|
||||
|
||||
public class AbstractTileCacheTest
|
||||
{
|
||||
private const int EXPECTED_LAYERS_PER_TILE = 4;
|
||||
private readonly float m_cellSize = 0.3f;
|
||||
private readonly float m_cellHeight = 0.2f;
|
||||
|
@ -38,15 +37,19 @@ public class AbstractTileCacheTest {
|
|||
private readonly float m_edgeMaxError = 1.3f;
|
||||
private readonly int m_tileSize = 48;
|
||||
|
||||
protected class TestTileCacheMeshProcess : TileCacheMeshProcess {
|
||||
public void process(NavMeshDataCreateParams option) {
|
||||
for (int i = 0; i < option.polyCount; ++i) {
|
||||
protected class TestTileCacheMeshProcess : TileCacheMeshProcess
|
||||
{
|
||||
public void process(NavMeshDataCreateParams option)
|
||||
{
|
||||
for (int i = 0; i < option.polyCount; ++i)
|
||||
{
|
||||
option.polyFlags[i] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TileCache getTileCache(InputGeomProvider geom, ByteOrder order, bool cCompatibility) {
|
||||
public TileCache getTileCache(InputGeomProvider geom, ByteOrder order, bool cCompatibility)
|
||||
{
|
||||
TileCacheParams option = new TileCacheParams();
|
||||
int[] twh = Recast.Recast.calcTileCount(geom.getMeshBoundsMin(), geom.getMeshBoundsMax(), m_cellSize, m_tileSize, m_tileSize);
|
||||
option.ch = m_cellHeight;
|
||||
|
@ -71,5 +74,4 @@ public class AbstractTileCacheTest {
|
|||
TileCacheCompressorFactory.get(cCompatibility), new TestTileCacheMeshProcess());
|
||||
return tc;
|
||||
}
|
||||
|
||||
}
|
|
@ -26,13 +26,13 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.TileCache.Test.Io;
|
||||
|
||||
public class TileCacheReaderTest {
|
||||
|
||||
public class TileCacheReaderTest
|
||||
{
|
||||
private readonly TileCacheReader reader = new TileCacheReader();
|
||||
|
||||
[Test]
|
||||
public void testNavmesh() {
|
||||
|
||||
public void testNavmesh()
|
||||
{
|
||||
using var ms = new MemoryStream(Loader.ToBytes("all_tiles_tilecache.bin"));
|
||||
using var @is = new BinaryReader(ms);
|
||||
TileCache tc = reader.read(@is, 6, null);
|
||||
|
@ -129,7 +129,8 @@ public class TileCacheReaderTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testDungeon() {
|
||||
public void testDungeon()
|
||||
{
|
||||
using var ms = new MemoryStream(Loader.ToBytes("dungeon_all_tiles_tilecache.bin"));
|
||||
using var @is = new BinaryReader(ms);
|
||||
TileCache tc = reader.read(@is, 6, null);
|
||||
|
@ -220,5 +221,4 @@ public class TileCacheReaderTest {
|
|||
Assert.That(data.verts[6], Is.EqualTo(48.484783f).Within(0.0001f));
|
||||
Assert.That(data.verts[9], Is.EqualTo(48.484783f).Within(0.0001f));
|
||||
}
|
||||
|
||||
}
|
|
@ -28,30 +28,33 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.TileCache.Test.Io;
|
||||
|
||||
|
||||
public class TileCacheReaderWriterTest : AbstractTileCacheTest {
|
||||
|
||||
public class TileCacheReaderWriterTest : AbstractTileCacheTest
|
||||
{
|
||||
private readonly TileCacheReader reader = new TileCacheReader();
|
||||
private readonly TileCacheWriter writer = new TileCacheWriter();
|
||||
|
||||
[Test]
|
||||
public void testFastLz() {
|
||||
public void testFastLz()
|
||||
{
|
||||
testDungeon(false);
|
||||
testDungeon(true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void testLZ4() {
|
||||
public void testLZ4()
|
||||
{
|
||||
testDungeon(true);
|
||||
testDungeon(false);
|
||||
}
|
||||
|
||||
private void testDungeon(bool cCompatibility) {
|
||||
private void testDungeon(bool cCompatibility)
|
||||
{
|
||||
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
|
||||
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
|
||||
List<byte[]> layers = layerBuilder.build(ByteOrder.LITTLE_ENDIAN, cCompatibility, 1);
|
||||
TileCache tc = getTileCache(geom, ByteOrder.LITTLE_ENDIAN, cCompatibility);
|
||||
foreach (byte[] layer in layers) {
|
||||
foreach (byte[] layer in layers)
|
||||
{
|
||||
long refs = tc.addTile(layer, 0);
|
||||
tc.buildNavMeshTile(refs);
|
||||
}
|
||||
|
@ -134,5 +137,4 @@ public class TileCacheReaderWriterTest : AbstractTileCacheTest {
|
|||
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
|
||||
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 3));
|
||||
}
|
||||
|
||||
}
|
|
@ -22,35 +22,43 @@ using DotRecast.Recast;
|
|||
|
||||
namespace DotRecast.Detour.TileCache.Test;
|
||||
|
||||
|
||||
|
||||
public class SampleAreaModifications {
|
||||
|
||||
public class SampleAreaModifications
|
||||
{
|
||||
public static int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
|
||||
|
||||
/// Value for the kind of ceil "ground"
|
||||
public static int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
|
||||
|
||||
/// Value for the kind of ceil "water"
|
||||
public static int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
|
||||
|
||||
/// Value for the kind of ceil "road"
|
||||
public static int SAMPLE_POLYAREA_TYPE_ROAD = 0x3;
|
||||
|
||||
/// Value for the kind of ceil "grass"
|
||||
public static int SAMPLE_POLYAREA_TYPE_GRASS = 0x4;
|
||||
|
||||
/// Flag for door area. Can be combined with area types and jump flag.
|
||||
public static int SAMPLE_POLYAREA_FLAG_DOOR = 0x08;
|
||||
|
||||
/// Flag for jump area. Can be combined with area types and door flag.
|
||||
public static int SAMPLE_POLYAREA_FLAG_JUMP = 0x10;
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
|
||||
SAMPLE_POLYAREA_TYPE_MASK);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
|
||||
SAMPLE_POLYAREA_TYPE_MASK);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
|
||||
SAMPLE_POLYAREA_TYPE_MASK);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
|
||||
SAMPLE_POLYAREA_TYPE_MASK);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_FLAG_DOOR,
|
||||
SAMPLE_POLYAREA_FLAG_DOOR);
|
||||
|
||||
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_FLAG_JUMP,
|
||||
SAMPLE_POLYAREA_FLAG_JUMP);
|
||||
|
||||
}
|
|
@ -26,19 +26,22 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.TileCache.Test;
|
||||
|
||||
public class TempObstaclesTest : AbstractTileCacheTest {
|
||||
|
||||
public class TempObstaclesTest : AbstractTileCacheTest
|
||||
{
|
||||
[Test]
|
||||
public void testDungeon() {
|
||||
public void testDungeon()
|
||||
{
|
||||
bool cCompatibility = true;
|
||||
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
|
||||
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
|
||||
List<byte[]> layers = layerBuilder.build(ByteOrder.LITTLE_ENDIAN, cCompatibility, 1);
|
||||
TileCache tc = getTileCache(geom, ByteOrder.LITTLE_ENDIAN, cCompatibility);
|
||||
foreach (byte[] data in layers) {
|
||||
foreach (byte[] data in layers)
|
||||
{
|
||||
long refs = tc.addTile(data, 0);
|
||||
tc.buildNavMeshTile(refs);
|
||||
}
|
||||
|
||||
List<MeshTile> tiles = tc.getNavMesh().getTilesAt(1, 4);
|
||||
MeshTile tile = tiles[0];
|
||||
Assert.That(tile.data.header.vertCount, Is.EqualTo(16));
|
||||
|
@ -60,16 +63,19 @@ public class TempObstaclesTest : AbstractTileCacheTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testDungeonBox() {
|
||||
public void testDungeonBox()
|
||||
{
|
||||
bool cCompatibility = true;
|
||||
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
|
||||
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
|
||||
List<byte[]> layers = layerBuilder.build(ByteOrder.LITTLE_ENDIAN, cCompatibility, 1);
|
||||
TileCache tc = getTileCache(geom, ByteOrder.LITTLE_ENDIAN, cCompatibility);
|
||||
foreach (byte[] data in layers) {
|
||||
foreach (byte[] data in layers)
|
||||
{
|
||||
long refs = tc.addTile(data, 0);
|
||||
tc.buildNavMeshTile(refs);
|
||||
}
|
||||
|
||||
List<MeshTile> tiles = tc.getNavMesh().getTilesAt(1, 4);
|
||||
MeshTile tile = tiles[0];
|
||||
Assert.That(tile.data.header.vertCount, Is.EqualTo(16));
|
||||
|
|
|
@ -22,13 +22,12 @@ using System.Collections.Generic;
|
|||
using DotRecast.Core;
|
||||
using DotRecast.Recast;
|
||||
using DotRecast.Recast.Geom;
|
||||
|
||||
using static DotRecast.Detour.DetourCommon;
|
||||
|
||||
namespace DotRecast.Detour.TileCache.Test;
|
||||
|
||||
public class TestTileLayerBuilder : AbstractTileLayersBuilder {
|
||||
|
||||
public class TestTileLayerBuilder : AbstractTileLayersBuilder
|
||||
{
|
||||
private const float m_cellSize = 0.3f;
|
||||
private const float m_cellHeight = 0.2f;
|
||||
private const float m_agentHeight = 2.0f;
|
||||
|
@ -50,7 +49,8 @@ public class TestTileLayerBuilder : AbstractTileLayersBuilder {
|
|||
private readonly int tw;
|
||||
private readonly int th;
|
||||
|
||||
public TestTileLayerBuilder(InputGeomProvider geom) {
|
||||
public TestTileLayerBuilder(InputGeomProvider geom)
|
||||
{
|
||||
this.geom = geom;
|
||||
rcConfig = new RecastConfig(true, m_tileSize, m_tileSize, RecastConfig.calcBorder(m_agentRadius, m_cellSize),
|
||||
PartitionType.WATERSHED, m_cellSize, m_cellHeight, m_agentMaxSlope, true, true, true, m_agentHeight,
|
||||
|
@ -63,24 +63,30 @@ public class TestTileLayerBuilder : AbstractTileLayersBuilder {
|
|||
th = twh[1];
|
||||
}
|
||||
|
||||
public List<byte[]> build(ByteOrder order, bool cCompatibility, int threads) {
|
||||
public List<byte[]> build(ByteOrder order, bool cCompatibility, int threads)
|
||||
{
|
||||
return build(order, cCompatibility, threads, tw, th);
|
||||
}
|
||||
|
||||
public int getTw() {
|
||||
public int getTw()
|
||||
{
|
||||
return tw;
|
||||
}
|
||||
|
||||
public int getTh() {
|
||||
public int getTh()
|
||||
{
|
||||
return th;
|
||||
}
|
||||
|
||||
protected override List<byte[]> build(int tx, int ty, ByteOrder order, bool cCompatibility) {
|
||||
protected override List<byte[]> build(int tx, int ty, ByteOrder order, bool cCompatibility)
|
||||
{
|
||||
HeightfieldLayerSet lset = getHeightfieldSet(tx, ty);
|
||||
List<byte[]> result = new();
|
||||
if (lset != null) {
|
||||
if (lset != null)
|
||||
{
|
||||
TileCacheBuilder builder = new TileCacheBuilder();
|
||||
for (int i = 0; i < lset.layers.Length; ++i) {
|
||||
for (int i = 0; i < lset.layers.Length; ++i)
|
||||
{
|
||||
HeightfieldLayerSet.HeightfieldLayer layer = lset.layers[i];
|
||||
|
||||
// Store header
|
||||
|
@ -107,10 +113,12 @@ public class TestTileLayerBuilder : AbstractTileLayersBuilder {
|
|||
result.Add(builder.compressTileCacheLayer(header, layer.heights, layer.areas, layer.cons, order, cCompatibility));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected HeightfieldLayerSet getHeightfieldSet(int tx, int ty) {
|
||||
protected HeightfieldLayerSet getHeightfieldSet(int tx, int ty)
|
||||
{
|
||||
RecastBuilder rcBuilder = new RecastBuilder();
|
||||
float[] bmin = geom.getMeshBoundsMin();
|
||||
float[] bmax = geom.getMeshBoundsMax();
|
||||
|
|
|
@ -26,14 +26,15 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.TileCache.Test;
|
||||
|
||||
public class TileCacheFindPathTest : AbstractTileCacheTest {
|
||||
|
||||
public class TileCacheFindPathTest : AbstractTileCacheTest
|
||||
{
|
||||
private readonly float[] start = { 39.44734f, 9.998177f, -0.784811f };
|
||||
private readonly float[] end = { 19.292645f, 11.611748f, -57.750366f };
|
||||
private readonly NavMesh navmesh;
|
||||
private readonly NavMeshQuery query;
|
||||
|
||||
public TileCacheFindPathTest() {
|
||||
public TileCacheFindPathTest()
|
||||
{
|
||||
using var msis = new MemoryStream(Loader.ToBytes("dungeon_all_tiles_tilecache.bin"));
|
||||
using var @is = new BinaryReader(msis);
|
||||
TileCache tcC = new TileCacheReader().read(@is, 6, new TestTileCacheMeshProcess());
|
||||
|
@ -42,7 +43,8 @@ public class TileCacheFindPathTest : AbstractTileCacheTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testFindPath() {
|
||||
public void testFindPath()
|
||||
{
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
float[] extents = new float[] { 2f, 4f, 2f };
|
||||
Result<FindNearestPolyResult> findPolyStart = query.findNearestPoly(start, extents, filter);
|
||||
|
@ -58,5 +60,4 @@ public class TileCacheFindPathTest : AbstractTileCacheTest {
|
|||
options);
|
||||
Assert.That(pathStr.result.Count, Is.EqualTo(8));
|
||||
}
|
||||
|
||||
}
|
|
@ -26,50 +26,64 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.TileCache.Test;
|
||||
|
||||
public class TileCacheNavigationTest : AbstractTileCacheTest {
|
||||
|
||||
public class TileCacheNavigationTest : AbstractTileCacheTest
|
||||
{
|
||||
protected readonly long[] startRefs = { 281475006070787L };
|
||||
protected readonly long[] endRefs = { 281474986147841L };
|
||||
protected readonly float[][] startPoss = { new[] { 39.447338f, 9.998177f, -0.784811f } };
|
||||
protected readonly float[][] endPoss = { new[] { 19.292645f, 11.611748f, -57.750366f } };
|
||||
private readonly Status[] statuses = { Status.SUCCSESS };
|
||||
private readonly long[][] results = { new[] { 281475006070787L, 281475006070785L, 281475005022208L, 281475005022209L, 281475003973633L,
|
||||
|
||||
private readonly long[][] results =
|
||||
{
|
||||
new[]
|
||||
{
|
||||
281475006070787L, 281475006070785L, 281475005022208L, 281475005022209L, 281475003973633L,
|
||||
281475003973634L, 281475003973632L, 281474996633604L, 281474996633605L, 281474996633603L, 281474995585027L,
|
||||
281474995585029L, 281474995585026L, 281474995585028L, 281474995585024L, 281474991390721L, 281474991390722L,
|
||||
281474991390725L, 281474991390720L, 281474987196418L, 281474987196417L, 281474988244995L, 281474988245001L,
|
||||
281474988244997L, 281474988244998L, 281474988245002L, 281474988245000L, 281474988244999L, 281474988244994L,
|
||||
281474985099264L, 281474985099266L, 281474986147841L } };
|
||||
281474985099264L, 281474985099266L, 281474986147841L
|
||||
}
|
||||
};
|
||||
|
||||
protected NavMesh navmesh;
|
||||
protected NavMeshQuery query;
|
||||
|
||||
[SetUp]
|
||||
public void setUp() {
|
||||
|
||||
public void setUp()
|
||||
{
|
||||
bool cCompatibility = true;
|
||||
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
|
||||
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
|
||||
List<byte[]> layers = layerBuilder.build(ByteOrder.LITTLE_ENDIAN, cCompatibility, 1);
|
||||
TileCache tc = getTileCache(geom, ByteOrder.LITTLE_ENDIAN, cCompatibility);
|
||||
foreach (byte[] data in layers) {
|
||||
foreach (byte[] data in layers)
|
||||
{
|
||||
tc.addTile(data, 0);
|
||||
}
|
||||
for (int y = 0; y < layerBuilder.getTh(); ++y) {
|
||||
for (int x = 0; x < layerBuilder.getTw(); ++x) {
|
||||
foreach (long refs in tc.getTilesAt(x, y)) {
|
||||
|
||||
for (int y = 0; y < layerBuilder.getTh(); ++y)
|
||||
{
|
||||
for (int x = 0; x < layerBuilder.getTw(); ++x)
|
||||
{
|
||||
foreach (long refs in tc.getTilesAt(x, y))
|
||||
{
|
||||
tc.buildNavMeshTile(refs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
navmesh = tc.getNavMesh();
|
||||
query = new NavMeshQuery(navmesh);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void testFindPathWithDefaultHeuristic() {
|
||||
public void testFindPathWithDefaultHeuristic()
|
||||
{
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
for (int i = 0; i < startRefs.Length; i++) {
|
||||
for (int i = 0; i < startRefs.Length; i++)
|
||||
{
|
||||
long startRef = startRefs[i];
|
||||
long endRef = endRefs[i];
|
||||
float[] startPos = startPoss[i];
|
||||
|
@ -77,16 +91,19 @@ public class TileCacheNavigationTest : AbstractTileCacheTest {
|
|||
Result<List<long>> path = query.findPath(startRef, endRef, startPos, endPos, filter);
|
||||
Assert.That(path.status, Is.EqualTo(statuses[i]));
|
||||
Assert.That(path.result.Count, Is.EqualTo(results[i].Length));
|
||||
for (int j = 0; j < results[i].Length; j++) {
|
||||
for (int j = 0; j < results[i].Length; j++)
|
||||
{
|
||||
Assert.That(path.result[j], Is.EqualTo(results[i][j])); // TODO : 확인 필요
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void testFindPathWithNoHeuristic() {
|
||||
public void testFindPathWithNoHeuristic()
|
||||
{
|
||||
QueryFilter filter = new DefaultQueryFilter();
|
||||
for (int i = 0; i < startRefs.Length; i++) {
|
||||
for (int i = 0; i < startRefs.Length; i++)
|
||||
{
|
||||
long startRef = startRefs[i];
|
||||
long endRef = endRefs[i];
|
||||
float[] startPos = startPoss[i];
|
||||
|
@ -95,10 +112,10 @@ public class TileCacheNavigationTest : AbstractTileCacheTest {
|
|||
0, 0);
|
||||
Assert.That(path.status, Is.EqualTo(statuses[i]));
|
||||
Assert.That(path.result.Count, Is.EqualTo(results[i].Length));
|
||||
for (int j = 0; j < results[i].Length; j++) {
|
||||
for (int j = 0; j < results[i].Length; j++)
|
||||
{
|
||||
Assert.That(path.result[j], Is.EqualTo(results[i][j])); // TODO : 확인 필요
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -28,10 +28,11 @@ using NUnit.Framework;
|
|||
|
||||
namespace DotRecast.Detour.TileCache.Test;
|
||||
|
||||
public class TileCacheTest : AbstractTileCacheTest {
|
||||
|
||||
public class TileCacheTest : AbstractTileCacheTest
|
||||
{
|
||||
[Test]
|
||||
public void testFastLz() {
|
||||
public void testFastLz()
|
||||
{
|
||||
testDungeon(ByteOrder.LITTLE_ENDIAN, false);
|
||||
testDungeon(ByteOrder.LITTLE_ENDIAN, true);
|
||||
testDungeon(ByteOrder.BIG_ENDIAN, false);
|
||||
|
@ -43,7 +44,8 @@ public class TileCacheTest : AbstractTileCacheTest {
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void testLZ4() {
|
||||
public void testLZ4()
|
||||
{
|
||||
testDungeon(ByteOrder.LITTLE_ENDIAN, false);
|
||||
testDungeon(ByteOrder.LITTLE_ENDIAN, true);
|
||||
testDungeon(ByteOrder.BIG_ENDIAN, false);
|
||||
|
@ -54,7 +56,8 @@ public class TileCacheTest : AbstractTileCacheTest {
|
|||
test(ByteOrder.BIG_ENDIAN, true);
|
||||
}
|
||||
|
||||
private void testDungeon(ByteOrder order, bool cCompatibility) {
|
||||
private void testDungeon(ByteOrder order, bool cCompatibility)
|
||||
{
|
||||
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
|
||||
TileCache tc = getTileCache(geom, order, cCompatibility);
|
||||
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
|
||||
|
@ -62,13 +65,15 @@ public class TileCacheTest : AbstractTileCacheTest {
|
|||
int cacheLayerCount = 0;
|
||||
int cacheCompressedSize = 0;
|
||||
int cacheRawSize = 0;
|
||||
foreach (byte[] layer in layers) {
|
||||
foreach (byte[] layer in layers)
|
||||
{
|
||||
long refs = tc.addTile(layer, 0);
|
||||
tc.buildNavMeshTile(refs);
|
||||
cacheLayerCount++;
|
||||
cacheCompressedSize += layer.Length;
|
||||
cacheRawSize += 4 * 48 * 48 + 56; // FIXME
|
||||
}
|
||||
|
||||
Console.WriteLine("Compressor: " + tc.getCompressor().GetType().Name + " C Compatibility: " + cCompatibility
|
||||
+ " Layers: " + cacheLayerCount + " Raw Size: " + cacheRawSize + " Compressed: " + cacheCompressedSize);
|
||||
Assert.That(tc.getNavMesh().getMaxTiles(), Is.EqualTo(256));
|
||||
|
@ -147,7 +152,8 @@ public class TileCacheTest : AbstractTileCacheTest {
|
|||
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 3));
|
||||
}
|
||||
|
||||
private void test(ByteOrder order, bool cCompatibility) {
|
||||
private void test(ByteOrder order, bool cCompatibility)
|
||||
{
|
||||
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("nav_test.obj"));
|
||||
TileCache tc = getTileCache(geom, order, cCompatibility);
|
||||
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
|
||||
|
@ -155,46 +161,56 @@ public class TileCacheTest : AbstractTileCacheTest {
|
|||
int cacheLayerCount = 0;
|
||||
int cacheCompressedSize = 0;
|
||||
int cacheRawSize = 0;
|
||||
foreach (byte[] layer in layers) {
|
||||
foreach (byte[] layer in layers)
|
||||
{
|
||||
long refs = tc.addTile(layer, 0);
|
||||
tc.buildNavMeshTile(refs);
|
||||
cacheLayerCount++;
|
||||
cacheCompressedSize += layer.Length;
|
||||
cacheRawSize += 4 * 48 * 48 + 56;
|
||||
}
|
||||
|
||||
Console.WriteLine("Compressor: " + tc.getCompressor().GetType().Name + " C Compatibility: " + cCompatibility
|
||||
+ " Layers: " + cacheLayerCount + " Raw Size: " + cacheRawSize + " Compressed: " + cacheCompressedSize);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void testPerformance() {
|
||||
public void testPerformance()
|
||||
{
|
||||
int threads = 4;
|
||||
ByteOrder order = ByteOrder.LITTLE_ENDIAN;
|
||||
bool cCompatibility = false;
|
||||
InputGeomProvider geom = ObjImporter.load(Loader.ToBytes("dungeon.obj"));
|
||||
TestTileLayerBuilder layerBuilder = new TestTileLayerBuilder(geom);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
layerBuilder.build(order, cCompatibility, 1);
|
||||
layerBuilder.build(order, cCompatibility, threads);
|
||||
}
|
||||
|
||||
long t1 = Stopwatch.GetTimestamp();
|
||||
List<byte[]> layers = null;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
layers = layerBuilder.build(order, cCompatibility, 1);
|
||||
}
|
||||
|
||||
long t2 = Stopwatch.GetTimestamp();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
layers = layerBuilder.build(order, cCompatibility, threads);
|
||||
}
|
||||
|
||||
long t3 = Stopwatch.GetTimestamp();
|
||||
Console.WriteLine(" Time ST : " + (t2 - t1) / 1000000);
|
||||
Console.WriteLine(" Time MT : " + (t3 - t2) / 1000000);
|
||||
TileCache tc = getTileCache(geom, order, cCompatibility);
|
||||
foreach (byte[] layer in layers) {
|
||||
foreach (byte[] layer in layers)
|
||||
{
|
||||
long refs = tc.addTile(layer, 0);
|
||||
tc.buildNavMeshTile(refs);
|
||||
}
|
||||
|
||||
Assert.That(tc.getNavMesh().getMaxTiles(), Is.EqualTo(256));
|
||||
Assert.That(tc.getNavMesh().getParams().maxPolys, Is.EqualTo(16384));
|
||||
Assert.That(tc.getNavMesh().getParams().tileWidth, Is.EqualTo(14.4f).Within(0.001f));
|
||||
|
@ -269,5 +285,4 @@ public class TileCacheTest : AbstractTileCacheTest {
|
|||
Assert.That(data.detailVerts.Length, Is.EqualTo(0));
|
||||
Assert.That(data.detailTris.Length, Is.EqualTo(4 * 3));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue