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