media/js/wisp/terrain2.wisp @ 61e20cb37f10
Timestamp
author |
Steve Losh <steve@stevelosh.com> |
date |
Mon, 27 Jun 2016 13:33:05 +0000 |
parents |
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 :