# HG changeset patch # User Robert McIntyre # Date 1319439266 25200 # Node ID cab2da252494064a7859f4e0ebefb597af1fe966 # Parent 157b416152ea4e28b24b3fb2d189cbe1a532bfcd split off the rest of cortex.org diff -r 157b416152ea -r cab2da252494 org/cortex.org --- a/org/cortex.org Sun Oct 23 23:35:04 2011 -0700 +++ b/org/cortex.org Sun Oct 23 23:54:26 2011 -0700 @@ -9,414 +9,12 @@ * Simulation Base -** Imports -jMonkeyEngine has a plethora of classes which can be overwhelming at -first. So that I one can get right to coding, it's good to take the -time right now and make a "import all" function which brings in all of -the important jme3 classes. Once I'm happy with the general structure -of a namespace I can deal with importing only the classes it actually -needs. - -#+srcname: import -#+begin_src clojure :results silent -(ns cortex.import - (:require swank.util.class-browse)) - -(defn permissive-import - [classname] - (eval `(try (import '~classname) - (catch java.lang.Exception e# - (println "couldn't import " '~classname)))) - classname) - -(defn jme-class? [classname] - (and - (.startsWith classname "com.jme3.") - ;; Don't import the Lwjgl stuff since it can throw exceptions - ;; upon being loaded. - (not (re-matches #".*Lwjgl.*" classname)))) - -(defn jme-classes - "returns a list of all jme3 classes" - [] - (filter - jme-class? - (map :name - swank.util.class-browse/available-classes))) - -(defn mega-import-jme3 - "Import ALL the jme classes. For REPL use." - [] - (doall - (map (comp permissive-import symbol) (jme-classes)))) -#+end_src - -The =mega-import-jme3= is quite usefull for debugging purposes since -it allows completion for almost all of JME's classes. - -Out of curiousity, let's see just how many classes =mega-import-jme3= -imports: - -#+begin_src clojure :exports both -(clojure.core/count (cortex.import/jme-classes)) -#+end_src - -#+results: -: 955 - -** Simplification -*** World - -It is comvienent to wrap the JME elements that deal with creating a -world, creation of basic objects, and Keyboard input with a nicer -interface (at least for my purposes). - -#+srcname: world-inputs -#+begin_src clojure :results silent -(ns cortex.world) -(require 'cortex.import) -(use 'clojure.contrib.def) -(rlm.rlm-commands/help) -(cortex.import/mega-import-jme3) - -(defvar *app-settings* - (doto (AppSettings. true) - (.setFullscreen false) - (.setTitle "Aurellem.") - ;; disable 32 bit stuff for now - ;;(.setAudioRenderer "Send") - ) - "These settings control how the game is displayed on the screen for - debugging purposes. Use binding forms to change this if desired. - Full-screen mode does not work on some computers.") - -(defn asset-manager - "returns a new, configured assetManager" [] - (JmeSystem/newAssetManager - (.getResource - (.getContextClassLoader (Thread/currentThread)) - "com/jme3/asset/Desktop.cfg"))) - -(defmacro no-exceptions - "Sweet relief like I never knew." - [& forms] - `(try ~@forms (catch Exception e# (.printStackTrace e#)))) - -(defn thread-exception-removal [] - (println "removing exceptions from " (Thread/currentThread)) - (.setUncaughtExceptionHandler - (Thread/currentThread) - (proxy [Thread$UncaughtExceptionHandler] [] - (uncaughtException - [thread thrown] - (println "uncaught-exception thrown in " thread) - (println (.getMessage thrown)))))) - -(def println-repl (bound-fn [& args] (apply println args))) - -(use '[pokemon [lpsolve :only [constant-map]]]) - -(defn no-op [& _]) - -(defn all-keys - "Construct a map of strings representing all the manual inputs from - either the keyboard or mouse." - [] - (let [inputs (constant-map KeyInput)] - (assoc - (zipmap (map (fn [field] - (.toLowerCase (re-gsub #"_" "-" field))) (vals inputs)) - (map (fn [val] (KeyTrigger. val)) (keys inputs))) - ;;explicitly add mouse controls - "mouse-left" (MouseButtonTrigger. 0) - "mouse-middle" (MouseButtonTrigger. 2) - "mouse-right" (MouseButtonTrigger. 1)))) - -(defn initialize-inputs - "more java-interop cruft to establish keybindings for a particular virtual world" - [game input-manager key-map] - (doall (map (fn [[name trigger]] - (.addMapping ^InputManager input-manager - name (into-array (class trigger) [trigger]))) key-map)) - (doall (map (fn [name] - (.addListener ^InputManager input-manager game - (into-array String [name]))) (keys key-map)))) - -#+end_src - -These functions are all for debug controlling of the world through -keyboard and mouse. - -We reuse =constant-map= from =pokemon.lpsolve= to get the numerical -values for all the keys defined in the =KeyInput= class. The -documentation for =constant-map= is: - -#+begin_src clojure :results output -(doc pokemon.lpsolve/constant-map) -#+end_src - -#+results: -: ------------------------- -: pokemon.lpsolve/constant-map -: ([class]) -: Takes a class and creates a map of the static constant integer -: fields with their names. This helps with C wrappers where they have -: just defined a bunch of integer constants instead of enums - - -Then, =all-keys= converts the constant names like =KEY_J= to the more -clojure-like =key-j=, and returns a map from these keys to -jMonkeyEngine KeyTrigger objects, the use of which will soon become -apparent. =all-keys= also adds the three mouse button controls to the -map. - -#+srcname: world -#+begin_src clojure :results silent -(in-ns 'cortex.world) - -(defn traverse - "apply f to every non-node, deeply" - [f node] - (if (isa? (class node) Node) - (dorun (map (partial traverse f) (.getChildren node))) - (f node))) - -(def gravity (Vector3f. 0 -9.81 0)) - -(defn world - [root-node key-map setup-fn update-fn] - (let [physics-manager (BulletAppState.) - shadow-renderer (BasicShadowRenderer. (asset-manager) (int 256)) - ;;maybe use a better shadow renderer someday! - ;;shadow-renderer (PssmShadowRenderer. (asset-manager) 256 1) - ] - (doto - (proxy [SimpleApplication ActionListener] [] - (simpleInitApp - [] - (no-exceptions - (.setTimer this (IsoTimer. 60)) - ;; Create key-map. - (.setFrustumFar (.getCamera this) 300) - (initialize-inputs this (.getInputManager this) (all-keys)) - ;; Don't take control of the mouse - (org.lwjgl.input.Mouse/setGrabbed false) - ;; add all objects to the world - (.attachChild (.getRootNode this) root-node) - ;; enable physics - ;; add a physics manager - (.attach (.getStateManager this) physics-manager) - (.setGravity (.getPhysicsSpace physics-manager) gravity) - - - ;; go through every object and add it to the physics manager - ;; if relavant. - (traverse (fn [geom] - (dorun - (for [n (range (.getNumControls geom))] - (do - (println-repl "adding control " (.getControl geom n)) - (.add (.getPhysicsSpace physics-manager) - (.getControl geom n)))))) - (.getRootNode this)) - ;;(.addAll (.getPhysicsSpace physics-manager) (.getRootNode this)) - - (setup-fn this) - (.setDirection shadow-renderer - (.normalizeLocal (Vector3f. -1 -1 -1))) - (.addProcessor (.getViewPort this) shadow-renderer) - (.setShadowMode (.getRootNode this) RenderQueue$ShadowMode/Off) - )) - (simpleUpdate - [tpf] - (no-exceptions - (update-fn this tpf))) - (onAction - [binding value tpf] - ;; whenever a key is pressed, call the function returned from - ;; key-map. - (no-exceptions - (if-let [react (key-map binding)] - (react this value))))) - ;; don't show a menu to change options. - - (.setShowSettings false) - (.setPauseOnLostFocus false) - (.setSettings *app-settings*)))) - -(defn apply-map - "Like apply, but works for maps and functions that expect an implicit map - and nothing else as in (fn [& {}]). -------- Example ------- - (defn jjj [& {:keys [www] :or {www \"oh yeah\"} :as env}] (println www)) - (apply-map jjj {:www \"whatever\"}) - -->\"whatever\"" - [fn m] - (apply fn (reduce #(into %1 %2) [] m))) - -#+end_src - - -=world= is the most important function here. -*** TODO more documentation - -#+srcname: world-shapes -#+begin_src clojure :results silent -(in-ns 'cortex.world) -(defrecord shape-description - [name - color - mass - friction - texture - material - position - rotation - shape - physical?]) - -(def base-shape - (shape-description. - "default-shape" - false - ;;ColorRGBA/Blue - 1.0 ;; mass - 1.0 ;; friction - ;; texture - "Textures/Terrain/BrickWall/BrickWall.jpg" - ;; material - "Common/MatDefs/Misc/Unshaded.j3md" - Vector3f/ZERO - Quaternion/IDENTITY - (Box. Vector3f/ZERO 0.5 0.5 0.5) - true)) - -(defn make-shape - [#^shape-description d] - (let [asset-manager (if (:asset-manager d) (:asset-manager d) (asset-manager)) - mat (Material. asset-manager (:material d)) - geom (Geometry. (:name d) (:shape d))] - (if (:texture d) - (let [key (TextureKey. (:texture d))] - (.setGenerateMips key true) - (.setTexture mat "ColorMap" (.loadTexture asset-manager key)))) - (if (:color d) (.setColor mat "Color" (:color d))) - (.setMaterial geom mat) - (if-let [rotation (:rotation d)] (.rotate geom rotation)) - (.setLocalTranslation geom (:position d)) - (if (:physical? d) - (let [impact-shape (doto (GImpactCollisionShape. - (.getMesh geom)) (.setMargin 0)) - physics-control (RigidBodyControl. - ;;impact-shape ;; comment to disable - (float (:mass d)))] - (.createJmeMesh impact-shape) - (.addControl geom physics-control) - ;;(.setSleepingThresholds physics-control (float 0) (float 0)) - (.setFriction physics-control (:friction d)))) - ;;the default is to keep this node in the physics engine forever. - ;;these commands must come after the control is added to the geometry. - ;; - geom)) - -(defn box - ([l w h & {:as options}] - (let [options (merge base-shape options)] - (make-shape (assoc options - :shape (Box. l w h))))) - ([] (box 0.5 0.5 0.5))) - -(defn sphere - ([r & {:as options}] - (let [options (merge base-shape options)] - (make-shape (assoc options - :shape (Sphere. 32 32 (float r)))))) - ([] (sphere 0.5))) - -(defn add-element - ([game element node] - (.addAll - (.getPhysicsSpace - (.getState - (.getStateManager game) - BulletAppState)) - element) - (.attachChild node element)) - ([game element] - (add-element game element (.getRootNode game)))) - - -(defn set-gravity* - [game gravity] - (traverse - (fn [geom] - (if-let - [control (.getControl geom RigidBodyControl)] - (do - (.setGravity control gravity) - (.applyImpulse control Vector3f/ZERO Vector3f/ZERO) - ))) - (.getRootNode game))) - -#+end_src - -These are convienence functions for creating JME objects and -manipulating a world. - -#+srcname: world-view -#+begin_src clojure :results silent -(in-ns 'cortex.world) - -(defprotocol Viewable - (view [something])) - -(extend-type com.jme3.scene.Geometry - Viewable - (view [geo] - (view (doto (Node.)(.attachChild geo))))) - -(extend-type com.jme3.scene.Node - Viewable - (view [node] - (.start - (world node - {} - (fn [world] - (.enableDebug - (.getPhysicsSpace - (.getState - (.getStateManager world) - BulletAppState)) - (asset-manager)) - (set-gravity* world Vector3f/ZERO) -;; (set-gravity* world (Vector3f. 0 (float -0.4) 0)) - (let [sun (doto (DirectionalLight.) - (.setDirection (.normalizeLocal (Vector3f. 1 0 -2))) - (.setColor ColorRGBA/White))] - (.addLight (.getRootNode world) sun))) - no-op)))) - -(defn position-camera [game] - (doto (.getCamera game) - (.setLocation (Vector3f. 0 6 6)) - (.lookAt Vector3f/ZERO (Vector3f. 0 1 0)))) - -#+end_src - -Here I make the =Viewable= protocol and extend it to JME's types. Now -hello-world can be written as easily as: - -#+begin_src clojure :results silent -(cortex.world/view (cortex.world/box)) -#+end_src ** Hello Here are the jmonkeyengine "Hello" programs translated to clojure. *** Hello Simple App -Here is the hello world example for jme3 in clojure. -It's a more or less direct translation from the java source -from -http://jmonkeyengine.org/wiki/doku.php/jme3:beginner:hello_simpleapplication. +Here is the hello world example for jme3 in clojure. It's a more or +less direct translation from the java source [[http://jmonkeyengine.org/wiki/doku.php/jme3:beginner:hello_simpleapplication][here]]. Of note is the fact that since we don't have access to the =AssetManager= via extendig =SimpleApplication=, we have to build one diff -r 157b416152ea -r cab2da252494 org/eyes.org --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org/eyes.org Sun Oct 23 23:54:26 2011 -0700 @@ -0,0 +1,243 @@ +#+title: Eyes +#+author: Robert McIntyre +#+email: rlm@mit.edu +#+description: Simulating senses for AI research using JMonkeyEngine3 +#+SETUPFILE: ../../aurellem/org/setup.org +#+INCLUDE: ../../aurellem/org/level-0.org +#+babel: :mkdirp yes :noweb yes :exports both + + + +** Eyes + +Ultimately I want to make creatures with eyes. Each eye can be +independely moved and should see its own version of the world +depending on where it is. +#+srcname: eyes +#+begin_src clojure +(ns body.eye) +(use 'cortex.world) +(use 'cortex.import) +(use 'clojure.contrib.def) +(cortex.import/mega-import-jme3) +(rlm.rlm-commands/help) +(import java.nio.ByteBuffer) +(import java.awt.image.BufferedImage) +(import java.awt.Color) +(import java.awt.Dimension) +(import java.awt.Graphics) +(import java.awt.Graphics2D) +(import java.awt.event.WindowAdapter) +(import java.awt.event.WindowEvent) +(import java.awt.image.BufferedImage) +(import java.nio.ByteBuffer) +(import javax.swing.JFrame) +(import javax.swing.JPanel) +(import javax.swing.SwingUtilities) +(import javax.swing.ImageIcon) +(import javax.swing.JOptionPane) +(import java.awt.image.ImageObserver) + + + +(defn scene-processor + "deals with converting FrameBuffers to BufferedImages so + that the continuation function can be defined only in terms + of what it does with BufferedImages" + [continuation] + (let [byte-buffer (atom nil) + renderer (atom nil) + image (atom nil)] + (proxy [SceneProcessor] [] + (initialize + [renderManager viewPort] + (let [cam (.getCamera viewPort) + width (.getWidth cam) + height (.getHeight cam)] + (reset! renderer (.getRenderer renderManager)) + (reset! byte-buffer + (BufferUtils/createByteBuffer + (* width height 4))) + (reset! image (BufferedImage. width height + BufferedImage/TYPE_4BYTE_ABGR)))) + (isInitialized [] (not (nil? @byte-buffer))) + (reshape [_ _ _]) + (preFrame [_]) + (postQueue [_]) + (postFrame + [#^FrameBuffer fb] + (.clear @byte-buffer) + (.readFrameBuffer @renderer fb @byte-buffer) + (Screenshots/convertScreenShot @byte-buffer @image) + (continuation @image)) + (cleanup [])))) + +(defn add-eye + "Add an eye to the world, and call continuation on + every frame produced" + [world camera continuation] + (let [width (.getWidth camera) + height (.getHeight camera) + render-manager (.getRenderManager world) + viewport (.createMainView render-manager "eye-view" camera)] + (doto viewport + (.setBackgroundColor ColorRGBA/Black) + (.setClearFlags true true true) + (.addProcessor (scene-processor continuation)) + (.attachScene (.getRootNode world))))) + +(defn make-display-frame [display width height] + (SwingUtilities/invokeLater + (fn [] + (.setPreferredSize display (Dimension. width height)) + (doto (JFrame. "Eye Camera!") + (-> (.getContentPane) (.add display)) + (.setDefaultCloseOperation JFrame/DISPOSE_ON_CLOSE) + (.pack) + (.setLocationRelativeTo nil) + (.setResizable false) + (.setVisible true))))) + +(defn image-monitor [#^BufferedImage image] + (proxy [JPanel] [] + (paintComponent + [g] + (proxy-super paintComponent g) + (locking image + (.drawImage g image 0 0 + (proxy [ImageObserver] + [] + (imageUpdate + [] + (proxy-super imageUpdate)))))))) + +(defn movie-image [] + (let [setup + (runonce + (fn [#^BufferedImage image] + (let [width (.getWidth image) + height (.getHeight image) + display (image-monitor image) + frame (make-display-frame display width height)] + display)))] + (fn [#^BufferedImage image] + (.repaint (setup image))))) + + +(defn observer + "place thy eye!" + [world camera] + (let [eye camera + width (.getWidth eye) + height (.getHeight eye)] + (no-exceptions + (add-eye + world + eye + (movie-image))))) +#+end_src + +#+srcname: test-vision +#+begin_src clojure + +(ns test.vision) +(use 'cortex.world) +(use 'cortex.import) +(use 'clojure.contrib.def) +(use 'body.eye) +(cortex.import/mega-import-jme3) +(rlm.rlm-commands/help) +(import java.nio.ByteBuffer) +(import java.awt.image.BufferedImage) +(import java.awt.Color) +(import java.awt.Dimension) +(import java.awt.Graphics) +(import java.awt.Graphics2D) +(import java.awt.event.WindowAdapter) +(import java.awt.event.WindowEvent) +(import java.awt.image.BufferedImage) +(import java.nio.ByteBuffer) +(import javax.swing.JFrame) +(import javax.swing.JPanel) +(import javax.swing.SwingUtilities) +(import javax.swing.ImageIcon) +(import javax.swing.JOptionPane) +(import java.awt.image.ImageObserver) + + +(def width 200) +(def height 200) + +(defn camera [] + (doto (Camera. width height) + (.setFrustumPerspective 45 1 1 1000) + (.setLocation (Vector3f. -3 0 -5)) + (.lookAt Vector3f/ZERO Vector3f/UNIT_Y))) + +(defn camera2 [] + (doto (Camera. width height) + (.setFrustumPerspective 45 1 1 1000) + (.setLocation (Vector3f. 3 0 -5)) + (.lookAt Vector3f/ZERO Vector3f/UNIT_Y))) + +(defn setup-fn [world] + (let [eye (camera) + width (.getWidth eye) + height (.getHeight eye)] + (no-exceptions + (add-eye + world + eye + (runonce visual)) + (add-eye + world + (camera2) + (runonce visual))))) + +(defn spider-eye [position] + (doto (Camera. 200 200 ) + (.setFrustumPerspective 45 1 1 1000) + (.setLocation position) + (.lookAt Vector3f/ZERO Vector3f/UNIT_Y))) + +(defn setup-fn* [world] + (let [eye (camera) + width (.getWidth eye) + height (.getHeight eye)] + ;;(.setClearFlags (.getViewPort world) true true true) + (observer world (.getCamera world)) + (observer world (spider-eye (Vector3f. 3 0 -5))) + ;;(observer world (spider-eye (Vector3f. 0 0 -5))) + ;; (observer world (spider-eye (Vector3f. -3 0 -5))) + ;; (observer world (spider-eye (Vector3f. 0 3 -5))) + ;; (observer world (spider-eye (Vector3f. 0 -3 -5))) + ;; (observer world (spider-eye (Vector3f. 3 3 -5))) + ;; (observer world (spider-eye (Vector3f. -3 3 -5))) + ;; (observer world (spider-eye (Vector3f. 3 -3 -5))) + ;; (observer world (spider-eye (Vector3f. -3 -3 -5))) + + ) + world) + +(defn test-world [] + (let [thing (box 1 1 1 :physical? false)] + (world + (doto (Node.) + (.attachChild thing)) + {} + setup-fn + (fn [world tpf] + (.rotate thing (* tpf 0.2) 0 0) + )))) + + +#+end_src + + +#+results: eyes +: #'body.eye/test-world + +Note the use of continuation passing style for connecting the eye to a +function to process the output. The example code will create two +videos of the same rotating cube from different angles, sutiable for +stereoscopic vision. diff -r 157b416152ea -r cab2da252494 org/util.org --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org/util.org Sun Oct 23 23:54:26 2011 -0700 @@ -0,0 +1,110 @@ +#+title: Helper Utilities +#+author: Robert McIntyre +#+email: rlm@mit.edu +#+description: Simulating senses for AI research using JMonkeyEngine3 +#+SETUPFILE: ../../aurellem/org/setup.org +#+INCLUDE: ../../aurellem/org/level-0.org +#+babel: :mkdirp yes :noweb yes :exports both + +** Imports +jMonkeyEngine has a plethora of classes which can be overwhelming at +first. So that I one can get right to coding, it's good to take the +time right now and make a "import all" function which brings in all of +the important jme3 classes. Once I'm happy with the general structure +of a namespace I can deal with importing only the classes it actually +needs. + +#+srcname: import +#+begin_src clojure :results silent +(ns cortex.import + (:require swank.util.class-browse)) + +(defn permissive-import + [classname] + (eval `(try (import '~classname) + (catch java.lang.Exception e# + (println "couldn't import " '~classname)))) + classname) + +(defn jme-class? [classname] + (and + (.startsWith classname "com.jme3.") + ;; Don't import the Lwjgl stuff since it can throw exceptions + ;; upon being loaded. + (not (re-matches #".*Lwjgl.*" classname)))) + +(defn jme-classes + "returns a list of all jme3 classes" + [] + (filter + jme-class? + (map :name + swank.util.class-browse/available-classes))) + +(defn mega-import-jme3 + "Import ALL the jme classes. For REPL use." + [] + (doall + (map (comp permissive-import symbol) (jme-classes)))) +#+end_src + +The =mega-import-jme3= is quite usefull for debugging purposes since +it allows completion for almost all of JME's classes. + +Out of curiousity, let's see just how many classes =mega-import-jme3= +imports: + +#+begin_src clojure :exports both +(clojure.core/count (cortex.import/jme-classes)) +#+end_src + +#+results: +: 955 + +** Simplification +#+srcname: world-view +#+begin_src clojure :results silent +(in-ns 'cortex.world) + +(defprotocol Viewable + (view [something])) + +(extend-type com.jme3.scene.Geometry + Viewable + (view [geo] + (view (doto (Node.)(.attachChild geo))))) + +(extend-type com.jme3.scene.Node + Viewable + (view [node] + (.start + (world node + {} + (fn [world] + (.enableDebug + (.getPhysicsSpace + (.getState + (.getStateManager world) + BulletAppState)) + (asset-manager)) + (set-gravity* world Vector3f/ZERO) +;; (set-gravity* world (Vector3f. 0 (float -0.4) 0)) + (let [sun (doto (DirectionalLight.) + (.setDirection (.normalizeLocal (Vector3f. 1 0 -2))) + (.setColor ColorRGBA/White))] + (.addLight (.getRootNode world) sun))) + no-op)))) + +(defn position-camera [game] + (doto (.getCamera game) + (.setLocation (Vector3f. 0 6 6)) + (.lookAt Vector3f/ZERO (Vector3f. 0 1 0)))) + +#+end_src + +Here I make the =Viewable= protocol and extend it to JME's types. Now +hello-world can be written as easily as: + +#+begin_src clojure :results silent +(cortex.world/view (cortex.world/box)) +#+end_src diff -r 157b416152ea -r cab2da252494 org/world.org --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org/world.org Sun Oct 23 23:54:26 2011 -0700 @@ -0,0 +1,308 @@ +#+title: A world for the creatures to live +#+author: Robert McIntyre +#+email: rlm@mit.edu +#+description: Simulating senses for AI research using JMonkeyEngine3 +#+SETUPFILE: ../../aurellem/org/setup.org +#+INCLUDE: ../../aurellem/org/level-0.org +#+babel: :mkdirp yes :noweb yes :exports both + + + +*** World + +It is comvienent to wrap the JME elements that deal with creating a +world, creation of basic objects, and Keyboard input with a nicer +interface (at least for my purposes). + +#+srcname: world-inputs +#+begin_src clojure :results silent +(ns cortex.world) +(require 'cortex.import) +(use 'clojure.contrib.def) +(rlm.rlm-commands/help) +(cortex.import/mega-import-jme3) + +(defvar *app-settings* + (doto (AppSettings. true) + (.setFullscreen false) + (.setTitle "Aurellem.") + ;; disable 32 bit stuff for now + ;;(.setAudioRenderer "Send") + ) + "These settings control how the game is displayed on the screen for + debugging purposes. Use binding forms to change this if desired. + Full-screen mode does not work on some computers.") + +(defn asset-manager + "returns a new, configured assetManager" [] + (JmeSystem/newAssetManager + (.getResource + (.getContextClassLoader (Thread/currentThread)) + "com/jme3/asset/Desktop.cfg"))) + +(defmacro no-exceptions + "Sweet relief like I never knew." + [& forms] + `(try ~@forms (catch Exception e# (.printStackTrace e#)))) + +(defn thread-exception-removal [] + (println "removing exceptions from " (Thread/currentThread)) + (.setUncaughtExceptionHandler + (Thread/currentThread) + (proxy [Thread$UncaughtExceptionHandler] [] + (uncaughtException + [thread thrown] + (println "uncaught-exception thrown in " thread) + (println (.getMessage thrown)))))) + +(def println-repl (bound-fn [& args] (apply println args))) + +(use '[pokemon [lpsolve :only [constant-map]]]) + +(defn no-op [& _]) + +(defn all-keys + "Construct a map of strings representing all the manual inputs from + either the keyboard or mouse." + [] + (let [inputs (constant-map KeyInput)] + (assoc + (zipmap (map (fn [field] + (.toLowerCase (re-gsub #"_" "-" field))) (vals inputs)) + (map (fn [val] (KeyTrigger. val)) (keys inputs))) + ;;explicitly add mouse controls + "mouse-left" (MouseButtonTrigger. 0) + "mouse-middle" (MouseButtonTrigger. 2) + "mouse-right" (MouseButtonTrigger. 1)))) + +(defn initialize-inputs + "more java-interop cruft to establish keybindings for a particular virtual world" + [game input-manager key-map] + (doall (map (fn [[name trigger]] + (.addMapping ^InputManager input-manager + name (into-array (class trigger) [trigger]))) key-map)) + (doall (map (fn [name] + (.addListener ^InputManager input-manager game + (into-array String [name]))) (keys key-map)))) + +#+end_src + +These functions are all for debug controlling of the world through +keyboard and mouse. + +We reuse =constant-map= from =pokemon.lpsolve= to get the numerical +values for all the keys defined in the =KeyInput= class. The +documentation for =constant-map= is: + +#+begin_src clojure :results output +(doc pokemon.lpsolve/constant-map) +#+end_src + +#+results: +: ------------------------- +: pokemon.lpsolve/constant-map +: ([class]) +: Takes a class and creates a map of the static constant integer +: fields with their names. This helps with C wrappers where they have +: just defined a bunch of integer constants instead of enums + + +Then, =all-keys= converts the constant names like =KEY_J= to the more +clojure-like =key-j=, and returns a map from these keys to +jMonkeyEngine KeyTrigger objects, the use of which will soon become +apparent. =all-keys= also adds the three mouse button controls to the +map. + +#+srcname: world +#+begin_src clojure :results silent +(in-ns 'cortex.world) + +(defn traverse + "apply f to every non-node, deeply" + [f node] + (if (isa? (class node) Node) + (dorun (map (partial traverse f) (.getChildren node))) + (f node))) + +(def gravity (Vector3f. 0 -9.81 0)) + +(defn world + [root-node key-map setup-fn update-fn] + (let [physics-manager (BulletAppState.) + shadow-renderer (BasicShadowRenderer. (asset-manager) (int 256)) + ;;maybe use a better shadow renderer someday! + ;;shadow-renderer (PssmShadowRenderer. (asset-manager) 256 1) + ] + (doto + (proxy [SimpleApplication ActionListener] [] + (simpleInitApp + [] + (no-exceptions + (.setTimer this (IsoTimer. 60)) + ;; Create key-map. + (.setFrustumFar (.getCamera this) 300) + (initialize-inputs this (.getInputManager this) (all-keys)) + ;; Don't take control of the mouse + (org.lwjgl.input.Mouse/setGrabbed false) + ;; add all objects to the world + (.attachChild (.getRootNode this) root-node) + ;; enable physics + ;; add a physics manager + (.attach (.getStateManager this) physics-manager) + (.setGravity (.getPhysicsSpace physics-manager) gravity) + + + ;; go through every object and add it to the physics manager + ;; if relavant. + (traverse (fn [geom] + (dorun + (for [n (range (.getNumControls geom))] + (do + (println-repl "adding control " (.getControl geom n)) + (.add (.getPhysicsSpace physics-manager) + (.getControl geom n)))))) + (.getRootNode this)) + ;;(.addAll (.getPhysicsSpace physics-manager) (.getRootNode this)) + + (setup-fn this) + (.setDirection shadow-renderer + (.normalizeLocal (Vector3f. -1 -1 -1))) + (.addProcessor (.getViewPort this) shadow-renderer) + (.setShadowMode (.getRootNode this) RenderQueue$ShadowMode/Off) + )) + (simpleUpdate + [tpf] + (no-exceptions + (update-fn this tpf))) + (onAction + [binding value tpf] + ;; whenever a key is pressed, call the function returned from + ;; key-map. + (no-exceptions + (if-let [react (key-map binding)] + (react this value))))) + ;; don't show a menu to change options. + + (.setShowSettings false) + (.setPauseOnLostFocus false) + (.setSettings *app-settings*)))) + +(defn apply-map + "Like apply, but works for maps and functions that expect an implicit map + and nothing else as in (fn [& {}]). +------- Example ------- + (defn jjj [& {:keys [www] :or {www \"oh yeah\"} :as env}] (println www)) + (apply-map jjj {:www \"whatever\"}) + -->\"whatever\"" + [fn m] + (apply fn (reduce #(into %1 %2) [] m))) + +#+end_src + + +=world= is the most important function here. +*** TODO more documentation + +#+srcname: world-shapes +#+begin_src clojure :results silent +(in-ns 'cortex.world) +(defrecord shape-description + [name + color + mass + friction + texture + material + position + rotation + shape + physical?]) + +(def base-shape + (shape-description. + "default-shape" + false + ;;ColorRGBA/Blue + 1.0 ;; mass + 1.0 ;; friction + ;; texture + "Textures/Terrain/BrickWall/BrickWall.jpg" + ;; material + "Common/MatDefs/Misc/Unshaded.j3md" + Vector3f/ZERO + Quaternion/IDENTITY + (Box. Vector3f/ZERO 0.5 0.5 0.5) + true)) + +(defn make-shape + [#^shape-description d] + (let [asset-manager (if (:asset-manager d) (:asset-manager d) (asset-manager)) + mat (Material. asset-manager (:material d)) + geom (Geometry. (:name d) (:shape d))] + (if (:texture d) + (let [key (TextureKey. (:texture d))] + (.setGenerateMips key true) + (.setTexture mat "ColorMap" (.loadTexture asset-manager key)))) + (if (:color d) (.setColor mat "Color" (:color d))) + (.setMaterial geom mat) + (if-let [rotation (:rotation d)] (.rotate geom rotation)) + (.setLocalTranslation geom (:position d)) + (if (:physical? d) + (let [impact-shape (doto (GImpactCollisionShape. + (.getMesh geom)) (.setMargin 0)) + physics-control (RigidBodyControl. + ;;impact-shape ;; comment to disable + (float (:mass d)))] + (.createJmeMesh impact-shape) + (.addControl geom physics-control) + ;;(.setSleepingThresholds physics-control (float 0) (float 0)) + (.setFriction physics-control (:friction d)))) + ;;the default is to keep this node in the physics engine forever. + ;;these commands must come after the control is added to the geometry. + ;; + geom)) + +(defn box + ([l w h & {:as options}] + (let [options (merge base-shape options)] + (make-shape (assoc options + :shape (Box. l w h))))) + ([] (box 0.5 0.5 0.5))) + +(defn sphere + ([r & {:as options}] + (let [options (merge base-shape options)] + (make-shape (assoc options + :shape (Sphere. 32 32 (float r)))))) + ([] (sphere 0.5))) + +(defn add-element + ([game element node] + (.addAll + (.getPhysicsSpace + (.getState + (.getStateManager game) + BulletAppState)) + element) + (.attachChild node element)) + ([game element] + (add-element game element (.getRootNode game)))) + + +(defn set-gravity* + [game gravity] + (traverse + (fn [geom] + (if-let + [control (.getControl geom RigidBodyControl)] + (do + (.setGravity control gravity) + (.applyImpulse control Vector3f/ZERO Vector3f/ZERO) + ))) + (.getRootNode game))) + +#+end_src + +These are convienence functions for creating JME objects and +manipulating a world. +