byte-engine 0.1.0

A composable Rust game engine focused on graphics, input, audio, physics, and retained UI.
use math::{
	mat::{MatScale as _, MatTranslate as _},
	Matrix4, Quaternion, Vector3, Vector4,
};

use crate::core::{
	channel::{Channel as _, DefaultChannel},
	factory::Handle,
	message::Message,
};

#[derive(Debug, Clone)]
pub struct Transform {
	position: Vector3,
	scale: Vector3,
	rotation: Quaternion,
}

impl Default for Transform {
	fn default() -> Self {
		Self {
			position: Vector3::new(0.0, 0.0, 0.0),
			scale: Vector3::new(1.0, 1.0, 1.0),
			rotation: Quaternion::identity(),
		}
	}
}

impl Transform {
	pub fn identity() -> Self {
		Self {
			position: Vector3::new(0.0, 0.0, 0.0),
			scale: Vector3::new(1.0, 1.0, 1.0),
			rotation: Quaternion::identity(),
		}
	}

	pub fn new(position: Vector3, scale: Vector3, rotation: Quaternion) -> Self {
		Self {
			position,
			scale,
			rotation,
		}
	}

	pub fn position(self, position: Vector3) -> Self {
		Self { position, ..self }
	}

	pub fn rotation(self, rotation: Quaternion) -> Self {
		Self { rotation, ..self }
	}

	pub fn from_position(position: Vector3) -> Self {
		Self {
			position,
			scale: Vector3::new(1.0, 1.0, 1.0),
			rotation: Quaternion::identity(),
		}
	}

	fn from_translation(position: Vector3) -> Self {
		Self {
			position,
			scale: Vector3::new(1.0, 1.0, 1.0),
			rotation: Quaternion::identity(),
		}
	}

	fn from_scale(scale: Vector3) -> Self {
		Self {
			position: Vector3::new(0.0, 0.0, 0.0),
			scale,
			rotation: Quaternion::identity(),
		}
	}

	fn from_rotation(rotation: Quaternion) -> Self {
		Self {
			position: Vector3::new(0.0, 0.0, 0.0),
			scale: Vector3::new(1.0, 1.0, 1.0),
			rotation,
		}
	}

	pub fn get_matrix(&self) -> Matrix4 {
		let rotation = self.rotation.get_matrix();
		let x = Vector4::from((rotation.get_row(0), 0.0));
		let y = Vector4::from((rotation.get_row(1), 0.0));
		let z = Vector4::from((rotation.get_row(2), 0.0));
		Matrix4::from_translation(self.position)
			* Matrix4::from((x, y, z, Vector4::new(0.0, 0.0, 0.0, 1.0)))
			* Matrix4::from_scale(self.scale)
	}

	pub fn set_position(&mut self, position: Vector3) {
		self.position = position;
	}

	pub fn get_position(&self) -> Vector3 {
		self.position
	}

	pub fn set_scale(&mut self, scale: Vector3) {
		self.scale = scale;
	}

	pub fn scale(&self) -> Vector3 {
		self.scale
	}

	pub fn set_orientation(&mut self, orientation: Quaternion) {
		self.rotation = orientation;
	}

	pub fn get_orientation(&self) -> Quaternion {
		self.rotation
	}
}

impl From<&Transform> for Matrix4 {
	fn from(transform: &Transform) -> Self {
		transform.get_matrix()
	}
}

#[derive(Clone, Debug)]
pub struct TransformationUpdate {
	handle: Handle,
	transform: Transform,
}

impl TransformationUpdate {
	pub fn new(handle: Handle, transform: Transform) -> Self {
		Self { handle, transform }
	}

	pub fn apply(channel: &mut DefaultChannel<Self>, handle: Handle, transform: Transform) {
		channel.send(TransformationUpdate::new(handle, transform));
	}

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

	pub fn handle(&self) -> &Handle {
		&self.handle
	}
}

impl Message for TransformationUpdate {}

pub trait Applicator {
	type Type;
	fn apply(&mut self, value: Self::Type);
}

#[cfg(test)]
mod tests {
	use math::{Quaternion, Vector3, Vector4};

	use super::{Transform, TransformationUpdate};
	use crate::{
		core::{
			channel::{Channel, DefaultChannel},
			factory::Factory,
			listener::Listener,
		},
		space::{Orientable, Positionable, Scalable, Transformable},
	};

	struct SpatialEntity {
		transform: Transform,
	}

	impl Transformable for SpatialEntity {
		fn transform(&self) -> &Transform {
			&self.transform
		}

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

	#[test]
	fn identity_and_default_have_the_same_spatial_effect() {
		let point = Vector4::new(2.0, -3.0, 4.0, 1.0);
		assert_eq!(Transform::default().get_matrix() * point, point);
		assert_eq!(Transform::identity().get_matrix() * point, point);
	}

	#[test]
	fn matrix_applies_scale_before_translation() {
		let transform = Transform::new(
			Vector3::new(10.0, 20.0, 30.0),
			Vector3::new(2.0, 3.0, 4.0),
			Quaternion::identity(),
		);

		let transformed = transform.get_matrix() * Vector4::new(1.0, 1.0, 1.0, 1.0);
		assert_eq!(transformed, Vector4::new(12.0, 23.0, 34.0, 1.0));
		assert_eq!(
			transform.get_matrix() * Vector4::new(1.0, 1.0, 1.0, 0.0),
			Vector4::new(2.0, 3.0, 4.0, 0.0)
		);
	}

	#[test]
	fn constructors_and_builders_preserve_unmodified_components() {
		let rotation = Quaternion::from_axis_angle(Vector3::new(0.0, 1.0, 0.0), 0.5);
		let transform = Transform::from_position(Vector3::new(1.0, 2.0, 3.0)).rotation(rotation);
		assert_eq!(transform.get_position(), Vector3::new(1.0, 2.0, 3.0));
		assert_eq!(transform.scale(), Vector3::new(1.0, 1.0, 1.0));
		assert_eq!(transform.get_orientation(), rotation);

		assert_eq!(
			Transform::from_translation(Vector3::new(4.0, 5.0, 6.0)).get_position(),
			Vector3::new(4.0, 5.0, 6.0)
		);
		assert_eq!(
			Transform::from_scale(Vector3::new(2.0, 3.0, 4.0)).scale(),
			Vector3::new(2.0, 3.0, 4.0)
		);
		assert_eq!(Transform::from_rotation(rotation).get_orientation(), rotation);
	}

	#[test]
	fn transformable_blanket_traits_mutate_one_shared_transform() {
		let orientation = Quaternion::from_axis_angle(Vector3::new(1.0, 0.0, 0.0), 0.25);
		let mut entity = SpatialEntity {
			transform: Transform::default(),
		};

		entity.set_position(Vector3::new(3.0, 4.0, 5.0));
		entity.set_scale(Vector3::new(2.0, 2.0, 2.0));
		entity.set_orientation(orientation);

		assert_eq!(entity.position(), Vector3::new(3.0, 4.0, 5.0));
		assert_eq!(entity.scale(), Vector3::new(2.0, 2.0, 2.0));
		assert_eq!(entity.orientation(), orientation);
		assert_eq!(entity.transform.get_position(), entity.position());
	}

	#[test]
	fn transformation_update_preserves_handle_and_payload_through_channel() {
		let mut factory = Factory::new();
		let handle = factory.create("entity");
		let transform = Transform::from_position(Vector3::new(7.0, 8.0, 9.0));
		let mut channel = DefaultChannel::new();
		let mut listener = channel.listener();

		TransformationUpdate::apply(&mut channel, handle, transform);
		let update = listener.read().expect("transformation update");
		assert_eq!(update.handle(), &handle);
		assert_eq!(update.transform().get_position(), Vector3::new(7.0, 8.0, 9.0));
	}
}