1 /*
2 * This file is part of the Colobot: Gold Edition source code
3 * Copyright (C) 2001-2020, Daniel Roux, EPSITEC SA & TerranovaTeam
4 * http://epsitec.ch; http://colobot.info; http://github.com/colobot
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 * See the GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see http://gnu.org/licenses
18 */
19
20
21 #include "graphics/engine/terrain.h"
22
23 #include "app/app.h"
24
25 #include "common/image.h"
26 #include "common/logger.h"
27
28 #include "graphics/engine/engine.h"
29 #include "graphics/engine/water.h"
30
31 #include "math/geometry.h"
32
33 #include <sstream>
34
35 #include <SDL.h>
36
37
38 // Graphics module namespace
39 namespace Gfx
40 {
41
42
CTerrain()43 CTerrain::CTerrain()
44 {
45 m_engine = CEngine::GetInstancePointer();
46 m_water = m_engine->GetWater();
47
48 m_mosaicCount = 20;
49 m_brickCount = 1 << 4;
50 m_brickSize = 10.0f;
51 m_vision = 200.0f;
52 m_textureScale = 0.01f;
53 m_scaleRelief = 1.0f;
54 m_textureSubdivCount = 1;
55 m_depth = 2;
56 m_maxMaterialID = 0;
57 m_wind = Math::Vector(0.0f, 0.0f, 0.0f);
58 m_defaultHardness = 0.5f;
59 m_useMaterials = false;
60
61 m_flyingMaxHeight = 0.0f;
62 m_maxMaterialID = 0;
63 m_materialAutoID = 0;
64 m_materialPointCount = 0;
65
66 FlushBuildingLevel();
67 FlushFlyingLimit();
68 FlushMaterials();
69 }
70
~CTerrain()71 CTerrain::~CTerrain()
72 {
73 }
74
Generate(int mosaicCount,int brickCountPow2,float brickSize,float vision,int depth,float hardness)75 bool CTerrain::Generate(int mosaicCount, int brickCountPow2, float brickSize,
76 float vision, int depth, float hardness)
77 {
78 m_mosaicCount = mosaicCount;
79 m_brickCount = 1 << brickCountPow2;
80 m_brickSize = brickSize;
81 m_vision = vision;
82 m_depth = depth;
83 m_defaultHardness = hardness;
84
85 m_engine->SetTerrainVision(vision);
86
87 m_textureScale = 1.0f / (m_brickCount*m_brickSize);
88 m_textureSubdivCount = 1;
89
90 m_useMaterials = false;
91
92 int dim = 0;
93
94 dim = (m_mosaicCount*m_brickCount+1)*(m_mosaicCount*m_brickCount+1);
95 std::vector<float>(dim).swap(m_relief);
96
97 dim = m_mosaicCount*m_textureSubdivCount*m_mosaicCount*m_textureSubdivCount;
98 std::vector<int>(dim).swap(m_textures);
99
100 dim = m_mosaicCount*m_mosaicCount;
101 std::vector<int>(dim, -1).swap(m_objRanks);
102
103 return true;
104 }
105
106
GetMosaicCount()107 int CTerrain::GetMosaicCount()
108 {
109 return m_mosaicCount;
110 }
111
GetBrickCount()112 int CTerrain::GetBrickCount()
113 {
114 return m_brickCount;
115 }
116
GetBrickSize()117 float CTerrain::GetBrickSize()
118 {
119 return m_brickSize;
120 }
121
GetReliefScale()122 float CTerrain::GetReliefScale()
123 {
124 return m_scaleRelief;
125 }
126
InitTextures(const std::string & baseName,int * table,int dx,int dy)127 bool CTerrain::InitTextures(const std::string& baseName, int* table, int dx, int dy)
128 {
129 m_useMaterials = false;
130
131 m_texBaseName = baseName;
132 auto pos = baseName.rfind('.');
133 if(pos < baseName.find_last_of('/')) pos = std::string::npos; // If last . is not a part of filename (some directory, possibly . or ..)
134
135 if (pos == std::string::npos)
136 {
137 m_texBaseExt = ".png";
138 }
139 else
140 {
141 m_texBaseExt = m_texBaseName.substr(pos);
142 m_texBaseName = m_texBaseName.substr(0, pos);
143 }
144
145 for (int y = 0; y < m_mosaicCount*m_textureSubdivCount; y++)
146 {
147 for (int x = 0; x < m_mosaicCount*m_textureSubdivCount; x++)
148 {
149 m_textures[x+y*m_mosaicCount] = table[(x%dx)+(y%dy)*dx];
150 }
151 }
152 return true;
153 }
154
155
FlushMaterials()156 void CTerrain::FlushMaterials()
157 {
158 m_materials.clear();
159 m_maxMaterialID = 0;
160 m_materialAutoID = 1000;
161 FlushMaterialPoints();
162 }
163
AddMaterial(int id,const std::string & texName,const Math::Point & uv,int up,int right,int down,int left,float hardness)164 void CTerrain::AddMaterial(int id, const std::string& texName, const Math::Point &uv,
165 int up, int right, int down, int left,
166 float hardness)
167 {
168 InitMaterialPoints();
169
170 if (id == 0)
171 id = m_materialAutoID++;
172
173 TerrainMaterial tm;
174 tm.texName = texName;
175 tm.id = id;
176 tm.uv = uv;
177 tm.mat[0] = up;
178 tm.mat[1] = right;
179 tm.mat[2] = down;
180 tm.mat[3] = left;
181 tm.hardness = hardness;
182
183 m_materials.push_back(tm);
184
185 if (m_maxMaterialID < up+1 ) m_maxMaterialID = up+1;
186 if (m_maxMaterialID < right+1) m_maxMaterialID = right+1;
187 if (m_maxMaterialID < down+1 ) m_maxMaterialID = down+1;
188 if (m_maxMaterialID < left+1 ) m_maxMaterialID = left+1;
189
190 m_useMaterials = true;
191 m_textureSubdivCount = 4;
192 }
193
194
195 // values from original bitmap palette
196 const std::map<TerrainRes, Gfx::IntColor> RESOURCE_PALETTE = {
197 {TR_STONE, Gfx::IntColor(255, 0, 0)},
198 {TR_URANIUM, Gfx::IntColor(255, 255, 0)},
199 {TR_POWER, Gfx::IntColor( 0, 255, 0)},
200 {TR_KEY_A, Gfx::IntColor( 0, 204, 0)},
201 {TR_KEY_B, Gfx::IntColor( 51, 204, 0)},
202 {TR_KEY_C, Gfx::IntColor(102, 204, 0)},
203 {TR_KEY_D, Gfx::IntColor(153, 204, 0)}
204 };
205
ResourceToColor(TerrainRes res)206 Gfx::IntColor ResourceToColor(TerrainRes res)
207 {
208 return RESOURCE_PALETTE.at(res);
209 }
210
211 /**
212 * The image must be 24 bits/pixel and grayscale and dx x dy in size
213 * with dx = dy = (mosaic*brick)+1 */
LoadResources(const std::string & fileName)214 bool CTerrain::LoadResources(const std::string& fileName)
215 {
216 CImage img;
217
218 if (! img.Load(fileName))
219 {
220 GetLogger()->Error("Cannot load resource file: '%s'\n", fileName.c_str());
221 return false;
222 }
223
224 ImageData *data = img.GetData();
225
226 int size = (m_mosaicCount*m_brickCount)+1;
227
228 std::vector<unsigned char>(size*size).swap(m_resources);
229
230 if ( (data->surface->w != size) || (data->surface->h != size) )
231 {
232 GetLogger()->Error("Invalid resource file! Expected %dx%d\n", size, size);
233 return false;
234 }
235
236 for (int x = 0; x < size; ++x)
237 {
238 for (int y = 0; y < size; ++y)
239 {
240 Gfx::IntColor pixel = img.GetPixelInt(Math::IntPoint(x, size - y - 1));
241 TerrainRes res = TR_NULL;
242
243 for (const auto& it : RESOURCE_PALETTE)
244 {
245 if (pixel.r == it.second.r && pixel.g == it.second.g && pixel.b == it.second.b)
246 res = it.first;
247 }
248
249 m_resources[x+size*y] = static_cast<unsigned char>(res);
250 }
251 }
252
253 return true;
254 }
255
GetResource(const Math::Vector & pos)256 TerrainRes CTerrain::GetResource(const Math::Vector &pos)
257 {
258 if (m_resources.empty())
259 return TR_NULL;
260
261 int x = static_cast<int>((pos.x + (m_mosaicCount*m_brickCount*m_brickSize)/2.0f)/m_brickSize);
262 int y = static_cast<int>((pos.z + (m_mosaicCount*m_brickCount*m_brickSize)/2.0f)/m_brickSize);
263
264 if ( x < 0 || x > m_mosaicCount*m_brickCount ||
265 y < 0 || y > m_mosaicCount*m_brickCount )
266 return TR_NULL;
267
268 int size = (m_mosaicCount*m_brickCount)+1;
269
270 return static_cast<TerrainRes>( m_resources[x+size*y] );
271 }
272
FlushRelief()273 void CTerrain::FlushRelief()
274 {
275 m_relief.clear();
276 m_resources.clear();
277 m_textures.clear();
278
279 for (int objRank : m_objRanks)
280 {
281 if (objRank == -1)
282 continue;
283
284 int baseObjRank = m_engine->GetObjectBaseRank(objRank);
285
286 if (baseObjRank != -1)
287 m_engine->DeleteBaseObject(baseObjRank);
288
289 m_engine->DeleteObject(objRank);
290 }
291
292 m_objRanks.clear();
293 }
294
295 /**
296 * The image must be 24 bits/pixel and dx x dy in size
297 * with dx = dy = (mosaic*brick)+1 */
LoadRelief(const std::string & fileName,float scaleRelief,bool adjustBorder)298 bool CTerrain::LoadRelief(const std::string &fileName, float scaleRelief,
299 bool adjustBorder)
300 {
301 m_scaleRelief = scaleRelief;
302
303 CImage img;
304
305 if (! img.Load(fileName))
306 {
307 GetLogger()->Error("Could not load relief file: '%s'!\n", fileName.c_str());
308 return false;
309 }
310
311 ImageData *data = img.GetData();
312
313 int size = (m_mosaicCount*m_brickCount)+1;
314 GetLogger()->Debug("Expected relief size for current terrain configuration is %dx%d\n", size, size);
315
316 if ( (data->surface->w != size) || (data->surface->h != size) )
317 {
318 GetLogger()->Error("Invalid relief file! Expected %dx%d\n", size, size);
319 return false;
320 }
321
322 float limit = 0.9f;
323 for (int y = 0; y < size; y++)
324 {
325 for (int x = 0; x < size; x++)
326 {
327 Gfx::IntColor color = img.GetPixelInt(Math::IntPoint(x, size - y - 1));
328
329 float avg = (color.r + color.g + color.b) / 3.0f; // to be sure it is grayscale
330 float level = (255.0f - avg) * scaleRelief;
331
332 float dist = Math::Max(fabs(static_cast<float>(x-size/2)),
333 fabs(static_cast<float>(y-size/2)));
334 dist = dist/ static_cast<float>(size / 2);
335 if (dist > limit && adjustBorder)
336 {
337 dist = (dist-limit)/(1.0f-limit); // 0..1
338 if (dist > 1.0f) dist = 1.0f;
339 float border = 300.0f+Math::Rand()*20.0f;
340 level = level+dist*(border-level);
341 }
342
343 m_relief[x+y*size] = level;
344 }
345 }
346
347 return true;
348 }
349
RandomizeRelief()350 bool CTerrain::RandomizeRelief()
351 {
352 // Perlin noise
353 // Based on Python implementation by Marek Rogalski (mafik)
354 // http://amt2014.pl/archiwum/perlin.py
355
356 int size = (m_mosaicCount*m_brickCount)+1;
357 const int octaveCount = 6;
358
359 std::unique_ptr<float[]> octaves[octaveCount];
360 for(int i = 0; i < octaveCount; i++)
361 {
362 int pxCount = static_cast<int>(pow(2, (i+1)*2));
363 octaves[i] = MakeUniqueArray<float>(pxCount);
364 for(int j = 0; j < pxCount; j++)
365 {
366 octaves[i][j] = Math::Rand();
367 }
368 }
369
370 for(int y2 = 0; y2 < size; y2++)
371 {
372 float y = static_cast<float>(y2) / size;
373 for(int x2 = 0; x2 < size; x2++)
374 {
375 float x = static_cast<float>(x2) / size;
376
377 float value = 0;
378 for(int i = 0; i < octaveCount; i++)
379 {
380 int octaveSize = sqrt(static_cast<int>(pow(2, (i+1)*2)));
381 double xi, yi, a, b;
382 a = modf(x * (octaveSize-1), &xi);
383 b = modf(y * (octaveSize-1), &yi);
384
385 float lg = octaves[i][static_cast<int>(yi * octaveSize + xi)];
386 float pg = octaves[i][static_cast<int>(yi * octaveSize + xi + 1)];
387 float ld = octaves[i][static_cast<int>((yi+1) * octaveSize + xi)];
388 float pd = octaves[i][static_cast<int>((yi+1) * octaveSize + xi + 1)];
389
390 float g = pg * a + lg * (1-a);
391 float d = pd * a + ld * (1-a);
392 float res = d * b + g * (1-b);
393 value += res;
394 }
395
396 value /= octaveCount;
397
398 m_relief[x2+y2*size] = value * 255.0f;
399 }
400 }
401 return true;
402 }
403
AddReliefPoint(Math::Vector pos,float scaleRelief)404 bool CTerrain::AddReliefPoint(Math::Vector pos, float scaleRelief)
405 {
406 float dim = (m_mosaicCount*m_brickCount*m_brickSize)/2.0f;
407 int size = (m_mosaicCount*m_brickCount)+1;
408
409 pos.x = (pos.x+dim)/m_brickSize;
410 pos.z = (pos.z+dim)/m_brickSize;
411
412 int x = static_cast<int>(pos.x);
413 int y = static_cast<int>(pos.z);
414
415 if ( x < 0 || x >= size ||
416 y < 0 || y >= size ) return false;
417
418 if (m_relief[x+y*size] < pos.y*scaleRelief)
419 m_relief[x+y*size] = pos.y*scaleRelief;
420
421 return true;
422 }
423
AdjustRelief()424 void CTerrain::AdjustRelief()
425 {
426 if (m_depth == 1) return;
427
428 int ii = m_mosaicCount*m_brickCount+1;
429 int b = 1 << (m_depth-1);
430
431 for (int y = 0; y < m_mosaicCount*m_brickCount; y += b)
432 {
433 for (int x = 0; x < m_mosaicCount*m_brickCount; x += b)
434 {
435 int xx = 0;
436 int yy = 0;
437 if ((y+yy)%m_brickCount == 0)
438 {
439 float level1 = m_relief[(x+0)+(y+yy)*ii];
440 float level2 = m_relief[(x+b)+(y+yy)*ii];
441 for (xx = 1; xx < b; xx++)
442 {
443 m_relief[(x+xx)+(y+yy)*ii] = ((level2-level1)/b)*xx+level1;
444 }
445 }
446
447 yy = b;
448 if ((y+yy)%m_brickCount == 0)
449 {
450 float level1 = m_relief[(x+0)+(y+yy)*ii];
451 float level2 = m_relief[(x+b)+(y+yy)*ii];
452 for (xx = 1; xx < b; xx++)
453 {
454 m_relief[(x+xx)+(y+yy)*ii] = ((level2-level1)/b)*xx+level1;
455 }
456 }
457
458 xx = 0;
459 if ((x+xx)%m_brickCount == 0)
460 {
461 float level1 = m_relief[(x+xx)+(y+0)*ii];
462 float level2 = m_relief[(x+xx)+(y+b)*ii];
463 for (yy = 1; yy < b; yy++)
464 {
465 m_relief[(x+xx)+(y+yy)*ii] = ((level2-level1)/b)*yy+level1;
466 }
467 }
468
469 xx = b;
470 if ((x+xx)%m_brickCount == 0)
471 {
472 float level1 = m_relief[(x+xx)+(y+0)*ii];
473 float level2 = m_relief[(x+xx)+(y+b)*ii];
474 for (yy = 1; yy < b; yy++)
475 {
476 m_relief[(x+xx)+(y+yy)*ii] = ((level2-level1)/b)*yy+level1;
477 }
478 }
479 }
480 }
481 }
482
GetVector(int x,int y)483 Math::Vector CTerrain::GetVector(int x, int y)
484 {
485 Math::Vector p;
486 p.x = x*m_brickSize - (m_mosaicCount*m_brickCount*m_brickSize) / 2.0;
487 p.z = y*m_brickSize - (m_mosaicCount*m_brickCount*m_brickSize) / 2.0;
488
489 if ( !m_relief.empty() &&
490 x >= 0 && x <= m_mosaicCount*m_brickCount &&
491 y >= 0 && y <= m_mosaicCount*m_brickCount )
492 {
493 p.y = m_relief[x+y*(m_mosaicCount*m_brickCount+1)];
494 }
495 else
496 {
497 p.y = 0.0f;
498 }
499
500 return p;
501 }
502
503 /** Calculates an averaged normal, taking into account the six adjacent triangles:
504
505 \verbatim
506 ^ y
507 |
508 b---c---+
509 |\ |\ |
510 | \| \|
511 a---o---d
512 |\ |\ |
513 | \| \|
514 +---f---e--> x
515 \endverbatim */
GetVertex(int x,int y,int step)516 VertexTex2 CTerrain::GetVertex(int x, int y, int step)
517 {
518 VertexTex2 v;
519
520 Math::Vector o = GetVector(x, y);
521 v.coord = o;
522
523 Math::Vector a = GetVector(x-step, y );
524 Math::Vector b = GetVector(x-step, y+step);
525 Math::Vector c = GetVector(x, y+step);
526 Math::Vector d = GetVector(x+step, y );
527 Math::Vector e = GetVector(x+step, y-step);
528 Math::Vector f = GetVector(x, y-step);
529
530 Math::Vector s(0.0f, 0.0f, 0.0f);
531
532 if (x-step >= 0 && y+step <= m_mosaicCount*m_brickCount+1)
533 {
534 s += Math::NormalToPlane(b,a,o);
535 s += Math::NormalToPlane(c,b,o);
536 }
537
538 if (x+step <= m_mosaicCount*m_brickCount+1 && y+step <= m_mosaicCount*m_brickCount+1)
539 s += Math::NormalToPlane(d,c,o);
540
541 if (x+step <= m_mosaicCount*m_brickCount+1 && y-step >= 0)
542 {
543 s += Math::NormalToPlane(e,d,o);
544 s += Math::NormalToPlane(f,e,o);
545 }
546
547 if (x-step >= 0 && y-step >= 0)
548 s += Math::NormalToPlane(a,f,o);
549
550 s = Normalize(s);
551 v.normal = s;
552
553 int brick = m_brickCount/m_textureSubdivCount;
554 Math::Vector oo = GetVector((x/brick)*brick, (y/brick)*brick);
555 o = GetVector(x, y);
556 v.texCoord.x = (o.x-oo.x)*m_textureScale*m_textureSubdivCount;
557 v.texCoord.y = 1.0f - (o.z-oo.z)*m_textureScale*m_textureSubdivCount;
558
559 v.texCoord2 = v.texCoord;
560
561 return v;
562 }
563
564 /** The origin of mosaic is its center.
565 \verbatim
566 ^ z
567 |
568 | 2---4---6--
569 | |\ |\ |\
570 | | \| \|
571 | 1---3---5--- ...
572 |
573 +-------------------> x
574 \endverbatim */
CreateMosaic(int ox,int oy,int step,int objRank,const Material & mat)575 bool CTerrain::CreateMosaic(int ox, int oy, int step, int objRank,
576 const Material &mat)
577 {
578 int baseObjRank = m_engine->GetObjectBaseRank(objRank);
579 if (baseObjRank == -1)
580 {
581 baseObjRank = m_engine->CreateBaseObject();
582 m_engine->SetObjectBaseRank(objRank, baseObjRank);
583 }
584
585 std::string texName1;
586 std::string texName2;
587
588 if ( step == 1 )
589 {
590 int i = (ox/5) + (oy/5)*(m_mosaicCount/5);
591 std::stringstream s;
592 s << "shadow";
593 s.width(2);
594 s.fill('0');
595 s << i;
596 s << ".png";
597 texName2 = s.str();
598 }
599
600 int brick = m_brickCount/m_textureSubdivCount;
601
602 VertexTex2 o = GetVertex(ox*m_brickCount+m_brickCount/2, oy*m_brickCount+m_brickCount/2, step);
603 int total = ((brick/step)+1)*2;
604
605 float pixel = 1.0f/256.0f; // 1 pixel cover (*)
606 float dp = 1.0f/512.0f;
607
608 Math::Point uv;
609
610 for (int my = 0; my < m_textureSubdivCount; my++)
611 {
612 for (int mx = 0; mx < m_textureSubdivCount; mx++)
613 {
614 if (m_useMaterials)
615 {
616 int xx = ox*m_brickCount + mx*(m_brickCount/m_textureSubdivCount);
617 int yy = oy*m_brickCount + my*(m_brickCount/m_textureSubdivCount);
618 GetTexture(xx, yy, texName1, uv);
619 }
620 else
621 {
622 int i = (ox*m_textureSubdivCount+mx)+(oy*m_textureSubdivCount+my)*m_mosaicCount;
623 std::stringstream s;
624 s << m_texBaseName;
625 s.width(3);
626 s.fill('0');
627 s << m_textures[i];
628 s << m_texBaseExt;
629 texName1 = s.str();
630 }
631
632 for (int y = 0; y < brick; y += step)
633 {
634 EngineBaseObjDataTier buffer;
635 buffer.vertices.reserve(total);
636
637 buffer.type = ENG_TRIANGLE_TYPE_SURFACE;
638 buffer.material = mat;
639
640 buffer.state = ENG_RSTATE_WRAP;
641
642 buffer.state |= ENG_RSTATE_SECOND;
643 if (step == 1)
644 buffer.state |= ENG_RSTATE_DUAL_BLACK;
645
646 for (int x = 0; x <= brick; x += step)
647 {
648 VertexTex2 p1 = GetVertex(ox*m_brickCount+mx*brick+x, oy*m_brickCount+my*brick+y+0 , step);
649 VertexTex2 p2 = GetVertex(ox*m_brickCount+mx*brick+x, oy*m_brickCount+my*brick+y+step, step);
650 p1.coord.x -= o.coord.x; p1.coord.z -= o.coord.z;
651 p2.coord.x -= o.coord.x; p2.coord.z -= o.coord.z;
652
653 // TODO: Find portable solution
654 //float offset = 0.5f / 256.0f; // Direct3D
655 float offset = 0.0f; // OpenGL
656
657 if (x == 0)
658 {
659 p1.texCoord.x = 0.0f + offset;
660 p2.texCoord.x = 0.0f + offset;
661 }
662 if (x == brick)
663 {
664 p1.texCoord.x = 1.0f - offset;
665 p2.texCoord.x = 1.0f - offset;
666 }
667 if (y == 0)
668 p1.texCoord.y = 1.0f - offset;
669
670 if (y == brick - step)
671 p2.texCoord.y = 0.0f + offset;
672
673 if (m_useMaterials)
674 {
675 p1.texCoord.x /= m_textureSubdivCount; // 0..1 -> 0..0.25
676 p1.texCoord.y /= m_textureSubdivCount;
677 p2.texCoord.x /= m_textureSubdivCount;
678 p2.texCoord.y /= m_textureSubdivCount;
679
680 if (x == 0)
681 {
682 p1.texCoord.x = 0.0f+dp;
683 p2.texCoord.x = 0.0f+dp;
684 }
685 if (x == brick)
686 {
687 p1.texCoord.x = (1.0f/m_textureSubdivCount)-dp;
688 p2.texCoord.x = (1.0f/m_textureSubdivCount)-dp;
689 }
690 if (y == 0)
691 p1.texCoord.y = (1.0f/m_textureSubdivCount)-dp;
692
693 if (y == brick - step)
694 p2.texCoord.y = 0.0f+dp;
695
696 p1.texCoord.x += uv.x;
697 p1.texCoord.y += uv.y;
698 p2.texCoord.x += uv.x;
699 p2.texCoord.y += uv.y;
700 }
701
702 int xx = mx*(m_brickCount/m_textureSubdivCount) + x;
703 int yy = my*(m_brickCount/m_textureSubdivCount) + y;
704 p1.texCoord2.x = (static_cast<float>(ox%5)*m_brickCount+xx+0.0f)/(m_brickCount*5);
705 p1.texCoord2.y = (static_cast<float>(oy%5)*m_brickCount+yy+0.0f)/(m_brickCount*5);
706 p2.texCoord2.x = (static_cast<float>(ox%5)*m_brickCount+xx+0.0f)/(m_brickCount*5);
707 p2.texCoord2.y = (static_cast<float>(oy%5)*m_brickCount+yy+1.0f)/(m_brickCount*5);
708
709 // Correction for 1 pixel cover
710 // There is 1 pixel cover around each of the 16 surfaces:
711 //
712 // |<--------------256-------------->|
713 // | |<----------254---------->| |
714 // |---|---|---|-- ... --|---|---|---|
715 // | 0.0 1.0 |
716 // | | | |
717 // 0.0 min max 1.0
718 //
719 // The uv coordinates used for texturing are between min and max (instead of 0 and 1)
720 // This allows to exclude the pixels situated in a margin of a pixel around the surface
721
722 p1.texCoord2.x = (p1.texCoord2.x+pixel)*(1.0f-pixel)/(1.0f+pixel);
723 p1.texCoord2.y = (p1.texCoord2.y+pixel)*(1.0f-pixel)/(1.0f+pixel);
724 p2.texCoord2.x = (p2.texCoord2.x+pixel)*(1.0f-pixel)/(1.0f+pixel);
725 p2.texCoord2.y = (p2.texCoord2.y+pixel)*(1.0f-pixel)/(1.0f+pixel);
726
727
728 buffer.vertices.push_back(p1);
729 buffer.vertices.push_back(p2);
730 }
731
732 m_engine->AddBaseObjQuick(baseObjRank, buffer, texName1, texName2, true);
733 }
734 }
735 }
736
737 Math::Matrix transform;
738 transform.LoadIdentity();
739 transform.Set(1, 4, o.coord.x);
740 transform.Set(3, 4, o.coord.z);
741 m_engine->SetObjectTransform(objRank, transform);
742
743 return true;
744 }
745
FindMaterial(int id)746 CTerrain::TerrainMaterial* CTerrain::FindMaterial(int id)
747 {
748 for (int i = 0; i < static_cast<int>( m_materials.size() ); i++)
749 {
750 if (id == m_materials[i].id)
751 return &m_materials[i];
752 }
753
754 return nullptr;
755 }
756
GetTexture(int x,int y,std::string & name,Math::Point & uv)757 void CTerrain::GetTexture(int x, int y, std::string& name, Math::Point &uv)
758 {
759 x /= m_brickCount/m_textureSubdivCount;
760 y /= m_brickCount/m_textureSubdivCount;
761
762 TerrainMaterial* tm = FindMaterial(m_materialPoints[x+y*m_materialPointCount].id);
763 if (tm == nullptr)
764 {
765 name = "";
766 uv = Math::Point(0.0f, 0.0f);
767 }
768 else
769 {
770 name = tm->texName;
771 uv = tm->uv;
772 }
773 }
774
GetHeight(int x,int y)775 float CTerrain::GetHeight(int x, int y)
776 {
777 int size = (m_mosaicCount*m_brickCount+1);
778
779 if (x < 0 ) x = 0;
780 if (x >= size) x = size-1;
781 if (y < 0 ) y = 0;
782 if (y >= size) y = size-1;
783
784 return m_relief[x+y*size];
785 }
786
CheckMaterialPoint(int x,int y,float min,float max,float slope)787 bool CTerrain::CheckMaterialPoint(int x, int y, float min, float max, float slope)
788 {
789 float hc = GetHeight(x, y);
790 float h[4] =
791 {
792 GetHeight(x+0, y+1),
793 GetHeight(x+1, y+0),
794 GetHeight(x+0, y-1),
795 GetHeight(x-1, y+0)
796 };
797
798 if (hc < min || hc > max)
799 return false;
800
801 if (slope == 0.0f)
802 return true;
803
804 if (slope > 0.0f)
805 {
806 for (int i = 0; i < 4; i++)
807 {
808 if (fabs(hc - h[i]) >= slope)
809 return false;
810 }
811 return true;
812 }
813
814 if (slope < 0.0f)
815 {
816 for (int i = 0; i < 4; i++)
817 {
818 if (fabs(hc - h[i]) < -slope)
819 return false;
820 }
821 return true;
822 }
823
824 return false;
825 }
826
FindMaterialByNeighbors(char * mat)827 int CTerrain::FindMaterialByNeighbors(char *mat)
828 {
829 for (int i = 0; i < static_cast<int>( m_materials.size() ); i++)
830 {
831 if ( m_materials[i].mat[0] == mat[0] &&
832 m_materials[i].mat[1] == mat[1] &&
833 m_materials[i].mat[2] == mat[2] &&
834 m_materials[i].mat[3] == mat[3] ) return i;
835 }
836
837 return -1;
838 }
839
SetMaterialPoint(int x,int y,int id,char * mat)840 void CTerrain::SetMaterialPoint(int x, int y, int id, char *mat)
841 {
842 TerrainMaterial* tm = FindMaterial(id);
843 if (tm == nullptr) return;
844
845 if ( tm->mat[0] != mat[0] ||
846 tm->mat[1] != mat[1] ||
847 tm->mat[2] != mat[2] ||
848 tm->mat[3] != mat[3] ) // id incompatible with mat?
849 {
850 int ii = FindMaterialByNeighbors(mat);
851 if (ii == -1) return;
852 id = m_materials[ii].id; // looking for a id compatible with mat
853 }
854
855 // Changes the point
856 m_materialPoints[x+y*m_materialPointCount].id = id;
857 m_materialPoints[x+y*m_materialPointCount].mat[0] = mat[0];
858 m_materialPoints[x+y*m_materialPointCount].mat[1] = mat[1];
859 m_materialPoints[x+y*m_materialPointCount].mat[2] = mat[2];
860 m_materialPoints[x+y*m_materialPointCount].mat[3] = mat[3];
861
862 // Changes the lower neighbor
863 if ( (x+0) >= 0 && (x+0) < m_materialPointCount &&
864 (y-1) >= 0 && (y-1) < m_materialPointCount )
865 {
866 int i = (x+0)+(y-1)*m_materialPointCount;
867 if (m_materialPoints[i].mat[0] != mat[2])
868 {
869 m_materialPoints[i].mat[0] = mat[2];
870 int ii = FindMaterialByNeighbors(m_materialPoints[i].mat);
871 if (ii != -1)
872 m_materialPoints[i].id = m_materials[ii].id;
873 }
874 }
875
876 // Modifies the left neighbor
877 if ( (x-1) >= 0 && (x-1) < m_materialPointCount &&
878 (y+0) >= 0 && (y+0) < m_materialPointCount )
879 {
880 int i = (x-1)+(y+0)*m_materialPointCount;
881 if (m_materialPoints[i].mat[1] != mat[3])
882 {
883 m_materialPoints[i].mat[1] = mat[3];
884 int ii = FindMaterialByNeighbors(m_materialPoints[i].mat);
885 if (ii != -1)
886 m_materialPoints[i].id = m_materials[ii].id;
887 }
888 }
889
890 // Changes the upper neighbor
891 if ( (x+0) >= 0 && (x+0) < m_materialPointCount &&
892 (y+1) >= 0 && (y+1) < m_materialPointCount )
893 {
894 int i = (x+0)+(y+1)*m_materialPointCount;
895 if (m_materialPoints[i].mat[2] != mat[0])
896 {
897 m_materialPoints[i].mat[2] = mat[0];
898 int ii = FindMaterialByNeighbors(m_materialPoints[i].mat);
899 if (ii != -1)
900 m_materialPoints[i].id = m_materials[ii].id;
901 }
902 }
903
904 // Changes the right neighbor
905 if ( (x+1) >= 0 && (x+1) < m_materialPointCount &&
906 (y+0) >= 0 && (y+0) < m_materialPointCount )
907 {
908 int i = (x+1)+(y+0)*m_materialPointCount;
909 if ( m_materialPoints[i].mat[3] != mat[1] )
910 {
911 m_materialPoints[i].mat[3] = mat[1];
912 int ii = FindMaterialByNeighbors(m_materialPoints[i].mat);
913 if (ii != -1)
914 m_materialPoints[i].id = m_materials[ii].id;
915 }
916 }
917 }
918
CondChangeMaterialPoint(int x,int y,int id,char * mat)919 bool CTerrain::CondChangeMaterialPoint(int x, int y, int id, char *mat)
920 {
921 char test[4];
922
923 // Compatible with lower neighbor?
924 if ( x+0 >= 0 && x+0 < m_materialPointCount &&
925 y-1 >= 0 && y-1 < m_materialPointCount )
926 {
927 test[0] = mat[2];
928 test[1] = m_materialPoints[(x+0)+(y-1)*m_materialPointCount].mat[1];
929 test[2] = m_materialPoints[(x+0)+(y-1)*m_materialPointCount].mat[2];
930 test[3] = m_materialPoints[(x+0)+(y-1)*m_materialPointCount].mat[3];
931
932 if ( FindMaterialByNeighbors(test) == -1 ) return false;
933 }
934
935 // Compatible with left neighbor?
936 if ( x-1 >= 0 && x-1 < m_materialPointCount &&
937 y+0 >= 0 && y+0 < m_materialPointCount )
938 {
939 test[0] = m_materialPoints[(x-1)+(y+0)*m_materialPointCount].mat[0];
940 test[1] = mat[3];
941 test[2] = m_materialPoints[(x-1)+(y+0)*m_materialPointCount].mat[2];
942 test[3] = m_materialPoints[(x-1)+(y+0)*m_materialPointCount].mat[3];
943
944 if ( FindMaterialByNeighbors(test) == -1 ) return false;
945 }
946
947 // Compatible with upper neighbor?
948 if ( x+0 >= 0 && x+0 < m_materialPointCount &&
949 y+1 >= 0 && y+1 < m_materialPointCount )
950 {
951 test[0] = m_materialPoints[(x+0)+(y+1)*m_materialPointCount].mat[0];
952 test[1] = m_materialPoints[(x+0)+(y+1)*m_materialPointCount].mat[1];
953 test[2] = mat[0];
954 test[3] = m_materialPoints[(x+0)+(y+1)*m_materialPointCount].mat[3];
955
956 if ( FindMaterialByNeighbors(test) == -1 ) return false;
957 }
958
959 // Compatible with right neighbor?
960 if ( x+1 >= 0 && x+1 < m_materialPointCount &&
961 y+0 >= 0 && y+0 < m_materialPointCount )
962 {
963 test[0] = m_materialPoints[(x+1)+(y+0)*m_materialPointCount].mat[0];
964 test[1] = m_materialPoints[(x+1)+(y+0)*m_materialPointCount].mat[1];
965 test[2] = m_materialPoints[(x+1)+(y+0)*m_materialPointCount].mat[2];
966 test[3] = mat[1];
967
968 if ( FindMaterialByNeighbors(test) == -1 ) return false;
969 }
970
971 SetMaterialPoint(x, y, id, mat); // puts the point
972 return true;
973 }
974
ChangeMaterialPoint(int x,int y,int id)975 bool CTerrain::ChangeMaterialPoint(int x, int y, int id)
976 {
977 char mat[4];
978
979 x /= m_brickCount/m_textureSubdivCount;
980 y /= m_brickCount/m_textureSubdivCount;
981
982 if ( x < 0 || x >= m_materialPointCount ||
983 y < 0 || y >= m_materialPointCount ) return false;
984
985 TerrainMaterial* tm = FindMaterial(id);
986 if (tm == nullptr) return false;
987
988 // Tries without changing neighbors.
989 if ( CondChangeMaterialPoint(x, y, id, tm->mat) ) return true;
990
991 // Tries changing a single neighbor (4x).
992 for (int up = 0; up < m_maxMaterialID; up++)
993 {
994 mat[0] = up;
995 mat[1] = tm->mat[1];
996 mat[2] = tm->mat[2];
997 mat[3] = tm->mat[3];
998
999 if (CondChangeMaterialPoint(x, y, id, mat)) return true;
1000 }
1001
1002 for (int right = 0; right < m_maxMaterialID; right++)
1003 {
1004 mat[0] = tm->mat[0];
1005 mat[1] = right;
1006 mat[2] = tm->mat[2];
1007 mat[3] = tm->mat[3];
1008
1009 if (CondChangeMaterialPoint(x, y, id, mat)) return true;
1010 }
1011
1012 for (int down = 0; down < m_maxMaterialID; down++)
1013 {
1014 mat[0] = tm->mat[0];
1015 mat[1] = tm->mat[1];
1016 mat[2] = down;
1017 mat[3] = tm->mat[3];
1018
1019 if (CondChangeMaterialPoint(x, y, id, mat)) return true;
1020 }
1021
1022 for (int left = 0; left < m_maxMaterialID; left++)
1023 {
1024 mat[0] = tm->mat[0];
1025 mat[1] = tm->mat[1];
1026 mat[2] = tm->mat[2];
1027 mat[3] = left;
1028
1029 if (CondChangeMaterialPoint(x, y, id, mat)) return true;
1030 }
1031
1032 // Tries changing two neighbors (6x).
1033 for (int up = 0; up < m_maxMaterialID; up++)
1034 {
1035 for (int down = 0; down < m_maxMaterialID; down++)
1036 {
1037 mat[0] = up;
1038 mat[1] = tm->mat[1];
1039 mat[2] = down;
1040 mat[3] = tm->mat[3];
1041
1042 if (CondChangeMaterialPoint(x, y, id, mat)) return true;
1043 }
1044 }
1045
1046 for (int right = 0; right < m_maxMaterialID; right++)
1047 {
1048 for (int left = 0; left < m_maxMaterialID; left++)
1049 {
1050 mat[0] = tm->mat[0];
1051 mat[1] = right;
1052 mat[2] = tm->mat[2];
1053 mat[3] = left;
1054
1055 if (CondChangeMaterialPoint(x, y, id, mat)) return true;
1056 }
1057 }
1058
1059 for (int up = 0; up < m_maxMaterialID; up++)
1060 {
1061 for (int right = 0; right < m_maxMaterialID; right++)
1062 {
1063 mat[0] = up;
1064 mat[1] = right;
1065 mat[2] = tm->mat[2];
1066 mat[3] = tm->mat[3];
1067
1068 if (CondChangeMaterialPoint(x, y, id, mat)) return true;
1069 }
1070 }
1071
1072 for (int right = 0; right < m_maxMaterialID; right++)
1073 {
1074 for (int down = 0; down < m_maxMaterialID; down++)
1075 {
1076 mat[0] = tm->mat[0];
1077 mat[1] = right;
1078 mat[2] = down;
1079 mat[3] = tm->mat[3];
1080
1081 if (CondChangeMaterialPoint(x, y, id, mat)) return true;
1082 }
1083 }
1084
1085 for (int down = 0; down < m_maxMaterialID; down++)
1086 {
1087 for (int left = 0; left < m_maxMaterialID; left++)
1088 {
1089 mat[0] = tm->mat[0];
1090 mat[1] = tm->mat[1];
1091 mat[2] = down;
1092 mat[3] = left;
1093
1094 if (CondChangeMaterialPoint(x, y, id, mat)) return true;
1095 }
1096 }
1097
1098 for (int up = 0; up < m_maxMaterialID; up++)
1099 {
1100 for (int left = 0; left < m_maxMaterialID; left++)
1101 {
1102 mat[0] = up;
1103 mat[1] = tm->mat[1];
1104 mat[2] = tm->mat[2];
1105 mat[3] = left;
1106
1107 if (CondChangeMaterialPoint(x, y, id, mat)) return true;
1108 }
1109 }
1110
1111 // Tries changing all the neighbors.
1112 for (int up = 0; up < m_maxMaterialID; up++)
1113 {
1114 for (int right = 0; right < m_maxMaterialID; right++)
1115 {
1116 for (int down = 0; down < m_maxMaterialID; down++)
1117 {
1118 for (int left = 0; left < m_maxMaterialID; left++)
1119 {
1120 mat[0] = up;
1121 mat[1] = right;
1122 mat[2] = down;
1123 mat[3] = left;
1124
1125 if (CondChangeMaterialPoint(x, y, id, mat)) return true;
1126 }
1127 }
1128 }
1129 }
1130
1131 GetLogger()->Error("AddMaterialPoint error\n");
1132 return false;
1133 }
1134
InitMaterials(int id)1135 bool CTerrain::InitMaterials(int id)
1136 {
1137 TerrainMaterial* tm = FindMaterial(id);
1138 if (tm == nullptr) return false;
1139
1140 for (int i = 0; i < m_materialPointCount*m_materialPointCount; i++)
1141 {
1142 m_materialPoints[i].id = id;
1143
1144 for (int j = 0; j < 4; j++)
1145 m_materialPoints[i].mat[j] = tm->mat[j];
1146 }
1147
1148 return true;
1149 }
1150
GenerateMaterials(int * id,float min,float max,float slope,float freq,Math::Vector center,float radius)1151 bool CTerrain::GenerateMaterials(int *id, float min, float max,
1152 float slope, float freq,
1153 Math::Vector center, float radius)
1154 {
1155 static char random[100] =
1156 {
1157 84,25,12, 6,34,52,85,38,97,16,
1158 21,31,65,19,62,40,72,22,48,61,
1159 56,47, 8,53,73,77, 4,91,26,88,
1160 76, 1,44,93,39,11,71,17,98,95,
1161 88,83,18,30, 3,57,28,49,74, 9,
1162 32,13,96,66,15,70,36,10,59,94,
1163 45,86, 2,29,63,42,51, 0,79,27,
1164 54, 7,20,69,89,23,64,43,81,92,
1165 90,33,46,14,67,35,50, 5,87,60,
1166 68,55,24,78,41,75,58,80,37,82,
1167 };
1168
1169 TerrainMaterial* tm = nullptr;
1170
1171 int i = 0;
1172 while (id[i] != 0)
1173 {
1174 tm = FindMaterial(id[i++]);
1175 if (tm == nullptr) return false;
1176 }
1177 int numID = i;
1178
1179 int group = m_brickCount / m_textureSubdivCount;
1180
1181 if (radius > 0.0f && radius < 5.0f) // just a square?
1182 {
1183 float dim = (m_mosaicCount*m_brickCount*m_brickSize)/2.0f;
1184
1185 int xx = static_cast<int>((center.x+dim)/m_brickSize);
1186 int yy = static_cast<int>((center.z+dim)/m_brickSize);
1187
1188 int x = xx/group;
1189 int y = yy/group;
1190
1191 tm = FindMaterial(id[0]);
1192 if (tm != nullptr)
1193 SetMaterialPoint(x, y, id[0], tm->mat); // puts the point
1194 }
1195 else
1196 {
1197 for (int y = 0; y < m_materialPointCount; y++)
1198 {
1199 for (int x = 0; x < m_materialPointCount; x++)
1200 {
1201 if (radius != 0.0f)
1202 {
1203 Math::Vector pos;
1204 pos.x = (static_cast<float>(x)-m_materialPointCount/2.0f)*group*m_brickSize;
1205 pos.z = (static_cast<float>(y)-m_materialPointCount/2.0f)*group*m_brickSize;
1206 if (Math::DistanceProjected(pos, center) > radius) continue;
1207 }
1208
1209 if (freq < 100.0f)
1210 {
1211 int rnd = random[(x%10)+(y%10)*10];
1212 if ( static_cast<float>(rnd) > freq ) continue;
1213 }
1214
1215 int xx = x*group + group/2;
1216 int yy = y*group + group/2;
1217
1218 if (CheckMaterialPoint(xx, yy, min, max, slope))
1219 {
1220 int rnd = random[(x%10)+(y%10)*10];
1221 int ii = rnd % numID;
1222 ChangeMaterialPoint(xx, yy, id[ii]);
1223 }
1224 }
1225 }
1226 }
1227
1228 return true;
1229 }
1230
InitMaterialPoints()1231 void CTerrain::InitMaterialPoints()
1232 {
1233 if (! m_useMaterials) return;
1234 if (! m_materialPoints.empty()) return; // already allocated
1235
1236 m_materialPointCount = (m_mosaicCount*m_brickCount)/(m_brickCount/m_textureSubdivCount)+1;
1237 std::vector<TerrainMaterialPoint>(m_materialPointCount*m_materialPointCount).swap(m_materialPoints);
1238
1239 for (int i = 0; i < m_materialPointCount * m_materialPointCount; i++)
1240 {
1241 for (int j = 0; j < 4; j++)
1242 m_materialPoints[i].mat[j] = 0;
1243 }
1244 }
1245
FlushMaterialPoints()1246 void CTerrain::FlushMaterialPoints()
1247 {
1248 m_materialPoints.clear();
1249 }
1250
CreateSquare(int x,int y)1251 bool CTerrain::CreateSquare(int x, int y)
1252 {
1253 Material mat;
1254 mat.diffuse = Color(1.0f, 1.0f, 1.0f);
1255 mat.ambient = Color(0.0f, 0.0f, 0.0f);
1256
1257 int objRank = m_engine->CreateObject();
1258 m_engine->SetObjectType(objRank, ENG_OBJTYPE_TERRAIN);
1259
1260 m_objRanks[x+y*m_mosaicCount] = objRank;
1261
1262 for (int step = 0; step < m_depth; step++)
1263 {
1264 CreateMosaic(x, y, 1 << step, objRank, mat);
1265 }
1266
1267 return true;
1268 }
1269
CreateObjects()1270 bool CTerrain::CreateObjects()
1271 {
1272 AdjustRelief();
1273
1274 for (int y = 0; y < m_mosaicCount; y++)
1275 {
1276 for (int x = 0; x < m_mosaicCount; x++)
1277 CreateSquare(x, y);
1278 }
1279
1280 return true;
1281 }
1282
1283 /** ATTENTION: ok only with m_depth = 2! */
Terraform(const Math::Vector & p1,const Math::Vector & p2,float height)1284 bool CTerrain::Terraform(const Math::Vector &p1, const Math::Vector &p2, float height)
1285 {
1286 float dim = (m_mosaicCount*m_brickCount*m_brickSize)/2.0f;
1287
1288 Math::IntPoint tp1, tp2;
1289 tp1.x = static_cast<int>((p1.x+dim+m_brickSize/2.0f)/m_brickSize);
1290 tp1.y = static_cast<int>((p1.z+dim+m_brickSize/2.0f)/m_brickSize);
1291 tp2.x = static_cast<int>((p2.x+dim+m_brickSize/2.0f)/m_brickSize);
1292 tp2.y = static_cast<int>((p2.z+dim+m_brickSize/2.0f)/m_brickSize);
1293
1294 if (tp1.x > tp2.x)
1295 {
1296 int x = tp1.x;
1297 tp1.x = tp2.x;
1298 tp2.x = x;
1299 }
1300
1301 if (tp1.y > tp2.y)
1302 {
1303 int y = tp1.y;
1304 tp1.y = tp2.y;
1305 tp2.y = y;
1306 }
1307
1308 int size = (m_mosaicCount*m_brickCount)+1;
1309
1310 // Calculates the current average height
1311 float avg = 0.0f;
1312 int nb = 0;
1313 for (int y = tp1.y; y <= tp2.y; y++)
1314 {
1315 for (int x = tp1.x; x <= tp2.x; x++)
1316 {
1317 avg += m_relief[x+y*size];
1318 nb ++;
1319 }
1320 }
1321 avg /= static_cast<float>(nb);
1322
1323 // Changes the description of the relief
1324 for (int y = tp1.y; y <= tp2.y; y++)
1325 {
1326 for (int x = tp1.x; x <= tp2.x; x++)
1327 {
1328 m_relief[x+y*size] = avg+height;
1329
1330 if (x % m_brickCount == 0 && y % m_depth != 0)
1331 {
1332 m_relief[(x+0)+(y-1)*size] = avg+height;
1333 m_relief[(x+0)+(y+1)*size] = avg+height;
1334 }
1335
1336 if (y % m_brickCount == 0 && x % m_depth != 0)
1337 {
1338 m_relief[(x-1)+(y+0)*size] = avg+height;
1339 m_relief[(x+1)+(y+0)*size] = avg+height;
1340 }
1341 }
1342 }
1343 AdjustRelief();
1344
1345 Math::IntPoint pp1, pp2;
1346 pp1.x = (tp1.x-2)/m_brickCount;
1347 pp1.y = (tp1.y-2)/m_brickCount;
1348 pp2.x = (tp2.x+1)/m_brickCount;
1349 pp2.y = (tp2.y+1)/m_brickCount;
1350
1351 if (pp1.x < 0 ) pp1.x = 0;
1352 if (pp1.x >= m_mosaicCount) pp1.x = m_mosaicCount-1;
1353 if (pp1.y < 0 ) pp1.y = 0;
1354 if (pp1.y >= m_mosaicCount) pp1.y = m_mosaicCount-1;
1355
1356 for (int y = pp1.y; y <= pp2.y; y++)
1357 {
1358 for (int x = pp1.x; x <= pp2.x; x++)
1359 {
1360 int objRank = m_objRanks[x+y*m_mosaicCount];
1361 int baseObjRank = m_engine->GetObjectBaseRank(objRank);
1362 m_engine->DeleteBaseObject(baseObjRank);
1363 m_engine->DeleteObject(objRank);
1364 CreateSquare(x, y); // recreates the square
1365 }
1366 }
1367 m_engine->Update();
1368
1369 return true;
1370 }
1371
SetWind(Math::Vector speed)1372 void CTerrain::SetWind(Math::Vector speed)
1373 {
1374 m_wind = speed;
1375 }
1376
GetWind()1377 Math::Vector CTerrain::GetWind()
1378 {
1379 return m_wind;
1380 }
1381
GetFineSlope(const Math::Vector & pos)1382 float CTerrain::GetFineSlope(const Math::Vector &pos)
1383 {
1384 Math::Vector n;
1385 if (! GetNormal(n, pos)) return 0.0f;
1386 return fabs(Math::RotateAngle(Math::Point(n.x, n.z).Length(), n.y) - Math::PI/2.0f);
1387 }
1388
GetCoarseSlope(const Math::Vector & pos)1389 float CTerrain::GetCoarseSlope(const Math::Vector &pos)
1390 {
1391 if (m_relief.empty()) return 0.0f;
1392
1393 float dim = (m_mosaicCount*m_brickCount*m_brickSize)/2.0f;
1394
1395 int x = static_cast<int>((pos.x+dim)/m_brickSize);
1396 int y = static_cast<int>((pos.z+dim)/m_brickSize);
1397
1398 if ( x < 0 || x >= m_mosaicCount*m_brickCount ||
1399 y < 0 || y >= m_mosaicCount*m_brickCount ) return 0.0f;
1400
1401 float level[4] =
1402 {
1403 m_relief[(x+0)+(y+0)*(m_mosaicCount*m_brickCount+1)],
1404 m_relief[(x+1)+(y+0)*(m_mosaicCount*m_brickCount+1)],
1405 m_relief[(x+0)+(y+1)*(m_mosaicCount*m_brickCount+1)],
1406 m_relief[(x+1)+(y+1)*(m_mosaicCount*m_brickCount+1)],
1407 };
1408
1409 float min = Math::Min(level[0], level[1], level[2], level[3]);
1410 float max = Math::Max(level[0], level[1], level[2], level[3]);
1411
1412 return atanf((max-min)/m_brickSize);
1413 }
1414
GetNormal(Math::Vector & n,const Math::Vector & p)1415 bool CTerrain::GetNormal(Math::Vector &n, const Math::Vector &p)
1416 {
1417 float dim = (m_mosaicCount*m_brickCount*m_brickSize)/2.0f;
1418
1419 int x = static_cast<int>((p.x+dim)/m_brickSize);
1420 int y = static_cast<int>((p.z+dim)/m_brickSize);
1421
1422 if ( x < 0 || x > m_mosaicCount*m_brickCount ||
1423 y < 0 || y > m_mosaicCount*m_brickCount ) return false;
1424
1425 Math::Vector p1 = GetVector(x+0, y+0);
1426 Math::Vector p2 = GetVector(x+1, y+0);
1427 Math::Vector p3 = GetVector(x+0, y+1);
1428 Math::Vector p4 = GetVector(x+1, y+1);
1429
1430 if ( fabs(p.z - p2.z) < fabs(p.x - p2.x) )
1431 n = Math::NormalToPlane(p1,p2,p3);
1432 else
1433 n = Math::NormalToPlane(p2,p4,p3);
1434
1435 return true;
1436 }
1437
GetFloorLevel(const Math::Vector & pos,bool brut,bool water)1438 float CTerrain::GetFloorLevel(const Math::Vector &pos, bool brut, bool water)
1439 {
1440 float dim = (m_mosaicCount*m_brickCount*m_brickSize)/2.0f;
1441
1442 int x = static_cast<int>((pos.x+dim)/m_brickSize);
1443 int y = static_cast<int>((pos.z+dim)/m_brickSize);
1444
1445 if ( x < 0 || x > m_mosaicCount*m_brickCount ||
1446 y < 0 || y > m_mosaicCount*m_brickCount ) return false;
1447
1448 Math::Vector p1 = GetVector(x+0, y+0);
1449 Math::Vector p2 = GetVector(x+1, y+0);
1450 Math::Vector p3 = GetVector(x+0, y+1);
1451 Math::Vector p4 = GetVector(x+1, y+1);
1452
1453 Math::Vector ps = pos;
1454 if ( fabs(pos.z-p2.z) < fabs(pos.x-p2.x) )
1455 {
1456 if ( !IntersectY(p1, p2, p3, ps) ) return 0.0f;
1457 }
1458 else
1459 {
1460 if ( !IntersectY(p2, p4, p3, ps) ) return 0.0f;
1461 }
1462
1463 if (! brut) AdjustBuildingLevel(ps);
1464
1465 if (water) // not going underwater?
1466 {
1467 float level = m_water->GetLevel();
1468 if (ps.y < level) ps.y = level; // not under water
1469 }
1470
1471 return ps.y;
1472 }
1473
GetHeightToFloor(const Math::Vector & pos,bool brut,bool water)1474 float CTerrain::GetHeightToFloor(const Math::Vector &pos, bool brut, bool water)
1475 {
1476 float dim = (m_mosaicCount*m_brickCount*m_brickSize)/2.0f;
1477
1478 int x = static_cast<int>((pos.x+dim)/m_brickSize);
1479 int y = static_cast<int>((pos.z+dim)/m_brickSize);
1480
1481 if ( x < 0 || x > m_mosaicCount*m_brickCount ||
1482 y < 0 || y > m_mosaicCount*m_brickCount ) return false;
1483
1484 Math::Vector p1 = GetVector(x+0, y+0);
1485 Math::Vector p2 = GetVector(x+1, y+0);
1486 Math::Vector p3 = GetVector(x+0, y+1);
1487 Math::Vector p4 = GetVector(x+1, y+1);
1488
1489 Math::Vector ps = pos;
1490 if ( fabs(pos.z-p2.z) < fabs(pos.x-p2.x) )
1491 {
1492 if ( !IntersectY(p1, p2, p3, ps) ) return 0.0f;
1493 }
1494 else
1495 {
1496 if ( !IntersectY(p2, p4, p3, ps) ) return 0.0f;
1497 }
1498
1499 if (! brut) AdjustBuildingLevel(ps);
1500
1501 if (water) // not going underwater?
1502 {
1503 float level = m_water->GetLevel();
1504 if (ps.y < level ) ps.y = level; // not under water
1505 }
1506
1507 return pos.y-ps.y;
1508 }
1509
AdjustToFloor(Math::Vector & pos,bool brut,bool water)1510 bool CTerrain::AdjustToFloor(Math::Vector &pos, bool brut, bool water)
1511 {
1512 float dim = (m_mosaicCount*m_brickCount*m_brickSize)/2.0f;
1513
1514 int x = static_cast<int>((pos.x + dim) / m_brickSize);
1515 int y = static_cast<int>((pos.z + dim) / m_brickSize);
1516
1517 if ( x < 0 || x > m_mosaicCount*m_brickCount ||
1518 y < 0 || y > m_mosaicCount*m_brickCount ) return false;
1519
1520 Math::Vector p1 = GetVector(x+0, y+0);
1521 Math::Vector p2 = GetVector(x+1, y+0);
1522 Math::Vector p3 = GetVector(x+0, y+1);
1523 Math::Vector p4 = GetVector(x+1, y+1);
1524
1525 if (fabs(pos.z - p2.z) < fabs(pos.x - p2.x))
1526 {
1527 if (! Math::IntersectY(p1, p2, p3, pos)) return false;
1528 }
1529 else
1530 {
1531 if (! Math::IntersectY(p2, p4, p3, pos)) return false;
1532 }
1533
1534 if (! brut) AdjustBuildingLevel(pos);
1535
1536 if (water) // not going underwater?
1537 {
1538 float level = m_water->GetLevel();
1539 if (pos.y < level) pos.y = level; // not under water
1540 }
1541
1542 return true;
1543 }
1544
1545 /**
1546 * \param pos position to adjust
1547 * \returns \c false if the initial coordinate was outside terrain area; \c true otherwise
1548 */
AdjustToStandardBounds(Math::Vector & pos)1549 bool CTerrain::AdjustToStandardBounds(Math::Vector& pos)
1550 {
1551 bool ok = true;
1552
1553 // In _TEEN there used to be a limit of 0.98f
1554 float limit = (m_mosaicCount*m_brickCount*m_brickSize)/2.0f*0.92f;
1555
1556 if (pos.x < -limit)
1557 {
1558 pos.x = -limit;
1559 ok = false;
1560 }
1561
1562 if (pos.z < -limit)
1563 {
1564 pos.z = -limit;
1565 ok = false;
1566 }
1567
1568 if (pos.x > limit)
1569 {
1570 pos.x = limit;
1571 ok = false;
1572 }
1573
1574 if (pos.z > limit)
1575 {
1576 pos.z = limit;
1577 ok = false;
1578 }
1579
1580 return ok;
1581 }
1582
1583 /**
1584 * \param pos position to adjust
1585 * \param margin margin to the terrain border
1586 * \returns \c false if the initial coordinate was outside terrain area; \c true otherwise
1587 */
AdjustToBounds(Math::Vector & pos,float margin)1588 bool CTerrain::AdjustToBounds(Math::Vector& pos, float margin)
1589 {
1590 bool ok = true;
1591 float limit = m_mosaicCount*m_brickCount*m_brickSize/2.0f - margin;
1592
1593 if (pos.x < -limit)
1594 {
1595 pos.x = -limit;
1596 ok = false;
1597 }
1598
1599 if (pos.z < -limit)
1600 {
1601 pos.z = -limit;
1602 ok = false;
1603 }
1604
1605 if (pos.x > limit)
1606 {
1607 pos.x = limit;
1608 ok = false;
1609 }
1610
1611 if (pos.z > limit)
1612 {
1613 pos.z = limit;
1614 ok = false;
1615 }
1616
1617 return ok;
1618 }
1619
FlushBuildingLevel()1620 void CTerrain::FlushBuildingLevel()
1621 {
1622 m_buildingLevels.clear();
1623 }
1624
AddBuildingLevel(Math::Vector center,float min,float max,float height,float factor)1625 bool CTerrain::AddBuildingLevel(Math::Vector center, float min, float max,
1626 float height, float factor)
1627 {
1628 int i = 0;
1629 for ( ; i < static_cast<int>( m_buildingLevels.size() ); i++)
1630 {
1631 if ( center.x == m_buildingLevels[i].center.x &&
1632 center.z == m_buildingLevels[i].center.z )
1633 {
1634 break;
1635 }
1636 }
1637
1638 if (i == static_cast<int>( m_buildingLevels.size() ))
1639 m_buildingLevels.push_back(BuildingLevel());
1640
1641 m_buildingLevels[i].center = center;
1642 m_buildingLevels[i].min = min;
1643 m_buildingLevels[i].max = max;
1644 m_buildingLevels[i].level = GetFloorLevel(center, true);
1645 m_buildingLevels[i].height = height;
1646 m_buildingLevels[i].factor = factor;
1647 m_buildingLevels[i].bboxMinX = center.x-max;
1648 m_buildingLevels[i].bboxMaxX = center.x+max;
1649 m_buildingLevels[i].bboxMinZ = center.z-max;
1650 m_buildingLevels[i].bboxMaxZ = center.z+max;
1651
1652 return true;
1653 }
1654
UpdateBuildingLevel(Math::Vector center)1655 bool CTerrain::UpdateBuildingLevel(Math::Vector center)
1656 {
1657 for (int i = 0; i < static_cast<int>( m_buildingLevels.size() ); i++)
1658 {
1659 if ( center.x == m_buildingLevels[i].center.x &&
1660 center.z == m_buildingLevels[i].center.z )
1661 {
1662 m_buildingLevels[i].center = center;
1663 m_buildingLevels[i].level = GetFloorLevel(center, true);
1664 return true;
1665 }
1666 }
1667 return false;
1668 }
1669
DeleteBuildingLevel(Math::Vector center)1670 bool CTerrain::DeleteBuildingLevel(Math::Vector center)
1671 {
1672 for (int i = 0; i < static_cast<int>( m_buildingLevels.size() ); i++)
1673 {
1674 if ( center.x == m_buildingLevels[i].center.x &&
1675 center.z == m_buildingLevels[i].center.z )
1676 {
1677 for (int j = i+1; j < static_cast<int>( m_buildingLevels.size() ); j++)
1678 m_buildingLevels[j-1] = m_buildingLevels[j];
1679
1680 m_buildingLevels.pop_back();
1681 return true;
1682 }
1683 }
1684 return false;
1685 }
1686
GetBuildingFactor(const Math::Vector & pos)1687 float CTerrain::GetBuildingFactor(const Math::Vector &pos)
1688 {
1689 for (int i = 0; i < static_cast<int>( m_buildingLevels.size() ); i++)
1690 {
1691 if ( pos.x < m_buildingLevels[i].bboxMinX ||
1692 pos.x > m_buildingLevels[i].bboxMaxX ||
1693 pos.z < m_buildingLevels[i].bboxMinZ ||
1694 pos.z > m_buildingLevels[i].bboxMaxZ ) continue;
1695
1696 float dist = Math::DistanceProjected(pos, m_buildingLevels[i].center);
1697
1698 if (dist <= m_buildingLevels[i].max)
1699 return m_buildingLevels[i].factor;
1700 }
1701 return 1.0f; // it is normal on the ground
1702 }
1703
AdjustBuildingLevel(Math::Vector & p)1704 void CTerrain::AdjustBuildingLevel(Math::Vector &p)
1705 {
1706 for (int i = 0; i < static_cast<int>( m_buildingLevels.size() ); i++)
1707 {
1708 if ( p.x < m_buildingLevels[i].bboxMinX ||
1709 p.x > m_buildingLevels[i].bboxMaxX ||
1710 p.z < m_buildingLevels[i].bboxMinZ ||
1711 p.z > m_buildingLevels[i].bboxMaxZ ) continue;
1712
1713 float dist = Math::DistanceProjected(p, m_buildingLevels[i].center);
1714
1715 if (dist > m_buildingLevels[i].max) continue;
1716
1717 if (dist < m_buildingLevels[i].min)
1718 {
1719 p.y = m_buildingLevels[i].level + m_buildingLevels[i].height;
1720 return;
1721 }
1722
1723 Math::Vector border;
1724 border.x = ((p.x - m_buildingLevels[i].center.x) * m_buildingLevels[i].max) /
1725 dist + m_buildingLevels[i].center.x;
1726 border.z = ((p.z - m_buildingLevels[i].center.z) * m_buildingLevels[i].max) /
1727 dist + m_buildingLevels[i].center.z;
1728
1729 float base = GetFloorLevel(border, true);
1730
1731 p.y = (m_buildingLevels[i].max - dist) /
1732 (m_buildingLevels[i].max - m_buildingLevels[i].min) *
1733 (m_buildingLevels[i].level + m_buildingLevels[i].height-base) +
1734 base;
1735
1736 return;
1737 }
1738 }
1739
GetHardness(const Math::Vector & pos)1740 float CTerrain::GetHardness(const Math::Vector &pos)
1741 {
1742 float factor = GetBuildingFactor(pos);
1743 if (factor != 1.0f) return 1.0f; // on building level
1744
1745 if (m_materialPoints.empty()) return m_defaultHardness;
1746
1747 float dim = (m_mosaicCount*m_brickCount*m_brickSize)/2.0f;
1748
1749 int x, y;
1750
1751 x = static_cast<int>((pos.x+dim)/m_brickSize);
1752 y = static_cast<int>((pos.z+dim)/m_brickSize);
1753
1754 if ( x < 0 || x > m_mosaicCount*m_brickCount ||
1755 y < 0 || y > m_mosaicCount*m_brickCount ) return m_defaultHardness;
1756
1757 x /= m_brickCount/m_textureSubdivCount;
1758 y /= m_brickCount/m_textureSubdivCount;
1759
1760 if ( x < 0 || x >= m_materialPointCount ||
1761 y < 0 || y >= m_materialPointCount ) return m_defaultHardness;
1762
1763 int id = m_materialPoints[x+y*m_materialPointCount].id;
1764 TerrainMaterial* tm = FindMaterial(id);
1765 if (tm == nullptr) return m_defaultHardness;
1766
1767 return tm->hardness;
1768 }
1769
ShowFlatGround(Math::Vector pos)1770 void CTerrain::ShowFlatGround(Math::Vector pos)
1771 {
1772 static char table[41*41] = { 1 };
1773
1774 float radius = 3200.0f/1024.0f;
1775
1776 for (int y = 0; y <= 40; y++)
1777 {
1778 for (int x = 0; x <= 40; x++)
1779 {
1780 int i = x + y*41;
1781 table[i] = 0;
1782
1783 Math::Vector p;
1784 p.x = (x-20)*radius;
1785 p.z = (y-20)*radius;
1786 p.y = 0.0f;
1787
1788 if (Math::Point(p.x, p.y).Length() > 20.0f*radius)
1789 continue;
1790
1791 float angle = GetFineSlope(pos+p);
1792
1793 if (angle < TERRAIN_FLATLIMIT)
1794 table[i] = 1;
1795 else
1796 table[i] = 2;
1797 }
1798 }
1799
1800 m_engine->CreateGroundMark(pos, 40.0f, 0.001f, 15.0f, 0.001f, 41, 41, table);
1801 }
1802
GetFlatZoneRadius(Math::Vector center,float max)1803 float CTerrain::GetFlatZoneRadius(Math::Vector center, float max)
1804 {
1805 float angle = GetFineSlope(center);
1806 if (angle >= TERRAIN_FLATLIMIT)
1807 return 0.0f;
1808
1809 float ref = GetFloorLevel(center, true);
1810 Math::Point c(center.x, center.z);
1811 float radius = 1.0f;
1812
1813 while (radius <= max)
1814 {
1815 angle = 0.0f;
1816 int nb = static_cast<int>(2.0f*Math::PI*radius);
1817 if (nb < 8) nb = 8;
1818
1819 Math::Point p (center.x+radius, center.z);
1820 for (int i = 0; i < nb; i++)
1821 {
1822 Math::Point result = Math::RotatePoint(c, angle, p);
1823 Math::Vector pos;
1824 pos.x = result.x;
1825 pos.z = result.y;
1826 float h = GetFloorLevel(pos, true);
1827 if ( fabs(h-ref) > 1.0f ) return radius;
1828
1829 angle += Math::PI*2.0f/8.0f;
1830 }
1831 radius += 1.0f;
1832 }
1833 return max;
1834 }
1835
SetFlyingMaxHeight(float height)1836 void CTerrain::SetFlyingMaxHeight(float height)
1837 {
1838 m_flyingMaxHeight = height;
1839 }
1840
GetFlyingMaxHeight()1841 float CTerrain::GetFlyingMaxHeight()
1842 {
1843 return m_flyingMaxHeight;
1844 }
1845
FlushFlyingLimit()1846 void CTerrain::FlushFlyingLimit()
1847 {
1848 m_flyingMaxHeight = 280.0f;
1849 m_flyingLimits.clear();
1850 }
1851
AddFlyingLimit(Math::Vector center,float extRadius,float intRadius,float maxHeight)1852 void CTerrain::AddFlyingLimit(Math::Vector center,
1853 float extRadius, float intRadius,
1854 float maxHeight)
1855 {
1856 FlyingLimit fl;
1857 fl.center = center;
1858 fl.extRadius = extRadius;
1859 fl.intRadius = intRadius;
1860 fl.maxHeight = maxHeight;
1861 m_flyingLimits.push_back(fl);
1862 }
1863
GetFlyingLimit(Math::Vector pos,bool noLimit)1864 float CTerrain::GetFlyingLimit(Math::Vector pos, bool noLimit)
1865 {
1866 if (noLimit)
1867 return 280.0f;
1868
1869 if (m_flyingLimits.empty())
1870 return m_flyingMaxHeight;
1871
1872 for (int i = 0; i < static_cast<int>( m_flyingLimits.size() ); i++)
1873 {
1874 float dist = Math::DistanceProjected(pos, m_flyingLimits[i].center);
1875
1876 if (dist >= m_flyingLimits[i].extRadius)
1877 continue;
1878
1879 if (dist <= m_flyingLimits[i].intRadius)
1880 return m_flyingLimits[i].maxHeight;
1881
1882 dist -= m_flyingLimits[i].intRadius;
1883
1884 float h = dist * (m_flyingMaxHeight - m_flyingLimits[i].maxHeight) /
1885 (m_flyingLimits[i].extRadius - m_flyingLimits[i].intRadius);
1886
1887 return h + m_flyingLimits[i].maxHeight;
1888 }
1889
1890 return m_flyingMaxHeight;
1891 }
1892
1893
1894 } // namespace Gfx
1895