static/media/js/wisp/terrain1.wisp @ 6d36ebb4ecee

Timestamp
author Steve Losh <steve@stevelosh.com>
date Mon, 19 Dec 2016 12:40:27 -0500
parents e7bc59b9ebda
children (none)
; 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 do-times [varname limit & body]
  (let [end (gensym)]
    `(let [~end ~limit]
       (loop [~varname 0]
         (when (< ~varname ~end)
           ~@body
           (recur (+ 1 ~varname)))))))

(defmacro do-nested [xname yname width & body]
  (let [iterations (gensym)]
    `(let [~iterations ~width]
       (do-times ~xname ~iterations
         (do-times ~yname ~iterations
           ~@body)))))

(defmacro inc! [place]
  `(set! ~place (+ ~place 1)))

(defmacro add! [place amount]
  `(set! ~place (+ ~place ~amount)))

(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 l [v]
  (console.log v))


(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)))


(defn rand []
  (Math.random))

(defn rand-around-zero [spread]
  (- (* spread (rand) 2) spread))


(defn jitter [value spread]
  (+ value (rand-around-zero spread)))


; Heightmap Helpers -----------------------------------------------------------
(defmacro heightmap-get [hm x y]
  `(aget ~hm (+ (* ~y (.-resolution ~hm)) ~x)))

(defn heightmap-get-safe [hm x y]
  (when (and (<= 0 x hm.last)
             (<= 0 y hm.last))
    (heightmap-get hm x y)))

(defmacro heightmap-set! [hm x y val]
  `(set! (heightmap-get ~hm ~x ~y) ~val))


(defn normalize [hm]
  (let [max (- Infinity)
        min Infinity]
    (do-times i hm.length
      (let [el (aget hm i)]
        (when (< max el) (set! max el))
        (when (> min el) (set! min el))))
    (let [span (- max min)]
      (do-times i hm.length
        (set! (aget hm i)
          (/ (- (aget hm i) min)
             span))))))

(defn zero-heightmap [heightmap]
  (do-times i heightmap.length
    (set! (aget heightmap i) 0.0))
  heightmap)

(defn make-heightmap [exponent]
  (let [resolution (+ 1 (Math.pow 2 exponent))]
    (l (+ "Creating " resolution " by " resolution " heightmap..."))
    (def heightmap
      (new Array (* resolution resolution)))
    (set! heightmap.resolution resolution)
    (set! heightmap.exponent exponent)
    (set! heightmap.last (- resolution 1))
    (zero-heightmap heightmap)))


; Random Noise ----------------------------------------------------------------
(defn random-noise [heightmap]
  (do-times i heightmap.length
    (set! (aget heightmap i) (rand))))


; Midpoint Displacement -------------------------------------------------------
(defn mpd-init-corners [heightmap]
  (heightmap-set! heightmap 0 0 (rand))
  (heightmap-set! heightmap 0 heightmap.last (rand))
  (heightmap-set! heightmap heightmap.last 0 (rand))
  (heightmap-set! heightmap heightmap.last heightmap.last (rand)))

(defn mpd-displace [heightmap lx rx by ty spread]
  (let [cx (midpoint lx rx)
        cy (midpoint by ty)

        bottom-left (heightmap-get heightmap lx by)
        bottom-right (heightmap-get heightmap rx by)
        top-left (heightmap-get heightmap lx ty)
        top-right (heightmap-get heightmap rx ty)

        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)]
    (heightmap-set! heightmap cx by (jitter bottom spread))
    (heightmap-set! heightmap cx ty (jitter top spread))
    (heightmap-set! heightmap lx cy (jitter left spread))
    (heightmap-set! heightmap rx cy (jitter right spread))
    (heightmap-set! heightmap cx cy (jitter center spread))))

(defn midpoint-displacement [heightmap]
  (mpd-init-corners heightmap)
  ; (mpd-displace heightmap 0 heightmap.last 0 heightmap.last 0.1)
  (loop [iter 0
         spread 0.3]
    (when (< iter heightmap.exponent)
      (let [chunks (Math.pow 2 iter)
            chunk-width (/ (- heightmap.resolution 1) chunks)]
        (do-nested xchunk ychunk chunks
          (let [left-x (* chunk-width xchunk)
                right-x (+ left-x chunk-width)
                bottom-y (* chunk-width ychunk)
                top-y (+ bottom-y chunk-width)]
            (mpd-displace heightmap left-x right-x bottom-y top-y spread))))
      (recur (+ 1 iter) (* spread 0.5))))
  (normalize heightmap))


(defn midpoint-displacement-final [heightmap]
  (mpd-init-corners heightmap)
  ; (let [spread ])
  (loop [iter 0
         spread (+ 0 (.val ($ "#input-starting-spread")))]
    (when (< iter heightmap.exponent)
      (let [chunks (Math.pow 2 iter)
            chunk-width (/ (- heightmap.resolution 1) chunks)]
        (do-nested xchunk ychunk chunks
          (let [left-x (* chunk-width xchunk)
                right-x (+ left-x chunk-width)
                bottom-y (* chunk-width ychunk)
                top-y (+ bottom-y chunk-width)]
            (mpd-displace heightmap left-x right-x bottom-y top-y spread))))
      (recur (+ 1 iter) (* spread
                           (+ 0 (.val ($ "#input-spread-reduction")))))))
  (normalize heightmap))


(defn mpd-displace-d2 [heightmap lx rx by ty spread]
  (let [cx (midpoint lx rx)
        cy (midpoint by ty)

        bottom-left (heightmap-get heightmap lx by)
        bottom-right (heightmap-get heightmap rx by)
        top-left (heightmap-get heightmap lx ty)
        top-right (heightmap-get heightmap rx ty)

        top (average2 top-left top-right)
        left (average2 bottom-left top-left)
        bottom (average2 bottom-left bottom-right)
        right (average2 bottom-right top-right)]
    (heightmap-set! heightmap cx by (jitter bottom spread))
    (heightmap-set! heightmap cx ty (jitter top spread))
    (heightmap-set! heightmap lx cy (jitter left spread))
    (heightmap-set! heightmap rx cy (jitter right spread))))

(defn midpoint-displacement-d1 [heightmap]
  (mpd-init-corners heightmap))

(defn midpoint-displacement-d2 [heightmap]
  (mpd-init-corners heightmap)
  (mpd-displace-d2 heightmap
                   0 heightmap.last
                   0 heightmap.last
                   0.1))

(defn midpoint-displacement-d3 [heightmap]
  (mpd-init-corners heightmap)
  (mpd-displace heightmap
                0 heightmap.last
                0 heightmap.last
                0.1))



; 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 [geometry (new THREE.PlaneGeometry
                      terrain-size
                      terrain-size
                      (- heightmap.resolution 1)
                      (- heightmap.resolution 1))]
    (set! geometry.dynamic true)
    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]
  (do-times i geometry.vertices.length
    (set! (.-z (aget geometry.vertices i))
      (* terrain-height (aget heightmap i))))
  (set! geometry.verticesNeedUpdate true)
  (geometry.computeFaceNormals)
  (geometry.computeVertexNormals)
  (geometry.computeMorphNormals)
  geometry)


; Main ------------------------------------------------------------------------
(defn make-demo [element-id algorithm size]
  (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 (make-geometry (make-heightmap size)))

  (scene.add (make-directional-light))
  (scene.add (new THREE.AmbientLight 0xffffff 0.05))
  (scene.add (make-plane geometry))

  (defn refresh []
    (l "Refreshing ========================================")
    (let [heightmap (make-heightmap size)]
      (l "Generating terrain...")
      (time (algorithm heightmap))
      (l "Refreshing geometry...")
      (time (update-geometry geometry heightmap))
      (l "Done!")))

  (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)))

  (render)

  nil)

(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 heightmap (make-heightmap (.val ($ "#input-exponent"))))
  (def geometry (make-geometry heightmap))
  (def plane (make-plane geometry))

  (scene.add (make-directional-light))
  (scene.add (new THREE.AmbientLight 0xffffff 0.05))
  (scene.add plane)

  (defn refresh []
    (l "Refreshing ========================================")
    (scene.remove plane)
    (set! heightmap (make-heightmap (.val ($ "#input-exponent"))))
    (set! geometry (make-geometry heightmap))
    (set! plane (make-plane geometry))
    (scene.add plane)
    (l "Generating terrain...")
    (time (midpoint-displacement-final heightmap))
    (l "Refreshing geometry...")
    (time (update-geometry geometry heightmap))
    (l "Done!"))

  (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)))

  (render)

  nil)


(defn run []
  (make-demo "demo-random" random-noise 7)
  (make-demo "demo-mpd-1" midpoint-displacement-d1 2)
  (make-demo "demo-mpd-2" midpoint-displacement-d2 2)
  (make-demo "demo-mpd-3" midpoint-displacement-d3 2)
  (make-demo "demo-mpd-4" midpoint-displacement 3)
  (make-final "demo-final")
  ; (make-demo "demo-midpoint" midpoint-displacement)
  ; (make-demo "demo-diamond" diamond-square)

  )

($ run)


; vim: lw+=do-times lw+=do-nested :