|
|
|
@@ -23,6 +23,7 @@ import java.nio.FloatBuffer;
|
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
import java.util.Iterator;
|
|
|
|
|
|
|
|
|
|
import com.seibel.lod.objects.LevelPosUtil;
|
|
|
|
|
import org.lwjgl.opengl.GL;
|
|
|
|
|
import org.lwjgl.opengl.GL11;
|
|
|
|
|
import org.lwjgl.opengl.NVFogDistance;
|
|
|
|
@@ -56,8 +57,8 @@ import net.minecraft.entity.Entity;
|
|
|
|
|
import net.minecraft.potion.EffectInstance;
|
|
|
|
|
import net.minecraft.potion.Effects;
|
|
|
|
|
import net.minecraft.profiler.IProfiler;
|
|
|
|
|
import net.minecraft.util.math.BlockPos;
|
|
|
|
|
import net.minecraft.util.math.ChunkPos;
|
|
|
|
|
import net.minecraft.util.math.BlockPos;
|
|
|
|
|
import net.minecraft.util.math.MathHelper;
|
|
|
|
|
import net.minecraft.util.math.vector.Matrix4f;
|
|
|
|
|
import net.minecraft.util.math.vector.Vector3d;
|
|
|
|
@@ -69,7 +70,7 @@ import net.minecraft.util.math.vector.Vector3f;
|
|
|
|
|
* This is where LODs are draw to the world.
|
|
|
|
|
*
|
|
|
|
|
* @author James Seibel
|
|
|
|
|
* @version 9-6-2021
|
|
|
|
|
* @version 8-31-2021
|
|
|
|
|
*/
|
|
|
|
|
public class LodRenderer
|
|
|
|
|
{
|
|
|
|
@@ -78,7 +79,7 @@ public class LodRenderer
|
|
|
|
|
* it should be something different than what is used by Minecraft
|
|
|
|
|
*/
|
|
|
|
|
private static final int LOD_GL_LIGHT_NUMBER = GL11.GL_LIGHT2;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 64 MB by default is the maximum amount of memory that
|
|
|
|
|
* can be directly allocated. <br><br>
|
|
|
|
@@ -91,80 +92,78 @@ public class LodRenderer
|
|
|
|
|
* https://stackoverflow.com/questions/50499238/bytebuffer-allocatedirect-and-xmx
|
|
|
|
|
*/
|
|
|
|
|
public static final int MAX_ALOCATEABLE_DIRECT_MEMORY = 64 * 1024 * 1024;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Does this computer's GPU support fancy fog?
|
|
|
|
|
*/
|
|
|
|
|
private static Boolean fancyFogAvailable = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* If true the LODs colors will be replaced with
|
|
|
|
|
* a checkerboard, this can be used for debugging.
|
|
|
|
|
*/
|
|
|
|
|
public DebugMode previousDebugMode = DebugMode.OFF;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private MinecraftWrapper mc;
|
|
|
|
|
private GameRenderer gameRender;
|
|
|
|
|
private IProfiler profiler;
|
|
|
|
|
private int farPlaneBlockDistance;
|
|
|
|
|
private ReflectionHandler reflectionHandler;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This is used to generate the buildable buffers
|
|
|
|
|
*/
|
|
|
|
|
private LodBufferBuilder lodBufferBuilder;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Each VertexBuffer represents 1 region
|
|
|
|
|
*/
|
|
|
|
|
private VertexBuffer[][] vbos;
|
|
|
|
|
public static final VertexFormat LOD_VERTEX_FORMAT = DefaultVertexFormats.POSITION_COLOR;
|
|
|
|
|
private ChunkPos vbosCenter = new ChunkPos(0,0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This is used to determine if the LODs should be regenerated
|
|
|
|
|
*/
|
|
|
|
|
private byte previousDetailLevel = 0;
|
|
|
|
|
private int previousChunkPosX = 0;
|
|
|
|
|
private int previousChunkPosZ = 0;
|
|
|
|
|
private int[] previousPos = new int[]{0,0,0};
|
|
|
|
|
private int prevRenderDistance = 0;
|
|
|
|
|
private long prevPlayerPosTime = 0;
|
|
|
|
|
private long prevVanillaChunkTime = 0;
|
|
|
|
|
private long prevChunkTime = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This is used to determine if the LODs should be regenerated
|
|
|
|
|
*/
|
|
|
|
|
private FogDistance prevFogDistance = FogDistance.NEAR_AND_FAR;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* if this is true the LOD buffers should be regenerated,
|
|
|
|
|
* provided they aren't already being regenerated.
|
|
|
|
|
*/
|
|
|
|
|
private volatile boolean partialRegen = false;
|
|
|
|
|
private volatile boolean fullRegen = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This HashSet contains every chunk that Vanilla Minecraft
|
|
|
|
|
* is going to render
|
|
|
|
|
*/
|
|
|
|
|
public boolean[][] vanillaRenderedChunks;
|
|
|
|
|
public boolean vanillaRenderedChunksChanged;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public LodRenderer(LodBufferBuilder newLodNodeBufferBuilder)
|
|
|
|
|
{
|
|
|
|
|
mc = MinecraftWrapper.INSTANCE;
|
|
|
|
|
gameRender = mc.getGameRenderer();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
reflectionHandler = new ReflectionHandler();
|
|
|
|
|
lodBufferBuilder = newLodNodeBufferBuilder;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Besides drawing the LODs this method also starts
|
|
|
|
|
* the async process of generating the Buffers that hold those LODs.
|
|
|
|
@@ -180,37 +179,37 @@ public class LodRenderer
|
|
|
|
|
// don't try drawing anything
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//===============//
|
|
|
|
|
// initial setup //
|
|
|
|
|
//===============//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
profiler = newProfiler;
|
|
|
|
|
profiler.push("LOD setup");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// only check the GPU capability's once
|
|
|
|
|
if (fancyFogAvailable == null)
|
|
|
|
|
{
|
|
|
|
|
// see if this GPU can run fancy fog
|
|
|
|
|
fancyFogAvailable = GL.getCapabilities().GL_NV_fog_distance;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!fancyFogAvailable)
|
|
|
|
|
{
|
|
|
|
|
ClientProxy.LOGGER.info("This GPU does not support GL_NV_fog_distance. This means that fancy fog options will not be available.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO move the buffer regeneration logic into its own class (probably called in the client proxy instead)
|
|
|
|
|
// starting here...
|
|
|
|
|
determineIfLodsShouldRegenerate(lodDim);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//=================//
|
|
|
|
|
// create the LODs //
|
|
|
|
|
//=================//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// only regenerate the LODs if:
|
|
|
|
|
// 1. we want to regenerate LODs
|
|
|
|
|
// 2. we aren't already regenerating the LODs
|
|
|
|
@@ -220,78 +219,87 @@ public class LodRenderer
|
|
|
|
|
{
|
|
|
|
|
// generate the LODs on a separate thread to prevent stuttering or freezing
|
|
|
|
|
lodBufferBuilder.generateLodBuffersAsync(this, lodDim, mc.getPlayer().blockPosition(), true);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// the regen process has been started,
|
|
|
|
|
// it will be done when lodBufferBuilder.newBuffersAvaliable()
|
|
|
|
|
// is true
|
|
|
|
|
fullRegen = false;
|
|
|
|
|
partialRegen = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO move the buffer regeneration logic into its own class (probably called in the client proxy instead)
|
|
|
|
|
// ...ending here
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// replace the buffers used to draw and build,
|
|
|
|
|
// this is only done when the createLodBufferGenerationThread
|
|
|
|
|
// has finished executing on a parallel thread.
|
|
|
|
|
if (lodBufferBuilder.newBuffersAvaliable())
|
|
|
|
|
{
|
|
|
|
|
swapBuffers();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//===========================//
|
|
|
|
|
// GL settings for rendering //
|
|
|
|
|
//===========================//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// set the required open GL settings
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (LodConfig.CLIENT.debugging.debugMode.get() == DebugMode.SHOW_DETAIL_WIREFRAME)
|
|
|
|
|
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_LINE);
|
|
|
|
|
else
|
|
|
|
|
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
|
|
|
|
GL11.glEnable(GL11.GL_CULL_FACE);
|
|
|
|
|
GL11.glEnable(GL11.GL_COLOR_MATERIAL);
|
|
|
|
|
GL11.glEnable(GL11.GL_DEPTH_TEST);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// disable the lights Minecraft uses
|
|
|
|
|
GL11.glDisable(GL11.GL_LIGHT0);
|
|
|
|
|
GL11.glDisable(GL11.GL_LIGHT1);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// get the default projection matrix so we can
|
|
|
|
|
// reset it after drawing the LODs
|
|
|
|
|
float[] defaultProjMatrix = new float[16];
|
|
|
|
|
GL11.glGetFloatv(GL11.GL_PROJECTION_MATRIX, defaultProjMatrix);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Matrix4f modelViewMatrix = generateModelViewMatrix(partialTicks);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// required for setupFog and setupProjectionMatrix
|
|
|
|
|
farPlaneBlockDistance = LodConfig.CLIENT.graphics.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setupProjectionMatrix(partialTicks);
|
|
|
|
|
setupLighting(lodDim, partialTicks);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NearFarFogSettings fogSettings = determineFogSettings();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// determine the current fog settings so they can be
|
|
|
|
|
// reset after drawing the LODs
|
|
|
|
|
float defaultFogStartDist = GL11.glGetFloat(GL11.GL_FOG_START);
|
|
|
|
|
float defaultFogEndDist = GL11.glGetFloat(GL11.GL_FOG_END);
|
|
|
|
|
int defaultFogMode = GL11.glGetInteger(GL11.GL_FOG_MODE);
|
|
|
|
|
int defaultFogDistance = GL11.glGetInteger(NVFogDistance.GL_FOG_DISTANCE_MODE_NV);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//===========//
|
|
|
|
|
// rendering //
|
|
|
|
|
//===========//
|
|
|
|
|
profiler.popPush("LOD draw");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (vbos != null)
|
|
|
|
|
{
|
|
|
|
|
Entity cameraEntity = mc.getCameraEntity();
|
|
|
|
|
Vector3d cameraDir = cameraEntity.getLookAngle().normalize();
|
|
|
|
|
cameraDir = mc.getOptions().getCameraType().isMirrored() ? cameraDir.reverse() : cameraDir;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// used to determine what type of fog to render
|
|
|
|
|
int halfWidth = vbos.length / 2;
|
|
|
|
|
int quarterWidth = vbos.length / 4;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < vbos.length; i++)
|
|
|
|
|
{
|
|
|
|
|
for (int j = 0; j < vbos.length; j++)
|
|
|
|
@@ -303,21 +311,21 @@ public class LodRenderer
|
|
|
|
|
setupFog(fogSettings.near.distance, fogSettings.near.quality);
|
|
|
|
|
else
|
|
|
|
|
setupFog(fogSettings.far.distance, fogSettings.far.quality);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sendLodsToGpuAndDraw(vbos[i][j], modelViewMatrix);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//=========//
|
|
|
|
|
// cleanup //
|
|
|
|
|
//=========//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
profiler.popPush("LOD cleanup");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
|
|
|
|
|
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
|
|
|
|
GL11.glDisable(LOD_GL_LIGHT_NUMBER);
|
|
|
|
@@ -325,23 +333,23 @@ public class LodRenderer
|
|
|
|
|
GL11.glEnable(GL11.GL_LIGHT0);
|
|
|
|
|
GL11.glEnable(GL11.GL_LIGHT1);
|
|
|
|
|
RenderSystem.disableLighting();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// reset the fog settings so the normal chunks
|
|
|
|
|
// will be drawn correctly
|
|
|
|
|
cleanupFog(fogSettings, defaultFogStartDist, defaultFogEndDist, defaultFogMode, defaultFogDistance);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// reset the projection matrix so anything drawn after
|
|
|
|
|
// the LODs will use the correct projection matrix
|
|
|
|
|
Matrix4f mvm = new Matrix4f(defaultProjMatrix);
|
|
|
|
|
mvm.transpose();
|
|
|
|
|
gameRender.resetProjectionMatrix(mvm);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// clear the depth buffer so anything drawn is drawn
|
|
|
|
|
// over the LODs
|
|
|
|
|
GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// replace the buffers used to draw and build,
|
|
|
|
|
// this is only done when the createLodBufferGenerationThread
|
|
|
|
|
// has finished executing on a parallel thread.
|
|
|
|
@@ -351,13 +359,13 @@ public class LodRenderer
|
|
|
|
|
// otherwise rubber banding may occur
|
|
|
|
|
swapBuffers();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// end of internal LOD profiling
|
|
|
|
|
profiler.pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This is where the actual drawing happens.
|
|
|
|
|
*/
|
|
|
|
@@ -365,22 +373,22 @@ public class LodRenderer
|
|
|
|
|
{
|
|
|
|
|
if (vbo == null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vbo.bind();
|
|
|
|
|
// 0L is the starting pointer
|
|
|
|
|
LOD_VERTEX_FORMAT.setupBufferState(0L);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vbo.draw(modelViewMatrix, GL11.GL_QUADS);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
VertexBuffer.unbind();
|
|
|
|
|
LOD_VERTEX_FORMAT.clearBufferState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//=================//
|
|
|
|
|
// Setup Functions //
|
|
|
|
|
//=================//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("deprecation")
|
|
|
|
|
private void setupFog(FogDistance fogDistance, FogQuality fogQuality)
|
|
|
|
|
{
|
|
|
|
@@ -390,12 +398,12 @@ public class LodRenderer
|
|
|
|
|
RenderSystem.disableFog();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (fogDistance == FogDistance.NEAR_AND_FAR)
|
|
|
|
|
{
|
|
|
|
|
throw new IllegalArgumentException("setupFog doesn't accept the NEAR_AND_FAR fog distance.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// determine the fog distance mode to use
|
|
|
|
|
int glFogDistanceMode;
|
|
|
|
|
if (fogQuality == FogQuality.FANCY)
|
|
|
|
@@ -408,7 +416,7 @@ public class LodRenderer
|
|
|
|
|
// fast fog (frustum distance based fog)
|
|
|
|
|
glFogDistanceMode = NVFogDistance.GL_EYE_PLANE_ABSOLUTE_NV;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// the multipliers are percentages
|
|
|
|
|
// of the regular view distance.
|
|
|
|
|
if (fogDistance == FogDistance.FAR)
|
|
|
|
@@ -417,7 +425,7 @@ public class LodRenderer
|
|
|
|
|
// is because we are using fog backwards to how
|
|
|
|
|
// it is normally used, with it hiding near objects
|
|
|
|
|
// instead of far objects.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (fogQuality == FogQuality.FANCY)
|
|
|
|
|
{
|
|
|
|
|
// for more realistic fog when using FAR
|
|
|
|
@@ -452,37 +460,37 @@ public class LodRenderer
|
|
|
|
|
RenderSystem.fogStart(mc.getRenderDistance() * 16 * 1.5f);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GL11.glEnable(GL11.GL_FOG);
|
|
|
|
|
RenderSystem.enableFog();
|
|
|
|
|
RenderSystem.setupNvFogDistance();
|
|
|
|
|
RenderSystem.fogMode(GlStateManager.FogMode.LINEAR);
|
|
|
|
|
GL11.glFogi(NVFogDistance.GL_FOG_DISTANCE_MODE_NV, glFogDistanceMode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Revert any changes that were made to the fog.
|
|
|
|
|
*/
|
|
|
|
|
private void cleanupFog(NearFarFogSettings fogSettings,
|
|
|
|
|
float defaultFogStartDist, float defaultFogEndDist,
|
|
|
|
|
int defaultFogMode, int defaultFogDistance)
|
|
|
|
|
float defaultFogStartDist, float defaultFogEndDist,
|
|
|
|
|
int defaultFogMode, int defaultFogDistance)
|
|
|
|
|
{
|
|
|
|
|
RenderSystem.fogStart(defaultFogStartDist);
|
|
|
|
|
RenderSystem.fogEnd(defaultFogEndDist);
|
|
|
|
|
RenderSystem.fogMode(defaultFogMode);
|
|
|
|
|
GL11.glFogi(NVFogDistance.GL_FOG_DISTANCE_MODE_NV, defaultFogDistance);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// disable fog if Minecraft wasn't rendering fog
|
|
|
|
|
// but we were
|
|
|
|
|
if (!fogSettings.vanillaIsRenderingFog &&
|
|
|
|
|
(fogSettings.near.quality != FogQuality.OFF ||
|
|
|
|
|
fogSettings.far.quality != FogQuality.OFF))
|
|
|
|
|
(fogSettings.near.quality != FogQuality.OFF ||
|
|
|
|
|
fogSettings.far.quality != FogQuality.OFF))
|
|
|
|
|
{
|
|
|
|
|
GL11.glDisable(GL11.GL_FOG);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create the model view matrix to move the LODs
|
|
|
|
|
* from object space into world space.
|
|
|
|
@@ -492,8 +500,8 @@ public class LodRenderer
|
|
|
|
|
// get all relevant camera info
|
|
|
|
|
ActiveRenderInfo renderInfo = mc.getGameRenderer().getMainCamera();
|
|
|
|
|
Vector3d projectedView = renderInfo.getPosition();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// generate the model view matrix
|
|
|
|
|
MatrixStack matrixStack = new MatrixStack();
|
|
|
|
|
matrixStack.pushPose();
|
|
|
|
@@ -508,11 +516,11 @@ public class LodRenderer
|
|
|
|
|
double xDiff = eyePos.x - bufferPos.getX();
|
|
|
|
|
double zDiff = eyePos.z - bufferPos.getZ();
|
|
|
|
|
matrixStack.translate(-xDiff, -projectedView.y, -zDiff);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return matrixStack.last().pose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* create a new projection matrix and send it over to the GPU
|
|
|
|
|
* <br><br>
|
|
|
|
@@ -530,16 +538,16 @@ public class LodRenderer
|
|
|
|
|
// Note: if the LOD objects don't distort correctly
|
|
|
|
|
// compared to regular minecraft terrain, make sure
|
|
|
|
|
// all the transformations in renderWorld are here too
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MatrixStack matrixStack = new MatrixStack();
|
|
|
|
|
matrixStack.pushPose();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gameRender.bobHurt(matrixStack, partialTicks);
|
|
|
|
|
if (this.mc.getOptions().bobView)
|
|
|
|
|
{
|
|
|
|
|
gameRender.bobView(matrixStack, partialTicks);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// potion and nausea effects
|
|
|
|
|
float f = MathHelper.lerp(partialTicks, this.mc.getPlayer().oPortalTime, this.mc.getPlayer().portalTime) * this.mc.getOptions().screenEffectScale * this.mc.getOptions().screenEffectScale;
|
|
|
|
|
if (f > 0.0F)
|
|
|
|
@@ -553,8 +561,8 @@ public class LodRenderer
|
|
|
|
|
float f2 = -(gameRender.tick + partialTicks) * i;
|
|
|
|
|
matrixStack.mulPose(vector3f.rotationDegrees(f2));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// this projection matrix allows us to see past the normal
|
|
|
|
|
// world render distance
|
|
|
|
|
Matrix4f projectionMatrix =
|
|
|
|
@@ -566,14 +574,14 @@ public class LodRenderer
|
|
|
|
|
// terrain, so I don't think it is much of an issue.
|
|
|
|
|
mc.getRenderDistance(),
|
|
|
|
|
farPlaneBlockDistance * LodUtil.CHUNK_WIDTH * 2);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// add the screen space distortions
|
|
|
|
|
projectionMatrix.multiply(matrixStack.last().pose());
|
|
|
|
|
gameRender.resetProjectionMatrix(projectionMatrix);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* setup the lighting to be used for the LODs
|
|
|
|
|
*/
|
|
|
|
@@ -594,27 +602,27 @@ public class LodRenderer
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float sunBrightness = lodDimension.dimension.hasSkyLight() ? mc.getSkyDarken(partialTicks) : 0.2f;
|
|
|
|
|
sunBrightness = playerHasNightVision ? 1.0f : sunBrightness;
|
|
|
|
|
float gammaMultiplyer = (float) mc.getOptions().gamma - 0.5f;
|
|
|
|
|
float lightStrength = ((sunBrightness / 2f) - 0.2f) + (gammaMultiplyer * 0.3f);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float lightAmbient[] = {lightStrength, lightStrength, lightStrength, 1.0f};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// can be used for debugging
|
|
|
|
|
// if (partialTicks < 0.005)
|
|
|
|
|
// ClientProxy.LOGGER.debug(lightStrength);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ByteBuffer temp = ByteBuffer.allocateDirect(16);
|
|
|
|
|
temp.order(ByteOrder.nativeOrder());
|
|
|
|
|
GL11.glLightfv(LOD_GL_LIGHT_NUMBER, GL11.GL_AMBIENT, (FloatBuffer) temp.asFloatBuffer().put(lightAmbient).flip());
|
|
|
|
|
GL11.glEnable(LOD_GL_LIGHT_NUMBER); // Enable the above lighting
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RenderSystem.enableLighting();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create all buffers that will be used.
|
|
|
|
|
*/
|
|
|
|
@@ -622,24 +630,24 @@ public class LodRenderer
|
|
|
|
|
{
|
|
|
|
|
// calculate the max amount of memory needed (in bytes)
|
|
|
|
|
int bufferMemory = RenderUtil.getBufferMemoryForRegion();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// if the required memory is greater than the
|
|
|
|
|
// MAX_ALOCATEABLE_DIRECT_MEMORY lower the lodChunkRadiusMultiplier
|
|
|
|
|
// to fit.
|
|
|
|
|
if (bufferMemory > MAX_ALOCATEABLE_DIRECT_MEMORY)
|
|
|
|
|
{
|
|
|
|
|
ClientProxy.LOGGER.warn("setupBuffers tried to allocate too much memory for the BufferBuilders."
|
|
|
|
|
+ " It tried to allocate \"" + bufferMemory + "\" bytes, when \"" + MAX_ALOCATEABLE_DIRECT_MEMORY + "\" is the max.");
|
|
|
|
|
+ " It tried to allocate \"" + bufferMemory + "\" bytes, when \"" + MAX_ALOCATEABLE_DIRECT_MEMORY + "\" is the max.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lodBufferBuilder.setupBuffers(numbRegionsWide, bufferMemory);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//======================//
|
|
|
|
|
// Other Misc Functions //
|
|
|
|
|
//======================//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* If this is called then the next time "drawLODs" is called
|
|
|
|
|
* the LODs will be regenerated; the same as if the player moved.
|
|
|
|
@@ -648,12 +656,12 @@ public class LodRenderer
|
|
|
|
|
{
|
|
|
|
|
fullRegen = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Replace the current Vertex Buffers with the newly
|
|
|
|
|
* created buffers from the lodBufferBuilder. <br><br>
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* For some reason this has to be called after the frame has been rendered,
|
|
|
|
|
* otherwise visual stuttering/rubber banding may happen. I'm not sure why...
|
|
|
|
|
*/
|
|
|
|
@@ -665,7 +673,7 @@ public class LodRenderer
|
|
|
|
|
vbos = result.vbos;
|
|
|
|
|
vbosCenter = result.drawableCenterChunkPos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Calls the BufferBuilder's destroyBuffers method.
|
|
|
|
|
*/
|
|
|
|
@@ -673,183 +681,183 @@ public class LodRenderer
|
|
|
|
|
{
|
|
|
|
|
lodBufferBuilder.destroyBuffers();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private double getFov(float partialTicks, boolean useFovSetting)
|
|
|
|
|
{
|
|
|
|
|
return mc.getGameRenderer().getFov(mc.getGameRenderer().getMainCamera(), partialTicks, useFovSetting);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return what fog settings should be used when rendering.
|
|
|
|
|
*/
|
|
|
|
|
private NearFarFogSettings determineFogSettings()
|
|
|
|
|
{
|
|
|
|
|
NearFarFogSettings fogSettings = new NearFarFogSettings();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FogQuality quality = reflectionHandler.getFogQuality();
|
|
|
|
|
FogDrawOverride override = LodConfig.CLIENT.graphics.fogDrawOverride.get();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (quality == FogQuality.OFF)
|
|
|
|
|
fogSettings.vanillaIsRenderingFog = false;
|
|
|
|
|
else
|
|
|
|
|
fogSettings.vanillaIsRenderingFog = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// use any fog overrides the user may have set
|
|
|
|
|
switch (override)
|
|
|
|
|
{
|
|
|
|
|
case ALWAYS_DRAW_FOG_FANCY:
|
|
|
|
|
quality = FogQuality.FANCY;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case NEVER_DRAW_FOG:
|
|
|
|
|
quality = FogQuality.OFF;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ALWAYS_DRAW_FOG_FAST:
|
|
|
|
|
quality = FogQuality.FAST;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case USE_OPTIFINE_FOG_SETTING:
|
|
|
|
|
// don't override anything
|
|
|
|
|
break;
|
|
|
|
|
case ALWAYS_DRAW_FOG_FANCY:
|
|
|
|
|
quality = FogQuality.FANCY;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case NEVER_DRAW_FOG:
|
|
|
|
|
quality = FogQuality.OFF;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ALWAYS_DRAW_FOG_FAST:
|
|
|
|
|
quality = FogQuality.FAST;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case USE_OPTIFINE_FOG_SETTING:
|
|
|
|
|
// don't override anything
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// only use fancy fog if the user's GPU can deliver
|
|
|
|
|
if (!fancyFogAvailable && quality == FogQuality.FANCY)
|
|
|
|
|
{
|
|
|
|
|
quality = FogQuality.FAST;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// how different distances are drawn depends on the quality set
|
|
|
|
|
switch (quality)
|
|
|
|
|
{
|
|
|
|
|
case FANCY:
|
|
|
|
|
fogSettings.near.quality = FogQuality.FANCY;
|
|
|
|
|
fogSettings.far.quality = FogQuality.FANCY;
|
|
|
|
|
|
|
|
|
|
switch (LodConfig.CLIENT.graphics.fogDistance.get())
|
|
|
|
|
{
|
|
|
|
|
case NEAR_AND_FAR:
|
|
|
|
|
fogSettings.near.distance = FogDistance.NEAR;
|
|
|
|
|
fogSettings.far.distance = FogDistance.FAR;
|
|
|
|
|
case FANCY:
|
|
|
|
|
fogSettings.near.quality = FogQuality.FANCY;
|
|
|
|
|
fogSettings.far.quality = FogQuality.FANCY;
|
|
|
|
|
|
|
|
|
|
switch (LodConfig.CLIENT.graphics.fogDistance.get())
|
|
|
|
|
{
|
|
|
|
|
case NEAR_AND_FAR:
|
|
|
|
|
fogSettings.near.distance = FogDistance.NEAR;
|
|
|
|
|
fogSettings.far.distance = FogDistance.FAR;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case NEAR:
|
|
|
|
|
fogSettings.near.distance = FogDistance.NEAR;
|
|
|
|
|
fogSettings.far.distance = FogDistance.NEAR;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case FAR:
|
|
|
|
|
fogSettings.near.distance = FogDistance.FAR;
|
|
|
|
|
fogSettings.far.distance = FogDistance.FAR;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case NEAR:
|
|
|
|
|
fogSettings.near.distance = FogDistance.NEAR;
|
|
|
|
|
fogSettings.far.distance = FogDistance.NEAR;
|
|
|
|
|
|
|
|
|
|
case FAST:
|
|
|
|
|
fogSettings.near.quality = FogQuality.FAST;
|
|
|
|
|
fogSettings.far.quality = FogQuality.FAST;
|
|
|
|
|
|
|
|
|
|
// fast fog setting should only have one type of
|
|
|
|
|
// fog, since the LODs are separated into a near
|
|
|
|
|
// and far portion; and fast fog is rendered from the
|
|
|
|
|
// frustrum's perspective instead of the camera
|
|
|
|
|
switch (LodConfig.CLIENT.graphics.fogDistance.get())
|
|
|
|
|
{
|
|
|
|
|
case NEAR_AND_FAR:
|
|
|
|
|
fogSettings.near.distance = FogDistance.NEAR;
|
|
|
|
|
fogSettings.far.distance = FogDistance.NEAR;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case NEAR:
|
|
|
|
|
fogSettings.near.distance = FogDistance.NEAR;
|
|
|
|
|
fogSettings.far.distance = FogDistance.NEAR;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case FAR:
|
|
|
|
|
fogSettings.near.distance = FogDistance.FAR;
|
|
|
|
|
fogSettings.far.distance = FogDistance.FAR;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case FAR:
|
|
|
|
|
fogSettings.near.distance = FogDistance.FAR;
|
|
|
|
|
fogSettings.far.distance = FogDistance.FAR;
|
|
|
|
|
|
|
|
|
|
case OFF:
|
|
|
|
|
|
|
|
|
|
fogSettings.near.quality = FogQuality.OFF;
|
|
|
|
|
fogSettings.far.quality = FogQuality.OFF;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case FAST:
|
|
|
|
|
fogSettings.near.quality = FogQuality.FAST;
|
|
|
|
|
fogSettings.far.quality = FogQuality.FAST;
|
|
|
|
|
|
|
|
|
|
// fast fog setting should only have one type of
|
|
|
|
|
// fog, since the LODs are separated into a near
|
|
|
|
|
// and far portion; and fast fog is rendered from the
|
|
|
|
|
// frustrum's perspective instead of the camera
|
|
|
|
|
switch (LodConfig.CLIENT.graphics.fogDistance.get())
|
|
|
|
|
{
|
|
|
|
|
case NEAR_AND_FAR:
|
|
|
|
|
fogSettings.near.distance = FogDistance.NEAR;
|
|
|
|
|
fogSettings.far.distance = FogDistance.NEAR;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case NEAR:
|
|
|
|
|
fogSettings.near.distance = FogDistance.NEAR;
|
|
|
|
|
fogSettings.far.distance = FogDistance.NEAR;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case FAR:
|
|
|
|
|
fogSettings.near.distance = FogDistance.FAR;
|
|
|
|
|
fogSettings.far.distance = FogDistance.FAR;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case OFF:
|
|
|
|
|
|
|
|
|
|
fogSettings.near.quality = FogQuality.OFF;
|
|
|
|
|
fogSettings.far.quality = FogQuality.OFF;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return fogSettings;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Determines if the LODs should have a fullRegen or partialRegen
|
|
|
|
|
*/
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
|
private void determineIfLodsShouldRegenerate(LodDimension lodDim)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
fullRegen = true;
|
|
|
|
|
short renderDistance = (short) mc.getRenderDistance();
|
|
|
|
|
//=============//
|
|
|
|
|
// full regens //
|
|
|
|
|
//=============//
|
|
|
|
|
// check if the view distance changed
|
|
|
|
|
if (ClientProxy.previousLodRenderDistance != LodConfig.CLIENT.graphics.lodChunkRenderDistance.get()
|
|
|
|
|
|| mc.getRenderDistance() != prevRenderDistance
|
|
|
|
|
|| prevFogDistance != LodConfig.CLIENT.graphics.fogDistance.get())
|
|
|
|
|
|| mc.getRenderDistance() != prevRenderDistance
|
|
|
|
|
|| prevFogDistance != LodConfig.CLIENT.graphics.fogDistance.get())
|
|
|
|
|
{
|
|
|
|
|
DetailDistanceUtil.updateSettings();
|
|
|
|
|
fullRegen = true;
|
|
|
|
|
previousChunkPosX = mc.getPlayer().xChunk;
|
|
|
|
|
previousChunkPosZ = mc.getPlayer().zChunk;
|
|
|
|
|
previousPos = LevelPosUtil.createLevelPos((byte) 4, mc.getPlayer().xChunk, mc.getPlayer().zChunk);
|
|
|
|
|
prevFogDistance = LodConfig.CLIENT.graphics.fogDistance.get();
|
|
|
|
|
prevRenderDistance = mc.getRenderDistance();
|
|
|
|
|
//should use this when it's ready
|
|
|
|
|
vanillaRenderedChunks = new boolean[renderDistance*2+2][renderDistance*2+2];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// did the user change the debug setting?
|
|
|
|
|
if (LodConfig.CLIENT.debugging.debugMode.get() != previousDebugMode)
|
|
|
|
|
{
|
|
|
|
|
previousDebugMode = LodConfig.CLIENT.debugging.debugMode.get();
|
|
|
|
|
fullRegen = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Bug
|
|
|
|
|
|
|
|
|
|
long newTime = System.currentTimeMillis();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// check if the player has moved
|
|
|
|
|
if (newTime - prevPlayerPosTime > LodConfig.CLIENT.buffers.bufferRebuildPlayerMoveTimeout.get())
|
|
|
|
|
{
|
|
|
|
|
if (mc.getPlayer().xChunk != previousChunkPosX
|
|
|
|
|
|| mc.getPlayer().zChunk != previousChunkPosZ)
|
|
|
|
|
if (LevelPosUtil.getDetailLevel(previousPos) == 0
|
|
|
|
|
|| mc.getPlayer().xChunk != LevelPosUtil.getPosX(previousPos)
|
|
|
|
|
|| mc.getPlayer().zChunk != LevelPosUtil.getPosZ(previousPos))
|
|
|
|
|
{
|
|
|
|
|
fullRegen = true;
|
|
|
|
|
previousChunkPosX = mc.getPlayer().xChunk;
|
|
|
|
|
previousChunkPosZ = mc.getPlayer().zChunk;
|
|
|
|
|
previousPos = LevelPosUtil.createLevelPos((byte) 4, mc.getPlayer().xChunk, mc.getPlayer().zChunk);
|
|
|
|
|
//should use this when it's ready
|
|
|
|
|
vanillaRenderedChunks = new boolean[renderDistance*2+2][renderDistance*2+2];
|
|
|
|
|
}
|
|
|
|
|
prevPlayerPosTime = newTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//================//
|
|
|
|
|
// partial regens //
|
|
|
|
|
//================//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// check if the vanilla rendered chunks changed
|
|
|
|
|
if (newTime - prevVanillaChunkTime > LodConfig.CLIENT.buffers.bufferRebuildChunkChangeTimeout.get())
|
|
|
|
|
{
|
|
|
|
@@ -857,12 +865,12 @@ public class LodRenderer
|
|
|
|
|
{
|
|
|
|
|
partialRegen = true;
|
|
|
|
|
vanillaRenderedChunksChanged = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
prevVanillaChunkTime = newTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// check if there is any newly generated terrain to show
|
|
|
|
|
if (newTime - prevChunkTime > LodConfig.CLIENT.buffers.bufferRebuildLodChangeTimeout.get())
|
|
|
|
|
{
|
|
|
|
@@ -873,14 +881,14 @@ public class LodRenderer
|
|
|
|
|
}
|
|
|
|
|
prevChunkTime = newTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//==============//
|
|
|
|
|
// LOD skipping //
|
|
|
|
|
//==============//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// determine which LODs should not be rendered close to the player
|
|
|
|
|
HashSet<ChunkPos> chunkPosToSkip = LodUtil.getNearbyLodChunkPosToSkip(lodDim, mc.getPlayer().blockPosition());
|
|
|
|
|
int chunkX;
|
|
|
|
@@ -902,8 +910,8 @@ public class LodRenderer
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// if the player is high enough, draw all LODs
|
|
|
|
|
if(chunkPosToSkip.isEmpty() && mc.getPlayer().position().y > 256)
|
|
|
|
|
{
|
|
|
|
@@ -911,5 +919,5 @@ public class LodRenderer
|
|
|
|
|
vanillaRenderedChunksChanged = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|