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
{
public static class ArrayUtils
{
public static T[] CopyOf<T>(T[] source, int startIdx, int length)
public static class ArrayUtils
{
var deatArr = new T[length];
for (int i = 0; i < length; ++i)
public static T[] CopyOf<T>(T[] source, int startIdx, int length)
{
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
{
public class AtomicBoolean
{
private volatile int _location;
public bool set(bool v)
public class AtomicBoolean
{
return 0 != Interlocked.Exchange(ref _location, v ? 1 : 0);
}
private volatile int _location;
public bool get()
{
return 0 != _location;
public bool set(bool v)
{
return 0 != Interlocked.Exchange(ref _location, v ? 1 : 0);
}
public bool get()
{
return 0 != _location;
}
}
}
}

View File

@ -2,30 +2,28 @@
namespace DotRecast.Core
{
public class AtomicFloat
{
private volatile float _location;
public AtomicFloat(float location)
public class AtomicFloat
{
_location = location;
}
private volatile float _location;
public float Get()
{
return _location;
}
public AtomicFloat(float location)
{
_location = location;
}
public float Exchange(float exchange)
{
return Interlocked.Exchange(ref _location, exchange);
}
public float Get()
{
return _location;
}
public float CompareExchange(float value, float comparand)
{
return Interlocked.CompareExchange(ref _location, value, comparand);
public float Exchange(float exchange)
{
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
{
public class AtomicInteger
{
private volatile int _location;
public AtomicInteger() : this(0)
public class AtomicInteger
{
}
private volatile int _location;
public AtomicInteger(int location)
{
_location = location;
}
public AtomicInteger() : this(0)
{
}
public int IncrementAndGet()
{
return Interlocked.Increment(ref _location);
}
public int GetAndIncrement()
{
var next = Interlocked.Increment(ref _location);
return next - 1;
}
public AtomicInteger(int location)
{
_location = location;
}
public int IncrementAndGet()
{
return Interlocked.Increment(ref _location);
}
public int GetAndIncrement()
{
var next = Interlocked.Increment(ref _location);
return next - 1;
}
public int DecrementAndGet()
{
return Interlocked.Decrement(ref _location);
}
public int DecrementAndGet()
{
return Interlocked.Decrement(ref _location);
}
public int Read()
{
return _location;
}
public int Read()
{
return _location;
}
public int GetSoft()
{
return _location;
}
public int GetSoft()
{
return _location;
}
public int Exchange(int exchange)
{
return Interlocked.Exchange(ref _location, exchange);
}
public int Exchange(int exchange)
{
return Interlocked.Exchange(ref _location, exchange);
}
public int Decrease(int value)
{
return Interlocked.Add(ref _location, -value);
}
public int Decrease(int value)
{
return Interlocked.Add(ref _location, -value);
}
public int CompareExchange(int value, int comparand)
{
return Interlocked.CompareExchange(ref _location, value, comparand);
}
public int CompareExchange(int value, int comparand)
{
return Interlocked.CompareExchange(ref _location, value, comparand);
}
public int Add(int value)
{
return Interlocked.Add(ref _location, value);
public int Add(int value)
{
return Interlocked.Add(ref _location, value);
}
}
}
}

View File

@ -2,54 +2,52 @@
namespace DotRecast.Core
{
public class AtomicLong
{
private long _location;
public AtomicLong() : this(0)
public class AtomicLong
{
}
public AtomicLong(long location)
{
_location = location;
}
private long _location;
public long IncrementAndGet()
{
return Interlocked.Increment(ref _location);
}
public AtomicLong() : this(0)
{
}
public long DecrementAndGet()
{
return Interlocked.Decrement(ref _location);
}
public AtomicLong(long location)
{
_location = location;
}
public long Read()
{
return Interlocked.Read(ref _location);
}
public long IncrementAndGet()
{
return Interlocked.Increment(ref _location);
}
public long Exchange(long exchange)
{
return Interlocked.Exchange(ref _location, exchange);
}
public long DecrementAndGet()
{
return Interlocked.Decrement(ref _location);
}
public long Decrease(long value)
{
return Interlocked.Add(ref _location, -value);
}
public long Read()
{
return Interlocked.Read(ref _location);
}
public long CompareExchange(long value, long comparand)
{
return Interlocked.CompareExchange(ref _location, value, comparand);
}
public long Exchange(long exchange)
{
return Interlocked.Exchange(ref _location, exchange);
}
public long AddAndGet(long value)
{
return Interlocked.Add(ref _location, value);
public long Decrease(long 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
{
public class ByteBuffer
{
private ByteOrder _order;
private byte[] _bytes;
private int _position;
public ByteBuffer(byte[] bytes)
public class ByteBuffer
{
_order = BitConverter.IsLittleEndian
? ByteOrder.LITTLE_ENDIAN
: ByteOrder.BIG_ENDIAN;
private ByteOrder _order;
private byte[] _bytes;
private int _position;
_bytes = 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)
public ByteBuffer(byte[] bytes)
{
return BinaryPrimitives.ReadInt16BigEndian(span);
}
else
{
return BinaryPrimitives.ReadInt16LittleEndian(span);
}
}
_order = BitConverter.IsLittleEndian
? ByteOrder.LITTLE_ENDIAN
: ByteOrder.BIG_ENDIAN;
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();
_bytes = bytes;
_position = 0;
}
return BitConverter.ToSingle(span);
}
public long getLong()
{
var span = ReadBytes(8);
if (_order == ByteOrder.BIG_ENDIAN)
public ByteOrder order()
{
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
{
public enum ByteOrder
{
/// <summary>Default on most Windows systems</summary>
LITTLE_ENDIAN,
BIG_ENDIAN,
}
public enum ByteOrder
{
/// <summary>Default on most Windows systems</summary>
LITTLE_ENDIAN,
BIG_ENDIAN,
}
}

View File

@ -3,30 +3,28 @@ using System.Collections.Generic;
namespace DotRecast.Core
{
public static class CollectionExtensions
{
public static void forEach<T>(this IEnumerable<T> collection, Action<T> action)
public static class CollectionExtensions
{
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
{
public static class ConvexUtils {
// Calculates convex hull on xz-plane of points on 'pts',
// stores the indices of the resulting hull in 'out' and
// returns number of points on hull.
public static List<int> convexhull(List<float> pts) {
int npts = pts.Count / 3;
List<int> @out = new List<int>();
// Find lower-leftmost point.
int hull = 0;
for (int i = 1; i < npts; ++i) {
float[] a = new float[] { pts[i * 3], pts[i * 3 + 1], pts[i * 3 + 2] };
float[] b = new float[] { pts[hull * 3], pts[hull * 3 + 1], pts[hull * 3 + 2] };
if (cmppt(a, b)) {
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;
public static class ConvexUtils
{
// Calculates convex hull on xz-plane of points on 'pts',
// stores the indices of the resulting hull in 'out' and
// returns number of points on hull.
public static List<int> convexhull(List<float> pts)
{
int npts = pts.Count / 3;
List<int> @out = new List<int>();
// Find lower-leftmost point.
int hull = 0;
for (int i = 1; i < npts; ++i)
{
float[] a = new float[] { pts[i * 3], pts[i * 3 + 1], pts[i * 3 + 2] };
float[] b = new float[] { pts[hull * 3], pts[hull * 3 + 1], pts[hull * 3 + 2] };
if (cmppt(a, b))
{
hull = i;
}
}
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'.
private static bool cmppt(float[] a, float[] b) {
if (a[0] < b[0]) {
return true;
hull = endpt;
} while (endpt != @out[0]);
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;
}
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
{
public class DemoMath {
public static float vDistSqr(float[] v1, float[] v2, int i) {
float dx = v2[i] - v1[0];
float dy = v2[i + 1] - v1[1];
float dz = v2[i + 2] - v1[2];
return dx * dx + dy * dy + dz * dz;
}
public static float[] vCross(float[] v1, float[] v2) {
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));
public class DemoMath
{
public static float vDistSqr(float[] v1, float[] v2, int i)
{
float dx = v2[i] - v1[0];
float dy = v2[i + 1] - v1[1];
float dz = v2[i + 2] - v1[2];
return dx * dx + dy * dy + dz * dz;
}
return totd;
}
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[] 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 step(float threshold, float v) {
return v < threshold ? 0.0f : 1.0f;
}
public static float vDot(float[] v1, float[] v2)
{
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
}
public static int clamp(int v, int min, int max) {
return Math.Max(Math.Min(v, max), min);
}
public static float sqr(float f)
{
return f * f;
}
public static float clamp(float v, float min, float max) {
return Math.Max(Math.Min(v, max), min);
}
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));
}
public static float lerp(float f, float g, float u) {
return u * g + (1f - u) * f;
}
}
return totd;
}
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>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Collections.Immutable" Version="7.0.0" />
<PackageReference Include="System.Text.Json" Version="7.0.2" />
<PackageReference Include="System.Collections.Immutable" Version="7.0.0"/>
<PackageReference Include="System.Text.Json" Version="7.0.2"/>
</ItemGroup>
</Project>

View File

@ -2,34 +2,32 @@
namespace DotRecast.Core
{
public static class Loader
{
public static byte[] ToBytes(string filename)
public static class Loader
{
var filepath = ToRPath(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)
public static byte[] ToBytes(string filename)
{
if (File.Exists(filePath))
{
return Path.GetFullPath(filePath);
}
var filepath = ToRPath(filename);
using var fs = new FileStream(filepath, FileMode.Open);
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
{
using System.Collections.Generic;
using System.Collections.Generic;
public class OrderedQueue<T>
{
private readonly List<T> _items;
private readonly Comparison<T> _comparison;
public OrderedQueue(Comparison<T> comparison)
public class OrderedQueue<T>
{
_items = new List<T>();
_comparison = comparison;
}
private readonly List<T> _items;
private readonly Comparison<T> _comparison;
public int count()
{
return _items.Count;
}
public OrderedQueue(Comparison<T> comparison)
{
_items = new List<T>();
_comparison = comparison;
}
public void clear() {
_items.Clear();
}
public int count()
{
return _items.Count;
}
public T top()
{
return _items[0];
}
public void clear()
{
_items.Clear();
}
public T Dequeue()
{
var node = top();
_items.Remove(node);
return node;
}
public T top()
{
return _items[0];
}
public void Enqueue(T item) {
_items.Add(item);
_items.Sort(_comparison);
}
public T Dequeue()
{
var node = top();
_items.Remove(node);
return node;
}
public void Remove(T item) {
_items.Remove(item);
}
public void Enqueue(T item)
{
_items.Add(item);
_items.Sort(_comparison);
}
public bool isEmpty()
{
return 0 == _items.Count;
}
}
public void Remove(T item)
{
_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
{
using static DetourCommon;
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.
/// Represents an agent managed by a #dtCrowd object.
/// @ingroup crowd
public enum CrowdAgentState {
DT_CROWDAGENT_STATE_INVALID, /// < The agent is not in a valid state.
DT_CROWDAGENT_STATE_WALKING, /// < The agent is traversing a normal navigation mesh polygon.
DT_CROWDAGENT_STATE_OFFMESH, /// < The agent is traversing an off-mesh connection.
};
public class CrowdAgent
{
/// The type of navigation mesh polygon the agent is currently traversing.
/// @ingroup crowd
public enum CrowdAgentState
{
DT_CROWDAGENT_STATE_INVALID,
public enum MoveRequestState {
DT_CROWDAGENT_TARGET_NONE,
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,
};
/// < The agent is not in a valid state.
DT_CROWDAGENT_STATE_WALKING,
public readonly long idx;
/// The type of mesh polygon the agent is traversing. (See: #CrowdAgentState)
public CrowdAgentState state;
/// True if the agent has valid path (targetState == DT_CROWDAGENT_TARGET_VALID) and the path does not lead to the
/// requested position, else false.
public bool partial;
/// The path corridor the agent is using.
public PathCorridor corridor;
/// The local boundary data for the agent.
public LocalBoundary boundary;
/// Time since the agent's path corridor was optimized.
public float topologyOptTime;
/// The known neighbors of the agent.
public List<Crowd.CrowdNeighbour> neis = new List<Crowd.CrowdNeighbour>();
/// The desired speed.
public float desiredSpeed;
/// < The agent is traversing a normal navigation mesh polygon.
DT_CROWDAGENT_STATE_OFFMESH, /// < The agent is traversing an off-mesh connection.
};
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)]
public enum MoveRequestState
{
DT_CROWDAGENT_TARGET_NONE,
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,
};
/// The agent's configuration parameters.
public CrowdAgentParams option;
/// The local path corridor corners for the agent.
public List<StraightPathItem> corners = new List<StraightPathItem>();
public readonly long idx;
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;
/// The type of mesh polygon the agent is traversing. (See: #CrowdAgentState)
public CrowdAgentState state;
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) {
this.idx = idx;
corridor = new PathCorridor();
boundary = new LocalBoundary();
animation = new CrowdAgentAnimation();
}
/// The path corridor the agent is using.
public PathCorridor corridor;
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);
/// The local boundary data for the agent.
public LocalBoundary boundary;
// Integrate
if (vLen(vel) > 0.0001f)
npos = vMad(npos, vel, dt);
else
vSet(vel, 0, 0, 0);
}
/// Time since the agent's path corridor was optimized.
public float topologyOptTime;
/// The known neighbors of the agent.
public List<Crowd.CrowdNeighbour> neis = new List<Crowd.CrowdNeighbour>();
/// The desired speed.
public float desiredSpeed;
public float[] npos = new float[3];
/// < The current agent position. [(x, y, z)]
public float[] disp = new float[3];
/// < A temporary value used to accumulate agent displacement during iterative
/// 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;
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;
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() {
float[] dir = new float[3];
if (0 < corners.Count) {
dir = vSub(corners[0].getPos(), npos);
dir[1] = 0;
vNormalize(dir);
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;
}
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;
public float[] calcStraightSteerDirection()
{
float[] dir = new float[3];
if (0 < corners.Count)
{
dir = vSub(corners[0].getPos(), npos);
dir[1] = 0;
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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.Crowd
{
public class CrowdAgentAnimation {
public bool active;
public float[] initPos = new float[3];
public float[] startPos = new float[3];
public float[] endPos = new float[3];
public long polyRef;
public float t, tmax;
}
public class CrowdAgentAnimation
{
public bool active;
public float[] initPos = new float[3];
public float[] startPos = new float[3];
public float[] endPos = new float[3];
public long polyRef;
public float t, tmax;
}
}

View File

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

View File

@ -23,61 +23,67 @@ using System.Linq;
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 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))
public float maxTimeToEnqueueRequest()
{
s = new List<long>();
_executionTimingSamples.Add(name, s);
return _maxTimeToEnqueueRequest;
}
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">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<ItemGroup>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" />
</ItemGroup>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj"/>
</ItemGroup>
</Project>

View File

@ -23,119 +23,149 @@ using System.Collections.Generic;
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 {
/** Segment start/end */
public float[] s = new float[6];
/** Distance for pruning. */
public float d;
}
public LocalBoundary()
{
m_center[0] = m_center[1] = m_center[2] = float.MaxValue;
}
float[] m_center = new float[3];
List<Segment> m_segs = new List<Segment>();
List<long> m_polys = new List<long>();
public void reset()
{
m_center[0] = m_center[1] = m_center[2] = float.MaxValue;
m_polys.Clear();
m_segs.Clear();
}
public LocalBoundary() {
m_center[0] = m_center[1] = m_center[2] = float.MaxValue;
}
protected void addSegment(float dist, float[] s)
{
// Insert neighbour based on the distance.
Segment seg = new Segment();
Array.Copy(s, seg.s, 6);
seg.d = dist;
if (0 == m_segs.Count)
{
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_center[0] = m_center[1] = m_center[2] = float.MaxValue;
m_polys.Clear();
m_segs.Clear();
}
m_segs.Add(seg);
}
else
{
// 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) {
// 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) {
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)
{
if (refs == 0)
{
reset();
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) {
if (refs == 0) {
reset();
return;
}
vCopy(m_center, pos);
// First query non-overlapping polygons.
Result<FindLocalNeighbourhoodResult> res = navquery.findLocalNeighbourhood(refs, pos, collisionQueryRange,
vCopy(m_center, pos);
// First query non-overlapping polygons.
Result<FindLocalNeighbourhoodResult> res = navquery.findLocalNeighbourhood(refs, pos, collisionQueryRange,
filter);
if (res.succeeded()) {
m_polys = res.result.getRefs();
m_segs.Clear();
// Secondly, store all polygon edges.
for (int j = 0; j < m_polys.Count; ++j) {
Result<GetPolyWallSegmentsResult> result = navquery.getPolyWallSegments(m_polys[j], false, filter);
if (result.succeeded()) {
GetPolyWallSegmentsResult gpws = result.result;
for (int k = 0; k < gpws.getSegmentRefs().Count; ++k) {
float[] s = gpws.getSegmentVerts()[k];
// Skip too distant segments.
Tuple<float, float> distseg = distancePtSegSqr2D(pos, s, 0, 3);
if (distseg.Item1 > sqr(collisionQueryRange)) {
continue;
if (res.succeeded())
{
m_polys = res.result.getRefs();
m_segs.Clear();
// Secondly, store all polygon edges.
for (int j = 0; j < m_polys.Count; ++j)
{
Result<GetPolyWallSegmentsResult> result = navquery.getPolyWallSegments(m_polys[j], false, filter);
if (result.succeeded())
{
GetPolyWallSegmentsResult gpws = result.result;
for (int k = 0; k < gpws.getSegmentRefs().Count; ++k)
{
float[] s = gpws.getSegmentVerts()[k];
// Skip too distant segments.
Tuple<float, float> distseg = distancePtSegSqr2D(pos, s, 0, 3);
if (distseg.Item1 > sqr(collisionQueryRange))
{
continue;
}
addSegment(distseg.Item1, s);
}
addSegment(distseg.Item1, s);
}
}
}
}
}
public bool isValid(NavMeshQuery navquery, QueryFilter filter) {
if (m_polys.Count == 0) {
return false;
}
// Check that all polygons still pass query filter.
foreach (long refs in m_polys) {
if (!navquery.isValidPolyRef(refs, filter)) {
public bool isValid(NavMeshQuery navquery, QueryFilter filter)
{
if (m_polys.Count == 0)
{
return false;
}
// Check that all polygons still pass query filter.
foreach (long refs in m_polys)
{
if (!navquery.isValidPolyRef(refs, filter))
{
return false;
}
}
return true;
}
return true;
}
public float[] getCenter()
{
return m_center;
}
public float[] getCenter() {
return m_center;
}
public float[] getSegment(int j)
{
return m_segs[j].s;
}
public float[] getSegment(int j) {
return m_segs[j].s;
public int getSegmentCount()
{
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
{
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.
public const int DT_MAX_PATTERN_RINGS = 4; /// < Max number of adaptive rings.
/** Velocity of the obstacle */
public readonly float[] vel = new float[3];
public class ObstacleCircle {
/** Position of the obstacle */
public readonly float[] p = new float[3];
/** Velocity of the obstacle */
public readonly float[] vel = new float[3];
/** Velocity of the obstacle */
public readonly float[] dvel = new float[3];
/** Radius of the obstacle */
public float rad;
/** Use for side selection during sampling. */
public readonly float[] dp = new float[3];
/** Use for side selection during sampling. */
public readonly float[] np = new float[3];
}
/** Velocity of the obstacle */
public readonly float[] dvel = new float[3];
public class ObstacleSegment {
/** End points of the obstacle segment */
public readonly float[] p = new float[3];
/** End points of the obstacle segment */
public readonly float[] q = new float[3];
public bool touch;
}
/** Radius of the obstacle */
public float rad;
public class ObstacleAvoidanceParams {
public float velBias;
public float weightDesVel;
public float weightCurVel;
public float weightSide;
public float weightToi;
public float horizTime;
public int gridSize; /// < grid
public int adaptiveDivs; /// < adaptive
public int adaptiveRings; /// < adaptive
public int adaptiveDepth; /// < adaptive
/** Use for side selection during sampling. */
public readonly float[] dp = new float[3];
public ObstacleAvoidanceParams() {
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;
/** Use for side selection during sampling. */
public readonly float[] np = new float[3];
}
public ObstacleAvoidanceParams(ObstacleAvoidanceParams option) {
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;
public class ObstacleSegment
{
/** End points of the obstacle segment */
public readonly float[] p = new float[3];
/** End points of the obstacle segment */
public readonly float[] q = new float[3];
public bool touch;
}
};
private ObstacleAvoidanceParams m_params;
private float m_invHorizTime;
private float m_vmax;
private float m_invVmax;
public class ObstacleAvoidanceParams
{
public float velBias;
public float weightDesVel;
public float weightCurVel;
public float weightSide;
public float weightToi;
public float horizTime;
public int gridSize;
private readonly int m_maxCircles;
private readonly ObstacleCircle[] m_circles;
private int m_ncircles;
/// < grid
public int adaptiveDivs;
private readonly int m_maxSegments;
private readonly ObstacleSegment[] m_segments;
private int m_nsegments;
/// < adaptive
public int adaptiveRings;
public ObstacleAvoidanceQuery(int maxCircles, int maxSegments) {
m_maxCircles = maxCircles;
m_ncircles = 0;
m_circles = new ObstacleCircle[m_maxCircles];
for (int i = 0; i < m_maxCircles; i++) {
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();
}
}
/// < adaptive
public int adaptiveDepth;
public void reset() {
m_ncircles = 0;
m_nsegments = 0;
}
/// < adaptive
public ObstacleAvoidanceParams()
{
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) {
if (m_ncircles >= m_maxCircles)
return;
public ObstacleAvoidanceParams(ObstacleAvoidanceParams option)
{
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++];
vCopy(cir.p, pos);
cir.rad = rad;
vCopy(cir.vel, vel);
vCopy(cir.dvel, dvel);
}
private ObstacleAvoidanceParams m_params;
private float m_invHorizTime;
private float m_vmax;
private float m_invVmax;
public void addSegment(float[] p, float[] q) {
if (m_nsegments >= m_maxSegments)
return;
ObstacleSegment seg = m_segments[m_nsegments++];
vCopy(seg.p, p);
vCopy(seg.q, q);
}
private readonly int m_maxCircles;
private readonly ObstacleCircle[] m_circles;
private int m_ncircles;
public int getObstacleCircleCount() {
return m_ncircles;
}
private readonly int m_maxSegments;
private readonly ObstacleSegment[] m_segments;
private int m_nsegments;
public ObstacleCircle getObstacleCircle(int i) {
return m_circles[i];
}
public ObstacleAvoidanceQuery(int maxCircles, int maxSegments)
{
m_maxCircles = maxCircles;
m_ncircles = 0;
m_circles = new ObstacleCircle[m_maxCircles];
for (int i = 0; i < m_maxCircles; i++)
{
m_circles[i] = new ObstacleCircle();
}
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];
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();
}
}
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);
public void reset()
{
m_ncircles = 0;
m_nsegments = 0;
}
}
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
public void addCircle(float[] pos, float rad, float[] vel, float[] dvel)
{
if (m_ncircles >= m_maxCircles)
return;
// 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);
}
ObstacleCircle cir = m_circles[m_ncircles++];
vCopy(cir.p, pos);
cir.rad = rad;
vCopy(cir.vel, vel);
vCopy(cir.dvel, dvel);
}
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);
}
public void addSegment(float[] p, float[] q)
{
if (m_nsegments >= m_maxSegments)
return;
ObstacleSegment seg = m_segments[m_nsegments++];
vCopy(seg.p, p);
vCopy(seg.q, q);
}
/**
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
*
* @param vcand
@ -242,272 +281,295 @@ public class ObstacleAvoidanceQuery {
* @param minPenalty
* threshold penalty for early out
*/
private float processSample(float[] vcand, float cs, float[] pos, float rad, float[] vel, float[] dvel,
float minPenalty, ObstacleAvoidanceDebugData debug) {
// penalty for straying away from the desired and current velocities
float vpen = m_params.weightDesVel * (vDist2D(vcand, dvel) * m_invVmax);
float vcpen = m_params.weightCurVel * (vDist2D(vcand, vel) * m_invVmax);
private float processSample(float[] vcand, float cs, float[] pos, float rad, float[] vel, float[] dvel,
float minPenalty, ObstacleAvoidanceDebugData debug)
{
// penalty for straying away from the desired and current velocities
float vpen = m_params.weightDesVel * (vDist2D(vcand, dvel) * m_invVmax);
float vcpen = m_params.weightCurVel * (vDist2D(vcand, vel) * m_invVmax);
// find the threshold hit time to bail out based on the early out penalty
// (see how the penalty is calculated below to understnad)
float minPen = minPenalty - vpen - vcpen;
float tThresold = (m_params.weightToi / minPen - 0.1f) * m_params.horizTime;
if (tThresold - m_params.horizTime > -float.MinValue)
return minPenalty; // already too much
// find the threshold hit time to bail out based on the early out penalty
// (see how the penalty is calculated below to understnad)
float minPen = minPenalty - vpen - vcpen;
float tThresold = (m_params.weightToi / minPen - 0.1f) * m_params.horizTime;
if (tThresold - m_params.horizTime > -float.MinValue)
return minPenalty; // already too much
// Find min time of impact and exit amongst all obstacles.
float tmin = m_params.horizTime;
float side = 0;
int nside = 0;
// Find min time of impact and exit amongst all obstacles.
float tmin = m_params.horizTime;
float side = 0;
int nside = 0;
for (int i = 0; i < m_ncircles; ++i) {
ObstacleCircle cir = m_circles[i];
for (int i = 0; i < m_ncircles; ++i)
{
ObstacleCircle cir = m_circles[i];
// RVO
float[] vab = vScale(vcand, 2);
vab = vSub(vab, vel);
vab = vSub(vab, cir.vel);
// RVO
float[] vab = vScale(vcand, 2);
vab = vSub(vab, vel);
vab = vSub(vab, cir.vel);
// Side
side += clamp(Math.Min(vDot2D(cir.dp, vab) * 0.5f + 0.5f, vDot2D(cir.np, vab) * 2), 0.0f, 1.0f);
nside++;
// Side
side += clamp(Math.Min(vDot2D(cir.dp, vab) * 0.5f + 0.5f, vDot2D(cir.np, vab) * 2), 0.0f, 1.0f);
nside++;
SweepCircleCircleResult sres = sweepCircleCircle(pos, rad, vab, cir.p, cir.rad);
if (!sres.intersection)
continue;
float htmin = sres.htmin, htmax = sres.htmax;
SweepCircleCircleResult sres = sweepCircleCircle(pos, rad, vab, cir.p, cir.rad);
if (!sres.intersection)
continue;
float htmin = sres.htmin, htmax = sres.htmax;
// Handle overlapping obstacles.
if (htmin < 0.0f && htmax > 0.0f) {
// Avoid more when overlapped.
htmin = -htmin * 0.5f;
// Handle overlapping obstacles.
if (htmin < 0.0f && htmax > 0.0f)
{
// 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.
if (htmin < tmin) {
if (htmin < tmin)
{
tmin = htmin;
if (tmin < tThresold)
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) {
ObstacleSegment seg = m_segments[i];
float htmin = 0;
public Tuple<int, float[]> sampleVelocityGrid(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;
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;
}
float[] nvel = new float[3];
vSet(nvel, 0f, 0f, 0f);
// Avoid less when facing walls.
htmin *= 2.0f;
if (debug != null)
debug.reset();
// The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
if (htmin < tmin) {
tmin = htmin;
if (tmin < tThresold)
return minPenalty;
}
}
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;
// Normalize side bias, to prevent it dominating too much.
if (nside != 0)
side /= nside;
float minPenalty = float.MaxValue;
int ns = 0;
float spen = m_params.weightSide * side;
float tpen = m_params.weightToi * (1.0f / (0.1f + tmin * m_invHorizTime));
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);
float penalty = vpen + vcpen + spen + tpen;
// Store different penalties for debug viewing
if (debug != null)
debug.addSample(vcand, cs, penalty, vpen, vcpen, spen, tpen);
if (sqr(vcand[0]) + sqr(vcand[2]) > sqr(vmax + cs / 2))
continue;
return penalty;
}
public Tuple<int, float[]> sampleVelocityGrid(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;
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);
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.
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.
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;
}
// vector normalization that ignores the y-component.
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;
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[] 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;
float[] nvel = new float[3];
vSet(nvel, 0f, 0f, 0f);
float[] nvel = new float[3];
vSet(nvel, 0f, 0f, 0f);
if (debug != null)
debug.reset();
if (debug != null)
debug.reset();
// Build sampling pattern aligned to desired velocity.
float[] pat = new float[(DT_MAX_PATTERN_DIVS * DT_MAX_PATTERN_RINGS + 1) * 2];
int npat = 0;
// Build sampling pattern aligned to desired velocity.
float[] pat = new float[(DT_MAX_PATTERN_DIVS * DT_MAX_PATTERN_RINGS + 1) * 2];
int npat = 0;
int ndivs = m_params.adaptiveDivs;
int nrings = m_params.adaptiveRings;
int depth = m_params.adaptiveDepth;
int ndivs = m_params.adaptiveDivs;
int nrings = m_params.adaptiveRings;
int depth = m_params.adaptiveDepth;
int nd = clamp(ndivs, 1, DT_MAX_PATTERN_DIVS);
int nr = clamp(nrings, 1, DT_MAX_PATTERN_RINGS);
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);
int nr = clamp(nrings, 1, DT_MAX_PATTERN_RINGS);
float da = (1.0f / nd) * DT_PI * 2;
float ca = (float) Math.Cos(da);
float sa = (float) Math.Sin(da);
// desired direction
float[] ddir = new float[6];
vCopy(ddir, dvel);
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];
// desired direction
float[] ddir = new float[6];
vCopy(ddir, dvel);
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;
// Always add sample at zero
pat[npat * 2 + 0] = 0;
pat[npat * 2 + 1] = 0;
npat++;
for (int i = 1; i < nd - 1; i += 2) {
// get next point on the "right" (rotate CW)
pat[npat * 2 + 0] = pat[last1] * ca + pat[last1 + 1] * sa;
pat[npat * 2 + 1] = -pat[last1] * sa + pat[last1 + 1] * ca;
// get next point on the "left" (rotate CCW)
pat[npat * 2 + 2] = pat[last2] * ca - pat[last2 + 1] * sa;
pat[npat * 2 + 3] = pat[last2] * sa + pat[last2 + 1] * ca;
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;
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++;
}
}
// 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);
for (int i = 1; i < nd - 1; i += 2)
{
// get next point on the "right" (rotate CW)
pat[npat * 2 + 0] = pat[last1] * ca + pat[last1 + 1] * sa;
pat[npat * 2 + 1] = -pat[last1] * sa + pat[last1 + 1] * ca;
// get next point on the "left" (rotate CCW)
pat[npat * 2 + 2] = pat[last2] * ca - pat[last2 + 1] * sa;
pat[npat * 2 + 3] = pat[last2] * sa + pat[last2 + 1] * ca;
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;
last1 = npat * 2;
last2 = last1 + 2;
npat += 2;
}
float penalty = processSample(vcand, cr / 10, pos, rad, vel, dvel, minPenalty, debug);
ns++;
if (penalty < minPenalty) {
minPenalty = penalty;
vCopy(bvel, vcand);
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++;
}
}
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
{
using static DetourCommon;
using static DetourCommon;
/**
/**
* 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
@ -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.
*
*/
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];
private readonly float[] m_target = new float[3];
private List<long> m_path;
protected List<long> mergeCorridorStartMoved(List<long> path, List<long> visited)
{
int furthestPath = -1;
int furthestVisited = -1;
protected List<long> mergeCorridorStartMoved(List<long> path, List<long> visited) {
int furthestPath = -1;
int furthestVisited = -1;
// Find furthest common polygon.
for (int i = path.Count - 1; i >= 0; --i)
{
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.
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 (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.
if (furthestPath == -1 || furthestVisited == -1) {
return path;
}
protected List<long> mergeCorridorEndMoved(List<long> path, List<long> visited)
{
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.
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;
}
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 (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.
if (furthestPath == -1 || furthestVisited == -1) {
return path;
}
protected List<long> mergeCorridorStartShortcut(List<long> path, List<long> visited)
{
int furthestPath = -1;
int furthestVisited = -1;
// Concatenate paths.
List<long> result = path.GetRange(0, furthestPath);
result.AddRange(visited.GetRange(furthestVisited, visited.Count - furthestVisited));
return result;
}
// 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;
}
}
protected List<long> mergeCorridorStartShortcut(List<long> path, List<long> visited) {
int furthestPath = -1;
int furthestVisited = -1;
// Find furthest common polygon.
for (int i = path.Count - 1; i >= 0; --i) {
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 (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.
*/
public PathCorridor() {
m_path = new List<long>();
}
public PathCorridor()
{
m_path = new List<long>();
}
/**
/**
* Resets the path corridor to the specified position.
*
* @param ref
@ -184,16 +205,17 @@ public class PathCorridor {
* @param pos
* The new position in the corridor. [(x, y, z)]
*/
public void reset(long refs, float[] pos) {
m_path.Clear();
m_path.Add(refs);
vCopy(m_pos, pos);
vCopy(m_target, pos);
}
public void reset(long refs, float[] pos)
{
m_path.Clear();
m_path.Add(refs);
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.)
*
* 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.
* @return Corners
*/
public List<StraightPathItem> findCorners(int maxCorners, NavMeshQuery navquery, QueryFilter filter) {
List<StraightPathItem> path = new List<StraightPathItem>();
Result<List<StraightPathItem>> result = navquery.findStraightPath(m_pos, m_target, m_path, maxCorners, 0);
if (result.succeeded()) {
path = result.result;
// Prune points in the beginning of the path which are too close.
int start = 0;
foreach (StraightPathItem spi in path) {
if ((spi.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0
|| vDist2DSqr(spi.getPos(), m_pos) > MIN_TARGET_DIST) {
break;
}
start++;
}
int end = path.Count;
// Prune points after an off-mesh connection.
for (int i = start; i < path.Count; i++) {
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;
}
public List<StraightPathItem> findCorners(int maxCorners, NavMeshQuery navquery, QueryFilter filter)
{
List<StraightPathItem> path = new List<StraightPathItem>();
Result<List<StraightPathItem>> result = navquery.findStraightPath(m_pos, m_target, m_path, maxCorners, 0);
if (result.succeeded())
{
path = result.result;
// Prune points in the beginning of the path which are too close.
int start = 0;
foreach (StraightPathItem spi in path)
{
if ((spi.getFlags() & NavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0
|| vDist2DSqr(spi.getPos(), m_pos) > MIN_TARGET_DIST)
{
break;
}
/**
start++;
}
int end = path.Count;
// Prune points after an off-mesh connection.
for (int i = start; i < path.Count; i++)
{
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.
*
* Inaccurate locomotion or dynamic obstacle avoidance can force the agent position significantly outside the
@ -265,34 +297,37 @@ public class PathCorridor {
* @param filter
* 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,
QueryFilter filter) {
// Clamp the ray to max distance.
float dist = vDist2D(m_pos, next);
// If too close to the goal, do not try to optimize.
if (dist < 0.01f)
{
return;
}
// If too close to the goal, do not try to optimize.
if (dist < 0.01f) {
return;
}
// Overshoot a little. This helps to optimize open fields in tiled
// meshes.
dist = Math.Min(dist + 0.01f, pathOptimizationRange);
// Overshoot a little. This helps to optimize open fields in tiled
// meshes.
dist = Math.Min(dist + 0.01f, pathOptimizationRange);
// Adjust ray length.
float[] delta = vSub(next, m_pos);
float[] goal = vMad(m_pos, delta, pathOptimizationRange / dist);
// Adjust ray length.
float[] delta = vSub(next, m_pos);
float[] goal = vMad(m_pos, delta, pathOptimizationRange / dist);
Result<RaycastHit> rc = navquery.raycast(m_path[0], m_pos, goal, filter, 0, 0);
if (rc.succeeded()) {
if (rc.result.path.Count > 1 && rc.result.t > 0.99f) {
m_path = mergeCorridorStartShortcut(m_path, rc.result.path);
Result<RaycastHit> rc = navquery.raycast(m_path[0], m_pos, goal, filter, 0, 0);
if (rc.succeeded())
{
if (rc.result.path.Count > 1 && rc.result.t > 0.99f)
{
m_path = mergeCorridorStartShortcut(m_path, rc.result.path);
}
}
}
}
/**
/**
* 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
@ -308,55 +343,64 @@ public class PathCorridor {
* The filter to apply to the operation.
*
*/
public bool optimizePathTopology(NavMeshQuery navquery, QueryFilter filter, int maxIterations) {
if (m_path.Count < 3) {
public bool optimizePathTopology(NavMeshQuery navquery, QueryFilter filter, int maxIterations)
{
if (m_path.Count < 3)
{
return false;
}
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;
}
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);
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 (fpr.succeeded() && fpr.result.Count > 0) {
m_path = mergeCorridorStartShortcut(m_path, fpr.result);
return true;
}
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;
}
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;
}
// 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
* the change.
*
@ -379,23 +423,28 @@ public class PathCorridor {
* @param filter
* The filter to apply to the operation.
*/
public bool movePosition(float[] npos, NavMeshQuery navquery, QueryFilter filter) {
// Move along navmesh and update new position.
Result<MoveAlongSurfaceResult> masResult = navquery.moveAlongSurface(m_path[0], m_pos, npos, filter);
if (masResult.succeeded()) {
m_path = mergeCorridorStartMoved(m_path, masResult.result.getVisited());
// Adjust the position to stay on top of the navmesh.
vCopy(m_pos, masResult.result.getResultPos());
Result<float> hr = navquery.getPolyHeight(m_path[0], masResult.result.getResultPos());
if (hr.succeeded()) {
m_pos[1] = hr.result;
}
return true;
}
return false;
}
public bool movePosition(float[] npos, NavMeshQuery navquery, QueryFilter filter)
{
// Move along navmesh and update new position.
Result<MoveAlongSurfaceResult> masResult = navquery.moveAlongSurface(m_path[0], m_pos, npos, filter);
if (masResult.succeeded())
{
m_path = mergeCorridorStartMoved(m_path, masResult.result.getVisited());
// Adjust the position to stay on top of the navmesh.
vCopy(m_pos, masResult.result.getResultPos());
Result<float> hr = navquery.getPolyHeight(m_path[0], masResult.result.getResultPos());
if (hr.succeeded())
{
m_pos[1] = hr.result;
}
/**
return true;
}
return false;
}
/**
* 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
* 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
* The filter to apply to the operation.
*/
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,
public bool moveTargetPosition(float[] npos, NavMeshQuery navquery, QueryFilter filter)
{
// Move along navmesh and update new position.
Result<MoveAlongSurfaceResult> masResult = navquery.moveAlongSurface(m_path[m_path.Count - 1], m_target,
npos, filter);
if (masResult.succeeded()) {
m_path = mergeCorridorEndMoved(m_path, masResult.result.getVisited());
// 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;
*/
vCopy(m_target, masResult.result.getResultPos());
return true;
}
return false;
}
if (masResult.succeeded())
{
m_path = mergeCorridorEndMoved(m_path, masResult.result.getVisited());
// TODO: should we do that?
// Adjust the position to stay on top of the navmesh.
/*
* 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;
}
/**
return false;
}
/**
* 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.
*
@ -440,52 +492,62 @@ public class PathCorridor {
* @param path
* The path corridor.
*/
public void setCorridor(float[] target, List<long> path) {
vCopy(m_target, target);
m_path = new List<long>(path);
}
public void fixPathStart(long safeRef, float[] safePos) {
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 setCorridor(float[] target, List<long> path)
{
vCopy(m_target, target);
m_path = new List<long>(path);
}
}
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.
public void fixPathStart(long safeRef, float[] safePos)
{
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.
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);
}
}
// 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
* 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.)
@ -498,69 +560,77 @@ public class PathCorridor {
* The filter to apply to the operation.
* @return
*/
public bool isValid(int maxLookAhead, NavMeshQuery navquery, QueryFilter filter) {
// Check that all polygons still pass query filter.
int n = Math.Min(m_path.Count, maxLookAhead);
for (int i = 0; i < n; ++i) {
if (!navquery.isValidPolyRef(m_path[i], filter)) {
return false;
public bool isValid(int maxLookAhead, NavMeshQuery navquery, QueryFilter filter)
{
// Check that all polygons still pass query filter.
int n = Math.Min(m_path.Count, maxLookAhead);
for (int i = 0; i < n; ++i)
{
if (!navquery.isValidPolyRef(m_path[i], filter))
{
return false;
}
}
return true;
}
return true;
}
/**
/**
* Gets the current position within the corridor. (In the first polygon.)
*
* @return The current position within the corridor.
*/
public float[] getPos() {
return m_pos;
}
public float[] getPos()
{
return m_pos;
}
/**
/**
* Gets the current target within the corridor. (In the last polygon.)
*
* @return The current target within the corridor.
*/
public float[] getTarget() {
return m_target;
}
public float[] getTarget()
{
return m_target;
}
/**
/**
* 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.)
*/
public long getFirstPoly() {
return 0 == m_path.Count ? 0 : m_path[0];
}
public long getFirstPoly()
{
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.
*
* @return The polygon reference id of the last polygon in the corridor. (Or zero if there is no path.)
*/
public long getLastPoly() {
return 0 == m_path.Count ? 0 : m_path[m_path.Count - 1];
}
public long getLastPoly()
{
return 0 == m_path.Count ? 0 : m_path[m_path.Count - 1];
}
/**
/**
* The corridor's path.
*/
public List<long> getPath() {
return m_path;
}
public List<long> getPath()
{
return m_path;
}
/**
/**
* The number of polygons in the current corridor path.
*
* @return The number of polygons in the current corridor path.
*/
public int getPathCount() {
return m_path.Count;
public int getPathCount()
{
return m_path.Count;
}
}
}
}

View File

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

View File

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

View File

@ -22,66 +22,76 @@ using System.Collections.Generic;
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;
private readonly LinkedList<PathQuery> queue = new LinkedList<PathQuery>();
// Handle query start.
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) {
this.config = config;
}
// Handle query in progress.
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) {
// 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;
}
// Handle query start.
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);
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) {
if (queue.Count >= config.pathQueueSize) {
return null;
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;
}
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
{
public class ProximityGrid
{
private readonly float m_cellSize;
private readonly float m_invCellSize;
private readonly Dictionary<ItemKey, List<CrowdAgent>> items;
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) {
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 ProximityGrid(float m_cellSize)
{
this.m_cellSize = m_cellSize;
m_invCellSize = 1.0f / m_cellSize;
items = new Dictionary<ItemKey, List<CrowdAgent>>();
}
}
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);
void clear()
{
items.Clear();
}
HashSet<CrowdAgent> result = new HashSet<CrowdAgent>();
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)) {
result.UnionWith(ids);
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);
}
}
}
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() {
return items
.Where(e => e.Value.Count > 0)
.Select(e => new int[] { e.Key.x, e.Key.y, e.Value.Count })
.ToList();
}
HashSet<CrowdAgent> result = new HashSet<CrowdAgent>();
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))
{
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;
}
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;
public List<int[]> getItemCounts()
{
return items
.Where(e => e.Value.Count > 0)
.Select(e => new int[] { e.Key.x, e.Key.y, e.Value.Count })
.ToList();
}
};
}
public float getCellSize()
{
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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.Crowd
{
public class SweepCircleCircleResult
{
public readonly bool intersection;
public readonly float htmin;
public readonly float htmax;
public class SweepCircleCircleResult {
public readonly bool intersection;
public readonly float htmin;
public readonly float htmax;
public SweepCircleCircleResult(bool intersection, float htmin, float htmax) {
this.intersection = intersection;
this.htmin = htmin;
this.htmax = htmax;
public SweepCircleCircleResult(bool intersection, float htmin, float htmax)
{
this.intersection = intersection;
this.htmin = htmin;
this.htmax = htmax;
}
}
}
}

View File

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

View File

@ -22,107 +22,121 @@ using System;
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 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;
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]);
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];
}
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() {
normalizeArray(m_pen, m_nsamples);
normalizeArray(m_vpen, m_nsamples);
normalizeArray(m_vcpen, m_nsamples);
normalizeArray(m_spen, m_nsamples);
normalizeArray(m_tpen, m_nsamples);
}
public void reset()
{
m_nsamples = 0;
}
public void addSample(float[] vel, float ssize, float pen, float vpen, float vcpen, float spen, float tpen) {
if (m_nsamples >= m_maxSamples)
return;
m_vel[m_nsamples * 3] = vel[0];
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++;
}
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]);
}
public int getSampleCount() {
return m_nsamples;
}
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 float[] getSampleVelocity(int 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 void normalizeSamples()
{
normalizeArray(m_pen, m_nsamples);
normalizeArray(m_vpen, m_nsamples);
normalizeArray(m_vcpen, m_nsamples);
normalizeArray(m_spen, m_nsamples);
normalizeArray(m_tpen, m_nsamples);
}
public float getSampleSize(int i) {
return m_ssize[i];
}
public void addSample(float[] vel, float ssize, float pen, float vpen, float vcpen, float spen, float tpen)
{
if (m_nsamples >= m_maxSamples)
return;
m_vel[m_nsamples * 3] = vel[0];
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) {
return m_pen[i];
}
public int getSampleCount()
{
return m_nsamples;
}
public float getSampleDesiredVelocityPenalty(int i) {
return m_vpen[i];
}
public float[] getSampleVelocity(int 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) {
return m_vcpen[i];
}
public float getSampleSize(int i)
{
return m_ssize[i];
}
public float getSamplePreferredSidePenalty(int i) {
return m_spen[i];
}
public float getSamplePenalty(int i)
{
return m_pen[i];
}
public float getSampleCollisionTimePenalty(int i) {
return m_tpen[i];
public float getSampleDesiredVelocityPenalty(int 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
{
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;
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 void process(DynamicTile tile)
{
tile.addCollider(colliderId, collider);
}
}
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
{
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)
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)
{
///?
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -21,45 +21,48 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Colliders
{
public class TrimeshCollider : AbstractCollider
{
private readonly float[] vertices;
private readonly int[] triangles;
public class TrimeshCollider : AbstractCollider {
private readonly float[] vertices;
private readonly int[] triangles;
public TrimeshCollider(float[] vertices, int[] triangles, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, computeBounds(vertices)) {
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]);
public TrimeshCollider(float[] vertices, int[] triangles, int area, float flagMergeThreshold) :
base(area, flagMergeThreshold, computeBounds(vertices))
{
this.vertices = vertices;
this.triangles = triangles;
}
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);
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)
{
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">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<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>
</PropertyGroup>
<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>
</Project>

View File

@ -29,202 +29,234 @@ using DotRecast.Recast;
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 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 NavMesh navMesh()
{
return _navMesh;
}
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}
*/
public VoxelQuery voxelQuery() {
return new VoxelQuery(navMeshParams.orig, navMeshParams.tileWidth, navMeshParams.tileHeight, lookupHeightfield);
}
public VoxelQuery voxelQuery()
{
return new VoxelQuery(navMeshParams.orig, navMeshParams.tileWidth, navMeshParams.tileHeight, lookupHeightfield);
}
private Heightfield lookupHeightfield(int x, int z) {
return getTileAt(x, z)?.checkpoint.heightfield;
}
private Heightfield lookupHeightfield(int x, int z)
{
return getTileAt(x, z)?.checkpoint.heightfield;
}
public long addCollider(Collider collider) {
long cid = currentColliderId.IncrementAndGet();
updateQueue.Add(new AddColliderQueueItem(cid, collider, getTiles(collider.bounds())));
return cid;
}
public long addCollider(Collider collider)
{
long cid = currentColliderId.IncrementAndGet();
updateQueue.Add(new AddColliderQueueItem(cid, collider, getTiles(collider.bounds())));
return cid;
}
public void removeCollider(long colliderId) {
updateQueue.Add(new RemoveColliderQueueItem(colliderId, getTilesByCollider(colliderId)));
}
public void removeCollider(long colliderId)
{
updateQueue.Add(new RemoveColliderQueueItem(colliderId, getTilesByCollider(colliderId)));
}
/**
/**
* Perform full build of the nav mesh
*/
public void build() {
processQueue();
rebuild(_tiles.Values);
}
public void build()
{
processQueue();
rebuild(_tiles.Values);
}
/**
/**
* Perform incremental update of the nav mesh
*/
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())
public bool update()
{
item.process(tile);
return rebuild(processQueue());
}
}
/**
* Perform full build concurrently using the given {@link ExecutorService}
*/
public Task<bool> build(TaskFactory executor) {
processQueue();
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;
private bool rebuild(ICollection<DynamicTile> stream)
{
foreach (var dynamicTile in stream)
rebuild(dynamicTile);
return updateNavMesh();
}
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);
}
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 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;
/**
* Perform full build concurrently using the given {@link ExecutorService}
*/
public Task<bool> build(TaskFactory executor)
{
processQueue();
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);
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
{
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 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) {
this.useTiles = useTiles;
this.tileSizeX = tileSizeX;
this.tileSizeZ = tileSizeZ;
this.cellSize = cellSize;
public DynamicNavMeshConfig(bool useTiles, int tileSizeX, int tileSizeZ, float cellSize)
{
this.useTiles = useTiles;
this.tileSizeX = tileSizeX;
this.tileSizeZ = tileSizeZ;
this.cellSize = cellSize;
}
}
}
}

View File

@ -27,131 +27,154 @@ using DotRecast.Recast;
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 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 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,
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);
meshData = NavMeshBuilder.createNavMeshData(option);
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);
meshData = NavMeshBuilder.createNavMeshData(option);
return true;
}
}
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,
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,
Heightfield heightfield, Telemetry telemetry)
{
RecastConfig rcConfig = new RecastConfig(config.useTiles, config.tileSizeX, config.tileSizeZ, vt.borderSize,
config.partitionType, vt.cellSize, vt.cellHeight, config.walkableSlopeAngle, true, true, true,
config.walkableHeight, config.walkableRadius, config.walkableClimb, config.minRegionArea, config.regionMergeArea,
config.maxEdgeLen, config.maxSimplificationError,
Math.Min(DynamicNavMesh.MAX_VERTS_PER_POLY, config.vertsPerPoly), true, config.detailSampleDistance,
config.detailSampleMaxError, null);
RecastBuilderResult r = builder.build(vt.tileX, vt.tileZ, null, rcConfig, heightfield, telemetry);
if (config.keepIntermediateResults) {
recastResult = r;
RecastBuilderResult r = builder.build(vt.tileX, vt.tileZ, null, rcConfig, heightfield, telemetry);
if (config.keepIntermediateResults)
{
recastResult = r;
}
return r;
}
return r;
}
public void addCollider(long cid, Collider collider) {
colliders[cid] = collider;
dirty = true;
}
public bool containsCollider(long cid) {
return colliders.ContainsKey(cid);
}
public void removeCollider(long colliderId) {
if (colliders.TryRemove(colliderId, out var collider)) {
public void addCollider(long cid, Collider collider)
{
colliders[cid] = collider;
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 DotRecast.Recast;
using static DotRecast.Detour.DetourCommon;
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 {
public readonly Heightfield heightfield;
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,
private Heightfield clone(Heightfield source)
{
Heightfield clone = new Heightfield(source.width, source.height, vCopy(source.bmin), vCopy(source.bmax), source.cs,
source.ch, source.borderSize);
for (int z = 0, pz = 0; z < source.height; z++, pz += source.width) {
for (int x = 0; x < source.width; x++) {
Span span = source.spans[pz + x];
Span prevCopy = null;
while (span != null) {
Span copy = new Span();
copy.smin = span.smin;
copy.smax = span.smax;
copy.area = span.area;
if (prevCopy == null) {
clone.spans[pz + x] = copy;
} else {
prevCopy.next = copy;
for (int z = 0, pz = 0; z < source.height; z++, pz += source.width)
{
for (int x = 0; x < source.width; x++)
{
Span span = source.spans[pz + x];
Span prevCopy = null;
while (span != null)
{
Span copy = new Span();
copy.smin = span.smin;
copy.smax = span.smax;
copy.area = span.area;
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
{
public static class ByteUtils {
public static int getInt(byte[] data, int position, ByteOrder order) {
return order == ByteOrder.BIG_ENDIAN ? getIntBE(data, position) : getIntLE(data, position);
}
public static int getIntBE(byte[] data, int position) {
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);
public static class ByteUtils
{
public static int getInt(byte[] data, int position, ByteOrder order)
{
return order == ByteOrder.BIG_ENDIAN ? getIntBE(data, position) : getIntLE(data, position);
}
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);
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)
{
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
{
public class LZ4VoxelTileCompressor
{
public byte[] decompress(byte[] data)
{
int compressedSize = ByteUtils.getIntBE(data, 0);
return LZ4Pickler.Unpickle(data.AsSpan(4, compressedSize));
}
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)
{
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;
}
}
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
{
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 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,
public RecastConfig getConfig(VoxelTile tile, PartitionType partitionType, int maxPolyVerts, int regionMergeSize,
bool filterLowHangingObstacles, bool filterLedgeSpans, bool filterWalkableLowHeightSpans,
AreaModification walkbableAreaMod, bool buildMeshDetail, float detailSampleDist, float detailSampleMaxError) {
return new RecastConfig(useTiles, tileSizeX, tileSizeZ, tile.borderSize, partitionType, cellSize, tile.cellHeight,
AreaModification walkbableAreaMod, bool buildMeshDetail, float detailSampleDist, float detailSampleMaxError)
{
return new RecastConfig(useTiles, tileSizeX, tileSizeZ, tile.borderSize, partitionType, cellSize, tile.cellHeight,
walkableSlopeAngle, filterLowHangingObstacles, filterLedgeSpans, filterWalkableLowHeightSpans, walkableHeight,
walkableRadius, walkableClimb, minRegionArea, regionMergeArea, maxEdgeLen, maxSimplificationError, maxPolyVerts,
buildMeshDetail, detailSampleDist, detailSampleMaxError, walkbableAreaMod);
}
public static VoxelFile from(RecastConfig config, List<RecastBuilderResult> results) {
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) {
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]);
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)
{
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
{
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 {
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);
}
buf.order(buf.order() == ByteOrder.BIG_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
}
file.version = buf.getInt();
bool isExportedFromAstar = (file.version & VoxelFile.VERSION_EXPORTER_MASK) == 0;
bool compression = (file.version & VoxelFile.VERSION_COMPRESSION_MASK) == VoxelFile.VERSION_COMPRESSION_LZ4;
file.walkableRadius = buf.getFloat();
file.walkableHeight = buf.getFloat();
file.walkableClimb = buf.getFloat();
file.walkableSlopeAngle = buf.getFloat();
file.cellSize = buf.getFloat();
file.maxSimplificationError = buf.getFloat();
file.maxEdgeLen = buf.getFloat();
file.minRegionArea = (int) buf.getFloat();
if (!isExportedFromAstar) {
file.regionMergeArea = buf.getFloat();
file.vertsPerPoly = buf.getInt();
file.buildMeshDetail = buf.get() != 0;
file.detailSampleDistance = 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];
file.version = buf.getInt();
bool isExportedFromAstar = (file.version & VoxelFile.VERSION_EXPORTER_MASK) == 0;
bool compression = (file.version & VoxelFile.VERSION_COMPRESSION_MASK) == VoxelFile.VERSION_COMPRESSION_LZ4;
file.walkableRadius = buf.getFloat();
file.walkableHeight = buf.getFloat();
file.walkableClimb = buf.getFloat();
file.walkableSlopeAngle = buf.getFloat();
file.cellSize = buf.getFloat();
file.maxSimplificationError = buf.getFloat();
file.maxEdgeLen = buf.getFloat();
file.minRegionArea = (int)buf.getFloat();
if (!isExportedFromAstar)
{
file.regionMergeArea = buf.getFloat();
file.vertsPerPoly = buf.getInt();
file.buildMeshDetail = buf.get() != 0;
file.detailSampleDistance = buf.getFloat();
file.detailSampleMaxError = buf.getFloat();
}
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);
else
{
file.regionMergeArea = 6 * file.minRegionArea;
file.vertsPerPoly = 6;
file.buildMeshDetail = true;
file.detailSampleDistance = file.maxEdgeLen * 0.5f;
file.detailSampleMaxError = file.maxSimplificationError * 0.8f;
}
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);
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();
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
{
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, f, VoxelFile.PREFERRED_BYTE_ORDER, compression);
}
public void write(BinaryWriter stream, VoxelFile f, ByteOrder byteOrder, bool compression) {
write(stream, VoxelFile.MAGIC, byteOrder);
write(stream, VoxelFile.VERSION_EXPORTER_RECAST4J | (compression ? VoxelFile.VERSION_COMPRESSION_LZ4 : 0), byteOrder);
write(stream, f.walkableRadius, byteOrder);
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);
write(stream, bytes.Length, byteOrder);
stream.Write(bytes);
}
}
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
{
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 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) {
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;
}
}
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);
}
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;
}
}
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);
}
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;
}
}
public Heightfield heightfield()
{
return VoxelFile.PREFERRED_BYTE_ORDER == ByteOrder.BIG_ENDIAN ? heightfieldBE() : heightfieldLE();
}
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;
private Heightfield heightfieldBE()
{
Heightfield hf = new Heightfield(width, depth, boundsMin, boundsMax, cellSize, cellHeight, borderSize);
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;
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 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
{
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;
private readonly ICollection<DynamicTile> _affectedTiles;
public RemoveColliderQueueItem(long colliderId, ICollection<DynamicTile> affectedTiles) {
this.colliderId = colliderId;
this._affectedTiles = affectedTiles;
public void process(DynamicTile tile)
{
tile.removeCollider(colliderId);
}
}
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
{
public interface UpdateQueueItem
{
ICollection<DynamicTile> affectedTiles();
public interface UpdateQueueItem {
ICollection<DynamicTile> affectedTiles();
void process(DynamicTile tile);
}
void process(DynamicTile tile);
}
}

View File

@ -21,144 +21,163 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic
{
/**
/**
* Voxel raycast based on the algorithm described in
*
* "A Fast Voxel Traversal Algorithm for Ray Tracing" by John Amanatides and Andrew Woo
*/
public class VoxelQuery {
public class VoxelQuery
{
private readonly float[] origin;
private readonly float tileWidth;
private readonly float tileDepth;
private readonly Func<int, int, Heightfield> heightfieldProvider;
private readonly float[] origin;
private readonly float tileWidth;
private readonly float tileDepth;
private readonly Func<int, int, Heightfield> heightfieldProvider;
public VoxelQuery(float[] origin, float tileWidth, float tileDepth, Func<int, int, Heightfield> heightfieldProvider)
{
this.origin = origin;
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.
*
* @return Optional with hit parameter (t) or empty if no hit found
*/
public float? raycast(float[] start, float[] 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;
}
public float? raycast(float[] start, float[] end)
{
return traverseTiles(start, end);
}
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);
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 = (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);
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 = hf.cs / tx;
float tDeltaZ = hf.cs / tz;
float tDeltaX = tileWidth / tx;
float tDeltaZ = tileDepth / 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;
}
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)) {
if ((dx > 0 ? sx >= ex : sx <= ex) && (dz > 0 ? sz >= ez : sz <= ez))
{
break;
}
if (tMaxX < tMaxZ) {
if (tMaxX < tMaxZ)
{
t = tMaxX;
tMaxX += tDeltaX;
sx += stepX;
} else {
}
else
{
t = tMaxZ;
tMaxZ += tDeltaZ;
sz += stepZ;
}
}
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
{
public class BVTreeBuilder {
public void build(MeshData data) {
data.bvTree = new BVNode[data.header.polyCount * 2];
data.header.bvNodeCount = data.bvTree.Length == 0 ? 0
public class BVTreeBuilder
{
public void build(MeshData data)
{
data.bvTree = new BVNode[data.header.polyCount * 2];
data.header.bvNodeCount = data.bvTree.Length == 0
? 0
: createBVTree(data, data.bvTree, data.header.bvQuantFactor);
}
private static int createBVTree(MeshData data, BVNode[] nodes, float quantFactor) {
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">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\DotRecast.Recast\DotRecast.Recast.csproj" />
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotRecast.Recast\DotRecast.Recast.csproj"/>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj"/>
</ItemGroup>
</Project>

View File

@ -1,52 +1,53 @@
using System;
using DotRecast.Recast;
using static DotRecast.Detour.DetourCommon;
namespace DotRecast.Detour.Extras.Jumplink
{
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)
public abstract class AbstractGroundSampler : GroundSampler
{
throw new NotImplementedException();
}
protected void sampleGroundSegment(Func<float[], float, Tuple<bool, float>> heightFunc, GroundSegment seg,
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;
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 sampleGroundSegment(Func<float[], float, Tuple<bool, float>> heightFunc, GroundSegment seg,
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
{
public class ClimbTrajectory : Trajectory {
public override float[] apply(float[] start, float[] end, float u) {
return new float[] { lerp(start[0], end[0], Math.Min(2f * u, 1f)),
public class ClimbTrajectory : Trajectory
{
public override float[] apply(float[] start, float[] end, float u)
{
return new float[]
{
lerp(start[0], end[0], Math.Min(2f * u, 1f)),
lerp(start[1], end[1], Math.Max(0f, 2f * u - 1f)),
lerp(start[2], end[2], Math.Min(2f * u, 1f)) };
lerp(start[2], end[2], Math.Min(2f * u, 1f))
};
}
}
}
}

View File

@ -1,10 +1,8 @@
namespace DotRecast.Detour.Extras.Jumplink
{
public class Edge {
public readonly float[] sp = new float[3];
public readonly float[] sq = new float[3];
}
public class Edge
{
public readonly float[] sp = 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
{
public class EdgeExtractor {
public Edge[] extractEdges(PolyMesh mesh) {
List<Edge> edges = new List<Edge>();
if (mesh != null) {
float[] orig = mesh.bmin;
float cs = mesh.cs;
float ch = mesh.ch;
for (int i = 0; i < mesh.npolys; i++) {
if (i > 41 || i < 41) {
// continue;
}
int nvp = mesh.nvp;
int p = i * 2 * nvp;
for (int j = 0; j < nvp; ++j) {
if (j != 1) {
// continue;
public class EdgeExtractor
{
public Edge[] extractEdges(PolyMesh mesh)
{
List<Edge> edges = new List<Edge>();
if (mesh != null)
{
float[] orig = mesh.bmin;
float cs = mesh.cs;
float ch = mesh.ch;
for (int i = 0; i < mesh.npolys; i++)
{
if (i > 41 || i < 41)
{
// 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 nvp = mesh.nvp;
int p = i * 2 * nvp;
for (int j = 0; j < nvp; ++j)
{
if (j != 1)
{
// 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 static DotRecast.Detour.DetourCommon;
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 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 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);
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
{
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);
}
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;
}
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));
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);
float dx = acfg.endDistance - 2 * acfg.agentRadius;
float cs = acfg.cellSize;
int nsamples = Math.Max(2, (int)Math.Ceiling(dx / cs));
float dx = acfg.endDistance - 2 * acfg.agentRadius;
float cs = acfg.cellSize;
int nsamples = Math.Max(2, (int) Math.Ceiling(dx / cs));
for (int j = 0; j < nsamples; ++j)
{
float v = (float)j / (float)(nsamples - 1);
float ox = 2 * acfg.agentRadius + dx * v;
trans2d(offset, es.az, es.ay, new float[] { ox, acfg.minHeight });
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) {
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 });
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];
}
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
{
public class GroundSample {
public readonly float[] p = new float[3];
public bool validTrajectory;
public bool validHeight;
}
public class GroundSample
{
public readonly float[] p = new float[3];
public bool validTrajectory;
public bool validHeight;
}
}

View File

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

View File

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

View File

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

View File

@ -7,82 +7,90 @@ using static DotRecast.Detour.DetourCommon;
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 {
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 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));
}
public JumpLinkBuilder(List<RecastBuilderResult> results)
{
this.results = results;
edges = results.Select(r => edgeExtractor.extractEdges(r.getMesh())).ToList();
}
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];
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)
{
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
{
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 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 JumpLinkBuilderConfig(float cellSize, float cellHeight, float agentRadius, float agentHeight,
float agentClimb, float groundTolerance, float startDistance, float endDistance, float minHeight,
float maxHeight, float jumpHeight) {
this.cellSize = cellSize;
this.cellHeight = cellHeight;
this.agentRadius = agentRadius;
this.agentClimb = agentClimb;
this.groundTolerance = groundTolerance;
this.agentHeight = agentHeight;
this.startDistance = startDistance;
this.endDistance = endDistance;
this.minHeight = minHeight;
heightRange = maxHeight - minHeight;
this.jumpHeight = jumpHeight;
float maxHeight, float jumpHeight)
{
this.cellSize = cellSize;
this.cellHeight = cellHeight;
this.agentRadius = agentRadius;
this.agentClimb = agentClimb;
this.groundTolerance = groundTolerance;
this.agentHeight = agentHeight;
this.startDistance = startDistance;
this.endDistance = endDistance;
this.minHeight = minHeight;
heightRange = maxHeight - minHeight;
this.jumpHeight = jumpHeight;
}
}
}
}

View File

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

View File

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

View File

@ -1,99 +1,130 @@
using System;
using System.Collections.Generic;
using DotRecast.Core;
namespace DotRecast.Detour.Extras.Jumplink
{
class JumpSegmentBuilder {
public JumpSegment[] build(JumpLinkBuilderConfig acfg, EdgeSampler es) {
int n = es.end[0].gsamples.Length;
int[][] sampleGrid = ArrayUtils.Of<int>(n, es.end.Count);
for (int j = 0; j < es.end.Count; j++) {
for (int i = 0; i < n; i++) {
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++;
}
class JumpSegmentBuilder
{
public JumpSegment[] build(JumpLinkBuilderConfig acfg, EdgeSampler es)
{
int n = es.end[0].gsamples.Length;
int[][] sampleGrid = ArrayUtils.Of<int>(n, es.end.Count);
for (int j = 0; j < es.end.Count; j++)
{
for (int i = 0; i < n; i++)
{
sampleGrid[i][j] = -1;
}
}
}
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;
// 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++;
}
}
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) {
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);
}
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 });
}
}
}
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
{
public class JumpTrajectory : Trajectory
{
private readonly float jumpHeight;
public class JumpTrajectory : Trajectory {
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;
public JumpTrajectory(float jumpHeight)
{
this.jumpHeight = jumpHeight;
}
float h1, h2;
if (ys >= ye) { // jump down
h1 = jumpHeight;
h2 = jumpHeight + ys - ye;
} else { // jump up
h1 = jumpHeight + ys - ye;
h2 = 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)
};
}
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;
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
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
{
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
class NavMeshGroundSampler : AbstractGroundSampler
{
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 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);
}
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;
}
}));
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
{
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)
public class Trajectory
{
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
{
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 {
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;
ssmp.validTrajectory = true;
esmp.validTrajectory = true;
}
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) {
float cs = Math.Min(acfg.cellSize, acfg.cellHeight);
float d = vDist2D(pa, pb) + Math.Abs(pa[1] - pb[1]);
int nsamples = Math.Max(2, (int) Math.Ceiling(d / cs));
for (int i = 0; i < nsamples; ++i) {
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])) {
private bool sampleTrajectory(JumpLinkBuilderConfig acfg, Heightfield solid, float[] pa, float[] pb, Trajectory tra)
{
float cs = Math.Min(acfg.cellSize, acfg.cellHeight);
float d = vDist2D(pa, pb) + Math.Abs(pa[1] - pb[1]);
int nsamples = Math.Max(2, (int)Math.Ceiling(d / cs));
for (int i = 0; i < nsamples; ++i)
{
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 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;
}
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;
Span s = solid.spans[ix + iz * w];
if (s == null)
{
return false;
}
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
{
public class ObjExporter {
public void export(NavMesh mesh) {
string filename = Path.Combine(Directory.GetCurrentDirectory(), "Demo", "astar.obj");
using var fs = new FileStream(filename, FileMode.CreateNew);
using var fw = new StreamWriter(fs);
for (int i = 0; i < mesh.getTileCount(); i++) {
MeshTile tile = mesh.getTile(i);
if (tile != null) {
for (int v = 0; v < tile.data.header.vertCount; v++) {
fw.Write("v " + tile.data.verts[v * 3] + " " + tile.data.verts[v * 3 + 1] + " "
+ tile.data.verts[v * 3 + 2] + "\n");
}
}
}
int vertexOffset = 1;
for (int i = 0; i < mesh.getTileCount(); i++) {
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 + " ");
public class ObjExporter
{
public void export(NavMesh mesh)
{
string filename = Path.Combine(Directory.GetCurrentDirectory(), "Demo", "astar.obj");
using var fs = new FileStream(filename, FileMode.CreateNew);
using var fw = new StreamWriter(fs);
for (int i = 0; i < mesh.getTileCount(); i++)
{
MeshTile tile = mesh.getTile(i);
if (tile != null)
{
for (int v = 0; v < tile.data.header.vertCount; v++)
{
fw.Write("v " + tile.data.verts[v * 3] + " " + tile.data.verts[v * 3 + 1] + " "
+ tile.data.verts[v * 3 + 2] + "\n");
}
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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.Extras
{
public class PolyUtils {
/**
public class PolyUtils
{
/**
* Find edge shared by 2 polygons within the same tile
*/
public static int findEdge(Poly node, Poly neighbour, MeshData tile, MeshData neighbourTile) {
// Compare indices first assuming there are no duplicate vertices
for (int i = 0; i < node.vertCount; i++) {
int j = (i + 1) % node.vertCount;
for (int k = 0; k < neighbour.vertCount; k++) {
int l = (k + 1) % neighbour.vertCount;
if ((node.verts[i] == neighbour.verts[l] && node.verts[j] == neighbour.verts[k])
|| (node.verts[i] == neighbour.verts[k] && node.verts[j] == neighbour.verts[l])) {
return i;
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++)
{
int j = (i + 1) % node.vertCount;
for (int k = 0; k < neighbour.vertCount; k++)
{
int l = (k + 1) % neighbour.vertCount;
if ((node.verts[i] == neighbour.verts[l] && node.verts[j] == neighbour.verts[k])
|| (node.verts[i] == neighbour.verts[k] && node.verts[j] == neighbour.verts[l]))
{
return i;
}
}
}
}
// Fall back to comparing actual positions in case of duplicate vertices
for (int i = 0; i < node.vertCount; i++) {
int j = (i + 1) % node.vertCount;
for (int k = 0; k < neighbour.vertCount; k++) {
int l = (k + 1) % neighbour.vertCount;
if ((samePosition(tile.verts, node.verts[i], neighbourTile.verts, neighbour.verts[l])
&& samePosition(tile.verts, node.verts[j], neighbourTile.verts, neighbour.verts[k]))
// Fall back to comparing actual positions in case of duplicate vertices
for (int i = 0; i < node.vertCount; i++)
{
int j = (i + 1) % node.vertCount;
for (int k = 0; k < neighbour.vertCount; k++)
{
int l = (k + 1) % neighbour.vertCount;
if ((samePosition(tile.verts, node.verts[i], neighbourTile.verts, neighbour.verts[l])
&& samePosition(tile.verts, node.verts[j], neighbourTile.verts, neighbour.verts[k]))
|| (samePosition(tile.verts, node.verts[i], neighbourTile.verts, neighbour.verts[k])
&& samePosition(tile.verts, node.verts[j], neighbourTile.verts, neighbour.verts[l]))) {
return i;
&& samePosition(tile.verts, node.verts[j], neighbourTile.verts, neighbour.verts[l])))
{
return i;
}
}
}
}
return -1;
}
private static bool samePosition(float[] verts, int v, float[] verts2, int v2) {
for (int i = 0; i < 3; i++) {
if (verts[3 * v + i] != verts2[3 * v2 + 1]) {
return false;
return -1;
}
private static bool samePosition(float[] verts, int v, float[] verts2, int v2)
{
for (int i = 0; i < 3; i++)
{
if (verts[3 * v + i] != verts2[3 * v2 + 1])
{
return false;
}
}
}
return true;
}
/**
return true;
}
/**
* Find edge closest to the given coordinate
*/
public static int findEdge(Poly node, MeshData tile, float value, int comp) {
float error = float.MaxValue;
int edge = 0;
for (int i = 0; i < node.vertCount; i++) {
int j = (i + 1) % node.vertCount;
float v1 = tile.verts[3 * node.verts[i] + comp] - value;
float v2 = tile.verts[3 * node.verts[j] + comp] - value;
float d = v1 * v1 + v2 * v2;
if (d < error) {
error = d;
edge = i;
public static int findEdge(Poly node, MeshData tile, float value, int comp)
{
float error = float.MaxValue;
int edge = 0;
for (int i = 0; i < node.vertCount; i++)
{
int j = (i + 1) % node.vertCount;
float v1 = tile.verts[3 * node.verts[i] + comp] - value;
float v2 = tile.verts[3 * node.verts[j] + comp] - value;
float d = v1 * v1 + v2 * v2;
if (d < error)
{
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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.Extras.Unity.Astar
{
public class BVTreeCreator
{
private readonly BVTreeBuilder builder = new BVTreeBuilder();
public class BVTreeCreator {
private readonly BVTreeBuilder builder = new BVTreeBuilder();
public void build(GraphMeshData graphData) {
foreach (MeshData d in graphData.tiles) {
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
{
class GraphConnectionReader : ZipBinaryReader {
public List<int[]> read(ZipArchive file, string filename, Meta meta, int[] indexToNode) {
List<int[]> connections = new List<int[]>();
ByteBuffer buffer = toByteBuffer(file, filename);
while (buffer.remaining() > 0) {
int count = buffer.getInt();
int[] nodeConnections = new int[count];
connections.Add(nodeConnections);
for (int i = 0; i < count; i++) {
int nodeIndex = buffer.getInt();
nodeConnections[i] = indexToNode[nodeIndex];
// XXX: Is there anything we can do with the cost?
int cost = buffer.getInt();
if (meta.isVersionAtLeast(Meta.UPDATED_STRUCT_VERSION)) {
byte shapeEdge = buffer.get();
class GraphConnectionReader : ZipBinaryReader
{
public List<int[]> read(ZipArchive file, string filename, Meta meta, int[] indexToNode)
{
List<int[]> connections = new List<int[]>();
ByteBuffer buffer = toByteBuffer(file, filename);
while (buffer.remaining() > 0)
{
int count = buffer.getInt();
int[] nodeConnections = new int[count];
connections.Add(nodeConnections);
for (int i = 0; i < count; i++)
{
int nodeIndex = buffer.getInt();
nodeConnections[i] = indexToNode[nodeIndex];
// XXX: Is there anything we can do with the cost?
int cost = buffer.getInt();
if (meta.isVersionAtLeast(Meta.UPDATED_STRUCT_VERSION))
{
byte shapeEdge = buffer.get();
}
}
}
return connections;
}
return connections;
}
}
}

View File

@ -20,29 +20,24 @@ using System.Collections.Generic;
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 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) {
this.meta = meta;
this.indexToNode = indexToNode;
this.nodeLinks2 = nodeLinks2;
this.graphMeta = graphMeta;
this.graphMeshData = graphMeshData;
this.graphConnections = 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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.Extras.Unity.Astar
{
public class GraphMeshData
{
public readonly int tileXCount;
public readonly int tileZCount;
public readonly MeshData[] tiles;
public class GraphMeshData {
public readonly int tileXCount;
public readonly int tileZCount;
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;
public GraphMeshData(int tileXCount, int tileZCount, MeshData[] tiles)
{
this.tileXCount = tileXCount;
this.tileZCount = tileZCount;
this.tiles = tiles;
}
return polyCount;
}
public Poly getNode(int node) {
int index = 0;
foreach (MeshData t in tiles) {
if (node - index >= 0 && node - index < t.header.polyCount) {
return t.polys[node - index];
public int countNodes()
{
int polyCount = 0;
foreach (MeshData t in tiles)
{
polyCount += 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;
return polyCount;
}
public Poly getNode(int node)
{
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
{
public class GraphMeshDataReader : ZipBinaryReader
{
public const float INT_PRECISION_FACTOR = 1000f;
public class GraphMeshDataReader : ZipBinaryReader {
public const float INT_PRECISION_FACTOR = 1000f;
public GraphMeshData read(ZipArchive file, string filename, GraphMeta meta, int maxVertPerPoly) {
ByteBuffer buffer = toByteBuffer(file, filename);
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;
public GraphMeshData read(ZipArchive file, string filename, GraphMeta meta, int maxVertPerPoly)
{
ByteBuffer buffer = toByteBuffer(file, filename);
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;
}
}
return new GraphMeshData(tileXCount, tileZCount, tiles);
}
return new GraphMeshData(tileXCount, tileZCount, tiles);
}
public static int highestOneBit(uint i)
{
i |= (i >> 1);
i |= (i >> 2);
i |= (i >> 4);
i |= (i >> 8);
i |= (i >> 16);
return (int)(i - (i >> 1));
}
// See NavmeshBase.cs: ASTAR_RECAST_LARGER_TILES
private int getVertMask(int vertsCount)
{
int vertMask = highestOneBit((uint)vertsCount);
if (vertMask != vertsCount) {
vertMask *= 2;
public static int highestOneBit(uint i)
{
i |= (i >> 1);
i |= (i >> 2);
i |= (i >> 4);
i |= (i >> 8);
i |= (i >> 16);
return (int)(i - (i >> 1));
}
// See NavmeshBase.cs: ASTAR_RECAST_LARGER_TILES
private int getVertMask(int vertsCount)
{
int vertMask = highestOneBit((uint)vertsCount);
if (vertMask != vertsCount)
{
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.
3. This notice may not be removed or altered from any source distribution.
*/
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 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 Vector3f rotation { get; set; }
public Vector3f forcedBoundsCenter { get; set; }
public Vector3f forcedBoundsSize { 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
{
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 {
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);
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
{
public class LinkBuilder {
// Process connections and transform them into recast neighbour flags
public void build(int nodeOffset, GraphMeshData graphData, List<int[]> connections) {
for (int n = 0; n < connections.Count; n++) {
int[] nodeConnections = connections[n];
MeshData tile = graphData.getTile(n);
Poly node = graphData.getNode(n);
foreach (int connection in nodeConnections) {
MeshData neighbourTile = graphData.getTile(connection - nodeOffset);
if (neighbourTile != tile) {
buildExternalLink(tile, node, neighbourTile);
} else {
Poly neighbour = graphData.getNode(connection - nodeOffset);
buildInternalLink(tile, node, neighbourTile, neighbour);
public class LinkBuilder
{
// Process connections and transform them into recast neighbour flags
public void build(int nodeOffset, GraphMeshData graphData, List<int[]> connections)
{
for (int n = 0; n < connections.Count; n++)
{
int[] nodeConnections = connections[n];
MeshData tile = graphData.getTile(n);
Poly node = graphData.getNode(n);
foreach (int connection in nodeConnections)
{
MeshData neighbourTile = graphData.getTile(connection - nodeOffset);
if (neighbourTile != tile)
{
buildExternalLink(tile, node, neighbourTile);
}
else
{
Poly neighbour = graphData.getNode(connection - nodeOffset);
buildInternalLink(tile, node, neighbourTile, neighbour);
}
}
}
}
}
private void buildInternalLink(MeshData tile, Poly node, MeshData neighbourTile, Poly neighbour) {
int edge = PolyUtils.findEdge(node, neighbour, tile, neighbourTile);
if (edge >= 0) {
node.neis[edge] = neighbour.index + 1;
} else {
throw new ArgumentException();
private void buildInternalLink(MeshData tile, Poly node, MeshData neighbourTile, Poly neighbour)
{
int edge = PolyUtils.findEdge(node, neighbour, tile, neighbourTile);
if (edge >= 0)
{
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
{
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 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() {
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;
}
public bool isSupportedVersion()
{
return isVersionAtLeast(MIN_SUPPORTED_VERSION);
}
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);
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 v;
}
throw new ArgumentException("Invalid version format: " + version);
}
public bool isSupportedType() {
foreach (string t in typeNames) {
if (t == TYPENAME_RECAST_GRAPH) {
return true;
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
{
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) {
ZipArchiveEntry entry = file.GetEntry(filename);
using StreamReader reader = new StreamReader(entry.Open());
// fixed : version 표기는 문자열이여야 한다
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));
}
var json = reader.ReadToEnd();
if (!meta.isSupportedVersion())
{
throw new ArgumentException("Unsupported version " + meta.version);
}
// fixed : version 표기는 문자열이여야 한다
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));
return meta;
}
if (!meta.isSupportedVersion()) {
throw new ArgumentException("Unsupported version " + meta.version);
}
return meta;
}
}
}

View File

@ -21,22 +21,21 @@ using DotRecast.Core;
namespace DotRecast.Detour.Extras.Unity.Astar
{
class NodeIndexReader : ZipBinaryReader
{
public int[] read(ZipArchive file, string filename)
{
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++;
}
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;
}
return int2Node;
}
}
}

View File

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

View File

@ -20,48 +20,57 @@ using DotRecast.Core;
namespace DotRecast.Detour.Extras.Unity.Astar
{
public class OffMeshLinkCreator {
public void build(GraphMeshData graphData, NodeLink2[] links, int nodeOffset) {
if (links.Length > 0) {
foreach (NodeLink2 l in links) {
MeshData startTile = graphData.getTile(l.startNode - nodeOffset);
Poly startNode = graphData.getNode(l.startNode - nodeOffset);
MeshData endTile = graphData.getTile(l.endNode - nodeOffset);
Poly endNode = graphData.getNode(l.endNode - nodeOffset);
if (startNode != null && endNode != null) {
// FIXME: Optimise
startTile.polys = ArrayUtils.CopyOf(startTile.polys, startTile.polys.Length + 1);
int poly = startTile.header.polyCount;
startTile.polys[poly] = new Poly(poly, 2);
startTile.polys[poly].verts[0] = startTile.header.vertCount;
startTile.polys[poly].verts[1] = startTile.header.vertCount + 1;
startTile.polys[poly].setType(Poly.DT_POLYTYPE_OFFMESH_CONNECTION);
startTile.verts = ArrayUtils.CopyOf(startTile.verts, startTile.verts.Length + 6);
startTile.header.polyCount++;
startTile.header.vertCount += 2;
OffMeshConnection connection = new OffMeshConnection();
connection.poly = poly;
connection.pos = new float[] { l.clamped1.x, l.clamped1.y, l.clamped1.z, l.clamped2.x, l.clamped2.y,
l.clamped2.z };
connection.rad = 0.1f;
connection.side = startTile == endTile ? 0xFF
public class OffMeshLinkCreator
{
public void build(GraphMeshData graphData, NodeLink2[] links, int nodeOffset)
{
if (links.Length > 0)
{
foreach (NodeLink2 l in links)
{
MeshData startTile = graphData.getTile(l.startNode - nodeOffset);
Poly startNode = graphData.getNode(l.startNode - nodeOffset);
MeshData endTile = graphData.getTile(l.endNode - nodeOffset);
Poly endNode = graphData.getNode(l.endNode - nodeOffset);
if (startNode != null && endNode != null)
{
// FIXME: Optimise
startTile.polys = ArrayUtils.CopyOf(startTile.polys, startTile.polys.Length + 1);
int poly = startTile.header.polyCount;
startTile.polys[poly] = new Poly(poly, 2);
startTile.polys[poly].verts[0] = startTile.header.vertCount;
startTile.polys[poly].verts[1] = startTile.header.vertCount + 1;
startTile.polys[poly].setType(Poly.DT_POLYTYPE_OFFMESH_CONNECTION);
startTile.verts = ArrayUtils.CopyOf(startTile.verts, startTile.verts.Length + 6);
startTile.header.polyCount++;
startTile.header.vertCount += 2;
OffMeshConnection connection = new OffMeshConnection();
connection.poly = poly;
connection.pos = new float[]
{
l.clamped1.x, l.clamped1.y, l.clamped1.z, l.clamped2.x, l.clamped2.y,
l.clamped2.z
};
connection.rad = 0.1f;
connection.side = startTile == endTile
? 0xFF
: NavMeshBuilder.classifyOffMeshPoint(new VectorPtr(connection.pos, 3),
startTile.header.bmin, startTile.header.bmax);
connection.userId = (int) l.linkID;
if (startTile.offMeshCons == null) {
startTile.offMeshCons = new OffMeshConnection[1];
} else {
startTile.offMeshCons = ArrayUtils.CopyOf(startTile.offMeshCons, startTile.offMeshCons.Length + 1);
startTile.header.bmin, startTile.header.bmax);
connection.userId = (int)l.linkID;
if (startTile.offMeshCons == null)
{
startTile.offMeshCons = new OffMeshConnection[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
{
/**
/**
* Import navmeshes created with A* Pathfinding Project Unity plugin (https://arongranberg.com/astar/). Graph data is
* loaded from a zip archive and converted to Recast navmesh objects.
*/
public class UnityAStarPathfindingImporter {
public class UnityAStarPathfindingImporter
{
private readonly UnityAStarPathfindingReader reader = new UnityAStarPathfindingReader();
private readonly BVTreeCreator bvTreeCreator = new BVTreeCreator();
private readonly LinkBuilder linkCreator = new LinkBuilder();
private readonly OffMeshLinkCreator offMeshLinkCreator = new OffMeshLinkCreator();
private readonly UnityAStarPathfindingReader reader = new UnityAStarPathfindingReader();
private readonly BVTreeCreator bvTreeCreator = new BVTreeCreator();
private readonly LinkBuilder linkCreator = new LinkBuilder();
private readonly OffMeshLinkCreator offMeshLinkCreator = new OffMeshLinkCreator();
public NavMesh[] load(FileStream zipFile)
{
GraphData graphData = reader.read(zipFile);
Meta meta = graphData.meta;
NodeLink2[] nodeLinks2 = graphData.nodeLinks2;
NavMesh[] meshes = new NavMesh[meta.graphs];
int nodeOffset = 0;
for (int graphIndex = 0; graphIndex < meta.graphs; graphIndex++)
{
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) {
GraphData graphData = reader.read(zipFile);
Meta meta = graphData.meta;
NodeLink2[] nodeLinks2 = graphData.nodeLinks2;
NavMesh[] meshes = new NavMesh[meta.graphs];
int nodeOffset = 0;
for (int graphIndex = 0; graphIndex < meta.graphs; graphIndex++) {
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);
// Build BV tree
bvTreeCreator.build(graphMeshData);
// 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();
}
// Build BV tree
bvTreeCreator.build(graphMeshData);
// 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;
}
return meshes;
}
}
}

View File

@ -22,50 +22,50 @@ using System.IO.Compression;
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 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) {
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,
public GraphData read(FileStream zipFile)
{
using ZipArchive file = new ZipArchive(zipFile);
// Read meta file and check version and graph type
Meta meta = metaReader.read(file, META_FILE_NAME);
// 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);
// Then graph connection data - links between nodes located in both the same tile and other tiles
List<int[]> connections = graphConnectionReader.read(file,
// Then graph connection data - links between nodes located in both the same tile and other tiles
List<int[]> connections = graphConnectionReader.read(file,
string.Format(GRAPH_CONNECTION_FILE_NAME_PATTERN, graphIndex), meta, indexToNode);
metaList.Add(graphMeta);
meshDataList.Add(graphData);
connectionsList.Add(connections);
}
return new GraphData(meta, indexToNode, nodeLinks2, metaList, meshDataList, connectionsList);
}
}
metaList.Add(graphMeta);
meshDataList.Add(graphData);
connectionsList.Add(connections);
}
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
{
public abstract class ZipBinaryReader {
protected ByteBuffer toByteBuffer(ZipArchive file, string filename) {
ZipArchiveEntry graphReferences = file.GetEntry(filename);
using var entryStream = graphReferences.Open();
using var bis = new BinaryReader(entryStream);
ByteBuffer buffer = IOUtils.toByteBuffer(bis);
buffer.order(ByteOrder.LITTLE_ENDIAN);
return buffer;
public abstract class ZipBinaryReader
{
protected ByteBuffer toByteBuffer(ZipArchive file, string filename)
{
ZipArchiveEntry graphReferences = file.GetEntry(filename);
using var entryStream = graphReferences.Open();
using var bis = new BinaryReader(entryStream);
ByteBuffer buffer = IOUtils.toByteBuffer(bis);
buffer.order(ByteOrder.LITTLE_ENDIAN);
return buffer;
}
}
}
}

View File

@ -15,25 +15,24 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.Extras
{
public class Vector3f
{
public float x { get; set; }
public float y { get; set; }
public float z { get; set; }
public Vector3f()
{
}
public class Vector3f {
public float x { get; set; }
public float y { get; set; }
public float z { get; set; }
public Vector3f() {
public Vector3f(float x, float y, float z)
{
this.x = x;
this.y = y;
this.z = z;
}
}
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
{
public abstract class AbstractTileLayersBuilder {
protected List<byte[]> build(ByteOrder order, bool cCompatibility, int threads, int tw, int th) {
if (threads == 1) {
return buildSingleThread(order, cCompatibility, tw, th);
}
return buildMultiThread(order, cCompatibility, tw, th, threads);
}
private List<byte[]> buildSingleThread(ByteOrder order, bool cCompatibility, int tw, int th) {
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));
public abstract class AbstractTileLayersBuilder
{
protected List<byte[]> build(ByteOrder order, bool cCompatibility, int threads, int tw, int th)
{
if (threads == 1)
{
return buildSingleThread(order, cCompatibility, tw, th);
}
}
return layers;
}
private List<byte[]> buildMultiThread(ByteOrder order, bool cCompatibility, int tw, int th, int threads) {
var tasks = new ConcurrentQueue<Task<Tuple<int, int, List<byte[]>>>>();
for (int y = 0; y < th; ++y) {
for (int x = 0; x < tw; ++x) {
int tx = x;
int ty = y;
var task = Task.Run(() => {
var partial= build(tx, ty, order, cCompatibility);
return Tuple.Create(tx, ty, partial);
});
tasks.Enqueue(task);
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;
}
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]);
private List<byte[]> buildMultiThread(ByteOrder order, bool cCompatibility, int tw, int th, int threads)
{
var tasks = new ConcurrentQueue<Task<Tuple<int, int, List<byte[]>>>>();
for (int y = 0; y < th; ++y)
{
for (int x = 0; x < tw; ++x)
{
int tx = x;
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.
3. This notice may not be removed or altered from any source distribution.
*/
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 readonly int index;
public int salt; /// < Counter describing modifications to the tile.
public TileCacheLayerHeader header;
public byte[] data;
public int compressed; // offset of compressed data
public int flags;
public CompressedTile next;
public byte[] data;
public int compressed; // offset of compressed data
public int flags;
public CompressedTile next;
public CompressedTile(int index) {
this.index = index;
salt = 1;
public CompressedTile(int index)
{
this.index = index;
salt = 1;
}
}
}
}

View File

@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<ItemGroup>
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.5" />
</ItemGroup>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="K4os.Compression.LZ4" Version="1.3.5"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotRecast.Detour\DotRecast.Detour.csproj"/>
</ItemGroup>
</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
{
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 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)
{
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);
}
}
public byte[] compress(byte[] buf) {
byte[] output = new byte[FastLz.calculateOutputBufferLength(buf.Length)];
int len = FastLz.compress(buf, 0, buf.Length, output, 0, output.Length);
return ArrayUtils.CopyOf(output, len);
}
}
}

View File

@ -22,18 +22,16 @@ using K4os.Compression.LZ4;
namespace DotRecast.Detour.TileCache.Io.Compress
{
public class LZ4TileCacheCompressor : TileCacheCompressor
{
public byte[] decompress(byte[] buf, int offset, int len, int outputlen)
{
return LZ4Pickler.Unpickle(buf, offset, len);
}
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)
{
return LZ4Pickler.Pickle(buf);
}
}
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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache.Io.Compress
{
public class TileCacheCompressorFactory {
public static TileCacheCompressor get(bool cCompatibility)
public class TileCacheCompressorFactory
{
if (cCompatibility)
return new FastLzTileCacheCompressor();
return new LZ4TileCacheCompressor();
}
}
public static TileCacheCompressor get(bool cCompatibility)
{
if (cCompatibility)
return new FastLzTileCacheCompressor();
return new LZ4TileCacheCompressor();
}
}
}

View File

@ -23,43 +23,46 @@ using DotRecast.Core;
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) {
TileCacheLayerHeader header = new TileCacheLayerHeader();
header.magic = data.getInt();
header.version = data.getInt();
for (int j = 0; j < 3; j++)
{
header.bmax[j] = data.getFloat();
}
if (header.magic != TileCacheLayerHeader.DT_TILECACHE_MAGIC)
throw new IOException("Invalid magic");
if (header.version != TileCacheLayerHeader.DT_TILECACHE_VERSION)
throw new IOException("Invalid version");
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
}
header.tx = data.getInt();
header.ty = data.getInt();
header.tlayer = data.getInt();
for (int j = 0; j < 3; j++) {
header.bmin[j] = data.getFloat();
return header;
}
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
{
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 {
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);
}
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
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
{
public class TileCacheReader
{
private readonly NavMeshParamReader paramReader = new NavMeshParamReader();
public class TileCacheReader {
private readonly NavMeshParamReader paramReader = new NavMeshParamReader();
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);
public TileCache read(BinaryReader @is, int maxVertPerPoly, TileCacheMeshProcess meshProcessor)
{
ByteBuffer bb = IOUtils.toByteBuffer(@is);
return read(bb, maxVertPerPoly, meshProcessor);
}
header.version = bb.getInt();
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION) {
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION_RECAST4J) {
throw new IOException("Invalid version");
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);
}
}
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,
header.version = bb.getInt();
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION)
{
if (header.version != TileCacheSetHeader.TILECACHESET_VERSION_RECAST4J)
{
throw new IOException("Invalid version");
}
}
bool cCompatibility = header.version == TileCacheSetHeader.TILECACHESET_VERSION;
header.numTiles = bb.getInt();
header.meshParams = paramReader.read(bb);
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);
// Read tiles.
for (int i = 0; i < header.numTiles; ++i) {
long tileRef = bb.getInt();
int dataSize = bb.getInt();
if (tileRef == 0 || dataSize == 0) {
break;
// Read tiles.
for (int i = 0; i < header.numTiles; ++i)
{
long tileRef = bb.getInt();
int dataSize = bb.getInt();
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();
long tile = tc.addTile(data, 0);
if (tile != 0) {
tc.buildNavMeshTile(tile);
return tc;
}
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) {
TileCacheParams option = new TileCacheParams();
for (int i = 0; i < 3; i++) {
option.orig[i] = bb.getFloat();
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;
}
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.
3. This notice may not be removed or altered from any source distribution.
*/
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 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 int version;
public int numTiles;
public NavMeshParams meshParams = new NavMeshParams();
public TileCacheParams cacheParams = new TileCacheParams();
}
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
{
public class TileCacheWriter : DetourWriter
{
private readonly NavMeshParamWriter paramWriter = new NavMeshParamWriter();
private readonly TileCacheBuilder builder = new TileCacheBuilder();
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) {
write(stream, TileCacheSetHeader.TILECACHESET_MAGIC, order);
write(stream, cCompatibility ? 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);
int numTiles = 0;
for (int i = 0; i < cache.getTileCount(); ++i) {
CompressedTile tile = cache.getTile(i);
if (tile == null || tile.data == null)
continue;
numTiles++;
int numTiles = 0;
for (int i = 0; i < cache.getTileCount(); ++i)
{
CompressedTile tile = cache.getTile(i);
if (tile == null || tile.data == null)
continue;
numTiles++;
}
write(stream, numTiles, order);
paramWriter.write(stream, cache.getNavMesh().getParams(), order);
writeCacheParams(stream, cache.getParams(), order);
for (int i = 0; i < cache.getTileCount(); i++)
{
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);
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);
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);
}
}
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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache
{
public class ObstacleRequest {
public ObstacleRequestAction action;
public long refs;
}
public class ObstacleRequest
{
public ObstacleRequestAction action;
public long refs;
}
}

View File

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

View File

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

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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache
{
public interface TileCacheCompressor
{
byte[] decompress(byte[] buf, int offset, int len, int outputlen);
public interface TileCacheCompressor {
byte[] decompress(byte[] buf, int offset, int len, int outputlen);
byte[] compress(byte[] buf);
}
byte[] compress(byte[] buf);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,34 +22,34 @@ using System.Collections.Generic;
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 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 TileCacheObstacle(int index) {
salt = 1;
this.index = index;
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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour.TileCache
{
public class TileCacheParams {
public readonly float[] orig = new float[3];
public float cs, ch;
public int width, height;
public float walkableHeight;
public float walkableRadius;
public float walkableClimb;
public float maxSimplificationError;
public int maxTiles;
public int maxObstacles;
}
public class TileCacheParams
{
public readonly float[] orig = new float[3];
public float cs, ch;
public int width, height;
public float walkableHeight;
public float walkableRadius;
public float walkableClimb;
public float maxSimplificationError;
public int maxTiles;
public int maxObstacles;
}
}

View File

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

View File

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

View File

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

View File

@ -17,30 +17,30 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
public class ClosestPointOnPolyResult
{
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;
private readonly float[] closest;
public ClosestPointOnPolyResult(bool posOverPoly, float[] closest) {
this.posOverPoly = posOverPoly;
this.closest = closest;
/** Returns the closest point on the polygon. [(x, y, z)] */
public float[] getClosest()
{
return 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
{
using static DetourCommon;
using static DetourCommon;
/**
/**
* Convex-convex intersection based on "Computational Geometry in C" by Joseph O'Rourke
*/
public static class ConvexConvexIntersection {
public static class ConvexConvexIntersection
{
private static readonly float EPSILON = 0.0001f;
private static readonly float EPSILON = 0.0001f;
private enum InFlag
{
Pin,
Qin,
Unknown,
}
private enum InFlag {
Pin, Qin, Unknown,
}
private enum Intersection
{
None,
Single,
Overlap,
}
private enum Intersection {
None, Single, Overlap,
}
public static float[] intersect(float[] p, float[] q)
{
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 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];
int aa = 0;
int ba = 0;
int ai = 0;
int bi = 0;
int aa = 0;
int ba = 0;
int ai = 0;
int bi = 0;
InFlag f = InFlag.Unknown;
bool FirstPoint = true;
float[] ip = new float[3];
float[] iq = new float[3];
InFlag f = InFlag.Unknown;
bool FirstPoint = true;
float[] ip = new float[3];
float[] iq = new float[3];
do
{
vCopy(a, p, 3 * (ai % n));
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 {
vCopy(a, p, 3 * (ai % n));
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[] B = vSub(b, b1);
float[] A = vSub(a, a1);
float[] B = vSub(b, b1);
float cross = B[0] * A[2] - A[0] * B[2];// triArea2D({0, 0}, A, B);
float aHB = triArea2D(b1, b, a);
float bHA = triArea2D(a1, a, b);
if (Math.Abs(cross) < EPSILON) {
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;
float cross = B[0] * A[2] - A[0] * B[2]; // triArea2D({0, 0}, A, B);
float aHB = triArea2D(b1, b, a);
float bHA = triArea2D(a1, a, b);
if (Math.Abs(cross) < EPSILON)
{
cross = 0f;
}
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.Overlap && vDot2D(A, B) < 0) {
ii = addVertex(inters, ii, ip);
ii = addVertex(inters, ii, iq);
break;
}
if (code == Intersection.Single)
{
if (FirstPoint)
{
FirstPoint = false;
aa = ba = 0;
}
/* Special case: A & B parallel and separated. */
if (parallel && aHB < 0f && bHA < 0f) {
ii = addVertex(inters, ii, ip);
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;
}
/* 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++;
float[] copied = new float[ii];
Array.Copy(inters, copied, ii);
return copied;
}
private static int addVertex(float[] inters, int ii, float[] p)
{
if (ii > 0)
{
if (inters[ii - 3] == p[0] && inters[ii - 2] == p[1] && inters[ii - 1] == p[2])
{
return ii;
}
if (inters[0] == p[0] && inters[1] == p[1] && inters[2] == p[2])
{
return ii;
}
}
/* 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++;
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;
}
}
/* 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 Intersection.None;
}
float[] copied = new float[ii];
Array.Copy(inters, copied, ii);
return copied;
}
private static int addVertex(float[] inters, int ii, float[] p) {
if (ii > 0) {
if (inters[ii - 3] == p[0] && inters[ii - 2] == p[1] && inters[ii - 1] == p[2]) {
return ii;
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 (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
{
using static DetourCommon;
using static DetourCommon;
/**
/**
* <b>The Default Implementation</b>
*
* 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
*/
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;
private int m_includeFlags;
private readonly float[] m_areaCost = new float[NavMesh.DT_MAX_AREAS];
public DefaultQueryFilter()
{
m_includeFlags = 0xffff;
m_excludeFlags = 0;
for (int i = 0; i < NavMesh.DT_MAX_AREAS; ++i)
{
m_areaCost[i] = 1.0f;
}
}
public DefaultQueryFilter() {
m_includeFlags = 0xffff;
m_excludeFlags = 0;
for (int i = 0; i < NavMesh.DT_MAX_AREAS; ++i) {
m_areaCost[i] = 1.0f;
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;
}
}
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
{
using static DetourCommon;
using static DetourCommon;
public class DefaultQueryHeuristic : QueryHeuristic {
private readonly float scale;
public DefaultQueryHeuristic() : this(0.999f)
public class DefaultQueryHeuristic : QueryHeuristic
{
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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
public class DetourBuilder
{
public MeshData build(NavMeshDataCreateParams option, int tileX, int tileY)
{
MeshData data = NavMeshBuilder.createNavMeshData(option);
if (data != null)
{
data.header.x = tileX;
data.header.y = tileY;
}
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;
}
return data;
}
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -17,33 +17,36 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
//TODO: (PP) Add comments
public class FindDistanceToWallResult {
private readonly float distance;
private readonly float[] position;
private readonly float[] normal;
public class FindDistanceToWallResult
{
private readonly float distance;
private readonly float[] position;
private readonly float[] normal;
public FindDistanceToWallResult(float distance, float[] position, float[] normal) {
this.distance = distance;
this.position = position;
this.normal = normal;
public FindDistanceToWallResult(float distance, float[] position, float[] normal)
{
this.distance = distance;
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
{
//TODO: (PP) Add comments
public class FindLocalNeighbourhoodResult {
private readonly List<long> refs;
private readonly List<long> parentRefs;
public class FindLocalNeighbourhoodResult
{
private readonly List<long> refs;
private readonly List<long> parentRefs;
public FindLocalNeighbourhoodResult(List<long> refs, List<long> parentRefs) {
this.@refs = refs;
this.parentRefs = parentRefs;
public FindLocalNeighbourhoodResult(List<long> refs, List<long> 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
{
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 class FindNearestPolyQuery : PolyQuery {
private readonly NavMeshQuery query;
private readonly float[] center;
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);
public FindNearestPolyQuery(NavMeshQuery query, float[] center)
{
this.query = query;
this.center = center;
nearestDistanceSqr = float.MaxValue;
nearestPt = new float[] { center[0], center[1], center[2] };
}
if (d < nearestDistanceSqr) {
nearestPt = closestPtPoly;
nearestDistanceSqr = d;
nearestRef = refs;
overPoly = posOverPoly;
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)
{
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.
3. This notice may not be removed or altered from any source distribution.
*/
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 {
private readonly long nearestRef;
private readonly float[] nearestPos;
private readonly bool overPoly;
/** Returns the reference id of the nearest polygon. 0 if no polygon is found. */
public long getNearestRef()
{
return nearestRef;
}
public FindNearestPolyResult(long nearestRef, float[] nearestPos, bool overPoly) {
this.nearestRef = nearestRef;
this.nearestPos = nearestPos;
this.overPoly = overPoly;
/** 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;
}
}
/** 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
{
// TODO: (PP) Add comments
public class FindPolysAroundResult {
private readonly List<long> refs;
private readonly List<long> parentRefs;
private readonly List<float> costs;
public class FindPolysAroundResult
{
private readonly List<long> refs;
private readonly List<long> parentRefs;
private readonly List<float> costs;
public FindPolysAroundResult(List<long> refs, List<long> parentRefs, List<float> costs) {
this.@refs = refs;
this.parentRefs = parentRefs;
this.costs = costs;
public FindPolysAroundResult(List<long> refs, List<long> parentRefs, List<float> costs)
{
this.@refs = refs;
this.parentRefs = parentRefs;
this.costs = costs;
}
public List<long> getRefs()
{
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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
//TODO: (PP) Add comments
public class FindRandomPointResult {
private readonly long randomRef;
private readonly float[] randomPt;
public class FindRandomPointResult
{
private readonly long randomRef;
private readonly float[] randomPt;
public FindRandomPointResult(long randomRef, float[] randomPt) {
this.randomRef = randomRef;
this.randomPt = randomPt;
public FindRandomPointResult(long randomRef, float[] randomPt)
{
this.randomRef = randomRef;
this.randomPt = randomPt;
}
/// @param[out] randomRef The reference id of the random location.
public long getRandomRef()
{
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
{
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 {
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<long> getSegmentRefs()
{
return 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
{
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) {
byte[] bytes = BitConverter.GetBytes(value);
int i = BitConverter.ToInt32(bytes, 0);
write(stream, i, 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, 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));
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);
}
}
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
{
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 @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 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 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;
}
}
}

View File

@ -21,185 +21,236 @@ using DotRecast.Core;
namespace DotRecast.Detour.Io
{
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)
public class MeshDataReader
{
MeshData data = new MeshData();
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 DT_POLY_DETAIL_SIZE = 10;
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();
public MeshData read(BinaryReader stream, int maxVertPerPoly)
{
ByteBuffer buf = IOUtils.toByteBuffer(stream);
return read(buf, maxVertPerPoly, false);
}
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;
public MeshData read(ByteBuffer buf, int maxVertPerPoly)
{
return read(buf, maxVertPerPoly, false);
}
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
}
public MeshData read32Bit(BinaryReader stream, int maxVertPerPoly)
{
ByteBuffer buf = IOUtils.toByteBuffer(stream);
return read(buf, maxVertPerPoly, true);
}
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;
public MeshData read32Bit(ByteBuffer buf, int maxVertPerPoly)
{
return read(buf, maxVertPerPoly, true);
}
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;
public MeshData read(ByteBuffer buf, int maxVertPerPoly, bool is32Bit)
{
MeshData data = new MeshData();
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");
}
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();
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);
}
}
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();
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();
}
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();
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 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
{
public class MeshDataWriter : DetourWriter {
public void write(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility) {
MeshHeader header = data.header;
write(stream, header.magic, order);
write(stream, cCompatibility ? MeshHeader.DT_NAVMESH_VERSION : MeshHeader.DT_NAVMESH_VERSION_RECAST4J_LAST, order);
write(stream, header.x, order);
write(stream, header.y, order);
write(stream, header.layer, order);
write(stream, header.userId, order);
write(stream, header.polyCount, order);
write(stream, header.vertCount, order);
write(stream, header.maxLinkCount, order);
write(stream, header.detailMeshCount, order);
write(stream, header.detailVertCount, order);
write(stream, header.detailTriCount, order);
write(stream, header.bvNodeCount, order);
write(stream, header.offMeshConCount, order);
write(stream, header.offMeshBase, order);
write(stream, header.walkableHeight, order);
write(stream, header.walkableRadius, order);
write(stream, header.walkableClimb, order);
write(stream, header.bmin[0], order);
write(stream, header.bmin[1], order);
write(stream, header.bmin[2], order);
write(stream, header.bmax[0], order);
write(stream, header.bmax[1], order);
write(stream, header.bmax[2], order);
write(stream, header.bvQuantFactor, order);
writeVerts(stream, data.verts, header.vertCount, order);
writePolys(stream, data, order, 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)
public class MeshDataWriter : DetourWriter
{
public void write(BinaryWriter stream, MeshData data, ByteOrder order, bool cCompatibility)
{
MeshHeader header = data.header;
write(stream, header.magic, order);
write(stream, cCompatibility ? MeshHeader.DT_NAVMESH_VERSION : MeshHeader.DT_NAVMESH_VERSION_RECAST4J_LAST, order);
write(stream, header.x, order);
write(stream, header.y, order);
write(stream, header.layer, order);
write(stream, header.userId, order);
write(stream, header.polyCount, order);
write(stream, header.vertCount, order);
write(stream, header.maxLinkCount, order);
write(stream, header.detailMeshCount, order);
write(stream, header.detailVertCount, order);
write(stream, header.detailTriCount, order);
write(stream, header.bvNodeCount, order);
write(stream, header.offMeshConCount, order);
write(stream, header.offMeshBase, order);
write(stream, header.walkableHeight, order);
write(stream, header.walkableRadius, order);
write(stream, header.walkableClimb, order);
write(stream, header.bmin[0], order);
write(stream, header.bmin[1], order);
write(stream, header.bmin[2], order);
write(stream, header.bmax[0], order);
write(stream, header.bmax[1], order);
write(stream, header.bmax[2], order);
write(stream, header.bvQuantFactor, order);
writeVerts(stream, data.verts, header.vertCount, order);
writePolys(stream, data, order, cCompatibility);
if (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);
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++)
{
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
{
using static DetourCommon;
using static DetourCommon;
public class MeshSetReader
{
private readonly MeshDataReader meshReader = new MeshDataReader();
private readonly NavMeshParamReader paramReader = new NavMeshParamReader();
public class MeshSetReader {
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);
public NavMesh read(BinaryReader @is, int maxVertPerPoly)
{
return read(IOUtils.toByteBuffer(@is), maxVertPerPoly, false);
}
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) {
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);
public NavMesh read(ByteBuffer bb, int maxVertPerPoly)
{
return read(bb, maxVertPerPoly, false);
}
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.
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();
throw new IOException("Invalid number of verts per poly " + header.maxVertsPerPoly);
}
tileHeader.dataSize = bb.getInt();
if (tileHeader.tileRef == 0 || tileHeader.dataSize == 0) {
break;
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)
{
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
{
public class MeshSetWriter : DetourWriter
{
private readonly MeshDataWriter writer = new MeshDataWriter();
private readonly NavMeshParamWriter paramWriter = new NavMeshParamWriter();
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) {
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++;
public void write(BinaryWriter stream, NavMesh mesh, ByteOrder order, bool cCompatibility)
{
writeHeader(stream, mesh, order, cCompatibility);
writeTiles(stream, mesh, order, cCompatibility);
}
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) {
for (int i = 0; i < mesh.getMaxTiles(); ++i) {
MeshTile tile = mesh.getTile(i);
if (tile == null || tile.data == null || tile.data.header == null) {
continue;
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++;
}
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
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)
{
for (int i = 0; i < mesh.getMaxTiles(); ++i)
{
MeshTile tile = mesh.getTile(i);
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
{
public class NavMeshParamReader {
public NavMeshParams read(ByteBuffer bb) {
NavMeshParams option = new NavMeshParams();
option.orig[0] = bb.getFloat();
option.orig[1] = bb.getFloat();
option.orig[2] = bb.getFloat();
option.tileWidth = bb.getFloat();
option.tileHeight = bb.getFloat();
option.maxTiles = bb.getInt();
option.maxPolys = bb.getInt();
return option;
}
}
public class NavMeshParamReader
{
public NavMeshParams read(ByteBuffer bb)
{
NavMeshParams option = new NavMeshParams();
option.orig[0] = bb.getFloat();
option.orig[1] = bb.getFloat();
option.orig[2] = bb.getFloat();
option.tileWidth = bb.getFloat();
option.tileHeight = bb.getFloat();
option.maxTiles = bb.getInt();
option.maxPolys = bb.getInt();
return option;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,29 +22,28 @@ using System.Collections.Generic;
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)] */
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 List<long> getVisited()
{
return 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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
/// Represents the source data used to build an navigation mesh tile.
public class NavMeshDataCreateParams
{
/// @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.
public class NavMeshDataCreateParams {
/// < The number vertices in the polygon mesh. [Limit: >= 3]
public int[] polys;
/// @name Polygon Mesh Attributes
/// Used to create the base navigation graph.
/// See #rcPolyMesh for details related to these attributes.
/// @{
/// < The polygon data. [Size: #polyCount * 2 * #nvp]
public int[] polyFlags;
public int[] verts; /// < The polygon mesh vertices. [(x, y, z) * #vertCount] [Unit: vx]
public int vertCount; /// < The number vertices in the polygon mesh. [Limit: >= 3]
public int[] polys; /// < The polygon data. [Size: #polyCount * 2 * #nvp]
public int[] polyFlags; /// < The user defined flags assigned to each polygon. [Size: #polyCount]
public int[] polyAreas; /// < The user defined area ids assigned to each polygon. [Size: #polyCount]
public int polyCount; /// < Number of polygons in the mesh. [Limit: >= 1]
public int nvp; /// < Number maximum number of vertices per polygon. [Limit: >= 3]
/// < The user defined flags assigned to each polygon. [Size: #polyCount]
public int[] polyAreas;
/// @}
/// @name Height Detail Attributes (Optional)
/// See #rcPolyMeshDetail for details related to these attributes.
/// @{
/// < The user defined area ids assigned to each polygon. [Size: #polyCount]
public int polyCount;
public int[] detailMeshes; /// < The height detail sub-mesh data. [Size: 4 * #polyCount]
public float[] detailVerts; /// < The detail mesh vertices. [Size: 3 * #detailVertsCount] [Unit: wu]
public int detailVertsCount; /// < The number of vertices in the detail mesh.
public int[] detailTris; /// < The detail mesh triangles. [Size: 4 * #detailTriCount]
public int detailTriCount; /// < The number of triangles in the detail mesh.
/// < Number of polygons in the mesh. [Limit: >= 1]
public int nvp;
/// @}
/// @name Off-Mesh Connections Attributes (Optional)
/// Used to define a custom point-to-point edge within the navigation graph, an
/// off-mesh connection is a user defined traversable connection made up to two vertices,
/// at least one of which resides within a navigation mesh polygon.
/// @{
/// < Number maximum number of vertices per polygon. [Limit: >= 3]
/// @}
/// @name Height Detail Attributes (Optional)
/// See #rcPolyMeshDetail for details related to these attributes.
/// @{
public int[] detailMeshes;
/// Off-mesh connection vertices. [(ax, ay, az, bx, by, bz) * #offMeshConCount] [Unit: wu]
public float[] offMeshConVerts;
/// Off-mesh connection radii. [Size: #offMeshConCount] [Unit: wu]
public float[] offMeshConRad;
/// User defined flags assigned to the off-mesh connections. [Size: #offMeshConCount]
public int[] offMeshConFlags;
/// User defined area ids assigned to the off-mesh connections. [Size: #offMeshConCount]
public int[] offMeshConAreas;
/// The permitted travel direction of the off-mesh connections. [Size: #offMeshConCount]
///
/// 0 = Travel only from endpoint A to endpoint B.<br/>
/// #DT_OFFMESH_CON_BIDIR = Bidirectional travel.
public int[] offMeshConDir;
/// The user defined ids of the off-mesh connection. [Size: #offMeshConCount]
public int[] offMeshConUserID;
/// The number of off-mesh connections. [Limit: >= 0]
public int offMeshConCount;
/// < The height detail sub-mesh data. [Size: 4 * #polyCount]
public float[] detailVerts;
/// @}
/// @name Tile Attributes
/// @note The tile grid/layer data can be left at zero if the destination is a single tile mesh.
/// @{
/// < The detail mesh vertices. [Size: 3 * #detailVertsCount] [Unit: wu]
public int detailVertsCount;
public int userId; /// < The user defined id of the tile.
public int tileX; /// < The tile's x-grid location within the multi-tile destination mesh. (Along the x-axis.)
public int tileZ; /// < The tile's y-grid location within the multi-tile desitation mesh. (Along the z-axis.)
public int tileLayer; /// < The tile's layer within the layered destination mesh. [Limit: >= 0] (Along the y-axis.)
public float[] bmin; /// < The minimum bounds of the tile. [(x, y, z)] [Unit: wu]
public float[] bmax; /// < The maximum bounds of the tile. [(x, y, z)] [Unit: wu]
/// < The number of vertices in the detail mesh.
public int[] detailTris;
/// @}
/// @name General Configuration Attributes
/// @{
/// < The detail mesh triangles. [Size: 4 * #detailTriCount]
public int detailTriCount;
public float walkableHeight; /// < The agent height. [Unit: wu]
public float walkableRadius; /// < The agent radius. [Unit: wu]
public float walkableClimb; /// < The agent maximum traversable ledge. (Up/Down) [Unit: wu]
public float cs; /// < The xz-plane cell size of the polygon mesh. [Limit: > 0] [Unit: wu]
public float ch; /// < The y-axis cell height of the polygon mesh. [Limit: > 0] [Unit: wu]
/// < The number of triangles in the detail mesh.
/// @}
/// @name Off-Mesh Connections Attributes (Optional)
/// Used to define a custom point-to-point edge within the navigation graph, an
/// off-mesh connection is a user defined traversable connection made up to two vertices,
/// at least one of which resides within a navigation mesh polygon.
/// @{
/// Off-mesh connection vertices. [(ax, ay, az, bx, by, bz) * #offMeshConCount] [Unit: wu]
public float[] offMeshConVerts;
/// 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;
/// 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;
/// @}
/// @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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
/**
/**
* Configuration parameters used to define multi-tile navigation meshes. The values are used to allocate space during
* the initialization of a navigation mesh.
*
* @see NavMesh
*/
public class NavMeshParams {
/** The world space origin of the navigation mesh's tile space. [(x, y, z)] */
public readonly float[] orig = new float[3];
/** The width of each tile. (Along the x-axis.) */
public float tileWidth;
/** The height of each tile. (Along the z-axis.) */
public float tileHeight;
/** The maximum number of tiles the navigation mesh can contain. */
public int maxTiles;
/** The maximum number of polygons each tile can contain. */
public int maxPolys;
}
public class NavMeshParams
{
/** The world space origin of the navigation mesh's tile space. [(x, y, z)] */
public readonly float[] orig = new float[3];
/** The width of each tile. (Along the x-axis.) */
public float tileWidth;
/** The height of each tile. (Along the z-axis.) */
public float tileHeight;
/** The maximum number of tiles the navigation mesh can contain. */
public int maxTiles;
/** The maximum number of polygons each tile can contain. */
public int maxPolys;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -22,45 +22,50 @@ using System.Collections.Generic;
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;
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;
/** Cost of reaching the given node. */
public float cost;
public readonly int index;
/** Total cost of reaching the goal via the given node including heuristics. */
public float total;
/** Position of the node. */
public float[] pos = new float[3];
/** Cost of reaching the given node. */
public float cost;
/** Total cost of reaching the goal via the given node including heuristics. */
public float total;
/** Index to parent node. */
public int pidx;
/**
/** Index to parent node. */
public int pidx;
/**
* extra state information. A polyRef can have multiple nodes with different extra info. see DT_MAX_STATES_PER_NODE
*/
public int state;
/** Node flags. A combination of dtNodeFlags. */
public int flags;
/** Polygon ref the node corresponds to. */
public long id;
/** Shortcut found by raycast. */
public List<long> shortcut;
public int state;
public Node(int index) {
this.index = index;
/** 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)
{
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
{
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 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>();
public NodePool()
{
}
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];
public void clear()
{
m_nodes.Clear();
m_map.Clear();
}
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;
public List<Node> findNodes(long id)
{
var hasNode = m_map.TryGetValue(id, out var nodes);
;
if (nodes == null)
{
nodes = new List<Node>();
}
return nodes;
}
public Node findNode(long id)
{
var hasNode = m_map.TryGetValue(id, out var nodes);
;
if (nodes != null && 0 != nodes.Count)
{
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) {
Node node = new Node(m_nodes.Count + 1);
node.id = id;
node.state = state;
m_nodes.Add(node);
var hasNode = m_map.TryGetValue(id, out var nodes);;
if (nodes == null) {
nodes = new List<Node>();
m_map.Add(id, nodes);
protected Node create(long id, int state)
{
Node node = new Node(m_nodes.Count + 1);
node.id = id;
node.state = state;
m_nodes.Add(node);
var hasNode = m_map.TryGetValue(id, out var nodes);
;
if (nodes == null)
{
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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
using System.Collections.Generic;
using System.Collections.Generic;
public class NodeQueue {
private readonly List<Node> m_heap = new List<Node>();
public int count()
public class NodeQueue
{
return m_heap.Count;
}
private readonly List<Node> m_heap = new List<Node>();
public void clear() {
m_heap.Clear();
}
public int count()
{
return m_heap.Count;
}
public Node top()
{
return m_heap[0];
}
public void clear()
{
m_heap.Clear();
}
public Node pop()
{
var node = top();
m_heap.Remove(node);
return node;
}
public Node top()
{
return m_heap[0];
}
public void push(Node node) {
m_heap.Add(node);
m_heap.Sort((x, y) => x.total.CompareTo(y.total));
}
public Node pop()
{
var node = top();
m_heap.Remove(node);
return node;
}
public void modify(Node node) {
m_heap.Remove(node);
push(node);
}
public void push(Node node)
{
m_heap.Add(node);
m_heap.Sort((x, y) => x.total.CompareTo(y.total));
}
public bool isEmpty()
{
return 0 == m_heap.Count;
}
}
public void modify(Node node)
{
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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
/**
/**
* Defines an navigation mesh off-mesh connection within a dtMeshTile object. An off-mesh connection is a user defined
* traversable connection made up to two vertices.
*/
public class OffMeshConnection {
/** The endpoints of the connection. [(ax, ay, az, bx, by, bz)] */
public float[] pos = new float[6];
/** The radius of the endpoints. [Limit: >= 0] */
public float rad;
/** The polygon reference of the connection within the tile. */
public int poly;
/**
public class OffMeshConnection
{
/** The endpoints of the connection. [(ax, ay, az, bx, by, bz)] */
public float[] pos = new float[6];
/** The radius of the endpoints. [Limit: >= 0] */
public float rad;
/** The polygon reference of the connection within the tile. */
public int poly;
/**
* Link flags.
*
* @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.
*/
public int flags;
/** End point side. */
public int side;
/** The id of the offmesh connection. (User assigned when the navigation mesh is built.) */
public int userId;
}
public int flags;
/** End point side. */
public int side;
/** The id of the offmesh connection. (User assigned when the navigation mesh is built.) */
public int userId;
}
}

View File

@ -17,59 +17,68 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
/** Defines a polygon within a MeshTile object. */
public class Poly
{
public 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. */
public class Poly {
/** The polygon is an off-mesh connection consisting of two vertices. */
public const int DT_POLYTYPE_OFFMESH_CONNECTION = 1;
public readonly int index;
/** The polygon is a standard convex polygon that is part of the surface of the mesh. */
public const int DT_POLYTYPE_GROUND = 0;
/** The polygon is an off-mesh connection consisting of two vertices. */
public const int DT_POLYTYPE_OFFMESH_CONNECTION = 1;
/** The indices of the polygon's vertices. The actual vertices are located in MeshTile::verts. */
public readonly int[] verts;
/** Packed data representing neighbor polygons references and flags for each edge. */
public readonly int[] neis;
/** The user defined polygon flags. */
public int flags;
/** The number of vertices in the polygon. */
public int vertCount;
/**
/** The indices of the polygon's vertices. The actual vertices are located in MeshTile::verts. */
public readonly int[] verts;
/** Packed data representing neighbor polygons references and flags for each edge. */
public readonly int[] neis;
/** The user defined polygon flags. */
public int flags;
/** The number of vertices in the polygon. */
public int vertCount;
/**
* The bit packed area id and polygon type.
*
* @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) {
this.index = index;
verts = new int[maxVertsPerPoly];
neis = new int[maxVertsPerPoly];
public Poly(int index, int maxVertsPerPoly)
{
this.index = index;
verts = new int[maxVertsPerPoly];
neis = new int[maxVertsPerPoly];
}
/** Sets the user defined area id. [Limit: &lt; {@link org.recast4j.detour.NavMesh#DT_MAX_AREAS}] */
public void setArea(int a)
{
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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
/** Defines the location of detail sub-mesh data within a dtMeshTile. */
public class PolyDetail
{
/** 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. */
public class PolyDetail {
/** The offset of the vertices in the MeshTile::detailVerts array. */
public int vertBase;
/** The offset of the triangles in the MeshTile::detailTris array. */
public int triBase;
/** The number of vertices in the sub-mesh. */
public int vertCount;
/** The number of triangles in the sub-mesh. */
public int triCount;
}
/** The number of vertices in the sub-mesh. */
public int vertCount;
/** The number of triangles in the sub-mesh. */
public int triCount;
}
}

View File

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

View File

@ -20,79 +20,92 @@ using System;
namespace DotRecast.Detour
{
using static DetourCommon;
public interface PolygonByCircleConstraint
{
float[] aply(float[] polyVerts, float[] circleCenter, float radius);
using static DetourCommon;
public interface PolygonByCircleConstraint {
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 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;
}
}
/**
* 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;
private static float[] unitCircle;
public float[] aply(float[] verts, float[] center, float radius) {
float radiusSqr = radius * radius;
int outsideVertex = -1;
for (int pv = 0; pv < verts.Length; pv += 3) {
if (vDist2DSqr(center, verts, pv) > radiusSqr) {
outsideVertex = pv;
break;
public float[] aply(float[] verts, float[] center, float radius)
{
float radiusSqr = radius * radius;
int outsideVertex = -1;
for (int pv = 0; pv < verts.Length; pv += 3)
{
if (vDist2DSqr(center, verts, pv) > radiusSqr)
{
outsideVertex = pv;
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 (unitCircle == null) {
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);
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;
}
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];
private float[] circle(float[] center, float radius)
{
if (unitCircle == null)
{
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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
public class QueryData {
public Status status;
public Node lastBestNode;
public float lastBestNodeCost;
public long startRef, endRef;
public float[] startPos = new float[3];
public float[] endPos = new float[3];
public QueryFilter filter;
public int options;
public float raycastLimitSqr;
public QueryHeuristic heuristic;
}
public class QueryData
{
public Status status;
public Node lastBestNode;
public float lastBestNodeCost;
public long startRef, endRef;
public float[] startPos = new float[3];
public float[] endPos = new float[3];
public QueryFilter filter;
public int options;
public float raycastLimitSqr;
public QueryHeuristic heuristic;
}
}

View File

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

View File

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

View File

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

View File

@ -17,71 +17,79 @@ freely, subject to the following restrictions:
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
public static class Results
{
public static Result<T> success<T>(T result)
{
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> success<T>(T result) {
return new Result<T>(result, Status.SUCCSESS, null);
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 static Result<T> failure<T>() {
return new Result<T>(default, Status.FAILURE, 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();
}
}
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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
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)
public class Status
{
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 static bool isFailed(this Status @this) {
return @this == Status.FAILURE || @this == Status.FAILURE_INVALID_PARAM;
public int Value { get; }
private Status(int vlaue)
{
Value = vlaue;
}
}
public static bool isInProgress(this Status @this) {
return @this == Status.IN_PROGRESS;
}
public static class StatusEx
{
public static bool isFailed(this Status @this)
{
return @this == Status.FAILURE || @this == Status.FAILURE_INVALID_PARAM;
}
public static bool isSuccess(this Status @this) {
return @this == Status.SUCCSESS || @this == Status.PARTIAL_RESULT;
}
public static bool isInProgress(this Status @this)
{
return @this == Status.IN_PROGRESS;
}
public static bool isPartial(this Status @this) {
return @this == Status.PARTIAL_RESULT;
}
}
public static bool isSuccess(this Status @this)
{
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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Detour
{
using static DetourCommon;
using static DetourCommon;
//TODO: (PP) Add comments
public class StraightPathItem {
public float[] pos;
public int flags;
public long refs;
public class StraightPathItem
{
public float[] pos;
public int flags;
public long refs;
public StraightPathItem(float[] pos, int flags, long refs) {
this.pos = vCopy(pos);
this.flags = flags;
this.refs = refs;
public StraightPathItem(float[] pos, int flags, long refs)
{
this.pos = vCopy(pos);
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
{
/**
/**
* Wrapper for 3-element pieces (3D vectors) of a bigger float array.
*
*/
public class VectorPtr
{
private readonly float[] array;
private readonly int index;
public VectorPtr(float[] array) :
this(array, 0)
public class VectorPtr
{
}
private readonly float[] array;
private readonly int index;
public VectorPtr(float[] array, int index)
{
this.array = array;
this.index = index;
}
public VectorPtr(float[] array) :
this(array, 0)
{
}
public float get(int offset)
{
return array[index + offset];
public VectorPtr(float[] array, int index)
{
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;
public abstract class AbstractNavMeshBuilder {
public abstract class AbstractNavMeshBuilder
{
protected NavMeshDataCreateParams getNavMeshCreateParams(DemoInputGeomProvider m_geom, float m_cellSize,
float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb,
RecastBuilderResult rcResult) {
float m_cellHeight, float m_agentHeight, float m_agentRadius, float m_agentMaxClimb,
RecastBuilderResult rcResult)
{
PolyMesh m_pmesh = rcResult.getMesh();
PolyMeshDetail m_dmesh = rcResult.getMeshDetail();
NavMeshDataCreateParams option = new NavMeshDataCreateParams();
for (int i = 0; i < m_pmesh.npolys; ++i) {
for (int i = 0; i < m_pmesh.npolys; ++i)
{
m_pmesh.flags[i] = 1;
}
option.verts = m_pmesh.verts;
option.vertCount = m_pmesh.nverts;
option.polys = m_pmesh.polys;
@ -21,13 +24,15 @@ public abstract class AbstractNavMeshBuilder {
option.polyFlags = m_pmesh.flags;
option.polyCount = m_pmesh.npolys;
option.nvp = m_pmesh.nvp;
if (m_dmesh != null) {
if (m_dmesh != null)
{
option.detailMeshes = m_dmesh.meshes;
option.detailVerts = m_dmesh.verts;
option.detailVertsCount = m_dmesh.nverts;
option.detailTris = m_dmesh.tris;
option.detailTriCount = m_dmesh.ntris;
}
option.walkableHeight = m_agentHeight;
option.walkableRadius = m_agentRadius;
option.walkableClimb = m_agentMaxClimb;
@ -44,35 +49,49 @@ public abstract class AbstractNavMeshBuilder {
option.offMeshConAreas = new int[option.offMeshConCount];
option.offMeshConFlags = new int[option.offMeshConCount];
option.offMeshConUserID = new int[option.offMeshConCount];
for (int i = 0; i < option.offMeshConCount; i++) {
for (int i = 0; i < option.offMeshConCount; i++)
{
DemoOffMeshConnection offMeshCon = m_geom.getOffMeshConnections()[i];
for (int j = 0; j < 6; j++) {
for (int j = 0; j < 6; j++)
{
option.offMeshConVerts[6 * i + j] = offMeshCon.verts[j];
}
option.offMeshConRad[i] = offMeshCon.radius;
option.offMeshConDir[i] = offMeshCon.bidir ? 1 : 0;
option.offMeshConAreas[i] = offMeshCon.area;
option.offMeshConFlags[i] = offMeshCon.flags;
}
return option;
}
protected MeshData updateAreaAndFlags(MeshData meshData) {
protected MeshData updateAreaAndFlags(MeshData meshData)
{
// Update poly flags from areas.
for (int i = 0; i < meshData.polys.Length; ++i) {
if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WALKABLE) {
for (int i = 0; i < meshData.polys.Length; ++i)
{
if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WALKABLE)
{
meshData.polys[i].setArea(SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND);
}
if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND
|| meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GRASS
|| meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD) {
|| meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GRASS
|| meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_ROAD)
{
meshData.polys[i].flags = SampleAreaModifications.SAMPLE_POLYFLAGS_WALK;
} else if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER) {
}
else if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_WATER)
{
meshData.polys[i].flags = SampleAreaModifications.SAMPLE_POLYFLAGS_SWIM;
} else if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_DOOR) {
}
else if (meshData.polys[i].getArea() == SampleAreaModifications.SAMPLE_POLYAREA_TYPE_DOOR)
{
meshData.polys[i].flags = SampleAreaModifications.SAMPLE_POLYFLAGS_DOOR;
}
}
return meshData;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,8 +26,8 @@ using DotRecast.Recast.Demo.Settings;
namespace DotRecast.Recast.Demo;
public class Sample {
public class Sample
{
private DemoInputGeomProvider inputGeom;
private NavMesh navMesh;
private NavMeshQuery navMeshQuery;
@ -36,7 +36,8 @@ public class Sample {
private bool changed;
public Sample(DemoInputGeomProvider inputGeom, IList<RecastBuilderResult> recastResults, NavMesh navMesh,
RcSettingsView rcSettingsView, RecastDebugDraw debugDraw) {
RcSettingsView rcSettingsView, RecastDebugDraw debugDraw)
{
this.inputGeom = inputGeom;
this.recastResults = recastResults;
this.navMesh = navMesh;
@ -45,43 +46,52 @@ public class Sample {
changed = true;
}
private void setQuery(NavMesh navMesh) {
private void setQuery(NavMesh navMesh)
{
navMeshQuery = navMesh != null ? new NavMeshQuery(navMesh) : null;
}
public DemoInputGeomProvider getInputGeom() {
public DemoInputGeomProvider getInputGeom()
{
return inputGeom;
}
public IList<RecastBuilderResult> getRecastResults() {
public IList<RecastBuilderResult> getRecastResults()
{
return recastResults;
}
public NavMesh getNavMesh() {
public NavMesh getNavMesh()
{
return navMesh;
}
public RcSettingsView getSettingsUI() {
public RcSettingsView getSettingsUI()
{
return _rcSettingsView;
}
public NavMeshQuery getNavMeshQuery() {
public NavMeshQuery getNavMeshQuery()
{
return navMeshQuery;
}
public bool isChanged() {
public bool isChanged()
{
return changed;
}
public void setChanged(bool changed) {
public void setChanged(bool changed)
{
this.changed = changed;
}
public void update(DemoInputGeomProvider geom, IList<RecastBuilderResult> recastResults, NavMesh navMesh) {
public void update(DemoInputGeomProvider geom, IList<RecastBuilderResult> recastResults, NavMesh navMesh)
{
inputGeom = geom;
this.recastResults = recastResults;
this.navMesh = navMesh;
setQuery(navMesh);
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 Slope", ref agentMaxSlope, 90f, 1f, $"{agentMaxSlope}");
ImGui.NewLine();
ImGui.Text("Region");
ImGui.Separator();
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.Collections.Generic;
using Silk.NET.Windowing;
using DotRecast.Core;
using DotRecast.Recast.Demo.Builder;
using DotRecast.Recast.Demo.Draw;
using DotRecast.Recast.Demo.Geom;
using static DotRecast.Recast.Demo.Draw.DebugDraw;
using static DotRecast.Recast.Demo.Draw.DebugDrawPrimitives;
namespace DotRecast.Recast.Demo.Tools;
public class ConvexVolumeTool : Tool {
public class ConvexVolumeTool : Tool
{
private Sample sample;
private AreaModification areaType = SampleAreaModifications.SAMPLE_AREAMOD_GRASS;
private readonly float[] boxHeight = new[] { 6f };
@ -42,105 +40,137 @@ public class ConvexVolumeTool : Tool {
private readonly List<float> pts = new();
private readonly List<int> hull = new();
public override void setSample(Sample m_sample) {
public override void setSample(Sample m_sample)
{
sample = m_sample;
}
public override void handleClick(float[] s, float[] p, bool shift) {
public override void handleClick(float[] s, float[] p, bool shift)
{
DemoInputGeomProvider geom = sample.getInputGeom();
if (geom == null) {
if (geom == null)
{
return;
}
if (shift) {
if (shift)
{
// Delete
int nearestIndex = -1;
IList<ConvexVolume> vols = geom.convexVolumes();
for (int i = 0; i < vols.Count; ++i) {
for (int i = 0; i < vols.Count; ++i)
{
if (PolyUtils.pointInPoly(vols[i].verts, p) && p[1] >= vols[i].hmin
&& p[1] <= vols[i].hmax) {
&& p[1] <= vols[i].hmax)
{
nearestIndex = i;
}
}
// If end point close enough, delete it.
if (nearestIndex != -1) {
if (nearestIndex != -1)
{
geom.convexVolumes().RemoveAt(nearestIndex);
}
} else {
}
else
{
// Create
// If clicked on that last pt, create the shape.
if (pts.Count > 0 && DemoMath.vDistSqr(p,
new float[] { pts[pts.Count - 3], pts[pts.Count - 2], pts[pts.Count - 1] },
0) < 0.2f * 0.2f) {
if (hull.Count > 2) {
0) < 0.2f * 0.2f)
{
if (hull.Count > 2)
{
// Create shape.
float[] verts = new float[hull.Count * 3];
for (int i = 0; i < hull.Count; ++i) {
for (int i = 0; i < hull.Count; ++i)
{
verts[i * 3] = pts[hull[i] * 3];
verts[i * 3 + 1] = pts[hull[i] * 3 + 1];
verts[i * 3 + 2] = pts[hull[i] * 3 + 2];
}
float minh = float.MaxValue, maxh = 0;
for (int i = 0; i < hull.Count; ++i) {
for (int i = 0; i < hull.Count; ++i)
{
minh = Math.Min(minh, verts[i * 3 + 1]);
}
minh -= boxDescent[0];
maxh = minh + boxHeight[0];
if (polyOffset[0] > 0.01f) {
if (polyOffset[0] > 0.01f)
{
float[] offset = new float[verts.Length * 2];
int noffset = PolyUtils.offsetPoly(verts, hull.Count, polyOffset[0], offset,
offset.Length);
if (noffset > 0) {
offset.Length);
if (noffset > 0)
{
geom.addConvexVolume(ArrayUtils.CopyOf(offset, 0, noffset * 3), minh, maxh, areaType);
}
} else {
}
else
{
geom.addConvexVolume(verts, minh, maxh, areaType);
}
}
pts.Clear();
hull.Clear();
} else {
}
else
{
// Add new point
pts.Add(p[0]);
pts.Add(p[1]);
pts.Add(p[2]);
// Update hull.
if (pts.Count > 3) {
if (pts.Count > 3)
{
hull.Clear();
hull.AddRange(ConvexUtils.convexhull(pts));
} else {
}
else
{
hull.Clear();
}
}
}
}
public override void handleRender(NavMeshRenderer renderer) {
public override void handleRender(NavMeshRenderer renderer)
{
RecastDebugDraw dd = renderer.getDebugDraw();
// Find height extent of the shape.
float minh = float.MaxValue, maxh = 0;
for (int i = 0; i < pts.Count; i += 3) {
for (int i = 0; i < pts.Count; i += 3)
{
minh = Math.Min(minh, pts[i + 1]);
}
minh -= boxDescent[0];
maxh = minh + boxHeight[0];
dd.begin(POINTS, 4.0f);
for (int i = 0; i < pts.Count; i += 3) {
for (int i = 0; i < pts.Count; i += 3)
{
int col = duRGBA(255, 255, 255, 255);
if (i == pts.Count - 3) {
if (i == pts.Count - 3)
{
col = duRGBA(240, 32, 16, 255);
}
dd.vertex(pts[i + 0], pts[i + 1] + 0.1f, pts[i + 2], col);
}
dd.end();
dd.begin(LINES, 2.0f);
for (int i = 0, j = hull.Count - 1; i < hull.Count; j = i++) {
for (int i = 0, j = hull.Count - 1; i < hull.Count; j = i++)
{
int vi = hull[j] * 3;
int vj = hull[i] * 3;
dd.vertex(pts[vj + 0], minh, pts[vj + 2], duRGBA(255, 255, 255, 64));
@ -150,10 +180,12 @@ public class ConvexVolumeTool : Tool {
dd.vertex(pts[vj + 0], minh, pts[vj + 2], duRGBA(255, 255, 255, 64));
dd.vertex(pts[vj + 0], maxh, pts[vj + 2], duRGBA(255, 255, 255, 64));
}
dd.end();
}
public override void layout(IWindow ctx) {
public override void layout(IWindow ctx)
{
// nk_layout_row_dynamic(ctx, 20, 1);
// nk_property_float(ctx, "Shape Height", 0.1f, boxHeight, 20f, 0.1f, 0.1f);
// nk_layout_row_dynamic(ctx, 20, 1);
@ -194,12 +226,13 @@ public class ConvexVolumeTool : Tool {
// }
}
public override string getName() {
public override string getName()
{
return "Create Convex Volumes";
}
public override void handleUpdate(float dt) {
public override void handleUpdate(float dt)
{
// TODO Auto-generated method stub
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,17 @@
namespace DotRecast.Recast.Demo.Tools;
public class SteerTarget {
public class SteerTarget
{
public readonly float[] steerPos;
public readonly int steerPosFlag;
public readonly long steerPosRef;
public readonly float[] steerPoints;
public SteerTarget(float[] steerPos, int steerPosFlag, long steerPosRef, float[] steerPoints) {
public SteerTarget(float[] steerPos, int steerPosFlag, long steerPosRef, float[] steerPoints)
{
this.steerPos = steerPos;
this.steerPosFlag = steerPosFlag;
this.steerPosRef = steerPosRef;
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;
public abstract class Tool {
public abstract class Tool
{
public abstract void setSample(Sample m_sample);
public abstract void handleClick(float[] s, float[] p, bool shift);
@ -41,4 +41,4 @@ public abstract class Tool {
{
// ...
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,54 +20,52 @@ freely, subject to the following restrictions:
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
*
* @param value
* The area id to apply. [Limit: &lt;= #RC_AREA_FLAGS_MASK]
*/
public AreaModification(int value)
{
this.value = value;
mask = RC_AREA_FLAGS_MASK;
}
public AreaModification(int value)
{
this.value = value;
mask = RC_AREA_FLAGS_MASK;
}
/**
/**
*
* @param value
* The area id to apply. [Limit: &lt;= #RC_AREA_FLAGS_MASK]
* @param mask
* Bitwise mask used when applying value. [Limit: &lt;= #RC_AREA_FLAGS_MASK]
*/
public AreaModification(int value, int mask)
{
this.value = value;
this.mask = mask;
}
public AreaModification(int value, int mask)
{
this.value = value;
this.mask = mask;
}
public AreaModification(AreaModification other)
{
value = other.value;
mask = other.mask;
}
public AreaModification(AreaModification other)
{
value = other.value;
mask = other.mask;
}
public int getMaskedValue()
{
return value & mask;
}
public int getMaskedValue()
{
return value & mask;
}
public int apply(int area)
{
return ((value & mask) | (area & ~mask));
public int apply(int area)
{
return ((value & mask) | (area & ~mask));
}
}
}
}

View File

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

View File

@ -20,57 +20,55 @@ freely, subject to the following restrictions:
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. */
public class CompactHeightfield
{
/** The width of the heightfield. (Along the x-axis in cell units.) */
public int width;
/** The number of spans in the heightfield. */
public int spanCount;
/** The height of the heightfield. (Along the z-axis in cell units.) */
public int height;
/** The walkable height used during the build of the field. (See: RecastConfig::walkableHeight) */
public int walkableHeight;
/** The number of spans in the heightfield. */
public int spanCount;
/** The walkable climb used during the build of the field. (See: RecastConfig::walkableClimb) */
public int walkableClimb;
/** The walkable height used during the build of the field. (See: RecastConfig::walkableHeight) */
public int walkableHeight;
/** The AABB border size used during the build of the field. (See: RecastConfig::borderSize) */
public int borderSize;
/** The walkable climb used during the build of the field. (See: RecastConfig::walkableClimb) */
public int walkableClimb;
/** The maximum distance value of any span within the field. */
public int maxDistance;
/** The AABB border size used during the build of the field. (See: RecastConfig::borderSize) */
public int borderSize;
/** The maximum region id of any span within the field. */
public int maxRegions;
/** The maximum distance value of any span within the field. */
public int maxDistance;
/** The minimum bounds in world space. [(x, y, z)] */
public float[] bmin = new float[3];
/** The maximum region id of any span within the field. */
public int maxRegions;
/** The maximum bounds in world space. [(x, y, z)] */
public float[] bmax = new float[3];
/** The minimum bounds in world space. [(x, y, z)] */
public float[] bmin = new float[3];
/** The size of each cell. (On the xz-plane.) */
public float cs;
/** The maximum bounds in world space. [(x, y, z)] */
public float[] bmax = new float[3];
/** The height of each cell. (The minimum increment along the y-axis.) */
public float ch;
/** The size of each cell. (On the xz-plane.) */
public float cs;
/** Array of cells. [Size: #width*#height] */
public CompactCell[] cells;
/** The height of each cell. (The minimum increment along the y-axis.) */
public float ch;
/** Array of spans. [Size: #spanCount] */
public CompactSpan[] spans;
/** Array of cells. [Size: #width*#height] */
public CompactCell[] cells;
/** Array containing border distance data. [Size: #spanCount] */
public int[] dist;
/** Array of spans. [Size: #spanCount] */
public CompactSpan[] spans;
/** Array containing border distance data. [Size: #spanCount] */
public int[] dist;
/** Array containing area id data. [Size: #spanCount] */
public int[] areas;
}
/** 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
{
/** 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. */
public class CompactSpan
{
/** The lower extent of the span. (Measured from the heightfield's base.) */
public int y;
/** Packed neighbor connection data. */
public int con;
/** The id of the region the span belongs to. (Or zero if not in a region.) */
public int reg;
/** Packed neighbor connection data. */
public int con;
/** The height of the span. (Measured from #y.) */
public int h;
}
/** 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
{
/** 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. */
public class Contour
{
/** Simplified contour vertex and connection data. [Size: 4 * #nverts] */
public int[] verts;
/** Raw contour vertex and connection data. [Size: 4 * #nrverts] */
public int[] rverts;
/** The number of vertices in the simplified contour. */
public int nverts;
/** The number of vertices in the raw contour. */
public int nrverts;
/** Raw contour vertex and connection data. [Size: 4 * #nrverts] */
public int[] rverts;
/** The region id of the contour. */
public int area;
/** The number of vertices in the raw contour. */
public int nrverts;
/** The region id of the contour. */
public int area;
/** The area id of the contour. */
public int reg;
}
/** The area id of the contour. */
public int reg;
}
}

View File

@ -22,36 +22,34 @@ using System.Collections.Generic;
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. */
public class ContourSet
{
/** A list of the contours in the set. */
public List<Contour> conts = new List<Contour>();
/** The maximum bounds in world space. [(x, y, z)] */
public float[] bmax = new float[3];
/** The minimum bounds in world space. [(x, y, z)] */
public float[] bmin = new float[3];
/** The size of each cell. (On the xz-plane.) */
public float cs;
/** The maximum bounds in world space. [(x, y, z)] */
public float[] bmax = new float[3];
/** The height of each cell. (The minimum increment along the y-axis.) */
public float ch;
/** The size of each cell. (On the xz-plane.) */
public float cs;
/** The width of the set. (Along the x-axis in cell units.) */
public int width;
/** The height of each cell. (The minimum increment along the y-axis.) */
public float ch;
/** The height of the set. (Along the z-axis in cell units.) */
public int height;
/** The width of the set. (Along the x-axis in cell units.) */
public int width;
/** The AABB border size used to generate the source data from which the contours were derived. */
public int borderSize;
/** The height of the set. (Along the z-axis in cell units.) */
public int height;
/** 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;
}
/** 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
{
public class ConvexVolume
{
public float[] verts;
public float hmin;
public float hmax;
public AreaModification areaMod;
}
public class ConvexVolume
{
public float[] verts;
public float hmin;
public float hmax;
public AreaModification areaMod;
}
}

View File

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

View File

@ -23,228 +23,226 @@ using System.Collections.Generic;
namespace DotRecast.Recast.Geom
{
public class ChunkyTriMesh
{
private class BoundsItem
public class ChunkyTriMesh
{
public readonly float[] bmin = new float[2];
public readonly float[] bmax = new float[2];
public int i;
}
private class CompareItemX : IComparer<BoundsItem>
{
public int Compare(BoundsItem? a, BoundsItem? b)
private class BoundsItem
{
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>
{
public int Compare(BoundsItem? a, BoundsItem? b)
private class CompareItemX : IComparer<BoundsItem>
{
return a.bmin[1].CompareTo(b.bmin[1]);
}
}
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])
public int Compare(BoundsItem? a, BoundsItem? b)
{
bmin[0] = it.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];
return a.bmin[0].CompareTo(b.bmin[0]);
}
}
}
private int longestAxis(float x, float y)
{
return y > x ? 1 : 0;
}
private void subdivide(BoundsItem[] items, int imin, int imax, int trisPerChunk, List<ChunkyTriMeshNode> nodes,
int[] inTris)
{
int inum = imax - imin;
ChunkyTriMeshNode node = new ChunkyTriMeshNode();
nodes.Add(node);
if (inum <= trisPerChunk)
private class CompareItemY : IComparer<BoundsItem>
{
// 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)
public int Compare(BoundsItem? a, BoundsItem? b)
{
int src = items[i].i * 3;
node.tris[dst++] = inTris[src];
node.tris[dst++] = inTris[src + 1];
node.tris[dst++] = inTris[src + 2];
return a.bmin[1].CompareTo(b.bmin[1]);
}
}
else
List<ChunkyTriMeshNode> nodes;
int ntris;
int maxTrisPerChunk;
private void calcExtends(BoundsItem[] items, int imin, int imax, float[] bmin, float[] bmax)
{
// Split
calcExtends(items, imin, imax, node.bmin, node.bmax);
bmin[0] = items[imin].bmin[0];
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());
// 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;
}
}
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])
BoundsItem it = items[i];
if (it.bmin[0] < 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);
// Calc max tris per node.
maxTrisPerChunk = 0;
foreach (ChunkyTriMeshNode node in nodes)
private int longestAxis(float x, float y)
{
bool isLeaf = node.i >= 0;
if (!isLeaf)
{
continue;
}
if (node.tris.Length / 3 > maxTrisPerChunk)
{
maxTrisPerChunk = node.tris.Length / 3;
}
return y > x ? 1 : 0;
}
}
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)
private void subdivide(BoundsItem[] items, int imin, int imax, int trisPerChunk, List<ChunkyTriMeshNode> nodes,
int[] inTris)
{
ChunkyTriMeshNode node = nodes[i];
bool overlap = checkOverlapRect(bmin, bmax, node.bmin, node.bmax);
bool isLeafNode = node.i >= 0;
int inum = imax - imin;
if (isLeafNode && overlap)
{
ids.Add(node);
}
ChunkyTriMeshNode node = new ChunkyTriMeshNode();
nodes.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
{
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
{
public class ChunkyTriMeshNode
{
public readonly float[] bmin = new float[2];
public readonly float[] bmax = new float[2];
public int i;
public int[] tris;
}
public class ChunkyTriMeshNode
{
public readonly float[] bmin = new float[2];
public readonly float[] bmax = new float[2];
public int i;
public int[] tris;
}
}

View File

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

View File

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

View File

@ -24,116 +24,115 @@ using System.Collections.Immutable;
namespace DotRecast.Recast.Geom
{
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 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>();
private static int[] mapFaces(List<int> meshFaces)
{
int[] faces = new int[meshFaces.Count];
for (int i = 0; i < faces.Length; i++)
public SimpleInputGeomProvider(List<float> vertexPositions, List<int> meshFaces)
: this(mapVertices(vertexPositions), mapFaces(meshFaces))
{
faces[i] = meshFaces[i];
}
return faces;
}
private static float[] mapVertices(List<float> vertexPositions)
{
float[] vertices = new float[vertexPositions.Count];
for (int i = 0; i < vertices.Length; i++)
private static int[] mapFaces(List<int> meshFaces)
{
vertices[i] = vertexPositions[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)
int[] faces = new int[meshFaces.Count];
for (int i = 0; i < faces.Length; i++)
{
e0[j] = vertices[v1 + j] - vertices[v0 + j];
e1[j] = vertices[v2 + j] - vertices[v0 + j];
faces[i] = meshFaces[i];
}
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)
return faces;
}
private static float[] mapVertices(List<float> vertexPositions)
{
float[] vertices = new float[vertexPositions.Count];
for (int i = 0; i < vertices.Length; i++)
{
d = 1.0f / d;
normals[i] *= d;
normals[i + 1] *= d;
normals[i + 2] *= d;
vertices[i] = vertexPositions[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];
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
{
public class SingleTrimeshInputGeomProvider : InputGeomProvider
{
private readonly float[] bmin;
private readonly float[] bmax;
private readonly TriMesh[] _meshes;
public SingleTrimeshInputGeomProvider(float[] vertices, int[] faces)
public class SingleTrimeshInputGeomProvider : InputGeomProvider
{
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++)
private readonly float[] bmin;
private readonly float[] bmax;
private readonly TriMesh[] _meshes;
public SingleTrimeshInputGeomProvider(float[] vertices, int[] faces)
{
RecastVectors.min(bmin, vertices, i * 3);
RecastVectors.max(bmax, vertices, i * 3);
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);
}
_meshes = new[] { new TriMesh(vertices, faces) };
}
_meshes = new[] { new TriMesh(vertices, faces) };
}
public float[] getMeshBoundsMin()
{
return bmin;
}
public float[] getMeshBoundsMin()
{
return bmin;
}
public float[] getMeshBoundsMax()
{
return bmax;
}
public float[] getMeshBoundsMax()
{
return bmax;
}
public IEnumerable<TriMesh> meshes()
{
return _meshes;
}
public IEnumerable<TriMesh> meshes()
{
return _meshes;
public IList<ConvexVolume> convexVolumes()
{
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
{
public class TriMesh
{
private readonly float[] vertices;
private readonly int[] faces;
private readonly ChunkyTriMesh chunkyTriMesh;
public TriMesh(float[] vertices, int[] faces)
public class TriMesh
{
this.vertices = vertices;
this.faces = faces;
chunkyTriMesh = new ChunkyTriMesh(vertices, faces, faces.Length / 3, 32);
}
private readonly float[] vertices;
private readonly int[] faces;
private readonly ChunkyTriMesh chunkyTriMesh;
public int[] getTris()
{
return faces;
}
public TriMesh(float[] vertices, int[] faces)
{
this.vertices = vertices;
this.faces = faces;
chunkyTriMesh = new ChunkyTriMesh(vertices, faces, faces.Length / 3, 32);
}
public float[] getVerts()
{
return vertices;
}
public int[] getTris()
{
return faces;
}
public List<ChunkyTriMeshNode> getChunksOverlappingRect(float[] bmin, float[] bmax)
{
return chunkyTriMesh.getChunksOverlappingRect(bmin, bmax);
public float[] getVerts()
{
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
{
/** 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)
/** Represents a heightfield layer within a layer set. */
public class Heightfield
{
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];
/** 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;
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
{
/// Represents a set of heightfield layers.
/// @ingroup recast
/// @see rcAllocHeightfieldLayerSet, rcFreeHeightfieldLayerSet
public class HeightfieldLayerSet
{
/// Represents a heightfield layer within a layer set.
/// @see rcHeightfieldLayerSet
public class HeightfieldLayer
/// Represents a set of heightfield layers.
/// @ingroup recast
/// @see rcAllocHeightfieldLayerSet, rcFreeHeightfieldLayerSet
public class HeightfieldLayerSet
{
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)]
public readonly float[] bmax = new float[3];
/// < The minimum bounds in world space. [(x, y, z)]
public readonly float[] bmax = new float[3];
/// < The maximum bounds in world space. [(x, y, z)]
public float cs;
/// < The maximum bounds in world space. [(x, y, z)]
public float cs;
/// < The size of each cell. (On the xz-plane.)
public float ch;
/// < The size of each cell. (On the xz-plane.)
public float ch;
/// < The height of each cell. (The minimum increment along the y-axis.)
public int width;
/// < The height of each cell. (The minimum increment along the y-axis.)
public int width;
/// < The width of the heightfield. (Along the x-axis in cell units.)
public int height;
/// < The width of the heightfield. (Along the x-axis in cell units.)
public int height;
/// < The height of the heightfield. (Along the z-axis in cell units.)
public int minx;
/// < The height of the heightfield. (Along the z-axis in cell units.)
public int minx;
/// < The minimum x-bounds of usable data.
public int maxx;
/// < The minimum x-bounds of usable data.
public int maxx;
/// < The maximum x-bounds of usable data.
public int miny;
/// < The maximum x-bounds of usable data.
public int miny;
/// < The minimum y-bounds of usable data. (Along the z-axis.)
public int maxy;
/// < The minimum y-bounds of usable data. (Along the z-axis.)
public int maxy;
/// < The maximum y-bounds of usable data. (Along the z-axis.)
public int hmin;
/// < The maximum y-bounds of usable data. (Along the z-axis.)
public int hmin;
/// < The minimum height bounds of usable data. (Along the y-axis.)
public int hmax;
/// < The minimum height bounds of usable data. (Along the y-axis.)
public int hmax;
/// < The maximum height bounds of usable data. (Along the y-axis.)
public int[] heights;
/// < The maximum height bounds of usable data. (Along the y-axis.)
public int[] heights;
/// < The heightfield. [Size: width * height]
public int[] areas;
/// < The heightfield. [Size: width * height]
public int[] areas;
/// < Area ids. [Size: Same as #heights]
public int[] cons; /// < Packed neighbor connection information. [Size: Same as #heights]
/// < Area ids. [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
{
public class InputGeomReader
{
}
public class InputGeomReader
{
}
}

View File

@ -23,119 +23,117 @@ using DotRecast.Recast.Geom;
namespace DotRecast.Recast
{
public static class ObjImporter
{
public class ObjImporterContext
public static class ObjImporter
{
public List<float> vertexPositions = new List<float>();
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
public class ObjImporterContext
{
using StreamReader reader = new StreamReader(new MemoryStream(chunck));
string line;
while ((line = reader.ReadLine()) != null)
public List<float> vertexPositions = new List<float>();
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
{
line = line.Trim();
readLine(line, context);
using StreamReader reader = new StreamReader(new MemoryStream(chunck));
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;
}
public static void readLine(string line, ObjImporterContext context)
{
if (line.StartsWith("v"))
private static void readVertex(string line, ObjImporterContext context)
{
readVertex(line, context);
}
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)
if (line.StartsWith("v "))
{
context.vertexPositions.Add(vp);
float[] vert = readVector3f(line);
foreach (float vp in vert)
{
context.vertexPositions.Add(vp);
}
}
}
}
private static float[] readVector3f(string line)
{
string[] v = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (v.Length < 4)
private static float[] readVector3f(string line)
{
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++)
string[] v = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (v.Length < 4)
{
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)
{
string[] v = face.Split("/");
return getIndex(int.Parse(v[0]), context.vertexPositions.Count);
}
private static int getIndex(int posi, int size)
{
if (posi > 0)
private static int readFaceVertex(string face, ObjImporterContext context)
{
posi--;
}
else if (posi < 0)
{
posi = size + posi;
}
else
{
throw new Exception("0 vertex index");
string[] v = face.Split("/");
return getIndex(int.Parse(v[0]), context.vertexPositions.Count);
}
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
{
/// < Tessellate edges between areas during contour
/// simplification.
public enum PartitionType
{
WATERSHED,
MONOTONE,
LAYERS
}
/// < Tessellate edges between areas during contour
/// simplification.
public enum PartitionType
{
WATERSHED,
MONOTONE,
LAYERS
}
}

View File

@ -19,54 +19,52 @@ freely, subject to the following restrictions:
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. */
public class PolyMesh
{
/** The mesh vertices. [Form: (x, y, z) coordinates * #nverts] */
public int[] verts;
/** The region id assigned to each polygon. [Length: #maxpolys] */
public int[] regs;
/** Polygon and neighbor data. [Length: #maxpolys * 2 * #nvp] */
public int[] polys;
/** The area id assigned to each polygon. [Length: #maxpolys] */
public int[] areas;
/** The region id assigned to each polygon. [Length: #maxpolys] */
public int[] regs;
/** The number of vertices. */
public int nverts;
/** The area id assigned to each polygon. [Length: #maxpolys] */
public int[] areas;
/** The number of polygons. */
public int npolys;
/** The number of vertices. */
public int nverts;
/** The maximum number of vertices per polygon. */
public int nvp;
/** The number of polygons. */
public int npolys;
/** The number of allocated polygons. */
public int maxpolys;
/** The maximum number of vertices per polygon. */
public int nvp;
/** The user defined flags for each polygon. [Length: #maxpolys] */
public int[] flags;
/** The number of allocated polygons. */
public int maxpolys;
/** The minimum bounds in world space. [(x, y, z)] */
public readonly float[] bmin = new float[3];
/** The user defined flags for each polygon. [Length: #maxpolys] */
public int[] flags;
/** The maximum bounds in world space. [(x, y, z)] */
public readonly float[] bmax = new float[3];
/** The minimum bounds in world space. [(x, y, z)] */
public readonly float[] bmin = new float[3];
/** The size of each cell. (On the xz-plane.) */
public float cs;
/** The maximum bounds in world space. [(x, y, z)] */
public readonly float[] bmax = new float[3];
/** The height of each cell. (The minimum increment along the y-axis.) */
public float ch;
/** The size of each cell. (On the xz-plane.) */
public float cs;
/** The AABB border size used to generate the source data from which the mesh was derived. */
public int borderSize;
/** The height of each cell. (The minimum increment along the y-axis.) */
public float ch;
/** 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;
}
/** 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
{
/**
/**
* Contains triangle meshes that represent detailed height data associated with the polygons in its associated polygon
* mesh object.
*/
public class PolyMeshDetail
{
/** The sub-mesh data. [Size: 4*#nmeshes] */
public int[] meshes;
public class PolyMeshDetail
{
/** The sub-mesh data. [Size: 4*#nmeshes] */
public int[] meshes;
/** The mesh vertices. [Size: 3*#nverts] */
public float[] verts;
/** The mesh vertices. [Size: 3*#nverts] */
public float[] verts;
/** The mesh triangles. [Size: 4*#ntris] */
public int[] tris;
/** The mesh triangles. [Size: 4*#ntris] */
public int[] tris;
/** The number of sub-meshes defined by #meshes. */
public int nmeshes;
/** The number of sub-meshes defined by #meshes. */
public int nmeshes;
/** The number of vertices in #verts. */
public int nverts;
/** The number of vertices in #verts. */
public int nverts;
/** The number of triangles in #tris. */
public int ntris;
}
/** The number of triangles in #tris. */
public int ntris;
}
}

View File

@ -22,104 +22,102 @@ using System;
namespace DotRecast.Recast
{
using static RecastConstants;
using static RecastConstants;
public class Recast
{
void calcBounds(float[] verts, int nv, float[] bmin, float[] bmax)
public class Recast
{
for (int i = 0; i < 3; i++)
void calcBounds(float[] verts, int nv, float[] bmin, float[] bmax)
{
bmin[i] = verts[i];
bmax[i] = verts[i];
for (int i = 0; i < 3; 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]);
bmax[j] = Math.Max(bmax[j], verts[i * 3 + j]);
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;
}
}
// 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
{
public class RecastBuilder
{
public interface RecastBuilderProgressListener
public class RecastBuilder
{
void onProgress(int completed, int total);
}
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)
public interface RecastBuilderProgressListener
{
buildMultiThreadAsync(geom, cfg, bmin, bmax, tw, th, results, taskFactory, default);
} else {
buildSingleThreadAsync(geom, cfg, bmin, bmax, tw, th, results);
void onProgress(int completed, int total);
}
return results;
}
private readonly RecastBuilderProgressListener progressListener;
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)
public RecastBuilder()
{
task = buildMultiThreadAsync(geom, cfg, bmin, bmax, tw, th, results, taskFactory, cancellationToken);
}
else
{
task = buildSingleThreadAsync(geom, cfg, bmin, bmax, tw, th, results);
progressListener = null;
}
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)
public RecastBuilder(RecastBuilderProgressListener progressListener)
{
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,
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)
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);
}
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)
{
int tx = x;
int ty = y;
var task = taskFactory.StartNew(() =>
for (int x = 0; x < tw; ++x)
{
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);
results.Add(buildTile(geom, cfg, bmin, bmax, x, y, counter, tw * th));
}
}
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();
}
catch (ThreadInterruptedException e)
{
}
AtomicInteger counter = new AtomicInteger(0);
CountdownEvent latch = new CountdownEvent(tw * th);
List<Task> tasks = new List<Task>();
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);
}
}
/*
* 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())
for (int x = 0; x < tw; ++x)
{
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)
{
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);
// 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);
}
}
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
{
using static RecastVectors;
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 class RecastBuilderConfig
{
}
public readonly RecastConfig cfg;
public RecastBuilderConfig(RecastConfig cfg, float[] bmin, float[] bmax, int tileX, int tileZ)
{
this.tileX = tileX;
this.tileZ = tileZ;
this.cfg = cfg;
copy(this.bmin, bmin);
copy(this.bmax, bmax);
if (cfg.useTiles)
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)
{
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);
width = wh[0];
height = wh[1];
this.tileX = tileX;
this.tileZ = tileZ;
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
{
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)
public class RecastBuilderResult
{
this.tileX = tileX;
this.tileZ = tileZ;
this.solid = solid;
this.chf = chf;
this.cs = cs;
this.pmesh = pmesh;
this.dmesh = dmesh;
telemetry = ctx;
}
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 PolyMesh getMesh()
{
return pmesh;
}
public RecastBuilderResult(int tileX, int tileZ, Heightfield solid, CompactHeightfield chf, ContourSet cs, PolyMesh pmesh,
PolyMeshDetail dmesh, Telemetry ctx)
{
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()
{
return dmesh;
}
public PolyMesh getMesh()
{
return pmesh;
}
public CompactHeightfield getCompactHeightfield()
{
return chf;
}
public PolyMeshDetail getMeshDetail()
{
return dmesh;
}
public ContourSet getContourSet()
{
return cs;
}
public CompactHeightfield getCompactHeightfield()
{
return chf;
}
public Heightfield getSolidHeightfield()
{
return solid;
}
public ContourSet getContourSet()
{
return cs;
}
public Telemetry getTelemetry()
{
return telemetry;
}
}
public Heightfield getSolidHeightfield()
{
return solid;
}
public Telemetry getTelemetry()
{
return telemetry;
}
}
}

View File

@ -22,67 +22,63 @@ using System;
namespace DotRecast.Recast
{
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)
public class RecastCommon
{
int shift = dir * 6;
return (s.con >> shift) & 0x3f;
/// 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;
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
{
using static RecastConstants;
using static RecastVectors;
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)
public class RecastCompact
{
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();
int w = hf.width;
int h = hf.height;
int spanCount = getHeightFieldSpanCount(hf);
// Fill in header.
chf.width = w;
chf.height = h;
chf.borderSize = hf.borderSize;
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++)
/// @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)
{
chf.cells[i] = new CompactCell();
}
ctx.startTimer("BUILD_COMPACTHEIGHTFIELD");
for (int i = 0; i < chf.spans.Length; i++)
{
chf.spans[i] = new CompactSpan();
}
CompactHeightfield chf = new CompactHeightfield();
int w = hf.width;
int h = hf.height;
int spanCount = getHeightFieldSpanCount(hf);
// Fill in cells and spans.
int idx = 0;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
// Fill in header.
chf.width = w;
chf.height = h;
chf.borderSize = hf.borderSize;
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];
// 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++;
}
chf.cells[i] = new CompactCell();
}
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.
int tooHighNeighbour = 0;
for (int y = 0; y < h; ++y)
{
for (int x = 0; x < w; ++x)
// Find neighbour connections.
int tooHighNeighbour = 0;
for (int y = 0; y < h; ++y)
{
CompactCell c = chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
for (int x = 0; x < w; ++x)
{
CompactSpan s = chf.spans[i];
for (int dir = 0; dir < 4; ++dir)
CompactCell c = chf.cells[x + y * w];
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
RecastCommon.SetCon(s, dir, RC_NOT_CONNECTED);
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;
CompactSpan s = chf.spans[i];
// Iterate over all neighbour spans and check if any of the is
// accessible from current cell.
CompactCell nc = chf.cells[nx + ny * w];
for (int k = nc.index, nk = nc.index + nc.count; k < nk; ++k)
for (int dir = 0; dir < 4; ++dir)
{
CompactSpan ns = chf.spans[k];
int bot = Math.Max(s.y, ns.y);
int top = Math.Min(s.y + s.h, ns.y + ns.h);
RecastCommon.SetCon(s, dir, RC_NOT_CONNECTED);
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;
// Check that the gap between the spans is walkable,
// and that the climb height between the gaps is not too high.
if ((top - bot) >= walkableHeight && Math.Abs(ns.y - s.y) <= walkableClimb)
// Iterate over all neighbour spans and check if any of the is
// accessible from current cell.
CompactCell nc = chf.cells[nx + ny * w];
for (int k = nc.index, nk = nc.index + nc.count; k < nk; ++k)
{
// Mark direction as walkable.
int lidx = k - nc.index;
if (lidx < 0 || lidx > MAX_LAYERS)
{
tooHighNeighbour = Math.Max(tooHighNeighbour, lidx);
continue;
}
CompactSpan ns = chf.spans[k];
int bot = Math.Max(s.y, ns.y);
int top = Math.Min(s.y + s.h, ns.y + ns.h);
RecastCommon.SetCon(s, dir, lidx);
break;
// Check that the gap between the spans is walkable,
// 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)
{
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 (tooHighNeighbour > MAX_LAYERS)
{
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)
spanCount++;
for (Span s = hf.spans[x + y * w]; s != null; s = s.next)
{
if (s.area != RC_NULL_AREA)
spanCount++;
}
}
}
}
return spanCount;
return spanCount;
}
}
}
}

View File

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

View File

@ -20,69 +20,66 @@ freely, subject to the following restrictions:
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_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;
}
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
{
using static RecastConstants;
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)
public class RecastFilter
{
ctx.startTimer("FILTER_LOW_OBSTACLES");
int w = solid.width;
int h = solid.height;
for (int y = 0; y < h; ++y)
/// @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)
{
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;
bool previousWalkable = false;
int previousArea = RC_NULL_AREA;
for (Span s = solid.spans[x + y * w]; s != null; ps = s, s = s.next)
for (int x = 0; x < w; ++x)
{
bool walkable = s.area != RC_NULL_AREA;
// If current span is not walkable, but there is walkable
// span just below it, mark the span above it walkable too.
if (!walkable && previousWalkable)
Span ps = null;
bool previousWalkable = false;
int previousArea = RC_NULL_AREA;
for (Span s = solid.spans[x + y * w]; s != null; ps = s, s = s.next)
{
if (Math.Abs(s.smax - ps.smax) <= walkableClimb)
s.area = previousArea;
}
// 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)
bool walkable = s.area != RC_NULL_AREA;
// If current span is not walkable, but there is walkable
// span just below it, mark the span above it walkable too.
if (!walkable && previousWalkable)
{
minh = Math.Min(minh, -walkableClimb - bot);
continue;
if (Math.Abs(s.smax - ps.smax) <= walkableClimb)
s.area = previousArea;
}
// 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.
if (Math.Min(top, ntop) - Math.Max(bot, nbot) > walkableHeight)
minh = Math.Min(minh, nbot - bot);
// Copy walkable flag so that it cannot propagate
// past multiple non-walkable objects.
previousWalkable = walkable;
previousArea = s.area;
}
}
}
// Rest of the spans.
for (ns = solid.spans[dx + dy * w]; ns != null; ns = ns.next)
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)
{
nbot = ns.smax;
ntop = ns.next != null ? ns.next.smin : SPAN_MAX_HEIGHT;
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);
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.
if (Math.Min(top, ntop) - Math.Max(bot, nbot) > walkableHeight)
{
minh = Math.Min(minh, nbot - bot);
// Find min/max accessible neighbour height.
if (Math.Abs(nbot - bot) <= walkableClimb)
// Rest of the spans.
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)
asmin = nbot;
if (nbot > asmax)
asmax = nbot;
minh = Math.Min(minh, nbot - bot);
// Find min/max accessible neighbour height.
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
// neighbour span is less than the walkableClimb.
if (minh < -walkableClimb)
s.area = RC_NULL_AREA;
// The current span is close to a ledge if the drop to any
// neighbour span is less than the walkableClimb.
if (minh < -walkableClimb)
s.area = RC_NULL_AREA;
// If the difference between all neighbours is too large,
// we are at steep slope, mark the span as ledge.
if ((asmax - asmin) > walkableClimb)
{
s.area = RC_NULL_AREA;
// If the difference between all neighbours is too large,
// we are at steep slope, mark the span as ledge.
if ((asmax - asmin) > walkableClimb)
{
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
/// maximum to the next higher span's minimum. (Same grid column.)
///
/// @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)
/// @par
///
/// For this filter, the clearance above the span is the distance from the span's
/// maximum to the next higher span's minimum. (Same grid column.)
///
/// @see rcHeightfield, rcConfig
public static void filterWalkableLowHeightSpans(Telemetry ctx, int walkableHeight, Heightfield solid)
{
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);
int top = s.next != null ? s.next.smin : SPAN_MAX_HEIGHT;
if ((top - bot) <= walkableHeight)
s.area = RC_NULL_AREA;
for (Span s = solid.spans[x + y * w]; s != null; s = s.next)
{
int bot = (s.smax);
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
{
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;
using static RecastConstants;
using static RecastVectors;
using static RecastRegion;
private class LayerRegion
{
public int id;
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;
const int RC_MAX_NEIS = 16;
private class LayerRegion {
public int id;
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 void addUnique(List<int> a, int v)
{
if (!a.Contains(v))
{
a.Add(v);
}
}
};
private static void addUnique(List<int> a, int v) {
if (!a.Contains(v)) {
a.Add(v);
private static bool contains(List<int> a, int v)
{
return a.Contains(v);
}
}
private static bool contains(List<int> a, int v) {
return a.Contains(v);
}
private static bool overlapRange(int amin, int amax, int bmin, int bmax) {
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();
private static bool overlapRange(int amin, int amax, int bmin, int bmax)
{
return (amin > bmax || amax < bmin) ? false : true;
}
// 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) {
CompactCell c = chf.cells[x + y * w];
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();
}
for (int i = c.index, ni = c.index + c.count; i < ni; ++i) {
CompactSpan s = chf.spans[i];
if (chf.areas[i] == RC_NULL_AREA)
continue;
int sid = 0xFF;
// -x
// 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;
if (GetCon(s, 0) != RC_NOT_CONNECTED) {
int ax = x + GetDirOffsetX(0);
int ay = y + GetDirOffsetY(0);
int ai = chf.cells[ax + ay * w].index + GetCon(s, 0);
if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xff)
sid = srcReg[ai];
}
for (int x = borderSize; x < w - borderSize; ++x)
{
CompactCell c = chf.cells[x + y * w];
if (sid == 0xff) {
sid = sweepId++;
sweeps[sid].nei = 0xff;
sweeps[sid].ns = 0;
}
for (int i = c.index, ni = c.index + c.count; i < ni; ++i)
{
CompactSpan s = chf.spans[i];
if (chf.areas[i] == RC_NULL_AREA)
continue;
int sid = 0xFF;
// -x
// -y
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 (GetCon(s, 0) != RC_NOT_CONNECTED)
{
int ax = x + GetDirOffsetX(0);
int ay = y + GetDirOffsetY(0);
int ai = chf.cells[ax + ay * w].index + GetCon(s, 0);
if (chf.areas[ai] != RC_NULL_AREA && srcReg[ai] != 0xff)
sid = srcReg[ai];
}
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;
if (sid == 0xff)
{
sid = sweepId++;
sweeps[sid].nei = 0xff;
sweeps[sid].ns = 0;
}
// -y
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.
for (int i = 0; i < sweepId; ++i) {
// If the neighbour is set and there is only one continuous
// connection to it,
// the sweep will be merged with the previous one, else new
// region is created.
if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == sweeps[i].ns) {
sweeps[i].id = sweeps[i].nei;
} else {
if (regId == 255) {
throw new Exception("rcBuildHeightfieldLayers: Region ID overflow.");
// Create unique ID.
for (int i = 0; i < sweepId; ++i)
{
// If the neighbour is set and there is only one continuous
// connection to it,
// the sweep will be merged with the previous one, else new
// region is created.
if (sweeps[i].nei != 0xff && prevCount[sweeps[i].nei] == sweeps[i].ns)
{
sweeps[i].id = sweeps[i].nei;
}
sweeps[i].id = regId++;
}
}
// Remap local sweep ids to region ids.
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);
else
{
if (regId == 255)
{
throw new Exception("rcBuildHeightfieldLayers: Region ID overflow.");
}
}
}
// 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]);
}
sweeps[i].id = regId++;
}
}
}
}
// Create 2D layers from regions.
int layerId = 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);
// Remap local sweep ids to region ids.
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;
}
}
}
}
// Compact layerIds
int[] remap = new int[256];
int nregs = regId;
LayerRegion[] regs = new LayerRegion[nregs];
// 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;
}
// Construct regions
for (int i = 0; i < nregs; ++i)
{
regs[i] = new LayerRegion(i);
}
layer.width = lw;
layer.height = lh;
layer.cs = chf.cs;
layer.ch = chf.ch;
// 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];
// 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;
lregs.Clear();
// 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)
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;
// 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);
regs[ri].ymin = Math.Min(regs[ri].ymin, s.y);
regs[ri].ymax = Math.Max(regs[ri].ymax, s.y);
// Store height and area type.
int idx = x + y * lw;
layer.heights[idx] = (char) (s.y - hmin);
layer.areas[idx] = chf.areas[j];
// Collect all region layers.
lregs.Add(ri);
// 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);
// 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 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);
}
int rai = srcReg[ai];
if (rai != 0xff && rai != ri)
addUnique(regs[ri].neis, rai);
}
}
}
// Update overlapping regions.
for (int i = 0; i < lregs.Count - 1; ++i)
{
for (int j = i + 1; j < lregs.Count; ++j)
{
if (lregs[i] != lregs[j])
{
LayerRegion ri = regs[lregs[i]];
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)
layer.minx = layer.maxx = 0;
if (layer.miny > layer.maxy)
layer.miny = layer.maxy = 0;
// Create 2D layers from regions.
int layerId = 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
{
using static RecastConstants;
using static RecastConstants;
public class RecastRasterization
{
/**
public class RecastRasterization
{
/**
* Check whether two bounding boxes overlap
*
* @param amin
@ -41,16 +39,16 @@ public class RecastRasterization
* Max axis extents of bounding box B
* @returns true if the two bounding boxes overlap. False otherwise
*/
private static bool overlapBounds(float[] amin, float[] amax, float[] bmin, float[] bmax)
{
bool overlap = true;
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap;
return overlap;
}
private static bool overlapBounds(float[] amin, float[] amax, float[] bmin, float[] bmax)
{
bool overlap = true;
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
overlap = (amin[2] > bmax[2] || amax[2] < bmin[2]) ? false : overlap;
return overlap;
}
/**
/**
* 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
* 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]
* @see Heightfield, Span.
*/
public static void addSpan(Heightfield heightfield, int x, int y, int spanMin, int spanMax, int areaId,
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)
public static void addSpan(Heightfield heightfield, int x, int y, int spanMin, int spanMax, int areaId,
int flagMergeThreshold)
{
heightfield.spans[idx] = s;
return;
}
int idx = x + y * heightfield.width;
Span prev = null;
Span cur = heightfield.spans[idx];
Span s = new Span();
s.smin = spanMin;
s.smax = spanMax;
s.area = areaId;
s.next = null;
// Insert and merge spans.
while (cur != null)
{
if (cur.smin > s.smax)
// Empty cell, add the first span.
if (heightfield.spans[idx] == null)
{
// Current span is further than the new span, break.
break;
heightfield.spans[idx] = s;
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.
prev = cur;
cur = cur.next;
if (cur.smin > s.smax)
{
// 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
{
// 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;
s.next = heightfield.spans[idx];
heightfield.spans[idx] = s;
}
}
// 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.
*
* @param inVerts
@ -160,63 +158,63 @@ public class RecastRasterization
* The separating axis
* @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,
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)
private static int[] dividePoly(float[] inVerts, int inVertsOffset, int inVertsCount, int outVerts1, int outVerts2, float axisOffset,
int axis)
{
bool ina = d[j] >= 0;
bool inb = d[i] >= 0;
if (ina != inb)
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)
{
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++;
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)
bool ina = d[j] >= 0;
bool inb = d[i] >= 0;
if (ina != inb)
{
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++;
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);
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);
n++;
}
return new int[] { m, 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
* maintaining maximum perf here.
*
@ -245,138 +243,138 @@ public class RecastRasterization
* @param flagMergeThreshold
* 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,
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)
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)
{
// Clip polygon to row. Store the remaining polygon as well
float cellZ = hfBBMin[2] + z * cellSize;
int[] nvrowin = dividePoly(buf, @in, nvIn, inRow, p1, cellZ + cellSize, 2);
nvRow = nvrowin[0];
nvIn = nvrowin[1];
{
int temp = @in;
@in = p1;
p1 = temp;
}
if (nvRow < 3)
continue;
float[] tmin = new float[3];
float[] tmax = new float[3];
float by = hfBBMax[1] - hfBBMin[1];
if (z < 0)
{
continue;
}
// 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);
// find the horizontal bounds in the row
float minX = buf[inRow], maxX = buf[inRow];
for (int i = 1; i < nvRow; ++i)
{
float v = buf[inRow + i * 3];
minX = Math.Min(minX, v);
maxX = Math.Max(maxX, v);
}
// If the triangle does not touch the bbox of the heightfield, skip the triagle.
if (!overlapBounds(hfBBMin, hfBBMax, tmin, tmax))
return;
int x0 = (int)((minX - hfBBMin[0]) * inverseCellSize);
int x1 = (int)((maxX - hfBBMin[0]) * inverseCellSize);
if (x1 < 0 || x0 >= w)
{
continue;
}
// 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);
x0 = RecastCommon.clamp(x0, -1, w - 1);
x1 = RecastCommon.clamp(x1, 0, w - 1);
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);
int nv, nv2 = nvRow;
for (int x = x0; x <= x1; ++x)
// 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 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];
// Clip polygon to row. Store the remaining polygon as well
float cellZ = hfBBMin[2] + z * cellSize;
int[] nvrowin = dividePoly(buf, @in, nvIn, inRow, p1, cellZ + cellSize, 2);
nvRow = nvrowin[0];
nvIn = nvrowin[1];
{
int temp = inRow;
inRow = p2;
p2 = temp;
int temp = @in;
@in = p1;
p1 = temp;
}
if (nv < 3)
if (nvRow < 3)
continue;
if (x < 0)
if (z < 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)
// find the horizontal bounds in the row
float minX = buf[inRow], maxX = buf[inRow];
for (int i = 1; i < nvRow; ++i)
{
spanMin = Math.Min(spanMin, buf[p1 + i * 3 + 1]);
spanMax = Math.Max(spanMax, buf[p1 + i * 3 + 1]);
float v = buf[inRow + i * 3];
minX = Math.Min(minX, v);
maxX = Math.Max(maxX, v);
}
spanMin -= hfBBMin[1];
spanMax -= hfBBMin[1];
// Skip the span if it is outside the heightfield bbox
if (spanMax < 0.0f)
int x0 = (int)((minX - hfBBMin[0]) * inverseCellSize);
int x1 = (int)((maxX - hfBBMin[0]) * inverseCellSize);
if (x1 < 0 || x0 >= w)
{
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);
x0 = RecastCommon.clamp(x0, -1, w - 1);
x1 = RecastCommon.clamp(x1, 0, w - 1);
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
* efficient than calling rasterizeTriangles. No spans will be added if the triangle does not overlap the
* 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]
* @see Heightfield
*/
public static void rasterizeTriangle(Heightfield heightfield, float[] verts, int v0, int v1, int v2, int area,
int flagMergeThreshold, Telemetry ctx)
{
ctx.startTimer("RASTERIZE_TRIANGLES");
public static void rasterizeTriangle(Heightfield heightfield, float[] verts, int v0, int v1, int v2, int area,
int flagMergeThreshold, Telemetry ctx)
{
ctx.startTimer("RASTERIZE_TRIANGLES");
float inverseCellSize = 1.0f / heightfield.cs;
float inverseCellHeight = 1.0f / heightfield.ch;
rasterizeTri(verts, v0, v1, v2, area, heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize,
inverseCellHeight, flagMergeThreshold);
float inverseCellSize = 1.0f / heightfield.cs;
float inverseCellHeight = 1.0f / heightfield.ch;
rasterizeTri(verts, v0, v1, v2, area, heightfield, heightfield.bmin, heightfield.bmax, heightfield.cs, inverseCellSize,
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
* 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]
* @see Heightfield
*/
public static void rasterizeTriangles(Heightfield heightfield, float[] verts, int[] tris, int[] areaIds, int numTris,
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)
public static void rasterizeTriangles(Heightfield heightfield, float[] verts, int[] tris, int[] areaIds, int numTris,
int flagMergeThreshold, Telemetry ctx)
{
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.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];
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
* 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]
* @see Heightfield
*/
public static void rasterizeTriangles(Heightfield heightfield, float[] verts, int[] areaIds, int numTris,
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)
public static void rasterizeTriangles(Heightfield heightfield, float[] verts, int[] areaIds, int numTris,
int flagMergeThreshold, Telemetry ctx)
{
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.startTimer("RASTERIZE_TRIANGLES");
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
{
public static class RecastVectors
{
public static void min(float[] a, float[] b, int i)
public static class RecastVectors
{
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 min(float[] a, float[] b, int i)
{
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)
{
a[0] = Math.Max(a[0], b[i + 0]);
a[1] = Math.Max(a[1], b[i + 1]);
a[2] = Math.Max(a[2], b[i + 2]);
}
public static void max(float[] a, float[] b, int i)
{
a[0] = Math.Max(a[0], b[i + 0]);
a[1] = Math.Max(a[1], b[i + 1]);
a[2] = Math.Max(a[2], b[i + 2]);
}
public static void copy(float[] @out, float[] @in, int i)
{
copy(@out, 0, @in, i);
}
public static void copy(float[] @out, float[] @in, int i)
{
copy(@out, 0, @in, i);
}
public static void copy(float[] @out, float[] @in)
{
copy(@out, 0, @in, 0);
}
public static void copy(float[] @out, float[] @in)
{
copy(@out, 0, @in, 0);
}
public static void copy(float[] @out, int n, float[] @in, int m)
{
@out[n] = @in[m];
@out[n + 1] = @in[m + 1];
@out[n + 2] = @in[m + 2];
}
public static void copy(float[] @out, int n, float[] @in, int m)
{
@out[n] = @in[m];
@out[n + 1] = @in[m + 1];
@out[n + 2] = @in[m + 2];
}
public static void add(float[] e0, float[] a, float[] verts, int i)
{
e0[0] = a[0] + verts[i];
e0[1] = a[1] + verts[i + 1];
e0[2] = a[2] + verts[i + 2];
}
public static void add(float[] e0, float[] a, float[] verts, int i)
{
e0[0] = a[0] + verts[i];
e0[1] = a[1] + verts[i + 1];
e0[2] = a[2] + verts[i + 2];
}
public static void sub(float[] e0, float[] verts, int i, int j)
{
e0[0] = verts[i] - verts[j];
e0[1] = verts[i + 1] - verts[j + 1];
e0[2] = verts[i + 2] - verts[j + 2];
}
public static void sub(float[] e0, float[] verts, int i, int j)
{
e0[0] = verts[i] - verts[j];
e0[1] = verts[i + 1] - verts[j + 1];
e0[2] = verts[i + 2] - verts[j + 2];
}
public static void sub(float[] e0, float[] i, float[] verts, int j)
{
e0[0] = i[0] - verts[j];
e0[1] = i[1] - verts[j + 1];
e0[2] = i[2] - verts[j + 2];
}
public static void sub(float[] e0, float[] i, float[] verts, int j)
{
e0[0] = i[0] - verts[j];
e0[1] = i[1] - verts[j + 1];
e0[2] = i[2] - verts[j + 2];
}
public static void cross(float[] dest, float[] v1, float[] v2)
{
dest[0] = v1[1] * v2[2] - v1[2] * v2[1];
dest[1] = v1[2] * v2[0] - v1[0] * v2[2];
dest[2] = v1[0] * v2[1] - v1[1] * v2[0];
}
public static void cross(float[] dest, float[] v1, float[] v2)
{
dest[0] = v1[1] * v2[2] - v1[2] * v2[1];
dest[1] = v1[2] * v2[0] - v1[0] * v2[2];
dest[2] = v1[0] * v2[1] - v1[1] * v2[0];
}
public static void normalize(float[] v)
{
float d = (float)(1.0f / Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]));
v[0] *= d;
v[1] *= d;
v[2] *= d;
}
public static void normalize(float[] v)
{
float d = (float)(1.0f / Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]));
v[0] *= d;
v[1] *= d;
v[2] *= d;
}
public static float dot(float[] v1, float[] v2)
{
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
public static float dot(float[] v1, float[] v2)
{
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
{
public class RecastVoxelization
{
public static Heightfield buildSolidHeightfield(InputGeomProvider geomProvider, RecastBuilderConfig builderCfg,
Telemetry ctx)
{
RecastConfig cfg = builderCfg.cfg;
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.
Heightfield solid = new Heightfield(builderCfg.width, builderCfg.height, builderCfg.bmin, builderCfg.bmax, cfg.cs,
// 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);
// Allocate array that can hold triangle area types.
// If you have multiple meshes you need to process, allocate
// and array which can hold the max number of triangles you need to
// process.
// Allocate array that can hold triangle area types.
// If you have multiple meshes you need to process, allocate
// and array which can hold the max number of triangles you need to
// process.
// Find triangles which are walkable based on their slope and rasterize
// them.
// If your input data is multiple meshes, you can transform them here,
// calculate
// the are type for each of the meshes and rasterize them.
foreach (TriMesh geom in geomProvider.meshes()) {
float[] verts = geom.getVerts();
if (cfg.useTiles) {
float[] tbmin = new float[2];
float[] tbmax = new float[2];
tbmin[0] = builderCfg.bmin[0];
tbmin[1] = builderCfg.bmin[2];
tbmax[0] = builderCfg.bmax[0];
tbmax[1] = builderCfg.bmax[2];
List<ChunkyTriMeshNode> nodes = geom.getChunksOverlappingRect(tbmin, tbmax);
foreach (ChunkyTriMeshNode node in nodes) {
int[] tris = node.tris;
// Find triangles which are walkable based on their slope and rasterize
// them.
// If your input data is multiple meshes, you can transform them here,
// calculate
// the are type for each of the meshes and rasterize them.
foreach (TriMesh geom in geomProvider.meshes())
{
float[] verts = geom.getVerts();
if (cfg.useTiles)
{
float[] tbmin = new float[2];
float[] tbmax = new float[2];
tbmin[0] = builderCfg.bmin[0];
tbmin[1] = builderCfg.bmin[2];
tbmax[0] = builderCfg.bmax[0];
tbmax[1] = builderCfg.bmax[2];
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[] 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);
}
} 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.
3. This notice may not be removed or altered from any source distribution.
*/
namespace DotRecast.Recast
{
/** Represents a span in a heightfield. */
public class Span
{
/** 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. */
public class Span {
/** The lower limit of the span. [Limit: &lt; smax] */
public int smin;
/** The upper limit of the span. [Limit: &lt;= SPAN_MAX_HEIGHT] */
public int smax;
/** The area id assigned to the span. */
public int area;
/** The next span higher up in column. */
public Span next;
}
/** The area id assigned to the span. */
public int area;
/** The next span higher up in column. */
public Span next;
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -24,288 +24,296 @@ using NUnit.Framework;
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 = {
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_A1Q2TVTAS =
{
new[] { 23.253357f, 10.197294f, -46.279934f, 0.074597f, 0.000000f, -0.017069f },
new[] { 23.336805f, 10.197294f, -46.374985f, 0.119401f, 0.000000f, -0.031009f },
new[] { 23.351542f, 10.197294f, -46.410728f, 0.053426f, 0.000000f, -0.093556f },
new[] { 23.362417f, 10.197294f, -46.429638f, 0.054377f, 0.000000f, -0.094549f },
new[] { 23.216995f, 10.197294f, -46.446442f, -0.727108f, 0.000000f, -0.084018f },
new[] { 22.902132f, 10.197294f, -46.426094f, -1.574317f, 0.000000f, 0.101733f },
new[] { 22.491152f, 10.197294f, -46.376247f, -2.054897f, 0.000000f, 0.249239f },
new[] { 22.058567f, 10.197294f, -46.289536f, -2.162925f, 0.000000f, 0.433569f },
new[] { 21.588501f, 10.197294f, -46.183846f, -2.350327f, 0.000000f, 0.528449f },
new[] { 21.116070f, 10.197294f, -46.072765f, -2.362152f, 0.000000f, 0.555402f },
new[] { 20.642447f, 10.197294f, -45.954391f, -2.368116f, 0.000000f, 0.591867f },
new[] { 20.168785f, 10.197294f, -45.827782f, -2.368307f, 0.000000f, 0.633050f },
new[] { 19.695480f, 10.197294f, -45.692959f, -2.366527f, 0.000000f, 0.674118f },
new[] { 19.221388f, 10.197294f, -45.549664f, -2.370462f, 0.000000f, 0.716482f },
new[] { 18.747715f, 10.197294f, -45.401028f, -2.368369f, 0.000000f, 0.743186f },
new[] { 18.274532f, 10.197294f, -45.247219f, -2.365911f, 0.000000f, 0.769049f },
new[] { 17.801916f, 10.197294f, -45.088371f, -2.363083f, 0.000000f, 0.794239f },
new[] { 17.329977f, 10.197294f, -44.924515f, -2.359692f, 0.000000f, 0.819281f },
new[] { 16.858923f, 10.197294f, -44.755489f, -2.355273f, 0.000000f, 0.845136f },
new[] { 16.389112f, 10.197294f, -44.580818f, -2.349057f, 0.000000f, 0.873354f },
new[] { 15.904601f, 10.197294f, -44.397808f, -2.422558f, 0.000000f, 0.915057f },
new[] { 15.422479f, 10.197294f, -44.204182f, -2.410610f, 0.000000f, 0.968130f },
new[] { 14.943066f, 10.197294f, -44.000332f, -2.397065f, 0.000000f, 1.019240f },
new[] { 14.466599f, 10.197294f, -43.786751f, -2.382330f, 0.000000f, 1.067903f },
new[] { 13.993264f, 10.197294f, -43.563339f, -2.366675f, 0.000000f, 1.117066f },
new[] { 13.539879f, 10.197294f, -43.333740f, -2.266925f, 0.000000f, 1.147989f },
new[] { 13.089582f, 10.197294f, -43.096500f, -2.251482f, 0.000000f, 1.186200f },
new[] { 12.642441f, 10.197294f, -42.852135f, -2.235710f, 0.000000f, 1.221827f },
new[] { 12.198518f, 10.197294f, -42.601135f, -2.219616f, 0.000000f, 1.254998f },
new[] { 11.757890f, 10.197294f, -42.343952f, -2.203139f, 0.000000f, 1.285906f },
new[] { 11.320659f, 10.197294f, -42.080994f, -2.186156f, 0.000000f, 1.314789f },
new[] { 10.886962f, 10.197294f, -41.812611f, -2.168484f, 0.000000f, 1.341919f },
new[] { 10.456985f, 10.197294f, -41.539093f, -2.149882f, 0.000000f, 1.367595f },
new[] { 10.030977f, 10.197294f, -41.260666f, -2.130040f, 0.000000f, 1.392133f },
new[] { 9.609450f, 10.197294f, -40.977509f, -2.107634f, 0.000000f, 1.415778f },
new[] { 9.193022f, 10.197294f, -40.690060f, -2.082145f, 0.000000f, 1.437238f },
new[] { 8.782290f, 10.197294f, -40.398022f, -2.053656f, 0.000000f, 1.460193f },
new[] { 8.378143f, 10.197294f, -40.101349f, -2.020734f, 0.000000f, 1.483373f },
new[] { 7.981577f, 10.197294f, -39.799294f, -1.982833f, 0.000000f, 1.510276f },
new[] { 7.592501f, 10.197294f, -39.491383f, -1.945382f, 0.000000f, 1.539564f },
new[] { 7.235472f, 10.197294f, -39.191612f, -1.785145f, 0.000000f, 1.498847f },
new[] { 6.856690f, 10.197294f, -38.869774f, -1.893909f, 0.000000f, 1.609192f },
new[] { 6.501227f, 10.197294f, -38.570526f, -1.777313f, 0.000000f, 1.496231f },
new[] { 6.224775f, 10.197294f, -38.301174f, -1.382261f, 0.000000f, 1.346764f },
new[] { 5.929498f, 10.197294f, -37.995701f, -1.476386f, 0.000000f, 1.527363f },
new[] { 5.629647f, 10.197294f, -37.639618f, -1.499255f, 0.000000f, 1.780412f },
new[] { 5.328491f, 10.197294f, -37.296764f, -1.505784f, 0.000000f, 1.714271f },
new[] { 5.023998f, 10.197294f, -36.908840f, -1.522466f, 0.000000f, 1.939620f },
new[] { 4.744634f, 10.197294f, -36.502831f, -1.396819f, 0.000000f, 2.030053f },
new[] { 4.492529f, 10.197294f, -36.078068f, -1.260521f, 0.000000f, 2.123809f },
new[] { 4.239073f, 10.197294f, -35.631050f, -1.267281f, 0.000000f, 2.235098f },
new[] { 3.989349f, 10.197294f, -35.178169f, -1.248621f, 0.000000f, 2.264413f },
new[] { 3.736778f, 10.197294f, -34.723938f, -1.262857f, 0.000000f, 2.271152f },
new[] { 3.491473f, 10.197294f, -34.264828f, -1.226526f, 0.000000f, 2.295557f },
new[] { 3.177553f, 10.197294f, -33.848518f, -1.569597f, 0.000000f, 2.081553f },
new[] { 2.866278f, 10.197294f, -33.427658f, -1.556375f, 0.000000f, 2.104302f },
new[] { 2.566121f, 10.197294f, -32.995960f, -1.500786f, 0.000000f, 2.158491f },
new[] { 2.291139f, 10.197294f, -32.544334f, -1.374910f, 0.000000f, 2.258132f },
new[] { 2.066509f, 10.197294f, -32.063156f, -1.123153f, 0.000000f, 2.405891f },
new[] { 1.949471f, 10.197294f, -31.544592f, -0.585186f, 0.000000f, 2.592823f },
new[] { 1.841830f, 10.197294f, -31.020411f, -0.538206f, 0.000000f, 2.620903f },
new[] { 1.804908f, 10.197294f, -30.484823f, -0.184613f, 0.000000f, 2.677938f },
new[] { 1.848811f, 10.197294f, -29.937574f, 0.219515f, 0.000000f, 2.736246f },
new[] { 1.946300f, 10.197294f, -29.417610f, 0.487449f, 0.000000f, 2.599819f },
new[] { 2.111085f, 10.197294f, -28.884066f, 0.823926f, 0.000000f, 2.667721f },
new[] { 2.288109f, 10.197294f, -28.358007f, 0.885119f, 0.000000f, 2.630293f },
new[] { 2.499196f, 10.197294f, -27.812208f, 1.055435f, 0.000000f, 2.728995f },
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_A1Q2TVTAS = {
new[] { 23.253357f, 10.197294f, -46.279934f, 0.074597f, 0.000000f, -0.017069f },
new[] { 23.336805f, 10.197294f, -46.374985f, 0.119401f, 0.000000f, -0.031009f },
new[] { 23.351542f, 10.197294f, -46.410728f, 0.053426f, 0.000000f, -0.093556f },
new[] { 23.362417f, 10.197294f, -46.429638f, 0.054377f, 0.000000f, -0.094549f },
new[] { 23.216995f, 10.197294f, -46.446442f, -0.727108f, 0.000000f, -0.084018f },
new[] { 22.902132f, 10.197294f, -46.426094f, -1.574317f, 0.000000f, 0.101733f },
new[] { 22.491152f, 10.197294f, -46.376247f, -2.054897f, 0.000000f, 0.249239f },
new[] { 22.058567f, 10.197294f, -46.289536f, -2.162925f, 0.000000f, 0.433569f },
new[] { 21.588501f, 10.197294f, -46.183846f, -2.350327f, 0.000000f, 0.528449f },
new[] { 21.116070f, 10.197294f, -46.072765f, -2.362152f, 0.000000f, 0.555402f },
new[] { 20.642447f, 10.197294f, -45.954391f, -2.368116f, 0.000000f, 0.591867f },
new[] { 20.168785f, 10.197294f, -45.827782f, -2.368307f, 0.000000f, 0.633050f },
new[] { 19.695480f, 10.197294f, -45.692959f, -2.366527f, 0.000000f, 0.674118f },
new[] { 19.221388f, 10.197294f, -45.549664f, -2.370462f, 0.000000f, 0.716482f },
new[] { 18.747715f, 10.197294f, -45.401028f, -2.368369f, 0.000000f, 0.743186f },
new[] { 18.274532f, 10.197294f, -45.247219f, -2.365911f, 0.000000f, 0.769049f },
new[] { 17.801916f, 10.197294f, -45.088371f, -2.363083f, 0.000000f, 0.794239f },
new[] { 17.329977f, 10.197294f, -44.924515f, -2.359692f, 0.000000f, 0.819281f },
new[] { 16.858923f, 10.197294f, -44.755489f, -2.355273f, 0.000000f, 0.845136f },
new[] { 16.389112f, 10.197294f, -44.580818f, -2.349057f, 0.000000f, 0.873354f },
new[] { 15.904601f, 10.197294f, -44.397808f, -2.422558f, 0.000000f, 0.915057f },
new[] { 15.422479f, 10.197294f, -44.204182f, -2.410610f, 0.000000f, 0.968130f },
new[] { 14.943066f, 10.197294f, -44.000332f, -2.397065f, 0.000000f, 1.019240f },
new[] { 14.466599f, 10.197294f, -43.786751f, -2.382330f, 0.000000f, 1.067903f },
new[] { 13.993264f, 10.197294f, -43.563339f, -2.366675f, 0.000000f, 1.117066f },
new[] { 13.539879f, 10.197294f, -43.333740f, -2.266925f, 0.000000f, 1.147989f },
new[] { 13.089582f, 10.197294f, -43.096500f, -2.251482f, 0.000000f, 1.186200f },
new[] { 12.642441f, 10.197294f, -42.852135f, -2.235710f, 0.000000f, 1.221827f },
new[] { 12.198518f, 10.197294f, -42.601135f, -2.219616f, 0.000000f, 1.254998f },
new[] { 11.757890f, 10.197294f, -42.343952f, -2.203139f, 0.000000f, 1.285906f },
new[] { 11.320659f, 10.197294f, -42.080994f, -2.186156f, 0.000000f, 1.314789f },
new[] { 10.886962f, 10.197294f, -41.812611f, -2.168484f, 0.000000f, 1.341919f },
new[] { 10.456985f, 10.197294f, -41.539093f, -2.149882f, 0.000000f, 1.367595f },
new[] { 10.030977f, 10.197294f, -41.260666f, -2.130040f, 0.000000f, 1.392133f },
new[] { 9.609450f, 10.197294f, -40.977509f, -2.107634f, 0.000000f, 1.415778f },
new[] { 9.193022f, 10.197294f, -40.690060f, -2.082145f, 0.000000f, 1.437238f },
new[] { 8.782290f, 10.197294f, -40.398022f, -2.053656f, 0.000000f, 1.460193f },
new[] { 8.378143f, 10.197294f, -40.101349f, -2.020734f, 0.000000f, 1.483373f },
new[] { 7.981577f, 10.197294f, -39.799294f, -1.982833f, 0.000000f, 1.510276f },
new[] { 7.592501f, 10.197294f, -39.491383f, -1.945382f, 0.000000f, 1.539564f },
new[] { 7.235472f, 10.197294f, -39.191612f, -1.785145f, 0.000000f, 1.498847f },
new[] { 6.856690f, 10.197294f, -38.869774f, -1.893909f, 0.000000f, 1.609192f },
new[] { 6.501227f, 10.197294f, -38.570526f, -1.777313f, 0.000000f, 1.496231f },
new[] { 6.224775f, 10.197294f, -38.301174f, -1.382261f, 0.000000f, 1.346764f },
new[] { 5.929498f, 10.197294f, -37.995701f, -1.476386f, 0.000000f, 1.527363f },
new[] { 5.629647f, 10.197294f, -37.639618f, -1.499255f, 0.000000f, 1.780412f },
new[] { 5.328491f, 10.197294f, -37.296764f, -1.505784f, 0.000000f, 1.714271f },
new[] { 5.023998f, 10.197294f, -36.908840f, -1.522466f, 0.000000f, 1.939620f },
new[] { 4.744634f, 10.197294f, -36.502831f, -1.396819f, 0.000000f, 2.030053f },
new[] { 4.492529f, 10.197294f, -36.078068f, -1.260521f, 0.000000f, 2.123809f },
new[] { 4.239073f, 10.197294f, -35.631050f, -1.267281f, 0.000000f, 2.235098f },
new[] { 3.989349f, 10.197294f, -35.178169f, -1.248621f, 0.000000f, 2.264413f },
new[] { 3.736778f, 10.197294f, -34.723938f, -1.262857f, 0.000000f, 2.271152f },
new[] { 3.491473f, 10.197294f, -34.264828f, -1.226526f, 0.000000f, 2.295557f },
new[] { 3.177553f, 10.197294f, -33.848518f, -1.569597f, 0.000000f, 2.081553f },
new[] { 2.866278f, 10.197294f, -33.427658f, -1.556375f, 0.000000f, 2.104302f },
new[] { 2.566121f, 10.197294f, -32.995960f, -1.500786f, 0.000000f, 2.158491f },
new[] { 2.291139f, 10.197294f, -32.544334f, -1.374910f, 0.000000f, 2.258132f },
new[] { 2.066509f, 10.197294f, -32.063156f, -1.123153f, 0.000000f, 2.405891f },
new[] { 1.949471f, 10.197294f, -31.544592f, -0.585186f, 0.000000f, 2.592823f },
new[] { 1.841830f, 10.197294f, -31.020411f, -0.538206f, 0.000000f, 2.620903f },
new[] { 1.804908f, 10.197294f, -30.484823f, -0.184613f, 0.000000f, 2.677938f },
new[] { 1.848811f, 10.197294f, -29.937574f, 0.219515f, 0.000000f, 2.736246f },
new[] { 1.946300f, 10.197294f, -29.417610f, 0.487449f, 0.000000f, 2.599819f },
new[] { 2.111085f, 10.197294f, -28.884066f, 0.823926f, 0.000000f, 2.667721f },
new[] { 2.288109f, 10.197294f, -28.358007f, 0.885119f, 0.000000f, 2.630293f },
new[] { 2.499196f, 10.197294f, -27.812208f, 1.055435f, 0.000000f, 2.728995f },
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 } };
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]
public void testAgent1Quality2TVTA() {
public void testAgent1Quality2TVTA()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE;
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q2TVTA.Length; i++) {
for (int i = 0; i < EXPECTED_A1Q2TVTA.Length; i++)
{
crowd.update(1 / 5f, null);
CrowdAgent ag = agents[2];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTA[i][0]).Within(0.001f), $"{i}");
@ -318,14 +326,16 @@ public class Crowd4Test : AbstractCrowdTest {
}
[Test]
public void testAgent1Quality2TVTAS() {
public void testAgent1Quality2TVTAS()
{
int updateFlags = CrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS | CrowdAgentParams.DT_CROWD_OPTIMIZE_VIS
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE
| CrowdAgentParams.DT_CROWD_SEPARATION;
| CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO | CrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE
| CrowdAgentParams.DT_CROWD_SEPARATION;
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
setMoveTarget(endPoss[0], false);
for (int i = 0; i < EXPECTED_A1Q2TVTAS.Length; i++) {
for (int i = 0; i < EXPECTED_A1Q2TVTAS.Length; i++)
{
crowd.update(1 / 5f, null);
CrowdAgent ag = agents[2];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2TVTAS[i][0]).Within(0.001f), $"{i}");
@ -338,7 +348,8 @@ public class Crowd4Test : AbstractCrowdTest {
}
[Test]
public void testAgent1Quality2T() {
public void testAgent1Quality2T()
{
int updateFlags = CrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO;
addAgentGrid(2, 0.3f, updateFlags, 2, startPoss[0]);
@ -352,13 +363,19 @@ public class Crowd4Test : AbstractCrowdTest {
crowd.update(1 / 5f, null);
CrowdAgent ag = agents[2];
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2T[i][0]).Within(0.00001f), $"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}"); Console.WriteLine($"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}");
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q2T[i][1]).Within(0.00001f), $"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}"); Console.WriteLine($"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}");
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q2T[i][2]).Within(0.00001f), $"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}"); Console.WriteLine($"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}");
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q2T[i][3]).Within(0.00001f), $"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}"); Console.WriteLine($"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}");
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q2T[i][4]).Within(0.00001f), $"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}"); Console.WriteLine($"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}");
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q2T[i][5]).Within(0.00001f), $"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}"); Console.WriteLine($"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}");
Assert.That(ag.npos[0], Is.EqualTo(EXPECTED_A1Q2T[i][0]).Within(0.00001f), $"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}");
Console.WriteLine($"{i} - {ag.npos[0]} {EXPECTED_A1Q2T[i][0]}");
Assert.That(ag.npos[1], Is.EqualTo(EXPECTED_A1Q2T[i][1]).Within(0.00001f), $"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}");
Console.WriteLine($"{i} - {ag.npos[1]} {EXPECTED_A1Q2T[i][1]}");
Assert.That(ag.npos[2], Is.EqualTo(EXPECTED_A1Q2T[i][2]).Within(0.00001f), $"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}");
Console.WriteLine($"{i} - {ag.npos[2]} {EXPECTED_A1Q2T[i][2]}");
Assert.That(ag.nvel[0], Is.EqualTo(EXPECTED_A1Q2T[i][3]).Within(0.00001f), $"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}");
Console.WriteLine($"{i} - {ag.nvel[0]} {EXPECTED_A1Q2T[i][3]}");
Assert.That(ag.nvel[1], Is.EqualTo(EXPECTED_A1Q2T[i][4]).Within(0.00001f), $"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}");
Console.WriteLine($"{i} - {ag.nvel[1]} {EXPECTED_A1Q2T[i][4]}");
Assert.That(ag.nvel[2], Is.EqualTo(EXPECTED_A1Q2T[i][5]).Within(0.00001f), $"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}");
Console.WriteLine($"{i} - {ag.nvel[2]} {EXPECTED_A1Q2T[i][5]}");
Thread.Sleep(1);
}
}
}
}

View File

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

View File

@ -1,28 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<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>
<IsPackable>false</IsPackable>
</PropertyGroup>
<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>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<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>
<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>

View File

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

View File

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

View File

@ -20,8 +20,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Crowd.Test;
public class SampleAreaModifications {
public class SampleAreaModifications
{
public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
@ -31,17 +31,22 @@ public class SampleAreaModifications {
public const int SAMPLE_POLYAREA_TYPE_JUMP = 0x6;
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);
SAMPLE_POLYAREA_TYPE_MASK);
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,
SAMPLE_POLYAREA_TYPE_MASK);
SAMPLE_POLYAREA_TYPE_MASK);
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,
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_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_DISABLED = 0x10; // Disabled polygon
public const int SAMPLE_POLYFLAGS_ALL = 0xffff; // All abilities.
}
}

View File

@ -1,26 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<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>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<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>
<ProjectReference Include="..\..\src\DotRecast.Detour.Dynamic\DotRecast.Detour.Dynamic.csproj" />
<ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj" />
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Detour.Dynamic\DotRecast.Detour.Dynamic.csproj"/>
<ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj"/>
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj"/>
</ItemGroup>
</Project>

View File

@ -8,8 +8,8 @@ using NUnit.Framework;
namespace DotRecast.Detour.Dynamic.Test;
public class DynamicNavMeshTest {
public class DynamicNavMeshTest
{
private static readonly float[] START_POS = new float[] { 70.87453f, 0.0010070801f, 86.69021f };
private static readonly float[] END_POS = new float[] { -50.22061f, 0.0010070801f, -70.761444f };
private static readonly float[] EXTENT = new float[] { 0.1f, 0.1f, 0.1f };
@ -17,11 +17,12 @@ public class DynamicNavMeshTest {
[Test]
public void e2eTest() {
public void e2eTest()
{
byte[] bytes = Loader.ToBytes("test_tiles.voxels");
using var ms = new MemoryStream(bytes);
using var bis = new BinaryReader(ms);
// load voxels from file
VoxelFileReader reader = new VoxelFileReader();
VoxelFile f = reader.read(bis);
@ -38,7 +39,7 @@ public class DynamicNavMeshTest {
FindNearestPolyResult start = query.findNearestPoly(START_POS, EXTENT, filter).result;
FindNearestPolyResult end = query.findNearestPoly(END_POS, EXTENT, filter).result;
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
Assert.That(path.Count, Is.EqualTo(16));
// place obstacle
@ -54,7 +55,7 @@ public class DynamicNavMeshTest {
start = query.findNearestPoly(START_POS, EXTENT, filter).result;
end = query.findNearestPoly(END_POS, EXTENT, filter).result;
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
Assert.That(path.Count, Is.EqualTo(19));
// remove obstacle
@ -69,8 +70,8 @@ public class DynamicNavMeshTest {
start = query.findNearestPoly(START_POS, EXTENT, filter).result;
end = query.findNearestPoly(END_POS, EXTENT, filter).result;
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
Assert.That(path.Count, Is.EqualTo(16));
}
}
}

View File

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

View File

@ -20,8 +20,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Dynamic.Test;
public class SampleAreaModifications {
public class SampleAreaModifications
{
public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
@ -31,17 +31,22 @@ public class SampleAreaModifications {
public const int SAMPLE_POLYAREA_TYPE_JUMP = 0x6;
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);
SAMPLE_POLYAREA_TYPE_MASK);
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,
SAMPLE_POLYAREA_TYPE_MASK);
SAMPLE_POLYAREA_TYPE_MASK);
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,
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_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_DISABLED = 0x10; // Disabled polygon
public const int SAMPLE_POLYFLAGS_ALL = 0xffff; // All abilities.
}
}

View File

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

View File

@ -1,26 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<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>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<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>
<ProjectReference Include="..\..\src\DotRecast.Detour.Extras\DotRecast.Detour.Extras.csproj" />
<ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj" />
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Detour.Extras\DotRecast.Detour.Extras.csproj"/>
<ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj"/>
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj"/>
</ItemGroup>
</Project>

View File

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

View File

@ -44,9 +44,9 @@ public abstract class AbstractDetourTest
protected static readonly float[][] endPoss =
{
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[] { 0.8635526f, 10.197294f, -10.31032f },
new[] { 0.8635526f, 10.197294f, -10.31032f },
new[] { 18.784092f, 10.197294f, 3.0543678f }
};

View File

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

View File

@ -1,26 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<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>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj" />
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<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.Detour\DotRecast.Detour.csproj"/>
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj"/>
</ItemGroup>
</Project>

View File

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

View File

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

View File

@ -20,64 +20,108 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class FindPolysAroundCircleTest : AbstractDetourTest
{
private static readonly long[][] REFS =
{
new[]
{
281474976710696L, 281474976710695L, 281474976710694L, 281474976710691L, 281474976710697L, 281474976710693L,
281474976710686L, 281474976710687L, 281474976710692L, 281474976710703L, 281474976710689L
},
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 = {
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, } };
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 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 } };
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]
public void testFindPolysAroundCircle() {
public void testFindPolysAroundCircle()
{
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
for (int i = 0; i < startRefs.Length; i++)
{
long startRef = startRefs[i];
float[] startPos = startPoss[i];
Result<FindPolysAroundResult> result = query.findPolysAroundCircle(startRef, startPos, 7.5f, filter);
Assert.That(result.succeeded(), Is.True);
FindPolysAroundResult polys = result.result;
Assert.That(polys.getRefs().Count, Is.EqualTo(REFS[i].Length));
for (int v = 0; v < REFS[i].Length; v++) {
for (int v = 0; v < REFS[i].Length; v++)
{
bool found = false;
for (int w = 0; w < REFS[i].Length; w++) {
if (REFS[i][v] == polys.getRefs()[w]) {
for (int w = 0; w < REFS[i].Length; w++)
{
if (REFS[i][v] == polys.getRefs()[w])
{
Assert.That(polys.getParentRefs()[w], Is.EqualTo(PARENT_REFS[i][v]));
Assert.That(polys.getCosts()[w], Is.EqualTo(COSTS[i][v]).Within(0.01f));
found = true;
}
}
Assert.That(found, Is.True, $"Ref not found {REFS[i][v]}");
}
}
}
}
}

View File

@ -20,91 +20,139 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class FindPolysAroundShapeTest : AbstractDetourTest
{
private static readonly long[][] REFS =
{
new[]
{
281474976710696L, 281474976710695L, 281474976710694L, 281474976710691L, 281474976710697L,
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 = {
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 } };
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 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 } };
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]
public void testFindPolysAroundShape() {
public void testFindPolysAroundShape()
{
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
for (int i = 0; i < startRefs.Length; i++)
{
long startRef = startRefs[i];
float[] startPos = startPoss[i];
Result<FindPolysAroundResult> polys = query.findPolysAroundShape(startRef,
getQueryPoly(startPos, endPoss[i]), filter);
getQueryPoly(startPos, endPoss[i]), filter);
Assert.That(polys.result.getRefs().Count, Is.EqualTo(REFS[i].Length));
for (int v = 0; v < REFS[i].Length; v++) {
for (int v = 0; v < REFS[i].Length; v++)
{
bool found = false;
for (int w = 0; w < REFS[i].Length; w++) {
if (REFS[i][v] == polys.result.getRefs()[w]) {
for (int w = 0; w < REFS[i].Length; w++)
{
if (REFS[i][v] == polys.result.getRefs()[w])
{
Assert.That(polys.result.getParentRefs()[w], Is.EqualTo(PARENT_REFS[i][v]));
Assert.That(polys.result.getCosts()[w], Is.EqualTo(COSTS[i][v]).Within(0.01f));
found = true;
}
}
Assert.That(found, Is.True);
}
}
}
private float[] getQueryPoly(float[] m_spos, float[] m_epos) {
private float[] getQueryPoly(float[] m_spos, float[] m_epos)
{
float nx = (m_epos[2] - m_spos[2]) * 0.25f;
float nz = -(m_epos[0] - m_spos[0]) * 0.25f;
float agentHeight = 2.0f;
@ -127,5 +175,4 @@ public class FindPolysAroundShapeTest : AbstractDetourTest {
m_queryPoly[11] = m_epos[2] + nz;
return m_queryPoly;
}
}
}

View File

@ -20,56 +20,80 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class GetPolyWallSegmentsTest : AbstractDetourTest
{
private static readonly float[][] VERTICES =
{
new[]
{
22.084785f, 10.197294f, -48.341274f, 22.684784f, 10.197294f, -44.141273f, 22.684784f, 10.197294f,
-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 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 } };
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 } };
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]
public void testFindDistanceToWall() {
public void testFindDistanceToWall()
{
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
for (int i = 0; i < startRefs.Length; i++)
{
Result<GetPolyWallSegmentsResult> result = query.getPolyWallSegments(startRefs[i], true, filter);
GetPolyWallSegmentsResult segments = result.result;
Assert.That(segments.getSegmentVerts().Count, Is.EqualTo(VERTICES[i].Length / 6));
Assert.That(segments.getSegmentRefs().Count, Is.EqualTo(REFS[i].Length));
for (int v = 0; v < VERTICES[i].Length / 6; v++) {
for (int n = 0; n < 6; n++) {
for (int v = 0; v < VERTICES[i].Length / 6; v++)
{
for (int n = 0; n < 6; n++)
{
Assert.That(segments.getSegmentVerts()[v][n], Is.EqualTo(VERTICES[i][v * 6 + n]).Within(0.001f));
}
}
for (int v = 0; v < REFS[i].Length; v++) {
for (int v = 0; v < REFS[i].Length; v++)
{
Assert.That(segments.getSegmentRefs()[v], Is.EqualTo(REFS[i][v]));
}
}
}
}
}

View File

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

View File

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

View File

@ -20,49 +20,70 @@ using NUnit.Framework;
namespace DotRecast.Detour.Test;
public class MoveAlongSurfaceTest : AbstractDetourTest {
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 = {
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 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 } };
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]
public void testMoveAlongSurface() {
public void testMoveAlongSurface()
{
QueryFilter filter = new DefaultQueryFilter();
for (int i = 0; i < startRefs.Length; i++) {
for (int i = 0; i < startRefs.Length; i++)
{
long startRef = startRefs[i];
float[] startPos = startPoss[i];
float[] endPos = endPoss[i];
Result<MoveAlongSurfaceResult> result = query.moveAlongSurface(startRef, startPos, endPos, filter);
Assert.That(result.succeeded(), Is.True);
MoveAlongSurfaceResult path = result.result;
for (int v = 0; v < 3; v++) {
for (int v = 0; v < 3; v++)
{
Assert.That(path.getResultPos()[v], Is.EqualTo(POSITION[i][v]).Within(0.01f));
}
Assert.That(path.getVisited().Count, Is.EqualTo(VISITED[i].Length));
for (int j = 0; j < POSITION[i].Length; j++) {
for (int j = 0; j < POSITION[i].Length; j++)
{
Assert.That(path.getVisited()[j], Is.EqualTo(VISITED[i][j]));
}
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -20,8 +20,8 @@ using DotRecast.Recast;
namespace DotRecast.Detour.Test;
public class SampleAreaModifications {
public class SampleAreaModifications
{
public const int SAMPLE_POLYAREA_TYPE_MASK = 0x07;
public const int SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
public const int SAMPLE_POLYAREA_TYPE_WATER = 0x2;
@ -31,17 +31,22 @@ public class SampleAreaModifications {
public const int SAMPLE_POLYAREA_TYPE_JUMP = 0x6;
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);
SAMPLE_POLYAREA_TYPE_MASK);
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,
SAMPLE_POLYAREA_TYPE_MASK);
SAMPLE_POLYAREA_TYPE_MASK);
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,
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_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_DISABLED = 0x10; // Disabled polygon
public const int SAMPLE_POLYFLAGS_ALL = 0xffff; // All abilities.
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,25 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<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>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<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.Detour.TileCache\DotRecast.Detour.TileCache.csproj" />
<ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj" />
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Detour.TileCache\DotRecast.Detour.TileCache.csproj"/>
<ProjectReference Include="..\..\src\DotRecast.Detour\DotRecast.Detour.csproj"/>
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj"/>
</ItemGroup>
</Project>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,25 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<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>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotRecast.Recast\DotRecast.Recast.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0"/>
<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>