001  (ns ^{:doc "Functions to apply a heightmap to a world.
002              
003              Heightmaps are considered only as greyscale images, so colour is redundent
004              (will be ignored). Darker shades are higher."
005        :author "Simon Brooke"}
006    mw-engine.heightmap
007    (:require [mikera.image.core :refer [load-image filter-image]]
008              [mikera.image.filters :as filters]
009              [mw-engine.utils :refer [get-int get-neighbours map-world]]
010              [mw-engine.world :refer [make-world]]))
011  
012  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
013  ;;;;
014  ;;;; mw-engine: the state/transition engine of MicroWorld.
015  ;;;;
016  ;;;; This program is free software; you can redistribute it and/or
017  ;;;; modify it under the terms of the GNU General Public License
018  ;;;; as published by the Free Software Foundation; either version 2
019  ;;;; of the License, or (at your option) any later version.
020  ;;;;
021  ;;;; This program is distributed in the hope that it will be useful,
022  ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
023  ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
024  ;;;; GNU General Public License for more details.
025  ;;;;
026  ;;;; You should have received a copy of the GNU General Public License
027  ;;;; along with this program; if not, write to the Free Software
028  ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
029  ;;;; USA.
030  ;;;;
031  ;;;; Copyright (C) 2014 Simon Brooke
032  ;;;;
033  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
034  
035  (defn abs 
036    "Prior to Clojure 1.11, there is no native `abs` function. Afterwards, there
037     is."
038    [n] 
039    (Math/abs n))
040  
041  (defn tag-property
042    "Set the value of this `property` of this cell from the corresponding pixel of this `heightmap`.
043     If the heightmap you supply is smaller than the world, this will break.
044  
045     * `world` not actually used, but present to enable this function to be
046       passed as an argument to `mw-engine.utils/map-world`, q.v.
047     * `cell` a cell, as discussed in world.clj, q.v. Alternatively, a map;
048     * `property` the property (normally a keyword) whose value will be set on the cell.
049     * `heightmap` an (ideally) greyscale image, whose x and y dimensions should
050       exceed those of the world of which the `cell` forms part."
051    ([_ cell property heightmap]
052      (tag-property cell property heightmap))
053    ([cell property heightmap]
054      (merge cell
055             {property
056              (+ (get-int cell property)
057                 (- 256
058                    (abs
059                      (mod
060                        (.getRGB heightmap
061                          (get-int cell :x)
062                          (get-int cell :y)) 256))))})))
063  
064  (defn tag-gradient
065    "Set the `gradient` property of this `cell` of this `world` to the difference in
066     altitude between its highest and lowest neghbours."
067    [world cell]
068    (let [heights (remove nil? (map :altitude (get-neighbours world cell)))
069          highest (cond (empty? heights) 0 ;; shouldn't happen
070                    :else (apply max heights))
071          lowest (cond (empty? heights) 0 ;; shouldn't
072                   :else (apply min heights))
073          gradient (- highest lowest)]
074      (merge cell {:gradient gradient})))
075  
076  (defn tag-gradients
077    "Set the `gradient` property of each cell in this `world` to the difference in
078     altitude between its highest and lowest neghbours."
079    [world]
080    (map-world world tag-gradient))
081  
082  (defn tag-altitude
083    "Set the altitude of this cell from the corresponding pixel of this heightmap.
084     If the heightmap you supply is smaller than the world, this will break.
085  
086     * `world` not actually used, but present to enable this function to be
087       passed as an argument to `mw-engine.utils/map-world`, q.v.;
088     * `cell` a cell, as discussed in world.clj, q.v. Alternatively, a map;
089     * `heightmap` an (ideally) greyscale image, whose x and y dimensions should
090       exceed those of the world of which the `cell` forms part."
091    ([_ cell heightmap]
092      (tag-property cell :altitude heightmap))
093    ([cell heightmap]
094      (tag-property cell :altitude heightmap)))
095  
096  (defn apply-heightmap
097    "Apply the image file loaded from this path to this world, and return a world whose
098    altitudes are modified (added to) by the altitudes in the heightmap. It is assumed that
099    the heightmap is at least as large in x and y dimensions as the world. Note that, in
100    addition to setting the `:altitude` of each cell, this function also sets the `:gradient`.
101  
102    * `world` a world, as defined in `world.clj`, q.v.; if world is not supplied,
103      a world the size of the heightmap will be created;
104    * `imagepath` a file path or URL which indicates an (ideally greyscale) image file."
105    ([world imagepath]
106      (let [heightmap (filter-image
107                       (load-image imagepath)
108                        (filters/grayscale))]
109        (map-world
110          (map-world world tag-altitude (list heightmap))
111          tag-gradient)))
112     ([imagepath]
113      (let [heightmap (filter-image
114                        (load-image imagepath)
115                        (filters/grayscale))
116            world (make-world (.getWidth heightmap) (.getHeight heightmap))]
117        (map-world
118          (map-world world tag-altitude (list heightmap))
119          tag-gradient))))
120  
121  (defn apply-valuemap
122    "Generalised from apply-heightmap, set an arbitrary property on each cell
123     of this `world` from the values in this (ideally greyscale) heightmap.
124  
125     * `world` a world, as defined in `world.clj`, q.v.;
126     * `imagepath` a file path or URL which indicates an (ideally greyscale) image file;
127     * `property` the property of each cell whose value should be added to from the
128        intensity of the corresponding cell of the image."
129    [world imagepath property]
130      (let [heightmap (filter-image
131                        (load-image imagepath)
132                        (filters/grayscale))]
133        (map-world world tag-property (list property heightmap))))