use concinnity_core::components::PropBody;
use concinnity_core::components::{
BodyDynamics, Children, Collider, Held, Interactable, MeshRenderer, ModelRenderer, Parent,
Pickup, Prop, PropInstance, SceneMember, Transform,
};
use concinnity_core::ecs::{Entity, PipelineContext};
use std::collections::HashMap;
pub(crate) fn run(ctx: &mut PipelineContext) {
let props: Vec<(Entity, Prop)> = ctx
.query_with_entity::<Prop>()
.map(|(entity, prop)| (entity, prop.clone()))
.collect();
for (entity, prop) in &props {
ctx.insert(*entity, PropInstance);
ctx.insert(
*entity,
Transform {
position: prop.position,
rotation_deg: prop.rotation_deg,
scale: prop.scale,
},
);
if let Some(model) = prop.model {
ctx.insert(
*entity,
ModelRenderer {
model: model.id(),
cull_distance: prop.cull_distance,
},
);
} else {
ctx.insert(
*entity,
MeshRenderer {
mesh: prop.mesh,
material: prop.material,
cull_distance: prop.cull_distance,
},
);
}
if let Some(collider) = &prop.collider {
ctx.insert(*entity, Collider(collider.clone()));
}
if prop.interactable {
ctx.insert(*entity, Interactable);
}
if prop.pickup {
ctx.insert(*entity, Pickup);
}
if prop.is_held {
ctx.insert(*entity, Held);
}
if let Some(scene) = prop.scene {
ctx.insert(*entity, SceneMember(scene.id()));
}
}
for body in ctx.drain::<PropBody>() {
let Some(name) = body.prop_name else { continue };
let Some(entity) = ctx.entity_of(name.id()) else {
continue;
};
ctx.insert(
entity,
BodyDynamics {
mass: body.mass,
friction: body.friction,
restitution: body.restitution,
gravity_scale: body.gravity_scale,
linear_damping: body.linear_damping,
impact_clip: body.impact_clip,
impact_volume: body.impact_volume,
},
);
}
let mut children: HashMap<Entity, concinnity_core::memory::InlineVec<Entity>> = HashMap::new();
for (entity, prop) in &props {
if let Some(parent_id) = prop.parent
&& let Some(parent) = ctx.entity_of(parent_id.id())
{
ctx.insert(*entity, Parent(parent));
children.entry(parent).or_default().push(*entity);
}
}
for (parent, kids) in children {
ctx.insert(parent, Children(kids));
}
ctx.drain::<Prop>();
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ecs::SYSTEMS;
use concinnity_core::components::{Prop, PropCollider};
use concinnity_core::ecs::Ref;
use concinnity_core::ecs::asset_id::AssetId;
use concinnity_core::ecs::{MaterialHandle, MeshHandle, World};
fn prop() -> Prop {
Prop::default()
}
#[test]
fn a_prop_parents_onto_the_sky_rotation_pivot() {
use concinnity_core::components::{Parent, SkyRotation};
let mut world = World::new();
world.push_identified(AssetId(7), SkyRotation::default());
let mut moon = prop();
moon.parent = Some(Ref::new(AssetId(7)));
world.push_identified(AssetId(2), moon);
world.start(SYSTEMS).unwrap();
let ctx = world.context();
let pivot = ctx.entity_of(AssetId(7)).expect("the pivot is identified");
let (_, parent) = ctx
.query_with_entity::<Parent>()
.next()
.expect("the prop gained a parent edge");
assert_eq!(parent.0, pivot);
}
#[test]
fn decomposes_props_onto_their_entities() {
let mut world = World::new();
let mut frame = prop();
frame.model = Some(Ref::new(AssetId(100)));
frame.position = [1.0, 2.0, 3.0];
world.push_identified(AssetId(1), frame);
let mut panel = prop();
panel.mesh = Some(MeshHandle(101));
panel.material = Some(MaterialHandle(102));
panel.collider = Some(PropCollider::default());
panel.interactable = true;
panel.scene = Some(Ref::new(AssetId(200)));
panel.parent = Some(Ref::new(AssetId(1)));
panel.position = [4.0, 5.0, 6.0];
panel.rotation_deg = [0.0, 90.0, 0.0];
world.push_identified(AssetId(2), panel);
world.start(SYSTEMS).expect("start");
assert_eq!(world.query::<Prop>().count(), 0);
let models: Vec<_> = world
.join2::<ModelRenderer, Transform>()
.map(|(e, m, t)| (e, m.model, t.position))
.collect();
assert_eq!(models.len(), 1);
let (frame_e, model_id, frame_pos) = models[0];
assert_eq!(model_id, AssetId(100));
assert_eq!(frame_pos, [1.0, 2.0, 3.0]);
let meshes: Vec<_> = world
.join2::<MeshRenderer, Transform>()
.map(|(e, m, t)| (e, m.mesh, m.material, t.position, t.rotation_deg))
.collect();
assert_eq!(meshes.len(), 1);
let (panel_e, mesh_id, material_id, panel_pos, panel_rot) = meshes[0];
assert_eq!(mesh_id, Some(MeshHandle(101)));
assert_eq!(material_id, Some(MaterialHandle(102)));
assert_eq!(panel_pos, [4.0, 5.0, 6.0]);
assert_eq!(panel_rot, [0.0, 90.0, 0.0]);
assert_eq!(world.query::<Collider>().count(), 1);
assert_eq!(world.query::<Interactable>().count(), 1);
assert_eq!(world.query::<Pickup>().count(), 0);
let scene_members: Vec<_> = world
.join2::<SceneMember, MeshRenderer>()
.map(|(_, s, _)| s.0)
.collect();
assert_eq!(scene_members, vec![AssetId(200)]);
let parents: Vec<_> = world
.join2::<Parent, Transform>()
.map(|(e, p, _)| (e, p.0))
.collect();
assert_eq!(parents, vec![(panel_e, frame_e)]);
let kids: Vec<_> = world
.join2::<Children, ModelRenderer>()
.map(|(e, c, _)| (e, c.0.to_vec()))
.collect();
assert_eq!(kids, vec![(frame_e, vec![panel_e])]);
}
#[test]
fn forward_parent_reference_resolves() {
let mut world = World::new();
let mut child = prop();
child.mesh = Some(MeshHandle(10));
child.parent = Some(Ref::new(AssetId(2)));
world.push_identified(AssetId(1), child);
let mut parent = prop();
parent.mesh = Some(MeshHandle(11));
world.push_identified(AssetId(2), parent);
world.start(SYSTEMS).expect("start");
let by_parent: Vec<_> = world
.join2::<Parent, MeshRenderer>()
.map(|(_, p, m)| (p.0, m.mesh))
.collect();
assert_eq!(by_parent.len(), 1);
let (parent_e, child_mesh) = by_parent[0];
assert_eq!(child_mesh, Some(MeshHandle(10)));
let parent_mesh = world
.join2::<MeshRenderer, Transform>()
.find(|(e, _, _)| *e == parent_e)
.map(|(_, m, _)| m.mesh);
assert_eq!(parent_mesh, Some(Some(MeshHandle(11))));
}
#[test]
fn decomposed_default_drains_prop_keeping_components() {
let mut world = World::new();
let mut a = prop();
a.mesh = Some(MeshHandle(10));
world.push_identified(AssetId(1), a);
let mut b = prop();
b.model = Some(Ref::new(AssetId(20)));
world.push_identified(AssetId(2), b);
world.start(SYSTEMS).expect("start");
assert_eq!(world.query::<Prop>().count(), 0, "Prop column drained");
assert_eq!(world.query::<Transform>().count(), 2, "Transforms survive");
assert_eq!(world.query::<MeshRenderer>().count(), 1);
assert_eq!(world.query::<ModelRenderer>().count(), 1);
}
#[test]
fn prop_body_decomposes_to_body_dynamics_on_the_owner() {
let mut world = World::new();
let mut crate_prop = prop();
crate_prop.mesh = Some(MeshHandle(10));
crate_prop.collider = Some(PropCollider::default());
world.push_identified(AssetId(1), crate_prop);
let mut wall = prop();
wall.mesh = Some(MeshHandle(11));
wall.collider = Some(PropCollider::default());
world.push_identified(AssetId(2), wall);
world.add_component(PropBody {
prop_name: Some(Ref::new(AssetId(1))),
mass: 4.0,
..Default::default()
});
world.start(SYSTEMS).expect("start");
assert_eq!(world.query::<PropBody>().count(), 0);
let dynamics: Vec<_> = world
.join2::<BodyDynamics, MeshRenderer>()
.map(|(_, b, m)| (m.mesh, b.mass))
.collect();
assert_eq!(dynamics, vec![(Some(MeshHandle(10)), 4.0)]);
}
#[test]
fn despawn_removes_an_entitys_components() {
use concinnity_core::components::MeshRenderer;
let mut world = World::new();
world.add_component(Prop::default());
world.start(SYSTEMS).expect("start");
let entity = world
.join2::<Transform, MeshRenderer>()
.next()
.map(|(e, _, _)| e)
.expect("the Prop decomposed onto one entity");
world.despawn(entity);
assert_eq!(world.query::<Transform>().count(), 0);
assert_eq!(world.query::<MeshRenderer>().count(), 0);
}
}