Created
December 24, 2023 16:57
-
-
Save dmlary/740ca7f48efd5084f19dcb65eadbf9bd to your computer and use it in GitHub Desktop.
bevy v0.12 demo of rendering a single entity at multiple locations
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// bevy v0.12 demo of rendering a single entity at multiple locations | |
/// | |
/// Motivation: | |
/// I have a use-case in my game where I have a single entity (with various | |
/// components) that must be visible at two places on the screen at the same | |
/// time (non-euclidean grid shenanigans). I didn't want to duplicate a | |
/// subset of the entity's components then manage the duplicate's lifetime. | |
/// Through conversations in the bevy discord, the idea came up of making | |
/// modifications to the render pipeline to achive this. | |
/// | |
/// It just turned out that this is really simple to do with a single system. | |
/// See `make_duplicates()` for the details. | |
use bevy::{ | |
ecs::query::QueryItem, | |
pbr::{ | |
Mesh3d, MeshTransforms, RenderMaterialInstances, RenderMeshInstance, RenderMeshInstances, | |
}, | |
prelude::*, | |
render::{ | |
extract_instances::{ExtractInstance, ExtractInstancesPlugin, ExtractedInstances}, | |
view::{ExtractedView, VisibleEntities}, | |
RenderApp, | |
}, | |
}; | |
fn main() { | |
App::new() | |
.add_plugins((DefaultPlugins, RenderDuplicatesPlugin)) | |
.add_systems(Startup, setup) | |
.run(); | |
} | |
/// set up a simple 3D scene | |
fn setup( | |
mut commands: Commands, | |
mut meshes: ResMut<Assets<Mesh>>, | |
mut materials: ResMut<Assets<StandardMaterial>>, | |
) { | |
// cube | |
commands.spawn(( | |
PbrBundle { | |
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), | |
material: materials.add(Color::rgb_u8(124, 144, 255).into()), | |
..default() | |
}, | |
// Have the renderer draw this entity at a second location | |
RenderDuplicate(GlobalTransform::from_xyz(1.0, 1.0, 1.0)), | |
)); | |
// light | |
commands.spawn(PointLightBundle { | |
point_light: PointLight { | |
intensity: 1500.0, | |
shadows_enabled: true, | |
..default() | |
}, | |
transform: Transform::from_xyz(4.0, 8.0, 4.0), | |
..default() | |
}); | |
// camera | |
commands.spawn(Camera3dBundle { | |
transform: Transform::from_xyz(-2.5, 4.5, 9.0).looking_at(Vec3::ZERO, Vec3::Y), | |
..default() | |
}); | |
} | |
#[derive(Debug, Reflect, Component, Clone)] | |
struct RenderDuplicate(GlobalTransform); | |
impl ExtractInstance for RenderDuplicate { | |
type Query = &'static RenderDuplicate; | |
type Filter = (); | |
fn extract(item: QueryItem<'_, Self::Query>) -> Option<Self> { | |
Some(item.clone()) | |
} | |
} | |
struct RenderDuplicatesPlugin; | |
impl Plugin for RenderDuplicatesPlugin { | |
fn build(&self, app: &mut App) { | |
// extract all ClonedEntity components into a Resource | |
app.add_plugins(ExtractInstancesPlugin::<RenderDuplicate>::extract_visible()); | |
let render_app = app | |
.get_sub_app_mut(RenderApp) | |
.expect("RenderApp should already exist in App"); | |
// our system to clone entities needs to run after: | |
// - meshes have been extracted | |
// - materials have been extracted | |
// - our ClonedEntity(ies) have been extracted | |
// | |
// and before we throw the entities into the render pipeline in | |
// `prepare_materials()` | |
render_app.add_systems( | |
ExtractSchedule, | |
(apply_deferred, make_duplicates) | |
.chain() | |
.after(bevy::pbr::extract_meshes) | |
.after(ExtractInstancesPlugin::<AssetId<StandardMaterial>>::extract_visible) | |
.after(ExtractInstancesPlugin::<RenderDuplicate>::extract_visible) | |
.before(bevy::pbr::prepare_materials::<StandardMaterial>), | |
); | |
} | |
} | |
/// duplicate any entities that require it | |
/// | |
/// This function will take the list of entities with the RenderDuplicate | |
/// component, and for each one create a new entity and do the following: | |
/// * Add the `Mesh3d` component to it (just a filler component) | |
/// * Add the new entity to `RenderMeshInstances` with a copy of the original | |
/// `RednerMeshInstance` with modified `transform` values | |
/// * Add the new entity to `RenderMaterialInstances<StandardMaterial>`, | |
/// copying the Material from the original entity | |
/// * Add the new entity to the `VisibleEntity` component of all views | |
/// * XXX this will need to be changed when multiple views are used | |
fn make_duplicates( | |
duplicate_entities: Res<ExtractedInstances<RenderDuplicate>>, | |
mut commands: Commands, | |
mut views: Query<(&ExtractedView, &mut VisibleEntities)>, | |
mut render_mesh_instances: ResMut<RenderMeshInstances>, | |
mut render_material_instances: ResMut<RenderMaterialInstances<StandardMaterial>>, | |
) { | |
let mut entities = Vec::new(); | |
for (source_entity, cloned_entity) in duplicate_entities.iter() { | |
let Some(render_mesh) = render_mesh_instances.get(source_entity) else { | |
continue; | |
}; | |
let Some(&material) = render_material_instances.get(source_entity) else { | |
continue; | |
}; | |
// these steps were extracted from `bevy::pbr::extract_meshes()` | |
let transform = cloned_entity.0.affine(); | |
let render_mesh_instance = RenderMeshInstance { | |
transforms: MeshTransforms { | |
transform: (&transform).into(), | |
previous_transform: (&transform).into(), | |
..render_mesh.transforms | |
}, | |
..*render_mesh | |
}; | |
let entity = commands.spawn(Mesh3d).id(); | |
debug!( | |
"cloned entity {:?} -> {:?} @ {:?}", | |
source_entity, entity, transform | |
); | |
// add the entity to the resources | |
render_mesh_instances.insert(entity, render_mesh_instance); | |
render_material_instances.insert(entity, material); | |
entities.push(entity); | |
} | |
// update all the views to add the new entities as visible | |
for (_view, mut visible_entities) in &mut views { | |
visible_entities.entities.extend(&entities); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment