terrain.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. #include "terrain.h"
  2. #include <algorithm>
  3. #include <cmath>
  4. #include <cstdint>
  5. namespace {
  6. constexpr float kDegToRad = static_cast<float>(M_PI) / 180.0f;
  7. inline std::uint32_t hashCoords(int x, int z, std::uint32_t seed) {
  8. std::uint32_t ux = static_cast<std::uint32_t>(x) * 73856093u;
  9. std::uint32_t uz = static_cast<std::uint32_t>(z) * 19349663u;
  10. std::uint32_t s = seed * 83492791u + 0x9e3779b9u;
  11. return ux ^ uz ^ s;
  12. }
  13. inline float hashToFloat01(std::uint32_t h) {
  14. h ^= h >> 17;
  15. h *= 0xed5ad4bbu;
  16. h ^= h >> 11;
  17. h *= 0xac4c1b51u;
  18. h ^= h >> 15;
  19. h *= 0x31848babu;
  20. h ^= h >> 14;
  21. return (h & 0x00FFFFFFu) / float(0x01000000);
  22. }
  23. inline float valueNoise2D(float x, float z, std::uint32_t seed) {
  24. int ix0 = static_cast<int>(std::floor(x));
  25. int iz0 = static_cast<int>(std::floor(z));
  26. int ix1 = ix0 + 1;
  27. int iz1 = iz0 + 1;
  28. float tx = x - static_cast<float>(ix0);
  29. float tz = z - static_cast<float>(iz0);
  30. float n00 = hashToFloat01(hashCoords(ix0, iz0, seed));
  31. float n10 = hashToFloat01(hashCoords(ix1, iz0, seed));
  32. float n01 = hashToFloat01(hashCoords(ix0, iz1, seed));
  33. float n11 = hashToFloat01(hashCoords(ix1, iz1, seed));
  34. float nx0 = n00 * (1.0f - tx) + n10 * tx;
  35. float nx1 = n01 * (1.0f - tx) + n11 * tx;
  36. return nx0 * (1.0f - tz) + nx1 * tz;
  37. }
  38. } // namespace
  39. namespace Game::Map {
  40. TerrainHeightMap::TerrainHeightMap(int width, int height, float tileSize)
  41. : m_width(width), m_height(height), m_tileSize(tileSize) {
  42. const int count = width * height;
  43. m_heights.resize(count, 0.0f);
  44. m_terrainTypes.resize(count, TerrainType::Flat);
  45. m_hillEntrances.resize(count, false);
  46. m_hillWalkable.resize(count, false);
  47. }
  48. void TerrainHeightMap::buildFromFeatures(
  49. const std::vector<TerrainFeature> &features) {
  50. std::fill(m_heights.begin(), m_heights.end(), 0.0f);
  51. std::fill(m_terrainTypes.begin(), m_terrainTypes.end(), TerrainType::Flat);
  52. std::fill(m_hillEntrances.begin(), m_hillEntrances.end(), false);
  53. std::fill(m_hillWalkable.begin(), m_hillWalkable.end(), false);
  54. const float gridHalfWidth = m_width * 0.5f - 0.5f;
  55. const float gridHalfHeight = m_height * 0.5f - 0.5f;
  56. for (const auto &feature : features) {
  57. const float gridCenterX = (feature.centerX / m_tileSize) + gridHalfWidth;
  58. const float gridCenterZ = (feature.centerZ / m_tileSize) + gridHalfHeight;
  59. const float gridRadius = std::max(feature.radius / m_tileSize, 1.0f);
  60. if (feature.type == TerrainType::Mountain) {
  61. const float majorRadius = std::max(gridRadius * 1.8f, gridRadius + 3.0f);
  62. const float minorRadius = std::max(gridRadius * 0.22f, 0.8f);
  63. const float bound = std::max(majorRadius, minorRadius) + 2.0f;
  64. const int minX = std::max(0, int(std::floor(gridCenterX - bound)));
  65. const int maxX =
  66. std::min(m_width - 1, int(std::ceil(gridCenterX + bound)));
  67. const int minZ = std::max(0, int(std::floor(gridCenterZ - bound)));
  68. const int maxZ =
  69. std::min(m_height - 1, int(std::ceil(gridCenterZ + bound)));
  70. const float angleRad = feature.rotationDeg * kDegToRad;
  71. const float cosA = std::cos(angleRad);
  72. const float sinA = std::sin(angleRad);
  73. for (int z = minZ; z <= maxZ; ++z) {
  74. for (int x = minX; x <= maxX; ++x) {
  75. const float localX = float(x) - gridCenterX;
  76. const float localZ = float(z) - gridCenterZ;
  77. const float rotatedX = localX * cosA + localZ * sinA;
  78. const float rotatedZ = -localX * sinA + localZ * cosA;
  79. const float norm =
  80. std::sqrt((rotatedX * rotatedX) / (majorRadius * majorRadius) +
  81. (rotatedZ * rotatedZ) / (minorRadius * minorRadius));
  82. if (norm <= 1.0f) {
  83. float blend = std::clamp(1.0f - norm, 0.0f, 1.0f);
  84. float height = feature.height * std::pow(blend, 3.5f);
  85. if (blend > 0.92f) {
  86. height = feature.height;
  87. }
  88. if (height > 0.01f) {
  89. int idx = indexAt(x, z);
  90. if (height > m_heights[idx]) {
  91. m_heights[idx] = height;
  92. m_terrainTypes[idx] = TerrainType::Mountain;
  93. }
  94. }
  95. }
  96. }
  97. }
  98. continue;
  99. }
  100. if (feature.type == TerrainType::Hill) {
  101. const float gridWidth = std::max(feature.width / m_tileSize, 1.0f);
  102. const float gridDepth = std::max(feature.depth / m_tileSize, 1.0f);
  103. const float plateauWidth = std::max(1.5f, gridWidth * 0.45f);
  104. const float plateauDepth = std::max(1.5f, gridDepth * 0.45f);
  105. const float slopeWidth = std::max(plateauWidth + 1.5f, gridWidth);
  106. const float slopeDepth = std::max(plateauDepth + 1.5f, gridDepth);
  107. const float maxExtent = std::max(slopeWidth, slopeDepth);
  108. const int minX =
  109. std::max(0, int(std::floor(gridCenterX - maxExtent - 1.0f)));
  110. const int maxX =
  111. std::min(m_width - 1, int(std::ceil(gridCenterX + maxExtent + 1.0f)));
  112. const int minZ =
  113. std::max(0, int(std::floor(gridCenterZ - maxExtent - 1.0f)));
  114. const int maxZ = std::min(m_height - 1,
  115. int(std::ceil(gridCenterZ + maxExtent + 1.0f)));
  116. std::vector<int> plateauCells;
  117. plateauCells.reserve(int(M_PI * plateauWidth * plateauDepth));
  118. const float angleRad = feature.rotationDeg * kDegToRad;
  119. const float cosA = std::cos(angleRad);
  120. const float sinA = std::sin(angleRad);
  121. for (int z = minZ; z <= maxZ; ++z) {
  122. for (int x = minX; x <= maxX; ++x) {
  123. const float dx = float(x) - gridCenterX;
  124. const float dz = float(z) - gridCenterZ;
  125. const float rotatedX = dx * cosA + dz * sinA;
  126. const float rotatedZ = -dx * sinA + dz * cosA;
  127. const float normPlateauDist =
  128. std::sqrt((rotatedX * rotatedX) / (plateauWidth * plateauWidth) +
  129. (rotatedZ * rotatedZ) / (plateauDepth * plateauDepth));
  130. const float normSlopeDist =
  131. std::sqrt((rotatedX * rotatedX) / (slopeWidth * slopeWidth) +
  132. (rotatedZ * rotatedZ) / (slopeDepth * slopeDepth));
  133. if (normSlopeDist > 1.0f) {
  134. continue;
  135. }
  136. const int idx = indexAt(x, z);
  137. float height = 0.0f;
  138. if (normPlateauDist <= 1.0f) {
  139. height = feature.height;
  140. plateauCells.push_back(idx);
  141. } else {
  142. float t = std::clamp((normSlopeDist - normPlateauDist) /
  143. (1.0f - normPlateauDist),
  144. 0.0f, 1.0f);
  145. float smooth = 0.5f * (1.0f + std::cos(t * float(M_PI)));
  146. height = feature.height * smooth;
  147. }
  148. if (height > m_heights[idx]) {
  149. m_heights[idx] = height;
  150. m_terrainTypes[idx] = TerrainType::Hill;
  151. }
  152. }
  153. }
  154. for (int idx : plateauCells) {
  155. m_hillWalkable[idx] = true;
  156. }
  157. for (const auto &entrance : feature.entrances) {
  158. int ex = int(std::round(entrance.x()));
  159. int ez = int(std::round(entrance.z()));
  160. if (!inBounds(ex, ez)) {
  161. continue;
  162. }
  163. const int entranceIdx = indexAt(ex, ez);
  164. m_hillEntrances[entranceIdx] = true;
  165. m_hillWalkable[entranceIdx] = true;
  166. float dirX = gridCenterX - float(ex);
  167. float dirZ = gridCenterZ - float(ez);
  168. float length = std::sqrt(dirX * dirX + dirZ * dirZ);
  169. if (length < 0.001f) {
  170. continue;
  171. }
  172. dirX /= length;
  173. dirZ /= length;
  174. float curX = float(ex);
  175. float curZ = float(ez);
  176. const int steps = int(length) + 3;
  177. for (int step = 0; step < steps; ++step) {
  178. int ix = int(std::round(curX));
  179. int iz = int(std::round(curZ));
  180. if (!inBounds(ix, iz)) {
  181. break;
  182. }
  183. const int idx = indexAt(ix, iz);
  184. const float cellDx = float(ix) - gridCenterX;
  185. const float cellDz = float(iz) - gridCenterZ;
  186. const float cellRotX = cellDx * cosA + cellDz * sinA;
  187. const float cellRotZ = -cellDx * sinA + cellDz * cosA;
  188. const float cellNormDist =
  189. std::sqrt((cellRotX * cellRotX) / (slopeWidth * slopeWidth) +
  190. (cellRotZ * cellRotZ) / (slopeDepth * slopeDepth));
  191. if (cellNormDist > 1.1f) {
  192. break;
  193. }
  194. m_hillWalkable[idx] = true;
  195. if (m_terrainTypes[idx] != TerrainType::Mountain) {
  196. m_terrainTypes[idx] = TerrainType::Hill;
  197. }
  198. if (m_heights[idx] < feature.height * 0.25f) {
  199. float t = std::clamp(cellNormDist, 0.0f, 1.0f);
  200. float rampHeight = feature.height * (1.0f - t * 0.85f);
  201. m_heights[idx] = std::max(m_heights[idx], rampHeight);
  202. }
  203. for (int oz = -1; oz <= 1; ++oz) {
  204. for (int ox = -1; ox <= 1; ++ox) {
  205. if (ox == 0 && oz == 0)
  206. continue;
  207. int nx = ix + ox;
  208. int nz = iz + oz;
  209. if (!inBounds(nx, nz))
  210. continue;
  211. const float nDx = float(nx) - gridCenterX;
  212. const float nDz = float(nz) - gridCenterZ;
  213. const float nRotX = nDx * cosA + nDz * sinA;
  214. const float nRotZ = -nDx * sinA + nDz * cosA;
  215. const float neighborNormDist =
  216. std::sqrt((nRotX * nRotX) / (slopeWidth * slopeWidth) +
  217. (nRotZ * nRotZ) / (slopeDepth * slopeDepth));
  218. if (neighborNormDist <= 1.05f) {
  219. int nIdx = indexAt(nx, nz);
  220. if (m_terrainTypes[nIdx] != TerrainType::Mountain) {
  221. m_hillWalkable[nIdx] = true;
  222. if (m_terrainTypes[nIdx] == TerrainType::Flat) {
  223. m_terrainTypes[nIdx] = TerrainType::Hill;
  224. }
  225. if (m_heights[nIdx] < m_heights[idx] * 0.8f) {
  226. m_heights[nIdx] =
  227. std::max(m_heights[nIdx], m_heights[idx] * 0.7f);
  228. }
  229. }
  230. }
  231. }
  232. }
  233. const float plateauNormDist =
  234. std::sqrt((cellRotX * cellRotX) / (plateauWidth * plateauWidth) +
  235. (cellRotZ * cellRotZ) / (plateauDepth * plateauDepth));
  236. if (plateauNormDist <= 1.05f) {
  237. break;
  238. }
  239. curX += dirX;
  240. curZ += dirZ;
  241. }
  242. }
  243. continue;
  244. }
  245. const float flatRadius = gridRadius;
  246. const int minX = std::max(0, int(std::floor(gridCenterX - flatRadius)));
  247. const int maxX =
  248. std::min(m_width - 1, int(std::ceil(gridCenterX + flatRadius)));
  249. const int minZ = std::max(0, int(std::floor(gridCenterZ - flatRadius)));
  250. const int maxZ =
  251. std::min(m_height - 1, int(std::ceil(gridCenterZ + flatRadius)));
  252. for (int z = minZ; z <= maxZ; ++z) {
  253. for (int x = minX; x <= maxX; ++x) {
  254. const float dx = float(x) - gridCenterX;
  255. const float dz = float(z) - gridCenterZ;
  256. const float dist = std::sqrt(dx * dx + dz * dz);
  257. if (dist > flatRadius)
  258. continue;
  259. float t = dist / std::max(flatRadius, 0.0001f);
  260. float height = feature.height * (1.0f - t);
  261. if (height <= 0.0f)
  262. continue;
  263. int idx = indexAt(x, z);
  264. if (height > m_heights[idx]) {
  265. m_heights[idx] = height;
  266. m_terrainTypes[idx] = TerrainType::Flat;
  267. }
  268. }
  269. }
  270. }
  271. }
  272. float TerrainHeightMap::getHeightAt(float worldX, float worldZ) const {
  273. const float gridHalfWidth = m_width * 0.5f - 0.5f;
  274. const float gridHalfHeight = m_height * 0.5f - 0.5f;
  275. float gx = worldX / m_tileSize + gridHalfWidth;
  276. float gz = worldZ / m_tileSize + gridHalfHeight;
  277. int x0 = int(std::floor(gx));
  278. int z0 = int(std::floor(gz));
  279. int x1 = x0 + 1;
  280. int z1 = z0 + 1;
  281. if (!inBounds(x0, z0))
  282. return 0.0f;
  283. float tx = gx - x0;
  284. float tz = gz - z0;
  285. float h00 = inBounds(x0, z0) ? m_heights[indexAt(x0, z0)] : 0.0f;
  286. float h10 = inBounds(x1, z0) ? m_heights[indexAt(x1, z0)] : 0.0f;
  287. float h01 = inBounds(x0, z1) ? m_heights[indexAt(x0, z1)] : 0.0f;
  288. float h11 = inBounds(x1, z1) ? m_heights[indexAt(x1, z1)] : 0.0f;
  289. float h0 = h00 * (1.0f - tx) + h10 * tx;
  290. float h1 = h01 * (1.0f - tx) + h11 * tx;
  291. return h0 * (1.0f - tz) + h1 * tz;
  292. }
  293. float TerrainHeightMap::getHeightAtGrid(int gridX, int gridZ) const {
  294. if (!inBounds(gridX, gridZ))
  295. return 0.0f;
  296. return m_heights[indexAt(gridX, gridZ)];
  297. }
  298. bool TerrainHeightMap::isWalkable(int gridX, int gridZ) const {
  299. if (!inBounds(gridX, gridZ))
  300. return false;
  301. TerrainType type = m_terrainTypes[indexAt(gridX, gridZ)];
  302. if (type == TerrainType::Mountain)
  303. return false;
  304. if (type == TerrainType::River)
  305. return false;
  306. if (type == TerrainType::Hill) {
  307. return m_hillWalkable[indexAt(gridX, gridZ)];
  308. }
  309. return true;
  310. }
  311. bool TerrainHeightMap::isHillEntrance(int gridX, int gridZ) const {
  312. if (!inBounds(gridX, gridZ))
  313. return false;
  314. return m_hillEntrances[indexAt(gridX, gridZ)];
  315. }
  316. TerrainType TerrainHeightMap::getTerrainType(int gridX, int gridZ) const {
  317. if (!inBounds(gridX, gridZ))
  318. return TerrainType::Flat;
  319. return m_terrainTypes[indexAt(gridX, gridZ)];
  320. }
  321. bool TerrainHeightMap::isRiverOrNearby(int gridX, int gridZ, int margin) const {
  322. if (!inBounds(gridX, gridZ))
  323. return false;
  324. if (m_terrainTypes[indexAt(gridX, gridZ)] == TerrainType::River)
  325. return true;
  326. for (int dz = -margin; dz <= margin; ++dz) {
  327. for (int dx = -margin; dx <= margin; ++dx) {
  328. if (dx == 0 && dz == 0)
  329. continue;
  330. int nx = gridX + dx;
  331. int nz = gridZ + dz;
  332. if (inBounds(nx, nz) &&
  333. m_terrainTypes[indexAt(nx, nz)] == TerrainType::River) {
  334. return true;
  335. }
  336. }
  337. }
  338. return false;
  339. }
  340. int TerrainHeightMap::indexAt(int x, int z) const { return z * m_width + x; }
  341. bool TerrainHeightMap::inBounds(int x, int z) const {
  342. return x >= 0 && x < m_width && z >= 0 && z < m_height;
  343. }
  344. float TerrainHeightMap::calculateFeatureHeight(const TerrainFeature &feature,
  345. float worldX,
  346. float worldZ) const {
  347. float dx = worldX - feature.centerX;
  348. float dz = worldZ - feature.centerZ;
  349. float dist = std::sqrt(dx * dx + dz * dz);
  350. if (dist > feature.radius)
  351. return 0.0f;
  352. float t = dist / feature.radius;
  353. float heightFactor = (std::cos(t * M_PI) + 1.0f) * 0.5f;
  354. return feature.height * heightFactor;
  355. }
  356. void TerrainHeightMap::applyBiomeVariation(const BiomeSettings &settings) {
  357. if (m_heights.empty())
  358. return;
  359. const float amplitude = std::max(0.0f, settings.heightNoiseAmplitude);
  360. if (amplitude <= 0.0001f)
  361. return;
  362. const float frequency = std::max(0.0001f, settings.heightNoiseFrequency);
  363. const float halfWidth = m_width * 0.5f - 0.5f;
  364. const float halfHeight = m_height * 0.5f - 0.5f;
  365. for (int z = 0; z < m_height; ++z) {
  366. for (int x = 0; x < m_width; ++x) {
  367. int idx = indexAt(x, z);
  368. TerrainType type = m_terrainTypes[idx];
  369. if (type == TerrainType::Mountain)
  370. continue;
  371. float worldX = (static_cast<float>(x) - halfWidth) * m_tileSize;
  372. float worldZ = (static_cast<float>(z) - halfHeight) * m_tileSize;
  373. float sampleX = worldX * frequency;
  374. float sampleZ = worldZ * frequency;
  375. float baseNoise = valueNoise2D(sampleX, sampleZ, settings.seed);
  376. float detailNoise = valueNoise2D(sampleX * 2.0f, sampleZ * 2.0f,
  377. settings.seed ^ 0xA21C9E37u);
  378. float blended = 0.65f * baseNoise + 0.35f * detailNoise;
  379. float perturb = (blended - 0.5f) * 2.0f * amplitude;
  380. if (type == TerrainType::Hill)
  381. perturb *= 0.6f;
  382. m_heights[idx] = std::max(0.0f, m_heights[idx] + perturb);
  383. }
  384. }
  385. }
  386. void TerrainHeightMap::addRiverSegments(
  387. const std::vector<RiverSegment> &riverSegments) {
  388. m_riverSegments = riverSegments;
  389. const float gridHalfWidth = m_width * 0.5f - 0.5f;
  390. const float gridHalfHeight = m_height * 0.5f - 0.5f;
  391. for (const auto &river : riverSegments) {
  392. QVector3D dir = river.end - river.start;
  393. float length = dir.length();
  394. if (length < 0.01f)
  395. continue;
  396. dir.normalize();
  397. QVector3D perpendicular(-dir.z(), 0.0f, dir.x());
  398. int steps = static_cast<int>(std::ceil(length / m_tileSize)) + 1;
  399. for (int i = 0; i < steps; ++i) {
  400. float t =
  401. static_cast<float>(i) / std::max(1.0f, static_cast<float>(steps - 1));
  402. QVector3D centerPos = river.start + dir * (length * t);
  403. float gridCenterX = (centerPos.x() / m_tileSize) + gridHalfWidth;
  404. float gridCenterZ = (centerPos.z() / m_tileSize) + gridHalfHeight;
  405. float halfWidth = river.width * 0.5f / m_tileSize;
  406. int minX = std::max(
  407. 0, static_cast<int>(std::floor(gridCenterX - halfWidth - 1.0f)));
  408. int maxX =
  409. std::min(m_width - 1,
  410. static_cast<int>(std::ceil(gridCenterX + halfWidth + 1.0f)));
  411. int minZ = std::max(
  412. 0, static_cast<int>(std::floor(gridCenterZ - halfWidth - 1.0f)));
  413. int maxZ =
  414. std::min(m_height - 1,
  415. static_cast<int>(std::ceil(gridCenterZ + halfWidth + 1.0f)));
  416. for (int z = minZ; z <= maxZ; ++z) {
  417. for (int x = minX; x <= maxX; ++x) {
  418. float dx = static_cast<float>(x) - gridCenterX;
  419. float dz = static_cast<float>(z) - gridCenterZ;
  420. float distAlongPerp =
  421. std::abs(dx * perpendicular.x() + dz * perpendicular.z());
  422. if (distAlongPerp <= halfWidth) {
  423. int idx = indexAt(x, z);
  424. if (m_terrainTypes[idx] != TerrainType::Mountain) {
  425. m_terrainTypes[idx] = TerrainType::River;
  426. m_heights[idx] = 0.0f;
  427. }
  428. }
  429. }
  430. }
  431. }
  432. }
  433. }
  434. void TerrainHeightMap::addBridges(const std::vector<Bridge> &bridges) {
  435. m_bridges = bridges;
  436. const float gridHalfWidth = m_width * 0.5f - 0.5f;
  437. const float gridHalfHeight = m_height * 0.5f - 0.5f;
  438. for (const auto &bridge : bridges) {
  439. QVector3D dir = bridge.end - bridge.start;
  440. float length = dir.length();
  441. if (length < 0.01f)
  442. continue;
  443. dir.normalize();
  444. QVector3D perpendicular(-dir.z(), 0.0f, dir.x());
  445. int steps = static_cast<int>(std::ceil(length / m_tileSize)) + 1;
  446. for (int i = 0; i < steps; ++i) {
  447. float t =
  448. static_cast<float>(i) / std::max(1.0f, static_cast<float>(steps - 1));
  449. QVector3D centerPos = bridge.start + dir * (length * t);
  450. float archCurve = 4.0f * t * (1.0f - t);
  451. float archHeight = bridge.height * archCurve * 0.8f;
  452. float deckHeight = bridge.start.y() + bridge.height + archHeight * 0.5f;
  453. float gridCenterX = (centerPos.x() / m_tileSize) + gridHalfWidth;
  454. float gridCenterZ = (centerPos.z() / m_tileSize) + gridHalfHeight;
  455. float halfWidth = bridge.width * 0.5f / m_tileSize;
  456. int minX =
  457. std::max(0, static_cast<int>(std::floor(gridCenterX - halfWidth)));
  458. int maxX = std::min(m_width - 1,
  459. static_cast<int>(std::ceil(gridCenterX + halfWidth)));
  460. int minZ =
  461. std::max(0, static_cast<int>(std::floor(gridCenterZ - halfWidth)));
  462. int maxZ = std::min(m_height - 1,
  463. static_cast<int>(std::ceil(gridCenterZ + halfWidth)));
  464. for (int z = minZ; z <= maxZ; ++z) {
  465. for (int x = minX; x <= maxX; ++x) {
  466. float dx = static_cast<float>(x) - gridCenterX;
  467. float dz = static_cast<float>(z) - gridCenterZ;
  468. float distAlongPerp =
  469. std::abs(dx * perpendicular.x() + dz * perpendicular.z());
  470. if (distAlongPerp <= halfWidth) {
  471. int idx = indexAt(x, z);
  472. if (m_terrainTypes[idx] == TerrainType::River) {
  473. m_terrainTypes[idx] = TerrainType::Flat;
  474. m_heights[idx] = deckHeight;
  475. }
  476. }
  477. }
  478. }
  479. }
  480. }
  481. }
  482. } // namespace Game::Map