avm2: Add Matrix3D.[recompose,decompose,copyColumnTo,invert,determinant]

The Flash Player 'Matrix3D.recompose' method throws exceptions under
certain circumstances with the "quaternion" orientation style.
I haven't yet figured this out yet, so I've marked that case as a stub.

All of the implementations are based on the OpenFL code, with some
tweaks to match Flash Player's behavior.
This commit is contained in:
Aaron Hill 2023-05-16 17:38:07 -05:00
parent c53a903d35
commit 358beabf5a
15 changed files with 554 additions and 4 deletions

View File

@ -1,6 +1,8 @@
// Based on the MIT-licensed OpenFL code https://github.com/openfl/openfl/blob/develop/src/openfl/geom/Matrix3D.hx
package flash.geom {
import __ruffle__.stub_method;
public class Matrix3D {
// The 4x4 matrix data, stored in column-major order
@ -17,7 +19,8 @@ package flash.geom {
public function Matrix3D(v:Vector.<Number> = null) {
if (v != null && v.length == 16) {
this._rawData = v.concat();
} else {
}
else {
this.identity();
}
}
@ -292,7 +295,304 @@ package flash.geom {
}
public function copyToMatrix3D(other:Matrix3D):void {
other.rawData = rawData
other.rawData = rawData;
}
// Based on OpenFL: https://github.com/openfl/openfl/blob/971a4c9e43b5472fd84d73920a2b7c1b3d8d9257/src/openfl/geom/Matrix3D.hx#L1437
public function recompose(components:Vector.<Vector3D>, orientationStyle:String = "eulerAngles"):Boolean {
checkOrientation(orientationStyle);
if (orientationStyle == Orientation3D.QUATERNION) {
// Flash throws exceptions from 'recompose' certain values of 'components',
// which we need to reproduce. See the 'matrix3d_compose' test
stub_method("flash.geom.Matrix3D", "recompose", "Orientation3D.QUATERNION");
}
// RUFFLE - unlike in OpenFL, we continue on even if some of the 'scale' components are 0
if (components.length < 3) {
return false;
}
identity();
var scale = [];
scale[0] = scale[1] = scale[2] = components[2].x;
scale[4] = scale[5] = scale[6] = components[2].y;
scale[8] = scale[9] = scale[10] = components[2].z;
switch (orientationStyle) {
case Orientation3D.EULER_ANGLES:
var cx = Math.cos(components[1].x);
var cy = Math.cos(components[1].y);
var cz = Math.cos(components[1].z);
var sx = Math.sin(components[1].x);
var sy = Math.sin(components[1].y);
var sz = Math.sin(components[1].z);
_rawData[0] = cy * cz * scale[0];
_rawData[1] = cy * sz * scale[1];
_rawData[2] = -sy * scale[2];
_rawData[3] = 0;
_rawData[4] = (sx * sy * cz - cx * sz) * scale[4];
_rawData[5] = (sx * sy * sz + cx * cz) * scale[5];
_rawData[6] = sx * cy * scale[6];
_rawData[7] = 0;
_rawData[8] = (cx * sy * cz + sx * sz) * scale[8];
_rawData[9] = (cx * sy * sz - sx * cz) * scale[9];
_rawData[10] = cx * cy * scale[10];
_rawData[11] = 0;
_rawData[12] = components[0].x;
_rawData[13] = components[0].y;
_rawData[14] = components[0].z;
_rawData[15] = 1;
break;
default:
var x = components[1].x;
var y = components[1].y;
var z = components[1].z;
var w = components[1].w;
if (orientationStyle == Orientation3D.AXIS_ANGLE) {
x *= Math.sin(w / 2);
y *= Math.sin(w / 2);
z *= Math.sin(w / 2);
w = Math.cos(w / 2);
}
_rawData[0] = (1 - 2 * y * y - 2 * z * z) * scale[0];
_rawData[1] = (2 * x * y + 2 * w * z) * scale[1];
_rawData[2] = (2 * x * z - 2 * w * y) * scale[2];
_rawData[3] = 0;
_rawData[4] = (2 * x * y - 2 * w * z) * scale[4];
_rawData[5] = (1 - 2 * x * x - 2 * z * z) * scale[5];
_rawData[6] = (2 * y * z + 2 * w * x) * scale[6];
_rawData[7] = 0;
_rawData[8] = (2 * x * z + 2 * w * y) * scale[8];
_rawData[9] = (2 * y * z - 2 * w * x) * scale[9];
_rawData[10] = (1 - 2 * x * x - 2 * y * y) * scale[10];
_rawData[11] = 0;
_rawData[12] = components[0].x;
_rawData[13] = components[0].y;
_rawData[14] = components[0].z;
_rawData[15] = 1;
}
if (components[2].x == 0) {
_rawData[0] = 1e-15;
}
if (components[2].y == 0) {
_rawData[5] = 1e-15;
}
if (components[2].z == 0) {
_rawData[10] = 1e-15;
}
return !(components[2].x == 0 || components[2].y == 0 || components[2].y == 0);
}
public function copyColumnTo(column:uint, vector3D:Vector3D):void {
if (column > 3) {
throw new ArgumentError("Error #2004: One of the parameters is invalid.", 2004);
}
switch (column) {
case 0:
vector3D.x = _rawData[0];
vector3D.y = _rawData[1];
vector3D.z = _rawData[2];
vector3D.w = _rawData[3];
break;
case 1:
vector3D.x = _rawData[4];
vector3D.y = _rawData[5];
vector3D.z = _rawData[6];
vector3D.w = _rawData[7];
break;
case 2:
vector3D.x = _rawData[8];
vector3D.y = _rawData[9];
vector3D.z = _rawData[10];
vector3D.w = _rawData[11];
break;
case 3:
vector3D.x = _rawData[12];
vector3D.y = _rawData[13];
vector3D.z = _rawData[14];
vector3D.w = _rawData[15];
break;
default:
}
}
public function decompose(orientationStyle:String = "eulerAngles"):Vector.<Vector3D> {
checkOrientation(orientationStyle);
var vec = new Vector.<Vector3D>([]);
var m = clone();
var mr = m.rawData;
var pos = new Vector3D(mr[12], mr[13], mr[14]);
mr[12] = 0;
mr[13] = 0;
mr[14] = 0;
var scale = new Vector3D();
scale.x = Math.sqrt(mr[0] * mr[0] + mr[1] * mr[1] + mr[2] * mr[2]);
scale.y = Math.sqrt(mr[4] * mr[4] + mr[5] * mr[5] + mr[6] * mr[6]);
scale.z = Math.sqrt(mr[8] * mr[8] + mr[9] * mr[9] + mr[10] * mr[10]);
if (mr[0] * (mr[5] * mr[10] - mr[6] * mr[9]) - mr[1] * (mr[4] * mr[10] - mr[6] * mr[8]) + mr[2] * (mr[4] * mr[9] - mr[5] * mr[8]) < 0) {
scale.z = -scale.z;
}
mr[0] /= scale.x;
mr[1] /= scale.x;
mr[2] /= scale.x;
mr[4] /= scale.y;
mr[5] /= scale.y;
mr[6] /= scale.y;
mr[8] /= scale.z;
mr[9] /= scale.z;
mr[10] /= scale.z;
var rot = new Vector3D();
switch (orientationStyle) {
case Orientation3D.AXIS_ANGLE:
rot.w = Math.acos((mr[0] + mr[5] + mr[10] - 1) / 2);
var len = Math.sqrt((mr[6] - mr[9]) * (mr[6] - mr[9]) + (mr[8] - mr[2]) * (mr[8] - mr[2]) + (mr[1] - mr[4]) * (mr[1] - mr[4]));
if (len != 0) {
rot.x = (mr[6] - mr[9]) / len;
rot.y = (mr[8] - mr[2]) / len;
rot.z = (mr[1] - mr[4]) / len;
}
else {
rot.x = rot.y = rot.z = 0;
}
break;
case Orientation3D.QUATERNION:
var tr = mr[0] + mr[5] + mr[10];
if (tr > 0) {
rot.w = Math.sqrt(1 + tr) / 2;
rot.x = (mr[6] - mr[9]) / (4 * rot.w);
rot.y = (mr[8] - mr[2]) / (4 * rot.w);
rot.z = (mr[1] - mr[4]) / (4 * rot.w);
}
else if ((mr[0] > mr[5]) && (mr[0] > mr[10])) {
rot.x = Math.sqrt(1 + mr[0] - mr[5] - mr[10]) / 2;
rot.w = (mr[6] - mr[9]) / (4 * rot.x);
rot.y = (mr[1] + mr[4]) / (4 * rot.x);
rot.z = (mr[8] + mr[2]) / (4 * rot.x);
}
else if (mr[5] > mr[10]) {
rot.y = Math.sqrt(1 + mr[5] - mr[0] - mr[10]) / 2;
rot.x = (mr[1] + mr[4]) / (4 * rot.y);
rot.w = (mr[8] - mr[2]) / (4 * rot.y);
rot.z = (mr[6] + mr[9]) / (4 * rot.y);
}
else {
rot.z = Math.sqrt(1 + mr[10] - mr[0] - mr[5]) / 2;
rot.x = (mr[8] + mr[2]) / (4 * rot.z);
rot.y = (mr[6] + mr[9]) / (4 * rot.z);
rot.w = (mr[1] - mr[4]) / (4 * rot.z);
}
break;
case Orientation3D.EULER_ANGLES:
rot.y = Math.asin(-mr[2]);
if (mr[2] != 1 && mr[2] != -1) {
rot.x = Math.atan2(mr[6], mr[10]);
rot.z = Math.atan2(mr[1], mr[0]);
}
else {
rot.z = 0;
rot.x = Math.atan2(mr[4], mr[5]);
}
break;
}
vec.push(pos);
vec.push(rot);
vec.push(scale);
return vec;
}
public function invert():Boolean {
var d = determinant;
var invertable = Math.abs(d) > 0.00000000001;
if (invertable) {
d = 1 / d;
var m11:Number = _rawData[0];
var m21:Number = _rawData[4];
var m31:Number = _rawData[8];
var m41:Number = _rawData[12];
var m12:Number = _rawData[1];
var m22:Number = _rawData[5];
var m32:Number = _rawData[9];
var m42:Number = _rawData[13];
var m13:Number = _rawData[2];
var m23:Number = _rawData[6];
var m33:Number = _rawData[10];
var m43:Number = _rawData[14];
var m14:Number = _rawData[3];
var m24:Number = _rawData[7];
var m34:Number = _rawData[11];
var m44:Number = _rawData[15];
_rawData[0] = d * (m22 * (m33 * m44 - m43 * m34) - m32 * (m23 * m44 - m43 * m24) + m42 * (m23 * m34 - m33 * m24));
_rawData[1] = -d * (m12 * (m33 * m44 - m43 * m34) - m32 * (m13 * m44 - m43 * m14) + m42 * (m13 * m34 - m33 * m14));
_rawData[2] = d * (m12 * (m23 * m44 - m43 * m24) - m22 * (m13 * m44 - m43 * m14) + m42 * (m13 * m24 - m23 * m14));
_rawData[3] = -d * (m12 * (m23 * m34 - m33 * m24) - m22 * (m13 * m34 - m33 * m14) + m32 * (m13 * m24 - m23 * m14));
_rawData[4] = -d * (m21 * (m33 * m44 - m43 * m34) - m31 * (m23 * m44 - m43 * m24) + m41 * (m23 * m34 - m33 * m24));
_rawData[5] = d * (m11 * (m33 * m44 - m43 * m34) - m31 * (m13 * m44 - m43 * m14) + m41 * (m13 * m34 - m33 * m14));
_rawData[6] = -d * (m11 * (m23 * m44 - m43 * m24) - m21 * (m13 * m44 - m43 * m14) + m41 * (m13 * m24 - m23 * m14));
_rawData[7] = d * (m11 * (m23 * m34 - m33 * m24) - m21 * (m13 * m34 - m33 * m14) + m31 * (m13 * m24 - m23 * m14));
_rawData[8] = d * (m21 * (m32 * m44 - m42 * m34) - m31 * (m22 * m44 - m42 * m24) + m41 * (m22 * m34 - m32 * m24));
_rawData[9] = -d * (m11 * (m32 * m44 - m42 * m34) - m31 * (m12 * m44 - m42 * m14) + m41 * (m12 * m34 - m32 * m14));
_rawData[10] = d * (m11 * (m22 * m44 - m42 * m24) - m21 * (m12 * m44 - m42 * m14) + m41 * (m12 * m24 - m22 * m14));
_rawData[11] = -d * (m11 * (m22 * m34 - m32 * m24) - m21 * (m12 * m34 - m32 * m14) + m31 * (m12 * m24 - m22 * m14));
_rawData[12] = -d * (m21 * (m32 * m43 - m42 * m33) - m31 * (m22 * m43 - m42 * m23) + m41 * (m22 * m33 - m32 * m23));
_rawData[13] = d * (m11 * (m32 * m43 - m42 * m33) - m31 * (m12 * m43 - m42 * m13) + m41 * (m12 * m33 - m32 * m13));
_rawData[14] = -d * (m11 * (m22 * m43 - m42 * m23) - m21 * (m12 * m43 - m42 * m13) + m41 * (m12 * m23 - m22 * m13));
_rawData[15] = d * (m11 * (m22 * m33 - m32 * m23) - m21 * (m12 * m33 - m32 * m13) + m31 * (m12 * m23 - m22 * m13));
}
return invertable;
}
public function get determinant():Number {
return 1 * ((_rawData[0] * _rawData[5] - _rawData[4] * _rawData[1]) * (_rawData[10] * _rawData[15] - _rawData[14] * _rawData[11])
- (_rawData[0] * _rawData[9] - _rawData[8] * _rawData[1]) * (_rawData[6] * _rawData[15] - _rawData[14] * _rawData[7])
+ (_rawData[0] * _rawData[13] - _rawData[12] * _rawData[1]) * (_rawData[6] * _rawData[11] - _rawData[10] * _rawData[7])
+ (_rawData[4] * _rawData[9] - _rawData[8] * _rawData[5]) * (_rawData[2] * _rawData[15] - _rawData[14] * _rawData[3])
- (_rawData[4] * _rawData[13] - _rawData[12] * _rawData[5]) * (_rawData[2] * _rawData[11] - _rawData[10] * _rawData[3])
+ (_rawData[8] * _rawData[13] - _rawData[12] * _rawData[9]) * (_rawData[2] * _rawData[7] - _rawData[6] * _rawData[3]));
}
}
}
import flash.geom.Orientation3D;
function checkOrientation(orientationStyle:String) {
if (!(orientationStyle == Orientation3D.AXIS_ANGLE || orientationStyle == Orientation3D.EULER_ANGLES || orientationStyle == Orientation3D.QUATERNION)) {
throw new Error("Error #2187: Invalid orientation style " + orientationStyle + ". Value must be one of 'Orientation3D.EULER_ANGLES', 'Orientation3D.AXIS_ANGLE', or 'Orientation3D.QUATERNION'.", 2187);
}
}

View File

@ -71,6 +71,18 @@
trace("Before modification: " + newMat.rawData);
modified[0] = 99999;
trace("After modification: " + newMat.rawData);
var newMat = new Matrix3D(Vector.<Number>([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]));
var col = new Vector3D();
for each (var i in [0, 1, 2, 3]) {
newMat.copyColumnTo(i, col);
trace("Column: " + col + " w=" + col.w);
}
try {
newMat.copyColumnTo(4, col);
} catch (e) {
trace("Column 4: " + e);
}
}
}
}

View File

@ -25,3 +25,8 @@ Too short: 1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1
Too long: 1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1
Before modification: 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
After modification: 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
Column: Vector3D(1, 2, 3) w=4
Column: Vector3D(5, 6, 7) w=8
Column: Vector3D(9, 10, 11) w=12
Column: Vector3D(13, 14, 15) w=16
Column 4: ArgumentError: Error #2004: One of the parameters is invalid.

View File

@ -0,0 +1,104 @@
package {
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
import flash.geom.Orientation3D;
public class Test {
public function Test() {
testRecompose();
testRecomposeWithZeroScale();
// FIXME - we don't reproduce the NaNs that Flash gives us here.
//testRecomposeWeird();
testDecompose();
testDecomposeOtherRotate();
try {
new Matrix3D().decompose("badOrientation");
} catch (e) {
trace("Caught error: " + e);
}
try {
new Matrix3D().recompose(Vector.<Vector3D>([]), "badOrientation");
} catch (e) {
trace("Caught error: " + e);
}
}
public function testRecompose():void {
var translation:Vector3D = new Vector3D(1, 2, 3);
var rotation:Vector3D = new Vector3D(4, 5, 6);
var scale:Vector3D = new Vector3D(7, 8, 9);
var matrix:Matrix3D = new Matrix3D();
var vectors = Vector.<Vector3D>([translation, rotation, scale]);
// FIXME - add back 'QUATERNION' when Ruffle properly throws an exception.
for each (var style in [Orientation3D.EULER_ANGLES, Orientation3D.AXIS_ANGLE, /*Orientation3D.QUATERNION*/]) {
try {
trace("Style: " + style);
trace("Recompose res: " + matrix.recompose(vectors, style));
trace("Recomposed:\n" + matrix.rawData);
} catch (e) {
trace("Caught error with style: " + style + " : " + e);
}
}
}
public function testRecomposeWithZeroScale():void {
var translation:Vector3D = new Vector3D(1, 2, 3);
var rotation:Vector3D = new Vector3D(0, 0, 0);
var scale:Vector3D = new Vector3D(0, 0, 0); // Zero scale
var matrix:Matrix3D = new Matrix3D();
var vectors = Vector.<Vector3D>([translation, rotation, scale]);
matrix.recompose(vectors);
trace("Recomposed zero scale:\n" + matrix.rawData);
}
function testRecomposeWeird() {
var matrix = new Matrix3D(Vector.<Number>([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]));
for each (var style in [Orientation3D.EULER_ANGLES, Orientation3D.AXIS_ANGLE]) {
trace("Style: " + style);
var decomposed = matrix.decompose(style);
trace("Weird Decompose: " + decomposed);
var recomposed = new Matrix3D();
recomposed.recompose(decomposed, style);
trace("Weird Original:");
trace(matrix.rawData);
trace("Weird Recomposed:");
trace(recomposed.rawData);
}
}
function testDecompose() {
var matrix = new Matrix3D();
matrix.appendRotation(1, Vector3D.Y_AXIS);
matrix.appendTranslation(100, 200 ,300);
matrix.appendScale(0.5, 3, 2);
for each (var style in [Orientation3D.EULER_ANGLES, Orientation3D.AXIS_ANGLE, Orientation3D.QUATERNION]) {
trace("Style: " + style);
var decomposed = matrix.decompose(style);
trace("Decompose: " + decomposed);
var recomposed = new Matrix3D();
recomposed.recompose(decomposed, style);
trace("Original:");
trace(matrix.rawData);
trace("Recomposed:");
trace(recomposed.rawData);
}
}
function testDecomposeOtherRotate() {
var matrix = new Matrix3D();
matrix.appendRotation(1, new Vector3D(1, 2, 3));
matrix.appendTranslation(100, 200 ,300);
matrix.appendScale(0.5, 3, 2);
for each (var style in [Orientation3D.EULER_ANGLES, Orientation3D.AXIS_ANGLE]) {
trace("Style: " + style);
var decomposed = matrix.decompose(style);
trace("Decompose: " + decomposed);
}
}
}
}

View File

@ -0,0 +1,34 @@
Style: eulerAngles
Recompose res: true
Recomposed:
1.9065481424331665,-0.554817259311676,6.712470054626465,0,4.113384246826172,-6.6430840492248535,-1.7174100875854492,0,7.31962776184082,4.96370792388916,-1.668725848197937,0,1,2,3,1
Style: axisAngle
Recompose res: true
Recomposed:
7,0,0,0,0,8,0,0,0,0,9,0,1,2,3,1
Recomposed zero scale:
0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,1
Style: eulerAngles
Decompose: Vector3D(50, 600, 600),Vector3D(0, 0.06970713287591934, 0),Vector3D(0.5011408925056458, 3, 1.9954469203948975)
Original:
0.49992385506629944,0,-0.03490481153130531,0,0,3,0,0,0.008726202882826328,0,1.9996954202651978,0,50,600,600,1
Recomposed:
0.49992385506629944,0,-0.03490481153130531,0,0,3,0,0,0.13898426294326782,0,1.9906009435653687,0,50,600,600,1
Style: axisAngle
Decompose: Vector3D(50, 600, 600),Vector3D(0, 1.0000101327896118, 0),Vector3D(0.5011408925056458, 3, 1.9954469203948975)
Original:
0.49992385506629944,0,-0.03490481153130531,0,0,3,0,0,0.008726202882826328,0,1.9996954202651978,0,50,600,600,1
Recomposed:
0.4999238848686218,0,-0.034904103726148605,0,0,3,0,0,0.13898144662380219,0,1.9906010627746582,0,50,600,600,1
Style: quaternion
Decompose: Vector3D(50, 600, 600),Vector3D(0, 0.034846510738134384, 0),Vector3D(0.5011408925056458, 3, 1.9954469203948975)
Original:
0.49992385506629944,0,-0.03490481153130531,0,0,3,0,0,0.008726202882826328,0,1.9996954202651978,0,50,600,600,1
Recomposed:
0.49992385506629944,0,-0.03490481153130531,0,0,3,0,0,0.13898426294326782,0,1.9906009435653687,0,50,600,600,1
Style: eulerAngles
Decompose: Vector3D(50, 600, 600),Vector3D(0.006189380772411823, 0.03704175353050232, 0.08390332758426666),Vector3D(0.5020385384559631, 2.9897639751434326, 1.9986984729766846)
Style: axisAngle
Decompose: Vector3D(50, 600, 600),Vector3D(0.050439074635505676, 0.4060268998146057, 0.9124709963798523),Vector3D(0.5020385384559631, 2.9897639751434326, 1.9986984729766846)
Caught error: Error: Error #2187: Invalid orientation style badOrientation. Value must be one of 'Orientation3D.EULER_ANGLES', 'Orientation3D.AXIS_ANGLE', or 'Orientation3D.QUATERNION'.
Caught error: Error: Error #2187: Invalid orientation style badOrientation. Value must be one of 'Orientation3D.EULER_ANGLES', 'Orientation3D.AXIS_ANGLE', or 'Orientation3D.QUATERNION'.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,6 @@
num_frames = 1
[approximations]
epsilon = 0.01
# Based on https://stackoverflow.com/questions/12643009/regular-expression-for-floating-point-numbers#comment101152072_12643009
number_patterns = ['([-+]?(?:[0-9]*[.])?[0-9]+(?:[eE][-+]?\d+)?|(?:NaN))']

View File

@ -0,0 +1,65 @@

package {
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
public class Test {
public function Test() {
var matrices:Array = [
new Matrix3D(), // Identity matrix
createTranslationMatrix(10, 20, 30), // Translation only
createScaleMatrix(2, 2, 2), // Scaling only
createRotationMatrix(0.3, 0.5, 0.7), // Rotation only
createRotationScaleMatrix(0.3, 0.5, 0.7, 2, 2, 2), // Rotation and scaling
createNonUniformScaleMatrix(2, 3, 4), // Non-uniform scaling
];
for each (var matrix:Matrix3D in matrices) {
trace("Original determinant: " + matrix.determinant);
// Invert the matrix
var invertedMatrix:Matrix3D = matrix.clone();
invertedMatrix.invert();
trace("Inverted:");
trace(invertedMatrix.rawData);
}
}
private function createTranslationMatrix(x:Number, y:Number, z:Number):Matrix3D {
var matrix:Matrix3D = new Matrix3D();
matrix.appendTranslation(x, y, z);
return matrix;
}
private function createScaleMatrix(x:Number, y:Number, z:Number):Matrix3D {
var matrix:Matrix3D = new Matrix3D();
matrix.appendScale(x, y, z);
return matrix;
}
private function createRotationMatrix(rx:Number, ry:Number, rz:Number):Matrix3D {
var matrix:Matrix3D = new Matrix3D();
matrix.appendRotation(rx, Vector3D.X_AXIS);
matrix.appendRotation(ry, Vector3D.Y_AXIS);
matrix.appendRotation(rz, Vector3D.Z_AXIS);
return matrix;
}
private function createRotationScaleMatrix(rx:Number, ry:Number, rz:Number, sx:Number, sy:Number, sz:Number):Matrix3D {
var matrix:Matrix3D = new Matrix3D();
matrix.appendRotation(rx, Vector3D.X_AXIS);
matrix.appendRotation(ry, Vector3D.Y_AXIS);
matrix.appendRotation(rz, Vector3D.Z_AXIS);
matrix.appendScale(sx, sy, sz);
return matrix;
}
private function createNonUniformScaleMatrix(sx:Number, sy:Number, sz:Number):Matrix3D {
var matrix:Matrix3D = new Matrix3D();
matrix.appendScale(sx, sy, sz);
return matrix;
}
}
}

View File

@ -0,0 +1,18 @@
Original determinant: 1
Inverted:
1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1
Original determinant: 1
Inverted:
1,0,0,0,0,1,0,0,0,0,1,0,-10,-20,-30,1
Original determinant: 8
Inverted:
0.5,0,0,0,0,0.5,0,0,0,0,0.5,0,0,0,0,1
Original determinant: 0.9999999403953552
Inverted:
0.9998874068260193,-0.012171145528554916,0.008789733052253723,0,0.012216536328196526,0.9999122023582458,-0.00512896291911602,0,-0.00872653815895319,0.005235764663666487,0.999948263168335,0,0,0,0,1
Original determinant: 7.999999523162842
Inverted:
0.49994370341300964,-0.006085572764277458,0.004394866526126862,0,0.006108268164098263,0.4999561011791229,-0.00256448145955801,0,-0.004363269079476595,0.0026178823318332434,0.4999741315841675,0,0,0,0,1
Original determinant: 24
Inverted:
0.5,0,0,0,0,0.3333333432674408,0,0,0,0,0.25,0,0,0,0,1

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,6 @@
num_frames = 1
[approximations]
epsilon = 0.01
# Based on https://stackoverflow.com/questions/12643009/regular-expression-for-floating-point-numbers#comment101152072_12643009
number_patterns = ['([-+]?(?:[0-9]*[.])?[0-9]+(?:[eE][-+]?\d+)?|(?:NaN))']

View File

@ -117,8 +117,8 @@ impl Test {
.expect("Failed to parse 'expected' capture group as float");
approximations.compare(actual_num, expected_num);
}
let modified_actual = pattern.replace(actual, "");
let modified_expected = pattern.replace(expected, "");
let modified_actual = pattern.replace_all(actual, "");
let modified_expected = pattern.replace_all(expected, "");
assert_eq!(modified_actual, modified_expected);
break;