1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-25 07:44:38 +01:00
Files
OpenRCT2/src/openrct2/world/MapHelpers.cpp
2018-11-01 13:53:50 +01:00

311 lines
13 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2018 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#include "MapHelpers.h"
#include "Map.h"
#include "Surface.h"
#include <algorithm>
/**
* Not perfect, this still leaves some particular tiles unsmoothed.
*/
int32_t map_smooth(int32_t l, int32_t t, int32_t r, int32_t b)
{
int32_t i, x, y, count, doubleCorner, raisedLand = 0;
uint8_t highest, cornerHeights[4];
TileElement *tileElement, *tileElement2;
for (y = t; y < b; y++)
{
for (x = l; x < r; x++)
{
tileElement = map_get_surface_element_at(x, y);
tileElement->AsSurface()->SetSlope(TILE_ELEMENT_SLOPE_FLAT);
// Raise to edge height - 2
highest = tileElement->base_height;
highest = std::max(highest, map_get_surface_element_at(x - 1, y + 0)->base_height);
highest = std::max(highest, map_get_surface_element_at(x + 1, y + 0)->base_height);
highest = std::max(highest, map_get_surface_element_at(x + 0, y - 1)->base_height);
highest = std::max(highest, map_get_surface_element_at(x + 0, y + 1)->base_height);
if (tileElement->base_height < highest - 2)
{
raisedLand = 1;
tileElement->base_height = tileElement->clearance_height = highest - 2;
}
// Check corners
doubleCorner = -1;
cornerHeights[0] = map_get_surface_element_at(x - 1, y - 1)->base_height;
cornerHeights[1] = map_get_surface_element_at(x + 1, y - 1)->base_height;
cornerHeights[2] = map_get_surface_element_at(x + 1, y + 1)->base_height;
cornerHeights[3] = map_get_surface_element_at(x - 1, y + 1)->base_height;
highest = tileElement->base_height;
for (i = 0; i < 4; i++)
highest = std::max(highest, cornerHeights[i]);
if (highest >= tileElement->base_height + 4)
{
count = 0;
int32_t canCompensate = 1;
for (i = 0; i < 4; i++)
if (cornerHeights[i] == highest)
{
count++;
// Check if surrounding corners aren't too high. The current tile
// can't compensate for all the height differences anymore if it has
// the extra height slope.
int32_t highestOnLowestSide;
switch (i)
{
default:
case 0:
highestOnLowestSide = std::max(
map_get_surface_element_at(x + 1, y)->base_height,
map_get_surface_element_at(x, y + 1)->base_height);
break;
case 1:
highestOnLowestSide = std::max(
map_get_surface_element_at(x - 1, y)->base_height,
map_get_surface_element_at(x, y + 1)->base_height);
break;
case 2:
highestOnLowestSide = std::max(
map_get_surface_element_at(x - 1, y)->base_height,
map_get_surface_element_at(x, y - 1)->base_height);
break;
case 3:
highestOnLowestSide = std::max(
map_get_surface_element_at(x + 1, y)->base_height,
map_get_surface_element_at(x, y - 1)->base_height);
break;
}
if (highestOnLowestSide > tileElement->base_height)
{
tileElement->base_height = tileElement->clearance_height = highestOnLowestSide;
raisedLand = 1;
canCompensate = 0;
}
}
if (count == 1 && canCompensate)
{
if (tileElement->base_height < highest - 4)
{
tileElement->base_height = tileElement->clearance_height = highest - 4;
raisedLand = 1;
}
if (cornerHeights[0] == highest && cornerHeights[2] <= cornerHeights[0] - 4)
doubleCorner = 0;
else if (cornerHeights[1] == highest && cornerHeights[3] <= cornerHeights[1] - 4)
doubleCorner = 1;
else if (cornerHeights[2] == highest && cornerHeights[0] <= cornerHeights[2] - 4)
doubleCorner = 2;
else if (cornerHeights[3] == highest && cornerHeights[1] <= cornerHeights[3] - 4)
doubleCorner = 3;
}
else
{
if (tileElement->base_height < highest - 2)
{
tileElement->base_height = tileElement->clearance_height = highest - 2;
raisedLand = 1;
}
}
}
if (doubleCorner != -1)
{
uint8_t slope = tileElement->AsSurface()->GetSlope() | TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT;
switch (doubleCorner)
{
case 0:
slope |= TILE_ELEMENT_SLOPE_N_CORNER_DN;
break;
case 1:
slope |= TILE_ELEMENT_SLOPE_W_CORNER_DN;
break;
case 2:
slope |= TILE_ELEMENT_SLOPE_S_CORNER_DN;
break;
case 3:
slope |= TILE_ELEMENT_SLOPE_E_CORNER_DN;
break;
}
tileElement->AsSurface()->SetSlope(slope);
}
else
{
uint8_t slope = tileElement->AsSurface()->GetSlope();
// Corners
tileElement2 = map_get_surface_element_at(x + 1, y + 1);
if (tileElement2->base_height > tileElement->base_height)
slope |= TILE_ELEMENT_SLOPE_N_CORNER_UP;
tileElement2 = map_get_surface_element_at(x - 1, y + 1);
if (tileElement2->base_height > tileElement->base_height)
slope |= TILE_ELEMENT_SLOPE_W_CORNER_UP;
tileElement2 = map_get_surface_element_at(x + 1, y - 1);
if (tileElement2->base_height > tileElement->base_height)
slope |= TILE_ELEMENT_SLOPE_E_CORNER_UP;
tileElement2 = map_get_surface_element_at(x - 1, y - 1);
if (tileElement2->base_height > tileElement->base_height)
slope |= TILE_ELEMENT_SLOPE_S_CORNER_UP;
// Sides
tileElement2 = map_get_surface_element_at(x + 1, y + 0);
if (tileElement2->base_height > tileElement->base_height)
slope |= TILE_ELEMENT_SLOPE_NE_SIDE_UP;
tileElement2 = map_get_surface_element_at(x - 1, y + 0);
if (tileElement2->base_height > tileElement->base_height)
slope |= TILE_ELEMENT_SLOPE_SW_SIDE_UP;
tileElement2 = map_get_surface_element_at(x + 0, y - 1);
if (tileElement2->base_height > tileElement->base_height)
slope |= TILE_ELEMENT_SLOPE_SE_SIDE_UP;
tileElement2 = map_get_surface_element_at(x + 0, y + 1);
if (tileElement2->base_height > tileElement->base_height)
slope |= TILE_ELEMENT_SLOPE_NW_SIDE_UP;
// Raise
if (slope == TILE_ELEMENT_SLOPE_ALL_CORNERS_UP)
{
slope = TILE_ELEMENT_SLOPE_FLAT;
tileElement->base_height = tileElement->clearance_height += 2;
}
tileElement->AsSurface()->SetSlope(slope);
}
}
}
return raisedLand;
}
/**
* Raises the corners based on the height offset of neighbour tiles.
* This does not change the base height, unless all corners have been raised.
* @returns 0 if no edits were made, 1 otherwise
*/
int32_t tile_smooth(int32_t x, int32_t y)
{
TileElement* const surfaceElement = map_get_surface_element_at(x, y);
// +-----+-----+-----+
// | W | NW | N |
// | 2 | 1 | 0 |
// +-----+-----+-----+
// | SW | _ | NE |
// | 4 | | 3 |
// +-----+-----+-----+
// | S | SE | E |
// | 7 | 6 | 5 |
// +-----+-----+-----+
union
{
int32_t baseheight[8];
struct
{
int32_t N;
int32_t NW;
int32_t W;
int32_t NE;
int32_t SW;
int32_t E;
int32_t SE;
int32_t S;
};
} neighbourHeightOffset = {};
// Find the neighbour base heights
for (int32_t index = 0, y_offset = -1; y_offset <= 1; y_offset++)
{
for (int32_t x_offset = -1; x_offset <= 1; x_offset++)
{
// Skip self
if (y_offset == 0 && x_offset == 0)
continue;
// Get neighbour height. If the element is not valid (outside of map) assume the same height
TileElement* neighbour_element = map_get_surface_element_at(x + x_offset, y + y_offset);
neighbourHeightOffset.baseheight[index] = neighbour_element ? neighbour_element->base_height
: surfaceElement->base_height;
// Make the height relative to the current surface element
neighbourHeightOffset.baseheight[index] -= surfaceElement->base_height;
index++;
}
}
// Count number from the three tiles that is currently higher
int8_t thresholdW = std::clamp(neighbourHeightOffset.SW, 0, 1) + std::clamp(neighbourHeightOffset.W, 0, 1)
+ std::clamp(neighbourHeightOffset.NW, 0, 1);
int8_t thresholdN = std::clamp(neighbourHeightOffset.NW, 0, 1) + std::clamp(neighbourHeightOffset.N, 0, 1)
+ std::clamp(neighbourHeightOffset.NE, 0, 1);
int8_t thresholdE = std::clamp(neighbourHeightOffset.NE, 0, 1) + std::clamp(neighbourHeightOffset.E, 0, 1)
+ std::clamp(neighbourHeightOffset.SE, 0, 1);
int8_t thresholdS = std::clamp(neighbourHeightOffset.SE, 0, 1) + std::clamp(neighbourHeightOffset.S, 0, 1)
+ std::clamp(neighbourHeightOffset.SW, 0, 1);
uint8_t slope = TILE_ELEMENT_SLOPE_FLAT;
slope |= (thresholdW >= 1) ? SLOPE_W_THRESHOLD_FLAGS : 0;
slope |= (thresholdN >= 1) ? SLOPE_N_THRESHOLD_FLAGS : 0;
slope |= (thresholdE >= 1) ? SLOPE_E_THRESHOLD_FLAGS : 0;
slope |= (thresholdS >= 1) ? SLOPE_S_THRESHOLD_FLAGS : 0;
// Set diagonal when three corners (one corner down) have been raised, and the middle one can be raised one more
if ((slope == TILE_ELEMENT_SLOPE_W_CORNER_DN && neighbourHeightOffset.W >= 4)
|| (slope == TILE_ELEMENT_SLOPE_S_CORNER_DN && neighbourHeightOffset.S >= 4)
|| (slope == TILE_ELEMENT_SLOPE_E_CORNER_DN && neighbourHeightOffset.E >= 4)
|| (slope == TILE_ELEMENT_SLOPE_N_CORNER_DN && neighbourHeightOffset.N >= 4))
{
slope |= TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT;
}
// Check if the calculated slope is the same already
uint8_t currentSlope = surfaceElement->AsSurface()->GetSlope();
if (currentSlope == slope)
{
return 0;
}
if ((slope & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP) == TILE_ELEMENT_SLOPE_ALL_CORNERS_UP)
{
// All corners are raised, raise the entire tile instead.
surfaceElement->AsSurface()->SetSlope(TILE_ELEMENT_SLOPE_FLAT);
surfaceElement->base_height = (surfaceElement->clearance_height += 2);
uint8_t waterHeight = surfaceElement->AsSurface()->GetWaterHeight() * 2;
if (waterHeight <= surfaceElement->base_height)
{
surfaceElement->AsSurface()->SetWaterHeight(0);
}
}
else
{
// Apply the slope to this tile
surfaceElement->AsSurface()->SetSlope(slope);
// Set correct clearance height
if (slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT)
surfaceElement->clearance_height = surfaceElement->base_height + 4;
else if (slope & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP)
surfaceElement->clearance_height = surfaceElement->base_height + 2;
}
return 1;
}