aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-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 ArrayL