diff --git a/src/main/java/com/jozufozu/flywheel/api/vertex/ShadedVertexList.java b/src/main/java/com/jozufozu/flywheel/api/vertex/ShadedVertexList.java new file mode 100644 index 000000000..227879b3a --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/api/vertex/ShadedVertexList.java @@ -0,0 +1,5 @@ +package com.jozufozu.flywheel.api.vertex; + +public interface ShadedVertexList extends VertexList { + boolean isShaded(int index); +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/model/BufferBuilderExtension.java b/src/main/java/com/jozufozu/flywheel/backend/model/BufferBuilderExtension.java index 87d840792..4aec62268 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/model/BufferBuilderExtension.java +++ b/src/main/java/com/jozufozu/flywheel/backend/model/BufferBuilderExtension.java @@ -12,6 +12,8 @@ import com.mojang.blaze3d.vertex.VertexFormat; */ public interface BufferBuilderExtension { + int flywheel$getVertices(); + /** * Frees the internal ByteBuffer, if it exists. */ @@ -24,4 +26,12 @@ public interface BufferBuilderExtension { * @param vertexCount The number of vertices in the buffer. */ void flywheel$injectForRender(ByteBuffer buffer, VertexFormat format, int vertexCount); + + /** + * Appends the remaining bytes from the given buffer to this BufferBuilder. + * @param buffer The buffer from which to copy bytes. + * @throws IllegalStateException If this BufferBuilder is not started or is the process of writing a vertex + * @throws IllegalArgumentException If the given buffer does not contain a whole number of vertices + */ + void flywheel$appendBufferUnsafe(ByteBuffer buffer); } diff --git a/src/main/java/com/jozufozu/flywheel/core/model/Model.java b/src/main/java/com/jozufozu/flywheel/core/model/Model.java index 6d1c2c0c5..eacfd3394 100644 --- a/src/main/java/com/jozufozu/flywheel/core/model/Model.java +++ b/src/main/java/com/jozufozu/flywheel/core/model/Model.java @@ -16,7 +16,7 @@ import com.jozufozu.flywheel.core.QuadConverter; *

* *
{@code
- * IModel model = ...;
+ * Model model = ...;
  * VecBuffer into = ...;
  *
  * int initial = VecBuffer.unwrap().position();
diff --git a/src/main/java/com/jozufozu/flywheel/core/model/ModelTransformer.java b/src/main/java/com/jozufozu/flywheel/core/model/ModelTransformer.java
index 10eafd1c4..52e542ec7 100644
--- a/src/main/java/com/jozufozu/flywheel/core/model/ModelTransformer.java
+++ b/src/main/java/com/jozufozu/flywheel/core/model/ModelTransformer.java
@@ -1,5 +1,8 @@
 package com.jozufozu.flywheel.core.model;
 
+import java.util.function.IntPredicate;
+
+import com.jozufozu.flywheel.api.vertex.ShadedVertexList;
 import com.jozufozu.flywheel.api.vertex.VertexList;
 import com.jozufozu.flywheel.util.DiffuseLightCalculator;
 import com.jozufozu.flywheel.util.transform.Transform;
@@ -19,12 +22,18 @@ public class ModelTransformer {
 
 	private final Model model;
 	private final VertexList reader;
+	private final IntPredicate shadedPredicate;
 
 	public final Context context = new Context();
 
 	public ModelTransformer(Model model) {
 		this.model = model;
 		reader = model.getReader();
+		if (reader instanceof ShadedVertexList shaded) {
+			shadedPredicate = shaded::isShaded;
+		} else {
+			shadedPredicate = index -> true;
+		}
 	}
 
 	public void renderInto(Params params, PoseStack input, VertexConsumer builder) {
@@ -82,7 +91,7 @@ public class ModelTransformer {
 				a = reader.getA(i);
 			}
 			if (context.outputColorDiffuse) {
-				float instanceDiffuse = diffuseCalculator.getDiffuse(nx, ny, nz);
+				float instanceDiffuse = diffuseCalculator.getDiffuse(nx, ny, nz, shadedPredicate.test(i));
 				int colorR = transformColor(r, instanceDiffuse);
 				int colorG = transformColor(g, instanceDiffuse);
 				int colorB = transformColor(b, instanceDiffuse);
diff --git a/src/main/java/com/jozufozu/flywheel/core/model/ModelUtil.java b/src/main/java/com/jozufozu/flywheel/core/model/ModelUtil.java
index c839e4093..0815adf28 100644
--- a/src/main/java/com/jozufozu/flywheel/core/model/ModelUtil.java
+++ b/src/main/java/com/jozufozu/flywheel/core/model/ModelUtil.java
@@ -38,6 +38,8 @@ public class ModelUtil {
 	 */
 	public static final BlockRenderDispatcher VANILLA_RENDERER = createVanillaRenderer();
 
+	private static final ThreadLocal THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new);
+
 	private static BlockRenderDispatcher createVanillaRenderer() {
 		BlockRenderDispatcher defaultDispatcher = Minecraft.getInstance().getBlockRenderer();
 		BlockRenderDispatcher dispatcher = new BlockRenderDispatcher(null, null, null);
@@ -54,23 +56,47 @@ public class ModelUtil {
 		return dispatcher;
 	}
 
-	public static BufferBuilder getBufferBuilder(BakedModel model, BlockState referenceState, PoseStack ms) {
+	public static ShadeSeparatedBufferBuilder getBufferBuilder(BakedModel model, BlockState referenceState, PoseStack poseStack) {
+		return getBufferBuilder(VirtualEmptyBlockGetter.INSTANCE, model, referenceState, poseStack);
+	}
+
+	public static ShadeSeparatedBufferBuilder getBufferBuilder(BlockAndTintGetter renderWorld, BakedModel model, BlockState referenceState, PoseStack poseStack) {
 		ModelBlockRenderer blockRenderer = VANILLA_RENDERER.getModelRenderer();
-		BufferBuilder builder = new BufferBuilder(512);
+		ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get();
+
+		ShadeSeparatingVertexConsumer shadeSeparatingWrapper = objects.shadeSeparatingWrapper;
+		ShadeSeparatedBufferBuilder builder = new ShadeSeparatedBufferBuilder(512);
+		BufferBuilder unshadedBuilder = objects.unshadedBuilder;
+
 		builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
-		blockRenderer.tesselateBlock(VirtualEmptyBlockGetter.INSTANCE, model, referenceState, BlockPos.ZERO, ms, builder,
-				false, new Random(), 42, OverlayTexture.NO_OVERLAY, VirtualEmptyModelData.INSTANCE);
+		unshadedBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
+		shadeSeparatingWrapper.prepare(builder, unshadedBuilder);
+		blockRenderer.tesselateBlock(renderWorld, model, referenceState, BlockPos.ZERO, poseStack, shadeSeparatingWrapper,
+				false, objects.random, 42, OverlayTexture.NO_OVERLAY, VirtualEmptyModelData.INSTANCE);
+		shadeSeparatingWrapper.clear();
+		unshadedBuilder.end();
+		builder.appendUnshadedVertices(unshadedBuilder);
 		builder.end();
+
 		return builder;
 	}
 
-	public static BufferBuilder getBufferBuilderFromTemplate(BlockAndTintGetter renderWorld, RenderType layer, Collection blocks) {
-		ModelBlockRenderer modelRenderer = VANILLA_RENDERER.getModelRenderer();
+	public static ShadeSeparatedBufferBuilder getBufferBuilderFromTemplate(BlockAndTintGetter renderWorld, RenderType layer, Collection blocks) {
+		return getBufferBuilderFromTemplate(renderWorld, layer, blocks, new PoseStack());
+	}
+
+	public static ShadeSeparatedBufferBuilder getBufferBuilderFromTemplate(BlockAndTintGetter renderWorld, RenderType layer, Collection blocks, PoseStack poseStack) {
+		ModelBlockRenderer modelRenderer = VANILLA_RENDERER.getModelRenderer();
+		ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get();
+
+		Random random = objects.random;
+		ShadeSeparatingVertexConsumer shadeSeparatingWrapper = objects.shadeSeparatingWrapper;
+		ShadeSeparatedBufferBuilder builder = new ShadeSeparatedBufferBuilder(512);
+		BufferBuilder unshadedBuilder = objects.unshadedBuilder;
 
-		PoseStack ms = new PoseStack();
-		Random random = new Random();
-		BufferBuilder builder = new BufferBuilder(512);
 		builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
+		unshadedBuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.BLOCK);
+		shadeSeparatingWrapper.prepare(builder, unshadedBuilder);
 
 		ForgeHooksClient.setRenderType(layer);
 		ModelBlockRenderer.enableCaching();
@@ -84,16 +110,20 @@ public class ModelUtil {
 
 			BlockPos pos = info.pos;
 
-			ms.pushPose();
-			ms.translate(pos.getX(), pos.getY(), pos.getZ());
-			modelRenderer.tesselateBlock(renderWorld, VANILLA_RENDERER.getBlockModel(state), state, pos, ms, builder,
+			poseStack.pushPose();
+			poseStack.translate(pos.getX(), pos.getY(), pos.getZ());
+			modelRenderer.tesselateBlock(renderWorld, VANILLA_RENDERER.getBlockModel(state), state, pos, poseStack, shadeSeparatingWrapper,
 					true, random, 42, OverlayTexture.NO_OVERLAY, EmptyModelData.INSTANCE);
-			ms.popPose();
+			poseStack.popPose();
 		}
 		ModelBlockRenderer.clearCache();
 		ForgeHooksClient.setRenderType(null);
 
+		shadeSeparatingWrapper.clear();
+		unshadedBuilder.end();
+		builder.appendUnshadedVertices(unshadedBuilder);
 		builder.end();
+
 		return builder;
 	}
 
@@ -107,4 +137,10 @@ public class ModelUtil {
 			return stack;
 		};
 	}
+
+	private static class ThreadLocalObjects {
+		public final Random random = new Random();
+		public final ShadeSeparatingVertexConsumer shadeSeparatingWrapper = new ShadeSeparatingVertexConsumer();
+		public final BufferBuilder unshadedBuilder = new BufferBuilder(512);
+	}
 }
diff --git a/src/main/java/com/jozufozu/flywheel/core/model/ShadeSeparatedBufferBuilder.java b/src/main/java/com/jozufozu/flywheel/core/model/ShadeSeparatedBufferBuilder.java
new file mode 100644
index 000000000..38043702b
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/core/model/ShadeSeparatedBufferBuilder.java
@@ -0,0 +1,25 @@
+package com.jozufozu.flywheel.core.model;
+
+import java.nio.ByteBuffer;
+
+import com.jozufozu.flywheel.backend.model.BufferBuilderExtension;
+import com.mojang.blaze3d.vertex.BufferBuilder;
+import com.mojang.datafixers.util.Pair;
+
+public class ShadeSeparatedBufferBuilder extends BufferBuilder {
+	protected int unshadedStartVertex;
+
+	public ShadeSeparatedBufferBuilder(int capacity) {
+		super(capacity);
+	}
+
+	public void appendUnshadedVertices(BufferBuilder unshadedBuilder) {
+		Pair data = unshadedBuilder.popNextBuffer();
+		unshadedStartVertex = ((BufferBuilderExtension) this).flywheel$getVertices();
+		((BufferBuilderExtension) this).flywheel$appendBufferUnsafe(data.getSecond());
+	}
+
+	public int getUnshadedStartVertex() {
+		return unshadedStartVertex;
+	}
+}
diff --git a/src/main/java/com/jozufozu/flywheel/core/model/ShadeSeparatingVertexConsumer.java b/src/main/java/com/jozufozu/flywheel/core/model/ShadeSeparatingVertexConsumer.java
new file mode 100644
index 000000000..ea50184a7
--- /dev/null
+++ b/src/main/java/com/jozufozu/flywheel/core/model/ShadeSeparatingVertexConsumer.java
@@ -0,0 +1,84 @@
+package com.jozufozu.flywheel.core.model;
+
+import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.blaze3d.vertex.VertexConsumer;
+
+import net.minecraft.client.renderer.block.model.BakedQuad;
+
+public class ShadeSeparatingVertexConsumer implements VertexConsumer {
+	protected VertexConsumer shadedConsumer;
+	protected VertexConsumer unshadedConsumer;
+
+	public void prepare(VertexConsumer shadedConsumer, VertexConsumer unshadedConsumer) {
+		this.shadedConsumer = shadedConsumer;
+		this.unshadedConsumer = unshadedConsumer;
+	}
+
+	public void clear() {
+		shadedConsumer = null;
+		unshadedConsumer = null;
+	}
+
+	@Override
+	public void putBulkData(PoseStack.Pose poseEntry, BakedQuad quad, float[] colorMuls, float red, float green, float blue, int[] combinedLights, int combinedOverlay, boolean mulColor) {
+		if (quad.isShade()) {
+			shadedConsumer.putBulkData(poseEntry, quad, colorMuls, red, green, blue, combinedLights, combinedOverlay, mulColor);
+		} else {
+			unshadedConsumer.putBulkData(poseEntry, quad, colorMuls, red, green, blue, combinedLights, combinedOverlay, mulColor);
+		}
+	}
+
+	@Override
+	public void putBulkData(PoseStack.Pose matrixEntry, BakedQuad bakedQuad, float[] baseBrightness, float red, float green, float blue, float alpha, int[] lightmapCoords, int overlayCoords, boolean readExistingColor) {
+		if (bakedQuad.isShade()) {
+			shadedConsumer.putBulkData(matrixEntry, bakedQuad, baseBrightness, red, green, blue, alpha, lightmapCoords, overlayCoords, readExistingColor);
+		} else {
+			unshadedConsumer.putBulkData(matrixEntry, bakedQuad, baseBrightness, red, green, blue, alpha, lightmapCoords, overlayCoords, readExistingColor);
+		}
+	}
+
+	@Override
+	public VertexConsumer vertex(double x, double y, double z) {
+		throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
+	}
+
+	@Override
+	public VertexConsumer color(int red, int green, int blue, int alpha) {
+		throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
+	}
+
+	@Override
+	public VertexConsumer uv(float u, float v) {
+		throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
+	}
+
+	@Override
+	public VertexConsumer overlayCoords(int u, int v) {
+		throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
+	}
+
+	@Override
+	public VertexConsumer uv2(int u, int v) {
+		throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
+	}
+
+	@Override
+	public VertexConsumer normal(float x, float y, float z) {
+		throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
+	}
+
+	@Override
+	public void endVertex() {
+		throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
+	}
+
+	@Override
+	public void defaultColor(int red, int green, int blue, int alpha) {
+		throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
+	}
+
+	@Override
+	public void unsetDefaultColor() {
+		throw new UnsupportedOperationException("ShadeSeparatingVertexConsumer only supports putBulkData!");
+	}
+}
diff --git a/src/main/java/com/jozufozu/flywheel/core/vertex/BlockVertex.java b/src/main/java/com/jozufozu/flywheel/core/vertex/BlockVertex.java
index e382d0ab7..d74dde894 100644
--- a/src/main/java/com/jozufozu/flywheel/core/vertex/BlockVertex.java
+++ b/src/main/java/com/jozufozu/flywheel/core/vertex/BlockVertex.java
@@ -6,6 +6,7 @@ import com.jozufozu.flywheel.api.vertex.VertexList;
 import com.jozufozu.flywheel.api.vertex.VertexType;
 import com.jozufozu.flywheel.core.layout.BufferLayout;
 import com.jozufozu.flywheel.core.layout.CommonItems;
+import com.jozufozu.flywheel.core.model.ShadeSeparatedBufferBuilder;
 import com.mojang.blaze3d.vertex.BufferBuilder;
 import com.mojang.blaze3d.vertex.DefaultVertexFormat;
 import com.mojang.datafixers.util.Pair;
@@ -57,6 +58,10 @@ Vertex FLWCreateVertex() {
 				""";
 	}
 
+	public BlockVertexListUnsafe.Shaded createReader(ByteBuffer buffer, int vertexCount, int unshadedStartVertex) {
+		return new BlockVertexListUnsafe.Shaded(buffer, vertexCount, unshadedStartVertex);
+	}
+
 	public VertexList createReader(BufferBuilder bufferBuilder) {
 		// TODO: try to avoid virtual model rendering
 		Pair pair = bufferBuilder.popNextBuffer();
@@ -66,6 +71,10 @@ Vertex FLWCreateVertex() {
 			throw new RuntimeException("Cannot use BufferBuilder with " + drawState.format());
 		}
 
-		return new BlockVertexListUnsafe(pair.getSecond(), drawState.vertexCount());
+		if (bufferBuilder instanceof ShadeSeparatedBufferBuilder separated) {
+			return createReader(pair.getSecond(), drawState.vertexCount(), separated.getUnshadedStartVertex());
+		} else {
+			return createReader(pair.getSecond(), drawState.vertexCount());
+		}
 	}
 }
diff --git a/src/main/java/com/jozufozu/flywheel/core/vertex/BlockVertexList.java b/src/main/java/com/jozufozu/flywheel/core/vertex/BlockVertexList.java
index ff4c98126..eaac02cee 100644
--- a/src/main/java/com/jozufozu/flywheel/core/vertex/BlockVertexList.java
+++ b/src/main/java/com/jozufozu/flywheel/core/vertex/BlockVertexList.java
@@ -2,7 +2,9 @@ package com.jozufozu.flywheel.core.vertex;
 
 import java.nio.ByteBuffer;
 
+import com.jozufozu.flywheel.api.vertex.ShadedVertexList;
 import com.jozufozu.flywheel.api.vertex.VertexList;
+import com.jozufozu.flywheel.core.model.ShadeSeparatedBufferBuilder;
 import com.jozufozu.flywheel.util.RenderMath;
 import com.mojang.blaze3d.vertex.BufferBuilder;
 import com.mojang.datafixers.util.Pair;
@@ -104,4 +106,20 @@ public class BlockVertexList implements VertexList {
 		return vertexCount;
 	}
 
+	public static class Shaded extends BlockVertexList implements ShadedVertexList {
+
+		private final int unshadedStartVertex;
+
+		public Shaded(ShadeSeparatedBufferBuilder builder) {
+			super(builder);
+			unshadedStartVertex = builder.getUnshadedStartVertex();
+		}
+
+		@Override
+		public boolean isShaded(int index) {
+			return index < unshadedStartVertex;
+		}
+
+	}
+
 }
diff --git a/src/main/java/com/jozufozu/flywheel/core/vertex/BlockVertexListUnsafe.java b/src/main/java/com/jozufozu/flywheel/core/vertex/BlockVertexListUnsafe.java
index 7c93b29d6..0993f29e5 100644
--- a/src/main/java/com/jozufozu/flywheel/core/vertex/BlockVertexListUnsafe.java
+++ b/src/main/java/com/jozufozu/flywheel/core/vertex/BlockVertexListUnsafe.java
@@ -4,6 +4,7 @@ import java.nio.ByteBuffer;
 
 import org.lwjgl.system.MemoryUtil;
 
+import com.jozufozu.flywheel.api.vertex.ShadedVertexList;
 import com.jozufozu.flywheel.api.vertex.VertexList;
 import com.jozufozu.flywheel.util.RenderMath;
 
@@ -97,4 +98,21 @@ public class BlockVertexListUnsafe implements VertexList {
 	public int getVertexCount() {
 		return vertexCount;
 	}
+
+	public static class Shaded extends BlockVertexListUnsafe implements ShadedVertexList {
+
+		private final int unshadedStartVertex;
+
+		public Shaded(ByteBuffer buffer, int vertexCount, int unshadedStartVertex) {
+			super(buffer, vertexCount);
+			this.unshadedStartVertex = unshadedStartVertex;
+		}
+
+		@Override
+		public boolean isShaded(int index) {
+			return index < unshadedStartVertex;
+		}
+
+	}
+
 }
diff --git a/src/main/java/com/jozufozu/flywheel/core/virtual/VirtualEmptyBlockGetter.java b/src/main/java/com/jozufozu/flywheel/core/virtual/VirtualEmptyBlockGetter.java
index 8058bcf68..ebfbe9389 100644
--- a/src/main/java/com/jozufozu/flywheel/core/virtual/VirtualEmptyBlockGetter.java
+++ b/src/main/java/com/jozufozu/flywheel/core/virtual/VirtualEmptyBlockGetter.java
@@ -22,115 +22,119 @@ import net.minecraft.world.level.lighting.LevelLightEngine;
 import net.minecraft.world.level.material.FluidState;
 import net.minecraft.world.level.material.Fluids;
 
-public enum VirtualEmptyBlockGetter implements BlockAndTintGetter {
-	INSTANCE;
-
-	private final LevelLightEngine lightEngine = new LevelLightEngine(new LightChunkGetter() {
-		@Override
-		public BlockGetter getChunkForLighting(int p_63023_, int p_63024_) {
-			return VirtualEmptyBlockGetter.this;
-		}
-
-		@Override
-		public BlockGetter getLevel() {
-			return VirtualEmptyBlockGetter.this;
-		}
-	}, false, false) {
-		private static final LayerLightEventListener SKY_DUMMY_LISTENER = new LayerLightEventListener() {
-			@Override
-			public void checkBlock(BlockPos pos) {
-			}
-
-			@Override
-			public void onBlockEmissionIncrease(BlockPos pos, int p_164456_) {
-			}
-
-			@Override
-			public boolean hasLightWork() {
-				return false;
-			}
-
-			@Override
-			public int runUpdates(int p_164449_, boolean p_164450_, boolean p_164451_) {
-				return p_164449_;
-			}
-
-			@Override
-			public void updateSectionStatus(SectionPos pos, boolean p_75838_) {
-			}
-
-			@Override
-			public void enableLightSources(ChunkPos pos, boolean p_164453_) {
-			}
-
-			@Override
-			public DataLayer getDataLayerData(SectionPos pos) {
-				return null;
-			}
-
-			@Override
-			public int getLightValue(BlockPos pos) {
-				return 15;
-			}
-		};
-
-		@Override
-		public LayerLightEventListener getLayerListener(LightLayer layer) {
-			if (layer == LightLayer.BLOCK) {
-				return LayerLightEventListener.DummyLightLayerEventListener.INSTANCE;
-			} else {
-				return SKY_DUMMY_LISTENER;
-			}
-		}
-
-		@Override
-		public int getRawBrightness(BlockPos pos, int skyDarken) {
-			return 15 - skyDarken;
-		}
-	};
+public interface VirtualEmptyBlockGetter extends BlockAndTintGetter {
+	public static final VirtualEmptyBlockGetter INSTANCE = new StaticLightImpl(0, 15);
+	public static final VirtualEmptyBlockGetter FULL_BRIGHT = new StaticLightImpl(15, 15);
+	public static final VirtualEmptyBlockGetter FULL_DARK = new StaticLightImpl(0, 0);
 
 	public static boolean is(BlockAndTintGetter blockGetter) {
-		return blockGetter == INSTANCE;
+		return blockGetter instanceof VirtualEmptyBlockGetter;
 	}
 
 	@Override
-	public BlockEntity getBlockEntity(BlockPos pos) {
+	default BlockEntity getBlockEntity(BlockPos pos) {
 		return null;
 	}
 
 	@Override
-	public BlockState getBlockState(BlockPos pos) {
+	default BlockState getBlockState(BlockPos pos) {
 		return Blocks.AIR.defaultBlockState();
 	}
 
 	@Override
-	public FluidState getFluidState(BlockPos pos) {
+	default FluidState getFluidState(BlockPos pos) {
 		return Fluids.EMPTY.defaultFluidState();
 	}
 
 	@Override
-	public int getHeight() {
+	default int getHeight() {
 		return 1;
 	}
 
 	@Override
-	public int getMinBuildHeight() {
+	default int getMinBuildHeight() {
 		return 0;
 	}
 
 	@Override
-	public float getShade(Direction direction, boolean bool) {
+	default float getShade(Direction direction, boolean shaded) {
 		return 1f;
 	}
 
 	@Override
-	public LevelLightEngine getLightEngine() {
-		return lightEngine;
-	}
-
-	@Override
-	public int getBlockTint(BlockPos pos, ColorResolver resolver) {
+	default int getBlockTint(BlockPos pos, ColorResolver resolver) {
 		Biome plainsBiome = Minecraft.getInstance().getConnection().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).getOrThrow(Biomes.PLAINS);
 		return resolver.getColor(plainsBiome, pos.getX(), pos.getZ());
 	}
+
+	public static class StaticLightImpl implements VirtualEmptyBlockGetter {
+		private final LevelLightEngine lightEngine;
+
+		public StaticLightImpl(int blockLight, int skyLight) {
+			lightEngine = new LevelLightEngine(new LightChunkGetter() {
+				@Override
+				public BlockGetter getChunkForLighting(int p_63023_, int p_63024_) {
+					return StaticLightImpl.this;
+				}
+
+				@Override
+				public BlockGetter getLevel() {
+					return StaticLightImpl.this;
+				}
+			}, false, false) {
+				private final LayerLightEventListener blockListener = createStaticListener(blockLight);
+				private final LayerLightEventListener skyListener = createStaticListener(skyLight);
+
+				@Override
+				public LayerLightEventListener getLayerListener(LightLayer layer) {
+					return layer == LightLayer.BLOCK ? blockListener : skyListener;
+				}
+			};
+		}
+
+		private static LayerLightEventListener createStaticListener(int light) {
+			return new LayerLightEventListener() {
+				@Override
+				public void checkBlock(BlockPos pos) {
+				}
+
+				@Override
+				public void onBlockEmissionIncrease(BlockPos pos, int p_164456_) {
+				}
+
+				@Override
+				public boolean hasLightWork() {
+					return false;
+				}
+
+				@Override
+				public int runUpdates(int p_164449_, boolean p_164450_, boolean p_164451_) {
+					return p_164449_;
+				}
+
+				@Override
+				public void updateSectionStatus(SectionPos pos, boolean p_75838_) {
+				}
+
+				@Override
+				public void enableLightSources(ChunkPos pos, boolean p_164453_) {
+				}
+
+				@Override
+				public DataLayer getDataLayerData(SectionPos pos) {
+					return null;
+				}
+
+				@Override
+				public int getLightValue(BlockPos pos) {
+					return light;
+				}
+			};
+		}
+
+		@Override
+		public LevelLightEngine getLightEngine() {
+			return lightEngine;
+		}
+	}
 }
diff --git a/src/main/java/com/jozufozu/flywheel/mixin/BufferBuilderMixin.java b/src/main/java/com/jozufozu/flywheel/mixin/BufferBuilderMixin.java
index 6b13b9a92..bc82deaf8 100644
--- a/src/main/java/com/jozufozu/flywheel/mixin/BufferBuilderMixin.java
+++ b/src/main/java/com/jozufozu/flywheel/mixin/BufferBuilderMixin.java
@@ -20,16 +20,10 @@ public abstract class BufferBuilderMixin implements BufferBuilderExtension {
 	private ByteBuffer buffer;
 
 	@Shadow
-	private boolean building;
+	private int nextElementByte;
 
 	@Shadow
-	public abstract void begin(VertexFormat.Mode p_166780_, VertexFormat p_166781_);
-
-	@Shadow
-	private VertexFormat.Mode mode;
-
-	@Shadow
-	private VertexFormat format;
+	private int vertices;
 
 	@Shadow
 	@Nullable
@@ -39,7 +33,22 @@ public abstract class BufferBuilderMixin implements BufferBuilderExtension {
 	private int elementIndex;
 
 	@Shadow
-	private int vertices;
+	private VertexFormat format;
+
+	@Shadow
+	private VertexFormat.Mode mode;
+
+	@Shadow
+	private boolean building;
+
+	@Shadow
+	private void ensureCapacity(int increaseAmount) {
+	}
+
+	@Override
+	public int flywheel$getVertices() {
+		return vertices;
+	}
 
 	@Override
 	public void flywheel$freeBuffer() {
@@ -61,4 +70,29 @@ public abstract class BufferBuilderMixin implements BufferBuilderExtension {
 		this.currentElement = this.format.getElements().get(0);
 		this.elementIndex = 0;
 	}
+
+	@Override
+	public void flywheel$appendBufferUnsafe(ByteBuffer buffer) {
+		if (!building) {
+			throw new IllegalStateException("BufferBuilder not started");
+		}
+		if (elementIndex != 0) {
+			throw new IllegalStateException("Cannot append buffer while building vertex");
+		}
+
+		int numBytes = buffer.remaining();
+		if (numBytes % format.getVertexSize() != 0) {
+			throw new IllegalArgumentException("Cannot append buffer with non-whole number of vertices");
+		}
+		int numVertices = numBytes / format.getVertexSize();
+
+		ensureCapacity(numBytes + format.getVertexSize());
+		int originalPosition = this.buffer.position();
+		this.buffer.position(nextElementByte);
+		MemoryUtil.memCopy(buffer, this.buffer);
+		this.buffer.position(originalPosition);
+
+		nextElementByte += numBytes;
+		vertices += numVertices;
+	}
 }
diff --git a/src/main/java/com/jozufozu/flywheel/util/DiffuseLightCalculator.java b/src/main/java/com/jozufozu/flywheel/util/DiffuseLightCalculator.java
index d7f712582..4f1ac0557 100644
--- a/src/main/java/com/jozufozu/flywheel/util/DiffuseLightCalculator.java
+++ b/src/main/java/com/jozufozu/flywheel/util/DiffuseLightCalculator.java
@@ -2,10 +2,9 @@ package com.jozufozu.flywheel.util;
 
 import net.minecraft.client.Minecraft;
 import net.minecraft.client.multiplayer.ClientLevel;
-import net.minecraftforge.client.model.pipeline.LightUtil;
 
 public interface DiffuseLightCalculator {
-	DiffuseLightCalculator DEFAULT = LightUtil::diffuseLight;
+	DiffuseLightCalculator DEFAULT = RenderMath::diffuseLight;
 	DiffuseLightCalculator NETHER = RenderMath::diffuseLightNether;
 
 	static DiffuseLightCalculator forCurrentLevel() {
@@ -16,5 +15,5 @@ public interface DiffuseLightCalculator {
 		return level.effects().constantAmbientLight() ? NETHER : DEFAULT;
 	}
 
-	float getDiffuse(float normalX, float normalY, float normalZ);
+	float getDiffuse(float normalX, float normalY, float normalZ, boolean shaded);
 }
diff --git a/src/main/java/com/jozufozu/flywheel/util/RenderMath.java b/src/main/java/com/jozufozu/flywheel/util/RenderMath.java
index f280c2064..9ee9ecddc 100644
--- a/src/main/java/com/jozufozu/flywheel/util/RenderMath.java
+++ b/src/main/java/com/jozufozu/flywheel/util/RenderMath.java
@@ -1,5 +1,7 @@
 package com.jozufozu.flywheel.util;
 
+import net.minecraftforge.client.model.pipeline.LightUtil;
+
 public class RenderMath {
 
 	/**
@@ -68,7 +70,17 @@ public class RenderMath {
 		return (float) (((((target - current) % 360) + 540) % 360) - 180);
 	}
 
-	public static float diffuseLightNether(float x, float y, float z) {
+	public static float diffuseLight(float x, float y, float z, boolean shaded) {
+		if (!shaded) {
+			return 1f;
+		}
+		return LightUtil.diffuseLight(x, y, z);
+	}
+
+	public static float diffuseLightNether(float x, float y, float z, boolean shaded) {
+		if (!shaded) {
+			return 0.9f;
+		}
 		return Math.min(x * x * 0.6f + y * y * 0.9f + z * z * 0.8f, 1f);
 	}
 }