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