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