package io.nanachiyo0721.shiroha.utils; import io.nanachiyo0721.shiroha.config.modules.optimizations.FrustumFilteringTrackerConfig; import net.minecraft.world.entity.player.Player; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Contract; import org.jspecify.annotations.NonNull; public class Culling { private static final float VERTICAL_FOV_RAD = (float) Math.toRadians(120); private static final float ASPECT = 1.0f; private static final float NEAR = 0.1f; private static final float FAR = 32.0f; private float fov; private float aspect; private float near; private float far; private final Player player; private float[] planes; public static Culling fromConfig(Player player) { return new Builder() .fov((float) Math.toRadians(FrustumFilteringTrackerConfig.camera_fov)) .near(NEAR) .aspect((float) FrustumFilteringTrackerConfig.aspect) .far(FAR) .build(player); } @Contract(value = "_ -> new", pure = true) public static @NonNull Culling newDefault(Player player) { return fromBuilder(new Builder(), player); } @Contract(value = "_, _ -> new", pure = true) public static @NonNull Culling fromBuilder(@NonNull Builder builder, Player player) { return new Culling(builder.fov, builder.aspect, builder.near, builder.far, player); } public static class Builder { private float fov; private float aspect; private float near; private float far; public Builder() { this.fov = VERTICAL_FOV_RAD; this.aspect = ASPECT; this.near = NEAR; this.far = FAR; } public Builder fov(float fov) { this.fov = fov; return this; } public Builder aspect(float aspect) { this.aspect = aspect; return this; } public Builder near(float near) { this.near = near; return this; } public Builder far(float far) { this.far = far; return this; } public Culling build(Player player) { return fromBuilder(this, player); } } public Culling(float fov, float aspect, float near, float far, Player player) { this.fov = fov; this.aspect = aspect; this.near = near; this.far = far; this.player = player; } public static float @NonNull [] buildFrustumPlanes( float eyeX, float eyeY, float eyeZ, float forwardX, float forwardY, float forwardZ, float upX, float upY, float upZ, float verticalFovRad, float aspect, float nearDist, float farDist) { float lenF = (float) Math.sqrt(forwardX * forwardX + forwardY * forwardY + forwardZ * forwardZ); float fx = forwardX / lenF; float fy = forwardY / lenF; float fz = forwardZ / lenF; float rx = fy * upZ - fz * upY; float ry = fz * upX - fx * upZ; float rz = fx * upY - fy * upX; float lenR = (float) Math.sqrt(rx * rx + ry * ry + rz * rz); if (lenR < 1.0e-4f) { float fallbackUpX = 0.0f, fallbackUpY = 0.0f, fallbackUpZ = 1.0f; if (Math.abs(fz) > 0.999f) { fallbackUpX = 1.0f; fallbackUpY = 0.0f; fallbackUpZ = 0.0f; } rx = fy * fallbackUpZ - fz * fallbackUpY; ry = fz * fallbackUpX - fx * fallbackUpZ; rz = fx * fallbackUpY - fy * fallbackUpX; lenR = (float) Math.sqrt(rx * rx + ry * ry + rz * rz); } rx /= lenR; ry /= lenR; rz /= lenR; float ux = ry * fz - rz * fy; float uy = rz * fx - rx * fz; float uz = rx * fy - ry * fx; float lenU = (float) Math.sqrt(ux * ux + uy * uy + uz * uz); ux /= lenU; uy /= lenU; uz /= lenU; float tanHalf = (float) Math.tan(verticalFovRad * 0.5); float halfVNear = tanHalf * nearDist; float halfHNear = halfVNear * aspect; float halfVFar = tanHalf * farDist; float halfHFar = halfVFar * aspect; float nearCX = eyeX + fx * nearDist; float nearCY = eyeY + fy * nearDist; float nearCZ = eyeZ + fz * nearDist; float farCX = eyeX + fx * farDist; float farCY = eyeY + fy * farDist; float farCZ = eyeZ + fz * farDist; float ntlX = nearCX + ux * halfVNear - rx * halfHNear; float ntlY = nearCY + uy * halfVNear - ry * halfHNear; float ntlZ = nearCZ + uz * halfVNear - rz * halfHNear; float ntrX = nearCX + ux * halfVNear + rx * halfHNear; float ntrY = nearCY + uy * halfVNear + ry * halfHNear; float ntrZ = nearCZ + uz * halfVNear + rz * halfHNear; float nblX = nearCX - ux * halfVNear - rx * halfHNear; float nblY = nearCY - uy * halfVNear - ry * halfHNear; float nblZ = nearCZ - uz * halfVNear - rz * halfHNear; float nbrX = nearCX - ux * halfVNear + rx * halfHNear; float nbrY = nearCY - uy * halfVNear + ry * halfHNear; float nbrZ = nearCZ - uz * halfVNear + rz * halfHNear; float ftlX = farCX + ux * halfVFar - rx * halfHFar; float ftlY = farCY + uy * halfVFar - ry * halfHFar; float ftlZ = farCZ + uz * halfVFar - rz * halfHFar; float ftrX = farCX + ux * halfVFar + rx * halfHFar; float ftrY = farCY + uy * halfVFar + ry * halfHFar; float ftrZ = farCZ + uz * halfVFar + rz * halfHFar; float fbrX = farCX - ux * halfVFar + rx * halfHFar; float fbrY = farCY - uy * halfVFar + ry * halfHFar; float fbrZ = farCZ - uz * halfVFar + rz * halfHFar; float midDist = (nearDist + farDist) * 0.5f; float refX = eyeX + fx * midDist; float refY = eyeY + fy * midDist; float refZ = eyeZ + fz * midDist; float[] planes = new float[24]; int idx = 0; idx = computePlane(planes, idx, eyeX, eyeY, eyeZ, ntlX, ntlY, ntlZ, nblX, nblY, nblZ, refX, refY, refZ); idx = computePlane(planes, idx, eyeX, eyeY, eyeZ, nbrX, nbrY, nbrZ, ntrX, ntrY, ntrZ, refX, refY, refZ); idx = computePlane(planes, idx, eyeX, eyeY, eyeZ, nblX, nblY, nblZ, nbrX, nbrY, nbrZ, refX, refY, refZ); idx = computePlane(planes, idx, eyeX, eyeY, eyeZ, ntrX, ntrY, ntrZ, ntlX, ntlY, ntlZ, refX, refY, refZ); idx = computePlane(planes, idx, ntlX, ntlY, ntlZ, ntrX, ntrY, ntrZ, nblX, nblY, nblZ, refX, refY, refZ); idx = computePlane(planes, idx, ftrX, ftrY, ftrZ, ftlX, ftlY, ftlZ, fbrX, fbrY, fbrZ, refX, refY, refZ); return planes; } private static int computePlane(float[] out, int start, float p0x, float p0y, float p0z, float p1x, float p1y, float p1z, float p2x, float p2y, float p2z, float refX, float refY, float refZ) { float e1x = p1x - p0x, e1y = p1y - p0y, e1z = p1z - p0z; float e2x = p2x - p0x, e2y = p2y - p0y, e2z = p2z - p0z; float nx = e1y * e2z - e1z * e2y; float ny = e1z * e2x - e1x * e2z; float nz = e1x * e2y - e1y * e2x; float d = -(nx * p0x + ny * p0y + nz * p0z); float val = nx * refX + ny * refY + nz * refZ + d; if (val < 0.0f) { nx = -nx; ny = -ny; nz = -nz; d = -d; } float len = (float) Math.sqrt(nx * nx + ny * ny + nz * nz); nx /= len; ny /= len; nz /= len; d = -(nx * p0x + ny * p0y + nz * p0z); out[start] = nx; out[start + 1] = ny; out[start + 2] = nz; out[start + 3] = d; return start + 4; } public void apply(@NonNull Builder builder) { this.fov = builder.fov; this.aspect = builder.aspect; this.near = builder.near; this.far = builder.far; } public void updateFrustum() { Vec3 eye = this.player.getEyePosition(); Vec3 look = this.player.getLookAngle(); this.planes = buildFrustumPlanes( (float) eye.x, (float) eye.y, (float) eye.z, (float) look.x, (float) look.y, (float) look.z, 0.0f, 1.0f, 0.0f, this.fov, this.aspect, this.near, this.far ); } public void far(float far) { this.far = far; } public boolean isVisibleFor(@NonNull AABB box) { final float[] planes = this.planes; for (int i = 0; i < 6; i++) { int base = i << 2; float nx = planes[base], ny = planes[base+1], nz = planes[base+2], d = planes[base+3]; float px = (nx >= 0) ? (float) box.maxX : (float) box.minX; float py = (ny >= 0) ? (float) box.maxY : (float) box.minY; float pz = (nz >= 0) ? (float) box.maxZ : (float) box.minZ; if (nx * px + ny * py + nz * pz + d < 0) { return false; } } return true; } }