diff --git a/crates/lyra-game/src/scene/camera.rs b/crates/lyra-game/src/scene/camera.rs index cbcb9d2..785e156 100755 --- a/crates/lyra-game/src/scene/camera.rs +++ b/crates/lyra-game/src/scene/camera.rs @@ -1,10 +1,40 @@ use glam::{Mat4, Vec2}; use lyra_ecs::{Bundle, Component}; -use lyra_math::Angle; +use lyra_math::{Angle, Rect}; use lyra_reflect::Reflect; +/// The method of scaling for an [`OrthographicProjection`]. +#[derive(Debug, Clone, Copy, PartialEq, Reflect)] +pub enum ScaleMode { + /// No scaling, keep everything the same size as the viewport. + Viewport, + /// The width of the projection in world units. + /// + /// Height will be set based off of the viewport's aspect ratio. + Width(f32), + /// The height of the projection in world units. + /// + /// Width will be set based off of the viewport's aspect ratio. + Height(f32), + /// The exact size of the projection in world units. + /// + /// Ignoring viewport size, likely causes stretching. + Size(Vec2), + /// Keep the projection size below a maximum value in world units, while keeping the aspect ratio. + MaxSize(Vec2), + /// Keep the projection size above a minimum value in world units, while keeping the aspect ratio. + MinSize(Vec2), +} + +impl Default for ScaleMode { + fn default() -> Self { + ScaleMode::Viewport + } +} + #[derive(Debug, Clone, Copy, PartialEq, Reflect)] pub struct OrthographicProjection { + pub scale_mode: ScaleMode, pub scale: f32, pub znear: f32, pub zfar: f32, @@ -13,6 +43,7 @@ pub struct OrthographicProjection { impl Default for OrthographicProjection { fn default() -> Self { Self { + scale_mode: Default::default(), scale: 1.0, znear: 0.0, zfar: 1000.0, @@ -21,15 +52,63 @@ impl Default for OrthographicProjection { } impl OrthographicProjection { + fn get_rect(&self, viewport_size: Vec2) -> Rect { + let origin; + let size; + + match self.scale_mode { + ScaleMode::Viewport => { + origin = viewport_size * 0.5; + size = viewport_size; + }, + ScaleMode::Width(width) => { + let aspect = viewport_size.x / viewport_size.y; + let origin_x = width * 0.5; + let scaled_height = width / aspect; + let origin_y = scaled_height * 0.5; + + origin = Vec2::new(origin_x, origin_y); + size = Vec2::new(width, scaled_height); + }, + ScaleMode::Height(height) => { + let aspect = viewport_size.x / viewport_size.y; + let origin_y = height * 0.5; + let scaled_width = height * aspect; + let origin_x = scaled_width * 0.5; + + origin = Vec2::new(origin_x, origin_y); + size = Vec2::new(scaled_width, height); + }, + ScaleMode::Size(s) => { + origin = s * 0.5; + size = s; + }, + ScaleMode::MaxSize(s) => { + let clamped = s.min(viewport_size); + origin = clamped * 0.5; + size = clamped; + }, + ScaleMode::MinSize(s) => { + let clamped = s.max(viewport_size); + origin = clamped * 0.5; + size = clamped; + } + } + + Rect::new(self.scale * -origin.x, + self.scale * -origin.y, + self.scale * size.x - origin.x, + self.scale * size.y - origin.y) + } + pub fn to_mat(&self, viewport_size: Vec2) -> Mat4 { - let origin_x = viewport_size.x as f32 * 0.5; - let origin_y = viewport_size.y as f32 * 0.5; + let rect = self.get_rect(viewport_size); glam::Mat4::orthographic_rh( - self.scale * -origin_x, - self.scale * viewport_size.x as f32 - origin_x, - self.scale * -origin_y, - self.scale * viewport_size.y as f32 - origin_y, + rect.min.x, + rect.max.x, + rect.min.y, + rect.max.y, -1000.0, 1000.0, ) diff --git a/crates/lyra-math/src/lib.rs b/crates/lyra-math/src/lib.rs index da7869a..f79c9bf 100644 --- a/crates/lyra-math/src/lib.rs +++ b/crates/lyra-math/src/lib.rs @@ -7,6 +7,9 @@ pub use angle::*; mod area; pub use area::*; +mod rect; +pub use rect::*; + pub mod transform; pub use transform::*; diff --git a/crates/lyra-math/src/rect.rs b/crates/lyra-math/src/rect.rs new file mode 100644 index 0000000..e2256fb --- /dev/null +++ b/crates/lyra-math/src/rect.rs @@ -0,0 +1,83 @@ +use glam::Vec2; + +#[derive(Debug, Clone, Copy, Default, PartialEq)] +pub struct Rect { + pub min: Vec2, + pub max: Vec2, +} + +impl Rect { + pub fn new(x1: f32, y1: f32, x2: f32, y2: f32) -> Self { + Self { + min: Vec2::new(x1, y1), + max: Vec2::new(x2, y2), + } + } + + pub fn from_vec(min: Vec2, max: Vec2) -> Self { + Self { + min, + max + } + } +} + +impl std::ops::Add for Rect { + type Output = Rect; + + fn add(self, rhs: Self) -> Self::Output { + Rect::from_vec(self.min + rhs.min, self.max + rhs.max) + } +} + +impl std::ops::AddAssign for Rect { + fn add_assign(&mut self, rhs: Self) { + self.min += rhs.min; + self.max += rhs.max; + } +} + +impl std::ops::Sub for Rect { + type Output = Rect; + + fn sub(self, rhs: Self) -> Self::Output { + Rect::from_vec(self.min - rhs.min, self.max - rhs.max) + } +} + +impl std::ops::SubAssign for Rect { + fn sub_assign(&mut self, rhs: Self) { + self.min -= rhs.min; + self.max -= rhs.max; + } +} + +impl std::ops::Mul for Rect { + type Output = Rect; + + fn mul(self, rhs: Self) -> Self::Output { + Rect::from_vec(self.min * rhs.min, self.max * rhs.max) + } +} + +impl std::ops::MulAssign for Rect { + fn mul_assign(&mut self, rhs: Self) { + self.min *= rhs.min; + self.max *= rhs.max; + } +} + +impl std::ops::Div for Rect { + type Output = Rect; + + fn div(self, rhs: Self) -> Self::Output { + Rect::from_vec(self.min / rhs.min, self.max / rhs.max) + } +} + +impl std::ops::DivAssign for Rect { + fn div_assign(&mut self, rhs: Self) { + self.min /= rhs.min; + self.max /= rhs.max; + } +} \ No newline at end of file diff --git a/examples/2d/src/main.rs b/examples/2d/src/main.rs index 639f2f7..4896047 100644 --- a/examples/2d/src/main.rs +++ b/examples/2d/src/main.rs @@ -9,7 +9,7 @@ use lyra_engine::{ math::{self, Transform, Vec3}, render::light::directional::DirectionalLight, scene::{ - system_update_world_transforms, Camera2dBundle, CameraProjection, FreeFlyCamera, FreeFlyCameraPlugin, OrthographicProjection, WorldTransform, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN + system_update_world_transforms, Camera2dBundle, CameraProjection, FreeFlyCamera, FreeFlyCameraPlugin, OrthographicProjection, ScaleMode, WorldTransform, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN }, sprite::{self, Sprite}, }; @@ -162,12 +162,16 @@ fn setup_scene_plugin(app: &mut App) { world.spawn(( Camera2dBundle { projection: CameraProjection::Orthographic(OrthographicProjection { - scale: 0.75, + //scale_mode: ScaleMode::Width(960.0), + scale_mode: ScaleMode::Height(180.0), + //scale_mode: ScaleMode::Width(320.0), + //scale_mode: ScaleMode::Size(lyra_engine::math::Vec2::new(320.0, 180.0)), ..Default::default() }), ..Default::default() }, - Transform::from_xyz(200.0, 120.0, 0.0), + //Transform::from_xyz(200.0, 120.0, 0.0), + Transform::from_xyz(0.0, 0.0, 0.0), FreeFlyCamera::default(), )); }