Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ so a test written against the reference reads the same here.
| `aephysics.manifold` | contact manifolds for sphere, capsule and hull in every pairing: the separating axis test with its cache, reference-face clipping, the feature pairs, reduction to four points | done, `test_manifold.ae` (43k checks, 7,000 pairs against a brute-force oracle); [same manifolds as the reference, warm cache at parity](bench/RESULTS.md#manifold) |
| `aephysics.triangle_manifold` | one mesh triangle against a sphere, capsule or hull: back-side cull with hysteresis, GJK shallow, the separating axis test deep with the triangle's edges as zero-area faces, the feature recorded for the mesh contact's ghost-collision reduction | done, `test_triangle_manifold.ae` (1.5k checks); [same manifolds as the reference, within 10% on hulls](bench/RESULTS.md#triangle_manifold) |
| `aephysics.mesh` | the triangle mesh: a BVH by binned SAH or median split with the triangles in depth-first order, vertex welding, edge flags, any scale including mirrored; overlap, ray cast, shape cast, the mover's planes, a box query | done, `test_mesh.ae` (1.6k checks); [same trees as the reference, traversals 1.7-2x](bench/RESULTS.md#mesh) |
| `aephysics.collision` | height field, shapes with mass properties, ray and shape casts | next |
| `aephysics.height_field` | the height field: quantised heights on a fixed diagonal, materials and holes per cell, edge flags per triangle, either winding; overlap, ray and shape casts by a walk along the grid, the mover's planes, a box query | done, `test_height_field.ae` (113 checks, casts against a brute force over a wave); [same results as the reference, query at parity, casts 1.4-2x](bench/RESULTS.md#height_field) |
| `aephysics.shape` | shapes with mass properties, ray and shape casts per shape, compounds | next |
| `aephysics.dynamics` | bodies, contacts, the constraint graph, islands, the Soft Step solver, joints (spherical, revolute, prismatic, distance, motor, weld, wheel), sensors, the character mover, the world | |
| `aephysics` | the public API | |

Expand Down
807 changes: 807 additions & 0 deletions aephysics/height_field/module.ae

Large diffs are not rendered by default.

635 changes: 635 additions & 0 deletions aephysics/test_height_field.ae

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions bench/RESULTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,32 @@ positives the query permits). The build is 1.2-1.5x, with the welding
map and the edge map through core's LongMap; the traversals are 1.7-2x,
the reference's SIMD box tests against scalar ones on 48-byte double
boxes, the same gap the dynamic tree's ray cast showed.

## height_field

`bench/height_field.ae` and `bench/height_field_box3d.c`: a 512 x 512
wave field of 522,242 triangles with a hole every sixteenth cell, built
ten times; 100,000 rays cast down onto it at slight angles; 100,000 box
queries over it; 10,000 sphere shape casts onto it; 10,000 sphere
overlaps at its surface.

| phase | aephysics | Box3D |
|---|---|---|
| 10 builds (522,242 triangles) | 200 ms | **131** |
| 100,000 ray casts | 16.0 | **11.3** |
| 100,000 box queries | **8.3** | 9.0 |
| 10,000 shape casts | 49.5 | **23.5** |
| 10,000 overlaps | 3.7 | **2.0** |

The same fields come out: every ray hits on both (94,058, the sum of hit
heights within 0.1%, the quantised heights held as doubles here and
floats there), every shape cast hits on both with equal fraction sums,
the overlaps agree (3,160), the box query reports 2,171,942 triangles
here against 2,171,914 there (0.001% more, boundary cases of the cell
bounds test). The build is 1.5x, the quantisation and the edge flags of
half a million triangles; the ray walk 1.4x; the box query at parity
(no SIMD in the reference's); the shape cast 2.1x, the GJK cast per
straddled cell on top of distance's 1.3-1.5x; the overlap 1.85x. The
field is 4.2 MB here against 1.3 MB there: the reference packs 16-bit
heights and 8-bit materials and flags, which Aether cannot yet address,
so they are ints.
104 changes: 104 additions & 0 deletions bench/height_field.ae
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// The height field on the same scenes as bench/height_field_box3d.c: a
// 512 x 512 wave field (522,242 triangles, a hole every sixteenth cell)
// built ten times; 100,000 ray casts down onto it; 100,000 box queries
// over it; 10,000 sphere shape casts onto it; 10,000 sphere overlaps at
// its surface. Single thread, wall time per phase, with the hit counts
// and sums as the checksum.
import std.string
import std.os
import aephysics.math
import aephysics.distance
import aephysics.height_field

extern calloc(count: int, size: int) -> ptr
extern free(p: ptr)
extern sin(x: float) -> float
extern cos(x: float) -> float

clock() -> long { return os.now_monotonic_ns() }
ms(ns: long) -> float { return (ns as float) / 1000000.0 }

const RAYS = 100000
const QUERIES = 100000
const CASTS = 10000
const OVERLAPS = 10000

var query_hits = 0

count_triangle(a: Vec3, b: Vec3, c: Vec3, triangle_index: int, context: ptr) -> bool {
query_hits = query_hits + 1
return true
}

main() {
scale = math.vec3(0.5, 2.0, 0.5)
t0 = clock()
data = height_field.create_wave(512, 512, scale, 0.02, 0.031, true)
i = 1
while i < 10 {
height_field.destroy_height_field(data)
data = height_field.create_wave(512, 512, scale, 0.02, 0.031, true)
i = i + 1
}
t1 = clock()

ray_hits = 0
ray_sum = 0.0
i = 0
while i < RAYS {
t = (i as float) / (RAYS as float)
origin = math.vec3(1.0 + 254.0 * t, 5.0, 128.0 + 120.0 * sin(40.0 * t))
translation = math.vec3(3.0 * cos(7.0 * t), 0.0 - 10.0, 3.0 * sin(9.0 * t))
out = height_field.ray_cast_height_field(data, origin, translation, 1.0)
if out.hit {
ray_hits = ray_hits + 1
ray_sum = ray_sum + out.point.y
}
i = i + 1
}
t2 = clock()

query_hits = 0
i = 0
while i < QUERIES {
t = (i as float) / (QUERIES as float)
c = math.vec3(2.0 + 252.0 * t, 0.3 * sin(3.0 * t), 128.0 + 120.0 * cos(30.0 * t))
h = math.vec3(0.6, 2.5, 0.6)
bounds = AABB { lower: math.sub(c, h), upper: math.add(c, h) }
height_field.query_height_field(data, bounds, count_triangle, null)
i = i + 1
}
t3 = clock()

cast_hits = 0
cast_sum = 0.0
start_block = calloc(1, sizeof(Vec3))
start = start_block as Vec3[]
i = 0
while i < CASTS {
t = (i as float) / (CASTS as float)
start[0] = math.vec3(2.0 + 252.0 * t, 5.0, 128.0 + 120.0 * sin(50.0 * t))
translation = math.vec3(4.0 * cos(11.0 * t), 0.0 - 10.0, 4.0 * sin(13.0 * t))
out = height_field.shape_cast_height_field(data, distance.shape_proxy(start_block, 1, 0.3), translation, 1.0, false)
if out.hit {
cast_hits = cast_hits + 1
cast_sum = cast_sum + out.fraction
}
i = i + 1
}
t4 = clock()

overlap_hits = 0
i = 0
while i < OVERLAPS {
t = (i as float) / (OVERLAPS as float)
start[0] = math.vec3(2.0 + 252.0 * t, 1.0 * sin(17.0 * t), 128.0 + 120.0 * cos(23.0 * t))
if height_field.overlap_height_field(data, math.transform_identity(), distance.shape_proxy(start_block, 1, 0.5)) { overlap_hits = overlap_hits + 1 }
i = i + 1
}
t5 = clock()

println("aephysics height_field: 10 builds ${ms(t1 - t0)} ms (${height_field.height_field_triangle_count(data)} triangles, ${data.byte_count} bytes), ${RAYS} rays ${ms(t2 - t1)} ms (${ray_hits} hits, sum ${ray_sum}), ${QUERIES} queries ${ms(t3 - t2)} ms (${query_hits} hits), ${CASTS} casts ${ms(t4 - t3)} ms (${cast_hits} hits, sum ${cast_sum}), ${OVERLAPS} overlaps ${ms(t5 - t4)} ms (${overlap_hits} hits)")
free(start_block)
height_field.destroy_height_field(data)
}
104 changes: 104 additions & 0 deletions bench/height_field_box3d.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// The height field of the reference on the same scenes as
// bench/height_field.ae: a 512 x 512 wave field (522,242 triangles, a
// hole every sixteenth cell) built ten times; 100,000 ray casts down onto
// it; 100,000 box queries over it; 10,000 sphere shape casts onto it;
// 10,000 sphere overlaps at its surface. Single thread, wall time per
// phase, with the hit counts and sums as the checksum.
#include "box3d/collision.h"
#include "box3d/math_functions.h"

#include <math.h>
#include <stdio.h>
#include <time.h>

static double now_ms( void )
{
struct timespec ts;
timespec_get( &ts, TIME_UTC );
return ts.tv_sec * 1000.0 + ts.tv_nsec / 1.0e6;
}

static int g_queryHits;
static bool count_triangle( b3Vec3 a, b3Vec3 b, b3Vec3 c, int triangleIndex, void* context )
{
(void)a; (void)b; (void)c; (void)triangleIndex; (void)context;
g_queryHits += 1;
return true;
}

#define RAYS 100000
#define QUERIES 100000
#define CASTS 10000
#define OVERLAPS 10000

int main( void )
{
b3Vec3 scale = { 0.5f, 2.0f, 0.5f };
double t0 = now_ms();
b3HeightFieldData* data = NULL;
for ( int i = 0; i < 10; ++i )
{
if ( data ) b3DestroyHeightField( data );
data = b3CreateWave( 512, 512, scale, 0.02f, 0.031f, true );
}
double t1 = now_ms();

int rayHits = 0;
double raySum = 0.0;
for ( int i = 0; i < RAYS; ++i )
{
float t = (float)i / (float)RAYS;
b3RayCastInput input = { { 1.0f + 254.0f * t, 5.0f, 128.0f + 120.0f * sinf( 40.0f * t ) }, { 3.0f * cosf( 7.0f * t ), -10.0f, 3.0f * sinf( 9.0f * t ) }, 1.0f };
b3CastOutput out = b3RayCastHeightField( data, &input );
if ( out.hit )
{
rayHits += 1;
raySum += out.point.y;
}
}
double t2 = now_ms();

g_queryHits = 0;
for ( int i = 0; i < QUERIES; ++i )
{
float t = (float)i / (float)QUERIES;
b3Vec3 c = { 2.0f + 252.0f * t, 0.3f * sinf( 3.0f * t ), 128.0f + 120.0f * cosf( 30.0f * t ) };
b3Vec3 h = { 0.6f, 2.5f, 0.6f };
b3AABB bounds = { b3Sub( c, h ), b3Add( c, h ) };
b3QueryHeightField( data, bounds, count_triangle, NULL );
}
double t3 = now_ms();

int castHits = 0;
double castSum = 0.0;
for ( int i = 0; i < CASTS; ++i )
{
float t = (float)i / (float)CASTS;
b3Vec3 start = { 2.0f + 252.0f * t, 5.0f, 128.0f + 120.0f * sinf( 50.0f * t ) };
b3ShapeCastInput input = { { &start, 1, 0.3f }, { 4.0f * cosf( 11.0f * t ), -10.0f, 4.0f * sinf( 13.0f * t ) }, 1.0f, false };
b3CastOutput out = b3ShapeCastHeightField( data, &input );
if ( out.hit )
{
castHits += 1;
castSum += out.fraction;
}
}
double t4 = now_ms();

int overlapHits = 0;
for ( int i = 0; i < OVERLAPS; ++i )
{
float t = (float)i / (float)OVERLAPS;
b3Vec3 center = { 2.0f + 252.0f * t, 1.0f * sinf( 17.0f * t ), 128.0f + 120.0f * cosf( 23.0f * t ) };
b3ShapeProxy proxy = { &center, 1, 0.5f };
if ( b3OverlapHeightField( data, b3Transform_identity, &proxy ) ) overlapHits += 1;
}
double t5 = now_ms();

printf( "box3d height_field: 10 builds %.1f ms (%d triangles, %d bytes), %d rays %.2f ms (%d hits, sum %.3f), %d queries %.2f ms (%d hits), "
"%d casts %.2f ms (%d hits, sum %.3f), %d overlaps %.2f ms (%d hits)\n",
t1 - t0, 2 * ( data->rowCount - 1 ) * ( data->columnCount - 1 ), data->byteCount, RAYS, t2 - t1, rayHits, raySum, QUERIES, t3 - t2, g_queryHits,
CASTS, t4 - t3, castHits, castSum, OVERLAPS, t5 - t4, overlapHits );
b3DestroyHeightField( data );
return 0;
}
25 changes: 18 additions & 7 deletions design.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,32 @@ started until its tests pass.
mirrored scale, the mover. The same trees as the reference; traversals
1.7-2x (its SIMD box tests). The mesh contact's cluster reduction
(mesh_contact.c) is dynamics-side and comes with the contacts.
9. **collision, static**: `height_field`, `shape` (mass properties, ray
and shape casts per shape). Tests: `test_collision`, `test_shape`,
`test_height_field`.
10. **dynamics**: `body`, `contact`, `constraint_graph` (graph colouring),
9. **height_field** (done): height_field.c as `aephysics.height_field`:
the heights quantised to a global range, materials and holes per
cell, the edge flags against the four neighbours, either winding;
overlap, the ray and shape casts by a DDA walk of the swept box's
leading corner through the cells, the mover's planes, the box query.
113 checks: test_height_field.c's create, index mapping, winding,
flat ray, overlap, straddle, brute-force shape and ray casts over a
wave, back-side and clockwise culling (the file roundtrip is not
ported), plus the flags of a ridge and of holes, a scaled field, the
query and the mover. The same results as the reference; the query at
parity, the casts 1.4-2x. The heights, materials and flags are ints
for want of 16- and 8-bit arrays, 3x the reference's bytes.
10. **shape**: shape.c (mass properties, ray and shape casts per shape,
compounds). Test: `test_shape`.
11. **dynamics**: `body`, `contact`, `constraint_graph` (graph colouring),
`solver_set`, `island`, `solver` (the Soft Step: sub-stepping, relax
iterations, restitution), `contact_solver` (scalar first; the wide SIMD
path second, measured), the joints (revolute, prismatic, distance,
motor, weld, wheel, spherical), `sensor`, `mover` (the character mover),
`physics_world`. Tests: `test_body`, `test_joint`, `test_world`,
`test_mover`, `test_determinism`, `test_large_world`.
11. **parallel**: `parallel_for` and the scheduler over Aether's actors;
12. **parallel**: `parallel_for` and the scheduler over Aether's actors;
the benchmarks by thread count as the original records them.
12. **recording and replay**, `world_snapshot`: last, since they are the
13. **recording and replay**, `world_snapshot`: last, since they are the
tooling and not the engine.
13. **benchmarks**: `reference/benchmark/main.c`'s nine scenes ported, run
14. **benchmarks**: `reference/benchmark/main.c`'s nine scenes ported, run
against the C build on the same machine, recorded under `benchmark/`.

## Measures
Expand Down
Loading