Skip to content
ikkyblob edited this page Sep 21, 2025 · 10 revisions

ikbLib is a library mod which currently implements the following:

  • A family of voronoi/cellular noise density functions
  • An API for creating new density functions which use the world seed to generate noise
  • An API for creating fluids which flow over or under one another

Voronoi Noise

ikbLib's voronoi noise functions all use a common system based on the FastNoiseLite library. Each divides the world into a series of square regions called plates, then places a point in the center of each plate and offsets it by a random amount in the x and z directions. When the function is called, it compares the current block position to the plate it lies inside and its eight neighbors. The plates are put into a sorted list from closest center point to furthest center point. Then a plate is chosen from the list, and a corresponding value is output.

Basic Voronoi Noise

"ikblibrary:voronoi"

This is a basic voronoi noise function. It recalculates each time it's called, making it ideal for one-time uses. It takes the following parameters (and their data types):

  • "scale" (double) the width of an average cell. This value must be defined by a data pack. Literally, it defines the size of the grid used for plates.
  • "salt" (long) a value which is added to the seed. Defaults to 0.
  • "flat" (boolean) whether or not to use "flat mode" calculations. By default, all plate centers and y positions are calculated as y = 0. When turned off, the actual y position is used and plates are offset in the y direction. Can be used with flat_cache to sample from a fixed y position but still offset plates in the y direction. Not recommended with high scales.
  • "jitter" (double) a value from 0 to 0.5 which scales the random offset of each plate's center. Defaults to 0.4. At 0, creates a perfectly square grid of points.
  • "metric" (int) a value from 0 to 5 which determines which distance metric to use. Defaults to 1 (see below).
  • "mode" (int) a value from 0 to 1 which determines the output mode. Defaults to 0 (see below).
  • "ordinal" (int) a value from 1 to 9 which determines which plate's values to output; 1 (default) is the plate with the closest center, 2 is the second closest, etc.

Modes

ikbLib's voronoi functions have three output modes:

  • 0: Distance (default) outputs the distance between the given block position and a nearby plate's center. The output is given relative to the scale parameter; 0 means the block is exactly at the plate's center, 0.5 means the distance is equal to half the scale, etc. Normal output range is 0 to 2. The higher the jitter, the more commonly outputs exceed 1. For the distance from a boundary, subtract the neighboring plate's distance from the nearest plate's distance.
  • 1: Value outputs a random value between 0 and 1. Each plate has a different value.
  • 2: Velocity (relative) outputs the relative velocity between the nearest plate and a neighboring plate, intended for tectonic plate simulations. Output range is untested, but estimated and -2 to 2; a result of 0 represents a passive margin or transform boundary. Ordinal 1 always gives a result of 0.
  • 3: Passive Margin Check checks if two plates have identical velocities. Outputs 1 if true and 0 if false.

Distance Metrics

ikbLib's voronoi functions come with six integrated distance functions:

  • 0: Taxicab/Manhattan. The difference in x, y, and z values are calculated separately, then added together. On a chessboard, this is the distance a rook would take to reach a point. This and Chebyshev both give cells with lines that run parallel to the x and z axes or at a 45 degree diagonal.
  • 1: Euclidean (default). This is the normal distance, where the diagonal of a square is about 1.4 times the size of its side length. On a chessboard, this is the distance an ant would take to reach a point.
  • 2: Chebyshev. The difference in x, y, and z values are calculated separately, then only the largest is used. On a chessboard, this is the distance a king would take to reach a point.
  • 3, 4, 5: Averages: 3, 4, and 5 each calculate two of the other metrics, then average them together. 3 is Taxicab + Euclidean, 4 is Taxicab + Chebyshev, and 5 is Chebyshev and Euclidean.

Cached Voronoi Noise

"ikblibrary:cached_voronoi" & "ikblibrary:pull_cached_voronoi"

These two technical functions work in tandem to yield multiple values from a single voronoi diagram. Because the calculations are slightly more involved, but don't have to repeat for each point, this is recommended if you need to make complex calculations based on several values taken from nearby plates.

The cached_voronoi function defines the voronoi diagram which you're sampling from, and stores several different values for it. It accepts the following parameters:

  • "salt", "flat", "scale", "jitter", "metric" see above. As with voronoi, only scale must be defined.
  • "maxCheck" (int) a value from 1 to 9 which tells minecraft how many plates to save data for. At the default value of 3, for example, it saves only the three nearest plates to a given point. If you attempt to pull values for a fourth, the function will fail and output 0 by default.

Then, pull_from_voronoi is used to take a cached value from the cached_voronoi (if you try to calculate from cached_voronoi as a normal density function, it simply returns 0). It accepts the following parameters:

  • "mode", "ordinal" see above. Both are optional and act as they do with the basic function.
  • "cache" (densityFunction) a required parameter which indicates which cached_voronoi function to pull from. It can also pull from a flat_cache, cache_once, or the like as long as it wraps a cached_voronoi. Multiple pull_from_voronoi functions can (and should) use the same cache parameter.

Seeded Density Functions

ikbLib has a simple, easy-to-use API for adding seeded density functions to minecraft. To start, add ikbLib as a new dependency using Modrinth Maven. Then, use the following template for your density function and (if you don't already have one) your noise sampler.

public class NewDensityFunction implements SeededDensityFunction {

    private static final MapCodec<NewDensityFunction> MAP_CODEC = RecordCodecBuilder.mapCodec((instance) ->
            instance.group(
                    Codec.DOUBLE.fieldOf("arg1").forGetter((input) -> input.arg1),
                    Codec.DOUBLE.fieldOf("arg2").forGetter((input) -> input.arg2)
            ).apply(instance, (NewDensityFunction::new))
    );

    public static final KeyDispatchDataCodec<NewDensityFunction> CODEC = KeyDispatchDataCodec.of(MAP_CODEC);

    @Nullable
    public ExampleNoise noise = null;

    private final double arg1;
    private final double arg2;

    public NewDensityFunction(double arg1, double arg2) {
        this.arg1 = arg1;
        this.arg2 = arg2;
    }

    @Override
    public double compute(FunctionContext pos) {
        return this.noise.sample(pos, arg1, arg2);
    }

    @Override
    public void fillArray(double[] doubles, ContextProvider contextProvider) {
        contextProvider.fillAllDirectly(doubles, this);
    }

    @Override
    public DensityFunction mapAll(Visitor visitor) {
        return visitor.apply(this);
    }

    @Override
    public double minValue() {}

    @Override
    public double maxValue() {}

    @Override
    public KeyDispatchDataCodec<? extends DensityFunction> codec() {
        return CODEC;
    }

    @Override
    public NewDensityFunction initialize(long levelSeed) {
        this.noise = ExampleNoise.create(levelSeed);
        return this;
    }

    public boolean initialized() {
        return this.noise != null;
    }

}
public class ExampleNoise {

    public static ExampleNoise create(long seed) {
        return new ExampleNoise(seed);
    }

    ExampleNoise(long seed) {
        this.seed = seed;
    }

    public long seed;

    public double sample(DensityFunction.FunctionContext blockPos, double arg1, double arg2) {
        //include whichever calculations you'd like here as long as it returns a double.
    }
}

Some Notes

Based on limited testing (and an amateur understanding of java), the following might help when troubleshooting:

  • Take special care that your density function is not a record; this interferes with the attempt to create an instance, which is important for keeping the noise attached to the function. Similarly, it's recommended to use a create() method to make your noise sampler.
  • If your noise sampler saves values in a hash map (like cached_voronoi uses), there's a chance that it can return null values from them. If that happens, you may need to double check that a value isn't null before returning it.
  • Your initialize() method should return the same type as your main class (NewDensityFunction in the example). I'm pretty sure that if you just try making it return SeededDensityFunction (as in the interface), it may cause issues with upcasting.

Stackable Fluids

"ikblibrary:stackable_wet" & "ikblibrary:stackable_molten"

ikbLib uses a simple, mostly data-driven system for making fluids that stack onto one another. To apply this behaviour, simply add a stackable tag to your fluids (see an example config below). For best results, also define a density when creating the fluid's type.

Fluids that share the same stackable tag will exhibit the following behaviours:

  • When flowing sideways, lighter fluids will treat denser fluids as impassable.
  • When flowing downwards, lighter fluids will disappear into denser fluids, but only if there's "space" (the fluid's level is 6 or lower).
  • When flowing in any direction, denser fluids will flow through lighter fluids as if they were air.
  • Where a lighter fluid and denser fluid touch, only the denser fluid's faces will render (the lighter fluid's faces will be cropped or culled to match).
  • If a lighter fluid sits on top of a denser fluid, the space between them that would normally be "air" is instead filled with the lighter fluid. This affects rendering fluid faces, fog, breathing, and the like.

If you encounter any missing functionality, please report the issue here on GitHub.

Example Configuration data/ikblibrary/tags/fluid/stackable_wet.json

{
  "values": [
    "ikbunderflow:source_brine",
    "ikbunderflow:flowing_brine",
    "minecraft:water",
    "minecraft:flowing_water"
  ]
}

Compatability

ikbLibrary uses mixins to modify the following files:

// For Seeded Density Functions
net.minecraft.world.level.levelgen.RandomState
// For Stackable Fluids
net.neoforged.neoforge.fluids.BaseFlowingFluid
net.minecraft.client.Camera
net.minecraft.client.renderer.block.LiquidBlockRenderer
net.minecraft.client.renderer.chunk.SectionCompiler
net.minecraft.world.entity.Entity
net.minecraft.world.level.material.FlowingFluid
net.minecraft.world.level.material.LavaFluid
net.minecraft.world.level.material.WaterFluid

Of the changes made, most should have minimal impacts on compatibility, with the following exceptions:

  • In the current version, large portions of LiquidBlockRenderer are entirely rewritten, and may behave strangely with other mods that alter fluid rendering. These changes have not been tested with shaders.
  • If a modded fluid overrides the canBeReplacedWith, canPassThrough, or isWaterHole methods, it may also override the logic that checks for stackable fluids. For full compatability, it may be ideal to replicate this logic from ikbLib's source code.