Compare commits

..
7 Commits
47 changed files with 1410 additions and 418 deletions

No files matched your search

+3 -1
View File
@@ -90,7 +90,7 @@ dependencies {
compileOnly "org.apache.ant:ant:1.8.2"
// Celeritas (transitive=false: deps provided by GTNHLib/runtime)
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.4-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.5-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.3-GTNH") { transitive = false }
// Because who doesn't want NEI
@@ -180,4 +180,6 @@ dependencies {
}
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
compileOnly("com.cardinalstar.cubicchunks:CubicChunks1710:v0.1.5-alpha:dev") { transitive = false }
}
@@ -40,6 +40,7 @@ public final class VAOManager {
current.vertexFlags = currentVertexFlags;
current.ebo = boundEBO;
current.attribs = currentAttribs;
current.clientSideEnabledCount = clientSideEnabledCount;
}
VAOData data = vaoMap.get(vaoId);
@@ -51,8 +52,7 @@ public final class VAOManager {
currentVertexFlags = data.vertexFlags;
currentAttribs = data.attribs;
boundEBO = data.ebo;
recomputeClientSideCount();
clientSideEnabledCount = data.clientSideEnabledCount;
}
public static void onDeleteVertexArray(int vaoId) {
@@ -68,6 +68,7 @@ public final class VAOManager {
public Attrib[] attribs;
public int vertexFlags;
public int ebo;
public int clientSideEnabledCount;
public VAOData() {
attribs = new Attrib[MAX_ATTRIBS];
@@ -183,14 +184,6 @@ public final class VAOManager {
private static void recomputeClientSideCount() {
int count = 0;
for (int i = 0; i < MAX_ATTRIBS; i++) {
if (isClientSideAttrib(i)) count++;
}
clientSideEnabledCount = count;
}
/**
* Returns true if any currently enabled vertex attribute was registered without a VBO
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
@@ -0,0 +1,82 @@
package com.gtnewhorizons.angelica.glsm;
import org.joml.FrustumIntersection;
import org.joml.Matrix4f;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class HugeFarPlaneFrustumTest {
private static final float SECTION_HALF = 8.0f + 1.0f + 0.125f;
private static final float DEGENERATE_FAR = 2.147483648e9f;
private static Matrix4f combined(float zFar) {
final Matrix4f proj = new Matrix4f().perspective((float) Math.toRadians(70.0), 854.0f / 480.0f, 0.05f, zFar);
final Matrix4f mv = new Matrix4f()
.rotate((float) Math.toRadians(25.0), 1, 0, 0)
.rotate((float) Math.toRadians(123.0), 0, 1, 0)
.translate(0.0f, -1.62f, 0.0f);
return new Matrix4f().set(proj).mul(mv);
}
private static boolean sectionVisible(FrustumIntersection f, int sx, int sy, int sz) {
final float cx = sx * 16.0f + 8.0f;
final float cy = sy * 16.0f + 8.0f;
final float cz = sz * 16.0f + 8.0f;
return f.testAab(cx - SECTION_HALF, cy - SECTION_HALF, cz - SECTION_HALF, cx + SECTION_HALF, cy + SECTION_HALF, cz + SECTION_HALF);
}
@Test
void unnormalizedFrustumMatchesNormalFarPlane() {
final FrustumIntersection normal = new FrustumIntersection(combined(512.0f), false);
final FrustumIntersection huge = new FrustumIntersection(combined(DEGENERATE_FAR), false);
int visible = 0;
int mismatches = 0;
for (int x = -12; x <= 12; x++) {
for (int y = -4; y <= 4; y++) {
for (int z = -12; z <= 12; z++) {
final boolean vNormal = sectionVisible(normal, x, y, z);
if (vNormal) visible++;
if (vNormal != sectionVisible(huge, x, y, z)) mismatches++;
}
}
}
assertTrue(visible > 100, "sanity check broken: normal frustum accepted only " + visible + " sections");
assertEquals(0, mismatches, "sections culled differently under the degenerate far plane");
}
@Test
void normalizedSetCullsEverythingOnDegenerateFarPlane() {
final FrustumIntersection normalized = new FrustumIntersection();
normalized.set(combined(DEGENERATE_FAR));
int visible = 0;
for (int x = -12; x <= 12; x++) {
for (int y = -4; y <= 4; y++) {
for (int z = -12; z <= 12; z++) {
if (sectionVisible(normalized, x, y, z)) visible++;
}
}
}
assertEquals(0, visible, "set(m) with a degenerate far plane no longer culls everything");
}
@Test
void identityFrustumOnlyPassesCameraSection() {
final FrustumIntersection identity = new FrustumIntersection(new Matrix4f());
assertTrue(sectionVisible(identity, 0, 0, 0), "identity frustum rejected the camera's own section");
int visible = 0;
for (int x = -12; x <= 12; x++) {
for (int y = -4; y <= 4; y++) {
for (int z = -12; z <= 12; z++) {
if (Math.abs(x) <= 1 && Math.abs(y) <= 1 && Math.abs(z) <= 1) continue;
if (sectionVisible(identity, x, y, z)) visible++;
}
}
}
assertEquals(0, visible, "non-adjacent sections accepted by the identity frustum");
}
}
+1 -1
View File
@@ -61,7 +61,7 @@ dependencies {
api(project(":glsm"))
compileOnly("com.github.GTNewHorizons:lwjgl3ify:3.0.15:dev") { isTransitive = false }
compileOnly("org.embeddedt.celeritas:celeritas-common:2.5.3-GTNH") { isTransitive = false }
compileOnly("org.embeddedt.celeritas:celeritas-common:2.5.5-GTNH") { isTransitive = false }
val osName = System.getProperty("os.name").lowercase()
val osArch = System.getProperty("os.arch").lowercase()
@@ -11,9 +11,13 @@ import net.minecraft.client.resources.DefaultResourcePack;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import cpw.mods.fml.common.Loader;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.GraphicsEnvironment;
import java.io.File;
import java.io.IOException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
@@ -27,13 +31,13 @@ public class FontStrategist {
public static final Logger LOGGER = LogManager.getLogger("Angelica");
static {
HashMap<String, Font> fontSet = new HashMap<>();
if (GraphicsEnvironment.isHeadless()) {
LOGGER.warn("GraphicsEnvironment.isHeadless() returned true! Custom fonts will be unavailable. This is likely a MacOS issue.");
availableFonts = new Font[0];
LOGGER.warn("GraphicsEnvironment.isHeadless() returned true! Only bundled fonts will be available. This is likely a MacOS issue.");
} else {
// get available fonts without duplicates (250 copies of dialog.plain need not apply)
Font[] availableFontsDirty = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
HashMap<String, Font> fontSet = new HashMap<>();
HashMultiset<String> duplicates = HashMultiset.create(); // for debugging
for (Font font : availableFontsDirty) {
@@ -57,11 +61,14 @@ public class FontStrategist {
sb.append(". Some fonts may be missing from the font selection menu.");
LOGGER.warn(sb.toString());
}
availableFonts = fontSet.values().stream().sorted(Comparator.comparing(Font::getFontName)).toArray(Font[]::new);
LOGGER.info("Got {} fonts from GraphicsEnvironment ({} after deduplication)", availableFontsDirty.length, availableFonts.length);
LOGGER.info("Got {} fonts from GraphicsEnvironment ({} after deduplication)", availableFontsDirty.length, fontSet.size());
}
loadBundledFonts(fontSet);
availableFonts = fontSet.values().stream().sorted(Comparator.comparing(Font::getFontName)).toArray(Font[]::new);
// create and add the resource pack that provides fonts
HashMap<String, File> packMap = new HashMap<>();
for (int i = 0; i < FontProviderCustom.ATLAS_COUNT; i++) {
@@ -74,6 +81,44 @@ public class FontStrategist {
defaultResourcePacks.add(fontResourcePack);
}
// Load .ttf/.otf shipped with the pack so customFontName* can point at a font that isn't installed
// on the system. fontfiles/ is SmoothFont's folder, kept so packs built for it work unchanged.
private static void loadBundledFonts(HashMap<String, Font> fontSet) {
File configDir = Loader.instance().getConfigDir();
if (configDir == null) {
LOGGER.warn("Loader.instance().getConfigDir() returned null. Bundled fonts will not be loaded.");
return;
}
File parent = configDir.getParentFile();
File[] fontDirs = { new File(parent, "fontfiles"), new File(configDir, "angelica/fonts") };
GraphicsEnvironment ge = GraphicsEnvironment.isHeadless() ? null : GraphicsEnvironment.getLocalGraphicsEnvironment();
int loaded = 0;
for (File dir : fontDirs) {
File[] files = dir.listFiles((d, name) -> name.toLowerCase().endsWith(".ttf") || name.toLowerCase().endsWith(".otf"));
if (files == null) continue;
for (File file : files) {
try {
Font font = Font.createFont(Font.TRUETYPE_FONT, file);
String name = font.getFontName();
if (fontSet.containsKey(name)) {
String deduped = name;
int n = 1;
while (fontSet.containsKey(deduped)) deduped = name + "_" + n++;
LOGGER.warn("Font name {} (from {}) is already registered; registering this one as {}", name, file.getName(), deduped);
name = deduped;
}
if (ge != null) ge.registerFont(font);
fontSet.put(name, font);
loaded++;
LOGGER.info("Loaded font {} from {}/{}", name, dir.getName(), file.getName());
} catch (FontFormatException | IOException e) {
LOGGER.error("Couldn't load font {}", file.getPath(), e);
}
}
}
if (loaded > 0) LOGGER.info("Loaded {} bundled font(s) from the pack", loaded);
}
/**
Lets you get a FontProvider per char while respecting font priority and fallbacks, the unicode flag, whether
SGA is on, if we're in a splash screen, if a font can even display a character in the first place, etc.
@@ -1,13 +1,14 @@
package com.gtnewhorizons.angelica.compat;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.falsepattern.endlessids.config.GeneralConfig;
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
import com.gtnewhorizons.angelica.helpers.LoadControllerHelper;
import com.gtnewhorizons.angelica.rendering.celeritas.CubeStatusEvents;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
import mods.battlegear2.Battlegear;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class ModStatus {
public static final Logger LOGGER = LogManager.getLogger("ModCompat");
@@ -29,6 +30,7 @@ public class ModStatus {
public static boolean isBaublesLoaded;
public static boolean isCosmeticArmorReworkedLoaded;
public static boolean isFluidLoggedLoaded;
public static boolean isCubicChunksLoaded;
public static boolean isBOPLoaded;
public static boolean isEtFuturumLoaded;
@@ -50,6 +52,7 @@ public class ModStatus {
isBaublesLoaded = Loader.isModLoaded("Baubles");
isCosmeticArmorReworkedLoaded = Loader.isModLoaded("cosmeticarmorreworked");
isFluidLoggedLoaded = Loader.isModLoaded("fluidlogged");
isCubicChunksLoaded = Loader.isModLoaded("cubicchunks");
isEtFuturumLoaded = Loader.isModLoaded("etfuturum");
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
@@ -71,6 +74,10 @@ public class ModStatus {
}
}
if (isCubicChunksLoaded) {
CubeStatusEvents.init();
}
boolean eidBlockItemExtended = false;
boolean eidBiomeExtended = false;
if (isEIDLoaded) {
@@ -0,0 +1,44 @@
package com.gtnewhorizons.angelica.compat.cubicchunks;
import net.minecraft.world.World;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import com.cardinalstar.cubicchunks.api.IColumn;
import com.cardinalstar.cubicchunks.api.ICube;
import com.cardinalstar.cubicchunks.world.ICubicWorld;
import com.cardinalstar.cubicchunks.world.cube.Cube;
import com.cardinalstar.cubicchunks.world.cube.ICubeProviderInternal;
import com.gtnewhorizons.angelica.compat.mojang.ChunkSectionPos;
public class CubicChunksAPI {
public static ExtendedBlockStorage getCubeStorage(World world, int x, int y, int z) {
Cube cube = ((ICubeProviderInternal) world.getChunkProvider()).getLoadedCube(x, y, z);
if (cube == null || cube.isEmpty()) return null;
return cube.getStorage();
}
public static int[] getLoadedCubeLevelsInColumn(World world, int chunkX, int chunkZ) {
IColumn column = (IColumn) world.getChunkFromChunkCoords(chunkX, chunkZ);
return column.getLoadedCubes().stream().mapToInt(ICube::getY).toArray();
}
public static int getMinHeight(World world) {
return ((ICubicWorld) world).getMinHeight();
}
public static int getMaxHeight(World world) {
return ((ICubicWorld) world).getMaxHeight();
}
public static int getMinSectionY(World world) {
return ChunkSectionPos.getSectionCoord(getMinHeight(world));
}
public static int getMaxSectionYExclusive(World world) {
return ChunkSectionPos.getSectionCoord(getMaxHeight(world) - 1) + 1;
}
}
@@ -0,0 +1,22 @@
package com.gtnewhorizons.angelica.compat.lotr;
import net.minecraft.world.WorldProvider;
public final class LOTRCompat {
private static final Class<?> LOTR_WORLD_PROVIDER_CLASS;
static {
Class<?> clazz = null;
try {
clazz = Class.forName("lotr.common.world.LOTRWorldProvider", false, LOTRCompat.class.getClassLoader());
} catch (ClassNotFoundException ignored) {}
LOTR_WORLD_PROVIDER_CLASS = clazz;
}
private LOTRCompat() {}
public static boolean isLotrProvider(WorldProvider provider) {
return LOTR_WORLD_PROVIDER_CLASS != null && LOTR_WORLD_PROVIDER_CLASS.isInstance(provider);
}
}
@@ -1,8 +1,10 @@
package com.gtnewhorizons.angelica.compat.mojang;
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
import org.joml.Vector3i;
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
// See if we can merge/mixin/extend ChunkPosition maybe?
public class ChunkSectionPos extends Vector3i {
@@ -25,11 +27,8 @@ public class ChunkSectionPos extends Vector3i {
}
public static long asLong(int x, int y, int z) {
long l = 0L;
l |= ((long)x & 4194303L) << 42;
l |= ((long)y & 1048575L) << 0;
l |= ((long)z & 4194303L) << 20;
return l;
// Use CoordinatePacker so that cubic chunks can change the bit packing layout
return CoordinatePacker.pack(x, y, z);
}
public static int getLocalCoord(int coord) {
@@ -1,5 +1,9 @@
package com.gtnewhorizons.angelica.compat.mojang;
import net.minecraft.util.MathHelper;
import com.gtnewhorizons.angelica.compat.ModStatus;
public class CompatMathHelper {
public static int smallestEncompassingPowerOfTwo(int value) {
int j = value - 1;
@@ -24,4 +28,13 @@ public class CompatMathHelper {
return k == 0 ? value : value + divisor - k;
}
}
public static int clampHeight(int y) {
if (ModStatus.isCubicChunksLoaded) {
// no clamping
return y;
} else {
return MathHelper.clamp_int(y, 0, 255);
}
}
}
@@ -83,6 +83,13 @@ public enum Mixins implements IMixins {
.addClientMixins("angelica.vbo.MixinRenderGlobal")
),
ANGELICA_VBO_CLOUDS_FAR_PLANE(
new MixinBuilder()
.setApplyIf(() -> AngelicaConfig.enableVBOClouds && !AngelicaConfig.enableNotFineFeatures)
.setPhase(Phase.EARLY)
.addClientMixins("notfine.clouds.MixinEntityRenderer")
),
ANGELICA_PANORAMA_BLUR(
new MixinBuilder("Replace main menu panorama with modern equivalent")
.setPhase(Phase.EARLY)
@@ -231,8 +238,7 @@ public enum Mixins implements IMixins {
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.enableCeleritas)
.addClientMixins(
"celeritas.terrain.ChunkTrackerAccessor"
, "celeritas.terrain.MixinChunkProviderClient"
"celeritas.terrain.MixinChunkProviderClient"
, "celeritas.terrain.MixinMinecraft_ChunkUpdates"
, "celeritas.terrain.MixinRenderGlobal"
, "celeritas.terrain.MixinRenderSectionManager"
@@ -299,6 +305,8 @@ public enum Mixins implements IMixins {
, "shaders.MixinTileEntityRendererDispatcher"
, "shaders.MixinGlProgram"
, "shaders.MixinTextureManager_ReloadCount"
, "shaders.AccessorModelBox"
, "shaders.MixinModelBiped"
)
),
@@ -1,12 +0,0 @@
package com.gtnewhorizons.angelica.mixins.interfaces;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
public interface ChunkTrackerAccessor {
Long2IntOpenHashMap angelica$getChunkStatus();
LongOpenHashSet angelica$getChunkReady();
LongSet angelica$getUnloadQueue();
LongSet angelica$getLoadQueue();
}
@@ -1,8 +1,11 @@
package com.gtnewhorizons.angelica.mixins.interfaces;
import org.joml.FrustumIntersection;
import org.joml.Matrix4fc;
public interface ClippingHelperExt {
FrustumIntersection celeritas$getFrustumIntersection();
void celeritas$updateFrustumIntersection();
Matrix4fc celeritas$getCombinedMatrix();
}
@@ -1,6 +1,10 @@
package com.gtnewhorizons.angelica.mixins.interfaces;
import com.gtnewhorizons.angelica.utils.ConcurrentTileEntityMap;
import java.util.Map;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.ChunkPosition;
public interface IChunkTileEntityMapHolder {
ConcurrentTileEntityMap angelica$getConcurrentTEMap();
@@ -1,5 +1,35 @@
package com.gtnewhorizons.angelica.proxy;
import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID;
import java.lang.management.ManagementFactory;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiVideoSettings;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.util.Direction;
import net.minecraft.util.MathHelper;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.client.event.EntityViewRenderEvent;
import net.minecraftforge.client.event.FOVUpdateEvent;
import net.minecraftforge.client.event.GuiOpenEvent;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.WorldEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import biomesoplenty.api.content.BOPCBlocks;
import com.google.common.base.Objects;
import com.gtnewhorizon.gtnhlib.client.model.loading.ModelRegistry;
@@ -8,6 +38,7 @@ import com.gtnewhorizons.angelica.commands.AngelicaCommand;
import com.gtnewhorizons.angelica.common.BlockError;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.bettercrashes.BetterCrashesCompat;
import com.gtnewhorizons.angelica.compat.mojang.CompatMathHelper;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.rendering.TileEntityRenderBoundsRegistry;
import com.gtnewhorizons.angelica.config.CompatConfig;
@@ -53,34 +84,6 @@ import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
import me.jellysquid.mods.sodium.client.gui.SodiumOptionsGUI;
import net.coderbot.iris.Iris;
import net.coderbot.iris.client.IrisDebugScreenHandler;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiMainMenu;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiVideoSettings;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.server.integrated.IntegratedServer;
import net.minecraft.util.Direction;
import net.minecraft.util.MathHelper;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.chunk.Chunk;
import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.client.event.EntityViewRenderEvent;
import net.minecraftforge.client.event.FOVUpdateEvent;
import net.minecraftforge.client.event.GuiOpenEvent;
import net.minecraftforge.client.event.GuiScreenEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.world.WorldEvent;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.lang.management.ManagementFactory;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID;
public final class ClientProxy extends CommonProxy {
@@ -293,10 +296,10 @@ public final class ClientProxy extends CommonProxy {
event.left.set(i + 4, String.format(
"lc: %d b: %s bl: %d sl: %d rl: %d",
chunk.getTopFilledSegment() + 15,
chunk.getBiomeGenForWorldCoords(bX & 15, bZ & 15, mc.theWorld.getWorldChunkManager()).biomeName,
chunk.getSavedLightValue(EnumSkyBlock.Block, bX & 15, MathHelper.clamp_int(bY, 0, 255), bZ & 15),
chunk.getSavedLightValue(EnumSkyBlock.Sky, bX & 15, MathHelper.clamp_int(bY, 0, 255), bZ & 15),
chunk.getBlockLightValue(bX & 15, MathHelper.clamp_int(bY, 0, 255), bZ & 15, 0)));
this.mc.theWorld.getBiomeGenForCoords(bX & 15, bZ & 15).biomeName,
chunk.getSavedLightValue(EnumSkyBlock.Block, bX & 15, CompatMathHelper.clampHeight(bY), bZ & 15),
chunk.getSavedLightValue(EnumSkyBlock.Sky, bX & 15, CompatMathHelper.clampHeight(bY), bZ & 15),
chunk.getBlockLightValue(bX & 15, CompatMathHelper.clampHeight(bY), bZ & 15, 0)));
}
}
// Draw a frametime graph
@@ -26,8 +26,12 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VertexBufferType;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.FogState;
import jss.notfine.core.Settings;
import jss.notfine.gui.options.named.GraphicsQualityOff;
import net.coderbot.iris.Iris;
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
import net.coderbot.iris.shaderpack.CloudSetting;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.EntityRenderer;
@@ -45,6 +49,7 @@ import org.embeddedt.embeddium.impl.gl.shader.ShaderType;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
import org.joml.Matrix4f;
import org.joml.Matrix4fStack;
import org.joml.Vector3d;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
@@ -76,6 +81,8 @@ public class CloudRenderer implements IResourceManagerReloadListener {
private static final float SCROLL_SPEED = 1.0f / 256.0f;
private static final float EDGE_OVERLAP = 0.0001f;
private static final float INTERIOR_INSET = EDGE_OVERLAP * 8.0f;
private static final float MAX_FAR_PLANE_DISTANCE = 65536.0f;
private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
@@ -103,11 +110,13 @@ public class CloudRenderer implements IResourceManagerReloadListener {
private int anchorCellX = Integer.MIN_VALUE, anchorCellZ = Integer.MIN_VALUE;
private int anchorMarginCells = 0;
private int cachedEdgesGeneration = Integer.MIN_VALUE;
private boolean cachedEmitTopFace, cachedEmitBottomFace, cachedCameraInsideCloud;
private boolean cachedEmitTopFace, cachedEmitBottomFace;
private int cachedRenderRadius = -1;
private float cachedCloudInteriorHeight = Float.NaN;
private boolean geomBuilt;
private int cloudMode = -1, renderDistance = -1, cloudElevation = -1, scaleMult = -1;
private boolean enabled;
private ByteBuffer emitBuffer;
private long emitBufferAddr;
@@ -136,6 +145,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
private GlProgram<CloudUniforms> program;
private final Matrix4f mvpScratch = new Matrix4f();
private final Matrix4f mvScratch = new Matrix4f();
public CloudRenderer() {
((IReloadableResourceManager) mc.getResourceManager()).registerReloadListener(this);
@@ -160,10 +170,13 @@ public class CloudRenderer implements IResourceManagerReloadListener {
final GraphicsQualityOff cloudGraphicsQuality = (GraphicsQualityOff) Settings.MODE_CLOUDS.option.getStore();
final int cloudQualitySetting = cloudGraphicsQuality == GraphicsQualityOff.FANCY
|| cloudGraphicsQuality == GraphicsQualityOff.DEFAULT && mc.gameSettings.fancyGraphics ? 2 : 1;
final float dimCloudHeight = mc.theWorld == null ? 128.0f : mc.theWorld.provider.getCloudHeight();
final boolean finiteCloudHeight = Float.isFinite(dimCloudHeight);
final boolean newEnabled = cloudGraphicsQuality != GraphicsQualityOff.OFF
&& mc.gameSettings.shouldRenderClouds()
&& mc.theWorld != null
&& mc.theWorld.provider.isSurfaceWorld();
&& mc.theWorld.provider.isSurfaceWorld()
&& finiteCloudHeight;
final int targetDistance = Math.max(mc.gameSettings.renderDistanceChunks, (int) Settings.RENDER_DISTANCE_CLOUDS.option.getStore());
final int cloudScaleMult = (int) Settings.CLOUD_SCALE.option.getStore();
@@ -174,11 +187,12 @@ public class CloudRenderer implements IResourceManagerReloadListener {
invalidateGeometry();
}
enabled = newEnabled;
cloudMode = cloudQualitySetting;
renderDistance = targetDistance;
scaleMult = cloudScaleMult;
cloudElevation = mc.theWorld == null ? 128 : (int) mc.theWorld.provider.getCloudHeight();
cloudElevation = finiteCloudHeight ? (int) dimCloudHeight : 128;
// Allows the setting to work with RFG and similar without hardcoding.
// The minimum height check is so stuff like Aether cloud height doesn't get messed up.
if (cloudElevation >= 96) {
@@ -186,6 +200,26 @@ public class CloudRenderer implements IResourceManagerReloadListener {
}
}
/**
* Far-plane distance needed so none of the cloud geometry gets clipped.
*/
public float getRequiredFarPlaneDistance() {
if (!enabled || scaleMult <= 0 || renderDistance <= 0) return 0.0f;
if (mc.theWorld == null) return 0.0f;
final float cloudInteriorWidth = 12.0f * scaleMult;
final int renderRadius = (int) Math.ceil(renderDistance * 64.0f / (CELLS_PER_CHUNK * cloudInteriorWidth));
final int marginCells = Math.max(CELLS_PER_CHUNK, renderRadius * CELLS_PER_CHUNK / 4);
final int marginChunks = (marginCells + CELLS_PER_CHUNK - 1) / CELLS_PER_CHUNK;
float radiusBlocks = (renderRadius + marginChunks) * CELLS_PER_CHUNK * cloudInteriorWidth;
if (cloudMode != 2) radiusBlocks *= (float) Math.sqrt(2);
float verticalSlack = cloudElevation;
final Entity view = mc.renderViewEntity;
if (view != null) {
verticalSlack = Math.abs(cloudElevation + 0.33f - (float) view.posY) + 4.0f * scaleMult;
}
return Math.min(radiusBlocks + verticalSlack, MAX_FAR_PLANE_DISTANCE);
}
private void invalidateGeometry() {
anchorCellX = Integer.MIN_VALUE;
anchorCellZ = Integer.MIN_VALUE;
@@ -193,6 +227,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
cachedEdgesGeneration = Integer.MIN_VALUE;
cachedCloudInteriorHeight = Float.NaN;
cachedVertexCount = 0;
geomBuilt = false;
}
private void initProgram() {
@@ -215,23 +250,30 @@ public class CloudRenderer implements IResourceManagerReloadListener {
program.unbind();
}
private void uploadFogUniforms(float partialTicks, int renderRadius, float cloudInteriorWidth) {
private void uploadFogUniforms() {
final CloudUniforms u = program.getInterface();
final boolean fogEnabled = GLStateManager.getFogMode().isEnabled();
u.setFogEnabled(fogEnabled);
if (fogEnabled) {
final float start = renderDistance * 16.0f;
final float end = renderRadius * CELLS_PER_CHUNK * cloudInteriorWidth;
final float range = end - start;
u.setFogParams(range != 0.0f ? -1.0f / range : 0.0f, range != 0.0f ? end / range : 1.0f);
final Vec3 sky = mc.theWorld.getSkyColor(mc.renderViewEntity, partialTicks);
u.setFogColor((float) sky.xCoord, (float) sky.yCoord, (float) sky.zCoord);
if (!fogEnabled) return;
final FogState fog = GLStateManager.getFogState();
final Vector3d color = fog.getFogColor();
u.setFogColor((float) color.x, (float) color.y, (float) color.z);
switch (fog.getFogMode()) {
case GL11.GL_LINEAR -> {
final float range = fog.getEnd() - fog.getStart();
u.setFogParams(range != 0.0f ? -1.0f / range : 0.0f, range != 0.0f ? fog.getEnd() / range : 1.0f, 0.0f, 0.0f);
}
case GL11.GL_EXP -> u.setFogParams(0.0f, 0.0f, fog.getDensity(), 1.0f);
default -> u.setFogParams(0.0f, 0.0f, fog.getDensity(), 2.0f);
}
}
public boolean render(int cloudTicks, float partialTicks) {
if (mc.theWorld == null || mc.renderViewEntity == null) return false;
if (cloudMode != 2 || scaleMult <= 0) return false;
if (!enabled || scaleMult <= 0) return false;
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
if (pipeline != null && pipeline.getCloudSetting() == CloudSetting.OFF) return false;
boolean textureSetupDone = false;
if (edges == null) {
@@ -266,41 +308,45 @@ public class CloudRenderer implements IResourceManagerReloadListener {
final boolean emitTopFace = cameraRelativeY + cloudInteriorHeight >= 0.0F;
final boolean emitBottomFace = cameraRelativeY <= 0.0F;
final boolean cameraInsideY = cameraRelativeY <= 0.0F && cameraRelativeY + cloudInteriorHeight >= 0.0F;
final boolean cameraInsideCloud = cameraInsideY && edges.cellOpaque(anchorX, anchorZ);
final int renderRadius = (int) Math.ceil(renderDistance * 64.0f / (CELLS_PER_CHUNK * cloudInteriorWidth));
final boolean anchorInRange = vao != null
&& Math.abs(anchorX - anchorCellX) <= anchorMarginCells
&& Math.abs(anchorZ - anchorCellZ) <= anchorMarginCells;
final boolean insideBand = cloudMode == 2 && emitTopFace && emitBottomFace;
final boolean anchorInRange = geomBuilt
&& (insideBand
? anchorX == anchorCellX && anchorZ == anchorCellZ
: Math.abs(anchorX - anchorCellX) <= anchorMarginCells
&& Math.abs(anchorZ - anchorCellZ) <= anchorMarginCells);
final boolean geomCacheValid = anchorInRange
&& cachedEdgesGeneration == edgesGeneration
&& cachedEmitTopFace == emitTopFace
&& cachedEmitBottomFace == emitBottomFace
&& cachedCameraInsideCloud == cameraInsideCloud
&& cachedRenderRadius == renderRadius
&& cachedCloudInteriorHeight == cloudInteriorHeight;
if (!geomCacheValid) {
final int marginCells = cameraInsideCloud ? 0 : Math.max(CELLS_PER_CHUNK, renderRadius * CELLS_PER_CHUNK / 4);
rebuildGeometry(anchorX, anchorZ, renderRadius, cloudInteriorHeight, emitTopFace, emitBottomFace, cameraInsideCloud, marginCells);
final int marginCells = Math.max(CELLS_PER_CHUNK, renderRadius * CELLS_PER_CHUNK / 4);
rebuildGeometry(anchorX, anchorZ, renderRadius, cloudInteriorHeight, emitTopFace, emitBottomFace, marginCells);
anchorCellX = anchorX;
anchorCellZ = anchorZ;
anchorMarginCells = marginCells;
cachedEdgesGeneration = edgesGeneration;
cachedEmitTopFace = emitTopFace;
cachedEmitBottomFace = emitBottomFace;
cachedCameraInsideCloud = cameraInsideCloud;
cachedRenderRadius = renderRadius;
cachedCloudInteriorHeight = cloudInteriorHeight;
geomBuilt = true;
}
if (vao == null || cachedVertexCount == 0) return true;
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
mc.renderEngine.bindTexture(texture);
if (!textureSetupDone) setupCloudTexture();
if (cloudTexId != -1 && !textureSetupDone) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, cloudTexId);
} else {
mc.renderEngine.bindTexture(texture);
if (!textureSetupDone) setupCloudTexture();
}
final Vec3 color = mc.theWorld.getCloudColour(partialTicks);
float r = (float) color.xCoord;
@@ -337,10 +383,12 @@ public class CloudRenderer implements IResourceManagerReloadListener {
mv.pushMatrix();
mv.scale(cloudInteriorWidth, 1.0f, cloudInteriorWidth);
mv.translate(-(cameraRelativeX + anchorDriftX), cameraRelativeY, -(cameraRelativeZ + anchorDriftZ));
mvScratch.set(mv);
GLStateManager.getProjectionMatrix().mul(mv, mvpScratch);
mv.popMatrix();
u.mvp.set(mvpScratch);
uploadFogUniforms(partialTicks, renderRadius, cloudInteriorWidth);
u.mv.set(mvScratch);
uploadFogUniforms();
drawAllFactors(r, g, b, false);
program.unbind();
@@ -362,15 +410,13 @@ public class CloudRenderer implements IResourceManagerReloadListener {
private void drawAllFactors(float r, float g, float b, boolean shadersActive) {
vao.bind();
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
GLStateManager.disableCull();
GLStateManager.disableBlend();
GLStateManager.glDepthMask(true);
GLStateManager.glColorMask(false, false, false, false);
// Depth prepass: alpha must match color pass so discard threshold writes same depth coverage.
if (shadersActive) {
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, CloudUniforms.ALPHA);
} else {
program.getInterface().setColorMult(1.0f, 1.0f, 1.0f);
}
final int totalQuads = cachedVertexCount / 4;
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, totalQuads * 6, GL11.GL_UNSIGNED_INT, 0L);
@@ -403,25 +449,32 @@ public class CloudRenderer implements IResourceManagerReloadListener {
}
vao.unbind();
GLStateManager.enableCull();
GLStateManager.glDepthMask(true);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GLStateManager.disableBlend();
}
private void rebuildGeometry(int anchorX, int anchorZ, int renderRadius, float cloudInteriorHeight, boolean emitTopFace, boolean emitBottomFace, boolean cameraInsideCloud, int marginCells) {
private void rebuildGeometry(int anchorX, int anchorZ, int renderRadius, float cloudInteriorHeight, boolean emitTopFace, boolean emitBottomFace, int marginCells) {
prepareEmitBuffer();
final int marginChunks = (marginCells + CELLS_PER_CHUNK - 1) / CELLS_PER_CHUNK;
final int chunkRange = cameraInsideCloud ? 0 : (renderRadius + marginChunks);
final int outerLo = cameraInsideCloud ? -1 : -chunkRange;
final int outerHi = cameraInsideCloud ? 0 : chunkRange;
final int chunkRange = renderRadius + marginChunks;
final int outerLo = -chunkRange;
final int outerHi = chunkRange;
final int radiusCellsWithMargin = cameraInsideCloud ? CELLS_PER_CHUNK : (chunkRange * CELLS_PER_CHUNK);
final int radiusCellsWithMargin = chunkRange * CELLS_PER_CHUNK;
final int radiusCellsSq = radiusCellsWithMargin * radiusCellsWithMargin;
final float scrollX = anchorX * SCROLL_SPEED;
final float scrollZ = anchorZ * SCROLL_SPEED;
if (cloudMode != 2) {
emitFastLayer(chunkRange * CELLS_PER_CHUNK, scrollX, scrollZ);
finishGeometry();
return;
}
final int rows = (outerHi - outerLo + 1) * CELLS_PER_CHUNK;
final int lpr = (rows + 63) >>> 6;
final int minX = outerLo * CELLS_PER_CHUNK;
@@ -446,7 +499,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
for (int chunkX = outerLo; chunkX <= outerHi; chunkX++) {
for (int chunkZ = outerLo; chunkZ <= outerHi; chunkZ++) {
emitChunk(chunkX, chunkZ, anchorX, anchorZ, radiusCellsSq, cameraInsideCloud, emitTopFace, emitBottomFace, y1, ye, scrollX, scrollZ);
emitChunk(chunkX, chunkZ, anchorX, anchorZ, radiusCellsSq, emitTopFace, emitBottomFace, y1, ye, scrollX, scrollZ);
}
}
@@ -454,6 +507,53 @@ public class CloudRenderer implements IResourceManagerReloadListener {
emitTopBottomGlobalGreedy(emitTopFace, emitBottomFace, ye, scrollX, scrollZ);
}
if (emitTopFace && emitBottomFace) {
emitInteriorFaces(anchorX, anchorZ, y1, scrollX, scrollZ);
}
finishGeometry();
}
private void emitInteriorFaces(int anchorX, int anchorZ, double y1, float scrollX, float scrollZ) {
for (int dz = -1; dz <= 1; dz++) {
for (int dx = -1; dx <= 1; dx++) {
if (!edges.cellOpaque(anchorX + dx, anchorZ + dz)) continue;
final double x0 = dx + INTERIOR_INSET;
final double x1 = dx + 1 - INTERIOR_INSET;
final double z0 = dz + INTERIOR_INSET;
final double z1 = dz + 1 - INTERIOR_INSET;
final double yb = INTERIOR_INSET;
final double yt = y1 - INTERIOR_INSET;
final float uL = dx * SCROLL_SPEED + scrollX;
final float uR = (dx + 1) * SCROLL_SPEED + scrollX;
final float uC = (dx + 0.5F) * SCROLL_SPEED + scrollX;
final float vN = dz * SCROLL_SPEED + scrollZ;
final float vS = (dz + 1) * SCROLL_SPEED + scrollZ;
final float vC = (dz + 0.5F) * SCROLL_SPEED + scrollZ;
ensureEmitCapacity(6 * QUAD_BYTES);
emitCloudQuad(x0, yb, z1, uL, vS, x1, yb, z1, uR, vS, x1, yb, z0, uR, vN, x0, yb, z0, uL, vN, F_TOP, N_POS_Y);
emitCloudQuad(x0, yt, z0, uL, vN, x1, yt, z0, uR, vN, x1, yt, z1, uR, vS, x0, yt, z1, uL, vS, F_BOTTOM, N_NEG_Y);
emitCloudQuad(x0, yb, z0, uC, vN, x0, yt, z0, uC, vN, x0, yt, z1, uC, vS, x0, yb, z1, uC, vS, F_X, N_POS_X);
emitCloudQuad(x1, yb, z1, uC, vS, x1, yt, z1, uC, vS, x1, yt, z0, uC, vN, x1, yb, z0, uC, vN, F_X, N_NEG_X);
emitCloudQuad(x0, yb, z0, uL, vC, x1, yb, z0, uR, vC, x1, yt, z0, uR, vC, x0, yt, z0, uL, vC, F_Z, N_POS_Z);
emitCloudQuad(x0, yt, z1, uL, vC, x1, yt, z1, uR, vC, x1, yb, z1, uR, vC, x0, yb, z1, uL, vC, F_Z, N_NEG_Z);
}
}
}
private void emitFastLayer(int radiusCells, float scrollX, float scrollZ) {
final double r0 = -radiusCells;
final double r1 = radiusCells;
final float u0 = -radiusCells * SCROLL_SPEED + scrollX;
final float u1 = radiusCells * SCROLL_SPEED + scrollX;
final float v0 = -radiusCells * SCROLL_SPEED + scrollZ;
final float v1 = radiusCells * SCROLL_SPEED + scrollZ;
ensureEmitCapacity(QUAD_BYTES);
emitCloudQuad(r0, 0.0, r0, u0, v0, r1, 0.0, r0, u1, v0, r1, 0.0, r1, u1, v1, r0, 0.0, r1, u0, v1, F_BOTTOM, N_POS_Y);
}
private void finishGeometry() {
final int totalBytes = (int) (writeAddr - emitBufferAddr);
final int vertexCount = totalBytes / VERTEX_STRIDE;
if (vertexCount == 0) {
@@ -514,7 +614,7 @@ public class CloudRenderer implements IResourceManagerReloadListener {
}
}
private void emitChunk(int chunkX, int chunkZ, int anchorX, int anchorZ, int radiusCellsSq, boolean cameraInsideCloud, boolean emitTopFace, boolean emitBottomFace, double y1, double ye, float scrollX, float scrollZ) {
private void emitChunk(int chunkX, int chunkZ, int anchorX, int anchorZ, int radiusCellsSq, boolean emitTopFace, boolean emitBottomFace, double y1, double ye, float scrollX, float scrollZ) {
final float chunkOffsetX = chunkX * (float) CELLS_PER_CHUNK;
final float chunkOffsetZ = chunkZ * (float) CELLS_PER_CHUNK;
final int baseU = chunkX * CELLS_PER_CHUNK + anchorX;
@@ -534,43 +634,11 @@ public class CloudRenderer implements IResourceManagerReloadListener {
final int relX = chunkX * CELLS_PER_CHUNK + k;
final int relZ = chunkZ * CELLS_PER_CHUNK + j;
if (relX * relX + relZ * relZ > radiusCellsSq) continue;
if (cameraInsideCloud && (Math.abs(relX) > 1 || Math.abs(relZ) > 1)) continue;
emittableBits |= 1L << (j * CELLS_PER_CHUNK + k);
}
}
if (emittableBits == 0L) return;
if (cameraInsideCloud) {
long bits = emittableBits;
while (bits != 0L) {
final int bit = Long.numberOfTrailingZeros(bits);
bits &= bits - 1;
final int k = bit & 7;
final int j = bit >>> 3;
final double x0 = chunkOffsetX + k;
final double x1 = chunkOffsetX + k + 1;
final double z0 = chunkOffsetZ + j;
final double z1 = chunkOffsetZ + j + 1;
final double xe = x1 - EDGE_OVERLAP;
final double zs = z1 - EDGE_OVERLAP;
final float uC = (chunkOffsetX + k + 0.5F) * SCROLL_SPEED + scrollX;
final float vC = (chunkOffsetZ + j + 0.5F) * SCROLL_SPEED + scrollZ;
final float uL = (chunkOffsetX + k) * SCROLL_SPEED + scrollX;
final float uR = (chunkOffsetX + k + 1) * SCROLL_SPEED + scrollX;
final float vN = (chunkOffsetZ + j) * SCROLL_SPEED + scrollZ;
final float vS = (chunkOffsetZ + j + 1) * SCROLL_SPEED + scrollZ;
ensureEmitCapacity(6 * QUAD_BYTES);
emitCloudQuad(x0, 0.0, z1, uL, vS, x1, 0.0, z1, uR, vS, x1, 0.0, z0, uR, vN, x0, 0.0, z0, uL, vN, F_TOP, N_POS_Y);
emitCloudQuad(x0, ye, z0, uL, vN, x1, ye, z0, uR, vN, x1, ye, z1, uR, vS, x0, ye, z1, uL, vS, F_BOTTOM, N_NEG_Y);
emitCloudQuad(x0, 0.0, z0, uC, vN, x0, y1, z0, uC, vN, x0, y1, z1, uC, vS, x0, 0.0, z1, uC, vS, F_X, N_POS_X);
emitCloudQuad(xe, 0.0, z1, uC, vS, xe, y1, z1, uC, vS, xe, y1, z0, uC, vN, xe, 0.0, z0, uC, vN, F_X, N_NEG_X);
emitCloudQuad(x0, 0.0, z0, uL, vC, x1, 0.0, z0, uR, vC, x1, y1, z0, uR, vC, x0, y1, z0, uL, vC, F_Z, N_POS_Z);
emitCloudQuad(x0, y1, zs, uL, vC, x1, y1, zs, uR, vC, x1, 0.0, zs, uR, vC, x0, 0.0, zs, uL, vC, F_Z, N_NEG_Z);
}
return;
}
if (emitTopFace || emitBottomFace) {
final int globalColStart = chunkX * CELLS_PER_CHUNK - gridMinX;
final int globalRowStart = chunkZ * CELLS_PER_CHUNK - gridMinZ;
@@ -9,6 +9,7 @@ final class CloudUniforms {
static final float ALPHA = 0.8f;
final GlUniformMatrix4f mvp;
final GlUniformMatrix4f mv;
final GlUniformFloat4v colorMult;
final GlUniformFloat4v fogParams;
final GlUniformFloat4v fogColor;
@@ -18,11 +19,12 @@ final class CloudUniforms {
private int lastFogEnabled = -1;
private float lastFogR = Float.NaN, lastFogG = Float.NaN, lastFogB = Float.NaN;
private float lastColorR = Float.NaN, lastColorG = Float.NaN, lastColorB = Float.NaN;
private float lastFogParamX = Float.NaN, lastFogParamY = Float.NaN;
private float lastFogParamX = Float.NaN, lastFogParamY = Float.NaN, lastFogParamZ = Float.NaN, lastFogParamW = Float.NaN;
private final float[] uploadBuf = new float[4];
CloudUniforms(ShaderBindingContext ctx) {
mvp = ctx.bindUniform("u_MVPMatrix", GlUniformMatrix4f::new);
mv = ctx.bindUniform("u_MVMatrix", GlUniformMatrix4f::new);
colorMult = ctx.bindUniform("u_ColorMult", GlUniformFloat4v::new);
fogParams = ctx.bindUniform("u_FogParams", GlUniformFloat4v::new);
fogColor = ctx.bindUniform("u_FogColor", GlUniformFloat4v::new);
@@ -51,14 +53,16 @@ final class CloudUniforms {
}
}
void setFogParams(float x, float y) {
if (x != lastFogParamX || y != lastFogParamY) {
void setFogParams(float x, float y, float z, float w) {
if (x != lastFogParamX || y != lastFogParamY || z != lastFogParamZ || w != lastFogParamW) {
lastFogParamX = x;
lastFogParamY = y;
lastFogParamZ = z;
lastFogParamW = w;
uploadBuf[0] = x;
uploadBuf[1] = y;
uploadBuf[2] = 0f;
uploadBuf[3] = 0f;
uploadBuf[2] = z;
uploadBuf[3] = w;
fogParams.set(uploadBuf);
}
}
@@ -7,8 +7,9 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import java.util.function.BooleanSupplier;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
public class AngelicaRenderQueue {
private static final Thread MAIN_THREAD = Thread.currentThread();
private static final Queue<Runnable> TASKS = new ConcurrentLinkedQueue<>();
// Metrics
@@ -33,11 +34,11 @@ public class AngelicaRenderQueue {
}
private static final Executor EXECUTOR = (runnable) -> {
if(Thread.currentThread() == MAIN_THREAD) {
if(GLStateManager.isMainThread()) {
runnable.run();
} else {
TASKS.add(runnable);
LockSupport.unpark(MAIN_THREAD);
LockSupport.unpark(GLStateManager.getMainThread());
}
};
@@ -45,6 +46,11 @@ public class AngelicaRenderQueue {
return EXECUTOR;
}
public static void submit(Runnable runnable) {
TASKS.add(runnable);
LockSupport.unpark(GLStateManager.getMainThread());
}
public static int processTasks(int max) {
int tasksRun = 0;
while(tasksRun < max) {
@@ -0,0 +1,229 @@
package com.gtnewhorizons.angelica.rendering;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.states.ColorMask;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import lombok.Getter;
import net.coderbot.iris.gl.image.GlImage;
import net.minecraft.client.model.ModelBiped;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.client.renderer.entity.RendererLivingEntity;
import net.minecraft.entity.player.EntityPlayer;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
/**
* Skins in 1.7.10 are 64x32 while modern has 64x64 textures. We have to address it here.
*/
public final class PlayerReflectionCapture {
public static final int VERTEX_COUNT = 288;
public static final int FLOATS_PER_VERTEX = 5; // x, y, z, u, v
private static final int RAW_INTS_PER_VERTEX = 8;
private static final int VERTEX_FLAGS = VertexFlags.convertToFlags(true, false, true, false);
private static boolean active;
private static boolean drawnThisCapture;
@Getter
private static Object target;
@Getter
private static EntityPlayer targetEntity;
private static int vbo;
private static int vao;
private static int[] rawData;
private static float[] vertexScratch;
private static int skin64Tex;
private static ByteBuffer skinPixels;
private static int lastCapturedSkin;
private static int lastUploadedAtlas;
private static int skinCopyWidth = 64;
private static int skinCopyHeight = 32;
private static ByteBuffer repackBuffer;
private static long repackAddress;
private static int repackCapacity;
private PlayerReflectionCapture() {}
public static void begin(EntityPlayer player) {
active = false;
drawnThisCapture = false;
target = null;
targetEntity = null;
if (player == null) return;
final Render render = RenderManager.instance.getEntityRenderObject(player);
if (render instanceof RendererLivingEntity living && living.mainModel instanceof ModelBiped model) {
target = model;
targetEntity = player;
active = true;
}
}
public static void end() {
active = false;
target = null;
targetEntity = null;
}
public static boolean shouldCapture() {
return active && !drawnThisCapture;
}
/**
* Emit the 288-vertex player batch VBO.
*/
public static void drawPlayerCapture(float[] xyzuv) {
if (drawnThisCapture) return;
drawnThisCapture = true;
if (xyzuv == null || xyzuv.length < VERTEX_COUNT * FLOATS_PER_VERTEX) return;
// Build the Tessellator layout
if (rawData == null) rawData = new int[VERTEX_COUNT * RAW_INTS_PER_VERTEX];
final int[] raw = rawData;
for (int i = 0; i < VERTEX_COUNT; i++) {
final int s = i * FLOATS_PER_VERTEX;
final int d = i * RAW_INTS_PER_VERTEX;
raw[d] = Float.floatToRawIntBits(xyzuv[s]);
raw[d + 1] = Float.floatToRawIntBits(xyzuv[s + 1]);
raw[d + 2] = Float.floatToRawIntBits(xyzuv[s + 2]);
raw[d + 3] = Float.floatToRawIntBits(xyzuv[s + 3]);
raw[d + 4] = Float.floatToRawIntBits(xyzuv[s + 4]);
raw[d + 5] = 0xFFFFFFFF; // color
raw[d + 6] = 0x00008000; // normal
raw[d + 7] = 0x00F000F0; // light
}
// Pack into our own native buffer using the player model's vertex format
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[VERTEX_FLAGS];
ensureRepackCapacity(VERTEX_COUNT * format.getVertexSize());
final long writePtr = format.writeToBuffer0(repackAddress, raw, VERTEX_COUNT * RAW_INTS_PER_VERTEX);
repackBuffer.position(0);
repackBuffer.limit((int) (writePtr - repackAddress));
// Upload to our dedicated VBO at offset 0
ensureVAO(format);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, repackBuffer, GL15.GL_STREAM_DRAW);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
// Bind a 64x64 copy of the skin as `tex`
final int activeSkin = GLStateManager.getBoundTextureForServerState();
final boolean haveSkin = activeSkin != 0 && activeSkin != skin64Tex;
if (haveSkin) {
ensureSkin64Texture();
final GlImage atlas = GlImage.BY_NAME.get("playerAtlas_img");
final int atlasId = atlas != null ? atlas.getId() : 0;
final boolean skinChanged = activeSkin != lastCapturedSkin;
if (skinChanged) {
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(activeSkin);
final int srcW = info.getWidth();
final int srcH = info.getHeight();
if (srcW > 0 && srcH > 0) {
final int required = srcW * srcH * 4;
if (skinPixels == null || skinPixels.capacity() < required) {
skinPixels = ByteBuffer.allocateDirect(required).order(ByteOrder.nativeOrder());
}
// The 1.7.10 model UVs only address the top 64x32 region.
skinCopyWidth = Math.min(64, srcW);
skinCopyHeight = Math.min(32, srcH);
skinPixels.clear();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, activeSkin);
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, skinPixels);
lastCapturedSkin = activeSkin;
}
}
if (skinPixels != null && (skinChanged || atlasId != lastUploadedAtlas)) {
skinPixels.rewind();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, skin64Tex);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, skinCopyWidth, skinCopyHeight, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, skinPixels);
if (atlas != null) {
skinPixels.rewind();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, atlasId);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, skinCopyWidth, skinCopyHeight, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, skinPixels);
}
lastUploadedAtlas = atlasId;
}
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, skin64Tex);
}
final ColorMask prevColor = GLStateManager.getColorMask();
final boolean pr = prevColor.red, pg = prevColor.green, pb = prevColor.blue, pa = prevColor.alpha;
final boolean prevDepthWrite = GLStateManager.getDepthState().isEnabled();
GLStateManager.glColorMask(false, false, false, false);
GLStateManager.glDepthMask(false);
QuadConverter.drawQuadsAsTriangles(0, VERTEX_COUNT);
GLStateManager.glDepthMask(prevDepthWrite);
GLStateManager.glColorMask(pr, pg, pb, pa);
if (haveSkin) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, activeSkin);
}
GLStateManager.glBindVertexArray(0);
}
public static float[] vertexScratch() {
if (vertexScratch == null) vertexScratch = new float[VERTEX_COUNT * FLOATS_PER_VERTEX];
return vertexScratch;
}
private static void ensureVAO(VertexFormat format) {
if (vao != 0) return;
vbo = GLStateManager.glGenBuffers();
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
format.setupBufferState(0L);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
}
private static void ensureSkin64Texture() {
if (skin64Tex != 0) return;
final int prev = GLStateManager.getBoundTextureForServerState();
skin64Tex = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, skin64Tex);
final ByteBuffer zero = ByteBuffer.allocateDirect(64 * 64 * 4);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, zero);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, prev);
}
private static void ensureRepackCapacity(int requiredBytes) {
if (repackBuffer != null && requiredBytes <= repackCapacity) return;
int newCapacity = Math.max(0x4000, repackCapacity);
while (newCapacity < requiredBytes) newCapacity *= 2;
if (repackBuffer != null) memFree(repackBuffer);
repackBuffer = memAlloc(newCapacity);
repackAddress = memAddress0(repackBuffer);
repackCapacity = newCapacity;
}
}
@@ -22,15 +22,14 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.mixins.interfaces.ChunkTrackerAccessor;
import it.unimi.dsi.fastutil.longs.LongCollection;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSets;
import org.embeddedt.embeddium.impl.render.chunk.RenderSectionManager;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkStatus;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTracker;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTrackerImpl;
import org.embeddedt.embeddium.impl.util.PositionUtil;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
/**
* Configurable chunk tracker supporting "fast" and "aggressive" loading modes.
* Based on Beddium's SafeChunkTracker by Ven and FalsePattern.
@@ -41,109 +40,23 @@ import org.embeddedt.embeddium.impl.util.PositionUtil;
*
* Mode can be switched at runtime via options menu - triggers renderer reload.
*/
public class AngelicaChunkTracker extends ChunkTracker {
private final LongOpenHashSet chunkReadyForced = new LongOpenHashSet();
public class AngelicaChunkTracker extends ChunkTrackerImpl {
private ChunkTrackerAccessor self() {
return (ChunkTrackerAccessor) this;
@Override
public void forEachReady(RenderSectionManager sectionManager) {
if (this.isFastModeEnabled() != AngelicaConfig.useVanillaChunkTracking) {
setFastMode(AngelicaConfig.useVanillaChunkTracking);
}
super.forEachReady(sectionManager);
}
@Override
public void onChunkStatusAdded(int x, int z, int flags) {
final var key = PositionUtil.packChunk(x, z);
final var prev = self().angelica$getChunkStatus().get(key);
final var cur = prev | flags;
if (prev == cur) {
return;
public void forEachEvent(RenderSectionManager sectionManager) {
if (this.isFastModeEnabled() != AngelicaConfig.useVanillaChunkTracking) {
setFastMode(AngelicaConfig.useVanillaChunkTracking);
}
self().angelica$getChunkStatus().put(key, cur);
this.updateNeighbors(x, z);
}
@Override
public void onChunkStatusRemoved(int x, int z, int flags) {
final var key = PositionUtil.packChunk(x, z);
final var prev = self().angelica$getChunkStatus().get(key);
final int cur = prev & ~flags;
if (prev == cur) {
return;
}
if (cur == self().angelica$getChunkStatus().defaultReturnValue()) {
self().angelica$getChunkStatus().remove(key);
} else {
self().angelica$getChunkStatus().put(key, cur);
}
this.updateNeighbors(x, z);
}
private void updateNeighbors(int x, int z) {
for (int ox = -1; ox <= 1; ox++) {
for (int oz = -1; oz <= 1; oz++) {
this.updateMerged(ox + x, oz + z);
}
}
}
private void updateMerged(int x, int z) {
final long key = PositionUtil.packChunk(x, z);
final int selfFlags = self().angelica$getChunkStatus().get(key);
// If self doesn't have FLAG_ALL, can't be ready at all
if (selfFlags != ChunkStatus.FLAG_ALL) {
// Remove from ready if present
final boolean wasReady = self().angelica$getChunkReady().remove(key) | this.chunkReadyForced.remove(key);
if (wasReady && !self().angelica$getLoadQueue().remove(key)) {
self().angelica$getUnloadQueue().add(key);
}
return;
}
// Check if all neighbors also have FLAG_ALL
int mergedFlags = selfFlags;
for (int ox = -1; ox <= 1; ox++) {
for (int oz = -1; oz <= 1; oz++) {
mergedFlags &= self().angelica$getChunkStatus().get(PositionUtil.packChunk(ox + x, oz + z));
}
}
if (mergedFlags == ChunkStatus.FLAG_ALL) {
// All neighbors ready - add to both sets -- Trigger load if newly added to chunkReadyForced (first time ready)
if ((this.chunkReadyForced.add(key) || self().angelica$getChunkReady().add(key)) && !self().angelica$getUnloadQueue().remove(key)) {
self().angelica$getLoadQueue().add(key);
}
} else {
// Self ready but neighbors not - add to forced, remove from proper
// Trigger load if state changed (moved from proper to forced, or newly forced)
if ((this.chunkReadyForced.add(key) || self().angelica$getChunkReady().remove(key)) && !self().angelica$getUnloadQueue().remove(key)) {
self().angelica$getLoadQueue().add(key);
}
}
}
@Override
public LongCollection getReadyChunks() {
if (AngelicaConfig.useVanillaChunkTracking) {
// Aggressive mode: return both ready sets
final LongOpenHashSet combined = new LongOpenHashSet(self().angelica$getChunkReady());
combined.addAll(this.chunkReadyForced);
return LongSets.unmodifiable(combined);
} else {
// Default mode: only return chunks with all neighbors ready
return LongSets.unmodifiable(self().angelica$getChunkReady());
}
}
@Override
public void forEachEvent(ChunkEventHandler loadEventHandler, ChunkEventHandler unloadEventHandler) {
ChunkTracker.forEachChunk(self().angelica$getUnloadQueue(), unloadEventHandler);
self().angelica$getUnloadQueue().clear();
ChunkTracker.forEachChunk(self().angelica$getLoadQueue(), loadEventHandler);
self().angelica$getLoadQueue().clear();
super.forEachEvent(sectionManager);
}
}
@@ -1,5 +1,8 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import com.cardinalstar.cubicchunks.world.ICubicWorld;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.cubicchunks.CubicChunksAPI;
import com.gtnewhorizons.angelica.mixins.interfaces.RenderSectionManagerAccessor;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.AngelicaRenderQueue;
@@ -53,7 +56,18 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
public static AngelicaRenderSectionManager create(ChunkVertexType vertexType, WorldClient world, int renderDistance, CommandList commandList) {
final ChunkTaskProvider provider = ChunkTaskRegistry.getActiveProvider();
return new AngelicaRenderSectionManager(AngelicaRenderPassConfiguration.build(vertexType), world, renderDistance, commandList, 0, 16, provider.threadCount(), provider);
final int minSection;
final int maxSection;
if (ModStatus.isCubicChunksLoaded && world instanceof ICubicWorld) {
minSection = CubicChunksAPI.getMinSectionY(world);
maxSection = CubicChunksAPI.getMaxSectionYExclusive(world);
} else {
minSection = 0;
maxSection = 16;
}
return new AngelicaRenderSectionManager(AngelicaRenderPassConfiguration.build(vertexType), world, renderDistance, commandList, minSection, maxSection, provider.threadCount(), provider);
}
public void setCameraPosition(double x, double y, double z) {
@@ -125,6 +139,12 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
if (chunk.isEmpty()) {
return true;
}
if (ModStatus.isCubicChunksLoaded) {
final var section = CubicChunksAPI.getCubeStorage(this.world, x, y, z);
return section == null || section.isEmpty();
}
final var array = chunk.getBlockStorageArray();
if (y < 0 || y >= array.length) {
return true;
@@ -198,6 +218,12 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
return IrisShaderProviderHolder.isShadowPass();
}
public void markShadowGraphDirty() {
if (this.shadowRenderListManager != null) {
this.shadowRenderListManager.setNeedsUpdate(true);
}
}
@Override
public Collection<String> getDebugStrings() {
List<String> list = new ArrayList<>(super.getDebugStrings());
@@ -1,15 +1,10 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightWorldRenderer;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.interfaces.ITileEntityBoundingBoxCache;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.RenderingState;
import com.gtnewhorizons.angelica.rendering.TileEntityRenderBoundsRegistry;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
import net.coderbot.iris.pipeline.ShadowRenderer;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
@@ -17,6 +12,7 @@ import net.minecraft.entity.Entity;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraftforge.client.MinecraftForgeClient;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.embeddedt.embeddium.impl.gl.device.CommandList;
@@ -32,10 +28,18 @@ import org.embeddedt.embeddium.impl.render.viewport.Viewport;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.cubicchunks.CubicChunksAPI;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightWorldRenderer;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.interfaces.ITileEntityBoundingBoxCache;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.RenderingState;
import com.gtnewhorizons.angelica.rendering.TileEntityRenderBoundsRegistry;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
import net.coderbot.iris.pipeline.ShadowRenderer;
public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, AngelicaRenderSectionManager, BlockRenderLayer, TileEntity, CeleritasWorldRenderer.TileEntityRenderContext> implements IDynamicLightWorldRenderer {
private static final Logger LOGGER = LogManager.getLogger("Angelica");
@@ -110,12 +114,20 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, Ang
@Override
public int getMinimumBuildHeight() {
return 0;
if (ModStatus.isCubicChunksLoaded) {
return CubicChunksAPI.getMinHeight(this.world);
} else {
return 0;
}
}
@Override
public int getMaximumBuildHeight() {
return 256;
if (ModStatus.isCubicChunksLoaded) {
return CubicChunksAPI.getMaxHeight(this.world);
} else {
return 256;
}
}
@Override
@@ -0,0 +1,42 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import net.minecraft.client.Minecraft;
import net.minecraftforge.common.MinecraftForge;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTracker;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTrackerHolder;
import com.cardinalstar.cubicchunks.api.event.CubeEvent;
import com.cardinalstar.cubicchunks.util.CubePos;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
public class CubeStatusEvents {
public static void init() {
MinecraftForge.EVENT_BUS.register(new CubeStatusEvents());
}
private ChunkTracker getTracker() {
return ChunkTrackerHolder.get(Minecraft.getMinecraft().theWorld);
}
@SubscribeEvent
public void onCubeLoaded(CubeEvent.Load event) {
if (!event.world.isRemote) return;
if (getTracker() instanceof CubeStatusTracker cubic) {
CubePos pos = event.pos;
cubic.onCubeLoaded(pos.getX(), pos.getY(), pos.getZ());
}
}
@SubscribeEvent
public void onCubeUnloaded(CubeEvent.Unload event) {
if (!event.world.isRemote) return;
if (getTracker() instanceof CubeStatusTracker cubic) {
CubePos pos = event.pos;
cubic.onCubeUnloaded(pos.getX(), pos.getY(), pos.getZ());
}
}
}
@@ -0,0 +1,157 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import org.embeddedt.embeddium.impl.render.chunk.RenderSectionManager;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTracker;
import com.cardinalstar.cubicchunks.util.HashMap3D;
import com.cardinalstar.cubicchunks.util.HashSet3D;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
public class CubeStatusTracker implements ChunkTracker {
/// Cubes that have been synced to the client and are currently loaded.
private final HashSet3D loadedCubes = new HashSet3D();
/// Cubes that have their neighbours loaded (as needed) and are ready to be rendered.
private final HashSet3D validCubes = new HashSet3D();
private final HashMap3D<PendingState> changedCubes = new HashMap3D<>();
private enum PendingState {
Add,
Remove
}
/// When true, adjacent cubes will not be checked.
private boolean fastMode = false;
public CubeStatusTracker() {
}
@Override
public void setFastMode(boolean fastMode) {
this.fastMode = fastMode;
this.loadedCubes.forEach(this::pollCube);
}
@Override
public boolean isFastModeEnabled() {
return fastMode;
}
public void onCubeLoaded(int cubeX, int cubeY, int cubeZ) {
if (loadedCubes.add(cubeX, cubeY, cubeZ)) {
onCubeChanged(cubeX, cubeY, cubeZ);
}
}
public void onCubeUnloaded(int cubeX, int cubeY, int cubeZ) {
if (loadedCubes.remove(cubeX, cubeY, cubeZ)) {
onCubeChanged(cubeX, cubeY, cubeZ);
}
}
private void onCubeChanged(int cubeX, int cubeY, int cubeZ) {
pollCube(cubeX, cubeY, cubeZ);
if (!fastMode) {
for (int dz = -1; dz <= 1; dz++) {
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
if (dx == 0 && dy == 0 && dz == 0) continue;
pollCube(cubeX + dx, cubeY + dy, cubeZ + dz);
}
}
}
}
}
private void pollCube(int cubeX, int cubeY, int cubeZ) {
boolean renderable = canRender(cubeX, cubeY, cubeZ);
boolean isRendering = validCubes.contains(cubeX, cubeY, cubeZ);
if (renderable != isRendering) {
changedCubes.put(cubeX, cubeY, cubeZ, renderable ? PendingState.Add : PendingState.Remove);
} else {
changedCubes.remove(cubeX, cubeY, cubeZ);
}
}
private boolean canRender(int cubeX, int cubeY, int cubeZ) {
if (!loadedCubes.contains(cubeX, cubeY, cubeZ)) return false;
if (!fastMode) {
for (int dz = -1; dz <= 1; dz++) {
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
if (dx == 0 && dy == 0 && dz == 0) continue;
if (!loadedCubes.contains(cubeX + dx, cubeY + dy, cubeZ + dz)) return false;
}
}
}
}
return true;
}
@Override
public void onChunkStatusAdded(int i, int i1, int i2) {
// do nothing
}
@Override
public void onChunkStatusRemoved(int i, int i1, int i2) {
// do nothing
}
@Override
public void forEachReady(RenderSectionManager renderSectionManager) {
if (this.isFastModeEnabled() != AngelicaConfig.useVanillaChunkTracking) {
setFastMode(AngelicaConfig.useVanillaChunkTracking);
}
changedCubes.forEach((x, y, z, state) -> {
boolean ready = state == PendingState.Add;
if (ready) {
validCubes.add(x, y, z);
} else {
validCubes.remove(x, y, z);
}
});
changedCubes.clear();
for (var pos : validCubes.fastEntryIterable()) {
int x = pos.getX();
int y = pos.getY();
int z = pos.getZ();
renderSectionManager.onSectionAdded(x, y, z);
}
}
@Override
public void forEachEvent(RenderSectionManager renderSectionManager) {
if (this.isFastModeEnabled() != AngelicaConfig.useVanillaChunkTracking) {
setFastMode(AngelicaConfig.useVanillaChunkTracking);
}
changedCubes.forEach((x, y, z, state) -> {
boolean ready = state == PendingState.Add;
if (ready) {
validCubes.add(x, y, z);
renderSectionManager.onSectionAdded(x, y, z);
} else {
validCubes.remove(x, y, z);
renderSectionManager.onSectionRemoved(x, y, z);
}
});
changedCubes.clear();
}
}
@@ -1,26 +1,12 @@
package com.gtnewhorizons.angelica.rendering.celeritas.world;
import com.gtnewhorizons.angelica.api.SectionLightData;
import com.gtnewhorizons.angelica.api.IBlockAccessExtended;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.mojang.ChunkSectionPos;
import com.gtnewhorizons.angelica.compat.mojang.CompatMathHelper;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightSource;
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
import java.util.Arrays;
import java.util.List;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ChunkRenderContext;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSection;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSectionCache;
import org.embeddedt.embeddium.impl.util.position.SectionPos;
import cpw.mods.fml.common.Optional;
import mega.fluidlogged.api.FLBlockAccess;
import net.minecraft.block.Block;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.init.Blocks;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.MathHelper;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.World;
import net.minecraft.world.biome.BiomeGenBase;
@@ -30,9 +16,24 @@ import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import net.minecraft.world.gen.structure.StructureBoundingBox;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.Fluid;
import org.embeddedt.embeddium.impl.util.position.SectionPos;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import com.gtnewhorizons.angelica.api.IBlockAccessExtended;
import com.gtnewhorizons.angelica.api.SectionLightData;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.cubicchunks.CubicChunksAPI;
import com.gtnewhorizons.angelica.compat.mojang.ChunkSectionPos;
import com.gtnewhorizons.angelica.compat.mojang.CompatMathHelper;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightSource;
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ChunkRenderContext;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSection;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSectionCache;
import cpw.mods.fml.common.Optional;
import mega.fluidlogged.api.FLBlockAccess;
/**
* Takes a slice of world state (block states, biome and light data arrays) and copies the data for use in off-thread
@@ -111,7 +112,13 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
public static ChunkRenderContext prepare(World world, ChunkSectionPos origin, ClonedChunkSectionCache sectionCache) {
final Chunk chunk = world.getChunkFromChunkCoords(origin.x, origin.z);
final ExtendedBlockStorage section = chunk.getBlockStorageArray()[origin.y];
final ExtendedBlockStorage section;
if (ModStatus.isCubicChunksLoaded) {
section = CubicChunksAPI.getCubeStorage(world, origin.x, origin.y, origin.z);
} else {
section = chunk.getBlockStorageArray()[origin.y];
}
// If the chunk section is absent or empty, terminate early
if (section == null || section.isEmpty()) {
@@ -241,7 +248,10 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
@Override
public int getLightBrightnessForSkyBlocks(int x, int y, int z, int min) {
if (y < 0 || y >= 256 || x < -30_000_000 || z < -30_000_000 || x >= 30_000_000 || z >= 30_000_000) {
final int minHeight = ModStatus.isCubicChunksLoaded ? CubicChunksAPI.getMinHeight(this.world) : 0;
final int maxHeight = ModStatus.isCubicChunksLoaded ? CubicChunksAPI.getMaxHeight(this.world) : 256;
if (y < minHeight || y >= maxHeight || x < -30_000_000 || z < -30_000_000 || x >= 30_000_000 || z >= 30_000_000) {
// skyBrightness = 15, blockBrightness = min
return (15 << 20) | (min << 4);
}
@@ -372,7 +382,7 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
if (!blockBoxContains(this.volume, x, y, z)) {
return 0;
}
y = MathHelper.clamp_int(y, 0, 255);
y = CompatMathHelper.clampHeight(y);
final int relX = x - this.baseX;
final int relY = y - this.baseY;
@@ -4,12 +4,11 @@ import com.gtnewhorizons.angelica.api.BlockLightProvider;
import com.gtnewhorizons.angelica.api.SectionLightData;
import com.gtnewhorizons.angelica.compat.ExtendedBlockStorageExt;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.lotr.LOTRCompat;
import com.gtnewhorizons.angelica.compat.cubicchunks.CubicChunksAPI;
import com.gtnewhorizons.angelica.compat.mojang.ChunkSectionPos;
import com.gtnewhorizons.angelica.mixins.interfaces.IChunkTileEntityMapHolder;
import com.gtnewhorizons.angelica.utils.ConcurrentTileEntityMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
import mega.fluidlogged.internal.mixin.hook.FLSubChunk;
@@ -25,9 +24,13 @@ import net.minecraft.world.chunk.NibbleArray;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import net.minecraftforge.fluids.Fluid;
import java.util.Map;
public class ClonedChunkSection {
private static final ExtendedBlockStorage EMPTY_SECTION = new ExtendedBlockStorage(0, false);
private static final int SECTION_BLOCK_LENGTH = 16;
private static final int BIOME_DATA_LENGTH = SECTION_BLOCK_LENGTH * SECTION_BLOCK_LENGTH;
private final ClonedChunkSectionCache backingCache;
private final World world;
@@ -53,7 +56,13 @@ public class ClonedChunkSection {
throw new RuntimeException("Couldn't retrieve chunk at " + pos.toChunkPos());
}
ExtendedBlockStorage section = getChunkSection(chunk, pos);
ExtendedBlockStorage section;
if (ModStatus.isCubicChunksLoaded) {
section = CubicChunksAPI.getCubeStorage(world, pos.x, pos.y, pos.z);
} else {
section = getChunkSection(chunk, pos);
}
if (section == null) {
section = EMPTY_SECTION;
@@ -62,7 +71,7 @@ public class ClonedChunkSection {
this.pos = pos;
this.data = new ExtendedBlockStorageExt(chunk, section);
this.biomeData = new BiomeGenBase[16 * 16];
this.biomeData = new BiomeGenBase[BIOME_DATA_LENGTH];
copyBlockEntities(chunk, pos);
@@ -72,15 +81,22 @@ public class ClonedChunkSection {
}
private void fillBiomeData(Chunk chunk) {
final WorldChunkManager wcm = world.getWorldChunkManager();
for (int lz = 0; lz < 16; lz++) {
for (int lx = 0; lx < 16; lx++) {
this.biomeData[lx | (lz << 4)] = chunk.getBiomeGenForWorldCoords(lx, lz, wcm);
if (LOTRCompat.isLotrProvider(world.provider)) {
for (int z = pos.getMinZ(); z <= pos.getMaxZ(); z++) {
for (int x = pos.getMinX(); x <= pos.getMaxX(); x++) {
this.biomeData[((z & 15) << 4) | (x & 15)] = world.getBiomeGenForCoords(x, z);
}
}
} else {
final WorldChunkManager wcm = world.getWorldChunkManager();
for (int lz = 0; lz < SECTION_BLOCK_LENGTH; lz++) {
for (int lx = 0; lx < SECTION_BLOCK_LENGTH; lx++) {
this.biomeData[(lz << 4) | lx] = chunk.getBiomeGenForWorldCoords(lx, lz, wcm);
}
}
}
}
@SuppressWarnings("unchecked")
private void copyBlockEntities(Chunk chunk, ChunkSectionPos pos) {
this.tileEntities.clear();
@@ -88,22 +104,13 @@ public class ClonedChunkSection {
map.readLock();
try {
final Object2ObjectOpenHashMap<ChunkPosition, TileEntity> delegate = map.getDelegate();
if (!delegate.isEmpty()) {
final int minY = pos.getMinY();
final int maxY = pos.getMaxY();
for (Object2ObjectMap.Entry<ChunkPosition, TileEntity> entry : Object2ObjectMaps.fastIterable(delegate)) {
final ChunkPosition tePos = entry.getKey();
if (tePos.chunkPosY < minY || tePos.chunkPosY > maxY) continue;
final TileEntity te = entry.getValue();
if (te != null && !te.isInvalid()) {
this.tileEntities.put(ChunkSectionPos.packLocal(tePos.chunkPosX & 15, tePos.chunkPosY & 15, tePos.chunkPosZ & 15), te);
}
}
}
map.forEachInYRange(pos.getMinY(), pos.getMaxY(), entry -> {
final ChunkPosition tePos = entry.getKey();
this.tileEntities.put(
ChunkSectionPos.packLocal(tePos.chunkPosX & 15, tePos.chunkPosY & 15, tePos.chunkPosZ & 15),
entry.getValue()
);
});
} finally {
map.readUnlock();
}
@@ -169,12 +176,15 @@ public class ClonedChunkSection {
this.lastUsedTimestamp = timestamp;
}
public static boolean isOutOfBuildLimitVertically(int y) {
public static boolean isOutOfBuildLimitVertically(World world, int y) {
if (ModStatus.isCubicChunksLoaded) {
return y < CubicChunksAPI.getMinHeight(world) || y >= CubicChunksAPI.getMaxHeight(world);
}
return y < 0 || y >= 256;
}
private static ExtendedBlockStorage getChunkSection(Chunk chunk, ChunkSectionPos pos) {
if (!isOutOfBuildLimitVertically(ChunkSectionPos.getBlockCoord(pos.y))) {
private ExtendedBlockStorage getChunkSection(Chunk chunk, ChunkSectionPos pos) {
if (!isOutOfBuildLimitVertically(this.world, ChunkSectionPos.getBlockCoord(pos.y))) {
return chunk.getBlockStorageArray()[pos.y];
}
return null;
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.utils;
import com.gtnewhorizons.angelica.rendering.RenderThreadContext;
import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.ChunkPosition;
@@ -10,15 +11,30 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
/**
* Thread-safe wrapper around Object2ObjectOpenHashMap for chunkTileEntityMap.
* Thread-safe wrapper around a tile entity map for chunkTileEntityMap.
* May wrap either a plain Object2ObjectOpenHashMap (vanilla) or a ColumnTileEntityMap (cubic chunks).
*/
public class ConcurrentTileEntityMap implements Map<ChunkPosition, TileEntity> {
private final Object2ObjectOpenHashMap<ChunkPosition, TileEntity> delegate = new Object2ObjectOpenHashMap<>();
private final Object2ObjectOpenHashMap<ChunkPosition, TileEntity> flatDelegate;
private final Map<ChunkPosition, TileEntity> delegate;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final ConcurrentLinkedQueue<ChunkPosition> invalidationQueue = new ConcurrentLinkedQueue<>();
/** Vanilla path: backs the map with a fresh flat map. */
public ConcurrentTileEntityMap() {
this.flatDelegate = new Object2ObjectOpenHashMap<>();
this.delegate = this.flatDelegate;
}
/** cubic chunks path: wraps an existing map (e.g. ColumnTileEntityMap). */
public ConcurrentTileEntityMap(Map<ChunkPosition, TileEntity> toWrap) {
this.flatDelegate = null;
this.delegate = toWrap;
}
public void queueInvalidation(ChunkPosition pos) {
invalidationQueue.add(pos);
}
@@ -59,14 +75,27 @@ public class ConcurrentTileEntityMap implements Map<ChunkPosition, TileEntity> {
}
}
public Object2ObjectOpenHashMap<ChunkPosition, TileEntity> getDelegate() {
return delegate;
}
public TileEntity putDirect(ChunkPosition key, TileEntity value) {
return delegate.put(key, value);
}
/**
* Iterates all non-invalid tile entities whose Y coordinate falls within [minY, maxY].
* Caller must hold the read lock.
*/
public void forEachInYRange(int minY, int maxY, Consumer<Map.Entry<ChunkPosition, TileEntity>> action) {
final Iterable<? extends Map.Entry<ChunkPosition, TileEntity>> entries =
flatDelegate != null ? Object2ObjectMaps.fastIterable(flatDelegate) : delegate.entrySet();
for (Map.Entry<ChunkPosition, TileEntity> entry : entries) {
final ChunkPosition tePos = entry.getKey();
if (tePos.chunkPosY < minY || tePos.chunkPosY > maxY) continue;
final TileEntity te = entry.getValue();
if (te != null && !te.isInvalid()) {
action.accept(entry);
}
}
}
private static boolean isRenderWorkerThread() {
return RenderThreadContext.hasWorldSlice();
}
@@ -135,7 +164,10 @@ public class ConcurrentTileEntityMap implements Map<ChunkPosition, TileEntity> {
lock.writeLock().lock();
try {
invalidationQueue.clear();
delegate.clear();
if (flatDelegate != null) {
flatDelegate.clear();
}
// wrapped delegate manages its own TE lifetime; skip clear()
} finally {
lock.writeLock().unlock();
}
@@ -6,6 +6,7 @@ import net.minecraft.util.IIcon;
import net.minecraft.world.IBlockAccess;
import com.prupe.mcpatcher.mal.block.BlockStateMatcher;
import net.minecraftforge.common.util.ForgeDirection;
final class BlockOrientation extends RenderBlockState {
@@ -44,7 +45,7 @@ final class BlockOrientation extends RenderBlockState {
private int textureFace;
private int textureFaceOrig;
private int rotateUV;
private boolean flipBottom;
private boolean flipped;
@Override
public void clear() {
@@ -57,7 +58,7 @@ final class BlockOrientation extends RenderBlockState {
offsetsComputed = false;
haveOffsets = false;
dx = dy = dz = 0;
flipBottom = !fixedBottomFaceUV;
flipped = false;
}
@Override
@@ -102,7 +103,10 @@ final class BlockOrientation extends RenderBlockState {
@Override
public int[] getOffset(int blockFace, int relativeDirection) {
return NEIGHBOR_OFFSET[flipBottom && blockFace == 0 ? 1 : blockFace][rotateUV(relativeDirection)];
if(flipped){
blockFace = ForgeDirection.OPPOSITES[blockFace];
}
return NEIGHBOR_OFFSET[blockFace][rotateUV(relativeDirection)];
}
@Override
@@ -146,8 +150,8 @@ final class BlockOrientation extends RenderBlockState {
}
public void setFlipBottom(){
flipBottom = true;
public void flipFace() {
flipped = true;
}
@Override
@@ -175,7 +179,7 @@ final class BlockOrientation extends RenderBlockState {
copy.textureFace = textureFace;
copy.textureFaceOrig = textureFaceOrig;
copy.rotateUV = rotateUV;
copy.flipBottom = flipBottom;
copy.flipped = flipped;
copy.blockAccess = blockAccess;
copy.block = block;
@@ -208,7 +212,7 @@ final class BlockOrientation extends RenderBlockState {
rotateUV = 0;
textureFace = blockFaceToTextureFace(blockFace);
metadataBits = (1 << metadata) | (1 << altMetadata);
flipBottom = !fixedBottomFaceUV;
if(!fixedBottomFaceUV && blockFace == ForgeDirection.DOWN.ordinal()) flipped = true;
}
void setBlockMetadata(Block block, int metadata, int face) {
@@ -4,6 +4,7 @@ import it.unimi.dsi.fastutil.ints.Int2IntMap;
import net.minecraft.block.Block;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.util.IIcon;
import net.minecraftforge.common.util.ForgeDirection;
public final class CompactCtmQuadProcessor {
@@ -52,29 +53,33 @@ public final class CompactCtmQuadProcessor {
this.override = compact;
}
public boolean processFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon origIcon) {
if(renderBlockState instanceof BlockOrientation blockOrientation){
blockOrientation.setFlipBottom();
public boolean processFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon origIcon, int face) {
boolean shouldFlip = face == ForgeDirection.DOWN.ordinal();
if(ForgeDirection.OPPOSITES[face] == renderBlockState.getBlockFace()){
shouldFlip = !shouldFlip;
}
if(shouldFlip && renderBlockState instanceof BlockOrientation blockOrientation){
blockOrientation.flipFace();
}
int neighborBits = override.getNeighborBits(renderBlockState, origIcon);
int ctmIndex = TileOverride.neighborMap[neighborBits & 0xFF];
IIcon replacement = getReplacementIcon(ctmIndex);
if (replacement != null) {
renderWholeFace(rb, renderBlockState, replacement);
renderWholeFace(rb, renderBlockState, replacement, face);
return true;
}
if (neighborBits == 0 || neighborBits == 0xFF) {
IIcon sprite = sprites[getCompactSpriteIndex(neighborBits)];
if (sprite != null) {
renderWholeFace(rb, renderBlockState, sprite);
renderWholeFace(rb, renderBlockState, sprite, face);
return true;
}
return false;
}
renderSplitFace(rb, renderBlockState, neighborBits);
renderSplitFace(rb, renderBlockState, neighborBits, face);
return true;
}
@@ -117,8 +122,7 @@ public final class CompactCtmQuadProcessor {
return SPRITE_DEFAULT;
}
private void renderSplitFace(RenderBlocks rb, RenderBlockState renderBlockState, int connections) {
final int face = renderBlockState.getBlockFace();
private void renderSplitFace(RenderBlocks rb, RenderBlockState renderBlockState, int connections, int face) {
final double minX = rb.renderMinX, minY = rb.renderMinY, minZ = rb.renderMinZ;
final double maxX = rb.renderMaxX, maxY = rb.renderMaxY, maxZ = rb.renderMaxZ;
@@ -155,7 +159,7 @@ public final class CompactCtmQuadProcessor {
}
setQuadrantBounds(rb, face, q, minX, minY, minZ, maxX, maxY, maxZ);
rb.overrideBlockTexture = icon;
renderFace(rb, renderBlockState, icon);
renderFace(rb, renderBlockState, icon, face);
}
if (ao) {
@@ -219,18 +223,17 @@ public final class CompactCtmQuadProcessor {
return (sky << 16) | (block & 0xFFFF);
}
private void renderWholeFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon icon) {
private void renderWholeFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon icon, int face) {
IIcon saved = rb.overrideBlockTexture;
rb.overrideBlockTexture = icon;
try {
renderFace(rb, renderBlockState, icon);
renderFace(rb, renderBlockState, icon, face);
} finally {
rb.overrideBlockTexture = saved;
}
}
private void renderFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon icon) {
int face = renderBlockState.getBlockFace();
private void renderFace(RenderBlocks rb, RenderBlockState renderBlockState, IIcon icon, int face) {
Block block = renderBlockState.getBlock();
int x = renderBlockState.getX();
int y = renderBlockState.getY();
@@ -1,8 +1,22 @@
package me.jellysquid.mods.sodium.client.gui;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.I18n;
import net.minecraft.client.settings.GameSettings;
import org.lwjgl.opengl.Display;
import com.cardinalstar.cubicchunks.api.compat.CubicChunksVideoSettings;
import com.google.common.collect.ImmutableList;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import cpw.mods.fml.common.Optional.Method;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import jss.notfine.core.Settings;
import jss.notfine.core.SettingsManager;
@@ -22,19 +36,12 @@ import me.jellysquid.mods.sodium.client.gui.options.named.LightingQuality;
import me.jellysquid.mods.sodium.client.gui.options.named.MultiDrawMode;
import me.jellysquid.mods.sodium.client.gui.options.named.ParticleMode;
import me.jellysquid.mods.sodium.client.gui.options.storage.AngelicaOptionsStorage;
import me.jellysquid.mods.sodium.client.gui.options.storage.CubicChunksOptionStorage;
import me.jellysquid.mods.sodium.client.gui.options.storage.MinecraftOptionsStorage;
import me.jellysquid.mods.sodium.client.gui.options.storage.SodiumOptionsStorage;
import net.coderbot.iris.Iris;
import net.coderbot.iris.gui.option.IrisVideoSettings;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.I18n;
import net.minecraft.client.settings.GameSettings;
import org.embeddedt.embeddium.impl.render.chunk.occlusion.AsyncOcclusionMode;
import org.lwjgl.opengl.Display;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class SodiumGameOptionPages {
private static final SodiumOptionsStorage sodiumOpts = new SodiumOptionsStorage();
@@ -54,7 +61,13 @@ public class SodiumGameOptionPages {
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build());
if (ModStatus.isCubicChunksLoaded) {
firstGroupBuilder.add(getCCVerticalViewDistance());
}
if(Iris.enabled) {
final OptionImpl<GameSettings, Integer> maxShadowDistanceSlider = OptionImpl.createBuilder(int.class, vanillaOpts)
.setName(I18n.format("options.iris.shadowDistance"))
.setTooltip(I18n.format("options.iris.shadowDistance.sodium_tooltip"))
@@ -499,6 +512,27 @@ public class SodiumGameOptionPages {
return new OptionPage(I18n.format("sodium.options.pages.performance"), ImmutableList.copyOf(groups));
}
@Method(modid = "cubicchunks")
private static OptionImpl<?, ?> getCCVerticalViewDistance() {
CubicChunksOptionStorage storage = new CubicChunksOptionStorage();
return OptionImpl.createBuilder(int.class, storage)
.setName(I18n.format("sodium.options.cc.vertical_view_distance.name"))
.setTooltip(I18n.format("sodium.options.cc.vertical_view_distance.tooltip"))
.setControl(option -> new SliderControl(
option,
CubicChunksVideoSettings.getMinVerticalViewDistance(),
CubicChunksVideoSettings.getMaxVerticalViewDistance(),
1,
ControlValueFormatter.quantity("options.chunks")))
.setBinding(
(options, value) -> options.verticalViewDistance = value,
options -> options.verticalViewDistance)
.setImpact(OptionImpact.HIGH)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build();
}
public static OptionPage appearance() {
final List<OptionGroup> groups = new ArrayList<>();
@@ -0,0 +1,22 @@
package me.jellysquid.mods.sodium.client.gui.options.storage;
import com.cardinalstar.cubicchunks.api.compat.CubicChunksVideoSettings;
public class CubicChunksOptionStorage implements OptionStorage<CubicChunksOptionStorage.CubicChunksOptions> {
public static class CubicChunksOptions {
public int verticalViewDistance = CubicChunksVideoSettings.getVerticalViewDistance();
}
private final CubicChunksOptions options = new CubicChunksOptions();
@Override
public CubicChunksOptions getData() {
return options;
}
@Override
public void save() {
CubicChunksVideoSettings.setVerticalViewDistance(options.verticalViewDistance);
}
}
@@ -12,7 +12,12 @@ import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL14;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class GlImage extends GlObject {
public static final Map<String, GlImage> BY_NAME = new ConcurrentHashMap<>();
protected final String name;
protected final String samplerName;
protected final TextureType target;
@@ -42,6 +47,8 @@ public class GlImage extends GlObject {
setup(texture, width, height, depth);
GLStateManager.glBindTexture(target.getGlType(), 0);
BY_NAME.put(name, this);
}
protected void setup(int texture, int width, int height, int depth) {
@@ -106,6 +113,7 @@ public class GlImage extends GlObject {
@Override
protected void destroyInternal() {
GLStateManager.glDeleteTextures(handle());
BY_NAME.remove(name, this);
}
public InternalTextureFormat getInternalFormat() {
@@ -7,6 +7,7 @@ import com.gtnewhorizons.angelica.compat.toremove.MatrixStack;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.rendering.PlayerReflectionCapture;
import com.gtnewhorizons.angelica.rendering.RenderingState;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
import net.coderbot.iris.Iris;
@@ -468,11 +469,13 @@ public class ShadowRenderer {
profiler.endStartSection("build geometry");
setupEntityShadowState(modelView, cameraX, cameraY, cameraZ);
PlayerReflectionCapture.begin(player);
try {
for (Entity entity : renderedEntitiesList) {
RenderManager.instance.renderEntitySimple(entity, tickDelta);
}
} finally {
PlayerReflectionCapture.end();
teardownEntityShadowState();
}
@@ -516,7 +519,7 @@ public class ShadowRenderer {
private void renderPlayerEntity(EntityRenderer levelRenderer, Frustrum frustum, Object bufferSource, MatrixStack modelView, double cameraX, double cameraY, double cameraZ, float tickDelta) {
profiler.startSection("cull");
Entity player = Minecraft.getMinecraft().thePlayer;
EntityPlayer player = Minecraft.getMinecraft().thePlayer;
// Skip if spectating or outside frustum
if (GameModeUtil.isSpectator()) {
@@ -547,7 +550,12 @@ public class ShadowRenderer {
shadowEntities++;
}
RenderManager.instance.renderEntitySimple(player, tickDelta);
PlayerReflectionCapture.begin(player);
try {
RenderManager.instance.renderEntitySimple(player, tickDelta);
} finally {
PlayerReflectionCapture.end();
}
shadowEntities++;
} finally {
teardownEntityShadowState();
@@ -684,7 +692,7 @@ public class ShadowRenderer {
// Mark the shadow graph as needing update before terrain setup
// Modern Celeritas does this to ensure the shadow render lists get populated
com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer.getInstance()
.getRenderSectionManager().markGraphDirty();
.getRenderSectionManager().markShadowGraphDirty();
// Execute the vanilla terrain setup / culling routines using our shadow frustum.
mc.renderGlobal.clipRenderersByFrustum(terrainFrustumHolder.getFrustum(), playerCamera.getPartialTicks());
@@ -17,6 +17,8 @@ sodium.options.pages.appearance=Appearance
sodium.options.pages.text=Text Effects
sodium.options.view_distance.tooltip=The render distance controls how far away terrain will be rendered. Shorter distances mean that less terrain will be rendered, improving frame rates.
sodium.options.simulation_distance.tooltip=The simulation distance controls how far away terrain and entities will be loaded and ticked. Shorter distances can reduce the internal server's load and may improve frame rates.
sodium.options.cc.vertical_view_distance.name=Vertical View Distance
sodium.options.cc.vertical_view_distance.tooltip=The vertical view distance controls the vertical range within which cubes will be loaded, centered on the player. Shorter distances mean that less terrain will be rendered, improving frame rates.
sodium.options.brightness.tooltip=Controls the brightness (gamma) of the game.
sodium.options.clouds.name=Clouds
sodium.options.clouds.tooltip=Controls whether or not clouds will be visible.
@@ -1,12 +1,12 @@
#version 330 core
in vec2 v_UV;
in float v_FogCoord;
in vec3 v_EyePos;
in float v_Factor;
uniform sampler2D u_Tex;
uniform vec4 u_ColorMult;
uniform vec4 u_FogParams; // x: -1/(end-start), y: end/(end-start)
uniform vec4 u_FogParams; // linear: x=-1/(end-start), y=end/(end-start); exp/exp2: z=density; w=mode (0=linear, 1=exp, 2=exp2)
uniform vec4 u_FogColor;
uniform bool u_FogEnabled;
@@ -19,8 +19,17 @@ void main() {
color.rgb *= v_Factor;
if (u_FogEnabled) {
float f = clamp(v_FogCoord * u_FogParams.x + u_FogParams.y, 0.0, 1.0);
color.rgb = mix(u_FogColor.rgb, color.rgb, f);
float fogCoord = length(v_EyePos);
float f;
if (u_FogParams.w == 0.0) {
f = fogCoord * u_FogParams.x + u_FogParams.y;
} else if (u_FogParams.w == 1.0) {
f = exp(-u_FogParams.z * fogCoord);
} else {
float d = u_FogParams.z * fogCoord;
f = exp(-d * d);
}
color.rgb = mix(u_FogColor.rgb, color.rgb, clamp(f, 0.0, 1.0));
}
fragColor = color;
@@ -5,14 +5,15 @@ layout(location = 2) in vec2 a_UV;
layout(location = 5) in float a_Factor;
uniform mat4 u_MVPMatrix;
uniform mat4 u_MVMatrix;
out vec2 v_UV;
out float v_FogCoord;
out vec3 v_EyePos;
out float v_Factor;
void main() {
gl_Position = u_MVPMatrix * vec4(a_Position, 1.0);
v_UV = a_UV;
v_FogCoord = length(a_Position.xz);
v_EyePos = (u_MVMatrix * vec4(a_Position, 1.0)).xyz;
v_Factor = a_Factor;
}
@@ -13,6 +13,7 @@ import net.minecraft.client.renderer.entity.RendererLivingEntity;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
@@ -22,7 +23,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* Single-pass damage-overlay tint, modeled on modern Minecraft's OverlayTexture.
* The tint is applied only while the main body model renders.
*/
@Mixin(RendererLivingEntity.class)
public class MixinRendererLivingEntity_OverlayTint {
@@ -62,13 +62,20 @@ public class MixinRendererLivingEntity_OverlayTint {
@Inject(
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderModel(Lnet/minecraft/entity/EntityLivingBase;FFFFFF)V",
shift = At.Shift.AFTER),
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderEquippedItems(Lnet/minecraft/entity/EntityLivingBase;F)V"),
require = 1, expect = 1
)
private void angelica$clearBodyTint(EntityLivingBase entity, double x, double y, double z,
float yaw, float partialTick, CallbackInfo ci) {
angelica$clearOverlayColor();
private void angelica$teardownOverlay(EntityLivingBase entity, double x, double y, double z,
float yaw, float partialTick, CallbackInfo ci) {
if (angelica$ffpOverlayActive) {
GLStateManager.setOverlayColor(0.0F, 0.0F, 0.0F, 0.0F);
angelica$ffpOverlayActive = false;
}
if (angelica$shaderEntityColorActive) {
// Don't tint held items
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0.0F, 0.0F, 0.0F, 0.0F);
angelica$shaderEntityColorActive = false;
}
}
/**
@@ -91,17 +98,45 @@ public class MixinRendererLivingEntity_OverlayTint {
original.call(model, entity, p1, p2, p3, p4, p5, p6);
}
/**
* Skip the overlay on additive passes (emissive eyes, enchantment glint).
*/
@WrapOperation(
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V"),
slice = @Slice(
from = @At(value = "INVOKE",
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;shouldRenderPass(Lnet/minecraft/entity/EntityLivingBase;IF)I"),
to = @At(value = "INVOKE",
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderEquippedItems(Lnet/minecraft/entity/EntityLivingBase;F)V")
),
require = 1, expect = 1
)
private void angelica$untintAdditivePass(ModelBase model, Entity entity,
float p1, float p2, float p3, float p4, float p5, float p6, Operation<Void> original) {
if (!angelica$skipReRender || !angelica$isAdditivePass()) {
original.call(model, entity, p1, p2, p3, p4, p5, p6);
return;
}
if (angelica$ffpOverlayActive) {
GLStateManager.setOverlayColor(0.0F, 0.0F, 0.0F, 0.0F);
original.call(model, entity, p1, p2, p3, p4, p5, p6);
GLStateManager.setOverlayColor(1.0F, 0.0F, 0.0F, ANGELICA$RED_MIX);
} else if (angelica$shaderEntityColorActive) {
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0.0F, 0.0F, 0.0F, 0.0F);
original.call(model, entity, p1, p2, p3, p4, p5, p6);
CapturedRenderingState.INSTANCE.setCurrentEntityColor(1.0F, 0.0F, 0.0F, ANGELICA$RED_MIX);
} else {
original.call(model, entity, p1, p2, p3, p4, p5, p6);
}
}
@Inject(
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
at = @At("RETURN")
)
private void angelica$reset(CallbackInfo ci) {
angelica$clearOverlayColor();
angelica$skipReRender = false;
}
@Unique
private void angelica$clearOverlayColor() {
if (angelica$ffpOverlayActive) {
GLStateManager.setOverlayColor(0.0F, 0.0F, 0.0F, 0.0F);
angelica$ffpOverlayActive = false;
@@ -110,6 +145,15 @@ public class MixinRendererLivingEntity_OverlayTint {
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0.0F, 0.0F, 0.0F, 0.0F);
angelica$shaderEntityColorActive = false;
}
angelica$skipReRender = false;
}
@Unique
private static boolean angelica$isAdditivePass() {
if (!GLStateManager.glIsEnabled(GL11.GL_BLEND)) return false;
if (GLStateManager.glGetInteger(GL14.GL_BLEND_DST_RGB) != GL11.GL_ONE) return false;
final int src = GLStateManager.glGetInteger(GL14.GL_BLEND_SRC_RGB);
return src == GL11.GL_ONE || src == GL11.GL_SRC_COLOR;
}
@Unique
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.mixins.interfaces.ClippingHelperExt;
import net.minecraft.client.renderer.culling.ClippingHelper;
import org.joml.FrustumIntersection;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
@@ -18,17 +19,24 @@ public class MixinClippingHelper implements ClippingHelperExt {
@Unique private final FrustumIntersection celeritas$frustumIntersection = new FrustumIntersection();
@Unique private final Matrix4f celeritas$projMatrix = new Matrix4f();
@Unique private final Matrix4f celeritas$mvMatrix = new Matrix4f();
@Unique private final Matrix4f celeritas$combined = new Matrix4f();
@Override
public FrustumIntersection celeritas$getFrustumIntersection() {
return this.celeritas$frustumIntersection;
}
@Override
public Matrix4fc celeritas$getCombinedMatrix() {
return this.celeritas$combined;
}
@Override
public void celeritas$updateFrustumIntersection() {
this.celeritas$projMatrix.set(this.projectionMatrix);
this.celeritas$mvMatrix.set(this.modelviewMatrix);
this.celeritas$frustumIntersection.set(this.celeritas$projMatrix.mul(this.celeritas$mvMatrix));
this.celeritas$combined.set(this.celeritas$projMatrix).mul(this.celeritas$mvMatrix);
this.celeritas$frustumIntersection.set(this.celeritas$combined, false);
}
/**
@@ -5,23 +5,48 @@ import net.minecraft.client.renderer.culling.ClippingHelper;
import net.minecraft.client.renderer.culling.Frustrum;
import org.embeddedt.embeddium.impl.render.viewport.Viewport;
import org.embeddedt.embeddium.impl.render.viewport.ViewportProvider;
import org.joml.FrustumIntersection;
import org.joml.Vector3d;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Frustrum.class)
public abstract class MixinFrustrum implements ViewportProvider {
@Shadow @Final private ClippingHelper clippingHelper;
@Shadow private double xPosition, yPosition, zPosition;
@Unique private final FrustumIntersection celeritas$snapshot = new FrustumIntersection();
@Inject(method = "<init>", at = @At("RETURN"))
private void celeritas$snapshotFrustum(CallbackInfo ci) {
this.celeritas$snapshot.set(((ClippingHelperExt) this.clippingHelper).celeritas$getCombinedMatrix(), false);
}
@Override
public Viewport sodium$createViewport() {
// Shadow frustum subclasses override this via their own ViewportProvider impl.
// This default handles vanilla Frustrum only.
// This default handles vanilla Frustrum only, using the per-instance snapshot
return new Viewport(
((ClippingHelperExt) this.clippingHelper).celeritas$getFrustumIntersection()::testAab,
this.celeritas$snapshot::testAab,
new Vector3d(this.xPosition, this.yPosition, this.zPosition)
);
}
@Overwrite
public boolean isBoxInFrustum(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
if (Double.isInfinite(minX) || Double.isInfinite(minY) || Double.isInfinite(minZ)
|| Double.isInfinite(maxX) || Double.isInfinite(maxY) || Double.isInfinite(maxZ)) {
return true;
}
return this.celeritas$snapshot.testAab(
(float) (minX - this.xPosition), (float) (minY - this.yPosition), (float) (minZ - this.zPosition),
(float) (maxX - this.xPosition), (float) (maxY - this.yPosition), (float) (maxZ - this.zPosition)
);
}
}
@@ -1,23 +0,0 @@
package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTracker;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(ChunkTracker.class)
public interface ChunkTrackerAccessor extends com.gtnewhorizons.angelica.mixins.interfaces.ChunkTrackerAccessor {
@Accessor("chunkStatus")
Long2IntOpenHashMap angelica$getChunkStatus();
@Accessor("chunkReady")
LongOpenHashSet angelica$getChunkReady();
@Accessor("unloadQueue")
LongSet angelica$getUnloadQueue();
@Accessor("loadQueue")
LongSet angelica$getLoadQueue();
}
@@ -2,11 +2,6 @@ package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain;
import java.util.Map;
import com.gtnewhorizon.gtnhlib.hash.Fnv1a64;
import com.gtnewhorizons.angelica.event.ChunkBiomeDataChangedEvent;
import com.gtnewhorizons.angelica.mixins.interfaces.IChunkTileEntityMapHolder;
import com.gtnewhorizons.angelica.rendering.RenderThreadContext;
import com.gtnewhorizons.angelica.utils.ConcurrentTileEntityMap;
import net.minecraft.block.Block;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.ChunkPosition;
@@ -25,10 +20,17 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.gtnewhorizon.gtnhlib.hash.Fnv1a64;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.event.ChunkBiomeDataChangedEvent;
import com.gtnewhorizons.angelica.mixins.interfaces.IChunkTileEntityMapHolder;
import com.gtnewhorizons.angelica.rendering.RenderThreadContext;
import com.gtnewhorizons.angelica.utils.ConcurrentTileEntityMap;
@Mixin(Chunk.class)
public abstract class MixinChunk implements IChunkTileEntityMapHolder {
@Shadow public Map<ChunkPosition, TileEntity> chunkTileEntityMap = new ConcurrentTileEntityMap();
@Shadow public Map<ChunkPosition, TileEntity> chunkTileEntityMap;
@Shadow public World worldObj;
@Shadow private ExtendedBlockStorage[] storageArrays;
@Final @Shadow public int xPosition;
@@ -41,6 +43,16 @@ public abstract class MixinChunk implements IChunkTileEntityMapHolder {
@Unique private long angelica$biomeHash;
@Unique private boolean angelica$biomeHashValid;
@Inject(method = "<init>", at = @At("TAIL"))
private void angelica$wrapTEMapIfNeeded(CallbackInfo ci) {
// Wrap the cubic chunks map, since it uses a custom map
if (ModStatus.isCubicChunksLoaded) {
this.chunkTileEntityMap = new ConcurrentTileEntityMap(this.chunkTileEntityMap);
} else {
this.chunkTileEntityMap = new ConcurrentTileEntityMap();
}
}
@Override
public ConcurrentTileEntityMap angelica$getConcurrentTEMap() {
return (ConcurrentTileEntityMap) this.chunkTileEntityMap;
@@ -1,19 +1,22 @@
package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain;
import com.gtnewhorizons.angelica.rendering.celeritas.AngelicaChunkTracker;
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
import com.gtnewhorizons.angelica.rendering.celeritas.WorldClientExtension;
import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.world.IBlockAccess;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTracker;
import org.embeddedt.embeddium.impl.render.chunk.map.ChunkTrackerHolder;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.rendering.celeritas.AngelicaChunkTracker;
import com.gtnewhorizons.angelica.rendering.celeritas.CubeStatusTracker;
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
import com.gtnewhorizons.angelica.rendering.celeritas.WorldClientExtension;
@Mixin(WorldClient.class)
public class MixinWorldClient implements ChunkTrackerHolder, WorldClientExtension {
@Unique private final ChunkTracker celeritas$tracker = new AngelicaChunkTracker();
@Unique private final ChunkTracker celeritas$tracker = ModStatus.isCubicChunksLoaded ? new CubeStatusTracker() : new AngelicaChunkTracker();
@Unique private final SmoothBiomeColorCache celeritas$smoothBiomeColorCache = new SmoothBiomeColorCache((IBlockAccess) this);
@Override
@@ -1,5 +1,7 @@
package com.gtnewhorizons.angelica.mixins.early.notfine.clouds;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.render.CloudRenderer;
import jss.notfine.core.SettingsManager;
import net.minecraft.client.renderer.EntityRenderer;
import org.spongepowered.asm.mixin.Mixin;
@@ -12,7 +14,11 @@ import org.spongepowered.asm.mixin.injection.ModifyConstant;
public abstract class MixinEntityRenderer {
@ModifyArg(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lorg/lwjgl/util/glu/Project;gluPerspective(FFFF)V", remap = false), index = 3)
private float notFine$modifyFarPlane(float original) {
return Math.max(original, SettingsManager.minimumFarPlaneDistance);
float required = SettingsManager.minimumFarPlaneDistance;
if (AngelicaConfig.enableVBOClouds) {
required = Math.max(required, CloudRenderer.getCloudRenderer().getRequiredFarPlaneDistance());
}
return Math.max(original, required);
}
}
@@ -21,6 +21,7 @@ import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.IIcon;
import net.minecraft.world.IBlockAccess;
import net.minecraftforge.common.util.ForgeDirection;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@@ -185,7 +186,7 @@ public abstract class MixinRenderBlocks {
public IBlockAccess blockAccess;
@Unique
private void angelica$handleCompactCtmFace(IIcon icon, CallbackInfo ci) {
private void angelica$handleCompactCtmFace(IIcon icon, ForgeDirection direction, CallbackInfo ci) {
CTMUtils.CTMCompactContext ctx = CTMUtils.getCurrentCompact();
if (ctx == null) {
return;
@@ -197,39 +198,39 @@ public abstract class MixinRenderBlocks {
return;
}
if (processor.processFace((RenderBlocks)(Object)this, renderBlockState, icon)) {
if (processor.processFace((RenderBlocks)(Object)this, renderBlockState, icon, direction.ordinal())) {
ci.cancel();
}
}
@Inject(method = "renderFaceYNeg", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceYNeg(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
angelica$handleCompactCtmFace(icon, ci);
angelica$handleCompactCtmFace(icon, ForgeDirection.DOWN, ci);
}
@Inject(method = "renderFaceYPos", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceYPos(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
angelica$handleCompactCtmFace(icon, ci);
angelica$handleCompactCtmFace(icon, ForgeDirection.UP, ci);
}
@Inject(method = "renderFaceZNeg", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceZNeg(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
angelica$handleCompactCtmFace(icon, ci);
angelica$handleCompactCtmFace(icon, ForgeDirection.NORTH, ci);
}
@Inject(method = "renderFaceZPos", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceZPos(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
angelica$handleCompactCtmFace(icon, ci);
angelica$handleCompactCtmFace(icon, ForgeDirection.SOUTH, ci);
}
@Inject(method = "renderFaceXNeg", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceXNeg(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
angelica$handleCompactCtmFace(icon, ci);
angelica$handleCompactCtmFace(icon, ForgeDirection.WEST, ci);
}
@Inject(method = "renderFaceXPos", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceXPos(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
angelica$handleCompactCtmFace(icon, ci);
angelica$handleCompactCtmFace(icon, ForgeDirection.EAST, ci);
}
@Inject(method = "renderStandardBlock(Lnet/minecraft/block/Block;III)Z", at = @At("RETURN"))
@@ -0,0 +1,13 @@
package com.gtnewhorizons.angelica.mixins.early.shaders;
import net.minecraft.client.model.ModelBox;
import net.minecraft.client.model.TexturedQuad;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(ModelBox.class)
public interface AccessorModelBox {
@Accessor("quadList")
TexturedQuad[] angelica$getQuadList();
}
@@ -0,0 +1,90 @@
package com.gtnewhorizons.angelica.mixins.early.shaders;
import com.gtnewhorizons.angelica.rendering.PlayerReflectionCapture;
import net.minecraft.client.model.ModelBiped;
import net.minecraft.client.model.ModelBox;
import net.minecraft.client.model.ModelRenderer;
import net.minecraft.client.model.PositionTextureVertex;
import net.minecraft.client.model.TexturedQuad;
import net.minecraft.entity.Entity;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.Unique;
import java.util.Arrays;
@Mixin(ModelBiped.class)
public abstract class MixinModelBiped {
@Unique private static final Matrix4f angelica$mat = new Matrix4f();
@Unique private static final Vector3f angelica$vec = new Vector3f();
@Inject(method = "render(Lnet/minecraft/entity/Entity;FFFFFF)V", at = @At("RETURN"))
private void angelica$capturePlayerReflection(Entity entity, float limbSwing, float limbSwingAmount,
float ageInTicks, float netHeadYaw, float headPitch, float scale, CallbackInfo ci) {
if (!PlayerReflectionCapture.shouldCapture()) return;
if ((Object) this != PlayerReflectionCapture.getTarget()) return;
if (entity != PlayerReflectionCapture.getTargetEntity()) return;
final ModelBiped model = (ModelBiped) (Object) this;
final float[] data = PlayerReflectionCapture.vertexScratch();
// Six 48-vertex blocks = body parts, each base box + overlay box
int o = 0;
o = angelica$emitBox(model.bipedHead, data, o, scale);
o = angelica$emitBox(model.bipedHeadwear, data, o, scale);
o = angelica$emitBox(model.bipedRightArm, data, o, scale);
o = angelica$emitBox(model.bipedRightArm, data, o, scale);
o = angelica$emitBox(model.bipedLeftLeg, data, o, scale);
o = angelica$emitBox(model.bipedLeftLeg, data, o, scale);
o = angelica$emitBox(model.bipedLeftArm, data, o, scale);
o = angelica$emitBox(model.bipedLeftArm, data, o, scale);
o = angelica$emitBox(model.bipedRightLeg, data, o, scale);
o = angelica$emitBox(model.bipedRightLeg, data, o, scale);
o = angelica$emitBox(model.bipedBody, data, o, scale);
angelica$emitBox(model.bipedBody, data, o, scale);
PlayerReflectionCapture.drawPlayerCapture(data);
}
private static int angelica$emitBox(ModelRenderer part, float[] out, int o, float scale) {
if (part == null || part.cubeList == null || part.cubeList.isEmpty()) {
final int end = o + 24 * PlayerReflectionCapture.FLOATS_PER_VERTEX;
Arrays.fill(out, o, end, 0.0f);
return end;
}
final Matrix4f m = angelica$mat.identity();
m.translate(part.offsetX, part.offsetY, part.offsetZ);
m.translate(part.rotationPointX * scale, part.rotationPointY * scale, part.rotationPointZ * scale);
if (part.rotateAngleZ != 0.0f) m.rotateZ(part.rotateAngleZ);
if (part.rotateAngleY != 0.0f) m.rotateY(part.rotateAngleY);
if (part.rotateAngleX != 0.0f) m.rotateX(part.rotateAngleX);
final ModelBox box = part.cubeList.getFirst();
final TexturedQuad[] quads = ((AccessorModelBox) (Object) box).angelica$getQuadList();
final Vector3f v = angelica$vec;
for (int q = 0; q < 6; q++) {
final PositionTextureVertex[] corners = quads[q].vertexPositions;
for (int c = 0; c < 4; c++) {
final PositionTextureVertex corner = corners[c];
v.set((float) corner.vector3D.xCoord * scale,
(float) corner.vector3D.yCoord * scale,
(float) corner.vector3D.zCoord * scale);
m.transformPosition(v);
out[o++] = v.x;
out[o++] = v.y;
out[o++] = v.z;
out[o++] = corner.texturePositionX;
out[o++] = corner.texturePositionY * 0.5f;
}
}
return o;
}
}
+2 -2
View File
@@ -33,8 +33,8 @@ dependencies {
}
shadowImplementation("org.antlr:antlr4-runtime:4.13.2")
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.4-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.5-GTNH) { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.3-GTNH") { transitive = false }
runtimeOnlyNonPublishable("org.embeddedt.celeritas:celeritas-common:2.5.4-GTNH") { transitive = false }
runtimeOnlyNonPublishable("org.embeddedt.celeritas:celeritas-common:2.5.5-GTNH") { transitive = false }
runtimeOnlyNonPublishable("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.3-GTNH") { transitive = false }
}