Stop fractured noise from drawing straight diagonal lines

Default fracturing pushed every sample by one value along the (1, -1)
diagonal, so heavily fractured styles such as FRACTAL_SMOKE and the
IRIS family formed straight 45 degree edges. It still takes a single
fracture sample, but the push direction now turns with that sample's
value. That costs a few multiplications per fracture level: 0% on
FRACTAL_SMOKE and FRACTAL_WATER, 4% on IRIS_DOUBLE.

axialFracturing read its second offset from the same noise with
swapped coordinates, which mirrors the warp across x = z. Each axis
now samples a far, uncorrelated part of the fracture noise instead.

Both change terrain for fractured styles, so existing worlds will show
seams at the border to newly generated chunks.

Model used: Claude Opus 5.5
This commit is contained in:
piexel
2026-10-18 14:35:31 +02:00
parent 8783fa09a5
commit 855ab90be4
2 changed files with 26 additions and 8 deletions
@@ -64,7 +64,7 @@ public class IrisGeneratorStyle {
@MinNumber(0.00001)
@Desc("The Output multiplier. Only used if parent is fracture.")
private double multiplier = 1;
@Desc("If set to true, each dimension will be fractured with a different order of input coordinates. This is usually 2 or 3 times slower than normal.")
@Desc("If set to true, each axis is warped by its own sample of this fracture instead of one shared value, which avoids straight diagonal lines. This is usually 2 or 3 times slower than normal.")
private boolean axialFracturing = false;
@Desc("Apply a generator to the coordinate field fed into this parent generator. I.e. Distort your generator with another generator.")
private IrisGeneratorStyle fracture = null;
@@ -51,6 +51,8 @@ public class CNG {
public static final NoiseInjector SRC_POW = (s, v) -> new double[]{Math.pow(s, v), 0};
public static final NoiseInjector DST_MOD = (s, v) -> new double[]{v % s, 0};
public static final NoiseInjector DST_POW = (s, v) -> new double[]{Math.pow(v, s), 0};
// Far enough from the first axis' sample to be uncorrelated, and not a multiple of the 65536-cell hash period.
private static final double FRACTURE_AXIS_OFFSET = 10_000.5;
public static long hits = 0;
public static long creates = 0;
private final double opacity;
@@ -456,19 +458,35 @@ public class CNG {
}
if (fracture.isTrueFracturing()) {
double x = dim.length > 0 ? dim[0] + ((fracture.noise(dim) - 0.5) * fscale) : 0D;
double y = dim.length > 1 ? dim[1] + ((fracture.noise(dim[1], dim[0]) - 0.5) * fscale) : 0D;
double z = dim.length > 2 ? dim[2] + ((fracture.noise(dim[2], dim[0], dim[1]) - 0.5) * fscale) : 0D;
double x = dim.length > 0 ? dim[0] + axialFractureOffset(0, dim) : 0D;
double y = dim.length > 1 ? dim[1] + axialFractureOffset(1, dim) : 0D;
double z = dim.length > 2 ? dim[2] + axialFractureOffset(2, dim) : 0D;
return generator.noise(x * scale, y * scale, z * scale) * opacity;
}
double f = fracture.noise(dim) * fscale;
double x = dim.length > 0 ? dim[0] + f : 0D;
double y = dim.length > 1 ? dim[1] - f : 0D;
double z = dim.length > 2 ? dim[2] - f : 0D;
// One sample has to drive both axes here, so the push direction turns with the sampled value.
// A fixed direction slides every point along the same diagonal and draws straight lines.
double fractureValue = fracture.noise(dim);
double push = (fractureValue - 0.5) * fscale;
double turn = fractureValue * fractureValue * (3 - 2 * fractureValue);
double pushAlong = push * (1 - 2 * turn);
double pushAcross = push * 4 * fractureValue * (1 - fractureValue);
double x = dim.length > 0 ? dim[0] + pushAlong : 0D;
double y = dim.length > 1 ? dim[1] + pushAcross : 0D;
double z = dim.length > 2 ? dim[2] + pushAcross : 0D;
return generator.noise(x * scale, y * scale, z * scale) * opacity;
}
private double axialFractureOffset(int axis, double[] dim) {
double shift = axis * FRACTURE_AXIS_OFFSET;
double sample = switch (dim.length) {
case 1 -> fracture.noise(dim[0] + shift);
case 2 -> fracture.noise(dim[0] + shift, dim[1] + shift);
default -> fracture.noise(dim[0] + shift, dim[1] + shift, dim[2] + shift);
};
return (sample - 0.5) * fscale;
}
public double invertNoise(double... dim) {
if (dim.length == 1) {
return noise(-dim[0]);