Mercurial > cortex
view org/util.org @ 255:e6e0bb3057b2
minor edits
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Mon, 13 Feb 2012 23:44:15 -0700 |
parents | f5ea63245b3b |
children | d1206b11ae2d |
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 usefull for debugging purposes since56 it allows completion for almost all of JME's classes from the REPL.58 Out of curiousity, 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 (:use clojure.contrib.def)87 (:import com.jme3.math.Vector3f)88 (:import com.jme3.math.Quaternion)89 (:import com.jme3.asset.TextureKey)90 (:import com.jme3.bullet.control.RigidBodyControl)91 (:import com.jme3.bullet.collision.shapes.GImpactCollisionShape)92 (:import com.jme3.scene.shape.Box)93 (:import com.jme3.scene.Node)94 (:import com.jme3.scene.shape.Sphere)95 (:import com.jme3.light.AmbientLight)96 (:import com.jme3.light.DirectionalLight)97 (:import (com.jme3.math Triangle ColorRGBA))98 (:import com.jme3.bullet.BulletAppState)99 (:import com.jme3.material.Material)100 (:import com.jme3.scene.Geometry)101 (:import java.awt.image.BufferedImage)102 (:import javax.swing.JPanel)103 (:import javax.swing.JFrame)104 (:import javax.swing.SwingUtilities)105 (:import com.jme3.scene.plugins.blender.BlenderModelLoader)106 (:import (java.util.logging Level Logger)))108 (defvar println-repl109 (bound-fn [& args] (apply println args))110 "println called from the LWJGL thread will not go to the REPL, but111 instead to whatever terminal started the JVM process. This function112 will always output to the REPL")114 (defn position-camera115 "Change the position of the in-world camera."116 ([world position direction up]117 (doto (.getCamera world)118 (.setLocation )119 (.lookAt direction up)))120 ([world position direction]121 (position-camera122 world position direction Vector3f/UNIT_Y)))124 (defn enable-debug125 "Turn on debug wireframes for every object in this simulation."126 [world]127 (.enableDebug128 (.getPhysicsSpace129 (.getState130 (.getStateManager world)131 BulletAppState))132 (asset-manager)))134 (defn speed-up135 "Increase the dismally slow speed of the world's camera."136 [world]137 (.setMoveSpeed (.getFlyByCamera world)138 (float 60))139 (.setRotationSpeed (.getFlyByCamera world)140 (float 3))141 world)144 (defn no-logging145 "Disable all of jMonkeyEngine's logging."146 []147 (.setLevel (Logger/getLogger "com.jme3") Level/OFF))149 (defn set-accuracy150 "Change the accuracy at which the World's Physics is calculated."151 [world new-accuracy]152 (let [physics-manager153 (.getState154 (.getStateManager world) BulletAppState)]155 (.setAccuracy156 (.getPhysicsSpace physics-manager)157 (float new-accuracy))))160 (defn set-gravity161 "In order to change the gravity of a scene, it is not only necessary162 to set the gravity variable, but to \"tap\" every physics object in163 the scene to reactivate physics calculations."164 [world gravity]165 (traverse166 (fn [geom]167 (if-let168 ;; only set gravity for physical objects.169 [control (.getControl geom RigidBodyControl)]170 (do171 (.setGravity control gravity)172 ;; tappsies!173 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO))))174 (.getRootNode world)))176 (defn add-element177 "Add the Spatial to the world's environment"178 ([world element node]179 (.addAll180 (.getPhysicsSpace181 (.getState182 (.getStateManager world)183 BulletAppState))184 element)185 (.attachChild node element))186 ([world element]187 (add-element world element (.getRootNode world))))189 (defn apply-map190 "Like apply, but works for maps and functions that expect an191 implicit map and nothing else as in (fn [& {}]).192 ------- Example -------193 (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}]194 (println www))195 (apply-map demo {:www \"hello!\"})196 -->\"hello\""197 [fn m]198 (apply fn (reduce #(into %1 %2) [] m)))200 (defn map-vals201 "Transform a map by applying a function to its values,202 keeping the keys the same."203 [f m] (zipmap (keys m) (map f (vals m))))205 (defn runonce206 "Decorator. returns a function which will run only once.207 Inspired by Halloway's version from Lancet."208 {:author "Robert McIntyre"}209 [function]210 (let [sentinel (Object.)211 result (atom sentinel)]212 (fn [& args]213 (locking sentinel214 (if (= @result sentinel)215 (reset! result (apply function args))216 @result)))))219 #+end_src221 #+results: util222 : #'cortex.util/apply-map225 *** Creating Basic Shapes227 #+name: shapes228 #+begin_src clojure :results silent229 (in-ns 'cortex.util)231 (defn load-bullet232 "Runnig this function unpacks the native bullet libraries and makes233 them available."234 []235 (let [sim (world (Node.) {} no-op no-op)]236 (doto sim237 (.enqueue238 (fn []239 (.stop sim)))240 (.start))))243 (defrecord shape-description244 [name245 color246 mass247 friction248 texture249 material250 position251 rotation252 shape253 physical?254 GImpact?255 ])257 (defvar base-shape258 (shape-description.259 "default-shape"260 false261 ;;ColorRGBA/Blue262 1.0 ;; mass263 1.0 ;; friction264 ;; texture265 "Textures/Terrain/BrickWall/BrickWall.jpg"266 ;; material267 "Common/MatDefs/Misc/Unshaded.j3md"268 Vector3f/ZERO269 Quaternion/IDENTITY270 (Box. Vector3f/ZERO 0.5 0.5 0.5)271 true272 false)273 "Basic settings for shapes.")275 (defn make-shape276 [#^shape-description d]277 (let [asset-manager (asset-manager)278 mat (Material. asset-manager (:material d))279 geom (Geometry. (:name d) (:shape d))]280 (if (:texture d)281 (let [key (TextureKey. (:texture d))]282 ;;(.setGenerateMips key true)283 ;;(.setTexture mat "ColorMap" (.loadTexture asset-manager key))284 ))285 (if (:color d) (.setColor mat "Color" (:color d)))286 (.setMaterial geom mat)287 (if-let [rotation (:rotation d)] (.rotate geom rotation))288 (.setLocalTranslation geom (:position d))289 (if (:physical? d)290 (let [physics-control291 (if (:GImpact d)292 ;; Create an accurate mesh collision shape if desired.293 (RigidBodyControl.294 (doto (GImpactCollisionShape.295 (.getMesh geom))296 (.createJmeMesh)297 ;;(.setMargin 0)298 )299 (float (:mass d)))300 ;; otherwise use jme3's default301 (RigidBodyControl. (float (:mass d))))]302 (.addControl geom physics-control)303 ;;(.setSleepingThresholds physics-control (float 0) (float 0))304 (.setFriction physics-control (:friction d))))305 geom))307 (defn box308 ([l w h & {:as options}]309 (let [options (merge base-shape options)]310 (make-shape (assoc options311 :shape (Box. l w h)))))312 ([] (box 0.5 0.5 0.5)))314 (defn sphere315 ([r & {:as options}]316 (let [options (merge base-shape options)]317 (make-shape (assoc options318 :shape (Sphere. 32 32 (float r))))))319 ([] (sphere 0.5)))321 (defn x-ray322 "A usefull material for debuging -- it can be seen no matter what323 object occuldes it."324 [#^ColorRGBA color]325 (doto (Material. (asset-manager)326 "Common/MatDefs/Misc/Unshaded.j3md")327 (.setColor "Color" color)328 (-> (.getAdditionalRenderState)329 (.setDepthTest false))))331 (defn node-seq332 "Take a node and return a seq of all its children333 recursively. There will be no nodes left in the resulting334 structure"335 [#^Node node]336 (tree-seq #(isa? (class %) Node) #(.getChildren %) node))338 (defn nodify339 "Take a sequence of things that can be attached to a node and return340 a node with all of them attached"341 ([name children]342 (let [node (Node. name)]343 (dorun (map #(.attachChild node %) children))344 node))345 ([children] (nodify "" children)))347 (defn load-blender-model348 "Load a .blend file using an asset folder relative path."349 [^String model]350 (.loadModel351 (doto (asset-manager)352 (.registerLoader BlenderModelLoader353 (into-array String ["blend"]))) model))356 #+end_src359 *** Debug Actions360 #+name: debug-actions361 #+begin_src clojure :results silent362 (in-ns 'cortex.util)364 (defn basic-light-setup365 "returns a sequence of lights appropiate for fully lighting a scene"366 []367 (conj368 (doall369 (map370 (fn [direction]371 (doto (DirectionalLight.)372 (.setDirection direction)373 (.setColor ColorRGBA/White)))374 [;; six faces of a cube375 Vector3f/UNIT_X376 Vector3f/UNIT_Y377 Vector3f/UNIT_Z378 (.mult Vector3f/UNIT_X (float -1))379 (.mult Vector3f/UNIT_Y (float -1))380 (.mult Vector3f/UNIT_Z (float -1))]))381 (doto (AmbientLight.)382 (.setColor ColorRGBA/White))))384 (defn light-up-everything385 "Add lights to a world appropiate for quickly seeing everything386 in the scene. Adds six DirectionalLights facing in orthogonal387 directions, and one AmbientLight to provide overall lighting388 coverage."389 [world]390 (dorun391 (map392 #(.addLight (.getRootNode world) %)393 (basic-light-setup))))395 (defn fire-cannon-ball396 "Creates a function that fires a cannon-ball from the current game's397 camera. The cannon-ball will be attached to the node if provided, or398 to the game's RootNode if no node is provided."399 ([node]400 (fn [game value]401 (if (not value)402 (let [camera (.getCamera game)403 cannon-ball404 (sphere 0.7405 :material "Common/MatDefs/Misc/Unshaded.j3md"406 :color ColorRGBA/White407 :name "cannonball!"408 :position409 (.add (.getLocation camera)410 (.mult (.getDirection camera) (float 1)))411 :mass 3)] ;200 0.05412 (.setLinearVelocity413 (.getControl cannon-ball RigidBodyControl)414 (.mult (.getDirection camera) (float 50))) ;50415 (add-element game cannon-ball (if node node (.getRootNode416 game)))417 cannon-ball))))418 ([]419 (fire-cannon-ball false)))421 (def standard-debug-controls422 {"key-space" (fire-cannon-ball)})425 (defn tap [obj direction force]426 (let [control (.getControl obj RigidBodyControl)]427 (.applyTorque428 control429 (.mult (.getPhysicsRotation control)430 (.mult (.normalize direction) (float force))))))433 (defn with-movement434 [object435 [up down left right roll-up roll-down :as keyboard]436 forces437 [root-node438 keymap439 intilization440 world-loop]]441 (let [add-keypress442 (fn [state keymap key]443 (merge keymap444 {key445 (fn [_ pressed?]446 (reset! state pressed?))}))447 move-up? (atom false)448 move-down? (atom false)449 move-left? (atom false)450 move-right? (atom false)451 roll-left? (atom false)452 roll-right? (atom false)454 directions [(Vector3f. 0 1 0)(Vector3f. 0 -1 0)455 (Vector3f. 0 0 1)(Vector3f. 0 0 -1)456 (Vector3f. -1 0 0)(Vector3f. 1 0 0)]457 atoms [move-left? move-right? move-up? move-down?458 roll-left? roll-right?]460 keymap* (reduce merge461 (map #(add-keypress %1 keymap %2)462 atoms463 keyboard))465 splice-loop (fn []466 (dorun467 (map468 (fn [sym direction force]469 (if @sym470 (tap object direction force)))471 atoms directions forces)))473 world-loop* (fn [world tpf]474 (world-loop world tpf)475 (splice-loop))]476 [root-node477 keymap*478 intilization479 world-loop*]))481 (import com.jme3.font.BitmapText)482 (import com.jme3.scene.control.AbstractControl)483 (import com.aurellem.capture.IsoTimer)485 (defn display-dialated-time486 "Shows the time as it is flowing in the simulation on a HUD display.487 Useful for making videos."488 [world timer]489 (let [font (.loadFont (asset-manager) "Interface/Fonts/Default.fnt")490 text (BitmapText. font false)]491 (.setLocalTranslation text 300 (.getLineHeight text) 0)492 (.addControl493 text494 (proxy [AbstractControl] []495 (controlUpdate [tpf]496 (.setText text (format497 "%.2f"498 (float (/ (.getTime timer) 1000)))))499 (controlRender [_ _])))500 (.attachChild (.getGuiNode world) text)))501 #+end_src504 *** Viewing Objects506 #+name: world-view507 #+begin_src clojure :results silent508 (in-ns 'cortex.util)510 (defprotocol Viewable511 (view [something]))513 (extend-type com.jme3.scene.Geometry514 Viewable515 (view [geo]516 (view (doto (Node.)(.attachChild geo)))))518 (extend-type com.jme3.scene.Node519 Viewable520 (view521 [node]522 (.start523 (world524 node525 {}526 (fn [world]527 (enable-debug world)528 (set-gravity world Vector3f/ZERO)529 (light-up-everything world))530 no-op))))532 (extend-type com.jme3.math.ColorRGBA533 Viewable534 (view535 [color]536 (view (doto (Node.)537 (.attachChild (box 1 1 1 :color color))))))539 (defprotocol Textual540 (text [something]541 "Display a detailed textual analysis of the given object."))543 (extend-type com.jme3.scene.Node544 Textual545 (text [node]546 (println "Total Vertexes: " (.getVertexCount node))547 (println "Total Triangles: " (.getTriangleCount node))548 (println "Controls :")549 (dorun (map #(text (.getControl node %)) (range (.getNumControls node))))550 (println "Has " (.getQuantity node) " Children:")551 (doall (map text (.getChildren node)))))553 (extend-type com.jme3.animation.AnimControl554 Textual555 (text [control]556 (let [animations (.getAnimationNames control)]557 (println "Animation Control with " (count animations) " animation(s):")558 (dorun (map println animations)))))560 (extend-type com.jme3.animation.SkeletonControl561 Textual562 (text [control]563 (println "Skeleton Control with the following skeleton:")564 (println (.getSkeleton control))))566 (extend-type com.jme3.bullet.control.KinematicRagdollControl567 Textual568 (text [control]569 (println "Ragdoll Control")))571 (extend-type com.jme3.scene.Geometry572 Textual573 (text [control]574 (println "...geo...")))576 (extend-type Triangle577 Textual578 (text [t]579 (println "Triangle: " \newline (.get1 t) \newline580 (.get2 t) \newline (.get3 t))))582 #+end_src584 Here I make the =Viewable= protocol and extend it to JME's types. Now585 JME3's =hello-world= can be written as easily as:587 #+begin_src clojure :results silent588 (cortex.util/view (cortex.util/box))589 #+end_src592 * COMMENT code generation593 #+begin_src clojure :tangle ../src/cortex/import.clj594 <<import>>595 #+end_src598 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes599 <<util>>600 <<shapes>>601 <<debug-actions>>602 <<world-view>>603 #+end_src