├── .gitignore
├── LICENSE.md
├── Makefile
├── README.md
└── src
├── base.cpp
├── base.h
├── blur.cpp
├── blur.h
├── cmdline.h
├── heightmap.cpp
├── heightmap.h
├── main.cpp
├── stb_image.h
├── stb_image_write.h
├── stl.cpp
├── stl.h
├── triangulator.cpp
└── triangulator.h
/.gitignore:
--------------------------------------------------------------------------------
1 | /bin
2 | /build
3 | /hmm
4 |
5 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (C) 2019 Michael Fogleman
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | #### PROJECT SETTINGS ####
2 | # The name of the executable to be created
3 | BIN_NAME := hmm
4 | # Compiler used
5 | C ?= g++
6 | # Extension of source files used in the project
7 | SRC_EXT = cpp
8 | # Path to the source directory, relative to the makefile
9 | SRC_PATH = src
10 | # General compiler flags
11 | COMPILE_FLAGS = -std=c++11 -flto -O3 -Wall -Wextra -Wno-sign-compare -march=native
12 | # Additional release-specific flags
13 | RCOMPILE_FLAGS = -D NDEBUG
14 | # Additional debug-specific flags
15 | DCOMPILE_FLAGS = -D DEBUG
16 | # Add additional include paths
17 | INCLUDES = -I $(SRC_PATH)
18 | # General linker settings
19 | LINK_FLAGS = -flto -O3
20 | # Additional release-specific linker settings
21 | RLINK_FLAGS =
22 | # Additional debug-specific linker settings
23 | DLINK_FLAGS =
24 | # Destination directory, like a jail or mounted system
25 | DESTDIR = /
26 | # Install path (bin/ is appended automatically)
27 | INSTALL_PREFIX = usr/local
28 | #### END PROJECT SETTINGS ####
29 |
30 | # Generally should not need to edit below this line
31 |
32 | # Shell used in this makefile
33 | # bash is used for 'echo -en'
34 | SHELL = /bin/bash
35 | # Clear built-in rules
36 | .SUFFIXES:
37 | # Programs for installation
38 | INSTALL = install
39 | INSTALL_PROGRAM = $(INSTALL)
40 | INSTALL_DATA = $(INSTALL) -m 644
41 |
42 | # Verbose option, to output compile and link commands
43 | export V := false
44 | export CMD_PREFIX := @
45 | ifeq ($(V),true)
46 | CMD_PREFIX :=
47 | endif
48 |
49 | # Combine compiler and linker flags
50 | release: export CFLAGS := $(CFLAGS) $(COMPILE_FLAGS) $(RCOMPILE_FLAGS)
51 | release: export LDFLAGS := $(LDFLAGS) $(LINK_FLAGS) $(RLINK_FLAGS)
52 | debug: export CFLAGS := $(CFLAGS) $(COMPILE_FLAGS) $(DCOMPILE_FLAGS)
53 | debug: export LDFLAGS := $(LDFLAGS) $(LINK_FLAGS) $(DLINK_FLAGS)
54 |
55 | # Build and output paths
56 | release: export BUILD_PATH := build/release
57 | release: export BIN_PATH := bin/release
58 | debug: export BUILD_PATH := build/debug
59 | debug: export BIN_PATH := bin/debug
60 | install: export BIN_PATH := bin/release
61 |
62 | # Find all source files in the source directory, sorted by most
63 | # recently modified
64 | SOURCES = $(shell find $(SRC_PATH)/ -name '*.$(SRC_EXT)' \
65 | | sort -k 1nr | cut -f2-)
66 | # fallback in case the above fails
67 | rwildcard = $(foreach d, $(wildcard $1*), $(call rwildcard,$d/,$2) \
68 | $(filter $(subst *,%,$2), $d))
69 | ifeq ($(SOURCES),)
70 | SOURCES := $(call rwildcard, $(SRC_PATH)/, *.$(SRC_EXT))
71 | endif
72 |
73 | # Set the object file names, with the source directory stripped
74 | # from the path, and the build path prepended in its place
75 | OBJECTS = $(SOURCES:$(SRC_PATH)/%.$(SRC_EXT)=$(BUILD_PATH)/%.o)
76 | # Set the dependency files that will be used to add header dependencies
77 | DEPS = $(OBJECTS:.o=.d)
78 |
79 | # Macros for timing compilation
80 | TIME_FILE = $(dir $@).$(notdir $@)_time
81 | START_TIME = date '+%s' > $(TIME_FILE)
82 | END_TIME = read st < $(TIME_FILE) ; \
83 | $(RM) $(TIME_FILE) ; \
84 | st=$$((`date '+%s'` - $$st - 86400)) ; \
85 | echo `date -u -d @$$st '+%H:%M:%S'`
86 |
87 | # Version macros
88 | # Comment/remove this section to remove versioning
89 | USE_VERSION := false
90 | # If this isn't a git repo or the repo has no tags, git describe will return non-zero
91 | ifeq ($(shell git describe > /dev/null 2>&1 ; echo $$?), 0)
92 | USE_VERSION := true
93 | VERSION := $(shell git describe --tags --long --dirty --always | \
94 | sed 's/v\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)-\?.*-\([0-9]*\)-\(.*\)/\1 \2 \3 \4 \5/g')
95 | VERSION_MAJOR := $(word 1, $(VERSION))
96 | VERSION_MINOR := $(word 2, $(VERSION))
97 | VERSION_PATCH := $(word 3, $(VERSION))
98 | VERSION_REVISION := $(word 4, $(VERSION))
99 | VERSION_HASH := $(word 5, $(VERSION))
100 | VERSION_STRING := \
101 | "$(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH).$(VERSION_REVISION)-$(VERSION_HASH)"
102 | override CFLAGS := $(CFLAGS) \
103 | -D VERSION_MAJOR=$(VERSION_MAJOR) \
104 | -D VERSION_MINOR=$(VERSION_MINOR) \
105 | -D VERSION_PATCH=$(VERSION_PATCH) \
106 | -D VERSION_REVISION=$(VERSION_REVISION) \
107 | -D VERSION_HASH=\"$(VERSION_HASH)\"
108 | endif
109 |
110 | # Standard, non-optimized release build
111 | .PHONY: release
112 | release: dirs
113 | ifeq ($(USE_VERSION), true)
114 | @echo "Beginning release build v$(VERSION_STRING)"
115 | else
116 | @echo "Beginning release build"
117 | endif
118 | @$(MAKE) all --no-print-directory
119 |
120 | # Debug build for gdb debugging
121 | .PHONY: debug
122 | debug: dirs
123 | ifeq ($(USE_VERSION), true)
124 | @echo "Beginning debug build v$(VERSION_STRING)"
125 | else
126 | @echo "Beginning debug build"
127 | endif
128 | @$(MAKE) all --no-print-directory
129 |
130 | # Create the directories used in the build
131 | .PHONY: dirs
132 | dirs:
133 | @echo "Creating directories"
134 | @mkdir -p $(dir $(OBJECTS))
135 | @mkdir -p $(BIN_PATH)
136 |
137 | # Installs to the set path
138 | .PHONY: install
139 | install:
140 | @echo "Installing to $(DESTDIR)$(INSTALL_PREFIX)/bin"
141 | @$(INSTALL_PROGRAM) $(BIN_PATH)/$(BIN_NAME) $(DESTDIR)$(INSTALL_PREFIX)/bin
142 |
143 | # Uninstalls the program
144 | .PHONY: uninstall
145 | uninstall:
146 | @echo "Removing $(DESTDIR)$(INSTALL_PREFIX)/bin/$(BIN_NAME)"
147 | @$(RM) $(DESTDIR)$(INSTALL_PREFIX)/bin/$(BIN_NAME)
148 |
149 | # Removes all build files
150 | .PHONY: clean
151 | clean:
152 | @echo "Deleting $(BIN_NAME) symlink"
153 | @$(RM) $(BIN_NAME)
154 | @echo "Deleting directories"
155 | @$(RM) -r build
156 | @$(RM) -r bin
157 |
158 | # Main rule, checks the executable and symlinks to the output
159 | all: $(BIN_PATH)/$(BIN_NAME)
160 | @echo "Making symlink: $(BIN_NAME) -> $<"
161 | @$(RM) $(BIN_NAME)
162 | @ln -s $(BIN_PATH)/$(BIN_NAME) $(BIN_NAME)
163 |
164 | # Link the executable
165 | $(BIN_PATH)/$(BIN_NAME): $(OBJECTS)
166 | @echo "Linking: $@"
167 | $(CMD_PREFIX)$(C) $(OBJECTS) $(LDFLAGS) -o $@
168 |
169 | # Add dependency files, if they exist
170 | -include $(DEPS)
171 |
172 | # Source file rules
173 | # After the first compilation they will be joined with the rules from the
174 | # dependency files to provide header dependencies
175 | $(BUILD_PATH)/%.o: $(SRC_PATH)/%.$(SRC_EXT)
176 | @echo "Compiling: $< -> $@"
177 | $(CMD_PREFIX)$(C) $(CFLAGS) $(INCLUDES) -MP -MMD -c $< -o $@
178 |
179 | .PHONY: run
180 | run: release
181 | time ./$(BIN_NAME)
182 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # hmm
2 |
3 | `hmm` is a heightmap meshing utility.
4 |
5 | If you've done any 3D game development, 3D printing, or other such things,
6 | you've likely wanted to convert a grayscale heightmap image into a 3D mesh. The
7 | naive way is pretty simple but generates huge meshes with millions of
8 | triangles. After hacking my way through various solutions over the years, I
9 | finally decided I needed to write a good tool for this purpose.
10 |
11 | `hmm` is a modern implementation of a nice algorithm from the 1995 paper
12 | [Fast Polygonal Approximation of Terrains and Height Fields](http://mgarland.org/files/papers/scape.pdf)
13 | by Garland and Heckbert. The meshes produced by `hmm` satisfy the Delaunay
14 | condition and can satisfy a specified maximal error or maximal number of
15 | triangles or vertices. It's also very fast.
16 |
17 | 
18 |
19 | ### Dependencies
20 |
21 | - C++11 or higher
22 | - [glm](https://glm.g-truc.net/0.9.9/index.html)
23 |
24 | ### Installation
25 |
26 | ```bash
27 | brew install glm # on macOS
28 | sudo apt-get install libglm-dev # on Ubuntu / Debian
29 |
30 | git clone https://github.com/fogleman/hmm.git
31 | cd hmm
32 | make
33 | make install
34 | ```
35 |
36 | ### Usage
37 |
38 | ```
39 | heightmap meshing utility
40 | usage: hmm --zscale=float [options] ... infile outfile.stl
41 | options:
42 | -z, --zscale z scale relative to x & y (float)
43 | -x, --zexagg z exaggeration (float [=1])
44 | -e, --error maximum triangulation error (float [=0.001])
45 | -t, --triangles maximum number of triangles (int [=0])
46 | -p, --points maximum number of vertices (int [=0])
47 | -b, --base solid base height (float [=0])
48 | --level auto level input to full grayscale range
49 | --invert invert heightmap
50 | --blur gaussian blur sigma (int [=0])
51 | --gamma gamma curve exponent (float [=0])
52 | --border-size border size in pixels (int [=0])
53 | --border-height border z height (float [=1])
54 | --normal-map path to write normal map png (string [=])
55 | --shade-path path to write hillshade png (string [=])
56 | --shade-alt hillshade light altitude (float [=45])
57 | --shade-az hillshade light azimuth (float [=0])
58 | -q, --quiet suppress console output
59 | -?, --help print this message
60 | ```
61 |
62 | `hmm` supports a variety of file formats like PNG, JPG, etc. for the input
63 | heightmap. The output is always a binary STL file. The only other required
64 | parameter is `-z`, which specifies how much to scale the Z axis in the output
65 | mesh.
66 |
67 | ```bash
68 | $ hmm input.png output.stl -z ZSCALE
69 | ```
70 |
71 | You can also provide a maximal allowed error, number of triangles, or number of
72 | vertices. (If multiple are specified, the first one reached is used.)
73 |
74 | ```bash
75 | $ hmm input.png output.stl -z 100 -e 0.001 -t 1000000
76 | ```
77 |
78 | ### Visual Guide
79 |
80 | Click on the image below to see examples of various command line arguments. You
81 | can try these examples yourself with this heightmap: [gale.png](https://www.michaelfogleman.com/static/hmm/guide/gale.png).
82 |
83 | 
84 |
85 | ### Z Scale
86 |
87 | The required `-z` parameter defines the distance between a fully black pixel
88 | and a fully white pixel in the vertical Z axis, with units equal to one pixel
89 | width or height. For example, if each pixel in the heightmap represented a 1x1
90 | meter square area, and the vertical range of the heightmap was 100 meters, then
91 | `-z 100` should be used.
92 |
93 | ### Z Exaggeration
94 |
95 | The `-x` parameter is simply an extra multiplier on top of the provided Z
96 | scale. It is provided as a convenience so you don't have to do multiplication
97 | in your head just to exaggerate by, e.g. 2x, since Z scales are often derived
98 | from real world data and can have strange values like 142.2378.
99 |
100 | ### Max Error
101 |
102 | The `-e` parameter defines the maximum allowed error in the output mesh, as a
103 | percentage of the total mesh height. For example, if `-e 0.01` is used, then no
104 | pixel will have an error of more than 1% of the distance between a fully black
105 | pixel and a fully white pixel. This means that for an 8-bit input image, an
106 | error of `e = 1 / 256 ~= 0.0039` will ensure that no pixel has an error greater
107 | than one full grayscale unit. (It may still be desirable to use a lower value
108 | like `0.5 / 256`.)
109 |
110 | ### Base Height
111 |
112 | When the `-b` option is used to create a solid mesh, it defines the height of
113 | the base before the lowest part of the heightmesh appears, as a percentage of
114 | the heightmap's height. For example, if `-z 100 -b 0.5` were used, then the
115 | final mesh would be about 150 units tall (if a fully white pixel exists in the
116 | input).
117 |
118 | ### Border
119 |
120 | A border can be added to the mesh with the `--border-size` and
121 | `--border-height` flags. The heightmap will be padded by `border-size` pixels
122 | before triangulating. The (pre-scaled) Z value of the border can be set with
123 | `border-height` which defaults to 1.
124 |
125 | ### Filters
126 |
127 | A Gaussian blur can be applied with the `--blur` flag. This is particularly
128 | useful for noisy images.
129 |
130 | The heightmap can be inverted with the `--invert` flag. This is useful for
131 | [lithophanes](https://en.wikipedia.org/wiki/Lithophane).
132 |
133 | The heightmap can be auto-leveled with the `--level` flag. This will stretch
134 | the grayscale values to use the entire black => white range.
135 |
136 | A gamma curve can be applied to the heightmap with the `--gamma` flag. This
137 | applies `x = x ^ gamma` to each pixel, where `x` is in [0, 1].
138 |
139 | ### Normal Maps
140 |
141 | A full resolution [normal map](https://en.wikipedia.org/wiki/Normal_mapping)
142 | can be generated with the `--normal-map` argument. This will save a normal map
143 | as an RGB PNG to the specified path. This is useful for rendering higher
144 | resolution bumps and details while using a lower resolution triangle mesh.
145 |
146 | ### Hillshade Images
147 |
148 | A grayscale hillshade image can be generated with the `--shade-path` argument.
149 | The altitude and azimuth of the light source can be changed with the
150 | `--shade-alt` and `--shade-az` arguments, which default to 45 degrees in
151 | altitude and 0 degrees from north (up).
152 |
153 | ### Performance
154 |
155 | Performance depends a lot on the amount of detail in the heightmap, but here
156 | are some figures for an example heightmap of a [40x40 kilometer area centered
157 | on Mount Everest](https://i.imgur.com/1i9djJ0.jpg). Various heightmap
158 | resolutions and permitted max errors are shown. Times computed on a 2018 13"
159 | MacBook Pro (2.7 GHz Intel Core i7).
160 |
161 | #### Runtime in Seconds
162 |
163 | | Image Size / Error | e=0.01 | e=0.001 | e=0.0005 | e=0.0001 |
164 | | ---: | ---: | ---: | ---: | ---: |
165 | | 9490 x 9490 px (90.0 MP) | 6.535 | 13.102 | 19.394 | 58.949 |
166 | | 4745 x 4745 px (22.5 MP) | 1.867 | 4.903 | 8.886 | 33.327 |
167 | | 2373 x 2373 px (5.6 MP) | 0.559 | 2.353 | 4.930 | 14.243 |
168 | | 1187 x 1187 px (1.4 MP) | 0.168 | 1.021 | 1.961 | 3.709 |
169 |
170 | #### Number of Triangles Output
171 |
172 | | Image Size / Error | e=0.01 | e=0.001 | e=0.0005 | e=0.0001 |
173 | | ---: | ---: | ---: | ---: | ---: |
174 | | 9490 x 9490 px (90.0 MP) | 33,869 | 1,084,972 | 2,467,831 | 14,488,022 |
175 | | 4745 x 4745 px (22.5 MP) | 33,148 | 1,032,263 | 2,323,772 | 11,719,491 |
176 | | 2373 x 2373 px (5.6 MP) | 31,724 | 935,787 | 1,979,227 | 6,561,070 |
177 | | 1187 x 1187 px (1.4 MP) | 27,275 | 629,352 | 1,160,079 | 2,347,713 |
178 |
179 | ### TODO
180 |
181 | - reconstruct grayscale image?
182 | - better error handling, especially for file I/O
183 | - better overflow handling - what's the largest supported heightmap?
184 | - mesh validation?
185 |
--------------------------------------------------------------------------------
/src/base.cpp:
--------------------------------------------------------------------------------
1 | #include "base.h"
2 |
3 | #define GLM_ENABLE_EXPERIMENTAL
4 |
5 | #include
6 | #include