1 /**
2  * @file
3  */
4 
5 /*
6 Copyright (C) 1997-2001 Id Software, Inc.
7 
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12 
13 This program 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.
16 
17 See the GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22 
23 */
24 
25 #include "lighting.h"
26 #include "bsp.h"
27 
28 #define	sun_angles			config.sun_angles[config.compile_for_day]
29 #define	sun_normal			config.sun_normal[config.compile_for_day]
30 #define	sun_color			config.sun_color[config.compile_for_day]
31 #define	sun_intensity		config.sun_intensity[config.compile_for_day]
32 #define	sun_ambient_color	config.sun_ambient_color[config.compile_for_day]
33 
34 vec3_t face_offset[MAX_MAP_FACES];		/**< for rotating bmodels */
35 
36 /** @brief lightinfo is a temporary bucket for lighting calculations */
37 typedef struct {
38 	vec_t	facedist;
39 	vec3_t	facenormal;
40 
41 	int		numsurfpt;
42 	vec3_t	*surfpt;
43 
44 	vec3_t	modelorg;		/**< for origined bmodels */
45 
46 	vec3_t	texorg;
47 	vec3_t	worldtotex[2];	/**< s = (world - texorg) . worldtotex[0] */
48 	vec3_t	textoworld[2];	/**< world = texorg + s * textoworld[0] */
49 
50 	int		texmins[2];
51 	int		texsize[2];		/**< the size of the lightmap in pixel */
52 
53 	int step; /**< step between samples in tex2world units */
54 
55 	dBspSurface_t	*face;
56 } lightinfo_t;
57 
58 /** @brief face extents */
59 typedef struct extents_s {
60 	vec3_t mins, maxs;
61 	vec3_t center;
62 	vec2_t stmins, stmaxs;
63 } extents_t;
64 
65 static extents_t face_extents[MAX_MAP_FACES];
66 
67 /**
68  * @brief Populates face_extents for all dBspSurface_t, prior to light creation.
69  * This is done so that sample positions may be nudged outward along
70  * the face normal and towards the face center to help with traces.
71  */
BuildFaceExtents(void)72 static void BuildFaceExtents (void)
73 {
74 	int k;
75 
76 	for (k = 0; k < curTile->numfaces; k++) {
77 		const dBspSurface_t* s = &curTile->faces[k];
78 		const dBspTexinfo_t* tex = &curTile->texinfo[s->texinfo];
79 
80 		float* mins = face_extents[k].mins;
81 		float* maxs = face_extents[k].maxs;
82 
83 		float* center = face_extents[k].center;
84 
85 		float* stmins = face_extents[k].stmins;
86 		float* stmaxs = face_extents[k].stmaxs;
87 		int i;
88 
89 		VectorSet(mins, 999999, 999999, 999999);
90 		VectorSet(maxs, -999999, -999999, -999999);
91 
92 		stmins[0] = stmins[1] = 999999;
93 		stmaxs[0] = stmaxs[1] = -999999;
94 
95 		for (i = 0; i < s->numedges; i++) {
96 			const int e = curTile->surfedges[s->firstedge + i];
97 			const dBspVertex_t* v;
98 			int j;
99 
100 			if (e >= 0)
101 				v = curTile->vertexes + curTile->edges[e].v[0];
102 			else
103 				v = curTile->vertexes + curTile->edges[-e].v[1];
104 
105 			for (j = 0; j < 3; j++) {  /* calculate mins, maxs */
106 				if (v->point[j] > maxs[j])
107 					maxs[j] = v->point[j];
108 				if (v->point[j] < mins[j])
109 					mins[j] = v->point[j];
110 			}
111 
112 			for (j = 0; j < 2; j++) {  /* calculate stmins, stmaxs */
113 				const float offset = tex->vecs[j][3];
114 				const float val = DotProduct(v->point, tex->vecs[j]) + offset;
115 				if (val < stmins[j])
116 					stmins[j] = val;
117 				if (val > stmaxs[j])
118 					stmaxs[j] = val;
119 			}
120 		}
121 
122 		for (i = 0; i < 3; i++)
123 			center[i] = (mins[i] + maxs[i]) / 2.0f;
124 	}
125 }
126 
127 /**
128  * @sa BuildFaceExtents
129  */
CalcLightinfoExtents(lightinfo_t * l)130 static void CalcLightinfoExtents (lightinfo_t* l)
131 {
132 	const dBspSurface_t* s;
133 	float* stmins, *stmaxs;
134 	vec2_t lm_mins, lm_maxs;
135 	int i;
136 	const int luxelScale = (1 << config.lightquant);
137 
138 	s = l->face;
139 
140 	stmins = face_extents[s - curTile->faces].stmins;
141 	stmaxs = face_extents[s - curTile->faces].stmaxs;
142 
143 	for (i = 0; i < 2; i++) {
144 		lm_mins[i] = floor(stmins[i] / luxelScale);
145 		lm_maxs[i] = ceil(stmaxs[i] / luxelScale);
146 
147 		l->texmins[i] = lm_mins[i];
148 		l->texsize[i] = lm_maxs[i] - lm_mins[i] + 1;
149 	}
150 
151 	if (l->texsize[0] * l->texsize[1] > MAX_MAP_LIGHTMAP)
152 		Sys_Error("Surface too large to light (%dx%d)", l->texsize[0], l->texsize[1]);
153 }
154 
155 /**
156  * @brief Fills in texorg, worldtotex. and textoworld
157  */
CalcLightinfoVectors(lightinfo_t * l)158 static void CalcLightinfoVectors (lightinfo_t* l)
159 {
160 	const dBspTexinfo_t* tex;
161 	int i;
162 	vec3_t texnormal;
163 	vec_t distscale, dist;
164 
165 	tex = &curTile->texinfo[l->face->texinfo];
166 
167 	for (i = 0; i < 2; i++)
168 		VectorCopy(tex->vecs[i], l->worldtotex[i]);
169 
170 	/* calculate a normal to the texture axis.  points can be moved along this
171 	 * without changing their S/T */
172 	texnormal[0] = tex->vecs[1][1] * tex->vecs[0][2]
173 					- tex->vecs[1][2] * tex->vecs[0][1];
174 	texnormal[1] = tex->vecs[1][2] * tex->vecs[0][0]
175 					- tex->vecs[1][0] * tex->vecs[0][2];
176 	texnormal[2] = tex->vecs[1][0] * tex->vecs[0][1]
177 					- tex->vecs[1][1] * tex->vecs[0][0];
178 	VectorNormalize(texnormal);
179 
180 	/* flip it towards plane normal */
181 	distscale = DotProduct(texnormal, l->facenormal);
182 	if (!distscale) {
183 		Verb_Printf(VERB_EXTRA, "WARNING: Texture axis perpendicular to face\n");
184 		distscale = 1.0;
185 	}
186 	if (distscale < 0.0) {
187 		distscale = -distscale;
188 		VectorSubtract(vec3_origin, texnormal, texnormal);
189 	}
190 
191 	/* distscale is the ratio of the distance along the texture normal to
192 	 * the distance along the plane normal */
193 	distscale = 1.0 / distscale;
194 
195 	for (i = 0; i < 2; i++) {
196 		const vec_t len = VectorLength(l->worldtotex[i]);
197 		const vec_t distance = DotProduct(l->worldtotex[i], l->facenormal) * distscale;
198 		VectorMA(l->worldtotex[i], -distance, texnormal, l->textoworld[i]);
199 		VectorScale(l->textoworld[i], (1.0f / len) * (1.0f / len), l->textoworld[i]);
200 	}
201 
202 	/* calculate texorg on the texture plane */
203 	for (i = 0; i < 3; i++)
204 		l->texorg[i] =
205 			-tex->vecs[0][3] * l->textoworld[0][i] -
206 			tex->vecs[1][3] * l->textoworld[1][i];
207 
208 	/* project back to the face plane */
209 	dist = DotProduct(l->texorg, l->facenormal) - l->facedist - 1;
210 	dist *= distscale;
211 	VectorMA(l->texorg, -dist, texnormal, l->texorg);
212 
213 	/* compensate for org'd bmodels */
214 	VectorAdd(l->texorg, l->modelorg, l->texorg);
215 
216 	/* total sample count */
217 	l->numsurfpt = l->texsize[0] * l->texsize[1];
218 	l->surfpt = Mem_AllocTypeN(vec3_t, l->numsurfpt);
219 	if (!l->surfpt)
220 		Sys_Error("Surface too large to light (" UFO_SIZE_T ")", l->numsurfpt * sizeof(*l->surfpt));
221 
222 	/* distance between samples */
223 	l->step = 1 << config.lightquant;
224 }
225 
226 /**
227  * @brief For each texture aligned grid point, back project onto the plane
228  * to get the world xyz value of the sample point
229  * @param[in,out] l The resulting lightinfo data
230  * @param[in] sofs The sample offset for the s coordinates
231  * @param[in] tofs The sample offset for the t coordinates
232  */
CalcPoints(lightinfo_t * l,float sofs,float tofs)233 static void CalcPoints (lightinfo_t* l, float sofs, float tofs)
234 {
235 	int s, t, j;
236 	int w, h, step;
237 	vec_t starts, startt;
238 	vec_t* surf;
239 
240 	/* fill in surforg
241 	 * the points are biased towards the center of the surfaces
242 	 * to help avoid edge cases just inside walls */
243 	surf = l->surfpt[0];
244 
245 	h = l->texsize[1];
246 	w = l->texsize[0];
247 
248 	step = l->step;
249 	starts = l->texmins[0] * step;
250 	startt = l->texmins[1] * step;
251 
252 	for (t = 0; t < h; t++) {
253 		for (s = 0; s < w; s++, surf += 3) {
254 			const vec_t us = starts + (s + sofs) * step;
255 			const vec_t ut = startt + (t + tofs) * step;
256 
257 			/* calculate texture point */
258 			for (j = 0; j < 3; j++)
259 				surf[j] = l->texorg[j] + l->textoworld[0][j] * us +
260 						l->textoworld[1][j] * ut;
261 		}
262 	}
263 }
264 
265 /** @brief buckets for sample accumulation - clipped by the surface */
266 typedef struct facelight_s {
267 	int numsamples;
268 	vec3_t* samples;    /**< lightmap samples */
269 	vec3_t* directions; /**< for specular lighting/bumpmapping */
270 } facelight_t;
271 
272 static facelight_t facelight[LIGHTMAP_MAX][MAX_MAP_FACES];
273 
274 /** @brief Different types of sources emitting light */
275 typedef enum {
276 	emit_surface,		/**< surface light via SURF_LIGHT */
277 	emit_point,			/**< point light given via light entity */
278 	emit_spotlight		/**< spotlight given via light entity (+target) or via light_spot entity */
279 } emittype_t;
280 
281 /** @brief a light source */
282 typedef struct light_s {
283 	struct light_s* next;	/**< next light in the chain */
284 	emittype_t	type;		/**< light type */
285 
286 	float		intensity;	/**< brightness */
287 	vec3_t		origin;		/**< the origin of the light */
288 	vec3_t		color;		/**< the color (RGB) of the light */
289 	vec3_t		normal;		/**< spotlight direction */
290 	float		stopdot;	/**< spotlights cone */
291 } light_t;
292 
293 static light_t* lights[LIGHTMAP_MAX];
294 static int numlights[LIGHTMAP_MAX];
295 
296 /**
297  * @brief Create lights out of patches and entity lights
298  * @sa LightWorld
299  * @sa BuildPatch
300  */
BuildLights(void)301 void BuildLights (void)
302 {
303 	int i;
304 	light_t* l;
305 
306 	/* surfaces */
307 	for (i = 0; i < MAX_MAP_FACES; i++) {
308 		/* iterate subdivided patches */
309 		for(const patch_t* p = face_patches[i]; p; p = p->next) {
310 			if (VectorEmpty(p->light))
311 				continue;
312 
313 			numlights[config.compile_for_day]++;
314 			l = Mem_AllocType(light_t);
315 
316 			VectorCopy(p->origin, l->origin);
317 
318 			l->next = lights[config.compile_for_day];
319 			lights[config.compile_for_day] = l;
320 
321 			l->type = emit_surface;
322 
323 			l->intensity = ColorNormalize(p->light, l->color);
324 			l->intensity *= p->area * config.surface_scale;
325 		}
326 	}
327 
328 	/* entities (skip the world) */
329 	for (i = 1; i < num_entities; i++) {
330 		float intensity;
331 		const char* color;
332 		const char* target;
333 		const entity_t* e = &entities[i];
334 		const char* name = ValueForKey(e, "classname");
335 		if (!Q_strstart(name, "light"))
336 			continue;
337 
338 		/* remove those lights that are only for the night version */
339 		if (config.compile_for_day) {
340 			const int spawnflags = atoi(ValueForKey(e, "spawnflags"));
341 			if (!(spawnflags & 1))	/* day */
342 				continue;
343 		}
344 
345 		numlights[config.compile_for_day]++;
346 		l = Mem_AllocType(light_t);
347 
348 		GetVectorForKey(e, "origin", l->origin);
349 
350 		/* link in */
351 		l->next = lights[config.compile_for_day];
352 		lights[config.compile_for_day] = l;
353 
354 		intensity = FloatForKey(e, "light");
355 		if (!intensity)
356 			intensity = 300.0;
357 		color = ValueForKey(e, "_color");
358 		if (color && color[0] != '\0'){
359 			if (sscanf(color, "%f %f %f", &l->color[0], &l->color[1], &l->color[2]) != 3)
360 				Sys_Error("Invalid _color entity property given: %s", color);
361 			ColorNormalize(l->color, l->color);
362 		} else
363 			VectorSet(l->color, 1.0, 1.0, 1.0);
364 		l->intensity = intensity * config.entity_scale;
365 		l->type = emit_point;
366 
367 		target = ValueForKey(e, "target");
368 		if (target[0] != '\0' || Q_streq(name, "light_spot")) {
369 			l->type = emit_spotlight;
370 			l->stopdot = FloatForKey(e, "_cone");
371 			if (!l->stopdot)
372 				l->stopdot = 10;
373 			l->stopdot = cos(l->stopdot * torad);
374 			if (target[0] != '\0') {	/* point towards target */
375 				entity_t* e2 = FindTargetEntity(target);
376 				if (!e2)
377 					Com_Printf("WARNING: light at (%i %i %i) has missing target '%s' - e.g. create an info_null that has a 'targetname' set to '%s'\n",
378 						(int)l->origin[0], (int)l->origin[1], (int)l->origin[2], target, target);
379 				else {
380 					vec3_t dest;
381 					GetVectorForKey(e2, "origin", dest);
382 					VectorSubtract(dest, l->origin, l->normal);
383 					VectorNormalize(l->normal);
384 				}
385 			} else {	/* point down angle */
386 				const float angle = FloatForKey(e, "angle");
387 				if (angle == ANGLE_UP) {
388 					l->normal[0] = l->normal[1] = 0.0;
389 					l->normal[2] = 1.0;
390 				} else if (angle == ANGLE_DOWN) {
391 					l->normal[0] = l->normal[1] = 0.0;
392 					l->normal[2] = -1.0;
393 				} else {
394 					l->normal[2] = 0;
395 					l->normal[0] = cos(angle * torad);
396 					l->normal[1] = sin(angle * torad);
397 				}
398 			}
399 		}
400 	}
401 
402 	/* handle worldspawn light settings */
403 	{
404 		const entity_t* e = &entities[0];
405 		const char* ambient, *light, *angles, *color;
406 		float f;
407 		int i;
408 
409 		if (config.compile_for_day) {
410 			ambient = ValueForKey(e, "ambient_day");
411 			light = ValueForKey(e, "light_day");
412 			angles = ValueForKey(e, "angles_day");
413 			color = ValueForKey(e, "color_day");
414 		} else {
415 			ambient = ValueForKey(e, "ambient_night");
416 			light = ValueForKey(e, "light_night");
417 			angles = ValueForKey(e, "angles_night");
418 			color = ValueForKey(e, "color_night");
419 		}
420 
421 		if (light[0] != '\0')
422 			sun_intensity = atoi(light);
423 
424 		if (angles[0] != '\0') {
425 			VectorClear(sun_angles);
426 			if (sscanf(angles, "%f %f", &sun_angles[0], &sun_angles[1]) != 2)
427 				Sys_Error("wrong angles values given: '%s'", angles);
428 			AngleVectors(sun_angles, sun_normal, nullptr, nullptr);
429 		}
430 
431 		if (color[0] != '\0') {
432 			GetVectorFromString(color, sun_color);
433 			ColorNormalize(sun_color, sun_color);
434 		}
435 
436 		if (ambient[0] != '\0')
437 			GetVectorFromString(ambient, sun_ambient_color);
438 
439 		/* optionally pull brightness from worldspawn */
440 		f = FloatForKey(e, "brightness");
441 		if (f > 0.0)
442 			config.brightness = f;
443 
444 		/* saturation as well */
445 		f = FloatForKey(e, "saturation");
446 		if (f > 0.0)
447 			config.saturation = f;
448 		else
449 			Verb_Printf(VERB_EXTRA, "Invalid saturation setting (%f) in worldspawn found\n", f);
450 
451 		f = FloatForKey(e, "contrast");
452 		if (f > 0.0)
453 			config.contrast = f;
454 		else
455 			Verb_Printf(VERB_EXTRA, "Invalid contrast setting (%f) in worldspawn found\n", f);
456 
457 		/* lightmap resolution downscale (e.g. 4 = 1 << 4) */
458 		i = atoi(ValueForKey(e, "quant"));
459 		if (i >= 1 && i <= 6)
460 			config.lightquant = i;
461 		else
462 			Verb_Printf(VERB_EXTRA, "Invalid quant setting (%i) in worldspawn found\n", i);
463 	}
464 
465 	Verb_Printf(VERB_EXTRA, "light settings:\n * intensity: %i\n * sun_angles: pitch %f yaw %f\n * sun_color: %f:%f:%f\n * sun_ambient_color: %f:%f:%f\n",
466 		sun_intensity, sun_angles[0], sun_angles[1], sun_color[0], sun_color[1], sun_color[2], sun_ambient_color[0], sun_ambient_color[1], sun_ambient_color[2]);
467 	Verb_Printf(VERB_NORMAL, "%i direct lights for %s lightmap\n", numlights[config.compile_for_day], (config.compile_for_day ? "day" : "night"));
468 }
469 
470 /**
471  * @brief Checks traces against a single-tile map, optimized for ufo2map. This trace is only for visible levels.
472  * @param[in] start The position to start the trace.
473  * @param[in] stop The position where the trace ends.
474  * @sa TR_TestLine
475  * @sa GatherSampleLight
476  * @return false if not blocked
477  */
TR_TestLineSingleTile(const vec3_t start,const vec3_t stop,int * headhint)478 static bool TR_TestLineSingleTile (const vec3_t start, const vec3_t stop, int* headhint)
479 {
480 	int i;
481 	static int shared_lastthead = 0;
482 	int lastthead = *headhint;
483 
484 	if (!lastthead) {
485 		lastthead = shared_lastthead;
486 		*headhint = lastthead;
487 	}
488 
489 	assert(mapTiles.numTiles == 1);
490 
491 	/* ufo2map does many traces to the same endpoint.
492 	 * Often an occluding node will be found in the same thead
493 	 * as the last trace, so test that one first. */
494 	if (curTile->theadlevel[lastthead] <= LEVEL_LASTLIGHTBLOCKING
495 		&& TR_TestLine_r(curTile, curTile->thead[lastthead], start, stop))
496 		return true;
497 
498 	for (i = 0; i < curTile->numtheads; i++) {
499 		const int level = curTile->theadlevel[i];
500 		if (i == lastthead)
501 			continue;
502 		if (level > LEVEL_LASTLIGHTBLOCKING)
503 			continue;
504 		if (TR_TestLine_r(curTile, curTile->thead[i], start, stop)) {
505 			shared_lastthead = *headhint = i;
506 			return true;
507 		}
508 	}
509 	return false;
510 }
511 
512 /**
513  * @brief A follow-up to GatherSampleLight, simply trace along the sun normal, adding sunlight
514  */
GatherSampleSunlight(const vec3_t pos,const vec3_t normal,float * sample,float * direction,float scale,int * headhint)515 static void GatherSampleSunlight (const vec3_t pos, const vec3_t normal, float* sample, float* direction, float scale, int* headhint)
516 {
517 	vec3_t delta;
518 	float dot, light;
519 
520 	if (!sun_intensity)
521 		return;
522 
523 	dot = DotProduct(sun_normal, normal);
524 	if (dot <= 0.001)
525 		return; /* wrong direction */
526 
527 	/* don't use only 512 (which would be the 8 level max unit) but a
528 	 * higher value - because the light angle is not fixed at 90 degree */
529 	VectorMA(pos, 8192, sun_normal, delta);
530 
531 	if (TR_TestLineSingleTile(pos, delta, headhint))
532 		return; /* occluded */
533 
534 	light = sun_intensity * dot;
535 	if (light > 255)
536 		light = 255;
537 	light *= scale;
538 
539 	/* add some light to it */
540 	VectorMA(sample, light, sun_color, sample);
541 
542 	/* and accumulate the direction */
543 	VectorMix(normal, sun_normal, light / sun_intensity, delta);
544 	VectorMA(direction, light * scale, delta, direction);
545 }
546 
547 
548 /**
549  * @param[out] sample The sample color
550  * @param[in] normal The light direction (normal vector)
551  * @param[in] pos The point in the world that receives the light
552  * @param[in] scale is the normalizer for multisampling
553  * @param[in,out] headhints An array of theads for each light to optimize the tracing
554  */
GatherSampleLight(vec3_t pos,const vec3_t normal,float * sample,float * direction,float scale,int * headhints)555 static void GatherSampleLight (vec3_t pos, const vec3_t normal, float* sample, float* direction, float scale, int* headhints)
556 {
557 	light_t* l;
558 	vec3_t delta;
559 	int* headhint;
560 
561 	for (l = lights[config.compile_for_day], headhint = headhints; l; l = l->next, headhint++) {
562 		float light = 0.0;
563 		float dot2;
564 
565 		/* Com_Printf("Looking with next hint.\n"); */
566 
567 		VectorSubtract(l->origin, pos, delta);
568 		float dist = VectorNormalize(delta);
569 
570 		float dot = DotProduct(delta, normal);
571 		if (dot <= 0.001)
572 			continue;	/* behind sample surface */
573 
574 		switch (l->type) {
575 		case emit_point:
576 			/* linear falloff */
577 			light = (l->intensity - dist) * dot;
578 			break;
579 
580 		case emit_surface:
581 			/* exponential falloff */
582 			light = (l->intensity / (dist * dist)) * dot;
583 			break;
584 
585 		case emit_spotlight:
586 			/* linear falloff with cone */
587 			dot2 = -DotProduct(delta, l->normal);
588 			if (dot2 > l->stopdot) {
589 				/* inside the cone */
590 				light = (l->intensity - dist) * dot;
591 			} else {
592 				/* outside the cone */
593 				light = (l->intensity * (dot2 / l->stopdot) - dist) * dot;
594 			}
595 			break;
596 		default:
597 			Sys_Error("Bad l->type");
598 		}
599 
600 		if (light <= 0.5)  /* almost no light */
601 			continue;
602 
603 		if (TR_TestLineSingleTile(pos, l->origin, headhint))
604 			continue;	/* occluded */
605 
606 		if (light > 255)
607 			light = 255;
608 		/* add some light to it */
609 		VectorMA(sample, light * scale, l->color, sample);
610 
611 		/* and add some direction */
612 		VectorMix(normal, delta, 2.0 * light / l->intensity, delta);
613 		VectorMA(direction, light * scale, delta, direction);
614 	}
615 
616 	/* Com_Printf("Looking with last hint.\n"); */
617 	GatherSampleSunlight(pos, normal, sample, direction, scale, headhint);
618 }
619 
620 #define SAMPLE_NUDGE 0.25
621 
622 /**
623  * @brief Move the incoming sample position towards the surface center and along the
624  * surface normal to reduce false-positive traces.
625  */
NudgeSamplePosition(const vec3_t in,const vec3_t normal,const vec3_t center,vec3_t out)626 static inline void NudgeSamplePosition (const vec3_t in, const vec3_t normal, const vec3_t center, vec3_t out)
627 {
628 	vec3_t dir;
629 
630 	VectorCopy(in, out);
631 
632 	/* move into the level using the normal and surface center */
633 	VectorSubtract(out, center, dir);
634 	VectorNormalize(dir);
635 
636 	VectorMA(out, SAMPLE_NUDGE, dir, out);
637 	VectorMA(out, SAMPLE_NUDGE, normal, out);
638 }
639 
640 #define MAX_VERT_FACES 256
641 
642 /**
643  * @brief Populate faces with indexes of all dBspFace_t's referencing the specified edge.
644  * @param[out] nfaces The number of dBspFace_t's referencing edge
645  */
FacesWithVert(int vert,int * faces,int * nfaces)646 static void FacesWithVert (int vert, int* faces, int* nfaces)
647 {
648 	int i, j, k;
649 
650 	k = 0;
651 	for (i = 0; i < curTile->numfaces; i++) {
652 		const dBspSurface_t* face = &curTile->faces[i];
653 		const dBspTexinfo_t* tex = &curTile->texinfo[face->texinfo];
654 
655 		/* only build vertex normals for phong shaded surfaces */
656 		if (!(tex->surfaceFlags & SURF_PHONG))
657 			continue;
658 
659 		for (j = 0; j < face->numedges; j++) {
660 			const int e = curTile->surfedges[face->firstedge + j];
661 			const int v = e >= 0 ? curTile->edges[e].v[0] : curTile->edges[-e].v[1];
662 
663 			/* face references vert */
664 			if (v == vert) {
665 				faces[k++] = i;
666 				if (k == MAX_VERT_FACES)
667 					Sys_Error("MAX_VERT_FACES");
668 				break;
669 			}
670 		}
671 	}
672 	*nfaces = k;
673 }
674 
675 /**
676  * @brief Calculate per-vertex (instead of per-plane) normal vectors. This is done by
677  * finding all of the faces which share a given vertex, and calculating a weighted
678  * average of their normals.
679  */
BuildVertexNormals(void)680 void BuildVertexNormals (void)
681 {
682 	int vert_faces[MAX_VERT_FACES];
683 	int num_vert_faces;
684 	vec3_t norm, delta;
685 	float scale;
686 	int i, j;
687 
688 	BuildFaceExtents();
689 
690 	for (i = 0; i < curTile->numvertexes; i++) {
691 		VectorClear(curTile->normals[i].normal);
692 
693 		FacesWithVert(i, vert_faces, &num_vert_faces);
694 		if (!num_vert_faces)  /* rely on plane normal only */
695 			continue;
696 
697 		for (j = 0; j < num_vert_faces; j++) {
698 			const dBspSurface_t* face = &curTile->faces[vert_faces[j]];
699 			const dBspPlane_t* plane = &curTile->planes[face->planenum];
700 			extents_t* extends = &face_extents[vert_faces[j]];
701 
702 			/* scale the contribution of each face based on size */
703 			VectorSubtract(extends->maxs, extends->mins, delta);
704 			scale = VectorLength(delta);
705 
706 			if (face->side)
707 				VectorScale(plane->normal, -scale, norm);
708 			else
709 				VectorScale(plane->normal, scale, norm);
710 
711 			VectorAdd(curTile->normals[i].normal, norm, curTile->normals[i].normal);
712 		}
713 		VectorNormalize(curTile->normals[i].normal);
714 	}
715 }
716 
717 
718 /**
719  * @brief For Phong-shaded samples, interpolate the vertex normals for the surface in
720  * question, weighting them according to their proximity to the sample position.
721  * @todo Implement it (just clones the normal of nearest vertex for now)
722  */
SampleNormal(const lightinfo_t * l,const vec3_t pos,vec3_t normal)723 static void SampleNormal (const lightinfo_t* l, const vec3_t pos, vec3_t normal)
724 {
725 	vec3_t temp;
726 	float dist[MAX_VERT_FACES];
727 	float nearest;
728 	int i, v, nearv;
729 
730 	nearest = 9999.0;
731 	nearv = 0;
732 
733 	/* calculate the distance to each vertex */
734 	for (i = 0; i < l->face->numedges; i++) {  /* find nearest and farthest verts */
735 		const int e = curTile->surfedges[l->face->firstedge + i];
736 		if (e >= 0)
737 			v = curTile->edges[e].v[0];
738 		else
739 			v = curTile->edges[-e].v[1];
740 
741 		VectorSubtract(pos, curTile->vertexes[v].point, temp);
742 		dist[i] = VectorLength(temp);
743 		if (dist[i] < nearest) {
744 			nearest = dist[i];
745 			nearv = v;
746 		}
747 	}
748 	VectorCopy(curTile->normals[nearv].normal, normal);
749 }
750 
751 
752 #define MAX_SAMPLES 5
753 #define SOFT_SAMPLES 4
754 static const float sampleofs[2][MAX_SAMPLES][2] = {
755 	{{0.0, 0.0}, {-0.125, -0.125}, {0.125, -0.125}, {0.125, 0.125}, {-0.125, 0.125}},
756 	{{-0.66, 0.33}, {-0.33, -0.66}, {0.33, 0.66}, {0.66, -0.33},{0.0,0.0}}
757 };
758 
759 /**
760  * @brief
761  * @sa FinalLightFace
762  */
BuildFacelights(unsigned int facenum)763 void BuildFacelights (unsigned int facenum)
764 {
765 	dBspSurface_t* face;
766 	dBspPlane_t* plane;
767 	dBspTexinfo_t* tex;
768 	float* center;
769 	float* sdir, *tdir;
770 	vec3_t normal, binormal;
771 	vec4_t tangent;
772 	lightinfo_t li;
773 	float scale;
774 	int i, j, numsamples;
775 	facelight_t* fl;
776 	int* headhints;
777 	const int grid_type = config.soft ? 1 : 0;
778 
779 	if (facenum >= MAX_MAP_FACES) {
780 		Com_Printf("MAX_MAP_FACES hit\n");
781 		return;
782 	}
783 
784 	face = &curTile->faces[facenum];
785 	plane = &curTile->planes[face->planenum];
786 	tex = &curTile->texinfo[face->texinfo];
787 
788 	if (tex->surfaceFlags & SURF_WARP)
789 		return;		/* non-lit texture */
790 
791 	sdir = tex->vecs[0];
792 	tdir = tex->vecs[1];
793 
794 	/* lighting -extra antialiasing */
795 	if (config.extrasamples)
796 		numsamples = config.soft ? SOFT_SAMPLES : MAX_SAMPLES;
797 	else
798 		numsamples = 1;
799 
800 	OBJZERO(li);
801 
802 	scale = 1.0 / numsamples; /* each sample contributes this much */
803 
804 	li.face = face;
805 	li.facedist = plane->dist;
806 	VectorCopy(plane->normal, li.facenormal);
807 	/* negate the normal and dist */
808 	if (face->side) {
809 		VectorNegate(li.facenormal, li.facenormal);
810 		li.facedist = -li.facedist;
811 	}
812 
813 	/* get the origin offset for rotating bmodels */
814 	VectorCopy(face_offset[facenum], li.modelorg);
815 
816 	/* calculate lightmap texture mins and maxs */
817 	CalcLightinfoExtents(&li);
818 
819 	/* and the lightmap texture vectors */
820 	CalcLightinfoVectors(&li);
821 
822 	/* now generate all of the sample points */
823 	CalcPoints(&li, 0, 0);
824 
825 	fl = &facelight[config.compile_for_day][facenum];
826 	fl->numsamples = li.numsurfpt;
827 	fl->samples    = Mem_AllocTypeN(vec3_t, fl->numsamples);
828 	fl->directions = Mem_AllocTypeN(vec3_t, fl->numsamples);
829 
830 	center = face_extents[facenum].center;  /* center of the face */
831 
832 	/* Also setup the hints.  Each hint is specific to each light source, including sunlight. */
833 	headhints = Mem_AllocTypeN(int, (numlights[config.compile_for_day] + 1));
834 
835 	/* calculate light for each sample */
836 	for (i = 0; i < fl->numsamples; i++) {
837 		float* const sample    = fl->samples[i];    /* accumulate lighting here */
838 		float* const direction = fl->directions[i]; /* accumulate direction here */
839 
840 		if (tex->surfaceFlags & SURF_PHONG)
841 			/* interpolated normal */
842 			SampleNormal(&li, li.surfpt[i], normal);
843 		else
844 			/* or just plane normal */
845 			VectorCopy(li.facenormal, normal);
846 
847 		for (j = 0; j < numsamples; j++) {  /* with antialiasing */
848 			vec3_t pos;
849 
850 			/* add offset for supersampling */
851 			VectorMA(li.surfpt[i], sampleofs[grid_type][j][0] * li.step, li.textoworld[0], pos);
852 			VectorMA(pos, sampleofs[grid_type][j][1] * li.step, li.textoworld[1], pos);
853 
854 			NudgeSamplePosition(pos, normal, center, pos);
855 
856 			GatherSampleLight(pos, normal, sample, direction, scale, headhints);
857 		}
858 		if (VectorNotEmpty(direction)) {
859 			vec3_t dir;
860 
861 			/* normalize it */
862 			VectorNormalize(direction);
863 
864 			/* finalize the lighting direction for the sample */
865 			TangentVectors(normal, sdir, tdir, tangent, binormal);
866 
867 			dir[0] = DotProduct(direction, tangent);
868 			dir[1] = DotProduct(direction, binormal);
869 			dir[2] = DotProduct(direction, normal);
870 
871 			VectorCopy(dir, direction);
872 		}
873 	}
874 
875 	/* Free the hints. */
876 	Mem_Free(headhints);
877 
878 	for (i = 0; i < fl->numsamples; i++) {  /* pad them */
879 		float* const direction = fl->directions[i];
880 		if (VectorEmpty(direction))
881 			VectorSet(direction, 0.0, 0.0, 1.0);
882 	}
883 
884 	/* free the sample positions for the face */
885 	Mem_Free(li.surfpt);
886 }
887 
888 #define TGA_HEADER_SIZE 18
WriteTGA24(const char * filename,const byte * data,int width,int height,int offset)889 static void WriteTGA24 (const char* filename, const byte*  data, int width, int height, int offset)
890 {
891 	const int size = width * height * 3;
892 	/* allocate a buffer and set it up */
893 	byte* buffer = Mem_AllocTypeN(byte, size + TGA_HEADER_SIZE);
894 	memset(buffer, 0, TGA_HEADER_SIZE);
895 	buffer[2] = 2;
896 	buffer[12] = width & 255;
897 	buffer[13] = width >> 8;
898 	buffer[14] = height & 255;
899 	buffer[15] = height >> 8;
900 	buffer[16] = 24;
901 	/* create top-down TGA */
902 	buffer[17] = 32;
903 
904 	/* swap rgb to bgr */
905 	for (int i = 0; i < size; i += 3) {
906 		buffer[i + TGA_HEADER_SIZE] = data[i*2 + offset + 2];	/* blue */
907 		buffer[i + TGA_HEADER_SIZE + 1] = data[i*2 + offset + 1];	/* green */
908 		buffer[i + TGA_HEADER_SIZE + 2] = data[i*2 + offset + 0];	/* red */
909 	}
910 
911 	/* write it and free the buffer */
912 	ScopedFile file;
913 	if (FS_OpenFile(filename, &file, FILE_WRITE) > 0)
914 		Sys_Error("Unable to open %s for writing", filename);
915 
916 	FS_Write(buffer, size + TGA_HEADER_SIZE, &file);
917 
918 	/* close the file */
919 	Mem_Free(buffer);
920 }
921 
922 /**
923  * @brief Calculates the texture width for the lightmap texture. This depends on the surface
924  * mins and maxs and the texture scale
925  * @param[in] s The surface to calculate the lightmap size for
926  * @param[out] texsize The resulting texture size vector. First value is width, second value is height
927  * @param[in] scale The scale (1/scale) of the lightmap texture in relation to the surface texture
928  */
CalcTextureSize(const dBspSurface_t * s,vec2_t texsize,int scale)929 static void CalcTextureSize (const dBspSurface_t* s, vec2_t texsize, int scale)
930 {
931 	const float* stmins = face_extents[s - curTile->faces].stmins;
932 	const float* stmaxs = face_extents[s - curTile->faces].stmaxs;
933 
934 	for (int i = 0; i < 2; i++) {
935 		const float mins = floor(stmins[i] / scale);
936 		const float maxs = ceil(stmaxs[i] / scale);
937 
938 		texsize[i] = maxs - mins + 1;
939 	}
940 }
941 
942 /**
943  * @brief Export all the faces for one particular lightmap (day or night)
944  * @param path The path to write the files into
945  * @param name The name of the map to export the lightmap for
946  * @param day @c true to export the day lightmap data, @c false to export the night lightmap data
947  */
ExportLightmap(const char * path,const char * name,bool day)948 static void ExportLightmap (const char* path, const char* name, bool day)
949 {
950 	const int lightmapIndex = day ? 1 : 0;
951 	const byte* bspLightBytes = curTile->lightdata[lightmapIndex];
952 	const byte quant = *bspLightBytes;
953 	const int scale = 1 << quant;
954 
955 	for (int i = 0; i < curTile->numfaces; i++) {
956 		const dBspSurface_t* face = &curTile->faces[i];
957 		const byte* lightmap = bspLightBytes + face->lightofs[lightmapIndex];
958 		vec2_t texSize;
959 
960 		CalcTextureSize(face, texSize, scale);
961 
962 		/* write a tga image out */
963 		if (Vector2NotEmpty(texSize)) {
964 			char filename[MAX_QPATH];
965 			Com_sprintf(filename, sizeof(filename), "%s/%s_lightmap_%04d%c.tga", path, name, i, day ? 'd' : 'n');
966 			Com_Printf("Writing %s (%.0fx%.0f)\n", filename, texSize[0], texSize[1]);
967 			WriteTGA24(filename, lightmap, texSize[0], texSize[1], 0);
968 			Com_sprintf(filename, sizeof(filename), "%s/%s_direction_%04d%c.tga", path, name, i, day ? 'd' : 'n');
969 			Com_Printf("Writing %s (%.0fx%.0f)\n", filename, texSize[0], texSize[1]);
970 			WriteTGA24(filename, lightmap, texSize[0], texSize[1], 3);
971 		}
972 	}
973 }
974 
975 /**
976  * @brief Export the day and night lightmap and direction data for the given map.
977  * @note The bsp file must already be loaded.
978  * @param bspFileName The path of the loaded bsp file.
979  */
ExportLightmaps(const char * bspFileName)980 void ExportLightmaps (const char* bspFileName)
981 {
982 	char path[MAX_QPATH], lightmapName[MAX_QPATH];
983 	const char* fileName = Com_SkipPath(bspFileName);
984 
985 	Com_FilePath(bspFileName, path, sizeof(path));
986 	Com_StripExtension(fileName, lightmapName, sizeof(lightmapName));
987 
988 	/* note it */
989 	Com_Printf("--- ExportLightmaps ---\n");
990 
991 	BuildFaceExtents();
992 
993 	ExportLightmap(path, lightmapName, true);
994 	ExportLightmap(path, lightmapName, false);
995 }
996 
997 static const vec3_t luminosity = {0.2125, 0.7154, 0.0721};
998 
999 /**
1000  * @brief Add the indirect lighting on top of the direct
1001  * lighting and save into final map format
1002  * @sa BuildFacelights
1003  */
FinalLightFace(unsigned int facenum)1004 void FinalLightFace (unsigned int facenum)
1005 {
1006 	int j, k;
1007 	vec3_t dir, intensity;
1008 	byte* dest;
1009 
1010 	dBspSurface_t* f = &curTile->faces[facenum];
1011 	facelight_t	*fl = &facelight[config.compile_for_day][facenum];
1012 
1013 	/* none-lit texture */
1014 	if (curTile->texinfo[f->texinfo].surfaceFlags & SURF_WARP)
1015 		return;
1016 
1017 	ThreadLock();
1018 
1019 	f->lightofs[config.compile_for_day] = curTile->lightdatasize[config.compile_for_day];
1020 	curTile->lightdatasize[config.compile_for_day] += fl->numsamples * 3;
1021 	/* account for light direction data as well */
1022 	curTile->lightdatasize[config.compile_for_day] += fl->numsamples * 3;
1023 
1024 	if (curTile->lightdatasize[config.compile_for_day] > MAX_MAP_LIGHTING)
1025 		Sys_Error("MAX_MAP_LIGHTING (%i exceeded %i) - try to reduce the brush size (%s)",
1026 			curTile->lightdatasize[config.compile_for_day], MAX_MAP_LIGHTING,
1027 			curTile->texinfo[f->texinfo].texture);
1028 
1029 	ThreadUnlock();
1030 
1031 	/* write it out */
1032 	dest = &curTile->lightdata[config.compile_for_day][f->lightofs[config.compile_for_day]];
1033 
1034 	for (j = 0; j < fl->numsamples; j++) {
1035 		vec3_t temp;
1036 
1037 		/* start with raw sample data */
1038 		VectorCopy(fl->samples[j], temp);
1039 
1040 		/* convert to float */
1041 		VectorScale(temp, 1.0 / 255.0, temp);
1042 
1043 		/* add an ambient term if desired */
1044 		VectorAdd(temp, sun_ambient_color, temp);
1045 
1046 		/* apply global scale factor */
1047 		VectorScale(temp, config.brightness, temp);
1048 
1049 		float max = 0.0;
1050 
1051 		/* find the brightest component */
1052 		for (k = 0; k < 3; k++) {
1053 			/* enforcing positive values */
1054 			if (temp[k] < 0.0)
1055 				temp[k] = 0.0;
1056 
1057 			if (temp[k] > max)
1058 				max = temp[k];
1059 		}
1060 
1061 		if (max > 255.0)  /* clamp without changing hue */
1062 			VectorScale(temp, 255.0 / max, temp);
1063 
1064 		for (k = 0; k < 3; k++) {  /* apply contrast */
1065 			temp[k] -= 0.5;  /* normalize to -0.5 through 0.5 */
1066 
1067 			temp[k] *= config.contrast;  /* scale */
1068 
1069 			temp[k] += 0.5;
1070 
1071 			if (temp[k] > 1.0)  /* clamp */
1072 				temp[k] = 1.0;
1073 			else if (temp[k] < 0)
1074 				temp[k] = 0;
1075 		}
1076 
1077 		/* apply saturation */
1078 		float d = DotProduct(temp, luminosity);
1079 
1080 		VectorSet(intensity, d, d, d);
1081 		VectorMix(intensity, temp, config.saturation, temp);
1082 
1083 		for (k = 0; k < 3; k++) {
1084 			temp[k] *= 255.0;  /* back to byte */
1085 
1086 			if (temp[k] > 255.0)  /* clamp */
1087 				temp[k] = 255.0;
1088 			else if (temp[k] < 0.0)
1089 				temp[k] = 0.0;
1090 
1091 			*dest++ = (byte)temp[k];
1092 		}
1093 
1094 		/* also write the directional data */
1095 		VectorCopy(fl->directions[j], dir);
1096 		for (k = 0; k < 3; k++)
1097 			*dest++ = (byte)((dir[k] + 1.0f) * 127.0f);
1098 	}
1099 }
1100