Add Galaxy Space compat and fix culling issues (#2131)

This commit is contained in:
Eclipse-Sol authored and GitHub committed 2026-09-25 19:18:45 -07:00
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;
}
}
@@ -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);
}
}
}