diff --git a/Cargo.toml b/Cargo.toml index 4f397a5..e4ddcf4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,25 @@ [workspace] -members = [ "crates/sparkl3d-core", "crates/sparkl3d-kernels", "crates/sparkl2d", "crates/sparkl2d-core", "crates/sparkl2d-kernels", "examples2d", - "crates/sparkl3d", "examples3d" ] +members = [ + "crates/sparkl3d-core", + "crates/sparkl3d-kernels", + "crates/sparkl2d", + "crates/sparkl2d-core", + "crates/sparkl2d-kernels", + "examples2d", + "crates/sparkl3d", + "examples3d", +] resolver = "2" [patch.crates-io] #rapier2d = { git = "https://github.com/dimforge/rapier", branch = "testbed-init-app" } #rapier3d = { git = "https://github.com/dimforge/rapier", branch = "testbed-init-app" } +#rapier2d = { path = "../rapier/crates/rapier2d" } +#rapier3d = { path = "../rapier/crates/rapier3d" } #rapier_testbed2d = { git = "https://github.com/dimforge/rapier", branch = "testbed-init-app" } #rapier_testbed3d = { git = "https://github.com/dimforge/rapier", branch = "testbed-init-app" } +#rapier_testbed2d = { path = "../rapier/crates/rapier_testbed2d" } +#rapier_testbed3d = { path = "../rapier/crates/rapier_testbed3d" } #nalgebra = { git = "https://github.com/dimforge/nalgebra" } #simba = { git = "https://github.com/dimforge/simba" } diff --git a/crates/sparkl2d/Cargo.toml b/crates/sparkl2d/Cargo.toml index 8926722..985de9f 100644 --- a/crates/sparkl2d/Cargo.toml +++ b/crates/sparkl2d/Cargo.toml @@ -1,14 +1,20 @@ [package] -name = "sparkl2d" +name = "sparkl2d" version = "0.4.0" -authors = [ "Sébastien Crozet " ] +authors = ["Sébastien Crozet "] description = "3-dimensional physics engine in Rust." documentation = "http://docs.rs/sparkl2d" homepage = "http://sparkl.rs" repository = "https://github.com/dimforge/sparkl" readme = "README.md" -categories = [ "science", "game-development", "mathematics", "simulation", "wasm"] -keywords = [ "physics", "dynamics", "rigid", "real-time", "joints" ] +categories = [ + "science", + "game-development", + "mathematics", + "simulation", + "wasm", +] +keywords = ["physics", "dynamics", "rigid", "real-time", "joints"] license = "Apache-2.0" edition = "2018" @@ -16,31 +22,35 @@ edition = "2018" maintenance = { status = "actively-developed" } [features] -default = [ "dim2", "f32" ] +default = ["dim2", "f32"] dim2 = [] f32 = [] -cuda = [ "cust", "sparkl2d-kernels" ] +cuda = ["cust", "sparkl2d-kernels"] -serde-serialize = [ "serde", "nalgebra/serde-serialize", "parry2d/serde-serialize", "rapier2d/serde-serialize", - "sparkl2d-core/serde-serialize"] +serde-serialize = [ + "serde", + "nalgebra/serde-serialize", + "parry2d/serde-serialize", + "rapier2d/serde-serialize", + "sparkl2d-core/serde-serialize", +] # Third-party integration. -rapier-testbed = [ "rapier_testbed2d", "graphics", "itertools" ] -rapier-harness = [ "rapier-testbed" ] -graphics = [ "bevy", "bevy_egui", "bevy_ecs" ] +rapier-testbed = ["rapier_testbed2d", "graphics", "itertools"] +rapier-harness = ["rapier-testbed"] +graphics = ["bevy", "bevy_egui", "bevy_ecs"] [lib] path = "../../src/lib.rs" -required-features = [ "dim2", "f32" ] - +required-features = ["dim2", "f32"] [dependencies] sparkl2d-core = { version = "0.4", path = "../sparkl2d-core" } nalgebra = "0.32" parry2d = "0.13" -rapier2d = { version = "0.17.2", git = "https://github.com/dimforge/rapier.git", rev = "e9ea2ca10b3058a6ac2d7f4b79d351ef18ad3c06" } -instant = { version = "0.1", features = [ "now" ] } +rapier2d = { version = "0.18" } +instant = { version = "0.1", features = ["now"] } bitflags = "1" anyhow = "1" log = "0.4" @@ -48,7 +58,7 @@ log = "0.4" oorandom = "11" # TODO: remove this, only for testing. # Serialization -serde = { version = "1.0", optional = true, features = [ "derive" ]} +serde = { version = "1.0", optional = true, features = ["derive"] } # Parallelism rayon = "1" @@ -59,9 +69,9 @@ memmap2 = "0.5" bytemuck = "1" # Third-party integration -rapier_testbed2d = { version = "0.17.0", optional = true , git = "https://github.com/dimforge/rapier.git", rev = "e9ea2ca10b3058a6ac2d7f4b79d351ef18ad3c06" } -bevy_egui = { version = "0.22", optional = true, features = [ "immutable_ctx" ] } -bevy_ecs = { version = "0.11", optional = true } +rapier_testbed2d = { version = "0.18.0", optional = true } +bevy_egui = { version = "0.23", optional = true, features = ["immutable_ctx"] } +bevy_ecs = { version = "0.12", optional = true } itertools = { version = "0.10", optional = true } # CUDA @@ -69,7 +79,14 @@ cust = { version = "0.3", optional = true } sparkl2d-kernels = { version = "0.4", optional = true, path = "../sparkl2d-kernels" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -bevy = { version = "0.11", default-features = false, features = ["bevy_winit", "bevy_pbr", "bevy_sprite", "bevy_core_pipeline", "bevy_render", "x11"], optional = true } +bevy = { version = "0.12", default-features = false, features = [ + "bevy_winit", + "bevy_pbr", + "bevy_sprite", + "bevy_core_pipeline", + "bevy_render", + "x11", +], optional = true } # Dependencies for WASM only. #[target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/crates/sparkl3d/Cargo.toml b/crates/sparkl3d/Cargo.toml index e441a00..2b80600 100644 --- a/crates/sparkl3d/Cargo.toml +++ b/crates/sparkl3d/Cargo.toml @@ -1,14 +1,20 @@ [package] -name = "sparkl3d" +name = "sparkl3d" version = "0.4.0" -authors = [ "Sébastien Crozet " ] +authors = ["Sébastien Crozet "] description = "3-dimensional physics engine in Rust." documentation = "http://docs.rs/sparkl3d" homepage = "http://sparkl.rs" repository = "https://github.com/dimforge/sparkl" readme = "README.md" -categories = [ "science", "game-development", "mathematics", "simulation", "wasm"] -keywords = [ "physics", "dynamics", "rigid", "real-time", "joints" ] +categories = [ + "science", + "game-development", + "mathematics", + "simulation", + "wasm", +] +keywords = ["physics", "dynamics", "rigid", "real-time", "joints"] license = "Apache-2.0" edition = "2018" @@ -16,31 +22,35 @@ edition = "2018" maintenance = { status = "actively-developed" } [features] -default = [ "dim3", "f32" ] +default = ["dim3", "f32"] dim3 = [] f32 = [] -cuda = [ "cust", "sparkl3d-kernels" ] +cuda = ["cust", "sparkl3d-kernels"] -serde-serialize = [ "serde", "nalgebra/serde-serialize", "parry3d/serde-serialize", "rapier3d/serde-serialize", - "sparkl3d-core/serde-serialize" ] +serde-serialize = [ + "serde", + "nalgebra/serde-serialize", + "parry3d/serde-serialize", + "rapier3d/serde-serialize", + "sparkl3d-core/serde-serialize", +] # Third-party integration. -rapier-testbed = [ "rapier_testbed3d", "graphics", "itertools" ] -rapier-harness = [ "rapier-testbed" ] -graphics = [ "bevy", "bevy_egui", "bevy_ecs" ] +rapier-testbed = ["rapier_testbed3d", "graphics", "itertools"] +rapier-harness = ["rapier-testbed"] +graphics = ["bevy", "bevy_egui", "bevy_ecs"] [lib] path = "../../src/lib.rs" -required-features = [ "dim3", "f32" ] - +required-features = ["dim3", "f32"] [dependencies] sparkl3d-core = { version = "0.4", path = "../sparkl3d-core" } nalgebra = "0.32" parry3d = "0.13" -rapier3d = { version = "0.17.2", git = "https://github.com/dimforge/rapier.git", rev = "e9ea2ca10b3058a6ac2d7f4b79d351ef18ad3c06" } -instant = { version = "0.1", features = [ "now" ] } +rapier3d = { version = "0.18" } +instant = { version = "0.1", features = ["now"] } bitflags = "1" anyhow = "1" log = "0.4" @@ -48,7 +58,7 @@ log = "0.4" oorandom = "11" # TODO: remove this, only for testing. # Serialization -serde = { version = "1.0", optional = true, features = [ "derive" ]} +serde = { version = "1.0", optional = true, features = ["derive"] } # Parallelism rayon = "1" @@ -59,10 +69,10 @@ memmap2 = "0.5" bytemuck = "1" # Third-party integration -rapier_testbed3d = { version = "0.17.0", optional = true, git = "https://github.com/dimforge/rapier.git", rev = "e9ea2ca10b3058a6ac2d7f4b79d351ef18ad3c06" } +rapier_testbed3d = { version = "0.18", optional = true } -bevy_egui = { version = "0.22", optional = true, features = [ "immutable_ctx" ] } -bevy_ecs = { version = "0.11", optional = true } +bevy_egui = { version = "0.23", optional = true, features = ["immutable_ctx"] } +bevy_ecs = { version = "0.12", optional = true } itertools = { version = "0.10", optional = true } # CUDA @@ -70,7 +80,11 @@ cust = { version = "0.3", optional = true } sparkl3d-kernels = { version = "0.4", optional = true, path = "../sparkl3d-kernels" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -bevy = { version = "0.11", default-features = false, features = ["bevy_winit", "bevy_render", "x11"], optional = true } +bevy = { version = "0.12", default-features = false, features = [ + "bevy_winit", + "bevy_render", + "x11", +], optional = true } # Dependencies for WASM only. #[target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/examples2d/Cargo.toml b/examples2d/Cargo.toml index 3615198..ee947b8 100644 --- a/examples2d/Cargo.toml +++ b/examples2d/Cargo.toml @@ -1,30 +1,30 @@ [package] -name = "sparkl-examples-2d" +name = "sparkl-examples-2d" version = "0.2.0" -authors = [ "Sébastien Crozet " ] +authors = ["Sébastien Crozet "] edition = "2018" default-run = "examples2d" [features] -cuda = [ "sparkl2d/cuda" ] +cuda = ["sparkl2d/cuda"] [dependencies] -Inflector = "0.11" +Inflector = "0.11" nalgebra = "0.32" -rapier_testbed2d = { version = "0.17.0", git = "https://github.com/dimforge/rapier.git", rev = "e9ea2ca10b3058a6ac2d7f4b79d351ef18ad3c06" } -rapier2d = { version = "0.17.2", git = "https://github.com/dimforge/rapier.git", rev = "e9ea2ca10b3058a6ac2d7f4b79d351ef18ad3c06" } +rapier_testbed2d = { version = "0.18.0" } +rapier2d = { version = "0.18" } oorandom = "11" -serde = { version = "1", features = [ "derive" ]} -serde_json = { version = "1", features = [ "preserve_order" ] } +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1", features = ["preserve_order"] } bincode = "1" -indexmap = { version = "1", features = [ "serde" ] } +indexmap = { version = "1", features = ["serde"] } uuid = "1" env_logger = "0.10" log = "0.4" [dependencies.sparkl2d] path = "../crates/sparkl2d" -features = [ "rapier-testbed", "serde-serialize" ] +features = ["rapier-testbed", "serde-serialize"] [[bin]] name = "examples2d" diff --git a/examples3d/Cargo.toml b/examples3d/Cargo.toml index bfeb874..c6a0807 100644 --- a/examples3d/Cargo.toml +++ b/examples3d/Cargo.toml @@ -1,30 +1,30 @@ [package] -name = "sparkl-examples-3d" +name = "sparkl-examples-3d" version = "0.2.0" -authors = [ "Sébastien Crozet " ] +authors = ["Sébastien Crozet "] edition = "2018" default-run = "examples3d" [features] -cuda = [ "sparkl3d/cuda" ] +cuda = ["sparkl3d/cuda"] [dependencies] -Inflector = "0.11" +Inflector = "0.11" nalgebra = "0.32" -rapier_testbed3d = { version = "0.17.0", git = "https://github.com/dimforge/rapier.git", rev = "e9ea2ca10b3058a6ac2d7f4b79d351ef18ad3c06" } -rapier3d = { version = "0.17.2", git = "https://github.com/dimforge/rapier.git", rev = "e9ea2ca10b3058a6ac2d7f4b79d351ef18ad3c06" } +rapier_testbed3d = { version = "0.18" } +rapier3d = { version = "0.18" } oorandom = "11" -serde = { version = "1", features = [ "derive" ]} -serde_json = { version = "1", features = [ "preserve_order" ] } +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1", features = ["preserve_order"] } bincode = "1" -indexmap = { version = "1", features = [ "serde" ] } +indexmap = { version = "1", features = ["serde"] } uuid = "1" env_logger = "0.10" log = "0.4" [dependencies.sparkl3d] path = "../crates/sparkl3d" -features = [ "rapier-testbed", "serde-serialize" ] +features = ["rapier-testbed", "serde-serialize"] [[bin]] name = "examples3d" diff --git a/src/third_party/rapier/point_cloud_render.rs b/src/third_party/rapier/point_cloud_render.rs index 7bf246f..e0055a7 100644 --- a/src/third_party/rapier/point_cloud_render.rs +++ b/src/third_party/rapier/point_cloud_render.rs @@ -4,9 +4,11 @@ use bevy::{ core_pipeline::core_3d::Opaque3d, ecs::system::{lifetimeless::*, SystemParamItem}, math::prelude::*, - pbr::{MeshPipeline, MeshPipelineKey, MeshUniform, SetMeshBindGroup, SetMeshViewBindGroup}, + pbr::{ + MeshPipeline, MeshPipelineKey, MeshTransforms, RenderMeshInstances, SetMeshBindGroup, + SetMeshViewBindGroup, + }, prelude::*, - reflect::TypeUuid, render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, mesh::{GpuBufferInfo, MeshVertexBufferLayout}, @@ -21,10 +23,11 @@ use bevy::{ Render, RenderApp, RenderSet, }, }; +use bevy_ecs::query::ROQueryItem; use bytemuck::{Pod, Zeroable}; -pub const PARTICLE_SHADER_HANDLE: HandleUntyped = - HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 10091001291240510013); +// From: https://discordapp.com/channels/691052431525675048/1170930650606489711/1170944743962849380 +const PARTICLE_SHADER_HANDLE: Handle = Handle::weak_from_u128(10091001291240510013); #[derive(Component, Clone)] pub struct ParticleInstanceMaterialData(pub Vec); @@ -43,7 +46,7 @@ pub struct ParticleMaterialPlugin; impl Plugin for ParticleMaterialPlugin { fn build(&self, app: &mut App) { - app.add_plugin(ExtractComponentPlugin::::default()); + app.add_plugins(ExtractComponentPlugin::::default()); app.sub_app_mut(RenderApp) .add_render_command::() .init_resource::>() @@ -51,21 +54,21 @@ impl Plugin for ParticleMaterialPlugin { .add_systems( Render, ( - queue_custom.in_set(RenderSet::Queue), - prepare_instance_buffers.in_set(RenderSet::Prepare), + queue_custom.in_set(RenderSet::QueueMeshes), + prepare_instance_buffers.in_set(RenderSet::PrepareResources), ), ); let mut shaders = app.world.get_resource_mut::>().unwrap(); - const WGSL_PATH: &'static str = "src/third_party/rapier/shaders/instancing3d.wgsl"; - shaders.set_untracked( - PARTICLE_SHADER_HANDLE, + const WGSL_PATH: &'static str = "../src/third_party/rapier/shaders/instancing3d.wgsl"; + + shaders.get_or_insert_with(PARTICLE_SHADER_HANDLE, || { Shader::from_wgsl( read_to_string(WGSL_PATH).expect("Couldn't read particle shader"), WGSL_PATH, - ), - ); + ) + }); } fn finish(&self, app: &mut App) { @@ -89,36 +92,37 @@ fn queue_custom( mut pipelines: ResMut>, mut pipeline_cache: ResMut, meshes: Res>, - material_meshes: Query< - (Entity, &MeshUniform, &Handle), - (With>, With), - >, + render_mesh_instances: Res, + material_meshes: Query>, mut views: Query<(&ExtractedView, &mut RenderPhase)>, ) { - let draw_custom = transparent_3d_draw_functions - .read() - .get_id::() - .unwrap(); + let draw_custom = transparent_3d_draw_functions.read().id::(); let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples()); for (view, mut transparent_phase) in views.iter_mut() { - let view_matrix = view.transform.compute_matrix(); - let view_row_2 = view_matrix.row(2); - for (entity, mesh_uniform, mesh_handle) in material_meshes.iter() { - if let Some(mesh) = meshes.get(mesh_handle) { - let key = - msaa_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); - let pipeline = pipelines - .specialize(&mut pipeline_cache, &custom_pipeline, key, &mesh.layout) - .unwrap(); - transparent_phase.add(Opaque3d { - entity, - pipeline, - draw_function: draw_custom, - distance: view_row_2.dot(mesh_uniform.transform.col(3)), - }); - } + let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr); + let rangefinder = view.rangefinder3d(); + for entity in &material_meshes { + let Some(mesh_instance) = render_mesh_instances.get(&entity) else { + continue; + }; + let Some(mesh) = meshes.get(mesh_instance.mesh_asset_id) else { + continue; + }; + let key = view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology); + let pipeline = pipelines + .specialize(&mut pipeline_cache, &custom_pipeline, key, &mesh.layout) + .unwrap(); + transparent_phase.add(Opaque3d { + entity, + pipeline, + draw_function: draw_custom, + distance: rangefinder + .distance_translation(&mesh_instance.transforms.transform.translation), + batch_range: 0..1, + dynamic_offset: None, + }); } } } @@ -155,13 +159,10 @@ pub struct ParticleRenderPipeline { impl FromWorld for ParticleRenderPipeline { fn from_world(world: &mut World) -> Self { - let world = world.cell(); - let asset_server = world.get_resource::().unwrap(); - // asset_server.watch_for_changes().unwrap(); - let mesh_pipeline = world.get_resource::().unwrap(); + let mesh_pipeline = world.resource::(); ParticleRenderPipeline { - shader: PARTICLE_SHADER_HANDLE.typed(), + shader: PARTICLE_SHADER_HANDLE, mesh_pipeline: mesh_pipeline.clone(), } } @@ -176,6 +177,15 @@ impl SpecializedMeshPipeline for ParticleRenderPipeline { layout: &MeshVertexBufferLayout, ) -> Result { let mut descriptor = self.mesh_pipeline.specialize(key, layout)?; + + // meshes typically live in bind group 2. because we are using bindgroup 1 + // we need to add MESH_BINDGROUP_1 shader def so that the bindings are correctly + // linked in the shader + descriptor + .vertex + .shader_defs + .push("MESH_BINDGROUP_1".into()); + descriptor.vertex.shader = self.shader.clone(); descriptor.vertex.buffers.push(VertexBufferLayout { array_stride: std::mem::size_of::() as u64, @@ -212,25 +222,27 @@ type DrawCustom = ( pub struct DrawParticlesInstanced; impl RenderCommand

for DrawParticlesInstanced { - type Param = ( - SRes>, - SQuery>>, - SQuery>, - ); + type Param = (SRes>, SRes); + type ViewWorldQuery = (); + type ItemWorldQuery = (Entity, Read); + #[inline] fn render<'w>( - _item: &P, - _view: Entity, - entity: Entity, - (meshes, mesh_query, instance_buffer_query): SystemParamItem<'w, '_, Self::Param>, + item: &P, + _view: (), + (entity, instance_buffer): ROQueryItem<'w, Self::ItemWorldQuery>, + (meshes, render_mesh_instances): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { - let mesh_handle = mesh_query.get(entity).unwrap(); - let instance_buffer = instance_buffer_query.get_inner(entity).unwrap(); + let Some(mesh_instance) = render_mesh_instances.get(&entity) else { + return RenderCommandResult::Failure; + }; - let gpu_mesh = match meshes.into_inner().get(mesh_handle) { + let gpu_mesh = match meshes.into_inner().get(mesh_instance.mesh_asset_id) { Some(gpu_mesh) => gpu_mesh, - None => return RenderCommandResult::Failure, + None => { + return RenderCommandResult::Failure; + } }; pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); @@ -251,12 +263,8 @@ impl RenderCommand

for DrawParticlesInstanced { } RenderCommandResult::Success } - - type ViewWorldQuery = Entity; - - type ItemWorldQuery = Entity; } pub fn init_renderer(app: &mut App) { - app.add_plugin(ParticleMaterialPlugin); + app.add_plugins(ParticleMaterialPlugin); } diff --git a/src/third_party/rapier/shaders/instancing3d.wgsl b/src/third_party/rapier/shaders/instancing3d.wgsl index bead3af..3139b46 100644 --- a/src/third_party/rapier/shaders/instancing3d.wgsl +++ b/src/third_party/rapier/shaders/instancing3d.wgsl @@ -1,8 +1,4 @@ -#import bevy_pbr::mesh_view_bindings view -#import bevy_pbr::mesh_types Mesh - -@group(1) @binding(0) -var mesh: Mesh; +#import bevy_pbr::mesh_functions::mesh_position_local_to_clip struct Vertex { @location(0) position: vec3, @@ -18,13 +14,22 @@ struct VertexOutput { @location(0) color: vec4, }; +const identity: mat4x4 = mat4x4( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 +); + @vertex fn vertex(vertex: Vertex) -> VertexOutput { let position = vertex.position * vertex.i_pos_scale.w + vertex.i_pos_scale.xyz; - let world_position = mesh.model * vec4(position, 1.0); var out: VertexOutput; - out.clip_position = view.view_proj * world_position; + out.clip_position = mesh_position_local_to_clip( + identity, + vec4(position, 1.0) + ); out.color = vertex.i_color; return out; } diff --git a/src/third_party/rapier/testbed_plugin.rs b/src/third_party/rapier/testbed_plugin.rs index a9e3e9b..dda21ff 100644 --- a/src/third_party/rapier/testbed_plugin.rs +++ b/src/third_party/rapier/testbed_plugin.rs @@ -1,5 +1,7 @@ use crate::math::{Real, Vector}; use bevy::prelude::*; +use bevy::render::batching::NoAutomaticBatching; +use bevy::render::view::NoFrustumCulling; use bevy_egui::{egui, EguiContexts}; use na::{Point3, Vector3}; use rapier::geometry::ColliderSet; @@ -393,11 +395,17 @@ impl TestbedPlugin for MpmTestbedPlugin { .try_into() .unwrap(), ), - Transform::from_xyz(0.0, 0.0, 0.0), - GlobalTransform::default(), + SpatialBundle::INHERITED_IDENTITY, ParticleInstanceMaterialData(vec![]), - Visibility::default(), - ComputedVisibility::default(), + // NOTE: Frustum culling is done based on the Aabb of the Mesh and the GlobalTransform. + // As the cube is at the origin, if its Aabb moves outside the view frustum, all the + // instanced cubes will be culled. + // The InstanceMaterialData contains the 'GlobalTransform' information for this custom + // instancing, and that is not taken into account with the built-in frustum culling. + // We must disable the built-in frustum culling by adding the `NoFrustumCulling` marker + // component to avoid incorrect culling. + NoFrustumCulling, + NoAutomaticBatching, )) .id(); @@ -753,9 +761,18 @@ impl TestbedPlugin for MpmTestbedPlugin { } #[cfg(feature = "dim3")] { - commands - .entity(gfx.entity) - .insert(ParticleInstanceMaterialData(instance_data)); + commands.entity(gfx.entity).insert(( + ParticleInstanceMaterialData(instance_data), + // NOTE: Frustum culling is done based on the Aabb of the Mesh and the GlobalTransform. + // As the cube is at the origin, if its Aabb moves outside the view frustum, all the + // instanced cubes will be culled. + // The InstanceMaterialData contains the 'GlobalTransform' information for this custom + // instancing, and that is not taken into account with the built-in frustum culling. + // We must disable the built-in frustum culling by adding the `NoFrustumCulling` marker + // component to avoid incorrect culling. + NoFrustumCulling, + NoAutomaticBatching, + )); } } }