diff options
Diffstat (limited to 'src/main')
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 |
