rlm@37: #+title: Simulated Sense of Touch rlm@0: #+author: Robert McIntyre rlm@0: #+email: rlm@mit.edu rlm@37: #+description: Simulated touch for AI research using JMonkeyEngine and clojure. rlm@37: #+keywords: simulation, tactile sense, jMonkeyEngine3, clojure rlm@4: #+SETUPFILE: ../../aurellem/org/setup.org rlm@4: #+INCLUDE: ../../aurellem/org/level-0.org rlm@0: rlm@37: * Touch rlm@0: rlm@226: Touch is critical to navigation and spatial reasoning and as such I rlm@226: need a simulated version of it to give to my AI creatures. rlm@0: rlm@66: #+name: skin-main rlm@0: #+begin_src clojure rlm@226: (in-ns 'cortex.touch) rlm@226: rlm@37: (defn triangles rlm@37: "Return a sequence of all the Triangles which compose a given rlm@37: Geometry." rlm@37: [#^Geometry geom] rlm@6: (let rlm@6: [mesh (.getMesh geom) rlm@6: triangles (transient [])] rlm@6: (dorun rlm@6: (for [n (range (.getTriangleCount mesh))] rlm@6: (let [tri (Triangle.)] rlm@6: (.getTriangle mesh n tri) rlm@37: ;; (.calculateNormal tri) rlm@37: ;; (.calculateCenter tri) rlm@6: (conj! triangles tri)))) rlm@6: (persistent! triangles))) rlm@6: rlm@7: (defn get-ray-origin rlm@37: "Return the origin which a Ray would have to have to be in the exact rlm@37: center of a particular Triangle in the Geometry in World rlm@37: Coordinates." rlm@7: [geom tri] rlm@7: (let [new (Vector3f.)] rlm@7: (.calculateCenter tri) rlm@37: (.localToWorld geom (.getCenter tri) new) new)) rlm@6: rlm@7: (defn get-ray-direction rlm@156: "Return the direction which a Ray would have to have to be to point rlm@37: normal to the Triangle, in coordinates relative to the center of the rlm@37: Triangle." rlm@7: [geom tri] rlm@9: (let [n+c (Vector3f.)] rlm@7: (.calculateNormal tri) rlm@9: (.calculateCenter tri) rlm@37: (.localToWorld rlm@37: geom rlm@37: (.add (.getCenter tri) (.getNormal tri)) n+c) rlm@37: (.subtract n+c (get-ray-origin geom tri)))) rlm@37: rlm@156: ;; Every Mesh has many triangles, each with its own index. rlm@156: ;; Every vertex has its own index as well. rlm@37: rlm@178: (defn tactile-sensor-profile rlm@156: "Return the touch-sensor distribution image in BufferedImage format, rlm@156: or nil if it does not exist." rlm@156: [#^Geometry obj] rlm@156: (if-let [image-path (meta-data obj "touch")] rlm@178: (load-image image-path))) rlm@156: rlm@156: (defn triangle rlm@156: "Get the triangle specified by triangle-index from the mesh within rlm@156: bounds." rlm@156: [#^Mesh mesh triangle-index] rlm@156: (let [scratch (Triangle.)] rlm@156: (.getTriangle mesh triangle-index scratch) rlm@156: scratch)) rlm@156: rlm@156: (defn triangle-vertex-indices rlm@156: "Get the triangle vertex indices of a given triangle from a given rlm@156: mesh." rlm@156: [#^Mesh mesh triangle-index] rlm@156: (let [indices (int-array 3)] rlm@156: (.getTriangle mesh triangle-index indices) rlm@156: (vec indices))) rlm@156: rlm@156: (defn vertex-UV-coord rlm@156: "Get the uv-coordinates of the vertex named by vertex-index" rlm@156: [#^Mesh mesh vertex-index] rlm@156: (let [UV-buffer rlm@156: (.getData rlm@156: (.getBuffer rlm@156: mesh rlm@156: VertexBuffer$Type/TexCoord))] rlm@156: [(.get UV-buffer (* vertex-index 2)) rlm@156: (.get UV-buffer (+ 1 (* vertex-index 2)))])) rlm@156: rlm@156: (defn triangle-UV-coord rlm@156: "Get the uv-cooridnates of the triangle's verticies." rlm@156: [#^Mesh mesh width height triangle-index] rlm@156: (map (fn [[u v]] (vector (* width u) (* height v))) rlm@156: (map (partial vertex-UV-coord mesh) rlm@156: (triangle-vertex-indices mesh triangle-index)))) rlm@156: rlm@156: (defn same-side? rlm@156: "Given the points p1 and p2 and the reference point ref, is point p rlm@156: on the same side of the line that goes through p1 and p2 as ref is?" rlm@156: [p1 p2 ref p] rlm@156: (<= rlm@156: 0 rlm@156: (.dot rlm@156: (.cross (.subtract p2 p1) (.subtract p p1)) rlm@156: (.cross (.subtract p2 p1) (.subtract ref p1))))) rlm@156: rlm@156: (defn triangle-seq [#^Triangle tri] rlm@156: [(.get1 tri) (.get2 tri) (.get3 tri)]) rlm@156: rlm@156: (defn vector3f-seq [#^Vector3f v] rlm@156: [(.getX v) (.getY v) (.getZ v)]) rlm@156: rlm@156: (defn inside-triangle? rlm@156: "Is the point inside the triangle?" rlm@156: {:author "Dylan Holmes"} rlm@156: [#^Triangle tri #^Vector3f p] rlm@156: (let [[vert-1 vert-2 vert-3] (triangle-seq tri)] rlm@156: (and rlm@156: (same-side? vert-1 vert-2 vert-3 p) rlm@156: (same-side? vert-2 vert-3 vert-1 p) rlm@156: (same-side? vert-3 vert-1 vert-2 p)))) rlm@156: rlm@156: (defn triangle->matrix4f rlm@156: "Converts the triangle into a 4x4 matrix: The first three columns rlm@156: contain the vertices of the triangle; the last contains the unit rlm@156: normal of the triangle. The bottom row is filled with 1s." rlm@156: [#^Triangle t] rlm@156: (let [mat (Matrix4f.) rlm@156: [vert-1 vert-2 vert-3] rlm@156: ((comp vec map) #(.get t %) (range 3)) rlm@156: unit-normal (do (.calculateNormal t)(.getNormal t)) rlm@156: vertices [vert-1 vert-2 vert-3 unit-normal]] rlm@156: (dorun rlm@156: (for [row (range 4) col (range 3)] rlm@37: (do rlm@156: (.set mat col row (.get (vertices row)col)) rlm@156: (.set mat 3 row 1)))) rlm@156: mat)) rlm@156: rlm@156: (defn triangle-transformation rlm@156: "Returns the affine transformation that converts each vertex in the rlm@156: first triangle into the corresponding vertex in the second rlm@156: triangle." rlm@156: [#^Triangle tri-1 #^Triangle tri-2] rlm@156: (.mult rlm@156: (triangle->matrix4f tri-2) rlm@156: (.invert (triangle->matrix4f tri-1)))) rlm@156: rlm@156: (defn point->vector2f [[u v]] rlm@156: (Vector2f. u v)) rlm@156: rlm@156: (defn vector2f->vector3f [v] rlm@156: (Vector3f. (.getX v) (.getY v) 0)) rlm@156: rlm@156: (defn map-triangle [f #^Triangle tri] rlm@156: (Triangle. rlm@156: (f 0 (.get1 tri)) rlm@156: (f 1 (.get2 tri)) rlm@156: (f 2 (.get3 tri)))) rlm@156: rlm@156: (defn points->triangle rlm@156: "Convert a list of points into a triangle." rlm@156: [points] rlm@156: (apply #(Triangle. %1 %2 %3) rlm@156: (map (fn [point] rlm@156: (let [point (vec point)] rlm@156: (Vector3f. (get point 0 0) rlm@156: (get point 1 0) rlm@156: (get point 2 0)))) rlm@156: (take 3 points)))) rlm@156: rlm@156: (defn convex-bounds rlm@178: "Returns the smallest square containing the given vertices, as a rlm@178: vector of integers [left top width height]." rlm@156: [uv-verts] rlm@156: (let [xs (map first uv-verts) rlm@156: ys (map second uv-verts) rlm@156: x0 (Math/floor (apply min xs)) rlm@156: y0 (Math/floor (apply min ys)) rlm@156: x1 (Math/ceil (apply max xs)) rlm@156: y1 (Math/ceil (apply max ys))] rlm@156: [x0 y0 (- x1 x0) (- y1 y0)])) rlm@156: rlm@156: (defn sensors-in-triangle rlm@178: "Locate the touch sensors in the triangle, returning a map of their rlm@178: UV and geometry-relative coordinates." rlm@156: [image mesh tri-index] rlm@156: (let [width (.getWidth image) rlm@156: height (.getHeight image) rlm@156: UV-vertex-coords (triangle-UV-coord mesh width height tri-index) rlm@156: bounds (convex-bounds UV-vertex-coords) rlm@156: rlm@156: cutout-triangle (points->triangle UV-vertex-coords) rlm@156: UV-sensor-coords rlm@156: (filter (comp (partial inside-triangle? cutout-triangle) rlm@156: (fn [[u v]] (Vector3f. u v 0))) rlm@156: (white-coordinates image bounds)) rlm@156: UV->geometry (triangle-transformation rlm@156: cutout-triangle rlm@156: (triangle mesh tri-index)) rlm@156: geometry-sensor-coords rlm@156: (map (fn [[u v]] (.mult UV->geometry (Vector3f. u v 0))) rlm@156: UV-sensor-coords)] rlm@156: {:UV UV-sensor-coords :geometry geometry-sensor-coords})) rlm@156: rlm@156: (defn-memo locate-feelers rlm@178: "Search the geometry's tactile UV profile for touch sensors, rlm@178: returning their positions in geometry-relative coordinates." rlm@156: [#^Geometry geo] rlm@156: (let [mesh (.getMesh geo) rlm@156: num-triangles (.getTriangleCount mesh)] rlm@178: (if-let [image (tactile-sensor-profile geo)] rlm@156: (map rlm@156: (partial sensors-in-triangle image mesh) rlm@156: (range num-triangles)) rlm@156: (repeat (.getTriangleCount mesh) {:UV nil :geometry nil})))) rlm@156: rlm@178: (defn-memo touch-topology rlm@178: "Return a sequence of vectors of the form [x y] describing the rlm@178: \"topology\" of the tactile sensors. Points that are close together rlm@178: in the touch-topology are generally close together in the simulation." rlm@178: [#^Gemoetry geo] rlm@156: (vec (collapse (reduce concat (map :UV (locate-feelers geo)))))) rlm@156: rlm@178: (defn-memo feeler-coordinates rlm@178: "The location of the touch sensors in world-space coordinates." rlm@178: [#^Geometry geo] rlm@156: (vec (map :geometry (locate-feelers geo)))) rlm@156: rlm@178: (defn touch-fn rlm@178: "Returns a function which returns tactile sensory data when called rlm@178: inside a running simulation." rlm@178: [#^Geometry geo] rlm@156: (let [feeler-coords (feeler-coordinates geo) rlm@156: tris (triangles geo) rlm@156: limit 0.1 rlm@156: ;;results (CollisionResults.) rlm@156: ] rlm@156: (if (empty? (touch-topology geo)) rlm@156: nil rlm@156: (fn [node] rlm@156: (let [sensor-origins rlm@156: (map rlm@156: #(map (partial local-to-world geo) %) rlm@156: feeler-coords) rlm@156: triangle-normals rlm@156: (map (partial get-ray-direction geo) rlm@156: tris) rlm@156: rays rlm@156: (flatten rlm@156: (map (fn [origins norm] rlm@156: (map #(doto (Ray. % norm) rlm@156: (.setLimit limit)) origins)) rlm@156: sensor-origins triangle-normals))] rlm@156: (vector rlm@156: (touch-topology geo) rlm@156: (vec rlm@156: (for [ray rays] rlm@156: (do rlm@156: (let [results (CollisionResults.)] rlm@156: (.collideWith node ray results) rlm@156: (let [touch-objects rlm@156: (filter #(not (= geo (.getGeometry %))) rlm@156: results)] rlm@156: (- 255 rlm@156: (if (empty? touch-objects) 255 rlm@156: (rem rlm@156: (int rlm@156: (* 255 (/ (.getDistance rlm@156: (first touch-objects)) limit))) rlm@156: 256)))))))))))))) rlm@156: rlm@178: (defn touch! rlm@178: "Endow the creature with the sense of touch. Returns a sequence of rlm@178: functions, one for each body part with a tactile-sensor-proile, rlm@178: each of which when called returns sensory data for that body part." rlm@178: [#^Node creature] rlm@178: (filter rlm@178: (comp not nil?) rlm@178: (map touch-fn rlm@178: (filter #(isa? (class %) Geometry) rlm@178: (node-seq creature))))) rlm@156: rlm@188: (defn view-touch rlm@189: "Creates a function which accepts a list of touch sensor-data and rlm@189: displays each element to the screen." rlm@188: [] rlm@187: (view-sense rlm@187: (fn rlm@187: [[coords sensor-data]] rlm@187: (let [image (points->image coords)] rlm@187: (dorun rlm@187: (for [i (range (count coords))] rlm@187: (.setRGB image ((coords i) 0) ((coords i) 1) rlm@187: (gray (sensor-data i))))) rlm@188: image)))) rlm@37: #+end_src rlm@37: rlm@226: * Headers rlm@226: #+begin_src clojure rlm@226: (ns cortex.touch rlm@226: "Simulate the sense of touch in jMonkeyEngine3. Enables any Geometry rlm@226: to be outfitted with touch sensors with density determined by a UV rlm@226: image. In this way a Geometry can know what parts of itself are rlm@226: touching nearby objects. Reads specially prepared blender files to rlm@226: construct this sense automatically." rlm@226: {:author "Robert McIntyre"} rlm@226: (:use (cortex world util sense)) rlm@226: (:use clojure.contrib.def) rlm@226: (:import (com.jme3.scene Geometry Node Mesh)) rlm@226: (:import com.jme3.collision.CollisionResults) rlm@226: (:import com.jme3.scene.VertexBuffer$Type) rlm@226: (:import (com.jme3.math Triangle Vector3f Vector2f Ray Matrix4f))) rlm@226: #+end_src rlm@37: rlm@226: * COMMENT Code Generation rlm@39: #+begin_src clojure :tangle ../src/cortex/touch.clj rlm@0: <> rlm@0: #+end_src rlm@0: rlm@68: #+begin_src clojure :tangle ../src/cortex/test/touch.clj rlm@39: #+end_src rlm@39: rlm@0: rlm@0: rlm@0: rlm@32: rlm@32: rlm@226: