Repository: ruffle-rs/ruffle (language: rust)

Issue:
Chirumiru: Display Bug
### Describe the bug


![image](https://user-images.githubusercontent.com/75534590/191404741-afdcfc94-5d1d-4620-a0a1-4a91a4796e6c.png)
![image](https://user-images.githubusercontent.com/75534590/191404882-68a68c38-8780-4ef8-8a33-7b801b02edce.png)
![image](https://user-images.githubusercontent.com/75534590/191404958-20f6eb74-59a8-49f6-9e3e-d4bf7f253ce0.png)
![image](https://user-images.githubusercontent.com/75534590/191404995-25e372ad-d660-47fd-b3a5-17241d8e3056.png)
![image](https://user-images.githubusercontent.com/75534590/191405023-07bee35a-6b89-4d05-84fd-d05074b21add.png)

```
[2022-09-21T02:55:36Z ERROR ruffle_core::avm2::activation] AVM2 error: Could not find script for class trait Multiname { ns: Single(Package("flash.geom")), name: Some("Matrix3D"), params: [], flags: (empty) }
[2022-09-21T02:55:36Z ERROR ruffle_core::avm2::activation] AVM2 error: Could not find script for class trait Multiname { ns: Single(Package("flash.geom")), name: Some("Matrix3D"), params: [], flags: (empty) }
[2022-09-21T02:55:36Z ERROR ruffle_core::avm2::activation] AVM2 error: Could not find script for class trait Multiname { ns: Single(Package("flash.geom")), name: Some("Matrix3D"), params: [], flags: (empty) }
[2022-09-21T02:55:36Z ERROR ruffle_core::avm2::activation] AVM2 error: Could not find script for class trait Multiname { ns: Single(Package("flash.geom")), name: Some("Matrix3D"), params: [], flags: (empty) }
[2022-09-21T02:55:36Z ERROR ruffle_core::display_object::movie_clip] Got Could not find script for class trait Multiname { ns: Single(Package("flash.geom")), name: Some("Matrix3D"), params: [], flags: (empty) } when constructing AVM2 side of movie clip of type nineball2_fla::MainTimeline
[2022-09-21T02:55:36Z ERROR ruffle_core::avm2::activation] AVM2 error: Cannot get undefined property "perspectiveProjection" on class Some(ClassObject { name: flash.geom::Transform, ptr: 0x20745267920 })
[2022-09-21T02:55:36Z ERROR ruffle_core::avm2::events] Error dispatching event EventObject(EventObject { type: "addedToStage", class: flash.events::Event, ptr: 0x207453ea2a0 }) to handler FunctionObject(FunctionObject(GcCell(Gc { ptr: 0x20745325fd8 }))) : "Cannot get undefined property \"perspectiveProjection\" on class Some(ClassObject { name: flash.geom::Transform, ptr: 0x20745267920 })"
[2022-09-21T02:56:47Z ERROR ruffle_core::avm2::activation] AVM2 error: Could not find script for class trait Multiname { ns: Single(Package("flash.geom")), name: Some("Matrix3D"), params: [], flags: (empty) }
[2022-09-21T02:56:47Z ERROR ruffle_core::avm2::activation] AVM2 error: Could not find script for class trait Multiname { ns: Single(Package("flash.geom")), name: Some("Matrix3D"), params: [], flags: (empty) }
[2022-09-21T02:56:47Z ERROR ruffle_core::avm2::activation] AVM2 error: Could not find script for class trait Multiname { ns: Single(Package("flash.geom")), name: Some("Matrix3D"), params: [], flags: (empty) }
[2022-09-21T02:56:47Z ERROR ruffle_core::avm2::activation] AVM2 error: Could not find script for class trait Multiname { ns: Single(Package("flash.geom")), name: Some("Matrix3D"), params: [], flags: (empty) }
[2022-09-21T02:56:47Z ERROR ruffle_core::display_object::movie_clip] Got Could not find script for class trait Multiname { ns: Single(Package("flash.geom")), name: Some("Matrix3D"), params: [], flags: (empty) } when constructing AVM2 side of movie clip of type nineball2_fla::A_85
```
On Nightly 2022-09-22
```
[2022-09-22T05:18:46Z ERROR ruffle_core::display_object::movie_clip] Got RustError("Could not find script for class trait Multiname { ns: Single(Package(\"flash.geom\")), name: Some(\"Matrix3D\"), params: [], flags: (empty) }") when constructing AVM2 side of movie clip of type nineball2_fla::MainTimeline
[2022-09-22T05:18:46Z ERROR ruffle_core::avm2::activation] AVM2 error: Object(ErrorObject(ErrorObject(GcCell(Gc { ptr: 0x28450ea5498 }))))
[2022-09-22T05:18:46Z ERROR ruffle_core::avm2::events] Error dispatching event EventObject(EventObject { type: "addedToStage", class: flash.events::Event, ptr: 0x28450e38e40 }) to handler FunctionObject(FunctionObject(GcCell(Gc { ptr: 0x28450a8f088 }))) : AvmError(Object(ErrorObject(ErrorObject(GcCell(Gc { ptr: 0x28450ea5498 })))))
[2022-09-22T05:19:59Z ERROR ruffle_core::display_object::movie_clip] Got RustError("Could not find script for class trait Multiname { ns: Single(Package(\"flash.geom\")), name: Some(\"Matrix3D\"), params: [], flags: (empty) }") when constructing AVM2 side of movie clip of type nineball2_fla::A_85
``` 

### Expected behavior

![image](https://user-images.githubusercontent.com/75534590/191406396-2d47a2f0-ae09-4142-9740-6aa03df0615d.png)
![image](https://user-images.githubusercontent.com/75534590/191406431-96beff44-6127-4834-99c9-0c8cdba9437d.png)
![image](https://user-images.githubusercontent.com/75534590/191406489-927b6f07-6955-41b9-a26f-81982168f1fc.png)
![image](https://user-images.githubusercontent.com/75534590/191406510-1f7c6b53-41ff-4f86-8285-00efc2dab0d3.png)
![image](https://user-images.githubusercontent.com/75534590/191406524-984ca1c7-f56a-4b94-9b77-85432f84d236.png)


### Affected platform

Desktop app

### Operating system

Windows 10

### Browser

_No response_

### Additional information

SWF:
https://web.archive.org/web/20100707002957/http://www.l-room.net/product/movie/swf/chirumiru.swf


Discussion / hints:
Strangely enough, since ruffle nightly 2023.05.08, the problematic objects doesn't display anymore at launch, but replaying at the end shows the same as before.
I believe 2023.05.08 only has https://github.com/ruffle-rs/ruffle/pull/10812.
![Screenshot_20230508_045550](https://user-images.githubusercontent.com/97262618/236724243-aa108a15-bec0-4df1-9c48-da4cb3598fab.png)
![Screenshot_20230508_045645](https://user-images.githubusercontent.com/97262618/236724268-5aa935fc-e7d4-4871-8d8c-eb72979fde53.png)
![Screenshot_20230508_045428](https://user-images.githubusercontent.com/97262618/236724288-c798bc13-56d9-4e80-a889-8131a49d88fc.png)

lots of this error :
```
2023-05-08T03:01:22.225514Z ERROR run_frame:run_all_phases_avm2: ruffle_core::avm2::events: Error dispatching event EventObject(EventObject { type: "frameConstructed", class: flash.events::Event, ptr: 0x555a11290518 }) to handler FunctionObject(FunctionObject { ptr: 0x555a0f3f9098 }) : ReferenceError: Error #1056: Cannot create property matrix3D on flash.geom.Transform.
        at fl.motion::Animator3D/setTime3D()
        at fl.motion::AnimatorBase/set time()
        at fl.motion::AnimatorBase$/processCurrentFrame()
        at fl.motion::AnimatorBase$/parentEnterFrameHandler()
2023-05-08T03:01:22.258445Z ERROR run_frame:run_all_phases_avm2: ruffle_core::avm2::events: Error dispatching event EventObject(EventObject { type: "frameConstructed", class: flash.events::Event, ptr: 0x555a11283cc8 }) to handler FunctionObject(FunctionObject { ptr: 0x555a0f3f9098 }) : ReferenceError: Error #1056: Cannot create property matrix3D on flash.geom.Transform.
        at fl.motion::Animator3D/setTime3D()
        at fl.motion::AnimatorBase/set time()
        at fl.motion::AnimatorBase$/processCurrentFrame()
        at fl.motion::AnimatorBase$/parentEnterFrameHandler()
2023-05-08T03:01:22.290301Z ERROR run_frame:run_all_phases_avm2: ruffle_core::avm2::events: Error dispatching event EventObject(EventObject { type: "frameConstructed", class: flash.events::Event, ptr: 0x555a112ab398 }) to handler FunctionObject(FunctionObject { ptr: 0x555a0f3f9098 }) : ReferenceError: Error #1056: Cannot create property matrix3D on flash.geom.Transform.
        at fl.motion::Animator3D/setTime3D()
        at fl.motion::AnimatorBase/set time()
        at fl.motion::AnimatorBase$/processCurrentFrame()
        at fl.motion::AnimatorBase$/parentEnterFrameHandler()
```
[chirumiru_log.txt](https://github.com/ruffle-rs/ruffle/files/11417359/chirumiru_log.txt)

What I mentioned in my last comment has been reverted. The only errors are now (ruffle nightly 2023.06.13) :
```
2023-06-13T13:36:58.016719Z  INFO ruffle_core::player: Loaded SWF version 10, resolution 512x384 @ 30 FPS
2023-06-13T13:36:58.206228Z  WARN run_frame:run_all_phases_avm2: ruffle_core::stub: Encountered stub: AVM2 flash.utils.Dictionary constructor with weak keys
2023-06-13T13:36:58.282709Z  WARN run_frame:run_all_phases_avm2: ruffle_core::stub: Encountered stub: AVM2 flash.geom.Transform.perspectiveProjection getter
2023-06-13T13:36:58.282781Z ERROR run_frame:run_all_phases_avm2: ruffle_core::avm2::events: Error dispatching event EventObject(EventObject { type: "addedToStage", class: flash.events::Event, ptr: 0x557fa3876208 }) to handler FunctionObject(FunctionObject { ptr: 0x557fa3784148 }) : TypeError: Error #1009: Cannot access a property or method of a null object reference. (accessing field: fieldOfView)
        at nineball_fla::MainTimeline/__setPerspectiveProjection_()
2023-06-13T13:37:06.233103Z  WARN run_frame:run_all_phases_avm2: ruffle_core::stub: Encountered stub: AVM2 flash.geom.Transform.matrix3D setter
2023-06-13T13:38:06.034173Z ERROR run_frame:run_all_phases_avm2: ruffle_core::display_object::movie_clip: Unable to instantiate display node id 455, reason being: Not a DisplayObject
2023-06-13T13:38:21.967688Z ERROR run_frame:run_all_phases_avm2: ruffle_core::display_object::movie_clip: Unable to instantiate display node id 559, reason being: Not a DisplayObject
```

Fix patch:
diff --git a/core/src/avm2/globals.rs b/core/src/avm2/globals.rs
index ce474b19bc0a2..7c4d6ca849b4f 100644
--- a/core/src/avm2/globals.rs
+++ b/core/src/avm2/globals.rs
@@ -112,6 +112,7 @@ pub struct SystemClasses<'gc> {
     pub transform: ClassObject<'gc>,
     pub colortransform: ClassObject<'gc>,
     pub matrix: ClassObject<'gc>,
+    pub matrix3d: ClassObject<'gc>,
     pub illegaloperationerror: ClassObject<'gc>,
     pub eventdispatcher: ClassObject<'gc>,
     pub rectangle: ClassObject<'gc>,
@@ -270,6 +271,7 @@ impl<'gc> SystemClasses<'gc> {
             transform: object,
             colortransform: object,
             matrix: object,
+            matrix3d: object,
             illegaloperationerror: object,
             eventdispatcher: object,
             rectangle: object,
@@ -926,6 +928,7 @@ pub fn init_native_system_classes(activation: &mut Activation<'_, '_>) {
             ("flash.events", "ContextMenuEvent", contextmenuevent),
             ("flash.events", "FocusEvent", focusevent),
             ("flash.geom", "Matrix", matrix),
+            ("flash.geom", "Matrix3D", matrix3d),
             ("flash.geom", "Point", point),
             ("flash.geom", "Rectangle", rectangle),
             ("flash.geom", "Transform", transform),
diff --git a/core/src/avm2/globals/flash/geom/Transform.as b/core/src/avm2/globals/flash/geom/Transform.as
index 1dbaf532f2038..7dd4db5e54afe 100644
--- a/core/src/avm2/globals/flash/geom/Transform.as
+++ b/core/src/avm2/globals/flash/geom/Transform.as
@@ -10,7 +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) {
@@ -30,15 +29,8 @@ package flash.geom {
 		public native function get concatenatedMatrix():Matrix;
 		public native function get pixelBounds():Rectangle;
 
-		public function get matrix3D():Matrix3D {
-			stub_getter("flash.geom.Transform", "matrix3D");
-			return this._matrix3D;
-		}
-
-		public function set matrix3D(m:Matrix3D):void {
-			stub_setter("flash.geom.Transform", "matrix3D");
-			this._matrix3D = m;
-		}
+		public native function get matrix3D():Matrix3D;
+		public native function set matrix3D(value:Matrix3D):void;
 
 		public function get perspectiveProjection():PerspectiveProjection {
 			stub_getter("flash.geom.Transform", "perspectiveProjection");
diff --git a/core/src/avm2/globals/flash/geom/transform.rs b/core/src/avm2/globals/flash/geom/transform.rs
index 8821333d2b53a..563bd2f47bf03 100644
--- a/core/src/avm2/globals/flash/geom/transform.rs
+++ b/core/src/avm2/globals/flash/geom/transform.rs
@@ -3,6 +3,8 @@ use crate::avm2::parameters::ParametersExt;
 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};
 
@@ -45,6 +47,36 @@ pub fn set_color_transform<'gc>(
     Ok(Value::Undefined)
 }
 
+pub fn get_matrix_3d<'gc>(
+    activation: &mut Activation<'_, 'gc>,
+    this: Object<'gc>,
+    _args: &[Value<'gc>],
+) -> Result<Value<'gc>, 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 matrix = *get_display_object(this, activation)?.base().matrix();
+    let matrix3d = Matrix3D::from(matrix);
+    matrix3d_to_object(matrix3d, activation)
+}
+
+pub fn set_matrix_3d<'gc>(
+    activation: &mut Activation<'_, 'gc>,
+    this: Object<'gc>,
+    args: &[Value<'gc>],
+) -> Result<Value<'gc>, 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 matrix3d = object_to_matrix3d(args.get_object(activation, 0, "value")?, activation)?;
+    let matrix = Matrix::from(matrix3d);
+    let matrix = matrix_to_object(matrix, activation)?;
+    set_matrix(activation, this, &[matrix])
+}
+
 pub fn get_matrix<'gc>(
     activation: &mut Activation<'_, 'gc>,
     this: Object<'gc>,
@@ -173,6 +205,42 @@ pub fn color_transform_to_object<'gc>(
     Ok(object.into())
 }
 
+fn matrix3d_to_object<'gc>(
+    matrix: Matrix3D,
+    activation: &mut Activation<'_, 'gc>,
+) -> Result<Value<'gc>, 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<Matrix3D, Error<'gc>> {
+    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<f64> = (0..16)
+        .map(|i| -> Result<f64, Error<'gc>> {
+            raw_data.get(i, activation)?.coerce_to_number(activation)
+        })
+        .collect::<Result<Vec<f64>, _>>()?;
+    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>,
diff --git a/render/src/lib.rs b/render/src/lib.rs
index e0f51091f79b7..03e26a8ce486a 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 0000000000000..862f8d742fbb2
--- /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<Matrix> 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<Matrix3D> 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]),
+        }
+    }
+}


Test patch: