Compare commits

..
8 Commits
61 changed files with 2058 additions and 479 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() {
@@ -11,6 +11,7 @@ import net.coderbot.iris.gui.screen.ShaderPackScreen;
import net.coderbot.iris.shaderpack.ShaderPack;
import net.coderbot.iris.shaderpack.option.menu.OptionMenuContainer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.client.renderer.Tessellator;
import java.util.ArrayList;
@@ -24,6 +25,12 @@ public class ShaderPackOptionList extends IrisGuiSlot {
private OptionMenuContainer container;
private final List<BaseEntry> entries = new ArrayList<>();
@Getter
private boolean searchModeActive = false;
private String typedSearchQuery = "";
private int reservedLeftWidth = 48; // Pixels reserved on the left of the header row for the search/clear button + gap.
private GuiTextField searchBox = null;
public ShaderPackOptionList(ShaderPackScreen screen, NavigationController navigation, ShaderPack pack, Minecraft client, int width, int height, int top, int bottom, int left, int right) {
super(client, width, height, top, bottom, 24);
this.navigation = navigation;
@@ -36,6 +43,74 @@ public class ShaderPackOptionList extends IrisGuiSlot {
this.container = pack.getMenuContainer();
}
/** Applies {@code query} to the option container and rebuilds the list rows. */
public void updateSearchQuery(String query) {
typedSearchQuery = query != null ? query : "";
if (this.container != null) {
this.container.setSearchQuery(query);
}
this.rebuild();
}
/** Resets all search state and clears the active filter without rebuilding entries. */
public void disableSearchMode() {
searchModeActive = false;
typedSearchQuery = "";
searchBox = null;
if (this.container != null) {
this.container.setSearchQuery(null);
}
}
public void disableSearchModeAndRebuild() {
disableSearchMode();
this.rebuild();
}
public void enableSearchModeAndRebuild() {
searchModeActive = true;
this.rebuild(); // First rebuild so that the button width is the current one
int listWidth = getListWidth();
int entryX = this.width / 2 - listWidth / 2;
int boxX = entryX + reservedLeftWidth + 3;
int boxY = this.top + 5; // got figured out by trial and error and works, don't question
int boxW = Math.max(40, listWidth - reservedLeftWidth - 5);
this.searchBox = new GuiTextField(Minecraft.getMinecraft().fontRenderer, boxX, boxY, boxW, 14);
this.searchBox.setMaxStringLength(64);
this.searchBox.setFocused(true);
}
/** True when the navigation stack is non-empty (a sub-screen is open). */
public boolean isOnSubScreen() {
return navigation != null && navigation.getCurrentScreen() != null;
}
public void setReservedLeftWidth(int w) {
reservedLeftWidth = Math.max(0, w);
}
/**
* Routes a keystroke into the live search text field, then propagates any text change
* to the option container and rebuilds the filtered list.
*/
public void handleSearchKeyTyped(char typedChar, int keyCode) {
if (searchBox == null || !searchModeActive) return;
searchBox.textboxKeyTyped(typedChar, keyCode);
String newText = searchBox.getText();
if (!newText.equals(typedSearchQuery)) {
updateSearchQuery(newText);
}
}
/** Draws the search text box overlay above the header row */
public void drawSearchBox() {
if (searchBox == null || !searchModeActive) return;
// Hide when the header has scrolled out of view
if (this.amountScrolled > 0.5f) return;
searchBox.drawTextBox();
}
public void rebuild() {
this.entries.clear();
this.amountScrolled = 0;
@@ -56,7 +131,7 @@ public class ShaderPackOptionList extends IrisGuiSlot {
}
public void addHeader(String text, boolean backButton) {
this.addEntry(new HeaderEntry(this.screen, this.navigation, text, backButton));
this.addEntry(new HeaderEntry(this.screen, this.navigation, text, backButton, this));
}
public void addWidgets(int columns, List<AbstractElementWidget<?>> elements) {
@@ -86,6 +161,15 @@ public class ShaderPackOptionList extends IrisGuiSlot {
return entries.size();
}
@Override
public boolean mouseClicked(int mouseX, int mouseY, int mouseButton) {
// Pass the click to the search box first so it can update its focus state
if (searchModeActive && searchBox != null && amountScrolled <= 0.5f) {
searchBox.mouseClicked(mouseX, mouseY, mouseButton);
}
return super.mouseClicked(mouseX, mouseY, mouseButton);
}
@Override
protected boolean elementClicked(int index, boolean doubleClick, int mouseX, int mouseY, int mouseButton) {
final BaseEntry entry = this.entries.get(index);
@@ -14,6 +14,7 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.MathHelper;
import java.io.IOException;
import java.nio.file.FileSystems;
@@ -142,10 +143,15 @@ public class ShaderPackSelectionList extends IrisGuiSlot {
topButtonRow.allowEnableShadersButton = !names.isEmpty();
int index = 0;
String selectedName = Iris.getIrisConfig().getShaderPackName().orElse(null);
int targetSelectionIndex = -1;
for (String name : names) {
this.addPackEntry(index, name);
if (name.equals(selectedName)) {
targetSelectionIndex = index;
}
index++;
addPackEntry(index, name);
}
// Try not to gaslight users in case they swap back and forth from lwjgl2 and lwjgl3
@@ -153,6 +159,29 @@ public class ShaderPackSelectionList extends IrisGuiSlot {
? "pack.iris.list.label"
: "pack.iris.list.label.lwjgl2";
addLabelEntries(EnumChatFormatting.GRAY.toString() + EnumChatFormatting.ITALIC + I18n.format(footerKey));
if (targetSelectionIndex != -1) {
scrollToIndex(targetSelectionIndex);
}
}
/**
* Scrolls the list to center the entry at {@code targetIndex} in the viewport.
*/
public void scrollToIndex(int targetIndex) {
if (targetIndex < 0 || targetIndex >= this.getSize()) {
return;
}
this.elementClicked(targetIndex, false, 0, 0);
this.selectedElement = targetIndex;
int viewportCenterY = (this.bottom - this.top) / 2;
int itemCenterPos = (targetIndex * this.slotHeight) + this.headerPadding + (this.slotHeight / 2);
float targetScroll = (float) (itemCenterPos - viewportCenterY);
float maxScroll = (float) this.func_148135_f();
this.amountScrolled = MathHelper.clamp_float(targetScroll, 0.0F, maxScroll);
}
public void addPackEntry(int index, String name) {
@@ -1,9 +1,11 @@
package net.coderbot.iris.gui.element.shaderoptions;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import net.coderbot.iris.Iris;
import net.coderbot.iris.gui.GuiUtil;
import net.coderbot.iris.gui.NavigationController;
import net.coderbot.iris.gui.element.IrisElementRow;
import net.coderbot.iris.gui.element.ShaderPackOptionList;
import net.coderbot.iris.gui.screen.ShaderPackScreen;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
@@ -16,58 +18,83 @@ import org.jetbrains.annotations.Nullable;
public class HeaderEntry extends BaseEntry {
public static final String BACK_BUTTON_TEXT = EnumChatFormatting.ITALIC + "< " + I18n.format("options.iris.back");
public static final String RESET_BUTTON_TEXT_INACTIVE = EnumChatFormatting.GRAY + I18n.format("options.iris.reset");
public static final String RESET_BUTTON_TEXT_ACTIVE = EnumChatFormatting.YELLOW + I18n.format("options.iris.reset");
public static final String RESET_HOLD_SHIFT_TOOLTIP = EnumChatFormatting.BOLD + I18n.format("options.iris.reset.tooltip.holdShift");
public static final String RESET_TOOLTIP = EnumChatFormatting.RED + I18n.format("options.iris.reset.tooltip");
public static final String IMPORT_TOOLTIP = I18n.format("options.iris.importSettings.tooltip");
// .withStyle(style -> style.withColor(TextColor.fromRgb(0x4da6ff)));
public static final String EXPORT_TOOLTIP = I18n.format("options.iris.exportSettings.tooltip");
// .withStyle(style -> style.withColor(TextColor.fromRgb(0xfc7d3d)));
private static String backButtonText() { return EnumChatFormatting.ITALIC + "< " + I18n.format("options.iris.back"); }
private static String resetButtonInactive() { return EnumChatFormatting.GRAY + I18n.format("options.iris.reset"); }
private static String resetButtonActive() { return EnumChatFormatting.YELLOW + I18n.format("options.iris.reset"); }
private static String resetHoldShiftTooltip() { return EnumChatFormatting.BOLD + I18n.format("options.iris.reset.tooltip.holdShift"); }
private static String resetTooltip() { return EnumChatFormatting.RED + I18n.format("options.iris.reset.tooltip"); }
private static String importTooltip() { return I18n.format("options.iris.importSettings.tooltip"); }
private static String exportTooltip() { return I18n.format("options.iris.exportSettings.tooltip"); }
private static String searchButtonText() { return I18n.format("options.iris.search.button"); }
private static String clearButtonText() { return I18n.format("options.iris.clear.button"); }
private static String searchTooltip() { return I18n.format("options.iris.search.tooltip"); }
private static String clearTooltip() { return I18n.format("options.iris.clear.tooltip"); }
private static final int MIN_SIDE_BUTTON_WIDTH = 42;
private static final int BUTTON_HEIGHT = 16;
private static final int SEARCH_BOX_GAP = 3;
private final ShaderPackScreen screen;
private final ShaderPackOptionList optionList;
private final @Nullable IrisElementRow backButton;
/** Width of the left-side button, used to clip the title text away from it. */
private final int leftButtonWidth;
private final IrisElementRow utilityButtons = new IrisElementRow();
private final IrisElementRow.TextButtonElement resetButton;
private final IrisElementRow.IconButtonElement importButton;
private final IrisElementRow.IconButtonElement exportButton;
private final String text;
public HeaderEntry(ShaderPackScreen screen, NavigationController navigation, String text, boolean hasBackButton) {
/** Non-null only on the main-screen header (search/clear toggle). */
private final @Nullable IrisElementRow.TextButtonElement searchToggleButton;
public HeaderEntry(ShaderPackScreen screen, NavigationController navigation, String text, boolean hasBackButton, ShaderPackOptionList optionList) {
super(navigation);
this.optionList = optionList;
this.screen = screen;
this.text = text;
IrisElementRow.TextButtonElement toggleRef = null;
if (hasBackButton) {
this.backButton = new IrisElementRow().add(new IrisElementRow.TextButtonElement(BACK_BUTTON_TEXT, this::backButtonClicked),
Math.max(MIN_SIDE_BUTTON_WIDTH, Minecraft.getMinecraft().fontRenderer.getStringWidth(BACK_BUTTON_TEXT) + 8));
// Sub-screen: navigating here while search is active → force-disable so the search box disappears
if (optionList.isSearchModeActive()) {
optionList.disableSearchMode();
}
String backText = backButtonText();
int w = Math.max(MIN_SIDE_BUTTON_WIDTH, Minecraft.getMinecraft().fontRenderer.getStringWidth(backText) + 8);
this.backButton = new IrisElementRow().add(new IrisElementRow.TextButtonElement(backText, this::backButtonClicked), w);
this.leftButtonWidth = w;
} else {
this.backButton = null;
// Main screen: left button is the search/clear toggle
String buttonText = optionList.isSearchModeActive() ? clearButtonText() : searchButtonText();
IrisElementRow.TextButtonElement searchBtn = new IrisElementRow.TextButtonElement(buttonText, this::searchButtonClicked);
int w = Math.max(MIN_SIDE_BUTTON_WIDTH, Minecraft.getMinecraft().fontRenderer.getStringWidth(buttonText) + 8);
this.backButton = new IrisElementRow().add(searchBtn, w);
toggleRef = searchBtn;
this.leftButtonWidth = w;
optionList.setReservedLeftWidth(w + SEARCH_BOX_GAP);
}
this.resetButton = new IrisElementRow.TextButtonElement(RESET_BUTTON_TEXT_INACTIVE, this::resetButtonClicked);
this.searchToggleButton = toggleRef;
this.resetButton = new IrisElementRow.TextButtonElement(resetButtonInactive(), this::resetButtonClicked);
this.importButton = new IrisElementRow.IconButtonElement(GuiUtil.Icon.IMPORT, GuiUtil.Icon.IMPORT_COLORED, this::importSettingsButtonClicked);
this.exportButton = new IrisElementRow.IconButtonElement(GuiUtil.Icon.EXPORT, GuiUtil.Icon.EXPORT_COLORED, this::exportSettingsButtonClicked);
this.utilityButtons.add(this.importButton, 15).add(this.exportButton, 15)
.add(this.resetButton, Math.max(MIN_SIDE_BUTTON_WIDTH, Minecraft.getMinecraft().fontRenderer.getStringWidth((RESET_BUTTON_TEXT_INACTIVE) + 8)));
this.screen = screen;
this.text = text;
.add(this.resetButton, Math.max(MIN_SIDE_BUTTON_WIDTH, Minecraft.getMinecraft().fontRenderer.getStringWidth(resetButtonInactive()) + 8));
}
@Override
public void drawEntry(ShaderPackScreen screen, int index, int x, int y, int slotWidth, int slotHeight, Tessellator tessellator, int mouseX, int mouseY, boolean isMouseOver) {
Gui.drawRect(x - 3, (y + slotHeight) - 3, x + slotWidth, (y + slotHeight) - 2, 0x66BEBEBE);
// Gui.drawRect leaves GL color set to the rect's color; reset to white so button textures aren't tinted.
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
final int tickDelta = 0;
final FontRenderer font = Minecraft.getMinecraft().fontRenderer;
GuiUtil.drawScrollingText(font, text, x + (int) (slotWidth * 0.5), x + 5,
((x + slotWidth) - 10) - this.utilityButtons.getWidth(), y + 5, y + 15, 0xFFFFFF);
GuiUtil.bindIrisWidgetsTexture();
// Draw back button if present
@@ -75,26 +102,42 @@ public class HeaderEntry extends BaseEntry {
backButton.drawScreen(x, y, BUTTON_HEIGHT, mouseX, mouseY, tickDelta, isMouseOver);
}
if (optionList.isSearchModeActive() && searchToggleButton != null) {
if (searchToggleButton.isHovered()) {
ShaderPackScreen.TOP_LAYER_RENDER_QUEUE.add(() -> GuiUtil.drawTextPanel(font, clearTooltip(), mouseX, mouseY - 16));
}
return;
}
GuiUtil.drawScrollingText(font, text, x + (int) (slotWidth * 0.5),
x + leftButtonWidth + 5,
((x + slotWidth) - 10) - this.utilityButtons.getWidth(),
y + 5, y + 15, 0xFFFFFF);
final boolean shiftDown = GuiScreen.isShiftKeyDown();
// Set the appearance of the reset button
this.resetButton.disabled = !shiftDown;
this.resetButton.text = shiftDown ? RESET_BUTTON_TEXT_ACTIVE : RESET_BUTTON_TEXT_INACTIVE;
this.resetButton.text = shiftDown ? resetButtonActive() : resetButtonInactive();
// Draw the utility buttons
this.utilityButtons.renderRightAligned((x + slotWidth) - 3, y, BUTTON_HEIGHT, mouseX, mouseY, tickDelta, isMouseOver);
// Draw the reset button's tooltip
if (this.resetButton.isHovered()) {
final String tooltip = shiftDown ? RESET_TOOLTIP : RESET_HOLD_SHIFT_TOOLTIP;
final String tooltip = shiftDown ? resetTooltip() : resetHoldShiftTooltip();
queueBottomRightAnchoredTooltip(mouseX, mouseY, font, tooltip);
}
// Draw the import/export button tooltips
if (this.importButton.isHovered()) {
queueBottomRightAnchoredTooltip(mouseX, mouseY, font, IMPORT_TOOLTIP);
queueBottomRightAnchoredTooltip(mouseX, mouseY, font, importTooltip());
}
if (this.exportButton.isHovered()) {
queueBottomRightAnchoredTooltip(mouseX, mouseY, font, EXPORT_TOOLTIP);
queueBottomRightAnchoredTooltip(mouseX, mouseY, font, exportTooltip());
}
// Search-button tooltip
if (searchToggleButton != null && searchToggleButton.isHovered()) {
ShaderPackScreen.TOP_LAYER_RENDER_QUEUE.add(() -> GuiUtil.drawTextPanel(font, searchTooltip(), mouseX, mouseY - 16));
}
}
@@ -104,6 +147,9 @@ public class HeaderEntry extends BaseEntry {
@Override
public boolean mouseClicked(int mouseX, int mouseY, int button) {
if (optionList.isSearchModeActive()) {
return backButton != null && backButton.mouseClicked(mouseX, mouseY, button);
}
final boolean backButtonResult = backButton != null && backButton.mouseClicked(mouseX, mouseY, button);
final boolean utilButtonResult = utilityButtons.mouseClicked(mouseX, mouseY, button);
@@ -122,6 +168,16 @@ public class HeaderEntry extends BaseEntry {
return true;
}
private boolean searchButtonClicked(IrisElementRow.TextButtonElement button) {
GuiUtil.playButtonClickSound();
if (optionList.isSearchModeActive()) {
optionList.disableSearchModeAndRebuild();
} else {
optionList.enableSearchModeAndRebuild();
}
return true;
}
private boolean resetButtonClicked(IrisElementRow.TextButtonElement button) {
if (GuiScreen.isShiftKeyDown()) {
Iris.resetShaderPackOptionsOnNextReload();
@@ -10,9 +10,10 @@ import net.minecraft.client.resources.I18n;
import java.util.function.Function;
public class TopButtonRowEntry extends BaseEntry {
private static final String NONE_PRESENT_LABEL = I18n.format("options.iris.shaders.nonePresent"); //.withStyle(ChatFormatting.GRAY);
private static final String SHADERS_DISABLED_LABEL = I18n.format("options.iris.shaders.disabled");
private static final String SHADERS_ENABLED_LABEL = I18n.format("options.iris.shaders.enabled");
private static String nonePresentLabel() { return I18n.format("options.iris.shaders.nonePresent"); }
private static String shadersDisabledLabel() { return I18n.format("options.iris.shaders.disabled"); }
private static String shadersEnabledLabel() { return I18n.format("options.iris.shaders.enabled"); }
private final ShaderPackSelectionList shaderPackSelectionList;
private final IrisElementRow buttons = new IrisElementRow();
@@ -54,7 +55,7 @@ public class TopButtonRowEntry extends BaseEntry {
}
private String getEnableDisableLabel() {
return this.allowEnableShadersButton ? this.shadersEnabled ? SHADERS_ENABLED_LABEL : SHADERS_DISABLED_LABEL : NONE_PRESENT_LABEL;
return this.allowEnableShadersButton ? this.shadersEnabled ? shadersEnabledLabel() : shadersDisabledLabel() : nonePresentLabel();
}
public boolean mouseClicked(double mouseX, double mouseY, int button) {
@@ -4,16 +4,19 @@ import net.coderbot.iris.gui.GuiUtil;
import net.coderbot.iris.gui.NavigationController;
import net.coderbot.iris.gui.screen.ShaderPackScreen;
import net.coderbot.iris.shaderpack.option.menu.OptionMenuElement;
import net.coderbot.iris.shaderpack.option.menu.OptionMenuOptionElement;
import net.coderbot.iris.shaderpack.option.menu.ShaderSearchEngine;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.EnumChatFormatting;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
public abstract class BaseOptionElementWidget<T extends OptionMenuElement> extends CommentedElementWidget<T> {
protected static final String SET_TO_DEFAULT = I18n.format("options.iris.setToDefault");
protected static String setToDefault() { return I18n.format("options.iris.setToDefault"); }
protected static final String DIVIDER = ": ";
protected String unmodifiedLabel;
@@ -101,7 +104,7 @@ public abstract class BaseOptionElementWidget<T extends OptionMenuElement> exten
protected final void tryRenderTooltip(int mouseX, int mouseY, boolean hovered) {
if (GuiScreen.isShiftKeyDown()) {
renderTooltip(SET_TO_DEFAULT, mouseX, mouseY, hovered);
renderTooltip(setToDefault(), mouseX, mouseY, hovered);
} else if (this.isLabelTrimmed && !this.screen.isDisplayingComment()) {
renderTooltip(this.unmodifiedLabel, mouseX, mouseY, hovered);
}
@@ -145,13 +148,39 @@ public abstract class BaseOptionElementWidget<T extends OptionMenuElement> exten
@Override
public Optional<String> getCommentBody() {
final String key = getCommentKey();
if (key == null) {
return Optional.empty();
Optional<String> base = Optional.ofNullable(getCommentKey()).map(I18n::format);
if (this.screen == null || !this.screen.isOptionMenuSearchActive()) {
return base;
}
final String translated = GuiUtil.translateLenient(key);
// Don't show comment boxes without a translation, otherwise you get something like option.{commentname}.comment
return translated.equals(key) ? Optional.empty() : Optional.of(translated);
String pathLabel = buildTranslatedPath();
if (pathLabel == null) {
return base;
}
String breadcrumb = EnumChatFormatting.BOLD.toString() + EnumChatFormatting.ITALIC + pathLabel + EnumChatFormatting.RESET;
if (base.isPresent()) {
return Optional.of(breadcrumb + ". " + base.get());
}
return Optional.of(breadcrumb);
}
@Nullable
private String buildTranslatedPath() {
if (!(this.element instanceof OptionMenuOptionElement)) return null;
OptionMenuOptionElement optEl = (OptionMenuOptionElement) this.element;
String rawPath = optEl.container.getOptionPath(optEl.optionId);
String[] segments = rawPath.split("/");
StringBuilder display = new StringBuilder();
for (String segment : segments) {
if ("root".equals(segment)) continue;
String translated = ShaderSearchEngine.getDisplaySettingsName(segment).replaceAll("\\s+>", "");
String label = translated.isEmpty() ? segment : translated;
if (display.length() > 0) display.append(" > ");
display.append(label);
}
return display.length() > 0 ? display.toString() : null;
}
@Override
@@ -9,10 +9,9 @@ import net.coderbot.iris.shaderpack.option.menu.OptionMenuBooleanOptionElement;
import net.minecraft.client.resources.I18n;
public class BooleanElementWidget extends BaseOptionElementWidget<OptionMenuBooleanOptionElement> {
private static final String TEXT_TRUE = I18n.format("label.iris.true");
private static final String TEXT_FALSE = I18n.format("label.iris.false");
private static final String TEXT_TRUE_DEFAULT = I18n.format("label.iris.true");
private static final String TEXT_FALSE_DEFAULT = I18n.format("label.iris.false");
private static String textTrue() { return I18n.format("label.iris.true"); }
private static String textFalse() { return I18n.format("label.iris.false"); }
private final BooleanOption option;
@@ -63,11 +62,7 @@ public class BooleanElementWidget extends BaseOptionElementWidget<OptionMenuBool
// Compatibility Mode to "On" when not needed. Now we use white for
// default to avoid giving a positive or negative connotation to a
// default value.
if (this.value == this.defaultValue) {
return this.value ? TEXT_TRUE_DEFAULT : TEXT_FALSE_DEFAULT;
}
return this.value ? TEXT_TRUE : TEXT_FALSE;
return this.value ? textTrue() : textFalse();
}
@Override
@@ -16,8 +16,9 @@ import net.minecraft.client.resources.I18n;
import java.util.Optional;
public class ProfileElementWidget extends BaseOptionElementWidget<OptionMenuProfileElement> {
private static final String PROFILE_LABEL = I18n.format("options.iris.profile");
private static final String PROFILE_CUSTOM = I18n.format("options.iris.profile.custom");
private static String profileLabel() { return I18n.format("options.iris.profile"); }
private static String profileCustom() { return I18n.format("options.iris.profile.custom"); }
private Profile next;
private Profile previous;
@@ -30,7 +31,7 @@ public class ProfileElementWidget extends BaseOptionElementWidget<OptionMenuProf
@Override
public void init(ShaderPackScreen screen, NavigationController navigation) {
super.init(screen, navigation);
this.setLabel(PROFILE_LABEL);
this.setLabel(profileLabel());
boolean secondProfileSet = ProfileElementTracker.isSecondProfileSet(this.element);
final ProfileSet profiles = this.element.profiles;
@@ -44,12 +45,12 @@ public class ProfileElementWidget extends BaseOptionElementWidget<OptionMenuProf
final Optional<String> profileName = result.current.map(p -> p.name);
String translationKey = "profile" + (secondProfileSet ? "2." : ".") + profileName.orElse("custom");
this.profileLabel = profileName.map(name -> GuiUtil.translateOrDefault(name, translationKey)).orElse(PROFILE_CUSTOM);
this.profileLabel = profileName.map(name -> GuiUtil.translateOrDefault(name, translationKey)).orElse(profileCustom());
}
@Override
public void drawScreen(int x, int y, int width, int height, int mouseX, int mouseY, float tickDelta, boolean hovered) {
this.updateRenderParams(width, width - (Minecraft.getMinecraft().fontRenderer.getStringWidth(PROFILE_LABEL) + 16));
this.updateRenderParams(width, width - (Minecraft.getMinecraft().fontRenderer.getStringWidth(profileLabel()) + 16));
this.renderOptionWithValue(x, y, width, height, hovered);
}
@@ -61,12 +62,12 @@ public class ProfileElementWidget extends BaseOptionElementWidget<OptionMenuProf
@Override
protected int getValueColor() {
return PROFILE_CUSTOM.equals(this.profileLabel) ? 0xFFFF55 : 0xFFFFFF;
return profileCustom().equals(this.profileLabel) ? 0xFFFF55 : 0xFFFFFF;
}
@Override
public Optional<String> getCommentTitle() {
return Optional.of(PROFILE_LABEL);
return Optional.of(profileLabel());
}
@Override
@@ -28,7 +28,7 @@ public class SliderElementWidget extends StringElementWidget {
}
if (GuiScreen.isShiftKeyDown()) {
renderTooltip(SET_TO_DEFAULT, mouseX, mouseY, hovered);
renderTooltip(setToDefault(), mouseX, mouseY, hovered);
} else if (!this.screen.isDisplayingComment()) {
renderTooltip(this.unmodifiedLabel, mouseX, mouseY, hovered);
}
@@ -53,8 +53,8 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
*/
public static final Set<Runnable> TOP_LAYER_RENDER_QUEUE = new HashSet<>();
private static final String SELECT_TITLE = EnumChatFormatting.GRAY.toString() + EnumChatFormatting.ITALIC + I18n.format("pack.iris.select.title");
private static final String CONFIGURE_TITLE = EnumChatFormatting.GRAY.toString() + EnumChatFormatting.ITALIC + I18n.format("pack.iris.configure.title");
private static String selectTitle() { return EnumChatFormatting.GRAY.toString() + EnumChatFormatting.ITALIC + I18n.format("pack.iris.select.title"); }
private static String configureTitle() { return EnumChatFormatting.GRAY.toString() + EnumChatFormatting.ITALIC + I18n.format("pack.iris.configure.title"); }
private static final int COMMENT_PANEL_WIDTH = 314;
private final GuiScreen parent;
@@ -104,6 +104,11 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
refreshForChangedPack();
}
/** Returns true when the option menu is open and the user has an active search query. */
public boolean isOptionMenuSearchActive() {
return this.optionMenuOpen && this.shaderOptionList != null && this.shaderOptionList.isSearchModeActive();
}
@Override
public void drawScreen(int mouseX, int mouseY, float delta) {
GLStateManager.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT);
@@ -124,6 +129,7 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
if (!this.guiHidden) {
if (optionMenuOpen && this.shaderOptionList != null) {
this.shaderOptionList.drawScreen(mouseX, mouseY, delta);
this.shaderOptionList.drawSearchBox();
} else {
this.shaderPackList.drawScreen(mouseX, mouseY, delta);
}
@@ -144,9 +150,9 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
drawCenteredString(this.fontRendererObj, notificationDialog, (int) (this.width * 0.5), 21, 0xFFFFFF);
} else {
if (optionMenuOpen) {
drawCenteredString(this.fontRendererObj, CONFIGURE_TITLE, (int) (this.width * 0.5), 21, 0xFFFFFF);
drawCenteredString(this.fontRendererObj, configureTitle(), (int) (this.width * 0.5), 21, 0xFFFFFF);
} else {
drawCenteredString(this.fontRendererObj, SELECT_TITLE, (int) (this.width * 0.5), 21, 0xFFFFFF);
drawCenteredString(this.fontRendererObj, selectTitle(), (int) (this.width * 0.5), 21, 0xFFFFFF);
}
}
@@ -207,6 +213,10 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
if (Iris.getCurrentPack().isPresent() && this.navigation != null) {
final ShaderPack currentPack = Iris.getCurrentPack().get();
if (currentPack.getMenuContainer() != null) {
currentPack.getMenuContainer().setSearchQuery(null);
}
this.shaderOptionList = new ShaderPackOptionList(this, this.navigation, currentPack, this.mc, this.width, this.height, 32, this.height - 58, 0, this.width);
this.navigation.setActiveOptionList(this.shaderOptionList);
@@ -364,6 +374,31 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
@Override
protected void keyTyped(char typedChar, int keyCode) {
// Route all input to the search box while search mode is active
if (optionMenuOpen && this.shaderOptionList != null && this.shaderOptionList.isSearchModeActive()) {
if (keyCode == Keyboard.KEY_ESCAPE) {
this.shaderOptionList.disableSearchModeAndRebuild();
return;
}
// Ctrl+F while search is open: toggle it closed
if (keyCode == Keyboard.KEY_F && GuiScreen.isCtrlKeyDown()) {
GuiUtil.playButtonClickSound();
this.shaderOptionList.disableSearchModeAndRebuild();
return;
}
this.shaderOptionList.handleSearchKeyTyped(typedChar, keyCode);
return;
}
// Ctrl+F: open search on the main option screen
if (optionMenuOpen && this.shaderOptionList != null
&& keyCode == Keyboard.KEY_F && GuiScreen.isCtrlKeyDown()) {
if (!this.shaderOptionList.isOnSubScreen()) {
GuiUtil.playButtonClickSound();
this.shaderOptionList.enableSearchModeAndRebuild();
return;
}
}
if (GuiScreen.isCtrlKeyDown() && keyCode == Keyboard.KEY_D) {
this.mc.displayGuiScreen(new GuiYesNo((result, id) -> {
Iris.setDebug(result);
@@ -496,6 +531,10 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable {
}
public void onClose() {
if (this.shaderOptionList != null) {
this.shaderOptionList.disableSearchModeAndRebuild();
}
if (!dropChanges) {
applyChanges();
} else {
@@ -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());
@@ -10,9 +10,12 @@ import net.coderbot.iris.shaderpack.option.ShaderPackOptions;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
public class OptionMenuContainer {
public final OptionMenuElementScreen mainScreen;
@@ -24,6 +27,8 @@ public class OptionMenuContainer {
private final Map<List<OptionMenuElement>, Integer> unusedOptionDumpQueue = new HashMap<>(); // Used by screens with "*" element
@Getter private final ProfileSet profiles;
@Getter private final ProfileSet profiles2;
private final List<OptionMenuElement> originalMainElements = new ArrayList<>(); // Saved snapshot of mainScreen.elements before any search filter is applied.
private final Map<String, String> cachedOptionPaths = new HashMap<>(); // Full "root/SCREEN1/SCREEN2" path for every option ID, built once after construction
public OptionMenuContainer(ShaderProperties shaderProperties, ShaderPackOptions shaderPackOptions, ProfileSet profiles) {
this.profiles = profiles;
@@ -69,6 +74,10 @@ public class OptionMenuContainer {
entry.getKey().addAll(entry.getValue(), elementsToInsert);
}
// Save the original layout and build the path cache once the full tree is constructed
this.originalMainElements.addAll(this.mainScreen.elements);
generateAllPaths();
}
// Screens will call this when they contain a "*" element, so that the list of
@@ -85,4 +94,81 @@ public class OptionMenuContainer {
unusedOptions.remove(optionId);
}
private void generateAllPaths() {
cachedOptionPaths.clear();
traverseScreen(this.mainScreen, "root", new HashSet<String>());
}
private void traverseScreen(OptionMenuElementScreen screen, String currentPath, Set<String> visited) {
if (screen == null) return;
for (OptionMenuElement element : screen.elements) {
if (element instanceof OptionMenuOptionElement optEl) {
if (optEl.optionId != null) {
cachedOptionPaths.putIfAbsent(optEl.optionId, currentPath);
}
} else if (element instanceof OptionMenuLinkElement link) {
if (link.targetScreenId != null) {
String targetId = link.targetScreenId;
if (visited.add(targetId)) {
OptionMenuElementScreen next = this.subScreens.get(targetId);
if (next != null) {
traverseScreen(next, currentPath + "/" + targetId, visited);
}
visited.remove(targetId);
}
}
}
}
}
public String getOptionPath(String optionId) {
if (optionId == null) return "root";
return cachedOptionPaths.getOrDefault(optionId, "root");
}
/**
* Filters and re-orders the main-screen options to match {@code query}, or restores the
* original layout when {@code query} is null or blank.
*/
public void setSearchQuery(String query) {
if (query == null || query.trim().isEmpty()) {
restoreOriginalLayout();
return;
}
String normalizedQuery = query.toLowerCase(Locale.ROOT).trim();
List<OptionMenuOptionElement> flatOptions = ShaderSearchEngine.getAllOptionsFlattened(usedOptionElements);
List<ShaderSearchEngine.ScoredOptionElement> scored = new ArrayList<>();
for (OptionMenuOptionElement el : flatOptions) {
int score = ShaderSearchEngine.computeMatchTier(el.optionId, normalizedQuery);
if (score > 0) {
scored.add(new ShaderSearchEngine.ScoredOptionElement(
el,
ShaderSearchEngine.getReadableTranslatedName(el.optionId),
ShaderSearchEngine.getReadableDefaultName(el.optionId),
getOptionPath(el.optionId),
score,
normalizedQuery
));
}
}
Collections.sort(scored);
applyFilteredLayout(scored);
}
private void applyFilteredLayout(List<ShaderSearchEngine.ScoredOptionElement> sortedElements) {
this.mainScreen.elements.clear();
for (ShaderSearchEngine.ScoredOptionElement scored : sortedElements) {
this.mainScreen.elements.add(scored.getElement());
}
}
private void restoreOriginalLayout() {
this.mainScreen.elements.clear();
this.mainScreen.elements.addAll(this.originalMainElements);
}
}
@@ -0,0 +1,251 @@
package net.coderbot.iris.shaderpack.option.menu;
import lombok.Getter;
import net.coderbot.iris.Iris;
import net.coderbot.iris.shaderpack.ShaderPack;
import net.minecraft.client.resources.I18n;
import org.jspecify.annotations.NonNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
/**
* Utility class for searching and scoring shader option elements based on a query string.
* <br>By SpacEagle17
*/
public class ShaderSearchEngine {
private static final String WHOLE_WORD_REGEX = "(?<=^|[^a-zA-Z0-9])%s(?=$|[^a-zA-Z0-9])";
private static final String STARTS_WITH_REGEX = "(?<=^|[^a-zA-Z0-9])%s";
private static final Pattern COLOR_CODE_PATTERN = Pattern.compile("§.");
/**
* Bitmask of bits from readable-name (translated + default) comparisons.
* Used by the comparator to separate name-match quality from rawId/comment noise.
*/
private static final int READABLE_NAME_BITS =
(1 << 13) | (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 3) | (1 << 2);
private static String strippedLanguageString(String key) {
String translated = I18n.format(key);
if (translated.equals(key)) return "";
return COLOR_CODE_PATTERN.matcher(translated).replaceAll("");
}
/** Translated display name for a sub-screen ID (looks up {@code "screen.<screenId>"}). */
public static String getDisplaySettingsName(String screenId) {
return strippedLanguageString("screen." + screenId);
}
/** Lowercase Minecraft-locale translation of an option's human-readable name. */
public static String getReadableTranslatedName(String optionId) {
return strippedLanguageString("option." + optionId).toLowerCase(Locale.ROOT);
}
/**
* Lowercase default (en_us from the shader pack's lang folder) translation.
* Returns {@code ""} when the current pack has no en_us lang file or the key is missing.
*/
public static String getReadableDefaultName(String optionId) {
try {
Optional<ShaderPack> packOpt = Iris.getCurrentPack();
if (packOpt.isEmpty()) return "";
Map<String, String> translations = packOpt.get().getLanguageMap().getTranslations("en_us");
if (translations == null) return "";
String value = translations.get("option." + optionId);
if (value == null) return "";
return COLOR_CODE_PATTERN.matcher(value).replaceAll("").toLowerCase(Locale.ROOT);
} catch (Throwable t) {
return "";
}
}
private static boolean isOnlyAscii(String s) {
for (int i = 0; i < s.length(); i++) {
if (s.charAt(i) > 127) return false;
}
return true;
}
/**
* Computes a 14-bit bitmask score for how well {@code optionId} matches {@code query}.
* Returns 0 when there is no match at all.
* <br> Read code to know what each bit means lmao
*/
public static int computeMatchTier(String optionId, String query) {
try {
if (optionId == null || query == null) return 0;
String q = query.toLowerCase(Locale.ROOT).trim();
if (q.isEmpty()) return 0;
String translatedName = getReadableTranslatedName(optionId);
String defaultName = getReadableDefaultName(optionId);
// Single ASCII char: only match when a readable name begins with it
if (q.length() == 1 && isOnlyAscii(q)) {
return (!translatedName.isEmpty() && translatedName.startsWith(q)) ? 1 : 0;
}
String escaped = Pattern.quote(q);
Pattern wholeWord = Pattern.compile(String.format(WHOLE_WORD_REGEX, escaped));
Pattern startsWith = Pattern.compile(String.format(STARTS_WITH_REGEX, escaped));
String rawId = optionId.toLowerCase(Locale.ROOT);
String commentText = strippedLanguageString("option." + optionId + ".comment").toLowerCase(Locale.ROOT);
int score = 0;
if (!translatedName.isEmpty() && translatedName.equals(q)) score |= (1 << 13);
if (!defaultName.isEmpty() && defaultName.equals(q)) score |= (1 << 12);
if (!translatedName.isEmpty() && wholeWord.matcher(translatedName).find()) score |= (1 << 11);
if (!defaultName.isEmpty() && wholeWord.matcher(defaultName).find()) score |= (1 << 10);
if (!translatedName.isEmpty() && startsWith.matcher(translatedName).find()) score |= (1 << 9);
if (!defaultName.isEmpty() && startsWith.matcher(defaultName).find()) score |= (1 << 8);
if (wholeWord.matcher(rawId).find()) score |= (1 << 7);
if (!commentText.isEmpty() && wholeWord.matcher(commentText).find()) score |= (1 << 6);
if (startsWith.matcher(rawId).find()) score |= (1 << 5);
if (!commentText.isEmpty() && startsWith.matcher(commentText).find()) score |= (1 << 4);
if (!translatedName.isEmpty() && translatedName.contains(q)) score |= (1 << 3);
if (!defaultName.isEmpty() && defaultName.contains(q)) score |= (1 << 2);
if (rawId.contains(q)) score |= (1 << 1);
if (!commentText.isEmpty() && commentText.contains(q)) score |= 1;
return score;
} catch (Exception e) {
return 0;
}
}
/** De-duplicates and flattens the pool of registered option elements. */
public static List<OptionMenuOptionElement> getAllOptionsFlattened(List<OptionMenuOptionElement> usedOptionElements) {
List<OptionMenuOptionElement> flat = new ArrayList<>();
List<String> seen = new ArrayList<>();
for (OptionMenuOptionElement el : usedOptionElements) {
if (el == null) continue;
String id = el.optionId != null ? el.optionId : el.toString();
if (!seen.contains(id)) {
seen.add(id);
flat.add(el);
}
}
return flat;
}
/**
* A scored search result carrying the original element and all data required for sorting.
*/
public static final class ScoredOptionElement implements Comparable<ScoredOptionElement> {
@Getter
private final OptionMenuOptionElement element;
public final String readableTranslatedName;
public final String readableDefaultName;
public final String path;
public final int score;
public final String query;
public ScoredOptionElement(OptionMenuOptionElement element,
String readableTranslatedName,
String readableDefaultName,
String path,
int score,
String query) {
this.element = element;
this.readableTranslatedName = readableTranslatedName;
this.readableDefaultName = readableDefaultName;
this.path = path;
this.score = score;
this.query = query;
}
@Override
public int compareTo(@NonNull ScoredOptionElement other) {
String q = this.query != null ? this.query.toLowerCase(Locale.ROOT).trim() : "";
int r;
if ((r = compareByReadableNameQuality(this, other)) != 0) return r;
if ((r = compareByMatchedWordLength(this, other, q)) != 0) return r;
if ((r = compareByWordCount(this, other)) != 0) return r;
if ((r = compareByFullScore(this, other)) != 0) return r;
if ((r = compareByPrefixBoost(this, other, q)) != 0) return r;
if ((r = compareByPathDepth(this, other)) != 0) return r;
return compareByAlphabetical(this, other);
}
// 1. How well does the readable name (translated + default) match?
private static int compareByReadableNameQuality(ScoredOptionElement a, ScoredOptionElement b) {
return Integer.compare(b.score & READABLE_NAME_BITS, a.score & READABLE_NAME_BITS);
}
// 2. Shorter matched word = query covers a higher fraction of that word.
private static int compareByMatchedWordLength(ScoredOptionElement a, ScoredOptionElement b, String q) {
if (q.isEmpty()) return 0;
String aw = findMatchingWord(a.readableTranslatedName, q);
if (aw == null) aw = findMatchingWord(a.readableDefaultName, q);
String bw = findMatchingWord(b.readableTranslatedName, q);
if (bw == null) bw = findMatchingWord(b.readableDefaultName, q);
int aLen = aw != null ? aw.length() : Integer.MAX_VALUE;
int bLen = bw != null ? bw.length() : Integer.MAX_VALUE;
return Integer.compare(aLen, bLen);
}
// 3. Fewer words = more precise ("Bloom" before "Bloom Strength").
private static int compareByWordCount(ScoredOptionElement a, ScoredOptionElement b) {
String ae = !a.readableTranslatedName.isEmpty() ? a.readableTranslatedName : a.readableDefaultName;
String be = !b.readableTranslatedName.isEmpty() ? b.readableTranslatedName : b.readableDefaultName;
return Integer.compare(countWords(ae), countWords(be));
}
// 4. Higher raw score wins.
private static int compareByFullScore(ScoredOptionElement a, ScoredOptionElement b) {
return Integer.compare(b.score, a.score);
}
// 5. Prefix boost: readable name starts with the exact query string.
private static int compareByPrefixBoost(ScoredOptionElement a, ScoredOptionElement b, String q) {
if (q.isEmpty()) return 0;
boolean ap = (a.readableTranslatedName != null && a.readableTranslatedName.startsWith(q))
|| (a.readableDefaultName != null && a.readableDefaultName.startsWith(q));
boolean bp = (b.readableTranslatedName != null && b.readableTranslatedName.startsWith(q))
|| (b.readableDefaultName != null && b.readableDefaultName.startsWith(q));
if (ap == bp) return 0;
return ap ? -1 : 1;
}
// 6. Shallower option path wins (fewer slashes = closer to root menu).
private static int compareByPathDepth(ScoredOptionElement a, ScoredOptionElement b) {
return Integer.compare(countSlashes(a.path), countSlashes(b.path));
}
// 7. Alphabetical tie-breaker.
private static int compareByAlphabetical(ScoredOptionElement a, ScoredOptionElement b) {
String aId = a.element != null ? a.element.optionId : null;
String bId = b.element != null ? b.element.optionId : null;
if (aId != null && bId != null) return aId.compareTo(bId);
return 0;
}
private static String findMatchingWord(String name, String query) {
if (name == null || query.isEmpty()) return null;
for (String word : name.split("\\s+")) {
if (word.startsWith(query)) return word;
}
return null;
}
private static int countWords(String s) {
if (s == null || s.trim().isEmpty()) return 0;
return s.trim().split("\\s+").length;
}
private static int countSlashes(String path) {
if (path == null) return 0;
int count = 0;
for (int i = 0; i < path.length(); i++) {
if (path.charAt(i) == '/') count++;
}
return count;
}
}
}
@@ -145,3 +145,7 @@ pack.iris.select.title=Auswählen
pack.iris.configure.title=Konfigurieren
label.iris.true=An
label.iris.false=Aus
options.iris.search.button=Suchen
options.iris.clear.button=Schließen
options.iris.search.tooltip=Shader-Einstellungen durchsuchen (Strg + F)
options.iris.clear.tooltip=Shader-Suche beenden (Esc)
@@ -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.
@@ -139,6 +141,10 @@ options.iris.shadowDistance.enabled=Allows you to change the maximum distance fo
options.iris.shadowDistance.disabled=Your current shader pack has already set a render distance for shadows; you cannot change it.
options.iris.gui.hide=Hide GUI
options.iris.gui.show=Show GUI
options.iris.search.button=Search
options.iris.clear.button=Clear
options.iris.search.tooltip=Search Shader Settings (Ctrl + F)
options.iris.clear.tooltip=Exit Shader Search (Esc)
options.dynamic_lights=Dynamic Lights
options.dynamic_lights.tooltip=Enables dynamic lights. Modes: OFF - off, FASTEST - 500ms delay, FAST - 250ms delay, FANCY - 50ms delay, REALTIME - no delay.
@@ -132,3 +132,7 @@ pack.iris.select.title=Выбрать
pack.iris.configure.title=Настроить
label.iris.true=Вкл
label.iris.false=Выкл
options.iris.search.button=Поиск
options.iris.clear.button=Очистить
options.iris.search.tooltip=Поиск настроек шейдеров (Ctrl + F)
options.iris.clear.tooltip=Выход из поиска шейдеров (Esc)
@@ -138,6 +138,10 @@ options.iris.shadowDistance.enabled=更改渲染阴影的最远距离。超出
options.iris.shadowDistance.disabled=当前光影包已重设阴影渲染距离,无法更改
options.iris.gui.hide=隐藏 GUI
options.iris.gui.show=显示 GUI
options.iris.search.button=搜索
options.iris.clear.button=清除搜索栏
options.iris.search.tooltip=搜索光影包设定 (Ctrl + F)
options.iris.clear.tooltip=退出搜索 (Esc)
options.dynamic_lights=动态光源
options.dynamic_lights.tooltip=启用动态光源,模式选择:OFF-关闭,FASTEST-500ms延迟,FAST-250ms延迟,FANCY-50ms延迟,REALTIME-无延迟
@@ -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 }
}