render: Replace `BoundingBox` with `swf::Rectangle`

This commit is contained in:
relrelb 2023-03-03 12:54:56 +02:00 committed by relrelb
parent 5756c847cd
commit 83c15b8033
26 changed files with 246 additions and 323 deletions

View File

@ -18,14 +18,13 @@ use gc_arena::{Gc, GcCell, MutationContext};
use indexmap::IndexMap; use indexmap::IndexMap;
use instant::Instant; use instant::Instant;
use rand::Rng; use rand::Rng;
use ruffle_render::bounding_box::BoundingBox;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::borrow::Cow; use std::borrow::Cow;
use std::cmp::min; use std::cmp::min;
use std::fmt; use std::fmt;
use swf::avm1::read::Reader; use swf::avm1::read::Reader;
use swf::avm1::types::*; use swf::avm1::types::*;
use swf::Twips; use swf::{Rectangle, Twips};
use url::form_urlencoded; use url::form_urlencoded;
use super::object_reference::MovieClipReference; use super::object_reference::MovieClipReference;
@ -3067,8 +3066,7 @@ pub fn start_drag<'gc>(
if y_max.get() < y_min.get() { if y_max.get() < y_min.get() {
std::mem::swap(&mut y_min, &mut y_max); std::mem::swap(&mut y_min, &mut y_max);
} }
BoundingBox { Rectangle {
valid: true,
x_min, x_min,
y_min, y_min,
x_max, x_max,

View File

@ -1205,8 +1205,7 @@ fn get_bounds<'gc>(
// the final matrix, but this matches Flash's behavior. // the final matrix, but this matches Flash's behavior.
let to_global_matrix = movie_clip.local_to_global_matrix(); let to_global_matrix = movie_clip.local_to_global_matrix();
let to_target_matrix = target.global_to_local_matrix(); let to_target_matrix = target.global_to_local_matrix();
let bounds_transform = to_target_matrix * to_global_matrix; to_target_matrix * to_global_matrix * bounds
bounds.transform(&bounds_transform)
}; };
let out = ScriptObject::new( let out = ScriptObject::new(

View File

@ -18,8 +18,7 @@ use crate::vminterface::Instantiator;
use crate::{avm2_stub_getter, avm2_stub_setter}; use crate::{avm2_stub_getter, avm2_stub_setter};
use ruffle_render::filters::Filter; use ruffle_render::filters::Filter;
use std::str::FromStr; use std::str::FromStr;
use swf::Twips; use swf::BlendMode;
use swf::{BlendMode, Rectangle};
pub use crate::avm2::object::stage_allocator as display_object_allocator; 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>( fn new_rectangle<'gc>(
activation: &mut Activation<'_, 'gc>, activation: &mut Activation<'_, 'gc>,
rectangle: BoundingBox, rectangle: Rectangle<Twips>,
) -> Result<Object<'gc>, Error<'gc>> { ) -> Result<Object<'gc>, Error<'gc>> {
let x = rectangle.x_min.to_pixels(); let x = rectangle.x_min.to_pixels();
let y = rectangle.y_min.to_pixels(); let y = rectangle.y_min.to_pixels();
@ -848,7 +847,7 @@ pub fn get_scroll_rect<'gc>(
) -> Result<Value<'gc>, Error<'gc>> { ) -> Result<Value<'gc>, Error<'gc>> {
if let Some(dobj) = this.and_then(|this| this.as_display_object()) { if let Some(dobj) = this.and_then(|this| this.as_display_object()) {
if dobj.has_scroll_rect() { 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 { } else {
return Ok(Value::Null); return Ok(Value::Null);
} }
@ -993,8 +992,7 @@ pub fn get_bounds<'gc>(
// the final matrix, but this matches Flash's behavior. // the final matrix, but this matches Flash's behavior.
let to_global_matrix = dobj.local_to_global_matrix(); let to_global_matrix = dobj.local_to_global_matrix();
let to_target_matrix = target.global_to_local_matrix(); let to_target_matrix = target.global_to_local_matrix();
let bounds_transform = to_target_matrix * to_global_matrix; to_target_matrix * to_global_matrix * bounds
bounds.transform(&bounds_transform)
}; };
return Ok(new_rectangle(activation, out_bounds)?.into()); return Ok(new_rectangle(activation, out_bounds)?.into());

View File

@ -7,9 +7,8 @@ use crate::avm2::Error;
use crate::avm2::Multiname; use crate::avm2::Multiname;
use crate::display_object::{MovieClip, SoundTransform, TDisplayObject}; use crate::display_object::{MovieClip, SoundTransform, TDisplayObject};
use crate::tag_utils::SwfMovie; use crate::tag_utils::SwfMovie;
use ruffle_render::bounding_box::BoundingBox;
use std::sync::Arc; use std::sync::Arc;
use swf::Twips; use swf::{Rectangle, Twips};
/// Implements `flash.display.Sprite`'s `init` method, which is called from the constructor /// Implements `flash.display.Sprite`'s `init` method, which is called from the constructor
pub fn init<'gc>( pub fn init<'gc>(
@ -215,8 +214,7 @@ pub fn start_drag<'gc>(
std::mem::swap(&mut y_min, &mut y_max); std::mem::swap(&mut y_min, &mut y_max);
} }
BoundingBox { Rectangle {
valid: true,
x_min, x_min,
y_min, y_min,
x_max, x_max,

View File

@ -18,7 +18,7 @@ use ruffle_render::transform::Transform;
use std::cell::{Ref, RefMut}; use std::cell::{Ref, RefMut};
use std::fmt::Debug; use std::fmt::Debug;
use std::sync::Arc; use std::sync::Arc;
use swf::{BlendMode, Fixed8, Rectangle}; use swf::{BlendMode, Fixed8};
mod avm1_button; mod avm1_button;
mod avm2_button; mod avm2_button;
@ -648,20 +648,20 @@ pub trait TDisplayObject<'gc>:
/// Implementors must override this method. /// Implementors must override this method.
/// Leaf DisplayObjects should return their bounds. /// Leaf DisplayObjects should return their bounds.
/// Composite DisplayObjects that only contain children should return `&Default::default()` /// Composite DisplayObjects that only contain children should return `&Default::default()`
fn self_bounds(&self) -> BoundingBox; fn self_bounds(&self) -> Rectangle<Twips>;
/// The untransformed bounding box of this object including children. /// The untransformed bounding box of this object including children.
fn bounds(&self) -> BoundingBox { fn bounds(&self) -> Rectangle<Twips> {
self.bounds_with_transform(&Matrix::default()) self.bounds_with_transform(&Matrix::default())
} }
/// The local bounding box of this object including children, in its parent's coordinate system. /// 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<Twips> {
self.bounds_with_transform(self.base().matrix()) self.bounds_with_transform(self.base().matrix())
} }
/// The world bounding box of this object including children, relative to the stage. /// The world bounding box of this object including children, relative to the stage.
fn world_bounds(&self) -> BoundingBox { fn world_bounds(&self) -> Rectangle<Twips> {
self.bounds_with_transform(&self.local_to_global_matrix()) 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 /// 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 /// it to the bounding box. This gives a tighter AABB then if we simply transformed
/// the overall AABB. /// the overall AABB.
fn bounds_with_transform(&self, matrix: &Matrix) -> BoundingBox { fn bounds_with_transform(&self, matrix: &Matrix) -> Rectangle<Twips> {
// A scroll rect completely overrides an object's bounds, // A scroll rect completely overrides an object's bounds,
// and can even the bounding box to be larger than the actual content // and can even the bounding box to be larger than the actual content
if let Some(scroll_rect) = self.scroll_rect() { if let Some(scroll_rect) = self.scroll_rect() {
return BoundingBox { return *matrix
x_min: Twips::from_pixels(0.0), * Rectangle {
y_min: Twips::from_pixels(0.0), x_min: Twips::ZERO,
x_max: scroll_rect.width(), y_min: Twips::ZERO,
y_max: scroll_rect.height(), x_max: scroll_rect.width(),
valid: true, y_max: scroll_rect.height(),
} };
.transform(matrix);
} }
let mut bounds = self.self_bounds().transform(matrix); let mut bounds = *matrix * self.self_bounds();
if let Some(ctr) = self.as_container() { if let Some(ctr) = self.as_container() {
for child in ctr.iter_render_list() { for child in ctr.iter_render_list() {
let matrix = *matrix * *child.base().matrix(); let matrix = *matrix * *child.base().matrix();
bounds.union(&child.bounds_with_transform(&matrix)); bounds = bounds.union(&child.bounds_with_transform(&matrix));
} }
} }

View File

@ -336,9 +336,9 @@ impl<'gc> TDisplayObject<'gc> for Avm1Button<'gc> {
self.render_children(context); self.render_children(context);
} }
fn self_bounds(&self) -> BoundingBox { fn self_bounds(&self) -> Rectangle<Twips> {
// No inherent bounds; contains child DisplayObjects. // No inherent bounds; contains child DisplayObjects.
BoundingBox::default() Default::default()
} }
fn hit_test_shape( fn hit_test_shape(

View File

@ -644,20 +644,20 @@ impl<'gc> TDisplayObject<'gc> for Avm2Button<'gc> {
} }
} }
fn self_bounds(&self) -> BoundingBox { fn self_bounds(&self) -> Rectangle<Twips> {
// No inherent bounds; contains child DisplayObjects. // 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<Twips> {
// Get self bounds // 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 // Add the bounds of the child, dictated by current state
let state = self.0.read().state; let state = self.0.read().state;
if let Some(child) = self.get_state_child(state.into()) { if let Some(child) = self.get_state_child(state.into()) {
let child_bounds = child.bounds_with_transform(matrix); let child_bounds = child.bounds_with_transform(matrix);
bounds.union(&child_bounds); bounds = bounds.union(&child_bounds);
} }
bounds bounds

View File

@ -242,13 +242,12 @@ impl<'gc> TDisplayObject<'gc> for Bitmap<'gc> {
self.0.read().id self.0.read().id
} }
fn self_bounds(&self) -> BoundingBox { fn self_bounds(&self) -> Rectangle<Twips> {
BoundingBox { Rectangle {
x_min: Twips::ZERO, x_min: Twips::ZERO,
y_min: Twips::ZERO, y_min: Twips::ZERO,
x_max: Twips::from_pixels(Bitmap::width(*self).into()), x_max: Twips::from_pixels(Bitmap::width(*self).into()),
y_max: Twips::from_pixels(Bitmap::height(*self).into()), y_max: Twips::from_pixels(Bitmap::height(*self).into()),
valid: true,
} }
} }

View File

@ -109,7 +109,7 @@ pub struct EditTextData<'gc> {
/// The current intrinsic bounds of the text field. /// The current intrinsic bounds of the text field.
#[collect(require_static)] #[collect(require_static)]
bounds: BoundingBox, bounds: Rectangle<Twips>,
/// The AVM1 object handle /// The AVM1 object handle
object: Option<AvmObject<'gc>>, object: Option<AvmObject<'gc>>,
@ -219,13 +219,11 @@ impl<'gc> EditText<'gc> {
AutoSizeMode::None AutoSizeMode::None
}; };
let bounds: BoundingBox = swf_tag.bounds().into();
let (layout, intrinsic_bounds) = LayoutBox::lower_from_text_spans( let (layout, intrinsic_bounds) = LayoutBox::lower_from_text_spans(
&text_spans, &text_spans,
context, context,
swf_movie.clone(), 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.is_word_wrap(),
!swf_tag.use_outlines(), !swf_tag.use_outlines(),
); );
@ -233,8 +231,8 @@ impl<'gc> EditText<'gc> {
let mut base = InteractiveObjectBase::default(); let mut base = InteractiveObjectBase::default();
base.base.matrix_mut().tx = bounds.x_min; base.base.matrix_mut().tx = swf_tag.bounds().x_min;
base.base.matrix_mut().ty = bounds.y_min; base.base.matrix_mut().ty = swf_tag.bounds().y_min;
let variable = if !swf_tag.variable_name().is_empty() { let variable = if !swf_tag.variable_name().is_empty() {
Some(swf_tag.variable_name()) Some(swf_tag.variable_name())
@ -275,7 +273,7 @@ impl<'gc> EditText<'gc> {
object: None, object: None,
layout, layout,
intrinsic_bounds, intrinsic_bounds,
bounds, bounds: swf_tag.bounds().clone(),
autosize, autosize,
variable: variable.map(|s| s.to_string_lossy(encoding)), variable: variable.map(|s| s.to_string_lossy(encoding)),
bound_stage_object: None, bound_stage_object: None,
@ -735,7 +733,7 @@ impl<'gc> EditText<'gc> {
AutoSizeMode::Right => edit_text.bounds.x_max - width, AutoSizeMode::Right => edit_text.bounds.x_max - width,
AutoSizeMode::None => unreachable!(), AutoSizeMode::None => unreachable!(),
}; };
edit_text.bounds.set_x(new_x); edit_text.bounds.x_min = new_x;
edit_text.bounds.set_width(width); edit_text.bounds.set_width(width);
} else { } else {
let width = edit_text.static_data.bounds.width(); 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()); self.0.write(mc).object = Some(to.into());
} }
fn self_bounds(&self) -> BoundingBox { fn self_bounds(&self) -> Rectangle<Twips> {
self.0.read().bounds.clone() self.0.read().bounds.clone()
} }
@ -1607,9 +1605,7 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
fn width(&self) -> f64 { fn width(&self) -> f64 {
let edit_text = self.0.read(); let edit_text = self.0.read();
edit_text (edit_text.base.base.transform.matrix * edit_text.bounds.clone())
.bounds
.transform(&edit_text.base.base.transform.matrix)
.width() .width()
.to_pixels() .to_pixels()
} }
@ -1626,9 +1622,7 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
fn height(&self) -> f64 { fn height(&self) -> f64 {
let edit_text = self.0.read(); let edit_text = self.0.read();
edit_text (edit_text.base.base.transform.matrix * edit_text.bounds.clone())
.bounds
.transform(&edit_text.base.base.transform.matrix)
.height() .height()
.to_pixels() .to_pixels()
} }

View File

@ -47,7 +47,7 @@ impl<'gc> Graphic<'gc> {
let library = context.library.library_for_movie(movie.clone()).unwrap(); let library = context.library.library_for_movie(movie.clone()).unwrap();
let static_data = GraphicStatic { let static_data = GraphicStatic {
id: swf_shape.id, id: swf_shape.id,
bounds: (&swf_shape.shape_bounds).into(), bounds: swf_shape.shape_bounds.clone(),
render_handle: Some(context.renderer.register_shape( render_handle: Some(context.renderer.register_shape(
(&swf_shape).into(), (&swf_shape).into(),
&MovieLibrarySource { &MovieLibrarySource {
@ -134,9 +134,9 @@ impl<'gc> TDisplayObject<'gc> for Graphic<'gc> {
self.0.read().static_data.id self.0.read().static_data.id
} }
fn self_bounds(&self) -> BoundingBox { fn self_bounds(&self) -> Rectangle<Twips> {
if let Some(drawing) = &self.0.read().drawing { if let Some(drawing) = &self.0.read().drawing {
drawing.self_bounds() drawing.self_bounds().clone()
} else { } else {
self.0.read().static_data.bounds.clone() self.0.read().static_data.bounds.clone()
} }
@ -269,6 +269,6 @@ struct GraphicStatic {
id: CharacterId, id: CharacterId,
shape: swf::Shape, shape: swf::Shape,
render_handle: Option<ShapeHandle>, render_handle: Option<ShapeHandle>,
bounds: BoundingBox, bounds: Rectangle<Twips>,
movie: Arc<SwfMovie>, movie: Arc<SwfMovie>,
} }

View File

@ -81,7 +81,7 @@ impl<'gc> TDisplayObject<'gc> for LoaderDisplay<'gc> {
self.render_children(context); self.render_children(context);
} }
fn self_bounds(&self) -> BoundingBox { fn self_bounds(&self) -> Rectangle<Twips> {
Default::default() Default::default()
} }

View File

@ -108,7 +108,7 @@ impl<'gc> TDisplayObject<'gc> for MorphShape<'gc> {
.render_shape(shape_handle, context.transform_stack.transform()); .render_shape(shape_handle, context.transform_stack.transform());
} }
fn self_bounds(&self) -> BoundingBox { fn self_bounds(&self) -> Rectangle<Twips> {
let this = self.0.read(); let this = self.0.read();
let ratio = this.ratio; let ratio = this.ratio;
let static_data = this.static_data; let static_data = this.static_data;
@ -148,7 +148,7 @@ impl<'gc> TDisplayObject<'gc> for MorphShape<'gc> {
struct Frame { struct Frame {
shape_handle: Option<ShapeHandle>, shape_handle: Option<ShapeHandle>,
shape: swf::Shape, shape: swf::Shape,
bounds: BoundingBox, bounds: Rectangle<Twips>,
} }
/// Static data shared between all instances of a morph shape. /// Static data shared between all instances of a morph shape.
@ -331,7 +331,7 @@ impl MorphShapeStatic {
Frame { Frame {
shape_handle: None, shape_handle: None,
shape, shape,
bounds: bounds.into(), bounds,
} }
} }

View File

@ -2483,8 +2483,8 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
self.render_children(context); self.render_children(context);
} }
fn self_bounds(&self) -> BoundingBox { fn self_bounds(&self) -> Rectangle<Twips> {
self.0.read().drawing.self_bounds() self.0.read().drawing.self_bounds().clone()
} }
fn hit_test_shape( fn hit_test_shape(

View File

@ -106,7 +106,7 @@ pub struct StageData<'gc> {
/// The bounds of the current viewport in twips, used for culling. /// The bounds of the current viewport in twips, used for culling.
#[collect(require_static)] #[collect(require_static)]
view_bounds: BoundingBox, view_bounds: Rectangle<Twips>,
/// The window mode of the viewport. /// The window mode of the viewport.
/// ///
@ -398,7 +398,7 @@ impl<'gc> Stage<'gc> {
self.0.write(context.gc_context).window_mode = window_mode; self.0.write(context.gc_context).window_mode = window_mode;
} }
pub fn view_bounds(self) -> BoundingBox { pub fn view_bounds(self) -> Rectangle<Twips> {
self.0.read().view_bounds.clone() 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() { self.0.write(context.gc_context).view_bounds = if self.should_letterbox() {
// Letterbox: movie area // Letterbox: movie area
BoundingBox { Rectangle {
x_min: Twips::ZERO, x_min: Twips::ZERO,
y_min: Twips::ZERO, y_min: Twips::ZERO,
x_max: Twips::from_pixels(movie_width), x_max: Twips::from_pixels(movie_width),
y_max: Twips::from_pixels(movie_height), y_max: Twips::from_pixels(movie_height),
valid: true,
} }
} else { } else {
// No letterbox: full visible stage area // No letterbox: full visible stage area
@ -527,12 +526,11 @@ impl<'gc> Stage<'gc> {
let margin_right = (width_delta - tx) / scale_x; let margin_right = (width_delta - tx) / scale_x;
let margin_top = ty / scale_y; let margin_top = ty / scale_y;
let margin_bottom = (height_delta - ty) / scale_y; let margin_bottom = (height_delta - ty) / scale_y;
BoundingBox { Rectangle {
x_min: Twips::from_pixels(-margin_left), x_min: Twips::from_pixels(-margin_left),
y_min: Twips::from_pixels(-margin_top), y_min: Twips::from_pixels(-margin_top),
x_max: Twips::from_pixels(movie_width + margin_right), x_max: Twips::from_pixels(movie_width + margin_right),
y_max: Twips::from_pixels(movie_height + margin_bottom), y_max: Twips::from_pixels(movie_height + margin_bottom),
valid: true,
} }
}; };
@ -763,7 +761,7 @@ impl<'gc> TDisplayObject<'gc> for Stage<'gc> {
u16::MAX u16::MAX
} }
fn self_bounds(&self) -> BoundingBox { fn self_bounds(&self) -> Rectangle<Twips> {
Default::default() Default::default()
} }

View File

@ -50,7 +50,7 @@ impl<'gc> Text<'gc> {
TextStatic { TextStatic {
swf, swf,
id: tag.id, id: tag.id,
bounds: (&tag.bounds).into(), bounds: tag.bounds.clone(),
text_transform: tag.matrix.into(), text_transform: tag.matrix.into(),
text_blocks: tag.records.clone(), text_blocks: tag.records.clone(),
}, },
@ -163,7 +163,7 @@ impl<'gc> TDisplayObject<'gc> for Text<'gc> {
context.transform_stack.pop(); context.transform_stack.pop();
} }
fn self_bounds(&self) -> BoundingBox { fn self_bounds(&self) -> Rectangle<Twips> {
self.0.read().static_data.bounds.clone() self.0.read().static_data.bounds.clone()
} }
@ -215,8 +215,7 @@ impl<'gc> TDisplayObject<'gc> for Text<'gc> {
matrix.invert(); matrix.invert();
let point = matrix * point; let point = matrix * point;
let glyph_shape = glyph.as_shape(); let glyph_shape = glyph.as_shape();
let glyph_bounds: BoundingBox = (&glyph_shape.shape_bounds).into(); if glyph_shape.shape_bounds.contains(point)
if glyph_bounds.contains(point)
&& ruffle_render::shape_utils::shape_hit_test( && ruffle_render::shape_utils::shape_hit_test(
&glyph_shape, &glyph_shape,
point, point,
@ -281,7 +280,7 @@ impl<'gc> TDisplayObject<'gc> for Text<'gc> {
struct TextStatic { struct TextStatic {
swf: Arc<SwfMovie>, swf: Arc<SwfMovie>,
id: CharacterId, id: CharacterId,
bounds: BoundingBox, bounds: Rectangle<Twips>,
text_transform: Matrix, text_transform: Matrix,
text_blocks: Vec<swf::TextRecord>, text_blocks: Vec<swf::TextRecord>,
} }

View File

@ -12,7 +12,6 @@ use crate::vminterface::{AvmObject, Instantiator};
use core::fmt; use core::fmt;
use gc_arena::{Collect, GcCell, MutationContext}; use gc_arena::{Collect, GcCell, MutationContext};
use ruffle_render::bitmap::BitmapInfo; use ruffle_render::bitmap::BitmapInfo;
use ruffle_render::bounding_box::BoundingBox;
use ruffle_render::commands::CommandHandler; use ruffle_render::commands::CommandHandler;
use ruffle_render::quality::StageQuality; use ruffle_render::quality::StageQuality;
use ruffle_video::error::Error; use ruffle_video::error::Error;
@ -425,17 +424,15 @@ impl<'gc> TDisplayObject<'gc> for Video<'gc> {
} }
} }
fn self_bounds(&self) -> BoundingBox { fn self_bounds(&self) -> Rectangle<Twips> {
let mut bounding_box = BoundingBox::default();
match (*self.0.read().source.read()).borrow() { match (*self.0.read().source.read()).borrow() {
VideoSource::Swf { streamdef, .. } => { VideoSource::Swf { streamdef, .. } => Rectangle {
bounding_box.set_width(Twips::from_pixels(streamdef.width as f64)); x_min: Twips::ZERO,
bounding_box.set_height(Twips::from_pixels(streamdef.height as f64)); 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) { fn render(&self, context: &mut RenderContext) {

View File

@ -2,18 +2,17 @@ use crate::context::RenderContext;
use gc_arena::Collect; use gc_arena::Collect;
use ruffle_render::backend::{RenderBackend, ShapeHandle}; use ruffle_render::backend::{RenderBackend, ShapeHandle};
use ruffle_render::bitmap::{BitmapHandle, BitmapInfo, BitmapSize, BitmapSource}; use ruffle_render::bitmap::{BitmapHandle, BitmapInfo, BitmapSize, BitmapSource};
use ruffle_render::bounding_box::BoundingBox;
use ruffle_render::commands::CommandHandler; use ruffle_render::commands::CommandHandler;
use ruffle_render::shape_utils::{DistilledShape, DrawCommand, DrawPath}; use ruffle_render::shape_utils::{DistilledShape, DrawCommand, DrawPath};
use std::cell::Cell; use std::cell::Cell;
use swf::{FillStyle, LineStyle, Twips}; use swf::{FillStyle, LineStyle, Rectangle, Twips};
#[derive(Clone, Debug, Collect)] #[derive(Clone, Debug, Collect)]
#[collect(require_static)] #[collect(require_static)]
pub struct Drawing { pub struct Drawing {
render_handle: Cell<Option<ShapeHandle>>, render_handle: Cell<Option<ShapeHandle>>,
shape_bounds: BoundingBox, shape_bounds: Rectangle<Twips>,
edge_bounds: BoundingBox, edge_bounds: Rectangle<Twips>,
dirty: Cell<bool>, dirty: Cell<bool>,
paths: Vec<DrawingPath>, paths: Vec<DrawingPath>,
bitmaps: Vec<BitmapInfo>, bitmaps: Vec<BitmapInfo>,
@ -34,8 +33,8 @@ impl Drawing {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
render_handle: Cell::new(None), render_handle: Cell::new(None),
shape_bounds: BoundingBox::default(), shape_bounds: Default::default(),
edge_bounds: BoundingBox::default(), edge_bounds: Default::default(),
dirty: Cell::new(false), dirty: Cell::new(false),
paths: Vec::new(), paths: Vec::new(),
bitmaps: Vec::new(), bitmaps: Vec::new(),
@ -50,8 +49,8 @@ impl Drawing {
pub fn from_swf_shape(shape: &swf::Shape) -> Self { pub fn from_swf_shape(shape: &swf::Shape) -> Self {
let mut this = Self { let mut this = Self {
render_handle: Cell::new(None), render_handle: Cell::new(None),
shape_bounds: (&shape.shape_bounds).into(), shape_bounds: shape.shape_bounds.clone(),
edge_bounds: (&shape.edge_bounds).into(), edge_bounds: shape.edge_bounds.clone(),
dirty: Cell::new(true), dirty: Cell::new(true),
paths: Vec::new(), paths: Vec::new(),
bitmaps: Vec::new(), bitmaps: Vec::new(),
@ -132,8 +131,8 @@ impl Drawing {
self.pending_lines.clear(); self.pending_lines.clear();
self.paths.clear(); self.paths.clear();
self.bitmaps.clear(); self.bitmaps.clear();
self.edge_bounds = BoundingBox::default(); self.edge_bounds = Default::default();
self.shape_bounds = BoundingBox::default(); self.shape_bounds = Default::default();
self.dirty.set(true); self.dirty.set(true);
self.cursor = (Twips::ZERO, Twips::ZERO); self.cursor = (Twips::ZERO, Twips::ZERO);
self.fill_start = (Twips::ZERO, Twips::ZERO); self.fill_start = (Twips::ZERO, Twips::ZERO);
@ -192,11 +191,11 @@ impl Drawing {
x: self.cursor.0, x: self.cursor.0,
y: self.cursor.1, y: self.cursor.1,
}; };
stretch_bounding_box(&mut self.shape_bounds, &command, stroke_width); self.shape_bounds = stretch_bounds(&self.shape_bounds, &command, stroke_width);
stretch_bounding_box(&mut self.edge_bounds, &command, Twips::ZERO); self.edge_bounds = stretch_bounds(&self.edge_bounds, &command, Twips::ZERO);
} }
stretch_bounding_box(&mut self.shape_bounds, &command, stroke_width); self.shape_bounds = stretch_bounds(&self.shape_bounds, &command, stroke_width);
stretch_bounding_box(&mut self.edge_bounds, &command, Twips::ZERO); self.edge_bounds = stretch_bounds(&self.edge_bounds, &command, Twips::ZERO);
} }
self.cursor = command.end_point(); self.cursor = command.end_point();
@ -296,8 +295,8 @@ impl Drawing {
} }
} }
pub fn self_bounds(&self) -> BoundingBox { pub fn self_bounds(&self) -> &Rectangle<Twips> {
self.shape_bounds.clone() &self.shape_bounds
} }
pub fn hit_test( pub fn hit_test(
@ -432,26 +431,24 @@ enum DrawingPath {
Line(DrawingLine), Line(DrawingLine),
} }
fn stretch_bounding_box( fn stretch_bounds(
bounding_box: &mut BoundingBox, bounds: &Rectangle<Twips>,
command: &DrawCommand, command: &DrawCommand,
stroke_width: Twips, stroke_width: Twips,
) { ) -> Rectangle<Twips> {
let radius = stroke_width / 2; let radius = stroke_width / 2;
let bounds = bounds.clone();
match *command { match *command {
DrawCommand::MoveTo { x, y } => { DrawCommand::MoveTo { x, y } => bounds
bounding_box.encompass(x - radius, y - radius); .encompass(x - radius, y - radius)
bounding_box.encompass(x + radius, y + radius); .encompass(x + radius, y + radius),
} DrawCommand::LineTo { x, y } => bounds
DrawCommand::LineTo { x, y } => { .encompass(x - radius, y - radius)
bounding_box.encompass(x - radius, y - radius); .encompass(x + radius, y + radius),
bounding_box.encompass(x + radius, y + radius); DrawCommand::CurveTo { x1, y1, x2, y2 } => bounds
} .encompass(x1 - radius, y1 - radius)
DrawCommand::CurveTo { x1, y1, x2, y2 } => { .encompass(x1 + radius, y1 + radius)
bounding_box.encompass(x1 - radius, y1 - radius); .encompass(x2 - radius, y2 - radius)
bounding_box.encompass(x1 + radius, y1 + radius); .encompass(x2 + radius, y2 + radius),
bounding_box.encompass(x2 - radius, y2 - radius);
bounding_box.encompass(x2 + radius, y2 + radius);
}
} }
} }

View File

@ -2308,7 +2308,7 @@ pub struct DragObject<'gc> {
/// The bounding rectangle where the clip will be maintained. /// The bounding rectangle where the clip will be maintained.
#[collect(require_static)] #[collect(require_static)]
pub constraint: BoundingBox, pub constraint: Rectangle<Twips>,
} }
fn run_mouse_pick<'gc>( fn run_mouse_pick<'gc>(

View File

@ -2,11 +2,10 @@ pub use crate::avm2::Value as Avm2Value;
pub use crate::display_object::{ pub use crate::display_object::{
DisplayObject, DisplayObjectContainer, HitTestOptions, TDisplayObject, TDisplayObjectContainer, DisplayObject, DisplayObjectContainer, HitTestOptions, TDisplayObject, TDisplayObjectContainer,
}; };
pub use ruffle_render::bounding_box::BoundingBox;
pub use ruffle_render::color_transform::ColorTransform; pub use ruffle_render::color_transform::ColorTransform;
pub use ruffle_render::matrix::Matrix; pub use ruffle_render::matrix::Matrix;
pub use std::ops::{Bound, RangeBounds}; 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}; pub use tracing::{error, info, trace, warn};
/// A depth for a Flash display object in AVM1. /// A depth for a Flash display object in AVM1.

View File

@ -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<swf::Rectangle<Twips>> for BoundingBox {
fn from(rect: swf::Rectangle<Twips>) -> 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<Twips>> for BoundingBox {
fn from(rect: &swf::Rectangle<Twips>) -> Self {
Self {
x_min: rect.x_min,
y_min: rect.y_min,
x_max: rect.x_max,
y_max: rect.y_max,
valid: true,
}
}
}

View File

@ -2,7 +2,6 @@
pub mod backend; pub mod backend;
pub mod bitmap; pub mod bitmap;
pub mod bounding_box;
pub mod color_transform; pub mod color_transform;
pub mod error; pub mod error;
pub mod filters; pub mod filters;

View File

@ -1,6 +1,4 @@
#![allow(clippy::suspicious_operation_groupings)] use swf::{Fixed16, Rectangle, Twips};
use swf::{Fixed16, Twips};
/// The transformation matrix used by Flash display objects. /// The transformation matrix used by Flash display objects.
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
@ -162,6 +160,27 @@ impl std::ops::Mul<(Twips, Twips)> for Matrix {
} }
} }
impl std::ops::Mul<Rectangle<Twips>> for Matrix {
type Output = Rectangle<Twips>;
fn mul(self, rhs: Rectangle<Twips>) -> 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 { impl Default for Matrix {
fn default() -> Matrix { fn default() -> Matrix {
Matrix::IDENTITY Matrix::IDENTITY

View File

@ -1,7 +1,6 @@
use crate::bounding_box::BoundingBox;
use crate::matrix::Matrix; use crate::matrix::Matrix;
use smallvec::SmallVec; 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<Twips> { pub fn calculate_shape_bounds(shape_records: &[swf::ShapeRecord]) -> swf::Rectangle<Twips> {
let mut bounds = swf::Rectangle { let mut bounds = swf::Rectangle {
@ -80,8 +79,8 @@ pub enum DrawPath<'a> {
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct DistilledShape<'a> { pub struct DistilledShape<'a> {
pub paths: Vec<DrawPath<'a>>, pub paths: Vec<DrawPath<'a>>,
pub shape_bounds: BoundingBox, pub shape_bounds: Rectangle<Twips>,
pub edge_bounds: BoundingBox, pub edge_bounds: Rectangle<Twips>,
pub id: CharacterId, pub id: CharacterId,
} }
@ -89,8 +88,8 @@ impl<'a> From<&'a swf::Shape> for DistilledShape<'a> {
fn from(shape: &'a Shape) -> Self { fn from(shape: &'a Shape) -> Self {
Self { Self {
paths: ShapeConverter::from_shape(shape).into_commands(), paths: ShapeConverter::from_shape(shape).into_commands(),
shape_bounds: (&shape.shape_bounds).into(), shape_bounds: shape.shape_bounds.clone(),
edge_bounds: (&shape.edge_bounds).into(), edge_bounds: shape.edge_bounds.clone(),
id: shape.id, id: shape.id,
} }
} }

View File

@ -2763,7 +2763,15 @@ pub mod tests {
let buf = [0b00000_000]; let buf = [0b00000_000];
let mut reader = Reader::new(&buf[..], 1); let mut reader = Reader::new(&buf[..], 1);
let rectangle = reader.read_rectangle().unwrap(); 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] #[test]

View File

@ -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<Output = Self> {} pub trait Coordinate: Copy + Ord + Add<Output = Self> + Sub<Output = Self> {
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. /// 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<T: Coordinate> { pub struct Rectangle<T: Coordinate> {
/// The minimum x-position of the rectangle. /// The minimum x-position of the rectangle.
pub x_min: T, pub x_min: T,
@ -21,11 +27,102 @@ pub struct Rectangle<T: Coordinate> {
} }
impl<T: Coordinate> Rectangle<T> { impl<T: Coordinate> Rectangle<T> {
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 { pub fn width(&self) -> T {
self.x_max - self.x_min 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 { pub fn height(&self) -> T {
self.y_max - self.y_min 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<T: Coordinate> Default for Rectangle<T> {
fn default() -> Self {
Self::INVALID
}
} }

View File

@ -2594,18 +2594,23 @@ mod tests {
#[test] #[test]
fn write_rectangle_zero() { fn write_rectangle_zero() {
let rect: Rectangle<Twips> = 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 buf = Vec::new();
{ {
let mut writer = Writer::new(&mut buf, 1); let mut writer = Writer::new(&mut buf, 1);
writer.write_rectangle(&rect).unwrap(); writer.write_rectangle(&rectangle).unwrap();
} }
assert_eq!(buf, [0]); assert_eq!(buf, [0]);
} }
#[test] #[test]
fn write_rectangle_signed() { fn write_rectangle_signed() {
let rect = Rectangle { let rectangle = Rectangle {
x_min: Twips::from_pixels(-1.0), x_min: Twips::from_pixels(-1.0),
x_max: Twips::from_pixels(1.0), x_max: Twips::from_pixels(1.0),
y_min: 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 buf = Vec::new();
{ {
let mut writer = Writer::new(&mut buf, 1); 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]); assert_eq!(buf, [0b_00110_101, 0b100_01010, 0b0_101100_0, 0b_10100_000]);
} }