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