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 #include "graphics/engine/lightning.h"
21 
22 #include "app/app.h"
23 
24 #include "common/logger.h"
25 
26 #include "graphics/core/device.h"
27 
28 #include "graphics/engine/camera.h"
29 #include "graphics/engine/terrain.h"
30 
31 #include "level/robotmain.h"
32 
33 #include "math/geometry.h"
34 
35 #include "object/object.h"
36 #include "object/object_manager.h"
37 
38 #include "object/auto/autopowercaptor.h"
39 
40 #include "object/interface/destroyable_object.h"
41 #include "object/interface/transportable_object.h"
42 
43 #include "sound/sound.h"
44 
45 
46 // Graphics module namespace
47 namespace Gfx
48 {
49 
50 namespace
51 {
52 const int LIGHTNING_SEGMENTS_COUNT = 50;
53 } // anonymous namespace
54 
55 
CLightning(CEngine * engine)56 CLightning::CLightning(CEngine* engine)
57     : m_engine(engine),
58       m_segments(LIGHTNING_SEGMENTS_COUNT, LightningSegment())
59 {}
60 
~CLightning()61 CLightning::~CLightning()
62 {
63 }
64 
Flush()65 void CLightning::Flush()
66 {
67     m_lightningExists = false;
68     m_phase = LightningPhase::Wait;
69     m_speed = 0.0f;
70     m_progress = 0.0f;
71 
72     std::fill(m_segments.begin(), m_segments.end(), LightningSegment());
73 }
74 
EventProcess(const Event & event)75 bool CLightning::EventProcess(const Event &event)
76 {
77     if (event.type == EVENT_FRAME)
78         return EventFrame(event);
79 
80     return true;
81 }
82 
EventFrame(const Event & event)83 bool CLightning::EventFrame(const Event &event)
84 {
85     if (m_terrain == nullptr)
86         m_terrain = CRobotMain::GetInstancePointer()->GetTerrain();
87 
88     if (m_camera == nullptr)
89         m_camera = CRobotMain::GetInstancePointer()->GetCamera();
90 
91     if (m_sound == nullptr)
92         m_sound = CApplication::GetInstancePointer()->GetSound();
93 
94     if (m_engine->GetPause()) return true;
95     if (CRobotMain::GetInstancePointer()->GetMovieLock()) return true;
96 
97     m_progress += event.rTime*m_speed;
98 
99     if (m_phase == LightningPhase::Wait && m_lightningExists)
100     {
101         if (m_progress >= 1.0f)
102         {
103             m_pos.x = (Math::Rand()-0.5f)*(3200.0f-200.0f);
104             m_pos.z = (Math::Rand()-0.5f)*(3200.0f-200.0f);
105             m_pos.y = 0.0f;
106 
107             CObject* obj = SearchObject(m_pos);
108             if (obj == nullptr)
109             {
110                 m_terrain->AdjustToFloor(m_pos, true);
111             }
112             else
113             {
114                 m_pos = obj->GetPosition();
115                 m_terrain->AdjustToFloor(m_pos, true);
116 
117                 // TODO: CLightningConductorObject
118                 ObjectType type = obj->GetType();
119                 if (type == OBJECT_BASE)
120                 {
121                     m_pos.y += 120.0f;  // top of the rocket
122                 }
123                 else if (type == OBJECT_PARA)
124                 {
125                     CAutoPowerCaptor* automat = static_cast<CAutoPowerCaptor*>(obj->GetAuto());
126                     if (automat != nullptr)
127                         automat->StartLightning();
128 
129                     m_pos.y += 67.0f;  // top of lightning rod
130                 }
131                 else
132                 {
133                     assert(obj->Implements(ObjectInterfaceType::Destroyable));
134                     dynamic_cast<CDestroyableObject*>(obj)->DamageObject(DamageType::Lightning, std::numeric_limits<float>::infinity());
135                 }
136             }
137 
138             StrikeAtPos(m_pos);
139         }
140     }
141 
142     if (m_phase == LightningPhase::Flash)
143     {
144         if (m_progress < 1.0f)
145         {
146             float max = 5.0f;
147             for (std::size_t i = 0; i < m_segments.size(); i++)
148             {
149                 max += 0.4f;
150 
151                 m_segments[i].shift.x += (Math::Rand()-0.5f)*max*2.0f;
152                 if ( m_segments[i].shift.x < -max )  m_segments[i].shift.x = -max;
153                 if ( m_segments[i].shift.x >  max )  m_segments[i].shift.x =  max;
154 
155                 m_segments[i].shift.y += (Math::Rand()-0.5f)*max*2.0f;
156                 if ( m_segments[i].shift.y < -max )  m_segments[i].shift.y = -max;
157                 if ( m_segments[i].shift.y >  max )  m_segments[i].shift.y =  max;
158 
159                 m_segments[i].width += (Math::Rand()-0.5f)*2.0f;
160                 if ( m_segments[i].width < 1.0f )  m_segments[i].width = 1.0f;
161                 if ( m_segments[i].width > 6.0f )  m_segments[i].width = 6.0f;
162             }
163             m_segments[0].shift.x = 0.0f;
164             m_segments[0].shift.y = 0.0f;
165             m_segments[0].width   = 0.0f;
166         }
167         else
168         {
169             m_phase    = LightningPhase::Wait;
170             m_progress = 0.0f;
171             m_speed    = 1.0f / (1.0f+Math::Rand()*m_delay);
172         }
173     }
174 
175     return true;
176 }
177 
Create(float sleep,float delay,float magnetic)178 bool CLightning::Create(float sleep, float delay, float magnetic)
179 {
180     m_lightningExists = true;
181     if (sleep < 1.0f) sleep = 1.0f;
182     m_sleep = sleep;
183     m_delay = delay;
184     m_magnetic = magnetic;
185 
186     m_phase    = LightningPhase::Wait;
187     m_progress = 0.0f;
188     m_speed    = 1.0f / m_sleep;
189 
190     return false;
191 }
192 
GetStatus(float & sleep,float & delay,float & magnetic,float & progress)193 bool CLightning::GetStatus(float &sleep, float &delay, float &magnetic, float &progress)
194 {
195     if (! m_lightningExists) return false;
196 
197     sleep = m_sleep;
198     delay = m_delay;
199     magnetic = m_magnetic;
200     progress = m_progress;
201 
202     return true;
203 }
204 
SetStatus(float sleep,float delay,float magnetic,float progress)205 bool CLightning::SetStatus(float sleep, float delay, float magnetic, float progress)
206 {
207     m_lightningExists = true;
208 
209     m_sleep = sleep;
210     m_delay = delay;
211     m_magnetic = magnetic;
212     m_progress = progress;
213     m_phase = LightningPhase::Wait;
214     m_speed = 1.0f/m_sleep;
215 
216     return true;
217 }
218 
Draw()219 void CLightning::Draw()
220 {
221     if (m_phase != LightningPhase::Flash) return;
222 
223     CDevice* device = m_engine->GetDevice();
224 
225     Math::Matrix mat;
226     mat.LoadIdentity();
227     device->SetTransform(TRANSFORM_WORLD, mat);
228 
229     m_engine->SetTexture("textures/effect00.png");
230     m_engine->SetState(ENG_RSTATE_TTEXTURE_BLACK);
231 
232     Math::Point texInf;
233     texInf.x = 64.5f/256.0f;
234     texInf.y = 33.0f/256.0f;
235     Math::Point texSup;
236     texSup.x = 95.5f/256.0f;
237     texSup.y = 34.0f/256.0f;  // blank
238 
239     Math::Vector p1 = m_pos;
240     Math::Vector eye = m_engine->GetEyePt();
241     float a = Math::RotateAngle(eye.x-p1.x, eye.z-p1.z);
242     Math::Vector n = Math::Normalize(p1-eye);
243 
244     Math::Vector corner[4];
245     Vertex vertex[4];
246 
247     for (std::size_t i = 0; i < m_segments.size() - 1; i++)
248     {
249         Math::Vector p2 = p1;
250         p2.y += 8.0f+0.2f*i;
251 
252         Math::Point rot;
253 
254         Math::Vector p = p1;
255         p.x += m_segments[i].width;
256         rot = Math::RotatePoint(Math::Point(p1.x, p1.z), a+Math::PI/2.0f, Math::Point(p.x, p.z));
257         corner[0].x = rot.x+m_segments[i].shift.x;
258         corner[0].y = p1.y;
259         corner[0].z = rot.y+m_segments[i].shift.y;
260         rot = Math::RotatePoint(Math::Point(p1.x, p1.z), a-Math::PI/2.0f, Math::Point(p.x, p.z));
261         corner[1].x = rot.x+m_segments[i].shift.x;
262         corner[1].y = p1.y;
263         corner[1].z = rot.y+m_segments[i].shift.y;
264 
265         p = p2;
266         p.x += m_segments[i+1].width;
267         rot = Math::RotatePoint(Math::Point(p2.x, p2.z), a+Math::PI/2.0f, Math::Point(p.x, p.z));
268         corner[2].x = rot.x+m_segments[i+1].shift.x;
269         corner[2].y = p2.y;
270         corner[2].z = rot.y+m_segments[i+1].shift.y;
271         rot = Math::RotatePoint(Math::Point(p2.x, p2.z), a-Math::PI/2.0f, Math::Point(p.x, p.z));
272         corner[3].x = rot.x+m_segments[i+1].shift.x;
273         corner[3].y = p2.y;
274         corner[3].z = rot.y+m_segments[i+1].shift.y;
275 
276         if (p2.y < p1.y)
277         {
278             vertex[0] = Vertex(corner[1], n, Math::Point(texSup.x, texSup.y));
279             vertex[1] = Vertex(corner[0], n, Math::Point(texInf.x, texSup.y));
280             vertex[2] = Vertex(corner[3], n, Math::Point(texSup.x, texInf.y));
281             vertex[3] = Vertex(corner[2], n, Math::Point(texInf.x, texInf.y));
282         }
283         else
284         {
285             vertex[0] = Vertex(corner[0], n, Math::Point(texSup.x, texSup.y));
286             vertex[1] = Vertex(corner[1], n, Math::Point(texInf.x, texSup.y));
287             vertex[2] = Vertex(corner[2], n, Math::Point(texSup.x, texInf.y));
288             vertex[3] = Vertex(corner[3], n, Math::Point(texInf.x, texInf.y));
289         }
290 
291         device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, 4);
292         m_engine->AddStatisticTriangle(2);
293 
294         p1 = p2;
295     }
296 }
297 
SearchObject(Math::Vector pos)298 CObject* CLightning::SearchObject(Math::Vector pos)
299 {
300     // Lightning conductors
301     std::vector<CObject*> paraObj;
302     paraObj.reserve(100);
303     std::vector<Math::Vector> paraObjPos;
304     paraObjPos.reserve(100);
305 
306     // Seeking the object closest to the point of impact of lightning.
307     CObject* bestObj = nullptr;
308     float min = 100000.0f;
309     for (CObject* obj : CObjectManager::GetInstancePointer()->GetAllObjects())
310     {
311         if (!obj->GetDetectable()) continue;  // inactive object?
312 
313         if (IsObjectBeingTransported(obj)) continue;
314 
315         ObjectType type = obj->GetType();
316         if ( type == OBJECT_BASE ||
317              type == OBJECT_PARA )  // building a lightning effect?
318         {
319             paraObj.push_back(obj);
320             paraObjPos.push_back(obj->GetPosition());
321             continue;
322         }
323 
324         if (!obj->Implements(ObjectInterfaceType::Destroyable)) continue;
325 
326         float detect = m_magnetic * dynamic_cast<CDestroyableObject&>(*obj).GetLightningHitProbability();
327         if (detect == 0.0f) continue;
328 
329         Math::Vector oPos = obj->GetPosition();
330         float dist = Math::DistanceProjected(oPos, pos);
331         if (dist > detect) continue;
332         if (dist < min)
333         {
334             min = dist;
335             bestObj = obj;
336         }
337     }
338 
339     if (bestObj == nullptr)
340         return nullptr;  // nothing found
341 
342     // Under the protection of a lightning conductor?
343     Math::Vector oPos = bestObj->GetPosition();
344     for (int i = paraObj.size()-1; i >= 0; i--)
345     {
346         float dist = Math::DistanceProjected(oPos, paraObjPos[i]);
347         if (dist <= LTNG_PROTECTION_RADIUS)
348             return paraObj[i];
349     }
350 
351     return bestObj;
352 }
353 
354 
StrikeAtPos(Math::Vector pos)355 void CLightning::StrikeAtPos(Math::Vector pos)
356 {
357     m_pos = pos;
358 
359     Math::Vector eye = m_engine->GetEyePt();
360     float dist = Math::Distance(m_pos, eye);
361     float deep = m_engine->GetDeepView();
362 
363     if (dist < deep)
364     {
365         Math::Vector pos = eye+((m_pos-eye)*0.2f);  // like so close!
366         m_sound->Play(SOUND_BLITZ, pos);
367 
368         m_camera->StartOver(CAM_OVER_EFFECT_LIGHTNING, m_pos, 1.0f);
369 
370         m_phase    = LightningPhase::Flash;
371         m_progress = 0.0f;
372         m_speed    = 1.0f;
373     }
374 }
375 
376 } // namespace Gfx
377