comparison 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
comparison
equal deleted inserted replaced
24:e965675ec4d0 25:775d97247dd0
1 #+title: A world for the creatures to live 1 #+title: A World for the Creatures
2 #+author: Robert McIntyre 2 #+author: Robert McIntyre
3 #+email: rlm@mit.edu 3 #+email: rlm@mit.edu
4 #+description: Simulating senses for AI research using JMonkeyEngine3 4 #+description: Creating a Virtual World for AI constructs using clojure and JME3
5 #+keywords: JME3, clojure, virtual world, exception handling
5 #+SETUPFILE: ../../aurellem/org/setup.org 6 #+SETUPFILE: ../../aurellem/org/setup.org
6 #+INCLUDE: ../../aurellem/org/level-0.org 7 #+INCLUDE: ../../aurellem/org/level-0.org
7 #+babel: :mkdirp yes :noweb yes :exports both 8 #+babel: :mkdirp yes :noweb yes :exports both
8 9
9 10 * The World
10 11
11 *** World 12 There's no point in having senses if there's nothing to experience. In
12 13 this section I make some tools with which to build virtual worlds for
13 It is comvienent to wrap the JME elements that deal with creating a 14 my characters to inhabit. If you look at the tutorials at [[http://www.jmonkeyengine.org/wiki/doku.php/jme3:beginner][the jme3
14 world, creation of basic objects, and Keyboard input with a nicer 15 website]], you will see a pattern in how virtual worlds are normally
15 interface (at least for my purposes). 16 built. I call this "the Java way" of making worlds.
16 17
17 #+srcname: world-inputs 18 - The Java way:
18 #+begin_src clojure :results silent 19 - Create a class that extends =SimpleApplication= or =Application=
19 (ns cortex.world) 20 - Implement setup functions that create all the scene objects using
20 (require 'cortex.import) 21 the inherited =assetManager= and call them by overriding the
21 (use 'clojure.contrib.def) 22 =simpleInitApp= method.
22 (rlm.rlm-commands/help) 23 - Create =ActionListeners= and add them to the =inputManager=
23 (cortex.import/mega-import-jme3) 24 inherited from =Application= to handle key-bindings.
25 - Override =simpleUpdate= to implement game logic.
26 - Running/Testing an Application involves creating a new JVM,
27 running the App, and then closing everything down.
28
29
30 - A more Clojureish way:
31 - Use a map from keys->functions to specify key-bindings.
32 - Use functions to create objects separately from any particular
33 application.
34 - Use an REPL -- this means that there's only ever one JVM, and
35 Applications come and go.
36
37 Since most development work using jMonkeyEngine is done in Java, jme3
38 supports "the Java way" quite well out of the box. To work "the
39 clojure way", it necessary to wrap the jme3 elements that deal with
40 the Application life-cycle with a REPL driven interface.
41
42 The most important modifications are:
43
44 - Separation of Object life-cycles with the Application life-cycle.
45 - Functional interface to the underlying =Application= and
46 =SimpleApplication= classes.
47
48 ** Header
49 #+srcname: header
50 #+begin_src clojure :results silent
51 (ns cortex.world
52 "World Creation, abstracion over jme3's input system, and REPL
53 driven exception handling"
54 {:author "Robert McIntyre"}
55
56 (:use (clojure.contrib (def :only (defvar))))
57 (:use [pokemon [lpsolve :only [constant-map]]])
58 (:use [clojure.contrib [str-utils :only [re-gsub]]])
59
60 (:import com.jme3.math.Vector3f)
61 (:import com.jme3.scene.Node)
62 (:import com.jme3.system.AppSettings)
63 (:import com.jme3.system.JmeSystem)
64 (:import com.jme3.system.IsoTimer)
65 (:import com.jme3.input.KeyInput)
66 (:import com.jme3.input.controls.KeyTrigger)
67 (:import com.jme3.input.controls.MouseButtonTrigger)
68 (:import com.jme3.input.InputManager)
69 (:import com.jme3.bullet.BulletAppState)
70 (:import com.jme3.shadow.BasicShadowRenderer)
71 (:import com.jme3.app.SimpleApplication)
72 (:import com.jme3.input.controls.ActionListener)
73 (:import com.jme3.renderer.queue.RenderQueue$ShadowMode)
74 (:import org.lwjgl.input.Mouse))
75 #+end_src
76
77 ** General Settings
78 #+srcname: settings
79 #+begin_src clojure
80 (in-ns 'cortex.world)
24 81
25 (defvar *app-settings* 82 (defvar *app-settings*
26 (doto (AppSettings. true) 83 (doto (AppSettings. true)
27 (.setFullscreen false) 84 (.setFullscreen false)
28 (.setTitle "Aurellem.") 85 (.setTitle "Aurellem.")
29 ;; disable 32 bit stuff for now 86 ;; disable 32 bit stuff for now
30 ;;(.setAudioRenderer "Send") 87 ;;(.setAudioRenderer "Send")
31 ) 88 )
32 "These settings control how the game is displayed on the screen for 89 "These settings control how the game is displayed on the screen for
33 debugging purposes. Use binding forms to change this if desired. 90 debugging purposes. Use binding forms to change this if desired.
34 Full-screen mode does not work on some computers.") 91 Full-screen mode does not work on some computers.")
35 92
36 (defn asset-manager 93 (defn asset-manager
37 "returns a new, configured assetManager" [] 94 "returns a new, configured assetManager" []
38 (JmeSystem/newAssetManager 95 (JmeSystem/newAssetManager
39 (.getResource 96 (.getResource
40 (.getContextClassLoader (Thread/currentThread)) 97 (.getContextClassLoader (Thread/currentThread))
41 "com/jme3/asset/Desktop.cfg"))) 98 "com/jme3/asset/Desktop.cfg")))
99 #+end_src
100
101 Normally, people just use the =AssetManager= inherited from
102 =Application= whenever they extend that class. However,
103 =AssetManagers= are useful on their own to create objects/ materials,
104 independent from any particular application. =(asset-manager)= makes
105 object creation less tightly bound to Application initialization.
106
107
108 ** Exception Protection
109 #+srcname: exceptions
110 #+begin_src clojure
111 (in-ns 'cortex.world)
42 112
43 (defmacro no-exceptions 113 (defmacro no-exceptions
44 "Sweet relief like I never knew." 114 "Sweet relief like I never knew."
45 [& forms] 115 [& forms]
46 `(try ~@forms (catch Exception e# (.printStackTrace e#)))) 116 `(try ~@forms (catch Exception e# (.printStackTrace e#))))
47 117
48 (defn thread-exception-removal [] 118 (defn thread-exception-removal
49 (println "removing exceptions from " (Thread/currentThread)) 119 "Exceptions thrown in the graphics rendering thread generally cause
120 the entire REPL to crash! It is good to suppress them while trying
121 things out to shorten the debug loop."
122 []
50 (.setUncaughtExceptionHandler 123 (.setUncaughtExceptionHandler
51 (Thread/currentThread) 124 (Thread/currentThread)
52 (proxy [Thread$UncaughtExceptionHandler] [] 125 (proxy [Thread$UncaughtExceptionHandler] []
53 (uncaughtException 126 (uncaughtException
54 [thread thrown] 127 [thread thrown]
55 (println "uncaught-exception thrown in " thread) 128 (println "uncaught-exception thrown in " thread)
56 (println (.getMessage thrown)))))) 129 (println (.getMessage thrown))))))
57 130
58 (def println-repl (bound-fn [& args] (apply println args))) 131 #+end_src
59 132
60 (use '[pokemon [lpsolve :only [constant-map]]]) 133 Exceptions thrown in the LWJGL render thread, if not caught, will
61 134 destroy the entire JVM process including the REPL and slow development
62 (defn no-op [& _]) 135 to a crawl. It is better to try to continue on in the face of
136 exceptions and keep the REPL alive as long as possible. Normally it
137 is possible to just exit the faulty Application, fix the bug,
138 reevaluate the appropriate forms, and be on your way, without
139 restarting the JVM.
140
141 ** Input
142 #+srcname: input
143 #+begin_src clojure
144 (in-ns 'cortex.world)
63 145
64 (defn all-keys 146 (defn all-keys
65 "Construct a map of strings representing all the manual inputs from 147 "Uses reflection to generate a map of string names to jme3 trigger
66 either the keyboard or mouse." 148 objects, which govern input from the keyboard and mouse"
67 [] 149 []
68 (let [inputs (constant-map KeyInput)] 150 (let [inputs (pokemon.lpsolve/constant-map KeyInput)]
69 (assoc 151 (assoc
70 (zipmap (map (fn [field] 152 (zipmap (map (fn [field]
71 (.toLowerCase (re-gsub #"_" "-" field))) (vals inputs)) 153 (.toLowerCase (re-gsub #"_" "-" field))) (vals inputs))
72 (map (fn [val] (KeyTrigger. val)) (keys inputs))) 154 (map (fn [val] (KeyTrigger. val)) (keys inputs)))
73 ;;explicitly add mouse controls 155 ;;explicitly add mouse controls
74 "mouse-left" (MouseButtonTrigger. 0) 156 "mouse-left" (MouseButtonTrigger. 0)
75 "mouse-middle" (MouseButtonTrigger. 2) 157 "mouse-middle" (MouseButtonTrigger. 2)
76 "mouse-right" (MouseButtonTrigger. 1)))) 158 "mouse-right" (MouseButtonTrigger. 1))))
77 159
78 (defn initialize-inputs 160 (defn initialize-inputs
79 "more java-interop cruft to establish keybindings for a particular virtual world" 161 "Establish key-bindings for a particular virtual world."
80 [game input-manager key-map] 162 [game input-manager key-map]
81 (doall (map (fn [[name trigger]] 163 (doall
82 (.addMapping ^InputManager input-manager 164 (map (fn [[name trigger]]
83 name (into-array (class trigger) [trigger]))) key-map)) 165 (.addMapping
84 (doall (map (fn [name] 166 ^InputManager input-manager
85 (.addListener ^InputManager input-manager game 167 name (into-array (class trigger)
86 (into-array String [name]))) (keys key-map)))) 168 [trigger]))) key-map))
87 169 (doall
88 #+end_src 170 (map (fn [name]
89 171 (.addListener
90 These functions are all for debug controlling of the world through 172 ^InputManager input-manager game
91 keyboard and mouse. 173 (into-array String [name]))) (keys key-map))))
92 174
93 We reuse =constant-map= from =pokemon.lpsolve= to get the numerical 175 #+end_src
176
177 These functions are for controlling the world through the keyboard and
178 mouse.
179
180 I reuse =constant-map= from [[../../pokemon-types/html/lpsolve.html#sec-3-3-4][=pokemon.lpsolve=]] to get the numerical
94 values for all the keys defined in the =KeyInput= class. The 181 values for all the keys defined in the =KeyInput= class. The
95 documentation for =constant-map= is: 182 documentation for =constant-map= is:
96 183
97 #+begin_src clojure :results output 184 #+begin_src clojure :results output
98 (doc pokemon.lpsolve/constant-map) 185 (doc pokemon.lpsolve/constant-map)
104 : ([class]) 191 : ([class])
105 : Takes a class and creates a map of the static constant integer 192 : Takes a class and creates a map of the static constant integer
106 : fields with their names. This helps with C wrappers where they have 193 : fields with their names. This helps with C wrappers where they have
107 : just defined a bunch of integer constants instead of enums 194 : just defined a bunch of integer constants instead of enums
108 195
109 196 =(all-keys)= converts the constant names like =KEY_J= to the more
110 Then, =all-keys= converts the constant names like =KEY_J= to the more
111 clojure-like =key-j=, and returns a map from these keys to 197 clojure-like =key-j=, and returns a map from these keys to
112 jMonkeyEngine KeyTrigger objects, the use of which will soon become 198 jMonkeyEngine =KeyTrigger= objects, which jMonkeyEngine3 uses as it's
113 apparent. =all-keys= also adds the three mouse button controls to the 199 abstraction over the physical keys. =all-keys= also adds the three
114 map. 200 mouse button controls to the map.
115 201
202
203 #+begin_src clojure :exports both :results output
204 (require 'clojure.contrib.pprint)
205 (clojure.contrib.pprint/pprint
206 (take 10 (keys (cortex.world/all-keys))))
207 #+end_src
208
209 #+results:
210 #+begin_example
211 ("key-n"
212 "key-apps"
213 "key-pgup"
214 "key-f8"
215 "key-o"
216 "key-at"
217 "key-f9"
218 "key-0"
219 "key-p"
220 "key-subtract")
221 #+end_example
222
223 #+begin_src clojure :exports both :results output
224 (clojure.contrib.pprint/pprint
225 (take 10 (vals (cortex.world/all-keys))))
226 #+end_src
227
228 #+results:
229 #+begin_example
230 (#<KeyTrigger com.jme3.input.controls.KeyTrigger@6ec21e52>
231 #<KeyTrigger com.jme3.input.controls.KeyTrigger@a54d24d>
232 #<KeyTrigger com.jme3.input.controls.KeyTrigger@1ba5e91b>
233 #<KeyTrigger com.jme3.input.controls.KeyTrigger@296af9cb>
234 #<KeyTrigger com.jme3.input.controls.KeyTrigger@2e3593ab>
235 #<KeyTrigger com.jme3.input.controls.KeyTrigger@3f71d740>
236 #<KeyTrigger com.jme3.input.controls.KeyTrigger@4aeacb4a>
237 #<KeyTrigger com.jme3.input.controls.KeyTrigger@7cc88db2>
238 #<KeyTrigger com.jme3.input.controls.KeyTrigger@52cee11e>
239 #<KeyTrigger com.jme3.input.controls.KeyTrigger@c1da30b>)
240 #+end_example
241
242
243
244 ** World Creation
116 #+srcname: world 245 #+srcname: world
117 #+begin_src clojure :results silent 246 #+begin_src clojure :results silent
118 (in-ns 'cortex.world) 247 (in-ns 'cortex.world)
248
249 (defn no-op
250 "Takes any number of arguments and does nothing."
251 [& _])
119 252
120 (defn traverse 253 (defn traverse
121 "apply f to every non-node, deeply" 254 "apply f to every non-node, deeply"
122 [f node] 255 [f node]
123 (if (isa? (class node) Node) 256 (if (isa? (class node) Node)
124 (dorun (map (partial traverse f) (.getChildren node))) 257 (dorun (map (partial traverse f) (.getChildren node)))
125 (f node))) 258 (f node)))
126 259
127 (def gravity (Vector3f. 0 -9.81 0))
128
129 (defn world 260 (defn world
261 "the =world= function takes care of the details of initializing a
262 SimpleApplication.
263
264 ***** Arguments:
265
266 - root-node : a com.jme3.scene.Node object which contains all of
267 the objects that should be in the simulation.
268
269 - key-map : a map from strings describing keys to functions that
270 should be executed whenever that key is pressed.
271 the functions should take a SimpleApplication object and a
272 boolean value. The SimpleApplication is the current simulation
273 that is running, and the boolean is true if the key is being
274 pressed, and false if it is being released. As an example,
275
276 {\"key-j\" (fn [game value] (if value (println \"key j pressed\")))}
277
278 is a valid key-map which will cause the simulation to print a
279 message whenever the 'j' key on the keyboard is pressed.
280
281 - setup-fn : a function that takes a SimpleApplication object. It
282 is called once when initializing the simulation. Use it to
283 create things like lights, change the gravity, initialize debug
284 nodes, etc.
285
286 - update-fn : this function takes a SimpleApplication object and a
287 float and is called every frame of the simulation. The float
288 tells how many seconds is has been since the last frame was
289 rendered, according to whatever clock jme is currently
290 using. The default is to use IsoTimer which will result in this
291 value always being the same.
292 "
130 [root-node key-map setup-fn update-fn] 293 [root-node key-map setup-fn update-fn]
131 (let [physics-manager (BulletAppState.) 294 (let [physics-manager (BulletAppState.)
132 shadow-renderer (BasicShadowRenderer. (asset-manager) (int 256)) 295 shadow-renderer (BasicShadowRenderer.
133 ;;maybe use a better shadow renderer someday! 296 (asset-manager) (int 256))]
134 ;;shadow-renderer (PssmShadowRenderer. (asset-manager) 256 1) 297 (doto
135 ] 298 (proxy [SimpleApplication ActionListener] []
136 (doto 299 (simpleInitApp
137 (proxy [SimpleApplication ActionListener] [] 300 []
138 (simpleInitApp 301 (no-exceptions
139 [] 302 ;; allow AI entities as much time as they need to think.
140 (no-exceptions 303 (.setTimer this (IsoTimer. 60))
141 (.setTimer this (IsoTimer. 60)) 304 (.setFrustumFar (.getCamera this) 300)
142 ;; Create key-map. 305 ;; Create default key-map.
143 (.setFrustumFar (.getCamera this) 300) 306 (initialize-inputs this (.getInputManager this) (all-keys))
144 (initialize-inputs this (.getInputManager this) (all-keys)) 307 ;; Don't take control of the mouse
145 ;; Don't take control of the mouse 308 (org.lwjgl.input.Mouse/setGrabbed false)
146 (org.lwjgl.input.Mouse/setGrabbed false) 309 ;; add all objects to the world
147 ;; add all objects to the world 310 (.attachChild (.getRootNode this) root-node)
148 (.attachChild (.getRootNode this) root-node) 311 ;; enable physics
149 ;; enable physics 312 ;; add a physics manager
150 ;; add a physics manager 313 (.attach (.getStateManager this) physics-manager)
151 (.attach (.getStateManager this) physics-manager) 314 (.setGravity (.getPhysicsSpace physics-manager)
152 (.setGravity (.getPhysicsSpace physics-manager) gravity) 315 (Vector3f. 0 -9.81 0))
153 316 ;; go through every object and add it to the physics
154 317 ;; manager if relevant.
155 ;; go through every object and add it to the physics manager 318 (traverse (fn [geom]
156 ;; if relavant. 319 (dorun
157 (traverse (fn [geom] 320 (for [n (range (.getNumControls geom))]
158 (dorun 321 (do
159 (for [n (range (.getNumControls geom))] 322 (.add (.getPhysicsSpace physics-manager)
160 (do 323 (.getControl geom n))))))
161 (println-repl "adding control " (.getControl geom n)) 324 (.getRootNode this))
162 (.add (.getPhysicsSpace physics-manager) 325 ;;(.addAll (.getPhysicsSpace physics-manager) (.getRootNode this))
163 (.getControl geom n)))))) 326
164 (.getRootNode this)) 327 ;; set some basic defaults for the shadow renderer.
165 ;;(.addAll (.getPhysicsSpace physics-manager) (.getRootNode this)) 328 ;; these can be undone in the setup function
166 329 (.setDirection shadow-renderer
167 (setup-fn this) 330 (.normalizeLocal (Vector3f. -1 -1 -1)))
168 (.setDirection shadow-renderer 331 (.addProcessor (.getViewPort this) shadow-renderer)
169 (.normalizeLocal (Vector3f. -1 -1 -1))) 332 (.setShadowMode (.getRootNode this)
170 (.addProcessor (.getViewPort this) shadow-renderer) 333 RenderQueue$ShadowMode/Off)
171 (.setShadowMode (.getRootNode this) RenderQueue$ShadowMode/Off) 334 ;; call the supplied setup-fn
172 )) 335 (if setup-fn
173 (simpleUpdate 336 (setup-fn this))))
174 [tpf] 337 (simpleUpdate
175 (no-exceptions 338 [tpf]
176 (update-fn this tpf))) 339 (no-exceptions
177 (onAction 340 (update-fn this tpf)))
178 [binding value tpf] 341 (onAction
179 ;; whenever a key is pressed, call the function returned from 342 [binding value tpf]
180 ;; key-map. 343 ;; whenever a key is pressed, call the function returned
181 (no-exceptions 344 ;; from key-map.
182 (if-let [react (key-map binding)] 345 (no-exceptions
183 (react this value))))) 346 (if-let [react (key-map binding)]
184 ;; don't show a menu to change options. 347 (react this value)))))
185 348 ;; don't show a menu to change options.
186 (.setShowSettings false) 349 (.setShowSettings false)
187 (.setPauseOnLostFocus false) 350 ;; continue running simulation even if the window has lost
188 (.setSettings *app-settings*)))) 351 ;; focus.
352 (.setPauseOnLostFocus false)
353 (.setSettings *app-settings*))))
189 354
190 (defn apply-map 355 (defn apply-map
191 "Like apply, but works for maps and functions that expect an implicit map 356 "Like apply, but works for maps and functions that expect an
192 and nothing else as in (fn [& {}]). 357 implicit map and nothing else as in (fn [& {}]).
193 ------- Example ------- 358 ------- Example -------
194 (defn jjj [& {:keys [www] :or {www \"oh yeah\"} :as env}] (println www)) 359 (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}]
195 (apply-map jjj {:www \"whatever\"}) 360 (println www))
196 -->\"whatever\"" 361 (apply-map demo {:www \"hello!\"})
362 -->\"hello\""
197 [fn m] 363 [fn m]
198 (apply fn (reduce #(into %1 %2) [] m))) 364 (apply fn (reduce #(into %1 %2) [] m)))
199 365
200 #+end_src 366 #+end_src
201 367
202 368
203 =world= is the most important function here. 369 =(world)= is the most important function here. It presents a more
204 *** TODO more documentation 370 functional interface to the Application life-cycle, and all it's
205 371 objects except =root-node= are plain clojure data structures. It's now
206 #+srcname: world-shapes 372 possible to extend functionally by composing multiple functions
207 #+begin_src clojure :results silent 373 together, and to add more keyboard-driven actions by combining clojure
208 (in-ns 'cortex.world) 374 maps.
209 (defrecord shape-description
210 [name
211 color
212 mass
213 friction
214 texture
215 material
216 position
217 rotation
218 shape
219 physical?])
220
221 (def base-shape
222 (shape-description.
223 "default-shape"
224 false
225 ;;ColorRGBA/Blue
226 1.0 ;; mass
227 1.0 ;; friction
228 ;; texture
229 "Textures/Terrain/BrickWall/BrickWall.jpg"
230 ;; material
231 "Common/MatDefs/Misc/Unshaded.j3md"
232 Vector3f/ZERO
233 Quaternion/IDENTITY
234 (Box. Vector3f/ZERO 0.5 0.5 0.5)
235 true))
236
237 (defn make-shape
238 [#^shape-description d]
239 (let [asset-manager (if (:asset-manager d) (:asset-manager d) (asset-manager))
240 mat (Material. asset-manager (:material d))
241 geom (Geometry. (:name d) (:shape d))]
242 (if (:texture d)
243 (let [key (TextureKey. (:texture d))]
244 (.setGenerateMips key true)
245 (.setTexture mat "ColorMap" (.loadTexture asset-manager key))))
246 (if (:color d) (.setColor mat "Color" (:color d)))
247 (.setMaterial geom mat)
248 (if-let [rotation (:rotation d)] (.rotate geom rotation))
249 (.setLocalTranslation geom (:position d))
250 (if (:physical? d)
251 (let [impact-shape (doto (GImpactCollisionShape.
252 (.getMesh geom)) (.setMargin 0))
253 physics-control (RigidBodyControl.
254 ;;impact-shape ;; comment to disable
255 (float (:mass d)))]
256 (.createJmeMesh impact-shape)
257 (.addControl geom physics-control)
258 ;;(.setSleepingThresholds physics-control (float 0) (float 0))
259 (.setFriction physics-control (:friction d))))
260 ;;the default is to keep this node in the physics engine forever.
261 ;;these commands must come after the control is added to the geometry.
262 ;;
263 geom))
264
265 (defn box
266 ([l w h & {:as options}]
267 (let [options (merge base-shape options)]
268 (make-shape (assoc options
269 :shape (Box. l w h)))))
270 ([] (box 0.5 0.5 0.5)))
271
272 (defn sphere
273 ([r & {:as options}]
274 (let [options (merge base-shape options)]
275 (make-shape (assoc options
276 :shape (Sphere. 32 32 (float r))))))
277 ([] (sphere 0.5)))
278
279 (defn add-element
280 ([game element node]
281 (.addAll
282 (.getPhysicsSpace
283 (.getState
284 (.getStateManager game)
285 BulletAppState))
286 element)
287 (.attachChild node element))
288 ([game element]
289 (add-element game element (.getRootNode game))))
290
291
292 (defn set-gravity*
293 [game gravity]
294 (traverse
295 (fn [geom]
296 (if-let
297 [control (.getControl geom RigidBodyControl)]
298 (do
299 (.setGravity control gravity)
300 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO)
301 )))
302 (.getRootNode game)))
303
304 #+end_src
305
306 These are convienence functions for creating JME objects and
307 manipulating a world.
308
309
310 375
311 376
312 377
313 * COMMENT code generation 378 * COMMENT code generation
314
315 #+begin_src clojure :tangle ../src/cortex/world.clj 379 #+begin_src clojure :tangle ../src/cortex/world.clj
316 <<world-inputs>> 380 <<header>>
381 <<settings>>
382 <<exceptions>>
383 <<input>>
317 <<world>> 384 <<world>>
318 <<world-shapes>> 385 #+end_src
319 #+end_src