From 855ab90be4a84bf91dcddb183cf4e161cdd8b1fe Mon Sep 17 00:00:00 2001 From: piexel Date: Sun, 18 Oct 2026 13:57:18 +0200 Subject: [PATCH] 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 --- .../engine/object/IrisGeneratorStyle.java | 2 +- .../java/com/volmit/iris/util/noise/CNG.java | 32 +++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisGeneratorStyle.java b/core/src/main/java/com/volmit/iris/engine/object/IrisGeneratorStyle.java index 2e9996d6e..8b8cb47ea 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisGeneratorStyle.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisGeneratorStyle.java @@ -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; diff --git a/core/src/main/java/com/volmit/iris/util/noise/CNG.java b/core/src/main/java/com/volmit/iris/util/noise/CNG.java index a17fee3f0..9364eabf6 100644 --- a/core/src/main/java/com/volmit/iris/util/noise/CNG.java +++ b/core/src/main/java/com/volmit/iris/util/noise/CNG.java @@ -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]);