Better Biome Blending (#2152)

This commit is contained in:
Eclipse-Sol authored and GitHub committed 2026-09-27 13:19:48 -07:00
1 parent e17b45c842
commit 233c74e0d7
22 files changed
+630 -25

No files matched your search

@@ -410,6 +410,8 @@ public enum Mixins implements IMixins {
, "celeritas.biome_blending.MixinBlockGrass"
, "celeritas.biome_blending.MixinBlockLeaves"
, "celeritas.biome_blending.MixinBlockLiquid"
, "celeritas.biome_blending.MixinWorld"
, "celeritas.biome_blending.MixinRenderBlocks"
, "celeritas.threading.MixinForgeHooksClient"
, "celeritas.terrain.MixinChunk"
, "celeritas.terrain.MixinWorldClient_WorkerAccess"
@@ -0,0 +1,6 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
public interface BiomeBlendTessellator {
BiomeVertexBlender angelica$getBiomeBlender();
void angelica$setBiomeBlender(BiomeVertexBlender blender);
}
@@ -0,0 +1,111 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import me.jellysquid.mods.sodium.client.gui.options.named.BiomeBlendMode;
import org.embeddedt.embeddium.api.util.ColorMixer;
import java.nio.ByteOrder;
public final class BiomeVertexBlender {
private static final boolean LITTLE_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN;
private SmoothBiomeColorCache cache;
private SmoothBiomeColorCache.ColorType type;
private int blockX, blockY, blockZ;
private boolean replacedBlockTint;
private final int[] cornerColors = new int[8];
private int populatedCorners;
public void setup(SmoothBiomeColorCache cache, SmoothBiomeColorCache.ColorType type, int x, int y, int z) {
this.cache = cache;
this.type = type;
this.blockX = x;
this.blockY = y;
this.blockZ = z;
this.replacedBlockTint = false;
this.populatedCorners = 0;
}
public void clear() {
cache = null;
type = null;
replacedBlockTint = false;
}
public boolean isGrass() {
return type == SmoothBiomeColorCache.ColorType.GRASS;
}
public boolean isActive() {
return replacedBlockTint;
}
public static boolean isRendering() {
if (SmoothBiomeColorCache.getActiveCache() == null || ClientProxy.options().quality.biomeBlendMode != BiomeBlendMode.FANCY) return false;
final BiomeVertexBlender blender = ((BiomeBlendTessellator) TessellatorManager.get()).angelica$getBiomeBlender();
return blender != null && blender.isActive();
}
public static int blockColor(SmoothBiomeColorCache cache, SmoothBiomeColorCache.ColorType type, int x, int y, int z) {
if (ClientProxy.options().quality.biomeBlendMode != BiomeBlendMode.FANCY) {
return cache.getColor(type, x, y, z);
}
final BiomeVertexBlender blender = ((BiomeBlendTessellator) TessellatorManager.get()).angelica$getBiomeBlender();
return blender == null ? cache.getColor(type, x, y, z) : blender.replaceBlockColor(cache, type, x, y, z);
}
int replaceBlockColor(SmoothBiomeColorCache cache, SmoothBiomeColorCache.ColorType type, int x, int y, int z) {
if (this.cache == cache && this.type == type && blockX == x && blockY == y && blockZ == z) {
replacedBlockTint = true;
return 0xFFFFFF;
}
return cache.getColor(type, x, y, z);
}
public int tint(int nativeColor, double x, double y, double z) {
if (!replacedBlockTint) return nativeColor;
final int rgb = sampleVertex(x, y, z);
final int abgr = LITTLE_ENDIAN ? nativeColor : Integer.reverseBytes(nativeColor);
final int tinted = multiply(abgr, rgb);
return LITTLE_ENDIAN ? tinted : Integer.reverseBytes(tinted);
}
private int sampleVertex(double x, double y, double z) {
final double dx = x - blockX;
final double dy = y - blockY;
final double dz = z - blockZ;
if ((dx != 0 && dx != 1) || (dy != 0 && dy != 1) || (dz != 0 && dz != 1)) {
return cache.getVertexColor(type, x, y, z);
}
final int corner = (int) dx | ((int) dy << 1) | ((int) dz << 2);
final int bit = 1 << corner;
if ((populatedCorners & bit) == 0) {
cornerColors[corner] = cache.getVertexColor(type, x, y, z);
populatedCorners |= bit;
}
return cornerColors[corner];
}
static int vertexColor(SmoothBiomeColorCache cache, SmoothBiomeColorCache.ColorType type, double x, double y, double z) {
x -= 0.5;
y -= 0.5;
z -= 0.5;
final int ix = (int) Math.floor(x);
final int iy = (int) Math.floor(y);
final int iz = (int) Math.floor(z);
final int c00 = cache.getColor(type, ix, iy, iz);
final int c01 = cache.getColor(type, ix, iy, iz + 1);
final int c10 = cache.getColor(type, ix + 1, iy, iz);
final int c11 = cache.getColor(type, ix + 1, iy, iz + 1);
final int z0 = c00 == c01 ? c00 : ColorMixer.mix(c01, c00, (float) (z - iz));
final int z1 = c10 == c11 ? c10 : ColorMixer.mix(c11, c10, (float) (z - iz));
return z0 == z1 ? z0 : ColorMixer.mix(z1, z0, (float) (x - ix));
}
static int multiply(int abgr, int rgb) {
final int r = (abgr & 255) * ((rgb >> 16) & 255) / 255;
final int g = ((abgr >> 8) & 255) * ((rgb >> 8) & 255) / 255;
final int b = ((abgr >> 16) & 255) * (rgb & 255) / 255;
return (abgr & 0xFF000000) | (b << 16) | (g << 8) | r;
}
}
@@ -0,0 +1,81 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import net.minecraft.world.World;
import net.minecraft.world.biome.BiomeGenBase;
public final class SkyColorCache {
private final double[][] colors = new double[4][3];
private World lastWorld;
private long lastTick;
private int lastX, lastY, lastZ, lastRadius;
public int getColor(World world, double x, double y, double z, int radius) {
final int blockX = (int) Math.floor(x);
final int blockY = (int) Math.floor(y);
final int blockZ = (int) Math.floor(z);
if (radius == 0) {
return sample(world, blockX, blockY, blockZ);
}
final long tick = world.getTotalWorldTime();
if (world != lastWorld || tick != lastTick || blockX != lastX || blockY != lastY || blockZ != lastZ || radius != lastRadius) {
updateAverages(world, blockX, blockY, blockZ, radius);
lastWorld = world;
lastTick = tick;
lastX = blockX;
lastY = blockY;
lastZ = blockZ;
lastRadius = radius;
}
final double dx = x - blockX;
final double dz = z - blockZ;
int color = 0;
for (int channel = 0; channel < 3; channel++) {
final double north = colors[0][channel] + (colors[1][channel] - colors[0][channel]) * dx;
final double south = colors[2][channel] + (colors[3][channel] - colors[2][channel]) * dx;
color = (color << 8) | (int) Math.round(north + (south - north) * dz);
}
return color;
}
private void updateAverages(World world, int x, int y, int z, int radius) {
for (double[] color : colors) {
color[0] = color[1] = color[2] = 0;
}
for (int dx = -radius; dx <= radius + 1; dx++) {
for (int dz = -radius; dz <= radius + 1; dz++) {
final int color = sample(world, x + dx, y, z + dz);
final int red = (color >> 16) & 255;
final int green = (color >> 8) & 255;
final int blue = color & 255;
if (dx <= radius) {
if (dz <= radius) add(colors[0], red, green, blue);
if (dz > -radius) add(colors[2], red, green, blue);
}
if (dx > -radius) {
if (dz <= radius) add(colors[1], red, green, blue);
if (dz > -radius) add(colors[3], red, green, blue);
}
}
}
final double count = (radius * 2 + 1) * (radius * 2 + 1);
for (double[] color : colors) {
color[0] /= count;
color[1] /= count;
color[2] /= count;
}
}
private static void add(double[] sum, int red, int green, int blue) {
sum[0] += red;
sum[1] += green;
sum[2] += blue;
}
private static int sample(World world, int x, int y, int z) {
final BiomeGenBase biome = world.getBiomeGenForCoords(x, z);
return biome.getSkyColorByTemp(biome.getFloatTemperature(x, y, z));
}
}
@@ -1,12 +1,49 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.biome.BiomeGenBase;
import net.minecraft.world.gen.NoiseGeneratorPerlin;
import org.embeddedt.embeddium.api.util.ColorMixer;
import org.embeddedt.embeddium.impl.biome.BiomeColorCache;
import org.embeddedt.embeddium.impl.util.color.BoxBlur;
import org.embeddedt.embeddium.impl.util.position.SectionPos;
import java.util.Arrays;
public class SmoothBiomeColorCache extends BiomeColorCache<BiomeGenBase, SmoothBiomeColorCache.ColorType> {
private static final int NEIGHBOR_RADIUS = 2;
private static final int SIZE_Y = 16 + NEIGHBOR_RADIUS * 2;
private static final int COLOR_TYPES = ColorType.values().length;
private final int radius;
private final IBlockAccess blockAccess;
private final BiomeGenBase[] biomes;
private final Slice[][] slices = new Slice[COLOR_TYPES][SIZE_Y];
private final BoxBlur.ColorBuffer blurBuffer;
private boolean biomesPopulated;
private long generation;
private int minY;
private final int sizeXZ;
private final double[] temperatureNoise;
private NoiseGeneratorPerlin noiseSource;
private int minX, minZ, waterY;
private static final ThreadLocal<SmoothBiomeColorCache> activeCache = new ThreadLocal<>();
private static final class Slice extends BoxBlur.ColorBuffer {
private long generation = -1;
private final boolean[] populated;
private boolean uniform;
private int uniformColor;
private Slice(int size, boolean unblended) {
super(size, size);
populated = unblended ? new boolean[size * size] : null;
}
private int[] colors() { return data; }
}
public static void setActiveCache(SmoothBiomeColorCache cache) {
activeCache.set(cache);
}
@@ -20,14 +57,151 @@ public class SmoothBiomeColorCache extends BiomeColorCache<BiomeGenBase, SmoothB
}
public SmoothBiomeColorCache(IBlockAccess blockAccess) {
super((x, y, z) -> blockAccess.getBiomeGenForCoords(x, z), 3);
this(blockAccess, configuredRadius());
}
SmoothBiomeColorCache(IBlockAccess blockAccess, int radius) {
super((x, y, z) -> blockAccess.getBiomeGenForCoords(x, z), radius);
this.radius = radius;
this.blockAccess = blockAccess;
this.sizeXZ = SIZE_Y + radius * 2;
this.temperatureNoise = new double[sizeXZ * sizeXZ];
this.biomes = radius == 0 ? null : new BiomeGenBase[sizeXZ * sizeXZ];
this.blurBuffer = radius == 0 ? null : new BoxBlur.ColorBuffer(sizeXZ, sizeXZ);
Arrays.fill(temperatureNoise, Double.NaN);
}
@Override
protected int resolveColor(ColorType type, BiomeGenBase biome, int relativeX, int relativeY, int relativeZ) {
public void update(SectionPos origin) {
super.update(origin);
minX = origin.minX() - NEIGHBOR_RADIUS - radius;
minZ = origin.minZ() - NEIGHBOR_RADIUS - radius;
waterY = origin.minY();
minY = origin.minY() - NEIGHBOR_RADIUS;
generation++;
biomesPopulated = false;
Arrays.fill(temperatureNoise, Double.NaN);
}
@Override
public int getColor(ColorType type, int x, int y, int z) {
// Keep the inherited behavior for world-cache callers before the first section update.
if (generation == 0) return super.getColor(type, x, y, z);
final int rx = Math.max(minX, Math.min(minX + sizeXZ - 1, x)) - minX;
final int rz = Math.max(minZ, Math.min(minZ + sizeXZ - 1, z)) - minZ;
final int ry = type == ColorType.WATER ? 0 : Math.max(minY, Math.min(minY + SIZE_Y - 1, y)) - minY;
final int index = rz * sizeXZ + rx;
final Slice slice = slice(type, ry);
if (radius == 0) {
if (slice.generation != generation) {
Arrays.fill(slice.populated, false);
slice.generation = generation;
}
if (!slice.populated[index]) {
final int wx = minX + rx, wz = minZ + rz;
slice.colors()[index] = resolveColor(type, blockAccess.getBiomeGenForCoords(wx, wz), wx, type == ColorType.WATER ? waterY : minY + ry, wz);
slice.populated[index] = true;
}
}
return slice.colors()[index];
}
private Slice slice(ColorType type, int ry) {
final Slice[] typeSlices = slices[type.ordinal()];
Slice slice = typeSlices[ry];
if (slice == null) typeSlices[ry] = slice = new Slice(sizeXZ, radius == 0);
if (radius > 0 && slice.generation != generation) populate(type, ry, slice);
return slice;
}
public int getVertexColor(ColorType type, double x, double y, double z) {
if (radius == 0 || generation == 0) return BiomeVertexBlender.vertexColor(this, type, x, y, z);
final int iy = (int) Math.floor(y - 0.5);
final int ry = type == ColorType.WATER ? 0 : Math.max(minY, Math.min(minY + SIZE_Y - 1, iy)) - minY;
final Slice slice = slice(type, ry);
return slice.uniform ? slice.uniformColor : interpolate(slice, x, z);
}
private int interpolate(Slice slice, double x, double z) {
x -= 0.5;
z -= 0.5;
final int ix = (int) Math.floor(x), iz = (int) Math.floor(z);
final int x0 = Math.max(minX, Math.min(minX + sizeXZ - 1, ix)) - minX;
final int x1 = Math.max(minX, Math.min(minX + sizeXZ - 1, ix + 1)) - minX;
final int z0 = (Math.max(minZ, Math.min(minZ + sizeXZ - 1, iz)) - minZ) * sizeXZ;
final int z1 = (Math.max(minZ, Math.min(minZ + sizeXZ - 1, iz + 1)) - minZ) * sizeXZ;
final int[] colors = slice.colors();
final int c00 = colors[z0 + x0], c01 = colors[z1 + x0];
final int c10 = colors[z0 + x1], c11 = colors[z1 + x1];
final int a = c00 == c01 ? c00 : ColorMixer.mix(c01, c00, (float) (z - iz));
final int b = c10 == c11 ? c10 : ColorMixer.mix(c11, c10, (float) (z - iz));
return a == b ? a : ColorMixer.mix(b, a, (float) (x - ix));
}
private void populate(ColorType type, int ry, Slice slice) {
if (!biomesPopulated) {
for (int rz = 0; rz < sizeXZ; rz++) {
for (int rx = 0; rx < sizeXZ; rx++) {
biomes[rz * sizeXZ + rx] = blockAccess.getBiomeGenForCoords(minX + rx, minZ + rz);
}
}
biomesPopulated = true;
}
final int worldY = type == ColorType.WATER ? waterY : minY + ry;
int firstSeenColor = 0;
boolean uniqueColor = true;
final int[] colors = slice.colors();
for (int rz = 0; rz < sizeXZ; rz++) {
for (int rx = 0; rx < sizeXZ; rx++) {
final int index = rz * sizeXZ + rx;
final int color = resolveColor(type, biomes[index], minX + rx, worldY, minZ + rz);
if (firstSeenColor == 0) firstSeenColor = color;
else if (firstSeenColor != color) uniqueColor = false;
colors[index] = color;
}
}
slice.uniform = uniqueColor && colors[0] == firstSeenColor;
slice.uniformColor = firstSeenColor;
if (!uniqueColor) BoxBlur.blur(slice, blurBuffer, radius);
slice.generation = generation;
}
public int temperatureNoiseIndex(NoiseGeneratorPerlin source, double x, double z) {
final double blockX = x * 8.0;
final double blockZ = z * 8.0;
final int ix = (int) blockX;
final int iz = (int) blockZ;
final int rx = ix - minX;
final int rz = iz - minZ;
if (ix != blockX || iz != blockZ || rx < 0 || rz < 0 || rx >= sizeXZ || rz >= sizeXZ) return -1;
if (noiseSource != source) {
noiseSource = source;
Arrays.fill(temperatureNoise, Double.NaN);
}
return rz * sizeXZ + rx;
}
public double getTemperatureNoise(int index) {
return temperatureNoise[index];
}
public void setTemperatureNoise(int index, double value) {
temperatureNoise[index] = value;
}
public static int configuredRadius() {
return Math.max(0, Math.min(7, ClientProxy.options().quality.biomeBlendRadius));
}
public boolean matchesConfiguredRadius() {
return radius == configuredRadius();
}
@Override
protected int resolveColor(ColorType type, BiomeGenBase biome, int x, int y, int z) {
return switch (type) {
case GRASS -> biome.getBiomeGrassColor(relativeX, relativeY, relativeZ);
case FOLIAGE -> biome.getBiomeFoliageColor(relativeX, relativeY, relativeZ);
case GRASS -> biome.getBiomeGrassColor(x, y, z);
case FOLIAGE -> biome.getBiomeFoliageColor(x, y, z);
case WATER -> biome.getWaterColorMultiplier();
};
}
@@ -106,7 +106,7 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
private StructureBoundingBox volume;
private Block renderingBlock;
private final SmoothBiomeColorCache biomeColorCache;
private SmoothBiomeColorCache biomeColorCache;
private List<IDynamicLightSource> chunkLightSources;
private DynamicLights dynamicLightsInstance;
@@ -201,6 +201,9 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
this.sections = context.getSections();
this.volume = context.getVolume();
if (!this.biomeColorCache.matchesConfiguredRadius()) {
this.biomeColorCache = new SmoothBiomeColorCache(this);
}
this.biomeColorCache.update(new SectionPos(origin.x, origin.y, origin.z));
this.baseX = (this.origin.x - NEIGHBOR_CHUNK_RADIUS) << 4;
@@ -1,10 +1,14 @@
package com.prupe.mcpatcher.cc;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import com.gtnewhorizons.angelica.rendering.celeritas.BiomeVertexBlender;
import net.minecraft.block.Block;
import net.minecraft.client.renderer.RenderBlocks;
@@ -70,6 +74,7 @@ public class ColorizeBlock {
// bitmaps from palette.block.*
private static final Map<Block, List<BlockStateMatcher>> blockColorMaps = new IdentityHashMap<>();
private static final Set<BlockStateMatcher> vanillaColorMaps = Collections.newSetFromMap(new IdentityHashMap<>());
private static IColorMap waterColorMap;
private static float[][] redstoneColor; // colormap/redstone.png
@@ -136,6 +141,7 @@ public class ColorizeBlock {
static void reset() {
blockColorMaps.clear();
vanillaColorMaps.clear();
waterColorMap = null;
resetVertexColors();
redstoneColor = null;
@@ -248,6 +254,7 @@ public class ColorizeBlock {
if (colorMap == null) {
return null;
}
final boolean vanilla = colorMap instanceof ColorMap.Vanilla;
colorMap = wrapBlockMap(colorMap);
for (String idString : idList.split("\\s+")) {
for (BlockStateMatcher blockMatcher : BlockAPI
@@ -256,6 +263,7 @@ public class ColorizeBlock {
.computeIfAbsent(blockMatcher.getBlock(), k -> new ArrayList<>());
blockMatcher.setData(colorMap);
maps.add(blockMatcher);
if (vanilla) vanillaColorMaps.add(blockMatcher);
if (resource != null) {
logger.fine(
"using %s for block %s, default color %06x",
@@ -303,6 +311,16 @@ public class ColorizeBlock {
return blockColorMaps.get(block);
}
public static boolean hasCustomColors(Block block, IBlockAccess access, int x, int y, int z) {
final List<BlockStateMatcher> maps = findColorMaps(block);
if (maps != null) {
for (BlockStateMatcher matcher : maps) {
if (matcher.match(access, x, y, z)) return !vanillaColorMaps.contains(matcher);
}
}
return false;
}
static IColorMap getThreadLocal(BlockStateMatcher matcher) {
IColorMap newMap = (IColorMap) matcher.getThreadData();
if (newMap == null) {
@@ -529,6 +547,7 @@ public class ColorizeBlock {
}
private static boolean setupBlockSmoothing(Block block, IBlockAccess blockAccess, int x, int y, int z, int face) {
if (BiomeVertexBlender.isRendering()) return false;
if (!checkBiomeSmoothing(block, face)) {
return false;
}
@@ -82,6 +82,8 @@ public class CTMUtils {
private final TileOverrideIterator.IJK ijkIterator = CTMUtils.newIJKIterator();
private final TileOverrideIterator.Metadata metadataIterator = CTMUtils.newMetadataIterator();
private final BlockOrientation renderBlockState = new BlockOrientation();
private IIcon originalIcon;
private IIcon replacementIcon;
private final int texturePackChangeCounter;
public StateAndIterator() {
@@ -242,6 +244,7 @@ public class CTMUtils {
lock.unlockRead(stamp);
}
}
rememberOriginalIcon(icon, value);
return value;
}
@@ -256,9 +259,22 @@ public class CTMUtils {
lock.unlockRead(stamp);
}
}
rememberOriginalIcon(icon, value);
return value;
}
private static void rememberOriginalIcon(IIcon original, IIcon replacement) {
final StateAndIterator state = stateAndIterator.get();
state.originalIcon = original;
state.replacementIcon = replacement;
}
/** Retains grass overlay tint semantics when CTM substitutes another texture. */
public static IIcon getOriginalIcon(IIcon icon) {
final StateAndIterator state = stateAndIterator.get();
return state.replacementIcon == icon ? state.originalIcon : icon;
}
public static IIcon getBlockIcon(IIcon icon, Block block, int face) {
return getBlockIcon(icon, block, face, 0);
}
@@ -35,6 +35,7 @@ import me.jellysquid.mods.sodium.client.gui.options.control.ControlValueFormatte
import me.jellysquid.mods.sodium.client.gui.options.control.CyclingControl;
import me.jellysquid.mods.sodium.client.gui.options.control.SliderControl;
import me.jellysquid.mods.sodium.client.gui.options.control.TickBoxControl;
import me.jellysquid.mods.sodium.client.gui.options.named.BiomeBlendMode;
import me.jellysquid.mods.sodium.client.gui.options.named.GraphicsMode;
import me.jellysquid.mods.sodium.client.gui.options.named.GraphicsQuality;
import me.jellysquid.mods.sodium.client.gui.options.named.LightingQuality;
@@ -209,6 +210,25 @@ public class SodiumGameOptionPages {
: EnumSet.noneOf(OptionFlag.class))
.build();
final OptionImpl<SodiumGameOptions, Integer> biomeBlendRadius = OptionImpl.createBuilder(int.class, sodiumOpts)
.setName(I18n.format("options.biomeBlendRadius"))
.setTooltip(I18n.format("sodium.options.biome_blend.tooltip"))
.setControl(option -> new SliderControl(option, 0, 7, 1, ControlValueFormatter.quantityOrDisabled("sodium.options.biome_blend.value", "sodium.options.none")))
.setBinding((opts, value) -> opts.quality.biomeBlendRadius = value, opts -> opts.quality.biomeBlendRadius)
.setImpact(OptionImpact.LOW)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build();
final OptionImpl<SodiumGameOptions, BiomeBlendMode> biomeBlendMode = OptionImpl.createBuilder(BiomeBlendMode.class, sodiumOpts)
.setName(I18n.format("sodium.options.biome_blend_mode.name"))
.setTooltip(I18n.format("sodium.options.biome_blend_mode.tooltip"))
.setControl(option -> new CyclingControl<>(option, BiomeBlendMode.class))
.setBinding((opts, value) -> opts.quality.biomeBlendMode = value, opts -> opts.quality.biomeBlendMode)
.setImpact(OptionImpact.LOW)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build();
biomeBlendMode.iris$dynamicallyEnable(() -> biomeBlendRadius.getValue() > 0);
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(GraphicsMode.class, vanillaOpts)
.setName(I18n.format("options.graphics"))
@@ -266,16 +286,10 @@ public class SodiumGameOptionPages {
.add(texelSampling)
.add(anisotropicFilteringSlider(vanillaOpts, textureFilterMode::getValue),
SodiumGameOptions.anisotropySupported())
.add(biomeBlendRadius)
.add(biomeBlendMode)
// TODO
/*.add(OptionImpl.createBuilder(int.class, vanillaOpts)
.setName(new TranslatableText("options.biomeBlendRadius"))
.setTooltip(new TranslatableText("sodium.options.biome_blend.tooltip"))
.setControl(option -> new SliderControl(option, 0, 7, 1, ControlValueFormatter.quantityOrDisabled("sodium.options.biome_blend.value", "gui.none")))
.setBinding((opts, value) -> opts.biomeBlendRadius = value, opts -> opts.biomeBlendRadius)
.setImpact(OptionImpact.LOW)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build())
.add(OptionImpl.createBuilder(int.class, vanillaOpts)
.setName(new TranslatableText("options.entityDistanceScaling"))
.setTooltip(new TranslatableText("sodium.options.entity_distance.tooltip"))
.setControl(option -> new SliderControl(option, 50, 500, 25, ControlValueFormatter.percentage()))
@@ -434,7 +448,7 @@ public class SodiumGameOptionPages {
.setName(I18n.format("sodium.options.async_occlusion_mode.name"))
.setTooltip(I18n.format("sodium.options.async_occlusion_mode.tooltip"))
.setControl(o -> new CyclingControl<>(o, AsyncOcclusionMode.class, new String[]{
I18n.format("sodium.options.async_occlusion_mode.none"),
I18n.format("sodium.options.none"),
I18n.format("sodium.options.async_occlusion_mode.only_shadow"),
I18n.format("sodium.options.async_occlusion_mode.everything") }))
.setImpact(OptionImpact.MEDIUM)
@@ -9,6 +9,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import me.jellysquid.mods.sodium.client.gui.options.named.BiomeBlendMode;
import me.jellysquid.mods.sodium.client.gui.options.named.GraphicsQuality;
import me.jellysquid.mods.sodium.client.gui.options.named.MultiDrawMode;
import me.jellysquid.mods.sodium.client.gui.options.named.TexelSampling;
@@ -244,6 +245,8 @@ public class SodiumGameOptions {
}
public static class QualitySettings {
public int biomeBlendRadius = 3;
public BiomeBlendMode biomeBlendMode = BiomeBlendMode.FANCY;
public GraphicsQuality grassQuality = GraphicsQuality.DEFAULT;
public boolean useCeleritasSmoothLighting = true;
public TextureFilterMode textureFilterMode = TextureFilterMode.RGSS_ANISOTROPIC;
@@ -0,0 +1,17 @@
package me.jellysquid.mods.sodium.client.gui.options.named;
public enum BiomeBlendMode implements NamedState {
FAST("options.graphics.fast"),
FANCY("options.graphics.fancy");
private final String key;
BiomeBlendMode(String key) {
this.key = key;
}
@Override
public String getKey() {
return key;
}
}
@@ -7,7 +7,7 @@ public enum TextureFilterMode implements NamedState {
RGSS("sodium.options.texture_filtering.rgss"),
ANISOTROPIC("sodium.options.texture_filtering.anisotropic"),
RGSS_ANISOTROPIC("sodium.options.texture_filtering.rgss_anisotropic"),
NONE("sodium.options.texture_filtering.none");
NONE("sodium.options.none");
private static final TextureFilterMode[] WITHOUT_ANISOTROPY = {RGSS, NONE};
private final String name;
@@ -44,7 +44,6 @@ sodium.options.celeritas_smooth_lighting.name=Celeritas Smooth Lighting
sodium.options.celeritas_smooth_lighting.tooltip=If enabled, Celeritas's lighting pipeline will be used for smooth lighting. This only has an effect if smooth lighting is enabled.
sodium.options.texture_filtering.name=Texture Filtering
sodium.options.texture_filtering.tooltip=Controls how block textures are sampled at a distance.
sodium.options.texture_filtering.none=None
sodium.options.texture_filtering.rgss=RGSS
sodium.options.texture_filtering.anisotropic=Anisotropic
sodium.options.texture_filtering.rgss_anisotropic=RGSS & Anisotropic
@@ -58,7 +57,10 @@ sodium.options.particle_quality.name=Particle Quality
sodium.options.particle_quality.tooltip=Controls the maximum number of particles which can be present on screen at any one time.
sodium.options.smooth_lighting.tooltip=Controls whether blocks will be smoothly lit and shaded. This slightly increases the amount of time needed to re-build a chunk, but doesn't affect frame rates.
sodium.options.biome_blend.value=%s block(s)
sodium.options.biome_blend.tooltip=Controls the range which biomes will be sampled for block colorization. Higher values greatly increase the amount of time it takes to build chunks for diminishing improvements in quality.
options.biomeBlendRadius=Biome Blend
sodium.options.biome_blend_mode.name=Block Biome Blending
sodium.options.biome_blend_mode.tooltip=Fast uses one biome tint per block. Fancy blends tint across grass, leaves, and water faces using the Biome Blend radius.
sodium.options.biome_blend.tooltip=Controls the radius used to blend biome colors for grass, foliage, water, and the sky.
sodium.options.entity_distance.tooltip=Controls how far away entities can render from the player. Higher values increase the render distance at the expense of frame rates.
sodium.options.entity_shadows.tooltip=If enabled, basic shadows will be rendered beneath mobs and other entities.
sodium.options.vignette.name=Vignette
@@ -125,14 +127,14 @@ sodium.options.always_defer_chunk_updates.tooltip=If enabled, rendering will nev
sodium.options.use_no_error_context.name=No Error Context
sodium.options.use_no_error_context.tooltip=If enabled, the OpenGL context will be created with error checking disabled. This may slightly improve performance, but it also increases the risk that the game will crash instead of gracefully handling OpenGL errors. You should disable this option if you are experiencing sudden unexplained crashes.
sodium.options.async_occlusion_mode.name=Async Occlusion
sodium.options.async_occlusion_mode.tooltip=Controls which render passes use asynchronous occlusion culling. ONLY_SHADOW (default) runs async occlusion only for the shadow pass, keeping the main render list synchronous. EVERYTHING runs all passes async, which may cause 1-frame visibility lag during fast camera movement. NONE disables async occlusion entirely.
sodium.options.async_occlusion_mode.none=None
sodium.options.async_occlusion_mode.tooltip=Controls which render passes use asynchronous occlusion culling. ONLY_SHADOW (default) runs async occlusion only for the shadow pass, keeping the main render list synchronous. EVERYTHING runs all passes async, which may cause 1-frame visibility lag during fast camera movement. "None" disables async occlusion entirely.
sodium.options.async_occlusion_mode.only_shadow=Only Shadow
sodium.options.async_occlusion_mode.everything=Everything
sodium.options.use_occlusion_culling.name=Occlusion Culling
sodium.options.use_occlusion_culling.tooltip=If enabled, chunks that are hidden behind other chunks will not be rendered. Disabling this will significantly reduce performance but can be useful for debugging.
sodium.options.threads.value=%s thread(s)
sodium.options.default=Default
sodium.options.none=None
sodium.options.buttons.undo=Undo
sodium.options.buttons.apply=Apply
sodium.options.anaglyph.tooltip=If enabled, the world will be rendered twice with red/cyan color masks.
@@ -101,13 +101,13 @@ sodium.options.use_no_error_context.name=Контекст без перевір
sodium.options.use_no_error_context.tooltip=Якщо увімкнено, контекст OpenGL створюватиметься із вимкненою перевіркою помилок. Це може дещо покращити продуктивність, але також збільшує ризик того, що гра просто вилетить замість коректної обробки графічних помилок. Вимкніть цей параметр, якщо ви стикаєтеся з раптовими незрозумілими вильотами.
sodium.options.async_occlusion_mode.name=Асинхронна оклюзія
sodium.options.async_occlusion_mode.tooltip=Керує тим, які проходи рендерингу використовують асинхронне відсікання невидимих об’єктів.\n- ONLY_SHADOW (стандартно): запускає асинхронну оклюзію лише для проходу тіней, залишаючи основний список рендерингу синхронним.\n- EVERYTHING: запускає всі проходи асинхронно, що може викликати затримку видимості в 1 кадр під час швидкого руху камери.\n- NONE: повністю вимикає асинхронну оклюзію.
sodium.options.async_occlusion_mode.none=Вимк.
sodium.options.async_occlusion_mode.only_shadow=Тільки тіні
sodium.options.async_occlusion_mode.everything=Усе
sodium.options.use_occlusion_culling.name=Оклюзійне відсікання
sodium.options.use_occlusion_culling.tooltip=Якщо увімкнено, чанки, які повністю сховані за іншими чанками, не будуть рендеритися. Вимкнення цього параметра суттєво знизить продуктивність, але може бути корисним для налагодження.
sodium.options.threads.value=Потоків: %s
sodium.options.default=За замовчуванням
sodium.options.none=Вимк.
sodium.options.buttons.undo=Скасувати
sodium.options.buttons.apply=Застосувати
sodium.options.anaglyph.tooltip=Якщо увімкнено, світ відображатиметься двічі з червоною та блакитною колірними масками.
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.mixins.early.celeritas.biome_blending;
import com.gtnewhorizons.angelica.rendering.celeritas.BiomeVertexBlender;
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
import com.gtnewhorizons.angelica.rendering.celeritas.WorldClientExtension;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
@@ -15,7 +16,7 @@ public class MixinBlockGrass {
// Check ThreadLocal first (for worker threads), then fall back to world cache
SmoothBiomeColorCache cache = SmoothBiomeColorCache.getActiveCache();
if (cache != null) {
return cache.getColor(SmoothBiomeColorCache.ColorType.GRASS, x, y, z);
return BiomeVertexBlender.blockColor(cache, SmoothBiomeColorCache.ColorType.GRASS, x, y, z);
} else if (access instanceof WorldClientExtension ext) {
return ext.celeritas$getSmoothBiomeColorCache().getColor(SmoothBiomeColorCache.ColorType.GRASS, x, y, z);
} else {
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.mixins.early.celeritas.biome_blending;
import com.gtnewhorizons.angelica.rendering.celeritas.BiomeVertexBlender;
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
import com.gtnewhorizons.angelica.rendering.celeritas.WorldClientExtension;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
@@ -15,7 +16,7 @@ public class MixinBlockLeaves {
// Check ThreadLocal first (for worker threads), then fall back to world cache
SmoothBiomeColorCache cache = SmoothBiomeColorCache.getActiveCache();
if (cache != null) {
return cache.getColor(SmoothBiomeColorCache.ColorType.FOLIAGE, x, y, z);
return BiomeVertexBlender.blockColor(cache, SmoothBiomeColorCache.ColorType.FOLIAGE, x, y, z);
} else if (access instanceof WorldClientExtension ext) {
return ext.celeritas$getSmoothBiomeColorCache().getColor(SmoothBiomeColorCache.ColorType.FOLIAGE, x, y, z);
} else {
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.mixins.early.celeritas.biome_blending;
import com.gtnewhorizons.angelica.rendering.celeritas.BiomeVertexBlender;
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
import com.gtnewhorizons.angelica.rendering.celeritas.WorldClientExtension;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
@@ -24,7 +25,7 @@ public abstract class MixinBlockLiquid extends Block {
// Check ThreadLocal first (for worker threads), then fall back to world cache
SmoothBiomeColorCache cache = SmoothBiomeColorCache.getActiveCache();
if (cache != null) {
return cache.getColor(SmoothBiomeColorCache.ColorType.WATER, x, y, z);
return BiomeVertexBlender.blockColor(cache, SmoothBiomeColorCache.ColorType.WATER, x, y, z);
} else if (access instanceof WorldClientExtension ext) {
return ext.celeritas$getSmoothBiomeColorCache().getColor(SmoothBiomeColorCache.ColorType.WATER, x, y, z);
} else {
@@ -0,0 +1,80 @@
package com.gtnewhorizons.angelica.mixins.early.celeritas.biome_blending;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.celeritas.BiomeBlendTessellator;
import com.gtnewhorizons.angelica.rendering.celeritas.BiomeVertexBlender;
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.prupe.mcpatcher.cc.ColorizeBlock;
import com.prupe.mcpatcher.ctm.CTMUtils;
import jss.notfine.config.MCPatcherForgeConfig;
import me.jellysquid.mods.sodium.client.gui.options.named.BiomeBlendMode;
import net.minecraft.block.Block;
import net.minecraft.block.BlockGrass;
import net.minecraft.client.renderer.EntityRenderer;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.init.Blocks;
import net.minecraft.util.IIcon;
import net.minecraft.world.IBlockAccess;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
@Mixin(RenderBlocks.class)
public abstract class MixinRenderBlocks {
@Shadow public IBlockAccess blockAccess;
@Shadow public abstract boolean hasOverrideBlockTexture();
@Unique private final BiomeVertexBlender angelica$vertexBlender = new BiomeVertexBlender();
@WrapMethod(method = {"renderStandardBlock", "renderBlockLiquid"})
private boolean angelica$blendBlock(Block block, int x, int y, int z, Operation<Boolean> original) {
final SmoothBiomeColorCache cache = SmoothBiomeColorCache.getActiveCache();
if (cache == null || ClientProxy.options().quality.biomeBlendMode != BiomeBlendMode.FANCY || SmoothBiomeColorCache.configuredRadius() == 0) {
return original.call(block, x, y, z);
}
final BiomeBlendTessellator tessellator = (BiomeBlendTessellator) TessellatorManager.get();
final BiomeVertexBlender previous = tessellator.angelica$getBiomeBlender();
final SmoothBiomeColorCache.ColorType type = angelica$colorType(block);
final boolean blend = previous == null && type != null && !hasOverrideBlockTexture() && !EntityRenderer.anaglyphEnable
&& !(AngelicaConfig.enableMCPatcherForgeFeatures && MCPatcherForgeConfig.CustomColors.enabled && ColorizeBlock.hasCustomColors(block, blockAccess, x, y, z));
if (blend) angelica$vertexBlender.setup(cache, type, x, y, z);
tessellator.angelica$setBiomeBlender(blend ? angelica$vertexBlender : null);
try {
return original.call(block, x, y, z);
} finally {
tessellator.angelica$setBiomeBlender(previous);
if (blend) angelica$vertexBlender.clear();
}
}
@Unique
private static SmoothBiomeColorCache.ColorType angelica$colorType(Block block) {
if (block == Blocks.grass) return SmoothBiomeColorCache.ColorType.GRASS;
if (block == Blocks.leaves || block == Blocks.leaves2) return SmoothBiomeColorCache.ColorType.FOLIAGE;
if (block == Blocks.water || block == Blocks.flowing_water) return SmoothBiomeColorCache.ColorType.WATER;
return null;
}
@WrapMethod(method = {"renderFaceYNeg", "renderFaceYPos", "renderFaceZNeg", "renderFaceZPos", "renderFaceXNeg", "renderFaceXPos"})
private void angelica$blendFace(Block block, double x, double y, double z, IIcon icon, Operation<Void> original) {
if (!angelica$vertexBlender.isActive()) {
original.call(block, x, y, z, icon);
return;
}
final BiomeBlendTessellator tessellator = (BiomeBlendTessellator) TessellatorManager.get();
final BiomeVertexBlender blender = tessellator.angelica$getBiomeBlender();
final IIcon originalIcon = AngelicaConfig.enableMCPatcherForgeFeatures && MCPatcherForgeConfig.ConnectedTextures.enabled ? CTMUtils.getOriginalIcon(icon) : icon;
if (blender != null && blender.isGrass() && icon != Blocks.grass.getIcon(1, 0) && icon != BlockGrass.getIconSideOverlay()
&& originalIcon != Blocks.grass.getIcon(1, 0) && originalIcon != BlockGrass.getIconSideOverlay()) {
tessellator.angelica$setBiomeBlender(null);
}
try {
original.call(block, x, y, z, icon);
} finally {
tessellator.angelica$setBiomeBlender(blender);
}
}
}
@@ -0,0 +1,31 @@
package com.gtnewhorizons.angelica.mixins.early.celeritas.biome_blending;
import com.gtnewhorizons.angelica.rendering.celeritas.SkyColorCache;
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import net.minecraft.entity.Entity;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(World.class)
public abstract class MixinWorld {
@Unique private final SkyColorCache angelica$skyColorCache = new SkyColorCache();
@WrapOperation(
method = "getSkyColorBody",
at = @At(value = "INVOKE",
target = "Lnet/minecraftforge/client/ForgeHooksClient;getSkyBlendColour(Lnet/minecraft/world/World;III)I",
remap = false),
remap = false)
private int angelica$blendSkyColor(World world, int x, int y, int z, Operation<Integer> original, @Local(argsOnly = true) Entity camera, @Local(argsOnly = true) float tickDelta) {
return angelica$skyColorCache.getColor(world,
camera.lastTickPosX + (camera.posX - camera.lastTickPosX) * tickDelta,
camera.lastTickPosY + (camera.posY - camera.lastTickPosY) * tickDelta,
camera.lastTickPosZ + (camera.posZ - camera.lastTickPosZ) * tickDelta,
SmoothBiomeColorCache.configuredRadius());
}
}
@@ -16,7 +16,7 @@ import com.gtnewhorizons.angelica.rendering.celeritas.WorldClientExtension;
@Mixin(WorldClient.class)
public class MixinWorldClient implements ChunkTrackerHolder, WorldClientExtension {
@Unique private final ChunkTracker celeritas$tracker = ModStatus.isCubicChunksLoaded ? new CubeStatusTracker() : new ChunkTracker();
@Unique private final SmoothBiomeColorCache celeritas$smoothBiomeColorCache = new SmoothBiomeColorCache((IBlockAccess) this);
@Unique private SmoothBiomeColorCache celeritas$smoothBiomeColorCache = new SmoothBiomeColorCache((IBlockAccess) this);
@Override
public ChunkTracker sodium$getTracker() {
@@ -25,6 +25,9 @@ public class MixinWorldClient implements ChunkTrackerHolder, WorldClientExtensio
@Override
public SmoothBiomeColorCache celeritas$getSmoothBiomeColorCache() {
if (!celeritas$smoothBiomeColorCache.matchesConfiguredRadius()) {
celeritas$smoothBiomeColorCache = new SmoothBiomeColorCache((IBlockAccess) this);
}
return this.celeritas$smoothBiomeColorCache;
}
}
@@ -1,8 +1,12 @@
package com.gtnewhorizons.angelica.mixins.early.rendering;
import com.gtnewhorizons.angelica.compat.iris.BiomeCategoryCache;
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
import com.gtnewhorizons.angelica.utils.EventUtils;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.world.biome.BiomeGenBase;
import net.minecraft.world.gen.NoiseGeneratorPerlin;
import net.minecraftforge.event.terraingen.BiomeEvent;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@@ -13,10 +17,24 @@ import org.spongepowered.asm.mixin.injection.Redirect;
public class MixinBiomeGenBase implements BiomeCategoryCache {
@Unique
private int cachedBiomeCategory = -1;
private final ThreadLocal<BiomeEvent.GetWaterColor> waterColorEventLocal = ThreadLocal.withInitial(() -> new BiomeEvent.GetWaterColor((BiomeGenBase)(Object)this, 0));
private final ThreadLocal<BiomeEvent.GetGrassColor> grassColorEventLocal = ThreadLocal.withInitial(() -> new BiomeEvent.GetGrassColor((BiomeGenBase)(Object)this, 0));
private final ThreadLocal<BiomeEvent.GetFoliageColor> foliageColorEventLocal = ThreadLocal.withInitial(() -> new BiomeEvent.GetFoliageColor((BiomeGenBase)(Object)this, 0));
@WrapOperation(method = "getFloatTemperature", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/gen/NoiseGeneratorPerlin;func_151601_a(DD)D"))
private double angelica$cachedTemperatureNoise(NoiseGeneratorPerlin noise, double x, double z, Operation<Double> original) {
final SmoothBiomeColorCache cache = SmoothBiomeColorCache.getActiveCache();
if (cache == null) return original.call(noise, x, z);
final int index = cache.temperatureNoiseIndex(noise, x, z);
if (index < 0) return original.call(noise, x, z);
final double cached = cache.getTemperatureNoise(index);
if (!Double.isNaN(cached)) return cached;
final double value = original.call(noise, x, z);
cache.setTemperatureNoise(index, value);
return value;
}
@Unique
private void prepareEvent(BiomeEvent.BiomeColor event, int defaultColor) {
event.newColor = defaultColor;
@@ -1,6 +1,10 @@
package com.gtnewhorizons.angelica.mixins.early.rendering;
import com.gtnewhorizons.angelica.rendering.StateAwareTessellator;
import com.gtnewhorizons.angelica.rendering.celeritas.BiomeBlendTessellator;
import com.gtnewhorizons.angelica.rendering.celeritas.BiomeVertexBlender;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import net.minecraft.client.renderer.Tessellator;
import org.spongepowered.asm.mixin.Mixin;
@@ -10,7 +14,25 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(Tessellator.class)
public class MixinTessellator implements StateAwareTessellator {
public class MixinTessellator implements StateAwareTessellator, BiomeBlendTessellator {
@Unique
private BiomeVertexBlender angelica$biomeBlender;
@Override
public BiomeVertexBlender angelica$getBiomeBlender() {
return angelica$biomeBlender;
}
@Override
public void angelica$setBiomeBlender(BiomeVertexBlender blender) {
angelica$biomeBlender = blender;
}
@ModifyExpressionValue(method = "addVertex", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/Tessellator;color:I"))
private int angelica$blendVertexColor(int original, @Local(argsOnly = true, ordinal = 0) double x, @Local(argsOnly = true, ordinal = 1) double y, @Local(argsOnly = true, ordinal = 2) double z) {
return angelica$biomeBlender == null ? original : angelica$biomeBlender.tint(original, x, y, z);
}
@Unique
private final IntArrayList vertexStates = new IntArrayList();