byte-engine 0.1.0

A composable Rust game engine focused on graphics, input, audio, physics, and retained UI.
use std::{future::join, sync::Arc};

#[cfg(feature = "headed")]
use math::Matrix4;
use math::Vector3;
use utils::BoxedFuture;

use super::transform::Transform;
#[cfg(feature = "headed")]
use crate::rendering::{
	mesh::{self},
	renderable::mesh::MeshSource,
};
use crate::{
	core::{Entity, EntityHandle},
	physics::{
		self,
		body::{Body, BodyTypes},
		collider::{Collider, Shapes},
	},
	rendering::{
		mesh::generator::{MeshGenerator, SphereMeshGenerator},
		RenderableMesh,
	},
	space::Transformable,
};

/// The `Object` struct combines a renderable mesh with a physical body for use
/// in the default game world.
#[derive(Clone)]
pub struct Object {
	source: MeshSource,
	transform: Transform,
	velocity: Vector3,
	body_type: BodyTypes,
	collider: Shapes,
	friction: f32,
}

impl Object {
	pub fn new<'a>(resource_id: &'static str, transform: Transform, body_type: BodyTypes, velocity: Vector3) -> Self {
		Object {
			source: MeshSource::Resource(resource_id),
			transform,
			velocity,
			body_type,
			collider: Shapes::Sphere { radius: 1.0 },
			friction: 0.5,
		}
	}

	pub fn sphere(radius: f32) -> Self {
		Object {
			source: MeshSource::Generated(Arc::new(SphereMeshGenerator::from_radius(radius))),
			transform: Transform::default(),
			velocity: Vector3::default(),
			body_type: BodyTypes::Dynamic,
			collider: Shapes::Sphere { radius },
			friction: 0.5,
		}
	}

	pub fn r#box(size: Vector3) -> Self {
		Object {
			source: MeshSource::Generated(Arc::new(mesh::generator::BoxMeshGenerator::from_size(size))),
			transform: Transform::default(),
			velocity: Vector3::default(),
			body_type: BodyTypes::Dynamic,
			collider: Shapes::Cube { size },
			friction: 0.5,
		}
	}

	pub fn from_mesh_source(mesh_source: MeshSource) -> Self {
		Object {
			source: mesh_source,
			transform: Transform::default(),
			velocity: Vector3::default(),
			body_type: BodyTypes::Dynamic,
			collider: Shapes::Sphere { radius: 1.0 },
			friction: 0.5,
		}
	}

	pub fn new_generated(mesh: Arc<dyn MeshGenerator>) -> Self {
		Object {
			source: MeshSource::Generated(mesh),
			transform: Transform::default(),
			velocity: Vector3::default(),
			body_type: BodyTypes::Dynamic,
			collider: Shapes::Sphere { radius: 1.0 },
			friction: 0.5,
		}
	}

	pub fn get_transform_mut(&mut self) -> &mut Transform {
		&mut self.transform
	}

	pub fn body_type_mut(&mut self) -> &mut BodyTypes {
		&mut self.body_type
	}

	pub fn set_velocity(&mut self, velocity: Vector3) {
		self.velocity = velocity;
	}
}

impl Transformable for Object {
	fn transform(&self) -> &Transform {
		&self.transform
	}
	fn transform_mut(&mut self) -> &mut Transform {
		&mut self.transform
	}
}

impl Collider for Object {
	fn shape(&self) -> Shapes {
		self.collider.clone()
	}

	fn friction(&self) -> f32 {
		self.friction
	}
}

impl Body for Object {
	fn velocity(&self) -> Vector3 {
		self.velocity
	}
	fn body_type(&self) -> BodyTypes {
		self.body_type
	}
}

#[cfg(feature = "headed")]
impl RenderableMesh for Object {
	fn get_mesh(&self) -> &MeshSource {
		&self.source
	}
}

#[cfg(test)]
mod tests {
	use math::Vector3;

	use super::Object;
	use crate::{
		physics::{
			body::{Body, BodyTypes},
			collider::{Collider, Shapes},
		},
		rendering::{
			mesh::generator::MeshGenerator,
			renderable::mesh::{MeshSource, RenderableMesh},
		},
		space::{Positionable, Scalable, Transformable},
	};

	#[test]
	fn sphere_constructor_keeps_render_and_collision_radius_in_sync() {
		let object = Object::sphere(2.5);
		assert!(matches!(object.shape(), Shapes::Sphere { radius } if radius == 2.5));
		assert_eq!(object.body_type(), BodyTypes::Dynamic);
		assert_eq!(object.velocity(), Vector3::new(0.0, 0.0, 0.0));
		assert_eq!(object.friction(), 0.5);
		match object.get_mesh() {
			MeshSource::Generated(generator) => {
				let positions = generator.positions();
				assert!(positions.iter().any(|&(_, y, _)| (y - 2.5).abs() < 1e-5));
			}
			MeshSource::Resource(_) => {
				panic!("Expected generated sphere geometry. The most likely cause is a mismatched object constructor.")
			}
		}
	}

	#[test]
	fn box_constructor_keeps_render_and_collision_extents_in_sync() {
		let size = Vector3::new(1.0, 2.0, 3.0);
		let object = Object::r#box(size);
		assert!(matches!(object.shape(), Shapes::Cube { size: collider_size } if collider_size == size));
		match object.get_mesh() {
			MeshSource::Generated(generator) => assert!(generator
				.positions()
				.iter()
				.all(|&(x, y, z)| x.abs() == size.x && y.abs() == size.y && z.abs() == size.z)),
			MeshSource::Resource(_) => {
				panic!("Expected generated box geometry. The most likely cause is a mismatched object constructor.")
			}
		}
	}

	#[test]
	fn object_physics_and_transform_mutators_are_observable_through_traits() {
		let mut object = Object::new(
			"mesh.resource",
			crate::gameplay::Transform::default(),
			BodyTypes::Static,
			Vector3::new(1.0, 2.0, 3.0),
		);
		assert!(matches!(object.get_mesh(), MeshSource::Resource("mesh.resource")));
		assert_eq!(object.body_type(), BodyTypes::Static);
		assert_eq!(object.velocity(), Vector3::new(1.0, 2.0, 3.0));

		*object.body_type_mut() = BodyTypes::Kinematic;
		object.set_velocity(Vector3::new(4.0, 5.0, 6.0));
		object.set_position(Vector3::new(7.0, 8.0, 9.0));
		object.set_scale(Vector3::new(2.0, 3.0, 4.0));
		assert_eq!(object.body_type(), BodyTypes::Kinematic);
		assert_eq!(object.velocity(), Vector3::new(4.0, 5.0, 6.0));
		assert_eq!(object.position(), Vector3::new(7.0, 8.0, 9.0));
		assert_eq!(object.transform().scale(), Vector3::new(2.0, 3.0, 4.0));
	}

	#[test]
	fn cloned_objects_have_independent_transform_and_velocity_state() {
		let original = Object::sphere(1.0);
		let mut clone = original.clone();
		clone.set_position(Vector3::new(1.0, 2.0, 3.0));
		clone.set_velocity(Vector3::new(4.0, 5.0, 6.0));

		assert_eq!(original.position(), Vector3::new(0.0, 0.0, 0.0));
		assert_eq!(original.velocity(), Vector3::new(0.0, 0.0, 0.0));
		assert_eq!(clone.position(), Vector3::new(1.0, 2.0, 3.0));
		assert_eq!(clone.velocity(), Vector3::new(4.0, 5.0, 6.0));
	}
}