terrain.cpp 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168
  1. #include "terrain.h"
  2. #include <algorithm>
  3. #include <cmath>
  4. #include <cstddef>
  5. #include <cstdint>
  6. #include <math.h>
  7. #include <numbers>
  8. #include <vector>
  9. namespace {
  10. constexpr float k_deg_to_rad = std::numbers::pi_v<float> / 180.0F;
  11. constexpr int k_hill_ramp_extra_steps = 18;
  12. constexpr float k_hill_ramp_steepness_exponent = 0.90F;
  13. constexpr float k_entry_ramp_width = 3.0F;
  14. constexpr float k_width_falloff_padding = 0.75F;
  15. constexpr float k_entry_bowl_exponent = 2.0F;
  16. constexpr float k_entry_base_width_scale = 1.55F;
  17. constexpr float k_entry_top_width_scale = 0.70F;
  18. constexpr float k_entry_outward_steps_fraction = 0.50F;
  19. constexpr int k_entry_outward_steps_min = 6;
  20. constexpr int k_entry_outward_steps_max = 16;
  21. constexpr float k_entry_mid_dip_strength = 0.40F;
  22. constexpr float k_entry_mid_depth_strength = 0.34F;
  23. constexpr float k_entry_toe_height_fraction = 0.01F;
  24. constexpr float k_walkable_width_threshold = 0.38F;
  25. inline auto hashCoords(int x, int z, std::uint32_t seed) -> std::uint32_t {
  26. std::uint32_t const ux = static_cast<std::uint32_t>(x) * 73856093U;
  27. std::uint32_t const uz = static_cast<std::uint32_t>(z) * 19349663U;
  28. std::uint32_t const s = seed * 83492791U + 0x9e3779b9U;
  29. return ux ^ uz ^ s;
  30. }
  31. inline auto hashToFloat01(std::uint32_t h) -> float {
  32. h ^= h >> 17;
  33. h *= 0xed5ad4bbU;
  34. h ^= h >> 11;
  35. h *= 0xac4c1b51U;
  36. h ^= h >> 15;
  37. h *= 0x31848babU;
  38. h ^= h >> 14;
  39. return (h & 0x00FFFFFFU) / float(0x01000000);
  40. }
  41. inline auto valueNoise2D(float x, float z, std::uint32_t seed) -> float {
  42. int const ix0 = static_cast<int>(std::floor(x));
  43. int const iz0 = static_cast<int>(std::floor(z));
  44. int const ix1 = ix0 + 1;
  45. int const iz1 = iz0 + 1;
  46. float const tx = x - static_cast<float>(ix0);
  47. float const tz = z - static_cast<float>(iz0);
  48. float const n00 = hashToFloat01(hashCoords(ix0, iz0, seed));
  49. float const n10 = hashToFloat01(hashCoords(ix1, iz0, seed));
  50. float const n01 = hashToFloat01(hashCoords(ix0, iz1, seed));
  51. float const n11 = hashToFloat01(hashCoords(ix1, iz1, seed));
  52. float const nx0 = n00 * (1.0F - tx) + n10 * tx;
  53. float const nx1 = n01 * (1.0F - tx) + n11 * tx;
  54. return nx0 * (1.0F - tz) + nx1 * tz;
  55. }
  56. } // namespace
  57. namespace Game::Map {
  58. TerrainHeightMap::TerrainHeightMap(int width, int height, float tile_size)
  59. : m_width(width), m_height(height), m_tile_size(tile_size) {
  60. const int count = width * height;
  61. m_heights.resize(count, 0.0F);
  62. m_terrain_types.resize(count, TerrainType::Flat);
  63. m_hillEntrances.resize(count, false);
  64. m_hillWalkable.resize(count, false);
  65. }
  66. void TerrainHeightMap::buildFromFeatures(
  67. const std::vector<TerrainFeature> &features) {
  68. std::fill(m_heights.begin(), m_heights.end(), 0.0F);
  69. std::fill(m_terrain_types.begin(), m_terrain_types.end(), TerrainType::Flat);
  70. std::fill(m_hillEntrances.begin(), m_hillEntrances.end(), false);
  71. std::fill(m_hillWalkable.begin(), m_hillWalkable.end(), false);
  72. const float grid_half_width = m_width * 0.5F - 0.5F;
  73. const float grid_half_height = m_height * 0.5F - 0.5F;
  74. for (const auto &feature : features) {
  75. const float grid_center_x =
  76. (feature.center_x / m_tile_size) + grid_half_width;
  77. const float grid_center_z =
  78. (feature.center_z / m_tile_size) + grid_half_height;
  79. const float grid_radius = std::max(feature.radius / m_tile_size, 1.0F);
  80. if (feature.type == TerrainType::Mountain) {
  81. const float major_radius =
  82. std::max(grid_radius * 1.8F, grid_radius + 3.0F);
  83. const float minor_radius = std::max(grid_radius * 0.22F, 0.8F);
  84. const float bound = std::max(major_radius, minor_radius) + 2.0F;
  85. const int min_x = std::max(0, int(std::floor(grid_center_x - bound)));
  86. const int max_x =
  87. std::min(m_width - 1, int(std::ceil(grid_center_x + bound)));
  88. const int min_z = std::max(0, int(std::floor(grid_center_z - bound)));
  89. const int max_z =
  90. std::min(m_height - 1, int(std::ceil(grid_center_z + bound)));
  91. const float angle_rad = feature.rotationDeg * k_deg_to_rad;
  92. const float cos_a = std::cos(angle_rad);
  93. const float sin_a = std::sin(angle_rad);
  94. for (int z = min_z; z <= max_z; ++z) {
  95. for (int x = min_x; x <= max_x; ++x) {
  96. const float local_x = float(x) - grid_center_x;
  97. const float local_z = float(z) - grid_center_z;
  98. const float rotated_x = local_x * cos_a + local_z * sin_a;
  99. const float rotated_z = -local_x * sin_a + local_z * cos_a;
  100. const float norm = std::sqrt(
  101. (rotated_x * rotated_x) / (major_radius * major_radius) +
  102. (rotated_z * rotated_z) / (minor_radius * minor_radius));
  103. if (norm <= 1.0F) {
  104. float const blend = std::clamp(1.0F - norm, 0.0F, 1.0F);
  105. float height = feature.height * std::pow(blend, 3.5F);
  106. if (blend > 0.92F) {
  107. height = feature.height;
  108. }
  109. if (height > 0.01F) {
  110. int const idx = indexAt(x, z);
  111. if (height > m_heights[idx]) {
  112. m_heights[idx] = height;
  113. m_terrain_types[idx] = TerrainType::Mountain;
  114. }
  115. }
  116. }
  117. }
  118. }
  119. continue;
  120. }
  121. if (feature.type == TerrainType::Hill) {
  122. const float grid_width = std::max(feature.width / m_tile_size, 1.0F);
  123. const float grid_depth = std::max(feature.depth / m_tile_size, 1.0F);
  124. const float plateau_width = std::max(1.5F, grid_width * 0.45F);
  125. const float plateau_depth = std::max(1.5F, grid_depth * 0.45F);
  126. const float slope_width = std::max(plateau_width + 1.5F, grid_width);
  127. const float slope_depth = std::max(plateau_depth + 1.5F, grid_depth);
  128. const float max_extent = std::max(slope_width, slope_depth);
  129. const int min_x =
  130. std::max(0, int(std::floor(grid_center_x - max_extent - 1.0F)));
  131. const int max_x = std::min(
  132. m_width - 1, int(std::ceil(grid_center_x + max_extent + 1.0F)));
  133. const int min_z =
  134. std::max(0, int(std::floor(grid_center_z - max_extent - 1.0F)));
  135. const int max_z = std::min(
  136. m_height - 1, int(std::ceil(grid_center_z + max_extent + 1.0F)));
  137. const int map_cell_count = m_width * m_height;
  138. std::vector<std::uint8_t> walkable_mask(map_cell_count, 0);
  139. std::vector<std::uint8_t> entrance_line_mask(map_cell_count, 0);
  140. std::vector<int> entrance_indices;
  141. const float angle_rad = feature.rotationDeg * k_deg_to_rad;
  142. const float cos_a = std::cos(angle_rad);
  143. const float sin_a = std::sin(angle_rad);
  144. for (int z = min_z; z <= max_z; ++z) {
  145. for (int x = min_x; x <= max_x; ++x) {
  146. const float dx = float(x) - grid_center_x;
  147. const float dz = float(z) - grid_center_z;
  148. const float rotated_x = dx * cos_a + dz * sin_a;
  149. const float rotated_z = -dx * sin_a + dz * cos_a;
  150. const float norm_plateau_dist = std::sqrt(
  151. (rotated_x * rotated_x) / (plateau_width * plateau_width) +
  152. (rotated_z * rotated_z) / (plateau_depth * plateau_depth));
  153. const float norm_slope_dist =
  154. std::sqrt((rotated_x * rotated_x) / (slope_width * slope_width) +
  155. (rotated_z * rotated_z) / (slope_depth * slope_depth));
  156. if (norm_slope_dist > 1.0F) {
  157. continue;
  158. }
  159. const int idx = indexAt(x, z);
  160. float height = 0.0F;
  161. if (norm_plateau_dist <= 1.0F) {
  162. height = feature.height;
  163. } else {
  164. float const t = std::clamp((norm_slope_dist - norm_plateau_dist) /
  165. (1.0F - norm_plateau_dist),
  166. 0.0F, 1.0F);
  167. float const smooth =
  168. 0.5F * (1.0F + std::cos(t * std::numbers::pi_v<float>));
  169. height = feature.height * smooth;
  170. }
  171. if (height > m_heights[idx]) {
  172. m_heights[idx] = height;
  173. m_terrain_types[idx] = TerrainType::Hill;
  174. }
  175. if (norm_plateau_dist <= 1.0F &&
  176. m_terrain_types[idx] == TerrainType::Hill) {
  177. walkable_mask[idx] = 1;
  178. }
  179. }
  180. }
  181. for (const auto &entrance : feature.entrances) {
  182. const float entrance_gx =
  183. (entrance.x() / m_tile_size) + grid_half_width;
  184. const float entrance_gz =
  185. (entrance.z() / m_tile_size) + grid_half_height;
  186. int const ex = int(std::round(entrance_gx));
  187. int const ez = int(std::round(entrance_gz));
  188. if (!inBounds(ex, ez)) {
  189. continue;
  190. }
  191. const int entrance_idx = indexAt(ex, ez);
  192. m_hillEntrances[entrance_idx] = true;
  193. entrance_indices.push_back(entrance_idx);
  194. if (m_terrain_types[entrance_idx] != TerrainType::Mountain) {
  195. if (m_terrain_types[entrance_idx] == TerrainType::Flat) {
  196. m_terrain_types[entrance_idx] = TerrainType::Hill;
  197. }
  198. walkable_mask[entrance_idx] = 1;
  199. entrance_line_mask[entrance_idx] = 1;
  200. m_hillWalkable[entrance_idx] = true;
  201. m_heights[entrance_idx] = std::max(m_heights[entrance_idx], 0.0F);
  202. }
  203. float dir_x = grid_center_x - float(ex);
  204. float dir_z = grid_center_z - float(ez);
  205. float const length = std::sqrt(dir_x * dir_x + dir_z * dir_z);
  206. if (length < 0.001F) {
  207. continue;
  208. }
  209. dir_x /= length;
  210. dir_z /= length;
  211. auto cur_x = float(ex);
  212. auto cur_z = float(ez);
  213. const int steps = int(length) + 3;
  214. auto smoothstep = [](float t) {
  215. t = std::clamp(t, 0.0F, 1.0F);
  216. return t * t * (3.0F - 2.0F * t);
  217. };
  218. auto smootherstep = [](float t) {
  219. t = std::clamp(t, 0.0F, 1.0F);
  220. return t * t * t * (t * (t * 6.0F - 15.0F) + 10.0F);
  221. };
  222. int plateau_steps = steps;
  223. {
  224. auto test_x = cur_x;
  225. auto test_z = cur_z;
  226. for (int step = 0; step < steps; ++step) {
  227. int const ix = int(std::round(test_x));
  228. int const iz = int(std::round(test_z));
  229. if (!inBounds(ix, iz)) {
  230. break;
  231. }
  232. const float cell_dx = float(ix) - grid_center_x;
  233. const float cell_dz = float(iz) - grid_center_z;
  234. const float cell_rot_x = cell_dx * cos_a + cell_dz * sin_a;
  235. const float cell_rot_z = -cell_dx * sin_a + cell_dz * cos_a;
  236. const float plateau_norm_dist = std::sqrt(
  237. (cell_rot_x * cell_rot_x) / (plateau_width * plateau_width) +
  238. (cell_rot_z * cell_rot_z) / (plateau_depth * plateau_depth));
  239. if (plateau_norm_dist <= 1.0F) {
  240. plateau_steps = std::max(1, step);
  241. break;
  242. }
  243. test_x += dir_x;
  244. test_z += dir_z;
  245. }
  246. }
  247. const int ramp_steps =
  248. std::min(steps, plateau_steps + k_hill_ramp_extra_steps);
  249. int const outward_steps = std::clamp(
  250. int(std::round(float(ramp_steps) * k_entry_outward_steps_fraction)),
  251. k_entry_outward_steps_min, k_entry_outward_steps_max);
  252. int const total_ramp_steps = outward_steps + ramp_steps;
  253. float const hill_min_extent = std::min(plateau_width, plateau_depth);
  254. float const entry_width = std::max(
  255. 1.5F, std::min(k_entry_ramp_width, hill_min_extent * 0.35F));
  256. float const perp_x = -dir_z;
  257. float const perp_z = dir_x;
  258. cur_x = float(ex) - dir_x * float(outward_steps);
  259. cur_z = float(ez) - dir_z * float(outward_steps);
  260. for (int ramp_step = 0; ramp_step < total_ramp_steps; ++ramp_step) {
  261. bool const is_outward = ramp_step < outward_steps;
  262. int const center_ix = int(std::round(cur_x));
  263. int const center_iz = int(std::round(cur_z));
  264. if (!inBounds(center_ix, center_iz)) {
  265. break;
  266. }
  267. const float cell_dx = float(center_ix) - grid_center_x;
  268. const float cell_dz = float(center_iz) - grid_center_z;
  269. const float cell_rot_x = cell_dx * cos_a + cell_dz * sin_a;
  270. const float cell_rot_z = -cell_dx * sin_a + cell_dz * cos_a;
  271. const float cell_norm_dist = std::sqrt(
  272. (cell_rot_x * cell_rot_x) / (slope_width * slope_width) +
  273. (cell_rot_z * cell_rot_z) / (slope_depth * slope_depth));
  274. const float plateau_norm_dist = std::sqrt(
  275. (cell_rot_x * cell_rot_x) / (plateau_width * plateau_width) +
  276. (cell_rot_z * cell_rot_z) / (plateau_depth * plateau_depth));
  277. if (!is_outward && cell_norm_dist > 1.1F) {
  278. cur_x += dir_x;
  279. cur_z += dir_z;
  280. continue;
  281. }
  282. float const ramp_progress =
  283. (total_ramp_steps > 1)
  284. ? (float(ramp_step) / float(total_ramp_steps - 1))
  285. : 1.0F;
  286. float const s = smootherstep(ramp_progress);
  287. float const mid = 4.0F * ramp_progress * (1.0F - ramp_progress);
  288. float const height_base = std::pow(s, k_hill_ramp_steepness_exponent);
  289. float const height_frac =
  290. std::clamp(height_base * (1.0F - k_entry_mid_dip_strength * mid),
  291. 0.0F, 1.0F);
  292. float const toe_frac =
  293. k_entry_toe_height_fraction * (1.0F - s) * (1.0F - s);
  294. float center_ramp_height =
  295. feature.height * std::max(height_frac, toe_frac);
  296. center_ramp_height *=
  297. std::clamp(1.0F - k_entry_mid_depth_strength * mid, 0.0F, 1.0F);
  298. float const width_scale = (1.0F - s) * k_entry_base_width_scale +
  299. s * k_entry_top_width_scale;
  300. float tapered_width = std::max(1.0F, entry_width * width_scale);
  301. if (is_outward && outward_steps > 0) {
  302. float const outward_t =
  303. float(ramp_step) / float(std::max(1, outward_steps));
  304. float const outward_width_mul =
  305. 0.55F + 0.45F * std::clamp(outward_t, 0.0F, 1.0F);
  306. tapered_width = std::max(1.0F, tapered_width * outward_width_mul);
  307. }
  308. int const width_radius = int(std::ceil(tapered_width));
  309. for (int w = -width_radius; w <= width_radius; ++w) {
  310. float const offset_x = cur_x + perp_x * float(w);
  311. float const offset_z = cur_z + perp_z * float(w);
  312. int const ix = int(std::round(offset_x));
  313. int const iz = int(std::round(offset_z));
  314. if (!inBounds(ix, iz)) {
  315. continue;
  316. }
  317. float const edge_t = std::clamp(
  318. std::abs(float(w)) / (tapered_width + k_width_falloff_padding),
  319. 0.0F, 1.0F);
  320. int const ramp_idx = indexAt(ix, iz);
  321. if (m_terrain_types[ramp_idx] != TerrainType::Mountain) {
  322. float const width_factor = 1.0F - edge_t;
  323. if (!is_outward) {
  324. if (m_terrain_types[ramp_idx] == TerrainType::Flat) {
  325. m_terrain_types[ramp_idx] = TerrainType::Hill;
  326. }
  327. if (width_factor > k_walkable_width_threshold) {
  328. walkable_mask[ramp_idx] = 1;
  329. entrance_line_mask[ramp_idx] = 1;
  330. m_hillEntrances[ramp_idx] = true;
  331. }
  332. }
  333. float const existing_height = m_heights[ramp_idx];
  334. float const bowl = std::pow(edge_t, k_entry_bowl_exponent);
  335. float const target_height =
  336. (1.0F - bowl) * center_ramp_height + bowl * existing_height;
  337. float const along =
  338. smootherstep(std::clamp((s - 0.20F) / 0.80F, 0.0F, 1.0F));
  339. float const carved = std::min(existing_height, target_height);
  340. float const joined = std::max(existing_height, target_height);
  341. m_heights[ramp_idx] = (1.0F - along) * carved + along * joined;
  342. }
  343. }
  344. cur_x += dir_x;
  345. cur_z += dir_z;
  346. }
  347. }
  348. for (int z = min_z; z <= max_z; ++z) {
  349. for (int x = min_x; x <= max_x; ++x) {
  350. int const idx = indexAt(x, z);
  351. if (m_terrain_types[idx] != TerrainType::Hill) {
  352. continue;
  353. }
  354. if (entrance_line_mask[idx] == 1) {
  355. continue;
  356. }
  357. const float dx = float(x) - grid_center_x;
  358. const float dz = float(z) - grid_center_z;
  359. const float rotated_x = dx * cos_a + dz * sin_a;
  360. const float rotated_z = -dx * sin_a + dz * cos_a;
  361. const float norm_plateau_dist = std::sqrt(
  362. (rotated_x * rotated_x) / (plateau_width * plateau_width) +
  363. (rotated_z * rotated_z) / (plateau_depth * plateau_depth));
  364. if (norm_plateau_dist > 1.0F) {
  365. walkable_mask[idx] = 0;
  366. }
  367. if (norm_plateau_dist > 0.85F) {
  368. bool adjacent_to_non_hill = false;
  369. constexpr int k_dirs[8][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1},
  370. {1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
  371. for (const auto &dir : k_dirs) {
  372. int const nx = x + dir[0];
  373. int const nz = z + dir[1];
  374. if (!inBounds(nx, nz)) {
  375. adjacent_to_non_hill = true;
  376. break;
  377. }
  378. int const n_idx = indexAt(nx, nz);
  379. if (m_terrain_types[n_idx] != TerrainType::Hill) {
  380. adjacent_to_non_hill = true;
  381. break;
  382. }
  383. }
  384. if (adjacent_to_non_hill) {
  385. walkable_mask[idx] = 0;
  386. }
  387. }
  388. }
  389. }
  390. if (!entrance_line_mask.empty()) {
  391. for (int z = min_z; z <= max_z; ++z) {
  392. for (int x = min_x; x <= max_x; ++x) {
  393. int const idx = indexAt(x, z);
  394. if (walkable_mask[idx] == 0 || entrance_line_mask[idx] == 1 ||
  395. m_terrain_types[idx] != TerrainType::Hill) {
  396. continue;
  397. }
  398. bool on_edge = false;
  399. constexpr int k_dirs[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
  400. for (const auto &dir : k_dirs) {
  401. int const nx = x + dir[0];
  402. int const nz = z + dir[1];
  403. if (!inBounds(nx, nz)) {
  404. on_edge = true;
  405. break;
  406. }
  407. int const n_idx = indexAt(nx, nz);
  408. if (m_terrain_types[n_idx] != TerrainType::Hill) {
  409. on_edge = true;
  410. break;
  411. }
  412. }
  413. if (on_edge) {
  414. walkable_mask[idx] = 0;
  415. }
  416. }
  417. }
  418. }
  419. if (!entrance_indices.empty()) {
  420. std::vector<std::uint8_t> visited(map_cell_count, 0);
  421. std::vector<int> queue;
  422. queue.reserve(entrance_indices.size());
  423. for (int const entrance_idx : entrance_indices) {
  424. if (visited[entrance_idx] || walkable_mask[entrance_idx] == 0) {
  425. continue;
  426. }
  427. visited[entrance_idx] = 1;
  428. m_hillWalkable[entrance_idx] = true;
  429. queue.push_back(entrance_idx);
  430. while (!queue.empty()) {
  431. int const idx = queue.back();
  432. queue.pop_back();
  433. int const cx = idx % m_width;
  434. int const cz = idx / m_width;
  435. constexpr int k_dirs[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
  436. for (const auto &dir : k_dirs) {
  437. int const nx = cx + dir[0];
  438. int const nz = cz + dir[1];
  439. if (!inBounds(nx, nz)) {
  440. continue;
  441. }
  442. int const n_idx = indexAt(nx, nz);
  443. if (visited[n_idx] || walkable_mask[n_idx] == 0) {
  444. continue;
  445. }
  446. visited[n_idx] = 1;
  447. m_hillWalkable[n_idx] = true;
  448. queue.push_back(n_idx);
  449. }
  450. }
  451. }
  452. }
  453. continue;
  454. }
  455. const float flat_radius = grid_radius;
  456. const int min_x = std::max(0, int(std::floor(grid_center_x - flat_radius)));
  457. const int max_x =
  458. std::min(m_width - 1, int(std::ceil(grid_center_x + flat_radius)));
  459. const int min_z = std::max(0, int(std::floor(grid_center_z - flat_radius)));
  460. const int max_z =
  461. std::min(m_height - 1, int(std::ceil(grid_center_z + flat_radius)));
  462. for (int z = min_z; z <= max_z; ++z) {
  463. for (int x = min_x; x <= max_x; ++x) {
  464. const float dx = float(x) - grid_center_x;
  465. const float dz = float(z) - grid_center_z;
  466. const float dist = std::sqrt(dx * dx + dz * dz);
  467. if (dist > flat_radius) {
  468. continue;
  469. }
  470. float const t = dist / std::max(flat_radius, 0.0001F);
  471. float const height = feature.height * (1.0F - t);
  472. if (height <= 0.0F) {
  473. continue;
  474. }
  475. int const idx = indexAt(x, z);
  476. if (height > m_heights[idx]) {
  477. m_heights[idx] = height;
  478. m_terrain_types[idx] = TerrainType::Flat;
  479. }
  480. }
  481. }
  482. }
  483. }
  484. auto TerrainHeightMap::getHeightAt(float world_x,
  485. float world_z) const -> float {
  486. const float grid_half_width = m_width * 0.5F - 0.5F;
  487. const float grid_half_height = m_height * 0.5F - 0.5F;
  488. float const gx = world_x / m_tile_size + grid_half_width;
  489. float const gz = world_z / m_tile_size + grid_half_height;
  490. int const x0 = int(std::floor(gx));
  491. int const z0 = int(std::floor(gz));
  492. int const x1 = x0 + 1;
  493. int const z1 = z0 + 1;
  494. if (!inBounds(x0, z0)) {
  495. return 0.0F;
  496. }
  497. float const tx = gx - x0;
  498. float const tz = gz - z0;
  499. float const h00 = inBounds(x0, z0) ? m_heights[indexAt(x0, z0)] : 0.0F;
  500. float const h10 = inBounds(x1, z0) ? m_heights[indexAt(x1, z0)] : 0.0F;
  501. float const h01 = inBounds(x0, z1) ? m_heights[indexAt(x0, z1)] : 0.0F;
  502. float const h11 = inBounds(x1, z1) ? m_heights[indexAt(x1, z1)] : 0.0F;
  503. float const h0 = h00 * (1.0F - tx) + h10 * tx;
  504. float const h1 = h01 * (1.0F - tx) + h11 * tx;
  505. float base_height = h0 * (1.0F - tz) + h1 * tz;
  506. if (isOnBridge(world_x, world_z)) {
  507. auto bridge_height_opt = getBridgeDeckHeight(world_x, world_z);
  508. if (bridge_height_opt.has_value()) {
  509. return bridge_height_opt.value();
  510. }
  511. }
  512. return base_height;
  513. }
  514. auto TerrainHeightMap::getHeightAtGrid(int grid_x, int grid_z) const -> float {
  515. if (!inBounds(grid_x, grid_z)) {
  516. return 0.0F;
  517. }
  518. return m_heights[indexAt(grid_x, grid_z)];
  519. }
  520. auto TerrainHeightMap::is_walkable(int grid_x, int grid_z) const -> bool {
  521. if (!inBounds(grid_x, grid_z)) {
  522. return false;
  523. }
  524. int const idx = indexAt(grid_x, grid_z);
  525. if (!m_onBridge.empty() && m_onBridge[idx]) {
  526. return true;
  527. }
  528. TerrainType const type = m_terrain_types[idx];
  529. if (type == TerrainType::Mountain) {
  530. return false;
  531. }
  532. if (type == TerrainType::River) {
  533. return false;
  534. }
  535. if (type == TerrainType::Hill) {
  536. return m_hillWalkable[indexAt(grid_x, grid_z)];
  537. }
  538. return true;
  539. }
  540. auto TerrainHeightMap::isHillEntrance(int grid_x, int grid_z) const -> bool {
  541. if (!inBounds(grid_x, grid_z)) {
  542. return false;
  543. }
  544. return m_hillEntrances[indexAt(grid_x, grid_z)];
  545. }
  546. auto TerrainHeightMap::getTerrainType(int grid_x,
  547. int grid_z) const -> TerrainType {
  548. if (!inBounds(grid_x, grid_z)) {
  549. return TerrainType::Flat;
  550. }
  551. return m_terrain_types[indexAt(grid_x, grid_z)];
  552. }
  553. auto TerrainHeightMap::isRiverOrNearby(int grid_x, int grid_z,
  554. int margin) const -> bool {
  555. if (!inBounds(grid_x, grid_z)) {
  556. return false;
  557. }
  558. if (m_terrain_types[indexAt(grid_x, grid_z)] == TerrainType::River) {
  559. return true;
  560. }
  561. for (int dz = -margin; dz <= margin; ++dz) {
  562. for (int dx = -margin; dx <= margin; ++dx) {
  563. if (dx == 0 && dz == 0) {
  564. continue;
  565. }
  566. int const nx = grid_x + dx;
  567. int const nz = grid_z + dz;
  568. if (inBounds(nx, nz) &&
  569. m_terrain_types[indexAt(nx, nz)] == TerrainType::River) {
  570. return true;
  571. }
  572. }
  573. }
  574. return false;
  575. }
  576. auto TerrainHeightMap::indexAt(int x, int z) const -> int {
  577. return z * m_width + x;
  578. }
  579. auto TerrainHeightMap::inBounds(int x, int z) const -> bool {
  580. return x >= 0 && x < m_width && z >= 0 && z < m_height;
  581. }
  582. auto TerrainHeightMap::calculateFeatureHeight(const TerrainFeature &feature,
  583. float world_x,
  584. float world_z) -> float {
  585. float const dx = world_x - feature.center_x;
  586. float const dz = world_z - feature.center_z;
  587. float const dist = std::sqrt(dx * dx + dz * dz);
  588. if (dist > feature.radius) {
  589. return 0.0F;
  590. }
  591. float const t = dist / feature.radius;
  592. float const height_factor = (std::cos(t * M_PI) + 1.0F) * 0.5F;
  593. return feature.height * height_factor;
  594. }
  595. void TerrainHeightMap::applyBiomeVariation(const BiomeSettings &settings) {
  596. if (m_heights.empty()) {
  597. return;
  598. }
  599. if (settings.ground_irregularity_enabled) {
  600. const float amplitude = std::max(0.0F, settings.irregularity_amplitude);
  601. if (amplitude > 0.0001F) {
  602. const float frequency = std::max(0.0001F, settings.irregularity_scale);
  603. const float half_width = m_width * 0.5F - 0.5F;
  604. const float half_height = m_height * 0.5F - 0.5F;
  605. for (int z = 0; z < m_height; ++z) {
  606. for (int x = 0; x < m_width; ++x) {
  607. int const idx = indexAt(x, z);
  608. TerrainType const type = m_terrain_types[idx];
  609. if (type != TerrainType::Flat) {
  610. continue;
  611. }
  612. if (isRiverOrNearby(x, z, 2)) {
  613. continue;
  614. }
  615. float const world_x =
  616. (static_cast<float>(x) - half_width) * m_tile_size;
  617. float const world_z =
  618. (static_cast<float>(z) - half_height) * m_tile_size;
  619. float const sample_x = world_x * frequency;
  620. float const sample_z = world_z * frequency;
  621. float const base_noise =
  622. valueNoise2D(sample_x, sample_z, settings.seed);
  623. float const detail_noise = valueNoise2D(
  624. sample_x * 2.5F, sample_z * 2.5F, settings.seed ^ 0xA21C9E37U);
  625. float const fine_noise = valueNoise2D(
  626. sample_x * 5.0F, sample_z * 5.0F, settings.seed ^ 0x7E4B92F1U);
  627. float const blended =
  628. 0.5F * base_noise + 0.35F * detail_noise + 0.15F * fine_noise;
  629. float const perturb = (blended - 0.5F) * 2.0F * amplitude;
  630. m_heights[idx] = std::max(0.0F, m_heights[idx] + perturb);
  631. }
  632. }
  633. }
  634. }
  635. const float legacy_amplitude =
  636. std::max(0.0F, settings.height_noise_amplitude);
  637. if (legacy_amplitude > 0.0001F) {
  638. const float frequency = std::max(0.0001F, settings.height_noise_frequency);
  639. const float half_width = m_width * 0.5F - 0.5F;
  640. const float half_height = m_height * 0.5F - 0.5F;
  641. for (int z = 0; z < m_height; ++z) {
  642. for (int x = 0; x < m_width; ++x) {
  643. int const idx = indexAt(x, z);
  644. TerrainType const type = m_terrain_types[idx];
  645. if (type == TerrainType::Mountain) {
  646. continue;
  647. }
  648. float const world_x =
  649. (static_cast<float>(x) - half_width) * m_tile_size;
  650. float const world_z =
  651. (static_cast<float>(z) - half_height) * m_tile_size;
  652. float const sample_x = world_x * frequency;
  653. float const sample_z = world_z * frequency;
  654. float const base_noise =
  655. valueNoise2D(sample_x, sample_z, settings.seed);
  656. float const detail_noise = valueNoise2D(
  657. sample_x * 2.0F, sample_z * 2.0F, settings.seed ^ 0xA21C9E37U);
  658. float const blended = 0.65F * base_noise + 0.35F * detail_noise;
  659. float perturb = (blended - 0.5F) * 2.0F * legacy_amplitude;
  660. if (type == TerrainType::Hill) {
  661. perturb *= 0.6F;
  662. }
  663. m_heights[idx] = std::max(0.0F, m_heights[idx] + perturb);
  664. }
  665. }
  666. }
  667. }
  668. void TerrainHeightMap::addRiverSegments(
  669. const std::vector<RiverSegment> &riverSegments) {
  670. m_riverSegments = riverSegments;
  671. const float grid_half_width = m_width * 0.5F - 0.5F;
  672. const float grid_half_height = m_height * 0.5F - 0.5F;
  673. for (const auto &river : riverSegments) {
  674. QVector3D dir = river.end - river.start;
  675. float const length = dir.length();
  676. if (length < 0.01F) {
  677. continue;
  678. }
  679. dir.normalize();
  680. QVector3D const perpendicular(-dir.z(), 0.0F, dir.x());
  681. int const steps = static_cast<int>(std::ceil(length / m_tile_size)) + 1;
  682. for (int i = 0; i < steps; ++i) {
  683. float const t =
  684. static_cast<float>(i) / std::max(1.0F, static_cast<float>(steps - 1));
  685. QVector3D const center_pos = river.start + dir * (length * t);
  686. float const grid_center_x =
  687. (center_pos.x() / m_tile_size) + grid_half_width;
  688. float const grid_center_z =
  689. (center_pos.z() / m_tile_size) + grid_half_height;
  690. float const half_width = river.width * 0.5F / m_tile_size;
  691. int const min_x = std::max(
  692. 0, static_cast<int>(std::floor(grid_center_x - half_width - 1.0F)));
  693. int const max_x = std::min(
  694. m_width - 1,
  695. static_cast<int>(std::ceil(grid_center_x + half_width + 1.0F)));
  696. int const min_z = std::max(
  697. 0, static_cast<int>(std::floor(grid_center_z - half_width - 1.0F)));
  698. int const max_z = std::min(
  699. m_height - 1,
  700. static_cast<int>(std::ceil(grid_center_z + half_width + 1.0F)));
  701. for (int z = min_z; z <= max_z; ++z) {
  702. for (int x = min_x; x <= max_x; ++x) {
  703. float const dx = static_cast<float>(x) - grid_center_x;
  704. float const dz = static_cast<float>(z) - grid_center_z;
  705. float const dist_along_perp =
  706. std::abs(dx * perpendicular.x() + dz * perpendicular.z());
  707. if (dist_along_perp <= half_width) {
  708. int const idx = indexAt(x, z);
  709. if (m_terrain_types[idx] != TerrainType::Mountain) {
  710. m_terrain_types[idx] = TerrainType::River;
  711. m_heights[idx] = 0.0F;
  712. }
  713. }
  714. }
  715. }
  716. }
  717. }
  718. }
  719. void TerrainHeightMap::addBridges(const std::vector<Bridge> &bridges) {
  720. constexpr float kBridgeSinkMin = 0.25F;
  721. constexpr float kBridgeSinkMax = 0.65F;
  722. constexpr float kBridgeEntryMarginTiles = 1.0F;
  723. m_bridges.clear();
  724. m_bridges.reserve(bridges.size());
  725. const float grid_half_width = m_width * 0.5F - 0.5F;
  726. const float grid_half_height = m_height * 0.5F - 0.5F;
  727. for (const auto &bridge : bridges) {
  728. const float sink_amount =
  729. std::clamp(bridge.width * 0.25F, kBridgeSinkMin, kBridgeSinkMax);
  730. Bridge adjusted = bridge;
  731. float const start_ground = getHeightAt(bridge.start.x(), bridge.start.z());
  732. float const end_ground = getHeightAt(bridge.end.x(), bridge.end.z());
  733. adjusted.start.setY(std::max(bridge.start.y(), start_ground - sink_amount));
  734. adjusted.end.setY(std::max(bridge.end.y(), end_ground - sink_amount));
  735. QVector3D dir = adjusted.end - adjusted.start;
  736. float const length = dir.length();
  737. if (length < 0.01F) {
  738. continue;
  739. }
  740. m_bridges.push_back(adjusted);
  741. const Bridge &stored_bridge = m_bridges.back();
  742. dir.normalize();
  743. QVector3D const perpendicular(-dir.z(), 0.0F, dir.x());
  744. float const bridge_half_width = stored_bridge.width * 0.5F;
  745. float const entry_margin = m_tile_size * kBridgeEntryMarginTiles;
  746. float const extended_length = length + (entry_margin * 2.0F);
  747. int const steps =
  748. static_cast<int>(std::ceil(extended_length / m_tile_size)) + 1;
  749. for (int i = 0; i < steps; ++i) {
  750. float const t =
  751. static_cast<float>(i) / std::max(1.0F, static_cast<float>(steps - 1));
  752. float const along = -entry_margin + extended_length * t;
  753. float const t_curve =
  754. std::clamp(along / std::max(length, 0.01F), 0.0F, 1.0F);
  755. QVector3D const center_pos = stored_bridge.start + dir * along;
  756. float const arch_curve = 4.0F * t_curve * (1.0F - t_curve);
  757. float const arch_height = stored_bridge.height * arch_curve * 0.8F;
  758. float const base_deck_height =
  759. stored_bridge.start.y() + stored_bridge.height + arch_height * 0.5F;
  760. float const terrain_height = getHeightAt(center_pos.x(), center_pos.z());
  761. float const deck_height = std::max(base_deck_height - sink_amount,
  762. terrain_height - sink_amount);
  763. float const grid_center_x =
  764. (center_pos.x() / m_tile_size) + grid_half_width;
  765. float const grid_center_z =
  766. (center_pos.z() / m_tile_size) + grid_half_height;
  767. int const min_x = std::max(
  768. 0, static_cast<int>(std::floor(grid_center_x - bridge_half_width)));
  769. int const max_x = std::min(
  770. m_width - 1,
  771. static_cast<int>(std::ceil(grid_center_x + bridge_half_width)));
  772. int const min_z = std::max(
  773. 0, static_cast<int>(std::floor(grid_center_z - bridge_half_width)));
  774. int const max_z = std::min(
  775. m_height - 1,
  776. static_cast<int>(std::ceil(grid_center_z + bridge_half_width)));
  777. for (int z = min_z; z <= max_z; ++z) {
  778. for (int x = min_x; x <= max_x; ++x) {
  779. float const dx = static_cast<float>(x) - grid_center_x;
  780. float const dz = static_cast<float>(z) - grid_center_z;
  781. float const dist_along_perp =
  782. std::abs(dx * perpendicular.x() + dz * perpendicular.z());
  783. if (dist_along_perp <= bridge_half_width) {
  784. int const idx = indexAt(x, z);
  785. if (m_terrain_types[idx] == TerrainType::River) {
  786. m_terrain_types[idx] = TerrainType::Flat;
  787. m_heights[idx] = deck_height;
  788. }
  789. }
  790. }
  791. }
  792. }
  793. }
  794. precomputeBridgeData();
  795. }
  796. void TerrainHeightMap::precomputeBridgeData() {
  797. const size_t grid_size = static_cast<size_t>(m_width * m_height);
  798. m_onBridge.clear();
  799. m_onBridge.resize(grid_size, false);
  800. m_bridgeCenters.clear();
  801. m_bridgeCenters.resize(grid_size, QVector3D(0.0F, 0.0F, 0.0F));
  802. constexpr float kBridgeEntryMarginTiles = 1.0F;
  803. const float grid_half_width = m_width * 0.5F - 0.5F;
  804. const float grid_half_height = m_height * 0.5F - 0.5F;
  805. for (const auto &bridge : m_bridges) {
  806. QVector3D dir = bridge.end - bridge.start;
  807. float const length = dir.length();
  808. if (length < 0.01F) {
  809. continue;
  810. }
  811. dir.normalize();
  812. QVector3D const perpendicular(-dir.z(), 0.0F, dir.x());
  813. float const bridge_half_width = bridge.width * 0.5F;
  814. float const entry_margin = m_tile_size * kBridgeEntryMarginTiles;
  815. float const extended_length = length + (entry_margin * 2.0F);
  816. int const steps =
  817. static_cast<int>(std::ceil(extended_length / m_tile_size)) + 1;
  818. for (int i = 0; i < steps; ++i) {
  819. float const t =
  820. static_cast<float>(i) / std::max(1.0F, static_cast<float>(steps - 1));
  821. float const along = -entry_margin + extended_length * t;
  822. QVector3D const center_pos = bridge.start + dir * along;
  823. float const grid_center_x =
  824. (center_pos.x() / m_tile_size) + grid_half_width;
  825. float const grid_center_z =
  826. (center_pos.z() / m_tile_size) + grid_half_height;
  827. int const min_x = std::max(
  828. 0, static_cast<int>(std::floor(grid_center_x - bridge_half_width)));
  829. int const max_x = std::min(
  830. m_width - 1,
  831. static_cast<int>(std::ceil(grid_center_x + bridge_half_width)));
  832. int const min_z = std::max(
  833. 0, static_cast<int>(std::floor(grid_center_z - bridge_half_width)));
  834. int const max_z = std::min(
  835. m_height - 1,
  836. static_cast<int>(std::ceil(grid_center_z + bridge_half_width)));
  837. for (int z = min_z; z <= max_z; ++z) {
  838. for (int x = min_x; x <= max_x; ++x) {
  839. float const dx = static_cast<float>(x) - grid_center_x;
  840. float const dz = static_cast<float>(z) - grid_center_z;
  841. float const dist_along_perp =
  842. std::abs(dx * perpendicular.x() + dz * perpendicular.z());
  843. if (dist_along_perp <= bridge_half_width) {
  844. int const idx = indexAt(x, z);
  845. m_onBridge[idx] = true;
  846. float const cell_world_x =
  847. (static_cast<float>(x) - grid_half_width) * m_tile_size;
  848. float const cell_world_z =
  849. (static_cast<float>(z) - grid_half_height) * m_tile_size;
  850. QVector3D const cell_point(cell_world_x, 0.0F, cell_world_z);
  851. QVector3D const to_cell = cell_point - bridge.start;
  852. float const center_along = QVector3D::dotProduct(to_cell, dir);
  853. float const clamped_along = std::clamp(center_along, 0.0F, length);
  854. m_bridgeCenters[idx] = bridge.start + dir * clamped_along;
  855. }
  856. }
  857. }
  858. }
  859. }
  860. }
  861. void TerrainHeightMap::restoreFromData(
  862. const std::vector<float> &heights,
  863. const std::vector<TerrainType> &terrain_types,
  864. const std::vector<RiverSegment> &rivers,
  865. const std::vector<Bridge> &bridges) {
  866. const auto expected_size = static_cast<size_t>(m_width * m_height);
  867. if (heights.size() == expected_size) {
  868. m_heights = heights;
  869. }
  870. if (terrain_types.size() == expected_size) {
  871. m_terrain_types = terrain_types;
  872. }
  873. m_hillEntrances.clear();
  874. m_hillEntrances.resize(expected_size, false);
  875. m_hillWalkable.clear();
  876. m_hillWalkable.resize(expected_size, true);
  877. for (size_t i = 0; i < m_terrain_types.size(); ++i) {
  878. if (m_terrain_types[i] == TerrainType::Hill) {
  879. m_hillWalkable[i] = false;
  880. }
  881. }
  882. m_riverSegments = rivers;
  883. m_bridges = bridges;
  884. precomputeBridgeData();
  885. }
  886. auto TerrainHeightMap::getBridgeDeckHeight(float world_x, float world_z) const
  887. -> std::optional<float> {
  888. for (const auto &bridge : m_bridges) {
  889. QVector3D dir = bridge.end - bridge.start;
  890. float const length = dir.length();
  891. if (length < 0.01F) {
  892. continue;
  893. }
  894. dir.normalize();
  895. QVector3D const perpendicular(-dir.z(), 0.0F, dir.x());
  896. float const bridge_half_width = bridge.width * 0.5F;
  897. QVector3D const query_point(world_x, 0.0F, world_z);
  898. QVector3D const to_query = query_point - bridge.start;
  899. float const along = QVector3D::dotProduct(to_query, dir);
  900. if (along < -0.5F || along > length + 0.5F) {
  901. continue;
  902. }
  903. float const perp_dist =
  904. std::abs(QVector3D::dotProduct(to_query, perpendicular));
  905. if (perp_dist > bridge_half_width) {
  906. continue;
  907. }
  908. float const t = std::clamp(along / length, 0.0F, 1.0F);
  909. float const arch_curve = 4.0F * t * (1.0F - t);
  910. float const arch_height = bridge.height * arch_curve * 0.8F;
  911. float const deck_height =
  912. bridge.start.y() + bridge.height + arch_height * 0.3F;
  913. return deck_height;
  914. }
  915. return std::nullopt;
  916. }
  917. auto TerrainHeightMap::isOnBridge(float world_x, float world_z) const -> bool {
  918. if (m_onBridge.empty()) {
  919. return false;
  920. }
  921. const float grid_half_width = m_width * 0.5F - 0.5F;
  922. const float grid_half_height = m_height * 0.5F - 0.5F;
  923. const int grid_x =
  924. static_cast<int>(std::round((world_x / m_tile_size) + grid_half_width));
  925. const int grid_z =
  926. static_cast<int>(std::round((world_z / m_tile_size) + grid_half_height));
  927. if (!inBounds(grid_x, grid_z)) {
  928. return false;
  929. }
  930. return m_onBridge[indexAt(grid_x, grid_z)];
  931. }
  932. auto TerrainHeightMap::getBridgeCenterPosition(
  933. float world_x, float world_z) const -> std::optional<QVector3D> {
  934. if (m_onBridge.empty()) {
  935. return std::nullopt;
  936. }
  937. const float grid_half_width = m_width * 0.5F - 0.5F;
  938. const float grid_half_height = m_height * 0.5F - 0.5F;
  939. const int grid_x =
  940. static_cast<int>(std::round((world_x / m_tile_size) + grid_half_width));
  941. const int grid_z =
  942. static_cast<int>(std::round((world_z / m_tile_size) + grid_half_height));
  943. if (!inBounds(grid_x, grid_z)) {
  944. return std::nullopt;
  945. }
  946. const int idx = indexAt(grid_x, grid_z);
  947. if (!m_onBridge[idx]) {
  948. return std::nullopt;
  949. }
  950. return m_bridgeCenters[idx];
  951. }
  952. } // namespace Game::Map