# HG changeset patch # User Robert McIntyre # Date 1319459101 25200 # Node ID 775d97247dd07049311c8eb461477183f72e240d # Parent e965675ec4d054b31ccdd2dd3379924d1a7eb23c cleaning up world.org diff -r e965675ec4d0 -r 775d97247dd0 org/cortex.org --- a/org/cortex.org Mon Oct 24 01:19:15 2011 -0700 +++ b/org/cortex.org Mon Oct 24 05:25:01 2011 -0700 @@ -78,6 +78,7 @@ (cortex.import/mega-import-jme3) (use '[pokemon [lpsolve :only [constant-map]]]) (use 'cortex.world) +(use 'cortex.util) #+end_src #+srcname: brick-wall-body @@ -744,250 +745,7 @@ -* The Body -** 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. - - - - - - * COMMENT code generation -#+begin_src clojure :tangle ../src/cortex/import.clj -<> -#+end_src #+begin_src clojure :tangle ../src/hello/brick_wall.clj <> @@ -997,13 +755,6 @@ #+begin_src clojure :tangle ../src/hello/hello_simple_app.clj <> #+end_src - -#+begin_src clojure :tangle ../src/cortex/world.clj -<> -<> -<> -<> -#+end_src #+begin_src clojure :tangle ../src/cortex/other_games.clj <> @@ -1029,13 +780,6 @@ <> #+end_src -#+begin_src clojure :tangle ../src/body/eye.clj -<> -#+end_src - -#+begin_src clojure :tangle ../src/test/vision.clj -<> -#+end_src diff -r e965675ec4d0 -r 775d97247dd0 org/eyes.org --- a/org/eyes.org Mon Oct 24 01:19:15 2011 -0700 +++ b/org/eyes.org Mon Oct 24 05:25:01 2011 -0700 @@ -38,8 +38,6 @@ (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 diff -r e965675ec4d0 -r 775d97247dd0 org/util.org --- a/org/util.org Mon Oct 24 01:19:15 2011 -0700 +++ b/org/util.org Mon Oct 24 05:25:01 2011 -0700 @@ -61,11 +61,13 @@ #+results: : 955 -** Simplification + #+srcname: world-view #+begin_src clojure :results silent -(in-ns 'cortex.debug) - +(ns cortex.util) +(require 'cortex.import) +(cortex.import/mega-import-jme3) +(use 'cortex.world) (defprotocol Viewable (view [something])) @@ -94,12 +96,6 @@ (.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 @@ -110,8 +106,131 @@ #+end_src +#+srcname: util +#+begin_src clojure +(in-ns 'cortex.util) + +(def println-repl (bound-fn [& args] (apply println args))) + +(defn position-camera [game] + (doto (.getCamera game) + (.setLocation (Vector3f. 0 6 6)) + (.lookAt Vector3f/ZERO (Vector3f. 0 1 0)))) + +(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 + + +#+srcname: shapes +#+begin_src clojure :results silent +(in-ns 'cortex.util) +(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)))) + + + +#+end_src + + * COMMENT code generation #+begin_src clojure :tangle ../src/cortex/import.clj <> #+end_src + + +#+begin_src clojure :tangle ../src/cortex/util.clj +<> +<> +<> +#+end_src + diff -r e965675ec4d0 -r 775d97247dd0 org/world.org --- a/org/world.org Mon Oct 24 01:19:15 2011 -0700 +++ b/org/world.org Mon Oct 24 05:25:01 2011 -0700 @@ -1,26 +1,83 @@ -#+title: A world for the creatures to live +#+title: A World for the Creatures #+author: Robert McIntyre #+email: rlm@mit.edu -#+description: Simulating senses for AI research using JMonkeyEngine3 +#+description: Creating a Virtual World for AI constructs using clojure and JME3 +#+keywords: JME3, clojure, virtual world, exception handling #+SETUPFILE: ../../aurellem/org/setup.org #+INCLUDE: ../../aurellem/org/level-0.org #+babel: :mkdirp yes :noweb yes :exports both +* The World +There's no point in having senses if there's nothing to experience. In +this section I make some tools with which to build virtual worlds for +my characters to inhabit. If you look at the tutorials at [[http://www.jmonkeyengine.org/wiki/doku.php/jme3:beginner][the jme3 +website]], you will see a pattern in how virtual worlds are normally +built. I call this "the Java way" of making worlds. -*** World + - The Java way: + - Create a class that extends =SimpleApplication= or =Application= + - Implement setup functions that create all the scene objects using + the inherited =assetManager= and call them by overriding the + =simpleInitApp= method. + - Create =ActionListeners= and add them to the =inputManager= + inherited from =Application= to handle key-bindings. + - Override =simpleUpdate= to implement game logic. + - Running/Testing an Application involves creating a new JVM, + running the App, and then closing everything down. -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) + - A more Clojureish way: + - Use a map from keys->functions to specify key-bindings. + - Use functions to create objects separately from any particular + application. + - Use an REPL -- this means that there's only ever one JVM, and + Applications come and go. + +Since most development work using jMonkeyEngine is done in Java, jme3 +supports "the Java way" quite well out of the box. To work "the +clojure way", it necessary to wrap the jme3 elements that deal with +the Application life-cycle with a REPL driven interface. + +The most important modifications are: + + - Separation of Object life-cycles with the Application life-cycle. + - Functional interface to the underlying =Application= and + =SimpleApplication= classes. + +** Header +#+srcname: header +#+begin_src clojure :results silent +(ns cortex.world + "World Creation, abstracion over jme3's input system, and REPL + driven exception handling" + {:author "Robert McIntyre"} + + (:use (clojure.contrib (def :only (defvar)))) + (:use [pokemon [lpsolve :only [constant-map]]]) + (:use [clojure.contrib [str-utils :only [re-gsub]]]) + + (:import com.jme3.math.Vector3f) + (:import com.jme3.scene.Node) + (:import com.jme3.system.AppSettings) + (:import com.jme3.system.JmeSystem) + (:import com.jme3.system.IsoTimer) + (:import com.jme3.input.KeyInput) + (:import com.jme3.input.controls.KeyTrigger) + (:import com.jme3.input.controls.MouseButtonTrigger) + (:import com.jme3.input.InputManager) + (:import com.jme3.bullet.BulletAppState) + (:import com.jme3.shadow.BasicShadowRenderer) + (:import com.jme3.app.SimpleApplication) + (:import com.jme3.input.controls.ActionListener) + (:import com.jme3.renderer.queue.RenderQueue$ShadowMode) + (:import org.lwjgl.input.Mouse)) +#+end_src + +** General Settings +#+srcname: settings +#+begin_src clojure +(in-ns 'cortex.world) (defvar *app-settings* (doto (AppSettings. true) @@ -31,7 +88,7 @@ ) "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.") + Full-screen mode does not work on some computers.") (defn asset-manager "returns a new, configured assetManager" [] @@ -39,33 +96,58 @@ (.getResource (.getContextClassLoader (Thread/currentThread)) "com/jme3/asset/Desktop.cfg"))) +#+end_src + +Normally, people just use the =AssetManager= inherited from +=Application= whenever they extend that class. However, +=AssetManagers= are useful on their own to create objects/ materials, +independent from any particular application. =(asset-manager)= makes +object creation less tightly bound to Application initialization. + + +** Exception Protection +#+srcname: exceptions +#+begin_src clojure +(in-ns 'cortex.world) (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)) +(defn thread-exception-removal + "Exceptions thrown in the graphics rendering thread generally cause + the entire REPL to crash! It is good to suppress them while trying + things out to shorten the debug loop." + [] (.setUncaughtExceptionHandler (Thread/currentThread) (proxy [Thread$UncaughtExceptionHandler] [] (uncaughtException - [thread thrown] - (println "uncaught-exception thrown in " thread) - (println (.getMessage thrown)))))) + [thread thrown] + (println "uncaught-exception thrown in " thread) + (println (.getMessage thrown)))))) -(def println-repl (bound-fn [& args] (apply println args))) +#+end_src -(use '[pokemon [lpsolve :only [constant-map]]]) +Exceptions thrown in the LWJGL render thread, if not caught, will +destroy the entire JVM process including the REPL and slow development +to a crawl. It is better to try to continue on in the face of +exceptions and keep the REPL alive as long as possible. Normally it +is possible to just exit the faulty Application, fix the bug, +reevaluate the appropriate forms, and be on your way, without +restarting the JVM. -(defn no-op [& _]) +** Input +#+srcname: input +#+begin_src clojure +(in-ns 'cortex.world) (defn all-keys - "Construct a map of strings representing all the manual inputs from - either the keyboard or mouse." + "Uses reflection to generate a map of string names to jme3 trigger + objects, which govern input from the keyboard and mouse" [] - (let [inputs (constant-map KeyInput)] + (let [inputs (pokemon.lpsolve/constant-map KeyInput)] (assoc (zipmap (map (fn [field] (.toLowerCase (re-gsub #"_" "-" field))) (vals inputs)) @@ -76,21 +158,26 @@ "mouse-right" (MouseButtonTrigger. 1)))) (defn initialize-inputs - "more java-interop cruft to establish keybindings for a particular virtual world" + "Establish key-bindings 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)))) + (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. +These functions are for controlling the world through the keyboard and +mouse. -We reuse =constant-map= from =pokemon.lpsolve= to get the numerical +I reuse =constant-map= from [[../../pokemon-types/html/lpsolve.html#sec-3-3-4][=pokemon.lpsolve=]] to get the numerical values for all the keys defined in the =KeyInput= class. The documentation for =constant-map= is: @@ -106,17 +193,63 @@ : fields with their names. This helps with C wrappers where they have : just defined a bunch of integer constants instead of enums +=(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, which jMonkeyEngine3 uses as it's +abstraction over the physical keys. =all-keys= also adds the three +mouse button controls to the map. -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. +#+begin_src clojure :exports both :results output +(require 'clojure.contrib.pprint) +(clojure.contrib.pprint/pprint + (take 10 (keys (cortex.world/all-keys)))) +#+end_src + +#+results: +#+begin_example +("key-n" + "key-apps" + "key-pgup" + "key-f8" + "key-o" + "key-at" + "key-f9" + "key-0" + "key-p" + "key-subtract") +#+end_example + +#+begin_src clojure :exports both :results output +(clojure.contrib.pprint/pprint + (take 10 (vals (cortex.world/all-keys)))) +#+end_src + +#+results: +#+begin_example +(# + # + # + # + # + # + # + # + # + #) +#+end_example + + + +** World Creation #+srcname: world #+begin_src clojure :results silent (in-ns 'cortex.world) +(defn no-op + "Takes any number of arguments and does nothing." + [& _]) + (defn traverse "apply f to every non-node, deeply" [f node] @@ -124,196 +257,129 @@ (dorun (map (partial traverse f) (.getChildren node))) (f node))) -(def gravity (Vector3f. 0 -9.81 0)) +(defn world + "the =world= function takes care of the details of initializing a + SimpleApplication. -(defn world + ***** Arguments: + + - root-node : a com.jme3.scene.Node object which contains all of + the objects that should be in the simulation. + + - key-map : a map from strings describing keys to functions that + should be executed whenever that key is pressed. + the functions should take a SimpleApplication object and a + boolean value. The SimpleApplication is the current simulation + that is running, and the boolean is true if the key is being + pressed, and false if it is being released. As an example, + + {\"key-j\" (fn [game value] (if value (println \"key j pressed\")))} + + is a valid key-map which will cause the simulation to print a + message whenever the 'j' key on the keyboard is pressed. + + - setup-fn : a function that takes a SimpleApplication object. It + is called once when initializing the simulation. Use it to + create things like lights, change the gravity, initialize debug + nodes, etc. + + - update-fn : this function takes a SimpleApplication object and a + float and is called every frame of the simulation. The float + tells how many seconds is has been since the last frame was + rendered, according to whatever clock jme is currently + using. The default is to use IsoTimer which will result in this + value always being the same. + " [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*)))) + shadow-renderer (BasicShadowRenderer. + (asset-manager) (int 256))] + (doto + (proxy [SimpleApplication ActionListener] [] + (simpleInitApp + [] + (no-exceptions + ;; allow AI entities as much time as they need to think. + (.setTimer this (IsoTimer. 60)) + (.setFrustumFar (.getCamera this) 300) + ;; Create default key-map. + (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) + (Vector3f. 0 -9.81 0)) + ;; go through every object and add it to the physics + ;; manager if relevant. + (traverse (fn [geom] + (dorun + (for [n (range (.getNumControls geom))] + (do + (.add (.getPhysicsSpace physics-manager) + (.getControl geom n)))))) + (.getRootNode this)) + ;;(.addAll (.getPhysicsSpace physics-manager) (.getRootNode this)) + + ;; set some basic defaults for the shadow renderer. + ;; these can be undone in the setup function + (.setDirection shadow-renderer + (.normalizeLocal (Vector3f. -1 -1 -1))) + (.addProcessor (.getViewPort this) shadow-renderer) + (.setShadowMode (.getRootNode this) + RenderQueue$ShadowMode/Off) + ;; call the supplied setup-fn + (if setup-fn + (setup-fn this)))) + (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) + ;; continue running simulation even if the window has lost + ;; focus. + (.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\"" + "Like apply, but works for maps and functions that expect an + implicit map and nothing else as in (fn [& {}]). + ------- Example ------- + (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}] + (println www)) + (apply-map demo {:www \"hello!\"}) + -->\"hello\"" [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. - - +=(world)= is the most important function here. It presents a more +functional interface to the Application life-cycle, and all it's +objects except =root-node= are plain clojure data structures. It's now +possible to extend functionally by composing multiple functions +together, and to add more keyboard-driven actions by combining clojure +maps. * COMMENT code generation - #+begin_src clojure :tangle ../src/cortex/world.clj -<> +<
> +<> +<> +<> <> -<> #+end_src