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/engine.h"
22 
23 #include "app/app.h"
24 #include "app/input.h"
25 
26 #include "common/image.h"
27 #include "common/key.h"
28 #include "common/logger.h"
29 #include "common/make_unique.h"
30 #include "common/profiler.h"
31 #include "common/stringutils.h"
32 
33 #include "common/system/system.h"
34 
35 #include "common/thread/resource_owning_thread.h"
36 
37 #include "graphics/core/device.h"
38 #include "graphics/core/framebuffer.h"
39 
40 #include "graphics/engine/camera.h"
41 #include "graphics/engine/cloud.h"
42 #include "graphics/engine/lightman.h"
43 #include "graphics/engine/lightning.h"
44 #include "graphics/engine/oldmodelmanager.h"
45 #include "graphics/engine/particle.h"
46 #include "graphics/engine/planet.h"
47 #include "graphics/engine/pyro_manager.h"
48 #include "graphics/engine/terrain.h"
49 #include "graphics/engine/text.h"
50 #include "graphics/engine/water.h"
51 
52 #include "graphics/model/model_mesh.h"
53 #include "graphics/model/model_shadow_spot.h"
54 
55 #include "level/robotmain.h"
56 
57 #include "math/geometry.h"
58 
59 #include "sound/sound.h"
60 
61 #include "ui/controls/interface.h"
62 
63 #include <iomanip>
64 #include <SDL_surface.h>
65 #include <SDL_thread.h>
66 
67 // Graphics module namespace
68 namespace Gfx
69 {
70 
71 /**
72  * \struct EngineMouse
73  * \brief Information about mouse cursor
74  */
75 struct EngineMouse
76 {
77     //! Index of texture element for 1st image
78     int icon1;
79     //! Index of texture element for 2nd image
80     int icon2;
81     //! Shadow texture part
82     int iconShadow;
83     //! Mode to render 1st image in
84     EngineRenderState mode1;
85     //! Mode to render 2nd image in
86     EngineRenderState mode2;
87     //! Hot point
88     Math::IntPoint hotPoint;
89 
EngineMouseGfx::EngineMouse90     EngineMouse(int icon1 = -1,
91                 int icon2 = -1,
92                 int iconShadow = -1,
93                 EngineRenderState mode1 = ENG_RSTATE_NORMAL,
94                 EngineRenderState mode2 = ENG_RSTATE_NORMAL,
95                 Math::IntPoint hotPoint = Math::IntPoint())
96         : icon1(icon1)
97         , icon2(icon2)
98         , iconShadow(iconShadow)
99         , mode1(mode1)
100         , mode2(mode2)
101         , hotPoint(hotPoint)
102     {}
103 };
104 
105 const Math::IntPoint MOUSE_SIZE(32, 32);
106 const std::map<EngineMouseType, EngineMouse> MOUSE_TYPES = {
107     {{ENG_MOUSE_NORM},    {EngineMouse( 0,  1, 32, ENG_RSTATE_TTEXTURE_WHITE, ENG_RSTATE_TTEXTURE_BLACK, Math::IntPoint( 1,  1))}},
108     {{ENG_MOUSE_WAIT},    {EngineMouse( 2,  3, 33, ENG_RSTATE_TTEXTURE_WHITE, ENG_RSTATE_TTEXTURE_BLACK, Math::IntPoint( 8, 12))}},
109     {{ENG_MOUSE_HAND},    {EngineMouse( 4,  5, 34, ENG_RSTATE_TTEXTURE_WHITE, ENG_RSTATE_TTEXTURE_BLACK, Math::IntPoint( 7,  2))}},
110     {{ENG_MOUSE_NO},      {EngineMouse( 6,  7, 35, ENG_RSTATE_TTEXTURE_WHITE, ENG_RSTATE_TTEXTURE_BLACK, Math::IntPoint(10, 10))}},
111     {{ENG_MOUSE_EDIT},    {EngineMouse( 8,  9, -1, ENG_RSTATE_TTEXTURE_BLACK, ENG_RSTATE_TTEXTURE_WHITE, Math::IntPoint( 6, 10))}},
112     {{ENG_MOUSE_CROSS},   {EngineMouse(10, 11, -1, ENG_RSTATE_TTEXTURE_BLACK, ENG_RSTATE_TTEXTURE_WHITE, Math::IntPoint(10, 10))}},
113     {{ENG_MOUSE_MOVEV},   {EngineMouse(12, 13, -1, ENG_RSTATE_TTEXTURE_BLACK, ENG_RSTATE_TTEXTURE_WHITE, Math::IntPoint( 5, 11))}},
114     {{ENG_MOUSE_MOVEH},   {EngineMouse(14, 15, -1, ENG_RSTATE_TTEXTURE_BLACK, ENG_RSTATE_TTEXTURE_WHITE, Math::IntPoint(11,  5))}},
115     {{ENG_MOUSE_MOVED},   {EngineMouse(16, 17, -1, ENG_RSTATE_TTEXTURE_BLACK, ENG_RSTATE_TTEXTURE_WHITE, Math::IntPoint( 9,  9))}},
116     {{ENG_MOUSE_MOVEI},   {EngineMouse(18, 19, -1, ENG_RSTATE_TTEXTURE_BLACK, ENG_RSTATE_TTEXTURE_WHITE, Math::IntPoint( 9,  9))}},
117     {{ENG_MOUSE_MOVE},    {EngineMouse(20, 21, -1, ENG_RSTATE_TTEXTURE_BLACK, ENG_RSTATE_TTEXTURE_WHITE, Math::IntPoint(11, 11))}},
118     {{ENG_MOUSE_TARGET},  {EngineMouse(22, 23, -1, ENG_RSTATE_TTEXTURE_BLACK, ENG_RSTATE_TTEXTURE_WHITE, Math::IntPoint(15, 15))}},
119     {{ENG_MOUSE_SCROLLL}, {EngineMouse(24, 25, 43, ENG_RSTATE_TTEXTURE_BLACK, ENG_RSTATE_TTEXTURE_WHITE, Math::IntPoint( 2,  9))}},
120     {{ENG_MOUSE_SCROLLR}, {EngineMouse(26, 27, 44, ENG_RSTATE_TTEXTURE_BLACK, ENG_RSTATE_TTEXTURE_WHITE, Math::IntPoint(17,  9))}},
121     {{ENG_MOUSE_SCROLLU}, {EngineMouse(28, 29, 45, ENG_RSTATE_TTEXTURE_BLACK, ENG_RSTATE_TTEXTURE_WHITE, Math::IntPoint( 9,  2))}},
122     {{ENG_MOUSE_SCROLLD}, {EngineMouse(30, 31, 46, ENG_RSTATE_TTEXTURE_BLACK, ENG_RSTATE_TTEXTURE_WHITE, Math::IntPoint( 9, 17))}},
123 };
124 
CEngine(CApplication * app,CSystemUtils * systemUtils)125 CEngine::CEngine(CApplication *app, CSystemUtils* systemUtils)
126     : m_app(app),
127       m_systemUtils(systemUtils),
128       m_ambientColor(),
129       m_fogColor(),
130       m_deepView(),
131       m_fogStart(),
132       m_highlightRank()
133 {
134     m_device = nullptr;
135 
136     m_lightMan   = nullptr;
137     m_text       = nullptr;
138     m_particle   = nullptr;
139     m_water      = nullptr;
140     m_cloud      = nullptr;
141     m_lightning  = nullptr;
142     m_planet     = nullptr;
143     m_sound      = nullptr;
144     m_terrain    = nullptr;
145 
146     m_showStats = false;
147 
148     m_focus = 0.75f;
149     m_hfov = 2.0f * atan((640.f/480.f) * tan(m_focus / 2.0f));
150 
151     m_rankView = 0;
152 
153     m_ambientColor[0] = Color(0.5f, 0.5f, 0.5f, 0.5f);
154     m_ambientColor[1] = Color(0.5f, 0.5f, 0.5f, 0.5f);
155     m_fogColor[0]     = Color(1.0f, 1.0f, 1.0f, 1.0f);
156     m_fogColor[1]     = Color(1.0f, 1.0f, 1.0f, 1.0f);
157     m_deepView[0]     = 1000.0f;
158     m_deepView[1]     = 1000.0f;
159     m_fogStart[0]     = 0.75f;
160     m_fogStart[1]     = 0.75f;
161     m_waterAddColor   = Color(0.0f, 0.0f, 0.0f, 0.0f);
162 
163     m_render            = true;
164     m_renderInterface   = true;
165     m_screenshotMode    = false;
166     m_dirty             = true;
167     m_fog               = true;
168     m_secondTex         = "";
169     m_eyeDirH           = 0.0f;
170     m_eyeDirV           = 0.0f;
171     m_backgroundName    = "";  // no background image
172     m_backgroundColorUp   = Color();
173     m_backgroundColorDown = Color();
174     m_backgroundCloudUp   = Color();
175     m_backgroundCloudDown = Color();
176     m_backgroundFull = false;
177     m_backgroundScale = false;
178     m_overFront = true;
179     m_overColor = Color();
180     m_overMode  = ENG_RSTATE_TCOLOR_BLACK;
181     m_highlight = false;
182     std::fill_n(m_highlightRank, 100, -1);
183     m_highlightTime = 0.0f;
184     m_eyePt    = Math::Vector(0.0f, 0.0f, 0.0f);
185     m_lookatPt = Math::Vector(0.0f, 0.0f, 1.0f);
186     m_drawWorld = true;
187     m_drawFront = false;
188     m_particleDensity = 1.0f;
189     m_clippingDistance = 1.0f;
190     m_terrainVision = 1000.0f;
191     m_textureMipmapLevel = 1;
192     m_textureAnisotropy = 1;
193     m_shadowMapping = true;
194     m_offscreenShadowRendering = true;
195     m_offscreenShadowRenderingResolution = 1024;
196     m_qualityShadows = true;
197     m_terrainShadows = false;
198     m_shadowRange = 0.0f;
199     m_multisample = 2;
200     m_vsync = 0;
201 
202     m_backForce = true;
203     m_lightMode = true;
204     m_editIndentMode = true;
205     m_editIndentValue = 4;
206     m_tracePrecision = 1.0f;
207     m_pauseBlurEnabled = true;
208 
209 
210     m_updateGeometry = false;
211     m_updateStaticBuffers = false;
212 
213     m_interfaceMode = false;
214 
215     m_debugLights = false;
216     m_debugDumpLights = false;
217 
218     m_mouseType    = ENG_MOUSE_NORM;
219 
220     m_fpsCounter = 0;
221     m_lastFrameTime = m_systemUtils->CreateTimeStamp();
222     m_currentFrameTime = m_systemUtils->CreateTimeStamp();
223 
224     m_shadowColor = 0.5f;
225 
226     m_defaultTexParams.format = TEX_IMG_AUTO;
227     m_defaultTexParams.filter = TEX_FILTER_BILINEAR;
228 
229     m_terrainTexParams.format = TEX_IMG_AUTO;
230     m_terrainTexParams.filter = TEX_FILTER_BILINEAR;
231 
232     // Compute bias matrix for shadow mapping
233     Math::Matrix temp1, temp2;
234     Math::LoadScaleMatrix(temp1, Math::Vector(0.5f, 0.5f, 0.5f));
235     Math::LoadTranslationMatrix(temp2, Math::Vector(1.0f, 1.0f, 1.0f));
236     m_shadowBias = Math::MultiplyMatrices(temp1, temp2);
237 
238     m_lastState = -1;
239     m_statisticTriangle = 0;
240     m_fps = 0.0f;
241     m_firstGroundSpot = false;
242 }
243 
~CEngine()244 CEngine::~CEngine()
245 {
246     m_systemUtils->DestroyTimeStamp(m_lastFrameTime);
247     m_lastFrameTime = nullptr;
248     m_systemUtils->DestroyTimeStamp(m_currentFrameTime);
249     m_currentFrameTime = nullptr;
250 }
251 
SetDevice(CDevice * device)252 void CEngine::SetDevice(CDevice *device)
253 {
254     m_device = device;
255 }
256 
GetDevice()257 CDevice* CEngine::GetDevice()
258 {
259     return m_device;
260 }
261 
GetModelManager()262 COldModelManager* CEngine::GetModelManager()
263 {
264     return m_modelManager.get();
265 }
266 
GetPyroManager()267 CPyroManager* CEngine::GetPyroManager()
268 {
269     return m_pyroManager.get();
270 }
271 
GetText()272 CText* CEngine::GetText()
273 {
274     return m_text.get();
275 }
276 
GetLightManager()277 CLightManager* CEngine::GetLightManager()
278 {
279     return m_lightMan.get();
280 }
281 
GetParticle()282 CParticle* CEngine::GetParticle()
283 {
284     return m_particle.get();
285 }
286 
GetTerrain()287 CTerrain* CEngine::GetTerrain()
288 {
289     return m_terrain;
290 }
291 
GetWater()292 CWater* CEngine::GetWater()
293 {
294     return m_water.get();
295 }
296 
GetLightning()297 CLightning* CEngine::GetLightning()
298 {
299     return m_lightning.get();
300 }
301 
GetPlanet()302 CPlanet* CEngine::GetPlanet()
303 {
304     return m_planet.get();
305 }
306 
GetCloud()307 CCloud* CEngine::GetCloud()
308 {
309     return m_cloud.get();
310 }
311 
SetTerrain(CTerrain * terrain)312 void CEngine::SetTerrain(CTerrain* terrain)
313 {
314     m_terrain = terrain;
315 }
316 
Create()317 bool CEngine::Create()
318 {
319     m_size = m_app->GetVideoConfig().size;
320 
321     // Use the setters to set defaults, because they automatically disable what is not supported
322     SetShadowMapping(m_shadowMapping);
323     SetShadowMappingQuality(m_qualityShadows);
324     SetShadowMappingOffscreen(m_offscreenShadowRendering);
325     SetShadowMappingOffscreenResolution(m_offscreenShadowRenderingResolution);
326     SetMultiSample(m_multisample);
327     SetVSync(m_vsync);
328 
329     m_modelManager = MakeUnique<COldModelManager>(this);
330     m_pyroManager = MakeUnique<CPyroManager>();
331     m_lightMan   = MakeUnique<CLightManager>(this);
332     m_text       = MakeUnique<CText>(this);
333     m_particle   = MakeUnique<CParticle>(this);
334     m_water      = MakeUnique<CWater>(this);
335     m_cloud      = MakeUnique<CCloud>(this);
336     m_lightning  = MakeUnique<CLightning>(this);
337     m_planet     = MakeUnique<CPlanet>(this);
338 
339     m_lightMan->SetDevice(m_device);
340     m_particle->SetDevice(m_device);
341 
342     m_text->SetDevice(m_device);
343     if (! m_text->Create())
344     {
345         std::string error = m_text->GetError();
346         GetLogger()->Error("Error creating CText: %s\n", error.c_str());
347         return false;
348     }
349 
350     m_device->SetClearColor(Color(0.0f, 0.0f, 0.0f, 0.0f));
351     m_device->SetShadeModel(SHADE_SMOOTH);
352     m_device->SetFillMode(FILL_POLY);
353 
354     SetFocus(m_focus);
355 
356     m_matWorldInterface.LoadIdentity();
357     m_matViewInterface.LoadIdentity();
358 
359     Math::LoadOrthoProjectionMatrix(m_matProjInterface, 0.0f, 1.0f, 0.0f, 1.0f, -1.0f, 1.0f);
360 
361     TextureCreateParams params;
362     params.format = TEX_IMG_AUTO;
363     params.filter = TEX_FILTER_NEAREST;
364     params.mipmap = false;
365     m_miceTexture = LoadTexture("textures/interface/mouse.png", params);
366 
367     m_systemUtils->GetCurrentTimeStamp(m_currentFrameTime);
368     m_systemUtils->GetCurrentTimeStamp(m_lastFrameTime);
369 
370     return true;
371 }
372 
Destroy()373 void CEngine::Destroy()
374 {
375     m_text->Destroy();
376 
377     if (m_shadowMap.id != 0)
378     {
379         if (m_offscreenShadowRendering)
380             m_device->DeleteFramebuffer("shadow");
381         else
382             m_device->DestroyTexture(m_shadowMap);
383 
384         m_shadowMap = Texture();
385     }
386 
387     m_lightMan.reset();
388     m_text.reset();
389     m_particle.reset();
390     m_water.reset();
391     m_cloud.reset();
392     m_lightning.reset();
393     m_planet.reset();
394 }
395 
ResetAfterVideoConfigChanged()396 void CEngine::ResetAfterVideoConfigChanged()
397 {
398     m_size = m_app->GetVideoConfig().size;
399 
400     // Update the camera projection matrix for new aspect ratio
401     ApplyChange();
402 
403     // This needs to be recreated on resolution change
404     m_device->DeleteFramebuffer("multisample");
405 }
406 
ReloadAllTextures()407 void CEngine::ReloadAllTextures()
408 {
409     FlushTextureCache();
410     m_text->FlushCache();
411     m_text->ReloadFonts();
412 
413     m_app->GetEventQueue()->AddEvent(Event(EVENT_RELOAD_TEXTURES));
414     UpdateGroundSpotTextures();
415     // LoadAllTextures() is called from CRobotMain on EVENT_RELOAD_TEXTURES
416     // This is required because all dynamic textures need to be loaded first
417 
418     // recapture 3D scene
419     if (m_worldCaptured)
420     {
421         m_captureWorld = true;
422         m_worldCaptured = false;
423     }
424 }
425 
ProcessEvent(const Event & event)426 bool CEngine::ProcessEvent(const Event &event)
427 {
428     if (event.type == EVENT_RESOLUTION_CHANGED)
429     {
430         ResetAfterVideoConfigChanged();
431     }
432 
433     if (event.type == EVENT_KEY_DOWN)
434     {
435         auto data = event.GetData<KeyEventData>();
436 
437         if (data->key == KEY(F11) || data->key == KEY(F12))
438         {
439             m_showStats = !m_showStats;
440             return false;
441         }
442     }
443 
444     // By default, pass on all events
445     return true;
446 }
447 
FrameUpdate()448 void CEngine::FrameUpdate()
449 {
450     float rTime = m_app->GetRelTime();
451 
452     m_lightMan->UpdateProgression(rTime);
453 
454     CProfiler::StartPerformanceCounter(PCNT_UPDATE_PARTICLE);
455     m_particle->FrameParticle(rTime);
456     CProfiler::StopPerformanceCounter(PCNT_UPDATE_PARTICLE);
457 
458     ComputeDistance();
459     UpdateGeometry();
460     UpdateStaticBuffers();
461 
462     m_highlightTime = m_app->GetAbsTime();
463 
464     if (m_groundMark.draw)
465     {
466         if (m_groundMark.phase == ENG_GR_MARK_PHASE_INC)  // growing?
467         {
468             m_groundMark.intensity += rTime*(1.0f/m_groundMark.delay[0]);
469             if (m_groundMark.intensity >= 1.0f)
470             {
471                 m_groundMark.intensity = 1.0f;
472                 m_groundMark.fix = 0.0f;
473                 m_groundMark.phase = ENG_GR_MARK_PHASE_FIX;
474             }
475         }
476         else if (m_groundMark.phase == ENG_GR_MARK_PHASE_FIX)  // fixed?
477         {
478             m_groundMark.fix += rTime*(1.0f/m_groundMark.delay[1]);
479             if (m_groundMark.fix >= 1.0f)
480                 m_groundMark.phase = ENG_GR_MARK_PHASE_DEC;
481         }
482         else if (m_groundMark.phase == ENG_GR_MARK_PHASE_DEC)  // decay?
483         {
484             m_groundMark.intensity -= rTime*(1.0f/m_groundMark.delay[2]);
485             if (m_groundMark.intensity < 0.0f)
486             {
487                 m_groundMark.intensity = 0.0f;
488                 m_groundMark.phase     = ENG_GR_MARK_PHASE_NULL;
489                 m_groundMark.draw      = false;
490             }
491         }
492     }
493 }
494 
WriteScreenShot(const std::string & fileName)495 void CEngine::WriteScreenShot(const std::string& fileName)
496 {
497     auto data = MakeUnique<WriteScreenShotData>();
498     data->img = MakeUnique<CImage>(Math::IntPoint(m_size.x, m_size.y));
499 
500     auto pixels = m_device->GetFrameBufferPixels();
501     data->img->SetDataPixels(pixels->GetPixelsData());
502     data->img->FlipVertically();
503 
504     data->fileName = fileName;
505 
506     CResourceOwningThread<WriteScreenShotData> thread(CEngine::WriteScreenShotThread, std::move(data), "WriteScreenShot thread");
507     thread.Start();
508 }
509 
WriteScreenShotThread(std::unique_ptr<WriteScreenShotData> data)510 void CEngine::WriteScreenShotThread(std::unique_ptr<WriteScreenShotData> data)
511 {
512     if ( data->img->SavePNG(data->fileName.c_str()) )
513     {
514        GetLogger()->Debug("Save screenshot saved successfully\n");
515     }
516     else
517     {
518        GetLogger()->Error("%s!\n", data->img->GetError().c_str());
519     }
520 
521     CApplication::GetInstancePointer()->GetEventQueue()->AddEvent(Event(EVENT_WRITE_SCENE_FINISHED));
522 }
523 
SetPause(bool pause)524 void CEngine::SetPause(bool pause)
525 {
526     m_pause = pause;
527 }
528 
GetPause()529 bool CEngine::GetPause()
530 {
531     return m_pause;
532 }
533 
SetShowStats(bool show)534 void CEngine::SetShowStats(bool show)
535 {
536     m_showStats = show;
537 }
538 
GetShowStats()539 bool CEngine::GetShowStats()
540 {
541     return m_showStats;
542 }
543 
SetRenderEnable(bool enable)544 void CEngine::SetRenderEnable(bool enable)
545 {
546     m_render = enable;
547 }
548 
SetRenderInterface(bool enable)549 void CEngine::SetRenderInterface(bool enable)
550 {
551     m_renderInterface = enable;
552 }
553 
GetRenderInterface()554 bool CEngine::GetRenderInterface()
555 {
556     return m_renderInterface;
557 }
558 
SetScreenshotMode(bool screenshotMode)559 void CEngine::SetScreenshotMode(bool screenshotMode)
560 {
561     m_screenshotMode = screenshotMode;
562 }
563 
GetScreenshotMode()564 bool CEngine::GetScreenshotMode()
565 {
566     return m_screenshotMode;
567 }
568 
GetWindowSize()569 Math::IntPoint CEngine::GetWindowSize()
570 {
571     return m_size;
572 }
573 
WindowToInterfaceCoords(Math::IntPoint pos)574 Math::Point CEngine::WindowToInterfaceCoords(Math::IntPoint pos)
575 {
576     return Math::Point(        static_cast<float>(pos.x) / static_cast<float>(m_size.x),
577                         1.0f - static_cast<float>(pos.y) / static_cast<float>(m_size.y)  );
578 }
579 
InterfaceToWindowCoords(Math::Point pos)580 Math::IntPoint CEngine::InterfaceToWindowCoords(Math::Point pos)
581 {
582     return Math::IntPoint(static_cast<int>(pos.x * m_size.x),
583                           static_cast<int>((1.0f - pos.y) * m_size.y));
584 }
585 
WindowToInterfaceSize(Math::IntPoint size)586 Math::Point CEngine::WindowToInterfaceSize(Math::IntPoint size)
587 {
588     return Math::Point(static_cast<float>(size.x) / static_cast<float>(m_size.x),
589                        static_cast<float>(size.y) / static_cast<float>(m_size.y));
590 }
591 
InterfaceToWindowSize(Math::Point size)592 Math::IntPoint CEngine::InterfaceToWindowSize(Math::Point size)
593 {
594     return Math::IntPoint(static_cast<int>(size.x * m_size.x),
595                           static_cast<int>(size.y * m_size.y));
596 }
597 
AddStatisticTriangle(int count)598 void CEngine::AddStatisticTriangle(int count)
599 {
600     m_statisticTriangle += count;
601 }
602 
GetStatisticTriangle()603 int CEngine::GetStatisticTriangle()
604 {
605     return m_statisticTriangle;
606 }
607 
SetStatisticPos(Math::Vector pos)608 void CEngine::SetStatisticPos(Math::Vector pos)
609 {
610     m_statisticPos = pos;
611 }
612 
SetTimerDisplay(const std::string & text)613 void CEngine::SetTimerDisplay(const std::string& text)
614 {
615     m_timerText = text;
616 }
617 
618 
619 
620 /*******************************************************
621                    Object management
622  *******************************************************/
623 
AddLevel2(EngineBaseObject & p1,const std::string & tex1Name,const std::string & tex2Name)624 EngineBaseObjTexTier& CEngine::AddLevel2(EngineBaseObject& p1, const std::string& tex1Name, const std::string& tex2Name)
625 {
626     for (int i = 0; i < static_cast<int>( p1.next.size() ); i++)
627     {
628         if (p1.next[i].tex1Name == tex1Name && p1.next[i].tex2Name == tex2Name)
629             return p1.next[i];
630     }
631 
632     p1.next.push_back(EngineBaseObjTexTier(tex1Name, tex2Name));
633     return p1.next.back();
634 }
635 
AddLevel3(EngineBaseObjTexTier & p3,EngineTriangleType type,const Material & material,int state)636 EngineBaseObjDataTier& CEngine::AddLevel3(EngineBaseObjTexTier& p3, EngineTriangleType type,
637                                           const Material& material, int state)
638 {
639     for (int i = 0; i < static_cast<int>( p3.next.size() ); i++)
640     {
641         if ( (p3.next[i].type == type) && (p3.next[i].material == material) && (p3.next[i].state == state) )
642             return p3.next[i];
643     }
644 
645     p3.next.push_back(EngineBaseObjDataTier(type, material, state));
646     return p3.next.back();
647 }
648 
CreateBaseObject()649 int CEngine::CreateBaseObject()
650 {
651     int baseObjRank = 0;
652     for ( ; baseObjRank < static_cast<int>( m_baseObjects.size() ); baseObjRank++)
653     {
654         if (! m_baseObjects[baseObjRank].used)
655         {
656             m_baseObjects[baseObjRank].LoadDefault();
657             break;
658         }
659     }
660 
661     if (baseObjRank == static_cast<int>( m_baseObjects.size() ))
662         m_baseObjects.push_back(EngineBaseObject());
663     else
664         m_baseObjects[baseObjRank].LoadDefault();
665 
666 
667     m_baseObjects[baseObjRank].used = true;
668 
669     return baseObjRank;
670 }
671 
DeleteBaseObject(int baseObjRank)672 void CEngine::DeleteBaseObject(int baseObjRank)
673 {
674     assert(baseObjRank >= 0 && baseObjRank < static_cast<int>( m_baseObjects.size() ));
675 
676     EngineBaseObject& p1 = m_baseObjects[baseObjRank];
677 
678     if (! p1.used)
679         return;
680 
681     for (int l2 = 0; l2 < static_cast<int>( p1.next.size() ); l2++)
682     {
683         EngineBaseObjTexTier& p2 = p1.next[l2];
684 
685         for (int l3 = 0; l3 < static_cast<int>( p2.next.size() ); l3++)
686         {
687             EngineBaseObjDataTier& p3 = p2.next[l3];
688             m_device->DestroyStaticBuffer(p3.staticBufferId);
689             p3.staticBufferId = 0;
690         }
691     }
692 
693     p1.next.clear();
694 
695     p1.used = false;
696 }
697 
DeleteAllBaseObjects()698 void CEngine::DeleteAllBaseObjects()
699 {
700     for (int baseObjRank = 0; baseObjRank < static_cast<int>( m_baseObjects.size() ); baseObjRank++)
701     {
702         EngineBaseObject& p1 = m_baseObjects[baseObjRank];
703         if (! p1.used)
704             continue;
705 
706         for (int l2 = 0; l2 < static_cast<int>( p1.next.size() ); l2++)
707         {
708             EngineBaseObjTexTier& p2 = p1.next[l2];
709 
710             for (int l3 = 0; l3 < static_cast<int>( p2.next.size() ); l3++)
711             {
712                 EngineBaseObjDataTier& p3 = p2.next[l3];
713                 m_device->DestroyStaticBuffer(p3.staticBufferId);
714                 p3.staticBufferId = 0;
715             }
716         }
717     }
718 
719     m_baseObjects.clear();
720 }
721 
CopyBaseObject(int sourceBaseObjRank,int destBaseObjRank)722 void CEngine::CopyBaseObject(int sourceBaseObjRank, int destBaseObjRank)
723 {
724     assert(sourceBaseObjRank >= 0 && sourceBaseObjRank < static_cast<int>( m_baseObjects.size() ));
725     assert(destBaseObjRank >= 0 && destBaseObjRank < static_cast<int>( m_baseObjects.size() ));
726 
727     m_baseObjects[destBaseObjRank] = m_baseObjects[sourceBaseObjRank];
728 
729     EngineBaseObject& p1 = m_baseObjects[destBaseObjRank];
730 
731     if (! p1.used)
732         return;
733 
734     for (int l2 = 0; l2 < static_cast<int>( p1.next.size() ); l2++)
735     {
736         EngineBaseObjTexTier& p2 = p1.next[l2];
737 
738         for (int l3 = 0; l3 < static_cast<int>( p2.next.size() ); l3++)
739         {
740             EngineBaseObjDataTier& p3 = p2.next[l3];
741             p3.staticBufferId = 0;
742         }
743     }
744 }
745 
AddBaseObjTriangles(int baseObjRank,const std::vector<VertexTex2> & vertices,const Material & material,int state,std::string tex1Name,std::string tex2Name)746 void CEngine::AddBaseObjTriangles(int baseObjRank, const std::vector<VertexTex2>& vertices,
747                                   const Material& material, int state,
748                                   std::string tex1Name, std::string tex2Name)
749 {
750     assert(baseObjRank >= 0 && baseObjRank < static_cast<int>( m_baseObjects.size() ));
751 
752     EngineBaseObject&      p1 = m_baseObjects[baseObjRank];
753     EngineBaseObjTexTier&  p2 = AddLevel2(p1, tex1Name, tex2Name);
754     EngineBaseObjDataTier& p3 = AddLevel3(p2, ENG_TRIANGLE_TYPE_TRIANGLES, material, state);
755 
756     p3.vertices.insert(p3.vertices.end(), vertices.begin(), vertices.end());
757 
758     p3.updateStaticBuffer = true;
759     m_updateStaticBuffers = true;
760 
761     for (int i = 0; i < static_cast<int>( vertices.size() ); i++)
762     {
763         p1.bboxMin.x = Math::Min(vertices[i].coord.x, p1.bboxMin.x);
764         p1.bboxMin.y = Math::Min(vertices[i].coord.y, p1.bboxMin.y);
765         p1.bboxMin.z = Math::Min(vertices[i].coord.z, p1.bboxMin.z);
766         p1.bboxMax.x = Math::Max(vertices[i].coord.x, p1.bboxMax.x);
767         p1.bboxMax.y = Math::Max(vertices[i].coord.y, p1.bboxMax.y);
768         p1.bboxMax.z = Math::Max(vertices[i].coord.z, p1.bboxMax.z);
769     }
770 
771     p1.boundingSphere = Math::BoundingSphereForBox(p1.bboxMin, p1.bboxMax);
772 
773     p1.totalTriangles += vertices.size() / 3;
774 }
775 
AddBaseObjQuick(int baseObjRank,const EngineBaseObjDataTier & buffer,std::string tex1Name,std::string tex2Name,bool globalUpdate)776 void CEngine::AddBaseObjQuick(int baseObjRank, const EngineBaseObjDataTier& buffer,
777                               std::string tex1Name, std::string tex2Name,
778                               bool globalUpdate)
779 {
780     assert(baseObjRank >= 0 && baseObjRank < static_cast<int>( m_baseObjects.size() ));
781 
782     EngineBaseObject&      p1 = m_baseObjects[baseObjRank];
783     EngineBaseObjTexTier&  p2 = AddLevel2(p1, tex1Name, tex2Name);
784 
785     p2.next.push_back(buffer);
786 
787     EngineBaseObjDataTier& p3 = p2.next.back();
788 
789     UpdateStaticBuffer(p3);
790 
791     if (globalUpdate)
792     {
793         m_updateGeometry = true;
794     }
795     else
796     {
797         for (int i = 0; i < static_cast<int>( p3.vertices.size() ); i++)
798         {
799             p1.bboxMin.x = Math::Min(p3.vertices[i].coord.x, p1.bboxMin.x);
800             p1.bboxMin.y = Math::Min(p3.vertices[i].coord.y, p1.bboxMin.y);
801             p1.bboxMin.z = Math::Min(p3.vertices[i].coord.z, p1.bboxMin.z);
802             p1.bboxMax.x = Math::Max(p3.vertices[i].coord.x, p1.bboxMax.x);
803             p1.bboxMax.y = Math::Max(p3.vertices[i].coord.y, p1.bboxMax.y);
804             p1.bboxMax.z = Math::Max(p3.vertices[i].coord.z, p1.bboxMax.z);
805         }
806 
807         p1.boundingSphere = Math::BoundingSphereForBox(p1.bboxMin, p1.bboxMax);
808     }
809 
810     if (p3.type == ENG_TRIANGLE_TYPE_TRIANGLES)
811         p1.totalTriangles += p3.vertices.size() / 3;
812     else if (p3.type == ENG_TRIANGLE_TYPE_SURFACE)
813         p1.totalTriangles += p3.vertices.size() - 2;
814 }
815 
DebugObject(int objRank)816 void CEngine::DebugObject(int objRank)
817 {
818     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
819 
820     CLogger* l = GetLogger();
821 
822     l->Debug("Debug object: %d\n", objRank);
823     if (! m_objects[objRank].used)
824     {
825         l->Debug(" not used\n");
826         return;
827     }
828 
829     l->Debug(" baseObjRank = %d\n", m_objects[objRank].baseObjRank);
830     l->Debug(" visible = %s\n", m_objects[objRank].visible ? "true" : "false");
831     l->Debug(" drawWorld = %s\n", m_objects[objRank].drawWorld ? "true" : "false");
832     l->Debug(" drawFront = %s\n", m_objects[objRank].drawFront ? "true" : "false");
833     l->Debug(" type = %d\n", m_objects[objRank].type);
834     l->Debug(" distance = %f\n", m_objects[objRank].distance);
835     l->Debug(" shadowRank = %d\n", m_objects[objRank].shadowRank);
836     l->Debug(" transparency = %f\n", m_objects[objRank].transparency);
837 
838     l->Debug(" baseObj:\n");
839     int baseObjRank = m_objects[objRank].baseObjRank;
840     if (baseObjRank == -1)
841     {
842         l->Debug("  null\n");
843         return;
844     }
845 
846     assert(baseObjRank >= 0 && baseObjRank < static_cast<int>( m_baseObjects.size() ));
847 
848     EngineBaseObject& p1 = m_baseObjects[baseObjRank];
849     if (!p1.used)
850     {
851         l->Debug("  not used\n");
852         return;
853     }
854 
855     std::string vecStr;
856 
857     vecStr = p1.bboxMin.ToString();
858     l->Debug("  bboxMin: %s\n", vecStr.c_str());
859     vecStr = p1.bboxMax.ToString();
860     l->Debug("  bboxMax: %s\n", vecStr.c_str());
861     l->Debug("  totalTriangles: %d\n", p1.totalTriangles);
862     l->Debug("  radius: %f\n", p1.boundingSphere.radius);
863 
864     for (int l2 = 0; l2 < static_cast<int>( p1.next.size() ); l2++)
865     {
866         EngineBaseObjTexTier& p2 = p1.next[l2];
867         l->Debug("  l2:\n");
868 
869         l->Debug("   tex1: %s (id: %u)\n", p2.tex1Name.c_str(), p2.tex1.id);
870         l->Debug("   tex2: %s (id: %u)\n", p2.tex2Name.c_str(), p2.tex2.id);
871 
872         for (int l3 = 0; l3 < static_cast<int>( p2.next.size() ); l3++)
873         {
874             EngineBaseObjDataTier& p3 = p2.next[l3];
875 
876             l->Debug("   l3:\n");
877             l->Debug("    type: %d\n", p3.type);
878             l->Debug("    state: %d\n", p3.state);
879             l->Debug("    staticBufferId: %u\n", p3.staticBufferId);
880             l->Debug("    updateStaticBuffer: %s\n", p3.updateStaticBuffer ? "true" : "false");
881         }
882     }
883 }
884 
CreateObject()885 int CEngine::CreateObject()
886 {
887     int objRank = 0;
888     for ( ; objRank < static_cast<int>( m_objects.size() ); objRank++)
889     {
890         if (! m_objects[objRank].used)
891         {
892             m_objects[objRank].LoadDefault();
893             break;
894         }
895     }
896 
897     if (objRank == static_cast<int>( m_objects.size() ))
898         m_objects.push_back(EngineObject());
899 
900 
901     m_objects[objRank].used = true;
902 
903     Math::Matrix mat;
904     mat.LoadIdentity();
905     SetObjectTransform(objRank, mat);
906 
907     m_objects[objRank].drawWorld = true;
908     m_objects[objRank].distance = 0.0f;
909     m_objects[objRank].baseObjRank = -1;
910     m_objects[objRank].shadowRank = -1;
911 
912     return objRank;
913 }
914 
DeleteAllObjects()915 void CEngine::DeleteAllObjects()
916 {
917     m_objects.clear();
918     m_shadowSpots.clear();
919 
920     DeleteAllGroundSpots();
921 }
922 
DeleteObject(int objRank)923 void CEngine::DeleteObject(int objRank)
924 {
925     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
926 
927     // Mark object as deleted
928     m_objects[objRank].used = false;
929 
930     // Delete associated shadows
931     DeleteShadowSpot(objRank);
932 }
933 
SetObjectBaseRank(int objRank,int baseObjRank)934 void CEngine::SetObjectBaseRank(int objRank, int baseObjRank)
935 {
936     assert(objRank == -1 || (objRank >= 0 && objRank < static_cast<int>( m_objects.size() )));
937 
938     m_objects[objRank].baseObjRank = baseObjRank;
939 }
940 
GetObjectBaseRank(int objRank)941 int CEngine::GetObjectBaseRank(int objRank)
942 {
943     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
944 
945     return m_objects[objRank].baseObjRank;
946 }
947 
SetObjectType(int objRank,EngineObjectType type)948 void CEngine::SetObjectType(int objRank, EngineObjectType type)
949 {
950     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
951 
952     m_objects[objRank].type = type;
953 }
954 
GetObjectType(int objRank)955 EngineObjectType CEngine::GetObjectType(int objRank)
956 {
957     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
958 
959     return m_objects[objRank].type;
960 }
961 
962 
SetObjectTransform(int objRank,const Math::Matrix & transform)963 void CEngine::SetObjectTransform(int objRank, const Math::Matrix& transform)
964 {
965     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
966 
967     m_objects[objRank].transform = transform;
968 }
969 
GetObjectTransform(int objRank,Math::Matrix & transform)970 void CEngine::GetObjectTransform(int objRank, Math::Matrix& transform)
971 {
972     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
973 
974     transform = m_objects[objRank].transform;
975 }
976 
SetObjectDrawWorld(int objRank,bool draw)977 void CEngine::SetObjectDrawWorld(int objRank, bool draw)
978 {
979     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
980 
981     m_objects[objRank].drawWorld = draw;
982 }
983 
SetObjectDrawFront(int objRank,bool draw)984 void CEngine::SetObjectDrawFront(int objRank, bool draw)
985 {
986     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
987 
988     m_objects[objRank].drawFront = draw;
989 }
990 
SetObjectTransparency(int objRank,float value)991 void CEngine::SetObjectTransparency(int objRank, float value)
992 {
993     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
994 
995     m_objects[objRank].transparency = value;
996 }
997 
GetObjectBBox(int objRank,Math::Vector & min,Math::Vector & max)998 void CEngine::GetObjectBBox(int objRank, Math::Vector& min, Math::Vector& max)
999 {
1000     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
1001 
1002     int baseObjRank = m_objects[objRank].baseObjRank;
1003     if (baseObjRank == -1)
1004         return;
1005 
1006     assert(baseObjRank >= 0 && baseObjRank < static_cast<int>(m_baseObjects.size()));
1007 
1008     min = m_baseObjects[baseObjRank].bboxMin;
1009     max = m_baseObjects[baseObjRank].bboxMax;
1010 }
1011 
1012 
GetObjectTotalTriangles(int objRank)1013 int CEngine::GetObjectTotalTriangles(int objRank)
1014 {
1015     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
1016 
1017     int baseObjRank = m_objects[objRank].baseObjRank;
1018     if (baseObjRank == -1)
1019         return 0;
1020 
1021     assert(baseObjRank >= 0 && baseObjRank < static_cast<int>( m_baseObjects.size() ));
1022 
1023     return m_baseObjects[baseObjRank].totalTriangles;
1024 }
1025 
FindTriangles(int objRank,const Material & material,int state,std::string tex1Name,std::string tex2Name)1026 EngineBaseObjDataTier* CEngine::FindTriangles(int objRank, const Material& material,
1027                                               int state, std::string tex1Name,
1028                                               std::string tex2Name)
1029 {
1030     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
1031 
1032     int baseObjRank = m_objects[objRank].baseObjRank;
1033     if (baseObjRank == -1)
1034         return nullptr;
1035 
1036     assert(baseObjRank >= 0 && baseObjRank < static_cast<int>( m_baseObjects.size() ));
1037 
1038     EngineBaseObject& p1 = m_baseObjects[baseObjRank];
1039 
1040     for (int l2 = 0; l2 < static_cast<int>( p1.next.size() ); l2++)
1041     {
1042         EngineBaseObjTexTier& p2 = p1.next[l2];
1043 
1044         if (p2.tex1Name != tex1Name)
1045             continue;
1046 
1047         for (int l3 = 0; l3 < static_cast<int>( p2.next.size() ); l3++)
1048         {
1049             EngineBaseObjDataTier& p3 = p2.next[l3];
1050 
1051             if ( (p3.state & (~(ENG_RSTATE_DUAL_BLACK|ENG_RSTATE_DUAL_WHITE))) != state ||
1052                   p3.material != material )
1053                 continue;
1054 
1055             return &p3;
1056         }
1057     }
1058 
1059     return nullptr;
1060 }
1061 
GetPartialTriangles(int objRank,float percent,int maxCount,std::vector<EngineTriangle> & triangles)1062 int CEngine::GetPartialTriangles(int objRank, float percent, int maxCount,
1063                                  std::vector<EngineTriangle>& triangles)
1064 {
1065     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
1066 
1067     int baseObjRank = m_objects[objRank].baseObjRank;
1068     if (baseObjRank == -1)
1069         return 0;
1070 
1071     assert(baseObjRank >= 0 && baseObjRank < static_cast<int>( m_baseObjects.size() ));
1072 
1073     EngineBaseObject& p1 = m_baseObjects[baseObjRank];
1074 
1075     int total = p1.totalTriangles;
1076     int expectedCount = static_cast<int>(percent * total);
1077     triangles.reserve(Math::Min(maxCount, expectedCount));
1078 
1079     int actualCount = 0;
1080 
1081     for (int l2 = 0; l2 < static_cast<int>( p1.next.size() ); l2++)
1082     {
1083         EngineBaseObjTexTier& p2 = p1.next[l2];
1084 
1085         for (int l3 = 0; l3 < static_cast<int>( p2.next.size() ); l3++)
1086         {
1087             EngineBaseObjDataTier& p3 = p2.next[l3];
1088 
1089             if (p3.type == ENG_TRIANGLE_TYPE_TRIANGLES)
1090             {
1091                 for (int i = 0; i < static_cast<int>( p3.vertices.size() ); i += 3)
1092                 {
1093                     if (static_cast<float>(actualCount) / total >= percent)
1094                         break;
1095 
1096                     if (actualCount >= maxCount)
1097                         break;
1098 
1099                     EngineTriangle t;
1100                     t.triangle[0] = p3.vertices[i];
1101                     t.triangle[1] = p3.vertices[i+1];
1102                     t.triangle[2] = p3.vertices[i+2];
1103                     t.material = p3.material;
1104                     t.state = p3.state;
1105                     t.tex1Name = p2.tex1Name;
1106                     t.tex2Name = p2.tex2Name;
1107 
1108                     triangles.push_back(t);
1109 
1110                     ++actualCount;
1111                 }
1112             }
1113             else if (p3.type == ENG_TRIANGLE_TYPE_SURFACE)
1114             {
1115                 for (int i = 0; i < static_cast<int>( p3.vertices.size() ); i += 1)
1116                 {
1117                     if (static_cast<float>(actualCount) / total >= percent)
1118                         break;
1119 
1120                     if (actualCount >= maxCount)
1121                         break;
1122 
1123                     EngineTriangle t;
1124                     t.triangle[0] = p3.vertices[i];
1125                     t.triangle[1] = p3.vertices[i+1];
1126                     t.triangle[2] = p3.vertices[i+2];
1127                     t.material = p3.material;
1128                     t.state = p3.state;
1129                     t.tex1Name = p2.tex1Name;
1130                     t.tex2Name = p2.tex2Name;
1131 
1132                     triangles.push_back(t);
1133 
1134                     ++actualCount;
1135                 }
1136             }
1137         }
1138     }
1139 
1140     return actualCount;
1141 }
1142 
ChangeSecondTexture(int objRank,const std::string & tex2Name)1143 void CEngine::ChangeSecondTexture(int objRank, const std::string& tex2Name)
1144 {
1145     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
1146 
1147     int baseObjRank = m_objects[objRank].baseObjRank;
1148     if (baseObjRank == -1)
1149         return;
1150 
1151     assert(baseObjRank >= 0 && baseObjRank < static_cast<int>( m_baseObjects.size() ));
1152 
1153     EngineBaseObject& p1 = m_baseObjects[baseObjRank];
1154 
1155     for (int l2 = 0; l2 < static_cast<int>( p1.next.size() ); l2++)
1156     {
1157         if (p1.next[l2].tex2Name == tex2Name)
1158             continue;  // already new
1159 
1160         std::string tex1Name = p1.next[l2].tex1Name;
1161         EngineBaseObjTexTier& newP2 = AddLevel2(p1, tex1Name, tex2Name);
1162         newP2.next.insert(newP2.next.end(), p1.next[l2].next.begin(), p1.next[l2].next.end());
1163         p1.next[l2].next.clear();
1164 
1165         if (!newP2.tex1.Valid() && !newP2.tex1Name.empty())
1166             newP2.tex1 = LoadTexture("textures/"+newP2.tex1Name);
1167 
1168         if (!newP2.tex2.Valid() && !newP2.tex2Name.empty())
1169             newP2.tex2 = LoadTexture("textures/"+newP2.tex2Name);
1170     }
1171 }
1172 
ChangeTextureMapping(int objRank,const Material & mat,int state,const std::string & tex1Name,const std::string & tex2Name,EngineTextureMapping mode,float au,float bu,float av,float bv)1173 void CEngine::ChangeTextureMapping(int objRank, const Material& mat, int state,
1174                                    const std::string& tex1Name, const std::string& tex2Name,
1175                                    EngineTextureMapping mode,
1176                                    float au, float bu, float av, float bv)
1177 {
1178     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
1179 
1180     EngineBaseObjDataTier* p4 = FindTriangles(objRank, mat, state, tex1Name, tex2Name);
1181     if (p4 == nullptr)
1182         return;
1183 
1184     int nb = p4->vertices.size();
1185 
1186     if (mode == ENG_TEX_MAPPING_X)
1187     {
1188         for (int i = 0; i < nb; i++)
1189         {
1190             p4->vertices[i].texCoord.x = p4->vertices[i].coord.z * au + bu;
1191             p4->vertices[i].texCoord.y = p4->vertices[i].coord.y * av + bv;
1192         }
1193     }
1194     else if (mode == ENG_TEX_MAPPING_Y)
1195     {
1196         for (int i = 0; i < nb; i++)
1197         {
1198             p4->vertices[i].texCoord.x = p4->vertices[i].coord.x * au + bu;
1199             p4->vertices[i].texCoord.y = p4->vertices[i].coord.z * av + bv;
1200         }
1201     }
1202     else if (mode == ENG_TEX_MAPPING_Z)
1203     {
1204         for (int i = 0; i < nb; i++)
1205         {
1206             p4->vertices[i].texCoord.x = p4->vertices[i].coord.x * au + bu;
1207             p4->vertices[i].texCoord.y = p4->vertices[i].coord.y * av + bv;
1208         }
1209     }
1210     else if (mode == ENG_TEX_MAPPING_1X)
1211     {
1212         for (int i = 0; i < nb; i++)
1213         {
1214             p4->vertices[i].texCoord.x = p4->vertices[i].coord.x * au + bu;
1215         }
1216     }
1217     else if (mode == ENG_TEX_MAPPING_1Y)
1218     {
1219         for (int i = 0; i < nb; i++)
1220         {
1221             p4->vertices[i].texCoord.y = p4->vertices[i].coord.y * au + bu;
1222         }
1223     }
1224     else if (mode == ENG_TEX_MAPPING_1Z)
1225     {
1226         for (int i = 0; i < nb; i++)
1227         {
1228             p4->vertices[i].texCoord.x = p4->vertices[i].coord.z * au + bu;
1229         }
1230     }
1231 
1232     UpdateStaticBuffer(*p4);
1233 }
1234 
TrackTextureMapping(int objRank,const Material & mat,int state,const std::string & tex1Name,const std::string & tex2Name,EngineTextureMapping mode,float pos,float factor,float tl,float ts,float tt)1235 void CEngine::TrackTextureMapping(int objRank, const Material& mat, int state,
1236                                   const std::string& tex1Name, const std::string& tex2Name,
1237                                   EngineTextureMapping mode,
1238                                   float pos, float factor, float tl, float ts, float tt)
1239 {
1240     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
1241 
1242     EngineBaseObjDataTier* p4 = FindTriangles(objRank, mat, state, tex1Name, tex2Name);
1243     if (p4 == nullptr)
1244         return;
1245 
1246     int tNum = p4->vertices.size();
1247     if (tNum < 12 || tNum % 6 != 0)
1248         return;
1249 
1250     std::vector<Gfx::VertexTex2>& vs = p4->vertices;
1251 
1252     while (pos < 0.0f)
1253         pos += 1.0f;  // never negative!
1254 
1255     Math::Vector current;
1256 
1257     for (int i = 0; i < 6; i++)
1258     {
1259         for (int j = 0; j < 6; j++)
1260         {
1261             if (Math::IsEqual(vs[i].coord.x, vs[j+6].coord.x) &&
1262                 Math::IsEqual(vs[i].coord.y, vs[j+6].coord.y))
1263             {
1264                 current.x = vs[i].coord.x;  // position end link
1265                 current.y = vs[i].coord.y;
1266                 break;
1267             }
1268         }
1269     }
1270 
1271     float ps = 0.0f;  // start position on the periphery
1272     float pe = 0.0f;
1273     int is[6] = { 0 }, ie[6] = { 0 };
1274 
1275     int tBase = 0;
1276     for (int ti = 0; ti < tNum / 6; ti++)
1277     {
1278         int s = 0;
1279         int e = 0;
1280 
1281         for (int i = 0; i < 6; i++)
1282         {
1283             if (Math::IsEqual(vs[tBase + i].coord.x, current.x, 0.0001f) &&
1284                 Math::IsEqual(vs[tBase + i].coord.y, current.y, 0.0001f))
1285             {
1286                 ie[e++] = i;
1287             }
1288             else
1289             {
1290                 is[s++] = i;
1291             }
1292         }
1293         if (s == 3 && e == 3)
1294         {
1295             pe = ps + Math::Point(vs[tBase + is[0]].coord.x - vs[tBase + ie[0]].coord.x,
1296                                   vs[tBase + is[0]].coord.y - vs[tBase + ie[0]].coord.y).Length() / factor;  // end position on the periphery
1297 
1298             float pps = ps + pos;
1299             float ppe = pe + pos;
1300             int offset = static_cast<int>(pps);
1301             ppe -= offset;
1302             pps -= offset;
1303 
1304             for (int i = 0; i < 3; i++)
1305             {
1306                 vs[tBase + is[i]].texCoord.x = ((pps * tl) + ts) / tt;
1307                 vs[tBase + ie[i]].texCoord.x = ((ppe * tl) + ts) / tt;
1308             }
1309         }
1310 
1311         if (ti >= (tNum / 6) - 1)
1312             break;
1313 
1314         for (int i = 0; i < 6; i++)
1315         {
1316             if (!Math::IsEqual(vs[tBase + i+6].coord.x, current.x, 0.0001f) ||
1317                 !Math::IsEqual(vs[tBase + i+6].coord.y, current.y, 0.0001f))
1318             {
1319                 current.x = vs[tBase + i+6].coord.x;  // end next link
1320                 current.y = vs[tBase + i+6].coord.y;
1321                 break;
1322             }
1323         }
1324         ps = pe;  // following start position on the periphery
1325         tBase += 6;
1326     }
1327 
1328     UpdateStaticBuffer(*p4);
1329 }
1330 
1331 
CreateShadowSpot(int objRank)1332 void CEngine::CreateShadowSpot(int objRank)
1333 {
1334     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
1335 
1336     // Already allocated?
1337     if (m_objects[objRank].shadowRank != -1)
1338         return;
1339 
1340     int index = 0;
1341     for ( ; index < static_cast<int>( m_shadowSpots.size() ); index++)
1342     {
1343         if (! m_shadowSpots[index].used)
1344         {
1345             m_shadowSpots[index].LoadDefault();
1346             break;
1347         }
1348     }
1349 
1350     if (index == static_cast<int>( m_shadowSpots.size() ))
1351         m_shadowSpots.push_back(EngineShadow());
1352 
1353     m_shadowSpots[index].used = true;
1354     m_shadowSpots[index].objRank = objRank;
1355     m_shadowSpots[index].height = 0.0f;
1356 
1357     m_objects[objRank].shadowRank = index;
1358 }
1359 
DeleteShadowSpot(int objRank)1360 void CEngine::DeleteShadowSpot(int objRank)
1361 {
1362     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
1363 
1364     int shadowRank = m_objects[objRank].shadowRank;
1365     if (shadowRank == -1)
1366         return;
1367 
1368     assert(shadowRank >= 0 && shadowRank < static_cast<int>( m_shadowSpots.size() ));
1369 
1370     m_shadowSpots[shadowRank].used = false;
1371     m_shadowSpots[shadowRank].objRank = -1;
1372 
1373     m_objects[objRank].shadowRank = -1;
1374 }
1375 
SetObjectShadowSpotHide(int objRank,bool hide)1376 void CEngine::SetObjectShadowSpotHide(int objRank, bool hide)
1377 {
1378     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
1379 
1380     int shadowRank = m_objects[objRank].shadowRank;
1381     if (shadowRank == -1)
1382         return;
1383 
1384     assert(shadowRank >= 0 && shadowRank < static_cast<int>( m_shadowSpots.size() ));
1385 
1386     m_shadowSpots[shadowRank].hide = hide;
1387 }
1388 
SetObjectShadowSpotType(int objRank,EngineShadowType type)1389 void CEngine::SetObjectShadowSpotType(int objRank, EngineShadowType type)
1390 {
1391     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
1392 
1393     int shadowRank = m_objects[objRank].shadowRank;
1394     if (shadowRank == -1)
1395         return;
1396 
1397     assert(shadowRank >= 0 && shadowRank < static_cast<int>( m_shadowSpots.size() ));
1398 
1399     m_shadowSpots[shadowRank].type = type;
1400 }
1401 
SetObjectShadowSpotPos(int objRank,const Math::Vector & pos)1402 void CEngine::SetObjectShadowSpotPos(int objRank, const Math::Vector& pos)
1403 {
1404     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
1405 
1406     int shadowRank = m_objects[objRank].shadowRank;
1407     if (shadowRank == -1)
1408         return;
1409 
1410     assert(shadowRank >= 0 && shadowRank < static_cast<int>( m_shadowSpots.size() ));
1411 
1412     m_shadowSpots[shadowRank].pos = pos;
1413 }
1414 
SetObjectShadowSpotAngle(int objRank,float angle)1415 void CEngine::SetObjectShadowSpotAngle(int objRank, float angle)
1416 {
1417     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
1418 
1419     int shadowRank = m_objects[objRank].shadowRank;
1420     if (shadowRank == -1)
1421         return;
1422 
1423     assert(shadowRank >= 0 && shadowRank < static_cast<int>( m_shadowSpots.size() ));
1424 
1425     m_shadowSpots[shadowRank].angle = angle;
1426 }
1427 
SetObjectShadowSpotRadius(int objRank,float radius)1428 void CEngine::SetObjectShadowSpotRadius(int objRank, float radius)
1429 {
1430     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
1431 
1432     int shadowRank = m_objects[objRank].shadowRank;
1433     if (shadowRank == -1)
1434         return;
1435 
1436     assert(shadowRank >= 0 && shadowRank < static_cast<int>( m_shadowSpots.size() ));
1437 
1438     m_shadowSpots[shadowRank].radius = radius;
1439 }
1440 
SetObjectShadowSpotIntensity(int objRank,float intensity)1441 void CEngine::SetObjectShadowSpotIntensity(int objRank, float intensity)
1442 {
1443     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
1444 
1445     int shadowRank = m_objects[objRank].shadowRank;
1446     if (shadowRank == -1)
1447         return;
1448 
1449     assert(shadowRank >= 0 && shadowRank < static_cast<int>( m_shadowSpots.size() ));
1450 
1451     m_shadowSpots[shadowRank].intensity = intensity;
1452 }
1453 
SetObjectShadowSpotHeight(int objRank,float height)1454 void CEngine::SetObjectShadowSpotHeight(int objRank, float height)
1455 {
1456     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
1457 
1458     int shadowRank = m_objects[objRank].shadowRank;
1459     if (shadowRank == -1)
1460         return;
1461 
1462     assert(shadowRank >= 0 && shadowRank < static_cast<int>( m_shadowSpots.size() ));
1463 
1464     m_shadowSpots[shadowRank].height = height;
1465 }
1466 
GetHighlight(Math::Point & p1,Math::Point & p2)1467 bool CEngine::GetHighlight(Math::Point &p1, Math::Point &p2)
1468 {
1469     p1 = m_highlightP1;
1470     p2 = m_highlightP2;
1471     return m_highlight;
1472 }
1473 
SetHighlightRank(int * rankList)1474 void CEngine::SetHighlightRank(int *rankList)
1475 {
1476     int i = 0;
1477     while ( *rankList != -1 )
1478     {
1479         m_highlightRank[i++] = *rankList++;
1480     }
1481     m_highlightRank[i] = -1;  // terminator
1482 }
1483 
GetBBox2D(int objRank,Math::Point & min,Math::Point & max)1484 bool CEngine::GetBBox2D(int objRank, Math::Point &min, Math::Point &max)
1485 {
1486     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
1487 
1488     min.x =  1000000.0f;
1489     min.y =  1000000.0f;
1490     max.x = -1000000.0f;
1491     max.y = -1000000.0f;
1492 
1493     int baseObjRank = m_objects[objRank].baseObjRank;
1494     if (baseObjRank == -1)
1495         return false;
1496 
1497     assert(baseObjRank >= 0 && baseObjRank < static_cast<int>( m_baseObjects.size() ));
1498 
1499     EngineBaseObject& p1 = m_baseObjects[baseObjRank];
1500 
1501     for (int i = 0; i < 8; i++)
1502     {
1503         Math::Vector p;
1504 
1505         if ( i & (1<<0) )  p.x = p1.bboxMin.x;
1506         else               p.x = p1.bboxMax.x;
1507         if ( i & (1<<1) )  p.y = p1.bboxMin.y;
1508         else               p.y = p1.bboxMax.y;
1509         if ( i & (1<<2) )  p.z = p1.bboxMin.z;
1510         else               p.z = p1.bboxMax.z;
1511 
1512         Math::Vector pp;
1513         if (TransformPoint(pp, objRank, p))
1514         {
1515             if (pp.x < min.x) min.x = pp.x;
1516             if (pp.x > max.x) max.x = pp.x;
1517             if (pp.y < min.y) min.y = pp.y;
1518             if (pp.y > max.y) max.y = pp.y;
1519         }
1520     }
1521 
1522     if (min.x ==  1000000.0f ||
1523         min.y ==  1000000.0f ||
1524         max.x == -1000000.0f ||
1525         max.y == -1000000.0f)
1526         return false;
1527 
1528     return true;
1529 }
1530 
DeleteAllGroundSpots()1531 void CEngine::DeleteAllGroundSpots()
1532 {
1533     m_groundSpots.clear();
1534     m_firstGroundSpot = true;
1535 
1536     for (int s = 0; s < 16; s++)
1537     {
1538         CImage shadowImg(Math::IntPoint(256, 256));
1539         shadowImg.Fill(Gfx::IntColor(255, 255, 255, 255));
1540 
1541         std::stringstream str;
1542         str << "shadow" << std::setfill('0') << std::setw(2) << s << ".png";
1543         std::string texName = str.str();
1544 
1545         CreateOrUpdateTexture(texName, &shadowImg);
1546     }
1547 }
1548 
CreateGroundSpot()1549 int CEngine::CreateGroundSpot()
1550 {
1551     int index = 0;
1552     for ( ; index < static_cast<int>( m_groundSpots.size() ); index++)
1553     {
1554         if (! m_groundSpots[index].used)
1555         {
1556             m_groundSpots[index].LoadDefault();
1557             break;
1558         }
1559     }
1560 
1561     m_groundSpots.push_back(EngineGroundSpot());
1562 
1563     m_groundSpots[index].used = true;
1564     m_groundSpots[index].smooth = 1.0f;
1565 
1566     return index;
1567 }
1568 
DeleteGroundSpot(int rank)1569 void CEngine::DeleteGroundSpot(int rank)
1570 {
1571     assert(rank >= 0 && rank < static_cast<int>( m_groundSpots.size() ));
1572 
1573     m_groundSpots[rank].used = false;
1574     m_groundSpots[rank].pos = Math::Vector(0.0f, 0.0f, 0.0f);
1575 }
1576 
SetObjectGroundSpotPos(int rank,const Math::Vector & pos)1577 void CEngine::SetObjectGroundSpotPos(int rank, const Math::Vector& pos)
1578 {
1579     assert(rank >= 0 && rank < static_cast<int>( m_groundSpots.size() ));
1580 
1581     m_groundSpots[rank].pos = pos;
1582 }
1583 
SetObjectGroundSpotRadius(int rank,float radius)1584 void CEngine::SetObjectGroundSpotRadius(int rank, float radius)
1585 {
1586     assert(rank >= 0 && rank < static_cast<int>( m_groundSpots.size() ));
1587 
1588     m_groundSpots[rank].radius = radius;
1589 }
1590 
SetObjectGroundSpotColor(int rank,const Color & color)1591 void CEngine::SetObjectGroundSpotColor(int rank, const Color& color)
1592 {
1593     assert(rank >= 0 && rank < static_cast<int>( m_groundSpots.size() ));
1594 
1595     m_groundSpots[rank].color = color;
1596 }
1597 
SetObjectGroundSpotMinMax(int rank,float min,float max)1598 void CEngine::SetObjectGroundSpotMinMax(int rank, float min, float max)
1599 {
1600     assert(rank >= 0 && rank < static_cast<int>( m_groundSpots.size() ));
1601 
1602     m_groundSpots[rank].min = min;
1603     m_groundSpots[rank].max = max;
1604 }
1605 
SetObjectGroundSpotSmooth(int rank,float smooth)1606 void CEngine::SetObjectGroundSpotSmooth(int rank, float smooth)
1607 {
1608     assert(rank >= 0 && rank < static_cast<int>( m_groundSpots.size() ));
1609 
1610     m_groundSpots[rank].smooth = smooth;
1611 }
1612 
CreateGroundMark(Math::Vector pos,float radius,float delay1,float delay2,float delay3,int dx,int dy,char * table)1613 void CEngine::CreateGroundMark(Math::Vector pos, float radius,
1614                                    float delay1, float delay2, float delay3,
1615                                    int dx, int dy, char* table)
1616 {
1617     m_groundMark.LoadDefault();
1618 
1619     m_groundMark.draw      = true;
1620     m_groundMark.phase     = ENG_GR_MARK_PHASE_INC;
1621     m_groundMark.delay[0]  = delay1;
1622     m_groundMark.delay[1]  = delay2;
1623     m_groundMark.delay[2]  = delay3;
1624     m_groundMark.pos       = pos;
1625     m_groundMark.radius    = radius;
1626     m_groundMark.intensity = 0.0f;
1627     m_groundMark.dx        = dx;
1628     m_groundMark.dy        = dy;
1629     m_groundMark.table     = table;
1630 }
1631 
DeleteGroundMark(int rank)1632 void CEngine::DeleteGroundMark(int rank)
1633 {
1634     m_groundMark.LoadDefault();
1635 }
1636 
ComputeDistance()1637 void CEngine::ComputeDistance()
1638 {
1639     for (int i = 0; i < static_cast<int>( m_objects.size() ); i++)
1640     {
1641         if (! m_objects[i].used)
1642             continue;
1643 
1644         Math::Vector v;
1645         v.x = m_eyePt.x - m_objects[i].transform.Get(1, 4);
1646         v.y = m_eyePt.y - m_objects[i].transform.Get(2, 4);
1647         v.z = m_eyePt.z - m_objects[i].transform.Get(3, 4);
1648         m_objects[i].distance = v.Length();
1649     }
1650 }
1651 
UpdateGeometry()1652 void CEngine::UpdateGeometry()
1653 {
1654     if (! m_updateGeometry)
1655         return;
1656 
1657     for (int baseObjRank = 0; baseObjRank < static_cast<int>( m_baseObjects.size() ); baseObjRank++)
1658     {
1659         EngineBaseObject &p1 = m_baseObjects[baseObjRank];
1660         if (! p1.used)
1661             continue;
1662 
1663         p1.bboxMin.LoadZero();
1664         p1.bboxMax.LoadZero();
1665 
1666         for (int l2 = 0; l2 < static_cast<int>( p1.next.size() ); l2++)
1667         {
1668             EngineBaseObjTexTier& p2 = p1.next[l2];
1669 
1670             for (int l3 = 0; l3 < static_cast<int>( p2.next.size() ); l3++)
1671             {
1672                 EngineBaseObjDataTier& p3 = p2.next[l3];
1673 
1674                 for (int i = 0; i < static_cast<int>( p3.vertices.size() ); i++)
1675                 {
1676                         p1.bboxMin.x = Math::Min(p3.vertices[i].coord.x, p1.bboxMin.x);
1677                         p1.bboxMin.y = Math::Min(p3.vertices[i].coord.y, p1.bboxMin.y);
1678                         p1.bboxMin.z = Math::Min(p3.vertices[i].coord.z, p1.bboxMin.z);
1679                         p1.bboxMax.x = Math::Max(p3.vertices[i].coord.x, p1.bboxMax.x);
1680                         p1.bboxMax.y = Math::Max(p3.vertices[i].coord.y, p1.bboxMax.y);
1681                         p1.bboxMax.z = Math::Max(p3.vertices[i].coord.z, p1.bboxMax.z);
1682                 }
1683             }
1684         }
1685 
1686         p1.boundingSphere = Math::BoundingSphereForBox(p1.bboxMin, p1.bboxMax);
1687     }
1688 
1689     m_updateGeometry = false;
1690 }
1691 
UpdateStaticBuffer(EngineBaseObjDataTier & p4)1692 void CEngine::UpdateStaticBuffer(EngineBaseObjDataTier& p4)
1693 {
1694     PrimitiveType type;
1695     if (p4.type == ENG_TRIANGLE_TYPE_TRIANGLES)
1696         type = PRIMITIVE_TRIANGLES;
1697     else
1698         type = PRIMITIVE_TRIANGLE_STRIP;
1699 
1700     if (p4.staticBufferId == 0)
1701         p4.staticBufferId = m_device->CreateStaticBuffer(type, &p4.vertices[0], p4.vertices.size());
1702     else
1703         m_device->UpdateStaticBuffer(p4.staticBufferId, type, &p4.vertices[0], p4.vertices.size());
1704 
1705     p4.updateStaticBuffer = false;
1706 }
1707 
UpdateStaticBuffers()1708 void CEngine::UpdateStaticBuffers()
1709 {
1710     if (!m_updateStaticBuffers)
1711         return;
1712 
1713     m_updateStaticBuffers = false;
1714 
1715     for (int baseObjRank = 0; baseObjRank < static_cast<int>( m_baseObjects.size() ); baseObjRank++)
1716     {
1717         EngineBaseObject& p1 = m_baseObjects[baseObjRank];
1718         if (! p1.used)
1719             continue;
1720 
1721         for (int l2 = 0; l2 < static_cast<int>( p1.next.size() ); l2++)
1722         {
1723             EngineBaseObjTexTier& p2 = p1.next[l2];
1724 
1725             for (int l3 = 0; l3 < static_cast<int>( p2.next.size() ); l3++)
1726             {
1727                 EngineBaseObjDataTier& p3 = p2.next[l3];
1728 
1729                 if (! p3.updateStaticBuffer)
1730                         continue;
1731 
1732                 UpdateStaticBuffer(p3);
1733             }
1734         }
1735     }
1736 }
1737 
Update()1738 void CEngine::Update()
1739 {
1740     ComputeDistance();
1741     UpdateGeometry();
1742     UpdateStaticBuffers();
1743 }
1744 
DetectBBox(int objRank,Math::Point mouse)1745 bool CEngine::DetectBBox(int objRank, Math::Point mouse)
1746 {
1747     assert(objRank >= 0 && objRank < static_cast<int>(m_objects.size()));
1748 
1749     int baseObjRank = m_objects[objRank].baseObjRank;
1750     if (baseObjRank == -1)
1751         return false;
1752 
1753     assert(baseObjRank >= 0 && baseObjRank < static_cast<int>(m_baseObjects.size()));
1754 
1755     EngineBaseObject& p1 = m_baseObjects[baseObjRank];
1756 
1757     Math::Point min, max;
1758     min.x =  1000000.0f;
1759     min.y =  1000000.0f;
1760     max.x = -1000000.0f;
1761     max.y = -1000000.0f;
1762 
1763     for (int i = 0; i < 8; i++)
1764     {
1765         Math::Vector p;
1766 
1767         if ( i & (1<<0) )  p.x = p1.bboxMin.x;
1768         else               p.x = p1.bboxMax.x;
1769         if ( i & (1<<1) )  p.y = p1.bboxMin.y;
1770         else               p.y = p1.bboxMax.y;
1771         if ( i & (1<<2) )  p.z = p1.bboxMin.z;
1772         else               p.z = p1.bboxMax.z;
1773 
1774         Math::Vector pp;
1775         if ( TransformPoint(pp, objRank, p) )
1776         {
1777             if (pp.x < min.x) min.x = pp.x;
1778             if (pp.x > max.x) max.x = pp.x;
1779             if (pp.y < min.y) min.y = pp.y;
1780             if (pp.y > max.y) max.y = pp.y;
1781         }
1782     }
1783 
1784     return ( mouse.x >= min.x &&
1785              mouse.x <= max.x &&
1786              mouse.y >= min.y &&
1787              mouse.y <= max.y );
1788 }
1789 
DetectObject(Math::Point mouse,Math::Vector & targetPos,bool terrain)1790 int CEngine::DetectObject(Math::Point mouse, Math::Vector& targetPos, bool terrain)
1791 {
1792     float min = 1000000.0f;
1793     int nearest = -1;
1794     Math::Vector pos;
1795 
1796     for (int objRank = 0; objRank < static_cast<int>( m_objects.size() ); objRank++)
1797     {
1798         if (! m_objects[objRank].used)
1799             continue;
1800 
1801         if (m_objects[objRank].type == ENG_OBJTYPE_TERRAIN && !terrain)
1802             continue;
1803 
1804         if (! DetectBBox(objRank, mouse))
1805             continue;
1806 
1807         int baseObjRank = m_objects[objRank].baseObjRank;
1808         if (baseObjRank == -1)
1809             continue;
1810 
1811         assert(baseObjRank >= 0 && baseObjRank < static_cast<int>(m_baseObjects.size()));
1812 
1813         EngineBaseObject& p1 = m_baseObjects[baseObjRank];
1814         if (! p1.used)
1815             continue;
1816 
1817         for (int l2 = 0; l2 < static_cast<int>( p1.next.size() ); l2++)
1818         {
1819             EngineBaseObjTexTier& p2 = p1.next[l2];
1820 
1821             for (int l3 = 0; l3 < static_cast<int>( p2.next.size() ); l3++)
1822             {
1823                 EngineBaseObjDataTier& p3 = p2.next[l3];
1824 
1825                 if (p3.type == ENG_TRIANGLE_TYPE_TRIANGLES)
1826                 {
1827                     for (int i = 0; i < static_cast<int>( p3.vertices.size() ); i += 3)
1828                     {
1829                         float dist = 0.0f;
1830                         if (DetectTriangle(mouse, &p3.vertices[i], objRank, dist, pos) && dist < min)
1831                         {
1832                             min = dist;
1833                             nearest = objRank;
1834                             targetPos = pos;
1835                         }
1836                     }
1837                 }
1838                 else if (p3.type == ENG_TRIANGLE_TYPE_SURFACE)
1839                 {
1840                     for (int i = 0; i < static_cast<int>( p3.vertices.size() ) - 2; i += 1)
1841                     {
1842                         float dist = 0.0f;
1843                         if (DetectTriangle(mouse, &p3.vertices[i], objRank, dist, pos) && dist < min)
1844                         {
1845                             min = dist;
1846                             nearest = objRank;
1847                             targetPos = pos;
1848                         }
1849                     }
1850                 }
1851             }
1852         }
1853     }
1854 
1855     return nearest;
1856 }
1857 
DetectTriangle(Math::Point mouse,VertexTex2 * triangle,int objRank,float & dist,Math::Vector & pos)1858 bool CEngine::DetectTriangle(Math::Point mouse, VertexTex2* triangle, int objRank, float& dist, Math::Vector& pos)
1859 {
1860     assert(objRank >= 0 && objRank < static_cast<int>(m_objects.size()));
1861 
1862     Math::Vector p2D[3], p3D;
1863 
1864     for (int i = 0; i < 3; i++)
1865     {
1866         p3D.x = triangle[i].coord.x;
1867         p3D.y = triangle[i].coord.y;
1868         p3D.z = triangle[i].coord.z;
1869 
1870         if (! TransformPoint(p2D[i], objRank, p3D))
1871             return false;
1872     }
1873 
1874     if (mouse.x < p2D[0].x &&
1875         mouse.x < p2D[1].x &&
1876         mouse.x < p2D[2].x)
1877         return false;
1878 
1879     if (mouse.x > p2D[0].x &&
1880         mouse.x > p2D[1].x &&
1881         mouse.x > p2D[2].x)
1882         return false;
1883 
1884     if (mouse.y < p2D[0].y &&
1885         mouse.y < p2D[1].y &&
1886         mouse.y < p2D[2].y)
1887         return false;
1888 
1889     if (mouse.y > p2D[0].y &&
1890         mouse.y > p2D[1].y &&
1891         mouse.y > p2D[2].y)
1892         return false;
1893 
1894     Math::Point a, b, c;
1895     a.x = p2D[0].x;
1896     a.y = p2D[0].y;
1897     b.x = p2D[1].x;
1898     b.y = p2D[1].y;
1899     c.x = p2D[2].x;
1900     c.y = p2D[2].y;
1901 
1902     if (! Math::IsInsideTriangle(a, b, c, mouse))
1903         return false;
1904 
1905     Math::Vector a2 = Math::Transform(m_objects[objRank].transform, triangle[0].coord);
1906     Math::Vector b2 = Math::Transform(m_objects[objRank].transform, triangle[1].coord);
1907     Math::Vector c2 = Math::Transform(m_objects[objRank].transform, triangle[2].coord);
1908     Math::Vector e  = Math::Transform(m_matView.Inverse(), Math::Vector(0.0f, 0.0f, -1.0f));
1909     Math::Vector f  = Math::Transform(m_matView.Inverse(), Math::Vector(
1910         (mouse.x*2.0f-1.0f)*m_matProj.Inverse().Get(1,1),
1911         (mouse.y*2.0f-1.0f)*m_matProj.Inverse().Get(2,2),
1912         0.0f));
1913     Math::Intersect(a2, b2, c2, e, f, pos);
1914 
1915     dist = (p2D[0].z + p2D[1].z + p2D[2].z) / 3.0f;
1916     return true;
1917 }
1918 
1919 //! Use only after world transform already set
IsVisible(int objRank)1920 bool CEngine::IsVisible(int objRank)
1921 {
1922     assert(objRank >= 0 && objRank < static_cast<int>(m_objects.size()));
1923 
1924     int baseObjRank = m_objects[objRank].baseObjRank;
1925     if (baseObjRank == -1)
1926         return false;
1927 
1928     assert(baseObjRank >= 0 && baseObjRank < static_cast<int>(m_baseObjects.size()));
1929 
1930     const auto& sphere = m_baseObjects[baseObjRank].boundingSphere;
1931     if (m_device->ComputeSphereVisibility(sphere.pos, sphere.radius) == Gfx::FRUSTUM_PLANE_ALL)
1932     {
1933         m_objects[objRank].visible = true;
1934         return true;
1935     }
1936 
1937     m_objects[objRank].visible = false;
1938     return false;
1939 }
1940 
TransformPoint(Math::Vector & p2D,int objRank,Math::Vector p3D)1941 bool CEngine::TransformPoint(Math::Vector& p2D, int objRank, Math::Vector p3D)
1942 {
1943     assert(objRank >= 0 && objRank < static_cast<int>(m_objects.size()));
1944 
1945     p3D = Math::Transform(m_objects[objRank].transform, p3D);
1946     p3D = Math::Transform(m_matView, p3D);
1947 
1948     if (p3D.z < 2.0f)
1949         return false;  // behind?
1950 
1951     p2D.x = (p3D.x/p3D.z)*m_matProj.Get(1,1);
1952     p2D.y = (p3D.y/p3D.z)*m_matProj.Get(2,2);
1953     p2D.z = p3D.z;
1954 
1955     p2D.x = (p2D.x+1.0f)/2.0f;  // [-1..1] -> [0..1]
1956     p2D.y = (p2D.y+1.0f)/2.0f;
1957 
1958     return true;
1959 }
1960 
1961 
1962 
1963 /*******************************************************
1964                     Mode setting
1965  *******************************************************/
1966 
1967 
1968 
SetState(int state,const Color & color)1969 void CEngine::SetState(int state, const Color& color)
1970 {
1971     if (state == m_lastState && color == m_lastColor)
1972         return;
1973 
1974     m_lastState = state;
1975     m_lastColor = color;
1976 
1977     if (state & ENG_RSTATE_TTEXTURE_BLACK)  // transparent black texture?
1978     {
1979         m_device->SetRenderState(RENDER_STATE_FOG,         false);
1980         m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, false);
1981         m_device->SetRenderState(RENDER_STATE_ALPHA_TEST,  false);
1982 
1983         m_device->SetRenderState(RENDER_STATE_BLENDING,    true);
1984         m_device->SetBlendFunc(BLEND_ONE, BLEND_INV_SRC_COLOR);
1985 
1986         TextureStageParams params;
1987         params.colorOperation = TEX_MIX_OPER_MODULATE;
1988         params.colorArg1 = TEX_MIX_ARG_TEXTURE;
1989         params.colorArg2 = TEX_MIX_ARG_FACTOR;
1990         params.alphaOperation = TEX_MIX_OPER_DEFAULT;
1991         params.factor = color;
1992 
1993         m_device->SetTextureEnabled(0, true);
1994         m_device->SetTextureStageParams(0, params);
1995     }
1996     else if (state & ENG_RSTATE_TTEXTURE_WHITE)  // transparent white texture?
1997     {
1998         m_device->SetRenderState(RENDER_STATE_FOG,         false);
1999         m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, false);
2000         m_device->SetRenderState(RENDER_STATE_ALPHA_TEST,  false);
2001 
2002         m_device->SetRenderState(RENDER_STATE_BLENDING,    true);
2003         m_device->SetBlendFunc(BLEND_DST_COLOR, BLEND_ZERO);
2004 
2005         TextureStageParams params;
2006         params.colorOperation = TEX_MIX_OPER_ADD;
2007         params.colorArg1 = TEX_MIX_ARG_TEXTURE;
2008         params.colorArg2 = TEX_MIX_ARG_FACTOR;
2009         params.alphaOperation = TEX_MIX_OPER_DEFAULT;
2010         params.factor = color.Inverse();
2011 
2012         m_device->SetTextureEnabled(0, true);
2013         m_device->SetTextureStageParams(0, params);
2014     }
2015     else if (state & ENG_RSTATE_TCOLOR_BLACK)  // transparent black color?
2016     {
2017         m_device->SetRenderState(RENDER_STATE_FOG,         false);
2018         m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, false);
2019         m_device->SetRenderState(RENDER_STATE_ALPHA_TEST,  false);
2020 
2021         m_device->SetRenderState(RENDER_STATE_BLENDING,    true);
2022         m_device->SetBlendFunc(BLEND_ONE, BLEND_INV_SRC_COLOR);
2023 
2024         m_device->SetTextureEnabled(0, false);
2025     }
2026     else if (state & ENG_RSTATE_TCOLOR_WHITE)  // transparent white color?
2027     {
2028         m_device->SetRenderState(RENDER_STATE_FOG,         false);
2029         m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, false);
2030         m_device->SetRenderState(RENDER_STATE_ALPHA_TEST,  false);
2031 
2032         m_device->SetRenderState(RENDER_STATE_BLENDING,    true);
2033         m_device->SetBlendFunc(BLEND_DST_COLOR, BLEND_ZERO);
2034 
2035         m_device->SetTextureEnabled(0, false);
2036     }
2037     else if (state & ENG_RSTATE_TDIFFUSE)  // diffuse color as transparent?
2038     {
2039         m_device->SetRenderState(RENDER_STATE_FOG,         false);
2040         m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, false);
2041         m_device->SetRenderState(RENDER_STATE_ALPHA_TEST,  false);
2042 
2043         m_device->SetRenderState(RENDER_STATE_BLENDING,    true);
2044         m_device->SetBlendFunc(BLEND_SRC_ALPHA, BLEND_DST_ALPHA);
2045 
2046         TextureStageParams params;
2047         params.colorOperation = TEX_MIX_OPER_REPLACE;
2048         params.colorArg1 = TEX_MIX_ARG_TEXTURE;
2049         params.alphaOperation = TEX_MIX_OPER_DEFAULT;
2050 
2051         m_device->SetTextureEnabled(0, true);
2052         m_device->SetTextureStageParams(0, params);
2053     }
2054     else if (state & ENG_RSTATE_OPAQUE_TEXTURE) // opaque texture ?
2055     {
2056         m_device->SetRenderState(RENDER_STATE_FOG,         false);
2057         m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, false);
2058         m_device->SetRenderState(RENDER_STATE_ALPHA_TEST,  false);
2059         m_device->SetRenderState(RENDER_STATE_BLENDING,    false);
2060 
2061         m_device->SetTextureEnabled(0, true);
2062         m_device->SetTextureStageParams(0, TextureStageParams()); // default operation
2063     }
2064     else if (state & ENG_RSTATE_OPAQUE_COLOR) // opaque color ?
2065     {
2066         m_device->SetRenderState(RENDER_STATE_FOG,         false);
2067         m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, false);
2068         m_device->SetRenderState(RENDER_STATE_ALPHA_TEST,  false);
2069         m_device->SetRenderState(RENDER_STATE_BLENDING,    false);
2070 
2071         m_device->SetTextureEnabled(0, false);
2072     }
2073     else if (state & ENG_RSTATE_TEXT)  // font rendering?
2074     {
2075         m_device->SetRenderState(RENDER_STATE_FOG,         false);
2076         m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, false);
2077         m_device->SetRenderState(RENDER_STATE_ALPHA_TEST,  false);
2078 
2079         m_device->SetRenderState(RENDER_STATE_BLENDING,    true);
2080         m_device->SetBlendFunc(BLEND_SRC_ALPHA, BLEND_INV_SRC_ALPHA);
2081 
2082         m_device->SetTextureEnabled(0, true);
2083         m_device->SetTextureStageParams(0, TextureStageParams()); // default operation
2084     }
2085     else if (state & ENG_RSTATE_ALPHA)  // image with alpha channel?
2086     {
2087         m_device->SetRenderState(RENDER_STATE_BLENDING,    true);
2088         m_device->SetBlendFunc(BLEND_SRC_ALPHA, BLEND_INV_SRC_ALPHA);
2089 
2090         m_device->SetRenderState(RENDER_STATE_FOG,         true);
2091         m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, true);
2092 
2093         m_device->SetRenderState(RENDER_STATE_ALPHA_TEST,  true);
2094 
2095         m_device->SetAlphaTestFunc(COMP_FUNC_GREATER, 0.5f);
2096 
2097         TextureStageParams params;
2098         params.colorOperation = TEX_MIX_OPER_MODULATE;
2099         params.colorArg1 = TEX_MIX_ARG_TEXTURE;
2100         params.colorArg2 = TEX_MIX_ARG_SRC_COLOR;
2101         params.alphaOperation = TEX_MIX_OPER_REPLACE;
2102         params.alphaArg1 = TEX_MIX_ARG_TEXTURE;
2103         params.factor = color;
2104 
2105         m_device->SetTextureEnabled(0, true);
2106         m_device->SetTextureStageParams(0, params);
2107     }
2108     else if (state & ENG_RSTATE_TTEXTURE_ALPHA)  // texture with alpha channel?
2109     {
2110         m_device->SetRenderState(RENDER_STATE_FOG,         false);
2111         m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, false);
2112 
2113         m_device->SetRenderState(RENDER_STATE_ALPHA_TEST,  true);
2114         m_device->SetAlphaTestFunc(COMP_FUNC_GREATER, 0.0f);
2115 
2116         m_device->SetRenderState(RENDER_STATE_BLENDING,    true);
2117         m_device->SetBlendFunc(BLEND_DST_COLOR, BLEND_INV_SRC_ALPHA);
2118 
2119         TextureStageParams params;
2120         params.colorOperation = TEX_MIX_OPER_MODULATE;
2121         params.colorArg1 = TEX_MIX_ARG_TEXTURE;
2122         params.colorArg2 = TEX_MIX_ARG_SRC_COLOR;
2123         params.alphaOperation = TEX_MIX_OPER_MODULATE;
2124         params.alphaArg1 = TEX_MIX_ARG_TEXTURE;
2125         params.alphaArg2 = TEX_MIX_ARG_FACTOR;
2126         params.factor = color;
2127 
2128         m_device->SetTextureEnabled(0, true);
2129         m_device->SetTextureStageParams(0, params);
2130     }
2131     else if (state & ENG_RSTATE_TCOLOR_ALPHA)
2132     {
2133         m_device->SetRenderState(RENDER_STATE_FOG,         false);
2134         m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, false);
2135         m_device->SetRenderState(RENDER_STATE_ALPHA_TEST,  false);
2136 
2137         m_device->SetRenderState(RENDER_STATE_BLENDING,    true);
2138         m_device->SetBlendFunc(BLEND_DST_COLOR, BLEND_INV_SRC_ALPHA);
2139 
2140         m_device->SetTextureEnabled(0, false);
2141     }
2142     else    // normal ?
2143     {
2144         m_device->SetRenderState(RENDER_STATE_ALPHA_TEST,  false);
2145         m_device->SetRenderState(RENDER_STATE_BLENDING,    false);
2146 
2147         m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, true);
2148         m_device->SetRenderState(RENDER_STATE_FOG,         true);
2149 
2150         TextureStageParams params;
2151         params.colorOperation = TEX_MIX_OPER_DEFAULT; // default modulate
2152         params.alphaOperation = TEX_MIX_OPER_DEFAULT;
2153 
2154         m_device->SetTextureEnabled(0, true);
2155         m_device->SetTextureStageParams(0, params);
2156     }
2157 
2158     if (state & ENG_RSTATE_FOG)
2159         m_device->SetRenderState(RENDER_STATE_FOG, true);
2160 
2161 
2162     bool second = m_dirty;
2163 
2164     // TODO: I'm pretty sure this is reversed and should be m_dirty instead of !m_dirty ~krzys_h
2165     if ( !m_dirty && (state & ENG_RSTATE_SECOND) == 0 ) second = false;
2166 
2167     if ((state & ENG_RSTATE_DUAL_BLACK) && second)
2168     {
2169         TextureStageParams params;
2170         params.colorOperation = TEX_MIX_OPER_MODULATE;
2171         params.colorArg1 = TEX_MIX_ARG_TEXTURE;
2172         params.colorArg2 = TEX_MIX_ARG_COMPUTED_COLOR;
2173         params.alphaOperation = TEX_MIX_OPER_DEFAULT;
2174         m_device->SetTextureEnabled(1, true);
2175         m_device->SetTextureStageParams(1, params);
2176     }
2177     else if ((state & ENG_RSTATE_DUAL_WHITE) && second)
2178     {
2179         TextureStageParams params;
2180         params.colorOperation = TEX_MIX_OPER_ADD;
2181         params.colorArg1 = TEX_MIX_ARG_TEXTURE;
2182         params.colorArg2 = TEX_MIX_ARG_COMPUTED_COLOR;
2183         params.alphaOperation = TEX_MIX_OPER_DEFAULT;
2184         m_device->SetTextureEnabled(1, true);
2185         m_device->SetTextureStageParams(1, params);
2186     }
2187     else
2188     {
2189         m_device->SetTextureEnabled(1, false);
2190     }
2191 
2192     if (state & ENG_RSTATE_WRAP)
2193     {
2194         m_device->SetTextureStageWrap(0, TEX_WRAP_REPEAT, TEX_WRAP_REPEAT);
2195         m_device->SetTextureStageWrap(1, TEX_WRAP_REPEAT, TEX_WRAP_REPEAT);
2196     }
2197     else if (state & ENG_RSTATE_CLAMP)
2198     {
2199         m_device->SetTextureStageWrap(0, TEX_WRAP_CLAMP, TEX_WRAP_CLAMP);
2200         m_device->SetTextureStageWrap(1, TEX_WRAP_CLAMP, TEX_WRAP_CLAMP);
2201     }
2202 
2203     if (state & ENG_RSTATE_2FACE)
2204     {
2205         m_device->SetRenderState(RENDER_STATE_CULLING, false);
2206     }
2207     else
2208     {
2209         m_device->SetRenderState(RENDER_STATE_CULLING, true);
2210         m_device->SetCullMode(CULL_CCW);
2211     }
2212 
2213     if (state & ENG_RSTATE_LIGHT)
2214         m_device->SetGlobalAmbient(Color(1.0f, 1.0f, 1.0f, 1.0f));
2215     else
2216         m_device->SetGlobalAmbient(m_ambientColor[m_rankView]);
2217 
2218 
2219     // In interface mode, disable lighting
2220     if (m_interfaceMode)
2221         m_device->SetRenderState(RENDER_STATE_LIGHTING, false);
2222 }
2223 
SetMaterial(const Material & mat)2224 void CEngine::SetMaterial(const Material& mat)
2225 {
2226     m_lastMaterial = mat;
2227     m_device->SetMaterial(mat);
2228 }
2229 
SetViewParams(const Math::Vector & eyePt,const Math::Vector & lookatPt,const Math::Vector & upVec)2230 void CEngine::SetViewParams(const Math::Vector &eyePt, const Math::Vector &lookatPt, const Math::Vector &upVec)
2231 {
2232     m_eyePt = eyePt;
2233     m_lookatPt = lookatPt;
2234     m_eyeDirH = Math::RotateAngle(eyePt.x - lookatPt.x, eyePt.z - lookatPt.z);
2235     m_eyeDirV = Math::RotateAngle(Math::DistanceProjected(eyePt, lookatPt), eyePt.y - lookatPt.y);
2236 
2237     Math::LoadViewMatrix(m_matView, eyePt, lookatPt, upVec);
2238 
2239     if (m_sound == nullptr)
2240         m_sound = m_app->GetSound();
2241 
2242     if (m_sound != nullptr)
2243         m_sound->SetListener(eyePt, lookatPt);
2244 }
2245 
CreateTexture(const std::string & texName,const TextureCreateParams & params,CImage * image)2246 Texture CEngine::CreateTexture(const std::string& texName, const TextureCreateParams& params, CImage* image)
2247 {
2248     if (texName.empty())
2249         return Texture(); // invalid texture
2250 
2251     if (m_texBlacklist.find(texName) != m_texBlacklist.end())
2252         return Texture(); // invalid texture
2253 
2254     Texture tex;
2255     CImage img;
2256 
2257     if (image == nullptr)
2258     {
2259         if (!img.Load(texName))
2260         {
2261             std::string error = img.GetError();
2262             GetLogger()->Error("Couldn't load texture '%s': %s, blacklisting\n", texName.c_str(), error.c_str());
2263             m_texBlacklist.insert(texName);
2264             return Texture(); // invalid texture
2265         }
2266 
2267         image = &img;
2268     }
2269 
2270     tex = m_device->CreateTexture(image, params);
2271 
2272     if (! tex.Valid())
2273     {
2274         GetLogger()->Error("Couldn't load texture '%s', blacklisting\n", texName.c_str());
2275         m_texBlacklist.insert(texName);
2276         return tex;
2277     }
2278 
2279     m_texNameMap[texName] = tex;
2280     m_revTexNameMap[tex] = texName;
2281 
2282     return tex;
2283 }
2284 
LoadTexture(const std::string & name)2285 Texture CEngine::LoadTexture(const std::string& name)
2286 {
2287     return LoadTexture(name, m_defaultTexParams);
2288 }
2289 
LoadTexture(const std::string & name,CImage * image)2290 Texture CEngine::LoadTexture(const std::string& name, CImage* image)
2291 {
2292     Texture tex = CreateTexture(name, m_defaultTexParams, image);
2293     return tex;
2294 }
2295 
LoadTexture(const std::string & name,const TextureCreateParams & params)2296 Texture CEngine::LoadTexture(const std::string& name, const TextureCreateParams& params)
2297 {
2298     if (m_texBlacklist.find(name) != m_texBlacklist.end())
2299         return Texture();
2300 
2301     std::map<std::string, Texture>::iterator it = m_texNameMap.find(name);
2302     if (it != m_texNameMap.end())
2303         return (*it).second;
2304 
2305     return CreateTexture(name, params);
2306 }
2307 
LoadAllTextures()2308 bool CEngine::LoadAllTextures()
2309 {
2310     m_miceTexture = LoadTexture("textures/interface/mouse.png");
2311     LoadTexture("textures/interface/button1.png");
2312     LoadTexture("textures/interface/button2.png");
2313     LoadTexture("textures/interface/button3.png");
2314     LoadTexture("textures/interface/button4.png");
2315     LoadTexture("textures/effect00.png");
2316     LoadTexture("textures/effect01.png");
2317     LoadTexture("textures/effect02.png");
2318     LoadTexture("textures/effect03.png");
2319 
2320     if (! m_backgroundName.empty())
2321     {
2322         TextureCreateParams params = m_defaultTexParams;
2323         params.padToNearestPowerOfTwo = true;
2324         m_backgroundTex = LoadTexture(m_backgroundName, params);
2325     }
2326     else
2327         m_backgroundTex.SetInvalid();
2328 
2329     if (! m_foregroundName.empty())
2330         m_foregroundTex = LoadTexture(m_foregroundName);
2331     else
2332         m_foregroundTex.SetInvalid();
2333 
2334     m_planet->LoadTexture();
2335 
2336     bool ok = true;
2337 
2338     for (int objRank = 0; objRank < static_cast<int>( m_objects.size() ); objRank++)
2339     {
2340         if (! m_objects[objRank].used)
2341             continue;
2342 
2343         bool terrain = false;
2344         if (m_objects[objRank].type == ENG_OBJTYPE_TERRAIN)
2345             terrain = true;
2346 
2347         int baseObjRank = m_objects[objRank].baseObjRank;
2348         if (baseObjRank == -1)
2349             continue;
2350 
2351         assert(baseObjRank >= 0 && baseObjRank < static_cast<int>( m_baseObjects.size() ));
2352 
2353         EngineBaseObject& p1 = m_baseObjects[baseObjRank];
2354         if (! p1.used)
2355             continue;
2356 
2357         for (int l2 = 0; l2 < static_cast<int>( p1.next.size() ); l2++)
2358         {
2359             EngineBaseObjTexTier& p2 = p1.next[l2];
2360 
2361             if (! p2.tex1Name.empty())
2362             {
2363                 if (terrain)
2364                     p2.tex1 = LoadTexture("textures/"+p2.tex1Name, m_terrainTexParams);
2365                 else
2366                     p2.tex1 = LoadTexture("textures/"+p2.tex1Name);
2367 
2368                 if (! p2.tex1.Valid())
2369                     ok = false;
2370             }
2371 
2372             if (! p2.tex2Name.empty())
2373             {
2374                 if (terrain)
2375                     p2.tex2 = LoadTexture("textures/"+p2.tex2Name, m_terrainTexParams);
2376                 else
2377                     p2.tex2 = LoadTexture("textures/"+p2.tex2Name);
2378 
2379                 if (! p2.tex2.Valid())
2380                     ok = false;
2381             }
2382         }
2383     }
2384 
2385     return ok;
2386 }
2387 
IsExcludeColor(Math::Point * exclude,int x,int y)2388 static bool IsExcludeColor(Math::Point *exclude, int x, int y)
2389 {
2390     int i = 0;
2391     while ( exclude[i+0].x != 0.0f || exclude[i+0].y != 0.0f ||
2392             exclude[i+1].y != 0.0f || exclude[i+1].y != 0.0f )
2393     {
2394         if ( x >= static_cast<int>(exclude[i+0].x*256.0f) &&
2395              x <  static_cast<int>(exclude[i+1].x*256.0f) &&
2396              y >= static_cast<int>(exclude[i+0].y*256.0f) &&
2397              y <  static_cast<int>(exclude[i+1].y*256.0f) )
2398             return true;  // exclude
2399 
2400         i += 2;
2401     }
2402 
2403     return false;  // point to include
2404 }
2405 
2406 
ChangeTextureColor(const std::string & texName,const std::string & srcName,Color colorRef1,Color colorNew1,Color colorRef2,Color colorNew2,float tolerance1,float tolerance2,Math::Point ts,Math::Point ti,Math::Point * exclude,float shift,bool hsv)2407 bool CEngine::ChangeTextureColor(const std::string& texName,
2408                                  const std::string& srcName,
2409                                  Color colorRef1, Color colorNew1,
2410                                  Color colorRef2, Color colorNew2,
2411                                  float tolerance1, float tolerance2,
2412                                  Math::Point ts, Math::Point ti,
2413                                  Math::Point *exclude, float shift, bool hsv)
2414 {
2415     CImage img;
2416     if (!img.Load(srcName))
2417     {
2418         std::string error = img.GetError();
2419         GetLogger()->Error("Couldn't load texture '%s': %s, blacklisting\n", srcName.c_str(), error.c_str());
2420         m_texBlacklist.insert(srcName);
2421         return false;
2422     }
2423 
2424     bool changeColorsNeeded = true;
2425 
2426     if (colorRef1.r == colorNew1.r &&
2427         colorRef1.g == colorNew1.g &&
2428         colorRef1.b == colorNew1.b &&
2429         colorRef2.r == colorNew2.r &&
2430         colorRef2.g == colorNew2.g &&
2431         colorRef2.b == colorNew2.b)
2432     {
2433         changeColorsNeeded = false;
2434     }
2435 
2436 
2437     int dx = img.GetSize().x;
2438     int dy = img.GetSize().y;
2439 
2440     int sx = static_cast<int>(Math::Max(ts.x*dx, 0));
2441     int sy = static_cast<int>(Math::Max(ts.y*dy, 0));
2442 
2443     int ex = static_cast<int>(Math::Min(ti.x*dx, dx));
2444     int ey = static_cast<int>(Math::Min(ti.y*dy, dy));
2445 
2446 
2447     ColorHSV cr1 = RGB2HSV(colorRef1);
2448     ColorHSV cn1 = RGB2HSV(colorNew1);
2449     ColorHSV cr2 = RGB2HSV(colorRef2);
2450     ColorHSV cn2 = RGB2HSV(colorNew2);
2451 
2452     if (changeColorsNeeded)
2453     {
2454         for (int y = sy; y < ey; y++)
2455         {
2456             for (int x = sx; x < ex; x++)
2457             {
2458                 if (exclude != nullptr && IsExcludeColor(exclude, x,y) )
2459                     continue;
2460 
2461                 Color color = img.GetPixel(Math::IntPoint(x, y));
2462 
2463                 if (hsv)
2464                 {
2465                     ColorHSV c = RGB2HSV(color);
2466                     if (c.s > 0.01f && fabs(c.h - cr1.h) < tolerance1)
2467                     {
2468                         c.h += cn1.h - cr1.h;
2469                         c.s += cn1.s - cr1.s;
2470                         c.v += cn1.v - cr1.v;
2471                         if (c.h < 0.0f) c.h -= 1.0f;
2472                         if (c.h > 1.0f) c.h += 1.0f;
2473                         color = HSV2RGB(c);
2474                         color.r = Math::Norm(color.r + shift);
2475                         color.g = Math::Norm(color.g + shift);
2476                         color.b = Math::Norm(color.b + shift);
2477                         img.SetPixel(Math::IntPoint(x, y), color);
2478                     }
2479                     else if (tolerance2 != -1.0f &&
2480                             c.s > 0.01f && fabs(c.h - cr2.h) < tolerance2)
2481                     {
2482                         c.h += cn2.h - cr2.h;
2483                         c.s += cn2.s - cr2.s;
2484                         c.v += cn2.v - cr2.v;
2485                         if (c.h < 0.0f) c.h -= 1.0f;
2486                         if (c.h > 1.0f) c.h += 1.0f;
2487                         color = HSV2RGB(c);
2488                         color.r = Math::Norm(color.r + shift);
2489                         color.g = Math::Norm(color.g + shift);
2490                         color.b = Math::Norm(color.b + shift);
2491                         img.SetPixel(Math::IntPoint(x, y), color);
2492                     }
2493                 }
2494                 else
2495                 {
2496                     if ( fabs(color.r - colorRef1.r) +
2497                         fabs(color.g - colorRef1.g) +
2498                         fabs(color.b - colorRef1.b) < tolerance1 * 3.0f)
2499                     {
2500                         color.r = Math::Norm(colorNew1.r + color.r - colorRef1.r + shift);
2501                         color.g = Math::Norm(colorNew1.g + color.g - colorRef1.g + shift);
2502                         color.b = Math::Norm(colorNew1.b + color.b - colorRef1.b + shift);
2503                         img.SetPixel(Math::IntPoint(x, y), color);
2504                     }
2505                     else if (tolerance2 != -1 &&
2506                             fabs(color.r - colorRef2.r) +
2507                             fabs(color.g - colorRef2.g) +
2508                             fabs(color.b - colorRef2.b) < tolerance2 * 3.0f)
2509                     {
2510                         color.r = Math::Norm(colorNew2.r + color.r - colorRef2.r + shift);
2511                         color.g = Math::Norm(colorNew2.g + color.g - colorRef2.g + shift);
2512                         color.b = Math::Norm(colorNew2.b + color.b - colorRef2.b + shift);
2513                         img.SetPixel(Math::IntPoint(x, y), color);
2514                     }
2515                 }
2516             }
2517         }
2518     }
2519 
2520     CreateOrUpdateTexture(texName, &img);
2521 
2522     return true;
2523 }
2524 
ChangeTextureColor(const std::string & texName,Color colorRef1,Color colorNew1,Color colorRef2,Color colorNew2,float tolerance1,float tolerance2,Math::Point ts,Math::Point ti,Math::Point * exclude,float shift,bool hsv)2525 bool CEngine::ChangeTextureColor(const std::string& texName,
2526                                  Color colorRef1, Color colorNew1,
2527                                  Color colorRef2, Color colorNew2,
2528                                  float tolerance1, float tolerance2,
2529                                  Math::Point ts, Math::Point ti,
2530                                  Math::Point *exclude, float shift, bool hsv)
2531 {
2532     return ChangeTextureColor(texName, texName, colorRef1, colorNew1, colorRef2, colorNew2, tolerance1, tolerance2, ts, ti, exclude, shift, hsv);
2533 }
2534 
DeleteTexture(const std::string & texName)2535 void CEngine::DeleteTexture(const std::string& texName)
2536 {
2537     auto it = m_texNameMap.find(texName);
2538     if (it == m_texNameMap.end())
2539         return;
2540 
2541     auto revIt = m_revTexNameMap.find((*it).second);
2542 
2543     m_device->DestroyTexture((*it).second);
2544 
2545     m_revTexNameMap.erase(revIt);
2546     m_texNameMap.erase(it);
2547 }
2548 
DeleteTexture(const Texture & tex)2549 void CEngine::DeleteTexture(const Texture& tex)
2550 {
2551     if (! tex.Valid())
2552         return;
2553 
2554     auto revIt = m_revTexNameMap.find(tex);
2555     if (revIt == m_revTexNameMap.end())
2556         return;
2557 
2558     m_device->DestroyTexture(tex);
2559 
2560     auto it = m_texNameMap.find((*revIt).second);
2561 
2562     m_revTexNameMap.erase(revIt);
2563     m_texNameMap.erase(it);
2564 }
2565 
CreateOrUpdateTexture(const std::string & texName,CImage * img)2566 void CEngine::CreateOrUpdateTexture(const std::string& texName, CImage* img)
2567 {
2568     auto it = m_texNameMap.find(texName);
2569     if (it == m_texNameMap.end())
2570     {
2571         LoadTexture(texName, img);
2572     }
2573     else
2574     {
2575         m_device->UpdateTexture((*it).second, Math::IntPoint(0, 0), img->GetData(), m_defaultTexParams.format);
2576     }
2577 }
2578 
FlushTextureCache()2579 void CEngine::FlushTextureCache()
2580 {
2581     m_device->DestroyAllTextures();
2582 
2583     m_backgroundTex.SetInvalid();
2584     m_foregroundTex.SetInvalid();
2585 
2586     m_texNameMap.clear();
2587     m_revTexNameMap.clear();
2588     m_texBlacklist.clear();
2589 
2590     m_firstGroundSpot = true;
2591 }
2592 
SetTexture(const std::string & name,int stage)2593 bool CEngine::SetTexture(const std::string& name, int stage)
2594 {
2595     auto it = m_texNameMap.find(name);
2596     if (it != m_texNameMap.end())
2597     {
2598         m_device->SetTexture(stage, (*it).second);
2599         return true;
2600     }
2601 
2602     if (! LoadTexture(name).Valid())
2603     {
2604         m_device->SetTexture(stage, 0); // invalid texture
2605         return false;
2606     }
2607 
2608     it = m_texNameMap.find(name);
2609     if (it != m_texNameMap.end())
2610     {
2611         m_device->SetTexture(stage, (*it).second);
2612         return true;
2613     }
2614 
2615     m_device->SetTexture(stage, 0); // invalid texture
2616     return false; // should not happen normally
2617 }
2618 
SetTexture(const Texture & tex,int stage)2619 void CEngine::SetTexture(const Texture& tex, int stage)
2620 {
2621     m_device->SetTexture(stage, tex);
2622 }
2623 
SetTerrainVision(float vision)2624 void CEngine::SetTerrainVision(float vision)
2625 {
2626     m_terrainVision = vision;
2627 }
2628 
SetFocus(float focus)2629 void CEngine::SetFocus(float focus)
2630 {
2631     m_focus = focus;
2632     m_size = m_app->GetVideoConfig().size;
2633 
2634     float farPlane = m_deepView[0] * m_clippingDistance;
2635 
2636     float aspect = static_cast<float>(m_size.x) / static_cast<float>(m_size.y);
2637 
2638     // Compute H-FoV from V-FoV and aspect ratio.
2639     m_hfov = 2.0f * atan(aspect * tan(focus / 2.0f));
2640 
2641     Math::LoadProjectionMatrix(m_matProj, m_focus, aspect, 0.5f, farPlane);
2642 }
2643 
GetFocus()2644 float CEngine::GetFocus()
2645 {
2646     return m_focus;
2647 }
2648 
GetVFovAngle()2649 float CEngine::GetVFovAngle()
2650 {
2651     return m_focus;
2652 }
2653 
GetHFovAngle()2654 float CEngine::GetHFovAngle()
2655 {
2656     return m_hfov;
2657 }
2658 
SetShadowColor(float value)2659 void CEngine::SetShadowColor(float value)
2660 {
2661     m_shadowColor = value;
2662 }
2663 
GetShadowColor()2664 float CEngine::GetShadowColor()
2665 {
2666     return m_shadowColor;
2667 }
2668 
SetShadowRange(float value)2669 void CEngine::SetShadowRange(float value)
2670 {
2671     m_shadowRange = value;
2672 }
2673 
GetShadowRange()2674 float CEngine::GetShadowRange()
2675 {
2676     return m_shadowRange;
2677 }
2678 
SetMultiSample(int value)2679 void CEngine::SetMultiSample(int value)
2680 {
2681     if(value == m_multisample) return;
2682     m_multisample = value;
2683     m_device->DeleteFramebuffer("multisample");
2684 }
2685 
GetMultiSample()2686 int CEngine::GetMultiSample()
2687 {
2688     return m_multisample;
2689 }
2690 
SetDirty(bool mode)2691 void CEngine::SetDirty(bool mode)
2692 {
2693     m_dirty = mode;
2694 }
2695 
GetDirty()2696 bool CEngine::GetDirty()
2697 {
2698     return m_dirty;
2699 }
2700 
SetFog(bool mode)2701 void CEngine::SetFog(bool mode)
2702 {
2703     m_fog = mode;
2704 }
2705 
GetFog()2706 bool CEngine::GetFog()
2707 {
2708     return m_fog;
2709 }
2710 
SetSecondTexture(const std::string & texNum)2711 void CEngine::SetSecondTexture(const std::string& texNum)
2712 {
2713     m_secondTex = texNum;
2714 }
2715 
GetSecondTexture()2716 const std::string& CEngine::GetSecondTexture()
2717 {
2718     return m_secondTex;
2719 }
2720 
SetRankView(int rank)2721 void CEngine::SetRankView(int rank)
2722 {
2723     if (rank < 0) rank = 0;
2724     if (rank > 1) rank = 1;
2725 
2726     if (m_rankView == 0 && rank == 1)  // enters the water?
2727         m_lightMan->AdaptLightColor(m_waterAddColor, +1.0f);
2728 
2729     if (m_rankView == 1 && rank == 0)  // out of the water?
2730         m_lightMan->AdaptLightColor(m_waterAddColor, -1.0f);
2731 
2732     m_rankView = rank;
2733 }
2734 
GetRankView()2735 int CEngine::GetRankView()
2736 {
2737     return m_rankView;
2738 }
2739 
SetDrawWorld(bool draw)2740 void CEngine::SetDrawWorld(bool draw)
2741 {
2742     m_drawWorld = draw;
2743 }
2744 
SetDrawFront(bool draw)2745 void CEngine::SetDrawFront(bool draw)
2746 {
2747     m_drawFront = draw;
2748 }
2749 
SetAmbientColor(const Color & color,int rank)2750 void CEngine::SetAmbientColor(const Color& color, int rank)
2751 {
2752     m_ambientColor[rank] = color;
2753 }
2754 
GetAmbientColor(int rank)2755 Color CEngine::GetAmbientColor(int rank)
2756 {
2757     return m_ambientColor[rank];
2758 }
2759 
SetWaterAddColor(const Color & color)2760 void CEngine::SetWaterAddColor(const Color& color)
2761 {
2762     m_waterAddColor = color;
2763 }
2764 
GetWaterAddColor()2765 Color CEngine::GetWaterAddColor()
2766 {
2767     return m_waterAddColor;
2768 }
2769 
SetFogColor(const Color & color,int rank)2770 void CEngine::SetFogColor(const Color& color, int rank)
2771 {
2772     m_fogColor[rank] = color;
2773 }
2774 
GetFogColor(int rank)2775 Color CEngine::GetFogColor(int rank)
2776 {
2777     return m_fogColor[rank];
2778 }
2779 
SetDeepView(float length,int rank,bool ref)2780 void CEngine::SetDeepView(float length, int rank, bool ref)
2781 {
2782     if (ref)
2783         length *= m_clippingDistance;
2784 
2785     m_deepView[rank] = length;
2786 }
2787 
GetDeepView(int rank)2788 float CEngine::GetDeepView(int rank)
2789 {
2790     return m_deepView[rank];
2791 }
2792 
SetFogStart(float start,int rank)2793 void CEngine::SetFogStart(float start, int rank)
2794 {
2795     if (start < 0.0f)
2796         m_fogStart[rank] = 0.0f;
2797     else
2798         m_fogStart[rank] = start;
2799 }
2800 
GetFogStart(int rank)2801 float CEngine::GetFogStart(int rank)
2802 {
2803     return m_fogStart[rank];
2804 }
2805 
SetBackground(const std::string & name,Color up,Color down,Color cloudUp,Color cloudDown,bool full,bool scale)2806 void CEngine::SetBackground(const std::string& name, Color up, Color down,
2807                             Color cloudUp, Color cloudDown, bool full, bool scale)
2808 {
2809     if (m_backgroundTex.Valid() && name != m_backgroundName)
2810     {
2811         DeleteTexture(m_backgroundTex);
2812         m_backgroundTex.SetInvalid();
2813     }
2814 
2815     m_backgroundName      = name;
2816     m_backgroundColorUp   = up;
2817     m_backgroundColorDown = down;
2818     m_backgroundCloudUp   = cloudUp;
2819     m_backgroundCloudDown = cloudDown;
2820     m_backgroundFull      = full;
2821     m_backgroundScale     = scale;
2822 
2823     if (! m_backgroundName.empty() && !m_backgroundTex.Valid())
2824     {
2825         TextureCreateParams params = m_defaultTexParams;
2826         params.padToNearestPowerOfTwo = true;
2827         m_backgroundTex = LoadTexture(m_backgroundName, params);
2828     }
2829 }
2830 
GetBackground(std::string & name,Color & up,Color & down,Color & cloudUp,Color & cloudDown,bool & full,bool & scale)2831 void CEngine::GetBackground(std::string& name, Color& up, Color& down,
2832                             Color& cloudUp, Color& cloudDown, bool &full, bool &scale)
2833 {
2834     name      = m_backgroundName;
2835     up        = m_backgroundColorUp;
2836     down      = m_backgroundColorDown;
2837     cloudUp   = m_backgroundCloudUp;
2838     cloudDown = m_backgroundCloudDown;
2839     full      = m_backgroundFull;
2840     scale     = m_backgroundScale;
2841 }
2842 
SetForegroundName(const std::string & name)2843 void CEngine::SetForegroundName(const std::string& name)
2844 {
2845     if (m_foregroundTex.Valid() && name != m_foregroundName)
2846     {
2847         DeleteTexture(m_foregroundTex);
2848         m_foregroundTex.SetInvalid();
2849     }
2850 
2851     m_foregroundName = name;
2852 
2853     if (! m_foregroundName.empty() && !m_foregroundTex.Valid())
2854         m_foregroundTex = LoadTexture(m_foregroundName);
2855 }
2856 
SetOverFront(bool front)2857 void CEngine::SetOverFront(bool front)
2858 {
2859     m_overFront = front;
2860 }
2861 
SetOverColor(const Color & color,int mode)2862 void CEngine::SetOverColor(const Color& color, int mode)
2863 {
2864     m_overColor = color;
2865     m_overMode  = mode;
2866 }
2867 
SetParticleDensity(float value)2868 void CEngine::SetParticleDensity(float value)
2869 {
2870     if (value < 0.0f) value = 0.0f;
2871     if (value > 2.0f) value = 2.0f;
2872     m_particleDensity = value;
2873 }
2874 
GetParticleDensity()2875 float CEngine::GetParticleDensity()
2876 {
2877     return m_particleDensity;
2878 }
2879 
ParticleAdapt(float factor)2880 float CEngine::ParticleAdapt(float factor)
2881 {
2882     if (m_particleDensity == 0.0f)
2883         return 1000000.0f;
2884 
2885     return factor / m_particleDensity;
2886 }
2887 
SetClippingDistance(float value)2888 void CEngine::SetClippingDistance(float value)
2889 {
2890     if (value < 0.5f) value = 0.5f;
2891     if (value > 2.0f) value = 2.0f;
2892     m_clippingDistance = value;
2893 }
2894 
GetClippingDistance()2895 float CEngine::GetClippingDistance()
2896 {
2897     return m_clippingDistance;
2898 }
2899 
SetTextureFilterMode(TexFilter value)2900 void CEngine::SetTextureFilterMode(TexFilter value)
2901 {
2902     if(m_defaultTexParams.filter == value && m_terrainTexParams.filter == value) return;
2903 
2904     m_defaultTexParams.filter = m_terrainTexParams.filter = value;
2905     m_defaultTexParams.mipmap = m_terrainTexParams.mipmap = (value == TEX_FILTER_TRILINEAR);
2906     ReloadAllTextures();
2907 }
2908 
GetTextureFilterMode()2909 TexFilter CEngine::GetTextureFilterMode()
2910 {
2911     return m_terrainTexParams.filter;
2912 }
2913 
SetTextureMipmapLevel(int value)2914 void CEngine::SetTextureMipmapLevel(int value)
2915 {
2916     if (value < 1) value = 1;
2917     if (value > 16) value = 16;
2918     if(m_textureMipmapLevel == value) return;
2919 
2920     m_textureMipmapLevel = value;
2921     ReloadAllTextures();
2922 }
2923 
GetTextureMipmapLevel()2924 int CEngine::GetTextureMipmapLevel()
2925 {
2926     return m_textureMipmapLevel;
2927 }
2928 
SetTextureAnisotropyLevel(int value)2929 void CEngine::SetTextureAnisotropyLevel(int value)
2930 {
2931     if (value < 1) value = 1;
2932     if (value > 16) value = 16;
2933 
2934     if(m_textureAnisotropy == value) return;
2935 
2936     m_textureAnisotropy = value;
2937     ReloadAllTextures();
2938 }
2939 
GetTextureAnisotropyLevel()2940 int CEngine::GetTextureAnisotropyLevel()
2941 {
2942     return m_textureAnisotropy;
2943 }
2944 
IsShadowMappingSupported()2945 bool CEngine::IsShadowMappingSupported()
2946 {
2947     return m_device->IsShadowMappingSupported() && m_device->GetMaxTextureStageCount() >= 3;
2948 }
2949 
SetShadowMapping(bool value)2950 void CEngine::SetShadowMapping(bool value)
2951 {
2952     if(!IsShadowMappingSupported()) value = false;
2953     if(value == m_shadowMapping) return;
2954     m_shadowMapping = value;
2955     if(!value)
2956     {
2957         m_device->DeleteFramebuffer("shadow");
2958         m_device->DestroyTexture(m_shadowMap);
2959         m_shadowMap.id = 0;
2960     }
2961 }
2962 
GetShadowMapping()2963 bool CEngine::GetShadowMapping()
2964 {
2965     return m_shadowMapping;
2966 }
2967 
SetShadowMappingOffscreen(bool value)2968 void CEngine::SetShadowMappingOffscreen(bool value)
2969 {
2970     if(!m_device->IsFramebufferSupported()) value = false;
2971     if(value == m_offscreenShadowRendering) return;
2972     m_offscreenShadowRendering = value;
2973     if(value)
2974     {
2975         m_device->DestroyTexture(m_shadowMap);
2976         m_shadowMap.id = 0;
2977     }
2978     else
2979     {
2980         m_device->DeleteFramebuffer("shadow");
2981         m_shadowMap.id = 0;
2982     }
2983 }
2984 
GetShadowMappingOffscreen()2985 bool CEngine::GetShadowMappingOffscreen()
2986 {
2987     return m_offscreenShadowRendering;
2988 }
2989 
SetShadowMappingOffscreenResolution(int resolution)2990 void CEngine::SetShadowMappingOffscreenResolution(int resolution)
2991 {
2992     resolution = Math::Min(resolution, m_device->GetMaxTextureSize());
2993     if(resolution == m_offscreenShadowRenderingResolution) return;
2994     m_offscreenShadowRenderingResolution = resolution;
2995     m_device->DeleteFramebuffer("shadow");
2996     m_shadowMap.id = 0;
2997 }
2998 
GetShadowMappingOffscreenResolution()2999 int CEngine::GetShadowMappingOffscreenResolution()
3000 {
3001     return m_offscreenShadowRenderingResolution;
3002 }
3003 
IsShadowMappingQualitySupported()3004 bool CEngine::IsShadowMappingQualitySupported()
3005 {
3006     return IsShadowMappingSupported() && m_device->GetMaxTextureStageCount() >= 3;
3007 }
3008 
SetShadowMappingQuality(bool value)3009 void CEngine::SetShadowMappingQuality(bool value)
3010 {
3011     if(!IsShadowMappingQualitySupported()) value = false;
3012     m_qualityShadows = value;
3013 }
3014 
GetShadowMappingQuality()3015 bool CEngine::GetShadowMappingQuality()
3016 {
3017     return m_qualityShadows;
3018 }
3019 
SetTerrainShadows(bool value)3020 void CEngine::SetTerrainShadows(bool value)
3021 {
3022     m_terrainShadows = value;
3023 }
3024 
GetTerrainShadows()3025 bool CEngine::GetTerrainShadows()
3026 {
3027     return m_terrainShadows;
3028 }
3029 
SetVSync(int value)3030 void CEngine::SetVSync(int value)
3031 {
3032     if (value < -1) value = -1;
3033     if (value > 1) value = 1;
3034     if(m_vsync == value) return;
3035     m_vsync = value;
3036 }
3037 
GetVSync()3038 int CEngine::GetVSync()
3039 {
3040     return m_vsync;
3041 }
3042 
SetBackForce(bool present)3043 void CEngine::SetBackForce(bool present)
3044 {
3045     m_backForce = present;
3046 }
3047 
GetBackForce()3048 bool CEngine::GetBackForce()
3049 {
3050     return m_backForce;
3051 }
3052 
SetLightMode(bool present)3053 void CEngine::SetLightMode(bool present)
3054 {
3055     m_lightMode = present;
3056 }
3057 
GetLightMode()3058 bool CEngine::GetLightMode()
3059 {
3060     return m_lightMode;
3061 }
3062 
SetEditIndentMode(bool autoIndent)3063 void CEngine::SetEditIndentMode(bool autoIndent)
3064 {
3065     m_editIndentMode = autoIndent;
3066 }
3067 
GetEditIndentMode()3068 bool CEngine::GetEditIndentMode()
3069 {
3070     return m_editIndentMode;
3071 }
3072 
SetEditIndentValue(int value)3073 void CEngine::SetEditIndentValue(int value)
3074 {
3075     m_editIndentValue = value;
3076 }
3077 
GetEditIndentValue()3078 int CEngine::GetEditIndentValue()
3079 {
3080     return m_editIndentValue;
3081 }
3082 
SetTracePrecision(float factor)3083 void CEngine::SetTracePrecision(float factor)
3084 {
3085     m_tracePrecision = factor;
3086 }
3087 
GetTracePrecision()3088 float CEngine::GetTracePrecision()
3089 {
3090     return m_tracePrecision;
3091 }
3092 
SetMouseType(EngineMouseType type)3093 void CEngine::SetMouseType(EngineMouseType type)
3094 {
3095     m_mouseType = type;
3096 }
3097 
GetMouseType()3098 EngineMouseType CEngine::GetMouseType()
3099 {
3100     return m_mouseType;
3101 }
3102 
SetPauseBlurEnabled(bool enable)3103 void CEngine::SetPauseBlurEnabled(bool enable)
3104 {
3105     m_pauseBlurEnabled = enable;
3106 }
3107 
GetPauseBlurEnabled()3108 bool CEngine::GetPauseBlurEnabled()
3109 {
3110     return m_pauseBlurEnabled;
3111 }
3112 
GetMatView()3113 const Math::Matrix& CEngine::GetMatView()
3114 {
3115     return m_matView;
3116 }
3117 
GetMatProj()3118 const Math::Matrix& CEngine::GetMatProj()
3119 {
3120     return m_matProj;
3121 }
3122 
GetEyePt()3123 Math::Vector CEngine::GetEyePt()
3124 {
3125     return m_eyePt;
3126 }
3127 
GetLookatPt()3128 Math::Vector CEngine::GetLookatPt()
3129 {
3130     return m_lookatPt;
3131 }
3132 
GetEyeDirH()3133 float CEngine::GetEyeDirH()
3134 {
3135     return m_eyeDirH;
3136 }
3137 
GetEyeDirV()3138 float CEngine::GetEyeDirV()
3139 {
3140     return m_eyeDirV;
3141 }
3142 
IsVisiblePoint(const Math::Vector & pos)3143 bool CEngine::IsVisiblePoint(const Math::Vector &pos)
3144 {
3145     return Math::Distance(m_eyePt, pos) <= (m_deepView[0] * m_clippingDistance);
3146 }
3147 
UpdateMatProj()3148 void CEngine::UpdateMatProj()
3149 {
3150     m_device->SetTransform(TRANSFORM_PROJECTION, m_matProj);
3151 }
3152 
ApplyChange()3153 void CEngine::ApplyChange()
3154 {
3155     SetFocus(m_focus);
3156 
3157     // recapture 3D scene
3158     if (m_worldCaptured)
3159     {
3160         m_captureWorld = true;
3161         m_worldCaptured = false;
3162     }
3163 }
3164 
3165 /*******************************************************
3166                       Rendering
3167  *******************************************************/
3168 
3169 
3170 
3171 /**
3172   This function sets up render states, clears the
3173   viewport, and renders the scene. */
Render()3174 void CEngine::Render()
3175 {
3176     m_fpsCounter++;
3177 
3178     m_systemUtils->GetCurrentTimeStamp(m_currentFrameTime);
3179     float diff = m_systemUtils->TimeStampDiff(m_lastFrameTime, m_currentFrameTime, STU_SEC);
3180     if (diff > 1.0f)
3181     {
3182         m_systemUtils->CopyTimeStamp(m_lastFrameTime, m_currentFrameTime);
3183 
3184         m_fps = m_fpsCounter / diff;
3185         m_fpsCounter = 0;
3186     }
3187 
3188     if (! m_render)
3189         return;
3190 
3191     m_statisticTriangle = 0;
3192     m_lastState = -1;
3193     m_lastColor = Color(-1.0f);
3194     m_lastMaterial = Material();
3195 
3196     m_lightMan->UpdateLights();
3197 
3198     Color color;
3199     if (m_cloud->GetLevel() != 0.0f)  // clouds?
3200         color = m_backgroundCloudDown;
3201     else
3202         color = m_backgroundColorDown;
3203 
3204     m_device->SetClearColor(color);
3205 
3206     // Begin the scene
3207     m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, true);
3208 
3209     m_device->BeginScene();
3210 
3211     // use currently captured scene for world
3212     if (m_worldCaptured && !m_captureWorld)
3213     {
3214         DrawCaptured3DScene();
3215     }
3216     else
3217     {
3218         // Render shadow map
3219         if (m_drawWorld && m_shadowMapping)
3220             RenderShadowMap();
3221 
3222         UseMSAA(true);
3223 
3224         DrawBackground();                // draws the background
3225 
3226         if (m_drawWorld)
3227             Draw3DScene();
3228 
3229         UseMSAA(false);
3230 
3231         // marked to capture currently rendered world
3232         if (m_captureWorld)
3233         {
3234             Capture3DScene();
3235             m_device->Clear();
3236             DrawCaptured3DScene();
3237         }
3238     }
3239 
3240     CProfiler::StartPerformanceCounter(PCNT_RENDER_INTERFACE);
3241     DrawInterface();
3242     CProfiler::StopPerformanceCounter(PCNT_RENDER_INTERFACE);
3243 
3244     // End the scene
3245     m_device->EndScene();
3246 }
3247 
Draw3DScene()3248 void CEngine::Draw3DScene()
3249 {
3250     if (!m_worldCaptured)
3251     {
3252         if (m_capturedWorldTexture.Valid())
3253         {
3254             m_device->DestroyTexture(m_capturedWorldTexture);
3255             m_capturedWorldTexture = Texture();
3256         }
3257     }
3258 
3259     m_device->SetRenderState(RENDER_STATE_DEPTH_TEST, false);
3260 
3261     UpdateGroundSpotTextures();
3262 
3263     DrawPlanet();  // draws the planets
3264     m_cloud->Draw();  // draws the clouds
3265 
3266     // Display the objects
3267 
3268     m_device->SetRenderState(RENDER_STATE_DEPTH_TEST, true);
3269     m_device->SetRenderState(RENDER_STATE_LIGHTING, true);
3270     m_device->SetRenderState(RENDER_STATE_FOG, true);
3271 
3272     float fogStart = m_deepView[m_rankView] * m_fogStart[m_rankView] * m_clippingDistance;
3273     float fogEnd = m_deepView[m_rankView] * m_clippingDistance;
3274     m_device->SetFogParams(FOG_LINEAR, m_fogColor[m_rankView], fogStart, fogEnd, 1.0f);
3275 
3276     m_device->SetTransform(TRANSFORM_PROJECTION, m_matProj);
3277     m_device->SetTransform(TRANSFORM_VIEW, m_matView);
3278 
3279     // TODO: This causes a rendering artifact and I can't see anything that breaks if you just comment it out
3280     // So I'll just leave it like that for now ~krzys_h
3281     //m_water->DrawBack();  // draws water background
3282 
3283     CProfiler::StartPerformanceCounter(PCNT_RENDER_TERRAIN);
3284 
3285     // Draw terrain
3286 
3287     m_lightMan->UpdateDeviceLights(ENG_OBJTYPE_TERRAIN);
3288 
3289     UseShadowMapping(true);
3290 
3291     for (int objRank = 0; objRank < static_cast<int>(m_objects.size()); objRank++)
3292     {
3293         if (! m_objects[objRank].used)
3294             continue;
3295 
3296         if (m_objects[objRank].type != ENG_OBJTYPE_TERRAIN)
3297             continue;
3298 
3299         if (! m_objects[objRank].drawWorld)
3300             continue;
3301 
3302         m_device->SetTransform(TRANSFORM_WORLD, m_objects[objRank].transform);
3303 
3304         if (! IsVisible(objRank))
3305             continue;
3306 
3307         int baseObjRank = m_objects[objRank].baseObjRank;
3308         if (baseObjRank == -1)
3309             continue;
3310 
3311         assert(baseObjRank >= 0 && baseObjRank < static_cast<int>( m_baseObjects.size() ));
3312 
3313         EngineBaseObject& p1 = m_baseObjects[baseObjRank];
3314         if (! p1.used)
3315             continue;
3316 
3317         for (int l2 = 0; l2 < static_cast<int>( p1.next.size() ); l2++)
3318         {
3319             EngineBaseObjTexTier& p2 = p1.next[l2];
3320 
3321             SetTexture(p2.tex1, 0);
3322             SetTexture(p2.tex2, 1);
3323 
3324             for (int l3 = 0; l3 < static_cast<int>( p2.next.size() ); l3++)
3325             {
3326                 EngineBaseObjDataTier& p3 = p2.next[l3];
3327 
3328                 SetMaterial(p3.material);
3329                 SetState(p3.state);
3330 
3331                 DrawObject(p3);
3332             }
3333         }
3334     }
3335 
3336     if (!m_qualityShadows)
3337         UseShadowMapping(false);
3338 
3339     // Draws the old-style shadow spots, if shadow mapping disabled
3340     if (!m_shadowMapping)
3341         DrawShadowSpots();
3342 
3343     CProfiler::StopPerformanceCounter(PCNT_RENDER_TERRAIN);
3344 
3345     // Draw other objects
3346 
3347     CProfiler::StartPerformanceCounter(PCNT_RENDER_OBJECTS);
3348 
3349     bool transparent = false;
3350 
3351     for (int objRank = 0; objRank < static_cast<int>(m_objects.size()); objRank++)
3352     {
3353         if (! m_objects[objRank].used)
3354             continue;
3355 
3356         if (m_objects[objRank].type == ENG_OBJTYPE_TERRAIN)
3357             continue;
3358 
3359         if (! m_objects[objRank].drawWorld)
3360             continue;
3361 
3362         m_device->SetTransform(TRANSFORM_WORLD, m_objects[objRank].transform);
3363 
3364         if (! IsVisible(objRank))
3365             continue;
3366 
3367         int baseObjRank = m_objects[objRank].baseObjRank;
3368         if (baseObjRank == -1)
3369             continue;
3370 
3371         assert(baseObjRank >= 0 && baseObjRank < static_cast<int>( m_baseObjects.size() ));
3372 
3373         EngineBaseObject& p1 = m_baseObjects[baseObjRank];
3374         if (! p1.used)
3375             continue;
3376 
3377         m_lightMan->UpdateDeviceLights(m_objects[objRank].type);
3378 
3379         for (int l2 = 0; l2 < static_cast<int>( p1.next.size() ); l2++)
3380         {
3381             EngineBaseObjTexTier& p2 = p1.next[l2];
3382 
3383             SetTexture(p2.tex1, 0);
3384             SetTexture(p2.tex2, 1);
3385 
3386             for (int l3 = 0; l3 < static_cast<int>( p2.next.size() ); l3++)
3387             {
3388                 EngineBaseObjDataTier& p3 = p2.next[l3];
3389 
3390                 if (m_objects[objRank].transparency != 0.0f)  // transparent ?
3391                 {
3392                     transparent = true;
3393                     continue;
3394                 }
3395 
3396                 SetMaterial(p3.material);
3397                 SetState(p3.state);
3398 
3399                 DrawObject(p3);
3400             }
3401         }
3402     }
3403 
3404     UseShadowMapping(false);
3405 
3406     // Draw transparent objects
3407 
3408     if (transparent)
3409     {
3410         int tState = ENG_RSTATE_TTEXTURE_BLACK | ENG_RSTATE_2FACE;
3411         Color tColor = Color(68.0f / 255.0f, 68.0f / 255.0f, 68.0f / 255.0f, 68.0f / 255.0f);
3412 
3413         for (int objRank = 0; objRank < static_cast<int>(m_objects.size()); objRank++)
3414         {
3415             if (! m_objects[objRank].used)
3416                 continue;
3417 
3418             if (m_objects[objRank].type == ENG_OBJTYPE_TERRAIN)
3419                 continue;
3420 
3421             if (! m_objects[objRank].drawWorld)
3422                 continue;
3423 
3424             m_device->SetTransform(TRANSFORM_WORLD, m_objects[objRank].transform);
3425 
3426             if (! IsVisible(objRank))
3427                 continue;
3428 
3429             int baseObjRank = m_objects[objRank].baseObjRank;
3430             if (baseObjRank == -1)
3431                 continue;
3432 
3433             assert(baseObjRank >= 0 && baseObjRank < static_cast<int>( m_baseObjects.size() ));
3434 
3435             EngineBaseObject& p1 = m_baseObjects[baseObjRank];
3436             if (! p1.used)
3437                 continue;
3438 
3439             m_lightMan->UpdateDeviceLights(m_objects[objRank].type);
3440 
3441             for (int l2 = 0; l2 < static_cast<int>( p1.next.size() ); l2++)
3442             {
3443                 EngineBaseObjTexTier& p2 = p1.next[l2];
3444 
3445                 SetTexture(p2.tex1, 0);
3446                 SetTexture(p2.tex2, 1);
3447 
3448                 for (int l3 = 0; l3 < static_cast<int>( p2.next.size() ); l3++)
3449                 {
3450                     EngineBaseObjDataTier& p3 = p2.next[l3];
3451 
3452                     if (m_objects[objRank].transparency == 0.0f)
3453                         continue;
3454 
3455                     SetMaterial(p3.material);
3456                     SetState(tState, tColor);
3457 
3458                     DrawObject(p3);
3459                 }
3460             }
3461         }
3462     }
3463 
3464     CProfiler::StopPerformanceCounter(PCNT_RENDER_OBJECTS);
3465 
3466     m_lightMan->UpdateDeviceLights(ENG_OBJTYPE_TERRAIN);
3467 
3468     if (m_debugLights)
3469         m_device->DebugLights();
3470 
3471     if (m_debugDumpLights)
3472     {
3473         m_debugDumpLights = false;
3474         m_lightMan->DebugDumpLights();
3475     }
3476 
3477     CProfiler::StartPerformanceCounter(PCNT_RENDER_WATER);
3478     m_water->DrawSurf(); // draws water surface
3479     CProfiler::StopPerformanceCounter(PCNT_RENDER_WATER);
3480 
3481     m_device->SetRenderState(RENDER_STATE_LIGHTING, false);
3482 
3483     RenderPendingDebugDraws();
3484 
3485     if (m_debugGoto)
3486     {
3487         Math::Matrix worldMatrix;
3488         worldMatrix.LoadIdentity();
3489         m_device->SetTransform(TRANSFORM_WORLD, worldMatrix);
3490 
3491         SetState(ENG_RSTATE_OPAQUE_COLOR);
3492 
3493         for (const auto& line : m_displayGoto)
3494         {
3495             m_device->DrawPrimitive(PRIMITIVE_LINE_STRIP, line.data(), line.size());
3496         }
3497     }
3498     m_displayGoto.clear();
3499 
3500     CProfiler::StartPerformanceCounter(PCNT_RENDER_PARTICLE_WORLD);
3501     m_particle->DrawParticle(SH_WORLD); // draws the particles of the 3D world
3502     CProfiler::StopPerformanceCounter(PCNT_RENDER_PARTICLE_WORLD);
3503 
3504     m_device->SetRenderState(RENDER_STATE_LIGHTING, true);
3505 
3506     m_lightning->Draw();                     // draws lightning
3507 
3508     DrawForegroundImage();   // draws the foreground
3509 
3510     if (! m_overFront) DrawOverColor();      // draws the foreground color
3511 }
3512 
Capture3DScene()3513 void CEngine::Capture3DScene()
3514 {
3515     // destroy existing texture
3516     if (m_capturedWorldTexture.Valid())
3517     {
3518         m_device->DestroyTexture(m_capturedWorldTexture);
3519         m_capturedWorldTexture = Texture();
3520     }
3521 
3522     // obtain pixels from screen
3523     int width = m_size.x;
3524     int height = m_size.y;
3525 
3526     auto pixels = m_device->GetFrameBufferPixels();
3527     unsigned char* data = reinterpret_cast<unsigned char*>(pixels->GetPixelsData());
3528 
3529     // calculate 2nd mipmap
3530     int newWidth = width / 4;
3531     int newHeight = height / 4;
3532     std::unique_ptr<unsigned char[]> mipmap = MakeUniqueArray<unsigned char>(4 * newWidth * newHeight);
3533 
3534     for (int x = 0; x < newWidth; x++)
3535     {
3536         for (int y = 0; y < newHeight; y++)
3537         {
3538             float color[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
3539 
3540             for (int i = 0; i < 4; i++)
3541             {
3542                 for (int j = 0; j < 4; j++)
3543                 {
3544                     int index = 4 * ((4 * x + i) + width * (4 * y + j));
3545 
3546                     for (int k = 0; k < 4; k++)
3547                         color[k] += data[index + k];
3548                 }
3549             }
3550 
3551             int index = 4 * (x + newWidth * y);
3552 
3553             for (int k = 0; k < 4; k++)
3554             {
3555                 mipmap[index + k] = static_cast<unsigned char>(color[k] * (1.0f / 16.0f));
3556             }
3557         }
3558     }
3559 
3560     // calculate Gaussian blur
3561     std::unique_ptr<unsigned char[]> blured = MakeUniqueArray<unsigned char>(4 * newWidth * newHeight);
3562 
3563     float matrix[7][7] =
3564         {
3565             { 0.00000067f, 0.00002292f, 0.00019117f, 0.00038771f, 0.00019117f, 0.00002292f, 0.00000067f },
3566             { 0.00002292f, 0.00078634f, 0.00655965f, 0.01330373f, 0.00655965f, 0.00078633f, 0.00002292f },
3567             { 0.00019117f, 0.00655965f, 0.05472157f, 0.11098164f, 0.05472157f, 0.00655965f, 0.00019117f },
3568             { 0.00038771f, 0.01330373f, 0.11098164f, 0.22508352f, 0.11098164f, 0.01330373f, 0.00038771f },
3569             { 0.00019117f, 0.00655965f, 0.05472157f, 0.11098164f, 0.05472157f, 0.00655965f, 0.00019117f },
3570             { 0.00002292f, 0.00078633f, 0.00655965f, 0.01330373f, 0.00655965f, 0.00078633f, 0.00002292f },
3571             { 0.00000067f, 0.00002292f, 0.00019117f, 0.00038771f, 0.00019117f, 0.00002292f, 0.00000067f }
3572         };
3573 
3574     for (int x = 0; x < newWidth; x++)
3575     {
3576         for (int y = 0; y < newHeight; y++)
3577         {
3578             float color[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
3579 
3580             for (int i = -3; i <= 3; i++)
3581             {
3582                 for (int j = -3; j <= 3; j++)
3583                 {
3584                     int xp = Math::Clamp(x + i, 0, newWidth - 1);
3585                     int yp = Math::Clamp(y + j, 0, newHeight - 1);
3586 
3587                     float weight = matrix[i + 3][j + 3];
3588 
3589                     int index = 4 * (newWidth * yp + xp);
3590 
3591                     for (int k = 0; k < 4; k++)
3592                         color[k] += weight * mipmap[index + k];
3593                 }
3594             }
3595 
3596             int index = 4 * (newWidth * y + x);
3597 
3598             for (int k = 0; k < 4; k++)
3599             {
3600                 float value = Math::Clamp(color[k], 0.0f, 255.0f);
3601                 blured[index + k] = static_cast<unsigned char>(value);
3602             }
3603         }
3604     }
3605 
3606     // create SDL surface and final texture
3607     ImageData image;
3608     image.surface = SDL_CreateRGBSurfaceFrom(blured.get(), newWidth, newHeight, 32, 0, 0, 0, 0, 0xFF000000);
3609 
3610     TextureCreateParams params;
3611     params.filter = TEX_FILTER_BILINEAR;
3612     params.format = TEX_IMG_RGBA;
3613     params.mipmap = false;
3614 
3615     m_capturedWorldTexture = m_device->CreateTexture(&image, params);
3616 
3617     SDL_FreeSurface(image.surface);
3618 
3619     m_captureWorld = false;
3620     m_worldCaptured = true;
3621 }
3622 
DrawCaptured3DScene()3623 void CEngine::DrawCaptured3DScene()
3624 {
3625     Math::Matrix identity;
3626 
3627     m_device->SetTransform(TRANSFORM_PROJECTION, identity);
3628     m_device->SetTransform(TRANSFORM_VIEW, identity);
3629     m_device->SetTransform(TRANSFORM_WORLD, identity);
3630 
3631     m_device->SetRenderState(RENDER_STATE_BLENDING, false);
3632     m_device->SetRenderState(RENDER_STATE_CULLING, false);
3633 
3634     Vertex vertices[4];
3635 
3636     vertices[0] = Vertex(Math::Vector(-1.0f, -1.0f, 0.0f), Math::Vector(0.0f, 1.0f, 0.0f), Math::Point(0.0f, 0.0f));
3637     vertices[1] = Vertex(Math::Vector(1.0f, -1.0f, 0.0f), Math::Vector(0.0f, 1.0f, 0.0f), Math::Point(1.0f, 0.0f));
3638     vertices[2] = Vertex(Math::Vector(-1.0f, 1.0f, 0.0f), Math::Vector(0.0f, 1.0f, 0.0f), Math::Point(0.0f, 1.0f));
3639     vertices[3] = Vertex(Math::Vector(1.0f, 1.0f, 0.0f), Math::Vector(0.0f, 1.0f, 0.0f), Math::Point(1.0f, 1.0f));
3640 
3641     m_device->SetRenderState(RENDER_STATE_DEPTH_TEST, false);
3642 
3643     m_device->SetTexture(TEXTURE_PRIMARY, m_capturedWorldTexture);
3644     m_device->SetTextureEnabled(TEXTURE_PRIMARY, true);
3645     m_device->SetTextureEnabled(TEXTURE_SECONDARY, false);
3646 
3647     m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertices, 4);
3648 }
3649 
RenderDebugSphere(const Math::Sphere & sphere,const Math::Matrix & transform,const Gfx::Color & color)3650 void CEngine::RenderDebugSphere(const Math::Sphere& sphere, const Math::Matrix& transform, const Gfx::Color& color)
3651 {
3652     static constexpr int LONGITUDE_DIVISIONS = 16;
3653     static constexpr int LATITUDE_DIVISIONS = 8;
3654     static constexpr int NUM_LINE_STRIPS = 2 + LONGITUDE_DIVISIONS + LATITUDE_DIVISIONS;
3655     static constexpr int VERTS_IN_LINE_STRIP = 32;
3656 
3657     static std::array<Math::Vector, NUM_LINE_STRIPS * VERTS_IN_LINE_STRIP> verticesTemplate = []
3658     {
3659         std::array<Math::Vector, NUM_LINE_STRIPS * VERTS_IN_LINE_STRIP> vertices;
3660 
3661         auto SpherePoint = [&](float latitude, float longitude)
3662         {
3663             float latitudeAngle = (latitude - 0.5f) * 2.0f * Math::PI;
3664             float longitudeAngle = longitude * 2.0f * Math::PI;
3665             return Math::Vector(sinf(latitudeAngle) * cosf(longitudeAngle),
3666                                 cosf(latitudeAngle),
3667                                 sinf(latitudeAngle) * sinf(longitudeAngle));
3668         };
3669 
3670         auto vert = vertices.begin();
3671 
3672         for (int longitudeDivision = 0; longitudeDivision <= LONGITUDE_DIVISIONS; ++longitudeDivision)
3673         {
3674             for (int segment = 0; segment < VERTS_IN_LINE_STRIP; ++segment)
3675             {
3676                 float latitude = static_cast<float>(segment) / VERTS_IN_LINE_STRIP;
3677                 float longitude = static_cast<float>(longitudeDivision) / (LONGITUDE_DIVISIONS);
3678                 *vert++ = SpherePoint(latitude, longitude);
3679             }
3680         }
3681 
3682         for (int latitudeDivision = 0; latitudeDivision <= LATITUDE_DIVISIONS; ++latitudeDivision)
3683         {
3684             for (int segment = 0; segment < VERTS_IN_LINE_STRIP; ++segment)
3685             {
3686                 float latitude = static_cast<float>(latitudeDivision + 1) / (LATITUDE_DIVISIONS + 2);
3687                 float longitude = static_cast<float>(segment) / VERTS_IN_LINE_STRIP;
3688                 *vert++ = SpherePoint(latitude, longitude);
3689             }
3690         }
3691         return vertices;
3692     }();
3693 
3694 
3695     const int firstDraw = m_pendingDebugDraws.firsts.size();
3696     const int firstVert = m_pendingDebugDraws.vertices.size();
3697 
3698     m_pendingDebugDraws.firsts.resize(m_pendingDebugDraws.firsts.size() + NUM_LINE_STRIPS);
3699     m_pendingDebugDraws.counts.resize(m_pendingDebugDraws.counts.size() + NUM_LINE_STRIPS);
3700     m_pendingDebugDraws.vertices.resize(m_pendingDebugDraws.vertices.size() + verticesTemplate.size());
3701 
3702     for (int i = 0; i < NUM_LINE_STRIPS; ++i)
3703     {
3704         m_pendingDebugDraws.firsts[i + firstDraw] = firstVert + i * VERTS_IN_LINE_STRIP;
3705     }
3706 
3707     for (int i = 0; i < NUM_LINE_STRIPS; ++i)
3708     {
3709         m_pendingDebugDraws.counts[i + firstDraw] = VERTS_IN_LINE_STRIP;
3710     }
3711 
3712     for (std::size_t i = 0; i < verticesTemplate.size(); ++i)
3713     {
3714         auto pos = Math::MatrixVectorMultiply(transform, sphere.pos + verticesTemplate[i] * sphere.radius);
3715         m_pendingDebugDraws.vertices[i + firstVert] = VertexCol{pos, color};
3716     }
3717 }
3718 
RenderDebugBox(const Math::Vector & mins,const Math::Vector & maxs,const Math::Matrix & transform,const Gfx::Color & color)3719 void CEngine::RenderDebugBox(const Math::Vector& mins, const Math::Vector& maxs, const Math::Matrix& transform, const Gfx::Color& color)
3720 {
3721     static constexpr int NUM_LINE_STRIPS = 4;
3722     static constexpr int VERTS_IN_LINE_STRIP = 4;
3723 
3724     const int firstDraw = m_pendingDebugDraws.firsts.size();
3725     const int firstVert = m_pendingDebugDraws.vertices.size();
3726 
3727     m_pendingDebugDraws.firsts.resize(m_pendingDebugDraws.firsts.size() + NUM_LINE_STRIPS);
3728     m_pendingDebugDraws.counts.resize(m_pendingDebugDraws.counts.size() + NUM_LINE_STRIPS);
3729     m_pendingDebugDraws.vertices.resize(m_pendingDebugDraws.vertices.size() + NUM_LINE_STRIPS * VERTS_IN_LINE_STRIP);
3730 
3731     for (int i = 0; i < NUM_LINE_STRIPS; ++i)
3732     {
3733         m_pendingDebugDraws.firsts[i + firstDraw] = firstVert + (i * VERTS_IN_LINE_STRIP);
3734     }
3735 
3736     for (int i = 0; i < NUM_LINE_STRIPS; ++i)
3737     {
3738         m_pendingDebugDraws.counts[i + firstDraw] = NUM_LINE_STRIPS;
3739     }
3740 
3741     auto vert = m_pendingDebugDraws.vertices.begin() + firstVert;
3742 
3743     *vert++ = VertexCol{Math::MatrixVectorMultiply(transform, Math::Vector{mins.x, mins.y, mins.z}), color};
3744     *vert++ = VertexCol{Math::MatrixVectorMultiply(transform, Math::Vector{maxs.x, mins.y, mins.z}), color};
3745     *vert++ = VertexCol{Math::MatrixVectorMultiply(transform, Math::Vector{maxs.x, maxs.y, mins.z}), color};
3746     *vert++ = VertexCol{Math::MatrixVectorMultiply(transform, Math::Vector{maxs.x, maxs.y, maxs.z}), color};
3747 
3748     *vert++ = VertexCol{Math::MatrixVectorMultiply(transform, Math::Vector{mins.x, mins.y, maxs.z}), color};
3749     *vert++ = VertexCol{Math::MatrixVectorMultiply(transform, Math::Vector{mins.x, mins.y, mins.z}), color};
3750     *vert++ = VertexCol{Math::MatrixVectorMultiply(transform, Math::Vector{mins.x, maxs.y, mins.z}), color};
3751     *vert++ = VertexCol{Math::MatrixVectorMultiply(transform, Math::Vector{maxs.x, maxs.y, mins.z}), color};
3752 
3753     *vert++ = VertexCol{Math::MatrixVectorMultiply(transform, Math::Vector{maxs.x, mins.y, maxs.z}), color};
3754     *vert++ = VertexCol{Math::MatrixVectorMultiply(transform, Math::Vector{mins.x, mins.y, maxs.z}), color};
3755     *vert++ = VertexCol{Math::MatrixVectorMultiply(transform, Math::Vector{mins.x, maxs.y, maxs.z}), color};
3756     *vert++ = VertexCol{Math::MatrixVectorMultiply(transform, Math::Vector{mins.x, maxs.y, mins.z}), color};
3757 
3758     *vert++ = VertexCol{Math::MatrixVectorMultiply(transform, Math::Vector{maxs.x, mins.y, mins.z}), color};
3759     *vert++ = VertexCol{Math::MatrixVectorMultiply(transform, Math::Vector{maxs.x, mins.y, maxs.z}), color};
3760     *vert++ = VertexCol{Math::MatrixVectorMultiply(transform, Math::Vector{maxs.x, maxs.y, maxs.z}), color};
3761     *vert++ = VertexCol{Math::MatrixVectorMultiply(transform, Math::Vector{mins.x, maxs.y, maxs.z}), color};
3762 }
3763 
RenderPendingDebugDraws()3764 void CEngine::RenderPendingDebugDraws()
3765 {
3766     if (m_pendingDebugDraws.firsts.empty()) return;
3767 
3768     m_device->SetTransform(TRANSFORM_WORLD, Math::Matrix{});
3769 
3770     SetState(ENG_RSTATE_OPAQUE_COLOR);
3771 
3772     m_device->DrawPrimitives(PRIMITIVE_LINE_STRIP,
3773                              m_pendingDebugDraws.vertices.data(),
3774                              m_pendingDebugDraws.firsts.data(),
3775                              m_pendingDebugDraws.counts.data(),
3776                              m_pendingDebugDraws.firsts.size());
3777 
3778     m_pendingDebugDraws.firsts.clear();
3779     m_pendingDebugDraws.counts.clear();
3780     m_pendingDebugDraws.vertices.clear();
3781 }
3782 
RenderShadowMap()3783 void CEngine::RenderShadowMap()
3784 {
3785     m_shadowMapping = m_shadowMapping && m_device->IsShadowMappingSupported();
3786     m_offscreenShadowRendering = m_offscreenShadowRendering && m_device->IsFramebufferSupported();
3787     m_offscreenShadowRenderingResolution = Math::Min(m_offscreenShadowRenderingResolution, m_device->GetMaxTextureSize());
3788 
3789     if (!m_shadowMapping) return;
3790 
3791     CProfiler::StartPerformanceCounter(PCNT_RENDER_SHADOW_MAP);
3792 
3793     // If no shadow map texture exists, create it
3794     if (m_shadowMap.id == 0)
3795     {
3796         int width = 256, height = 256;
3797         int depth;
3798 
3799         if (m_offscreenShadowRendering)
3800         {
3801             width = height = m_offscreenShadowRenderingResolution;
3802 
3803             FramebufferParams params;
3804             params.width = params.height = width;
3805             params.depth = depth = 32;
3806             params.colorAttachment = FramebufferParams::AttachmentType::None;
3807             params.depthAttachment = FramebufferParams::AttachmentType::Texture;
3808 
3809             CFramebuffer *framebuffer = m_device->CreateFramebuffer("shadow", params);
3810             if (framebuffer == nullptr)
3811             {
3812                 GetLogger()->Error("Could not create shadow mapping framebuffer, disabling dynamic shadows\n");
3813                 m_shadowMapping = false;
3814                 m_offscreenShadowRendering = false;
3815                 m_qualityShadows = false;
3816                 CProfiler::StopPerformanceCounter(PCNT_RENDER_SHADOW_MAP);
3817                 return;
3818             }
3819 
3820             m_shadowMap.id = framebuffer->GetDepthTexture();
3821             m_shadowMap.size = Math::IntPoint(width, height);
3822         }
3823         else
3824         {
3825             int min = Math::Min(m_size.x, m_size.y);
3826 
3827             for (int i = 0; i < 16; i++)
3828             {
3829                 if (min < (1 << i)) break;
3830 
3831                 width = height = 1 << i;
3832             }
3833 
3834             depth = m_app->GetInstance().GetVideoConfig().depthSize;
3835 
3836             m_shadowMap = m_device->CreateDepthTexture(width, height, depth);
3837         }
3838 
3839         GetLogger()->Info("Created shadow map texture: %dx%d, depth %d\n", width, height, depth);
3840     }
3841 
3842     if (m_offscreenShadowRendering)
3843     {
3844         m_device->GetFramebuffer("shadow")->Bind();
3845     }
3846 
3847     m_device->SetRenderMode(RENDER_MODE_SHADOW);
3848     m_device->Clear();
3849 
3850     // change state to rendering shadow maps
3851     m_device->SetColorMask(false, false, false, false);
3852     m_device->SetRenderState(RENDER_STATE_DEPTH_TEST, true);
3853     m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, true);
3854     m_device->SetRenderState(RENDER_STATE_BLENDING, false);
3855     m_device->SetRenderState(RENDER_STATE_LIGHTING, false);
3856     m_device->SetRenderState(RENDER_STATE_FOG, false);
3857     m_device->SetRenderState(RENDER_STATE_CULLING, false);
3858     m_device->SetRenderState(RENDER_STATE_ALPHA_TEST, true);
3859     m_device->SetAlphaTestFunc(COMP_FUNC_GREATER, 0.5f);
3860     m_device->SetRenderState(RENDER_STATE_DEPTH_BIAS, true);
3861     m_device->SetDepthBias(2.0f, 8.0f);
3862     m_device->SetViewport(0, 0, m_shadowMap.size.x, m_shadowMap.size.y);
3863 
3864     // recompute matrices
3865     Math::Vector worldUp(0.0f, 1.0f, 0.0f);
3866     Math::Vector lightDir = Math::Vector(1.0f, 2.0f, -1.0f);
3867     Math::Vector dir = m_lookatPt - m_eyePt;
3868     dir.y = 0.0f;
3869     dir.Normalize();
3870 
3871     float dist = m_shadowRange;
3872     float depth = 200.0f;
3873 
3874     if (dist < 0.5f)
3875     {
3876         float scale = log(m_shadowMap.size.x) / log(2.0f) - 6.5f;
3877         dist = 75.0f * scale;
3878     }
3879 
3880     Math::Vector pos = m_lookatPt + 0.25f * dist * dir;
3881 
3882     {
3883         // To prevent 'shadow shimmering', we ensure that the position only moves in texel-sized
3884         // increments. To do this we transform the position to a space where the light's forward/right/up
3885         // axes are aligned with the x/y/z axes (not necessarily in that order, and +/- signs don't matter).
3886         Math::Matrix lightRotation;
3887         Math::LoadViewMatrix(lightRotation, Math::Vector{}, lightDir, worldUp);
3888         pos = Math::MatrixVectorMultiply(lightRotation, pos);
3889         // ...then we round to the nearest worldUnitsPerTexel:
3890         const float worldUnitsPerTexel = (dist * 2.0f) / m_shadowMap.size.x;
3891         pos /= worldUnitsPerTexel;
3892         pos.x = round(pos.x);
3893         pos.y = round(pos.y);
3894         pos.z = round(pos.z);
3895         pos *= worldUnitsPerTexel;
3896         // ...and convert back to world space.
3897         pos = Math::MatrixVectorMultiply(lightRotation.Inverse(), pos);
3898     }
3899 
3900     Math::Vector lookAt = pos - lightDir;
3901 
3902     Math::LoadOrthoProjectionMatrix(m_shadowProjMat, -dist, dist, -dist, dist, -depth, depth);
3903     Math::LoadViewMatrix(m_shadowViewMat, pos, lookAt, worldUp);
3904 
3905     Math::Matrix scaleMat;
3906     Math::LoadScaleMatrix(scaleMat, Math::Vector(1.0f, 1.0f, -1.0f));
3907     m_shadowViewMat = Math::MultiplyMatrices(scaleMat, m_shadowViewMat);
3908 
3909     Math::Matrix temporary = Math::MultiplyMatrices(m_shadowProjMat, m_shadowViewMat);
3910     m_shadowTextureMat = Math::MultiplyMatrices(m_shadowBias, temporary);
3911 
3912     m_shadowViewMat = Math::MultiplyMatrices(scaleMat, m_shadowViewMat);
3913 
3914     m_device->SetTransform(TRANSFORM_PROJECTION, m_shadowProjMat);
3915     m_device->SetTransform(TRANSFORM_VIEW, m_shadowViewMat);
3916 
3917     m_device->SetTexture(0, 0);
3918     m_device->SetTexture(1, 0);
3919     m_device->SetTexture(2, 0);
3920 
3921     // render objects into shadow map
3922     for (int objRank = 0; objRank < static_cast<int>(m_objects.size()); objRank++)
3923     {
3924         if (!m_objects[objRank].used)
3925             continue;
3926 
3927         bool terrain = (m_objects[objRank].type == ENG_OBJTYPE_TERRAIN);
3928 
3929         if (terrain)
3930         {
3931             if (m_terrainShadows)
3932             {
3933                 m_device->SetRenderState(RENDER_STATE_ALPHA_TEST, false);
3934                 m_device->SetRenderState(RENDER_STATE_CULLING, true);
3935                 m_device->SetCullMode(CULL_CCW);
3936             }
3937             else
3938                 continue;
3939         }
3940         else
3941         {
3942             m_device->SetRenderState(RENDER_STATE_ALPHA_TEST, true);
3943             m_device->SetRenderState(RENDER_STATE_CULLING, false);
3944         }
3945 
3946         m_device->SetTransform(TRANSFORM_WORLD, m_objects[objRank].transform);
3947 
3948         if (!IsVisible(objRank))
3949             continue;
3950 
3951         int baseObjRank = m_objects[objRank].baseObjRank;
3952         if (baseObjRank == -1)
3953             continue;
3954 
3955         assert(baseObjRank >= 0 && baseObjRank < static_cast<int>(m_baseObjects.size()));
3956 
3957         EngineBaseObject& p1 = m_baseObjects[baseObjRank];
3958         if (!p1.used)
3959             continue;
3960 
3961         for (int l2 = 0; l2 < static_cast<int>(p1.next.size()); l2++)
3962         {
3963             EngineBaseObjTexTier& p2 = p1.next[l2];
3964 
3965             SetTexture(p2.tex1, 0);
3966 
3967             for (int l3 = 0; l3 < static_cast<int>(p2.next.size()); l3++)
3968             {
3969                 EngineBaseObjDataTier& p3 = p2.next[l3];
3970 
3971                 DrawObject(p3);
3972             }
3973         }
3974     }
3975 
3976     m_device->SetRenderState(RENDER_STATE_DEPTH_BIAS, false);
3977     m_device->SetDepthBias(0.0f, 0.0f);
3978     m_device->SetRenderState(RENDER_STATE_ALPHA_TEST, false);
3979     m_device->SetRenderState(RENDER_STATE_CULLING, false);
3980     m_device->SetCullMode(CULL_CW);
3981 
3982     if (m_offscreenShadowRendering)     // shadow map texture already have depth information, just unbind it
3983     {
3984         m_device->GetFramebuffer("shadow")->Unbind();
3985     }
3986     else    // copy depth buffer to shadow map
3987     {
3988         m_device->CopyFramebufferToTexture(m_shadowMap, 0, 0, 0, 0, m_shadowMap.size.x, m_shadowMap.size.y);
3989     }
3990 
3991     // restore default state
3992     m_device->SetViewport(0, 0, m_size.x, m_size.y);
3993 
3994     m_device->SetColorMask(true, true, true, true);
3995     m_device->Clear();
3996 
3997     CProfiler::StopPerformanceCounter(PCNT_RENDER_SHADOW_MAP);
3998 
3999     m_device->SetRenderMode(RENDER_MODE_NORMAL);
4000     m_device->SetRenderState(RENDER_STATE_DEPTH_TEST, false);
4001 }
4002 
UseShadowMapping(bool enable)4003 void CEngine::UseShadowMapping(bool enable)
4004 {
4005     if (!m_shadowMapping) return;
4006     if (m_shadowMap.id == 0) return;
4007 
4008     if (enable)
4009     {
4010         m_device->SetShadowColor(m_shadowColor);
4011         m_device->SetTransform(TRANSFORM_SHADOW, m_shadowTextureMat);
4012         m_device->SetTexture(TEXTURE_SHADOW, m_shadowMap);
4013         m_device->SetTextureStageWrap(TEXTURE_SHADOW, TEX_WRAP_CLAMP_TO_BORDER, TEX_WRAP_CLAMP_TO_BORDER);
4014         m_device->SetRenderState(RENDER_STATE_SHADOW_MAPPING, true);
4015     }
4016     else
4017     {
4018         m_device->SetRenderState(RENDER_STATE_SHADOW_MAPPING, false);
4019         m_device->SetTexture(TEXTURE_SHADOW, 0);
4020     }
4021 }
4022 
UseMSAA(bool enable)4023 void CEngine::UseMSAA(bool enable)
4024 {
4025     m_multisample = Math::Min(m_device->GetMaxSamples(), m_multisample);
4026     if (m_multisample < 2) return;
4027 
4028     if (enable)
4029     {
4030         if (m_multisample > 1)
4031         {
4032             CFramebuffer* framebuffer = m_device->GetFramebuffer("multisample");
4033 
4034             if (framebuffer == nullptr)
4035             {
4036                 CFramebuffer* screen = m_device->GetFramebuffer("default");
4037 
4038                 FramebufferParams params;
4039                 params.width = screen->GetWidth();
4040                 params.height = screen->GetHeight();
4041                 params.depth = 24;
4042                 params.samples = m_multisample;
4043 
4044                 framebuffer = m_device->CreateFramebuffer("multisample", params);
4045 
4046                 if (framebuffer == nullptr)
4047                 {
4048                     GetLogger()->Error("Could not create MSAA framebuffer, disabling MSAA\n");
4049                     m_multisample = 1;
4050                 }
4051             }
4052 
4053             if (framebuffer != nullptr)
4054             {
4055                 framebuffer->Bind();
4056             }
4057 
4058             m_device->SetRenderState(RENDER_STATE_DEPTH_TEST, true);
4059             m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, true);
4060 
4061             m_device->Clear();
4062         }
4063     }
4064     else
4065     {
4066         if (m_multisample > 1)
4067         {
4068             CFramebuffer* framebuffer = m_device->GetFramebuffer("multisample");
4069             framebuffer->Unbind();
4070 
4071             CFramebuffer* screen = m_device->GetFramebuffer("default");
4072 
4073             int width = screen->GetWidth();
4074             int height = screen->GetHeight();
4075 
4076             framebuffer->CopyToScreen(0, 0, width, height, 0, 0, width, height);
4077         }
4078     }
4079 }
4080 
DrawObject(const EngineBaseObjDataTier & p4)4081 void CEngine::DrawObject(const EngineBaseObjDataTier& p4)
4082 {
4083     if (p4.staticBufferId != 0)
4084     {
4085         m_device->DrawStaticBuffer(p4.staticBufferId);
4086 
4087         if (p4.type == ENG_TRIANGLE_TYPE_TRIANGLES)
4088             m_statisticTriangle += p4.vertices.size() / 3;
4089         else
4090             m_statisticTriangle += p4.vertices.size() - 2;
4091     }
4092     else
4093     {
4094         if (p4.type == ENG_TRIANGLE_TYPE_TRIANGLES)
4095         {
4096             m_device->DrawPrimitive(PRIMITIVE_TRIANGLES, &p4.vertices[0], p4.vertices.size());
4097             m_statisticTriangle += p4.vertices.size() / 3;
4098         }
4099         else
4100         {
4101             m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, &p4.vertices[0], p4.vertices.size() );
4102             m_statisticTriangle += p4.vertices.size() - 2;
4103         }
4104     }
4105 }
4106 
DrawInterface()4107 void CEngine::DrawInterface()
4108 {
4109     m_device->SetRenderMode(RENDER_MODE_INTERFACE);
4110 
4111     m_device->SetRenderState(RENDER_STATE_DEPTH_TEST, false);
4112     m_device->SetRenderState(RENDER_STATE_LIGHTING, false);
4113     m_device->SetRenderState(RENDER_STATE_FOG, false);
4114 
4115     SetInterfaceCoordinates();
4116 
4117     // Force new state to disable lighting
4118     m_interfaceMode = true;
4119     m_lastState = -1;
4120     SetState(Gfx::ENG_RSTATE_NORMAL);
4121 
4122     // Draw the entire interface
4123     Ui::CInterface* interface = CRobotMain::GetInstancePointer()->GetInterface();
4124     if (interface != nullptr && m_renderInterface)
4125     {
4126         interface->Draw();
4127     }
4128 
4129     m_interfaceMode = false;
4130     m_lastState = -1;
4131     SetState(Gfx::ENG_RSTATE_NORMAL);
4132 
4133     if (!m_screenshotMode && m_renderInterface)
4134     {
4135         CProfiler::StartPerformanceCounter(PCNT_RENDER_PARTICLE_IFACE);
4136         m_particle->DrawParticle(SH_INTERFACE);  // draws the particles of the interface
4137         CProfiler::StopPerformanceCounter(PCNT_RENDER_PARTICLE_IFACE);
4138     }
4139 
4140     // 3D objects drawn in front of interface
4141     if (m_drawFront)
4142     {
4143         m_device->SetRenderMode(RENDER_MODE_NORMAL);
4144 
4145         // Display the objects
4146         m_device->SetRenderState(RENDER_STATE_DEPTH_TEST, true);
4147 
4148         m_device->SetTransform(TRANSFORM_PROJECTION, m_matProj);
4149 
4150         m_device->SetGlobalAmbient(m_ambientColor[m_rankView]);
4151         m_device->SetRenderState(RENDER_STATE_LIGHTING, true);
4152 
4153         m_device->SetRenderState(RENDER_STATE_FOG, true);
4154 
4155         float fogStart = m_deepView[m_rankView] * m_fogStart[m_rankView] * m_clippingDistance;
4156         float fogEnd = m_deepView[m_rankView] * m_clippingDistance;
4157         m_device->SetFogParams(FOG_LINEAR, m_fogColor[m_rankView], fogStart, fogEnd, 1.0f);
4158 
4159         m_device->SetTransform(TRANSFORM_VIEW, m_matView);
4160 
4161         for (int objRank = 0; objRank < static_cast<int>(m_objects.size()); objRank++)
4162         {
4163             if (! m_objects[objRank].used)
4164                 continue;
4165 
4166             if (m_objects[objRank].type == ENG_OBJTYPE_TERRAIN)
4167                 continue;
4168 
4169             if (! m_objects[objRank].drawFront)
4170                 continue;
4171 
4172             m_device->SetTransform(TRANSFORM_WORLD, m_objects[objRank].transform);
4173 
4174             if (! IsVisible(objRank))
4175                 continue;
4176 
4177             int baseObjRank = m_objects[objRank].baseObjRank;
4178             if (baseObjRank == -1)
4179                 continue;
4180 
4181             assert(baseObjRank >= 0 && baseObjRank < static_cast<int>( m_baseObjects.size() ));
4182 
4183             EngineBaseObject& p1 = m_baseObjects[baseObjRank];
4184             if (! p1.used)
4185                 continue;
4186 
4187             m_lightMan->UpdateDeviceLights(m_objects[objRank].type);
4188 
4189             for (int l2 = 0; l2 < static_cast<int>( p1.next.size() ); l2++)
4190             {
4191                 EngineBaseObjTexTier& p2 = p1.next[l2];
4192 
4193                 SetTexture(p2.tex1, 0);
4194                 SetTexture(p2.tex2, 1);
4195 
4196                 for (int l3 = 0; l3 < static_cast<int>( p2.next.size() ); l3++)
4197                 {
4198                     EngineBaseObjDataTier& p3 = p2.next[l3];
4199 
4200                     SetMaterial(p3.material);
4201                     SetState(p3.state);
4202 
4203                     DrawObject(p3);
4204                 }
4205             }
4206         }
4207 
4208         m_particle->DrawParticle(SH_FRONT);  // draws the particles of the 3D world
4209 
4210         m_device->SetRenderState(RENDER_STATE_DEPTH_TEST, false);
4211         m_device->SetRenderState(RENDER_STATE_LIGHTING, false);
4212         m_device->SetRenderState(RENDER_STATE_FOG, false);
4213 
4214         m_device->SetRenderMode(RENDER_MODE_INTERFACE);
4215 
4216         SetInterfaceCoordinates();
4217     }
4218 
4219     // Draw foreground color
4220     if (m_overFront)
4221         DrawOverColor();
4222 
4223     // At the end to not overlap
4224     if (m_renderInterface)
4225         DrawHighlight();
4226 
4227     DrawTimer();
4228     DrawStats();
4229 
4230     if (m_renderInterface)
4231         DrawMouse();
4232 
4233     m_device->SetRenderMode(RENDER_MODE_NORMAL);
4234 }
4235 
UpdateGroundSpotTextures()4236 void CEngine::UpdateGroundSpotTextures()
4237 {
4238     if (!m_firstGroundSpot                                   &&
4239         m_groundMark.drawPos.x     == m_groundMark.pos.x     &&
4240         m_groundMark.drawPos.z     == m_groundMark.pos.z     &&
4241         m_groundMark.drawRadius    == m_groundMark.radius    &&
4242         m_groundMark.drawIntensity == m_groundMark.intensity)
4243         return;
4244 
4245     for (int s = 0; s < 16; s++)
4246     {
4247         Math::Point min, max;
4248         min.x = (s%4) * 254.0f - 1.0f;  // 1 pixel cover
4249         min.y = (s/4) * 254.0f - 1.0f;
4250         max.x = min.x + 254.0f + 2.0f;
4251         max.y = min.y + 254.0f + 2.0f;
4252 
4253         bool clear = false;
4254         bool set   = false;
4255 
4256         // Calculate the area to be erased.
4257         int dot = static_cast<int>(m_groundMark.drawRadius/2.0f);
4258 
4259         float tu, tv;
4260         float cx, cy;
4261 
4262         tu = (m_groundMark.drawPos.x+1600.0f)/3200.0f;
4263         tv = (m_groundMark.drawPos.z+1600.0f)/3200.0f;  // 0..1
4264 
4265         cx = (tu*254.0f*4.0f)-0.5f;
4266         cy = (tv*254.0f*4.0f)-0.5f;
4267 
4268         if (dot == 0)
4269         {
4270             cx += 0.5f;
4271             cy += 0.5f;
4272         }
4273 
4274         float px = cx-Math::Mod(cx, 1.0f);
4275         float py = cy-Math::Mod(cy, 1.0f);  // multiple of 1
4276 
4277         if (m_firstGroundSpot ||
4278             (m_groundMark.drawRadius != 0.0f    &&
4279              px+dot >= min.x && py+dot >= min.y &&
4280              px-dot <= max.x && py-dot <= max.y))
4281         {
4282             clear = true;
4283         }
4284 
4285         // Calculate the area to draw.
4286         dot = static_cast<int>(m_groundMark.radius/2.0f);
4287 
4288         tu = (m_groundMark.pos.x+1600.0f)/3200.0f;
4289         tv = (m_groundMark.pos.z+1600.0f)/3200.0f;  // 0..1
4290 
4291         cx = (tu*254.0f*4.0f)-0.5f;
4292         cy = (tv*254.0f*4.0f)-0.5f;
4293 
4294         if ( dot == 0 )
4295         {
4296             cx += 0.5f;
4297             cy += 0.5f;
4298         }
4299 
4300         px = cx - Math::Mod(cx, 1.0f);
4301         py = cy - Math::Mod(cy, 1.0f);  // multiple of 1
4302 
4303         if (m_groundMark.draw &&
4304             px+dot >= min.x && py+dot >= min.y &&
4305             px-dot <= max.x && py-dot <= max.y)
4306         {
4307             set = true;
4308         }
4309 
4310         if (clear || set || m_debugResources || m_displayGotoImage != nullptr)
4311         {
4312             CImage shadowImg(Math::IntPoint(256, 256));
4313             shadowImg.Fill(Gfx::IntColor(255, 255, 255, 255));
4314 
4315             // Draw the new shadows.
4316             for (int i = 0; i < static_cast<int>( m_groundSpots.size() ); i++)
4317             {
4318                 if (m_groundSpots[i].used == false ||
4319                     m_groundSpots[i].radius == 0.0f)
4320                     continue;
4321 
4322                 if (m_groundSpots[i].min == 0.0f &&
4323                     m_groundSpots[i].max == 0.0f)
4324                 {
4325                     dot = static_cast<int>(m_groundSpots[i].radius/2.0f);
4326 
4327                     tu = (m_groundSpots[i].pos.x+1600.0f)/3200.0f;
4328                     tv = (m_groundSpots[i].pos.z+1600.0f)/3200.0f;  // 0..1
4329 
4330                     cx = (tu*254.0f*4.0f) - 0.5f;
4331                     cy = (tv*254.0f*4.0f) - 0.5f;
4332 
4333                     if (dot == 0)
4334                     {
4335                         cx += 0.5f;
4336                         cy += 0.5f;
4337                     }
4338 
4339                     px = cx-Math::Mod(cx, 1.0f);
4340                     py = cy-Math::Mod(cy, 1.0f);  // multiple of 1
4341 
4342                     if (px+dot < min.x || py+dot < min.y ||
4343                         px-dot > max.x || py-dot > max.y)
4344                         continue;
4345 
4346                     for (int iy = -dot; iy <= dot; iy++)
4347                     {
4348                         for (int ix =- dot; ix <= dot; ix++)
4349                         {
4350                             float ppx = px+ix;
4351                             float ppy = py+iy;
4352 
4353                             if (ppx <  min.x || ppy <  min.y ||
4354                                 ppx >= max.x || ppy >= max.y)
4355                                 continue;
4356 
4357                             float intensity;
4358                             if (dot == 0)
4359                                 intensity = 0.0f;
4360                             else
4361                                 intensity = Math::Point(ppx-cx, ppy-cy).Length()/dot;
4362 
4363                             ppx -= min.x;  // on the texture
4364                             ppy -= min.y;
4365                             Math::IntPoint pp(ppx, ppy);
4366 
4367                             Gfx::Color color = shadowImg.GetPixel(pp);
4368                             color.r *= Math::Norm(m_groundSpots[i].color.r+intensity);
4369                             color.g *= Math::Norm(m_groundSpots[i].color.g+intensity);
4370                             color.b *= Math::Norm(m_groundSpots[i].color.b+intensity);
4371                             shadowImg.SetPixel(pp, color);
4372                         }
4373                     }
4374                 }
4375                 else
4376                 {
4377                     for (int iy = 0; iy < 256; iy++)
4378                     {
4379                         for (int ix = 0; ix < 256; ix++)
4380                         {
4381                             Math::Vector pos;
4382                             pos.x = (256.0f * (s%4) + ix) * 3200.0f/1024.0f - 1600.0f;
4383                             pos.z = (256.0f * (s/4) + iy) * 3200.0f/1024.0f - 1600.0f;
4384                             pos.y = 0.0f;
4385 
4386                             float level = m_terrain->GetFloorLevel(pos, true);
4387                             if (level < m_groundSpots[i].min ||
4388                                 level > m_groundSpots[i].max)
4389                                 continue;
4390 
4391                             float intensity;
4392                             if (level > (m_groundSpots[i].max+m_groundSpots[i].min)/2.0f)
4393                                 intensity = 1.0f - (m_groundSpots[i].max-level) / m_groundSpots[i].smooth;
4394                             else
4395                                 intensity = 1.0f - (level-m_groundSpots[i].min) / m_groundSpots[i].smooth;
4396 
4397                             if (intensity < 0.0f) intensity = 0.0f;
4398 
4399                             Math::IntPoint pp(ix, iy);
4400 
4401                             Gfx::Color color = shadowImg.GetPixel(pp);
4402                             color.r *= Math::Norm(m_groundSpots[i].color.r+intensity);
4403                             color.g *= Math::Norm(m_groundSpots[i].color.g+intensity);
4404                             color.b *= Math::Norm(m_groundSpots[i].color.b+intensity);
4405                             shadowImg.SetPixel(pp, color);
4406                         }
4407                     }
4408                 }
4409             }
4410 
4411             if (set)
4412             {
4413                 dot = static_cast<int>(m_groundMark.radius/2.0f);
4414 
4415                 tu = (m_groundMark.pos.x + 1600.0f) / 3200.0f;
4416                 tv = (m_groundMark.pos.z + 1600.0f) / 3200.0f;  // 0..1
4417 
4418                 cx = (tu*254.0f*4.0f)-0.5f;
4419                 cy = (tv*254.0f*4.0f)-0.5f;
4420 
4421                 if (dot == 0)
4422                 {
4423                     cx += 0.5f;
4424                     cy += 0.5f;
4425                 }
4426 
4427                 px = cx-Math::Mod(cx, 1.0f);
4428                 py = cy-Math::Mod(cy, 1.0f);  // multiple of 1
4429 
4430                 for (int iy = -dot; iy <= dot; iy++)
4431                 {
4432                     for (int ix = -dot; ix <= dot; ix++)
4433                     {
4434                         float ppx = px+ix;
4435                         float ppy = py+iy;
4436 
4437                         if (ppx <  min.x || ppy <  min.y ||
4438                             ppx >= max.x || ppy >= max.y)
4439                             continue;
4440 
4441                         ppx -= min.x;  // on the texture
4442                         ppy -= min.y;
4443 
4444                         float intensity = 1.0f - Math::Point(ix, iy).Length() / dot;
4445                         if (intensity <= 0.0f)
4446                             continue;
4447 
4448                         intensity *= m_groundMark.intensity;
4449 
4450                         int j = (ix+dot) + (iy+dot) * m_groundMark.dx;
4451                         if (m_groundMark.table[j] == 1)  // green ?
4452                         {
4453                             Math::IntPoint pp(ppx, ppy);
4454                             Gfx::Color color = shadowImg.GetPixel(pp);
4455                             color.r *= Math::Norm(1.0f-intensity);
4456                             color.g *= 1.0f;
4457                             color.b *= Math::Norm(1.0f-intensity);
4458                             shadowImg.SetPixel(pp, color);
4459                         }
4460                         if (m_groundMark.table[j] == 2)  // red ?
4461                         {
4462                             Math::IntPoint pp(ppx, ppy);
4463                             Gfx::Color color = shadowImg.GetPixel(pp);
4464                             color.r *= 1.0f;
4465                             color.g *= Math::Norm(1.0f-intensity);
4466                             color.b *= Math::Norm(1.0f-intensity);
4467                             shadowImg.SetPixel(pp, color);
4468                         }
4469                     }
4470                 }
4471             }
4472 
4473             if (m_debugResources)
4474             {
4475                 for (float x = min.x; x < max.x; x += 1.0f)
4476                 {
4477                     for (float y = min.y; y < max.y; y += 1.0f)
4478                     {
4479                         Math::Vector pos(
4480                             x / 4.0f / 254.0f * 3200.0f - 1600.0f,
4481                             0.0f,
4482                             y / 4.0f / 254.0f * 3200.0f - 1600.0f
4483                         );
4484                         TerrainRes res = m_terrain->GetResource(pos);
4485                         Math::IntPoint p(x-min.x, y-min.y);
4486                         if (res == TR_NULL)
4487                         {
4488                             shadowImg.SetPixel(p, Gfx::Color(0.5f, 0.5f, 0.5f));
4489                             continue;
4490                         }
4491                         shadowImg.SetPixelInt(p, ResourceToColor(res));
4492                     }
4493                 }
4494             }
4495 
4496             if (m_displayGotoImage != nullptr)
4497             {
4498                 Math::IntPoint size = m_displayGotoImage->GetSize();
4499                 for (float x = min.x; x < max.x; x += 1.0f)
4500                 {
4501                     for (float y = min.y; y < max.y; y += 1.0f)
4502                     {
4503                         int px = x / 4.0f / 254.0f * size.x;
4504                         int py = y / 4.0f / 254.0f * size.y;
4505                         // This can happen because the shadow??.png textures have a 1 pixel margin around them
4506                         if (px < 0 || px >= size.x || py < 0 || py >= size.y)
4507                             continue;
4508                         shadowImg.SetPixelInt(Math::IntPoint(x-min.x, y-min.y), m_displayGotoImage->GetPixelInt(Math::IntPoint(px, py)));
4509                     }
4510                 }
4511             }
4512 
4513             std::stringstream str;
4514             str << "textures/shadow" << std::setfill('0') << std::setw(2) << s << ".png";
4515             std::string texName = str.str();
4516 
4517             CreateOrUpdateTexture(texName, &shadowImg);
4518         }
4519     }
4520 
4521     for (int i = 0; i < static_cast<int>( m_groundSpots.size() ); i++)
4522     {
4523         if (m_groundSpots[i].used == false ||
4524             m_groundSpots[i].radius == 0.0f)
4525         {
4526             m_groundSpots[i].drawRadius = 0.0f;
4527         }
4528         else
4529         {
4530             m_groundSpots[i].drawPos    = m_groundSpots[i].pos;
4531             m_groundSpots[i].drawRadius = m_groundSpots[i].radius;
4532         }
4533     }
4534 
4535     m_groundMark.drawPos       = m_groundMark.pos;
4536     m_groundMark.drawRadius    = m_groundMark.radius;
4537     m_groundMark.drawIntensity = m_groundMark.intensity;
4538 
4539     m_firstGroundSpot = false;
4540 }
4541 
DrawShadowSpots()4542 void CEngine::DrawShadowSpots()
4543 {
4544     m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, false);
4545     m_device->SetRenderState(RENDER_STATE_LIGHTING, false);
4546 
4547     Math::Matrix matrix;
4548     matrix.LoadIdentity();
4549     m_device->SetTransform(TRANSFORM_WORLD, matrix);
4550 
4551 
4552     Material material;
4553     material.diffuse = Color(1.0f, 1.0f, 1.0f);
4554     material.ambient = Color(0.5f, 0.5f, 0.5f);
4555     SetMaterial(material);
4556 
4557     // TODO: create a separate texture
4558     SetTexture("textures/effect03.png");
4559 
4560     Math::Point ts, ti;
4561 
4562     float dp = 0.5f/256.0f;
4563     ts.y = 192.0f/256.0f;
4564     ti.y = 224.0f/256.0f;
4565     ts.y += dp;
4566     ti.y -= dp;
4567 
4568     Math::Vector n(0.0f, 1.0f, 0.0f);
4569 
4570     float startDeepView = m_deepView[m_rankView] * m_fogStart[m_rankView] * m_clippingDistance;
4571     float endDeepView = m_deepView[m_rankView] * m_clippingDistance;
4572 
4573     float lastIntensity = -1.0f;
4574     for (int i = 0; i < static_cast<int>( m_shadowSpots.size() ); i++)
4575     {
4576         if (m_shadowSpots[i].hide || !m_shadowSpots[i].used)
4577             continue;
4578 
4579         Math::Vector pos = m_shadowSpots[i].pos;  // pos = center of the shadow on the ground
4580 
4581         if (m_eyePt.y == pos.y)
4582             continue;  // camera at the same level?
4583 
4584         float d = 0.0f;
4585         float D = 0.0f;
4586 
4587         // h is the height above the ground to which the shadow
4588         // will be drawn.
4589         if (m_eyePt.y > pos.y)  // camera on?
4590         {
4591             float height = m_eyePt.y-pos.y;
4592             float h = m_shadowSpots[i].radius;
4593             float max = height*0.5f;
4594             if ( h > max  )  h = max;
4595             if ( h > 4.0f )  h = 4.0f;
4596 
4597             D = Math::Distance(m_eyePt, pos);
4598             if (D >= endDeepView)
4599                 continue;
4600 
4601             d = D*h/height;
4602 
4603             pos.x += (m_eyePt.x-pos.x)*d/D;
4604             pos.z += (m_eyePt.z-pos.z)*d/D;
4605             pos.y += h;
4606         }
4607         else    // camera underneath?
4608         {
4609             float height = pos.y-m_eyePt.y;
4610             float h = m_shadowSpots[i].radius;
4611             float max = height*0.1f;
4612             if ( h > max  )  h = max;
4613             if ( h > 4.0f )  h = 4.0f;
4614 
4615             D = Math::Distance(m_eyePt, pos);
4616             if (D >= endDeepView)
4617                 continue;
4618 
4619             d = D*h/height;
4620 
4621             pos.x += (m_eyePt.x-pos.x)*d/D;
4622             pos.z += (m_eyePt.z-pos.z)*d/D;
4623             pos.y -= h;
4624         }
4625 
4626         // The hFactor decreases the intensity and size increases more
4627         // the object is high relative to the ground.
4628         float hFactor = m_shadowSpots[i].height/20.0f;
4629         if ( hFactor < 0.0f )  hFactor = 0.0f;
4630         if ( hFactor > 1.0f )  hFactor = 1.0f;
4631         hFactor = powf(1.0f-hFactor, 2.0f);
4632         if ( hFactor < 0.2f )  hFactor = 0.2f;
4633 
4634         float radius = m_shadowSpots[i].radius*1.5f;
4635         radius *= 2.0f-hFactor;  // greater if high
4636         radius *= 1.0f-d/D;  // smaller if close
4637 
4638 
4639         Math::Vector corner[4];
4640 
4641         if (m_shadowSpots[i].type == ENG_SHADOW_NORM)
4642         {
4643             corner[0].x = +radius;
4644             corner[0].z = +radius;
4645             corner[0].y = 0.0f;
4646 
4647             corner[1].x = -radius;
4648             corner[1].z = +radius;
4649             corner[1].y = 0.0f;
4650 
4651             corner[2].x = +radius;
4652             corner[2].z = -radius;
4653             corner[2].y = 0.0f;
4654 
4655             corner[3].x = -radius;
4656             corner[3].z = -radius;
4657             corner[3].y = 0.0f;
4658 
4659             ts.x =  64.0f/256.0f;
4660             ti.x =  96.0f/256.0f;
4661         }
4662         else
4663         {
4664             Math::Point rot;
4665 
4666             rot = Math::RotatePoint(-m_shadowSpots[i].angle, Math::Point(radius, radius));
4667             corner[0].x = rot.x;
4668             corner[0].z = rot.y;
4669             corner[0].y = 0.0f;
4670 
4671             rot = Math::RotatePoint(-m_shadowSpots[i].angle, Math::Point(-radius, radius));
4672             corner[1].x = rot.x;
4673             corner[1].z = rot.y;
4674             corner[1].y = 0.0f;
4675 
4676             rot = Math::RotatePoint(-m_shadowSpots[i].angle, Math::Point(radius, -radius));
4677             corner[2].x = rot.x;
4678             corner[2].z = rot.y;
4679             corner[2].y = 0.0f;
4680 
4681             rot = Math::RotatePoint(-m_shadowSpots[i].angle, Math::Point(-radius, -radius));
4682             corner[3].x = rot.x;
4683             corner[3].z = rot.y;
4684             corner[3].y = 0.0f;
4685 
4686             if (m_shadowSpots[i].type == ENG_SHADOW_WORM)
4687             {
4688                 ts.x =  96.0f/256.0f;
4689                 ti.x = 128.0f/256.0f;
4690             }
4691             else
4692             {
4693                 ts.x =  64.0f/256.0f;
4694                 ti.x =  96.0f/256.0f;
4695             }
4696         }
4697 
4698         corner[0] = Math::CrossProduct(corner[0], m_shadowSpots[i].normal);
4699         corner[1] = Math::CrossProduct(corner[1], m_shadowSpots[i].normal);
4700         corner[2] = Math::CrossProduct(corner[2], m_shadowSpots[i].normal);
4701         corner[3] = Math::CrossProduct(corner[3], m_shadowSpots[i].normal);
4702 
4703         corner[0] += pos;
4704         corner[1] += pos;
4705         corner[2] += pos;
4706         corner[3] += pos;
4707 
4708         ts.x += dp;
4709         ti.x -= dp;
4710 
4711         Vertex vertex[4] =
4712         {
4713             Vertex(corner[1], n, Math::Point(ts.x, ts.y)),
4714             Vertex(corner[0], n, Math::Point(ti.x, ts.y)),
4715             Vertex(corner[3], n, Math::Point(ts.x, ti.y)),
4716             Vertex(corner[2], n, Math::Point(ti.x, ti.y))
4717         };
4718 
4719         float intensity = (0.5f+m_shadowSpots[i].intensity*0.5f)*hFactor;
4720 
4721         // Decreases the intensity of the shade if you're in the area
4722         // between the beginning and the end of the fog.
4723         if ( D > startDeepView )
4724             intensity *= 1.0f-(D-startDeepView)/(endDeepView-startDeepView);
4725 
4726         if (intensity == 0.0f)
4727             continue;
4728 
4729         if (lastIntensity != intensity)  // intensity changed?
4730         {
4731             lastIntensity = intensity;
4732             SetState(ENG_RSTATE_TTEXTURE_WHITE, Color(intensity, intensity, intensity, intensity));
4733         }
4734 
4735         m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, 4);
4736         AddStatisticTriangle(2);
4737     }
4738 
4739     m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, true);
4740     m_device->SetRenderState(RENDER_STATE_LIGHTING, true);
4741 }
4742 
DrawBackground()4743 void CEngine::DrawBackground()
4744 {
4745     m_device->SetRenderMode(RENDER_MODE_INTERFACE);
4746 
4747     if (m_cloud->GetLevel() != 0.0f)  // clouds ?
4748     {
4749         if (m_backgroundCloudUp != m_backgroundCloudDown)  // degraded?
4750             DrawBackgroundGradient(m_backgroundCloudUp, m_backgroundCloudDown);
4751     }
4752     else
4753     {
4754         if (m_backgroundColorUp != m_backgroundColorDown)  // degraded?
4755             DrawBackgroundGradient(m_backgroundColorUp, m_backgroundColorDown);
4756     }
4757 
4758     if (m_backForce || !m_backgroundName.empty() )
4759     {
4760         DrawBackgroundImage();  // image
4761     }
4762 
4763     m_device->SetRenderMode(RENDER_MODE_NORMAL);
4764 }
4765 
DrawBackgroundGradient(const Color & up,const Color & down)4766 void CEngine::DrawBackgroundGradient(const Color& up, const Color& down)
4767 {
4768     Math::Point p1(0.0f, 0.5f);
4769     Math::Point p2(1.0f, 1.0f);
4770 
4771     Color color[3] =
4772     {
4773         up,
4774         down,
4775         Color(0.0f, 0.0f, 0.0f, 0.0f)
4776     };
4777 
4778     SetState(ENG_RSTATE_OPAQUE_COLOR);
4779 
4780     m_device->SetTransform(TRANSFORM_VIEW, m_matViewInterface);
4781     m_device->SetTransform(TRANSFORM_PROJECTION, m_matProjInterface);
4782     m_device->SetTransform(TRANSFORM_WORLD, m_matWorldInterface);
4783 
4784     VertexCol vertex[4] =
4785     {
4786         VertexCol(Math::Vector(p1.x, p1.y, 0.0f), color[1]),
4787         VertexCol(Math::Vector(p1.x, p2.y, 0.0f), color[0]),
4788         VertexCol(Math::Vector(p2.x, p1.y, 0.0f), color[1]),
4789         VertexCol(Math::Vector(p2.x, p2.y, 0.0f), color[0])
4790     };
4791 
4792     m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, 4);
4793     AddStatisticTriangle(2);
4794 }
4795 
DrawBackgroundImage()4796 void CEngine::DrawBackgroundImage()
4797 {
4798     Math::Point p1, p2;
4799     p1.x = 0.0f;
4800     p1.y = 0.0f;
4801     p2.x = 1.0f;
4802     p2.y = 1.0f;
4803 
4804     Math::Vector n = Math::Vector(0.0f, 0.0f, -1.0f);  // normal
4805 
4806     float u1, u2, v1, v2;
4807     if (m_backgroundFull)
4808     {
4809         u1 = 0.0f;
4810         v1 = 0.0f;
4811         u2 = 1.0f;
4812         v2 = 1.0f;
4813     }
4814     else
4815     {
4816         float h = 0.5f;  // visible area vertically (1=all)
4817         float a = m_eyeDirV-Math::PI*0.15f;
4818         if (a >  Math::PI     )  a -= Math::PI*2.0f;  // a = -Math::PI..Math::PI
4819         if (a >  Math::PI/4.0f)  a =  Math::PI/4.0f;
4820         if (a < -Math::PI/4.0f)  a = -Math::PI/4.0f;
4821 
4822         // Note the background covers Math::PI radians, i.e. it repeats twice per rotation!
4823         u1 = (-m_eyeDirH - GetHFovAngle()/2.0f) / Math::PI;
4824         u2 = u1 + (GetHFovAngle() / Math::PI);
4825 
4826         v1 = (1.0f-h)*(0.5f+a/(2.0f*Math::PI/4.0f))+0.1f;
4827         v2 = v1+h;
4828     }
4829 
4830     Math::Point backgroundScale;
4831     backgroundScale.x = static_cast<float>(m_backgroundTex.originalSize.x) / static_cast<float>(m_backgroundTex.size.x);
4832     backgroundScale.y = static_cast<float>(m_backgroundTex.originalSize.y) / static_cast<float>(m_backgroundTex.size.y);
4833 
4834     u2 *= backgroundScale.x;
4835     v2 *= backgroundScale.y;
4836 
4837     if (m_backgroundScale)
4838     {
4839         Math::Point scale;
4840         scale.x = static_cast<float>(m_size.x) / static_cast<float>(m_backgroundTex.originalSize.x);
4841         scale.y = static_cast<float>(m_size.y) / static_cast<float>(m_backgroundTex.originalSize.y);
4842         if (scale.x > scale.y)
4843         {
4844             scale.y /= scale.x;
4845             scale.x = 1;
4846         }
4847         else
4848         {
4849             scale.x /= scale.y;
4850             scale.y = 1;
4851         }
4852         float margin_u = (1-scale.x)/2;
4853         float margin_v = (1-scale.y)/2;
4854         margin_u *= backgroundScale.x;
4855         margin_v *= backgroundScale.y;
4856         u1 += margin_u;
4857         v1 += margin_v;
4858         u2 -= margin_u;
4859         v2 -= margin_v;
4860     }
4861 
4862     SetTexture(m_backgroundTex);
4863     SetState(ENG_RSTATE_OPAQUE_TEXTURE | ENG_RSTATE_WRAP);
4864 
4865     m_device->SetTransform(TRANSFORM_VIEW, m_matViewInterface);
4866     m_device->SetTransform(TRANSFORM_PROJECTION, m_matProjInterface);
4867     m_device->SetTransform(TRANSFORM_WORLD, m_matWorldInterface);
4868 
4869     Vertex vertex[4] =
4870     {
4871         Vertex(Math::Vector(p1.x, p1.y, 0.0f), n, Math::Point(u1, v2)),
4872         Vertex(Math::Vector(p1.x, p2.y, 0.0f), n, Math::Point(u1, v1)),
4873         Vertex(Math::Vector(p2.x, p1.y, 0.0f), n, Math::Point(u2, v2)),
4874         Vertex(Math::Vector(p2.x, p2.y, 0.0f), n, Math::Point(u2, v1))
4875     };
4876 
4877     m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, 4);
4878     AddStatisticTriangle(2);
4879 }
4880 
DrawPlanet()4881 void CEngine::DrawPlanet()
4882 {
4883     if (! m_planet->PlanetExist())
4884         return;
4885 
4886     m_device->SetRenderState(RENDER_STATE_DEPTH_WRITE, false);
4887     m_device->SetRenderState(RENDER_STATE_LIGHTING, false);
4888     m_device->SetRenderState(RENDER_STATE_FOG, false);
4889 
4890     m_device->SetTransform(TRANSFORM_VIEW, m_matViewInterface);
4891     m_device->SetTransform(TRANSFORM_PROJECTION, m_matProjInterface);
4892     m_device->SetTransform(TRANSFORM_WORLD, m_matWorldInterface);
4893 
4894     m_planet->Draw();  // draws the planets
4895 }
4896 
DrawForegroundImage()4897 void CEngine::DrawForegroundImage()
4898 {
4899     if (m_foregroundName.empty())
4900         return;
4901 
4902     Math::Vector n = Math::Vector(0.0f, 0.0f, -1.0f);  // normal
4903 
4904     Math::Point p1(0.0f, 0.0f);
4905     Math::Point p2(1.0f, 1.0f);
4906 
4907     float u1 = -m_eyeDirH/(Math::PI*0.6f)+Math::PI*0.5f;
4908     float u2 = u1+0.50f;
4909 
4910     float v1 = 0.2f;
4911     float v2 = 1.0f;
4912 
4913 
4914     Vertex vertex[4] =
4915     {
4916         Vertex(Math::Vector(p1.x, p1.y, 0.0f), n, Math::Point(u1, v2)),
4917         Vertex(Math::Vector(p1.x, p2.y, 0.0f), n, Math::Point(u1, v1)),
4918         Vertex(Math::Vector(p2.x, p1.y, 0.0f), n, Math::Point(u2, v2)),
4919         Vertex(Math::Vector(p2.x, p2.y, 0.0f), n, Math::Point(u2, v1))
4920     };
4921 
4922     SetTexture(m_foregroundTex);
4923     SetState(ENG_RSTATE_CLAMP | ENG_RSTATE_TTEXTURE_BLACK);
4924 
4925     m_device->SetRenderMode(RENDER_MODE_INTERFACE);
4926 
4927     m_device->SetTransform(TRANSFORM_VIEW, m_matViewInterface);
4928     m_device->SetTransform(TRANSFORM_PROJECTION, m_matProjInterface);
4929     m_device->SetTransform(TRANSFORM_WORLD, m_matWorldInterface);
4930 
4931     m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, 4);
4932     AddStatisticTriangle(2);
4933 
4934     m_device->SetRenderMode(RENDER_MODE_NORMAL);
4935 }
4936 
DrawOverColor()4937 void CEngine::DrawOverColor()
4938 {
4939     if ((m_overColor == Color(0.0f, 0.0f, 0.0f, 0.0f) && m_overMode == ENG_RSTATE_TCOLOR_BLACK) ||
4940         (m_overColor == Color(1.0f, 1.0f, 1.0f, 1.0f) && m_overMode == ENG_RSTATE_TCOLOR_WHITE))
4941         return;
4942 
4943     Math::Point p1(0.0f, 0.0f);
4944     Math::Point p2(1.0f, 1.0f);
4945 
4946     Color color[3] =
4947     {
4948         m_overColor,
4949         m_overColor,
4950         Color(0.0f, 0.0f, 0.0f, 0.0f)
4951     };
4952 
4953     m_device->SetRenderMode(RENDER_MODE_INTERFACE);
4954 
4955     SetState(m_overMode);
4956 
4957     m_device->SetTransform(TRANSFORM_VIEW, m_matViewInterface);
4958     m_device->SetTransform(TRANSFORM_PROJECTION, m_matProjInterface);
4959     m_device->SetTransform(TRANSFORM_WORLD, m_matWorldInterface);
4960     m_device->SetRenderState(RENDER_STATE_LIGHTING, false);
4961 
4962     VertexCol vertex[4] =
4963     {
4964         VertexCol(Math::Vector(p1.x, p1.y, 0.0f), color[1]),
4965         VertexCol(Math::Vector(p1.x, p2.y, 0.0f), color[0]),
4966         VertexCol(Math::Vector(p2.x, p1.y, 0.0f), color[1]),
4967         VertexCol(Math::Vector(p2.x, p2.y, 0.0f), color[0])
4968     };
4969 
4970     m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, 4);
4971     AddStatisticTriangle(2);
4972 
4973     m_device->SetRenderMode(RENDER_MODE_NORMAL);
4974 }
4975 
DrawHighlight()4976 void CEngine::DrawHighlight()
4977 {
4978     Math::Point min, max;
4979     min.x = 1000000.0f;
4980     min.y = 1000000.0f;
4981     max.x = -1000000.0f;
4982     max.y = -1000000.0f;
4983 
4984     int i = 0;
4985     while (m_highlightRank[i] != -1)
4986     {
4987         Math::Point omin, omax;
4988         if (GetBBox2D(m_highlightRank[i++], omin, omax))
4989         {
4990             min.x = Math::Min(min.x, omin.x);
4991             min.y = Math::Min(min.y, omin.y);
4992             max.x = Math::Max(max.x, omax.x);
4993             max.y = Math::Max(max.y, omax.y);
4994         }
4995     }
4996 
4997     if (min.x == 1000000.0f ||
4998         min.y == 1000000.0f ||
4999         max.x == -1000000.0f ||
5000         max.y == -1000000.0f)
5001     {
5002         m_highlight = false;  // not highlighted
5003     }
5004     else
5005     {
5006         m_highlightP1 = min;
5007         m_highlightP2 = max;
5008         m_highlight = true;
5009     }
5010 
5011     if (!m_highlight)
5012         return;
5013 
5014     Math::Point p1 = m_highlightP1;
5015     Math::Point p2 = m_highlightP2;
5016 
5017     int nbOut = 0;
5018     if (p1.x < 0.0f || p1.x > 1.0f) nbOut++;
5019     if (p1.y < 0.0f || p1.y > 1.0f) nbOut++;
5020     if (p2.x < 0.0f || p2.x > 1.0f) nbOut++;
5021     if (p2.y < 0.0f || p2.y > 1.0f) nbOut++;
5022     if (nbOut > 2)
5023         return;
5024 
5025     SetState(ENG_RSTATE_OPAQUE_COLOR);
5026 
5027     float d = 0.5f + sinf(m_highlightTime * 6.0f) * 0.5f;
5028     d *= (p2.x - p1.x) * 0.1f;
5029     p1.x += d;
5030     p1.y += d;
5031     p2.x -= d;
5032     p2.y -= d;
5033 
5034     Color color(1.0f, 1.0f, 0.0f);  // yellow
5035 
5036     VertexCol line[3] =
5037         {
5038             VertexCol(Math::Vector(), color),
5039             VertexCol(Math::Vector(), color),
5040             VertexCol(Math::Vector(), color)
5041         };
5042 
5043     float dx = (p2.x - p1.x) / 5.0f;
5044     float dy = (p2.y - p1.y) / 5.0f;
5045 
5046     line[0].coord = Math::Vector(p1.x, p1.y + dy, 0.0f);
5047     line[1].coord = Math::Vector(p1.x, p1.y, 0.0f);
5048     line[2].coord = Math::Vector(p1.x + dx, p1.y, 0.0f);
5049     m_device->DrawPrimitive(PRIMITIVE_LINE_STRIP, line, 3);
5050 
5051     line[0].coord = Math::Vector(p2.x - dx, p1.y, 0.0f);
5052     line[1].coord = Math::Vector(p2.x, p1.y, 0.0f);
5053     line[2].coord = Math::Vector(p2.x, p1.y + dy, 0.0f);
5054     m_device->DrawPrimitive(PRIMITIVE_LINE_STRIP, line, 3);
5055 
5056     line[0].coord = Math::Vector(p2.x, p2.y - dy, 0.0f);
5057     line[1].coord = Math::Vector(p2.x, p2.y, 0.0f);
5058     line[2].coord = Math::Vector(p2.x - dx, p2.y, 0.0f);
5059     m_device->DrawPrimitive(PRIMITIVE_LINE_STRIP, line, 3);
5060 
5061     line[0].coord = Math::Vector(p1.x + dx, p2.y, 0.0f);
5062     line[1].coord = Math::Vector(p1.x, p2.y, 0.0f);
5063     line[2].coord = Math::Vector(p1.x, p2.y - dy, 0.0f);
5064     m_device->DrawPrimitive(PRIMITIVE_LINE_STRIP, line, 3);
5065 }
5066 
DrawMouse()5067 void CEngine::DrawMouse()
5068 {
5069     MouseMode mode = m_app->GetMouseMode();
5070     if (mode != MOUSE_ENGINE && mode != MOUSE_BOTH)
5071         return;
5072 
5073     SetWindowCoordinates();
5074 
5075     Material material;
5076     material.diffuse = Color(1.0f, 1.0f, 1.0f);
5077     material.ambient = Color(0.5f, 0.5f, 0.5f);
5078 
5079     m_device->SetMaterial(material);
5080     m_device->SetTexture(0, m_miceTexture);
5081 
5082     Math::Point mousePos = CInput::GetInstancePointer()->GetMousePos();
5083     Math::IntPoint pos(mousePos.x * m_size.x, m_size.y - mousePos.y * m_size.y);
5084     pos.x -= MOUSE_TYPES.at(m_mouseType).hotPoint.x;
5085     pos.y -= MOUSE_TYPES.at(m_mouseType).hotPoint.y;
5086 
5087     Math::IntPoint shadowPos;
5088     shadowPos.x = pos.x + 4;
5089     shadowPos.y = pos.y - 3;
5090 
5091     SetState(ENG_RSTATE_TCOLOR_WHITE);
5092     DrawMouseSprite(shadowPos, MOUSE_SIZE, MOUSE_TYPES.at(m_mouseType).iconShadow);
5093 
5094     SetState(MOUSE_TYPES.at(m_mouseType).mode1);
5095     DrawMouseSprite(pos, MOUSE_SIZE, MOUSE_TYPES.at(m_mouseType).icon1);
5096 
5097     SetState(MOUSE_TYPES.at(m_mouseType).mode2);
5098     DrawMouseSprite(pos, MOUSE_SIZE, MOUSE_TYPES.at(m_mouseType).icon2);
5099 
5100     SetInterfaceCoordinates();
5101 }
5102 
DrawMouseSprite(Math::IntPoint pos,Math::IntPoint size,int icon)5103 void CEngine::DrawMouseSprite(Math::IntPoint pos, Math::IntPoint size, int icon)
5104 {
5105     if (icon == -1)
5106         return;
5107 
5108     Math::IntPoint p1 = pos;
5109     Math::IntPoint p2 = p1 + size;
5110 
5111     float u1 = (32.0f / 256.0f) * (icon % 8);
5112     float v1 = (32.0f / 256.0f) * (icon / 8);
5113     float u2 = u1 + (32.0f / 256.0f);
5114     float v2 = v1 + (32.0f / 256.0f);
5115 
5116     float dp = 0.5f / 256.0f;
5117     u1 += dp;
5118     v1 += dp;
5119     u2 -= dp;
5120     v2 -= dp;
5121 
5122     Math::Vector normal(0.0f, 0.0f, -1.0f);
5123 
5124     Vertex vertex[4] =
5125     {
5126         Vertex(Math::Vector(p1.x, p2.y, 0.0f), normal, Math::Point(u1, v2)),
5127         Vertex(Math::Vector(p1.x, p1.y, 0.0f), normal, Math::Point(u1, v1)),
5128         Vertex(Math::Vector(p2.x, p2.y, 0.0f), normal, Math::Point(u2, v2)),
5129         Vertex(Math::Vector(p2.x, p1.y, 0.0f), normal, Math::Point(u2, v1))
5130     };
5131 
5132     m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, 4);
5133     AddStatisticTriangle(2);
5134 }
5135 
DrawStats()5136 void CEngine::DrawStats()
5137 {
5138     if (!m_showStats)
5139         return;
5140 
5141     float height = m_text->GetAscent(FONT_COMMON, 13.0f);
5142     float width = 0.4f;
5143     const int TOTAL_LINES = 22;
5144 
5145     Math::Point pos(0.05f * m_size.x/m_size.y, 0.05f + TOTAL_LINES * height);
5146 
5147     SetState(ENG_RSTATE_TCOLOR_ALPHA);
5148 
5149     Gfx::Color black(0.0f, 0.0f, 0.0f, 0.75f);
5150 
5151     Math::Point margin = Math::Point(5.f / m_size.x, 5.f / m_size.y);
5152 
5153     VertexCol vertex[4] =
5154     {
5155         VertexCol(Math::Vector(pos.x         - margin.x, pos.y - (TOTAL_LINES + 1) * height - margin.y, 0.0f), black),
5156         VertexCol(Math::Vector(pos.x         - margin.x, pos.y + height                     + margin.y, 0.0f), black),
5157         VertexCol(Math::Vector(pos.x + width + margin.x, pos.y - (TOTAL_LINES + 1) * height - margin.y, 0.0f), black),
5158         VertexCol(Math::Vector(pos.x + width + margin.x, pos.y + height                     + margin.y, 0.0f), black)
5159     };
5160 
5161     m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, 4);
5162 
5163     SetState(ENG_RSTATE_TEXT);
5164 
5165     auto drawStatsLine = [&](const std::string& name, const std::string& value, const std::string& value2)
5166     {
5167         if (!name.empty())
5168             m_text->DrawText(name+":", FONT_COMMON, 12.0f, pos, 1.0f, TEXT_ALIGN_LEFT, 0, Color(1.0f, 1.0f, 1.0f, 1.0f));
5169         pos.x += 0.25f;
5170         if (!value.empty())
5171             m_text->DrawText(value, FONT_COMMON, 12.0f, pos, 1.0f, TEXT_ALIGN_LEFT, 0, Color(1.0f, 1.0f, 1.0f, 1.0f));
5172         pos.x += 0.15f;
5173         if (!value2.empty())
5174             m_text->DrawText(value2, FONT_COMMON, 12.0f, pos, 1.0f, TEXT_ALIGN_RIGHT, 0, Color(1.0f, 1.0f, 1.0f, 1.0f));
5175         pos.x -= 0.4f;
5176         pos.y -= height;
5177     };
5178 
5179     auto drawStatsValue = [&](const std::string& name, long long time)
5180     {
5181         float value = static_cast<float>(time)/CProfiler::GetPerformanceCounterTime(PCNT_ALL);
5182         drawStatsLine(name, StrUtils::Format("%.2f", value), StrUtils::Format("%.2f ms", time/1e6f));
5183     };
5184 
5185     auto drawStatsCounter = [&](const std::string& name, PerformanceCounter counter)
5186     {
5187         drawStatsValue(name, CProfiler::GetPerformanceCounterTime(counter));
5188     };
5189 
5190     // TODO: Find a more generic way to calculate these in CProfiler
5191 
5192     long long engineUpdate = CProfiler::GetPerformanceCounterTime(PCNT_UPDATE_ENGINE) -
5193                              CProfiler::GetPerformanceCounterTime(PCNT_UPDATE_PARTICLE);
5194 
5195     long long gameUpdate = CProfiler::GetPerformanceCounterTime(PCNT_UPDATE_GAME) -
5196                            CProfiler::GetPerformanceCounterTime(PCNT_UPDATE_CBOT);
5197 
5198     long long otherUpdate = CProfiler::GetPerformanceCounterTime(PCNT_UPDATE_ALL) -
5199                             CProfiler::GetPerformanceCounterTime(PCNT_UPDATE_ENGINE) -
5200                             CProfiler::GetPerformanceCounterTime(PCNT_UPDATE_GAME);
5201 
5202     long long otherRender = CProfiler::GetPerformanceCounterTime(PCNT_RENDER_ALL) -
5203                             CProfiler::GetPerformanceCounterTime(PCNT_RENDER_PARTICLE_WORLD) -
5204                             CProfiler::GetPerformanceCounterTime(PCNT_RENDER_WATER) -
5205                             CProfiler::GetPerformanceCounterTime(PCNT_RENDER_TERRAIN) -
5206                             CProfiler::GetPerformanceCounterTime(PCNT_RENDER_OBJECTS) -
5207                             CProfiler::GetPerformanceCounterTime(PCNT_RENDER_INTERFACE) -
5208                             CProfiler::GetPerformanceCounterTime(PCNT_RENDER_SHADOW_MAP);
5209 
5210     drawStatsCounter("Event processing", PCNT_EVENT_PROCESSING);
5211     drawStatsLine(   "", "", "");
5212     drawStatsCounter("Frame update",      PCNT_UPDATE_ALL);
5213     drawStatsValue  ("    Engine update",     engineUpdate);
5214     drawStatsCounter("    Particle update",   PCNT_UPDATE_PARTICLE);
5215     drawStatsValue  ("    Game update",       gameUpdate);
5216     drawStatsCounter("    CBot programs",     PCNT_UPDATE_CBOT);
5217     drawStatsValue(  "    Other update",      otherUpdate);
5218     drawStatsLine(   "", "", "");
5219     drawStatsCounter("Frame render",      PCNT_RENDER_ALL);
5220     drawStatsCounter("    Particle render",   PCNT_RENDER_PARTICLE_WORLD);
5221     drawStatsCounter("    Water render",      PCNT_RENDER_WATER);
5222     drawStatsCounter("    Terrain render",    PCNT_RENDER_TERRAIN);
5223     drawStatsCounter("    Objects render",    PCNT_RENDER_OBJECTS);
5224     drawStatsCounter("    UI render",         PCNT_RENDER_INTERFACE);
5225     drawStatsCounter("        particles",     PCNT_RENDER_PARTICLE_IFACE);
5226     drawStatsCounter("    Shadow map render", PCNT_RENDER_SHADOW_MAP);
5227     drawStatsValue(  "    Other render",      otherRender);
5228     drawStatsCounter("Swap buffers & VSync",  PCNT_SWAP_BUFFERS);
5229     drawStatsLine(   "", "", "");
5230     drawStatsLine(   "Triangles",         StrUtils::ToString<int>(m_statisticTriangle), "");
5231     drawStatsLine(   "FPS",               StrUtils::Format("%.3f", m_fps), "");
5232     drawStatsLine(   "", "", "");
5233     std::stringstream str;
5234     str << std::fixed << std::setprecision(2) << m_statisticPos.x << "; " << m_statisticPos.z;
5235     drawStatsLine(   "Position",          str.str(), "");
5236 }
5237 
DrawTimer()5238 void CEngine::DrawTimer()
5239 {
5240     SetState(ENG_RSTATE_TEXT);
5241 
5242     Math::Point pos(0.98f, 0.98f-m_text->GetAscent(FONT_COMMON, 15.0f));
5243     m_text->DrawText(m_timerText, FONT_COMMON, 15.0f, pos, 1.0f, TEXT_ALIGN_RIGHT, 0, Color(1.0f, 1.0f, 1.0f, 1.0f));
5244 }
5245 
AddBaseObjTriangles(int baseObjRank,const std::vector<Gfx::ModelTriangle> & triangles)5246 void CEngine::AddBaseObjTriangles(int baseObjRank, const std::vector<Gfx::ModelTriangle>& triangles)
5247 {
5248     std::vector<VertexTex2> vs(3, VertexTex2());
5249 
5250     for (const auto& triangle : triangles)
5251     {
5252         vs[0] = triangle.p1;
5253         vs[1] = triangle.p2;
5254         vs[2] = triangle.p3;
5255 
5256         Material material;
5257         material.ambient = triangle.ambient;
5258         material.diffuse = triangle.diffuse;
5259         material.specular = triangle.specular;
5260 
5261         int state = GetEngineState(triangle);
5262 
5263         std::string tex1Name;
5264         if (!triangle.tex1Name.empty())
5265             tex1Name = "objects/" + triangle.tex1Name;
5266 
5267         std::string tex2Name;
5268         if (triangle.variableTex2)
5269             tex2Name = GetSecondTexture();
5270         else
5271             tex2Name = triangle.tex2Name;
5272 
5273         AddBaseObjTriangles(baseObjRank, vs, material, state, tex1Name, tex2Name);
5274     }
5275 }
5276 
GetEngineState(const ModelTriangle & triangle)5277 int CEngine::GetEngineState(const ModelTriangle& triangle)
5278 {
5279     int state = 0;
5280 
5281     if (!triangle.tex2Name.empty() || triangle.variableTex2)
5282         state |= ENG_RSTATE_DUAL_BLACK;
5283 
5284     switch (triangle.transparentMode)
5285     {
5286         case ModelTransparentMode::None:
5287             break;
5288 
5289         case ModelTransparentMode::AlphaChannel:
5290             state |= ENG_RSTATE_ALPHA;
5291             break;
5292 
5293         case ModelTransparentMode::MapBlackToAlpha:
5294             state |= ENG_RSTATE_TTEXTURE_BLACK;
5295             break;
5296 
5297         case ModelTransparentMode::MapWhiteToAlpha:
5298             state |= ENG_RSTATE_TTEXTURE_WHITE;
5299             break;
5300     }
5301 
5302     switch (triangle.specialMark)
5303     {
5304         case ModelSpecialMark::None:
5305             break;
5306 
5307         case ModelSpecialMark::Part1:
5308             state |= ENG_RSTATE_PART1;
5309             break;
5310 
5311         case ModelSpecialMark::Part2:
5312             state |= ENG_RSTATE_PART2;
5313             break;
5314 
5315         case ModelSpecialMark::Part3:
5316             state |= ENG_RSTATE_PART3;
5317             break;
5318     }
5319 
5320     if (triangle.doubleSided)
5321         state |= ENG_RSTATE_2FACE;
5322 
5323     return state;
5324 }
5325 
UpdateObjectShadowSpotNormal(int objRank)5326 void CEngine::UpdateObjectShadowSpotNormal(int objRank)
5327 {
5328     assert(objRank >= 0 && objRank < static_cast<int>( m_objects.size() ));
5329 
5330     int shadowRank = m_objects[objRank].shadowRank;
5331     if (shadowRank == -1)
5332         return;
5333 
5334     assert(shadowRank >= 0 && shadowRank < static_cast<int>( m_shadowSpots.size() ));
5335 
5336     // Calculating the normal to the ground in nine strategic locations,
5337     // then perform a weighted average (the dots in the center are more important).
5338 
5339     Math::Vector pos = m_shadowSpots[shadowRank].pos;
5340     float radius = m_shadowSpots[shadowRank].radius;
5341 
5342     Math::Vector n[20];
5343     Math::Vector norm;
5344     int i = 0;
5345 
5346     m_terrain->GetNormal(norm, pos);
5347     n[i++] = norm;
5348     n[i++] = norm;
5349     n[i++] = norm;
5350 
5351     Math::Vector shPos = pos;
5352     shPos.x += radius*0.6f;
5353     shPos.z += radius*0.6f;
5354     m_terrain->GetNormal(norm, shPos);
5355     n[i++] = norm;
5356     n[i++] = norm;
5357 
5358     shPos = pos;
5359     shPos.x -= radius*0.6f;
5360     shPos.z += radius*0.6f;
5361     m_terrain->GetNormal(norm, shPos);
5362     n[i++] = norm;
5363     n[i++] = norm;
5364 
5365     shPos = pos;
5366     shPos.x += radius*0.6f;
5367     shPos.z -= radius*0.6f;
5368     m_terrain->GetNormal(norm, shPos);
5369     n[i++] = norm;
5370     n[i++] = norm;
5371 
5372     shPos = pos;
5373     shPos.x -= radius*0.6f;
5374     shPos.z -= radius*0.6f;
5375     m_terrain->GetNormal(norm, shPos);
5376     n[i++] = norm;
5377     n[i++] = norm;
5378 
5379     shPos = pos;
5380     shPos.x += radius;
5381     shPos.z += radius;
5382     m_terrain->GetNormal(norm, shPos);
5383     n[i++] = norm;
5384 
5385     shPos = pos;
5386     shPos.x -= radius;
5387     shPos.z += radius;
5388     m_terrain->GetNormal(norm, shPos);
5389     n[i++] = norm;
5390 
5391     shPos = pos;
5392     shPos.x += radius;
5393     shPos.z -= radius;
5394     m_terrain->GetNormal(norm, shPos);
5395     n[i++] = norm;
5396 
5397     shPos = pos;
5398     shPos.x -= radius;
5399     shPos.z -= radius;
5400     m_terrain->GetNormal(norm, shPos);
5401     n[i++] = norm;
5402 
5403     norm.LoadZero();
5404     for (int j = 0; j < i; j++)
5405     {
5406         norm += n[j];
5407     }
5408     norm /= static_cast<float>(i);  // average vector
5409 
5410     m_shadowSpots[shadowRank].normal = norm;
5411 }
5412 
AddStaticMesh(const std::string & key,const CModelMesh * mesh,const Math::Matrix & worldMatrix)5413 int CEngine::AddStaticMesh(const std::string& key, const CModelMesh* mesh, const Math::Matrix& worldMatrix)
5414 {
5415     int baseObjRank = -1;
5416 
5417     auto it = m_staticMeshBaseObjects.find(key);
5418     if (it == m_staticMeshBaseObjects.end())
5419     {
5420         baseObjRank = CreateBaseObject();
5421         AddBaseObjTriangles(baseObjRank, mesh->GetTriangles());
5422         m_staticMeshBaseObjects[key] = baseObjRank;
5423     }
5424     else
5425     {
5426         baseObjRank = it->second;
5427     }
5428 
5429     int objRank = CreateObject();
5430     SetObjectBaseRank(objRank, baseObjRank);
5431     SetObjectTransform(objRank, worldMatrix);
5432     SetObjectType(objRank, ENG_OBJTYPE_FIX);
5433 
5434     return objRank;
5435 }
5436 
AddStaticMeshShadowSpot(int meshHandle,const ModelShadowSpot & shadowSpot)5437 void CEngine::AddStaticMeshShadowSpot(int meshHandle, const ModelShadowSpot& shadowSpot)
5438 {
5439     int objRank = meshHandle;
5440 
5441     CreateShadowSpot(objRank);
5442     SetObjectShadowSpotRadius(objRank, shadowSpot.radius);
5443     SetObjectShadowSpotIntensity(objRank, shadowSpot.intensity);
5444     SetObjectShadowSpotType(objRank, ENG_SHADOW_NORM);
5445     SetObjectShadowSpotHeight(objRank, 0.0f);
5446     SetObjectShadowSpotAngle(objRank, 0.0f);
5447     UpdateObjectShadowSpotNormal(objRank);
5448 }
5449 
DeleteStaticMesh(int meshHandle)5450 void CEngine::DeleteStaticMesh(int meshHandle)
5451 {
5452     int objRank = meshHandle;
5453 
5454     DeleteShadowSpot(objRank);
5455     DeleteObject(objRank);
5456 }
5457 
GetStaticMeshWorldMatrix(int meshHandle)5458 const Math::Matrix& CEngine::GetStaticMeshWorldMatrix(int meshHandle)
5459 {
5460     int objRank = meshHandle;
5461     return m_objects[objRank].transform;
5462 }
5463 
SetStaticMeshTransparency(int meshHandle,float value)5464 void CEngine::SetStaticMeshTransparency(int meshHandle, float value)
5465 {
5466     int objRank = meshHandle;
5467     SetObjectTransparency(objRank, value);
5468 }
5469 
SetDebugLights(bool debugLights)5470 void CEngine::SetDebugLights(bool debugLights)
5471 {
5472     m_debugLights = debugLights;
5473 }
5474 
GetDebugLights()5475 bool CEngine::GetDebugLights()
5476 {
5477     return m_debugLights;
5478 }
5479 
DebugDumpLights()5480 void CEngine::DebugDumpLights()
5481 {
5482     m_debugDumpLights = true;
5483 }
5484 
SetDebugResources(bool debugResources)5485 void CEngine::SetDebugResources(bool debugResources)
5486 {
5487     m_debugResources = debugResources;
5488     m_firstGroundSpot = true; // Force a refresh of ground spot textures
5489     UpdateGroundSpotTextures();
5490 }
5491 
GetDebugResources()5492 bool CEngine::GetDebugResources()
5493 {
5494     return m_debugResources;
5495 }
5496 
SetDebugGoto(bool debugGoto)5497 void CEngine::SetDebugGoto(bool debugGoto)
5498 {
5499     m_debugGoto = debugGoto;
5500     if (!m_debugGoto)
5501     {
5502         m_displayGotoImage.reset();
5503     }
5504 }
5505 
GetDebugGoto()5506 bool CEngine::GetDebugGoto()
5507 {
5508     return m_debugGoto;
5509 }
5510 
AddDebugGotoLine(std::vector<Gfx::VertexCol> line)5511 void CEngine::AddDebugGotoLine(std::vector<Gfx::VertexCol> line)
5512 {
5513     m_displayGoto.push_back(line);
5514 }
5515 
SetDebugGotoBitmap(std::unique_ptr<CImage> debugImage)5516 void CEngine::SetDebugGotoBitmap(std::unique_ptr<CImage> debugImage)
5517 {
5518     m_displayGotoImage = std::move(debugImage);
5519     m_firstGroundSpot = true; // Force ground spot texture reload
5520     UpdateGroundSpotTextures();
5521 }
5522 
SetInterfaceCoordinates()5523 void CEngine::SetInterfaceCoordinates()
5524 {
5525     m_device->SetTransform(TRANSFORM_VIEW,       m_matViewInterface);
5526     m_device->SetTransform(TRANSFORM_PROJECTION, m_matProjInterface);
5527     m_device->SetTransform(TRANSFORM_WORLD,      m_matWorldInterface);
5528 }
5529 
EnablePauseBlur()5530 void CEngine::EnablePauseBlur()
5531 {
5532     if (!m_pauseBlurEnabled) return;
5533 
5534     m_captureWorld = true;
5535     m_worldCaptured = false;
5536 }
5537 
DisablePauseBlur()5538 void CEngine::DisablePauseBlur()
5539 {
5540     m_captureWorld = false;
5541     m_worldCaptured = false;
5542 }
5543 
SetWindowCoordinates()5544 void CEngine::SetWindowCoordinates()
5545 {
5546     Math::Matrix matWorldWindow;
5547     matWorldWindow.LoadIdentity();
5548 
5549     Math::Matrix matViewWindow;
5550     matViewWindow.LoadIdentity();
5551 
5552     Math::Matrix matProjWindow;
5553     Math::LoadOrthoProjectionMatrix(matProjWindow, 0.0f, m_size.x, m_size.y, 0.0f, -1.0f, 1.0f);
5554 
5555     m_device->SetTransform(TRANSFORM_VIEW,       matViewWindow);
5556     m_device->SetTransform(TRANSFORM_PROJECTION, matProjWindow);
5557     m_device->SetTransform(TRANSFORM_WORLD,      matWorldWindow);
5558 }
5559 
5560 } // namespace Gfx
5561