//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "map2dif/createLightmaps.h" #include "dgl/gBitmap.h" #include "math/mMath.h" #include "core/bitMatrix.h" #include "interior/interior.h" #ifdef DUMP_LIGHTMAPS #include "core/fileStream.h" #endif //------------------------------------------------------------------------------ // globals //------------------------------------------------------------------------------ namespace { static char * newStrDup(const char * src) { char * buffer = new char [dStrlen(src) + 1]; dStrcpy(buffer, src); return(buffer); } static const F32 AnimationTimes[] = { 2.f, 1.f, 0.5f, 0.25f, 0.125f }; Lighting * gWorkingLighting; } extern bool gBuildAsLowDetail; void sgIlluminateSurface(EditGeometry::Surface &surface) { if(!surface.pLMap) return; for(U32 y=0; ygetHeight(); y++) { for(U32 x=0; xgetWidth(); x++) { U8 *lexel = surface.pLMap->getAddress(x,y); for(U32 c=0; c<3; c++) { U32 l = lexel[c]; l += surface.sgSelfIllumination[c]; lexel[c] = mClamp(l, U32(0), U32(255)); } } } } //------------------------------------------------------------------------------ // Class Lighting //------------------------------------------------------------------------------ Lighting::Lighting() : mNumAmbiguousPlanes(0), mNodeRepository(0), mWindingStore(0), mSurfaceEmitterInfos(0), mEmitterSurfaceIndices(0) { } Lighting::~Lighting() { for(U32 i = 0; i < mLights.size(); i++) delete mLights[i]; for(U32 i = 0; i < mEmitters.size(); i++) delete mEmitters[i]; // destroy all the vectors for(U32 i = 0; i < mSurfaces.size(); i++) for(U32 j = 0; j < mSurfaces[i]->mNumEmitters; j++) mSurfaces[i]->mEmitters[j]->mShadowed.~UniqueVector(); // mEmitterInfoChunker.clear(); mNodeChunker.clear(); mSurfaceChunker.clear(); // delete [] mSurfaceEmitterInfos; delete [] mEmitterSurfaceIndices; } //------------------------------------------------------------------------------ Lighting::SVNode * Lighting::getShadowVolume(U32 index) { return(mShadowVolumes[index]); } Lighting::Surface * Lighting::getSurface(U32 index) { return(mSurfaces[index]); } //------------------------------------------------------------------------------ /*F64 Lighting::getLumelScale() { return(gWorkingGeometry->mWorldEntity->mLumelScale); }*/ //------------------------------------------------------------------------------ void Lighting::grabLights(bool alarmMode) { // grab all the light emitters and targets first.. for(U32 i = 0; i < gWorkingGeometry->mEntities.size(); i++) { BaseLightEmitterEntity * emitter = dynamic_cast(gWorkingGeometry->mEntities[i]); if(emitter) mBaseLightEmitters.push_back(emitter); TargetEntity * target = dynamic_cast(gWorkingGeometry->mEntities[i]); if(target) mTargets.push_back(target); } // process all the lights for(U32 i = 0; i < gWorkingGeometry->mEntities.size(); i++) { BaseLightEntity * entity = dynamic_cast(gWorkingGeometry->mEntities[i]); if(entity) { if((entity->mAlarmStatus == BaseLightEntity::NormalOnly && alarmMode) || (entity->mAlarmStatus == BaseLightEntity::AlarmOnly && !alarmMode)) continue; Light * light = new Light; if(!light->build(entity)) { delete light; continue; } light->alarm = alarmMode; mLights.push_back(light); } } // Merge the animated lights that allow it... // for (U32 i = 0; i < mLights.size(); i++) { Light* lightCompare = mLights[i]; if (!lightCompare->mAnimated) continue; for (U32 j = i + 1; j < mLights.size();) { Light* lightPossible = mLights[j]; if (!lightPossible->mAnimated || (lightCompare->mNumStates != lightPossible->mNumStates || lightCompare->mAnimType != lightPossible->mAnimType || lightCompare->alarm != lightPossible->alarm)) { j++; continue; } bool valid = true; for (U32 k = 0; k < lightCompare->mNumStates && valid == true; k++) { const Light::LightState& state1 = mLightStates[lightCompare->mStateIndex + k]; const Light::LightState& state2 = mLightStates[lightPossible->mStateIndex + k]; if (state1.mDuration != state2.mDuration) valid = false; // All the emitters in an animated light share the same color, so // we can just check the first... if (mEmitters[state1.mEmitterIndex]->mColor != mEmitters[state2.mEmitterIndex]->mColor) valid = false; } if (valid) { // Since the states are the same, we just want to loop through them, and reassign their // emitters. We'll just put the new emitter range at the end of the mEmitters array, // null the current pointers. for (U32 k = 0; k < lightCompare->mNumStates; k++) { Light::LightState* state1 = &mLightStates[lightCompare->mStateIndex + k]; const Light::LightState* state2 = &mLightStates[lightPossible->mStateIndex + k]; U32 newEmitterIndex = mEmitters.size(); Emitter* pEmitter; for (U32 l = 0; l < state1->mNumEmitters; l++) { pEmitter = mEmitters[state1->mEmitterIndex + l]; pEmitter->mIndex = mEmitters.size(); mEmitters.push_back(pEmitter); mEmitters[state1->mEmitterIndex + l] = NULL; } for (U32 l = 0; l < state2->mNumEmitters; l++) { pEmitter = mEmitters[state2->mEmitterIndex + l]; pEmitter->mIndex = mEmitters.size(); mEmitters.push_back(pEmitter); mEmitters[state2->mEmitterIndex + l] = NULL; } state1->mEmitterIndex = newEmitterIndex; state1->mNumEmitters = mEmitters.size() - newEmitterIndex; } delete mLights[j]; mLights.erase(j); } else { // Step to the next light... j++; } } } } //------------------------------------------------------------------------------ void Lighting::convertToFan(MiniWinding & winding, U32 fanMask) { U32 tmpIndices[LightingMaxWindingPoints]; U32 fanIndices[LightingMaxWindingPoints]; tmpIndices[0] = 0; U32 idx = 1; for(U32 i = 1; i < winding.mNumIndices; i += 2) tmpIndices[idx++] = i; for(U32 i = ((winding.mNumIndices - 1) & (~0x1)); i > 0; i -= 2) tmpIndices[idx++] = i; idx = 0; for(U32 i = 0; i < winding.mNumIndices; i++) if(fanMask & (1 << i)) fanIndices[idx++] = winding.mIndices[tmpIndices[i]]; // set the data winding.mNumIndices = idx; for(U32 i = 0; i < winding.mNumIndices; i++) winding.mIndices[i] = fanIndices[i]; } void Lighting::copyWinding(MiniWinding & dest, Winding & src) { dest.mNumIndices = src.numIndices; dMemcpy(dest.mIndices, src.indices, src.numIndices * sizeof(U32)); } //------------------------------------------------------------------------------ void Lighting::grabSurfaces() { for(U32 i = 0; i < gWorkingGeometry->mSurfaces.size(); i++) { Surface * surface = createSurface(); surface->mSurfaceIndex = i; surface->sgLightingScale = gWorkingGeometry->mSurfaces[i].sgLightingScale; copyWinding(surface->mWinding, gWorkingGeometry->mSurfaces[i].winding); surface->mPlaneIndex = gWorkingGeometry->mSurfaces[i].planeIndex; // convert the surface... convertToFan(surface->mWinding, gWorkingGeometry->mSurfaces[i].fanMask); mSurfaces.push_back(surface); } } Lighting::EmitterInfo * Lighting::createEmitterInfo() { // clear it out (will not call vector ctor's) EmitterInfo * info = mEmitterInfoChunker.alloc(); dMemset(info, 0, sizeof(EmitterInfo)); return(info); } //------------------------------------------------------------------------------ void Lighting::processSurfaces() { Vector list; list.reserve(mSurfaces.size() * 6); // create a run list for the surfaces.. for(U32 i = 0; i < mSurfaces.size(); i++) { mSurfaces[i]->mNumEmitters = 0; // for(U32 j = 0; j < mEmitters.size(); j++) { if(mEmitters[j] != NULL && mEmitters[j]->isSurfaceLit(mSurfaces[i])) { mSurfaces[i]->mNumEmitters++; list.push_back(j); } } } if(!list.size()) return; mSurfaceEmitterInfos = new EmitterInfo* [list.size()]; U32 curOffset = 0; // now walk this and set the emitterinfo pntr/size, also create the infos for(U32 i = 0; i < mSurfaces.size(); i++) { mSurfaces[i]->mEmitters = &mSurfaceEmitterInfos[curOffset]; const U32 & numEmitters = mSurfaces[i]->mNumEmitters; if(numEmitters) { for(U32 j = 0; j < numEmitters; j++) { EmitterInfo * eInfo = createEmitterInfo(); eInfo->mEmitter = list[curOffset++]; mSurfaces[i]->mEmitters[j] = eInfo; mEmitters[eInfo->mEmitter]->mNumSurfaces++; } } } // now do the same for the emitters - equally sized lists.. mEmitterSurfaceIndices = new U32 [list.size()]; curOffset = 0; for(U32 i = 0; i < mEmitters.size(); i++) { if (mEmitters[i] == NULL) continue; mEmitters[i]->mSurfaces = &mEmitterSurfaceIndices[curOffset]; U32 count = mEmitters[i]->mNumSurfaces; curOffset += count; U32 curSurface = 0; // go until filled for(U32 j = 0; curSurface != count; j++) for(U32 k = 0; k < mSurfaces[j]->mNumEmitters; k++) if(mSurfaces[j]->mEmitters[k]->mEmitter == i) mEmitters[i]->mSurfaces[curSurface++] = j; } } //------------------------------------------------------------------------------ U32 Lighting::constructPlane(const Point3D& Point1, const Point3D& Point2, const Point3D& Point3) const { // |yi zi 1| |xi zi 1| |xi yi 1| |xi yi zi| // |yj zj 1| x + |xj zj 1| y + |xj yj 1| z = |xj yj zj| // |yk zk 1| |xk zk 1| |xk yk 1| |xk yk zk| // Point3D normal; F64 dist; normal.x = Point1.y * Point2.z - Point1.y * Point3.z + Point3.y * Point1.z - Point2.y * Point1.z + Point2.y * Point3.z - Point3.y * Point2.z; normal.y = Point1.x * Point2.z - Point1.x * Point3.z + Point3.x * Point1.z - Point2.x * Point1.z + Point2.x * Point3.z - Point3.x * Point2.z; normal.z = Point1.x * Point2.y - Point1.x * Point3.y + Point3.x * Point1.y - Point2.x * Point1.y + Point2.x * Point3.y - Point3.x * Point2.y; dist = Point1.x * Point2.y * Point3.z - Point1.x * Point2.z * Point3.y + Point1.y * Point2.z * Point3.x - Point1.y * Point2.x * Point3.z + Point1.z * Point2.x * Point3.y - Point1.z * Point2.y * Point3.x; normal.x = -normal.x; normal.z = -normal.z; // return(gWorkingGeometry->insertPlaneEQ(normal, dist)); } //------------------------------------------------------------------------------ void Lighting::createShadowVolumes() { for(U32 i = 0; i < mSurfaces.size(); i++) { // if(!mSurfaces[i]->mNumEmitters) continue; // create this surface's poly node mSurfaces[i]->mPolyNode = createNode(SVNode::PolyPlane); *(mSurfaces[i]->mPolyNode->mWinding) = mSurfaces[i]->mWinding; mSurfaces[i]->mPolyNode->mPlaneIndex = mSurfaces[i]->mPlaneIndex; // create a shadow volume for each of the emitters for this surface for(U32 j = 0; j < mSurfaces[i]->mNumEmitters; j++) { const MiniWinding & winding = mSurfaces[i]->mWinding; const Emitter * emitter = mEmitters[mSurfaces[i]->mEmitters[j]->mEmitter]; SVNode * root = 0; SVNode ** current = &root; for(U32 k = 0; k < winding.mNumIndices; k++) { SVNode * sNode = createNode(SVNode::ShadowPlane); // create the plane for this node sNode->mPlaneIndex = constructPlane( gWorkingGeometry->getPoint(winding.mIndices[(k+1) % winding.mNumIndices]), gWorkingGeometry->getPoint(winding.mIndices[k]), gWorkingGeometry->getPoint(emitter->mPoint)); // target the original poly sNode->mTarget = mSurfaces[i]->mPolyNode; // add to the front *current = sNode; current = &(*current)->mFront; } mSurfaces[i]->mEmitters[j]->mShadowVolume = mShadowVolumes.size(); mShadowVolumes.push_back(root); } } } //------------------------------------------------------------------------------ void Lighting::processEmitterBSPs() { for(U32 i = 0; i < mEmitters.size(); i++) if (mEmitters[i] != NULL) mEmitters[i]->processBSP(); } //------------------------------------------------------------------------------ // simple point store for use with the lexel nodes void Lighting::flushLexelPoints() { mLexelPoints.setSize(0); mLexelPoints.reserve(LexelPointStoreSize); } const Point3D & Lighting::getLexelPoint(U32 index) { AssertFatal(index < mLexelPoints.size(), "Lighting::getLexelPoint: index out of range"); return(mLexelPoints[index]); } U32 Lighting::insertLexelPoint(const Point3D & pnt) { if(!mLexelPoints.size()) mLexelPoints.reserve(LexelPointStoreSize); if(mLexelPoints.size() == (mLexelPoints.capacity() - 1)) mLexelPoints.reserve(mLexelPoints.size() + LexelPointStoreSize); mLexelPoints.push_back(pnt); return(mLexelPoints.size() - 1); } //------------------------------------------------------------------------------ void Lighting::lightSurfaces() { for(U32 i = 0; i < mSurfaces.size(); i++) { Surface * surface = mSurfaces[i]; // must have emitters on this surface if(!surface->mNumEmitters) continue; const PlaneEQ & plane = gWorkingGeometry->getPlaneEQ(surface->mPlaneIndex); const EditGeometry::Surface * editSurface = &gWorkingGeometry->mSurfaces[surface->mSurfaceIndex]; // must have a lightmap if(!editSurface->pLMap) continue; const F32* const & lGenX = editSurface->lmapTexGenX; const F32* const & lGenY = editSurface->lmapTexGenY; // AssertFatal((lGenX[0] * lGenX[1] == 0.f) && (lGenX[0] * lGenX[2] == 0.f) && (lGenX[1] * lGenX[2] == 0.f), "Bad texgen!"); AssertFatal((lGenY[0] * lGenY[1] == 0.f) && (lGenY[0] * lGenY[2] == 0.f) && (lGenY[1] * lGenY[2] == 0.f), "Bad texgen!"); // get the axis index for the texgens (could be swapped) S32 si; S32 ti; S32 axis = -1; // if(lGenX[0] == 0.f && lGenY[0] == 0.f) // YZ { axis = 0; if(lGenX[1] == 0.f) { // swapped? si = 2; ti = 1; } else { si = 1; ti = 2; } } else if(lGenX[1] == 0.f && lGenY[1] == 0.f) // XZ { axis = 1; if(lGenX[0] == 0.f) { // swapped? si = 2; ti = 0; } else { si = 0; ti = 2; } } else if(lGenX[2] == 0.f && lGenY[2] == 0.f) // XY { axis = 2; if(lGenX[0] == 0.f) { // swapped? si = 1; ti = 0; } else { si = 0; ti = 1; } } AssertFatal(!(axis == -1), "Lighting::lightSurfaces: bad TexGen!"); // get the start point for this lightmap (project min to surface) Point3D start; F64 * pStart = ((F64*)start); const F64 * pNormal = ((const F64*)plane.normal); // TexGens come in scaled by (1/lightmapscale) pStart[si] = -lGenX[3] * surface->sgLightingScale; pStart[ti] = -lGenY[3] * surface->sgLightingScale; pStart[axis] = ((pNormal[si] * pStart[si]) + (pNormal[ti] * pStart[ti]) + plane.dist) / -pNormal[axis]; // get the s/t vecs oriented on the surface Point3D sVec; Point3D tVec; F64 * pSVec = ((F64*)sVec); F64 * pTVec = ((F64*)tVec); Point3D planeNormal; F64 angle; // s pSVec[si] = 1.f; pSVec[ti] = 0.f; planeNormal = plane.normal; ((F64*)planeNormal)[ti] = 0.f; planeNormal.normalize(); angle = mAcos(mClampF(((F64*)planeNormal)[axis], -1.f, 1.f)); pSVec[axis] = (((F64*)planeNormal)[si] < 0.f) ? mTan(angle) : -mTan(angle); // t pTVec[ti] = 1.f; pTVec[si] = 0.f; planeNormal = plane.normal; ((F64*)planeNormal)[si] = 0.f; planeNormal.normalize(); angle = mAcos(mClampF(((F64*)planeNormal)[axis], -1.f, 1.f)); pTVec[axis] = (((F64*)planeNormal)[ti] < 0.f) ? mTan(angle) : -mTan(angle); // scale them sVec *= surface->sgLightingScale; tVec *= surface->sgLightingScale; // create all the animated light bitmaps for(U32 j = 0; j < surface->mNumEmitters; j++) { EmitterInfo * emitterInfo = surface->mEmitters[j]; Emitter * emitter = mEmitters[emitterInfo->mEmitter]; if(!emitter->mAnimated) continue; emitterInfo->mLightMap = new GBitmap(editSurface->lMapDimX, editSurface->lMapDimY, false, GBitmap::Intensity); } Point3D & curPos = start; Point3D sRun = sVec * editSurface->lMapDimX; // get the lexel area Point3D cross; mCross(sVec, tVec, &cross); F64 maxLexelArea = cross.len(); // BitMatrix outsideLexels(editSurface->lMapDimX, editSurface->lMapDimY); // outsideLexels.clearAllBits(); // get the world coordinate for each lexel for(U32 y = 0; y < editSurface->lMapDimY; y++) { for(U32 x = 0; x < editSurface->lMapDimX; x++) { SVNode * lNode = createNode(SVNode::LexelPlane); lNode->mPlaneIndex = surface->mPlaneIndex; lNode->mWinding->mNumIndices = 4; // set the poly indices lNode->mWinding->mIndices[0] = insertLexelPoint(curPos); lNode->mWinding->mIndices[1] = insertLexelPoint(curPos + sVec); lNode->mWinding->mIndices[2] = insertLexelPoint(curPos + sVec + tVec); lNode->mWinding->mIndices[3] = insertLexelPoint(curPos + tVec); ColorF colSum(0,0,0); // walk the emitters for each that is not shadowed, light it for(U32 j = 0; j < surface->mNumEmitters; j++) { EmitterInfo * emitterInfo = surface->mEmitters[j]; Emitter * emitter = mEmitters[emitterInfo->mEmitter]; // get the light value F64 intensity = emitter->calcIntensity(lNode); F64 shadowScale = 1.f; if(emitterInfo->mShadowed.size()) { // insert a copy of the lexel node, unless last emitter SVNode * nodeStore = 0; if(j == (surface->mNumEmitters-1)) { // use the lexel node nodeStore = lNode; lNode = 0; } else { // copy nodeStore = createNode(SVNode::LexelPlane); *nodeStore->mWinding = *lNode->mWinding; nodeStore->mPlaneIndex = lNode->mPlaneIndex; } // // clip the node to own shadow volume // SVNode * tmp = 0; // nodeStore->clipToInverseVolume(&tmp, getShadowVolume(emitterInfo->mShadowVolume)); // nodeStore = tmp; // // // mark the lexel if it is not within own poly // if(!nodeStore) // { // outsideLexels.setBit(x, y); // continue; // } F64 lexelArea = nodeStore->getWindingSurfaceArea(); // clip the lexel node(s) to the shadow volumes for(U32 k = 0; nodeStore && (k < emitterInfo->mShadowed.size()); k++) { SVNode * nodeList = 0; SVNode * traverse = nodeStore; while(traverse) { SVNode * next = traverse->mFront; SVNode * currentStore = 0; traverse->clipToVolume(¤tStore, getShadowVolume(emitterInfo->mShadowed[k])); if(currentStore) currentStore->move(&nodeList); traverse = next; } nodeStore = nodeList; } // sum the lexel area of the remaining poly fragments F64 area = nodeStore ? nodeStore->getWindingSurfaceArea() : 0.f; // clamp it if(area > lexelArea) area = lexelArea; // set the scale shadowScale = area / lexelArea; recycleNode(nodeStore); } // fill in animated light here... if(emitterInfo->mLightMap) *(emitterInfo->mLightMap->getAddress(x,y)) = U8(intensity * shadowScale * 255.f); else if(shadowScale != 0.f) { ColorF col = emitter->mColor; col *= intensity; col *= shadowScale; colSum += col; colSum.clamp(); } } // recycleNode(lNode); // set the col U8 * pDest = editSurface->pLMap->getAddress(x,y); // add in the ambient... if(!(editSurface->flags & Interior::SurfaceOutsideVisible)) colSum += mAmbientColor; colSum.clamp(); pDest[0] = U8(colSum.red * 255.f); pDest[1] = U8(colSum.green * 255.f); pDest[2] = U8(colSum.blue * 255.f); // flushLexelPoints(); curPos += sVec; } // curPos -= sRun; curPos += tVec; } // // filter the outside lexels // static F32 filterTable[3][3] = // {{1, 2, 1}, // {2, 0, 2}, // {1, 2, 1}}; // // for(S32 y = 0; y < editSurface->lMapDimY; y++) // for(S32 x = 0; x < editSurface->lMapDimX; x++) // { // if(!outsideLexels.isSet(x, y)) // continue; // // F32 rSum = 0.f; // F32 gSum = 0.f; // F32 bSum = 0.f; // // F32 totalWeight = 0.f; // // for(S32 i = -1; i <= 1; i++) // for(S32 j = -1; j <= 1; j++) // { // Point2I pos(x+j, y+i); // // if(pos.x < 0 || pos.x >= editSurface->lMapDimX || // pos.y < 0 || pos.y >= editSurface->lMapDimY) // continue; // // // // if(!outsideLexels.isSet(pos.x, pos.y)) // { // U8 * pCol = editSurface->pLMap->getAddress(pos.x, pos.y); // F32 weight = filterTable[i+1][j+1]; // // rSum += weight * F32(pCol[0]); // gSum += weight * F32(pCol[1]); // bSum += weight * F32(pCol[2]); // // totalWeight += weight; // } // } // // // // if(totalWeight != 0.f) // { // U8 * pCol = editSurface->pLMap->getAddress(x, y); // pCol[0] = U8(rSum / totalWeight); // pCol[1] = U8(gSum / totalWeight); // pCol[2] = U8(bSum / totalWeight); // } // } #ifdef DUMP_LIGHTMAPS // static U32 majCnt = 0; U32 minCnt = 0; for(U32 j = 0; j < surface->mNumEmitters; j++) { EmitterInfo * emitterInfo = surface->mEmitters[j]; if(emitterInfo->mLightMap) { // convert the intensity bitmap to RGB GBitmap bitmap(emitterInfo->mLightMap->getWidth(), emitterInfo->mLightMap->getHeight()); for(U32 y = 0; y < emitterInfo->mLightMap->getHeight(); y++) for(U32 x = 0; x < emitterInfo->mLightMap->getWidth(); x++) { U8 * pDest = bitmap.getAddress(x, y); U8 src = *emitterInfo->mLightMap->getAddress(x, y); *pDest++ = src; *pDest++ = src; *pDest++ = src; } // FileStream output; output.open(avar("lightmap_%d_%d.png", majCnt, minCnt++), FileStream::Write); bitmap.writePNG(output); } } majCnt++; #endif } } //------------------------------------------------------------------------------ // Class Lighting::SVNode //------------------------------------------------------------------------------ Lighting::MiniWinding * Lighting::createWinding() { if(mWindingStore) { MiniWinding * winding = mWindingStore; mWindingStore = mWindingStore->mNext; // winding->mNumIndices = 0; return(winding); } // create it MiniWinding * winding = mWindingChunker.alloc(); winding->mNumIndices = 0; return(winding); } void Lighting::recycleWinding(MiniWinding * winding) { if(!winding) return; // add to head winding->mNext = mWindingStore; mWindingStore = winding; } void Lighting::recycleNode(SVNode * node) { if(!node) return; recycleNode(node->mFront); recycleNode(node->mBack); // fresh'n it up a bit node->mFront = 0; node->mTarget = 0; node->mEmitterInfo = 0; recycleWinding(node->mWinding); node->mWinding = 0; // add node->mBack = mNodeRepository; mNodeRepository = node; } //------------------------------------------------------------------- Lighting::SVNode * Lighting::createNode(SVNode::Type type) { // try to get a recycled node first... if(mNodeRepository) { SVNode * node = mNodeRepository; mNodeRepository = mNodeRepository->mBack; // node->mType = type; node->mBack = 0; node->mWinding = 0; // winding.. if(type == SVNode::PolyPlane || type == SVNode::LexelPlane) node->mWinding = createWinding(); return(node); } // create the node SVNode * node = mNodeChunker.alloc(); node->mFront = node->mBack = 0; node->mTarget = 0; node->mEmitterInfo = 0; node->mWinding = 0; // winding.. if(type == SVNode::PolyPlane || type == SVNode::LexelPlane) node->mWinding = createWinding(); node->mType = type; return(node); } //------------------------------------------------------------------------------ Lighting::SVNode::Side Lighting::SVNode::whichSide(SVNode * node) { AssertFatal(node, "SVNode::whichSide - NULL node"); bool front = false; bool back = false; const PlaneEQ & plane = gWorkingGeometry->getPlaneEQ(mPlaneIndex); for(U32 i = 0; i < node->mWinding->mNumIndices; i++) { switch(plane.whichSide(node->mType == LexelPlane ? gWorkingLighting->getLexelPoint(node->mWinding->mIndices[i]) : gWorkingGeometry->getPoint(node->mWinding->mIndices[i]))) { case PlaneFront: if(back) return(Split); front = true; break; case PlaneBack: if(front) return(Split); back = true; break; default: break; } } AssertFatal(!(front && back), "SVNode::whichSide - failed to classify node"); if(!front && !back) return(On); return(front ? Front : Back); } //------------------------------------------------------------------------------ void Lighting::SVNode::addToList(SVNode ** list) { AssertFatal(list, "Lighting::SVNode::addToList - invalid list"); SVNode * head = (*list); // add at the head... (*list) = this; mFront = head; mBack = 0; } //------------------------------------------------------------------------------ void Lighting::SVNode::split(SVNode ** front, SVNode ** back, U32 planeIndex) { AssertFatal(front && !*front, "Lighting::SVNode::splitNode - invalid front param"); AssertFatal(back && !*back, "Lighting::SVNode::splitNode - invalid back param"); *front = gWorkingLighting->createNode(mType); *back = gWorkingLighting->createNode(mType); // copy the node *(*front)->mWinding = *(*back)->mWinding = *mWinding; (*front)->mPlaneIndex = (*back)->mPlaneIndex = mPlaneIndex; (*front)->mEmitterInfo = (*back)->mEmitterInfo = mEmitterInfo; (*front)->mTarget = (*back)->mTarget = mTarget; bool success; success = (*front)->clipWindingToPlaneFront(planeIndex); AssertFatal(success, "Lighting::SVNode::splitNode - failed to generate front lexel poly"); success = (*back)->clipWindingToPlaneFront(gWorkingGeometry->getPlaneInverse(planeIndex)); AssertFatal(success, "Lighting::SVNode::splitNode - failed to generate back lexel poly"); } //------------------------------------------------------------------------------ void Lighting::SVNode::insertShadowVolume(SVNode ** root) { AssertFatal(root, "Lighting::SVNode::insertShadowVolume- invalid root"); AssertFatal(!*root, "Lighting::SVNode::insertShadowVolume- root non-null"); AssertFatal(mType == PolyPlane, "Lighting::SVNode::insertShadowVolume - invalid node type"); AssertFatal(mEmitterInfo, "Lighting::SVNode::insertShadowVolume - no emitter info"); SVNode ** current = root; SVNode * traverse = gWorkingLighting->getShadowVolume(mEmitterInfo->mShadowVolume); AssertFatal(traverse, "Lighting::Emitter::insertShadowVolume - bad shadow volume"); // walk the shadow planes and add at current root while(traverse) { SVNode * sNode = gWorkingLighting->createNode(ShadowPlane); sNode->mPlaneIndex = traverse->mPlaneIndex; *current = sNode; current = &(*current)->mFront; traverse = traverse->mFront; } // add the poly node *current = this; } //------------------------------------------------------------------------------ void Lighting::SVNode::insertFront(SVNode ** root) { AssertFatal(root, "Lighting::SVNode::insertFront - invalid root"); AssertFatal(mType == PolyPlane, "Lighting::SVNode::insertFront - invalid node type"); if(!(*root)->mFront) { AssertFatal((*root)->mType == PolyPlane, "Lighting::SVNode::insertFront - invalid node"); mTarget = *root; insert(&(*root)->mBack); } else insert(&(*root)->mFront); } //------------------------------------------------------------------------------ void Lighting::SVNode::insertBack(SVNode ** root) { AssertFatal(root, "Lighting::SVNode::insertBack - invalid root"); AssertFatal(mType == PolyPlane, "Lighting::SVNode::insertBack - invalid node type"); if((*root)->mType == PolyPlane) { mEmitterInfo->mShadowed.pushBackUnique((*root)->mEmitterInfo->mShadowVolume); gWorkingLighting->recycleNode(this); } else insert(&(*root)->mBack); } //------------------------------------------------------------------------------ void Lighting::SVNode::insert(SVNode ** root) { AssertFatal(root, "Lighting::SVNode::insert - invalid root"); AssertFatal(mType == PolyPlane, "Lighting::SVNode::insert - invalid node type"); if(!*root) { insertShadowVolume(root); if(mTarget) mTarget->mEmitterInfo->mShadowed.pushBackUnique(mEmitterInfo->mShadowVolume); } else { switch((*root)->whichSide(this)) { case Front: insertFront(root); break; case Back: insertBack(root); break; case Split: { SVNode * front = 0; SVNode * back = 0; split(&front, &back, (*root)->mPlaneIndex); AssertFatal(front && back, "Lighting::SVNode::insert - failed to split node"); gWorkingLighting->recycleNode(this); // push down both sides front->insertFront(root); back->insertBack(root); break; } default: // sliver poly encountered (just remove for now..) gWorkingLighting->mNumAmbiguousPlanes++; gWorkingLighting->recycleNode(this); //AssertFatal(false, "Lighting::SVNode::insert - invalid classification!"); break; } } } //------------------------------------------------------------------------------ // * this functino is used to clip a lexel node to it's own surface void Lighting::SVNode::clipToInverseVolume(SVNode ** store, SVNode * volume) { AssertFatal(store, "Lighting::SVNode::clipToInverseVolume: invalid store param"); AssertFatal(mType == LexelPlane, "Lighting::SVNode::clipToInverseVolume: node must be of lexelPlane"); if(!volume) { addToList(store); return; } AssertFatal(volume->mType == ShadowPlane, "Lighting::SVNode::clipToInverseVolume: invalid node in volume"); switch(volume->whichSide(this)) { case Front: clipToInverseVolume(store, volume->mFront); break; case Back: { mFront = mBack = 0; gWorkingLighting->recycleNode(this); break; } case Split: { SVNode * front = 0; SVNode * back = 0; split(&front, &back, volume->mPlaneIndex); AssertFatal(front && back, "Lighting::SVNode::clipToInverseVolume - invalid split!"); mFront = mBack = 0; gWorkingLighting->recycleNode(this); back->mFront = back->mBack = 0; gWorkingLighting->recycleNode(back); front->clipToInverseVolume(store, volume->mFront); break; } case On: { // sliver poly: mFront = mBack = 0; gWorkingLighting->recycleNode(this); break; } } } void Lighting::SVNode::clipToVolume(SVNode ** store, SVNode * volume) { AssertFatal(store, "Lighting::SVNode::clipToVolume - invalid store param"); AssertFatal(mType == LexelPlane, "Lighting::SVNode::clipToVolume - node must be a lexelPlane"); if(!volume) { mFront = mBack = 0; gWorkingLighting->recycleNode(this); return; } AssertFatal(volume->mType == ShadowPlane, "Lighting::SVNode::clipToVolume - volume must be shadow planes"); switch(volume->whichSide(this)) { case Back: addToList(store); break; case Front: clipToVolume(store, volume->mFront); break; case Split: { SVNode * front = 0; SVNode * back = 0; split(&front, &back, volume->mPlaneIndex); AssertFatal(front && back, "Lighting::SVNode::clipToVolume - invalid split!"); mFront = mBack = 0; gWorkingLighting->recycleNode(this); back->addToList(store); front->clipToVolume(store, volume->mFront); break; } case On: { // sliver poly mFront = mBack = 0; gWorkingLighting->recycleNode(this); break; } } } //------------------------------------------------------------------------------ Point3D Lighting::SVNode::getCenter() { AssertFatal(mWinding, "Lighting::SVNode::getCenter - no winding"); AssertFatal(mWinding->mNumIndices >= 3, "Lighting::SVNode::getCenter - invalid node"); Point3D pnt(0,0,0); for(U32 i = 0; i < mWinding->mNumIndices; i++) pnt += gWorkingLighting->getLexelPoint(mWinding->mIndices[i]); pnt /= mWinding->mNumIndices; return(pnt); } //------------------------------------------------------------------------------ void Lighting::SVNode::move(SVNode ** list) { AssertFatal(list, "Lighting::SVNode::moveNodes - invalid list param"); SVNode * node = this; while(node) { AssertFatal(node->mType == LexelPlane, "Lighting::SVNode::moveNodes - invalid node type"); SVNode * next = node->mFront; node->addToList(list); node = next; } } //------------------------------------------------------------------------------ F64 Lighting::SVNode::getWindingSurfaceArea() { Point3D areaNormal(0, 0, 0); for (U32 i = 0; i < mWinding->mNumIndices; i++) { U32 j = (i + 1) % mWinding->mNumIndices; Point3D temp; mCross(getPoint(mWinding->mIndices[i]), getPoint(mWinding->mIndices[j]), &temp); areaNormal += temp; } F64 area = mDot(gWorkingGeometry->getPlaneEQ(mPlaneIndex).normal, areaNormal); if (area < 0.0) area *= -0.5; else area *= 0.5; // if(mFront) area += mFront->getWindingSurfaceArea(); return(area); } //------------------------------------------------------------------------------ const Point3D & Lighting::SVNode::getPoint(U32 index) { if(mType == LexelPlane) return(gWorkingLighting->getLexelPoint(index)); return(gWorkingGeometry->getPoint(index)); } U32 Lighting::SVNode::insertPoint(const Point3D & pnt) { if(mType == LexelPlane) return(gWorkingLighting->insertLexelPoint(pnt)); else return(gWorkingGeometry->insertPoint(pnt)); } bool Lighting::SVNode::clipWindingToPlaneFront(U32 planeEQIndex) { const PlaneEQ& rClipPlane = gWorkingGeometry->getPlaneEQ(planeEQIndex); U32 start; for (start = 0; start < mWinding->mNumIndices; start++) { const Point3D& rPoint = getPoint(mWinding->mIndices[start]); if (rClipPlane.whichSide(rPoint) == PlaneFront) break; } if (start == mWinding->mNumIndices) { mWinding->mNumIndices = 0; return true; } U32 finalIndices[LightingMaxWindingPoints]; U32 numFinalIndices = 0; U32 baseStart = start; U32 end = (start + 1) % mWinding->mNumIndices; bool modified = false; while (end != baseStart) { const Point3D& rStartPoint = getPoint(mWinding->mIndices[start]); const Point3D& rEndPoint = getPoint(mWinding->mIndices[end]); PlaneSide fSide = rClipPlane.whichSide(rStartPoint); PlaneSide eSide = rClipPlane.whichSide(rEndPoint); S32 code = fSide * 3 + eSide; switch (code) { case 4: // f f case 3: // f o case 1: // o f case 0: // o o // No Clipping required finalIndices[numFinalIndices++] = mWinding->mIndices[start]; start = end; end = (end + 1) % mWinding->mNumIndices; break; case 2: { // f b // In this case, we emit the front point, Insert the intersection, // and advancing to point to first point that is in front or on... // finalIndices[numFinalIndices++] = mWinding->mIndices[start]; Point3D vector = rEndPoint - rStartPoint; F64 t = -(rClipPlane.distanceToPlane(rStartPoint) / mDot(rClipPlane.normal, vector)); Point3D intersection = rStartPoint + (vector * t); AssertFatal(rClipPlane.whichSide(intersection) == PlaneOn, "Lighting::SVNode::clipWindingToPlaneFront: error in computing intersection"); finalIndices[numFinalIndices++] = insertPoint(intersection); U32 endSeek = (end + 1) % mWinding->mNumIndices; while (rClipPlane.whichSide(getPoint(mWinding->mIndices[endSeek])) == PlaneBack) endSeek = (endSeek + 1) % mWinding->mNumIndices; PlaneSide esSide = rClipPlane.whichSide(getPoint(mWinding->mIndices[endSeek])); if(esSide == PlaneFront) { end = endSeek; start = (end + (mWinding->mNumIndices - 1)) % mWinding->mNumIndices; const Point3D& rNewStartPoint = getPoint(mWinding->mIndices[start]); const Point3D& rNewEndPoint = getPoint(mWinding->mIndices[end]); vector = rNewEndPoint - rNewStartPoint; t = -(rClipPlane.distanceToPlane(rNewStartPoint) / mDot(rClipPlane.normal, vector)); intersection = rNewStartPoint + (vector * t); AssertFatal(rClipPlane.whichSide(intersection) == PlaneOn, "Lighting::SVNode::clipWindingToPlaneFront: error in computing intersection"); mWinding->mIndices[start] = insertPoint(intersection); AssertFatal(mWinding->mIndices[start] != mWinding->mIndices[end], "Error"); } else { start = endSeek; end = (endSeek + 1) % mWinding->mNumIndices; } modified = true; } break; case -1: {// o b // In this case, we emit the front point, and advance to point to first // point that is in front or on... // finalIndices[numFinalIndices++] = mWinding->mIndices[start]; U32 endSeek = (end + 1) % mWinding->mNumIndices; while (rClipPlane.whichSide(getPoint(mWinding->mIndices[endSeek])) == PlaneBack) endSeek = (endSeek + 1) % mWinding->mNumIndices; PlaneSide esSide = rClipPlane.whichSide(getPoint(mWinding->mIndices[endSeek])); if(esSide == PlaneFront) { end = endSeek; start = (end + (mWinding->mNumIndices - 1)) % mWinding->mNumIndices; const Point3D& rNewStartPoint = getPoint(mWinding->mIndices[start]); const Point3D& rNewEndPoint = getPoint(mWinding->mIndices[end]); Point3D vector = rNewEndPoint - rNewStartPoint; F64 t = -(rClipPlane.distanceToPlane(rNewStartPoint) / mDot(rClipPlane.normal, vector)); Point3D intersection = rNewStartPoint + (vector * t); AssertFatal(rClipPlane.whichSide(intersection) == PlaneOn, "Lighting::SVNode::clipWindingToPlaneFront: error in computing intersection"); mWinding->mIndices[start] = insertPoint(intersection); AssertFatal(mWinding->mIndices[start] != mWinding->mIndices[end], "Error"); } else { start = endSeek; end = (endSeek + 1) % mWinding->mNumIndices; } modified = true; } break; case -2: // b f case -3: // b o case -4: // b b // In the algorithm used here, this should never happen... AssertISV(false, "Lighting::SVNode::clipWindingToPlaneFront: error in polygon clipper"); break; default: AssertFatal(false, "Lighting::SVNode::clipWindingToPlaneFront: bad outcode"); break; } } // Emit the last point. AssertFatal(rClipPlane.whichSide(getPoint(mWinding->mIndices[start])) != PlaneBack, "Lighting::SVNode::clipWindingToPlaneFront: bad final point in clipper"); finalIndices[numFinalIndices++] = mWinding->mIndices[start]; AssertFatal(numFinalIndices >= 3, "Error, line out of clip!"); // Copy the new rWinding, and we're set! // dMemcpy(mWinding->mIndices, finalIndices, numFinalIndices * sizeof(U32)); mWinding->mNumIndices = numFinalIndices; AssertISV(mWinding->mNumIndices <= LightingMaxWindingPoints, avar("Increase lightingMaxWindingPoints. Talk to JohnF(%d, %d)", LightingMaxWindingPoints, numFinalIndices)); #if defined(TORQUE_DEBUG) // valid point indices? for(U32 i = 0; i < mWinding->mNumIndices; i++) { U32 size = mType == LexelPlane ? gWorkingLighting->mLexelPoints.size() : gWorkingGeometry->mPoints.size(); AssertFatal(mWinding->mIndices[i] < size, "Lighting::SVNode::clipWindingToPlaneFront: invalid point index"); } #endif return(modified); } //------------------------------------------------------------------------------ // Class Lighting::Surface //------------------------------------------------------------------------------ Lighting::Surface * Lighting::createSurface() { Surface * surface = mSurfaceChunker.alloc(); // clear it out (will not call vector ctor's) dMemset(surface, 0, sizeof(Surface)); return(surface); } //------------------------------------------------------------------------------ Lighting::EmitterInfo * Lighting::Surface::getEmitterInfo(U32 emitter) { for(U32 i = 0; i < mNumEmitters; i++) if(mEmitters[i]->mEmitter == emitter) return(mEmitters[i]); AssertFatal(false, "Lighting::Surface::getEmitterInfo - failed to get emitter info"); return NULL; } //------------------------------------------------------------------------------ // Class Lighting::Emitter //------------------------------------------------------------------------------ Lighting::Emitter::Emitter() { mIndex = 0; mNumSurfaces = 0; mSurfaces = 0; mAnimated = false; } void Lighting::Emitter::processBSP() { SVNode * root = 0; // the polynode gets trashed, so insert a copy for(U32 i = 0; i < mNumSurfaces; i++) { Surface * surface = gWorkingLighting->getSurface(mSurfaces[i]); AssertFatal(surface && surface->mPolyNode, "Lighting::Emitter::processBSP - invalid surface"); SVNode * pNode = gWorkingLighting->createNode(Lighting::SVNode::PolyPlane); *pNode->mWinding = *surface->mPolyNode->mWinding; pNode->mPlaneIndex = surface->mPolyNode->mPlaneIndex; pNode->mEmitterInfo = surface->getEmitterInfo(mIndex); pNode->insert(&root); } gWorkingLighting->recycleNode(root); } //------------------------------------------------------------------------------ // Class Lighting::PointEmitter //------------------------------------------------------------------------------ bool Lighting::PointEmitter::isSurfaceLit(const Surface * surface) { AssertFatal(surface, "Lighting::PointEmitter::isSurfaceLit - NULL surface"); const PlaneEQ & plane = gWorkingGeometry->getPlaneEQ(surface->mPlaneIndex); if(plane.whichSide(mPos) != PlaneFront) return(false); // can light something on the surface? F64 distance = plane.distanceToPlane(mPos); if(distance > mFalloffs[1]) return(false); // check if the point is inside the poly.. Point3D pnt = mPos - (plane.normal * distance); const MiniWinding & winding = surface->mWinding; // inside this surface? bool inside = true; for(U32 i = 0; inside && (i < winding.mNumIndices); i++) { U32 j = (i+1) % winding.mNumIndices; Point3D vec1 = gWorkingGeometry->getPoint(winding.mIndices[j]) - gWorkingGeometry->getPoint(winding.mIndices[i]); Point3D vec2 = pnt - gWorkingGeometry->getPoint(winding.mIndices[i]); Point3D cross; mCross(vec1, vec2, &cross); if(mDot(plane.normal, cross) > 0.f) inside = false; } if(inside) return(true); // check if any of the line segments intersect the light sphere for(U32 i = 0; i < winding.mNumIndices; i++) { // substitute the line equation into the sphere and // solve the quadratic U32 j = (i+1) % winding.mNumIndices; Point3D p1 = gWorkingGeometry->getPoint(winding.mIndices[i]); Point3D p2 = gWorkingGeometry->getPoint(winding.mIndices[j]); Point3D & p3 = mPos; F64 r = mFalloffs[1]; // get the quadratic terms F64 a = (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y) + (p2.z - p1.z) * (p2.z - p1.z); F64 b = 2 * ((p2.x - p1.x) * (p1.x - p3.x) + (p2.y - p1.y) * (p1.y - p3.y) + (p2.z - p1.z) * (p1.z - p3.z)); F64 c = p3.x * p3.x + p3.y * p3.y + p3.z * p3.z + p1.x * p1.x + p1.y * p1.y + p1.z * p1.z - 2 * (p3.x * p1.x + p3.y * p1.y + p3.z * p1.z) - r * r; // if the expression within the square root is: // <0: the line does not intersect the sphere // 0: the line is tangential // >0: intersects at two points F64 d = (b * b - 4 * a * c); if(d > 0.f) { // get the times of intersection... // if either time in 0>1 then intersected, otherwise // if there is a t<0 && t>1 then contained inside the sphere d = mSqrtD(d); F64 t1 = (-b + d) / (2 * a); if(t1 > 0.f && t1 < 1.f) return(true); F64 t2 = (-b - d) / (2 * a); if(t2 > 0.f && t2 < 1.f) return(true); // contained? if(t1 * t2 < 0.f) return(true); } } return(false); } //------------------------------------------------------------------------------ F64 Lighting::PointEmitter::calcIntensity(SVNode * lSurface) { AssertFatal(lSurface, "Lighting::PointEmitter::calcIntensity - NULL surface"); AssertFatal(lSurface->mType == SVNode::LexelPlane, "Lighting::PointEmitter::calcIntensity - invalid node passed"); Point3D center = lSurface->getCenter(); F64 len = Point3D(mPos - center).len(); if(len > mFalloffs[1]) return(0.f); if(len > mFalloffs[0]) return(1.f - (len - F64(mFalloffs[0])) / F64((mFalloffs[1]-mFalloffs[0]))); return(1); } //------------------------------------------------------------------------------ // Class Lighting::SpotEmitter //------------------------------------------------------------------------------ bool Lighting::SpotEmitter::isSurfaceLit(const Surface * surface) { AssertFatal(surface, "Lighting::SpotEmitter::isSurfaceLit - NULL surface"); const PlaneEQ & plane = gWorkingGeometry->getPlaneEQ(surface->mPlaneIndex); if(plane.whichSide(mPos) != PlaneFront) return(false); // can light something on the surface? F64 distance = plane.distanceToPlane(mPos); if(distance > mFalloffs[1]) return(false); // check if the point is inside the poly.. Point3D pnt = mPos - (plane.normal * distance); const MiniWinding & winding = surface->mWinding; // inside this surface? bool inside = true; for(U32 i = 0; inside && (i < winding.mNumIndices); i++) { U32 j = (i+1) % winding.mNumIndices; Point3D vec1 = gWorkingGeometry->getPoint(winding.mIndices[j]) - gWorkingGeometry->getPoint(winding.mIndices[i]); Point3D vec2 = pnt - gWorkingGeometry->getPoint(winding.mIndices[i]); Point3D cross; mCross(vec1, vec2, &cross); if(mDot(plane.normal, cross) > 0.f) inside = false; } if(inside) return(true); // check if any of the line segments intersect the light sphere for(U32 i = 0; i < winding.mNumIndices; i++) { // substitute the line equation into the sphere and // solve the quadratic U32 j = (i+1) % winding.mNumIndices; Point3D p1 = gWorkingGeometry->getPoint(winding.mIndices[i]); Point3D p2 = gWorkingGeometry->getPoint(winding.mIndices[j]); Point3D & p3 = mPos; F64 r = mFalloffs[1]; // get the quadratic terms F64 a = (p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y) + (p2.z - p1.z) * (p2.z - p1.z); F64 b = 2 * ((p2.x - p1.x) * (p1.x - p3.x) + (p2.y - p1.y) * (p1.y - p3.y) + (p2.z - p1.z) * (p1.z - p3.z)); F64 c = p3.x * p3.x + p3.y * p3.y + p3.z * p3.z + p1.x * p1.x + p1.y * p1.y + p1.z * p1.z - 2 * (p3.x * p1.x + p3.y * p1.y + p3.z * p1.z) - r * r; // if the expression within the square root is: // <0: the line does not intersect the sphere // 0: the line is tangential // >0: intersects at two points F64 d = (b * b - 4 * a * c); if(d > 0.f) { // get the times of intersection... // if either time in 0>1 then intersected, otherwise // if there is a t<0 && t>1 then contained inside the sphere d = mSqrtD(d); F64 t1 = (-b + d) / (2 * a); if(t1 > 0.f && t1 < 1.f) return(true); F64 t2 = (-b - d) / (2 * a); if(t2 > 0.f && t2 < 1.f) return(true); // contained? if(t1 * t2 < 0.f) return(true); } } return(false); } //------------------------------------------------------------------------------ F64 Lighting::SpotEmitter::calcIntensity(SVNode * lSurface) { AssertFatal(lSurface, "Lighting::SpotEmitter::calcIntensity - NULL surface"); AssertFatal(lSurface->mType == SVNode::LexelPlane, "Lighting::SpotEmitter::calcIntensity - invalid node passed"); Point3D center = lSurface->getCenter(); F64 len = Point3D(center - mPos).len(); if(len > mFalloffs[1]) return(0.f); // F64 intensity = (len > mFalloffs[0]) ? 1 - ((len - F64(mFalloffs[0])) / F64((mFalloffs[1]-mFalloffs[0]))) : 1; Point3D vec = Point3D(center - mPos); vec.normalize(); F64 angle = mAcos(mDot(mDirection, vec)); if(angle <= mAngles[0]) return(intensity); if(angle > mAngles[1]) return(0.f); return(intensity * (1 - ((angle - mAngles[0]) / (mAngles[1] - mAngles[0])))); } //------------------------------------------------------------------------------ // Class Lighting::Light //------------------------------------------------------------------------------ Lighting::Light::Light() { mNumStates = 0; mStateIndex = 0; mAnimated = false; mAnimType = 0; mName = 0; } Lighting::Light::~Light() { if(!mAnimated) delete [] mName; mName = NULL; } //------------------------------------------------------------------------------ bool Lighting::Light::getTargetPosition(const char * name, Point3D & pos) { TargetEntity * target = 0; for(U32 i = 0; !target && i < gWorkingLighting->mTargets.size(); i++) if(!dStricmp(gWorkingLighting->mTargets[i]->mTargetName, name)) target = gWorkingLighting->mTargets[i]; // if(!target) { dPrintf(" ! Target entity not found: '%s'", name); dFflushStdout(); return(false); } pos = target->mOrigin; return(true); } //------------------------------------------------------------------------------ bool Lighting::Light::buildLight(BaseLightEntity * entity) { AssertFatal(entity, "Lighting::Light::buildLight - NULL entity"); LightEntity * light = static_cast(entity); AssertFatal(light->mLightName[0], "Lighting::Light::buildLight - unnamed light encountered."); AssertFatal(light->mNumStates, avar("Lighting::Light::buildLight - light '%s' has no states.", light->mLightName)); mName = newStrDup(light->mLightName); mNumStates = light->mNumStates; mStateIndex = gWorkingLighting->mLightStates.size(); // assume the light to be animated if there is more than one state if(mNumStates > 1) { // animated stuff... mAnimType = light->mFlags; mAnimated = true; } // grab the emitters for this light Vector myEmitters; for(U32 i = 0; i < gWorkingLighting->mBaseLightEmitters.size(); i++) if(gWorkingLighting->mBaseLightEmitters[i]->mTargetLight[0] && !dStricmp(gWorkingLighting->mBaseLightEmitters[i]->mTargetLight, mName)) myEmitters.push_back(gWorkingLighting->mBaseLightEmitters[i]); // process the states.... for(U32 i = 0; i < mNumStates; i++) { LightState state; state.mNumEmitters = 0; state.mEmitterIndex = gWorkingLighting->mLightStateEmitterStore.size(); state.mDuration = light->mStates[i].mDuration; // grab them emitters for(U32 j = 0; j < myEmitters.size(); j++) { if(myEmitters[j]->mStateIndex != i) continue; // process the entity if(dynamic_cast(myEmitters[j])) { PointEmitterEntity * pointEntity = (PointEmitterEntity*)myEmitters[j]; // add a point emitter PointEmitter * emitter = new PointEmitter; emitter->mPos = light->mOrigin; emitter->mColor = light->mStates[i].mColor; emitter->mPoint = gWorkingGeometry->insertPoint(pointEntity->mOrigin); emitter->mIndex = state.mEmitterIndex + state.mNumEmitters++; emitter->mAnimated = mAnimated; emitter->mFalloffs[0] = pointEntity->mFalloff1; emitter->mFalloffs[1] = pointEntity->mFalloff2; state.mNumEmitters++; } else if(dynamic_cast(myEmitters[j])) { SpotEmitterEntity * spotEntity = (SpotEmitterEntity*)myEmitters[j]; // add the spot emitter SpotEmitter * emitter = new SpotEmitter; emitter->mPos = light->mOrigin; emitter->mColor = light->mStates[i].mColor; emitter->mPoint = gWorkingGeometry->insertPoint(spotEntity->mOrigin); emitter->mIndex = state.mEmitterIndex + state.mNumEmitters++; emitter->mAnimated = mAnimated; emitter->mFalloffs[0] = spotEntity->mFalloff1; emitter->mFalloffs[1] = spotEntity->mFalloff2; emitter->mDirection = spotEntity->mDirection; emitter->mAngles[0] = spotEntity->mInnerAngle; emitter->mAngles[1] = spotEntity->mOuterAngle; state.mNumEmitters++; } else AssertFatal(false, "Lighting::Light::buildLight - invalid entity encountered."); } gWorkingLighting->mLightStates.push_back(state); } return(true); } //------------------------------------------------------------------------------ // All the scripted lights: //------------------------------------------------------------------------------ bool Lighting::Light::buildOmniLight(BaseLightEntity * entity) { AssertFatal(entity, "Lighting::Light::buildOmniLight - NULL entity"); LightOmniEntity * omniLight = static_cast(entity); AssertFatal(omniLight, "Lighting::Light::buildOmniLight - invalid entity"); // fill the light, state and then the emittter info mNumStates = 1; mStateIndex = gWorkingLighting->mLightStates.size(); mName = newStrDup(omniLight->mLightName); Light::LightState state; state.mNumEmitters = 1; state.mEmitterIndex = gWorkingLighting->mEmitters.size(); gWorkingLighting->mLightStateEmitterStore.push_back(state.mEmitterIndex); PointEmitter * emitter = new PointEmitter; emitter->mPos = omniLight->mOrigin; emitter->mColor = omniLight->mColor; emitter->mPoint = gWorkingGeometry->insertPoint(emitter->mPos); emitter->mIndex = state.mEmitterIndex; emitter->mFalloffs[0] = omniLight->mFalloff1; emitter->mFalloffs[1] = omniLight->mFalloff2; gWorkingLighting->mEmitters.push_back(emitter); gWorkingLighting->mLightStates.push_back(state); return(true); } //------------------------------------------------------------------------------ bool Lighting::Light::buildSpotLight(BaseLightEntity * entity) { AssertFatal(entity, "Lighting::Light::buildSpotLight - NULL entity"); LightSpotEntity * spotLight = static_cast(entity); AssertFatal(spotLight, "Lighting::Light::buildSpotLight - invalid entity"); // Point3D target; if(!getTargetPosition(spotLight->mTarget, target)) return(false); // fill the light, state and then the emittter info mNumStates = 1; mStateIndex = gWorkingLighting->mLightStates.size(); mName = newStrDup(spotLight->mLightName); Light::LightState state; state.mNumEmitters = 1; state.mDuration = 0.f; state.mEmitterIndex = gWorkingLighting->mEmitters.size(); gWorkingLighting->mLightStateEmitterStore.push_back(state.mEmitterIndex); SpotEmitter * emitter = new SpotEmitter; emitter->mPos = spotLight->mOrigin; emitter->mColor = spotLight->mColor; // calculate the direction and angles... emitter->mDirection = target - spotLight->mOrigin; F32 len = emitter->mDirection.len(); // emitter->mDirection.normalize(); emitter->mAngles[0] = mAtan(spotLight->mInnerDistance, len); emitter->mAngles[1] = mAtan(spotLight->mOuterDistance, len); emitter->mPoint = gWorkingGeometry->insertPoint(emitter->mPos); emitter->mIndex = state.mEmitterIndex; emitter->mFalloffs[0] = spotLight->mFalloff1; emitter->mFalloffs[1] = spotLight->mFalloff2; gWorkingLighting->mEmitters.push_back(emitter); gWorkingLighting->mLightStates.push_back(state); return(true); } // Animated script lights: ----------------------------------------------------- bool Lighting::Light::buildStrobeLight(BaseLightEntity * entity) { AssertFatal(entity, "Lighting::Light::buildStrobeLight - NULL entity"); LightStrobeEntity * strobeLight = static_cast(entity); AssertFatal(strobeLight, "Lighting::Light::buildStrobeLight - invalid entity"); if(strobeLight->mSpeed > LightStrobeEntity::VeryFast) { dPrintf(" ! Invalid speed for strobe light: %s", strobeLight->mLightName); return(false); } // total of 4 states mNumStates = 4; mStateIndex = gWorkingLighting->mLightStates.size(); mAnimated = true; mAnimType = (strobeLight->mFlags & ~Interior::AnimationFlicker) | Interior::AnimationLoop; mName = newStrDup(strobeLight->mLightName); for(U32 i = 0; i < 4; i++) { // create this state Light::LightState state; state.mNumEmitters = 1; state.mEmitterIndex = gWorkingLighting->mEmitters.size(); gWorkingLighting->mLightStateEmitterStore.push_back(state.mEmitterIndex); // create the emitter PointEmitter * emitter = new PointEmitter; emitter->mPos = strobeLight->mOrigin; emitter->mPoint = gWorkingGeometry->insertPoint(emitter->mPos); emitter->mIndex = state.mEmitterIndex; emitter->mFalloffs[0] = strobeLight->mFalloff1; emitter->mFalloffs[1] = strobeLight->mFalloff2; emitter->mAnimated = true; state.mDuration = (i & 0x01) ? 0.f : AnimationTimes[strobeLight->mSpeed]; emitter->mColor = (i & 0x02) ? strobeLight->mColor2 : strobeLight->mColor1; gWorkingLighting->mEmitters.push_back(emitter); gWorkingLighting->mLightStates.push_back(state); } return(true); } //------------------------------------------------------------------------------ bool Lighting::Light::buildPulseLight(BaseLightEntity * entity) { AssertFatal(entity, "Lighting::Light::buildPulseLight - NULL entity"); LightPulseEntity * pulseLight = static_cast(entity); AssertFatal(pulseLight, "Lighting::Light::buildPulseLight - invalid entity"); if(pulseLight->mSpeed > LightPulseEntity::VeryFast) { dPrintf(" ! Invalid speed for pulse light: %s", pulseLight->mLightName); return(false); } // total of 2 states mNumStates = 2; mStateIndex = gWorkingLighting->mLightStates.size(); mAnimated = true; mAnimType = (pulseLight->mFlags & ~Interior::AnimationFlicker) | Interior::AnimationLoop; mName = newStrDup(pulseLight->mLightName); for(U32 i = 0; i < 2; i++) { // create this state Light::LightState state; state.mNumEmitters = 1; state.mEmitterIndex = gWorkingLighting->mEmitters.size(); gWorkingLighting->mLightStateEmitterStore.push_back(state.mEmitterIndex); // create the emitter PointEmitter * emitter = new PointEmitter; emitter->mPos = pulseLight->mOrigin; emitter->mPoint = gWorkingGeometry->insertPoint(emitter->mPos); emitter->mIndex = state.mEmitterIndex; emitter->mFalloffs[0] = pulseLight->mFalloff1; emitter->mFalloffs[1] = pulseLight->mFalloff2; emitter->mAnimated = true; // color and duration state.mDuration = AnimationTimes[pulseLight->mSpeed]; emitter->mColor = i ? pulseLight->mColor2 : pulseLight->mColor1; gWorkingLighting->mEmitters.push_back(emitter); gWorkingLighting->mLightStates.push_back(state); } return(true); } //------------------------------------------------------------------------------ bool Lighting::Light::buildPulse2Light(BaseLightEntity * entity) { AssertFatal(entity, "Lighting::Light::buildPulse2Light - NULL entity"); LightPulse2Entity * pulseLight = static_cast(entity); AssertFatal(pulseLight, "Lighting::Light::buildPulse2Light - invalid entity"); // total of 4 states mNumStates = 4; mStateIndex = gWorkingLighting->mLightStates.size(); mAnimated = true; mAnimType = (pulseLight->mFlags & ~Interior::AnimationFlicker) | Interior::AnimationLoop; mName = newStrDup(pulseLight->mLightName); F32 durations[] = { pulseLight->mAttack, pulseLight->mSustain1, pulseLight->mDecay, pulseLight->mSustain2 }; for(U32 i = 0; i < 4; i++) { // create this state Light::LightState state; state.mNumEmitters = 1; state.mEmitterIndex = gWorkingLighting->mEmitters.size(); gWorkingLighting->mLightStateEmitterStore.push_back(state.mEmitterIndex); // create the emitter PointEmitter * emitter = new PointEmitter; emitter->mPos = pulseLight->mOrigin; emitter->mPoint = gWorkingGeometry->insertPoint(emitter->mPos); emitter->mIndex = state.mEmitterIndex; emitter->mFalloffs[0] = pulseLight->mFalloff1; emitter->mFalloffs[1] = pulseLight->mFalloff2; emitter->mAnimated = true; // color and duration state.mDuration = durations[i]; emitter->mColor = (i == 0 || i == 3) ? pulseLight->mColor1 : pulseLight->mColor2; gWorkingLighting->mEmitters.push_back(emitter); gWorkingLighting->mLightStates.push_back(state); } return(true); } //------------------------------------------------------------------------------ bool Lighting::Light::buildFlickerLight(BaseLightEntity * entity) { AssertFatal(entity, "Lighting::Light::buildFlickerLight - NULL entity"); LightFlickerEntity * flickerLight = static_cast(entity); AssertFatal(flickerLight, "Lighting::Light::buildFlickerLight - invalid entity"); if(flickerLight->mSpeed > LightFlickerEntity::VeryFast) { dPrintf(" ! Invalid speed for flicker light: %s", flickerLight->mLightName); return(false); } // total of 5 states mNumStates = 5; mStateIndex = gWorkingLighting->mLightStates.size(); mAnimated = true; mAnimType = (flickerLight->mFlags & ~Interior::AnimationLoop) | Interior::AnimationFlicker; mName = newStrDup(flickerLight->mLightName); ColorF colors [] = { flickerLight->mColor1, flickerLight->mColor2, flickerLight->mColor3, flickerLight->mColor4, flickerLight->mColor5}; for(U32 i = 0; i < 5; i++) { // create this state Light::LightState state; state.mNumEmitters = 1; state.mEmitterIndex = gWorkingLighting->mEmitters.size(); gWorkingLighting->mLightStateEmitterStore.push_back(state.mEmitterIndex); // create the emitter PointEmitter * emitter = new PointEmitter; emitter->mPos = flickerLight->mOrigin; emitter->mPoint = gWorkingGeometry->insertPoint(emitter->mPos); emitter->mIndex = state.mEmitterIndex; emitter->mFalloffs[0] = flickerLight->mFalloff1; emitter->mFalloffs[1] = flickerLight->mFalloff2; emitter->mAnimated = true; // color and duration state.mDuration = AnimationTimes[flickerLight->mSpeed]; emitter->mColor = colors[i]; gWorkingLighting->mEmitters.push_back(emitter); gWorkingLighting->mLightStates.push_back(state); } return(true); } //------------------------------------------------------------------------------ bool Lighting::Light::buildRunwayLight(BaseLightEntity * entity) { AssertFatal(entity, "Lighting::Light::buildRunwayLight - NULL entity"); LightRunwayEntity * runwayLight = static_cast(entity); AssertFatal(runwayLight, "Lighting::Light::buildRunwayLight - invalid entity"); if(runwayLight->mSpeed > LightRunwayEntity::VeryFast) { dPrintf(" ! Invalid speed for runway light: %s", runwayLight->mLightName); return(false); } Point3D endPos; if(!getTargetPosition(runwayLight->mEndTarget, endPos)) return(false); // mNumStates = 0; mStateIndex = gWorkingLighting->mLightStates.size(); mAnimated = true; mAnimType = (runwayLight->mFlags & ~Interior::AnimationFlicker) | Interior::AnimationLoop; mName = newStrDup(runwayLight->mLightName); Point3D step = (endPos - runwayLight->mOrigin) / (runwayLight->mSteps + 1); Point3D lightPos = runwayLight->mOrigin; for(U32 i = 0; i < (runwayLight->mSteps+2); i++) { // create this state Light::LightState state; state.mNumEmitters = 1; state.mEmitterIndex = gWorkingLighting->mEmitters.size(); state.mDuration = AnimationTimes[runwayLight->mSpeed]; gWorkingLighting->mLightStateEmitterStore.push_back(state.mEmitterIndex); // create the emitter PointEmitter * emitter = new PointEmitter; emitter->mIndex = state.mEmitterIndex; emitter->mFalloffs[0] = runwayLight->mFalloff1; emitter->mFalloffs[1] = runwayLight->mFalloff2; emitter->mAnimated = true; emitter->mColor = runwayLight->mColor; // figure out the position... emitter->mPos = lightPos; emitter->mPoint = gWorkingGeometry->insertPoint(emitter->mPos); lightPos += step; mNumStates++; gWorkingLighting->mEmitters.push_back(emitter); gWorkingLighting->mLightStates.push_back(state); } // pingpong??? if(runwayLight->mPingPong) { lightPos -= step; for(U32 i = 0; i < runwayLight->mSteps; i++) { // create this state Light::LightState state; state.mNumEmitters = 1; state.mEmitterIndex = gWorkingLighting->mEmitters.size(); state.mDuration = AnimationTimes[runwayLight->mSpeed]; gWorkingLighting->mLightStateEmitterStore.push_back(state.mEmitterIndex); // create the emitter PointEmitter * emitter = new PointEmitter; emitter->mIndex = state.mEmitterIndex; emitter->mFalloffs[0] = runwayLight->mFalloff1; emitter->mFalloffs[1] = runwayLight->mFalloff2; emitter->mAnimated = true; emitter->mColor = runwayLight->mColor; // figure out the position... lightPos -= step; emitter->mPos = lightPos; emitter->mPoint = gWorkingGeometry->insertPoint(emitter->mPos); mNumStates++; gWorkingLighting->mEmitters.push_back(emitter); gWorkingLighting->mLightStates.push_back(state); } } return(true); } //------------------------------------------------------------------------------ bool Lighting::Light::build(BaseLightEntity * entity) { AssertFatal(entity, "Lighting::Light::build - NULL entity"); // build the light here.... if(dynamic_cast(entity)) return(buildLight(entity)); // scripted lights... if(dynamic_cast(entity)) return(buildOmniLight(entity)); if(dynamic_cast(entity)) return(buildSpotLight(entity)); // animated lights... if(dynamic_cast(entity)) return(buildStrobeLight(entity)); if(dynamic_cast(entity)) return(buildPulseLight(entity)); if(dynamic_cast(entity)) return(buildPulse2Light(entity)); if(dynamic_cast(entity)) return(buildFlickerLight(entity)); if(dynamic_cast(entity)) return(buildRunwayLight(entity)); // unsupported light type return(false); } //------------------------------------------------------------------------------ // Class Lighting::Light::LightState //------------------------------------------------------------------------------ void Lighting::Light::LightState::fillStateData(EditGeometry::LightState * state) { AssertFatal(state, "Lighting::Light::LightState::fillStateData - invalid state"); AssertFatal(mNumEmitters, "Lighting::Light::LightState::fillStateData - no emitters in state"); // all emitters are same color.. just grab from first Emitter * emitter = gWorkingLighting->mEmitters[mEmitterIndex]; state->color.set(U8(emitter->mColor.red * 255.0f), U8(emitter->mColor.green * 255.0f), U8(emitter->mColor.blue * 255.0f)); // state->duration = mDuration; // merge the lightmaps? for(U32 i = 0; i < mNumEmitters; i++) { emitter = gWorkingLighting->mEmitters[mEmitterIndex + i]; for(U32 k = 0; k < emitter->mNumSurfaces; k++) { Surface * surface = gWorkingLighting->mSurfaces[emitter->mSurfaces[k]]; EmitterInfo * eInfo = surface->getEmitterInfo(mEmitterIndex + i); AssertFatal(eInfo->mLightMap, "Lighting::Light::LightState::fillStateData - null Lightmap"); EditGeometry::StateData stateData; stateData.pLMap = eInfo->mLightMap; #ifdef DUMP_LIGHTMAPS // if(stateData.pLMap) { // convert the intensity bitmap to RGB GBitmap bitmap(stateData.pLMap->getWidth(), stateData.pLMap->getHeight()); for(U32 y = 0; y < stateData.pLMap->getHeight(); y++) for(U32 x = 0; x < stateData.pLMap->getWidth(); x++) { U8 * pDest = bitmap.getAddress(x, y); U8 src = *stateData.pLMap->getAddress(x, y); *pDest++ = src; *pDest++ = src; *pDest++ = src; } // FileStream output; output.open(avar("anim_%x_%d_%d.png", state, i, k), FileStream::Write); bitmap.writePNG(output); } #endif stateData.surfaceIndex = surface->mSurfaceIndex; state->stateData.push_back(stateData); } } } //------------------------------------------------------------------------------ void Lighting::processAnimatedLights() { // walk the animated lights... for(U32 i = 0; i < mLights.size(); i++) { Light * light = mLights[i]; if(!light->mAnimated) continue; EditGeometry::AnimatedLight * animLight = new EditGeometry::AnimatedLight; // fill in state info for(U32 j = 0; j < light->mNumStates; j++) { EditGeometry::LightState * destLightState = new EditGeometry::LightState; Light::LightState srcLightState = mLightStates[light->mStateIndex + j]; // should we be merging the lightmaps here? srcLightState.fillStateData(destLightState); animLight->states.push_back(destLightState); } // Copy the properties animLight->name = light->mName; animLight->type = light->mAnimType; animLight->alarm = light->alarm; gWorkingGeometry->mAnimatedLights.push_back(animLight); } } //------------------------------------------------------------------------------ // Class EditGeometry //------------------------------------------------------------------------------ void EditGeometry::computeLightmaps(const bool alarmMode) { // clear the current surfaces - low detail just white's out the lightmaps ColorF ambientF = alarmMode ? mWorldEntity->mEmergencyAmbientColor : mWorldEntity->mAmbientColor; ColorI ambientI(ambientF.red * 255.f, ambientF.green * 255.f, ambientF.blue * 255.f); for(U32 i = 0; i < mSurfaces.size(); i++) { Surface & surface = mSurfaces[i]; if(!alarmMode) fillInLightmapInfo(surface); surface.pLMap = alarmMode ? surface.pAlarmLMap : surface.pNormalLMap; // if(surface.pLMap) { if(gBuildAsLowDetail) { for(U32 y = 0; y < surface.pLMap->getHeight(); y++) dMemset(surface.pLMap->getAddress(0, y), 0xff, (surface.pLMap->getWidth() * surface.pLMap->bytesPerPixel)); } else if(!(surface.flags & Interior::SurfaceOutsideVisible)) { // fill with ambient for(U32 y = 0; y < surface.pLMap->getHeight(); y++) for(U32 x = 0; x < surface.pLMap->getWidth(); x++) { U8 * pPixel = surface.pLMap->getAddress(x,y); *(pPixel++) = ambientI.red; *(pPixel++) = ambientI.green; *(pPixel) = ambientI.blue; } } else // clear out the outside lightmaps { for(U32 y = 0; y < surface.pLMap->getHeight(); y++) dMemset(surface.pLMap->getAddress(0, y), 0, (surface.pLMap->getWidth() * surface.pLMap->bytesPerPixel)); } sgIlluminateSurface(surface); } } if(gBuildAsLowDetail) return; Lighting * lighting = new Lighting; gWorkingLighting = lighting; gWorkingLighting->mAmbientColor = ambientF; // lighting->grabLights(alarmMode); // if(alarmMode == true) { if((lighting->mLights.size() != 0)) mHasAlarmState = true; else if((mWorldEntity->mEmergencyAmbientColor != ColorF(0,0,0)) && (mWorldEntity->mEmergencyAmbientColor != mWorldEntity->mAmbientColor)) { delete lighting; mHasAlarmState = true; return; } else { delete lighting; mHasAlarmState = false; return; } } lighting->grabSurfaces(); lighting->processSurfaces(); lighting->createShadowVolumes(); lighting->processEmitterBSPs(); lighting->lightSurfaces(); lighting->processAnimatedLights(); if(lighting->mNumAmbiguousPlanes) dPrintf("\n * Number of ambiguous planes (dropped): %d\n", lighting->mNumAmbiguousPlanes); // delete lighting; }