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