source: ps/trunk/source/graphics/Terrain.cpp@ 25266

Last change on this file since 25266 was 25266, checked in by wraitii, 3 years ago

Use type_identity to simplify Clamp usage.

Differential Revision: https://code.wildfiregames.com/D3859

  • Property svn:eol-style set to native
File size: 26.6 KB
Line 
1/* Copyright (C) 2021 Wildfire Games.
2 * This file is part of 0 A.D.
3 *
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18/*
19 * Describes ground via heightmap and array of CPatch.
20 */
21
22#include "precompiled.h"
23
24#include "lib/res/graphics/ogl_tex.h"
25#include "lib/sysdep/cpu.h"
26
27#include "renderer/Renderer.h"
28
29#include "TerrainProperties.h"
30#include "TerrainTextureEntry.h"
31#include "TerrainTextureManager.h"
32
33#include <string.h>
34#include "Terrain.h"
35#include "Patch.h"
36#include "maths/FixedVector3D.h"
37#include "maths/MathUtil.h"
38#include "ps/CLogger.h"
39#include "simulation2/helpers/Pathfinding.h"
40
41///////////////////////////////////////////////////////////////////////////////
42// CTerrain constructor
43CTerrain::CTerrain()
44: m_Heightmap(0), m_Patches(0), m_MapSize(0), m_MapSizePatches(0),
45m_BaseColor(255, 255, 255, 255)
46{
47}
48
49///////////////////////////////////////////////////////////////////////////////
50// CTerrain constructor
51CTerrain::~CTerrain()
52{
53 ReleaseData();
54}
55
56
57///////////////////////////////////////////////////////////////////////////////
58// ReleaseData: delete any data allocated by this terrain
59void CTerrain::ReleaseData()
60{
61 m_HeightMipmap.ReleaseData();
62
63 delete[] m_Heightmap;
64 delete[] m_Patches;
65}
66
67
68///////////////////////////////////////////////////////////////////////////////
69// Initialise: initialise this terrain to the given size
70// using given heightmap to setup elevation data
71bool CTerrain::Initialize(ssize_t patchesPerSide, const u16* data)
72{
73 // clean up any previous terrain
74 ReleaseData();
75
76 // store terrain size
77 m_MapSize = patchesPerSide * PATCH_SIZE + 1;
78 m_MapSizePatches = patchesPerSide;
79 // allocate data for new terrain
80 m_Heightmap = new u16[m_MapSize * m_MapSize];
81 m_Patches = new CPatch[m_MapSizePatches * m_MapSizePatches];
82
83 // given a heightmap?
84 if (data)
85 {
86 // yes; keep a copy of it
87 memcpy(m_Heightmap, data, m_MapSize*m_MapSize*sizeof(u16));
88 }
89 else
90 {
91 // build a flat terrain
92 memset(m_Heightmap, 0, m_MapSize*m_MapSize*sizeof(u16));
93 }
94
95 // setup patch parents, indices etc
96 InitialisePatches();
97
98 // initialise mipmap
99 m_HeightMipmap.Initialize(m_MapSize, m_Heightmap);
100
101 return true;
102}
103
104///////////////////////////////////////////////////////////////////////////////
105
106CStr8 CTerrain::GetMovementClass(ssize_t i, ssize_t j) const
107{
108 CMiniPatch* tile = GetTile(i, j);
109 if (tile && tile->GetTextureEntry())
110 return tile->GetTextureEntry()->GetProperties().GetMovementClass();
111
112 return "default";
113}
114
115///////////////////////////////////////////////////////////////////////////////
116// CalcPosition: calculate the world space position of the vertex at (i,j)
117// If i,j is off the map, it acts as if the edges of the terrain are extended
118// outwards to infinity
119void CTerrain::CalcPosition(ssize_t i, ssize_t j, CVector3D& pos) const
120{
121 ssize_t hi = Clamp(i, 0, m_MapSize - 1);
122 ssize_t hj = Clamp(j, 0, m_MapSize - 1);
123 u16 height = m_Heightmap[hj*m_MapSize + hi];
124 pos.X = float(i*TERRAIN_TILE_SIZE);
125 pos.Y = float(height*HEIGHT_SCALE);
126 pos.Z = float(j*TERRAIN_TILE_SIZE);
127}
128
129///////////////////////////////////////////////////////////////////////////////
130// CalcPositionFixed: calculate the world space position of the vertex at (i,j)
131void CTerrain::CalcPositionFixed(ssize_t i, ssize_t j, CFixedVector3D& pos) const
132{
133 ssize_t hi = Clamp(i, 0, m_MapSize - 1);
134 ssize_t hj = Clamp(j, 0, m_MapSize - 1);
135 u16 height = m_Heightmap[hj*m_MapSize + hi];
136 pos.X = fixed::FromInt(i) * (int)TERRAIN_TILE_SIZE;
137 // fixed max value is 32767, but height is a u16, so divide by two to avoid overflow
138 pos.Y = fixed::FromInt(height/ 2 ) / ((int)HEIGHT_UNITS_PER_METRE / 2);
139 pos.Z = fixed::FromInt(j) * (int)TERRAIN_TILE_SIZE;
140}
141
142
143///////////////////////////////////////////////////////////////////////////////
144// CalcNormal: calculate the world space normal of the vertex at (i,j)
145void CTerrain::CalcNormal(ssize_t i, ssize_t j, CVector3D& normal) const
146{
147 CVector3D left, right, up, down;
148
149 // Calculate normals of the four half-tile triangles surrounding this vertex:
150
151 // get position of vertex where normal is being evaluated
152 CVector3D basepos;
153 CalcPosition(i, j, basepos);
154
155 if (i > 0) {
156 CalcPosition(i-1, j, left);
157 left -= basepos;
158 left.Normalize();
159 }
160
161 if (i < m_MapSize-1) {
162 CalcPosition(i+1, j, right);
163 right -= basepos;
164 right.Normalize();
165 }
166
167 if (j > 0) {
168 CalcPosition(i, j-1, up);
169 up -= basepos;
170 up.Normalize();
171 }
172
173 if (j < m_MapSize-1) {
174 CalcPosition(i, j+1, down);
175 down -= basepos;
176 down.Normalize();
177 }
178
179 CVector3D n0 = up.Cross(left);
180 CVector3D n1 = left.Cross(down);
181 CVector3D n2 = down.Cross(right);
182 CVector3D n3 = right.Cross(up);
183
184 // Compute the mean of the normals
185 normal = n0 + n1 + n2 + n3;
186 float nlen=normal.Length();
187 if (nlen>0.00001f) normal*=1.0f/nlen;
188}
189
190///////////////////////////////////////////////////////////////////////////////
191// CalcNormalFixed: calculate the world space normal of the vertex at (i,j)
192void CTerrain::CalcNormalFixed(ssize_t i, ssize_t j, CFixedVector3D& normal) const
193{
194 CFixedVector3D left, right, up, down;
195
196 // Calculate normals of the four half-tile triangles surrounding this vertex:
197
198 // get position of vertex where normal is being evaluated
199 CFixedVector3D basepos;
200 CalcPositionFixed(i, j, basepos);
201
202 if (i > 0) {
203 CalcPositionFixed(i-1, j, left);
204 left -= basepos;
205 left.Normalize();
206 }
207
208 if (i < m_MapSize-1) {
209 CalcPositionFixed(i+1, j, right);
210 right -= basepos;
211 right.Normalize();
212 }
213
214 if (j > 0) {
215 CalcPositionFixed(i, j-1, up);
216 up -= basepos;
217 up.Normalize();
218 }
219
220 if (j < m_MapSize-1) {
221 CalcPositionFixed(i, j+1, down);
222 down -= basepos;
223 down.Normalize();
224 }
225
226 CFixedVector3D n0 = up.Cross(left);
227 CFixedVector3D n1 = left.Cross(down);
228 CFixedVector3D n2 = down.Cross(right);
229 CFixedVector3D n3 = right.Cross(up);
230
231 // Compute the mean of the normals
232 normal = n0 + n1 + n2 + n3;
233 normal.Normalize();
234}
235
236CVector3D CTerrain::CalcExactNormal(float x, float z) const
237{
238 // Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
239 const ssize_t xi = Clamp<ssize_t>(floor(x / TERRAIN_TILE_SIZE), 0, m_MapSize - 2);
240 const ssize_t zi = Clamp<ssize_t>(floor(z / TERRAIN_TILE_SIZE), 0, m_MapSize - 2);
241
242 const float xf = Clamp(x / TERRAIN_TILE_SIZE-xi, 0.0f, 1.0f);
243 const float zf = Clamp(z / TERRAIN_TILE_SIZE-zi, 0.0f, 1.0f);
244
245 float h00 = m_Heightmap[zi*m_MapSize + xi];
246 float h01 = m_Heightmap[(zi+1)*m_MapSize + xi];
247 float h10 = m_Heightmap[zi*m_MapSize + (xi+1)];
248 float h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)];
249
250 // Determine which terrain triangle this point is on,
251 // then compute the normal of that triangle's plane
252
253 if (GetTriangulationDir(xi, zi))
254 {
255 if (xf + zf <= 1.f)
256 {
257 // Lower-left triangle (don't use h11)
258 return -CVector3D(TERRAIN_TILE_SIZE, (h10-h00)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h01-h00)*HEIGHT_SCALE, TERRAIN_TILE_SIZE)).Normalized();
259 }
260 else
261 {
262 // Upper-right triangle (don't use h00)
263 return -CVector3D(TERRAIN_TILE_SIZE, (h11-h01)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h11-h10)*HEIGHT_SCALE, TERRAIN_TILE_SIZE)).Normalized();
264 }
265 }
266 else
267 {
268 if (xf <= zf)
269 {
270 // Upper-left triangle (don't use h10)
271 return -CVector3D(TERRAIN_TILE_SIZE, (h11-h01)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h01-h00)*HEIGHT_SCALE, TERRAIN_TILE_SIZE)).Normalized();
272 }
273 else
274 {
275 // Lower-right triangle (don't use h01)
276 return -CVector3D(TERRAIN_TILE_SIZE, (h10-h00)*HEIGHT_SCALE, 0).Cross(CVector3D(0, (h11-h10)*HEIGHT_SCALE, TERRAIN_TILE_SIZE)).Normalized();
277 }
278 }
279}
280
281///////////////////////////////////////////////////////////////////////////////
282// GetPatch: return the patch at (i,j) in patch space, or null if the patch is
283// out of bounds
284CPatch* CTerrain::GetPatch(ssize_t i, ssize_t j) const
285{
286 // range check (invalid indices are passed in by the culling and
287 // patch blend code because they iterate from 0..#patches and examine
288 // neighbors without checking if they're already on the edge)
289 if( (size_t)i >= (size_t)m_MapSizePatches || (size_t)j >= (size_t)m_MapSizePatches )
290 return 0;
291
292 return &m_Patches[(j*m_MapSizePatches)+i];
293}
294
295
296///////////////////////////////////////////////////////////////////////////////
297// GetTile: return the tile at (i,j) in tile space, or null if the tile is out
298// of bounds
299CMiniPatch* CTerrain::GetTile(ssize_t i, ssize_t j) const
300{
301 // see comment above
302 if( (size_t)i >= (size_t)(m_MapSize-1) || (size_t)j >= (size_t)(m_MapSize-1) )
303 return 0;
304
305 CPatch* patch=GetPatch(i/PATCH_SIZE, j/PATCH_SIZE); // can't fail (due to above check)
306 return &patch->m_MiniPatches[j%PATCH_SIZE][i%PATCH_SIZE];
307}
308
309float CTerrain::GetVertexGroundLevel(ssize_t i, ssize_t j) const
310{
311 i = Clamp(i, 0, m_MapSize - 1);
312 j = Clamp(j, 0, m_MapSize - 1);
313 return HEIGHT_SCALE * m_Heightmap[j*m_MapSize + i];
314}
315
316fixed CTerrain::GetVertexGroundLevelFixed(ssize_t i, ssize_t j) const
317{
318 i = Clamp(i, 0, m_MapSize - 1);
319 j = Clamp(j, 0, m_MapSize - 1);
320 // Convert to fixed metres (being careful to avoid intermediate overflows)
321 return fixed::FromInt(m_Heightmap[j*m_MapSize + i] / 2) / (int)(HEIGHT_UNITS_PER_METRE / 2);
322}
323
324fixed CTerrain::GetSlopeFixed(ssize_t i, ssize_t j) const
325{
326 // Clamp to size-2 so we can use the tiles (i,j)-(i+1,j+1)
327 i = Clamp(i, 0, m_MapSize - 2);
328 j = Clamp(j, 0, m_MapSize - 2);
329
330 u16 h00 = m_Heightmap[j*m_MapSize + i];
331 u16 h01 = m_Heightmap[(j+1)*m_MapSize + i];
332 u16 h10 = m_Heightmap[j*m_MapSize + (i+1)];
333 u16 h11 = m_Heightmap[(j+1)*m_MapSize + (i+1)];
334
335 // Difference of highest point from lowest point
336 u16 delta = std::max(std::max(h00, h01), std::max(h10, h11)) -
337 std::min(std::min(h00, h01), std::min(h10, h11));
338
339 // Compute fractional slope (being careful to avoid intermediate overflows)
340 return fixed::FromInt(delta / TERRAIN_TILE_SIZE) / (int)HEIGHT_UNITS_PER_METRE;
341}
342
343fixed CTerrain::GetExactSlopeFixed(fixed x, fixed z) const
344{
345 // Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
346 const ssize_t xi = Clamp<ssize_t>((x / static_cast<int>(TERRAIN_TILE_SIZE)).ToInt_RoundToZero(), 0, m_MapSize - 2);
347 const ssize_t zi = Clamp<ssize_t>((z / static_cast<int>(TERRAIN_TILE_SIZE)).ToInt_RoundToZero(), 0, m_MapSize - 2);
348
349 const fixed one = fixed::FromInt(1);
350
351 const fixed xf = Clamp((x / static_cast<int>(TERRAIN_TILE_SIZE)) - fixed::FromInt(xi), fixed::Zero(), one);
352 const fixed zf = Clamp((z / static_cast<int>(TERRAIN_TILE_SIZE)) - fixed::FromInt(zi), fixed::Zero(), one);
353
354 u16 h00 = m_Heightmap[zi*m_MapSize + xi];
355 u16 h01 = m_Heightmap[(zi+1)*m_MapSize + xi];
356 u16 h10 = m_Heightmap[zi*m_MapSize + (xi+1)];
357 u16 h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)];
358
359 u16 delta;
360 if (GetTriangulationDir(xi, zi))
361 {
362 if (xf + zf <= one)
363 {
364 // Lower-left triangle (don't use h11)
365 delta = std::max(std::max(h00, h01), h10) -
366 std::min(std::min(h00, h01), h10);
367 }
368 else
369 {
370 // Upper-right triangle (don't use h00)
371 delta = std::max(std::max(h01, h10), h11) -
372 std::min(std::min(h01, h10), h11);
373 }
374 }
375 else
376 {
377 if (xf <= zf)
378 {
379 // Upper-left triangle (don't use h10)
380 delta = std::max(std::max(h00, h01), h11) -
381 std::min(std::min(h00, h01), h11);
382 }
383 else
384 {
385 // Lower-right triangle (don't use h01)
386 delta = std::max(std::max(h00, h10), h11) -
387 std::min(std::min(h00, h10), h11);
388 }
389 }
390
391 // Compute fractional slope (being careful to avoid intermediate overflows)
392 return fixed::FromInt(delta / TERRAIN_TILE_SIZE) / (int)HEIGHT_UNITS_PER_METRE;
393}
394
395float CTerrain::GetFilteredGroundLevel(float x, float z, float radius) const
396{
397 // convert to [0,1] interval
398 float nx = x / (TERRAIN_TILE_SIZE*m_MapSize);
399 float nz = z / (TERRAIN_TILE_SIZE*m_MapSize);
400 float nr = radius / (TERRAIN_TILE_SIZE*m_MapSize);
401
402 // get trilinear filtered mipmap height
403 return HEIGHT_SCALE * m_HeightMipmap.GetTrilinearGroundLevel(nx, nz, nr);
404}
405
406float CTerrain::GetExactGroundLevel(float x, float z) const
407{
408 // Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
409 const ssize_t xi = Clamp<ssize_t>(floor(x / TERRAIN_TILE_SIZE), 0, m_MapSize - 2);
410 const ssize_t zi = Clamp<ssize_t>(floor(z / TERRAIN_TILE_SIZE), 0, m_MapSize - 2);
411
412 const float xf = Clamp(x / TERRAIN_TILE_SIZE - xi, 0.0f, 1.0f);
413 const float zf = Clamp(z / TERRAIN_TILE_SIZE - zi, 0.0f, 1.0f);
414
415 float h00 = m_Heightmap[zi*m_MapSize + xi];
416 float h01 = m_Heightmap[(zi+1)*m_MapSize + xi];
417 float h10 = m_Heightmap[zi*m_MapSize + (xi+1)];
418 float h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)];
419
420 // Determine which terrain triangle this point is on,
421 // then compute the linearly-interpolated height on that triangle's plane
422
423 if (GetTriangulationDir(xi, zi))
424 {
425 if (xf + zf <= 1.f)
426 {
427 // Lower-left triangle (don't use h11)
428 return HEIGHT_SCALE * (h00 + (h10-h00)*xf + (h01-h00)*zf);
429 }
430 else
431 {
432 // Upper-right triangle (don't use h00)
433 return HEIGHT_SCALE * (h11 + (h01-h11)*(1-xf) + (h10-h11)*(1-zf));
434 }
435 }
436 else
437 {
438 if (xf <= zf)
439 {
440 // Upper-left triangle (don't use h10)
441 return HEIGHT_SCALE * (h00 + (h11-h01)*xf + (h01-h00)*zf);
442 }
443 else
444 {
445 // Lower-right triangle (don't use h01)
446 return HEIGHT_SCALE * (h00 + (h10-h00)*xf + (h11-h10)*zf);
447 }
448 }
449}
450
451fixed CTerrain::GetExactGroundLevelFixed(fixed x, fixed z) const
452{
453 // Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
454 const ssize_t xi = Clamp<ssize_t>((x / static_cast<int>(TERRAIN_TILE_SIZE)).ToInt_RoundToZero(), 0, m_MapSize - 2);
455 const ssize_t zi = Clamp<ssize_t>((z / static_cast<int>(TERRAIN_TILE_SIZE)).ToInt_RoundToZero(), 0, m_MapSize - 2);
456
457 const fixed one = fixed::FromInt(1);
458
459 const fixed xf = Clamp((x / static_cast<int>(TERRAIN_TILE_SIZE)) - fixed::FromInt(xi), fixed::Zero(), one);
460 const fixed zf = Clamp((z / static_cast<int>(TERRAIN_TILE_SIZE)) - fixed::FromInt(zi), fixed::Zero(), one);
461
462 u16 h00 = m_Heightmap[zi*m_MapSize + xi];
463 u16 h01 = m_Heightmap[(zi+1)*m_MapSize + xi];
464 u16 h10 = m_Heightmap[zi*m_MapSize + (xi+1)];
465 u16 h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)];
466
467 // Intermediate scaling of xf, so we don't overflow in the multiplications below
468 // (h00 <= 65535, xf <= 1, max fixed is < 32768; divide by 2 here so xf1*h00 <= 32767.5)
469 const fixed xf0 = xf / 2;
470 const fixed xf1 = (one - xf) / 2;
471
472 // Linearly interpolate
473 return ((one - zf).Multiply(xf1 * h00 + xf0 * h10)
474 + zf.Multiply(xf1 * h01 + xf0 * h11)) / (int)(HEIGHT_UNITS_PER_METRE / 2);
475
476 // TODO: This should probably be more like GetExactGroundLevel()
477 // in handling triangulation properly
478}
479
480bool CTerrain::GetTriangulationDir(ssize_t i, ssize_t j) const
481{
482 // Clamp to size-2 so we can use the tiles (i,j)-(i+1,j+1)
483 i = Clamp(i, 0, m_MapSize - 2);
484 j = Clamp(j, 0, m_MapSize - 2);
485
486 int h00 = m_Heightmap[j*m_MapSize + i];
487 int h01 = m_Heightmap[(j+1)*m_MapSize + i];
488 int h10 = m_Heightmap[j*m_MapSize + (i+1)];
489 int h11 = m_Heightmap[(j+1)*m_MapSize + (i+1)];
490
491 // Prefer triangulating in whichever direction means the midpoint of the diagonal
492 // will be the highest. (In particular this means a diagonal edge will be straight
493 // along the top, and jagged along the bottom, which makes sense for terrain.)
494 int mid1 = h00+h11;
495 int mid2 = h01+h10;
496 return (mid1 < mid2);
497}
498
499void CTerrain::ResizeAndOffset(ssize_t size, ssize_t horizontalOffset, ssize_t verticalOffset)
500{
501 if (size == m_MapSizePatches && horizontalOffset == 0 && verticalOffset == 0)
502 {
503 // Inexplicable request to resize terrain to the same size, ignore it.
504 return;
505 }
506
507 if (!m_Heightmap ||
508 std::abs(horizontalOffset) >= size / 2 + m_MapSizePatches / 2 ||
509 std::abs(verticalOffset) >= size / 2 + m_MapSizePatches / 2)
510 {
511 // We have not yet created a terrain, or we are offsetting outside the current source.
512 // Let's build a default terrain of the given size now.
513 Initialize(size, 0);
514 return;
515 }
516
517 // Allocate data for new terrain.
518 const ssize_t newMapSize = size * PATCH_SIZE + 1;
519 u16* newHeightmap = new u16[newMapSize * newMapSize];
520 memset(newHeightmap, 0, newMapSize * newMapSize * sizeof(u16));
521 CPatch* newPatches = new CPatch[size * size];
522
523 // O--------------------+
524 // | Source |
525 // | |
526 // | Source Center (SC) |
527 // | X |
528 // | A------+----------------+
529 // | | | Destination |
530 // | | | |
531 // +-------------+------B |
532 // | Dest. Center (DC) |
533 // | X |
534 // | |
535 // | |
536 // | |
537 // | |
538 // +-----------------------+
539 //
540 // Calculations below should also account cases like:
541 //
542 // +----------+ +----------+ +----------+ +---+--+---+ +------+
543 // |S | |D | |S | |S | | D| |D |
544 // | +---+ | | +---+ | +-+-+ | | | | | | +---+--+
545 // | | D | | | | S | | |D| | | +---+--+---+ +--+---+ |
546 // | +---+ | | +---+ | +-+-+ | | S|
547 // +----------+ +----------+ +----------+ +------+
548 //
549 // O = (0, 0)
550 // SC = (m_MapSizePatches / 2, m_MapSizePatches / 2)
551 // DC - SC = (horizontalOffset, verticalOffset)
552 //
553 // Source upper left:
554 // A = (max(0, (m_MapSizePatches - size) / 2 + horizontalOffset),
555 // max(0, (m_MapSizePatches - size) / 2 + verticalOffset))
556 // Source bottom right:
557 // B = (min(m_MapSizePatches, (m_MapSizePatches + size) / 2 + horizontalOffset),
558 // min(m_MapSizePatches, (m_MapSizePatches + size) / 2 + verticalOffset))
559 //
560 // A-B is the area that we have to copy from the source to the destination.
561
562 // Restate center offset as a window over destination.
563 // This has the effect of always considering the source to be the same size or smaller.
564 const ssize_t sourceUpperLeftX = std::max(
565 static_cast<ssize_t>(0), m_MapSizePatches / 2 - size / 2 + horizontalOffset);
566 const ssize_t sourceUpperLeftZ = std::max(
567 static_cast<ssize_t>(0), m_MapSizePatches / 2 - size / 2 + verticalOffset);
568
569 const ssize_t destUpperLeftX = std::max(
570 static_cast<ssize_t>(0), (size / 2 - m_MapSizePatches / 2 - horizontalOffset));
571 const ssize_t destUpperLeftZ = std::max(
572 static_cast<ssize_t>(0), (size / 2 - m_MapSizePatches / 2 - verticalOffset));
573
574 const ssize_t width =
575 std::min(m_MapSizePatches, m_MapSizePatches / 2 + horizontalOffset + size / 2) - sourceUpperLeftX;
576 const ssize_t depth =
577 std::min(m_MapSizePatches, m_MapSizePatches / 2 + verticalOffset + size / 2) - sourceUpperLeftZ;
578
579 for (ssize_t j = 0; j < depth * PATCH_SIZE; ++j)
580 {
581 // Copy the main part from the source. Destination heightmap:
582 // +----------+
583 // | |
584 // | 1234 | < current j-th row for example.
585 // | 5678 |
586 // | |
587 // +----------+
588 u16* dst = newHeightmap + (j + destUpperLeftZ * PATCH_SIZE) * newMapSize + destUpperLeftX * PATCH_SIZE;
589 u16* src = m_Heightmap + (j + sourceUpperLeftZ * PATCH_SIZE) * m_MapSize + sourceUpperLeftX * PATCH_SIZE;
590 std::copy_n(src, width * PATCH_SIZE, dst);
591 if (destUpperLeftX > 0)
592 {
593 // Fill the preceding part by copying the first elements of the
594 // main part. Destination heightmap:
595 // +----------+
596 // | |
597 // |1111234 | < current j-th row for example.
598 // | 5678 |
599 // | |
600 // +----------+
601 u16* dst_prefix = newHeightmap + (j + destUpperLeftZ * PATCH_SIZE) * newMapSize;
602 std::fill_n(dst_prefix, destUpperLeftX * PATCH_SIZE, dst[0]);
603 }
604 if ((destUpperLeftX + width) * PATCH_SIZE < newMapSize)
605 {
606 // Fill the succeeding part by copying the last elements of the
607 // main part. Destination heightmap:
608 // +----------+
609 // | |
610 // |1111234444| < current j-th row for example.
611 // | 5678 |
612 // | |
613 // +----------+
614 u16* dst_suffix = dst + width * PATCH_SIZE;
615 std::fill_n(
616 dst_suffix,
617 newMapSize - (width + destUpperLeftX) * PATCH_SIZE,
618 dst[width * PATCH_SIZE - 1]);
619 }
620 }
621 // Copy over heights from the preceding row. Destination heightmap:
622 // +----------+
623 // |1111234444| < copied from the row below
624 // |1111234444|
625 // |5555678888|
626 // | |
627 // +----------+
628 for (ssize_t j = 0; j < destUpperLeftZ * PATCH_SIZE; ++j)
629 {
630
631 u16* dst = newHeightmap + j * newMapSize;
632 u16* src = newHeightmap + destUpperLeftZ * PATCH_SIZE * newMapSize;
633 std::copy_n(src, newMapSize, dst);
634 }
635 // Copy over heights from the succeeding row. Destination heightmap:
636 // +----------+
637 // |1111234444|
638 // |1111234444|
639 // |5555678888|
640 // |5555678888| < copied from the row above
641 // +----------+
642 for (ssize_t j = (destUpperLeftZ + depth) * PATCH_SIZE; j < newMapSize; ++j)
643 {
644 u16* dst = newHeightmap + j * newMapSize;
645 u16* src = newHeightmap + ((destUpperLeftZ + depth) * PATCH_SIZE - 1) * newMapSize;
646 std::copy_n(src, newMapSize, dst);
647 }
648
649 // Now build new patches. The same process as for the heightmap.
650 for (ssize_t j = 0; j < depth; ++j)
651 {
652 for (ssize_t i = 0; i < width; ++i)
653 {
654 const CPatch& src =
655 m_Patches[(sourceUpperLeftZ + j) * m_MapSizePatches + sourceUpperLeftX + i];
656 CPatch& dst =
657 newPatches[(destUpperLeftZ + j) * size + destUpperLeftX + i];
658 std::copy_n(&src.m_MiniPatches[0][0], PATCH_SIZE * PATCH_SIZE, &dst.m_MiniPatches[0][0]);
659 }
660 for (ssize_t i = 0; i < destUpperLeftX; ++i)
661 for (ssize_t jPatch = 0; jPatch < PATCH_SIZE; ++jPatch)
662 {
663 const CMiniPatch& src =
664 newPatches[(destUpperLeftZ + j) * size + destUpperLeftX]
665 .m_MiniPatches[jPatch][0];
666 for (ssize_t iPatch = 0; iPatch < PATCH_SIZE; ++iPatch)
667 {
668 CMiniPatch& dst =
669 newPatches[(destUpperLeftZ + j) * size + i]
670 .m_MiniPatches[jPatch][iPatch];
671 dst = src;
672 }
673 }
674 for (ssize_t i = destUpperLeftX + width; i < size; ++i)
675 {
676 for (ssize_t jPatch = 0; jPatch < PATCH_SIZE; ++jPatch)
677 {
678 const CMiniPatch& src =
679 newPatches[(destUpperLeftZ + j) * size + destUpperLeftX + width - 1]
680 .m_MiniPatches[jPatch][PATCH_SIZE - 1];
681 for (ssize_t iPatch = 0; iPatch < PATCH_SIZE; ++iPatch)
682 {
683 CMiniPatch& dst =
684 newPatches[(destUpperLeftZ + j) * size + i].m_MiniPatches[jPatch][iPatch];
685 dst = src;
686 }
687 }
688 }
689 }
690
691 for (ssize_t j = 0; j < destUpperLeftZ; ++j)
692 for (ssize_t i = 0; i < size; ++i)
693 for (ssize_t iPatch = 0; iPatch < PATCH_SIZE; ++iPatch)
694 {
695 const CMiniPatch& src =
696 newPatches[destUpperLeftZ * size + i].m_MiniPatches[0][iPatch];
697 for (ssize_t jPatch = 0; jPatch < PATCH_SIZE; ++jPatch)
698 {
699 CMiniPatch& dst =
700 newPatches[j * size + i].m_MiniPatches[jPatch][iPatch];
701 dst = src;
702 }
703 }
704 for (ssize_t j = destUpperLeftZ + depth; j < size; ++j)
705 for (ssize_t i = 0; i < size; ++i)
706 for (ssize_t iPatch = 0; iPatch < PATCH_SIZE; ++iPatch)
707 {
708 const CMiniPatch& src =
709 newPatches[(destUpperLeftZ + depth - 1) * size + i].m_MiniPatches[0][iPatch];
710 for (ssize_t jPatch = 0; jPatch < PATCH_SIZE; ++jPatch)
711 {
712 CMiniPatch& dst =
713 newPatches[j * size + i].m_MiniPatches[jPatch][iPatch];
714 dst = src;
715 }
716 }
717
718 // Release all the original data.
719 ReleaseData();
720
721 // Store new data.
722 m_Heightmap = newHeightmap;
723 m_Patches = newPatches;
724 m_MapSize = newMapSize;
725 m_MapSizePatches = size;
726
727 // Initialise all the new patches.
728 InitialisePatches();
729
730 // Initialise mipmap.
731 m_HeightMipmap.Initialize(m_MapSize, m_Heightmap);
732}
733
734///////////////////////////////////////////////////////////////////////////////
735// InitialisePatches: initialise patch data
736void CTerrain::InitialisePatches()
737{
738 for (ssize_t j = 0; j < m_MapSizePatches; j++)
739 {
740 for (ssize_t i = 0; i < m_MapSizePatches; i++)
741 {
742 CPatch* patch = GetPatch(i, j); // can't fail
743 patch->Initialize(this, i, j);
744 }
745 }
746}
747
748///////////////////////////////////////////////////////////////////////////////
749// SetHeightMap: set up a new heightmap from 16-bit source data;
750// assumes heightmap matches current terrain size
751void CTerrain::SetHeightMap(u16* heightmap)
752{
753 // keep a copy of the given heightmap
754 memcpy(m_Heightmap, heightmap, m_MapSize*m_MapSize*sizeof(u16));
755
756 // recalculate patch bounds, invalidate vertices
757 for (ssize_t j = 0; j < m_MapSizePatches; j++)
758 {
759 for (ssize_t i = 0; i < m_MapSizePatches; i++)
760 {
761 CPatch* patch = GetPatch(i, j); // can't fail
762 patch->InvalidateBounds();
763 patch->SetDirty(RENDERDATA_UPDATE_VERTICES);
764 }
765 }
766
767 // update mipmap
768 m_HeightMipmap.Update(m_Heightmap);
769}
770
771
772///////////////////////////////////////////////////////////////////////////////
773
774void CTerrain::MakeDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1, int dirtyFlags)
775{
776 // Finds the inclusive limits of the patches that include the specified range of tiles
777 ssize_t pi0 = Clamp( i0 /PATCH_SIZE, 0, m_MapSizePatches-1);
778 ssize_t pi1 = Clamp((i1-1)/PATCH_SIZE, 0, m_MapSizePatches-1);
779 ssize_t pj0 = Clamp( j0 /PATCH_SIZE, 0, m_MapSizePatches-1);
780 ssize_t pj1 = Clamp((j1-1)/PATCH_SIZE, 0, m_MapSizePatches-1);
781
782 for (ssize_t j = pj0; j <= pj1; j++)
783 {
784 for (ssize_t i = pi0; i <= pi1; i++)
785 {
786 CPatch* patch = GetPatch(i, j); // can't fail (i,j were clamped)
787 if (dirtyFlags & RENDERDATA_UPDATE_VERTICES)
788 patch->CalcBounds();
789 patch->SetDirty(dirtyFlags);
790 }
791 }
792
793 if (m_Heightmap)
794 {
795 m_HeightMipmap.Update(m_Heightmap,
796 Clamp(i0, 0, m_MapSize - 1),
797 Clamp(j0, 0, m_MapSize - 1),
798 Clamp(i1, 1, m_MapSize),
799 Clamp(j1, 1, m_MapSize)
800 );
801 }
802}
803
804void CTerrain::MakeDirty(int dirtyFlags)
805{
806 for (ssize_t j = 0; j < m_MapSizePatches; j++)
807 {
808 for (ssize_t i = 0; i < m_MapSizePatches; i++)
809 {
810 CPatch* patch = GetPatch(i, j); // can't fail
811 if (dirtyFlags & RENDERDATA_UPDATE_VERTICES)
812 patch->CalcBounds();
813 patch->SetDirty(dirtyFlags);
814 }
815 }
816
817 if (m_Heightmap)
818 m_HeightMipmap.Update(m_Heightmap);
819}
820
821CBoundingBoxAligned CTerrain::GetVertexesBound(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1)
822{
823 i0 = Clamp(i0, 0, m_MapSize - 1);
824 j0 = Clamp(j0, 0, m_MapSize - 1);
825 i1 = Clamp(i1, 0, m_MapSize - 1);
826 j1 = Clamp(j1, 0, m_MapSize - 1);
827
828 u16 minH = 65535;
829 u16 maxH = 0;
830
831 for (ssize_t j = j0; j <= j1; ++j)
832 {
833 for (ssize_t i = i0; i <= i1; ++i)
834 {
835 minH = std::min(minH, m_Heightmap[j*m_MapSize + i]);
836 maxH = std::max(maxH, m_Heightmap[j*m_MapSize + i]);
837 }
838 }
839
840 CBoundingBoxAligned bound;
841 bound[0].X = (float)(i0*TERRAIN_TILE_SIZE);
842 bound[0].Y = (float)(minH*HEIGHT_SCALE);
843 bound[0].Z = (float)(j0*TERRAIN_TILE_SIZE);
844 bound[1].X = (float)(i1*TERRAIN_TILE_SIZE);
845 bound[1].Y = (float)(maxH*HEIGHT_SCALE);
846 bound[1].Z = (float)(j1*TERRAIN_TILE_SIZE);
847 return bound;
848}
Note: See TracBrowser for help on using the repository browser.