mirror of
https://github.com/4jcraft/4jcraft.git
synced 2026-04-24 09:23:37 +00:00
CRITICAL_SECTION is reentrant; std::mutex is not. This caused deadlocks during world generation, post-processing, and saving.
4086 lines
160 KiB
C++
4086 lines
160 KiB
C++
#include <thread>
|
|
#include <chrono>
|
|
#include <array>
|
|
#include <mutex>
|
|
|
|
#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
|
|
|
|
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;
|
|
|
|
uint64_t* LevelRenderer::globalChunkConnectivity =
|
|
nullptr; // bad placement do bettr juicey
|
|
|
|
#if defined(_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 = nullptr;
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
level[i] = nullptr;
|
|
tileRenderer[i] = nullptr;
|
|
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] = nullptr; // 4J - removed - not
|
|
// sorting
|
|
// our chunks anymore
|
|
chunks[i] = ClipChunkArray();
|
|
lastPlayerCount[i] = 0;
|
|
}
|
|
|
|
// std::mutex members are default-constructed
|
|
|
|
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());
|
|
|
|
globalChunkConnectivity = new uint64_t[getGlobalChunkCount()];
|
|
memset(globalChunkConnectivity, 0xFF,
|
|
getGlobalChunkCount() * sizeof(uint64_t)); // 0xFF >> Fully open
|
|
|
|
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();
|
|
}
|
|
|
|
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] != nullptr) {
|
|
// 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] != nullptr) {
|
|
delete tileRenderer[playerIndex];
|
|
}
|
|
tileRenderer[playerIndex] = new TileRenderer(level);
|
|
if (level != nullptr) {
|
|
// 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 != nullptr) {
|
|
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 = nullptr;
|
|
chunks[playerIndex].length = 0;
|
|
// delete sortedChunks[playerIndex]; // 4J -
|
|
// removed - not sorting our chunks anymore
|
|
// sortedChunks[playerIndex] = nullptr; // 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 nullptr
|
|
if (playerIndex == ProfileManager.GetPrimaryPad()) {
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_csRenderableTileEntities);
|
|
renderableTileEntities.clear();
|
|
m_renderableTileEntitiesPendingRemoval.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.
|
|
if (level[playerIndex] == nullptr) {
|
|
return;
|
|
}
|
|
|
|
Minecraft::GetInstance()->gameRenderer->DisableUpdateThread();
|
|
|
|
Tile::leaves->setFancy(mc->options->fancyGraphics);
|
|
lastViewDistance = mc->options->viewDistance;
|
|
|
|
// Calculate size of area we can render based on number of players we need
|
|
// to render for
|
|
int dist = (int)sqrtf((float)PLAYER_RENDER_AREA / (float)activePlayers());
|
|
|
|
// AP - poor little Vita just can't cope with such a big area
|
|
|
|
lastPlayerCount[playerIndex] = activePlayers();
|
|
|
|
xChunks = dist;
|
|
yChunks = Level::maxBuildHeight / CHUNK_SIZE;
|
|
zChunks = dist;
|
|
|
|
if (chunks[playerIndex].data != nullptr) {
|
|
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 nullptr). 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] != nullptr) {
|
|
std::shared_ptr<Entity> player = mc->cameraTargetPlayer;
|
|
if (player != nullptr) {
|
|
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();
|
|
|
|
}
|
|
|
|
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 itEndGE = level[playerIndex]->globalEntities.end();
|
|
for (auto 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 itEndEnts = entities.end();
|
|
for (auto 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() != nullptr)) {
|
|
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.
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_csRenderableTileEntities);
|
|
for (auto 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 it2 = it->second.tiles.begin();
|
|
it2 != it->second.tiles.end(); it2++) {
|
|
TileEntityRenderDispatcher::instance->render(*it2, a);
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
std::lock_guard<std::recursive_mutex> lock(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();
|
|
}
|
|
|
|
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);
|
|
mc->gameRenderer->turnOnLightLayer(alpha);
|
|
|
|
int count = renderChunks(0, (int)chunks[playerIndex].length, layer, alpha);
|
|
mc->gameRenderer->turnOffLightLayer(alpha);
|
|
return count;
|
|
}
|
|
|
|
int LevelRenderer::renderChunks(int from, int to, int layer, double alpha) {
|
|
int playerIndex = mc->player->GetXboxPad();
|
|
if (chunks[playerIndex].data == nullptr) return 0;
|
|
mc->gameRenderer->turnOnLightLayer(alpha);
|
|
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);
|
|
|
|
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) {
|
|
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);
|
|
}
|
|
|
|
glPopMatrix();
|
|
mc->gameRenderer->turnOffLightLayer(alpha);
|
|
|
|
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 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);
|
|
|
|
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 != nullptr) {
|
|
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);
|
|
|
|
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 (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 (!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);
|
|
}
|
|
|
|
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.
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
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() {
|
|
#if defined(_LARGE_WORLDS)
|
|
struct NearestClipChunkSet {
|
|
std::array<std::pair<ClipChunk*, int>, MAX_CONCURRENT_CHUNK_REBUILDS>
|
|
items;
|
|
int count = 0;
|
|
|
|
bool empty() const noexcept { return count == 0; }
|
|
int size() const noexcept { return count; }
|
|
|
|
bool wouldAccept(int distSqWeighted) const noexcept {
|
|
return (count < MAX_CONCURRENT_CHUNK_REBUILDS) ||
|
|
(distSqWeighted < items[count - 1].second);
|
|
}
|
|
|
|
void insert(ClipChunk* chunk, int distSqWeighted) noexcept {
|
|
int pos = 0;
|
|
while ((pos < count) && (items[pos].second <= distSqWeighted)) {
|
|
++pos;
|
|
}
|
|
|
|
if ((count == MAX_CONCURRENT_CHUNK_REBUILDS) &&
|
|
(pos >= MAX_CONCURRENT_CHUNK_REBUILDS)) {
|
|
return;
|
|
}
|
|
|
|
const int newCount = (count < MAX_CONCURRENT_CHUNK_REBUILDS)
|
|
? (count + 1)
|
|
: MAX_CONCURRENT_CHUNK_REBUILDS;
|
|
for (int i = newCount - 1; i > pos; --i) {
|
|
items[i] = items[i - 1];
|
|
}
|
|
items[pos] = std::pair<ClipChunk*, int>(chunk, distSqWeighted);
|
|
count = newCount;
|
|
}
|
|
} nearestClipChunks;
|
|
#endif
|
|
|
|
ClipChunk* nearChunk = nullptr; // Nearest chunk that is dirty
|
|
int veryNearCount = 0;
|
|
int minDistSq = 0x7fffffff; // Distances to this chunk
|
|
std::unique_lock<std::recursive_mutex> dirtyChunksLock(m_csDirtyChunks);
|
|
|
|
// 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);
|
|
|
|
// 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");
|
|
|
|
// 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
|
|
// nullptr 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 == nullptr) continue;
|
|
if (chunks[p].data == nullptr) continue;
|
|
if (level[p] == nullptr) 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?
|
|
#if defined(_LARGE_WORLDS)
|
|
bool isNearer =
|
|
nearestClipChunks.wouldAccept(
|
|
distSqWeighted);
|
|
#else
|
|
bool isNearer = distSqWeighted < minDistSq;
|
|
#endif
|
|
|
|
#if defined(_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;
|
|
#if defined(_LARGE_WORLDS)
|
|
nearestClipChunks.insert(nearChunk,
|
|
minDistSq);
|
|
#endif
|
|
} else {
|
|
chunk->clearDirty();
|
|
globalChunkFlags[pClipChunk
|
|
->globalIdx] |=
|
|
CHUNK_FLAG_EMPTYBOTH;
|
|
wouldBeNearButEmpty++;
|
|
}
|
|
}
|
|
|
|
#if defined(_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);
|
|
}
|
|
PIXEndNamedEvent();
|
|
}
|
|
}
|
|
|
|
Chunk* chunk = nullptr;
|
|
#if defined(_LARGE_WORLDS)
|
|
if (!nearestClipChunks.empty()) {
|
|
int index = 0;
|
|
{
|
|
FRAME_PROFILE_SCOPE(ChunkRebuildSchedule);
|
|
for (int i = 0; i < nearestClipChunks.size(); ++i) {
|
|
chunk = nearestClipChunks.items[i].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;
|
|
}
|
|
dirtyChunksLock.unlock();
|
|
|
|
--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);
|
|
dirtyChunksLock.unlock();
|
|
}
|
|
// 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;
|
|
}
|
|
dirtyChunksLock.unlock();
|
|
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 != nullptr) {
|
|
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();
|
|
t->offset((float)-xo, (float)-yo, (float)-zo);
|
|
t->noColor();
|
|
|
|
auto 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] : nullptr;
|
|
if (tile == nullptr) 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 == nullptr) level = this->level[mc->player->GetXboxPad()];
|
|
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 nullptr 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) {
|
|
#if defined(_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
|
|
|
|
PIXSetMarkerDeprecated(0, "Setting chunk %d %d %d dirty",
|
|
x * 16, y * 16, z * 16);
|
|
}
|
|
// setGlobalChunkFlag(x * 16, y *
|
|
// 16, z * 16, level, CHUNK_FLAG_DIRTY);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LevelRenderer::tileChanged(int x, int y, int z) {
|
|
setDirty(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1, nullptr);
|
|
}
|
|
|
|
void LevelRenderer::tileLightChanged(int x, int y, int z) {
|
|
setDirty(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1, nullptr);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// 4jcraft: optional occlusion culling system, i hope to upgrade it soon
|
|
// gives better performances but mostly breaks chunk rendering
|
|
void LevelRenderer::cull(Culler* culler, float a) {
|
|
int playerIndex = mc->player->GetXboxPad();
|
|
if (chunks[playerIndex].data == nullptr) return;
|
|
|
|
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));
|
|
}
|
|
|
|
#if defined(OCCLUSION_MODE_NONE)
|
|
// just check if chunk is compiled and non-empty
|
|
for (unsigned int i = 0; i < chunks[playerIndex].length; i++) {
|
|
ClipChunk* cc = &chunks[playerIndex][i];
|
|
if (cc->globalIdx < 0) {
|
|
cc->visible = false;
|
|
continue;
|
|
}
|
|
|
|
unsigned char flags = globalChunkFlags[cc->globalIdx];
|
|
bool isCompiled = (flags & CHUNK_FLAG_COMPILED) != 0;
|
|
bool isEmptyBoth =
|
|
(flags & CHUNK_FLAG_EMPTYBOTH) == CHUNK_FLAG_EMPTYBOTH;
|
|
|
|
cc->visible = isCompiled && !isEmptyBoth;
|
|
}
|
|
|
|
#elif defined(OCCLUSION_MODE_FRUSTUM)
|
|
// Just ~~monika~~ frustum culling
|
|
for (unsigned int i = 0; i < chunks[playerIndex].length; i++) {
|
|
ClipChunk* cc = &chunks[playerIndex][i];
|
|
if (cc->globalIdx < 0) {
|
|
cc->visible = false;
|
|
continue;
|
|
}
|
|
|
|
unsigned char flags = globalChunkFlags[cc->globalIdx];
|
|
bool isCompiled = (flags & CHUNK_FLAG_COMPILED) != 0;
|
|
bool isEmptyBoth =
|
|
(flags & CHUNK_FLAG_EMPTYBOTH) == CHUNK_FLAG_EMPTYBOTH;
|
|
|
|
if (isCompiled && !isEmptyBoth) {
|
|
float cellBounds[6] = {(float)cc->chunk->x - 0.1f,
|
|
(float)cc->chunk->y - 0.1f,
|
|
(float)cc->chunk->z - 0.1f,
|
|
(float)cc->chunk->x + CHUNK_XZSIZE + 0.1f,
|
|
(float)cc->chunk->y + CHUNK_SIZE + 0.1f,
|
|
(float)cc->chunk->z + CHUNK_XZSIZE + 0.1f};
|
|
cc->visible = clip(cellBounds, fdraw);
|
|
} else {
|
|
cc->visible = false;
|
|
}
|
|
}
|
|
|
|
#elif defined(OCCLUSION_MODE_HARDWARE)
|
|
// TODO: Hardware occlusion culling using GPU queries
|
|
// For now, fall back to frustum culling
|
|
#warning \
|
|
"OCCLUSION_MODE_HARDWARE is not implemented yet, falling back to frustum culling"
|
|
for (unsigned int i = 0; i < chunks[playerIndex].length; i++) {
|
|
ClipChunk* cc = &chunks[playerIndex][i];
|
|
if (cc->globalIdx < 0) {
|
|
cc->visible = false;
|
|
continue;
|
|
}
|
|
|
|
unsigned char flags = globalChunkFlags[cc->globalIdx];
|
|
bool isCompiled = (flags & CHUNK_FLAG_COMPILED) != 0;
|
|
bool isEmptyBoth =
|
|
(flags & CHUNK_FLAG_EMPTYBOTH) == CHUNK_FLAG_EMPTYBOTH;
|
|
|
|
if (isCompiled && !isEmptyBoth) {
|
|
float cellBounds[6] = {(float)cc->chunk->x - 0.1f,
|
|
(float)cc->chunk->y - 0.1f,
|
|
(float)cc->chunk->z - 0.1f,
|
|
(float)cc->chunk->x + CHUNK_XZSIZE + 0.1f,
|
|
(float)cc->chunk->y + CHUNK_SIZE + 0.1f,
|
|
(float)cc->chunk->z + CHUNK_XZSIZE + 0.1f};
|
|
cc->visible = clip(cellBounds, fdraw);
|
|
} else {
|
|
cc->visible = false;
|
|
}
|
|
}
|
|
|
|
#elif defined(OCCLUSION_MODE_BFS)
|
|
// Experimental BFS occlusion culling.
|
|
// Check https://tomcc.github.io/2014/08/31/visibility-1.html
|
|
// And https://tomcc.github.io/2014/08/31/visibility-2.html
|
|
// And finally https://en.wikipedia.org/wiki/Breadth-first_search
|
|
std::shared_ptr<LivingEntity> player = mc->cameraTargetPlayer;
|
|
float camX = (float)(player->xOld + (player->x - player->xOld) * a);
|
|
float camY = (float)(player->yOld + (player->y - player->yOld) * a);
|
|
float camZ = (float)(player->zOld + (player->z - player->zOld) * a);
|
|
|
|
auto intFloorDiv = [](int v, int div) {
|
|
if (v < 0 && v % div != 0) return (v / div) - 1;
|
|
return v / div;
|
|
};
|
|
|
|
auto floatFloorDiv = [](float v, int div) {
|
|
int iv = (int)v;
|
|
if (v < 0 && v != iv) iv--;
|
|
if (iv < 0 && iv % div != 0) return (iv / div) - 1;
|
|
return iv / div;
|
|
};
|
|
|
|
int minCx = INT_MAX, minCy = INT_MAX, minCz = INT_MAX;
|
|
int maxCx = INT_MIN, maxCy = INT_MIN, maxCz = INT_MIN;
|
|
|
|
for (unsigned int i = 0; i < chunks[playerIndex].length; i++) {
|
|
ClipChunk* cc = &chunks[playerIndex][i];
|
|
cc->visible = false;
|
|
if (cc->globalIdx < 0) continue;
|
|
|
|
int cx = intFloorDiv(cc->chunk->x, CHUNK_XZSIZE);
|
|
int cy = intFloorDiv(cc->chunk->y, CHUNK_SIZE);
|
|
int cz = intFloorDiv(cc->chunk->z, CHUNK_XZSIZE);
|
|
|
|
if (cx < minCx) minCx = cx;
|
|
if (cy < minCy) minCy = cy;
|
|
if (cz < minCz) minCz = cz;
|
|
if (cx > maxCx) maxCx = cx;
|
|
if (cy > maxCy) maxCy = cy;
|
|
if (cz > maxCz) maxCz = cz;
|
|
}
|
|
|
|
if (minCx > maxCx) return;
|
|
|
|
int sizeX = maxCx - minCx + 1;
|
|
int sizeY = maxCy - minCy + 1;
|
|
int sizeZ = maxCz - minCz + 1;
|
|
int gridSize = sizeX * sizeY * sizeZ;
|
|
|
|
if (m_bfsGrid.size() < gridSize) {
|
|
m_bfsGrid.resize(gridSize);
|
|
}
|
|
|
|
memset(m_bfsGrid.data(), 0, gridSize * sizeof(ClipChunk*));
|
|
for (unsigned int i = 0; i < chunks[playerIndex].length; i++) {
|
|
ClipChunk* cc = &chunks[playerIndex][i];
|
|
if (cc->globalIdx < 0) continue;
|
|
int lx = intFloorDiv(cc->chunk->x, CHUNK_XZSIZE) - minCx;
|
|
int ly = intFloorDiv(cc->chunk->y, CHUNK_SIZE) - minCy;
|
|
int lz = intFloorDiv(cc->chunk->z, CHUNK_XZSIZE) - minCz;
|
|
m_bfsGrid[(lx * sizeY + ly) * sizeZ + lz] = cc;
|
|
}
|
|
|
|
auto getChunkAt = [&](int cx, int cy, int cz) -> ClipChunk* {
|
|
int lx = cx - minCx;
|
|
int ly = cy - minCy;
|
|
int lz = cz - minCz;
|
|
if (lx >= 0 && lx < sizeX && ly >= 0 && ly < sizeY && lz >= 0 &&
|
|
lz < sizeZ) {
|
|
return m_bfsGrid[(lx * sizeY + ly) * sizeZ + lz];
|
|
}
|
|
return nullptr;
|
|
};
|
|
|
|
int startCx = floatFloorDiv(camX, CHUNK_XZSIZE);
|
|
int startCy = floatFloorDiv(camY, CHUNK_SIZE);
|
|
int startCz = floatFloorDiv(camZ, CHUNK_XZSIZE);
|
|
|
|
if (startCx < minCx)
|
|
startCx = minCx;
|
|
else if (startCx > maxCx)
|
|
startCx = maxCx;
|
|
if (startCy < minCy)
|
|
startCy = minCy;
|
|
else if (startCy > maxCy)
|
|
startCy = maxCy;
|
|
if (startCz < minCz)
|
|
startCz = minCz;
|
|
else if (startCz > maxCz)
|
|
startCz = maxCz;
|
|
|
|
ClipChunk* startChunk = getChunkAt(startCx, startCy, startCz);
|
|
|
|
if (!startChunk) {
|
|
float minDist = 1e30f;
|
|
for (unsigned int i = 0; i < chunks[playerIndex].length; i++) {
|
|
ClipChunk* cc = &chunks[playerIndex][i];
|
|
if (cc->globalIdx < 0) continue;
|
|
float midX = cc->chunk->x + CHUNK_XZSIZE * 0.5f;
|
|
float midY = cc->chunk->y + CHUNK_SIZE * 0.5f;
|
|
float midZ = cc->chunk->z + CHUNK_XZSIZE * 0.5f;
|
|
float dist = (camX - midX) * (camX - midX) +
|
|
(camY - midY) * (camY - midY) +
|
|
(camZ - midZ) * (camZ - midZ);
|
|
if (dist < minDist) {
|
|
minDist = dist;
|
|
startChunk = cc;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!startChunk) return;
|
|
|
|
struct BFSNode {
|
|
ClipChunk* cc;
|
|
int incomingFace;
|
|
};
|
|
|
|
static thread_local std::vector<BFSNode> q;
|
|
q.clear();
|
|
q.reserve(chunks[playerIndex].length);
|
|
int qHead = 0;
|
|
|
|
int visitedSize = chunks[playerIndex].length;
|
|
if (m_bfsVisitedFaces[playerIndex].size() < visitedSize) {
|
|
m_bfsVisitedFaces[playerIndex].resize(visitedSize, 0);
|
|
}
|
|
memset(m_bfsVisitedFaces[playerIndex].data(), 0, visitedSize);
|
|
|
|
q.push_back({startChunk, -1});
|
|
m_bfsVisitedFaces[playerIndex][startChunk - chunks[playerIndex].data] =
|
|
0x3F;
|
|
|
|
static const int OFFSETS[6][3] = {
|
|
{0, -1, 0}, // 0: -Y
|
|
{0, 1, 0}, // 1: +Y
|
|
{0, 0, -1}, // 2: -Z
|
|
{0, 0, 1}, // 3: +Z
|
|
{-1, 0, 0}, // 4: -X
|
|
{1, 0, 0} // 5: +X
|
|
};
|
|
|
|
while (qHead < (int)q.size()) {
|
|
BFSNode node = q[qHead++];
|
|
ClipChunk* curr = node.cc;
|
|
int incFace = node.incomingFace;
|
|
|
|
unsigned char flags = globalChunkFlags[curr->globalIdx];
|
|
bool isCompiled = (flags & CHUNK_FLAG_COMPILED) != 0;
|
|
bool isEmptyBoth =
|
|
(flags & CHUNK_FLAG_EMPTYBOTH) == CHUNK_FLAG_EMPTYBOTH;
|
|
|
|
if (isCompiled && !isEmptyBoth) {
|
|
curr->visible = true;
|
|
}
|
|
|
|
int cx = intFloorDiv(curr->chunk->x, CHUNK_XZSIZE);
|
|
int cy = intFloorDiv(curr->chunk->y, CHUNK_SIZE);
|
|
int cz = intFloorDiv(curr->chunk->z, CHUNK_XZSIZE);
|
|
|
|
uint64_t conn = getGlobalChunkConnectivity(curr->globalIdx);
|
|
|
|
for (int i = 0; i < 6; i++) {
|
|
int outFace = i;
|
|
|
|
bool canGo = false;
|
|
float chkX = curr->chunk->x;
|
|
float chkY = curr->chunk->y;
|
|
float chkZ = curr->chunk->z;
|
|
switch (outFace) {
|
|
case 0:
|
|
canGo = camY >= chkY;
|
|
break;
|
|
case 1:
|
|
canGo = camY <= chkY + CHUNK_SIZE;
|
|
break;
|
|
case 2:
|
|
canGo = camZ >= chkZ;
|
|
break;
|
|
case 3:
|
|
canGo = camZ <= chkZ + CHUNK_XZSIZE;
|
|
break;
|
|
case 4:
|
|
canGo = camX >= chkX;
|
|
break;
|
|
case 5:
|
|
canGo = camX <= chkX + CHUNK_XZSIZE;
|
|
break;
|
|
}
|
|
if (!canGo) continue;
|
|
|
|
if (incFace != -1) {
|
|
int shift = (incFace * 6) + outFace;
|
|
if ((conn & (1ULL << shift)) == 0) continue;
|
|
}
|
|
|
|
int nx = cx + OFFSETS[i][0];
|
|
int ny = cy + OFFSETS[i][1];
|
|
int nz = cz + OFFSETS[i][2];
|
|
|
|
ClipChunk* neighbor = getChunkAt(nx, ny, nz);
|
|
if (!neighbor) continue;
|
|
|
|
int nIdx = neighbor - chunks[playerIndex].data;
|
|
int nextIncFace = outFace ^ 1;
|
|
|
|
if ((m_bfsVisitedFaces[playerIndex][nIdx] & (1 << nextIncFace)) !=
|
|
0)
|
|
continue;
|
|
|
|
float cellBounds[6] = {
|
|
(float)neighbor->chunk->x - 0.1f,
|
|
(float)neighbor->chunk->y - 0.1f,
|
|
(float)neighbor->chunk->z - 0.1f,
|
|
(float)neighbor->chunk->x + CHUNK_XZSIZE + 0.1f,
|
|
(float)neighbor->chunk->y + CHUNK_SIZE + 0.1f,
|
|
(float)neighbor->chunk->z + CHUNK_XZSIZE + 0.1f};
|
|
|
|
if (!clip(cellBounds, fdraw)) continue;
|
|
|
|
m_bfsVisitedFaces[playerIndex][nIdx] |= (1 << nextIncFace);
|
|
q.push_back({neighbor, nextIncFace});
|
|
}
|
|
}
|
|
|
|
#else
|
|
#error \
|
|
"Unknown occlusion mode, this should NEVER happen, check meson.build for misconfiguration"
|
|
#endif
|
|
}
|
|
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 == nullptr || mc->cameraTargetPlayer == nullptr || mc->particleEngine ==
|
|
nullptr) 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 == nullptr || mc->cameraTargetPlayer == nullptr ||
|
|
mc->particleEngine == nullptr) {
|
|
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 nullptr 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 == nullptr) {
|
|
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 != nullptr && 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 != nullptr) {
|
|
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
|
|
}
|
|
|
|
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 != nullptr) {
|
|
// 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 != nullptr) {
|
|
// 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 != nullptr) {
|
|
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 != nullptr) {
|
|
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 != nullptr) {
|
|
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 nullptr, 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 it = destroyingBlocks.find(id);
|
|
if (it != destroyingBlocks.end()) {
|
|
delete it->second;
|
|
destroyingBlocks.erase(it);
|
|
}
|
|
// destroyingBlocks.remove(id);
|
|
} else {
|
|
BlockDestructionProgress* entry = nullptr;
|
|
|
|
auto it = destroyingBlocks.find(id);
|
|
if (it != destroyingBlocks.end()) entry = it->second;
|
|
|
|
if (entry == nullptr || 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) {
|
|
#if defined(_LARGE_WORLDS)
|
|
std::lock_guard<std::mutex> lock(m_csChunkFlags);
|
|
#endif
|
|
globalChunkFlags[index] = flags;
|
|
}
|
|
}
|
|
|
|
void LevelRenderer::setGlobalChunkFlag(int index, unsigned char flag,
|
|
unsigned char shift) {
|
|
unsigned char sflag = flag << shift;
|
|
|
|
if (index != -1) {
|
|
#if defined(_LARGE_WORLDS)
|
|
std::lock_guard<std::mutex> lock(m_csChunkFlags);
|
|
#endif
|
|
globalChunkFlags[index] |= sflag;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
#if defined(_LARGE_WORLDS)
|
|
std::lock_guard<std::mutex> lock(m_csChunkFlags);
|
|
#endif
|
|
globalChunkFlags[index] |= sflag;
|
|
}
|
|
}
|
|
|
|
void LevelRenderer::setGlobalChunkConnectivity(int index, uint64_t conn) {
|
|
if (index >= 0 && index < getGlobalChunkCount()) {
|
|
globalChunkConnectivity[index] = conn;
|
|
}
|
|
}
|
|
|
|
uint64_t LevelRenderer::getGlobalChunkConnectivity(int index) {
|
|
if (index >= 0 && index < getGlobalChunkCount()) {
|
|
return globalChunkConnectivity[index];
|
|
}
|
|
return ~(uint64_t)0; // out of bounds
|
|
}
|
|
|
|
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) {
|
|
#if defined(_LARGE_WORLDS)
|
|
std::lock_guard<std::mutex> lock(m_csChunkFlags);
|
|
#endif
|
|
globalChunkFlags[index] &= ~sflag;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
void LevelRenderer::queueRenderableTileEntityForRemoval_Locked(
|
|
int key, TileEntity* tileEntity) {
|
|
m_renderableTileEntitiesPendingRemoval[key].insert(tileEntity);
|
|
}
|
|
|
|
void LevelRenderer::addRenderableTileEntity_Locked(
|
|
int key, const std::shared_ptr<TileEntity>& tileEntity) {
|
|
RenderableTileEntityBucket& bucket = renderableTileEntities[key];
|
|
TileEntity* tileEntityPtr = tileEntity.get();
|
|
if (bucket.indexByTile.find(tileEntityPtr) != bucket.indexByTile.end()) {
|
|
return;
|
|
}
|
|
|
|
size_t index = bucket.tiles.size();
|
|
bucket.tiles.push_back(tileEntity);
|
|
bucket.indexByTile.insert(std::make_pair(tileEntityPtr, index));
|
|
}
|
|
|
|
void LevelRenderer::eraseRenderableTileEntity_Locked(
|
|
RenderableTileEntityBucket& bucket, TileEntity* tileEntity) {
|
|
auto it = bucket.indexByTile.find(tileEntity);
|
|
if (it == bucket.indexByTile.end()) {
|
|
return;
|
|
}
|
|
|
|
size_t index = it->second;
|
|
size_t lastIndex = bucket.tiles.size() - 1;
|
|
if (index != lastIndex) {
|
|
std::shared_ptr<TileEntity> moved = bucket.tiles[lastIndex];
|
|
bucket.tiles[index] = moved;
|
|
bucket.indexByTile[moved.get()] = index;
|
|
}
|
|
|
|
bucket.tiles.pop_back();
|
|
bucket.indexByTile.erase(it);
|
|
}
|
|
|
|
void LevelRenderer::retireRenderableTileEntitiesForChunkKey(int key) {
|
|
if (key == -1) return;
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_csRenderableTileEntities);
|
|
renderableTileEntities.erase(key);
|
|
m_renderableTileEntitiesPendingRemoval.erase(key);
|
|
}
|
|
}
|
|
|
|
// 4J added
|
|
void LevelRenderer::fullyFlagRenderableTileEntitiesToBeRemoved() {
|
|
FRAME_PROFILE_SCOPE(RenderableTileEntityCleanup);
|
|
|
|
std::lock_guard<std::mutex> lock(m_csRenderableTileEntities);
|
|
if (m_renderableTileEntitiesPendingRemoval.empty()) {
|
|
return;
|
|
}
|
|
|
|
auto itKeyEnd = m_renderableTileEntitiesPendingRemoval.end();
|
|
for (auto itKey = m_renderableTileEntitiesPendingRemoval.begin();
|
|
itKey != itKeyEnd; itKey++) {
|
|
auto itChunk = renderableTileEntities.find(itKey->first);
|
|
if (itChunk == renderableTileEntities.end()) continue;
|
|
|
|
RenderableTileEntityBucket& bucket = itChunk->second;
|
|
for (auto itPending = itKey->second.begin();
|
|
itPending != itKey->second.end(); itPending++) {
|
|
if (bucket.indexByTile.find(*itPending) ==
|
|
bucket.indexByTile.end()) {
|
|
continue;
|
|
}
|
|
|
|
if (!(*itPending)->finalizeRenderRemoveStage()) {
|
|
continue;
|
|
}
|
|
|
|
eraseRenderableTileEntity_Locked(bucket, *itPending);
|
|
}
|
|
|
|
if (bucket.tiles.empty()) {
|
|
renderableTileEntities.erase(itChunk);
|
|
}
|
|
}
|
|
m_renderableTileEntitiesPendingRemoval.clear();
|
|
}
|
|
|
|
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() {
|
|
// std::mutex is default-constructed
|
|
}
|
|
|
|
LevelRenderer::DestroyedTileManager::~DestroyedTileManager() {
|
|
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) {
|
|
std::lock_guard<std::mutex> lock(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 != nullptr) {
|
|
tile->addAABBs(level, x, y, z, &box, &recentTile->boxes, nullptr);
|
|
}
|
|
|
|
m_destroyedTiles.push_back(recentTile);
|
|
}
|
|
|
|
// 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) {
|
|
std::lock_guard<std::mutex> lock(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++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
std::lock_guard<std::mutex> lock(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});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void LevelRenderer::DestroyedTileManager::tick() {
|
|
std::lock_guard<std::mutex> lock(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++;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(_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);
|
|
} 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;
|
|
}
|