Mercurial > cortex
view org/util.org @ 396:0898d93a39ef
make touch sensors much less concentrated.
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Mon, 03 Mar 2014 23:04:02 -0500 |
parents | 4c37d39a3cf6 |
children | 6ba908c1a0a9 a44d8a28cbea |
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)))136 (defn enable-debug137 "Turn on debug wireframes for every object in this simulation."138 [world]139 (.enableDebug140 (.getPhysicsSpace141 (.getState142 (.getStateManager world)143 BulletAppState))144 (asset-manager)))146 (defn speed-up147 "Increase the dismally slow speed of the world's camera."148 [world]149 (.setMoveSpeed (.getFlyByCamera world)150 (float 60))151 (.setRotationSpeed (.getFlyByCamera world)152 (float 3))153 world)156 (defn no-logging157 "Disable all of jMonkeyEngine's logging."158 []159 (.setLevel (Logger/getLogger "com.jme3") Level/OFF))161 (defn set-accuracy162 "Change the accuracy at which the World's Physics is calculated."163 [world new-accuracy]164 (let [physics-manager165 (.getState166 (.getStateManager world) BulletAppState)]167 (.setAccuracy168 (.getPhysicsSpace physics-manager)169 (float new-accuracy))))172 (defn set-gravity173 "In order to change the gravity of a scene, it is not only necessary174 to set the gravity variable, but to \"tap\" every physics object in175 the scene to reactivate physics calculations."176 [world gravity]177 (traverse178 (fn [geom]179 (if-let180 ;; only set gravity for physical objects.181 [control (.getControl geom RigidBodyControl)]182 (do183 (.setGravity control gravity)184 ;; tappsies!185 (.applyImpulse control Vector3f/ZERO Vector3f/ZERO))))186 (.getRootNode world)))188 (defn add-element189 "Add the Spatial to the world's environment"190 ([world element node]191 (.addAll192 (.getPhysicsSpace193 (.getState194 (.getStateManager world)195 BulletAppState))196 element)197 (.attachChild node element))198 ([world element]199 (add-element world element (.getRootNode world))))201 (defn apply-map202 "Like apply, but works for maps and functions that expect an203 implicit map and nothing else as in (fn [& {}]).204 ------- Example -------205 (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}]206 (println www))207 (apply-map demo {:www \"hello!\"})208 -->\"hello\""209 [fn m]210 (apply fn (reduce #(into %1 %2) [] m)))212 (defn map-vals213 "Transform a map by applying a function to its values,214 keeping the keys the same."215 [f m] (zipmap (keys m) (map f (vals m))))217 (defn runonce218 "Decorator. returns a function which will run only once.219 Inspired by Halloway's version from Lancet."220 {:author "Robert McIntyre"}221 [function]222 (let [sentinel (Object.)223 result (atom sentinel)]224 (fn [& args]225 (locking sentinel226 (if (= @result sentinel)227 (reset! result (apply function args))228 @result)))))231 #+end_src233 #+results: util234 : #'cortex.util/runonce237 *** Creating Basic Shapes239 #+name: shapes240 #+begin_src clojure :results silent241 (in-ns 'cortex.util)243 (defn load-bullet244 "Running this function unpacks the native bullet libraries and makes245 them available."246 []247 (let [sim (world (Node.) {} no-op no-op)]248 (doto sim249 (.enqueue250 (fn []251 (.stop sim)))252 (.start))))255 (defrecord shape-description256 [name257 color258 mass259 friction260 texture261 material262 position263 rotation264 shape265 physical?266 GImpact?267 ])269 (def base-shape270 "Basic settings for shapes."271 (shape-description.272 "default-shape"273 false274 ;;ColorRGBA/Blue275 1.0 ;; mass276 1.0 ;; friction277 ;; texture278 "Textures/Terrain/BrickWall/BrickWall.jpg"279 ;; material280 "Common/MatDefs/Misc/Unshaded.j3md"281 Vector3f/ZERO282 Quaternion/IDENTITY283 (Box. Vector3f/ZERO 0.5 0.5 0.5)284 true285 false))287 (defn make-shape288 [#^shape-description d]289 (let [asset-manager (asset-manager)290 mat (Material. asset-manager (:material d))291 geom (Geometry. (:name d) (:shape d))]292 (if (:texture d)293 (let [key (TextureKey. (:texture d))]294 (.setGenerateMips key true)295 (.setTexture mat "ColorMap" (.loadTexture asset-manager key))296 ))297 (if (:color d) (.setColor mat "Color" (:color d)))298 (.setMaterial geom mat)299 (if-let [rotation (:rotation d)] (.rotate geom rotation))300 (.setLocalTranslation geom (:position d))301 (if (:physical? d)302 (let [physics-control303 (if (:GImpact d)304 ;; Create an accurate mesh collision shape if desired.305 (RigidBodyControl.306 (doto (GImpactCollisionShape.307 (.getMesh geom))308 (.createJmeMesh)309 ;;(.setMargin 0)310 )311 (float (:mass d)))312 ;; otherwise use jme3's default313 (RigidBodyControl. (float (:mass d))))]314 (.addControl geom physics-control)315 ;;(.setSleepingThresholds physics-control (float 0) (float 0))316 (.setFriction physics-control (:friction d))))317 geom))319 (defn box320 ([l w h & {:as options}]321 (let [options (merge base-shape options)]322 (make-shape (assoc options323 :shape (Box. l w h)))))324 ([] (box 0.5 0.5 0.5)))326 (defn sphere327 ([r & {:as options}]328 (let [options (merge base-shape options)]329 (make-shape (assoc options330 :shape (Sphere. 32 32 (float r))))))331 ([] (sphere 0.5)))333 (defn x-ray334 "A useful material for debugging -- it can be seen no matter what335 object occludes it."336 [#^ColorRGBA color]337 (doto (Material. (asset-manager)338 "Common/MatDefs/Misc/Unshaded.j3md")339 (.setColor "Color" color)340 (-> (.getAdditionalRenderState)341 (.setDepthTest false))))343 (defn node-seq344 "Take a node and return a seq of all its children345 recursively. There will be no nodes left in the resulting346 structure"347 [#^Node node]348 (tree-seq #(isa? (class %) Node) #(.getChildren %) node))350 (defn nodify351 "Take a sequence of things that can be attached to a node and return352 a node with all of them attached"353 ([name children]354 (let [node (Node. name)]355 (dorun (map #(.attachChild node %) children))356 node))357 ([children] (nodify "" children)))359 (defn load-blender-model360 "Load a .blend file using an asset folder relative path."361 [^String model]362 (.loadModel363 (doto (asset-manager)364 (.registerLoader BlenderModelLoader365 (into-array String ["blend"]))) model))368 #+end_src371 *** Debug Actions372 #+name: debug-actions373 #+begin_src clojure :results silent374 (in-ns 'cortex.util)376 (defn basic-light-setup377 "returns a sequence of lights appropriate for fully lighting a scene"378 []379 (conj380 (doall381 (map382 (fn [direction]383 (doto (DirectionalLight.)384 (.setDirection direction)385 (.setColor ColorRGBA/White)))386 [;; six faces of a cube387 Vector3f/UNIT_X388 Vector3f/UNIT_Y389 Vector3f/UNIT_Z390 (.mult Vector3f/UNIT_X (float -1))391 (.mult Vector3f/UNIT_Y (float -1))392 (.mult Vector3f/UNIT_Z (float -1))]))393 (doto (AmbientLight.)394 (.setColor ColorRGBA/White))))396 (defn light-up-everything397 "Add lights to a world appropriate for quickly seeing everything398 in the scene. Adds six DirectionalLights facing in orthogonal399 directions, and one AmbientLight to provide overall lighting400 coverage."401 [world]402 (dorun403 (map404 #(.addLight (.getRootNode world) %)405 (basic-light-setup))))407 (defn fire-cannon-ball408 "Creates a function that fires a cannon-ball from the current game's409 camera. The cannon-ball will be attached to the node if provided, or410 to the game's RootNode if no node is provided."411 ([node]412 (fn [game value]413 (if (not value)414 (let [camera (.getCamera game)415 cannon-ball416 (sphere 0.4417 ;;:texture nil418 :material "Common/MatDefs/Misc/Unshaded.j3md"419 :color ColorRGBA/Blue420 :name "cannonball!"421 :position422 (.add (.getLocation camera)423 (.mult (.getDirection camera) (float 1)))424 :mass 25)] ;200 0.05425 (.setLinearVelocity426 (.getControl cannon-ball RigidBodyControl)427 (.mult (.getDirection camera) (float 50))) ;50428 (add-element game cannon-ball (if node node (.getRootNode429 game)))430 cannon-ball))))431 ([]432 (fire-cannon-ball false)))434 (def standard-debug-controls435 {"key-space" (fire-cannon-ball)})438 (defn tap [obj direction force]439 (let [control (.getControl obj RigidBodyControl)]440 (.applyTorque441 control442 (.mult (.getPhysicsRotation control)443 (.mult (.normalize direction) (float force))))))446 (defn with-movement447 [object448 [up down left right roll-up roll-down :as keyboard]449 forces450 [root-node451 keymap452 initialization453 world-loop]]454 (let [add-keypress455 (fn [state keymap key]456 (merge keymap457 {key458 (fn [_ pressed?]459 (reset! state pressed?))}))460 move-up? (atom false)461 move-down? (atom false)462 move-left? (atom false)463 move-right? (atom false)464 roll-left? (atom false)465 roll-right? (atom false)467 directions [(Vector3f. 0 1 0)(Vector3f. 0 -1 0)468 (Vector3f. 0 0 1)(Vector3f. 0 0 -1)469 (Vector3f. -1 0 0)(Vector3f. 1 0 0)]470 atoms [move-left? move-right? move-up? move-down?471 roll-left? roll-right?]473 keymap* (reduce merge474 (map #(add-keypress %1 keymap %2)475 atoms476 keyboard))478 splice-loop (fn []479 (dorun480 (map481 (fn [sym direction force]482 (if @sym483 (tap object direction force)))484 atoms directions forces)))486 world-loop* (fn [world tpf]487 (world-loop world tpf)488 (splice-loop))]489 [root-node490 keymap*491 initialization492 world-loop*]))494 (import com.jme3.font.BitmapText)495 (import com.jme3.scene.control.AbstractControl)496 (import com.aurellem.capture.IsoTimer)498 (defn display-dilated-time499 "Shows the time as it is flowing in the simulation on a HUD display.500 Useful for making videos."501 [world timer]502 (let [font (.loadFont (asset-manager) "Interface/Fonts/Default.fnt")503 text (BitmapText. font false)]504 (.setLocalTranslation text 300 (.getLineHeight text) 0)505 (.addControl506 text507 (proxy [AbstractControl] []508 (controlUpdate [tpf]509 (.setText text (format510 "%.2f"511 (float (.getTimeInSeconds timer)))))512 (controlRender [_ _])))513 (.attachChild (.getGuiNode world) text)))514 #+end_src517 *** Viewing Objects519 #+name: world-view520 #+begin_src clojure :results silent521 (in-ns 'cortex.util)523 (defprotocol Viewable524 (view [something]))526 (extend-type com.jme3.scene.Geometry527 Viewable528 (view [geo]529 (view (doto (Node.)(.attachChild geo)))))531 (extend-type com.jme3.scene.Node532 Viewable533 (view534 [node]535 (.start536 (world537 node538 {}539 (fn [world]540 (enable-debug world)541 (set-gravity world Vector3f/ZERO)542 (light-up-everything world))543 no-op))))545 (extend-type com.jme3.math.ColorRGBA546 Viewable547 (view548 [color]549 (view (doto (Node.)550 (.attachChild (box 1 1 1 :color color))))))552 (extend-type ij.ImagePlus553 Viewable554 (view [image]555 (.show image)))557 (extend-type java.awt.image.BufferedImage558 Viewable559 (view560 [image]561 (view (ImagePlus. "view-buffered-image" image))))564 (defprotocol Textual565 (text [something]566 "Display a detailed textual analysis of the given object."))568 (extend-type com.jme3.scene.Node569 Textual570 (text [node]571 (println "Total Vertexes: " (.getVertexCount node))572 (println "Total Triangles: " (.getTriangleCount node))573 (println "Controls :")574 (dorun (map #(text (.getControl node %)) (range (.getNumControls node))))575 (println "Has " (.getQuantity node) " Children:")576 (doall (map text (.getChildren node)))))578 (extend-type com.jme3.animation.AnimControl579 Textual580 (text [control]581 (let [animations (.getAnimationNames control)]582 (println "Animation Control with " (count animations) " animation(s):")583 (dorun (map println animations)))))585 (extend-type com.jme3.animation.SkeletonControl586 Textual587 (text [control]588 (println "Skeleton Control with the following skeleton:")589 (println (.getSkeleton control))))591 (extend-type com.jme3.bullet.control.KinematicRagdollControl592 Textual593 (text [control]594 (println "Ragdoll Control")))596 (extend-type com.jme3.scene.Geometry597 Textual598 (text [control]599 (println "...geo...")))601 (extend-type Triangle602 Textual603 (text [t]604 (println "Triangle: " \newline (.get1 t) \newline605 (.get2 t) \newline (.get3 t))))607 #+end_src609 Here I make the =Viewable= protocol and extend it to JME's types. Now610 JME3's =hello-world= can be written as easily as:612 #+begin_src clojure :results silent613 (cortex.util/view (cortex.util/box))614 #+end_src617 * COMMENT code generation618 #+begin_src clojure :tangle ../src/cortex/import.clj619 <<import>>620 #+end_src623 #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes624 <<util>>625 <<shapes>>626 <<debug-actions>>627 <<world-view>>628 #+end_src