#include "stdafx.h" #include "LevelRenderer.h" #include "Textures.h" #include "Tesselator.h" #include "Chunk.h" #include "EntityRenderDispatcher.h" #include "TileEntityRenderDispatcher.h" #include "DistanceChunkSorter.h" #include "DirtyChunkSorter.h" #include "MobSkinTextureProcessor.h" #include "MobSkinMemTextureProcessor.h" #include "GameRenderer.h" #include "BubbleParticle.h" #include "SmokeParticle.h" #include "NoteParticle.h" #include "NetherPortalParticle.h" #include "EnderParticle.h" #include "ExplodeParticle.h" #include "FlameParticle.h" #include "LavaParticle.h" #include "FootstepParticle.h" #include "SplashParticle.h" #include "SmokeParticle.h" #include "RedDustParticle.h" #include "BreakingItemParticle.h" #include "SnowShovelParticle.h" #include "BreakingItemParticle.h" #include "HeartParticle.h" #include "HugeExplosionParticle.h" #include "HugeExplosionSeedParticle.h" #include "SuspendedParticle.h" #include "SuspendedTownParticle.h" #include "CritParticle2.h" #include "TerrainParticle.h" #include "SpellParticle.h" #include "DripParticle.h" #include "EchantmentTableParticle.h" #include "DragonBreathParticle.h" #include "Lighting.h" #include "Options.h" #include "MultiPlayerChunkCache.h" #include "..\Minecraft.World\ParticleTypes.h" #include "..\Minecraft.World\IntCache.h" #include "..\Minecraft.World\IntBuffer.h" #include "..\Minecraft.World\JavaMath.h" #include "..\Minecraft.World\net.minecraft.world.level.h" #include "..\Minecraft.World\net.minecraft.world.level.dimension.h" #include "..\Minecraft.World\net.minecraft.world.level.tile.h" #include "..\Minecraft.World\net.minecraft.world.phys.h" #include "..\Minecraft.World\net.minecraft.world.entity.player.h" #include "..\Minecraft.World\net.minecraft.world.item.h" #include "..\Minecraft.World\System.h" #include "..\Minecraft.World\StringHelpers.h" #include "..\Minecraft.World\net.minecraft.world.level.chunk.h" #include "..\Minecraft.World\net.minecraft.world.entity.projectile.h" #include "..\Minecraft.World\net.minecraft.world.h" #include "MultiplayerLocalPlayer.h" #include "MultiPlayerLevel.h" #include "..\Minecraft.World\SoundTypes.h" #include "FrustumCuller.h" #include "..\Minecraft.World\BasicTypeContainers.h" #ifdef __PS3__ #include "PS3\SPU_Tasks\LevelRenderer_cull\LevelRenderer_cull.h" #include "PS3\SPU_Tasks\LevelRenderer_FindNearestChunk\LevelRenderer_FindNearestChunk.h" #include "C4JSpursJob.h" static LevelRenderer_cull_DataIn g_cullDataIn[4] __attribute__((__aligned__(16))); static LevelRenderer_FindNearestChunk_DataIn g_findNearestChunkDataIn __attribute__((__aligned__(16))); #endif const unsigned int HALO_RING_RADIUS = 100; #ifdef _LARGE_WORLDS Chunk LevelRenderer::permaChunk[MAX_CONCURRENT_CHUNK_REBUILDS]; C4JThread *LevelRenderer::rebuildThreads[MAX_CHUNK_REBUILD_THREADS]; C4JThread::EventArray *LevelRenderer::s_rebuildCompleteEvents; C4JThread::Event *LevelRenderer::s_activationEventA[MAX_CHUNK_REBUILD_THREADS]; // This defines the maximum size of renderable level, must be big enough to cope with actual size of level + view distance at each side // so that we can render the "infinite" sea at the edges. Currently defined as: const int overworldSize = LEVEL_MAX_WIDTH + LevelRenderer::PLAYER_VIEW_DISTANCE + LevelRenderer::PLAYER_VIEW_DISTANCE; const int netherSize = HELL_LEVEL_MAX_WIDTH + 2; // 4J Stu - The plus 2 is really just to make our total chunk count a multiple of 8 for the flags, we will never see these in the nether const int endSize = END_LEVEL_MAX_WIDTH; const int LevelRenderer::MAX_LEVEL_RENDER_SIZE[3] = { overworldSize, netherSize, endSize }; const int LevelRenderer::DIMENSION_OFFSETS[3] = { 0, (overworldSize * overworldSize * CHUNK_Y_COUNT) , (overworldSize * overworldSize * CHUNK_Y_COUNT) + ( netherSize * netherSize * CHUNK_Y_COUNT ) }; #else // This defines the maximum size of renderable level, must be big enough to cope with actual size of level + view distance at each side // so that we can render the "infinite" sea at the edges. Currently defined as: // Dimension idx 0 (overworld) : 80 ( = 54 + 13 + 13 ) // Dimension idx 1 (nether) : 44 ( = 18 + 13 + 13 ) // Dimension idx 2 (the end) : 44 ( = 18 + 13 + 13 ) const int LevelRenderer::MAX_LEVEL_RENDER_SIZE[3] = { 80, 44, 44 }; // Linked directly to the sizes in the previous array, these next values dictate the start offset for each dimension index into the global array for these things. // Each dimension uses MAX_LEVEL_RENDER_SIZE[i]^2 * 8 indices, as a MAX_LEVEL_RENDER_SIZE * MAX_LEVEL_RENDER_SIZE * 8 sized cube of references. const int LevelRenderer::DIMENSION_OFFSETS[3] = { 0, (80 * 80 * CHUNK_Y_COUNT) , (80 * 80 * CHUNK_Y_COUNT) + ( 44 * 44 * CHUNK_Y_COUNT ) }; #endif LevelRenderer::LevelRenderer(Minecraft *mc, Textures *textures) { breakingTextures = NULL; for( int i = 0; i < 4; i++ ) { level[i] = NULL; tileRenderer[i] = NULL; xOld[i] = -9999; yOld[i] = -9999; zOld[i] = -9999; } xChunks= yChunks= zChunks = 0; chunkLists = 0; ticks = 0; starList= skyList= darkList = 0; xMinChunk= yMinChunk= zMinChunk = 0; xMaxChunk= yMaxChunk= zMaxChunk = 0; lastViewDistance = -1; noEntityRenderFrames = 2; totalEntities = 0; renderedEntities = 0; culledEntities = 0; chunkFixOffs = 0; frame = 0; repeatList = MemoryTracker::genLists(1); destroyProgress = 0.0f; totalChunks= offscreenChunks= occludedChunks= renderedChunks= emptyChunks = 0; for( int i = 0; i < 4; i++ ) { // sortedChunks[i] = NULL; // 4J - removed - not sorting our chunks anymore chunks[i] = ClipChunkArray(); lastPlayerCount[i] = 0; } InitializeCriticalSection(&m_csDirtyChunks); InitializeCriticalSection(&m_csRenderableTileEntities); #ifdef _LARGE_WORLDS InitializeCriticalSection(&m_csChunkFlags); #endif dirtyChunkPresent = false; lastDirtyChunkFound = 0; this->mc = mc; this->textures = textures; chunkLists = MemoryTracker::genLists(getGlobalChunkCount()*2); // *2 here is because there is one renderlist per chunk here for each of the opaque & transparent layers globalChunkFlags = new unsigned char[getGlobalChunkCount()]; memset(globalChunkFlags, 0, getGlobalChunkCount()); starList = MemoryTracker::genLists(4); glPushMatrix(); glNewList(starList, GL_COMPILE); renderStars(); glEndList(); // 4J added - create geometry for rendering clouds createCloudMesh(); glPopMatrix(); Tesselator *t = Tesselator::getInstance(); skyList = starList + 1; glNewList(skyList, GL_COMPILE); glDepthMask(false); // 4J - added to get depth mask disabled within the command buffer float yy; int s = 64; int d = (256 / s) + 2; yy = (float) (16); for (int xx = -s * d; xx <= s * d; xx += s) { for (int zz = -s * d; zz <= s * d; zz += s) { t->begin(); t->vertex((float)(xx + 0), (float)( yy), (float)( zz + 0)); t->vertex((float)(xx + s), (float)( yy), (float)( zz + 0)); t->vertex((float)(xx + s), (float)( yy), (float)( zz + s)); t->vertex((float)(xx + 0), (float)( yy), (float)( zz + s)); t->end(); } } glEndList(); darkList = starList + 2; glNewList(darkList, GL_COMPILE); yy = -(float) (16); t->begin(); for (int xx = -s * d; xx <= s * d; xx += s) { for (int zz = -s * d; zz <= s * d; zz += s) { t->vertex((float)(xx + s), (float)( yy), (float)( zz + 0)); t->vertex((float)(xx + 0), (float)( yy), (float)( zz + 0)); t->vertex((float)(xx + 0), (float)( yy), (float)( zz + s)); t->vertex((float)(xx + s), (float)( yy), (float)( zz + s)); } } t->end(); glEndList(); // HALO ring for the texture pack { const unsigned int ARC_SEGMENTS = 50; const float VERTICAL_OFFSET = HALO_RING_RADIUS * 999/1000; // How much we raise the circle origin to make the circle curve back towards us const int WIDTH = 10; const float ARC_RADIANS = 2.0f*PI/ARC_SEGMENTS; const float HALF_ARC_SEG = ARC_SEGMENTS/2; const float WIDE_ARC_SEGS = ARC_SEGMENTS/8; const float WIDE_ARC_SEGS_SQR = WIDE_ARC_SEGS * WIDE_ARC_SEGS; float u = 0.0f; float width = WIDTH; haloRingList = starList + 3; glNewList(haloRingList, GL_COMPILE); t->begin(GL_TRIANGLE_STRIP); t->color(0xffffff); for(unsigned int i = 0; i <= ARC_SEGMENTS; ++i) { float DIFF = abs(i - HALF_ARC_SEG); if(DIFF<(HALF_ARC_SEG-WIDE_ARC_SEGS)) DIFF = 0; else DIFF-=(HALF_ARC_SEG-WIDE_ARC_SEGS); width = 1 + ( (DIFF * DIFF) / (WIDE_ARC_SEGS_SQR) ) * WIDTH; t->vertexUV((HALO_RING_RADIUS * cos(i*ARC_RADIANS)) - VERTICAL_OFFSET, (HALO_RING_RADIUS * sin(i*ARC_RADIANS)), 0-width, u, 0); t->vertexUV((HALO_RING_RADIUS * cos(i*ARC_RADIANS)) - VERTICAL_OFFSET, (HALO_RING_RADIUS * sin(i*ARC_RADIANS)), 0+width, u, 1); //--u; u -= 0.25; } t->end(); glEndList(); } Chunk::levelRenderer = this; destroyedTileManager = new DestroyedTileManager(); dirtyChunksLockFreeStack.Initialize(); #ifdef __PS3__ m_jobPort_CullSPU = new C4JSpursJobQueue::Port("C4JSpursJob_LevelRenderer_cull"); m_jobPort_FindNearestChunk = new C4JSpursJobQueue::Port("C4JSpursJob_LevelRenderer_FindNearestChunk"); #endif // __PS3__ } void LevelRenderer::renderStars() { Random random = Random(10842); Tesselator *t = Tesselator::getInstance(); t->begin(); for (int i = 0; i < 1500; i++) { double x = random.nextFloat() * 2 - 1; double y = random.nextFloat() * 2 - 1; double z = random.nextFloat() * 2 - 1; double ss = 0.15f + random.nextFloat() * 0.10f; double d = x * x + y * y + z * z; if (d < 1 && d > 0.01) { d = 1 / sqrt(d); x *= d; y *= d; z *= d; double xp = x * 160; // 4J - moved further away (were 100) as they were cutting through far chunks double yp = y * 160; double zp = z * 160; double yRot = atan2(x, z); double ySin = sin(yRot); double yCos = cos(yRot); double xRot = atan2(sqrt(x * x + z * z), y); double xSin = sin(xRot); double xCos = cos(xRot); double zRot = random.nextDouble() * PI * 2; double zSin = sin(zRot); double zCos = cos(zRot); for (int c = 0; c < 4; c++) { double ___xo = 0; double ___yo = ((c & 2) - 1) * ss; double ___zo = (((c + 1) & 2) - 1) * ss; double __xo = ___xo; double __yo = ___yo * zCos - ___zo * zSin; double __zo = ___zo * zCos + ___yo * zSin; double _zo = __zo; double _yo = __yo * xSin + __xo * xCos; double _xo = __xo * xSin - __yo * xCos; double xo = _xo * ySin - _zo * yCos; double yo = _yo; double zo = _zo * ySin + _xo * yCos; t->vertex((float)(xp + xo), (float)( yp + yo), (float)( zp + zo)); } } } t->end(); } void LevelRenderer::setLevel(int playerIndex, MultiPlayerLevel *level) { if (this->level[playerIndex] != NULL) { // Remove listener for this level if this is the last player referencing it Level *prevLevel = this->level[playerIndex]; int refCount = 0; for( int i = 0; i < 4; i++ ) { if( this->level[i] == prevLevel ) refCount++; } if( refCount == 1 ) { this->level[playerIndex]->removeListener(this); } } xOld[playerIndex] = -9999; yOld[playerIndex] = -9999; zOld[playerIndex] = -9999; this->level[playerIndex] = level; if( tileRenderer[playerIndex] != NULL ) { delete tileRenderer[playerIndex]; } tileRenderer[playerIndex] = new TileRenderer(level); if (level != NULL) { // If we're the only player referencing this level, add a new listener for it int refCount = 0; for( int i = 0; i < 4; i++ ) { if( this->level[i] == level ) refCount++; } if( refCount == 1 ) { level->addListener(this); } allChanged(playerIndex); } else { // printf("NULLing player %d, chunks @ 0x%x\n",playerIndex,chunks[playerIndex]); if( chunks[playerIndex].data != NULL ) { for (unsigned int i = 0; i < chunks[playerIndex].length; i++) { chunks[playerIndex][i].chunk->_delete(); delete chunks[playerIndex][i].chunk; } delete chunks[playerIndex].data; chunks[playerIndex].data = NULL; chunks[playerIndex].length = 0; // delete sortedChunks[playerIndex]; // 4J - removed - not sorting our chunks anymore // sortedChunks[playerIndex] = NULL; // 4J - removed - not sorting our chunks anymore } // 4J Stu - If we do this for splitscreen players leaving, then all the tile entities in the world dissappear // We should only do this when actually exiting the game, so only when the primary player sets there level to NULL if(playerIndex == ProfileManager.GetPrimaryPad()) renderableTileEntities.clear(); } } void LevelRenderer::AddDLCSkinsToMemTextures() { for(int i=0;iaddMemTexture(app.vSkinNames[i], new MobSkinMemTextureProcessor()); } } void LevelRenderer::allChanged() { int playerIndex = mc->player->GetXboxPad(); // 4J added allChanged(playerIndex); } int LevelRenderer::activePlayers() { int playerCount = 0; for( int i = 0; i < 4; i++ ) { if( level[i] ) playerCount++; } return playerCount; } void LevelRenderer::allChanged(int playerIndex) { // 4J Stu - This was required by the threaded Minecraft::tick(). If we need to add it back then: // If this CS is entered before DisableUpdateThread is called then (on 360 at least) we can get a // deadlock when starting a game in splitscreen. //EnterCriticalSection(&m_csDirtyChunks); if( level == NULL ) { return; } Minecraft::GetInstance()->gameRenderer->DisableUpdateThread(); Tile::leaves->setFancy(mc->options->fancyGraphics); lastViewDistance = mc->options->viewDistance; // Calculate size of area we can render based on number of players we need to render for int dist = (int)sqrtf( (float)PLAYER_RENDER_AREA / (float)activePlayers() ); // AP - poor little Vita just can't cope with such a big area lastPlayerCount[playerIndex] = activePlayers(); xChunks = dist; yChunks = Level::maxBuildHeight / CHUNK_SIZE; zChunks = dist; if( chunks[playerIndex].data != NULL ) { for (unsigned int i = 0; i < chunks[playerIndex].length; i++) { chunks[playerIndex][i].chunk->_delete(); delete chunks[playerIndex][i].chunk; } delete chunks[playerIndex].data; // delete sortedChunks[playerIndex]; // 4J - removed - not sorting our chunks anymore } chunks[playerIndex] = ClipChunkArray(xChunks * yChunks * zChunks); // sortedChunks[playerIndex] = new vector(xChunks * yChunks * zChunks); // 4J - removed - not sorting our chunks anymore int id = 0; int count = 0; xMinChunk = 0; yMinChunk = 0; zMinChunk = 0; xMaxChunk = xChunks; yMaxChunk = yChunks; zMaxChunk = zChunks; // 4J removed - we now only fully clear this on exiting the game (setting level to NULL). Apart from that, the chunk rebuilding is responsible for maintaining this // renderableTileEntities.clear(); for (int x = 0; x < xChunks; x++) { for (int y = 0; y < yChunks; y++) { for (int z = 0; z < zChunks; z++) { chunks[playerIndex][(z * yChunks + y) * xChunks + x].chunk = new Chunk(level[playerIndex], renderableTileEntities, m_csRenderableTileEntities, x * CHUNK_XZSIZE, y * CHUNK_SIZE, z * CHUNK_XZSIZE, &chunks[playerIndex][(z * yChunks + y) * xChunks + x]); chunks[playerIndex][(z * yChunks + y) * xChunks + x].visible = true; chunks[playerIndex][(z * yChunks + y) * xChunks + x].chunk->id = count++; // sortedChunks[playerIndex]->at((z * yChunks + y) * xChunks + x) = chunks[playerIndex]->at((z * yChunks + y) * xChunks + x); // 4J - removed - not sorting our chunks anymore id += 3; } } } nonStackDirtyChunksAdded(); if (level != NULL) { shared_ptr player = mc->cameraTargetPlayer; if (player != NULL) { this->resortChunks(Mth::floor(player->x), Mth::floor(player->y), Mth::floor(player->z)); // sort(sortedChunks[playerIndex]->begin(),sortedChunks[playerIndex]->end(), DistanceChunkSorter(player)); // 4J - removed - not sorting our chunks anymore } } noEntityRenderFrames = 2; Minecraft::GetInstance()->gameRenderer->EnableUpdateThread(); // 4J Stu - Remove. See comment above. //LeaveCriticalSection(&m_csDirtyChunks); } void LevelRenderer::renderEntities(Vec3 *cam, Culler *culler, float a) { int playerIndex = mc->player->GetXboxPad(); // 4J added // 4J Stu - Set these up every time, even when not rendering as other things (like particle render) may depend on it for those frames. TileEntityRenderDispatcher::instance->prepare(level[playerIndex], textures, mc->font, mc->cameraTargetPlayer, a); EntityRenderDispatcher::instance->prepare(level[playerIndex], textures, mc->font, mc->cameraTargetPlayer, mc->options, a); if (noEntityRenderFrames > 0) { noEntityRenderFrames--; return; } totalEntities = 0; renderedEntities = 0; culledEntities = 0; shared_ptr player = mc->cameraTargetPlayer; EntityRenderDispatcher::xOff = (player->xOld + (player->x - player->xOld) * a); EntityRenderDispatcher::yOff = (player->yOld + (player->y - player->yOld) * a); EntityRenderDispatcher::zOff = (player->zOld + (player->z - player->zOld) * a); TileEntityRenderDispatcher::xOff = (player->xOld + (player->x - player->xOld) * a); TileEntityRenderDispatcher::yOff = (player->yOld + (player->y - player->yOld) * a); TileEntityRenderDispatcher::zOff = (player->zOld + (player->z - player->zOld) * a); mc->gameRenderer->turnOnLightLayer(a); // 4J - brought forward from 1.8.2 vector > entities = level[playerIndex]->getAllEntities(); totalEntities = (int)entities.size(); AUTO_VAR(itEndGE, level[playerIndex]->globalEntities.end()); for (AUTO_VAR(it, level[playerIndex]->globalEntities.begin()); it != itEndGE; it++) { shared_ptr entity = *it; //level->globalEntities[i]; renderedEntities++; if (entity->shouldRender(cam)) EntityRenderDispatcher::instance->render(entity, a); } AUTO_VAR(itEndEnts, entities.end()); for (AUTO_VAR(it, entities.begin()); it != itEndEnts; it++) { shared_ptr entity = *it; //entities[i]; if ((entity->shouldRender(cam) && (entity->noCulling || culler->isVisible(entity->bb)))) { // 4J-PB - changing this to be per player //if (entity == mc->cameraTargetPlayer && !mc->options->thirdPersonView && !mc->cameraTargetPlayer->isSleeping()) continue; shared_ptr localplayer = dynamic_pointer_cast(mc->cameraTargetPlayer); if (localplayer && entity == mc->cameraTargetPlayer && !localplayer->ThirdPersonView() && !mc->cameraTargetPlayer->isSleeping()) continue; if (!level[playerIndex]->hasChunkAt(Mth::floor(entity->x), 0, Mth::floor(entity->z))) { continue; } renderedEntities++; EntityRenderDispatcher::instance->render(entity, a); } } Lighting::turnOn(); // 4J - have restructed this so that the tile entities are stored within a hashmap by chunk/dimension index. The index // is calculated in the same way as the global flags. EnterCriticalSection(&m_csRenderableTileEntities); for (AUTO_VAR(it, renderableTileEntities.begin()); it != renderableTileEntities.end(); it++) { int idx = it->first; // Don't render if it isn't in the same dimension as this player if( !isGlobalIndexInSameDimension(idx, level[playerIndex]) ) continue; for( AUTO_VAR(it2, it->second.begin()); it2 != it->second.end(); it2++) { TileEntityRenderDispatcher::instance->render(*it2, a); } } // Now consider if any of these renderable tile entities have been flagged for removal, and if so, remove for (AUTO_VAR(it, renderableTileEntities.begin()); it != renderableTileEntities.end();) { int idx = it->first; for( AUTO_VAR(it2, it->second.begin()); it2 != it->second.end(); ) { // If it has been flagged for removal, remove if((*it2)->shouldRemoveForRender()) { it2 = it->second.erase(it2); } else { it2++; } } // If there aren't any entities left for this key, then delete the key if( it->second.size() == 0 ) { it = renderableTileEntities.erase(it); } else { it++; } } LeaveCriticalSection(&m_csRenderableTileEntities); mc->gameRenderer->turnOffLightLayer(a); // 4J - brought forward from 1.8.2 } wstring LevelRenderer::gatherStats1() { return L"C: " + _toString(renderedChunks) + L"/" + _toString(totalChunks) + L". F: " + _toString(offscreenChunks) + L", O: " + _toString(occludedChunks) + L", E: " + _toString(emptyChunks); } wstring LevelRenderer::gatherStats2() { return L"E: " + _toString(renderedEntities) + L"/" + _toString(totalEntities) + L". B: " + _toString(culledEntities) + L", I: " + _toString((totalEntities - culledEntities) - renderedEntities); } void LevelRenderer::resortChunks(int xc, int yc, int zc) { EnterCriticalSection(&m_csDirtyChunks); xc -= CHUNK_XZSIZE / 2; yc -= CHUNK_SIZE / 2; zc -= CHUNK_XZSIZE / 2; xMinChunk = INT_MAX; yMinChunk = INT_MAX; zMinChunk = INT_MAX; xMaxChunk = INT_MIN; yMaxChunk = INT_MIN; zMaxChunk = INT_MIN; int playerIndex = mc->player->GetXboxPad(); // 4J added int s2 = xChunks * CHUNK_XZSIZE; int s1 = s2 / 2; for (int x = 0; x < xChunks; x++) { int xx = x * CHUNK_XZSIZE; int xOff = (xx + s1 - xc); if (xOff < 0) xOff -= (s2 - 1); xOff /= s2; xx -= xOff * s2; if (xx < xMinChunk) xMinChunk = xx; if (xx > xMaxChunk) xMaxChunk = xx; for (int z = 0; z < zChunks; z++) { int zz = z * CHUNK_XZSIZE; int zOff = (zz + s1 - zc); if (zOff < 0) zOff -= (s2 - 1); zOff /= s2; zz -= zOff * s2; if (zz < zMinChunk) zMinChunk = zz; if (zz > zMaxChunk) zMaxChunk = zz; for (int y = 0; y < yChunks; y++) { int yy = y * CHUNK_SIZE; if (yy < yMinChunk) yMinChunk = yy; if (yy > yMaxChunk) yMaxChunk = yy; Chunk *chunk = chunks[playerIndex][(z * yChunks + y) * xChunks + x].chunk; chunk->setPos(xx, yy, zz); } } } nonStackDirtyChunksAdded(); LeaveCriticalSection(&m_csDirtyChunks); } int LevelRenderer::render(shared_ptr player, int layer, double alpha, bool updateChunks) { int playerIndex = mc->player->GetXboxPad(); // 4J - added - if the number of players has changed, we need to rebuild things for the new draw distance this will require if( lastPlayerCount[playerIndex] != activePlayers() ) { allChanged(); } else if (mc->options->viewDistance != lastViewDistance) { allChanged(); } if (layer == 0) { totalChunks = 0; offscreenChunks = 0; occludedChunks = 0; renderedChunks = 0; emptyChunks = 0; } double xOff = player->xOld + (player->x - player->xOld) * alpha; double yOff = player->yOld + (player->y - player->yOld) * alpha; double zOff = player->zOld + (player->z - player->zOld) * alpha; double xd = player->x - xOld[playerIndex]; double yd = player->y - yOld[playerIndex]; double zd = player->z - zOld[playerIndex]; if (xd * xd + yd * yd + zd * zd > 4 * 4) { xOld[playerIndex] = player->x; yOld[playerIndex] = player->y; zOld[playerIndex] = player->z; resortChunks(Mth::floor(player->x), Mth::floor(player->y), Mth::floor(player->z)); // sort(sortedChunks[playerIndex]->begin(),sortedChunks[playerIndex]->end(), DistanceChunkSorter(player)); // 4J - removed - not sorting our chunks anymore } Lighting::turnOff(); int count = renderChunks(0, (int)chunks[playerIndex].length, layer, alpha); return count; } int LevelRenderer::renderChunks(int from, int to, int layer, double alpha) { int playerIndex = mc->player->GetXboxPad(); // 4J added #if 1 // 4J - cut down version, we're not using offsetted render lists, or a sorted chunk list, anymore mc->gameRenderer->turnOnLightLayer(alpha); // 4J - brought forward from 1.8.2 shared_ptr player = mc->cameraTargetPlayer; double xOff = player->xOld + (player->x - player->xOld) * alpha; double yOff = player->yOld + (player->y - player->yOld) * alpha; double zOff = player->zOld + (player->z - player->zOld) * alpha; glPushMatrix(); glTranslatef((float)-xOff, (float)-yOff, (float)-zOff); #if defined __PS3__ && !defined DISABLE_SPU_CODE // pre- calc'd on the SPU int count = 0; waitForCull_SPU(); if(layer == 0) { count = g_cullDataIn[playerIndex].numToRender_layer0; RenderManager.CBuffCallMultiple(g_cullDataIn[playerIndex].listArray_layer0, count); } else // layer == 1 { count = g_cullDataIn[playerIndex].numToRender_layer1; RenderManager.CBuffCallMultiple(g_cullDataIn[playerIndex].listArray_layer1, count); } #else // __PS3__ bool first = true; int count = 0; ClipChunk *pClipChunk = chunks[playerIndex].data; unsigned char emptyFlag = LevelRenderer::CHUNK_FLAG_EMPTY0 << layer; for( int i = 0; i < chunks[playerIndex].length; i++, pClipChunk++ ) { if( !pClipChunk->visible ) continue; // This will be set if the chunk isn't visible, or isn't compiled, or has both empty flags set if( pClipChunk->globalIdx == -1 ) continue; // Not sure if we should ever encounter this... TODO check if( ( globalChunkFlags[pClipChunk->globalIdx] & emptyFlag ) == emptyFlag ) continue; // Check that this particular layer isn't empty // List can be calculated directly from the chunk's global idex int list = pClipChunk->globalIdx * 2 + layer; list += chunkLists; if(RenderManager.CBuffCall(list, first)) { first = false; } count++; } #endif // __PS3__ glPopMatrix(); mc->gameRenderer->turnOffLightLayer(alpha); // 4J - brought forward from 1.8.2 #else _renderChunks.clear(); // int p = 0; int count = 0; for (int i = from; i < to; i++) { if (layer == 0) { totalChunks++; if (sortedChunks[playerIndex]->at(i)->emptyFlagSet(layer)) emptyChunks++; else if (!sortedChunks[playerIndex]->at(i)->visible) offscreenChunks++; else renderedChunks++; } // if (!sortedChunks[i].empty[layer] && sortedChunks[i].visible && (sortedChunks[i].occlusion_visible)) { if (!(sortedChunks[playerIndex]->at(i)->emptyFlagSet(layer) && sortedChunks[playerIndex]->at(i)->visible )) { int list = sortedChunks[playerIndex]->at(i)->getList(layer); if (list >= 0) { _renderChunks.push_back(sortedChunks[playerIndex]->at(i)); count++; } } } shared_ptr player = mc->cameraTargetPlayer; double xOff = player->xOld + (player->x - player->xOld) * alpha; double yOff = player->yOld + (player->y - player->yOld) * alpha; double zOff = player->zOld + (player->z - player->zOld) * alpha; int lists = 0; for (int l = 0; l < RENDERLISTS_LENGTH; l++) { renderLists[l].clear(); } AUTO_VAR(itEnd, _renderChunks.end()); for (AUTO_VAR(it, _renderChunks.begin()); it != itEnd; it++) { Chunk *chunk = *it; //_renderChunks[i]; int list = -1; for (int l = 0; l < lists; l++) { if (renderLists[l].isAt(chunk->xRender, chunk->yRender, chunk->zRender)) { list = l; } } if (list < 0) { list = lists++; renderLists[list].init(chunk->xRender, chunk->yRender, chunk->zRender, xOff, yOff, zOff); } renderLists[list].add(chunk->getList(layer)); } renderSameAsLast(layer, alpha); #endif return count; } void LevelRenderer::renderSameAsLast(int layer, double alpha) { for (int i = 0; i < RENDERLISTS_LENGTH; i++) { renderLists[i].render(); } } void LevelRenderer::tick() { ticks++; if ((ticks % SharedConstants::TICKS_PER_SECOND) == 0) { AUTO_VAR(it , destroyingBlocks.begin()); while (it != destroyingBlocks.end()) { BlockDestructionProgress *block = it->second; int updatedRenderTick = block->getUpdatedRenderTick(); if (ticks - updatedRenderTick > (SharedConstants::TICKS_PER_SECOND * 20)) { delete it->second; it = destroyingBlocks.erase(it); } else { ++it; } } } } void LevelRenderer::renderSky(float alpha) { if (mc->level->dimension->id == 1) { glDisable(GL_FOG); glDisable(GL_ALPHA_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); Lighting::turnOff(); glDepthMask(false); textures->bind(textures->loadTexture(TN_MISC_TUNNEL)); // 4J was L"/1_2_2/misc/tunnel.png" Tesselator *t = Tesselator::getInstance(); t->setMipmapEnable(false); for (int i = 0; i < 6; i++) { glPushMatrix(); if (i == 1) glRotatef(90, 1, 0, 0); if (i == 2) glRotatef(-90, 1, 0, 0); if (i == 3) glRotatef(180, 1, 0, 0); if (i == 4) glRotatef(90, 0, 0, 1); if (i == 5) glRotatef(-90, 0, 0, 1); t->begin(); t->color(0x282828); t->vertexUV(-100, -100, -100, 0, 0); t->vertexUV(-100, -100, +100, 0, 16); t->vertexUV(+100, -100, +100, 16, 16); t->vertexUV(+100, -100, -100, 16, 0); t->end(); glPopMatrix(); } t->setMipmapEnable(true); glDepthMask(true); glEnable(GL_TEXTURE_2D); glEnable(GL_ALPHA_TEST); return; } if (!mc->level->dimension->isNaturalDimension()) return; glDisable(GL_TEXTURE_2D); int playerIndex = mc->player->GetXboxPad(); Vec3 *sc = level[playerIndex]->getSkyColor(mc->cameraTargetPlayer, alpha); float sr = (float) sc->x; float sg = (float) sc->y; float sb = (float) sc->z; if (mc->options->anaglyph3d) { float srr = (sr * 30 + sg * 59 + sb * 11) / 100; float sgg = (sr * 30 + sg * 70) / (100); float sbb = (sr * 30 + sb * 70) / (100); sr = srr; sg = sgg; sb = sbb; } glColor3f(sr, sg, sb); Tesselator *t = Tesselator::getInstance(); glDepthMask(false); glEnable(GL_FOG); glColor3f(sr, sg, sb); glCallList(skyList); glDisable(GL_FOG); glDisable(GL_ALPHA_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); Lighting::turnOff(); float *c = level[playerIndex]->dimension->getSunriseColor(level[playerIndex]->getTimeOfDay(alpha), alpha); if (c != NULL) { glDisable(GL_TEXTURE_2D); glShadeModel(GL_SMOOTH); glPushMatrix(); { glRotatef(90, 1, 0, 0); glRotatef(Mth::sin(level[playerIndex]->getSunAngle(alpha)) < 0 ? 180 : 0, 0, 0, 1); glRotatef(90, 0, 0, 1); float r = c[0]; float g = c[1]; float b = c[2]; if (mc->options->anaglyph3d) { float srr = (r * 30 + g * 59 + b * 11) / 100; float sgg = (r * 30 + g * 70) / (100); float sbb = (r * 30 + b * 70) / (100); r = srr; g = sgg; b = sbb; } t->begin(GL_TRIANGLE_FAN); t->color(r, g, b, c[3]); t->vertex((float)(0), (float)( 100), (float)( 0)); int steps = 16; t->color(c[0], c[1], c[2], 0.0f); for (int i = 0; i <= steps; i++) { float a = i * PI * 2 / steps; float _sin = Mth::sin(a); float _cos = Mth::cos(a); t->vertex((float)(_sin * 120), (float)( _cos * 120), (float)( -_cos * 40 * c[3])); } t->end(); } glPopMatrix(); glShadeModel(GL_FLAT); } glEnable(GL_TEXTURE_2D); glBlendFunc(GL_SRC_ALPHA, GL_ONE); glPushMatrix(); { float rainBrightness = 1 - level[playerIndex]->getRainLevel(alpha); float xp = 0; float yp = 0; float zp = 0; glColor4f(1, 1, 1, rainBrightness); glTranslatef(xp, yp, zp); glRotatef(-90, 0, 1, 0); glRotatef(level[playerIndex]->getTimeOfDay(alpha) * 360, 1, 0, 0); float ss = 30; MemSect(31); textures->bindTexture(TN_TERRAIN_SUN); // 4J was L"/terrain/sun.png" MemSect(0); t->begin(); t->vertexUV((float)(-ss), (float)( 100), (float)( -ss), (float)( 0), (float)( 0)); t->vertexUV((float)(+ss), (float)( 100), (float)( -ss), (float)( 1), (float)( 0)); t->vertexUV((float)(+ss), (float)( 100), (float)( +ss), (float)( 1), (float)( 1)); t->vertexUV((float)(-ss), (float)( 100), (float)( +ss), (float)( 0), (float)( 1)); t->end(); ss = 20; textures->bindTexture(TN_TERRAIN_MOON_PHASES); // 4J was L"/1_2_2/terrain/moon_phases.png" int phase = level[playerIndex]->getMoonPhase(alpha); int u = phase % 4; int v = phase / 4 % 2; float u0 = (u + 0) / 4.0f; float v0 = (v + 0) / 2.0f; float u1 = (u + 1) / 4.0f; float v1 = (v + 1) / 2.0f; t->begin(); t->vertexUV(-ss, -100, +ss, u1, v1); t->vertexUV(+ss, -100, +ss, u0, v1); t->vertexUV(+ss, -100, -ss, u0, v0); t->vertexUV(-ss, -100, -ss, u1, v0); t->end(); glDisable(GL_TEXTURE_2D); float br = level[playerIndex]->getStarBrightness(alpha) * rainBrightness; if (br > 0) { glColor4f(br, br, br, br); glCallList(starList); } glColor4f(1, 1, 1, 1); } glDisable(GL_BLEND); glEnable(GL_ALPHA_TEST); glEnable(GL_FOG); glPopMatrix(); glDisable(GL_TEXTURE_2D); glColor3f(0, 0, 0); double yy = mc->player->getPos(alpha)->y - level[playerIndex]->getHorizonHeight(); // 4J - getHorizonHeight moved forward from 1.2.3 if (yy < 0) { glPushMatrix(); glTranslatef(0, -(float) (-12), 0); glCallList(darkList); glPopMatrix(); // 4J - can't work out what this big black box is for. Taking it out until someone misses it... it causes a big black box to visible appear in 3rd person mode whilst under the ground. #if 0 float ss = 1; float yo = -(float) (yy + 65); float y0 = -ss; float y1 = yo; t->begin(); t->color(0x000000, 255); t->vertex(-ss, y1, ss); t->vertex(+ss, y1, ss); t->vertex(+ss, y0, ss); t->vertex(-ss, y0, ss); t->vertex(-ss, y0, -ss); t->vertex(+ss, y0, -ss); t->vertex(+ss, y1, -ss); t->vertex(-ss, y1, -ss); t->vertex(+ss, y0, -ss); t->vertex(+ss, y0, +ss); t->vertex(+ss, y1, +ss); t->vertex(+ss, y1, -ss); t->vertex(-ss, y1, -ss); t->vertex(-ss, y1, +ss); t->vertex(-ss, y0, +ss); t->vertex(-ss, y0, -ss); t->vertex(-ss, y0, -ss); t->vertex(-ss, y0, +ss); t->vertex(+ss, y0, +ss); t->vertex(+ss, y0, -ss); t->end(); #endif } if (level[playerIndex]->dimension->hasGround()) { glColor3f(sr * 0.2f + 0.04f, sg * 0.2f + 0.04f, sb * 0.6f + 0.1f); } else { glColor3f(sr, sg, sb); } glPushMatrix(); glTranslatef(0, -(float) (yy - 16), 0); glCallList(darkList); glPopMatrix(); glEnable(GL_TEXTURE_2D); glDepthMask(true); } void LevelRenderer::renderHaloRing(float alpha) { #if !defined(__PS3__) if (!mc->level->dimension->isNaturalDimension()) return; glDisable(GL_ALPHA_TEST); glDisable(GL_TEXTURE_2D); glDepthMask(false); glEnable(GL_FOG); int playerIndex = mc->player->GetXboxPad(); Vec3 *sc = level[playerIndex]->getSkyColor(mc->cameraTargetPlayer, alpha); float sr = (float) sc->x; float sg = (float) sc->y; float sb = (float) sc->z; // Rough lumninance calculation float Y = (sr+sr+sb+sg+sg+sg)/6; float br = 0.6f + (Y*0.4f); //app.DebugPrintf("Luminance = %f, brightness = %f\n", Y, br); glColor3f(br,br,br); // Fog at the base near the world glFogi(GL_FOG_MODE, GL_LINEAR); glFogf(GL_FOG_START, HALO_RING_RADIUS); glFogf(GL_FOG_END, HALO_RING_RADIUS * 0.20f); Lighting::turnOn(); glDepthMask(false); textures->bindTexture(L"misc/haloRing.png"); // 4J was L"/1_2_2/misc/tunnel.png" Tesselator *t = Tesselator::getInstance(); bool prev = t->setMipmapEnable(true); glPushMatrix(); glRotatef(-90, 1, 0, 0); glRotatef(90, 0, 1, 0); glCallList(haloRingList); glPopMatrix(); t->setMipmapEnable(prev); glDepthMask(true); glEnable(GL_TEXTURE_2D); glEnable(GL_ALPHA_TEST); glDisable(GL_FOG); #endif } void LevelRenderer::renderClouds(float alpha) { int iTicks=ticks; int playerIndex = mc->player->GetXboxPad(); // if the primary player has clouds off, so do all players on this machine if(app.GetGameSettings(ProfileManager.GetPrimaryPad(),eGameSetting_Clouds)==0) { return; } // debug setting added to keep it at day time if (!mc->level->dimension->isNaturalDimension()) return; if (mc->options->fancyGraphics) { renderAdvancedClouds(alpha); return; } if(app.DebugSettingsOn()) { if(app.GetGameSettingsDebugMask(ProfileManager.GetPrimaryPad())&(1L<cameraTargetPlayer->yOld + (mc->cameraTargetPlayer->y - mc->cameraTargetPlayer->yOld) * alpha); int s = 32; int d = 256 / s; Tesselator *t = Tesselator::getInstance(); textures->bindTexture(TN_ENVIRONMENT_CLOUDS); // 4J was L"/environment/clouds.png" glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); Vec3 *cc = level[playerIndex]->getCloudColor(alpha); float cr = (float) cc->x; float cg = (float) cc->y; float cb = (float) cc->z; if (mc->options->anaglyph3d) { float crr = (cr * 30 + cg * 59 + cb * 11) / 100; float cgg = (cr * 30 + cg * 70) / (100); float cbb = (cr * 30 + cb * 70) / (100); cr = crr; cg = cgg; cb = cbb; } float scale = 1 / 2048.0f; double time = (ticks + alpha); double xo = mc->cameraTargetPlayer->xo + (mc->cameraTargetPlayer->x - mc->cameraTargetPlayer->xo) * alpha + time * 0.03f; double zo = mc->cameraTargetPlayer->zo + (mc->cameraTargetPlayer->z - mc->cameraTargetPlayer->zo) * alpha; int xOffs = Mth::floor(xo / 2048); int zOffs = Mth::floor(zo / 2048); xo -= xOffs * 2048; zo -= zOffs * 2048; float yy = (float) (level[playerIndex]->dimension->getCloudHeight() - yOffs + 0.33f); float uo = (float) (xo * scale); float vo = (float) (zo * scale); t->begin(); t->color(cr, cg, cb, 0.8f); for (int xx = -s * d; xx < +s * d; xx += s) { for (int zz = -s * d; zz < +s * d; zz += s) { t->vertexUV((float)(xx + 0), (float)( yy), (float)( zz + s), (float)( (xx + 0) * scale + uo), (float)( (zz + s) * scale + vo)); t->vertexUV((float)(xx + s), (float)( yy), (float)( zz + s), (float)( (xx + s) * scale + uo), (float)( (zz + s) * scale + vo)); t->vertexUV((float)(xx + s), (float)( yy), (float)( zz + 0), (float)( (xx + s) * scale + uo), (float)( (zz + 0) * scale + vo)); t->vertexUV((float)(xx + 0), (float)( yy), (float)( zz + 0), (float)( (xx + 0) * scale + uo), (float)( (zz + 0) * scale + vo)); } } t->end(); glColor4f(1, 1, 1, 1.0f); glDisable(GL_BLEND); glEnable(GL_CULL_FACE); if(app.DebugSettingsOn()) { if(!(app.GetGameSettingsDebugMask(ProfileManager.GetPrimaryPad())&(1L<begin(); for( int zt = 0; zt < D; zt++ ) { for( int xt = 0; xt < D; xt++ ) { float u = (((float) xt ) + 0.5f ) / 256.0f; float v = (((float) zt ) + 0.5f ) / 256.0f; float x0 = (float)xt; float x1 = x0 + 1.0f; float y0 = 0; float y1 = h; float z0 = (float)zt; float z1 = z0 + 1.0f; t->color(0.7f, 0.7f, 0.7f, 0.8f); t->normal(0, -1, 0); t->vertexUV(x0, y0, z0, u, v ); t->vertexUV(x1, y0, z0, u, v ); t->vertexUV(x1, y0, z1, u, v ); t->vertexUV(x0, y0, z1, u, v ); } } t->end(); } if( ( i == 1 ) || ( i == 6 ) ) { t->begin(); for( int zt = 0; zt < D; zt++ ) { for( int xt = 0; xt < D; xt++ ) { float u = (((float) xt ) + 0.5f ) / 256.0f; float v = (((float) zt ) + 0.5f ) / 256.0f; float x0 = (float)xt; float x1 = x0 + 1.0f; float y0 = 0; float y1 = h; float z0 = (float)zt; float z1 = z0 + 1.0f; t->color(1.0f, 1.0f, 1.0f, 0.8f); t->normal(0, 1, 0); t->vertexUV(x0, y1, z1, u, v ); t->vertexUV(x1, y1, z1, u, v ); t->vertexUV(x1, y1, z0, u, v ); t->vertexUV(x0, y1, z0, u, v ); } } t->end(); } if( ( i == 2 ) || ( i == 6 ) ) { t->begin(); for( int zt = 0; zt < D; zt++ ) { for( int xt = 0; xt < D; xt++ ) { float u = (((float) xt ) + 0.5f ) / 256.0f; float v = (((float) zt ) + 0.5f ) / 256.0f; float x0 = (float)xt; float x1 = x0 + 1.0f; float y0 = 0; float y1 = h; float z0 = (float)zt; float z1 = z0 + 1.0f; t->color(0.9f, 0.9f, 0.9f, 0.8f); t->normal(-1, 0, 0); t->vertexUV(x0, y0, z1, u, v ); t->vertexUV(x0, y1, z1, u, v ); t->vertexUV(x0, y1, z0, u, v ); t->vertexUV(x0, y0, z0, u, v ); } } t->end(); } if( ( i == 3 ) || ( i == 6 ) ) { t->begin(); for( int zt = 0; zt < D; zt++ ) { for( int xt = 0; xt < D; xt++ ) { float u = (((float) xt ) + 0.5f ) / 256.0f; float v = (((float) zt ) + 0.5f ) / 256.0f; float x0 = (float)xt; float x1 = x0 + 1.0f; float y0 = 0; float y1 = h; float z0 = (float)zt; float z1 = z0 + 1.0f; t->color(0.9f, 0.9f, 0.9f, 0.8f); t->normal(1, 0, 0); t->vertexUV(x1, y0, z0, u, v ); t->vertexUV(x1, y1, z0, u, v ); t->vertexUV(x1, y1, z1, u, v ); t->vertexUV(x1, y0, z1, u, v ); } } t->end(); } if( ( i == 4 ) || ( i == 6 ) ) { t->begin(); for( int zt = 0; zt < D; zt++ ) { for( int xt = 0; xt < D; xt++ ) { float u = (((float) xt ) + 0.5f ) / 256.0f; float v = (((float) zt ) + 0.5f ) / 256.0f; float x0 = (float)xt; float x1 = x0 + 1.0f; float y0 = 0; float y1 = h; float z0 = (float)zt; float z1 = z0 + 1.0f; t->color(0.8f, 0.8f, 0.8f, 0.8f); t->normal(-1, 0, 0); t->vertexUV(x0, y1, z0, u, v ); t->vertexUV(x1, y1, z0, u, v ); t->vertexUV(x1, y0, z0, u, v ); t->vertexUV(x0, y0, z0, u, v ); } } t->end(); } if( ( i == 5 ) || ( i == 6 ) ) { t->begin(); for( int zt = 0; zt < D; zt++ ) { for( int xt = 0; xt < D; xt++ ) { float u = (((float) xt ) + 0.5f ) / 256.0f; float v = (((float) zt ) + 0.5f ) / 256.0f; float x0 = (float)xt; float x1 = x0 + 1.0f; float y0 = 0; float y1 = h; float z0 = (float)zt; float z1 = z0 + 1.0f; t->color(0.8f, 0.8f, 0.8f, 0.8f); t->normal(1, 0, 0); t->vertexUV(x0, y0, z1, u, v ); t->vertexUV(x1, y0, z1, u, v ); t->vertexUV(x1, y1, z1, u, v ); t->vertexUV(x0, y1, z1, u, v ); } } t->end(); } glEndList(); } } void LevelRenderer::renderAdvancedClouds(float alpha) { // MGH - added, we were getting dark clouds sometimes on PS3, with this being setup incorrectly glMultiTexCoord2f(GL_TEXTURE1, 0, 0); // 4J - most of our viewports are now rendered with no clip planes but using stencilling to limit the area drawn to. Clouds have a relatively large fill area compared to // the number of vertices that they have, and so enabling clipping here to try and reduce fill rate cost. RenderManager.StateSetEnableViewportClipPlanes(true); float yOffs = (float) (mc->cameraTargetPlayer->yOld + (mc->cameraTargetPlayer->y - mc->cameraTargetPlayer->yOld) * alpha); Tesselator *t = Tesselator::getInstance(); int playerIndex = mc->player->GetXboxPad(); int iTicks=ticks; if(app.DebugSettingsOn()) { if(app.GetGameSettingsDebugMask(ProfileManager.GetPrimaryPad())&(1L<cameraTargetPlayer->xo + (mc->cameraTargetPlayer->x - mc->cameraTargetPlayer->xo) * alpha + time * 0.03f) / ss; double zo = (mc->cameraTargetPlayer->zo + (mc->cameraTargetPlayer->z - mc->cameraTargetPlayer->zo) * alpha) / ss + 0.33f; float yy = (float) (level[playerIndex]->dimension->getCloudHeight() - yOffs + 0.33f); int xOffs = Mth::floor(xo / 2048); int zOffs = Mth::floor(zo / 2048); xo -= xOffs * 2048; zo -= zOffs * 2048; // 4J - we are now conditionally rendering the clouds in two ways // (1) if we are (by our y height) in the clouds, then we render in a mode quite like the original, with no backface culling, and // decisions on which sides of the clouds to render based on the positions of the 8x8 blocks of cloud texels // (2) if we aren't in the clouds, then we do a simpler form of rendering with backface culling on // This is because the complex sort of rendering is really there so that the clouds seem more solid when you might be in them, but it has more risk of artifacts so // we don't want to do it when not necessary bool noBFCMode = ( (yy > -h - 1) && (yy <= h + 1) ); if( noBFCMode ) { glDisable(GL_CULL_FACE); } else { glEnable(GL_CULL_FACE); } MemSect(31); textures->bindTexture(TN_ENVIRONMENT_CLOUDS); // 4J was L"/environment/clouds.png" MemSect(0); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); Vec3 *cc = level[playerIndex]->getCloudColor(alpha); float cr = (float) cc->x; float cg = (float) cc->y; float cb = (float) cc->z; if (mc->options->anaglyph3d) { float crr = (cr * 30 + cg * 59 + cb * 11) / 100; float cgg = (cr * 30 + cg * 70) / (100); float cbb = (cr * 30 + cb * 70) / (100); cr = crr; cg = cgg; cb = cbb; } float uo = (float) (xo * 0); float vo = (float) (zo * 0); float scale = 1 / 256.0f; uo = (float) (Mth::floor(xo)) * scale; vo = (float) (Mth::floor(zo)) * scale; // 4J - keep our UVs +ve - there's a small bug in the xbox GPU that incorrectly rounds small -ve UVs (between -1/(64*size) and 0) up to 0, which leaves gaps in our clouds... while( uo < 1.0f ) uo += 1.0f; while( vo < 1.0f ) vo += 1.0f; float xoffs = (float) (xo - Mth::floor(xo)); float zoffs = (float) (zo - Mth::floor(zo)); int D = 8; int radius = 3; if( activePlayers() > 2 ) radius = 2; // 4J - reduce the cloud render distance a bit for 3 & 4 player split screen float e = 1 / 1024.0f; glScalef(ss, 1, ss); FrustumData* pFrustumData = Frustum::getFrustum(); for (int pass = 0; pass < 2; pass++) { if (pass == 0) { // 4J - changed to use blend rather than color mask to avoid writing to frame buffer, to work with our command buffers glBlendFunc(GL_ZERO, GL_ONE); // glColorMask(false, false, false, false); } else { // 4J - changed to use blend rather than color mask to avoid writing to frame buffer, to work with our command buffers glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // glColorMask(true, true, true, true); } for (int xPos = -radius + 1; xPos <= radius; xPos++) { for (int zPos = -radius + 1; zPos <= radius; zPos++) { // 4J - reimplemented the clouds with full cube-per-texel geometry to get rid of seams. This is a huge amount more quads to render, so // now using command buffers to render each section to cut CPU hit. #if 1 float xx = (float)(xPos * D); float zz = (float)(zPos * D); float xp = xx - xoffs; float zp = zz - zoffs; if( !pFrustumData->cubeInFrustum(0+xp,0+yy,0+zp, 8+xp,4+yy,8+zp) ) continue; glMatrixMode(GL_TEXTURE); glLoadIdentity(); glTranslatef(xx / 256.0f + uo, zz / 256.0f + vo, 0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glTranslatef(xp,yy,zp); glColor4f(cr, cg, cb, 1.0f ); if( noBFCMode ) { // This is the more complex form of render the clouds, based on the way that the original code picked which sides to render, with backface culling disabled. // This is to give a more solid version of the clouds for when the player might be inside them. bool draw[6] = {false,false,false,false,false,false}; // These rules to decide which sides to draw are the same as the original code below if (yy > -h - 1) draw[0] = true; if (yy <= h + 1) draw[1] = true; if (xPos > -1) draw[2] = true; if (xPos <= 1) draw[3] = true; if (zPos > -1) draw[4] = true; if (zPos <= 1) draw[5] = true; // Top and bottom just render when required if( draw[0] ) glCallList(cloudList); if( draw[1] ) glCallList(cloudList + 1); // For x facing sides, if we are actually in the clouds and about to draw both sides of the x sides too, then // do a little offsetting here to avoid z fighting if( draw[0] && draw[1] && draw[2] && draw[3] ) { glTranslatef(e, 0.0f, 0.0f ); glCallList(cloudList + 2); glTranslatef(-e, 0.0f, 0.0f ); glCallList(cloudList + 3); } else { if( draw[2] ) glCallList(cloudList + 2); if( draw[3] ) glCallList(cloudList + 3); } // For z facing sides, if we are actually in the clouds and about to draw both sides of the z sides too, then // do a little offsetting here to avoid z fighting if( draw[0] && draw[1] && draw[4] && draw[5] ) { glTranslatef(0.0f, 0.0f, e ); glCallList(cloudList + 4); glTranslatef(0.0f, 0.0f, -e ); glCallList(cloudList + 5); } else { if( draw[4] ) glCallList(cloudList + 4); if( draw[5] ) glCallList(cloudList + 5); } } else { // Simpler form of rendering that we can do most of the time, when we aren't potentially inside a cloud glCallList(cloudList + 6); } glPopMatrix(); glMatrixMode(GL_TEXTURE); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); #else t->begin(); float xx = (float)(xPos * D); float zz = (float)(zPos * D); float xp = xx - xoffs; float zp = zz - zoffs; if (yy > -h - 1) { t->color(cr * 0.7f, cg * 0.7f, cb * 0.7f, 0.8f); t->normal(0, -1, 0); t->vertexUV((float)(xp + 0), (float)( yy + 0), (float)( zp + D), (float)( (xx + 0) * scale + uo), (float)( (zz + D) * scale + vo)); t->vertexUV((float)(xp + D), (float)( yy + 0), (float)( zp + D), (float)( (xx + D) * scale + uo), (float)( (zz + D) * scale + vo)); t->vertexUV((float)(xp + D), (float)( yy + 0), (float)( zp + 0), (float)( (xx + D) * scale + uo), (float)( (zz + 0) * scale + vo)); t->vertexUV((float)(xp + 0), (float)( yy + 0), (float)( zp + 0), (float)( (xx + 0) * scale + uo), (float)( (zz + 0) * scale + vo)); } if (yy <= h + 1) { t->color(cr, cg, cb, 0.8f); t->normal(0, 1, 0); t->vertexUV((float)(xp + 0), (float)( yy + h - e), (float)( zp + D), (float)( (xx + 0) * scale + uo), (float)( (zz + D) * scale + vo)); t->vertexUV((float)(xp + D), (float)( yy + h - e), (float)( zp + D), (float)( (xx + D) * scale + uo), (float)( (zz + D) * scale + vo)); t->vertexUV((float)(xp + D), (float)( yy + h - e), (float)( zp + 0), (float)( (xx + D) * scale + uo), (float)( (zz + 0) * scale + vo)); t->vertexUV((float)(xp + 0), (float)( yy + h - e), (float)( zp + 0), (float)( (xx + 0) * scale + uo), (float)( (zz + 0) * scale + vo)); } t->color(cr * 0.9f, cg * 0.9f, cb * 0.9f, 0.8f); if (xPos > -1) { t->normal(-1, 0, 0); for (int i = 0; i < D; i++) { t->vertexUV((float)(xp + i + 0), (float)( yy + 0), (float)( zp + D), (float)( (xx + i + 0.5f) * scale + uo), (float)( (zz + D) * scale + vo)); t->vertexUV((float)(xp + i + 0), (float)( yy + h), (float)( zp + D), (float)( (xx + i + 0.5f) * scale + uo), (float)( (zz + D) * scale + vo)); t->vertexUV((float)(xp + i + 0), (float)( yy + h), (float)( zp + 0), (float)( (xx + i + 0.5f) * scale + uo), (float)( (zz + 0) * scale + vo)); t->vertexUV((float)(xp + i + 0), (float)( yy + 0), (float)( zp + 0), (float)( (xx + i + 0.5f) * scale + uo), (float)( (zz + 0) * scale + vo)); } } if (xPos <= 1) { t->normal(+1, 0, 0); for (int i = 0; i < D; i++) { t->vertexUV((float)(xp + i + 1 - e), (float)( yy + 0), (float)( zp + D), (float)( (xx + i + 0.5f) * scale + uo), (float)( (zz + D) * scale + vo)); t->vertexUV((float)(xp + i + 1 - e), (float)( yy + h), (float)( zp + D), (float)( (xx + i + 0.5f) * scale + uo), (float)( (zz + D) * scale + vo)); t->vertexUV((float)(xp + i + 1 - e), (float)( yy + h), (float)( zp + 0), (float)( (xx + i + 0.5f) * scale + uo), (float)( (zz + 0) * scale + vo)); t->vertexUV((float)(xp + i + 1 - e), (float)( yy + 0), (float)( zp + 0), (float)( (xx + i + 0.5f) * scale + uo), (float)( (zz + 0) * scale + vo)); } } t->color(cr * 0.8f, cg * 0.8f, cb * 0.8f, 0.8f); if (zPos > -1) { t->normal(0, 0, -1); for (int i = 0; i < D; i++) { t->vertexUV((float)(xp + 0), (float)( yy + h), (float)( zp + i + 0), (float)( (xx + 0) * scale + uo), (float)( (zz + i + 0.5f) * scale + vo)); t->vertexUV((float)(xp + D), (float)( yy + h), (float)( zp + i + 0), (float)( (xx + D) * scale + uo), (float)( (zz + i + 0.5f) * scale + vo)); t->vertexUV((float)(xp + D), (float)( yy + 0), (float)( zp + i + 0), (float)( (xx + D) * scale + uo), (float)( (zz + i + 0.5f) * scale + vo)); t->vertexUV((float)(xp + 0), (float)( yy + 0), (float)( zp + i + 0), (float)( (xx + 0) * scale + uo), (float)( (zz + i + 0.5f) * scale + vo)); } } if (zPos <= 1) { t->normal(0, 0, 1); for (int i = 0; i < D; i++) { t->vertexUV((float)(xp + 0), (float)( yy + h), (float)( zp + i + 1 - e), (float)( (xx + 0) * scale + uo), (float)( (zz + i + 0.5f) * scale + vo)); t->vertexUV((float)(xp + D), (float)( yy + h), (float)( zp + i + 1 - e), (float)( (xx + D) * scale + uo), (float)( (zz + i + 0.5f) * scale + vo)); t->vertexUV((float)(xp + D), (float)( yy + 0), (float)( zp + i + 1 - e), (float)( (xx + D) * scale + uo), (float)( (zz + i + 0.5f) * scale + vo)); t->vertexUV((float)(xp + 0), (float)( yy + 0), (float)( zp + i + 1 - e), (float)( (xx + 0) * scale + uo), (float)( (zz + i + 0.5f) * scale + vo)); } } t->end(); #endif } } } glColor4f(1, 1, 1, 1.0f); glDisable(GL_BLEND); glEnable(GL_CULL_FACE); if(app.DebugSettingsOn()) { if(!(app.GetGameSettingsDebugMask(ProfileManager.GetPrimaryPad())&(1L< > nearestClipChunks; #endif ClipChunk *nearChunk = NULL; // Nearest chunk that is dirty int veryNearCount = 0; int minDistSq = 0x7fffffff; // Distances to this chunk // Set a flag if we should only rebuild existing chunks, not create anything new unsigned int memAlloc = RenderManager.CBuffSize(-1); /* static int throttle = 0; if( ( throttle % 100 ) == 0 ) { app.DebugPrintf("CBuffSize: %d\n",memAlloc/(1024*1024)); } throttle++; */ PIXAddNamedCounter(((float)memAlloc)/(1024.0f*1024.0f),"Command buffer allocations"); bool onlyRebuild = ( memAlloc >= MAX_COMMANDBUFFER_ALLOCATIONS ); EnterCriticalSection(&m_csDirtyChunks); // Move any dirty chunks stored in the lock free stack into global flags int index = 0; do { // See comment on dirtyChunksLockFreeStack.Push() regarding details of this casting/subtracting -2. index = (size_t)dirtyChunksLockFreeStack.Pop(); #ifdef _CRITICAL_CHUNKS int oldIndex = index; index &= 0x0fffffff; // remove the top bit that marked the chunk as non-critical #endif if( index == 1 ) dirtyChunkPresent = true; // 1 is a special value passed to let this thread know that a chunk which isn't on this stack has been set to dirty else if( index > 1 ) { int i2 = index - 2; if( i2 >= DIMENSION_OFFSETS[2] ) { i2 -= DIMENSION_OFFSETS[2]; int y2 = i2 & (CHUNK_Y_COUNT-1); i2 /= CHUNK_Y_COUNT; int z2 = i2 / MAX_LEVEL_RENDER_SIZE[2]; int x2 = i2 - z2 * MAX_LEVEL_RENDER_SIZE[2]; x2 -= MAX_LEVEL_RENDER_SIZE[2] / 2; z2 -= MAX_LEVEL_RENDER_SIZE[2] / 2; } setGlobalChunkFlag(index - 2, CHUNK_FLAG_DIRTY); #ifdef _CRITICAL_CHUNKS if( !(oldIndex & 0x10000000) ) // was this chunk not marked as non-critical. Ugh double negatives { setGlobalChunkFlag(index - 2, CHUNK_FLAG_CRITICAL); } #endif dirtyChunkPresent = true; } } while( index ); // Only bother searching round all the chunks if we have some dirty chunk(s) if( dirtyChunkPresent ) { lastDirtyChunkFound = System::currentTimeMillis(); PIXBeginNamedEvent(0,"Finding nearest chunk\n"); #if defined __PS3__ && !defined DISABLE_SPU_CODE // find the nearest chunk with a spu task, copy all the data over here for uploading to SPU g_findNearestChunkDataIn.numGlobalChunks = getGlobalChunkCount(); g_findNearestChunkDataIn.pGlobalChunkFlags = globalChunkFlags; g_findNearestChunkDataIn.onlyRebuild = onlyRebuild; g_findNearestChunkDataIn.lowerOffset = (int)&((LevelChunk*)0)->lowerBlocks; // dodgy bit of class structure poking, as we don't want to try and get the whole of LevelChunk copmpiling on SPU g_findNearestChunkDataIn.upperOffset = (int)&((LevelChunk*)0)->upperBlocks; g_findNearestChunkDataIn.xChunks = xChunks; g_findNearestChunkDataIn.yChunks = yChunks; g_findNearestChunkDataIn.zChunks = zChunks; for(int i=0;i<4;i++) { g_findNearestChunkDataIn.chunks[i] = (LevelRenderer_FindNearestChunk_DataIn::ClipChunk*)chunks[i].data; g_findNearestChunkDataIn.chunkLengths[i] = chunks[i].length; g_findNearestChunkDataIn.level[i] = level[i]; g_findNearestChunkDataIn.playerData[i].bValid = mc->localplayers[i] != NULL; if(mc->localplayers[i] != NULL) { g_findNearestChunkDataIn.playerData[i].x = mc->localplayers[i]->x; g_findNearestChunkDataIn.playerData[i].y = mc->localplayers[i]->y; g_findNearestChunkDataIn.playerData[i].z = mc->localplayers[i]->z; } if(level[i] != NULL) { g_findNearestChunkDataIn.multiplayerChunkCache[i].XZOFFSET = ((MultiPlayerChunkCache*)(level[i]->chunkSource))->XZOFFSET; g_findNearestChunkDataIn.multiplayerChunkCache[i].XZSIZE = ((MultiPlayerChunkCache*)(level[i]->chunkSource))->XZSIZE; g_findNearestChunkDataIn.multiplayerChunkCache[i].cache = (void**)((MultiPlayerChunkCache*)(level[i]->chunkSource))->cache; } } // assert(sizeof(LevelRenderer_FindNearestChunk_DataIn::Chunk) == sizeof(Chunk)); C4JSpursJob_LevelRenderer_FindNearestChunk findJob(&g_findNearestChunkDataIn); m_jobPort_FindNearestChunk->submitJob(&findJob); m_jobPort_FindNearestChunk->waitForCompletion(); nearChunk = (ClipChunk*)g_findNearestChunkDataIn.nearChunk; veryNearCount = g_findNearestChunkDataIn.veryNearCount; #else // __PS3__ #ifdef _LARGE_WORLDS int maxNearestChunks = MAX_CONCURRENT_CHUNK_REBUILDS; // 4J Stu - On XboxOne we should cut this down if in a constrained state so the saving threads get more time #endif // Find nearest chunk that is dirty for( int p = 0; p < XUSER_MAX_COUNT; p++ ) { // It's possible that the localplayers member can be set to NULL on the main thread when a player chooses to exit the game // So take a reference to the player object now. As it is a shared_ptr it should live as long as we need it shared_ptr player = mc->localplayers[p]; if( player == NULL ) continue; if( chunks[p].data == NULL ) continue; if( level[p] == NULL ) continue; if( chunks[p].length != xChunks * zChunks * CHUNK_Y_COUNT ) continue; int px = (int)player->x; int py = (int)player->y; int pz = (int)player->z; // app.DebugPrintf("!! %d %d %d, %d %d %d {%d,%d} ",px,py,pz,stackChunkDirty,nonStackChunkDirty,onlyRebuild, xChunks, zChunks); int considered = 0; int wouldBeNearButEmpty = 0; for( int x = 0; x < xChunks; x++ ) { for( int z = 0; z < zChunks; z++ ) { for( int y = 0; y < CHUNK_Y_COUNT; y++ ) { ClipChunk *pClipChunk = &chunks[p][(z * yChunks + y) * xChunks + x]; // Get distance to this chunk - deliberately not calling the chunk's method of doing this to avoid overheads (passing entitie, type conversion etc.) that this involves int xd = pClipChunk->xm - px; int yd = pClipChunk->ym - py; int zd = pClipChunk->zm - pz; int distSq = xd * xd + yd * yd + zd * zd; int distSqWeighted = xd * xd + yd * yd * 4 + zd * zd; // Weighting against y to prioritise things in same x/z plane as player first if( globalChunkFlags[ pClipChunk->globalIdx ] & CHUNK_FLAG_DIRTY ) { if( (!onlyRebuild) || globalChunkFlags[ pClipChunk->globalIdx ] & CHUNK_FLAG_COMPILED || ( distSq < 20 * 20 ) ) // Always rebuild really near things or else building (say) at tower up into empty blocks when we are low on memory will not create render data { considered++; // Is this chunk nearer than our nearest? #ifdef _LARGE_WORLDS bool isNearer = nearestClipChunks.empty(); AUTO_VAR(itNearest, nearestClipChunks.begin()); for(; itNearest != nearestClipChunks.end(); ++itNearest) { isNearer = distSqWeighted < itNearest->second; if(isNearer) break; } isNearer = isNearer || (nearestClipChunks.size() < maxNearestChunks); #else bool isNearer = distSqWeighted < minDistSq; #endif #ifdef _CRITICAL_CHUNKS // AP - this will make sure that if a deferred grouping has started, only critical chunks go into that // grouping, even if a non-critical chunk is closer. if( (!veryNearCount && isNearer) || (distSq < 20 * 20 && (globalChunkFlags[ pClipChunk->globalIdx ] & CHUNK_FLAG_CRITICAL)) ) #else if( isNearer ) #endif { // At this point we've got a chunk that we would like to consider for rendering, at least based on its proximity to the player(s). // Its *quite* quick to generate empty render data for render chunks, but if we let the rebuilding do that then the after rebuilding we will have // to start searching for the next nearest chunk from scratch again. Instead, its better to detect empty chunks at this stage, flag them up as not dirty // (and empty), and carry on. The levelchunk's isRenderChunkEmpty method can be quite optimal as it can make use of the chunk's data compression to detect // emptiness without actually testing as many data items as uncompressed data would. Chunk *chunk = pClipChunk->chunk; LevelChunk *lc = level[p]->getChunkAt(chunk->x,chunk->z); if( !lc->isRenderChunkEmpty(y * 16) ) { nearChunk = pClipChunk; minDistSq = distSqWeighted; #ifdef _LARGE_WORLDS nearestClipChunks.insert(itNearest, std::pair(nearChunk, minDistSq) ); if(nearestClipChunks.size() > maxNearestChunks) { nearestClipChunks.pop_back(); } #endif } else { chunk->clearDirty(); globalChunkFlags[ pClipChunk->globalIdx ] |= CHUNK_FLAG_EMPTYBOTH; wouldBeNearButEmpty++; } } #ifdef _CRITICAL_CHUNKS // AP - is the chunk near and also critical if( distSq < 20 * 20 && ((globalChunkFlags[ pClipChunk->globalIdx ] & CHUNK_FLAG_CRITICAL)) ) #else if( distSq < 20 * 20 ) #endif { veryNearCount++; } } } } } } // app.DebugPrintf("[%d,%d,%d]\n",nearestClipChunks.empty(),considered,wouldBeNearButEmpty); } #endif // __PS3__ PIXEndNamedEvent(); } Chunk *chunk = NULL; #ifdef _LARGE_WORLDS if(!nearestClipChunks.empty()) { int index = 0; for(AUTO_VAR(it, nearestClipChunks.begin()); it != nearestClipChunks.end(); ++it) { chunk = it->first->chunk; // If this chunk is very near, then move the renderer into a deferred mode. This won't commit any command buffers // for rendering until we call CBuffDeferredModeEnd(), allowing us to group any near changes into an atomic unit. This // is essential so we don't temporarily create any holes in the environment whilst updating one chunk and not the neighbours. // The "ver near" aspect of this is just a cosmetic nicety - exactly the same thing would happen further away, but we just don't // care about it so much from terms of visual impact. if( veryNearCount > 0 ) { RenderManager.CBuffDeferredModeStart(); } // Build this chunk & return false to continue processing chunk->clearDirty(); // Take a copy of the details that are required for chunk rebuilding, and rebuild That instead of the original chunk data. This is done within // the m_csDirtyChunks critical section, which means that any chunks can't be repositioned whilst we are doing this copy. The copy will then // be guaranteed to be consistent whilst rebuilding takes place outside of that critical section. permaChunk[index].makeCopyForRebuild(chunk); ++index; } LeaveCriticalSection(&m_csDirtyChunks); --index; // Bring it back into 0 counted range for(int i = MAX_CHUNK_REBUILD_THREADS - 1; i >= 0; --i) { // Set the events that won't run if( (i+1) > index) s_rebuildCompleteEvents->Set(i); else break; } for(; index >=0; --index) { bool bAtomic = false; if((veryNearCount > 0)) bAtomic = true; //MGH - if veryNearCount, then we're trying to rebuild atomically, so do it all on the main thread if( bAtomic || (index == 0) ) { //PIXBeginNamedEvent(0,"Rebuilding near chunk %d %d %d",chunk->x, chunk->y, chunk->z); // static __int64 totalTime = 0; // static __int64 countTime = 0; // __int64 startTime = System::currentTimeMillis(); //app.DebugPrintf("Rebuilding permaChunk %d\n", index); permaChunk[index].rebuild(); if(index !=0) s_rebuildCompleteEvents->Set(index-1); // MGH - this rebuild happening on the main thread instead, mark the thread it should have been running on as complete // __int64 endTime = System::currentTimeMillis(); // totalTime += (endTime - startTime); // countTime++; // printf("%d : %f\n", countTime, (float)totalTime / (float)countTime); //PIXEndNamedEvent(); } // 4J Stu - Ignore this path when in constrained mode on Xbox One else { // Activate thread to rebuild this chunk s_activationEventA[index - 1]->Set(); } } // Wait for the other threads to be done as well s_rebuildCompleteEvents->WaitForAll(INFINITE); } #else if( nearChunk ) { chunk = nearChunk->chunk; PIXBeginNamedEvent(0,"Rebuilding near chunk %d %d %d",chunk->x, chunk->y, chunk->z); // If this chunk is very near, then move the renderer into a deferred mode. This won't commit any command buffers // for rendering until we call CBuffDeferredModeEnd(), allowing us to group any near changes into an atomic unit. This // is essential so we don't temporarily create any holes in the environment whilst updating one chunk and not the neighbours. // The "ver near" aspect of this is just a cosmetic nicety - exactly the same thing would happen further away, but we just don't // care about it so much from terms of visual impact. if( veryNearCount > 0 ) { RenderManager.CBuffDeferredModeStart(); } // Build this chunk & return false to continue processing chunk->clearDirty(); // Take a copy of the details that are required for chunk rebuilding, and rebuild That instead of the original chunk data. This is done within // the m_csDirtyChunks critical section, which means that any chunks can't be repositioned whilst we are doing this copy. The copy will then // be guaranteed to be consistent whilst rebuilding takes place outside of that critical section. static Chunk permaChunk; permaChunk.makeCopyForRebuild(chunk); LeaveCriticalSection(&m_csDirtyChunks); // static __int64 totalTime = 0; // static __int64 countTime = 0; // __int64 startTime = System::currentTimeMillis(); permaChunk.rebuild(); // __int64 endTime = System::currentTimeMillis(); // totalTime += (endTime - startTime); // countTime++; // printf("%d : %f\n", countTime, (float)totalTime / (float)countTime); PIXEndNamedEvent(); } #endif else { // Nothing to do - clear flags that there are things to process, unless it's been a while since we found any dirty chunks in which case force a check next time through if( ( System::currentTimeMillis() - lastDirtyChunkFound ) > FORCE_DIRTY_CHUNK_CHECK_PERIOD_MS ) { dirtyChunkPresent = true; } else { dirtyChunkPresent = false; } LeaveCriticalSection(&m_csDirtyChunks); #ifdef __PS3__ Sleep(5); #endif // __PS3__ return false; } // If there was more than one very near thing found in our initial assessment, then return true so that we will keep doing the other one(s) // in an atomic unit if( veryNearCount > 1 ) { destroyedTileManager->updatedChunkAt(chunk->level, chunk->x, chunk->y, chunk->z, veryNearCount ); return true; } // If the chunk we've just built was near, and it has been marked dirty at some point while we are rebuilding, also return true so // we can rebuild the same thing atomically - if its data was changed during creating render data, it may well be invalid if( ( veryNearCount == 1 ) && getGlobalChunkFlag(chunk->x, chunk->y, chunk->z, chunk->level, CHUNK_FLAG_DIRTY ) ) { destroyedTileManager->updatedChunkAt(chunk->level, chunk->x, chunk->y, chunk->z, veryNearCount + 1); return true; } if( nearChunk ) destroyedTileManager->updatedChunkAt(chunk->level, chunk->x, chunk->y, chunk->z, veryNearCount ); return false; } void LevelRenderer::renderHit(shared_ptr player, HitResult *h, int mode, shared_ptr inventoryItem, float a) { Tesselator *t = Tesselator::getInstance(); glEnable(GL_BLEND); glEnable(GL_ALPHA_TEST); glBlendFunc(GL_SRC_ALPHA, GL_ONE); glColor4f(1, 1, 1, ((float) (Mth::sin(Minecraft::currentTimeMillis() / 100.0f)) * 0.2f + 0.4f) * 0.5f); if (mode != 0 && inventoryItem != NULL) { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); float br = (Mth::sin(Minecraft::currentTimeMillis() / 100.0f) * 0.2f + 0.8f); glColor4f(br, br, br, (Mth::sin(Minecraft::currentTimeMillis() / 200.0f) * 0.2f + 0.5f)); textures->bindTexture(TN_TERRAIN); //L"/terrain.png"); } glDisable(GL_BLEND); glDisable(GL_ALPHA_TEST); } void LevelRenderer::renderDestroyAnimation(Tesselator *t, shared_ptr player, float a) { double xo = player->xOld + (player->x - player->xOld) * a; double yo = player->yOld + (player->y - player->yOld) * a; double zo = player->zOld + (player->z - player->zOld) * a; int playerIndex = mc->player->GetXboxPad(); if (!destroyingBlocks.empty()) { glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR); textures->bindTexture(TN_TERRAIN); // 4J was L"/terrain.png" glColor4f(1, 1, 1, 0.5f); glPushMatrix(); glDisable(GL_ALPHA_TEST); glPolygonOffset(-3.0f, -3.0f); glEnable(GL_POLYGON_OFFSET_FILL); glEnable(GL_ALPHA_TEST); t->begin(); t->offset((float)-xo, (float)-yo,(float) -zo); t->noColor(); AUTO_VAR(it, destroyingBlocks.begin()); while (it != destroyingBlocks.end()) { BlockDestructionProgress *block = it->second; double xd = block->getX() - xo; double yd = block->getY() - yo; double zd = block->getZ() - zo; if (xd * xd + yd * yd + zd * zd < 32 * 32) // 4J MGH - now only culling instead of removing, as the list is shared in split screen { int iPad = mc->player->GetXboxPad(); // 4J added int tileId = level[iPad]->getTile(block->getX(), block->getY(), block->getZ()); Tile *tile = tileId > 0 ? Tile::tiles[tileId] : NULL; if (tile == NULL) tile = Tile::rock; tileRenderer[iPad]->tesselateInWorldFixedTexture(tile, block->getX(), block->getY(), block->getZ(), breakingTextures[block->getProgress()]); // 4J renamed to differentiate from tesselateInWorld } ++it; } t->end(); t->offset(0, 0, 0); glDisable(GL_ALPHA_TEST); /* * for (int i = 0; i < 6; i++) { tile.renderFace(t, h.x, h.y, * h.z, i, 15 * 16 + (int) (destroyProgress * 10)); } */ glPolygonOffset(0.0f, 0.0f); glDisable(GL_POLYGON_OFFSET_FILL); glEnable(GL_ALPHA_TEST); glDepthMask(true); glPopMatrix(); } } void LevelRenderer::renderHitOutline(shared_ptr player, HitResult *h, int mode, shared_ptr inventoryItem, float a) { if (mode == 0 && h->type == HitResult::TILE) { int iPad = mc->player->GetXboxPad(); // 4J added // 4J-PB - If Display HUD is false, don't render the hit outline if ( app.GetGameSettings(iPad,eGameSetting_DisplayHUD)==0 ) return; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4f(0, 0, 0, 0.4f); glLineWidth(2.0f); glDisable(GL_TEXTURE_2D); glDepthMask(false); float ss = 0.002f; int tileId = level[iPad]->getTile(h->x, h->y, h->z); if (tileId > 0) { Tile::tiles[tileId]->updateShape(level[iPad], h->x, h->y, h->z); double xo = player->xOld + (player->x - player->xOld) * a; double yo = player->yOld + (player->y - player->yOld) * a; double zo = player->zOld + (player->z - player->zOld) * a; render(Tile::tiles[tileId]->getTileAABB(level[iPad], h->x, h->y, h->z)->grow(ss, ss, ss)->cloneMove(-xo, -yo, -zo)); } glDepthMask(true); glEnable(GL_TEXTURE_2D); glDisable(GL_BLEND); } } void LevelRenderer::render(AABB *b) { Tesselator *t = Tesselator::getInstance(); t->begin(GL_LINE_STRIP); t->vertex((float)(b->x0), (float)( b->y0), (float)( b->z0)); t->vertex((float)(b->x1), (float)( b->y0), (float)( b->z0)); t->vertex((float)(b->x1), (float)( b->y0), (float)( b->z1)); t->vertex((float)(b->x0), (float)( b->y0), (float)( b->z1)); t->vertex((float)(b->x0), (float)( b->y0), (float)( b->z0)); t->end(); t->begin(GL_LINE_STRIP); t->vertex((float)(b->x0), (float)( b->y1), (float)( b->z0)); t->vertex((float)(b->x1), (float)( b->y1), (float)( b->z0)); t->vertex((float)(b->x1), (float)( b->y1), (float)( b->z1)); t->vertex((float)(b->x0), (float)( b->y1), (float)( b->z1)); t->vertex((float)(b->x0), (float)( b->y1), (float)( b->z0)); t->end(); t->begin(GL_LINES); t->vertex((float)(b->x0), (float)( b->y0), (float)( b->z0)); t->vertex((float)(b->x0), (float)( b->y1), (float)( b->z0)); t->vertex((float)(b->x1), (float)( b->y0), (float)( b->z0)); t->vertex((float)(b->x1), (float)( b->y1), (float)( b->z0)); t->vertex((float)(b->x1), (float)( b->y0), (float)( b->z1)); t->vertex((float)(b->x1), (float)( b->y1), (float)( b->z1)); t->vertex((float)(b->x0), (float)( b->y0), (float)( b->z1)); t->vertex((float)(b->x0), (float)( b->y1), (float)( b->z1)); t->end(); } void LevelRenderer::setDirty(int x0, int y0, int z0, int x1, int y1, int z1, Level *level) // 4J - added level param { // 4J - level is passed if this is coming from setTilesDirty, which could come from when connection is being ticked outside of normal level tick, and player won't // be set up if( level == NULL ) level = this->level[mc->player->GetXboxPad()]; // EnterCriticalSection(&m_csDirtyChunks); int _x0 = Mth::intFloorDiv(x0, CHUNK_XZSIZE); int _y0 = Mth::intFloorDiv(y0, CHUNK_SIZE); int _z0 = Mth::intFloorDiv(z0, CHUNK_XZSIZE); int _x1 = Mth::intFloorDiv(x1, CHUNK_XZSIZE); int _y1 = Mth::intFloorDiv(y1, CHUNK_SIZE); int _z1 = Mth::intFloorDiv(z1, CHUNK_XZSIZE); for (int x = _x0; x <= _x1; x++) { for (int y = _y0; y <= _y1; y++) { for (int z = _z0; z <= _z1; z++) { // printf("Setting %d %d %d dirty\n",x,y,z); int index = getGlobalIndexForChunk(x * 16, y * 16, z * 16, level); // Rather than setting the flags directly, add any dirty chunks into a lock free stack - this avoids having to lock m_csDirtyChunks . // These chunks are then added to the global flags in the render update thread. // An XLockFreeQueue actually implements a queue of pointers to its templated type, and I don't want to have to go allocating ints here just to store the // pointer to them in a queue. Hence actually pretending that the int Is a pointer here. Our Index has a a valid range from 0 to something quite big, // but including zero. The lock free queue, since it thinks it is dealing with pointers, uses a NULL pointer to signify that a Pop hasn't succeeded. // We also want to reserve one special value (of 1 ) for use when multiple chunks not individually listed are made dirty. Therefore adding 2 to our // index value here to move our valid range from 1 to something quite big + 2 if( index > -1 ) { #ifdef _CRITICAL_CHUNKS index += 2; // AP - by the time we reach this function the area passed in has a 1 block border added to it to make sure geometry and lighting is updated correctly. // Some of those blocks will only need lighting updated so it is acceptable to not have those blocks grouped in the deferral system as the mismatch // will hardly be noticable. The blocks that need geometry updated will be adjacent to the original, non-bordered area. // This bit of code will mark a chunk as 'non-critical' if all of the blocks inside it are NOT adjacent to the original area. This has the greatest effect // when digging a single block. Only 6 of the blocks out of the possible 26 are actually adjacent to the original block. The other 20 only need lighting updated. // Note I have noticed a new side effect of this system where it's possible to see into the sides of water but this is acceptable compared to seeing through // the entire landscape. // is the left or right most block just inside this chunk if( ((x0 & 15) == 15 && x == _x0) || ((x1 & 15) == 0 && x == _x1) ) { // is the front, back, top or bottom most block just inside this chunk if( ((z0 & 15) == 15 && z == _z0) || ((z1 & 15) == 0 && z == _z1) || ((y0 & 15) == 15 && y == _y0) || ((y1 & 15) == 0 && y == _y1)) { index |= 0x10000000; } } else { // is the front or back most block just inside this chunk if( ((z0 & 15) == 15 && z == _z0) || ((z1 & 15) == 0 && z == _z1) ) { // is the top or bottom most block just inside this chunk if( ((y0 & 15) == 15 && y == _y0) || ((y1 & 15) == 0 && y == _y1)) { index |= 0x10000000; } } } dirtyChunksLockFreeStack.Push((int *)(index)); #else dirtyChunksLockFreeStack.Push((int *)(index + 2)); #endif PIXSetMarkerDeprecated(0,"Setting chunk %d %d %d dirty",x * 16,y * 16,z * 16); } // setGlobalChunkFlag(x * 16, y * 16, z * 16, level, CHUNK_FLAG_DIRTY); } } } // LeaveCriticalSection(&m_csDirtyChunks); } void LevelRenderer::tileChanged(int x, int y, int z) { setDirty(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1, NULL); } void LevelRenderer::tileLightChanged(int x, int y, int z) { setDirty(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1, NULL); } void LevelRenderer::setTilesDirty(int x0, int y0, int z0, int x1, int y1, int z1, Level *level) // 4J - added level param { setDirty(x0 - 1, y0 - 1, z0 - 1, x1 + 1, y1 + 1, z1 + 1, level); } bool inline clip(float *bb, float *frustum) { for (int i = 0; i < 6; ++i, frustum += 4) { if (frustum[0] * (bb[0]) + frustum[1] * (bb[1]) + frustum[2] * (bb[2]) + frustum[3] > 0) continue; if (frustum[0] * (bb[3]) + frustum[1] * (bb[1]) + frustum[2] * (bb[2]) + frustum[3] > 0) continue; if (frustum[0] * (bb[0]) + frustum[1] * (bb[4]) + frustum[2] * (bb[2]) + frustum[3] > 0) continue; if (frustum[0] * (bb[3]) + frustum[1] * (bb[4]) + frustum[2] * (bb[2]) + frustum[3] > 0) continue; if (frustum[0] * (bb[0]) + frustum[1] * (bb[1]) + frustum[2] * (bb[5]) + frustum[3] > 0) continue; if (frustum[0] * (bb[3]) + frustum[1] * (bb[1]) + frustum[2] * (bb[5]) + frustum[3] > 0) continue; if (frustum[0] * (bb[0]) + frustum[1] * (bb[4]) + frustum[2] * (bb[5]) + frustum[3] > 0) continue; if (frustum[0] * (bb[3]) + frustum[1] * (bb[4]) + frustum[2] * (bb[5]) + frustum[3] > 0) continue; return false; } return true; } #ifdef __PS3__ int g_listArray_layer0[4][LevelRenderer_cull_DataIn::sc_listSize]__attribute__((__aligned__(16))); // 8000 int g_listArray_layer1[4][LevelRenderer_cull_DataIn::sc_listSize]__attribute__((__aligned__(16))); float g_zDepth_layer0[4][LevelRenderer_cull_DataIn::sc_listSize]__attribute__((__aligned__(16))); // 8000 float g_zDepth_layer1[4][LevelRenderer_cull_DataIn::sc_listSize]__attribute__((__aligned__(16))); volatile bool g_useIdent = false; volatile float g_maxDepthRender = 1000; volatile float g_maxHeightRender = -1000; volatile float g_offMulVal = 1; void LevelRenderer::cull_SPU(int playerIndex, Culler *culler, float a) { if(m_bSPUCullStarted[playerIndex]) { return; // running already } FrustumCuller *fc = (FrustumCuller *)culler; FrustumData *fd = fc->frustum; float fdraw[6 * 4]; for( int i = 0; i < 6; i++ ) { double fx = fd->m_Frustum[i][0]; double fy = fd->m_Frustum[i][1]; double fz = fd->m_Frustum[i][2]; fdraw[i * 4 + 0] = (float)fx; fdraw[i * 4 + 1] = (float)fy; fdraw[i * 4 + 2] = (float)fz; fdraw[i * 4 + 3] = (float)(fd->m_Frustum[i][3] + ( fx * -fc->xOff ) + ( fy * - fc->yOff ) + ( fz * -fc->zOff )); } memcpy(&g_cullDataIn[playerIndex].fdraw, fdraw, sizeof(fdraw)); g_cullDataIn[playerIndex].numClipChunks = chunks[playerIndex].length; g_cullDataIn[playerIndex].pClipChunks = (ClipChunk_SPU*)chunks[playerIndex].data; g_cullDataIn[playerIndex].numGlobalChunks = getGlobalChunkCount(); g_cullDataIn[playerIndex].pGlobalChunkFlags = globalChunkFlags; g_cullDataIn[playerIndex].chunkLists = chunkLists; g_cullDataIn[playerIndex].listArray_layer0 = g_listArray_layer0[playerIndex]; g_cullDataIn[playerIndex].listArray_layer1 = g_listArray_layer1[playerIndex]; g_cullDataIn[playerIndex].zDepth_layer0 = g_zDepth_layer0[playerIndex]; g_cullDataIn[playerIndex].zDepth_layer1 = g_zDepth_layer1[playerIndex]; g_cullDataIn[playerIndex].maxDepthRender = g_maxDepthRender; g_cullDataIn[playerIndex].maxHeightRender = g_maxHeightRender; if(g_useIdent) g_cullDataIn[playerIndex].clipMat = Vectormath::Aos::Matrix4::identity(); else { memcpy(&g_cullDataIn[playerIndex].clipMat, &fc->frustum->modl[0], sizeof(float) * 16); g_cullDataIn[playerIndex].clipMat[3][0] = -fc->xOff; g_cullDataIn[playerIndex].clipMat[3][1] = -fc->yOff; g_cullDataIn[playerIndex].clipMat[3][2] = -fc->zOff; } C4JSpursJob_LevelRenderer_cull cullJob(&g_cullDataIn[playerIndex]); C4JSpursJob_LevelRenderer_zSort sortJob(&g_cullDataIn[playerIndex]); m_jobPort_CullSPU->submitJob(&cullJob); m_jobPort_CullSPU->submitSync(); // static int doSort = false; // if(doSort) { m_jobPort_CullSPU->submitJob(&sortJob); } // doSort ^= 1; m_bSPUCullStarted[playerIndex] = true; } void LevelRenderer::waitForCull_SPU() { m_jobPort_CullSPU->waitForCompletion(); int playerIndex = mc->player->GetXboxPad(); // 4J added m_bSPUCullStarted[playerIndex] = false; } #endif // __PS3__ void LevelRenderer::cull(Culler *culler, float a) { int playerIndex = mc->player->GetXboxPad(); // 4J added #if defined __PS3__ && !defined DISABLE_SPU_CODE cull_SPU(playerIndex, culler, a); return; #endif // __PS3__ FrustumCuller *fc = (FrustumCuller *)culler; FrustumData *fd = fc->frustum; float fdraw[6 * 4]; for( int i = 0; i < 6; i++ ) { double fx = fd->m_Frustum[i][0]; double fy = fd->m_Frustum[i][1]; double fz = fd->m_Frustum[i][2]; fdraw[i * 4 + 0] = (float)fx; fdraw[i * 4 + 1] = (float)fy; fdraw[i * 4 + 2] = (float)fz; fdraw[i * 4 + 3] = (float)(fd->m_Frustum[i][3] + ( fx * -fc->xOff ) + ( fy * - fc->yOff ) + ( fz * -fc->zOff )); } ClipChunk *pClipChunk = chunks[playerIndex].data; int vis = 0; int total = 0; int numWrong = 0; for (unsigned int i = 0; i < chunks[playerIndex].length; i++) { unsigned char flags = pClipChunk->globalIdx == -1 ? 0 : globalChunkFlags[ pClipChunk->globalIdx ]; if ( (flags & CHUNK_FLAG_COMPILED ) && ( ( flags & CHUNK_FLAG_EMPTYBOTH ) != CHUNK_FLAG_EMPTYBOTH ) ) { bool clipres = clip(pClipChunk->aabb, fdraw); pClipChunk->visible = clipres; if( pClipChunk->visible ) vis++; total++; } else { pClipChunk->visible = false; } pClipChunk++; } } void LevelRenderer::playStreamingMusic(const wstring& name, int x, int y, int z) { if (name != L"") { mc->gui->setNowPlaying(L"C418 - " + name); } mc->soundEngine->playStreaming(name, (float) x, (float) y, (float) z, 1, 1); } void LevelRenderer::playSound(int iSound, double x, double y, double z, float volume, float pitch, float fSoundClipDist) { // 4J-PB - removed in 1.4 //float dd = 16; /*if (volume > 1) fSoundClipDist *= volume; // 4J - find min distance to any players rather than just the current one float minDistSq = FLT_MAX; for( int i = 0; i < XUSER_MAX_COUNT; i++ ) { if( mc->localplayers[i] ) { float distSq = mc->localplayers[i]->distanceToSqr(x, y, z ); if( distSq < minDistSq ) { minDistSq = distSq; } } } if (minDistSq < fSoundClipDist * fSoundClipDist) { mc->soundEngine->play(iSound, (float) x, (float) y, (float) z, volume, pitch); } */ } void LevelRenderer::playSound(shared_ptr entity,int iSound, double x, double y, double z, float volume, float pitch, float fSoundClipDist) { } // 4J-PB - original function. I've changed to an enum instead of string compares // 4J removed - /* void LevelRenderer::addParticle(const wstring& name, double x, double y, double z, double xa, double ya, double za) { if (mc == NULL || mc->cameraTargetPlayer == NULL || mc->particleEngine == NULL) return; double xd = mc->cameraTargetPlayer->x - x; double yd = mc->cameraTargetPlayer->y - y; double zd = mc->cameraTargetPlayer->z - z; double particleDistance = 16; if (xd * xd + yd * yd + zd * zd > particleDistance * particleDistance) return; int playerIndex = mc->player->GetXboxPad(); // 4J added if (name== L"bubble") mc->particleEngine->add(shared_ptr( new BubbleParticle(level[playerIndex], x, y, z, xa, ya, za) ) ); else if (name== L"smoke") mc->particleEngine->add(shared_ptr( new SmokeParticle(level[playerIndex], x, y, z, xa, ya, za) ) ); else if (name== L"note") mc->particleEngine->add(shared_ptr( new NoteParticle(level[playerIndex], x, y, z, xa, ya, za) ) ); else if (name== L"portal") mc->particleEngine->add(shared_ptr( new PortalParticle(level[playerIndex], x, y, z, xa, ya, za) ) ); else if (name== L"explode") mc->particleEngine->add(shared_ptr( new ExplodeParticle(level[playerIndex], x, y, z, xa, ya, za) ) ); else if (name== L"flame") mc->particleEngine->add(shared_ptr( new FlameParticle(level[playerIndex], x, y, z, xa, ya, za) ) ); else if (name== L"lava") mc->particleEngine->add(shared_ptr( new LavaParticle(level[playerIndex], x, y, z) ) ); else if (name== L"footstep") mc->particleEngine->add(shared_ptr( new FootstepParticle(textures, level[playerIndex], x, y, z) ) ); else if (name== L"splash") mc->particleEngine->add(shared_ptr( new SplashParticle(level[playerIndex], x, y, z, xa, ya, za) ) ); else if (name== L"largesmoke") mc->particleEngine->add(shared_ptr( new SmokeParticle(level[playerIndex], x, y, z, xa, ya, za, 2.5f) ) ); else if (name== L"reddust") mc->particleEngine->add(shared_ptr( new RedDustParticle(level[playerIndex], x, y, z, (float) xa, (float) ya, (float) za) ) ); else if (name== L"snowballpoof") mc->particleEngine->add(shared_ptr( new BreakingItemParticle(level[playerIndex], x, y, z, Item::snowBall) ) ); else if (name== L"snowshovel") mc->particleEngine->add(shared_ptr( new SnowShovelParticle(level[playerIndex], x, y, z, xa, ya, za) ) ); else if (name== L"slime") mc->particleEngine->add(shared_ptr( new BreakingItemParticle(level[playerIndex], x, y, z, Item::slimeBall)) ) ; else if (name== L"heart") mc->particleEngine->add(shared_ptr( new HeartParticle(level[playerIndex], x, y, z, xa, ya, za) ) ); } */ void LevelRenderer::addParticle(ePARTICLE_TYPE eParticleType, double x, double y, double z, double xa, double ya, double za) { addParticleInternal( eParticleType, x, y, z, xa, ya, za ); } shared_ptr LevelRenderer::addParticleInternal(ePARTICLE_TYPE eParticleType, double x, double y, double z, double xa, double ya, double za) { if (mc == NULL || mc->cameraTargetPlayer == NULL || mc->particleEngine == NULL) { return nullptr; } // 4J added - do some explicit checking for NaN. The normal depth clipping seems to generally work for NaN (ie they get rejected), except on optimised PS3 code which // reverses the logic on the comparison with particleDistanceSquared and gets the opposite result to what you might expect. if( Double::isNaN(x) ) return nullptr; if( Double::isNaN(y) ) return nullptr; if( Double::isNaN(z) ) return nullptr; int particleLevel = mc->options->particles; Level *lev; int playerIndex = mc->player->GetXboxPad(); // 4J added lev = level[playerIndex]; if (particleLevel == 1) { // when playing at "decreased" particle level, randomly filter // particles by setting the level to "minimal" if (level[playerIndex]->random->nextInt(3) == 0) { particleLevel = 2; } } // 4J - the java code doesn't distance cull these two particle types, we need to implement this behaviour differently as our distance check is // mixed up with other things bool distCull = true; if ( (eParticleType == eParticleType_hugeexplosion) || (eParticleType == eParticleType_largeexplode) || (eParticleType == eParticleType_dragonbreath) ) { distCull = false; } // 4J - this is a bit of hack to get communication through from the level itself, but if Minecraft::animateTickLevel is NULL then // we are to behave as normal, and if it is set, then we should use that as a pointer to the level the particle is to be created with // rather than try to work it out from the current player. This is because in this state we are calling from a loop that is trying // to amalgamate particle creation between all players for a particular level. Also don't do distance clipping as it isn't for a particular // player, and distance is already taken into account before we get here anyway by the code in Level::animateTickDoWork if( mc->animateTickLevel == NULL ) { double particleDistanceSquared = 16 * 16; double xd = 0.0f; double yd = 0.0f; double zd = 0.0f; // 4J Stu - Changed this as we need to check all local players in case one of them is in range of this particle // Fix for #13454 - art : note blocks do not show notes bool inRange = false; for(unsigned int i = 0; i < XUSER_MAX_COUNT; ++i) { shared_ptr thisPlayer = mc->localplayers[i]; if(thisPlayer != NULL && level[i] == lev) { xd = thisPlayer->x - x; yd = thisPlayer->y - y; zd = thisPlayer->z - z; if (xd * xd + yd * yd + zd * zd <= particleDistanceSquared) inRange = true; } } if( (!inRange) && distCull ) return nullptr; } else { lev = mc->animateTickLevel; } if (particleLevel > 1) { // TODO: If any of the particles below are necessary even if // particles are turned off, then modify this if statement return nullptr; } shared_ptr particle; switch(eParticleType) { case eParticleType_hugeexplosion: particle = shared_ptr(new HugeExplosionSeedParticle(lev, x, y, z, xa, ya, za)); break; case eParticleType_largeexplode: particle = shared_ptr(new HugeExplosionParticle(textures, lev, x, y, z, xa, ya, za)); break; case eParticleType_bubble: particle = shared_ptr( new BubbleParticle(lev, x, y, z, xa, ya, za) ); break; case eParticleType_suspended: particle = shared_ptr( new SuspendedParticle(lev, x, y, z, xa, ya, za) ); break; case eParticleType_depthsuspend: particle = shared_ptr( new SuspendedTownParticle(lev, x, y, z, xa, ya, za) ); break; case eParticleType_townaura: particle = shared_ptr( new SuspendedTownParticle(lev, x, y, z, xa, ya, za) ); break; case eParticleType_crit: { shared_ptr critParticle2 = shared_ptr(new CritParticle2(lev, x, y, z, xa, ya, za)); critParticle2->CritParticle2PostConstructor(); particle = shared_ptr( critParticle2 ); // request from 343 to set pink for the needler in the Halo Texture Pack // Set particle colour from colour-table. unsigned int cStart = Minecraft::GetInstance()->getColourTable()->getColor( eMinecraftColour_Particle_CritStart ); unsigned int cEnd = Minecraft::GetInstance()->getColourTable()->getColor( eMinecraftColour_Particle_CritEnd ); // If the start and end colours are the same, just set that colour, otherwise random between them if(cStart==cEnd) { critParticle2->SetAgeUniformly(); particle->setColor( ( (cStart>>16)&0xFF )/255.0f, ( (cStart>>8)&0xFF )/255.0, ( cStart&0xFF )/255.0 ); } else { float fStart=((float)(cStart&0xFF)); float fDiff=(float)((cEnd-cStart)&0xFF); float fCol = (fStart + (Math::random() * fDiff))/255.0f; particle->setColor( fCol, fCol, fCol ); } } break; case eParticleType_magicCrit: { shared_ptr critParticle2 = shared_ptr(new CritParticle2(lev, x, y, z, xa, ya, za)); critParticle2->CritParticle2PostConstructor(); particle = shared_ptr(critParticle2); particle->setColor(particle->getRedCol() * 0.3f, particle->getGreenCol() * 0.8f, particle->getBlueCol()); particle->setNextMiscAnimTex(); } break; case eParticleType_smoke: particle = shared_ptr( new SmokeParticle(lev, x, y, z, xa, ya, za) ); break; case eParticleType_endportal: // 4J - Added. { SmokeParticle *tmp = new SmokeParticle(lev, x, y, z, xa, ya, za); // 4J-JEV: Set particle colour from colour-table. unsigned int col = Minecraft::GetInstance()->getColourTable()->getColor( eMinecraftColour_Particle_EnderPortal ); tmp->setColor( ( (col>>16)&0xFF )/255.0f, ( (col>>8)&0xFF )/255.0, ( col&0xFF )/255.0 ); particle = shared_ptr(tmp); } break; case eParticleType_mobSpell: particle = shared_ptr(new SpellParticle(lev, x, y, z, 0, 0, 0)); particle->setColor((float) xa, (float) ya, (float) za); break; case eParticleType_spell: particle = shared_ptr( new SpellParticle(lev, x, y, z, xa, ya, za) ); break; case eParticleType_instantSpell: particle = shared_ptr(new SpellParticle(lev, x, y, z, xa, ya, za)); dynamic_pointer_cast(particle)->setBaseTex(9 * 16); break; case eParticleType_note: particle = shared_ptr( new NoteParticle(lev, x, y, z, xa, ya, za) ); break; case eParticleType_netherportal: particle = shared_ptr( new NetherPortalParticle(lev, x, y, z, xa, ya, za) ); break; case eParticleType_ender: particle = shared_ptr( new EnderParticle(lev, x, y, z, xa, ya, za) ); break; case eParticleType_enchantmenttable: particle = shared_ptr(new EchantmentTableParticle(lev, x, y, z, xa, ya, za) ); break; case eParticleType_explode: particle = shared_ptr( new ExplodeParticle(lev, x, y, z, xa, ya, za) ); break; case eParticleType_flame: particle = shared_ptr( new FlameParticle(lev, x, y, z, xa, ya, za) ); break; case eParticleType_lava: particle = shared_ptr( new LavaParticle(lev, x, y, z) ); break; case eParticleType_footstep: particle = shared_ptr( new FootstepParticle(textures, lev, x, y, z) ); break; case eParticleType_splash: particle = shared_ptr( new SplashParticle(lev, x, y, z, xa, ya, za) ); break; case eParticleType_largesmoke: particle = shared_ptr( new SmokeParticle(lev, x, y, z, xa, ya, za, 2.5f) ); break; case eParticleType_reddust: particle = shared_ptr( new RedDustParticle(lev, x, y, z, (float) xa, (float) ya, (float) za) ); break; case eParticleType_snowballpoof: particle = shared_ptr( new BreakingItemParticle(lev, x, y, z, Item::snowBall, textures) ); break; case eParticleType_dripWater: particle = shared_ptr( new DripParticle(lev, x, y, z, Material::water) ); break; case eParticleType_dripLava: particle = shared_ptr( new DripParticle(lev, x, y, z, Material::lava) ); break; case eParticleType_snowshovel: particle = shared_ptr( new SnowShovelParticle(lev, x, y, z, xa, ya, za) ); break; case eParticleType_slime: particle = shared_ptr( new BreakingItemParticle(lev, x, y, z, Item::slimeBall, textures)); break; case eParticleType_heart: particle = shared_ptr( new HeartParticle(lev, x, y, z, xa, ya, za) ); break; case eParticleType_angryVillager: particle = shared_ptr( new HeartParticle(lev, x, y + 0.5f, z, xa, ya, za) ); particle->setMiscTex(1 + 16 * 5); particle->setColor(1, 1, 1); break; case eParticleType_happyVillager: particle = shared_ptr( new SuspendedTownParticle(lev, x, y, z, xa, ya, za) ); particle->setMiscTex(2 + 16 * 5); particle->setColor(1, 1, 1); break; case eParticleType_dragonbreath: particle = shared_ptr( new DragonBreathParticle(lev, x, y, z, xa, ya, za) ); break; default: if( ( eParticleType >= eParticleType_iconcrack_base ) && ( eParticleType <= eParticleType_iconcrack_last ) ) { int id = PARTICLE_CRACK_ID(eParticleType), data = PARTICLE_CRACK_DATA(eParticleType); particle = shared_ptr(new BreakingItemParticle(lev, x, y, z, xa, ya, za, Item::items[id], textures, data)); } else if( ( eParticleType >= eParticleType_tilecrack_base ) && ( eParticleType <= eParticleType_tilecrack_last ) ) { int id = PARTICLE_CRACK_ID(eParticleType), data = PARTICLE_CRACK_DATA(eParticleType); particle = dynamic_pointer_cast( shared_ptr(new TerrainParticle(lev, x, y, z, xa, ya, za, Tile::tiles[id], 0, data, textures))->init(data) ); } } if (particle != NULL) { mc->particleEngine->add(particle); } return particle; } void LevelRenderer::entityAdded(shared_ptr entity) { entity->prepareCustomTextures(); // 4J - these empty string comparisons used to check for NULL references, but we don't have string pointers (currently) in entities, // hopefully this should be equivalent /* 4J - removed temp */ //if (entity->customTextureUrl != L"") textures->addHttpTexture(entity->customTextureUrl, new MobSkinTextureProcessor()); //if (entity->customTextureUrl2 != L"") textures->addHttpTexture(entity->customTextureUrl2, new MobSkinTextureProcessor()); // 4J-PB - adding these from global title storage if (entity->customTextureUrl != L"") { textures->addMemTexture(entity->customTextureUrl, new MobSkinMemTextureProcessor()); } if (entity->customTextureUrl2 != L"") { textures->addMemTexture(entity->customTextureUrl2, new MobSkinMemTextureProcessor()); } // if (entity->customTextureUrl2 != L"") textures->addHttpTexture(entity->customTextureUrl2, new MobSkinTextureProcessor()); } void LevelRenderer::entityRemoved(shared_ptr entity) { /* 4J - removed temp if (entity->customTextureUrl != L"") textures->removeHttpTexture(entity->customTextureUrl); if (entity->customTextureUrl2 != L"") textures->removeHttpTexture(entity->customTextureUrl2); */ if (entity->customTextureUrl != L"") { textures->removeMemTexture(entity->customTextureUrl); } if (entity->customTextureUrl2 != L"") { textures->removeMemTexture(entity->customTextureUrl2); } } void LevelRenderer::skyColorChanged() { // 4J - no longer used #if 0 EnterCriticalSection(&m_csDirtyChunks); for( int i = 0; i < getGlobalChunkCountForOverworld(); i++ ) { if( ( globalChunkFlags[i] & CHUNK_FLAG_NOTSKYLIT ) == 0 ) { globalChunkFlags[i] |= CHUNK_FLAG_DIRTY; } } LeaveCriticalSection(&m_csDirtyChunks); #endif } void LevelRenderer::clear() { MemoryTracker::releaseLists(chunkLists); } void LevelRenderer::levelEvent(shared_ptr source, int type, int x, int y, int z, int data) { int playerIndex = mc->player->GetXboxPad(); // 4J added Random *random = level[playerIndex]->random; switch (type) { //case LevelEvent::SOUND_WITHER_BOSS_SPAWN: case LevelEvent::SOUND_DRAGON_DEATH: if (mc->cameraTargetPlayer != NULL) { // play the sound at an offset from the player double dx = x - mc->cameraTargetPlayer->x; double dy = y - mc->cameraTargetPlayer->y; double dz = z - mc->cameraTargetPlayer->z; double len = sqrt(dx * dx + dy * dy + dz * dz); double sx = mc->cameraTargetPlayer->x; double sy = mc->cameraTargetPlayer->y; double sz = mc->cameraTargetPlayer->z; if (len > 0) { sx += (dx / len) * 2; sy += (dy / len) * 2; sz += (dz / len) * 2; } level[playerIndex]->playLocalSound(sx, sy, sz, eSoundType_MOB_ENDERDRAGON_END, 5.0f, 1.0f); } break; case LevelEvent::SOUND_CLICK_FAIL: //level[playerIndex]->playSound(x, y, z, L"random.click", 1.0f, 1.2f); level[playerIndex]->playLocalSound(x, y, z, eSoundType_RANDOM_CLICK, 1.0f, 1.2f); break; case LevelEvent::SOUND_CLICK: level[playerIndex]->playLocalSound(x, y, z, eSoundType_RANDOM_CLICK, 1.0f, 1.0f); break; case LevelEvent::SOUND_LAUNCH: level[playerIndex]->playLocalSound(x, y, z, eSoundType_RANDOM_BOW, 1.0f, 1.2f); break; case LevelEvent::PARTICLES_SHOOT: { int xd = (data % 3) - 1; int zd = (data / 3 % 3) - 1; double xp = x + xd * 0.6 + 0.5; double yp = y + 0.5; double zp = z + zd * 0.6 + 0.5; for (int i = 0; i < 10; i++) { double pow = random->nextDouble() * 0.2 + 0.01; double xs = xp + xd * 0.01 + (random->nextDouble() - 0.5) * zd * 0.5; double ys = yp + (random->nextDouble() - 0.5) * 0.5; double zs = zp + zd * 0.01 + (random->nextDouble() - 0.5) * xd * 0.5; double xsa = xd * pow + random->nextGaussian() * 0.01; double ysa = -0.03 + random->nextGaussian() * 0.01; double zsa = zd * pow + random->nextGaussian() * 0.01; addParticle(eParticleType_smoke, xs, ys, zs, xsa, ysa, zsa); } break; } case LevelEvent::PARTICLES_EYE_OF_ENDER_DEATH: { double xp = x + 0.5; double yp = y; double zp = z + 0.5; ePARTICLE_TYPE particle = PARTICLE_ICONCRACK(Item::eyeOfEnder->id,0); for (int i = 0; i < 8; i++) { addParticle(particle, xp, yp, zp, random->nextGaussian() * 0.15, random->nextDouble() * 0.2, random->nextGaussian() * .15); } for (double a = 0; a < PI * 2.0; a += PI * 0.05) { addParticle(eParticleType_ender, xp + cos(a) * 5, yp - .4, zp + sin(a) * 5, cos(a) * -5, 0, sin(a) * -5); addParticle(eParticleType_ender, xp + cos(a) * 5, yp - .4, zp + sin(a) * 5, cos(a) * -7, 0, sin(a) * -7); } } break; case LevelEvent::PARTICLES_POTION_SPLASH: { double xp = x; double yp = y; double zp = z; ePARTICLE_TYPE particle = PARTICLE_ICONCRACK(Item::potion->id,0); for (int i = 0; i < 8; i++) { addParticle(particle, xp, yp, zp, random->nextGaussian() * 0.15, random->nextDouble() * 0.2, random->nextGaussian() * 0.15); } int colorValue = Item::potion->getColor(data); float red = (float) ((colorValue >> 16) & 0xff) / 255.0f; float green = (float) ((colorValue >> 8) & 0xff) / 255.0f; float blue = (float) ((colorValue >> 0) & 0xff) / 255.0f; ePARTICLE_TYPE particleName = eParticleType_spell; if (Item::potion->hasInstantenousEffects(data)) { particleName = eParticleType_instantSpell; } for (int i = 0; i < 100; i++) { double dist = random->nextDouble() * ThrownPotion::SPLASH_RANGE; double angle = random->nextDouble() * PI * 2; double xs = cos(angle) * dist; double ys = 0.01 + random->nextDouble() * 0.5; double zs = sin(angle) * dist; shared_ptr spellParticle = addParticleInternal(particleName, xp + xs * 0.1, yp + 0.3, zp + zs * 0.1, xs, ys, zs); if (spellParticle != NULL) { float randBrightness = 0.75f + random->nextFloat() * 0.25f; spellParticle->setColor(red * randBrightness, green * randBrightness, blue * randBrightness); spellParticle->setPower((float) dist); } } level[playerIndex]->playLocalSound(x + 0.5, y + 0.5, z + 0.5, eSoundType_RANDOM_GLASS, 1, level[playerIndex]->random->nextFloat() * 0.1f + 0.9f); } break; case LevelEvent::ENDERDRAGON_FIREBALL_SPLASH: { double xp = x; double yp = y; double zp = z; ePARTICLE_TYPE particleName = eParticleType_dragonbreath; for (int i = 0; i < 200; i++) { double dist = random->nextDouble() * DragonFireball::SPLASH_RANGE; double angle = random->nextDouble() * PI * 2; double xs = cos(angle) * dist; double ys = 0.01 + random->nextDouble() * 0.5; double zs = sin(angle) * dist; shared_ptr acidParticle = addParticleInternal(particleName, xp + xs * 0.1, yp + 0.3, zp + zs * 0.1, xs, ys, zs); if (acidParticle != NULL) { float randBrightness = 0.75f + random->nextFloat() * 0.25f; acidParticle->setPower((float) dist); } } level[playerIndex]->playLocalSound(x + 0.5, y + 0.5, z + 0.5, eSoundType_RANDOM_EXPLODE, 1, level[playerIndex]->random->nextFloat() * 0.1f + 0.9f); } break; case LevelEvent::PARTICLES_DESTROY_BLOCK: { int t = data & Tile::TILE_NUM_MASK; if (t > 0) { Tile *oldTile = Tile::tiles[t]; mc->soundEngine->play(oldTile->soundType->getBreakSound(), x + 0.5f, y + 0.5f, z + 0.5f, (oldTile->soundType->getVolume() + 1) / 2, oldTile->soundType->getPitch() * 0.8f); } mc->particleEngine->destroy(x, y, z, data & Tile::TILE_NUM_MASK, (data >> Tile::TILE_NUM_SHIFT) & 0xff); break; } case LevelEvent::PARTICLES_MOBTILE_SPAWN: { for (int i = 0; i < 20; i++) { double xP = x + 0.5 + (level[playerIndex]->random->nextFloat() - 0.5) * 2; double yP = y + 0.5 + (level[playerIndex]->random->nextFloat() - 0.5) * 2; double zP = z + 0.5 + (level[playerIndex]->random->nextFloat() - 0.5) * 2; level[playerIndex]->addParticle(eParticleType_smoke, xP, yP, zP, 0, 0, 0); level[playerIndex]->addParticle(eParticleType_flame, xP, yP, zP, 0, 0, 0); } break; } case LevelEvent::SOUND_OPEN_DOOR: if (Math::random() < 0.5) { level[playerIndex]->playLocalSound(x + 0.5, y + 0.5, z + 0.5, eSoundType_RANDOM_DOOR_OPEN, 1.0f, level[playerIndex]->random->nextFloat() * 0.1f + 0.9f); } else { level[playerIndex]->playLocalSound(x + 0.5, y + 0.5, z + 0.5, eSoundType_RANDOM_DOOR_CLOSE, 1.0f, level[playerIndex]->random->nextFloat() * 0.1f + 0.9f); } break; case LevelEvent::SOUND_FIZZ: level[playerIndex]->playLocalSound(x + 0.5f, y + 0.5f, z + 0.5f, eSoundType_RANDOM_FIZZ, 0.5f, 2.6f + (random->nextFloat() - random->nextFloat()) * 0.8f); break; case LevelEvent::SOUND_ANVIL_BROKEN: level[playerIndex]->playLocalSound(x + 0.5f, y + 0.5f, z + 0.5f, eSoundType_RANDOM_ANVIL_BREAK, 1.0f, level[playerIndex]->random->nextFloat() * 0.1f + 0.9f); break; case LevelEvent::SOUND_ANVIL_USED: level[playerIndex]->playLocalSound(x + 0.5f, y + 0.5f, z + 0.5f, eSoundType_RANDOM_ANVIL_USE, 1.0f, level[playerIndex]->random->nextFloat() * 0.1f + 0.9f); break; case LevelEvent::SOUND_ANVIL_LAND: level[playerIndex]->playLocalSound(x + 0.5f, y + 0.5f, z + 0.5f, eSoundType_RANDOM_ANVIL_LAND, 0.3f, level[playerIndex]->random->nextFloat() * 0.1f + 0.9f); break; case LevelEvent::SOUND_PLAY_RECORDING: { RecordingItem *rci = dynamic_cast(Item::items[data]); if (rci != NULL) { level[playerIndex]->playStreamingMusic(rci->recording, x, y, z); } else { // 4J-PB - only play streaming music if there isn't already some playing - the CD playing may have finished, and game music started playing already if(!mc->soundEngine->GetIsPlayingStreamingGameMusic()) { level[playerIndex]->playStreamingMusic(L"", x, y, z); // 4J - used to pass NULL, but using empty string here now instead } } mc->localplayers[playerIndex]->updateRichPresence(); } break; // 4J - new level event sounds brought forward from 1.2.3 case LevelEvent::SOUND_GHAST_WARNING: level[playerIndex]->playLocalSound(x + 0.5, y + 0.5, z + 0.5, eSoundType_MOB_GHAST_CHARGE, 2.0f, (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f, 80.0f); break; case LevelEvent::SOUND_GHAST_FIREBALL: level[playerIndex]->playLocalSound(x + 0.5, y + 0.5, z + 0.5, eSoundType_MOB_GHAST_FIREBALL, 2.0f, (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f, 80.0f); break; case LevelEvent::SOUND_ZOMBIE_WOODEN_DOOR: level[playerIndex]->playLocalSound(x + 0.5, y + 0.5, z + 0.5, eSoundType_MOB_ZOMBIE_WOOD, 2.0f, (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f); break; case LevelEvent::SOUND_ZOMBIE_DOOR_CRASH: level[playerIndex]->playLocalSound(x + 0.5, y + 0.5, z + 0.5, eSoundType_MOB_ZOMBIE_WOOD_BREAK, 2.0f, (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f); break; case LevelEvent::SOUND_ZOMBIE_IRON_DOOR: level[playerIndex]->playLocalSound(x + 0.5, y + 0.5, z + 0.5, eSoundType_MOB_ZOMBIE_METAL, 2.0f, (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f); break; case LevelEvent::SOUND_ZOMBIE_INFECTED: level[playerIndex]->playLocalSound(x + 0.5, y + 0.5, z + 0.5, eSoundType_MOB_ZOMBIE_INFECT, 2.0f, (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f);//, false); break; case LevelEvent::SOUND_ZOMBIE_CONVERTED: level[playerIndex]->playLocalSound(x + 0.5, y + 0.5, z + 0.5, eSoundType_MOB_ZOMBIE_UNFECT, 2.0f, (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f);//, false); break; // 4J Added TU9 to fix #77475 - TU9: Content: Art: Dragon egg teleport particle effect isn't present. case LevelEvent::END_EGG_TELEPORT: // 4J Added to show the paricles when the End egg teleports after being attacked EggTile::generateTeleportParticles(level[playerIndex],x,y,z,data); break; } } void LevelRenderer::destroyTileProgress(int id, int x, int y, int z, int progress) { if (progress < 0 || progress >= 10) { AUTO_VAR(it, destroyingBlocks.find(id)); if(it != destroyingBlocks.end()) { delete it->second; destroyingBlocks.erase(it); } //destroyingBlocks.remove(id); } else { BlockDestructionProgress *entry = NULL; AUTO_VAR(it, destroyingBlocks.find(id)); if(it != destroyingBlocks.end()) entry = it->second; if (entry == NULL || entry->getX() != x || entry->getY() != y || entry->getZ() != z) { entry = new BlockDestructionProgress(id, x, y, z); destroyingBlocks.insert( unordered_map::value_type(id, entry) ); } entry->setProgress(progress); entry->updateTick(ticks); } } void LevelRenderer::registerTextures(IconRegister *iconRegister) { breakingTextures = new Icon*[10]; for (int i = 0; i < 10; i++) { breakingTextures[i] = iconRegister->registerIcon(L"destroy_" + _toString(i) ); } } // Gets a dimension index (0, 1, or 2) from an id ( 0, -1, 1) int LevelRenderer::getDimensionIndexFromId(int id) { return ( 3 - id ) % 3; } // 4J - added for new render list handling. Render lists used to be allocated per chunk, but these are now allocated per fixed chunk position // in our (now finite) maps. int LevelRenderer::getGlobalIndexForChunk(int x, int y, int z, Level *level) { return getGlobalIndexForChunk(x,y,z,level->dimension->id); } int LevelRenderer::getGlobalIndexForChunk(int x, int y, int z, int dimensionId) { int dimIdx = getDimensionIndexFromId(dimensionId); int xx = ( x / CHUNK_XZSIZE ) + ( MAX_LEVEL_RENDER_SIZE[dimIdx] / 2 ); int yy = y / CHUNK_SIZE; int zz = ( z / CHUNK_XZSIZE ) + ( MAX_LEVEL_RENDER_SIZE[dimIdx] / 2 ); if( ( xx < 0 ) || ( xx >= MAX_LEVEL_RENDER_SIZE[dimIdx] ) ) return -1; if( ( zz < 0 ) || ( zz >= MAX_LEVEL_RENDER_SIZE[dimIdx] ) ) return -1; if( ( yy < 0 ) || ( yy >= CHUNK_Y_COUNT ) ) return -1; int dimOffset = DIMENSION_OFFSETS[dimIdx]; int offset = dimOffset; // Offset caused by current dimension offset += ( zz * MAX_LEVEL_RENDER_SIZE[dimIdx] + xx ) * CHUNK_Y_COUNT; // Offset by x/z pos offset += yy; // Offset by y pos return offset; } bool LevelRenderer::isGlobalIndexInSameDimension( int idx, Level *level) { int dim = getDimensionIndexFromId(level->dimension->id); int idxDim = 0; if( idx >= DIMENSION_OFFSETS[2] ) idxDim = 2; else if ( idx >= DIMENSION_OFFSETS[1] ) idxDim = 1; return (dim == idxDim); } int LevelRenderer::getGlobalChunkCount() { return ( MAX_LEVEL_RENDER_SIZE[0] * MAX_LEVEL_RENDER_SIZE[0] * CHUNK_Y_COUNT ) + ( MAX_LEVEL_RENDER_SIZE[1] * MAX_LEVEL_RENDER_SIZE[1] * CHUNK_Y_COUNT ) + ( MAX_LEVEL_RENDER_SIZE[2] * MAX_LEVEL_RENDER_SIZE[2] * CHUNK_Y_COUNT ); } int LevelRenderer::getGlobalChunkCountForOverworld() { return ( MAX_LEVEL_RENDER_SIZE[0] * MAX_LEVEL_RENDER_SIZE[0] * CHUNK_Y_COUNT ); } unsigned char LevelRenderer::getGlobalChunkFlags(int x, int y, int z, Level *level) { int index = getGlobalIndexForChunk(x, y, z, level); if( index == -1 ) { return 0; } else { return globalChunkFlags[ index ]; } } void LevelRenderer::setGlobalChunkFlags(int x, int y, int z, Level *level, unsigned char flags) { int index = getGlobalIndexForChunk(x, y, z, level); if( index != -1 ) { #ifdef _LARGE_WORLDS EnterCriticalSection(&m_csChunkFlags); #endif globalChunkFlags[ index ] = flags; #ifdef _LARGE_WORLDS LeaveCriticalSection(&m_csChunkFlags); #endif } } void LevelRenderer::setGlobalChunkFlag(int index, unsigned char flag, unsigned char shift) { unsigned char sflag = flag << shift; if( index != -1 ) { #ifdef _LARGE_WORLDS EnterCriticalSection(&m_csChunkFlags); #endif globalChunkFlags[ index ] |= sflag; #ifdef _LARGE_WORLDS LeaveCriticalSection(&m_csChunkFlags); #endif } } void LevelRenderer::setGlobalChunkFlag(int x, int y, int z, Level *level, unsigned char flag, unsigned char shift) { unsigned char sflag = flag << shift; int index = getGlobalIndexForChunk(x, y, z, level); if( index != -1 ) { #ifdef _LARGE_WORLDS EnterCriticalSection(&m_csChunkFlags); #endif globalChunkFlags[ index ] |= sflag; #ifdef _LARGE_WORLDS LeaveCriticalSection(&m_csChunkFlags); #endif } } void LevelRenderer::clearGlobalChunkFlag(int x, int y, int z, Level *level, unsigned char flag, unsigned char shift) { unsigned char sflag = flag << shift; int index = getGlobalIndexForChunk(x, y, z, level); if( index != -1 ) { #ifdef _LARGE_WORLDS EnterCriticalSection(&m_csChunkFlags); #endif globalChunkFlags[ index ] &= ~sflag; #ifdef _LARGE_WORLDS LeaveCriticalSection(&m_csChunkFlags); #endif } } bool LevelRenderer::getGlobalChunkFlag(int x, int y, int z, Level *level, unsigned char flag, unsigned char shift) { unsigned char sflag = flag << shift; int index = getGlobalIndexForChunk(x, y, z, level); if( index == -1 ) { return false; } else { return ( globalChunkFlags[ index ] & sflag ) == sflag; } } unsigned char LevelRenderer::incGlobalChunkRefCount(int x, int y, int z, Level *level) { int index = getGlobalIndexForChunk(x, y, z, level); if( index != -1 ) { unsigned char flags = globalChunkFlags[ index ]; unsigned char refCount = (flags >> CHUNK_FLAG_REF_SHIFT ) & CHUNK_FLAG_REF_MASK; refCount++; flags &= ~(CHUNK_FLAG_REF_MASK<> CHUNK_FLAG_REF_SHIFT ) & CHUNK_FLAG_REF_MASK; refCount--; flags &= ~(CHUNK_FLAG_REF_MASK<second.end()); for( AUTO_VAR(it2, it->second.begin()); it2 != itTEEnd; it2++ ) { (*it2)->upgradeRenderRemoveStage(); } } LeaveCriticalSection(&m_csRenderableTileEntities); } LevelRenderer::DestroyedTileManager::RecentTile::RecentTile(int x, int y, int z, Level *level) : x(x), y(y), z(z), level(level) { timeout_ticks = 20; rebuilt = false; } LevelRenderer::DestroyedTileManager::RecentTile::~RecentTile() { for( AUTO_VAR(it, boxes.begin()); it!= boxes.end(); it++ ) { delete *it; } } LevelRenderer::DestroyedTileManager::DestroyedTileManager() { InitializeCriticalSection(&m_csDestroyedTiles); } LevelRenderer::DestroyedTileManager::~DestroyedTileManager() { DeleteCriticalSection(&m_csDestroyedTiles); for( unsigned int i = 0; i < m_destroyedTiles.size(); i++ ) { delete m_destroyedTiles[i]; } } // For game to let this manager know that a tile is about to be destroyed (must be called before it actually is) void LevelRenderer::DestroyedTileManager::destroyingTileAt( Level *level, int x, int y, int z ) { EnterCriticalSection(&m_csDestroyedTiles); // Store a list of AABBs that the tile to be destroyed would have made, before we go and destroy it. This // is made slightly more complicated as the addAABBs method for tiles adds temporary AABBs and we need permanent // ones, so make a temporary list and then copy over RecentTile *recentTile = new RecentTile(x, y, z, level); AABB *box = AABB::newTemp((float)x, (float)y, (float)z, (float)(x+1), (float)(y+1), (float)(z+1)); Tile *tile = Tile::tiles[level->getTile(x, y, z)]; if (tile != NULL) { tile->addAABBs(level, x, y, z, box, &recentTile->boxes, nullptr); } // Make these temporary AABBs into permanently allocated AABBs for( unsigned int i = 0; i < recentTile->boxes.size(); i++ ) { recentTile->boxes[i] = AABB::newPermanent(recentTile->boxes[i]->x0, recentTile->boxes[i]->y0, recentTile->boxes[i]->z0, recentTile->boxes[i]->x1, recentTile->boxes[i]->y1, recentTile->boxes[i]->z1); } m_destroyedTiles.push_back( recentTile ); LeaveCriticalSection(&m_csDestroyedTiles); } // For chunk rebuilding to inform the manager that a chunk (a 16x16x16 tile render chunk) has been updated void LevelRenderer::DestroyedTileManager::updatedChunkAt(Level *level, int x, int y, int z, int veryNearCount) { EnterCriticalSection(&m_csDestroyedTiles); // There's 2 stages to this. This function is called when a renderer chunk has been rebuilt, but that chunk's render data might be grouped atomically with // changes to other very near chunks. Therefore, we don't want to consider the render data to be fully updated until the chunk that it is in has been // rebuilt, AND there aren't any very near things waiting to be rebuilt. // First pass through - see if any tiles are within the chunk which is being rebuilt, and mark up by setting their rebuilt flag bool printed = false; for( unsigned int i = 0; i < m_destroyedTiles.size(); i++) { if( ( m_destroyedTiles[i]->level == level ) && ( m_destroyedTiles[i]->x >= x ) && ( m_destroyedTiles[i]->x < ( x + 16 ) ) && ( m_destroyedTiles[i]->y >= y ) && ( m_destroyedTiles[i]->y < ( y + 16 ) ) && ( m_destroyedTiles[i]->z >= z ) && ( m_destroyedTiles[i]->z < ( z + 16 ) ) ) { printed = true; m_destroyedTiles[i]->rebuilt = true; } } // Now go through every tile that has been marked up as already being rebuilt, and fully remove it once there aren't going to be any more // very near chunks. This might not happen on the same call to this function that rebuilt the chunk with the tile in. if( veryNearCount <= 1 ) { for( unsigned int i = 0; i < m_destroyedTiles.size(); ) { if( m_destroyedTiles[i]->rebuilt ) { printed = true; delete m_destroyedTiles[i]; m_destroyedTiles[i] = m_destroyedTiles[m_destroyedTiles.size() - 1]; m_destroyedTiles.pop_back(); } else { i++; } } } LeaveCriticalSection(&m_csDestroyedTiles); } // For game to get any AABBs that the user should be colliding with as render data has not yet been updated void LevelRenderer::DestroyedTileManager::addAABBs( Level *level, AABB *box, AABBList *boxes ) { EnterCriticalSection(&m_csDestroyedTiles); for( unsigned int i = 0; i < m_destroyedTiles.size(); i++ ) { if( m_destroyedTiles[i]->level == level ) { for( unsigned int j = 0; j < m_destroyedTiles[i]->boxes.size(); j++ ) { // If we find any AABBs intersecting the region we are interested in, add them to the output list, making a temp AABB copy so that we can destroy our own copy // without worrying about the lifespan of the copy we've passed out if( m_destroyedTiles[i]->boxes[j]->intersects( box ) ) { boxes->push_back(AABB::newTemp( m_destroyedTiles[i]->boxes[j]->x0, m_destroyedTiles[i]->boxes[j]->y0, m_destroyedTiles[i]->boxes[j]->z0, m_destroyedTiles[i]->boxes[j]->x1, m_destroyedTiles[i]->boxes[j]->y1, m_destroyedTiles[i]->boxes[j]->z1 ) ); } } } } LeaveCriticalSection(&m_csDestroyedTiles); } void LevelRenderer::DestroyedTileManager::tick() { EnterCriticalSection(&m_csDestroyedTiles); // Remove any tiles that have timed out for( unsigned int i = 0; i < m_destroyedTiles.size(); ) { if( --m_destroyedTiles[i]->timeout_ticks == 0 ) { delete m_destroyedTiles[i]; m_destroyedTiles[i] = m_destroyedTiles[m_destroyedTiles.size() - 1]; m_destroyedTiles.pop_back(); } else { i++; } } LeaveCriticalSection(&m_csDestroyedTiles); } #ifdef _LARGE_WORLDS void LevelRenderer::staticCtor() { s_rebuildCompleteEvents = new C4JThread::EventArray(MAX_CHUNK_REBUILD_THREADS); char threadName[256]; for(unsigned int i = 0; i < MAX_CHUNK_REBUILD_THREADS; ++i) { sprintf(threadName,"Rebuild Chunk Thread %d\n",i); rebuildThreads[i] = new C4JThread(rebuildChunkThreadProc,(void *)i,threadName); s_activationEventA[i] = new C4JThread::Event(); // Threads 1,3 and 5 are generally idle so use them if((i%3) == 0) rebuildThreads[i]->SetProcessor(CPU_CORE_CHUNK_REBUILD_A); else if((i%3) == 1) { rebuildThreads[i]->SetProcessor(CPU_CORE_CHUNK_REBUILD_B); } else if((i%3) == 2) rebuildThreads[i]->SetProcessor(CPU_CORE_CHUNK_REBUILD_C); //ResumeThread( saveThreads[j] ); rebuildThreads[i]->Run(); } } int LevelRenderer::rebuildChunkThreadProc(LPVOID lpParam) { Vec3::CreateNewThreadStorage(); AABB::CreateNewThreadStorage(); IntCache::CreateNewThreadStorage(); Tesselator::CreateNewThreadStorage(1024*1024); RenderManager.InitialiseContext(); Chunk::CreateNewThreadStorage(); Tile::CreateNewThreadStorage(); int index = (size_t)lpParam; while(true) { s_activationEventA[index]->WaitForSignal(INFINITE); //app.DebugPrintf("Rebuilding permaChunk %d\n", index + 1); permaChunk[index + 1].rebuild(); // Inform the producer thread that we are done with this chunk s_rebuildCompleteEvents->Set(index); } return 0; } #endif // This is called when chunks require rebuilding, but they haven't been added individually to the dirtyChunksLockFreeStack. Once in this // state, the rebuilding thread will keep assuming there are dirty chunks until it has had a full pass through the chunks and found no dirty ones void LevelRenderer::nonStackDirtyChunksAdded() { dirtyChunksLockFreeStack.Push((int *)1); }