Simplify Mat2D api
diff --git a/include/rive/math/mat2d.hpp b/include/rive/math/mat2d.hpp
index c2876dc..0e492ab 100644
--- a/include/rive/math/mat2d.hpp
+++ b/include/rive/math/mat2d.hpp
@@ -32,11 +32,21 @@
             return *this;
         }
 
-        static Mat2D scale(const Mat2D& mat, const Vec2D& vec);
+        // If returns true, result holds the inverse.
+        // If returns false, result is unchnaged.
+        bool invert(Mat2D* result) const;
+
+        Mat2D invertOrIdentity() const {
+            Mat2D inverse;          // initialized to identity
+            (void)invert(&inverse); // inverse is unchanged if invert() fails
+            return inverse;
+        }
+    
+        TransformComponents decompose() const;
+        static Mat2D compose(const TransformComponents&);
+        Mat2D scale(Vec2D) const;
+
         static Mat2D multiply(const Mat2D& a, const Mat2D& b);
-        static bool invert(Mat2D& result, const Mat2D& a);
-        static void decompose(TransformComponents& result, const Mat2D& m);
-        static void compose(Mat2D& result, const TransformComponents& components);
 
         float xx() const { return m_Buffer[0]; }
         float xy() const { return m_Buffer[1]; }
diff --git a/include/rive/math/transform_components.hpp b/include/rive/math/transform_components.hpp
index 44e9d4b..1fc74f9 100644
--- a/include/rive/math/transform_components.hpp
+++ b/include/rive/math/transform_components.hpp
@@ -37,22 +37,17 @@
         float skew() const { return m_Skew; }
         void skew(float value) { m_Skew = value; }
 
-        void translation(Vec2D& result) const {
-            result[0] = m_X;
-            result[1] = m_Y;
-        }
-        void scale(Vec2D& result) const {
-            result[0] = m_ScaleX;
-            result[1] = m_ScaleY;
-        }
+        Vec2D translation() const { return {m_X, m_Y}; }
+        Vec2D scale() const { return {m_ScaleX, m_ScaleY}; }
 
-        static void copy(TransformComponents& result, const TransformComponents& a) {
-            result.m_X = a.m_X;
-            result.m_Y = a.m_Y;
-            result.m_ScaleX = a.m_ScaleX;
-            result.m_ScaleY = a.m_ScaleY;
-            result.m_Rotation = a.m_Rotation;
-            result.m_Skew = a.m_Skew;
+        TransformComponents& operator=(const TransformComponents& a) {
+            m_X = a.m_X;
+            m_Y = a.m_Y;
+            m_ScaleX = a.m_ScaleX;
+            m_ScaleY = a.m_ScaleY;
+            m_Rotation = a.m_Rotation;
+            m_Skew = a.m_Skew;
+            return *this;
         }
     };
 } // namespace rive
diff --git a/skia/viewer/src/main.cpp b/skia/viewer/src/main.cpp
index 0115a33..8568137 100644
--- a/skia/viewer/src/main.cpp
+++ b/skia/viewer/src/main.cpp
@@ -289,7 +289,7 @@
                                                         artboardInstance->bounds());
             renderer.transform(viewTransform);
             // Store the inverse view so we can later go from screen to world.
-            rive::Mat2D::invert(gInverseViewTransform, viewTransform);
+            gInverseViewTransform = viewTransform.invertOrIdentity();
             // post_mouse_event(artboard.get(), canvas->getTotalMatrix());
 
             artboardInstance->draw(&renderer);
diff --git a/src/bones/tendon.cpp b/src/bones/tendon.cpp
index 9e66364..6e8d204 100644
--- a/src/bones/tendon.cpp
+++ b/src/bones/tendon.cpp
@@ -14,7 +14,7 @@
     bind[4] = tx();
     bind[5] = ty();
 
-    if (!Mat2D::invert(m_InverseBind, bind)) {
+    if (!bind.invert(&m_InverseBind)) {
         return StatusCode::FailedInversion;
     }
 
diff --git a/src/constraints/ik_constraint.cpp b/src/constraints/ik_constraint.cpp
index c2c3613..fc75ff0 100644
--- a/src/constraints/ik_constraint.cpp
+++ b/src/constraints/ik_constraint.cpp
@@ -205,11 +205,11 @@
     for (BoneChainLink& item : m_FkChain) {
         auto bone = item.bone;
         const Mat2D& parentWorld = getParentWorld(*bone);
-        Mat2D::invert(item.parentWorldInverse, parentWorld);
+        item.parentWorldInverse = parentWorld.invertOrIdentity();
 
         Mat2D& boneTransform = bone->mutableTransform();
         boneTransform = item.parentWorldInverse * bone->worldTransform();
-        Mat2D::decompose(item.transformComponents, boneTransform);
+        item.transformComponents = boneTransform.decompose();
     }
 
     int count = (int)m_FkChain.size();
@@ -228,7 +228,7 @@
                 solve2(item, tip, worldTargetTranslation);
                 for (int j = item->index + 1, end = m_FkChain.size() - 1; j < end; j++) {
                     BoneChainLink& fk = m_FkChain[j];
-                    Mat2D::invert(fk.parentWorldInverse, getParentWorld(*fk.bone));
+                    fk.parentWorldInverse = getParentWorld(*fk.bone).invertOrIdentity();
                 }
             }
             break;
diff --git a/src/constraints/rotation_constraint.cpp b/src/constraints/rotation_constraint.cpp
index 798f4b7..7122ac7 100644
--- a/src/constraints/rotation_constraint.cpp
+++ b/src/constraints/rotation_constraint.cpp
@@ -8,22 +8,22 @@
 void RotationConstraint::constrain(TransformComponent* component) {
     const Mat2D& transformA = component->worldTransform();
     Mat2D transformB;
-    Mat2D::decompose(m_ComponentsA, transformA);
+    m_ComponentsA = transformA.decompose();
     if (m_Target == nullptr) {
         transformB = transformA;
-        TransformComponents::copy(m_ComponentsB, m_ComponentsA);
+        m_ComponentsB = m_ComponentsA;
     } else {
         transformB = m_Target->worldTransform();
         if (sourceSpace() == TransformSpace::local) {
             Mat2D inverse;
 
-            if (!Mat2D::invert(inverse, getParentWorld(*m_Target))) {
+            if (!getParentWorld(*m_Target).invert(&inverse)) {
                 return;
             }
             transformB = inverse * transformB;
         }
 
-        Mat2D::decompose(m_ComponentsB, transformB);
+        m_ComponentsB = transformB.decompose();
 
         if (!doesCopy()) {
             m_ComponentsB.rotation(destSpace() == TransformSpace::local ? 0.0f
@@ -40,22 +40,21 @@
             // the parent local transform and get it in world, then decompose
             // the world for interpolation.
 
-            Mat2D::compose(transformB, m_ComponentsB);
+            transformB = Mat2D::compose(m_ComponentsB);
             transformB = getParentWorld(*component) * transformB;
-            Mat2D::decompose(m_ComponentsB, transformB);
+            m_ComponentsB = transformB.decompose();
         }
     }
     bool clampLocal = minMaxSpace() == TransformSpace::local;
     if (clampLocal) {
         // Apply min max in local space, so transform to local coordinates
         // first.
-        Mat2D::compose(transformB, m_ComponentsB);
-        Mat2D inverse = Mat2D();
-        if (!Mat2D::invert(inverse, getParentWorld(*component))) {
+        transformB = Mat2D::compose(m_ComponentsB);
+        Mat2D inverse;
+        if (!getParentWorld(*component).invert(&inverse)) {
             return;
         }
-        transformB = inverse * transformB;
-        Mat2D::decompose(m_ComponentsB, transformB);
+        m_ComponentsB = (inverse * transformB).decompose();
     }
     if (max() && m_ComponentsB.rotation() > maxValue()) {
         m_ComponentsB.rotation(maxValue());
@@ -65,9 +64,9 @@
     }
     if (clampLocal) {
         // Transform back to world.
-        Mat2D::compose(transformB, m_ComponentsB);
+        transformB = Mat2D::compose(m_ComponentsB);
         transformB = getParentWorld(*component) * transformB;
-        Mat2D::decompose(m_ComponentsB, transformB);
+        m_ComponentsB = transformB.decompose();
     }
 
     float angleA = std::fmod(m_ComponentsA.rotation(), (float)M_PI * 2);
@@ -87,5 +86,5 @@
     m_ComponentsB.scaleY(m_ComponentsA.scaleY());
     m_ComponentsB.skew(m_ComponentsA.skew());
 
-    Mat2D::compose(component->mutableWorldTransform(), m_ComponentsB);
+    component->mutableWorldTransform() = Mat2D::compose(m_ComponentsB);
 }
diff --git a/src/constraints/scale_constraint.cpp b/src/constraints/scale_constraint.cpp
index 891fd29..1396d15 100644
--- a/src/constraints/scale_constraint.cpp
+++ b/src/constraints/scale_constraint.cpp
@@ -8,20 +8,20 @@
 void ScaleConstraint::constrain(TransformComponent* component) {
     const Mat2D& transformA = component->worldTransform();
     Mat2D transformB;
-    Mat2D::decompose(m_ComponentsA, transformA);
+    m_ComponentsA = transformA.decompose();
     if (m_Target == nullptr) {
         transformB = transformA;
-        TransformComponents::copy(m_ComponentsB, m_ComponentsA);
+        m_ComponentsB = m_ComponentsA;
     } else {
         transformB = m_Target->worldTransform();
         if (sourceSpace() == TransformSpace::local) {
             Mat2D inverse;
-            if (!Mat2D::invert(inverse, getParentWorld(*m_Target))) {
+            if (!getParentWorld(*m_Target).invert(&inverse)) {
                 return;
             }
             transformB = inverse * transformB;
         }
-        Mat2D::decompose(m_ComponentsB, transformB);
+        m_ComponentsB = transformB.decompose();
 
         if (!doesCopy()) {
             m_ComponentsB.scaleX(destSpace() == TransformSpace::local ? 1.0f
@@ -48,9 +48,9 @@
             // the parent local transform and get it in world, then decompose
             // the world for interpolation.
 
-            Mat2D::compose(transformB, m_ComponentsB);
+            transformB = Mat2D::compose(m_ComponentsB);
             transformB = getParentWorld(*component) * transformB;
-            Mat2D::decompose(m_ComponentsB, transformB);
+            m_ComponentsB = transformB.decompose();
         }
     }
 
@@ -58,13 +58,13 @@
     if (clamplocal) {
         // Apply min max in local space, so transform to local coordinates
         // first.
-        Mat2D::compose(transformB, m_ComponentsB);
+        transformB = Mat2D::compose(m_ComponentsB);
         Mat2D inverse;
-        if (!Mat2D::invert(inverse, getParentWorld(*component))) {
+        if (!getParentWorld(*component).invert(&inverse)) {
             return;
         }
         transformB = inverse * transformB;
-        Mat2D::decompose(m_ComponentsB, transformB);
+        m_ComponentsB = transformB.decompose();
     }
     if (max() && m_ComponentsB.scaleX() > maxValue()) {
         m_ComponentsB.scaleX(maxValue());
@@ -80,9 +80,9 @@
     }
     if (clamplocal) {
         // Transform back to world.
-        Mat2D::compose(transformB, m_ComponentsB);
+        transformB = Mat2D::compose(m_ComponentsB);
         transformB = getParentWorld(*component) * transformB;
-        Mat2D::decompose(m_ComponentsB, transformB);
+        m_ComponentsB = transformB.decompose();
     }
 
     float t = strength();
@@ -95,5 +95,5 @@
     m_ComponentsB.scaleY(m_ComponentsA.scaleY() * ti + m_ComponentsB.scaleY() * t);
     m_ComponentsB.skew(m_ComponentsA.skew());
 
-    Mat2D::compose(component->mutableWorldTransform(), m_ComponentsB);
+    component->mutableWorldTransform() = Mat2D::compose(m_ComponentsB);
 }
diff --git a/src/constraints/transform_constraint.cpp b/src/constraints/transform_constraint.cpp
index d92d824..988c34a 100644
--- a/src/constraints/transform_constraint.cpp
+++ b/src/constraints/transform_constraint.cpp
@@ -16,7 +16,7 @@
         const Mat2D& targetParentWorld = getParentWorld(*m_Target);
 
         Mat2D inverse;
-        if (!Mat2D::invert(inverse, targetParentWorld)) {
+        if (!targetParentWorld.invert(&inverse)) {
             return;
         }
         transformB = inverse * transformB;
@@ -26,8 +26,8 @@
         transformB = targetParentWorld * transformB;
     }
 
-    Mat2D::decompose(m_ComponentsA, transformA);
-    Mat2D::decompose(m_ComponentsB, transformB);
+    m_ComponentsA = transformA.decompose();
+    m_ComponentsB = transformB.decompose();
 
     float angleA = std::fmod(m_ComponentsA.rotation(), (float)M_PI * 2);
     float angleB = std::fmod(m_ComponentsB.rotation(), (float)M_PI * 2);
@@ -48,5 +48,5 @@
     m_ComponentsB.scaleY(m_ComponentsA.scaleY() * ti + m_ComponentsB.scaleY() * t);
     m_ComponentsB.skew(m_ComponentsA.skew() * ti + m_ComponentsB.skew() * t);
 
-    Mat2D::compose(component->mutableWorldTransform(), m_ComponentsB);
+    component->mutableWorldTransform() = Mat2D::compose(m_ComponentsB);
 }
diff --git a/src/constraints/translation_constraint.cpp b/src/constraints/translation_constraint.cpp
index 1c12267..4ade22b 100644
--- a/src/constraints/translation_constraint.cpp
+++ b/src/constraints/translation_constraint.cpp
@@ -18,7 +18,7 @@
             const Mat2D& targetParentWorld = getParentWorld(*m_Target);
 
             Mat2D inverse;
-            if (!Mat2D::invert(inverse, targetParentWorld)) {
+            if (!targetParentWorld.invert(&inverse)) {
                 return;
             }
             transformB = inverse * transformB;
@@ -55,12 +55,12 @@
     if (clampLocal) {
         // Apply min max in local space, so transform to local coordinates
         // first.
-        Mat2D invert;
-        if (!Mat2D::invert(invert, getParentWorld(*component))) {
+        Mat2D inverse;
+        if (!getParentWorld(*component).invert(&inverse)) {
             return;
         }
         // Get our target world coordinates in parent local.
-        translationB = invert * translationB;
+        translationB = inverse * translationB;
     }
     if (max() && translationB[0] > maxValue()) {
         translationB[0] = maxValue();
diff --git a/src/math/mat2d.cpp b/src/math/mat2d.cpp
index 7d62d59..eb9b8ec 100644
--- a/src/math/mat2d.cpp
+++ b/src/math/mat2d.cpp
@@ -14,21 +14,20 @@
     return {c, s, -s, c, 0, 0};
 }
 
-Mat2D Mat2D::scale(const Mat2D& mat, const Vec2D& vec) {
-    const float v0 = vec[0], v1 = vec[1];
+Mat2D Mat2D::scale(Vec2D vec) const {
     return {
-        mat[0] * v0,
-        mat[1] * v0,
-        mat[2] * v1,
-        mat[3] * v1,
-        mat[4],
-        mat[5],
+        m_Buffer[0] * vec.x(),
+        m_Buffer[1] * vec.x(),
+        m_Buffer[2] * vec.y(),
+        m_Buffer[3] * vec.y(),
+        m_Buffer[4],
+        m_Buffer[5],
     };
 }
 
 Mat2D Mat2D::multiply(const Mat2D& a, const Mat2D& b) {
-    float a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5], b0 = b[0], b1 = b[1],
-          b2 = b[2], b3 = b[3], b4 = b[4], b5 = b[5];
+    float a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5],
+          b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3], b4 = b[4], b5 = b[5];
     return {
         a0 * b0 + a2 * b1,
         a1 * b0 + a3 * b1,
@@ -39,8 +38,9 @@
     };
 }
 
-bool Mat2D::invert(Mat2D& result, const Mat2D& a) {
-    float aa = a[0], ab = a[1], ac = a[2], ad = a[3], atx = a[4], aty = a[5];
+bool Mat2D::invert(Mat2D* result) const {
+    float aa = m_Buffer[0], ab  = m_Buffer[1], ac  = m_Buffer[2],
+          ad = m_Buffer[3], atx = m_Buffer[4], aty = m_Buffer[5];
 
     float det = aa * ad - ab * ac;
     if (det == 0.0f) {
@@ -48,17 +48,20 @@
     }
     det = 1.0f / det;
 
-    result[0] = ad * det;
-    result[1] = -ab * det;
-    result[2] = -ac * det;
-    result[3] = aa * det;
-    result[4] = (ac * aty - ad * atx) * det;
-    result[5] = (ab * atx - aa * aty) * det;
+    *result = {
+         ad * det,
+        -ab * det,
+        -ac * det,
+         aa * det,
+        (ac * aty - ad * atx) * det,
+        (ab * atx - aa * aty) * det,
+    };
     return true;
 }
 
-void Mat2D::decompose(TransformComponents& result, const Mat2D& m) {
-    float m0 = m[0], m1 = m[1], m2 = m[2], m3 = m[3];
+TransformComponents Mat2D::decompose() const {
+    float m0 = m_Buffer[0], m1 = m_Buffer[1],
+          m2 = m_Buffer[2], m3 = m_Buffer[3];
 
     float rotation = (float)std::atan2(m1, m0);
     float denom = m0 * m0 + m1 * m1;
@@ -66,27 +69,28 @@
     float scaleY = (m0 * m3 - m2 * m1) / scaleX;
     float skewX = (float)std::atan2(m0 * m2 + m1 * m3, denom);
 
-    result.x(m[4]);
-    result.y(m[5]);
+    TransformComponents result;
+    result.x(m_Buffer[4]);
+    result.y(m_Buffer[5]);
     result.scaleX(scaleX);
     result.scaleY(scaleY);
     result.rotation(rotation);
     result.skew(skewX);
+    return result;
 }
 
-void Mat2D::compose(Mat2D& result, const TransformComponents& components) {
-    result = Mat2D::fromRotation(components.rotation());
+Mat2D Mat2D::compose(const TransformComponents& components) {
+    auto result = Mat2D::fromRotation(components.rotation());
     result[4] = components.x();
     result[5] = components.y();
-    Vec2D scale;
-    components.scale(scale);
-    result = Mat2D::scale(result, scale);
+    result = result.scale(components.scale());
 
     float sk = components.skew();
     if (sk != 0.0f) {
         result[2] = result[0] * sk + result[2];
         result[3] = result[1] * sk + result[3];
     }
+    return result;
 }
 
 void Mat2D::scaleByValues(float sx, float sy) {
diff --git a/src/shapes/path.cpp b/src/shapes/path.cpp
index 243d758..942cf86 100644
--- a/src/shapes/path.cpp
+++ b/src/shapes/path.cpp
@@ -257,11 +257,7 @@
     if (transformToParent && parent()->is<TransformComponent>()) {
         // Put the transform in parent space.
         auto world = parent()->as<TransformComponent>()->worldTransform();
-        Mat2D inverseWorld;
-        if (!Mat2D::invert(inverseWorld, world)) {
-            inverseWorld = Mat2D();
-        }
-        transform = inverseWorld * transform;
+        transform = world.invertOrIdentity() * transform;
     }
 
     FlattenedPath* flat = new FlattenedPath();
diff --git a/src/shapes/path_composer.cpp b/src/shapes/path_composer.cpp
index ff843d6..7c2d20d 100644
--- a/src/shapes/path_composer.cpp
+++ b/src/shapes/path_composer.cpp
@@ -28,10 +28,7 @@
                 m_LocalPath->reset();
             }
             auto world = m_Shape->worldTransform();
-            Mat2D inverseWorld;
-            if (!Mat2D::invert(inverseWorld, world)) {
-                inverseWorld = Mat2D();
-            }
+            Mat2D inverseWorld = world.invertOrIdentity();
             // Get all the paths into local shape space.
             for (auto path : m_Shape->paths()) {
                 const auto localTransform = inverseWorld * path->pathTransform();
diff --git a/test/rotation_constraint_test.cpp b/test/rotation_constraint_test.cpp
index 007f6f9..974e284 100644
--- a/test/rotation_constraint_test.cpp
+++ b/test/rotation_constraint_test.cpp
@@ -20,10 +20,8 @@
     auto rectangle = artboard->find<rive::TransformComponent>("rect");
 
     artboard->advance(0.0f);
-    rive::TransformComponents targetComponents;
-    rive::Mat2D::decompose(targetComponents, target->worldTransform());
-    rive::TransformComponents rectComponents;
-    rive::Mat2D::decompose(rectComponents, rectangle->worldTransform());
+    auto targetComponents = target->worldTransform().decompose();
+    auto rectComponents = rectangle->worldTransform().decompose();
 
     REQUIRE(targetComponents.rotation() == rectComponents.rotation());
 }
diff --git a/test/scale_constraint_test.cpp b/test/scale_constraint_test.cpp
index bd9827b..edf96f5 100644
--- a/test/scale_constraint_test.cpp
+++ b/test/scale_constraint_test.cpp
@@ -21,10 +21,8 @@
 
     artboard->advance(0.0f);
 
-    rive::TransformComponents targetComponents;
-    rive::Mat2D::decompose(targetComponents, target->worldTransform());
-    rive::TransformComponents rectComponents;
-    rive::Mat2D::decompose(rectComponents, rectangle->worldTransform());
+    auto targetComponents = target->worldTransform().decompose();
+    auto rectComponents = rectangle->worldTransform().decompose();
 
     REQUIRE(targetComponents.scaleX() == rectComponents.scaleX());
     REQUIRE(targetComponents.scaleY() == rectComponents.scaleY());
diff --git a/test/translation_constraint_test.cpp b/test/translation_constraint_test.cpp
index d01f15b..e8a0eb8 100644
--- a/test/translation_constraint_test.cpp
+++ b/test/translation_constraint_test.cpp
@@ -21,10 +21,8 @@
 
     artboard->advance(0.0f);
 
-    rive::TransformComponents targetComponents;
-    rive::Mat2D::decompose(targetComponents, target->worldTransform());
-    rive::TransformComponents rectComponents;
-    rive::Mat2D::decompose(rectComponents, rectangle->worldTransform());
+    auto targetComponents = target->worldTransform().decompose();
+    auto rectComponents = rectangle->worldTransform().decompose();
 
     REQUIRE(targetComponents.x() == rectComponents.x());
     REQUIRE(targetComponents.y() == rectComponents.y());