001  (ns cc.journeyman.the-great-game.world.heightmap
002    "Functions dealing with the tessellated multi-layer heightmap."
003      (:require [clojure.math.numeric-tower :refer [expt sqrt]]
004                [mw-engine.core :refer []]
005                [mw-engine.heightmap :refer [apply-heightmap]]
006                [mw-engine.utils :refer [get-cell in-bounds? map-world]]
007                [cc.journeyman.the-great-game.utils :refer [value-or-default]]))
008  
009  ;; It's not at all clear to me yet what the workflow for getting a MicroWorld
010  ;; map into The Great Game, and whether it passes through Walkmap to get here.
011  ;; This file as currently written assumes it doesn't.
012  
013  ;; It's utterly impossible to hold a whole continent at one metre scale in
014  ;; memory at one time. So we have to be able to regenerate high resolution
015  ;; surfaces from much lower resolution heightmaps.
016  ;;
017  ;; Thus to reproduce a segment of surface at a particular level of detail,
018  ;; we:
019  ;; 1. load the base heightmap into a grid (see
020  ;;    `mw-engine.heightmap/apply-heightmap`);
021  ;; 2. scale the base hightmap to kilometre scale (see `scale-grid`);
022  ;; 3. exerpt the portion of that that we want to reproduce (see `exerpt-grid`);
023  ;; 4. interpolate that grid to get the resolution we require (see
024  ;;    `interpolate-grid`);
025  ;; 5. create an appropriate purturbation grid from the noise map(s) for the
026  ;;    same coordinates to break up the smooth interpolation;
027  ;; 6. sum the altitudes of the two grids.
028  ;;
029  ;; In production this will have to be done **very** fast!
030  
031  (def ^:dynamic *base-map* "resources/maps/heightmap.png")
032  (def ^:dynamic *noise-map* "resources/maps/noise.png")
033  
034  (defn scale-grid
035    "multiply all `:x` and `:y` values in this `grid` by this `n`."
036    [grid n]
037    (map-world grid (fn [w c x] (assoc c :x (* (:x c) n) :y (* (:y c) n)))))
038  
039  
040  
041  ;; Each of the east-west curve and the north-south curve are of course two
042  ;; dimensional curves; the east-west curve is in the :x/:z plane and the
043  ;; north-south curve is in the :y/:z plane (except, perhaps unwisely,
044  ;; we've been using :altitude to label the :z plane). We have a library
045  ;; function `walkmap.edge/intersection2d`, but as currently written it
046  ;; can only find intersections in :x/:y plane.
047  ;;
048  ;; TODO: rewrite the function so that it can use arbitrary coordinates.
049  ;; AFTER TRYING: OK, there are too many assumptions about the way that
050  ;; function is written to allow for easy rotation. TODO: think!
051  
052  (defn interpolate-altitude
053    "Return the altitude of the point at `x-offset`, `y-offset` within this
054    `cell` having this `src-width`, taken from this `grid`."
055    [cell grid src-width x-offset y-offset ]
056    (let [c-alt (:altitude cell)
057          n-alt (or (:altitude (get-cell grid (:x cell) (dec (:y cell)))) c-alt)
058          w-alt (or (:altitude (get-cell grid (inc (:x cell)) (:y cell))) c-alt)
059          s-alt (or (:altitude (get-cell grid (:x cell) (inc (:y cell)))) c-alt)
060          e-alt (or (:altitude (get-cell grid (dec (:x cell)) (:y cell))) c-alt)]
061      ;; TODO: construct two curves (arcs of circles good enough for now)
062      ;; n-alt...c-alt...s-alt and e-alt...c-alt...w-alt;
063      ;; then interpolate x-offset along e-alt...c-alt...w-alt and y-offset
064      ;; along n-alt...c-alt...s-alt;
065      ;; then return the average of the two
066  
067      0))
068  
069  (defn interpolate-cell
070    "Construct a grid (array of arrays) of cells each of width `target-width`
071    from this `cell`, of width `src-width`, taken from this `grid`"
072    [cell grid src-width target-width]
073    (let [offsets (map #(* target-width %) (range (/ src-width target-width)))]
074      (into
075        []
076        (map
077          (fn [r]
078            (into
079              []
080              (map
081                (fn [c]
082                  (assoc cell
083                    :x (+ (:x cell) c)
084                    :y (+ (:y cell) r)
085                    :altitude (interpolate-altitude cell grid src-width c r)))
086                offsets)))
087          offsets))))
088  
089  (defn interpolate-grid
090    "Return a grid interpolated from this `grid` of rows, cols given scaling
091    from this `src-width` to this `target-width`"
092    [grid src-width target-width]
093    (reduce
094      concat
095      (into
096        []
097        (map
098          (fn [row]
099            (reduce
100              (fn [g1 g2]
101                (into [] (map #(into [] (concat %1 %2)) g1 g2)))
102              (into [] (map #(interpolate-cell % grid src-width target-width) row))))
103          grid))))
104  
105  (defn excerpt-grid
106    "Return that section of this `grid` where the `:x` co-ordinate of each cell
107    is greater than or equal to this `x-offset`, the `:y` co-ordinate is greater
108    than or equal to this `y-offset`, whose width is not greater than this
109    `width`, and whose height is not greater than this `height`."
110    [grid x-offset y-offset width height]
111    (into
112      []
113      (remove
114        nil?
115        (map
116          (fn [row]
117            (when
118              (and
119                (>= (:y (first row)) y-offset)
120                (< (:y (first row)) (+ y-offset height)))
121              (into
122                []
123                (remove
124                  nil?
125                  (map
126                    (fn [cell]
127                      (when
128                        (and
129                          (>= (:x cell) x-offset)
130                          (< (:x cell) (+ x-offset width)))
131                        cell))
132                    row)))))
133         grid))))
134  
135  (defn get-surface
136    "Return, as a vector of vectors of cells represented as Clojure maps, a
137    segment of surface from this `base-map` as modified by this
138    `noise-map` at this `cell-size` starting at this `x-offset` and `y-offset`
139    and having this `width` and `height`.
140  
141    If `base-map` and `noise-map` are not supplied, the bindings of `*base-map*`
142    and `*noise-map*` will be used, respectively.
143  
144    `base-map` and `noise-map` may be passed either as strings, assumed to be
145    file paths of PNG files, or as MicroWorld style world arrays. It is assumed
146    that one pixel in `base-map` represents one square kilometre in the game
147    world. It is assumed that `cell-size`, `x-offset`, `y-offset`, `width` and
148    `height` are integer numbers of metres."
149    ([cell-size x-offset y-offset width height]
150     (get-surface *base-map* *noise-map* cell-size x-offset y-offset width height))
151    ([base-map noise-map cell-size x-offset y-offset width height]
152     (let [b (if (seq? base-map) base-map (scale-grid (apply-heightmap base-map) 1000))
153           n (if (seq? noise-map) noise-map (apply-heightmap noise-map))]
154       (if (and (in-bounds? b x-offset y-offset)
155                (in-bounds? b (+ x-offset width) (+ y-offset height)))
156         b ;; actually do stuff
157         (throw (Exception. "Surface out of bounds for map.")))
158       )))
159