From 55c1787a13cf415c617fa044bcef6ccb77118eba Mon Sep 17 00:00:00 2001 From: Broxzier Date: Sun, 12 Feb 2017 19:25:10 +0100 Subject: [PATCH] Add heightmap smoothing Fixed underflow error that was visible for non-tilable maps. Reordered widgets to reflect the same order of the algorithm. Always regenerate the map when a setting is changed. --- src/openrct2/windows/mapgen.c | 117 ++++++++++++++++++++++------------ src/openrct2/world/mapgen.c | 76 +++++++++++----------- src/openrct2/world/mapgen.h | 1 + 3 files changed, 117 insertions(+), 77 deletions(-) diff --git a/src/openrct2/windows/mapgen.c b/src/openrct2/windows/mapgen.c index 20611f003b..f24fb8fb7c 100644 --- a/src/openrct2/windows/mapgen.c +++ b/src/openrct2/windows/mapgen.c @@ -87,8 +87,11 @@ enum { WIDX_SIMPLEX_WALL_TEXTURE, WIDX_HEIGHTMAP_SMOOTH_HEIGHTMAP = TAB_BEGIN, - WIDX_HEIGHTMAP_SMOOTH_TILES, + WIDX_HEIGHTMAP_STRENGTH, + WIDX_HEIGHTMAP_STRENGTH_UP, + WIDX_HEIGHTMAP_STRENGTH_DOWN, WIDX_HEIGHTMAP_NORMALIZE, + WIDX_HEIGHTMAP_SMOOTH_TILES, WIDX_HEIGHTMAP_LOW, WIDX_HEIGHTMAP_LOW_UP, WIDX_HEIGHTMAP_LOW_DOWN, @@ -202,21 +205,25 @@ static rct_widget HeightmapWidgets[] = { { WWT_DROPDOWN_BUTTON, 1, 104, 198, 52, 63, STR_MAPGEN_ACTION_GENERATE, STR_NONE }, // WIDX_GENERATE { WWT_CHECKBOX, 1, 4, 103, 52, 63, STR_MAPGEN_SMOOTH_HEIGHTMAP,STR_NONE }, // WIDX_HEIGHTMAP_SMOOTH_HEIGHTMAP - { WWT_CHECKBOX, 1, 4, 103, 70, 81, STR_MAPGEN_SMOOTH_TILE, STR_NONE }, // WIDX_HEIGHTMAP_SMOOTH_TILES + { WWT_SPINNER, 1, 104, 198, 70, 81, STR_NONE, STR_NONE }, // WIDX_HEIGHTMAP_STRENGTH + { WWT_DROPDOWN_BUTTON, 1, 187, 197, 71, 75, STR_NUMERIC_UP, STR_NONE }, // WIDX_HEIGHTMAP_STRENGTH_UP + { WWT_DROPDOWN_BUTTON, 1, 187, 197, 76, 80, STR_NUMERIC_DOWN, STR_NONE }, // WIDX_HEIGHTMAP_STRENGTH_DOWN { WWT_CHECKBOX, 1, 4, 103, 88, 99, STR_MAPGEN_NORMALIZE, STR_NONE }, // WIDX_HEIGHTMAP_NORMALIZE - { WWT_SPINNER, 1, 104, 198, 106, 117, STR_NONE, STR_NONE }, // WIDX_HEIGHTMAP_LOW - { WWT_DROPDOWN_BUTTON, 1, 187, 197, 107, 111, STR_NUMERIC_UP, STR_NONE }, // WIDX_HEIGHTMAP_LOW_UP - { WWT_DROPDOWN_BUTTON, 1, 187, 197, 112, 116, STR_NUMERIC_DOWN, STR_NONE }, // WIDX_HEIGHTMAP_LOW_DOWN + { WWT_CHECKBOX, 1, 4, 103, 106, 117, STR_MAPGEN_SMOOTH_TILE, STR_NONE }, // WIDX_HEIGHTMAP_SMOOTH_TILES - { WWT_SPINNER, 1, 104, 198, 124, 135, STR_NONE, STR_NONE }, // WIDX_HEIGHTMAP_HIGH - { WWT_DROPDOWN_BUTTON, 1, 187, 197, 125, 129, STR_NUMERIC_UP, STR_NONE }, // WIDX_HEIGHTMAP_HIGH_UP - { WWT_DROPDOWN_BUTTON, 1, 187, 197, 130, 134, STR_NUMERIC_DOWN, STR_NONE }, // WIDX_HEIGHTMAP_HIGH_DOWN + { WWT_SPINNER, 1, 104, 198, 124, 135, STR_NONE, STR_NONE }, // WIDX_HEIGHTMAP_LOW + { WWT_DROPDOWN_BUTTON, 1, 187, 197, 125, 129, STR_NUMERIC_UP, STR_NONE }, // WIDX_HEIGHTMAP_LOW_UP + { WWT_DROPDOWN_BUTTON, 1, 187, 197, 130, 134, STR_NUMERIC_DOWN, STR_NONE }, // WIDX_HEIGHTMAP_LOW_DOWN - { WWT_SPINNER, 1, 104, 198, 142, 153, STR_NONE, STR_NONE }, // WIDX_HEIGHTMAP_WATER_LEVEL - { WWT_DROPDOWN_BUTTON, 1, 187, 197, 143, 147, STR_NUMERIC_UP, STR_NONE }, // WIDX_HEIGHTMAP_WATER_LEVEL_UP - { WWT_DROPDOWN_BUTTON, 1, 187, 197, 148, 152, STR_NUMERIC_DOWN, STR_NONE }, // WIDX_HEIGHTMAP_WATER_LEVEL_DOWN + { WWT_SPINNER, 1, 104, 198, 142, 153, STR_NONE, STR_NONE }, // WIDX_HEIGHTMAP_HIGH + { WWT_DROPDOWN_BUTTON, 1, 187, 197, 143, 147, STR_NUMERIC_UP, STR_NONE }, // WIDX_HEIGHTMAP_HIGH_UP + { WWT_DROPDOWN_BUTTON, 1, 187, 197, 148, 152, STR_NUMERIC_DOWN, STR_NONE }, // WIDX_HEIGHTMAP_HIGH_DOWN + + { WWT_SPINNER, 1, 104, 198, 160, 171, STR_NONE, STR_NONE }, // WIDX_HEIGHTMAP_WATER_LEVEL + { WWT_DROPDOWN_BUTTON, 1, 187, 197, 161, 165, STR_NUMERIC_UP, STR_NONE }, // WIDX_HEIGHTMAP_WATER_LEVEL_UP + { WWT_DROPDOWN_BUTTON, 1, 187, 197, 166, 170, STR_NUMERIC_DOWN, STR_NONE }, // WIDX_HEIGHTMAP_WATER_LEVEL_DOWN { WIDGETS_END }, }; @@ -465,14 +472,17 @@ static uint32 PageEnabledWidgets[] = { (1ULL << WIDX_TAB_4) | (1ULL << WIDX_GENERATE) | (1ULL << WIDX_HEIGHTMAP_SMOOTH_HEIGHTMAP) | - (1ULL << WIDX_HEIGHTMAP_SMOOTH_TILES) | + (1ULL << WIDX_HEIGHTMAP_STRENGTH) | + (1ULL << WIDX_HEIGHTMAP_STRENGTH_UP) | + (1ULL << WIDX_HEIGHTMAP_STRENGTH_DOWN) | (1ULL << WIDX_HEIGHTMAP_NORMALIZE) | - (1ULL << WIDX_HEIGHTMAP_LOW) | - (1ULL << WIDX_HEIGHTMAP_LOW_UP) | - (1ULL << WIDX_HEIGHTMAP_LOW_DOWN) | + (1ULL << WIDX_HEIGHTMAP_SMOOTH_TILES) | (1ULL << WIDX_HEIGHTMAP_HIGH) | (1ULL << WIDX_HEIGHTMAP_HIGH_UP) | (1ULL << WIDX_HEIGHTMAP_HIGH_DOWN) | + (1ULL << WIDX_HEIGHTMAP_LOW) | + (1ULL << WIDX_HEIGHTMAP_LOW_UP) | + (1ULL << WIDX_HEIGHTMAP_LOW_DOWN) | (1ULL << WIDX_HEIGHTMAP_WATER_LEVEL) | (1ULL << WIDX_HEIGHTMAP_WATER_LEVEL_UP) | (1ULL << WIDX_HEIGHTMAP_WATER_LEVEL_DOWN) @@ -518,6 +528,7 @@ static const sint32 TabAnimationLoops[] = { #define BASESIZE_MAX 60 #define WATERLEVEL_MIN 0 #define WATERLEVEL_MAX 54 +#define MAX_SMOOTH_ITERATIONS 20 static void window_mapgen_set_page(rct_window *w, sint32 page); static void window_mapgen_set_pressed_tab(rct_window *w); @@ -549,8 +560,10 @@ static sint32 _simplex_high = 10; static sint32 _simplex_base_freq = 60; static sint32 _simplex_octaves = 4; +static bool _heightmapSmoothMap = false; +static sint32 _heightmapSmoothStrength = 1; static bool _heightmapNormalize = true; -static sint32 _heightmapLow = 10; +static sint32 _heightmapLow = 6; static sint32 _heightmapHigh = 40; rct_window *window_mapgen_open() @@ -1132,50 +1145,48 @@ static void window_mapgen_simplex_paint(rct_window *w, rct_drawpixelinfo *dpi) static void window_mapgen_heightmap_mouseup(rct_window *w, sint32 widgetIndex) { - mapgen_settings mapgenSettings; - switch (widgetIndex) { + // The close and tabs return, so that the map doesn't get regenerated case WIDX_CLOSE: window_close(w); - break; + return; case WIDX_TAB_1: case WIDX_TAB_2: case WIDX_TAB_3: case WIDX_TAB_4: window_mapgen_set_page(w, widgetIndex - WIDX_TAB_1); - break; + return; + + // Page widgets case WIDX_GENERATE: - mapgenSettings.water_level = _waterLevel; - mapgenSettings.smooth = widget_is_pressed(w, WIDX_HEIGHTMAP_SMOOTH_TILES); - mapgenSettings.smooth_height_map = widget_is_pressed(w, WIDX_HEIGHTMAP_SMOOTH_HEIGHTMAP); - mapgenSettings.normalize_height = _heightmapNormalize; - mapgenSettings.simplex_low = _heightmapLow; - mapgenSettings.simplex_high = _heightmapHigh; - mapgen_generate_from_heightmap(&mapgenSettings); - gfx_invalidate_screen(); + // Handled at the end of this function break; case WIDX_HEIGHTMAP_SMOOTH_HEIGHTMAP: - widget_set_checkbox_value(w, WIDX_HEIGHTMAP_SMOOTH_HEIGHTMAP, !widget_is_pressed(w, WIDX_HEIGHTMAP_SMOOTH_HEIGHTMAP)); + _heightmapSmoothMap = !_heightmapSmoothMap; + widget_set_checkbox_value(w, WIDX_HEIGHTMAP_SMOOTH_HEIGHTMAP, _heightmapSmoothMap); + widget_set_enabled(w, WIDX_HEIGHTMAP_STRENGTH, _heightmapSmoothMap); + widget_set_enabled(w, WIDX_HEIGHTMAP_STRENGTH_UP, _heightmapSmoothMap); + widget_set_enabled(w, WIDX_HEIGHTMAP_STRENGTH_DOWN, _heightmapSmoothMap); widget_invalidate(w, WIDX_HEIGHTMAP_SMOOTH_HEIGHTMAP); + widget_invalidate(w, WIDX_HEIGHTMAP_STRENGTH); break; - case WIDX_HEIGHTMAP_SMOOTH_TILES: - widget_set_checkbox_value(w, WIDX_HEIGHTMAP_SMOOTH_TILES, !widget_is_pressed(w, WIDX_HEIGHTMAP_SMOOTH_TILES)); - widget_invalidate(w, WIDX_HEIGHTMAP_SMOOTH_TILES); + case WIDX_HEIGHTMAP_STRENGTH_UP: + _heightmapSmoothStrength = min(_heightmapSmoothStrength + 1, MAX_SMOOTH_ITERATIONS); + widget_invalidate(w, WIDX_HEIGHTMAP_STRENGTH); + break; + case WIDX_HEIGHTMAP_STRENGTH_DOWN: + _heightmapSmoothStrength = max(_heightmapSmoothStrength - 1, 0); + widget_invalidate(w, WIDX_HEIGHTMAP_STRENGTH); break; case WIDX_HEIGHTMAP_NORMALIZE: _heightmapNormalize = !_heightmapNormalize; widget_set_checkbox_value(w, WIDX_HEIGHTMAP_NORMALIZE, _heightmapNormalize); widget_invalidate(w, WIDX_HEIGHTMAP_NORMALIZE); break; - case WIDX_HEIGHTMAP_HIGH_UP: - _heightmapHigh = min(_heightmapHigh + 1, 142); - window_invalidate(w); - break; - case WIDX_HEIGHTMAP_HIGH_DOWN: - _heightmapHigh = max(_heightmapHigh - 1, 2 + 1); - _heightmapLow = min(_heightmapLow, _heightmapHigh - 1); - window_invalidate(w); + case WIDX_HEIGHTMAP_SMOOTH_TILES: + widget_set_checkbox_value(w, WIDX_HEIGHTMAP_SMOOTH_TILES, !widget_is_pressed(w, WIDX_HEIGHTMAP_SMOOTH_TILES)); + widget_invalidate(w, WIDX_HEIGHTMAP_SMOOTH_TILES); break; case WIDX_HEIGHTMAP_LOW_UP: _heightmapLow = min(_heightmapLow + 1, 142 - 1); @@ -1186,6 +1197,15 @@ static void window_mapgen_heightmap_mouseup(rct_window *w, sint32 widgetIndex) _heightmapLow = max(_heightmapLow - 1, 2); window_invalidate(w); break; + case WIDX_HEIGHTMAP_HIGH_UP: + _heightmapHigh = min(_heightmapHigh + 1, 142); + window_invalidate(w); + break; + case WIDX_HEIGHTMAP_HIGH_DOWN: + _heightmapHigh = max(_heightmapHigh - 1, 2 + 1); + _heightmapLow = min(_heightmapLow, _heightmapHigh - 1); + window_invalidate(w); + break; case WIDX_HEIGHTMAP_WATER_LEVEL_UP: _waterLevel = min(_waterLevel + 2, 54); window_invalidate(w); @@ -1195,6 +1215,19 @@ static void window_mapgen_heightmap_mouseup(rct_window *w, sint32 widgetIndex) window_invalidate(w); break; } + + // Always regenerate the map after one of the page widgets has been changed + mapgen_settings mapgenSettings; + mapgenSettings.water_level = _waterLevel; + mapgenSettings.smooth = widget_is_pressed(w, WIDX_HEIGHTMAP_SMOOTH_TILES); + mapgenSettings.smooth_height_map = _heightmapSmoothMap; + mapgenSettings.smooth_strength = _heightmapSmoothStrength; + mapgenSettings.normalize_height = _heightmapNormalize; + mapgenSettings.simplex_low = _heightmapLow; + mapgenSettings.simplex_high = _heightmapHigh; + mapgen_generate_from_heightmap(&mapgenSettings); + gfx_invalidate_screen(); + } static void window_mapgen_heightmap_invalidate(rct_window *w) @@ -1218,6 +1251,10 @@ static void window_mapgen_heightmap_paint(rct_window *w, rct_drawpixelinfo *dpi) window_draw_widgets(w, dpi); window_mapgen_draw_tab_images(dpi, w); + // Smooth strength label and value + gfx_draw_string_left(dpi, STR_MAPGEN_SMOOTH_STRENGTH, NULL, w->colours[1], w->x + 5, w->y + w->widgets[WIDX_HEIGHTMAP_STRENGTH].top + 1); + gfx_draw_string_left(dpi, STR_COMMA16, &_heightmapSmoothStrength, w->colours[1], w->x + w->widgets[WIDX_HEIGHTMAP_STRENGTH].left + 1, w->y + w->widgets[WIDX_HEIGHTMAP_STRENGTH].top + 1); + // Low label and value gfx_draw_string_left(dpi, STR_MAPGEN_SIMPLEX_NOISE_LOW_, NULL, COLOUR_BLACK, w->x + 5, w->y + w->widgets[WIDX_HEIGHTMAP_LOW].top + 1); gfx_draw_string_left(dpi, STR_COMMA16, &_heightmapLow, w->colours[1], w->x + w->widgets[WIDX_HEIGHTMAP_LOW].left + 1, w->y + w->widgets[WIDX_HEIGHTMAP_LOW].top + 1); diff --git a/src/openrct2/world/mapgen.c b/src/openrct2/world/mapgen.c index 35b9292570..3fb8a8682e 100644 --- a/src/openrct2/world/mapgen.c +++ b/src/openrct2/world/mapgen.c @@ -767,53 +767,55 @@ static void mapgen_simplex(mapgen_settings *settings) #pragma region Heightmap /** - * Applies box Gaussian blur to the surface + * Applies box blur to the surface N times */ -void mapgen_smooth_heightmap(SDL_Surface *surface) +void mapgen_smooth_heightmap(SDL_Surface *surface, sint32 strength) { SDL_LockSurface(surface); - // Apply box Gaussian blur - const uint32 width = surface->w; - const uint32 height = surface->h; - const uint32 numChannels = surface->format->BytesPerPixel; + const sint32 width = surface->w; + const sint32 height = surface->h; + const sint32 numChannels = surface->format->BytesPerPixel; const size_t pitch = surface->pitch; // Create buffer to store one channel uint8 *dest = (uint8*)malloc(width * height); uint8 *src = surface->pixels; - // Calculate box Gaussian blur value to all pixels of the surface - for (uint32 y = 0; y < height; y++) + for (sint32 i = 0; i < strength; i++) { - for (uint32 x = 0; x < width; x++) + // Calculate box blur value to all pixels of the surface + for (sint32 y = 0; y < height; y++) { - uint32 sum = 0; - - // Loop over neightbour pixels, all of them have the same weight - for (sint8 offsetX = -1; offsetX <= 1; offsetX++) + for (sint32 x = 0; x < width; x++) { - for (sint8 offsetY = -1; offsetY <= 1; offsetY++) + uint32 heightSum = 0; + + // Loop over neightbour pixels, all of them have the same weight + for (sint8 offsetX = -1; offsetX <= 1; offsetX++) { - // Clamp x and y so they stay within the image - // This assumes the height map is not tiled, and increases the weight of the edges - const sint32 readX = clamp(x + offsetX, 0, width - 1); - const sint32 readY = clamp(y + offsetY, 0, height - 1); - sum += src[readX * numChannels + readY * pitch]; + for (sint8 offsetY = -1; offsetY <= 1; offsetY++) + { + // Clamp x and y so they stay within the image + // This assumes the height map is not tiled, and increases the weight of the edges + const sint32 readX = clamp(x + offsetX, 0, width - 1); + const sint32 readY = clamp(y + offsetY, 0, height - 1); + heightSum += src[readX * numChannels + readY * pitch]; + } } + + // Take average + dest[x + y * width] = heightSum / 9; } - - // Take average - dest[x + y * width] = sum / 9; } - } - // Now apply the Gaussian blur to the real pixels - for (uint32 y = 0; y < height; y++) - { - for (uint32 x = 0; x < width; x++) + // Now apply the blur to the source pixels + for (sint32 y = 0; y < height; y++) { - src[x * numChannels + y * pitch] = dest[x + y * width]; + for (sint32 x = 0; x < width; x++) + { + src[x * numChannels + y * pitch] = dest[x + y * width]; + } } } @@ -832,8 +834,8 @@ void mapgen_generate_from_heightmap(mapgen_settings *settings) return; } - const uint32 width = bitmap->w; - const uint32 height = bitmap->h; + const sint32 width = bitmap->w; + const sint32 height = bitmap->h; const uint8 numChannels = bitmap->format->BytesPerPixel; map_init(width + 2); // + 2 for the black tiles around the map @@ -844,7 +846,7 @@ void mapgen_generate_from_heightmap(mapgen_settings *settings) if (settings->smooth_height_map) { // Smooth height map - mapgen_smooth_heightmap(bitmap); + mapgen_smooth_heightmap(bitmap, settings->smooth_strength); } SDL_LockSurface(bitmap); @@ -854,9 +856,9 @@ void mapgen_generate_from_heightmap(mapgen_settings *settings) // Get highest and lowest pixel value maxValue = 0; minValue = 0xff; - for (uint32 y = 0; y < height; y++) + for (sint32 y = 0; y < height; y++) { - for (uint32 x = 0; x < width; x++) + for (sint32 x = 0; x < width; x++) { uint8 value = src[x * numChannels + y * bitmap->pitch]; maxValue = max(maxValue, value); @@ -878,9 +880,9 @@ void mapgen_generate_from_heightmap(mapgen_settings *settings) const uint8 rangeIn = maxValue - minValue; const uint8 rangeOut = settings->simplex_high - settings->simplex_low; - for (uint32 y = 0; y < height; y++) + for (sint32 y = 0; y < height; y++) { - for (uint32 x = 0; x < width; x++) + for (sint32 x = 0; x < width; x++) { // The x and y axis are flipped in the world, so this uses y for x and x for y. rct_map_element *const surfaceElement = map_get_surface_element_at(y + 1, x + 1); @@ -910,9 +912,9 @@ void mapgen_generate_from_heightmap(mapgen_settings *settings) while (true) { sint32 numTilesChanged = 0; - for (uint32 y = 1; y <= height; y++) + for (sint32 y = 1; y <= height; y++) { - for (uint32 x = 1; x <= width; x++) + for (sint32 x = 1; x <= width; x++) { numTilesChanged += tile_smooth(x, y); } diff --git a/src/openrct2/world/mapgen.h b/src/openrct2/world/mapgen.h index 86c173dc7f..9755b2f4ca 100644 --- a/src/openrct2/world/mapgen.h +++ b/src/openrct2/world/mapgen.h @@ -37,6 +37,7 @@ typedef struct mapgen_settings { // Height map settings bool smooth; bool smooth_height_map; + uint32 smooth_strength; bool normalize_height; } mapgen_settings;