annotate org/util.org @ 69:39e4e1542e4a

updated test-suite
author Robert McIntyre <rlm@mit.edu>
date Fri, 09 Dec 2011 23:11:28 -0600
parents 1381a6ebd08b
children 0235c32152af
rev   line source
rlm@29 1 #+title: Clojure Utilities for jMonkeyEngine3
rlm@23 2 #+author: Robert McIntyre
rlm@23 3 #+email: rlm@mit.edu
rlm@29 4 #+description:
rlm@29 5 #+keywords: JME3, clojure, import, utilities
rlm@23 6 #+SETUPFILE: ../../aurellem/org/setup.org
rlm@23 7 #+INCLUDE: ../../aurellem/org/level-0.org
rlm@29 8
rlm@34 9 [TABLE-OF-CONTENTS]
rlm@29 10
rlm@29 11 These are a collection of functions to make programming jMonkeyEngine
rlm@29 12 in clojure easier.
rlm@23 13
rlm@34 14 * Imports
rlm@28 15
rlm@66 16 #+name: import
rlm@23 17 #+begin_src clojure :results silent
rlm@23 18 (ns cortex.import
rlm@23 19 (:require swank.util.class-browse))
rlm@23 20
rlm@23 21 (defn permissive-import
rlm@23 22 [classname]
rlm@23 23 (eval `(try (import '~classname)
rlm@23 24 (catch java.lang.Exception e#
rlm@23 25 (println "couldn't import " '~classname))))
rlm@23 26 classname)
rlm@23 27
rlm@23 28 (defn jme-class? [classname]
rlm@23 29 (and
rlm@23 30 (.startsWith classname "com.jme3.")
rlm@23 31 ;; Don't import the Lwjgl stuff since it can throw exceptions
rlm@23 32 ;; upon being loaded.
rlm@23 33 (not (re-matches #".*Lwjgl.*" classname))))
rlm@23 34
rlm@23 35 (defn jme-classes
rlm@23 36 "returns a list of all jme3 classes"
rlm@23 37 []
rlm@23 38 (filter
rlm@23 39 jme-class?
rlm@23 40 (map :name
rlm@23 41 swank.util.class-browse/available-classes)))
rlm@23 42
rlm@23 43 (defn mega-import-jme3
rlm@23 44 "Import ALL the jme classes. For REPL use."
rlm@23 45 []
rlm@23 46 (doall
rlm@23 47 (map (comp permissive-import symbol) (jme-classes))))
rlm@23 48 #+end_src
rlm@23 49
rlm@29 50 jMonkeyEngine3 has a plethora of classes which can be overwhelming to
rlm@29 51 manage. This code uses reflection to import all of them. Once I'm
rlm@29 52 happy with the general structure of a namespace I can deal with
rlm@29 53 importing only the classes it actually needs.
rlm@29 54
rlm@23 55 The =mega-import-jme3= is quite usefull for debugging purposes since
rlm@34 56 it allows completion for almost all of JME's classes from the REPL.
rlm@23 57
rlm@23 58 Out of curiousity, let's see just how many classes =mega-import-jme3=
rlm@23 59 imports:
rlm@23 60
rlm@29 61 #+begin_src clojure :exports both :results output
rlm@29 62 (println (clojure.core/count (cortex.import/jme-classes)) "classes")
rlm@23 63 #+end_src
rlm@23 64
rlm@23 65 #+results:
rlm@29 66 : 955 classes
rlm@23 67
rlm@25 68
rlm@34 69 * Utilities
rlm@23 70
rlm@29 71 The utilities here come in three main groups:
rlm@29 72 - Changing settings in a running =Application=
rlm@29 73 - Creating objects
rlm@50 74 - Debug Actions
rlm@29 75 - Visualizing objects
rlm@23 76
rlm@23 77
rlm@50 78
rlm@29 79 *** Changing Settings
rlm@24 80
rlm@66 81 #+name: util
rlm@25 82 #+begin_src clojure
rlm@29 83 (ns cortex.util
rlm@34 84 "Utility functions for making jMonkeyEngine3 easier to program from
rlm@34 85 clojure."
rlm@29 86 {:author "Robert McIntyre"}
rlm@29 87 (:use cortex.world)
rlm@29 88 (:use clojure.contrib.def)
rlm@29 89 (:import com.jme3.math.Vector3f)
rlm@29 90 (:import com.jme3.math.Quaternion)
rlm@29 91 (:import com.jme3.asset.TextureKey)
rlm@29 92 (:import com.jme3.bullet.control.RigidBodyControl)
rlm@29 93 (:import com.jme3.bullet.collision.shapes.GImpactCollisionShape)
rlm@29 94 (:import com.jme3.scene.shape.Box)
rlm@29 95 (:import com.jme3.scene.Node)
rlm@29 96 (:import com.jme3.scene.shape.Sphere)
rlm@47 97 (:import com.jme3.light.AmbientLight)
rlm@29 98 (:import com.jme3.light.DirectionalLight)
rlm@29 99 (:import com.jme3.math.ColorRGBA)
rlm@29 100 (:import com.jme3.bullet.BulletAppState)
rlm@29 101 (:import com.jme3.material.Material)
rlm@40 102 (:import com.jme3.scene.Geometry)
rlm@40 103 (:import (java.util.logging Level Logger)))
rlm@40 104
rlm@40 105
rlm@25 106
rlm@29 107 (defvar println-repl
rlm@29 108 (bound-fn [& args] (apply println args))
rlm@29 109 "println called from the LWJGL thread will not go to the REPL, but
rlm@29 110 instead to whatever terminal started the JVM process. This function
rlm@29 111 will always output to the REPL")
rlm@25 112
rlm@29 113 (defn position-camera
rlm@34 114 "Change the position of the in-world camera."
rlm@34 115 ([world position direction up]
rlm@34 116 (doto (.getCamera world)
rlm@29 117 (.setLocation )
rlm@29 118 (.lookAt direction up)))
rlm@34 119 ([world position direction]
rlm@29 120 (position-camera
rlm@34 121 world position direction Vector3f/UNIT_Y)))
rlm@25 122
rlm@29 123 (defn enable-debug
rlm@34 124 "Turn on debug wireframes for every object in this simulation."
rlm@29 125 [world]
rlm@29 126 (.enableDebug
rlm@29 127 (.getPhysicsSpace
rlm@29 128 (.getState
rlm@29 129 (.getStateManager world)
rlm@29 130 BulletAppState))
rlm@29 131 (asset-manager)))
rlm@29 132
rlm@40 133 (defn no-logging
rlm@40 134 "Disable all of jMonkeyEngine's logging."
rlm@40 135 []
rlm@40 136 (.setLevel (Logger/getLogger "com.jme3") Level/OFF))
rlm@40 137
rlm@40 138 (defn set-accuracy
rlm@40 139 "Change the accuracy at which the World's Physics is calculated."
rlm@40 140 [world new-accuracy]
rlm@40 141 (let [physics-manager
rlm@40 142 (.getState
rlm@40 143 (.getStateManager world) BulletAppState)]
rlm@40 144 (.setAccuracy
rlm@40 145 (.getPhysicsSpace physics-manager)
rlm@40 146 (float new-accuracy))))
rlm@40 147
rlm@40 148
rlm@34 149 (defn set-gravity
rlm@29 150 "In order to change the gravity of a scene, it is not only necessary
rlm@29 151 to set the gravity variable, but to \"tap\" every physics object in
rlm@29 152 the scene to reactivate physics calculations."
rlm@34 153 [world gravity]
rlm@25 154 (traverse
rlm@25 155 (fn [geom]
rlm@25 156 (if-let
rlm@29 157 ;; only set gravity for physical objects.
rlm@25 158 [control (.getControl geom RigidBodyControl)]
rlm@25 159 (do
rlm@25 160 (.setGravity control gravity)
rlm@29 161 ;; tappsies!
rlm@29 162 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO))))
rlm@34 163 (.getRootNode world)))
rlm@29 164
rlm@29 165 (defn add-element
rlm@34 166 "Add the Spatial to the world's environment"
rlm@34 167 ([world element node]
rlm@29 168 (.addAll
rlm@29 169 (.getPhysicsSpace
rlm@29 170 (.getState
rlm@34 171 (.getStateManager world)
rlm@29 172 BulletAppState))
rlm@29 173 element)
rlm@29 174 (.attachChild node element))
rlm@34 175 ([world element]
rlm@34 176 (add-element world element (.getRootNode world))))
rlm@29 177
rlm@29 178 (defn apply-map
rlm@29 179 "Like apply, but works for maps and functions that expect an
rlm@29 180 implicit map and nothing else as in (fn [& {}]).
rlm@29 181 ------- Example -------
rlm@29 182 (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}]
rlm@29 183 (println www))
rlm@29 184 (apply-map demo {:www \"hello!\"})
rlm@29 185 -->\"hello\""
rlm@29 186 [fn m]
rlm@29 187 (apply fn (reduce #(into %1 %2) [] m)))
rlm@29 188
rlm@25 189 #+end_src
rlm@25 190
rlm@47 191 #+results: util
rlm@47 192 : #'cortex.util/apply-map
rlm@47 193
rlm@25 194
rlm@29 195 *** Creating Basic Shapes
rlm@29 196
rlm@66 197 #+name: shapes
rlm@25 198 #+begin_src clojure :results silent
rlm@25 199 (in-ns 'cortex.util)
rlm@29 200
rlm@25 201 (defrecord shape-description
rlm@25 202 [name
rlm@25 203 color
rlm@25 204 mass
rlm@25 205 friction
rlm@25 206 texture
rlm@25 207 material
rlm@25 208 position
rlm@25 209 rotation
rlm@25 210 shape
rlm@29 211 physical?
rlm@29 212 GImpact?
rlm@29 213 ])
rlm@25 214
rlm@34 215 (defvar base-shape
rlm@25 216 (shape-description.
rlm@25 217 "default-shape"
rlm@25 218 false
rlm@25 219 ;;ColorRGBA/Blue
rlm@25 220 1.0 ;; mass
rlm@25 221 1.0 ;; friction
rlm@25 222 ;; texture
rlm@25 223 "Textures/Terrain/BrickWall/BrickWall.jpg"
rlm@25 224 ;; material
rlm@25 225 "Common/MatDefs/Misc/Unshaded.j3md"
rlm@25 226 Vector3f/ZERO
rlm@25 227 Quaternion/IDENTITY
rlm@25 228 (Box. Vector3f/ZERO 0.5 0.5 0.5)
rlm@29 229 true
rlm@34 230 false)
rlm@34 231 "Basic settings for shapes.")
rlm@25 232
rlm@25 233 (defn make-shape
rlm@25 234 [#^shape-description d]
rlm@29 235 (let [asset-manager (asset-manager)
rlm@25 236 mat (Material. asset-manager (:material d))
rlm@25 237 geom (Geometry. (:name d) (:shape d))]
rlm@25 238 (if (:texture d)
rlm@25 239 (let [key (TextureKey. (:texture d))]
rlm@34 240 ;;(.setGenerateMips key true)
rlm@34 241 ;;(.setTexture mat "ColorMap" (.loadTexture asset-manager key))
rlm@34 242 ))
rlm@25 243 (if (:color d) (.setColor mat "Color" (:color d)))
rlm@25 244 (.setMaterial geom mat)
rlm@25 245 (if-let [rotation (:rotation d)] (.rotate geom rotation))
rlm@25 246 (.setLocalTranslation geom (:position d))
rlm@25 247 (if (:physical? d)
rlm@29 248 (let [physics-control
rlm@29 249 (if (:GImpact d)
rlm@29 250 ;; Create an accurate mesh collision shape if desired.
rlm@29 251 (RigidBodyControl.
rlm@29 252 (doto (GImpactCollisionShape.
rlm@29 253 (.getMesh geom))
rlm@29 254 (.createJmeMesh)
rlm@29 255 (.setMargin 0))
rlm@29 256 (float (:mass d)))
rlm@29 257 ;; otherwise use jme3's default
rlm@29 258 (RigidBodyControl. (float (:mass d))))]
rlm@25 259 (.addControl geom physics-control)
rlm@25 260 ;;(.setSleepingThresholds physics-control (float 0) (float 0))
rlm@25 261 (.setFriction physics-control (:friction d))))
rlm@25 262 geom))
rlm@25 263
rlm@25 264 (defn box
rlm@25 265 ([l w h & {:as options}]
rlm@25 266 (let [options (merge base-shape options)]
rlm@25 267 (make-shape (assoc options
rlm@25 268 :shape (Box. l w h)))))
rlm@25 269 ([] (box 0.5 0.5 0.5)))
rlm@25 270
rlm@25 271 (defn sphere
rlm@25 272 ([r & {:as options}]
rlm@25 273 (let [options (merge base-shape options)]
rlm@25 274 (make-shape (assoc options
rlm@25 275 :shape (Sphere. 32 32 (float r))))))
rlm@25 276 ([] (sphere 0.5)))
rlm@62 277
rlm@62 278 (defn node-seq
rlm@62 279 "Take a node and return a seq of all its children
rlm@62 280 recursively. There will be no nodes left in the resulting
rlm@62 281 structure"
rlm@62 282 [#^Node node]
rlm@62 283 (tree-seq #(isa? (class %) Node) #(.getChildren %) node))
rlm@62 284
rlm@62 285 (defn nodify
rlm@62 286 "Take a sequence of things that can be attached to a node and return
rlm@62 287 a node with all of them attached"
rlm@62 288 ([name children]
rlm@62 289 (let [node (Node. name)]
rlm@62 290 (dorun (map #(.attachChild node %) children))
rlm@62 291 node))
rlm@62 292 ([children] (nodify "" children)))
rlm@62 293
rlm@62 294
rlm@29 295 #+end_src
rlm@25 296
rlm@25 297
rlm@50 298 *** Debug Actions
rlm@66 299 #+name: debug-actions
rlm@29 300 #+begin_src clojure :results silent
rlm@60 301 (in-ns 'cortex.util)
rlm@60 302
rlm@47 303 (defn basic-light-setup
rlm@47 304 "returns a sequence of lights appropiate for fully lighting a scene"
rlm@47 305 []
rlm@47 306 (conj
rlm@47 307 (doall
rlm@47 308 (map
rlm@47 309 (fn [direction]
rlm@47 310 (doto (DirectionalLight.)
rlm@47 311 (.setDirection direction)
rlm@47 312 (.setColor ColorRGBA/White)))
rlm@47 313 [;; six faces of a cube
rlm@47 314 Vector3f/UNIT_X
rlm@47 315 Vector3f/UNIT_Y
rlm@47 316 Vector3f/UNIT_Z
rlm@47 317 (.mult Vector3f/UNIT_X (float -1))
rlm@47 318 (.mult Vector3f/UNIT_Y (float -1))
rlm@47 319 (.mult Vector3f/UNIT_Z (float -1))]))
rlm@47 320 (doto (AmbientLight.)
rlm@47 321 (.setColor ColorRGBA/White))))
rlm@47 322
rlm@47 323 (defn light-up-everything
rlm@47 324 "Add lights to a world appropiate for quickly seeing everything
rlm@47 325 in the scene. Adds six DirectionalLights facing in orthogonal
rlm@47 326 directions, and one AmbientLight to provide overall lighting
rlm@47 327 coverage."
rlm@47 328 [world]
rlm@47 329 (dorun
rlm@47 330 (map
rlm@47 331 #(.addLight (.getRootNode world) %)
rlm@47 332 (basic-light-setup))))
rlm@50 333
rlm@50 334 (defn fire-cannon-ball
rlm@50 335 "Creates a function that fires a cannon-ball from the current game's
rlm@50 336 camera. The cannon-ball will be attached to the node if provided, or
rlm@50 337 to the game's RootNode if no node is provided."
rlm@50 338 ([node]
rlm@50 339 (fn [game value]
rlm@50 340 (if (not value)
rlm@50 341 (let [camera (.getCamera game)
rlm@50 342 cannon-ball
rlm@50 343 (sphere 0.7
rlm@50 344 :material "Common/MatDefs/Misc/Unshaded.j3md"
rlm@50 345 :texture "Textures/PokeCopper.jpg"
rlm@50 346 :position
rlm@50 347 (.add (.getLocation camera)
rlm@50 348 (.mult (.getDirection camera) (float 1)))
rlm@50 349 :mass 3)] ;200 0.05
rlm@50 350 (.setLinearVelocity
rlm@50 351 (.getControl cannon-ball RigidBodyControl)
rlm@50 352 (.mult (.getDirection camera) (float 50))) ;50
rlm@50 353 (add-element game cannon-ball (if node node (.getRootNode game)))))))
rlm@50 354 ([]
rlm@50 355 (fire-cannon-ball false)))
rlm@60 356
rlm@60 357 (def standard-debug-controls
rlm@60 358 {"key-space" (fire-cannon-ball)})
rlm@60 359
rlm@60 360
rlm@60 361
rlm@60 362
rlm@50 363 #+end_src
rlm@50 364
rlm@50 365
rlm@50 366 *** Viewing Objects
rlm@50 367
rlm@66 368 #+name: world-view
rlm@50 369 #+begin_src clojure :results silent
rlm@50 370 (in-ns 'cortex.util)
rlm@50 371
rlm@50 372 (defprotocol Viewable
rlm@50 373 (view [something]))
rlm@50 374
rlm@50 375 (extend-type com.jme3.scene.Geometry
rlm@50 376 Viewable
rlm@50 377 (view [geo]
rlm@50 378 (view (doto (Node.)(.attachChild geo)))))
rlm@47 379
rlm@29 380 (extend-type com.jme3.scene.Node
rlm@29 381 Viewable
rlm@29 382 (view
rlm@29 383 [node]
rlm@29 384 (.start
rlm@29 385 (world
rlm@29 386 node
rlm@29 387 {}
rlm@29 388 (fn [world]
rlm@29 389 (enable-debug world)
rlm@29 390 (set-gravity world Vector3f/ZERO)
rlm@47 391 (light-up-everything world))
rlm@29 392 no-op))))
rlm@62 393
rlm@62 394 (extend-type com.jme3.math.ColorRGBA
rlm@62 395 Viewable
rlm@62 396 (view
rlm@62 397 [color]
rlm@62 398 (view (doto (Node.)
rlm@62 399 (.attachChild (box 1 1 1 :color color))))))
rlm@62 400
rlm@62 401 (defprotocol Textual
rlm@62 402 (text [something]
rlm@62 403 "Display a detailed textual analysis of the given object."))
rlm@62 404
rlm@62 405 (extend-type com.jme3.scene.Node
rlm@62 406 Textual
rlm@62 407 (text [node]
rlm@62 408 (println "Total Vertexes: " (.getVertexCount node))
rlm@62 409 (println "Total Triangles: " (.getTriangleCount node))
rlm@62 410 (println "Controls :")
rlm@62 411 (dorun (map #(text (.getControl node %)) (range (.getNumControls node))))
rlm@62 412 (println "Has " (.getQuantity node) " Children:")
rlm@62 413 (doall (map text (.getChildren node)))))
rlm@62 414
rlm@62 415 (extend-type com.jme3.animation.AnimControl
rlm@62 416 Textual
rlm@62 417 (text [control]
rlm@62 418 (let [animations (.getAnimationNames control)]
rlm@62 419 (println "Animation Control with " (count animations) " animation(s):")
rlm@62 420 (dorun (map println animations)))))
rlm@62 421
rlm@62 422 (extend-type com.jme3.animation.SkeletonControl
rlm@62 423 Textual
rlm@62 424 (text [control]
rlm@62 425 (println "Skeleton Control with the following skeleton:")
rlm@62 426 (println (.getSkeleton control))))
rlm@62 427
rlm@62 428 (extend-type com.jme3.bullet.control.KinematicRagdollControl
rlm@62 429 Textual
rlm@62 430 (text [control]
rlm@62 431 (println "Ragdoll Control")))
rlm@62 432
rlm@62 433 (extend-type com.jme3.scene.Geometry
rlm@62 434 Textual
rlm@62 435 (text [control]
rlm@62 436 (println "...geo...")))
rlm@25 437 #+end_src
rlm@25 438
rlm@29 439 Here I make the =Viewable= protocol and extend it to JME's types. Now
rlm@34 440 JME3's =hello-world= can be written as easily as:
rlm@29 441
rlm@29 442 #+begin_src clojure :results silent
rlm@29 443 (cortex.util/view (cortex.util/box))
rlm@29 444 #+end_src
rlm@29 445
rlm@29 446
rlm@24 447 * COMMENT code generation
rlm@24 448 #+begin_src clojure :tangle ../src/cortex/import.clj
rlm@24 449 <<import>>
rlm@24 450 #+end_src
rlm@25 451
rlm@25 452
rlm@29 453 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes
rlm@25 454 <<util>>
rlm@25 455 <<shapes>>
rlm@50 456 <<debug-actions>>
rlm@29 457 <<world-view>>
rlm@25 458 #+end_src
rlm@25 459
rlm@29 460
rlm@29 461
rlm@29 462
rlm@29 463
rlm@29 464
rlm@29 465
rlm@29 466
rlm@29 467
rlm@29 468
rlm@29 469
rlm@29 470
rlm@29 471
rlm@29 472
rlm@29 473
rlm@29 474
rlm@29 475