//----------------------------------------------- // Synapse Gaming - Lighting Code Pack // Copyright © Synapse Gaming 2003 - 2005 // Written by John Kabus // // Overview: // Code from the Lighting Pack's (Torque Lighting Kit) // lighting system, which was modified for use // with Constructor. //----------------------------------------------- #include "math/mBox.h" #include "math/mathUtils.h" #include "sceneGraph/sceneGraph.h" #include "terrain/terrData.h" #include "platform/profiler.h" #include "interior/interior.h" #include "interior/interiorInstance.h" #include "lightingSystem/sgLightMap.h" #include "lightingSystem/sgLightingModel.h" #include "math/mRandom.h" #include "math/mathUtils.h" #include "util/triRayCheck.h" /// used to calculate the start and end points /// for ray casting directional light. #define SG_STATIC_LIGHT_VECTOR_DIST 100 #define SG_MIN_LEXEL_INTENSITY 0.0039215f #define SG_STATICMESH_BVPT_SHADOWS bool badtexgen = false; Vector sgShadowObjects::sgObjectInfoStorage; sgShadowObjects::sgStaticMeshBVPTEntry sgShadowObjects::sgStaticMeshBVPTMap; VectorPtr sgShadowObjects::sgObjects; U32 sgPlanarLightMap::sgCurrentOccluderMaskId = 0; /// used to generate light map indexes that wrap around /// instead of exceeding the index bounds. inline S32 sgGetIndex(S32 width, S32 height, S32 x, S32 y) { if(x > (width-1)) x -= width; else if(x < 0) x += width; if(y > (height-1)) y -= height; else if(y < 0) y += height; return (y * width) + x; } inline F32 sgGetDistanceSquared(const Point3F &linepointa, const Point3F &linepointb, const Point3F &point) { Point3F vect = linepointb - linepointa; F32 dist = vect.lenSquared(); if(dist <= 0.0f) return 100000.0f; F32 tang = ((point.x + linepointa.x) * (linepointb.x + linepointa.x)) + ((point.y + linepointa.y) * (linepointb.y + linepointa.y)) + ((point.z + linepointa.z) * (linepointb.z + linepointa.z)); tang /= dist; if(tang >= 1.0f) { vect = linepointb - point; return vect.lenSquared(); } if(tang <= 0.0f) { vect = linepointa - point; return vect.lenSquared(); } vect = linepointa + (vect * tang); vect = vect - point; return vect.lenSquared(); } void sgObjectCallback(SceneObject *object, void *vector) { VectorPtr *intersectingobjects = static_cast *>(vector); intersectingobjects->push_back(object); } void sgShadowObjects::sgGetObjects(SceneObject *obj) { sgObjects.clear(); obj->getContainer()->findObjects(ShadowCasterObjectType, &sgObjectCallback, &sgObjects); } bool sgShadowObjects::sgCastRayStaticMesh(Point3F s, Point3F e, ConstructorSimpleMesh *staticmesh) { sgStaticMeshBVPTEntry *entry = sgStaticMeshBVPTMap.find(U32(staticmesh)); AssertFatal((entry), "hash_map should always return an entry!"); // is the BVPT there? if(!entry->info) { sgObjectInfo *objinfo = new sgObjectInfo(); entry->info = objinfo; sgObjectInfoStorage.push_back(objinfo); objinfo->sgBVPT.init(staticmesh->bounds); objinfo->sgInverseTransform = staticmesh->transform; objinfo->sgInverseTransform.inverse(); // get the tri count... U32 tricount = 0; for(U32 p=0; pprimitives.size(); p++) { ConstructorSimpleMesh::primitive &prim = staticmesh->primitives[p]; if(prim.alpha) continue; tricount += prim.count; } objinfo->sgTris.reserve(tricount); // add the tris... for(U32 p=0; pprimitives.size(); p++) { ConstructorSimpleMesh::primitive &prim = staticmesh->primitives[p]; if(prim.alpha || (prim.count < 3)) continue; PlaneF plane = PlaneF(staticmesh->verts[0], staticmesh->verts[1], staticmesh->verts[2]); Point3F norm = (staticmesh->norms[0] + staticmesh->norms[1] + staticmesh->norms[2]) * 0.3333f; bool flip = (mDot(plane, norm) < 0.0f); for(U32 t=2; tsgTris.increment(); sgStaticMeshTri &tri = objinfo->sgTris.last(); bool winding = (t & 0x1); if(flip) winding = !winding; if(winding) { tri.sgVert[0] = staticmesh->verts[baseindex-1]; tri.sgVert[1] = staticmesh->verts[baseindex-2]; tri.sgVert[2] = staticmesh->verts[baseindex]; tri.sgPlane = PlaneF(tri.sgVert[0], tri.sgVert[1], tri.sgVert[2]); } else { tri.sgVert[0] = staticmesh->verts[baseindex-2]; tri.sgVert[1] = staticmesh->verts[baseindex-1]; tri.sgVert[2] = staticmesh->verts[baseindex]; tri.sgPlane = PlaneF(tri.sgVert[0], tri.sgVert[1], tri.sgVert[2]); } } } // need to do this last so any vector copy/resize doesn't kill the pointers... for(U32 t=0; tsgTris.size(); t++) { sgStaticMeshTri &tri = objinfo->sgTris[t]; Box3F box; box.min = tri.sgVert[0]; box.max = tri.sgVert[0]; for(U32 v=1; v<3; v++) { box.min.setMin(tri.sgVert[v]); box.max.setMax(tri.sgVert[v]); } objinfo->sgBVPT.storeObject(box, &tri); } } // convert to static mesh space... sgObjectInfo *objinfo = entry->info; objinfo->sgInverseTransform.mulP(s); objinfo->sgInverseTransform.mulP(e); s.convolveInverse(staticmesh->scale); e.convolveInverse(staticmesh->scale); // early out test... F32 t; Point3F n; if(!staticmesh->bounds.collideLine(s, e, &t, &n)) return false; // get the likely occluders... sgStaticMeshBVPT::objectList list; objinfo->sgBVPT.collectObjectsClipped(s, e, list); //objinfo->sgBVPT.collectObjects(staticmesh->bounds, list); Point3F vect = e - s; F32 raycastdist; Point2F temp2; // now cast against them... U32 tc = list.size(); sgStaticMeshTri **tris = list.address(); for(U32 i=0; isgPlane, vect) >= 0.0f) continue; //stats sgStatistics::sgStaticMeshSurfaceOccluderCount++; if(castRayTriangle(s, vect, tri->sgVert[0], tri->sgVert[1], tri->sgVert[2], raycastdist, temp2)) return true; } return false; } void sgShadowObjects::sgClearStaticMeshBVPTData() { for(U32 i=0; igetWorldBox())) continue; sgIntersectingSceneObjects.push_back(obj); InteriorInstance *inst = dynamic_cast(obj); if(!inst) continue; Interior *detail = inst->getDetailLevel(0); for(U32 sm=0; smgetStaticMeshCount(); sm++) { const ConstructorSimpleMesh *smobj = detail->getStaticMesh(sm); Box3F bounds = smobj->bounds; Box3F worldbounds; bounds.min.convolve(smobj->scale); bounds.max.convolve(smobj->scale); MathUtils::transformBoundingBox(bounds, smobj->transform, worldbounds); bounds = worldbounds; bounds.min.convolve(inst->getScale()); bounds.max.convolve(inst->getScale()); MathUtils::transformBoundingBox(bounds, inst->getTransform(), worldbounds); if(!surfacebox.isOverlapped(worldbounds)) continue; sgIntersectingStaticMeshObjects.increment(1); sgStaticMeshInfo &sminfo = sgIntersectingStaticMeshObjects.last(); sminfo.sgStaticMesh = (ConstructorSimpleMesh *)smobj; sminfo.sgInteriorInstance = inst; } } } bool sgPlanarLightMap::sgCastRay(Point3F s, Point3F e, SceneObject *obj, Interior *detail, ConstructorSimpleMesh *sm, sgOccluder &occluderinfo) { obj->getWorldTransform().mulP(s); obj->getWorldTransform().mulP(e); s.convolveInverse(obj->getScale()); e.convolveInverse(obj->getScale()); if(sm) { #ifdef SG_STATICMESH_BVPT_SHADOWS // expects points in interior space... if(!sgShadowObjects::sgCastRayStaticMesh(s, e, sm)) return false; #else MatrixF mat = sm->transform; mat.inverse(); mat.mulP(s); mat.mulP(e); s.convolveInverse(sm->scale); e.convolveInverse(sm->scale); F32 t; Point3F n; if(!sm->bounds.collideLine(s, e, &t, &n)) return false; RayInfo ri; if(!sm->castRay(s, e, &ri)) return false; #endif occluderinfo.sgObject = sm; occluderinfo.sgSurface = SG_NULL_SURFACE; } else if(detail) { RayInfo ri; if(!detail->castRay(s, e, &ri)) return false; occluderinfo.sgObject = obj; occluderinfo.sgSurface = ri.face; } else { RayInfo ri; if(!obj->castRay(s, e, &ri)) return false; occluderinfo.sgObject = obj; occluderinfo.sgSurface = ri.face; } return true; } bool sgPlanarLightMap::sgIsValidOccluder(const sgOccluder &occluderinfo, Vector &validoccluders, bool isinnerlexel) { if(isinnerlexel) { validoccluders.push_back(occluderinfo); return true; } for(U32 i=0; i tris; if(windingcount < 3) return; tris.increment(windingcount - 2); // test for smoothing... Point3F normal = triStrip[0].sgNorm; for(U32 i=1; i 0.0f) && (t > 0.0f)) // continue; if(s > smax) { sgSAxis = i; smax = s; } if(t > tmax) { sgTAxis = i; tmax = t; } } // try again... badtexgen = false; if(sgSAxis == sgTAxis) { // find the axis with the minimal diff (the bad axis)... U32 a = (sgSAxis + 1) % 3; U32 b = (sgSAxis + 2) % 3; F64 absa = mFabs(mFabs(sgLightMapSVector[sgSAxis]) - mFabs(sgLightMapSVector[a])); F64 absb = mFabs(mFabs(sgLightMapSVector[sgSAxis]) - mFabs(sgLightMapSVector[b])); smax = getMin(absa, absb); a = (sgTAxis + 1) % 3; b = (sgTAxis + 2) % 3; absa = mFabs(mFabs(sgLightMapTVector[sgTAxis]) - mFabs(sgLightMapTVector[a])); absb = mFabs(mFabs(sgLightMapTVector[sgTAxis]) - mFabs(sgLightMapTVector[b])); tmax = getMin(absa, absb); S32 avoidaxis = -1; S32 *newaxis = NULL; Point3D *vector = NULL; if(smax < tmax) { avoidaxis = sgTAxis; newaxis = &sgSAxis; vector = &sgLightMapSVector; } else { avoidaxis = sgSAxis; newaxis = &sgTAxis; vector = &sgLightMapTVector; } // find the max on the bad axis that's not on the original axis... F64 max = 0.0f; for(S32 i=0; i<3; i++) { if(i == avoidaxis) continue; F64 val = mFabs((*vector)[i]); if(val > max) { (*newaxis) = i; max = val; } } //Con::warnf("Questionable light map axis gen..."); badtexgen = true; } AssertFatal(((sgSAxis != -1) && (sgTAxis != -1) && (sgSAxis != sgTAxis)), "Unable to determine axis info!"); // these are tristrips!!! for(U32 t=0; t 0.0f), "!"); // could break this out for speed... if((t == 0) && (v == 0)) sgSurfaceBox = Box3F(pointw, pointw); else { sgSurfaceBox.max.setMax(pointw); sgSurfaceBox.min.setMin(pointw); } } if(sgUseSmoothing) sgBuildDerivatives(tris[t]); } sgBuildLexels(tris); // stats... sgStatistics::sgInteriorSurfaceIncludedCount++; if(sgUseSmoothing) { sgStatistics::sgInteriorSurfaceSmoothedCount++; sgStatistics::sgInteriorSurfaceSmoothedLexelCount += sgInnerLexels.size() + sgOuterLexels.size(); } } void sgPlanarLightMap::sgBuildDerivatives(sgSmoothingTri &tri) { // build the derivatives for linear interpolation... sgSmoothingVert &va = tri.sgVerts[0]; sgSmoothingVert &vb = tri.sgVerts[1]; sgSmoothingVert &vc = tri.sgVerts[2]; F32 vsac = va.sgVert[sgSAxis] - vc.sgVert[sgSAxis]; F32 vsbc = vb.sgVert[sgSAxis] - vc.sgVert[sgSAxis]; F32 vtac = va.sgVert[sgTAxis] - vc.sgVert[sgTAxis]; F32 vtbc = vb.sgVert[sgTAxis] - vc.sgVert[sgTAxis]; F32 spartial = 1.0f / ((vsbc * vtac) - (vsac * vtbc)); F32 tpartial = 1.0f / ((vsac * vtbc) - (vsbc * vtac)); for(S32 c=0; c<3; c++) { F32 nac = va.sgNorm[c] - vc.sgNorm[c]; F32 nbc = vb.sgNorm[c] - vc.sgNorm[c]; tri.sgSDerivative[c] = ((nbc * vtac) - (nac * vtbc)) * spartial; tri.sgTDerivative[c] = ((nbc * vsac) - (nac * vsbc)) * tpartial; } } void sgPlanarLightMap::sgBuildLexels(const Vector &tris) { // loop through the texels... // sort by inner and outer... const U32 lexelmax = sgHeight * sgWidth; const U32 buffersize = lexelmax * sizeof(sgLexel); sgInnerLexels.clear(); sgInnerLexels.reserve(lexelmax); sgOuterLexels.clear(); sgOuterLexels.reserve(lexelmax); // this is faster than Vector[]... sgSmoothingTri *trisptr = tris.address(); bool outer; Point3F vec2; Point3F cross; Point3D pos = sgWorldPosition; Point3D run = sgLightMapSVector * sgWidth; sgSmoothingTri *container; bool halfsize = !LightManager::sgAllowFullLightMaps(); U32 lmscalemask = LightManager::sgGetLightMapScale() - 1; for(U32 y=0; ysgVerts[0].sgVert; normal = container->sgVerts[0].sgNorm; normal += (container->sgSDerivative * posrelative[sgSAxis]) + (container->sgTDerivative * posrelative[sgTAxis]); normal.normalize(); } // find respective vector... if(outer) { sgOuterLexels.increment(); sgLexel &temp = sgOuterLexels.last(); temp.lmPos.x = x; temp.lmPos.y = y; temp.worldPos = pos32; temp.normal = normal; } else { sgInnerLexels.increment(); sgLexel &temp = sgInnerLexels.last(); temp.lmPos.x = x; temp.lmPos.y = y; temp.worldPos = pos32; temp.normal = normal; } pos += sgLightMapSVector; } pos -= run; pos += sgLightMapTVector; } // if no inner lexels exist fake some for shadow testing... if(sgInnerLexels.size() < 1) { for(U32 i=0; isgDiffuseRestrictZone || light->sgAmbientRestrictZone) { for(U32 z=0; zgetNumCurrZones(); z++) { S32 zone = sgInteriorInstance->getCurrZone(z); if(zone > 0) { if((zone == light->sgZone[0]) || (zone == light->sgZone[1])) { isinzone = true; break; } } } if(!isinzone && (sgInteriorSurface != SG_NULL_SURFACE)) { S32 zone = sgInteriorInstance->getSurfaceZone(sgInteriorSurface, sgInteriorDetail); if((light->sgZone[0] == zone) || (light->sgZone[1] == zone)) isinzone = true; } if(!isinzone && sgInteriorStaticMesh) { // need to find zone(s)... } } // allow what? bool allowdiffuse = (!light->sgDiffuseRestrictZone) || isinzone; bool allowambient = (!light->sgAmbientRestrictZone) || isinzone; // should I bother? if((!allowdiffuse) && (!allowambient)) return; // first get lighting model... sgLightingModel &model = sgLightingModelManager::sgGetLightingModel(light->sgLightingModelName); model.sgSetState(light); // test for early out... if(!model.sgCanIlluminate(sgSurfaceBox)) { model.sgResetState(); return; } // this is slow, so do it after the early out... model.sgInitStateLM(); // setup shadow objects... Box3F lightvolume = sgSurfaceBox; if(light->mType == LightInfo::Vector) { const Point3F lightVector = (SG_STATIC_LIGHT_VECTOR_DIST * -1) * light->mDirection; for(U32 i=0; imPos); lightvolume.min.setMin(light->mPos); } // build a list of potential shadow casters... if(light->sgCastsShadows && LightManager::sgAllowShadows()) sgGetIntersectingObjects(lightvolume, sgInteriorInstance); // stats... sgStatistics::sgInteriorSurfaceIlluminatedCount++; sgStatistics::sgInteriorLexelCount += sgInnerLexels.size() + sgOuterLexels.size(); // put rayCast into lighting mode... Interior::smLightingCastRays = true; Vector shadowingsurfaces; ColorF diffuse; ColorF ambient; Point3F lightingnormal; for(i=0; i SG_MIN_LEXEL_INTENSITY) || (diffuse.green > SG_MIN_LEXEL_INTENSITY) || (diffuse.blue > SG_MIN_LEXEL_INTENSITY))) { // stats sgStatistics::sgInteriorLexelDiffuseCount++; // step four: check for shadows... bool shadowed = false; if(LightManager::sgAllowShadows()) { // set light pos for shadows... Point3F lightpos; if(light->mType == LightInfo::Vector) { lightpos = lexel.worldPos + ((SG_STATIC_LIGHT_VECTOR_DIST * -1) * light->mDirection); } else { lightpos = light->mPos; } // potential problem with this setup is the rare but possible // occurrence of outer lexel light leaks from raycast hitting an // interior surface that's not in the shadow list but also has valid // occluders between it and the lexel (ie: stops the ray before // finding the valid occluders). // // possible solutions: // -Ctor style shadow system // -custom raycast that collects ALL occluding surfaces // // both solutions are heavyweight - first see if the problem occurs // in the wild, this may be a theoretical problem. // start by finding any shadow casters sgOccluder info; for(U32 o=0; ogetStaticMeshCount(); sm++) { ConstructorSimpleMesh *mesh = (ConstructorSimpleMesh *)sgInteriorDetail->getStaticMesh(sm); if(mesh == sgInteriorStaticMesh) continue; if(!sgCastRay(lightpos, lexel.worldPos, sgInteriorInstance, NULL, mesh, info)) continue; if(!sgIsValidOccluder(info, shadowingsurfaces, (i == sglpInner))) continue; shadowed = true; break; } } } // if false then self sm test... if(!shadowed && sgInteriorStaticMesh) { // reverse cast direction... if(sgCastRay(lexel.worldPos, lightpos, sgInteriorInstance, NULL, sgInteriorStaticMesh, info)) { if(sgIsValidOccluder(info, shadowingsurfaces, (i == sglpInner))) shadowed = true; } } } if(!shadowed) { // cool, got this far so either there was no occluder // or it was an alpha and eventually we found a light // ray segment that didn't get blocked... // // step five: apply the lighting to the light map... sgDirty = true; const U32 lmindex = (U32)((lexel.lmPos.y * sgWidth) + lexel.lmPos.x); sgTexels->sgData[lmindex] += diffuse; } } if(allowambient && ((ambient.red > SG_MIN_LEXEL_INTENSITY) || (ambient.green > SG_MIN_LEXEL_INTENSITY) || (ambient.blue > SG_MIN_LEXEL_INTENSITY))) { sgDirty = true; const U32 lmindex = (U32)((lexel.lmPos.y * sgWidth) + lexel.lmPos.x); sgTexels->sgData[lmindex] += ambient; } } } // put rayCast back to normal mode... Interior::smLightingCastRays = false; model.sgResetState(); } void sgPlanarLightMap::sgMergeLighting(GBitmap *lightmap, U32 xoffset, U32 yoffset) { // stats elapsedTimeAggregate time = elapsedTimeAggregate(sgStatistics::sgInteriorSurfaceMergeTime); sgStatistics::sgInteriorSurfaceMergeCount++; sgTexels->sgFillInLighting(); sgTexels->sgBlur(); U32 y, x, d, s, frag; U8 *bits; ColorF *texels = sgTexels->sgData; for(y=0; ygetAddress(xoffset, (yoffset + y)); d = 0; for(x=0; x 255) frag = 255; bits[d++] = frag; frag = bits[d] + (texel.green * 255.0f); if(frag > 255) frag = 255; bits[d++] = frag; frag = bits[d] + (texel.blue * 255.0f); if(frag > 255) frag = 255; bits[d++] = frag; } } } //---------------------------------------------- void sgTerrainLightMap::sgCalculateLighting(LightInfo *light) { // setup zone info... bool isinzone = (light->sgZone[0] == 0) || (light->sgZone[1] == 0); // allow what? bool allowdiffuse = (!light->sgDiffuseRestrictZone) || isinzone; bool allowambient = (!light->sgAmbientRestrictZone) || isinzone; // should I bother? if((!allowdiffuse) && (!allowambient)) return; AssertFatal((sgTerrain), "Member 'sgTerrain' must be populated."); // setup constants... F32 terrainlength = (sgTerrain->getSquareSize() * TerrainBlock::BlockSize); const F32 halfterrainlength = terrainlength * 0.5; U32 time = Platform::getRealMilliseconds(); Point2F s, t; s[0] = sgLightMapSVector[0]; s[1] = sgLightMapSVector[1]; t[0] = sgLightMapTVector[0]; t[1] = sgLightMapTVector[1]; Point2F run = t * sgWidth; Point2F start; start[0] = sgWorldPosition[0] + halfterrainlength; start[1] = sgWorldPosition[1] + halfterrainlength; sgLightingModel &model = sgLightingModelManager::sgGetLightingModel( light->sgLightingModelName); model.sgSetState(light); model.sgInitStateLM(); Point2I lmindexmin, lmindexmax; if(light->mType == LightInfo::Vector) { lmindexmin.x = 0; lmindexmin.y = 0; lmindexmax.x = (sgWidth - 1); lmindexmax.y = (sgHeight - 1); } else { F32 maxrad = model.sgGetMaxRadius(); Box3F worldbox = Box3F(light->mPos, light->mPos); worldbox.min -= Point3F(maxrad, maxrad, maxrad); worldbox.max += Point3F(maxrad, maxrad, maxrad); lmindexmin.x = (worldbox.min.x - sgWorldPosition.x) / s.x; lmindexmin.y = (worldbox.min.y - sgWorldPosition.y) / t.y; lmindexmax.x = (worldbox.max.x - sgWorldPosition.x) / s.x; lmindexmax.y = (worldbox.max.y - sgWorldPosition.y) / t.y; lmindexmin.x = getMax(lmindexmin.x, S32(0)); lmindexmin.y = getMax(lmindexmin.y, S32(0)); lmindexmax.x = getMin(lmindexmax.x, S32(sgWidth - 1)); lmindexmax.y = getMin(lmindexmax.y, S32(sgHeight - 1)); } S32 lmx, lmy, lmindex; Point3F lexelworldpos; Point3F normal; ColorF diffuse(0.0f, 0.0f, 0.0f); ColorF ambient(0.0f, 0.0f, 0.0f); Point3F lightingnormal(0.0f, 0.0f, 0.0f); Point2F point = ((t * lmindexmin.y) + start + (s * lmindexmin.x)); for(lmy=lmindexmin.y; lmygetNormalAndHeight(point, &normal, &lexelworldpos.z, false); // too often unset, must do these here... ambient.set(0.0f, 0.0f, 0.0f); diffuse.set(0.0f, 0.0f, 0.0f); lightingnormal.set(0.0f, 0.0f, 0.0f); model.sgLightingLM(lexelworldpos, normal, diffuse, ambient, lightingnormal); if(allowdiffuse && ((diffuse.red > SG_MIN_LEXEL_INTENSITY) || (diffuse.green > SG_MIN_LEXEL_INTENSITY) || (diffuse.blue > SG_MIN_LEXEL_INTENSITY))) { // step four: check for shadows... bool shadowed = false; RayInfo info; if(light->sgCastsShadows && LightManager::sgAllowShadows()) { // set light pos for shadows... Point3F lightpos = light->mPos; if(light->mType == LightInfo::Vector) { lightpos = SG_STATIC_LIGHT_VECTOR_DIST * light->mDirection * -1; lightpos = lexelworldpos + lightpos; } // make texels terrain space coord into a world space coord... RayInfo info; if(sgTerrain->getContainer()->castRay(lightpos, (lexelworldpos + (lightingnormal * 0.5f)), ShadowCasterObjectType, &info)) { shadowed = true; } } if(!shadowed) { // step five: apply the lighting to the light map... sgTexels->sgData[lmindex] += diffuse; } } if(allowambient && ((ambient.red > 0.0f) || (ambient.green > 0.0f) || (ambient.blue > 0.0f))) { //sgTexels->sgData[lmindex] += ambient; } /*if((lmx & 0x1) == (lmy & 0x1)) sgTexels[lmindex] += ColorF(1.0, 0.0, 0.0); else sgTexels[lmindex] += ColorF(0.0, 1.0, 0.0);*/ // stats... sgStatistics::sgTerrainLexelCount++; point += s; } point = ((t * lmy) + start + (s * lmindexmin.x)); } model.sgResetState(); // stats... sgStatistics::sgTerrainLexelTime += Platform::getRealMilliseconds() - time; } void sgTerrainLightMap::sgMergeLighting(ColorF *lightmap) { sgTexels->sgBlur(); U32 y, x, index; for(y=0; ysgData[index]; } } }