From 7f90b7430cf03ed1c5a9be296b526452f4c942e8 Mon Sep 17 00:00:00 2001 From: kcz Date: Thu, 5 Dec 2024 20:20:51 -0500 Subject: [PATCH 1/5] avm2: Implement Matrix3D with 2D support only Replace stubs for flash.geom.Transform.matrix3D getter/setter with an actual implementation of Matrix3D with limited support. This implementation is just a proxy to the existing 2D matrix implementation. Therefore transformations beyond 2D transformation works differently from the expected result. --- core/src/avm2/globals/flash/geom/transform.rs | 91 +++++++++++++++---- render/src/lib.rs | 1 + render/src/matrix3d.rs | 50 ++++++++++ 3 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 render/src/matrix3d.rs diff --git a/core/src/avm2/globals/flash/geom/transform.rs b/core/src/avm2/globals/flash/geom/transform.rs index 99139599aac7..2cd6b3ecac9e 100644 --- a/core/src/avm2/globals/flash/geom/transform.rs +++ b/core/src/avm2/globals/flash/geom/transform.rs @@ -6,6 +6,7 @@ use crate::avm2::{Activation, Error, Object, TObject, Value}; use crate::display_object::TDisplayObject; use crate::prelude::{DisplayObject, Matrix, Twips}; use crate::{avm2_stub_getter, avm2_stub_setter}; +use ruffle_render::matrix3d::Matrix3D; use ruffle_render::quality::StageQuality; use swf::{ColorTransform, Fixed8, Rectangle}; @@ -48,8 +49,12 @@ pub fn get_matrix<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - let matrix = matrix_from_transform_object(this); - matrix_to_object(matrix, activation) + if get_display_object(this).base().has_matrix3d_stub() { + Ok(Value::Null) + } else { + let matrix = matrix_from_transform_object(this); + matrix_to_object(matrix, activation) + } } pub fn set_matrix<'gc>( @@ -57,17 +62,20 @@ pub fn set_matrix<'gc>( this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { - // TODO: Despite what the docs say, FP accepts a null matrix here, and returns - // null when trying to get the matrix- but the DO's actual transform matrix will - // remain its previous non-null value. - let matrix = object_to_matrix(args.get_object(activation, 0, "value")?, activation)?; let dobj = get_display_object(this); + let Some(obj) = args.try_get_object(activation, 0) else { + dobj.base_mut(activation.gc()).set_has_matrix3d_stub(true); + return Ok(Value::Undefined); + }; + + let matrix = object_to_matrix(obj, activation)?; dobj.set_matrix(activation.context.gc_context, matrix); if let Some(parent) = dobj.parent() { // Self-transform changes are automatically handled, // we only want to inform ancestors to avoid unnecessary invalidations for tx/ty parent.invalidate_cached_bitmap(activation.context.gc_context); } + dobj.base_mut(activation.gc()).set_has_matrix3d_stub(false); Ok(Value::Undefined) } @@ -181,6 +189,42 @@ pub fn color_transform_to_object<'gc>( Ok(object.into()) } +fn matrix3d_to_object<'gc>( + matrix: Matrix3D, + activation: &mut Activation<'_, 'gc>, +) -> Result, Error<'gc>> { + let args = matrix.raw_data.map(Into::into); + let object = activation + .avm2() + .classes() + .matrix3d + .construct(activation, &args)?; + Ok(object.into()) +} + +fn object_to_matrix3d<'gc>( + object: Object<'gc>, + activation: &mut Activation<'_, 'gc>, +) -> Result> { + let raw_data = object + .get_public_property("rawData", activation)? + .as_object() + .expect("rawData cannot be null"); + let raw_data = raw_data + .as_vector_storage() + .expect("rawData is not a Vector"); + let raw_data: Vec = (0..16) + .map(|i| -> Result> { + raw_data.get(i, activation)?.coerce_to_number(activation) + }) + .collect::, _>>()?; + let raw_data = raw_data + .as_slice() + .try_into() + .expect("rawData size must be 16"); + Ok(Matrix3D { raw_data }) +} + pub fn matrix_to_object<'gc>( matrix: Matrix, activation: &mut Activation<'_, 'gc>, @@ -261,16 +305,16 @@ pub fn get_matrix_3d<'gc>( this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { + // FIXME: This Matrix3D is generated from the 2D Matrix. + // It does not work when the matrix contains any transformation in 3D. + // Support native Matrix3D. avm2_stub_getter!(activation, "flash.geom.Transform", "matrix3D"); let display_object = get_display_object(this); if display_object.base().has_matrix3d_stub() { - let object = activation - .avm2() - .classes() - .matrix3d - .construct(activation, &[])?; - Ok(object.into()) + let matrix = *get_display_object(this).base().matrix(); + let matrix3d = Matrix3D::from(matrix); + matrix3d_to_object(matrix3d, activation) } else { Ok(Value::Null) } @@ -281,16 +325,29 @@ pub fn set_matrix_3d<'gc>( this: Object<'gc>, args: &[Value<'gc>], ) -> Result, Error<'gc>> { + // FIXME: This sets 2D Matrix generated from the given Matrix3D, ignoring 3D parameters. + // Support native Matrix3D. avm2_stub_setter!(activation, "flash.geom.Transform", "matrix3D"); - let set = args - .get(0) - .map(|arg| arg.as_object().is_some()) - .unwrap_or_default(); let display_object = get_display_object(this); + let Some(obj) = args.try_get_object(activation, 0) else { + display_object + .base_mut(activation.gc()) + .set_has_matrix3d_stub(false); + return Ok(Value::Undefined); + }; + + let matrix3d = object_to_matrix3d(obj, activation)?; + let matrix = Matrix::from(matrix3d); + display_object.set_matrix(activation.context.gc_context, matrix); + if let Some(parent) = display_object.parent() { + // Self-transform changes are automatically handled, + // we only want to inform ancestors to avoid unnecessary invalidations for tx/ty + parent.invalidate_cached_bitmap(activation.context.gc_context); + } display_object .base_mut(activation.gc()) - .set_has_matrix3d_stub(set); + .set_has_matrix3d_stub(true); Ok(Value::Undefined) } diff --git a/render/src/lib.rs b/render/src/lib.rs index e0f51091f79b..03e26a8ce486 100644 --- a/render/src/lib.rs +++ b/render/src/lib.rs @@ -8,6 +8,7 @@ pub mod error; pub mod filters; pub mod lines; pub mod matrix; +pub mod matrix3d; pub mod pixel_bender; // The `renderdoc` crate doesn't compile on apple platforms #[cfg(all(feature = "renderdoc", not(target_vendor = "apple")))] diff --git a/render/src/matrix3d.rs b/render/src/matrix3d.rs new file mode 100644 index 000000000000..862f8d742fbb --- /dev/null +++ b/render/src/matrix3d.rs @@ -0,0 +1,50 @@ +use crate::matrix::Matrix; +use swf::Twips; + +/// The transformation matrix for 3D used by Flash display objects. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Matrix3D { + /// 4x4 matrix elements. + pub raw_data: [f64; 16], +} + +impl From for Matrix3D { + fn from(matrix: Matrix) -> Self { + Self { + raw_data: [ + // 1st column + matrix.a.into(), + matrix.b.into(), + 0.0, + 0.0, + // 2nd column + matrix.c.into(), + matrix.d.into(), + 0.0, + 0.0, + // 3rd column + 0.0, + 0.0, + 1.0, + 0.0, + // 4th column + matrix.tx.to_pixels(), + matrix.ty.to_pixels(), + 0.0, + 1.0, + ], + } + } +} +impl From for Matrix { + fn from(matrix: Matrix3D) -> Self { + Self { + a: matrix.raw_data[0] as f32, + b: matrix.raw_data[1] as f32, + c: matrix.raw_data[4] as f32, + d: matrix.raw_data[5] as f32, + tx: Twips::from_pixels(matrix.raw_data[12]), + ty: Twips::from_pixels(matrix.raw_data[13]), + } + } +} From 69d86845d65bc486a1670b30d57f89cc4404765a Mon Sep 17 00:00:00 2001 From: kcz Date: Thu, 5 Dec 2024 20:25:47 -0500 Subject: [PATCH 2/5] test: Add test for flash.geom.transform matrix ops --- tests/tests/swfs/avm2/geom_transform/Test.as | 195 ++++++++++++++++++ .../avm2/geom_transform/output.expected.png | Bin 0 -> 7177 bytes .../tests/swfs/avm2/geom_transform/output.txt | 50 +++++ tests/tests/swfs/avm2/geom_transform/test.swf | Bin 0 -> 2342 bytes .../tests/swfs/avm2/geom_transform/test.toml | 7 + 5 files changed, 252 insertions(+) create mode 100644 tests/tests/swfs/avm2/geom_transform/Test.as create mode 100644 tests/tests/swfs/avm2/geom_transform/output.expected.png create mode 100644 tests/tests/swfs/avm2/geom_transform/output.txt create mode 100644 tests/tests/swfs/avm2/geom_transform/test.swf create mode 100644 tests/tests/swfs/avm2/geom_transform/test.toml diff --git a/tests/tests/swfs/avm2/geom_transform/Test.as b/tests/tests/swfs/avm2/geom_transform/Test.as new file mode 100644 index 000000000000..2806c5dc4659 --- /dev/null +++ b/tests/tests/swfs/avm2/geom_transform/Test.as @@ -0,0 +1,195 @@ +package { + import flash.display.*; + import flash.text.*; + import flash.geom.*; + + [SWF(width="500", height="500")] + public class Test extends MovieClip { + public function Test() { + super(); + + test2D(); + trace(""); + + test3D(); + trace(""); + + testCopy2D(); + trace(""); + + testCopy3D(); + trace(""); + + testImageComparison(); + } + + private function test2D() : void { + var sprite2D : Sprite = new Sprite(); + + trace("// sprite2D: new Sprite has null matrix3D and valid matrix"); + trace("sprite2D.transform.matrix", sprite2D.transform.matrix); + trace("sprite2D.transform.matrix3D", sprite2D.transform.matrix3D); + + trace("// sprite2D: set identity matrix"); + var mat2D : Matrix = new Matrix(); + mat2D.identity(); + sprite2D.transform.matrix = mat2D; + trace("sprite2D.transform.matrix", sprite2D.transform.matrix); + trace("sprite2D.transform.matrix3D", sprite2D.transform.matrix3D); + trace("mat2D", mat2D); + + trace("// sprite2D: .matrix = null"); + sprite2D.transform.matrix = null; + trace("sprite2D.transform.matrix", sprite2D.transform.matrix); + trace("sprite2D.transform.matrix3D", sprite2D.transform.matrix3D); + trace("sprite2D.transform.matrix3D.rawData", sprite2D.transform.matrix3D.rawData); + trace("mat2D", mat2D); + + trace("// sprite2D: .matrix3D = null"); + sprite2D.transform.matrix3D = null; + trace("sprite2D.transform.matrix", sprite2D.transform.matrix); + trace("sprite2D.transform.matrix3D", sprite2D.transform.matrix3D); + trace("mat2D", mat2D); + + trace("// sprite2D: set x = 30, y = 50"); + sprite2D.x = 30; + sprite2D.y = 50; + trace("sprite2D.transform.matrix", sprite2D.transform.matrix); + trace("sprite2D.transform.matrix3D", sprite2D.transform.matrix3D); + trace("mat2D", mat2D); + } + + private function test3D() : void { + var sprite3D : Sprite = new Sprite(); + + trace("// sprite3D: set identity matrix3D"); + var mat3D : Matrix3D = new Matrix3D(); + mat3D.identity(); + sprite3D.transform.matrix3D = mat3D; + trace("sprite3D.transform.matrix", sprite3D.transform.matrix); + trace("sprite3D.transform.matrix3D", sprite3D.transform.matrix3D); + trace("sprite3D.transform.matrix3D.rawData", sprite3D.transform.matrix3D.rawData); + trace("mat3D.rawData", mat3D.rawData); + + trace("// sprite3D: .matrix = null"); + sprite3D.transform.matrix = null; + trace("sprite3D.transform.matrix", sprite3D.transform.matrix); + trace("sprite3D.transform.matrix3D", sprite3D.transform.matrix3D); + trace("sprite3D.transform.matrix3D.rawData", sprite3D.transform.matrix3D.rawData); + trace("mat3D.rawData", mat3D.rawData); + + //// FIXME: matrix3D.rawData should be updated by x/y update. + // trace("// sprite3D: set x = 30, y = 50"); + // sprite3D.x = 30; + // sprite3D.y = 50; + // trace("sprite3D.transform.matrix", sprite3D.transform.matrix); + // trace("sprite3D.transform.matrix3D", sprite3D.transform.matrix3D); + // trace("sprite3D.transform.matrix3D.rawData", sprite3D.transform.matrix3D.rawData); + // trace("mat3D", mat3D); + + trace("// sprite3D: .matrix3D = null"); + sprite3D.transform.matrix3D = null; + trace("sprite3D.transform.matrix", sprite3D.transform.matrix); + trace("sprite3D.transform.matrix3D", sprite3D.transform.matrix3D); + trace("mat3D.rawData", mat3D.rawData); + } + + private function testCopy2D() : void { + var sprite1 : Sprite = new Sprite(); + var sprite2 : Sprite = new Sprite(); + + trace("// Copy2D"); + var mat2D : Matrix = new Matrix(1, 2, 3, 4, 5, 6); + sprite1.transform.matrix = mat2D; + sprite2.transform = sprite1.transform; + trace("sprite1.transform.matrix", sprite1.transform.matrix); + trace("sprite1.transform.matrix3D", sprite1.transform.matrix3D); + trace("sprite2.transform.matrix", sprite2.transform.matrix); + trace("sprite2.transform.matrix3D", sprite2.transform.matrix3D); + } + + private function testCopy3D() : void { + var sprite1 : Sprite = new Sprite(); + var sprite2 : Sprite = new Sprite(); + + trace("// Copy3D"); + var mat3D : Matrix3D = new Matrix3D(); + mat3D.appendRotation(1, Vector3D.Z_AXIS); + mat3D.appendScale(2, 3, 1); // FIXME: zScale shouldn't be one (1) for test coverage. Unsupported now. + mat3D.appendTranslation(5, 6, 0); // FIXME: z shouldn't be zero (0) for test coverage. Unsupported now. + sprite1.transform.matrix3D = mat3D; + sprite2.transform = sprite1.transform; + trace("sprite1.transform.matrix", sprite1.transform.matrix); + trace("sprite1.transform.matrix3D", sprite1.transform.matrix3D); + trace("sprite1.transform.matrix3D.rawData", sprite1.transform.matrix3D.rawData); + trace("sprite2.transform.matrix", sprite2.transform.matrix); + trace("sprite2.transform.matrix3D", sprite2.transform.matrix3D); + trace("sprite2.transform.matrix3D.rawData", sprite2.transform.matrix3D.rawData); + } + + private function testImageComparison() : void { + var m : Matrix3D = new Matrix3D(); + + // id + var s1 : Sprite = new Sprite(); + s1.x = 10; + s1.y = 10; + var bd1 : BitmapData = new BitmapData(50, 50, false, 0xFF0000); + var b1 : Bitmap = new Bitmap(bd1); + m.identity(); + b1.transform.matrix3D = m.clone(); + s1.addChild(b1); + addChild(s1); + + // scale + var s2 : Sprite = new Sprite(); + s2.x = 160; + s2.y = 10; + var bd2 : BitmapData = new BitmapData(50, 50, false, 0x00FF00); + var b2 : Bitmap = new Bitmap(bd2); + m.identity(); + m.appendScale(1.5, 3, 1); + b2.transform.matrix3D = m.clone(); + s2.addChild(b2); + addChild(s2); + + // rotation + var s3 : Sprite = new Sprite(); + s3.x = 310; + s3.y = 10; + var bd3 : BitmapData = new BitmapData(50, 50, false, 0x00FFFF); + var b3 : Bitmap = new Bitmap(bd3); + m.identity(); + m.appendRotation(30, Vector3D.Z_AXIS); + b3.transform.matrix3D = m.clone(); + s3.addChild(b3); + addChild(s3); + + // translation + var s4 : Sprite = new Sprite(); + s4.x = 10; + s4.y = 160; + var bd4 : BitmapData = new BitmapData(50, 50, false, 0x0000FF); + var b4 : Bitmap = new Bitmap(bd4); + m.identity(); + m.appendTranslation(50, 50, 0); + b4.transform.matrix3D = m.clone(); + s4.addChild(b4); + addChild(s4); + + // scale + rotation + translation + var s5 : Sprite = new Sprite(); + s5.x = 160; + s5.y = 160; + var bd5 : BitmapData = new BitmapData(50, 50, false, 0xFF00FF); + var b5 : Bitmap = new Bitmap(bd5); + m.identity(); + m.appendScale(2, 3, 1); + m.appendRotation(30, Vector3D.Z_AXIS); + m.appendTranslation(50, 50, 0); + b5.transform.matrix3D = m.clone(); + s5.addChild(b5); + addChild(s5); + } + } +} diff --git a/tests/tests/swfs/avm2/geom_transform/output.expected.png b/tests/tests/swfs/avm2/geom_transform/output.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..06a24583337195bab0eaedf91dc8b728b3c905dd GIT binary patch literal 7177 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 M51;-20?;A#p7?!<$N&HU literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/geom_transform/test.toml b/tests/tests/swfs/avm2/geom_transform/test.toml new file mode 100644 index 000000000000..8bea6327f073 --- /dev/null +++ b/tests/tests/swfs/avm2/geom_transform/test.toml @@ -0,0 +1,7 @@ +num_frames = 1 + +[image_comparisons.output] +tolerance = 0 + +[player_options] +with_renderer = { optional = true, sample_count = 1 } From 7869c5c054e02cd4b13c44dd05dd4be64ca1c3f8 Mon Sep 17 00:00:00 2001 From: kcz Date: Thu, 5 Dec 2024 20:37:15 -0500 Subject: [PATCH 3/5] avm2: Use VectorStorage in Matrix3D object construction Co-authored-by: Kamil Jarosz --- core/src/avm2/globals/flash/geom/transform.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/core/src/avm2/globals/flash/geom/transform.rs b/core/src/avm2/globals/flash/geom/transform.rs index 2cd6b3ecac9e..432b0775e255 100644 --- a/core/src/avm2/globals/flash/geom/transform.rs +++ b/core/src/avm2/globals/flash/geom/transform.rs @@ -1,7 +1,9 @@ use crate::avm2::globals::slots::flash_geom_color_transform as ct_slots; use crate::avm2::globals::slots::flash_geom_matrix as matrix_slots; use crate::avm2::globals::slots::flash_geom_transform as transform_slots; +use crate::avm2::object::VectorObject; use crate::avm2::parameters::ParametersExt; +use crate::avm2::vector::VectorStorage; use crate::avm2::{Activation, Error, Object, TObject, Value}; use crate::display_object::TDisplayObject; use crate::prelude::{DisplayObject, Matrix, Twips}; @@ -193,12 +195,17 @@ fn matrix3d_to_object<'gc>( matrix: Matrix3D, activation: &mut Activation<'_, 'gc>, ) -> Result, Error<'gc>> { - let args = matrix.raw_data.map(Into::into); + let number = activation.avm2().class_defs().number; + let mut raw_data_storage = VectorStorage::new(16, true, Some(number), activation); + for (i, data) in matrix.raw_data.iter().enumerate() { + raw_data_storage.set(i, Value::Number(*data), activation)?; + } + let vector = VectorObject::from_vector(raw_data_storage, activation)?.into(); let object = activation .avm2() .classes() .matrix3d - .construct(activation, &args)?; + .construct(activation, &[vector])?; Ok(object.into()) } From 78444cf42dd567217c1871718da444795feebd54 Mon Sep 17 00:00:00 2001 From: kcz Date: Fri, 6 Dec 2024 00:14:28 -0500 Subject: [PATCH 4/5] avm2: Support matrix3D in transform copy reference: https://docs.ruffle.rs/en_US/FlashPlatform/reference/actionscript/3/flash/display/DisplayObject.html#transform --- core/src/avm2/globals/flash/display/display_object.rs | 6 ++++-- core/src/avm2/globals/flash/geom/transform.rs | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/core/src/avm2/globals/flash/display/display_object.rs b/core/src/avm2/globals/flash/display/display_object.rs index 1c2a82bb8cfc..9fae702e7ba5 100644 --- a/core/src/avm2/globals/flash/display/display_object.rs +++ b/core/src/avm2/globals/flash/display/display_object.rs @@ -4,6 +4,7 @@ use crate::avm2::activation::Activation; use crate::avm2::error::{illegal_operation_error, make_error_2007, make_error_2008}; use crate::avm2::filters::FilterAvm2Ext; use crate::avm2::globals::flash::geom::transform::color_transform_from_transform_object; +use crate::avm2::globals::flash::geom::transform::has_matrix3d_from_transform_object; use crate::avm2::globals::flash::geom::transform::matrix_from_transform_object; use crate::avm2::globals::slots::flash_display_shader as shader_slots; use crate::avm2::globals::slots::flash_geom_point as point_slots; @@ -755,14 +756,15 @@ pub fn set_transform<'gc>( ) -> Result, Error<'gc>> { let transform = args.get_object(activation, 0, "transform")?; - // FIXME - consider 3D matrix and pixel bounds + // FIXME - consider pixel bounds let matrix = matrix_from_transform_object(transform); - + let has_matrix3d = has_matrix3d_from_transform_object(transform); let color_transform = color_transform_from_transform_object(transform); let dobj = this.as_display_object().unwrap(); let mut write = dobj.base_mut(activation.context.gc_context); write.set_matrix(matrix); + write.set_has_matrix3d_stub(has_matrix3d); write.set_color_transform(color_transform); drop(write); if let Some(parent) = dobj.parent() { diff --git a/core/src/avm2/globals/flash/geom/transform.rs b/core/src/avm2/globals/flash/geom/transform.rs index 432b0775e255..75bca98e393a 100644 --- a/core/src/avm2/globals/flash/geom/transform.rs +++ b/core/src/avm2/globals/flash/geom/transform.rs @@ -119,6 +119,12 @@ pub fn get_concatenated_matrix<'gc>( } } +pub fn has_matrix3d_from_transform_object(transform_object: Object<'_>) -> bool { + get_display_object(transform_object) + .base() + .has_matrix3d_stub() +} + pub fn matrix_from_transform_object(transform_object: Object<'_>) -> Matrix { *get_display_object(transform_object).base().matrix() } From 6417e5f1e9f9870dfdbe18a8034e0c16b78a034c Mon Sep 17 00:00:00 2001 From: kcz Date: Fri, 13 Dec 2024 18:59:15 -0500 Subject: [PATCH 5/5] avm2: Remove unused private property in Transform --- core/src/avm2/globals/flash/geom/Transform.as | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/src/avm2/globals/flash/geom/Transform.as b/core/src/avm2/globals/flash/geom/Transform.as index b305d61254fb..c574b7007c99 100644 --- a/core/src/avm2/globals/flash/geom/Transform.as +++ b/core/src/avm2/globals/flash/geom/Transform.as @@ -10,9 +10,6 @@ package flash.geom { [Ruffle(InternalSlot)] private var displayObject:DisplayObject; - private var _matrix3D:Matrix3D = null; - private var _perspectiveProjection:PerspectiveProjection = null; - function Transform(object:DisplayObject) { this.displayObject = object; }