mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-10-25 20:22:22 +00:00
Add Galaxy Space compat and fix culling issues (#2131)
This commit is contained in:
1 parent
a23cefba92
commit
888a4de0c9
11 files changed
+1205
-833
No files matched your search
@@ -0,0 +1,39 @@
|
||||
package com.gtnewhorizons.angelica.api.clouds;
|
||||
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public record CloudLayer(
|
||||
String id,
|
||||
ResourceLocation texture,
|
||||
float height,
|
||||
float cellWidth,
|
||||
float cellHeight,
|
||||
float coordinateWidth,
|
||||
double offsetX,
|
||||
double offsetZ,
|
||||
float red,
|
||||
float green,
|
||||
float blue,
|
||||
float alpha) {
|
||||
|
||||
public CloudLayer {
|
||||
if (id == null || id.isBlank()) throw new IllegalArgumentException("Cloud layer id must not be empty");
|
||||
if (texture == null) throw new IllegalArgumentException("Cloud layer texture must not be null");
|
||||
if (!Float.isFinite(height)) throw new IllegalArgumentException("Cloud layer height must be finite");
|
||||
if (!Float.isFinite(cellWidth) || cellWidth <= 0
|
||||
|| !Float.isFinite(cellHeight) || cellHeight <= 0
|
||||
|| !Float.isFinite(coordinateWidth) || coordinateWidth <= 0) {
|
||||
throw new IllegalArgumentException("Cloud layer dimensions must be finite and positive");
|
||||
}
|
||||
if (!Double.isFinite(offsetX) || !Double.isFinite(offsetZ)) {
|
||||
throw new IllegalArgumentException("Cloud layer offsets must be finite");
|
||||
}
|
||||
if (!Float.isFinite(red) || red < 0 || !Float.isFinite(green) || green < 0
|
||||
|| !Float.isFinite(blue) || blue < 0) {
|
||||
throw new IllegalArgumentException("Cloud layer colors must be finite and nonnegative");
|
||||
}
|
||||
if (!Float.isFinite(alpha) || alpha < 0 || alpha > 1) {
|
||||
throw new IllegalArgumentException("Cloud layer alpha must be between zero and one");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.api.clouds;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.client.multiplayer.WorldClient;
|
||||
|
||||
public interface CloudLayerProvider {
|
||||
|
||||
List<CloudLayer> getCloudLayers(WorldClient world, int cloudTicks, float partialTicks);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.gtnewhorizons.angelica.compat.galaxyspace;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.clouds.CloudLayer;
|
||||
import net.minecraft.client.multiplayer.WorldClient;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.client.IRenderHandler;
|
||||
|
||||
// Compat should live here, not porting all of this to Galaxy Space :trolley:
|
||||
public final class GalaxySpaceClouds {
|
||||
|
||||
private static final String VENUS = "galaxyspace.SolarSystem.planets.venus.dimension.WorldProviderVenus";
|
||||
private static final String BARNARDA_C = "galaxyspace.BarnardsSystem.planets.barnardaC.dimension.WorldProviderBarnardaC";
|
||||
private static final String TCETI_E = "galaxyspace.TCetiSystem.planets.tcetiE.dimension.WorldProviderTCetiE";
|
||||
|
||||
private static final String VENUS_RENDERER =
|
||||
"galaxyspace.SolarSystem.planets.venus.dimension.sky.CloudProviderVenus";
|
||||
private static final String BARNARDA_C_RENDERER =
|
||||
"galaxyspace.BarnardsSystem.planets.barnardaC.dimension.sky.CloudProviderBarnardaC";
|
||||
private static final String TCETI_E_RENDERER =
|
||||
"galaxyspace.TCetiSystem.planets.tcetiE.dimension.sky.CloudProviderTCetiE";
|
||||
|
||||
private static final ResourceLocation CLOUD_TEXTURE = new ResourceLocation("textures/environment/clouds.png");
|
||||
private static final double Z_PHASE = 0.33000001311302185;
|
||||
|
||||
private GalaxySpaceClouds() {}
|
||||
|
||||
public static boolean supports(WorldClient world, IRenderHandler renderer) {
|
||||
if (world == null || world.provider == null) return false;
|
||||
String expected = switch (world.provider.getClass().getName()) {
|
||||
case VENUS -> VENUS_RENDERER;
|
||||
case BARNARDA_C -> BARNARDA_C_RENDERER;
|
||||
case TCETI_E -> TCETI_E_RENDERER;
|
||||
default -> null;
|
||||
};
|
||||
return expected != null && (renderer == null || expected.equals(renderer.getClass().getName()));
|
||||
}
|
||||
|
||||
public static List<CloudLayer> getLayers(WorldClient world, int cloudTicks, float partialTicks) {
|
||||
String provider = world.provider.getClass().getName();
|
||||
if (VENUS.equals(provider)) {
|
||||
return fourLayers("galaxyspace:venus", world.provider.getCloudHeight(), 2.0f, cloudTicks, partialTicks);
|
||||
}
|
||||
if (BARNARDA_C.equals(provider)) {
|
||||
return fourLayers("galaxyspace:barnarda_c", world.provider.getCloudHeight(), 5.0f, cloudTicks, partialTicks);
|
||||
}
|
||||
if (TCETI_E.equals(provider)) {
|
||||
return List.of(new CloudLayer("galaxyspace:tceti_e/0", CLOUD_TEXTURE, world.provider.getCloudHeight(),
|
||||
12.0f, 4.0f, 12.0f, offsetX(cloudTicks, partialTicks, 0), Z_PHASE,
|
||||
230.0f / 1200.0f, 230.0f / 1200.0f, 230.0f / 1200.0f, 0.9f));
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
private static List<CloudLayer> fourLayers(String id, float baseHeight, float widthStep,
|
||||
int cloudTicks, float partialTicks) {
|
||||
List<CloudLayer> layers = new ArrayList<>(4);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float divisor = 1200.0f + 300.0f * i;
|
||||
layers.add(new CloudLayer(id + "/" + i, CLOUD_TEXTURE, baseHeight + 20.0f * i,
|
||||
12.0f + widthStep * i, 4.0f, 12.0f, offsetX(cloudTicks, partialTicks, i), Z_PHASE,
|
||||
234.0f / divisor, 147.0f / divisor, 9.0f / divisor, 0.9f));
|
||||
}
|
||||
return layers;
|
||||
}
|
||||
|
||||
private static double offsetX(int cloudTicks, float partialTicks, int layer) {
|
||||
final double layerFraction = layer / 5.0;
|
||||
return (cloudTicks * (1.0 - layerFraction) + layerFraction * partialTicks + layer * 250000.0) * 0.03 / 12.0;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.render;
|
||||
|
||||
import org.joml.Matrix4fc;
|
||||
|
||||
/**
|
||||
* The shape of the drawn disc, how it splits into wedges, and how far out each kind of geometry should
|
||||
* really be worth building.
|
||||
@@ -49,6 +51,29 @@ final class CloudDisc {
|
||||
return (displayHeight * 0.5) / Math.tan(Math.toRadians(fovDegrees) * 0.5);
|
||||
}
|
||||
|
||||
static double pixelsPerRadian(int displayWidth, int displayHeight, Matrix4fc projection) {
|
||||
if (displayWidth <= 0 || displayHeight <= 0 || projection == null) return 0.0;
|
||||
final float perspectiveW = projection.m23();
|
||||
if (!Float.isFinite(perspectiveW) || perspectiveW >= 0.0f || projection.m33() != 0.0f) return 0.0;
|
||||
|
||||
final double scale = -0.5 / perspectiveW;
|
||||
final double a = displayWidth * projection.m00() * scale;
|
||||
final double b = displayWidth * projection.m10() * scale;
|
||||
final double c = displayHeight * projection.m01() * scale;
|
||||
final double d = displayHeight * projection.m11() * scale;
|
||||
if (!Double.isFinite(a) || !Double.isFinite(b) || !Double.isFinite(c) || !Double.isFinite(d)) {
|
||||
return 0.0;
|
||||
}
|
||||
if (b == 0.0 && c == 0.0) return Math.max(Math.abs(a), Math.abs(d));
|
||||
final double column0 = a * a + c * c;
|
||||
final double column1 = b * b + d * d;
|
||||
final double cross = a * b + c * d;
|
||||
final double largest = 0.5 * (column0 + column1
|
||||
+ Math.hypot(column0 - column1, 2.0 * cross));
|
||||
final double pixels = Math.sqrt(largest);
|
||||
return Double.isFinite(pixels) ? pixels : 0.0;
|
||||
}
|
||||
|
||||
static int wallCutCells(double pixelsPerRadian, boolean platesInFront) {
|
||||
if (!platesInFront || pixelsPerRadian <= 0.0) return Integer.MAX_VALUE;
|
||||
|
||||
|
||||
@@ -73,6 +73,27 @@ final class CloudFaceMesh {
|
||||
interiorCount = 0;
|
||||
}
|
||||
|
||||
void delete() {
|
||||
if (vao != -1) GLStateManager.glDeleteVertexArrays(vao);
|
||||
if (vbo != -1) GLStateManager.glDeleteBuffers(vbo);
|
||||
if (ebo != -1) GLStateManager.glDeleteBuffers(ebo);
|
||||
vao = -1;
|
||||
vbo = -1;
|
||||
ebo = -1;
|
||||
vboCapacityBytes = 0;
|
||||
clear();
|
||||
stampedCount = 0;
|
||||
ringsPerBucket = 1;
|
||||
buildAnchorX = Integer.MIN_VALUE;
|
||||
buildAnchorZ = Integer.MIN_VALUE;
|
||||
orderAnchorX = Integer.MIN_VALUE;
|
||||
orderAnchorZ = Integer.MIN_VALUE;
|
||||
shape = null;
|
||||
uploadBuffer = null;
|
||||
Arrays.fill(wallStart, 0);
|
||||
Arrays.fill(plateStart, 0);
|
||||
}
|
||||
|
||||
void build(CloudShape shape, int anchorX, int anchorZ, int radiusCells, int radiusCellsSq, int wallCutCells) {
|
||||
stampedCount = 0;
|
||||
this.shape = shape;
|
||||
|
||||
@@ -0,0 +1,710 @@
|
||||
/*
|
||||
* Backported from Minecraft Forge 1.12 under the LGPL 2.1
|
||||
*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package com.gtnewhorizons.angelica.render;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.clouds.CloudLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.states.FogState;
|
||||
import net.irisshaders.iris.api.v0.IrisApi;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.EntityRenderer;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.GlProgram;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fStack;
|
||||
import org.joml.Vector3d;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.CELLS_PER_CHUNK;
|
||||
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.WEDGES_PER_RADIAN;
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.textureOffset;
|
||||
import static com.gtnewhorizons.angelica.render.CloudDisc.withinMargin;
|
||||
|
||||
/**
|
||||
* Draws a cloud layer.
|
||||
*
|
||||
* <h2>Per frame, in {@link #render}</h2>
|
||||
* <ol>
|
||||
* <li>Reduce the texture to cells, plate rectangles and wall runs into {@link CloudShape}.
|
||||
* <li>Convert the camera position to cell space: the anchor cell, the fraction across it, and how far the
|
||||
* cloud base sits above the camera.
|
||||
* <li>Work out which faces the view needs: the underside, the top surface, or both when the camera is
|
||||
* inside the deck.
|
||||
* <li>Get the two detail distances from {@link CloudDisc}: where walls stop being built, and where
|
||||
* {@link CloudPlateCover} switches to coarse plate rectangles.
|
||||
* <li>Choose the mesher: {@link CloudFaceMesh} inside the deck, {@link CloudVertexMesh} elsewhere and
|
||||
* always for shader packs.
|
||||
* <li>Pick the matching program depending on whether every solid texel is plain white.
|
||||
* <li>Rebuild when the cache is stale. {@link CloudVertexMesh} builds on a worker thread.
|
||||
* <li>Reorder near-to-far on every cell crossed, and keep the cell the camera sits in up to date.
|
||||
* <li>Upload matrices, fog and the texture scroll into {@link CloudUniforms}, then
|
||||
* {@link #drawVisibleWedges}.
|
||||
* </ol>
|
||||
*/
|
||||
final class CloudLayerRenderer {
|
||||
|
||||
static final int MODE_FAST = 1;
|
||||
static final int MODE_FANCY = 2;
|
||||
private static final int UPLOAD_SLICE_BYTES = 256 * 1024;
|
||||
|
||||
private static final Tracy.ZoneId Z_RENDER = Tracy.zoneId("cloudRender", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_SUBMIT = Tracy.zoneId("cloudSubmit", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_DRAW = Tracy.zoneId("cloudDraw", Tracy.COLOR_CLIENT);
|
||||
private static final Tracy.ZoneId Z_ASYNC_BUILD = Tracy.zoneId("cloudAsyncBuild", Tracy.COLOR_CLIENT);
|
||||
private static final Executor BUILD_THREAD = Executors.newSingleThreadExecutor(task -> {
|
||||
final Thread thread = new Thread(task, "Angelica Cloud Builder");
|
||||
thread.setDaemon(true);
|
||||
thread.setPriority(Thread.NORM_PRIORITY - 1);
|
||||
return thread;
|
||||
});
|
||||
private final Minecraft mc = Minecraft.getMinecraft();
|
||||
private final CloudRenderResources resources;
|
||||
final CloudRenderResources.Texture texture;
|
||||
CloudLayer layer;
|
||||
private final CloudFaceMesh faceMesh = new CloudFaceMesh();
|
||||
private CloudVertexMesh vertexMesh = new CloudVertexMesh();
|
||||
private CloudVertexMesh spareMesh = new CloudVertexMesh();
|
||||
private final CloudVertexMesh interiorMesh = new CloudVertexMesh();
|
||||
private int interiorAnchorX = Integer.MIN_VALUE, interiorAnchorZ = Integer.MIN_VALUE;
|
||||
private int interiorShapeGeneration = Integer.MIN_VALUE;
|
||||
private float interiorCellHeight = Float.NaN;
|
||||
private boolean interiorShadersActive, interiorActive;
|
||||
private float interiorDriftX, interiorDriftZ;
|
||||
private CompletableFuture<Void> buildInFlight;
|
||||
private CompletableFuture<Void> reorderInFlight;
|
||||
private CloudVertexMesh reorderMesh;
|
||||
private BuildParams buildParams;
|
||||
private BuildParams uploadParams;
|
||||
private final Matrix4f mvpScratch = new Matrix4f();
|
||||
private final Matrix4f mvpInteriorScratch = new Matrix4f();
|
||||
private final Matrix4f modelViewScratch = new Matrix4f();
|
||||
private CloudShape shape;
|
||||
private int shapeGeneration;
|
||||
private boolean faceMeshActive;
|
||||
private int anchorCellX = Integer.MIN_VALUE, anchorCellZ = Integer.MIN_VALUE;
|
||||
private int cachedShapeGeneration = Integer.MIN_VALUE;
|
||||
private boolean cachedEmitUnderside, cachedEmitTopSurface;
|
||||
private int cachedRenderRadiusChunks = -1;
|
||||
private float cachedCellHeightBlocks = Float.NaN;
|
||||
private boolean cachedShadersActive;
|
||||
private int cachedWallCutCells = -1;
|
||||
private int cachedPlateLodCells = -1;
|
||||
private boolean geometryBuilt;
|
||||
private int wallCutCells = Integer.MAX_VALUE;
|
||||
private int plateLodCells = Integer.MAX_VALUE;
|
||||
private CloudView cloudView;
|
||||
private int cloudMode = -1, renderDistance = -1, scaleMult = -1;
|
||||
private float alpha;
|
||||
private double driftCells;
|
||||
private double cullingHeightCells;
|
||||
private int arcFrom, arcTo;
|
||||
private int drawCalls;
|
||||
private GlProgram<CloudUniforms> program;
|
||||
|
||||
CloudLayerRenderer(CloudRenderResources resources, CloudRenderResources.Texture texture) {
|
||||
this.resources = resources;
|
||||
this.texture = texture;
|
||||
}
|
||||
|
||||
void configure(CloudLayer layer, int mode, int distance, int scale) {
|
||||
this.layer = layer;
|
||||
if (cloudMode != mode || renderDistance != distance || scaleMult != scale) {
|
||||
invalidateGeometry();
|
||||
}
|
||||
cloudMode = mode;
|
||||
renderDistance = distance;
|
||||
scaleMult = scale;
|
||||
alpha = layer.alpha();
|
||||
}
|
||||
|
||||
private void invalidateGeometry() {
|
||||
anchorCellX = Integer.MIN_VALUE;
|
||||
anchorCellZ = Integer.MIN_VALUE;
|
||||
cachedRenderRadiusChunks = -1;
|
||||
cachedShapeGeneration = Integer.MIN_VALUE;
|
||||
cachedCellHeightBlocks = Float.NaN;
|
||||
cancelAsyncBuild();
|
||||
vertexMesh.clear();
|
||||
spareMesh.clear();
|
||||
interiorMesh.clear();
|
||||
interiorAnchorX = Integer.MIN_VALUE;
|
||||
interiorAnchorZ = Integer.MIN_VALUE;
|
||||
interiorActive = false;
|
||||
faceMesh.clear();
|
||||
geometryBuilt = false;
|
||||
}
|
||||
|
||||
private boolean untextured() {
|
||||
return cloudMode == MODE_FANCY && shape != null && shape.opaqueTexelsAllWhite;
|
||||
}
|
||||
|
||||
private void uploadFogUniforms(CloudUniforms uniforms, float fogStart, float fogEnd) {
|
||||
final boolean fogEnabled = GLStateManager.getFogMode().isEnabled();
|
||||
uniforms.setFogEnabled(fogEnabled);
|
||||
if (!fogEnabled) return;
|
||||
final FogState fog = GLStateManager.getFogState();
|
||||
final Vector3d color = fog.getFogColor();
|
||||
uniforms.setFogColor((float) color.x, (float) color.y, (float) color.z);
|
||||
switch (fog.getFogMode()) {
|
||||
case GL11.GL_LINEAR -> {
|
||||
final float range = fogEnd - fogStart;
|
||||
uniforms.setFogParams(range != 0.0f ? -1.0f / range : 0.0f, range != 0.0f ? fogEnd / range : 1.0f, 0.0f, 0.0f);
|
||||
}
|
||||
case GL11.GL_EXP -> uniforms.setFogParams(0.0f, 0.0f, fog.getDensity(), 1.0f);
|
||||
default -> uniforms.setFogParams(0.0f, 0.0f, fog.getDensity(), 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
boolean render(float partialTicks, CloudView cloudView) {
|
||||
this.cloudView = cloudView;
|
||||
if (!Tracy.ENABLED) return renderInner(partialTicks);
|
||||
Tracy.beginZone(Z_RENDER);
|
||||
try {
|
||||
return renderInner(partialTicks);
|
||||
} finally {
|
||||
Tracy.endZone();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean renderInner(float partialTicks) {
|
||||
texture.bind();
|
||||
shape = texture.shape;
|
||||
shapeGeneration = texture.generation;
|
||||
if (shape == null) return false;
|
||||
|
||||
final Entity viewEntity = mc.renderViewEntity;
|
||||
final float cellWidthBlocks = layer.cellWidth() * scaleMult;
|
||||
final float cellHeightBlocks = layer.cellHeight() * scaleMult;
|
||||
final float coordinateWidth = layer.coordinateWidth() * scaleMult;
|
||||
|
||||
final float cameraY = (float) (viewEntity.lastTickPosY + (viewEntity.posY - viewEntity.lastTickPosY) * partialTicks);
|
||||
final double cameraCellX = CloudDisc.cellCoordinate(viewEntity.prevPosX + (viewEntity.posX - viewEntity.prevPosX) * partialTicks, coordinateWidth, layer.offsetX());
|
||||
final double cameraCellZ = CloudDisc.cellCoordinate(viewEntity.prevPosZ + (viewEntity.posZ - viewEntity.prevPosZ) * partialTicks, coordinateWidth, layer.offsetZ());
|
||||
|
||||
final float cloudBaseRelativeY = layer.height() - cameraY + 0.33F;
|
||||
final float cellFractionX = (float) (cameraCellX - MathHelper.floor_double(cameraCellX));
|
||||
final float cellFractionZ = (float) (cameraCellZ - MathHelper.floor_double(cameraCellZ));
|
||||
|
||||
final int anchorX = MathHelper.floor_double(cameraCellX);
|
||||
final int anchorZ = MathHelper.floor_double(cameraCellZ);
|
||||
|
||||
final boolean emitUnderside = cloudBaseRelativeY + cellHeightBlocks >= 0.0F;
|
||||
final boolean emitTopSurface = cloudBaseRelativeY <= 0.0F;
|
||||
final boolean insideDeck = emitUnderside && emitTopSurface;
|
||||
|
||||
final int renderRadiusChunks = (int) Math.ceil(renderDistance * 64.0f / (CELLS_PER_CHUNK * cellWidthBlocks));
|
||||
|
||||
final boolean shadersActive = IrisApi.getInstance().isShaderPackInUse();
|
||||
final boolean backFaceCulling = cullBackFaces();
|
||||
|
||||
final double pixelsPerRadian = cloudView.pixelsPerRadian;
|
||||
wallCutCells = CloudDisc.wallCutCells(pixelsPerRadian, !(backFaceCulling && insideDeck));
|
||||
plateLodCells = CloudDisc.plateLodCells(pixelsPerRadian, cloudBaseRelativeY, cellWidthBlocks,
|
||||
shape.coarsePlateRects != null && shape.opaqueTexelsAllWhite);
|
||||
|
||||
final int marginChunks = (MARGIN_CELLS + CELLS_PER_CHUNK - 1) / CELLS_PER_CHUNK;
|
||||
final int radiusChunks = renderRadiusChunks + marginChunks;
|
||||
final int radiusCells = radiusChunks * CELLS_PER_CHUNK;
|
||||
faceMeshActive = cloudMode == MODE_FANCY && !shadersActive && insideDeck && radiusCells <= CloudFaceMesh.CELL_LIMIT;
|
||||
|
||||
if (!shadersActive) program = resources.program(faceMeshActive, untextured());
|
||||
|
||||
final boolean anchorInRange = geometryBuilt
|
||||
&& withinMargin(anchorX - anchorCellX, anchorZ - anchorCellZ);
|
||||
final boolean settingsUnchanged = geometryBuilt
|
||||
&& cachedShapeGeneration == shapeGeneration
|
||||
&& cachedEmitUnderside == emitUnderside
|
||||
&& cachedEmitTopSurface == emitTopSurface
|
||||
&& cachedRenderRadiusChunks == renderRadiusChunks
|
||||
&& cachedCellHeightBlocks == cellHeightBlocks
|
||||
&& cachedShadersActive == shadersActive
|
||||
&& cachedWallCutCells == wallCutCells
|
||||
&& (faceMeshActive || cachedPlateLodCells == plateLodCells);
|
||||
final boolean geomCacheValid = anchorInRange && settingsUnchanged;
|
||||
|
||||
final boolean canBuildAsync = settingsUnchanged && !faceMeshActive && vertexMesh.built() && CloudDisc.withinDisc((long) anchorX - anchorCellX, (long) anchorZ - anchorCellZ, radiusCells);
|
||||
|
||||
final boolean builderBusy = buildInFlight != null || uploadParams != null;
|
||||
if (!geomCacheValid && !(canBuildAsync && builderBusy)) {
|
||||
final BuildParams params = captureBuildParams(anchorX, anchorZ, radiusCells, renderRadiusChunks,
|
||||
cellHeightBlocks, emitUnderside, emitTopSurface, shadersActive, backFaceCulling);
|
||||
if (canBuildAsync) {
|
||||
startAsyncBuild(params);
|
||||
} else {
|
||||
cancelAsyncBuild();
|
||||
if (faceMeshActive) {
|
||||
faceMesh.build(shape, anchorX, anchorZ, radiusCells, radiusCells * radiusCells, wallCutCells);
|
||||
} else {
|
||||
buildVertexMesh(vertexMesh, params);
|
||||
vertexMesh.uploadBuilt();
|
||||
}
|
||||
markGeometryCached(params);
|
||||
}
|
||||
}
|
||||
|
||||
collectFinishedBuild();
|
||||
|
||||
final boolean wantInterior = insideDeck && cloudMode == MODE_FANCY && !faceMeshActive;
|
||||
if (wantInterior && (interiorAnchorX != anchorX || interiorAnchorZ != anchorZ
|
||||
|| interiorShapeGeneration != shapeGeneration || interiorCellHeight != cellHeightBlocks
|
||||
|| interiorShadersActive != shadersActive)) {
|
||||
interiorMesh.buildInterior(shape, anchorX, anchorZ, cellHeightBlocks, textureOffset(anchorX, shape.width), textureOffset(anchorZ, shape.height), untextured() && !shadersActive, shadersActive);
|
||||
interiorMesh.uploadBuilt();
|
||||
interiorAnchorX = anchorX;
|
||||
interiorAnchorZ = anchorZ;
|
||||
interiorShapeGeneration = shapeGeneration;
|
||||
interiorCellHeight = cellHeightBlocks;
|
||||
interiorShadersActive = shadersActive;
|
||||
}
|
||||
interiorActive = wantInterior && interiorMesh.built();
|
||||
|
||||
if (faceMeshActive) {
|
||||
if (faceMesh.orderStale(anchorX, anchorZ)) faceMesh.reorder(anchorX, anchorZ);
|
||||
if (faceMesh.uploadedCount() == 0 && faceMesh.interiorCount() == 0) return true;
|
||||
} else if (!vertexMesh.built()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!faceMeshActive && !shadersActive && cloudMode == MODE_FANCY) {
|
||||
if (reorderInFlight != null && reorderInFlight.isDone()) {
|
||||
final boolean usable = !reorderInFlight.isCompletedExceptionally() && reorderMesh == vertexMesh;
|
||||
reorderInFlight = null;
|
||||
reorderMesh = null;
|
||||
if (usable) vertexMesh.reorderUpload();
|
||||
else vertexMesh.discardPendingReorder();
|
||||
}
|
||||
if (reorderInFlight == null && vertexMesh.orderStale(anchorX, anchorZ)) {
|
||||
final CloudVertexMesh target = vertexMesh;
|
||||
reorderMesh = target;
|
||||
reorderInFlight = CompletableFuture.runAsync(() -> target.reorder(anchorX, anchorZ), BUILD_THREAD);
|
||||
}
|
||||
}
|
||||
|
||||
if (Tracy.FINE_ZONES) Tracy.beginZone(Z_SUBMIT);
|
||||
float r = layer.red();
|
||||
float g = layer.green();
|
||||
float b = layer.blue();
|
||||
if (mc.gameSettings.anaglyph) {
|
||||
final float tempR = (r * 30.0F + g * 59.0F + b * 11.0F) / 100.0F;
|
||||
final float tempG = (r * 30.0F + g * 70.0F) / 100.0F;
|
||||
final float tempB = (r * 30.0F + b * 70.0F) / 100.0F;
|
||||
r = tempR;
|
||||
g = tempG;
|
||||
b = tempB;
|
||||
}
|
||||
|
||||
final float origFogStart = GLStateManager.getFogState().getStart();
|
||||
final float origFogEnd = GLStateManager.getFogState().getEnd();
|
||||
final float cloudExtentBlocks = renderRadiusChunks * CELLS_PER_CHUNK * cellWidthBlocks;
|
||||
final boolean stretchFog = GLStateManager.getFogMode().isEnabled()
|
||||
&& GLStateManager.getFogState().getFogMode() == GL11.GL_LINEAR
|
||||
&& origFogEnd > 0.0f
|
||||
&& origFogEnd < cloudExtentBlocks;
|
||||
final float fogStart = stretchFog ? origFogStart * (cloudExtentBlocks / origFogEnd) : origFogStart;
|
||||
final float fogEnd = stretchFog ? cloudExtentBlocks : origFogEnd;
|
||||
|
||||
final boolean pushFog = stretchFog && shadersActive;
|
||||
if (pushFog) {
|
||||
GLStateManager.glFogf(GL11.GL_FOG_START, fogStart);
|
||||
GLStateManager.glFogf(GL11.GL_FOG_END, fogEnd);
|
||||
}
|
||||
|
||||
final float anchorDriftX = (float) (anchorX - anchorCellX);
|
||||
final float anchorDriftZ = (float) (anchorZ - anchorCellZ);
|
||||
// Face wedges are re-bucketed around the current cell; vertex wedges keep their build anchor.
|
||||
final double cameraDriftX = (faceMeshActive ? 0 : anchorDriftX) + cellFractionX + cloudView.offsetX / cellWidthBlocks;
|
||||
final double cameraDriftZ = (faceMeshActive ? 0 : anchorDriftZ) + cellFractionZ + cloudView.offsetZ / cellWidthBlocks;
|
||||
driftCells = Math.hypot(cameraDriftX, cameraDriftZ);
|
||||
final double baseFromEye = cloudBaseRelativeY - cloudView.offsetY;
|
||||
cullingHeightCells = Math.max(Math.abs(baseFromEye), Math.abs(baseFromEye + cellHeightBlocks)) / cellWidthBlocks;
|
||||
|
||||
interiorDriftX = anchorDriftX;
|
||||
interiorDriftZ = anchorDriftZ;
|
||||
|
||||
if (!shadersActive) {
|
||||
program.bind();
|
||||
final CloudUniforms uniforms = program.getInterface();
|
||||
final Matrix4fStack modelView = GLStateManager.getModelViewMatrix();
|
||||
modelView.pushMatrix();
|
||||
modelView.scale(cellWidthBlocks, 1.0f, cellWidthBlocks);
|
||||
if (interiorActive) {
|
||||
modelView.pushMatrix();
|
||||
modelView.translate(-cellFractionX, cloudBaseRelativeY, -cellFractionZ);
|
||||
GLStateManager.getProjectionMatrix().mul(modelView, mvpInteriorScratch);
|
||||
modelView.popMatrix();
|
||||
}
|
||||
modelView.translate(-(cellFractionX + anchorDriftX), cloudBaseRelativeY, -(cellFractionZ + anchorDriftZ));
|
||||
modelViewScratch.set(modelView);
|
||||
GLStateManager.getProjectionMatrix().mul(modelView, mvpScratch);
|
||||
modelView.popMatrix();
|
||||
uniforms.mvp.set(mvpScratch);
|
||||
uniforms.modelView.set(modelViewScratch);
|
||||
if (faceMeshActive) {
|
||||
uniforms.setCellHeight(cellHeightBlocks);
|
||||
uniforms.setScroll(textureOffset(faceMesh.buildAnchorX(), shape.width), textureOffset(faceMesh.buildAnchorZ(), shape.height), 1.0f / shape.width, 1.0f / shape.height);
|
||||
}
|
||||
uploadFogUniforms(uniforms, fogStart, fogEnd);
|
||||
drawClouds(r, g, b, false);
|
||||
program.unbind();
|
||||
} else {
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glScalef(cellWidthBlocks, 1.0f, cellWidthBlocks);
|
||||
GLStateManager.glTranslatef(-(cellFractionX + anchorDriftX), cloudBaseRelativeY, -(cellFractionZ + anchorDriftZ));
|
||||
drawClouds(r, g, b, true);
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
|
||||
if (pushFog) {
|
||||
GLStateManager.glFogf(GL11.GL_FOG_START, origFogStart);
|
||||
GLStateManager.glFogf(GL11.GL_FOG_END, origFogEnd);
|
||||
}
|
||||
if (Tracy.FINE_ZONES) Tracy.endZone();
|
||||
return true;
|
||||
}
|
||||
|
||||
private record BuildParams(CloudShape shape, boolean fancy, int anchorX, int anchorZ, int radiusCells,
|
||||
float cellHeightBlocks, boolean emitUnderside, boolean emitTopSurface, boolean emitPlates,
|
||||
boolean shadersActive, int wallCutCells, int plateLodCells, boolean untextured,
|
||||
int shapeGeneration, int renderRadiusChunks) {}
|
||||
|
||||
private BuildParams captureBuildParams(int anchorX, int anchorZ, int radiusCells, int renderRadiusChunks,
|
||||
float cellHeightBlocks, boolean emitUnderside, boolean emitTopSurface,
|
||||
boolean shadersActive, boolean backFaceCulling) {
|
||||
return new BuildParams(shape, cloudMode == MODE_FANCY, anchorX, anchorZ, radiusCells, cellHeightBlocks,
|
||||
emitUnderside, emitTopSurface, !(backFaceCulling && emitUnderside && emitTopSurface), shadersActive,
|
||||
wallCutCells, plateLodCells, untextured() && !shadersActive, shapeGeneration, renderRadiusChunks);
|
||||
}
|
||||
|
||||
private static void buildVertexMesh(CloudVertexMesh mesh, BuildParams p) {
|
||||
final float scrollX = textureOffset(p.anchorX(), p.shape().width);
|
||||
final float scrollZ = textureOffset(p.anchorZ(), p.shape().height);
|
||||
|
||||
if (!p.fancy()) {
|
||||
mesh.buildFast(p.shape(), p.radiusCells(), scrollX, scrollZ, p.shadersActive());
|
||||
return;
|
||||
}
|
||||
|
||||
mesh.build(p.shape(), p.anchorX(), p.anchorZ(), p.radiusCells(), p.radiusCells() * p.radiusCells(),
|
||||
p.cellHeightBlocks(), scrollX, scrollZ, p.emitUnderside(), p.emitTopSurface(), p.emitPlates(),
|
||||
p.wallCutCells(), p.plateLodCells(), p.untextured(), p.shadersActive());
|
||||
}
|
||||
|
||||
private void markGeometryCached(BuildParams p) {
|
||||
anchorCellX = p.anchorX();
|
||||
anchorCellZ = p.anchorZ();
|
||||
cachedShapeGeneration = p.shapeGeneration();
|
||||
cachedEmitUnderside = p.emitUnderside();
|
||||
cachedEmitTopSurface = p.emitTopSurface();
|
||||
cachedRenderRadiusChunks = p.renderRadiusChunks();
|
||||
cachedCellHeightBlocks = p.cellHeightBlocks();
|
||||
cachedShadersActive = p.shadersActive();
|
||||
cachedWallCutCells = p.wallCutCells();
|
||||
cachedPlateLodCells = p.plateLodCells();
|
||||
geometryBuilt = true;
|
||||
}
|
||||
|
||||
private void startAsyncBuild(BuildParams p) {
|
||||
if (buildInFlight != null || uploadParams != null) return;
|
||||
final CloudVertexMesh target = spareMesh;
|
||||
buildParams = p;
|
||||
buildInFlight = CompletableFuture.runAsync(() -> {
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_ASYNC_BUILD);
|
||||
try {
|
||||
buildVertexMesh(target, p);
|
||||
} finally {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
}
|
||||
}, BUILD_THREAD);
|
||||
}
|
||||
|
||||
private void collectFinishedBuild() {
|
||||
if (buildInFlight != null) {
|
||||
if (!buildInFlight.isDone()) return;
|
||||
|
||||
final boolean usable = !buildInFlight.isCompletedExceptionally()
|
||||
&& buildParams.shapeGeneration() == shapeGeneration;
|
||||
buildInFlight = null;
|
||||
if (!usable) {
|
||||
spareMesh.discardPendingUpload();
|
||||
buildParams = null;
|
||||
return;
|
||||
}
|
||||
uploadParams = buildParams;
|
||||
buildParams = null;
|
||||
if (!spareMesh.beginUpload()) {
|
||||
swapMeshes();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (uploadParams == null) return;
|
||||
if (!spareMesh.uploadSlice(UPLOAD_SLICE_BYTES)) return;
|
||||
swapMeshes();
|
||||
}
|
||||
|
||||
private void swapMeshes() {
|
||||
discardReorder();
|
||||
final CloudVertexMesh finished = spareMesh;
|
||||
spareMesh = vertexMesh;
|
||||
vertexMesh = finished;
|
||||
markGeometryCached(uploadParams);
|
||||
uploadParams = null;
|
||||
}
|
||||
|
||||
private void discardReorder() {
|
||||
if (reorderInFlight != null) {
|
||||
reorderInFlight.join();
|
||||
reorderInFlight = null;
|
||||
}
|
||||
if (reorderMesh != null) {
|
||||
reorderMesh.discardPendingReorder();
|
||||
reorderMesh = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelAsyncBuild() {
|
||||
discardReorder();
|
||||
if (buildInFlight != null) {
|
||||
buildInFlight.join();
|
||||
buildInFlight = null;
|
||||
}
|
||||
buildParams = null;
|
||||
uploadParams = null;
|
||||
spareMesh.discardPendingUpload();
|
||||
}
|
||||
|
||||
private void drawClouds(float r, float g, float b, boolean shadersActive) {
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_DRAW);
|
||||
try {
|
||||
drawCloudsInner(r, g, b, shadersActive);
|
||||
} finally {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
}
|
||||
}
|
||||
|
||||
private void drawCloudsInner(float r, float g, float b, boolean shadersActive) {
|
||||
drawCalls = 0;
|
||||
|
||||
if (faceMeshActive) {
|
||||
drawFaces(r, g, b);
|
||||
} else {
|
||||
drawVertices(r, g, b, shadersActive);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawFaces(float r, float g, float b) {
|
||||
GLStateManager.enableCull();
|
||||
applyAnaglyphColorMask();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glDepthMask(true);
|
||||
|
||||
program.getInterface().setColorMult(r, g, b, alpha);
|
||||
|
||||
faceMesh.bind();
|
||||
final int interiorFaces = faceMesh.interiorCount();
|
||||
if (interiorFaces > 0) {
|
||||
faceMesh.drawInterior();
|
||||
drawCalls++;
|
||||
}
|
||||
drawVisibleWedges(faceMesh.plateStarts(), faceMesh.wallStarts(), faceMesh.uploadedCount(), true);
|
||||
faceMesh.unbind();
|
||||
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
|
||||
private void drawVertices(float r, float g, float b, boolean depthPrepass) {
|
||||
vertexMesh.bind();
|
||||
final int totalQuads = vertexMesh.quadCount();
|
||||
|
||||
if (cullBackFaces()) {
|
||||
GLStateManager.enableCull();
|
||||
} else {
|
||||
GLStateManager.disableCull();
|
||||
}
|
||||
|
||||
if (depthPrepass) {
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glColorMask(false, false, false, false);
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, alpha);
|
||||
vertexMesh.drawAll();
|
||||
drawCalls++;
|
||||
if (interiorActive) {
|
||||
vertexMesh.unbind();
|
||||
pushInteriorTransform(true);
|
||||
interiorMesh.bind();
|
||||
interiorMesh.drawAll();
|
||||
interiorMesh.unbind();
|
||||
popInteriorTransform(true);
|
||||
vertexMesh.bind();
|
||||
drawCalls++;
|
||||
}
|
||||
}
|
||||
|
||||
applyAnaglyphColorMask();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glDepthMask(!depthPrepass);
|
||||
|
||||
if (depthPrepass) {
|
||||
for (int face = 0; face < 4; face++) {
|
||||
if (vertexMesh.faceEmpty(face)) continue;
|
||||
final float shade = CloudVertexMesh.FACE_SHADE[face];
|
||||
GLStateManager.glColor4f(shade * r, shade * g, shade * b, alpha);
|
||||
vertexMesh.drawFace(face);
|
||||
drawCalls++;
|
||||
}
|
||||
} else {
|
||||
program.getInterface().setColorMult(r, g, b, alpha);
|
||||
if (cloudMode == MODE_FANCY && vertexMesh.orderPublished()) {
|
||||
drawVisibleWedges(vertexMesh.plateStarts(), vertexMesh.wallStarts(), totalQuads, false);
|
||||
} else {
|
||||
drawRange(0, totalQuads, false);
|
||||
}
|
||||
}
|
||||
|
||||
vertexMesh.unbind();
|
||||
|
||||
if (interiorActive) {
|
||||
pushInteriorTransform(depthPrepass);
|
||||
interiorMesh.bind();
|
||||
if (depthPrepass) {
|
||||
for (int face = 0; face < 4; face++) {
|
||||
if (interiorMesh.faceEmpty(face)) continue;
|
||||
final float shade = CloudVertexMesh.FACE_SHADE[face];
|
||||
GLStateManager.glColor4f(shade * r, shade * g, shade * b, alpha);
|
||||
interiorMesh.drawFace(face);
|
||||
drawCalls++;
|
||||
}
|
||||
} else {
|
||||
drawCalls++;
|
||||
interiorMesh.drawRange(0, interiorMesh.quadCount());
|
||||
}
|
||||
interiorMesh.unbind();
|
||||
popInteriorTransform(depthPrepass);
|
||||
}
|
||||
|
||||
GLStateManager.enableCull();
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
|
||||
private void pushInteriorTransform(boolean shadersActive) {
|
||||
if (shadersActive) {
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glTranslatef(interiorDriftX, 0.0f, interiorDriftZ);
|
||||
} else {
|
||||
program.getInterface().mvp.set(mvpInteriorScratch);
|
||||
}
|
||||
}
|
||||
|
||||
private void popInteriorTransform(boolean shadersActive) {
|
||||
if (shadersActive) {
|
||||
GLStateManager.glPopMatrix();
|
||||
} else {
|
||||
program.getInterface().mvp.set(mvpScratch);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean cullBackFaces() {
|
||||
return cloudMode == MODE_FANCY;
|
||||
}
|
||||
|
||||
private void drawVisibleWedges(int[] plateStarts, int[] wallStarts, int total, boolean fromFaceMesh) {
|
||||
if (!computeVisibleArc()) {
|
||||
drawRange(0, total, fromFaceMesh);
|
||||
return;
|
||||
}
|
||||
|
||||
drawRange(0, plateStarts[0], fromFaceMesh);
|
||||
|
||||
if (arcFrom <= arcTo) {
|
||||
drawRange(plateStarts[arcFrom], plateStarts[arcTo + 1], fromFaceMesh);
|
||||
drawRange(plateStarts[WEDGE_COUNT], wallStarts[0], fromFaceMesh);
|
||||
drawRange(wallStarts[arcFrom], wallStarts[arcTo + 1], fromFaceMesh);
|
||||
} else {
|
||||
drawRange(plateStarts[arcFrom], plateStarts[WEDGE_COUNT], fromFaceMesh);
|
||||
drawRange(plateStarts[0], plateStarts[arcTo + 1], fromFaceMesh);
|
||||
drawRange(plateStarts[WEDGE_COUNT], wallStarts[0], fromFaceMesh);
|
||||
drawRange(wallStarts[arcFrom], wallStarts[WEDGE_COUNT], fromFaceMesh);
|
||||
drawRange(wallStarts[0], wallStarts[arcTo + 1], fromFaceMesh);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void drawRange(int start, int end, boolean fromFaceMesh) {
|
||||
final int count = end - start;
|
||||
if (count <= 0) return;
|
||||
drawCalls++;
|
||||
if (fromFaceMesh) {
|
||||
faceMesh.drawRange(start, end);
|
||||
} else {
|
||||
vertexMesh.drawRange(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean computeVisibleArc() {
|
||||
final double halfSpan = cloudView.halfSpan(cullingHeightCells, driftCells) * WEDGES_PER_RADIAN + 0.25;
|
||||
if (halfSpan * 2.0 + 1.0 >= WEDGE_COUNT) return false;
|
||||
|
||||
final double centre = (cloudView.facing + Math.PI) * WEDGES_PER_RADIAN;
|
||||
arcFrom = Math.floorMod((int) Math.floor(centre - halfSpan), WEDGE_COUNT);
|
||||
arcTo = Math.floorMod((int) Math.ceil(centre + halfSpan), WEDGE_COUNT);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void applyAnaglyphColorMask() {
|
||||
if (!mc.gameSettings.anaglyph) {
|
||||
GLStateManager.glColorMask(true, true, true, true);
|
||||
return;
|
||||
}
|
||||
switch (EntityRenderer.anaglyphField) {
|
||||
case 0 -> GLStateManager.glColorMask(false, true, true, true);
|
||||
case 1 -> GLStateManager.glColorMask(true, false, false, true);
|
||||
default -> GLStateManager.glColorMask(true, true, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
void delete() {
|
||||
invalidateGeometry();
|
||||
vertexMesh.deleteVao();
|
||||
vertexMesh.deleteEbo();
|
||||
spareMesh.deleteVao();
|
||||
spareMesh.deleteEbo();
|
||||
interiorMesh.deleteVao();
|
||||
interiorMesh.deleteEbo();
|
||||
faceMesh.delete();
|
||||
program = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.gtnewhorizons.angelica.render;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.GlProgram;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.GlShader;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.ShaderConstants;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.ShaderType;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
final class CloudRenderResources {
|
||||
|
||||
private final Map<ResourceLocation, Texture> textures = new HashMap<>();
|
||||
private final Map<Integer, GlProgram<CloudUniforms>> programs = new HashMap<>(4);
|
||||
|
||||
Texture texture(ResourceLocation location) {
|
||||
return textures.computeIfAbsent(location, Texture::new);
|
||||
}
|
||||
|
||||
GlProgram<CloudUniforms> program(boolean faces, boolean untextured) {
|
||||
int key = (faces ? 2 : 0) | (untextured ? 1 : 0);
|
||||
return programs.computeIfAbsent(key, ignored -> buildProgram(faces, untextured));
|
||||
}
|
||||
|
||||
void clear() {
|
||||
for (GlProgram<CloudUniforms> program : programs.values()) program.delete();
|
||||
programs.clear();
|
||||
textures.clear();
|
||||
}
|
||||
|
||||
private static GlProgram<CloudUniforms> buildProgram(boolean faces, boolean untextured) {
|
||||
final ShaderConstants.Builder builder = ShaderConstants.builder();
|
||||
if (untextured) builder.add("UNTEXTURED");
|
||||
final ShaderConstants constants = builder.build();
|
||||
final String vertName = faces ? "angelica:cloud_faces.vert" : "angelica:cloud.vert";
|
||||
final String fragName = faces ? "angelica:cloud_faces.frag" : "angelica:cloud.frag";
|
||||
final GlShader vert = ShaderLoader.loadShader(ShaderType.VERTEX, vertName, constants);
|
||||
final GlShader frag = ShaderLoader.loadShader(ShaderType.FRAGMENT, fragName, constants);
|
||||
final GlProgram<CloudUniforms> built;
|
||||
try {
|
||||
built = GlProgram.builder("angelica:cloud")
|
||||
.attachShader(vert)
|
||||
.attachShader(frag)
|
||||
.link(CloudUniforms::new);
|
||||
} finally {
|
||||
vert.delete();
|
||||
frag.delete();
|
||||
}
|
||||
|
||||
built.bind();
|
||||
if (!untextured) built.getInterface().textureUnit.setInt(0);
|
||||
built.unbind();
|
||||
return built;
|
||||
}
|
||||
|
||||
static final class Texture {
|
||||
|
||||
final ResourceLocation location;
|
||||
CloudShape shape;
|
||||
int generation;
|
||||
int width;
|
||||
|
||||
private final Minecraft mc = Minecraft.getMinecraft();
|
||||
private int mipmappedTexId = -1;
|
||||
private int cloudTexId = -1;
|
||||
private ByteBuffer texelStagingBuffer;
|
||||
|
||||
private Texture(ResourceLocation location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
void bind() {
|
||||
if (mc.renderEngine == null) return;
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
mc.renderEngine.bindTexture(location);
|
||||
final int boundTexId = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
if (boundTexId != mipmappedTexId) {
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 4);
|
||||
GLStateManager.glGenerateMipmap(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST_MIPMAP_LINEAR);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
|
||||
mipmappedTexId = boundTexId;
|
||||
}
|
||||
if (boundTexId != cloudTexId || shape == null) {
|
||||
extractCloudShape();
|
||||
cloudTexId = boundTexId;
|
||||
}
|
||||
}
|
||||
|
||||
private void extractCloudShape() {
|
||||
final int texWidth = GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH);
|
||||
final int texHeight = GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT);
|
||||
if (texWidth <= 0 || texHeight <= 0) return;
|
||||
final int neededBytes = texWidth * texHeight * 4;
|
||||
if (texelStagingBuffer == null || texelStagingBuffer.capacity() < neededBytes) {
|
||||
texelStagingBuffer = BufferUtils.createByteBuffer(neededBytes);
|
||||
} else {
|
||||
texelStagingBuffer.clear();
|
||||
}
|
||||
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE,
|
||||
texelStagingBuffer);
|
||||
width = texWidth;
|
||||
shape = new CloudShape(texWidth, texHeight, texelStagingBuffer);
|
||||
generation++;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -26,7 +26,7 @@ final class CloudUniforms {
|
||||
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 lastColorR = Float.NaN, lastColorG = Float.NaN, lastColorB = Float.NaN, lastColorA = Float.NaN;
|
||||
private float lastFogParamX = Float.NaN, lastFogParamY = Float.NaN, lastFogParamZ = Float.NaN, lastFogParamW = Float.NaN;
|
||||
private float lastCellHeight = Float.NaN;
|
||||
|
||||
@@ -96,15 +96,16 @@ final class CloudUniforms {
|
||||
}
|
||||
}
|
||||
|
||||
void setColorMult(float r, float g, float b) {
|
||||
if (r != lastColorR || g != lastColorG || b != lastColorB) {
|
||||
void setColorMult(float r, float g, float b, float alpha) {
|
||||
if (r != lastColorR || g != lastColorG || b != lastColorB || alpha != lastColorA) {
|
||||
lastColorR = r;
|
||||
lastColorG = g;
|
||||
lastColorB = b;
|
||||
lastColorA = alpha;
|
||||
vec4Buf[0] = r;
|
||||
vec4Buf[1] = g;
|
||||
vec4Buf[2] = b;
|
||||
vec4Buf[3] = ALPHA;
|
||||
vec4Buf[3] = alpha;
|
||||
colorMult.set(vec4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.gtnewhorizons.angelica.render;
|
||||
|
||||
import org.joml.Matrix4fc;
|
||||
|
||||
final class CloudView {
|
||||
|
||||
private float lastForwardX = Float.NaN, lastForwardZ = Float.NaN;
|
||||
double pixelsPerRadian;
|
||||
double facing;
|
||||
float offsetX, offsetY, offsetZ;
|
||||
private double sinHalfFovSquared;
|
||||
private boolean uncullable;
|
||||
|
||||
void update(Matrix4fc projection, Matrix4fc modelView, int width, int height) {
|
||||
pixelsPerRadian = CloudDisc.pixelsPerRadian(width, height, projection);
|
||||
final boolean projectionCullable = updateProjection(projection);
|
||||
final boolean cameraCullable = updateCamera(modelView);
|
||||
uncullable = !projectionCullable || !cameraCullable;
|
||||
}
|
||||
|
||||
private boolean updateProjection(Matrix4fc projection) {
|
||||
if (!projection.isFinite() || projection.m23() >= 0.0f || projection.m33() != 0.0f
|
||||
|| projection.m03() != 0.0f || projection.m13() != 0.0f
|
||||
|| projection.m30() != 0.0f || projection.m31() != 0.0f
|
||||
|| projection.m01() != 0.0f || projection.m10() != 0.0f
|
||||
|| projection.m00() == 0.0f || projection.m11() == 0.0f) return false;
|
||||
|
||||
final double tanX = (-projection.m23() + Math.abs(projection.m20())) / Math.abs(projection.m00());
|
||||
final double tanY = (-projection.m23() + Math.abs(projection.m21())) / Math.abs(projection.m11());
|
||||
final double tanSquared = tanX * tanX + tanY * tanY;
|
||||
sinHalfFovSquared = tanSquared / (1.0 + tanSquared);
|
||||
return Double.isFinite(sinHalfFovSquared);
|
||||
}
|
||||
|
||||
private boolean updateCamera(Matrix4fc modelView) {
|
||||
offsetX = offsetY = offsetZ = 0.0f;
|
||||
if (!modelView.isFinite() || modelView.m03() != 0.0f || modelView.m13() != 0.0f
|
||||
|| modelView.m23() != 0.0f || modelView.m33() != 1.0f || !rigid(modelView)) return false;
|
||||
final float tx = modelView.m30(), ty = modelView.m31(), tz = modelView.m32();
|
||||
offsetX = -(modelView.m00() * tx + modelView.m01() * ty + modelView.m02() * tz);
|
||||
offsetY = -(modelView.m10() * tx + modelView.m11() * ty + modelView.m12() * tz);
|
||||
offsetZ = -(modelView.m20() * tx + modelView.m21() * ty + modelView.m22() * tz);
|
||||
if (!Float.isFinite(offsetX) || !Float.isFinite(offsetY) || !Float.isFinite(offsetZ)) return false;
|
||||
final float forwardX = -modelView.m02(), forwardZ = -modelView.m22();
|
||||
if (forwardX != lastForwardX || forwardZ != lastForwardZ) {
|
||||
facing = Math.atan2(forwardZ, forwardX);
|
||||
lastForwardX = forwardX;
|
||||
lastForwardZ = forwardZ;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
double halfSpan(double heightCells, double driftCells) {
|
||||
final double radius = CloudDisc.ALWAYS_DRAWN_CELLS - 2.0 - driftCells;
|
||||
if (uncullable || radius <= 0.0 || driftCells * 2.0 >= CloudDisc.ALWAYS_DRAWN_CELLS) return Math.PI;
|
||||
final double slope = heightCells / radius;
|
||||
final double spreadSquared = sinHalfFovSquared * (1.0 + slope * slope);
|
||||
if (spreadSquared >= 1.0) return Math.PI;
|
||||
return Math.asin(Math.sqrt(spreadSquared)) + CloudDisc.driftAngle(driftCells);
|
||||
}
|
||||
|
||||
private static boolean rigid(Matrix4fc matrix) {
|
||||
if ((matrix.properties() & Matrix4fc.PROPERTY_ORTHONORMAL) != 0) return true;
|
||||
final double xx = matrix.m00() * matrix.m00() + matrix.m01() * matrix.m01() + matrix.m02() * matrix.m02();
|
||||
final double yy = matrix.m10() * matrix.m10() + matrix.m11() * matrix.m11() + matrix.m12() * matrix.m12();
|
||||
final double zz = matrix.m20() * matrix.m20() + matrix.m21() * matrix.m21() + matrix.m22() * matrix.m22();
|
||||
final double xy = matrix.m00() * matrix.m10() + matrix.m01() * matrix.m11() + matrix.m02() * matrix.m12();
|
||||
final double xz = matrix.m00() * matrix.m20() + matrix.m01() * matrix.m21() + matrix.m02() * matrix.m22();
|
||||
final double yz = matrix.m10() * matrix.m20() + matrix.m11() * matrix.m21() + matrix.m12() * matrix.m22();
|
||||
return Math.abs(xx - 1.0) < 1.0e-4 && Math.abs(yy - 1.0) < 1.0e-4 && Math.abs(zz - 1.0) < 1.0e-4
|
||||
&& Math.abs(xy) < 1.0e-4 && Math.abs(xz) < 1.0e-4 && Math.abs(yz) < 1.0e-4;
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -21,13 +21,14 @@ public class MixinRenderGlobal {
|
||||
*/
|
||||
@Overwrite
|
||||
public void renderClouds(float partialTicks) {
|
||||
final IRenderHandler renderer;
|
||||
if((renderer = theWorld.provider.getCloudRenderer()) != null) {
|
||||
final IRenderHandler renderer = theWorld.provider.getCloudRenderer();
|
||||
final boolean layered = CloudRenderer.supportsLayers(theWorld, renderer);
|
||||
if (renderer != null && !layered) {
|
||||
renderer.render(partialTicks, theWorld, mc);
|
||||
return;
|
||||
}
|
||||
if(mc.theWorld.provider.isSurfaceWorld()) {
|
||||
CloudRenderer.getCloudRenderer().render(cloudTickCounter, partialTicks);
|
||||
if (layered || theWorld.provider.isSurfaceWorld()) {
|
||||
CloudRenderer.getCloudRenderer().render(cloudTickCounter, partialTicks, renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user