//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "terrain/terrData.h" #include "math/mMath.h" #include "dgl/dgl.h" #include "console/console.h" #include "console/consoleTypes.h" #include "dgl/gBitmap.h" #include "terrain/terrRender.h" #include "dgl/materialList.h" #include "sceneGraph/sceneState.h" #include "terrain/waterBlock.h" #include "terrain/blender.h" #include "core/frameAllocator.h" #include "sceneGraph/sceneGraph.h" #include "sceneGraph/sgUtil.h" #include "platform/profiler.h" inline F32 custom_dot(Point4F &a, Point3F &b) { return (a.x * b.x) + (a.y * b.y) + (a.z * b.z) + a.w; } struct LightTriangle { ColorF color; Point2F texco1; Point3F point1; Point2F texco2; Point3F point2; Point2F texco3; Point3F point3; LightTriangle* next; U32 flags; // 0 if inactive AllocatedTexture *chunkTexture; }; static LightTriangle* sgCurrLightTris = NULL; GBitmap* TerrainRender::mBlendBitmap = NULL; S32 TerrainRender::mTextureMinSquareSize; bool TerrainRender::mEnableTerrainDetails = true; // CW - stuff with bump maps // So, this is how it works... // There are 3 fields: // 1) bumpScale: This is the size of the bumps. It determines how much the bump texture is stretched // 2) bumpOffset: How much the two textures are offset from each other. This should be VERY close to // 0. An appropriate value is usually around 0.004 and 0.01. Play around with it and see what // works best for your implementation. // 3) zeroBumpScale: This is a bitshift value for how far the bumps are drawn. The LESS the value // the FARTHER they will exist. If you go above 7-8, the bumps tend to go a little wacky. // 8 should be the max. 6-7 are good values. bool TerrainRender::mEnableTerrainEmbossBumps = true; // This is only for win32 #ifdef TORQUE_OS_WIN32 bool TerrainRender::mRenderGL = false; #endif // CW - end bump map stuff bool TerrainRender::mEnableTerrainDynLights = true; U32 TerrainRender::mNumClipPlanes = 4; MatrixF TerrainRender::mCameraToObject; AllocatedTexture TerrainRender::mTextureFrameListHead; AllocatedTexture TerrainRender::mTextureFrameListTail; AllocatedTexture TerrainRender::mTextureFreeListHead; AllocatedTexture TerrainRender::mTextureFreeListTail; AllocatedTexture TerrainRender::mTextureFreeBigListHead; AllocatedTexture TerrainRender::mTextureFreeBigListTail; AllocatedTexture *TerrainRender::mTextureGrid[AllocatedTextureCount]; AllocatedTexture **TerrainRender::mTextureGridPtr[5]; AllocatedTexture *TerrainRender::mCurrentTexture = NULL; U32 TerrainRender::mTextureSlopSize = 512; static bool sgTextureFreeListPrimed = false; static U32 sgFreeListPrimeCount = 32; Vector TerrainRender::mTextureFreeList(__FILE__, __LINE__); SceneState* TerrainRender::mSceneState; TerrainBlock* TerrainRender::mCurrentBlock; S32 TerrainRender::mSquareSize; F32 TerrainRender::mScreenSize; U32 TerrainRender::mFrameIndex; Point2F TerrainRender::mBlockPos; Point2I TerrainRender::mBlockOffset; Point2I TerrainRender::mTerrainOffset; PlaneF TerrainRender::mClipPlane[MaxClipPlanes]; Point3F TerrainRender::mCamPos; TextureHandle* TerrainRender::mGrainyTexture = NULL; U32 TerrainRender::mDynamicLightCount; F32 TerrainRender::mPixelError; TerrLightInfo TerrainRender::mTerrainLights[MaxTerrainLights]; bool TerrainRender::mRenderingCommander = false; F32 TerrainRender::mScreenError; F32 TerrainRender::mMinSquareSize; F32 TerrainRender::mFarDistance; S32 TerrainRender::mDynamicTextureCount; S32 TerrainRender::mStaticTextureCount; const U32 maxTerrPoints = 100; ColorF TerrainRender::mFogColor; bool TerrainRender::mRenderOutline; U32 TerrainRender::mMaterialCount; namespace { Point4F sgTexGenS; Point4F sgTexGenT; Point4F sgLMGenS; Point4F sgLMGenT; Point4F bumpTexGenS; Point4F bumpTexGenT; Point2F bumpTextureOffset; } // namespace {} static S32 getPower(S32 x) { // Returns 2^n (the highest bit). S32 i = 0; if (x) do i++; while (x >>= 1); return i; } void TerrainRender::init() { S32 i; mTextureMinSquareSize = 0; mFrameIndex = 0; mScreenError = 4; mScreenSize = 45; mMinSquareSize = 4; mFarDistance = 500; mRenderOutline = false; mTextureFrameListHead.next = &mTextureFrameListTail; mTextureFrameListHead.previous = NULL; mTextureFrameListTail.next = NULL; mTextureFrameListTail.previous = &mTextureFrameListHead; mTextureFreeListHead.next = &mTextureFreeListTail; mTextureFreeListHead.previous = NULL; mTextureFreeListTail.next = NULL; mTextureFreeListTail.previous = &mTextureFreeListHead; mTextureFreeBigListHead.next = &mTextureFreeBigListTail; mTextureFreeBigListHead.previous = NULL; mTextureFreeBigListTail.next = NULL; mTextureFreeBigListTail.previous = &mTextureFreeBigListHead; for(i = 0; i < AllocatedTextureCount; i++) mTextureGrid[i] = 0; mTextureGridPtr[0] = mTextureGrid; mTextureGridPtr[1] = mTextureGrid + 4096; mTextureGridPtr[2] = mTextureGrid + 4096 + 1024; mTextureGridPtr[3] = mTextureGrid + 4096 + 1024 + 256; mTextureGridPtr[4] = mTextureGrid + 4096 + 1024 + 256 + 64; /* for(i = 0; i < 256; i++) { mSquareSeqAdd[i] = 0; for(S32 val = 0; val < 9; val++) { if(i & (1 << val)) mSquareSeqAdd[i] += (1 << val) * (1 << val); } } */ mBlendBitmap = new GBitmap(TerrainTextureSize, TerrainTextureSize, true, GBitmap::RGB5551); Con::addVariable("T2::dynamicTextureCount", TypeS32, &mDynamicTextureCount); Con::addVariable("T2::staticTextureCount", TypeS32, &mStaticTextureCount); Con::addVariable("screenSize", TypeF32, &mScreenSize); Con::addVariable("farDistance", TypeF32, &mFarDistance); Con::addVariable("pref::Terrain::texDetail", TypeS32, &mTextureMinSquareSize); Con::addVariable("pref::Terrain::enableDetails", TypeBool, &mEnableTerrainDetails); // CW - stuff with bump maps Con::addVariable("pref::Terrain::enableEmbossBumps", TypeBool, &mEnableTerrainEmbossBumps); // CW - end bump map stuff Con::addVariable("pref::Terrain::dynamicLights", TypeBool, &mEnableTerrainDynLights); Con::addVariable("pref::Terrain::screenError", TypeF32, &mScreenError); Con::addVariable("pref::Terrain::textureCacheSize", TypeS32, &mTextureSlopSize); } void TerrainRender::shutdown() { delete mBlendBitmap; mBlendBitmap = NULL; flushCache(); } void TerrainRender::buildClippingPlanes(bool flipClipPlanes) { F64 frustumParam[6]; dglGetFrustum(&frustumParam[0], &frustumParam[1], &frustumParam[2], &frustumParam[3], &frustumParam[4], &frustumParam[5]); Point3F osCamPoint(0, 0, 0); mCameraToObject.mulP(osCamPoint); sgComputeOSFrustumPlanes(frustumParam, mCameraToObject, osCamPoint, mClipPlane[4], mClipPlane[0], mClipPlane[1], mClipPlane[2], mClipPlane[3]); // no need mNumClipPlanes = 4; // near plane is needed as well... //PlaneF p(0, 1, 0, -frustumParam[4]); //mTransformPlane(mCameraToObject, Point3F(1,1,1), p, &mClipPlane[0]); if (flipClipPlanes) { mClipPlane[0].neg(); mClipPlane[1].neg(); mClipPlane[2].neg(); mClipPlane[3].neg(); mClipPlane[4].neg(); mClipPlane[5].neg(); } } S32 TerrainRender::TestSquareVisibility(Point3F &min, Point3F &max, S32 mask, F32 expand) { S32 retMask = 0; Point3F minPoint, maxPoint; for(S32 i = 0; i < mNumClipPlanes; i++) { if(mask & (1 << i)) { if(mClipPlane[i].x > 0.0f) { maxPoint.x = max.x; minPoint.x = min.x; } else { maxPoint.x = min.x; minPoint.x = max.x; } if(mClipPlane[i].y > 0.0f) { maxPoint.y = max.y; minPoint.y = min.y; } else { maxPoint.y = min.y; minPoint.y = max.y; } if(mClipPlane[i].z > 0.0f) { maxPoint.z = max.z; minPoint.z = min.z; } else { maxPoint.z = min.z; minPoint.z = max.z; } F32 maxDot = mDot(maxPoint, mClipPlane[i]); F32 minDot = mDot(minPoint, mClipPlane[i]); F32 planeD = mClipPlane[i].d; if(maxDot <= -(planeD + expand)) return -1; if(minDot <= -planeD) retMask |= (1 << i); } } return retMask; } ChunkCornerPoint *TerrainRender::allocInitialPoint(Point3F pos) { ChunkCornerPoint *ret = (ChunkCornerPoint *) FrameAllocator::alloc(sizeof(ChunkCornerPoint)); ret->x = pos.x; ret->y = pos.y; ret->z = pos.z; ret->distance = (*ret - mCamPos).len(); gClientSceneGraph->getFogCoordPair(ret->distance, ret->z, ret->fogRed, ret->fogGreen); ret->xfIndex = 0; return ret; } ChunkCornerPoint *TerrainRender::allocPoint(Point2I pos) { ChunkCornerPoint *ret = (ChunkCornerPoint *) FrameAllocator::alloc(sizeof(ChunkCornerPoint)); ret->x = pos.x * mSquareSize + mBlockPos.x; ret->y = pos.y * mSquareSize + mBlockPos.y; ret->z = fixedToFloat(mCurrentBlock->getHeight(pos.x, pos.y)); ret->distance = (*ret - mCamPos).len(); gClientSceneGraph->getFogCoordPair(ret->distance, ret->z, ret->fogRed, ret->fogGreen); ret->xfIndex = 0; return ret; } void TerrainRender::allocRenderEdges(U32 edgeCount, EdgeParent **dest, bool renderEdge) { if(renderEdge) { for(U32 i = 0; i < edgeCount; i++) { ChunkEdge *edge = (ChunkEdge *) FrameAllocator::alloc(sizeof(ChunkEdge)); edge->c1 = NULL; edge->c2 = NULL; edge->xfIndex = 0; dest[i] = edge; } } else { for(U32 i = 0; i < edgeCount; i++) { ChunkScanEdge *edge = (ChunkScanEdge *) FrameAllocator::alloc(sizeof(ChunkScanEdge)); edge->mp = NULL; dest[i] = edge; } } } void TerrainRender::subdivideChunkEdge(ChunkScanEdge *e, Point2I pos, bool chunkEdge) { if(!e->mp) { allocRenderEdges(2, &e->e1, chunkEdge); e->mp = allocPoint(pos); e->e1->p1 = e->p1; e->e1->p2 = e->mp; e->e2->p1 = e->mp; e->e2->p2 = e->p2; } } F32 TerrainRender::getSquareDistance(const Point3F& minPoint, const Point3F& maxPoint, F32* zDiff) { Point3F vec( 0.0f, 0.0f, 0.0f ); if(mCamPos.z < minPoint.z) vec.z = minPoint.z - mCamPos.z; else if(mCamPos.z > maxPoint.z) vec.z = maxPoint.z - mCamPos.z; if(mCamPos.x < minPoint.x) vec.x = minPoint.x - mCamPos.x; else if(mCamPos.x > maxPoint.x) vec.x = mCamPos.x - maxPoint.x; if(mCamPos.y < minPoint.y) vec.y = minPoint.y - mCamPos.y; else if(mCamPos.y > maxPoint.y) vec.y = mCamPos.y - maxPoint.y; *zDiff = vec.z; return vec.len(); } void TerrainRender::emitTerrChunk(SquareStackNode *n, F32 squareDistance, U32 lightMask, bool farClip, bool drawDetails, bool drawBumps) { //if(n->pos.x || n->pos.y) // return; GridChunk *gc = mCurrentBlock->findChunk(n->pos); EmitChunk *chunk = (EmitChunk *) FrameAllocator::alloc(sizeof(EmitChunk)); chunk->x = n->pos.x + mBlockOffset.x + mTerrainOffset.x; chunk->y = n->pos.y + mBlockOffset.y + mTerrainOffset.y; chunk->gridX = n->pos.x; chunk->gridY = n->pos.y; chunk->lightMask = lightMask; chunk->next = mCurrentTexture->list; chunk->chunkTexture = mCurrentTexture; mCurrentTexture->list = chunk; if(mRenderingCommander) return; chunk->edge[0] = (ChunkEdge *) n->top; chunk->edge[1] = (ChunkEdge *) n->right; chunk->edge[2] = (ChunkEdge *) n->bottom; chunk->edge[3] = (ChunkEdge *) n->left; chunk->edge[0]->c2 = chunk; chunk->edge[1]->c1 = chunk; chunk->edge[2]->c1 = chunk; chunk->edge[3]->c2 = chunk; // holes only in the primary terrain block if (gc->emptyFlags && mBlockPos.x == 0.0f && mBlockPos.y == 0.0f) chunk->emptyFlags = gc->emptyFlags; else chunk->emptyFlags = 0; S32 subDivLevel; F32 growFactor = 0.0f; F32 minSubdivideDistance = 1000000.0f; chunk->clip = farClip; chunk->renderDetails = drawDetails; chunk->renderBumps = drawBumps; if(squareDistance < 1.0f) { subDivLevel = -1; } else { const F32 invPixelError = 1.0f / mPixelError; for(subDivLevel = 2; subDivLevel >= 0; subDivLevel--) { F32 subdivideDistance = fixedToFloat(gc->heightDeviance[subDivLevel]) * invPixelError; if(subdivideDistance > minSubdivideDistance) subdivideDistance = minSubdivideDistance; if(squareDistance >= subdivideDistance) break; F32 clampDistance = subdivideDistance * 0.75f; if(squareDistance > clampDistance) { growFactor = (squareDistance - clampDistance) / (0.25f * subdivideDistance); subDivLevel--; break; } minSubdivideDistance = clampDistance; } } chunk->subDivLevel = subDivLevel; chunk->growFactor = growFactor; } void TerrainRender::processCurrentBlock(SceneState*, EdgeParent *topEdge, EdgeParent *rightEdge, EdgeParent *bottomEdge, EdgeParent *leftEdge) { SquareStackNode stack[TerrainBlock::BlockShift*4]; Point3F minPoint, maxPoint; stack[0].level = TerrainBlock::BlockShift; stack[0].clipFlags = ((1 << mNumClipPlanes) - 1) | FarSphereMask; // test all the planes stack[0].pos.set(0,0); stack[0].top = topEdge; stack[0].right = rightEdge; stack[0].bottom = bottomEdge; stack[0].left = leftEdge; stack[0].lightMask = (1 << mDynamicLightCount) - 1; // test all the lights stack[0].texAllocated = false; Vector *posFog = mSceneState->getPosFogBands(); Vector *negFog = mSceneState->getNegFogBands(); bool clipAbove = posFog->size() > 0 && (*posFog)[0].isFog == false; bool clipBelow = negFog->size() > 0 && (*negFog)[0].isFog == false; bool clipOn = posFog->size() > 0 && (*posFog)[0].isFog == true; if(posFog->size() != 0 || negFog->size() != 0) stack[0].clipFlags |= FogPlaneBoxMask; S32 curStackSize = 1; F32 worldToScreenScale = dglProjectRadius(1,1); F32 zeroDetailDistance = (mSquareSize * worldToScreenScale) / (1 << 6) - (mSquareSize >> 1); F32 zeroBumpDistance = (mSquareSize * worldToScreenScale) / (1 << mCurrentBlock->mZeroBumpScale) - (mSquareSize >> 1); while(curStackSize) { SquareStackNode *n = stack + curStackSize - 1; // see if it's visible GridSquare *sq = mCurrentBlock->findSquare(n->level, n->pos); minPoint.set(mSquareSize * n->pos.x + mBlockPos.x, mSquareSize * n->pos.y + mBlockPos.y, fixedToFloat(sq->minHeight)); maxPoint.set(minPoint.x + (mSquareSize << n->level), minPoint.y + (mSquareSize << n->level), fixedToFloat(sq->maxHeight)); // holes only in the primary terrain block if ((sq->flags & GridSquare::Empty) && mBlockPos.x == 0.0f && mBlockPos.y == 0.0f) { curStackSize--; continue; } F32 zDiff; F32 squareDistance = getSquareDistance(minPoint, maxPoint, &zDiff); S32 nextClipFlags = 0; if(n->clipFlags) { if(n->clipFlags & FogPlaneBoxMask) { F32 camZ = mCamPos.z; bool boxBelow = camZ > maxPoint.z; bool boxAbove = camZ < minPoint.z; bool boxOn = !(boxAbove || boxBelow); if( clipOn || (clipAbove && boxAbove && (maxPoint.z - camZ > (*posFog)[0].cap)) || (clipBelow && boxBelow && (camZ - minPoint.z > (*negFog)[0].cap)) || (boxOn && (( clipAbove && maxPoint.z - camZ > (*posFog)[0].cap ) || ( clipBelow && camZ - minPoint.z > (*negFog)[0].cap )))) { // Using the fxSunLight can cause the "sky" to extend down below the camera. // To avoid the sun showing through, the terrain must always be rendered. // If the fxSunLight is not being used, the following optimization can be // uncommented. #if 0 if(boxBelow && !mSceneState->isBoxFogVisible(squareDistance, maxPoint.z, minPoint.z)) { // Totally fogged terrain tiles can be thrown out as long as they are // below the camera. If they are ubove, the sky will show through the // fog. curStackSize--; continue; } #endif nextClipFlags |= FogPlaneBoxMask; } } if(n->clipFlags & FarSphereMask) { if(squareDistance >= mFarDistance) { curStackSize--; continue; } S32 squareSz = mSquareSize << n->level; if(squareDistance + maxPoint.z - minPoint.z + squareSz + squareSz > mFarDistance) nextClipFlags |= FarSphereMask; } // zDelta for screen error height deviance. F32 zDelta = squareDistance * mPixelError; minPoint.z -= zDelta; maxPoint.z += zDelta; nextClipFlags |= TestSquareVisibility(minPoint, maxPoint, n->clipFlags, mSquareSize); if(nextClipFlags == -1) { //if(!n->texAllocated) // textureRecurse(n); // trivially rejected, so pop it off the stack curStackSize--; continue; } } if(!n->texAllocated) { S32 squareSz = mSquareSize << n->level; // first check the level - if its 3 or less, we have to just make a bitmap: // level 3 == 8x8 square - 8x8 * 16x16 == 128x128 if(n->level > 6) goto notexalloc; S32 mipLevel = TerrainTextureMipLevel; if(!mRenderingCommander) { if(n->level > mTextureMinSquareSize + 2) { // get the mip level of the square and see if we're in range if(squareDistance > 0.001f) { S32 size = S32(dglProjectRadius(squareDistance + (squareSz >> 1), squareSz)); mipLevel = getPower((S32)(size * 0.75)); if(mipLevel > TerrainTextureMipLevel) // too big for this square goto notexalloc; } else goto notexalloc; } } allocTerrTexture(n->pos, n->level, mipLevel, true, squareDistance); n->texAllocated = true; if(mRenderingCommander) // level == 6 { emitTerrChunk(n, 0.0f, 0, false, false, false); curStackSize--; continue; } } notexalloc: if(n->lightMask) n->lightMask = TestSquareLights(sq, n->level, n->pos, n->lightMask); if(n->level == 2) { AssertFatal(n->texAllocated, "Invalid texture index."); bool drawDetails = false; if (mEnableTerrainDetails && squareDistance < zeroDetailDistance) drawDetails = true; //END bool drawBumps = false; // CW - stuff with bump maps if (mEnableTerrainEmbossBumps && squareDistance < zeroBumpDistance) drawBumps = true; // CW - end bump map stuff emitTerrChunk(n, squareDistance, n->lightMask, nextClipFlags & FarSphereMask, drawDetails, drawBumps); curStackSize--; continue; } bool allocChunkEdges = (n->level == 3); Point2I pos = n->pos; ChunkScanEdge *top = (ChunkScanEdge *) n->top; ChunkScanEdge *right = (ChunkScanEdge *) n->right; ChunkScanEdge *bottom = (ChunkScanEdge *) n->bottom; ChunkScanEdge *left = (ChunkScanEdge *) n->left; // subdivide this square and throw it on the stack S32 squareOneSize = 1 << n->level; S32 squareHalfSize = squareOneSize >> 1; ChunkCornerPoint *midPoint = allocPoint(Point2I(pos.x + squareHalfSize, pos.y + squareHalfSize)); S32 nextLevel = n->level - 1; subdivideChunkEdge(top, Point2I(pos.x + squareHalfSize, pos.y + squareOneSize), allocChunkEdges); subdivideChunkEdge(right, Point2I(pos.x + squareOneSize, pos.y + squareHalfSize), allocChunkEdges); subdivideChunkEdge(bottom, Point2I(pos.x + squareHalfSize, pos.y), allocChunkEdges); subdivideChunkEdge(left, Point2I(pos.x, pos.y + squareHalfSize), allocChunkEdges); // cross edges go top, right, bottom, left EdgeParent *crossEdges[4]; allocRenderEdges(4, crossEdges, allocChunkEdges); crossEdges[0]->p1 = top->mp; crossEdges[0]->p2 = midPoint; crossEdges[1]->p1 = midPoint; crossEdges[1]->p2 = right->mp; crossEdges[2]->p1 = midPoint; crossEdges[2]->p2 = bottom->mp; crossEdges[3]->p1 = left->mp; crossEdges[3]->p2 = midPoint; n->level = nextLevel; n->clipFlags = nextClipFlags; for(S32 i = 1; i < 4; i++) { n[i].level = nextLevel; n[i].clipFlags = nextClipFlags; n[i].lightMask = n->lightMask; n[i].texAllocated = n->texAllocated; } // push in reverse order of processing. n[3].pos = pos; n[3].top = crossEdges[3]; n[3].right = crossEdges[2]; n[3].bottom = bottom->e1; n[3].left = left->e2; n[2].pos.set(pos.x + squareHalfSize, pos.y); n[2].top = crossEdges[1]; n[2].right = right->e2; n[2].bottom = bottom->e2; n[2].left = crossEdges[2]; n[1].pos.set(pos.x, pos.y + squareHalfSize); n[1].top = top->e1; n[1].right = crossEdges[0]; n[1].bottom = crossEdges[3]; n[1].left = left->e1; n[0].pos.set(pos.x + squareHalfSize, pos.y + squareHalfSize); n[0].top = top->e2; n[0].right = right->e1; n[0].bottom = crossEdges[1]; n[0].left = crossEdges[0]; curStackSize += 3; } } //--------------------------------------------------------------- //--------------------------------------------------------------- // Root block render function //--------------------------------------------------------------- //--------------------------------------------------------------- void TerrainRender::fixEdge(ChunkEdge *edge, S32 x, S32 y, S32 dx, S32 dy) { S32 minLevel, maxLevel; F32 growFactor; if(edge->c1) { minLevel = edge->c1->subDivLevel; maxLevel = edge->c1->subDivLevel; growFactor = edge->c1->growFactor; if(edge->c2) { if(edge->c2->subDivLevel < minLevel) minLevel = edge->c2->subDivLevel; else if(edge->c2->subDivLevel > maxLevel) { maxLevel = edge->c2->subDivLevel; growFactor = edge->c2->growFactor; } else if(edge->c2->growFactor > growFactor) growFactor = edge->c2->growFactor; } } else { minLevel = maxLevel = edge->c2->subDivLevel; growFactor = edge->c2->growFactor; } if(minLevel == 2) { edge->pointCount = 0; return; } // get the mid heights EdgePoint *pmid = &edge->pt[1]; ChunkCornerPoint *p1 = edge->p1; ChunkCornerPoint *p2 = edge->p2; pmid->x = (p1->x + p2->x) * 0.5f; pmid->y = (p1->y + p2->y) * 0.5f; if(maxLevel == 2) { // pure interp pmid->z = (p1->z + p2->z) * 0.5f; pmid->distance = (*pmid - mCamPos).len(); pmid->fogRed = (p1->fogRed + p2->fogRed) * 0.5f; pmid->fogGreen = (p1->fogGreen + p2->fogGreen) * 0.5f; if(minLevel >= 0) { edge->pointCount = 1; return; } } else { pmid->z = fixedToFloat(mCurrentBlock->getHeight(x + dx + dx, y + dy + dy)); if(maxLevel == 1) // interp the z and haze pmid->z = pmid->z + growFactor * (((p1->z + p2->z) * 0.5f) - pmid->z); pmid->distance = (*pmid - mCamPos).len(); gClientSceneGraph->getFogCoordPair(pmid->distance, pmid->z, pmid->fogRed, pmid->fogGreen); if(maxLevel == 1) // interp the z and haze { pmid->fogRed = pmid->fogRed + growFactor * (((p1->fogRed + p2->fogRed) * 0.5f) - pmid->fogRed); pmid->fogGreen = pmid->fogGreen + growFactor * (((p1->fogGreen + p2->fogGreen) * 0.5f) - pmid->fogGreen); } if(minLevel >= 0) { edge->pointCount = 1; return; } } // last case - minLevel == -1, midPoint calc'd edge->pointCount = 3; EdgePoint *pm1 = &edge->pt[0]; EdgePoint *pm2 = &edge->pt[2]; pm1->x = (p1->x + pmid->x) * 0.5f; pm1->y = (p1->y + pmid->y) * 0.5f; pm2->x = (p2->x + pmid->x) * 0.5f; pm2->y = (p2->y + pmid->y) * 0.5f; if(maxLevel != -1) { // clamp it: pm1->z = (p1->z + pmid->z) * 0.5f; pm1->distance = (*pm1 - mCamPos).len(); pm1->fogRed = (p1->fogRed + pmid->fogRed) * 0.5f; pm1->fogGreen = (p1->fogGreen + pmid->fogGreen) * 0.5f; pm2->z = (p2->z + pmid->z) * 0.5f; pm2->distance = (*pm2 - mCamPos).len(); pm2->fogRed = (p2->fogRed + pmid->fogRed) * 0.5f; pm2->fogGreen = (p2->fogGreen + pmid->fogGreen) * 0.5f; return; } // compute the real deals: pm1->z = fixedToFloat(mCurrentBlock->getHeight(x + dx, y + dy)); pm2->z = fixedToFloat(mCurrentBlock->getHeight(x + dx + dx + dx, y + dy + dy + dy)); if(growFactor) { pm1->z = pm1->z + growFactor * (((p1->z + pmid->z) * 0.5f) - pm1->z); pm2->z = pm2->z + growFactor * (((p2->z + pmid->z) * 0.5f) - pm2->z); } pm1->distance = (*pm1 - mCamPos).len(); gClientSceneGraph->getFogCoordPair(pm1->distance, pm1->z, pm1->fogRed, pm1->fogGreen); pm2->distance = (*pm2 - mCamPos).len(); gClientSceneGraph->getFogCoordPair(pm2->distance, pm2->z, pm2->fogRed, pm2->fogGreen); if(growFactor) { pm1->fogRed = pm1->fogRed + growFactor * (((p1->fogRed + pmid->fogRed) * 0.5f) - pm1->fogRed); pm1->fogGreen = pm1->fogGreen + growFactor * (((p1->fogGreen + pmid->fogGreen) * 0.5f) - pm1->fogGreen); pm2->fogRed = pm2->fogRed + growFactor * (((p2->fogRed + pmid->fogRed) * 0.5f) - pm2->fogRed); pm2->fogGreen = pm2->fogGreen + growFactor * (((p2->fogGreen + pmid->fogGreen) * 0.5f) - pm2->fogGreen); } } EdgePoint *mXFVertices = NULL; U16 *mXFIndexBuffer; U16 *mXFIndexPtr; U32 mXFIndexCount; U32 mXFPointCount; U32 mXFIndex; inline U32 clipPoint(EdgePoint *p1, EdgePoint *p2, F32 dist) { F32 frac = (dist - p1->distance) / (p2->distance - p1->distance); F32 onefrac = 1.0f - frac; U32 clipIndex = mXFPointCount++; EdgePoint *ip = mXFVertices + clipIndex; ip->x = (p2->x * frac) + (p1->x * onefrac); ip->y = (p2->y * frac) + (p1->y * onefrac); ip->z = (p2->z * frac) + (p1->z * onefrac); ip->fogRed = (p2->fogRed * frac) + (p1->fogRed * onefrac); ip->fogGreen = (p2->fogGreen * frac) + (p1->fogGreen * onefrac); ip->distance = dist; return clipIndex; } void TerrainRender::clip(U32 indexStart) { static U16 dest[16 * 3 * 2]; U32 vertexCount = mXFIndexBuffer[indexStart + 1]; U32 centerPoint = mXFIndexBuffer[indexStart + 2]; U16 *source = mXFIndexBuffer + indexStart + 3; EdgePoint *center = mXFVertices + centerPoint; U32 destIndex = 0; if(mXFVertices[centerPoint].distance > mFarDistance) { // loop through all the tris and clip em: // there are vertexCount - 1 triangles. EdgePoint *p1 = mXFVertices + source[0]; bool p1out = p1->distance >= mFarDistance; U32 p1idx = source[0]; U32 clip1; if(!p1out) clip1 = clipPoint(p1, center, mFarDistance); for(U32 i = 0; i < vertexCount - 2; i++) { U32 p2idx = source[i+1]; EdgePoint *p2 = mXFVertices + p2idx; bool p2out = p2->distance >= mFarDistance; if(!p2out) { U32 clip2 = clipPoint(p2, center, mFarDistance); if(p1out) { // p2 is the only "in" point: dest[destIndex++] = p2idx; dest[destIndex++] = clip2; dest[destIndex++] = clipPoint(p1, p2, mFarDistance); } else { dest[destIndex++] = clip2; dest[destIndex++] = clip1; dest[destIndex++] = p1idx; dest[destIndex++] = p2idx; dest[destIndex++] = clip2; dest[destIndex++] = p1idx; } clip1 = clip2; } else if(!p1out) { dest[destIndex++] = p1idx; dest[destIndex++] = clipPoint(p1, p2, mFarDistance); dest[destIndex++] = clip1; } p1idx = p2idx; p1out = p2out; p1 = p2; } if(destIndex) { // copy this in.. mXFIndexBuffer[indexStart] = GL_TRIANGLES; mXFIndexBuffer[indexStart + 1] = destIndex; for(U32 i = 0; i < destIndex; i++) mXFIndexBuffer[indexStart + i + 2] = dest[i]; mXFIndexCount = destIndex + indexStart + 2; } else mXFIndexCount = indexStart; } else { EdgePoint *prev = mXFVertices + source[0]; bool prevIn = prev->distance <= mFarDistance; U32 i; for(i = 1; i < vertexCount - 1; i++) { EdgePoint *pt = mXFVertices + source[i]; bool curIn = pt->distance <= mFarDistance; if((curIn && !prevIn) || (!curIn && prevIn)) dest[destIndex++] = clipPoint(pt, prev, mFarDistance); if(curIn) dest[destIndex++] = source[i]; else dest[destIndex++] = clipPoint(pt, center, mFarDistance); prev = pt; prevIn = curIn; } for(i = 0; i < destIndex; i++) mXFIndexBuffer[indexStart + i + 3] = dest[i]; mXFIndexBuffer[indexStart + destIndex + 3] = dest[0]; mXFIndexBuffer[indexStart + 1] = destIndex + 2; mXFIndexCount = indexStart + destIndex + 4; } } inline U32 TerrainRender::constructPoint(S32 x, S32 y) { U32 ret = mXFPointCount++; EdgePoint *pt = mXFVertices + ret; pt->x = x * mSquareSize; pt->y = y * mSquareSize; pt->z = fixedToFloat(mCurrentBlock->getHeight(x, y)); pt->distance = (*pt - mCamPos).len(); gClientSceneGraph->getFogCoordPair(pt->distance, pt->z, pt->fogRed, pt->fogGreen); return ret; } inline U32 TerrainRender::interpPoint(U32 p1, U32 p2, S32 x, S32 y, F32 growFactor) { U32 ret = mXFPointCount++; EdgePoint *pt = mXFVertices + ret; pt->x = x * mSquareSize; pt->y = y * mSquareSize; pt->z = fixedToFloat(mCurrentBlock->getHeight(x, y)); pt->z = pt->z + growFactor * (((mXFVertices[p1].z + mXFVertices[p2].z) * 0.5f) - pt->z); pt->distance = (*pt - mCamPos).len(); gClientSceneGraph->getFogCoordPair(pt->distance, pt->z, pt->fogRed, pt->fogGreen); return ret; } inline void TerrainRender::addEdge(ChunkEdge *edge) { if(edge->pointCount == 1) { edge->pointIndex = mXFPointCount; mXFVertices[mXFPointCount++] = * ((EdgePoint *) &edge->pt[1]); } else if(edge->pointCount == 3) { edge->pointIndex = mXFPointCount; mXFVertices[mXFPointCount++] = *((EdgePoint *) &edge->pt[0]); mXFVertices[mXFPointCount++] = *((EdgePoint *) &edge->pt[1]); mXFVertices[mXFPointCount++] = *((EdgePoint *) &edge->pt[2]); } edge->xfIndex = mXFIndex; } inline void emitTri(U32 i1, U32 i2, U32 i3) { mXFIndexBuffer[mXFIndexCount] = i1; mXFIndexBuffer[mXFIndexCount + 1] = i2; mXFIndexBuffer[mXFIndexCount + 2] = i3; mXFIndexCount += 3; } inline U32 emitCornerPoint(ChunkCornerPoint *p) { if(p->xfIndex != mXFIndex) { p->pointIndex = mXFPointCount; p->xfIndex = mXFIndex; mXFVertices[mXFPointCount++] = *((EdgePoint *) p); } return p->pointIndex; } void buildLightTri(LightTriangle* pTri, TerrLightInfo* pInfo) { // Get the plane normal Point3F normal; mCross((pTri->point1 - pTri->point2), (pTri->point3 - pTri->point2), &normal); if (normal.lenSquared() < 1e-7f) { pTri->flags = 0; return; } PlaneF plane(pTri->point2, normal); // Assumes that mPlane.h normalizes incoming point Point3F centerPoint; F32 d = plane.distToPlane(pInfo->pos); centerPoint = pInfo->pos - plane * d; d = mFabs(d); if (d >= pInfo->radius) { pTri->flags = 0; return; } F32 mr = mSqrt(pInfo->radiusSquared - d*d); Point3F normalS; Point3F normalT; mCross(plane, Point3F(0.0f, 1.0f, 0.0f), &normalS); mCross(plane, normalS, &normalT); PlaneF splane(centerPoint, normalS); // Assumes that mPlane.h normalizes incoming point PlaneF tplane(centerPoint, normalT); // Assumes that mPlane.h normalizes incoming point pTri->color.red = pInfo->r; pTri->color.green = pInfo->g; pTri->color.blue = pInfo->b; pTri->color.alpha = (pInfo->radius - d) / pInfo->radius; pTri->texco1.set(((splane.distToPlane(pTri->point1) / mr) + 1.0f) * 0.5f, ((tplane.distToPlane(pTri->point1) / mr) + 1.0f) * 0.5f); pTri->texco2.set(((splane.distToPlane(pTri->point2) / mr) + 1.0f) * 0.5f, ((tplane.distToPlane(pTri->point2) / mr) + 1.0f) * 0.5f); pTri->texco3.set(((splane.distToPlane(pTri->point3) / mr) + 1.0f) * 0.5f, ((tplane.distToPlane(pTri->point3) / mr) + 1.0f) * 0.5f); pTri->flags = 1; } void TerrainRender::renderChunkCommander(EmitChunk *chunk) { U32 ll = mXFPointCount; for(U32 y = 0; y <= 64; y += 4) for(U32 x = (y & 4) ? 4 : 0; x <= 64; x += 8) constructPoint(chunk->x + x,chunk->y + y); for(U32 y = 0; y < 8; y++) { for(U32 x = 0; x < 8; x++) { U16 *ib = mXFIndexBuffer + mXFIndexCount; ib[0] = GL_TRIANGLE_FAN; ib[1] = 6; ib[2] = ll + 9; ib[3] = ll; ib[4] = ll + 17; ib[5] = ll + 18; ib[6] = ll + 1; ib[7] = ll; mXFIndexCount += 8; ll++; } ll += 9; } } void TerrainRender::renderChunkOutline(EmitChunk *chunk) { U32 startXFIndex = mXFIndexCount; ChunkEdge *e0 = chunk->edge[0]; ChunkEdge *e1 = chunk->edge[1]; ChunkEdge *e2 = chunk->edge[2]; ChunkEdge *e3 = chunk->edge[3]; if(e0->xfIndex != mXFIndex) { if(!e0->xfIndex) fixEdge(e0, chunk->x, chunk->y + 4, 1, 0); addEdge(e0); } if(e1->xfIndex != mXFIndex) { if(!e1->xfIndex) fixEdge(e1, chunk->x + 4, chunk->y + 4, 0, -1); addEdge(e1); } if(e2->xfIndex != mXFIndex) { if(!e2->xfIndex) fixEdge(e2, chunk->x, chunk->y, 1, 0); addEdge(e2); } if(e3->xfIndex != mXFIndex) { if(!e3->xfIndex) fixEdge(e3, chunk->x, chunk->y + 4, 0, -1); addEdge(e3); } U32 p0 = emitCornerPoint(e0->p1); U32 p1 = emitCornerPoint(e0->p2); U32 p2 = emitCornerPoint(e2->p2); U32 p3 = emitCornerPoint(e2->p1); // build the interior points: U32 ip0 = constructPoint(chunk->x + 2, chunk->y + 2); F32 growFactor = chunk->growFactor; if(chunk->subDivLevel >= 1) { // just emit the fan for the whole square: S32 i; mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLE_FAN; U32 indexStart = mXFIndexCount++; mXFIndexBuffer[mXFIndexCount++] = ip0; mXFIndexBuffer[mXFIndexCount++] = p0; for(i = 0; i < e0->pointCount; i++) mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex + i; mXFIndexBuffer[mXFIndexCount++] = p1; for(i = 0; i < e1->pointCount; i++) mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex + i; mXFIndexBuffer[mXFIndexCount++] = p2; for(i = e2->pointCount - 1; i >= 0; i--) mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex + i; mXFIndexBuffer[mXFIndexCount++] = p3; for(i = e3->pointCount - 1; i >= 0; i--) mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex + i; mXFIndexBuffer[mXFIndexCount++] = p0; mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; if(chunk->clip) clip(indexStart - 1); } else { if(chunk->subDivLevel == 0) { U32 ip1 = interpPoint(p0, ip0, chunk->x + 1, chunk->y + 3, growFactor); U32 ip2 = interpPoint(p1, ip0, chunk->x + 3, chunk->y + 3, growFactor); U32 ip3 = interpPoint(p2, ip0, chunk->x + 3, chunk->y + 1, growFactor); U32 ip4 = interpPoint(p3, ip0, chunk->x + 1, chunk->y + 1, growFactor); // emit the 4 fans: U32 indexStart; if((chunk->emptyFlags & CornerEmpty_0_1) != CornerEmpty_0_1) { mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLE_FAN; indexStart = mXFIndexCount++; mXFIndexBuffer[mXFIndexCount++] = ip1; mXFIndexBuffer[mXFIndexCount++] = p0; if(e0->pointCount == 1) mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex; else // has to be 3: { mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex; mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex + 1; } mXFIndexBuffer[mXFIndexCount++] = ip0; if(e3->pointCount == 1) mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex; else { mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex + 1; mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex; } mXFIndexBuffer[mXFIndexCount++] = p0; mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; if(chunk->clip) clip(indexStart - 1); } if((chunk->emptyFlags & CornerEmpty_1_1) != CornerEmpty_1_1) { mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLE_FAN; indexStart = mXFIndexCount++; mXFIndexBuffer[mXFIndexCount++] = ip2; mXFIndexBuffer[mXFIndexCount++] = p1; if(e1->pointCount == 1) mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex; else { mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex; mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex + 1; } mXFIndexBuffer[mXFIndexCount++] = ip0; if(e0->pointCount == 1) mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex; else { mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex + 1; mXFIndexBuffer[mXFIndexCount++] = e0->pointIndex + 2; } mXFIndexBuffer[mXFIndexCount++] = p1; mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; if(chunk->clip) clip(indexStart - 1); } if((chunk->emptyFlags & CornerEmpty_1_0) != CornerEmpty_1_0) { mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLE_FAN; indexStart = mXFIndexCount++; mXFIndexBuffer[mXFIndexCount++] = ip3; mXFIndexBuffer[mXFIndexCount++] = p2; if(e2->pointCount == 1) mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex; else { mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex + 2; mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex + 1; } mXFIndexBuffer[mXFIndexCount++] = ip0; if(e1->pointCount == 1) mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex; else { mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex + 1; mXFIndexBuffer[mXFIndexCount++] = e1->pointIndex + 2; } mXFIndexBuffer[mXFIndexCount++] = p2; mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; if(chunk->clip) clip(indexStart - 1); } if((chunk->emptyFlags & CornerEmpty_0_0) != CornerEmpty_0_0) { mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLE_FAN; indexStart = mXFIndexCount++; mXFIndexBuffer[mXFIndexCount++] = ip4; mXFIndexBuffer[mXFIndexCount++] = p3; if(e3->pointCount == 1) mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex; else { mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex + 2; mXFIndexBuffer[mXFIndexCount++] = e3->pointIndex + 1; } mXFIndexBuffer[mXFIndexCount++] = ip0; if(e2->pointCount == 1) mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex; else { mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex + 1; mXFIndexBuffer[mXFIndexCount++] = e2->pointIndex; } mXFIndexBuffer[mXFIndexCount++] = p3; mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; if(chunk->clip) clip(indexStart - 1); } } else { // subDiv == -1 U32 ip1 = constructPoint(chunk->x + 1, chunk->y + 3); U32 ip2 = constructPoint(chunk->x + 3, chunk->y + 3); U32 ip3 = constructPoint(chunk->x + 3, chunk->y + 1); U32 ip4 = constructPoint(chunk->x + 1, chunk->y + 1); U32 ip5 = interpPoint(e0->pointIndex + 1, ip0, chunk->x + 2, chunk->y + 3, growFactor); U32 ip6 = interpPoint(e1->pointIndex + 1, ip0, chunk->x + 3, chunk->y + 2, growFactor); U32 ip7 = interpPoint(e2->pointIndex + 1, ip0, chunk->x + 2, chunk->y + 1, growFactor); U32 ip8 = interpPoint(e3->pointIndex + 1, ip0, chunk->x + 1, chunk->y + 2, growFactor); // now do the squares: U16 *ib; if(chunk->emptyFlags & CornerEmpty_0_1) { if((chunk->emptyFlags & CornerEmpty_0_1) != CornerEmpty_0_1) { mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLES; U32 indexStart = mXFIndexCount++; if(!(chunk->emptyFlags & SquareEmpty_0_3)) { emitTri(ip1, e3->pointIndex, p0); emitTri(ip1, p0, e0->pointIndex); } if(!(chunk->emptyFlags & SquareEmpty_1_3)) { emitTri(ip1, e0->pointIndex, e0->pointIndex + 1); emitTri(ip1, e0->pointIndex + 1, ip5); } if(!(chunk->emptyFlags & SquareEmpty_1_2)) { emitTri(ip1, ip5, ip0); emitTri(ip1, ip0, ip8); } if(!(chunk->emptyFlags & SquareEmpty_0_2)) { emitTri(ip1, ip8, e3->pointIndex + 1); emitTri(ip1, e3->pointIndex + 1, e3->pointIndex); } mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; } } else { ib = mXFIndexBuffer + mXFIndexCount + 1; ib[-1] = GL_TRIANGLE_FAN; ib[0] = 10; ib[1] = ip1; ib[2] = p0; ib[3] = e0->pointIndex; ib[4] = e0->pointIndex + 1; ib[5] = ip5; ib[6] = ip0; ib[7] = ip8; ib[8] = e3->pointIndex + 1; ib[9] = e3->pointIndex; ib[10] = ib[2]; mXFIndexCount += 12; if(chunk->clip) clip(mXFIndexCount - 12); } if(chunk->emptyFlags & CornerEmpty_1_1) { if((chunk->emptyFlags & CornerEmpty_1_1) != CornerEmpty_1_1) { mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLES; U32 indexStart = mXFIndexCount++; if(!(chunk->emptyFlags & SquareEmpty_3_3)) { emitTri(ip2, e0->pointIndex + 2, p1); emitTri(ip2, p1, e1->pointIndex); } if(!(chunk->emptyFlags & SquareEmpty_3_2)) { emitTri(ip2, e1->pointIndex, e1->pointIndex + 1); emitTri(ip2, e1->pointIndex + 1, ip6); } if(!(chunk->emptyFlags & SquareEmpty_2_2)) { emitTri(ip2, ip6, ip0); emitTri(ip2, ip0, ip5); } if(!(chunk->emptyFlags & SquareEmpty_2_3)) { emitTri(ip2, ip5, e0->pointIndex + 1); emitTri(ip2, e0->pointIndex + 1, e0->pointIndex + 2); } mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; } } else { ib = mXFIndexBuffer + mXFIndexCount + 1; ib[-1] = GL_TRIANGLE_FAN; ib[0] = 10; ib[1] = ip2; ib[2] = p1; ib[3] = e1->pointIndex; ib[4] = e1->pointIndex + 1; ib[5] = ip6; ib[6] = ip0; ib[7] = ip5; ib[8] = e0->pointIndex + 1; ib[9] = e0->pointIndex + 2; ib[10] = ib[2]; mXFIndexCount += 12; if(chunk->clip) clip(mXFIndexCount - 12); } if(chunk->emptyFlags & CornerEmpty_1_0) { if((chunk->emptyFlags & CornerEmpty_1_0) != CornerEmpty_1_0) { mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLES; U32 indexStart = mXFIndexCount++; if(!(chunk->emptyFlags & SquareEmpty_3_0)) { emitTri(ip3, e1->pointIndex + 2, p2); emitTri(ip3, p2, e2->pointIndex + 2); } if(!(chunk->emptyFlags & SquareEmpty_2_0)) { emitTri(ip3, e2->pointIndex + 2, e2->pointIndex + 1); emitTri(ip3, e2->pointIndex + 1, ip7); } if(!(chunk->emptyFlags & SquareEmpty_2_1)) { emitTri(ip3, ip7, ip0); emitTri(ip3, ip0, ip6); } if(!(chunk->emptyFlags & SquareEmpty_3_1)) { emitTri(ip3, ip6, e1->pointIndex + 1); emitTri(ip3, e1->pointIndex + 1, e1->pointIndex + 2); } mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; } } else { ib = mXFIndexBuffer + mXFIndexCount + 1; ib[-1] = GL_TRIANGLE_FAN; ib[0] = 10; ib[1] = ip3; ib[2] = p2; ib[3] = e2->pointIndex + 2; ib[4] = e2->pointIndex + 1; ib[5] = ip7; ib[6] = ip0; ib[7] = ip6; ib[8] = e1->pointIndex + 1; ib[9] = e1->pointIndex + 2; ib[10] = ib[2]; mXFIndexCount += 12; if(chunk->clip) clip(mXFIndexCount - 12); } if(chunk->emptyFlags & CornerEmpty_0_0) { if((chunk->emptyFlags & CornerEmpty_0_0) != CornerEmpty_0_0) { mXFIndexBuffer[mXFIndexCount++] = GL_TRIANGLES; U32 indexStart = mXFIndexCount++; if(!(chunk->emptyFlags & SquareEmpty_0_0)) { emitTri(ip4, e2->pointIndex, p3); emitTri(ip4, p3, e3->pointIndex + 2); } if(!(chunk->emptyFlags & SquareEmpty_0_1)) { emitTri(ip4, e3->pointIndex + 2, e3->pointIndex + 1); emitTri(ip4, e3->pointIndex + 1, ip8); } if(!(chunk->emptyFlags & SquareEmpty_1_1)) { emitTri(ip4, ip8, ip0); emitTri(ip4, ip0, ip7); } if(!(chunk->emptyFlags & SquareEmpty_1_0)) { emitTri(ip4, ip7, e2->pointIndex + 1); emitTri(ip4, e2->pointIndex + 1, e2->pointIndex); } mXFIndexBuffer[indexStart] = mXFIndexCount - indexStart - 1; } } else { ib = mXFIndexBuffer + mXFIndexCount + 1; ib[-1] = GL_TRIANGLE_FAN; ib[0] = 10; ib[1] = ip4; ib[2] = p3; ib[3] = e3->pointIndex + 2; ib[4] = e3->pointIndex + 1; ib[5] = ip8; ib[6] = ip0; ib[7] = ip7; ib[8] = e2->pointIndex + 1; ib[9] = e2->pointIndex; ib[10] = ib[2]; mXFIndexCount += 12; if(chunk->clip) clip(mXFIndexCount - 12); } } } // Do dynamic lighting here... if (mEnableTerrainDynLights && chunk->lightMask != 0) { for (U32 i = 0; i < 32; i++) { if ((chunk->lightMask & (1 << i)) == 0) continue; for (U32 start = startXFIndex; start < mXFIndexCount; start++) { if (mXFIndexBuffer[start] == GL_TRIANGLE_FAN) { start++; U32 count = mXFIndexBuffer[start]; U32 triCount = count - 2; LightTriangle* lightTris = (LightTriangle*)FrameAllocator::alloc(sizeof(LightTriangle) * triCount); U32 j; for (j = 0; j < (triCount-1); j++) lightTris[j].next = &lightTris[j+1]; lightTris[triCount-1].next = sgCurrLightTris; sgCurrLightTris = lightTris; // Copy out tri data here... for (j = 0; j < triCount; j++) { lightTris[j].point1 = mXFVertices[mXFIndexBuffer[start + 1 + 0]]; lightTris[j].point2 = mXFVertices[mXFIndexBuffer[start + 1 + j + 1]]; lightTris[j].point3 = mXFVertices[mXFIndexBuffer[start + 1 + j + 2]]; lightTris[j].chunkTexture = chunk->chunkTexture; buildLightTri(&lightTris[j], &mTerrainLights[i]); } start += count; } else { AssertFatal(mXFIndexBuffer[start] == GL_TRIANGLES, "Error, bad start code!"); start++; U32 count = mXFIndexBuffer[start]; AssertFatal((count / 3) * 3 == count, "Error, vertex count not divisible by 3!"); U32 triCount = count/3; LightTriangle* lightTris = (LightTriangle*)FrameAllocator::alloc(sizeof(LightTriangle) * triCount); U32 j; for (j = 0; j < (triCount-1); j++) lightTris[j].next = &lightTris[j+1]; lightTris[triCount-1].next = sgCurrLightTris; sgCurrLightTris = lightTris; // Copy out tri data here... for (j = 0; j < triCount; j++) { lightTris[j].point1 = mXFVertices[mXFIndexBuffer[start + 1 + (j*3) + 0]]; lightTris[j].point2 = mXFVertices[mXFIndexBuffer[start + 1 + (j*3) + 1]]; lightTris[j].point3 = mXFVertices[mXFIndexBuffer[start + 1 + (j*3) + 2]]; lightTris[j].chunkTexture = chunk->chunkTexture; buildLightTri(&lightTris[j], &mTerrainLights[i]); } start += count; } } } } } /*void TerrainRender::drawTriFan(U32 vCount, U32 *indexBuffer) { glBegin(GL_LINES); U32 cur = vCount - 1; for(U32 i = 1; i < vCount; i++) { glArrayElement(indexBuffer[0]); glArrayElement(indexBuffer[cur]); glArrayElement(indexBuffer[cur]); glArrayElement(indexBuffer[i]); cur = i; } glEnd(); }*/ void TerrainRender::renderXFCache() { U32 count = 0; while (count < mXFIndexCount) { U32 mode = mXFIndexBuffer[count]; U32 vertexCount = mXFIndexBuffer[count + 1]; glDrawElements(mode, vertexCount, GL_UNSIGNED_SHORT, mXFIndexBuffer + count + 2); count += vertexCount + 2; } } void doTexGens(Point4F &s, Point4F &t) { EdgePoint *pt = mXFVertices; EdgePoint *last = pt + mXFPointCount; for(;pt < last; pt++) { pt->haze = pt->x * s.x + pt->y * s.y + pt->z * s.z + s.w; pt->distance = pt->x * t.x + pt->y * t.y + pt->z * t.z + t.w; } } #ifdef TORQUE_OS_WIN32 void TerrainRender::renderD3DBumps(Point2F bumpTextureOffset) { PROFILE_START(TerrainRenderBumpsD3D); glColor3f(1.0f,1.0f,1.0f); //First pass - normal bump map, no blending, no offset doTexGens(bumpTexGenS, bumpTexGenT); glBindTexture(GL_TEXTURE_2D, mCurrentBlock->mBumpTextureHandle.getGLName()); renderXFCache(); //Second pass - inverted bump map, additive blending, offset Point4F bumpTexGenOS = bumpTexGenS; Point4F bumpTexGenOT = bumpTexGenT; bumpTexGenOS.w += bumpTextureOffset.x; bumpTexGenOT.w += bumpTextureOffset.y; doTexGens(bumpTexGenOS, bumpTexGenOT); glBindTexture(GL_TEXTURE_2D, mCurrentBlock->mInvertedBumpTextureHandle.getGLName()); glBlendFunc(GL_ONE, GL_ONE); glEnable(GL_BLEND); renderXFCache(); //Setup blending for the terrain pass glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR); PROFILE_END(); } #endif void TerrainRender::renderGLBumps(Point2F bumpTextureOffset, U32 hazeName) { PROFILE_START(TerrainRenderBumpsGL); //have to set color to white or bumps turn up black on the terrain glColor3f(1.0f,1.0f,1.0f); doTexGens(bumpTexGenS, bumpTexGenT); //Set up TMU #0 //Normal bump texture glBindTexture(GL_TEXTURE_2D, mCurrentBlock->mBumpTextureHandle.getGLName()); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE); //Set up TMU #1 //Inverted bump texture //Offset the texture matrix glActiveTextureARB(GL_TEXTURE1_ARB); glClientActiveTextureARB(GL_TEXTURE1_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, mCurrentBlock->mInvertedBumpTextureHandle.getGLName()); glTexCoordPointer(2, GL_FLOAT, sizeof(EdgePoint), &mXFVertices->haze); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_ADD); glMatrixMode(GL_TEXTURE); glPushMatrix(); glTranslatef(bumpTextureOffset.x, bumpTextureOffset.y, 0.0f); //Render! renderXFCache(); //Restore TMU #1 glPopMatrix(); glMatrixMode(GL_MODELVIEW); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); glBindTexture(GL_TEXTURE_2D, hazeName); glTexCoordPointer(2, GL_FLOAT, sizeof(EdgePoint), &mXFVertices->fogRed); //Restore TMU #0 glActiveTextureARB(GL_TEXTURE0_ARB); glClientActiveTextureARB(GL_TEXTURE0_ARB); //Set up for the bump mapping application glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glEnable(GL_BLEND); //Do the rest in the function PROFILE_END(); } void TerrainRender::renderBlock(TerrainBlock *block, SceneState *state) { PROFILE_START(TerrainRender); // verify lighting type is the same... static bool lastblended = false; bool blended = LightManager::sgAllowBlendedTerrainDynamicLighting(); if(lastblended != blended) { block->triggerLightmapReload(); block->flushCache(); lastblended = blended; } PROFILE_START(TerrainRenderSetup); dglSetRenderPrimType(1); U32 storedWaterMark = FrameAllocator::getWaterMark(); if (sgTextureFreeListPrimed == false) { sgTextureFreeListPrimed = true; AssertFatal(mTextureFreeList.size() == 0, "Error, unprimed free list should always be size 0"); mTextureFreeList.setSize(sgFreeListPrimeCount); for (U32 i = 0; i < sgFreeListPrimeCount; i++) { constructInPlace(&mTextureFreeList[i]); mTextureFreeList[i] = TextureHandle((const char*)NULL, mBlendBitmap, TerrainTexture, true); } } mFrameIndex++; mSceneState = state; mFarDistance = state->getVisibleDistance(); dglGetModelview(&mCameraToObject); mCameraToObject.inverse(); mCameraToObject.getColumn(3, &mCamPos); mFogColor = state->getFogColor(); TextureHandle hazeTexture = gClientSceneGraph->getFogTexture(); mCurrentBlock = block; Point4F detTexGenS(1.0f, 0.0f, 0.0f, 0.0f); Point4F detTexGenT(0.0f, 1.0f, 0.0f, 0.0f); if (mEnableTerrainDetails && mCurrentBlock->mDetailTextureHandle.getGLName() != 0) { detTexGenS.x *= 62.0f / mCurrentBlock->mDetailTextureHandle.getWidth(); detTexGenT.y *= 62.0f / mCurrentBlock->mDetailTextureHandle.getHeight(); detTexGenS.w = -(S32) (mCamPos.x*detTexGenS.x); detTexGenT.w = -(S32) (mCamPos.y*detTexGenT.y); } // CW - stuff with bump maps //calculate the texture offset if (mEnableTerrainEmbossBumps && mCurrentBlock->mBumpTextureHandle.getGLName() != 0) { PROFILE_START(TerrainRenderBumpBuild); //There should be a better way to do this, really... #ifdef TORQUE_OS_WIN32 if (dStrcmp(Con::getVariable( "$pref::Video::displayDevice" ), "OpenGL") == 0) mRenderGL = true; else mRenderGL = false; #endif //note: don't multiply light by inverse modelview matrix //because it is already in object space (or world or anything, really) //The first light in the light manager is always the sun. //This is also how players are lit, so if this is broken, that is broken as well. Point3F sTangent,tTangent; LightInfo *sun = gClientSceneGraph->getLightManager()->sgGetSpecialLight(LightManager::sgSunLightType); VectorF sunVector = -sun->mDirection; //find s and t tangents F32 pHeight[4]; for (int i = 0; i < 4; i++) { int x = i == 0 || i == 3 ? 0 : 255; int y = i == 1 || i == 2 ? 0 : 255; block->getHeight(Point2F(x,y), &pHeight[i]); } S32 pSquareSize = block->getSquareSize(); sTangent.x = pSquareSize; sTangent.y = pHeight[3] - pHeight[0]; sTangent.z = 0; tTangent.x = 0; tTangent.y = pHeight[1] - pHeight[0]; tTangent.z = pSquareSize; sTangent.normalize(); tTangent.normalize(); // The above is needed for both emboss and dot3 bump mapping // techniques. However, the below 2 lines are emboss-specific. // So, dot3 bump mapping would not be too difficult because // I have already computed most of the necessary info. bumpTextureOffset.x = mDot(sTangent, sunVector) * mCurrentBlock->mBumpOffset; bumpTextureOffset.y = mDot(tTangent, sunVector) * mCurrentBlock->mBumpOffset; PROFILE_END(); } // CW - end bump map stuff mSquareSize = block->getSquareSize(); // mNewGenTextureCount = 0; // compute pixelError if(mScreenError >= 0.001f) mPixelError = 1.0f / dglProjectRadius(mScreenError, 1); else mPixelError = 0.000001f; buildClippingPlanes(state->mFlipCull); buildLightArray(); F32 worldToScreenScale = dglProjectRadius(1,1); F32 zeroDetailDistance = (mSquareSize * worldToScreenScale) / (1 << 6) - (mSquareSize >> 1); F32 zeroBumpDistance = (mSquareSize * worldToScreenScale) / (1 << mCurrentBlock->mZeroBumpScale) - (mSquareSize >> 1); F32 blockSize = mSquareSize * TerrainBlock::BlockSquareWidth; S32 xStart; S32 xEnd; S32 yStart; S32 yEnd; if(mCurrentBlock->mTile) { xStart = (S32)mFloor( (mCamPos.x - mFarDistance) / blockSize ); xEnd = (S32)mCeil ( (mCamPos.x + mFarDistance) / blockSize ); yStart = (S32)mFloor( (mCamPos.y - mFarDistance) / blockSize ); yEnd = (S32)mCeil ( (mCamPos.y + mFarDistance) / blockSize ); } else { xStart = 0; xEnd = 1; yStart = 0; yEnd = 1; } S32 xExt = (S32)(xEnd - xStart); S32 yExt = (S32)(yEnd - yStart); PROFILE_END(); PROFILE_START(TerrainRenderRecurse); F32 height = fixedToFloat(block->getHeight(0,0)); EdgeParent **bottomEdges = (EdgeParent **) FrameAllocator::alloc(sizeof(ChunkScanEdge *) * xExt); TerrainRender::allocRenderEdges(xExt, bottomEdges, false); ChunkCornerPoint *prevCorner = TerrainRender::allocInitialPoint(Point3F(xStart * blockSize, yStart * blockSize, height)); mTerrainOffset.set(xStart * TerrainBlock::BlockSquareWidth, yStart * TerrainBlock::BlockSquareWidth); for(S32 x = 0; x < xExt; x++) { bottomEdges[x]->p1 = prevCorner; prevCorner = TerrainRender::allocInitialPoint(Point3F((xStart + x ) * blockSize, yStart * blockSize, height)); bottomEdges[x]->p2 = prevCorner; } for(S32 y = 0; y < yExt; y++) { // allocate the left edge: EdgeParent *left; TerrainRender::allocRenderEdges(1, &left, false); left->p1 = TerrainRender::allocInitialPoint(Point3F(xStart * blockSize, (yStart + y + 1) * blockSize, height)); left->p2 = bottomEdges[0]->p1; for(S32 x = 0; x < xExt; x++) { EdgeParent *right; TerrainRender::allocRenderEdges(1, &right, false); right->p1 = TerrainRender::allocInitialPoint(Point3F((xStart + x + 1) * blockSize, (yStart + y + 1) * blockSize, height)); right->p2 = bottomEdges[x]->p2; EdgeParent *top; TerrainRender::allocRenderEdges(1, &top, false); top->p1 = left->p1; top->p2 = right->p1; mBlockOffset.set(x << TerrainBlock::BlockShift, y << TerrainBlock::BlockShift); mBlockPos.set((xStart + x) * blockSize, (yStart + y) * blockSize); TerrainRender::processCurrentBlock(state, top, right, bottomEdges[x], left); left = right; bottomEdges[x] = top; } } U32 slop = 0; AllocatedTexture *fwalk = mTextureFreeListHead.next; while(fwalk != &mTextureFreeListTail && slop < mTextureSlopSize) { fwalk = fwalk->next; slop++; } while(fwalk != &mTextureFreeListTail) { AllocatedTexture *next = fwalk->next; fwalk->unlink(); S32 x = (fwalk->x & TerrainBlock::BlockMask) >> fwalk->level; S32 y = (fwalk->y & TerrainBlock::BlockMask) >> fwalk->level; mTextureGridPtr[fwalk->level - 2][x + (y << (8 - fwalk->level))] = NULL; freeTerrTexture(fwalk); fwalk = next; } PROFILE_END(); PROFILE_START(TerrainRenderEmit); bool lockArrays = dglDoesSupportCompiledVertexArray() && dglDoesSupportARBMultitexture(); bool vertexBuffer = dglDoesSupportVertexBuffer() && (block->mVertexBuffer != -1); bool blendedlighting = LightManager::sgAllowBlendedTerrainDynamicLighting(); glFrontFace(GL_CW); glEnable(GL_CULL_FACE); glEnableClientState(GL_VERTEX_ARRAY); if(!vertexBuffer) mXFVertices = (EdgePoint *) FrameAllocator::alloc(sizeof(EdgePoint) * VertexBufferSize); //CW - D3D goes crazy if we stick our bump colors in the EdgePoint structure, so make a new one ColorI *bumpColors = (ColorI*)FrameAllocator::alloc(sizeof(ColorI) * VertexBufferSize); glVertexPointer(3, GL_FLOAT, sizeof(EdgePoint), mXFVertices); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ZERO); // see if we should setup hazing on the second TMU // if not, we'll have to 2-pass the terrain... // and that ain't gonna be fast no matter how ya slice it. // Hazing if(blendedlighting) { // set this up for bump and dynamic... glClientActiveTextureARB(GL_TEXTURE1_ARB); glActiveTextureARB(GL_TEXTURE1_ARB); glEnable(GL_TEXTURE_2D); // set this up for fog... // disable so not on in all passes... // base pass must turn fog on... glClientActiveTextureARB(GL_TEXTURE2_ARB); glActiveTextureARB(GL_TEXTURE2_ARB); glDisable(GL_TEXTURE_2D); } else { glClientActiveTextureARB(GL_TEXTURE1_ARB); glActiveTextureARB(GL_TEXTURE1_ARB); glEnable(GL_TEXTURE_2D); } glBindTexture(GL_TEXTURE_2D, hazeTexture.getGLName()); //handle glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(2, GL_FLOAT, sizeof(EdgePoint), &mXFVertices->fogRed); // Base textures glClientActiveTextureARB(GL_TEXTURE0_ARB); glActiveTextureARB(GL_TEXTURE0_ARB); glEnable(GL_TEXTURE_2D); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(2, GL_FLOAT, sizeof(EdgePoint), &mXFVertices->haze); glDisable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); mXFIndex = 0; sgCurrLightTris = NULL; AllocatedTexture *walk; mXFIndexBuffer = (U16 *) FrameAllocator::alloc(sizeof(U16) * 64 * 64 * 4); Vector deleteList; while((walk = mTextureFrameListHead.next) != &mTextureFrameListTail) { walk->unlink(); if(walk->level != 6) walk->linkAfter(&mTextureFreeListHead); else walk->linkAfter(&mTextureFreeBigListHead); mStaticTextureCount++; if(!walk->handle) buildBlendMap(walk); AllocatedTexture *step = walk; while(step) { // loop through the list and draw all the squares: F32 invLevel = 1.0f / F32(mSquareSize << step->level); sgTexGenS.set(invLevel, 0.0f, 0.0f, -(step->x >> step->level)); sgTexGenT.set(0.0f, invLevel, 0.0f, -(step->y >> step->level)); // Bump map texture coordinate "calculation" (hack) for ATi cards -CW // Why don't Point4F's have a multiply operator? const F32 scale = 32.0f / mCurrentBlock->mBumpScale; bumpTexGenS.x = sgTexGenS.x * scale; bumpTexGenS.y = sgTexGenS.y * scale; bumpTexGenS.z = sgTexGenS.z * scale; bumpTexGenS.w = sgTexGenS.w * scale; bumpTexGenT.x = sgTexGenT.x * scale; bumpTexGenT.y = sgTexGenT.y * scale; bumpTexGenT.z = sgTexGenT.z * scale; bumpTexGenT.w = sgTexGenT.w * scale; // End -CW while(step->list) { mXFPointCount = 0; mXFIndex++; mXFIndexPtr = mXFIndexBuffer; mXFIndexCount = 0; PROFILE_START(TerrainRenderLock); if(vertexBuffer) mXFVertices = (EdgePoint *) glLockVertexBufferEXT(block->mVertexBuffer, VertexBufferSize); PROFILE_END(); PROFILE_START(TerrainRenderBuild); bool renderDetails = false; // CW - stuff with bump maps bool renderBumps = false; // CW - end bump map stuff EmitChunk *sq; for(sq = step->list; sq && mXFPointCount < maxTerrPoints; sq = sq->next) { if(mRenderingCommander) { renderChunkCommander(sq); } else { renderDetails |= sq->renderDetails && mEnableTerrainDetails; // CW - stuff with bump maps renderBumps |= sq->renderBumps && mEnableTerrainEmbossBumps; // CW - end bump map stuff renderChunkOutline(sq); } AssertFatal(mXFPointCount <= VertexBufferSize, "Invalid point count."); AssertFatal(mXFIndexCount <= 64 * 64 * 4, "Index count sucks."); } step->list = sq; if(mXFIndexCount && renderDetails && mCurrentBlock->mDetailTextureHandle.getGLName() != 0) { for(U32 a = 0; a < mXFPointCount; a++) { EdgePoint& rPoint = mXFVertices[a]; F32 distFactor = (zeroDetailDistance - rPoint.distance) / zeroDetailDistance; if(distFactor < 0.0f) distFactor = 0.0f; else if(distFactor > 1.0f) distFactor = 1.0f; F32 factor = state->getHazeAndFog(rPoint.distance, rPoint.z); U8 c = U8((((1.0f - factor) * distFactor) * 255.0f) + 0.5f); rPoint.detailColor.red = c; rPoint.detailColor.green = c; rPoint.detailColor.blue = c; rPoint.detailColor.alpha = c; } } // for bumps, it shouldn't fade out purely linearly // instead, only start linear fade out at 3/4 out if(mXFIndexCount && renderBumps && mCurrentBlock->mBumpTextureHandle.getGLName() != 0) { for(U32 a = 0; a < mXFPointCount; a++) { EdgePoint& rPoint = mXFVertices[a]; F32 distFactor = rPoint.distance / zeroBumpDistance; if(distFactor < 0.0f) distFactor = 0.0f; else if(distFactor > 1.0f) distFactor = 1.0f; U8 c = 0; if(distFactor > 0.75f) c = U8( (distFactor - 0.75f) * 4.0f * 255.0f + 0.5f ); else c = 0; bumpColors[a].red = 127; bumpColors[a].green = 127; bumpColors[a].blue = 127; bumpColors[a].alpha = c; } } PROFILE_END(); PROFILE_START(TerrainRenderUnlock); if(vertexBuffer) glUnlockVertexBufferEXT(block->mVertexBuffer); PROFILE_END(); // Render the bump maps! -CW glDisable(GL_BLEND); if(renderBumps && mCurrentBlock->mBumpTextureHandle.getGLName() != 0 && mCurrentBlock->mInvertedBumpTextureHandle.getGLName() != 0 && mEnableTerrainEmbossBumps) { #ifdef TORQUE_OS_WIN32 if(mRenderGL) renderGLBumps(bumpTextureOffset, hazeTexture.getGLName()); else // D3D sucks... renderD3DBumps(bumpTextureOffset); #else renderGLBumps(bumpTextureOffset, hazeTexture.getGLName()); #endif //add a little fade-out glEnable(GL_BLEND); glDisable(GL_TEXTURE_2D); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ColorI), bumpColors); glActiveTextureARB(GL_TEXTURE1_ARB); glDisable(GL_TEXTURE_2D); renderXFCache(); glEnable(GL_TEXTURE_2D); glActiveTextureARB(GL_TEXTURE0_ARB); glEnable(GL_TEXTURE_2D); glDisableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR); glColor3f(1.0f,1.0f,1.0f); } // CW - end bump map render if(mXFIndexCount) { if(blendedlighting) { glActiveTextureARB(GL_TEXTURE1_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, mCurrentBlock->lightMapTexture.getGLName()); LightManager::sgSetupExposureRendering(); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); F32 offset = 1.0f / F32(mCurrentBlock->getSquareSize() * TerrainBlock::BlockSize); glTexGenfv(GL_S, GL_OBJECT_PLANE, Point4F(offset, 0.0f, 0.0f, 0.0f)); glTexGenfv(GL_T, GL_OBJECT_PLANE, Point4F(0.0f, offset, 0.0f, 0.0f)); glActiveTextureARB(GL_TEXTURE2_ARB); glEnable(GL_TEXTURE_2D); glActiveTextureARB(GL_TEXTURE0_ARB); } PROFILE_START(TerrainRenderBind); glBindTexture(GL_TEXTURE_2D, walk->handle.getGLName()); doTexGens(sgTexGenS, sgTexGenT); PROFILE_END(); PROFILE_START(TerrainRenderSetVertexBuffer); if(vertexBuffer) glSetVertexBufferEXT(block->mVertexBuffer); if(lockArrays) glLockArraysEXT(0, mXFPointCount); // lock the array PROFILE_END(); PROFILE_START(TerrainRenderXFRender); renderXFCache(); PROFILE_END(); if(blendedlighting) { glActiveTextureARB(GL_TEXTURE1_ARB); glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); LightManager::sgResetExposureRendering(); glActiveTextureARB(GL_TEXTURE2_ARB); glDisable(GL_TEXTURE_2D); glActiveTextureARB(GL_TEXTURE0_ARB); } if(renderDetails && mCurrentBlock->mDetailTextureHandle.getGLName() != 0) { PROFILE_START(TerrainRenderDetails); glEnableClientState(GL_COLOR_ARRAY); glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(EdgePoint), &mXFVertices->detailColor); glBindTexture(GL_TEXTURE_2D, mCurrentBlock->mDetailTextureHandle.getGLName()); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); if(vertexBuffer) mXFVertices = (EdgePoint *) glLockVertexBufferEXT(block->mVertexBuffer, VertexBufferSize); doTexGens(detTexGenS, detTexGenT); if(vertexBuffer) glUnlockVertexBufferEXT(block->mVertexBuffer); glEnable(GL_BLEND); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE); const F32 gray[4] = {0.5f, 0.5f, 0.5f, 0.5f}; glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, gray); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_PRIMARY_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_ALPHA); glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR); glActiveTextureARB(GL_TEXTURE1_ARB); glDisable(GL_TEXTURE_2D); renderXFCache(); glEnable(GL_TEXTURE_2D); glActiveTextureARB(GL_TEXTURE0_ARB); glDisable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glDisableClientState(GL_COLOR_ARRAY); PROFILE_END(); } if(lockArrays) glUnlockArraysEXT(); } } AllocatedTexture *nextStep = step->nextLink; step->list = 0; step->nextLink = 0; if(step != walk) deleteList.push_back(step); step = nextStep; } } glActiveTextureARB(GL_TEXTURE1_ARB); glClientActiveTextureARB(GL_TEXTURE1_ARB); glDisable(GL_TEXTURE_2D); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glActiveTextureARB(GL_TEXTURE0_ARB); glClientActiveTextureARB(GL_TEXTURE0_ARB); glDisable(GL_TEXTURE_2D); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); if(mEnableTerrainDynLights && sgCurrLightTris) { glEnable(GL_TEXTURE_2D); glDepthMask(GL_FALSE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glBindTexture(GL_TEXTURE_2D, mCurrentBlock->mDynLightTexture.getGLName()); if(blendedlighting) { glActiveTextureARB(GL_TEXTURE1_ARB); glEnable(GL_TEXTURE_2D); LightManager::sgSetupExposureRendering(); // terrain texture alpha is garbage... glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); } else { LightManager::sgSetupExposureRendering(); } LightTriangle* walkLT = sgCurrLightTris; while(walkLT) { if(walkLT->flags) { if(blendedlighting) { F32 invLevel = 1.0f / F32(mSquareSize << walkLT->chunkTexture->level); sgTexGenS.set(invLevel, 0.0f, 0.0f, -(walkLT->chunkTexture->x >> walkLT->chunkTexture->level)); sgTexGenT.set(0.0f, invLevel, 0.0f, -(walkLT->chunkTexture->y >> walkLT->chunkTexture->level)); glBindTexture(GL_TEXTURE_2D, walkLT->chunkTexture->handle.getGLName()); } glBegin(GL_TRIANGLES); glColor4fv(walkLT->color); glMultiTexCoord2fARB(GL_TEXTURE0_ARB, walkLT->texco1.x, walkLT->texco1.y); if(blendedlighting) { glMultiTexCoord2fARB(GL_TEXTURE1_ARB, custom_dot(sgTexGenS, walkLT->point1), custom_dot(sgTexGenT, walkLT->point1)); } glVertex3fv(walkLT->point1); glMultiTexCoord2fARB(GL_TEXTURE0_ARB, walkLT->texco2.x, walkLT->texco2.y); if(blendedlighting) { glMultiTexCoord2fARB(GL_TEXTURE1_ARB, custom_dot(sgTexGenS, walkLT->point2), custom_dot(sgTexGenT, walkLT->point2)); } glVertex3fv(walkLT->point2); glMultiTexCoord2fARB(GL_TEXTURE0_ARB, walkLT->texco3.x, walkLT->texco3.y); if(blendedlighting) { glMultiTexCoord2fARB(GL_TEXTURE1_ARB, custom_dot(sgTexGenS, walkLT->point3), custom_dot(sgTexGenT, walkLT->point3)); } glVertex3fv(walkLT->point3); glEnd(); } walkLT = walkLT->next; } if(blendedlighting) { LightManager::sgResetExposureRendering(); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glDisable(GL_TEXTURE_2D); glActiveTextureARB(GL_TEXTURE0_ARB); } else { LightManager::sgResetExposureRendering(); } glDisable(GL_TEXTURE_2D); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glDepthMask(GL_TRUE); glDisable(GL_BLEND); glBlendFunc(GL_ONE, GL_ZERO); } for(U32 i=0; iunlink(); delete tex; } while( (tex = mTextureFreeBigListHead.next) != &mTextureFreeBigListTail) { tex->unlink(); delete tex; } } void TerrainRender::flushCacheRect(RectI bRect) { bRect.inset(-1,-1); for(S32 i = 0; i < AllocatedTextureCount; i++) { AllocatedTexture *tex = mTextureGrid[i]; if(!tex) continue; RectI texRect(tex->x, tex->y, 1 << tex->level, 1 << tex->level); if(texRect.intersect(bRect)) { mTextureGrid[i] = NULL; tex->unlink(); freeTerrTexture(tex); } } } //--------------------------------------------------------------- void TerrainRender::freeTerrTexture(AllocatedTexture *texture) { if(texture->handle) { mTextureFreeList.increment(); constructInPlace(&mTextureFreeList.last()); mTextureFreeList.last() = texture->handle; } delete texture; } //--------------------------------------------------------------- void TerrainRender::allocTerrTexture(Point2I pos, U32 level, U32 mipLevel, bool vis, F32 distance) { vis; S32 blockX = mBlockOffset.x + mTerrainOffset.x; S32 blockY = mBlockOffset.y + mTerrainOffset.y; S32 x = pos.x + blockX; S32 y = pos.y + blockY; S32 px = (pos.x & TerrainBlock::BlockMask) >> level; S32 py = (pos.y & TerrainBlock::BlockMask) >> level; AssertFatal(level >= 2 && level <= 6, "Invalid level"); AllocatedTexture *cur = mTextureGridPtr[level - 2][px + (py << (8 - level))]; if(!cur) { cur = new AllocatedTexture; cur->list = NULL; cur->mipLevel = mipLevel; cur->x = x; cur->y = y; cur->level = level; cur->nextLink = NULL; } else { AssertFatal(cur->level == level, "Invalid block for this level."); if(cur->list && (cur->x != x || cur->y != y)) { // see if the texture is already in the list... AllocatedTexture *walk = cur->nextLink; while(walk && walk->x != x && walk->y != y) walk = walk->nextLink; if(walk) { mCurrentTexture = walk; return; } AllocatedTexture *tail = new AllocatedTexture; tail->list = NULL; tail->x = x; tail->y = y; tail->level = level; tail->nextLink = cur->nextLink; tail->distance = distance; cur->nextLink = tail; mCurrentTexture = tail; return; } else { cur->x = x; cur->y = y; cur->unlink(); } } cur->linkAfter(&mTextureFrameListHead); cur->distance = distance; mCurrentTexture = cur; mTextureGridPtr[level - 2][px + (py << (8 - level))] = cur; } static inline void fixcolors(GBitmap* bmp) { #if defined(TORQUE_OS_MAC) && defined(TORQUE_LITTLE_ENDIAN) U32 _bpp = bmp->bytesPerPixel; for(int miplevel = 0; miplevel < bmp->getNumMipLevels(); miplevel++) { U16* pixels = (U16*)bmp->getWritableBits(miplevel); U32 numpixels = bmp->getWidth(miplevel) * bmp->getHeight(miplevel); for( int i = 0; i < numpixels; i++ ) { register const U16 c = *(pixels + i); // from: // *sourceFormat = GL_RGBA; // *byteFormat = GL_UNSIGNED_SHORT_5_5_5_1; // to: // *sourceFormat = GL_BGRA_EXT; // *byteFormat = GL_UNSIGNED_SHORT_1_5_5_5_REV; //static U16 __color = 0xF800; // rrrrr ggggg bbbbb a // a bbbbb ggggg rrrrr register U16 red = ( c & 0xf800 ) >> 11; register U16 green = ( c & 0x07C0 ) >> 6; register U16 blue = ( c & 0x003e ) >> 1; register U16 alpha = ( c & 0x0001 ); *(pixels + i) = alpha << 15 | red << 10 | green << 5 | blue; } } #endif } void TerrainRender::buildBlendMap(AllocatedTexture *tex) { PROFILE_START(TerrainRenderBuildBlendMap); GBitmap *bmp = mBlendBitmap; S32 x = tex->x; S32 y = tex->y; S32 level = tex->level; AssertFatal(mCurrentBlock->lightMap->getFormat() == GBitmap::RGB5551, "Error, lightmap must be 5551"); AssertFatal(bmp->getFormat() == GBitmap::RGB5551, "Error, destination must be 565"); AssertFatal(bmp->getWidth() == TerrainTextureSize && bmp->getHeight() == TerrainTextureSize, avar("Error, bitmaps must be %d high and wide for the terrain", TerrainTextureSize)); AssertFatal(mCurrentBlock->lightMap->getWidth() == 512 && mCurrentBlock->lightMap->getHeight() == 512, "Fast blender requires a 512 lightmap!"); U16* mips[TerrainTextureMipLevel + 1]; for (U32 i = 0; i < bmp->getNumMipLevels(); i++) mips[i] = (U16*)bmp->getWritableBits(i); GBitmap *lightmap = NULL; if(LightManager::sgAllowBlendedTerrainDynamicLighting()) { if(!mCurrentBlock->whiteMap) { #define SG_GRAY 0x7BDF// 01111 01111 01111 1 #define SG_REDMASK 0xF800// 11111 00000 00000 0 #define SG_BLUEMASK 0x3E // 00000 00000 11111 0 #define SG_GREENMASK 0x7C0 // 00000 11111 00000 0 #define SG_ALPHAMASK 0x1 // 00000 00000 00000 1 mCurrentBlock->whiteMap = new GBitmap( TerrainBlock::LightmapSize, TerrainBlock::LightmapSize, false, GBitmap::RGB5551); U16 color = SG_GRAY; U32 count = mCurrentBlock->whiteMap->byteSize / 2; U16 *bits = (U16 *)mCurrentBlock->whiteMap->getWritableBits(); for(U32 i=0; ilightMap->getWritableBits(); U16 *lmbits = (U16 *)lm->getWritableBits(); for(U32 i=0; i> 11; U16 blue = (color & SG_BLUEMASK) >> 1; U16 green = (color & SG_GREENMASK) >> 6; U16 alpha = (color & SG_ALPHAMASK); #if defined(TORQUE_OS_MAC) // a bbbbb ggggg rrrrr lmbits[i] = (alpha << 15) | (blue << 10) | (green << 5) | red; #else // bbbbb ggggg rrrrr a lmbits[i] = (blue << 11) | (green << 6) | (red << 1) | alpha; #endif } mCurrentBlock->lightMapTexture = TextureHandle(NULL, lm); } lightmap = mCurrentBlock->whiteMap; } else { lightmap = mCurrentBlock->lightMap; } mCurrentBlock->mBlender->blend(x, y, level, (const U16*)lightmap->getBits(), mips); fixcolors(bmp); mDynamicTextureCount++; if(mTextureFreeList.size()) { tex->handle = mTextureFreeList.last(); mTextureFreeList.last() = NULL; mTextureFreeList.decrement(); tex->handle.refresh(mBlendBitmap); } else { tex->handle = TextureHandle((const char*)NULL, mBlendBitmap, TerrainTexture, true); } PROFILE_END(); }