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

View File

@ -2,43 +2,41 @@
namespace DotRecast.Core namespace DotRecast.Core
{ {
public static class ArrayUtils
public static class ArrayUtils
{
public static T[] CopyOf<T>(T[] source, int startIdx, int length)
{ {
var deatArr = new T[length]; public static T[] CopyOf<T>(T[] source, int startIdx, int length)
for (int i = 0; i < length; ++i)
{ {
deatArr[i] = source[startIdx + i]; var deatArr = new T[length];
for (int i = 0; i < length; ++i)
{
deatArr[i] = source[startIdx + i];
}
return deatArr;
} }
return deatArr; public static T[] CopyOf<T>(T[] source, int length)
{
var deatArr = new T[length];
var count = Math.Max(0, Math.Min(source.Length, length));
for (int i = 0; i < count; ++i)
{
deatArr[i] = source[i];
}
return deatArr;
}
public static T[][] Of<T>(int len1, int len2)
{
var temp = new T[len1][];
for (int i = 0; i < len1; ++i)
{
temp[i] = new T[len2];
}
return temp;
}
} }
public static T[] CopyOf<T>(T[] source, int length)
{
var deatArr = new T[length];
var count = Math.Max(0, Math.Min(source.Length, length));
for (int i = 0; i < count; ++i)
{
deatArr[i] = source[i];
}
return deatArr;
}
public static T[][] Of<T>(int len1, int len2)
{
var temp = new T[len1][];
for (int i = 0; i < len1; ++i)
{
temp[i] = new T[len2];
}
return temp;
}
}
} }

View File

@ -2,20 +2,18 @@
namespace DotRecast.Core namespace DotRecast.Core
{ {
public class AtomicBoolean
public class AtomicBoolean
{
private volatile int _location;
public bool set(bool v)
{ {
return 0 != Interlocked.Exchange(ref _location, v ? 1 : 0); private volatile int _location;
}
public bool get() public bool set(bool v)
{ {
return 0 != _location; return 0 != Interlocked.Exchange(ref _location, v ? 1 : 0);
}
public bool get()
{
return 0 != _location;
}
} }
}
} }

View File

@ -2,30 +2,28 @@
namespace DotRecast.Core namespace DotRecast.Core
{ {
public class AtomicFloat
public class AtomicFloat
{
private volatile float _location;
public AtomicFloat(float location)
{ {
_location = location; private volatile float _location;
}
public float Get() public AtomicFloat(float location)
{ {
return _location; _location = location;
} }
public float Exchange(float exchange) public float Get()
{ {
return Interlocked.Exchange(ref _location, exchange); return _location;
} }
public float CompareExchange(float value, float comparand) public float Exchange(float exchange)
{ {
return Interlocked.CompareExchange(ref _location, value, comparand); return Interlocked.Exchange(ref _location, exchange);
}
public float CompareExchange(float value, float comparand)
{
return Interlocked.CompareExchange(ref _location, value, comparand);
}
} }
}
} }

View File

@ -2,68 +2,64 @@
namespace DotRecast.Core namespace DotRecast.Core
{ {
public class AtomicInteger
public class AtomicInteger
{
private volatile int _location;
public AtomicInteger() : this(0)
{ {
private volatile int _location;
}
public AtomicInteger(int location) public AtomicInteger() : this(0)
{ {
_location = location; }
}
public int IncrementAndGet() public AtomicInteger(int location)
{ {
return Interlocked.Increment(ref _location); _location = location;
} }
public int GetAndIncrement() public int IncrementAndGet()
{ {
var next = Interlocked.Increment(ref _location); return Interlocked.Increment(ref _location);
return next - 1; }
}
public int GetAndIncrement()
{
var next = Interlocked.Increment(ref _location);
return next - 1;
}
public int DecrementAndGet() public int DecrementAndGet()
{ {
return Interlocked.Decrement(ref _location); return Interlocked.Decrement(ref _location);
} }
public int Read() public int Read()
{ {
return _location; return _location;
} }
public int GetSoft() public int GetSoft()
{ {
return _location; return _location;
} }
public int Exchange(int exchange) public int Exchange(int exchange)
{ {
return Interlocked.Exchange(ref _location, exchange); return Interlocked.Exchange(ref _location, exchange);
} }
public int Decrease(int value) public int Decrease(int value)
{ {
return Interlocked.Add(ref _location, -value); return Interlocked.Add(ref _location, -value);
} }
public int CompareExchange(int value, int comparand) public int CompareExchange(int value, int comparand)
{ {
return Interlocked.CompareExchange(ref _location, value, comparand); return Interlocked.CompareExchange(ref _location, value, comparand);
} }
public int Add(int value) public int Add(int value)
{ {
return Interlocked.Add(ref _location, value); return Interlocked.Add(ref _location, value);
}
} }
}
} }

View File

@ -2,54 +2,52 @@
namespace DotRecast.Core namespace DotRecast.Core
{ {
public class AtomicLong
public class AtomicLong
{
private long _location;
public AtomicLong() : this(0)
{ {
} private long _location;
public AtomicLong(long location)
{
_location = location;
}
public long IncrementAndGet() public AtomicLong() : this(0)
{ {
return Interlocked.Increment(ref _location); }
}
public long DecrementAndGet() public AtomicLong(long location)
{ {
return Interlocked.Decrement(ref _location); _location = location;
} }
public long Read() public long IncrementAndGet()
{ {
return Interlocked.Read(ref _location); return Interlocked.Increment(ref _location);
} }
public long Exchange(long exchange) public long DecrementAndGet()
{ {
return Interlocked.Exchange(ref _location, exchange); return Interlocked.Decrement(ref _location);
} }
public long Decrease(long value) public long Read()
{ {
return Interlocked.Add(ref _location, -value); return Interlocked.Read(ref _location);
} }
public long CompareExchange(long value, long comparand) public long Exchange(long exchange)
{ {
return Interlocked.CompareExchange(ref _location, value, comparand); return Interlocked.Exchange(ref _location, exchange);
} }
public long AddAndGet(long value) public long Decrease(long value)
{ {
return Interlocked.Add(ref _location, value); return Interlocked.Add(ref _location, -value);
}
public long CompareExchange(long value, long comparand)
{
return Interlocked.CompareExchange(ref _location, value, comparand);
}
public long AddAndGet(long value)
{
return Interlocked.Add(ref _location, value);
}
} }
}
} }

View File

@ -3,131 +3,131 @@ using System.Buffers.Binary;
namespace DotRecast.Core namespace DotRecast.Core
{ {
public class ByteBuffer
public class ByteBuffer
{
private ByteOrder _order;
private byte[] _bytes;
private int _position;
public ByteBuffer(byte[] bytes)
{ {
_order = BitConverter.IsLittleEndian private ByteOrder _order;
? ByteOrder.LITTLE_ENDIAN private byte[] _bytes;
: ByteOrder.BIG_ENDIAN; private int _position;
_bytes = bytes; public ByteBuffer(byte[] bytes)
_position = 0;
}
public ByteOrder order()
{
return _order;
}
public void order(ByteOrder order)
{
_order = order;
}
public int limit() {
return _bytes.Length - _position;
}
public int remaining() {
int rem = limit();
return rem > 0 ? rem : 0;
}
public void position(int pos)
{
_position = pos;
}
public int position()
{
return _position;
}
public Span<byte> ReadBytes(int length)
{
var nextPos = _position + length;
(nextPos, _position) = (_position, nextPos);
return _bytes.AsSpan(nextPos, length);
}
public byte get()
{
var span = ReadBytes(1);
return span[0];
}
public short getShort()
{
var span = ReadBytes(2);
if (_order == ByteOrder.BIG_ENDIAN)
{ {
return BinaryPrimitives.ReadInt16BigEndian(span); _order = BitConverter.IsLittleEndian
} ? ByteOrder.LITTLE_ENDIAN
else : ByteOrder.BIG_ENDIAN;
{
return BinaryPrimitives.ReadInt16LittleEndian(span);
}
}
_bytes = bytes;
public int getInt() _position = 0;
{
var span = ReadBytes(4);
if (_order == ByteOrder.BIG_ENDIAN)
{
return BinaryPrimitives.ReadInt32BigEndian(span);
}
else
{
return BinaryPrimitives.ReadInt32LittleEndian(span);
}
}
public float getFloat()
{
var span = ReadBytes(4);
if (_order == ByteOrder.BIG_ENDIAN && BitConverter.IsLittleEndian)
{
span.Reverse();
}
else if (_order == ByteOrder.LITTLE_ENDIAN && !BitConverter.IsLittleEndian)
{
span.Reverse();
} }
return BitConverter.ToSingle(span); public ByteOrder order()
}
public long getLong()
{
var span = ReadBytes(8);
if (_order == ByteOrder.BIG_ENDIAN)
{ {
return BinaryPrimitives.ReadInt64BigEndian(span); return _order;
} }
else
public void order(ByteOrder order)
{ {
return BinaryPrimitives.ReadInt64LittleEndian(span); _order = order;
}
public int limit()
{
return _bytes.Length - _position;
}
public int remaining()
{
int rem = limit();
return rem > 0 ? rem : 0;
}
public void position(int pos)
{
_position = pos;
}
public int position()
{
return _position;
}
public Span<byte> ReadBytes(int length)
{
var nextPos = _position + length;
(nextPos, _position) = (_position, nextPos);
return _bytes.AsSpan(nextPos, length);
}
public byte get()
{
var span = ReadBytes(1);
return span[0];
}
public short getShort()
{
var span = ReadBytes(2);
if (_order == ByteOrder.BIG_ENDIAN)
{
return BinaryPrimitives.ReadInt16BigEndian(span);
}
else
{
return BinaryPrimitives.ReadInt16LittleEndian(span);
}
}
public int getInt()
{
var span = ReadBytes(4);
if (_order == ByteOrder.BIG_ENDIAN)
{
return BinaryPrimitives.ReadInt32BigEndian(span);
}
else
{
return BinaryPrimitives.ReadInt32LittleEndian(span);
}
}
public float getFloat()
{
var span = ReadBytes(4);
if (_order == ByteOrder.BIG_ENDIAN && BitConverter.IsLittleEndian)
{
span.Reverse();
}
else if (_order == ByteOrder.LITTLE_ENDIAN && !BitConverter.IsLittleEndian)
{
span.Reverse();
}
return BitConverter.ToSingle(span);
}
public long getLong()
{
var span = ReadBytes(8);
if (_order == ByteOrder.BIG_ENDIAN)
{
return BinaryPrimitives.ReadInt64BigEndian(span);
}
else
{
return BinaryPrimitives.ReadInt64LittleEndian(span);
}
}
public void putFloat(float v)
{
// ?
}
public void putInt(int v)
{
// ?
} }
} }
public void putFloat(float v)
{
// ?
}
public void putInt(int v)
{
// ?
}
}
} }

View File

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

View File

@ -3,30 +3,28 @@ using System.Collections.Generic;
namespace DotRecast.Core namespace DotRecast.Core
{ {
public static class CollectionExtensions
public static class CollectionExtensions
{
public static void forEach<T>(this IEnumerable<T> collection, Action<T> action)
{ {
foreach (var item in collection) public static void forEach<T>(this IEnumerable<T> collection, Action<T> action)
{ {
action.Invoke(item); foreach (var item in collection)
{
action.Invoke(item);
}
}
public static void Shuffle<T>(this IList<T> list)
{
Random random = new Random();
int n = list.Count;
while (n > 1)
{
n--;
int k = random.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
} }
} }
public static void Shuffle<T>(this IList<T> list)
{
Random random = new Random();
int n = list.Count;
while (n > 1)
{
n--;
int k = random.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
}
} }

View File

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

View File

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

View File

@ -3,10 +3,10 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Collections.Immutable" Version="7.0.0" /> <PackageReference Include="System.Collections.Immutable" Version="7.0.0"/>
<PackageReference Include="System.Text.Json" Version="7.0.2" /> <PackageReference Include="System.Text.Json" Version="7.0.2"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -2,34 +2,32 @@
namespace DotRecast.Core namespace DotRecast.Core
{ {
public static class Loader
public static class Loader
{
public static byte[] ToBytes(string filename)
{ {
var filepath = ToRPath(filename); public static byte[] ToBytes(string filename)
using var fs = new FileStream(filepath, FileMode.Open);
byte[] buffer = new byte[fs.Length];
fs.Read(buffer, 0, buffer.Length);
return buffer;
}
public static string ToRPath(string filename)
{
string filePath = Path.Combine("resources", filename);
for (int i = 0; i < 10; ++i)
{ {
if (File.Exists(filePath)) var filepath = ToRPath(filename);
{ using var fs = new FileStream(filepath, FileMode.Open);
return Path.GetFullPath(filePath); byte[] buffer = new byte[fs.Length];
} fs.Read(buffer, 0, buffer.Length);
filePath = Path.Combine("..", filePath); return buffer;
} }
return filename; public static string ToRPath(string filename)
{
string filePath = Path.Combine("resources", filename);
for (int i = 0; i < 10; ++i)
{
if (File.Exists(filePath))
{
return Path.GetFullPath(filePath);
}
filePath = Path.Combine("..", filePath);
}
return filename;
}
} }
}
} }

View File

@ -22,56 +22,55 @@ using System;
namespace DotRecast.Core namespace DotRecast.Core
{ {
using System.Collections.Generic;
public class OrderedQueue<T>
using System.Collections.Generic;
public class OrderedQueue<T>
{
private readonly List<T> _items;
private readonly Comparison<T> _comparison;
public OrderedQueue(Comparison<T> comparison)
{ {
_items = new List<T>(); private readonly List<T> _items;
_comparison = comparison; private readonly Comparison<T> _comparison;
}
public int count() public OrderedQueue(Comparison<T> comparison)
{ {
return _items.Count; _items = new List<T>();
} _comparison = comparison;
}
public void clear() { public int count()
_items.Clear(); {
} return _items.Count;
}
public T top() public void clear()
{ {
return _items[0]; _items.Clear();
} }
public T Dequeue() public T top()
{ {
var node = top(); return _items[0];
_items.Remove(node); }
return node;
}
public void Enqueue(T item) { public T Dequeue()
_items.Add(item); {
_items.Sort(_comparison); var node = top();
} _items.Remove(node);
return node;
}
public void Remove(T item) { public void Enqueue(T item)
_items.Remove(item); {
} _items.Add(item);
_items.Sort(_comparison);
}
public bool isEmpty() public void Remove(T item)
{ {
return 0 == _items.Count; _items.Remove(item);
} }
}
public bool isEmpty()
{
return 0 == _items.Count;
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -23,173 +23,221 @@ using System.Collections.Generic;
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {
using static DetourCommon;
/// Represents an agent managed by a #dtCrowd object.
using static DetourCommon;
/// Represents an agent managed by a #dtCrowd object.
/// @ingroup crowd
public class CrowdAgent {
/// The type of navigation mesh polygon the agent is currently traversing.
/// @ingroup crowd /// @ingroup crowd
public enum CrowdAgentState { public class CrowdAgent
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. /// The type of navigation mesh polygon the agent is currently traversing.
DT_CROWDAGENT_STATE_OFFMESH, /// < The agent is traversing an off-mesh connection. /// @ingroup crowd
}; public enum CrowdAgentState
{
DT_CROWDAGENT_STATE_INVALID,
public enum MoveRequestState { /// < The agent is not in a valid state.
DT_CROWDAGENT_TARGET_NONE, DT_CROWDAGENT_STATE_WALKING,
DT_CROWDAGENT_TARGET_FAILED,
DT_CROWDAGENT_TARGET_VALID,
DT_CROWDAGENT_TARGET_REQUESTING,
DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE,
DT_CROWDAGENT_TARGET_WAITING_FOR_PATH,
DT_CROWDAGENT_TARGET_VELOCITY,
};
public readonly long idx; /// < The agent is traversing a normal navigation mesh polygon.
/// The type of mesh polygon the agent is traversing. (See: #CrowdAgentState) DT_CROWDAGENT_STATE_OFFMESH, /// < The agent is traversing an off-mesh connection.
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 enum MoveRequestState
public float[] disp = new float[3]; /// < A temporary value used to accumulate agent displacement during iterative {
/// collision resolution. [(x, y, z)] DT_CROWDAGENT_TARGET_NONE,
public float[] dvel = new float[3]; /// < The desired velocity of the agent. Based on the current path, calculated DT_CROWDAGENT_TARGET_FAILED,
/// from DT_CROWDAGENT_TARGET_VALID,
/// scratch each frame. [(x, y, z)] DT_CROWDAGENT_TARGET_REQUESTING,
public float[] nvel = new float[3]; /// < The desired velocity adjusted by obstacle avoidance, calculated from scratch each DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE,
/// frame. [(x, y, z)] DT_CROWDAGENT_TARGET_WAITING_FOR_PATH,
public float[] vel = new float[3]; /// < The actual velocity of the agent. The change from nvel -> vel is DT_CROWDAGENT_TARGET_VELOCITY,
/// constrained by max acceleration. [(x, y, z)] };
/// The agent's configuration parameters. public readonly long idx;
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. /// The type of mesh polygon the agent is traversing. (See: #CrowdAgentState)
public long targetRef; /// < Target polyref of the movement request. public CrowdAgentState state;
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 float targetReplanWaitTime;
public CrowdAgentAnimation animation; /// 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;
public CrowdAgent(int idx) { /// The path corridor the agent is using.
this.idx = idx; public PathCorridor corridor;
corridor = new PathCorridor();
boundary = new LocalBoundary();
animation = new CrowdAgentAnimation();
}
public void integrate(float dt) { /// The local boundary data for the agent.
// Fake dynamic constraint. public LocalBoundary boundary;
float maxDelta = option.maxAcceleration * dt;
float[] dv = vSub(nvel, vel);
float ds = vLen(dv);
if (ds > maxDelta)
dv = vScale(dv, maxDelta / ds);
vel = vAdd(vel, dv);
// Integrate /// Time since the agent's path corridor was optimized.
if (vLen(vel) > 0.0001f) public float topologyOptTime;
npos = vMad(npos, vel, dt);
else /// The known neighbors of the agent.
vSet(vel, 0, 0, 0); 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
/// collision resolution. [(x, y, z)]
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)]
/// 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
/// 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 float targetReplanWaitTime;
public CrowdAgentAnimation animation;
public CrowdAgent(int idx)
{
this.idx = idx;
corridor = new PathCorridor();
boundary = new LocalBoundary();
animation = new CrowdAgentAnimation();
}
public void integrate(float dt)
{
// Fake dynamic constraint.
float maxDelta = option.maxAcceleration * dt;
float[] dv = vSub(nvel, vel);
float ds = vLen(dv);
if (ds > maxDelta)
dv = vScale(dv, maxDelta / ds);
vel = vAdd(vel, dv);
// Integrate
if (vLen(vel) > 0.0001f)
npos = vMad(npos, vel, dt);
else
vSet(vel, 0, 0, 0);
}
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)
{
float distSq = vDist2DSqr(npos, corners[corners.Count - 1].getPos());
if (distSq < radius * radius)
return true;
}
public bool overOffmeshConnection(float radius) {
if (0 == corners.Count)
return false; return false;
bool offMeshConnection = ((corners[corners.Count - 1].getFlags()
& 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;
} }
return false; public float getDistanceToGoal(float range)
} {
if (0 == corners.Count)
return range;
bool endOfPath = ((corners[corners.Count - 1].getFlags() & NavMeshQuery.DT_STRAIGHTPATH_END) != 0) ? true : false;
if (endOfPath)
return Math.Min(vDist2D(npos, corners[corners.Count - 1].getPos()), range);
public float getDistanceToGoal(float range) {
if (0 == corners.Count)
return range; return range;
bool endOfPath = ((corners[corners.Count - 1].getFlags() & NavMeshQuery.DT_STRAIGHTPATH_END) != 0) ? true : false;
if (endOfPath)
return Math.Min(vDist2D(npos, corners[corners.Count - 1].getPos()), range);
return range;
}
public float[] calcSmoothSteerDirection() {
float[] dir = new float[3];
if (0 < corners.Count) {
int ip0 = 0;
int ip1 = Math.Min(1, corners.Count - 1);
float[] p0 = corners[ip0].getPos();
float[] p1 = corners[ip1].getPos();
float[] dir0 = vSub(p0, npos);
float[] dir1 = vSub(p1, npos);
dir0[1] = 0;
dir1[1] = 0;
float len0 = vLen(dir0);
float len1 = vLen(dir1);
if (len1 > 0.001f)
dir1 = vScale(dir1, 1.0f / len1);
dir[0] = dir0[0] - dir1[0] * len0 * 0.5f;
dir[1] = 0;
dir[2] = dir0[2] - dir1[2] * len0 * 0.5f;
vNormalize(dir);
} }
return dir;
}
public float[] calcStraightSteerDirection() { public float[] calcSmoothSteerDirection()
float[] dir = new float[3]; {
if (0 < corners.Count) { float[] dir = new float[3];
dir = vSub(corners[0].getPos(), npos); if (0 < corners.Count)
dir[1] = 0; {
vNormalize(dir); int ip0 = 0;
int ip1 = Math.Min(1, corners.Count - 1);
float[] p0 = corners[ip0].getPos();
float[] p1 = corners[ip1].getPos();
float[] dir0 = vSub(p0, npos);
float[] dir1 = vSub(p1, npos);
dir0[1] = 0;
dir1[1] = 0;
float len0 = vLen(dir0);
float len1 = vLen(dir1);
if (len1 > 0.001f)
dir1 = vScale(dir1, 1.0f / len1);
dir[0] = dir0[0] - dir1[0] * len0 * 0.5f;
dir[1] = 0;
dir[2] = dir0[2] - dir1[2] * len0 * 0.5f;
vNormalize(dir);
}
return dir;
} }
return dir;
}
public void setTarget(long refs, float[] pos) { public float[] calcStraightSteerDirection()
targetRef = refs; {
vCopy(targetPos, pos); float[] dir = new float[3];
targetPathQueryResult = null; if (0 < corners.Count)
if (targetRef != 0) { {
targetState = MoveRequestState.DT_CROWDAGENT_TARGET_REQUESTING; dir = vSub(corners[0].getPos(), npos);
} else { dir[1] = 0;
targetState = MoveRequestState.DT_CROWDAGENT_TARGET_FAILED; vNormalize(dir);
}
return dir;
}
public void setTarget(long refs, float[] pos)
{
targetRef = refs;
vCopy(targetPos, pos);
targetPathQueryResult = null;
if (targetRef != 0)
{
targetState = MoveRequestState.DT_CROWDAGENT_TARGET_REQUESTING;
}
else
{
targetState = MoveRequestState.DT_CROWDAGENT_TARGET_FAILED;
}
} }
} }
}
} }

View File

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

View File

@ -22,44 +22,55 @@ using System;
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {
/// Configuration parameters for a crowd agent.
/// @ingroup crowd
public class CrowdAgentParams
{
public float radius;
/// < Agent radius. [Limit: >= 0]
public float height;
/// Configuration parameters for a crowd agent. /// < Agent height. [Limit: > 0]
/// @ingroup crowd public float maxAcceleration;
public class CrowdAgentParams {
public float radius; /// < Agent radius. [Limit: >= 0]
public float height; /// < Agent height. [Limit: > 0]
public float maxAcceleration; /// < Maximum allowed acceleration. [Limit: >= 0]
public float maxSpeed; /// < Maximum allowed speed. [Limit: >= 0]
/// Defines how close a collision element must be before it is considered for steering behaviors. [Limits: > 0] /// < Maximum allowed acceleration. [Limit: >= 0]
public float collisionQueryRange; public float maxSpeed;
public float pathOptimizationRange; /// < The path visibility optimization range. [Limit: > 0] /// < 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;
/// How aggresive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0] public float pathOptimizationRange;
public float separationWeight;
/// Crowd agent update flags. /// < The path visibility optimization range. [Limit: > 0]
public const int DT_CROWD_ANTICIPATE_TURNS = 1; /// How aggresive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0]
public const int DT_CROWD_OBSTACLE_AVOIDANCE = 2; public float separationWeight;
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.
/// Flags that impact steering behavior. (See: #UpdateFlags) /// Crowd agent update flags.
public int updateFlags; public const int DT_CROWD_ANTICIPATE_TURNS = 1;
/// The index of the avoidance configuration to use for the agent. public const int DT_CROWD_OBSTACLE_AVOIDANCE = 2;
/// [Limits: 0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS] public const int DT_CROWD_SEPARATION = 4;
public int obstacleAvoidanceType; public const int DT_CROWD_OPTIMIZE_VIS = 8;
/// The index of the query filter used by this agent. /// < Use #dtPathCorridor::optimizePathVisibility() to optimize
public int queryFilterType; /// the agent path.
public const int DT_CROWD_OPTIMIZE_TOPO = 16;
/// User defined data attached to the agent. /// < Use dtPathCorridor::optimizePathTopology() to optimize
public object userData; /// the agent path.
} /// Flags that impact steering behavior. (See: #UpdateFlags)
public int updateFlags;
/// The index of the avoidance configuration to use for the agent.
/// [Limits: 0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
public int obstacleAvoidanceType;
/// The index of the query filter used by this agent.
public int queryFilterType;
/// User defined data attached to the agent.
public object userData;
}
} }

View File

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

View File

@ -23,61 +23,67 @@ using System.Linq;
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {
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 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() {
return _maxTimeToEnqueueRequest;
}
public float maxTimeToFindPath() {
return _maxTimeToFindPath;
}
public Dictionary<string, long> executionTimings() {
return _executionTimings;
}
public void start() {
_maxTimeToEnqueueRequest = 0;
_maxTimeToFindPath = 0;
_executionTimings.Clear();
}
public void recordMaxTimeToEnqueueRequest(float time) {
_maxTimeToEnqueueRequest = Math.Max(_maxTimeToEnqueueRequest, time);
}
public void recordMaxTimeToFindPath(float time) {
_maxTimeToFindPath = Math.Max(_maxTimeToFindPath, time);
}
public void start(string name) {
_executionTimings.Add(name, Stopwatch.GetTimestamp());
}
public void stop(string name) {
long duration = Stopwatch.GetTimestamp() - _executionTimings[name];
if (!_executionTimingSamples.TryGetValue(name, out var s))
{ {
s = new List<long>(); return _maxTimeToEnqueueRequest;
_executionTimingSamples.Add(name, s);
} }
if (s.Count == TIMING_SAMPLES) {
s.RemoveAt(0);
}
s.Add(duration);
_executionTimings[name] = (long) s.Average();
}
}
public float maxTimeToFindPath()
{
return _maxTimeToFindPath;
}
public Dictionary<string, long> executionTimings()
{
return _executionTimings;
}
public void start()
{
_maxTimeToEnqueueRequest = 0;
_maxTimeToFindPath = 0;
_executionTimings.Clear();
}
public void recordMaxTimeToEnqueueRequest(float time)
{
_maxTimeToEnqueueRequest = Math.Max(_maxTimeToEnqueueRequest, time);
}
public void recordMaxTimeToFindPath(float time)
{
_maxTimeToFindPath = Math.Max(_maxTimeToFindPath, time);
}
public void start(string name)
{
_executionTimings.Add(name, Stopwatch.GetTimestamp());
}
public void stop(string name)
{
long duration = Stopwatch.GetTimestamp() - _executionTimings[name];
if (!_executionTimingSamples.TryGetValue(name, out var s))
{
s = new List<long>();
_executionTimingSamples.Add(name, s);
}
if (s.Count == TIMING_SAMPLES)
{
s.RemoveAt(0);
}
s.Add(duration);
_executionTimings[name] = (long)s.Average();
}
}
} }

View File

@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup> </PropertyGroup>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" />
</ItemGroup> <ItemGroup>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj"/>
</ItemGroup>
</Project> </Project>

View File

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

View File

@ -23,216 +23,255 @@ using DotRecast.Detour.Crowd.Tracking;
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {
using static DetourCommon;
public class ObstacleAvoidanceQuery
{
public const int DT_MAX_PATTERN_DIVS = 32;
using static DetourCommon; /// < Max numver of adaptive divs.
public const int DT_MAX_PATTERN_RINGS = 4;
public class ObstacleAvoidanceQuery { /// < Max number of adaptive rings.
public class ObstacleCircle
{
/** Position of the obstacle */
public readonly float[] p = new float[3];
public const int DT_MAX_PATTERN_DIVS = 32; /// < Max numver of adaptive divs. /** Velocity of the obstacle */
public const int DT_MAX_PATTERN_RINGS = 4; /// < Max number of adaptive rings. public readonly float[] vel = new float[3];
public class ObstacleCircle { /** Velocity of the obstacle */
/** Position of the obstacle */ public readonly float[] dvel = new float[3];
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 { /** Radius of the obstacle */
/** End points of the obstacle segment */ public float rad;
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 { /** Use for side selection during sampling. */
public float velBias; public readonly float[] dp = new float[3];
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 ObstacleAvoidanceParams() { /** Use for side selection during sampling. */
velBias = 0.4f; public readonly float[] np = new float[3];
weightDesVel = 2.0f;
weightCurVel = 0.75f;
weightSide = 0.75f;
weightToi = 2.5f;
horizTime = 2.5f;
gridSize = 33;
adaptiveDivs = 7;
adaptiveRings = 2;
adaptiveDepth = 5;
} }
public ObstacleAvoidanceParams(ObstacleAvoidanceParams option) { public class ObstacleSegment
velBias = option.velBias; {
weightDesVel = option.weightDesVel; /** End points of the obstacle segment */
weightCurVel = option.weightCurVel; public readonly float[] p = new float[3];
weightSide = option.weightSide;
weightToi = option.weightToi; /** End points of the obstacle segment */
horizTime = option.horizTime; public readonly float[] q = new float[3];
gridSize = option.gridSize;
adaptiveDivs = option.adaptiveDivs; public bool touch;
adaptiveRings = option.adaptiveRings;
adaptiveDepth = option.adaptiveDepth;
} }
};
private ObstacleAvoidanceParams m_params; public class ObstacleAvoidanceParams
private float m_invHorizTime; {
private float m_vmax; public float velBias;
private float m_invVmax; public float weightDesVel;
public float weightCurVel;
public float weightSide;
public float weightToi;
public float horizTime;
public int gridSize;
private readonly int m_maxCircles; /// < grid
private readonly ObstacleCircle[] m_circles; public int adaptiveDivs;
private int m_ncircles;
private readonly int m_maxSegments; /// < adaptive
private readonly ObstacleSegment[] m_segments; public int adaptiveRings;
private int m_nsegments;
public ObstacleAvoidanceQuery(int maxCircles, int maxSegments) { /// < adaptive
m_maxCircles = maxCircles; public int adaptiveDepth;
m_ncircles = 0;
m_circles = new ObstacleCircle[m_maxCircles];
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++) {
m_segments[i] = new ObstacleSegment();
}
}
public void reset() { /// < adaptive
m_ncircles = 0; public ObstacleAvoidanceParams()
m_nsegments = 0; {
} velBias = 0.4f;
weightDesVel = 2.0f;
weightCurVel = 0.75f;
weightSide = 0.75f;
weightToi = 2.5f;
horizTime = 2.5f;
gridSize = 33;
adaptiveDivs = 7;
adaptiveRings = 2;
adaptiveDepth = 5;
}
public void addCircle(float[] pos, float rad, float[] vel, float[] dvel) { public ObstacleAvoidanceParams(ObstacleAvoidanceParams option)
if (m_ncircles >= m_maxCircles) {
return; velBias = option.velBias;
weightDesVel = option.weightDesVel;
weightCurVel = option.weightCurVel;
weightSide = option.weightSide;
weightToi = option.weightToi;
horizTime = option.horizTime;
gridSize = option.gridSize;
adaptiveDivs = option.adaptiveDivs;
adaptiveRings = option.adaptiveRings;
adaptiveDepth = option.adaptiveDepth;
}
};
ObstacleCircle cir = m_circles[m_ncircles++]; private ObstacleAvoidanceParams m_params;
vCopy(cir.p, pos); private float m_invHorizTime;
cir.rad = rad; private float m_vmax;
vCopy(cir.vel, vel); private float m_invVmax;
vCopy(cir.dvel, dvel);
}
public void addSegment(float[] p, float[] q) { private readonly int m_maxCircles;
if (m_nsegments >= m_maxSegments) private readonly ObstacleCircle[] m_circles;
return; private int m_ncircles;
ObstacleSegment seg = m_segments[m_nsegments++];
vCopy(seg.p, p);
vCopy(seg.q, q);
}
public int getObstacleCircleCount() { private readonly int m_maxSegments;
return m_ncircles; private readonly ObstacleSegment[] m_segments;
} private int m_nsegments;
public ObstacleCircle getObstacleCircle(int i) { public ObstacleAvoidanceQuery(int maxCircles, int maxSegments)
return m_circles[i]; {
} m_maxCircles = maxCircles;
m_ncircles = 0;
m_circles = new ObstacleCircle[m_maxCircles];
for (int i = 0; i < m_maxCircles; i++)
{
m_circles[i] = new ObstacleCircle();
}
public int getObstacleSegmentCount() { m_maxSegments = maxSegments;
return m_nsegments; m_nsegments = 0;
} m_segments = new ObstacleSegment[m_maxSegments];
for (int i = 0; i < m_maxSegments; i++)
public ObstacleSegment getObstacleSegment(int i) { {
return m_segments[i]; m_segments[i] = new ObstacleSegment();
}
private void prepare(float[] pos, float[] dvel) {
// Prepare obstacles
for (int i = 0; i < m_ncircles; ++i) {
ObstacleCircle cir = m_circles[i];
// Side
float[] pa = pos;
float[] pb = cir.p;
float[] orig = { 0f, 0f, 0f };
float[] dv = new float[3];
vCopy(cir.dp, vSub(pb, pa));
vNormalize(cir.dp);
dv = vSub(cir.dvel, dvel);
float a = triArea2D(orig, cir.dp, dv);
if (a < 0.01f) {
cir.np[0] = -cir.dp[2];
cir.np[2] = cir.dp[0];
} else {
cir.np[0] = cir.dp[2];
cir.np[2] = -cir.dp[0];
} }
} }
for (int i = 0; i < m_nsegments; ++i) { public void reset()
ObstacleSegment seg = m_segments[i]; {
m_ncircles = 0;
// Precalc if the agent is really close to the segment. m_nsegments = 0;
float r = 0.01f;
Tuple<float, float> dt = distancePtSegSqr2D(pos, seg.p, seg.q);
seg.touch = dt.Item1 < sqr(r);
} }
}
SweepCircleCircleResult sweepCircleCircle(float[] c0, float r0, float[] v, float[] c1, float r1) { public void addCircle(float[] pos, float rad, float[] vel, float[] dvel)
const float EPS = 0.0001f; {
float[] s = vSub(c1, c0); if (m_ncircles >= m_maxCircles)
float r = r0 + r1; return;
float c = vDot2D(s, s) - r * r;
float a = vDot2D(v, v);
if (a < EPS)
return new SweepCircleCircleResult(false, 0f, 0f); // not moving
// Overlap, calc time to exit. ObstacleCircle cir = m_circles[m_ncircles++];
float b = vDot2D(v, s); vCopy(cir.p, pos);
float d = b * b - a * c; cir.rad = rad;
if (d < 0.0f) vCopy(cir.vel, vel);
return new SweepCircleCircleResult(false, 0f, 0f); // no intersection. vCopy(cir.dvel, dvel);
a = 1.0f / a; }
float rd = (float) Math.Sqrt(d);
return new SweepCircleCircleResult(true, (b - rd) * a, (b + rd) * a);
}
Tuple<bool, float> isectRaySeg(float[] ap, float[] u, float[] bp, float[] bq) { public void addSegment(float[] p, float[] q)
float[] v = vSub(bq, bp); {
float[] w = vSub(ap, bp); if (m_nsegments >= m_maxSegments)
float d = vPerp2D(u, v); return;
if (Math.Abs(d) < 1e-6f) ObstacleSegment seg = m_segments[m_nsegments++];
return Tuple.Create(false, 0f); vCopy(seg.p, p);
d = 1.0f / d; vCopy(seg.q, q);
float t = vPerp2D(v, w) * d; }
if (t < 0 || t > 1)
return Tuple.Create(false, 0f);
float s = vPerp2D(u, w) * d;
if (s < 0 || s > 1)
return Tuple.Create(false, 0f);
return Tuple.Create(true, t);
}
/** public int getObstacleCircleCount()
{
return m_ncircles;
}
public ObstacleCircle getObstacleCircle(int i)
{
return m_circles[i];
}
public int getObstacleSegmentCount()
{
return m_nsegments;
}
public ObstacleSegment getObstacleSegment(int i)
{
return m_segments[i];
}
private void prepare(float[] pos, float[] dvel)
{
// Prepare obstacles
for (int i = 0; i < m_ncircles; ++i)
{
ObstacleCircle cir = m_circles[i];
// Side
float[] pa = pos;
float[] pb = cir.p;
float[] orig = { 0f, 0f, 0f };
float[] dv = new float[3];
vCopy(cir.dp, vSub(pb, pa));
vNormalize(cir.dp);
dv = vSub(cir.dvel, dvel);
float a = triArea2D(orig, cir.dp, dv);
if (a < 0.01f)
{
cir.np[0] = -cir.dp[2];
cir.np[2] = cir.dp[0];
}
else
{
cir.np[0] = cir.dp[2];
cir.np[2] = -cir.dp[0];
}
}
for (int i = 0; i < m_nsegments; ++i)
{
ObstacleSegment seg = m_segments[i];
// Precalc if the agent is really close to the segment.
float r = 0.01f;
Tuple<float, float> dt = distancePtSegSqr2D(pos, seg.p, seg.q);
seg.touch = dt.Item1 < sqr(r);
}
}
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;
float c = vDot2D(s, s) - r * r;
float a = vDot2D(v, v);
if (a < EPS)
return new SweepCircleCircleResult(false, 0f, 0f); // not moving
// Overlap, calc time to exit.
float b = vDot2D(v, s);
float d = b * b - a * c;
if (d < 0.0f)
return new SweepCircleCircleResult(false, 0f, 0f); // no intersection.
a = 1.0f / a;
float rd = (float)Math.Sqrt(d);
return new SweepCircleCircleResult(true, (b - rd) * a, (b + rd) * a);
}
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);
if (Math.Abs(d) < 1e-6f)
return Tuple.Create(false, 0f);
d = 1.0f / d;
float t = vPerp2D(v, w) * d;
if (t < 0 || t > 1)
return Tuple.Create(false, 0f);
float s = vPerp2D(u, w) * d;
if (s < 0 || s > 1)
return Tuple.Create(false, 0f);
return Tuple.Create(true, t);
}
/**
* Calculate the collision penalty for a given velocity vector * Calculate the collision penalty for a given velocity vector
* *
* @param vcand * @param vcand
@ -242,272 +281,295 @@ public class ObstacleAvoidanceQuery {
* @param minPenalty * @param minPenalty
* threshold penalty for early out * threshold penalty for early out
*/ */
private float processSample(float[] vcand, float cs, float[] pos, float rad, float[] vel, float[] dvel, private float processSample(float[] vcand, float cs, float[] pos, float rad, float[] vel, float[] dvel,
float minPenalty, ObstacleAvoidanceDebugData debug) { float minPenalty, ObstacleAvoidanceDebugData debug)
// penalty for straying away from the desired and current velocities {
float vpen = m_params.weightDesVel * (vDist2D(vcand, dvel) * m_invVmax); // penalty for straying away from the desired and current velocities
float vcpen = m_params.weightCurVel * (vDist2D(vcand, vel) * m_invVmax); float vpen = m_params.weightDesVel * (vDist2D(vcand, dvel) * m_invVmax);
float vcpen = m_params.weightCurVel * (vDist2D(vcand, vel) * m_invVmax);
// find the threshold hit time to bail out based on the early out penalty // find the threshold hit time to bail out based on the early out penalty
// (see how the penalty is calculated below to understnad) // (see how the penalty is calculated below to understnad)
float minPen = minPenalty - vpen - vcpen; float minPen = minPenalty - vpen - vcpen;
float tThresold = (m_params.weightToi / minPen - 0.1f) * m_params.horizTime; float tThresold = (m_params.weightToi / minPen - 0.1f) * m_params.horizTime;
if (tThresold - m_params.horizTime > -float.MinValue) if (tThresold - m_params.horizTime > -float.MinValue)
return minPenalty; // already too much return minPenalty; // already too much
// Find min time of impact and exit amongst all obstacles. // Find min time of impact and exit amongst all obstacles.
float tmin = m_params.horizTime; float tmin = m_params.horizTime;
float side = 0; float side = 0;
int nside = 0; int nside = 0;
for (int i = 0; i < m_ncircles; ++i) { for (int i = 0; i < m_ncircles; ++i)
ObstacleCircle cir = m_circles[i]; {
ObstacleCircle cir = m_circles[i];
// RVO // RVO
float[] vab = vScale(vcand, 2); float[] vab = vScale(vcand, 2);
vab = vSub(vab, vel); vab = vSub(vab, vel);
vab = vSub(vab, cir.vel); vab = vSub(vab, cir.vel);
// Side // Side
side += clamp(Math.Min(vDot2D(cir.dp, vab) * 0.5f + 0.5f, vDot2D(cir.np, vab) * 2), 0.0f, 1.0f); side += clamp(Math.Min(vDot2D(cir.dp, vab) * 0.5f + 0.5f, vDot2D(cir.np, vab) * 2), 0.0f, 1.0f);
nside++; nside++;
SweepCircleCircleResult sres = sweepCircleCircle(pos, rad, vab, cir.p, cir.rad); SweepCircleCircleResult sres = sweepCircleCircle(pos, rad, vab, cir.p, cir.rad);
if (!sres.intersection) if (!sres.intersection)
continue; continue;
float htmin = sres.htmin, htmax = sres.htmax; float htmin = sres.htmin, htmax = sres.htmax;
// Handle overlapping obstacles. // Handle overlapping obstacles.
if (htmin < 0.0f && htmax > 0.0f) { if (htmin < 0.0f && htmax > 0.0f)
// Avoid more when overlapped. {
htmin = -htmin * 0.5f; // Avoid more when overlapped.
htmin = -htmin * 0.5f;
}
if (htmin >= 0.0f)
{
// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
if (htmin < tmin)
{
tmin = htmin;
if (tmin < tThresold)
return minPenalty;
}
}
} }
if (htmin >= 0.0f) { for (int i = 0; i < m_nsegments; ++i)
{
ObstacleSegment seg = m_segments[i];
float htmin = 0;
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];
snorm[0] = -sdir[2];
snorm[2] = sdir[0];
// If the velocity is pointing towards the segment, no collision.
if (vDot2D(snorm, vcand) < 0.0f)
continue;
// Else immediate collision.
htmin = 0.0f;
}
else
{
Tuple<bool, float> ires = isectRaySeg(pos, vcand, seg.p, seg.q);
if (!ires.Item1)
continue;
htmin = ires.Item2;
}
// Avoid less when facing walls.
htmin *= 2.0f;
// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle. // The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
if (htmin < tmin) { if (htmin < tmin)
{
tmin = htmin; tmin = htmin;
if (tmin < tThresold) if (tmin < tThresold)
return minPenalty; return minPenalty;
} }
} }
// Normalize side bias, to prevent it dominating too much.
if (nside != 0)
side /= nside;
float spen = m_params.weightSide * side;
float tpen = m_params.weightToi * (1.0f / (0.1f + tmin * m_invHorizTime));
float penalty = vpen + vcpen + spen + tpen;
// Store different penalties for debug viewing
if (debug != null)
debug.addSample(vcand, cs, penalty, vpen, vcpen, spen, tpen);
return penalty;
} }
for (int i = 0; i < m_nsegments; ++i) { public Tuple<int, float[]> sampleVelocityGrid(float[] pos, float rad, float vmax, float[] vel, float[] dvel,
ObstacleSegment seg = m_segments[i]; ObstacleAvoidanceParams option, ObstacleAvoidanceDebugData debug)
float htmin = 0; {
prepare(pos, dvel);
m_params = option;
m_invHorizTime = 1.0f / m_params.horizTime;
m_vmax = vmax;
m_invVmax = vmax > 0 ? 1.0f / vmax : float.MaxValue;
if (seg.touch) { float[] nvel = new float[3];
// Special case when the agent is very close to the segment. vSet(nvel, 0f, 0f, 0f);
float[] sdir = vSub(seg.q, seg.p);
float[] snorm = new float[3];
snorm[0] = -sdir[2];
snorm[2] = sdir[0];
// If the velocity is pointing towards the segment, no collision.
if (vDot2D(snorm, vcand) < 0.0f)
continue;
// Else immediate collision.
htmin = 0.0f;
} else {
Tuple<bool, float> ires = isectRaySeg(pos, vcand, seg.p, seg.q);
if (!ires.Item1)
continue;
htmin = ires.Item2;
}
// Avoid less when facing walls. if (debug != null)
htmin *= 2.0f; debug.reset();
// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle. float cvx = dvel[0] * m_params.velBias;
if (htmin < tmin) { float cvz = dvel[2] * m_params.velBias;
tmin = htmin; float cs = vmax * 2 * (1 - m_params.velBias) / (m_params.gridSize - 1);
if (tmin < tThresold) float half = (m_params.gridSize - 1) * cs * 0.5f;
return minPenalty;
}
}
// Normalize side bias, to prevent it dominating too much. float minPenalty = float.MaxValue;
if (nside != 0) int ns = 0;
side /= nside;
float spen = m_params.weightSide * side; for (int y = 0; y < m_params.gridSize; ++y)
float tpen = m_params.weightToi * (1.0f / (0.1f + tmin * m_invHorizTime)); {
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);
float penalty = vpen + vcpen + spen + tpen; if (sqr(vcand[0]) + sqr(vcand[2]) > sqr(vmax + cs / 2))
// Store different penalties for debug viewing continue;
if (debug != null)
debug.addSample(vcand, cs, penalty, vpen, vcpen, spen, tpen);
return penalty; float penalty = processSample(vcand, cs, pos, rad, vel, dvel, minPenalty, debug);
} ns++;
if (penalty < minPenalty)
public Tuple<int, float[]> sampleVelocityGrid(float[] pos, float rad, float vmax, float[] vel, float[] dvel, {
ObstacleAvoidanceParams option, ObstacleAvoidanceDebugData debug) { minPenalty = penalty;
prepare(pos, dvel); vCopy(nvel, vcand);
m_params = option; }
m_invHorizTime = 1.0f / m_params.horizTime;
m_vmax = vmax;
m_invVmax = vmax > 0 ? 1.0f / vmax : float.MaxValue;
float[] nvel = new float[3];
vSet(nvel, 0f, 0f, 0f);
if (debug != null)
debug.reset();
float cvx = dvel[0] * m_params.velBias;
float cvz = dvel[2] * m_params.velBias;
float cs = vmax * 2 * (1 - m_params.velBias) / (m_params.gridSize - 1);
float half = (m_params.gridSize - 1) * cs * 0.5f;
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) {
float[] vcand = new float[3];
vSet(vcand, cvx + x * cs - half, 0f, cvz + y * cs - half);
if (sqr(vcand[0]) + sqr(vcand[2]) > sqr(vmax + cs / 2))
continue;
float penalty = processSample(vcand, cs, pos, rad, vel, dvel, minPenalty, debug);
ns++;
if (penalty < minPenalty) {
minPenalty = penalty;
vCopy(nvel, vcand);
} }
} }
return Tuple.Create(ns, nvel);
} }
return Tuple.Create(ns, nvel); // vector normalization that ignores the y-component.
} void dtNormalize2D(float[] v)
{
float d = (float)Math.Sqrt(v[0] * v[0] + v[2] * v[2]);
if (d == 0)
return;
d = 1.0f / d;
v[0] *= d;
v[2] *= d;
}
// vector normalization that ignores the y-component. // vector normalization that ignores the y-component.
void dtNormalize2D(float[] v) { float[] dtRotate2D(float[] v, float ang)
float d = (float) Math.Sqrt(v[0] * v[0] + v[2] * v[2]); {
if (d == 0) float[] dest = new float[3];
return; float c = (float)Math.Cos(ang);
d = 1.0f / d; float s = (float)Math.Sin(ang);
v[0] *= d; dest[0] = v[0] * c - v[2] * s;
v[2] *= d; dest[2] = v[0] * s + v[2] * c;
} dest[1] = v[1];
return dest;
}
// vector normalization that ignores the y-component. static readonly float DT_PI = 3.14159265f;
float[] dtRotate2D(float[] v, float ang) {
float[] dest = new float[3];
float c = (float) Math.Cos(ang);
float s = (float) Math.Sin(ang);
dest[0] = v[0] * c - v[2] * s;
dest[2] = v[0] * s + v[2] * c;
dest[1] = v[1];
return dest;
}
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)
{
prepare(pos, dvel);
m_params = option;
m_invHorizTime = 1.0f / m_params.horizTime;
m_vmax = vmax;
m_invVmax = vmax > 0 ? 1.0f / vmax : float.MaxValue;
public Tuple<int, float[]> sampleVelocityAdaptive(float[] pos, float rad, float vmax, float[] vel, float[] nvel = new float[3];
float[] dvel, ObstacleAvoidanceParams option, ObstacleAvoidanceDebugData debug) { vSet(nvel, 0f, 0f, 0f);
prepare(pos, dvel);
m_params = option;
m_invHorizTime = 1.0f / m_params.horizTime;
m_vmax = vmax;
m_invVmax = vmax > 0 ? 1.0f / vmax : float.MaxValue;
float[] nvel = new float[3]; if (debug != null)
vSet(nvel, 0f, 0f, 0f); debug.reset();
if (debug != null) // Build sampling pattern aligned to desired velocity.
debug.reset(); float[] pat = new float[(DT_MAX_PATTERN_DIVS * DT_MAX_PATTERN_RINGS + 1) * 2];
int npat = 0;
// Build sampling pattern aligned to desired velocity. int ndivs = m_params.adaptiveDivs;
float[] pat = new float[(DT_MAX_PATTERN_DIVS * DT_MAX_PATTERN_RINGS + 1) * 2]; int nrings = m_params.adaptiveRings;
int npat = 0; int depth = m_params.adaptiveDepth;
int ndivs = m_params.adaptiveDivs; int nd = clamp(ndivs, 1, DT_MAX_PATTERN_DIVS);
int nrings = m_params.adaptiveRings; int nr = clamp(nrings, 1, DT_MAX_PATTERN_RINGS);
int depth = m_params.adaptiveDepth; float da = (1.0f / nd) * DT_PI * 2;
float ca = (float)Math.Cos(da);
float sa = (float)Math.Sin(da);
int nd = clamp(ndivs, 1, DT_MAX_PATTERN_DIVS); // desired direction
int nr = clamp(nrings, 1, DT_MAX_PATTERN_RINGS); float[] ddir = new float[6];
float da = (1.0f / nd) * DT_PI * 2; vCopy(ddir, dvel);
float ca = (float) Math.Cos(da); dtNormalize2D(ddir);
float sa = (float) Math.Sin(da); float[] rotated = dtRotate2D(ddir, da * 0.5f); // rotated by da/2
ddir[3] = rotated[0];
ddir[4] = rotated[1];
ddir[5] = rotated[2];
// desired direction // Always add sample at zero
float[] ddir = new float[6]; pat[npat * 2 + 0] = 0;
vCopy(ddir, dvel); pat[npat * 2 + 1] = 0;
dtNormalize2D(ddir);
float[] rotated = dtRotate2D(ddir, da * 0.5f); // rotated by da/2
ddir[3] = rotated[0];
ddir[4] = rotated[1];
ddir[5] = rotated[2];
// Always add sample at zero
pat[npat * 2 + 0] = 0;
pat[npat * 2 + 1] = 0;
npat++;
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;
int last1 = npat * 2;
int last2 = last1;
npat++; npat++;
for (int i = 1; i < nd - 1; i += 2) { for (int j = 0; j < nr; ++j)
// get next point on the "right" (rotate CW) {
pat[npat * 2 + 0] = pat[last1] * ca + pat[last1 + 1] * sa; float r = (float)(nr - j) / (float)nr;
pat[npat * 2 + 1] = -pat[last1] * sa + pat[last1 + 1] * ca; pat[npat * 2 + 0] = ddir[(j % 2) * 3] * r;
// get next point on the "left" (rotate CCW) pat[npat * 2 + 1] = ddir[(j % 2) * 3 + 2] * r;
pat[npat * 2 + 2] = pat[last2] * ca - pat[last2 + 1] * sa; int last1 = npat * 2;
pat[npat * 2 + 3] = pat[last2] * sa + pat[last2 + 1] * ca; int last2 = last1;
last1 = npat * 2;
last2 = last1 + 2;
npat += 2;
}
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++; npat++;
}
}
// Start sampling. for (int i = 1; i < nd - 1; i += 2)
float cr = vmax * (1.0f - m_params.velBias); {
float[] res = new float[3]; // get next point on the "right" (rotate CW)
vSet(res, dvel[0] * m_params.velBias, 0, dvel[2] * m_params.velBias); pat[npat * 2 + 0] = pat[last1] * ca + pat[last1 + 1] * sa;
int ns = 0; pat[npat * 2 + 1] = -pat[last1] * sa + pat[last1 + 1] * ca;
for (int k = 0; k < depth; ++k) { // get next point on the "left" (rotate CCW)
float minPenalty = float.MaxValue; pat[npat * 2 + 2] = pat[last2] * ca - pat[last2 + 1] * sa;
float[] bvel = new float[3]; pat[npat * 2 + 3] = pat[last2] * sa + pat[last2 + 1] * ca;
vSet(bvel, 0, 0, 0);
for (int i = 0; i < npat; ++i) { last1 = npat * 2;
float[] vcand = new float[3]; last2 = last1 + 2;
vSet(vcand, res[0] + pat[i * 2 + 0] * cr, 0f, res[2] + pat[i * 2 + 1] * cr); npat += 2;
if (sqr(vcand[0]) + sqr(vcand[2]) > sqr(vmax + 0.001f)) }
continue;
float penalty = processSample(vcand, cr / 10, pos, rad, vel, dvel, minPenalty, debug); if ((nd & 1) == 0)
ns++; {
if (penalty < minPenalty) { pat[npat * 2 + 2] = pat[last2] * ca - pat[last2 + 1] * sa;
minPenalty = penalty; pat[npat * 2 + 3] = pat[last2] * sa + pat[last2 + 1] * ca;
vCopy(bvel, vcand); npat++;
} }
} }
vCopy(res, bvel); // Start sampling.
float cr = vmax * (1.0f - m_params.velBias);
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)
{
float minPenalty = float.MaxValue;
float[] bvel = new float[3];
vSet(bvel, 0, 0, 0);
cr *= 0.5f; 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))
continue;
float penalty = processSample(vcand, cr / 10, pos, rad, vel, dvel, minPenalty, debug);
ns++;
if (penalty < minPenalty)
{
minPenalty = penalty;
vCopy(bvel, vcand);
}
}
vCopy(res, bvel);
cr *= 0.5f;
}
vCopy(nvel, res);
return Tuple.Create(ns, nvel);
} }
vCopy(nvel, res);
return Tuple.Create(ns, nvel);
} }
}
} }

View File

@ -23,11 +23,9 @@ using System.Collections.Generic;
namespace DotRecast.Detour.Crowd namespace DotRecast.Detour.Crowd
{ {
using static DetourCommon;
/**
using static DetourCommon;
/**
* Represents a dynamic polygon corridor used to plan agent movement. * Represents a dynamic polygon corridor used to plan agent movement.
* *
* The corridor is loaded with a path, usually obtained from a #NavMeshQuery::findPath() query. The corridor is then * The corridor is loaded with a path, usually obtained from a #NavMeshQuery::findPath() query. The corridor is then
@ -64,119 +62,142 @@ using static DetourCommon;
* replanning may be needed. E.g. If you move the target, check #getLastPoly() to see if it is the expected polygon. * replanning may be needed. E.g. If you move the target, check #getLastPoly() to see if it is the expected polygon.
* *
*/ */
public class PathCorridor { public class PathCorridor
{
private readonly float[] m_pos = new float[3];
private readonly float[] m_target = new float[3];
private List<long> m_path;
private readonly float[] m_pos = new float[3]; protected List<long> mergeCorridorStartMoved(List<long> path, List<long> visited)
private readonly float[] m_target = new float[3]; {
private List<long> m_path; int furthestPath = -1;
int furthestVisited = -1;
protected List<long> mergeCorridorStartMoved(List<long> path, List<long> visited) { // Find furthest common polygon.
int furthestPath = -1; for (int i = path.Count - 1; i >= 0; --i)
int furthestVisited = -1; {
bool found = false;
for (int j = visited.Count - 1; j >= 0; --j)
{
if (path[i] == visited[j])
{
furthestPath = i;
furthestVisited = j;
found = true;
}
}
// Find furthest common polygon. if (found)
for (int i = path.Count - 1; i >= 0; --i) { {
bool found = false; break;
for (int j = visited.Count - 1; j >= 0; --j) {
if (path[i] == visited[j]) {
furthestPath = i;
furthestVisited = j;
found = true;
} }
} }
if (found) {
break; // If no intersection found just return current path.
if (furthestPath == -1 || furthestVisited == -1)
{
return path;
} }
// Concatenate paths.
// 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)
{
result.Add(visited[i]);
}
result.AddRange(path.GetRange(furthestPath, path.Count - furthestPath));
return result;
} }
// If no intersection found just return current path. protected List<long> mergeCorridorEndMoved(List<long> path, List<long> visited)
if (furthestPath == -1 || furthestVisited == -1) { {
return path; int furthestPath = -1;
} int furthestVisited = -1;
// Concatenate paths. // Find furthest common polygon.
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])
{
furthestPath = i;
furthestVisited = j;
found = true;
}
}
// Adjust beginning of the buffer to include the visited. if (found)
List<long> result = new List<long>(); {
// Store visited break;
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) {
int furthestPath = -1;
int furthestVisited = -1;
// Find furthest common polygon.
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]) {
furthestPath = i;
furthestVisited = j;
found = true;
} }
} }
if (found) {
break; // If no intersection found just return current path.
if (furthestPath == -1 || furthestVisited == -1)
{
return path;
} }
// Concatenate paths.
List<long> result = path.GetRange(0, furthestPath);
result.AddRange(visited.GetRange(furthestVisited, visited.Count - furthestVisited));
return result;
} }
// If no intersection found just return current path. protected List<long> mergeCorridorStartShortcut(List<long> path, List<long> visited)
if (furthestPath == -1 || furthestVisited == -1) { {
return path; int furthestPath = -1;
} int furthestVisited = -1;
// Concatenate paths. // Find furthest common polygon.
List<long> result = path.GetRange(0, furthestPath); for (int i = path.Count - 1; i >= 0; --i)
result.AddRange(visited.GetRange(furthestVisited, visited.Count - furthestVisited)); {
return result; bool found = false;
} for (int j = visited.Count - 1; j >= 0; --j)
{
if (path[i] == visited[j])
{
furthestPath = i;
furthestVisited = j;
found = true;
}
}
protected List<long> mergeCorridorStartShortcut(List<long> path, List<long> visited) { if (found)
{
int furthestPath = -1; break;
int furthestVisited = -1;
// Find furthest common polygon.
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]) {
furthestPath = i;
furthestVisited = j;
found = true;
} }
} }
if (found) {
break; // If no intersection found just return current path.
if (furthestPath == -1 || furthestVisited <= 0)
{
return path;
} }
// Concatenate paths.
// Adjust beginning of the buffer to include the visited.
List<long> result = visited.GetRange(0, furthestVisited);
result.AddRange(path.GetRange(furthestPath, path.Count - furthestPath));
return result;
} }
// If no intersection found just return current path. /**
if (furthestPath == -1 || furthestVisited <= 0) {
return path;
}
// Concatenate paths.
// Adjust beginning of the buffer to include the visited.
List<long> result = visited.GetRange(0, furthestVisited);
result.AddRange(path.GetRange(furthestPath, path.Count - furthestPath));
return result;
}
/**
* Allocates the corridor's path buffer. * Allocates the corridor's path buffer.
*/ */
public PathCorridor() { public PathCorridor()
m_path = new List<long>(); {
} m_path = new List<long>();
}
/** /**
* Resets the path corridor to the specified position. * Resets the path corridor to the specified position.
* *
* @param ref * @param ref
@ -184,16 +205,17 @@ public class PathCorridor {
* @param pos * @param pos
* The new position in the corridor. [(x, y, z)] * The new position in the corridor. [(x, y, z)]
*/ */
public void reset(long refs, float[] pos) { public void reset(long refs, float[] pos)
m_path.Clear(); {
m_path.Add(refs); m_path.Clear();
vCopy(m_pos, pos); m_path.Add(refs);
vCopy(m_target, pos); vCopy(m_pos, pos);
} vCopy(m_target, pos);
}
private static readonly float MIN_TARGET_DIST = sqr(0.01f); private static readonly float MIN_TARGET_DIST = sqr(0.01f);
/** /**
* Finds the corners in the corridor from the position toward the target. (The straightened path.) * Finds the corners in the corridor from the position toward the target. (The straightened path.)
* *
* This is the function used to plan local movement within the corridor. One or more corners can be detected in * This is the function used to plan local movement within the corridor. One or more corners can be detected in
@ -210,35 +232,45 @@ public class PathCorridor {
* @param[in] navquery The query object used to build the corridor. * @param[in] navquery The query object used to build the corridor.
* @return Corners * @return Corners
*/ */
public List<StraightPathItem> findCorners(int maxCorners, NavMeshQuery navquery, QueryFilter filter) { public List<StraightPathItem> findCorners(int maxCorners, NavMeshQuery navquery, QueryFilter filter)
List<StraightPathItem> path = new List<StraightPathItem>(); {
Result<List<StraightPathItem>> result = navquery.findStraightPath(m_pos, m_target, m_path, maxCorners, 0); List<StraightPathItem> path = new List<StraightPathItem>();
if (result.succeeded()) { Result<List<StraightPathItem>> result = navquery.findStraightPath(m_pos, m_target, m_path, maxCorners, 0);
path = result.result; if (result.succeeded())
// Prune points in the beginning of the path which are too close. {
int start = 0; path = result.result;
foreach (StraightPathItem spi in path) { // Prune points in the beginning of the path which are too close.
if ((spi.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0 int start = 0;
|| vDist2DSqr(spi.getPos(), m_pos) > MIN_TARGET_DIST) { foreach (StraightPathItem spi in path)
break; {
} if ((spi.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0
start++; || vDist2DSqr(spi.getPos(), m_pos) > MIN_TARGET_DIST)
} {
int end = path.Count; break;
// Prune points after an off-mesh connection. }
for (int i = start; i < path.Count; i++) {
StraightPathItem spi = path[i];
if ((spi.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0) {
end = i + 1;
break;
}
}
path = path.GetRange(start, end - start);
}
return path;
}
/** start++;
}
int end = path.Count;
// Prune points after an off-mesh connection.
for (int i = start; i < path.Count; i++)
{
StraightPathItem spi = path[i];
if ((spi.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
{
end = i + 1;
break;
}
}
path = path.GetRange(start, end - start);
}
return path;
}
/**
* Attempts to optimize the path if the specified point is visible from the current position. * Attempts to optimize the path if the specified point is visible from the current position.
* *
* Inaccurate locomotion or dynamic obstacle avoidance can force the agent position significantly outside the * Inaccurate locomotion or dynamic obstacle avoidance can force the agent position significantly outside the
@ -265,34 +297,37 @@ public class PathCorridor {
* @param filter * @param filter
* The filter to apply to the operation. * The filter to apply to the operation.
*/ */
public void optimizePathVisibility(float[] next, float pathOptimizationRange, NavMeshQuery navquery,
QueryFilter filter)
{
// Clamp the ray to max distance.
float dist = vDist2D(m_pos, next);
public void optimizePathVisibility(float[] next, float pathOptimizationRange, NavMeshQuery navquery, // If too close to the goal, do not try to optimize.
QueryFilter filter) { if (dist < 0.01f)
// Clamp the ray to max distance. {
float dist = vDist2D(m_pos, next); return;
}
// If too close to the goal, do not try to optimize. // Overshoot a little. This helps to optimize open fields in tiled
if (dist < 0.01f) { // meshes.
return; dist = Math.Min(dist + 0.01f, pathOptimizationRange);
}
// Overshoot a little. This helps to optimize open fields in tiled // Adjust ray length.
// meshes. float[] delta = vSub(next, m_pos);
dist = Math.Min(dist + 0.01f, pathOptimizationRange); float[] goal = vMad(m_pos, delta, pathOptimizationRange / dist);
// Adjust ray length. Result<RaycastHit> rc = navquery.raycast(m_path[0], m_pos, goal, filter, 0, 0);
float[] delta = vSub(next, m_pos); if (rc.succeeded())
float[] goal = vMad(m_pos, delta, pathOptimizationRange / dist); {
if (rc.result.path.Count > 1 && rc.result.t > 0.99f)
Result<RaycastHit> rc = navquery.raycast(m_path[0], m_pos, goal, filter, 0, 0); {
if (rc.succeeded()) { m_path = mergeCorridorStartShortcut(m_path, rc.result.path);
if (rc.result.path.Count > 1 && rc.result.t > 0.99f) { }
m_path = mergeCorridorStartShortcut(m_path, rc.result.path);
} }
} }
}
/** /**
* Attempts to optimize the path using a local area search. (Partial replanning.) * Attempts to optimize the path using a local area search. (Partial replanning.)
* *
* Inaccurate locomotion or dynamic obstacle avoidance can force the agent position significantly outside the * Inaccurate locomotion or dynamic obstacle avoidance can force the agent position significantly outside the
@ -308,55 +343,64 @@ public class PathCorridor {
* The filter to apply to the operation. * The filter to apply to the operation.
* *
*/ */
public bool optimizePathTopology(NavMeshQuery navquery, QueryFilter filter, int maxIterations) { public bool optimizePathTopology(NavMeshQuery navquery, QueryFilter filter, int maxIterations)
if (m_path.Count < 3) { {
if (m_path.Count < 3)
{
return false;
}
navquery.initSlicedFindPath(m_path[0], m_path[m_path.Count - 1], m_pos, m_target, filter, 0);
navquery.updateSlicedFindPath(maxIterations);
Result<List<long>> fpr = navquery.finalizeSlicedFindPathPartial(m_path);
if (fpr.succeeded() && fpr.result.Count > 0)
{
m_path = mergeCorridorStartShortcut(m_path, fpr.result);
return true;
}
return false; return false;
} }
navquery.initSlicedFindPath(m_path[0], m_path[m_path.Count - 1], m_pos, m_target, filter, 0); public bool moveOverOffmeshConnection(long offMeshConRef, long[] refs, float[] start, float[] end,
navquery.updateSlicedFindPath(maxIterations); NavMeshQuery navquery)
Result<List<long>> fpr = navquery.finalizeSlicedFindPathPartial(m_path); {
// 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)
{
prevRef = polyRef;
polyRef = m_path[npos];
npos++;
}
if (fpr.succeeded() && fpr.result.Count > 0) { if (npos == m_path.Count)
m_path = mergeCorridorStartShortcut(m_path, fpr.result); {
return true; // Could not find offMeshConRef
} return false;
}
return false; // Prune path
} m_path = m_path.GetRange(npos, m_path.Count - npos);
refs[0] = prevRef;
refs[1] = polyRef;
NavMesh nav = navquery.getAttachedNavMesh();
Result<Tuple<float[], float[]>> startEnd = nav.getOffMeshConnectionPolyEndPoints(refs[0], refs[1]);
if (startEnd.succeeded())
{
vCopy(m_pos, startEnd.result.Item2);
vCopy(start, startEnd.result.Item1);
vCopy(end, startEnd.result.Item2);
return true;
}
public bool moveOverOffmeshConnection(long offMeshConRef, long[] refs, float[] start, float[] end,
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) {
prevRef = polyRef;
polyRef = m_path[npos];
npos++;
}
if (npos == m_path.Count) {
// Could not find offMeshConRef
return false; return false;
} }
// Prune path /**
m_path = m_path.GetRange(npos, m_path.Count - npos);
refs[0] = prevRef;
refs[1] = polyRef;
NavMesh nav = navquery.getAttachedNavMesh();
Result<Tuple<float[], float[]>> startEnd = nav.getOffMeshConnectionPolyEndPoints(refs[0], refs[1]);
if (startEnd.succeeded()) {
vCopy(m_pos, startEnd.result.Item2);
vCopy(start, startEnd.result.Item1);
vCopy(end, startEnd.result.Item2);
return true;
}
return false;
}
/**
* Moves the position from the current location to the desired location, adjusting the corridor as needed to reflect * Moves the position from the current location to the desired location, adjusting the corridor as needed to reflect
* the change. * the change.
* *
@ -379,23 +423,28 @@ public class PathCorridor {
* @param filter * @param filter
* The filter to apply to the operation. * The filter to apply to the operation.
*/ */
public bool movePosition(float[] npos, NavMeshQuery navquery, QueryFilter filter) { public bool movePosition(float[] npos, NavMeshQuery navquery, QueryFilter filter)
// Move along navmesh and update new position. {
Result<MoveAlongSurfaceResult> masResult = navquery.moveAlongSurface(m_path[0], m_pos, npos, filter); // Move along navmesh and update new position.
if (masResult.succeeded()) { Result<MoveAlongSurfaceResult> masResult = navquery.moveAlongSurface(m_path[0], m_pos, npos, filter);
m_path = mergeCorridorStartMoved(m_path, masResult.result.getVisited()); if (masResult.succeeded())
// Adjust the position to stay on top of the navmesh. {
vCopy(m_pos, masResult.result.getResultPos()); m_path = mergeCorridorStartMoved(m_path, masResult.result.getVisited());
Result<float> hr = navquery.getPolyHeight(m_path[0], masResult.result.getResultPos()); // Adjust the position to stay on top of the navmesh.
if (hr.succeeded()) { vCopy(m_pos, masResult.result.getResultPos());
m_pos[1] = hr.result; Result<float> hr = navquery.getPolyHeight(m_path[0], masResult.result.getResultPos());
} if (hr.succeeded())
return true; {
} m_pos[1] = hr.result;
return false; }
}
/** return true;
}
return false;
}
/**
* Moves the target from the curent location to the desired location, adjusting the corridor as needed to reflect * Moves the target from the curent location to the desired location, adjusting the corridor as needed to reflect
* the change. Behavior: - The movement is constrained to the surface of the navigation mesh. - The corridor is * the change. Behavior: - The movement is constrained to the surface of the navigation mesh. - The corridor is
* automatically adjusted (shorted or lengthened) in order to remain valid. - The new target will be located in the * automatically adjusted (shorted or lengthened) in order to remain valid. - The new target will be located in the
@ -412,25 +461,28 @@ public class PathCorridor {
* @param filter * @param filter
* The filter to apply to the operation. * The filter to apply to the operation.
*/ */
public bool moveTargetPosition(float[] npos, NavMeshQuery navquery, QueryFilter filter) { public bool moveTargetPosition(float[] npos, NavMeshQuery navquery, QueryFilter filter)
// Move along navmesh and update new position. {
Result<MoveAlongSurfaceResult> masResult = navquery.moveAlongSurface(m_path[m_path.Count - 1], m_target, // Move along navmesh and update new position.
Result<MoveAlongSurfaceResult> masResult = navquery.moveAlongSurface(m_path[m_path.Count - 1], m_target,
npos, filter); npos, filter);
if (masResult.succeeded()) { if (masResult.succeeded())
m_path = mergeCorridorEndMoved(m_path, masResult.result.getVisited()); {
// TODO: should we do that? m_path = mergeCorridorEndMoved(m_path, masResult.result.getVisited());
// Adjust the position to stay on top of the navmesh. // TODO: should we do that?
/* // Adjust the position to stay on top of the navmesh.
* float h = m_target[1]; navquery->getPolyHeight(m_path[m_npath-1], /*
* result, &h); result[1] = h; * float h = m_target[1]; navquery->getPolyHeight(m_path[m_npath-1],
*/ * result, &h); result[1] = h;
vCopy(m_target, masResult.result.getResultPos()); */
return true; vCopy(m_target, masResult.result.getResultPos());
} return true;
return false; }
}
/** return false;
}
/**
* Loads a new path and target into the corridor. The current corridor position is expected to be within the first * Loads a new path and target into the corridor. The current corridor position is expected to be within the first
* polygon in the path. The target is expected to be in the last polygon. * polygon in the path. The target is expected to be in the last polygon.
* *
@ -440,52 +492,62 @@ public class PathCorridor {
* @param path * @param path
* The path corridor. * The path corridor.
*/ */
public void setCorridor(float[] target, List<long> path)
public void setCorridor(float[] target, List<long> path) { {
vCopy(m_target, target); vCopy(m_target, target);
m_path = new List<long>(path); m_path = new List<long>(path);
}
public void fixPathStart(long safeRef, float[] safePos) {
vCopy(m_pos, safePos);
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 {
m_path.Clear();
m_path.Add(safeRef);
m_path.Add(0L);
} }
} public void fixPathStart(long safeRef, float[] safePos)
{
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)) {
n++;
}
if (n == 0) {
// The first polyref is bad, use current safe values.
vCopy(m_pos, safePos); vCopy(m_pos, safePos);
m_path.Clear(); if (m_path.Count < 3 && m_path.Count > 0)
m_path.Add(safeRef); {
} else if (n < m_path.Count) { long p = m_path[m_path.Count - 1];
m_path = m_path.GetRange(0, n); m_path.Clear();
// The path is partially usable. m_path.Add(safeRef);
m_path.Add(0L);
m_path.Add(p);
}
else
{
m_path.Clear();
m_path.Add(safeRef);
m_path.Add(0L);
}
} }
// Clamp target pos to last poly
Result<float[]> result = navquery.closestPointOnPolyBoundary(m_path[m_path.Count - 1], m_target);
if (result.succeeded()) {
vCopy(m_target, result.result);
}
}
/** 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))
{
n++;
}
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)
{
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())
{
vCopy(m_target, result.result);
}
}
/**
* Checks the current corridor path to see if its polygon references remain valid. The path can be invalidated if * Checks the current corridor path to see if its polygon references remain valid. The path can be invalidated if
* there are structural changes to the underlying navigation mesh, or the state of a polygon within the path changes * there are structural changes to the underlying navigation mesh, or the state of a polygon within the path changes
* resulting in it being filtered out. (E.g. An exclusion or inclusion flag changes.) * resulting in it being filtered out. (E.g. An exclusion or inclusion flag changes.)
@ -498,69 +560,77 @@ public class PathCorridor {
* The filter to apply to the operation. * The filter to apply to the operation.
* @return * @return
*/ */
public bool isValid(int maxLookAhead, NavMeshQuery navquery, QueryFilter filter) { public bool isValid(int maxLookAhead, NavMeshQuery navquery, QueryFilter filter)
// Check that all polygons still pass query filter. {
int n = Math.Min(m_path.Count, maxLookAhead); // Check that all polygons still pass query filter.
for (int i = 0; i < n; ++i) { int n = Math.Min(m_path.Count, maxLookAhead);
if (!navquery.isValidPolyRef(m_path[i], filter)) { for (int i = 0; i < n; ++i)
return false; {
if (!navquery.isValidPolyRef(m_path[i], filter))
{
return false;
}
} }
return true;
} }
return true; /**
}
/**
* Gets the current position within the corridor. (In the first polygon.) * Gets the current position within the corridor. (In the first polygon.)
* *
* @return The current position within the corridor. * @return The current position within the corridor.
*/ */
public float[] getPos() { public float[] getPos()
return m_pos; {
} return m_pos;
}
/** /**
* Gets the current target within the corridor. (In the last polygon.) * Gets the current target within the corridor. (In the last polygon.)
* *
* @return The current target within the corridor. * @return The current target within the corridor.
*/ */
public float[] getTarget() { public float[] getTarget()
return m_target; {
} return m_target;
}
/** /**
* The polygon reference id of the first polygon in the corridor, the polygon containing the position. * The polygon reference id of the first polygon in the corridor, the polygon containing the position.
* *
* @return The polygon reference id of the first polygon in the corridor. (Or zero if there is no path.) * @return The polygon reference id of the first polygon in the corridor. (Or zero if there is no path.)
*/ */
public long getFirstPoly() { public long getFirstPoly()
return 0 == m_path.Count ? 0 : m_path[0]; {
} return 0 == m_path.Count ? 0 : m_path[0];
}
/** /**
* The polygon reference id of the last polygon in the corridor, the polygon containing the target. * The polygon reference id of the last polygon in the corridor, the polygon containing the target.
* *
* @return The polygon reference id of the last polygon in the corridor. (Or zero if there is no path.) * @return The polygon reference id of the last polygon in the corridor. (Or zero if there is no path.)
*/ */
public long getLastPoly() { public long getLastPoly()
return 0 == m_path.Count ? 0 : m_path[m_path.Count - 1]; {
} return 0 == m_path.Count ? 0 : m_path[m_path.Count - 1];
}
/** /**
* The corridor's path. * The corridor's path.
*/ */
public List<long> getPath() { public List<long> getPath()
return m_path; {
} return m_path;
}
/** /**
* The number of polygons in the current corridor path. * The number of polygons in the current corridor path.
* *
* @return The number of polygons in the current corridor path. * @return The number of polygons in the current corridor path.
*/ */
public int getPathCount() { public int getPathCount()
return m_path.Count; {
return m_path.Count;
}
} }
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,107 +22,121 @@ using System;
namespace DotRecast.Detour.Crowd.Tracking namespace DotRecast.Detour.Crowd.Tracking
{ {
using static DetourCommon;
public class ObstacleAvoidanceDebugData
{
int m_nsamples;
int m_maxSamples;
float[] m_vel;
float[] m_ssize;
float[] m_pen;
float[] m_vpen;
float[] m_vcpen;
float[] m_spen;
float[] m_tpen;
using static DetourCommon; public ObstacleAvoidanceDebugData(int maxSamples)
{
public class ObstacleAvoidanceDebugData { m_maxSamples = maxSamples;
int m_nsamples; m_vel = new float[3 * m_maxSamples];
int m_maxSamples; m_pen = new float[m_maxSamples];
float[] m_vel; m_ssize = new float[m_maxSamples];
float[] m_ssize; m_vpen = new float[m_maxSamples];
float[] m_pen; m_vcpen = new float[m_maxSamples];
float[] m_vpen; m_spen = new float[m_maxSamples];
float[] m_vcpen; m_tpen = new float[m_maxSamples];
float[] m_spen;
float[] m_tpen;
public ObstacleAvoidanceDebugData(int maxSamples) {
m_maxSamples = maxSamples;
m_vel = new float[3 * m_maxSamples];
m_pen = new float[m_maxSamples];
m_ssize = new float[m_maxSamples];
m_vpen = new float[m_maxSamples];
m_vcpen = new float[m_maxSamples];
m_spen = new float[m_maxSamples];
m_tpen = new float[m_maxSamples];
}
public void reset() {
m_nsamples = 0;
}
void normalizeArray(float[] arr, int n) {
// Normalize penaly range.
float minPen = float.MaxValue;
float maxPen = -float.MaxValue;
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 reset()
normalizeArray(m_pen, m_nsamples); {
normalizeArray(m_vpen, m_nsamples); m_nsamples = 0;
normalizeArray(m_vcpen, m_nsamples); }
normalizeArray(m_spen, m_nsamples);
normalizeArray(m_tpen, m_nsamples);
}
public void addSample(float[] vel, float ssize, float pen, float vpen, float vcpen, float spen, float tpen) { void normalizeArray(float[] arr, int n)
if (m_nsamples >= m_maxSamples) {
return; // Normalize penaly range.
m_vel[m_nsamples * 3] = vel[0]; float minPen = float.MaxValue;
m_vel[m_nsamples * 3 + 1] = vel[1]; float maxPen = -float.MaxValue;
m_vel[m_nsamples * 3 + 2] = vel[2]; for (int i = 0; i < n; ++i)
m_ssize[m_nsamples] = ssize; {
m_pen[m_nsamples] = pen; minPen = Math.Min(minPen, arr[i]);
m_vpen[m_nsamples] = vpen; maxPen = Math.Max(maxPen, arr[i]);
m_vcpen[m_nsamples] = vcpen; }
m_spen[m_nsamples] = spen;
m_tpen[m_nsamples] = tpen;
m_nsamples++;
}
public int getSampleCount() { float penRange = maxPen - minPen;
return m_nsamples; 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 float[] getSampleVelocity(int i) { public void normalizeSamples()
float[] vel = new float[3]; {
vel[0] = m_vel[i * 3]; normalizeArray(m_pen, m_nsamples);
vel[1] = m_vel[i * 3 + 1]; normalizeArray(m_vpen, m_nsamples);
vel[2] = m_vel[i * 3 + 2]; normalizeArray(m_vcpen, m_nsamples);
return vel; normalizeArray(m_spen, m_nsamples);
} normalizeArray(m_tpen, m_nsamples);
}
public float getSampleSize(int i) { public void addSample(float[] vel, float ssize, float pen, float vpen, float vcpen, float spen, float tpen)
return m_ssize[i]; {
} if (m_nsamples >= m_maxSamples)
return;
m_vel[m_nsamples * 3] = vel[0];
m_vel[m_nsamples * 3 + 1] = vel[1];
m_vel[m_nsamples * 3 + 2] = vel[2];
m_ssize[m_nsamples] = ssize;
m_pen[m_nsamples] = pen;
m_vpen[m_nsamples] = vpen;
m_vcpen[m_nsamples] = vcpen;
m_spen[m_nsamples] = spen;
m_tpen[m_nsamples] = tpen;
m_nsamples++;
}
public float getSamplePenalty(int i) { public int getSampleCount()
return m_pen[i]; {
} return m_nsamples;
}
public float getSampleDesiredVelocityPenalty(int i) { public float[] getSampleVelocity(int i)
return m_vpen[i]; {
} float[] vel = new float[3];
vel[0] = m_vel[i * 3];
vel[1] = m_vel[i * 3 + 1];
vel[2] = m_vel[i * 3 + 2];
return vel;
}
public float getSampleCurrentVelocityPenalty(int i) { public float getSampleSize(int i)
return m_vcpen[i]; {
} return m_ssize[i];
}
public float getSamplePreferredSidePenalty(int i) { public float getSamplePenalty(int i)
return m_spen[i]; {
} return m_pen[i];
}
public float getSampleCollisionTimePenalty(int i) { public float getSampleDesiredVelocityPenalty(int i)
return m_tpen[i]; {
return m_vpen[i];
}
public float getSampleCurrentVelocityPenalty(int i)
{
return m_vcpen[i];
}
public float getSamplePreferredSidePenalty(int i)
{
return m_spen[i];
}
public float getSampleCollisionTimePenalty(int i)
{
return m_tpen[i];
}
} }
}
} }

View File

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

View File

@ -20,29 +20,27 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders namespace DotRecast.Detour.Dynamic.Colliders
{ {
public abstract class AbstractCollider : Collider
public abstract class AbstractCollider : Collider {
protected readonly int area;
protected readonly float flagMergeThreshold;
protected readonly float[] _bounds;
public AbstractCollider(int area, float flagMergeThreshold, float[] bounds) {
this.area = area;
this.flagMergeThreshold = flagMergeThreshold;
this._bounds = bounds;
}
public float[] bounds() {
return _bounds;
}
public void rasterize(Heightfield hf, Telemetry telemetry)
{ {
///? protected readonly int area;
protected readonly float flagMergeThreshold;
protected readonly float[] _bounds;
public AbstractCollider(int area, float flagMergeThreshold, float[] bounds)
{
this.area = area;
this.flagMergeThreshold = flagMergeThreshold;
this._bounds = bounds;
}
public float[] bounds()
{
return _bounds;
}
public void rasterize(Heightfield hf, Telemetry telemetry)
{
///?
}
} }
}
} }

View File

@ -21,63 +21,68 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders namespace DotRecast.Detour.Dynamic.Colliders
{ {
public class BoxCollider : AbstractCollider
public class BoxCollider : AbstractCollider {
private readonly float[] center;
private readonly float[][] halfEdges;
public BoxCollider(float[] center, float[][] halfEdges, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, bounds(center, halfEdges))
{ {
this.center = center; private readonly float[] center;
this.halfEdges = halfEdges; private readonly float[][] halfEdges;
}
private static float[] bounds(float[] center, float[][] halfEdges) { public BoxCollider(float[] center, float[][] halfEdges, int area, float flagMergeThreshold) :
float[] bounds = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, base(area, flagMergeThreshold, bounds(center, halfEdges))
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity }; {
for (int i = 0; i < 8; ++i) { this.center = center;
float s0 = (i & 1) != 0 ? 1f : -1f; this.halfEdges = halfEdges;
float s1 = (i & 2) != 0 ? 1f : -1f;
float s2 = (i & 4) != 0 ? 1f : -1f;
float vx = center[0] + s0 * halfEdges[0][0] + s1 * halfEdges[1][0] + s2 * halfEdges[2][0];
float vy = center[1] + s0 * halfEdges[0][1] + s1 * halfEdges[1][1] + s2 * halfEdges[2][1];
float vz = center[2] + s0 * halfEdges[0][2] + s1 * halfEdges[1][2] + s2 * halfEdges[2][2];
bounds[0] = Math.Min(bounds[0], vx);
bounds[1] = Math.Min(bounds[1], vy);
bounds[2] = Math.Min(bounds[2], vz);
bounds[3] = Math.Max(bounds[3], vx);
bounds[4] = Math.Max(bounds[4], vy);
bounds[5] = Math.Max(bounds[5], vz);
} }
return bounds;
}
public void rasterize(Heightfield hf, Telemetry telemetry) { private static float[] bounds(float[] center, float[][] halfEdges)
RecastFilledVolumeRasterization.rasterizeBox(hf, center, halfEdges, area, (int) Math.Floor(flagMergeThreshold / hf.ch), {
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;
float vx = center[0] + s0 * halfEdges[0][0] + s1 * halfEdges[1][0] + s2 * halfEdges[2][0];
float vy = center[1] + s0 * halfEdges[0][1] + s1 * halfEdges[1][1] + s2 * halfEdges[2][1];
float vz = center[2] + s0 * halfEdges[0][2] + s1 * halfEdges[1][2] + s2 * halfEdges[2][2];
bounds[0] = Math.Min(bounds[0], vx);
bounds[1] = Math.Min(bounds[1], vy);
bounds[2] = Math.Min(bounds[2], vz);
bounds[3] = Math.Max(bounds[3], vx);
bounds[4] = Math.Max(bounds[4], vy);
bounds[5] = Math.Max(bounds[5], vz);
}
return bounds;
}
public void rasterize(Heightfield hf, Telemetry telemetry)
{
RecastFilledVolumeRasterization.rasterizeBox(hf, center, halfEdges, area, (int)Math.Floor(flagMergeThreshold / hf.ch),
telemetry); telemetry);
}
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);
RecastVectors.normalize(halfEdges[0]);
RecastVectors.cross(halfEdges[2], halfEdges[0], up);
RecastVectors.normalize(halfEdges[2]);
halfEdges[0][0] *= extent[0];
halfEdges[0][1] *= extent[0];
halfEdges[0][2] *= extent[0];
halfEdges[1][0] *= extent[1];
halfEdges[1][1] *= extent[1];
halfEdges[1][2] *= extent[1];
halfEdges[2][0] *= extent[2];
halfEdges[2][1] *= extent[2];
halfEdges[2][2] *= extent[2];
return halfEdges;
}
} }
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);
RecastVectors.normalize(halfEdges[0]);
RecastVectors.cross(halfEdges[2], halfEdges[0], up);
RecastVectors.normalize(halfEdges[2]);
halfEdges[0][0] *= extent[0];
halfEdges[0][1] *= extent[0];
halfEdges[0][2] *= extent[0];
halfEdges[1][0] *= extent[1];
halfEdges[1][1] *= extent[1];
halfEdges[1][2] *= extent[1];
halfEdges[2][0] *= extent[2];
halfEdges[2][1] *= extent[2];
halfEdges[2][2] *= extent[2];
return halfEdges;
}
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,45 +21,48 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders namespace DotRecast.Detour.Dynamic.Colliders
{ {
public class TrimeshCollider : AbstractCollider
{
private readonly float[] vertices;
private readonly int[] triangles;
public TrimeshCollider(float[] vertices, int[] triangles, int area, float flagMergeThreshold) :
public class TrimeshCollider : AbstractCollider { base(area, flagMergeThreshold, computeBounds(vertices))
{
private readonly float[] vertices; this.vertices = vertices;
private readonly int[] triangles; this.triangles = triangles;
public TrimeshCollider(float[] vertices, int[] triangles, int area, float flagMergeThreshold) :
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) {
this.vertices = vertices;
this.triangles = triangles;
}
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) {
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]);
bounds[3] = Math.Max(bounds[3], vertices[i]);
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) { public TrimeshCollider(float[] vertices, int[] triangles, float[] bounds, int area, float flagMergeThreshold) :
for (int i = 0; i < triangles.Length; i += 3) { base(area, flagMergeThreshold, bounds)
RecastRasterization.rasterizeTriangle(hf, vertices, triangles[i], triangles[i + 1], triangles[i + 2], area, {
(int) Math.Floor(flagMergeThreshold / hf.ch), telemetry); this.vertices = vertices;
this.triangles = triangles;
}
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)
{
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]);
bounds[3] = Math.Max(bounds[3], vertices[i]);
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)
{
RecastRasterization.rasterizeTriangle(hf, vertices, triangles[i], triangles[i + 1], triangles[i + 2], area,
(int)Math.Floor(flagMergeThreshold / hf.ch), telemetry);
}
} }
} }
}
} }

View File

@ -1,17 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup> </PropertyGroup>
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.5" />
</ItemGroup> <ItemGroup>
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.5"/>
<ItemGroup> </ItemGroup>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" />
<ProjectReference Include="..\DotRecast.Recast\DotRecast.Recast.csproj" /> <ItemGroup>
</ItemGroup> <ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj"/>
<ProjectReference Include="..\DotRecast.Recast\DotRecast.Recast.csproj"/>
</ItemGroup>
</Project> </Project>

View File

@ -29,202 +29,234 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic namespace DotRecast.Detour.Dynamic
{ {
public class DynamicNavMesh
{
public const int MAX_VERTS_PER_POLY = 6;
public readonly DynamicNavMeshConfig config;
private readonly RecastBuilder builder;
private readonly Dictionary<long, DynamicTile> _tiles = new Dictionary<long, DynamicTile>();
private readonly Telemetry telemetry;
private readonly NavMeshParams navMeshParams;
private readonly BlockingCollection<UpdateQueueItem> updateQueue = new BlockingCollection<UpdateQueueItem>();
private readonly AtomicLong currentColliderId = new AtomicLong(0);
private NavMesh _navMesh;
private bool dirty = true;
public DynamicNavMesh(VoxelFile voxelFile)
{
config = new DynamicNavMeshConfig(voxelFile.useTiles, voxelFile.tileSizeX, voxelFile.tileSizeZ, voxelFile.cellSize);
config.walkableHeight = voxelFile.walkableHeight;
config.walkableRadius = voxelFile.walkableRadius;
config.walkableClimb = voxelFile.walkableClimb;
config.walkableSlopeAngle = voxelFile.walkableSlopeAngle;
config.maxSimplificationError = voxelFile.maxSimplificationError;
config.maxEdgeLen = voxelFile.maxEdgeLen;
config.minRegionArea = voxelFile.minRegionArea;
config.regionMergeArea = voxelFile.regionMergeArea;
config.vertsPerPoly = voxelFile.vertsPerPoly;
config.buildDetailMesh = voxelFile.buildMeshDetail;
config.detailSampleDistance = voxelFile.detailSampleDistance;
config.detailSampleMaxError = voxelFile.detailSampleMaxError;
builder = new RecastBuilder();
navMeshParams = new NavMeshParams();
navMeshParams.orig[0] = voxelFile.bounds[0];
navMeshParams.orig[1] = voxelFile.bounds[1];
navMeshParams.orig[2] = voxelFile.bounds[2];
navMeshParams.tileWidth = voxelFile.cellSize * voxelFile.tileSizeX;
navMeshParams.tileHeight = voxelFile.cellSize * voxelFile.tileSizeZ;
navMeshParams.maxTiles = voxelFile.tiles.Count;
navMeshParams.maxPolys = 0x8000;
foreach (var t in voxelFile.tiles)
{
_tiles.Add(lookupKey(t.tileX, t.tileZ), new DynamicTile(t));
}
public class DynamicNavMesh { ;
telemetry = new Telemetry();
}
public const int MAX_VERTS_PER_POLY = 6; public NavMesh navMesh()
public readonly DynamicNavMeshConfig config; {
private readonly RecastBuilder builder; return _navMesh;
private readonly Dictionary<long, DynamicTile> _tiles = new Dictionary<long, DynamicTile>(); }
private readonly Telemetry telemetry;
private readonly NavMeshParams navMeshParams;
private readonly BlockingCollection<UpdateQueueItem> updateQueue = new BlockingCollection<UpdateQueueItem>();
private readonly AtomicLong currentColliderId = new AtomicLong(0);
private NavMesh _navMesh;
private bool dirty = true;
public DynamicNavMesh(VoxelFile voxelFile) { /**
config = new DynamicNavMeshConfig(voxelFile.useTiles, voxelFile.tileSizeX, voxelFile.tileSizeZ, voxelFile.cellSize);
config.walkableHeight = voxelFile.walkableHeight;
config.walkableRadius = voxelFile.walkableRadius;
config.walkableClimb = voxelFile.walkableClimb;
config.walkableSlopeAngle = voxelFile.walkableSlopeAngle;
config.maxSimplificationError = voxelFile.maxSimplificationError;
config.maxEdgeLen = voxelFile.maxEdgeLen;
config.minRegionArea = voxelFile.minRegionArea;
config.regionMergeArea = voxelFile.regionMergeArea;
config.vertsPerPoly = voxelFile.vertsPerPoly;
config.buildDetailMesh = voxelFile.buildMeshDetail;
config.detailSampleDistance = voxelFile.detailSampleDistance;
config.detailSampleMaxError = voxelFile.detailSampleMaxError;
builder = new RecastBuilder();
navMeshParams = new NavMeshParams();
navMeshParams.orig[0] = voxelFile.bounds[0];
navMeshParams.orig[1] = voxelFile.bounds[1];
navMeshParams.orig[2] = voxelFile.bounds[2];
navMeshParams.tileWidth = voxelFile.cellSize * voxelFile.tileSizeX;
navMeshParams.tileHeight = voxelFile.cellSize * voxelFile.tileSizeZ;
navMeshParams.maxTiles = voxelFile.tiles.Count;
navMeshParams.maxPolys = 0x8000;
foreach (var t in voxelFile.tiles) {
_tiles.Add(lookupKey(t.tileX, t.tileZ), new DynamicTile(t));
};
telemetry = new Telemetry();
}
public NavMesh navMesh() {
return _navMesh;
}
/**
* Voxel queries require checkpoints to be enabled in {@link DynamicNavMeshConfig} * Voxel queries require checkpoints to be enabled in {@link DynamicNavMeshConfig}
*/ */
public VoxelQuery voxelQuery() { public VoxelQuery voxelQuery()
return new VoxelQuery(navMeshParams.orig, navMeshParams.tileWidth, navMeshParams.tileHeight, lookupHeightfield); {
} return new VoxelQuery(navMeshParams.orig, navMeshParams.tileWidth, navMeshParams.tileHeight, lookupHeightfield);
}
private Heightfield lookupHeightfield(int x, int z) { private Heightfield lookupHeightfield(int x, int z)
return getTileAt(x, z)?.checkpoint.heightfield; {
} return getTileAt(x, z)?.checkpoint.heightfield;
}
public long addCollider(Collider collider) { public long addCollider(Collider collider)
long cid = currentColliderId.IncrementAndGet(); {
updateQueue.Add(new AddColliderQueueItem(cid, collider, getTiles(collider.bounds()))); long cid = currentColliderId.IncrementAndGet();
return cid; 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))); {
} updateQueue.Add(new RemoveColliderQueueItem(colliderId, getTilesByCollider(colliderId)));
}
/** /**
* Perform full build of the nav mesh * Perform full build of the nav mesh
*/ */
public void build() { public void build()
processQueue(); {
rebuild(_tiles.Values); processQueue();
} rebuild(_tiles.Values);
}
/** /**
* Perform incremental update of the nav mesh * Perform incremental update of the nav mesh
*/ */
public bool update() { public bool update()
return rebuild(processQueue());
}
private bool rebuild(ICollection<DynamicTile> stream) {
foreach (var dynamicTile in stream)
rebuild(dynamicTile);
return updateNavMesh();
}
private HashSet<DynamicTile> processQueue() {
var items = consumeQueue();
foreach (var item in items) {
process(item);
}
return items.SelectMany(i => i.affectedTiles()).ToHashSet();
}
private List<UpdateQueueItem> consumeQueue() {
List<UpdateQueueItem> items = new List<UpdateQueueItem>();
while (updateQueue.TryTake(out var item)) {
items.Add(item);
}
return items;
}
private void process(UpdateQueueItem item) {
foreach (var tile in item.affectedTiles())
{ {
item.process(tile); return rebuild(processQueue());
} }
}
/** private bool rebuild(ICollection<DynamicTile> stream)
* Perform full build concurrently using the given {@link ExecutorService} {
*/ foreach (var dynamicTile in stream)
public Task<bool> build(TaskFactory executor) { rebuild(dynamicTile);
processQueue(); return updateNavMesh();
return rebuild(_tiles.Values, executor);
}
/**
* Perform incremental update concurrently using the given {@link ExecutorService}
*/
public Task<bool> update(TaskFactory executor) {
return rebuild(processQueue(), executor);
}
private Task<bool> rebuild(ICollection<DynamicTile> tiles, TaskFactory executor)
{
var tasks = tiles.Select(tile => executor.StartNew(() => rebuild(tile))).ToArray();
return Task.WhenAll(tasks).ContinueWith(k => updateNavMesh());
}
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); private HashSet<DynamicTile> processQueue()
int maxx = (int) Math.Floor((bounds[3] - navMeshParams.orig[0]) / navMeshParams.tileWidth); {
int maxz = (int) Math.Floor((bounds[5] - navMeshParams.orig[2]) / navMeshParams.tileHeight); var items = consumeQueue();
List<DynamicTile> tiles = new List<DynamicTile>(); foreach (var item in items)
for (int z = minz; z <= maxz; ++z) { {
for (int x = minx; x <= maxx; ++x) { process(item);
DynamicTile tile = getTileAt(x, z); }
if (tile != null) {
tiles.Add(tile); return items.SelectMany(i => i.affectedTiles()).ToHashSet();
} }
private List<UpdateQueueItem> consumeQueue()
{
List<UpdateQueueItem> items = new List<UpdateQueueItem>();
while (updateQueue.TryTake(out var item))
{
items.Add(item);
}
return items;
}
private void process(UpdateQueueItem item)
{
foreach (var tile in item.affectedTiles())
{
item.process(tile);
} }
} }
return tiles;
}
private List<DynamicTile> getTilesByCollider(long cid) { /**
return _tiles.Values.Where(t => t.containsCollider(cid)).ToList(); * Perform full build concurrently using the given {@link ExecutorService}
} */
public Task<bool> build(TaskFactory executor)
private void rebuild(DynamicTile tile) { {
NavMeshDataCreateParams option = new NavMeshDataCreateParams(); processQueue();
option.walkableHeight = config.walkableHeight; return rebuild(_tiles.Values, executor);
dirty = dirty | tile.build(builder, config, telemetry); }
}
/**
private bool updateNavMesh() { * Perform incremental update concurrently using the given {@link ExecutorService}
if (dirty) { */
NavMesh navMesh = new NavMesh(navMeshParams, MAX_VERTS_PER_POLY); public Task<bool> update(TaskFactory executor)
foreach (var t in _tiles.Values) {
t.addTo(navMesh); return rebuild(processQueue(), executor);
}
this._navMesh = navMesh;
dirty = false; private Task<bool> rebuild(ICollection<DynamicTile> tiles, TaskFactory executor)
return true; {
var tasks = tiles.Select(tile => executor.StartNew(() => rebuild(tile))).ToArray();
return Task.WhenAll(tasks).ContinueWith(k => updateNavMesh());
}
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)
{
DynamicTile tile = getTileAt(x, z);
if (tile != null)
{
tiles.Add(tile);
}
}
}
return tiles;
}
private List<DynamicTile> getTilesByCollider(long cid)
{
return _tiles.Values.Where(t => t.containsCollider(cid)).ToList();
}
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)
{
NavMesh navMesh = new NavMesh(navMeshParams, MAX_VERTS_PER_POLY);
foreach (var t in _tiles.Values)
t.addTo(navMesh);
this._navMesh = navMesh;
dirty = false;
return true;
}
return false;
}
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)
{
return (z << 32) | x;
}
public List<VoxelTile> voxelTiles()
{
return _tiles.Values.Select(t => t.voxelTile).ToList();
}
public List<RecastBuilderResult> recastResults()
{
return _tiles.Values.Select(t => t.recastResult).ToList();
} }
return false;
} }
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) {
return (z << 32) | x;
}
public List<VoxelTile> voxelTiles() {
return _tiles.Values.Select(t => t.voxelTile).ToList();
}
public List<RecastBuilderResult> recastResults() {
return _tiles.Values.Select(t => t.recastResult).ToList();
}
}
} }

View File

@ -20,41 +20,38 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic namespace DotRecast.Detour.Dynamic
{ {
public class DynamicNavMeshConfig
{
public readonly bool useTiles;
public readonly int tileSizeX;
public readonly int tileSizeZ;
public readonly float cellSize;
public PartitionType partitionType = PartitionType.WATERSHED;
public AreaModification walkableAreaModification = new AreaModification(1);
public float walkableHeight;
public float walkableSlopeAngle;
public float walkableRadius;
public float walkableClimb;
public float minRegionArea;
public float regionMergeArea;
public float maxEdgeLen;
public float maxSimplificationError;
public int vertsPerPoly;
public bool buildDetailMesh;
public float detailSampleDistance;
public float detailSampleMaxError;
public bool filterLowHangingObstacles = true;
public bool filterLedgeSpans = true;
public bool filterWalkableLowHeightSpans = true;
public bool enableCheckpoints = true;
public bool keepIntermediateResults = false;
public DynamicNavMeshConfig(bool useTiles, int tileSizeX, int tileSizeZ, float cellSize)
public class DynamicNavMeshConfig { {
this.useTiles = useTiles;
public readonly bool useTiles; this.tileSizeX = tileSizeX;
public readonly int tileSizeX; this.tileSizeZ = tileSizeZ;
public readonly int tileSizeZ; this.cellSize = cellSize;
public readonly float cellSize; }
public PartitionType partitionType = PartitionType.WATERSHED;
public AreaModification walkableAreaModification = new AreaModification(1);
public float walkableHeight;
public float walkableSlopeAngle;
public float walkableRadius;
public float walkableClimb;
public float minRegionArea;
public float regionMergeArea;
public float maxEdgeLen;
public float maxSimplificationError;
public int vertsPerPoly;
public bool buildDetailMesh;
public float detailSampleDistance;
public float detailSampleMaxError;
public bool filterLowHangingObstacles = true;
public bool filterLedgeSpans = true;
public bool filterWalkableLowHeightSpans = true;
public bool enableCheckpoints = true;
public bool keepIntermediateResults = false;
public DynamicNavMeshConfig(bool useTiles, int tileSizeX, int tileSizeZ, float cellSize) {
this.useTiles = useTiles;
this.tileSizeX = tileSizeX;
this.tileSizeZ = tileSizeZ;
this.cellSize = cellSize;
} }
}
} }

View File

@ -27,131 +27,154 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic namespace DotRecast.Detour.Dynamic
{ {
public class DynamicTile
{
public readonly VoxelTile voxelTile;
public DynamicTileCheckpoint checkpoint;
public RecastBuilderResult recastResult;
MeshData meshData;
private readonly ConcurrentDictionary<long, Collider> colliders = new ConcurrentDictionary<long, Collider>();
private bool dirty = true;
private long id;
public DynamicTile(VoxelTile voxelTile)
{
this.voxelTile = voxelTile;
}
public class DynamicTile { public bool build(RecastBuilder builder, DynamicNavMeshConfig config, Telemetry telemetry)
{
public readonly VoxelTile voxelTile; if (dirty)
public DynamicTileCheckpoint checkpoint; {
public RecastBuilderResult recastResult; Heightfield heightfield = buildHeightfield(config, telemetry);
MeshData meshData; RecastBuilderResult r = buildRecast(builder, config, voxelTile, heightfield, telemetry);
private readonly ConcurrentDictionary<long, Collider> colliders = new ConcurrentDictionary<long, Collider>(); NavMeshDataCreateParams option = navMeshCreateParams(voxelTile.tileX, voxelTile.tileZ, voxelTile.cellSize,
private bool dirty = true;
private long id;
public DynamicTile(VoxelTile voxelTile) {
this.voxelTile = voxelTile;
}
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,
voxelTile.cellHeight, config, r); voxelTile.cellHeight, config, r);
meshData = NavMeshBuilder.createNavMeshData(option); meshData = NavMeshBuilder.createNavMeshData(option);
return true; return true;
}
return false;
}
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)) {
heightfield.bmax[1] = Math.Max(heightfield.bmax[1], c.bounds()[4] + heightfield.ch * 2);
c.rasterize(heightfield, telemetry);
} }
}
if (config.enableCheckpoints) {
checkpoint = new DynamicTileCheckpoint(heightfield, colliders.Keys.ToHashSet());
}
return heightfield;
}
private RecastBuilderResult buildRecast(RecastBuilder builder, DynamicNavMeshConfig config, VoxelTile vt, return false;
Heightfield heightfield, Telemetry telemetry) { }
RecastConfig rcConfig = new RecastConfig(config.useTiles, config.tileSizeX, config.tileSizeZ, vt.borderSize,
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))
{
heightfield.bmax[1] = Math.Max(heightfield.bmax[1], c.bounds()[4] + heightfield.ch * 2);
c.rasterize(heightfield, telemetry);
}
}
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)
{
RecastConfig rcConfig = new RecastConfig(config.useTiles, config.tileSizeX, config.tileSizeZ, vt.borderSize,
config.partitionType, vt.cellSize, vt.cellHeight, config.walkableSlopeAngle, true, true, true, config.partitionType, vt.cellSize, vt.cellHeight, config.walkableSlopeAngle, true, true, true,
config.walkableHeight, config.walkableRadius, config.walkableClimb, config.minRegionArea, config.regionMergeArea, config.walkableHeight, config.walkableRadius, config.walkableClimb, config.minRegionArea, config.regionMergeArea,
config.maxEdgeLen, config.maxSimplificationError, config.maxEdgeLen, config.maxSimplificationError,
Math.Min(DynamicNavMesh.MAX_VERTS_PER_POLY, config.vertsPerPoly), true, config.detailSampleDistance, Math.Min(DynamicNavMesh.MAX_VERTS_PER_POLY, config.vertsPerPoly), true, config.detailSampleDistance,
config.detailSampleMaxError, null); config.detailSampleMaxError, null);
RecastBuilderResult r = builder.build(vt.tileX, vt.tileZ, null, rcConfig, heightfield, telemetry); RecastBuilderResult r = builder.build(vt.tileX, vt.tileZ, null, rcConfig, heightfield, telemetry);
if (config.keepIntermediateResults) { if (config.keepIntermediateResults)
recastResult = r; {
recastResult = r;
}
return r;
} }
return r;
}
public void addCollider(long cid, Collider collider) { public void addCollider(long cid, Collider collider)
colliders[cid] = collider; {
dirty = true; colliders[cid] = collider;
}
public bool containsCollider(long cid) {
return colliders.ContainsKey(cid);
}
public void removeCollider(long colliderId) {
if (colliders.TryRemove(colliderId, out var collider)) {
dirty = true; dirty = true;
checkpoint = null; }
public bool containsCollider(long cid)
{
return colliders.ContainsKey(cid);
}
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)
{
PolyMesh m_pmesh = rcResult.getMesh();
PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
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;
option.vertCount = m_pmesh.nverts;
option.polys = m_pmesh.polys;
option.polyAreas = m_pmesh.areas;
option.polyFlags = m_pmesh.flags;
option.polyCount = m_pmesh.npolys;
option.nvp = m_pmesh.nvp;
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;
option.bmin = m_pmesh.bmin;
option.bmax = m_pmesh.bmax;
option.cs = cellSize;
option.ch = cellHeight;
option.buildBvTree = true;
option.offMeshConCount = 0;
option.offMeshConVerts = new float[0];
option.offMeshConRad = new float[0];
option.offMeshConDir = new int[0];
option.offMeshConAreas = new int[0];
option.offMeshConFlags = new int[0];
option.offMeshConUserID = new int[0];
return option;
}
public void addTo(NavMesh navMesh)
{
if (meshData != null)
{
id = navMesh.addTile(meshData, 0, 0);
}
else
{
navMesh.removeTile(id);
id = 0;
}
} }
} }
private NavMeshDataCreateParams navMeshCreateParams(int tilex, int tileZ, float cellSize, float cellHeight,
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) {
m_pmesh.flags[i] = 1;
}
option.tileX = tilex;
option.tileZ = tileZ;
option.verts = m_pmesh.verts;
option.vertCount = m_pmesh.nverts;
option.polys = m_pmesh.polys;
option.polyAreas = m_pmesh.areas;
option.polyFlags = m_pmesh.flags;
option.polyCount = m_pmesh.npolys;
option.nvp = m_pmesh.nvp;
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;
option.bmin = m_pmesh.bmin;
option.bmax = m_pmesh.bmax;
option.cs = cellSize;
option.ch = cellHeight;
option.buildBvTree = true;
option.offMeshConCount = 0;
option.offMeshConVerts = new float[0];
option.offMeshConRad = new float[0];
option.offMeshConDir = new int[0];
option.offMeshConAreas = new int[0];
option.offMeshConFlags = new int[0];
option.offMeshConUserID = new int[0];
return option;
}
public void addTo(NavMesh navMesh) {
if (meshData != null) {
id = navMesh.addTile(meshData, 0, 0);
} else {
navMesh.removeTile(id);
id = 0;
}
}
}
} }

View File

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

View File

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

View File

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

View File

@ -23,130 +23,140 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Io namespace DotRecast.Detour.Dynamic.Io
{ {
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;
public const int VERSION_COMPRESSION_MASK = 0x0F00;
public const int VERSION_EXPORTER_RECAST4J = 0x1000;
public const int VERSION_COMPRESSION_LZ4 = 0x0100;
public int version;
public PartitionType partitionType = PartitionType.WATERSHED;
public bool filterLowHangingObstacles = true;
public bool filterLedgeSpans = true;
public bool filterWalkableLowHeightSpans = true;
public float walkableRadius;
public float walkableHeight;
public float walkableClimb;
public float walkableSlopeAngle;
public float cellSize;
public float maxSimplificationError;
public float maxEdgeLen;
public float minRegionArea;
public float regionMergeArea;
public int vertsPerPoly;
public bool buildMeshDetail;
public float detailSampleDistance;
public float detailSampleMaxError;
public bool useTiles;
public int tileSizeX;
public int tileSizeZ;
public float[] rotation = new float[3];
public float[] bounds = new float[6];
public readonly List<VoxelTile> tiles = new List<VoxelTile>();
public void addTile(VoxelTile tile)
{
tiles.Add(tile);
}
public class VoxelFile { public RecastConfig getConfig(VoxelTile tile, PartitionType partitionType, int maxPolyVerts, int regionMergeSize,
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;
public const int VERSION_COMPRESSION_MASK = 0x0F00;
public const int VERSION_EXPORTER_RECAST4J = 0x1000;
public const int VERSION_COMPRESSION_LZ4 = 0x0100;
public int version;
public PartitionType partitionType = PartitionType.WATERSHED;
public bool filterLowHangingObstacles = true;
public bool filterLedgeSpans = true;
public bool filterWalkableLowHeightSpans = true;
public float walkableRadius;
public float walkableHeight;
public float walkableClimb;
public float walkableSlopeAngle;
public float cellSize;
public float maxSimplificationError;
public float maxEdgeLen;
public float minRegionArea;
public float regionMergeArea;
public int vertsPerPoly;
public bool buildMeshDetail;
public float detailSampleDistance;
public float detailSampleMaxError;
public bool useTiles;
public int tileSizeX;
public int tileSizeZ;
public float[] rotation = new float[3];
public float[] bounds = new float[6];
public readonly List<VoxelTile> tiles = new List<VoxelTile>();
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, bool filterLowHangingObstacles, bool filterLedgeSpans, bool filterWalkableLowHeightSpans,
AreaModification walkbableAreaMod, bool buildMeshDetail, float detailSampleDist, float detailSampleMaxError) { AreaModification walkbableAreaMod, bool buildMeshDetail, float detailSampleDist, float detailSampleMaxError)
return new RecastConfig(useTiles, tileSizeX, tileSizeZ, tile.borderSize, partitionType, cellSize, tile.cellHeight, {
return new RecastConfig(useTiles, tileSizeX, tileSizeZ, tile.borderSize, partitionType, cellSize, tile.cellHeight,
walkableSlopeAngle, filterLowHangingObstacles, filterLedgeSpans, filterWalkableLowHeightSpans, walkableHeight, walkableSlopeAngle, filterLowHangingObstacles, filterLedgeSpans, filterWalkableLowHeightSpans, walkableHeight,
walkableRadius, walkableClimb, minRegionArea, regionMergeArea, maxEdgeLen, maxSimplificationError, maxPolyVerts, walkableRadius, walkableClimb, minRegionArea, regionMergeArea, maxEdgeLen, maxSimplificationError, maxPolyVerts,
buildMeshDetail, detailSampleDist, detailSampleMaxError, walkbableAreaMod); buildMeshDetail, detailSampleDist, detailSampleMaxError, walkbableAreaMod);
}
public static VoxelFile from(RecastConfig config, List<RecastBuilderResult> results) {
VoxelFile f = new VoxelFile();
f.version = 1;
f.partitionType = config.partitionType;
f.filterLowHangingObstacles = config.filterLowHangingObstacles;
f.filterLedgeSpans = config.filterLedgeSpans;
f.filterWalkableLowHeightSpans = config.filterWalkableLowHeightSpans;
f.walkableRadius = config.walkableRadiusWorld;
f.walkableHeight = config.walkableHeightWorld;
f.walkableClimb = config.walkableClimbWorld;
f.walkableSlopeAngle = config.walkableSlopeAngle;
f.cellSize = config.cs;
f.maxSimplificationError = config.maxSimplificationError;
f.maxEdgeLen = config.maxEdgeLenWorld;
f.minRegionArea = config.minRegionAreaWorld;
f.regionMergeArea = config.mergeRegionAreaWorld;
f.vertsPerPoly = config.maxVertsPerPoly;
f.buildMeshDetail = config.buildMeshDetail;
f.detailSampleDistance = config.detailSampleDist;
f.detailSampleMaxError = config.detailSampleMaxError;
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.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]);
f.bounds[2] = Math.Min(f.bounds[2], r.getSolidHeightfield().bmin[2]);
f.bounds[3] = Math.Max(f.bounds[3], r.getSolidHeightfield().bmax[0]);
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(RecastConfig config, List<RecastBuilderResult> results)
VoxelFile f = new VoxelFile(); {
f.version = 1; VoxelFile f = new VoxelFile();
DynamicNavMeshConfig config = mesh.config; f.version = 1;
f.partitionType = config.partitionType; f.partitionType = config.partitionType;
f.filterLowHangingObstacles = config.filterLowHangingObstacles; f.filterLowHangingObstacles = config.filterLowHangingObstacles;
f.filterLedgeSpans = config.filterLedgeSpans; f.filterLedgeSpans = config.filterLedgeSpans;
f.filterWalkableLowHeightSpans = config.filterWalkableLowHeightSpans; f.filterWalkableLowHeightSpans = config.filterWalkableLowHeightSpans;
f.walkableRadius = config.walkableRadius; f.walkableRadius = config.walkableRadiusWorld;
f.walkableHeight = config.walkableHeight; f.walkableHeight = config.walkableHeightWorld;
f.walkableClimb = config.walkableClimb; f.walkableClimb = config.walkableClimbWorld;
f.walkableSlopeAngle = config.walkableSlopeAngle; f.walkableSlopeAngle = config.walkableSlopeAngle;
f.cellSize = config.cellSize; f.cellSize = config.cs;
f.maxSimplificationError = config.maxSimplificationError; f.maxSimplificationError = config.maxSimplificationError;
f.maxEdgeLen = config.maxEdgeLen; f.maxEdgeLen = config.maxEdgeLenWorld;
f.minRegionArea = config.minRegionArea; f.minRegionArea = config.minRegionAreaWorld;
f.regionMergeArea = config.regionMergeArea; f.regionMergeArea = config.mergeRegionAreaWorld;
f.vertsPerPoly = config.vertsPerPoly; f.vertsPerPoly = config.maxVertsPerPoly;
f.buildMeshDetail = config.buildDetailMesh; f.buildMeshDetail = config.buildMeshDetail;
f.detailSampleDistance = config.detailSampleDistance; f.detailSampleDistance = config.detailSampleDist;
f.detailSampleMaxError = config.detailSampleMaxError; f.detailSampleMaxError = config.detailSampleMaxError;
f.useTiles = config.useTiles; f.useTiles = config.useTiles;
f.tileSizeX = config.tileSizeX; f.tileSizeX = config.tileSizeX;
f.tileSizeZ = config.tileSizeZ; f.tileSizeZ = config.tileSizeZ;
f.bounds = new float[] { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, f.bounds = new float[]
float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity }; {
foreach (VoxelTile vt in mesh.voxelTiles()) { float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity,
Heightfield heightfield = vt.heightfield(); float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity
f.tiles.Add(new VoxelTile(vt.tileX, vt.tileZ, heightfield)); };
f.bounds[0] = Math.Min(f.bounds[0], vt.boundsMin[0]); foreach (RecastBuilderResult r in results)
f.bounds[1] = Math.Min(f.bounds[1], vt.boundsMin[1]); {
f.bounds[2] = Math.Min(f.bounds[2], vt.boundsMin[2]); f.tiles.Add(new VoxelTile(r.tileX, r.tileZ, r.getSolidHeightfield()));
f.bounds[3] = Math.Max(f.bounds[3], vt.boundsMax[0]); f.bounds[0] = Math.Min(f.bounds[0], r.getSolidHeightfield().bmin[0]);
f.bounds[4] = Math.Max(f.bounds[4], vt.boundsMax[1]); f.bounds[1] = Math.Min(f.bounds[1], r.getSolidHeightfield().bmin[1]);
f.bounds[5] = Math.Max(f.bounds[5], vt.boundsMax[2]); f.bounds[2] = Math.Min(f.bounds[2], r.getSolidHeightfield().bmin[2]);
f.bounds[3] = Math.Max(f.bounds[3], r.getSolidHeightfield().bmax[0]);
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)
{
VoxelFile f = new VoxelFile();
f.version = 1;
DynamicNavMeshConfig config = mesh.config;
f.partitionType = config.partitionType;
f.filterLowHangingObstacles = config.filterLowHangingObstacles;
f.filterLedgeSpans = config.filterLedgeSpans;
f.filterWalkableLowHeightSpans = config.filterWalkableLowHeightSpans;
f.walkableRadius = config.walkableRadius;
f.walkableHeight = config.walkableHeight;
f.walkableClimb = config.walkableClimb;
f.walkableSlopeAngle = config.walkableSlopeAngle;
f.cellSize = config.cellSize;
f.maxSimplificationError = config.maxSimplificationError;
f.maxEdgeLen = config.maxEdgeLen;
f.minRegionArea = config.minRegionArea;
f.regionMergeArea = config.regionMergeArea;
f.vertsPerPoly = config.vertsPerPoly;
f.buildMeshDetail = config.buildDetailMesh;
f.detailSampleDistance = config.detailSampleDistance;
f.detailSampleMaxError = config.detailSampleMaxError;
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())
{
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]);
f.bounds[1] = Math.Min(f.bounds[1], vt.boundsMin[1]);
f.bounds[2] = Math.Min(f.bounds[2], vt.boundsMin[2]);
f.bounds[3] = Math.Max(f.bounds[3], vt.boundsMax[0]);
f.bounds[4] = Math.Max(f.bounds[4], vt.boundsMax[1]);
f.bounds[5] = Math.Max(f.bounds[5], vt.boundsMax[2]);
}
return f;
} }
return f;
} }
}
} }

View File

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

View File

@ -22,72 +22,74 @@ using DotRecast.Detour.Io;
namespace DotRecast.Detour.Dynamic.Io namespace DotRecast.Detour.Dynamic.Io
{ {
public class VoxelFileWriter : DetourWriter
{
private readonly LZ4VoxelTileCompressor compressor = new LZ4VoxelTileCompressor();
public void write(BinaryWriter stream, VoxelFile f, bool compression)
{
write(stream, f, VoxelFile.PREFERRED_BYTE_ORDER, compression);
}
public class VoxelFileWriter : DetourWriter { 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);
write(stream, f.walkableHeight, byteOrder);
write(stream, f.walkableClimb, byteOrder);
write(stream, f.walkableSlopeAngle, byteOrder);
write(stream, f.cellSize, byteOrder);
write(stream, f.maxSimplificationError, byteOrder);
write(stream, f.maxEdgeLen, byteOrder);
write(stream, f.minRegionArea, byteOrder);
write(stream, f.regionMergeArea, byteOrder);
write(stream, f.vertsPerPoly, byteOrder);
write(stream, f.buildMeshDetail);
write(stream, f.detailSampleDistance, byteOrder);
write(stream, f.detailSampleMaxError, byteOrder);
write(stream, f.useTiles);
write(stream, f.tileSizeX, byteOrder);
write(stream, f.tileSizeZ, byteOrder);
write(stream, f.rotation[0], byteOrder);
write(stream, f.rotation[1], byteOrder);
write(stream, f.rotation[2], byteOrder);
write(stream, f.bounds[0], byteOrder);
write(stream, f.bounds[1], byteOrder);
write(stream, f.bounds[2], byteOrder);
write(stream, f.bounds[3], byteOrder);
write(stream, f.bounds[4], byteOrder);
write(stream, f.bounds[5], byteOrder);
write(stream, f.tiles.Count, byteOrder);
foreach (VoxelTile t in f.tiles)
{
writeTile(stream, t, byteOrder, compression);
}
}
private readonly LZ4VoxelTileCompressor compressor = new LZ4VoxelTileCompressor(); 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);
write(stream, tile.depth, byteOrder);
write(stream, tile.borderSize, byteOrder);
write(stream, tile.boundsMin[0], byteOrder);
write(stream, tile.boundsMin[1], byteOrder);
write(stream, tile.boundsMin[2], byteOrder);
write(stream, tile.boundsMax[0], byteOrder);
write(stream, tile.boundsMax[1], byteOrder);
write(stream, tile.boundsMax[2], byteOrder);
write(stream, tile.cellSize, byteOrder);
write(stream, tile.cellHeight, byteOrder);
byte[] bytes = tile.spanData;
if (compression)
{
bytes = compressor.compress(bytes);
}
public void write(BinaryWriter stream, VoxelFile f, bool compression) { write(stream, bytes.Length, byteOrder);
write(stream, f, VoxelFile.PREFERRED_BYTE_ORDER, compression); stream.Write(bytes);
}
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);
write(stream, f.walkableHeight, byteOrder);
write(stream, f.walkableClimb, byteOrder);
write(stream, f.walkableSlopeAngle, byteOrder);
write(stream, f.cellSize, byteOrder);
write(stream, f.maxSimplificationError, byteOrder);
write(stream, f.maxEdgeLen, byteOrder);
write(stream, f.minRegionArea, byteOrder);
write(stream, f.regionMergeArea, byteOrder);
write(stream, f.vertsPerPoly, byteOrder);
write(stream, f.buildMeshDetail);
write(stream, f.detailSampleDistance, byteOrder);
write(stream, f.detailSampleMaxError, byteOrder);
write(stream, f.useTiles);
write(stream, f.tileSizeX, byteOrder);
write(stream, f.tileSizeZ, byteOrder);
write(stream, f.rotation[0], byteOrder);
write(stream, f.rotation[1], byteOrder);
write(stream, f.rotation[2], byteOrder);
write(stream, f.bounds[0], byteOrder);
write(stream, f.bounds[1], byteOrder);
write(stream, f.bounds[2], byteOrder);
write(stream, f.bounds[3], byteOrder);
write(stream, f.bounds[4], byteOrder);
write(stream, f.bounds[5], byteOrder);
write(stream, f.tiles.Count, byteOrder);
foreach (VoxelTile t in f.tiles) {
writeTile(stream, t, byteOrder, 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);
write(stream, tile.depth, byteOrder);
write(stream, tile.borderSize, byteOrder);
write(stream, tile.boundsMin[0], byteOrder);
write(stream, tile.boundsMin[1], byteOrder);
write(stream, tile.boundsMin[2], byteOrder);
write(stream, tile.boundsMax[0], byteOrder);
write(stream, tile.boundsMax[1], byteOrder);
write(stream, tile.boundsMax[2], byteOrder);
write(stream, tile.cellSize, byteOrder);
write(stream, tile.cellHeight, byteOrder);
byte[] bytes = tile.spanData;
if (compression) {
bytes = compressor.compress(bytes);
}
write(stream, bytes.Length, byteOrder);
stream.Write(bytes);
}
}
} }

View File

@ -21,165 +21,198 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Io namespace DotRecast.Detour.Dynamic.Io
{ {
public class VoxelTile
{
private const int SERIALIZED_SPAN_COUNT_BYTES = 2;
private const int SERIALIZED_SPAN_BYTES = 12;
public readonly int tileX;
public readonly int tileZ;
public readonly int borderSize;
public int width;
public int depth;
public readonly float[] boundsMin;
public float[] boundsMax;
public float cellSize;
public float cellHeight;
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)
public class VoxelTile { {
this.tileX = tileX;
private const int SERIALIZED_SPAN_COUNT_BYTES = 2; this.tileZ = tileZ;
private const int SERIALIZED_SPAN_BYTES = 12; this.width = width;
public readonly int tileX; this.depth = depth;
public readonly int tileZ; this.boundsMin = boundsMin;
public readonly int borderSize; this.boundsMax = boundsMax;
public int width; this.cellSize = cellSize;
public int depth; this.cellHeight = cellHeight;
public readonly float[] boundsMin; this.borderSize = borderSize;
public float[] boundsMax; spanData = toByteArray(buffer, width, depth, VoxelFile.PREFERRED_BYTE_ORDER);
public float cellSize;
public float cellHeight;
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) {
this.tileX = tileX;
this.tileZ = tileZ;
this.width = width;
this.depth = depth;
this.boundsMin = boundsMin;
this.boundsMax = boundsMax;
this.cellSize = cellSize;
this.cellHeight = cellHeight;
this.borderSize = borderSize;
spanData = toByteArray(buffer, width, depth, VoxelFile.PREFERRED_BYTE_ORDER);
}
public VoxelTile(int tileX, int tileZ, Heightfield heightfield) {
this.tileX = tileX;
this.tileZ = tileZ;
width = heightfield.width;
depth = heightfield.height;
boundsMin = heightfield.bmin;
boundsMax = heightfield.bmax;
cellSize = heightfield.cs;
cellHeight = heightfield.ch;
borderSize = heightfield.borderSize;
spanData = serializeSpans(heightfield, VoxelFile.PREFERRED_BYTE_ORDER);
}
public Heightfield heightfield() {
return VoxelFile.PREFERRED_BYTE_ORDER == ByteOrder.BIG_ENDIAN ? heightfieldBE() : heightfieldLE();
}
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++) {
Span prev = null;
int spanCount = ByteUtils.getShortBE(spanData, position);
position += 2;
for (int s = 0; s < spanCount; s++) {
Span span = new Span();
span.smin = ByteUtils.getIntBE(spanData, position);
position += 4;
span.smax = ByteUtils.getIntBE(spanData, position);
position += 4;
span.area = ByteUtils.getIntBE(spanData, position);
position += 4;
if (prev == null) {
hf.spans[pz + x] = span;
} else {
prev.next = span;
}
prev = span;
}
}
} }
return hf;
}
private Heightfield heightfieldLE() { public VoxelTile(int tileX, int tileZ, Heightfield heightfield)
Heightfield hf = new Heightfield(width, depth, boundsMin, boundsMax, cellSize, cellHeight, borderSize); {
int position = 0; this.tileX = tileX;
for (int z = 0, pz = 0; z < depth; z++, pz += width) { this.tileZ = tileZ;
for (int x = 0; x < width; x++) { width = heightfield.width;
Span prev = null; depth = heightfield.height;
int spanCount = ByteUtils.getShortLE(spanData, position); boundsMin = heightfield.bmin;
position += 2; boundsMax = heightfield.bmax;
for (int s = 0; s < spanCount; s++) { cellSize = heightfield.cs;
Span span = new Span(); cellHeight = heightfield.ch;
span.smin = ByteUtils.getIntLE(spanData, position); borderSize = heightfield.borderSize;
position += 4; spanData = serializeSpans(heightfield, VoxelFile.PREFERRED_BYTE_ORDER);
span.smax = ByteUtils.getIntLE(spanData, position);
position += 4;
span.area = ByteUtils.getIntLE(spanData, position);
position += 4;
if (prev == null) {
hf.spans[pz + x] = span;
} else {
prev.next = span;
}
prev = span;
}
}
} }
return hf;
}
private byte[] serializeSpans(Heightfield heightfield, ByteOrder order) { public Heightfield heightfield()
int[] counts = new int[heightfield.width * heightfield.height]; {
int totalCount = 0; return VoxelFile.PREFERRED_BYTE_ORDER == ByteOrder.BIG_ENDIAN ? heightfieldBE() : heightfieldLE();
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) {
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++) {
position = ByteUtils.putShort(counts[pz + x], data, position, order);
Span span = heightfield.spans[pz + x];
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);
span = span.next;
}
}
}
return data;
}
private byte[] toByteArray(ByteBuffer buf, int width, int height, ByteOrder order) { private Heightfield heightfieldBE()
byte[] data; {
if (buf.order() == order) { Heightfield hf = new Heightfield(width, depth, boundsMin, boundsMax, cellSize, cellHeight, borderSize);
data = buf.ReadBytes(buf.limit()).ToArray();
} else {
data = new byte[buf.limit()];
int l = width * height;
int position = 0; int position = 0;
for (int i = 0; i < l; i++) { for (int z = 0, pz = 0; z < depth; z++, pz += width)
int count = buf.getShort(); {
ByteUtils.putShort(count, data, position, order); for (int x = 0; x < width; x++)
position += 2; {
for (int j = 0; j < count; j++) { Span prev = null;
ByteUtils.putInt(buf.getInt(), data, position, order); int spanCount = ByteUtils.getShortBE(spanData, position);
position += 4; position += 2;
ByteUtils.putInt(buf.getInt(), data, position, order); for (int s = 0; s < spanCount; s++)
position += 4; {
ByteUtils.putInt(buf.getInt(), data, position, order); Span span = new Span();
position += 4; span.smin = ByteUtils.getIntBE(spanData, position);
position += 4;
span.smax = ByteUtils.getIntBE(spanData, position);
position += 4;
span.area = ByteUtils.getIntBE(spanData, position);
position += 4;
if (prev == null)
{
hf.spans[pz + x] = span;
}
else
{
prev.next = span;
}
prev = span;
}
} }
} }
}
return data;
}
}
return hf;
}
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++)
{
Span prev = null;
int spanCount = ByteUtils.getShortLE(spanData, position);
position += 2;
for (int s = 0; s < spanCount; s++)
{
Span span = new Span();
span.smin = ByteUtils.getIntLE(spanData, position);
position += 4;
span.smax = ByteUtils.getIntLE(spanData, position);
position += 4;
span.area = ByteUtils.getIntLE(spanData, position);
position += 4;
if (prev == null)
{
hf.spans[pz + x] = span;
}
else
{
prev.next = span;
}
prev = span;
}
}
}
return hf;
}
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++)
{
Span span = heightfield.spans[pz + x];
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++)
{
position = ByteUtils.putShort(counts[pz + x], data, position, order);
Span span = heightfield.spans[pz + x];
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);
span = span.next;
}
}
}
return data;
}
private byte[] toByteArray(ByteBuffer buf, int width, int height, ByteOrder order)
{
byte[] data;
if (buf.order() == order)
{
data = buf.ReadBytes(buf.limit()).ToArray();
}
else
{
data = new byte[buf.limit()];
int l = width * height;
int position = 0;
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++)
{
ByteUtils.putInt(buf.getInt(), data, position, order);
position += 4;
ByteUtils.putInt(buf.getInt(), data, position, order);
position += 4;
ByteUtils.putInt(buf.getInt(), data, position, order);
position += 4;
}
}
}
return data;
}
}
} }

View File

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

View File

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

View File

@ -21,144 +21,163 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic namespace DotRecast.Detour.Dynamic
{ {
/**
/**
* Voxel raycast based on the algorithm described in * Voxel raycast based on the algorithm described in
* *
* "A Fast Voxel Traversal Algorithm for Ray Tracing" by John Amanatides and Andrew Woo * "A Fast Voxel Traversal Algorithm for Ray Tracing" by John Amanatides and Andrew Woo
*/ */
public class VoxelQuery { public class VoxelQuery
{
private readonly float[] origin;
private readonly float tileWidth;
private readonly float tileDepth;
private readonly Func<int, int, Heightfield> heightfieldProvider;
private readonly float[] origin; public VoxelQuery(float[] origin, float tileWidth, float tileDepth, Func<int, int, Heightfield> heightfieldProvider)
private readonly float tileWidth; {
private readonly float tileDepth; this.origin = origin;
private readonly Func<int, int, Heightfield> heightfieldProvider; this.tileWidth = tileWidth;
this.tileDepth = tileDepth;
this.heightfieldProvider = heightfieldProvider;
}
public VoxelQuery(float[] origin, float tileWidth, float tileDepth, Func<int, int, Heightfield> heightfieldProvider) { /**
this.origin = origin;
this.tileWidth = tileWidth;
this.tileDepth = tileDepth;
this.heightfieldProvider = heightfieldProvider;
}
/**
* Perform raycast using voxels heightfields. * Perform raycast using voxels heightfields.
* *
* @return Optional with hit parameter (t) or empty if no hit found * @return Optional with hit parameter (t) or empty if no hit found
*/ */
public float? raycast(float[] start, float[] end) { public float? raycast(float[] start, float[] end)
return traverseTiles(start, end); {
} return traverseTiles(start, end);
private float? traverseTiles(float[] start, float[] end) {
float relStartX = start[0] - origin[0];
float relStartZ = start[2] - origin[2];
int sx = (int) Math.Floor(relStartX / tileWidth);
int sz = (int) Math.Floor(relStartZ / tileDepth);
int ex = (int) Math.Floor((end[0] - origin[0]) / tileWidth);
int ez = (int) Math.Floor((end[2] - origin[2]) / tileDepth);
int dx = ex - sx;
int dz = ez - sz;
int stepX = dx < 0 ? -1 : 1;
int stepZ = dz < 0 ? -1 : 1;
float xRem = (tileWidth + (relStartX % tileWidth)) % tileWidth;
float zRem = (tileDepth + (relStartZ % tileDepth)) % tileDepth;
float tx = end[0] - start[0];
float tz = end[2] - start[2];
float xOffest = Math.Abs(tx < 0 ? xRem : tileWidth - xRem);
float zOffest = Math.Abs(tz < 0 ? zRem : tileDepth - zRem);
tx = Math.Abs(tx);
tz = Math.Abs(tz);
float tMaxX = xOffest / tx;
float tMaxZ = zOffest / tz;
float tDeltaX = tileWidth / tx;
float tDeltaZ = tileDepth / tz;
float t = 0;
while (true) {
float? hit = traversHeightfield(sx, sz, start, end, t, Math.Min(1, Math.Min(tMaxX, tMaxZ)));
if (hit.HasValue) {
return hit;
}
if ((dx > 0 ? sx >= ex : sx <= ex) && (dz > 0 ? sz >= ez : sz <= ez)) {
break;
}
if (tMaxX < tMaxZ) {
t = tMaxX;
tMaxX += tDeltaX;
sx += stepX;
} else {
t = tMaxZ;
tMaxZ += tDeltaZ;
sz += stepZ;
}
} }
return null; private float? traverseTiles(float[] start, float[] end)
} {
float relStartX = start[0] - origin[0];
private float? traversHeightfield(int x, int z, float[] start, float[] end, float tMin, float tMax) { float relStartZ = start[2] - origin[2];
Heightfield hf = heightfieldProvider.Invoke(x, z); int sx = (int)Math.Floor(relStartX / tileWidth);
if (null != hf) { int sz = (int)Math.Floor(relStartZ / tileDepth);
float tx = end[0] - start[0]; int ex = (int)Math.Floor((end[0] - origin[0]) / tileWidth);
float ty = end[1] - start[1]; int ez = (int)Math.Floor((end[2] - origin[2]) / tileDepth);
float tz = end[2] - start[2];
float[] entry = { start[0] + tMin * tx, start[1] + tMin * ty, start[2] + tMin * tz };
float[] exit = { start[0] + tMax * tx, start[1] + tMax * ty, start[2] + tMax * tz };
float relStartX = entry[0] - hf.bmin[0];
float relStartZ = entry[2] - hf.bmin[2];
int sx = (int) Math.Floor(relStartX / hf.cs);
int sz = (int) Math.Floor(relStartZ / hf.cs);
int ex = (int) Math.Floor((exit[0] - hf.bmin[0]) / hf.cs);
int ez = (int) Math.Floor((exit[2] - hf.bmin[2]) / hf.cs);
int dx = ex - sx; int dx = ex - sx;
int dz = ez - sz; int dz = ez - sz;
int stepX = dx < 0 ? -1 : 1; int stepX = dx < 0 ? -1 : 1;
int stepZ = dz < 0 ? -1 : 1; int stepZ = dz < 0 ? -1 : 1;
float xRem = (hf.cs + (relStartX % hf.cs)) % hf.cs; float xRem = (tileWidth + (relStartX % tileWidth)) % tileWidth;
float zRem = (hf.cs + (relStartZ % hf.cs)) % hf.cs; float zRem = (tileDepth + (relStartZ % tileDepth)) % tileDepth;
float xOffest = Math.Abs(tx < 0 ? xRem : hf.cs - xRem); float tx = end[0] - start[0];
float zOffest = Math.Abs(tz < 0 ? zRem : hf.cs - zRem); float tz = end[2] - start[2];
float xOffest = Math.Abs(tx < 0 ? xRem : tileWidth - xRem);
float zOffest = Math.Abs(tz < 0 ? zRem : tileDepth - zRem);
tx = Math.Abs(tx); tx = Math.Abs(tx);
tz = Math.Abs(tz); tz = Math.Abs(tz);
float tMaxX = xOffest / tx; float tMaxX = xOffest / tx;
float tMaxZ = zOffest / tz; float tMaxZ = zOffest / tz;
float tDeltaX = hf.cs / tx; float tDeltaX = tileWidth / tx;
float tDeltaZ = hf.cs / tz; float tDeltaZ = tileDepth / tz;
float t = 0; float t = 0;
while (true) { while (true)
if (sx >= 0 && sx < hf.width && sz >= 0 && sz < hf.height) { {
float y1 = start[1] + ty * (tMin + t) - hf.bmin[1]; float? hit = traversHeightfield(sx, sz, start, end, t, Math.Min(1, Math.Min(tMaxX, tMaxZ)));
float y2 = start[1] + ty * (tMin + Math.Min(tMaxX, tMaxZ)) - hf.bmin[1]; if (hit.HasValue)
float ymin = Math.Min(y1, y2) / hf.ch; {
float ymax = Math.Max(y1, y2) / hf.ch; return hit;
Span span = hf.spans[sx + sz * hf.width];
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; break;
} }
if (tMaxX < tMaxZ) {
if (tMaxX < tMaxZ)
{
t = tMaxX; t = tMaxX;
tMaxX += tDeltaX; tMaxX += tDeltaX;
sx += stepX; sx += stepX;
} else { }
else
{
t = tMaxZ; t = tMaxZ;
tMaxZ += tDeltaZ; tMaxZ += tDeltaZ;
sz += stepZ; sz += stepZ;
} }
} }
return null;
} }
return null; private float? traversHeightfield(int x, int z, float[] start, float[] end, float tMin, float tMax)
{
Heightfield hf = heightfieldProvider.Invoke(x, z);
if (null != hf)
{
float tx = end[0] - start[0];
float ty = end[1] - start[1];
float tz = end[2] - start[2];
float[] entry = { start[0] + tMin * tx, start[1] + tMin * ty, start[2] + tMin * tz };
float[] exit = { start[0] + tMax * tx, start[1] + tMax * ty, start[2] + tMax * tz };
float relStartX = entry[0] - hf.bmin[0];
float relStartZ = entry[2] - hf.bmin[2];
int sx = (int)Math.Floor(relStartX / hf.cs);
int sz = (int)Math.Floor(relStartZ / hf.cs);
int ex = (int)Math.Floor((exit[0] - hf.bmin[0]) / hf.cs);
int ez = (int)Math.Floor((exit[2] - hf.bmin[2]) / hf.cs);
int dx = ex - sx;
int dz = ez - sz;
int stepX = dx < 0 ? -1 : 1;
int stepZ = dz < 0 ? -1 : 1;
float xRem = (hf.cs + (relStartX % hf.cs)) % hf.cs;
float zRem = (hf.cs + (relStartZ % hf.cs)) % hf.cs;
float xOffest = Math.Abs(tx < 0 ? xRem : hf.cs - xRem);
float zOffest = Math.Abs(tz < 0 ? zRem : hf.cs - zRem);
tx = Math.Abs(tx);
tz = Math.Abs(tz);
float tMaxX = xOffest / tx;
float tMaxZ = zOffest / tz;
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)
{
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)
{
return Math.Min(1, tMin + t);
}
span = span.next;
}
}
if ((dx > 0 ? sx >= ex : sx <= ex) && (dz > 0 ? sz >= ez : sz <= ez))
{
break;
}
if (tMaxX < tMaxZ)
{
t = tMaxX;
tMaxX += tDeltaX;
sx += stepX;
}
else
{
t = tMaxZ;
tMaxZ += tDeltaZ;
sz += stepZ;
}
}
}
return null;
}
} }
}
} }

View File

@ -20,40 +20,43 @@ using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Extras namespace DotRecast.Detour.Extras
{ {
public class BVTreeBuilder
{
public class BVTreeBuilder { public void build(MeshData data)
{
public void build(MeshData data) { data.bvTree = new BVNode[data.header.polyCount * 2];
data.bvTree = new BVNode[data.header.polyCount * 2]; data.header.bvNodeCount = data.bvTree.Length == 0
data.header.bvNodeCount = data.bvTree.Length == 0 ? 0 ? 0
: createBVTree(data, data.bvTree, data.header.bvQuantFactor); : createBVTree(data, data.bvTree, data.header.bvQuantFactor);
}
private static int createBVTree(MeshData data, BVNode[] nodes, float quantFactor) {
NavMeshBuilder.BVItem[] items = new NavMeshBuilder.BVItem[data.header.polyCount];
for (int i = 0; i < data.header.polyCount; i++) {
NavMeshBuilder.BVItem it = new NavMeshBuilder.BVItem();
items[i] = it;
it.i = i;
float[] bmin = new float[3];
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++) {
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);
it.bmax[0] = clamp((int) ((bmax[0] - data.header.bmin[0]) * quantFactor), 0, 0x7fffffff);
it.bmax[1] = clamp((int) ((bmax[1] - data.header.bmin[1]) * quantFactor), 0, 0x7fffffff);
it.bmax[2] = clamp((int) ((bmax[2] - data.header.bmin[2]) * quantFactor), 0, 0x7fffffff);
} }
return NavMeshBuilder.subdivide(items, data.header.polyCount, 0, data.header.polyCount, 0, nodes);
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++)
{
NavMeshBuilder.BVItem it = new NavMeshBuilder.BVItem();
items[i] = it;
it.i = i;
float[] bmin = new float[3];
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++)
{
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);
it.bmax[0] = clamp((int)((bmax[0] - data.header.bmin[0]) * quantFactor), 0, 0x7fffffff);
it.bmax[1] = clamp((int)((bmax[1] - data.header.bmin[1]) * quantFactor), 0, 0x7fffffff);
it.bmax[2] = clamp((int)((bmax[2] - data.header.bmin[2]) * quantFactor), 0, 0x7fffffff);
}
return NavMeshBuilder.subdivide(items, data.header.polyCount, 0, data.header.polyCount, 0, nodes);
}
} }
}
} }

View File

@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DotRecast.Recast\DotRecast.Recast.csproj" /> <ProjectReference Include="..\DotRecast.Recast\DotRecast.Recast.csproj"/>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" /> <ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,82 +2,86 @@ using System;
namespace DotRecast.Detour.Extras.Jumplink namespace DotRecast.Detour.Extras.Jumplink
{ {
class EdgeSamplerFactory
{
public EdgeSampler get(JumpLinkBuilderConfig acfg, JumpLinkType type, Edge edge)
{
EdgeSampler es = null;
switch (type)
{
case JumpLinkType.EDGE_JUMP:
es = initEdgeJumpSampler(acfg, edge);
break;
case JumpLinkType.EDGE_CLIMB_DOWN:
es = initClimbDownSampler(acfg, edge);
break;
case JumpLinkType.EDGE_JUMP_OVER:
default:
throw new ArgumentException("Unsupported jump type " + type);
}
return es;
class EdgeSamplerFactory {
public EdgeSampler get(JumpLinkBuilderConfig acfg, JumpLinkType type, Edge edge) {
EdgeSampler es = null;
switch (type) {
case JumpLinkType.EDGE_JUMP:
es = initEdgeJumpSampler(acfg, edge);
break;
case JumpLinkType.EDGE_CLIMB_DOWN:
es = initClimbDownSampler(acfg, edge);
break;
case JumpLinkType.EDGE_JUMP_OVER:
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];
trans2d(offset, es.az, es.ay, new float[] { acfg.startDistance, -acfg.agentClimb });
vadd(es.start.p, edge.sp, offset);
vadd(es.start.q, edge.sq, offset);
EdgeSampler es = new EdgeSampler(edge, new JumpTrajectory(acfg.jumpHeight)); float dx = acfg.endDistance - 2 * acfg.agentRadius;
es.start.height = acfg.agentClimb * 2; float cs = acfg.cellSize;
float[] offset = new float[3]; int nsamples = Math.Max(2, (int)Math.Ceiling(dx / cs));
trans2d(offset, es.az, es.ay, new float[] { acfg.startDistance, -acfg.agentClimb });
vadd(es.start.p, edge.sp, offset);
vadd(es.start.q, edge.sq, offset);
float dx = acfg.endDistance - 2 * acfg.agentRadius; for (int j = 0; j < nsamples; ++j)
float cs = acfg.cellSize; {
int nsamples = Math.Max(2, (int) Math.Ceiling(dx / cs)); 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 });
GroundSegment end = new GroundSegment();
end.height = acfg.heightRange;
vadd(end.p, edge.sp, offset);
vadd(end.q, edge.sq, offset);
es.end.Add(end);
}
for (int j = 0; j < nsamples; ++j) { return es;
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 }); 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];
trans2d(offset, es.az, es.ay, new float[] { acfg.startDistance, -acfg.agentClimb });
vadd(es.start.p, edge.sp, offset);
vadd(es.start.q, edge.sq, offset);
trans2d(offset, es.az, es.ay, new float[] { acfg.endDistance, acfg.minHeight });
GroundSegment end = new GroundSegment(); GroundSegment end = new GroundSegment();
end.height = acfg.heightRange; end.height = acfg.heightRange;
vadd(end.p, edge.sp, offset); vadd(end.p, edge.sp, offset);
vadd(end.q, edge.sq, offset); vadd(end.q, edge.sq, offset);
es.end.Add(end); es.end.Add(end);
return es;
}
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)
{
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];
} }
return es;
} }
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];
trans2d(offset, es.az, es.ay, new float[] { acfg.startDistance, -acfg.agentClimb });
vadd(es.start.p, edge.sp, offset);
vadd(es.start.q, edge.sq, offset);
trans2d(offset, es.az, es.ay, new float[] { acfg.endDistance, acfg.minHeight });
GroundSegment end = new GroundSegment();
end.height = acfg.heightRange;
vadd(end.p, edge.sp, offset);
vadd(end.q, edge.sq, offset);
es.end.Add(end);
return es;
}
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) {
dst[0] = ax[0] * pt[0] + ay[0] * pt[1];
dst[1] = ax[1] * pt[0] + ay[1] * pt[1];
dst[2] = ax[2] * pt[0] + ay[2] * pt[1];
}
}
} }

View File

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

View File

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

View File

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

View File

@ -1,19 +1,15 @@
namespace DotRecast.Detour.Extras.Jumplink namespace DotRecast.Detour.Extras.Jumplink
{ {
public class JumpLink
{
public class JumpLink { public const int MAX_SPINE = 8;
public readonly int nspine = MAX_SPINE;
public const int MAX_SPINE = 8; public readonly float[] spine0 = new float[MAX_SPINE * 3];
public readonly int nspine = MAX_SPINE; public readonly float[] spine1 = new float[MAX_SPINE * 3];
public readonly float[] spine0 = new float[MAX_SPINE * 3]; public GroundSample[] startSamples;
public readonly float[] spine1 = new float[MAX_SPINE * 3]; public GroundSample[] endSamples;
public GroundSample[] startSamples; public GroundSegment start;
public GroundSample[] endSamples; public GroundSegment end;
public GroundSegment start; public Trajectory trajectory;
public GroundSegment end; }
public Trajectory trajectory;
}
} }

View File

@ -7,82 +7,90 @@ using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Extras.Jumplink namespace DotRecast.Detour.Extras.Jumplink
{ {
public class JumpLinkBuilder
{
private readonly EdgeExtractor edgeExtractor = new EdgeExtractor();
private readonly EdgeSamplerFactory edgeSamplerFactory = new EdgeSamplerFactory();
private readonly GroundSampler groundSampler = new NavMeshGroundSampler();
private readonly TrajectorySampler trajectorySampler = new TrajectorySampler();
private readonly JumpSegmentBuilder jumpSegmentBuilder = new JumpSegmentBuilder();
private readonly List<Edge[]> edges;
private readonly List<RecastBuilderResult> results;
public class JumpLinkBuilder { public JumpLinkBuilder(List<RecastBuilderResult> results)
{
private readonly EdgeExtractor edgeExtractor = new EdgeExtractor(); this.results = results;
private readonly EdgeSamplerFactory edgeSamplerFactory = new EdgeSamplerFactory(); edges = results.Select(r => edgeExtractor.extractEdges(r.getMesh())).ToList();
private readonly GroundSampler groundSampler = new NavMeshGroundSampler();
private readonly TrajectorySampler trajectorySampler = new TrajectorySampler();
private readonly JumpSegmentBuilder jumpSegmentBuilder = new JumpSegmentBuilder();
private readonly List<Edge[]> edges;
private readonly 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) {
List<JumpLink> links = new List<JumpLink>();
for (int tile = 0; tile < results.Count; tile++) {
Edge[] edges = this.edges[tile];
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) { public List<JumpLink> build(JumpLinkBuilderConfig acfg, JumpLinkType type)
EdgeSampler es = edgeSamplerFactory.get(acfg, type, edge); {
groundSampler.sample(acfg, result, es); List<JumpLink> links = new List<JumpLink>();
trajectorySampler.sample(acfg, result.getSolidHeightfield(), es); for (int tile = 0; tile < results.Count; tile++)
JumpSegment[] jumpSegments = jumpSegmentBuilder.build(acfg, es); {
return buildJumpLinks(acfg, es, jumpSegments); Edge[] edges = this.edges[tile];
} foreach (Edge edge in edges)
{
links.AddRange(processEdge(acfg, results[tile], type, edge));
private List<JumpLink> buildJumpLinks(JumpLinkBuilderConfig acfg, EdgeSampler es, JumpSegment[] jumpSegments) {
List<JumpLink> links = new List<JumpLink>();
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) {
JumpLink link = new JumpLink();
links.Add(link);
link.startSamples = ArrayUtils.CopyOf(es.start.gsamples, js.startSample, js.samples - js.startSample);
link.endSamples = ArrayUtils.CopyOf(end.gsamples, js.startSample, js.samples - js.startSample);
link.start = es.start;
link.end = end;
link.trajectory = es.trajectory;
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];
link.spine0[j * 3 + 1] = p[1];
link.spine0[j * 3 + 2] = p[2];
p = es.trajectory.apply(sq, eq, u);
link.spine1[j * 3] = p[0];
link.spine1[j * 3 + 1] = p[1];
link.spine1[j * 3 + 2] = p[2];
} }
} }
return links;
}
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);
JumpSegment[] jumpSegments = jumpSegmentBuilder.build(acfg, es);
return buildJumpLinks(acfg, es, jumpSegments);
}
private List<JumpLink> buildJumpLinks(JumpLinkBuilderConfig acfg, EdgeSampler es, JumpSegment[] jumpSegments)
{
List<JumpLink> links = new List<JumpLink>();
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)
{
JumpLink link = new JumpLink();
links.Add(link);
link.startSamples = ArrayUtils.CopyOf(es.start.gsamples, js.startSample, js.samples - js.startSample);
link.endSamples = ArrayUtils.CopyOf(end.gsamples, js.startSample, js.samples - js.startSample);
link.start = es.start;
link.end = end;
link.trajectory = es.trajectory;
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];
link.spine0[j * 3 + 1] = p[1];
link.spine0[j * 3 + 2] = p[2];
p = es.trajectory.apply(sq, eq, u);
link.spine1[j * 3] = p[0];
link.spine1[j * 3 + 1] = p[1];
link.spine1[j * 3 + 2] = p[2];
}
}
}
return links;
}
public List<Edge[]> getEdges()
{
return edges;
} }
return links;
} }
public List<Edge[]> getEdges() {
return edges;
}
}
} }

View File

@ -1,37 +1,34 @@
namespace DotRecast.Detour.Extras.Jumplink namespace DotRecast.Detour.Extras.Jumplink
{ {
public class JumpLinkBuilderConfig
{
public readonly float cellSize;
public readonly float cellHeight;
public readonly float agentClimb;
public readonly float agentRadius;
public readonly float groundTolerance;
public readonly float agentHeight;
public readonly float startDistance;
public readonly float endDistance;
public readonly float jumpHeight;
public readonly float minHeight;
public readonly float heightRange;
public JumpLinkBuilderConfig(float cellSize, float cellHeight, float agentRadius, float agentHeight,
public class JumpLinkBuilderConfig {
public readonly float cellSize;
public readonly float cellHeight;
public readonly float agentClimb;
public readonly float agentRadius;
public readonly float groundTolerance;
public readonly float agentHeight;
public readonly float startDistance;
public readonly float endDistance;
public readonly float jumpHeight;
public readonly float minHeight;
public readonly float heightRange;
public JumpLinkBuilderConfig(float cellSize, float cellHeight, float agentRadius, float agentHeight,
float agentClimb, float groundTolerance, float startDistance, float endDistance, float minHeight, float agentClimb, float groundTolerance, float startDistance, float endDistance, float minHeight,
float maxHeight, float jumpHeight) { float maxHeight, float jumpHeight)
this.cellSize = cellSize; {
this.cellHeight = cellHeight; this.cellSize = cellSize;
this.agentRadius = agentRadius; this.cellHeight = cellHeight;
this.agentClimb = agentClimb; this.agentRadius = agentRadius;
this.groundTolerance = groundTolerance; this.agentClimb = agentClimb;
this.agentHeight = agentHeight; this.groundTolerance = groundTolerance;
this.startDistance = startDistance; this.agentHeight = agentHeight;
this.endDistance = endDistance; this.startDistance = startDistance;
this.minHeight = minHeight; this.endDistance = endDistance;
heightRange = maxHeight - minHeight; this.minHeight = minHeight;
this.jumpHeight = jumpHeight; heightRange = maxHeight - minHeight;
this.jumpHeight = jumpHeight;
}
} }
}
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -4,92 +4,97 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Extras.Jumplink namespace DotRecast.Detour.Extras.Jumplink
{ {
class NavMeshGroundSampler : AbstractGroundSampler
class NavMeshGroundSampler : AbstractGroundSampler {
private readonly QueryFilter filter = new NoOpFilter();
private 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) {
return 0;
}
}
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) {
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = r.getMesh().verts;
option.vertCount = r.getMesh().nverts;
option.polys = r.getMesh().polys;
option.polyAreas = r.getMesh().areas;
option.polyFlags = r.getMesh().flags;
option.polyCount = r.getMesh().npolys;
option.nvp = r.getMesh().nvp;
option.detailMeshes = r.getMeshDetail().meshes;
option.detailVerts = r.getMeshDetail().verts;
option.detailVertsCount = r.getMeshDetail().nverts;
option.detailTris = r.getMeshDetail().tris;
option.detailTriCount = r.getMeshDetail().ntris;
option.walkableRadius = agentRadius;
option.walkableHeight = agentHeight;
option.walkableClimb = agentClimb;
option.bmin = r.getMesh().bmin;
option.bmax = r.getMesh().bmax;
option.cs = r.getMesh().cs;
option.ch = r.getMesh().ch;
option.buildBvTree = true;
return new NavMeshQuery(new NavMesh(NavMeshBuilder.createNavMeshData(option), option.nvp, 0));
}
public class PolyQueryInvoker : PolyQuery
{ {
public readonly Action<MeshTile, Poly, long> _callback; private readonly QueryFilter filter = new NoOpFilter();
public PolyQueryInvoker(Action<MeshTile, Poly, long> callback) private class NoOpFilter : QueryFilter
{ {
_callback = callback; public bool passFilter(long refs, MeshTile tile, Poly poly)
} {
return true;
public void process(MeshTile tile, Poly poly, long refs) }
{
_callback?.Invoke(tile, poly, refs); 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)
} {
return 0;
private Tuple<bool, float> getNavMeshHeight(NavMeshQuery navMeshQuery, float[] pt, float cs,
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) => {
Result<float> h = navMeshQuery.getPolyHeight(refs, pt);
if (h.succeeded()) {
float y = h.result;
if (y > minHeight.Get() && y < maxHeight) {
minHeight.Exchange(y);
found.set(true);
}
} }
}));
if (found.get()) {
return Tuple.Create(true, minHeight.Get());
} }
return Tuple.Create(false, pt[1]);
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)
{
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = r.getMesh().verts;
option.vertCount = r.getMesh().nverts;
option.polys = r.getMesh().polys;
option.polyAreas = r.getMesh().areas;
option.polyFlags = r.getMesh().flags;
option.polyCount = r.getMesh().npolys;
option.nvp = r.getMesh().nvp;
option.detailMeshes = r.getMeshDetail().meshes;
option.detailVerts = r.getMeshDetail().verts;
option.detailVertsCount = r.getMeshDetail().nverts;
option.detailTris = r.getMeshDetail().tris;
option.detailTriCount = r.getMeshDetail().ntris;
option.walkableRadius = agentRadius;
option.walkableHeight = agentHeight;
option.walkableClimb = agentClimb;
option.bmin = r.getMesh().bmin;
option.bmax = r.getMesh().bmax;
option.cs = r.getMesh().cs;
option.ch = r.getMesh().ch;
option.buildBvTree = true;
return new NavMeshQuery(new NavMesh(NavMeshBuilder.createNavMeshData(option), option.nvp, 0));
}
public class PolyQueryInvoker : PolyQuery
{
public readonly Action<MeshTile, Poly, long> _callback;
public PolyQueryInvoker(Action<MeshTile, Poly, long> callback)
{
_callback = callback;
}
public void process(MeshTile tile, Poly poly, long refs)
{
_callback?.Invoke(tile, poly, refs);
}
}
private Tuple<bool, float> getNavMeshHeight(NavMeshQuery navMeshQuery, float[] pt, float cs,
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) =>
{
Result<float> h = navMeshQuery.getPolyHeight(refs, pt);
if (h.succeeded())
{
float y = h.result;
if (y > minHeight.Get() && y < maxHeight)
{
minHeight.Exchange(y);
found.set(true);
}
}
}));
if (found.get())
{
return Tuple.Create(true, minHeight.Get());
}
return Tuple.Create(false, pt[1]);
}
} }
}
} }

View File

@ -2,19 +2,16 @@ using System;
namespace DotRecast.Detour.Extras.Jumplink namespace DotRecast.Detour.Extras.Jumplink
{ {
public class Trajectory
public class Trajectory {
public float lerp(float f, float g, float u) {
return u * g + (1f - u) * f;
}
public virtual float[] apply(float[] start, float[] end, float u)
{ {
throw new NotImplementedException(); public float lerp(float f, float g, float u)
{
return u * g + (1f - u) * f;
}
public virtual float[] apply(float[] start, float[] end, float u)
{
throw new NotImplementedException();
}
} }
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,143 +23,152 @@ using DotRecast.Core;
namespace DotRecast.Detour.Extras.Unity.Astar namespace DotRecast.Detour.Extras.Unity.Astar
{ {
public class GraphMeshDataReader : ZipBinaryReader
{
public const float INT_PRECISION_FACTOR = 1000f;
public GraphMeshData read(ZipArchive file, string filename, GraphMeta meta, int maxVertPerPoly)
public class GraphMeshDataReader : ZipBinaryReader { {
ByteBuffer buffer = toByteBuffer(file, filename);
public const float INT_PRECISION_FACTOR = 1000f; int tileXCount = buffer.getInt();
if (tileXCount < 0)
public GraphMeshData read(ZipArchive file, string filename, GraphMeta meta, int maxVertPerPoly) { {
ByteBuffer buffer = toByteBuffer(file, filename); return null;
int tileXCount = buffer.getInt();
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++) {
int tileIndex = x + z * tileXCount;
int tx = buffer.getInt();
int tz = buffer.getInt();
if (tx != x || tz != z) {
throw new ArgumentException("Inconsistent tile positions");
}
tiles[tileIndex] = new MeshData();
int width = buffer.getInt();
int depth = buffer.getInt();
int trisCount = buffer.getInt();
int[] tris = new int[trisCount];
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++) {
verts[i] = buffer.getInt() / INT_PRECISION_FACTOR;
}
int[] vertsInGraphSpace = new int[3 * buffer.getInt()];
for (int i = 0; i < vertsInGraphSpace.Length; i++) {
vertsInGraphSpace[i] = buffer.getInt();
}
int nodeCount = buffer.getInt();
Poly[] nodes = new Poly[nodeCount];
PolyDetail[] detailNodes = new PolyDetail[nodeCount];
float[] detailVerts = new float[0];
int[] detailTris = new int[4 * nodeCount];
int vertMask = getVertMask(vertsCount);
float ymin = float.PositiveInfinity;
float ymax = float.NegativeInfinity;
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?
int penalty = buffer.getInt();
nodes[i].flags = buffer.getInt();
nodes[i].verts[0] = buffer.getInt() & vertMask;
nodes[i].verts[1] = buffer.getInt() & vertMask;
nodes[i].verts[2] = buffer.getInt() & vertMask;
ymin = Math.Min(ymin, verts[nodes[i].verts[0] * 3 + 1]);
ymin = Math.Min(ymin, verts[nodes[i].verts[1] * 3 + 1]);
ymin = Math.Min(ymin, verts[nodes[i].verts[2] * 3 + 1]);
ymax = Math.Max(ymax, verts[nodes[i].verts[0] * 3 + 1]);
ymax = Math.Max(ymax, verts[nodes[i].verts[1] * 3 + 1]);
ymax = Math.Max(ymax, verts[nodes[i].verts[2] * 3 + 1]);
detailNodes[i] = new PolyDetail();
detailNodes[i].vertBase = 0;
detailNodes[i].vertCount = 0;
detailNodes[i].triBase = i;
detailNodes[i].triCount = 1;
detailTris[4 * i] = 0;
detailTris[4 * i + 1] = 1;
detailTris[4 * i + 2] = 2;
// Bit for each edge that belongs to poly boundary, basically all edges marked as boundary as it is
// a triangle
detailTris[4 * i + 3] = (1 << 4) | (1 << 2) | 1;
}
tiles[tileIndex].verts = verts;
tiles[tileIndex].polys = nodes;
tiles[tileIndex].detailMeshes = detailNodes;
tiles[tileIndex].detailVerts = detailVerts;
tiles[tileIndex].detailTris = detailTris;
MeshHeader header = new MeshHeader();
header.magic = MeshHeader.DT_NAVMESH_MAGIC;
header.version = MeshHeader.DT_NAVMESH_VERSION;
header.x = x;
header.y = z;
header.polyCount = nodeCount;
header.vertCount = vertsCount;
header.detailMeshCount = nodeCount;
header.detailTriCount = nodeCount;
header.maxLinkCount = nodeCount * 3 * 2; // XXX: Needed by Recast, not needed by recast4j
header.bmin[0] = meta.forcedBoundsCenter.x - 0.5f * meta.forcedBoundsSize.x
+ meta.cellSize * meta.tileSizeX * x;
header.bmin[1] = ymin;
header.bmin[2] = meta.forcedBoundsCenter.z - 0.5f * meta.forcedBoundsSize.z
+ meta.cellSize * meta.tileSizeZ * z;
header.bmax[0] = meta.forcedBoundsCenter.x - 0.5f * meta.forcedBoundsSize.x
+ meta.cellSize * meta.tileSizeX * (x + 1);
header.bmax[1] = ymax;
header.bmax[2] = meta.forcedBoundsCenter.z - 0.5f * meta.forcedBoundsSize.z
+ meta.cellSize * meta.tileSizeZ * (z + 1);
header.bvQuantFactor = 1.0f / meta.cellSize;
header.offMeshBase = nodeCount;
header.walkableClimb = meta.walkableClimb;
header.walkableHeight = meta.walkableHeight;
header.walkableRadius = meta.characterRadius;
tiles[tileIndex].header = header;
} }
int tileZCount = buffer.getInt();
MeshData[] tiles = new MeshData[tileXCount * tileZCount];
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)
{
throw new ArgumentException("Inconsistent tile positions");
}
tiles[tileIndex] = new MeshData();
int width = buffer.getInt();
int depth = buffer.getInt();
int trisCount = buffer.getInt();
int[] tris = new int[trisCount];
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++)
{
verts[i] = buffer.getInt() / INT_PRECISION_FACTOR;
}
int[] vertsInGraphSpace = new int[3 * buffer.getInt()];
for (int i = 0; i < vertsInGraphSpace.Length; i++)
{
vertsInGraphSpace[i] = buffer.getInt();
}
int nodeCount = buffer.getInt();
Poly[] nodes = new Poly[nodeCount];
PolyDetail[] detailNodes = new PolyDetail[nodeCount];
float[] detailVerts = new float[0];
int[] detailTris = new int[4 * nodeCount];
int vertMask = getVertMask(vertsCount);
float ymin = float.PositiveInfinity;
float ymax = float.NegativeInfinity;
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?
int penalty = buffer.getInt();
nodes[i].flags = buffer.getInt();
nodes[i].verts[0] = buffer.getInt() & vertMask;
nodes[i].verts[1] = buffer.getInt() & vertMask;
nodes[i].verts[2] = buffer.getInt() & vertMask;
ymin = Math.Min(ymin, verts[nodes[i].verts[0] * 3 + 1]);
ymin = Math.Min(ymin, verts[nodes[i].verts[1] * 3 + 1]);
ymin = Math.Min(ymin, verts[nodes[i].verts[2] * 3 + 1]);
ymax = Math.Max(ymax, verts[nodes[i].verts[0] * 3 + 1]);
ymax = Math.Max(ymax, verts[nodes[i].verts[1] * 3 + 1]);
ymax = Math.Max(ymax, verts[nodes[i].verts[2] * 3 + 1]);
detailNodes[i] = new PolyDetail();
detailNodes[i].vertBase = 0;
detailNodes[i].vertCount = 0;
detailNodes[i].triBase = i;
detailNodes[i].triCount = 1;
detailTris[4 * i] = 0;
detailTris[4 * i + 1] = 1;
detailTris[4 * i + 2] = 2;
// Bit for each edge that belongs to poly boundary, basically all edges marked as boundary as it is
// a triangle
detailTris[4 * i + 3] = (1 << 4) | (1 << 2) | 1;
}
tiles[tileIndex].verts = verts;
tiles[tileIndex].polys = nodes;
tiles[tileIndex].detailMeshes = detailNodes;
tiles[tileIndex].detailVerts = detailVerts;
tiles[tileIndex].detailTris = detailTris;
MeshHeader header = new MeshHeader();
header.magic = MeshHeader.DT_NAVMESH_MAGIC;
header.version = MeshHeader.DT_NAVMESH_VERSION;
header.x = x;
header.y = z;
header.polyCount = nodeCount;
header.vertCount = vertsCount;
header.detailMeshCount = nodeCount;
header.detailTriCount = nodeCount;
header.maxLinkCount = nodeCount * 3 * 2; // XXX: Needed by Recast, not needed by recast4j
header.bmin[0] = meta.forcedBoundsCenter.x - 0.5f * meta.forcedBoundsSize.x
+ meta.cellSize * meta.tileSizeX * x;
header.bmin[1] = ymin;
header.bmin[2] = meta.forcedBoundsCenter.z - 0.5f * meta.forcedBoundsSize.z
+ meta.cellSize * meta.tileSizeZ * z;
header.bmax[0] = meta.forcedBoundsCenter.x - 0.5f * meta.forcedBoundsSize.x
+ meta.cellSize * meta.tileSizeX * (x + 1);
header.bmax[1] = ymax;
header.bmax[2] = meta.forcedBoundsCenter.z - 0.5f * meta.forcedBoundsSize.z
+ meta.cellSize * meta.tileSizeZ * (z + 1);
header.bvQuantFactor = 1.0f / meta.cellSize;
header.offMeshBase = nodeCount;
header.walkableClimb = meta.walkableClimb;
header.walkableHeight = meta.walkableHeight;
header.walkableRadius = meta.characterRadius;
tiles[tileIndex].header = header;
}
}
return new GraphMeshData(tileXCount, tileZCount, tiles);
} }
return new GraphMeshData(tileXCount, tileZCount, tiles);
}
public static int highestOneBit(uint i) public static int highestOneBit(uint i)
{ {
i |= (i >> 1); i |= (i >> 1);
i |= (i >> 2); i |= (i >> 2);
i |= (i >> 4); i |= (i >> 4);
i |= (i >> 8); i |= (i >> 8);
i |= (i >> 16); i |= (i >> 16);
return (int)(i - (i >> 1)); return (int)(i - (i >> 1));
} }
// See NavmeshBase.cs: ASTAR_RECAST_LARGER_TILES // See NavmeshBase.cs: ASTAR_RECAST_LARGER_TILES
private int getVertMask(int vertsCount) private int getVertMask(int vertsCount)
{ {
int vertMask = highestOneBit((uint)vertsCount); int vertMask = highestOneBit((uint)vertsCount);
if (vertMask != vertsCount) { if (vertMask != vertsCount)
vertMask *= 2; {
vertMask *= 2;
}
vertMask--;
return vertMask;
} }
vertMask--;
return vertMask;
} }
}
} }

View File

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

View File

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

View File

@ -21,50 +21,64 @@ using System.Collections.Generic;
namespace DotRecast.Detour.Extras.Unity.Astar namespace DotRecast.Detour.Extras.Unity.Astar
{ {
public class LinkBuilder
{
public class LinkBuilder { // Process connections and transform them into recast neighbour flags
public void build(int nodeOffset, GraphMeshData graphData, List<int[]> connections)
// 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++)
for (int n = 0; n < connections.Count; n++) { {
int[] nodeConnections = connections[n]; int[] nodeConnections = connections[n];
MeshData tile = graphData.getTile(n); MeshData tile = graphData.getTile(n);
Poly node = graphData.getNode(n); Poly node = graphData.getNode(n);
foreach (int connection in nodeConnections) { foreach (int connection in nodeConnections)
MeshData neighbourTile = graphData.getTile(connection - nodeOffset); {
if (neighbourTile != tile) { MeshData neighbourTile = graphData.getTile(connection - nodeOffset);
buildExternalLink(tile, node, neighbourTile); if (neighbourTile != tile)
} else { {
Poly neighbour = graphData.getNode(connection - nodeOffset); buildExternalLink(tile, node, neighbourTile);
buildInternalLink(tile, node, neighbourTile, neighbour); }
else
{
Poly neighbour = graphData.getNode(connection - nodeOffset);
buildInternalLink(tile, node, neighbourTile, neighbour);
}
} }
} }
} }
}
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) { int edge = PolyUtils.findEdge(node, neighbour, tile, neighbourTile);
node.neis[edge] = neighbour.index + 1; if (edge >= 0)
} else { {
throw new ArgumentException(); node.neis[edge] = neighbour.index + 1;
}
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])
{
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])
{
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])
{
node.neis[PolyUtils.findEdge(node, tile, neighbourTile.header.bmin[2], 2)] = NavMesh.DT_EXT_LINK | 2;
}
else
{
node.neis[PolyUtils.findEdge(node, tile, tile.header.bmin[2], 2)] = NavMesh.DT_EXT_LINK | 6;
}
} }
} }
// 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]) {
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]) {
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]) {
node.neis[PolyUtils.findEdge(node, tile, neighbourTile.header.bmin[2], 2)] = NavMesh.DT_EXT_LINK | 2;
} else {
node.neis[PolyUtils.findEdge(node, tile, tile.header.bmin[2], 2)] = NavMesh.DT_EXT_LINK | 6;
}
}
}
} }

View File

@ -21,57 +21,69 @@ using System.Text.RegularExpressions;
namespace DotRecast.Detour.Extras.Unity.Astar namespace DotRecast.Detour.Extras.Unity.Astar
{ {
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";
public static readonly Regex VERSION_PATTERN = new Regex(@"(\d+)\.(\d+)\.(\d+)");
public string version { get; set; }
public int graphs { get; set; }
public string[] guids { get; set; }
public string[] typeNames { get; set; }
public bool isSupportedVersion()
public class Meta { {
return isVersionAtLeast(MIN_SUPPORTED_VERSION);
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";
public static readonly Regex VERSION_PATTERN = new Regex(@"(\d+)\.(\d+)\.(\d+)");
public string version { get; set; }
public int graphs { get; set; }
public string[] guids { get; set; }
public string[] typeNames { get; set; }
public bool isSupportedVersion() {
return isVersionAtLeast(MIN_SUPPORTED_VERSION);
}
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]) {
return true;
} else if (minSupported[i] > actual[i]) {
return false;
}
} }
return true;
}
private int[] parseVersion(string version) { public bool isVersionAtLeast(string minVersion)
Match m = VERSION_PATTERN.Match(version); {
if (m.Success) { int[] actual = parseVersion(version);
int[] v = new int[m.Groups.Count - 1]; int[] minSupported = parseVersion(minVersion);
for (int i = 0; i < v.Length; i++) { for (int i = 0; i < Math.Min(actual.Length, minSupported.Length); i++)
v[i] = int.Parse(m.Groups[i + 1].Value); {
if (actual[i] > minSupported[i])
{
return true;
}
else if (minSupported[i] > actual[i])
{
return false;
}
} }
return v;
}
throw new ArgumentException("Invalid version format: " + version);
}
public bool isSupportedType() { return true;
foreach (string t in typeNames) { }
if (t == TYPENAME_RECAST_GRAPH) {
return true; private int[] parseVersion(string version)
{
Match m = VERSION_PATTERN.Match(version);
if (m.Success)
{
int[] v = new int[m.Groups.Count - 1];
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)
{
return true;
}
}
return false;
} }
return false;
} }
}
} }

View File

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

View File

@ -21,22 +21,21 @@ using DotRecast.Core;
namespace DotRecast.Detour.Extras.Unity.Astar namespace DotRecast.Detour.Extras.Unity.Astar
{ {
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)
{
int index = buffer.getInt();
int2Node[index] = node++;
}
return int2Node;
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) {
int index = buffer.getInt();
int2Node[index] = node++;
} }
return int2Node;
} }
}
} }

View File

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

View File

@ -21,33 +21,33 @@ using DotRecast.Core;
namespace DotRecast.Detour.Extras.Unity.Astar namespace DotRecast.Detour.Extras.Unity.Astar
{ {
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++)
{
long linkID = buffer.getLong();
int startNode = indexToNode[buffer.getInt()];
int endNode = indexToNode[buffer.getInt()];
int connectedNode1 = buffer.getInt();
int connectedNode2 = buffer.getInt();
Vector3f clamped1 = new Vector3f();
clamped1.x = buffer.getFloat();
clamped1.y = buffer.getFloat();
clamped1.z = buffer.getFloat();
Vector3f clamped2 = new Vector3f();
clamped2.x = buffer.getFloat();
clamped2.y = buffer.getFloat();
clamped2.z = buffer.getFloat();
bool postScanCalled = buffer.get() != 0;
links[i] = new NodeLink2(linkID, startNode, endNode, clamped1, clamped2);
}
return links;
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++) {
long linkID = buffer.getLong();
int startNode = indexToNode[buffer.getInt()];
int endNode = indexToNode[buffer.getInt()];
int connectedNode1 = buffer.getInt();
int connectedNode2 = buffer.getInt();
Vector3f clamped1 = new Vector3f();
clamped1.x = buffer.getFloat();
clamped1.y = buffer.getFloat();
clamped1.z = buffer.getFloat();
Vector3f clamped2 = new Vector3f();
clamped2.x = buffer.getFloat();
clamped2.y = buffer.getFloat();
clamped2.z = buffer.getFloat();
bool postScanCalled = buffer.get() != 0;
links[i] = new NodeLink2(linkID, startNode, endNode, clamped1, clamped2);
} }
return links;
} }
}
} }

View File

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

View File

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

View File

@ -22,50 +22,50 @@ using System.IO.Compression;
namespace DotRecast.Detour.Extras.Unity.Astar namespace DotRecast.Detour.Extras.Unity.Astar
{ {
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";
private const string GRAPH_META_FILE_NAME_PATTERN = "graph{0}.json";
private const string GRAPH_DATA_FILE_NAME_PATTERN = "graph{0}_extra.binary";
private const string GRAPH_CONNECTION_FILE_NAME_PATTERN = "graph{0}_references.binary";
private const int MAX_VERTS_PER_POLY = 3;
private readonly MetaReader metaReader = new MetaReader();
private readonly NodeIndexReader nodeIndexReader = new NodeIndexReader();
private readonly GraphMetaReader graphMetaReader = new GraphMetaReader();
private readonly GraphMeshDataReader graphDataReader = new GraphMeshDataReader();
private readonly GraphConnectionReader graphConnectionReader = new GraphConnectionReader();
private readonly NodeLink2Reader nodeLink2Reader = new NodeLink2Reader();
public GraphData read(FileStream zipFile)
public class UnityAStarPathfindingReader { {
using ZipArchive file = new ZipArchive(zipFile);
private const string META_FILE_NAME = "meta.json"; // Read meta file and check version and graph type
private const string NODE_INDEX_FILE_NAME = "graph_references.binary"; Meta meta = metaReader.read(file, META_FILE_NAME);
private const string NODE_LINK_2_FILE_NAME = "node_link2.binary"; // Read index to node mapping
private const string GRAPH_META_FILE_NAME_PATTERN = "graph{0}.json"; int[] indexToNode = nodeIndexReader.read(file, NODE_INDEX_FILE_NAME);
private const string GRAPH_DATA_FILE_NAME_PATTERN = "graph{0}_extra.binary"; // Read NodeLink2 data (off-mesh links)
private const string GRAPH_CONNECTION_FILE_NAME_PATTERN = "graph{0}_references.binary"; NodeLink2[] nodeLinks2 = nodeLink2Reader.read(file, NODE_LINK_2_FILE_NAME, indexToNode);
private const int MAX_VERTS_PER_POLY = 3; // Read graph by graph
private readonly MetaReader metaReader = new MetaReader(); List<GraphMeta> metaList = new List<GraphMeta>();
private readonly NodeIndexReader nodeIndexReader = new NodeIndexReader(); List<GraphMeshData> meshDataList = new List<GraphMeshData>();
private readonly GraphMetaReader graphMetaReader = new GraphMetaReader(); List<List<int[]>> connectionsList = new List<List<int[]>>();
private readonly GraphMeshDataReader graphDataReader = new GraphMeshDataReader(); for (int graphIndex = 0; graphIndex < meta.graphs; graphIndex++)
private readonly GraphConnectionReader graphConnectionReader = new GraphConnectionReader(); {
private readonly NodeLink2Reader nodeLink2Reader = new NodeLink2Reader(); GraphMeta graphMeta = graphMetaReader.read(file, string.Format(GRAPH_META_FILE_NAME_PATTERN, graphIndex));
// First graph mesh data - vertices and polygons
public GraphData read(FileStream zipFile) { GraphMeshData graphData = graphDataReader.read(file,
using ZipArchive file = new ZipArchive(zipFile);
// Read meta file and check version and graph type
Meta meta = metaReader.read(file, META_FILE_NAME);
// Read index to node mapping
int[] indexToNode = nodeIndexReader.read(file, NODE_INDEX_FILE_NAME);
// Read NodeLink2 data (off-mesh links)
NodeLink2[] nodeLinks2 = nodeLink2Reader.read(file, NODE_LINK_2_FILE_NAME, indexToNode);
// Read graph by graph
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++) {
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,
string.Format(GRAPH_DATA_FILE_NAME_PATTERN, graphIndex), graphMeta, MAX_VERTS_PER_POLY); string.Format(GRAPH_DATA_FILE_NAME_PATTERN, graphIndex), graphMeta, MAX_VERTS_PER_POLY);
// Then graph connection data - links between nodes located in both the same tile and other tiles // Then graph connection data - links between nodes located in both the same tile and other tiles
List<int[]> connections = graphConnectionReader.read(file, List<int[]> connections = graphConnectionReader.read(file,
string.Format(GRAPH_CONNECTION_FILE_NAME_PATTERN, graphIndex), meta, indexToNode); string.Format(GRAPH_CONNECTION_FILE_NAME_PATTERN, graphIndex), meta, indexToNode);
metaList.Add(graphMeta); metaList.Add(graphMeta);
meshDataList.Add(graphData); meshDataList.Add(graphData);
connectionsList.Add(connections); connectionsList.Add(connections);
} }
return new GraphData(meta, indexToNode, nodeLinks2, metaList, meshDataList, connectionsList);
}
}
return new GraphData(meta, indexToNode, nodeLinks2, metaList, meshDataList, connectionsList);
}
}
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup> </PropertyGroup>
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.5" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" /> <PackageReference Include="K4os.Compression.LZ4" Version="1.3.5"/>
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj"/>
</ItemGroup>
</Project> </Project>

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -23,43 +23,46 @@ using DotRecast.Core;
namespace DotRecast.Detour.TileCache.Io namespace DotRecast.Detour.TileCache.Io
{ {
public class TileCacheLayerHeaderReader
{
public TileCacheLayerHeader read(ByteBuffer data, bool cCompatibility)
{
TileCacheLayerHeader header = new TileCacheLayerHeader();
header.magic = data.getInt();
header.version = data.getInt();
if (header.magic != TileCacheLayerHeader.DT_TILECACHE_MAGIC)
throw new IOException("Invalid magic");
if (header.version != TileCacheLayerHeader.DT_TILECACHE_VERSION)
throw new IOException("Invalid version");
public class TileCacheLayerHeaderReader { header.tx = data.getInt();
header.ty = data.getInt();
header.tlayer = data.getInt();
for (int j = 0; j < 3; j++)
{
header.bmin[j] = data.getFloat();
}
public TileCacheLayerHeader read(ByteBuffer data, bool cCompatibility) { for (int j = 0; j < 3; j++)
TileCacheLayerHeader header = new TileCacheLayerHeader(); {
header.magic = data.getInt(); header.bmax[j] = data.getFloat();
header.version = data.getInt(); }
if (header.magic != TileCacheLayerHeader.DT_TILECACHE_MAGIC) header.hmin = data.getShort() & 0xFFFF;
throw new IOException("Invalid magic"); header.hmax = data.getShort() & 0xFFFF;
if (header.version != TileCacheLayerHeader.DT_TILECACHE_VERSION) header.width = data.get() & 0xFF;
throw new IOException("Invalid version"); header.height = data.get() & 0xFF;
header.minx = data.get() & 0xFF;
header.maxx = data.get() & 0xFF;
header.miny = data.get() & 0xFF;
header.maxy = data.get() & 0xFF;
if (cCompatibility)
{
data.getShort(); // C struct padding
}
header.tx = data.getInt(); return header;
header.ty = data.getInt();
header.tlayer = data.getInt();
for (int j = 0; j < 3; j++) {
header.bmin[j] = data.getFloat();
} }
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;
header.height = data.get() & 0xFF;
header.minx = data.get() & 0xFF;
header.maxx = data.get() & 0xFF;
header.miny = data.get() & 0xFF;
header.maxy = data.get() & 0xFF;
if (cCompatibility) {
data.getShort(); // C struct padding
}
return header;
} }
}
} }

View File

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

View File

@ -25,75 +25,88 @@ using DotRecast.Detour.TileCache.Io.Compress;
namespace DotRecast.Detour.TileCache.Io namespace DotRecast.Detour.TileCache.Io
{ {
public class TileCacheReader
{
private readonly NavMeshParamReader paramReader = new NavMeshParamReader();
public TileCache read(BinaryReader @is, int maxVertPerPoly, TileCacheMeshProcess meshProcessor)
public class TileCacheReader { {
ByteBuffer bb = IOUtils.toByteBuffer(@is);
private readonly NavMeshParamReader paramReader = new NavMeshParamReader(); return read(bb, maxVertPerPoly, 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) {
TileCacheSetHeader header = new TileCacheSetHeader();
header.magic = bb.getInt();
if (header.magic != TileCacheSetHeader.TILECACHESET_MAGIC) {
header.magic = IOUtils.swapEndianness(header.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) { public TileCache read(ByteBuffer bb, int maxVertPerPoly, TileCacheMeshProcess meshProcessor)
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION_RECAST4J) { {
throw new IOException("Invalid version"); TileCacheSetHeader header = new TileCacheSetHeader();
header.magic = bb.getInt();
if (header.magic != TileCacheSetHeader.TILECACHESET_MAGIC)
{
header.magic = IOUtils.swapEndianness(header.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);
} }
}
bool cCompatibility = header.version == TileCacheSetHeader.TILECACHESET_VERSION; header.version = bb.getInt();
header.numTiles = bb.getInt(); if (header.version != TileCacheSetHeader.TILECACHESET_VERSION)
header.meshParams = paramReader.read(bb); {
header.cacheParams = readCacheParams(bb, cCompatibility); if (header.version != TileCacheSetHeader.TILECACHESET_VERSION_RECAST4J)
NavMesh mesh = new NavMesh(header.meshParams, maxVertPerPoly); {
TileCacheCompressor compressor = TileCacheCompressorFactory.get(cCompatibility); throw new IOException("Invalid version");
TileCache tc = new TileCache(header.cacheParams, new TileCacheStorageParams(bb.order(), cCompatibility), mesh, }
}
bool cCompatibility = header.version == TileCacheSetHeader.TILECACHESET_VERSION;
header.numTiles = bb.getInt();
header.meshParams = paramReader.read(bb);
header.cacheParams = readCacheParams(bb, cCompatibility);
NavMesh mesh = new NavMesh(header.meshParams, maxVertPerPoly);
TileCacheCompressor compressor = TileCacheCompressorFactory.get(cCompatibility);
TileCache tc = new TileCache(header.cacheParams, new TileCacheStorageParams(bb.order(), cCompatibility), mesh,
compressor, meshProcessor); compressor, meshProcessor);
// Read tiles. // Read tiles.
for (int i = 0; i < header.numTiles; ++i) { for (int i = 0; i < header.numTiles; ++i)
long tileRef = bb.getInt(); {
int dataSize = bb.getInt(); long tileRef = bb.getInt();
if (tileRef == 0 || dataSize == 0) { int dataSize = bb.getInt();
break; if (tileRef == 0 || dataSize == 0)
{
break;
}
byte[] data = bb.ReadBytes(dataSize).ToArray();
long tile = tc.addTile(data, 0);
if (tile != 0)
{
tc.buildNavMeshTile(tile);
}
} }
byte[] data = bb.ReadBytes(dataSize).ToArray(); return tc;
long tile = tc.addTile(data, 0); }
if (tile != 0) {
tc.buildNavMeshTile(tile); private TileCacheParams readCacheParams(ByteBuffer bb, bool cCompatibility)
{
TileCacheParams option = new TileCacheParams();
for (int i = 0; i < 3; i++)
{
option.orig[i] = bb.getFloat();
} }
}
return tc;
}
private TileCacheParams readCacheParams(ByteBuffer bb, bool cCompatibility) { option.cs = bb.getFloat();
TileCacheParams option = new TileCacheParams(); option.ch = bb.getFloat();
for (int i = 0; i < 3; i++) { option.width = bb.getInt();
option.orig[i] = bb.getFloat(); option.height = bb.getInt();
option.walkableHeight = bb.getFloat();
option.walkableRadius = bb.getFloat();
option.walkableClimb = bb.getFloat();
option.maxSimplificationError = bb.getFloat();
option.maxTiles = bb.getInt();
option.maxObstacles = bb.getInt();
return option;
} }
option.cs = bb.getFloat();
option.ch = bb.getFloat();
option.width = bb.getInt();
option.height = bb.getInt();
option.walkableHeight = bb.getFloat();
option.walkableRadius = bb.getFloat();
option.walkableClimb = bb.getFloat();
option.maxSimplificationError = bb.getFloat();
option.maxTiles = bb.getInt();
option.maxObstacles = bb.getInt();
return option;
} }
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@ -17,24 +17,32 @@ freely, subject to the following restrictions:
misrepresented as being the original software. misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
namespace DotRecast.Detour.TileCache namespace DotRecast.Detour.TileCache
{ {
public class TileCacheLayerHeader
{
public const int DT_TILECACHE_MAGIC = 'D' << 24 | 'T' << 16 | 'L' << 8 | 'R';
/// < 'DTLR';
public const int DT_TILECACHE_VERSION = 1;
public class TileCacheLayerHeader { public int magic;
public const int DT_TILECACHE_MAGIC = 'D' << 24 | 'T' << 16 | 'L' << 8 | 'R'; /// < 'DTLR'; /// < Data magic
public const int DT_TILECACHE_VERSION = 1; public int version;
public int magic; /// < Data magic /// < Data version
public int version; /// < Data version public int tx, ty, tlayer;
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 minx, maxx, miny, maxy; /// < Usable sub-region.
} 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 minx, maxx, miny, maxy; /// < Usable sub-region.
}
} }

View File

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

View File

@ -22,34 +22,34 @@ using System.Collections.Generic;
namespace DotRecast.Detour.TileCache namespace DotRecast.Detour.TileCache
{ {
public class TileCacheObstacle
{
public enum TileCacheObstacleType
{
CYLINDER,
BOX,
ORIENTED_BOX
};
public readonly int index;
public TileCacheObstacleType type;
public readonly float[] pos = new float[3];
public readonly float[] bmin = new float[3];
public readonly float[] bmax = new float[3];
public float radius, height;
public readonly float[] center = new float[3];
public readonly float[] extents = new float[3];
public readonly float[] rotAux = new float[2]; // { cos(0.5f*angle)*sin(-0.5f*angle); cos(0.5f*angle)*cos(0.5f*angle) - 0.5 }
public List<long> touched = new List<long>();
public readonly List<long> pending = new List<long>();
public int salt;
public ObstacleState state = ObstacleState.DT_OBSTACLE_EMPTY;
public TileCacheObstacle next;
public class TileCacheObstacle { public TileCacheObstacle(int index)
{
public enum TileCacheObstacleType { salt = 1;
CYLINDER, BOX, ORIENTED_BOX this.index = index;
}; }
public readonly int index;
public TileCacheObstacleType type;
public readonly float[] pos = new float[3];
public readonly float[] bmin = new float[3];
public readonly float[] bmax = new float[3];
public float radius, height;
public readonly float[] center = new float[3];
public readonly float[] extents = new float[3];
public readonly float[] rotAux = new float[2]; // { cos(0.5f*angle)*sin(-0.5f*angle); cos(0.5f*angle)*cos(0.5f*angle) - 0.5 }
public List<long> touched = new List<long>();
public readonly List<long> pending = new List<long>();
public int salt;
public ObstacleState state = ObstacleState.DT_OBSTACLE_EMPTY;
public TileCacheObstacle next;
public TileCacheObstacle(int index) {
salt = 1;
this.index = index;
} }
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup> </PropertyGroup>
<ProjectReference Include="..\DotRecast.Core\DotRecast.Core.csproj" />
</ItemGroup> <ItemGroup>
<ProjectReference Include="..\DotRecast.Core\DotRecast.Core.csproj"/>
</ItemGroup>
</Project> </Project>

View File

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

View File

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

View File

@ -2,56 +2,58 @@ using System;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
using static DetourCommon;
public class FindNearestPolyQuery : PolyQuery
{
private readonly NavMeshQuery query;
private readonly float[] center;
private long nearestRef;
private float[] nearestPt;
private bool overPoly;
private float nearestDistanceSqr;
using static DetourCommon; public FindNearestPolyQuery(NavMeshQuery query, float[] center)
{
public class FindNearestPolyQuery : PolyQuery { this.query = query;
this.center = center;
private readonly NavMeshQuery query; nearestDistanceSqr = float.MaxValue;
private readonly float[] center; nearestPt = new float[] { center[0], center[1], center[2] };
private long nearestRef;
private float[] nearestPt;
private bool overPoly;
private float nearestDistanceSqr;
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) {
// Find nearest polygon amongst the nearby polygons.
Result<ClosestPointOnPolyResult> closest = query.closestPointOnPoly(refs, center);
bool posOverPoly = closest.result.isPosOverPoly();
float[] closestPtPoly = closest.result.getClosest();
// If a point is directly over a polygon and closer than
// climb height, favor that instead of straight line nearest point.
float d = 0;
float[] diff = vSub(center, closestPtPoly);
if (posOverPoly) {
d = Math.Abs(diff[1]) - tile.data.header.walkableClimb;
d = d > 0 ? d * d : 0;
} else {
d = vLenSqr(diff);
} }
if (d < nearestDistanceSqr) { public void process(MeshTile tile, Poly poly, long refs)
nearestPt = closestPtPoly; {
nearestDistanceSqr = d; // Find nearest polygon amongst the nearby polygons.
nearestRef = refs; Result<ClosestPointOnPolyResult> closest = query.closestPointOnPoly(refs, center);
overPoly = posOverPoly; bool posOverPoly = closest.result.isPosOverPoly();
float[] closestPtPoly = closest.result.getClosest();
// If a point is directly over a polygon and closer than
// climb height, favor that instead of straight line nearest point.
float d = 0;
float[] diff = vSub(center, closestPtPoly);
if (posOverPoly)
{
d = Math.Abs(diff[1]) - tile.data.header.walkableClimb;
d = d > 0 ? d * d : 0;
}
else
{
d = vLenSqr(diff);
}
if (d < nearestDistanceSqr)
{
nearestPt = closestPtPoly;
nearestDistanceSqr = d;
nearestRef = refs;
overPoly = posOverPoly;
}
} }
public FindNearestPolyResult result()
{
return new FindNearestPolyResult(nearestRef, nearestPt, overPoly);
}
} }
public FindNearestPolyResult result() {
return new FindNearestPolyResult(nearestRef, nearestPt, overPoly);
}
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,126 +21,155 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io namespace DotRecast.Detour.Io
{ {
public class MeshDataWriter : DetourWriter
{
public class MeshDataWriter : DetourWriter { public void write(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility)
{
public void write(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility) { MeshHeader header = data.header;
MeshHeader header = data.header; write(stream, header.magic, order);
write(stream, header.magic, order); write(stream, cCompatibility ? MeshHeader.DT_NAVMESH_VERSION : MeshHeader.DT_NAVMESH_VERSION_RECAST4J_LAST, order);
write(stream, cCompatibility ? MeshHeader.DT_NAVMESH_VERSION : MeshHeader.DT_NAVMESH_VERSION_RECAST4J_LAST, order); write(stream, header.x, order);
write(stream, header.x, order); write(stream, header.y, order);
write(stream, header.y, order); write(stream, header.layer, order);
write(stream, header.layer, order); write(stream, header.userId, order);
write(stream, header.userId, order); write(stream, header.polyCount, order);
write(stream, header.polyCount, order); write(stream, header.vertCount, order);
write(stream, header.vertCount, order); write(stream, header.maxLinkCount, order);
write(stream, header.maxLinkCount, order); write(stream, header.detailMeshCount, order);
write(stream, header.detailMeshCount, order); write(stream, header.detailVertCount, order);
write(stream, header.detailVertCount, order); write(stream, header.detailTriCount, order);
write(stream, header.detailTriCount, order); write(stream, header.bvNodeCount, order);
write(stream, header.bvNodeCount, order); write(stream, header.offMeshConCount, order);
write(stream, header.offMeshConCount, order); write(stream, header.offMeshBase, order);
write(stream, header.offMeshBase, order); write(stream, header.walkableHeight, order);
write(stream, header.walkableHeight, order); write(stream, header.walkableRadius, order);
write(stream, header.walkableRadius, order); write(stream, header.walkableClimb, order);
write(stream, header.walkableClimb, order); write(stream, header.bmin[0], order);
write(stream, header.bmin[0], order); write(stream, header.bmin[1], order);
write(stream, header.bmin[1], order); write(stream, header.bmin[2], order);
write(stream, header.bmin[2], order); write(stream, header.bmax[0], order);
write(stream, header.bmax[0], order); write(stream, header.bmax[1], order);
write(stream, header.bmax[1], order); write(stream, header.bmax[2], order);
write(stream, header.bmax[2], order); write(stream, header.bvQuantFactor, order);
write(stream, header.bvQuantFactor, order); writeVerts(stream, data.verts, header.vertCount, order);
writeVerts(stream, data.verts, header.vertCount, order); writePolys(stream, data, order, cCompatibility);
writePolys(stream, data, order, cCompatibility); if (cCompatibility)
if (cCompatibility) {
byte[] linkPlaceholder = new byte[header.maxLinkCount * MeshDataReader.getSizeofLink(false)];
stream.Write(linkPlaceholder);
}
writePolyDetails(stream, data, order, cCompatibility);
writeVerts(stream, data.detailVerts, header.detailVertCount, order);
writeDTris(stream, data);
writeBVTree(stream, data, order, cCompatibility);
writeOffMeshCons(stream, data, order);
}
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) {
write(stream, 0xFFFF, order);
}
for (int j = 0; j < data.polys[i].verts.Length; j++) {
write(stream, (short) data.polys[i].verts[j], order);
}
for (int j = 0; j < data.polys[i].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);
}
}
private void writePolyDetails(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility)
{ {
for (int i = 0; i < data.header.detailMeshCount; i++) { byte[] linkPlaceholder = new byte[header.maxLinkCount * MeshDataReader.getSizeofLink(false)];
write(stream, data.detailMeshes[i].vertBase, order); stream.Write(linkPlaceholder);
write(stream, data.detailMeshes[i].triBase, order); }
write(stream, (byte)data.detailMeshes[i].vertCount);
write(stream, (byte)data.detailMeshes[i].triCount); writePolyDetails(stream, data, order, cCompatibility);
if (cCompatibility) { writeVerts(stream, data.detailVerts, header.detailVertCount, order);
write(stream, (short) 0, order); writeDTris(stream, data);
writeBVTree(stream, data, order, cCompatibility);
writeOffMeshCons(stream, data, order);
}
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)
{
write(stream, 0xFFFF, order);
}
for (int j = 0; j < data.polys[i].verts.Length; j++)
{
write(stream, (short)data.polys[i].verts[j], order);
}
for (int j = 0; j < data.polys[i].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);
}
}
private void writePolyDetails(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility)
{
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)
{
write(stream, (short)0, order);
}
}
}
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++)
{
write(stream, (short)data.bvTree[i].bmin[j], order);
}
for (int j = 0; j < 3; j++)
{
write(stream, (short)data.bvTree[i].bmax[j], order);
}
}
else
{
for (int j = 0; j < 3; j++)
{
write(stream, data.bvTree[i].bmin[j], order);
}
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++)
{
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);
write(stream, (byte)data.offMeshCons[i].side);
write(stream, data.offMeshCons[i].userId, order);
} }
} }
} }
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++) {
write(stream, (short) data.bvTree[i].bmin[j], order);
}
for (int j = 0; j < 3; j++) {
write(stream, (short) data.bvTree[i].bmax[j], order);
}
} else {
for (int j = 0; j < 3; j++) {
write(stream, data.bvTree[i].bmin[j], order);
}
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++) {
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);
write(stream, (byte) data.offMeshCons[i].side);
write(stream, data.offMeshCons[i].userId, order);
}
}
}
} }

View File

@ -22,110 +22,135 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io namespace DotRecast.Detour.Io
{ {
using static DetourCommon;
using static DetourCommon; public class MeshSetReader
{
private readonly MeshDataReader meshReader = new MeshDataReader();
private readonly NavMeshParamReader paramReader = new NavMeshParamReader();
public NavMesh read(BinaryReader @is, int maxVertPerPoly)
public class MeshSetReader { {
return read(IOUtils.toByteBuffer(@is), maxVertPerPoly, false);
private readonly MeshDataReader meshReader = new MeshDataReader();
private readonly NavMeshParamReader paramReader = new NavMeshParamReader();
public NavMesh read(BinaryReader @is, int maxVertPerPoly) {
return read(IOUtils.toByteBuffer(@is), maxVertPerPoly, false);
}
public NavMesh read(ByteBuffer bb, int maxVertPerPoly) {
return read(bb, maxVertPerPoly, false);
}
public NavMesh read32Bit(BinaryReader @is, int maxVertPerPoly) {
return read(IOUtils.toByteBuffer(@is), maxVertPerPoly, true);
}
public NavMesh read32Bit(ByteBuffer bb, int maxVertPerPoly) {
return read(bb, maxVertPerPoly, true);
}
public NavMesh read(BinaryReader @is) {
return read(IOUtils.toByteBuffer(@is));
}
public NavMesh read(ByteBuffer bb) {
return read(bb, -1, false);
}
NavMesh read(ByteBuffer bb, int maxVertPerPoly, bool is32Bit) {
NavMeshSetHeader header = readHeader(bb, maxVertPerPoly);
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) { public NavMesh read(ByteBuffer bb, int maxVertPerPoly)
NavMeshSetHeader header = new NavMeshSetHeader(); {
header.magic = bb.getInt(); return read(bb, maxVertPerPoly, false);
if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC) {
header.magic = IOUtils.swapEndianness(header.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) {
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) {
header.maxVertsPerPoly = bb.getInt();
}
return header;
}
private void readTiles(ByteBuffer bb, bool is32Bit, NavMeshSetHeader header, bool cCompatibility, NavMesh mesh) public NavMesh read32Bit(BinaryReader @is, int maxVertPerPoly)
{
return read(IOUtils.toByteBuffer(@is), maxVertPerPoly, true);
}
public NavMesh read32Bit(ByteBuffer bb, int maxVertPerPoly)
{
return read(bb, maxVertPerPoly, true);
}
public NavMesh read(BinaryReader @is)
{
return read(IOUtils.toByteBuffer(@is));
}
public NavMesh read(ByteBuffer bb)
{
return read(bb, -1, false);
}
NavMesh read(ByteBuffer bb, int maxVertPerPoly, bool is32Bit)
{
NavMeshSetHeader header = readHeader(bb, maxVertPerPoly);
if (header.maxVertsPerPoly <= 0)
{ {
// Read tiles. throw new IOException("Invalid number of verts per poly " + header.maxVertsPerPoly);
for (int i = 0; i < header.numTiles; ++i) {
NavMeshTileHeader tileHeader = new NavMeshTileHeader();
if (is32Bit) {
tileHeader.tileRef = convert32BitRef(bb.getInt(), header.option);
} else {
tileHeader.tileRef = bb.getLong();
} }
tileHeader.dataSize = bb.getInt();
if (tileHeader.tileRef == 0 || tileHeader.dataSize == 0) { bool cCompatibility = header.version == NavMeshSetHeader.NAVMESHSET_VERSION;
break; NavMesh mesh = new NavMesh(header.option, header.maxVertsPerPoly);
readTiles(bb, is32Bit, header, cCompatibility, mesh);
return mesh;
}
private NavMeshSetHeader readHeader(ByteBuffer bb, int maxVertsPerPoly)
{
NavMeshSetHeader header = new NavMeshSetHeader();
header.magic = bb.getInt();
if (header.magic != NavMeshSetHeader.NAVMESHSET_MAGIC)
{
header.magic = IOUtils.swapEndianness(header.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);
} }
if (cCompatibility && !is32Bit) {
bb.getInt(); // C struct padding header.version = bb.getInt();
if (header.version != NavMeshSetHeader.NAVMESHSET_VERSION && header.version != NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J_1
&& header.version != NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J)
{
throw new IOException("Invalid version " + header.version);
} }
MeshData data = meshReader.read(bb, mesh.getMaxVertsPerPoly(), is32Bit);
mesh.addTile(data, i, tileHeader.tileRef); header.numTiles = bb.getInt();
header.option = paramReader.read(bb);
header.maxVertsPerPoly = maxVertsPerPoly;
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)
{
NavMeshTileHeader tileHeader = new NavMeshTileHeader();
if (is32Bit)
{
tileHeader.tileRef = convert32BitRef(bb.getInt(), header.option);
}
else
{
tileHeader.tileRef = bb.getLong();
}
tileHeader.dataSize = bb.getInt();
if (tileHeader.tileRef == 0 || tileHeader.dataSize == 0)
{
break;
}
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)
{
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.
int m_saltBits = Math.Min(31, 32 - m_tileBits - m_polyBits);
int saltMask = (1 << m_saltBits) - 1;
int tileMask = (1 << m_tileBits) - 1;
int polyMask = (1 << m_polyBits) - 1;
int salt = ((refs >> (m_polyBits + m_tileBits)) & saltMask);
int it = ((refs >> m_polyBits) & tileMask);
int ip = refs & polyMask;
return NavMesh.encodePolyId(salt, it, ip);
} }
} }
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.
int m_saltBits = Math.Min(31, 32 - m_tileBits - m_polyBits);
int saltMask = (1 << m_saltBits) - 1;
int tileMask = (1 << m_tileBits) - 1;
int polyMask = (1 << m_polyBits) - 1;
int salt = ((refs >> (m_polyBits + m_tileBits)) & saltMask);
int it = ((refs >> m_polyBits) & tileMask);
int ip = refs & polyMask;
return NavMesh.encodePolyId(salt, it, ip);
}
}
} }

View File

@ -21,62 +21,70 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io namespace DotRecast.Detour.Io
{ {
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 class MeshSetWriter : DetourWriter { {
writeHeader(stream, mesh, order, cCompatibility);
private readonly MeshDataWriter writer = new MeshDataWriter(); writeTiles(stream, mesh, order, cCompatibility);
private readonly NavMeshParamWriter paramWriter = new NavMeshParamWriter();
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) {
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) {
MeshTile tile = mesh.getTile(i);
if (tile == null || tile.data == null || tile.data.header == null) {
continue;
}
numTiles++;
} }
write(stream, numTiles, order);
paramWriter.write(stream, mesh.getParams(), order);
if (!cCompatibility) {
write(stream, mesh.getMaxVertsPerPoly(), order);
}
}
private void writeTiles(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility) { private void writeHeader(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility)
for (int i = 0; i < mesh.getMaxTiles(); ++i) { {
MeshTile tile = mesh.getTile(i); write(stream, NavMeshSetHeader.NAVMESHSET_MAGIC, order);
if (tile == null || tile.data == null || tile.data.header == null) { write(stream, cCompatibility ? NavMeshSetHeader.NAVMESHSET_VERSION : NavMeshSetHeader.NAVMESHSET_VERSION_RECAST4J, order);
continue; int numTiles = 0;
for (int i = 0; i < mesh.getMaxTiles(); ++i)
{
MeshTile tile = mesh.getTile(i);
if (tile == null || tile.data == null || tile.data.header == null)
{
continue;
}
numTiles++;
} }
NavMeshTileHeader tileHeader = new NavMeshTileHeader(); write(stream, numTiles, order);
tileHeader.tileRef = mesh.getTileRef(tile); paramWriter.write(stream, mesh.getParams(), order);
using MemoryStream bb = new MemoryStream(); if (!cCompatibility)
using BinaryWriter baos = new BinaryWriter(bb); {
writer.write(baos, tile.data, order, cCompatibility); write(stream, mesh.getMaxVertsPerPoly(), order);
baos.Flush(); }
baos.Close(); }
byte[] ba = bb.ToArray(); private void writeTiles(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility)
tileHeader.dataSize = ba.Length; {
write(stream, tileHeader.tileRef, order); for (int i = 0; i < mesh.getMaxTiles(); ++i)
write(stream, tileHeader.dataSize, order); {
if (cCompatibility) { MeshTile tile = mesh.getTile(i);
write(stream, 0, order); // C struct padding if (tile == null || tile.data == null || tile.data.header == null)
{
continue;
}
NavMeshTileHeader tileHeader = new NavMeshTileHeader();
tileHeader.tileRef = mesh.getTileRef(tile);
using MemoryStream bb = new MemoryStream();
using BinaryWriter baos = new BinaryWriter(bb);
writer.write(baos, tile.data, order, cCompatibility);
baos.Flush();
baos.Close();
byte[] ba = bb.ToArray();
tileHeader.dataSize = ba.Length;
write(stream, tileHeader.tileRef, order);
write(stream, tileHeader.dataSize, order);
if (cCompatibility)
{
write(stream, 0, order); // C struct padding
}
stream.Write(ba);
} }
stream.Write(ba);
} }
} }
}
} }

View File

@ -2,22 +2,19 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io namespace DotRecast.Detour.Io
{ {
public class NavMeshParamReader
{
public class NavMeshParamReader { public NavMeshParams read(ByteBuffer bb)
{
public NavMeshParams read(ByteBuffer bb) { NavMeshParams option = new NavMeshParams();
NavMeshParams option = new NavMeshParams(); option.orig[0] = bb.getFloat();
option.orig[0] = bb.getFloat(); option.orig[1] = bb.getFloat();
option.orig[1] = bb.getFloat(); option.orig[2] = bb.getFloat();
option.orig[2] = bb.getFloat(); option.tileWidth = bb.getFloat();
option.tileWidth = bb.getFloat(); option.tileHeight = bb.getFloat();
option.tileHeight = bb.getFloat(); option.maxTiles = bb.getInt();
option.maxTiles = bb.getInt(); option.maxPolys = bb.getInt();
option.maxPolys = bb.getInt(); return option;
return option; }
} }
}
} }

View File

@ -3,20 +3,17 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io namespace DotRecast.Detour.Io
{ {
public class NavMeshParamWriter : DetourWriter
{
public class NavMeshParamWriter : DetourWriter { public void write(BinaryWriter stream, NavMeshParams option, ByteOrder order)
{
public void write(BinaryWriter stream, NavMeshParams option, ByteOrder order) { write(stream, option.orig[0], order);
write(stream, option.orig[0], order); write(stream, option.orig[1], order);
write(stream, option.orig[1], order); write(stream, option.orig[2], order);
write(stream, option.orig[2], order); write(stream, option.tileWidth, order);
write(stream, option.tileWidth, order); write(stream, option.tileHeight, order);
write(stream, option.tileHeight, order); write(stream, option.maxTiles, order);
write(stream, option.maxTiles, order); write(stream, option.maxPolys, order);
write(stream, option.maxPolys, order); }
} }
}
} }

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -22,29 +22,33 @@ using System.Collections.Generic;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
/**
/**
* Defines a navigation mesh tile. * Defines a navigation mesh tile.
*/ */
public class MeshTile { public class MeshTile
public readonly int index; {
/** Counter describing modifications to the tile. */ public readonly int index;
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) { /** Counter describing modifications to the tile. */
this.index = index; 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)
{
this.index = index;
}
} }
}
} }

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -17,51 +17,52 @@ freely, subject to the following restrictions:
misrepresented as being the original software. misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
using System.Collections.Generic;
public class NodeQueue
using System.Collections.Generic;
public class NodeQueue {
private readonly List<Node> m_heap = new List<Node>();
public int count()
{ {
return m_heap.Count; private readonly List<Node> m_heap = new List<Node>();
}
public void clear() { public int count()
m_heap.Clear(); {
} return m_heap.Count;
}
public Node top() public void clear()
{ {
return m_heap[0]; m_heap.Clear();
} }
public Node pop() public Node top()
{ {
var node = top(); return m_heap[0];
m_heap.Remove(node); }
return node;
}
public void push(Node node) { public Node pop()
m_heap.Add(node); {
m_heap.Sort((x, y) => x.total.CompareTo(y.total)); var node = top();
} m_heap.Remove(node);
return node;
}
public void modify(Node node) { public void push(Node node)
m_heap.Remove(node); {
push(node); m_heap.Add(node);
} m_heap.Sort((x, y) => x.total.CompareTo(y.total));
}
public bool isEmpty() public void modify(Node node)
{ {
return 0 == m_heap.Count; m_heap.Remove(node);
} push(node);
} }
public bool isEmpty()
{
return 0 == m_heap.Count;
}
}
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -20,79 +20,92 @@ using System;
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
using static DetourCommon;
public interface PolygonByCircleConstraint
{
float[] aply(float[] polyVerts, float[] circleCenter, float radius);
using static DetourCommon; public static PolygonByCircleConstraint noop()
{
public interface PolygonByCircleConstraint { return new NoOpPolygonByCircleConstraint();
float[] aply(float[] polyVerts, float[] circleCenter, float radius);
public static PolygonByCircleConstraint noop() {
return new NoOpPolygonByCircleConstraint();
}
public static PolygonByCircleConstraint strict() {
return new StrictPolygonByCircleConstraint();
}
public class NoOpPolygonByCircleConstraint : PolygonByCircleConstraint {
public float[] aply(float[] polyVerts, float[] circleCenter, float radius) {
return polyVerts;
} }
} public static PolygonByCircleConstraint strict()
{
return new StrictPolygonByCircleConstraint();
}
/** 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. * 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;
private const int CIRCLE_SEGMENTS = 12; public float[] aply(float[] verts, float[] center, float radius)
private static float[] unitCircle; {
float radiusSqr = radius * radius;
public float[] aply(float[] verts, float[] center, float radius) { int outsideVertex = -1;
float radiusSqr = radius * radius; for (int pv = 0; pv < verts.Length; pv += 3)
int outsideVertex = -1; {
for (int pv = 0; pv < verts.Length; pv += 3) { if (vDist2DSqr(center, verts, pv) > radiusSqr)
if (vDist2DSqr(center, verts, pv) > radiusSqr) { {
outsideVertex = pv; outsideVertex = pv;
break; break;
}
} }
}
if (outsideVertex == -1) {
// 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)) {
// circle inside polygon
return qCircle;
}
return intersection;
}
private float[] circle(float[] center, float radius) { if (outsideVertex == -1)
if (unitCircle == null) { {
unitCircle = new float[CIRCLE_SEGMENTS * 3]; // polygon inside circle
for (int i = 0; i < CIRCLE_SEGMENTS; i++) { return verts;
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[] qCircle = circle(center, radius);
float[] intersection = ConvexConvexIntersection.intersect(verts, qCircle);
if (intersection == null && pointInPolygon(center, verts, verts.Length / 3))
{
// circle inside polygon
return qCircle;
}
return intersection;
} }
float[] circle = new float[12 * 3];
for (int i = 0; i < CIRCLE_SEGMENTS * 3; i += 3) { private float[] circle(float[] center, float radius)
circle[i] = unitCircle[i] * radius + center[0]; {
circle[i + 1] = center[1]; if (unitCircle == null)
circle[i + 2] = unitCircle[i + 2] * radius + center[2]; {
unitCircle = new float[CIRCLE_SEGMENTS * 3];
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)
{
circle[i] = unitCircle[i] * radius + center[0];
circle[i + 1] = center[1];
circle[i + 2] = unitCircle[i + 2] * radius + center[2];
}
return circle;
} }
return circle;
} }
} }
}
} }

View File

@ -17,20 +17,20 @@ freely, subject to the following restrictions:
misrepresented as being the original software. misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
namespace DotRecast.Detour namespace DotRecast.Detour
{ {
public class QueryData
{
public class QueryData { public Status status;
public Status status; public Node lastBestNode;
public Node lastBestNode; public float lastBestNodeCost;
public float lastBestNodeCost; public long startRef, endRef;
public long startRef, endRef; public float[] startPos = new float[3];
public float[] startPos = new float[3]; public float[] endPos = new float[3];
public float[] endPos = new float[3]; public QueryFilter filter;
public QueryFilter filter; public int options;
public int options; public float raycastLimitSqr;
public float raycastLimitSqr; public QueryHeuristic heuristic;
public QueryHeuristic heuristic; }
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,10 +17,11 @@ freely, subject to the following restrictions:
misrepresented as being the original software. misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
namespace DotRecast.Recast.Demo.Builder; namespace DotRecast.Recast.Demo.Builder;
public class SampleAreaModifications { public class SampleAreaModifications
{
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x0; public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x0;
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x1; public const int SAMPLE_POLYAREA_TYPE_WATER = 0x1;
public const int SAMPLE_POLYAREA_TYPE_ROAD = 0x2; public const int SAMPLE_POLYAREA_TYPE_ROAD = 0x2;
@ -44,4 +45,4 @@ public class SampleAreaModifications {
public static readonly int SAMPLE_POLYFLAGS_JUMP = 0x08; // Ability to jump. public static readonly int SAMPLE_POLYFLAGS_JUMP = 0x08; // Ability to jump.
public static readonly int SAMPLE_POLYFLAGS_DISABLED = 0x10; // Disabled polygon public static readonly int SAMPLE_POLYFLAGS_DISABLED = 0x10; // Disabled polygon
public static readonly int SAMPLE_POLYFLAGS_ALL = 0xffff; // All abilities. public static readonly int SAMPLE_POLYFLAGS_ALL = 0xffff; // All abilities.
} }

View File

@ -24,47 +24,49 @@ using DotRecast.Recast.Demo.Geom;
namespace DotRecast.Recast.Demo.Builder; namespace DotRecast.Recast.Demo.Builder;
public class SoloNavMeshBuilder : AbstractNavMeshBuilder { public class SoloNavMeshBuilder : AbstractNavMeshBuilder
{
public Tuple<IList<RecastBuilderResult>, NavMesh> build(DemoInputGeomProvider m_geom, PartitionType m_partitionType, public Tuple<IList<RecastBuilderResult>, NavMesh> build(DemoInputGeomProvider m_geom, PartitionType m_partitionType,
float m_cellSize, float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_cellSize, float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb,
float m_agentMaxSlope, int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, float m_agentMaxSlope, int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError,
int m_vertsPerPoly, float m_detailSampleDist, float m_detailSampleMaxError, bool filterLowHangingObstacles, int m_vertsPerPoly, float m_detailSampleDist, float m_detailSampleMaxError, bool filterLowHangingObstacles,
bool filterLedgeSpans, bool filterWalkableLowHeightSpans) { bool filterLedgeSpans, bool filterWalkableLowHeightSpans)
{
RecastBuilderResult rcResult = buildRecastResult(m_geom, m_partitionType, m_cellSize, m_cellHeight, m_agentHeight, RecastBuilderResult rcResult = buildRecastResult(m_geom, m_partitionType, m_cellSize, m_cellHeight, m_agentHeight,
m_agentRadius, m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError, m_agentRadius, m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, filterLowHangingObstacles, filterLedgeSpans, m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, filterLowHangingObstacles, filterLedgeSpans,
filterWalkableLowHeightSpans); filterWalkableLowHeightSpans);
return Tuple.Create(ImmutableArray.Create(rcResult) as IList<RecastBuilderResult>, return Tuple.Create(ImmutableArray.Create(rcResult) as IList<RecastBuilderResult>,
buildNavMesh( buildNavMesh(
buildMeshData(m_geom, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius, m_agentMaxClimb, rcResult), buildMeshData(m_geom, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius, m_agentMaxClimb, rcResult),
m_vertsPerPoly)); m_vertsPerPoly));
} }
private NavMesh buildNavMesh(MeshData meshData, int m_vertsPerPoly) { private NavMesh buildNavMesh(MeshData meshData, int m_vertsPerPoly)
{
return new NavMesh(meshData, m_vertsPerPoly, 0); return new NavMesh(meshData, m_vertsPerPoly, 0);
} }
private RecastBuilderResult buildRecastResult(DemoInputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize, private RecastBuilderResult buildRecastResult(DemoInputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize,
float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope, float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope,
int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly, int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly,
float m_detailSampleDist, float m_detailSampleMaxError, bool filterLowHangingObstacles, bool filterLedgeSpans, float m_detailSampleDist, float m_detailSampleMaxError, bool filterLowHangingObstacles, bool filterLedgeSpans,
bool filterWalkableLowHeightSpans) { bool filterWalkableLowHeightSpans)
{
RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize, m_cellHeight, m_agentMaxSlope, filterLowHangingObstacles, RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize, m_cellHeight, m_agentMaxSlope, filterLowHangingObstacles,
filterLedgeSpans, filterWalkableLowHeightSpans, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_regionMinSize, filterLedgeSpans, filterWalkableLowHeightSpans, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_regionMinSize,
m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError,
SampleAreaModifications.SAMPLE_AREAMOD_WALKABLE, true); SampleAreaModifications.SAMPLE_AREAMOD_WALKABLE, true);
RecastBuilderConfig bcfg = new RecastBuilderConfig(cfg, m_geom.getMeshBoundsMin(), m_geom.getMeshBoundsMax()); RecastBuilderConfig bcfg = new RecastBuilderConfig(cfg, m_geom.getMeshBoundsMin(), m_geom.getMeshBoundsMax());
RecastBuilder rcBuilder = new RecastBuilder(); RecastBuilder rcBuilder = new RecastBuilder();
return rcBuilder.build(m_geom, bcfg); return rcBuilder.build(m_geom, bcfg);
} }
private MeshData buildMeshData(DemoInputGeomProvider m_geom, float m_cellSize, float m_cellHeight, float m_agentHeight, private MeshData buildMeshData(DemoInputGeomProvider m_geom, float m_cellSize, float m_cellHeight, float m_agentHeight,
float m_agentRadius, float m_agentMaxClimb, RecastBuilderResult rcResult) { float m_agentRadius, float m_agentMaxClimb, RecastBuilderResult rcResult)
{
NavMeshDataCreateParams option = getNavMeshCreateParams(m_geom, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius, NavMeshDataCreateParams option = getNavMeshCreateParams(m_geom, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius,
m_agentMaxClimb, rcResult); m_agentMaxClimb, rcResult);
return updateAreaAndFlags(NavMeshBuilder.createNavMeshData(option)); return updateAreaAndFlags(NavMeshBuilder.createNavMeshData(option));
} }
}
}

View File

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

View File

@ -1,24 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Serilog" Version="2.12.0" /> <PackageReference Include="Serilog" Version="2.12.0"/>
<PackageReference Include="Silk.NET" Version="2.16.0" /> <PackageReference Include="Silk.NET" Version="2.16.0"/>
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.16.0" /> <PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.16.0"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DotRecast.Detour.Crowd\DotRecast.Detour.Crowd.csproj" /> <ProjectReference Include="..\DotRecast.Detour.Crowd\DotRecast.Detour.Crowd.csproj"/>
<ProjectReference Include="..\DotRecast.Detour.Dynamic\DotRecast.Detour.Dynamic.csproj" /> <ProjectReference Include="..\DotRecast.Detour.Dynamic\DotRecast.Detour.Dynamic.csproj"/>
<ProjectReference Include="..\DotRecast.Detour.Extras\DotRecast.Detour.Extras.csproj" /> <ProjectReference Include="..\DotRecast.Detour.Extras\DotRecast.Detour.Extras.csproj"/>
<ProjectReference Include="..\DotRecast.Detour.TileCache\DotRecast.Detour.TileCache.csproj" /> <ProjectReference Include="..\DotRecast.Detour.TileCache\DotRecast.Detour.TileCache.csproj"/>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" /> <ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj"/>
<ProjectReference Include="..\DotRecast.Recast\DotRecast.Recast.csproj" /> <ProjectReference Include="..\DotRecast.Recast\DotRecast.Recast.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -288,7 +288,7 @@ public class RecastDemo : MouseListener
// glfwWindowHint(GLFW_GREEN_BITS, mode.greenBits()); // glfwWindowHint(GLFW_GREEN_BITS, mode.greenBits());
// glfwWindowHint(GLFW_BLUE_BITS, mode.blueBits()); // glfwWindowHint(GLFW_BLUE_BITS, mode.blueBits());
// glfwWindowHint(GLFW_REFRESH_RATE, mode.refreshRate()); // glfwWindowHint(GLFW_REFRESH_RATE, mode.refreshRate());
var options = WindowOptions.Default; var options = WindowOptions.Default;
options.Title = title; options.Title = title;
@ -363,7 +363,7 @@ public class RecastDemo : MouseListener
{ {
width = size.X; width = size.X;
height = size.Y; height = size.Y;
//_gl.FramebufferParameter(GLEnum.Size); //_gl.FramebufferParameter(GLEnum.Size);
//_graphicsDevice.ResizeMainWindow((uint)size.X, (uint)size.Y); //_graphicsDevice.ResizeMainWindow((uint)size.X, (uint)size.Y);
} }
@ -377,11 +377,11 @@ public class RecastDemo : MouseListener
private void OnWindowOnLoad() private void OnWindowOnLoad()
{ {
var s = SdlWindowing.GetExistingApi(window); var s = SdlWindowing.GetExistingApi(window);
_input = window.CreateInput(); _input = window.CreateInput();
_gl = window.CreateOpenGL(); _gl = window.CreateOpenGL();
//dd.init(_gl, camr); //dd.init(_gl, camr);
_imgui = new ImGuiController(_gl, window, _input); _imgui = new ImGuiController(_gl, window, _input);
@ -703,14 +703,14 @@ public class RecastDemo : MouseListener
{ {
_gl.ClearColor(Color.CornflowerBlue); _gl.ClearColor(Color.CornflowerBlue);
_gl.Clear(ClearBufferMask.ColorBufferBit); _gl.Clear(ClearBufferMask.ColorBufferBit);
mouseOverMenu = _viewSys.render(window, 0, 0, width, height, (int)mousePos[0], (int)mousePos[1]); mouseOverMenu = _viewSys.render(window, 0, 0, width, height, (int)mousePos[0], (int)mousePos[1]);
ImGui.Button("hello"); ImGui.Button("hello");
ImGui.Button("world"); ImGui.Button("world");
_imgui.Render(); _imgui.Render();
window.SwapBuffers(); window.SwapBuffers();
} }

View File

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

View File

@ -100,7 +100,7 @@ public class RcSettingsView : IRcView
ImGui.SliderFloat("Max Climb", ref agentMaxClimb, 5f, 0.1f, $"{agentMaxClimb}"); ImGui.SliderFloat("Max Climb", ref agentMaxClimb, 5f, 0.1f, $"{agentMaxClimb}");
ImGui.SliderFloat("Max Slope", ref agentMaxSlope, 90f, 1f, $"{agentMaxSlope}"); ImGui.SliderFloat("Max Slope", ref agentMaxSlope, 90f, 1f, $"{agentMaxSlope}");
ImGui.NewLine(); ImGui.NewLine();
ImGui.Text("Region"); ImGui.Text("Region");
ImGui.Separator(); ImGui.Separator();
ImGui.SliderInt("Min Region Size", ref minRegionSize, 1, 150); ImGui.SliderInt("Min Region Size", ref minRegionSize, 1, 150);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,54 +20,52 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class AreaModification
{
public readonly int RC_AREA_FLAGS_MASK = 0x3F;
private readonly int value;
private readonly int mask;
public class AreaModification /**
{
public readonly int RC_AREA_FLAGS_MASK = 0x3F;
private readonly int value;
private readonly int mask;
/**
* Mask is set to all available bits, which means value is fully applied * Mask is set to all available bits, which means value is fully applied
* *
* @param value * @param value
* The area id to apply. [Limit: &lt;= #RC_AREA_FLAGS_MASK] * The area id to apply. [Limit: &lt;= #RC_AREA_FLAGS_MASK]
*/ */
public AreaModification(int value) public AreaModification(int value)
{ {
this.value = value; this.value = value;
mask = RC_AREA_FLAGS_MASK; mask = RC_AREA_FLAGS_MASK;
} }
/** /**
* *
* @param value * @param value
* The area id to apply. [Limit: &lt;= #RC_AREA_FLAGS_MASK] * The area id to apply. [Limit: &lt;= #RC_AREA_FLAGS_MASK]
* @param mask * @param mask
* Bitwise mask used when applying value. [Limit: &lt;= #RC_AREA_FLAGS_MASK] * Bitwise mask used when applying value. [Limit: &lt;= #RC_AREA_FLAGS_MASK]
*/ */
public AreaModification(int value, int mask) public AreaModification(int value, int mask)
{ {
this.value = value; this.value = value;
this.mask = mask; this.mask = mask;
} }
public AreaModification(AreaModification other) public AreaModification(AreaModification other)
{ {
value = other.value; value = other.value;
mask = other.mask; mask = other.mask;
} }
public int getMaskedValue() public int getMaskedValue()
{ {
return value & mask; return value & mask;
} }
public int apply(int area) public int apply(int area)
{ {
return ((value & mask) | (area & ~mask)); return ((value & mask) | (area & ~mask));
}
} }
}
} }

View File

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

View File

@ -20,57 +20,55 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/** A compact, static heightfield representing unobstructed space. */
public class CompactHeightfield
{
/** The width of the heightfield. (Along the x-axis in cell units.) */
public int width;
/** The height of the heightfield. (Along the z-axis in cell units.) */
public int height;
/** A compact, static heightfield representing unobstructed space. */ /** The number of spans in the heightfield. */
public class CompactHeightfield public int spanCount;
{
/** The width of the heightfield. (Along the x-axis in cell units.) */
public int width;
/** The height of the heightfield. (Along the z-axis in cell units.) */ /** The walkable height used during the build of the field. (See: RecastConfig::walkableHeight) */
public int height; public int walkableHeight;
/** The number of spans in the heightfield. */ /** The walkable climb used during the build of the field. (See: RecastConfig::walkableClimb) */
public int spanCount; public int walkableClimb;
/** The walkable height used during the build of the field. (See: RecastConfig::walkableHeight) */ /** The AABB border size used during the build of the field. (See: RecastConfig::borderSize) */
public int walkableHeight; public int borderSize;
/** The walkable climb used during the build of the field. (See: RecastConfig::walkableClimb) */ /** The maximum distance value of any span within the field. */
public int walkableClimb; public int maxDistance;
/** The AABB border size used during the build of the field. (See: RecastConfig::borderSize) */ /** The maximum region id of any span within the field. */
public int borderSize; public int maxRegions;
/** The maximum distance value of any span within the field. */ /** The minimum bounds in world space. [(x, y, z)] */
public int maxDistance; public float[] bmin = new float[3];
/** The maximum region id of any span within the field. */ /** The maximum bounds in world space. [(x, y, z)] */
public int maxRegions; public float[] bmax = new float[3];
/** The minimum bounds in world space. [(x, y, z)] */ /** The size of each cell. (On the xz-plane.) */
public float[] bmin = new float[3]; public float cs;
/** The maximum bounds in world space. [(x, y, z)] */ /** The height of each cell. (The minimum increment along the y-axis.) */
public float[] bmax = new float[3]; public float ch;
/** The size of each cell. (On the xz-plane.) */ /** Array of cells. [Size: #width*#height] */
public float cs; public CompactCell[] cells;
/** The height of each cell. (The minimum increment along the y-axis.) */ /** Array of spans. [Size: #spanCount] */
public float ch; public CompactSpan[] spans;
/** Array of cells. [Size: #width*#height] */ /** Array containing border distance data. [Size: #spanCount] */
public CompactCell[] cells; public int[] dist;
/** Array of spans. [Size: #spanCount] */ /** Array containing area id data. [Size: #spanCount] */
public CompactSpan[] spans; public int[] areas;
}
/** Array containing border distance data. [Size: #spanCount] */
public int[] dist;
/** Array containing area id data. [Size: #spanCount] */
public int[] areas;
}
} }

View File

@ -20,21 +20,19 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/** Represents a span of unobstructed space within a compact heightfield. */
public class CompactSpan
{
/** The lower extent of the span. (Measured from the heightfield's base.) */
public int y;
/** The id of the region the span belongs to. (Or zero if not in a region.) */
public int reg;
/** Represents a span of unobstructed space within a compact heightfield. */ /** Packed neighbor connection data. */
public class CompactSpan public int con;
{
/** The lower extent of the span. (Measured from the heightfield's base.) */
public int y;
/** The id of the region the span belongs to. (Or zero if not in a region.) */ /** The height of the span. (Measured from #y.) */
public int reg; public int h;
}
/** Packed neighbor connection data. */
public int con;
/** The height of the span. (Measured from #y.) */
public int h;
}
} }

View File

@ -20,27 +20,25 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/** Represents a simple, non-overlapping contour in field space. */
public class Contour
{
/** Simplified contour vertex and connection data. [Size: 4 * #nverts] */
public int[] verts;
/** The number of vertices in the simplified contour. */
public int nverts;
/** Represents a simple, non-overlapping contour in field space. */ /** Raw contour vertex and connection data. [Size: 4 * #nrverts] */
public class Contour public int[] rverts;
{
/** Simplified contour vertex and connection data. [Size: 4 * #nverts] */
public int[] verts;
/** The number of vertices in the simplified contour. */ /** The number of vertices in the raw contour. */
public int nverts; public int nrverts;
/** Raw contour vertex and connection data. [Size: 4 * #nrverts] */ /** The region id of the contour. */
public int[] rverts; public int area;
/** The number of vertices in the raw contour. */ /** The area id of the contour. */
public int nrverts; public int reg;
}
/** The region id of the contour. */
public int area;
/** The area id of the contour. */
public int reg;
}
} }

View File

@ -22,36 +22,34 @@ using System.Collections.Generic;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/** Represents a group of related contours. */
public class ContourSet
{
/** A list of the contours in the set. */
public List<Contour> conts = new List<Contour>();
/** The minimum bounds in world space. [(x, y, z)] */
public float[] bmin = new float[3];
/** Represents a group of related contours. */ /** The maximum bounds in world space. [(x, y, z)] */
public class ContourSet public float[] bmax = new float[3];
{
/** A list of the contours in the set. */
public List<Contour> conts = new List<Contour>();
/** The minimum bounds in world space. [(x, y, z)] */ /** The size of each cell. (On the xz-plane.) */
public float[] bmin = new float[3]; public float cs;
/** The maximum bounds in world space. [(x, y, z)] */ /** The height of each cell. (The minimum increment along the y-axis.) */
public float[] bmax = new float[3]; public float ch;
/** The size of each cell. (On the xz-plane.) */ /** The width of the set. (Along the x-axis in cell units.) */
public float cs; public int width;
/** The height of each cell. (The minimum increment along the y-axis.) */ /** The height of the set. (Along the z-axis in cell units.) */
public float ch; public int height;
/** The width of the set. (Along the x-axis in cell units.) */ /** The AABB border size used to generate the source data from which the contours were derived. */
public int width; public int borderSize;
/** The height of the set. (Along the z-axis in cell units.) */ /** The max edge error that this contour set was simplified with. */
public int height; public float maxError;
}
/** The AABB border size used to generate the source data from which the contours were derived. */
public int borderSize;
/** The max edge error that this contour set was simplified with. */
public float maxError;
}
} }

View File

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

View File

@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework> <TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DotRecast.Core\DotRecast.Core.csproj" /> <ProjectReference Include="..\DotRecast.Core\DotRecast.Core.csproj"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -23,228 +23,226 @@ using System.Collections.Generic;
namespace DotRecast.Recast.Geom namespace DotRecast.Recast.Geom
{ {
public class ChunkyTriMesh
public class ChunkyTriMesh
{
private class BoundsItem
{ {
public readonly float[] bmin = new float[2]; private class BoundsItem
public readonly float[] bmax = new float[2];
public int i;
}
private class CompareItemX : IComparer<BoundsItem>
{
public int Compare(BoundsItem? a, BoundsItem? b)
{ {
return a.bmin[0].CompareTo(b.bmin[0]); public readonly float[] bmin = new float[2];
public readonly float[] bmax = new float[2];
public int i;
} }
}
private class CompareItemY : IComparer<BoundsItem> private class CompareItemX : IComparer<BoundsItem>
{
public int Compare(BoundsItem? a, BoundsItem? b)
{ {
return a.bmin[1].CompareTo(b.bmin[1]); public int Compare(BoundsItem? a, BoundsItem? b)
}
}
List<ChunkyTriMeshNode> nodes;
int ntris;
int maxTrisPerChunk;
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)
{
BoundsItem it = items[i];
if (it.bmin[0] < bmin[0])
{ {
bmin[0] = it.bmin[0]; return a.bmin[0].CompareTo(b.bmin[0]);
}
if (it.bmin[1] < bmin[1])
{
bmin[1] = it.bmin[1];
}
if (it.bmax[0] > bmax[0])
{
bmax[0] = it.bmax[0];
}
if (it.bmax[1] > bmax[1])
{
bmax[1] = it.bmax[1];
} }
} }
}
private int longestAxis(float x, float y) private class CompareItemY : IComparer<BoundsItem>
{
return y > x ? 1 : 0;
}
private void subdivide(BoundsItem[] items, int imin, int imax, int trisPerChunk, List<ChunkyTriMeshNode> nodes,
int[] inTris)
{
int inum = imax - imin;
ChunkyTriMeshNode node = new ChunkyTriMeshNode();
nodes.Add(node);
if (inum <= trisPerChunk)
{ {
// Leaf public int Compare(BoundsItem? a, BoundsItem? b)
calcExtends(items, imin, imax, node.bmin, node.bmax);
// Copy triangles.
node.i = nodes.Count;
node.tris = new int[inum * 3];
int dst = 0;
for (int i = imin; i < imax; ++i)
{ {
int src = items[i].i * 3; return a.bmin[1].CompareTo(b.bmin[1]);
node.tris[dst++] = inTris[src];
node.tris[dst++] = inTris[src + 1];
node.tris[dst++] = inTris[src + 2];
} }
} }
else
List<ChunkyTriMeshNode> nodes;
int ntris;
int maxTrisPerChunk;
private void calcExtends(BoundsItem[] items, int imin, int imax, float[] bmin, float[] bmax)
{ {
// Split bmin[0] = items[imin].bmin[0];
calcExtends(items, imin, imax, node.bmin, node.bmax); bmin[1] = items[imin].bmin[1];
int axis = longestAxis(node.bmax[0] - node.bmin[0], node.bmax[1] - node.bmin[1]); bmax[0] = items[imin].bmax[0];
bmax[1] = items[imin].bmax[1];
if (axis == 0) for (int i = imin + 1; i < imax; ++i)
{ {
Array.Sort(items, imin, imax - imin, new CompareItemX()); BoundsItem it = items[i];
// Sort along x-axis if (it.bmin[0] < bmin[0])
}
else if (axis == 1)
{
Array.Sort(items, imin, imax - imin, new CompareItemY());
// Sort along y-axis
}
int isplit = imin + inum / 2;
// Left
subdivide(items, imin, isplit, trisPerChunk, nodes, inTris);
// Right
subdivide(items, isplit, imax, trisPerChunk, nodes, inTris);
// Negative index means escape.
node.i = -nodes.Count;
}
}
public ChunkyTriMesh(float[] verts, int[] tris, int ntris, int trisPerChunk)
{
int nchunks = (ntris + trisPerChunk - 1) / trisPerChunk;
nodes = new List<ChunkyTriMeshNode>(nchunks);
this.ntris = ntris;
// Build tree
BoundsItem[] items = new BoundsItem[ntris];
for (int i = 0; i < ntris; i++)
{
int t = i * 3;
BoundsItem it = items[i] = new BoundsItem();
it.i = i;
// Calc triangle XZ bounds.
it.bmin[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)
{
int v = tris[t + j] * 3;
if (verts[v] < it.bmin[0])
{ {
it.bmin[0] = verts[v]; bmin[0] = it.bmin[0];
} }
if (verts[v + 2] < it.bmin[1]) if (it.bmin[1] < bmin[1])
{ {
it.bmin[1] = verts[v + 2]; bmin[1] = it.bmin[1];
} }
if (verts[v] > it.bmax[0]) if (it.bmax[0] > bmax[0])
{ {
it.bmax[0] = verts[v]; bmax[0] = it.bmax[0];
} }
if (verts[v + 2] > it.bmax[1]) if (it.bmax[1] > bmax[1])
{ {
it.bmax[1] = verts[v + 2]; bmax[1] = it.bmax[1];
} }
} }
} }
subdivide(items, 0, ntris, trisPerChunk, nodes, tris); private int longestAxis(float x, float y)
// Calc max tris per node.
maxTrisPerChunk = 0;
foreach (ChunkyTriMeshNode node in nodes)
{ {
bool isLeaf = node.i >= 0; return y > x ? 1 : 0;
if (!isLeaf)
{
continue;
}
if (node.tris.Length / 3 > maxTrisPerChunk)
{
maxTrisPerChunk = node.tris.Length / 3;
}
} }
}
private bool checkOverlapRect(float[] amin, float[] amax, float[] bmin, float[] bmax) private void subdivide(BoundsItem[] items, int imin, int imax, int trisPerChunk, List<ChunkyTriMeshNode> nodes,
{ int[] inTris)
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> getChunksOverlappingRect(float[] bmin, float[] bmax)
{
// Traverse tree
List<ChunkyTriMeshNode> ids = new List<ChunkyTriMeshNode>();
int i = 0;
while (i < nodes.Count)
{ {
ChunkyTriMeshNode node = nodes[i]; int inum = imax - imin;
bool overlap = checkOverlapRect(bmin, bmax, node.bmin, node.bmax);
bool isLeafNode = node.i >= 0;
if (isLeafNode && overlap) ChunkyTriMeshNode node = new ChunkyTriMeshNode();
{ nodes.Add(node);
ids.Add(node);
}
if (overlap || isLeafNode) if (inum <= trisPerChunk)
{ {
i++; // Leaf
calcExtends(items, imin, imax, node.bmin, node.bmax);
// Copy triangles.
node.i = nodes.Count;
node.tris = new int[inum * 3];
int dst = 0;
for (int i = imin; i < imax; ++i)
{
int src = items[i].i * 3;
node.tris[dst++] = inTris[src];
node.tris[dst++] = inTris[src + 1];
node.tris[dst++] = inTris[src + 2];
}
} }
else else
{ {
i = -node.i; // 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)
{
Array.Sort(items, imin, imax - imin, new CompareItemX());
// Sort along x-axis
}
else if (axis == 1)
{
Array.Sort(items, imin, imax - imin, new CompareItemY());
// Sort along y-axis
}
int isplit = imin + inum / 2;
// Left
subdivide(items, imin, isplit, trisPerChunk, nodes, inTris);
// Right
subdivide(items, isplit, imax, trisPerChunk, nodes, inTris);
// Negative index means escape.
node.i = -nodes.Count;
} }
} }
return ids; public ChunkyTriMesh(float[] verts, int[] tris, int ntris, int trisPerChunk)
{
int nchunks = (ntris + trisPerChunk - 1) / trisPerChunk;
nodes = new List<ChunkyTriMeshNode>(nchunks);
this.ntris = ntris;
// Build tree
BoundsItem[] items = new BoundsItem[ntris];
for (int i = 0; i < ntris; i++)
{
int t = i * 3;
BoundsItem it = items[i] = new BoundsItem();
it.i = i;
// Calc triangle XZ bounds.
it.bmin[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)
{
int v = tris[t + j] * 3;
if (verts[v] < it.bmin[0])
{
it.bmin[0] = verts[v];
}
if (verts[v + 2] < it.bmin[1])
{
it.bmin[1] = verts[v + 2];
}
if (verts[v] > it.bmax[0])
{
it.bmax[0] = verts[v];
}
if (verts[v + 2] > it.bmax[1])
{
it.bmax[1] = verts[v + 2];
}
}
}
subdivide(items, 0, ntris, trisPerChunk, nodes, tris);
// Calc max tris per node.
maxTrisPerChunk = 0;
foreach (ChunkyTriMeshNode node in nodes)
{
bool isLeaf = node.i >= 0;
if (!isLeaf)
{
continue;
}
if (node.tris.Length / 3 > maxTrisPerChunk)
{
maxTrisPerChunk = node.tris.Length / 3;
}
}
}
private bool checkOverlapRect(float[] amin, float[] amax, 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> getChunksOverlappingRect(float[] bmin, float[] bmax)
{
// Traverse tree
List<ChunkyTriMeshNode> ids = new List<ChunkyTriMeshNode>();
int i = 0;
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)
{
ids.Add(node);
}
if (overlap || isLeafNode)
{
i++;
}
else
{
i = -node.i;
}
}
return ids;
}
} }
}
} }

View File

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

View File

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

View File

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

View File

@ -24,116 +24,115 @@ using System.Collections.Immutable;
namespace DotRecast.Recast.Geom namespace DotRecast.Recast.Geom
{ {
public class SimpleInputGeomProvider : InputGeomProvider
public class SimpleInputGeomProvider : InputGeomProvider
{
public readonly float[] vertices;
public readonly int[] faces;
public readonly float[] normals;
readonly float[] bmin;
readonly float[] bmax;
readonly List<ConvexVolume> volumes = new List<ConvexVolume>();
public SimpleInputGeomProvider(List<float> vertexPositions, List<int> meshFaces)
: this(mapVertices(vertexPositions), mapFaces(meshFaces))
{ {
} public readonly float[] vertices;
public readonly int[] faces;
public readonly float[] normals;
readonly float[] bmin;
readonly float[] bmax;
readonly List<ConvexVolume> volumes = new List<ConvexVolume>();
private static int[] mapFaces(List<int> meshFaces) public SimpleInputGeomProvider(List<float> vertexPositions, List<int> meshFaces)
{ : this(mapVertices(vertexPositions), mapFaces(meshFaces))
int[] faces = new int[meshFaces.Count];
for (int i = 0; i < faces.Length; i++)
{ {
faces[i] = meshFaces[i];
} }
return faces; private static int[] mapFaces(List<int> meshFaces)
}
private static float[] mapVertices(List<float> vertexPositions)
{
float[] vertices = new float[vertexPositions.Count];
for (int i = 0; i < vertices.Length; i++)
{ {
vertices[i] = vertexPositions[i]; int[] faces = new int[meshFaces.Count];
} for (int i = 0; i < faces.Length; i++)
return vertices;
}
public SimpleInputGeomProvider(float[] vertices, int[] faces)
{
this.vertices = vertices;
this.faces = faces;
normals = new float[faces.Length];
calculateNormals();
bmin = new float[3];
bmax = new float[3];
RecastVectors.copy(bmin, vertices, 0);
RecastVectors.copy(bmax, vertices, 0);
for (int i = 1; i < vertices.Length / 3; i++)
{
RecastVectors.min(bmin, vertices, i * 3);
RecastVectors.max(bmax, vertices, i * 3);
}
}
public float[] getMeshBoundsMin()
{
return bmin;
}
public float[] getMeshBoundsMax()
{
return bmax;
}
public IList<ConvexVolume> convexVolumes()
{
return volumes;
}
public void addConvexVolume(float[] verts, float minh, float maxh, AreaModification areaMod)
{
ConvexVolume vol = new ConvexVolume();
vol.hmin = minh;
vol.hmax = maxh;
vol.verts = verts;
vol.areaMod = areaMod;
volumes.Add(vol);
}
public IEnumerable<TriMesh> meshes() {
return ImmutableArray.Create(new TriMesh(vertices, faces));
}
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)
{ {
e0[j] = vertices[v1 + j] - vertices[v0 + j]; faces[i] = meshFaces[i];
e1[j] = vertices[v2 + j] - vertices[v0 + j];
} }
normals[i] = e0[1] * e1[2] - e0[2] * e1[1]; return faces;
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]); private static float[] mapVertices(List<float> vertexPositions)
if (d > 0) {
float[] vertices = new float[vertexPositions.Count];
for (int i = 0; i < vertices.Length; i++)
{ {
d = 1.0f / d; vertices[i] = vertexPositions[i];
normals[i] *= d; }
normals[i + 1] *= d;
normals[i + 2] *= d; return vertices;
}
public SimpleInputGeomProvider(float[] vertices, int[] faces)
{
this.vertices = vertices;
this.faces = faces;
normals = new float[faces.Length];
calculateNormals();
bmin = new float[3];
bmax = new float[3];
RecastVectors.copy(bmin, vertices, 0);
RecastVectors.copy(bmax, vertices, 0);
for (int i = 1; i < vertices.Length / 3; i++)
{
RecastVectors.min(bmin, vertices, i * 3);
RecastVectors.max(bmax, vertices, i * 3);
}
}
public float[] getMeshBoundsMin()
{
return bmin;
}
public float[] getMeshBoundsMax()
{
return bmax;
}
public IList<ConvexVolume> convexVolumes()
{
return volumes;
}
public void addConvexVolume(float[] verts, float minh, float maxh, AreaModification areaMod)
{
ConvexVolume vol = new ConvexVolume();
vol.hmin = minh;
vol.hmax = maxh;
vol.verts = verts;
vol.areaMod = areaMod;
volumes.Add(vol);
}
public IEnumerable<TriMesh> meshes()
{
return ImmutableArray.Create(new TriMesh(vertices, faces));
}
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)
{
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)
{
d = 1.0f / d;
normals[i] *= d;
normals[i + 1] *= d;
normals[i + 2] *= d;
}
} }
} }
} }
}
} }

View File

@ -22,47 +22,45 @@ using System.Collections.Immutable;
namespace DotRecast.Recast.Geom namespace DotRecast.Recast.Geom
{ {
public class SingleTrimeshInputGeomProvider : InputGeomProvider
public class SingleTrimeshInputGeomProvider : InputGeomProvider
{
private readonly float[] bmin;
private readonly float[] bmax;
private readonly TriMesh[] _meshes;
public SingleTrimeshInputGeomProvider(float[] vertices, int[] faces)
{ {
bmin = new float[3]; private readonly float[] bmin;
bmax = new float[3]; private readonly float[] bmax;
RecastVectors.copy(bmin, vertices, 0); private readonly TriMesh[] _meshes;
RecastVectors.copy(bmax, vertices, 0);
for (int i = 1; i < vertices.Length / 3; i++) public SingleTrimeshInputGeomProvider(float[] vertices, int[] faces)
{ {
RecastVectors.min(bmin, vertices, i * 3); bmin = new float[3];
RecastVectors.max(bmax, vertices, i * 3); bmax = new float[3];
RecastVectors.copy(bmin, vertices, 0);
RecastVectors.copy(bmax, vertices, 0);
for (int i = 1; i < vertices.Length / 3; i++)
{
RecastVectors.min(bmin, vertices, i * 3);
RecastVectors.max(bmax, vertices, i * 3);
}
_meshes = new[] { new TriMesh(vertices, faces) };
} }
_meshes = new[] { new TriMesh(vertices, faces) }; public float[] getMeshBoundsMin()
} {
return bmin;
}
public float[] getMeshBoundsMin() public float[] getMeshBoundsMax()
{ {
return bmin; return bmax;
} }
public float[] getMeshBoundsMax() public IEnumerable<TriMesh> meshes()
{ {
return bmax; return _meshes;
} }
public IEnumerable<TriMesh> meshes() public IList<ConvexVolume> convexVolumes()
{ {
return _meshes; return ImmutableArray<ConvexVolume>.Empty;
}
} }
public IList<ConvexVolume> convexVolumes()
{
return ImmutableArray<ConvexVolume>.Empty;
}
}
} }

View File

@ -22,34 +22,32 @@ using System.Collections.Generic;
namespace DotRecast.Recast.Geom namespace DotRecast.Recast.Geom
{ {
public class TriMesh
public class TriMesh
{
private readonly float[] vertices;
private readonly int[] faces;
private readonly ChunkyTriMesh chunkyTriMesh;
public TriMesh(float[] vertices, int[] faces)
{ {
this.vertices = vertices; private readonly float[] vertices;
this.faces = faces; private readonly int[] faces;
chunkyTriMesh = new ChunkyTriMesh(vertices, faces, faces.Length / 3, 32); private readonly ChunkyTriMesh chunkyTriMesh;
}
public int[] getTris() public TriMesh(float[] vertices, int[] faces)
{ {
return faces; this.vertices = vertices;
} this.faces = faces;
chunkyTriMesh = new ChunkyTriMesh(vertices, faces, faces.Length / 3, 32);
}
public float[] getVerts() public int[] getTris()
{ {
return vertices; return faces;
} }
public List<ChunkyTriMeshNode> getChunksOverlappingRect(float[] bmin, float[] bmax) public float[] getVerts()
{ {
return chunkyTriMesh.getChunksOverlappingRect(bmin, bmax); return vertices;
}
public List<ChunkyTriMeshNode> getChunksOverlappingRect(float[] bmin, float[] bmax)
{
return chunkyTriMesh.getChunksOverlappingRect(bmin, bmax);
}
} }
}
} }

View File

@ -20,45 +20,43 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/** Represents a heightfield layer within a layer set. */
public class Heightfield
/** Represents a heightfield layer within a layer set. */
public class Heightfield
{
/** The width of the heightfield. (Along the x-axis in cell units.) */
public readonly int width;
/** The height of the heightfield. (Along the z-axis in cell units.) */
public readonly int height;
/** The minimum bounds in world space. [(x, y, z)] */
public readonly float[] bmin;
/** The maximum bounds in world space. [(x, y, z)] */
public readonly float[] bmax;
/** The size of each cell. (On the xz-plane.) */
public readonly float cs;
/** The height of each cell. (The minimum increment along the y-axis.) */
public readonly float ch;
/** Heightfield of spans (width*height). */
public readonly Span[] spans;
/** Border size in cell units */
public readonly int borderSize;
public Heightfield(int width, int height, float[] bmin, float[] bmax, float cs, float ch, int borderSize)
{ {
this.width = width; /** The width of the heightfield. (Along the x-axis in cell units.) */
this.height = height; public readonly int width;
this.bmin = bmin;
this.bmax = bmax; /** The height of the heightfield. (Along the z-axis in cell units.) */
this.cs = cs; public readonly int height;
this.ch = ch;
this.borderSize = borderSize; /** The minimum bounds in world space. [(x, y, z)] */
spans = new Span[width * height]; public readonly float[] bmin;
/** The maximum bounds in world space. [(x, y, z)] */
public readonly float[] bmax;
/** The size of each cell. (On the xz-plane.) */
public readonly float cs;
/** The height of each cell. (The minimum increment along the y-axis.) */
public readonly float ch;
/** Heightfield of spans (width*height). */
public readonly Span[] spans;
/** Border size in cell units */
public readonly int borderSize;
public Heightfield(int width, int height, float[] bmin, float[] bmax, float cs, float ch, int borderSize)
{
this.width = width;
this.height = height;
this.bmin = bmin;
this.bmax = bmax;
this.cs = cs;
this.ch = ch;
this.borderSize = borderSize;
spans = new Span[width * height];
}
} }
}
} }

View File

@ -20,62 +20,60 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/// Represents a set of heightfield layers.
/// @ingroup recast
/// Represents a set of heightfield layers. /// @see rcAllocHeightfieldLayerSet, rcFreeHeightfieldLayerSet
/// @ingroup recast public class HeightfieldLayerSet
/// @see rcAllocHeightfieldLayerSet, rcFreeHeightfieldLayerSet
public class HeightfieldLayerSet
{
/// Represents a heightfield layer within a layer set.
/// @see rcHeightfieldLayerSet
public class HeightfieldLayer
{ {
public readonly float[] bmin = new float[3]; /// Represents a heightfield layer within a layer set.
/// @see rcHeightfieldLayerSet
public class HeightfieldLayer
{
public readonly float[] bmin = new float[3];
/// < The minimum bounds in world space. [(x, y, z)] /// < The minimum bounds in world space. [(x, y, z)]
public readonly float[] bmax = new float[3]; public readonly float[] bmax = new float[3];
/// < The maximum bounds in world space. [(x, y, z)] /// < The maximum bounds in world space. [(x, y, z)]
public float cs; public float cs;
/// < The size of each cell. (On the xz-plane.) /// < The size of each cell. (On the xz-plane.)
public float ch; public float ch;
/// < The height of each cell. (The minimum increment along the y-axis.) /// < The height of each cell. (The minimum increment along the y-axis.)
public int width; public int width;
/// < The width of the heightfield. (Along the x-axis in cell units.) /// < The width of the heightfield. (Along the x-axis in cell units.)
public int height; public int height;
/// < The height of the heightfield. (Along the z-axis in cell units.) /// < The height of the heightfield. (Along the z-axis in cell units.)
public int minx; public int minx;
/// < The minimum x-bounds of usable data. /// < The minimum x-bounds of usable data.
public int maxx; public int maxx;
/// < The maximum x-bounds of usable data. /// < The maximum x-bounds of usable data.
public int miny; public int miny;
/// < The minimum y-bounds of usable data. (Along the z-axis.) /// < The minimum y-bounds of usable data. (Along the z-axis.)
public int maxy; public int maxy;
/// < The maximum y-bounds of usable data. (Along the z-axis.) /// < The maximum y-bounds of usable data. (Along the z-axis.)
public int hmin; public int hmin;
/// < The minimum height bounds of usable data. (Along the y-axis.) /// < The minimum height bounds of usable data. (Along the y-axis.)
public int hmax; public int hmax;
/// < The maximum height bounds of usable data. (Along the y-axis.) /// < The maximum height bounds of usable data. (Along the y-axis.)
public int[] heights; public int[] heights;
/// < The heightfield. [Size: width * height] /// < The heightfield. [Size: width * height]
public int[] areas; public int[] areas;
/// < Area ids. [Size: Same as #heights] /// < Area ids. [Size: Same as #heights]
public int[] cons; /// < Packed neighbor connection information. [Size: Same as #heights] public int[] cons; /// < Packed neighbor connection information. [Size: Same as #heights]
}
public HeightfieldLayer[] layers; /// < The layers in the set. [Size: #nlayers]
} }
public HeightfieldLayer[] layers; /// < The layers in the set. [Size: #nlayers]
}
} }

View File

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

View File

@ -23,119 +23,117 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public static class ObjImporter
public static class ObjImporter
{
public class ObjImporterContext
{ {
public List<float> vertexPositions = new List<float>(); public class ObjImporterContext
public List<int> meshFaces = new List<int>();
}
public static InputGeomProvider load(byte[] chunck)
{
var context = loadContext(chunck);
return new SimpleInputGeomProvider(context.vertexPositions, context.meshFaces);
}
public static ObjImporterContext loadContext(byte[] chunck)
{
ObjImporterContext context = new ObjImporterContext();
try
{ {
using StreamReader reader = new StreamReader(new MemoryStream(chunck)); public List<float> vertexPositions = new List<float>();
string line; public List<int> meshFaces = new List<int>();
while ((line = reader.ReadLine()) != null) }
public static InputGeomProvider load(byte[] chunck)
{
var context = loadContext(chunck);
return new SimpleInputGeomProvider(context.vertexPositions, context.meshFaces);
}
public static ObjImporterContext loadContext(byte[] chunck)
{
ObjImporterContext context = new ObjImporterContext();
try
{ {
line = line.Trim(); using StreamReader reader = new StreamReader(new MemoryStream(chunck));
readLine(line, context); string line;
while ((line = reader.ReadLine()) != null)
{
line = line.Trim();
readLine(line, context);
}
}
catch (Exception e)
{
throw new Exception(e.Message, e);
}
return context;
}
public static void readLine(string line, ObjImporterContext context)
{
if (line.StartsWith("v"))
{
readVertex(line, context);
}
else if (line.StartsWith("f"))
{
readFace(line, context);
} }
} }
catch (Exception e)
{
throw new Exception(e.Message, e);
}
return context; private static void readVertex(string line, ObjImporterContext context)
}
public static void readLine(string line, ObjImporterContext context)
{
if (line.StartsWith("v"))
{ {
readVertex(line, context); if (line.StartsWith("v "))
}
else if (line.StartsWith("f"))
{
readFace(line, context);
}
}
private static void readVertex(string line, ObjImporterContext context)
{
if (line.StartsWith("v "))
{
float[] vert = readVector3f(line);
foreach (float vp in vert)
{ {
context.vertexPositions.Add(vp); float[] vert = readVector3f(line);
foreach (float vp in vert)
{
context.vertexPositions.Add(vp);
}
} }
} }
}
private static float[] readVector3f(string line) private static float[] readVector3f(string line)
{
string[] v = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (v.Length < 4)
{ {
throw new Exception("Invalid vector, expected 3 coordinates, found " + (v.Length - 1)); string[] v = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
} if (v.Length < 4)
return new float[] { float.Parse(v[1]), float.Parse(v[2]), float.Parse(v[3]) };
}
private static void readFace(string line, ObjImporterContext context)
{
string[] v = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (v.Length < 4)
{
throw new Exception("Invalid number of face vertices: 3 coordinates expected, found " + v.Length);
}
for (int j = 0; j < v.Length - 3; j++)
{
context.meshFaces.Add(readFaceVertex(v[1], context));
for (int i = 0; i < 2; i++)
{ {
context.meshFaces.Add(readFaceVertex(v[2 + j + i], context)); throw new Exception("Invalid vector, expected 3 coordinates, found " + (v.Length - 1));
}
return new float[] { float.Parse(v[1]), float.Parse(v[2]), float.Parse(v[3]) };
}
private static void readFace(string line, ObjImporterContext context)
{
string[] v = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (v.Length < 4)
{
throw new Exception("Invalid number of face vertices: 3 coordinates expected, found " + v.Length);
}
for (int j = 0; j < v.Length - 3; j++)
{
context.meshFaces.Add(readFaceVertex(v[1], context));
for (int i = 0; i < 2; i++)
{
context.meshFaces.Add(readFaceVertex(v[2 + j + i], context));
}
} }
} }
}
private static int readFaceVertex(string face, ObjImporterContext context) private static int readFaceVertex(string face, ObjImporterContext context)
{
string[] v = face.Split("/");
return getIndex(int.Parse(v[0]), context.vertexPositions.Count);
}
private static int getIndex(int posi, int size)
{
if (posi > 0)
{ {
posi--; string[] v = face.Split("/");
} return getIndex(int.Parse(v[0]), context.vertexPositions.Count);
else if (posi < 0)
{
posi = size + posi;
}
else
{
throw new Exception("0 vertex index");
} }
return posi; private static int getIndex(int posi, int size)
{
if (posi > 0)
{
posi--;
}
else if (posi < 0)
{
posi = size + posi;
}
else
{
throw new Exception("0 vertex index");
}
return posi;
}
} }
}
} }

View File

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

View File

@ -19,54 +19,52 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/** Represents a polygon mesh suitable for use in building a navigation mesh. */
public class PolyMesh
{
/** The mesh vertices. [Form: (x, y, z) coordinates * #nverts] */
public int[] verts;
/** Polygon and neighbor data. [Length: #maxpolys * 2 * #nvp] */
public int[] polys;
/** Represents a polygon mesh suitable for use in building a navigation mesh. */ /** The region id assigned to each polygon. [Length: #maxpolys] */
public class PolyMesh public int[] regs;
{
/** The mesh vertices. [Form: (x, y, z) coordinates * #nverts] */
public int[] verts;
/** Polygon and neighbor data. [Length: #maxpolys * 2 * #nvp] */ /** The area id assigned to each polygon. [Length: #maxpolys] */
public int[] polys; public int[] areas;
/** The region id assigned to each polygon. [Length: #maxpolys] */ /** The number of vertices. */
public int[] regs; public int nverts;
/** The area id assigned to each polygon. [Length: #maxpolys] */ /** The number of polygons. */
public int[] areas; public int npolys;
/** The number of vertices. */ /** The maximum number of vertices per polygon. */
public int nverts; public int nvp;
/** The number of polygons. */ /** The number of allocated polygons. */
public int npolys; public int maxpolys;
/** The maximum number of vertices per polygon. */ /** The user defined flags for each polygon. [Length: #maxpolys] */
public int nvp; public int[] flags;
/** The number of allocated polygons. */ /** The minimum bounds in world space. [(x, y, z)] */
public int maxpolys; public readonly float[] bmin = new float[3];
/** The user defined flags for each polygon. [Length: #maxpolys] */ /** The maximum bounds in world space. [(x, y, z)] */
public int[] flags; public readonly float[] bmax = new float[3];
/** The minimum bounds in world space. [(x, y, z)] */ /** The size of each cell. (On the xz-plane.) */
public readonly float[] bmin = new float[3]; public float cs;
/** The maximum bounds in world space. [(x, y, z)] */ /** The height of each cell. (The minimum increment along the y-axis.) */
public readonly float[] bmax = new float[3]; public float ch;
/** The size of each cell. (On the xz-plane.) */ /** The AABB border size used to generate the source data from which the mesh was derived. */
public float cs; public int borderSize;
/** The height of each cell. (The minimum increment along the y-axis.) */ /** The max error of the polygon edges in the mesh. */
public float ch; public float maxEdgeError;
}
/** The AABB border size used to generate the source data from which the mesh was derived. */
public int borderSize;
/** The max error of the polygon edges in the mesh. */
public float maxEdgeError;
}
} }

View File

@ -20,30 +20,28 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
/**
/**
* Contains triangle meshes that represent detailed height data associated with the polygons in its associated polygon * Contains triangle meshes that represent detailed height data associated with the polygons in its associated polygon
* mesh object. * mesh object.
*/ */
public class PolyMeshDetail public class PolyMeshDetail
{ {
/** The sub-mesh data. [Size: 4*#nmeshes] */ /** The sub-mesh data. [Size: 4*#nmeshes] */
public int[] meshes; public int[] meshes;
/** The mesh vertices. [Size: 3*#nverts] */ /** The mesh vertices. [Size: 3*#nverts] */
public float[] verts; public float[] verts;
/** The mesh triangles. [Size: 4*#ntris] */ /** The mesh triangles. [Size: 4*#ntris] */
public int[] tris; public int[] tris;
/** The number of sub-meshes defined by #meshes. */ /** The number of sub-meshes defined by #meshes. */
public int nmeshes; public int nmeshes;
/** The number of vertices in #verts. */ /** The number of vertices in #verts. */
public int nverts; public int nverts;
/** The number of triangles in #tris. */ /** The number of triangles in #tris. */
public int ntris; public int ntris;
} }
} }

View File

@ -22,104 +22,102 @@ using System;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RecastConstants;
public class Recast
using static RecastConstants;
public class Recast
{
void calcBounds(float[] verts, int nv, float[] bmin, float[] bmax)
{ {
for (int i = 0; i < 3; i++) void calcBounds(float[] verts, int nv, float[] bmin, float[] bmax)
{ {
bmin[i] = verts[i]; for (int i = 0; i < 3; i++)
bmax[i] = verts[i]; {
bmin[i] = verts[i];
bmax[i] = verts[i];
}
for (int i = 1; i < nv; ++i)
{
for (int j = 0; j < 3; j++)
{
bmin[j] = Math.Min(bmin[j], verts[i * 3 + j]);
bmax[j] = Math.Max(bmax[j], verts[i * 3 + j]);
}
}
// Calculate bounding box.
} }
for (int i = 1; i < nv; ++i) public static int[] calcGridSize(float[] bmin, float[] bmax, float cs)
{ {
for (int j = 0; j < 3; j++) return new int[] { (int)((bmax[0] - bmin[0]) / cs + 0.5f), (int)((bmax[2] - bmin[2]) / cs + 0.5f) };
}
public static int[] calcTileCount(float[] bmin, float[] bmax, float cs, int tileSizeX, int tileSizeZ)
{
int[] gwd = Recast.calcGridSize(bmin, bmax, cs);
int gw = gwd[0];
int gd = gwd[1];
int tw = (gw + tileSizeX - 1) / tileSizeX;
int td = (gd + tileSizeZ - 1) / tileSizeZ;
return new int[] { tw, td };
}
/// @par
///
/// Modifies the area id of all triangles with a slope below the specified value.
///
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles
public static int[] markWalkableTriangles(Telemetry ctx, float walkableSlopeAngle, float[] verts, int[] tris, int nt,
AreaModification areaMod)
{
int[] areas = new int[nt];
float walkableThr = (float)Math.Cos(walkableSlopeAngle / 180.0f * Math.PI);
float[] norm = new float[3];
for (int i = 0; i < nt; ++i)
{ {
bmin[j] = Math.Min(bmin[j], verts[i * 3 + j]); int tri = i * 3;
bmax[j] = Math.Max(bmax[j], verts[i * 3 + j]); calcTriNormal(verts, tris[tri], tris[tri + 1], tris[tri + 2], norm);
// Check if the face is walkable.
if (norm[1] > walkableThr)
areas[i] = areaMod.apply(areas[i]);
}
return areas;
}
static void calcTriNormal(float[] verts, int v0, int v1, int v2, float[] norm)
{
float[] e0 = new float[3];
float[] e1 = new float[3];
RecastVectors.sub(e0, verts, v1 * 3, v0 * 3);
RecastVectors.sub(e1, verts, v2 * 3, v0 * 3);
RecastVectors.cross(norm, e0, e1);
RecastVectors.normalize(norm);
}
/// @par
///
/// Only sets the area id's for the unwalkable triangles. Does not alter the
/// area id's for walkable triangles.
///
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles
public static void clearUnwalkableTriangles(Telemetry ctx, float walkableSlopeAngle, float[] verts, int nv,
int[] tris, int nt, int[] areas)
{
float walkableThr = (float)Math.Cos(walkableSlopeAngle / 180.0f * Math.PI);
float[] norm = new float[3];
for (int i = 0; i < nt; ++i)
{
int tri = i * 3;
calcTriNormal(verts, tris[tri], tris[tri + 1], tris[tri + 2], norm);
// Check if the face is walkable.
if (norm[1] <= walkableThr)
areas[i] = RC_NULL_AREA;
} }
} }
// Calculate bounding box.
} }
public static int[] calcGridSize(float[] bmin, float[] bmax, float cs)
{
return new int[] { (int)((bmax[0] - bmin[0]) / cs + 0.5f), (int)((bmax[2] - bmin[2]) / cs + 0.5f) };
}
public static int[] calcTileCount(float[] bmin, float[] bmax, float cs, int tileSizeX, int tileSizeZ)
{
int[] gwd = Recast.calcGridSize(bmin, bmax, cs);
int gw = gwd[0];
int gd = gwd[1];
int tw = (gw + tileSizeX - 1) / tileSizeX;
int td = (gd + tileSizeZ - 1) / tileSizeZ;
return new int[] { tw, td };
}
/// @par
///
/// Modifies the area id of all triangles with a slope below the specified value.
///
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles
public static int[] markWalkableTriangles(Telemetry ctx, float walkableSlopeAngle, float[] verts, int[] tris, int nt,
AreaModification areaMod)
{
int[] areas = new int[nt];
float walkableThr = (float)Math.Cos(walkableSlopeAngle / 180.0f * Math.PI);
float[] norm = new float[3];
for (int i = 0; i < nt; ++i)
{
int tri = i * 3;
calcTriNormal(verts, tris[tri], tris[tri + 1], tris[tri + 2], norm);
// Check if the face is walkable.
if (norm[1] > walkableThr)
areas[i] = areaMod.apply(areas[i]);
}
return areas;
}
static void calcTriNormal(float[] verts, int v0, int v1, int v2, float[] norm)
{
float[] e0 = new float[3];
float[] e1 = new float[3];
RecastVectors.sub(e0, verts, v1 * 3, v0 * 3);
RecastVectors.sub(e1, verts, v2 * 3, v0 * 3);
RecastVectors.cross(norm, e0, e1);
RecastVectors.normalize(norm);
}
/// @par
///
/// Only sets the area id's for the unwalkable triangles. Does not alter the
/// area id's for walkable triangles.
///
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcHeightfield, rcClearUnwalkableTriangles, rcRasterizeTriangles
public static void clearUnwalkableTriangles(Telemetry ctx, float walkableSlopeAngle, float[] verts, int nv,
int[] tris, int nt, int[] areas)
{
float walkableThr = (float)Math.Cos(walkableSlopeAngle / 180.0f * Math.PI);
float[] norm = new float[3];
for (int i = 0; i < nt; ++i)
{
int tri = i * 3;
calcTriNormal(verts, tris[tri], tris[tri + 1], tris[tri + 2], norm);
// Check if the face is walkable.
if (norm[1] <= walkableThr)
areas[i] = RC_NULL_AREA;
}
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -27,297 +27,298 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class RecastBuilder
public class RecastBuilder
{
public interface RecastBuilderProgressListener
{ {
void onProgress(int completed, int total); public interface RecastBuilderProgressListener
}
private readonly RecastBuilderProgressListener progressListener;
public RecastBuilder()
{
progressListener = null;
}
public RecastBuilder(RecastBuilderProgressListener progressListener)
{
this.progressListener = progressListener;
}
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);
int tw = twh[0];
int th = twh[1];
List<RecastBuilderResult> results = new List<RecastBuilderResult>();
if (null != taskFactory)
{ {
buildMultiThreadAsync(geom, cfg, bmin, bmax, tw, th, results, taskFactory, default); void onProgress(int completed, int total);
} else {
buildSingleThreadAsync(geom, cfg, bmin, bmax, tw, th, results);
} }
return results; private readonly RecastBuilderProgressListener progressListener;
}
public RecastBuilder()
public Task buildTilesAsync(InputGeomProvider geom, RecastConfig cfg, int threads, List<RecastBuilderResult> results, TaskFactory taskFactory, CancellationToken cancellationToken)
{
float[] bmin = geom.getMeshBoundsMin();
float[] bmax = geom.getMeshBoundsMax();
int[] twh = Recast.calcTileCount(bmin, bmax, cfg.cs, cfg.tileSizeX, cfg.tileSizeZ);
int tw = twh[0];
int th = twh[1];
Task task;
if (1 < threads)
{ {
task = buildMultiThreadAsync(geom, cfg, bmin, bmax, tw, th, results, taskFactory, cancellationToken); progressListener = null;
}
else
{
task = buildSingleThreadAsync(geom, cfg, bmin, bmax, tw, th, results);
} }
return task; public RecastBuilder(RecastBuilderProgressListener progressListener)
}
private Task buildSingleThreadAsync(InputGeomProvider geom, RecastConfig cfg, float[] bmin, float[] bmax,
int tw, int th, List<RecastBuilderResult> results)
{
AtomicInteger counter = new AtomicInteger(0);
for (int y = 0; y < th; ++y)
{ {
for (int x = 0; x < tw; ++x) this.progressListener = progressListener;
}
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);
int tw = twh[0];
int th = twh[1];
List<RecastBuilderResult> results = new List<RecastBuilderResult>();
if (null != taskFactory)
{ {
results.Add(buildTile(geom, cfg, bmin, bmax, x, y, counter, tw * th)); buildMultiThreadAsync(geom, cfg, bmin, bmax, tw, th, results, taskFactory, default);
} }
else
{
buildSingleThreadAsync(geom, cfg, bmin, bmax, tw, th, results);
}
return results;
} }
return Task.CompletedTask;
}
private Task buildMultiThreadAsync(InputGeomProvider geom, RecastConfig cfg, float[] bmin, float[] bmax, public Task buildTilesAsync(InputGeomProvider geom, RecastConfig cfg, int threads, List<RecastBuilderResult> results, TaskFactory taskFactory, CancellationToken cancellationToken)
int tw, int th, List<RecastBuilderResult> results, TaskFactory taskFactory, CancellationToken cancellationToken)
{
AtomicInteger counter = new AtomicInteger(0);
CountdownEvent latch = new CountdownEvent(tw * th);
List<Task> tasks = new List<Task>();
for (int x = 0; x < tw; ++x)
{ {
float[] bmin = geom.getMeshBoundsMin();
float[] bmax = geom.getMeshBoundsMax();
int[] twh = Recast.calcTileCount(bmin, bmax, cfg.cs, cfg.tileSizeX, cfg.tileSizeZ);
int tw = twh[0];
int th = twh[1];
Task task;
if (1 < threads)
{
task = buildMultiThreadAsync(geom, cfg, bmin, bmax, tw, th, results, taskFactory, cancellationToken);
}
else
{
task = buildSingleThreadAsync(geom, cfg, bmin, bmax, tw, th, results);
}
return task;
}
private Task buildSingleThreadAsync(InputGeomProvider geom, RecastConfig cfg, float[] bmin, float[] bmax,
int tw, int th, List<RecastBuilderResult> results)
{
AtomicInteger counter = new AtomicInteger(0);
for (int y = 0; y < th; ++y) for (int y = 0; y < th; ++y)
{ {
int tx = x; for (int x = 0; x < tw; ++x)
int ty = y;
var task = taskFactory.StartNew(() =>
{ {
if (cancellationToken.IsCancellationRequested) results.Add(buildTile(geom, cfg, bmin, bmax, x, y, counter, tw * th));
return; }
try
{
RecastBuilderResult tile = buildTile(geom, cfg, bmin, bmax, tx, ty, counter, tw * th);
lock (results)
{
results.Add(tile);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
latch.Signal();
}, cancellationToken);
tasks.Add(task);
} }
return Task.CompletedTask;
} }
try private Task buildMultiThreadAsync(InputGeomProvider geom, RecastConfig cfg, float[] bmin, float[] bmax,
int tw, int th, List<RecastBuilderResult> results, TaskFactory taskFactory, CancellationToken cancellationToken)
{ {
latch.Wait(); AtomicInteger counter = new AtomicInteger(0);
} CountdownEvent latch = new CountdownEvent(tw * th);
catch (ThreadInterruptedException e) List<Task> tasks = new List<Task>();
{
}
return Task.WhenAll(tasks.ToArray()); for (int x = 0; x < tw; ++x)
}
private RecastBuilderResult buildTile(InputGeomProvider geom, RecastConfig cfg, float[] bmin, float[] bmax, int tx,
int ty, AtomicInteger counter, int total)
{
RecastBuilderResult result = build(geom, new RecastBuilderConfig(cfg, bmin, bmax, tx, ty));
if (progressListener != null)
{
progressListener.onProgress(counter.IncrementAndGet(), total);
}
return result;
}
public RecastBuilderResult build(InputGeomProvider geom, RecastBuilderConfig builderCfg)
{
RecastConfig cfg = builderCfg.cfg;
Telemetry ctx = new Telemetry();
//
// Step 1. Rasterize input polygon soup.
//
Heightfield solid = RecastVoxelization.buildSolidHeightfield(geom, builderCfg, ctx);
return build(builderCfg.tileX, builderCfg.tileZ, geom, cfg, solid, ctx);
}
public RecastBuilderResult build(int tileX, int tileZ, ConvexVolumeProvider geom, RecastConfig cfg, Heightfield solid,
Telemetry ctx)
{
filterHeightfield(solid, cfg, ctx);
CompactHeightfield chf = buildCompactHeightfield(geom, cfg, ctx, solid);
// Partition the heightfield so that we can use simple algorithm later
// to triangulate the walkable areas.
// There are 3 martitioning methods, each with some pros and cons:
// 1) Watershed partitioning
// - the classic Recast partitioning
// - creates the nicest tessellation
// - usually slowest
// - partitions the heightfield into nice regions without holes or
// overlaps
// - the are some corner cases where this method creates produces holes
// and overlaps
// - holes may appear when a small obstacles is close to large open area
// (triangulation can handle this)
// - overlaps may occur if you have narrow spiral corridors (i.e
// stairs), this make triangulation to fail
// * generally the best choice if you precompute the nacmesh, use this
// if you have large open areas
// 2) Monotone partioning
// - fastest
// - partitions the heightfield into regions without holes and overlaps
// (guaranteed)
// - creates long thin polygons, which sometimes causes paths with
// detours
// * use this if you want fast navmesh generation
// 3) Layer partitoining
// - quite fast
// - partitions the heighfield into non-overlapping regions
// - relies on the triangulation code to cope with holes (thus slower
// than monotone partitioning)
// - produces better triangles than monotone partitioning
// - does not have the corner cases of watershed partitioning
// - can be slow and create a bit ugly tessellation (still better than
// monotone)
// if you have large open areas with small obstacles (not a problem if
// you use tiles)
// * good choice to use for tiled navmesh with medium and small sized
// tiles
if (cfg.partitionType == PartitionType.WATERSHED)
{
// Prepare for region partitioning, by calculating distance field
// along the walkable surface.
RecastRegion.buildDistanceField(ctx, chf);
// Partition the walkable surface into simple regions without holes.
RecastRegion.buildRegions(ctx, chf, cfg.minRegionArea, cfg.mergeRegionArea);
}
else if (cfg.partitionType == PartitionType.MONOTONE)
{
// Partition the walkable surface into simple regions without holes.
// Monotone partitioning does not need distancefield.
RecastRegion.buildRegionsMonotone(ctx, chf, cfg.minRegionArea, cfg.mergeRegionArea);
}
else
{
// Partition the walkable surface into simple regions without holes.
RecastRegion.buildLayerRegions(ctx, chf, cfg.minRegionArea);
}
//
// Step 5. Trace and simplify region contours.
//
// Create contours.
ContourSet cset = RecastContour.buildContours(ctx, chf, cfg.maxSimplificationError, cfg.maxEdgeLen,
RecastConstants.RC_CONTOUR_TESS_WALL_EDGES);
//
// Step 6. Build polygons mesh from contours.
//
PolyMesh pmesh = RecastMesh.buildPolyMesh(ctx, cset, cfg.maxVertsPerPoly);
//
// Step 7. Create detail mesh which allows to access approximate height
// on each polygon.
//
PolyMeshDetail dmesh = cfg.buildMeshDetail
? RecastMeshDetail.buildPolyMeshDetail(ctx, pmesh, chf, cfg.detailSampleDist, cfg.detailSampleMaxError)
: null;
return new RecastBuilderResult(tileX, tileZ, solid, chf, cset, pmesh, dmesh, ctx);
}
/*
* Step 2. Filter walkable surfaces.
*/
private void filterHeightfield(Heightfield solid, RecastConfig cfg, Telemetry ctx)
{
// Once all geometry is rasterized, we do initial pass of filtering to
// remove unwanted overhangs caused by the conservative rasterization
// as well as filter spans where the character cannot possibly stand.
if (cfg.filterLowHangingObstacles)
{
RecastFilter.filterLowHangingWalkableObstacles(ctx, cfg.walkableClimb, solid);
}
if (cfg.filterLedgeSpans)
{
RecastFilter.filterLedgeSpans(ctx, cfg.walkableHeight, cfg.walkableClimb, solid);
}
if (cfg.filterWalkableLowHeightSpans)
{
RecastFilter.filterWalkableLowHeightSpans(ctx, cfg.walkableHeight, solid);
}
}
/*
* Step 3. Partition walkable surface to simple regions.
*/
private CompactHeightfield buildCompactHeightfield(ConvexVolumeProvider volumeProvider, RecastConfig cfg, Telemetry ctx,
Heightfield solid)
{
// Compact the heightfield so that it is faster to handle from now on.
// This will result more cache coherent data as well as the neighbours
// between walkable cells will be calculated.
CompactHeightfield chf = RecastCompact.buildCompactHeightfield(ctx, cfg.walkableHeight, cfg.walkableClimb, solid);
// Erode the walkable area by agent radius.
RecastArea.erodeWalkableArea(ctx, cfg.walkableRadius, chf);
// (Optional) Mark areas.
if (volumeProvider != null)
{
foreach (ConvexVolume vol in volumeProvider.convexVolumes())
{ {
RecastArea.markConvexPolyArea(ctx, vol.verts, vol.hmin, vol.hmax, vol.areaMod, chf); for (int y = 0; y < th; ++y)
{
int tx = x;
int ty = y;
var task = taskFactory.StartNew(() =>
{
if (cancellationToken.IsCancellationRequested)
return;
try
{
RecastBuilderResult tile = buildTile(geom, cfg, bmin, bmax, tx, ty, counter, tw * th);
lock (results)
{
results.Add(tile);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
latch.Signal();
}, cancellationToken);
tasks.Add(task);
}
}
try
{
latch.Wait();
}
catch (ThreadInterruptedException e)
{
}
return Task.WhenAll(tasks.ToArray());
}
private RecastBuilderResult buildTile(InputGeomProvider geom, RecastConfig cfg, float[] bmin, float[] bmax, int tx,
int ty, AtomicInteger counter, int total)
{
RecastBuilderResult result = build(geom, new RecastBuilderConfig(cfg, bmin, bmax, tx, ty));
if (progressListener != null)
{
progressListener.onProgress(counter.IncrementAndGet(), total);
}
return result;
}
public RecastBuilderResult build(InputGeomProvider geom, RecastBuilderConfig builderCfg)
{
RecastConfig cfg = builderCfg.cfg;
Telemetry ctx = new Telemetry();
//
// Step 1. Rasterize input polygon soup.
//
Heightfield solid = RecastVoxelization.buildSolidHeightfield(geom, builderCfg, ctx);
return build(builderCfg.tileX, builderCfg.tileZ, geom, cfg, solid, ctx);
}
public RecastBuilderResult build(int tileX, int tileZ, ConvexVolumeProvider geom, RecastConfig cfg, Heightfield solid,
Telemetry ctx)
{
filterHeightfield(solid, cfg, ctx);
CompactHeightfield chf = buildCompactHeightfield(geom, cfg, ctx, solid);
// Partition the heightfield so that we can use simple algorithm later
// to triangulate the walkable areas.
// There are 3 martitioning methods, each with some pros and cons:
// 1) Watershed partitioning
// - the classic Recast partitioning
// - creates the nicest tessellation
// - usually slowest
// - partitions the heightfield into nice regions without holes or
// overlaps
// - the are some corner cases where this method creates produces holes
// and overlaps
// - holes may appear when a small obstacles is close to large open area
// (triangulation can handle this)
// - overlaps may occur if you have narrow spiral corridors (i.e
// stairs), this make triangulation to fail
// * generally the best choice if you precompute the nacmesh, use this
// if you have large open areas
// 2) Monotone partioning
// - fastest
// - partitions the heightfield into regions without holes and overlaps
// (guaranteed)
// - creates long thin polygons, which sometimes causes paths with
// detours
// * use this if you want fast navmesh generation
// 3) Layer partitoining
// - quite fast
// - partitions the heighfield into non-overlapping regions
// - relies on the triangulation code to cope with holes (thus slower
// than monotone partitioning)
// - produces better triangles than monotone partitioning
// - does not have the corner cases of watershed partitioning
// - can be slow and create a bit ugly tessellation (still better than
// monotone)
// if you have large open areas with small obstacles (not a problem if
// you use tiles)
// * good choice to use for tiled navmesh with medium and small sized
// tiles
if (cfg.partitionType == PartitionType.WATERSHED)
{
// Prepare for region partitioning, by calculating distance field
// along the walkable surface.
RecastRegion.buildDistanceField(ctx, chf);
// Partition the walkable surface into simple regions without holes.
RecastRegion.buildRegions(ctx, chf, cfg.minRegionArea, cfg.mergeRegionArea);
}
else if (cfg.partitionType == PartitionType.MONOTONE)
{
// Partition the walkable surface into simple regions without holes.
// Monotone partitioning does not need distancefield.
RecastRegion.buildRegionsMonotone(ctx, chf, cfg.minRegionArea, cfg.mergeRegionArea);
}
else
{
// Partition the walkable surface into simple regions without holes.
RecastRegion.buildLayerRegions(ctx, chf, cfg.minRegionArea);
}
//
// Step 5. Trace and simplify region contours.
//
// Create contours.
ContourSet cset = RecastContour.buildContours(ctx, chf, cfg.maxSimplificationError, cfg.maxEdgeLen,
RecastConstants.RC_CONTOUR_TESS_WALL_EDGES);
//
// Step 6. Build polygons mesh from contours.
//
PolyMesh pmesh = RecastMesh.buildPolyMesh(ctx, cset, cfg.maxVertsPerPoly);
//
// Step 7. Create detail mesh which allows to access approximate height
// on each polygon.
//
PolyMeshDetail dmesh = cfg.buildMeshDetail
? RecastMeshDetail.buildPolyMeshDetail(ctx, pmesh, chf, cfg.detailSampleDist, cfg.detailSampleMaxError)
: null;
return new RecastBuilderResult(tileX, tileZ, solid, chf, cset, pmesh, dmesh, ctx);
}
/*
* Step 2. Filter walkable surfaces.
*/
private void filterHeightfield(Heightfield solid, RecastConfig cfg, Telemetry ctx)
{
// Once all geometry is rasterized, we do initial pass of filtering to
// remove unwanted overhangs caused by the conservative rasterization
// as well as filter spans where the character cannot possibly stand.
if (cfg.filterLowHangingObstacles)
{
RecastFilter.filterLowHangingWalkableObstacles(ctx, cfg.walkableClimb, solid);
}
if (cfg.filterLedgeSpans)
{
RecastFilter.filterLedgeSpans(ctx, cfg.walkableHeight, cfg.walkableClimb, solid);
}
if (cfg.filterWalkableLowHeightSpans)
{
RecastFilter.filterWalkableLowHeightSpans(ctx, cfg.walkableHeight, solid);
} }
} }
return chf; /*
} * Step 3. Partition walkable surface to simple regions.
*/
private CompactHeightfield buildCompactHeightfield(ConvexVolumeProvider volumeProvider, RecastConfig cfg, Telemetry ctx,
Heightfield solid)
{
// Compact the heightfield so that it is faster to handle from now on.
// This will result more cache coherent data as well as the neighbours
// between walkable cells will be calculated.
CompactHeightfield chf = RecastCompact.buildCompactHeightfield(ctx, cfg.walkableHeight, cfg.walkableClimb, solid);
public HeightfieldLayerSet buildLayers(InputGeomProvider geom, RecastBuilderConfig builderCfg) // Erode the walkable area by agent radius.
{ RecastArea.erodeWalkableArea(ctx, cfg.walkableRadius, chf);
Telemetry ctx = new Telemetry(); // (Optional) Mark areas.
Heightfield solid = RecastVoxelization.buildSolidHeightfield(geom, builderCfg, ctx); if (volumeProvider != null)
filterHeightfield(solid, builderCfg.cfg, ctx); {
CompactHeightfield chf = buildCompactHeightfield(geom, builderCfg.cfg, ctx, solid); foreach (ConvexVolume vol in volumeProvider.convexVolumes())
return RecastLayers.buildHeightfieldLayers(ctx, chf, builderCfg.cfg.walkableHeight); {
RecastArea.markConvexPolyArea(ctx, vol.verts, vol.hmin, vol.hmax, vol.areaMod, chf);
}
}
return chf;
}
public HeightfieldLayerSet buildLayers(InputGeomProvider geom, RecastBuilderConfig builderCfg)
{
Telemetry ctx = new Telemetry();
Heightfield solid = RecastVoxelization.buildSolidHeightfield(geom, builderCfg, ctx);
filterHeightfield(solid, builderCfg.cfg, ctx);
CompactHeightfield chf = buildCompactHeightfield(geom, builderCfg.cfg, ctx, solid);
return RecastLayers.buildHeightfieldLayers(ctx, chf, builderCfg.cfg.walkableHeight);
}
} }
}
} }

View File

@ -20,84 +20,82 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RecastVectors;
public class RecastBuilderConfig
using static RecastVectors;
public class RecastBuilderConfig
{
public readonly RecastConfig cfg;
public readonly int tileX;
public readonly int tileZ;
/** The width of the field along the x-axis. [Limit: >= 0] [Units: vx] **/
public readonly int width;
/** The height of the field along the z-axis. [Limit: >= 0] [Units: vx] **/
public readonly int height;
/** The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu] **/
public readonly float[] bmin = new float[3];
/** The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu] **/
public readonly float[] bmax = new float[3];
public RecastBuilderConfig(RecastConfig cfg, float[] bmin, float[] bmax) : this(cfg, bmin, bmax, 0, 0)
{ {
} public readonly RecastConfig cfg;
public RecastBuilderConfig(RecastConfig cfg, float[] bmin, float[] bmax, int tileX, int tileZ) public readonly int tileX;
{ public readonly int tileZ;
this.tileX = tileX;
this.tileZ = tileZ; /** The width of the field along the x-axis. [Limit: >= 0] [Units: vx] **/
this.cfg = cfg; public readonly int width;
copy(this.bmin, bmin);
copy(this.bmax, bmax); /** The height of the field along the z-axis. [Limit: >= 0] [Units: vx] **/
if (cfg.useTiles) public readonly int height;
/** The minimum bounds of the field's AABB. [(x, y, z)] [Units: wu] **/
public readonly float[] bmin = new float[3];
/** The maximum bounds of the field's AABB. [(x, y, z)] [Units: wu] **/
public readonly float[] bmax = new float[3];
public RecastBuilderConfig(RecastConfig cfg, float[] bmin, float[] bmax) : this(cfg, bmin, bmax, 0, 0)
{ {
float tsx = cfg.tileSizeX * cfg.cs;
float tsz = cfg.tileSizeZ * cfg.cs;
this.bmin[0] += tileX * tsx;
this.bmin[2] += tileZ * tsz;
this.bmax[0] = this.bmin[0] + tsx;
this.bmax[2] = this.bmin[2] + tsz;
// Expand the heighfield bounding box by border size to find the extents of geometry we need to build this
// tile.
//
// This is done in order to make sure that the navmesh tiles connect correctly at the borders,
// and the obstacles close to the border work correctly with the dilation process.
// No polygons (or contours) will be created on the border area.
//
// IMPORTANT!
//
// :''''''''':
// : +-----+ :
// : | | :
// : | |<--- tile to build
// : | | :
// : +-----+ :<-- geometry needed
// :.........:
//
// You should use this bounding box to query your input geometry.
//
// For example if you build a navmesh for terrain, and want the navmesh tiles to match the terrain tile size
// you will need to pass in data from neighbour terrain tiles too! In a simple case, just pass in all the 8
// neighbours,
// or use the bounding box below to only pass in a sliver of each of the 8 neighbours.
this.bmin[0] -= cfg.borderSize * cfg.cs;
this.bmin[2] -= cfg.borderSize * cfg.cs;
this.bmax[0] += cfg.borderSize * cfg.cs;
this.bmax[2] += cfg.borderSize * cfg.cs;
width = cfg.tileSizeX + cfg.borderSize * 2;
height = cfg.tileSizeZ + cfg.borderSize * 2;
} }
else
public RecastBuilderConfig(RecastConfig cfg, float[] bmin, float[] bmax, int tileX, int tileZ)
{ {
int[] wh = Recast.calcGridSize(this.bmin, this.bmax, cfg.cs); this.tileX = tileX;
width = wh[0]; this.tileZ = tileZ;
height = wh[1]; this.cfg = cfg;
copy(this.bmin, bmin);
copy(this.bmax, bmax);
if (cfg.useTiles)
{
float tsx = cfg.tileSizeX * cfg.cs;
float tsz = cfg.tileSizeZ * cfg.cs;
this.bmin[0] += tileX * tsx;
this.bmin[2] += tileZ * tsz;
this.bmax[0] = this.bmin[0] + tsx;
this.bmax[2] = this.bmin[2] + tsz;
// Expand the heighfield bounding box by border size to find the extents of geometry we need to build this
// tile.
//
// This is done in order to make sure that the navmesh tiles connect correctly at the borders,
// and the obstacles close to the border work correctly with the dilation process.
// No polygons (or contours) will be created on the border area.
//
// IMPORTANT!
//
// :''''''''':
// : +-----+ :
// : | | :
// : | |<--- tile to build
// : | | :
// : +-----+ :<-- geometry needed
// :.........:
//
// You should use this bounding box to query your input geometry.
//
// For example if you build a navmesh for terrain, and want the navmesh tiles to match the terrain tile size
// you will need to pass in data from neighbour terrain tiles too! In a simple case, just pass in all the 8
// neighbours,
// or use the bounding box below to only pass in a sliver of each of the 8 neighbours.
this.bmin[0] -= cfg.borderSize * cfg.cs;
this.bmin[2] -= cfg.borderSize * cfg.cs;
this.bmax[0] += cfg.borderSize * cfg.cs;
this.bmax[2] += cfg.borderSize * cfg.cs;
width = cfg.tileSizeX + cfg.borderSize * 2;
height = cfg.tileSizeZ + cfg.borderSize * 2;
}
else
{
int[] wh = Recast.calcGridSize(this.bmin, this.bmax, cfg.cs);
width = wh[0];
height = wh[1];
}
} }
} }
}
} }

View File

@ -1,60 +1,57 @@
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class RecastBuilderResult
public class RecastBuilderResult
{
public readonly int tileX;
public readonly int tileZ;
private readonly CompactHeightfield chf;
private readonly ContourSet cs;
private readonly PolyMesh pmesh;
private readonly PolyMeshDetail dmesh;
private readonly Heightfield solid;
private readonly Telemetry telemetry;
public RecastBuilderResult(int tileX, int tileZ, Heightfield solid, CompactHeightfield chf, ContourSet cs, PolyMesh pmesh,
PolyMeshDetail dmesh, Telemetry ctx)
{ {
this.tileX = tileX; public readonly int tileX;
this.tileZ = tileZ; public readonly int tileZ;
this.solid = solid; private readonly CompactHeightfield chf;
this.chf = chf; private readonly ContourSet cs;
this.cs = cs; private readonly PolyMesh pmesh;
this.pmesh = pmesh; private readonly PolyMeshDetail dmesh;
this.dmesh = dmesh; private readonly Heightfield solid;
telemetry = ctx; private readonly Telemetry telemetry;
}
public PolyMesh getMesh() public RecastBuilderResult(int tileX, int tileZ, Heightfield solid, CompactHeightfield chf, ContourSet cs, PolyMesh pmesh,
{ PolyMeshDetail dmesh, Telemetry ctx)
return pmesh; {
} this.tileX = tileX;
this.tileZ = tileZ;
this.solid = solid;
this.chf = chf;
this.cs = cs;
this.pmesh = pmesh;
this.dmesh = dmesh;
telemetry = ctx;
}
public PolyMeshDetail getMeshDetail() public PolyMesh getMesh()
{ {
return dmesh; return pmesh;
} }
public CompactHeightfield getCompactHeightfield() public PolyMeshDetail getMeshDetail()
{ {
return chf; return dmesh;
} }
public ContourSet getContourSet() public CompactHeightfield getCompactHeightfield()
{ {
return cs; return chf;
} }
public Heightfield getSolidHeightfield() public ContourSet getContourSet()
{ {
return solid; return cs;
} }
public Telemetry getTelemetry() public Heightfield getSolidHeightfield()
{ {
return telemetry; return solid;
} }
}
public Telemetry getTelemetry()
{
return telemetry;
}
}
} }

View File

@ -22,67 +22,63 @@ using System;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class RecastCommon
public class RecastCommon
{
/// Gets neighbor connection data for the specified direction.
/// @param[in] s The span to check.
/// @param[in] dir The direction to check. [Limits: 0 <= value < 4]
/// @return The neighbor connection data for the specified direction,
/// or #RC_NOT_CONNECTED if there is no connection.
public static int GetCon(CompactSpan s, int dir)
{ {
int shift = dir * 6; /// Gets neighbor connection data for the specified direction.
return (s.con >> shift) & 0x3f; /// @param[in] s The span to check.
/// @param[in] dir The direction to check. [Limits: 0 <= value < 4]
/// @return The neighbor connection data for the specified direction,
/// or #RC_NOT_CONNECTED if there is no connection.
public static int GetCon(CompactSpan s, int dir)
{
int shift = dir * 6;
return (s.con >> shift) & 0x3f;
}
/// Gets the standard width (x-axis) offset for the specified direction.
/// @param[in] dir The direction. [Limits: 0 <= value < 4]
/// @return The width offset to apply to the current cell position to move
/// in the direction.
public static int GetDirOffsetX(int dir)
{
int[] offset = { -1, 0, 1, 0, };
return offset[dir & 0x03];
}
/// Gets the standard height (z-axis) offset for the specified direction.
/// @param[in] dir The direction. [Limits: 0 <= value < 4]
/// @return The height offset to apply to the current cell position to move
/// in the direction.
public static int GetDirOffsetY(int dir)
{
int[] offset = { 0, 1, 0, -1 };
return offset[dir & 0x03];
}
/// Gets the direction for the specified offset. One of x and y should be 0.
/// @param[in] x The x offset. [Limits: -1 <= value <= 1]
/// @param[in] y The y offset. [Limits: -1 <= value <= 1]
/// @return The direction that represents the offset.
public static int rcGetDirForOffset(int x, int y)
{
int[] dirs = { 3, 0, -1, 2, 1 };
return dirs[((y + 1) << 1) + x];
}
/// Sets the neighbor connection data for the specified direction.
/// @param[in] s The span to update.
/// @param[in] dir The direction to set. [Limits: 0 <= value < 4]
/// @param[in] i The index of the neighbor span.
public static void SetCon(CompactSpan s, int dir, int i)
{
int shift = dir * 6;
int con = s.con;
s.con = (con & ~(0x3f << shift)) | ((i & 0x3f) << shift);
}
public static int clamp(int v, int min, int max)
{
return Math.Max(Math.Min(max, v), min);
}
} }
/// Gets the standard width (x-axis) offset for the specified direction.
/// @param[in] dir The direction. [Limits: 0 <= value < 4]
/// @return The width offset to apply to the current cell position to move
/// in the direction.
public static int GetDirOffsetX(int dir)
{
int[] offset = { -1, 0, 1, 0, };
return offset[dir & 0x03];
}
/// Gets the standard height (z-axis) offset for the specified direction.
/// @param[in] dir The direction. [Limits: 0 <= value < 4]
/// @return The height offset to apply to the current cell position to move
/// in the direction.
public static int GetDirOffsetY(int dir)
{
int[] offset = { 0, 1, 0, -1 };
return offset[dir & 0x03];
}
/// Gets the direction for the specified offset. One of x and y should be 0.
/// @param[in] x The x offset. [Limits: -1 <= value <= 1]
/// @param[in] y The y offset. [Limits: -1 <= value <= 1]
/// @return The direction that represents the offset.
public static int rcGetDirForOffset(int x, int y)
{
int[] dirs = { 3, 0, -1, 2, 1 };
return dirs[((y + 1) << 1) + x];
}
/// Sets the neighbor connection data for the specified direction.
/// @param[in] s The span to update.
/// @param[in] dir The direction to set. [Limits: 0 <= value < 4]
/// @param[in] i The index of the neighbor span.
public static void SetCon(CompactSpan s, int dir, int i)
{
int shift = dir * 6;
int con = s.con;
s.con = (con & ~(0x3f << shift)) | ((i & 0x3f) << shift);
}
public static int clamp(int v, int min, int max)
{
return Math.Max(Math.Min(max, v), min);
}
}
} }

View File

@ -20,170 +20,168 @@ using System;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RecastConstants;
using static RecastVectors;
public class RecastCompact
using static RecastConstants;
using static RecastVectors;
public class RecastCompact
{
private const int MAX_LAYERS = RC_NOT_CONNECTED - 1;
private const int MAX_HEIGHT = RecastConstants.SPAN_MAX_HEIGHT;
/// @par
///
/// This is just the beginning of the process of fully building a compact heightfield.
/// Various filters may be applied, then the distance field and regions built.
/// E.g: #rcBuildDistanceField and #rcBuildRegions
///
/// See the #rcConfig documentation for more information on the configuration parameters.
///
/// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig
public static CompactHeightfield buildCompactHeightfield(Telemetry ctx, int walkableHeight, int walkableClimb,
Heightfield hf)
{ {
ctx.startTimer("BUILD_COMPACTHEIGHTFIELD"); private const int MAX_LAYERS = RC_NOT_CONNECTED - 1;
private const int MAX_HEIGHT = RecastConstants.SPAN_MAX_HEIGHT;
CompactHeightfield chf = new CompactHeightfield(); /// @par
int w = hf.width; ///
int h = hf.height; /// This is just the beginning of the process of fully building a compact heightfield.
int spanCount = getHeightFieldSpanCount(hf); /// Various filters may be applied, then the distance field and regions built.
/// E.g: #rcBuildDistanceField and #rcBuildRegions
// Fill in header. ///
chf.width = w; /// See the #rcConfig documentation for more information on the configuration parameters.
chf.height = h; ///
chf.borderSize = hf.borderSize; /// @see rcAllocCompactHeightfield, rcHeightfield, rcCompactHeightfield, rcConfig
chf.spanCount = spanCount; public static CompactHeightfield buildCompactHeightfield(Telemetry ctx, int walkableHeight, int walkableClimb,
chf.walkableHeight = walkableHeight; Heightfield hf)
chf.walkableClimb = walkableClimb;
chf.maxRegions = 0;
copy(chf.bmin, hf.bmin);
copy(chf.bmax, hf.bmax);
chf.bmax[1] += walkableHeight * hf.ch;
chf.cs = hf.cs;
chf.ch = hf.ch;
chf.cells = new CompactCell[w * h];
chf.spans = new CompactSpan[spanCount];
chf.areas = new int[spanCount];
for (int i = 0; i < chf.cells.Length; i++)
{ {
chf.cells[i] = new CompactCell(); ctx.startTimer("BUILD_COMPACTHEIGHTFIELD");
}
for (int i = 0; i < chf.spans.Length; i++) CompactHeightfield chf = new CompactHeightfield();
{ int w = hf.width;
chf.spans[i] = new CompactSpan(); int h = hf.height;
} int spanCount = getHeightFieldSpanCount(hf);
// Fill in cells and spans. // Fill in header.
int idx = 0; chf.width = w;
for (int y = 0; y < h; ++y) chf.height = h;
{ chf.borderSize = hf.borderSize;
for (int x = 0; x < w; ++x) chf.spanCount = spanCount;
chf.walkableHeight = walkableHeight;
chf.walkableClimb = walkableClimb;
chf.maxRegions = 0;
copy(chf.bmin, hf.bmin);
copy(chf.bmax, hf.bmax);
chf.bmax[1] += walkableHeight * hf.ch;
chf.cs = hf.cs;
chf.ch = hf.ch;
chf.cells = new CompactCell[w * h];
chf.spans = new CompactSpan[spanCount];
chf.areas = new int[spanCount];
for (int i = 0; i < chf.cells.Length; i++)
{ {
Span s = hf.spans[x + y * w]; chf.cells[i] = new CompactCell();
// If there are no spans at this cell, just leave the data to index=0, count=0. }
if (s == null)
continue;
CompactCell c = chf.cells[x + y * w];
c.index = idx;
c.count = 0;
while (s != null)
{
if (s.area != RC_NULL_AREA)
{
int bot = s.smax;
int top = s.next != null ? (int)s.next.smin : MAX_HEIGHT;
chf.spans[idx].y = RecastCommon.clamp(bot, 0, MAX_HEIGHT);
chf.spans[idx].h = RecastCommon.clamp(top - bot, 0, MAX_HEIGHT);
chf.areas[idx] = s.area;
idx++;
c.count++;
}
s = s.next; for (int i = 0; i < chf.spans.Length; i++)
{
chf.spans[i] = new CompactSpan();
}
// Fill in cells and spans.
int idx = 0;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
Span s = hf.spans[x + y * w];
// If there are no spans at this cell, just leave the data to index=0, count=0.
if (s == null)
continue;
CompactCell c = chf.cells[x + y * w];
c.index = idx;
c.count = 0;
while (s != null)
{
if (s.area != RC_NULL_AREA)
{
int bot = s.smax;
int top = s.next != null ? (int)s.next.smin : MAX_HEIGHT;
chf.spans[idx].y = RecastCommon.clamp(bot, 0, MAX_HEIGHT);
chf.spans[idx].h = RecastCommon.clamp(top - bot, 0, MAX_HEIGHT);
chf.areas[idx] = s.area;
idx++;
c.count++;
}
s = s.next;
}
} }
} }
}
// Find neighbour connections. // Find neighbour connections.
int tooHighNeighbour = 0; int tooHighNeighbour = 0;
for (int y = 0; y < h; ++y) for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{ {
CompactCell c = chf.cells[x + y * w]; for (int x = 0; x < w; ++x)
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{ {
CompactSpan s = chf.spans[i]; CompactCell c = chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
for (int dir = 0; dir < 4; ++dir)
{ {
RecastCommon.SetCon(s, dir, RC_NOT_CONNECTED); CompactSpan s = chf.spans[i];
int nx = x + RecastCommon.GetDirOffsetX(dir);
int ny = y + RecastCommon.GetDirOffsetY(dir);
// First check that the neighbour cell is in bounds.
if (nx < 0 || ny < 0 || nx >= w || ny >= h)
continue;
// Iterate over all neighbour spans and check if any of the is for (int dir = 0; dir < 4; ++dir)
// accessible from current cell.
CompactCell nc = chf.cells[nx + ny * w];
for (int k = nc.index, nk = nc.index + nc.count; k < nk; ++k)
{ {
CompactSpan ns = chf.spans[k]; RecastCommon.SetCon(s, dir, RC_NOT_CONNECTED);
int bot = Math.Max(s.y, ns.y); int nx = x + RecastCommon.GetDirOffsetX(dir);
int top = Math.Min(s.y + s.h, ns.y + ns.h); int ny = y + RecastCommon.GetDirOffsetY(dir);
// First check that the neighbour cell is in bounds.
if (nx < 0 || ny < 0 || nx >= w || ny >= h)
continue;
// Check that the gap between the spans is walkable, // Iterate over all neighbour spans and check if any of the is
// and that the climb height between the gaps is not too high. // accessible from current cell.
if ((top - bot) >= walkableHeight && Math.Abs(ns.y - s.y) <= walkableClimb) CompactCell nc = chf.cells[nx + ny * w];
for (int k = nc.index, nk = nc.index + nc.count; k < nk; ++k)
{ {
// Mark direction as walkable. CompactSpan ns = chf.spans[k];
int lidx = k - nc.index; int bot = Math.Max(s.y, ns.y);
if (lidx < 0 || lidx > MAX_LAYERS) int top = Math.Min(s.y + s.h, ns.y + ns.h);
{
tooHighNeighbour = Math.Max(tooHighNeighbour, lidx);
continue;
}
RecastCommon.SetCon(s, dir, lidx); // Check that the gap between the spans is walkable,
break; // and that the climb height between the gaps is not too high.
if ((top - bot) >= walkableHeight && Math.Abs(ns.y - s.y) <= walkableClimb)
{
// Mark direction as walkable.
int lidx = k - nc.index;
if (lidx < 0 || lidx > MAX_LAYERS)
{
tooHighNeighbour = Math.Max(tooHighNeighbour, lidx);
continue;
}
RecastCommon.SetCon(s, dir, lidx);
break;
}
} }
} }
} }
} }
} }
}
if (tooHighNeighbour > MAX_LAYERS) if (tooHighNeighbour > MAX_LAYERS)
{
throw new Exception("rcBuildCompactHeightfield: Heightfield has too many layers " + tooHighNeighbour
+ " (max: " + MAX_LAYERS + ")");
}
ctx.stopTimer("BUILD_COMPACTHEIGHTFIELD");
return chf;
}
private static int getHeightFieldSpanCount(Heightfield hf)
{
int w = hf.width;
int h = hf.height;
int spanCount = 0;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{ {
for (Span s = hf.spans[x + y * w]; s != null; s = s.next) throw new Exception("rcBuildCompactHeightfield: Heightfield has too many layers " + tooHighNeighbour
+ " (max: " + MAX_LAYERS + ")");
}
ctx.stopTimer("BUILD_COMPACTHEIGHTFIELD");
return chf;
}
private static int getHeightFieldSpanCount(Heightfield hf)
{
int w = hf.width;
int h = hf.height;
int spanCount = 0;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{ {
if (s.area != RC_NULL_AREA) for (Span s = hf.spans[x + y * w]; s != null; s = s.next)
spanCount++; {
if (s.area != RC_NULL_AREA)
spanCount++;
}
} }
} }
}
return spanCount; return spanCount;
}
} }
}
} }

View File

@ -22,168 +22,166 @@ using System;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RecastConstants;
public class RecastConfig
{
public readonly PartitionType partitionType;
using static RecastConstants; public readonly bool useTiles;
public class RecastConfig /** The width/depth size of tile's on the xz-plane. [Limit: &gt;= 0] [Units: vx] **/
{ public readonly int tileSizeX;
public readonly PartitionType partitionType;
public readonly bool useTiles; public readonly int tileSizeZ;
/** The width/depth size of tile's on the xz-plane. [Limit: &gt;= 0] [Units: vx] **/ /** The xz-plane cell size to use for fields. [Limit: &gt; 0] [Units: wu] **/
public readonly int tileSizeX; public readonly float cs;
public readonly int tileSizeZ; /** The y-axis cell size to use for fields. [Limit: &gt; 0] [Units: wu] **/
public readonly float ch;
/** The xz-plane cell size to use for fields. [Limit: &gt; 0] [Units: wu] **/ /** The maximum slope that is considered walkable. [Limits: 0 &lt;= value &lt; 90] [Units: Degrees] **/
public readonly float cs; public readonly float walkableSlopeAngle;
/** The y-axis cell size to use for fields. [Limit: &gt; 0] [Units: wu] **/ /**
public readonly float ch;
/** The maximum slope that is considered walkable. [Limits: 0 &lt;= value &lt; 90] [Units: Degrees] **/
public readonly float walkableSlopeAngle;
/**
* Minimum floor to 'ceiling' height that will still allow the floor area to be considered walkable. [Limit: &gt;= 3] * Minimum floor to 'ceiling' height that will still allow the floor area to be considered walkable. [Limit: &gt;= 3]
* [Units: vx] * [Units: vx]
**/ **/
public readonly int walkableHeight; public readonly int walkableHeight;
/** Maximum ledge height that is considered to still be traversable. [Limit: &gt;=0] [Units: vx] **/ /** Maximum ledge height that is considered to still be traversable. [Limit: &gt;=0] [Units: vx] **/
public readonly int walkableClimb; public readonly int walkableClimb;
/** /**
* The distance to erode/shrink the walkable area of the heightfield away from obstructions. [Limit: &gt;=0] [Units: * The distance to erode/shrink the walkable area of the heightfield away from obstructions. [Limit: &gt;=0] [Units:
* vx] * vx]
**/ **/
public readonly int walkableRadius; public readonly int walkableRadius;
/** The maximum allowed length for contour edges along the border of the mesh. [Limit: &gt;=0] [Units: vx] **/ /** The maximum allowed length for contour edges along the border of the mesh. [Limit: &gt;=0] [Units: vx] **/
public readonly int maxEdgeLen; public readonly int maxEdgeLen;
/** /**
* The maximum distance a simplfied contour's border edges should deviate the original raw contour. [Limit: &gt;=0] * The maximum distance a simplfied contour's border edges should deviate the original raw contour. [Limit: &gt;=0]
* [Units: vx] * [Units: vx]
**/ **/
public readonly float maxSimplificationError; public readonly float maxSimplificationError;
/** The minimum number of cells allowed to form isolated island areas. [Limit: &gt;=0] [Units: vx] **/ /** The minimum number of cells allowed to form isolated island areas. [Limit: &gt;=0] [Units: vx] **/
public readonly int minRegionArea; public readonly int minRegionArea;
/** /**
* Any regions with a span count smaller than this value will, if possible, be merged with larger regions. [Limit: * Any regions with a span count smaller than this value will, if possible, be merged with larger regions. [Limit:
* &gt;=0] [Units: vx] * &gt;=0] [Units: vx]
**/ **/
public readonly int mergeRegionArea; public readonly int mergeRegionArea;
/** /**
* The maximum number of vertices allowed for polygons generated during the contour to polygon conversion process. * The maximum number of vertices allowed for polygons generated during the contour to polygon conversion process.
* [Limit: &gt;= 3] * [Limit: &gt;= 3]
**/ **/
public readonly int maxVertsPerPoly; public readonly int maxVertsPerPoly;
/** /**
* Sets the sampling distance to use when generating the detail mesh. (For height detail only.) [Limits: 0 or >= * Sets the sampling distance to use when generating the detail mesh. (For height detail only.) [Limits: 0 or >=
* 0.9] [Units: wu] * 0.9] [Units: wu]
**/ **/
public readonly float detailSampleDist; public readonly float detailSampleDist;
/** /**
* The maximum distance the detail mesh surface should deviate from heightfield data. (For height detail only.) * The maximum distance the detail mesh surface should deviate from heightfield data. (For height detail only.)
* [Limit: &gt;=0] [Units: wu] * [Limit: &gt;=0] [Units: wu]
**/ **/
public readonly float detailSampleMaxError; public readonly float detailSampleMaxError;
public readonly AreaModification walkableAreaMod; public readonly AreaModification walkableAreaMod;
public readonly bool filterLowHangingObstacles; public readonly bool filterLowHangingObstacles;
public readonly bool filterLedgeSpans; public readonly bool filterLedgeSpans;
public readonly bool filterWalkableLowHeightSpans; public readonly bool filterWalkableLowHeightSpans;
/** Set to false to disable building detailed mesh **/ /** Set to false to disable building detailed mesh **/
public readonly bool buildMeshDetail; public readonly bool buildMeshDetail;
/** The size of the non-navigable border around the heightfield. [Limit: &gt;=0] [Units: vx] **/ /** The size of the non-navigable border around the heightfield. [Limit: &gt;=0] [Units: vx] **/
public readonly int borderSize; public readonly int borderSize;
/** Set of original settings passed in world units */ /** Set of original settings passed in world units */
public readonly float minRegionAreaWorld; public readonly float minRegionAreaWorld;
public readonly float mergeRegionAreaWorld; public readonly float mergeRegionAreaWorld;
public readonly float walkableHeightWorld; public readonly float walkableHeightWorld;
public readonly float walkableClimbWorld; public readonly float walkableClimbWorld;
public readonly float walkableRadiusWorld; public readonly float walkableRadiusWorld;
public readonly float maxEdgeLenWorld; public readonly float maxEdgeLenWorld;
/** /**
* Non-tiled build configuration * Non-tiled build configuration
*/ */
public RecastConfig(PartitionType partitionType, float cellSize, float cellHeight, float agentHeight, float agentRadius, public RecastConfig(PartitionType partitionType, float cellSize, float cellHeight, float agentHeight, float agentRadius,
float agentMaxClimb, float agentMaxSlope, int regionMinSize, int regionMergeSize, float edgeMaxLen, float agentMaxClimb, float agentMaxSlope, int regionMinSize, int regionMergeSize, float edgeMaxLen,
float edgeMaxError, int vertsPerPoly, float detailSampleDist, float detailSampleMaxError, float edgeMaxError, int vertsPerPoly, float detailSampleDist, float detailSampleMaxError,
AreaModification walkableAreaMod) : this(partitionType, cellSize, cellHeight, agentMaxSlope, true, true, true, agentHeight, agentRadius, agentMaxClimb, AreaModification walkableAreaMod) : this(partitionType, cellSize, cellHeight, agentMaxSlope, true, true, true, agentHeight, agentRadius, agentMaxClimb,
regionMinSize, regionMergeSize, edgeMaxLen, edgeMaxError, vertsPerPoly, detailSampleDist, detailSampleMaxError, regionMinSize, regionMergeSize, edgeMaxLen, edgeMaxError, vertsPerPoly, detailSampleDist, detailSampleMaxError,
walkableAreaMod, true) walkableAreaMod, true)
{ {
} }
/** /**
* Non-tiled build configuration * Non-tiled build configuration
*/ */
public RecastConfig(PartitionType partitionType, float cellSize, float cellHeight, float agentMaxSlope, public RecastConfig(PartitionType partitionType, float cellSize, float cellHeight, float agentMaxSlope,
bool filterLowHangingObstacles, bool filterLedgeSpans, bool filterWalkableLowHeightSpans, float agentHeight, bool filterLowHangingObstacles, bool filterLedgeSpans, bool filterWalkableLowHeightSpans, float agentHeight,
float agentRadius, float agentMaxClimb, int regionMinSize, int regionMergeSize, float edgeMaxLen, float edgeMaxError, float agentRadius, float agentMaxClimb, int regionMinSize, int regionMergeSize, float edgeMaxLen, float edgeMaxError,
int vertsPerPoly, float detailSampleDist, float detailSampleMaxError, AreaModification walkableAreaMod, int vertsPerPoly, float detailSampleDist, float detailSampleMaxError, AreaModification walkableAreaMod,
bool buildMeshDetail) : this(false, 0, 0, 0, partitionType, cellSize, cellHeight, agentMaxSlope, filterLowHangingObstacles, filterLedgeSpans, bool buildMeshDetail) : this(false, 0, 0, 0, partitionType, cellSize, cellHeight, agentMaxSlope, filterLowHangingObstacles, filterLedgeSpans,
filterWalkableLowHeightSpans, agentHeight, agentRadius, agentMaxClimb, filterWalkableLowHeightSpans, agentHeight, agentRadius, agentMaxClimb,
regionMinSize * regionMinSize * cellSize * cellSize, regionMergeSize * regionMergeSize * cellSize * cellSize, regionMinSize * regionMinSize * cellSize * cellSize, regionMergeSize * regionMergeSize * cellSize * cellSize,
edgeMaxLen, edgeMaxError, vertsPerPoly, buildMeshDetail, detailSampleDist, detailSampleMaxError, walkableAreaMod) edgeMaxLen, edgeMaxError, vertsPerPoly, buildMeshDetail, detailSampleDist, detailSampleMaxError, walkableAreaMod)
{ {
// Note: area = size*size in [Units: wu] // Note: area = size*size in [Units: wu]
} }
public RecastConfig(bool useTiles, int tileSizeX, int tileSizeZ, int borderSize, PartitionType partitionType, public RecastConfig(bool useTiles, int tileSizeX, int tileSizeZ, int borderSize, PartitionType partitionType,
float cellSize, float cellHeight, float agentMaxSlope, bool filterLowHangingObstacles, bool filterLedgeSpans, float cellSize, float cellHeight, float agentMaxSlope, bool filterLowHangingObstacles, bool filterLedgeSpans,
bool filterWalkableLowHeightSpans, float agentHeight, float agentRadius, float agentMaxClimb, float minRegionArea, bool filterWalkableLowHeightSpans, float agentHeight, float agentRadius, float agentMaxClimb, float minRegionArea,
float mergeRegionArea, float edgeMaxLen, float edgeMaxError, int vertsPerPoly, bool buildMeshDetail, float mergeRegionArea, float edgeMaxLen, float edgeMaxError, int vertsPerPoly, bool buildMeshDetail,
float detailSampleDist, float detailSampleMaxError, AreaModification walkableAreaMod) float detailSampleDist, float detailSampleMaxError, AreaModification walkableAreaMod)
{ {
this.useTiles = useTiles; this.useTiles = useTiles;
this.tileSizeX = tileSizeX; this.tileSizeX = tileSizeX;
this.tileSizeZ = tileSizeZ; this.tileSizeZ = tileSizeZ;
this.borderSize = borderSize; this.borderSize = borderSize;
this.partitionType = partitionType; this.partitionType = partitionType;
cs = cellSize; cs = cellSize;
ch = cellHeight; ch = cellHeight;
walkableSlopeAngle = agentMaxSlope; walkableSlopeAngle = agentMaxSlope;
walkableHeight = (int)Math.Ceiling(agentHeight / ch); walkableHeight = (int)Math.Ceiling(agentHeight / ch);
walkableHeightWorld = agentHeight; walkableHeightWorld = agentHeight;
walkableClimb = (int)Math.Floor(agentMaxClimb / ch); walkableClimb = (int)Math.Floor(agentMaxClimb / ch);
walkableClimbWorld = agentMaxClimb; walkableClimbWorld = agentMaxClimb;
walkableRadius = (int)Math.Ceiling(agentRadius / cs); walkableRadius = (int)Math.Ceiling(agentRadius / cs);
walkableRadiusWorld = agentRadius; walkableRadiusWorld = agentRadius;
this.minRegionArea = (int)Math.Round(minRegionArea / (cs * cs)); this.minRegionArea = (int)Math.Round(minRegionArea / (cs * cs));
minRegionAreaWorld = minRegionArea; minRegionAreaWorld = minRegionArea;
this.mergeRegionArea = (int)Math.Round(mergeRegionArea / (cs * cs)); this.mergeRegionArea = (int)Math.Round(mergeRegionArea / (cs * cs));
mergeRegionAreaWorld = mergeRegionArea; mergeRegionAreaWorld = mergeRegionArea;
maxEdgeLen = (int)(edgeMaxLen / cellSize); maxEdgeLen = (int)(edgeMaxLen / cellSize);
maxEdgeLenWorld = edgeMaxLen; maxEdgeLenWorld = edgeMaxLen;
maxSimplificationError = edgeMaxError; maxSimplificationError = edgeMaxError;
maxVertsPerPoly = vertsPerPoly; maxVertsPerPoly = vertsPerPoly;
this.detailSampleDist = detailSampleDist < 0.9f ? 0 : cellSize * detailSampleDist; this.detailSampleDist = detailSampleDist < 0.9f ? 0 : cellSize * detailSampleDist;
this.detailSampleMaxError = cellHeight * detailSampleMaxError; this.detailSampleMaxError = cellHeight * detailSampleMaxError;
this.walkableAreaMod = walkableAreaMod; this.walkableAreaMod = walkableAreaMod;
this.filterLowHangingObstacles = filterLowHangingObstacles; this.filterLowHangingObstacles = filterLowHangingObstacles;
this.filterLedgeSpans = filterLedgeSpans; this.filterLedgeSpans = filterLedgeSpans;
this.filterWalkableLowHeightSpans = filterWalkableLowHeightSpans; this.filterWalkableLowHeightSpans = filterWalkableLowHeightSpans;
this.buildMeshDetail = buildMeshDetail; this.buildMeshDetail = buildMeshDetail;
} }
public static int calcBorder(float agentRadius, float cs) public static int calcBorder(float agentRadius, float cs)
{ {
return 3 + (int)Math.Ceiling(agentRadius / cs); return 3 + (int)Math.Ceiling(agentRadius / cs);
}
} }
}
} }

View File

@ -20,69 +20,66 @@ freely, subject to the following restrictions:
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public static class RecastConstants
{
public const int RC_NULL_AREA = 0;
public const int RC_NOT_CONNECTED = 0x3f;
/// Defines the number of bits allocated to rcSpan::smin and rcSpan::smax.
public const int SPAN_HEIGHT_BITS = 20;
/// Defines the maximum value for rcSpan::smin and rcSpan::smax.
public const int SPAN_MAX_HEIGHT = (1 << SPAN_HEIGHT_BITS) - 1;
/// Heighfield border flag.
/// If a heightfield region ID has this bit set, then the region is a border
/// region and its spans are considered unwalkable.
/// (Used during the region and contour build process.)
/// @see rcCompactSpan::reg
public const int RC_BORDER_REG = 0x8000;
/// Polygon touches multiple regions.
/// If a polygon has this region ID it was merged with or created
/// from polygons of different regions during the polymesh
/// build step that removes redundant border vertices.
/// (Used during the polymesh and detail polymesh build processes)
/// @see rcPolyMesh::regs
public const int RC_MULTIPLE_REGS = 0;
// Border vertex flag.
/// If a region ID has this bit set, then the associated element lies on
/// a tile border. If a contour vertex's region ID has this bit set, the
/// vertex will later be removed in order to match the segments and vertices
/// at tile boundaries.
/// (Used during the build process.)
/// @see rcCompactSpan::reg, #rcContour::verts, #rcContour::rverts
public const int RC_BORDER_VERTEX = 0x10000;
/// Area border flag.
/// If a region ID has this bit set, then the associated element lies on
/// the border of an area.
/// (Used during the region and contour build process.)
/// @see rcCompactSpan::reg, #rcContour::verts, #rcContour::rverts
public const int RC_AREA_BORDER = 0x20000;
/// Applied to the region id field of contour vertices in order to extract the region id.
/// The region id field of a vertex may have several flags applied to it. So the
/// fields value can't be used directly.
/// @see rcContour::verts, rcContour::rverts
public const int RC_CONTOUR_REG_MASK = 0xffff;
/// A value which indicates an invalid index within a mesh.
/// @note This does not necessarily indicate an error.
/// @see rcPolyMesh::polys
public const int RC_MESH_NULL_IDX = 0xffff;
public const int RC_CONTOUR_TESS_WALL_EDGES = 0x01;
/// < Tessellate solid (impassable) edges during contour
/// simplification.
public const int RC_CONTOUR_TESS_AREA_EDGES = 0x02;
public static class RecastConstants public const int RC_LOG_WARNING = 1;
{ }
public const int RC_NULL_AREA = 0;
public const int RC_NOT_CONNECTED = 0x3f;
/// Defines the number of bits allocated to rcSpan::smin and rcSpan::smax.
public const int SPAN_HEIGHT_BITS = 20;
/// Defines the maximum value for rcSpan::smin and rcSpan::smax.
public const int SPAN_MAX_HEIGHT = (1 << SPAN_HEIGHT_BITS) - 1;
/// Heighfield border flag.
/// If a heightfield region ID has this bit set, then the region is a border
/// region and its spans are considered unwalkable.
/// (Used during the region and contour build process.)
/// @see rcCompactSpan::reg
public const int RC_BORDER_REG = 0x8000;
/// Polygon touches multiple regions.
/// If a polygon has this region ID it was merged with or created
/// from polygons of different regions during the polymesh
/// build step that removes redundant border vertices.
/// (Used during the polymesh and detail polymesh build processes)
/// @see rcPolyMesh::regs
public const int RC_MULTIPLE_REGS = 0;
// Border vertex flag.
/// If a region ID has this bit set, then the associated element lies on
/// a tile border. If a contour vertex's region ID has this bit set, the
/// vertex will later be removed in order to match the segments and vertices
/// at tile boundaries.
/// (Used during the build process.)
/// @see rcCompactSpan::reg, #rcContour::verts, #rcContour::rverts
public const int RC_BORDER_VERTEX = 0x10000;
/// Area border flag.
/// If a region ID has this bit set, then the associated element lies on
/// the border of an area.
/// (Used during the region and contour build process.)
/// @see rcCompactSpan::reg, #rcContour::verts, #rcContour::rverts
public const int RC_AREA_BORDER = 0x20000;
/// Applied to the region id field of contour vertices in order to extract the region id.
/// The region id field of a vertex may have several flags applied to it. So the
/// fields value can't be used directly.
/// @see rcContour::verts, rcContour::rverts
public const int RC_CONTOUR_REG_MASK = 0xffff;
/// A value which indicates an invalid index within a mesh.
/// @note This does not necessarily indicate an error.
/// @see rcPolyMesh::polys
public const int RC_MESH_NULL_IDX = 0xffff;
public const int RC_CONTOUR_TESS_WALL_EDGES = 0x01;
/// < Tessellate solid (impassable) edges during contour
/// simplification.
public const int RC_CONTOUR_TESS_AREA_EDGES = 0x02;
public const int RC_LOG_WARNING = 1;
}
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -22,187 +22,185 @@ using System;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RecastConstants;
public class RecastFilter
using static RecastConstants;
public class RecastFilter
{
/// @par
///
/// Allows the formation of walkable regions that will flow over low lying
/// objects such as curbs, and up structures such as stairways.
///
/// Two neighboring spans are walkable if: <tt>rcAbs(currentSpan.smax - neighborSpan.smax) < waklableClimb</tt>
///
/// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call
/// #rcFilterLedgeSpans after calling this filter.
///
/// @see rcHeightfield, rcConfig
public static void filterLowHangingWalkableObstacles(Telemetry ctx, int walkableClimb, Heightfield solid)
{ {
ctx.startTimer("FILTER_LOW_OBSTACLES"); /// @par
///
int w = solid.width; /// Allows the formation of walkable regions that will flow over low lying
int h = solid.height; /// objects such as curbs, and up structures such as stairways.
///
for (int y = 0; y < h; ++y) /// Two neighboring spans are walkable if: <tt>rcAbs(currentSpan.smax - neighborSpan.smax) < waklableClimb</tt>
///
/// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call
/// #rcFilterLedgeSpans after calling this filter.
///
/// @see rcHeightfield, rcConfig
public static void filterLowHangingWalkableObstacles(Telemetry ctx, int walkableClimb, Heightfield solid)
{ {
for (int x = 0; x < w; ++x) ctx.startTimer("FILTER_LOW_OBSTACLES");
int w = solid.width;
int h = solid.height;
for (int y = 0; y < h; ++y)
{ {
Span ps = null; for (int x = 0; x < w; ++x)
bool previousWalkable = false;
int previousArea = RC_NULL_AREA;
for (Span s = solid.spans[x + y * w]; s != null; ps = s, s = s.next)
{ {
bool walkable = s.area != RC_NULL_AREA; Span ps = null;
// If current span is not walkable, but there is walkable bool previousWalkable = false;
// span just below it, mark the span above it walkable too. int previousArea = RC_NULL_AREA;
if (!walkable && previousWalkable)
for (Span s = solid.spans[x + y * w]; s != null; ps = s, s = s.next)
{ {
if (Math.Abs(s.smax - ps.smax) <= walkableClimb) bool walkable = s.area != RC_NULL_AREA;
s.area = previousArea; // If current span is not walkable, but there is walkable
} // span just below it, mark the span above it walkable too.
if (!walkable && previousWalkable)
// Copy walkable flag so that it cannot propagate
// past multiple non-walkable objects.
previousWalkable = walkable;
previousArea = s.area;
}
}
}
ctx.stopTimer("FILTER_LOW_OBSTACLES");
}
/// @par
///
/// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb
/// from the current span's maximum.
/// This method removes the impact of the overestimation of conservative voxelization
/// so the resulting mesh will not have regions hanging in the air over ledges.
///
/// A span is a ledge if: <tt>rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb</tt>
///
/// @see rcHeightfield, rcConfig
public static void filterLedgeSpans(Telemetry ctx, int walkableHeight, int walkableClimb, Heightfield solid)
{
ctx.startTimer("FILTER_LEDGE");
int w = solid.width;
int h = solid.height;
// Mark border spans.
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
for (Span s = solid.spans[x + y * w]; s != null; s = s.next)
{
// Skip non walkable spans.
if (s.area == RC_NULL_AREA)
continue;
int bot = (s.smax);
int top = s.next != null ? s.next.smin : SPAN_MAX_HEIGHT;
// Find neighbours minimum height.
int minh = SPAN_MAX_HEIGHT;
// Min and max height of accessible neighbours.
int asmin = s.smax;
int asmax = s.smax;
for (int dir = 0; dir < 4; ++dir)
{
int dx = x + RecastCommon.GetDirOffsetX(dir);
int dy = y + RecastCommon.GetDirOffsetY(dir);
// Skip neighbours which are out of bounds.
if (dx < 0 || dy < 0 || dx >= w || dy >= h)
{ {
minh = Math.Min(minh, -walkableClimb - bot); if (Math.Abs(s.smax - ps.smax) <= walkableClimb)
continue; s.area = previousArea;
} }
// From minus infinity to the first span. // Copy walkable flag so that it cannot propagate
Span ns = solid.spans[dx + dy * w]; // past multiple non-walkable objects.
int nbot = -walkableClimb; previousWalkable = walkable;
int ntop = ns != null ? ns.smin : SPAN_MAX_HEIGHT; previousArea = s.area;
// Skip neightbour if the gap between the spans is too small. }
if (Math.Min(top, ntop) - Math.Max(bot, nbot) > walkableHeight) }
minh = Math.Min(minh, nbot - bot); }
// Rest of the spans. ctx.stopTimer("FILTER_LOW_OBSTACLES");
for (ns = solid.spans[dx + dy * w]; ns != null; ns = ns.next) }
/// @par
///
/// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb
/// from the current span's maximum.
/// This method removes the impact of the overestimation of conservative voxelization
/// so the resulting mesh will not have regions hanging in the air over ledges.
///
/// A span is a ledge if: <tt>rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb</tt>
///
/// @see rcHeightfield, rcConfig
public static void filterLedgeSpans(Telemetry ctx, int walkableHeight, int walkableClimb, Heightfield solid)
{
ctx.startTimer("FILTER_LEDGE");
int w = solid.width;
int h = solid.height;
// Mark border spans.
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
{
for (Span s = solid.spans[x + y * w]; s != null; s = s.next)
{
// Skip non walkable spans.
if (s.area == RC_NULL_AREA)
continue;
int bot = (s.smax);
int top = s.next != null ? s.next.smin : SPAN_MAX_HEIGHT;
// Find neighbours minimum height.
int minh = SPAN_MAX_HEIGHT;
// Min and max height of accessible neighbours.
int asmin = s.smax;
int asmax = s.smax;
for (int dir = 0; dir < 4; ++dir)
{ {
nbot = ns.smax; int dx = x + RecastCommon.GetDirOffsetX(dir);
ntop = ns.next != null ? ns.next.smin : SPAN_MAX_HEIGHT; int dy = y + RecastCommon.GetDirOffsetY(dir);
// Skip neighbours which are out of bounds.
if (dx < 0 || dy < 0 || dx >= w || dy >= h)
{
minh = Math.Min(minh, -walkableClimb - bot);
continue;
}
// From minus infinity to the first span.
Span ns = solid.spans[dx + dy * w];
int nbot = -walkableClimb;
int ntop = ns != null ? ns.smin : SPAN_MAX_HEIGHT;
// Skip neightbour if the gap between the spans is too small. // Skip neightbour if the gap between the spans is too small.
if (Math.Min(top, ntop) - Math.Max(bot, nbot) > walkableHeight) if (Math.Min(top, ntop) - Math.Max(bot, nbot) > walkableHeight)
{
minh = Math.Min(minh, nbot - bot); minh = Math.Min(minh, nbot - bot);
// Find min/max accessible neighbour height. // Rest of the spans.
if (Math.Abs(nbot - bot) <= walkableClimb) for (ns = solid.spans[dx + dy * w]; ns != null; ns = ns.next)
{
nbot = ns.smax;
ntop = ns.next != null ? ns.next.smin : SPAN_MAX_HEIGHT;
// Skip neightbour if the gap between the spans is too small.
if (Math.Min(top, ntop) - Math.Max(bot, nbot) > walkableHeight)
{ {
if (nbot < asmin) minh = Math.Min(minh, nbot - bot);
asmin = nbot;
if (nbot > asmax) // Find min/max accessible neighbour height.
asmax = nbot; if (Math.Abs(nbot - bot) <= walkableClimb)
{
if (nbot < asmin)
asmin = nbot;
if (nbot > asmax)
asmax = nbot;
}
} }
} }
} }
}
// The current span is close to a ledge if the drop to any // The current span is close to a ledge if the drop to any
// neighbour span is less than the walkableClimb. // neighbour span is less than the walkableClimb.
if (minh < -walkableClimb) if (minh < -walkableClimb)
s.area = RC_NULL_AREA; s.area = RC_NULL_AREA;
// If the difference between all neighbours is too large, // If the difference between all neighbours is too large,
// we are at steep slope, mark the span as ledge. // we are at steep slope, mark the span as ledge.
if ((asmax - asmin) > walkableClimb) if ((asmax - asmin) > walkableClimb)
{ {
s.area = RC_NULL_AREA; s.area = RC_NULL_AREA;
}
} }
} }
} }
ctx.stopTimer("FILTER_LEDGE");
} }
ctx.stopTimer("FILTER_LEDGE"); /// @par
} ///
/// For this filter, the clearance above the span is the distance from the span's
/// @par /// maximum to the next higher span's minimum. (Same grid column.)
/// ///
/// For this filter, the clearance above the span is the distance from the span's /// @see rcHeightfield, rcConfig
/// maximum to the next higher span's minimum. (Same grid column.) public static void filterWalkableLowHeightSpans(Telemetry ctx, int walkableHeight, Heightfield solid)
///
/// @see rcHeightfield, rcConfig
public static void filterWalkableLowHeightSpans(Telemetry ctx, int walkableHeight, Heightfield solid)
{
ctx.startTimer("FILTER_WALKABLE");
int w = solid.width;
int h = solid.height;
// Remove walkable flag from spans which do not have enough
// space above them for the agent to stand there.
for (int y = 0; y < h; ++y)
{ {
for (int x = 0; x < w; ++x) ctx.startTimer("FILTER_WALKABLE");
int w = solid.width;
int h = solid.height;
// Remove walkable flag from spans which do not have enough
// space above them for the agent to stand there.
for (int y = 0; y < h; ++y)
{ {
for (Span s = solid.spans[x + y * w]; s != null; s = s.next) for (int x = 0; x < w; ++x)
{ {
int bot = (s.smax); for (Span s = solid.spans[x + y * w]; s != null; s = s.next)
int top = s.next != null ? s.next.smin : SPAN_MAX_HEIGHT; {
if ((top - bot) <= walkableHeight) int bot = (s.smax);
s.area = RC_NULL_AREA; int top = s.next != null ? s.next.smin : SPAN_MAX_HEIGHT;
if ((top - bot) <= walkableHeight)
s.area = RC_NULL_AREA;
}
} }
} }
}
ctx.stopTimer("FILTER_WALKABLE"); ctx.stopTimer("FILTER_WALKABLE");
}
} }
}
} }

View File

@ -23,489 +23,546 @@ using System.Collections.Generic;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RecastCommon;
using static RecastConstants;
using static RecastVectors;
using static RecastRegion;
public class RecastLayers
{
const int RC_MAX_LAYERS = RecastConstants.RC_NOT_CONNECTED;
const int RC_MAX_NEIS = 16;
using static RecastCommon; private class LayerRegion
using static RecastConstants; {
using static RecastVectors; public int id;
using static RecastRegion; public int layerId;
public bool @base;
public int ymin, ymax;
public List<int> layers;
public List<int> neis;
public class RecastLayers { public LayerRegion(int i)
{
id = i;
ymin = 0xFFFF;
layerId = 0xff;
layers = new List<int>();
neis = new List<int>();
}
};
const int RC_MAX_LAYERS = RecastConstants.RC_NOT_CONNECTED; private static void addUnique(List<int> a, int v)
const int RC_MAX_NEIS = 16; {
if (!a.Contains(v))
private class LayerRegion { {
public int id; a.Add(v);
public int layerId; }
public bool @base;
public int ymin, ymax;
public List<int> layers;
public List<int> neis;
public LayerRegion(int i) {
id = i;
ymin = 0xFFFF;
layerId = 0xff;
layers = new List<int>();
neis = new List<int>();
} }
}; private static bool contains(List<int> a, int v)
{
private static void addUnique(List<int> a, int v) { return a.Contains(v);
if (!a.Contains(v)) {
a.Add(v);
} }
}
private static bool contains(List<int> a, int v) { private static bool overlapRange(int amin, int amax, int bmin, int bmax)
return a.Contains(v); {
} return (amin > bmax || amax < bmin) ? false : true;
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) {
ctx.startTimer("RC_TIMER_BUILD_LAYERS");
int w = chf.width;
int h = chf.height;
int borderSize = chf.borderSize;
int[] srcReg = new int[chf.spanCount];
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++) {
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) {
// Collect spans from this row.
Array.Fill(prevCount, 0, 0, (regId) - (0));
int sweepId = 0;
for (int x = borderSize; x < w - borderSize; ++x) { public static HeightfieldLayerSet buildHeightfieldLayers(Telemetry ctx, CompactHeightfield chf, int walkableHeight)
CompactCell c = chf.cells[x + y * w]; {
ctx.startTimer("RC_TIMER_BUILD_LAYERS");
int w = chf.width;
int h = chf.height;
int borderSize = chf.borderSize;
int[] srcReg = new int[chf.spanCount];
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++)
{
sweeps[i] = new SweepSpan();
}
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) { // Partition walkable area into monotone regions.
CompactSpan s = chf.spans[i]; int[] prevCount = new int[256];
if (chf.areas[i] == RC_NULL_AREA) int regId = 0;
continue; // Sweep one line at a time.
int sid = 0xFF; for (int y = borderSize; y < h - borderSize; ++y)
// -x {
// Collect spans from this row.
Array.Fill(prevCount, 0, 0, (regId) - (0));
int sweepId = 0;
if (GetCon(s, 0) != RC_NOT_CONNECTED) { for (int x = borderSize; x < w - borderSize; ++x)
int ax = x + GetDirOffsetX(0); {
int ay = y + GetDirOffsetY(0); CompactCell c = chf.cells[x + y * w];
int ai = chf.cells[ax + ay * w].index + GetCon(s, 0);
if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xff)
sid = srcReg[ai];
}
if (sid == 0xff) { for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
sid = sweepId++; {
sweeps[sid].nei = 0xff; CompactSpan s = chf.spans[i];
sweeps[sid].ns = 0; if (chf.areas[i] == RC_NULL_AREA)
} continue;
int sid = 0xFF;
// -x
// -y if (GetCon(s, 0) != RC_NOT_CONNECTED)
if (GetCon(s, 3) != RC_NOT_CONNECTED) { {
int ax = x + GetDirOffsetX(3); int ax = x + GetDirOffsetX(0);
int ay = y + GetDirOffsetY(3); int ay = y + GetDirOffsetY(0);
int ai = chf.cells[ax + ay * w].index + GetCon(s, 3); int ai = chf.cells[ax + ay * w].index + GetCon(s, 0);
int nr = srcReg[ai]; if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xff)
if (nr != 0xff) { sid = srcReg[ai];
// Set neighbour when first valid neighbour is }
// encoutered.
if (sweeps[sid].ns == 0)
sweeps[sid].nei = nr;
if (sweeps[sid].nei == nr) { if (sid == 0xff)
// Update existing neighbour {
sweeps[sid].ns++; sid = sweepId++;
prevCount[nr]++; sweeps[sid].nei = 0xff;
} else { sweeps[sid].ns = 0;
// This is hit if there is nore than one }
// neighbour.
// Invalidate the neighbour. // -y
sweeps[sid].nei = 0xff; 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)
{
// Set neighbour when first valid neighbour is
// encoutered.
if (sweeps[sid].ns == 0)
sweeps[sid].nei = nr;
if (sweeps[sid].nei == nr)
{
// Update existing neighbour
sweeps[sid].ns++;
prevCount[nr]++;
}
else
{
// This is hit if there is nore than one
// neighbour.
// Invalidate the neighbour.
sweeps[sid].nei = 0xff;
}
} }
} }
srcReg[i] = sid;
} }
srcReg[i] = sid;
} }
}
// Create unique ID. // Create unique ID.
for (int i = 0; i < sweepId; ++i) { for (int i = 0; i < sweepId; ++i)
// If the neighbour is set and there is only one continuous {
// connection to it, // If the neighbour is set and there is only one continuous
// the sweep will be merged with the previous one, else new // connection to it,
// region is created. // the sweep will be merged with the previous one, else new
if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == sweeps[i].ns) { // region is created.
sweeps[i].id = sweeps[i].nei; if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == sweeps[i].ns)
} else { {
if (regId == 255) { sweeps[i].id = sweeps[i].nei;
throw new Exception("rcBuildHeightfieldLayers: Region ID overflow.");
} }
sweeps[i].id = regId++; else
} {
} if (regId == 255)
{
// Remap local sweep ids to region ids. throw new Exception("rcBuildHeightfieldLayers: Region ID overflow.");
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) {
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) {
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) {
CompactCell c = chf.cells[x + y * w];
lregs.Clear();
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)
continue;
regs[ri].ymin = Math.Min(regs[ri].ymin, s.y);
regs[ri].ymax = Math.Max(regs[ri].ymax, s.y);
// Collect all region layers.
lregs.Add(ri);
// Update neighbours
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);
int rai = srcReg[ai];
if (rai != 0xff && rai != ri)
addUnique(regs[ri].neis, rai);
} }
}
} sweeps[i].id = regId++;
// 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]) {
LayerRegion ri = regs[lregs[i]];
LayerRegion rj = regs[lregs[j]];
addUnique(ri.layers, lregs[j]);
addUnique(rj.layers, lregs[i]);
}
} }
} }
} // Remap local sweep ids to region ids.
} for (int x = borderSize; x < w - borderSize; ++x)
{
// Create 2D layers from regions. CompactCell c = chf.cells[x + y * w];
int layerId = 0; for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
List<int> stack = new List<int>(); if (srcReg[i] != 0xff)
srcReg[i] = sweeps[srcReg[i]].id;
for (int i = 0; i < nregs; ++i) {
LayerRegion root = regs[i];
// Skip already visited.
if (root.layerId != 0xff)
continue;
// Start search.
root.layerId = layerId;
root.@base = true;
stack.Add(i);
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) {
LayerRegion regn = regs[nei];
// Skip already visited.
if (regn.layerId != 0xff)
continue;
// Skip if the neighbour is overlapping root region.
if (contains(root.layers, nei))
continue;
// Skip if the height range would become too large.
int ymin = Math.Min(root.ymin, regn.ymin);
int ymax = Math.Max(root.ymax, regn.ymax);
if ((ymax - ymin) >= 255)
continue;
// Deepen
stack.Add(nei);
// Mark layer id
regn.layerId = layerId;
// Merge current layers to root.
foreach (int layer in regn.layers)
addUnique(root.layers, layer);
root.ymin = Math.Min(root.ymin, regn.ymin);
root.ymax = Math.Max(root.ymax, regn.ymax);
}
}
layerId++;
}
// Merge non-overlapping regions that are close in height.
int mergeHeight = walkableHeight * 4;
for (int i = 0; i < nregs; ++i) {
LayerRegion ri = regs[i];
if (!ri.@base)
continue;
int newId = ri.layerId;
for (;;) {
int oldId = 0xff;
for (int j = 0; j < nregs; ++j) {
if (i == j)
continue;
LayerRegion rj = regs[j];
if (!rj.@base)
continue;
// Skip if the regions are not close to each other.
if (!overlapRange(ri.ymin, ri.ymax + mergeHeight, rj.ymin, rj.ymax + mergeHeight))
continue;
// Skip if the height range would become too large.
int ymin = Math.Min(ri.ymin, rj.ymin);
int ymax = Math.Max(ri.ymax, rj.ymax);
if ((ymax - ymin) >= 255)
continue;
// Make sure that there is no overlap when merging 'ri' and
// 'rj'.
bool overlap = false;
// Iterate over all regions which have the same layerId as
// 'rj'
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)) {
overlap = true;
break;
}
}
// Cannot merge of regions overlap.
if (overlap)
continue;
// Can merge i and j.
oldId = rj.layerId;
break;
}
// Could not find anything to merge with, stop.
if (oldId == 0xff)
break;
// Merge
for (int j = 0; j < nregs; ++j) {
LayerRegion rj = regs[j];
if (rj.layerId == oldId) {
rj.@base = false;
// Remap layerIds.
rj.layerId = newId;
// Add overlaid layers from 'rj' to 'ri'.
foreach (int layer in rj.layers)
addUnique(ri.layers, layer);
// Update height bounds.
ri.ymin = Math.Min(ri.ymin, rj.ymin);
ri.ymax = Math.Max(ri.ymax, rj.ymax);
} }
} }
} }
}
// Compact layerIds int nregs = regId;
int[] remap = new int[256]; LayerRegion[] regs = new LayerRegion[nregs];
// Find number of unique layers. // Construct regions
layerId = 0; for (int i = 0; i < nregs; ++i)
for (int i = 0; i < nregs; ++i) {
remap[regs[i].layerId] = 1; regs[i] = new LayerRegion(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) {
// ctx.stopTimer(RC_TIMER_BUILD_LAYERS);
return null;
}
// Create layers.
// rcAssert(lset.layers == 0);
int lw = w - borderSize * 2;
int lh = h - borderSize * 2;
// Build contracted bbox for layers.
float[] bmin = new float[3];
float[] bmax = new float[3];
copy(bmin, chf.bmin);
copy(bmax, chf.bmax);
bmin[0] += borderSize * chf.cs;
bmin[2] += borderSize * chf.cs;
bmax[0] -= borderSize * chf.cs;
bmax[2] -= borderSize * chf.cs;
HeightfieldLayerSet lset = new HeightfieldLayerSet();
lset.layers = new HeightfieldLayerSet.HeightfieldLayer[layerId];
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) {
int curId = i;
HeightfieldLayerSet.HeightfieldLayer layer = lset.layers[i];
int gridSize = lw * lh;
layer.heights = new int[gridSize];
Array.Fill(layer.heights, 0xFF);
layer.areas = new int[gridSize];
layer.cons = new int[gridSize];
// Find layer height bounds.
int hmin = 0, hmax = 0;
for (int j = 0; j < nregs; ++j) {
if (regs[j].@base && regs[j].layerId == curId) {
hmin = regs[j].ymin;
hmax = regs[j].ymax;
}
} }
layer.width = lw; // Find region neighbours and overlapping regions.
layer.height = lh; List<int> lregs = new List<int>();
layer.cs = chf.cs; for (int y = 0; y < h; ++y)
layer.ch = chf.ch; {
for (int x = 0; x < w; ++x)
{
CompactCell c = chf.cells[x + y * w];
// Adjust the bbox to fit the heightfield. lregs.Clear();
copy(layer.bmin, bmin);
copy(layer.bmax, bmax);
layer.bmin[1] = bmin[1] + hmin * chf.ch;
layer.bmax[1] = bmin[1] + hmax * chf.ch;
layer.hmin = hmin;
layer.hmax = hmax;
// Update usable data region. for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
layer.minx = layer.width; {
layer.maxx = 0; CompactSpan s = chf.spans[i];
layer.miny = layer.height; int ri = srcReg[i];
layer.maxy = 0; if (ri == 0xff)
// Copy height and area from compact heightfield.
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) {
CompactSpan s = chf.spans[j];
// Skip unassigned regions.
if (srcReg[j] == 0xff)
continue;
// Skip of does nto belong to current layer.
int lid = regs[srcReg[j]].layerId;
if (lid != curId)
continue; continue;
// Update data bounds. regs[ri].ymin = Math.Min(regs[ri].ymin, s.y);
layer.minx = Math.Min(layer.minx, x); regs[ri].ymax = Math.Max(regs[ri].ymax, s.y);
layer.maxx = Math.Max(layer.maxx, x);
layer.miny = Math.Min(layer.miny, y);
layer.maxy = Math.Max(layer.maxy, y);
// Store height and area type. // Collect all region layers.
int idx = x + y * lw; lregs.Add(ri);
layer.heights[idx] = (char) (s.y - hmin);
layer.areas[idx] = chf.areas[j];
// Check connection. // Update neighbours
char portal = (char)0; for (int dir = 0; dir < 4; ++dir)
char con = (char)0; {
for (int dir = 0; dir < 4; ++dir) { if (GetCon(s, dir) != RC_NOT_CONNECTED)
if (GetCon(s, dir) != RC_NOT_CONNECTED) { {
int ax = cx + GetDirOffsetX(dir); int ax = x + GetDirOffsetX(dir);
int ay = cy + GetDirOffsetY(dir); int ay = y + GetDirOffsetY(dir);
int ai = chf.cells[ax + ay * w].index + GetCon(s, dir); int ai = chf.cells[ax + ay * w].index + GetCon(s, dir);
int alid = srcReg[ai] != 0xff ? regs[srcReg[ai]].layerId : 0xff; int rai = srcReg[ai];
// Portal mask if (rai != 0xff && rai != ri)
if (chf.areas[ai] != RC_NULL_AREA && lid != alid) { addUnique(regs[ri].neis, rai);
portal |= (char)(1 << dir); }
// Update height so that it matches on both }
// sides of the portal. }
CompactSpan @as = chf.spans[ai];
if (@as.y > hmin) // Update overlapping regions.
layer.heights[idx] = Math.Max(layer.heights[idx], (char) (@as.y - hmin)); for (int i = 0; i < lregs.Count - 1; ++i)
} {
// Valid connection mask for (int j = i + 1; j < lregs.Count; ++j)
if (chf.areas[ai] != RC_NULL_AREA && lid == alid) { {
int nx = ax - borderSize; if (lregs[i] != lregs[j])
int ny = ay - borderSize; {
if (nx >= 0 && ny >= 0 && nx < lw && ny < lh) LayerRegion ri = regs[lregs[i]];
con |= (char)(1 << dir); LayerRegion rj = regs[lregs[j]];
} addUnique(ri.layers, lregs[j]);
addUnique(rj.layers, lregs[i]);
} }
} }
layer.cons[idx] = (portal << 4) | con;
} }
} }
} }
if (layer.minx > layer.maxx) // Create 2D layers from regions.
layer.minx = layer.maxx = 0; int layerId = 0;
if (layer.miny > layer.maxy)
layer.miny = layer.maxy = 0; List<int> stack = new List<int>();
for (int i = 0; i < nregs; ++i)
{
LayerRegion root = regs[i];
// Skip already visited.
if (root.layerId != 0xff)
continue;
// Start search.
root.layerId = layerId;
root.@base = true;
stack.Add(i);
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)
{
LayerRegion regn = regs[nei];
// Skip already visited.
if (regn.layerId != 0xff)
continue;
// Skip if the neighbour is overlapping root region.
if (contains(root.layers, nei))
continue;
// Skip if the height range would become too large.
int ymin = Math.Min(root.ymin, regn.ymin);
int ymax = Math.Max(root.ymax, regn.ymax);
if ((ymax - ymin) >= 255)
continue;
// Deepen
stack.Add(nei);
// Mark layer id
regn.layerId = layerId;
// Merge current layers to root.
foreach (int layer in regn.layers)
addUnique(root.layers, layer);
root.ymin = Math.Min(root.ymin, regn.ymin);
root.ymax = Math.Max(root.ymax, regn.ymax);
}
}
layerId++;
}
// Merge non-overlapping regions that are close in height.
int mergeHeight = walkableHeight * 4;
for (int i = 0; i < nregs; ++i)
{
LayerRegion ri = regs[i];
if (!ri.@base)
continue;
int newId = ri.layerId;
for (;;)
{
int oldId = 0xff;
for (int j = 0; j < nregs; ++j)
{
if (i == j)
continue;
LayerRegion rj = regs[j];
if (!rj.@base)
continue;
// Skip if the regions are not close to each other.
if (!overlapRange(ri.ymin, ri.ymax + mergeHeight, rj.ymin, rj.ymax + mergeHeight))
continue;
// Skip if the height range would become too large.
int ymin = Math.Min(ri.ymin, rj.ymin);
int ymax = Math.Max(ri.ymax, rj.ymax);
if ((ymax - ymin) >= 255)
continue;
// Make sure that there is no overlap when merging 'ri' and
// 'rj'.
bool overlap = false;
// Iterate over all regions which have the same layerId as
// 'rj'
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))
{
overlap = true;
break;
}
}
// Cannot merge of regions overlap.
if (overlap)
continue;
// Can merge i and j.
oldId = rj.layerId;
break;
}
// Could not find anything to merge with, stop.
if (oldId == 0xff)
break;
// Merge
for (int j = 0; j < nregs; ++j)
{
LayerRegion rj = regs[j];
if (rj.layerId == oldId)
{
rj.@base = false;
// Remap layerIds.
rj.layerId = newId;
// Add overlaid layers from 'rj' to 'ri'.
foreach (int layer in rj.layers)
addUnique(ri.layers, layer);
// Update height bounds.
ri.ymin = Math.Min(ri.ymin, rj.ymin);
ri.ymax = Math.Max(ri.ymax, rj.ymax);
}
}
}
}
// Compact layerIds
int[] remap = new int[256];
// Find number of unique layers.
layerId = 0;
for (int i = 0; i < nregs; ++i)
remap[regs[i].layerId] = 1;
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)
{
// ctx.stopTimer(RC_TIMER_BUILD_LAYERS);
return null;
}
// Create layers.
// rcAssert(lset.layers == 0);
int lw = w - borderSize * 2;
int lh = h - borderSize * 2;
// Build contracted bbox for layers.
float[] bmin = new float[3];
float[] bmax = new float[3];
copy(bmin, chf.bmin);
copy(bmax, chf.bmax);
bmin[0] += borderSize * chf.cs;
bmin[2] += borderSize * chf.cs;
bmax[0] -= borderSize * chf.cs;
bmax[2] -= borderSize * chf.cs;
HeightfieldLayerSet lset = new HeightfieldLayerSet();
lset.layers = new HeightfieldLayerSet.HeightfieldLayer[layerId];
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)
{
int curId = i;
HeightfieldLayerSet.HeightfieldLayer layer = lset.layers[i];
int gridSize = lw * lh;
layer.heights = new int[gridSize];
Array.Fill(layer.heights, 0xFF);
layer.areas = new int[gridSize];
layer.cons = new int[gridSize];
// Find layer height bounds.
int hmin = 0, hmax = 0;
for (int j = 0; j < nregs; ++j)
{
if (regs[j].@base && regs[j].layerId == curId)
{
hmin = regs[j].ymin;
hmax = regs[j].ymax;
}
}
layer.width = lw;
layer.height = lh;
layer.cs = chf.cs;
layer.ch = chf.ch;
// Adjust the bbox to fit the heightfield.
copy(layer.bmin, bmin);
copy(layer.bmax, bmax);
layer.bmin[1] = bmin[1] + hmin * chf.ch;
layer.bmax[1] = bmin[1] + hmax * chf.ch;
layer.hmin = hmin;
layer.hmax = hmax;
// Update usable data region.
layer.minx = layer.width;
layer.maxx = 0;
layer.miny = layer.height;
layer.maxy = 0;
// Copy height and area from compact heightfield.
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)
{
CompactSpan s = chf.spans[j];
// Skip unassigned regions.
if (srcReg[j] == 0xff)
continue;
// Skip of does nto belong to current layer.
int lid = regs[srcReg[j]].layerId;
if (lid != curId)
continue;
// Update data bounds.
layer.minx = Math.Min(layer.minx, x);
layer.maxx = Math.Max(layer.maxx, x);
layer.miny = Math.Min(layer.miny, y);
layer.maxy = Math.Max(layer.maxy, y);
// Store height and area type.
int idx = x + y * lw;
layer.heights[idx] = (char)(s.y - hmin);
layer.areas[idx] = chf.areas[j];
// Check connection.
char portal = (char)0;
char con = (char)0;
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)
{
portal |= (char)(1 << dir);
// Update height so that it matches on both
// sides of the portal.
CompactSpan @as = chf.spans[ai];
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)
{
int nx = ax - borderSize;
int ny = ay - borderSize;
if (nx >= 0 && ny >= 0 && nx < lw && ny < lh)
con |= (char)(1 << dir);
}
}
}
layer.cons[idx] = (portal << 4) | con;
}
}
}
if (layer.minx > layer.maxx)
layer.minx = layer.maxx = 0;
if (layer.miny > layer.maxy)
layer.miny = layer.maxy = 0;
}
// ctx->stopTimer(RC_TIMER_BUILD_LAYERS);
return lset;
} }
// ctx->stopTimer(RC_TIMER_BUILD_LAYERS);
return lset;
} }
}
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -22,13 +22,11 @@ using System;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
using static RecastConstants;
public class RecastRasterization
using static RecastConstants; {
/**
public class RecastRasterization
{
/**
* Check whether two bounding boxes overlap * Check whether two bounding boxes overlap
* *
* @param amin * @param amin
@ -41,16 +39,16 @@ public class RecastRasterization
* Max axis extents of bounding box B * Max axis extents of bounding box B
* @returns true if the two bounding boxes overlap. False otherwise * @returns true if the two bounding boxes overlap. False otherwise
*/ */
private static bool overlapBounds(float[] amin, float[] amax, float[] bmin, float[] bmax) private static bool overlapBounds(float[] amin, float[] amax, float[] bmin, float[] bmax)
{ {
bool overlap = true; bool overlap = true;
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap; overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap; overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap; overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap;
return overlap; return overlap;
} }
/** /**
* Adds a span to the heightfield. If the new span overlaps existing spans, it will merge the new span with the * Adds a span to the heightfield. If the new span overlaps existing spans, it will merge the new span with the
* existing ones. The span addition can be set to favor flags. If the span is merged to another span and the new * existing ones. The span addition can be set to favor flags. If the span is merged to another span and the new
* spanMax is within flagMergeThreshold units from the existing span, the span flags are merged. * spanMax is within flagMergeThreshold units from the existing span, the span flags are merged.
@ -71,77 +69,77 @@ public class RecastRasterization
* The merge theshold. [Limit: >= 0] [Units: vx] * The merge theshold. [Limit: >= 0] [Units: vx]
* @see Heightfield, Span. * @see Heightfield, Span.
*/ */
public static void addSpan(Heightfield heightfield, int x, int y, int spanMin, int spanMax, int areaId, public static void addSpan(Heightfield heightfield, int x, int y, int spanMin, int spanMax, int areaId,
int flagMergeThreshold) int flagMergeThreshold)
{
int idx = x + y * heightfield.width;
Span s = new Span();
s.smin = spanMin;
s.smax = spanMax;
s.area = areaId;
s.next = null;
// Empty cell, add the first span.
if (heightfield.spans[idx] == null)
{ {
heightfield.spans[idx] = s; int idx = x + y * heightfield.width;
return;
}
Span prev = null; Span s = new Span();
Span cur = heightfield.spans[idx]; s.smin = spanMin;
s.smax = spanMax;
s.area = areaId;
s.next = null;
// Insert and merge spans. // Empty cell, add the first span.
while (cur != null) if (heightfield.spans[idx] == null)
{
if (cur.smin > s.smax)
{ {
// Current span is further than the new span, break. heightfield.spans[idx] = s;
break; return;
} }
else if (cur.smax < s.smin)
Span prev = null;
Span cur = heightfield.spans[idx];
// Insert and merge spans.
while (cur != null)
{ {
// Current span is before the new span advance. if (cur.smin > s.smax)
prev = cur; {
cur = cur.next; // Current span is further than the new span, break.
break;
}
else if (cur.smax < s.smin)
{
// Current span is before the new span advance.
prev = cur;
cur = cur.next;
}
else
{
// Merge spans.
if (cur.smin < s.smin)
s.smin = cur.smin;
if (cur.smax > s.smax)
s.smax = cur.smax;
// Merge flags.
if (Math.Abs(s.smax - cur.smax) <= flagMergeThreshold)
s.area = Math.Max(s.area, cur.area);
// Remove current span.
Span next = cur.next;
if (prev != null)
prev.next = next;
else
heightfield.spans[idx] = next;
cur = next;
}
}
// Insert new span.
if (prev != null)
{
s.next = prev.next;
prev.next = s;
} }
else else
{ {
// Merge spans. s.next = heightfield.spans[idx];
if (cur.smin < s.smin) heightfield.spans[idx] = s;
s.smin = cur.smin;
if (cur.smax > s.smax)
s.smax = cur.smax;
// Merge flags.
if (Math.Abs(s.smax - cur.smax) <= flagMergeThreshold)
s.area = Math.Max(s.area, cur.area);
// Remove current span.
Span next = cur.next;
if (prev != null)
prev.next = next;
else
heightfield.spans[idx] = next;
cur = next;
} }
} }
// Insert new span. /**
if (prev != null)
{
s.next = prev.next;
prev.next = s;
}
else
{
s.next = heightfield.spans[idx];
heightfield.spans[idx] = s;
}
}
/**
* Divides a convex polygon of max 12 vertices into two convex polygons across a separating axis. * Divides a convex polygon of max 12 vertices into two convex polygons across a separating axis.
* *
* @param inVerts * @param inVerts
@ -160,63 +158,63 @@ public class RecastRasterization
* The separating axis * The separating axis
* @return The number of resulting polygon 1 and polygon 2 vertices * @return The number of resulting polygon 1 and polygon 2 vertices
*/ */
private static int[] dividePoly(float[] inVerts, int inVertsOffset, int inVertsCount, int outVerts1, int outVerts2, float axisOffset, private static int[] dividePoly(float[] inVerts, int inVertsOffset, int inVertsCount, int outVerts1, int outVerts2, float axisOffset,
int axis) int axis)
{
float[] d = new float[12];
for (int i = 0; i < inVertsCount; ++i)
d[i] = axisOffset - inVerts[inVertsOffset + i * 3 + axis];
int m = 0, n = 0;
for (int i = 0, j = inVertsCount - 1; i < inVertsCount; j = i, ++i)
{ {
bool ina = d[j] >= 0; float[] d = new float[12];
bool inb = d[i] >= 0; for (int i = 0; i < inVertsCount; ++i)
if (ina != inb) d[i] = axisOffset - inVerts[inVertsOffset + i * 3 + axis];
int m = 0, n = 0;
for (int i = 0, j = inVertsCount - 1; i < inVertsCount; j = i, ++i)
{ {
float s = d[j] / (d[j] - d[i]); bool ina = d[j] >= 0;
inVerts[outVerts1 + m * 3 + 0] = inVerts[inVertsOffset + j * 3 + 0] bool inb = d[i] >= 0;
+ (inVerts[inVertsOffset + i * 3 + 0] - inVerts[inVertsOffset + j * 3 + 0]) * s; if (ina != inb)
inVerts[outVerts1 + m * 3 + 1] = inVerts[inVertsOffset + j * 3 + 1]
+ (inVerts[inVertsOffset + i * 3 + 1] - inVerts[inVertsOffset + j * 3 + 1]) * s;
inVerts[outVerts1 + m * 3 + 2] = inVerts[inVertsOffset + j * 3 + 2]
+ (inVerts[inVertsOffset + i * 3 + 2] - inVerts[inVertsOffset + j * 3 + 2]) * s;
RecastVectors.copy(inVerts, outVerts2 + n * 3, inVerts, outVerts1 + m * 3);
m++;
n++;
// add the i'th point to the right polygon. Do NOT add points that are on the dividing line
// since these were already added above
if (d[i] > 0)
{ {
RecastVectors.copy(inVerts, outVerts1 + m * 3, inVerts, inVertsOffset + i * 3); float s = d[j] / (d[j] - d[i]);
inVerts[outVerts1 + m * 3 + 0] = inVerts[inVertsOffset + j * 3 + 0]
+ (inVerts[inVertsOffset + i * 3 + 0] - inVerts[inVertsOffset + j * 3 + 0]) * s;
inVerts[outVerts1 + m * 3 + 1] = inVerts[inVertsOffset + j * 3 + 1]
+ (inVerts[inVertsOffset + i * 3 + 1] - inVerts[inVertsOffset + j * 3 + 1]) * s;
inVerts[outVerts1 + m * 3 + 2] = inVerts[inVertsOffset + j * 3 + 2]
+ (inVerts[inVertsOffset + i * 3 + 2] - inVerts[inVertsOffset + j * 3 + 2]) * s;
RecastVectors.copy(inVerts, outVerts2 + n * 3, inVerts, outVerts1 + m * 3);
m++; m++;
n++;
// add the i'th point to the right polygon. Do NOT add points that are on the dividing line
// since these were already added above
if (d[i] > 0)
{
RecastVectors.copy(inVerts, outVerts1 + m * 3, inVerts, inVertsOffset + i * 3);
m++;
}
else if (d[i] < 0)
{
RecastVectors.copy(inVerts, outVerts2 + n * 3, inVerts, inVertsOffset + i * 3);
n++;
}
} }
else if (d[i] < 0) else // same side
{ {
// add the i'th point to the right polygon. Addition is done even for points on the dividing line
if (d[i] >= 0)
{
RecastVectors.copy(inVerts, outVerts1 + m * 3, inVerts, inVertsOffset + i * 3);
m++;
if (d[i] != 0)
continue;
}
RecastVectors.copy(inVerts, outVerts2 + n * 3, inVerts, inVertsOffset + i * 3); RecastVectors.copy(inVerts, outVerts2 + n * 3, inVerts, inVertsOffset + i * 3);
n++; n++;
} }
} }
else // same side
{
// add the i'th point to the right polygon. Addition is done even for points on the dividing line
if (d[i] >= 0)
{
RecastVectors.copy(inVerts, outVerts1 + m * 3, inVerts, inVertsOffset + i * 3);
m++;
if (d[i] != 0)
continue;
}
RecastVectors.copy(inVerts, outVerts2 + n * 3, inVerts, inVertsOffset + i * 3); return new int[] { m, n };
n++;
}
} }
return new int[] { m, n }; /**
}
/**
* Rasterize a single triangle to the heightfield. This code is extremely hot, so much care should be given to * Rasterize a single triangle to the heightfield. This code is extremely hot, so much care should be given to
* maintaining maximum perf here. * maintaining maximum perf here.
* *
@ -245,138 +243,138 @@ public class RecastRasterization
* @param flagMergeThreshold * @param flagMergeThreshold
* The threshold in which area flags will be merged * The threshold in which area flags will be merged
*/ */
private static void rasterizeTri(float[] verts, int v0, int v1, int v2, int area, Heightfield hf, float[] hfBBMin, private static void rasterizeTri(float[] verts, int v0, int v1, int v2, int area, Heightfield hf, float[] hfBBMin,
float[] hfBBMax, float cellSize, float inverseCellSize, float inverseCellHeight, int flagMergeThreshold) float[] hfBBMax, float cellSize, float inverseCellSize, float inverseCellHeight, int flagMergeThreshold)
{
float[] tmin = new float[3];
float[] tmax = new float[3];
float by = hfBBMax[1] - hfBBMin[1];
// Calculate the bounding box of the triangle.
RecastVectors.copy(tmin, verts, v0 * 3);
RecastVectors.copy(tmax, verts, v0 * 3);
RecastVectors.min(tmin, verts, v1 * 3);
RecastVectors.min(tmin, verts, v2 * 3);
RecastVectors.max(tmax, verts, v1 * 3);
RecastVectors.max(tmax, verts, v2 * 3);
// If the triangle does not touch the bbox of the heightfield, skip the triagle.
if (!overlapBounds(hfBBMin, hfBBMax, tmin, tmax))
return;
// Calculate the footprint of the triangle on the grid's y-axis
int z0 = (int)((tmin[2] - hfBBMin[2]) * inverseCellSize);
int z1 = (int)((tmax[2] - hfBBMin[2]) * inverseCellSize);
int w = hf.width;
int h = hf.height;
// use -1 rather than 0 to cut the polygon properly at the start of the tile
z0 = RecastCommon.clamp(z0, -1, h - 1);
z1 = RecastCommon.clamp(z1, 0, h - 1);
// Clip the triangle into all grid cells it touches.
float[] buf = new float[7 * 3 * 4];
int @in = 0;
int inRow = 7 * 3;
int p1 = inRow + 7 * 3;
int p2 = p1 + 7 * 3;
RecastVectors.copy(buf, 0, verts, v0 * 3);
RecastVectors.copy(buf, 3, verts, v1 * 3);
RecastVectors.copy(buf, 6, verts, v2 * 3);
int nvRow, nvIn = 3;
for (int z = z0; z <= z1; ++z)
{ {
// Clip polygon to row. Store the remaining polygon as well float[] tmin = new float[3];
float cellZ = hfBBMin[2] + z * cellSize; float[] tmax = new float[3];
int[] nvrowin = dividePoly(buf, @in, nvIn, inRow, p1, cellZ + cellSize, 2); float by = hfBBMax[1] - hfBBMin[1];
nvRow = nvrowin[0];
nvIn = nvrowin[1];
{
int temp = @in;
@in = p1;
p1 = temp;
}
if (nvRow < 3)
continue;
if (z < 0) // Calculate the bounding box of the triangle.
{ RecastVectors.copy(tmin, verts, v0 * 3);
continue; RecastVectors.copy(tmax, verts, v0 * 3);
} RecastVectors.min(tmin, verts, v1 * 3);
RecastVectors.min(tmin, verts, v2 * 3);
RecastVectors.max(tmax, verts, v1 * 3);
RecastVectors.max(tmax, verts, v2 * 3);
// find the horizontal bounds in the row // If the triangle does not touch the bbox of the heightfield, skip the triagle.
float minX = buf[inRow], maxX = buf[inRow]; if (!overlapBounds(hfBBMin, hfBBMax, tmin, tmax))
for (int i = 1; i < nvRow; ++i) return;
{
float v = buf[inRow + i * 3];
minX = Math.Min(minX, v);
maxX = Math.Max(maxX, v);
}
int x0 = (int)((minX - hfBBMin[0]) * inverseCellSize); // Calculate the footprint of the triangle on the grid's y-axis
int x1 = (int)((maxX - hfBBMin[0]) * inverseCellSize); int z0 = (int)((tmin[2] - hfBBMin[2]) * inverseCellSize);
if (x1 < 0 || x0 >= w) int z1 = (int)((tmax[2] - hfBBMin[2]) * inverseCellSize);
{
continue;
}
x0 = RecastCommon.clamp(x0, -1, w - 1); int w = hf.width;
x1 = RecastCommon.clamp(x1, 0, w - 1); int h = hf.height;
// use -1 rather than 0 to cut the polygon properly at the start of the tile
z0 = RecastCommon.clamp(z0, -1, h - 1);
z1 = RecastCommon.clamp(z1, 0, h - 1);
int nv, nv2 = nvRow; // Clip the triangle into all grid cells it touches.
for (int x = x0; x <= x1; ++x) float[] buf = new float[7 * 3 * 4];
int @in = 0;
int inRow = 7 * 3;
int p1 = inRow + 7 * 3;
int p2 = p1 + 7 * 3;
RecastVectors.copy(buf, 0, verts, v0 * 3);
RecastVectors.copy(buf, 3, verts, v1 * 3);
RecastVectors.copy(buf, 6, verts, v2 * 3);
int nvRow, nvIn = 3;
for (int z = z0; z <= z1; ++z)
{ {
// Clip polygon to column. store the remaining polygon as well // Clip polygon to row. Store the remaining polygon as well
float cx = hfBBMin[0] + x * cellSize; float cellZ = hfBBMin[2] + z * cellSize;
int[] nvnv2 = dividePoly(buf, inRow, nv2, p1, p2, cx + cellSize, 0); int[] nvrowin = dividePoly(buf, @in, nvIn, inRow, p1, cellZ + cellSize, 2);
nv = nvnv2[0]; nvRow = nvrowin[0];
nv2 = nvnv2[1]; nvIn = nvrowin[1];
{ {
int temp = inRow; int temp = @in;
inRow = p2; @in = p1;
p2 = temp; p1 = temp;
} }
if (nv < 3) if (nvRow < 3)
continue; continue;
if (x < 0)
if (z < 0)
{ {
continue; continue;
} }
// Calculate min and max of the span. // find the horizontal bounds in the row
float spanMin = buf[p1 + 1]; float minX = buf[inRow], maxX = buf[inRow];
float spanMax = buf[p1 + 1]; for (int i = 1; i < nvRow; ++i)
for (int i = 1; i < nv; ++i)
{ {
spanMin = Math.Min(spanMin, buf[p1 + i * 3 + 1]); float v = buf[inRow + i * 3];
spanMax = Math.Max(spanMax, buf[p1 + i * 3 + 1]); minX = Math.Min(minX, v);
maxX = Math.Max(maxX, v);
} }
spanMin -= hfBBMin[1]; int x0 = (int)((minX - hfBBMin[0]) * inverseCellSize);
spanMax -= hfBBMin[1]; int x1 = (int)((maxX - hfBBMin[0]) * inverseCellSize);
// Skip the span if it is outside the heightfield bbox if (x1 < 0 || x0 >= w)
if (spanMax < 0.0f) {
continue; continue;
if (spanMin > by) }
continue;
// Clamp the span to the heightfield bbox.
if (spanMin < 0.0f)
spanMin = 0;
if (spanMax > by)
spanMax = by;
// Snap the span to the heightfield height grid. x0 = RecastCommon.clamp(x0, -1, w - 1);
int spanMinCellIndex = RecastCommon.clamp((int)Math.Floor(spanMin * inverseCellHeight), 0, SPAN_MAX_HEIGHT); x1 = RecastCommon.clamp(x1, 0, w - 1);
int spanMaxCellIndex = RecastCommon.clamp((int)Math.Ceiling(spanMax * inverseCellHeight), spanMinCellIndex + 1, SPAN_MAX_HEIGHT);
addSpan(hf, x, z, spanMinCellIndex, spanMaxCellIndex, area, flagMergeThreshold); int nv, nv2 = nvRow;
for (int x = x0; x <= x1; ++x)
{
// Clip polygon to column. store the remaining polygon as well
float cx = hfBBMin[0] + x * cellSize;
int[] nvnv2 = dividePoly(buf, inRow, nv2, p1, p2, cx + cellSize, 0);
nv = nvnv2[0];
nv2 = nvnv2[1];
{
int temp = inRow;
inRow = p2;
p2 = temp;
}
if (nv < 3)
continue;
if (x < 0)
{
continue;
}
// Calculate min and max of the span.
float spanMin = buf[p1 + 1];
float spanMax = buf[p1 + 1];
for (int i = 1; i < nv; ++i)
{
spanMin = Math.Min(spanMin, buf[p1 + i * 3 + 1]);
spanMax = Math.Max(spanMax, buf[p1 + i * 3 + 1]);
}
spanMin -= hfBBMin[1];
spanMax -= hfBBMin[1];
// Skip the span if it is outside the heightfield bbox
if (spanMax < 0.0f)
continue;
if (spanMin > by)
continue;
// Clamp the span to the heightfield bbox.
if (spanMin < 0.0f)
spanMin = 0;
if (spanMax > by)
spanMax = by;
// Snap the span to the heightfield height grid.
int spanMinCellIndex = RecastCommon.clamp((int)Math.Floor(spanMin * inverseCellHeight), 0, SPAN_MAX_HEIGHT);
int spanMaxCellIndex = RecastCommon.clamp((int)Math.Ceiling(spanMax * inverseCellHeight), spanMinCellIndex + 1, SPAN_MAX_HEIGHT);
addSpan(hf, x, z, spanMinCellIndex, spanMaxCellIndex, area, flagMergeThreshold);
}
} }
} }
}
/** /**
* Rasterizes a single triangle into the specified heightfield. Calling this for each triangle in a mesh is less * Rasterizes a single triangle into the specified heightfield. Calling this for each triangle in a mesh is less
* efficient than calling rasterizeTriangles. No spans will be added if the triangle does not overlap the * efficient than calling rasterizeTriangles. No spans will be added if the triangle does not overlap the
* heightfield grid. * heightfield grid.
@ -397,20 +395,20 @@ public class RecastRasterization
* The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx] * The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx]
* @see Heightfield * @see Heightfield
*/ */
public static void rasterizeTriangle(Heightfield heightfield, float[] verts, int v0, int v1, int v2, int area, public static void rasterizeTriangle(Heightfield heightfield, float[] verts, int v0, int v1, int v2, int area,
int flagMergeThreshold, Telemetry ctx) int flagMergeThreshold, Telemetry ctx)
{ {
ctx.startTimer("RASTERIZE_TRIANGLES"); ctx.startTimer("RASTERIZE_TRIANGLES");
float inverseCellSize = 1.0f / heightfield.cs; float inverseCellSize = 1.0f / heightfield.cs;
float inverseCellHeight = 1.0f / heightfield.ch; float inverseCellHeight = 1.0f / heightfield.ch;
rasterizeTri(verts, v0, v1, v2, area, heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize, rasterizeTri(verts, v0, v1, v2, area, heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize,
inverseCellHeight, flagMergeThreshold); inverseCellHeight, flagMergeThreshold);
ctx.stopTimer("RASTERIZE_TRIANGLES"); ctx.stopTimer("RASTERIZE_TRIANGLES");
} }
/** /**
* Rasterizes an indexed triangle mesh into the specified heightfield. Spans will only be added for triangles that * Rasterizes an indexed triangle mesh into the specified heightfield. Spans will only be added for triangles that
* overlap the heightfield grid. * overlap the heightfield grid.
* *
@ -428,26 +426,26 @@ public class RecastRasterization
* The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx] * The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx]
* @see Heightfield * @see Heightfield
*/ */
public static void rasterizeTriangles(Heightfield heightfield, float[] verts, int[] tris, int[] areaIds, int numTris, public static void rasterizeTriangles(Heightfield heightfield, float[] verts, int[] tris, int[] areaIds, int numTris,
int flagMergeThreshold, Telemetry ctx) int flagMergeThreshold, Telemetry ctx)
{
ctx.startTimer("RASTERIZE_TRIANGLES");
float inverseCellSize = 1.0f / heightfield.cs;
float inverseCellHeight = 1.0f / heightfield.ch;
for (int triIndex = 0; triIndex < numTris; ++triIndex)
{ {
int v0 = tris[triIndex * 3 + 0]; ctx.startTimer("RASTERIZE_TRIANGLES");
int v1 = tris[triIndex * 3 + 1];
int v2 = tris[triIndex * 3 + 2]; float inverseCellSize = 1.0f / heightfield.cs;
rasterizeTri(verts, v0, v1, v2, areaIds[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, float inverseCellHeight = 1.0f / heightfield.ch;
inverseCellSize, inverseCellHeight, flagMergeThreshold); for (int triIndex = 0; triIndex < numTris; ++triIndex)
{
int v0 = tris[triIndex * 3 + 0];
int v1 = tris[triIndex * 3 + 1];
int v2 = tris[triIndex * 3 + 2];
rasterizeTri(verts, v0, v1, v2, areaIds[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs,
inverseCellSize, inverseCellHeight, flagMergeThreshold);
}
ctx.stopTimer("RASTERIZE_TRIANGLES");
} }
ctx.stopTimer("RASTERIZE_TRIANGLES"); /**
}
/**
* Rasterizes a triangle list into the specified heightfield. Expects each triangle to be specified as three * Rasterizes a triangle list into the specified heightfield. Expects each triangle to be specified as three
* sequential vertices of 3 floats. Spans will only be added for triangles that overlap the heightfield grid. * sequential vertices of 3 floats. Spans will only be added for triangles that overlap the heightfield grid.
* *
@ -465,23 +463,23 @@ public class RecastRasterization
* The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx] * The distance where the walkable flag is favored over the non-walkable flag. [Limit: >= 0] [Units: vx]
* @see Heightfield * @see Heightfield
*/ */
public static void rasterizeTriangles(Heightfield heightfield, float[] verts, int[] areaIds, int numTris, public static void rasterizeTriangles(Heightfield heightfield, float[] verts, int[] areaIds, int numTris,
int flagMergeThreshold, Telemetry ctx) int flagMergeThreshold, Telemetry ctx)
{
ctx.startTimer("RASTERIZE_TRIANGLES");
float inverseCellSize = 1.0f / heightfield.cs;
float inverseCellHeight = 1.0f / heightfield.ch;
for (int triIndex = 0; triIndex < numTris; ++triIndex)
{ {
int v0 = (triIndex * 3 + 0); ctx.startTimer("RASTERIZE_TRIANGLES");
int v1 = (triIndex * 3 + 1);
int v2 = (triIndex * 3 + 2);
rasterizeTri(verts, v0, v1, v2, areaIds[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs,
inverseCellSize, inverseCellHeight, flagMergeThreshold);
}
ctx.stopTimer("RASTERIZE_TRIANGLES"); float inverseCellSize = 1.0f / heightfield.cs;
float inverseCellHeight = 1.0f / heightfield.ch;
for (int triIndex = 0; triIndex < numTris; ++triIndex)
{
int v0 = (triIndex * 3 + 0);
int v1 = (triIndex * 3 + 1);
int v2 = (triIndex * 3 + 2);
rasterizeTri(verts, v0, v1, v2, areaIds[triIndex], heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs,
inverseCellSize, inverseCellHeight, flagMergeThreshold);
}
ctx.stopTimer("RASTERIZE_TRIANGLES");
}
} }
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -22,80 +22,78 @@ using System;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public static class RecastVectors
public static class RecastVectors
{
public static void min(float[] a, float[] b, int i)
{ {
a[0] = Math.Min(a[0], b[i + 0]); public static void min(float[] a, float[] b, int i)
a[1] = Math.Min(a[1], b[i + 1]); {
a[2] = Math.Min(a[2], b[i + 2]); a[0] = Math.Min(a[0], b[i + 0]);
} a[1] = Math.Min(a[1], b[i + 1]);
a[2] = Math.Min(a[2], b[i + 2]);
}
public static void max(float[] a, float[] b, int i) public static void max(float[] a, float[] b, int i)
{ {
a[0] = Math.Max(a[0], b[i + 0]); a[0] = Math.Max(a[0], b[i + 0]);
a[1] = Math.Max(a[1], b[i + 1]); a[1] = Math.Max(a[1], b[i + 1]);
a[2] = Math.Max(a[2], b[i + 2]); a[2] = Math.Max(a[2], b[i + 2]);
} }
public static void copy(float[] @out, float[] @in, int i) public static void copy(float[] @out, float[] @in, int i)
{ {
copy(@out, 0, @in, i); copy(@out, 0, @in, i);
} }
public static void copy(float[] @out, float[] @in) public static void copy(float[] @out, float[] @in)
{ {
copy(@out, 0, @in, 0); copy(@out, 0, @in, 0);
} }
public static void copy(float[] @out, int n, float[] @in, int m) public static void copy(float[] @out, int n, float[] @in, int m)
{ {
@out[n] = @in[m]; @out[n] = @in[m];
@out[n + 1] = @in[m + 1]; @out[n + 1] = @in[m + 1];
@out[n + 2] = @in[m + 2]; @out[n + 2] = @in[m + 2];
} }
public static void add(float[] e0, float[] a, float[] verts, int i) public static void add(float[] e0, float[] a, float[] verts, int i)
{ {
e0[0] = a[0] + verts[i]; e0[0] = a[0] + verts[i];
e0[1] = a[1] + verts[i + 1]; e0[1] = a[1] + verts[i + 1];
e0[2] = a[2] + verts[i + 2]; e0[2] = a[2] + verts[i + 2];
} }
public static void sub(float[] e0, float[] verts, int i, int j) public static void sub(float[] e0, float[] verts, int i, int j)
{ {
e0[0] = verts[i] - verts[j]; e0[0] = verts[i] - verts[j];
e0[1] = verts[i + 1] - verts[j + 1]; e0[1] = verts[i + 1] - verts[j + 1];
e0[2] = verts[i + 2] - verts[j + 2]; e0[2] = verts[i + 2] - verts[j + 2];
} }
public static void sub(float[] e0, float[] i, float[] verts, int j) public static void sub(float[] e0, float[] i, float[] verts, int j)
{ {
e0[0] = i[0] - verts[j]; e0[0] = i[0] - verts[j];
e0[1] = i[1] - verts[j + 1]; e0[1] = i[1] - verts[j + 1];
e0[2] = i[2] - verts[j + 2]; e0[2] = i[2] - verts[j + 2];
} }
public static void cross(float[] dest, float[] v1, float[] v2) public static void cross(float[] dest, float[] v1, float[] v2)
{ {
dest[0] = v1[1] * v2[2] - v1[2] * v2[1]; dest[0] = v1[1] * v2[2] - v1[2] * v2[1];
dest[1] = v1[2] * v2[0] - v1[0] * v2[2]; dest[1] = v1[2] * v2[0] - v1[0] * v2[2];
dest[2] = v1[0] * v2[1] - v1[1] * v2[0]; dest[2] = v1[0] * v2[1] - v1[1] * v2[0];
} }
public static void normalize(float[] v) public static void normalize(float[] v)
{ {
float d = (float)(1.0f / Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])); float d = (float)(1.0f / Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]));
v[0] *= d; v[0] *= d;
v[1] *= d; v[1] *= d;
v[2] *= d; v[2] *= d;
} }
public static float dot(float[] v1, float[] v2) public static float dot(float[] v1, float[] v2)
{ {
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
}
} }
}
} }

View File

@ -21,57 +21,59 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class RecastVoxelization
{
public static Heightfield buildSolidHeightfield(InputGeomProvider geomProvider, RecastBuilderConfig builderCfg,
Telemetry ctx)
{
RecastConfig cfg = builderCfg.cfg;
// Allocate voxel heightfield where we rasterize our input data to.
public class RecastVoxelization { Heightfield solid = new Heightfield(builderCfg.width, builderCfg.height, builderCfg.bmin, builderCfg.bmax, cfg.cs,
public static Heightfield buildSolidHeightfield(InputGeomProvider geomProvider, RecastBuilderConfig builderCfg,
Telemetry ctx) {
RecastConfig cfg = builderCfg.cfg;
// Allocate voxel heightfield where we rasterize our input data to.
Heightfield solid = new Heightfield(builderCfg.width, builderCfg.height, builderCfg.bmin, builderCfg.bmax, cfg.cs,
cfg.ch, cfg.borderSize); cfg.ch, cfg.borderSize);
// Allocate array that can hold triangle area types. // Allocate array that can hold triangle area types.
// If you have multiple meshes you need to process, allocate // If you have multiple meshes you need to process, allocate
// and array which can hold the max number of triangles you need to // and array which can hold the max number of triangles you need to
// process. // process.
// Find triangles which are walkable based on their slope and rasterize // Find triangles which are walkable based on their slope and rasterize
// them. // them.
// If your input data is multiple meshes, you can transform them here, // If your input data is multiple meshes, you can transform them here,
// calculate // calculate
// the are type for each of the meshes and rasterize them. // the are type for each of the meshes and rasterize them.
foreach (TriMesh geom in geomProvider.meshes()) { foreach (TriMesh geom in geomProvider.meshes())
float[] verts = geom.getVerts(); {
if (cfg.useTiles) { float[] verts = geom.getVerts();
float[] tbmin = new float[2]; if (cfg.useTiles)
float[] tbmax = new float[2]; {
tbmin[0] = builderCfg.bmin[0]; float[] tbmin = new float[2];
tbmin[1] = builderCfg.bmin[2]; float[] tbmax = new float[2];
tbmax[0] = builderCfg.bmax[0]; tbmin[0] = builderCfg.bmin[0];
tbmax[1] = builderCfg.bmax[2]; tbmin[1] = builderCfg.bmin[2];
List<ChunkyTriMeshNode> nodes = geom.getChunksOverlappingRect(tbmin, tbmax); tbmax[0] = builderCfg.bmax[0];
foreach (ChunkyTriMeshNode node in nodes) { tbmax[1] = builderCfg.bmax[2];
int[] tris = node.tris; List<ChunkyTriMeshNode> nodes = geom.getChunksOverlappingRect(tbmin, tbmax);
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
{
int[] tris = geom.getTris();
int ntris = tris.Length / 3; int ntris = tris.Length / 3;
int[] m_triareas = Recast.markWalkableTriangles(ctx, cfg.walkableSlopeAngle, verts, tris, ntris, int[] m_triareas = Recast.markWalkableTriangles(ctx, cfg.walkableSlopeAngle, verts, tris, ntris,
cfg.walkableAreaMod); cfg.walkableAreaMod);
RecastRasterization.rasterizeTriangles(solid, verts, tris, m_triareas, ntris, cfg.walkableClimb, ctx); RecastRasterization.rasterizeTriangles(solid, verts, tris, m_triareas, ntris, cfg.walkableClimb, ctx);
} }
} else {
int[] tris = geom.getTris();
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);
} }
return solid;
} }
return solid;
} }
}
} }

View File

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

View File

@ -25,36 +25,34 @@ using DotRecast.Core;
namespace DotRecast.Recast namespace DotRecast.Recast
{ {
public class Telemetry
public class Telemetry
{
private readonly ThreadLocal<Dictionary<string, AtomicLong>> timerStart = new ThreadLocal<Dictionary<string, AtomicLong>>(() => new Dictionary<string, AtomicLong>());
private readonly ConcurrentDictionary<string, AtomicLong> timerAccum = new ConcurrentDictionary<string, AtomicLong>();
public void startTimer(string name)
{ {
timerStart.Value[name] = new AtomicLong(Stopwatch.GetTimestamp()); private readonly ThreadLocal<Dictionary<string, AtomicLong>> timerStart = new ThreadLocal<Dictionary<string, AtomicLong>>(() => new Dictionary<string, AtomicLong>());
} private readonly ConcurrentDictionary<string, AtomicLong> timerAccum = new ConcurrentDictionary<string, AtomicLong>();
public void stopTimer(string name) public void startTimer(string name)
{
timerAccum
.GetOrAdd(name, _ => new AtomicLong(0))
.AddAndGet(Stopwatch.GetTimestamp() - timerStart.Value?[name].Read() ?? 0);
}
public void warn(string @string)
{
Console.WriteLine(@string);
}
public void print()
{
foreach (var (n, v) in timerAccum)
{ {
Console.WriteLine(n + ": " + v.Read() / 1000000); timerStart.Value[name] = new AtomicLong(Stopwatch.GetTimestamp());
}
public void stopTimer(string name)
{
timerAccum
.GetOrAdd(name, _ => new AtomicLong(0))
.AddAndGet(Stopwatch.GetTimestamp() - timerStart.Value?[name].Read() ?? 0);
}
public void warn(string @string)
{
Console.WriteLine(@string);
}
public void print()
{
foreach (var (n, v) in timerAccum)
{
Console.WriteLine(n + ": " + v.Read() / 1000000);
}
} }
} }
}
} }

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -24,288 +24,296 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test; namespace DotRecast.Detour.Crowd.Test;
public class Crowd4Test : AbstractCrowdTest { public class Crowd4Test : AbstractCrowdTest
{
static readonly float[][] EXPECTED_A1Q2TVTA =
{
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 },
new[] { 23.338102f, 10.197294f, -46.372726f, -0.048912f, 0.000000f, -0.204439f },
new[] { 23.158630f, 10.197294f, -46.386150f, -0.897364f, 0.000000f, -0.067119f },
new[] { 22.750568f, 10.197294f, -46.389927f, -2.040308f, 0.000000f, -0.018881f },
new[] { 22.173302f, 10.197294f, -46.272018f, -2.886330f, 0.000000f, 0.589539f },
new[] { 21.579432f, 10.197294f, -46.052692f, -2.969354f, 0.000000f, 1.096630f },
new[] { 20.957502f, 10.197294f, -45.761692f, -3.109650f, 0.000000f, 1.455011f },
new[] { 20.365976f, 10.197294f, -45.476425f, -2.957630f, 0.000000f, 1.426327f },
new[] { 19.775288f, 10.197294f, -45.189430f, -2.953444f, 0.000000f, 1.434977f },
new[] { 19.185482f, 10.197294f, -44.900627f, -2.949032f, 0.000000f, 1.444020f },
new[] { 18.596607f, 10.197294f, -44.609928f, -2.944376f, 0.000000f, 1.453491f },
new[] { 18.008717f, 10.197294f, -44.317242f, -2.939449f, 0.000000f, 1.463430f },
new[] { 17.421871f, 10.197294f, -44.022465f, -2.934223f, 0.000000f, 1.473880f },
new[] { 16.836138f, 10.197294f, -43.725487f, -2.928665f, 0.000000f, 1.484893f },
new[] { 16.237518f, 10.197294f, -43.413433f, -2.993098f, 0.000000f, 1.560270f },
new[] { 15.662568f, 10.197294f, -43.098225f, -2.874751f, 0.000000f, 1.576043f },
new[] { 15.094946f, 10.197294f, -42.777035f, -2.838109f, 0.000000f, 1.605944f },
new[] { 14.528272f, 10.197294f, -42.454178f, -2.833370f, 0.000000f, 1.614288f },
new[] { 13.962630f, 10.197294f, -42.129513f, -2.828207f, 0.000000f, 1.623317f },
new[] { 13.391782f, 10.197294f, -41.806934f, -2.854241f, 0.000000f, 1.612890f },
new[] { 12.799945f, 10.197294f, -41.482201f, -2.959186f, 0.000000f, 1.623668f },
new[] { 12.197092f, 10.197294f, -41.139999f, -3.014262f, 0.000000f, 1.711000f },
new[] { 11.594053f, 10.197294f, -40.811634f, -3.015195f, 0.000000f, 1.641821f },
new[] { 10.996500f, 10.197294f, -40.460262f, -2.987766f, 0.000000f, 1.756859f },
new[] { 10.401724f, 10.197294f, -40.104210f, -2.973881f, 0.000000f, 1.780260f },
new[] { 9.810114f, 10.197294f, -39.742920f, -2.958048f, 0.000000f, 1.806445f },
new[] { 9.222152f, 10.197294f, -39.375725f, -2.939809f, 0.000000f, 1.835979f },
new[] { 8.638441f, 10.197294f, -39.001808f, -2.918552f, 0.000000f, 1.869585f },
new[] { 8.059751f, 10.197294f, -38.620167f, -2.893450f, 0.000000f, 1.908203f },
new[] { 7.536088f, 10.197294f, -38.213108f, -2.618314f, 0.000000f, 2.035301f },
new[] { 7.008043f, 10.197294f, -37.798481f, -2.640225f, 0.000000f, 2.073141f },
new[] { 6.484523f, 10.197294f, -37.378155f, -2.617601f, 0.000000f, 2.101635f },
new[] { 5.957591f, 10.197294f, -36.962112f, -2.634658f, 0.000000f, 2.080211f },
new[] { 5.458399f, 10.197294f, -36.525387f, -2.495958f, 0.000000f, 2.183624f },
new[] { 4.952830f, 10.197294f, -36.083633f, -2.527847f, 0.000000f, 2.208776f },
new[] { 4.445582f, 10.197294f, -35.638187f, -2.536238f, 0.000000f, 2.227235f },
new[] { 3.997866f, 10.197294f, -35.100090f, -2.238582f, 0.000000f, 2.690493f },
new[] { 3.604921f, 10.197294f, -34.520786f, -1.964725f, 0.000000f, 2.896524f },
new[] { 3.216267f, 10.197294f, -33.938595f, -1.943270f, 0.000000f, 2.910962f },
new[] { 2.839496f, 10.197294f, -33.348644f, -1.883856f, 0.000000f, 2.949760f },
new[] { 2.482899f, 10.197294f, -32.746284f, -1.782983f, 0.000000f, 3.011806f },
new[] { 2.165045f, 10.197294f, -32.122612f, -1.589272f, 0.000000f, 3.118367f },
new[] { 1.968905f, 10.197294f, -31.542366f, -0.980699f, 0.000000f, 2.901230f },
new[] { 1.830858f, 10.197294f, -30.899487f, -0.690238f, 0.000000f, 3.214399f },
new[] { 1.790072f, 10.197294f, -30.240873f, -0.203930f, 0.000000f, 3.293068f },
new[] { 1.860591f, 10.197294f, -29.561634f, 0.352598f, 0.000000f, 3.396194f },
new[] { 2.125831f, 10.197294f, -28.913832f, 1.326197f, 0.000000f, 3.239013f },
new[] { 2.391070f, 10.197294f, -28.266029f, 1.326197f, 0.000000f, 3.239013f },
new[] { 2.656309f, 10.197294f, -27.618227f, 1.326197f, 0.000000f, 3.239013f },
new[] { 2.921549f, 10.197294f, -26.970425f, 1.326197f, 0.000000f, 3.239013f },
new[] { 3.186788f, 10.197294f, -26.322622f, 1.326197f, 0.000000f, 3.239013f },
new[] { 3.452027f, 10.197294f, -25.674820f, 1.326197f, 0.000000f, 3.239013f },
new[] { 3.717267f, 10.197294f, -25.027018f, 1.326197f, 0.000000f, 3.239013f },
new[] { 3.982506f, 10.197294f, -24.379215f, 1.326197f, 0.000000f, 3.239013f },
new[] { 4.247745f, 10.197294f, -23.731413f, 1.326197f, 0.000000f, 3.239013f },
new[] { 4.512984f, 10.197294f, -23.083611f, 1.326197f, 0.000000f, 3.239013f },
new[] { 4.778224f, 10.197294f, -22.435808f, 1.326197f, 0.000000f, 3.239013f },
new[] { 5.043463f, 10.197294f, -21.788006f, 1.326197f, 0.000000f, 3.239013f },
new[] { 5.308702f, 10.197294f, -21.140203f, 1.326197f, 0.000000f, 3.239013f },
new[] { 5.573941f, 10.197294f, -20.492401f, 1.326197f, 0.000000f, 3.239013f },
new[] { 5.839180f, 10.197294f, -19.844599f, 1.326197f, 0.000000f, 3.239012f },
new[] { 6.173420f, 10.197294f, -19.342373f, 1.706390f, 0.000000f, 2.813494f },
new[] { 6.501637f, 10.197294f, -19.076807f, 1.451270f, 0.000000f, 2.463549f },
new[] { 6.803561f, 10.197294f, -18.729990f, 1.002026f, 0.000000f, 2.679962f },
new[] { 6.799414f, 10.197294f, -18.366598f, -0.993883f, 0.000000f, 1.390529f },
new[] { 6.990967f, 10.197294f, -18.240343f, 2.109758f, 0.000000f, 0.180499f },
new[] { 7.157530f, 10.197294f, -18.320511f, 0.758936f, 0.000000f, 0.228570f },
new[] { 7.349619f, 10.197294f, -18.347782f, 0.926001f, 0.000000f, 0.008640f },
new[] { 7.448954f, 10.197294f, -18.390112f, 0.496676f, 0.000000f, -0.211644f },
new[] { 7.524421f, 10.197294f, -18.477961f, 0.377335f, 0.000000f, -0.439247f },
new[] { 7.628986f, 10.197294f, -18.484478f, 0.522823f, 0.000000f, -0.032589f },
new[] { 7.751089f, 10.197294f, -18.466608f, 0.610513f, 0.000000f, 0.089349f },
new[] { 7.891870f, 10.197294f, -18.490461f, 0.703905f, 0.000000f, -0.119263f },
new[] { 8.032493f, 10.197294f, -18.515228f, 0.703115f, 0.000000f, -0.123836f },
new[] { 8.172967f, 10.1862135f, -18.540827f, 0.702371f, 0.000000f, -0.127992f },
new[] { 8.182732f, 10.1862135f, -18.626057f, 0.048824f, 0.000000f, -0.426151f },
new[] { 8.165586f, 10.188671f, -18.846558f, -0.085729f, 0.000000f, -1.102502f },
new[] { 8.048562f, 10.197294f, -18.763044f, -0.785845f, 0.000000f, 1.028553f },
new[] { 7.802718f, 10.197294f, -18.943884f, -1.229222f, 0.000000f, -0.904202f },
new[] { 7.655485f, 10.197294f, -19.137533f, -0.736162f, 0.000000f, -0.968250f },
new[] { 7.542105f, 10.197294f, -19.244339f, -0.566899f, 0.000000f, -0.534033f },
new[] { 7.427822f, 10.197294f, -19.031147f, -0.572634f, 0.000000f, 1.497748f },
new[] { 7.339287f, 10.197294f, -18.757444f, -0.442677f, 0.000000f, 1.368514f },
new[] { 7.255370f, 10.197294f, -18.471191f, -0.419582f, 0.000000f, 1.431268f },
new[] { 7.153419f, 10.197294f, -18.314838f, -0.509755f, 0.000000f, 0.781767f },
new[] { 7.129016f, 10.197294f, -18.194052f, -0.122013f, 0.000000f, 0.603930f },
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 }
};
static readonly float[][] EXPECTED_A1Q2TVTA = { static readonly float[][] EXPECTED_A1Q2TVTAS =
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.253357f, 10.197294f, -46.279934f, 0.074597f, 0.000000f, -0.017069f },
new[] { 23.347885f, 10.197294f, -46.331837f, -0.024102f, 0.000000f, -0.093108f }, new[] { 23.336805f, 10.197294f, -46.374985f, 0.119401f, 0.000000f, -0.031009f },
new[] { 23.338102f, 10.197294f, -46.372726f, -0.048912f, 0.000000f, -0.204439f }, new[] { 23.351542f, 10.197294f, -46.410728f, 0.053426f, 0.000000f, -0.093556f },
new[] { 23.158630f, 10.197294f, -46.386150f, -0.897364f, 0.000000f, -0.067119f }, new[] { 23.362417f, 10.197294f, -46.429638f, 0.054377f, 0.000000f, -0.094549f },
new[] { 22.750568f, 10.197294f, -46.389927f, -2.040308f, 0.000000f, -0.018881f }, new[] { 23.216995f, 10.197294f, -46.446442f, -0.727108f, 0.000000f, -0.084018f },
new[] { 22.173302f, 10.197294f, -46.272018f, -2.886330f, 0.000000f, 0.589539f }, new[] { 22.902132f, 10.197294f, -46.426094f, -1.574317f, 0.000000f, 0.101733f },
new[] { 21.579432f, 10.197294f, -46.052692f, -2.969354f, 0.000000f, 1.096630f }, new[] { 22.491152f, 10.197294f, -46.376247f, -2.054897f, 0.000000f, 0.249239f },
new[] { 20.957502f, 10.197294f, -45.761692f, -3.109650f, 0.000000f, 1.455011f }, new[] { 22.058567f, 10.197294f, -46.289536f, -2.162925f, 0.000000f, 0.433569f },
new[] { 20.365976f, 10.197294f, -45.476425f, -2.957630f, 0.000000f, 1.426327f }, new[] { 21.588501f, 10.197294f, -46.183846f, -2.350327f, 0.000000f, 0.528449f },
new[] { 19.775288f, 10.197294f, -45.189430f, -2.953444f, 0.000000f, 1.434977f }, new[] { 21.116070f, 10.197294f, -46.072765f, -2.362152f, 0.000000f, 0.555402f },
new[] { 19.185482f, 10.197294f, -44.900627f, -2.949032f, 0.000000f, 1.444020f }, new[] { 20.642447f, 10.197294f, -45.954391f, -2.368116f, 0.000000f, 0.591867f },
new[] { 18.596607f, 10.197294f, -44.609928f, -2.944376f, 0.000000f, 1.453491f }, new[] { 20.168785f, 10.197294f, -45.827782f, -2.368307f, 0.000000f, 0.633050f },
new[] { 18.008717f, 10.197294f, -44.317242f, -2.939449f, 0.000000f, 1.463430f }, new[] { 19.695480f, 10.197294f, -45.692959f, -2.366527f, 0.000000f, 0.674118f },
new[] { 17.421871f, 10.197294f, -44.022465f, -2.934223f, 0.000000f, 1.473880f }, new[] { 19.221388f, 10.197294f, -45.549664f, -2.370462f, 0.000000f, 0.716482f },
new[] { 16.836138f, 10.197294f, -43.725487f, -2.928665f, 0.000000f, 1.484893f }, new[] { 18.747715f, 10.197294f, -45.401028f, -2.368369f, 0.000000f, 0.743186f },
new[] { 16.237518f, 10.197294f, -43.413433f, -2.993098f, 0.000000f, 1.560270f }, new[] { 18.274532f, 10.197294f, -45.247219f, -2.365911f, 0.000000f, 0.769049f },
new[] { 15.662568f, 10.197294f, -43.098225f, -2.874751f, 0.000000f, 1.576043f }, new[] { 17.801916f, 10.197294f, -45.088371f, -2.363083f, 0.000000f, 0.794239f },
new[] { 15.094946f, 10.197294f, -42.777035f, -2.838109f, 0.000000f, 1.605944f }, new[] { 17.329977f, 10.197294f, -44.924515f, -2.359692f, 0.000000f, 0.819281f },
new[] { 14.528272f, 10.197294f, -42.454178f, -2.833370f, 0.000000f, 1.614288f }, new[] { 16.858923f, 10.197294f, -44.755489f, -2.355273f, 0.000000f, 0.845136f },
new[] { 13.962630f, 10.197294f, -42.129513f, -2.828207f, 0.000000f, 1.623317f }, new[] { 16.389112f, 10.197294f, -44.580818f, -2.349057f, 0.000000f, 0.873354f },
new[] { 13.391782f, 10.197294f, -41.806934f, -2.854241f, 0.000000f, 1.612890f }, new[] { 15.904601f, 10.197294f, -44.397808f, -2.422558f, 0.000000f, 0.915057f },
new[] { 12.799945f, 10.197294f, -41.482201f, -2.959186f, 0.000000f, 1.623668f }, new[] { 15.422479f, 10.197294f, -44.204182f, -2.410610f, 0.000000f, 0.968130f },
new[] { 12.197092f, 10.197294f, -41.139999f, -3.014262f, 0.000000f, 1.711000f }, new[] { 14.943066f, 10.197294f, -44.000332f, -2.397065f, 0.000000f, 1.019240f },
new[] { 11.594053f, 10.197294f, -40.811634f, -3.015195f, 0.000000f, 1.641821f }, new[] { 14.466599f, 10.197294f, -43.786751f, -2.382330f, 0.000000f, 1.067903f },
new[] { 10.996500f, 10.197294f, -40.460262f, -2.987766f, 0.000000f, 1.756859f }, new[] { 13.993264f, 10.197294f, -43.563339f, -2.366675f, 0.000000f, 1.117066f },
new[] { 10.401724f, 10.197294f, -40.104210f, -2.973881f, 0.000000f, 1.780260f }, new[] { 13.539879f, 10.197294f, -43.333740f, -2.266925f, 0.000000f, 1.147989f },
new[] { 9.810114f, 10.197294f, -39.742920f, -2.958048f, 0.000000f, 1.806445f }, new[] { 13.089582f, 10.197294f, -43.096500f, -2.251482f, 0.000000f, 1.186200f },
new[] { 9.222152f, 10.197294f, -39.375725f, -2.939809f, 0.000000f, 1.835979f }, new[] { 12.642441f, 10.197294f, -42.852135f, -2.235710f, 0.000000f, 1.221827f },
new[] { 8.638441f, 10.197294f, -39.001808f, -2.918552f, 0.000000f, 1.869585f }, new[] { 12.198518f, 10.197294f, -42.601135f, -2.219616f, 0.000000f, 1.254998f },
new[] { 8.059751f, 10.197294f, -38.620167f, -2.893450f, 0.000000f, 1.908203f }, new[] { 11.757890f, 10.197294f, -42.343952f, -2.203139f, 0.000000f, 1.285906f },
new[] { 7.536088f, 10.197294f, -38.213108f, -2.618314f, 0.000000f, 2.035301f }, new[] { 11.320659f, 10.197294f, -42.080994f, -2.186156f, 0.000000f, 1.314789f },
new[] { 7.008043f, 10.197294f, -37.798481f, -2.640225f, 0.000000f, 2.073141f }, new[] { 10.886962f, 10.197294f, -41.812611f, -2.168484f, 0.000000f, 1.341919f },
new[] { 6.484523f, 10.197294f, -37.378155f, -2.617601f, 0.000000f, 2.101635f }, new[] { 10.456985f, 10.197294f, -41.539093f, -2.149882f, 0.000000f, 1.367595f },
new[] { 5.957591f, 10.197294f, -36.962112f, -2.634658f, 0.000000f, 2.080211f }, new[] { 10.030977f, 10.197294f, -41.260666f, -2.130040f, 0.000000f, 1.392133f },
new[] { 5.458399f, 10.197294f, -36.525387f, -2.495958f, 0.000000f, 2.183624f }, new[] { 9.609450f, 10.197294f, -40.977509f, -2.107634f, 0.000000f, 1.415778f },
new[] { 4.952830f, 10.197294f, -36.083633f, -2.527847f, 0.000000f, 2.208776f }, new[] { 9.193022f, 10.197294f, -40.690060f, -2.082145f, 0.000000f, 1.437238f },
new[] { 4.445582f, 10.197294f, -35.638187f, -2.536238f, 0.000000f, 2.227235f }, new[] { 8.782290f, 10.197294f, -40.398022f, -2.053656f, 0.000000f, 1.460193f },
new[] { 3.997866f, 10.197294f, -35.100090f, -2.238582f, 0.000000f, 2.690493f }, new[] { 8.378143f, 10.197294f, -40.101349f, -2.020734f, 0.000000f, 1.483373f },
new[] { 3.604921f, 10.197294f, -34.520786f, -1.964725f, 0.000000f, 2.896524f }, new[] { 7.981577f, 10.197294f, -39.799294f, -1.982833f, 0.000000f, 1.510276f },
new[] { 3.216267f, 10.197294f, -33.938595f, -1.943270f, 0.000000f, 2.910962f }, new[] { 7.592501f, 10.197294f, -39.491383f, -1.945382f, 0.000000f, 1.539564f },
new[] { 2.839496f, 10.197294f, -33.348644f, -1.883856f, 0.000000f, 2.949760f }, new[] { 7.235472f, 10.197294f, -39.191612f, -1.785145f, 0.000000f, 1.498847f },
new[] { 2.482899f, 10.197294f, -32.746284f, -1.782983f, 0.000000f, 3.011806f }, new[] { 6.856690f, 10.197294f, -38.869774f, -1.893909f, 0.000000f, 1.609192f },
new[] { 2.165045f, 10.197294f, -32.122612f, -1.589272f, 0.000000f, 3.118367f }, new[] { 6.501227f, 10.197294f, -38.570526f, -1.777313f, 0.000000f, 1.496231f },
new[] { 1.968905f, 10.197294f, -31.542366f, -0.980699f, 0.000000f, 2.901230f }, new[] { 6.224775f, 10.197294f, -38.301174f, -1.382261f, 0.000000f, 1.346764f },
new[] { 1.830858f, 10.197294f, -30.899487f, -0.690238f, 0.000000f, 3.214399f }, new[] { 5.929498f, 10.197294f, -37.995701f, -1.476386f, 0.000000f, 1.527363f },
new[] { 1.790072f, 10.197294f, -30.240873f, -0.203930f, 0.000000f, 3.293068f }, new[] { 5.629647f, 10.197294f, -37.639618f, -1.499255f, 0.000000f, 1.780412f },
new[] { 1.860591f, 10.197294f, -29.561634f, 0.352598f, 0.000000f, 3.396194f }, new[] { 5.328491f, 10.197294f, -37.296764f, -1.505784f, 0.000000f, 1.714271f },
new[] { 2.125831f, 10.197294f, -28.913832f, 1.326197f, 0.000000f, 3.239013f }, new[] { 5.023998f, 10.197294f, -36.908840f, -1.522466f, 0.000000f, 1.939620f },
new[] { 2.391070f, 10.197294f, -28.266029f, 1.326197f, 0.000000f, 3.239013f }, new[] { 4.744634f, 10.197294f, -36.502831f, -1.396819f, 0.000000f, 2.030053f },
new[] { 2.656309f, 10.197294f, -27.618227f, 1.326197f, 0.000000f, 3.239013f }, new[] { 4.492529f, 10.197294f, -36.078068f, -1.260521f, 0.000000f, 2.123809f },
new[] { 2.921549f, 10.197294f, -26.970425f, 1.326197f, 0.000000f, 3.239013f }, new[] { 4.239073f, 10.197294f, -35.631050f, -1.267281f, 0.000000f, 2.235098f },
new[] { 3.186788f, 10.197294f, -26.322622f, 1.326197f, 0.000000f, 3.239013f }, new[] { 3.989349f, 10.197294f, -35.178169f, -1.248621f, 0.000000f, 2.264413f },
new[] { 3.452027f, 10.197294f, -25.674820f, 1.326197f, 0.000000f, 3.239013f }, new[] { 3.736778f, 10.197294f, -34.723938f, -1.262857f, 0.000000f, 2.271152f },
new[] { 3.717267f, 10.197294f, -25.027018f, 1.326197f, 0.000000f, 3.239013f }, new[] { 3.491473f, 10.197294f, -34.264828f, -1.226526f, 0.000000f, 2.295557f },
new[] { 3.982506f, 10.197294f, -24.379215f, 1.326197f, 0.000000f, 3.239013f }, new[] { 3.177553f, 10.197294f, -33.848518f, -1.569597f, 0.000000f, 2.081553f },
new[] { 4.247745f, 10.197294f, -23.731413f, 1.326197f, 0.000000f, 3.239013f }, new[] { 2.866278f, 10.197294f, -33.427658f, -1.556375f, 0.000000f, 2.104302f },
new[] { 4.512984f, 10.197294f, -23.083611f, 1.326197f, 0.000000f, 3.239013f }, new[] { 2.566121f, 10.197294f, -32.995960f, -1.500786f, 0.000000f, 2.158491f },
new[] { 4.778224f, 10.197294f, -22.435808f, 1.326197f, 0.000000f, 3.239013f }, new[] { 2.291139f, 10.197294f, -32.544334f, -1.374910f, 0.000000f, 2.258132f },
new[] { 5.043463f, 10.197294f, -21.788006f, 1.326197f, 0.000000f, 3.239013f }, new[] { 2.066509f, 10.197294f, -32.063156f, -1.123153f, 0.000000f, 2.405891f },
new[] { 5.308702f, 10.197294f, -21.140203f, 1.326197f, 0.000000f, 3.239013f }, new[] { 1.949471f, 10.197294f, -31.544592f, -0.585186f, 0.000000f, 2.592823f },
new[] { 5.573941f, 10.197294f, -20.492401f, 1.326197f, 0.000000f, 3.239013f }, new[] { 1.841830f, 10.197294f, -31.020411f, -0.538206f, 0.000000f, 2.620903f },
new[] { 5.839180f, 10.197294f, -19.844599f, 1.326197f, 0.000000f, 3.239012f }, new[] { 1.804908f, 10.197294f, -30.484823f, -0.184613f, 0.000000f, 2.677938f },
new[] { 6.173420f, 10.197294f, -19.342373f, 1.706390f, 0.000000f, 2.813494f }, new[] { 1.848811f, 10.197294f, -29.937574f, 0.219515f, 0.000000f, 2.736246f },
new[] { 6.501637f, 10.197294f, -19.076807f, 1.451270f, 0.000000f, 2.463549f }, new[] { 1.946300f, 10.197294f, -29.417610f, 0.487449f, 0.000000f, 2.599819f },
new[] { 6.803561f, 10.197294f, -18.729990f, 1.002026f, 0.000000f, 2.679962f }, new[] { 2.111085f, 10.197294f, -28.884066f, 0.823926f, 0.000000f, 2.667721f },
new[] { 6.799414f, 10.197294f, -18.366598f, -0.993883f, 0.000000f, 1.390529f }, new[] { 2.288109f, 10.197294f, -28.358007f, 0.885119f, 0.000000f, 2.630293f },
new[] { 6.990967f, 10.197294f, -18.240343f, 2.109758f, 0.000000f, 0.180499f }, new[] { 2.499196f, 10.197294f, -27.812208f, 1.055435f, 0.000000f, 2.728995f },
new[] { 7.157530f, 10.197294f, -18.320511f, 0.758936f, 0.000000f, 0.228570f }, new[] { 2.709832f, 10.197294f, -27.265604f, 1.053180f, 0.000000f, 2.733017f },
new[] { 7.349619f, 10.197294f, -18.347782f, 0.926001f, 0.000000f, 0.008640f }, new[] { 2.920712f, 10.197294f, -26.718000f, 1.054401f, 0.000000f, 2.738015f },
new[] { 7.448954f, 10.197294f, -18.390112f, 0.496676f, 0.000000f, -0.211644f }, new[] { 3.166102f, 10.197294f, -26.183590f, 1.226950f, 0.000000f, 2.672051f },
new[] { 7.524421f, 10.197294f, -18.477961f, 0.377335f, 0.000000f, -0.439247f }, new[] { 3.409404f, 10.197294f, -25.647873f, 1.216508f, 0.000000f, 2.678585f },
new[] { 7.628986f, 10.197294f, -18.484478f, 0.522823f, 0.000000f, -0.032589f }, new[] { 3.604466f, 10.197294f, -25.169361f, 0.975311f, 0.000000f, 2.392557f },
new[] { 7.751089f, 10.197294f, -18.466608f, 0.610513f, 0.000000f, 0.089349f }, new[] { 3.799465f, 10.197294f, -24.688942f, 0.974994f, 0.000000f, 2.402100f },
new[] { 7.891870f, 10.197294f, -18.490461f, 0.703905f, 0.000000f, -0.119263f }, new[] { 4.008917f, 10.197294f, -24.179743f, 1.047259f, 0.000000f, 2.545998f },
new[] { 8.032493f, 10.197294f, -18.515228f, 0.703115f, 0.000000f, -0.123836f }, new[] { 4.254181f, 10.197294f, -23.667099f, 1.226322f, 0.000000f, 2.563215f },
new[] { 8.172967f, 10.1862135f, -18.540827f, 0.702371f, 0.000000f, -0.127992f }, new[] { 4.411407f, 10.197294f, -23.188858f, 0.786131f, 0.000000f, 2.391205f },
new[] { 8.182732f, 10.1862135f, -18.626057f, 0.048824f, 0.000000f, -0.426151f }, new[] { 4.630627f, 10.197294f, -22.706358f, 1.096096f, 0.000000f, 2.412497f },
new[] { 8.165586f, 10.188671f, -18.846558f, -0.085729f, 0.000000f, -1.102502f }, new[] { 4.733336f, 10.197294f, -22.348816f, 0.513548f, 0.000000f, 1.787710f },
new[] { 8.048562f, 10.197294f, -18.763044f, -0.785845f, 0.000000f, 1.028553f }, new[] { 4.886934f, 10.197294f, -22.007650f, 0.767987f, 0.000000f, 1.705831f },
new[] { 7.802718f, 10.197294f, -18.943884f, -1.229222f, 0.000000f, -0.904202f }, new[] { 4.930230f, 10.197294f, -21.656166f, 0.216483f, 0.000000f, 1.757425f },
new[] { 7.655485f, 10.197294f, -19.137533f, -0.736162f, 0.000000f, -0.968250f }, new[] { 5.044151f, 10.197294f, -21.343338f, 0.569606f, 0.000000f, 1.564138f },
new[] { 7.542105f, 10.197294f, -19.244339f, -0.566899f, 0.000000f, -0.534033f }, new[] { 5.169365f, 10.197294f, -21.074562f, 0.626069f, 0.000000f, 1.343884f },
new[] { 7.427822f, 10.197294f, -19.031147f, -0.572634f, 0.000000f, 1.497748f }, new[] { 5.241940f, 10.197294f, -20.714607f, 0.362874f, 0.000000f, 1.799778f },
new[] { 7.339287f, 10.197294f, -18.757444f, -0.442677f, 0.000000f, 1.368514f }, new[] { 5.323618f, 10.197294f, -20.576834f, 0.408392f, 0.000000f, 0.688868f },
new[] { 7.255370f, 10.197294f, -18.471191f, -0.419582f, 0.000000f, 1.431268f }, new[] { 5.316272f, 10.197294f, -20.384502f, -0.036732f, 0.000000f, 0.961652f },
new[] { 7.153419f, 10.197294f, -18.314838f, -0.509755f, 0.000000f, 0.781767f }, new[] { 5.301373f, 10.197294f, -20.182859f, -0.074500f, 0.000000f, 1.008217f },
new[] { 7.129016f, 10.197294f, -18.194052f, -0.122013f, 0.000000f, 0.603930f }, new[] { 5.295803f, 10.197294f, -19.982578f, -0.027847f, 0.000000f, 1.001405f },
new[] { 7.099013f, 10.197294f, -18.064573f, -0.150015f, 0.000000f, 0.647393f }, new[] { 5.286469f, 10.197294f, -19.742945f, -0.046671f, 0.000000f, 1.198163f },
new[] { 7.085871f, 10.197294f, -17.946556f, -0.065714f, 0.000000f, 0.590090f }, new[] { 5.275438f, 10.197294f, -19.603636f, -0.055155f, 0.000000f, 0.696540f },
new[] { 7.067722f, 10.197294f, -17.801628f, -0.090745f, 0.000000f, 0.724639f }, new[] { 5.288567f, 10.197294f, -19.521788f, 0.065643f, 0.000000f, 0.409236f },
new[] { 7.048226f, 10.197294f, -17.673695f, -0.097479f, 0.000000f, 0.639666f } }; new[] { 5.440337f, 10.197294f, -19.479048f, 0.758851f, 0.000000f, 0.213696f },
new[] { 5.633360f, 10.197294f, -19.464310f, 0.965118f, 0.000000f, 0.073694f },
new[] { 5.825089f, 10.197294f, -19.461432f, 0.958646f, 0.000000f, 0.014389f },
new[] { 5.962496f, 10.197294f, -19.404169f, 0.687035f, 0.000000f, 0.286309f },
new[] { 6.051316f, 10.197294f, -19.417604f, 0.444099f, 0.000000f, -0.067175f },
new[] { 6.134418f, 10.197294f, -19.466887f, 0.415510f, 0.000000f, -0.246414f },
new[] { 6.195343f, 10.197294f, -19.520611f, 0.304628f, 0.000000f, -0.268624f },
new[] { 6.233473f, 10.197294f, -19.589584f, 0.190648f, 0.000000f, -0.344869f },
new[] { 6.143032f, 10.197294f, -19.644495f, -0.452208f, 0.000000f, -0.274552f },
new[] { 6.066758f, 10.197294f, -19.669443f, -0.381368f, 0.000000f, -0.124736f },
new[] { 5.988646f, 10.197294f, -19.691917f, -0.390560f, 0.000000f, -0.112371f },
new[] { 5.880603f, 10.197294f, -19.641851f, -0.540214f, 0.000000f, 0.250327f },
new[] { 5.768869f, 10.197294f, -19.553535f, -0.558670f, 0.000000f, 0.441585f },
new[] { 5.773450f, 10.197294f, -19.545095f, 0.022906f, 0.000000f, 0.042200f },
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 }
};
static readonly float[][] EXPECTED_A1Q2TVTAS = { static readonly float[][] EXPECTED_A1Q2T =
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[] { 22.990597f, 10.197294f, -46.112606f, -2.999564f, 0.000000f, 1.803501f },
new[] { 23.351542f, 10.197294f, -46.410728f, 0.053426f, 0.000000f, -0.093556f }, new[] { 22.524744f, 10.197294f, -45.867702f, -2.989815f, 0.000000f, 1.819617f },
new[] { 23.362417f, 10.197294f, -46.429638f, 0.054377f, 0.000000f, -0.094549f }, new[] { 21.946421f, 10.197294f, -45.530769f, -2.987121f, 0.000000f, 1.824035f },
new[] { 23.216995f, 10.197294f, -46.446442f, -0.727108f, 0.000000f, -0.084018f }, new[] { 21.357445f, 10.197294f, -45.183083f, -2.985955f, 0.000000f, 1.825945f },
new[] { 22.902132f, 10.197294f, -46.426094f, -1.574317f, 0.000000f, 0.101733f }, new[] { 20.766855f, 10.197294f, -44.833454f, -2.985027f, 0.000000f, 1.827461f },
new[] { 22.491152f, 10.197294f, -46.376247f, -2.054897f, 0.000000f, 0.249239f }, new[] { 20.176287f, 10.197294f, -44.483444f, -2.984109f, 0.000000f, 1.828960f },
new[] { 22.058567f, 10.197294f, -46.289536f, -2.162925f, 0.000000f, 0.433569f }, new[] { 19.586048f, 10.197294f, -44.133289f, -2.983157f, 0.000000f, 1.830513f },
new[] { 21.588501f, 10.197294f, -46.183846f, -2.350327f, 0.000000f, 0.528449f }, new[] { 18.996214f, 10.197294f, -43.783005f, -2.982163f, 0.000000f, 1.832130f },
new[] { 21.116070f, 10.197294f, -46.072765f, -2.362152f, 0.000000f, 0.555402f }, new[] { 18.406816f, 10.197294f, -43.432552f, -2.981127f, 0.000000f, 1.833817f },
new[] { 20.642447f, 10.197294f, -45.954391f, -2.368116f, 0.000000f, 0.591867f }, new[] { 17.817883f, 10.197294f, -43.081890f, -2.980047f, 0.000000f, 1.835571f },
new[] { 20.168785f, 10.197294f, -45.827782f, -2.368307f, 0.000000f, 0.633050f }, new[] { 17.229431f, 10.197294f, -42.730961f, -2.978924f, 0.000000f, 1.837393f },
new[] { 19.695480f, 10.197294f, -45.692959f, -2.366527f, 0.000000f, 0.674118f }, new[] { 16.641487f, 10.197294f, -42.379704f, -2.977759f, 0.000000f, 1.839280f },
new[] { 19.221388f, 10.197294f, -45.549664f, -2.370462f, 0.000000f, 0.716482f }, new[] { 16.054066f, 10.197294f, -42.028053f, -2.976555f, 0.000000f, 1.841228f },
new[] { 18.747715f, 10.197294f, -45.401028f, -2.368369f, 0.000000f, 0.743186f }, new[] { 15.467182f, 10.197294f, -41.675930f, -2.975313f, 0.000000f, 1.843233f },
new[] { 18.274532f, 10.197294f, -45.247219f, -2.365911f, 0.000000f, 0.769049f }, new[] { 14.880149f, 10.197294f, -41.325222f, -2.974040f, 0.000000f, 1.845288f },
new[] { 17.801916f, 10.197294f, -45.088371f, -2.363083f, 0.000000f, 0.794239f }, new[] { 14.292006f, 10.197294f, -40.972252f, -2.972459f, 0.000000f, 1.847835f },
new[] { 17.329977f, 10.197294f, -44.924515f, -2.359692f, 0.000000f, 0.819281f }, new[] { 13.702817f, 10.197294f, -40.616268f, -2.970898f, 0.000000f, 1.850342f },
new[] { 16.858923f, 10.197294f, -44.755489f, -2.355273f, 0.000000f, 0.845136f }, new[] { 13.113760f, 10.197294f, -40.258976f, -2.969465f, 0.000000f, 1.852640f },
new[] { 16.389112f, 10.197294f, -44.580818f, -2.349057f, 0.000000f, 0.873354f }, new[] { 12.525526f, 10.197294f, -39.902176f, -2.968050f, 0.000000f, 1.854907f },
new[] { 15.904601f, 10.197294f, -44.397808f, -2.422558f, 0.000000f, 0.915057f }, new[] { 11.938206f, 10.197294f, -39.545849f, -2.966453f, 0.000000f, 1.857459f },
new[] { 15.422479f, 10.197294f, -44.204182f, -2.410610f, 0.000000f, 0.968130f }, new[] { 11.351962f, 10.197294f, -39.190098f, -2.964650f, 0.000000f, 1.860337f },
new[] { 14.943066f, 10.197294f, -44.000332f, -2.397065f, 0.000000f, 1.019240f }, new[] { 10.767020f, 10.197294f, -38.835106f, -2.962592f, 0.000000f, 1.863611f },
new[] { 14.466599f, 10.197294f, -43.786751f, -2.382330f, 0.000000f, 1.067903f }, new[] { 10.183690f, 10.197294f, -38.481129f, -2.960215f, 0.000000f, 1.867385f },
new[] { 13.993264f, 10.197294f, -43.563339f, -2.366675f, 0.000000f, 1.117066f }, new[] { 9.602149f, 10.197294f, -38.128098f, -2.957422f, 0.000000f, 1.871805f },
new[] { 13.539879f, 10.197294f, -43.333740f, -2.266925f, 0.000000f, 1.147989f }, new[] { 9.023255f, 10.197294f, -37.777180f, -2.954136f, 0.000000f, 1.876987f },
new[] { 13.089582f, 10.197294f, -43.096500f, -2.251482f, 0.000000f, 1.186200f }, new[] { 8.448158f, 10.197294f, -37.429573f, -2.950055f, 0.000000f, 1.883394f },
new[] { 12.642441f, 10.197294f, -42.852135f, -2.235710f, 0.000000f, 1.221827f }, new[] { 7.879329f, 10.197294f, -37.087776f, -2.944782f, 0.000000f, 1.891628f },
new[] { 12.198518f, 10.197294f, -42.601135f, -2.219616f, 0.000000f, 1.254998f }, new[] { 7.319548f, 10.197294f, -36.753658f, -2.937588f, 0.000000f, 1.902782f },
new[] { 11.757890f, 10.197294f, -42.343952f, -2.203139f, 0.000000f, 1.285906f }, new[] { 6.776391f, 10.197294f, -36.434063f, -2.927629f, 0.000000f, 1.918068f },
new[] { 11.320659f, 10.197294f, -42.080994f, -2.186156f, 0.000000f, 1.314789f }, new[] { 6.269217f, 10.197294f, -36.142403f, -2.912834f, 0.000000f, 1.940462f },
new[] { 10.886962f, 10.197294f, -41.812611f, -2.168484f, 0.000000f, 1.341919f }, new[] { 5.846630f, 10.197294f, -35.881496f, -2.890513f, 0.000000f, 1.973559f },
new[] { 10.456985f, 10.197294f, -41.539093f, -2.149882f, 0.000000f, 1.367595f }, new[] { 5.411777f, 10.197294f, -35.547710f, -2.874764f, 0.000000f, 1.996430f },
new[] { 10.030977f, 10.197294f, -41.260666f, -2.130040f, 0.000000f, 1.392133f }, new[] { 4.896369f, 10.197294f, -35.184120f, -2.896978f, 0.000000f, 1.964057f },
new[] { 9.609450f, 10.197294f, -40.977509f, -2.107634f, 0.000000f, 1.415778f }, new[] { 4.367188f, 10.197294f, -34.833569f, -2.910470f, 0.000000f, 1.944008f },
new[] { 9.193022f, 10.197294f, -40.690060f, -2.082145f, 0.000000f, 1.437238f }, new[] { 3.807542f, 10.197294f, -34.472218f, -2.906039f, 0.000000f, 1.950624f },
new[] { 8.782290f, 10.197294f, -40.398022f, -2.053656f, 0.000000f, 1.460193f }, new[] { 3.238266f, 10.197294f, -34.064877f, -2.846376f, 0.000000f, 2.036700f },
new[] { 8.378143f, 10.197294f, -40.101349f, -2.020734f, 0.000000f, 1.483373f }, new[] { 2.917057f, 10.197294f, -33.455391f, -1.420109f, 0.000000f, 3.198952f },
new[] { 7.981577f, 10.197294f, -39.799294f, -1.982833f, 0.000000f, 1.510276f }, new[] { 2.645415f, 10.197294f, -32.810249f, -1.358214f, 0.000000f, 3.225718f },
new[] { 7.592501f, 10.197294f, -39.491383f, -1.945382f, 0.000000f, 1.539564f }, new[] { 2.373772f, 10.197294f, -32.165104f, -1.358212f, 0.000000f, 3.225718f },
new[] { 7.235472f, 10.197294f, -39.191612f, -1.785145f, 0.000000f, 1.498847f }, new[] { 2.103753f, 10.197294f, -31.530035f, -1.358215f, 0.000000f, 3.225717f },
new[] { 6.856690f, 10.197294f, -38.869774f, -1.893909f, 0.000000f, 1.609192f }, new[] { 1.837103f, 10.197294f, -30.882812f, -1.333248f, 0.000000f, 3.236116f },
new[] { 6.501227f, 10.197294f, -38.570526f, -1.777313f, 0.000000f, 1.496231f }, new[] { 1.887709f, 10.197294f, -30.193766f, 0.449056f, 0.000000f, 3.471073f },
new[] { 6.224775f, 10.197294f, -38.301174f, -1.382261f, 0.000000f, 1.346764f }, new[] { 2.034543f, 10.197294f, -29.509338f, 0.734173f, 0.000000f, 3.422132f },
new[] { 5.929498f, 10.197294f, -37.995701f, -1.476386f, 0.000000f, 1.527363f }, new[] { 2.266320f, 10.197294f, -28.848824f, 1.158885f, 0.000000f, 3.302572f },
new[] { 5.629647f, 10.197294f, -37.639618f, -1.499255f, 0.000000f, 1.780412f }, new[] { 2.498098f, 10.197294f, -28.188309f, 1.158885f, 0.000000f, 3.302572f },
new[] { 5.328491f, 10.197294f, -37.296764f, -1.505784f, 0.000000f, 1.714271f }, new[] { 2.729875f, 10.197294f, -27.527794f, 1.158886f, 0.000000f, 3.302572f },
new[] { 5.023998f, 10.197294f, -36.908840f, -1.522466f, 0.000000f, 1.939620f }, new[] { 2.992905f, 10.197294f, -26.879091f, 1.315149f, 0.000000f, 3.243514f },
new[] { 4.744634f, 10.197294f, -36.502831f, -1.396819f, 0.000000f, 2.030053f }, new[] { 3.255934f, 10.197294f, -26.230389f, 1.315149f, 0.000000f, 3.243514f },
new[] { 4.492529f, 10.197294f, -36.078068f, -1.260521f, 0.000000f, 2.123809f }, new[] { 3.518964f, 10.197294f, -25.581686f, 1.315149f, 0.000000f, 3.243514f },
new[] { 4.239073f, 10.197294f, -35.631050f, -1.267281f, 0.000000f, 2.235098f }, new[] { 3.781994f, 10.197294f, -24.932983f, 1.315149f, 0.000000f, 3.243514f },
new[] { 3.989349f, 10.197294f, -35.178169f, -1.248621f, 0.000000f, 2.264413f }, new[] { 4.045024f, 10.197294f, -24.284281f, 1.315149f, 0.000000f, 3.243514f },
new[] { 3.736778f, 10.197294f, -34.723938f, -1.262857f, 0.000000f, 2.271152f }, new[] { 4.308054f, 10.197294f, -23.635578f, 1.315149f, 0.000000f, 3.243514f },
new[] { 3.491473f, 10.197294f, -34.264828f, -1.226526f, 0.000000f, 2.295557f }, new[] { 4.571084f, 10.197294f, -22.986876f, 1.315149f, 0.000000f, 3.243514f },
new[] { 3.177553f, 10.197294f, -33.848518f, -1.569597f, 0.000000f, 2.081553f }, new[] { 4.834114f, 10.197294f, -22.338173f, 1.315149f, 0.000000f, 3.243514f },
new[] { 2.866278f, 10.197294f, -33.427658f, -1.556375f, 0.000000f, 2.104302f }, new[] { 5.097065f, 10.197294f, -21.689661f, 1.315149f, 0.000000f, 3.243514f },
new[] { 2.566121f, 10.197294f, -32.995960f, -1.500786f, 0.000000f, 2.158491f }, new[] { 5.349040f, 10.197294f, -21.068123f, 1.315149f, 0.000000f, 3.243514f },
new[] { 2.291139f, 10.197294f, -32.544334f, -1.374910f, 0.000000f, 2.258132f }, new[] { 5.564819f, 10.197294f, -20.535385f, 1.315191f, 0.000000f, 3.243497f },
new[] { 2.066509f, 10.197294f, -32.063156f, -1.123153f, 0.000000f, 2.405891f }, new[] { 5.742907f, 10.197294f, -20.094234f, 1.315492f, 0.000000f, 3.243375f },
new[] { 1.949471f, 10.197294f, -31.544592f, -0.585186f, 0.000000f, 2.592823f }, new[] { 5.844197f, 10.197294f, -19.830488f, 1.316821f, 0.000000f, 3.242836f },
new[] { 1.841830f, 10.197294f, -31.020411f, -0.538206f, 0.000000f, 2.620903f }, new[] { 5.890507f, 10.197294f, -19.651239f, 1.327608f, 0.000000f, 3.238434f },
new[] { 1.804908f, 10.197294f, -30.484823f, -0.184613f, 0.000000f, 2.677938f }, new[] { 5.895338f, 10.197294f, -19.433264f, 1.384180f, 0.000000f, 3.214661f },
new[] { 1.848811f, 10.197294f, -29.937574f, 0.219515f, 0.000000f, 2.736246f }, new[] { 5.917773f, 10.197294f, -19.188416f, 1.594034f, 0.000000f, 3.115936f },
new[] { 1.946300f, 10.197294f, -29.417610f, 0.487449f, 0.000000f, 2.599819f }, new[] { 5.947361f, 10.197294f, -19.047245f, 1.574677f, 0.000000f, 2.491868f },
new[] { 2.111085f, 10.197294f, -28.884066f, 0.823926f, 0.000000f, 2.667721f }, new[] { 5.960892f, 10.197294f, -18.990824f, 1.488381f, 0.000000f, 2.080121f },
new[] { 2.288109f, 10.197294f, -28.358007f, 0.885119f, 0.000000f, 2.630293f }, new[] { 5.971087f, 10.197294f, -18.963556f, 1.448915f, 0.000000f, 1.915559f },
new[] { 2.499196f, 10.197294f, -27.812208f, 1.055435f, 0.000000f, 2.728995f }, new[] { 5.977089f, 10.197294f, -18.950905f, 1.419177f, 0.000000f, 1.836029f }
new[] { 2.709832f, 10.197294f, -27.265604f, 1.053180f, 0.000000f, 2.733017f }, };
new[] { 2.920712f, 10.197294f, -26.718000f, 1.054401f, 0.000000f, 2.738015f },
new[] { 3.166102f, 10.197294f, -26.183590f, 1.226950f, 0.000000f, 2.672051f },
new[] { 3.409404f, 10.197294f, -25.647873f, 1.216508f, 0.000000f, 2.678585f },
new[] { 3.604466f, 10.197294f, -25.169361f, 0.975311f, 0.000000f, 2.392557f },
new[] { 3.799465f, 10.197294f, -24.688942f, 0.974994f, 0.000000f, 2.402100f },
new[] { 4.008917f, 10.197294f, -24.179743f, 1.047259f, 0.000000f, 2.545998f },
new[] { 4.254181f, 10.197294f, -23.667099f, 1.226322f, 0.000000f, 2.563215f },
new[] { 4.411407f, 10.197294f, -23.188858f, 0.786131f, 0.000000f, 2.391205f },
new[] { 4.630627f, 10.197294f, -22.706358f, 1.096096f, 0.000000f, 2.412497f },
new[] { 4.733336f, 10.197294f, -22.348816f, 0.513548f, 0.000000f, 1.787710f },
new[] { 4.886934f, 10.197294f, -22.007650f, 0.767987f, 0.000000f, 1.705831f },
new[] { 4.930230f, 10.197294f, -21.656166f, 0.216483f, 0.000000f, 1.757425f },
new[] { 5.044151f, 10.197294f, -21.343338f, 0.569606f, 0.000000f, 1.564138f },
new[] { 5.169365f, 10.197294f, -21.074562f, 0.626069f, 0.000000f, 1.343884f },
new[] { 5.241940f, 10.197294f, -20.714607f, 0.362874f, 0.000000f, 1.799778f },
new[] { 5.323618f, 10.197294f, -20.576834f, 0.408392f, 0.000000f, 0.688868f },
new[] { 5.316272f, 10.197294f, -20.384502f, -0.036732f, 0.000000f, 0.961652f },
new[] { 5.301373f, 10.197294f, -20.182859f, -0.074500f, 0.000000f, 1.008217f },
new[] { 5.295803f, 10.197294f, -19.982578f, -0.027847f, 0.000000f, 1.001405f },
new[] { 5.286469f, 10.197294f, -19.742945f, -0.046671f, 0.000000f, 1.198163f },
new[] { 5.275438f, 10.197294f, -19.603636f, -0.055155f, 0.000000f, 0.696540f },
new[] { 5.288567f, 10.197294f, -19.521788f, 0.065643f, 0.000000f, 0.409236f },
new[] { 5.440337f, 10.197294f, -19.479048f, 0.758851f, 0.000000f, 0.213696f },
new[] { 5.633360f, 10.197294f, -19.464310f, 0.965118f, 0.000000f, 0.073694f },
new[] { 5.825089f, 10.197294f, -19.461432f, 0.958646f, 0.000000f, 0.014389f },
new[] { 5.962496f, 10.197294f, -19.404169f, 0.687035f, 0.000000f, 0.286309f },
new[] { 6.051316f, 10.197294f, -19.417604f, 0.444099f, 0.000000f, -0.067175f },
new[] { 6.134418f, 10.197294f, -19.466887f, 0.415510f, 0.000000f, -0.246414f },
new[] { 6.195343f, 10.197294f, -19.520611f, 0.304628f, 0.000000f, -0.268624f },
new[] { 6.233473f, 10.197294f, -19.589584f, 0.190648f, 0.000000f, -0.344869f },
new[] { 6.143032f, 10.197294f, -19.644495f, -0.452208f, 0.000000f, -0.274552f },
new[] { 6.066758f, 10.197294f, -19.669443f, -0.381368f, 0.000000f, -0.124736f },
new[] { 5.988646f, 10.197294f, -19.691917f, -0.390560f, 0.000000f, -0.112371f },
new[] { 5.880603f, 10.197294f, -19.641851f, -0.540214f, 0.000000f, 0.250327f },
new[] { 5.768869f, 10.197294f, -19.553535f, -0.558670f, 0.000000f, 0.441585f },
new[] { 5.773450f, 10.197294f, -19.545095f, 0.022906f, 0.000000f, 0.042200f },
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 } };
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 },
new[] { 21.357445f, 10.197294f, -45.183083f, -2.985955f, 0.000000f, 1.825945f },
new[] { 20.766855f, 10.197294f, -44.833454f, -2.985027f, 0.000000f, 1.827461f },
new[] { 20.176287f, 10.197294f, -44.483444f, -2.984109f, 0.000000f, 1.828960f },
new[] { 19.586048f, 10.197294f, -44.133289f, -2.983157f, 0.000000f, 1.830513f },
new[] { 18.996214f, 10.197294f, -43.783005f, -2.982163f, 0.000000f, 1.832130f },
new[] { 18.406816f, 10.197294f, -43.432552f, -2.981127f, 0.000000f, 1.833817f },
new[] { 17.817883f, 10.197294f, -43.081890f, -2.980047f, 0.000000f, 1.835571f },
new[] { 17.229431f, 10.197294f, -42.730961f, -2.978924f, 0.000000f, 1.837393f },
new[] { 16.641487f, 10.197294f, -42.379704f, -2.977759f, 0.000000f, 1.839280f },
new[] { 16.054066f, 10.197294f, -42.028053f, -2.976555f, 0.000000f, 1.841228f },
new[] { 15.467182f, 10.197294f, -41.675930f, -2.975313f, 0.000000f, 1.843233f },
new[] { 14.880149f, 10.197294f, -41.325222f, -2.974040f, 0.000000f, 1.845288f },
new[] { 14.292006f, 10.197294f, -40.972252f, -2.972459f, 0.000000f, 1.847835f },
new[] { 13.702817f, 10.197294f, -40.616268f, -2.970898f, 0.000000f, 1.850342f },
new[] { 13.113760f, 10.197294f, -40.258976f, -2.969465f, 0.000000f, 1.852640f },
new[] { 12.525526f, 10.197294f, -39.902176f, -2.968050f, 0.000000f, 1.854907f },
new[] { 11.938206f, 10.197294f, -39.545849f, -2.966453f, 0.000000f, 1.857459f },
new[] { 11.351962f, 10.197294f, -39.190098f, -2.964650f, 0.000000f, 1.860337f },
new[] { 10.767020f, 10.197294f, -38.835106f, -2.962592f, 0.000000f, 1.863611f },
new[] { 10.183690f, 10.197294f, -38.481129f, -2.960215f, 0.000000f, 1.867385f },
new[] { 9.602149f, 10.197294f, -38.128098f, -2.957422f, 0.000000f, 1.871805f },
new[] { 9.023255f, 10.197294f, -37.777180f, -2.954136f, 0.000000f, 1.876987f },
new[] { 8.448158f, 10.197294f, -37.429573f, -2.950055f, 0.000000f, 1.883394f },
new[] { 7.879329f, 10.197294f, -37.087776f, -2.944782f, 0.000000f, 1.891628f },
new[] { 7.319548f, 10.197294f, -36.753658f, -2.937588f, 0.000000f, 1.902782f },
new[] { 6.776391f, 10.197294f, -36.434063f, -2.927629f, 0.000000f, 1.918068f },
new[] { 6.269217f, 10.197294f, -36.142403f, -2.912834f, 0.000000f, 1.940462f },
new[] { 5.846630f, 10.197294f, -35.881496f, -2.890513f, 0.000000f, 1.973559f },
new[] { 5.411777f, 10.197294f, -35.547710f, -2.874764f, 0.000000f, 1.996430f },
new[] { 4.896369f, 10.197294f, -35.184120f, -2.896978f, 0.000000f, 1.964057f },
new[] { 4.367188f, 10.197294f, -34.833569f, -2.910470f, 0.000000f, 1.944008f },
new[] { 3.807542f, 10.197294f, -34.472218f, -2.906039f, 0.000000f, 1.950624f },
new[] { 3.238266f, 10.197294f, -34.064877f, -2.846376f, 0.000000f, 2.036700f },
new[] { 2.917057f, 10.197294f, -33.455391f, -1.420109f, 0.000000f, 3.198952f },
new[] { 2.645415f, 10.197294f, -32.810249f, -1.358214f, 0.000000f, 3.225718f },
new[] { 2.373772f, 10.197294f, -32.165104f, -1.358212f, 0.000000f, 3.225718f },
new[] { 2.103753f, 10.197294f, -31.530035f, -1.358215f, 0.000000f, 3.225717f },
new[] { 1.837103f, 10.197294f, -30.882812f, -1.333248f, 0.000000f, 3.236116f },
new[] { 1.887709f, 10.197294f, -30.193766f, 0.449056f, 0.000000f, 3.471073f },
new[] { 2.034543f, 10.197294f, -29.509338f, 0.734173f, 0.000000f, 3.422132f },
new[] { 2.266320f, 10.197294f, -28.848824f, 1.158885f, 0.000000f, 3.302572f },
new[] { 2.498098f, 10.197294f, -28.188309f, 1.158885f, 0.000000f, 3.302572f },
new[] { 2.729875f, 10.197294f, -27.527794f, 1.158886f, 0.000000f, 3.302572f },
new[] { 2.992905f, 10.197294f, -26.879091f, 1.315149f, 0.000000f, 3.243514f },
new[] { 3.255934f, 10.197294f, -26.230389f, 1.315149f, 0.000000f, 3.243514f },
new[] { 3.518964f, 10.197294f, -25.581686f, 1.315149f, 0.000000f, 3.243514f },
new[] { 3.781994f, 10.197294f, -24.932983f, 1.315149f, 0.000000f, 3.243514f },
new[] { 4.045024f, 10.197294f, -24.284281f, 1.315149f, 0.000000f, 3.243514f },
new[] { 4.308054f, 10.197294f, -23.635578f, 1.315149f, 0.000000f, 3.243514f },
new[] { 4.571084f, 10.197294f, -22.986876f, 1.315149f, 0.000000f, 3.243514f },
new[] { 4.834114f, 10.197294f, -22.338173f, 1.315149f, 0.000000f, 3.243514f },
new[] { 5.097065f, 10.197294f, -21.689661f, 1.315149f, 0.000000f, 3.243514f },
new[] { 5.349040f, 10.197294f, -21.068123f, 1.315149f, 0.000000f, 3.243514f },
new[] { 5.564819f, 10.197294f, -20.535385f, 1.315191f, 0.000000f, 3.243497f },
new[] { 5.742907f, 10.197294f, -20.094234f, 1.315492f, 0.000000f, 3.243375f },
new[] { 5.844197f, 10.197294f, -19.830488f, 1.316821f, 0.000000f, 3.242836f },
new[] { 5.890507f, 10.197294f, -19.651239f, 1.327608f, 0.000000f, 3.238434f },
new[] { 5.895338f, 10.197294f, -19.433264f, 1.384180f, 0.000000f, 3.214661f },
new[] { 5.917773f, 10.197294f, -19.188416f, 1.594034f, 0.000000f, 3.115936f },
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 } };
[Test] [Test]
public void testAgent1Quality2TVTA() { public void testAgent1Quality2TVTA()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE; | CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]); addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
setMoveTarget(endPoss[0], false); setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q2TVTA.Length; i++) { for (int i = 0; i < EXPECTED_A1Q2TVTA.Length; i++)
{
crowd.update(1 / 5f, null); crowd.update(1 / 5f, null);
CrowdAgent ag = agents[2]; CrowdAgent ag = agents[2];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTA[i][0]).Within(0.001f), $"{i}"); Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTA[i][0]).Within(0.001f), $"{i}");
@ -318,14 +326,16 @@ public class Crowd4Test : AbstractCrowdTest {
} }
[Test] [Test]
public void testAgent1Quality2TVTAS() { public void testAgent1Quality2TVTAS()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE | CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE
| CrowdAgentParams.DT_CROWD_SEPARATION; | CrowdAgentParams.DT_CROWD_SEPARATION;
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]); addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
setMoveTarget(endPoss[0], false); setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q2TVTAS.Length; i++) { for (int i = 0; i < EXPECTED_A1Q2TVTAS.Length; i++)
{
crowd.update(1 / 5f, null); crowd.update(1 / 5f, null);
CrowdAgent ag = agents[2]; CrowdAgent ag = agents[2];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTAS[i][0]).Within(0.001f), $"{i}"); Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTAS[i][0]).Within(0.001f), $"{i}");
@ -338,7 +348,8 @@ public class Crowd4Test : AbstractCrowdTest {
} }
[Test] [Test]
public void testAgent1Quality2T() { public void testAgent1Quality2T()
{
int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO; int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]); addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
@ -352,13 +363,19 @@ public class Crowd4Test : AbstractCrowdTest {
crowd.update(1 / 5f, null); crowd.update(1 / 5f, null);
CrowdAgent ag = agents[2]; CrowdAgent ag = agents[2];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2T[i][0]).Within(0.00001f), $"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}"); Console.WriteLine($"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}"); Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2T[i][0]).Within(0.00001f), $"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}");
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q2T[i][1]).Within(0.00001f), $"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}"); Console.WriteLine($"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}"); Console.WriteLine($"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}");
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q2T[i][2]).Within(0.00001f), $"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}"); Console.WriteLine($"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}"); Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q2T[i][1]).Within(0.00001f), $"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}");
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q2T[i][3]).Within(0.00001f), $"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}"); Console.WriteLine($"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}"); Console.WriteLine($"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}");
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q2T[i][4]).Within(0.00001f), $"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}"); Console.WriteLine($"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}"); Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q2T[i][2]).Within(0.00001f), $"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}");
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q2T[i][5]).Within(0.00001f), $"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}"); Console.WriteLine($"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}"); Console.WriteLine($"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}");
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q2T[i][3]).Within(0.00001f), $"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}");
Console.WriteLine($"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}");
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q2T[i][4]).Within(0.00001f), $"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}");
Console.WriteLine($"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}");
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q2T[i][5]).Within(0.00001f), $"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}");
Console.WriteLine($"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}");
Thread.Sleep(1); Thread.Sleep(1);
} }
} }
} }

View File

@ -22,90 +22,95 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test; namespace DotRecast.Detour.Crowd.Test;
public class Crowd4VelocityTest : AbstractCrowdTest { public class Crowd4VelocityTest : AbstractCrowdTest
{
static readonly float[][] EXPECTED_A1Q3TVTA = { static readonly float[][] EXPECTED_A1Q3TVTA =
{
new[] { 6.101694f, 10.197294f, -17.678480f, 0.000000f, 0.000000f, 0.000000f }, new[] { 6.101694f, 10.197294f, -17.678480f, 0.000000f, 0.000000f, 0.000000f },
new[] { 6.024141f, 10.197294f, -17.589798f, -0.107331f, 0.000000f, 0.098730f }, new[] { 6.024141f, 10.197294f, -17.589798f, -0.107331f, 0.000000f, 0.098730f },
new[] { 6.004839f, 10.197294f, -17.554886f, -0.096506f, 0.000000f, 0.174561f }, new[] { 6.004839f, 10.197294f, -17.554886f, -0.096506f, 0.000000f, 0.174561f },
new[] { 5.744515f, 10.197294f, -17.309479f, -2.590961f, 0.000000f, 2.353066f }, new[] { 5.744515f, 10.197294f, -17.309479f, -2.590961f, 0.000000f, 2.353066f },
new[] { 5.253671f, 10.197294f, -16.842125f, -2.534360f, 0.000000f, 2.413922f }, new[] { 5.253671f, 10.197294f, -16.842125f, -2.534360f, 0.000000f, 2.413922f },
new[] { 4.789658f, 10.197294f, -16.318014f, -2.320063f, 0.000000f, 2.620555f }, new[] { 4.789658f, 10.197294f, -16.318014f, -2.320063f, 0.000000f, 2.620555f },
new[] { 4.407527f, 10.197294f, -15.731520f, -1.910654f, 0.000000f, 2.932474f }, new[] { 4.407527f, 10.197294f, -15.731520f, -1.910654f, 0.000000f, 2.932474f },
new[] { 4.023476f, 10.197294f, -15.146280f, -1.920256f, 0.000000f, 2.926195f }, new[] { 4.023476f, 10.197294f, -15.146280f, -1.920256f, 0.000000f, 2.926195f },
new[] { 3.645756f, 10.197294f, -14.556935f, -1.888601f, 0.000000f, 2.946725f }, new[] { 3.645756f, 10.197294f, -14.556935f, -1.888601f, 0.000000f, 2.946725f },
new[] { 3.277534f, 10.197294f, -13.961610f, -1.841108f, 0.000000f, 2.976629f }, new[] { 3.277534f, 10.197294f, -13.961610f, -1.841108f, 0.000000f, 2.976629f },
new[] { 2.924562f, 10.197294f, -13.357118f, -1.764861f, 0.000000f, 3.022460f }, new[] { 2.924562f, 10.197294f, -13.357118f, -1.764861f, 0.000000f, 3.022460f },
new[] { 2.598673f, 10.197294f, -12.737605f, -1.629447f, 0.000000f, 3.097564f }, new[] { 2.598673f, 10.197294f, -12.737605f, -1.629447f, 0.000000f, 3.097564f },
new[] { 2.330214f, 10.197294f, -12.091130f, -1.342291f, 0.000000f, 3.232376f }, new[] { 2.330214f, 10.197294f, -12.091130f, -1.342291f, 0.000000f, 3.232376f },
new[] { 2.174726f, 10.197294f, -11.408617f, -0.777438f, 0.000000f, 3.412564f }, new[] { 2.174726f, 10.197294f, -11.408617f, -0.777438f, 0.000000f, 3.412564f },
new[] { 2.040282f, 10.197294f, -10.721649f, -0.672225f, 0.000000f, 3.434838f }, new[] { 2.040282f, 10.197294f, -10.721649f, -0.672225f, 0.000000f, 3.434838f },
new[] { 1.958181f, 10.197294f, -10.026481f, -0.410503f, 0.000000f, 3.475843f }, new[] { 1.958181f, 10.197294f, -10.026481f, -0.410503f, 0.000000f, 3.475843f },
new[] { 1.653389f, 10.197294f, -9.396320f, -1.523958f, 0.000000f, 3.150802f }, new[] { 1.653389f, 10.197294f, -9.396320f, -1.523958f, 0.000000f, 3.150802f },
new[] { 1.642715f, 10.197294f, -8.696402f, -0.053371f, 0.000000f, 3.499593f }, new[] { 1.642715f, 10.197294f, -8.696402f, -0.053371f, 0.000000f, 3.499593f },
new[] { 1.937517f, 10.197294f, -8.091795f, 2.033996f, 0.000000f, 2.848308f }, new[] { 1.937517f, 10.197294f, -8.091795f, 2.033996f, 0.000000f, 2.848308f },
new[] { 2.364934f, 10.197294f, -7.537435f, 2.137084f, 0.000000f, 2.771799f }, new[] { 2.364934f, 10.197294f, -7.537435f, 2.137084f, 0.000000f, 2.771799f },
new[] { 2.802262f, 10.197294f, -6.990860f, 2.186641f, 0.000000f, 2.732875f }, new[] { 2.802262f, 10.197294f, -6.990860f, 2.186641f, 0.000000f, 2.732875f },
new[] { 3.186367f, 10.197294f, -6.759828f, 1.617082f, 0.000000f, -0.643841f }, new[] { 3.186367f, 10.197294f, -6.759828f, 1.617082f, 0.000000f, -0.643841f },
new[] { 3.460433f, 10.197294f, -6.829281f, 1.002684f, 0.000000f, -1.351205f }, new[] { 3.460433f, 10.197294f, -6.829281f, 1.002684f, 0.000000f, -1.351205f },
new[] { 3.605715f, 10.197294f, -6.794649f, 0.726412f, 0.000000f, 0.173160f }, new[] { 3.605715f, 10.197294f, -6.794649f, 0.726412f, 0.000000f, 0.173160f },
new[] { 3.796394f, 10.197294f, -6.840563f, 0.953395f, 0.000000f, -0.229571f }, new[] { 3.796394f, 10.197294f, -6.840563f, 0.953395f, 0.000000f, -0.229571f },
new[] { 3.882745f, 10.197294f, -6.956440f, 0.431757f, 0.000000f, -0.579388f }, new[] { 3.882745f, 10.197294f, -6.956440f, 0.431757f, 0.000000f, -0.579388f },
new[] { 3.983807f, 10.197294f, -7.160242f, 0.505308f, 0.000000f, -1.019009f }, new[] { 3.983807f, 10.197294f, -7.160242f, 0.505308f, 0.000000f, -1.019009f },
new[] { 4.031534f, 10.197294f, -7.358752f, 0.238635f, 0.000000f, -0.992549f }, new[] { 4.031534f, 10.197294f, -7.358752f, 0.238635f, 0.000000f, -0.992549f },
new[] { 4.081295f, 10.197294f, -7.517536f, 0.248804f, 0.000000f, -0.793922f }, new[] { 4.081295f, 10.197294f, -7.517536f, 0.248804f, 0.000000f, -0.793922f },
new[] { 4.108567f, 10.197294f, -7.630970f, 0.136363f, 0.000000f, -0.567171f }, new[] { 4.108567f, 10.197294f, -7.630970f, 0.136363f, 0.000000f, -0.567171f },
new[] { 4.092495f, 10.197294f, -7.727181f, -0.080361f, 0.000000f, -0.481056f }, new[] { 4.092495f, 10.197294f, -7.727181f, -0.080361f, 0.000000f, -0.481056f },
new[] { 4.096027f, 10.197294f, -7.807384f, 0.017662f, 0.000000f, -0.401016f }, new[] { 4.096027f, 10.197294f, -7.807384f, 0.017662f, 0.000000f, -0.401016f },
new[] { 4.131466f, 10.197294f, -7.874563f, 0.177196f, 0.000000f, -0.335895f }, new[] { 4.131466f, 10.197294f, -7.874563f, 0.177196f, 0.000000f, -0.335895f },
new[] { 4.102508f, 10.197294f, -7.917174f, -0.144795f, 0.000000f, -0.213056f }, new[] { 4.102508f, 10.197294f, -7.917174f, -0.144795f, 0.000000f, -0.213056f },
new[] { 4.073549f, 10.197294f, -7.959785f, -0.144795f, 0.000000f, -0.213056f }, new[] { 4.073549f, 10.197294f, -7.959785f, -0.144795f, 0.000000f, -0.213056f },
new[] { 4.044590f, 10.197294f, -8.002397f, -0.144795f, 0.000000f, -0.213056f }, new[] { 4.044590f, 10.197294f, -8.002397f, -0.144795f, 0.000000f, -0.213056f },
new[] { 3.983432f, 10.197294f, -8.032723f, -0.305791f, 0.000000f, -0.151636f }, new[] { 3.983432f, 10.197294f, -8.032723f, -0.305791f, 0.000000f, -0.151636f },
new[] { 3.948404f, 10.197294f, -8.050093f, -0.175139f, 0.000000f, -0.086848f }, new[] { 3.948404f, 10.197294f, -8.050093f, -0.175139f, 0.000000f, -0.086848f },
new[] { 3.935988f, 10.197294f, -8.078673f, -0.062080f, 0.000000f, -0.142903f }, new[] { 3.935988f, 10.197294f, -8.078673f, -0.062080f, 0.000000f, -0.142903f },
new[] { 3.943177f, 10.197294f, -8.091246f, 0.035943f, 0.000000f, -0.062864f }, new[] { 3.943177f, 10.197294f, -8.091246f, 0.035943f, 0.000000f, -0.062864f },
new[] { 3.950365f, 10.197294f, -8.103818f, 0.035943f, 0.000000f, -0.062864f }, new[] { 3.950365f, 10.197294f, -8.103818f, 0.035943f, 0.000000f, -0.062864f },
new[] { 3.957554f, 10.197294f, -8.116390f, 0.035943f, 0.000000f, -0.062864f }, new[] { 3.957554f, 10.197294f, -8.116390f, 0.035943f, 0.000000f, -0.062864f },
new[] { 3.964742f, 10.197294f, -8.128963f, 0.035943f, 0.000000f, -0.062864f }, new[] { 3.964742f, 10.197294f, -8.128963f, 0.035943f, 0.000000f, -0.062864f },
new[] { 4.003838f, 10.197294f, -8.128510f, 0.195477f, 0.000000f, 0.002258f }, new[] { 4.003838f, 10.197294f, -8.128510f, 0.195477f, 0.000000f, 0.002258f },
new[] { 4.042933f, 10.197294f, -8.128058f, 0.195477f, 0.000000f, 0.002258f }, new[] { 4.042933f, 10.197294f, -8.128058f, 0.195477f, 0.000000f, 0.002258f },
new[] { 4.082028f, 10.197294f, -8.127606f, 0.195477f, 0.000000f, 0.002258f }, new[] { 4.082028f, 10.197294f, -8.127606f, 0.195477f, 0.000000f, 0.002258f },
new[] { 4.121124f, 10.197294f, -8.127154f, 0.195477f, 0.000000f, 0.002258f }, new[] { 4.121124f, 10.197294f, -8.127154f, 0.195477f, 0.000000f, 0.002258f },
new[] { 4.160219f, 10.197294f, -8.126702f, 0.195477f, 0.000000f, 0.002258f }, new[] { 4.160219f, 10.197294f, -8.126702f, 0.195477f, 0.000000f, 0.002258f },
new[] { 4.199315f, 10.197294f, -8.126250f, 0.195477f, 0.000000f, 0.002258f }, new[] { 4.199315f, 10.197294f, -8.126250f, 0.195477f, 0.000000f, 0.002258f },
new[] { 4.206211f, 10.197294f, -8.113515f, 0.034481f, 0.000000f, 0.063677f }, new[] { 4.206211f, 10.197294f, -8.113515f, 0.034481f, 0.000000f, 0.063677f },
new[] { 4.213107f, 10.197294f, -8.100780f, 0.034481f, 0.000000f, 0.063677f }, new[] { 4.213107f, 10.197294f, -8.100780f, 0.034481f, 0.000000f, 0.063677f },
new[] { 4.230340f, 10.197294f, -8.092234f, 0.086165f, 0.000000f, 0.042728f }, new[] { 4.230340f, 10.197294f, -8.092234f, 0.086165f, 0.000000f, 0.042728f },
new[] { 4.247572f, 10.197294f, -8.083688f, 0.086165f, 0.000000f, 0.042728f }, new[] { 4.247572f, 10.197294f, -8.083688f, 0.086165f, 0.000000f, 0.042728f },
new[] { 4.246885f, 10.197294f, -8.098154f, -0.003438f, 0.000000f, -0.072332f }, new[] { 4.246885f, 10.197294f, -8.098154f, -0.003438f, 0.000000f, -0.072332f },
new[] { 4.264118f, 10.197294f, -8.089608f, 0.086165f, 0.000000f, 0.042728f }, new[] { 4.264118f, 10.197294f, -8.089608f, 0.086165f, 0.000000f, 0.042728f },
new[] { 4.281351f, 10.197294f, -8.081062f, 0.086165f, 0.000000f, 0.042728f }, new[] { 4.281351f, 10.197294f, -8.081062f, 0.086165f, 0.000000f, 0.042728f },
new[] { 4.280663f, 10.197294f, -8.095529f, -0.003438f, 0.000000f, -0.072332f }, new[] { 4.280663f, 10.197294f, -8.095529f, -0.003438f, 0.000000f, -0.072332f },
new[] { 4.297896f, 10.197294f, -8.086983f, 0.086165f, 0.000000f, 0.042728f }, new[] { 4.297896f, 10.197294f, -8.086983f, 0.086165f, 0.000000f, 0.042728f },
new[] { 4.315129f, 10.197294f, -8.078437f, 0.086165f, 0.000000f, 0.042728f }, new[] { 4.315129f, 10.197294f, -8.078437f, 0.086165f, 0.000000f, 0.042728f },
new[] { 4.332362f, 10.197294f, -8.069891f, 0.086165f, 0.000000f, 0.042728f }, new[] { 4.332362f, 10.197294f, -8.069891f, 0.086165f, 0.000000f, 0.042728f },
new[] { 4.322824f, 10.197294f, -8.046370f, -0.047688f, 0.000000f, 0.117607f }, new[] { 4.322824f, 10.197294f, -8.046370f, -0.047688f, 0.000000f, 0.117607f },
new[] { 4.322136f, 10.197294f, -8.060836f, -0.003438f, 0.000000f, -0.072332f }, new[] { 4.322136f, 10.197294f, -8.060836f, -0.003438f, 0.000000f, -0.072332f },
new[] { 4.321449f, 10.197294f, -8.075302f, -0.003438f, 0.000000f, -0.072332f }, new[] { 4.321449f, 10.197294f, -8.075302f, -0.003438f, 0.000000f, -0.072332f },
new[] { 4.320761f, 10.197294f, -8.089768f, -0.003438f, 0.000000f, -0.072332f }, new[] { 4.320761f, 10.197294f, -8.089768f, -0.003438f, 0.000000f, -0.072332f },
new[] { 4.337994f, 10.197294f, -8.081223f, 0.086165f, 0.000000f, 0.042728f }, new[] { 4.337994f, 10.197294f, -8.081223f, 0.086165f, 0.000000f, 0.042728f },
new[] { 4.328456f, 10.197294f, -8.057701f, -0.047688f, 0.000000f, 0.117607f }, new[] { 4.328456f, 10.197294f, -8.057701f, -0.047688f, 0.000000f, 0.117607f },
new[] { 4.327769f, 10.197294f, -8.072167f, -0.003438f, 0.000000f, -0.072332f } }; new[] { 4.327769f, 10.197294f, -8.072167f, -0.003438f, 0.000000f, -0.072332f }
};
[Test] [Test]
public void testAgent1Quality3TVTA() { public void testAgent1Quality3TVTA()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE; | CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(2, 0.3f, updateFlags, 3, endPoss[0]); addAgentGrid(2, 0.3f, updateFlags, 3, endPoss[0]);
setMoveTarget(endPoss[4], false); setMoveTarget(endPoss[4], false);
for (int i = 0; i < EXPECTED_A1Q3TVTA.Length; i++) { for (int i = 0; i < EXPECTED_A1Q3TVTA.Length; i++)
{
crowd.update(1 / 5f, null); crowd.update(1 / 5f, null);
if (i == 20) { if (i == 20)
{
setMoveTarget(startPoss[2], true); setMoveTarget(startPoss[2], true);
} }
CrowdAgent ag = agents[1]; CrowdAgent ag = agents[1];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q3TVTA[i][0]).Within(0.001f)); Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q3TVTA[i][0]).Within(0.001f));
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q3TVTA[i][1]).Within(0.001f)); Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q3TVTA[i][1]).Within(0.001f));
@ -115,5 +120,4 @@ public class Crowd4VelocityTest : AbstractCrowdTest {
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q3TVTA[i][5]).Within(0.001f)); Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q3TVTA[i][5]).Within(0.001f));
} }
} }
}
}

View File

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

View File

@ -22,18 +22,20 @@ using NUnit.Framework;
namespace DotRecast.Detour.Crowd.Test; namespace DotRecast.Detour.Crowd.Test;
public class PathCorridorTest { public class PathCorridorTest
{
private readonly PathCorridor corridor = new PathCorridor(); private readonly PathCorridor corridor = new PathCorridor();
private readonly QueryFilter filter = new DefaultQueryFilter(); private readonly QueryFilter filter = new DefaultQueryFilter();
[SetUp] [SetUp]
public void setUp() { public void setUp()
corridor.reset(0, new float[] {10,20,30}); {
corridor.reset(0, new float[] { 10, 20, 30 });
} }
[Test] [Test]
public void shouldKeepOriginalPathInFindCornersWhenNothingCanBePruned() { public void shouldKeepOriginalPathInFindCornersWhenNothingCanBePruned()
{
List<StraightPathItem> straightPath = new(); List<StraightPathItem> straightPath = new();
straightPath.Add(new StraightPathItem(new float[] { 11, 20, 30.00001f }, 0, 0)); straightPath.Add(new StraightPathItem(new float[] { 11, 20, 30.00001f }, 0, 0));
straightPath.Add(new StraightPathItem(new float[] { 12, 20, 30.00002f }, 0, 0)); straightPath.Add(new StraightPathItem(new float[] { 12, 20, 30.00002f }, 0, 0));
@ -42,10 +44,10 @@ public class PathCorridorTest {
Result<List<StraightPathItem>> result = Results.success(straightPath); Result<List<StraightPathItem>> result = Results.success(straightPath);
var mockQuery = new Mock<NavMeshQuery>(It.IsAny<NavMesh>()); var mockQuery = new Mock<NavMeshQuery>(It.IsAny<NavMesh>());
mockQuery.Setup(q => q.findStraightPath( mockQuery.Setup(q => q.findStraightPath(
It.IsAny<float[]>(), It.IsAny<float[]>(),
It.IsAny<float[]>(), It.IsAny<float[]>(),
It.IsAny<List<long>>(), It.IsAny<List<long>>(),
It.IsAny<int>(), It.IsAny<int>(),
It.IsAny<int>()) It.IsAny<int>())
).Returns(result); ).Returns(result);
List<StraightPathItem> path = corridor.findCorners(int.MaxValue, mockQuery.Object, filter); List<StraightPathItem> path = corridor.findCorners(int.MaxValue, mockQuery.Object, filter);
@ -54,7 +56,8 @@ public class PathCorridorTest {
} }
[Test] [Test]
public void shouldPrunePathInFindCorners() { public void shouldPrunePathInFindCorners()
{
List<StraightPathItem> straightPath = new(); List<StraightPathItem> straightPath = new();
straightPath.Add(new StraightPathItem(new float[] { 10, 20, 30.00001f }, 0, 0)); // too close straightPath.Add(new StraightPathItem(new float[] { 10, 20, 30.00001f }, 0, 0)); // too close
straightPath.Add(new StraightPathItem(new float[] { 10, 20, 30.00002f }, 0, 0)); // too close straightPath.Add(new StraightPathItem(new float[] { 10, 20, 30.00002f }, 0, 0)); // too close
@ -76,5 +79,4 @@ public class PathCorridorTest {
Assert.That(path.Count, Is.EqualTo(2)); Assert.That(path.Count, Is.EqualTo(2));
Assert.That(path, Is.EqualTo(new List<StraightPathItem> { straightPath[2], straightPath[3] })); Assert.That(path, Is.EqualTo(new List<StraightPathItem> { straightPath[2], straightPath[3] }));
} }
}
}

View File

@ -22,8 +22,8 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Detour.Crowd.Test; namespace DotRecast.Detour.Crowd.Test;
public class RecastTestMeshBuilder { public class RecastTestMeshBuilder
{
private readonly MeshData meshData; private readonly MeshData meshData;
public const float m_cellSize = 0.3f; public const float m_cellSize = 0.3f;
public const float m_cellHeight = 0.2f; public const float m_cellHeight = 0.2f;
@ -40,26 +40,29 @@ public class RecastTestMeshBuilder {
public const float m_detailSampleMaxError = 1.0f; public const float m_detailSampleMaxError = 1.0f;
public RecastTestMeshBuilder() : this(ObjImporter.load(Loader.ToBytes("dungeon.obj")), public RecastTestMeshBuilder() : this(ObjImporter.load(Loader.ToBytes("dungeon.obj")),
PartitionType.WATERSHED, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_agentMaxSlope, PartitionType.WATERSHED, 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_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, m_detailSampleDist,
m_detailSampleMaxError) m_detailSampleMaxError)
{ {
} }
public RecastTestMeshBuilder(InputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize, public RecastTestMeshBuilder(InputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize,
float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope, float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope,
int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly, int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly,
float m_detailSampleDist, float m_detailSampleMaxError) { float m_detailSampleDist, float m_detailSampleMaxError)
{
RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius, RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius,
m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError, m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND); m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
RecastBuilderConfig bcfg = new RecastBuilderConfig(cfg, m_geom.getMeshBoundsMin(), m_geom.getMeshBoundsMax()); RecastBuilderConfig bcfg = new RecastBuilderConfig(cfg, m_geom.getMeshBoundsMin(), m_geom.getMeshBoundsMax());
RecastBuilder rcBuilder = new RecastBuilder(); RecastBuilder rcBuilder = new RecastBuilder();
RecastBuilderResult rcResult = rcBuilder.build(m_geom, bcfg); RecastBuilderResult rcResult = rcBuilder.build(m_geom, bcfg);
PolyMesh m_pmesh = rcResult.getMesh(); PolyMesh m_pmesh = rcResult.getMesh();
for (int i = 0; i < m_pmesh.npolys; ++i) { for (int i = 0; i < m_pmesh.npolys; ++i)
{
m_pmesh.flags[i] = 1; m_pmesh.flags[i] = 1;
} }
PolyMeshDetail m_dmesh = rcResult.getMeshDetail(); PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
NavMeshDataCreateParams option = new NavMeshDataCreateParams(); NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = m_pmesh.verts; option.verts = m_pmesh.verts;
@ -104,7 +107,8 @@ public class RecastTestMeshBuilder {
meshData = NavMeshBuilder.createNavMeshData(option); meshData = NavMeshBuilder.createNavMeshData(option);
} }
public MeshData getMeshData() { public MeshData getMeshData()
{
return meshData; return meshData;
} }
} }

View File

@ -20,8 +20,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Crowd.Test; namespace DotRecast.Detour.Crowd.Test;
public class SampleAreaModifications { public class SampleAreaModifications
{
public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07; public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1; public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2; public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
@ -31,17 +31,22 @@ public class SampleAreaModifications {
public const int SAMPLE_POLYAREA_TYPE_JUMP = 0x6; public const int SAMPLE_POLYAREA_TYPE_JUMP = 0x6;
public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND, public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER, public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD, public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS, public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR, public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR,
SAMPLE_POLYAREA_TYPE_DOOR); SAMPLE_POLYAREA_TYPE_DOOR);
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP, public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP,
SAMPLE_POLYAREA_TYPE_JUMP); SAMPLE_POLYAREA_TYPE_JUMP);
public const int SAMPLE_POLYFLAGS_WALK = 0x01; // Ability to walk (ground, grass, road) public const int SAMPLE_POLYFLAGS_WALK = 0x01; // Ability to walk (ground, grass, road)
public const int SAMPLE_POLYFLAGS_SWIM = 0x02; // Ability to swim (water). public const int SAMPLE_POLYFLAGS_SWIM = 0x02; // Ability to swim (water).
@ -49,4 +54,4 @@ public class SampleAreaModifications {
public const int SAMPLE_POLYFLAGS_JUMP = 0x08; // Ability to jump. public const int SAMPLE_POLYFLAGS_JUMP = 0x08; // Ability to jump.
public const int SAMPLE_POLYFLAGS_DISABLED = 0x10; // Disabled polygon public const int SAMPLE_POLYFLAGS_DISABLED = 0x10; // Disabled polygon
public const int SAMPLE_POLYFLAGS_ALL = 0xffff; // All abilities. public const int SAMPLE_POLYFLAGS_ALL = 0xffff; // All abilities.
} }

View File

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

View File

@ -8,8 +8,8 @@ using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test; namespace DotRecast.Detour.Dynamic.Test;
public class DynamicNavMeshTest { public class DynamicNavMeshTest
{
private static readonly float[] START_POS = new float[] { 70.87453f, 0.0010070801f, 86.69021f }; private static readonly float[] START_POS = new float[] { 70.87453f, 0.0010070801f, 86.69021f };
private static readonly float[] END_POS = new float[] { -50.22061f, 0.0010070801f, -70.761444f }; private static readonly float[] END_POS = new float[] { -50.22061f, 0.0010070801f, -70.761444f };
private static readonly float[] EXTENT = new float[] { 0.1f, 0.1f, 0.1f }; private static readonly float[] EXTENT = new float[] { 0.1f, 0.1f, 0.1f };
@ -17,11 +17,12 @@ public class DynamicNavMeshTest {
[Test] [Test]
public void e2eTest() { public void e2eTest()
{
byte[] bytes = Loader.ToBytes("test_tiles.voxels"); byte[] bytes = Loader.ToBytes("test_tiles.voxels");
using var ms = new MemoryStream(bytes); using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms); using var bis = new BinaryReader(ms);
// load voxels from file // load voxels from file
VoxelFileReader reader = new VoxelFileReader(); VoxelFileReader reader = new VoxelFileReader();
VoxelFile f = reader.read(bis); VoxelFile f = reader.read(bis);
@ -38,7 +39,7 @@ public class DynamicNavMeshTest {
FindNearestPolyResult start = query.findNearestPoly(START_POS, EXTENT, filter).result; FindNearestPolyResult start = query.findNearestPoly(START_POS, EXTENT, filter).result;
FindNearestPolyResult end = query.findNearestPoly(END_POS, EXTENT, filter).result; FindNearestPolyResult end = query.findNearestPoly(END_POS, EXTENT, filter).result;
List<long> path = query.findPath(start.getNearestRef(), end.getNearestRef(), start.getNearestPos(), List<long> path = query.findPath(start.getNearestRef(), end.getNearestRef(), start.getNearestPos(),
end.getNearestPos(), filter, NavMeshQuery.DT_FINDPATH_ANY_ANGLE, float.MaxValue).result; end.getNearestPos(), filter, NavMeshQuery.DT_FINDPATH_ANY_ANGLE, float.MaxValue).result;
// check path length without any obstacles // check path length without any obstacles
Assert.That(path.Count, Is.EqualTo(16)); Assert.That(path.Count, Is.EqualTo(16));
// place obstacle // place obstacle
@ -54,7 +55,7 @@ public class DynamicNavMeshTest {
start = query.findNearestPoly(START_POS, EXTENT, filter).result; start = query.findNearestPoly(START_POS, EXTENT, filter).result;
end = query.findNearestPoly(END_POS, EXTENT, filter).result; end = query.findNearestPoly(END_POS, EXTENT, filter).result;
path = query.findPath(start.getNearestRef(), end.getNearestRef(), start.getNearestPos(), end.getNearestPos(), filter, path = query.findPath(start.getNearestRef(), end.getNearestRef(), start.getNearestPos(), end.getNearestPos(), filter,
NavMeshQuery.DT_FINDPATH_ANY_ANGLE, float.MaxValue).result; NavMeshQuery.DT_FINDPATH_ANY_ANGLE, float.MaxValue).result;
// check path length with obstacles // check path length with obstacles
Assert.That(path.Count, Is.EqualTo(19)); Assert.That(path.Count, Is.EqualTo(19));
// remove obstacle // remove obstacle
@ -69,8 +70,8 @@ public class DynamicNavMeshTest {
start = query.findNearestPoly(START_POS, EXTENT, filter).result; start = query.findNearestPoly(START_POS, EXTENT, filter).result;
end = query.findNearestPoly(END_POS, EXTENT, filter).result; end = query.findNearestPoly(END_POS, EXTENT, filter).result;
path = query.findPath(start.getNearestRef(), end.getNearestRef(), start.getNearestPos(), end.getNearestPos(), filter, path = query.findPath(start.getNearestRef(), end.getNearestRef(), start.getNearestPos(), end.getNearestPos(), filter,
NavMeshQuery.DT_FINDPATH_ANY_ANGLE, float.MaxValue).result; NavMeshQuery.DT_FINDPATH_ANY_ANGLE, float.MaxValue).result;
// path length should be back to the initial value // path length should be back to the initial value
Assert.That(path.Count, Is.EqualTo(16)); Assert.That(path.Count, Is.EqualTo(16));
} }
} }

View File

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

View File

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

View File

@ -20,8 +20,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Test; namespace DotRecast.Detour.Dynamic.Test;
public class SampleAreaModifications { public class SampleAreaModifications
{
public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07; public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1; public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2; public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
@ -31,17 +31,22 @@ public class SampleAreaModifications {
public const int SAMPLE_POLYAREA_TYPE_JUMP = 0x6; public const int SAMPLE_POLYAREA_TYPE_JUMP = 0x6;
public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND, public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER, public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD, public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS, public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR, public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR,
SAMPLE_POLYAREA_TYPE_DOOR); SAMPLE_POLYAREA_TYPE_DOOR);
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP, public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP,
SAMPLE_POLYAREA_TYPE_JUMP); SAMPLE_POLYAREA_TYPE_JUMP);
public const int SAMPLE_POLYFLAGS_WALK = 0x01; // Ability to walk (ground, grass, road) public const int SAMPLE_POLYFLAGS_WALK = 0x01; // Ability to walk (ground, grass, road)
public const int SAMPLE_POLYFLAGS_SWIM = 0x02; // Ability to swim (water). public const int SAMPLE_POLYFLAGS_SWIM = 0x02; // Ability to swim (water).
@ -49,4 +54,4 @@ public class SampleAreaModifications {
public const int SAMPLE_POLYFLAGS_JUMP = 0x08; // Ability to jump. public const int SAMPLE_POLYFLAGS_JUMP = 0x08; // Ability to jump.
public const int SAMPLE_POLYFLAGS_DISABLED = 0x10; // Disabled polygon public const int SAMPLE_POLYFLAGS_DISABLED = 0x10; // Disabled polygon
public const int SAMPLE_POLYFLAGS_ALL = 0xffff; // All abilities. public const int SAMPLE_POLYFLAGS_ALL = 0xffff; // All abilities.
} }

View File

@ -28,18 +28,18 @@ using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test; namespace DotRecast.Detour.Dynamic.Test;
public class VoxelQueryTest { public class VoxelQueryTest
{
private const int TILE_WIDTH = 100; private const int TILE_WIDTH = 100;
private const int TILE_DEPTH = 90; private const int TILE_DEPTH = 90;
private static readonly float[] ORIGIN = new float[] { 50, 10, 40 }; private static readonly float[] ORIGIN = new float[] { 50, 10, 40 };
[Test] [Test]
public void shouldTraverseTiles() public void shouldTraverseTiles()
{ {
var hfProvider = new Mock<Func<int, int, Heightfield>>(); var hfProvider = new Mock<Func<int, int, Heightfield>>();
// Given // Given
List<int> captorX = new(); List<int> captorX = new();
List<int> captorZ = new(); List<int> captorZ = new();
@ -52,21 +52,22 @@ public class VoxelQueryTest {
captorX.Add(x); captorX.Add(x);
captorZ.Add(z); captorZ.Add(z);
}); });
VoxelQuery query = new VoxelQuery(ORIGIN, TILE_WIDTH, TILE_DEPTH, hfProvider.Object); VoxelQuery query = new VoxelQuery(ORIGIN, TILE_WIDTH, TILE_DEPTH, hfProvider.Object);
float[] start = { 120, 10, 365 }; float[] start = { 120, 10, 365 };
float[] end = { 320, 10, 57 }; float[] end = { 320, 10, 57 };
// When // When
query.raycast(start, end); query.raycast(start, end);
// Then // Then
hfProvider.Verify(mock => mock.Invoke(It.IsAny<int>(), It.IsAny<int>()), Times.Exactly(6)); hfProvider.Verify(mock => mock.Invoke(It.IsAny<int>(), It.IsAny<int>()), Times.Exactly(6));
Assert.That(captorX, Is.EqualTo(new[] { 0, 1, 1, 1, 2, 2})); Assert.That(captorX, Is.EqualTo(new[] { 0, 1, 1, 1, 2, 2 }));
Assert.That(captorZ, Is.EqualTo(new[] { 3, 3, 2, 1, 1, 0})); Assert.That(captorZ, Is.EqualTo(new[] { 3, 3, 2, 1, 1, 0 }));
} }
[Test] [Test]
public void shouldHandleRaycastWithoutObstacles() { public void shouldHandleRaycastWithoutObstacles()
{
DynamicNavMesh mesh = createDynaMesh(); DynamicNavMesh mesh = createDynaMesh();
VoxelQuery query = mesh.voxelQuery(); VoxelQuery query = mesh.voxelQuery();
float[] start = { 7.4f, 0.5f, -64.8f }; float[] start = { 7.4f, 0.5f, -64.8f };
@ -76,7 +77,8 @@ public class VoxelQueryTest {
} }
[Test] [Test]
public void shouldHandleRaycastWithObstacles() { public void shouldHandleRaycastWithObstacles()
{
DynamicNavMesh mesh = createDynaMesh(); DynamicNavMesh mesh = createDynaMesh();
VoxelQuery query = mesh.voxelQuery(); VoxelQuery query = mesh.voxelQuery();
float[] start = { 32.3f, 0.5f, 47.9f }; float[] start = { 32.3f, 0.5f, 47.9f };
@ -86,11 +88,12 @@ public class VoxelQueryTest {
Assert.That(hit.Value, Is.EqualTo(0.5263836f).Within(1e-7f)); Assert.That(hit.Value, Is.EqualTo(0.5263836f).Within(1e-7f));
} }
private DynamicNavMesh createDynaMesh() { private DynamicNavMesh createDynaMesh()
{
var bytes = Loader.ToBytes("test_tiles.voxels"); var bytes = Loader.ToBytes("test_tiles.voxels");
using var ms = new MemoryStream(bytes); using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms); using var bis = new BinaryReader(ms);
// load voxels from file // load voxels from file
VoxelFileReader reader = new VoxelFileReader(); VoxelFileReader reader = new VoxelFileReader();
VoxelFile f = reader.read(bis); VoxelFile f = reader.read(bis);
@ -102,4 +105,4 @@ public class VoxelQueryTest {
var _ = future.Result; var _ = future.Result;
return mesh; return mesh;
} }
} }

View File

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

View File

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

View File

@ -44,9 +44,9 @@ public abstract class AbstractDetourTest
protected static readonly float[][] endPoss = protected static readonly float[][] endPoss =
{ {
new[] { 6.4576626f, 10.197294f, -18.33406f }, new[] { 6.4576626f, 10.197294f, -18.33406f },
new[] { -5.8023443f, 0.19729415f, 3.008419f }, new[] { -5.8023443f, 0.19729415f, 3.008419f },
new[] { 38.423977f, 10.197294f, -0.116066754f }, new[] { 38.423977f, 10.197294f, -0.116066754f },
new[] { 0.8635526f, 10.197294f, -10.31032f }, new[] { 0.8635526f, 10.197294f, -10.31032f },
new[] { 18.784092f, 10.197294f, 3.0543678f } new[] { 18.784092f, 10.197294f, 3.0543678f }
}; };

View File

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

View File

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

View File

@ -37,8 +37,8 @@ public class FindDistanceToWallTest : AbstractDetourTest
{ {
new[] { -0.955779f, 0.0f, -0.29408592f }, new[] { -0.955779f, 0.0f, -0.29408592f },
new[] { 0.0f, 0.0f, 1.0f }, new[] { 0.0f, 0.0f, 1.0f },
new[] { 0.97014254f, 0.0f, 0.24253564f }, new[] { 0.97014254f, 0.0f, 0.24253564f },
new[] { -1.0f, 0.0f, 0.0f }, new[] { -1.0f, 0.0f, 0.0f },
new[] { 1.0f, 0.0f, 0.0f } new[] { 1.0f, 0.0f, 0.0f }
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,12 +24,13 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test.Io; namespace DotRecast.Detour.Test.Io;
public class MeshSetReaderTest { public class MeshSetReaderTest
{
private readonly MeshSetReader reader = new MeshSetReader(); private readonly MeshSetReader reader = new MeshSetReader();
[Test] [Test]
public void testNavmesh() { public void testNavmesh()
{
byte[] @is = Loader.ToBytes("all_tiles_navmesh.bin"); byte[] @is = Loader.ToBytes("all_tiles_navmesh.bin");
using var ms = new MemoryStream(@is); using var ms = new MemoryStream(@is);
using var bris = new BinaryReader(ms); using var bris = new BinaryReader(ms);
@ -56,7 +57,8 @@ public class MeshSetReaderTest {
} }
[Test] [Test]
public void testDungeon() { public void testDungeon()
{
byte[] @is = Loader.ToBytes("dungeon_all_tiles_navmesh.bin"); byte[] @is = Loader.ToBytes("dungeon_all_tiles_navmesh.bin");
using var ms = new MemoryStream(@is); using var ms = new MemoryStream(@is);
using var bris = new BinaryReader(ms); using var bris = new BinaryReader(ms);
@ -84,7 +86,8 @@ public class MeshSetReaderTest {
} }
[Test] [Test]
public void testDungeon32Bit() { public void testDungeon32Bit()
{
byte[] @is = Loader.ToBytes("dungeon_all_tiles_navmesh_32bit.bin"); byte[] @is = Loader.ToBytes("dungeon_all_tiles_navmesh_32bit.bin");
using var ms = new MemoryStream(@is); using var ms = new MemoryStream(@is);
using var bris = new BinaryReader(ms); using var bris = new BinaryReader(ms);
@ -110,4 +113,4 @@ public class MeshSetReaderTest {
Assert.That(tiles[0].data.polys.Length, Is.EqualTo(5)); Assert.That(tiles[0].data.polys.Length, Is.EqualTo(5));
Assert.That(tiles[0].data.verts.Length, Is.EqualTo(17 * 3)); Assert.That(tiles[0].data.verts.Length, Is.EqualTo(17 * 3));
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,8 +22,8 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
public class RecastTestMeshBuilder { public class RecastTestMeshBuilder
{
private readonly MeshData meshData; private readonly MeshData meshData;
private const float m_cellSize = 0.3f; private const float m_cellSize = 0.3f;
private const float m_cellHeight = 0.2f; private const float m_cellHeight = 0.2f;
@ -41,26 +41,29 @@ public class RecastTestMeshBuilder {
public RecastTestMeshBuilder() : public RecastTestMeshBuilder() :
this(ObjImporter.load(Loader.ToBytes("dungeon.obj")), this(ObjImporter.load(Loader.ToBytes("dungeon.obj")),
PartitionType.WATERSHED, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_agentMaxSlope, PartitionType.WATERSHED, 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_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, m_detailSampleDist,
m_detailSampleMaxError) m_detailSampleMaxError)
{ {
} }
public RecastTestMeshBuilder(InputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize, public RecastTestMeshBuilder(InputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize,
float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope, float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope,
int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly, int m_regionMinSize, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly,
float m_detailSampleDist, float m_detailSampleMaxError) { float m_detailSampleDist, float m_detailSampleMaxError)
{
RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius, RecastConfig cfg = new RecastConfig(m_partitionType, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius,
m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError, m_agentMaxClimb, m_agentMaxSlope, m_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError,
m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND); m_vertsPerPoly, m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
RecastBuilderConfig bcfg = new RecastBuilderConfig(cfg, m_geom.getMeshBoundsMin(), m_geom.getMeshBoundsMax()); RecastBuilderConfig bcfg = new RecastBuilderConfig(cfg, m_geom.getMeshBoundsMin(), m_geom.getMeshBoundsMax());
RecastBuilder rcBuilder = new RecastBuilder(); RecastBuilder rcBuilder = new RecastBuilder();
RecastBuilderResult rcResult = rcBuilder.build(m_geom, bcfg); RecastBuilderResult rcResult = rcBuilder.build(m_geom, bcfg);
PolyMesh m_pmesh = rcResult.getMesh(); PolyMesh m_pmesh = rcResult.getMesh();
for (int i = 0; i < m_pmesh.npolys; ++i) { for (int i = 0; i < m_pmesh.npolys; ++i)
{
m_pmesh.flags[i] = 1; m_pmesh.flags[i] = 1;
} }
PolyMeshDetail m_dmesh = rcResult.getMeshDetail(); PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
NavMeshDataCreateParams option = new NavMeshDataCreateParams(); NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = m_pmesh.verts; option.verts = m_pmesh.verts;
@ -105,7 +108,8 @@ public class RecastTestMeshBuilder {
meshData = NavMeshBuilder.createNavMeshData(option); meshData = NavMeshBuilder.createNavMeshData(option);
} }
public MeshData getMeshData() { public MeshData getMeshData()
{
return meshData; return meshData;
} }
} }

View File

@ -20,8 +20,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
public class SampleAreaModifications { public class SampleAreaModifications
{
public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07; public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1; public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2; public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
@ -31,17 +31,22 @@ public class SampleAreaModifications {
public const int SAMPLE_POLYAREA_TYPE_JUMP = 0x6; public const int SAMPLE_POLYAREA_TYPE_JUMP = 0x6;
public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND, public static AreaModification SAMPLE_AREAMOD_GROUND = new AreaModification(SAMPLE_POLYAREA_TYPE_GROUND,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER, public static AreaModification SAMPLE_AREAMOD_WATER = new AreaModification(SAMPLE_POLYAREA_TYPE_WATER,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD, public static AreaModification SAMPLE_AREAMOD_ROAD = new AreaModification(SAMPLE_POLYAREA_TYPE_ROAD,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS, public static AreaModification SAMPLE_AREAMOD_GRASS = new AreaModification(SAMPLE_POLYAREA_TYPE_GRASS,
SAMPLE_POLYAREA_TYPE_MASK); SAMPLE_POLYAREA_TYPE_MASK);
public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR, public static AreaModification SAMPLE_AREAMOD_DOOR = new AreaModification(SAMPLE_POLYAREA_TYPE_DOOR,
SAMPLE_POLYAREA_TYPE_DOOR); SAMPLE_POLYAREA_TYPE_DOOR);
public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP, public static AreaModification SAMPLE_AREAMOD_JUMP = new AreaModification(SAMPLE_POLYAREA_TYPE_JUMP,
SAMPLE_POLYAREA_TYPE_JUMP); SAMPLE_POLYAREA_TYPE_JUMP);
public const int SAMPLE_POLYFLAGS_WALK = 0x01; // Ability to walk (ground, grass, road) public const int SAMPLE_POLYFLAGS_WALK = 0x01; // Ability to walk (ground, grass, road)
public const int SAMPLE_POLYFLAGS_SWIM = 0x02; // Ability to swim (water). public const int SAMPLE_POLYFLAGS_SWIM = 0x02; // Ability to swim (water).
@ -49,4 +54,4 @@ public class SampleAreaModifications {
public const int SAMPLE_POLYFLAGS_JUMP = 0x08; // Ability to jump. public const int SAMPLE_POLYFLAGS_JUMP = 0x08; // Ability to jump.
public const int SAMPLE_POLYFLAGS_DISABLED = 0x10; // Disabled polygon public const int SAMPLE_POLYFLAGS_DISABLED = 0x10; // Disabled polygon
public const int SAMPLE_POLYFLAGS_ALL = 0xffff; // All abilities. public const int SAMPLE_POLYFLAGS_ALL = 0xffff; // All abilities.
} }

View File

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

View File

@ -20,13 +20,12 @@ using System.Collections.Generic;
using DotRecast.Core; using DotRecast.Core;
using DotRecast.Recast; using DotRecast.Recast;
using DotRecast.Recast.Geom; using DotRecast.Recast.Geom;
using static DotRecast.Recast.RecastVectors; using static DotRecast.Recast.RecastVectors;
namespace DotRecast.Detour.Test; namespace DotRecast.Detour.Test;
public class TestTiledNavMeshBuilder { public class TestTiledNavMeshBuilder
{
private readonly NavMesh navMesh; private readonly NavMesh navMesh;
private const float m_cellSize = 0.3f; private const float m_cellSize = 0.3f;
private const float m_cellHeight = 0.2f; private const float m_cellHeight = 0.2f;
@ -47,17 +46,17 @@ public class TestTiledNavMeshBuilder {
public TestTiledNavMeshBuilder() : public TestTiledNavMeshBuilder() :
this(ObjImporter.load(Loader.ToBytes("dungeon.obj")), this(ObjImporter.load(Loader.ToBytes("dungeon.obj")),
PartitionType.WATERSHED, m_cellSize, m_cellHeight, m_agentHeight, m_agentRadius, m_agentMaxClimb, m_agentMaxSlope, PartitionType.WATERSHED, 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_regionMinSize, m_regionMergeSize, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, m_detailSampleDist,
m_detailSampleMaxError, m_tileSize) m_detailSampleMaxError, m_tileSize)
{ {
} }
public TestTiledNavMeshBuilder(InputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize, float m_cellHeight, public TestTiledNavMeshBuilder(InputGeomProvider m_geom, PartitionType m_partitionType, float m_cellSize, float m_cellHeight,
float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope, int m_regionMinSize, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb, float m_agentMaxSlope, int m_regionMinSize,
int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly, float m_detailSampleDist, int m_regionMergeSize, float m_edgeMaxLen, float m_edgeMaxError, int m_vertsPerPoly, float m_detailSampleDist,
float m_detailSampleMaxError, int m_tileSize) { float m_detailSampleMaxError, int m_tileSize)
{
// Create empty nav mesh // Create empty nav mesh
NavMeshParams navMeshParams = new NavMeshParams(); NavMeshParams navMeshParams = new NavMeshParams();
copy(navMeshParams.orig, m_geom.getMeshBoundsMin()); copy(navMeshParams.orig, m_geom.getMeshBoundsMin());
@ -69,22 +68,27 @@ public class TestTiledNavMeshBuilder {
// Build all tiles // Build all tiles
RecastConfig cfg = new RecastConfig(true, m_tileSize, m_tileSize, RecastConfig.calcBorder(m_agentRadius, m_cellSize), RecastConfig cfg = new RecastConfig(true, m_tileSize, m_tileSize, RecastConfig.calcBorder(m_agentRadius, m_cellSize),
m_partitionType, m_cellSize, m_cellHeight, m_agentMaxSlope, true, true, true, m_agentHeight, m_agentRadius, m_partitionType, m_cellSize, m_cellHeight, m_agentMaxSlope, true, true, true, m_agentHeight, m_agentRadius,
m_agentMaxClimb, m_regionMinArea, m_regionMergeArea, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, true, m_agentMaxClimb, m_regionMinArea, m_regionMergeArea, m_edgeMaxLen, m_edgeMaxError, m_vertsPerPoly, true,
m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND); m_detailSampleDist, m_detailSampleMaxError, SampleAreaModifications.SAMPLE_AREAMOD_GROUND);
RecastBuilder rcBuilder = new RecastBuilder(); RecastBuilder rcBuilder = new RecastBuilder();
List<RecastBuilderResult> rcResult = rcBuilder.buildTiles(m_geom, cfg, null); List<RecastBuilderResult> rcResult = rcBuilder.buildTiles(m_geom, cfg, null);
// Add tiles to nav mesh // Add tiles to nav mesh
foreach (RecastBuilderResult result in rcResult) { foreach (RecastBuilderResult result in rcResult)
{
PolyMesh pmesh = result.getMesh(); PolyMesh pmesh = result.getMesh();
if (pmesh.npolys == 0) { if (pmesh.npolys == 0)
{
continue; continue;
} }
for (int i = 0; i < pmesh.npolys; ++i) {
for (int i = 0; i < pmesh.npolys; ++i)
{
pmesh.flags[i] = 1; pmesh.flags[i] = 1;
} }
NavMeshDataCreateParams option = new NavMeshDataCreateParams(); NavMeshDataCreateParams option = new NavMeshDataCreateParams();
option.verts = pmesh.verts; option.verts = pmesh.verts;
option.vertCount = pmesh.nverts; option.vertCount = pmesh.nverts;
@ -113,8 +117,8 @@ public class TestTiledNavMeshBuilder {
} }
} }
public NavMesh getNavMesh() { public NavMesh getNavMesh()
{
return navMesh; return navMesh;
} }
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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