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@388: (:import java.io.File java.util.jar.JarFile)) 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@306: ;; 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@388: (defn jme-jars [] rlm@388: (map rlm@388: #(JarFile. (File. %)) rlm@388: (filter (partial re-matches #".*jME3.*") rlm@388: (clojure.string/split rlm@388: (System/getProperty "java.class.path") #":")))) rlm@388: rlm@388: (defn jme-class-names [] rlm@23: (filter rlm@23: jme-class? rlm@388: (map rlm@388: (comp rlm@388: #(.replace % File/separator ".") rlm@388: #(clojure.string/replace % ".class" "")) rlm@388: (filter rlm@388: (partial re-matches #".*\.class$") rlm@388: (mapcat rlm@388: #(map rlm@388: str rlm@388: (enumeration-seq rlm@388: (.entries %))) rlm@388: (jme-jars)))))) rlm@388: rlm@23: (defn mega-import-jme3 rlm@23: "Import ALL the jme classes. For REPL use." rlm@23: [] rlm@388: (dorun rlm@388: (map (comp permissive-import symbol) (jme-class-names)))) 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@306: The =mega-import-jme3= is quite useful for debugging purposes since rlm@34: it allows completion for almost all of JME's classes from the REPL. rlm@23: rlm@306: Out of curiosity, 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@455: (println (clojure.core/count (cortex.import/jme-class-names)) "classes") rlm@23: #+end_src rlm@23: rlm@23: #+results: rlm@455: : 938 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: (: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@114: (:import (com.jme3.math Triangle ColorRGBA)) rlm@29: (:import com.jme3.bullet.BulletAppState) rlm@29: (:import com.jme3.material.Material) rlm@40: (:import com.jme3.scene.Geometry) rlm@114: (:import java.awt.image.BufferedImage) rlm@114: (:import javax.swing.JPanel) rlm@114: (:import javax.swing.JFrame) rlm@335: (:import ij.ImagePlus) rlm@114: (:import javax.swing.SwingUtilities) rlm@192: (:import com.jme3.scene.plugins.blender.BlenderModelLoader) rlm@40: (:import (java.util.logging Level Logger))) rlm@40: rlm@320: (def println-repl rlm@29: "println called from the LWJGL thread will not go to the REPL, but rlm@114: instead to whatever terminal started the JVM process. This function rlm@320: will always output to the REPL" rlm@320: (bound-fn [& args] (apply println args))) rlm@25: rlm@29: (defn position-camera rlm@34: "Change the position of the in-world camera." rlm@297: [world #^Vector3f position #^Quaternion rotation] rlm@34: (doto (.getCamera world) rlm@297: (.setLocation position) rlm@297: (.setRotation rotation))) 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@78: (defn speed-up rlm@78: "Increase the dismally slow speed of the world's camera." rlm@78: [world] rlm@78: (.setMoveSpeed (.getFlyByCamera world) rlm@122: (float 60)) rlm@78: (.setRotationSpeed (.getFlyByCamera world) rlm@122: (float 3)) rlm@78: world) rlm@78: rlm@78: 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@151: (defn map-vals rlm@151: "Transform a map by applying a function to its values, rlm@151: keeping the keys the same." rlm@151: [f m] (zipmap (keys m) (map f (vals m)))) rlm@151: rlm@164: (defn runonce rlm@164: "Decorator. returns a function which will run only once. rlm@164: Inspired by Halloway's version from Lancet." rlm@164: {:author "Robert McIntyre"} rlm@164: [function] rlm@164: (let [sentinel (Object.) rlm@164: result (atom sentinel)] rlm@164: (fn [& args] rlm@164: (locking sentinel rlm@164: (if (= @result sentinel) rlm@164: (reset! result (apply function args)) rlm@164: @result))))) rlm@164: rlm@151: rlm@25: #+end_src rlm@25: rlm@47: #+results: util rlm@297: : #'cortex.util/runonce 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@153: (defn load-bullet rlm@306: "Running this function unpacks the native bullet libraries and makes rlm@153: them available." rlm@153: [] rlm@153: (let [sim (world (Node.) {} no-op no-op)] rlm@153: (doto sim rlm@153: (.enqueue rlm@153: (fn [] rlm@153: (.stop sim))) rlm@153: (.start)))) rlm@153: rlm@153: 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@320: (def base-shape rlm@320: "Basic settings for shapes." rlm@320: (shape-description. rlm@320: "default-shape" rlm@320: false rlm@320: ;;ColorRGBA/Blue rlm@320: 1.0 ;; mass rlm@320: 1.0 ;; friction rlm@320: ;; texture rlm@320: "Textures/Terrain/BrickWall/BrickWall.jpg" rlm@320: ;; material rlm@320: "Common/MatDefs/Misc/Unshaded.j3md" rlm@320: Vector3f/ZERO rlm@320: Quaternion/IDENTITY rlm@320: (Box. Vector3f/ZERO 0.5 0.5 0.5) rlm@320: true rlm@320: false)) 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@363: (.setGenerateMips key true) rlm@363: (.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@192: (defn x-ray rlm@306: "A useful material for debugging -- it can be seen no matter what rlm@306: object occludes it." rlm@192: [#^ColorRGBA color] rlm@192: (doto (Material. (asset-manager) rlm@192: "Common/MatDefs/Misc/Unshaded.j3md") rlm@192: (.setColor "Color" color) rlm@192: (-> (.getAdditionalRenderState) rlm@192: (.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@192: (defn load-blender-model rlm@192: "Load a .blend file using an asset folder relative path." rlm@192: [^String model] rlm@192: (.loadModel rlm@192: (doto (asset-manager) rlm@192: (.registerLoader BlenderModelLoader rlm@192: (into-array String ["blend"]))) model)) rlm@192: rlm@62: rlm@458: rlm@458: (def brick-length 0.48) rlm@458: (def brick-width 0.24) rlm@458: (def brick-height 0.12) rlm@458: (def gravity (Vector3f. 0 -9.81 0)) rlm@458: rlm@458: (import com.jme3.math.Vector2f) rlm@458: (import com.jme3.renderer.queue.RenderQueue$ShadowMode) rlm@458: (import com.jme3.texture.Texture$WrapMode) rlm@458: rlm@458: (defn brick* [position] rlm@458: (println "get brick.") rlm@458: (doto (box brick-length brick-height brick-width rlm@458: :position position :name "brick" rlm@458: :material "Common/MatDefs/Misc/Unshaded.j3md" rlm@458: :texture "Textures/Terrain/BrickWall/BrickWall.jpg" rlm@458: :mass 34) rlm@458: (-> rlm@458: (.getMesh) rlm@458: (.scaleTextureCoordinates (Vector2f. 1 0.5))) rlm@458: (.setShadowMode RenderQueue$ShadowMode/CastAndReceive) rlm@458: ) rlm@458: ) rlm@458: rlm@458: rlm@458: (defn floor* rlm@458: "make a sturdy, unmovable physical floor" rlm@458: [] rlm@458: (box 10 0.1 5 :name "floor" :mass 0 rlm@458: :color ColorRGBA/Gray :position (Vector3f. 0 0 0))) rlm@458: rlm@458: (defn floor* [] rlm@458: (doto (box 10 0.1 5 :name "floor" ;10 0.1 5 ; 240 0.1 240 rlm@458: :material "Common/MatDefs/Misc/Unshaded.j3md" rlm@458: :texture "Textures/BronzeCopper030.jpg" rlm@458: :position (Vector3f. 0 0 0 ) rlm@458: :mass 0) rlm@458: (-> rlm@458: (.getMesh) rlm@458: (.scaleTextureCoordinates (Vector2f. 3 6)));64 64 rlm@458: (-> rlm@458: (.getMaterial) rlm@458: (.getTextureParam "ColorMap") rlm@458: (.getTextureValue) rlm@458: (.setWrap Texture$WrapMode/Repeat)) rlm@458: (.setShadowMode RenderQueue$ShadowMode/Receive) rlm@458: )) rlm@458: rlm@458: rlm@458: (defn brick-wall* [] rlm@458: (let [node (Node. "brick-wall")] rlm@458: (dorun rlm@458: (map rlm@458: (comp #(.attachChild node %) brick*) rlm@458: (for [y (range 10) rlm@458: x (range 4) rlm@458: z (range 1)] rlm@458: (Vector3f. rlm@458: (+ (* 2 x brick-length) rlm@458: (if (even? (+ y z)) rlm@458: (/ brick-length 4) (/ brick-length -4))) rlm@458: (+ (* brick-height (inc (* 2 y)))) rlm@458: (* 2 z brick-width) )))) rlm@458: (.setShadowMode node RenderQueue$ShadowMode/CastAndReceive) rlm@458: node)) rlm@458: rlm@458: 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@306: "returns a sequence of lights appropriate 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@306: "Add lights to a world appropriate 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@363: (sphere 0.4 rlm@363: ;;:texture nil rlm@50: :material "Common/MatDefs/Misc/Unshaded.j3md" rlm@363: :color ColorRGBA/Blue rlm@216: :name "cannonball!" rlm@50: :position rlm@50: (.add (.getLocation camera) rlm@50: (.mult (.getDirection camera) (float 1))) rlm@363: :mass 25)] ;200 0.05 rlm@50: (.setLinearVelocity rlm@50: (.getControl cannon-ball RigidBodyControl) rlm@50: (.mult (.getDirection camera) (float 50))) ;50 rlm@216: (add-element game cannon-ball (if node node (.getRootNode rlm@216: game))) rlm@216: cannon-ball)))) 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@160: (defn tap [obj direction force] rlm@160: (let [control (.getControl obj RigidBodyControl)] rlm@160: (.applyTorque rlm@160: control rlm@160: (.mult (.getPhysicsRotation control) rlm@160: (.mult (.normalize direction) (float force)))))) rlm@160: rlm@160: rlm@160: (defn with-movement rlm@160: [object rlm@160: [up down left right roll-up roll-down :as keyboard] rlm@160: forces rlm@160: [root-node rlm@160: keymap rlm@306: initialization rlm@160: world-loop]] rlm@160: (let [add-keypress rlm@160: (fn [state keymap key] rlm@160: (merge keymap rlm@160: {key rlm@160: (fn [_ pressed?] rlm@160: (reset! state pressed?))})) rlm@160: move-up? (atom false) rlm@160: move-down? (atom false) rlm@160: move-left? (atom false) rlm@160: move-right? (atom false) rlm@160: roll-left? (atom false) rlm@160: roll-right? (atom false) rlm@160: rlm@160: directions [(Vector3f. 0 1 0)(Vector3f. 0 -1 0) rlm@160: (Vector3f. 0 0 1)(Vector3f. 0 0 -1) rlm@160: (Vector3f. -1 0 0)(Vector3f. 1 0 0)] rlm@160: atoms [move-left? move-right? move-up? move-down? rlm@160: roll-left? roll-right?] rlm@160: rlm@160: keymap* (reduce merge rlm@160: (map #(add-keypress %1 keymap %2) rlm@160: atoms rlm@160: keyboard)) rlm@160: rlm@160: splice-loop (fn [] rlm@160: (dorun rlm@160: (map rlm@160: (fn [sym direction force] rlm@160: (if @sym rlm@160: (tap object direction force))) rlm@160: atoms directions forces))) rlm@160: rlm@160: world-loop* (fn [world tpf] rlm@160: (world-loop world tpf) rlm@160: (splice-loop))] rlm@160: [root-node rlm@160: keymap* rlm@306: initialization rlm@160: world-loop*])) rlm@160: rlm@205: (import com.jme3.font.BitmapText) rlm@205: (import com.jme3.scene.control.AbstractControl) rlm@205: (import com.aurellem.capture.IsoTimer) rlm@60: rlm@306: (defn display-dilated-time rlm@205: "Shows the time as it is flowing in the simulation on a HUD display. rlm@205: Useful for making videos." rlm@205: [world timer] rlm@205: (let [font (.loadFont (asset-manager) "Interface/Fonts/Default.fnt") rlm@205: text (BitmapText. font false)] rlm@205: (.setLocalTranslation text 300 (.getLineHeight text) 0) rlm@205: (.addControl rlm@205: text rlm@205: (proxy [AbstractControl] [] rlm@205: (controlUpdate [tpf] rlm@205: (.setText text (format rlm@205: "%.2f" rlm@341: (float (.getTimeInSeconds timer))))) rlm@205: (controlRender [_ _]))) rlm@205: (.attachChild (.getGuiNode world) text))) 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@336: (extend-type ij.ImagePlus rlm@336: Viewable rlm@336: (view [image] rlm@336: (.show image))) rlm@336: rlm@335: (extend-type java.awt.image.BufferedImage rlm@335: Viewable rlm@335: (view rlm@335: [image] rlm@336: (view (ImagePlus. "view-buffered-image" image)))) rlm@336: rlm@336: 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@108: rlm@108: (extend-type Triangle rlm@108: Textual rlm@108: (text [t] rlm@108: (println "Triangle: " \newline (.get1 t) \newline rlm@108: (.get2 t) \newline (.get3 t)))) rlm@108: 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: