diff --git a/core/src/avm1/activation.rs b/core/src/avm1/activation.rs index fe3d24450..06b4c2ced 100644 --- a/core/src/avm1/activation.rs +++ b/core/src/avm1/activation.rs @@ -18,14 +18,13 @@ use gc_arena::{Gc, GcCell, MutationContext}; use indexmap::IndexMap; use instant::Instant; use rand::Rng; -use ruffle_render::bounding_box::BoundingBox; use smallvec::SmallVec; use std::borrow::Cow; use std::cmp::min; use std::fmt; use swf::avm1::read::Reader; use swf::avm1::types::*; -use swf::Twips; +use swf::{Rectangle, Twips}; use url::form_urlencoded; use super::object_reference::MovieClipReference; @@ -3067,8 +3066,7 @@ pub fn start_drag<'gc>( if y_max.get() < y_min.get() { std::mem::swap(&mut y_min, &mut y_max); } - BoundingBox { - valid: true, + Rectangle { x_min, y_min, x_max, diff --git a/core/src/avm1/globals/movie_clip.rs b/core/src/avm1/globals/movie_clip.rs index 6b5372678..7053b8582 100644 --- a/core/src/avm1/globals/movie_clip.rs +++ b/core/src/avm1/globals/movie_clip.rs @@ -1205,8 +1205,7 @@ fn get_bounds<'gc>( // the final matrix, but this matches Flash's behavior. let to_global_matrix = movie_clip.local_to_global_matrix(); let to_target_matrix = target.global_to_local_matrix(); - let bounds_transform = to_target_matrix * to_global_matrix; - bounds.transform(&bounds_transform) + to_target_matrix * to_global_matrix * bounds }; let out = ScriptObject::new( diff --git a/core/src/avm2/globals/flash/display/display_object.rs b/core/src/avm2/globals/flash/display/display_object.rs index dc70dee32..53c3a865d 100644 --- a/core/src/avm2/globals/flash/display/display_object.rs +++ b/core/src/avm2/globals/flash/display/display_object.rs @@ -18,8 +18,7 @@ use crate::vminterface::Instantiator; use crate::{avm2_stub_getter, avm2_stub_setter}; use ruffle_render::filters::Filter; use std::str::FromStr; -use swf::Twips; -use swf::{BlendMode, Rectangle}; +use swf::BlendMode; pub use crate::avm2::object::stage_allocator as display_object_allocator; @@ -827,7 +826,7 @@ pub fn set_blend_mode<'gc>( fn new_rectangle<'gc>( activation: &mut Activation<'_, 'gc>, - rectangle: BoundingBox, + rectangle: Rectangle, ) -> Result, Error<'gc>> { let x = rectangle.x_min.to_pixels(); let y = rectangle.y_min.to_pixels(); @@ -848,7 +847,7 @@ pub fn get_scroll_rect<'gc>( ) -> Result, Error<'gc>> { if let Some(dobj) = this.and_then(|this| this.as_display_object()) { if dobj.has_scroll_rect() { - return Ok(new_rectangle(activation, dobj.next_scroll_rect().into())?.into()); + return Ok(new_rectangle(activation, dobj.next_scroll_rect())?.into()); } else { return Ok(Value::Null); } @@ -993,8 +992,7 @@ pub fn get_bounds<'gc>( // the final matrix, but this matches Flash's behavior. let to_global_matrix = dobj.local_to_global_matrix(); let to_target_matrix = target.global_to_local_matrix(); - let bounds_transform = to_target_matrix * to_global_matrix; - bounds.transform(&bounds_transform) + to_target_matrix * to_global_matrix * bounds }; return Ok(new_rectangle(activation, out_bounds)?.into()); diff --git a/core/src/avm2/globals/flash/display/sprite.rs b/core/src/avm2/globals/flash/display/sprite.rs index 84b0f42ac..d6ecc8772 100644 --- a/core/src/avm2/globals/flash/display/sprite.rs +++ b/core/src/avm2/globals/flash/display/sprite.rs @@ -7,9 +7,8 @@ use crate::avm2::Error; use crate::avm2::Multiname; use crate::display_object::{MovieClip, SoundTransform, TDisplayObject}; use crate::tag_utils::SwfMovie; -use ruffle_render::bounding_box::BoundingBox; use std::sync::Arc; -use swf::Twips; +use swf::{Rectangle, Twips}; /// Implements `flash.display.Sprite`'s `init` method, which is called from the constructor pub fn init<'gc>( @@ -215,8 +214,7 @@ pub fn start_drag<'gc>( std::mem::swap(&mut y_min, &mut y_max); } - BoundingBox { - valid: true, + Rectangle { x_min, y_min, x_max, diff --git a/core/src/display_object.rs b/core/src/display_object.rs index c2b06f28c..9ddd0cacd 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -18,7 +18,7 @@ use ruffle_render::transform::Transform; use std::cell::{Ref, RefMut}; use std::fmt::Debug; use std::sync::Arc; -use swf::{BlendMode, Fixed8, Rectangle}; +use swf::{BlendMode, Fixed8}; mod avm1_button; mod avm2_button; @@ -648,20 +648,20 @@ pub trait TDisplayObject<'gc>: /// Implementors must override this method. /// Leaf DisplayObjects should return their bounds. /// Composite DisplayObjects that only contain children should return `&Default::default()` - fn self_bounds(&self) -> BoundingBox; + fn self_bounds(&self) -> Rectangle; /// The untransformed bounding box of this object including children. - fn bounds(&self) -> BoundingBox { + fn bounds(&self) -> Rectangle { self.bounds_with_transform(&Matrix::default()) } /// The local bounding box of this object including children, in its parent's coordinate system. - fn local_bounds(&self) -> BoundingBox { + fn local_bounds(&self) -> Rectangle { self.bounds_with_transform(self.base().matrix()) } /// The world bounding box of this object including children, relative to the stage. - fn world_bounds(&self) -> BoundingBox { + fn world_bounds(&self) -> Rectangle { self.bounds_with_transform(&self.local_to_global_matrix()) } @@ -669,26 +669,25 @@ pub trait TDisplayObject<'gc>: /// This function recurses down and transforms the AABB each child before adding /// it to the bounding box. This gives a tighter AABB then if we simply transformed /// the overall AABB. - fn bounds_with_transform(&self, matrix: &Matrix) -> BoundingBox { + fn bounds_with_transform(&self, matrix: &Matrix) -> Rectangle { // A scroll rect completely overrides an object's bounds, // and can even the bounding box to be larger than the actual content if let Some(scroll_rect) = self.scroll_rect() { - return BoundingBox { - x_min: Twips::from_pixels(0.0), - y_min: Twips::from_pixels(0.0), - x_max: scroll_rect.width(), - y_max: scroll_rect.height(), - valid: true, - } - .transform(matrix); + return *matrix + * Rectangle { + x_min: Twips::ZERO, + y_min: Twips::ZERO, + x_max: scroll_rect.width(), + y_max: scroll_rect.height(), + }; } - let mut bounds = self.self_bounds().transform(matrix); + let mut bounds = *matrix * self.self_bounds(); if let Some(ctr) = self.as_container() { for child in ctr.iter_render_list() { let matrix = *matrix * *child.base().matrix(); - bounds.union(&child.bounds_with_transform(&matrix)); + bounds = bounds.union(&child.bounds_with_transform(&matrix)); } } diff --git a/core/src/display_object/avm1_button.rs b/core/src/display_object/avm1_button.rs index 280639d29..41c3aa95c 100644 --- a/core/src/display_object/avm1_button.rs +++ b/core/src/display_object/avm1_button.rs @@ -336,9 +336,9 @@ impl<'gc> TDisplayObject<'gc> for Avm1Button<'gc> { self.render_children(context); } - fn self_bounds(&self) -> BoundingBox { + fn self_bounds(&self) -> Rectangle { // No inherent bounds; contains child DisplayObjects. - BoundingBox::default() + Default::default() } fn hit_test_shape( diff --git a/core/src/display_object/avm2_button.rs b/core/src/display_object/avm2_button.rs index 881bba98d..4189a026d 100644 --- a/core/src/display_object/avm2_button.rs +++ b/core/src/display_object/avm2_button.rs @@ -644,20 +644,20 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> { } } - fn self_bounds(&self) -> BoundingBox { + fn self_bounds(&self) -> Rectangle { // No inherent bounds; contains child DisplayObjects. - BoundingBox::default() + Default::default() } - fn bounds_with_transform(&self, matrix: &Matrix) -> BoundingBox { + fn bounds_with_transform(&self, matrix: &Matrix) -> Rectangle { // Get self bounds - let mut bounds = self.self_bounds().transform(matrix); + let mut bounds = *matrix * self.self_bounds(); // Add the bounds of the child, dictated by current state let state = self.0.read().state; if let Some(child) = self.get_state_child(state.into()) { let child_bounds = child.bounds_with_transform(matrix); - bounds.union(&child_bounds); + bounds = bounds.union(&child_bounds); } bounds diff --git a/core/src/display_object/bitmap.rs b/core/src/display_object/bitmap.rs index 2d09bbbe7..a693d4029 100644 --- a/core/src/display_object/bitmap.rs +++ b/core/src/display_object/bitmap.rs @@ -242,13 +242,12 @@ impl<'gc> TDisplayObject<'gc> for Bitmap<'gc> { self.0.read().id } - fn self_bounds(&self) -> BoundingBox { - BoundingBox { + fn self_bounds(&self) -> Rectangle { + Rectangle { x_min: Twips::ZERO, y_min: Twips::ZERO, x_max: Twips::from_pixels(Bitmap::width(*self).into()), y_max: Twips::from_pixels(Bitmap::height(*self).into()), - valid: true, } } diff --git a/core/src/display_object/edit_text.rs b/core/src/display_object/edit_text.rs index 4c7612dee..b74402ba2 100644 --- a/core/src/display_object/edit_text.rs +++ b/core/src/display_object/edit_text.rs @@ -109,7 +109,7 @@ pub struct EditTextData<'gc> { /// The current intrinsic bounds of the text field. #[collect(require_static)] - bounds: BoundingBox, + bounds: Rectangle, /// The AVM1 object handle object: Option>, @@ -219,13 +219,11 @@ impl<'gc> EditText<'gc> { AutoSizeMode::None }; - let bounds: BoundingBox = swf_tag.bounds().into(); - let (layout, intrinsic_bounds) = LayoutBox::lower_from_text_spans( &text_spans, context, swf_movie.clone(), - bounds.width() - Twips::from_pixels(Self::INTERNAL_PADDING * 2.0), + swf_tag.bounds().width() - Twips::from_pixels(Self::INTERNAL_PADDING * 2.0), swf_tag.is_word_wrap(), !swf_tag.use_outlines(), ); @@ -233,8 +231,8 @@ impl<'gc> EditText<'gc> { let mut base = InteractiveObjectBase::default(); - base.base.matrix_mut().tx = bounds.x_min; - base.base.matrix_mut().ty = bounds.y_min; + base.base.matrix_mut().tx = swf_tag.bounds().x_min; + base.base.matrix_mut().ty = swf_tag.bounds().y_min; let variable = if !swf_tag.variable_name().is_empty() { Some(swf_tag.variable_name()) @@ -275,7 +273,7 @@ impl<'gc> EditText<'gc> { object: None, layout, intrinsic_bounds, - bounds, + bounds: swf_tag.bounds().clone(), autosize, variable: variable.map(|s| s.to_string_lossy(encoding)), bound_stage_object: None, @@ -735,7 +733,7 @@ impl<'gc> EditText<'gc> { AutoSizeMode::Right => edit_text.bounds.x_max - width, AutoSizeMode::None => unreachable!(), }; - edit_text.bounds.set_x(new_x); + edit_text.bounds.x_min = new_x; edit_text.bounds.set_width(width); } else { let width = edit_text.static_data.bounds.width(); @@ -1570,7 +1568,7 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> { self.0.write(mc).object = Some(to.into()); } - fn self_bounds(&self) -> BoundingBox { + fn self_bounds(&self) -> Rectangle { self.0.read().bounds.clone() } @@ -1607,9 +1605,7 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> { fn width(&self) -> f64 { let edit_text = self.0.read(); - edit_text - .bounds - .transform(&edit_text.base.base.transform.matrix) + (edit_text.base.base.transform.matrix * edit_text.bounds.clone()) .width() .to_pixels() } @@ -1626,9 +1622,7 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> { fn height(&self) -> f64 { let edit_text = self.0.read(); - edit_text - .bounds - .transform(&edit_text.base.base.transform.matrix) + (edit_text.base.base.transform.matrix * edit_text.bounds.clone()) .height() .to_pixels() } diff --git a/core/src/display_object/graphic.rs b/core/src/display_object/graphic.rs index a670cdb5f..a63daa75a 100644 --- a/core/src/display_object/graphic.rs +++ b/core/src/display_object/graphic.rs @@ -47,7 +47,7 @@ impl<'gc> Graphic<'gc> { let library = context.library.library_for_movie(movie.clone()).unwrap(); let static_data = GraphicStatic { id: swf_shape.id, - bounds: (&swf_shape.shape_bounds).into(), + bounds: swf_shape.shape_bounds.clone(), render_handle: Some(context.renderer.register_shape( (&swf_shape).into(), &MovieLibrarySource { @@ -134,9 +134,9 @@ impl<'gc> TDisplayObject<'gc> for Graphic<'gc> { self.0.read().static_data.id } - fn self_bounds(&self) -> BoundingBox { + fn self_bounds(&self) -> Rectangle { if let Some(drawing) = &self.0.read().drawing { - drawing.self_bounds() + drawing.self_bounds().clone() } else { self.0.read().static_data.bounds.clone() } @@ -269,6 +269,6 @@ struct GraphicStatic { id: CharacterId, shape: swf::Shape, render_handle: Option, - bounds: BoundingBox, + bounds: Rectangle, movie: Arc, } diff --git a/core/src/display_object/loader_display.rs b/core/src/display_object/loader_display.rs index 97cedcb1d..61838f9e3 100644 --- a/core/src/display_object/loader_display.rs +++ b/core/src/display_object/loader_display.rs @@ -81,7 +81,7 @@ impl<'gc> TDisplayObject<'gc> for LoaderDisplay<'gc> { self.render_children(context); } - fn self_bounds(&self) -> BoundingBox { + fn self_bounds(&self) -> Rectangle { Default::default() } diff --git a/core/src/display_object/morph_shape.rs b/core/src/display_object/morph_shape.rs index 159aacaf8..01d6226cb 100644 --- a/core/src/display_object/morph_shape.rs +++ b/core/src/display_object/morph_shape.rs @@ -108,7 +108,7 @@ impl<'gc> TDisplayObject<'gc> for MorphShape<'gc> { .render_shape(shape_handle, context.transform_stack.transform()); } - fn self_bounds(&self) -> BoundingBox { + fn self_bounds(&self) -> Rectangle { let this = self.0.read(); let ratio = this.ratio; let static_data = this.static_data; @@ -148,7 +148,7 @@ impl<'gc> TDisplayObject<'gc> for MorphShape<'gc> { struct Frame { shape_handle: Option, shape: swf::Shape, - bounds: BoundingBox, + bounds: Rectangle, } /// Static data shared between all instances of a morph shape. @@ -331,7 +331,7 @@ impl MorphShapeStatic { Frame { shape_handle: None, shape, - bounds: bounds.into(), + bounds, } } diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index dbcc6d3d3..f59749f7e 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -2483,8 +2483,8 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> { self.render_children(context); } - fn self_bounds(&self) -> BoundingBox { - self.0.read().drawing.self_bounds() + fn self_bounds(&self) -> Rectangle { + self.0.read().drawing.self_bounds().clone() } fn hit_test_shape( diff --git a/core/src/display_object/stage.rs b/core/src/display_object/stage.rs index f80f08eb1..48856f205 100644 --- a/core/src/display_object/stage.rs +++ b/core/src/display_object/stage.rs @@ -106,7 +106,7 @@ pub struct StageData<'gc> { /// The bounds of the current viewport in twips, used for culling. #[collect(require_static)] - view_bounds: BoundingBox, + view_bounds: Rectangle, /// The window mode of the viewport. /// @@ -398,7 +398,7 @@ impl<'gc> Stage<'gc> { self.0.write(context.gc_context).window_mode = window_mode; } - pub fn view_bounds(self) -> BoundingBox { + pub fn view_bounds(self) -> Rectangle { self.0.read().view_bounds.clone() } @@ -514,12 +514,11 @@ impl<'gc> Stage<'gc> { self.0.write(context.gc_context).view_bounds = if self.should_letterbox() { // Letterbox: movie area - BoundingBox { + Rectangle { x_min: Twips::ZERO, y_min: Twips::ZERO, x_max: Twips::from_pixels(movie_width), y_max: Twips::from_pixels(movie_height), - valid: true, } } else { // No letterbox: full visible stage area @@ -527,12 +526,11 @@ impl<'gc> Stage<'gc> { let margin_right = (width_delta - tx) / scale_x; let margin_top = ty / scale_y; let margin_bottom = (height_delta - ty) / scale_y; - BoundingBox { + Rectangle { x_min: Twips::from_pixels(-margin_left), y_min: Twips::from_pixels(-margin_top), x_max: Twips::from_pixels(movie_width + margin_right), y_max: Twips::from_pixels(movie_height + margin_bottom), - valid: true, } }; @@ -763,7 +761,7 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> { u16::MAX } - fn self_bounds(&self) -> BoundingBox { + fn self_bounds(&self) -> Rectangle { Default::default() } diff --git a/core/src/display_object/text.rs b/core/src/display_object/text.rs index 7961b8edf..558ca44dd 100644 --- a/core/src/display_object/text.rs +++ b/core/src/display_object/text.rs @@ -50,7 +50,7 @@ impl<'gc> Text<'gc> { TextStatic { swf, id: tag.id, - bounds: (&tag.bounds).into(), + bounds: tag.bounds.clone(), text_transform: tag.matrix.into(), text_blocks: tag.records.clone(), }, @@ -163,7 +163,7 @@ impl<'gc> TDisplayObject<'gc> for Text<'gc> { context.transform_stack.pop(); } - fn self_bounds(&self) -> BoundingBox { + fn self_bounds(&self) -> Rectangle { self.0.read().static_data.bounds.clone() } @@ -215,8 +215,7 @@ impl<'gc> TDisplayObject<'gc> for Text<'gc> { matrix.invert(); let point = matrix * point; let glyph_shape = glyph.as_shape(); - let glyph_bounds: BoundingBox = (&glyph_shape.shape_bounds).into(); - if glyph_bounds.contains(point) + if glyph_shape.shape_bounds.contains(point) && ruffle_render::shape_utils::shape_hit_test( &glyph_shape, point, @@ -281,7 +280,7 @@ impl<'gc> TDisplayObject<'gc> for Text<'gc> { struct TextStatic { swf: Arc, id: CharacterId, - bounds: BoundingBox, + bounds: Rectangle, text_transform: Matrix, text_blocks: Vec, } diff --git a/core/src/display_object/video.rs b/core/src/display_object/video.rs index f7942e132..d84c79736 100644 --- a/core/src/display_object/video.rs +++ b/core/src/display_object/video.rs @@ -12,7 +12,6 @@ use crate::vminterface::{AvmObject, Instantiator}; use core::fmt; use gc_arena::{Collect, GcCell, MutationContext}; use ruffle_render::bitmap::BitmapInfo; -use ruffle_render::bounding_box::BoundingBox; use ruffle_render::commands::CommandHandler; use ruffle_render::quality::StageQuality; use ruffle_video::error::Error; @@ -425,17 +424,15 @@ impl<'gc> TDisplayObject<'gc> for Video<'gc> { } } - fn self_bounds(&self) -> BoundingBox { - let mut bounding_box = BoundingBox::default(); - + fn self_bounds(&self) -> Rectangle { match (*self.0.read().source.read()).borrow() { - VideoSource::Swf { streamdef, .. } => { - bounding_box.set_width(Twips::from_pixels(streamdef.width as f64)); - bounding_box.set_height(Twips::from_pixels(streamdef.height as f64)); - } + VideoSource::Swf { streamdef, .. } => Rectangle { + x_min: Twips::ZERO, + y_min: Twips::ZERO, + x_max: Twips::from_pixels_i32(streamdef.width.into()), + y_max: Twips::from_pixels_i32(streamdef.height.into()), + }, } - - bounding_box } fn render(&self, context: &mut RenderContext) { diff --git a/core/src/drawing.rs b/core/src/drawing.rs index c4b77358e..769c67db8 100644 --- a/core/src/drawing.rs +++ b/core/src/drawing.rs @@ -2,18 +2,17 @@ use crate::context::RenderContext; use gc_arena::Collect; use ruffle_render::backend::{RenderBackend, ShapeHandle}; use ruffle_render::bitmap::{BitmapHandle, BitmapInfo, BitmapSize, BitmapSource}; -use ruffle_render::bounding_box::BoundingBox; use ruffle_render::commands::CommandHandler; use ruffle_render::shape_utils::{DistilledShape, DrawCommand, DrawPath}; use std::cell::Cell; -use swf::{FillStyle, LineStyle, Twips}; +use swf::{FillStyle, LineStyle, Rectangle, Twips}; #[derive(Clone, Debug, Collect)] #[collect(require_static)] pub struct Drawing { render_handle: Cell>, - shape_bounds: BoundingBox, - edge_bounds: BoundingBox, + shape_bounds: Rectangle, + edge_bounds: Rectangle, dirty: Cell, paths: Vec, bitmaps: Vec, @@ -34,8 +33,8 @@ impl Drawing { pub fn new() -> Self { Self { render_handle: Cell::new(None), - shape_bounds: BoundingBox::default(), - edge_bounds: BoundingBox::default(), + shape_bounds: Default::default(), + edge_bounds: Default::default(), dirty: Cell::new(false), paths: Vec::new(), bitmaps: Vec::new(), @@ -50,8 +49,8 @@ impl Drawing { pub fn from_swf_shape(shape: &swf::Shape) -> Self { let mut this = Self { render_handle: Cell::new(None), - shape_bounds: (&shape.shape_bounds).into(), - edge_bounds: (&shape.edge_bounds).into(), + shape_bounds: shape.shape_bounds.clone(), + edge_bounds: shape.edge_bounds.clone(), dirty: Cell::new(true), paths: Vec::new(), bitmaps: Vec::new(), @@ -132,8 +131,8 @@ impl Drawing { self.pending_lines.clear(); self.paths.clear(); self.bitmaps.clear(); - self.edge_bounds = BoundingBox::default(); - self.shape_bounds = BoundingBox::default(); + self.edge_bounds = Default::default(); + self.shape_bounds = Default::default(); self.dirty.set(true); self.cursor = (Twips::ZERO, Twips::ZERO); self.fill_start = (Twips::ZERO, Twips::ZERO); @@ -192,11 +191,11 @@ impl Drawing { x: self.cursor.0, y: self.cursor.1, }; - stretch_bounding_box(&mut self.shape_bounds, &command, stroke_width); - stretch_bounding_box(&mut self.edge_bounds, &command, Twips::ZERO); + self.shape_bounds = stretch_bounds(&self.shape_bounds, &command, stroke_width); + self.edge_bounds = stretch_bounds(&self.edge_bounds, &command, Twips::ZERO); } - stretch_bounding_box(&mut self.shape_bounds, &command, stroke_width); - stretch_bounding_box(&mut self.edge_bounds, &command, Twips::ZERO); + self.shape_bounds = stretch_bounds(&self.shape_bounds, &command, stroke_width); + self.edge_bounds = stretch_bounds(&self.edge_bounds, &command, Twips::ZERO); } self.cursor = command.end_point(); @@ -296,8 +295,8 @@ impl Drawing { } } - pub fn self_bounds(&self) -> BoundingBox { - self.shape_bounds.clone() + pub fn self_bounds(&self) -> &Rectangle { + &self.shape_bounds } pub fn hit_test( @@ -432,26 +431,24 @@ enum DrawingPath { Line(DrawingLine), } -fn stretch_bounding_box( - bounding_box: &mut BoundingBox, +fn stretch_bounds( + bounds: &Rectangle, command: &DrawCommand, stroke_width: Twips, -) { +) -> Rectangle { let radius = stroke_width / 2; + let bounds = bounds.clone(); match *command { - DrawCommand::MoveTo { x, y } => { - bounding_box.encompass(x - radius, y - radius); - bounding_box.encompass(x + radius, y + radius); - } - DrawCommand::LineTo { x, y } => { - bounding_box.encompass(x - radius, y - radius); - bounding_box.encompass(x + radius, y + radius); - } - DrawCommand::CurveTo { x1, y1, x2, y2 } => { - bounding_box.encompass(x1 - radius, y1 - radius); - bounding_box.encompass(x1 + radius, y1 + radius); - bounding_box.encompass(x2 - radius, y2 - radius); - bounding_box.encompass(x2 + radius, y2 + radius); - } + DrawCommand::MoveTo { x, y } => bounds + .encompass(x - radius, y - radius) + .encompass(x + radius, y + radius), + DrawCommand::LineTo { x, y } => bounds + .encompass(x - radius, y - radius) + .encompass(x + radius, y + radius), + DrawCommand::CurveTo { x1, y1, x2, y2 } => bounds + .encompass(x1 - radius, y1 - radius) + .encompass(x1 + radius, y1 + radius) + .encompass(x2 - radius, y2 - radius) + .encompass(x2 + radius, y2 + radius), } } diff --git a/core/src/player.rs b/core/src/player.rs index 8c0d59ebe..c7154971e 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -2308,7 +2308,7 @@ pub struct DragObject<'gc> { /// The bounding rectangle where the clip will be maintained. #[collect(require_static)] - pub constraint: BoundingBox, + pub constraint: Rectangle, } fn run_mouse_pick<'gc>( diff --git a/core/src/prelude.rs b/core/src/prelude.rs index 2bdc0b90a..26a12f31c 100644 --- a/core/src/prelude.rs +++ b/core/src/prelude.rs @@ -2,11 +2,10 @@ pub use crate::avm2::Value as Avm2Value; pub use crate::display_object::{ DisplayObject, DisplayObjectContainer, HitTestOptions, TDisplayObject, TDisplayObjectContainer, }; -pub use ruffle_render::bounding_box::BoundingBox; pub use ruffle_render::color_transform::ColorTransform; pub use ruffle_render::matrix::Matrix; pub use std::ops::{Bound, RangeBounds}; -pub use swf::{CharacterId, Color, Twips}; +pub use swf::{CharacterId, Color, Rectangle, Twips}; pub use tracing::{error, info, trace, warn}; /// A depth for a Flash display object in AVM1. diff --git a/render/src/bounding_box.rs b/render/src/bounding_box.rs deleted file mode 100644 index f07835d93..000000000 --- a/render/src/bounding_box.rs +++ /dev/null @@ -1,179 +0,0 @@ -use crate::matrix::Matrix; -use swf::Twips; - -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct BoundingBox { - pub x_min: Twips, - pub y_min: Twips, - pub x_max: Twips, - pub y_max: Twips, - pub valid: bool, -} - -impl BoundingBox { - /// Clamps the given point inside this bounding box. - pub fn clamp(&self, (x, y): (Twips, Twips)) -> (Twips, Twips) { - if self.valid { - ( - x.clamp(self.x_min, self.x_max), - y.clamp(self.y_min, self.y_max), - ) - } else { - (x, y) - } - } - - pub fn transform(&self, matrix: &Matrix) -> Self { - if !self.valid { - return Self::default(); - } - - use std::cmp::{max, min}; - let pt0 = *matrix * (self.x_min, self.y_min); - let pt1 = *matrix * (self.x_min, self.y_max); - let pt2 = *matrix * (self.x_max, self.y_min); - let pt3 = *matrix * (self.x_max, self.y_max); - BoundingBox { - x_min: min(pt0.0, min(pt1.0, min(pt2.0, pt3.0))), - y_min: min(pt0.1, min(pt1.1, min(pt2.1, pt3.1))), - x_max: max(pt0.0, max(pt1.0, max(pt2.0, pt3.0))), - y_max: max(pt0.1, max(pt1.1, max(pt2.1, pt3.1))), - valid: true, - } - } - - pub fn encompass(&mut self, x: Twips, y: Twips) { - if self.valid { - if x < self.x_min { - self.x_min = x; - } - if x > self.x_max { - self.x_max = x; - } - if y < self.y_min { - self.y_min = y; - } - if y > self.y_max { - self.y_max = y; - } - } else { - self.x_min = x; - self.x_max = x; - self.y_min = y; - self.y_max = y; - self.valid = true; - } - } - - pub fn union(&mut self, other: &BoundingBox) { - use std::cmp::{max, min}; - if other.valid { - if self.valid { - self.x_min = min(self.x_min, other.x_min); - self.x_max = max(self.x_max, other.x_max); - self.y_min = min(self.y_min, other.y_min); - self.y_max = max(self.y_max, other.y_max); - } else { - *self = other.clone(); - } - } - } - - pub fn intersects(&self, other: &BoundingBox) -> bool { - if !self.valid || !other.valid { - return false; - } - - use std::cmp::{max, min}; - let x_min = max(self.x_min, other.x_min); - let y_min = max(self.y_min, other.y_min); - let x_max = min(self.x_max, other.x_max); - let y_max = min(self.y_max, other.y_max); - - x_min <= x_max && y_min <= y_max - } - - pub fn contains(&self, (x, y): (Twips, Twips)) -> bool { - self.valid && x >= self.x_min && x <= self.x_max && y >= self.y_min && y <= self.y_max - } - - /// Set the X coordinate to a particular value, maintaining the width of - /// this box (if possible). - pub fn set_x(&mut self, x: Twips) { - let width = self.width(); - self.x_min = x; - self.x_max = x + width; - - if self.y_max >= self.y_min { - self.valid = true; - } - } - - /// Set the Y coordinate to a particular value, maintaining the width of - /// this box (if possible). - pub fn set_y(&mut self, y: Twips) { - let height = self.height(); - self.y_min = y; - self.y_max = y + height; - - if self.x_max >= self.x_min { - self.valid = true; - } - } - - /// Determine the width of the bounding box. - pub fn width(&self) -> Twips { - if self.valid { - self.x_max - self.x_min - } else { - Default::default() - } - } - - /// Adjust the width of the bounding box. - pub fn set_width(&mut self, width: Twips) { - self.x_max = self.x_min + width; - - self.valid = self.x_max >= self.x_min && self.y_max >= self.y_min; - } - - /// Determine the height of the bounding box. - pub fn height(&self) -> Twips { - if self.valid { - self.y_max - self.y_min - } else { - Default::default() - } - } - - /// Adjust the height of the bounding box. - pub fn set_height(&mut self, height: Twips) { - self.y_max = self.y_min + height; - - self.valid = self.x_max >= self.x_min && self.y_max >= self.y_min; - } -} - -impl From> for BoundingBox { - fn from(rect: swf::Rectangle) -> Self { - Self { - x_min: rect.x_min, - y_min: rect.y_min, - x_max: rect.x_max, - y_max: rect.y_max, - valid: true, - } - } -} - -impl From<&swf::Rectangle> for BoundingBox { - fn from(rect: &swf::Rectangle) -> Self { - Self { - x_min: rect.x_min, - y_min: rect.y_min, - x_max: rect.x_max, - y_max: rect.y_max, - valid: true, - } - } -} diff --git a/render/src/lib.rs b/render/src/lib.rs index 9430001bc..1946c6f06 100644 --- a/render/src/lib.rs +++ b/render/src/lib.rs @@ -2,7 +2,6 @@ pub mod backend; pub mod bitmap; -pub mod bounding_box; pub mod color_transform; pub mod error; pub mod filters; diff --git a/render/src/matrix.rs b/render/src/matrix.rs index ad7410062..5c812aa24 100644 --- a/render/src/matrix.rs +++ b/render/src/matrix.rs @@ -1,6 +1,4 @@ -#![allow(clippy::suspicious_operation_groupings)] - -use swf::{Fixed16, Twips}; +use swf::{Fixed16, Rectangle, Twips}; /// The transformation matrix used by Flash display objects. #[derive(Copy, Clone, Debug, PartialEq)] @@ -162,6 +160,27 @@ impl std::ops::Mul<(Twips, Twips)> for Matrix { } } +impl std::ops::Mul> for Matrix { + type Output = Rectangle; + + fn mul(self, rhs: Rectangle) -> Self::Output { + if !rhs.is_valid() { + return Default::default(); + } + + let (x0, y0) = self * (rhs.x_min, rhs.y_min); + let (x1, y1) = self * (rhs.x_min, rhs.y_max); + let (x2, y2) = self * (rhs.x_max, rhs.y_min); + let (x3, y3) = self * (rhs.x_max, rhs.y_max); + Rectangle { + x_min: x0.min(x1).min(x2).min(x3), + x_max: x0.max(x1).max(x2).max(x3), + y_min: y0.min(y1).min(y2).min(y3), + y_max: y0.max(y1).max(y2).max(y3), + } + } +} + impl Default for Matrix { fn default() -> Matrix { Matrix::IDENTITY diff --git a/render/src/shape_utils.rs b/render/src/shape_utils.rs index 18698f385..4d05c2e6a 100644 --- a/render/src/shape_utils.rs +++ b/render/src/shape_utils.rs @@ -1,7 +1,6 @@ -use crate::bounding_box::BoundingBox; use crate::matrix::Matrix; use smallvec::SmallVec; -use swf::{CharacterId, FillStyle, LineStyle, Shape, ShapeRecord, Twips}; +use swf::{CharacterId, FillStyle, LineStyle, Rectangle, Shape, ShapeRecord, Twips}; pub fn calculate_shape_bounds(shape_records: &[swf::ShapeRecord]) -> swf::Rectangle { let mut bounds = swf::Rectangle { @@ -80,8 +79,8 @@ pub enum DrawPath<'a> { #[derive(Clone, Debug, Eq, PartialEq)] pub struct DistilledShape<'a> { pub paths: Vec>, - pub shape_bounds: BoundingBox, - pub edge_bounds: BoundingBox, + pub shape_bounds: Rectangle, + pub edge_bounds: Rectangle, pub id: CharacterId, } @@ -89,8 +88,8 @@ impl<'a> From<&'a swf::Shape> for DistilledShape<'a> { fn from(shape: &'a Shape) -> Self { Self { paths: ShapeConverter::from_shape(shape).into_commands(), - shape_bounds: (&shape.shape_bounds).into(), - edge_bounds: (&shape.edge_bounds).into(), + shape_bounds: shape.shape_bounds.clone(), + edge_bounds: shape.edge_bounds.clone(), id: shape.id, } } diff --git a/swf/src/read.rs b/swf/src/read.rs index 1c865a5fd..44a717d5f 100644 --- a/swf/src/read.rs +++ b/swf/src/read.rs @@ -2763,7 +2763,15 @@ pub mod tests { let buf = [0b00000_000]; let mut reader = Reader::new(&buf[..], 1); let rectangle = reader.read_rectangle().unwrap(); - assert_eq!(rectangle, Default::default()); + assert_eq!( + rectangle, + Rectangle { + x_min: Twips::ZERO, + y_min: Twips::ZERO, + x_max: Twips::ZERO, + y_max: Twips::ZERO, + } + ); } #[test] diff --git a/swf/src/types/rectangle.rs b/swf/src/types/rectangle.rs index eaf22938c..0b6d36e0f 100644 --- a/swf/src/types/rectangle.rs +++ b/swf/src/types/rectangle.rs @@ -1,11 +1,17 @@ -use std::ops::Sub; +use crate::Twips; +use std::cmp::Ord; +use std::ops::{Add, Sub}; -pub trait Coordinate: Copy + Sub {} +pub trait Coordinate: Copy + Ord + Add + Sub { + const INVALID: Self; +} -impl Coordinate for crate::Twips {} +impl Coordinate for Twips { + const INVALID: Self = Self::new(0x7ffffff); +} /// A rectangular region defined by minimum and maximum x- and y-coordinate positions. -#[derive(Clone, Debug, Default, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Rectangle { /// The minimum x-position of the rectangle. pub x_min: T, @@ -21,11 +27,102 @@ pub struct Rectangle { } impl Rectangle { + const INVALID: Self = Self { + x_min: T::INVALID, + x_max: T::INVALID, + y_min: T::INVALID, + y_max: T::INVALID, + }; + + #[inline] + #[must_use] + pub fn is_valid(&self) -> bool { + self.x_min != T::INVALID + } + + #[inline] + #[must_use] pub fn width(&self) -> T { self.x_max - self.x_min } + #[inline] + pub fn set_width(&mut self, width: T) { + self.x_max = self.x_min + width; + } + + #[inline] + #[must_use] pub fn height(&self) -> T { self.y_max - self.y_min } + + #[inline] + pub fn set_height(&mut self, height: T) { + self.y_max = self.y_min + height; + } + + /// Clamp a given point inside this rectangle. + #[must_use] + pub fn clamp(&self, (x, y): (T, T)) -> (T, T) { + if self.is_valid() { + ( + x.clamp(self.x_min, self.x_max), + y.clamp(self.y_min, self.y_max), + ) + } else { + (x, y) + } + } + + #[must_use] + pub fn encompass(mut self, x: T, y: T) -> Self { + if self.is_valid() { + self.x_min = self.x_min.min(x); + self.x_max = self.x_max.max(x); + self.y_min = self.y_min.min(y); + self.y_max = self.y_max.max(y); + } else { + self.x_min = x; + self.x_max = x; + self.y_min = y; + self.y_max = y; + } + self + } + + #[must_use] + pub fn union(mut self, other: &Self) -> Self { + if !self.is_valid() { + other.clone() + } else { + if other.is_valid() { + self.x_min = self.x_min.min(other.x_min); + self.x_max = self.x_max.max(other.x_max); + self.y_min = self.y_min.min(other.y_min); + self.y_max = self.y_max.max(other.y_max); + } + self + } + } + + #[must_use] + pub fn intersects(&self, other: &Self) -> bool { + self.is_valid() + && self.x_min <= other.x_max + && self.x_max >= other.x_min + && self.y_min <= other.y_max + && self.y_max >= other.y_min + } + + #[must_use] + pub fn contains(&self, (x, y): (T, T)) -> bool { + x >= self.x_min && x <= self.x_max && y >= self.y_min && y <= self.y_max + } +} + +impl Default for Rectangle { + fn default() -> Self { + Self::INVALID + } } diff --git a/swf/src/write.rs b/swf/src/write.rs index b91a2c172..2965b5c55 100644 --- a/swf/src/write.rs +++ b/swf/src/write.rs @@ -2594,18 +2594,23 @@ mod tests { #[test] fn write_rectangle_zero() { - let rect: Rectangle = Default::default(); + let rectangle = Rectangle { + x_min: Twips::ZERO, + y_min: Twips::ZERO, + x_max: Twips::ZERO, + y_max: Twips::ZERO, + }; let mut buf = Vec::new(); { let mut writer = Writer::new(&mut buf, 1); - writer.write_rectangle(&rect).unwrap(); + writer.write_rectangle(&rectangle).unwrap(); } assert_eq!(buf, [0]); } #[test] fn write_rectangle_signed() { - let rect = Rectangle { + let rectangle = Rectangle { x_min: Twips::from_pixels(-1.0), x_max: Twips::from_pixels(1.0), y_min: Twips::from_pixels(-1.0), @@ -2614,7 +2619,7 @@ mod tests { let mut buf = Vec::new(); { let mut writer = Writer::new(&mut buf, 1); - writer.write_rectangle(&rect).unwrap(); + writer.write_rectangle(&rectangle).unwrap(); } assert_eq!(buf, [0b_00110_101, 0b100_01010, 0b0_101100_0, 0b_10100_000]); }