1 /************************************************************************************
2
3 AstroMenace
4 Hardcore 3D space scroll-shooter with spaceship upgrade possibilities.
5 Copyright (c) 2006-2019 Mikhail Kurinnoi, Viewizard
6
7
8 AstroMenace is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 AstroMenace is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with AstroMenace. If not, see <https://www.gnu.org/licenses/>.
20
21
22 Website: https://viewizard.com/
23 Project: https://github.com/viewizard/astromenace
24 E-mail: viewizard@viewizard.com
25
26 *************************************************************************************/
27
28 #include "../graphics/graphics.h"
29 #include "../math/math.h"
30 #include "light.h"
31
32 namespace viewizard {
33
34 namespace {
35
36 // no point to calculate attenuation for all scene, limit it by 10
37 constexpr float AttenuationLimit{10.0f};
38 // all lights, indexed by light's type
39 std::unordered_multimap<eLightType, std::shared_ptr<cLight>, sEnumHash> LightsMap;
40
41 } // unnamed namespace
42
43
44 /*
45 * Calculate affected lights counter and create sorted map with affected lights.
46 * Note, all attenuation-related calculations not involved in real rendering by OpenGL,
47 * and need for internal use only in order to activate (via OpenGL) proper lights.
48 */
vw_CalculateAllPointLightsAttenuation(const sVECTOR3D & Location,float Radius2,std::multimap<float,cLight * > * AffectedLightsMap)49 int vw_CalculateAllPointLightsAttenuation(const sVECTOR3D &Location, float Radius2,
50 std::multimap<float, cLight*> *AffectedLightsMap)
51 {
52 int AffectedLightsCount{0};
53
54 auto range = LightsMap.equal_range(eLightType::Point);
55 for (; range.first != range.second; ++range.first) {
56 auto &tmpLight = *range.first;
57 if (tmpLight.second->On) {
58 float tmpAttenuation = tmpLight.second->ConstantAttenuation;
59
60 // care about distance to object
61 sVECTOR3D DistV{Location.x - tmpLight.second->Location.x,
62 Location.y - tmpLight.second->Location.y,
63 Location.z - tmpLight.second->Location.z};
64 float Dist2 = DistV.x * DistV.x + DistV.y * DistV.y + DistV.z * DistV.z;
65 if (Dist2 > Radius2) {
66 Dist2 -= Radius2;
67 // Constant and Quadratic first (this is all about sqrt(), that we need for Linear)
68 tmpAttenuation += tmpLight.second->QuadraticAttenuation * Dist2;
69
70 if ((tmpAttenuation < AttenuationLimit) &&
71 (tmpLight.second->LinearAttenuation > 0.0f))
72 tmpAttenuation += tmpLight.second->LinearAttenuation * vw_sqrtf(Dist2);
73 }
74
75 if (tmpAttenuation <= AttenuationLimit) {
76 AffectedLightsCount++;
77 if (AffectedLightsMap)
78 AffectedLightsMap->emplace(tmpAttenuation, tmpLight.second.get());
79 }
80 }
81 }
82
83 return AffectedLightsCount;
84 }
85
86 /*
87 * Activate proper lights for particular object (presented by location and radius^2).
88 */
vw_CheckAndActivateAllLights(int & Type1,int & Type2,const sVECTOR3D & Location,float Radius2,int DirLimit,int PointLimit,const float (& Matrix)[16])89 int vw_CheckAndActivateAllLights(int &Type1, int &Type2, const sVECTOR3D &Location, float Radius2,
90 int DirLimit, int PointLimit, const float (&Matrix)[16])
91 {
92 Type1 = 0; // counter for directional light
93 Type2 = 0; // counter for point light
94
95 // directional light should be first, since this is the main scene light
96 auto range = LightsMap.equal_range(eLightType::Directional);
97 for (; (range.first != range.second) &&
98 (Type1 < DirLimit) &&
99 (Type1 < vw_DevCaps().MaxActiveLights); ++range.first) {
100 auto &tmpLight = *range.first;
101 if (tmpLight.second->Activate(Type1, Matrix))
102 Type1++;
103 }
104
105 // point lights
106 if (PointLimit > 0) {
107 std::multimap<float, cLight*> AffectedLightsMap;
108 // call for std::map calculation with sorted by attenuation affected lights
109 vw_CalculateAllPointLightsAttenuation(Location, Radius2, &AffectedLightsMap);
110
111 // enable lights with less attenuation first
112 for (auto &tmpLight : AffectedLightsMap) {
113 if ((Type2 >= PointLimit) ||
114 (Type1 + Type2 >= vw_DevCaps().MaxActiveLights))
115 break;
116 if (tmpLight.second->Activate(Type1 + Type2, Matrix))
117 Type2++;
118 }
119 }
120
121 vw_Lighting(true);
122 return Type1 + Type2;
123 }
124
125 /*
126 * Deactivate all lights.
127 */
vw_DeActivateAllLights()128 void vw_DeActivateAllLights()
129 {
130 for (auto &tmpLight : LightsMap) {
131 tmpLight.second->DeActivate();
132 }
133
134 vw_Lighting(false);
135 }
136
137 /*
138 * Release light.
139 */
vw_ReleaseLight(std::weak_ptr<cLight> & Light)140 void vw_ReleaseLight(std::weak_ptr<cLight> &Light)
141 {
142 if (auto sharedLight = Light.lock()) {
143 for (auto iter = LightsMap.begin(); iter != LightsMap.end(); ++iter) {
144 if (iter->second.get() == sharedLight.get()) {
145 LightsMap.erase(iter);
146 // forced to leave - current iterator invalidated by erase()
147 return;
148 }
149 }
150 }
151 }
152
153 /*
154 * Release all lights.
155 */
vw_ReleaseAllLights()156 void vw_ReleaseAllLights()
157 {
158 LightsMap.clear();
159 }
160
161 /*
162 * Create light.
163 */
vw_CreateLight(eLightType Type)164 std::weak_ptr<cLight> vw_CreateLight(eLightType Type)
165 {
166 auto Light = LightsMap.emplace(Type, std::shared_ptr<cLight>{new cLight, [](cLight *p) {delete p;}});
167 Light->second->LightType = Type;
168 return Light->second;
169 }
170
171 /*
172 * Create point light with initialization.
173 */
vw_CreatePointLight(const sVECTOR3D & Location,float R,float G,float B,float Linear,float Quadratic)174 std::weak_ptr<cLight> vw_CreatePointLight(const sVECTOR3D &Location,
175 float R, float G, float B,
176 float Linear, float Quadratic)
177 {
178 auto Light = vw_CreateLight(eLightType::Point);
179
180 if (auto sharedLight = Light.lock()) {
181 sharedLight->Diffuse[0] = sharedLight->Specular[0] = R;
182 sharedLight->Diffuse[1] = sharedLight->Specular[1] = G;
183 sharedLight->Diffuse[2] = sharedLight->Specular[2] = B;
184 sharedLight->Diffuse[3] = sharedLight->Specular[3] = 1.0f;
185 sharedLight->LinearAttenuation = sharedLight->LinearAttenuationBase = Linear;
186 sharedLight->QuadraticAttenuation = sharedLight->QuadraticAttenuationBase = Quadratic;
187 sharedLight->Location = Location;
188 }
189
190 return Light;
191 }
192
193 /*
194 * Get main direct light. Usually, first one is the main.
195 */
vw_GetMainDirectLight(std::weak_ptr<cLight> & Light)196 bool vw_GetMainDirectLight(std::weak_ptr<cLight> &Light)
197 {
198 auto tmpLight = LightsMap.find(eLightType::Directional);
199 if (tmpLight != LightsMap.end()) {
200 Light = tmpLight->second;
201 return true;
202 }
203
204 return false;
205 }
206
207 /*
208 * Activate and setup for proper light type (OpenGL-related).
209 */
Activate(int CurrentLightNum,const float (& Matrix)[16])210 bool cLight::Activate(int CurrentLightNum, const float (&Matrix)[16])
211 {
212 if (!On)
213 return false;
214 RealLightNum = CurrentLightNum;
215
216 vw_PushMatrix();
217 vw_LoadIdentity();
218 vw_SetMatrix(Matrix);
219
220 if (LightType == eLightType::Directional) {
221 float RenderDirection[4]{-Direction.x, -Direction.y, -Direction.z, 0.0f};
222 float RenderLocation[4]{-Direction.x, -Direction.y, -Direction.z, 0.0f};
223
224 // we don't reset OpenGL lights status, forced to reset everything for current light
225 vw_SetLightV(RealLightNum, eLightVParameter::DIFFUSE, Diffuse);
226 vw_SetLightV(RealLightNum, eLightVParameter::SPECULAR, Specular);
227 vw_SetLightV(RealLightNum, eLightVParameter::AMBIENT, Ambient);
228 vw_SetLightV(RealLightNum, eLightVParameter::SPOT_DIRECTION, RenderDirection);
229 vw_SetLightV(RealLightNum, eLightVParameter::POSITION, RenderLocation);
230 } else {
231 float RenderDirection[4]{0.0f, 0.0f, 0.0f, 0.0f};
232 float RenderLocation[4]{Location.x, Location.y, Location.z, 1.0f};
233
234 // we don't reset OpenGL lights status, forced to reset everything for current light
235 vw_SetLight(RealLightNum, eLightParameter::CONSTANT_ATTENUATION, ConstantAttenuation);
236 vw_SetLight(RealLightNum, eLightParameter::LINEAR_ATTENUATION, LinearAttenuation);
237 vw_SetLight(RealLightNum, eLightParameter::QUADRATIC_ATTENUATION, QuadraticAttenuation);
238
239 vw_SetLightV(RealLightNum, eLightVParameter::DIFFUSE, Diffuse);
240 vw_SetLightV(RealLightNum, eLightVParameter::SPECULAR, Specular);
241 vw_SetLightV(RealLightNum, eLightVParameter::AMBIENT, Ambient);
242 vw_SetLightV(RealLightNum, eLightVParameter::SPOT_DIRECTION, RenderDirection);
243 vw_SetLightV(RealLightNum, eLightVParameter::POSITION, RenderLocation);
244 }
245
246 vw_LightEnable(RealLightNum, true);
247 vw_PopMatrix();
248
249 return true;
250 }
251
252 /*
253 * Deactivate (OpenGL-related).
254 */
DeActivate()255 void cLight::DeActivate()
256 {
257 if (!On)
258 return;
259 if (RealLightNum > -1) {
260 vw_LightEnable(RealLightNum, false);
261 RealLightNum = -1;
262 }
263 }
264
265 /*
266 * Set location.
267 */
SetLocation(sVECTOR3D NewLocation)268 void cLight::SetLocation(sVECTOR3D NewLocation)
269 {
270 if (LightType != eLightType::Directional)
271 Location = NewLocation;
272 }
273
274 } // viewizard namespace
275