rlm@29: #+title: Clojure Utilities for jMonkeyEngine3 rlm@23: #+author: Robert McIntyre rlm@23: #+email: rlm@mit.edu rlm@29: #+description: rlm@29: #+keywords: JME3, clojure, import, utilities rlm@23: #+SETUPFILE: ../../aurellem/org/setup.org rlm@23: #+INCLUDE: ../../aurellem/org/level-0.org rlm@29: rlm@34: [TABLE-OF-CONTENTS] rlm@29: rlm@29: These are a collection of functions to make programming jMonkeyEngine rlm@29: in clojure easier. rlm@23: rlm@34: * Imports rlm@28: rlm@66: #+name: import rlm@23: #+begin_src clojure :results silent rlm@23: (ns cortex.import rlm@23: (:require swank.util.class-browse)) rlm@23: rlm@23: (defn permissive-import rlm@23: [classname] rlm@23: (eval `(try (import '~classname) rlm@23: (catch java.lang.Exception e# rlm@23: (println "couldn't import " '~classname)))) rlm@23: classname) rlm@23: rlm@23: (defn jme-class? [classname] rlm@23: (and rlm@23: (.startsWith classname "com.jme3.") rlm@23: ;; Don't import the Lwjgl stuff since it can throw exceptions rlm@23: ;; upon being loaded. rlm@23: (not (re-matches #".*Lwjgl.*" classname)))) rlm@23: rlm@23: (defn jme-classes rlm@23: "returns a list of all jme3 classes" rlm@23: [] rlm@23: (filter rlm@23: jme-class? rlm@23: (map :name rlm@23: swank.util.class-browse/available-classes))) rlm@23: rlm@23: (defn mega-import-jme3 rlm@23: "Import ALL the jme classes. For REPL use." rlm@23: [] rlm@23: (doall rlm@23: (map (comp permissive-import symbol) (jme-classes)))) rlm@23: #+end_src rlm@23: rlm@29: jMonkeyEngine3 has a plethora of classes which can be overwhelming to rlm@29: manage. This code uses reflection to import all of them. Once I'm rlm@29: happy with the general structure of a namespace I can deal with rlm@29: importing only the classes it actually needs. rlm@29: rlm@23: The =mega-import-jme3= is quite usefull for debugging purposes since rlm@34: it allows completion for almost all of JME's classes from the REPL. rlm@23: rlm@23: Out of curiousity, let's see just how many classes =mega-import-jme3= rlm@23: imports: rlm@23: rlm@29: #+begin_src clojure :exports both :results output rlm@29: (println (clojure.core/count (cortex.import/jme-classes)) "classes") rlm@23: #+end_src rlm@23: rlm@23: #+results: rlm@29: : 955 classes rlm@23: rlm@25: rlm@34: * Utilities rlm@23: rlm@29: The utilities here come in three main groups: rlm@29: - Changing settings in a running =Application= rlm@29: - Creating objects rlm@50: - Debug Actions rlm@29: - Visualizing objects rlm@23: rlm@29: *** Changing Settings rlm@24: rlm@66: #+name: util rlm@25: #+begin_src clojure rlm@29: (ns cortex.util rlm@34: "Utility functions for making jMonkeyEngine3 easier to program from rlm@34: clojure." rlm@29: {:author "Robert McIntyre"} rlm@29: (:use cortex.world) rlm@29: (:use clojure.contrib.def) rlm@29: (:import com.jme3.math.Vector3f) rlm@29: (:import com.jme3.math.Quaternion) rlm@29: (:import com.jme3.asset.TextureKey) rlm@29: (:import com.jme3.bullet.control.RigidBodyControl) rlm@29: (:import com.jme3.bullet.collision.shapes.GImpactCollisionShape) rlm@29: (:import com.jme3.scene.shape.Box) rlm@29: (:import com.jme3.scene.Node) rlm@29: (:import com.jme3.scene.shape.Sphere) rlm@47: (:import com.jme3.light.AmbientLight) rlm@29: (:import com.jme3.light.DirectionalLight) rlm@29: (:import com.jme3.math.ColorRGBA) rlm@29: (:import com.jme3.bullet.BulletAppState) rlm@29: (:import com.jme3.material.Material) rlm@40: (:import com.jme3.scene.Geometry) rlm@40: (:import (java.util.logging Level Logger))) rlm@40: rlm@40: rlm@25: rlm@29: (defvar println-repl rlm@29: (bound-fn [& args] (apply println args)) rlm@29: "println called from the LWJGL thread will not go to the REPL, but rlm@29: instead to whatever terminal started the JVM process. This function rlm@29: will always output to the REPL") rlm@25: rlm@29: (defn position-camera rlm@34: "Change the position of the in-world camera." rlm@34: ([world position direction up] rlm@34: (doto (.getCamera world) rlm@29: (.setLocation ) rlm@29: (.lookAt direction up))) rlm@34: ([world position direction] rlm@29: (position-camera rlm@34: world position direction Vector3f/UNIT_Y))) rlm@25: rlm@29: (defn enable-debug rlm@34: "Turn on debug wireframes for every object in this simulation." rlm@29: [world] rlm@29: (.enableDebug rlm@29: (.getPhysicsSpace rlm@29: (.getState rlm@29: (.getStateManager world) rlm@29: BulletAppState)) rlm@29: (asset-manager))) rlm@29: rlm@40: (defn no-logging rlm@40: "Disable all of jMonkeyEngine's logging." rlm@40: [] rlm@40: (.setLevel (Logger/getLogger "com.jme3") Level/OFF)) rlm@40: rlm@40: (defn set-accuracy rlm@40: "Change the accuracy at which the World's Physics is calculated." rlm@40: [world new-accuracy] rlm@40: (let [physics-manager rlm@40: (.getState rlm@40: (.getStateManager world) BulletAppState)] rlm@40: (.setAccuracy rlm@40: (.getPhysicsSpace physics-manager) rlm@40: (float new-accuracy)))) rlm@40: rlm@40: rlm@34: (defn set-gravity rlm@29: "In order to change the gravity of a scene, it is not only necessary rlm@29: to set the gravity variable, but to \"tap\" every physics object in rlm@29: the scene to reactivate physics calculations." rlm@34: [world gravity] rlm@25: (traverse rlm@25: (fn [geom] rlm@25: (if-let rlm@29: ;; only set gravity for physical objects. rlm@25: [control (.getControl geom RigidBodyControl)] rlm@25: (do rlm@25: (.setGravity control gravity) rlm@29: ;; tappsies! rlm@29: (.applyImpulse control Vector3f/ZERO Vector3f/ZERO)))) rlm@34: (.getRootNode world))) rlm@29: rlm@29: (defn add-element rlm@34: "Add the Spatial to the world's environment" rlm@34: ([world element node] rlm@29: (.addAll rlm@29: (.getPhysicsSpace rlm@29: (.getState rlm@34: (.getStateManager world) rlm@29: BulletAppState)) rlm@29: element) rlm@29: (.attachChild node element)) rlm@34: ([world element] rlm@34: (add-element world element (.getRootNode world)))) rlm@29: rlm@29: (defn apply-map rlm@29: "Like apply, but works for maps and functions that expect an rlm@29: implicit map and nothing else as in (fn [& {}]). rlm@29: ------- Example ------- rlm@29: (defn demo [& {:keys [www] :or {www \"oh yeah\"} :as env}] rlm@29: (println www)) rlm@29: (apply-map demo {:www \"hello!\"}) rlm@29: -->\"hello\"" rlm@29: [fn m] rlm@29: (apply fn (reduce #(into %1 %2) [] m))) rlm@29: rlm@25: #+end_src rlm@25: rlm@47: #+results: util rlm@47: : #'cortex.util/apply-map rlm@73: rlm@25: rlm@29: *** Creating Basic Shapes rlm@29: rlm@66: #+name: shapes rlm@25: #+begin_src clojure :results silent rlm@25: (in-ns 'cortex.util) rlm@29: rlm@25: (defrecord shape-description rlm@25: [name rlm@25: color rlm@25: mass rlm@25: friction rlm@25: texture rlm@25: material rlm@25: position rlm@25: rotation rlm@25: shape rlm@29: physical? rlm@29: GImpact? rlm@29: ]) rlm@25: rlm@34: (defvar base-shape rlm@25: (shape-description. rlm@25: "default-shape" rlm@25: false rlm@25: ;;ColorRGBA/Blue rlm@25: 1.0 ;; mass rlm@25: 1.0 ;; friction rlm@25: ;; texture rlm@25: "Textures/Terrain/BrickWall/BrickWall.jpg" rlm@25: ;; material rlm@25: "Common/MatDefs/Misc/Unshaded.j3md" rlm@25: Vector3f/ZERO rlm@25: Quaternion/IDENTITY rlm@25: (Box. Vector3f/ZERO 0.5 0.5 0.5) rlm@29: true rlm@34: false) rlm@34: "Basic settings for shapes.") rlm@25: rlm@25: (defn make-shape rlm@25: [#^shape-description d] rlm@29: (let [asset-manager (asset-manager) rlm@25: mat (Material. asset-manager (:material d)) rlm@25: geom (Geometry. (:name d) (:shape d))] rlm@25: (if (:texture d) rlm@25: (let [key (TextureKey. (:texture d))] rlm@34: ;;(.setGenerateMips key true) rlm@34: ;;(.setTexture mat "ColorMap" (.loadTexture asset-manager key)) rlm@34: )) rlm@25: (if (:color d) (.setColor mat "Color" (:color d))) rlm@25: (.setMaterial geom mat) rlm@25: (if-let [rotation (:rotation d)] (.rotate geom rotation)) rlm@25: (.setLocalTranslation geom (:position d)) rlm@25: (if (:physical? d) rlm@29: (let [physics-control rlm@29: (if (:GImpact d) rlm@29: ;; Create an accurate mesh collision shape if desired. rlm@29: (RigidBodyControl. rlm@29: (doto (GImpactCollisionShape. rlm@29: (.getMesh geom)) rlm@29: (.createJmeMesh) rlm@73: ;;(.setMargin 0) rlm@73: ) rlm@29: (float (:mass d))) rlm@29: ;; otherwise use jme3's default rlm@29: (RigidBodyControl. (float (:mass d))))] rlm@25: (.addControl geom physics-control) rlm@25: ;;(.setSleepingThresholds physics-control (float 0) (float 0)) rlm@25: (.setFriction physics-control (:friction d)))) rlm@25: geom)) rlm@25: rlm@25: (defn box rlm@25: ([l w h & {:as options}] rlm@25: (let [options (merge base-shape options)] rlm@25: (make-shape (assoc options rlm@25: :shape (Box. l w h))))) rlm@25: ([] (box 0.5 0.5 0.5))) rlm@25: rlm@25: (defn sphere rlm@25: ([r & {:as options}] rlm@25: (let [options (merge base-shape options)] rlm@25: (make-shape (assoc options rlm@25: :shape (Sphere. 32 32 (float r)))))) rlm@25: ([] (sphere 0.5))) rlm@62: rlm@74: (defn green-x-ray rlm@74: "A usefull material for debuging -- it can be seen no matter what rlm@74: object occuldes it." rlm@74: [] rlm@74: (doto (Material. (asset-manager) rlm@74: "Common/MatDefs/Misc/Unshaded.j3md") rlm@74: (.setColor "Color" ColorRGBA/Green) rlm@74: (-> (.getAdditionalRenderState) rlm@74: (.setDepthTest false)))) rlm@74: rlm@62: (defn node-seq rlm@62: "Take a node and return a seq of all its children rlm@62: recursively. There will be no nodes left in the resulting rlm@62: structure" rlm@62: [#^Node node] rlm@62: (tree-seq #(isa? (class %) Node) #(.getChildren %) node)) rlm@62: rlm@62: (defn nodify rlm@62: "Take a sequence of things that can be attached to a node and return rlm@62: a node with all of them attached" rlm@62: ([name children] rlm@62: (let [node (Node. name)] rlm@62: (dorun (map #(.attachChild node %) children)) rlm@62: node)) rlm@62: ([children] (nodify "" children))) rlm@62: rlm@62: rlm@29: #+end_src rlm@25: rlm@25: rlm@50: *** Debug Actions rlm@66: #+name: debug-actions rlm@29: #+begin_src clojure :results silent rlm@60: (in-ns 'cortex.util) rlm@60: rlm@47: (defn basic-light-setup rlm@47: "returns a sequence of lights appropiate for fully lighting a scene" rlm@47: [] rlm@47: (conj rlm@47: (doall rlm@47: (map rlm@47: (fn [direction] rlm@47: (doto (DirectionalLight.) rlm@47: (.setDirection direction) rlm@47: (.setColor ColorRGBA/White))) rlm@47: [;; six faces of a cube rlm@47: Vector3f/UNIT_X rlm@47: Vector3f/UNIT_Y rlm@47: Vector3f/UNIT_Z rlm@47: (.mult Vector3f/UNIT_X (float -1)) rlm@47: (.mult Vector3f/UNIT_Y (float -1)) rlm@47: (.mult Vector3f/UNIT_Z (float -1))])) rlm@47: (doto (AmbientLight.) rlm@47: (.setColor ColorRGBA/White)))) rlm@47: rlm@47: (defn light-up-everything rlm@47: "Add lights to a world appropiate for quickly seeing everything rlm@47: in the scene. Adds six DirectionalLights facing in orthogonal rlm@47: directions, and one AmbientLight to provide overall lighting rlm@47: coverage." rlm@47: [world] rlm@47: (dorun rlm@47: (map rlm@47: #(.addLight (.getRootNode world) %) rlm@47: (basic-light-setup)))) rlm@50: rlm@50: (defn fire-cannon-ball rlm@50: "Creates a function that fires a cannon-ball from the current game's rlm@50: camera. The cannon-ball will be attached to the node if provided, or rlm@50: to the game's RootNode if no node is provided." rlm@50: ([node] rlm@50: (fn [game value] rlm@50: (if (not value) rlm@50: (let [camera (.getCamera game) rlm@50: cannon-ball rlm@50: (sphere 0.7 rlm@50: :material "Common/MatDefs/Misc/Unshaded.j3md" rlm@50: :texture "Textures/PokeCopper.jpg" rlm@50: :position rlm@50: (.add (.getLocation camera) rlm@50: (.mult (.getDirection camera) (float 1))) rlm@50: :mass 3)] ;200 0.05 rlm@50: (.setLinearVelocity rlm@50: (.getControl cannon-ball RigidBodyControl) rlm@50: (.mult (.getDirection camera) (float 50))) ;50 rlm@50: (add-element game cannon-ball (if node node (.getRootNode game))))))) rlm@50: ([] rlm@50: (fire-cannon-ball false))) rlm@60: rlm@60: (def standard-debug-controls rlm@60: {"key-space" (fire-cannon-ball)}) rlm@60: rlm@60: rlm@60: rlm@60: rlm@50: #+end_src rlm@50: rlm@50: rlm@50: *** Viewing Objects rlm@50: rlm@66: #+name: world-view rlm@50: #+begin_src clojure :results silent rlm@50: (in-ns 'cortex.util) rlm@50: rlm@50: (defprotocol Viewable rlm@50: (view [something])) rlm@50: rlm@50: (extend-type com.jme3.scene.Geometry rlm@50: Viewable rlm@50: (view [geo] rlm@50: (view (doto (Node.)(.attachChild geo))))) rlm@47: rlm@29: (extend-type com.jme3.scene.Node rlm@29: Viewable rlm@29: (view rlm@29: [node] rlm@29: (.start rlm@29: (world rlm@29: node rlm@29: {} rlm@29: (fn [world] rlm@29: (enable-debug world) rlm@29: (set-gravity world Vector3f/ZERO) rlm@47: (light-up-everything world)) rlm@29: no-op)))) rlm@62: rlm@62: (extend-type com.jme3.math.ColorRGBA rlm@62: Viewable rlm@62: (view rlm@62: [color] rlm@62: (view (doto (Node.) rlm@62: (.attachChild (box 1 1 1 :color color)))))) rlm@62: rlm@62: (defprotocol Textual rlm@62: (text [something] rlm@62: "Display a detailed textual analysis of the given object.")) rlm@62: rlm@62: (extend-type com.jme3.scene.Node rlm@62: Textual rlm@62: (text [node] rlm@62: (println "Total Vertexes: " (.getVertexCount node)) rlm@62: (println "Total Triangles: " (.getTriangleCount node)) rlm@62: (println "Controls :") rlm@62: (dorun (map #(text (.getControl node %)) (range (.getNumControls node)))) rlm@62: (println "Has " (.getQuantity node) " Children:") rlm@62: (doall (map text (.getChildren node))))) rlm@62: rlm@62: (extend-type com.jme3.animation.AnimControl rlm@62: Textual rlm@62: (text [control] rlm@62: (let [animations (.getAnimationNames control)] rlm@62: (println "Animation Control with " (count animations) " animation(s):") rlm@62: (dorun (map println animations))))) rlm@62: rlm@62: (extend-type com.jme3.animation.SkeletonControl rlm@62: Textual rlm@62: (text [control] rlm@62: (println "Skeleton Control with the following skeleton:") rlm@62: (println (.getSkeleton control)))) rlm@62: rlm@62: (extend-type com.jme3.bullet.control.KinematicRagdollControl rlm@62: Textual rlm@62: (text [control] rlm@62: (println "Ragdoll Control"))) rlm@62: rlm@62: (extend-type com.jme3.scene.Geometry rlm@62: Textual rlm@62: (text [control] rlm@62: (println "...geo..."))) rlm@25: #+end_src rlm@25: rlm@29: Here I make the =Viewable= protocol and extend it to JME's types. Now rlm@34: JME3's =hello-world= can be written as easily as: rlm@29: rlm@29: #+begin_src clojure :results silent rlm@29: (cortex.util/view (cortex.util/box)) rlm@29: #+end_src rlm@29: rlm@29: rlm@24: * COMMENT code generation rlm@24: #+begin_src clojure :tangle ../src/cortex/import.clj rlm@24: <> rlm@24: #+end_src rlm@25: rlm@25: rlm@29: #+begin_src clojure :tangle ../src/cortex/util.clj :noweb yes rlm@25: <> rlm@25: <> rlm@50: <> rlm@29: <> rlm@25: #+end_src rlm@25: rlm@29: rlm@29: rlm@29: rlm@29: rlm@29: rlm@29: rlm@29: rlm@29: rlm@29: rlm@29: rlm@29: rlm@29: rlm@29: rlm@29: rlm@29: rlm@29: