mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 01:34:48 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c578970a66 | ||
|
|
ef68af5850 | ||
|
|
b6a4497fb3 | ||
|
|
939e31461a | ||
|
|
02f0b0fca1 |
No files matched your search
@@ -0,0 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import org.lwjgl.opengl.OpenGLException;
|
||||
|
||||
final class GLErrorReporter {
|
||||
|
||||
private GLErrorReporter() {}
|
||||
|
||||
static void reportError(int error) {
|
||||
throw new OpenGLException(error);
|
||||
}
|
||||
}
|
||||
@@ -5398,6 +5398,11 @@ public class GLStateManager {
|
||||
return RENDER_BACKEND.getError();
|
||||
}
|
||||
|
||||
public static void checkGLError() {
|
||||
final int error = glGetError();
|
||||
if (error != GL11.GL_NO_ERROR) GLErrorReporter.reportError(error);
|
||||
}
|
||||
|
||||
public static String glGetString(int pname) {
|
||||
if (!RENDER_BACKEND.hasContext()) return "no valid GL/render context";
|
||||
return RENDER_BACKEND.getString(pname);
|
||||
|
||||
@@ -637,6 +637,11 @@ public class RenderSystem {
|
||||
GLStateManager.glSamplerParameteri(sampler, pname, param);
|
||||
}
|
||||
|
||||
public static void samplerParameterf(int sampler, int pname, float param) {
|
||||
if (!supportsSamplerObjects || sampler == 0) return;
|
||||
GLStateManager.glSamplerParameterf(sampler, pname, param);
|
||||
}
|
||||
|
||||
public static void bindSamplerToUnit(int unit, int sampler) {
|
||||
if (!supportsSamplerObjects) return;
|
||||
GLStateManager.glBindSampler(unit, sampler);
|
||||
|
||||
@@ -810,6 +810,8 @@ public class GLSMRedirector {
|
||||
.add("gluBuild2DMipmaps")
|
||||
.add("gluErrorString")
|
||||
);
|
||||
// Must honor disableErrorChecks like glGetError, or it throws on errors those checks left latched
|
||||
methodRedirects.put("org/lwjgl/opengl/Util", RedirectMap.newMap().add("checkGLError"));
|
||||
|
||||
// Quadric type replacements
|
||||
typeRedirects.put("org/lwjgl/util/glu/Sphere", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaSphere");
|
||||
|
||||
@@ -276,6 +276,11 @@ class GLSMRedirectorTest {
|
||||
assertTrue(unmappedSeen().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void utilCheckGLErrorIsRedirected() throws Exception {
|
||||
assertRedirectedToDeclaredTarget(new String[][]{{"org/lwjgl/opengl/Util", "checkGLError", "()V"}});
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexedGettersHaveRealTargets() throws Exception {
|
||||
final Class<?> glsm = Class.forName(GLSM.replace('/', '.'), false, getClass().getClassLoader());
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.gtnewhorizons.angelica.render;
|
||||
|
||||
/**
|
||||
* The shape of the drawn disc, how it splits into wedges, and how far out each kind of geometry should
|
||||
* really be worth building.
|
||||
*/
|
||||
final class CloudDisc {
|
||||
|
||||
static final int CELLS_PER_CHUNK = 8;
|
||||
static final int MARGIN_CELLS = 16;
|
||||
static final int WEDGE_COUNT = 32;
|
||||
static final double WEDGES_PER_RADIAN = WEDGE_COUNT / (2.0 * Math.PI);
|
||||
static final int ALWAYS_DRAWN_CELLS = Math.max(CELLS_PER_CHUNK, 4 * MARGIN_CELLS);
|
||||
private static final double WALL_CUT_PIXELS = 0.625;
|
||||
private static final double PLATE_LOD_PIXELS = 0.35;
|
||||
private static final double TAN_11_25_DEG = 0.19891236737965800; // √(4+2√2)−√2−1
|
||||
private static final double TAN_22_5_DEG = 0.41421356237309503; // √2−1
|
||||
private static final double TAN_33_75_DEG = 0.66817863791929892; // √(4−2√2)+1−√2
|
||||
private static final int DISTANCE_STEP_CELLS = 32;
|
||||
|
||||
static {
|
||||
if (WEDGE_COUNT != 32) {
|
||||
throw new AssertionError("wedgeOf splits four quadrants into eight; WEDGE_COUNT must be 32");
|
||||
}
|
||||
}
|
||||
|
||||
private CloudDisc() {
|
||||
}
|
||||
|
||||
static double cellCoordinate(double worldPosition, float cellWidthBlocks, double offset) {
|
||||
return worldPosition / cellWidthBlocks + offset;
|
||||
}
|
||||
|
||||
static float textureOffset(int anchor, int textureSize) {
|
||||
return Math.floorMod(anchor, textureSize) * (1.0f / textureSize);
|
||||
}
|
||||
|
||||
static boolean withinMargin(int driftX, int driftZ) {
|
||||
// Reject distant anchors before squaring
|
||||
return driftX >= -MARGIN_CELLS && driftX <= MARGIN_CELLS && driftZ >= -MARGIN_CELLS && driftZ <= MARGIN_CELLS && driftX * driftX + driftZ * driftZ <= MARGIN_CELLS * MARGIN_CELLS;
|
||||
}
|
||||
|
||||
static boolean withinDisc(long driftX, long driftZ, int radiusCells) {
|
||||
return driftX >= -radiusCells && driftX <= radiusCells && driftZ >= -radiusCells && driftZ <= radiusCells && driftX * driftX + driftZ * driftZ <= (long) radiusCells * radiusCells;
|
||||
}
|
||||
|
||||
static double pixelsPerRadian(int displayHeight, float fovDegrees) {
|
||||
if (!(fovDegrees > 0.0f) || fovDegrees >= 180.0f) return 0.0;
|
||||
return (displayHeight * 0.5) / Math.tan(Math.toRadians(fovDegrees) * 0.5);
|
||||
}
|
||||
|
||||
static int wallCutCells(double pixelsPerRadian, boolean platesInFront) {
|
||||
if (!platesInFront || pixelsPerRadian <= 0.0) return Integer.MAX_VALUE;
|
||||
|
||||
return roundUpToStep(pixelsPerRadian / (3.0 * WALL_CUT_PIXELS));
|
||||
}
|
||||
|
||||
static int plateLodCells(double pixelsPerRadian, float cloudBaseRelativeY, float cellWidthBlocks, boolean coarseCoverUsable) {
|
||||
if (!coarseCoverUsable || pixelsPerRadian <= 0.0) return Integer.MAX_VALUE;
|
||||
|
||||
final double heightBlocks = Math.max(1.0, Math.abs(cloudBaseRelativeY));
|
||||
return roundUpToStep(Math.sqrt(heightBlocks * pixelsPerRadian / (cellWidthBlocks * PLATE_LOD_PIXELS)));
|
||||
}
|
||||
|
||||
private static int roundUpToStep(double cells) {
|
||||
final long steps = (long) (cells / DISTANCE_STEP_CELLS) + 1;
|
||||
return (int) Math.min(steps * DISTANCE_STEP_CELLS, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
static int wedgeOf(int x, int z) {
|
||||
int foldedX = -x;
|
||||
int foldedZ = -z;
|
||||
if (foldedX == 0 && foldedZ == 0) return WEDGE_COUNT / 2;
|
||||
|
||||
final int quadrant;
|
||||
if (foldedX > 0 && foldedZ >= 0) {
|
||||
quadrant = 0;
|
||||
} else if (foldedX <= 0 && foldedZ > 0) {
|
||||
quadrant = 1;
|
||||
final int oldX = foldedX;
|
||||
foldedX = foldedZ;
|
||||
foldedZ = -oldX;
|
||||
} else if (foldedX < 0) {
|
||||
quadrant = 2;
|
||||
foldedX = -foldedX;
|
||||
foldedZ = -foldedZ;
|
||||
} else {
|
||||
quadrant = 3;
|
||||
final int oldX = foldedX;
|
||||
foldedX = -foldedZ;
|
||||
foldedZ = oldX;
|
||||
}
|
||||
|
||||
final int withinQuadrant;
|
||||
if (foldedZ < foldedX) {
|
||||
if (foldedZ < TAN_11_25_DEG * foldedX) withinQuadrant = 0;
|
||||
else if (foldedZ < TAN_22_5_DEG * foldedX) withinQuadrant = 1;
|
||||
else if (foldedZ < TAN_33_75_DEG * foldedX) withinQuadrant = 2;
|
||||
else withinQuadrant = 3;
|
||||
} else {
|
||||
if (foldedX > TAN_33_75_DEG * foldedZ) withinQuadrant = 4;
|
||||
else if (foldedX > TAN_22_5_DEG * foldedZ) withinQuadrant = 5;
|
||||
else if (foldedX > TAN_11_25_DEG * foldedZ) withinQuadrant = 6;
|
||||
else withinQuadrant = 7;
|
||||
}
|
||||
|
||||
return quadrant * 8 + withinQuadrant;
|
||||
}
|
||||
|
||||
static double driftAngle(double driftCells) {
|
||||
final double nearestCulledCells = ALWAYS_DRAWN_CELLS - driftCells;
|
||||
return nearestCulledCells <= driftCells ? Math.PI / 2.0 : Math.asin(driftCells / nearestCulledCells);
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.render;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
final class CloudEdges {
|
||||
private static final int CELL_EMPTY_THRESHOLD = 10;
|
||||
|
||||
final int width, height;
|
||||
private final int wMask, hMask;
|
||||
private final boolean whPow2;
|
||||
private final long[] opaqueBits;
|
||||
final int opaqueCount;
|
||||
|
||||
CloudEdges(int width, int height, ByteBuffer rgba) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.wMask = width - 1;
|
||||
this.hMask = height - 1;
|
||||
this.whPow2 = (width & wMask) == 0 && (height & hMask) == 0;
|
||||
final int total = width * height;
|
||||
final long[] bits = new long[(total + 63) >>> 6];
|
||||
int op = 0;
|
||||
for (int i = 0; i < total; i++) {
|
||||
if ((rgba.get(i * 4 + 3) & 0xFF) >= CELL_EMPTY_THRESHOLD) {
|
||||
bits[i >>> 6] |= 1L << (i & 63);
|
||||
op++;
|
||||
}
|
||||
}
|
||||
this.opaqueBits = bits;
|
||||
this.opaqueCount = op;
|
||||
}
|
||||
|
||||
boolean cellOpaque(int x, int z) {
|
||||
final int xw, zh;
|
||||
if (whPow2) {
|
||||
xw = x & wMask;
|
||||
zh = z & hMask;
|
||||
} else {
|
||||
xw = Math.floorMod(x, width);
|
||||
zh = Math.floorMod(z, height);
|
||||
}
|
||||
final int idx = xw + zh * width;
|
||||
return ((opaqueBits[idx >>> 6] >>> (idx & 63)) & 1L) != 0L;
|
||||
}
|
||||
|
||||
long readOpaqueRowBits(int x0, int z) {
|
||||
final int zMod, x0Mod;
|
||||
if (whPow2) {
|
||||
zMod = z & hMask;
|
||||
x0Mod = x0 & wMask;
|
||||
} else {
|
||||
zMod = Math.floorMod(z, height);
|
||||
x0Mod = Math.floorMod(x0, width);
|
||||
}
|
||||
if (x0Mod + 10 <= width) {
|
||||
return readContigBits(opaqueBits, (long) zMod * width + x0Mod, 10);
|
||||
}
|
||||
final int colsBeforeWrap = width - x0Mod;
|
||||
final long firstPart = readContigBits(opaqueBits, (long) zMod * width + x0Mod, colsBeforeWrap);
|
||||
final long secondPart = readContigBits(opaqueBits, (long) zMod * width, 10 - colsBeforeWrap);
|
||||
return firstPart | (secondPart << colsBeforeWrap);
|
||||
}
|
||||
|
||||
private static long readContigBits(long[] bits, long startBitIdx, int count) {
|
||||
final int wordIdx = (int) (startBitIdx >>> 6);
|
||||
final int bitOff = (int) (startBitIdx & 63);
|
||||
final long mask = count == 64 ? -1L : (1L << count) - 1L;
|
||||
final long w0 = bits[wordIdx];
|
||||
if (bitOff + count <= 64) {
|
||||
return (w0 >>> bitOff) & mask;
|
||||
}
|
||||
final long w1 = bits[wordIdx + 1];
|
||||
return ((w0 >>> bitOff) | (w1 << (64 - bitOff))) & mask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
package com.gtnewhorizons.angelica.render;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.ALWAYS_DRAWN_CELLS;
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.MARGIN_CELLS;
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.WEDGE_COUNT;
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.wedgeOf;
|
||||
|
||||
/**
|
||||
* Walls as one instanced face each.
|
||||
*/
|
||||
final class CloudFaceMesh {
|
||||
|
||||
static final int DIR_DOWN = 0, DIR_UP = 1, DIR_NORTH = 2, DIR_SOUTH = 3, DIR_WEST = 4, DIR_EAST = 5;
|
||||
static final int CELL_LIMIT = 511;
|
||||
private static final int FLAG_INSIDE = 1;
|
||||
private static final int FACE_ATTRIB = 0;
|
||||
/** cellX 10b | cellZ 10b | dir 3b | flags 2b. */
|
||||
private static final int FACE_BYTES = 4;
|
||||
private static final int INTERIOR_FACES = 6;
|
||||
private static final int BUCKET_COUNT = WEDGE_COUNT + 1;
|
||||
private static final int BUCKET_ALWAYS_DRAWN = 0;
|
||||
private static final int BUCKET_WALL_WEDGE = 1;
|
||||
|
||||
private static final Tracy.ZoneId Z_STAMP_WALLS = Tracy.zoneId("cloudFaceStampWalls", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_UPLOAD = Tracy.zoneId("cloudFaceUpload", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_REORDER = Tracy.zoneId("cloudFaceReorder", Tracy.COLOR_CLIENT);
|
||||
private final int[] wallStart = new int[WEDGE_COUNT + 1];
|
||||
private final int[] plateStart = new int[WEDGE_COUNT + 1];
|
||||
private int vao = -1;
|
||||
private int vbo = -1;
|
||||
private int ebo = -1;
|
||||
private int vboCapacityBytes;
|
||||
private int uploadedCount;
|
||||
private int stampedCount;
|
||||
private int interiorCount;
|
||||
private int[] faceData = new int[4096];
|
||||
private int[] faceSortKey = new int[4096];
|
||||
private int[] sortKeyStart = new int[0];
|
||||
private int ringsPerBucket = 1;
|
||||
private int buildAnchorX = Integer.MIN_VALUE, buildAnchorZ = Integer.MIN_VALUE;
|
||||
private int orderAnchorX = Integer.MIN_VALUE, orderAnchorZ = Integer.MIN_VALUE;
|
||||
private CloudShape shape;
|
||||
private ByteBuffer uploadBuffer;
|
||||
|
||||
int uploadedCount() {
|
||||
return uploadedCount;
|
||||
}
|
||||
|
||||
int interiorCount() {
|
||||
return interiorCount;
|
||||
}
|
||||
|
||||
int buildAnchorX() {
|
||||
return buildAnchorX;
|
||||
}
|
||||
|
||||
int buildAnchorZ() {
|
||||
return buildAnchorZ;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
uploadedCount = 0;
|
||||
interiorCount = 0;
|
||||
}
|
||||
|
||||
void build(CloudShape shape, int anchorX, int anchorZ, int radiusCells, int radiusCellsSq, int wallCutCells) {
|
||||
stampedCount = 0;
|
||||
this.shape = shape;
|
||||
ringsPerBucket = 2 * (radiusCells + MARGIN_CELLS) + 1;
|
||||
buildAnchorX = anchorX;
|
||||
buildAnchorZ = anchorZ;
|
||||
orderAnchorX = anchorX;
|
||||
orderAnchorZ = anchorZ;
|
||||
|
||||
if (Tracy.FINE_ZONES) Tracy.beginZone(Z_STAMP_WALLS);
|
||||
addWalls(shape, anchorX, anchorZ, radiusCells, radiusCellsSq, wallCutCells);
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
|
||||
if (Tracy.FINE_ZONES) Tracy.beginZone(Z_UPLOAD);
|
||||
finishBuild();
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
}
|
||||
|
||||
private static int packFace(int cellX, int cellZ, int dir, int flags) {
|
||||
return ((cellX + 512) & 0x3FF)
|
||||
| (((cellZ + 512) & 0x3FF) << 10)
|
||||
| (dir << 20)
|
||||
| (flags << 23);
|
||||
}
|
||||
|
||||
private void addFace(int cellX, int cellZ, int dir, int bucket, int ring) {
|
||||
if (stampedCount + INTERIOR_FACES == faceSortKey.length) {
|
||||
faceSortKey = Arrays.copyOf(faceSortKey, faceSortKey.length * 2);
|
||||
faceData = Arrays.copyOf(faceData, faceData.length * 2);
|
||||
}
|
||||
faceData[stampedCount] = packFace(cellX, cellZ, dir, 0);
|
||||
faceSortKey[stampedCount] = bucket * ringsPerBucket + ring;
|
||||
stampedCount++;
|
||||
}
|
||||
|
||||
private void stampInterior(int anchorX, int anchorZ) {
|
||||
interiorCount = 0;
|
||||
if (shape == null || (shape.cellFlagsAt(anchorX, anchorZ) & CloudShape.OPAQUE) == 0) return;
|
||||
|
||||
final int driftX = anchorX - buildAnchorX;
|
||||
final int driftZ = anchorZ - buildAnchorZ;
|
||||
for (int dir = DIR_DOWN; dir <= DIR_EAST; dir++) {
|
||||
faceData[uploadedCount + dir] = packFace(driftX, driftZ, dir, FLAG_INSIDE);
|
||||
}
|
||||
interiorCount = INTERIOR_FACES;
|
||||
}
|
||||
|
||||
private void addWalls(CloudShape shape, int anchorX, int anchorZ, int radiusCells, int radiusCellsSq, int wallCutCells) {
|
||||
stampWallRuns(shape, shape.westRuns, DIR_WEST, true, anchorX, anchorZ, radiusCells, radiusCellsSq, wallCutCells);
|
||||
stampWallRuns(shape, shape.eastRuns, DIR_EAST, true, anchorX, anchorZ, radiusCells, radiusCellsSq, wallCutCells);
|
||||
stampWallRuns(shape, shape.northRuns, DIR_NORTH, false, anchorX, anchorZ, radiusCells, radiusCellsSq, wallCutCells);
|
||||
stampWallRuns(shape, shape.southRuns, DIR_SOUTH, false, anchorX, anchorZ, radiusCells, radiusCellsSq, wallCutCells);
|
||||
}
|
||||
|
||||
private void stampWallRuns(CloudShape shape, int[] runs, int dir, boolean alongZ, int anchorX, int anchorZ,
|
||||
int radiusCells, int radiusCellsSq, int wallCutCells) {
|
||||
final int texW = shape.width;
|
||||
final int texH = shape.height;
|
||||
final int minCell = -radiusCells;
|
||||
final int maxCell = radiusCells;
|
||||
final int nearAlwaysSq = ALWAYS_DRAWN_CELLS * ALWAYS_DRAWN_CELLS;
|
||||
final int wallCutSq = wallCutCells >= radiusCells ? Integer.MAX_VALUE : wallCutCells * wallCutCells;
|
||||
|
||||
for (int r = 0; r < runs.length; r += 3) {
|
||||
final int runLength = runs[r + 2];
|
||||
final int baseX = Math.floorMod(runs[r] - anchorX, texW);
|
||||
final int baseZ = Math.floorMod(runs[r + 1] - anchorZ, texH);
|
||||
|
||||
for (int kz = Math.floorDiv(minCell - baseZ - (alongZ ? runLength - 1 : 0), texH); kz <= Math.floorDiv(maxCell - baseZ, texH); kz++) {
|
||||
final int runZ = baseZ + kz * texH;
|
||||
for (int kx = Math.floorDiv(minCell - baseX - (alongZ ? 0 : runLength - 1), texW); kx <= Math.floorDiv(maxCell - baseX, texW); kx++) {
|
||||
final int runX = baseX + kx * texW;
|
||||
|
||||
for (int step = 0; step < runLength; step++) {
|
||||
final int cellX = alongZ ? runX : runX + step;
|
||||
final int cellZ = alongZ ? runZ + step : runZ;
|
||||
|
||||
if (dir == DIR_WEST ? cellX < 1 - MARGIN_CELLS
|
||||
: dir == DIR_EAST ? cellX > MARGIN_CELLS - 1
|
||||
: dir == DIR_NORTH ? cellZ < 1 - MARGIN_CELLS
|
||||
: cellZ > MARGIN_CELLS - 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final int distSq = cellX * cellX + cellZ * cellZ;
|
||||
if (distSq > radiusCellsSq) continue;
|
||||
final boolean near = distSq <= nearAlwaysSq;
|
||||
if (!near && distSq > wallCutSq) continue;
|
||||
|
||||
final int bucket = near ? BUCKET_ALWAYS_DRAWN : BUCKET_WALL_WEDGE + wedgeOf(cellX, cellZ);
|
||||
addFace(cellX, cellZ, dir, bucket, Math.abs(cellX) + Math.abs(cellZ));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean orderStale(int anchorX, int anchorZ) {
|
||||
return anchorX != orderAnchorX || anchorZ != orderAnchorZ;
|
||||
}
|
||||
|
||||
void reorder(int anchorX, int anchorZ) {
|
||||
if (Tracy.FINE_ZONES) Tracy.beginZone(Z_REORDER);
|
||||
try {
|
||||
final int driftX = anchorX - buildAnchorX;
|
||||
final int driftZ = anchorZ - buildAnchorZ;
|
||||
final int nearAlwaysSq = ALWAYS_DRAWN_CELLS * ALWAYS_DRAWN_CELLS;
|
||||
for (int i = 0; i < uploadedCount; i++) {
|
||||
final int packed = faceData[i];
|
||||
final int cellX = (packed & 0x3FF) - 512 - driftX;
|
||||
final int cellZ = ((packed >> 10) & 0x3FF) - 512 - driftZ;
|
||||
final int distSq = cellX * cellX + cellZ * cellZ;
|
||||
final int bucket = distSq <= nearAlwaysSq ? BUCKET_ALWAYS_DRAWN : BUCKET_WALL_WEDGE + wedgeOf(cellX, cellZ);
|
||||
final int ring = Math.min(Math.abs(cellX) + Math.abs(cellZ), ringsPerBucket - 1);
|
||||
faceSortKey[i] = bucket * ringsPerBucket + ring;
|
||||
}
|
||||
stampInterior(anchorX, anchorZ);
|
||||
sortAndUpload();
|
||||
orderAnchorX = anchorX;
|
||||
orderAnchorZ = anchorZ;
|
||||
} finally {
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
}
|
||||
}
|
||||
|
||||
private void finishBuild() {
|
||||
uploadedCount = stampedCount;
|
||||
stampInterior(buildAnchorX, buildAnchorZ);
|
||||
if (uploadedCount == 0) Arrays.fill(wallStart, 0);
|
||||
sortAndUpload();
|
||||
}
|
||||
|
||||
private void sortAndUpload() {
|
||||
final int sortKeyCount = BUCKET_COUNT * ringsPerBucket;
|
||||
if (sortKeyStart.length < sortKeyCount + 1) sortKeyStart = new int[sortKeyCount + 1];
|
||||
else Arrays.fill(sortKeyStart, 0, sortKeyCount + 1, 0);
|
||||
|
||||
for (int i = 0; i < uploadedCount; i++) sortKeyStart[faceSortKey[i]]++;
|
||||
int acc = 0;
|
||||
for (int k = 0; k < sortKeyCount; k++) {
|
||||
final int keyTotal = sortKeyStart[k];
|
||||
sortKeyStart[k] = acc;
|
||||
acc += keyTotal;
|
||||
}
|
||||
sortKeyStart[sortKeyCount] = acc;
|
||||
|
||||
for (int w = 0; w < WEDGE_COUNT; w++) wallStart[w] = sortKeyStart[(BUCKET_WALL_WEDGE + w) * ringsPerBucket];
|
||||
wallStart[WEDGE_COUNT] = uploadedCount;
|
||||
|
||||
final int totalFaces = uploadedCount + interiorCount;
|
||||
if (totalFaces == 0) return;
|
||||
|
||||
final int uploadBytes = totalFaces * FACE_BYTES;
|
||||
if (uploadBuffer == null || uploadBuffer.capacity() < uploadBytes) {
|
||||
uploadBuffer = BufferUtils.createByteBuffer(Math.max(uploadBytes, 8192));
|
||||
}
|
||||
uploadBuffer.clear();
|
||||
final IntBuffer ints = uploadBuffer.asIntBuffer();
|
||||
for (int i = 0; i < uploadedCount; i++) {
|
||||
ints.put(sortKeyStart[faceSortKey[i]]++, faceData[i]);
|
||||
}
|
||||
for (int i = 0; i < interiorCount; i++) {
|
||||
ints.put(uploadedCount + i, faceData[uploadedCount + i]);
|
||||
}
|
||||
uploadBuffer.position(0).limit(uploadBytes);
|
||||
|
||||
final boolean freshBuffers = vbo == -1;
|
||||
if (freshBuffers) vbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
if (uploadBytes > vboCapacityBytes) {
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, uploadBuffer, GL15.GL_DYNAMIC_DRAW);
|
||||
vboCapacityBytes = uploadBytes;
|
||||
} else {
|
||||
GLStateManager.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer);
|
||||
}
|
||||
|
||||
if (freshBuffers) {
|
||||
ensureQuadIndices();
|
||||
if (vao == -1) vao = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glEnableVertexAttribArray(FACE_ATTRIB);
|
||||
GLStateManager.glVertexAttribDivisor(FACE_ATTRIB, 1);
|
||||
GLStateManager.glVertexAttribIPointer(FACE_ATTRIB, 1, GL11.GL_INT, FACE_BYTES, 0L);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
private void ensureQuadIndices() {
|
||||
if (ebo != -1) return;
|
||||
ebo = GLStateManager.glGenBuffers();
|
||||
final IntBuffer indices = BufferUtils.createIntBuffer(6);
|
||||
indices.put(0).put(1).put(2).put(0).put(2).put(3);
|
||||
indices.flip();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLStateManager.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indices, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
void bind() {
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
}
|
||||
|
||||
void unbind() {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void drawRange(int startFace, int endFace) {
|
||||
final int faces = endFace - startFace;
|
||||
if (faces <= 0) return;
|
||||
GLStateManager.glVertexAttribIPointer(FACE_ATTRIB, 1, GL11.GL_INT, FACE_BYTES, (long) startFace * FACE_BYTES);
|
||||
GLStateManager.glDrawElementsInstanced(GL11.GL_TRIANGLES, 6, GL11.GL_UNSIGNED_INT, 0L, faces);
|
||||
}
|
||||
|
||||
void drawInterior() {
|
||||
drawRange(uploadedCount, uploadedCount + interiorCount);
|
||||
}
|
||||
|
||||
int[] plateStarts() {
|
||||
return plateStart;
|
||||
}
|
||||
|
||||
int[] wallStarts() {
|
||||
return wallStart;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.gtnewhorizons.angelica.render;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.ALWAYS_DRAWN_CELLS;
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.WEDGE_COUNT;
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.wedgeOf;
|
||||
|
||||
/**
|
||||
* The plate rectangles that land inside the disc for one anchor.
|
||||
*/
|
||||
final class CloudPlateCover {
|
||||
|
||||
private final int[] wedgeBucketCount = new int[WEDGE_COUNT + 2];
|
||||
private int[] rects = new int[4096 * 4];
|
||||
private int[] rectWedgeBucket = new int[4096];
|
||||
private int[] orderedRects = new int[4096];
|
||||
private int count;
|
||||
private boolean groupByWedge = true;
|
||||
|
||||
int count() {
|
||||
return count;
|
||||
}
|
||||
|
||||
int orderedRect(int order) {
|
||||
return orderedRects[order];
|
||||
}
|
||||
|
||||
int minX(int rect) {
|
||||
return rects[rect * 4];
|
||||
}
|
||||
|
||||
int minZ(int rect) {
|
||||
return rects[rect * 4 + 1];
|
||||
}
|
||||
|
||||
int maxX(int rect) {
|
||||
return rects[rect * 4 + 2];
|
||||
}
|
||||
|
||||
int maxZ(int rect) {
|
||||
return rects[rect * 4 + 3];
|
||||
}
|
||||
|
||||
int wedgeBucket(int rect) {
|
||||
return rectWedgeBucket[rect];
|
||||
}
|
||||
|
||||
void build(CloudShape shape, int anchorX, int anchorZ, int radiusCells, int radiusCellsSq, int lodCells, boolean groupByWedge) {
|
||||
count = 0;
|
||||
this.groupByWedge = groupByWedge;
|
||||
Arrays.fill(wedgeBucketCount, 0);
|
||||
|
||||
final int texW = shape.width;
|
||||
final int texH = shape.height;
|
||||
final int[] coarseRects = shape.coarsePlateRects;
|
||||
|
||||
if (coarseRects == null || lodCells >= radiusCells) {
|
||||
stampRects(shape.plateRects, anchorX, anchorZ, texW, texH, radiusCells, radiusCellsSq, 0, 0, -1, -1, false);
|
||||
} else {
|
||||
final int fineX0 = 2 * Math.floorDiv(anchorX - lodCells, 2) - anchorX;
|
||||
final int fineZ0 = 2 * Math.floorDiv(anchorZ - lodCells, 2) - anchorZ;
|
||||
final int fineX1 = 2 * Math.floorDiv(anchorX + lodCells, 2) + 1 - anchorX;
|
||||
final int fineZ1 = 2 * Math.floorDiv(anchorZ + lodCells, 2) + 1 - anchorZ;
|
||||
stampRects(shape.plateRects, anchorX, anchorZ, texW, texH, radiusCells, radiusCellsSq, fineX0, fineZ0, fineX1, fineZ1, false);
|
||||
stampRects(coarseRects, anchorX, anchorZ, texW, texH, radiusCells, radiusCellsSq, fineX0, fineZ0, fineX1, fineZ1, true);
|
||||
}
|
||||
|
||||
if (groupByWedge) orderByWedge();
|
||||
}
|
||||
|
||||
private void stampRects(int[] sourceRects, int anchorX, int anchorZ, int texW, int texH, int radiusCells, int radiusCellsSq,
|
||||
int fineX0, int fineZ0, int fineX1, int fineZ1, boolean outsideFineBox) {
|
||||
final int minCell = -radiusCells;
|
||||
final int maxCell = radiusCells;
|
||||
|
||||
for (int r = 0; r < sourceRects.length; r += 4) {
|
||||
final int rectW = sourceRects[r + 2];
|
||||
final int rectH = sourceRects[r + 3];
|
||||
final int baseX = Math.floorMod(sourceRects[r] - anchorX, texW);
|
||||
final int baseZ = Math.floorMod(sourceRects[r + 1] - anchorZ, texH);
|
||||
|
||||
for (int kz = Math.floorDiv(minCell - rectH + 1 - baseZ, texH); kz <= Math.floorDiv(maxCell - baseZ, texH); kz++) {
|
||||
final int cellZ0 = Math.max(baseZ + kz * texH, minCell);
|
||||
final int cellZ1 = Math.min(baseZ + kz * texH + rectH - 1, maxCell);
|
||||
if (cellZ0 > cellZ1) continue;
|
||||
|
||||
for (int kx = Math.floorDiv(minCell - rectW + 1 - baseX, texW); kx <= Math.floorDiv(maxCell - baseX, texW); kx++) {
|
||||
final int cellX0 = Math.max(baseX + kx * texW, minCell);
|
||||
final int cellX1 = Math.min(baseX + kx * texW + rectW - 1, maxCell);
|
||||
if (cellX0 > cellX1) continue;
|
||||
|
||||
final boolean touchesFineBox = fineX1 >= fineX0
|
||||
&& cellX1 >= fineX0 && cellX0 <= fineX1 && cellZ1 >= fineZ0 && cellZ0 <= fineZ1;
|
||||
|
||||
if (fineX1 < fineX0) {
|
||||
clipToDisc(cellX0, cellZ0, cellX1, cellZ1, radiusCellsSq);
|
||||
} else if (!touchesFineBox) {
|
||||
if (outsideFineBox) clipToDisc(cellX0, cellZ0, cellX1, cellZ1, radiusCellsSq);
|
||||
} else if (!outsideFineBox) {
|
||||
clipToDisc(Math.max(cellX0, fineX0), Math.max(cellZ0, fineZ0),
|
||||
Math.min(cellX1, fineX1), Math.min(cellZ1, fineZ1), radiusCellsSq);
|
||||
} else {
|
||||
clipToDisc(cellX0, cellZ0, cellX1, Math.min(cellZ1, fineZ0 - 1), radiusCellsSq);
|
||||
clipToDisc(cellX0, Math.max(cellZ0, fineZ1 + 1), cellX1, cellZ1, radiusCellsSq);
|
||||
final int midZ0 = Math.max(cellZ0, fineZ0);
|
||||
final int midZ1 = Math.min(cellZ1, fineZ1);
|
||||
clipToDisc(cellX0, midZ0, Math.min(cellX1, fineX0 - 1), midZ1, radiusCellsSq);
|
||||
clipToDisc(Math.max(cellX0, fineX1 + 1), midZ0, cellX1, midZ1, radiusCellsSq);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clipToDisc(int cellX0, int cellZ0, int cellX1, int cellZ1, int radiusCellsSq) {
|
||||
if (cellX0 > cellX1 || cellZ0 > cellZ1) return;
|
||||
|
||||
final int farthestX = Math.max(Math.abs(cellX0), Math.abs(cellX1));
|
||||
final int farthestZ = Math.max(Math.abs(cellZ0), Math.abs(cellZ1));
|
||||
if (farthestX * farthestX + farthestZ * farthestZ <= radiusCellsSq) {
|
||||
addRect(cellX0, cellZ0, cellX1, cellZ1);
|
||||
return;
|
||||
}
|
||||
|
||||
final int nearestX = (cellX0 <= 0 && cellX1 >= 0) ? 0 : Math.min(Math.abs(cellX0), Math.abs(cellX1));
|
||||
final int nearestZ = (cellZ0 <= 0 && cellZ1 >= 0) ? 0 : Math.min(Math.abs(cellZ0), Math.abs(cellZ1));
|
||||
if (nearestX * nearestX + nearestZ * nearestZ > radiusCellsSq) return;
|
||||
|
||||
for (int cellZ = cellZ0; cellZ <= cellZ1; cellZ++) {
|
||||
final int halfWidth = (int) Math.sqrt(radiusCellsSq - cellZ * cellZ);
|
||||
final int rowX0 = Math.max(cellX0, -halfWidth);
|
||||
final int rowX1 = Math.min(cellX1, halfWidth);
|
||||
if (rowX0 > rowX1) continue;
|
||||
addRect(rowX0, cellZ, rowX1, cellZ);
|
||||
}
|
||||
}
|
||||
|
||||
private void addRect(int cellX0, int cellZ0, int cellX1, int cellZ1) {
|
||||
int bucket = 0;
|
||||
if (groupByWedge) {
|
||||
final int nearestX = (cellX0 <= 0 && cellX1 >= 0) ? 0 : Math.min(Math.abs(cellX0), Math.abs(cellX1));
|
||||
final int nearestZ = (cellZ0 <= 0 && cellZ1 >= 0) ? 0 : Math.min(Math.abs(cellZ0), Math.abs(cellZ1));
|
||||
final boolean near = nearestX * nearestX + nearestZ * nearestZ <= ALWAYS_DRAWN_CELLS * ALWAYS_DRAWN_CELLS;
|
||||
|
||||
final int wedge = wedgeOf(cellX0, cellZ0);
|
||||
if (!near
|
||||
&& wedge == wedgeOf(cellX1 + 1, cellZ0) && wedge == wedgeOf(cellX0, cellZ1 + 1) && wedge == wedgeOf(cellX1 + 1, cellZ1 + 1)) {
|
||||
bucket = wedge + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (count * 4 + 4 > rects.length) {
|
||||
rects = Arrays.copyOf(rects, rects.length * 2);
|
||||
rectWedgeBucket = Arrays.copyOf(rectWedgeBucket, rectWedgeBucket.length * 2);
|
||||
}
|
||||
final int slot = count * 4;
|
||||
rects[slot] = cellX0;
|
||||
rects[slot + 1] = cellZ0;
|
||||
rects[slot + 2] = cellX1;
|
||||
rects[slot + 3] = cellZ1;
|
||||
rectWedgeBucket[count] = bucket;
|
||||
wedgeBucketCount[bucket]++;
|
||||
count++;
|
||||
}
|
||||
|
||||
private void orderByWedge() {
|
||||
int acc = 0;
|
||||
for (int b = 0; b <= WEDGE_COUNT + 1; b++) {
|
||||
final int bucketTotal = wedgeBucketCount[b];
|
||||
wedgeBucketCount[b] = acc;
|
||||
acc += bucketTotal;
|
||||
}
|
||||
if (orderedRects.length < count) orderedRects = new int[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
orderedRects[wedgeBucketCount[rectWedgeBucket[i]]++] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,193 @@
|
||||
package com.gtnewhorizons.angelica.render;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Takes {@code clouds.png}, figures out which cells are cloud cells, precomputes greedy rectangles for the
|
||||
* plates, and merges the exposed edges into wall runs.
|
||||
*/
|
||||
final class CloudShape {
|
||||
static final int OPAQUE = 1 << 4;
|
||||
static final int WEST_EMPTY = 1;
|
||||
static final int EAST_EMPTY = 1 << 1;
|
||||
static final int NORTH_EMPTY = 1 << 2;
|
||||
static final int SOUTH_EMPTY = 1 << 3;
|
||||
private static final int MIN_OPAQUE_ALPHA = 10;
|
||||
final int width, height;
|
||||
final int[] plateRects;
|
||||
final int[] coarsePlateRects;
|
||||
final int[] westRuns, eastRuns, northRuns, southRuns;
|
||||
final boolean opaqueTexelsAllWhite;
|
||||
private final int widthMask, heightMask;
|
||||
private final boolean sizeIsPowerOfTwo;
|
||||
private final byte[] cellFlags;
|
||||
|
||||
CloudShape(int width, int height, ByteBuffer rgba) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.widthMask = width - 1;
|
||||
this.heightMask = height - 1;
|
||||
this.sizeIsPowerOfTwo = (width & widthMask) == 0 && (height & heightMask) == 0;
|
||||
final int cellCount = width * height;
|
||||
final boolean[] opaque = new boolean[cellCount];
|
||||
boolean allWhite = true;
|
||||
for (int i = 0; i < cellCount; i++) {
|
||||
final int alpha = rgba.get(i * 4 + 3) & 0xFF;
|
||||
if (alpha < MIN_OPAQUE_ALPHA) continue;
|
||||
opaque[i] = true;
|
||||
if (allWhite && (alpha != 0xFF
|
||||
|| (rgba.get(i * 4) & 0xFF) != 0xFF
|
||||
|| (rgba.get(i * 4 + 1) & 0xFF) != 0xFF
|
||||
|| (rgba.get(i * 4 + 2) & 0xFF) != 0xFF)) {
|
||||
allWhite = false;
|
||||
}
|
||||
}
|
||||
this.opaqueTexelsAllWhite = allWhite;
|
||||
|
||||
final byte[] cellFlags = new byte[cellCount];
|
||||
for (int z = 0; z < height; z++) {
|
||||
final int rowStart = z * width;
|
||||
final int northRowStart = Math.floorMod(z - 1, height) * width;
|
||||
final int southRowStart = Math.floorMod(z + 1, height) * width;
|
||||
for (int x = 0; x < width; x++) {
|
||||
if (!opaque[rowStart + x]) continue;
|
||||
int flagBits = OPAQUE;
|
||||
if (!opaque[rowStart + Math.floorMod(x - 1, width)]) flagBits |= WEST_EMPTY;
|
||||
if (!opaque[rowStart + Math.floorMod(x + 1, width)]) flagBits |= EAST_EMPTY;
|
||||
if (!opaque[northRowStart + x]) flagBits |= NORTH_EMPTY;
|
||||
if (!opaque[southRowStart + x]) flagBits |= SOUTH_EMPTY;
|
||||
cellFlags[rowStart + x] = (byte) flagBits;
|
||||
}
|
||||
}
|
||||
this.cellFlags = cellFlags;
|
||||
this.plateRects = buildPlateRects(opaque, width, height);
|
||||
this.coarsePlateRects = (width % 2 == 0 && height % 2 == 0) ? buildCoarsePlateRects(opaque, width, height) : null;
|
||||
|
||||
this.westRuns = buildRunsAlongZ(cellFlags, width, height, WEST_EMPTY);
|
||||
this.eastRuns = buildRunsAlongZ(cellFlags, width, height, EAST_EMPTY);
|
||||
this.northRuns = buildRunsAlongX(cellFlags, width, height, NORTH_EMPTY);
|
||||
this.southRuns = buildRunsAlongX(cellFlags, width, height, SOUTH_EMPTY);
|
||||
}
|
||||
|
||||
private static int[] buildPlateRects(boolean[] opaque, int width, int height) {
|
||||
final boolean[] uncovered = opaque.clone();
|
||||
int[] rects = new int[256];
|
||||
int count = 0;
|
||||
|
||||
for (int z = 0; z < height; z++) {
|
||||
final int rowStart = z * width;
|
||||
for (int x = 0; x < width; x++) {
|
||||
if (!uncovered[rowStart + x]) continue;
|
||||
|
||||
int rectW = 1;
|
||||
while (x + rectW < width && uncovered[rowStart + x + rectW]) rectW++;
|
||||
|
||||
int rectH = 1;
|
||||
extend:
|
||||
while (z + rectH < height) {
|
||||
final int nextRowStart = (z + rectH) * width;
|
||||
for (int dx = 0; dx < rectW; dx++) {
|
||||
if (!uncovered[nextRowStart + x + dx]) break extend;
|
||||
}
|
||||
rectH++;
|
||||
}
|
||||
|
||||
for (int dz = 0; dz < rectH; dz++) {
|
||||
final int coveredRowStart = (z + dz) * width;
|
||||
for (int dx = 0; dx < rectW; dx++) uncovered[coveredRowStart + x + dx] = false;
|
||||
}
|
||||
|
||||
if (count * 4 + 4 > rects.length) rects = Arrays.copyOf(rects, rects.length * 2);
|
||||
rects[count * 4] = x;
|
||||
rects[count * 4 + 1] = z;
|
||||
rects[count * 4 + 2] = rectW;
|
||||
rects[count * 4 + 3] = rectH;
|
||||
count++;
|
||||
x += rectW - 1;
|
||||
}
|
||||
}
|
||||
return Arrays.copyOf(rects, count * 4);
|
||||
}
|
||||
|
||||
private static int[] buildCoarsePlateRects(boolean[] opaque, int width, int height) {
|
||||
final int coarseWidth = width / 2;
|
||||
final int coarseHeight = height / 2;
|
||||
final boolean[] coarseOpaque = new boolean[coarseWidth * coarseHeight];
|
||||
for (int z = 0; z < coarseHeight; z++) {
|
||||
final int topRowStart = (z * 2) * width;
|
||||
final int bottomRowStart = (z * 2 + 1) * width;
|
||||
for (int x = 0; x < coarseWidth; x++) {
|
||||
int filled = 0;
|
||||
if (opaque[topRowStart + x * 2]) filled++;
|
||||
if (opaque[topRowStart + x * 2 + 1]) filled++;
|
||||
if (opaque[bottomRowStart + x * 2]) filled++;
|
||||
if (opaque[bottomRowStart + x * 2 + 1]) filled++;
|
||||
coarseOpaque[z * coarseWidth + x] = filled >= 2;
|
||||
}
|
||||
}
|
||||
|
||||
final int[] rects = buildPlateRects(coarseOpaque, coarseWidth, coarseHeight);
|
||||
for (int i = 0; i < rects.length; i++) rects[i] *= 2;
|
||||
return rects;
|
||||
}
|
||||
|
||||
private static int[] buildRunsAlongZ(byte[] cellFlags, int width, int height, int emptyBit) {
|
||||
int[] runs = new int[192];
|
||||
int count = 0;
|
||||
for (int x = 0; x < width; x++) {
|
||||
int z = 0;
|
||||
while (z < height) {
|
||||
if ((cellFlags[x + z * width] & emptyBit) == 0) {
|
||||
z++;
|
||||
continue;
|
||||
}
|
||||
int runLength = 1;
|
||||
while (z + runLength < height && (cellFlags[x + (z + runLength) * width] & emptyBit) != 0) runLength++;
|
||||
if (count * 3 + 3 > runs.length) runs = Arrays.copyOf(runs, runs.length * 2);
|
||||
runs[count * 3] = x;
|
||||
runs[count * 3 + 1] = z;
|
||||
runs[count * 3 + 2] = runLength;
|
||||
count++;
|
||||
z += runLength;
|
||||
}
|
||||
}
|
||||
return Arrays.copyOf(runs, count * 3);
|
||||
}
|
||||
|
||||
private static int[] buildRunsAlongX(byte[] cellFlags, int width, int height, int emptyBit) {
|
||||
int[] runs = new int[192];
|
||||
int count = 0;
|
||||
for (int z = 0; z < height; z++) {
|
||||
final int rowStart = z * width;
|
||||
int x = 0;
|
||||
while (x < width) {
|
||||
if ((cellFlags[rowStart + x] & emptyBit) == 0) {
|
||||
x++;
|
||||
continue;
|
||||
}
|
||||
int runLength = 1;
|
||||
while (x + runLength < width && (cellFlags[rowStart + x + runLength] & emptyBit) != 0) runLength++;
|
||||
if (count * 3 + 3 > runs.length) runs = Arrays.copyOf(runs, runs.length * 2);
|
||||
runs[count * 3] = x;
|
||||
runs[count * 3 + 1] = z;
|
||||
runs[count * 3 + 2] = runLength;
|
||||
count++;
|
||||
x += runLength;
|
||||
}
|
||||
}
|
||||
return Arrays.copyOf(runs, count * 3);
|
||||
}
|
||||
|
||||
int cellFlagsAt(int x, int z) {
|
||||
final int wrappedX, wrappedZ;
|
||||
if (sizeIsPowerOfTwo) {
|
||||
wrappedX = x & widthMask;
|
||||
wrappedZ = z & heightMask;
|
||||
} else {
|
||||
wrappedX = Math.floorMod(x, width);
|
||||
wrappedZ = Math.floorMod(z, height);
|
||||
}
|
||||
return cellFlags[wrappedX + wrappedZ * width] & 0xFF;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,71 @@
|
||||
package com.gtnewhorizons.angelica.render;
|
||||
|
||||
import org.embeddedt.embeddium.impl.gl.shader.ShaderBindingContext;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat4v;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformInt;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformMatrix4f;
|
||||
|
||||
/**
|
||||
* The uniform handles both cloud programs bind.
|
||||
*/
|
||||
final class CloudUniforms {
|
||||
static final float ALPHA = 0.8f;
|
||||
|
||||
final GlUniformMatrix4f mvp;
|
||||
final GlUniformMatrix4f mv;
|
||||
final GlUniformMatrix4f modelView;
|
||||
final GlUniformFloat4v colorMult;
|
||||
final GlUniformFloat4v fogParams;
|
||||
final GlUniformFloat4v fogColor;
|
||||
final GlUniformInt fogEnabled;
|
||||
final GlUniformInt tex;
|
||||
|
||||
final GlUniformInt textureUnit;
|
||||
final GlUniformFloat cellHeight;
|
||||
final GlUniformFloat4v scroll;
|
||||
private final float[] vec4Buf = new float[4];
|
||||
private float lastScrollX = Float.NaN, lastScrollZ = Float.NaN;
|
||||
private float lastTextureScaleX = Float.NaN, lastTextureScaleZ = Float.NaN;
|
||||
private int lastFogEnabled = -1;
|
||||
private float lastFogR = Float.NaN, lastFogG = Float.NaN, lastFogB = Float.NaN;
|
||||
private float lastColorR = Float.NaN, lastColorG = Float.NaN, lastColorB = Float.NaN;
|
||||
private float lastFogParamX = Float.NaN, lastFogParamY = Float.NaN, lastFogParamZ = Float.NaN, lastFogParamW = Float.NaN;
|
||||
private final float[] uploadBuf = new float[4];
|
||||
private float lastCellHeight = Float.NaN;
|
||||
|
||||
CloudUniforms(ShaderBindingContext ctx) {
|
||||
mvp = ctx.bindUniform("u_MVPMatrix", GlUniformMatrix4f::new);
|
||||
mv = ctx.bindUniform("u_MVMatrix", GlUniformMatrix4f::new);
|
||||
colorMult = ctx.bindUniform("u_ColorMult", GlUniformFloat4v::new);
|
||||
fogParams = ctx.bindUniform("u_FogParams", GlUniformFloat4v::new);
|
||||
fogColor = ctx.bindUniform("u_FogColor", GlUniformFloat4v::new);
|
||||
fogEnabled = ctx.bindUniform("u_FogEnabled", GlUniformInt::new);
|
||||
tex = ctx.bindUniform("u_Tex", GlUniformInt::new);
|
||||
CloudUniforms(ShaderBindingContext context) {
|
||||
mvp = context.bindUniform("u_MVPMatrix", GlUniformMatrix4f::new);
|
||||
modelView = context.bindUniform("u_MVMatrix", GlUniformMatrix4f::new);
|
||||
colorMult = context.bindUniform("u_ColorMult", GlUniformFloat4v::new);
|
||||
fogParams = context.bindUniform("u_FogParams", GlUniformFloat4v::new);
|
||||
fogColor = context.bindUniform("u_FogColor", GlUniformFloat4v::new);
|
||||
fogEnabled = context.bindUniform("u_FogEnabled", GlUniformInt::new);
|
||||
textureUnit = context.bindUniformIfPresent("u_Tex", GlUniformInt::new);
|
||||
cellHeight = context.bindUniformIfPresent("u_CellHeight", GlUniformFloat::new);
|
||||
scroll = context.bindUniformIfPresent("u_Scroll", GlUniformFloat4v::new);
|
||||
}
|
||||
|
||||
void setScroll(float x, float z, float textureScaleX, float textureScaleZ) {
|
||||
if (scroll == null || (x == lastScrollX && z == lastScrollZ && textureScaleX == lastTextureScaleX && textureScaleZ == lastTextureScaleZ)) return;
|
||||
lastScrollX = x;
|
||||
lastScrollZ = z;
|
||||
lastTextureScaleX = textureScaleX;
|
||||
lastTextureScaleZ = textureScaleZ;
|
||||
vec4Buf[0] = x;
|
||||
vec4Buf[1] = z;
|
||||
vec4Buf[2] = textureScaleX;
|
||||
vec4Buf[3] = textureScaleZ;
|
||||
scroll.set(vec4Buf);
|
||||
}
|
||||
|
||||
void setCellHeight(float blocks) {
|
||||
if (blocks == lastCellHeight) return;
|
||||
lastCellHeight = blocks;
|
||||
cellHeight.setFloat(blocks);
|
||||
}
|
||||
|
||||
void setFogEnabled(boolean enabled) {
|
||||
final int val = enabled ? 1 : 0;
|
||||
if (val != lastFogEnabled) {
|
||||
lastFogEnabled = val;
|
||||
fogEnabled.setInt(val);
|
||||
final int enabledValue = enabled ? 1 : 0;
|
||||
if (enabledValue != lastFogEnabled) {
|
||||
lastFogEnabled = enabledValue;
|
||||
fogEnabled.setInt(enabledValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,11 +74,11 @@ final class CloudUniforms {
|
||||
lastFogR = r;
|
||||
lastFogG = g;
|
||||
lastFogB = b;
|
||||
uploadBuf[0] = r;
|
||||
uploadBuf[1] = g;
|
||||
uploadBuf[2] = b;
|
||||
uploadBuf[3] = 1.0f;
|
||||
fogColor.set(uploadBuf);
|
||||
vec4Buf[0] = r;
|
||||
vec4Buf[1] = g;
|
||||
vec4Buf[2] = b;
|
||||
vec4Buf[3] = 1.0f;
|
||||
fogColor.set(vec4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,11 +88,11 @@ final class CloudUniforms {
|
||||
lastFogParamY = y;
|
||||
lastFogParamZ = z;
|
||||
lastFogParamW = w;
|
||||
uploadBuf[0] = x;
|
||||
uploadBuf[1] = y;
|
||||
uploadBuf[2] = z;
|
||||
uploadBuf[3] = w;
|
||||
fogParams.set(uploadBuf);
|
||||
vec4Buf[0] = x;
|
||||
vec4Buf[1] = y;
|
||||
vec4Buf[2] = z;
|
||||
vec4Buf[3] = w;
|
||||
fogParams.set(vec4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,11 +101,11 @@ final class CloudUniforms {
|
||||
lastColorR = r;
|
||||
lastColorG = g;
|
||||
lastColorB = b;
|
||||
uploadBuf[0] = r;
|
||||
uploadBuf[1] = g;
|
||||
uploadBuf[2] = b;
|
||||
uploadBuf[3] = ALPHA;
|
||||
colorMult.set(uploadBuf);
|
||||
vec4Buf[0] = r;
|
||||
vec4Buf[1] = g;
|
||||
vec4Buf[2] = b;
|
||||
vec4Buf[3] = ALPHA;
|
||||
colorMult.set(vec4Buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,971 @@
|
||||
package com.gtnewhorizons.angelica.render;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VertexBufferType;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.ALWAYS_DRAWN_CELLS;
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.WEDGE_COUNT;
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.wedgeOf;
|
||||
import static org.joml.Math.clamp;
|
||||
|
||||
/**
|
||||
* Plates and walls as vertices.
|
||||
*/
|
||||
final class CloudVertexMesh {
|
||||
|
||||
static final byte FACE_DOWN = 0, FACE_UP = 1, FACE_SIDE_X = 2, FACE_SIDE_Z = 3;
|
||||
static final float[] FACE_SHADE = {0.7f, 1.0f, 0.9f, 0.8f};
|
||||
private static final int[] FACE_SHADE_PACKED;
|
||||
private static final int FACE_SHADE_ATTRIB = 5;
|
||||
private static final int STRIDE_WITH_UV = 24;
|
||||
private static final int NORMAL_OFFSET_WITH_UV = 20;
|
||||
private static final int STRIDE_NO_UV = 16;
|
||||
private static final int NORMAL_OFFSET_NO_UV = 12;
|
||||
private static final float EDGE_OVERLAP = 0.0001f;
|
||||
private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
|
||||
private static final int NORMAL_POS_X = packNormal((byte) 127, (byte) 0, (byte) 0);
|
||||
private static final int NORMAL_NEG_X = packNormal((byte) -127, (byte) 0, (byte) 0);
|
||||
private static final int NORMAL_POS_Y = packNormal((byte) 0, (byte) 127, (byte) 0);
|
||||
private static final int NORMAL_NEG_Y = packNormal((byte) 0, (byte) -127, (byte) 0);
|
||||
private static final int NORMAL_POS_Z = packNormal((byte) 0, (byte) 0, (byte) 127);
|
||||
private static final int NORMAL_NEG_Z = packNormal((byte) 0, (byte) 0, (byte) -127);
|
||||
private static final int DIR_WEST = 0, DIR_EAST = 1, DIR_NORTH = 2, DIR_SOUTH = 3;
|
||||
private static final int DIR_NONE = 4;
|
||||
private static final int GROUP_PLATE_ALWAYS = 0, GROUP_PLATE_WEDGE = 1, GROUP_ALWAYS_DRAWN = 2, GROUP_WALL_WEDGE = 3;
|
||||
private static final int ORDER_GROUP_COUNT = 2 * WEDGE_COUNT + 3;
|
||||
private static final int ORDER_BANDS_PER_CELL = 4;
|
||||
private static final int WEDGE_BUCKETS = WEDGE_COUNT + 1;
|
||||
private static final int FACE_WEDGE_BUCKETS = 4 * WEDGE_BUCKETS;
|
||||
private static final int WALL_SORT_INDEX_BITS = 26;
|
||||
private static final int WALL_SORT_DISTANCE_BITS = 20;
|
||||
private static final int WALL_SORT_BUCKET_SHIFT = WALL_SORT_INDEX_BITS + WALL_SORT_DISTANCE_BITS;
|
||||
private static final long WALL_SORT_INDEX_MASK = (1L << WALL_SORT_INDEX_BITS) - 1;
|
||||
private static final int WALL_SORT_MAX_RUNS = 1 << WALL_SORT_INDEX_BITS;
|
||||
private static final int WALL_SORT_MAX_DISTANCE = (1 << WALL_SORT_DISTANCE_BITS) - 1;
|
||||
private static final int RADIX_BITS = 10;
|
||||
private static final int RADIX_SIZE = 1 << RADIX_BITS;
|
||||
private static final int RADIX_MASK = RADIX_SIZE - 1;
|
||||
private static final Tracy.ZoneId Z_STAMP_PLATES = Tracy.zoneId("cloudVertexStampPlates", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_PLATE_COVER = Tracy.zoneId("cloudPlateCover", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_STAMP_WALLS = Tracy.zoneId("cloudVertexStampWalls", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_UPLOAD = Tracy.zoneId("cloudVertexUpload", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_GEOMETRY = Tracy.zoneId("cloudVertexGeometry", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_REORDER = Tracy.zoneId("cloudVertexReorder", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_WALL_SORT = Tracy.zoneId("cloudVertexWallSort", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_REORDER_UPLOAD = Tracy.zoneId("cloudVertexReorderUpload", Tracy.COLOR_CLIENT);
|
||||
|
||||
static {
|
||||
FACE_SHADE_PACKED = new int[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
final int shadeByte = Math.round(FACE_SHADE[i] * 255f) & 0xFF;
|
||||
FACE_SHADE_PACKED[i] = BIG_ENDIAN ? shadeByte : (shadeByte << 24);
|
||||
}
|
||||
}
|
||||
|
||||
private final int[] faceWedgeStart = new int[FACE_WEDGE_BUCKETS + 1];
|
||||
private final int[] faceWedgeCursor = new int[FACE_WEDGE_BUCKETS];
|
||||
private final int[] wallStart = new int[WEDGE_COUNT + 1];
|
||||
private final int[] plateStart = new int[WEDGE_COUNT + 1];
|
||||
private final int[] wallStartScratch = new int[WEDGE_COUNT + 1];
|
||||
private final int[] plateStartScratch = new int[WEDGE_COUNT + 1];
|
||||
private int orderPendingQuads;
|
||||
private int orderPendingAnchorX, orderPendingAnchorZ;
|
||||
private final CloudPlateCover plates = new CloudPlateCover();
|
||||
private IVertexArrayObject vao;
|
||||
private int vertexCount;
|
||||
private int eboId = -1;
|
||||
private int eboQuadCapacity;
|
||||
private boolean eboIsSequential;
|
||||
private boolean vaoHasUv = true;
|
||||
private int vertexStride = STRIDE_WITH_UV;
|
||||
private int normalOffset = NORMAL_OFFSET_WITH_UV;
|
||||
private int quadBytes = 4 * STRIDE_WITH_UV;
|
||||
private ByteBuffer emitBuffer;
|
||||
private long emitBufferAddr;
|
||||
private int emitBufferCapacity;
|
||||
private long writeAddr, writeEnd;
|
||||
private ByteBuffer sortedBuffer;
|
||||
private long sortedBufferAddr;
|
||||
private int sortedBufferCapacity;
|
||||
private byte[] quadFace = new byte[2048];
|
||||
private int quadCount;
|
||||
private int[] quadCell = new int[2048];
|
||||
private short[] quadGroupBits = new short[2048];
|
||||
private int nextQuadCellX, nextQuadCellZ, nextQuadGroup, nextQuadWedge, nextQuadDir = DIR_NONE;
|
||||
private int buildAnchorX = Integer.MIN_VALUE, buildAnchorZ = Integer.MIN_VALUE;
|
||||
private int orderAnchorX = Integer.MIN_VALUE, orderAnchorZ = Integer.MIN_VALUE;
|
||||
private int buildRadiusCells;
|
||||
private int[] orderBucketCount = new int[0];
|
||||
private int[] quadBucket = new int[0];
|
||||
private IntBuffer orderIndexBuffer;
|
||||
private int[] orderIndexScratch = new int[0];
|
||||
private int[] quadSortKey = new int[0];
|
||||
private int[] wallRuns = new int[4096 * 4];
|
||||
private int wallRunCount;
|
||||
private int[] wallRunBucket = new int[4096];
|
||||
private int[] wallRunDistance = new int[4096];
|
||||
private long[] wallSortKey = new long[4096];
|
||||
private long[] wallSortScratch = new long[4096];
|
||||
private final int[] radixCounts = new int[RADIX_SIZE];
|
||||
private float textureScaleX, textureScaleZ;
|
||||
private boolean forPackShader;
|
||||
private int wallCutCells = Integer.MAX_VALUE;
|
||||
private int wallClipRadiusSq;
|
||||
private ByteBuffer pendingUpload;
|
||||
private int pendingVertices = -1;
|
||||
private int vboCapacityVertices;
|
||||
private int uploadCursorBytes = -1;
|
||||
private int uploadTotalBytes;
|
||||
private int uploadVertices;
|
||||
private static final Tracy.ZoneId Z_EBO = Tracy.zoneId("cloudVertexEbo", Tracy.COLOR_CLIENT);
|
||||
private static IntBuffer sharedSequentialIndices;
|
||||
private static int sharedSequentialQuads;
|
||||
|
||||
private static int packNormal(byte nx, byte ny, byte nz) {
|
||||
final int x = nx & 0xFF, y = ny & 0xFF, z = nz & 0xFF;
|
||||
return BIG_ENDIAN ? (x << 24) | (y << 16) | (z << 8) : x | (y << 8) | (z << 16);
|
||||
}
|
||||
|
||||
int quadCount() {
|
||||
return vertexCount / 4;
|
||||
}
|
||||
|
||||
boolean built() {
|
||||
return vao != null && vertexCount != 0;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
vertexCount = 0;
|
||||
orderAnchorX = Integer.MIN_VALUE;
|
||||
orderAnchorZ = Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
int[] plateStarts() {
|
||||
return plateStart;
|
||||
}
|
||||
|
||||
int[] wallStarts() {
|
||||
return wallStart;
|
||||
}
|
||||
|
||||
void buildFast(CloudShape shape, int radiusCells, float scrollX, float scrollZ, boolean forPackShader) {
|
||||
beginBuild(shape, false, forPackShader);
|
||||
|
||||
nextQuadDir = DIR_NONE;
|
||||
nextQuadGroup = GROUP_PLATE_ALWAYS;
|
||||
nextQuadWedge = 0;
|
||||
nextQuadCellX = 0;
|
||||
nextQuadCellZ = 0;
|
||||
final double minCell = -radiusCells;
|
||||
final double maxCell = radiusCells;
|
||||
final float uWest = -radiusCells * textureScaleX + scrollX;
|
||||
final float uEast = radiusCells * textureScaleX + scrollX;
|
||||
final float vNorth = -radiusCells * textureScaleZ + scrollZ;
|
||||
final float vSouth = radiusCells * textureScaleZ + scrollZ;
|
||||
ensureEmitCapacity(quadBytes);
|
||||
emitQuad(minCell, 0.0, minCell, uWest, vNorth, maxCell, 0.0, minCell, uEast, vNorth,
|
||||
maxCell, 0.0, maxCell, uEast, vSouth, minCell, 0.0, maxCell, uWest, vSouth, FACE_UP, NORMAL_POS_Y);
|
||||
|
||||
finishGeometry();
|
||||
}
|
||||
|
||||
void build(CloudShape shape, int anchorX, int anchorZ, int radiusCells, int radiusCellsSq,
|
||||
double cellHeightBlocks, float scrollX, float scrollZ,
|
||||
boolean emitUnderside, boolean emitTopSurface, boolean emitPlates,
|
||||
int wallCut, int plateLodCells, boolean untextured, boolean forPack) {
|
||||
beginBuild(shape, untextured, forPack);
|
||||
this.wallCutCells = wallCut;
|
||||
buildAnchorX = anchorX;
|
||||
buildAnchorZ = anchorZ;
|
||||
buildRadiusCells = radiusCells;
|
||||
|
||||
final double deckTopY = cellHeightBlocks;
|
||||
final double plateTopY = deckTopY - EDGE_OVERLAP;
|
||||
|
||||
if (emitPlates) {
|
||||
if (Tracy.FINE_ZONES) Tracy.beginZone(Z_STAMP_PLATES);
|
||||
addPlates(shape, anchorX, anchorZ, radiusCells, radiusCellsSq, emitUnderside, emitTopSurface, plateTopY, scrollX, scrollZ, plateLodCells);
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
}
|
||||
|
||||
if (Tracy.FINE_ZONES) Tracy.beginZone(Z_STAMP_WALLS);
|
||||
addWalls(shape, anchorX, anchorZ, radiusCells, radiusCellsSq, deckTopY, scrollX, scrollZ);
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
|
||||
if (Tracy.FINE_ZONES) Tracy.beginZone(Z_GEOMETRY);
|
||||
finishGeometry();
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
}
|
||||
|
||||
void buildInterior(CloudShape shape, int anchorX, int anchorZ, double deckTopY,
|
||||
float scrollX, float scrollZ, boolean untextured, boolean forPack) {
|
||||
beginBuild(shape, untextured, forPack);
|
||||
addInteriorFaces(shape, anchorX, anchorZ, deckTopY, scrollX, scrollZ);
|
||||
finishGeometry();
|
||||
}
|
||||
|
||||
private void beginBuild(CloudShape shape, boolean untextured, boolean forPack) {
|
||||
textureScaleX = 1.0f / shape.width;
|
||||
textureScaleZ = 1.0f / shape.height;
|
||||
forPackShader = forPack;
|
||||
if (!forPack && quadCell.length < quadFace.length) {
|
||||
quadCell = new int[quadFace.length];
|
||||
quadGroupBits = new short[quadFace.length];
|
||||
}
|
||||
final boolean withUv = !untextured;
|
||||
vertexStride = withUv ? STRIDE_WITH_UV : STRIDE_NO_UV;
|
||||
normalOffset = withUv ? NORMAL_OFFSET_WITH_UV : NORMAL_OFFSET_NO_UV;
|
||||
quadBytes = 4 * vertexStride;
|
||||
prepareEmitBuffer();
|
||||
}
|
||||
|
||||
private void addPlates(CloudShape shape, int anchorX, int anchorZ, int radiusCells, int radiusCellsSq,
|
||||
boolean emitUnderside, boolean emitTopSurface, double plateTopY, float scrollX, float scrollZ, int plateLodCells) {
|
||||
final int bytesPerRect = (emitUnderside ? quadBytes : 0) + (emitTopSurface ? quadBytes : 0);
|
||||
if (bytesPerRect == 0) return;
|
||||
|
||||
if (Tracy.FINE_ZONES) Tracy.beginZone(Z_PLATE_COVER);
|
||||
plates.build(shape, anchorX, anchorZ, radiusCells, radiusCellsSq, plateLodCells, !forPackShader);
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
|
||||
if (forPackShader) {
|
||||
nextQuadDir = DIR_NONE;
|
||||
nextQuadGroup = GROUP_PLATE_ALWAYS;
|
||||
nextQuadWedge = 0;
|
||||
if (emitUnderside) emitPlatePass(true, false, plateTopY, scrollX, scrollZ);
|
||||
if (emitTopSurface) emitPlatePass(false, true, plateTopY, scrollX, scrollZ);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < plates.count(); i++) {
|
||||
final int rect = plates.orderedRect(i);
|
||||
final int wedgeBucket = plates.wedgeBucket(rect);
|
||||
nextQuadDir = DIR_NONE;
|
||||
nextQuadGroup = wedgeBucket == 0 ? GROUP_PLATE_ALWAYS : GROUP_PLATE_WEDGE;
|
||||
nextQuadWedge = wedgeBucket == 0 ? 0 : wedgeBucket - 1;
|
||||
nextQuadCellX = plates.minX(rect);
|
||||
nextQuadCellZ = plates.minZ(rect);
|
||||
emitPlateRect(plates.minX(rect), plates.minZ(rect), plates.maxX(rect) + 1, plates.maxZ(rect) + 1,
|
||||
emitUnderside, emitTopSurface, plateTopY, scrollX, scrollZ, bytesPerRect);
|
||||
}
|
||||
}
|
||||
|
||||
private void emitPlatePass(boolean emitUnderside, boolean emitTopSurface, double plateTopY, float scrollX, float scrollZ) {
|
||||
for (int rect = 0; rect < plates.count(); rect++) {
|
||||
nextQuadCellX = plates.minX(rect);
|
||||
nextQuadCellZ = plates.minZ(rect);
|
||||
emitPlateRect(plates.minX(rect), plates.minZ(rect), plates.maxX(rect) + 1, plates.maxZ(rect) + 1,
|
||||
emitUnderside, emitTopSurface, plateTopY, scrollX, scrollZ, quadBytes);
|
||||
}
|
||||
}
|
||||
|
||||
private void addWalls(CloudShape shape, int anchorX, int anchorZ, int radiusCells, int radiusCellsSq,
|
||||
double deckTopY, float scrollX, float scrollZ) {
|
||||
final int texW = shape.width;
|
||||
final int texH = shape.height;
|
||||
|
||||
final int bandsPerWedge = radiusCells + 1;
|
||||
final long cutCellsSq = (long) wallCutCells * wallCutCells;
|
||||
wallClipRadiusSq = forPackShader && cutCellsSq < radiusCellsSq ? (int) cutCellsSq : radiusCellsSq;
|
||||
wallRunCount = 0;
|
||||
collectWallRuns(shape.westRuns, DIR_WEST, anchorX, anchorZ, texW, texH, bandsPerWedge);
|
||||
collectWallRuns(shape.eastRuns, DIR_EAST, anchorX, anchorZ, texW, texH, bandsPerWedge);
|
||||
collectWallRuns(shape.northRuns, DIR_NORTH, anchorX, anchorZ, texW, texH, bandsPerWedge);
|
||||
collectWallRuns(shape.southRuns, DIR_SOUTH, anchorX, anchorZ, texW, texH, bandsPerWedge);
|
||||
final int found = wallRunCount;
|
||||
|
||||
if (found == 0) return;
|
||||
|
||||
if (forPackShader) {
|
||||
nextQuadGroup = GROUP_ALWAYS_DRAWN;
|
||||
nextQuadWedge = 0;
|
||||
for (int i = 0; i < found; i++) {
|
||||
final int run = i * 4;
|
||||
nextQuadDir = wallRuns[run];
|
||||
nextQuadCellX = wallRuns[run + 1];
|
||||
nextQuadCellZ = wallRuns[run + 2];
|
||||
emitWallRun(wallRuns[run], wallRuns[run + 1], wallRuns[run + 2], wallRuns[run + 3], deckTopY, scrollX, scrollZ);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (wallSortKey.length < found) wallSortKey = new long[found];
|
||||
for (int i = 0; i < found; i++) {
|
||||
wallSortKey[i] = ((long) wallRunBucket[i] << WALL_SORT_BUCKET_SHIFT)
|
||||
| ((long) wallRunDistance[i] << WALL_SORT_INDEX_BITS)
|
||||
| i;
|
||||
}
|
||||
if (Tracy.FINE_ZONES) Tracy.beginZone(Z_WALL_SORT);
|
||||
radixSortWallKeys(found);
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
|
||||
for (int i = 0; i < found; i++) {
|
||||
final long key = wallSortKey[i];
|
||||
final int bucket = (int) (key >>> WALL_SORT_BUCKET_SHIFT);
|
||||
final int wedge = bucket == 0 ? -1 : (bucket - 1) / bandsPerWedge;
|
||||
final int run = (int) (key & WALL_SORT_INDEX_MASK) * 4;
|
||||
nextQuadGroup = bucket == 0 ? GROUP_ALWAYS_DRAWN : GROUP_WALL_WEDGE;
|
||||
nextQuadWedge = bucket == 0 ? 0 : wedge;
|
||||
nextQuadDir = wallRuns[run];
|
||||
nextQuadCellX = wallRuns[run + 1];
|
||||
nextQuadCellZ = wallRuns[run + 2];
|
||||
emitWallRun(wallRuns[run], wallRuns[run + 1], wallRuns[run + 2], wallRuns[run + 3], deckTopY, scrollX, scrollZ);
|
||||
}
|
||||
}
|
||||
|
||||
private void collectWallRuns(int[] runs, int dir, int anchorX, int anchorZ, int texW, int texH, int bandsPerWedge) {
|
||||
final boolean alongZ = dir == DIR_WEST || dir == DIR_EAST;
|
||||
final int clipRadiusCells = (int) Math.sqrt(wallClipRadiusSq);
|
||||
final int minCell = -clipRadiusCells;
|
||||
final int maxCell = clipRadiusCells;
|
||||
|
||||
for (int r = 0; r < runs.length; r += 3) {
|
||||
final int len = runs[r + 2];
|
||||
final int baseX = Math.floorMod(runs[r] - anchorX, texW);
|
||||
final int baseZ = Math.floorMod(runs[r + 1] - anchorZ, texH);
|
||||
|
||||
for (int kz = Math.floorDiv(minCell - baseZ - (alongZ ? len - 1 : 0), texH); kz <= Math.floorDiv(maxCell - baseZ, texH); kz++) {
|
||||
final int runZ = baseZ + kz * texH;
|
||||
for (int kx = Math.floorDiv(minCell - baseX - (alongZ ? 0 : len - 1), texW); kx <= Math.floorDiv(maxCell - baseX, texW); kx++) {
|
||||
final int runX = baseX + kx * texW;
|
||||
|
||||
final int startX, startZ, span;
|
||||
if (alongZ) {
|
||||
if (runX * runX > wallClipRadiusSq) continue;
|
||||
final int halfSpan = (int) Math.sqrt(wallClipRadiusSq - runX * runX);
|
||||
final int z0 = Math.max(runZ, -halfSpan);
|
||||
final int z1 = Math.min(runZ + len - 1, halfSpan);
|
||||
if (z0 > z1) continue;
|
||||
startX = runX;
|
||||
startZ = z0;
|
||||
span = z1 - z0 + 1;
|
||||
} else {
|
||||
if (runZ * runZ > wallClipRadiusSq) continue;
|
||||
final int halfSpan = (int) Math.sqrt(wallClipRadiusSq - runZ * runZ);
|
||||
final int x0 = Math.max(runX, -halfSpan);
|
||||
final int x1 = Math.min(runX + len - 1, halfSpan);
|
||||
if (x0 > x1) continue;
|
||||
startX = x0;
|
||||
startZ = runZ;
|
||||
span = x1 - x0 + 1;
|
||||
}
|
||||
|
||||
if (forPackShader) {
|
||||
recordWallRun(dir, startX, startZ, span, 0, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
int pieceStart = 0;
|
||||
int pieceWedge = wedgeOf(startX, startZ);
|
||||
double pieceDist = Math.sqrt(startX * startX + startZ * startZ);
|
||||
int pieceBand = (int) pieceDist;
|
||||
for (int c = 1; c <= span; c++) {
|
||||
final int cX = alongZ ? startX : startX + c;
|
||||
final int cZ = alongZ ? startZ + c : startZ;
|
||||
final boolean last = c == span;
|
||||
final int wedgeHere = last ? -1 : wedgeOf(cX, cZ);
|
||||
final double distHere = last ? -1.0 : Math.sqrt(cX * cX + cZ * cZ);
|
||||
final int bandHere = last ? -1 : (int) distHere;
|
||||
if (wedgeHere == pieceWedge && bandHere == pieceBand) {
|
||||
if (distHere < pieceDist) pieceDist = distHere;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pieceBand <= wallCutCells) {
|
||||
recordWallRun(dir,
|
||||
alongZ ? startX : startX + pieceStart,
|
||||
alongZ ? startZ + pieceStart : startZ,
|
||||
c - pieceStart,
|
||||
pieceBand <= ALWAYS_DRAWN_CELLS ? 0 : 1 + pieceWedge * bandsPerWedge + pieceBand,
|
||||
(int) (pieceDist * 256.0));
|
||||
}
|
||||
|
||||
pieceStart = c;
|
||||
pieceWedge = wedgeHere;
|
||||
pieceDist = distHere;
|
||||
pieceBand = bandHere;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void radixSortWallKeys(int count) {
|
||||
if (count < 2) return;
|
||||
if (wallSortScratch.length < count) wallSortScratch = new long[count];
|
||||
|
||||
long[] src = wallSortKey;
|
||||
long[] dst = wallSortScratch;
|
||||
final int[] counts = radixCounts;
|
||||
|
||||
for (int shift = WALL_SORT_INDEX_BITS; shift < Long.SIZE; shift += RADIX_BITS) {
|
||||
Arrays.fill(counts, 0);
|
||||
for (int i = 0; i < count; i++) counts[(int) ((src[i] >>> shift) & RADIX_MASK)]++;
|
||||
|
||||
boolean uniform = false;
|
||||
int acc = 0;
|
||||
for (int d = 0; d < RADIX_SIZE; d++) {
|
||||
final int c = counts[d];
|
||||
if (c == count) uniform = true;
|
||||
counts[d] = acc;
|
||||
acc += c;
|
||||
}
|
||||
if (uniform) continue;
|
||||
|
||||
for (int i = 0; i < count; i++) dst[counts[(int) ((src[i] >>> shift) & RADIX_MASK)]++] = src[i];
|
||||
final long[] swap = src;
|
||||
src = dst;
|
||||
dst = swap;
|
||||
}
|
||||
|
||||
if (src != wallSortKey) System.arraycopy(src, 0, wallSortKey, 0, count);
|
||||
}
|
||||
|
||||
private void recordWallRun(int dir, int relX, int relZ, int span, int bucket, int distance256ths) {
|
||||
if (wallRunCount == WALL_SORT_MAX_RUNS) return;
|
||||
if (wallRunCount * 4 + 4 > wallRuns.length) {
|
||||
wallRuns = Arrays.copyOf(wallRuns, wallRuns.length * 2);
|
||||
wallRunBucket = Arrays.copyOf(wallRunBucket, wallRunBucket.length * 2);
|
||||
wallRunDistance = Arrays.copyOf(wallRunDistance, wallRunDistance.length * 2);
|
||||
}
|
||||
final int slot = wallRunCount * 4;
|
||||
wallRuns[slot] = dir;
|
||||
wallRuns[slot + 1] = relX;
|
||||
wallRuns[slot + 2] = relZ;
|
||||
wallRuns[slot + 3] = span;
|
||||
wallRunBucket[wallRunCount] = bucket;
|
||||
wallRunDistance[wallRunCount] = Math.min(distance256ths, WALL_SORT_MAX_DISTANCE);
|
||||
wallRunCount++;
|
||||
}
|
||||
|
||||
private void emitWallRun(int dir, int relX, int relZ, int span, double deckTopY, float scrollX, float scrollZ) {
|
||||
ensureEmitCapacity(quadBytes);
|
||||
final double x0 = relX;
|
||||
final double z0 = relZ;
|
||||
|
||||
if (dir == DIR_WEST || dir == DIR_EAST) {
|
||||
final double z1 = relZ + span;
|
||||
final float uCentre = (relX + 0.5F) * textureScaleX + scrollX;
|
||||
final float vNorth = relZ * textureScaleZ + scrollZ;
|
||||
final float vSouth = (relZ + span) * textureScaleZ + scrollZ;
|
||||
if (dir == DIR_WEST) {
|
||||
emitQuad(x0, 0.0, z1, uCentre, vSouth, x0, deckTopY, z1, uCentre, vSouth,
|
||||
x0, deckTopY, z0, uCentre, vNorth, x0, 0.0, z0, uCentre, vNorth, FACE_SIDE_X, NORMAL_NEG_X);
|
||||
} else {
|
||||
final double xEast = relX + 1 - EDGE_OVERLAP;
|
||||
emitQuad(xEast, 0.0, z0, uCentre, vNorth, xEast, deckTopY, z0, uCentre, vNorth,
|
||||
xEast, deckTopY, z1, uCentre, vSouth, xEast, 0.0, z1, uCentre, vSouth, FACE_SIDE_X, NORMAL_POS_X);
|
||||
}
|
||||
} else {
|
||||
final double x1 = relX + span;
|
||||
final float uLeft = relX * textureScaleX + scrollX;
|
||||
final float uRight = (relX + span) * textureScaleX + scrollX;
|
||||
final float vCentre = (relZ + 0.5F) * textureScaleZ + scrollZ;
|
||||
if (dir == DIR_NORTH) {
|
||||
emitQuad(x0, deckTopY, z0, uLeft, vCentre, x1, deckTopY, z0, uRight, vCentre,
|
||||
x1, 0.0, z0, uRight, vCentre, x0, 0.0, z0, uLeft, vCentre, FACE_SIDE_Z, NORMAL_NEG_Z);
|
||||
} else {
|
||||
final double zSouth = relZ + 1 - EDGE_OVERLAP;
|
||||
emitQuad(x0, 0.0, zSouth, uLeft, vCentre, x1, 0.0, zSouth, uRight, vCentre,
|
||||
x1, deckTopY, zSouth, uRight, vCentre, x0, deckTopY, zSouth, uLeft, vCentre, FACE_SIDE_Z, NORMAL_POS_Z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void emitPlateRect(int relX0, int relZ0, int relX1, int relZ1, boolean emitUnderside, boolean emitTopSurface,
|
||||
double plateTopY, float scrollX, float scrollZ, int bytesPerRect) {
|
||||
final double x0 = relX0, x1 = relX1, z0 = relZ0, z1 = relZ1;
|
||||
final float uLeft = relX0 * textureScaleX + scrollX;
|
||||
final float uRight = relX1 * textureScaleX + scrollX;
|
||||
final float vNorth = relZ0 * textureScaleZ + scrollZ;
|
||||
final float vSouth = relZ1 * textureScaleZ + scrollZ;
|
||||
|
||||
ensureEmitCapacity(bytesPerRect);
|
||||
if (emitUnderside) {
|
||||
emitQuad(x0, 0.0, z0, uLeft, vNorth, x1, 0.0, z0, uRight, vNorth,
|
||||
x1, 0.0, z1, uRight, vSouth, x0, 0.0, z1, uLeft, vSouth, FACE_DOWN, NORMAL_NEG_Y);
|
||||
}
|
||||
if (emitTopSurface) {
|
||||
emitQuad(x0, plateTopY, z1, uLeft, vSouth, x1, plateTopY, z1, uRight, vSouth,
|
||||
x1, plateTopY, z0, uRight, vNorth, x0, plateTopY, z0, uLeft, vNorth, FACE_UP, NORMAL_POS_Y);
|
||||
}
|
||||
}
|
||||
|
||||
private void addInteriorFaces(CloudShape shape, int anchorX, int anchorZ, double deckTopY, float scrollX, float scrollZ) {
|
||||
if ((shape.cellFlagsAt(anchorX, anchorZ) & CloudShape.OPAQUE) == 0) return;
|
||||
|
||||
nextQuadDir = DIR_NONE;
|
||||
nextQuadGroup = GROUP_ALWAYS_DRAWN;
|
||||
nextQuadWedge = 0;
|
||||
nextQuadCellX = 0;
|
||||
nextQuadCellZ = 0;
|
||||
|
||||
final float uLeft = scrollX;
|
||||
final float uRight = textureScaleX + scrollX;
|
||||
final float uCentre = 0.5F * textureScaleX + scrollX;
|
||||
final float vNorth = scrollZ;
|
||||
final float vSouth = textureScaleZ + scrollZ;
|
||||
final float vCentre = 0.5F * textureScaleZ + scrollZ;
|
||||
|
||||
final double x0 = 0.0;
|
||||
final double x1 = 1.0;
|
||||
final double z0 = 0.0;
|
||||
final double z1 = 1.0;
|
||||
final double yBottom = 0.0;
|
||||
final double yTop = deckTopY;
|
||||
|
||||
ensureEmitCapacity(6 * quadBytes);
|
||||
emitQuad(x0, yBottom, z1, uLeft, vSouth, x1, yBottom, z1, uRight, vSouth, x1, yBottom, z0, uRight, vNorth, x0, yBottom, z0, uLeft, vNorth, FACE_DOWN, NORMAL_POS_Y);
|
||||
emitQuad(x0, yTop, z0, uLeft, vNorth, x1, yTop, z0, uRight, vNorth, x1, yTop, z1, uRight, vSouth, x0, yTop, z1, uLeft, vSouth, FACE_UP, NORMAL_NEG_Y);
|
||||
emitQuad(x0, yBottom, z0, uCentre, vNorth, x0, yTop, z0, uCentre, vNorth, x0, yTop, z1, uCentre, vSouth, x0, yBottom, z1, uCentre, vSouth, FACE_SIDE_X, NORMAL_POS_X);
|
||||
emitQuad(x1, yBottom, z1, uCentre, vSouth, x1, yTop, z1, uCentre, vSouth, x1, yTop, z0, uCentre, vNorth, x1, yBottom, z0, uCentre, vNorth, FACE_SIDE_X, NORMAL_NEG_X);
|
||||
emitQuad(x0, yBottom, z0, uLeft, vCentre, x1, yBottom, z0, uRight, vCentre, x1, yTop, z0, uRight, vCentre, x0, yTop, z0, uLeft, vCentre, FACE_SIDE_Z, NORMAL_POS_Z);
|
||||
emitQuad(x0, yTop, z1, uLeft, vCentre, x1, yTop, z1, uRight, vCentre, x1, yBottom, z1, uRight, vCentre, x0, yBottom, z1, uLeft, vCentre, FACE_SIDE_Z, NORMAL_NEG_Z);
|
||||
}
|
||||
|
||||
private void finishGeometry() {
|
||||
final int totalBytes = (int) (writeAddr - emitBufferAddr);
|
||||
final int vertices = totalBytes / vertexStride;
|
||||
pendingVertices = vertices;
|
||||
if (vertices == 0) {
|
||||
pendingUpload = null;
|
||||
return;
|
||||
}
|
||||
if (forPackShader) {
|
||||
pendingUpload = sortQuadsByFace(totalBytes);
|
||||
} else {
|
||||
emitBuffer.position(0).limit(totalBytes);
|
||||
pendingUpload = emitBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
void discardPendingUpload() {
|
||||
pendingVertices = -1;
|
||||
pendingUpload = null;
|
||||
uploadCursorBytes = -1;
|
||||
}
|
||||
|
||||
void uploadBuilt() {
|
||||
if (beginUpload()) uploadSlice(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
boolean beginUpload() {
|
||||
final int vertices = pendingVertices;
|
||||
pendingVertices = -1;
|
||||
if (vertices <= 0) {
|
||||
pendingUpload = null;
|
||||
if (vao != null) {
|
||||
vao.delete();
|
||||
vao = null;
|
||||
}
|
||||
vboCapacityVertices = 0;
|
||||
vertexCount = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Tracy.FINE_ZONES) Tracy.beginZone(Z_UPLOAD);
|
||||
try {
|
||||
final boolean wantUv = vertexStride == STRIDE_WITH_UV;
|
||||
if (vao != null && vaoHasUv != wantUv) {
|
||||
vao.delete();
|
||||
vao = null;
|
||||
vboCapacityVertices = 0;
|
||||
}
|
||||
|
||||
uploadVertices = vertices;
|
||||
uploadTotalBytes = vertices * vertexStride;
|
||||
|
||||
if (vao != null && vertices <= vboCapacityVertices) {
|
||||
uploadCursorBytes = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
final ByteBuffer source = pendingUpload;
|
||||
final int roomyVertices = clamp(vertices, vertices + (vertices >> 1), source.capacity() / vertexStride);
|
||||
source.position(0).limit(roomyVertices * vertexStride);
|
||||
if (vao == null) {
|
||||
vao = VertexBufferType.MUTABLE_RESIZABLE.allocate(
|
||||
wantUv ? DefaultVertexFormat.POSITION_TEXTURE_NORMAL : DefaultVertexFormat.POSITION_NORMAL,
|
||||
GL11.GL_TRIANGLES, source, roomyVertices);
|
||||
vaoHasUv = wantUv;
|
||||
setupShadeAttrib();
|
||||
} else {
|
||||
vao.getVBO().allocate(source, roomyVertices);
|
||||
}
|
||||
vboCapacityVertices = roomyVertices;
|
||||
uploadCursorBytes = uploadTotalBytes;
|
||||
return true;
|
||||
} finally {
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
}
|
||||
}
|
||||
|
||||
boolean uploadSlice(int maxBytes) {
|
||||
if (uploadCursorBytes < 0) return true;
|
||||
if (Tracy.FINE_ZONES) Tracy.beginZone(Z_UPLOAD);
|
||||
try {
|
||||
if (uploadCursorBytes < uploadTotalBytes) {
|
||||
final int sliceBytes = Math.min(maxBytes, uploadTotalBytes - uploadCursorBytes);
|
||||
final ByteBuffer source = pendingUpload;
|
||||
source.position(uploadCursorBytes).limit(uploadCursorBytes + sliceBytes);
|
||||
vao.getVBO().update(source, uploadCursorBytes);
|
||||
uploadCursorBytes += sliceBytes;
|
||||
if (uploadCursorBytes < uploadTotalBytes) return false;
|
||||
}
|
||||
finishUpload();
|
||||
return true;
|
||||
} finally {
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
}
|
||||
}
|
||||
|
||||
private void finishUpload() {
|
||||
uploadCursorBytes = -1;
|
||||
pendingUpload = null;
|
||||
vertexCount = uploadVertices;
|
||||
if (Tracy.FINE_ZONES) Tracy.beginZone(Z_EBO);
|
||||
ensureEbo(uploadVertices / 4);
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
orderAnchorX = Integer.MIN_VALUE;
|
||||
orderAnchorZ = Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
private void setupShadeAttrib() {
|
||||
vao.bind();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vao.getVBO().getId());
|
||||
GLStateManager.glEnableVertexAttribArray(FACE_SHADE_ATTRIB);
|
||||
GLStateManager.glVertexAttribPointer(FACE_SHADE_ATTRIB, 1, GL11.GL_UNSIGNED_BYTE, true, vertexStride, normalOffset + 3);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
vao.unbind();
|
||||
}
|
||||
|
||||
boolean orderStale(int anchorX, int anchorZ) {
|
||||
return anchorX != orderAnchorX || anchorZ != orderAnchorZ;
|
||||
}
|
||||
|
||||
boolean orderPublished() {
|
||||
return orderAnchorX != Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
void reorder(int anchorX, int anchorZ) {
|
||||
final int quads = vertexCount / 4;
|
||||
if (quads == 0) return;
|
||||
if (quads > quadCell.length) return;
|
||||
if (Tracy.FINE_ZONES) Tracy.beginZone(Z_REORDER);
|
||||
|
||||
final int dx = anchorX - buildAnchorX;
|
||||
final int dz = anchorZ - buildAnchorZ;
|
||||
final int bands = (buildRadiusCells + 2) * ORDER_BANDS_PER_CELL;
|
||||
final int bucketCount = ORDER_GROUP_COUNT * bands;
|
||||
|
||||
if (orderBucketCount.length < bucketCount + 1) orderBucketCount = new int[bucketCount + 1];
|
||||
else Arrays.fill(orderBucketCount, 0, bucketCount + 1, 0);
|
||||
|
||||
if (quadBucket.length < quads) quadBucket = new int[quads];
|
||||
|
||||
for (int q = 0; q < quads; q++) {
|
||||
final int cell = quadCell[q];
|
||||
final int cx = (short) (cell >> 16) - dx;
|
||||
final int cz = (short) cell - dz;
|
||||
final int groupBits = quadGroupBits[q];
|
||||
final int group = (groupBits >> 5) & 3;
|
||||
final int wedge = groupBits & 31;
|
||||
|
||||
final boolean facingAway = group == GROUP_WALL_WEDGE && switch ((groupBits >> 7) & 7) {
|
||||
case DIR_WEST -> cx < 1;
|
||||
case DIR_EAST -> cx > -1;
|
||||
case DIR_NORTH -> cz < 1;
|
||||
case DIR_SOUTH -> cz > -1;
|
||||
default -> false;
|
||||
};
|
||||
|
||||
int band = (int) (Math.sqrt((double) cx * cx + (double) cz * cz) * ORDER_BANDS_PER_CELL);
|
||||
if (band >= bands) band = bands - 1;
|
||||
|
||||
final int groupSlot = facingAway ? 2 + 2 * WEDGE_COUNT : switch (group) {
|
||||
case GROUP_PLATE_ALWAYS -> 0;
|
||||
case GROUP_PLATE_WEDGE -> 1 + wedge;
|
||||
case GROUP_ALWAYS_DRAWN -> 1 + WEDGE_COUNT;
|
||||
default -> 2 + WEDGE_COUNT + wedge;
|
||||
};
|
||||
final int bucket = groupSlot * bands + band;
|
||||
quadBucket[q] = bucket;
|
||||
orderBucketCount[bucket]++;
|
||||
}
|
||||
|
||||
int acc = 0;
|
||||
for (int b = 0; b < bucketCount; b++) {
|
||||
final int count = orderBucketCount[b];
|
||||
orderBucketCount[b] = acc;
|
||||
acc += count;
|
||||
}
|
||||
orderBucketCount[bucketCount] = acc;
|
||||
|
||||
for (int w = 0; w < WEDGE_COUNT; w++) plateStartScratch[w] = orderBucketCount[(1 + w) * bands];
|
||||
plateStartScratch[WEDGE_COUNT] = orderBucketCount[(1 + WEDGE_COUNT) * bands];
|
||||
for (int w = 0; w < WEDGE_COUNT; w++) wallStartScratch[w] = orderBucketCount[(2 + WEDGE_COUNT + w) * bands];
|
||||
wallStartScratch[WEDGE_COUNT] = orderBucketCount[(2 + 2 * WEDGE_COUNT) * bands];
|
||||
|
||||
final int needed = quads * 6;
|
||||
if (orderIndexBuffer == null || orderIndexBuffer.capacity() < needed) {
|
||||
orderIndexBuffer = BufferUtils.createIntBuffer(Math.max(needed, 4096));
|
||||
}
|
||||
|
||||
if (orderIndexScratch.length < needed) orderIndexScratch = new int[needed];
|
||||
final int[] out = orderIndexScratch;
|
||||
for (int q = 0; q < quads; q++) {
|
||||
final int slot = orderBucketCount[quadBucket[q]]++;
|
||||
final int v = q * 4;
|
||||
final int base = slot * 6;
|
||||
out[base] = v;
|
||||
out[base + 1] = v + 1;
|
||||
out[base + 2] = v + 2;
|
||||
out[base + 3] = v;
|
||||
out[base + 4] = v + 2;
|
||||
out[base + 5] = v + 3;
|
||||
}
|
||||
|
||||
final IntBuffer indices = orderIndexBuffer;
|
||||
indices.clear();
|
||||
indices.put(out, 0, needed);
|
||||
indices.flip();
|
||||
|
||||
orderPendingQuads = quads;
|
||||
orderPendingAnchorX = anchorX;
|
||||
orderPendingAnchorZ = anchorZ;
|
||||
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
}
|
||||
|
||||
void reorderUpload() {
|
||||
if (orderPendingQuads <= 0) return;
|
||||
if (Tracy.FINE_ZONES) Tracy.beginZone(Z_REORDER_UPLOAD);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
GLStateManager.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, orderIndexBuffer, GL15.GL_DYNAMIC_DRAW);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
System.arraycopy(plateStartScratch, 0, plateStart, 0, plateStart.length);
|
||||
System.arraycopy(wallStartScratch, 0, wallStart, 0, wallStart.length);
|
||||
eboQuadCapacity = orderPendingQuads;
|
||||
eboIsSequential = false;
|
||||
orderAnchorX = orderPendingAnchorX;
|
||||
orderAnchorZ = orderPendingAnchorZ;
|
||||
orderPendingQuads = 0;
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
}
|
||||
|
||||
void discardPendingReorder() {
|
||||
orderPendingQuads = 0;
|
||||
}
|
||||
|
||||
private void ensureEbo(int quads) {
|
||||
if (eboId == -1) {
|
||||
eboId = GLStateManager.glGenBuffers();
|
||||
}
|
||||
|
||||
if (quads <= eboQuadCapacity && eboIsSequential) return;
|
||||
eboIsSequential = true;
|
||||
final int capacity = Math.max(quads + (quads >> 1), 4096);
|
||||
final IntBuffer indices = sequentialIndices(capacity);
|
||||
indices.position(0).limit(capacity * 6);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
GLStateManager.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indices, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
eboQuadCapacity = capacity;
|
||||
}
|
||||
|
||||
private static IntBuffer sequentialIndices(int quads) {
|
||||
if (sharedSequentialIndices != null && sharedSequentialQuads >= quads) return sharedSequentialIndices;
|
||||
|
||||
final IntBuffer indices = BufferUtils.createIntBuffer(quads * 6);
|
||||
for (int q = 0; q < quads; q++) {
|
||||
final int v = q * 4;
|
||||
indices.put(v).put(v + 1).put(v + 2);
|
||||
indices.put(v).put(v + 2).put(v + 3);
|
||||
}
|
||||
indices.flip();
|
||||
sharedSequentialIndices = indices;
|
||||
sharedSequentialQuads = quads;
|
||||
return indices;
|
||||
}
|
||||
|
||||
void deleteEbo() {
|
||||
if (eboId != -1) {
|
||||
GLStateManager.glDeleteBuffers(eboId);
|
||||
eboId = -1;
|
||||
eboQuadCapacity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void deleteVao() {
|
||||
if (vao != null) {
|
||||
vao.delete();
|
||||
vao = null;
|
||||
}
|
||||
vboCapacityVertices = 0;
|
||||
vertexCount = 0;
|
||||
}
|
||||
|
||||
void bind() {
|
||||
vao.bind();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||
}
|
||||
|
||||
void unbind() {
|
||||
vao.unbind();
|
||||
}
|
||||
|
||||
void drawRange(int startQuad, int endQuad) {
|
||||
final int quads = endQuad - startQuad;
|
||||
if (quads <= 0) return;
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, quads * 6, GL11.GL_UNSIGNED_INT, (long) startQuad * 6L * 4L);
|
||||
}
|
||||
|
||||
void drawAll() {
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, (vertexCount / 4) * 6, GL11.GL_UNSIGNED_INT, 0L);
|
||||
}
|
||||
|
||||
boolean faceEmpty(int face) {
|
||||
final int base = face * WEDGE_BUCKETS;
|
||||
return faceWedgeStart[base + WEDGE_BUCKETS] == faceWedgeStart[base];
|
||||
}
|
||||
|
||||
void drawFace(int face) {
|
||||
final int base = face * WEDGE_BUCKETS;
|
||||
drawRange(faceWedgeStart[base], faceWedgeStart[base + WEDGE_BUCKETS]);
|
||||
}
|
||||
|
||||
private void prepareEmitBuffer() {
|
||||
if (emitBuffer == null) {
|
||||
emitBufferCapacity = 256 * 1024;
|
||||
emitBuffer = MemoryUtilities.memRealloc((ByteBuffer) null, emitBufferCapacity);
|
||||
emitBufferAddr = MemoryUtilities.memAddress(emitBuffer, 0);
|
||||
}
|
||||
|
||||
emitBuffer.clear();
|
||||
writeAddr = emitBufferAddr;
|
||||
writeEnd = emitBufferAddr + emitBufferCapacity;
|
||||
quadCount = 0;
|
||||
}
|
||||
|
||||
private void growEmitBuffer(int neededBytes) {
|
||||
final long oldBytes = writeAddr - emitBufferAddr;
|
||||
int newCap = emitBufferCapacity;
|
||||
while (newCap < neededBytes) newCap *= 2;
|
||||
emitBuffer = MemoryUtilities.memRealloc(emitBuffer, newCap);
|
||||
emitBufferAddr = MemoryUtilities.memAddress(emitBuffer, 0);
|
||||
emitBufferCapacity = newCap;
|
||||
writeAddr = emitBufferAddr + oldBytes;
|
||||
writeEnd = emitBufferAddr + newCap;
|
||||
}
|
||||
|
||||
private void ensureEmitCapacity(int bytes) {
|
||||
if (writeAddr + bytes > writeEnd) {
|
||||
growEmitBuffer((int) (writeAddr - emitBufferAddr) + bytes);
|
||||
}
|
||||
}
|
||||
|
||||
private void emitQuad(double x0, double y0, double z0, float u0, float v0, double x1, double y1, double z1, float u1, float v1, double x2, double y2, double z2, float u2, float v2, double x3, double y3, double z3, float u3, float v3, byte face, int packedNormal) {
|
||||
if (quadCount == quadFace.length) {
|
||||
quadFace = Arrays.copyOf(quadFace, quadFace.length * 2);
|
||||
if (!forPackShader) {
|
||||
quadCell = Arrays.copyOf(quadCell, quadCell.length * 2);
|
||||
quadGroupBits = Arrays.copyOf(quadGroupBits, quadGroupBits.length * 2);
|
||||
}
|
||||
}
|
||||
if (!forPackShader) {
|
||||
quadCell[quadCount] = (nextQuadCellX << 16) | (nextQuadCellZ & 0xFFFF);
|
||||
quadGroupBits[quadCount] = (short) ((nextQuadDir << 7) | (nextQuadGroup << 5) | nextQuadWedge);
|
||||
}
|
||||
quadFace[quadCount++] = face;
|
||||
|
||||
final int normalAndShade = packedNormal | FACE_SHADE_PACKED[face];
|
||||
long addr = writeAddr;
|
||||
addr = writeVertex(addr, x0, y0, z0, u0, v0, normalAndShade);
|
||||
addr = writeVertex(addr, x1, y1, z1, u1, v1, normalAndShade);
|
||||
addr = writeVertex(addr, x2, y2, z2, u2, v2, normalAndShade);
|
||||
addr = writeVertex(addr, x3, y3, z3, u3, v3, normalAndShade);
|
||||
writeAddr = addr;
|
||||
}
|
||||
|
||||
private long writeVertex(long addr, double x, double y, double z, float u, float v, int normalAndShade) {
|
||||
MemoryUtilities.memPutFloat(addr, (float) x);
|
||||
MemoryUtilities.memPutFloat(addr + 4, (float) y);
|
||||
MemoryUtilities.memPutFloat(addr + 8, (float) z);
|
||||
if (normalOffset == NORMAL_OFFSET_WITH_UV) {
|
||||
MemoryUtilities.memPutFloat(addr + 12, u);
|
||||
MemoryUtilities.memPutFloat(addr + 16, v);
|
||||
}
|
||||
MemoryUtilities.memPutInt(addr + normalOffset, normalAndShade);
|
||||
return addr + vertexStride;
|
||||
}
|
||||
|
||||
private ByteBuffer sortQuadsByFace(int emittedBytes) {
|
||||
final int quadTotal = quadCount;
|
||||
Arrays.fill(faceWedgeStart, 0);
|
||||
|
||||
if (quadSortKey.length < quadTotal) quadSortKey = new int[quadTotal];
|
||||
final byte[] faces = quadFace;
|
||||
boolean alreadyGrouped = true;
|
||||
int previousKey = 0;
|
||||
for (int q = 0; q < quadTotal; q++) {
|
||||
final int key = faces[q] * WEDGE_BUCKETS;
|
||||
if (key < previousKey) alreadyGrouped = false;
|
||||
previousKey = key;
|
||||
quadSortKey[q] = key;
|
||||
faceWedgeStart[key]++;
|
||||
}
|
||||
|
||||
int acc = 0;
|
||||
for (int b = 0; b < FACE_WEDGE_BUCKETS; b++) {
|
||||
final int count = faceWedgeStart[b];
|
||||
faceWedgeStart[b] = acc;
|
||||
faceWedgeCursor[b] = acc;
|
||||
acc += count;
|
||||
}
|
||||
faceWedgeStart[FACE_WEDGE_BUCKETS] = acc;
|
||||
|
||||
if (alreadyGrouped) {
|
||||
emitBuffer.position(0).limit(emittedBytes);
|
||||
return emitBuffer;
|
||||
}
|
||||
|
||||
final int totalBytes = quadTotal * quadBytes;
|
||||
if (sortedBuffer == null || sortedBufferCapacity < totalBytes) {
|
||||
int rounded = Math.max(totalBytes, 64 * 1024);
|
||||
int pow2 = Integer.highestOneBit(rounded - 1) << 1;
|
||||
if (pow2 <= 0) pow2 = rounded;
|
||||
sortedBuffer = MemoryUtilities.memRealloc(sortedBuffer, pow2);
|
||||
sortedBufferAddr = MemoryUtilities.memAddress(sortedBuffer, 0);
|
||||
sortedBufferCapacity = pow2;
|
||||
}
|
||||
|
||||
final long srcBase = emitBufferAddr;
|
||||
int q = 0;
|
||||
while (q < quadTotal) {
|
||||
final int key = quadSortKey[q];
|
||||
int runEnd = q + 1;
|
||||
while (runEnd < quadTotal && quadSortKey[runEnd] == key) runEnd++;
|
||||
final int runLen = runEnd - q;
|
||||
final long runBytes = (long) runLen * quadBytes;
|
||||
MemoryUtilities.memCopy(srcBase + (long) q * quadBytes,
|
||||
sortedBufferAddr + (long) faceWedgeCursor[key] * quadBytes, runBytes);
|
||||
faceWedgeCursor[key] += runLen;
|
||||
q = runEnd;
|
||||
}
|
||||
|
||||
sortedBuffer.position(0).limit(totalBytes);
|
||||
return sortedBuffer;
|
||||
}
|
||||
}
|
||||
+6
-2
@@ -202,6 +202,10 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
|
||||
}
|
||||
|
||||
private void bindTerrainSampler() {
|
||||
if (!SodiumGameOptions.usesTerrainTexelSnap()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int anisotropy = SodiumGameOptions.resolvedAnisotropicFiltering();
|
||||
final boolean nearest = ClientProxy.options().quality.texelSampling.isNearest();
|
||||
|
||||
@@ -259,8 +263,8 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
|
||||
RenderSystem.samplerParameteri(sampler, GL11.GL_TEXTURE_MAG_FILTER,
|
||||
nearest ? GL11.GL_NEAREST : GL11.GL_LINEAR);
|
||||
if (SodiumGameOptions.anisotropySupported()) {
|
||||
RenderSystem.samplerParameteri(sampler,
|
||||
EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy);
|
||||
RenderSystem.samplerParameterf(sampler,
|
||||
EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, (float) anisotropy);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import com.google.common.collect.ImmutableListMultimap;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.utils.SpritePadding;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
|
||||
@@ -29,9 +30,21 @@ public class AngelicaRenderPassConfiguration {
|
||||
.vertexType(vertexType)
|
||||
.primitiveType(QuadPrimitiveType.TRIANGULATED);
|
||||
|
||||
final int mipmapLevels = SodiumGameOptions.terrainMipmapLevels();
|
||||
|
||||
if (rgssEnabled) {
|
||||
builder.extraDefine("USE_RGSS", "");
|
||||
}
|
||||
if (SodiumGameOptions.usesTerrainTexelSnap()) {
|
||||
builder.extraDefine("USE_TEXEL_SNAP", "");
|
||||
}
|
||||
if (SodiumGameOptions.effectiveTextureFilterMode().usesAnisotropy()) {
|
||||
builder.extraDefine("USE_ANISOTROPIC", "");
|
||||
builder.extraDefine("TERRAIN_GUTTER", SpritePadding.gutterFor(mipmapLevels, true) + ".0");
|
||||
}
|
||||
if (mipmapLevels <= 0) {
|
||||
builder.extraDefine("TERRAIN_NO_MIPS", "");
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@@ -46,33 +46,27 @@ public final class SpritePadding {
|
||||
final int levelWidth = width >> level;
|
||||
final int levelHeight = height >> level;
|
||||
final int levelGutter = gutter >> level;
|
||||
final int size = (levelWidth + 2 * levelGutter) * (levelHeight + 2 * levelGutter);
|
||||
final int stride = levelWidth + 2 * levelGutter;
|
||||
final int size = stride * (levelHeight + 2 * levelGutter);
|
||||
|
||||
int[] target = padded[level];
|
||||
if (target == null || target.length < size) {
|
||||
target = new int[size];
|
||||
padded[level] = target;
|
||||
}
|
||||
System.arraycopy(levelData, 0, target, 0, Math.min(levelData.length, levelWidth * levelHeight));
|
||||
padClampToEdge(target, levelWidth, levelHeight, levelGutter);
|
||||
|
||||
int remaining = Math.min(levelData.length, levelWidth * levelHeight);
|
||||
for (int y = 0; y < levelHeight && remaining > 0; y++) {
|
||||
final int run = Math.min(levelWidth, remaining);
|
||||
System.arraycopy(levelData, y * levelWidth, target,
|
||||
(y + levelGutter) * stride + levelGutter, run);
|
||||
remaining -= run;
|
||||
}
|
||||
clampBorderToEdge(target, stride, levelHeight + 2 * levelGutter, levelGutter);
|
||||
}
|
||||
return padded;
|
||||
}
|
||||
|
||||
private static void padClampToEdge(int[] data, int width, int height, int border) {
|
||||
if (border <= 0 || width <= 0 || height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int stride = width + 2 * border;
|
||||
|
||||
for (int y = height - 1; y >= 0; y--) {
|
||||
System.arraycopy(data, y * width, data, border + (y + border) * stride, width);
|
||||
}
|
||||
|
||||
clampBorderToEdge(data, stride, height + 2 * border, border);
|
||||
}
|
||||
|
||||
private static void clampBorderToEdge(int[] image, int paddedWidth, int paddedHeight, int border) {
|
||||
if (border <= 0) {
|
||||
return;
|
||||
|
||||
@@ -2,6 +2,7 @@ package me.jellysquid.mods.sodium.client.gui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
@@ -202,6 +203,10 @@ public class SodiumGameOptionPages {
|
||||
.setBinding((opts, value) -> opts.quality.texelSampling = value,
|
||||
opts -> opts.quality.texelSampling)
|
||||
.setImpact(OptionImpact.LOW)
|
||||
// Only this mode needs a rerender
|
||||
.setFlags(() -> textureFilterMode.getValue() == TextureFilterMode.NONE
|
||||
? EnumSet.of(OptionFlag.REQUIRES_RENDERER_RELOAD)
|
||||
: EnumSet.noneOf(OptionFlag.class))
|
||||
.build();
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
|
||||
@@ -80,6 +80,11 @@ public class SodiumGameOptions {
|
||||
return resolveFilterMode(ClientProxy.options().quality.textureFilterMode);
|
||||
}
|
||||
|
||||
public static boolean usesTerrainTexelSnap() {
|
||||
return effectiveTextureFilterMode() != TextureFilterMode.NONE
|
||||
|| !ClientProxy.options().quality.texelSampling.isNearest();
|
||||
}
|
||||
|
||||
public static TextureFilterMode resolveFilterMode(TextureFilterMode mode) {
|
||||
if (mode == null) {
|
||||
mode = TextureFilterMode.RGSS_ANISOTROPIC;
|
||||
|
||||
@@ -16,6 +16,7 @@ import java.util.EnumSet;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class OptionImpl<S, T> implements OptionExtended<T> {
|
||||
private final OptionStorage<S> storage;
|
||||
@@ -23,7 +24,7 @@ public class OptionImpl<S, T> implements OptionExtended<T> {
|
||||
private final OptionBinding<S, T> binding;
|
||||
private final Control<T> control;
|
||||
|
||||
private final EnumSet<OptionFlag> flags;
|
||||
private final Supplier<EnumSet<OptionFlag>> flags;
|
||||
|
||||
private final String name;
|
||||
private final String tooltip;
|
||||
@@ -41,7 +42,7 @@ public class OptionImpl<S, T> implements OptionExtended<T> {
|
||||
String tooltip,
|
||||
OptionBinding<S, T> binding,
|
||||
Function<OptionImpl<S, T>, Control<T>> control,
|
||||
EnumSet<OptionFlag> flags,
|
||||
Supplier<EnumSet<OptionFlag>> flags,
|
||||
OptionImpact impact,
|
||||
boolean enabled) {
|
||||
this.storage = storage;
|
||||
@@ -121,7 +122,7 @@ public class OptionImpl<S, T> implements OptionExtended<T> {
|
||||
|
||||
@Override
|
||||
public Collection<OptionFlag> getFlags() {
|
||||
return this.flags;
|
||||
return this.flags.get();
|
||||
}
|
||||
|
||||
public static <S, T> OptionImpl.Builder<S, T> createBuilder(Class<T> type, OptionStorage<S> storage) {
|
||||
@@ -136,6 +137,7 @@ public class OptionImpl<S, T> implements OptionExtended<T> {
|
||||
private Function<OptionImpl<S, T>, Control<T>> control;
|
||||
private OptionImpact impact;
|
||||
private final EnumSet<OptionFlag> flags = EnumSet.noneOf(OptionFlag.class);
|
||||
private Supplier<EnumSet<OptionFlag>> flagsSupplier = () -> this.flags;
|
||||
private boolean enabled = true;
|
||||
|
||||
private Builder(OptionStorage<S> storage) {
|
||||
@@ -202,13 +204,21 @@ public class OptionImpl<S, T> implements OptionExtended<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<S, T> setFlags(Supplier<EnumSet<OptionFlag>> flags) {
|
||||
Validate.notNull(flags, "Flags supplier must not be null");
|
||||
|
||||
this.flagsSupplier = flags;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public OptionImpl<S, T> build() {
|
||||
Validate.notNull(this.name, "Name must be specified");
|
||||
Validate.notNull(this.tooltip, "Tooltip must be specified");
|
||||
Validate.notNull(this.binding, "Option binding must be specified");
|
||||
Validate.notNull(this.control, "Control must be specified");
|
||||
|
||||
return new OptionImpl<>(this.storage, this.name, this.tooltip, this.binding, this.control, this.flags, this.impact, this.enabled);
|
||||
return new OptionImpl<>(this.storage, this.name, this.tooltip, this.binding, this.control, this.flagsSupplier, this.impact, this.enabled);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,13 +60,33 @@ vec2 texelsPerPixelOf(vec2 du, vec2 dv, vec2 texelSize) {
|
||||
return sqrt(du * du + dv * dv) / texelSize;
|
||||
}
|
||||
|
||||
#ifdef USE_ANISOTROPIC
|
||||
float footprintScale(vec2 du, vec2 dv, vec2 texelSize, float limitTexels) {
|
||||
float footprint = max(length(du / texelSize), length(dv / texelSize));
|
||||
return min(1.0, limitTexels / max(footprint, 1e-8));
|
||||
}
|
||||
#endif
|
||||
|
||||
vec4 sampleTexelSnapped(sampler2D tex, vec2 uv, vec2 texelSize, vec2 du, vec2 dv, vec2 texelsPerPixel) {
|
||||
vec2 snapped = snapToTexelCentre(uv, texelSize, texelsPerPixel);
|
||||
|
||||
#if defined(TERRAIN_NO_MIPS) && !defined(USE_ANISOTROPIC)
|
||||
return textureLod(tex, snapped, 0.0);
|
||||
#else
|
||||
float gradientScale = exp2(v_MaterialMipBias);
|
||||
#ifdef USE_ANISOTROPIC
|
||||
gradientScale *= footprintScale(du * gradientScale, dv * gradientScale, texelSize, TERRAIN_GUTTER);
|
||||
#endif
|
||||
|
||||
return textureGrad(tex, snapped, du * gradientScale, dv * gradientScale);
|
||||
#endif
|
||||
}
|
||||
|
||||
vec4 sampleTexelSnapped(sampler2D tex, vec2 uv, vec2 texelSize) {
|
||||
vec2 du = dFdx(uv);
|
||||
vec2 dv = dFdy(uv);
|
||||
|
||||
float gradientScale = exp2(v_MaterialMipBias);
|
||||
return textureGrad(tex, snapToTexelCentre(uv, texelSize, texelsPerPixelOf(du, dv, texelSize)),
|
||||
du * gradientScale, dv * gradientScale);
|
||||
return sampleTexelSnapped(tex, uv, texelSize, du, dv, texelsPerPixelOf(du, dv, texelSize));
|
||||
}
|
||||
|
||||
#ifdef USE_RGSS
|
||||
@@ -83,33 +103,76 @@ const vec2 RGSS_OFFSETS[4] = vec2[4](
|
||||
vec2(-0.375, 0.125)
|
||||
);
|
||||
|
||||
#ifdef USE_ANISOTROPIC
|
||||
const float RGSS_FOOTPRINT_LIMIT = 0.75 * TERRAIN_GUTTER;
|
||||
#endif
|
||||
|
||||
vec4 sampleRGSS(sampler2D tex, vec2 uv, vec2 texelSize) {
|
||||
vec2 du = dFdx(uv);
|
||||
vec2 dv = dFdy(uv);
|
||||
|
||||
vec2 snapped = snapToTexelCentre(uv, texelSize, texelsPerPixelOf(du, dv, texelSize));
|
||||
vec2 texelsPerPixel = texelsPerPixelOf(du, dv, texelSize);
|
||||
float rgssFactor = smoothstep(1.0, 2.0, max(texelsPerPixel.x, texelsPerPixel.y));
|
||||
|
||||
float gradientScale = exp2(v_MaterialMipBias + RGSS_MIP_BIAS);
|
||||
vec2 gradU = du * gradientScale;
|
||||
vec2 gradV = dv * gradientScale;
|
||||
#ifdef USE_ANISOTROPIC
|
||||
float footprint = max(length(du / texelSize), length(dv / texelSize));
|
||||
rgssFactor *= 1.0 - smoothstep(RGSS_FOOTPRINT_LIMIT, TERRAIN_GUTTER, footprint);
|
||||
#endif
|
||||
|
||||
vec4 color = vec4(0.0);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
vec2 tap = RGSS_OFFSETS[i].x * du + RGSS_OFFSETS[i].y * dv;
|
||||
color += textureGrad(tex, snapped + tap, gradU, gradV);
|
||||
if (rgssFactor <= 0.0) {
|
||||
return sampleTexelSnapped(tex, uv, texelSize, du, dv, texelsPerPixel);
|
||||
}
|
||||
|
||||
return color * 0.25;
|
||||
vec4 rgss = vec4(0.0);
|
||||
#ifdef USE_ANISOTROPIC
|
||||
float fit = footprintScale(du, dv, texelSize, RGSS_FOOTPRINT_LIMIT);
|
||||
vec2 spreadU = du * fit;
|
||||
vec2 spreadV = dv * fit;
|
||||
|
||||
// The taps span the footprint; the gradients are one mip sharper, which is what four of them buy.
|
||||
float gradientScale = exp2(v_MaterialMipBias + RGSS_MIP_BIAS);
|
||||
vec2 gradU = spreadU * gradientScale;
|
||||
vec2 gradV = spreadV * gradientScale;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
rgss += textureGrad(tex, uv + RGSS_OFFSETS[i].x * spreadU + RGSS_OFFSETS[i].y * spreadV, gradU, gradV);
|
||||
}
|
||||
#elif defined(TERRAIN_NO_MIPS)
|
||||
const float lod = 0.0;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
rgss += textureLod(tex, uv + RGSS_OFFSETS[i] * texelSize, lod);
|
||||
}
|
||||
#else
|
||||
float duLength = length(du / texelSize);
|
||||
float dvLength = length(dv / texelSize);
|
||||
float lod = max(0.0, 0.5 * log2(duLength * dvLength) + v_MaterialMipBias + RGSS_MIP_BIAS);
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
rgss += textureLod(tex, uv + RGSS_OFFSETS[i] * texelSize, lod);
|
||||
}
|
||||
#endif
|
||||
rgss *= 0.25;
|
||||
|
||||
if (rgssFactor >= 1.0) {
|
||||
return rgss;
|
||||
}
|
||||
|
||||
return mix(sampleTexelSnapped(tex, uv, texelSize, du, dv, texelsPerPixel), rgss, rgssFactor);
|
||||
}
|
||||
#endif
|
||||
|
||||
void main() {
|
||||
#ifdef USE_TEXEL_SNAP
|
||||
vec2 texelSize = 1.0 / vec2(textureSize(u_BlockTex, 0));
|
||||
#ifdef USE_RGSS
|
||||
vec4 diffuseColor = sampleRGSS(u_BlockTex, v_TexCoord, texelSize);
|
||||
#else
|
||||
vec4 diffuseColor = sampleTexelSnapped(u_BlockTex, v_TexCoord, texelSize);
|
||||
#endif
|
||||
#else
|
||||
vec4 diffuseColor = texture(u_BlockTex, v_TexCoord, v_MaterialMipBias);
|
||||
#endif
|
||||
|
||||
#ifdef USE_FRAGMENT_DISCARD
|
||||
if (diffuseColor.a < v_MaterialAlphaCutoff) {
|
||||
|
||||
@@ -1,36 +1,30 @@
|
||||
#version 330 core
|
||||
|
||||
in vec2 v_UV;
|
||||
in vec3 v_EyePos;
|
||||
in float v_Factor;
|
||||
#import <angelica:include/cloud_fog.glsl>
|
||||
|
||||
#ifndef UNTEXTURED
|
||||
in vec2 v_UV;
|
||||
#endif
|
||||
in vec3 v_EyePos;
|
||||
in float v_Shade;
|
||||
|
||||
#ifndef UNTEXTURED
|
||||
uniform sampler2D u_Tex;
|
||||
#endif
|
||||
uniform vec4 u_ColorMult;
|
||||
uniform vec4 u_FogParams; // linear: x=-1/(end-start), y=end/(end-start); exp/exp2: z=density; w=mode (0=linear, 1=exp, 2=exp2)
|
||||
uniform vec4 u_FogColor;
|
||||
uniform bool u_FogEnabled;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
#ifdef UNTEXTURED
|
||||
vec4 color = u_ColorMult;
|
||||
#else
|
||||
vec4 texel = texture(u_Tex, v_UV);
|
||||
if (texel.a < 0.1) discard;
|
||||
vec4 color = texel * u_ColorMult;
|
||||
color.rgb *= v_Factor;
|
||||
|
||||
if (u_FogEnabled) {
|
||||
float fogCoord = length(v_EyePos);
|
||||
float f;
|
||||
if (u_FogParams.w == 0.0) {
|
||||
f = fogCoord * u_FogParams.x + u_FogParams.y;
|
||||
} else if (u_FogParams.w == 1.0) {
|
||||
f = exp(-u_FogParams.z * fogCoord);
|
||||
} else {
|
||||
float d = u_FogParams.z * fogCoord;
|
||||
f = exp(-d * d);
|
||||
}
|
||||
color.rgb = mix(u_FogColor.rgb, color.rgb, clamp(f, 0.0, 1.0));
|
||||
}
|
||||
#endif
|
||||
color.rgb *= v_Shade;
|
||||
color.rgb = applyCloudFog(color.rgb, v_EyePos);
|
||||
|
||||
fragColor = color;
|
||||
}
|
||||
@@ -1,19 +1,25 @@
|
||||
#version 330 core
|
||||
|
||||
layout(location = 0) in vec3 a_Position;
|
||||
#ifndef UNTEXTURED
|
||||
layout(location = 2) in vec2 a_UV;
|
||||
layout(location = 5) in float a_Factor;
|
||||
#endif
|
||||
layout(location = 5) in float a_Shade;
|
||||
|
||||
uniform mat4 u_MVPMatrix;
|
||||
uniform mat4 u_MVMatrix;
|
||||
|
||||
#ifndef UNTEXTURED
|
||||
out vec2 v_UV;
|
||||
#endif
|
||||
out vec3 v_EyePos;
|
||||
out float v_Factor;
|
||||
out float v_Shade;
|
||||
|
||||
void main() {
|
||||
gl_Position = u_MVPMatrix * vec4(a_Position, 1.0);
|
||||
#ifndef UNTEXTURED
|
||||
v_UV = a_UV;
|
||||
#endif
|
||||
v_EyePos = (u_MVMatrix * vec4(a_Position, 1.0)).xyz;
|
||||
v_Factor = a_Factor;
|
||||
v_Shade = a_Shade;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#version 330 core
|
||||
|
||||
#import <angelica:include/cloud_fog.glsl>
|
||||
|
||||
in vec3 v_EyePos;
|
||||
flat in float v_Shade;
|
||||
|
||||
#ifndef UNTEXTURED
|
||||
in vec2 v_UV;
|
||||
uniform sampler2D u_Tex;
|
||||
#endif
|
||||
|
||||
uniform vec4 u_ColorMult;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
#ifdef UNTEXTURED
|
||||
vec4 color = u_ColorMult;
|
||||
#else
|
||||
vec4 texel = texture(u_Tex, v_UV);
|
||||
if (texel.a < 0.1) discard;
|
||||
vec4 color = texel * u_ColorMult;
|
||||
#endif
|
||||
color.rgb *= v_Shade;
|
||||
color.rgb = applyCloudFog(color.rgb, v_EyePos);
|
||||
|
||||
fragColor = color;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#version 330 core
|
||||
|
||||
layout(location = 0) in int a_Face;
|
||||
|
||||
uniform mat4 u_MVPMatrix;
|
||||
uniform mat4 u_MVMatrix;
|
||||
uniform float u_CellHeight;
|
||||
|
||||
out vec3 v_EyePos;
|
||||
flat out float v_Shade;
|
||||
|
||||
#ifndef UNTEXTURED
|
||||
uniform vec4 u_Scroll;
|
||||
out vec2 v_UV;
|
||||
const int DIR_NORTH = 2;
|
||||
const int DIR_WEST = 4;
|
||||
#endif
|
||||
|
||||
const int FLAG_INSIDE = 1;
|
||||
|
||||
const vec3 CORNERS[24] = vec3[24](
|
||||
vec3(1, 0, 0), vec3(1, 0, 1), vec3(0, 0, 1), vec3(0, 0, 0),
|
||||
vec3(0, 1, 0), vec3(0, 1, 1), vec3(1, 1, 1), vec3(1, 1, 0),
|
||||
vec3(0, 0, 0), vec3(0, 1, 0), vec3(1, 1, 0), vec3(1, 0, 0),
|
||||
vec3(1, 0, 1), vec3(1, 1, 1), vec3(0, 1, 1), vec3(0, 0, 1),
|
||||
vec3(0, 0, 1), vec3(0, 1, 1), vec3(0, 1, 0), vec3(0, 0, 0),
|
||||
vec3(1, 0, 0), vec3(1, 1, 0), vec3(1, 1, 1), vec3(1, 0, 1)
|
||||
);
|
||||
|
||||
const float FACE_SHADE[6] = float[6](0.7, 1.0, 0.8, 0.8, 0.9, 0.9);
|
||||
|
||||
void main() {
|
||||
int faceBits = a_Face;
|
||||
|
||||
int cellX = (faceBits & 0x3FF) - 512;
|
||||
int cellZ = ((faceBits >> 10) & 0x3FF) - 512;
|
||||
int dir = (faceBits >> 20) & 7;
|
||||
int flags = (faceBits >> 23) & 3;
|
||||
|
||||
vec3 extent = vec3(1.0, u_CellHeight, 1.0);
|
||||
|
||||
int corner = gl_VertexID;
|
||||
if ((flags & FLAG_INSIDE) != 0) corner = 3 - corner;
|
||||
vec3 unit = vec3(CORNERS[dir * 4 + corner]);
|
||||
|
||||
vec3 pos = vec3(float(cellX), 0.0, float(cellZ)) + unit * extent;
|
||||
|
||||
#ifndef UNTEXTURED
|
||||
vec2 uvCell = vec2(float(cellX), float(cellZ)) + unit.xz;
|
||||
if (dir >= DIR_WEST) uvCell.x = float(cellX) + 0.5;
|
||||
else if (dir >= DIR_NORTH) uvCell.y = float(cellZ) + 0.5;
|
||||
v_UV = uvCell * u_Scroll.zw + u_Scroll.xy;
|
||||
#endif
|
||||
|
||||
gl_Position = u_MVPMatrix * vec4(pos, 1.0);
|
||||
v_EyePos = (u_MVMatrix * vec4(pos, 1.0)).xyz;
|
||||
v_Shade = FACE_SHADE[dir];
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
uniform vec4 u_FogParams; // linear: x=-1/(end-start), y=end/(end-start); exp/exp2: z=density; w=mode (0=linear, 1=exp, 2=exp2)
|
||||
uniform vec4 u_FogColor;
|
||||
uniform bool u_FogEnabled;
|
||||
|
||||
vec3 applyCloudFog(vec3 color, vec3 eyePos) {
|
||||
if (!u_FogEnabled) return color;
|
||||
|
||||
float fogCoord = length(eyePos);
|
||||
float f;
|
||||
if (u_FogParams.w == 0.0) {
|
||||
f = fogCoord * u_FogParams.x + u_FogParams.y;
|
||||
} else if (u_FogParams.w == 1.0) {
|
||||
f = exp(-u_FogParams.z * fogCoord);
|
||||
} else {
|
||||
float d = u_FogParams.z * fogCoord;
|
||||
f = exp(-d * d);
|
||||
}
|
||||
return mix(u_FogColor.rgb, color, clamp(f, 0.0, 1.0));
|
||||
}
|
||||
+3
-4
@@ -150,12 +150,11 @@ public abstract class MixinTextureAtlasSprite implements SpriteExtension {
|
||||
}
|
||||
|
||||
/**
|
||||
* Vanilla's anisotropic path inflates width/height by 16 and insets the UVs by 8, which is the number
|
||||
* mods subtract to unpad. The gutter goes in the stitcher slot instead, so this always reads false and
|
||||
* the sprite keeps its real dims.
|
||||
* Vanilla's anisotropic path inflates width/height by 16 and insets the UVs by 8. The custom atlas path uses
|
||||
* the selected filter mode's gutter in the stitcher slot instead.
|
||||
*/
|
||||
@ModifyVariable(method = "loadSprite", at = @At("HEAD"), argsOnly = true)
|
||||
private boolean angelica$neverInflateSprite(boolean useAnisotropicFiltering) {
|
||||
private boolean angelica$disableVanillaAnisotropicPadding(boolean useAnisotropicFiltering) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user