Mercurial > cortex
diff org/world.org @ 25:775d97247dd0
cleaning up world.org
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Mon, 24 Oct 2011 05:25:01 -0700 |
parents | e965675ec4d0 |
children | bbffa41a12a9 |
line wrap: on
line diff
1.1 --- a/org/world.org Mon Oct 24 01:19:15 2011 -0700 1.2 +++ b/org/world.org Mon Oct 24 05:25:01 2011 -0700 1.3 @@ -1,26 +1,83 @@ 1.4 -#+title: A world for the creatures to live 1.5 +#+title: A World for the Creatures 1.6 #+author: Robert McIntyre 1.7 #+email: rlm@mit.edu 1.8 -#+description: Simulating senses for AI research using JMonkeyEngine3 1.9 +#+description: Creating a Virtual World for AI constructs using clojure and JME3 1.10 +#+keywords: JME3, clojure, virtual world, exception handling 1.11 #+SETUPFILE: ../../aurellem/org/setup.org 1.12 #+INCLUDE: ../../aurellem/org/level-0.org 1.13 #+babel: :mkdirp yes :noweb yes :exports both 1.14 1.15 +* The World 1.16 1.17 +There's no point in having senses if there's nothing to experience. In 1.18 +this section I make some tools with which to build virtual worlds for 1.19 +my characters to inhabit. If you look at the tutorials at [[http://www.jmonkeyengine.org/wiki/doku.php/jme3:beginner][the jme3 1.20 +website]], you will see a pattern in how virtual worlds are normally 1.21 +built. I call this "the Java way" of making worlds. 1.22 1.23 -*** World 1.24 + - The Java way: 1.25 + - Create a class that extends =SimpleApplication= or =Application= 1.26 + - Implement setup functions that create all the scene objects using 1.27 + the inherited =assetManager= and call them by overriding the 1.28 + =simpleInitApp= method. 1.29 + - Create =ActionListeners= and add them to the =inputManager= 1.30 + inherited from =Application= to handle key-bindings. 1.31 + - Override =simpleUpdate= to implement game logic. 1.32 + - Running/Testing an Application involves creating a new JVM, 1.33 + running the App, and then closing everything down. 1.34 1.35 -It is comvienent to wrap the JME elements that deal with creating a 1.36 -world, creation of basic objects, and Keyboard input with a nicer 1.37 -interface (at least for my purposes). 1.38 1.39 -#+srcname: world-inputs 1.40 -#+begin_src clojure :results silent 1.41 -(ns cortex.world) 1.42 -(require 'cortex.import) 1.43 -(use 'clojure.contrib.def) 1.44 -(rlm.rlm-commands/help) 1.45 -(cortex.import/mega-import-jme3) 1.46 + - A more Clojureish way: 1.47 + - Use a map from keys->functions to specify key-bindings. 1.48 + - Use functions to create objects separately from any particular 1.49 + application. 1.50 + - Use an REPL -- this means that there's only ever one JVM, and 1.51 + Applications come and go. 1.52 + 1.53 +Since most development work using jMonkeyEngine is done in Java, jme3 1.54 +supports "the Java way" quite well out of the box. To work "the 1.55 +clojure way", it necessary to wrap the jme3 elements that deal with 1.56 +the Application life-cycle with a REPL driven interface. 1.57 + 1.58 +The most important modifications are: 1.59 + 1.60 + - Separation of Object life-cycles with the Application life-cycle. 1.61 + - Functional interface to the underlying =Application= and 1.62 + =SimpleApplication= classes. 1.63 + 1.64 +** Header 1.65 +#+srcname: header 1.66 +#+begin_src clojure :results silent 1.67 +(ns cortex.world 1.68 + "World Creation, abstracion over jme3's input system, and REPL 1.69 + driven exception handling" 1.70 + {:author "Robert McIntyre"} 1.71 + 1.72 + (:use (clojure.contrib (def :only (defvar)))) 1.73 + (:use [pokemon [lpsolve :only [constant-map]]]) 1.74 + (:use [clojure.contrib [str-utils :only [re-gsub]]]) 1.75 + 1.76 + (:import com.jme3.math.Vector3f) 1.77 + (:import com.jme3.scene.Node) 1.78 + (:import com.jme3.system.AppSettings) 1.79 + (:import com.jme3.system.JmeSystem) 1.80 + (:import com.jme3.system.IsoTimer) 1.81 + (:import com.jme3.input.KeyInput) 1.82 + (:import com.jme3.input.controls.KeyTrigger) 1.83 + (:import com.jme3.input.controls.MouseButtonTrigger) 1.84 + (:import com.jme3.input.InputManager) 1.85 + (:import com.jme3.bullet.BulletAppState) 1.86 + (:import com.jme3.shadow.BasicShadowRenderer) 1.87 + (:import com.jme3.app.SimpleApplication) 1.88 + (:import com.jme3.input.controls.ActionListener) 1.89 + (:import com.jme3.renderer.queue.RenderQueue$ShadowMode) 1.90 + (:import org.lwjgl.input.Mouse)) 1.91 +#+end_src 1.92 + 1.93 +** General Settings 1.94 +#+srcname: settings 1.95 +#+begin_src clojure 1.96 +(in-ns 'cortex.world) 1.97 1.98 (defvar *app-settings* 1.99 (doto (AppSettings. true) 1.100 @@ -31,7 +88,7 @@ 1.101 ) 1.102 "These settings control how the game is displayed on the screen for 1.103 debugging purposes. Use binding forms to change this if desired. 1.104 - Full-screen mode does not work on some computers.") 1.105 + Full-screen mode does not work on some computers.") 1.106 1.107 (defn asset-manager 1.108 "returns a new, configured assetManager" [] 1.109 @@ -39,33 +96,58 @@ 1.110 (.getResource 1.111 (.getContextClassLoader (Thread/currentThread)) 1.112 "com/jme3/asset/Desktop.cfg"))) 1.113 +#+end_src 1.114 + 1.115 +Normally, people just use the =AssetManager= inherited from 1.116 +=Application= whenever they extend that class. However, 1.117 +=AssetManagers= are useful on their own to create objects/ materials, 1.118 +independent from any particular application. =(asset-manager)= makes 1.119 +object creation less tightly bound to Application initialization. 1.120 + 1.121 + 1.122 +** Exception Protection 1.123 +#+srcname: exceptions 1.124 +#+begin_src clojure 1.125 +(in-ns 'cortex.world) 1.126 1.127 (defmacro no-exceptions 1.128 "Sweet relief like I never knew." 1.129 [& forms] 1.130 `(try ~@forms (catch Exception e# (.printStackTrace e#)))) 1.131 1.132 -(defn thread-exception-removal [] 1.133 - (println "removing exceptions from " (Thread/currentThread)) 1.134 +(defn thread-exception-removal 1.135 + "Exceptions thrown in the graphics rendering thread generally cause 1.136 + the entire REPL to crash! It is good to suppress them while trying 1.137 + things out to shorten the debug loop." 1.138 + [] 1.139 (.setUncaughtExceptionHandler 1.140 (Thread/currentThread) 1.141 (proxy [Thread$UncaughtExceptionHandler] [] 1.142 (uncaughtException 1.143 - [thread thrown] 1.144 - (println "uncaught-exception thrown in " thread) 1.145 - (println (.getMessage thrown)))))) 1.146 + [thread thrown] 1.147 + (println "uncaught-exception thrown in " thread) 1.148 + (println (.getMessage thrown)))))) 1.149 1.150 -(def println-repl (bound-fn [& args] (apply println args))) 1.151 +#+end_src 1.152 1.153 -(use '[pokemon [lpsolve :only [constant-map]]]) 1.154 +Exceptions thrown in the LWJGL render thread, if not caught, will 1.155 +destroy the entire JVM process including the REPL and slow development 1.156 +to a crawl. It is better to try to continue on in the face of 1.157 +exceptions and keep the REPL alive as long as possible. Normally it 1.158 +is possible to just exit the faulty Application, fix the bug, 1.159 +reevaluate the appropriate forms, and be on your way, without 1.160 +restarting the JVM. 1.161 1.162 -(defn no-op [& _]) 1.163 +** Input 1.164 +#+srcname: input 1.165 +#+begin_src clojure 1.166 +(in-ns 'cortex.world) 1.167 1.168 (defn all-keys 1.169 - "Construct a map of strings representing all the manual inputs from 1.170 - either the keyboard or mouse." 1.171 + "Uses reflection to generate a map of string names to jme3 trigger 1.172 + objects, which govern input from the keyboard and mouse" 1.173 [] 1.174 - (let [inputs (constant-map KeyInput)] 1.175 + (let [inputs (pokemon.lpsolve/constant-map KeyInput)] 1.176 (assoc 1.177 (zipmap (map (fn [field] 1.178 (.toLowerCase (re-gsub #"_" "-" field))) (vals inputs)) 1.179 @@ -76,21 +158,26 @@ 1.180 "mouse-right" (MouseButtonTrigger. 1)))) 1.181 1.182 (defn initialize-inputs 1.183 - "more java-interop cruft to establish keybindings for a particular virtual world" 1.184 + "Establish key-bindings for a particular virtual world." 1.185 [game input-manager key-map] 1.186 - (doall (map (fn [[name trigger]] 1.187 - (.addMapping ^InputManager input-manager 1.188 - name (into-array (class trigger) [trigger]))) key-map)) 1.189 - (doall (map (fn [name] 1.190 - (.addListener ^InputManager input-manager game 1.191 - (into-array String [name]))) (keys key-map)))) 1.192 + (doall 1.193 + (map (fn [[name trigger]] 1.194 + (.addMapping 1.195 + ^InputManager input-manager 1.196 + name (into-array (class trigger) 1.197 + [trigger]))) key-map)) 1.198 + (doall 1.199 + (map (fn [name] 1.200 + (.addListener 1.201 + ^InputManager input-manager game 1.202 + (into-array String [name]))) (keys key-map)))) 1.203 1.204 #+end_src 1.205 1.206 -These functions are all for debug controlling of the world through 1.207 -keyboard and mouse. 1.208 +These functions are for controlling the world through the keyboard and 1.209 +mouse. 1.210 1.211 -We reuse =constant-map= from =pokemon.lpsolve= to get the numerical 1.212 +I reuse =constant-map= from [[../../pokemon-types/html/lpsolve.html#sec-3-3-4][=pokemon.lpsolve=]] to get the numerical 1.213 values for all the keys defined in the =KeyInput= class. The 1.214 documentation for =constant-map= is: 1.215 1.216 @@ -106,17 +193,63 @@ 1.217 : fields with their names. This helps with C wrappers where they have 1.218 : just defined a bunch of integer constants instead of enums 1.219 1.220 +=(all-keys)= converts the constant names like =KEY_J= to the more 1.221 +clojure-like =key-j=, and returns a map from these keys to 1.222 +jMonkeyEngine =KeyTrigger= objects, which jMonkeyEngine3 uses as it's 1.223 +abstraction over the physical keys. =all-keys= also adds the three 1.224 +mouse button controls to the map. 1.225 1.226 -Then, =all-keys= converts the constant names like =KEY_J= to the more 1.227 -clojure-like =key-j=, and returns a map from these keys to 1.228 -jMonkeyEngine KeyTrigger objects, the use of which will soon become 1.229 -apparent. =all-keys= also adds the three mouse button controls to the 1.230 -map. 1.231 1.232 +#+begin_src clojure :exports both :results output 1.233 +(require 'clojure.contrib.pprint) 1.234 +(clojure.contrib.pprint/pprint 1.235 + (take 10 (keys (cortex.world/all-keys)))) 1.236 +#+end_src 1.237 + 1.238 +#+results: 1.239 +#+begin_example 1.240 +("key-n" 1.241 + "key-apps" 1.242 + "key-pgup" 1.243 + "key-f8" 1.244 + "key-o" 1.245 + "key-at" 1.246 + "key-f9" 1.247 + "key-0" 1.248 + "key-p" 1.249 + "key-subtract") 1.250 +#+end_example 1.251 + 1.252 +#+begin_src clojure :exports both :results output 1.253 +(clojure.contrib.pprint/pprint 1.254 + (take 10 (vals (cortex.world/all-keys)))) 1.255 +#+end_src 1.256 + 1.257 +#+results: 1.258 +#+begin_example 1.259 +(#<KeyTrigger com.jme3.input.controls.KeyTrigger@6ec21e52> 1.260 + #<KeyTrigger com.jme3.input.controls.KeyTrigger@a54d24d> 1.261 + #<KeyTrigger com.jme3.input.controls.KeyTrigger@1ba5e91b> 1.262 + #<KeyTrigger com.jme3.input.controls.KeyTrigger@296af9cb> 1.263 + #<KeyTrigger com.jme3.input.controls.KeyTrigger@2e3593ab> 1.264 + #<KeyTrigger com.jme3.input.controls.KeyTrigger@3f71d740> 1.265 + #<KeyTrigger com.jme3.input.controls.KeyTrigger@4aeacb4a> 1.266 + #<KeyTrigger com.jme3.input.controls.KeyTrigger@7cc88db2> 1.267 + #<KeyTrigger com.jme3.input.controls.KeyTrigger@52cee11e> 1.268 + #<KeyTrigger com.jme3.input.controls.KeyTrigger@c1da30b>) 1.269 +#+end_example 1.270 + 1.271 + 1.272 + 1.273 +** World Creation 1.274 #+srcname: world 1.275 #+begin_src clojure :results silent 1.276 (in-ns 'cortex.world) 1.277 1.278 +(defn no-op 1.279 + "Takes any number of arguments and does nothing." 1.280 + [& _]) 1.281 + 1.282 (defn traverse 1.283 "apply f to every non-node, deeply" 1.284 [f node] 1.285 @@ -124,196 +257,129 @@ 1.286 (dorun (map (partial traverse f) (.getChildren node))) 1.287 (f node))) 1.288 1.289 -(def gravity (Vector3f. 0 -9.81 0)) 1.290 +(defn world 1.291 + "the =world= function takes care of the details of initializing a 1.292 + SimpleApplication. 1.293 1.294 -(defn world 1.295 + ***** Arguments: 1.296 + 1.297 + - root-node : a com.jme3.scene.Node object which contains all of 1.298 + the objects that should be in the simulation. 1.299 + 1.300 + - key-map : a map from strings describing keys to functions that 1.301 + should be executed whenever that key is pressed. 1.302 + the functions should take a SimpleApplication object and a 1.303 + boolean value. The SimpleApplication is the current simulation 1.304 + that is running, and the boolean is true if the key is being 1.305 + pressed, and false if it is being released. As an example, 1.306 + 1.307 + {\"key-j\" (fn [game value] (if value (println \"key j pressed\")))} 1.308 + 1.309 + is a valid key-map which will cause the simulation to print a 1.310 + message whenever the 'j' key on the keyboard is pressed. 1.311 + 1.312 + - setup-fn : a function that takes a SimpleApplication object. It 1.313 + is called once when initializing the simulation. Use it to 1.314 + create things like lights, change the gravity, initialize debug 1.315 + nodes, etc. 1.316 + 1.317 + - update-fn : this function takes a SimpleApplication object and a 1.318 + float and is called every frame of the simulation. The float 1.319 + tells how many seconds is has been since the last frame was 1.320 + rendered, according to whatever clock jme is currently 1.321 + using. The default is to use IsoTimer which will result in this 1.322 + value always being the same. 1.323 + " 1.324 [root-node key-map setup-fn update-fn] 1.325 (let [physics-manager (BulletAppState.) 1.326 - shadow-renderer (BasicShadowRenderer. (asset-manager) (int 256)) 1.327 - ;;maybe use a better shadow renderer someday! 1.328 - ;;shadow-renderer (PssmShadowRenderer. (asset-manager) 256 1) 1.329 - ] 1.330 - (doto 1.331 - (proxy [SimpleApplication ActionListener] [] 1.332 - (simpleInitApp 1.333 - [] 1.334 - (no-exceptions 1.335 - (.setTimer this (IsoTimer. 60)) 1.336 - ;; Create key-map. 1.337 - (.setFrustumFar (.getCamera this) 300) 1.338 - (initialize-inputs this (.getInputManager this) (all-keys)) 1.339 - ;; Don't take control of the mouse 1.340 - (org.lwjgl.input.Mouse/setGrabbed false) 1.341 - ;; add all objects to the world 1.342 - (.attachChild (.getRootNode this) root-node) 1.343 - ;; enable physics 1.344 - ;; add a physics manager 1.345 - (.attach (.getStateManager this) physics-manager) 1.346 - (.setGravity (.getPhysicsSpace physics-manager) gravity) 1.347 - 1.348 - 1.349 - ;; go through every object and add it to the physics manager 1.350 - ;; if relavant. 1.351 - (traverse (fn [geom] 1.352 - (dorun 1.353 - (for [n (range (.getNumControls geom))] 1.354 - (do 1.355 - (println-repl "adding control " (.getControl geom n)) 1.356 - (.add (.getPhysicsSpace physics-manager) 1.357 - (.getControl geom n)))))) 1.358 - (.getRootNode this)) 1.359 - ;;(.addAll (.getPhysicsSpace physics-manager) (.getRootNode this)) 1.360 - 1.361 - (setup-fn this) 1.362 - (.setDirection shadow-renderer 1.363 - (.normalizeLocal (Vector3f. -1 -1 -1))) 1.364 - (.addProcessor (.getViewPort this) shadow-renderer) 1.365 - (.setShadowMode (.getRootNode this) RenderQueue$ShadowMode/Off) 1.366 - )) 1.367 - (simpleUpdate 1.368 - [tpf] 1.369 - (no-exceptions 1.370 - (update-fn this tpf))) 1.371 - (onAction 1.372 - [binding value tpf] 1.373 - ;; whenever a key is pressed, call the function returned from 1.374 - ;; key-map. 1.375 - (no-exceptions 1.376 - (if-let [react (key-map binding)] 1.377 - (react this value))))) 1.378 - ;; don't show a menu to change options. 1.379 - 1.380 - (.setShowSettings false) 1.381 - (.setPauseOnLostFocus false) 1.382 - (.setSettings *app-settings*)))) 1.383 + shadow-renderer (BasicShadowRenderer. 1.384 + (asset-manager) (int 256))] 1.385 + (doto 1.386 + (proxy [SimpleApplication ActionListener] [] 1.387 + (simpleInitApp 1.388 + [] 1.389 + (no-exceptions 1.390 + ;; allow AI entities as much time as they need to think. 1.391 + (.setTimer this (IsoTimer. 60)) 1.392 + (.setFrustumFar (.getCamera this) 300) 1.393 + ;; Create default key-map. 1.394 + (initialize-inputs this (.getInputManager this) (all-keys)) 1.395 + ;; Don't take control of the mouse 1.396 + (org.lwjgl.input.Mouse/setGrabbed false) 1.397 + ;; add all objects to the world 1.398 + (.attachChild (.getRootNode this) root-node) 1.399 + ;; enable physics 1.400 + ;; add a physics manager 1.401 + (.attach (.getStateManager this) physics-manager) 1.402 + (.setGravity (.getPhysicsSpace physics-manager) 1.403 + (Vector3f. 0 -9.81 0)) 1.404 + ;; go through every object and add it to the physics 1.405 + ;; manager if relevant. 1.406 + (traverse (fn [geom] 1.407 + (dorun 1.408 + (for [n (range (.getNumControls geom))] 1.409 + (do 1.410 + (.add (.getPhysicsSpace physics-manager) 1.411 + (.getControl geom n)))))) 1.412 + (.getRootNode this)) 1.413 + ;;(.addAll (.getPhysicsSpace physics-manager) (.getRootNode this)) 1.414 + 1.415 + ;; set some basic defaults for the shadow renderer. 1.416 + ;; these can be undone in the setup function 1.417 + (.setDirection shadow-renderer 1.418 + (.normalizeLocal (Vector3f. -1 -1 -1))) 1.419 + (.addProcessor (.getViewPort this) shadow-renderer) 1.420 + (.setShadowMode (.getRootNode this) 1.421 + RenderQueue$ShadowMode/Off) 1.422 + ;; call the supplied setup-fn 1.423 + (if setup-fn 1.424 + (setup-fn this)))) 1.425 + (simpleUpdate 1.426 + [tpf] 1.427 + (no-exceptions 1.428 + (update-fn this tpf))) 1.429 + (onAction 1.430 + [binding value tpf] 1.431 + ;; whenever a key is pressed, call the function returned 1.432 + ;; from key-map. 1.433 + (no-exceptions 1.434 + (if-let [react (key-map binding)] 1.435 + (react this value))))) 1.436 + ;; don't show a menu to change options. 1.437 + (.setShowSettings false) 1.438 + ;; continue running simulation even if the window has lost 1.439 + ;; focus. 1.440 + (.setPauseOnLostFocus false) 1.441 + (.setSettings *app-settings*)))) 1.442 1.443 (defn apply-map 1.444 - "Like apply, but works for maps and functions that expect an implicit map 1.445 - and nothing else as in (fn [& {}]). 1.446 -------- Example ------- 1.447 - (defn jjj [& {:keys [www] :or {www \"oh yeah\"} :as env}] (println www)) 1.448 - (apply-map jjj {:www \"whatever\"}) 1.449 - -->\"whatever\"" 1.450 + "Like apply, but works for maps and functions that expect an 1.451 + implicit map and nothing else as in (fn [& {}]). 1.452 + ------- Example ------- 1.453 + (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}] 1.454 + (println www)) 1.455 + (apply-map demo {:www \"hello!\"}) 1.456 + -->\"hello\"" 1.457 [fn m] 1.458 (apply fn (reduce #(into %1 %2) [] m))) 1.459 1.460 #+end_src 1.461 1.462 1.463 -=world= is the most important function here. 1.464 -*** TODO more documentation 1.465 - 1.466 -#+srcname: world-shapes 1.467 -#+begin_src clojure :results silent 1.468 -(in-ns 'cortex.world) 1.469 -(defrecord shape-description 1.470 - [name 1.471 - color 1.472 - mass 1.473 - friction 1.474 - texture 1.475 - material 1.476 - position 1.477 - rotation 1.478 - shape 1.479 - physical?]) 1.480 - 1.481 -(def base-shape 1.482 - (shape-description. 1.483 - "default-shape" 1.484 - false 1.485 - ;;ColorRGBA/Blue 1.486 - 1.0 ;; mass 1.487 - 1.0 ;; friction 1.488 - ;; texture 1.489 - "Textures/Terrain/BrickWall/BrickWall.jpg" 1.490 - ;; material 1.491 - "Common/MatDefs/Misc/Unshaded.j3md" 1.492 - Vector3f/ZERO 1.493 - Quaternion/IDENTITY 1.494 - (Box. Vector3f/ZERO 0.5 0.5 0.5) 1.495 - true)) 1.496 - 1.497 -(defn make-shape 1.498 - [#^shape-description d] 1.499 - (let [asset-manager (if (:asset-manager d) (:asset-manager d) (asset-manager)) 1.500 - mat (Material. asset-manager (:material d)) 1.501 - geom (Geometry. (:name d) (:shape d))] 1.502 - (if (:texture d) 1.503 - (let [key (TextureKey. (:texture d))] 1.504 - (.setGenerateMips key true) 1.505 - (.setTexture mat "ColorMap" (.loadTexture asset-manager key)))) 1.506 - (if (:color d) (.setColor mat "Color" (:color d))) 1.507 - (.setMaterial geom mat) 1.508 - (if-let [rotation (:rotation d)] (.rotate geom rotation)) 1.509 - (.setLocalTranslation geom (:position d)) 1.510 - (if (:physical? d) 1.511 - (let [impact-shape (doto (GImpactCollisionShape. 1.512 - (.getMesh geom)) (.setMargin 0)) 1.513 - physics-control (RigidBodyControl. 1.514 - ;;impact-shape ;; comment to disable 1.515 - (float (:mass d)))] 1.516 - (.createJmeMesh impact-shape) 1.517 - (.addControl geom physics-control) 1.518 - ;;(.setSleepingThresholds physics-control (float 0) (float 0)) 1.519 - (.setFriction physics-control (:friction d)))) 1.520 - ;;the default is to keep this node in the physics engine forever. 1.521 - ;;these commands must come after the control is added to the geometry. 1.522 - ;; 1.523 - geom)) 1.524 - 1.525 -(defn box 1.526 - ([l w h & {:as options}] 1.527 - (let [options (merge base-shape options)] 1.528 - (make-shape (assoc options 1.529 - :shape (Box. l w h))))) 1.530 - ([] (box 0.5 0.5 0.5))) 1.531 - 1.532 -(defn sphere 1.533 - ([r & {:as options}] 1.534 - (let [options (merge base-shape options)] 1.535 - (make-shape (assoc options 1.536 - :shape (Sphere. 32 32 (float r)))))) 1.537 - ([] (sphere 0.5))) 1.538 - 1.539 -(defn add-element 1.540 - ([game element node] 1.541 - (.addAll 1.542 - (.getPhysicsSpace 1.543 - (.getState 1.544 - (.getStateManager game) 1.545 - BulletAppState)) 1.546 - element) 1.547 - (.attachChild node element)) 1.548 - ([game element] 1.549 - (add-element game element (.getRootNode game)))) 1.550 - 1.551 - 1.552 -(defn set-gravity* 1.553 - [game gravity] 1.554 - (traverse 1.555 - (fn [geom] 1.556 - (if-let 1.557 - [control (.getControl geom RigidBodyControl)] 1.558 - (do 1.559 - (.setGravity control gravity) 1.560 - (.applyImpulse control Vector3f/ZERO Vector3f/ZERO) 1.561 - ))) 1.562 - (.getRootNode game))) 1.563 - 1.564 -#+end_src 1.565 - 1.566 -These are convienence functions for creating JME objects and 1.567 -manipulating a world. 1.568 - 1.569 - 1.570 +=(world)= is the most important function here. It presents a more 1.571 +functional interface to the Application life-cycle, and all it's 1.572 +objects except =root-node= are plain clojure data structures. It's now 1.573 +possible to extend functionally by composing multiple functions 1.574 +together, and to add more keyboard-driven actions by combining clojure 1.575 +maps. 1.576 1.577 1.578 1.579 * COMMENT code generation 1.580 - 1.581 #+begin_src clojure :tangle ../src/cortex/world.clj 1.582 -<<world-inputs>> 1.583 +<<header>> 1.584 +<<settings>> 1.585 +<<exceptions>> 1.586 +<<input>> 1.587 <<world>> 1.588 -<<world-shapes>> 1.589 #+end_src