Skip to content

Instantly share code, notes, and snippets.

@dmlary
Created December 24, 2023 16:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dmlary/740ca7f48efd5084f19dcb65eadbf9bd to your computer and use it in GitHub Desktop.
Save dmlary/740ca7f48efd5084f19dcb65eadbf9bd to your computer and use it in GitHub Desktop.
bevy v0.12 demo of rendering a single entity at multiple locations
/// 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