Move SSD review hit testing to typed Kotlin

ober

e9d9b216db1c6d8156ad0d5d38ee5623d17f8f93

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 086687d..bc24666 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -902,6 +902,31 @@
            (imageWidth : Int32)
            (imageHeight : Int32)))))
 
+    (typed-kotlin-file "com/sfb/ssdreview/SsdReviewGeometry.kt"
+      (typed-library (com sfb ssdreview)
+        (export hitGroupId)
+        (type Float32)
+        (type FloatArray)
+        (def (pointInPaddedGroup? (group : SsdGroup)
+                                  (x : Float32)
+                                  (y : Float32)
+                                  (pad : Float32)) : Bool
+          (let ((bbox (SsdGroup-bbox group)))
+            (and (>= x (- (float-array-ref bbox (int32 0)) pad))
+                 (and (<= x (+ (float-array-ref bbox (int32 2)) pad))
+                      (and (>= y (- (float-array-ref bbox (int32 1)) pad))
+                           (<= y (+ (float-array-ref bbox (int32 3)) pad)))))))
+        (def (hitGroupId (groups : (MutableList SsdGroup))
+                         (x : Float32)
+                         (y : Float32)
+                         (pad : Float32)) : (Nullable String)
+          (for/fold ((found (nullable-none String)))
+                    ((i (in-range (int32 0) (list-size groups))))
+            (let ((group (list-ref groups i)))
+              (if (pointInPaddedGroup? group x y pad)
+                  (nullable-some (SsdGroup-id group))
+                  found))))))
+
     (typed-kotlin-file "com/sfb/ssdreview/TruthStoreCounters.kt"
       (typed-library (com sfb ssdreview)
         (export make-ImportBudget ImportBudget?
@@ -5551,12 +5576,7 @@
        "    private fun hitGroup(x: Float, y: Float): String? {"
        "        val sess = session ?: return null"
        "        val pad = max(8f, 32f / scale)"
-       "        return sess.groups.asReversed().firstOrNull { group ->"
-       "            x >= group.bbox[0] - pad &&"
-       "                x <= group.bbox[2] + pad &&"
-       "                y >= group.bbox[1] - pad &&"
-       "                y <= group.bbox[3] + pad"
-       "        }?.id"
+       "        return hitGroupId(sess.groups, x, y, pad)"
        "    }"
        ""
        "}"