aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/makamys/lodmod/renderer
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/main/java/makamys/lodmod/renderer
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/main/java/makamys/lodmod/renderer')
-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
9 files changed, 1844 insertions, 0 deletions
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]);
+ }
+ }
+ }
+
+ // ma