2012-01-31 19:41:34 +00:00
|
|
|
/*
|
|
|
|
===========================================================================
|
|
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
|
|
|
|
|
|
This file is part of Quake III Arena source code.
|
|
|
|
|
|
|
|
Quake III Arena source code is free software; you can redistribute it
|
|
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
|
|
or (at your option) any later version.
|
|
|
|
|
|
|
|
Quake III Arena source code is distributed in the hope that it will be
|
|
|
|
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with Foobar; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
===========================================================================
|
|
|
|
*/
|
2005-08-26 17:39:27 +00:00
|
|
|
// light.c
|
|
|
|
|
|
|
|
#include "light.h"
|
|
|
|
#ifdef _WIN32
|
|
|
|
#ifdef _TTIMOBUILD
|
|
|
|
#include "pakstuff.h"
|
|
|
|
#else
|
|
|
|
#include "../libs/pakstuff.h"
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
#define EXTRASCALE 2
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
float plane[4];
|
|
|
|
vec3_t origin;
|
|
|
|
vec3_t vectors[2];
|
|
|
|
shaderInfo_t *si;
|
|
|
|
} filter_t;
|
|
|
|
|
|
|
|
#define MAX_FILTERS 1024
|
|
|
|
filter_t filters[MAX_FILTERS];
|
|
|
|
int numFilters;
|
|
|
|
|
|
|
|
extern char source[1024];
|
|
|
|
|
|
|
|
qboolean notrace;
|
|
|
|
qboolean patchshadows;
|
|
|
|
qboolean dump;
|
|
|
|
qboolean extra;
|
|
|
|
qboolean extraWide;
|
|
|
|
qboolean lightmapBorder;
|
|
|
|
|
|
|
|
qboolean noSurfaces;
|
|
|
|
|
|
|
|
int samplesize = 16; //sample size in units
|
|
|
|
int novertexlighting = 0;
|
|
|
|
int nogridlighting = 0;
|
|
|
|
|
|
|
|
// for run time tweaking of all area sources in the level
|
|
|
|
float areaScale = 0.25;
|
|
|
|
|
|
|
|
// for run time tweaking of all point sources in the level
|
|
|
|
float pointScale = 7500;
|
|
|
|
|
|
|
|
qboolean exactPointToPolygon = qtrue;
|
|
|
|
|
|
|
|
float formFactorValueScale = 3;
|
|
|
|
|
|
|
|
float linearScale = 1.0 / 8000;
|
|
|
|
|
|
|
|
light_t *lights;
|
|
|
|
int numPointLights;
|
|
|
|
int numAreaLights;
|
|
|
|
|
|
|
|
FILE *dumpFile;
|
|
|
|
|
|
|
|
int c_visible, c_occluded;
|
|
|
|
|
|
|
|
//int defaultLightSubdivide = 128; // vary by surface size?
|
|
|
|
int defaultLightSubdivide = 999; // vary by surface size?
|
|
|
|
|
|
|
|
vec3_t ambientColor;
|
|
|
|
|
|
|
|
vec3_t surfaceOrigin[ MAX_MAP_DRAW_SURFS ];
|
|
|
|
int entitySurface[ MAX_MAP_DRAW_SURFS ];
|
|
|
|
|
|
|
|
// 7,9,11 normalized to avoid being nearly coplanar with common faces
|
|
|
|
//vec3_t sunDirection = { 0.441835, 0.56807, 0.694313 };
|
|
|
|
//vec3_t sunDirection = { 0.45, 0, 0.9 };
|
|
|
|
//vec3_t sunDirection = { 0, 0, 1 };
|
|
|
|
|
|
|
|
// these are usually overrided by shader values
|
|
|
|
vec3_t sunDirection = { 0.45, 0.3, 0.9 };
|
|
|
|
vec3_t sunLight = { 100, 100, 50 };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
dbrush_t *b;
|
|
|
|
vec3_t bounds[2];
|
|
|
|
} skyBrush_t;
|
|
|
|
|
|
|
|
int numSkyBrushes;
|
|
|
|
skyBrush_t skyBrushes[MAX_MAP_BRUSHES];
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
the corners of a patch mesh will always be exactly at lightmap samples.
|
|
|
|
The dimensions of the lightmap will be equal to the average length of the control
|
|
|
|
mesh in each dimension divided by 2.
|
|
|
|
The lightmap sample points should correspond to the chosen subdivision points.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============================================================
|
|
|
|
|
|
|
|
SURFACE LOADING
|
|
|
|
|
|
|
|
===============================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define MAX_FACE_POINTS 128
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
SubdivideAreaLight
|
|
|
|
|
|
|
|
Subdivide area lights that are very large
|
|
|
|
A light that is subdivided will never backsplash, avoiding weird pools of light near edges
|
|
|
|
===============
|
|
|
|
*/
|
|
|
|
void SubdivideAreaLight( shaderInfo_t *ls, winding_t *w, vec3_t normal,
|
|
|
|
float areaSubdivide, qboolean backsplash ) {
|
|
|
|
float area, value, intensity;
|
|
|
|
light_t *dl, *dl2;
|
|
|
|
vec3_t mins, maxs;
|
|
|
|
int axis;
|
|
|
|
winding_t *front, *back;
|
|
|
|
vec3_t planeNormal;
|
|
|
|
float planeDist;
|
|
|
|
|
|
|
|
if ( !w ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
WindingBounds( w, mins, maxs );
|
|
|
|
|
|
|
|
// check for subdivision
|
|
|
|
for ( axis = 0 ; axis < 3 ; axis++ ) {
|
|
|
|
if ( maxs[axis] - mins[axis] > areaSubdivide ) {
|
|
|
|
VectorClear( planeNormal );
|
|
|
|
planeNormal[axis] = 1;
|
|
|
|
planeDist = ( maxs[axis] + mins[axis] ) * 0.5;
|
|
|
|
ClipWindingEpsilon ( w, planeNormal, planeDist, ON_EPSILON, &front, &back );
|
|
|
|
SubdivideAreaLight( ls, front, normal, areaSubdivide, qfalse );
|
|
|
|
SubdivideAreaLight( ls, back, normal, areaSubdivide, qfalse );
|
|
|
|
FreeWinding( w );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// create a light from this
|
|
|
|
area = WindingArea (w);
|
|
|
|
if ( area <= 0 || area > 20000000 ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
numAreaLights++;
|
|
|
|
dl = malloc(sizeof(*dl));
|
|
|
|
memset (dl, 0, sizeof(*dl));
|
|
|
|
dl->next = lights;
|
|
|
|
lights = dl;
|
|
|
|
dl->type = emit_area;
|
|
|
|
|
|
|
|
WindingCenter( w, dl->origin );
|
|
|
|
dl->w = w;
|
|
|
|
VectorCopy ( normal, dl->normal);
|
|
|
|
dl->dist = DotProduct( dl->origin, normal );
|
|
|
|
|
|
|
|
value = ls->value;
|
|
|
|
intensity = value * area * areaScale;
|
|
|
|
VectorAdd( dl->origin, dl->normal, dl->origin );
|
|
|
|
|
|
|
|
VectorCopy( ls->color, dl->color );
|
|
|
|
|
|
|
|
dl->photons = intensity;
|
|
|
|
|
|
|
|
// emitColor is irrespective of the area
|
|
|
|
VectorScale( ls->color, value*formFactorValueScale*areaScale, dl->emitColor );
|
|
|
|
|
|
|
|
dl->si = ls;
|
|
|
|
|
|
|
|
if ( ls->contents & CONTENTS_FOG ) {
|
|
|
|
dl->twosided = qtrue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// optionally create a point backsplash light
|
|
|
|
if ( backsplash && ls->backsplashFraction > 0 ) {
|
|
|
|
dl2 = malloc(sizeof(*dl));
|
|
|
|
memset (dl2, 0, sizeof(*dl2));
|
|
|
|
dl2->next = lights;
|
|
|
|
lights = dl2;
|
|
|
|
dl2->type = emit_point;
|
|
|
|
|
|
|
|
VectorMA( dl->origin, ls->backsplashDistance, normal, dl2->origin );
|
|
|
|
|
|
|
|
VectorCopy( ls->color, dl2->color );
|
|
|
|
|
|
|
|
dl2->photons = dl->photons * ls->backsplashFraction;
|
|
|
|
dl2->si = ls;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CountLightmaps
|
|
|
|
===============
|
|
|
|
*/
|
|
|
|
void CountLightmaps( void ) {
|
|
|
|
int count;
|
|
|
|
int i;
|
|
|
|
dsurface_t *ds;
|
|
|
|
|
|
|
|
qprintf ("--- CountLightmaps ---\n");
|
|
|
|
count = 0;
|
|
|
|
for ( i = 0 ; i < numDrawSurfaces ; i++ ) {
|
|
|
|
// see if this surface is light emiting
|
|
|
|
ds = &drawSurfaces[i];
|
|
|
|
if ( ds->lightmapNum > count ) {
|
|
|
|
count = ds->lightmapNum;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
count++;
|
|
|
|
numLightBytes = count * LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT * 3;
|
|
|
|
if ( numLightBytes > MAX_MAP_LIGHTING ) {
|
|
|
|
Error("MAX_MAP_LIGHTING exceeded");
|
|
|
|
}
|
|
|
|
|
|
|
|
qprintf( "%5i drawSurfaces\n", numDrawSurfaces );
|
|
|
|
qprintf( "%5i lightmaps\n", count );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
CreateSurfaceLights
|
|
|
|
|
|
|
|
This creates area lights
|
|
|
|
===============
|
|
|
|
*/
|
|
|
|
void CreateSurfaceLights( void ) {
|
|
|
|
int i, j, side;
|
|
|
|
dsurface_t *ds;
|
|
|
|
shaderInfo_t *ls;
|
|
|
|
winding_t *w;
|
|
|
|
cFacet_t *f;
|
|
|
|
light_t *dl;
|
|
|
|
vec3_t origin;
|
|
|
|
drawVert_t *dv;
|
|
|
|
int c_lightSurfaces;
|
|
|
|
float lightSubdivide;
|
|
|
|
vec3_t normal;
|
|
|
|
|
|
|
|
qprintf ("--- CreateSurfaceLights ---\n");
|
|
|
|
c_lightSurfaces = 0;
|
|
|
|
|
|
|
|
for ( i = 0 ; i < numDrawSurfaces ; i++ ) {
|
|
|
|
// see if this surface is light emiting
|
|
|
|
ds = &drawSurfaces[i];
|
|
|
|
|
|
|
|
ls = ShaderInfoForShader( dshaders[ ds->shaderNum].shader );
|
|
|
|
if ( ls->value == 0 ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// determine how much we need to chop up the surface
|
|
|
|
if ( ls->lightSubdivide ) {
|
|
|
|
lightSubdivide = ls->lightSubdivide;
|
|
|
|
} else {
|
|
|
|
lightSubdivide = defaultLightSubdivide;
|
|
|
|
}
|
|
|
|
|
|
|
|
c_lightSurfaces++;
|
|
|
|
|
|
|
|
// an autosprite shader will become
|
|
|
|
// a point light instead of an area light
|
|
|
|
if ( ls->autosprite ) {
|
|
|
|
// autosprite geometry should only have four vertexes
|
|
|
|
if ( surfaceTest[i] ) {
|
|
|
|
// curve or misc_model
|
|
|
|
f = surfaceTest[i]->facets;
|
|
|
|
if ( surfaceTest[i]->numFacets != 1 || f->numBoundaries != 4 ) {
|
|
|
|
_printf( "WARNING: surface at (%i %i %i) has autosprite shader but isn't a quad\n",
|
|
|
|
(int)f->points[0], (int)f->points[1], (int)f->points[2] );
|
|
|
|
}
|
|
|
|
VectorAdd( f->points[0], f->points[1], origin );
|
|
|
|
VectorAdd( f->points[2], origin, origin );
|
|
|
|
VectorAdd( f->points[3], origin, origin );
|
|
|
|
VectorScale( origin, 0.25, origin );
|
|
|
|
} else {
|
|
|
|
// normal polygon
|
|
|
|
dv = &drawVerts[ ds->firstVert ];
|
|
|
|
if ( ds->numVerts != 4 ) {
|
|
|
|
_printf( "WARNING: surface at (%i %i %i) has autosprite shader but %i verts\n",
|
|
|
|
(int)dv->xyz[0], (int)dv->xyz[1], (int)dv->xyz[2] );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorAdd( dv[0].xyz, dv[1].xyz, origin );
|
|
|
|
VectorAdd( dv[2].xyz, origin, origin );
|
|
|
|
VectorAdd( dv[3].xyz, origin, origin );
|
|
|
|
VectorScale( origin, 0.25, origin );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
numPointLights++;
|
|
|
|
dl = malloc(sizeof(*dl));
|
|
|
|
memset (dl, 0, sizeof(*dl));
|
|
|
|
dl->next = lights;
|
|
|
|
lights = dl;
|
|
|
|
|
|
|
|
VectorCopy( origin, dl->origin );
|
|
|
|
VectorCopy( ls->color, dl->color );
|
|
|
|
dl->photons = ls->value * pointScale;
|
|
|
|
dl->type = emit_point;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// possibly create for both sides of the polygon
|
|
|
|
for ( side = 0 ; side <= ls->twoSided ; side++ ) {
|
|
|
|
// create area lights
|
|
|
|
if ( surfaceTest[i] ) {
|
|
|
|
// curve or misc_model
|
|
|
|
for ( j = 0 ; j < surfaceTest[i]->numFacets ; j++ ) {
|
|
|
|
f = surfaceTest[i]->facets + j;
|
|
|
|
w = AllocWinding( f->numBoundaries );
|
|
|
|
w->numpoints = f->numBoundaries;
|
|
|
|
memcpy( w->p, f->points, f->numBoundaries * 12 );
|
|
|
|
|
|
|
|
VectorCopy( f->surface, normal );
|
|
|
|
if ( side ) {
|
|
|
|
winding_t *t;
|
|
|
|
|
|
|
|
t = w;
|
|
|
|
w = ReverseWinding( t );
|
|
|
|
FreeWinding( t );
|
|
|
|
VectorSubtract( vec3_origin, normal, normal );
|
|
|
|
}
|
|
|
|
SubdivideAreaLight( ls, w, normal, lightSubdivide, qtrue );
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// normal polygon
|
|
|
|
|
|
|
|
w = AllocWinding( ds->numVerts );
|
|
|
|
w->numpoints = ds->numVerts;
|
|
|
|
for ( j = 0 ; j < ds->numVerts ; j++ ) {
|
|
|
|
VectorCopy( drawVerts[ds->firstVert+j].xyz, w->p[j] );
|
|
|
|
}
|
|
|
|
VectorCopy( ds->lightmapVecs[2], normal );
|
|
|
|
if ( side ) {
|
|
|
|
winding_t *t;
|
|
|
|
|
|
|
|
t = w;
|
|
|
|
w = ReverseWinding( t );
|
|
|
|
FreeWinding( t );
|
|
|
|
VectorSubtract( vec3_origin, normal, normal );
|
|
|
|
}
|
|
|
|
SubdivideAreaLight( ls, w, normal, lightSubdivide, qtrue );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_printf( "%5i light emitting surfaces\n", c_lightSurfaces );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
FindSkyBrushes
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void FindSkyBrushes( void ) {
|
|
|
|
int i, j;
|
|
|
|
dbrush_t *b;
|
|
|
|
skyBrush_t *sb;
|
|
|
|
shaderInfo_t *si;
|
|
|
|
dbrushside_t *s;
|
|
|
|
|
|
|
|
// find the brushes
|
|
|
|
for ( i = 0 ; i < numbrushes ; i++ ) {
|
|
|
|
b = &dbrushes[i];
|
|
|
|
for ( j = 0 ; j < b->numSides ; j++ ) {
|
|
|
|
s = &dbrushsides[ b->firstSide + j ];
|
|
|
|
if ( dshaders[ s->shaderNum ].surfaceFlags & SURF_SKY ) {
|
|
|
|
sb = &skyBrushes[ numSkyBrushes ];
|
|
|
|
sb->b = b;
|
|
|
|
sb->bounds[0][0] = -dplanes[ dbrushsides[ b->firstSide + 0 ].planeNum ].dist - 1;
|
|
|
|
sb->bounds[1][0] = dplanes[ dbrushsides[ b->firstSide + 1 ].planeNum ].dist + 1;
|
|
|
|
sb->bounds[0][1] = -dplanes[ dbrushsides[ b->firstSide + 2 ].planeNum ].dist - 1;
|
|
|
|
sb->bounds[1][1] = dplanes[ dbrushsides[ b->firstSide + 3 ].planeNum ].dist + 1;
|
|
|
|
sb->bounds[0][2] = -dplanes[ dbrushsides[ b->firstSide + 4 ].planeNum ].dist - 1;
|
|
|
|
sb->bounds[1][2] = dplanes[ dbrushsides[ b->firstSide + 5 ].planeNum ].dist + 1;
|
|
|
|
numSkyBrushes++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// default
|
|
|
|
VectorNormalize( sunDirection, sunDirection );
|
|
|
|
|
|
|
|
// find the sky shader
|
|
|
|
for ( i = 0 ; i < numDrawSurfaces ; i++ ) {
|
|
|
|
si = ShaderInfoForShader( dshaders[ drawSurfaces[i].shaderNum ].shader );
|
|
|
|
if ( si->surfaceFlags & SURF_SKY ) {
|
|
|
|
VectorCopy( si->sunLight, sunLight );
|
|
|
|
VectorCopy( si->sunDirection, sunDirection );
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================================================================
|
|
|
|
|
|
|
|
LIGHT SETUP
|
|
|
|
|
|
|
|
=================================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
FindTargetEntity
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
entity_t *FindTargetEntity( const char *target ) {
|
|
|
|
int i;
|
|
|
|
const char *n;
|
|
|
|
|
|
|
|
for ( i = 0 ; i < num_entities ; i++ ) {
|
|
|
|
n = ValueForKey (&entities[i], "targetname");
|
|
|
|
if ( !strcmp (n, target) ) {
|
|
|
|
return &entities[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
CreateEntityLights
|
|
|
|
=============
|
|
|
|
*/
|
|
|
|
void CreateEntityLights (void)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
light_t *dl;
|
|
|
|
entity_t *e, *e2;
|
|
|
|
const char *name;
|
|
|
|
const char *target;
|
|
|
|
vec3_t dest;
|
|
|
|
const char *_color;
|
|
|
|
float intensity;
|
|
|
|
int spawnflags;
|
|
|
|
|
|
|
|
//
|
|
|
|
// entities
|
|
|
|
//
|
|
|
|
for ( i = 0 ; i < num_entities ; i++ ) {
|
|
|
|
e = &entities[i];
|
|
|
|
name = ValueForKey (e, "classname");
|
|
|
|
if (strncmp (name, "light", 5))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
numPointLights++;
|
|
|
|
dl = malloc(sizeof(*dl));
|
|
|
|
memset (dl, 0, sizeof(*dl));
|
|
|
|
dl->next = lights;
|
|
|
|
lights = dl;
|
|
|
|
|
|
|
|
spawnflags = FloatForKey (e, "spawnflags");
|
|
|
|
if ( spawnflags & 1 ) {
|
|
|
|
dl->linearLight = qtrue;
|
|
|
|
}
|
|
|
|
|
|
|
|
GetVectorForKey (e, "origin", dl->origin);
|
|
|
|
dl->style = FloatForKey (e, "_style");
|
|
|
|
if (!dl->style)
|
|
|
|
dl->style = FloatForKey (e, "style");
|
|
|
|
if (dl->style < 0)
|
|
|
|
dl->style = 0;
|
|
|
|
|
|
|
|
intensity = FloatForKey (e, "light");
|
|
|
|
if (!intensity)
|
|
|
|
intensity = FloatForKey (e, "_light");
|
|
|
|
if (!intensity)
|
|
|
|
intensity = 300;
|
|
|
|
_color = ValueForKey (e, "_color");
|
|
|
|
if (_color && _color[0])
|
|
|
|
{
|
|
|
|
sscanf (_color, "%f %f %f", &dl->color[0],&dl->color[1],&dl->color[2]);
|
|
|
|
ColorNormalize (dl->color, dl->color);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
dl->color[0] = dl->color[1] = dl->color[2] = 1.0;
|
|
|
|
|
|
|
|
intensity = intensity * pointScale;
|
|
|
|
dl->photons = intensity;
|
|
|
|
|
|
|
|
dl->type = emit_point;
|
|
|
|
|
|
|
|
// lights with a target will be spotlights
|
|
|
|
target = ValueForKey (e, "target");
|
|
|
|
|
|
|
|
if ( target[0] ) {
|
|
|
|
float radius;
|
|
|
|
float dist;
|
|
|
|
|
|
|
|
e2 = FindTargetEntity (target);
|
|
|
|
if (!e2) {
|
|
|
|
_printf ("WARNING: light at (%i %i %i) has missing target\n",
|
|
|
|
(int)dl->origin[0], (int)dl->origin[1], (int)dl->origin[2]);
|
|
|
|
} else {
|
|
|
|
GetVectorForKey (e2, "origin", dest);
|
|
|
|
VectorSubtract (dest, dl->origin, dl->normal);
|
|
|
|
dist = VectorNormalize (dl->normal, dl->normal);
|
|
|
|
radius = FloatForKey (e, "radius");
|
|
|
|
if ( !radius ) {
|
|
|
|
radius = 64;
|
|
|
|
}
|
|
|
|
if ( !dist ) {
|
|
|
|
dist = 64;
|
|
|
|
}
|
|
|
|
dl->radiusByDist = (radius + 16) / dist;
|
|
|
|
dl->type = emit_spotlight;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//=================================================================
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
SetEntityOrigins
|
|
|
|
|
|
|
|
Find the offset values for inline models
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void SetEntityOrigins( void ) {
|
|
|
|
int i, j;
|
|
|
|
entity_t *e;
|
|
|
|
vec3_t origin;
|
|
|
|
const char *key;
|
|
|
|
int modelnum;
|
|
|
|
dmodel_t *dm;
|
|
|
|
|
|
|
|
for ( i=0 ; i < num_entities ; i++ ) {
|
|
|
|
e = &entities[i];
|
|
|
|
key = ValueForKey (e, "model");
|
|
|
|
if ( key[0] != '*' ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
modelnum = atoi( key + 1 );
|
|
|
|
dm = &dmodels[ modelnum ];
|
|
|
|
|
|
|
|
// set entity surface to true for all surfaces for this model
|
|
|
|
for ( j = 0 ; j < dm->numSurfaces ; j++ ) {
|
|
|
|
entitySurface[ dm->firstSurface + j ] = qtrue;
|
|
|
|
}
|
|
|
|
|
|
|
|
key = ValueForKey (e, "origin");
|
|
|
|
if ( !key[0] ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
GetVectorForKey ( e, "origin", origin );
|
|
|
|
|
|
|
|
// set origin for all surfaces for this model
|
|
|
|
for ( j = 0 ; j < dm->numSurfaces ; j++ ) {
|
|
|
|
VectorCopy( origin, surfaceOrigin[ dm->firstSurface + j ] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================================================================
|
|
|
|
|
|
|
|
|
|
|
|
=================================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define MAX_POINTS_ON_WINDINGS 64
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
PointToPolygonFormFactor
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
float PointToPolygonFormFactor( const vec3_t point, const vec3_t normal, const winding_t *w ) {
|
|
|
|
vec3_t triVector, triNormal;
|
|
|
|
int i, j;
|
|
|
|
vec3_t dirs[MAX_POINTS_ON_WINDING];
|
|
|
|
float total;
|
|
|
|
float dot, angle, facing;
|
|
|
|
|
|
|
|
for ( i = 0 ; i < w->numpoints ; i++ ) {
|
|
|
|
VectorSubtract( w->p[i], point, dirs[i] );
|
|
|
|
VectorNormalize( dirs[i], dirs[i] );
|
|
|
|
}
|
|
|
|
|
|
|
|
// duplicate first vertex to avoid mod operation
|
|
|
|
VectorCopy( dirs[0], dirs[i] );
|
|
|
|
|
|
|
|
total = 0;
|
|
|
|
for ( i = 0 ; i < w->numpoints ; i++ ) {
|
|
|
|
j = i+1;
|
|
|
|
dot = DotProduct( dirs[i], dirs[j] );
|
|
|
|
|
|
|
|
// roundoff can cause slight creep, which gives an IND from acos
|
|
|
|
if ( dot > 1.0 ) {
|
|
|
|
dot = 1.0;
|
|
|
|
} else if ( dot < -1.0 ) {
|
|
|
|
dot = -1.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
angle = acos( dot );
|
|
|
|
CrossProduct( dirs[i], dirs[j], triVector );
|
|
|
|
if ( VectorNormalize( triVector, triNormal ) < 0.0001 ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
facing = DotProduct( normal, triNormal );
|
|
|
|
total += facing * angle;
|
|
|
|
|
|
|
|
if ( total > 6.3 || total < -6.3 ) {
|
|
|
|
static qboolean printed;
|
|
|
|
|
|
|
|
if ( !printed ) {
|
|
|
|
printed = qtrue;
|
|
|
|
_printf( "WARNING: bad PointToPolygonFormFactor: %f at %1.1f %1.1f %1.1f from %1.1f %1.1f %1.1f\n", total,
|
|
|
|
w->p[i][0], w->p[i][1], w->p[i][2], point[0], point[1], point[2]);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
total /= 2*3.141592657; // now in the range of 0 to 1 over the entire incoming hemisphere
|
|
|
|
|
|
|
|
return total;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
FilterTrace
|
|
|
|
|
|
|
|
Returns 0 to 1.0 filter fractions for the given trace
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void FilterTrace( const vec3_t start, const vec3_t end, vec3_t filter ) {
|
|
|
|
float d1, d2;
|
|
|
|
filter_t *f;
|
|
|
|
int filterNum;
|
|
|
|
vec3_t point;
|
|
|
|
float frac;
|
|
|
|
int i;
|
|
|
|
float s, t;
|
|
|
|
int u, v;
|
|
|
|
int x, y;
|
|
|
|
byte *pixel;
|
|
|
|
float radius;
|
|
|
|
float len;
|
|
|
|
vec3_t total;
|
|
|
|
|
|
|
|
filter[0] = 1.0;
|
|
|
|
filter[1] = 1.0;
|
|
|
|
filter[2] = 1.0;
|
|
|
|
|
|
|
|
for ( filterNum = 0 ; filterNum < numFilters ; filterNum++ ) {
|
|
|
|
f = &filters[ filterNum ];
|
|
|
|
|
|
|
|
// see if the plane is crossed
|
|
|
|
d1 = DotProduct( start, f->plane ) - f->plane[3];
|
|
|
|
d2 = DotProduct( end, f->plane ) - f->plane[3];
|
|
|
|
|
|
|
|
if ( ( d1 < 0 ) == ( d2 < 0 ) ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate the crossing point
|
|
|
|
frac = d1 / ( d1 - d2 );
|
|
|
|
|
|
|
|
for ( i = 0 ; i < 3 ; i++ ) {
|
|
|
|
point[i] = start[i] + frac * ( end[i] - start[i] );
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorSubtract( point, f->origin, point );
|
|
|
|
|
|
|
|
s = DotProduct( point, f->vectors[0] );
|
|
|
|
t = 1.0 - DotProduct( point, f->vectors[1] );
|
|
|
|
if ( s < 0 || s >= 1.0 || t < 0 || t >= 1.0 ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// decide the filter size
|
|
|
|
radius = 10 * frac;
|
|
|
|
len = VectorLength( f->vectors[0] );
|
|
|
|
if ( !len ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
radius = radius * len * f->si->width;
|
|
|
|
|
|
|
|
// look up the filter, taking multiple samples
|
|
|
|
VectorClear( total );
|
|
|
|
for ( u = -1 ; u <= 1 ; u++ ) {
|
|
|
|
for ( v = -1 ; v <=1 ; v++ ) {
|
|
|
|
x = s * f->si->width + u * radius;
|
|
|
|
if ( x < 0 ) {
|
|
|
|
x = 0;
|
|
|
|
}
|
|
|
|
if ( x >= f->si->width ) {
|
|
|
|
x = f->si->width - 1;
|
|
|
|
}
|
|
|
|
y = t * f->si->height + v * radius;
|
|
|
|
if ( y < 0 ) {
|
|
|
|
y = 0;
|
|
|
|
}
|
|
|
|
if ( y >= f->si->height ) {
|
|
|
|
y = f->si->height - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
pixel = f->si->pixels + ( y * f->si->width + x ) * 4;
|
|
|
|
total[0] += pixel[0];
|
|
|
|
total[1] += pixel[1];
|
|
|
|
total[2] += pixel[2];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
filter[0] *= total[0]/(255.0*9);
|
|
|
|
filter[1] *= total[1]/(255.0*9);
|
|
|
|
filter[2] *= total[2]/(255.0*9);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
SunToPoint
|
|
|
|
|
|
|
|
Returns an amount of light to add at the point
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
int c_sunHit, c_sunMiss;
|
|
|
|
void SunToPoint( const vec3_t origin, traceWork_t *tw, vec3_t addLight ) {
|
|
|
|
int i;
|
|
|
|
trace_t trace;
|
|
|
|
skyBrush_t *b;
|
|
|
|
vec3_t end;
|
|
|
|
|
|
|
|
if ( !numSkyBrushes ) {
|
|
|
|
VectorClear( addLight );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorMA( origin, MAX_WORLD_COORD * 2, sunDirection, end );
|
|
|
|
|
|
|
|
TraceLine( origin, end, &trace, qtrue, tw );
|
|
|
|
|
|
|
|
// see if trace.hit is inside a sky brush
|
|
|
|
for ( i = 0 ; i < numSkyBrushes ; i++) {
|
|
|
|
b = &skyBrushes[ i ];
|
|
|
|
|
|
|
|
// this assumes that sky brushes are axial...
|
|
|
|
if ( trace.hit[0] < b->bounds[0][0]
|
|
|
|
|| trace.hit[0] > b->bounds[1][0]
|
|
|
|
|| trace.hit[1] < b->bounds[0][1]
|
|
|
|
|| trace.hit[1] > b->bounds[1][1]
|
|
|
|
|| trace.hit[2] < b->bounds[0][2]
|
|
|
|
|| trace.hit[2] > b->bounds[1][2] ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// trace again to get intermediate filters
|
|
|
|
TraceLine( origin, trace.hit, &trace, qtrue, tw );
|
|
|
|
|
|
|
|
// we hit the sky, so add sunlight
|
|
|
|
if ( numthreads == 1 ) {
|
|
|
|
c_sunHit++;
|
|
|
|
}
|
|
|
|
addLight[0] = trace.filter[0] * sunLight[0];
|
|
|
|
addLight[1] = trace.filter[1] * sunLight[1];
|
|
|
|
addLight[2] = trace.filter[2] * sunLight[2];
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( numthreads == 1 ) {
|
|
|
|
c_sunMiss++;
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorClear( addLight );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
SunToPlane
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void SunToPlane( const vec3_t origin, const vec3_t normal, vec3_t color, traceWork_t *tw ) {
|
|
|
|
float angle;
|
|
|
|
vec3_t sunColor;
|
|
|
|
|
|
|
|
if ( !numSkyBrushes ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
angle = DotProduct( normal, sunDirection );
|
|
|
|
if ( angle <= 0 ) {
|
|
|
|
return; // facing away
|
|
|
|
}
|
|
|
|
|
|
|
|
SunToPoint( origin, tw, sunColor );
|
|
|
|
VectorMA( color, angle, sunColor, color );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
LightingAtSample
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void LightingAtSample( vec3_t origin, vec3_t normal, vec3_t color,
|
|
|
|
qboolean testOcclusion, qboolean forceSunLight, traceWork_t *tw ) {
|
|
|
|
light_t *light;
|
|
|
|
trace_t trace;
|
|
|
|
float angle;
|
|
|
|
float add;
|
|
|
|
float dist;
|
|
|
|
vec3_t dir;
|
|
|
|
|
|
|
|
VectorCopy( ambientColor, color );
|
|
|
|
|
|
|
|
// trace to all the lights
|
|
|
|
for ( light = lights ; light ; light = light->next ) {
|
|
|
|
|
|
|
|
//MrE: if the light is behind the surface
|
|
|
|
if ( DotProduct(light->origin, normal) - DotProduct(normal, origin) < 0 )
|
|
|
|
continue;
|
|
|
|
// testing exact PTPFF
|
|
|
|
if ( exactPointToPolygon && light->type == emit_area ) {
|
|
|
|
float factor;
|
|
|
|
float d;
|
|
|
|
vec3_t pushedOrigin;
|
|
|
|
|
|
|
|
// see if the point is behind the light
|
|
|
|
d = DotProduct( origin, light->normal ) - light->dist;
|
|
|
|
if ( !light->twosided ) {
|
|
|
|
if ( d < -1 ) {
|
|
|
|
continue; // point is behind light
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// test occlusion and find light filters
|
|
|
|
// clip the line, tracing from the surface towards the light
|
|
|
|
if ( !notrace && testOcclusion ) {
|
|
|
|
TraceLine( origin, light->origin, &trace, qfalse, tw );
|
|
|
|
|
|
|
|
// other light rays must not hit anything
|
|
|
|
if ( trace.passSolid ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
trace.filter[0] = 1.0;
|
|
|
|
trace.filter[1] = 1.0;
|
|
|
|
trace.filter[2] = 1.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// nudge the point so that it is clearly forward of the light
|
|
|
|
// so that surfaces meeting a light emiter don't get black edges
|
|
|
|
if ( d > -8 && d < 8 ) {
|
|
|
|
VectorMA( origin, (8-d), light->normal, pushedOrigin );
|
|
|
|
} else {
|
|
|
|
VectorCopy( origin, pushedOrigin );
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate the contribution
|
|
|
|
factor = PointToPolygonFormFactor( pushedOrigin, normal, light->w );
|
|
|
|
if ( factor <= 0 ) {
|
|
|
|
if ( light->twosided ) {
|
|
|
|
factor = -factor;
|
|
|
|
} else {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
color[0] += factor * light->emitColor[0] * trace.filter[0];
|
|
|
|
color[1] += factor * light->emitColor[1] * trace.filter[1];
|
|
|
|
color[2] += factor * light->emitColor[2] * trace.filter[2];
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate the amount of light at this sample
|
|
|
|
if ( light->type == emit_point ) {
|
|
|
|
VectorSubtract( light->origin, origin, dir );
|
|
|
|
dist = VectorNormalize( dir, dir );
|
|
|
|
// clamp the distance to prevent super hot spots
|
|
|
|
if ( dist < 16 ) {
|
|
|
|
dist = 16;
|
|
|
|
}
|
|
|
|
angle = DotProduct( normal, dir );
|
|
|
|
if ( light->linearLight ) {
|
|
|
|
add = angle * light->photons * linearScale - dist;
|
|
|
|
if ( add < 0 ) {
|
|
|
|
add = 0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
add = light->photons / ( dist * dist ) * angle;
|
|
|
|
}
|
|
|
|
} else if ( light->type == emit_spotlight ) {
|
|
|
|
float distByNormal;
|
|
|
|
vec3_t pointAtDist;
|
|
|
|
float radiusAtDist;
|
|
|
|
float sampleRadius;
|
|
|
|
vec3_t distToSample;
|
|
|
|
float coneScale;
|
|
|
|
|
|
|
|
VectorSubtract( light->origin, origin, dir );
|
|
|
|
|
|
|
|
distByNormal = -DotProduct( dir, light->normal );
|
|
|
|
if ( distByNormal < 0 ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
VectorMA( light->origin, distByNormal, light->normal, pointAtDist );
|
|
|
|
radiusAtDist = light->radiusByDist * distByNormal;
|
|
|
|
|
|
|
|
VectorSubtract( origin, pointAtDist, distToSample );
|
|
|
|
sampleRadius = VectorLength( distToSample );
|
|
|
|
|
|
|
|
if ( sampleRadius >= radiusAtDist ) {
|
|
|
|
continue; // outside the cone
|
|
|
|
}
|
|
|
|
if ( sampleRadius <= radiusAtDist - 32 ) {
|
|
|
|
coneScale = 1.0; // fully inside
|
|
|
|
} else {
|
|
|
|
coneScale = ( radiusAtDist - sampleRadius ) / 32.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
dist = VectorNormalize( dir, dir );
|
|
|
|
// clamp the distance to prevent super hot spots
|
|
|
|
if ( dist < 16 ) {
|
|
|
|
dist = 16;
|
|
|
|
}
|
|
|
|
angle = DotProduct( normal, dir );
|
|
|
|
add = light->photons / ( dist * dist ) * angle * coneScale;
|
|
|
|
|
|
|
|
} else if ( light->type == emit_area ) {
|
|
|
|
VectorSubtract( light->origin, origin, dir );
|
|
|
|
dist = VectorNormalize( dir, dir );
|
|
|
|
// clamp the distance to prevent super hot spots
|
|
|
|
if ( dist < 16 ) {
|
|
|
|
dist = 16;
|
|
|
|
}
|
|
|
|
angle = DotProduct( normal, dir );
|
|
|
|
if ( angle <= 0 ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
angle *= -DotProduct( light->normal, dir );
|
|
|
|
if ( angle <= 0 ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( light->linearLight ) {
|
|
|
|
add = angle * light->photons * linearScale - dist;
|
|
|
|
if ( add < 0 ) {
|
|
|
|
add = 0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
add = light->photons / ( dist * dist ) * angle;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( add <= 1.0 ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// clip the line, tracing from the surface towards the light
|
|
|
|
if ( !notrace && testOcclusion ) {
|
|
|
|
TraceLine( origin, light->origin, &trace, qfalse, tw );
|
|
|
|
|
|
|
|
// other light rays must not hit anything
|
|
|
|
if ( trace.passSolid ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
trace.filter[0] = 1;
|
|
|
|
trace.filter[1] = 1;
|
|
|
|
trace.filter[2] = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// add the result
|
|
|
|
color[0] += add * light->color[0] * trace.filter[0];
|
|
|
|
color[1] += add * light->color[1] * trace.filter[1];
|
|
|
|
color[2] += add * light->color[2] * trace.filter[2];
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// trace directly to the sun
|
|
|
|
//
|
|
|
|
if ( testOcclusion || forceSunLight ) {
|
|
|
|
SunToPlane( origin, normal, color, tw );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
PrintOccluded
|
|
|
|
|
|
|
|
For debugging
|
|
|
|
=============
|
|
|
|
*/
|
|
|
|
void PrintOccluded( byte occluded[LIGHTMAP_WIDTH*EXTRASCALE][LIGHTMAP_HEIGHT*EXTRASCALE],
|
|
|
|
int width, int height ) {
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
_printf( "\n" );
|
|
|
|
|
|
|
|
for ( i = 0 ; i < height ; i++ ) {
|
|
|
|
for ( j = 0 ; j < width ; j++ ) {
|
|
|
|
_printf("%i", (int)occluded[j][i] );
|
|
|
|
}
|
|
|
|
_printf( "\n" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
VertexLighting
|
|
|
|
|
|
|
|
Vertex lighting will completely ignore occlusion, because
|
|
|
|
shadows would not be resolvable anyway.
|
|
|
|
=============
|
|
|
|
*/
|
|
|
|
void VertexLighting( dsurface_t *ds, qboolean testOcclusion, qboolean forceSunLight, float scale, traceWork_t *tw ) {
|
|
|
|
int i, j;
|
|
|
|
drawVert_t *dv;
|
|
|
|
vec3_t sample, normal;
|
|
|
|
float max;
|
|
|
|
|
|
|
|
VectorCopy( ds->lightmapVecs[2], normal );
|
|
|
|
|
|
|
|
// generate vertex lighting
|
|
|
|
for ( i = 0 ; i < ds->numVerts ; i++ ) {
|
|
|
|
dv = &drawVerts[ ds->firstVert + i ];
|
|
|
|
|
|
|
|
if ( ds->patchWidth ) {
|
|
|
|
LightingAtSample( dv->xyz, dv->normal, sample, testOcclusion, forceSunLight, tw );
|
|
|
|
}
|
|
|
|
else if (ds->surfaceType == MST_TRIANGLE_SOUP) {
|
|
|
|
LightingAtSample( dv->xyz, dv->normal, sample, testOcclusion, forceSunLight, tw );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
LightingAtSample( dv->xyz, normal, sample, testOcclusion, forceSunLight, tw );
|
|
|
|
}
|
|
|
|
|
|
|
|
if (scale >= 0)
|
|
|
|
VectorScale(sample, scale, sample);
|
|
|
|
// clamp with color normalization
|
|
|
|
max = sample[0];
|
|
|
|
if ( sample[1] > max ) {
|
|
|
|
max = sample[1];
|
|
|
|
}
|
|
|
|
if ( sample[2] > max ) {
|
|
|
|
max = sample[2];
|
|
|
|
}
|
|
|
|
if ( max > 255 ) {
|
|
|
|
VectorScale( sample, 255/max, sample );
|
|
|
|
}
|
|
|
|
|
|
|
|
// save the sample
|
|
|
|
for ( j = 0 ; j < 3 ; j++ ) {
|
|
|
|
if ( sample[j] > 255 ) {
|
|
|
|
sample[j] = 255;
|
|
|
|
}
|
|
|
|
dv->color[j] = sample[j];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't bother writing alpha since it will already be set to 255,
|
|
|
|
// plus we don't want to write over alpha generated by SetTerrainTextures
|
|
|
|
//dv->color[3] = 255;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
LinearSubdivideMesh
|
|
|
|
|
|
|
|
For extra lighting, just midpoint one of the axis.
|
|
|
|
The edges are clamped at the original edges.
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
mesh_t *LinearSubdivideMesh( mesh_t *in ) {
|
|
|
|
int i, j;
|
|
|
|
mesh_t *out;
|
|
|
|
drawVert_t *v1, *v2, *vout;
|
|
|
|
|
|
|
|
out = malloc( sizeof( *out ) );
|
|
|
|
|
|
|
|
out->width = in->width * 2;
|
|
|
|
out->height = in->height;
|
|
|
|
out->verts = malloc( out->width * out->height * sizeof(*out->verts) );
|
|
|
|
for ( j = 0 ; j < in->height ; j++ ) {
|
|
|
|
out->verts[ j * out->width + 0 ] = in->verts[ j * in->width + 0 ];
|
|
|
|
out->verts[ j * out->width + out->width - 1 ] = in->verts[ j * in->width + in->width - 1 ];
|
|
|
|
for ( i = 1 ; i < out->width - 1 ; i+= 2 ) {
|
|
|
|
v1 = in->verts + j * in->width + (i >> 1);
|
|
|
|
v2 = v1 + 1;
|
|
|
|
vout = out->verts + j * out->width + i;
|
|
|
|
|
|
|
|
vout->xyz[0] = 0.75 * v1->xyz[0] + 0.25 * v2->xyz[0];
|
|
|
|
vout->xyz[1] = 0.75 * v1->xyz[1] + 0.25 * v2->xyz[1];
|
|
|
|
vout->xyz[2] = 0.75 * v1->xyz[2] + 0.25 * v2->xyz[2];
|
|
|
|
|
|
|
|
vout->normal[0] = 0.75 * v1->normal[0] + 0.25 * v2->normal[0];
|
|
|
|
vout->normal[1] = 0.75 * v1->normal[1] + 0.25 * v2->normal[1];
|
|
|
|
vout->normal[2] = 0.75 * v1->normal[2] + 0.25 * v2->normal[2];
|
|
|
|
|
|
|
|
VectorNormalize( vout->normal, vout->normal );
|
|
|
|
|
|
|
|
vout++;
|
|
|
|
|
|
|
|
vout->xyz[0] = 0.25 * v1->xyz[0] + 0.75 * v2->xyz[0];
|
|
|
|
vout->xyz[1] = 0.25 * v1->xyz[1] + 0.75 * v2->xyz[1];
|
|
|
|
vout->xyz[2] = 0.25 * v1->xyz[2] + 0.75 * v2->xyz[2];
|
|
|
|
|
|
|
|
vout->normal[0] = 0.25 * v1->normal[0] + 0.75 * v2->normal[0];
|
|
|
|
vout->normal[1] = 0.25 * v1->normal[1] + 0.75 * v2->normal[1];
|
|
|
|
vout->normal[2] = 0.25 * v1->normal[2] + 0.75 * v2->normal[2];
|
|
|
|
|
|
|
|
VectorNormalize( vout->normal, vout->normal );
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FreeMesh( in );
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==============
|
|
|
|
ColorToBytes
|
|
|
|
==============
|
|
|
|
*/
|
|
|
|
void ColorToBytes( const float *color, byte *colorBytes ) {
|
|
|
|
float max;
|
|
|
|
vec3_t sample;
|
|
|
|
|
|
|
|
VectorCopy( color, sample );
|
|
|
|
|
|
|
|
// clamp with color normalization
|
|
|
|
max = sample[0];
|
|
|
|
if ( sample[1] > max ) {
|
|
|
|
max = sample[1];
|
|
|
|
}
|
|
|
|
if ( sample[2] > max ) {
|
|
|
|
max = sample[2];
|
|
|
|
}
|
|
|
|
if ( max > 255 ) {
|
|
|
|
VectorScale( sample, 255/max, sample );
|
|
|
|
}
|
|
|
|
colorBytes[ 0 ] = sample[0];
|
|
|
|
colorBytes[ 1 ] = sample[1];
|
|
|
|
colorBytes[ 2 ] = sample[2];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
TraceLtm
|
|
|
|
=============
|
|
|
|
*/
|
|
|
|
void TraceLtm( int num ) {
|
|
|
|
dsurface_t *ds;
|
|
|
|
int i, j, k;
|
|
|
|
int x, y;
|
|
|
|
int position, numPositions;
|
|
|
|
vec3_t base, origin, normal;
|
|
|
|
byte occluded[LIGHTMAP_WIDTH*EXTRASCALE][LIGHTMAP_HEIGHT*EXTRASCALE];
|
|
|
|
vec3_t color[LIGHTMAP_WIDTH*EXTRASCALE][LIGHTMAP_HEIGHT*EXTRASCALE];
|
|
|
|
traceWork_t tw;
|
|
|
|
vec3_t average;
|
|
|
|
int count;
|
|
|
|
mesh_t srcMesh, *mesh, *subdivided;
|
|
|
|
shaderInfo_t *si;
|
|
|
|
static float nudge[2][9] = {
|
|
|
|
{ 0, -1, 0, 1, -1, 1, -1, 0, 1 },
|
|
|
|
{ 0, -1, -1, -1, 0, 0, 1, 1, 1 }
|
|
|
|
};
|
|
|
|
int sampleWidth, sampleHeight, ssize;
|
|
|
|
vec3_t lightmapOrigin, lightmapVecs[2];
|
|
|
|
int widthtable[LIGHTMAP_WIDTH], heighttable[LIGHTMAP_WIDTH];
|
|
|
|
|
|
|
|
ds = &drawSurfaces[num];
|
|
|
|
si = ShaderInfoForShader( dshaders[ ds->shaderNum].shader );
|
|
|
|
|
|
|
|
// vertex-lit triangle model
|
|
|
|
if ( ds->surfaceType == MST_TRIANGLE_SOUP ) {
|
|
|
|
VertexLighting( ds, !si->noVertexShadows, si->forceSunLight, 1.0, &tw );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ds->lightmapNum == -1 ) {
|
|
|
|
return; // doesn't need lighting at all
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!novertexlighting) {
|
|
|
|
// calculate the vertex lighting for gouraud shade mode
|
|
|
|
VertexLighting( ds, si->vertexShadows, si->forceSunLight, si->vertexScale, &tw );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ds->lightmapNum < 0 ) {
|
|
|
|
return; // doesn't need lightmap lighting
|
|
|
|
}
|
|
|
|
|
|
|
|
si = ShaderInfoForShader( dshaders[ ds->shaderNum].shader );
|
|
|
|
ssize = samplesize;
|
|
|
|
if (si->lightmapSampleSize)
|
|
|
|
ssize = si->lightmapSampleSize;
|
|
|
|
|
|
|
|
if (si->patchShadows)
|
|
|
|
tw.patchshadows = qtrue;
|
|
|
|
else
|
|
|
|
tw.patchshadows = patchshadows;
|
|
|
|
|
|
|
|
if ( ds->surfaceType == MST_PATCH ) {
|
|
|
|
srcMesh.width = ds->patchWidth;
|
|
|
|
srcMesh.height = ds->patchHeight;
|
|
|
|
srcMesh.verts = drawVerts + ds->firstVert;
|
|
|
|
mesh = SubdivideMesh( srcMesh, 8, 999 );
|
|
|
|
PutMeshOnCurve( *mesh );
|
|
|
|
MakeMeshNormals( *mesh );
|
|
|
|
|
|
|
|
subdivided = RemoveLinearMeshColumnsRows( mesh );
|
|
|
|
FreeMesh(mesh);
|
|
|
|
|
|
|
|
mesh = SubdivideMeshQuads( subdivided, ssize, LIGHTMAP_WIDTH, widthtable, heighttable);
|
|
|
|
if ( mesh->width != ds->lightmapWidth || mesh->height != ds->lightmapHeight ) {
|
|
|
|
Error( "Mesh lightmap miscount");
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( extra ) {
|
|
|
|
mesh_t *mp;
|
|
|
|
|
|
|
|
// chop it up for more light samples (leaking memory...)
|
|
|
|
mp = mesh;//CopyMesh( mesh );
|
|
|
|
mp = LinearSubdivideMesh( mp );
|
|
|
|
mp = TransposeMesh( mp );
|
|
|
|
mp = LinearSubdivideMesh( mp );
|
|
|
|
mp = TransposeMesh( mp );
|
|
|
|
|
|
|
|
mesh = mp;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
VectorCopy( ds->lightmapVecs[2], normal );
|
|
|
|
|
|
|
|
if ( !extra ) {
|
|
|
|
VectorCopy( ds->lightmapOrigin, lightmapOrigin );
|
|
|
|
VectorCopy( ds->lightmapVecs[0], lightmapVecs[0] );
|
|
|
|
VectorCopy( ds->lightmapVecs[1], lightmapVecs[1] );
|
|
|
|
} else {
|
|
|
|
// sample at a closer spacing for antialiasing
|
|
|
|
VectorCopy( ds->lightmapOrigin, lightmapOrigin );
|
|
|
|
VectorScale( ds->lightmapVecs[0], 0.5, lightmapVecs[0] );
|
|
|
|
VectorScale( ds->lightmapVecs[1], 0.5, lightmapVecs[1] );
|
|
|
|
VectorMA( lightmapOrigin, -0.5, lightmapVecs[0], lightmapOrigin );
|
|
|
|
VectorMA( lightmapOrigin, -0.5, lightmapVecs[1], lightmapOrigin );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( extra ) {
|
|
|
|
sampleWidth = ds->lightmapWidth * 2;
|
|
|
|
sampleHeight = ds->lightmapHeight * 2;
|
|
|
|
} else {
|
|
|
|
sampleWidth = ds->lightmapWidth;
|
|
|
|
sampleHeight = ds->lightmapHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset ( color, 0, sizeof( color ) );
|
|
|
|
|
|
|
|
// determine which samples are occluded
|
|
|
|
memset ( occluded, 0, sizeof( occluded ) );
|
|
|
|
for ( i = 0 ; i < sampleWidth ; i++ ) {
|
|
|
|
for ( j = 0 ; j < sampleHeight ; j++ ) {
|
|
|
|
|
|
|
|
if ( ds->patchWidth ) {
|
|
|
|
numPositions = 9;
|
|
|
|
VectorCopy( mesh->verts[j*mesh->width+i].normal, normal );
|
|
|
|
// VectorNormalize( normal, normal );
|
|
|
|
// push off of the curve a bit
|
|
|
|
VectorMA( mesh->verts[j*mesh->width+i].xyz, 1, normal, base );
|
|
|
|
|
|
|
|
MakeNormalVectors( normal, lightmapVecs[0], lightmapVecs[1] );
|
|
|
|
} else {
|
|
|
|
numPositions = 9;
|
|
|
|
for ( k = 0 ; k < 3 ; k++ ) {
|
|
|
|
base[k] = lightmapOrigin[k] + normal[k]
|
|
|
|
+ i * lightmapVecs[0][k]
|
|
|
|
+ j * lightmapVecs[1][k];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
VectorAdd( base, surfaceOrigin[ num ], base );
|
|
|
|
|
|
|
|
// we may need to slightly nudge the sample point
|
|
|
|
// if directly on a wall
|
|
|
|
for ( position = 0 ; position < numPositions ; position++ ) {
|
|
|
|
// calculate lightmap sample position
|
|
|
|
for ( k = 0 ; k < 3 ; k++ ) {
|
|
|
|
origin[k] = base[k] +
|
|
|
|
+ ( nudge[0][position]/16 ) * lightmapVecs[0][k]
|
|
|
|
+ ( nudge[1][position]/16 ) * lightmapVecs[1][k];
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( notrace ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if ( !PointInSolid( origin ) ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if none of the nudges worked, this sample is occluded
|
|
|
|
if ( position == numPositions ) {
|
|
|
|
occluded[i][j] = qtrue;
|
|
|
|
if ( numthreads == 1 ) {
|
|
|
|
c_occluded++;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( numthreads == 1 ) {
|
|
|
|
c_visible++;
|
|
|
|
}
|
|
|
|
occluded[i][j] = qfalse;
|
|
|
|
LightingAtSample( origin, normal, color[i][j], qtrue, qfalse, &tw );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( dump ) {
|
|
|
|
PrintOccluded( occluded, sampleWidth, sampleHeight );
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate average values for occluded samples
|
|
|
|
for ( i = 0 ; i < sampleWidth ; i++ ) {
|
|
|
|
for ( j = 0 ; j < sampleHeight ; j++ ) {
|
|
|
|
if ( !occluded[i][j] ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// scan all surrounding samples
|
|
|
|
count = 0;
|
|
|
|
VectorClear( average );
|
|
|
|
for ( x = -1 ; x <= 1; x++ ) {
|
|
|
|
for ( y = -1 ; y <= 1 ; y++ ) {
|
|
|
|
if ( i + x < 0 || i + x >= sampleWidth ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ( j + y < 0 || j + y >= sampleHeight ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ( occluded[i+x][j+y] ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
count++;
|
|
|
|
VectorAdd( color[i+x][j+y], average, average );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( count ) {
|
|
|
|
VectorScale( average, 1.0/count, color[i][j] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// average together the values if we are extra sampling
|
|
|
|
if ( ds->lightmapWidth != sampleWidth ) {
|
|
|
|
for ( i = 0 ; i < ds->lightmapWidth ; i++ ) {
|
|
|
|
for ( j = 0 ; j < ds->lightmapHeight ; j++ ) {
|
|
|
|
for ( k = 0 ; k < 3 ; k++ ) {
|
|
|
|
float value, coverage;
|
|
|
|
|
|
|
|
value = color[i*2][j*2][k] + color[i*2][j*2+1][k] +
|
|
|
|
color[i*2+1][j*2][k] + color[i*2+1][j*2+1][k];
|
|
|
|
coverage = 4;
|
|
|
|
if ( extraWide ) {
|
|
|
|
// wider than box filter
|
|
|
|
if ( i > 0 ) {
|
|
|
|
value += color[i*2-1][j*2][k] + color[i*2-1][j*2+1][k];
|
|
|
|
value += color[i*2-2][j*2][k] + color[i*2-2][j*2+1][k];
|
|
|
|
coverage += 4;
|
|
|
|
}
|
|
|
|
if ( i < ds->lightmapWidth - 1 ) {
|
|
|
|
value += color[i*2+2][j*2][k] + color[i*2+2][j*2+1][k];
|
|
|
|
value += color[i*2+3][j*2][k] + color[i*2+3][j*2+1][k];
|
|
|
|
coverage += 4;
|
|
|
|
}
|
|
|
|
if ( j > 0 ) {
|
|
|
|
value += color[i*2][j*2-1][k] + color[i*2+1][j*2-1][k];
|
|
|
|
value += color[i*2][j*2-2][k] + color[i*2+1][j*2-2][k];
|
|
|
|
coverage += 4;
|
|
|
|
}
|
|
|
|
if ( j < ds->lightmapHeight - 1 ) {
|
|
|
|
value += color[i*2][j*2+2][k] + color[i*2+1][j*2+2][k];
|
|
|
|
value += color[i*2][j*2+3][k] + color[i*2+1][j*2+3][k];
|
|
|
|
coverage += 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
color[i][j][k] = value / coverage;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// optionally create a debugging border around the lightmap
|
|
|
|
if ( lightmapBorder ) {
|
|
|
|
for ( i = 0 ; i < ds->lightmapWidth ; i++ ) {
|
|
|
|
color[i][0][0] = 255;
|
|
|
|
color[i][0][1] = 0;
|
|
|
|
color[i][0][2] = 0;
|
|
|
|
|
|
|
|
color[i][ds->lightmapHeight-1][0] = 255;
|
|
|
|
color[i][ds->lightmapHeight-1][1] = 0;
|
|
|
|
color[i][ds->lightmapHeight-1][2] = 0;
|
|
|
|
}
|
|
|
|
for ( i = 0 ; i < ds->lightmapHeight ; i++ ) {
|
|
|
|
color[0][i][0] = 255;
|
|
|
|
color[0][i][1] = 0;
|
|
|
|
color[0][i][2] = 0;
|
|
|
|
|
|
|
|
color[ds->lightmapWidth-1][i][0] = 255;
|
|
|
|
color[ds->lightmapWidth-1][i][1] = 0;
|
|
|
|
color[ds->lightmapWidth-1][i][2] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// clamp the colors to bytes and store off
|
|
|
|
for ( i = 0 ; i < ds->lightmapWidth ; i++ ) {
|
|
|
|
for ( j = 0 ; j < ds->lightmapHeight ; j++ ) {
|
|
|
|
k = ( ds->lightmapNum * LIGHTMAP_HEIGHT + ds->lightmapY + j)
|
|
|
|
* LIGHTMAP_WIDTH + ds->lightmapX + i;
|
|
|
|
|
|
|
|
ColorToBytes( color[i][j], lightBytes + k*3 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ds->surfaceType == MST_PATCH)
|
|
|
|
{
|
|
|
|
FreeMesh(mesh);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//=============================================================================
|
|
|
|
|
|
|
|
vec3_t gridMins;
|
|
|
|
vec3_t gridSize = { 64, 64, 128 };
|
|
|
|
int gridBounds[3];
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
========================
|
|
|
|
LightContributionToPoint
|
|
|
|
========================
|
|
|
|
*/
|
|
|
|
qboolean LightContributionToPoint( const light_t *light, const vec3_t origin,
|
|
|
|
vec3_t color, traceWork_t *tw ) {
|
|
|
|
trace_t trace;
|
|
|
|
float add;
|
|
|
|
|
|
|
|
add = 0;
|
|
|
|
|
|
|
|
VectorClear( color );
|
|
|
|
|
|
|
|
// testing exact PTPFF
|
|
|
|
if ( exactPointToPolygon && light->type == emit_area ) {
|
|
|
|
float factor;
|
|
|
|
float d;
|
|
|
|
vec3_t normal;
|
|
|
|
|
|
|
|
// see if the point is behind the light
|
|
|
|
d = DotProduct( origin, light->normal ) - light->dist;
|
|
|
|
if ( !light->twosided ) {
|
|
|
|
if ( d < 1 ) {
|
|
|
|
return qfalse; // point is behind light
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// test occlusion
|
|
|
|
// clip the line, tracing from the surface towards the light
|
|
|
|
TraceLine( origin, light->origin, &trace, qfalse, tw );
|
|
|
|
if ( trace.passSolid ) {
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate the contribution
|
|
|
|
VectorSubtract( light->origin, origin, normal );
|
|
|
|
if ( VectorNormalize( normal, normal ) == 0 ) {
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
factor = PointToPolygonFormFactor( origin, normal, light->w );
|
|
|
|
if ( factor <= 0 ) {
|
|
|
|
if ( light->twosided ) {
|
|
|
|
factor = -factor;
|
|
|
|
} else {
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
VectorScale( light->emitColor, factor, color );
|
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// calculate the amount of light at this sample
|
|
|
|
if ( light->type == emit_point || light->type == emit_spotlight ) {
|
|
|
|
vec3_t dir;
|
|
|
|
float dist;
|
|
|
|
|
|
|
|
VectorSubtract( light->origin, origin, dir );
|
|
|
|
dist = VectorLength( dir );
|
|
|
|
// clamp the distance to prevent super hot spots
|
|
|
|
if ( dist < 16 ) {
|
|
|
|
dist = 16;
|
|
|
|
}
|
|
|
|
if ( light->linearLight ) {
|
|
|
|
add = light->photons * linearScale - dist;
|
|
|
|
if ( add < 0 ) {
|
|
|
|
add = 0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
add = light->photons / ( dist * dist );
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( add <= 1.0 ) {
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
// clip the line, tracing from the surface towards the light
|
|
|
|
TraceLine( origin, light->origin, &trace, qfalse, tw );
|
|
|
|
|
|
|
|
// other light rays must not hit anything
|
|
|
|
if ( trace.passSolid ) {
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
// add the result
|
|
|
|
color[0] = add * light->color[0];
|
|
|
|
color[1] = add * light->color[1];
|
|
|
|
color[2] = add * light->color[2];
|
|
|
|
|
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
vec3_t dir;
|
|
|
|
vec3_t color;
|
|
|
|
} contribution_t;
|
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
TraceGrid
|
|
|
|
|
|
|
|
Grid samples are foe quickly determining the lighting
|
|
|
|
of dynamically placed entities in the world
|
|
|
|
=============
|
|
|
|
*/
|
|
|
|
#define MAX_CONTRIBUTIONS 1024
|
|
|
|
void TraceGrid( int num ) {
|
|
|
|
int x, y, z;
|
|
|
|
vec3_t origin;
|
|
|
|
light_t *light;
|
|
|
|
vec3_t color;
|
|
|
|
int mod;
|
|
|
|
vec3_t directedColor;
|
|
|
|
vec3_t summedDir;
|
|
|
|
contribution_t contributions[MAX_CONTRIBUTIONS];
|
|
|
|
int numCon;
|
|
|
|
int i;
|
|
|
|
traceWork_t tw;
|
|
|
|
float addSize;
|
|
|
|
|
|
|
|
mod = num;
|
|
|
|
z = mod / ( gridBounds[0] * gridBounds[1] );
|
|
|
|
mod -= z * ( gridBounds[0] * gridBounds[1] );
|
|
|
|
|
|
|
|
y = mod / gridBounds[0];
|
|
|
|
mod -= y * gridBounds[0];
|
|
|
|
|
|
|
|
x = mod;
|
|
|
|
|
|
|
|
origin[0] = gridMins[0] + x * gridSize[0];
|
|
|
|
origin[1] = gridMins[1] + y * gridSize[1];
|
|
|
|
origin[2] = gridMins[2] + z * gridSize[2];
|
|
|
|
|
|
|
|
if ( PointInSolid( origin ) ) {
|
|
|
|
vec3_t baseOrigin;
|
|
|
|
int step;
|
|
|
|
|
|
|
|
VectorCopy( origin, baseOrigin );
|
|
|
|
|
|
|
|
// try to nudge the origin around to find a valid point
|
|
|
|
for ( step = 9 ; step <= 18 ; step += 9 ) {
|
|
|
|
for ( i = 0 ; i < 8 ; i++ ) {
|
|
|
|
VectorCopy( baseOrigin, origin );
|
|
|
|
if ( i & 1 ) {
|
|
|
|
origin[0] += step;
|
|
|
|
} else {
|
|
|
|
origin[0] -= step;
|
|
|
|
}
|
|
|
|
if ( i & 2 ) {
|
|
|
|
origin[1] += step;
|
|
|
|
} else {
|
|
|
|
origin[1] -= step;
|
|
|
|
}
|
|
|
|
if ( i & 4 ) {
|
|
|
|
origin[2] += step;
|
|
|
|
} else {
|
|
|
|
origin[2] -= step;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !PointInSolid( origin ) ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( i != 8 ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( step > 18 ) {
|
|
|
|
// can't find a valid point at all
|
|
|
|
for ( i = 0 ; i < 8 ; i++ ) {
|
|
|
|
gridData[ num*8 + i ] = 0;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorClear( summedDir );
|
|
|
|
|
|
|
|
// trace to all the lights
|
|
|
|
|
|
|
|
// find the major light direction, and divide the
|
|
|
|
// total light between that along the direction and
|
|
|
|
// the remaining in the ambient
|
|
|
|
numCon = 0;
|
|
|
|
for ( light = lights ; light ; light = light->next ) {
|
|
|
|
vec3_t add;
|
|
|
|
vec3_t dir;
|
|
|
|
float addSize;
|
|
|
|
|
|
|
|
if ( !LightContributionToPoint( light, origin, add, &tw ) ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorSubtract( light->origin, origin, dir );
|
|
|
|
VectorNormalize( dir, dir );
|
|
|
|
|
|
|
|
VectorCopy( add, contributions[numCon].color );
|
|
|
|
VectorCopy( dir, contributions[numCon].dir );
|
|
|
|
numCon++;
|
|
|
|
|
|
|
|
addSize = VectorLength( add );
|
|
|
|
VectorMA( summedDir, addSize, dir, summedDir );
|
|
|
|
|
|
|
|
if ( numCon == MAX_CONTRIBUTIONS-1 ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// trace directly to the sun
|
|
|
|
//
|
|
|
|
SunToPoint( origin, &tw, color );
|
|
|
|
addSize = VectorLength( color );
|
|
|
|
if ( addSize > 0 ) {
|
|
|
|
VectorCopy( color, contributions[numCon].color );
|
|
|
|
VectorCopy( sunDirection, contributions[numCon].dir );
|
|
|
|
VectorMA( summedDir, addSize, sunDirection, summedDir );
|
|
|
|
numCon++;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// now that we have identified the primary light direction,
|
|
|
|
// go back and seperate all the light into directed and ambient
|
|
|
|
VectorNormalize( summedDir, summedDir );
|
|
|
|
VectorCopy( ambientColor, color );
|
|
|
|
VectorClear( directedColor );
|
|
|
|
|
|
|
|
for ( i = 0 ; i < numCon ; i++ ) {
|
|
|
|
float d;
|
|
|
|
|
|
|
|
d = DotProduct( contributions[i].dir, summedDir );
|
|
|
|
if ( d < 0 ) {
|
|
|
|
d = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorMA( directedColor, d, contributions[i].color, directedColor );
|
|
|
|
|
|
|
|
// the ambient light will be at 1/4 the value of directed light
|
|
|
|
d = 0.25 * ( 1.0 - d );
|
|
|
|
VectorMA( color, d, contributions[i].color, color );
|
|
|
|
}
|
|
|
|
|
|
|
|
// now do some fudging to keep the ambient from being too low
|
|
|
|
VectorMA( color, 0.25, directedColor, color );
|
|
|
|
|
|
|
|
//
|
|
|
|
// save the resulting value out
|
|
|
|
//
|
|
|
|
ColorToBytes( color, gridData + num*8 );
|
|
|
|
ColorToBytes( directedColor, gridData + num*8 + 3 );
|
|
|
|
|
|
|
|
VectorNormalize( summedDir, summedDir );
|
|
|
|
NormalToLatLong( summedDir, gridData + num*8 + 6);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
SetupGrid
|
|
|
|
=============
|
|
|
|
*/
|
|
|
|
void SetupGrid( void ) {
|
|
|
|
int i;
|
|
|
|
vec3_t maxs;
|
|
|
|
|
|
|
|
for ( i = 0 ; i < 3 ; i++ ) {
|
|
|
|
gridMins[i] = gridSize[i] * ceil( dmodels[0].mins[i] / gridSize[i] );
|
|
|
|
maxs[i] = gridSize[i] * floor( dmodels[0].maxs[i] / gridSize[i] );
|
|
|
|
gridBounds[i] = (maxs[i] - gridMins[i])/gridSize[i] + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
numGridPoints = gridBounds[0] * gridBounds[1] * gridBounds[2];
|
|
|
|
if (numGridPoints * 8 >= MAX_MAP_LIGHTGRID)
|
|
|
|
Error("MAX_MAP_LIGHTGRID");
|
|
|
|
qprintf( "%5i gridPoints\n", numGridPoints );
|
|
|
|
}
|
|
|
|
|
|
|
|
//=============================================================================
|
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
RemoveLightsInSolid
|
|
|
|
=============
|
|
|
|
*/
|
|
|
|
void RemoveLightsInSolid(void)
|
|
|
|
{
|
|
|
|
light_t *light, *prev;
|
|
|
|
int numsolid = 0;
|
|
|
|
|
|
|
|
prev = NULL;
|
|
|
|
for ( light = lights ; light ; ) {
|
|
|
|
if (PointInSolid(light->origin))
|
|
|
|
{
|
|
|
|
if (prev) prev->next = light->next;
|
|
|
|
else lights = light->next;
|
|
|
|
if (light->w)
|
|
|
|
FreeWinding(light->w);
|
|
|
|
free(light);
|
|
|
|
numsolid++;
|
|
|
|
if (prev)
|
|
|
|
light = prev->next;
|
|
|
|
else
|
|
|
|
light = lights;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
prev = light;
|
|
|
|
light = light->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_printf (" %7i lights in solid\n", numsolid);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
LightWorld
|
|
|
|
=============
|
|
|
|
*/
|
|
|
|
void LightWorld (void) {
|
|
|
|
float f;
|
|
|
|
|
|
|
|
// determine the number of grid points
|
|
|
|
SetupGrid();
|
|
|
|
|
|
|
|
// find the optional world ambient
|
|
|
|
GetVectorForKey( &entities[0], "_color", ambientColor );
|
|
|
|
f = FloatForKey( &entities[0], "ambient" );
|
|
|
|
VectorScale( ambientColor, f, ambientColor );
|
|
|
|
|
|
|
|
// create lights out of patches and lights
|
|
|
|
qprintf ("--- CreateLights ---\n");
|
|
|
|
CreateEntityLights ();
|
|
|
|
qprintf ("%i point lights\n", numPointLights);
|
|
|
|
qprintf ("%i area lights\n", numAreaLights);
|
|
|
|
|
|
|
|
if (!nogridlighting) {
|
|
|
|
qprintf ("--- TraceGrid ---\n");
|
|
|
|
RunThreadsOnIndividual( numGridPoints, qtrue, TraceGrid );
|
|
|
|
qprintf( "%i x %i x %i = %i grid\n", gridBounds[0], gridBounds[1],
|
|
|
|
gridBounds[2], numGridPoints);
|
|
|
|
}
|
|
|
|
|
|
|
|
qprintf ("--- TraceLtm ---\n");
|
|
|
|
RunThreadsOnIndividual( numDrawSurfaces, qtrue, TraceLtm );
|
|
|
|
qprintf( "%5i visible samples\n", c_visible );
|
|
|
|
qprintf( "%5i occluded samples\n", c_occluded );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
========
|
|
|
|
CreateFilters
|
|
|
|
|
|
|
|
EXPERIMENTAL, UNUSED
|
|
|
|
|
|
|
|
Look for transparent light filter surfaces.
|
|
|
|
|
|
|
|
This will only work for flat 3*3 patches that exactly hold one copy of the texture.
|
|
|
|
========
|
|
|
|
*/
|
|
|
|
#define PLANAR_PATCH_EPSILON 0.1
|
|
|
|
void CreateFilters( void ) {
|
|
|
|
int i;
|
|
|
|
filter_t *f;
|
|
|
|
dsurface_t *ds;
|
|
|
|
shaderInfo_t *si;
|
|
|
|
drawVert_t *v1, *v2, *v3;
|
|
|
|
vec3_t d1, d2;
|
|
|
|
int vertNum;
|
|
|
|
|
|
|
|
numFilters = 0;
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
for ( i = 0 ; i < numDrawSurfaces ; i++ ) {
|
|
|
|
ds = &drawSurfaces[i];
|
|
|
|
if ( !ds->patchWidth ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
si = ShaderInfoForShader( dshaders[ ds->shaderNum ].shader );
|
|
|
|
/*
|
|
|
|
if ( !(si->surfaceFlags & SURF_LIGHTFILTER) ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
// we have a filter patch
|
|
|
|
v1 = &drawVerts[ ds->firstVert ];
|
|
|
|
|
|
|
|
if ( ds->patchWidth != 3 || ds->patchHeight != 3 ) {
|
|
|
|
_printf("WARNING: patch at %i %i %i has SURF_LIGHTFILTER but isn't a 3 by 3\n",
|
|
|
|
v1->xyz[0], v1->xyz[1], v1->xyz[2] );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( numFilters == MAX_FILTERS ) {
|
|
|
|
Error( "MAX_FILTERS" );
|
|
|
|
}
|
|
|
|
f = &filters[ numFilters ];
|
|
|
|
numFilters++;
|
|
|
|
|
|
|
|
v2 = &drawVerts[ ds->firstVert + 2 ];
|
|
|
|
v3 = &drawVerts[ ds->firstVert + 6 ];
|
|
|
|
|
|
|
|
VectorSubtract( v2->xyz, v1->xyz, d1 );
|
|
|
|
VectorSubtract( v3->xyz, v1->xyz, d2 );
|
|
|
|
VectorNormalize( d1, d1 );
|
|
|
|
VectorNormalize( d2, d2 );
|
|
|
|
CrossProduct( d1, d2, f->plane );
|
|
|
|
f->plane[3] = DotProduct( v1->xyz, f->plane );
|
|
|
|
|
|
|
|
// make sure all the control points are on the plane
|
|
|
|
for ( vertNum = 0 ; vertNum < ds->numVerts ; vertNum++ ) {
|
|
|
|
float d;
|
|
|
|
|
|
|
|
d = DotProduct( drawVerts[ ds->firstVert + vertNum ].xyz, f->plane ) - f->plane[3];
|
|
|
|
if ( fabs( d ) > PLANAR_PATCH_EPSILON ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( vertNum != ds->numVerts ) {
|
|
|
|
numFilters--;
|
|
|
|
_printf("WARNING: patch at %i %i %i has SURF_LIGHTFILTER but isn't flat\n",
|
|
|
|
v1->xyz[0], v1->xyz[1], v1->xyz[2] );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
f = &filters[0];
|
|
|
|
numFilters = 1;
|
|
|
|
|
|
|
|
f->plane[0] = 1;
|
|
|
|
f->plane[1] = 0;
|
|
|
|
f->plane[2] = 0;
|
|
|
|
f->plane[3] = 448;
|
|
|
|
|
|
|
|
f->origin[0] = 448;
|
|
|
|
f->origin[1] = 192;
|
|
|
|
f->origin[2] = 0;
|
|
|
|
|
|
|
|
f->vectors[0][0] = 0;
|
|
|
|
f->vectors[0][1] = -1.0 / 128;
|
|
|
|
f->vectors[0][2] = 0;
|
|
|
|
|
|
|
|
f->vectors[1][0] = 0;
|
|
|
|
f->vectors[1][1] = 0;
|
|
|
|
f->vectors[1][2] = 1.0 / 128;
|
|
|
|
|
|
|
|
f->si = ShaderInfoForShader( "textures/hell/blocks11ct" );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
VertexLightingThread
|
|
|
|
=============
|
|
|
|
*/
|
|
|
|
void VertexLightingThread(int num) {
|
|
|
|
dsurface_t *ds;
|
|
|
|
traceWork_t tw;
|
|
|
|
shaderInfo_t *si;
|
|
|
|
|
|
|
|
ds = &drawSurfaces[num];
|
|
|
|
|
|
|
|
// vertex-lit triangle model
|
|
|
|
if ( ds->surfaceType == MST_TRIANGLE_SOUP ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (novertexlighting)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( ds->lightmapNum == -1 ) {
|
|
|
|
return; // doesn't need lighting at all
|
|
|
|
}
|
|
|
|
|
|
|
|
si = ShaderInfoForShader( dshaders[ ds->shaderNum].shader );
|
|
|
|
|
|
|
|
// calculate the vertex lighting for gouraud shade mode
|
|
|
|
VertexLighting( ds, si->vertexShadows, si->forceSunLight, si->vertexScale, &tw );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
TriSoupLightingThread
|
|
|
|
=============
|
|
|
|
*/
|
|
|
|
void TriSoupLightingThread(int num) {
|
|
|
|
dsurface_t *ds;
|
|
|
|
traceWork_t tw;
|
|
|
|
shaderInfo_t *si;
|
|
|
|
|
|
|
|
ds = &drawSurfaces[num];
|
|
|
|
si = ShaderInfoForShader( dshaders[ ds->shaderNum].shader );
|
|
|
|
|
|
|
|
// vertex-lit triangle model
|
|
|
|
if ( ds->surfaceType == MST_TRIANGLE_SOUP ) {
|
|
|
|
VertexLighting( ds, !si->noVertexShadows, si->forceSunLight, 1.0, &tw );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
GridAndVertexLighting
|
|
|
|
=============
|
|
|
|
*/
|
|
|
|
void GridAndVertexLighting(void) {
|
|
|
|
SetupGrid();
|
|
|
|
|
|
|
|
FindSkyBrushes();
|
|
|
|
CreateFilters();
|
|
|
|
InitTrace();
|
|
|
|
CreateEntityLights ();
|
|
|
|
CreateSurfaceLights();
|
|
|
|
|
|
|
|
if (!nogridlighting) {
|
|
|
|
_printf ("--- TraceGrid ---\n");
|
|
|
|
RunThreadsOnIndividual( numGridPoints, qtrue, TraceGrid );
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!novertexlighting) {
|
|
|
|
_printf ("--- Vertex Lighting ---\n");
|
|
|
|
RunThreadsOnIndividual( numDrawSurfaces, qtrue, VertexLightingThread );
|
|
|
|
}
|
|
|
|
|
|
|
|
_printf("--- Model Lighting ---\n");
|
|
|
|
RunThreadsOnIndividual( numDrawSurfaces, qtrue, TriSoupLightingThread );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
========
|
|
|
|
LightMain
|
|
|
|
|
|
|
|
========
|
|
|
|
*/
|
|
|
|
int LightMain (int argc, char **argv) {
|
|
|
|
int i;
|
|
|
|
double start, end;
|
|
|
|
const char *value;
|
|
|
|
|
|
|
|
_printf ("----- Lighting ----\n");
|
|
|
|
|
|
|
|
verbose = qfalse;
|
|
|
|
|
|
|
|
for (i=1 ; i<argc ; i++) {
|
|
|
|
if (!strcmp(argv[i],"-tempname"))
|
|
|
|
{
|
|
|
|
i++;
|
|
|
|
} else if (!strcmp(argv[i],"-v")) {
|
|
|
|
verbose = qtrue;
|
|
|
|
} else if (!strcmp(argv[i],"-threads")) {
|
|
|
|
numthreads = atoi (argv[i+1]);
|
|
|
|
i++;
|
|
|
|
} else if (!strcmp(argv[i],"-area")) {
|
|
|
|
areaScale *= atof(argv[i+1]);
|
|
|
|
_printf ("area light scaling at %f\n", areaScale);
|
|
|
|
i++;
|
|
|
|
} else if (!strcmp(argv[i],"-point")) {
|
|
|
|
pointScale *= atof(argv[i+1]);
|
|
|
|
_printf ("point light scaling at %f\n", pointScale);
|
|
|
|
i++;
|
|
|
|
} else if (!strcmp(argv[i],"-notrace")) {
|
|
|
|
notrace = qtrue;
|
|
|
|
_printf ("No occlusion tracing\n");
|
|
|
|
} else if (!strcmp(argv[i],"-patchshadows")) {
|
|
|
|
patchshadows = qtrue;
|
|
|
|
_printf ("Patch shadow casting enabled\n");
|
|
|
|
} else if (!strcmp(argv[i],"-extra")) {
|
|
|
|
extra = qtrue;
|
|
|
|
_printf ("Extra detail tracing\n");
|
|
|
|
} else if (!strcmp(argv[i],"-extrawide")) {
|
|
|
|
extra = qtrue;
|
|
|
|
extraWide = qtrue;
|
|
|
|
_printf ("Extra wide detail tracing\n");
|
|
|
|
} else if (!strcmp(argv[i], "-samplesize")) {
|
|
|
|
samplesize = atoi(argv[i+1]);
|
|
|
|
if (samplesize < 1) samplesize = 1;
|
|
|
|
i++;
|
|
|
|
_printf("lightmap sample size is %dx%d units\n", samplesize, samplesize);
|
|
|
|
} else if (!strcmp(argv[i], "-novertex")) {
|
|
|
|
novertexlighting = qtrue;
|
|
|
|
_printf("no vertex lighting = true\n");
|
|
|
|
} else if (!strcmp(argv[i], "-nogrid")) {
|
|
|
|
nogridlighting = qtrue;
|
|
|
|
_printf("no grid lighting = true\n");
|
|
|
|
} else if (!strcmp(argv[i],"-border")) {
|
|
|
|
lightmapBorder = qtrue;
|
|
|
|
_printf ("Adding debug border to lightmaps\n");
|
|
|
|
} else if (!strcmp(argv[i],"-nosurf")) {
|
|
|
|
noSurfaces = qtrue;
|
|
|
|
_printf ("Not tracing against surfaces\n" );
|
|
|
|
} else if (!strcmp(argv[i],"-dump")) {
|
|
|
|
dump = qtrue;
|
|
|
|
_printf ("Dumping occlusion maps\n");
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ThreadSetDefault ();
|
|
|
|
|
|
|
|
if (i != argc - 1) {
|
|
|
|
_printf("usage: q3map -light [-<switch> [-<switch> ...]] <mapname>\n"
|
|
|
|
"\n"
|
|
|
|
"Switches:\n"
|
|
|
|
" v = verbose output\n"
|
|
|
|
" threads <X> = set number of threads to X\n"
|
|
|
|
" area <V> = set the area light scale to V\n"
|
|
|
|
" point <W> = set the point light scale to W\n"
|
|
|
|
" notrace = don't cast any shadows\n"
|
|
|
|
" extra = enable super sampling for anti-aliasing\n"
|
|
|
|
" extrawide = same as extra but smoothen more\n"
|
|
|
|
" nogrid = don't calculate light grid for dynamic model lighting\n"
|
|
|
|
" novertex = don't calculate vertex lighting\n"
|
|
|
|
" samplesize <N> = set the lightmap pixel size to NxN units\n");
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
start = I_FloatTime ();
|
|
|
|
|
|
|
|
SetQdirFromPath (argv[i]);
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
InitPakFile(gamedir, NULL);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
strcpy (source, ExpandArg(argv[i]));
|
|
|
|
StripExtension (source);
|
|
|
|
DefaultExtension (source, ".bsp");
|
|
|
|
|
|
|
|
LoadShaderInfo();
|
|
|
|
|
|
|
|
_printf ("reading %s\n", source);
|
|
|
|
|
|
|
|
LoadBSPFile (source);
|
|
|
|
|
|
|
|
FindSkyBrushes();
|
|
|
|
|
|
|
|
ParseEntities();
|
|
|
|
|
|
|
|
value = ValueForKey( &entities[0], "gridsize" );
|
|
|
|
if (strlen(value)) {
|
|
|
|
sscanf( value, "%f %f %f", &gridSize[0], &gridSize[1], &gridSize[2] );
|
|
|
|
_printf("grid size = {%1.1f, %1.1f, %1.1f}\n", gridSize[0], gridSize[1], gridSize[2]);
|
|
|
|
}
|
|
|
|
|
|
|
|
CreateFilters();
|
|
|
|
|
|
|
|
InitTrace();
|
|
|
|
|
|
|
|
SetEntityOrigins();
|
|
|
|
|
|
|
|
CountLightmaps();
|
|
|
|
|
|
|
|
CreateSurfaceLights();
|
|
|
|
|
|
|
|
LightWorld();
|
|
|
|
|
|
|
|
_printf ("writing %s\n", source);
|
|
|
|
WriteBSPFile (source);
|
|
|
|
|
|
|
|
end = I_FloatTime ();
|
|
|
|
_printf ("%5.0f seconds elapsed\n", end-start);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|