|
1 | 1 | use bevy::mesh::VertexAttributeValues; |
2 | 2 | use bevy::prelude::*; |
3 | | -use std::sync::Arc; |
| 3 | +use std::collections::HashSet; |
| 4 | +use std::sync::{Arc, Mutex}; |
4 | 5 |
|
5 | | -use crate::chunk_map::ChunkMapUpdateBuffer; |
| 6 | +use crate::chunk_map::{ChunkMapInsertBuffer, ChunkMapUpdateBuffer}; |
6 | 7 | use crate::configuration::VoxelWorldConfig; |
7 | 8 | use crate::mesh_cache::MeshCacheInsertBuffer; |
8 | 9 | use crate::meshing::generate_chunk_mesh_for_shape; |
9 | 10 | use crate::prelude::*; |
10 | 11 | use crate::voxel_traversal::voxel_line_traversal; |
11 | 12 | use crate::{ |
12 | | - chunk::{ChunkData, ChunkTask, FillType, CHUNK_SIZE_F, PADDED_CHUNK_SIZE}, |
| 13 | + chunk::{ |
| 14 | + Chunk, ChunkData, ChunkTask, FillType, CHUNK_SIZE_F, CHUNK_SIZE_I, |
| 15 | + PADDED_CHUNK_SIZE, |
| 16 | + }, |
13 | 17 | prelude::VoxelWorldCamera, |
14 | 18 | voxel_world::*, |
15 | 19 | voxel_world_internal::ModifiedVoxels, |
@@ -208,6 +212,126 @@ fn chunk_will_update_event() { |
208 | 212 | app.update(); |
209 | 213 | } |
210 | 214 |
|
| 215 | +#[test] |
| 216 | +fn affected_chunk_positions_include_padding_neighbors() { |
| 217 | + let affected: HashSet<_> = get_affected_chunk_positions(IVec3::new(0, 0, 0)) |
| 218 | + .into_iter() |
| 219 | + .collect(); |
| 220 | + let expected = HashSet::from([ |
| 221 | + IVec3::new(0, 0, 0), |
| 222 | + IVec3::new(0, 0, -1), |
| 223 | + IVec3::new(0, -1, 0), |
| 224 | + IVec3::new(0, -1, -1), |
| 225 | + IVec3::new(-1, 0, 0), |
| 226 | + IVec3::new(-1, 0, -1), |
| 227 | + IVec3::new(-1, -1, 0), |
| 228 | + IVec3::new(-1, -1, -1), |
| 229 | + ]); |
| 230 | + |
| 231 | + assert_eq!(affected, expected); |
| 232 | + |
| 233 | + assert_eq!( |
| 234 | + get_affected_chunk_positions(IVec3::new(1, 1, 1)), |
| 235 | + vec![IVec3::new(0, 0, 0)] |
| 236 | + ); |
| 237 | + |
| 238 | + let affected: HashSet<_> = |
| 239 | + get_affected_chunk_positions(IVec3::new(CHUNK_SIZE_I - 1, 1, 1)) |
| 240 | + .into_iter() |
| 241 | + .collect(); |
| 242 | + let expected = HashSet::from([IVec3::new(0, 0, 0), IVec3::new(1, 0, 0)]); |
| 243 | + |
| 244 | + assert_eq!(affected, expected); |
| 245 | +} |
| 246 | + |
| 247 | +#[test] |
| 248 | +fn set_voxel_on_chunk_boundary_marks_padding_neighbors_for_remesh() { |
| 249 | + let expected = HashSet::from([ |
| 250 | + IVec3::new(0, 0, 0), |
| 251 | + IVec3::new(0, 0, -1), |
| 252 | + IVec3::new(0, -1, 0), |
| 253 | + IVec3::new(0, -1, -1), |
| 254 | + IVec3::new(-1, 0, 0), |
| 255 | + IVec3::new(-1, 0, -1), |
| 256 | + IVec3::new(-1, -1, 0), |
| 257 | + IVec3::new(-1, -1, -1), |
| 258 | + ]); |
| 259 | + |
| 260 | + let mut app = App::new(); |
| 261 | + app.add_plugins((MinimalPlugins, VoxelWorldPlugin::<DefaultWorld>::minimal())); |
| 262 | + let camera_transform = |
| 263 | + Transform::from_xyz(10.0, 10.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y); |
| 264 | + app.world_mut().spawn(( |
| 265 | + Camera::default(), |
| 266 | + Camera3d::default(), |
| 267 | + camera_transform, |
| 268 | + GlobalTransform::from(camera_transform), |
| 269 | + VoxelWorldCamera::<DefaultWorld>::default(), |
| 270 | + )); |
| 271 | + app.update(); |
| 272 | + |
| 273 | + type Mat = <DefaultWorld as VoxelWorldConfig>::MaterialIndex; |
| 274 | + let mut chunks = Vec::new(); |
| 275 | + for chunk_pos in expected.iter().copied() { |
| 276 | + let entity = app.world_mut().spawn_empty().id(); |
| 277 | + let shape = UVec3::splat(PADDED_CHUNK_SIZE); |
| 278 | + app.world_mut() |
| 279 | + .entity_mut(entity) |
| 280 | + .insert(Chunk::<DefaultWorld>::new( |
| 281 | + chunk_pos, 0, entity, shape, shape, |
| 282 | + )); |
| 283 | + chunks.push((chunk_pos, ChunkData::<Mat>::with_entity(entity))); |
| 284 | + } |
| 285 | + |
| 286 | + app.world_mut() |
| 287 | + .resource_mut::<ChunkMapInsertBuffer<DefaultWorld, Mat>>() |
| 288 | + .extend(chunks); |
| 289 | + app.update(); |
| 290 | + for _ in 0..100 { |
| 291 | + app.update(); |
| 292 | + } |
| 293 | + |
| 294 | + app.world_mut() |
| 295 | + .resource_mut::<Messages<ChunkWillRemesh<DefaultWorld>>>() |
| 296 | + .clear(); |
| 297 | + |
| 298 | + let remeshed_chunks = Arc::new(Mutex::new(Vec::new())); |
| 299 | + let remeshed_chunks_writer = remeshed_chunks.clone(); |
| 300 | + app.add_systems( |
| 301 | + Update, |
| 302 | + move |mut events: MessageReader<ChunkWillRemesh<DefaultWorld>>| { |
| 303 | + remeshed_chunks_writer |
| 304 | + .lock() |
| 305 | + .unwrap() |
| 306 | + .extend(events.read().map(|event| event.chunk_key)); |
| 307 | + }, |
| 308 | + ); |
| 309 | + |
| 310 | + app.add_systems( |
| 311 | + Update, |
| 312 | + |mut voxel_world: VoxelWorld<DefaultWorld>, mut did_set: Local<bool>| { |
| 313 | + if *did_set { |
| 314 | + return; |
| 315 | + } |
| 316 | + |
| 317 | + voxel_world.set_voxel(IVec3::new(0, 0, 0), WorldVoxel::Solid(1)); |
| 318 | + *did_set = true; |
| 319 | + }, |
| 320 | + ); |
| 321 | + |
| 322 | + app.update(); |
| 323 | + app.update(); |
| 324 | + app.update(); |
| 325 | + |
| 326 | + let remeshed_chunks: HashSet<_> = |
| 327 | + remeshed_chunks.lock().unwrap().iter().copied().collect(); |
| 328 | + |
| 329 | + assert!( |
| 330 | + expected.is_subset(&remeshed_chunks), |
| 331 | + "expected {expected:?} to be remeshed, got {remeshed_chunks:?}" |
| 332 | + ); |
| 333 | +} |
| 334 | + |
211 | 335 | #[test] |
212 | 336 | fn chunk_generate_reuses_previous_data_when_configured() { |
213 | 337 | type Mat = <DefaultWorld as VoxelWorldConfig>::MaterialIndex; |
|
0 commit comments