use crate::core::{Bundle, Entity, EntityName, World};
use crate::math::{Quat, Vec3, Vec4};
use gizmo_physics_core::Transform;
#[cfg(feature = "render")]
use crate::renderer::components::{
Camera, DirectionalLight, LightRole, Material, Mesh, MeshRenderer, PointLight, SpotLight,
};
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg(feature = "render")]
pub struct DirectionalLightBundle {
pub rotation: Quat,
pub color: Vec3,
pub intensity: f32,
pub role: LightRole,
}
#[cfg(feature = "render")]
impl Default for DirectionalLightBundle {
fn default() -> Self {
Self {
rotation: Quat::from_rotation_x(-std::f32::consts::PI / 4.0),
color: Vec3::new(1.0, 1.0, 1.0),
intensity: 3.0,
role: LightRole::Sun,
}
}
}
#[cfg(feature = "render")]
impl Bundle for DirectionalLightBundle {
fn get_infos() -> Vec<gizmo_core::archetype::ComponentInfo> { vec![] }
unsafe fn write_to_archetype(self, _arch: &mut gizmo_core::archetype::Archetype, _row: usize, _tick: u32) {}
fn apply(self, world: &mut World, entity: Entity) {
world.add_component(
entity,
Transform::new(Vec3::ZERO).with_rotation(self.rotation),
);
world.add_component(entity, gizmo_physics_core::components::GlobalTransform::default());
world.add_component(
entity,
DirectionalLight::new(self.color, self.intensity, self.role),
);
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg(feature = "render")]
pub struct PointLightBundle {
pub position: Vec3,
pub color: Vec3,
pub intensity: f32,
pub radius: f32,
}
#[cfg(feature = "render")]
impl Default for PointLightBundle {
fn default() -> Self {
Self {
position: Vec3::ZERO,
color: Vec3::new(1.0, 1.0, 1.0),
intensity: 5.0,
radius: 20.0,
}
}
}
#[cfg(feature = "render")]
impl Bundle for PointLightBundle {
fn get_infos() -> Vec<gizmo_core::archetype::ComponentInfo> { vec![] }
unsafe fn write_to_archetype(self, _arch: &mut gizmo_core::archetype::Archetype, _row: usize, _tick: u32) {}
fn apply(self, world: &mut World, entity: Entity) {
world.add_component(entity, Transform::new(self.position));
world.add_component(entity, gizmo_physics_core::components::GlobalTransform::default());
world.add_component(
entity,
PointLight::new(self.color, self.intensity, self.radius),
);
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg(feature = "render")]
pub struct SpotLightBundle {
pub position: Vec3,
pub rotation: Quat,
pub color: Vec3,
pub intensity: f32,
pub radius: f32,
pub inner_angle: f32,
pub outer_angle: f32,
}
#[cfg(feature = "render")]
impl Default for SpotLightBundle {
fn default() -> Self {
Self {
position: Vec3::ZERO,
rotation: Quat::IDENTITY,
color: Vec3::new(1.0, 1.0, 1.0),
intensity: 10.0,
radius: 30.0,
inner_angle: 0.4,
outer_angle: 0.6,
}
}
}
#[cfg(feature = "render")]
impl Bundle for SpotLightBundle {
fn get_infos() -> Vec<gizmo_core::archetype::ComponentInfo> { vec![] }
unsafe fn write_to_archetype(self, _arch: &mut gizmo_core::archetype::Archetype, _row: usize, _tick: u32) {}
fn apply(self, world: &mut World, entity: Entity) {
world.add_component(
entity,
Transform::new(self.position).with_rotation(self.rotation),
);
world.add_component(entity, gizmo_physics_core::components::GlobalTransform::default());
world.add_component(
entity,
SpotLight::new(
self.color,
self.intensity,
self.radius,
self.inner_angle,
self.outer_angle,
),
);
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg(feature = "render")]
pub struct CameraBundle {
pub position: Vec3,
pub fov: f32,
pub near: f32,
pub far: f32,
pub yaw: f32,
pub pitch: f32,
pub exposure: f32,
pub primary: bool,
}
#[cfg(feature = "render")]
impl Default for CameraBundle {
fn default() -> Self {
Self {
position: Vec3::new(0.0, 5.0, 10.0),
fov: std::f32::consts::FRAC_PI_3,
near: 0.1,
far: 1500.0,
yaw: 0.0,
pitch: 0.0,
exposure: 1.0,
primary: true,
}
}
}
#[cfg(feature = "render")]
impl Bundle for CameraBundle {
fn get_infos() -> Vec<gizmo_core::archetype::ComponentInfo> { vec![] }
unsafe fn write_to_archetype(self, _arch: &mut gizmo_core::archetype::Archetype, _row: usize, _tick: u32) {}
fn apply(self, world: &mut World, entity: Entity) {
world.add_component(entity, Transform::new(self.position));
world.add_component(entity, gizmo_physics_core::components::GlobalTransform::default());
let mut cam = Camera::new(
self.fov,
self.near,
self.far,
self.yaw,
self.pitch,
self.primary,
);
cam.exposure = self.exposure;
world.add_component(entity, cam);
}
}
#[cfg(feature = "render")]
pub struct MeshBundle {
pub position: Vec3,
pub rotation: Quat,
pub scale: Vec3,
pub mesh: crate::core::asset::Handle<Mesh>,
pub material: crate::core::asset::Handle<Material>,
pub name: Option<String>,
}
#[cfg(feature = "render")]
impl MeshBundle {
pub fn new(
mesh: crate::core::asset::Handle<Mesh>,
material: crate::core::asset::Handle<Material>,
) -> Self {
Self {
position: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
mesh,
material,
name: None,
}
}
pub fn at(mut self, position: Vec3) -> Self {
self.position = position;
self
}
pub fn with_rotation(mut self, rotation: Quat) -> Self {
self.rotation = rotation;
self
}
pub fn with_scale(mut self, scale: Vec3) -> Self {
self.scale = scale;
self
}
pub fn with_name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}
}
#[cfg(feature = "render")]
impl Bundle for MeshBundle {
fn get_infos() -> Vec<gizmo_core::archetype::ComponentInfo> { vec![] }
unsafe fn write_to_archetype(self, _arch: &mut gizmo_core::archetype::Archetype, _row: usize, _tick: u32) {}
fn apply(self, world: &mut World, entity: Entity) {
world.add_component(
entity,
Transform::new(self.position)
.with_rotation(self.rotation)
.with_scale(self.scale),
);
world.add_component(entity, gizmo_physics_core::components::GlobalTransform::default());
world.add_component(entity, self.mesh);
world.add_component(entity, self.material);
world.add_component(entity, MeshRenderer::new());
if let Some(name) = self.name {
world.add_component(entity, EntityName(name));
}
}
}
use gizmo_physics_core::{BoxShape, Collider, ColliderShape};
#[cfg(feature = "physics")]
use gizmo_physics_rigid::components::{RigidBody, Velocity};
#[cfg(feature = "physics")]
#[derive(Debug, Clone, PartialEq, Default)]
pub struct RigidBodyBundle {
pub rigid_body: RigidBody,
pub velocity: Velocity,
pub collider: Collider,
}
#[cfg(feature = "physics")]
impl RigidBodyBundle {
pub fn dynamic(mass: f32) -> Self {
Self {
rigid_body: RigidBody::new(mass, true),
..Default::default()
}
}
pub fn static_body() -> Self {
Self {
rigid_body: RigidBody::new_static(),
..Default::default()
}
}
pub fn kinematic() -> Self {
Self {
rigid_body: RigidBody::new_kinematic(),
..Default::default()
}
}
pub fn with_collider(mut self, collider: Collider) -> Self {
self.collider = collider;
self
}
pub fn with_velocity(mut self, linear: Vec3) -> Self {
self.velocity = Velocity::new(linear);
self
}
pub fn with_ccd(mut self) -> Self {
self.rigid_body.ccd_enabled = true;
self
}
pub fn with_air_drag(mut self, cd: f32, area: f32) -> Self {
self.rigid_body = self.rigid_body.with_air_drag(cd, area);
self
}
pub fn with_restitution(mut self, restitution: f32) -> Self {
self.collider = self.collider.with_restitution(restitution);
self
}
pub fn with_friction(mut self, friction: f32) -> Self {
self.collider = self.collider.with_friction(friction);
self
}
pub fn with_damping(mut self, linear: f32, angular: f32) -> Self {
self.rigid_body = self.rigid_body.with_damping(linear, angular);
self
}
pub fn with_gravity(mut self, enabled: bool) -> Self {
self.rigid_body = self.rigid_body.with_gravity(enabled);
self
}
pub fn with_center_of_mass(mut self, com: Vec3) -> Self {
self.rigid_body = self.rigid_body.with_center_of_mass(com);
self
}
pub fn lock_rotation(mut self) -> Self {
self.rigid_body = self.rigid_body.lock_rotation();
self
}
pub fn with_angular_velocity(mut self, angular: Vec3) -> Self {
self.velocity.angular = angular;
self
}
}
#[cfg(feature = "physics")]
impl Bundle for RigidBodyBundle {
fn get_infos() -> Vec<gizmo_core::archetype::ComponentInfo> {
<(RigidBody, Velocity, Collider)>::get_infos()
}
unsafe fn write_to_archetype(self, arch: &mut gizmo_core::archetype::Archetype, row: usize, tick: u32) {
let mut rb = self.rigid_body;
rb.update_inertia_from_collider(&self.collider);
(rb, self.velocity, self.collider).write_to_archetype(arch, row, tick)
}
fn apply(self, world: &mut World, entity: Entity) {
let mut rb = self.rigid_body;
rb.update_inertia_from_collider(&self.collider);
(rb, self.velocity, self.collider).apply(world, entity)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ergonomic_collider_and_bundle_builders() {
let c = Collider::sphere(0.5).with_restitution(0.9).with_friction(0.3);
assert_eq!(c.material.restitution, 0.9);
assert_eq!(c.material.static_friction, 0.3);
assert_eq!(c.material.dynamic_friction, 0.3);
let b = RigidBodyBundle::dynamic(2.0)
.with_collider(Collider::sphere(0.5))
.with_air_drag(0.47, 0.8)
.with_restitution(0.85);
assert_eq!(b.rigid_body.drag_coefficient, 0.47);
assert_eq!(b.rigid_body.drag_area, 0.8);
assert_eq!(b.collider.material.restitution, 0.85);
let b2 = RigidBodyBundle::dynamic(1.0)
.with_damping(0.1, 0.2)
.with_gravity(false)
.lock_rotation()
.with_center_of_mass(Vec3::new(0.0, 0.5, 0.0))
.with_angular_velocity(Vec3::new(0.0, 3.0, 0.0));
assert_eq!(b2.rigid_body.linear_damping, 0.1);
assert!(!b2.rigid_body.use_gravity);
assert!(b2.rigid_body.lock_rotation_x);
assert_eq!(b2.rigid_body.center_of_mass, Vec3::new(0.0, 0.5, 0.0));
assert_eq!(b2.velocity.angular, Vec3::new(0.0, 3.0, 0.0));
}
#[test]
fn rigid_body_bundle_derives_inertia_from_collider() {
let mut world = World::new();
let e = world
.spawn_bundle(RigidBodyBundle::dynamic(2.0).with_collider(Collider::sphere(0.5)));
let rbs = world.borrow::<RigidBody>();
let rb = rbs.get(e.id()).expect("rigid body spawned");
assert!(
(rb.local_inertia - Vec3::splat(0.2)).length() < 1e-6,
"bundle must derive sphere inertia from the collider, got {:?}",
rb.local_inertia
);
assert!(
(rb.local_inertia - Vec3::splat(1.0)).length() > 1e-3,
"inertia must be derived, not left at the default"
);
}
#[test]
fn rigid_body_bundle_static_body_inertia_derivation_is_noop() {
let mut world = World::new();
let e = world
.spawn_bundle(RigidBodyBundle::static_body().with_collider(Collider::sphere(0.5)));
assert!(world.borrow::<RigidBody>().get(e.id()).is_some());
}
}
#[cfg(all(feature = "render", feature = "physics"))]
#[derive(Clone)]
pub struct Prefab {
mesh: Mesh,
material: Material,
body: Option<RigidBodyBundle>,
auto_box: Option<Vec3>,
}
#[cfg(all(feature = "render", feature = "physics"))]
impl Prefab {
pub fn new(mesh: Mesh, material: Material) -> Self {
Self {
mesh,
material,
body: None,
auto_box: None,
}
}
pub fn with_body(mut self, body: RigidBodyBundle) -> Self {
self.body = Some(body);
self
}
pub fn with_pbr(mut self, albedo: Vec4, roughness: f32, metallic: f32) -> Self {
self.material = self.material.clone().with_pbr(albedo, roughness, metallic);
self
}
pub fn auto_box_collider(self) -> Self {
self.auto_box_collider_scaled(Vec3::ONE)
}
pub fn auto_box_collider_scaled(mut self, base: Vec3) -> Self {
self.auto_box = Some(base);
self
}
pub fn spawn(&self, world: &mut World, transform: Transform) -> Entity {
self.spawn_inner(world, transform, None)
}
pub fn spawn_at(&self, world: &mut World, position: Vec3) -> Entity {
self.spawn(world, Transform::new(position))
}
pub fn spawn_with_mass(&self, world: &mut World, transform: Transform, mass: f32) -> Entity {
self.spawn_inner(world, transform, Some(mass))
}
fn spawn_inner(
&self,
world: &mut World,
transform: Transform,
mass_override: Option<f32>,
) -> Entity {
let scale = transform.scale;
let e = world.spawn_bundle((
transform,
self.mesh.clone(),
self.material.clone(),
MeshRenderer::new(),
));
if let Some(body) = &self.body {
let mut body = body.clone();
if let Some(base) = self.auto_box {
let he = crate::systems::auto_collider::derived_box_half_extents(scale, base);
body.collider.shape = ColliderShape::Box(BoxShape { half_extents: he });
}
if let Some(m) = mass_override {
body.rigid_body.mass = m;
}
world.add_bundle(e, body);
}
e
}
}