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