diff --git a/core/src/avm1/object/stage_object.rs b/core/src/avm1/object/stage_object.rs index 85b7144f3..69bd5e85f 100644 --- a/core/src/avm1/object/stage_object.rs +++ b/core/src/avm1/object/stage_object.rs @@ -625,7 +625,7 @@ fn x_scale<'gc>( activation: &mut Activation<'_, 'gc, '_>, this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { - let val = this.scale_x(activation.context.gc_context) * 100.0; + let val: f64 = this.scale_x(activation.context.gc_context).into(); Ok(val.into()) } @@ -635,7 +635,7 @@ fn set_x_scale<'gc>( val: Value<'gc>, ) -> Result<(), Error<'gc>> { if let Some(val) = property_coerce_to_number(activation, val)? { - this.set_scale_x(activation.context.gc_context, val / 100.0); + this.set_scale_x(activation.context.gc_context, val.into()); } Ok(()) } @@ -644,7 +644,7 @@ fn y_scale<'gc>( activation: &mut Activation<'_, 'gc, '_>, this: DisplayObject<'gc>, ) -> Result, Error<'gc>> { - let scale_y = this.scale_y(activation.context.gc_context) * 100.0; + let scale_y: f64 = this.scale_y(activation.context.gc_context).into(); Ok(scale_y.into()) } @@ -654,7 +654,7 @@ fn set_y_scale<'gc>( val: Value<'gc>, ) -> Result<(), Error<'gc>> { if let Some(val) = property_coerce_to_number(activation, val)? { - this.set_scale_y(activation.context.gc_context, val / 100.0); + this.set_scale_y(activation.context.gc_context, val.into()); } Ok(()) } diff --git a/core/src/display_object.rs b/core/src/display_object.rs index e4fb6538b..ef6e5f716 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -1,5 +1,6 @@ use crate::avm1::{Object, TObject, Value}; use crate::context::{RenderContext, UpdateContext}; +use crate::percentage::Percent; use crate::player::NEWEST_PLAYER_VERSION; use crate::prelude::*; use crate::tag_utils::SwfMovie; @@ -40,11 +41,15 @@ pub struct DisplayObjectBase<'gc> { clip_depth: Depth, // Cached transform properties `_xscale`, `_yscale`, `_rotation`. - // These are expensive to calculate, so they will be calculated and cached when AS requests - // one of these properties. + // These are expensive to calculate, so they will be calculated and cached + // when AS requests one of these properties. + // + // `_xscale` and `_yscale` are stored in units of percentages to avoid + // floating-point precision errors with movies that accumulate onto the + // scale parameters. rotation: f64, - scale_x: f64, - scale_y: f64, + scale_x: Percent, + scale_y: Percent, skew: f64, /// The first child of this display object in order of execution. @@ -71,8 +76,8 @@ impl<'gc> Default for DisplayObjectBase<'gc> { name: Default::default(), clip_depth: Default::default(), rotation: 0.0, - scale_x: 1.0, - scale_y: 1.0, + scale_x: Percent::from_unit(1.0), + scale_y: Percent::from_unit(1.0), skew: 0.0, first_child: None, prev_sibling: None, @@ -187,8 +192,8 @@ impl<'gc> DisplayObjectBase<'gc> { let scale_x = f32::sqrt(a * a + b * b); let scale_y = f32::sqrt(c * c + d * d); self.rotation = rotation_x.into(); - self.scale_x = scale_x.into(); - self.scale_y = scale_y.into(); + self.scale_x = Percent::from_unit(scale_x.into()); + self.scale_y = Percent::from_unit(scale_y.into()); self.skew = (rotation_y - rotation_x).into(); self.flags.insert(DisplayObjectFlags::ScaleRotationCached); } @@ -200,8 +205,8 @@ impl<'gc> DisplayObjectBase<'gc> { let rotation = rotation.to_radians(); let cos_x = f32::cos(rotation); let sin_x = f32::sin(rotation); - self.scale_x = scale_x.into(); - self.scale_y = scale_y.into(); + self.scale_x = Percent::from_unit(scale_x.into()); + self.scale_y = Percent::from_unit(scale_y.into()); self.rotation = rotation.into(); matrix.a = (scale_x * cos_x) as f32; matrix.b = (scale_x * sin_x) as f32; @@ -222,38 +227,38 @@ impl<'gc> DisplayObjectBase<'gc> { let cos_y = f64::cos(radians + self.skew); let sin_y = f64::sin(radians + self.skew); let mut matrix = &mut self.transform.matrix; - matrix.a = (self.scale_x * cos_x) as f32; - matrix.b = (self.scale_x * sin_x) as f32; - matrix.c = (self.scale_y * -sin_y) as f32; - matrix.d = (self.scale_y * cos_y) as f32; + matrix.a = (self.scale_x.into_unit() * cos_x) as f32; + matrix.b = (self.scale_x.into_unit() * sin_x) as f32; + matrix.c = (self.scale_y.into_unit() * -sin_y) as f32; + matrix.d = (self.scale_y.into_unit() * cos_y) as f32; } - fn scale_x(&mut self) -> f64 { + fn scale_x(&mut self) -> Percent { self.cache_scale_rotation(); self.scale_x } - fn set_scale_x(&mut self, value: f64) { + fn set_scale_x(&mut self, value: Percent) { self.set_transformed_by_script(true); self.cache_scale_rotation(); self.scale_x = value; let cos = f64::cos(self.rotation); let sin = f64::sin(self.rotation); let mut matrix = &mut self.transform.matrix; - matrix.a = (cos * value) as f32; - matrix.b = (sin * value) as f32; + matrix.a = (cos * value.into_unit()) as f32; + matrix.b = (sin * value.into_unit()) as f32; } - fn scale_y(&mut self) -> f64 { + fn scale_y(&mut self) -> Percent { self.cache_scale_rotation(); self.scale_y } - fn set_scale_y(&mut self, value: f64) { + fn set_scale_y(&mut self, value: Percent) { self.set_transformed_by_script(true); self.cache_scale_rotation(); self.scale_y = value; let cos = f64::cos(self.rotation + self.skew); let sin = f64::sin(self.rotation + self.skew); let mut matrix = &mut self.transform.matrix; - matrix.c = (-sin * value) as f32; - matrix.d = (cos * value) as f32; + matrix.c = (-sin * value.into_unit()) as f32; + matrix.d = (cos * value.into_unit()) as f32; } fn name(&self) -> &str { @@ -499,22 +504,22 @@ pub trait TDisplayObject<'gc>: /// The X axis scale for this display object in local space. /// The normal scale is 1. /// Returned by the `_xscale`/`scaleX` ActionScript properties. - fn scale_x(&self, gc_context: MutationContext<'gc, '_>) -> f64; + fn scale_x(&self, gc_context: MutationContext<'gc, '_>) -> Percent; /// Sets the scale of the X axis for this display object in local space. /// The normal scale is 1. /// Set by the `_xscale`/`scaleX` ActionScript properties. - fn set_scale_x(&self, gc_context: MutationContext<'gc, '_>, value: f64); + fn set_scale_x(&self, gc_context: MutationContext<'gc, '_>, value: Percent); /// The Y axis scale for this display object in local space. /// The normal scale is 1. /// Returned by the `_yscale`/`scaleY` ActionScript properties. - fn scale_y(&self, gc_context: MutationContext<'gc, '_>) -> f64; + fn scale_y(&self, gc_context: MutationContext<'gc, '_>) -> Percent; /// Sets the Y axis scale for this display object in local space. /// The normal scale is 1. /// Returned by the `_yscale`/`scaleY` ActionScript properties. - fn set_scale_y(&self, gc_context: MutationContext<'gc, '_>, value: f64); + fn set_scale_y(&self, gc_context: MutationContext<'gc, '_>, value: Percent); /// Sets the pixel width of this display object in local space. /// The width is based on the AABB of the object. @@ -544,8 +549,8 @@ pub trait TDisplayObject<'gc>: // It has to do with the length of the sides A, B of an AABB enclosing the object's OBB with sides a, b: // A = sin(t) * a + cos(t) * b // B = cos(t) * a + sin(t) * b - let prev_scale_x = self.scale_x(gc_context); - let prev_scale_y = self.scale_y(gc_context); + let prev_scale_x = self.scale_x(gc_context).into_unit(); + let prev_scale_y = self.scale_y(gc_context).into_unit(); let rotation = self.rotation(gc_context); let cos = f64::abs(f64::cos(rotation)); let sin = f64::abs(f64::sin(rotation)); @@ -553,8 +558,8 @@ pub trait TDisplayObject<'gc>: / ((cos + aspect_ratio * sin) * (aspect_ratio * cos + sin)); let new_scale_y = (sin * prev_scale_x + aspect_ratio * cos * prev_scale_y) / (aspect_ratio * cos + sin); - self.set_scale_x(gc_context, new_scale_x); - self.set_scale_y(gc_context, new_scale_y); + self.set_scale_x(gc_context, Percent::from_unit(new_scale_x)); + self.set_scale_y(gc_context, Percent::from_unit(new_scale_y)); } /// Gets the pixel height of the AABB containing this display object in local space. /// Returned by the ActionScript `_height`/`height` properties. @@ -581,8 +586,8 @@ pub trait TDisplayObject<'gc>: // It has to do with the length of the sides A, B of an AABB enclosing the object's OBB with sides a, b: // A = sin(t) * a + cos(t) * b // B = cos(t) * a + sin(t) * b - let prev_scale_x = self.scale_x(gc_context); - let prev_scale_y = self.scale_y(gc_context); + let prev_scale_x = self.scale_x(gc_context).into_unit(); + let prev_scale_y = self.scale_y(gc_context).into_unit(); let rotation = self.rotation(gc_context); let cos = f64::abs(f64::cos(rotation)); let sin = f64::abs(f64::sin(rotation)); @@ -590,8 +595,8 @@ pub trait TDisplayObject<'gc>: (aspect_ratio * cos * prev_scale_x + sin * prev_scale_y) / (aspect_ratio * cos + sin); let new_scale_y = aspect_ratio * (sin * target_scale_x + cos * target_scale_y) / ((cos + aspect_ratio * sin) * (aspect_ratio * cos + sin)); - self.set_scale_x(gc_context, new_scale_x); - self.set_scale_y(gc_context, new_scale_y); + self.set_scale_x(gc_context, Percent::from_unit(new_scale_x)); + self.set_scale_y(gc_context, Percent::from_unit(new_scale_y)); } /// The opacity of this display object. /// 1 is fully opaque. @@ -996,16 +1001,16 @@ macro_rules! impl_display_object_sansbounds { fn set_rotation(&self, gc_context: gc_arena::MutationContext<'gc, '_>, radians: f64) { self.0.write(gc_context).$field.set_rotation(radians) } - fn scale_x(&self, gc_context: gc_arena::MutationContext<'gc, '_>) -> f64 { + fn scale_x(&self, gc_context: gc_arena::MutationContext<'gc, '_>) -> Percent { self.0.write(gc_context).$field.scale_x() } - fn set_scale_x(&self, gc_context: gc_arena::MutationContext<'gc, '_>, value: f64) { + fn set_scale_x(&self, gc_context: gc_arena::MutationContext<'gc, '_>, value: Percent) { self.0.write(gc_context).$field.set_scale_x(value) } - fn scale_y(&self, gc_context: gc_arena::MutationContext<'gc, '_>) -> f64 { + fn scale_y(&self, gc_context: gc_arena::MutationContext<'gc, '_>) -> Percent { self.0.write(gc_context).$field.scale_y() } - fn set_scale_y(&self, gc_context: gc_arena::MutationContext<'gc, '_>, value: f64) { + fn set_scale_y(&self, gc_context: gc_arena::MutationContext<'gc, '_>, value: Percent) { self.0.write(gc_context).$field.set_scale_y(value) } fn alpha(&self) -> f64 { diff --git a/core/src/display_object/bitmap.rs b/core/src/display_object/bitmap.rs index c62805611..7bf53156c 100644 --- a/core/src/display_object/bitmap.rs +++ b/core/src/display_object/bitmap.rs @@ -3,6 +3,7 @@ use crate::backend::render::BitmapHandle; use crate::context::{RenderContext, UpdateContext}; use crate::display_object::{DisplayObjectBase, TDisplayObject}; +use crate::percentage::Percent; use crate::prelude::*; use gc_arena::{Collect, Gc, GcCell}; diff --git a/core/src/display_object/button.rs b/core/src/display_object/button.rs index 134f7a1c1..ccd1b40ef 100644 --- a/core/src/display_object/button.rs +++ b/core/src/display_object/button.rs @@ -2,6 +2,7 @@ use crate::avm1::{Object, StageObject, Value}; use crate::context::{ActionType, RenderContext, UpdateContext}; use crate::display_object::{DisplayObjectBase, TDisplayObject}; use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult}; +use crate::percentage::Percent; use crate::prelude::*; use crate::tag_utils::{SwfMovie, SwfSlice}; use gc_arena::{Collect, GcCell, MutationContext}; diff --git a/core/src/display_object/edit_text.rs b/core/src/display_object/edit_text.rs index 118e7de7e..51d86c927 100644 --- a/core/src/display_object/edit_text.rs +++ b/core/src/display_object/edit_text.rs @@ -7,6 +7,7 @@ use crate::display_object::{DisplayObjectBase, TDisplayObject}; use crate::drawing::Drawing; use crate::font::{round_down_to_pixel, Glyph}; use crate::html::{BoxBounds, FormatSpans, LayoutBox, TextFormat}; +use crate::percentage::Percent; use crate::prelude::*; use crate::shape_utils::DrawCommand; use crate::tag_utils::SwfMovie; diff --git a/core/src/display_object/graphic.rs b/core/src/display_object/graphic.rs index 7e7655d35..7261b60ff 100644 --- a/core/src/display_object/graphic.rs +++ b/core/src/display_object/graphic.rs @@ -1,6 +1,7 @@ use crate::backend::render::ShapeHandle; use crate::context::{RenderContext, UpdateContext}; use crate::display_object::{DisplayObjectBase, TDisplayObject}; +use crate::percentage::Percent; use crate::prelude::*; use gc_arena::{Collect, GcCell}; diff --git a/core/src/display_object/morph_shape.rs b/core/src/display_object/morph_shape.rs index e507d9b35..303b12a99 100644 --- a/core/src/display_object/morph_shape.rs +++ b/core/src/display_object/morph_shape.rs @@ -1,6 +1,7 @@ use crate::backend::render::{RenderBackend, ShapeHandle}; use crate::context::{RenderContext, UpdateContext}; use crate::display_object::{DisplayObjectBase, TDisplayObject}; +use crate::percentage::Percent; use crate::prelude::*; use gc_arena::{Collect, Gc, GcCell, MutationContext}; use swf::Twips; diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs index 5f2d87e00..e5bde75ae 100644 --- a/core/src/display_object/movie_clip.rs +++ b/core/src/display_object/movie_clip.rs @@ -11,6 +11,7 @@ use crate::display_object::{ use crate::drawing::Drawing; use crate::events::{ButtonKeyCode, ClipEvent, ClipEventResult}; use crate::font::Font; +use crate::percentage::Percent; use crate::prelude::*; use crate::shape_utils::DrawCommand; use crate::tag_utils::{self, DecodeResult, SwfMovie, SwfSlice, SwfStream}; diff --git a/core/src/display_object/text.rs b/core/src/display_object/text.rs index 7492db033..bae4b9c21 100644 --- a/core/src/display_object/text.rs +++ b/core/src/display_object/text.rs @@ -1,5 +1,6 @@ use crate::context::{RenderContext, UpdateContext}; use crate::display_object::{DisplayObjectBase, TDisplayObject}; +use crate::percentage::Percent; use crate::prelude::*; use crate::tag_utils::SwfMovie; use crate::transform::Transform; diff --git a/core/src/lib.rs b/core/src/lib.rs index 1e8f0d098..845230095 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -28,6 +28,7 @@ mod font; mod html; mod library; pub mod loader; +mod percentage; mod player; mod prelude; mod property_map; diff --git a/core/src/percentage.rs b/core/src/percentage.rs new file mode 100644 index 000000000..3f1d762d9 --- /dev/null +++ b/core/src/percentage.rs @@ -0,0 +1,40 @@ +//! Percentage type + +use gc_arena::Collect; + +/// Percent units for things that need to be stored as percentages. +/// +/// Actual percentages (0-100) can be stored in here by `From` and `Into` +/// coercions. Thus, this wrapper serves as a unit marker. To convert into unit +/// ranges (0-1), use the `from_unit` and `into_unit` methods. +/// +/// No arithmetic operators are provided on percentages as most of the math +/// they are involved in should be done in unit proportions rather than +/// percentages. +#[derive(Copy, Clone, Debug, Collect, PartialEq, PartialOrd)] +#[collect(require_static)] +pub struct Percent(f64); + +impl Percent { + /// Convert a unit proportion into a percentage. + pub fn from_unit(unit: f64) -> Self { + Self(unit * 100.0) + } + + /// Convert a percentage into a unit proportion. + pub fn into_unit(self) -> f64 { + self.0 / 100.0 + } +} + +impl From for Percent { + fn from(percent: f64) -> Self { + Self(percent) + } +} + +impl Into for Percent { + fn into(self) -> f64 { + self.0 + } +}