byte-engine 0.1.0

A composable Rust game engine focused on graphics, input, audio, physics, and retained UI.
#[derive(Debug, Clone, Copy)]
pub struct Bounds {
	min: Vector3,
	max: Vector3,
}

impl Bounds {
	pub fn zero() -> Self {
		Self::new(Vector3::zero(), Vector3::zero())
	}

	pub fn new(min: Vector3, max: Vector3) -> Self {
		Self {
			min: Vector3::new(min.x.min(max.x), min.y.min(max.y), min.z.min(max.z)),
			max: Vector3::new(min.x.max(max.x), min.y.max(max.y), min.z.max(max.z)),
		}
	}

	pub fn intersects(&self, other: &Bounds) -> bool {
		self.max.x >= other.min.x
			&& self.min.x <= other.max.x
			&& self.max.y >= other.min.y
			&& self.min.y <= other.max.y
			&& self.max.z >= other.min.z
			&& self.min.z <= other.max.z
	}

	pub fn expand_to_fit_point(&mut self, point: Vector3) {
		self.min.x = self.min.x.min(point.x);
		self.min.y = self.min.y.min(point.y);
		self.min.z = self.min.z.min(point.z);
		self.max.x = self.max.x.max(point.x);
		self.max.y = self.max.y.max(point.y);
		self.max.z = self.max.z.max(point.z);
	}

	pub fn expand_to_fit_points(&mut self, points: &[Vector3]) {
		for point in points {
			self.expand_to_fit_point(*point);
		}
	}

	pub fn expand(&mut self, other: &Bounds) {
		self.min.x = self.min.x.min(other.min.x);
		self.min.y = self.min.y.min(other.min.y);
		self.min.z = self.min.z.min(other.min.z);
		self.max.x = self.max.x.max(other.max.x);
		self.max.y = self.max.y.max(other.max.y);
		self.max.z = self.max.z.max(other.max.z);
	}

	pub fn translated(&self, offset: Vector3) -> Self {
		Self::new(self.min + offset, self.max + offset)
	}

	pub fn size(&self) -> Vector3 {
		self.max - self.min
	}

	pub fn expanded_by(&self, other: Vector3) -> Self {
		Self::new(self.min - other, self.max + other)
	}

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

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

impl Add<Vector3> for Bounds {
	type Output = Self;

	fn add(self, other: Vector3) -> Self {
		self.translated(other)
	}
}

use std::ops::Add;

use math::{Base as _, Vector3};

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

	use super::*;

	#[test]
	fn new_normalizes_inverted_bounds() {
		let bounds = Bounds::new(Vector3::new(2.0, -1.0, 4.0), Vector3::new(-3.0, 5.0, 1.0));

		assert_vec3f_near!(bounds.min(), Vector3::new(-3.0, -1.0, 1.0));
		assert_vec3f_near!(bounds.max(), Vector3::new(2.0, 5.0, 4.0));
	}

	#[test]
	fn intersects_handles_touching_and_inverted_inputs() {
		let a = Bounds::new(Vector3::new(1.0, 1.0, 1.0), Vector3::new(-1.0, -1.0, -1.0));
		let touching = Bounds::new(Vector3::new(1.0, -0.5, -0.5), Vector3::new(2.0, 0.5, 0.5));
		let separate = Bounds::new(Vector3::new(2.1, 0.0, 0.0), Vector3::new(3.0, 1.0, 1.0));

		assert!(a.intersects(&touching));
		assert!(!a.intersects(&separate));
	}

	#[test]
	fn expand_to_fit_points_tolerates_empty_input() {
		let mut bounds = Bounds::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(1.0, 2.0, 3.0));
		bounds.expand_to_fit_points(&[]);

		assert_vec3f_near!(bounds.min(), Vector3::new(1.0, 2.0, 3.0));
		assert_vec3f_near!(bounds.max(), Vector3::new(1.0, 2.0, 3.0));
	}
}