//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "sim/decalManager.h" #include "dgl/dgl.h" #include "sceneGraph/sceneGraph.h" #include "sceneGraph/sceneState.h" #include "ts/tsShapeInstance.h" #include "core/bitStream.h" #include "console/consoleTypes.h" bool DecalManager::smDecalsOn = true; bool DecalManager::sgThisIsSelfIlluminated = false; bool DecalManager::sgLastWasSelfIlluminated = false; const U32 DecalManager::csmFreePoolBlockSize = 256; U32 DecalManager::smMaxNumDecals = 256; U32 DecalManager::smDecalTimeout = 5000; DecalManager* gDecalManager = NULL; IMPLEMENT_CONOBJECT(DecalManager); IMPLEMENT_CO_DATABLOCK_V1(DecalData); namespace { int QSORT_CALLBACK cmpDecalInstance(const void* p1, const void* p2) { const DecalInstance** pd1 = (const DecalInstance**)p1; const DecalInstance** pd2 = (const DecalInstance**)p2; return int(((char *)(*pd1)->decalData) - ((char *)(*pd2)->decalData)); } } // namespace {} //-------------------------------------------------------------------------- DecalData::DecalData() { sizeX = 1; sizeY = 1; textureName = ""; selfIlluminated = false; lifeSpan = DecalManager::smDecalTimeout; } DecalData::~DecalData() { if(gDecalManager) gDecalManager->dataDeleted(this); } void DecalData::packData(BitStream* stream) { Parent::packData(stream); stream->write(sizeX); stream->write(sizeY); stream->writeString(textureName); stream->write(selfIlluminated); stream->write(lifeSpan); } void DecalData::unpackData(BitStream* stream) { Parent::unpackData(stream); stream->read(&sizeX); stream->read(&sizeY); textureName = stream->readSTString(); stream->read(&selfIlluminated); stream->read(&lifeSpan); } bool DecalData::preload(bool server, char errorBuffer[256]) { if (Parent::preload(server, errorBuffer) == false) return false; if (sizeX < 0.0) { Con::warnf("DecalData::preload: sizeX < 0"); sizeX = 0; } if (sizeY < 0.0) { Con::warnf("DecalData::preload: sizeX < 0"); sizeY = 0; } if (textureName == NULL || textureName[0] == '\0') { Con::errorf("No texture name for decal!"); return false; } if (!server) { textureHandle = TextureHandle(textureName, MeshTexture); if (textureHandle.getGLName() == 0) { Con::errorf("Unable to load texture: %s for decal!", textureName); return false; } } return true; } IMPLEMENT_CONSOLETYPE(DecalData) IMPLEMENT_SETDATATYPE(DecalData) IMPLEMENT_GETDATATYPE(DecalData) void DecalData::initPersistFields() { addField("sizeX", TypeF32, Offset(sizeX, DecalData)); addField("sizeY", TypeF32, Offset(sizeY, DecalData)); addField("textureName", TypeFilename, Offset(textureName, DecalData)); addField("SelfIlluminated", TypeBool, Offset(selfIlluminated, DecalData)); addField("LifeSpan", TypeS32, Offset(lifeSpan, DecalData)); } DecalManager::DecalManager() { mTypeMask |= DecalManagerObjectType; mObjBox.min.set(-1e7, -1e7, -1e7); mObjBox.max.set( 1e7, 1e7, 1e7); mWorldBox.min.set(-1e7, -1e7, -1e7); mWorldBox.max.set( 1e7, 1e7, 1e7); mFreePool = NULL; VECTOR_SET_ASSOCIATION(mDecalQueue); VECTOR_SET_ASSOCIATION(mFreePoolBlocks); } DecalManager::~DecalManager() { mFreePool = NULL; for (S32 i = 0; i < mFreePoolBlocks.size(); i++) { delete [] mFreePoolBlocks[i]; } mDecalQueue.clear(); } DecalInstance* DecalManager::allocateDecalInstance() { if (mFreePool == NULL) { // Allocate a new block of decals mFreePoolBlocks.push_back(new DecalInstance[csmFreePoolBlockSize]); // Init them onto the free pool chain DecalInstance* pNewInstances = mFreePoolBlocks.last(); for (U32 i = 0; i < csmFreePoolBlockSize - 1; i++) pNewInstances[i].next = &pNewInstances[i + 1]; pNewInstances[csmFreePoolBlockSize - 1].next = NULL; mFreePool = pNewInstances; } AssertFatal(mFreePool != NULL, "Error, should always have a free pool available here!"); DecalInstance* pRet = mFreePool; mFreePool = pRet->next; pRet->next = NULL; return pRet; } void DecalManager::freeDecalInstance(DecalInstance* trash) { AssertFatal(trash != NULL, "Error, no trash pointer to free!"); trash->next = mFreePool; mFreePool = trash; } void DecalManager::dataDeleted(DecalData *data) { for(S32 i = mDecalQueue.size() - 1; i >= 0; i--) { DecalInstance *inst = mDecalQueue[i]; if(inst->decalData == data) { freeDecalInstance(inst); mDecalQueue.erase(U32(i)); } } } void DecalManager::consoleInit() { Con::addVariable("$pref::decalsOn", TypeBool, &smDecalsOn); Con::addVariable("$pref::Decal::maxNumDecals", TypeS32, &smMaxNumDecals); Con::addVariable("$pref::Decal::decalTimeout", TypeS32, &smDecalTimeout); } void DecalManager::addDecal(const Point3F& pos, Point3F normal, DecalData* decalData) { if (smMaxNumDecals == 0) return; // DMM: Rework this, should be based on time if(mDecalQueue.size() >= smMaxNumDecals) { findSpace(); } Point3F vecX, vecY; DecalInstance* newDecal = allocateDecalInstance(); newDecal->decalData = decalData; newDecal->allocTime = Platform::getVirtualMilliseconds(); if(mFabs(normal.z) > 0.9f) mCross(normal, Point3F(0.0f, 1.0f, 0.0f), &vecX); else mCross(normal, Point3F(0.0f, 0.0f, 1.0f), &vecX); mCross(vecX, normal, &vecY); normal.normalizeSafe(); Point3F position = Point3F(pos.x + (normal.x * 0.008), pos.y + (normal.y * 0.008), pos.z + (normal.z * 0.008)); vecX.normalizeSafe(); vecY.normalizeSafe(); vecX *= decalData->sizeX; vecY *= decalData->sizeY; newDecal->point[0] = position + vecX + vecY; newDecal->point[1] = position + vecX - vecY; newDecal->point[2] = position - vecX - vecY; newDecal->point[3] = position - vecX + vecY; mDecalQueue.push_back(newDecal); mQueueDirty = true; } void DecalManager::addDecal(const Point3F& pos, const Point3F& rot, Point3F normal, DecalData* decalData) { if (smMaxNumDecals == 0) return; addDecal( pos, rot, normal, Point3F( 1, 1, 1 ), decalData ); } void DecalManager::addDecal(const Point3F& pos, const Point3F& rot, Point3F normal, const Point3F& scale, DecalData* decalData) { if (smMaxNumDecals == 0) return; if(mDot(rot, normal) < 0.98) { // DMM: Rework this, should be based on time if(mDecalQueue.size() >= smMaxNumDecals) { findSpace(); } Point3F vecX, vecY; DecalInstance* newDecal = allocateDecalInstance(); newDecal->decalData = decalData; newDecal->allocTime = Platform::getVirtualMilliseconds(); mCross(rot, normal, &vecX); mCross(normal, vecX, &vecY); normal.normalize(); Point3F position = Point3F(pos.x + (normal.x * 0.008), pos.y + (normal.y * 0.008), pos.z + (normal.z * 0.008)); vecX.normalize(); vecX.convolve( scale ); vecY.normalize(); vecY.convolve( scale ); vecX *= decalData->sizeX; vecY *= decalData->sizeY; newDecal->point[0] = position + vecX + vecY; newDecal->point[1] = position + vecX - vecY; newDecal->point[2] = position - vecX - vecY; newDecal->point[3] = position - vecX + vecY; mDecalQueue.push_back(newDecal); mQueueDirty = true; } } bool DecalManager::prepRenderImage(SceneState* state, const U32 stateKey, const U32 /*startZone*/, const bool /*modifyBaseState*/) { if (!smDecalsOn) return false; if (isLastState(state, stateKey)) return false; setLastState(state, stateKey); if (mDecalQueue.size() == 0) return false; // This should be sufficient for most objects that don't manage zones, and // don't need to return a specialized RenderImage... SceneRenderImage* image = new SceneRenderImage; image->obj = this; image->isTranslucent = true; image->sortType = SceneRenderImage::BeginSort; state->insertRenderImage(image); U32 currMs = Platform::getVirtualMilliseconds(); for (S32 i = mDecalQueue.size() - 1; i >= 0; i--) { U32 age = currMs - mDecalQueue[i]->allocTime; U32 timeout = mDecalQueue[i]->decalData->lifeSpan; if (age > timeout) { freeDecalInstance(mDecalQueue[i]); mDecalQueue.erase(i); } else if (age > ((3 * timeout) / 4)) { mDecalQueue[i]->fade = 1.0f - (F32(age - ((3 * timeout) / 4)) / F32(timeout / 4)); } else { mDecalQueue[i]->fade = 1.0f; } } if (mQueueDirty == true) { // Sort the decals based on the data pointers... dQsort(mDecalQueue.address(), mDecalQueue.size(), sizeof(DecalInstance*), cmpDecalInstance); mQueueDirty = false; } return false; } void DecalManager::renderObject(SceneState* state, SceneRenderImage*) { if (!smDecalsOn) return; AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry"); RectI viewport; glMatrixMode(GL_PROJECTION); glPushMatrix(); dglGetViewport(&viewport); state->setupBaseProjection(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); renderDecal(); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); dglSetViewport(viewport); AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit"); } struct DecalVert { Point3F vert; Point2F texCoord; ColorI color; }; void DecalManager::renderDecal() { static Vector renderVerts(__FILE__, __LINE__); static Vector indices(__FILE__, __LINE__); renderVerts.clear(); indices.clear(); U32 decalCount = 0; glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glEnable(GL_ALPHA_TEST); glDepthMask(GL_FALSE); glAlphaFunc(GL_GREATER, 0.1f); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); sgThisIsSelfIlluminated = false; sgLastWasSelfIlluminated = false; glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(-1,-1); glDepthMask(GL_FALSE); DecalData* pLastData = NULL; for (S32 x = 0; x < mDecalQueue.size(); x++) { if (mDecalQueue[x]->decalData != pLastData) { glVertexPointer(3, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].vert)); glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(DecalVert), &(renderVerts[0].color)); glTexCoordPointer(2, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].texCoord)); glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_SHORT, indices.address()); renderVerts.clear(); indices.clear(); decalCount = 0; glBindTexture(GL_TEXTURE_2D, mDecalQueue[x]->decalData->textureHandle.getGLName()); pLastData = mDecalQueue[x]->decalData; } sgThisIsSelfIlluminated = mDecalQueue[x]->decalData->selfIlluminated; if(sgThisIsSelfIlluminated != sgLastWasSelfIlluminated) { glVertexPointer(3, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].vert)); glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(DecalVert), &(renderVerts[0].color)); glTexCoordPointer(2, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].texCoord)); glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_SHORT, indices.address()); renderVerts.clear(); indices.clear(); decalCount = 0; if(sgThisIsSelfIlluminated) glBlendFunc(GL_SRC_ALPHA, GL_ONE); else glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); sgLastWasSelfIlluminated = sgThisIsSelfIlluminated; } renderVerts.increment(4); indices.increment(6); DecalVert *verts = &(renderVerts[decalCount * 4]); U16 *ind = &(indices[decalCount * 6]); const U8 fade = mDecalQueue[x]->fade * 255; verts[0].vert = mDecalQueue[x]->point[3]; verts[0].texCoord.set(0, 0); verts[0].color.set(255, 255, 255, fade); ind[0] = decalCount * 4; ind[5] = decalCount * 4; verts[1].vert = mDecalQueue[x]->point[2]; verts[1].texCoord.set(0, 1); verts[1].color.set(255, 255, 255, fade); ind[1] = decalCount * 4 + 1; verts[2].vert = mDecalQueue[x]->point[1]; verts[2].texCoord.set(1, 1); verts[2].color.set(255, 255, 255, fade); ind[2] = decalCount * 4 + 2; ind[3] = decalCount * 4 + 2; verts[3].vert = mDecalQueue[x]->point[0]; verts[3].texCoord.set(1, 0); verts[3].color.set(255, 255, 255, fade); ind[4] = decalCount * 4 + 3; decalCount++; } if(decalCount) { glVertexPointer(3, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].vert)); glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(DecalVert), &(renderVerts[0].color)); glTexCoordPointer(2, GL_FLOAT, sizeof(DecalVert), &(renderVerts[0].texCoord)); glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_SHORT, indices.address()); } glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisable(GL_POLYGON_OFFSET_FILL); glDepthMask(GL_TRUE); glDepthMask(GL_TRUE); glDisable(GL_BLEND); glDisable(GL_TEXTURE_2D); glDisable(GL_ALPHA_TEST); } void DecalManager::findSpace() { S32 besttime = S32_MAX; U32 bestindex = 0; DecalInstance *bestdecal = NULL; U32 time = Platform::getVirtualMilliseconds(); for(U32 i=0; iallocTime; U32 timeleft = inst->decalData->lifeSpan - age; if(besttime > timeleft) { besttime = timeleft; bestindex = i; bestdecal = inst; } } AssertFatal((bestdecal), "No good decals?"); mDecalQueue.erase_fast(bestindex); freeDecalInstance(bestdecal); } void DecalManager::addDecal(const Point3F& pos, const Point3F& rot, Point3F normal, const Point3F& scale, DecalData *decaldata, U32 ownerid) { if(smMaxNumDecals == 0) return; if(mDot(rot, normal) < 0.98) { if(mDecalQueue.size() >= smMaxNumDecals) findSpace(); Point3F vecX, vecY; DecalInstance* newDecal = allocateDecalInstance(); newDecal->decalData = decaldata; newDecal->allocTime = Platform::getVirtualMilliseconds(); newDecal->ownerId = ownerid; mCross(rot, normal, &vecX); mCross(normal, vecX, &vecY); normal.normalize(); Point3F position = Point3F(pos.x + (normal.x * 0.008), pos.y + (normal.y * 0.008), pos.z + (normal.z * 0.008)); vecX.normalize(); vecX.convolve( scale ); vecY.normalize(); vecY.convolve( scale ); vecX *= decaldata->sizeX; vecY *= decaldata->sizeY; newDecal->point[0] = position + vecX + vecY; newDecal->point[1] = position + vecX - vecY; newDecal->point[2] = position - vecX - vecY; newDecal->point[3] = position - vecX + vecY; mDecalQueue.push_back(newDecal); mQueueDirty = true; } } void DecalManager::ageDecal(U32 ownerid) { for(U32 i=0; iownerId == ownerid) { freeDecalInstance(inst); mDecalQueue.erase(U32(i)); } } }