Mercurial > cortex
view org/util.org @ 446:3e91585b2a1c
save.
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Tue, 25 Mar 2014 03:24:28 -0400 |
parents | 6ba908c1a0a9 |
children | 0a4362d1f138 |
line wrap: on
line source
1 #+title: Clojure Utilities for jMonkeyEngine32 #+author: Robert McIntyre3 #+email: rlm@mit.edu4 #+description:5 #+keywords: JME3, clojure, import, utilities6 #+SETUPFILE: ../../aurellem/org/setup.org7 #+INCLUDE: ../../aurellem/org/level-0.org9 [TABLE-OF-CONTENTS]11 These are a collection of functions to make programming jMonkeyEngine12 in clojure easier.14 * Imports16 #+name: import17 #+begin_src clojure :results silent18 (ns cortex.import19 (:import java.io.File java.util.jar.JarFile))21 (defn permissive-import22 [classname]23 (eval `(try (import '~classname)24 (catch java.lang.Exception e#25 (println "couldn't import " '~classname))))26 classname)28 (defn jme-class? [classname]29 (and30 (.startsWith classname "com.jme3.")31 ;; Don't import the LWJGL stuff since it can throw exceptions32 ;; upon being loaded.33 (not (re-matches #".*Lwjgl.*" classname))))35 (defn jme-jars []36 (map37 #(JarFile. (File. %))38 (filter (partial re-matches #".*jME3.*")39 (clojure.string/split40 (System/getProperty "java.class.path") #":"))))42 (defn jme-class-names []43 (filter44 jme-class?45 (map46 (comp47 #(.replace % File/separator ".")48 #(clojure.string/replace % ".class" ""))49 (filter50 (partial re-matches #".*\.class$")51 (mapcat52 #(map53 str54 (enumeration-seq55 (.entries %)))56 (jme-jars))))))58 (defn mega-import-jme359 "Import ALL the jme classes. For REPL use."60 []61 (dorun62 (map (comp permissive-import symbol) (jme-class-names))))63 #+end_src65 jMonkeyEngine3 has a plethora of classes which can be overwhelming to66 manage. This code uses reflection to import all of them. Once I'm67 happy with the general structure of a namespace I can deal with68 importing only the classes it actually needs.70 The =mega-import-jme3= is quite useful for debugging purposes since71 it allows completion for almost all of JME's classes from the REPL.73 Out of curiosity, let's see just how many classes =mega-import-jme3=74 imports:76 #+begin_src clojure :exports both :results output77 (println (clojure.core/count (cortex.import/jme-classes)) "classes")78 #+end_src80 #+results:81 : 955 classes84 * Utilities86 The utilities here come in three main groups:87 - Changing settings in a running =Application=88 - Creating objects89 - Debug Actions90 - Visualizing objects92 *** Changing Settings94 #+name: util95 #+begin_src clojure96 (ns cortex.util97 "Utility functions for making jMonkeyEngine3 easier to program from98 clojure."99 {:author "Robert McIntyre"}100 (:use cortex.world)101 (:import com.jme3.math.Vector3f)102 (:import com.jme3.math.Quaternion)103 (:import com.jme3.asset.TextureKey)104 (:import com.jme3.bullet.control.RigidBodyControl)105 (:import com.jme3.bullet.collision.shapes.GImpactCollisionShape)106 (:import com.jme3.scene.shape.Box)107 (:import com.jme3.scene.Node)108 (:import com.jme3.scene.shape.Sphere)109 (:import com.jme3.light.AmbientLight)110 (:import com.jme3.light.DirectionalLight)111 (:import (com.jme3.math Triangle ColorRGBA))112 (:import com.jme3.bullet.BulletAppState)113 (:import com.jme3.material.Material)114 (:import com.jme3.scene.Geometry)115 (:import java.awt.image.BufferedImage)116 (:import javax.swing.JPanel)117 (:import javax.swing.JFrame)118 (:import ij.ImagePlus)119 (:import javax.swing.SwingUtilities)120 (:import com.jme3.scene.plugins.blender.BlenderModelLoader)121 (:import (java.util.logging Level Logger)))123 (def println-repl124 "println called from the LWJGL thread will not go to the REPL, but125 instead to whatever terminal started the JVM process. This function126 will always output to the REPL"127 (bound-fn [& args] (apply println args)))129 (defn position-camera130 "Change the position of the in-world camera."131 ([world #^Vector3f position #^Quaternion rotation]132 (doto (.getCamera world)133 (.setLocation position)134 (.setRotation rotation)))135 ([world [position rotation]]136 (position-camera world position rotation)))139 (defn enable-debug140 "Turn on debug wireframes for every object in this simulation."141 [world]142 (.enableDebug143 (.getPhysicsSpace144 (.getState145 (.getStateManager world)146 BulletAppState))147 (asset-manager)))149 (defn speed-up150 "Increase the dismally slow speed of the world's camera."151 [world]152 (.setMoveSpeed (.getFlyByCamera world)153 (float 60))154 (.setRotationSpeed (.getFlyByCamera world)155 (float 3))156 world)159 (defn no-logging160 "Disable all of jMonkeyEngine's logging."161 []162 (.setLevel (Logger/getLogger "com.jme3") Level/OFF))164 (defn set-accuracy165 "Change the accuracy at which the World's Physics is calculated."166 [world new-accuracy]167 (let [physics-manager168 (.getState169 (.getStateManager world) BulletAppState)]170 (.setAccuracy171 (.getPhysicsSpace physics-manager)172 (float new-accuracy))))175 (defn set-gravity176 "In order to change the gravity of a scene, it is not only necessary177 to set the gravity variable, but to \"tap\" every physics object in178 the scene to reactivate physics calculations."179 [world gravity]180 (traverse181 (fn [geom]182 (if-let183 ;; only set gravity for physical objects.184 [control (.getControl geom RigidBodyControl)]185 (do186 (.setGravity control gravity)187 ;; tappsies!188 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO))))189 (.getRootNode world)))191 (defn add-element192 "Add the Spatial to the world's environment"193 ([world element node]194 (.addAll195 (.getPhysicsSpace196 (.getState197 (.getStateManager world)198 BulletAppState))199 element)200 (.attachChild node element))201 ([world element]202 (add-element world element (.getRootNode world))))204 (defn apply-map205 "Like apply, but works for maps and functions that expect an206 implicit map and nothing else as in (fn [& {}]).207 ------- Example -------208 (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}]209 (println www))210 (apply-map demo {:www \"hello!\"})211 -->\"hello\""212 [fn m]213 (apply fn (reduce #(into %1 %2) [] m)))215 (defn map-vals216 "Transform a map by applying a function to its values,217 keeping the keys the same."218 [f m] (zipmap (keys m) (map f (vals m))))220 (defn runonce221 "Decorator. returns a function which will run only once.222 Inspired by Halloway's version from Lancet."223 {:author "Robert McIntyre"}224 [function]225 (let [sentinel (Object.)226 result (atom sentinel)]227 (fn [& args]228 (locking sentinel229 (if (= @result sentinel)230 (reset! result (apply function args))231 @result)))))234 #+end_src236 #+results: util237 : #'cortex.util/runonce240 *** Creating Basic Shapes242 #+name: shapes243 #+begin_src clojure :results silent244 (in-ns 'cortex.util)246 (defn load-bullet247 "Running this function unpacks the native bullet libraries and makes248 them available."249 []250 (let [sim (world (Node.) {} no-op no-op)]251 (doto sim252 (.enqueue253 (fn []254 (.stop sim)))255 (.start))))258 (defrecord shape-description259 [name260 color261 mass262 friction263 texture264 material265 position266 rotation267 shape268 physical?269 GImpact?270 ])272 (def base-shape273 "Basic settings for shapes."274 (shape-description.275 "default-shape"276 false277 ;;ColorRGBA/Blue278 1.0 ;; mass279 1.0 ;; friction280 ;; texture281 "Textures/Terrain/BrickWall/BrickWall.jpg"282 ;; material283 "Common/MatDefs/Misc/Unshaded.j3md"284 Vector3f/ZERO285 Quaternion/IDENTITY286 (Box. Vector3f/ZERO 0.5 0.5 0.5)287 true288 false))290 (defn make-shape291 [#^shape-description d]292 (let [asset-manager (asset-manager)293 mat (Material. asset-manager (:material d))294 geom (Geometry. (:name d) (:shape d))]295 (if (:texture d)296 (let [key (TextureKey. (:texture d))]297 (.setGenerateMips key true)298 (.setTexture mat "ColorMap" (.loadTexture asset-manager key))299 ))300 (if (:color d) (.setColor mat "Color" (:color d)))301 (.setMaterial geom mat)302 (if-let [rotation (:rotation d)] (.rotate geom rotation))303 (.setLocalTranslation geom (:position d))304 (if (:physical? d)305 (let [physics-control306 (if (:GImpact d)307 ;; Create an accurate mesh collision shape if desired.308 (RigidBodyControl.309 (doto (GImpactCollisionShape.310 (.getMesh geom))311 (.createJmeMesh)312 ;;(.setMargin 0)313 )314 (float (:mass d)))315 ;; otherwise use jme3's default316 (RigidBodyControl. (float (:mass d))))]317 (.addControl geom physics-control)318 ;;(.setSleepingThresholds physics-control (float 0) (float 0))319 (.setFriction physics-control (:friction d))))320 geom))322 (defn box323 ([l w h & {:as options}]324 (let [options (merge base-shape options)]325 (make-shape (assoc options326 :shape (Box. l w h)))))327 ([] (box 0.5 0.5 0.5)))329 (defn sphere330 ([r & {:as options}]331 (let [options (merge base-shape options)]332 (make-shape (assoc options333 :shape (Sphere. 32 32 (float r))))))334 ([] (sphere 0.5)))336 (defn x-ray337 "A useful material for debugging -- it can be seen no matter what338 object occludes it."339 [#^ColorRGBA color]340 (doto (Material. (asset-manager)341 "Common/MatDefs/Misc/Unshaded.j3md")342 (.setColor "Color" color)343 (-> (.getAdditionalRenderState)344 (.setDepthTest false))))346 (defn node-seq347 "Take a node and return a seq of all its children348 recursively. There will be no nodes left in the resulting349 structure"350 [#^Node node]351 (tree-seq #(isa? (class %) Node) #(.getChildren %) node))353 (defn nodify354 "Take a sequence of things that can be attached to a node and return355 a node with all of them attached"356 ([name children]357 (let [node (Node. name)]358 (dorun (map #(.attachChild node %) children))359 node))360 ([children] (nodify "" children)))362 (defn load-blender-model363 "Load a .blend file using an asset folder relative path."364 [^String model]365 (.loadModel366 (doto (asset-manager)367 (.registerLoader BlenderModelLoader368 (into-array String ["blend"]))) model))371 #+end_src374 *** Debug Actions375 #+name: debug-actions376 #+begin_src clojure :results silent377 (in-ns 'cortex.util)379 (defn basic-light-setup380 "returns a sequence of lights appropriate for fully lighting a scene"381 []382 (conj383 (doall384 (map385 (fn [direction]386 (doto (DirectionalLight.)387 (.setDirection direction)388 (.setColor ColorRGBA/White)))389 [;; six faces of a cube390 Vector3f/UNIT_X391 Vector3f/UNIT_Y392 Vector3f/UNIT_Z393 (.mult Vector3f/UNIT_X (float -1))394 (.mult Vector3f/UNIT_Y (float -1))395 (.mult Vector3f/UNIT_Z (float -1))]))396 (doto (AmbientLight.)397 (.setColor ColorRGBA/White))))399 (defn light-up-everything400 "Add lights to a world appropriate for quickly seeing everything401 in the scene. Adds six DirectionalLights facing in orthogonal402 directions, and one AmbientLight to provide overall lighting403 coverage."404 [world]405 (dorun406 (map407 #(.addLight (.getRootNode world) %)408 (basic-light-setup))))410 (defn fire-cannon-ball411 "Creates a function that fires a cannon-ball from the current game's412 camera. The cannon-ball will be attached to the node if provided, or413 to the game's RootNode if no node is provided."414 ([node]415 (fn [game value]416 (if (not value)417 (let [camera (.getCamera game)418 cannon-ball419 (sphere 0.4420 ;;:texture nil421 :material "Common/MatDefs/Misc/Unshaded.j3md"422 :color ColorRGBA/Blue423 :name "cannonball!"424 :position425 (.add (.getLocation camera)426 (.mult (.getDirection camera) (float 1)))427 :mass 25)] ;200 0.05428 (.setLinearVelocity429 (.getControl cannon-ball RigidBodyControl)430 (.mult (.getDirection camera) (float 50))) ;50431 (add-element game cannon-ball (if node node (.getRootNode432 game)))433 cannon-ball))))434 ([]435 (fire-cannon-ball false)))437 (def standard-debug-controls438 {"key-space" (fire-cannon-ball)})441 (defn tap [obj direction force]442 (let [control (.getControl obj RigidBodyControl)]443 (.applyTorque444 control445 (.mult (.getPhysicsRotation control)446 (.mult (.normalize direction) (float force))))))449 (defn with-movement450 [object451 [up down left right roll-up roll-down :as keyboard]452 forces453 [root-node454 keymap455 initialization456 world-loop]]457 (let [add-keypress458 (fn [state keymap key]459 (merge keymap460 {key461 (fn [_ pressed?]462 (reset! state pressed?))}))463 move-up? (atom false)464 move-down? (atom false)465 move-left? (atom false)466 move-right? (atom false)467 roll-left? (atom false)468 roll-right? (atom false)470 directions [(Vector3f. 0 1 0)(Vector3f. 0 -1 0)471 (Vector3f. 0 0 1)(Vector3f. 0 0 -1)472 (Vector3f. -1 0 0)(Vector3f. 1 0 0)]473 atoms [move-left? move-right? move-up? move-down?474 roll-left? roll-right?]476 keymap* (reduce merge477 (map #(add-keypress %1 keymap %2)478 atoms479 keyboard))481 splice-loop (fn []482 (dorun483 (map484 (fn [sym direction force]485 (if @sym486 (tap object direction force)))487 atoms directions forces)))489 world-loop* (fn [world tpf]490 (world-loop world tpf)491 (splice-loop))]492 [root-node493 keymap*494 initialization495 world-loop*]))497 (import com.jme3.font.BitmapText)498 (import com.jme3.scene.control.AbstractControl)499 (import com.aurellem.capture.IsoTimer)501 (defn display-dilated-time502 "Shows the time as it is flowing in the simulation on a HUD display.503 Useful for making videos."504 [world timer]505 (let [font (.loadFont (asset-manager) "Interface/Fonts/Default.fnt")506 text (BitmapText. font false)]507 (.setLocalTranslation text 300 (.getLineHeight text) 0)508 (.addControl509 text510 (proxy [AbstractControl] []511 (controlUpdate [tpf]512 (.setText text (format513 "%.2f"514 (float (.getTimeInSeconds timer)))))515 (controlRender [_ _])))516 (.attachChild (.getGuiNode world) text)))517 #+end_src520 *** Viewing Objects522 #+name: world-view523 #+begin_src clojure :results silent524 (in-ns 'cortex.util)526 (defprotocol Viewable527 (view [something]))529 (extend-type com.jme3.scene.Geometry530 Viewable531 (view [geo]532 (view (doto (Node.)(.attachChild geo)))))534 (extend-type com.jme3.scene.Node535 Viewable536 (view537 [node]538 (.start539 (world540 node541 {}542 (fn [world]543 (enable-debug world)544 (set-gravity world Vector3f/ZERO)545 (light-up-everything world))546 no-op))))548 (extend-type com.jme3.math.ColorRGBA549 Viewable550 (view551 [color]552 (view (doto (Node.)553 (.attachChild (box 1 1 1 :color color))))))555 (extend-type ij.ImagePlus556 Viewable557 (view [image]558 (.show image)))560 (extend-type java.awt.image.BufferedImage561 Viewable562 (view563 [image]564 (view (ImagePlus. "view-buffered-image" image))))567 (defprotocol Textual568 (text [something]569 "Display a detailed textual analysis of the given object."))571 (extend-type com.jme3.scene.Node572 Textual573 (text [node]574 (println "Total Vertexes: " (.getVertexCount node))575 (println "Total Triangles: " (.getTriangleCount node))576 (println "Controls :")577 (dorun (map #(text (.getControl node %)) (range (.getNumControls node))))578 (println "Has " (.getQuantity node) " Children:")579 (doall (map text (.getChildren node)))))581 (extend-type com.jme3.animation.AnimControl582 Textual583 (text [control]584 (let [animations (.getAnimationNames control)]585 (println "Animation Control with " (count animations) " animation(s):")586 (dorun (map println animations)))))588 (extend-type com.jme3.animation.SkeletonControl589 Textual590 (text [control]591 (println "Skeleton Control with the following skeleton:")592 (println (.getSkeleton control))))594 (extend-type com.jme3.bullet.control.KinematicRagdollControl595 Textual596 (text [control]597 (println "Ragdoll Control")))599 (extend-type com.jme3.scene.Geometry600 Textual601 (text [control]602 (println "...geo...")))604 (extend-type Triangle605 Textual606 (text [t]607 (println "Triangle: " \newline (.get1 t) \newline608 (.get2 t) \newline (.get3 t))))610 #+end_src612 Here I make the =Viewable= protocol and extend it to JME's types. Now613 JME3's =hello-world= can be written as easily as:615 #+begin_src clojure :results silent616 (cortex.util/view (cortex.util/box))617 #+end_src620 * code generation621 #+begin_src clojure :tangle ../src/cortex/import.clj622 <<import>>623 #+end_src626 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes627 <<util>>628 <<shapes>>629 <<debug-actions>>630 <<world-view>>631 #+end_src