Hack in hittesting
diff --git a/include/rive/artboard.hpp b/include/rive/artboard.hpp
index 1d9a53a..9cee3f7 100644
--- a/include/rive/artboard.hpp
+++ b/include/rive/artboard.hpp
@@ -1,13 +1,16 @@
 #ifndef _RIVE_ARTBOARD_HPP_
 #define _RIVE_ARTBOARD_HPP_
+
 #include "rive/animation/linear_animation.hpp"
 #include "rive/animation/state_machine.hpp"
 #include "rive/core_context.hpp"
 #include "rive/generated/artboard_base.hpp"
+#include "rive/hit_info.hpp"
 #include "rive/math/aabb.hpp"
 #include "rive/renderer.hpp"
 #include "rive/shapes/shape_paint_container.hpp"
 #include <vector>
+
 namespace rive {
     class File;
     class Drawable;
@@ -56,6 +59,10 @@
 
         Core* resolve(int id) const override;
 
+        // EXPERIMENTAL -- for internal testing only for now.
+        // DO NOT RELY ON THIS as it may change/disappear in the future.
+        Core* hitTest(HitInfo*, const Mat2D* = nullptr);
+
         void onComponentDirty(Component* component);
 
         /// Update components that depend on each other in DAG order.
diff --git a/include/rive/drawable.hpp b/include/rive/drawable.hpp
index d693baa..99fde46 100644
--- a/include/rive/drawable.hpp
+++ b/include/rive/drawable.hpp
@@ -1,6 +1,7 @@
 #ifndef _RIVE_DRAWABLE_HPP_
 #define _RIVE_DRAWABLE_HPP_
 #include "rive/generated/drawable_base.hpp"
+#include "rive/hit_info.hpp"
 #include "rive/renderer.hpp"
 #include <vector>
 
@@ -24,6 +25,7 @@
         BlendMode blendMode() const { return (BlendMode)blendModeValue(); }
         bool clip(Renderer* renderer) const;
         virtual void draw(Renderer* renderer) = 0;
+        virtual Core* hitTest(HitInfo*, const Mat2D&) = 0;
         void addClippingShape(ClippingShape* shape);
         inline const std::vector<ClippingShape*>& clippingShapes() const {
             return m_ClippingShapes;
@@ -37,4 +39,4 @@
     };
 } // namespace rive
 
-#endif
\ No newline at end of file
+#endif
diff --git a/include/rive/hit_info.hpp b/include/rive/hit_info.hpp
new file mode 100644
index 0000000..9ca9136
--- /dev/null
+++ b/include/rive/hit_info.hpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2022 Rive
+ */
+
+#ifndef _RIVE_HITINFO_HPP_
+#define _RIVE_HITINFO_HPP_
+
+#include "rive/math/aabb.hpp"
+#include <vector>
+
+namespace rive {
+
+class NestedArtboard;
+
+struct HitInfo {
+    IAABB                        area;      // input
+    std::vector<NestedArtboard*> mounts;    // output
+};
+
+}
+#endif
diff --git a/include/rive/nested_artboard.hpp b/include/rive/nested_artboard.hpp
index b7deee4..76cb46a 100644
--- a/include/rive/nested_artboard.hpp
+++ b/include/rive/nested_artboard.hpp
@@ -1,7 +1,10 @@
 #ifndef _RIVE_NESTED_ARTBOARD_HPP_
 #define _RIVE_NESTED_ARTBOARD_HPP_
+
 #include "rive/generated/nested_artboard_base.hpp"
+#include "rive/hit_info.hpp"
 #include <stdio.h>
+
 namespace rive {
     class NestedAnimation;
     class NestedArtboard : public NestedArtboardBase {
@@ -14,6 +17,7 @@
         ~NestedArtboard();
         StatusCode onAddedClean(CoreContext* context) override;
         void draw(Renderer* renderer) override;
+        Core* hitTest(HitInfo*, const Mat2D&) override;
         void addNestedAnimation(NestedAnimation* nestedAnimation);
 
         void nest(Artboard* artboard);
@@ -25,4 +29,4 @@
     };
 } // namespace rive
 
-#endif
\ No newline at end of file
+#endif
diff --git a/include/rive/shapes/image.hpp b/include/rive/shapes/image.hpp
index 491d9ec..364a3f3 100644
--- a/include/rive/shapes/image.hpp
+++ b/include/rive/shapes/image.hpp
@@ -1,7 +1,10 @@
 #ifndef _RIVE_IMAGE_HPP_
 #define _RIVE_IMAGE_HPP_
+
+#include "rive/hit_info.hpp"
 #include "rive/generated/shapes/image_base.hpp"
 #include "rive/assets/file_asset_referencer.hpp"
+
 namespace rive {
     class ImageAsset;
     class Mesh;
@@ -15,10 +18,11 @@
         void setMesh(Mesh* mesh);
         ImageAsset* imageAsset() const { return m_ImageAsset; }
         void draw(Renderer* renderer) override;
+        Core* hitTest(HitInfo*, const Mat2D&) override;
         StatusCode import(ImportStack& importStack) override;
         void assets(const std::vector<FileAsset*>& assets) override;
         Core* clone() const override;
     };
 } // namespace rive
 
-#endif
\ No newline at end of file
+#endif
diff --git a/include/rive/shapes/shape.hpp b/include/rive/shapes/shape.hpp
index 6361a2f..4112d46 100644
--- a/include/rive/shapes/shape.hpp
+++ b/include/rive/shapes/shape.hpp
@@ -1,5 +1,7 @@
 #ifndef _RIVE_SHAPE_HPP_
 #define _RIVE_SHAPE_HPP_
+
+#include "rive/hit_info.hpp"
 #include "rive/generated/shapes/shape_base.hpp"
 #include "rive/shapes/path_composer.hpp"
 #include "rive/shapes/shape_paint_container.hpp"
@@ -25,6 +27,7 @@
 
         void update(ComponentDirt value) override;
         void draw(Renderer* renderer) override;
+        Core* hitTest(HitInfo*, const Mat2D&) override;
 
         PathComposer* pathComposer() const {
             return (PathComposer*)&m_PathComposer;
@@ -36,4 +39,4 @@
     };
 } // namespace rive
 
-#endif
\ No newline at end of file
+#endif
diff --git a/src/artboard.cpp b/src/artboard.cpp
index b7b8ed6..090f776 100644
--- a/src/artboard.cpp
+++ b/src/artboard.cpp
@@ -13,6 +13,8 @@
 #include "rive/importers/import_stack.hpp"
 #include "rive/importers/backboard_importer.hpp"
 #include "rive/nested_artboard.hpp"
+
+#include <stack>
 #include <unordered_map>
 
 using namespace rive;
@@ -364,6 +366,37 @@
     return updateComponents();
 }
 
+Core* Artboard::hitTest(HitInfo* hinfo, const Mat2D* xform) {
+    if (clip()) {
+        // TODO: can we get the rawpath for the clip?
+    }
+
+    auto mx = xform ? *xform : Mat2D();
+    if (m_FrameOrigin) {
+        mx *= Mat2D::fromTranslate(width() * originX(), height() * originY());
+    }
+
+    Drawable* last = m_FirstDrawable;
+    if (last) {
+        // walk to the end, so we can visit in reverse-order
+        while (last->prev) {
+            last = last->prev;
+        }
+    }
+    for (auto drawable = last; drawable; drawable = drawable->next) {
+        if (drawable->isHidden()) {
+            continue;
+        }
+        if (auto c = drawable->hitTest(hinfo, mx)) {
+            return c;
+        }
+    }
+    
+    // TODO: should we hit-test the background?
+    
+    return nullptr;
+}
+
 void Artboard::draw(Renderer* renderer, DrawOption option) {
     renderer->save();
     if (clip()) {
diff --git a/src/nested_artboard.cpp b/src/nested_artboard.cpp
index bd5c608..a4658ad 100644
--- a/src/nested_artboard.cpp
+++ b/src/nested_artboard.cpp
@@ -28,18 +28,34 @@
     m_NestedInstance->advance(0.0f);
 }
 
+static Mat2D makeTranslate(const Artboard* artboard) {
+    return Mat2D::fromTranslate(-artboard->originX() * artboard->width(),
+                                -artboard->originY() * artboard->height());
+}
+
 void NestedArtboard::draw(Renderer* renderer) {
     if (m_NestedInstance == nullptr) {
         return;
     }
     renderer->save();
-    renderer->transform(worldTransform());
-    renderer->translate(-m_NestedInstance->originX() * m_NestedInstance->width(),
-                        -m_NestedInstance->originY() * m_NestedInstance->height());
+    renderer->transform(worldTransform() * makeTranslate(m_NestedInstance));
     m_NestedInstance->draw(renderer);
     renderer->restore();
 }
 
+Core* NestedArtboard::hitTest(HitInfo* hinfo, const Mat2D& xform) {
+    if (m_NestedInstance == nullptr) {
+        return nullptr;
+    }
+    hinfo->mounts.push_back(this);
+    auto mx = xform * worldTransform() * makeTranslate(m_NestedInstance);
+    if (auto c = m_NestedInstance->hitTest(hinfo, &mx)) {
+        return c;
+    }
+    hinfo->mounts.pop_back();
+    return nullptr;
+}
+
 StatusCode NestedArtboard::import(ImportStack& importStack) {
     auto backboardImporter =
         importStack.latest<BackboardImporter>(Backboard::typeKey);
diff --git a/src/shapes/image.cpp b/src/shapes/image.cpp
index bb8ac07..6fe50c6 100644
--- a/src/shapes/image.cpp
+++ b/src/shapes/image.cpp
@@ -1,3 +1,4 @@
+#include "rive/math/hit_test.hpp"
 #include "rive/shapes/image.hpp"
 #include "rive/backboard.hpp"
 #include "rive/importers/backboard_importer.hpp"
@@ -33,6 +34,28 @@
     renderer->restore();
 }
 
+Core* Image::hitTest(HitInfo* hinfo, const Mat2D& xform) {
+    // TODO: handle clip?
+
+    auto renderImage = m_ImageAsset->renderImage();
+    auto width = renderImage->width();
+    auto height = renderImage->height();
+
+    if (m_Mesh) {
+        printf("Missing mesh\n");
+        // TODO: hittest mesh
+    } else {
+        auto mx = xform * worldTransform() * Mat2D::fromTranslate(-width*0.5f, -height*0.5f);
+        HitTester tester(hinfo->area);
+        tester.addRect(AABB(0, 0, width, height), mx);
+        if (tester.test()) {
+            return this;
+        }
+    }
+    return nullptr;
+}
+
+
 StatusCode Image::import(ImportStack& importStack) {
     auto backboardImporter =
         importStack.latest<BackboardImporter>(Backboard::typeKey);
@@ -61,4 +84,4 @@
 }
 
 void Image::setMesh(Mesh* mesh) { m_Mesh = mesh; }
-Mesh* Image::mesh() const { return m_Mesh; }
\ No newline at end of file
+Mesh* Image::mesh() const { return m_Mesh; }
diff --git a/src/shapes/shape.cpp b/src/shapes/shape.cpp
index 78fbb30..b7c4cdc 100644
--- a/src/shapes/shape.cpp
+++ b/src/shapes/shape.cpp
@@ -1,3 +1,5 @@
+#include "rive/hittest_command_path.hpp"
+#include "rive/shapes/path.hpp"
 #include "rive/shapes/shape.hpp"
 #include "rive/shapes/clipping_shape.hpp"
 #include "rive/shapes/paint/blend_mode.hpp"
@@ -57,6 +59,48 @@
     }
 }
 
+Core* Shape::hitTest(HitInfo* hinfo, const Mat2D& xform) {
+    if (renderOpacity() == 0.0f) {
+        return nullptr;
+    }
+    
+    // TODO: clip:
+
+    const bool shapeIsLocal = (pathSpace() & PathSpace::Local) == PathSpace::Local;
+
+    for (auto rit = m_ShapePaints.rbegin(); rit != m_ShapePaints.rend(); ++rit) {
+        auto shapePaint = *rit;
+        if (shapePaint->isTranslucent()) {
+            continue;
+        }
+        if (!shapePaint->isVisible()) {
+            continue;
+        }
+
+        auto paintIsLocal = (shapePaint->pathSpace() & PathSpace::Local) == PathSpace::Local;
+
+        auto mx = xform;
+        if (paintIsLocal) {
+            mx *= worldTransform();
+        }
+        
+        HitTestCommandPath tester(hinfo->area);
+        
+        for (auto path : m_Paths) {
+            if (shapeIsLocal) {
+                tester.setXform(xform * path->pathTransform());
+            } else {
+                tester.setXform(mx * path->pathTransform());
+            }
+            path->buildPath(tester);
+        }
+        if (tester.wasHit()) {
+            return this;
+        }
+    }
+    return nullptr;
+}
+
 void Shape::buildDependencies() {
     // Make sure to propagate the call to PathComposer as it's no longer part of
     // Core and owned only by the Shape.