Mercurial > cortex
view org/util.org @ 380:2d0afb231081
spellcheck
author | rlm |
---|---|
date | Wed, 10 Apr 2013 16:49:05 -0400 |
parents | 9fa92af29c3a |
children | 4c37d39a3cf6 |
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 (:require swank.util.class-browse))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-classes36 "returns a list of all jme3 classes"37 []38 (filter39 jme-class?40 (map :name41 swank.util.class-browse/available-classes)))43 (defn mega-import-jme344 "Import ALL the jme classes. For REPL use."45 []46 (doall47 (map (comp permissive-import symbol) (jme-classes))))48 #+end_src50 jMonkeyEngine3 has a plethora of classes which can be overwhelming to51 manage. This code uses reflection to import all of them. Once I'm52 happy with the general structure of a namespace I can deal with53 importing only the classes it actually needs.55 The =mega-import-jme3= is quite useful for debugging purposes since56 it allows completion for almost all of JME's classes from the REPL.58 Out of curiosity, let's see just how many classes =mega-import-jme3=59 imports:61 #+begin_src clojure :exports both :results output62 (println (clojure.core/count (cortex.import/jme-classes)) "classes")63 #+end_src65 #+results:66 : 955 classes69 * Utilities71 The utilities here come in three main groups:72 - Changing settings in a running =Application=73 - Creating objects74 - Debug Actions75 - Visualizing objects77 *** Changing Settings79 #+name: util80 #+begin_src clojure81 (ns cortex.util82 "Utility functions for making jMonkeyEngine3 easier to program from83 clojure."84 {:author "Robert McIntyre"}85 (:use cortex.world)86 (:import com.jme3.math.Vector3f)87 (:import com.jme3.math.Quaternion)88 (:import com.jme3.asset.TextureKey)89 (:import com.jme3.bullet.control.RigidBodyControl)90 (:import com.jme3.bullet.collision.shapes.GImpactCollisionShape)91 (:import com.jme3.scene.shape.Box)92 (:import com.jme3.scene.Node)93 (:import com.jme3.scene.shape.Sphere)94 (:import com.jme3.light.AmbientLight)95 (:import com.jme3.light.DirectionalLight)96 (:import (com.jme3.math Triangle ColorRGBA))97 (:import com.jme3.bullet.BulletAppState)98 (:import com.jme3.material.Material)99 (:import com.jme3.scene.Geometry)100 (:import java.awt.image.BufferedImage)101 (:import javax.swing.JPanel)102 (:import javax.swing.JFrame)103 (:import ij.ImagePlus)104 (:import javax.swing.SwingUtilities)105 (:import com.jme3.scene.plugins.blender.BlenderModelLoader)106 (:import (java.util.logging Level Logger)))108 (def println-repl109 "println called from the LWJGL thread will not go to the REPL, but110 instead to whatever terminal started the JVM process. This function111 will always output to the REPL"112 (bound-fn [& args] (apply println args)))114 (defn position-camera115 "Change the position of the in-world camera."116 [world #^Vector3f position #^Quaternion rotation]117 (doto (.getCamera world)118 (.setLocation position)119 (.setRotation rotation)))121 (defn enable-debug122 "Turn on debug wireframes for every object in this simulation."123 [world]124 (.enableDebug125 (.getPhysicsSpace126 (.getState127 (.getStateManager world)128 BulletAppState))129 (asset-manager)))131 (defn speed-up132 "Increase the dismally slow speed of the world's camera."133 [world]134 (.setMoveSpeed (.getFlyByCamera world)135 (float 60))136 (.setRotationSpeed (.getFlyByCamera world)137 (float 3))138 world)141 (defn no-logging142 "Disable all of jMonkeyEngine's logging."143 []144 (.setLevel (Logger/getLogger "com.jme3") Level/OFF))146 (defn set-accuracy147 "Change the accuracy at which the World's Physics is calculated."148 [world new-accuracy]149 (let [physics-manager150 (.getState151 (.getStateManager world) BulletAppState)]152 (.setAccuracy153 (.getPhysicsSpace physics-manager)154 (float new-accuracy))))157 (defn set-gravity158 "In order to change the gravity of a scene, it is not only necessary159 to set the gravity variable, but to \"tap\" every physics object in160 the scene to reactivate physics calculations."161 [world gravity]162 (traverse163 (fn [geom]164 (if-let165 ;; only set gravity for physical objects.166 [control (.getControl geom RigidBodyControl)]167 (do168 (.setGravity control gravity)169 ;; tappsies!170 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO))))171 (.getRootNode world)))173 (defn add-element174 "Add the Spatial to the world's environment"175 ([world element node]176 (.addAll177 (.getPhysicsSpace178 (.getState179 (.getStateManager world)180 BulletAppState))181 element)182 (.attachChild node element))183 ([world element]184 (add-element world element (.getRootNode world))))186 (defn apply-map187 "Like apply, but works for maps and functions that expect an188 implicit map and nothing else as in (fn [& {}]).189 ------- Example -------190 (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}]191 (println www))192 (apply-map demo {:www \"hello!\"})193 -->\"hello\""194 [fn m]195 (apply fn (reduce #(into %1 %2) [] m)))197 (defn map-vals198 "Transform a map by applying a function to its values,199 keeping the keys the same."200 [f m] (zipmap (keys m) (map f (vals m))))202 (defn runonce203 "Decorator. returns a function which will run only once.204 Inspired by Halloway's version from Lancet."205 {:author "Robert McIntyre"}206 [function]207 (let [sentinel (Object.)208 result (atom sentinel)]209 (fn [& args]210 (locking sentinel211 (if (= @result sentinel)212 (reset! result (apply function args))213 @result)))))216 #+end_src218 #+results: util219 : #'cortex.util/runonce222 *** Creating Basic Shapes224 #+name: shapes225 #+begin_src clojure :results silent226 (in-ns 'cortex.util)228 (defn load-bullet229 "Running this function unpacks the native bullet libraries and makes230 them available."231 []232 (let [sim (world (Node.) {} no-op no-op)]233 (doto sim234 (.enqueue235 (fn []236 (.stop sim)))237 (.start))))240 (defrecord shape-description241 [name242 color243 mass244 friction245 texture246 material247 position248 rotation249 shape250 physical?251 GImpact?252 ])254 (def base-shape255 "Basic settings for shapes."256 (shape-description.257 "default-shape"258 false259 ;;ColorRGBA/Blue260 1.0 ;; mass261 1.0 ;; friction262 ;; texture263 "Textures/Terrain/BrickWall/BrickWall.jpg"264 ;; material265 "Common/MatDefs/Misc/Unshaded.j3md"266 Vector3f/ZERO267 Quaternion/IDENTITY268 (Box. Vector3f/ZERO 0.5 0.5 0.5)269 true270 false))272 (defn make-shape273 [#^shape-description d]274 (let [asset-manager (asset-manager)275 mat (Material. asset-manager (:material d))276 geom (Geometry. (:name d) (:shape d))]277 (if (:texture d)278 (let [key (TextureKey. (:texture d))]279 (.setGenerateMips key true)280 (.setTexture mat "ColorMap" (.loadTexture asset-manager key))281 ))282 (if (:color d) (.setColor mat "Color" (:color d)))283 (.setMaterial geom mat)284 (if-let [rotation (:rotation d)] (.rotate geom rotation))285 (.setLocalTranslation geom (:position d))286 (if (:physical? d)287 (let [physics-control288 (if (:GImpact d)289 ;; Create an accurate mesh collision shape if desired.290 (RigidBodyControl.291 (doto (GImpactCollisionShape.292 (.getMesh geom))293 (.createJmeMesh)294 ;;(.setMargin 0)295 )296 (float (:mass d)))297 ;; otherwise use jme3's default298 (RigidBodyControl. (float (:mass d))))]299 (.addControl geom physics-control)300 ;;(.setSleepingThresholds physics-control (float 0) (float 0))301 (.setFriction physics-control (:friction d))))302 geom))304 (defn box305 ([l w h & {:as options}]306 (let [options (merge base-shape options)]307 (make-shape (assoc options308 :shape (Box. l w h)))))309 ([] (box 0.5 0.5 0.5)))311 (defn sphere312 ([r & {:as options}]313 (let [options (merge base-shape options)]314 (make-shape (assoc options315 :shape (Sphere. 32 32 (float r))))))316 ([] (sphere 0.5)))318 (defn x-ray319 "A useful material for debugging -- it can be seen no matter what320 object occludes it."321 [#^ColorRGBA color]322 (doto (Material. (asset-manager)323 "Common/MatDefs/Misc/Unshaded.j3md")324 (.setColor "Color" color)325 (-> (.getAdditionalRenderState)326 (.setDepthTest false))))328 (defn node-seq329 "Take a node and return a seq of all its children330 recursively. There will be no nodes left in the resulting331 structure"332 [#^Node node]333 (tree-seq #(isa? (class %) Node) #(.getChildren %) node))335 (defn nodify336 "Take a sequence of things that can be attached to a node and return337 a node with all of them attached"338 ([name children]339 (let [node (Node. name)]340 (dorun (map #(.attachChild node %) children))341 node))342 ([children] (nodify "" children)))344 (defn load-blender-model345 "Load a .blend file using an asset folder relative path."346 [^String model]347 (.loadModel348 (doto (asset-manager)349 (.registerLoader BlenderModelLoader350 (into-array String ["blend"]))) model))353 #+end_src356 *** Debug Actions357 #+name: debug-actions358 #+begin_src clojure :results silent359 (in-ns 'cortex.util)361 (defn basic-light-setup362 "returns a sequence of lights appropriate for fully lighting a scene"363 []364 (conj365 (doall366 (map367 (fn [direction]368 (doto (DirectionalLight.)369 (.setDirection direction)370 (.setColor ColorRGBA/White)))371 [;; six faces of a cube372 Vector3f/UNIT_X373 Vector3f/UNIT_Y374 Vector3f/UNIT_Z375 (.mult Vector3f/UNIT_X (float -1))376 (.mult Vector3f/UNIT_Y (float -1))377 (.mult Vector3f/UNIT_Z (float -1))]))378 (doto (AmbientLight.)379 (.setColor ColorRGBA/White))))381 (defn light-up-everything382 "Add lights to a world appropriate for quickly seeing everything383 in the scene. Adds six DirectionalLights facing in orthogonal384 directions, and one AmbientLight to provide overall lighting385 coverage."386 [world]387 (dorun388 (map389 #(.addLight (.getRootNode world) %)390 (basic-light-setup))))392 (defn fire-cannon-ball393 "Creates a function that fires a cannon-ball from the current game's394 camera. The cannon-ball will be attached to the node if provided, or395 to the game's RootNode if no node is provided."396 ([node]397 (fn [game value]398 (if (not value)399 (let [camera (.getCamera game)400 cannon-ball401 (sphere 0.4402 ;;:texture nil403 :material "Common/MatDefs/Misc/Unshaded.j3md"404 :color ColorRGBA/Blue405 :name "cannonball!"406 :position407 (.add (.getLocation camera)408 (.mult (.getDirection camera) (float 1)))409 :mass 25)] ;200 0.05410 (.setLinearVelocity411 (.getControl cannon-ball RigidBodyControl)412 (.mult (.getDirection camera) (float 50))) ;50413 (add-element game cannon-ball (if node node (.getRootNode414 game)))415 cannon-ball))))416 ([]417 (fire-cannon-ball false)))419 (def standard-debug-controls420 {"key-space" (fire-cannon-ball)})423 (defn tap [obj direction force]424 (let [control (.getControl obj RigidBodyControl)]425 (.applyTorque426 control427 (.mult (.getPhysicsRotation control)428 (.mult (.normalize direction) (float force))))))431 (defn with-movement432 [object433 [up down left right roll-up roll-down :as keyboard]434 forces435 [root-node436 keymap437 initialization438 world-loop]]439 (let [add-keypress440 (fn [state keymap key]441 (merge keymap442 {key443 (fn [_ pressed?]444 (reset! state pressed?))}))445 move-up? (atom false)446 move-down? (atom false)447 move-left? (atom false)448 move-right? (atom false)449 roll-left? (atom false)450 roll-right? (atom false)452 directions [(Vector3f. 0 1 0)(Vector3f. 0 -1 0)453 (Vector3f. 0 0 1)(Vector3f. 0 0 -1)454 (Vector3f. -1 0 0)(Vector3f. 1 0 0)]455 atoms [move-left? move-right? move-up? move-down?456 roll-left? roll-right?]458 keymap* (reduce merge459 (map #(add-keypress %1 keymap %2)460 atoms461 keyboard))463 splice-loop (fn []464 (dorun465 (map466 (fn [sym direction force]467 (if @sym468 (tap object direction force)))469 atoms directions forces)))471 world-loop* (fn [world tpf]472 (world-loop world tpf)473 (splice-loop))]474 [root-node475 keymap*476 initialization477 world-loop*]))479 (import com.jme3.font.BitmapText)480 (import com.jme3.scene.control.AbstractControl)481 (import com.aurellem.capture.IsoTimer)483 (defn display-dilated-time484 "Shows the time as it is flowing in the simulation on a HUD display.485 Useful for making videos."486 [world timer]487 (let [font (.loadFont (asset-manager) "Interface/Fonts/Default.fnt")488 text (BitmapText. font false)]489 (.setLocalTranslation text 300 (.getLineHeight text) 0)490 (.addControl491 text492 (proxy [AbstractControl] []493 (controlUpdate [tpf]494 (.setText text (format495 "%.2f"496 (float (.getTimeInSeconds timer)))))497 (controlRender [_ _])))498 (.attachChild (.getGuiNode world) text)))499 #+end_src502 *** Viewing Objects504 #+name: world-view505 #+begin_src clojure :results silent506 (in-ns 'cortex.util)508 (defprotocol Viewable509 (view [something]))511 (extend-type com.jme3.scene.Geometry512 Viewable513 (view [geo]514 (view (doto (Node.)(.attachChild geo)))))516 (extend-type com.jme3.scene.Node517 Viewable518 (view519 [node]520 (.start521 (world522 node523 {}524 (fn [world]525 (enable-debug world)526 (set-gravity world Vector3f/ZERO)527 (light-up-everything world))528 no-op))))530 (extend-type com.jme3.math.ColorRGBA531 Viewable532 (view533 [color]534 (view (doto (Node.)535 (.attachChild (box 1 1 1 :color color))))))537 (extend-type ij.ImagePlus538 Viewable539 (view [image]540 (.show image)))542 (extend-type java.awt.image.BufferedImage543 Viewable544 (view545 [image]546 (view (ImagePlus. "view-buffered-image" image))))549 (defprotocol Textual550 (text [something]551 "Display a detailed textual analysis of the given object."))553 (extend-type com.jme3.scene.Node554 Textual555 (text [node]556 (println "Total Vertexes: " (.getVertexCount node))557 (println "Total Triangles: " (.getTriangleCount node))558 (println "Controls :")559 (dorun (map #(text (.getControl node %)) (range (.getNumControls node))))560 (println "Has " (.getQuantity node) " Children:")561 (doall (map text (.getChildren node)))))563 (extend-type com.jme3.animation.AnimControl564 Textual565 (text [control]566 (let [animations (.getAnimationNames control)]567 (println "Animation Control with " (count animations) " animation(s):")568 (dorun (map println animations)))))570 (extend-type com.jme3.animation.SkeletonControl571 Textual572 (text [control]573 (println "Skeleton Control with the following skeleton:")574 (println (.getSkeleton control))))576 (extend-type com.jme3.bullet.control.KinematicRagdollControl577 Textual578 (text [control]579 (println "Ragdoll Control")))581 (extend-type com.jme3.scene.Geometry582 Textual583 (text [control]584 (println "...geo...")))586 (extend-type Triangle587 Textual588 (text [t]589 (println "Triangle: " \newline (.get1 t) \newline590 (.get2 t) \newline (.get3 t))))592 #+end_src594 Here I make the =Viewable= protocol and extend it to JME's types. Now595 JME3's =hello-world= can be written as easily as:597 #+begin_src clojure :results silent598 (cortex.util/view (cortex.util/box))599 #+end_src602 * COMMENT code generation603 #+begin_src clojure :tangle ../src/cortex/import.clj604 <<import>>605 #+end_src608 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes609 <<util>>610 <<shapes>>611 <<debug-actions>>612 <<world-view>>613 #+end_src