mirror of
https://github.com/GTNewHorizons/StructureLib.git
synced 2026-10-25 20:32:24 +00:00
optimize large hologram rendering
This commit is contained in:
1 parent
f31de65a1d
commit
9f9fe63729
2 files changed
+216
-105
No files matched your search
@@ -6,7 +6,6 @@ import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
@@ -15,6 +14,7 @@ import net.minecraft.block.Block;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.entity.EntityClientPlayerMP;
|
||||
import net.minecraft.client.gui.GuiNewChat;
|
||||
import net.minecraft.client.particle.EffectRenderer;
|
||||
import net.minecraft.client.particle.EntityFX;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.renderer.culling.Frustrum;
|
||||
@@ -62,7 +62,19 @@ public class ClientProxy extends CommonProxy {
|
||||
|
||||
private static final short[] RGBA_NO_TINT = { 255, 255, 255, 255 };
|
||||
private static final short[] RGBA_RED_TINT = { 255, 128, 128, 0 };
|
||||
/**
|
||||
* Vanilla {@link EffectRenderer} keeps at most 4000 particles per layer and evicts the surplus with an
|
||||
* {@code ArrayList.remove(0)}, so spawning more than that in one projection costs a lot of copying for particles
|
||||
* that are thrown away before they are ever drawn.
|
||||
*/
|
||||
private static final int MAX_SPAWNED_PARTICLES_PER_GROUP = 4000;
|
||||
private static final Map<HintParticleInfo, HintGroup> allHints = new HashMap<>();
|
||||
/**
|
||||
* Total number of hints held across {@link #allGroups} and {@link #currentHints}, i.e. what
|
||||
* {@link ConfigurationHandler#getMaxRenderCount()} is compared against. Not the same as {@code allHints.size()},
|
||||
* which is keyed by position and therefore collapses hints that several holograms put at the same spot.
|
||||
*/
|
||||
private static int storedHintCount;
|
||||
/**
|
||||
* All batches of hints.
|
||||
*/
|
||||
@@ -72,12 +84,36 @@ public class ClientProxy extends CommonProxy {
|
||||
* Current batch of hints. Belongs to the same logical multiblock.
|
||||
*/
|
||||
private static HintGroup currentHints;
|
||||
/**
|
||||
* Player position and squared radius captured when {@link #currentHints} was opened, so the per-hint radius test is
|
||||
* plain arithmetic instead of a player lookup and a config lookup.
|
||||
*/
|
||||
private static double hintOriginX, hintOriginY, hintOriginZ;
|
||||
private static double hintRadiusSq;
|
||||
/**
|
||||
* Decorative particles spawned for {@link #currentHints} so far, capped at
|
||||
* {@link #MAX_SPAWNED_PARTICLES_PER_GROUP}.
|
||||
*/
|
||||
private static int spawnedParticles;
|
||||
/**
|
||||
* Collection of all hints. Sorted from farthest (index 0) to nearest (index size - 1)
|
||||
* <p>
|
||||
* 10k should be more than enough, if not a bit wasteful
|
||||
*/
|
||||
private static final List<HintParticleInfo> allHintsForRender = new ArrayList<>(10000);
|
||||
/**
|
||||
* Hints with {@link HintParticleInfo#renderThrough} set, collected during the main render pass and drawn afterwards
|
||||
* with the depth test off. Keeping them out of the main pass means the depth test is toggled once per frame rather
|
||||
* than once per renderThrough boundary in the distance ordering.
|
||||
*/
|
||||
private static final List<HintParticleInfo> deferredRenderThrough = new ArrayList<>();
|
||||
/**
|
||||
* Sorts farthest first, so transparent hints blend back to front and the nearest
|
||||
* {@link ConfigurationHandler#getMaxRenderCount()} hints are the tail of the list. Reads the distance cached by
|
||||
* {@link HintParticleInfo#cacheDistance} rather than recomputing it for every comparison.
|
||||
*/
|
||||
private static final Comparator<HintParticleInfo> FARTHEST_FIRST = (a, b) -> Double
|
||||
.compare(b.distSqToPlayer, a.distSqToPlayer);
|
||||
/**
|
||||
* If the diff to current player position is too great sort the allHints. Initial value is very far off below y=0
|
||||
* but not huge enough to make a NaN.
|
||||
@@ -89,10 +125,6 @@ public class ClientProxy extends CommonProxy {
|
||||
* if true rebuild the allHints list from all hintOwners (and sort it)
|
||||
*/
|
||||
private static boolean allHintsDirty = false;
|
||||
/**
|
||||
* counter keeping track of how many renderThrough hint particle we have. enable optimization if no renderThrough
|
||||
*/
|
||||
private static int renderThrough;
|
||||
/**
|
||||
* A throttle map for local player. Integrated server will use the throttle map in super class instead.
|
||||
*/
|
||||
@@ -101,20 +133,25 @@ public class ClientProxy extends CommonProxy {
|
||||
@Override
|
||||
public void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) {
|
||||
ensureHinting();
|
||||
if (!isWithinHintRadius(x, y, z)) return;
|
||||
if (storedHintCount >= ConfigurationHandler.INSTANCE.getMaxRenderCount()) return;
|
||||
HintParticleInfo info = new HintParticleInfo(w, x, y, z, icons, RGBa);
|
||||
HintGroup existing = allHints.get(info);
|
||||
// this very projection already covered this position
|
||||
if (existing == currentHints) return;
|
||||
|
||||
// check and remove colliding holograms
|
||||
if (ConfigurationHandler.INSTANCE.isRemoveCollidingHologram()) {
|
||||
HintGroup dupe = allHints.get(info);
|
||||
if (dupe != null && dupe != currentHints) {
|
||||
allGroups.remove(dupe);
|
||||
removeGroup(dupe);
|
||||
}
|
||||
if (existing != null && ConfigurationHandler.INSTANCE.isRemoveCollidingHologram()) {
|
||||
allGroups.remove(existing);
|
||||
removeGroup(existing);
|
||||
}
|
||||
allHints.put(info, currentHints);
|
||||
currentHints.getHints().add(info);
|
||||
storedHintCount++;
|
||||
allHintsDirty = true;
|
||||
|
||||
if (spawnedParticles >= MAX_SPAWNED_PARTICLES_PER_GROUP) return;
|
||||
spawnedParticles++;
|
||||
EntityFX particle = new WeightlessParticleFX(
|
||||
w,
|
||||
x + RANDOM.nextFloat() * 0.5F,
|
||||
@@ -190,10 +227,13 @@ public class ClientProxy extends CommonProxy {
|
||||
}
|
||||
|
||||
static void removeGroup(HintGroup group) {
|
||||
for (HintParticleInfo hintParticleInfo : group.getHints()) {
|
||||
allHints.remove(hintParticleInfo);
|
||||
if (hintParticleInfo.renderThrough) renderThrough--;
|
||||
List<HintParticleInfo> hints = group.getHints();
|
||||
for (int i = 0, size = hints.size(); i < size; i++) {
|
||||
HintParticleInfo hint = hints.get(i);
|
||||
// a later hologram may have claimed this position, in which case the mapping is not ours to drop
|
||||
if (allHints.get(hint) == group) allHints.remove(hint);
|
||||
}
|
||||
storedHintCount -= hints.size();
|
||||
allHintsDirty = true;
|
||||
}
|
||||
|
||||
@@ -227,25 +267,49 @@ public class ClientProxy extends CommonProxy {
|
||||
public void startHinting(World w) {
|
||||
if (!w.isRemote) return;
|
||||
if (currentHints != null) endHinting(w);
|
||||
currentHints = new HintGroup();
|
||||
beginGroup();
|
||||
}
|
||||
|
||||
private void ensureHinting() {
|
||||
if (currentHints == null) currentHints = new HintGroup();
|
||||
if (currentHints == null) beginGroup();
|
||||
}
|
||||
|
||||
private static void beginGroup() {
|
||||
int maxCoexisting = ConfigurationHandler.INSTANCE.getMaxCoexistingHologram();
|
||||
// >= because we are about to add one more
|
||||
while (!allGroups.isEmpty() && allGroups.size() >= maxCoexisting) {
|
||||
removeGroup(allGroups.remove(0));
|
||||
}
|
||||
EntityPlayer player = Minecraft.getMinecraft().thePlayer;
|
||||
if (player == null) {
|
||||
hintRadiusSq = Double.POSITIVE_INFINITY;
|
||||
} else {
|
||||
hintOriginX = player.posX;
|
||||
hintOriginY = player.posY;
|
||||
hintOriginZ = player.posZ;
|
||||
double radius = ConfigurationHandler.INSTANCE.getMaxRenderRadius();
|
||||
hintRadiusSq = radius * radius;
|
||||
}
|
||||
spawnedParticles = 0;
|
||||
currentHints = new HintGroup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endHinting(World w) {
|
||||
if (!w.isRemote || currentHints == null) return;
|
||||
while (!allGroups.isEmpty() && allGroups.size() >= ConfigurationHandler.INSTANCE.getMaxCoexistingHologram()) {
|
||||
allGroups.remove(0);
|
||||
}
|
||||
if (!currentHints.getHints().isEmpty()) allGroups.add(currentHints);
|
||||
// we use the player existence time here as some worlds don't really advance the time ticker
|
||||
currentHints.setCreationTime(getCurrentPlayer().ticksExisted);
|
||||
currentHints = null;
|
||||
}
|
||||
|
||||
private static boolean isWithinHintRadius(int x, int y, int z) {
|
||||
double dx = x + 0.5 - hintOriginX;
|
||||
double dy = y + 0.5 - hintOriginY;
|
||||
double dz = z + 0.5 - hintOriginZ;
|
||||
return dx * dx + dy * dy + dz * dz <= hintRadiusSq;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getOverworldTime() {
|
||||
// there is no overworld, better just hope current world time is ok...
|
||||
@@ -311,7 +375,7 @@ public class ClientProxy extends CommonProxy {
|
||||
|
||||
private static class HintGroup {
|
||||
|
||||
private final List<HintParticleInfo> hints = new LinkedList<>();
|
||||
private final List<HintParticleInfo> hints = new ArrayList<>();
|
||||
private int creationTime = -1;
|
||||
|
||||
public List<HintParticleInfo> getHints() {
|
||||
@@ -335,6 +399,7 @@ public class ClientProxy extends CommonProxy {
|
||||
private final IIcon[] icons;
|
||||
private short[] tint;
|
||||
private boolean renderThrough;
|
||||
private double distSqToPlayer;
|
||||
|
||||
private final long creationTime = System.currentTimeMillis(); // use tick time instead maybe
|
||||
|
||||
@@ -360,10 +425,7 @@ public class ClientProxy extends CommonProxy {
|
||||
}
|
||||
|
||||
public void setRenderThrough() {
|
||||
if (!this.renderThrough) {
|
||||
ClientProxy.renderThrough += 1;
|
||||
allHintsDirty = true;
|
||||
}
|
||||
// no need to invalidate allHintsForRender: the render pass partitions on this flag per frame
|
||||
this.renderThrough = true;
|
||||
}
|
||||
|
||||
@@ -379,18 +441,14 @@ public class ClientProxy extends CommonProxy {
|
||||
return frustrum.isBoxInFrustum(x + 0.25, y + 0.25, z + 0.25, x + 0.75, y + 0.75, z + 0.75);
|
||||
}
|
||||
|
||||
public void draw(Tessellator tes, double eyeX, double eyeY, double eyeZ, int eyeXint, int eyeYint,
|
||||
int eyeZint) {
|
||||
public void draw(Tessellator tes, int transparency, double eyeX, double eyeY, double eyeZ, int eyeXint,
|
||||
int eyeYint, int eyeZint) {
|
||||
double size = 0.5;
|
||||
|
||||
int brightness = w.blockExists(x, 0, z) ? w.getLightBrightnessForSkyBlocks(x, y, z, 0) : 0;
|
||||
tes.setBrightness(brightness);
|
||||
|
||||
tes.setColorRGBA(
|
||||
(int) (tint[0] * .9F),
|
||||
(int) (tint[1] * .95F),
|
||||
(int) (tint[2] * 1F),
|
||||
ConfigurationHandler.INSTANCE.getHintTransparency());
|
||||
tes.setColorRGBA((int) (tint[0] * .9F), (int) (tint[1] * .95F), (int) (tint[2] * 1F), transparency);
|
||||
|
||||
double X = (x - eyeXint) + 0.25;
|
||||
double Y = (y - eyeYint) + 0.25;
|
||||
@@ -486,8 +544,18 @@ public class ClientProxy extends CommonProxy {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
public double getSquareDistanceTo(Vec3 point) {
|
||||
return point.squareDistanceTo(x + 0.5, y + 0.5, z + 0.5);
|
||||
private void cacheDistance(double playerX, double playerY, double playerZ) {
|
||||
double dx = x + 0.5 - playerX;
|
||||
double dy = y + 0.5 - playerY;
|
||||
double dz = z + 0.5 - playerZ;
|
||||
distSqToPlayer = dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
private boolean isWithinRadius(double eyeX, double eyeY, double eyeZ, double radiusSq) {
|
||||
double dx = x + 0.5 - eyeX;
|
||||
double dy = y + 0.5 - eyeY;
|
||||
double dz = z + 0.5 - eyeZ;
|
||||
return dx * dx + dy * dy + dz * dz <= radiusSq;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,12 +564,6 @@ public class ClientProxy extends CommonProxy {
|
||||
static boolean connected = false;
|
||||
static boolean configSent = false;
|
||||
|
||||
private void resetPlayerLocation() {
|
||||
lastPlayerPos.xCoord = Minecraft.getMinecraft().thePlayer.posX;
|
||||
lastPlayerPos.yCoord = Minecraft.getMinecraft().thePlayer.posY;
|
||||
lastPlayerPos.zCoord = Minecraft.getMinecraft().thePlayer.posZ;
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onPlayerLogIn(FMLNetworkEvent.ClientConnectedToServerEvent e) {
|
||||
connected = true;
|
||||
@@ -523,42 +585,52 @@ public class ClientProxy extends CommonProxy {
|
||||
|
||||
@SubscribeEvent
|
||||
public void onClientTick(ClientTickEvent e) {
|
||||
if (e.phase == Phase.END && Minecraft.getMinecraft().thePlayer != null) {
|
||||
Vec3 playerPos = Minecraft.getMinecraft().thePlayer.getPosition(1);
|
||||
boolean sortRequired = false;
|
||||
// The allGroups is implicitly sorted by creationTime
|
||||
// here we exploit this ordering to reduce iteration size
|
||||
int deadline = Minecraft.getMinecraft().thePlayer.ticksExisted
|
||||
- ConfigurationHandler.INSTANCE.getHintLifespan();
|
||||
int i;
|
||||
for (i = 0; i < allGroups.size(); i++) {
|
||||
if (allGroups.get(i).getCreationTime() > deadline) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i != 0) {
|
||||
sortRequired = true;
|
||||
// clear this block and go no further
|
||||
List<HintGroup> toRemove = allGroups.subList(0, i);
|
||||
toRemove.forEach(ClientProxy::removeGroup);
|
||||
toRemove.clear();
|
||||
}
|
||||
if (allHintsDirty) {
|
||||
allHintsForRender.clear();
|
||||
// we need greatly efficient sequential access and mildly efficient sorting
|
||||
// rebuild is sort of rare, and it's expected to lag anyway
|
||||
// so no min-heap here
|
||||
for (HintGroup c : allGroups) allHintsForRender.addAll(c.getHints());
|
||||
sortRequired = true;
|
||||
}
|
||||
if (sortRequired || playerPos.squareDistanceTo(lastPlayerPos) > 1e-2) {
|
||||
// only redo sort if player moved some distance
|
||||
// default is 0.1 block
|
||||
// if there was a full rebuild, go sort it as well
|
||||
allHintsForRender.sort(Comparator.comparingDouble(info -> -info.getSquareDistanceTo(playerPos)));
|
||||
resetPlayerLocation();
|
||||
EntityPlayer player = Minecraft.getMinecraft().thePlayer;
|
||||
if (e.phase != Phase.END || player == null) return;
|
||||
boolean sortRequired = false;
|
||||
// The allGroups is implicitly sorted by creationTime
|
||||
// here we exploit this ordering to reduce iteration size
|
||||
int deadline = player.ticksExisted - ConfigurationHandler.INSTANCE.getHintLifespan();
|
||||
int i;
|
||||
for (i = 0; i < allGroups.size(); i++) {
|
||||
if (allGroups.get(i).getCreationTime() > deadline) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i != 0) {
|
||||
sortRequired = true;
|
||||
// clear this block and go no further
|
||||
List<HintGroup> toRemove = allGroups.subList(0, i);
|
||||
toRemove.forEach(ClientProxy::removeGroup);
|
||||
toRemove.clear();
|
||||
}
|
||||
if (allHintsDirty) {
|
||||
allHintsForRender.clear();
|
||||
// we need greatly efficient sequential access and mildly efficient sorting
|
||||
// rebuild is sort of rare, and it's expected to lag anyway
|
||||
// so no min-heap here
|
||||
for (HintGroup c : allGroups) allHintsForRender.addAll(c.getHints());
|
||||
allHintsDirty = false;
|
||||
sortRequired = true;
|
||||
}
|
||||
final int size = allHintsForRender.size();
|
||||
if (size == 0) return;
|
||||
double px = player.posX, py = player.posY, pz = player.posZ;
|
||||
if (sortRequired || lastPlayerPos.squareDistanceTo(px, py, pz) > resortThreshold(size)) {
|
||||
// only redo sort if player moved some distance
|
||||
// if there was a full rebuild, go sort it as well
|
||||
for (int j = 0; j < size; j++) allHintsForRender.get(j).cacheDistance(px, py, pz);
|
||||
allHintsForRender.sort(FARTHEST_FIRST);
|
||||
lastPlayerPos.xCoord = px;
|
||||
lastPlayerPos.yCoord = py;
|
||||
lastPlayerPos.zCoord = pz;
|
||||
}
|
||||
}
|
||||
|
||||
private static double resortThreshold(int size) {
|
||||
if (size > 100_000) return 64; // 8 blocks
|
||||
if (size > 10_000) return 4; // 2 blocks
|
||||
return 1e-2; // 0.1 blocks
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,17 +677,29 @@ public class ClientProxy extends CommonProxy {
|
||||
if (e.world.isRemote) {
|
||||
// flush hints. we are in a different world now.
|
||||
allHintsForRender.clear();
|
||||
deferredRenderThrough.clear();
|
||||
allHints.clear();
|
||||
allGroups.clear();
|
||||
currentHints = null;
|
||||
storedHintCount = 0;
|
||||
allHintsDirty = false;
|
||||
lastPlayerPos.yCoord = -1e30;
|
||||
renderThrough = 0;
|
||||
// clear throttles. hopefully a world switch is enough long as a cool down.
|
||||
localThrottleMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static int flushIfFull(Tessellator tes, int cubeDrawn) {
|
||||
if (cubeDrawn + 1 < 4096 / (6 * 2 * 3)) return cubeDrawn;
|
||||
tes.draw();
|
||||
tes.startDrawing(GL11.GL_TRIANGLES);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onRenderWorldLast(RenderWorldLastEvent e) {
|
||||
if (allHintsForRender.isEmpty()) return;
|
||||
final int size = allHintsForRender.size();
|
||||
if (size == 0) return;
|
||||
|
||||
// seriously, I'm not a OpenGL expert, so I'm probably doing a lot of very stupid stuff here.
|
||||
// Please consider contributing patches if you find something to optimize.
|
||||
@@ -635,6 +719,15 @@ public class ClientProxy extends CommonProxy {
|
||||
int i0 = (int) d0, i1 = (int) d1, i2 = (int) d2;
|
||||
frustrum.setPosition(d0, d1, d2);
|
||||
|
||||
// hoist every config lookup out of the hot loop
|
||||
final int transparency = ConfigurationHandler.INSTANCE.getHintTransparency();
|
||||
final double radius = ConfigurationHandler.INSTANCE.getMaxRenderRadius();
|
||||
final double radiusSq = radius * radius;
|
||||
// allHintsForRender is sorted farthest first, so the nearest hints the budget should keep are the tail of
|
||||
// the list. Skipping a prefix is O(1) and, unlike counting as we draw, does not make hints pop in and out
|
||||
// as the player turns around.
|
||||
final int from = Math.max(0, size - ConfigurationHandler.INSTANCE.getMaxRenderCount());
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT); // TODO figure out original states
|
||||
// we need the back facing rendered because the thing is transparent
|
||||
@@ -643,39 +736,41 @@ public class ClientProxy extends CommonProxy {
|
||||
GL11.glEnable(GL11.GL_BLEND); // enable blend so it is transparent
|
||||
GL11.glBlendFunc(GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_SRC_ALPHA);
|
||||
// depth test begin as enabled
|
||||
boolean renderThrough = false;
|
||||
Minecraft.getMinecraft().renderEngine.bindTexture(TextureMap.locationBlocksTexture);
|
||||
GL11.glTranslated(-d0 + i0, -d1 + i1, -d2 + i2);
|
||||
Tessellator tes = Tessellator.instance;
|
||||
tes.startDrawing(GL11.GL_TRIANGLES);
|
||||
for (int i = 0, allHintsForRenderSize = allHintsForRender.size(), cubeDrawn = 0; i
|
||||
< allHintsForRenderSize; i++, cubeDrawn++) {
|
||||
int cubeDrawn = 0;
|
||||
for (int i = from; i < size; i++) {
|
||||
HintParticleInfo hint = allHintsForRender.get(i);
|
||||
if (!hint.isWithinRadius(d0, d1, d2, radiusSq)) continue;
|
||||
if (!hint.isInFrustrum(frustrum)) continue;
|
||||
if (renderThrough != hint.renderThrough) {
|
||||
if (i > 0) {
|
||||
p.endStartSection("Draw");
|
||||
tes.draw();
|
||||
tes.startDrawing(GL11.GL_TRIANGLES);
|
||||
cubeDrawn = 0;
|
||||
p.endStartSection("Prepare");
|
||||
}
|
||||
if (hint.renderThrough) GL11.glDisable(GL11.GL_DEPTH_TEST);
|
||||
else GL11.glEnable(GL11.GL_DEPTH_TEST);
|
||||
renderThrough = hint.renderThrough;
|
||||
if (hint.renderThrough) {
|
||||
// drawn in a second pass, so the depth test is toggled once rather than at every boundary
|
||||
deferredRenderThrough.add(hint);
|
||||
continue;
|
||||
}
|
||||
// Minecraft Tessellator draw in batch of 4096. This plays nicely with quads, but for trigs
|
||||
// it might occasionally end up with one half in first batch and the other half in later batch.
|
||||
// so we manually flush the buffer here if that would happen.
|
||||
// 6 faces per particle, 2 trig per face, 3 vertex per trig
|
||||
if ((cubeDrawn + 1) >= 4096 / (6 * 2 * 3)) {
|
||||
tes.draw();
|
||||
tes.startDrawing(GL11.GL_TRIANGLES);
|
||||
}
|
||||
hint.draw(tes, d0, d1, d2, i0, i1, i2);
|
||||
cubeDrawn = flushIfFull(tes, cubeDrawn);
|
||||
hint.draw(tes, transparency, d0, d1, d2, i0, i1, i2);
|
||||
cubeDrawn++;
|
||||
}
|
||||
p.endStartSection("Draw");
|
||||
tes.draw();
|
||||
|
||||
if (!deferredRenderThrough.isEmpty()) {
|
||||
p.endStartSection("Prepare");
|
||||
GL11.glDisable(GL11.GL_DEPTH_TEST);
|
||||
tes.startDrawing(GL11.GL_TRIANGLES);
|
||||
cubeDrawn = 0;
|
||||
for (int i = 0, deferred = deferredRenderThrough.size(); i < deferred; i++) {
|
||||
cubeDrawn = flushIfFull(tes, cubeDrawn);
|
||||
deferredRenderThrough.get(i).draw(tes, transparency, d0, d1, d2, i0, i1, i2);
|
||||
cubeDrawn++;
|
||||
}
|
||||
p.endStartSection("Draw");
|
||||
tes.draw();
|
||||
deferredRenderThrough.clear();
|
||||
}
|
||||
p.endSection();
|
||||
|
||||
GL11.glPopAttrib();
|
||||
|
||||
@@ -12,6 +12,7 @@ import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import com.gtnewhorizon.structurelib.ConfigurationHandler;
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.StructureLibAPI;
|
||||
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
|
||||
@@ -443,6 +444,8 @@ public interface IStructureDefinition<T> {
|
||||
return success;
|
||||
} else {
|
||||
if (hintsOnly) {
|
||||
// elements are not strictly required to have a side effect free check, hence the opt out
|
||||
final boolean skipCorrectlyPlaced = ConfigurationHandler.INSTANCE.isSkipCorrectlyPlacedHints();
|
||||
StructureUtility.iterateV2(
|
||||
elements,
|
||||
world,
|
||||
@@ -453,14 +456,27 @@ public interface IStructureDefinition<T> {
|
||||
basePositionA,
|
||||
basePositionB,
|
||||
basePositionC,
|
||||
ignoreBlockUnloaded((e, w, x, y, z, a, b, c) -> {
|
||||
e.spawnHint(object, world, x, y, z, trigger);
|
||||
if (ChannelDataAccessor.hasSubChannel(trigger, StructureLibAPI.CHANNEL_SHOW_ERROR)
|
||||
&& !e.couldBeValid(object, world, x, y, z, trigger)) {
|
||||
StructureLibAPI.markHintParticleError(StructureLib.getCurrentPlayer(), world, x, y, z);
|
||||
new IStructureWalker<T>() {
|
||||
|
||||
@Override
|
||||
public boolean visit(IStructureElement<T> e, World w, int x, int y, int z, int a, int b,
|
||||
int c) {
|
||||
if (skipCorrectlyPlaced && e.check(object, w, x, y, z)) return true;
|
||||
e.spawnHint(object, w, x, y, z, trigger);
|
||||
if (ChannelDataAccessor.hasSubChannel(trigger, StructureLibAPI.CHANNEL_SHOW_ERROR)
|
||||
&& !e.couldBeValid(object, w, x, y, z, trigger)) {
|
||||
StructureLibAPI.markHintParticleError(StructureLib.getCurrentPlayer(), w, x, y, z);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
|
||||
@Override
|
||||
public boolean blockNotLoaded(IStructureElement<T> e, World w, int x, int y, int z, int a,
|
||||
int b, int c) {
|
||||
e.spawnHint(object, w, x, y, z, trigger);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
"spawnHint");
|
||||
} else {
|
||||
StructureUtility.iterateV2(
|
||||
|
||||
Reference in new issue
Block a user