aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authormakamys <makamys@outlook.com>2021-05-07 08:42:32 +0200
committermakamys <makamys@outlook.com>2021-05-07 08:42:32 +0200
commitc233ea31da07957d8872d3859d6e75b99937becd (patch)
treeb9d4301b7b503aa466d4af2db8498918f3e6dfee /src
parentbc6c84d2d2342073b6f4d1b8c1213b7102bb7ebf (diff)
downloadNeodymium-c233ea31da07957d8872d3859d6e75b99937becd.tar.gz
Neodymium-c233ea31da07957d8872d3859d6e75b99937becd.tar.bz2
Neodymium-c233ea31da07957d8872d3859d6e75b99937becd.zip
Port mod from MCP to Forge!
The only known regression is the sides of LOD=1 chunks look darker for some reason
Diffstat (limited to 'src')
-rw-r--r--src/main/java/makamys/lodmod/LODMod.java1
-rw-r--r--src/main/java/makamys/lodmod/ducks/ITessellator.java10
-rw-r--r--src/main/java/makamys/lodmod/ducks/IWorldRenderer.java12
-rw-r--r--src/main/java/makamys/lodmod/mixin/IRenderGlobal.java26
-rw-r--r--src/main/java/makamys/lodmod/mixin/MixinChunkCache.java28
-rw-r--r--src/main/java/makamys/lodmod/mixin/MixinEntityRenderer.java3
-rw-r--r--src/main/java/makamys/lodmod/mixin/MixinMinecraftServer.java30
-rw-r--r--src/main/java/makamys/lodmod/mixin/MixinRenderGlobal.java19
-rw-r--r--src/main/java/makamys/lodmod/mixin/MixinTessellator.java161
-rw-r--r--src/main/java/makamys/lodmod/mixin/MixinWorldRenderer.java117
-rw-r--r--src/main/java/makamys/lodmod/renderer/ChunkMesh.java352
-rw-r--r--src/main/java/makamys/lodmod/renderer/FarChunkCache.java12
-rw-r--r--src/main/java/makamys/lodmod/renderer/FarWorldRenderer.java14
-rw-r--r--src/main/java/makamys/lodmod/renderer/LODChunk.java67
-rw-r--r--src/main/java/makamys/lodmod/renderer/LODRegion.java62
-rw-r--r--src/main/java/makamys/lodmod/renderer/Mesh.java15
-rw-r--r--src/main/java/makamys/lodmod/renderer/MeshQuad.java424
-rw-r--r--src/main/java/makamys/lodmod/renderer/MyRenderer.java748
-rw-r--r--src/main/java/makamys/lodmod/renderer/SimpleChunkMesh.java150
-rw-r--r--src/main/java/makamys/lodmod/util/Util.java30
-rw-r--r--src/main/resources/META-INF/LODMod_at.cfg2
-rw-r--r--src/main/resources/lodmod.mixin.json9
-rw-r--r--src/main/resources/shaders/chunk.frag61
-rw-r--r--src/main/resources/shaders/chunk.vert34
24 files changed, 2357 insertions, 30 deletions
diff --git a/src/main/java/makamys/lodmod/LODMod.java b/src/main/java/makamys/lodmod/LODMod.java
index c31a5d7..ab4fb07 100644
--- a/src/main/java/makamys/lodmod/LODMod.java
+++ b/src/main/java/makamys/lodmod/LODMod.java
@@ -24,7 +24,6 @@ import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.gameevent.TickEvent.ClientTickEvent;
-import makamys.lodmod.mixin.IRenderGlobal;
@Mod(modid = LODMod.MODID, version = LODMod.VERSION)
public class LODMod
diff --git a/src/main/java/makamys/lodmod/ducks/ITessellator.java b/src/main/java/makamys/lodmod/ducks/ITessellator.java
new file mode 100644
index 0000000..5a57741
--- /dev/null
+++ b/src/main/java/makamys/lodmod/ducks/ITessellator.java
@@ -0,0 +1,10 @@
+package makamys.lodmod.ducks;
+
+import org.spongepowered.asm.mixin.Mixin;
+
+import makamys.lodmod.renderer.ChunkMesh;
+import net.minecraft.client.renderer.Tessellator;
+
+public interface ITessellator {
+ public ChunkMesh toChunkMesh();
+}
diff --git a/src/main/java/makamys/lodmod/ducks/IWorldRenderer.java b/src/main/java/makamys/lodmod/ducks/IWorldRenderer.java
new file mode 100644
index 0000000..83857d6
--- /dev/null
+++ b/src/main/java/makamys/lodmod/ducks/IWorldRenderer.java
@@ -0,0 +1,12 @@
+package makamys.lodmod.ducks;
+
+import java.util.List;
+
+import org.spongepowered.asm.mixin.Mixin;
+
+import makamys.lodmod.renderer.ChunkMesh;
+import net.minecraft.client.renderer.WorldRenderer;
+
+public interface IWorldRenderer {
+ public List<ChunkMesh> getChunkMeshes();
+}
diff --git a/src/main/java/makamys/lodmod/mixin/IRenderGlobal.java b/src/main/java/makamys/lodmod/mixin/IRenderGlobal.java
deleted file mode 100644
index d5ac050..0000000
--- a/src/main/java/makamys/lodmod/mixin/IRenderGlobal.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package makamys.lodmod.mixin;
-
-import java.nio.IntBuffer;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.gen.Accessor;
-
-import net.minecraft.client.Minecraft;
-import net.minecraft.client.multiplayer.WorldClient;
-import net.minecraft.client.renderer.RenderBlocks;
-import net.minecraft.client.renderer.RenderGlobal;
-import net.minecraft.client.renderer.WorldRenderer;
-import net.minecraft.client.renderer.texture.TextureManager;
-import net.minecraft.entity.Entity;
-import net.minecraft.util.ResourceLocation;
-import net.minecraft.world.IWorldAccess;
-
-@Mixin(RenderGlobal.class)
-public interface IRenderGlobal {
- @Accessor("cloudTickCounter")
- int cloudTickCounter();
-} \ No newline at end of file
diff --git a/src/main/java/makamys/lodmod/mixin/MixinChunkCache.java b/src/main/java/makamys/lodmod/mixin/MixinChunkCache.java
new file mode 100644
index 0000000..bbfcca1
--- /dev/null
+++ b/src/main/java/makamys/lodmod/mixin/MixinChunkCache.java
@@ -0,0 +1,28 @@
+package makamys.lodmod.mixin;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+import makamys.lodmod.renderer.FarChunkCache;
+import makamys.lodmod.renderer.MyRenderer;
+import net.minecraft.world.ChunkCache;
+import net.minecraft.world.World;
+import net.minecraft.world.chunk.Chunk;
+
+@Mixin(ChunkCache.class)
+abstract class MixinChunkCache {
+
+ @Redirect(method = "<init>*", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;getChunkFromChunkCoords(II)Lnet/minecraft/world/chunk/Chunk;"))
+ private Chunk redirectGetChunkFromChunkCoords(World world, int p1, int p2) {
+ Chunk chunk = world.getChunkFromChunkCoords(p1, p2);
+ if(FarChunkCache.class.isInstance(this.getClass()) && chunk.isEmpty()) {
+ Chunk myChunk = MyRenderer.getChunkFromChunkCoords(p1, p2);
+ if(myChunk != null) {
+ chunk = myChunk;
+ }
+ }
+ return chunk;
+ }
+
+}
diff --git a/src/main/java/makamys/lodmod/mixin/MixinEntityRenderer.java b/src/main/java/makamys/lodmod/mixin/MixinEntityRenderer.java
index 933b577..544d131 100644
--- a/src/main/java/makamys/lodmod/mixin/MixinEntityRenderer.java
+++ b/src/main/java/makamys/lodmod/mixin/MixinEntityRenderer.java
@@ -8,6 +8,7 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import makamys.lodmod.renderer.MyRenderer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.EntityRenderer;
import net.minecraft.entity.EntityLivingBase;
@@ -27,7 +28,7 @@ abstract class MixinEntityRenderer {
@Inject(method = "renderWorld", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glAlphaFunc(IF)V", shift = At.Shift.AFTER, ordinal = 1))
private void afterSortAndRender(float alpha, long something, CallbackInfo ci) {
Minecraft.getMinecraft().entityRenderer.enableLightmap((double)alpha);
- //MyRenderer.beforeRenderTerrain();
+ MyRenderer.beforeRenderTerrain();
Minecraft.getMinecraft().entityRenderer.disableLightmap((double)alpha);
}
diff --git a/src/main/java/makamys/lodmod/mixin/MixinMinecraftServer.java b/src/main/java/makamys/lodmod/mixin/MixinMinecraftServer.java
new file mode 100644
index 0000000..747cbb8
--- /dev/null
+++ b/src/main/java/makamys/lodmod/mixin/MixinMinecraftServer.java
@@ -0,0 +1,30 @@
+package makamys.lodmod.mixin;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import makamys.lodmod.renderer.MyRenderer;
+import net.minecraft.server.MinecraftServer;
+
+@Mixin(MinecraftServer.class)
+abstract class MixinMinecraftServer {
+
+ @Shadow
+ boolean worldIsBeingDeleted;
+
+ @Inject(method = "stopServer", at = @At("HEAD"))
+ public void stopServer(CallbackInfo ci) {
+ if(!worldIsBeingDeleted) {
+ MyRenderer.onStopServer();
+ }
+ }
+
+ @Inject(method = "updateTimeLightAndEntities", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/NetworkSystem;networkTick()V"))
+ public void preServerTick(CallbackInfo ci) {
+ MyRenderer.serverTick();
+ }
+
+}
diff --git a/src/main/java/makamys/lodmod/mixin/MixinRenderGlobal.java b/src/main/java/makamys/lodmod/mixin/MixinRenderGlobal.java
new file mode 100644
index 0000000..f2b89fd
--- /dev/null
+++ b/src/main/java/makamys/lodmod/mixin/MixinRenderGlobal.java
@@ -0,0 +1,19 @@
+package makamys.lodmod.mixin;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+
+import makamys.lodmod.renderer.MyRenderer;
+import net.minecraft.client.renderer.RenderGlobal;
+
+@Mixin(RenderGlobal.class)
+abstract class MixinRenderGlobal {
+
+ @Redirect(method = "renderSortedRenderers", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/RenderGlobal;renderAllRenderLists(ID)V"))
+ private void redirectRenderAllRenderLists(RenderGlobal thiz, int p1, double p2) {
+ if(MyRenderer.renderWorld) {
+ thiz.renderAllRenderLists(p1, p2);
+ }
+ }
+}
diff --git a/src/main/java/makamys/lodmod/mixin/MixinTessellator.java b/src/main/java/makamys/lodmod/mixin/MixinTessellator.java
new file mode 100644
index 0000000..544190d
--- /dev/null
+++ b/src/main/java/makamys/lodmod/mixin/MixinTessellator.java
@@ -0,0 +1,161 @@
+package makamys.lodmod.mixin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+
+import makamys.lodmod.ducks.ITessellator;
+import makamys.lodmod.renderer.ChunkMesh;
+import makamys.lodmod.renderer.MeshQuad;
+import makamys.lodmod.renderer.MeshQuad.QuadPlaneComparator;
+import net.minecraft.client.renderer.Tessellator;
+
+@Mixin(Tessellator.class)
+abstract class MixinTessellator implements ITessellator {
+
+ @Shadow
+ private int vertexCount;
+
+ @Shadow
+ private int[] rawBuffer;
+
+ @Shadow
+ private double xOffset;
+ @Shadow
+ private double yOffset;
+ @Shadow
+ private double zOffset;
+
+ @Shadow
+ private boolean hasTexture;
+ @Shadow
+ private boolean hasBrightness;
+ @Shadow
+ private boolean hasColor;
+ @Shadow
+ private boolean hasNormals;
+
+ private static int totalOriginalQuadCount = 0;
+ private static int totalSimplifiedQuadCount = 0;
+ /*
+ public static void endSave() {
+ if(out == null) {
+ return;
+ }
+ try {
+ out.close();
+ String nameList = String.join("\n", ((TextureMap)Minecraft.getMinecraft().getTextureManager().getTexture(TextureMap.locationBlocksTexture)).mapUploadedSprites.keySet());
+ Files.write(nameList, new File("tessellator_strings.txt"), Charset.forName("UTF-8"));
+ out = null;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }*/
+
+ public ChunkMesh toChunkMesh() {
+ if(this.vertexCount % 4 != 0) {
+ System.out.println("Error: Vertex count is not a multiple of 4");
+ return null;
+ }
+
+ List<MeshQuad> quads = new ArrayList<>();
+
+ List<Integer> spriteIndexes = new ArrayList<>();
+ List<Byte> xs = new ArrayList<>();
+ List<Byte> ys = new ArrayList<>();
+ List<Byte> zs = new ArrayList<>();
+ List<Byte> relUs = new ArrayList<>();
+ List<Byte> relVs = new ArrayList<>();
+ List<Byte> bUs = new ArrayList<>();
+ List<Byte> bVs = new ArrayList<>();
+ List<Integer> cs = new ArrayList<>();
+
+ for(int quadI = 0; quadI < this.vertexCount / 4; quadI++) {
+ MeshQuad quad = new MeshQuad(rawBuffer, quadI * 32, new ChunkMesh.Flags(hasTexture, hasBrightness, hasColor, hasNormals));
+ if(quad.bUs[0] == quad.bUs[1] && quad.bUs[1] == quad.bUs[2] && quad.bUs[2] == quad.bUs[3] && quad.bUs[3] == quad.bVs[0] && quad.bVs[0] == quad.bVs[1] && quad.bVs[1] == quad.bVs[2] && quad.bVs[2] == quad.bVs[3] && quad.bVs[3] == 0) {
+ quad.deleted = true;
+ }
+ if(quad.plane == quad.PLANE_XZ && !quad.isClockwiseXZ()) {
+ // water hack
+ quad.deleted = true;
+ }
+ quads.add(quad);
+ }
+ boolean optimize = true;
+ if(optimize) {
+ ArrayList<ArrayList<MeshQuad>> quadsByPlaneDir = new ArrayList<>(); // XY, XZ, YZ
+ for(int i = 0; i < 3; i++) {
+ quadsByPlaneDir.add(new ArrayList<MeshQuad>());
+ }
+ for(MeshQuad quad : quads) {
+ if(quad.plane != MeshQuad.PLANE_NONE) {
+ quadsByPlaneDir.get(quad.plane).add(quad);
+ }
+ }
+ for(int plane = 0; plane < 3; plane++) {
+ quadsByPlaneDir.get(plane).sort(MeshQuad.QuadPlaneComparator.quadPlaneComparators[plane]);
+ }
+
+ for(int plane = 0; plane < 3; plane++) {
+ List<MeshQuad> planeDirQuads = quadsByPlaneDir.get(plane);
+ int planeStart = 0;
+ for(int quadI = 0; quadI < planeDirQuads.size(); quadI++) {
+ MeshQuad quad = planeDirQuads.get(quadI);
+ MeshQuad nextQuad = quadI == planeDirQuads.size() - 1 ? null : planeDirQuads.get(quadI + 1);
+ if(!quad.onSamePlaneAs(nextQuad)) {
+ simplifyPlane(planeDirQuads.subList(planeStart, quadI));
+ planeStart = quadI + 1;
+ }
+ }
+ }
+ }
+
+ int quadCount = countValidQuads(quads);
+
+ totalOriginalQuadCount += quads.size();
+ totalSimplifiedQuadCount += quadCount;
+ //System.out.println("simplified quads " + totalOriginalQuadCount + " -> " + totalSimplifiedQuadCount + " (ratio: " + ((float)totalSimplifiedQuadCount / (float)totalOriginalQuadCount) + ") totalMergeCountByPlane: " + Arrays.toString(totalMergeCountByPlane));
+
+ if(quadCount > 0) {
+ return new ChunkMesh(
+ (int)(-xOffset / 16), (int)(-yOffset / 16), (int)(-zOffset / 16),
+ new ChunkMesh.Flags(hasTexture, hasBrightness, hasColor, hasNormals),
+ quadCount, quads);
+ } else {
+ return null;
+ }
+ }
+
+ private void simplifyPlane(List<MeshQuad> planeQuads) {
+ MeshQuad lastQuad = null;
+ // Pass 1: merge quads to create rows
+ for(MeshQuad quad : planeQuads) {
+ if(lastQuad != null) {
+ lastQuad.tryToMerge(quad);
+ }
+ if(quad.isValid(quad)) {
+ lastQuad = quad;
+ }
+ }
+
+ // Pass 2: merge rows to create rectangles
+ // TODO optimize?
+ for(int i = 0; i < planeQuads.size(); i++) {
+ for(int j = i + 1; j < planeQuads.size(); j++) {
+ planeQuads.get(i).tryToMerge(planeQuads.get(j));
+ }
+ }
+ }
+
+ private int countValidQuads(List<MeshQuad> quads) {
+ int quadCount = 0;
+ for(MeshQuad quad : quads) {
+ if(!quad.deleted) {
+ quadCount++;
+ }
+ }
+ return quadCount;
+ }
+}
diff --git a/src/main/java/makamys/lodmod/mixin/MixinWorldRenderer.java b/src/main/java/makamys/lodmod/mixin/MixinWorldRenderer.java
new file mode 100644
index 0000000..abfcc4e
--- /dev/null
+++ b/src/main/java/makamys/lodmod/mixin/MixinWorldRenderer.java
@@ -0,0 +1,117 @@
+package makamys.lodmod.mixin;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.lwjgl.opengl.GL11;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.Redirect;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import makamys.lodmod.ducks.ITessellator;
+import makamys.lodmod.ducks.IWorldRenderer;
+import makamys.lodmod.renderer.ChunkMesh;
+import makamys.lodmod.renderer.FarChunkCache;
+import makamys.lodmod.renderer.FarWorldRenderer;
+import makamys.lodmod.renderer.MyRenderer;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.WorldRenderer;
+import net.minecraft.client.renderer.entity.RenderItem;
+import net.minecraft.util.AxisAlignedBB;
+import net.minecraft.world.ChunkCache;
+import net.minecraft.world.World;
+
+@Mixin(WorldRenderer.class)
+abstract class MixinWorldRenderer implements IWorldRenderer {
+
+ @Shadow
+ public int posX;
+ @Shadow
+ public int posY;
+ @Shadow
+ public int posZ;
+
+ public List<ChunkMesh> chunkMeshes = new ArrayList<>();
+
+ @Redirect(method = "setPosition", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/Render;renderAABB(Lnet/minecraft/util/AxisAlignedBB;)V"))
+ private void redirectRenderAABB(AxisAlignedBB p1) {
+ if(!FarWorldRenderer.class.isInstance(this.getClass())) {
+ RenderItem.renderAABB(p1);
+ }
+ }
+
+ @Redirect(method = "updateRenderer", at = @At(value = "NEW", target = "Lnet/minecraft/world/ChunkCache;"))
+ private ChunkCache redirectConstructChunkCache(World p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8) {
+ if(!FarWorldRenderer.class.isInstance(this.getClass())) {
+ return new ChunkCache(p1, p2, p3, p4, p5, p6, p7, p8);
+ } else {
+ return new FarChunkCache(p1, p2, p3, p4, p5, p6, p7, p8);
+ }
+ }
+
+ @Inject(method = "updateRenderer", at = @At(value = "HEAD"))
+ private void preUpdateRenderer(CallbackInfo ci) {
+ chunkMeshes.clear();
+ }
+
+ @Inject(method = "updateRenderer", at = @At(value = "TAIL"))
+ private void postUpdateRenderer(CallbackInfo ci) {
+ MyRenderer.onWorldRendererPost(WorldRenderer.class.cast(this));
+ chunkMeshes.clear();
+ }
+
+ @Inject(method = "postRenderBlocks", at = @At(value = "HEAD"))
+ private void prePostRenderBlocks(CallbackInfo ci) {
+ chunkMeshes.add(((ITessellator)Tessellator.instance).toChunkMesh());
+ }
+
+ @Redirect(method = "postRenderBlocks", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;draw()I"))
+ private int redirectPostRenderBlocksDraw() {
+ if(!FarWorldRenderer.class.isInstance(this.getClass())) {
+ return Tessellator.instance.draw();
+ } else {
+ Tessellator.instance.reset();
+ return 0;
+ }
+ }
+
+ // There's probably a nicer way to do this
+
+ @Redirect(method = "postRenderBlocks", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glPopMatrix()V"))
+ private void redirectPostRenderBlocksGL1() {
+ if(!FarWorldRenderer.class.isInstance(this.getClass())) {
+ GL11.glPopMatrix();
+ }
+ }
+
+ @Redirect(method = "postRenderBlocks", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glEndList()V"))
+ private void redirectPostRenderBlocksGL2() {
+ if(!FarWorldRenderer.class.isInstance(this.getClass())) {
+ GL11.glEndList();
+ }
+ }
+
+ // XXX this is inconsistent, Forge callbacks are preserved in postRenderBlocks but not preRenderBlocks
+
+ @Inject(method = "preRenderBlocks", at = @At(value = "HEAD"))
+ private void preRenderBlocksInjector(CallbackInfo ci) {
+ if(FarWorldRenderer.class.isInstance(this.getClass())) {
+ Tessellator.instance.setTranslation((double)(-this.posX), (double)(-this.posY), (double)(-this.posZ));
+ ci.cancel();
+ }
+ }
+
+ @Inject(method = "setDontDraw", at = @At(value = "HEAD"))
+ private void preSetDontDraw(CallbackInfo ci) {
+ MyRenderer.onDontDraw(WorldRenderer.class.cast(this));
+ }
+
+ @Override
+ public List<ChunkMesh> getChunkMeshes() {
+ return chunkMeshes;
+ }
+}
diff --git a/src/main/java/makamys/lodmod/renderer/ChunkMesh.java b/src/main/java/makamys/lodmod/renderer/ChunkMesh.java
new file mode 100644
index 0000000..4175b9f
--- /dev/null
+++ b/src/main/java/makamys/lodmod/renderer/ChunkMesh.java
@@ -0,0 +1,352 @@
+package makamys.lodmod.renderer;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.lwjgl.BufferUtils;
+
+import makamys.lodmod.ducks.IWorldRenderer;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.Tessellator;
+import net.minecraft.client.renderer.WorldRenderer;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.client.renderer.texture.TextureMap;
+import net.minecraft.tileentity.TileEntity;
+
+public class ChunkMesh extends Mesh {
+
+ int x;
+ int y;
+ int z;
+ Flags flags;
+
+ private ChunkMesh(int x, int y, int z, Flags flags, int quadCount, byte[] data, List<String> stringTable) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.flags = flags;
+ this.quadCount = quadCount;
+
+ this.buffer = createBuffer(data, stringTable);
+ }
+
+ public ChunkMesh(int x, int y, int z, Flags flags, int quadCount, List<MeshQuad> quads) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.flags = flags;
+ this.quadCount = quadCount;
+
+ this.buffer = createBuffer(quads);
+ }
+
+ private ByteBuffer createBuffer(List<MeshQuad> quads) {
+ if(!(flags.hasTexture && flags.hasColor && flags.hasBrightness && !flags.hasNormals)) {
+ // for simplicity's sake we just assume this setup
+ System.out.println("invalid mesh properties, expected a chunk");
+ return null;
+ }
+
+ ByteBuffer buffer = BufferUtils.createByteBuffer(quadCount * 6 * getStride());
+ FloatBuffer floatBuffer = buffer.asFloatBuffer();
+ ShortBuffer shortBuffer = buffer.asShortBuffer();
+ IntBuffer intBuffer = buffer.asIntBuffer();
+
+ try {
+ for(MeshQuad quad : quads) {
+ if(quad.deleted) {
+ continue;
+ }
+ String spriteName = quad.spriteName;
+
+ TextureAtlasSprite tas = ((TextureMap)Minecraft.getMinecraft().getTextureManager().getTexture(TextureMap.locationBlocksTexture)).getAtlasSprite(spriteName);
+
+ for (int vertexNum = 0; vertexNum < 6; vertexNum++) {
+ int vi = new int[]{0, 1, 3, 1, 2, 3}[vertexNum];
+ int simpleX = quad.xs[vi];
+ if(simpleX == 255) simpleX = 256;
+ int simpleY = quad.ys[vi];
+ if(simpleY == 255) simpleY = 256;
+ int simpleZ = quad.zs[vi];
+ if(simpleZ == 255) simpleZ = 256;
+ floatBuffer.put(x * 16 + simpleX / 16f); // x
+ floatBuffer.put(y * 16 + simpleY / 16f); // y
+ floatBuffer.put(z * 16 + simpleZ / 16f); // z
+
+ int relU = quad.relUs[vi];
+ int relV = quad.relVs[vi];
+
+ floatBuffer.put(tas.getMinU() + (tas.getMaxU() - tas.getMinU()) * (relU / 16f)); // u
+ floatBuffer.put(tas.getMinV() + (tas.getMaxV() - tas.getMinV()) * (relV / 16f)); // v
+
+ shortBuffer.position(floatBuffer.position() * 2);
+
+ shortBuffer.put((short)quad.bUs[vi]); // bU
+ shortBuffer.put((short)quad.bVs[vi]); // bV
+
+ intBuffer.position(shortBuffer.position() / 2);
+
+ intBuffer.put(quad.cs[vi]); // c
+
+ floatBuffer.position(intBuffer.position());
+ }
+ }
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+
+ buffer.position(floatBuffer.position() * 4);
+ buffer.flip();
+
+ return buffer;
+ }
+
+ private ByteBuffer createBuffer(byte[] data, List<String> stringTable) {
+ if(!(flags.hasTexture && flags.hasColor && flags.hasBrightness && !flags.hasNormals)) {
+ // for simplicity's sake we just assume this setup
+ System.out.println("invalid mesh properties, expected a chunk");
+ return null;
+ }
+ int coordsOffset = quadCount * 2;
+ int textureOffset = quadCount * (2 + 4 + 4 + 4);
+ int brightnessOffset = quadCount * (2 + 4 + 4 + 4 + 4 + 4);
+ int colorOffset = quadCount * (2 + 4 + 4 + 4 + 4 + 4 + 4 + 4);
+
+ DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
+
+ ByteBuffer buffer = BufferUtils.createByteBuffer(quadCount * 6 * getStride());
+ FloatBuffer floatBuffer = buffer.asFloatBuffer();
+ ShortBuffer shortBuffer = buffer.asShortBuffer();
+ IntBuffer intBuffer = buffer.asIntBuffer();
+
+ try {
+ for(int quadI = 0; quadI < quadCount; quadI++) {
+ short spriteIndex = readShortAt(in, quadI * 2);
+ String spriteName = stringTable.get(spriteIndex);
+
+ TextureAtlasSprite tas = ((TextureMap)Minecraft.getMinecraft().getTextureManager().getTexture(TextureMap.locationBlocksTexture)).getAtlasSprite(spriteName);
+
+ for (int vertexNum = 0; vertexNum < 6; vertexNum++) {
+ int vi = new int[]{0, 1, 3, 1, 2, 3}[vertexNum];
+ int vertexI = 4 * quadI + vi;
+ int offset = vertexI * getStride();
+ int simpleX = Byte.toUnsignedInt(data[coordsOffset + 0 * 4 * quadCount + 4 * quadI + vi]);
+ if(simpleX == 255) simpleX = 256;
+ int simpleY = Byte.toUnsignedInt(data[coordsOffset + 1 * 4 * quadCount + 4 * quadI + vi]);
+ if(simpleY == 255) simpleY = 256;
+ int simpleZ = Byte.toUnsignedInt(data[coordsOffset + 2 * 4 * quadCount + 4 * quadI + vi]);
+ if(simpleZ == 255) simpleZ = 256;
+ floatBuffer.put(x * 16 + simpleX / 16f); // x
+ floatBuffer.put(y * 16 + simpleY / 16f); // y
+ floatBuffer.put(z * 16 + simpleZ / 16f); // z
+
+ byte relU = data[textureOffset + 0 * 4 * quadCount + 4 * quadI + vi];
+ byte relV = data[textureOffset + 1 * 4 * quadCount + 4 * quadI + vi];
+
+ floatBuffer.put(tas.getMinU() + (tas.getMaxU() - tas.getMinU()) * (relU / 16f)); // u
+ floatBuffer.put(tas.getMinV() + (tas.getMaxV() - tas.getMinV()) * (relV / 16f)); // v
+
+ shortBuffer.position(floatBuffer.position() * 2);
+
+ shortBuffer.put((short)Byte.toUnsignedInt(data[brightnessOffset + 0 * 4 * quadCount + 4 * quadI + vi])); // bU
+ shortBuffer.put((short)Byte.toUnsignedInt(data[brightnessOffset + 1 * 4 * quadCount + 4 * quadI + vi])); // bV
+
+ intBuffer.position(shortBuffer.position() / 2);
+
+ intBuffer.put(readIntAt(in, colorOffset + 4 * 4 * quadI + 4 * vi)); // c
+
+ floatBuffer.position(intBuffer.position());
+ }
+ }
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+
+ buffer.position(floatBuffer.position() * 4);
+ buffer.flip();
+
+ return buffer;
+ }
+
+ // Java is weird.
+ public static short readShortAt(DataInputStream in, int offset) {
+ try {
+ in.reset();
+ in.skip(offset);
+ return in.readShort();
+ } catch(IOException e) {
+ return -1;
+ }
+ }
+
+ public static int readIntAt(DataInputStream in, int offset) {
+ try {
+ in.reset();
+ in.skip(offset);
+ return in.readInt();
+ } catch(IOException e) {
+ return -1;
+ }
+ }
+
+ public int getStride() {
+ return (3 * 4 + (flags.hasTexture ? 8 : 0) + (flags.hasBrightness ? 4 : 0) + (flags.hasColor ? 4 : 0) + (flags.hasNormals ? 4 : 0));
+ }
+
+ static ChunkMesh loadChunkMesh(int x, int y, int z) {
+ try(DataInputStream in = new DataInputStream(new FileInputStream("tessellator_dump.dat"))){
+ List<String> stringTable = Files.readAllLines(Paths.get("tessellator_strings.txt"));
+
+ while(true) {
+ int xOffset = in.readInt();
+ int yOffset = in.readInt();
+ int zOffset = in.readInt();
+ Flags flags = new Flags(in.readByte());
+
+ int quadSize = 2 + 4 * (3 + (flags.hasTexture ? 2 : 0) + (flags.hasBrightness ? 2 : 0)
+ + (flags.hasColor ? 4 : 0) + (flags.hasNormals ? 4 : 0));
+
+
+ int quadCount = in.readInt();
+
+ if(xOffset == x && yOffset == y && zOffset == z) {
+ byte[] data = new byte[quadSize * quadCount];
+ in.read(data, 0, data.length);
+
+ return new ChunkMesh(x, y, z, flags, quadCount, data, stringTable);
+ } else {
+ in.skip(quadSize * quadCount);
+ }
+ }
+ } catch(EOFException eof) {
+ System.out.println("end??");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ // TODO don't repeat yourself
+ static List<ChunkMesh> loadAll(){
+ List<ChunkMesh> list = new ArrayList<ChunkMesh>();
+ try(DataInputStream in = new DataInputStream(new FileInputStream("tessellator_dump.dat"))){
+ List<String> stringTable = Files.readAllLines(Paths.get("tessellator_strings.txt"));
+
+ while(true) {
+ int xOffset = in.readInt();
+ int yOffset = in.readInt();
+ int zOffset = in.readInt();
+ Flags flags = new Flags(in.readByte());
+
+ int quadSize = 2 + 4 * (3 + (flags.hasTexture ? 2 : 0) + (flags.hasBrightness ? 2 : 0)
+ + (flags.hasColor ? 4 : 0) + (flags.hasNormals ? 4 : 0));
+
+
+ int quadCount = in.readInt();
+
+ if(quadCount > 0) {
+ byte[] data = new byte[quadSize * quadCount];
+ in.read(data, 0, data.length);
+
+ list.add(new ChunkMesh(xOffset, yOffset, zOffset, flags, quadCount, data, stringTable));
+ }
+ }
+ } catch(EOFException eof) {
+ System.out.println("end??");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return list;
+ }
+
+ static void saveChunks(List<Integer> coords) {
+ System.out.println("saving " + (coords.size() / 3) + " cchunks");
+ for(int i = 0; i < coords.size(); i += 3) {
+ if(i % 300 == 0) {
+ System.out.println((i / 3) + " / " + (coords.size() / 3));
+ }
+ int theX = coords.get(i);
+ int theY = coords.get(i + 1);
+ int theZ = coords.get(i + 2);
+
+ WorldRenderer wr = new WorldRenderer(Minecraft.getMinecraft().theWorld, new ArrayList<TileEntity>(), theX * 16, theY * 16, theZ * 16, 100000);
+ /*
+ if (this.occlusionEnabled)
+ {
+ this.worldRenderers[(var6 * this.renderChunksTall + var5) * this.renderChunksWide + var4].glOcclusionQuery = this.glOcclusionQueryBase.get(var3);
+ }*/
+
+ wr.isWaitingOnOcclusionQuery = false;
+ wr.isVisible = true;
+ wr.isInFrustum = true;
+ wr.chunkIndex = 0;
+ wr.markDirty();
+ wr.updateRenderer(Minecraft.getMinecraft().thePlayer);
+ }
+ //Tessellator.endSave();
+ }
+
+ static List<ChunkMesh> getChunkMesh(int theX, int theY, int theZ) {
+ WorldRenderer wr = new WorldRenderer(Minecraft.getMinecraft().theWorld, new ArrayList<TileEntity>(), theX * 16, theY * 16, theZ * 16, 100000);
+
+ wr.isWaitingOnOcclusionQuery = false;
+ wr.isVisible = true;
+ wr.isInFrustum = true;
+ wr.chunkIndex = 0;
+ wr.markDirty();
+ wr.updateRenderer(Minecraft.getMinecraft().thePlayer);
+ return ((IWorldRenderer)wr).getChunkMeshes();
+ }
+
+ public static class Flags {
+ boolean hasTexture;
+ boolean hasBrightness;
+ boolean hasColor;
+ boolean hasNormals;
+
+ public Flags(byte flags) {
+ hasTexture = (flags & 1) != 0;
+ hasBrightness = (flags & 2) != 0;
+ hasColor = (flags & 4) != 0;
+ hasNormals = (flags & 8) != 0;
+ }
+
+ public Flags(boolean hasTexture, boolean hasBrightness, boolean hasColor, boolean hasNormals) {
+ this.hasTexture = hasTexture;
+ this.hasBrightness = hasBrightness;
+ this.hasColor = hasColor;
+ this.hasNormals = hasNormals;
+ }
+
+ public byte toByte() {
+ byte flags = 0;
+ if(hasTexture) {
+ flags |= 1;
+ }
+ if(hasBrightness) {
+ flags |= 2;
+ }
+ if(hasColor) {
+ flags |= 4;
+ }
+ if(hasNormals) {
+ flags |= 8;
+ }
+ return flags;
+ }
+ }
+
+}
+
diff --git a/src/main/java/makamys/lodmod/renderer/FarChunkCache.java b/src/main/java/makamys/lodmod/renderer/FarChunkCache.java
new file mode 100644
index 0000000..f4e3cca
--- /dev/null
+++ b/src/main/java/makamys/lodmod/renderer/FarChunkCache.java
@@ -0,0 +1,12 @@
+package makamys.lodmod.renderer;
+
+import net.minecraft.world.ChunkCache;
+import net.minecraft.world.World;
+
+public class FarChunkCache extends ChunkCache {
+
+ public FarChunkCache(World p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8) {
+ super(p1, p2, p3, p4, p5, p6, p7, p8);
+ }
+
+}
diff --git a/src/main/java/makamys/lodmod/renderer/FarWorldRenderer.java b/src/main/java/makamys/lodmod/renderer/FarWorldRenderer.java
new file mode 100644
index 0000000..72eb4fd
--- /dev/null
+++ b/src/main/java/makamys/lodmod/renderer/FarWorldRenderer.java
@@ -0,0 +1,14 @@
+package makamys.lodmod.renderer;
+
+import java.util.List;
+
+import net.minecraft.client.renderer.WorldRenderer;
+import net.minecraft.world.World;
+
+public class FarWorldRenderer extends WorldRenderer {
+
+ public FarWorldRenderer(World p1, List p2, int p3, int p4, int p5, int p6) {
+ super(p1, p2, p3, p4, p5, p6);
+ }
+
+}
diff --git a/src/main/java/makamys/lodmod/renderer/LODChunk.java b/src/main/java/makamys/lodmod/renderer/LODChunk.java
new file mode 100644
index 0000000..bae235c
--- /dev/null
+++ b/src/main/java/makamys/lodmod/renderer/LODChunk.java
@@ -0,0 +1,67 @@
+package makamys.lodmod.renderer;
+
+import java.util.List;
+
+import net.minecraft.entity.Entity;
+import net.minecraft.world.chunk.Chunk;
+
+public class LODChunk {
+
+ int x, z;
+ Chunk chunk;
+ public boolean waitingForData = false;
+ int lod = 0;
+ boolean visible;
+
+ SimpleChunkMesh simpleMesh;
+ ChunkMesh[] chunkMeshes = new ChunkMesh[32];
+
+ public LODChunk(int x, int z) {
+ this.x = x;
+ this.z = z;
+ }
+
+ @Override
+ public String toString() {
+ return "LODChunk(" + x + ", " + z + ")";
+ }
+
+ public double distSq(Entity entity) {
+ return Math.pow(entity.posX - x * 16, 2) + Math.pow(entity.posZ - z * 16, 2);
+ }
+
+ public void putChunkMeshes(int cy, List<ChunkMesh> newChunkMeshes) {
+ for(int i = 0; i < 2; i++) {
+ if(chunkMeshes[cy * 2 + i] != null) {
+ MyRenderer.setMeshVisible(chunkMeshes[cy * 2 + i], false);
+ chunkMeshes[cy * 2 + i] = null;
+ }
+ }
+
+ for(int i = 0; i < newChunkMeshes.size(); i++) {
+ chunkMeshes[cy * 2 + i] = newChunkMeshes.get(i);
+ MyRenderer.sendMeshToGPU(newChunkMeshes.get(i));
+ }
+ }
+
+ public boolean hasChunkMeshes() {
+ for(ChunkMesh cm : chunkMeshes) {
+ if(cm != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void tick(Entity player) {
+ double distSq = distSq(player);
+ if(distSq < Math.pow(32 * 16, 2)) {
+ MyRenderer.setLOD(this, 2);
+ } else if(distSq < Math.pow(64 * 16, 2)) {
+ MyRenderer.setLOD(this, 1);
+ } else {
+ MyRenderer.setVisible(this, false);
+ }
+ }
+
+}
diff --git a/src/main/java/makamys/lodmod/renderer/LODRegion.java b/src/main/java/makamys/lodmod/renderer/LODRegion.java
new file mode 100644
index 0000000..695202d
--- /dev/null
+++ b/src/main/java/makamys/lodmod/renderer/LODRegion.java
@@ -0,0 +1,62 @@
+package makamys.lodmod.renderer;
+
+import net.minecraft.entity.Entity;
+import net.minecraft.world.chunk.Chunk;
+
+public class LODRegion {
+
+ private LODChunk[][] data = new LODChunk[32][32];
+
+ int regionX, regionZ;
+
+ public LODRegion(int regionX, int regionZ) {
+ this.regionX = regionX;
+ this.regionZ = regionZ;
+
+ for(int i = 0; i < 32; i++) {
+ for(int j = 0; j < 32; j++) {
+ data[i][j] = new LODChunk(regionX * 32 + i, regionZ * 32 + j);
+ }
+ }
+ }
+
+ public static LODRegion load(int regionX, int regionZ) {
+ return new LODRegion(regionX, regionZ); // TODO
+ }
+
+ public LODChunk getChunkAbsolute(int chunkXAbs, int chunkZAbs) {
+ return getChunk(chunkXAbs - regionX * 32, chunkZAbs - regionZ * 32);
+ }
+
+ public LODChunk getChunk(int x, int z) {
+ if(x >= 0 && x < 32 && z >= 0 && z < 32) {
+ return data[x][z];
+ } else {
+ return null;
+ }
+ }
+
+ public LODChunk putChunk(Chunk chunk) {
+ int relX = chunk.xPosition - regionX * 32;
+ int relZ = chunk.zPosition - regionZ * 32;
+
+ if(relX >= 0 && relX < 32 && relZ >= 0 && relZ < 32) {
+ data[relX][relZ].chunk = chunk;
+ data[relX][relZ].waitingForData = false;
+ return data[relX][relZ];
+ }
+ return null;
+ }
+
+ public void tick(Entity player) {
+ for(int i = 0; i < 32; i++) {
+ for(int j = 0; j < 32; j++) {
+ LODChunk chunk = data[i][j];
+ if(chunk != null) {
+ chunk.tick(player);
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/makamys/lodmod/renderer/Mesh.java b/src/main/java/makamys/lodmod/renderer/Mesh.java
new file mode 100644
index 0000000..be379f6
--- /dev/null
+++ b/src/main/java/makamys/lodmod/renderer/Mesh.java
@@ -0,0 +1,15 @@
+package makamys.lodmod.renderer;
+
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+
+public abstract class Mesh {
+
+ public ByteBuffer buffer;
+ public int quadCount;
+ public boolean visible;
+ public int iFirst, iCount;
+
+ public abstract int getStride();
+
+}
diff --git a/src/main/java/makamys/lodmod/renderer/MeshQuad.java b/src/main/java/makamys/lodmod/renderer/MeshQuad.java
new file mode 100644
index 0000000..a48815d
--- /dev/null
+++ b/src/main/java/makamys/lodmod/renderer/MeshQuad.java
@@ -0,0 +1,424 @@
+package makamys.lodmod.renderer;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.Locale;
+import java.util.Map;
+
+import makamys.lodmod.renderer.MeshQuad.QuadPlaneComparator;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.client.renderer.texture.TextureMap;
+import net.minecraft.util.EnumFacing;
+
+public class MeshQuad {
+ public int spriteIndex;
+ public String spriteName;
+ public int[] xs = new int[4];
+ public int[] ys = new int[4];
+ public int[] zs = new int[4];
+ public int minX = Integer.MAX_VALUE;
+ public int minY = Integer.MAX_VALUE;
+ public int minZ = Integer.MAX_VALUE;
+ public int maxX = Integer.MIN_VALUE;
+ public int maxY = Integer.MIN_VALUE;
+ public int maxZ = Integer.MIN_VALUE;
+ public int[] relUs = new int[4];
+ public int[] relVs = new int[4];
+ public int[] bUs = new int[4];
+ public int[] bVs = new int[4];
+ public int[] cs = new int[4];
+ public int[] normals = new int[4];
+ public boolean deleted;
+ public boolean isFullQuad;
+
+ public static final int PLANE_NONE = -1, PLANE_XY = 0, PLANE_XZ = 1, PLANE_YZ = 2;
+ public int plane = PLANE_NONE;
+ public int offset;
+ public ChunkMesh.Flags flags;
+
+ public static int[] totalMergeCountByPlane = new int[3];
+
+ private int minPositive(int a, int b) {
+ if(a == -1) {
+ return b;
+ } else {
+ return a < b ? a : b;
+ }
+ }
+ private int maxPositive(int a, int b) {
+ if(a == -1) {
+ return b;
+ } else {
+ return a > b ? a : b;
+ }
+ }
+
+ public MeshQuad(int[] rawBuffer, int offset, ChunkMesh.Flags flags) {
+ this.offset = offset;
+ this.flags = flags;
+ int i = offset;
+ float[] us = new float[4];
+ float uSum = 0;
+ float[] vs = new float[4];
+ float vSum = 0;
+ for(int vertexI = 0; vertexI < 4; vertexI++) {
+ float u = Float.intBitsToFloat(rawBuffer[vertexI * 8 + i + 3]);
+ float v = Float.intBitsToFloat(rawBuffer[vertexI * 8 + i + 4]);
+
+ us[vertexI] = u;
+ vs[vertexI] = v;
+
+ uSum += u;
+ vSum += v;
+ }
+
+ float avgU = uSum / 4f;
+ float avgV = vSum / 4f;
+
+ TextureAtlasSprite sprite = null;
+ Map<String, TextureAtlasSprite> uploadedSprites = ((TextureMap)Minecraft.getMinecraft().getTextureManager().getTexture(TextureMap.locationBlocksTexture)).mapUploadedSprites;
+
+ spriteIndex = MyRenderer.getSpriteIndexForUV(avgU, avgV);
+ sprite = MyRenderer.getSprite(spriteIndex);
+
+ if(sprite == null) {
+ System.out.println("Error: couldn't find sprite");
+ } else {
+ spriteName = sprite.getIconName();
+ for(int vertexI = 0; vertexI < 4; vertexI++) {
+ float x = Float.intBitsToFloat(rawBuffer[i + 0]);
+ float y = Float.intBitsToFloat(rawBuffer[i + 1]);
+ float z = Float.intBitsToFloat(rawBuffer[i + 2]);
+
+ int simpleX = (int)(x * 16);
+ //if(simpleX == 256) simpleX = 255;
+ int simpleY = (int)(y * 16);
+ //if(simpleY == 256) simpleY = 255;
+ int simpleZ = (int)(z * 16);
+ //if(simpleZ == 256) simpleZ = 255;
+
+ xs[vertexI] = simpleX;
+ ys[vertexI] = simpleY;
+ zs[vertexI] = simpleZ;
+
+ // hasTexture
+ float u = us[vertexI];
+ float v = vs[vertexI];
+
+ int simpleRelU = (int)((u - sprite.getMinU()) / (sprite.getMaxU() - sprite.getMinU()) * 16);
+ int simpleRelV = (int)((v - sprite.getMinV()) / (sprite.getMaxV() - sprite.getMinV()) * 16);
+ if(flags.hasTexture) {
+ relUs[vertexI] = simpleRelU;
+ relVs[vertexI] = simpleRelV;
+ }
+
+ // hasBrightness
+ int brightness = rawBuffer[i + 7];
+ int brightnessU = brightness & 0xFFFF;
+ int brightnessV = (brightness >> 16) & 0xFFFF;
+ if(flags.hasBrightness) {
+ bUs[vertexI] = (int)brightnessU;
+ bVs[vertexI] = (int)brightnessV;
+ }
+
+ // hasColor
+ int color = rawBuffer[i + 5];
+ if(flags.hasColor) {
+ cs[vertexI] = color;
+ }
+
+ // hasNormals
+ int normal = rawBuffer[i + 6];
+ if(flags.hasNormals) {
+ normals[vertexI] = normal;
+ }
+
+ i += 8;
+ }
+ }
+
+ updateMinMaxXYZ();
+
+ if(ys[0] == ys[1] && ys[1] == ys[2] && ys[2] == ys[3]) {
+ plane = PLANE_XZ;
+ } else if(xs[0] == xs[1] && xs[1] == xs[2] && xs[2] == xs[3]) {
+ plane = PLANE_YZ;
+ } else if(zs[0] == zs[1] && zs[1] == zs[2] && zs[2] == zs[3]) {
+ plane = PLANE_XY;
+ } else {
+ plane = PLANE_NONE;
+ }
+
+ boolean equalToAABB = true;
+ for(int minOrMaxX = 0; minOrMaxX < 2; minOrMaxX++) {
+ for(int minOrMaxY = 0; minOrMaxY < 2; minOrMaxY++) {
+ for(int minOrMaxZ = 0; minOrMaxZ < 2; minOrMaxZ++) {
+ if(getCornerVertex(minOrMaxX == 1, minOrMaxY == 1, minOrMaxZ == 1) == -1) {
+ equalToAABB = false;
+ break;
+ }
+ }
+ }
+ }
+
+ switch(plane) {
+ case PLANE_XY:
+ isFullQuad = equalToAABB && (maxX - minX) == 16 && (maxY - minY) == 16;
+ break;
+ case PLANE_XZ:
+ isFullQuad = equalToAABB && (maxX - minX) == 16 && (maxZ - minZ) == 16;
+ break;
+ case PLANE_YZ:
+ isFullQuad = equalToAABB && (maxY - minY) == 16 && (maxZ - minZ) == 16;
+ break;
+ default:
+ isFullQuad = false;
+ }
+
+ for(int c = 0; c < 3; c++) {
+ if(getMin(c) < 0 || getMax(c) < 0 || getMax(c) - getMin(c) > 16 || getMin(c) > 256 || getMax(c) > 256) {
+ this.deleted = true;
+ // TODO handle weirdness more gracefully
+ }
+ }
+ }
+
+ // yeah this is kinda unoptimal
+ private int getCornerVertex(boolean minOrMaxX, boolean minOrMaxY, boolean minOrMaxZ) {
+ int aabbCornerX = !minOrMaxX ? minX : maxX;
+ int aabbCornerY = !minOrMaxY ? minY : maxY;
+ int aabbCornerZ = !minOrMaxZ ? minZ : maxZ;
+
+ for(int vi = 0; vi < 4; vi++) {
+ if(xs[vi] == aabbCornerX && ys[vi] == aabbCornerY && zs[vi] == aabbCornerZ) {
+ return vi;
+ }
+ }
+ return -1;
+ }
+
+ public void tryToMerge(MeshQuad o) {
+ if(isValid(this) && isValid(o) && plane == o.plane
+ && spriteIndex == o.spriteIndex && isFullQuad && o.isFullQuad) {
+ int numVerticesTouching = 0;
+ for(int i = 0; i < 4; i++) {
+ for(int j = 0; j < 4; j++) {
+ if(xs[i] == o.xs[j] && ys[i] == o.ys[j] && zs[i] == o.zs[j]) {
+ numVerticesTouching++;
+ }
+ }
+ }
+ if(numVerticesTouching == 2) {
+ mergeWithQuad(o);
+
+ totalMergeCountByPlane[plane]++;
+
+ o.deleted = true;
+ }
+ }
+ }
+
+ private void mergeWithQuad(MeshQuad o) {
+ if(minX < o.minX) {
+ copyEdgeFrom(o, EnumFacing.EAST);
+ } else if(minX > o.minX) {
+ copyEdgeFrom(o, EnumFacing.WEST);
+ } else if(minY < o.minY) {
+ copyEdgeFrom(o, EnumFacing.UP);
+ } else if(minY > o.minY) {
+ copyEdgeFrom(o, EnumFacing.DOWN);
+ } else if(minZ < o.minZ) {
+ copyEdgeFrom(o, EnumFacing.NORTH);
+ } else if(minX > o.minX) {
+ copyEdgeFrom(o, EnumFacing.SOUTH);
+ }
+ }
+
+ private void copyEdgeFrom(MeshQuad o, EnumFacing side) {
+ int whichX, whichY, whichZ;
+ whichX = whichY = whichZ = -1;
+
+ switch(plane) {
+ case PLANE_XY:
+ whichZ = 0;
+ break;
+ case PLANE_XZ:
+ whichY = 0;
+ break;
+ case PLANE_YZ:
+ whichX = 0;
+ break;
+ }
+
+ switch(side) {
+ case EAST:
+ copyCornerVertexFrom(o, 1, whichY, whichZ);
+ break;
+ case WEST:
+ copyCornerVertexFrom(o, 0, whichY, whichZ);
+ break;
+ case UP:
+ copyCornerVertexFrom(o, whichX, 1, whichZ);
+ break;
+ case DOWN:
+ copyCornerVertexFrom(o, whichX, 0, whichZ);
+ break;
+ case NORTH:
+ copyCornerVertexFrom(o, whichX, whichY, 1);
+ break;
+ case SOUTH:
+ copyCornerVertexFrom(o, whichX, whichY, 0);
+ break;
+ }
+
+ updateMinMaxXYZ();
+ }
+
+ private void updateMinMaxXYZ() {
+ for(int i = 0; i < 4; i++) {
+ minX = Math.min(minX, xs[i]);
+ minY = Math.min(minY, ys[i]);
+ minZ = Math.min(minZ, zs[i]);
+ maxX = Math.max(maxX, xs[i]);
+ maxY = Math.max(maxY, ys[i]);
+ maxZ = Math.max(maxZ, zs[i]);
+ }
+ }
+
+ private void copyCornerVertexFrom(MeshQuad o, int whichX, int whichY, int whichZ) {
+ int whichXMin, whichXMax, whichYMin, whichYMax, whichZMin, whichZMax;
+ whichXMin = whichYMin = whichZMin = 0;
+ whichXMax = whichYMax = whichZMax = 1;
+
+ if(whichX != -1) whichXMin = whichXMax = whichX;
+ if(whichY != -1) whichYMin = whichYMax = whichY;
+ if(whichZ != -1) whichZMin = whichZMax = whichZ;
+
+ for(int minOrMaxX = whichXMin; minOrMaxX <= whichXMax; minOrMaxX++) {
+ for(int minOrMaxY = whichYMin; minOrMaxY <= whichYMax; minOrMaxY++) {
+ for(int minOrMaxZ = whichZMin; minOrMaxZ <= whichZMax; minOrMaxZ++) {
+ copyVertexFrom(o,
+ o.getCornerVertex(minOrMaxX == 1, minOrMaxY == 1, minOrMaxZ == 1),
+ getCornerVertex(minOrMaxX == 1, minOrMaxY == 1, minOrMaxZ == 1));
+ }
+ }
+ }
+ }
+
+ private void copyVertexFrom(MeshQuad o, int src, int dest) {
+ xs[dest] = o.xs[src];
+ ys[dest] = o.ys[src];
+ zs[dest] = o.zs[src];
+ relUs[dest] = o.relUs[src];
+ relVs[dest] = o.relVs[src];
+ bUs[dest] = o.bUs[src];
+ bVs[dest] = o.bVs[src];
+ cs[dest] = o.cs[src];
+ normals[dest] = o.normals[src];
+ }
+
+ public void writeToDisk(DataOutputStream out, int pass) throws IOException {
+ if(deleted) {
+ return;
+ }
+
+ if(flags.hasTexture) {
+ if(pass == 0) out.writeShort(spriteIndex);
+ }
+ for (int vertexI = 0; vertexI < 4; vertexI++) {
+ if(pass == 1) out.writeByte(xs[vertexI] == 256 ? 255 : xs[vertexI]);
+ if(pass == 2) out.writeByte(ys[vertexI] == 256 ? 255 : ys[vertexI]);
+ if(pass == 3) out.writeByte(zs[vertexI] == 256 ? 255 : zs[vertexI]);
+
+ if (flags.hasTexture) {
+ if(pass == 4) out.writeByte(relUs[vertexI]);
+ if(pass == 5) out.writeByte(relVs[vertexI]);
+ }
+
+ if (flags.hasBrightness) {
+ if(pass == 6) out.writeByte(bUs[vertexI]);
+ if(pass == 7) out.writeByte(bVs[vertexI]);
+ }
+
+ if (flags.hasColor) {
+ if(pass == 8) out.writeInt(cs[vertexI]);
+ }
+
+ if (flags.hasNormals) {
+ if(pass == 9) out.writeInt(normals[vertexI]);
+ }
+ }
+ }
+
+ // maybe minXYZ and maxXYZ should be arrays instead
+ public int getMin(int coord) {
+ return coord == 0 ? minX : coord == 1 ? minY : coord == 2 ? minZ : -1;
+ }
+
+ public int getMax(int coord) {
+ return coord == 0 ? maxX : coord == 1 ? maxY : coord == 2 ? maxZ : -1;
+ }
+
+ public boolean onSamePlaneAs(MeshQuad o) {
+ return isValid(this) && isValid(o) && plane == o.plane &&
+ ((plane == PLANE_XY && minZ == o.minZ) ||
+ (plane == PLANE_XZ && minY == o.minY) ||
+ (plane == PLANE_YZ && minX == o.minX));
+ }
+
+ // this should be static..
+ public boolean isValid(MeshQuad q) {
+ return q != null && !q.deleted;
+ }
+
+ public boolean isClockwiseXZ() {
+ return (xs[1] - xs[0]) * (zs[2] - zs[0]) - (xs[2] - xs[0]) * (zs[1] - zs[0]) < 0;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(Locale.ENGLISH, "%s(%.1f, %.1f, %.1f -- %.1f, %.1f, %.1f) %s", deleted ? "XXX " : "", minX/16f, minY/16f, minZ/16f, maxX/16f, maxY/16f, maxZ/16f, spriteName);
+ }
+
+ public static class QuadPlaneComparator implements Comparator<MeshQuad> {
+
+ public static final QuadPlaneComparator[] quadPlaneComparators = new QuadPlaneComparator[]{
+ new QuadPlaneComparator(2, 1, 0), // PLANE_XY -> ZYX
+ new QuadPlaneComparator(1, 2, 0), // PLANE_XZ -> YZX
+ new QuadPlaneComparator(0, 2, 1) // PLANE_YZ -> XZY
+ };
+
+ private int c0, c1, c2;
+
+ public QuadPlaneComparator(int firstCoordToCompare, int secondCoordToCompare, int thirdCoordToCompare) {
+ this.c0 = firstCoordToCompare;
+ this.c1 = secondCoordToCompare;
+ this.c2 = thirdCoordToCompare;
+ }
+
+ @Override
+ public int compare(MeshQuad a, MeshQuad b) {
+ if(a.getMin(c0) < b.getMin(c0)) {
+ return -1;
+ } else if(a.getMin(c0) > b.getMin(c0)) {
+ return 1;
+ } else {
+ if(a.getMin(c1) < b.getMin(c1)) {
+ return -1;
+ } else if(a.getMin(c1) > b.getMin(c1)) {
+ return 1;
+ } else {
+ if(a.getMin(c2) < b.getMin(c2)) {
+ return -1;
+ } else if(a.getMin(c2) > b.getMin(c2)) {
+ return 1;
+ } else {
+ return (int)Math.signum(a.offset - b.offset);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/makamys/lodmod/renderer/MyRenderer.java b/src/main/java/makamys/lodmod/renderer/MyRenderer.java
new file mode 100644
index 0000000..a1ce086
--- /dev/null
+++ b/src/main/java/makamys/lodmod/renderer/MyRenderer.java
@@ -0,0 +1,748 @@
+package makamys.lodmod.renderer;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.particle.EntityFX;
+import net.minecraft.client.renderer.WorldRenderer;
+import net.minecraft.client.renderer.texture.ITextureObject;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.client.renderer.texture.TextureMap;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.EntityLivingBase;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.ChunkCoordinates;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.world.ChunkCoordIntPair;
+import net.minecraft.world.chunk.Chunk;
+import net.minecraft.world.gen.ChunkProviderServer;
+
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.SynchronousQueue;
+import java.util.stream.Collectors;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.lwjgl.BufferUtils;
+import org.lwjgl.Sys;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.input.Mouse;
+import org.lwjgl.opengl.ARBDebugOutputCallback;
+import org.lwjgl.opengl.ARBShaderObjects;
+import org.lwjgl.opengl.Display;
+import org.lwjgl.opengl.DisplayMode;
+import org.lwjgl.opengl.GL11;
+import org.lwjgl.opengl.GLContext;
+import org.lwjgl.util.vector.Matrix4f;
+
+import makamys.lodmod.ducks.IWorldRenderer;
+import makamys.lodmod.util.Util;
+
+import static org.lwjgl.opengl.ARBBufferObject.*;
+import static org.lwjgl.opengl.ARBVertexBufferObject.*;
+import static org.lwjgl.opengl.GL11.*;
+import static org.lwjgl.opengl.GL13.*;
+import static org.lwjgl.opengl.GL14.*;
+import static org.lwjgl.opengl.GL15.*;
+import static org.lwjgl.opengl.GL20.*;
+import static org.lwjgl.opengl.GL30.*;
+import static org.lwjgl.util.glu.GLU.*;
+
+public class MyRenderer {
+
+ private static boolean hasInited = false;
+
+ private static boolean[] wasDown = new boolean[256];
+ private static int renderQuads = 0;
+
+ public static boolean renderWorld = true;
+ public static boolean renderLOD = true;
+
+ //private static int[] spriteIndexMap;
+ public static List<TextureAtlasSprite> sprites;
+
+ static private Map<Long, Integer> uv2spriteIndex = new HashMap<>();
+
+ //static ChunkMesh mesh;
+
+ private static void destroy() {
+ glDeleteProgram(shaderProgram);
+ glDeleteVertexArrays(VAO);
+ glDeleteBuffers(VBO);
+ }
+
+ private static void mainLoop() {
+ if(Keyboard.isKeyDown(Keyboard.KEY_F) && !wasDown[Keyboard.KEY_F]) {
+ renderLOD = !renderLOD;
+ }
+ if(Keyboard.isKeyDown(Keyboard.KEY_V) && !wasDown[Keyboard.KEY_V]) {
+ renderWorld = !renderWorld;
+ }
+ if(hasInited) {
+ /*if(Keyboard.isKeyDown(Keyboard.KEY_X) && !wasDown[Keyboard.KEY_X]) {
+ renderQuads--;
+ if(renderQuads < 0) {
+ renderQuads = 0;
+ }
+ }
+ if(Keyboard.isKeyDown(Keyboard.KEY_C) && !wasDown[Keyboard.KEY_C]) {
+ renderQuads++;
+ if(renderQuads > mesh.quadCount) {
+ renderQuads = mesh.quadCount;
+ }
+ }*/
+ }
+
+
+ for(int i = 0; i < 256; i++) {
+ wasDown[i] = Keyboard.isKeyDown(i);
+ }
+ }
+
+ private static void render() {
+ GL11.glPushAttrib(GL11.GL_ENABLE_BIT);
+ //GL11.glDisable(GL11.GL_CULL_FACE);
+ //GL11.glDisable(GL11.GL_LIGHTING);
+ GL11.glDisable(GL11.GL_TEXTURE_2D);
+
+ GL11.glEnable(GL11.GL_BLEND);
+ GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
+ //GL11.glColor4f(0, 1, 0, 1); // change this for your colour
+ //GL11.glLineWidth(9.0F);
+ //GL11.glDepthMask(false);
+ float z = 0;
+
+ /*GL11.glBegin(GL11.GL_TRIANGLES);
+ GL11.glVertex3f(-0.9f,-0.9f,0);
+ GL11.glVertex3f(0.9f,0.9f,0);
+ GL11.glVertex3f(-0.9f,0.9f,0);
+ GL11.glEnd();*/
+
+ //glClear(GL_COLOR_BUFFER_BIT);
+
+ glUseProgram(shaderProgram);
+
+ int u_modelView = glGetUniformLocation(shaderProgram, "modelView");
+ int u_proj = glGetUniformLocation(shaderProgram, "proj");
+ int u_playerPos = glGetUniformLocation(shaderProgram, "playerPos");
+ int u_light = glGetUniformLocation(shaderProgram, "lightTex");
+ int u_viewport = glGetUniformLocation(shaderProgram, "viewport");
+ int u_projInv = glGetUniformLocation(shaderProgram, "projInv");
+ int u_fogColor = glGetUniformLocation(shaderProgram, "fogColor");
+ int u_fogStartEnd = glGetUniformLocation(shaderProgram, "fogStartEnd");
+
+ if(false && (u_modelView == -1 || u_proj == -1 || u_playerPos == -1 || u_light == -1 || u_viewport == -1 || u_projInv == -1 || u_fogColor == -1 || u_fogStartEnd == -1)) {
+ System.out.println("failed to get the uniform");
+ } else {
+ FloatBuffer modelView = BufferUtils.createFloatBuffer(16);
+ glGetFloat(GL_MODELVIEW_MATRIX, modelView);
+
+ FloatBuffer projBuf = BufferUtils.createFloatBuffer(16);
+ glGetFloat(GL_PROJECTION_MATRIX, projBuf);
+ //modelView.flip();
+
+ IntBuffer viewportBuf = BufferUtils.createIntBuffer(16);
+ glGetInteger(GL_VIEWPORT, viewportBuf);
+ viewportBuf.limit(4);
+
+ FloatBuffer projInvBuf = BufferUtils.createFloatBuffer(16);
+ Matrix4f m = new Matrix4f();
+ m.load(projBuf);
+ projBuf.flip();
+ m.invert();
+ m.store(projInvBuf);
+ projInvBuf.flip();
+
+ FloatBuffer fogColorBuf = BufferUtils.createFloatBuffer(16);
+ glGetFloat(GL_FOG_COLOR, fogColorBuf);
+ fogColorBuf.limit(4);
+
+ FloatBuffer fogStartEnd = BufferUtils.createFloatBuffer(2);
+ fogStartEnd.put(glGetFloat(GL_FOG_START));
+ fogStartEnd.put(glGetFloat(GL_FOG_END));
+ fogStartEnd.flip();
+
+ glUniformMatrix4(u_modelView, false, modelView);
+ glUniformMatrix4(u_proj, false, projBuf);
+ glUniformMatrix4(u_projInv, false, projInvBuf);
+ //glUniform4(u_viewport, viewportBuf);
+ glUniform4f(u_viewport, viewportBuf.get(0),viewportBuf.get(1),viewportBuf.get(2),viewportBuf.get(3));
+ glUniform4(u_fogColor, fogColorBuf);
+ glUniform2(u_fogStartEnd, fogStartEnd);
+
+ float originX = 0;
+ float originY = 0;
+ float originZ = 0;
+
+ glUniform3f(u_playerPos, (float)EntityFX.interpPosX - originX, (float)EntityFX.interpPosY - originY, (float)EntityFX.interpPosZ - originZ);
+
+ glUniform1i(u_light, 1);
+ }
+
+ glBindVertexArray(VAO);
+ //glEnableVertexAttribArray(0);
+ //glDrawArrays(GL_TRIANGLES, 0, 3);
+ //glActiveTexture(GL_TEXTURE1);
+ //ITextureObject hmm = Minecraft.getMinecraft().getTextureManager().getTexture(new ResourceLocation("dynamic/lightMap_1"));
+ //glBindTexture(GL_TEXTURE_2D, hmm.getGlTextureId());
+ //glDrawArrays(GL_TRIANGLES, 0, 6*renderQuads);
+ glMultiDrawArrays(GL_TRIANGLES, piFirst, piCount);
+
+ //glDisableVertexAttribArray(0);
+ glBindVertexArray(0);
+ glUseProgram(0);
+
+ GL11.glDepthMask(true);
+ GL11.glPopAttrib();
+
+
+ }
+
+ private static int VAO, VBO, EBO, shaderProgram;
+ private static IntBuffer piFirst, piCount;
+
+ private static String readFile(String path){
+ try {
+ return new String(Files.readAllBytes(Util.getResourcePath(path)));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return "";
+ }
+
+ private static byte[] byteBufferToArray(ByteBuffer buffer) {
+ byte[] dst = new byte[buffer.remaining()];
+ buffer.get(dst);
+ buffer.flip();
+ return dst;
+ }
+
+ private static int[] intBufferToArray(IntBuffer buffer) {
+ int[] dst = new int[buffer.remaining()];
+ buffer.get(dst);
+ buffer.flip();
+ return dst;
+ }
+
+ private static int findSpriteIndexForUV(float u, float v) {
+ Map<String, TextureAtlasSprite> uploadedSprites = ((TextureMap)Minecraft.getMinecraft().getTextureManager().getTexture(TextureMap.locationBlocksTexture)).mapUploadedSprites;
+
+ int spriteIndex = 0;
+ for(TextureAtlasSprite tas : uploadedSprites.values()) {
+ if(tas.getMinU() <= u && u <= tas.getMaxU() && tas.getMinV() <= v && v <= tas.getMaxV()) {
+ break;
+ }
+ spriteIndex++;
+ }
+ return spriteIndex;
+ }
+
+ public static int getSpriteIndexForUV(float u, float v){
+ long key = ChunkCoordIntPair.chunkXZ2Int((int)(u * Integer.MAX_VALUE), (int)(v * Integer.MAX_VALUE));
+ int index = uv2spriteIndex.getOrDefault(key, -1);
+ if(index == -1) {
+ index = findSpriteIndexForUV(u, v);
+ uv2spriteIndex.put(key, index);
+ }
+ return index;
+ }
+
+ public static TextureAtlasSprite getSprite(int i){
+ if(i >= 0 && i < sprites.size()) {
+ return sprites.get(i);
+ } else {
+ return null;
+ }
+ }
+
+ private static boolean init() {
+ Map<String, TextureAtlasSprite> uploadedSprites = ((TextureMap)Minecraft.getMinecraft().getTextureManager().getTexture(TextureMap.locationBlocksTexture)).mapUploadedSprites;
+ sprites = uploadedSprites.values().stream().collect(Collectors.toList());
+
+ int vertexShader;
+ vertexShader = glCreateShader(GL_VERTEX_SHADER);
+
+ glShaderSource(vertexShader, readFile("shaders/chunk.vert"));
+ glCompileShader(vertexShader);
+
+ if(glGetShaderi(vertexShader, GL_COMPILE_STATUS) == 0) {
+ System.out.println("Error compiling vertex shader: " + glGetShaderInfoLog(vertexShader, 256));
+ }
+
+ int fragmentShader;
+ fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
+
+ glShaderSource(fragmentShader, readFile("shaders/chunk.frag"));
+ glCompileShader(fragmentShader);
+
+ if(glGetShaderi(fragmentShader, GL_COMPILE_STATUS) == 0) {
+ System.out.println("Error compiling fragment shader: " + glGetShaderInfoLog(fragmentShader, 256));
+ }
+
+ shaderProgram = glCreateProgram();
+ glAttachShader(shaderProgram, vertexShader);
+ glAttachShader(shaderProgram, fragmentShader);
+ glLinkProgram(shaderProgram);
+
+ if(glGetProgrami(shaderProgram, GL_LINK_STATUS) == 0) {
+ System.out.println("Error linking shader: " + glGetShaderInfoLog(shaderProgram, 256));
+ }
+
+ glDeleteShader(vertexShader);
+ glDeleteShader(fragmentShader);
+
+ VAO = glGenVertexArrays();
+ glBindVertexArray(VAO);
+
+ VBO = glGenBuffers();
+ glBindBuffer(GL_ARRAY_BUFFER, VBO);
+
+ EBO = glGenBuffers();
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
+ boolean rough = false;
+
+ /*
+ List<Integer> chunkCoords = new ArrayList<>();*/
+ /*chunkCoords.add(-19);
+ chunkCoords.add(4);
+ chunkCoords.add(15);*/
+ /*List<Mesh> meshes = new ArrayList<Mesh>();
+ for(Chunk chunk : myChunks) {
+ Entity player = Minecraft.getMinecraft().thePlayer;
+ int minRadius = 0;
+ int radius = 10;
+ if(minRadius <= Math.abs(chunk.xPosition - player.chunkCoordX) && Math.abs(chunk.xPosition - player.chunkCoordX) < radius && minRadius <= Math.abs(chunk.zPosition - player.chunkCoordZ) && Math.abs(chunk.zPosition - player.chunkCoordZ) < radius) {
+ if(!rough) {
+ for(int y = 0; y < 16; y++) {
+ chunkCoords.add(chunk.xPosition);
+ chunkCoords.add(y);
+ chunkCoords.add(chunk.zPosition);
+ }
+ } else {
+ meshes.add(new SimpleChunkMesh(chunk));
+ }
+ }
+
+ }
+ long t0 = System.nanoTime();
+ if(!rough) {
+ ChunkMesh.saveChunks(chunkCoords);
+
+ meshes.addAll(ChunkMesh.loadAll());
+ }
+ System.out.println("loaded " + meshes.size() + " cchunks in " + ((System.nanoTime() - t0) / 1000000000f) + "s");
+ //meshes = meshes.subList(2, 3);
+ int bufferSize = 0;
+ int quadCount = 0;
+ Mesh firstMesh = meshes.get(0);
+ int stride = -1;
+ for(Mesh mesh : meshes) {
+ bufferSize += mesh.buffer.limit();
+ quadCount += mesh.quadCount;
+ }
+
+ if(Runtime.getRuntime().freeMemory() < BUFFER_SIZE) {
+ System.out.println("not enough memory");
+ // TODO optimize memory usage
+ return false;
+ }
+ ByteBuffer buffer = BufferUtils.createByteBuffer(BUFFER_SIZE);
+
+ for(Mesh mesh : meshes) {
+ int verticesSoFar = buffer.position() / firstMesh.getStride();
+ buffer.put(mesh.buffer);
+ }
+ buffer.flip();*/
+
+ glBufferData(GL_ARRAY_BUFFER, BUFFER_SIZE, GL_STATIC_DRAW);
+
+ int stride = 7 * 4;
+
+ glVertexAttribPointer(0, 3, GL_FLOAT, false, stride, 0);
+ glVertexAttribPointer(1, 2, GL_FLOAT, false, stride, 3 * 4);
+ glVertexAttribPointer(2, 2, GL_SHORT, false, stride, 5 * 4);
+ glVertexAttribPointer(3, 4, GL_UNSIGNED_BYTE, false, stride, 6 * 4);
+
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+ glEnableVertexAttribArray(2);
+ glEnableVertexAttribArray(3);
+
+ //renderQuads = quadCount;
+
+ int nextTri = 0;
+ piFirst = BufferUtils.createIntBuffer(MAX_MESHES);
+ piFirst.flip();
+ piCount = BufferUtils.createIntBuffer(MAX_MESHES);
+ piCount.flip();
+ /*for(int i = 0; i < meshes.size(); i++) {
+ Mesh mesh = meshes.get(i);
+
+ piFirst.put(nextTri);
+ piCount.put(mesh.quadCount * 6);
+ nextTri += mesh.quadCount * 6;
+ }*/
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindVertexArray(0);
+ //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+ return true;
+ }
+
+ public static boolean doRender = true;
+ private static boolean hasInited2 = false;
+
+ private static int BUFFER_SIZE = 1024 * 1024 * 1024;
+ private static int MAX_MESHES = 100000;
+
+ public static void beforeRenderTerrain() {
+ init2();
+
+ if(doRender) {
+ mainLoop();
+ if(hasInited && renderLOD) {
+ render();
+ }
+ }
+ }
+
+ public static void onWorldRendererPost(WorldRenderer wr) {
+ LODChunk lodChunk = getLODChunk(Math.floorDiv(wr.posX, 16), Math.floorDiv(wr.posZ, 16));
+ lodChunk.putChunkMeshes(Math.floorDiv(wr.posY, 16), ((IWorldRenderer)wr).getChunkMeshes());
+ setVisible(lodChunk, false);
+ }
+
+ public static void onDontDraw(WorldRenderer wr) {
+ int chunkX = Math.floorDiv(wr.posX, 16);
+ int chunkY = Math.floorDiv(wr.posY, 16);
+ int chunkZ = Math.floorDiv(wr.posZ, 16);
+
+ Entity player = (Entity)Minecraft.getMinecraft().getIntegratedServer().getConfigurationManager().playerEntityList.get(0);
+ LODChunk lodChunk = getLODChunk(chunkX, chunkZ);
+
+ if(lodChunk.hasChunkMeshes()) {
+ setLOD(lodChunk, 2);//(lodChunk.distSq(player) < (16 * 16 * 16 * 16)) ? 1 : 2);
+ }
+ setVisible(lodChunk, true);
+ }
+
+ private static void init2() {
+ if(!hasInited2) {
+ hasInited = init();
+ hasInited2 = true;
+ } else {
+ while(!farChunks.isEmpty()) {
+ LODChunk lodChunk = receiveFarChunk(farChunks.remove());
+ sendChunkToGPU(lodChunk);
+ }
+
+ List<Object> players = Minecraft.getMinecraft().getIntegratedServer().getConfigurationManager().playerEntityList;
+ if(!players.isEmpty()) {
+ Entity player = (Entity)players.get(0);
+
+ List<ChunkCoordIntPair> newServerChunkLoadQueue = new ArrayList<>();
+
+ if(Double.isNaN(lastSortX) || getLastSortDistanceSq(player) > 16 * 16) {
+ int centerX = (int)Math.floor(player.posX / 16.0);
+ int centerZ = (int)Math.floor(player.posZ / 16.0);
+
+ int range = 64;
+ for(int x = -range; x <= range; x++) {
+ for(int z = -range; z <= range; z++) {
+ int chunkX = centerX + x;
+ int chunkZ = centerZ + z;
+
+ if(getLODChunk(chunkX, chunkZ).chunk == null) {
+ newServerChunkLoadQueue.add(new ChunkCoordIntPair(chunkX, chunkZ));
+ }
+ }
+ }
+ Collections.sort(newServerChunkLoadQueue, new ChunkCoordDistanceComparator(player));
+ setServerChunkLoadQueue(newServerChunkLoadQueue);
+ hasInited2 = true;
+
+ lastSortX = player.posX;
+ lastSortY = player.posY;
+ lastSortZ = player.posZ;
+
+ loadedRegionsMap.forEach((k, v) -> v.tick(player));
+ }
+ }
+ }
+ }
+
+ private static double getLastSortDistanceSq(Entity player) {
+ return Math.pow(lastSortX - player.posX, 2) + Math.pow(lastSortZ - player.posZ, 2);
+ }
+
+ private static synchronized void setServerChunkLoadQueue(List<ChunkCoordIntPair> coords) {
+ serverChunkLoadQueue = coords;
+ }
+
+ private static LODChunk receiveFarChunk(Chunk chunk) {
+ LODRegion region = getRegionContaining(chunk.xPosition, chunk.zPosition);
+ myChunks.add(chunk);
+ return region.putChunk(chunk);
+ }
+
+ private static LODChunk getLODChunk(int chunkX, int chunkZ) {
+ return getRegionContaining(chunkX, chunkZ).getChunkAbsolute(chunkX, chunkZ);
+ }
+
+ static List<Chunk> myChunks = new ArrayList<Chunk>();
+ static List<LODChunk> pendingLODChunks = new ArrayList<>();
+
+ private static boolean hasServerInited = false;
+ private static HashMap<ChunkCoordIntPair, LODRegion> loadedRegionsMap = new HashMap<>();
+
+ // TODO make these packets to make this work on dedicated servers
+ static Queue<Chunk> farChunks = new ConcurrentLinkedQueue<>();
+
+ static List<ChunkCoordIntPair> serverChunkLoadQueue = new ArrayList<>();
+
+ private static double lastSortX = Double.NaN;
+ private static double lastSortY = Double.NaN;
+ private static double lastSortZ = Double.NaN;
+
+ public static void onStopServer() {
+
+ }
+
+ public static synchronized void serverTick() {
+ int chunkLoadsRemaining = 64;
+ while(!serverChunkLoadQueue.isEmpty() && chunkLoadsRemaining-- > 0) {
+ ChunkCoordIntPair coords = serverChunkLoadQueue.remove(0);
+ ChunkProviderServer chunkProviderServer = Minecraft.getMinecraft().getIntegratedServer().worldServers[0].theChunkProviderServer;
+ Chunk chunk = chunkProviderServer.currentChunkProvider.provideChunk(coords.chunkXPos, coords.chunkZPos);
+ farChunks.add(chunk);
+ }
+ }
+
+ private static LODRegion getRegionContaining(int chunkX, int chunkZ) {
+ ChunkCoordIntPair key = new ChunkCoordIntPair(Math.floorDiv(chunkX , 32), Math.floorDiv(chunkZ, 32));
+ LODRegion region = loadedRegionsMap.get(key);
+ if(region == null) {
+ region = LODRegion.load(Math.floorDiv(chunkX , 32), Math.floorDiv(chunkZ , 32));
+ loadedRegionsMap.put(key, region);
+ }
+ return region;
+ }
+
+ private static void loadChunk(int chunkX, int chunkZ) {
+ LODRegion region = getRegionContaining(chunkX, chunkZ);
+ LODChunk lodChunk = region.getChunkAbsolute(chunkX, chunkZ);
+ if(lodChunk == null) {
+ ChunkProviderServer chunkProviderServer = Minecraft.getMinecraft().getIntegratedServer().worldServers[0].theChunkProviderServer;
+ //Chunk chunk = chunkProviderServer.loadChunk(chunkX, chunkZ);
+ Chunk chunk = chunkProviderServer.currentChunkProvider.provideChunk(chunkX, chunkZ);
+ /*Chunk chunk = chunkProviderServer.safeLoadChunk(chunkX, chunkZ);
+ if(chunk == null) {
+ chunk = chunkProviderServer.currentChunkProvider.provideChunk(chunkX, chunkZ);
+ }
+ if(chunk != null) {
+ chunk.populateChunk(chunkProviderServer, chunkProviderServer, chunkX, chunkZ);
+ myChunks.add(chunk);
+ }*/
+ if(chunk != null) {
+ myChunks.add(chunk);
+ }
+ //lodChunk = region.putChunk(new LODChunk(chunk));
+ }
+ sendChunkToGPU(lodChunk);
+ }
+
+ private static void sendChunkToGPU(LODChunk lodChunk) {
+ lodChunk.simpleMesh = new SimpleChunkMesh(lodChunk.chunk);
+ /*for(int cy = 0; cy < 16; cy++) {
+ lodChunk.chunkMeshes[cy] = ChunkMesh.getChunkMesh(lodChunk.x, cy, lodChunk.z);
+ sendMeshToGPU(lodChunk.chunkMeshes[cy]);
+ }*/
+
+ sendMeshToGPU(lodChunk.simpleMesh);
+
+ Entity player = (Entity) Minecraft.getMinecraft().getIntegratedServer().getConfigurationManager().playerEntityList.get(0);
+
+ setLOD(lodChunk, 1);//lodChunk.distSq(player) < 16 * 16 * 16 * 16 ? 2 : 1);
+ setVisible(lodChunk, true);
+ }
+
+ public static void setLOD(LODChunk lodChunk, int lod) {
+ if(lod == lodChunk.lod) return;
+
+ lodChunk.lod = lod;
+ lodChunkChanged(lodChunk);
+ }
+
+ public static void setVisible(LODChunk lodChunk, boolean visible) {
+ if(visible == lodChunk.visible) return;
+
+ lodChunk.visible = visible;
+ lodChunkChanged(lodChunk);
+ }
+
+ public static void lodChunkChanged(LODChunk lodChunk) {
+ int newLOD = (!lodChunk.hasChunkMeshes() && lodChunk.lod == 2) ? 1 : lodChunk.lod;
+ if(lodChunk.simpleMesh != null) {
+ if(lodChunk.visible && newLOD == 1) {
+ if(!lodChunk.simpleMesh.visible) {
+ setMeshVisible(lodChunk.simpleMesh, true);
+ }
+ } else {
+ if(lodChunk.simpleMesh.visible) {
+ setMeshVisible(lodChunk.simpleMesh, false);
+ }
+ }
+ }
+ for(ChunkMesh cm : lodChunk.chunkMeshes) {
+ if(cm != null) {
+ if(lodChunk.visible && newLOD == 2) {
+ if(!cm.visible) {
+ setMeshVisible(cm, true);
+ }
+ } else {
+ if(cm.visible) {
+ setMeshVisible(cm, false);
+ }
+ }
+ }
+ }
+ }
+
+ private static int nextTri;
+ private static int nextMeshOffset;
+ private static int nextMesh;
+
+ protected static void sendMeshToGPU(Mesh mesh) {
+ if(mesh == null) {
+ return;
+ }
+ glBindVertexArray(VAO);
+ glBindBuffer(GL_ARRAY_BUFFER, VBO);
+
+ glBufferSubData(GL_ARRAY_BUFFER, nextMeshOffset, mesh.buffer);
+ mesh.iFirst = nextTri;
+ mesh.iCount = mesh.quadCount * 6;
+
+ nextTri += mesh.quadCount * 6;
+ nextMeshOffset += mesh.buffer.limit();
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindVertexArray(0);
+ }
+
+ protected static void setMeshVisible(Mesh mesh, boolean visible) {
+ if(mesh == null) return;
+
+ if(mesh.visible != visible) {
+ if(!visible) {
+ piFirst.position(0);
+ int[] piFirstArr = new int[piFirst.limit()];
+ piFirst.get(piFirstArr);
+ int index = ArrayUtils.indexOf(piFirstArr, mesh.iFirst);
+ piFirstArr = ArrayUtils.remove(piFirstArr, index);
+ piFirst.position(0);
+ piFirst.put(piFirstArr);
+ piFirst.position(0);
+ piFirst.limit(piFirst.limit() - 1);
+
+ piCount.position(0);
+ int[] piCountArr = new int[piCount.limit()];
+ piCount.get(piCountArr);
+ piCountArr = ArrayUtils.remove(piCountArr, index);
+ piCount.position(0);
+ piCount.put(piCountArr);
+ piCount.position(0);
+ piCount.limit(piCount.limit() - 1);
+ nextMesh--;
+ } else if(visible) {
+ piFirst.limit(piFirst.limit() + 1);
+ piFirst.put(nextMesh, mesh.iFirst);
+ piCount.limit(piCount.limit() + 1);
+ piCount.put(nextMesh, mesh.iCount);
+ nextMesh++;
+ }
+ mesh.visible = visible;
+ }
+ }
+
+ public static Chunk getChunkFromChunkCoords(int x, int z) {
+ for(Chunk chunk : myChunks) {
+ if(chunk.xPosition == x && chunk.zPosition == z) {
+ return chunk;
+ }
+ }
+ return null;
+ }
+
+ public static class LODChunkComparator implements Comparator<LODChunk> {
+ Entity player;
+
+ public LODChunkComparator(Entity player) {
+ this.player = player;
+ }
+
+ @Override
+ public int compare(LODChunk p1, LODChunk p2) {
+ int distSq1 = distSq(p1);
+ int distSq2 = distSq(p2);
+ return distSq1 < distSq2 ? -1 : distSq1 > distSq2 ? 1 : 0;
+ }
+
+ int distSq(LODChunk p) {
+ return (int)(
+ Math.pow(((p.x * 16) - player.chunkCoordX), 2) +
+ Math.pow(((p.z * 16) - player.chunkCoordZ), 2)
+ );
+ }
+ }
+
+ public static class ChunkCoordDistanceComparator implements Comparator<ChunkCoordIntPair> {
+ Entity player;
+
+ public ChunkCoordDistanceComparator(Entity player) {
+ this.player = player;
+ }
+
+ @Override
+ public int compare(ChunkCoordIntPair p1, ChunkCoordIntPair p2) {
+ int distSq1 = distSq(p1);
+ int distSq2 = distSq(p2);
+ return distSq1 < distSq2 ? -1 : distSq1 > distSq2 ? 1 : 0;
+ }
+
+ int distSq(ChunkCoordIntPair p) {
+ return (int)(
+ Math.pow(((p.chunkXPos * 16) - player.posX), 2) +
+ Math.pow(((p.chunkZPos * 16) - player.posZ), 2)
+ );
+ }
+ }
+}
diff --git a/src/main/java/makamys/lodmod/renderer/SimpleChunkMesh.java b/src/main/java/makamys/lodmod/renderer/SimpleChunkMesh.java
new file mode 100644
index 0000000..190a398
--- /dev/null
+++ b/src/main/java/makamys/lodmod/renderer/SimpleChunkMesh.java
@@ -0,0 +1,150 @@
+package makamys.lodmod.renderer;
+
+import static org.lwjgl.opengl.GL11.GL_FLOAT;
+import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
+import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
+import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
+import static org.lwjgl.opengl.GL15.glBufferData;
+import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
+import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
+
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+import org.lwjgl.BufferUtils;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.material.Material;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.client.renderer.texture.TextureMap;
+import net.minecraft.util.IIcon;
+import net.minecraft.world.chunk.Chunk;
+
+public class SimpleChunkMesh extends Mesh {
+
+ private FloatBuffer vertices;
+
+ public SimpleChunkMesh(Chunk target) {
+ int divisions = 4;
+ quadCount = divisions * divisions * 5;
+
+ buffer = BufferUtils.createByteBuffer(4 * 6 * 7 * quadCount);
+ vertices = buffer.asFloatBuffer();
+
+ for(int divX = 0; divX < divisions; divX++) {
+ for(int divZ = 0; divZ < divisions; divZ++) {
+ IIcon icon = null;
+ int color = 0xFFFFFFFF;
+ int size = 16 / divisions;
+ int y = 255;
+ boolean foundWater = false;
+ for(y = 255; y > 0; y--) {
+ int xOff = divX * size;
+ int zOff = divZ * size;
+ Block block = target.getBlock(xOff, y, zOff);
+
+ float offX = target.xPosition * 16 + divX * size;
+ float offY = y;
+ float offZ = target.zPosition * 16 + divZ * size;
+
+ if(!foundWater && block.getMaterial() == Material.water) {
+ // TODO just add a face here, and keep the seabed
+ foundWater = true;
+ int meta = target.getBlockMetadata(xOff, y, zOff);
+ IIcon waterIcon = block.getIcon(1, meta);
+ int waterColor = block.colorMultiplier(Minecraft.getMinecraft().theWorld, target.xPosition * 16 + xOff, y, target.zPosition * 16 + zOff);
+ waterColor |= 0xFF000000;
+
+ addCube(offX, offY, offZ, size, size, size*4, waterIcon, waterColor);
+ break;
+ }
+
+ if(block.isBlockNormalCube() && block.isOpaqueCube() && block.renderAsNormalBlock()) {
+ int meta = target.getBlockMetadata(xOff, y, zOff);
+ icon = block.getIcon(1, meta);
+ color = block.colorMultiplier(Minecraft.getMinecraft().theWorld, target.xPosition * 16 + xOff, y, target.zPosition * 16 + zOff);
+ color = (0xFF << 24) | ((color >> 16 & 0xFF) << 0) | ((color >> 8 & 0xFF) << 8) | ((color >> 0 & 0xFF) << 16);
+
+ addCube(offX, offY, offZ, size, size, offY, icon, color);
+ break;
+ }
+ }
+ }
+ }
+ vertices.flip();
+ }
+
+ private void addCube(float x, float y, float z, float sizeX, float sizeZ, float sizeY, IIcon icon, int color) {
+ addFace(
+ x + 0, y + 0, z + 0,
+ x + 0, y + 0, z + sizeZ,
+ x + sizeX, y + 0, z + sizeZ,
+ x + sizeX, y + 0, z + 0,
+ icon, color, 240
+ );
+ addFace(
+ x + 0, y - sizeY, z + 0,
+ x + 0, y + 0, z + 0,
+ x + sizeX, y + 0, z + 0,
+ x + sizeX, y - sizeY, z + 0,
+ icon, color, 180
+ );
+ addFace(
+ x + sizeX, y - sizeY, z + sizeZ,
+ x + sizeX, y + 0, z + sizeZ,
+ x + 0, y + 0, z + sizeZ,
+ x + 0, y - sizeY, z + sizeZ,
+ icon, color, 180
+ );
+ addFace(
+ x + sizeX, y - sizeY, z + 0,
+ x + sizeX, y + 0, z + 0,
+ x + sizeX, y + 0, z + sizeZ,
+ x + sizeX, y - sizeY, z + sizeZ,
+ icon, color, 120
+ );
+ addFace(
+ x + 0, y - sizeY, z + sizeZ,
+ x + 0, y + 0, z + sizeZ,
+ x + 0, y + 0, z + 0,
+ x + 0, y - sizeY, z + 0,
+ icon, color, 120
+ );
+ }
+
+ private void addFace(float p1x, float p1y, float p1z,
+ float p2x, float p2y, float p2z,
+ float p3x, float p3y, float p3z,
+ float p4x, float p4y, float p4z,
+ IIcon icon, int color, int brightness) {
+ int off = vertices.position() * 4;
+ vertices.put(new float[] {
+ p1x, p1y, p1z, icon.getMinU(), icon.getMaxV(), 0, 0,
+ p2x, p2y, p2z, icon.getMinU(), icon.getMinV(), 0, 0,
+ p4x, p4y, p4z, icon.getMaxU(), icon.getMaxV(), 0, 0,
+ p2x, p2y, p2z, icon.getMinU(), icon.getMinV(), 0, 0,
+ p3x, p3y, p3z, icon.getMaxU(), icon.getMinV(), 0, 0,
+ p4x, p4y, p4z, icon.getMaxU(), icon.getMaxV(), 0, 0
+ });
+ buffer.putInt(off + 0 * getStride() + 6 * 4, color);
+ buffer.putShort(off + 0 * getStride() + 5 * 4 + 2, (short)brightness);
+ buffer.putInt(off + 1 * getStride() + 6 * 4, color);
+ buffer.putShort(off + 1 * getStride() + 5 * 4 + 2, (short)brightness);
+ buffer.putInt(off + 2 * getStride() + 6 * 4, color);
+ buffer.putShort(off + 2 * getStride() + 5 * 4 + 2, (short)brightness);
+ buffer.putInt(off + 3 * getStride() + 6 * 4, color);
+ buffer.putShort(off + 3 * getStride() + 5 * 4 + 2, (short)brightness);
+ buffer.putInt(off + 4 * getStride() + 6 * 4, color);
+ buffer.putShort(off + 4 * getStride() + 5 * 4 + 2, (short)brightness);
+ buffer.putInt(off + 5 * getStride() + 6 * 4, color);
+ buffer.putShort(off + 5 * getStride() + 5 * 4 + 2, (short)brightness);
+
+ }
+
+ public int getStride() {
+ return (3 * 4 + 8 + 4 + 4);
+ }
+
+}
diff --git a/src/main/java/makamys/lodmod/util/Util.java b/src/main/java/makamys/lodmod/util/Util.java
new file mode 100644
index 0000000..92cc5cd
--- /dev/null
+++ b/src/main/java/makamys/lodmod/util/Util.java
@@ -0,0 +1,30 @@
+package makamys.lodmod.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+
+public class Util {
+ public static Path getResourcePath(String relPath) {
+ try {
+ URL resourceURL = Util.class.getClassLoader().getResource(relPath);
+
+ switch(resourceURL.getProtocol()) {
+ case "jar":
+ String urlString = resourceURL.getPath();
+ int lastExclamation = urlString.lastIndexOf('!');
+ String newURLString = urlString.substring(0, lastExclamation);
+ return FileSystems.newFileSystem(new File(URI.create(newURLString)).toPath(), null).getPath(relPath);
+ case "file":
+ return new File(URI.create(resourceURL.toString())).toPath();
+ default:
+ return null;
+ }
+ } catch(IOException e) {
+ return null;
+ }
+ }
+}
diff --git a/src/main/resources/META-INF/LODMod_at.cfg b/src/main/resources/META-INF/LODMod_at.cfg
new file mode 100644
index 0000000..ff15140
--- /dev/null
+++ b/src/main/resources/META-INF/LODMod_at.cfg
@@ -0,0 +1,2 @@
+public net.minecraft.client.renderer.texture.TextureMap field_94252_e #mapUploadedSprites
+public net.minecraft.client.renderer.Tessellator func_78379_d()V #reset \ No newline at end of file
diff --git a/src/main/resources/lodmod.mixin.json b/src/main/resources/lodmod.mixin.json
index 8ded54d..d8bfed1 100644
--- a/src/main/resources/lodmod.mixin.json
+++ b/src/main/resources/lodmod.mixin.json
@@ -5,8 +5,13 @@
"refmap": "lodmod.mixin.refmap.json",
"compatibilityLevel": "JAVA_8",
"mixins": [
- "MixinSoundManager",
+ "MixinChunkCache",
"MixinEntityRenderer",
- "IRenderGlobal"
+ "MixinMinecraftServer",
+ "MixinRenderGlobal",
+ "MixinSoundManager",
+ "MixinSoundManager",
+ "MixinTessellator",
+ "MixinWorldRenderer"
]
}
diff --git a/src/main/resources/shaders/chunk.frag b/src/main/resources/shaders/chunk.frag
new file mode 100644
index 0000000..ae187cb
--- /dev/null
+++ b/src/main/resources/shaders/chunk.frag
@@ -0,0 +1,61 @@
+#version 330 core
+out vec4 FragColor;
+
+in vec2 TexCoord;
+in vec2 BTexCoord;
+in vec4 DaColor;
+in vec4 Viewport;
+in mat4 ProjInv;
+in vec4 FogColor;
+in vec2 FogStartEnd;
+
+uniform sampler2D atlas;
+uniform sampler2D lightTex;
+
+void main()
+{
+ //FragColor = texture(lightTex, TexCoord);
+ //FragColor = texture(lightTex, vec2(8.0/256.0,8.0/256.0));
+ //FragColor = texture(lightTex, vec2(0, 0));
+ //FragColor = vec4(texture(lightTex, vec2(0, 0)).xyz,1);
+ //FragColor = vec4(BTexCoord.xy / 256, 0, 1);
+ //FragColor = texture(lightTex, (BTexCoord + 8.0) / 256.0);
+ //FragColor = texture(light, vec2(16,16));
+ //FragColor = vec4(1,0,0,1);
+ //FragColor = DaColor/256.0;
+
+ vec4 texColor = texture(atlas, TexCoord);
+ vec4 colorMult = DaColor/256.0;
+
+ vec4 lightyColor = texture(lightTex, (BTexCoord + 8.0) / 256.0);
+ //vec4 lightyMultColor = vec4(lightyColor.x * colorMult.x, lightyColor.y * colorMult.y, lightyColor.z * colorMult.z, lightyColor.w * colorMult.w);
+
+ //FragColor = vec4(texColor.x * colorMult.x, texColor.y * colorMult.y, texColor.z * colorMult.z, texColor.w * colorMult.w);
+ //vec4 texMultColor = texColor * colorMult;
+
+ //FragColor = texMultColor;// + (lightyColor - vec4(0.5,0.5,0.5,0.0));
+ //FragColor = vec4(lightyColor.xyz, 1);
+ //FragColor = ((texColor * colorMult) * lightyColor);
+ vec4 rasterColor = ((texColor * colorMult) * lightyColor);
+ float s = FogStartEnd.x;
+ float e = FogStartEnd.y;
+
+ vec4 ndcPos;
+ ndcPos.xy = ((2.0 * gl_FragCoord.xy) - (2.0 * Viewport.xy)) / (Viewport.zw) - 1;
+ ndcPos.z = (2.0 * gl_FragCoord.z - gl_DepthRange.near - gl_DepthRange.far) /
+ (gl_DepthRange.far - gl_DepthRange.near);
+ ndcPos.w = 1.0;
+
+ vec4 clipPos = ndcPos / gl_FragCoord.w;
+ vec4 eyePos = ProjInv * clipPos;
+
+ float z = length(eyePos);
+ /*
+ if(z < 200){
+ discard;
+ }
+ */
+ float f = (e - z) / (e - s);
+ f = clamp(f, 0, 1);
+ FragColor = mix(FogColor, rasterColor, f);
+} \ No newline at end of file
diff --git a/src/main/resources/shaders/chunk.vert b/src/main/resources/shaders/chunk.vert
new file mode 100644
index 0000000..3f2ad1c
--- /dev/null
+++ b/src/main/resources/shaders/chunk.vert
@@ -0,0 +1,34 @@
+#version 330 core
+layout (location = 0) in vec3 aPos;
+layout (location = 1) in vec2 aTexCoord;
+layout (location = 2) in vec2 aBTexCoord;
+layout (location = 3) in vec4 aDaColor;
+
+uniform mat4 modelView;
+uniform mat4 proj;
+uniform mat4 projInv;
+uniform vec4 viewport;
+uniform vec4 fogColor;
+uniform vec2 fogStartEnd;
+
+uniform vec3 playerPos;
+
+out vec2 TexCoord;
+out vec2 BTexCoord;
+out vec4 DaColor;
+out vec4 Viewport;
+out mat4 ProjInv;
+out vec4 FogColor;
+out vec2 FogStartEnd;
+
+void main()
+{
+ gl_Position = proj * modelView * vec4(aPos - playerPos, 1.0);
+ TexCoord = aTexCoord;
+ BTexCoord = aBTexCoord;
+ DaColor = aDaColor;
+ Viewport = viewport;
+ ProjInv = projInv;
+ FogColor = fogColor;
+ FogStartEnd = fogStartEnd;
+} \ No newline at end of file