rlm@151: #+title: General sense/effector utilities rlm@151: #+author: Robert McIntyre rlm@151: #+email: rlm@mit.edu rlm@151: #+description: sensory utilities rlm@151: #+keywords: simulation, jMonkeyEngine3, clojure, simulated senses rlm@151: #+SETUPFILE: ../../aurellem/org/setup.org rlm@151: #+INCLUDE: ../../aurellem/org/level-0.org rlm@151: rlm@183: #+name: sense rlm@151: #+begin_src clojure rlm@182: (ns cortex.sense rlm@183: "Here are functions useful in the construction of two or more rlm@183: sensors/effectors." rlm@182: {:author "Robert McInytre"} rlm@182: (:use (cortex world util)) rlm@182: (:import ij.process.ImageProcessor) rlm@182: (:import jme3tools.converters.ImageToAwt) rlm@182: (:import java.awt.image.BufferedImage) rlm@182: (:import com.jme3.collision.CollisionResults) rlm@182: (:import com.jme3.bounding.BoundingBox) rlm@182: (:import (com.jme3.scene Node Spatial)) rlm@182: (:import com.jme3.scene.control.AbstractControl) rlm@182: (:import (com.jme3.math Quaternion Vector3f))) rlm@151: rlm@181: (defn meta-data rlm@181: "Get the meta-data for a node created with blender." rlm@181: [blender-node key] rlm@151: (if-let [data (.getUserData blender-node "properties")] rlm@151: (.findValue data key) rlm@151: nil)) rlm@151: rlm@151: (defn closest-node rlm@182: "Return the node in creature which is closest to the given node." rlm@151: [#^Node creature #^Node eye] rlm@151: (loop [radius (float 0.01)] rlm@151: (let [results (CollisionResults.)] rlm@151: (.collideWith rlm@151: creature rlm@151: (BoundingBox. (.getWorldTranslation eye) rlm@151: radius radius radius) rlm@151: results) rlm@151: (if-let [target (first results)] rlm@151: (.getGeometry target) rlm@151: (recur (float (* 2 radius))))))) rlm@151: rlm@151: (defn bind-sense rlm@151: "Bind the sense to the Spatial such that it will maintain its rlm@151: current position relative to the Spatial no matter how the spatial rlm@151: moves. 'sense can be either a Camera or Listener object." rlm@151: [#^Spatial obj sense] rlm@151: (let [sense-offset (.subtract (.getLocation sense) rlm@151: (.getWorldTranslation obj)) rlm@151: initial-sense-rotation (Quaternion. (.getRotation sense)) rlm@151: base-anti-rotation (.inverse (.getWorldRotation obj))] rlm@151: (.addControl rlm@151: obj rlm@151: (proxy [AbstractControl] [] rlm@151: (controlUpdate [tpf] rlm@151: (let [total-rotation rlm@151: (.mult base-anti-rotation (.getWorldRotation obj))] rlm@181: (.setLocation rlm@181: sense rlm@181: (.add rlm@181: (.mult total-rotation sense-offset) rlm@181: (.getWorldTranslation obj))) rlm@181: (.setRotation rlm@181: sense rlm@181: (.mult total-rotation initial-sense-rotation)))) rlm@151: (controlRender [_ _]))))) rlm@151: rlm@181: (def white 0xFFFFFF) rlm@181: rlm@181: (defn white? [rgb] rlm@181: (= (bit-and white rgb) white)) rlm@181: rlm@151: (defn filter-pixels rlm@151: "List the coordinates of all pixels matching pred, within the bounds rlm@182: provided. rlm@182: bounds -> [x0 y0 width height]" rlm@151: {:author "Dylan Holmes"} rlm@151: ([pred #^BufferedImage image] rlm@151: (filter-pixels pred image [0 0 (.getWidth image) (.getHeight image)])) rlm@151: ([pred #^BufferedImage image [x0 y0 width height]] rlm@151: ((fn accumulate [x y matches] rlm@151: (cond rlm@151: (>= y (+ height y0)) matches rlm@151: (>= x (+ width x0)) (recur 0 (inc y) matches) rlm@151: (pred (.getRGB image x y)) rlm@151: (recur (inc x) y (conj matches [x y])) rlm@151: :else (recur (inc x) y matches))) rlm@151: x0 y0 []))) rlm@151: rlm@151: (defn white-coordinates rlm@151: "Coordinates of all the white pixels in a subset of the image." rlm@151: ([#^BufferedImage image bounds] rlm@181: (filter-pixels white? image bounds)) rlm@151: ([#^BufferedImage image] rlm@181: (filter-pixels white? image))) rlm@151: rlm@151: (defn points->image rlm@151: "Take a sparse collection of points and visuliaze it as a rlm@151: BufferedImage." rlm@151: [points] rlm@151: (if (empty? points) rlm@151: (BufferedImage. 1 1 BufferedImage/TYPE_BYTE_BINARY) rlm@151: (let [xs (vec (map first points)) rlm@151: ys (vec (map second points)) rlm@151: x0 (apply min xs) rlm@151: y0 (apply min ys) rlm@151: width (- (apply max xs) x0) rlm@151: height (- (apply max ys) y0) rlm@151: image (BufferedImage. (inc width) (inc height) rlm@151: BufferedImage/TYPE_INT_RGB)] rlm@151: (dorun rlm@151: (for [x (range (.getWidth image)) rlm@151: y (range (.getHeight image))] rlm@151: (.setRGB image x y 0xFF0000))) rlm@151: (dorun rlm@151: (for [index (range (count points))] rlm@151: (.setRGB image (- (xs index) x0) (- (ys index) y0) -1))) rlm@151: image))) rlm@151: rlm@151: (defn average [coll] rlm@151: (/ (reduce + coll) (count coll))) rlm@151: rlm@151: (defn collapse-1d rlm@182: "One dimensional analogue of collapse." rlm@151: [center line] rlm@151: (let [length (count line) rlm@151: num-above (count (filter (partial < center) line)) rlm@151: num-below (- length num-above)] rlm@151: (range (- center num-below) rlm@151: (+ center num-above)))) rlm@151: rlm@151: (defn collapse rlm@151: "Take a set of pairs of integers and collapse them into a rlm@182: contigous bitmap with no \"holes\"." rlm@151: [points] rlm@151: (if (empty? points) [] rlm@151: (let rlm@151: [num-points (count points) rlm@151: center (vector rlm@151: (int (average (map first points))) rlm@151: (int (average (map first points)))) rlm@151: flattened rlm@151: (reduce rlm@151: concat rlm@151: (map rlm@151: (fn [column] rlm@151: (map vector rlm@151: (map first column) rlm@151: (collapse-1d (second center) rlm@151: (map second column)))) rlm@151: (partition-by first (sort-by first points)))) rlm@151: squeezed rlm@151: (reduce rlm@151: concat rlm@151: (map rlm@151: (fn [row] rlm@151: (map vector rlm@151: (collapse-1d (first center) rlm@151: (map first row)) rlm@151: (map second row))) rlm@151: (partition-by second (sort-by second flattened)))) rlm@182: relocated rlm@151: (let [min-x (apply min (map first squeezed)) rlm@151: min-y (apply min (map second squeezed))] rlm@151: (map (fn [[x y]] rlm@151: [(- x min-x) rlm@151: (- y min-y)]) rlm@151: squeezed))] rlm@182: relocated))) rlm@151: rlm@156: (defn world-to-local rlm@182: "Convert the world coordinates into coordinates relative to the rlm@156: object (i.e. local coordinates), taking into account the rotation rlm@156: of object." rlm@182: [#^Spatial object world-coordinate] rlm@182: (.worldToLocal object world-coordinate nil)) rlm@156: rlm@156: (defn local-to-world rlm@182: "Convert the local coordinates into world relative coordinates" rlm@182: [#^Spatial object local-coordinate] rlm@182: (.localToWorld object local-coordinate nil)) rlm@156: rlm@182: (defn sense-nodes rlm@182: "For each sense there is a special blender node whose children are rlm@182: considered markers for an instance of a that sense. This function rlm@182: generates functions to find those children, given the name of the rlm@182: special parent node." rlm@182: [parent-name] rlm@164: (fn [#^Node creature] rlm@164: (if-let [sense-node (.getChild creature parent-name)] rlm@164: (seq (.getChildren sense-node)) rlm@164: (do (println-repl "could not find" parent-name "node") [])))) rlm@164: rlm@167: (defn load-image rlm@167: "Load an image as a BufferedImage using the asset-manager system." rlm@167: [asset-relative-path] rlm@167: (ImageToAwt/convert rlm@167: (.getImage (.loadTexture (asset-manager) asset-relative-path)) rlm@167: false false 0)) rlm@164: rlm@181: (defn jme-to-blender rlm@181: "Convert from JME coordinates to Blender coordinates" rlm@181: [#^Vector3f in] rlm@181: (Vector3f. (.getX in) rlm@181: (- (.getZ in)) rlm@181: (.getY in))) rlm@181: rlm@181: (defn blender-to-jme rlm@181: "Convert from Blender coordinates to JME coordinates" rlm@181: [#^Vector3f in] rlm@181: (Vector3f. (.getX in) rlm@181: (.getZ in) rlm@181: (- (.getY in)))) rlm@187: rlm@187: rlm@187: (defn view-sense rlm@187: "Take a function that produces a BufferedImage from some sense data rlm@187: and return a function which takes a list of sense and displays rlm@187: those images in multiple JFrames." rlm@187: [sense-display-kernel] rlm@187: (let rlm@187: [windows (atom [])] rlm@187: (fn [data] rlm@187: (if (> (count data) (count @windows)) rlm@187: (reset! windows (map (fn [_] (view-image)) rlm@187: (range (count data))))) rlm@187: (dorun rlm@187: (map rlm@187: (fn [display gen data] rlm@187: (display (gen data))) rlm@187: @windows sense-display-kernel data))))) rlm@187: rlm@187: rlm@187: rlm@151: #+end_src rlm@151: rlm@151: rlm@151: * COMMENT generate source rlm@151: #+begin_src clojure :tangle ../src/cortex/sense.clj rlm@183: <> rlm@151: #+end_src