#[cfg(feature = "physics")]
use crate::render::terrain_config::*;
#[cfg(feature = "physics")]
use nalgebra_glm::Vec3;
#[cfg(feature = "physics")]
pub fn terrain_collider_system(world: &mut crate::ecs::world::World, center: Vec3) {
use crate::ecs::physics::components::{ColliderComponent, ColliderShape, RigidBodyComponent};
use crate::ecs::physics::types::InteractionGroups;
use crate::ecs::world::commands::spawn_entities;
use crate::ecs::world::{COLLIDER, RIGID_BODY};
let settings_enabled = world.resources.terrain.enabled;
let settings_revision = world.resources.terrain.revision;
let collider_radius = world.resources.terrain.collider_radius;
if world.resources.terrain_render.collider_revision != settings_revision {
let entities: Vec<freecs::Entity> = world
.resources
.terrain_render
.collider_tiles
.values()
.map(|tile| crate::render_driver::scene_entity(tile.entity))
.collect();
for entity in entities {
world.despawn_entities(&[entity]);
}
world.resources.terrain_render.collider_tiles.clear();
world.resources.terrain_render.height_tiles.clear();
world.resources.terrain_render.collider_revision = settings_revision;
}
if !settings_enabled {
return;
}
let center_tile_x = (center.x / TERRAIN_COLLIDER_TILE_METERS).floor() as i32;
let center_tile_z = (center.z / TERRAIN_COLLIDER_TILE_METERS).floor() as i32;
let mut missing = Vec::new();
for offset_z in -collider_radius..=collider_radius {
for offset_x in -collider_radius..=collider_radius {
let coord = (center_tile_x + offset_x, center_tile_z + offset_z);
if !world
.resources
.terrain_render
.collider_tiles
.contains_key(&coord)
{
missing.push(coord);
}
}
}
let share = world.resources.terrain_render.share.clone();
let extracted: Vec<((i32, i32), Vec<f32>)> = share.with(|shared| {
let mut extracted = Vec::new();
let snapshot_done = matches!(
&shared.snapshot,
Some(snapshot)
if matches!(snapshot.state, TerrainSnapshotState::Mapping)
&& snapshot.map_done.load(std::sync::atomic::Ordering::Relaxed)
);
if snapshot_done {
let snapshot = shared.snapshot.take().expect("snapshot present");
if snapshot.revision == settings_revision {
{
let mapped = snapshot.buffer.slice(..).get_mapped_range();
let texels: &[f32] = bytemuck::cast_slice(&mapped);
for coord in &missing {
if let Some(heights) = extract_tile(texels, &snapshot, *coord) {
extracted.push((*coord, heights));
}
}
}
}
snapshot.buffer.unmap();
}
if !missing.is_empty()
&& shared.snapshot.is_none()
&& shared.snapshot_request.is_none()
&& extracted.len() < missing.len()
{
shared.snapshot_request = Some([center.x, center.z]);
}
extracted
});
for (coord, heights) in extracted {
let entity = spawn_entities(world, RIGID_BODY | COLLIDER, 1)[0];
let tile_center_x =
coord.0 as f32 * TERRAIN_COLLIDER_TILE_METERS + TERRAIN_COLLIDER_TILE_METERS * 0.5;
let tile_center_z =
coord.1 as f32 * TERRAIN_COLLIDER_TILE_METERS + TERRAIN_COLLIDER_TILE_METERS * 0.5;
if let Some(rigid_body) =
world.get_mut::<crate::ecs::physics::components::RigidBodyComponent>(entity)
{
*rigid_body = RigidBodyComponent::new_static().with_translation(
tile_center_x,
0.0,
tile_center_z,
);
}
if let Some(collider) =
world.get_mut::<crate::ecs::physics::components::ColliderComponent>(entity)
{
*collider = ColliderComponent {
handle: None,
shape: ColliderShape::HeightField {
nrows: TERRAIN_COLLIDER_SAMPLES,
ncols: TERRAIN_COLLIDER_SAMPLES,
heights: transpose_heights(&heights),
scale: [
TERRAIN_COLLIDER_TILE_METERS,
1.0,
TERRAIN_COLLIDER_TILE_METERS,
],
},
friction: 0.9,
restitution: 0.0,
density: 0.0,
is_sensor: false,
collision_groups: InteractionGroups::all(),
solver_groups: InteractionGroups::all(),
};
}
world.resources.terrain_render.collider_tiles.insert(
coord,
TerrainColliderTile {
entity: crate::render_driver::render_entity(entity),
},
);
world
.resources
.terrain_render
.height_tiles
.insert(coord, heights);
}
let stale: Vec<(i32, i32)> = world
.resources
.terrain_render
.collider_tiles
.keys()
.filter(|(tile_x, tile_z)| {
(tile_x - center_tile_x)
.abs()
.max((tile_z - center_tile_z).abs())
> collider_radius + 1
})
.copied()
.collect();
for coord in stale {
if let Some(tile) = world.resources.terrain_render.collider_tiles.remove(&coord) {
world.despawn_entities(&[crate::render_driver::scene_entity(tile.entity)]);
}
world.resources.terrain_render.height_tiles.remove(&coord);
}
}
#[cfg(feature = "physics")]
fn extract_tile(texels: &[f32], snapshot: &TerrainSnapshot, coord: (i32, i32)) -> Option<Vec<f32>> {
let cache_size = TERRAIN_CACHE_SIZE as i32;
let texels_per_meter = 1.0 / snapshot.texel_size;
let tile_origin_x =
(coord.0 as f32 * TERRAIN_COLLIDER_TILE_METERS * texels_per_meter).round() as i32;
let tile_origin_z =
(coord.1 as f32 * TERRAIN_COLLIDER_TILE_METERS * texels_per_meter).round() as i32;
let samples = TERRAIN_COLLIDER_SAMPLES as i32;
if tile_origin_x < snapshot.origin_texels[0]
|| tile_origin_z < snapshot.origin_texels[1]
|| tile_origin_x + samples > snapshot.origin_texels[0] + cache_size
|| tile_origin_z + samples > snapshot.origin_texels[1] + cache_size
{
return None;
}
let mut heights = Vec::with_capacity((samples * samples) as usize);
for row in 0..samples {
for column in 0..samples {
let texel_x = (tile_origin_x + column).rem_euclid(cache_size);
let texel_z = (tile_origin_z + row).rem_euclid(cache_size);
heights.push(texels[(texel_z * cache_size + texel_x) as usize]);
}
}
Some(heights)
}
#[cfg(feature = "physics")]
fn transpose_heights(heights: &[f32]) -> Vec<f32> {
let samples = TERRAIN_COLLIDER_SAMPLES;
let mut output = vec![0.0; heights.len()];
for row in 0..samples {
for column in 0..samples {
output[row + samples * column] = heights[row * samples + column];
}
}
output
}