static/media/js/wisp/terrain2.wisp @ e7bc59b9ebda
Switch to Hugo
| author | Steve Losh <steve@stevelosh.com> |
|---|---|
| date | Fri, 07 Oct 2016 12:48:36 +0000 |
| parents | media/js/wisp/terrain2.wisp@749ec5a03533 |
| children | (none) |
(ns demo (:require [ndarray])) ; Constants ------------------------------------------------------------------- (def width 610) (def height 400) (def wireframe true) (def wireframe-width 1.2) (def terrain-height 50) (def terrain-size 100) ; General Utilities ----------------------------------------------------------- (defmacro when [condition & body] `(if ~condition (do ~@body))) (defmacro when-not [condition & body] `(when (not ~condition) ~@body)) (defmacro -> [& operations] (reduce (fn [form operation] (cons (first operation) (cons form (rest operation)))) (first operations) (rest operations))) (defn inc [x] (+ x 1)) (defn dec [x] (- x 1)) (defmacro do-times [varname limit & body] (let [end (gensym)] `(let [~end ~limit] (loop [~varname 0] (when (< ~varname ~end) ~@body (recur (inc ~varname))))))) (defmacro do-stride [varnames start-form end-form stride-form & body] (let [stride (gensym "stride") start (gensym "start") end (gensym "end") build (fn build [vars] (if (empty? vars) `(do ~@body) (let [varname (first vars)] `(loop [~varname ~start] (when (< ~varname ~end) ~(build (rest vars)) (recur (+ ~varname ~stride)))))))] ; Fix the numbers once outside the nested loops, ; and then build the guts. `(let [~start ~start-form ~end ~end-form ~stride ~stride-form] ~(build varnames)))) (defmacro do-ndarray [vars array-form & body] (let [array-var (gensym "array") build (fn build [vars n] (if (empty? vars) `(do ~@body) `(do-times ~(first vars) (aget (.-shape ~array-var) ~n) ~(build (rest vars) (inc n)))))] `(let [~array-var ~array-form] ~(build vars 0)))) (defmacro do-ndarray-el [element array-form & body] (let [index (gensym "index") array (gensym "array")] `(let [~array ~array-form] (do-times ~index (.-length (.-data ~array)) (let [~element (aget (.-data ~array) ~index)] ~@body))))) (defmacro inc! [place] `(set! ~place (inc ~place))) (defmacro add! [place amount] `(set! ~place (+ ~place ~amount))) (defmacro l [& forms] `(console.log ~@forms)) (defmacro time [& body] (let [start (gensym) end (gensym) result (gensym)] `(let [~start (.getTime (new Date)) ~result (do ~@body) ~end (.getTime (new Date))] (l (+ "Elapsed time: " (- ~end ~start) "ms.")) ~result))) (defn midpoint [a b] (/ (+ a b) 2)) (defn average2 [a b] (/ (+ a b) 2)) (defn average4 [a b c d] (/ (+ a b c d) 4)) (defn safe-average [a b c d] (let [total 0 count 0] (when a (add! total a) (inc! count)) (when b (add! total b) (inc! count)) (when c (add! total c) (inc! count)) (when d (add! total d) (inc! count)) (/ total count))) ; Randomness ------------------------------------------------------------------ (defn rand [] (Math.random)) (defn rand-around-zero [spread] (- (* spread (rand) 2) spread)) (defn jitter [value spread] (+ value (rand-around-zero spread))) ; Heightmap Helpers ----------------------------------------------------------- (defn heightmap-resolution [heightmap] (aget heightmap.shape 0)) (defn heightmap-last-index [heightmap] (dec (heightmap-resolution heightmap))) (defn heightmap-center-index [heightmap] (midpoint 0 (heightmap-last-index heightmap))) (defn heightmap-get [heightmap x y] (.get heightmap x y)) (defn heightmap-get-safe [heightmap x y] (let [last (heightmap-last-index heightmap)] (when (and (<= 0 x last) (<= 0 y last)) (heightmap-get heightmap x y)))) (defn heightmap-set! [heightmap x y val] (.set heightmap x y val)) (defn heightmap-set-if-unset! [heightmap x y val] (when (== 0 (heightmap-get heightmap x y)) (heightmap-set! heightmap x y val))) (defn normalize [heightmap] (let [max (- Infinity) min Infinity] (do-ndarray-el el heightmap (when (< max el) (set! max el)) (when (> min el) (set! min el))) (let [span (- max min)] (do-ndarray [x y] heightmap (heightmap-set! heightmap x y (/ (- (heightmap-get heightmap x y) min) span)))))) (defn make-heightmap [exponent] (let [resolution (+ (Math.pow 2 exponent) 1)] (let [heightmap (ndarray (new Float64Array (* resolution resolution)) [resolution resolution])] (set! heightmap.exponent exponent) (set! heightmap.resolution resolution) (set! heightmap.last (dec resolution)) heightmap))) (defn top-left-corner [heightmap] (let [center (heightmap-center-index heightmap)] (-> heightmap (.lo 0 0) (.hi (inc center) (inc center))))) (defn top-right-corner [heightmap] (let [center (heightmap-center-index heightmap)] (-> heightmap (.lo center 0) (.hi (inc center) (inc center))))) (defn bottom-left-corner [heightmap] (let [center (heightmap-center-index heightmap)] (-> heightmap (.lo 0 center) (.hi (inc center) (inc center))))) (defn bottom-right-corner [heightmap] (let [center (heightmap-center-index heightmap)] (-> heightmap (.lo center center) (.hi (inc center) (inc center))))) ; Midpoint Displacement ------------------------------------------------------- (defn mpd-init-corners [heightmap] (let [last (heightmap-last-index heightmap)] (heightmap-set! heightmap 0 0 (rand)) (heightmap-set! heightmap 0 last (rand)) (heightmap-set! heightmap last 0 (rand)) (heightmap-set! heightmap last last (rand)))) (defn mpd-displace [heightmap spread spread-reduction] (let [last (heightmap-last-index heightmap) c (midpoint 0 last) bottom-left (heightmap-get heightmap 0 0) bottom-right (heightmap-get heightmap last 0) top-left (heightmap-get heightmap 0 last) top-right (heightmap-get heightmap last last) top (average2 top-left top-right) left (average2 bottom-left top-left) bottom (average2 bottom-left bottom-right) right (average2 bottom-right top-right) center (average4 top left bottom right) next-spread (* spread spread-reduction)] (heightmap-set-if-unset! heightmap c 0 (jitter bottom spread)) (heightmap-set-if-unset! heightmap c last (jitter top spread)) (heightmap-set-if-unset! heightmap 0 c (jitter left spread)) (heightmap-set-if-unset! heightmap last c (jitter right spread)) (heightmap-set-if-unset! heightmap c c (jitter center spread)) (when-not (== 3 (heightmap-resolution heightmap)) (mpd-displace (top-left-corner heightmap) next-spread spread-reduction) (mpd-displace (top-right-corner heightmap) next-spread spread-reduction) (mpd-displace (bottom-left-corner heightmap) next-spread spread-reduction) (mpd-displace (bottom-right-corner heightmap) next-spread spread-reduction)))) (defn midpoint-displacement [heightmap] (let [initial-spread 0.3 spread-reduction 0.55] (mpd-init-corners heightmap) (mpd-displace heightmap initial-spread spread-reduction) (normalize heightmap))) ; Three.js Helpers ------------------------------------------------------------ (defn make-directional-light [] (let [light (new THREE.DirectionalLight 0xffffff 1)] (light.position.set 100 0 150) light)) (defn make-camera [] (let [camera (new THREE.PerspectiveCamera 55, (/ width height) 0.1, 1000)] (camera.position.set 0 -100 150) camera)) (defn make-renderer [] (let [renderer (new THREE.WebGLRenderer {:antialias false})] (renderer.setClearColor 0xffffff) (renderer.setSize width height) (renderer.setPixelRatio 2) renderer)) (defn make-geometry [heightmap] (let [resolution (aget heightmap.shape 0) geometry (new THREE.PlaneGeometry terrain-size terrain-size (- resolution 1) (- resolution 1))] geometry)) (defn make-controls [camera renderer] (let [controls (new THREE.TrackballControls camera renderer.domElement)] (set! controls.rotateSpeed 1.4) (set! controls.zoomSpeed 0.5) (set! controls.staticMoving true) (set! controls.dynamicDampingFactor 0.3) controls)) (defn make-plane [geometry] (let [material (new THREE.MeshLambertMaterial {:wireframe wireframe :wireframeLinewidth wireframe-width :color 0x00bb00})] (new THREE.Mesh geometry material))) (defn attach-to-dom [renderer el-name refresh-fn] (let [container (document.getElementById el-name) settings (document.createElement "div") refresh-button (document.createElement "button") button-text (document.createTextNode "Refresh") cancel-scroll (fn [e] (.preventDefault e))] (set! refresh-button.onclick refresh-fn) (set! renderer.domElement.onmousewheel cancel-scroll) (renderer.domElement.addEventListener "MozMousePixelScroll" cancel-scroll false) (.appendChild refresh-button button-text) (.appendChild container renderer.domElement) (.appendChild container settings) (.appendChild settings refresh-button))) (defn update-geometry [geometry heightmap] (loop [i 0] (if (< i geometry.vertices.length) (do (set! (.-z (aget geometry.vertices i)) (* terrain-height (aget (.-data heightmap) i))) (recur (+ i 1))))) (geometry.computeVertexNormals) geometry) ; Main ------------------------------------------------------------------------ (defn make-final [element-id] (def scene (new THREE.Scene)) (scene.add (new THREE.AxisHelper 100)) (def clock (new THREE.Clock)) (def camera (make-camera)) (def renderer (make-renderer)) (def geometry) (def plane) (scene.add (make-directional-light)) (scene.add (new THREE.AmbientLight 0xffffff 0.05)) (defn refresh [] (let [heightmap (make-heightmap 6)] (l "Generating terrain...") (time (midpoint-displacement heightmap)) (l "Rebuilding geometry...") (time (set! geometry (make-geometry heightmap)) (update-geometry geometry heightmap)) (l "Rebuilding plane...") (time (scene.remove plane) (set! plane (make-plane geometry)) (scene.add plane)))) (attach-to-dom renderer element-id refresh) (def controls (make-controls camera renderer)) (defn render [] (let [delta (clock.getDelta)] (requestAnimationFrame render) (.update controls delta) (renderer.render scene camera))) (refresh) (render) nil) (defn run [] (make-final "demo-final")) ($ run) ; vim: lw+=do-times lw+=do-nested :