//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// #include "vrad.h" #include "lightmap.h" #include "radial.h" #include "mathlib/bumpvects.h" #include "utlrbtree.h" #include "mathlib/VMatrix.h" #include "macro_texture.h" void WorldToLuxelSpace(lightinfo_t const *l, Vector const &world, Vector2D &coord) { Vector pos; VectorSubtract(world, l->luxelOrigin, pos); coord[0] = DotProduct(pos, l->worldToLuxelSpace[0]) - l->face->m_LightmapTextureMinsInLuxels[0]; coord[1] = DotProduct(pos, l->worldToLuxelSpace[1]) - l->face->m_LightmapTextureMinsInLuxels[1]; } void LuxelSpaceToWorld(lightinfo_t const *l, float s, float t, Vector &world) { Vector pos; s += l->face->m_LightmapTextureMinsInLuxels[0]; t += l->face->m_LightmapTextureMinsInLuxels[1]; VectorMA(l->luxelOrigin, s, l->luxelToWorldSpace[0], pos); VectorMA(pos, t, l->luxelToWorldSpace[1], world); } void WorldToLuxelSpace(lightinfo_t const *l, FourVectors const &world, FourVectors &coord) { FourVectors luxelOrigin; luxelOrigin.DuplicateVector(l->luxelOrigin); FourVectors pos = world; pos -= luxelOrigin; coord.x = pos * l->worldToLuxelSpace[0]; coord.x = SubSIMD(coord.x, ReplicateX4(l->face->m_LightmapTextureMinsInLuxels[0])); coord.y = pos * l->worldToLuxelSpace[1]; coord.y = SubSIMD(coord.y, ReplicateX4(l->face->m_LightmapTextureMinsInLuxels[1])); coord.z = Four_Zeros; } void LuxelSpaceToWorld(lightinfo_t const *l, fltx4 s, fltx4 t, FourVectors &world) { world.DuplicateVector(l->luxelOrigin); FourVectors st; s = AddSIMD(s, ReplicateX4(l->face->m_LightmapTextureMinsInLuxels[0])); st.DuplicateVector(l->luxelToWorldSpace[0]); st *= s; world += st; t = AddSIMD(t, ReplicateX4(l->face->m_LightmapTextureMinsInLuxels[1])); st.DuplicateVector(l->luxelToWorldSpace[1]); st *= t; world += st; } void AddDirectToRadial(radial_t *rad, Vector const &pnt, Vector2D const &coordmins, Vector2D const &coordmaxs, LightingValue_t const light[NUM_BUMP_VECTS + 1], bool hasBumpmap, bool neighborHasBumpmap) { int s_min, s_max, t_min, t_max; Vector2D coord; int s, t; float ds, dt; float r; float area; int bumpSample; // convert world pos into local lightmap texture coord WorldToLuxelSpace(&rad->l, pnt, coord); s_min = (int)(coordmins[0]); t_min = (int)(coordmins[1]); s_max = (int)(coordmaxs[0] + 0.9999f) + 1; // ???? t_max = (int)(coordmaxs[1] + 0.9999f) + 1; s_min = max(s_min, 0); t_min = max(t_min, 0); s_max = min(s_max, rad->w); t_max = min(t_max, rad->h); for (s = s_min; s < s_max; s++) { for (t = t_min; t < t_max; t++) { float s0 = max(coordmins[0] - s, -1.0); float t0 = max(coordmins[1] - t, -1.0); float s1 = min(coordmaxs[0] - s, 1.0); float t1 = min(coordmaxs[1] - t, 1.0); area = (s1 - s0) * (t1 - t0); if (area > EQUAL_EPSILON) { ds = fabs(coord[0] - s); dt = fabs(coord[1] - t); r = max(ds, dt); if (r < 0.1) { r = area / 0.1; } else { r = area / r; } int i = s + t*rad->w; if (hasBumpmap) { if (neighborHasBumpmap) { for (bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++) { rad->light[bumpSample][i].AddWeighted(light[bumpSample], r); } } else { rad->light[0][i].AddWeighted(light[0], r); for (bumpSample = 1; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++) { rad->light[bumpSample][i].AddWeighted(light[0], r * OO_SQRT_3); } } } else { rad->light[0][i].AddWeighted(light[0], r); } rad->weight[i] += r; } } } } void AddBouncedToRadial(radial_t *rad, Vector const &pnt, Vector2D const &coordmins, Vector2D const &coordmaxs, Vector const light[NUM_BUMP_VECTS + 1], bool hasBumpmap, bool neighborHasBumpmap) { int s_min, s_max, t_min, t_max; Vector2D coord; int s, t; float ds, dt; float r; int bumpSample; // convert world pos into local lightmap texture coord WorldToLuxelSpace(&rad->l, pnt, coord); float dists, distt; dists = (coordmaxs[0] - coordmins[0]); distt = (coordmaxs[1] - coordmins[1]); // patches less than a luxel in size could be mistakeningly filtered, so clamp. dists = max(1.0, dists); distt = max(1.0, distt); // find possible domain of patch influence s_min = (int)(coord[0] - dists * RADIALDIST); t_min = (int)(coord[1] - distt * RADIALDIST); s_max = (int)(coord[0] + dists * RADIALDIST + 1.0f); t_max = (int)(coord[1] + distt * RADIALDIST + 1.0f); // clamp to valid luxel s_min = max(s_min, 0); t_min = max(t_min, 0); s_max = min(s_max, rad->w); t_max = min(t_max, rad->h); for (s = s_min; s < s_max; s++) { for (t = t_min; t < t_max; t++) { // patch influence is based on patch size ds = (coord[0] - s) / dists; dt = (coord[1] - t) / distt; r = RADIALDIST2 - (ds * ds + dt * dt); int i = s + t*rad->w; if (r > 0) { if (hasBumpmap) { if (neighborHasBumpmap) { for (bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++) { rad->light[bumpSample][i].AddWeighted(light[bumpSample], r); } } else { rad->light[0][i].AddWeighted(light[0], r); for (bumpSample = 1; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++) { rad->light[bumpSample][i].AddWeighted(light[0], r * OO_SQRT_3); } } } else { rad->light[0][i].AddWeighted(light[0], r); } rad->weight[i] += r; } } } } void PatchLightmapCoordRange(radial_t *rad, int ndxPatch, Vector2D &mins, Vector2D &maxs) { winding_t *w; int i; Vector2D coord; mins.Init(1E30, 1E30); maxs.Init(-1E30, -1E30); CPatch *patch = &g_Patches.Element(ndxPatch); w = patch->winding; for (i = 0; i < w->numpoints; i++) { WorldToLuxelSpace(&rad->l, w->p[i], coord); mins[0] = min(mins[0], coord[0]); maxs[0] = max(maxs[0], coord[0]); mins[1] = min(mins[1], coord[1]); maxs[1] = max(maxs[1], coord[1]); } } radial_t *AllocateRadial(int facenum) { radial_t *rad; rad = (radial_t*)calloc(1, sizeof(*rad)); rad->facenum = facenum; InitLightinfo(&rad->l, facenum); rad->w = rad->l.face->m_LightmapTextureSizeInLuxels[0] + 1; rad->h = rad->l.face->m_LightmapTextureSizeInLuxels[1] + 1; return rad; } void FreeRadial(radial_t *rad) { if (rad) free(rad); } radial_t *BuildPatchRadial(int facenum) { int j; radial_t *rad; CPatch *patch; faceneighbor_t *fn; Vector2D mins, maxs; bool needsBumpmap, neighborNeedsBumpmap; needsBumpmap = texinfo[g_pFaces[facenum].texinfo].flags & SURF_BUMPLIGHT ? true : false; rad = AllocateRadial(facenum); fn = &faceneighbor[rad->facenum]; CPatch *pNextPatch; if (g_FacePatches.Element(rad->facenum) != g_FacePatches.InvalidIndex()) { for (patch = &g_Patches.Element(g_FacePatches.Element(rad->facenum)); patch; patch = pNextPatch) { // next patch pNextPatch = NULL; if (patch->ndxNext != g_Patches.InvalidIndex()) { pNextPatch = &g_Patches.Element(patch->ndxNext); } // skip patches with children if (patch->child1 != g_Patches.InvalidIndex()) continue; // get the range of patch lightmap texture coords int ndxPatch = patch - g_Patches.Base(); PatchLightmapCoordRange(rad, ndxPatch, mins, maxs); if (patch->numtransfers == 0) { // Error, using patch that was never evaluated or has no samples // patch->totallight[1] = 255; } // // displacement surface patch origin position and normal vectors have been changed to // represent the displacement surface position and normal -- for radial "blending" // we need to get the base surface patch origin! // if (ValidDispFace(&g_pFaces[facenum])) { Vector patchOrigin; WindingCenter(patch->winding, patchOrigin); AddBouncedToRadial(rad, patchOrigin, mins, maxs, patch->totallight.light, needsBumpmap, needsBumpmap); } else { AddBouncedToRadial(rad, patch->origin, mins, maxs, patch->totallight.light, needsBumpmap, needsBumpmap); } } } for (j = 0; jnumneighbors; j++) { if (g_FacePatches.Element(fn->neighbor[j]) != g_FacePatches.InvalidIndex()) { for (patch = &g_Patches.Element(g_FacePatches.Element(fn->neighbor[j])); patch; patch = pNextPatch) { // next patch pNextPatch = NULL; if (patch->ndxNext != g_Patches.InvalidIndex()) { pNextPatch = &g_Patches.Element(patch->ndxNext); } // skip patches with children if (patch->child1 != g_Patches.InvalidIndex()) continue; // get the range of patch lightmap texture coords int ndxPatch = patch - g_Patches.Base(); PatchLightmapCoordRange(rad, ndxPatch, mins, maxs); neighborNeedsBumpmap = texinfo[g_pFaces[facenum].texinfo].flags & SURF_BUMPLIGHT ? true : false; // // displacement surface patch origin position and normal vectors have been changed to // represent the displacement surface position and normal -- for radial "blending" // we need to get the base surface patch origin! // if (ValidDispFace(&g_pFaces[fn->neighbor[j]])) { Vector patchOrigin; WindingCenter(patch->winding, patchOrigin); AddBouncedToRadial(rad, patchOrigin, mins, maxs, patch->totallight.light, needsBumpmap, needsBumpmap); } else { AddBouncedToRadial(rad, patch->origin, mins, maxs, patch->totallight.light, needsBumpmap, needsBumpmap); } } } } return rad; } radial_t *BuildLuxelRadial(int facenum, int style) { LightingValue_t light[NUM_BUMP_VECTS + 1]; facelight_t *fl = &facelight[facenum]; faceneighbor_t *fn = &faceneighbor[facenum]; radial_t *rad = AllocateRadial(facenum); bool needsBumpmap = texinfo[g_pFaces[facenum].texinfo].flags & SURF_BUMPLIGHT ? true : false; for (int k = 0; knumsamples; k++) { if (needsBumpmap) { for (int bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++) { light[bumpSample] = fl->light[style][bumpSample][k]; } } else { light[0] = fl->light[style][0][k]; } AddDirectToRadial(rad, fl->sample[k].pos, fl->sample[k].mins, fl->sample[k].maxs, light, needsBumpmap, needsBumpmap); } for (int j = 0; j < fn->numneighbors; j++) { fl = &facelight[fn->neighbor[j]]; bool neighborHasBumpmap = false; if (texinfo[g_pFaces[fn->neighbor[j]].texinfo].flags & SURF_BUMPLIGHT) { neighborHasBumpmap = true; } int nstyle = 0; // look for style that matches if (g_pFaces[fn->neighbor[j]].styles[nstyle] != g_pFaces[facenum].styles[style]) { for (nstyle = 1; nstyle < MAXLIGHTMAPS; nstyle++) if (g_pFaces[fn->neighbor[j]].styles[nstyle] == g_pFaces[facenum].styles[style]) break; // if not found, skip this neighbor if (nstyle >= MAXLIGHTMAPS) continue; } lightinfo_t l; InitLightinfo(&l, fn->neighbor[j]); for (int k = 0; knumsamples; k++) { if (neighborHasBumpmap) { for (int bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++) { light[bumpSample] = fl->light[nstyle][bumpSample][k]; } } else { light[0] = fl->light[nstyle][0][k]; } Vector tmp; Vector2D mins, maxs; LuxelSpaceToWorld(&l, fl->sample[k].mins[0], fl->sample[k].mins[1], tmp); WorldToLuxelSpace(&rad->l, tmp, mins); LuxelSpaceToWorld(&l, fl->sample[k].maxs[0], fl->sample[k].maxs[1], tmp); WorldToLuxelSpace(&rad->l, tmp, maxs); AddDirectToRadial(rad, fl->sample[k].pos, mins, maxs, light, needsBumpmap, neighborHasBumpmap); } } return rad; } //----------------------------------------------------------------------------- // Purpose: returns the closest light value for a given point on the surface // this is normally a 1:1 mapping //----------------------------------------------------------------------------- bool SampleRadial(radial_t *rad, Vector& pnt, LightingValue_t light[NUM_BUMP_VECTS + 1], int bumpSampleCount) { int bumpSample; Vector2D coord; WorldToLuxelSpace(&rad->l, pnt, coord); int u = (int)(coord[0] + 0.5f); int v = (int)(coord[1] + 0.5f); int i = u + v * rad->w; if (u < 0 || u > rad->w || v < 0 || v > rad->h) { static bool warning = false; if (!warning) { // punting over to KenB // 2d coord indexes off of lightmap, generation of pnt seems suspect Warning("SampleRadial: Punting, Waiting for fix\n"); warning = true; } for (bumpSample = 0; bumpSample < bumpSampleCount; bumpSample++) { light[bumpSample].m_vecLighting.Init(2550, 0, 0); } return false; } bool baseSampleOk = true; for (bumpSample = 0; bumpSample < bumpSampleCount; bumpSample++) { light[bumpSample].Zero(); if (rad->weight[i] > WEIGHT_EPS) { light[bumpSample] = rad->light[bumpSample][i]; light[bumpSample].Scale(1.0 / rad->weight[i]); } else { if (bRed2Black) { // Error, luxel has no samples light[bumpSample].m_vecLighting.Init(0, 0, 0); } else { // Error, luxel has no samples // Yes, it actually should be 2550 light[bumpSample].m_vecLighting.Init(2550, 0, 0); } if (bumpSample == 0) baseSampleOk = false; } } return baseSampleOk; } bool FloatLess(float const& src1, float const& src2) { return src1 < src2; } //----------------------------------------------------------------------------- // Debugging! //----------------------------------------------------------------------------- void GetRandomColor(unsigned char *color) { static bool firstTime = true; if (firstTime) { firstTime = false; srand(0); } color[0] = (unsigned char)(rand() * (255.0f / VALVE_RAND_MAX)); color[1] = (unsigned char)(rand() * (255.0f / VALVE_RAND_MAX)); color[2] = (unsigned char)(rand() * (255.0f / VALVE_RAND_MAX)); } #if 0 // debugging! -- not accurate! void DumpLuxels(facelight_t *pFaceLight, Vector *luxelColors, int ndxFace) { static FileHandle_t pFpLuxels = NULL; ThreadLock(); if (!pFpLuxels) { pFpLuxels = g_pFileSystem->Open("luxels.txt", "w"); } dface_t *pFace = &g_pFaces[ndxFace]; bool bDisp = (pFace->dispinfo != -1); for (int ndx = 0; ndx < pFaceLight->numluxels; ndx++) { WriteWinding(pFpLuxels, pFaceLight->sample[ndx].w, luxelColors[ndx]); if (bDumpNormals && bDisp) { WriteNormal(pFpLuxels, pFaceLight->luxel[ndx], pFaceLight->luxelNormals[ndx], 15.0f, Vector(255, 255, 0)); } } ThreadUnlock(); } #endif static FileHandle_t pFileLuxels[4] = { NULL, NULL, NULL, NULL }; void DumpDispLuxels(int iFace, Vector &color, int iLuxel, int nBump) { // Lock the thread and dump the luxel data. ThreadLock(); // Get the face and facelight data. facelight_t *pFaceLight = &facelight[iFace]; // Open the luxel files. char szFileName[512]; for (int iBump = 0; iBump < (NUM_BUMP_VECTS + 1); ++iBump) { if (pFileLuxels[iBump] == NULL) { sprintf(szFileName, "luxels_bump%d.txt", iBump); pFileLuxels[iBump] = g_pFileSystem->Open(szFileName, "w"); } } WriteWinding(pFileLuxels[nBump], pFaceLight->sample[iLuxel].w, color); ThreadUnlock(); } void CloseDispLuxels() { for (int iBump = 0; iBump < (NUM_BUMP_VECTS + 1); ++iBump) { if (pFileLuxels[iBump]) { g_pFileSystem->Close(pFileLuxels[iBump]); } } } /* ============= FinalLightFace Add the indirect lighting on top of the direct lighting and save into final map format ============= */ void FinalLightFace(int iThread, int facenum) { dface_t *f; int i, j, k; facelight_t *fl; float minlight; int lightstyles; LightingValue_t lb[NUM_BUMP_VECTS + 1], v[NUM_BUMP_VECTS + 1]; unsigned char *pdata[NUM_BUMP_VECTS + 1]; int bumpSample; radial_t *rad = NULL; radial_t *prad = NULL; f = &g_pFaces[facenum]; // test for non-lit texture if (texinfo[f->texinfo].flags & TEX_SPECIAL) return; fl = &facelight[facenum]; for (lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++) { if (f->styles[lightstyles] == 255) break; } if (!lightstyles) return; // // sample the triangulation // minlight = FloatForKey(face_entity[facenum], "_minlight") * 128; bool needsBumpmap = (texinfo[f->texinfo].flags & SURF_BUMPLIGHT) ? true : false; int bumpSampleCount = needsBumpmap ? NUM_BUMP_VECTS + 1 : 1; bool bDisp = (f->dispinfo != -1); //#define RANDOM_COLOR #ifdef RANDOM_COLOR unsigned char randomColor[3]; GetRandomColor(randomColor); #endif // NOTE: I'm using these RB trees to sort all the illumination values // to compute median colors. Turns out that this is a somewhat better // method that using the average; usually if there are surfaces // with a large light intensity variation, the extremely bright regions // have a very small area and tend to influence the average too much. CUtlRBTree< float, int > m_Red(0, 256, FloatLess); CUtlRBTree< float, int > m_Green(0, 256, FloatLess); CUtlRBTree< float, int > m_Blue(0, 256, FloatLess); for (k = 0; k < lightstyles; k++) { m_Red.RemoveAll(); m_Green.RemoveAll(); m_Blue.RemoveAll(); if (!do_fast) { if (!bDisp) { rad = BuildLuxelRadial(facenum, k); } else { rad = StaticDispMgr()->BuildLuxelRadial(facenum, k, needsBumpmap); } } if (numbounce > 0 && k == 0) { // currently only radiosity light non-displacement surfaces! if (!bDisp) { prad = BuildPatchRadial(facenum); } else { prad = StaticDispMgr()->BuildPatchRadial(facenum, needsBumpmap); } } // pack the nonbump texture and the three bump texture for the given // lightstyle right next to each other. // NOTE: Even though it's building positions for all bump-mapped data, // it isn't going to use those positions (see loop over bumpSample below) // The file offset is correctly computed to only store space for 1 set // of light data if we don't have bumped lighting. for (bumpSample = 0; bumpSample < bumpSampleCount; ++bumpSample) { pdata[bumpSample] = &(*pdlightdata)[f->lightofs + (k * bumpSampleCount + bumpSample) * fl->numluxels * 4]; } // Compute the average luxel color, but not for the bump samples Vector avg(0.0f, 0.0f, 0.0f); int avgCount = 0; for (j = 0; jnumluxels; j++) { // garymct - direct lighting bool baseSampleOk = true; if (!do_fast) { if (!bDisp) { baseSampleOk = SampleRadial(rad, fl->luxel[j], lb, bumpSampleCount); } else { baseSampleOk = StaticDispMgr()->SampleRadial(facenum, rad, fl->luxel[j], j, lb, bumpSampleCount, false); } } else { for (int iBump = 0; iBump < bumpSampleCount; iBump++) { lb[iBump] = fl->light[0][iBump][j]; } } if (prad) { // garymct - bounced light // v is indirect light that is received on the luxel. if (!bDisp) { SampleRadial(prad, fl->luxel[j], v, bumpSampleCount); } else { StaticDispMgr()->SampleRadial(facenum, prad, fl->luxel[j], j, v, bumpSampleCount, true); } for (bumpSample = 0; bumpSample < bumpSampleCount; ++bumpSample) { lb[bumpSample].AddLight(v[bumpSample]); } } if (bDisp && g_bDumpPatches) { for (bumpSample = 0; bumpSample < bumpSampleCount; ++bumpSample) { DumpDispLuxels(facenum, lb[bumpSample].m_vecLighting, j, bumpSample); } } if (fl->numsamples == 0) { for (i = 0; i < bumpSampleCount; i++) { lb[i].Init(255, 0, 0); } baseSampleOk = false; } int bumpSample; for (bumpSample = 0; bumpSample < bumpSampleCount; bumpSample++) { // clip from the bottom first // garymct: minlight is a per entity minimum light value? for (i = 0; i<3; i++) { lb[bumpSample].m_vecLighting[i] = max(lb[bumpSample].m_vecLighting[i], minlight); } // Do the average light computation, I'm assuming (perhaps incorrectly?) // that all luxels in a particular lightmap have the same area here. // Also, don't bother doing averages for the bump samples. Doing it here // because of the minlight clamp above + the random color testy thingy. // Also have to do it before Vec3toColorRGBExp32 because it // destructively modifies lb[bumpSample] (Feh!) if ((bumpSample == 0) && baseSampleOk) { ++avgCount; ApplyMacroTextures(facenum, fl->luxel[j], lb[0].m_vecLighting); // For median computation m_Red.Insert(lb[bumpSample].m_vecLighting[0]); m_Green.Insert(lb[bumpSample].m_vecLighting[1]); m_Blue.Insert(lb[bumpSample].m_vecLighting[2]); } #ifdef RANDOM_COLOR pdata[bumpSample][0] = randomColor[0] / (bumpSample + 1); pdata[bumpSample][1] = randomColor[1] / (bumpSample + 1); pdata[bumpSample][2] = randomColor[2] / (bumpSample + 1); pdata[bumpSample][3] = 0; #else // convert to a 4 byte r,g,b,signed exponent format VectorToColorRGBExp32(Vector(lb[bumpSample].m_vecLighting.x, lb[bumpSample].m_vecLighting.y, lb[bumpSample].m_vecLighting.z), *(ColorRGBExp32 *)pdata[bumpSample]); #endif pdata[bumpSample] += 4; } } FreeRadial(rad); if (prad) { FreeRadial(prad); prad = NULL; } // Compute the median color for this lightstyle // Remember, the data goes *before* the specified light_ofs, in *reverse order* ColorRGBExp32 *pAvgColor = dface_AvgLightColor(f, k); if (avgCount == 0) { Vector median(0, 0, 0); VectorToColorRGBExp32(median, *pAvgColor); } else { unsigned int r, g, b; r = m_Red.FirstInorder(); g = m_Green.FirstInorder(); b = m_Blue.FirstInorder(); avgCount >>= 1; while (avgCount > 0) { r = m_Red.NextInorder(r); g = m_Green.NextInorder(g); b = m_Blue.NextInorder(b); --avgCount; } Vector median(m_Red[r], m_Green[g], m_Blue[b]); VectorToColorRGBExp32(median, *pAvgColor); } } }