diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs index 9f704e9cf..6b3307d61 100644 --- a/core/src/avm2/globals.rs +++ b/core/src/avm2/globals.rs @@ -103,6 +103,7 @@ pub struct SystemPrototypes<'gc> { pub xml_list: Object<'gc>, pub display_object: Object<'gc>, pub shape: Object<'gc>, + pub point: Object<'gc>, } impl<'gc> SystemPrototypes<'gc> { @@ -141,6 +142,7 @@ impl<'gc> SystemPrototypes<'gc> { xml_list: empty, display_object: empty, shape: empty, + point: empty, } } } @@ -662,6 +664,21 @@ pub fn load_player_globals<'gc>( script, )?; + // package `flash.geom` + activation + .context + .avm2 + .system_prototypes + .as_mut() + .unwrap() + .point = class( + activation, + flash::geom::point::create_class(mc), + implicit_deriver, + domain, + script, + )?; + // package `flash.media` activation .context diff --git a/core/src/avm2/globals/flash.rs b/core/src/avm2/globals/flash.rs index 2ace30441..467ff3ac2 100644 --- a/core/src/avm2/globals/flash.rs +++ b/core/src/avm2/globals/flash.rs @@ -2,6 +2,7 @@ pub mod display; pub mod events; +pub mod geom; pub mod media; pub mod system; pub mod utils; diff --git a/core/src/avm2/globals/flash/geom.rs b/core/src/avm2/globals/flash/geom.rs new file mode 100644 index 000000000..425abb1db --- /dev/null +++ b/core/src/avm2/globals/flash/geom.rs @@ -0,0 +1,3 @@ +//! `flash.geom` namespace + +pub mod point; diff --git a/core/src/avm2/globals/flash/geom/point.rs b/core/src/avm2/globals/flash/geom/point.rs new file mode 100644 index 000000000..9537304c9 --- /dev/null +++ b/core/src/avm2/globals/flash/geom/point.rs @@ -0,0 +1,399 @@ +//! `flash.geom.Point` builtin/prototype + +use crate::avm1::AvmString; +use crate::avm2::class::{Class, ClassAttributes}; +use crate::avm2::method::Method; +use crate::avm2::traits::Trait; +use crate::avm2::{Activation, Error, Namespace, Object, QName, TObject, Value}; +use gc_arena::{GcCell, MutationContext}; + +fn create_point<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + coords: (f64, f64), +) -> Result, Error> { + let proto = activation.context.avm2.prototypes().point; + let args = [Value::Number(coords.0), Value::Number(coords.1)]; + let new_point = proto.construct(activation, &args)?; + instance_init(activation, Some(new_point), &args)?; + + Ok(new_point.into()) +} + +/// Implements `flash.geom.Point`'s instance constructor. +pub fn instance_init<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + let _ = set_to(activation, this, args)?; + Ok(Value::Undefined) +} + +fn coords<'gc>( + this: &mut Object<'gc>, + activation: &mut Activation<'_, 'gc, '_>, +) -> Result<(f64, f64), Error> { + let x = this + .get_property(*this, &QName::new(Namespace::public(), "x"), activation)? + .coerce_to_number(activation)?; + let y = this + .get_property(*this, &QName::new(Namespace::public(), "y"), activation)? + .coerce_to_number(activation)?; + Ok((x, y)) +} + +fn set_coords<'gc>( + this: &mut Object<'gc>, + activation: &mut Activation<'_, 'gc, '_>, + value: (f64, f64), +) -> Result<(), Error> { + this.set_property( + *this, + &QName::new(Namespace::public(), "x"), + value.0.into(), + activation, + )?; + this.set_property( + *this, + &QName::new(Namespace::public(), "y"), + value.1.into(), + activation, + )?; + Ok(()) +} + +/// Implements `flash.geom.Point`'s class initializer. +pub fn class_init<'gc>( + _activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + Ok(Value::Undefined) +} + +/// Implements the `length` property +pub fn length<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + if let Some(mut this) = this { + let (x, y) = coords(&mut this, activation)?; + + return Ok((x * x + y * y).sqrt().into()); + } + + Ok(Value::Undefined) +} + +/// Implements `add` +pub fn add<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + if let Some(mut this) = this { + if let Some(other) = args.get(0) { + let mut other_obj = other.coerce_to_object(activation)?; + let (our_x, our_y) = coords(&mut this, activation)?; + let (their_x, their_y) = coords(&mut other_obj, activation)?; + + return create_point(activation, (our_x + their_x, our_y + their_y)); + } + } + + Ok(Value::Undefined) +} + +/// Implements `clone` +pub fn clone<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + if let Some(mut this) = this { + let (our_x, our_y) = coords(&mut this, activation)?; + + return create_point(activation, (our_x, our_y)); + } + + Ok(Value::Undefined) +} + +/// Implements `copyFrom` +pub fn copy_from<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + if let Some(mut this) = this { + if let Some(other) = args.get(0) { + let mut other_obj = other.coerce_to_object(activation)?; + let (their_x, their_y) = coords(&mut other_obj, activation)?; + + set_coords(&mut this, activation, (their_x, their_y))?; + } + } + + Ok(Value::Undefined) +} + +/// Implements `distance` +pub fn distance<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + if let Some(first) = args.get(0) { + let mut first_object = first.coerce_to_object(activation)?; + if let Some(second) = args.get(1) { + let mut second_obj = second.coerce_to_object(activation)?; + let (our_x, our_y) = coords(&mut first_object, activation)?; + let (their_x, their_y) = coords(&mut second_obj, activation)?; + + return Ok(((our_x - their_x).powf(2.0) + (our_y - their_y).powf(2.0)) + .sqrt() + .into()); + } + } + + Ok(Value::Undefined) +} + +/// Implements `equals` +#[allow(clippy::float_cmp)] +pub fn equals<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + if let Some(mut this) = this { + if let Some(other) = args.get(0) { + let mut other_obj = other.coerce_to_object(activation)?; + + let (our_x, our_y) = coords(&mut this, activation)?; + let (their_x, their_y) = coords(&mut other_obj, activation)?; + + return Ok((our_x == their_x && our_y == their_y).into()); + } + } + + Ok(Value::Undefined) +} + +/// Implements `interpolate` +pub fn interpolate<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + if args.len() < 3 { + return create_point(activation, (f64::NAN, f64::NAN)); + } + + let (a_x, a_y) = coords( + &mut args.get(0).unwrap().coerce_to_object(activation)?, + activation, + )?; + let (b_x, b_y) = coords( + &mut args.get(1).unwrap().coerce_to_object(activation)?, + activation, + )?; + let f = args.get(2).unwrap().coerce_to_number(activation)?; + + let result = (b_x - (b_x - a_x) * f, b_y - (b_y - a_y) * f); + create_point(activation, result) +} + +/// Implements `normalize` +pub fn normalize<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + if let Some(mut this) = this { + let thickness = args + .get(0) + .unwrap_or(&0.into()) + .coerce_to_number(activation)?; + + let length = length(activation, Some(this), args)?.coerce_to_number(activation)?; + + if length > 0.0 { + let inv_d = thickness / length; + + let (old_x, old_y) = coords(&mut this, activation)?; + set_coords(&mut this, activation, (old_x * inv_d, old_y * inv_d))?; + } + } + + Ok(Value::Undefined) +} + +/// Implements `offset` +pub fn offset<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + if let Some(mut this) = this { + let (x, y) = coords(&mut this, activation)?; + + let dx = args + .get(0) + .unwrap_or(&0.into()) + .coerce_to_number(activation)?; + let dy = args + .get(1) + .unwrap_or(&0.into()) + .coerce_to_number(activation)?; + + set_coords(&mut this, activation, (x + dx, y + dy))?; + } + + Ok(Value::Undefined) +} + +/// Implements `polar` +pub fn polar<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + _this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + let length = args + .get(0) + .unwrap_or(&Value::Undefined) + .coerce_to_number(activation)?; + let angle = args + .get(1) + .unwrap_or(&Value::Undefined) + .coerce_to_number(activation)?; + + create_point(activation, (length * angle.cos(), length * angle.sin())) +} + +/// Implements `setTo` +pub fn set_to<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + if let Some(mut this) = this { + let x = args + .get(0) + .unwrap_or(&0.into()) + .coerce_to_number(activation)?; + let y = args + .get(1) + .unwrap_or(&0.into()) + .coerce_to_number(activation)?; + + set_coords(&mut this, activation, (x, y))?; + } + + Ok(Value::Undefined) +} + +/// Implements `subtract` +pub fn subtract<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + args: &[Value<'gc>], +) -> Result, Error> { + if let Some(mut this) = this { + if let Some(other) = args.get(0) { + let mut other_obj = other.coerce_to_object(activation)?; + let (our_x, our_y) = coords(&mut this, activation)?; + let (their_x, their_y) = coords(&mut other_obj, activation)?; + + return create_point(activation, (our_x - their_x, our_y - their_y)); + } + } + + Ok(Value::Undefined) +} + +/// Implements `toString` +pub fn to_string<'gc>( + activation: &mut Activation<'_, 'gc, '_>, + this: Option>, + _args: &[Value<'gc>], +) -> Result, Error> { + if let Some(mut this) = this { + let (x, y) = coords(&mut this, activation)?; + return Ok( + AvmString::new(activation.context.gc_context, format!("(x={}, y={})", x, y)).into(), + ); + } + + Ok(Value::Undefined) +} + +/// Construct `Point`'s class. +pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>> { + let class = Class::new( + QName::new(Namespace::package("flash.geom"), "Point"), + Some(QName::new(Namespace::public(), "Object").into()), + Method::from_builtin(instance_init), + Method::from_builtin(class_init), + mc, + ); + + let mut write = class.write(mc); + write.set_attributes(ClassAttributes::SEALED); + + write.define_instance_trait(Trait::from_getter( + QName::new(Namespace::public(), "length"), + Method::from_builtin(length), + )); + write.define_instance_trait(Trait::from_method( + QName::new(Namespace::public(), "add"), + Method::from_builtin(add), + )); + write.define_instance_trait(Trait::from_method( + QName::new(Namespace::public(), "clone"), + Method::from_builtin(clone), + )); + write.define_instance_trait(Trait::from_method( + QName::new(Namespace::public(), "copyFrom"), + Method::from_builtin(copy_from), + )); + write.define_class_trait(Trait::from_method( + QName::new(Namespace::public(), "distance"), + Method::from_builtin(distance), + )); + write.define_instance_trait(Trait::from_method( + QName::new(Namespace::public(), "equals"), + Method::from_builtin(equals), + )); + write.define_class_trait(Trait::from_method( + QName::new(Namespace::public(), "interpolate"), + Method::from_builtin(interpolate), + )); + write.define_instance_trait(Trait::from_method( + QName::new(Namespace::public(), "normalize"), + Method::from_builtin(normalize), + )); + write.define_instance_trait(Trait::from_method( + QName::new(Namespace::public(), "offset"), + Method::from_builtin(offset), + )); + write.define_class_trait(Trait::from_method( + QName::new(Namespace::public(), "polar"), + Method::from_builtin(polar), + )); + write.define_instance_trait(Trait::from_method( + QName::new(Namespace::public(), "setTo"), + Method::from_builtin(set_to), + )); + write.define_instance_trait(Trait::from_method( + QName::new(Namespace::public(), "subtract"), + Method::from_builtin(subtract), + )); + write.define_instance_trait(Trait::from_method( + QName::new(Namespace::public(), "toString"), + Method::from_builtin(to_string), + )); + + class +} diff --git a/core/tests/regression_tests.rs b/core/tests/regression_tests.rs index c3edf1482..803d7458a 100644 --- a/core/tests/regression_tests.rs +++ b/core/tests/regression_tests.rs @@ -532,6 +532,7 @@ swf_tests! { (as3_regexp_constr, "avm2/regexp_constr", 1), (as3_regexp_test, "avm2/regexp_test", 1), (as3_regexp_exec, "avm2/regexp_exec", 1), + (as3_point, "avm2/point", 1), } // TODO: These tests have some inaccuracies currently, so we use approx_eq to test that numeric values are close enough. diff --git a/core/tests/swfs/avm2/point/Test.as b/core/tests/swfs/avm2/point/Test.as new file mode 100644 index 000000000..a44e20273 --- /dev/null +++ b/core/tests/swfs/avm2/point/Test.as @@ -0,0 +1,210 @@ +package { + public class Test { + } +} + +import flash.geom.Point; + +trace("/// Constructors"); +trace("// new Point()"); +trace(new Point()); +trace(""); + +trace("// new Point(1)"); +trace(new Point(1)); +trace(""); + +trace("// new Point(1, 2)"); +trace(new Point(1, 2)); +trace(""); + +trace("// new Point({}, 2)"); +var temp = {}; +trace(new Point(temp, 2)); +trace(""); +trace(""); + +trace("/// Add"); + +var point2 = new Point(); +trace("// point.add(new Point(1, 2))"); +trace(point2.add(new Point(1, 2))); +trace(""); + +trace("// point"); +trace(point2); +trace(""); +trace(""); + +trace("/// Subtract"); + +var point3 = new Point(); +trace("// point.subtract(new Point(1, 2))"); +trace(point3.subtract(new Point(1, 2))); +trace(""); + +trace("// point"); +trace(point3); +trace(""); +trace(""); + +trace("/// Distance"); + +trace("// Point.distance(new Point(), new Point())"); +trace(Point.distance(new Point(), new Point())); +trace(""); + +trace("// Point.distance(new Point(-100, 200), new Point(100, 200))"); +trace(Point.distance(new Point(-100, 200), new Point(100, 200))); +trace(""); + +trace("/// Equals"); + +var point4 = new Point(); +trace("// point.equals(new Point(1, 2))"); +trace(point4.equals(new Point(1, 2))); +trace(""); + + +trace("// point.equals(point)"); +trace(point4.equals(point4)); +trace(""); + +trace("// point"); +trace(point4); +trace(""); +trace(""); + + +trace("/// Clone"); + +var point5 = new Point(1, 2); +var clone = point5.clone(); +trace("// point"); +trace(point5); +trace(""); + +trace("// clone"); +trace(clone); +trace(""); + +trace("// point === clone"); +trace(point5 === clone); +trace(""); + +trace("// point.equals(clone)"); +trace(point5.equals(clone)); +trace(""); +trace(""); + +trace("/// Interpolate"); +trace("// Point.interpolate(new Point(-100, -200), new Point(100, 200), -1)"); +trace(Point.interpolate(new Point(-100, -200), new Point(100, 200), -1)); +trace(""); + +trace("// Point.interpolate(new Point(-100, -200), new Point(100, 200), 0)"); +trace(Point.interpolate(new Point(-100, -200), new Point(100, 200), 0)); +trace(""); + +trace("// Point.interpolate(new Point(-100, -200), new Point(100, 200), 0.5)"); +trace(Point.interpolate(new Point(-100, -200), new Point(100, 200), 0.5)); +trace(""); + +trace("// Point.interpolate(new Point(-100, -200), new Point(100, 200), 1)"); +trace(Point.interpolate(new Point(-100, -200), new Point(100, 200), 1)); +trace(""); + +trace("// Point.interpolate(new Point(-100, -200), new Point(100, 200), 2)"); +trace(Point.interpolate(new Point(-100, -200), new Point(100, 200), 2)); +trace(""); + +trace("/// length"); +trace("new Point().length"); +trace(new Point().length); +trace(""); + +trace("new Point(100, 0).length"); +trace(new Point(100, 0).length); +trace(""); + +trace("new Point(0, -200).length"); +trace(new Point(0, -200).length); +trace(""); +trace(""); + +trace("/// Normalize"); +trace("// new Point() normalize(10)"); +var point6 = new Point(); +point6.normalize(10); +trace(point6); +trace(""); + +trace("// new Point() normalize(-5)"); +var point7 = new Point(); +point7.normalize(-5); +trace(point7); +trace(""); + +trace("// new Point(100, 200) normalize(10)"); +var point8 = new Point(100, 200); +point8.normalize(10); +trace(point8); +trace(""); + +trace("// new Point(100, 200) normalize(-5)"); +var point9 = new Point(100, 200); +point9.normalize(-5); +trace(point9); +trace(""); + +trace("// new Point(-200, 100) normalize(10)"); +var point10 = new Point(-200, 100); +point10.normalize(10); +trace(point10); +trace(""); + +trace("// new Point(-200, 100) normalize(-5)"); +var point11 = new Point(-200, 100); +point11.normalize(-5); +trace(point11); +trace(""); + +trace("// new Point(undefined, 100) normalize(1)"); +var point14 = new Point(undefined, 100); +point14.normalize(1); +trace(point14); +trace(""); +trace(""); + +trace("// new Point(100, null) normalize(1)"); +var point15 = new Point(100, null); +point15.normalize(1); +trace(point15); +trace(""); +trace(""); + +trace("/// Offset"); +var point16 = new Point(); +trace("// point = new Point()"); +trace(point16); +trace(""); + +point16.offset(100, 200); +trace("// point.offset(100, 200)"); +trace(point16); +trace(""); + +point16.offset(-1000, -2000); +trace("// point.offset(-1000, -2000)"); +trace(point16); +trace(""); + + +trace("/// polar"); +trace("// Point.polar(5, Math.atan(3/4))"); +trace(Point.polar(5, Math.atan(3/4))); +trace(""); + +trace("// Point.polar(0, Math.atan(3/4))"); +trace(Point.polar(0, Math.atan(3/4))); +trace(""); diff --git a/core/tests/swfs/avm2/point/output.txt b/core/tests/swfs/avm2/point/output.txt new file mode 100644 index 000000000..54c8c8881 --- /dev/null +++ b/core/tests/swfs/avm2/point/output.txt @@ -0,0 +1,133 @@ +/// Constructors +// new Point() +(x=0, y=0) + +// new Point(1) +(x=1, y=0) + +// new Point(1, 2) +(x=1, y=2) + +// new Point({}, 2) +(x=NaN, y=2) + + +/// Add +// point.add(new Point(1, 2)) +(x=1, y=2) + +// point +(x=0, y=0) + + +/// Subtract +// point.subtract(new Point(1, 2)) +(x=-1, y=-2) + +// point +(x=0, y=0) + + +/// Distance +// Point.distance(new Point(), new Point()) +0 + +// Point.distance(new Point(-100, 200), new Point(100, 200)) +200 + +/// Equals +// point.equals(new Point(1, 2)) +false + +// point.equals(point) +true + +// point +(x=0, y=0) + + +/// Clone +// point +(x=1, y=2) + +// clone +(x=1, y=2) + +// point === clone +false + +// point.equals(clone) +true + + +/// Interpolate +// Point.interpolate(new Point(-100, -200), new Point(100, 200), -1) +(x=300, y=600) + +// Point.interpolate(new Point(-100, -200), new Point(100, 200), 0) +(x=100, y=200) + +// Point.interpolate(new Point(-100, -200), new Point(100, 200), 0.5) +(x=0, y=0) + +// Point.interpolate(new Point(-100, -200), new Point(100, 200), 1) +(x=-100, y=-200) + +// Point.interpolate(new Point(-100, -200), new Point(100, 200), 2) +(x=-300, y=-600) + +/// length +new Point().length +0 + +new Point(100, 0).length +100 + +new Point(0, -200).length +200 + + +/// Normalize +// new Point() normalize(10) +(x=0, y=0) + +// new Point() normalize(-5) +(x=0, y=0) + +// new Point(100, 200) normalize(10) +(x=4.47213595499958, y=8.94427190999916) + +// new Point(100, 200) normalize(-5) +(x=-2.23606797749979, y=-4.47213595499958) + +// new Point(-200, 100) normalize(10) +(x=-8.94427190999916, y=4.47213595499958) + +// new Point(-200, 100) normalize(-5) +(x=4.47213595499958, y=-2.23606797749979) + +// new Point(undefined, 100) normalize(1) +(x=NaN, y=100) + + +// new Point(100, null) normalize(1) +(x=1, y=0) + + +/// Offset +// point = new Point() +(x=0, y=0) + +// point.offset(100, 200) +(x=100, y=200) + +// point.offset(-1000, -2000) +(x=-900, y=-1800) + +/// polar +// Point.polar(5, Math.atan(3/4)) +(x=4, y=3) + +// Point.polar(0, Math.atan(3/4)) +(x=0, y=0) + diff --git a/core/tests/swfs/avm2/point/test.fla b/core/tests/swfs/avm2/point/test.fla new file mode 100644 index 000000000..7a2d53915 Binary files /dev/null and b/core/tests/swfs/avm2/point/test.fla differ diff --git a/core/tests/swfs/avm2/point/test.swf b/core/tests/swfs/avm2/point/test.swf new file mode 100644 index 000000000..d3347d3ca Binary files /dev/null and b/core/tests/swfs/avm2/point/test.swf differ