mirror of
https://github.com/4jcraft/4jcraft.git
synced 2026-04-23 17:53:35 +00:00
4196 lines
166 KiB
C++
4196 lines
166 KiB
C++
#include <thread>
|
|
#include <chrono>
|
|
|
|
#include "../Platform/stdafx.h"
|
|
#include "LevelRenderer.h"
|
|
#include <cmath>
|
|
#include "../Textures/Textures.h"
|
|
#include "../Textures/TextureAtlas.h"
|
|
#include "Tesselator.h"
|
|
#include "Chunk.h"
|
|
#include "EntityRenderers/EntityRenderDispatcher.h"
|
|
#include "EntityRenderers/TileEntityRenderDispatcher.h"
|
|
#include "Culling/DistanceChunkSorter.h"
|
|
#include "Culling/DirtyChunkSorter.h"
|
|
#include "../Textures/MobSkinTextureProcessor.h"
|
|
#include "../Textures/MobSkinMemTextureProcessor.h"
|
|
#include "GameRenderer.h"
|
|
#include "Particles/BubbleParticle.h"
|
|
#include "Particles/SmokeParticle.h"
|
|
#include "Particles/NoteParticle.h"
|
|
#include "Particles/NetherPortalParticle.h"
|
|
#include "Particles/EnderParticle.h"
|
|
#include "Particles/ExplodeParticle.h"
|
|
#include "Particles/FlameParticle.h"
|
|
#include "Particles/LavaParticle.h"
|
|
#include "Particles/FootstepParticle.h"
|
|
#include "Particles/SplashParticle.h"
|
|
#include "Particles/SmokeParticle.h"
|
|
#include "Particles/RedDustParticle.h"
|
|
#include "Particles/BreakingItemParticle.h"
|
|
#include "Particles/SnowShovelParticle.h"
|
|
#include "Particles/BreakingItemParticle.h"
|
|
#include "Particles/HeartParticle.h"
|
|
#include "Particles/HugeExplosionParticle.h"
|
|
#include "Particles/HugeExplosionSeedParticle.h"
|
|
#include "Particles/SuspendedParticle.h"
|
|
#include "Particles/SuspendedTownParticle.h"
|
|
#include "Particles/CritParticle2.h"
|
|
#include "Particles/TerrainParticle.h"
|
|
#include "Particles/SpellParticle.h"
|
|
#include "Particles/DripParticle.h"
|
|
#include "Particles/EnchantmentTableParticle.h"
|
|
#include "Particles/DragonBreathParticle.h"
|
|
#include "Particles/FireworksParticles.h"
|
|
#include "Lighting.h"
|
|
#include "../GameState/Options.h"
|
|
#include "../Network/MultiPlayerChunkCache.h"
|
|
#include "../../Minecraft.World/Util/ParticleTypes.h"
|
|
#include "../../Minecraft.World/IO/Streams/IntBuffer.h"
|
|
#include "../../Minecraft.World/Util/JavaMath.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.level.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.level.dimension.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.level.tile.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.phys.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.entity.player.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.item.h"
|
|
#include "../../Minecraft.World/Platform/System.h"
|
|
#include "../../Minecraft.World/Util/StringHelpers.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.level.chunk.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.entity.projectile.h"
|
|
#include "../../Minecraft.World/Headers/net.minecraft.world.h"
|
|
#include "../Player/MultiPlayerLocalPlayer.h"
|
|
#include "../Level/MultiPlayerLevel.h"
|
|
#include "../../Minecraft.World/Util/SoundTypes.h"
|
|
#include "FrustumCuller.h"
|
|
#include "../Utils/FrameProfiler.h"
|
|
|
|
// #define DISABLE_SPU_CODE
|
|
|
|
#ifdef __PS3__
|
|
#include "../Platform/PS3/SPU_Tasks/LevelRenderer_cull/LevelRenderer_cull.h"
|
|
#include "../Platform/PS3/SPU_Tasks/LevelRenderer_FindNearestChunk/LevelRenderer_FindNearestChunk.h"
|
|
#include "../Platform/PS3/PS3Extras/C4JSpursJob.h"
|
|
|
|
static LevelRenderer_cull_DataIn g_cullDataIn[4]
|
|
__attribute__((__aligned__(16)));
|
|
static LevelRenderer_FindNearestChunk_DataIn g_findNearestChunkDataIn
|
|
__attribute__((__aligned__(16)));
|
|
#endif
|
|
|
|
ResourceLocation LevelRenderer::MOON_LOCATION =
|
|
ResourceLocation(TN_TERRAIN_MOON);
|
|
ResourceLocation LevelRenderer::MOON_PHASES_LOCATION =
|
|
ResourceLocation(TN_TERRAIN_MOON_PHASES);
|
|
ResourceLocation LevelRenderer::SUN_LOCATION = ResourceLocation(TN_TERRAIN_SUN);
|
|
ResourceLocation LevelRenderer::CLOUDS_LOCATION =
|
|
ResourceLocation(TN_ENVIRONMENT_CLOUDS);
|
|
ResourceLocation LevelRenderer::END_SKY_LOCATION =
|
|
ResourceLocation(TN_MISC_TUNNEL);
|
|
|
|
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; i < app.vSkinNames.size(); i++) {
|
|
textures->addMemTexture(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[playerIndex] == 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
|
|
#ifdef __PSVITA__
|
|
dist = 10;
|
|
#endif
|
|
|
|
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<Chunk *>(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[playerIndex] != NULL) {
|
|
std::shared_ptr<Entity> 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->crosshairPickMob, mc->options, a);
|
|
|
|
if (noEntityRenderFrames > 0) {
|
|
noEntityRenderFrames--;
|
|
return;
|
|
}
|
|
|
|
totalEntities = 0;
|
|
renderedEntities = 0;
|
|
culledEntities = 0;
|
|
|
|
std::shared_ptr<Entity> 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);
|
|
|
|
// 4jcraft: we use scaleLight for entity lighting
|
|
mc->gameRenderer->turnOnLightLayer(
|
|
a, true); // 4J - brought forward from 1.8.2
|
|
|
|
std::vector<std::shared_ptr<Entity> > 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++) {
|
|
std::shared_ptr<Entity> 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++) {
|
|
std::shared_ptr<Entity> entity = *it; // entities[i];
|
|
|
|
bool shouldRender =
|
|
(entity->shouldRender(cam) &&
|
|
(entity->noCulling || culler->isVisible(&entity->bb)));
|
|
|
|
// Render the mob if the mob's leash holder is within the culler
|
|
if (!shouldRender && entity->instanceof(eTYPE_MOB)) {
|
|
std::shared_ptr<Mob> mob = std::dynamic_pointer_cast<Mob>(entity);
|
|
if (mob->isLeashed() && (mob->getLeashHolder() != NULL)) {
|
|
std::shared_ptr<Entity> leashHolder = mob->getLeashHolder();
|
|
shouldRender = culler->isVisible(&leashHolder->bb);
|
|
}
|
|
}
|
|
|
|
if (shouldRender) {
|
|
// 4J-PB - changing this to be per player
|
|
// if (entity == mc->cameraTargetPlayer &&
|
|
// !mc->options->thirdPersonView &&
|
|
// !mc->cameraTargetPlayer->isSleeping()) continue;
|
|
std::shared_ptr<LocalPlayer> localplayer =
|
|
mc->cameraTargetPlayer->instanceof(eTYPE_LOCALPLAYER)
|
|
? std::dynamic_pointer_cast<LocalPlayer>(
|
|
mc->cameraTargetPlayer)
|
|
: nullptr;
|
|
|
|
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
|
|
}
|
|
|
|
std::wstring LevelRenderer::gatherStats1() {
|
|
return L"C: " + _toString<int>(renderedChunks) + L"/" +
|
|
_toString<int>(totalChunks) + L". F: " +
|
|
_toString<int>(offscreenChunks) + L", O: " +
|
|
_toString<int>(occludedChunks) + L", E: " +
|
|
_toString<int>(emptyChunks);
|
|
}
|
|
|
|
std::wstring LevelRenderer::gatherStats2() {
|
|
return L"E: " + _toString<int>(renderedEntities) + L"/" +
|
|
_toString<int>(totalEntities) + L". B: " +
|
|
_toString<int>(culledEntities) + L", I: " +
|
|
_toString<int>((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(std::shared_ptr<LivingEntity> player, int layer,
|
|
double alpha, bool updateChunks) {
|
|
FRAME_PROFILE_SCOPE(Terrain);
|
|
|
|
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();
|
|
glColor4f(1, 1, 1, 1);
|
|
glColor4f(1, 1, 1, 1);
|
|
|
|
int count = renderChunks(0, (int)chunks[playerIndex].length, layer, alpha);
|
|
|
|
return count;
|
|
}
|
|
|
|
#ifdef __PSVITA__
|
|
#include <stdlib.h>
|
|
|
|
// this is need to sort the chunks by depth
|
|
typedef struct {
|
|
int Index;
|
|
float Depth;
|
|
} SChunckSort;
|
|
|
|
int compare(const void* a, const void* b) {
|
|
return (((SChunckSort*)a)->Depth - ((SChunckSort*)b)->Depth);
|
|
}
|
|
|
|
#endif
|
|
|
|
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
|
|
std::shared_ptr<LivingEntity> 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);
|
|
|
|
#ifdef __PSVITA__
|
|
// AP - also set the camera position so we can work out if a chunk is fogged
|
|
// or not
|
|
RenderManager.SetCameraPosition((float)-xOff, (float)-yOff, (float)-zOff);
|
|
#endif
|
|
|
|
#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__
|
|
|
|
#ifdef __PSVITA__
|
|
// AP - alpha cut out is expensive on vita. First render all the non-alpha
|
|
// cut outs
|
|
glDisable(GL_ALPHA_TEST);
|
|
#endif
|
|
|
|
bool first = true;
|
|
int count = 0;
|
|
ClipChunk* pClipChunk = chunks[playerIndex].data;
|
|
unsigned char emptyFlag = LevelRenderer::CHUNK_FLAG_EMPTY0 << layer;
|
|
static thread_local std::vector<ClipChunk*> sortList;
|
|
sortList.clear();
|
|
if (sortList.capacity() < (size_t)chunks[playerIndex].length) {
|
|
sortList.reserve(chunks[playerIndex].length);
|
|
}
|
|
{
|
|
FRAME_PROFILE_SCOPE(ChunkCollect);
|
|
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;
|
|
|
|
sortList.push_back(pClipChunk);
|
|
}
|
|
// he sorts me till i
|
|
std::sort(sortList.begin(), sortList.end(),
|
|
[xOff, yOff, zOff, layer](ClipChunk* a, ClipChunk* b) {
|
|
float dxA = (float)((a->chunk->x + 8.0f) - xOff);
|
|
float dyA = (float)((a->chunk->y + 8.0f) - yOff);
|
|
float dzA = (float)((a->chunk->z + 8.0f) - zOff);
|
|
float distSqA = dxA * dxA + dyA * dyA + dzA * dzA;
|
|
|
|
float dxB = (float)((b->chunk->x + 8.0f) - xOff);
|
|
float dyB = (float)((b->chunk->y + 8.0f) - yOff);
|
|
float dzB = (float)((b->chunk->z + 8.0f) - zOff);
|
|
float distSqB = dxB * dxB + dyB * dyB + dzB * dzB;
|
|
|
|
if (layer == 0)
|
|
return distSqA < distSqB; // Opaque: Closest first
|
|
return distSqA > distSqB; // Transparent: Furthest
|
|
// first
|
|
});
|
|
}
|
|
{
|
|
FRAME_PROFILE_SCOPE(ChunkPlayback);
|
|
for (ClipChunk* chunk : sortList) {
|
|
// ugly occluder
|
|
float dx = (chunk->chunk->x + 8.0f) - (float)xOff;
|
|
float dy = (chunk->chunk->y + 8.0f) - (float)yOff;
|
|
float dz = (chunk->chunk->z + 8.0f) - (float)zOff;
|
|
bool isVeryNear = (dx * dx + dy * dy + dz * dz) < (16.0f * 16.0f);
|
|
|
|
if (!isVeryNear && layer == 0) {
|
|
// todo: occlusion flag
|
|
}
|
|
|
|
int list = chunk->globalIdx * 2 + layer;
|
|
list += chunkLists;
|
|
|
|
// 4jcraft: replaced glPushMatrix/glTranslatef/glPopMatrix per chunk
|
|
// no more full MVP upload per chunk, can also be bkwards compat
|
|
RenderManager.SetChunkOffset((float)chunk->chunk->x,
|
|
(float)chunk->chunk->y,
|
|
(float)chunk->chunk->z);
|
|
|
|
if (RenderManager.CBuffCall(list, first)) {
|
|
first = false;
|
|
}
|
|
count++;
|
|
}
|
|
RenderManager.SetChunkOffset(0.f, 0.f, 0.f);
|
|
}
|
|
|
|
#ifdef __PSVITA__
|
|
// AP - alpha cut out is expensive on vita. Now we render all the alpha cut
|
|
// outs
|
|
glEnable(GL_ALPHA_TEST);
|
|
RenderManager.StateSetForceLOD(
|
|
0); // AP - force mipmapping off for cut outs
|
|
first = true;
|
|
pClipChunk = chunks[playerIndex].data;
|
|
emptyFlag = LevelRenderer::CHUNK_FLAG_EMPTY0 << layer;
|
|
{
|
|
FRAME_PROFILE_SCOPE(ChunkPlayback);
|
|
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
|
|
if (!(globalChunkFlags[pClipChunk->globalIdx] &
|
|
LevelRenderer::CHUNK_FLAG_CUT_OUT))
|
|
continue; // Does this chunk contain any cut out geometry
|
|
|
|
// List can be calculated directly from the chunk's global idex
|
|
int list = pClipChunk->globalIdx * 2 + layer;
|
|
list += chunkLists;
|
|
|
|
if (RenderManager.CBuffCallCutOut(list, first)) {
|
|
first = false;
|
|
}
|
|
}
|
|
}
|
|
RenderManager.StateSetForceLOD(-1); // AP - back to normal mipmapping
|
|
#endif
|
|
|
|
#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++;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<Mob> 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;
|
|
|
|
for (int l = 0; l < RENDERLISTS_LENGTH; l++) renderLists[l].clear();
|
|
int lists = 0;
|
|
for (auto it = _renderChunks.begin(); it != _renderChunks.end(); it++) {
|
|
Chunk* chunk = *it;
|
|
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->bindTexture(
|
|
&END_SKY_LOCATION); // 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);
|
|
|
|
#ifdef __PSVITA__
|
|
// AP - alpha cut out is expensive on vita.
|
|
glDisable(GL_ALPHA_TEST);
|
|
#endif
|
|
|
|
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(&SUN_LOCATION);
|
|
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(
|
|
&MOON_PHASES_LOCATION); // 4J was L"/1_2_2/terrain/moon_phases.png"
|
|
int phase = level[playerIndex]->getMoonPhase();
|
|
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);
|
|
|
|
#ifdef __PSVITA__
|
|
// AP - alpha cut out is expensive on vita.
|
|
glDisable(GL_ALPHA_TEST);
|
|
#endif
|
|
|
|
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__) && !defined(__ORBIS__) && !defined(__PSVITA__)
|
|
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 << eDebugSetting_FreezeTime)) {
|
|
iTicks = m_freezeticks;
|
|
}
|
|
}
|
|
glDisable(GL_CULL_FACE);
|
|
float yOffs =
|
|
(float)(mc->cameraTargetPlayer->yOld +
|
|
(mc->cameraTargetPlayer->y - mc->cameraTargetPlayer->yOld) *
|
|
alpha);
|
|
int s = 32;
|
|
int d = 256 / s;
|
|
Tesselator* t = Tesselator::getInstance();
|
|
|
|
textures->bindTexture(&CLOUDS_LOCATION);
|
|
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 << eDebugSetting_FreezeTime))) {
|
|
m_freezeticks = iTicks;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool LevelRenderer::isInCloud(double x, double y, double z, float alpha) {
|
|
return false;
|
|
}
|
|
|
|
// 4J - new geometry for clouds. This is a full array of cubes, one per texel -
|
|
// the original is an array of intersecting fins which aren't ever going to
|
|
// render perfectly. The geometry is split into 6 command buffers, one per
|
|
// facing direction. This is to keep rendering similar to the original, where
|
|
// the geometry isn't backface culled, but a decision on which sides to render
|
|
// is made per 8x8 chunk of sky - this keeps the cloud more solid looking when
|
|
// you are actually inside it. Also make a 7th list that includes all 6
|
|
// directions, to make rendering of all 6 at once more optimal (we do this when
|
|
// the player isn't potentially inside the clouds)
|
|
void LevelRenderer::createCloudMesh() {
|
|
cloudList = MemoryTracker::genLists(7);
|
|
|
|
Tesselator* t = Tesselator::getInstance();
|
|
const float h = 4.0f;
|
|
const int D = 8;
|
|
|
|
for (int i = 0; i < 7; i++) {
|
|
glNewList(cloudList + i, GL_COMPILE);
|
|
|
|
if ((i == 0) || (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.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 << eDebugSetting_FreezeTime)) {
|
|
iTicks = m_freezeticks;
|
|
}
|
|
}
|
|
|
|
float ss = 12.0f;
|
|
float h = 4.0f;
|
|
|
|
double time = (ticks + alpha);
|
|
double xo =
|
|
(mc->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(
|
|
&CLOUDS_LOCATION); // 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 0
|
|
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
|
|
glDisable(GL_CULL_FACE);
|
|
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 << eDebugSetting_FreezeTime))) {
|
|
m_freezeticks = iTicks;
|
|
}
|
|
}
|
|
RenderManager.StateSetEnableViewportClipPlanes(false);
|
|
}
|
|
|
|
bool LevelRenderer::updateDirtyChunks() {
|
|
#ifdef _LARGE_WORLDS
|
|
std::list<std::pair<ClipChunk*, int> > 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
|
|
{
|
|
FRAME_PROFILE_SCOPE(ChunkDirtyScan);
|
|
|
|
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
|
|
std::shared_ptr<LocalPlayer> 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<ClipChunk*, int>(
|
|
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;
|
|
{
|
|
FRAME_PROFILE_SCOPE(ChunkRebuildSchedule);
|
|
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_t
|
|
// totalTime =
|
|
// 0; static int64_t countTime = 0;
|
|
// int64_t startTime = System::currentTimeMillis();
|
|
|
|
// app.DebugPrintf("Rebuilding permaChunk %d\n", index);
|
|
|
|
{
|
|
FRAME_PROFILE_SCOPE(ChunkRebuildBody);
|
|
permaChunk[index].rebuild();
|
|
}
|
|
|
|
if (index != 0) {
|
|
FRAME_PROFILE_SCOPE(ChunkRebuildSchedule);
|
|
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_t 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
|
|
FRAME_PROFILE_SCOPE(ChunkRebuildSchedule);
|
|
s_activationEventA[index - 1]->Set();
|
|
}
|
|
}
|
|
|
|
// Wait for the other threads to be done as well
|
|
{
|
|
FRAME_PROFILE_SCOPE(ChunkRebuildSchedule);
|
|
s_rebuildCompleteEvents->WaitForAll(INFINITE);
|
|
}
|
|
}
|
|
#else
|
|
if (nearChunk) {
|
|
chunk = nearChunk->chunk;
|
|
PIXBeginNamedEvent(0, "Rebuilding near chunk %d %d %d", chunk->x,
|
|
chunk->y, chunk->z);
|
|
static Chunk permaChunk;
|
|
{
|
|
FRAME_PROFILE_SCOPE(ChunkRebuildSchedule);
|
|
// 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.makeCopyForRebuild(chunk);
|
|
LeaveCriticalSection(&m_csDirtyChunks);
|
|
}
|
|
// static int64_t totalTime = 0;
|
|
// static int64_t countTime = 0;
|
|
// int64_t startTime = System::currentTimeMillis();
|
|
{
|
|
FRAME_PROFILE_SCOPE(ChunkRebuildBody);
|
|
permaChunk.rebuild();
|
|
}
|
|
// int64_t 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__
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(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(std::shared_ptr<Player> player, HitResult* h,
|
|
int mode,
|
|
std::shared_ptr<ItemInstance> 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(&TextureAtlas::LOCATION_BLOCKS);
|
|
}
|
|
glDisable(GL_BLEND);
|
|
glDisable(GL_ALPHA_TEST);
|
|
}
|
|
|
|
void LevelRenderer::renderDestroyAnimation(Tesselator* t,
|
|
std::shared_ptr<Player> 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(&TextureAtlas::LOCATION_BLOCKS);
|
|
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();
|
|
#ifdef __PSVITA__
|
|
// AP : fix for bug 4952. No amount of polygon offset will push this
|
|
// close enough to be seen above the second tile layer when looking
|
|
// straight down so just add on a little bit of y to fix this. hacky
|
|
// hacky
|
|
t->offset((float)-xo, (float)-yo + 0.01f, (float)-zo);
|
|
#else
|
|
t->offset((float)-xo, (float)-yo, (float)-zo);
|
|
#endif
|
|
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::stone;
|
|
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(std::shared_ptr<Player> player,
|
|
HitResult* h, int mode, float a) {
|
|
if (mode == 0 && h->type == HitResult::TILE) {
|
|
int iPad = mc->player->GetXboxPad(); // 4J added
|
|
|
|
const float ss = 0.002f;
|
|
|
|
// 4J-PB - If Display HUD is false, don't render the hit outline
|
|
if (app.GetGameSettings(iPad, eGameSetting_DisplayHUD) == 0) return;
|
|
RenderManager.StateSetLightingEnable(false);
|
|
glDisable(GL_TEXTURE_2D);
|
|
|
|
// draw hit outline
|
|
RenderManager.StateSetColour(0.0f, 0.0f, 0.0f, 0.4f);
|
|
RenderManager.StateSetLineWidth(1.0f);
|
|
|
|
// hack
|
|
glDepthFunc(GL_LEQUAL);
|
|
glEnable(GL_POLYGON_OFFSET_LINE);
|
|
glPolygonOffset(-2.0f, -2.0f);
|
|
|
|
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;
|
|
|
|
AABB bb = Tile::tiles[tileId]
|
|
->getTileAABB(level[iPad], h->x, h->y, h->z)
|
|
.grow(ss, ss, ss)
|
|
.move(-xo, -yo, -zo);
|
|
|
|
render(&bb);
|
|
}
|
|
|
|
// restore
|
|
glDisable(GL_POLYGON_OFFSET_LINE);
|
|
RenderManager.StateSetColour(1.0f, 1.0f, 1.0f, 1.0f);
|
|
glEnable(GL_TEXTURE_2D);
|
|
RenderManager.StateSetLightingEnable(true);
|
|
}
|
|
}
|
|
|
|
void LevelRenderer::render(AABB* b) {
|
|
Tesselator* t = Tesselator::getInstance();
|
|
RenderManager.StateSetLightingEnable(false);
|
|
glDisable(GL_TEXTURE_2D);
|
|
RenderManager.StateSetColour(0.0f, 0.0f, 0.0f, 0.4f);
|
|
|
|
// prevent zfight
|
|
glEnable(GL_POLYGON_OFFSET_LINE);
|
|
glPolygonOffset(-2.0f, -2.0f);
|
|
|
|
// One call please!
|
|
t->begin(GL_LINES);
|
|
|
|
// Bottom
|
|
t->vertex(b->x0, b->y0, b->z0);
|
|
t->vertex(b->x1, b->y0, b->z0);
|
|
t->vertex(b->x1, b->y0, b->z0);
|
|
t->vertex(b->x1, b->y0, b->z1);
|
|
t->vertex(b->x1, b->y0, b->z1);
|
|
t->vertex(b->x0, b->y0, b->z1);
|
|
t->vertex(b->x0, b->y0, b->z1);
|
|
t->vertex(b->x0, b->y0, b->z0);
|
|
|
|
// Top
|
|
t->vertex(b->x0, b->y1, b->z0);
|
|
t->vertex(b->x1, b->y1, b->z0);
|
|
t->vertex(b->x1, b->y1, b->z0);
|
|
t->vertex(b->x1, b->y1, b->z1);
|
|
t->vertex(b->x1, b->y1, b->z1);
|
|
t->vertex(b->x0, b->y1, b->z1);
|
|
t->vertex(b->x0, b->y1, b->z1);
|
|
t->vertex(b->x0, b->y1, b->z0);
|
|
|
|
// Vertical
|
|
t->vertex(b->x0, b->y0, b->z0);
|
|
t->vertex(b->x0, b->y1, b->z0);
|
|
t->vertex(b->x1, b->y0, b->z0);
|
|
t->vertex(b->x1, b->y1, b->z0);
|
|
t->vertex(b->x1, b->y0, b->z1);
|
|
t->vertex(b->x1, b->y1, b->z1);
|
|
t->vertex(b->x0, b->y0, b->z1);
|
|
t->vertex(b->x0, b->y1, b->z1);
|
|
|
|
t->end();
|
|
glDisable(GL_POLYGON_OFFSET_LINE);
|
|
RenderManager.StateSetLightingEnable(true);
|
|
glEnable(GL_TEXTURE_2D);
|
|
RenderManager.StateSetColour(1.0f, 1.0f, 1.0f, 1.0f);
|
|
}
|
|
|
|
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*)(intptr_t)(uintptr_t)(index + 2));
|
|
#endif
|
|
|
|
#ifdef _XBOX
|
|
PIXSetMarker(0, "Setting chunk %d %d %d dirty", x * 16,
|
|
y * 16, z * 16);
|
|
#else
|
|
PIXSetMarkerDeprecated(0, "Setting chunk %d %d %d dirty",
|
|
x * 16, y * 16, z * 16);
|
|
#endif
|
|
}
|
|
// 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 std::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(std::shared_ptr<Entity> entity, int iSound,
|
|
double x, double y, double z, float volume,
|
|
float pitch, float fSoundClipDist) {}
|
|
|
|
void LevelRenderer::playSoundExceptPlayer(std::shared_ptr<Player> player,
|
|
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<BubbleParticle>( new
|
|
BubbleParticle(level[playerIndex], x, y, z, xa, ya, za) ) ); else if (name==
|
|
L"smoke") mc->particleEngine->add(shared_ptr<SmokeParticle>( new
|
|
SmokeParticle(level[playerIndex], x, y, z, xa, ya, za) ) ); else if (name==
|
|
L"note") mc->particleEngine->add(shared_ptr<NoteParticle>( new
|
|
NoteParticle(level[playerIndex], x, y, z, xa, ya, za) ) ); else if (name==
|
|
L"portal") mc->particleEngine->add(shared_ptr<PortalParticle>( new
|
|
PortalParticle(level[playerIndex], x, y, z, xa, ya, za) ) ); else if (name==
|
|
L"explode") mc->particleEngine->add(shared_ptr<ExplodeParticle>( new
|
|
ExplodeParticle(level[playerIndex], x, y, z, xa, ya, za) ) ); else if (name==
|
|
L"flame") mc->particleEngine->add(shared_ptr<FlameParticle>( new
|
|
FlameParticle(level[playerIndex], x, y, z, xa, ya, za) ) ); else if (name==
|
|
L"lava") mc->particleEngine->add(shared_ptr<LavaParticle>( new
|
|
LavaParticle(level[playerIndex], x, y, z) ) ); else if (name== L"footstep")
|
|
mc->particleEngine->add(shared_ptr<FootstepParticle>( new
|
|
FootstepParticle(textures, level[playerIndex], x, y, z) ) ); else if (name==
|
|
L"splash") mc->particleEngine->add(shared_ptr<SplashParticle>( new
|
|
SplashParticle(level[playerIndex], x, y, z, xa, ya, za) ) ); else if (name==
|
|
L"largesmoke") mc->particleEngine->add(shared_ptr<SmokeParticle>( new
|
|
SmokeParticle(level[playerIndex], x, y, z, xa, ya, za, 2.5f) ) ); else if
|
|
(name== L"reddust") mc->particleEngine->add(shared_ptr<RedDustParticle>( new
|
|
RedDustParticle(level[playerIndex], x, y, z, (float) xa, (float) ya, (float) za)
|
|
) ); else if (name== L"snowballpoof")
|
|
mc->particleEngine->add(shared_ptr<BreakingItemParticle>( new
|
|
BreakingItemParticle(level[playerIndex], x, y, z, Item::snowBall) ) ); else if
|
|
(name== L"snowshovel") mc->particleEngine->add(shared_ptr<SnowShovelParticle>(
|
|
new SnowShovelParticle(level[playerIndex], x, y, z, xa, ya, za) ) ); else if
|
|
(name== L"slime") mc->particleEngine->add(shared_ptr<BreakingItemParticle>( new
|
|
BreakingItemParticle(level[playerIndex], x, y, z, Item::slimeBall)) ) ; else if
|
|
(name== L"heart") mc->particleEngine->add(shared_ptr<HeartParticle>( 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);
|
|
}
|
|
|
|
std::shared_ptr<Particle> 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 (std::isnan(x)) return nullptr;
|
|
if (std::isnan(y)) return nullptr;
|
|
if (std::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) {
|
|
std::shared_ptr<Player> 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;
|
|
}
|
|
|
|
std::shared_ptr<Particle> particle;
|
|
|
|
switch (eParticleType) {
|
|
case eParticleType_hugeexplosion:
|
|
particle = std::shared_ptr<Particle>(
|
|
new HugeExplosionSeedParticle(lev, x, y, z, xa, ya, za));
|
|
break;
|
|
case eParticleType_largeexplode:
|
|
particle = std::shared_ptr<Particle>(
|
|
new HugeExplosionParticle(textures, lev, x, y, z, xa, ya, za));
|
|
break;
|
|
case eParticleType_fireworksspark:
|
|
particle = std::shared_ptr<Particle>(
|
|
new FireworksParticles::FireworksSparkParticle(
|
|
lev, x, y, z, xa, ya, za, mc->particleEngine));
|
|
particle->setAlpha(0.99f);
|
|
break;
|
|
|
|
case eParticleType_bubble:
|
|
particle = std::shared_ptr<Particle>(
|
|
new BubbleParticle(lev, x, y, z, xa, ya, za));
|
|
break;
|
|
|
|
case eParticleType_suspended:
|
|
particle = std::shared_ptr<Particle>(
|
|
new SuspendedParticle(lev, x, y, z, xa, ya, za));
|
|
break;
|
|
case eParticleType_depthsuspend:
|
|
particle = std::shared_ptr<Particle>(
|
|
new SuspendedTownParticle(lev, x, y, z, xa, ya, za));
|
|
break;
|
|
case eParticleType_townaura:
|
|
particle = std::shared_ptr<Particle>(
|
|
new SuspendedTownParticle(lev, x, y, z, xa, ya, za));
|
|
break;
|
|
case eParticleType_crit: {
|
|
std::shared_ptr<CritParticle2> critParticle2 =
|
|
std::shared_ptr<CritParticle2>(
|
|
new CritParticle2(lev, x, y, z, xa, ya, za));
|
|
critParticle2->CritParticle2PostConstructor();
|
|
particle = std::shared_ptr<Particle>(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: {
|
|
std::shared_ptr<CritParticle2> critParticle2 =
|
|
std::shared_ptr<CritParticle2>(
|
|
new CritParticle2(lev, x, y, z, xa, ya, za));
|
|
critParticle2->CritParticle2PostConstructor();
|
|
particle = std::shared_ptr<Particle>(critParticle2);
|
|
particle->setColor(particle->getRedCol() * 0.3f,
|
|
particle->getGreenCol() * 0.8f,
|
|
particle->getBlueCol());
|
|
particle->setNextMiscAnimTex();
|
|
} break;
|
|
case eParticleType_smoke:
|
|
particle = std::shared_ptr<Particle>(
|
|
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 = std::shared_ptr<Particle>(tmp);
|
|
} break;
|
|
case eParticleType_mobSpell:
|
|
particle = std::shared_ptr<Particle>(
|
|
new SpellParticle(lev, x, y, z, 0, 0, 0));
|
|
particle->setColor((float)xa, (float)ya, (float)za);
|
|
break;
|
|
case eParticleType_mobSpellAmbient:
|
|
particle = std::shared_ptr<SpellParticle>(
|
|
new SpellParticle(lev, x, y, z, 0, 0, 0));
|
|
particle->setAlpha(0.15f);
|
|
particle->setColor((float)xa, (float)ya, (float)za);
|
|
break;
|
|
case eParticleType_spell:
|
|
particle = std::shared_ptr<Particle>(
|
|
new SpellParticle(lev, x, y, z, xa, ya, za));
|
|
break;
|
|
case eParticleType_witchMagic: {
|
|
particle = std::shared_ptr<SpellParticle>(
|
|
new SpellParticle(lev, x, y, z, xa, ya, za));
|
|
std::dynamic_pointer_cast<SpellParticle>(particle)->setBaseTex(9 *
|
|
16);
|
|
float randBrightness = lev->random->nextFloat() * 0.5f + 0.35f;
|
|
particle->setColor(1 * randBrightness, 0 * randBrightness,
|
|
1 * randBrightness);
|
|
} break;
|
|
case eParticleType_instantSpell:
|
|
particle = std::shared_ptr<Particle>(
|
|
new SpellParticle(lev, x, y, z, xa, ya, za));
|
|
std::dynamic_pointer_cast<SpellParticle>(particle)->setBaseTex(9 *
|
|
16);
|
|
break;
|
|
case eParticleType_note:
|
|
particle = std::shared_ptr<Particle>(
|
|
new NoteParticle(lev, x, y, z, xa, ya, za));
|
|
break;
|
|
case eParticleType_netherportal:
|
|
particle = std::shared_ptr<Particle>(
|
|
new NetherPortalParticle(lev, x, y, z, xa, ya, za));
|
|
break;
|
|
case eParticleType_ender:
|
|
particle = std::shared_ptr<Particle>(
|
|
new EnderParticle(lev, x, y, z, xa, ya, za));
|
|
break;
|
|
case eParticleType_enchantmenttable:
|
|
particle = std::shared_ptr<Particle>(
|
|
new EchantmentTableParticle(lev, x, y, z, xa, ya, za));
|
|
break;
|
|
case eParticleType_explode:
|
|
particle = std::shared_ptr<Particle>(
|
|
new ExplodeParticle(lev, x, y, z, xa, ya, za));
|
|
break;
|
|
case eParticleType_flame:
|
|
particle = std::shared_ptr<Particle>(
|
|
new FlameParticle(lev, x, y, z, xa, ya, za));
|
|
break;
|
|
case eParticleType_lava:
|
|
particle =
|
|
std::shared_ptr<Particle>(new LavaParticle(lev, x, y, z));
|
|
break;
|
|
case eParticleType_footstep:
|
|
particle = std::shared_ptr<Particle>(
|
|
new FootstepParticle(textures, lev, x, y, z));
|
|
break;
|
|
case eParticleType_splash:
|
|
particle = std::shared_ptr<Particle>(
|
|
new SplashParticle(lev, x, y, z, xa, ya, za));
|
|
break;
|
|
case eParticleType_largesmoke:
|
|
particle = std::shared_ptr<Particle>(
|
|
new SmokeParticle(lev, x, y, z, xa, ya, za, 2.5f));
|
|
break;
|
|
case eParticleType_reddust:
|
|
particle = std::shared_ptr<Particle>(new RedDustParticle(
|
|
lev, x, y, z, (float)xa, (float)ya, (float)za));
|
|
break;
|
|
case eParticleType_snowballpoof:
|
|
particle = std::shared_ptr<Particle>(new BreakingItemParticle(
|
|
lev, x, y, z, Item::snowBall, textures));
|
|
break;
|
|
case eParticleType_dripWater:
|
|
particle = std::shared_ptr<Particle>(
|
|
new DripParticle(lev, x, y, z, Material::water));
|
|
break;
|
|
case eParticleType_dripLava:
|
|
particle = std::shared_ptr<Particle>(
|
|
new DripParticle(lev, x, y, z, Material::lava));
|
|
break;
|
|
case eParticleType_snowshovel:
|
|
particle = std::shared_ptr<Particle>(
|
|
new SnowShovelParticle(lev, x, y, z, xa, ya, za));
|
|
break;
|
|
case eParticleType_slime:
|
|
particle = std::shared_ptr<Particle>(new BreakingItemParticle(
|
|
lev, x, y, z, Item::slimeBall, textures));
|
|
break;
|
|
case eParticleType_heart:
|
|
particle = std::shared_ptr<Particle>(
|
|
new HeartParticle(lev, x, y, z, xa, ya, za));
|
|
break;
|
|
case eParticleType_angryVillager:
|
|
particle = std::shared_ptr<Particle>(
|
|
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 = std::shared_ptr<Particle>(
|
|
new SuspendedTownParticle(lev, x, y, z, xa, ya, za));
|
|
particle->setMiscTex(2 + 16 * 5);
|
|
particle->setColor(1, 1, 1);
|
|
break;
|
|
case eParticleType_dragonbreath:
|
|
particle = std::shared_ptr<Particle>(
|
|
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 = std::shared_ptr<Particle>(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 = std::dynamic_pointer_cast<Particle>(
|
|
std::shared_ptr<TerrainParticle>(
|
|
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(std::shared_ptr<Entity> entity) {
|
|
if (entity->instanceof(eTYPE_PLAYER)) {
|
|
std::shared_ptr<Player> player =
|
|
std::dynamic_pointer_cast<Player>(entity);
|
|
player->prepareCustomTextures();
|
|
|
|
// 4J-PB - adding these from global title storage
|
|
if (player->customTextureUrl != L"") {
|
|
textures->addMemTexture(player->customTextureUrl,
|
|
new MobSkinMemTextureProcessor());
|
|
}
|
|
if (player->customTextureUrl2 != L"") {
|
|
textures->addMemTexture(player->customTextureUrl2,
|
|
new MobSkinMemTextureProcessor());
|
|
}
|
|
}
|
|
}
|
|
|
|
void LevelRenderer::entityRemoved(std::shared_ptr<Entity> entity) {
|
|
if (entity->instanceof(eTYPE_PLAYER)) {
|
|
std::shared_ptr<Player> player =
|
|
std::dynamic_pointer_cast<Player>(entity);
|
|
if (player->customTextureUrl != L"") {
|
|
textures->removeMemTexture(player->customTextureUrl);
|
|
}
|
|
if (player->customTextureUrl2 != L"") {
|
|
textures->removeMemTexture(player->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::globalLevelEvent(int type, int sourceX, int sourceY,
|
|
int sourceZ, int data) {
|
|
Level* lev;
|
|
int playerIndex = mc->player->GetXboxPad(); // 4J added
|
|
lev = level[playerIndex];
|
|
|
|
Random* random = lev->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 = sourceX - mc->cameraTargetPlayer->x;
|
|
double dy = sourceY - mc->cameraTargetPlayer->y;
|
|
double dz = sourceZ - 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;
|
|
}
|
|
if (type == LevelEvent::SOUND_WITHER_BOSS_SPAWN) {
|
|
lev->playLocalSound(sx, sy, sz, eSoundType_MOB_WITHER_SPAWN,
|
|
1.0f, 1.0f, false);
|
|
} else if (type == LevelEvent::SOUND_DRAGON_DEATH) {
|
|
lev->playLocalSound(sx, sy, sz,
|
|
eSoundType_MOB_ENDERDRAGON_END, 5.0f,
|
|
1.0f, false);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void LevelRenderer::levelEvent(std::shared_ptr<Player> 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, false);
|
|
break;
|
|
case LevelEvent::SOUND_CLICK:
|
|
level[playerIndex]->playLocalSound(x, y, z, eSoundType_RANDOM_CLICK,
|
|
1.0f, 1.0f, false);
|
|
break;
|
|
case LevelEvent::SOUND_LAUNCH:
|
|
level[playerIndex]->playLocalSound(x, y, z, eSoundType_RANDOM_BOW,
|
|
1.0f, 1.2f, false);
|
|
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, data);
|
|
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;
|
|
|
|
std::shared_ptr<Particle> 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, false);
|
|
} 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;
|
|
|
|
std::shared_ptr<Particle> 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::PARTICLES_PLANT_GROWTH:
|
|
DyePowderItem::addGrowthParticles(level[playerIndex], x, y, z,
|
|
data);
|
|
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,
|
|
false);
|
|
} 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,
|
|
false);
|
|
}
|
|
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,
|
|
false);
|
|
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,
|
|
false);
|
|
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, false);
|
|
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,
|
|
false);
|
|
break;
|
|
case LevelEvent::SOUND_PLAY_RECORDING: {
|
|
RecordingItem* rci =
|
|
dynamic_cast<RecordingItem*>(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,
|
|
false, 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,
|
|
false, 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_BLAZE_FIREBALL:
|
|
level[playerIndex]->playLocalSound(
|
|
x + 0.5, y + 0.5, z + 0.5, eSoundType_MOB_GHAST_FIREBALL, 2,
|
|
(random->nextFloat() - random->nextFloat()) * 0.2f +
|
|
1.0f); //, false);
|
|
break;
|
|
case LevelEvent::SOUND_WITHER_BOSS_SHOOT:
|
|
level[playerIndex]->playLocalSound(
|
|
x + 0.5, y + 0.5, z + 0.5, eSoundType_MOB_WITHER_SHOOT, 2,
|
|
(random->nextFloat() - random->nextFloat()) * 0.2f +
|
|
1.0f); //, false);
|
|
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;
|
|
case LevelEvent::SOUND_BAT_LIFTOFF:
|
|
level[playerIndex]->playLocalSound(
|
|
x + 0.5, y + 0.5, z + 0.5, eSoundType_MOB_BAT_TAKEOFF, .05f,
|
|
(random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f);
|
|
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(
|
|
std::unordered_map<int, BlockDestructionProgress*>::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 );
|
|
int xx = (Mth::intFloorDiv(x, CHUNK_XZSIZE)) +
|
|
(MAX_LEVEL_RENDER_SIZE[dimIdx] / 2);
|
|
int yy = Mth::intFloorDiv(y, CHUNK_SIZE);
|
|
int zz = (Mth::intFloorDiv(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);
|
|
flags |= refCount << CHUNK_FLAG_REF_SHIFT;
|
|
globalChunkFlags[index] = flags;
|
|
|
|
return refCount;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
unsigned char LevelRenderer::decGlobalChunkRefCount(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);
|
|
flags |= refCount << CHUNK_FLAG_REF_SHIFT;
|
|
globalChunkFlags[index] = flags;
|
|
|
|
return refCount;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// 4J added
|
|
void LevelRenderer::fullyFlagRenderableTileEntitiesToBeRemoved() {
|
|
EnterCriticalSection(&m_csRenderableTileEntities);
|
|
AUTO_VAR(itChunkEnd, renderableTileEntities.end());
|
|
for (AUTO_VAR(it, renderableTileEntities.begin()); it != itChunkEnd; it++) {
|
|
AUTO_VAR(itTEEnd, it->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::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((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);
|
|
}
|
|
|
|
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({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*)(intptr_t)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);
|
|
#ifdef __ORBIS__
|
|
rebuildThreads[i]->SetPriority(
|
|
THREAD_PRIORITY_BELOW_NORMAL); // On Orbis, this core is also
|
|
// used for Matching 2, and that
|
|
// priority of that seems to be
|
|
// always at default no matter
|
|
// what we set it to. Prioritise
|
|
// this below Matching 2.
|
|
#endif
|
|
} else if ((i % 3) == 2)
|
|
rebuildThreads[i]->SetProcessor(CPU_CORE_CHUNK_REBUILD_C);
|
|
|
|
// ResumeThread( saveThreads[j] );
|
|
rebuildThreads[i]->Run();
|
|
}
|
|
}
|
|
|
|
int LevelRenderer::rebuildChunkThreadProc(void* lpParam) {
|
|
Tesselator::CreateNewThreadStorage(1024 * 1024);
|
|
RenderManager.InitialiseContext();
|
|
Chunk::CreateNewThreadStorage();
|
|
Tile::CreateNewThreadStorage();
|
|
|
|
int index = (int)(uintptr_t)lpParam;
|
|
|
|
while (true) {
|
|
s_activationEventA[index]->WaitForSignal(INFINITE);
|
|
|
|
// app.DebugPrintf("Rebuilding permaChunk %d\n", index + 1);
|
|
{
|
|
FRAME_PROFILE_SCOPE(ChunkRebuildBody);
|
|
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);
|
|
}
|
|
|
|
// 4J - for test purposes, check all chunks that are currently present for the
|
|
// player. Currently this is implemented to do tests to identify missing client
|
|
// chunks in flat worlds, but this could be extended to do other kinds of
|
|
// automated testing. Returns the number of chunks that are present, so that
|
|
// from the calling function we can determine when chunks have finished
|
|
// loading/generating round the current location.
|
|
int LevelRenderer::checkAllPresentChunks(bool* faultFound) {
|
|
int playerIndex = mc->player->GetXboxPad(); // 4J added
|
|
|
|
int presentCount = 0;
|
|
ClipChunk* pClipChunk = chunks[playerIndex].data;
|
|
for (int i = 0; i < chunks[playerIndex].length; i++, pClipChunk++) {
|
|
if (pClipChunk->chunk->y == 0) {
|
|
bool chunkPresent = level[0]->reallyHasChunk(
|
|
pClipChunk->chunk->x >> 4, pClipChunk->chunk->z >> 4);
|
|
if (chunkPresent) {
|
|
presentCount++;
|
|
LevelChunk* levelChunk = level[0]->getChunk(
|
|
pClipChunk->chunk->x >> 4, pClipChunk->chunk->z >> 4);
|
|
|
|
for (int cx = 4; cx <= 12; cx++) {
|
|
for (int cz = 4; cz <= 12; cz++) {
|
|
int t0 = levelChunk->getTile(cx, 0, cz);
|
|
if ((t0 != Tile::unbreakable_Id) &&
|
|
(t0 != Tile::dirt_Id)) {
|
|
*faultFound = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return presentCount;
|
|
}
|