Refactor LodQuadBuilder

This commit is contained in:
James Seibel
2022-04-09 21:28:30 -05:00
parent ff1c6cad2a
commit 3e42541fe9
10 changed files with 800 additions and 706 deletions
@@ -0,0 +1,35 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.lodBuilding.bufferBuilding;
/**
* EastWest <Br>
* NorthSouthOrUpDown
*
* @author James Seibel
* @version 2022-4-9
*/
public enum BufferMergeDirectionEnum
{
EastWest,
/** NorthSouth and UpDown are merged since */
NorthSouthOrUpDown
}
@@ -0,0 +1,278 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.lodBuilding.bufferBuilding;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.util.ColorUtil;
import static com.seibel.lod.core.render.LodRenderer.EVENT_LOGGER;
/**
* Represents a renderable quad.
*
* @author James Seibel
* @author ?
* @version 4-9-2022
*/
public class BufferQuad
{
final short x;
final short y;
final short z;
short widthEastWest;
/** This is both North/South and Up/Down since the merging logic is the same either way */
short widthNorthSouthOrUpDown;
int color;
final byte skyLight;
final byte blockLight;
final LodDirection direction;
BufferQuad(short x, short y, short z, short widthEastWest, short widthNorthSouthOrUpDown,
int color, byte skylight, byte blocklight,
LodDirection direction)
{
if (widthEastWest == 0 || widthNorthSouthOrUpDown == 0)
throw new IllegalArgumentException("Size 0 quad!");
if (widthEastWest < 0 || widthNorthSouthOrUpDown < 0)
throw new IllegalArgumentException("Negative sized quad!");
this.x = x;
this.y = y;
this.z = z;
this.widthEastWest = widthEastWest;
this.widthNorthSouthOrUpDown = widthNorthSouthOrUpDown;
this.color = color;
this.skyLight = skylight;
this.blockLight = blocklight;
this.direction = direction;
}
/** a rough but fast calculation */
double calculateDistance(double relativeX, double relativeY, double relativeZ)
{
return Math.pow(relativeX - x, 2) + Math.pow(relativeY - y, 2) + Math.pow(relativeZ - z, 2);
}
/** compares this quad's position to the given quad */
public int compare(BufferQuad quad, BufferMergeDirectionEnum compareDirection)
{
if (direction != quad.direction)
throw new IllegalArgumentException("The other quad is not in the same direction: " + quad.direction + " vs " + direction);
if (compareDirection == BufferMergeDirectionEnum.EastWest)
{
switch (direction.getAxis())
{
case X:
return threeDimensionalCompare(x, y, z, quad.x, quad.y, quad.z);
case Y:
return threeDimensionalCompare(y, z, x, quad.y, quad.z, quad.x);
case Z:
return threeDimensionalCompare(z, y, x, quad.z, quad.y, quad.x);
default:
throw new IllegalArgumentException("Invalid Axis enum: " + direction.getAxis());
}
}
else // if ()
{
switch (direction.getAxis())
{
case X:
return threeDimensionalCompare(x, z, y, quad.x, quad.z, quad.y);
case Y:
return threeDimensionalCompare(y, x, z, quad.y, quad.x, quad.z);
case Z:
return threeDimensionalCompare(z, x, y, quad.z, quad.x, quad.y);
default:
throw new IllegalArgumentException("Invalid Axis enum: " + direction.getAxis());
}
}
}
/**
* Compares two 3D points A and B. <br>
* The X, Y, and Z coordinates can be passed into parameters 0, 1, and 2 in any order
* provided they are in the same order for both A and B. <br>
* With the 0th parameter being the most significant when comparing.
*/
private static int threeDimensionalCompare(short a0, short a1, short a2, short b0, short b1, short b2)
{
long a = (long) a0 << 48 | (long) a1 << 32 | (long) a2 << 16;
long b = (long) b0 << 48 | (long) b1 << 32 | (long) b2 << 16;
return Long.compare(a, b);
}
/**
* Attempts to merge the given quad into this one.
* @returns true if the quads were merged, false otherwise.
*/
public boolean tryMerge(BufferQuad quad, BufferMergeDirectionEnum mergeDirection)
{
// only merge quads that are in the same direction
if (direction != quad.direction)
return false;
// make sure these quads share the same perpendicular axis
if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y) ||
(mergeDirection == BufferMergeDirectionEnum.NorthSouthOrUpDown && this.x != quad.x))
{
return false;
}
// get the position of each quad to compare against
short thisPerpendicularCompareStartPos; // edge perpendicular to the merge direction
short thisParallelCompareStartPos; // edge parallel to the merge direction
short otherPerpendicularCompareStartPos;
short otherParallelCompareStartPos;
switch (this.direction.getAxis())
{
default: // shouldn't normally happen, just here to make the compiler happy
case X:
if (mergeDirection == BufferMergeDirectionEnum.EastWest)
{
thisPerpendicularCompareStartPos = this.z;
thisParallelCompareStartPos = this.x;
otherPerpendicularCompareStartPos = quad.z;
otherParallelCompareStartPos = quad.x;
}
else //if (mergeDirection == MergeDirection.NorthSouthOrUpDown)
{
thisPerpendicularCompareStartPos = this.y;
thisParallelCompareStartPos = this.z;
otherPerpendicularCompareStartPos = quad.y;
otherParallelCompareStartPos = quad.z;
}
break;
case Y:
if (mergeDirection == BufferMergeDirectionEnum.EastWest)
{
thisPerpendicularCompareStartPos = this.x;
thisParallelCompareStartPos = this.z;
otherPerpendicularCompareStartPos = quad.x;
otherParallelCompareStartPos = quad.z;
}
else //if (mergeDirection == MergeDirection.NorthSouthOrUpDown)
{
thisPerpendicularCompareStartPos = this.z;
thisParallelCompareStartPos = this.y;
otherPerpendicularCompareStartPos = quad.z;
otherParallelCompareStartPos = quad.y;
}
break;
case Z:
if (mergeDirection == BufferMergeDirectionEnum.EastWest)
{
thisPerpendicularCompareStartPos = this.x;
thisParallelCompareStartPos = this.z;
otherPerpendicularCompareStartPos = quad.x;
otherParallelCompareStartPos = quad.z;
}
else //if (mergeDirection == MergeDirection.NorthSouthOrUpDown)
{
thisPerpendicularCompareStartPos = this.y;
thisParallelCompareStartPos = this.z;
otherPerpendicularCompareStartPos = quad.y;
otherParallelCompareStartPos = quad.z;
}
break;
}
// get the width of this quad in the relevant axis
short thisPerpendicularCompareWidth;
short thisParallelCompareWidth;
short otherParallelCompareWidth;
if (mergeDirection == BufferMergeDirectionEnum.EastWest)
{
thisPerpendicularCompareWidth = this.widthEastWest;
thisParallelCompareWidth = this.widthNorthSouthOrUpDown;
otherParallelCompareWidth = quad.widthNorthSouthOrUpDown;
}
else
{
thisPerpendicularCompareWidth = this.widthNorthSouthOrUpDown;
thisParallelCompareWidth = this.widthEastWest;
otherParallelCompareWidth = quad.widthEastWest;
}
// check if these quads are adjacent
if (thisPerpendicularCompareStartPos + thisPerpendicularCompareWidth < otherPerpendicularCompareStartPos ||
thisParallelCompareStartPos != otherParallelCompareStartPos)
{
// these quads aren't adjacent, they can't be merged
return false;
}
else if (thisPerpendicularCompareStartPos + thisPerpendicularCompareWidth > otherPerpendicularCompareStartPos)
{
// these quads are overlapping, they can't be merged
EVENT_LOGGER.warn("Overlapping quads detected!");
quad.color = ColorUtil.rgbToInt(255, 0, 0);
return false;
}
// only merge quads that have the same width edges
if (thisParallelCompareWidth != otherParallelCompareWidth)
{
return false;
}
// do the quads' color, light, etc. match?
if (color != quad.color ||
skyLight != quad.skyLight ||
blockLight != quad.blockLight)
{
// we can only merge identically colored/lit quads
return false;
}
// merge the two quads
if (mergeDirection == BufferMergeDirectionEnum.NorthSouthOrUpDown)
{
widthNorthSouthOrUpDown += quad.widthNorthSouthOrUpDown;
}
else // if (mergeDirection == MergeDirection.EastWest)
{
widthEastWest += quad.widthEastWest;
}
// merge successful
return true;
}
}
@@ -19,19 +19,15 @@
package com.seibel.lod.core.builders.lodBuilding.bufferBuilding;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.enums.rendering.DebugMode;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.opengl.LodBox;
import com.seibel.lod.core.objects.opengl.LodQuadBuilder;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import java.awt.*;
/**
* Builds LODs as rectangular prisms.
* @author James Seibel
@@ -0,0 +1,468 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.lodBuilding.bufferBuilding;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.LodDirection.Axis;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.opengl.LodVertexBuffer;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import static com.seibel.lod.core.render.LodRenderer.EVENT_LOGGER;
/**
* Used to create the quads before they are converted to renderable buffers.
*
* @version 2022-4-9
*/
public class LodQuadBuilder
{
static final int MAX_BUFFER_SIZE = (1024 * 1024);
static final int QUAD_BYTE_SIZE = (12 * 6);
static final int MAX_QUADS_PER_BUFFER = MAX_BUFFER_SIZE / QUAD_BYTE_SIZE;
static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
public final boolean skipQuadsWithZeroSkylight;
public final short skyLightCullingBelow;
final ArrayList<BufferQuad>[] quads = (ArrayList<BufferQuad>[]) new ArrayList[6];
public static final int[][][] DIRECTION_VERTEX_QUAD = new int[][][]
{
// X,Z //
{ // UP
{ 1, 0 }, // 0
{ 1, 1 }, // 1
{ 0, 1 }, // 2
{ 1, 0 }, // 0
{ 0, 1 }, // 2
{ 0, 0 }, // 3
},
{ // DOWN
{ 0, 0 }, // 0
{ 0, 1 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 1, 0 }, // 3
},
// X,Y //
{ // NORTH
{ 0, 0 }, // 0
{ 0, 1 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 1, 0 }, // 3
},
{ // SOUTH
{ 1, 0 }, // 0
{ 1, 1 }, // 1
{ 0, 1 }, // 2
{ 1, 0 }, // 0
{ 0, 1 }, // 2
{ 0, 0 }, // 3
},
// Z,Y //
{ // WEST
{ 0, 0 }, // 0
{ 1, 0 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 0, 1 }, // 3
},
{ // EAST
{ 0, 1 }, // 0
{ 1, 1 }, // 1
{ 1, 0 }, // 2
{ 0, 1 }, // 0
{ 1, 0 }, // 2
{ 0, 0 }, // 3
},
};
public LodQuadBuilder(boolean enableSkylightCulling, int skyLightCullingBelow)
{
for (int i = 0; i < 6; i++)
quads[i] = new ArrayList<>();
this.skipQuadsWithZeroSkylight = enableSkylightCulling;
this.skyLightCullingBelow = (short) (skyLightCullingBelow - LodBuilder.MIN_WORLD_HEIGHT);
}
public void addQuadAdj(LodDirection dir, short x, short y, short z,
short widthEastWest, short widthNorthSouthOrUpDown,
int color, byte skylight, byte blocklight)
{
if (dir.ordinal() <= LodDirection.DOWN.ordinal())
throw new IllegalArgumentException("addQuadAdj() is only for adj direction! Not UP or Down!");
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[dir.ordinal()].add(new BufferQuad(x, y, z, widthEastWest, widthNorthSouthOrUpDown, color, skylight, blocklight, dir));
}
// XZ
public void addQuadUp(short x, short y, short z, short width, short wz, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.UP.ordinal()].add(new BufferQuad(x, y, z, width, wz, color, skylight, blocklight, LodDirection.UP));
}
public void addQuadDown(short x, short y, short z, short width, short wz, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.DOWN.ordinal()].add(new BufferQuad(x, y, z, width, wz, color, skylight, blocklight, LodDirection.DOWN));
}
// XY
public void addQuadN(short x, short y, short z, short width, short wy, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.NORTH.ordinal()].add(new BufferQuad(x, y, z, width, wy, color, skylight, blocklight, LodDirection.NORTH));
}
public void addQuadS(short x, short y, short z, short width, short wy, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.SOUTH.ordinal()].add(new BufferQuad(x, y, z, width, wy, color, skylight, blocklight, LodDirection.SOUTH));
}
// ZY
public void addQuadW(short x, short y, short z, short width, short wy, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.WEST.ordinal()].add(new BufferQuad(x, y, z, width, wy, color, skylight, blocklight, LodDirection.WEST));
}
public void addQuadE(short x, short y, short z, short width, short wy, int color, byte skylight, byte blocklight)
{
if (skipQuadsWithZeroSkylight && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.EAST.ordinal()].add(new BufferQuad(x, y, z, width, wy, color, skylight, blocklight, LodDirection.EAST));
}
private static void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte skylight, byte blocklight)
{
skylight %= 16;
blocklight %= 16;
bb.putShort(x);
bb.putShort(y);
bb.putShort(z);
bb.putShort((short) (skylight | (blocklight << 4)));
byte r = (byte) ColorUtil.getRed(color);
byte g = (byte) ColorUtil.getGreen(color);
byte b = (byte) ColorUtil.getBlue(color);
byte a = (byte) ColorUtil.getAlpha(color);
bb.put(r);
bb.put(g);
bb.put(b);
bb.put(a);
}
private static void putQuad(ByteBuffer bb, BufferQuad quad)
{
int[][] quadBase = DIRECTION_VERTEX_QUAD[quad.direction.ordinal()];
short widthEastWest = quad.widthEastWest;
short widthNorthSouth = quad.widthNorthSouthOrUpDown;
Axis axis = quad.direction.getAxis();
for (int i = 0; i < quadBase.length; i++)
{
short dx, dy, dz;
switch (axis)
{
case X: // ZY
dx = 0;
dy = quadBase[i][1] == 1 ? widthNorthSouth : 0;
dz = quadBase[i][0] == 1 ? widthEastWest : 0;
break;
case Y: // XZ
dx = quadBase[i][0] == 1 ? widthEastWest : 0;
dy = 0;
dz = quadBase[i][1] == 1 ? widthNorthSouth : 0;
break;
case Z: // XY
dx = quadBase[i][0] == 1 ? widthEastWest : 0;
dy = quadBase[i][1] == 1 ? widthNorthSouth : 0;
dz = 0;
break;
default:
throw new IllegalArgumentException("Invalid Axis enum: " + axis);
}
putVertex(bb, (short) (quad.x + dx), (short) (quad.y + dy), (short) (quad.z + dz), quad.color,
quad.skyLight, quad.blockLight);
}
}
/** Uses Greedy meshing to merge this builder's Quads. */
public void mergeQuads()
{
long mergeCount = 0;
long preQuadsCount = getCurrentQuadsCount();
if (preQuadsCount <= 1)
return;
for (int directionIndex = 0; directionIndex < 6; directionIndex++)
{
mergeCount += mergeQuadsInternal(directionIndex, BufferMergeDirectionEnum.EastWest);
// only merge after the top has been merged
if (directionIndex == 1)
{
long pass2 = mergeQuadsInternal(directionIndex, BufferMergeDirectionEnum.NorthSouthOrUpDown);
mergeCount += pass2;
}
}
long postQuadsCount = getCurrentQuadsCount();
//if (mergeCount != 0)
EVENT_LOGGER.debug("Merged {}/{}({}) quads", mergeCount, preQuadsCount, mergeCount / (double) preQuadsCount);
}
/** Merges all of this builder's quads for the given directionIndex (up, down, left, etc.) in the given direction */
private long mergeQuadsInternal(int directionIndex, BufferMergeDirectionEnum mergeDirection)
{
if (quads[directionIndex].size() <= 1)
return 0;
quads[directionIndex].sort( (objOne, objTwo) -> objOne.compare(objTwo, mergeDirection) );
long mergeCount = 0;
ListIterator<BufferQuad> iter = quads[directionIndex].listIterator();
BufferQuad currentQuad = iter.next();
while (iter.hasNext())
{
BufferQuad nextQuad = iter.next();
if (currentQuad.tryMerge(nextQuad, mergeDirection))
{
// merge successful, attempt to merge the next quad
mergeCount++;
iter.set(null);
}
else
{
// merge fail, move on to the next quad
currentQuad = nextQuad;
}
}
quads[directionIndex].removeIf(o -> o == null);
return mergeCount;
}
public Iterator<ByteBuffer> makeVertexBuffers()
{
return new Iterator<ByteBuffer>()
{
final ByteBuffer bb = ByteBuffer.allocateDirect(MAX_QUADS_PER_BUFFER * QUAD_BYTE_SIZE)
.order(ByteOrder.nativeOrder());
int dir = skipEmpty(0);
int quad = 0;
private int skipEmpty(int d)
{
while (d < 6 && quads[d].isEmpty())
d++;
return d;
}
@Override
public boolean hasNext()
{
return dir < 6;
}
@Override
public ByteBuffer next()
{
if (dir >= 6)
{
return null;
}
bb.clear();
bb.limit(MAX_QUADS_PER_BUFFER * QUAD_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData();
}
bb.limit(bb.position());
bb.rewind();
return bb;
}
private void writeData()
{
int i = quad;
for (; i < quads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, quads[dir].get(i));
}
if (i >= quads[dir].size())
{
quad = 0;
dir++;
dir = skipEmpty(dir);
}
else
{
quad = i;
}
}
};
}
public interface BufferFiller
{
/** If true: more data needs to be filled */
boolean fill(LodVertexBuffer vbo);
}
public BufferFiller makeBufferFiller(GpuUploadMethod method)
{
return new BufferFiller()
{
int dir = 0;
int quad = 0;
public boolean fill(LodVertexBuffer vbo)
{
if (dir >= 6)
{
vbo.vertexCount = 0;
return false;
}
int numOfQuads = _countRemainingQuads();
if (numOfQuads > MAX_QUADS_PER_BUFFER)
numOfQuads = MAX_QUADS_PER_BUFFER;
if (numOfQuads == 0)
{
vbo.vertexCount = 0;
return false;
}
ByteBuffer bb = vbo.mapBuffer(numOfQuads * QUAD_BYTE_SIZE, method, MAX_QUADS_PER_BUFFER * QUAD_BYTE_SIZE);
if (bb == null)
throw new NullPointerException("mapBuffer returned null");
bb.clear();
bb.limit(numOfQuads * QUAD_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData(bb);
}
bb.rewind();
vbo.unmapBuffer(method);
vbo.vertexCount = numOfQuads * 6;
return dir < 6;
}
private int _countRemainingQuads()
{
int a = quads[dir].size() - quad;
for (int i = dir + 1; i < quads.length; i++)
{
a += quads[i].size();
}
return a;
}
private void writeData(ByteBuffer bb)
{
int startQ = quad;
int i = startQ;
for (i = startQ; i < quads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, quads[dir].get(i));
}
if (i >= quads[dir].size())
{
quad = 0;
dir++;
while (dir < 6 && quads[dir].isEmpty())
dir++;
}
else
{
quad = i;
}
}
};
}
public int getCurrentQuadsCount()
{
int i = 0;
for (ArrayList<BufferQuad> qs : quads)
i += qs.size();
return i;
}
/** Returns how many Buffers will be needed to render everything in this builder. */
public int getCurrentNeededVertexBufferCount()
{
return LodUtil.ceilDiv(getCurrentQuadsCount(), MAX_QUADS_PER_BUFFER);
}
}
@@ -28,11 +28,18 @@ import java.util.stream.Collectors;
import com.seibel.lod.core.objects.math.Vec3i;
/**
* A (almost) exact copy of Minecraft's
* An (almost) exact copy of Minecraft's
* Direction enum.
*
*
* Up <Br>
* Down <Br>
* North <Br>
* South <Br>
* East <Br>
* West <Br>
*
* @author James Seibel
* @version 11-13-2021
* @version 2021-11-13
*/
public enum LodDirection
{
@@ -19,11 +19,11 @@
package com.seibel.lod.core.objects.opengl;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
public class LodBox
@@ -1,693 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.objects.opengl;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.LodDirection.Axis;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import static com.seibel.lod.core.render.LodRenderer.EVENT_LOGGER;
public class LodQuadBuilder
{
static final int MAX_BUFFER_SIZE = (1024 * 1024);
static final int QUAD_BYTE_SIZE = (12 * 6);
static final int MAX_QUADS_PER_BUFFER = MAX_BUFFER_SIZE / QUAD_BYTE_SIZE;
//static final int MAX_MERGED_QUAD_SIZE = 64;
static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
public final boolean skipSkylight0Quads;
public final short skyLightCullingBelow;
static class Quad
{
final short x;
final short y;
final short z;
short w0;
short w1;
int color;
final byte skylight;
final byte blocklight;
final LodDirection dir;
double distance = 0d;
Quad(short x, short y, short z, short w0, short w1, int color, byte skylight, byte blocklight,
LodDirection dir)
{
if (w0 == 0 || w1 == 0)
throw new IllegalArgumentException("Size 0 quad!");
if (w0 < 0 || w1 < 0)
throw new IllegalArgumentException("Negative sized quad!");
this.x = x;
this.y = y;
this.z = z;
this.w0 = w0;
this.w1 = w1;
this.color = color;
this.skylight = skylight;
this.blocklight = blocklight;
this.dir = dir;
}
private static double pow(double d)
{
return d * d;
}
// NOTE: This is only a rough but fast calculation!
void calculateDistance(double relativeX, double relativeY, double relativeZ)
{
distance = pow(relativeX - x) + pow(relativeY - y) + pow(relativeZ - z);
}
private static int _compondCompare(short a0, short a1, short a2, short b0, short b1, short b2)
{
long a = (long) a0 << 48 | (long) a1 << 32 | (long) a2 << 16;
long b = (long) b0 << 48 | (long) b1 << 32 | (long) b2 << 16;
return Long.compare(a, b);
}
public int compareTo1(Quad o)
{
if (dir != o.dir)
throw new IllegalArgumentException("The other quad is not in the same direction: " + o.dir + " vs " + dir);
switch (dir.getAxis())
{
case X:
return _compondCompare(x, y, z, o.x, o.y, o.z);
case Y:
return _compondCompare(y, z, x, o.y, o.z, o.x);
case Z:
return _compondCompare(z, y, x, o.z, o.y, o.x);
default:
throw new IllegalArgumentException("Invalid Axis enum: " + dir.getAxis());
}
}
public int compareTo2(Quad o)
{
if (dir != o.dir)
throw new IllegalArgumentException("The other quad is not in the same direction: " + o.dir + " vs " + dir);
switch (dir.getAxis())
{
case X:
return _compondCompare(x, z, y, o.x, o.z, o.y);
case Y:
return _compondCompare(y, x, z, o.y, o.x, o.z);
case Z:
return _compondCompare(z, x, y, o.z, o.x, o.y);
default:
throw new IllegalArgumentException("Invalid Axis enum: " + dir.getAxis());
}
}
public boolean tryMergeWith1(Quad o)
{
if (dir != o.dir)
return false;
//if (w0 >= MAX_MERGED_QUAD_SIZE) return false;
switch (dir.getAxis())
{
case X:
if (x != o.x ||
y != o.y ||
z + w0 < o.z)
return false;
if (z + w0 > o.z)
{
EVENT_LOGGER.warn("Overlapping quads detected!");
o.color = ColorUtil.rgbToInt(255, 0, 0);
return false;
}
if (w1 != o.w1 ||
color != o.color ||
skylight != o.skylight ||
blocklight != o.blocklight)
return false;
w0 += o.w0;
return true;
case Y:
if (y != o.y ||
z != o.z ||
x + w0 < o.x)
return false;
if (x + w0 > o.x)
{
EVENT_LOGGER.warn("Overlapping quads detected!");
o.color = ColorUtil.rgbToInt(255, 0, 0);
return false;
}
if (w1 != o.w1 ||
color != o.color ||
skylight != o.skylight ||
blocklight != o.blocklight)
return false;
w0 += o.w0;
return true;
case Z:
if (z != o.z ||
y != o.y ||
x + w0 < o.x)
return false;
if (x + w0 > o.x)
{
EVENT_LOGGER.warn("Overlapping quads detected!");
o.color = ColorUtil.rgbToInt(255, 0, 0);
return false;
}
if (w1 != o.w1 ||
color != o.color ||
skylight != o.skylight ||
blocklight != o.blocklight)
return false;
w0 += o.w0;
return true;
default:
throw new IllegalArgumentException("Invalid Axis enum: " + dir.getAxis());
}
}
public boolean tryMergeWith2(Quad o)
{
if (dir != o.dir)
return false;
//if (w1 >= MAX_MERGED_QUAD_SIZE) return false;
switch (dir.getAxis())
{
case X:
if (x != o.x ||
z != o.z ||
y + w1 < o.y)
return false;
if (y + w1 > o.y)
{
EVENT_LOGGER.warn("Overlapping quads detected!");
o.color = ColorUtil.rgbToInt(255, 0, 0);
return false;
}
if (w0 != o.w0 ||
color != o.color ||
skylight != o.skylight ||
blocklight != o.blocklight)
return false;
w1 += o.w1;
return true;
case Y:
if (y != o.y ||
x != o.x ||
z + w1 < o.z)
return false;
if (z + w1 > o.z)
{
EVENT_LOGGER.warn("Overlapping quads detected!");
o.color = ColorUtil.rgbToInt(255, 0, 0);
return false;
}
if (w0 != o.w0 ||
color != o.color ||
skylight != o.skylight ||
blocklight != o.blocklight)
return false;
w1 += o.w1;
return true;
case Z:
if (z != o.z ||
x != o.x ||
y + w1 < o.y)
return false;
if (y + w1 > o.y)
{
EVENT_LOGGER.warn("Overlapping quads detected!");
o.color = ColorUtil.rgbToInt(255, 0, 0);
return false;
}
if (w0 != o.w0 ||
color != o.color ||
skylight != o.skylight ||
blocklight != o.blocklight)
return false;
w1 += o.w1;
return true;
default:
throw new IllegalArgumentException("Invalid Axis enum: " + dir.getAxis());
}
}
}
final ArrayList<Quad>[] quads = (ArrayList<Quad>[]) new ArrayList[6];
public LodQuadBuilder(int initialSize, boolean enableSkylightCulling, int skyLightCullingBelow)
{
for (int i = 0; i < 6; i++)
quads[i] = new ArrayList<Quad>();
this.skipSkylight0Quads = enableSkylightCulling;
this.skyLightCullingBelow = (short) (skyLightCullingBelow - LodBuilder.MIN_WORLD_HEIGHT);
}
public void addQuadAdj(LodDirection dir, short x, short y, short z, short w0, short wy, int color, byte skylight,
byte blocklight)
{
if (dir.ordinal() <= LodDirection.DOWN.ordinal())
throw new IllegalArgumentException("addQuadAdj() is only for adj direction! Not UP or Down!");
if (skipSkylight0Quads && skylight == 0 && y < skyLightCullingBelow)
return;
quads[dir.ordinal()].add(new Quad(x, y, z, w0, wy, color, skylight, blocklight, dir));
}
// XZ
public void addQuadUp(short x, short y, short z, short wx, short wz, int color, byte skylight, byte blocklight)
{
if (skipSkylight0Quads && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.UP.ordinal()].add(new Quad(x, y, z, wx, wz, color, skylight, blocklight, LodDirection.UP));
}
public void addQuadDown(short x, short y, short z, short wx, short wz, int color, byte skylight, byte blocklight)
{
if (skipSkylight0Quads && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.DOWN.ordinal()].add(new Quad(x, y, z, wx, wz, color, skylight, blocklight, LodDirection.DOWN));
}
// XY
public void addQuadN(short x, short y, short z, short wx, short wy, int color, byte skylight, byte blocklight)
{
if (skipSkylight0Quads && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.NORTH.ordinal()].add(new Quad(x, y, z, wx, wy, color, skylight, blocklight, LodDirection.NORTH));
}
public void addQuadS(short x, short y, short z, short wx, short wy, int color, byte skylight, byte blocklight)
{
if (skipSkylight0Quads && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.SOUTH.ordinal()].add(new Quad(x, y, z, wx, wy, color, skylight, blocklight, LodDirection.SOUTH));
}
// ZY
public void addQuadW(short x, short y, short z, short wz, short wy, int color, byte skylight, byte blocklight)
{
if (skipSkylight0Quads && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.WEST.ordinal()].add(new Quad(x, y, z, wz, wy, color, skylight, blocklight, LodDirection.WEST));
}
public void addQuadE(short x, short y, short z, short wz, short wy, int color, byte skylight, byte blocklight)
{
if (skipSkylight0Quads && skylight == 0 && y < skyLightCullingBelow)
return;
quads[LodDirection.EAST.ordinal()].add(new Quad(x, y, z, wz, wy, color, skylight, blocklight, LodDirection.EAST));
}
private static void putVertex(ByteBuffer bb, short x, short y, short z, int color, byte skylight, byte blocklight)
{
skylight %= 16;
blocklight %= 16;
bb.putShort(x);
bb.putShort(y);
bb.putShort(z);
bb.putShort((short) (skylight | (blocklight << 4)));
byte r = (byte) ColorUtil.getRed(color);
byte g = (byte) ColorUtil.getGreen(color);
byte b = (byte) ColorUtil.getBlue(color);
byte a = (byte) ColorUtil.getAlpha(color);
bb.put(r);
bb.put(g);
bb.put(b);
bb.put(a);
}
private static void putQuad(ByteBuffer bb, Quad quad)
{
int[][] quadBase = DIRECTION_VERTEX_QUAD[quad.dir.ordinal()];
short d0 = quad.w0;
short d1 = quad.w1;
Axis axis = quad.dir.getAxis();
for (int i = 0; i < quadBase.length; i++)
{
short dx, dy, dz;
switch (axis)
{
case X: // ZY
dx = 0;
dz = quadBase[i][0] == 1 ? d0 : 0;
dy = quadBase[i][1] == 1 ? d1 : 0;
break;
case Y: // XZ
dy = 0;
dx = quadBase[i][0] == 1 ? d0 : 0;
dz = quadBase[i][1] == 1 ? d1 : 0;
break;
case Z: // XY
dz = 0;
dx = quadBase[i][0] == 1 ? d0 : 0;
dy = quadBase[i][1] == 1 ? d1 : 0;
break;
default:
throw new IllegalArgumentException("Invalid Axis enum: " + axis);
}
putVertex(bb, (short) (quad.x + dx), (short) (quad.y + dy), (short) (quad.z + dz), quad.color,
quad.skylight, quad.blocklight);
}
}
public void sort(double dPlayerPosX, double dPlayerPosY, double dPlayerPosZ)
{
}
private long mergeQuadsPass1(int dir)
{
if (quads[dir].size() <= 1)
return 0;
quads[dir].sort(Quad::compareTo1);
ListIterator<Quad> iter = quads[dir].listIterator();
long mergeCount = 0;
Quad currentQuad = iter.next();
while (iter.hasNext())
{
Quad nextQuad = iter.next();
if (currentQuad.tryMergeWith1(nextQuad))
{
mergeCount++;
iter.set(null);
}
else
{
currentQuad = nextQuad;
}
}
quads[dir].removeIf(o -> o == null);
return mergeCount;
}
private long mergeQuadsPass2(int dir)
{
if (quads[dir].size() <= 1)
return 0;
quads[dir].sort(Quad::compareTo2);
ListIterator<Quad> iter = quads[dir].listIterator();
long mergeCount = 0;
Quad currentQuad = iter.next();
while (iter.hasNext())
{
Quad nextQuad = iter.next();
if (currentQuad.tryMergeWith2(nextQuad))
{
mergeCount++;
iter.set(null);
}
else
{
currentQuad = nextQuad;
}
}
quads[dir].removeIf(o -> o == null);
return mergeCount;
}
public void mergeQuads()
{
long mergeCount = 0;
long preQuadsCount = getCurrentQuadsCount();
if (preQuadsCount <= 1)
return;
for (int i = 0; i < 6; i++)
{
mergeCount += mergeQuadsPass1(i);
if (i >= 2)
{
continue;
//long pass2 = mergeQuadsPass2(i);
//mergeCount += pass2;
//skipperMerge += pass2;
}
else
{
long pass2 = mergeQuadsPass2(i);
mergeCount += pass2;
}
}
long postQuadsCount = getCurrentQuadsCount();
//if (mergeCount != 0)
EVENT_LOGGER.debug("Merged {}/{}({}) quads", mergeCount, preQuadsCount, mergeCount / (double) preQuadsCount);
}
public Iterator<ByteBuffer> makeVertexBuffers()
{
return new Iterator<ByteBuffer>()
{
final ByteBuffer bb = ByteBuffer.allocateDirect(MAX_QUADS_PER_BUFFER * QUAD_BYTE_SIZE)
.order(ByteOrder.nativeOrder());
int dir = skipEmpty(0);
int quad = 0;
private int skipEmpty(int d)
{
while (d < 6 && quads[d].isEmpty())
d++;
return d;
}
@Override
public boolean hasNext()
{
return dir < 6;
}
@Override
public ByteBuffer next()
{
if (dir >= 6)
{
return null;
}
bb.clear();
bb.limit(MAX_QUADS_PER_BUFFER * QUAD_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData();
}
bb.limit(bb.position());
bb.rewind();
return bb;
}
private void writeData()
{
int startQ = quad;
int i = startQ;
for (i = startQ; i < quads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, quads[dir].get(i));
}
if (i >= quads[dir].size())
{
quad = 0;
dir++;
dir = skipEmpty(dir);
}
else
{
quad = i;
}
}
};
}
public interface BufferFiller
{
boolean fill(LodVertexBuffer vbo); // If true: means more data is needed to be filled
}
public BufferFiller makeBufferFiller(GpuUploadMethod method)
{
return new BufferFiller()
{
int dir = 0;
int quad = 0;
public boolean fill(LodVertexBuffer vbo)
{
if (dir >= 6)
{
vbo.vertexCount = 0;
return false;
}
int numOfQuads = _countRemainingQuads();
if (numOfQuads > MAX_QUADS_PER_BUFFER)
numOfQuads = MAX_QUADS_PER_BUFFER;
if (numOfQuads == 0)
{
vbo.vertexCount = 0;
return false;
}
ByteBuffer bb = vbo.mapBuffer(numOfQuads * QUAD_BYTE_SIZE, method, MAX_QUADS_PER_BUFFER * QUAD_BYTE_SIZE);
if (bb == null)
throw new NullPointerException("mapBuffer returned null");
bb.clear();
bb.limit(numOfQuads * QUAD_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData(bb);
}
bb.rewind();
vbo.unmapBuffer(method);
vbo.vertexCount = numOfQuads * 6;
return dir < 6;
}
private int _countRemainingQuads()
{
int a = quads[dir].size() - quad;
for (int i = dir + 1; i < quads.length; i++)
{
a += quads[i].size();
}
return a;
}
private void writeData(ByteBuffer bb)
{
int startQ = quad;
int i = startQ;
for (i = startQ; i < quads[dir].size(); i++)
{
if (!bb.hasRemaining())
{
break;
}
putQuad(bb, quads[dir].get(i));
}
if (i >= quads[dir].size())
{
quad = 0;
dir++;
while (dir < 6 && quads[dir].isEmpty())
dir++;
}
else
{
quad = i;
}
}
};
}
public int getCurrentQuadsCount()
{
int i = 0;
for (ArrayList<Quad> qs : quads)
i += qs.size();
return i;
}
public int getCurrentNeededVertexBuffers()
{
return LodUtil.ceilDiv(getCurrentQuadsCount(), MAX_QUADS_PER_BUFFER);
}
public static final int[][][] DIRECTION_VERTEX_QUAD = new int[][][] {
// X,Z
{ // UP
{ 1, 0 }, // 0
{ 1, 1 }, // 1
{ 0, 1 }, // 2
{ 1, 0 }, // 0
{ 0, 1 }, // 2
{ 0, 0 }, // 3
}, { // DOWN
{ 0, 0 }, // 0
{ 0, 1 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 1, 0 }, // 3
},
// X,Y
{ // NORTH
{ 0, 0 }, // 0
{ 0, 1 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 1, 0 }, // 3
}, { // SOUTH
{ 1, 0 }, // 0
{ 1, 1 }, // 1
{ 0, 1 }, // 2
{ 1, 0 }, // 0
{ 0, 1 }, // 2
{ 0, 0 }, // 3
},
// Z,Y
{ // WEST
{ 0, 0 }, // 0
{ 1, 0 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 0, 1 }, // 3
}, { // EAST
{ 0, 1 }, // 0
{ 1, 1 }, // 1
{ 1, 0 }, // 2
{ 0, 1 }, // 0
{ 1, 0 }, // 2
{ 0, 0 }, // 3
}, };
}
@@ -21,6 +21,7 @@ package com.seibel.lod.core.objects.opengl;
import java.util.ConcurrentModificationException;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.util.StatsMap;
@@ -29,6 +29,7 @@ import java.util.concurrent.atomic.AtomicReference;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.CubicLodTemplate;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.enums.rendering.DebugMode;
@@ -160,7 +161,7 @@ public class RenderRegion implements AutoCloseable
private void recreateBuffer(LodQuadBuilder builder) {
if (renderBufferBack != null) throw new RuntimeException("Assert Error");
boolean useSimpleBuffer = (builder.getCurrentNeededVertexBuffers() <= 6) || true;
boolean useSimpleBuffer = (builder.getCurrentNeededVertexBufferCount() <= 6) || true;
renderBufferBack = useSimpleBuffer ?
new SimpleRenderBuffer()
: null; //new ComplexRenderRegion(regPos);
@@ -192,7 +193,7 @@ public class RenderRegion implements AutoCloseable
int skyLightCullingBelow = CONFIG.client().graphics().advancedGraphics().getCaveCullingHeight();
// FIXME: Clamp also to the max world height.
skyLightCullingBelow = Math.max(skyLightCullingBelow, LodBuilder.MIN_WORLD_HEIGHT);
LodQuadBuilder builder = new LodQuadBuilder(10, useSkylightCulling, skyLightCullingBelow);
LodQuadBuilder builder = new LodQuadBuilder(useSkylightCulling, skyLightCullingBelow);
Runnable buildRun = ()->{
makeLodRenderData(builder, region, adjRegions, playerPosX, playerPosZ);
};
@@ -23,13 +23,14 @@ import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder;
import org.lwjgl.opengl.GL32;
import com.seibel.lod.core.api.ApiShared;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.objects.opengl.LodQuadBuilder.BufferFiller;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder.BufferFiller;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.util.LodUtil;
@@ -112,7 +113,7 @@ public class SimpleRenderBuffer extends RenderBuffer
}
private void _uploadBuffersDirect(LodQuadBuilder builder, GpuUploadMethod method) {
resize(builder.getCurrentNeededVertexBuffers());
resize(builder.getCurrentNeededVertexBufferCount());
long remainingNS = 0;
long BPerNS = CONFIG.client().advanced().buffers().getGpuUploadPerMegabyteInMilliseconds();
@@ -147,7 +148,7 @@ public class SimpleRenderBuffer extends RenderBuffer
private void _uploadBuffersMapped(LodQuadBuilder builder, GpuUploadMethod method)
{
resize(builder.getCurrentNeededVertexBuffers());
resize(builder.getCurrentNeededVertexBufferCount());
for (int i=0; i<vbos.length; i++) {
if (vbos[i]==null) vbos[i] = new LodVertexBuffer(method.useBufferStorage);
}