Mercurial > cortex
view org/util.org @ 473:486ce07f5545
s.
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Fri, 28 Mar 2014 20:49:13 -0400 |
parents | 763d13f77e03 |
children | 01934317b25b |
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 (import com.aurellem.capture.IsoTimer)63 (map (comp permissive-import symbol) (jme-class-names))))64 #+end_src66 jMonkeyEngine3 has a plethora of classes which can be overwhelming to67 manage. This code uses reflection to import all of them. Once I'm68 happy with the general structure of a namespace I can deal with69 importing only the classes it actually needs.71 The =mega-import-jme3= is quite useful for debugging purposes since72 it allows completion for almost all of JME's classes from the REPL.74 Out of curiosity, let's see just how many classes =mega-import-jme3=75 imports:77 #+begin_src clojure :exports both :results output78 (println (clojure.core/count (cortex.import/jme-class-names)) "classes")79 #+end_src81 #+results:82 : 938 classes85 * Utilities87 The utilities here come in three main groups:88 - Changing settings in a running =Application=89 - Creating objects90 - Debug Actions91 - Visualizing objects93 *** Changing Settings95 #+name: util96 #+begin_src clojure97 (ns cortex.util98 "Utility functions for making jMonkeyEngine3 easier to program from99 clojure."100 {:author "Robert McIntyre"}101 (:use cortex.world)102 (:import com.jme3.math.Vector3f)103 (:import com.jme3.math.Quaternion)104 (:import com.jme3.asset.TextureKey)105 (:import com.jme3.bullet.control.RigidBodyControl)106 (:import com.jme3.bullet.collision.shapes.GImpactCollisionShape)107 (:import com.jme3.scene.shape.Box)108 (:import com.jme3.scene.Node)109 (:import com.jme3.scene.shape.Sphere)110 (:import com.jme3.light.AmbientLight)111 (:import com.jme3.light.DirectionalLight)112 (:import (com.jme3.math Triangle ColorRGBA))113 (:import com.jme3.bullet.BulletAppState)114 (:import com.jme3.material.Material)115 (:import com.jme3.scene.Geometry)116 (:import java.awt.image.BufferedImage)117 (:import javax.swing.JPanel)118 (:import javax.swing.JFrame)119 (:import ij.ImagePlus)120 (:import javax.swing.SwingUtilities)121 (:import com.jme3.scene.plugins.blender.BlenderModelLoader)122 (:import (java.util.logging Level Logger)))124 (def println-repl125 "println called from the LWJGL thread will not go to the REPL, but126 instead to whatever terminal started the JVM process. This function127 will always output to the REPL"128 (bound-fn [& args] (apply println args)))130 (defn position-camera131 "Change the position of the in-world camera."132 ([world #^Vector3f position #^Quaternion rotation]133 (doto (.getCamera world)134 (.setLocation position)135 (.setRotation rotation)))136 ([world [position rotation]]137 (position-camera world position rotation)))140 (defn enable-debug141 "Turn on debug wireframes for every object in this simulation."142 [world]143 (.enableDebug144 (.getPhysicsSpace145 (.getState146 (.getStateManager world)147 BulletAppState))148 (asset-manager)))150 (defn speed-up151 "Increase the dismally slow speed of the world's camera."152 ([world] (speed-up world 1))153 ([world amount]154 (.setMoveSpeed (.getFlyByCamera world)155 (float (* amount 60)))156 (.setRotationSpeed (.getFlyByCamera world)157 (float (* amount 3)))158 world))160 (defn no-logging161 "Disable all of jMonkeyEngine's logging."162 []163 (.setLevel (Logger/getLogger "com.jme3") Level/OFF))165 (defn set-accuracy166 "Change the accuracy at which the World's Physics is calculated."167 [world new-accuracy]168 (let [physics-manager169 (.getState170 (.getStateManager world) BulletAppState)]171 (.setAccuracy172 (.getPhysicsSpace physics-manager)173 (float new-accuracy))))176 (defn set-gravity177 "In order to change the gravity of a scene, it is not only necessary178 to set the gravity variable, but to \"tap\" every physics object in179 the scene to reactivate physics calculations."180 [world gravity]181 (traverse182 (fn [geom]183 (if-let184 ;; only set gravity for physical objects.185 [control (.getControl geom RigidBodyControl)]186 (do187 (.setGravity control gravity)188 ;; tappsies!189 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO))))190 (.getRootNode world)))192 (defn add-element193 "Add the Spatial to the world's environment"194 ([world element node]195 (.addAll196 (.getPhysicsSpace197 (.getState198 (.getStateManager world)199 BulletAppState))200 element)201 (.attachChild node element))202 ([world element]203 (add-element world element (.getRootNode world))))205 (defn apply-map206 "Like apply, but works for maps and functions that expect an207 implicit map and nothing else as in (fn [& {}]).208 ------- Example -------209 (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}]210 (println www))211 (apply-map demo {:www \"hello!\"})212 -->\"hello\""213 [fn m]214 (apply fn (reduce #(into %1 %2) [] m)))216 (defn map-vals217 "Transform a map by applying a function to its values,218 keeping the keys the same."219 [f m] (zipmap (keys m) (map f (vals m))))221 (defn runonce222 "Decorator. returns a function which will run only once.223 Inspired by Halloway's version from Lancet."224 {:author "Robert McIntyre"}225 [function]226 (let [sentinel (Object.)227 result (atom sentinel)]228 (fn [& args]229 (locking sentinel230 (if (= @result sentinel)231 (reset! result (apply function args))232 @result)))))235 #+end_src237 #+results: util238 : #'cortex.util/runonce241 *** Creating Basic Shapes243 #+name: shapes244 #+begin_src clojure :results silent245 (in-ns 'cortex.util)247 (defn load-bullet248 "Running this function unpacks the native bullet libraries and makes249 them available."250 []251 (let [sim (world (Node.) {} no-op no-op)]252 (doto sim253 (.enqueue254 (fn []255 (.stop sim)))256 (.start))))259 (defrecord shape-description260 [name261 color262 mass263 friction264 texture265 material266 position267 rotation268 shape269 physical?270 GImpact?271 ])273 (def base-shape274 "Basic settings for shapes."275 (shape-description.276 "default-shape"277 false278 ;;ColorRGBA/Blue279 1.0 ;; mass280 1.0 ;; friction281 ;; texture282 "Textures/Terrain/BrickWall/BrickWall.jpg"283 ;; material284 "Common/MatDefs/Misc/Unshaded.j3md"285 Vector3f/ZERO286 Quaternion/IDENTITY287 (Box. Vector3f/ZERO 0.5 0.5 0.5)288 true289 false))291 (defn make-shape292 [#^shape-description d]293 (let [asset-manager (asset-manager)294 mat (Material. asset-manager (:material d))295 geom (Geometry. (:name d) (:shape d))]296 (if (:texture d)297 (let [key (TextureKey. (:texture d))]298 (.setGenerateMips key true)299 (.setTexture mat "ColorMap" (.loadTexture asset-manager key))300 ))301 (if (:color d) (.setColor mat "Color" (:color d)))302 (.setMaterial geom mat)303 (if-let [rotation (:rotation d)] (.rotate geom rotation))304 (.setLocalTranslation geom (:position d))305 (if (:physical? d)306 (let [physics-control307 (if (:GImpact d)308 ;; Create an accurate mesh collision shape if desired.309 (RigidBodyControl.310 (doto (GImpactCollisionShape.311 (.getMesh geom))312 (.createJmeMesh)313 ;;(.setMargin 0)314 )315 (float (:mass d)))316 ;; otherwise use jme3's default317 (RigidBodyControl. (float (:mass d))))]318 (.addControl geom physics-control)319 ;;(.setSleepingThresholds physics-control (float 0) (float 0))320 (.setFriction physics-control (:friction d))))321 geom))323 (defn box324 ([l w h & {:as options}]325 (let [options (merge base-shape options)]326 (make-shape (assoc options327 :shape (Box. l w h)))))328 ([] (box 0.5 0.5 0.5)))330 (defn sphere331 ([r & {:as options}]332 (let [options (merge base-shape options)]333 (make-shape (assoc options334 :shape (Sphere. 32 32 (float r))))))335 ([] (sphere 0.5)))337 (defn x-ray338 "A useful material for debugging -- it can be seen no matter what339 object occludes it."340 [#^ColorRGBA color]341 (doto (Material. (asset-manager)342 "Common/MatDefs/Misc/Unshaded.j3md")343 (.setColor "Color" color)344 (-> (.getAdditionalRenderState)345 (.setDepthTest false))))347 (defn node-seq348 "Take a node and return a seq of all its children349 recursively. There will be no nodes left in the resulting350 structure"351 [#^Node node]352 (tree-seq #(isa? (class %) Node) #(.getChildren %) node))354 (defn nodify355 "Take a sequence of things that can be attached to a node and return356 a node with all of them attached"357 ([name children]358 (let [node (Node. name)]359 (dorun (map #(.attachChild node %) children))360 node))361 ([children] (nodify "" children)))363 (defn load-blender-model364 "Load a .blend file using an asset folder relative path."365 [^String model]366 (.loadModel367 (doto (asset-manager)368 (.registerLoader BlenderModelLoader369 (into-array String ["blend"]))) model))373 (def brick-length 0.48)374 (def brick-width 0.24)375 (def brick-height 0.12)376 (def gravity (Vector3f. 0 -9.81 0))378 (import com.jme3.math.Vector2f)379 (import com.jme3.renderer.queue.RenderQueue$ShadowMode)380 (import com.jme3.texture.Texture$WrapMode)382 (defn brick* [position]383 (println "get brick.")384 (doto (box brick-length brick-height brick-width385 :position position :name "brick"386 :material "Common/MatDefs/Misc/Unshaded.j3md"387 :texture "Textures/Terrain/BrickWall/BrickWall.jpg"388 :mass 34)389 (->390 (.getMesh)391 (.scaleTextureCoordinates (Vector2f. 1 0.5)))392 (.setShadowMode RenderQueue$ShadowMode/CastAndReceive)393 )394 )397 (defn floor*398 "make a sturdy, unmovable physical floor"399 []400 (box 10 0.1 5 :name "floor" :mass 0401 :color ColorRGBA/Gray :position (Vector3f. 0 0 0)))403 (defn floor* []404 (doto (box 10 0.1 5 :name "floor" ;10 0.1 5 ; 240 0.1 240405 :material "Common/MatDefs/Misc/Unshaded.j3md"406 :texture "Textures/BronzeCopper030.jpg"407 :position (Vector3f. 0 0 0 )408 :mass 0)409 (->410 (.getMesh)411 (.scaleTextureCoordinates (Vector2f. 3 6)));64 64412 (->413 (.getMaterial)414 (.getTextureParam "ColorMap")415 (.getTextureValue)416 (.setWrap Texture$WrapMode/Repeat))417 (.setShadowMode RenderQueue$ShadowMode/Receive)418 ))421 (defn brick-wall* []422 (let [node (Node. "brick-wall")]423 (dorun424 (map425 (comp #(.attachChild node %) brick*)426 (for [y (range 10)427 x (range 4)428 z (range 1)]429 (Vector3f.430 (+ (* 2 x brick-length)431 (if (even? (+ y z))432 (/ brick-length 4) (/ brick-length -4)))433 (+ (* brick-height (inc (* 2 y))))434 (* 2 z brick-width) ))))435 (.setShadowMode node RenderQueue$ShadowMode/CastAndReceive)436 node))439 #+end_src442 *** Debug Actions443 #+name: debug-actions444 #+begin_src clojure :results silent445 (in-ns 'cortex.util)447 (defn basic-light-setup448 "returns a sequence of lights appropriate for fully lighting a scene"449 []450 (conj451 (doall452 (map453 (fn [direction]454 (doto (DirectionalLight.)455 (.setDirection direction)456 (.setColor ColorRGBA/White)))457 [;; six faces of a cube458 Vector3f/UNIT_X459 Vector3f/UNIT_Y460 Vector3f/UNIT_Z461 (.mult Vector3f/UNIT_X (float -1))462 (.mult Vector3f/UNIT_Y (float -1))463 (.mult Vector3f/UNIT_Z (float -1))]))464 (doto (AmbientLight.)465 (.setColor ColorRGBA/White))))467 (defn light-up-everything468 "Add lights to a world appropriate for quickly seeing everything469 in the scene. Adds six DirectionalLights facing in orthogonal470 directions, and one AmbientLight to provide overall lighting471 coverage."472 [world]473 (dorun474 (map475 #(.addLight (.getRootNode world) %)476 (basic-light-setup))))478 (defn fire-cannon-ball479 "Creates a function that fires a cannon-ball from the current game's480 camera. The cannon-ball will be attached to the node if provided, or481 to the game's RootNode if no node is provided."482 ([node]483 (fn [game value]484 (if (not value)485 (let [camera (.getCamera game)486 cannon-ball487 (sphere 0.4488 ;;:texture nil489 :material "Common/MatDefs/Misc/Unshaded.j3md"490 :color ColorRGBA/Blue491 :name "cannonball!"492 :position493 (.add (.getLocation camera)494 (.mult (.getDirection camera) (float 1)))495 :mass 25)] ;200 0.05496 (.setLinearVelocity497 (.getControl cannon-ball RigidBodyControl)498 (.mult (.getDirection camera) (float 50))) ;50499 (add-element game cannon-ball (if node node (.getRootNode500 game)))501 cannon-ball))))502 ([]503 (fire-cannon-ball false)))505 (def standard-debug-controls506 {"key-space" (fire-cannon-ball)})509 (defn tap [obj direction force]510 (let [control (.getControl obj RigidBodyControl)]511 (.applyTorque512 control513 (.mult (.getPhysicsRotation control)514 (.mult (.normalize direction) (float force))))))517 (defn with-movement518 [object519 [up down left right roll-up roll-down :as keyboard]520 forces521 [root-node522 keymap523 initialization524 world-loop]]525 (let [add-keypress526 (fn [state keymap key]527 (merge keymap528 {key529 (fn [_ pressed?]530 (reset! state pressed?))}))531 move-up? (atom false)532 move-down? (atom false)533 move-left? (atom false)534 move-right? (atom false)535 roll-left? (atom false)536 roll-right? (atom false)538 directions [(Vector3f. 0 1 0)(Vector3f. 0 -1 0)539 (Vector3f. 0 0 1)(Vector3f. 0 0 -1)540 (Vector3f. -1 0 0)(Vector3f. 1 0 0)]541 atoms [move-left? move-right? move-up? move-down?542 roll-left? roll-right?]544 keymap* (reduce merge545 (map #(add-keypress %1 keymap %2)546 atoms547 keyboard))549 splice-loop (fn []550 (dorun551 (map552 (fn [sym direction force]553 (if @sym554 (tap object direction force)))555 atoms directions forces)))557 world-loop* (fn [world tpf]558 (world-loop world tpf)559 (splice-loop))]560 [root-node561 keymap*562 initialization563 world-loop*]))565 (import com.jme3.font.BitmapText)566 (import com.jme3.scene.control.AbstractControl)567 (import com.aurellem.capture.IsoTimer)569 (defn display-dilated-time570 "Shows the time as it is flowing in the simulation on a HUD display.571 Useful for making videos."572 [world timer]573 (let [font (.loadFont (asset-manager) "Interface/Fonts/Default.fnt")574 text (BitmapText. font false)]575 (.setLocalTranslation text 300 (.getLineHeight text) 0)576 (.addControl577 text578 (proxy [AbstractControl] []579 (controlUpdate [tpf]580 (.setText text (format581 "%.2f"582 (float (.getTimeInSeconds timer)))))583 (controlRender [_ _])))584 (.attachChild (.getGuiNode world) text)))585 #+end_src588 *** Viewing Objects590 #+name: world-view591 #+begin_src clojure :results silent592 (in-ns 'cortex.util)594 (defprotocol Viewable595 (view [something]))597 (extend-type com.jme3.scene.Geometry598 Viewable599 (view [geo]600 (view (doto (Node.)(.attachChild geo)))))602 (extend-type com.jme3.scene.Node603 Viewable604 (view605 [node]606 (.start607 (world608 node609 {}610 (fn [world]611 (enable-debug world)612 (set-gravity world Vector3f/ZERO)613 (light-up-everything world))614 no-op))))616 (extend-type com.jme3.math.ColorRGBA617 Viewable618 (view619 [color]620 (view (doto (Node.)621 (.attachChild (box 1 1 1 :color color))))))623 (extend-type ij.ImagePlus624 Viewable625 (view [image]626 (.show image)))628 (extend-type java.awt.image.BufferedImage629 Viewable630 (view631 [image]632 (view (ImagePlus. "view-buffered-image" image))))635 (defprotocol Textual636 (text [something]637 "Display a detailed textual analysis of the given object."))639 (extend-type com.jme3.scene.Node640 Textual641 (text [node]642 (println "Total Vertexes: " (.getVertexCount node))643 (println "Total Triangles: " (.getTriangleCount node))644 (println "Controls :")645 (dorun (map #(text (.getControl node %)) (range (.getNumControls node))))646 (println "Has " (.getQuantity node) " Children:")647 (doall (map text (.getChildren node)))))649 (extend-type com.jme3.animation.AnimControl650 Textual651 (text [control]652 (let [animations (.getAnimationNames control)]653 (println "Animation Control with " (count animations) " animation(s):")654 (dorun (map println animations)))))656 (extend-type com.jme3.animation.SkeletonControl657 Textual658 (text [control]659 (println "Skeleton Control with the following skeleton:")660 (println (.getSkeleton control))))662 (extend-type com.jme3.bullet.control.KinematicRagdollControl663 Textual664 (text [control]665 (println "Ragdoll Control")))667 (extend-type com.jme3.scene.Geometry668 Textual669 (text [control]670 (println "...geo...")))672 (extend-type Triangle673 Textual674 (text [t]675 (println "Triangle: " \newline (.get1 t) \newline676 (.get2 t) \newline (.get3 t))))678 #+end_src680 Here I make the =Viewable= protocol and extend it to JME's types. Now681 JME3's =hello-world= can be written as easily as:683 #+begin_src clojure :results silent684 (cortex.util/view (cortex.util/box))685 #+end_src688 * code generation689 #+begin_src clojure :tangle ../src/cortex/import.clj690 <<import>>691 #+end_src694 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes695 <<util>>696 <<shapes>>697 <<debug-actions>>698 <<world-view>>699 #+end_src