mirror of
https://github.com/SK8-ENGINE/skate-3-rust-engine.git
synced 2026-10-26 19:47:44 +00:00
Accelerate custom map collision with bounded ordered query clusters
This commit is contained in:
1 parent
52295937df
commit
8d35f5267b
2 files changed
+90
-7
No files matched your search
@@ -322,19 +322,28 @@ pub(crate) fn collision_world(
|
||||
.ok_or("Invalid SKATE collision volume")?,
|
||||
);
|
||||
}
|
||||
let bounds = Bounds::from_points(triangles.iter().flat_map(|t| t.triangle.vertices))
|
||||
.ok_or("SKATE collision bounds empty")?;
|
||||
let metadata = QueryMetadata {
|
||||
packed_surfaces,
|
||||
meshes: vec![QueryMesh {
|
||||
// Portable maps have no native cluster hierarchy. Bound contiguous ranges
|
||||
// once at load time so the existing BVH can reject distant geometry. Keep
|
||||
// triangle order and mesh identity/filter values: contact tie-breaking,
|
||||
// packed surfaces and adjacency must not change with this acceleration.
|
||||
let mut meshes = Vec::new();
|
||||
for start in (0..triangles.len()).step_by(64) {
|
||||
let end = (start + 64).min(triangles.len());
|
||||
let bounds = Bounds::from_points(triangles[start..end].iter().flat_map(|t| t.triangle.vertices))
|
||||
.ok_or("SKATE collision bounds empty")?;
|
||||
meshes.push(QueryMesh {
|
||||
geometry: 0, rejection_flags: 0,
|
||||
triangle_range: 0..triangles.len(),
|
||||
triangle_range: start..end,
|
||||
local_to_world: RetailAffineTransform::IDENTITY,
|
||||
world_to_local: RetailAffineTransform::IDENTITY,
|
||||
local_bounds: bounds,
|
||||
matching_group: -1,
|
||||
pool: QueryPool::Ground,
|
||||
}],
|
||||
});
|
||||
}
|
||||
let metadata = QueryMetadata {
|
||||
packed_surfaces,
|
||||
meshes,
|
||||
static_edges: vec![],
|
||||
island_flags: 0,
|
||||
};
|
||||
@@ -676,6 +685,61 @@ pub(crate) fn spawn(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
fn compare_cluster_queries(world: &BoardWorld) -> (usize, usize) {
|
||||
let mut metadata = world.query_metadata().unwrap().clone();
|
||||
let mut single = metadata.meshes[0].clone();
|
||||
single.triangle_range = 0..world.triangles().len();
|
||||
single.local_bounds = Bounds::from_points(world.triangles().iter().flat_map(|t| t.triangle.vertices)).unwrap();
|
||||
metadata.meshes = vec![single];
|
||||
let baseline = BoardWorld::with_query_metadata(world.triangles().to_vec(), metadata).unwrap();
|
||||
let mut visited = [0usize; 2];
|
||||
let mut times = [std::time::Duration::ZERO; 2];
|
||||
for triangle in world.triangles().iter().step_by((world.triangles().len() / 256).max(1)) {
|
||||
let p = triangle.triangle.vertices[0];
|
||||
let start = Vector3::new(p.x, p.y + 1., p.z);
|
||||
let end = Vector3::new(p.x, p.y - 1., p.z);
|
||||
let mut candidates = Vec::new();
|
||||
for (i, scene) in [&baseline, world].into_iter().enumerate() {
|
||||
let bounds = scene.line_candidate_bounds(start, end, 0.1);
|
||||
visited[i] += scene.candidate_ranges(bounds).iter().map(|r| r.len()).sum::<usize>();
|
||||
let now = std::time::Instant::now();
|
||||
candidates.push(scene.line_candidates(start, end, 0.1).map(|(id, _)| id).collect::<Vec<_>>());
|
||||
times[i] += now.elapsed();
|
||||
}
|
||||
assert_eq!(candidates[0], candidates[1], "candidate identity/order changed");
|
||||
}
|
||||
eprintln!("CUSTOM_QUERY_BENCH baseline_triangles={} clustered_triangles={} baseline_ms={} clustered_ms={} clusters={}",
|
||||
visited[0], visited[1], times[0].as_secs_f64()*1000., times[1].as_secs_f64()*1000.,
|
||||
world.query_metadata().unwrap().meshes.len());
|
||||
(visited[0], visited[1])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn portable_collision_clusters_preserve_queries_and_cull_distant_geometry() {
|
||||
let mut map = demo();
|
||||
let source = map.geometry.collision.remove(0);
|
||||
map.geometry.collision.clear();
|
||||
for i in 0..256 {
|
||||
map.geometry.collision.push(skate_data::skate_map::Collision {
|
||||
points: source.points.map(|p| [p[0] + i as f32 * 100., p[1], p[2]]),
|
||||
surface: source.surface, material: source.material, native_edges: source.native_edges,
|
||||
});
|
||||
}
|
||||
let world = collision_world(&map, material()).unwrap();
|
||||
assert_eq!(world.query_metadata().unwrap().meshes.len(), 4);
|
||||
let (before, after) = compare_cluster_queries(&world);
|
||||
assert!(after < before / 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires a supplied map; static collision-query benchmark only"]
|
||||
fn supplied_custom_map_collision_query_benchmark() {
|
||||
let path = std::env::var("SKATE_TEST_CUSTOM_MAP").unwrap();
|
||||
let map = SkateMap::load(std::path::Path::new(&path)).unwrap();
|
||||
let world = collision_world(&map, material()).unwrap();
|
||||
let (before, after) = compare_cluster_queries(&world);
|
||||
assert!(after < before);
|
||||
}
|
||||
fn demo() -> SkateMap {
|
||||
SkateMap::parse(include_bytes!("../../../maps/format-demo.skate")).unwrap()
|
||||
}
|
||||
|
||||
@@ -90,3 +90,22 @@ XYZ/radius. Six contact-toolkit tests pass. The existing
|
||||
`embedded_static_rwcm_hits_distinct_actor_query_ids` test fails its metadata
|
||||
expectation (`matching_group == -1 && geometry == 0`) before executing the
|
||||
changed query path; that unrelated fixture expectation remains unchanged.
|
||||
|
||||
## Custom collision performance
|
||||
|
||||
Portable map collision previously occupied one query mesh, so nearby queries
|
||||
scanned the entire map. It now uses contiguous 64-triangle bounds in the
|
||||
existing static hierarchy, built once during loading. Triangle order, geometry
|
||||
IDs, filters, packed surfaces and adjacency remain unchanged. Native RWCM
|
||||
clusters retain their existing path.
|
||||
|
||||
A static query benchmark on the supplied custom map (89,613 triangles) sampled
|
||||
257 local segments with identical candidate IDs/order before and after.
|
||||
Triangle checks fell from 23,030,541 to 4,198,157; measured candidate-query time
|
||||
fell from 16.51 ms to 3.82 ms total (about 4.3x). A 16-triangle partition was
|
||||
also tried, but had higher query time (4.69 ms) despite fewer triangle checks.
|
||||
These are CPU microbenchmark results, not gameplay FPS or GPU measurements.
|
||||
Ten map tests passed. The supplied-map benchmark is opt-in via
|
||||
`SKATE_TEST_CUSTOM_MAP`. Frame timing can be captured with the existing
|
||||
`SKATE_PERF_REPORT` environment setting (10-second warmup, report/exit after
|
||||
25 seconds); no automated gameplay was run.
|
||||
Reference in new issue
Block a user