1 /*
2 	This file is part of Warzone 2100.
3 	Copyright (C) 1999-2004  Eidos Interactive
4 	Copyright (C) 2005-2020  Warzone 2100 Project
5 
6 	Warzone 2100 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 2 of the License, or
9 	(at your option) any later version.
10 
11 	Warzone 2100 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. See the
14 	GNU General Public License for more details.
15 
16 	You should have received a copy of the GNU General Public License
17 	along with Warzone 2100; if not, write to the Free Software
18 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 /**
21  * @file lighting.c
22  * Calculates the shading values for the terrain world.
23  * The terrain intensity values are calculated at map load/creation time.
24  * - Alex McLean, Pumpkin Studios, EIDOS Interactive.
25  */
26 
27 #include "lib/framework/frame.h"
28 #include "lib/framework/math_ext.h"
29 
30 #include "lib/ivis_opengl/piestate.h"
31 #include "lib/ivis_opengl/piematrix.h"
32 #include "lib/ivis_opengl/pienormalize.h"
33 #include "lib/ivis_opengl/piepalette.h"
34 #include "lib/framework/fixedpoint.h"
35 
36 #include "lib/gamelib/gtime.h"
37 
38 #include "map.h"
39 #include "lighting.h"
40 #include "display3d.h"
41 #include "terrain.h"
42 
43 // These values determine the fog when fully zoomed in
44 // Determine these when fully zoomed in
45 #define FOG_DEPTH 1000
46 #define FOG_END 6500
47 
48 // These values are multiplied by the camera distance
49 // to obtain the optimal settings when fully zoomed out
50 // Determine these when fully zoomed out
51 #define FOG_BEGIN_SCALE 0.3
52 #define FOG_END_SCALE 0.6
53 
54 /*	The vector that holds the sun's lighting direction - planar */
55 static Vector3f theSun(0.f, 0.f, 0.f);
56 static Vector3f theSun_ForTileIllumination(0.f, 0.f, 0.f);
57 
58 /*	Module function Prototypes */
59 static UDWORD calcDistToTile(UDWORD tileX, UDWORD tileY, Vector3i *pos);
60 static void calcTileIllum(UDWORD tileX, UDWORD tileY);
61 
setTheSun(Vector3f newSun)62 void setTheSun(Vector3f newSun)
63 {
64 	Vector3f oldSun = theSun;
65 	theSun = normalise(newSun) * float(FP12_MULTIPLIER);
66 	theSun_ForTileIllumination = Vector3f(-theSun.x, -theSun.y, theSun.z);
67 	if(oldSun != theSun)
68 	{
69 		// The sun has changed - must relcalulate lighting
70 		initLighting(0, 0, mapWidth, mapHeight);
71 	}
72 }
73 
getTheSun()74 Vector3f getTheSun()
75 {
76 	return theSun;
77 }
78 
79 /*****************************************************************************/
80 /*
81  * SOURCE
82  */
83 /*****************************************************************************/
84 
85 //By passing in params - it means that if the scroll limits are changed mid-mission
86 //we can re-do over the area that hasn't been seen
initLighting(UDWORD x1,UDWORD y1,UDWORD x2,UDWORD y2)87 void initLighting(UDWORD x1, UDWORD y1, UDWORD x2, UDWORD y2)
88 {
89 	// quick check not trying to go off the map - don't need to check for < 0 since UWORD's!!
90 	if (x1 > mapWidth || x2 > mapWidth || y1 > mapHeight || y2 > mapHeight)
91 	{
92 		ASSERT(false, "initLighting: coords off edge of map");
93 		return;
94 	}
95 
96 	for (unsigned i = x1; i < x2; i++)
97 	{
98 		for (unsigned j = y1; j < y2; j++)
99 		{
100 			MAPTILE	*psTile = mapTile(i, j);
101 
102 			// always make the edge tiles dark
103 			if (i == 0 || j == 0 || i >= mapWidth - 1 || j >= mapHeight - 1)
104 			{
105 				psTile->illumination = 16;
106 			}
107 			else
108 			{
109 				calcTileIllum(i, j);
110 			}
111 			// Basically darkens down the tiles that are outside the scroll
112 			// limits - thereby emphasising the cannot-go-there-ness of them
113 			if ((SDWORD)i < scrollMinX + 4 || (SDWORD)i > scrollMaxX - 4
114 			    || (SDWORD)j < scrollMinY + 4 || (SDWORD)j > scrollMaxY - 4)
115 			{
116 				psTile->illumination /= 3;
117 			}
118 		}
119 	}
120 }
121 
122 
normalsOnTile(unsigned int tileX,unsigned int tileY,unsigned int quadrant,unsigned int * numNormals,Vector3f normals[])123 static void normalsOnTile(unsigned int tileX, unsigned int tileY, unsigned int quadrant, unsigned int *numNormals, Vector3f normals[])
124 {
125 	Vector2i tiles[2][2];
126 	MAPTILE *psTiles[2][2];
127 	Vector3f corners[2][2];
128 
129 	for (unsigned j = 0; j < 2; ++j)
130 		for (unsigned i = 0; i < 2; ++i)
131 		{
132 			tiles[i][j] = Vector2i(tileX + i, tileY + j);
133 			/* Get a pointer to our tile */
134 			/* And to the ones to the east, south and southeast of it */
135 			psTiles[i][j] = mapTile(tiles[i][j]);
136 			corners[i][j] = Vector3f(world_coord(tiles[i][j]), psTiles[i][j]->height);
137 		}
138 
139 	int flipped = TRI_FLIPPED(psTiles[0][0]) ? 10 : 0;
140 
141 	switch (quadrant + flipped)
142 	{
143 	// Not flipped.
144 	case 0:
145 	case 2:
146 		/* Otherwise, it's not flipped and so two triangles*/
147 		normals[(*numNormals)++] = pie_SurfaceNormal3fv(corners[0][0], corners[1][0], corners[1][1]);
148 		normals[(*numNormals)++] = pie_SurfaceNormal3fv(corners[0][0], corners[1][1], corners[0][1]);
149 		break;
150 	case 1:
151 		normals[(*numNormals)++] = pie_SurfaceNormal3fv(corners[0][0], corners[1][1], corners[0][1]);
152 		break;
153 	case 3:
154 		normals[(*numNormals)++] = pie_SurfaceNormal3fv(corners[0][0], corners[1][0], corners[1][1]);
155 		break;
156 	// Flipped.
157 	case 10:
158 		/* Is it flipped? In this case one triangle  */
159 		normals[(*numNormals)++] = pie_SurfaceNormal3fv(corners[1][0], corners[1][1], corners[0][1]);
160 		break;
161 	case 12:
162 		normals[(*numNormals)++] = pie_SurfaceNormal3fv(corners[0][0], corners[1][0], corners[0][1]);
163 		break;
164 	case 11:
165 	case 13:
166 		/* Is it flipped? In this case two triangles  */
167 		normals[(*numNormals)++] = pie_SurfaceNormal3fv(corners[0][0], corners[1][0], corners[0][1]);
168 		normals[(*numNormals)++] = pie_SurfaceNormal3fv(corners[1][0], corners[1][1], corners[0][1]);
169 		break;
170 	default:
171 		ASSERT(false, "Invalid quadrant in lighting code");
172 	} // end switch
173 }
174 
175 
calcTileIllum(UDWORD tileX,UDWORD tileY)176 static void calcTileIllum(UDWORD tileX, UDWORD tileY)
177 {
178 	unsigned int numNormals = 0; // How many normals have we got?
179 	Vector3f normals[8]; // Maximum 8 possible normals
180 
181 	/* Quadrants look like:-
182 
183 				  *
184 				  *
185 			0	  *    1
186 				  *
187 				  *
188 		**********V**********
189 				  *
190 				  *
191 			3	  *	   2
192 				  *
193 				  *
194 	*/
195 
196 	/* Do quadrant 0 - tile that's above and left*/
197 	normalsOnTile(tileX - 1, tileY - 1, 0, &numNormals, normals);
198 
199 	/* Do quadrant 1 - tile that's above and right*/
200 	normalsOnTile(tileX, tileY - 1, 1, &numNormals, normals);
201 
202 	/* Do quadrant 2 - tile that's down and right*/
203 	normalsOnTile(tileX, tileY, 2, &numNormals, normals);
204 
205 	/* Do quadrant 3 - tile that's down and left*/
206 	normalsOnTile(tileX - 1, tileY, 3, &numNormals, normals);
207 
208 	// The number or normals that we got is in numNormals
209 	Vector3f finalVector(0.0f, 0.0f, 0.0f);
210 	for (unsigned i = 0; i < numNormals; i++)
211 	{
212 		finalVector += normals[i];
213 	}
214 
215 	float dotProduct = glm::dot(normalise(finalVector), theSun_ForTileIllumination)/16;
216 
217 	// Primitive ambient occlusion calculation.
218 	float ao = 0;
219 	const int cx = world_coord(tileX), cy = world_coord(tileY), maxX = world_coord(mapWidth), maxY = world_coord(mapHeight);
220 	float height = map_Height(clip<int>(cx, 0, maxX), clip<int>(cy, 0, maxY));
221 	constexpr float I = 100;
222 	constexpr float H = I*0.70710678118654752440f;  // √½
223 	constexpr int Dirs = 8;
224 	constexpr float dx[Dirs] = {0, H, I,  H,  0, -H, -I, -H};  // I sin(2π dir/Dirs)
225 	constexpr float dy[Dirs] = {I, H, 0, -H, -I, -H,  0,  H};  // I cos(2π dir/Dirs)
226 
227 	for (int dir = 0; dir < Dirs; ++dir) {
228 		float maxTangent = 0;
229 		for (int dist = 1; dist < 9; ++dist) {
230 			float tangent = (map_Height(clip<int>(cx + dx[dir]*dist, 0, maxX), clip<int>(cy + dy[dir]*dist, 0, maxY)) - height)/(I*dist);
231 			maxTangent = std::max(maxTangent, tangent);
232 		}
233 		// Ambient light in this direction is proportional to the integral from tan(φ) = tangent to tan(φ) = ∞ of dφ cos(φ).
234 		// Indefinite integral is sin(φ), so definite integral is 1 - sin(atan(tangent)) = 1 - tangent/√(tangent² + 1).
235 		ao += 1 - maxTangent/sqrtf(maxTangent*maxTangent + 1);
236 	}
237 	ao *= 1.f/Dirs;
238 
239 	mapTile(tileX, tileY)->illumination = static_cast<uint8_t>(clip<int>(abs(dotProduct*ao), 1, 254));
240 }
241 
colourTile(SDWORD xIndex,SDWORD yIndex,PIELIGHT light_colour,double fraction)242 static void colourTile(SDWORD xIndex, SDWORD yIndex, PIELIGHT light_colour, double fraction)
243 {
244 	PIELIGHT colour = getTileColour(xIndex, yIndex);
245 	colour.byte.r = MIN(255, colour.byte.r + light_colour.byte.r * fraction);
246 	colour.byte.g = MIN(255, colour.byte.g + light_colour.byte.g * fraction);
247 	colour.byte.b = MIN(255, colour.byte.b + light_colour.byte.b * fraction);
248 	setTileColour(xIndex, yIndex, colour);
249 }
250 
processLight(LIGHT * psLight)251 void processLight(LIGHT *psLight)
252 {
253 	/* Firstly - there's no point processing lights that are off the grid */
254 	if (clipXY(psLight->position.x, psLight->position.z) == false)
255 	{
256 		return;
257 	}
258 
259 	const int tileX = psLight->position.x / TILE_UNITS;
260 	const int tileY = psLight->position.z / TILE_UNITS;
261 	const int rangeSkip = sqrtf(psLight->range * psLight->range * 2) / TILE_UNITS + 1;
262 
263 	/* Rough guess? */
264 	int startX = tileX - rangeSkip;
265 	int endX = tileX + rangeSkip;
266 	int startY = tileY - rangeSkip;
267 	int endY = tileY + rangeSkip;
268 
269 	/* Clip to grid limits */
270 	startX = MAX(startX, 0);
271 	endX = MAX(endX, 0);
272 	endX = MIN(endX, mapWidth - 1);
273 	startX = MIN(startX, endX);
274 	startY = MAX(startY, 0);
275 	endY = MAX(endY, 0);
276 	endY = MIN(endY, mapHeight - 1);
277 	startY = MIN(startY, endY);
278 
279 	for (int i = startX; i <= endX; i++)
280 	{
281 		for (int j = startY; j <= endY; j++)
282 		{
283 			int distToCorner = calcDistToTile(i, j, &psLight->position);
284 
285 			/* If we're inside the range of the light */
286 			if (distToCorner < (SDWORD)psLight->range)
287 			{
288 				/* Find how close we are to it */
289 				double ratio = (100.0 - PERCENT(distToCorner, psLight->range)) / 100.0;
290 				colourTile(i, j, psLight->colour, ratio);
291 			}
292 		}
293 	}
294 }
295 
296 
calcDistToTile(UDWORD tileX,UDWORD tileY,Vector3i * pos)297 static UDWORD calcDistToTile(UDWORD tileX, UDWORD tileY, Vector3i *pos)
298 {
299 	int x1, y1, z1;
300 
301 	/* The coordinates of the tile corner */
302 	x1 = tileX * TILE_UNITS;
303 	y1 = map_TileHeight(tileX, tileY);
304 	z1 = tileY * TILE_UNITS;
305 
306 	return iHypot3(x1 - pos->x, y1 - pos->y, z1 - pos->z);
307 }
308 
309 /// Sets the begin and end distance for the distance fog (mist)
310 /// It should provide maximum visibility and minimum
311 /// "popping" tiles
UpdateFogDistance(float distance)312 void UpdateFogDistance(float distance)
313 {
314 	pie_UpdateFogDistance(FOG_END - FOG_DEPTH + distance * FOG_BEGIN_SCALE, FOG_END + distance * FOG_END_SCALE);
315 }
316 
317 
318 #define MIN_DROID_LIGHT_LEVEL	96
319 #define	DROID_SEEK_LIGHT_SPEED	2
320 
calcDroidIllumination(DROID * psDroid)321 void calcDroidIllumination(DROID *psDroid)
322 {
323 	int lightVal, presVal, retVal;
324 	float adjust;
325 	const int tileX = map_coord(psDroid->pos.x);
326 	const int tileY = map_coord(psDroid->pos.y);
327 
328 	/* Are we at the edge, or even on the map */
329 	if (!tileOnMap(tileX, tileY))
330 	{
331 		psDroid->illumination = UBYTE_MAX;
332 		return;
333 	}
334 	else if (tileX <= 1 || tileX >= mapWidth - 2 || tileY <= 1 || tileY >= mapHeight - 2)
335 	{
336 		lightVal = mapTile(tileX, tileY)->illumination;
337 		lightVal += MIN_DROID_LIGHT_LEVEL;
338 	}
339 	else
340 	{
341 		lightVal = mapTile(tileX, tileY)->illumination +		 //
342 		           mapTile(tileX - 1, tileY)->illumination +	 //		 *
343 		           mapTile(tileX, tileY - 1)->illumination +	 //		***		pattern
344 		           mapTile(tileX + 1, tileY)->illumination +	 //		 *
345 		           mapTile(tileX + 1, tileY + 1)->illumination;	 //
346 		lightVal /= 5;
347 		lightVal += MIN_DROID_LIGHT_LEVEL;
348 	}
349 
350 	/* Saturation */
351 	if (lightVal > 255)
352 	{
353 		lightVal = 255;
354 	}
355 	presVal = psDroid->illumination;
356 	adjust = (float)lightVal - (float)presVal;
357 	adjust *= graphicsTimeAdjustedIncrement(DROID_SEEK_LIGHT_SPEED);
358 	retVal = presVal + adjust;
359 	if (retVal > 255)
360 	{
361 		retVal = 255;
362 	}
363 	psDroid->illumination = retVal;
364 }
365 
doBuildingLights()366 void doBuildingLights()
367 {
368 	STRUCTURE	*psStructure;
369 	UDWORD	i;
370 	LIGHT	light;
371 
372 	for (i = 0; i < MAX_PLAYERS; i++)
373 	{
374 		for (psStructure = apsStructLists[i]; psStructure; psStructure = psStructure->psNext)
375 		{
376 			light.range = psStructure->pStructureType->baseWidth * TILE_UNITS;
377 			light.position.x = psStructure->pos.x;
378 			light.position.z = psStructure->pos.y;
379 			light.position.y = map_Height(light.position.x, light.position.z);
380 			light.range = psStructure->pStructureType->baseWidth * TILE_UNITS;
381 			light.colour = pal_Colour(255, 255, 255);
382 			processLight(&light);
383 		}
384 	}
385 }
386 
387 #if 0
388 /* Experimental moving shadows code */
389 void findSunVector()
390 {
391 	Vector3f val(
392 	    4096 - getModularScaledGraphicsTime(16384, 8192),
393 	    0 - getModularScaledGraphicsTime(16384, 4096),
394 	    4096 - getModularScaledGraphicsTime(49152, 8192)
395 	);
396 
397 	setTheSun(val);
398 }
399 #endif
400