diff --git a/include/light/light.h b/include/light/light.h index ea92aa9f..76cb38b8 100644 --- a/include/light/light.h +++ b/include/light/light.h @@ -32,10 +32,48 @@ #define MAXLIGHTS 1024 -qboolean TestSky(const vec3_t start, const vec3_t dirn, vec3_t skypoint); -qboolean TestLine(const vec3_t start, const vec3_t stop); -qboolean TestLineModel(const dmodel_t *model, - const vec3_t start, const vec3_t stop); + +#define TRACE_HIT_NONE 0 +#define TRACE_HIT_SOLID (1 << 0) +#define TRACE_HIT_WATER (1 << 1) +#define TRACE_HIT_SLIME (1 << 2) +#define TRACE_HIT_LAVA (1 << 3) +#define TRACE_HIT_SKY (1 << 4) + +typedef struct { + const dplane_t *dplane; + int side; + vec3_t point; +} tracepoint_t; + +/* + * --------- + * TraceLine + * --------- + * Generic BSP model ray tracing function. Traces a ray from start towards + * stop. If the trace line hits one of the flagged contents along the way, the + * corresponding TRACE flag will be returned. Furthermore, if hitpoint is + * non-null, information about the point the ray hit will be filled in. + * + * model - The bsp model to trace against + * flags - contents which will stop the trace (must be > 0) + * start - coordinates to start trace + * stop - coordinates to end the trace + * hitpoint - filled in if result > 0 and hitpoint is non-NULL + * + * TraceLine will return a negative traceflag if the point 'start' resides + * inside a leaf with one of the contents types which stop the trace. + */ +int TraceLine(const dmodel_t *model, const int traceflags, + const vec3_t start, const vec3_t end, tracepoint_t *hitpoint); + +/* + * Convenience functions TestLight and TestSky will test against all shadow + * casting bmodels and self-shadow the model 'self' if self != NULL. Returns + * true if sky or light is visible, respectively. + */ +qboolean TestSky(const vec3_t start, const vec3_t dirn, const dmodel_t *self); +qboolean TestLight(const vec3_t start, const vec3_t stop, const dmodel_t *self); typedef struct { vec_t light; diff --git a/light/ltface.c b/light/ltface.c index 1a04e1e6..997475be 100644 --- a/light/ltface.c +++ b/light/ltface.c @@ -336,6 +336,29 @@ CalcFaceExtents(const dface_t *face, const vec3_t offset, lightsurf_t *surf) } } +/* + * Print warning for CalcPoint where the midpoint of a polygon, one + * unit above the surface is covered by a solid brush. + */ +static void +WarnBadMidpoint(const vec3_t point) +{ +#if 0 + static qboolean warned = false; + + if (warned) + return; + + warned = true; + logprint("WARNING: unable to lightmap surface near (%s)\n" + " This is usually caused by an unintentional tiny gap between\n" + " two solid brushes which doesn't leave enough room for the\n" + " lightmap to fit (one world unit). Further instances of this\n" + " warning during this compile will be supressed.\n", + VecStr(point)); +#endif +} + /* * ================= * CalcPoints @@ -348,66 +371,50 @@ CalcPoints(const dmodel_t *model, const texorg_t *texorg, lightsurf_t *surf) { int i; int s, t; - int w, h, step; + int width, height, step; vec_t starts, startt, us, ut; vec_t *point; - vec_t mids, midt; - vec3_t facemid, move; + vec3_t midpoint, move; - /* fill in surforg */ - /* the points are biased towards the center of the surface */ - /* to help avoid edge cases just inside walls */ + /* + * Fill in the surface points. The points are biased towards the center of + * the surface to help avoid edge cases just inside walls + */ + TexCoordToWorld(surf->exactmid[0], surf->exactmid[1], texorg, midpoint); - point = surf->points[0]; - mids = surf->exactmid[0]; - midt = surf->exactmid[1]; - - TexCoordToWorld(mids, midt, texorg, facemid); - - h = (surf->texsize[1] + 1) * oversample; - w = (surf->texsize[0] + 1) * oversample; + width = (surf->texsize[0] + 1) * oversample; + height = (surf->texsize[1] + 1) * oversample; starts = (surf->texmins[0] - 0.5 + (0.5 / oversample)) * 16; startt = (surf->texmins[1] - 0.5 + (0.5 / oversample)) * 16; step = 16 / oversample; - surf->numpoints = w * h; - for (t = 0; t < h; t++) { - for (s = 0; s < w; s++, point += 3) { + point = surf->points[0]; + surf->numpoints = width * height; + for (t = 0; t < height; t++) { + for (s = 0; s < width; s++, point += 3) { us = starts + s * step; ut = startt + t * step; - /* if a line can be traced from surf to facemid, point is good */ + TexCoordToWorld(us, ut, texorg, point); for (i = 0; i < 6; i++) { - TexCoordToWorld(us, ut, texorg, point); + const int flags = TRACE_HIT_SOLID; + tracepoint_t hit; + int result; + vec_t dist; - if (TestLineModel(model, facemid, point)) - break; /* got it */ - if (i & 1) { // i is odd - if (us > mids) { - us -= 8; - if (us < mids) - us = mids; - } else { - us += 8; - if (us > mids) - us = mids; - } - } else { - if (ut > midt) { - ut -= 8; - if (ut < midt) - ut = midt; - } else { - ut += 8; - if (ut > midt) - ut = midt; - } + result = TraceLine(model, flags, midpoint, point, &hit); + if (result == TRACE_HIT_NONE) + break; + if (result != TRACE_HIT_SOLID) { + WarnBadMidpoint(midpoint); + break; } - /* move surf 8 pixels towards the center */ - VectorSubtract(facemid, point, move); - VectorNormalize(move); - VectorMA(point, 8, move, point); + /* Move the point 1 unit above the obstructing surface */ + dist = DotProduct(point, hit.dplane->normal) - hit.dplane->dist; + dist = hit.side ? -dist - 1 : -dist + 1; + VectorScale(hit.dplane->normal, dist, move); + VectorAdd(point, move, point); } } } @@ -502,12 +509,13 @@ SingleLightFace(const entity_t *entity, const lightsample_t *light, { const modelinfo_t *modelinfo = lightsurf->modelinfo; const plane_t *plane = &lightsurf->plane; + const dmodel_t *shadowself; + const vec_t *surfpoint; vec_t dist; vec_t angle, spotscale; vec_t add; qboolean newmap, hit; int i, mapnum; - const vec_t *surfpoint; lightsample_t *sample; lightmap_t newlightmap; @@ -544,6 +552,7 @@ SingleLightFace(const entity_t *entity, const lightsample_t *light, * Check it for real */ hit = false; + shadowself = modelinfo->shadowself ? modelinfo->model : NULL; surfpoint = lightsurf->points[0]; for (i = 0; i < lightsurf->numpoints; i++, sample++, surfpoint += 3) { vec3_t ray; @@ -571,12 +580,8 @@ SingleLightFace(const entity_t *entity, const lightsample_t *light, } } - /* Test for line of sight */ - if (!TestLine(entity->origin, surfpoint)) + if (!TestLight(entity->origin, surfpoint, shadowself)) continue; - if (modelinfo->shadowself) - if (!TestLineModel(modelinfo->model, entity->origin, surfpoint)) - continue; angle = (1.0 - scalecos) + scalecos * angle; add = GetLightValue(light, entity, dist) * angle * spotscale; @@ -614,6 +619,7 @@ SkyLightFace(const lightsample_t *light, const lightsurf_t *lightsurf, { const modelinfo_t *modelinfo = lightsurf->modelinfo; const plane_t *plane = &lightsurf->plane; + const dmodel_t *shadowself; const vec_t *surfpoint; int i, mapnum; vec3_t incoming; @@ -642,15 +648,12 @@ SkyLightFace(const lightsample_t *light, const lightsurf_t *lightsurf, angle = (1.0 - scalecos) + scalecos * angle; /* Check each point... */ + shadowself = modelinfo->shadowself ? modelinfo->model : NULL; sample = lightmaps[mapnum].samples; surfpoint = lightsurf->points[0]; for (i = 0; i < lightsurf->numpoints; i++, sample++, surfpoint += 3) { - vec3_t skypoint; - if (!TestSky(surfpoint, sunvec, skypoint)) + if (!TestSky(surfpoint, sunvec, shadowself)) continue; - if (modelinfo->shadowself) - if (!TestLineModel(modelinfo->model, surfpoint, skypoint)) - continue; sample->light += angle * light->light; if (colored) VectorMA(sample->color, angle * light->light / 255.0f, light->color, @@ -668,10 +671,11 @@ FixMinlight(const lightsample_t *minlight, const lightsurf_t *lightsurf, lightmap_t *lightmaps) { const modelinfo_t *modelinfo = lightsurf->modelinfo; - int mapnum, i, j, k; - lightsample_t *sample; + const dmodel_t *shadowself; const entity_t *entity; const vec_t *surfpoint; + int mapnum, i, j, k; + lightsample_t *sample; /* Find a style 0 lightmap */ for (mapnum = 0; mapnum < MAXLIGHTMAPS; mapnum++) { @@ -699,6 +703,7 @@ FixMinlight(const lightsample_t *minlight, const lightsurf_t *lightsurf, } /* Cast rays for local minlight entities */ + shadowself = modelinfo->shadowself ? modelinfo->model : NULL; for (i = 0, entity = entities; i < num_entities; i++, entity++) { if (entity->formula != LF_LOCALMIN) continue; @@ -708,14 +713,9 @@ FixMinlight(const lightsample_t *minlight, const lightsurf_t *lightsurf, for (j = 0; j < lightsurf->numpoints; j++, sample++, surfpoint += 3) { qboolean trace = false; if (sample->light < entity->light.light) { - trace = TestLine(entity->origin, surfpoint); + trace = TestLight(entity->origin, surfpoint, shadowself); if (!trace) continue; - if (modelinfo->shadowself) { - trace = TestLineModel(modelinfo->model, entity->origin, surfpoint); - if (!trace) - continue; - } sample->light = entity->light.light; } if (!colored) @@ -723,15 +723,9 @@ FixMinlight(const lightsample_t *minlight, const lightsurf_t *lightsurf, for (k = 0; k < 3; k++) { if (sample->color[k] < entity->light.color[k]) { if (!trace) { - trace = TestLine(entity->origin, surfpoint); + trace = TestLight(entity->origin, surfpoint, shadowself); if (!trace) break; - if (modelinfo->shadowself) { - trace = TestLineModel(modelinfo->model, - entity->origin, surfpoint); - if (!trace) - break; - } } sample->color[k] = entity->light.color[k]; } diff --git a/light/trace.c b/light/trace.c index 3ca23f68..40b783c9 100644 --- a/light/trace.c +++ b/light/trace.c @@ -82,9 +82,10 @@ MakeTnodes(void) */ typedef struct { - vec3_t backpt; - int side; + vec3_t back; + vec3_t front; int node; + int side; } tracestack_t; /* @@ -93,52 +94,81 @@ typedef struct { * ============== */ #define MAX_TSTACK 128 -static qboolean -TestLineOrSky(const dmodel_t *model, const vec3_t start, const vec3_t stop, - qboolean skytest, vec3_t skypoint) +int +TraceLine(const dmodel_t *model, const int traceflags, + const vec3_t start, const vec3_t stop, tracepoint_t *hitpoint) { - int node, side; + int node, side, tracehit; vec3_t front, back; vec_t frontdist, backdist; tracestack_t tracestack[MAX_TSTACK]; - tracestack_t *tstack; + tracestack_t *tstack, *crossnode; tnode_t *tnode; const tracestack_t *const tstack_max = tracestack + MAX_TSTACK; + if (traceflags <= 0) + Error("Internal error: %s - bad traceflags (%d)", + __func__, traceflags); + VectorCopy(start, front); VectorCopy(stop, back); tstack = tracestack; node = model->headnode[0]; + crossnode = NULL; + tracehit = TRACE_HIT_NONE; while (1) { - while (node < 0 && node != CONTENTS_SOLID) { - if (skytest && node == CONTENTS_SKY) + while (node < 0) { + switch (node) { + case CONTENTS_SOLID: + if (traceflags & TRACE_HIT_SOLID) + tracehit = TRACE_HIT_SOLID; break; + case CONTENTS_WATER: + if (traceflags & TRACE_HIT_WATER) + tracehit = TRACE_HIT_WATER; + break; + case CONTENTS_SLIME: + if (traceflags & TRACE_HIT_SLIME) + tracehit = TRACE_HIT_SLIME; + break; + case CONTENTS_LAVA: + if (traceflags & TRACE_HIT_LAVA) + tracehit = TRACE_HIT_LAVA; + break; + case CONTENTS_SKY: + if (traceflags & TRACE_HIT_SKY) + tracehit = TRACE_HIT_SKY; + break; + default: + break; + } + if (tracehit != TRACE_HIT_NONE) { + /* If we haven't crossed, start was inside flagged contents */ + if (!crossnode) + return -tracehit; + if (hitpoint) { + const int planenum = dnodes[crossnode->node].planenum; + hitpoint->dplane = dplanes + planenum; + hitpoint->side = crossnode->side; + VectorCopy(crossnode->back, hitpoint->point); + } + return tracehit; + } - /* If the stack is empty, not obstructions were hit */ + /* If the stack is empty, no obstructions were hit */ if (tstack == tracestack) - return !skytest; + return TRACE_HIT_NONE; - /* - * pop the stack, set the hit point for this plane and - * go down the back side - */ - tstack--; - VectorCopy(back, front); - VectorCopy(tstack->backpt, back); + /* Pop the stack and go down the back side */ + crossnode = --tstack; + VectorCopy(tstack->front, front); + VectorCopy(tstack->back, back); node = tnodes[tstack->node].children[!tstack->side]; } - if (node == CONTENTS_SOLID) - return false; - if (node == CONTENTS_SKY && skytest) { - VectorCopy(front, skypoint); - return true; - } - tnode = &tnodes[node]; - switch (tnode->type) { case PLANE_X: frontdist = front[0] - tnode->dist; @@ -158,74 +188,116 @@ TestLineOrSky(const dmodel_t *model, const vec3_t start, const vec3_t stop, break; } - if (frontdist > -ON_EPSILON && backdist > -ON_EPSILON) { + if (frontdist > ON_EPSILON && backdist > ON_EPSILON) { node = tnode->children[0]; continue; } - if (frontdist < ON_EPSILON && backdist < ON_EPSILON) { + if (frontdist < -ON_EPSILON && backdist < -ON_EPSILON) { node = tnode->children[1]; continue; } - if (tstack == tstack_max) - Error("%s: tstack overflow\n", __func__); + if (frontdist >= -ON_EPSILON && frontdist <= ON_EPSILON) { + if (backdist >= -ON_EPSILON && backdist <= ON_EPSILON) { + /* Front and back on-node, go down both sides */ + if (tstack == tstack_max) + Error("%s: tstack overflow\n", __func__); + tstack->node = node; + tstack->side = 0; + VectorCopy(front, tstack->front); + VectorCopy(back, tstack->back); + crossnode = tstack++; + node = tnode->children[0]; + continue; + } - side = frontdist < 0.0f ? 1 : 0; + /* If only front is on-node, go down the side containing back */ + side = back < 0; + node = tnode->children[side]; + continue; + } + + if (backdist >= -ON_EPSILON && backdist <= ON_EPSILON) { + /* If only back is on-node, record a cross point but continue */ + if (tstack == tstack_max) + Error("%s: tstack overflow\n", __func__); + side = frontdist < 0; + tstack->node = node; + tstack->side = side; + VectorCopy(front, tstack->front); + VectorCopy(back, tstack->back); + crossnode = tstack; + node = tnode->children[side]; + continue; + } + + /* + * If we get here, we have a clean split with front and back on + * opposite sides. The new back is the intersection point with the + * node plane. Push the other segment onto the stack and continue. + */ + side = frontdist < 0; tstack->node = node; tstack->side = side; - VectorCopy(back, tstack->backpt); - tstack++; - - /* The new back is the intersection point with the node plane */ + VectorCopy(back, tstack->back); VectorSubtract(back, front, back); VectorMA(front, frontdist / (frontdist - backdist), back, back); - + VectorCopy(back, tstack->front); + crossnode = tstack++; node = tnode->children[side]; } } qboolean -TestLine(const vec3_t start, const vec3_t stop) +TestLight(const vec3_t start, const vec3_t stop, const dmodel_t *self) { const dmodel_t *const *model; + const int traceflags = TRACE_HIT_SOLID; + int result = TRACE_HIT_NONE; - for (model = tracelist; *model; model++) - if (!TestLineModel(*model, start, stop)) + /* Check against the list of global shadow casters */ + for (model = tracelist; *model; model++) { + result = TraceLine(*model, traceflags, start, stop, NULL); + if (result != TRACE_HIT_NONE) break; + } - return !*model; + /* If not yet obscured, check against the self-shadow model */ + if (result == TRACE_HIT_NONE && self) + result = TraceLine(self, traceflags, start, stop, NULL); + + return (result == TRACE_HIT_NONE); } -/* - * Wrapper functions for testing LOS between two points (TestLine) - * and testing LOS to a sky brush along a direction vector (TestSky) - */ qboolean -TestLineModel(const dmodel_t *model, const vec3_t start, const vec3_t stop) -{ - return TestLineOrSky(model, start, stop, false, NULL); -} - -/* - * ======= - * TestSky - * ======= - * Returns true if the ray cast from point 'start' in the - * direction of vector 'dirn' hits a CONTENTS_SKY node before - * a CONTENTS_SOLID node. - */ -qboolean -TestSky(const vec3_t start, const vec3_t dirn, vec3_t skypoint) +TestSky(const vec3_t start, const vec3_t dirn, const dmodel_t *self) { const dmodel_t *const *model; + int traceflags = TRACE_HIT_SKY | TRACE_HIT_SOLID; + int result = TRACE_HIT_NONE; + vec3_t stop; + tracepoint_t hit; - VectorAdd(dirn, start, skypoint); - if (!TestLineOrSky(tracelist[0], start, skypoint, true, skypoint)) + /* Trace towards the sunlight for a sky brush */ + VectorAdd(dirn, start, stop); + result = TraceLine(tracelist[0], traceflags, start, stop, &hit); + if (result != TRACE_HIT_SKY) return false; - for (model = tracelist + 1; *model; model++) - if (!TestLineModel(*model, start, skypoint)) - break; + /* If good, check it isn't shadowed by another model */ + traceflags = TRACE_HIT_SOLID; + for (model = tracelist + 1; *model; model++) { + result = TraceLine(*model, traceflags, start, hit.point, NULL); + if (result != TRACE_HIT_NONE) + return false; + } - return !*model; + /* Check for self-shadowing */ + if (self) { + result = TraceLine(self, traceflags, start, hit.point, NULL); + if (result != TRACE_HIT_NONE) + return false; + } + + return true; }