From 05d5f225f6167859907ac4ab64e8c64ceb1b2ceb Mon Sep 17 00:00:00 2001 From: ItsJules Date: Mon, 14 Jul 2025 19:12:37 +0800 Subject: [PATCH 01/18] Added KDTree, CellularImageSampler, CellularImageSamplerTemplate and temporarily increment version for testing --- common/addons/library-image/build.gradle.kts | 2 +- .../terra/addons/image/ImageLibraryAddon.java | 2 + .../CellularImageSamplerTemplate.java | 41 ++++ .../noisesampler/CellularImageSampler.java | 209 ++++++++++++++++++ .../dfsek/terra/addons/image/util/KDTree.java | 114 ++++++++++ 5 files changed, 367 insertions(+), 1 deletion(-) create mode 100644 common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java create mode 100644 common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java create mode 100644 common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/KDTree.java diff --git a/common/addons/library-image/build.gradle.kts b/common/addons/library-image/build.gradle.kts index b8682c80ba..d371766e48 100644 --- a/common/addons/library-image/build.gradle.kts +++ b/common/addons/library-image/build.gradle.kts @@ -1,4 +1,4 @@ -version = version("1.1.0") +version = version("1.1.1-cellularimage") dependencies { compileOnlyApi(project(":common:addons:manifest-addon-loader")) diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/ImageLibraryAddon.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/ImageLibraryAddon.java index b383c4ca75..cc77c89050 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/ImageLibraryAddon.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/ImageLibraryAddon.java @@ -15,6 +15,7 @@ import com.dfsek.terra.addons.image.config.colorsampler.mutate.TranslateColorSamplerTemplate; import com.dfsek.terra.addons.image.config.image.ImageTemplate; import com.dfsek.terra.addons.image.config.image.StitchedImageTemplate; +import com.dfsek.terra.addons.image.config.noisesampler.CellularImageSamplerTemplate; import com.dfsek.terra.addons.image.config.noisesampler.ChannelNoiseSamplerTemplate; import com.dfsek.terra.addons.image.config.noisesampler.DistanceTransformNoiseSamplerTemplate; import com.dfsek.terra.addons.image.image.Image; @@ -75,6 +76,7 @@ public void initialize() { NOISE_SAMPLER_TOKEN); noiseRegistry.register(addon.key("DISTANCE_TRANSFORM"), DistanceTransformNoiseSamplerTemplate::new); noiseRegistry.register(addon.key("CHANNEL"), ChannelNoiseSamplerTemplate::new); + noiseRegistry.register(addon.key("CELLULAR_IMAGE"), CellularImageSamplerTemplate::new); }) .then(event -> { CheckedRegistry>> colorSamplerRegistry = event.getPack().getOrCreateRegistry( diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java new file mode 100644 index 0000000000..84595c9309 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java @@ -0,0 +1,41 @@ +package com.dfsek.terra.addons.image.config.noisesampler; + +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + + +import com.dfsek.terra.addons.image.image.Image; +import com.dfsek.terra.addons.image.noisesampler.CellularImageSampler; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.noise.NoiseSampler; + + +public class CellularImageSamplerTemplate implements ObjectTemplate { + + @Value("image") + private Image image; + + @Value("distance") + @Default + private CellularImageSampler.@Meta DistanceFunction cellularDistanceFunction = CellularImageSampler.DistanceFunction.EuclideanSq; + + @Value("return") + @Default + private CellularImageSampler.@Meta ReturnType cellularReturnType = CellularImageSampler.ReturnType.Distance; + + @Value("lookup") + @Default + private @Meta NoiseSampler lookup; + + @Override + public NoiseSampler get() { + CellularImageSampler sampler = new CellularImageSampler(); + sampler.setReturnType(cellularReturnType); + sampler.setDistanceFunction(cellularDistanceFunction); + sampler.setImage(image); + sampler.setNoiseLookup(lookup); + return sampler; + } +} + diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java new file mode 100644 index 0000000000..0706d9ca61 --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2020-2025 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.image.noisesampler; + +import com.dfsek.terra.addons.image.image.Image; +import com.dfsek.terra.addons.image.util.KDTree; +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.util.vector.Vector2; + + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + + +/** + * NoiseSampler implementation for A Modified Cellular (Voronoi/Worley) Noise with Image Sampling for Seeding White pixels #FFFFFF being seeds + */ +public class CellularImageSampler implements NoiseSampler { + private List featurePoints = new ArrayList<>(); + private DistanceFunction distanceFunction = DistanceFunction.EuclideanSq; + private ReturnType returnType = ReturnType.Distance; + private NoiseSampler noiseLookup; + private Image image; + + + public void setDistanceFunction(DistanceFunction distanceFunction) { + this.distanceFunction = distanceFunction; + } + + public void setNoiseLookup(NoiseSampler noiseLookup) { + this.noiseLookup = noiseLookup; + } + + + public void setReturnType(ReturnType returnType) { + this.returnType = returnType; + } + + public void setImage(Image image){ + this.image = image; + } + + + private volatile boolean initialized = false; + + + + public List extractWhitePixels(Image image) { + int width = image.getWidth(); + int height = image.getHeight(); + + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int rgb = image.getRGB(x, y) & 0xFFFFFF; // Ignore alpha + if (rgb == 0xFFFFFF) { // Pure white + featurePoints.add(Vector2.of(x, y)); + System.out.println(Vector2.of(x, y)); + } + } + } + return featurePoints; + } + + private KDTree tree; + + + + + @Override + public double noise(long sl, double x, double z) { + if(!initialized){ + CompletableFuture.runAsync(() -> { + if(featurePoints.isEmpty()) { + featurePoints = extractWhitePixels(image); + tree = new KDTree(featurePoints); + System.out.println("CellularImageSampler initialized!"); + } + initialized = true; + }); + + } + + int xr = (int) Math.round(x); + int zr = (int) Math.round(z); + + Vector2 query = Vector2.of(xr, zr); + + List nearest = tree.kNearest(query, 3); + + double distance0 = Double.MAX_VALUE; + double distance1 = Double.MAX_VALUE; + double distance2 = Double.MAX_VALUE; + + if(distanceFunction == DistanceFunction.Manhattan) { + distance0 = Math.abs(query.getX() - nearest.get(0).getX()) + Math.abs(query.getZ() - nearest.get(0).getZ()); + distance1 = Math.abs(query.getX() - nearest.get(1).getX()) + Math.abs(query.getZ() - nearest.get(1).getZ()); + distance2 = Math.abs(query.getX() - nearest.get(2).getX()) + Math.abs(query.getZ() - nearest.get(2).getZ()); + } else { + distance0 = applyDistanceFunction(distanceFunction, query.distanceSquared(nearest.get(0))); + distance1 = applyDistanceFunction(distanceFunction, query.distanceSquared(nearest.get(1))); + distance2 = applyDistanceFunction(distanceFunction, query.distanceSquared(nearest.get(2))); + } + + System.out.printf("distance0: " + distance0); + System.out.printf("distance0: " + distance1); + System.out.printf("distance0: " + distance2); + + double distanceX = nearest.get(0).getX(); + double distanceZ = nearest.get(0).getZ(); + + System.out.printf("Distance: " + (distance0 - 1)); + System.out.printf("Distance2: " + (distance1- 1)); + System.out.printf("Distance2Add: " + ((distance1 + distance0) * 0.5 - 1)); + System.out.printf("Distance2Sub: " + (distance1 - distance0 - 1)); + System.out.printf("Distance2Mul: " + (distance1 * distance0 * 0.5 - 1)); + System.out.printf("Distance2Div: " + (distance0 / distance1 - 1)); + System.out.printf("NoiseLookup: " + noiseLookup.noise(sl, distanceX, distanceZ)); + System.out.printf("LocalNoiseLookup: " + noiseLookup.noise(sl, x - distanceX, z - distanceZ)); + System.out.printf("Distance3: " + (distance2 - 1)); + System.out.printf("Distance3Add: " + ((distance2 + distance0) * 0.5 - 1)); + System.out.printf("Distance3Sub: " + (distance2 - distance0 - 1)); + System.out.printf("Distance3Mul: " + (distance2 * distance0 - 1)); + System.out.printf("Distance3Div: " + (distance0 / distance2 - 1)); + System.out.printf("Angle: " + Math.atan2(distanceX - x, distanceZ - z)); + System.out.printf("CellValue: " + hashNormalized((int) distanceX, (int) distanceZ)); + + return switch(returnType) { + case Distance -> distance0 - 1; + case Distance2 -> distance1 - 1; + case Distance2Add -> (distance1 + distance0) * 0.5 - 1; + case Distance2Sub -> distance1 - distance0 - 1; + case Distance2Mul -> distance1 * distance0 * 0.5 - 1; + case Distance2Div -> distance0 / distance1 - 1; + case NoiseLookup -> noiseLookup.noise(sl, distanceX, distanceZ); + case LocalNoiseLookup -> noiseLookup.noise(sl, x - distanceX, z - distanceZ); + case Distance3 -> distance2 - 1; + case Distance3Add -> (distance2 + distance0) * 0.5 - 1; + case Distance3Sub -> distance2 - distance0 - 1; + case Distance3Mul -> distance2 * distance0 - 1; + case Distance3Div -> distance0 / distance2 - 1; + case Angle -> Math.atan2(distanceX - x, distanceZ - z); + case CellValue -> hashNormalized((int) distanceX, (int) distanceZ); + + }; + } + + private double hashNormalized(int x, int z) { + int h = x * 73428767 ^ z * 912367; + h ^= (h >>> 13); + h *= 0x85ebca6b; + h ^= (h >>> 16); + return (h & 0x7FFFFFFF) / (double) 0x7FFFFFFF * 2.0 - 1.0; // Map to [-1.0, 1.0) + } + + + private double applyDistanceFunction(DistanceFunction function, double distSq) { + return switch (function) { + case Euclidean -> Math.sqrt(distSq); + case EuclideanSq -> distSq; + case Manhattan -> { + // This is just a fallback interpretation + yield Math.sqrt(distSq) * 1.5; // Approximate + } + case Hybrid -> Math.sqrt(distSq) + 0.25 * distSq; + }; + } + + + + + @Override + public double noise(long seed, double x, double y, double z) { + return noise(seed, x, z); + } + + public enum DistanceFunction { + Euclidean, + EuclideanSq, + Manhattan, + Hybrid + } + + + public enum ReturnType { + Distance, + Distance2, + Distance2Add, + Distance2Sub, + Distance2Mul, + Distance2Div, + NoiseLookup, + LocalNoiseLookup, + Distance3, + Distance3Add, + Distance3Sub, + Distance3Mul, + Distance3Div, + Angle, + CellValue + } + +} diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/KDTree.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/KDTree.java new file mode 100644 index 0000000000..291f02336e --- /dev/null +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/KDTree.java @@ -0,0 +1,114 @@ +package com.dfsek.terra.addons.image.util; + +import com.dfsek.terra.api.util.vector.Vector2; + +import java.util.*; + +public class KDTree { + private static class Node { + Vector2 point; + Node left, right; + int axis; + + Node(Vector2 point, int axis) { + this.point = point; + this.axis = axis; + } + } + + private Node root; + + public KDTree(List points) { + root = build(points, 0); + } + + private Node build(List pts, int depth) { + if (pts.isEmpty()) return null; + + int axis = depth % 2; + pts.sort(Comparator.comparingDouble(p -> axis == 0 ? p.getX() : p.getZ())); + int median = pts.size() / 2; + + Node node = new Node(pts.get(median), axis); + node.left = build(pts.subList(0, median), depth + 1); + node.right = build(pts.subList(median + 1, pts.size()), depth + 1); + return node; + } + + public Vector2 nearest(Vector2 target) { + return nearest(root, target, root.point, Double.POSITIVE_INFINITY); + } + + private Vector2 nearest(Node node, Vector2 target, Vector2 bestPoint, double bestDistSq) { + if (node == null) return bestPoint; + + double d = distSq(target, node.point); + if (d < bestDistSq) { + bestDistSq = d; + bestPoint = node.point; + } + + Node near = (getAxis(target, node.axis) < getAxis(node.point, node.axis)) ? node.left : node.right; + Node far = (near == node.left) ? node.right : node.left; + + bestPoint = nearest(near, target, bestPoint, bestDistSq); + bestDistSq = distSq(target, bestPoint); + + double axisDist = getAxis(target, node.axis) - getAxis(node.point, node.axis); + if (axisDist * axisDist < bestDistSq) { + bestPoint = nearest(far, target, bestPoint, bestDistSq); + } + + return bestPoint; + } + + public List kNearest(Vector2 target, int k) { + PriorityQueue best = new PriorityQueue<>(Comparator.comparingDouble(n -> -n.distSq)); + kNearest(root, target, k, best); + List result = new ArrayList<>(); + for (Neighbor n : best) result.add(n.point); + return result; + } + + private void kNearest(Node node, Vector2 target, int k, PriorityQueue best) { + if (node == null) return; + + double dSq = distSq(target, node.point); + if (best.size() < k) { + best.add(new Neighbor(node.point, dSq)); + } else if (dSq < best.peek().distSq) { + best.poll(); + best.add(new Neighbor(node.point, dSq)); + } + + Node near = (getAxis(target, node.axis) < getAxis(node.point, node.axis)) ? node.left : node.right; + Node far = (near == node.left) ? node.right : node.left; + + kNearest(near, target, k, best); + + double axisDist = getAxis(target, node.axis) - getAxis(node.point, node.axis); + if (best.size() < k || axisDist * axisDist < best.peek().distSq) { + kNearest(far, target, k, best); + } + } + + private double distSq(Vector2 a, Vector2 b) { + double dx = a.getX() - b.getX(); + double dy = a.getZ() - b.getZ(); + return dx * dx + dy * dy; + } + + private double getAxis(Vector2 v, int axis) { + return axis == 0 ? v.getX() : v.getZ(); + } + + private static class Neighbor { + Vector2 point; + double distSq; + + Neighbor(Vector2 point, double distSq) { + this.point = point; + this.distSq = distSq; + } + } +} From a458d78272a28ba87f0a12c48c24e2b832b6bf3b Mon Sep 17 00:00:00 2001 From: ItsJules Date: Mon, 14 Jul 2025 19:14:22 +0800 Subject: [PATCH 02/18] remove debug messages for now --- .../noisesampler/CellularImageSampler.java | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java index 0706d9ca61..8c9b2d3331 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java @@ -61,7 +61,6 @@ public List extractWhitePixels(Image image) { int rgb = image.getRGB(x, y) & 0xFFFFFF; // Ignore alpha if (rgb == 0xFFFFFF) { // Pure white featurePoints.add(Vector2.of(x, y)); - System.out.println(Vector2.of(x, y)); } } } @@ -80,7 +79,6 @@ public double noise(long sl, double x, double z) { if(featurePoints.isEmpty()) { featurePoints = extractWhitePixels(image); tree = new KDTree(featurePoints); - System.out.println("CellularImageSampler initialized!"); } initialized = true; }); @@ -108,29 +106,10 @@ public double noise(long sl, double x, double z) { distance2 = applyDistanceFunction(distanceFunction, query.distanceSquared(nearest.get(2))); } - System.out.printf("distance0: " + distance0); - System.out.printf("distance0: " + distance1); - System.out.printf("distance0: " + distance2); double distanceX = nearest.get(0).getX(); double distanceZ = nearest.get(0).getZ(); - System.out.printf("Distance: " + (distance0 - 1)); - System.out.printf("Distance2: " + (distance1- 1)); - System.out.printf("Distance2Add: " + ((distance1 + distance0) * 0.5 - 1)); - System.out.printf("Distance2Sub: " + (distance1 - distance0 - 1)); - System.out.printf("Distance2Mul: " + (distance1 * distance0 * 0.5 - 1)); - System.out.printf("Distance2Div: " + (distance0 / distance1 - 1)); - System.out.printf("NoiseLookup: " + noiseLookup.noise(sl, distanceX, distanceZ)); - System.out.printf("LocalNoiseLookup: " + noiseLookup.noise(sl, x - distanceX, z - distanceZ)); - System.out.printf("Distance3: " + (distance2 - 1)); - System.out.printf("Distance3Add: " + ((distance2 + distance0) * 0.5 - 1)); - System.out.printf("Distance3Sub: " + (distance2 - distance0 - 1)); - System.out.printf("Distance3Mul: " + (distance2 * distance0 - 1)); - System.out.printf("Distance3Div: " + (distance0 / distance2 - 1)); - System.out.printf("Angle: " + Math.atan2(distanceX - x, distanceZ - z)); - System.out.printf("CellValue: " + hashNormalized((int) distanceX, (int) distanceZ)); - return switch(returnType) { case Distance -> distance0 - 1; case Distance2 -> distance1 - 1; From 22dc3425df476fbb06695a5d083e8cacd9faed14 Mon Sep 17 00:00:00 2001 From: ItsJules Date: Mon, 14 Jul 2025 19:40:32 +0800 Subject: [PATCH 03/18] Fix NPEs fix NPEs due to feature points and kdtree not being initalized --- common/addons/library-image/build.gradle.kts | 2 +- .../image/noisesampler/CellularImageSampler.java | 13 +++---------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/common/addons/library-image/build.gradle.kts b/common/addons/library-image/build.gradle.kts index d371766e48..dda4d905b9 100644 --- a/common/addons/library-image/build.gradle.kts +++ b/common/addons/library-image/build.gradle.kts @@ -1,4 +1,4 @@ -version = version("1.1.1-cellularimage") +version = version("1.1.0-cellularimage") dependencies { compileOnlyApi(project(":common:addons:manifest-addon-loader")) diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java index 8c9b2d3331..5972254ea1 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java @@ -74,17 +74,10 @@ public List extractWhitePixels(Image image) { @Override public double noise(long sl, double x, double z) { - if(!initialized){ - CompletableFuture.runAsync(() -> { - if(featurePoints.isEmpty()) { - featurePoints = extractWhitePixels(image); - tree = new KDTree(featurePoints); - } - initialized = true; - }); - - } + featurePoints = extractWhitePixels(image); + tree = new KDTree(featurePoints); + int xr = (int) Math.round(x); int zr = (int) Math.round(z); From dd175aa2a87cc3369fab9dadfee1b7e6ef757a78 Mon Sep 17 00:00:00 2001 From: ItsJules Date: Mon, 14 Jul 2025 19:45:14 +0800 Subject: [PATCH 04/18] make it only pull the image lazily and async i think --- .../noisesampler/CellularImageSampler.java | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java index 5972254ea1..ba330607fd 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java @@ -27,6 +27,10 @@ public class CellularImageSampler implements NoiseSampler { private ReturnType returnType = ReturnType.Distance; private NoiseSampler noiseLookup; private Image image; + private volatile CompletableFuture initFuture; + private final Object initLock = new Object(); + private KDTree tree; + public void setDistanceFunction(DistanceFunction distanceFunction) { @@ -47,8 +51,6 @@ public void setImage(Image image){ } - private volatile boolean initialized = false; - public List extractWhitePixels(Image image) { @@ -67,17 +69,26 @@ public List extractWhitePixels(Image image) { return featurePoints; } - private KDTree tree; + @Override + public double noise(long sl, double x, double z) { + initializeAsyncIfNeeded(); + if (initFuture == null || !initFuture.isDone()) { + // Still loading — return fallback like 0 + return 0.0; + } - @Override - public double noise(long sl, double x, double z) { + try { + initFuture.get(); // Ensure it's fully initialized + } catch (Exception e) { + e.printStackTrace(); + return 0.0; + } + + System.out.println(featurePoints); - featurePoints = extractWhitePixels(image); - tree = new KDTree(featurePoints); - int xr = (int) Math.round(x); int zr = (int) Math.round(z); @@ -123,6 +134,21 @@ public double noise(long sl, double x, double z) { }; } + private void initializeAsyncIfNeeded() { + if (initFuture == null) { + synchronized (initLock) { + if (initFuture == null) { + initFuture = CompletableFuture.runAsync(() -> { + featurePoints = extractWhitePixels(image); + tree = new KDTree(featurePoints); + System.out.println("KDTree initialized with " + featurePoints.size() + " white pixels."); + }); + } + } + } + } + + private double hashNormalized(int x, int z) { int h = x * 73428767 ^ z * 912367; h ^= (h >>> 13); From 8e9a2b6c4fa3835ab84cf70e046e67f7ccda4054 Mon Sep 17 00:00:00 2001 From: ItsJules Date: Mon, 14 Jul 2025 20:20:25 +0800 Subject: [PATCH 05/18] Update CellularImageSampler.java remove debug --- .../noisesampler/CellularImageSampler.java | 57 +++++++------------ 1 file changed, 19 insertions(+), 38 deletions(-) diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java index ba330607fd..9bd2ee7605 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java @@ -31,8 +31,6 @@ public class CellularImageSampler implements NoiseSampler { private final Object initLock = new Object(); private KDTree tree; - - public void setDistanceFunction(DistanceFunction distanceFunction) { this.distanceFunction = distanceFunction; } @@ -41,7 +39,6 @@ public void setNoiseLookup(NoiseSampler noiseLookup) { this.noiseLookup = noiseLookup; } - public void setReturnType(ReturnType returnType) { this.returnType = returnType; } @@ -50,57 +47,47 @@ public void setImage(Image image){ this.image = image; } - - - public List extractWhitePixels(Image image) { int width = image.getWidth(); int height = image.getHeight(); - for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - int rgb = image.getRGB(x, y) & 0xFFFFFF; // Ignore alpha - if (rgb == 0xFFFFFF) { // Pure white - featurePoints.add(Vector2.of(x, y)); + int rgb = image.getRGB(x, y) & 0xFFFFFF; + if (rgb == 0xFFFFFF) { + Vector2 point = Vector2.of(x, y); + featurePoints.add(point); } } } + return featurePoints; } - @Override public double noise(long sl, double x, double z) { initializeAsyncIfNeeded(); if (initFuture == null || !initFuture.isDone()) { - // Still loading — return fallback like 0 return 0.0; } try { - initFuture.get(); // Ensure it's fully initialized + initFuture.get(); } catch (Exception e) { - e.printStackTrace(); return 0.0; } - System.out.println(featurePoints); - int xr = (int) Math.round(x); int zr = (int) Math.round(z); - Vector2 query = Vector2.of(xr, zr); List nearest = tree.kNearest(query, 3); - double distance0 = Double.MAX_VALUE; - double distance1 = Double.MAX_VALUE; - double distance2 = Double.MAX_VALUE; + double distance0, distance1, distance2; - if(distanceFunction == DistanceFunction.Manhattan) { + if (distanceFunction == DistanceFunction.Manhattan) { distance0 = Math.abs(query.getX() - nearest.get(0).getX()) + Math.abs(query.getZ() - nearest.get(0).getZ()); distance1 = Math.abs(query.getX() - nearest.get(1).getX()) + Math.abs(query.getZ() - nearest.get(1).getZ()); distance2 = Math.abs(query.getX() - nearest.get(2).getX()) + Math.abs(query.getZ() - nearest.get(2).getZ()); @@ -110,11 +97,12 @@ public double noise(long sl, double x, double z) { distance2 = applyDistanceFunction(distanceFunction, query.distanceSquared(nearest.get(2))); } - double distanceX = nearest.get(0).getX(); double distanceZ = nearest.get(0).getZ(); - return switch(returnType) { + ReturnType type = returnType; + + double result = switch(type) { case Distance -> distance0 - 1; case Distance2 -> distance1 - 1; case Distance2Add -> (distance1 + distance0) * 0.5 - 1; @@ -122,7 +110,7 @@ public double noise(long sl, double x, double z) { case Distance2Mul -> distance1 * distance0 * 0.5 - 1; case Distance2Div -> distance0 / distance1 - 1; case NoiseLookup -> noiseLookup.noise(sl, distanceX, distanceZ); - case LocalNoiseLookup -> noiseLookup.noise(sl, x - distanceX, z - distanceZ); + case LocalNoiseLookup -> noiseLookup.noise(sl, x - distanceX, z - distanceZ); case Distance3 -> distance2 - 1; case Distance3Add -> (distance2 + distance0) * 0.5 - 1; case Distance3Sub -> distance2 - distance0 - 1; @@ -130,8 +118,11 @@ public double noise(long sl, double x, double z) { case Distance3Div -> distance0 / distance2 - 1; case Angle -> Math.atan2(distanceX - x, distanceZ - z); case CellValue -> hashNormalized((int) distanceX, (int) distanceZ); - }; + + + + return result; } private void initializeAsyncIfNeeded() { @@ -141,38 +132,29 @@ private void initializeAsyncIfNeeded() { initFuture = CompletableFuture.runAsync(() -> { featurePoints = extractWhitePixels(image); tree = new KDTree(featurePoints); - System.out.println("KDTree initialized with " + featurePoints.size() + " white pixels."); }); } } } } - private double hashNormalized(int x, int z) { int h = x * 73428767 ^ z * 912367; h ^= (h >>> 13); h *= 0x85ebca6b; h ^= (h >>> 16); - return (h & 0x7FFFFFFF) / (double) 0x7FFFFFFF * 2.0 - 1.0; // Map to [-1.0, 1.0) + return (h & 0x7FFFFFFF) / (double) 0x7FFFFFFF * 2.0 - 1.0; } - private double applyDistanceFunction(DistanceFunction function, double distSq) { return switch (function) { case Euclidean -> Math.sqrt(distSq); case EuclideanSq -> distSq; - case Manhattan -> { - // This is just a fallback interpretation - yield Math.sqrt(distSq) * 1.5; // Approximate - } + case Manhattan -> Math.sqrt(distSq) * 1.5; case Hybrid -> Math.sqrt(distSq) + 0.25 * distSq; }; } - - - @Override public double noise(long seed, double x, double y, double z) { return noise(seed, x, z); @@ -185,7 +167,6 @@ public enum DistanceFunction { Hybrid } - public enum ReturnType { Distance, Distance2, @@ -203,5 +184,5 @@ public enum ReturnType { Angle, CellValue } - } + From 1d02c6641242eedd7b55ef2002367b82bd582673 Mon Sep 17 00:00:00 2001 From: ItsJules Date: Tue, 15 Jul 2025 18:05:26 +0800 Subject: [PATCH 06/18] Added a lock for the KD tree so it initializes first and blocks until its finished --- .../CellularImageSamplerTemplate.java | 2 +- .../noisesampler/CellularImageSampler.java | 34 ++++++++++++------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java index 84595c9309..57d3b9aa90 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java @@ -31,9 +31,9 @@ public class CellularImageSamplerTemplate implements ObjectTemplate initFuture; private final Object initLock = new Object(); private KDTree tree; + private volatile boolean initialized = false; public void setDistanceFunction(DistanceFunction distanceFunction) { this.distanceFunction = distanceFunction; @@ -60,14 +61,13 @@ public List extractWhitePixels(Image image) { } } } - return featurePoints; } @Override public double noise(long sl, double x, double z) { - initializeAsyncIfNeeded(); + initializeIfNeeded(); if (initFuture == null || !initFuture.isDone()) { return 0.0; @@ -102,6 +102,8 @@ public double noise(long sl, double x, double z) { ReturnType type = returnType; + + double result = switch(type) { case Distance -> distance0 - 1; case Distance2 -> distance1 - 1; @@ -120,22 +122,28 @@ public double noise(long sl, double x, double z) { case CellValue -> hashNormalized((int) distanceX, (int) distanceZ); }; - - return result; } - private void initializeAsyncIfNeeded() { - if (initFuture == null) { - synchronized (initLock) { - if (initFuture == null) { - initFuture = CompletableFuture.runAsync(() -> { - featurePoints = extractWhitePixels(image); - tree = new KDTree(featurePoints); - }); - } + private void initializeIfNeeded() { + if (initialized) return; + + synchronized (initLock) { + if (!initialized) { + initFuture = CompletableFuture.runAsync(() -> { + List whitePixels = extractWhitePixels(image); + tree = new KDTree(whitePixels); + }).thenRun(() -> { + initialized = true; + }); } } + + try { + initFuture.get(); // block until KDTree is ready + } catch (Exception e) { + throw new RuntimeException("Failed to initialize KDTree", e); + } } private double hashNormalized(int x, int z) { From dc1eec765147cd00ff85ead30fcaac5a8605a294 Mon Sep 17 00:00:00 2001 From: ItsJules Date: Tue, 15 Jul 2025 21:39:48 +0800 Subject: [PATCH 07/18] fix the KDtree not properly sorting --- .../CellularImageSamplerTemplate.java | 6 ++++ .../noisesampler/CellularImageSampler.java | 36 +++++++++++++++---- .../dfsek/terra/addons/image/util/KDTree.java | 6 +++- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java index 57d3b9aa90..cf6a3af903 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java @@ -5,6 +5,7 @@ import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; +import com.dfsek.terra.addons.image.colorsampler.image.transform.Alignment; import com.dfsek.terra.addons.image.image.Image; import com.dfsek.terra.addons.image.noisesampler.CellularImageSampler; import com.dfsek.terra.api.config.meta.Meta; @@ -28,6 +29,10 @@ public class CellularImageSamplerTemplate implements ObjectTemplate extractWhitePixels(Image image) { int width = image.getWidth(); int height = image.getHeight(); + int offsetX = 0; + int offsetZ = 0; + + if (alignment == Alignment.CENTER) { + offsetX = -width / 2; + offsetZ = -height / 2; + } + + List points = new ArrayList<>(); + for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int rgb = image.getRGB(x, y) & 0xFFFFFF; if (rgb == 0xFFFFFF) { - Vector2 point = Vector2.of(x, y); - featurePoints.add(point); + Vector2 point = Vector2.of(x + offsetX, y + offsetZ); + points.add(point); + System.out.println(point); } } } - return featurePoints; + + return points; } + + @Override public double noise(long sl, double x, double z) { @@ -132,10 +155,9 @@ private void initializeIfNeeded() { if (!initialized) { initFuture = CompletableFuture.runAsync(() -> { List whitePixels = extractWhitePixels(image); - tree = new KDTree(whitePixels); - }).thenRun(() -> { - initialized = true; - }); + this.featurePoints = whitePixels; // Safe reassignment + this.tree = new KDTree(whitePixels); + }).thenRun(() -> initialized = true); } } diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/KDTree.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/KDTree.java index 291f02336e..36e75bad51 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/KDTree.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/KDTree.java @@ -65,11 +65,15 @@ private Vector2 nearest(Node node, Vector2 target, Vector2 bestPoint, double bes public List kNearest(Vector2 target, int k) { PriorityQueue best = new PriorityQueue<>(Comparator.comparingDouble(n -> -n.distSq)); kNearest(root, target, k, best); + List sorted = new ArrayList<>(best); + sorted.sort(Comparator.comparingDouble(n -> n.distSq)); + List result = new ArrayList<>(); - for (Neighbor n : best) result.add(n.point); + for (Neighbor n : sorted) result.add(n.point); return result; } + private void kNearest(Node node, Vector2 target, int k, PriorityQueue best) { if (node == null) return; From 0096aa25e57cb249995f2976970d6cfbc432df01 Mon Sep 17 00:00:00 2001 From: ItsJules Date: Tue, 15 Jul 2025 21:55:36 +0800 Subject: [PATCH 08/18] remove sout and add alignment config --- .../terra/addons/image/noisesampler/CellularImageSampler.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java index ec5e504125..ab86c08304 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java @@ -10,8 +10,6 @@ import com.dfsek.terra.addons.image.colorsampler.image.transform.Alignment; import com.dfsek.terra.addons.image.image.Image; import com.dfsek.terra.addons.image.util.KDTree; -import com.dfsek.terra.api.config.meta.Meta; -import com.dfsek.terra.api.entity.Player; import com.dfsek.terra.api.noise.NoiseSampler; import com.dfsek.terra.api.util.vector.Vector2; @@ -77,7 +75,6 @@ public List extractWhitePixels(Image image) { if (rgb == 0xFFFFFF) { Vector2 point = Vector2.of(x + offsetX, y + offsetZ); points.add(point); - System.out.println(point); } } } From 4dfc0a349b0447497aa1f3bae9f1dab21102a542 Mon Sep 17 00:00:00 2001 From: ItsJules <79675118+ItsJuls@users.noreply.github.com> Date: Wed, 16 Jul 2025 09:43:19 +0800 Subject: [PATCH 09/18] bump version --- common/addons/library-image/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/addons/library-image/build.gradle.kts b/common/addons/library-image/build.gradle.kts index dda4d905b9..26dc43765b 100644 --- a/common/addons/library-image/build.gradle.kts +++ b/common/addons/library-image/build.gradle.kts @@ -1,4 +1,4 @@ -version = version("1.1.0-cellularimage") +version = version("1.1.1") dependencies { compileOnlyApi(project(":common:addons:manifest-addon-loader")) From fb7c30266d1487f791b74fad9adf0aed7af09408 Mon Sep 17 00:00:00 2001 From: ItsJules Date: Wed, 16 Jul 2025 22:56:32 +0800 Subject: [PATCH 10/18] Made it so KD-Tree initializes only at startup with a hash --- common/addons/library-image/build.gradle.kts | 2 +- .../CellularImageSamplerTemplate.java | 3 + .../noisesampler/CellularImageSampler.java | 61 +++++++++---------- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/common/addons/library-image/build.gradle.kts b/common/addons/library-image/build.gradle.kts index dda4d905b9..26dc43765b 100644 --- a/common/addons/library-image/build.gradle.kts +++ b/common/addons/library-image/build.gradle.kts @@ -1,4 +1,4 @@ -version = version("1.1.0-cellularimage") +version = version("1.1.1") dependencies { compileOnlyApi(project(":common:addons:manifest-addon-loader")) diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java index cf6a3af903..303c7f86d0 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java @@ -41,6 +41,9 @@ public NoiseSampler get() { sampler.setDistanceFunction(cellularDistanceFunction); sampler.setNoiseLookup(lookup); sampler.setAlignment(align); + if(!sampler.isTreeSet()){ + sampler.doKDTree(); + } return sampler; } } diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java index ab86c08304..a1b981a156 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java @@ -16,23 +16,24 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; /** * NoiseSampler implementation for A Modified Cellular (Voronoi/Worley) Noise with Image Sampling for Seeding White pixels #FFFFFF being seeds */ public class CellularImageSampler implements NoiseSampler { - private List featurePoints = new ArrayList<>(); private DistanceFunction distanceFunction = DistanceFunction.EuclideanSq; private ReturnType returnType = ReturnType.Distance; private NoiseSampler noiseLookup; private Image image; - private volatile CompletableFuture initFuture; - private final Object initLock = new Object(); private KDTree tree; - private volatile boolean initialized = false; private Alignment alignment = Alignment.NONE; + private static final Map> treeFutures = new ConcurrentHashMap<>(); + public void setDistanceFunction(DistanceFunction distanceFunction) { @@ -55,6 +56,25 @@ public void setAlignment(Alignment alignment) { this.alignment = alignment; } + private int hash() { + return Objects.hash(alignment.name()); + } + + public boolean isTreeSet() { + CompletableFuture future = treeFutures.get(hash()); + return future != null && future.isDone() && !future.isCompletedExceptionally(); + } + + public void doKDTree() { + treeFutures.computeIfAbsent(hash(), h -> CompletableFuture.supplyAsync(() -> { + List whitePixels = extractWhitePixels(image); + return new KDTree(whitePixels); + })).thenAccept(tree -> { + this.tree = tree; + }); + } + + public List extractWhitePixels(Image image) { int width = image.getWidth(); int height = image.getHeight(); @@ -75,6 +95,7 @@ public List extractWhitePixels(Image image) { if (rgb == 0xFFFFFF) { Vector2 point = Vector2.of(x + offsetX, y + offsetZ); points.add(point); + } } } @@ -82,21 +103,18 @@ public List extractWhitePixels(Image image) { return points; } - - @Override public double noise(long sl, double x, double z) { + CompletableFuture future = treeFutures.get(hash()); - initializeIfNeeded(); - - if (initFuture == null || !initFuture.isDone()) { - return 0.0; + if (future == null || future.isCompletedExceptionally()) { + throw new IllegalStateException("KDTree not initialized for image."); } try { - initFuture.get(); + tree = future.get(); } catch (Exception e) { - return 0.0; + throw new RuntimeException("Error retrieving KDTree", e); } int xr = (int) Math.round(x); @@ -145,25 +163,6 @@ public double noise(long sl, double x, double z) { return result; } - private void initializeIfNeeded() { - if (initialized) return; - - synchronized (initLock) { - if (!initialized) { - initFuture = CompletableFuture.runAsync(() -> { - List whitePixels = extractWhitePixels(image); - this.featurePoints = whitePixels; // Safe reassignment - this.tree = new KDTree(whitePixels); - }).thenRun(() -> initialized = true); - } - } - - try { - initFuture.get(); // block until KDTree is ready - } catch (Exception e) { - throw new RuntimeException("Failed to initialize KDTree", e); - } - } private double hashNormalized(int x, int z) { int h = x * 73428767 ^ z * 912367; From 68452bfbf8ba9ae5815f33015402a191f687d55c Mon Sep 17 00:00:00 2001 From: ItsJules Date: Fri, 18 Jul 2025 16:41:21 +0800 Subject: [PATCH 11/18] revert version back to 1.1.0 as its minor --- common/addons/library-image/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/addons/library-image/build.gradle.kts b/common/addons/library-image/build.gradle.kts index 26dc43765b..b8682c80ba 100644 --- a/common/addons/library-image/build.gradle.kts +++ b/common/addons/library-image/build.gradle.kts @@ -1,4 +1,4 @@ -version = version("1.1.1") +version = version("1.1.0") dependencies { compileOnlyApi(project(":common:addons:manifest-addon-loader")) From d123bc9377cc454202417b7cac21146d98196b1e Mon Sep 17 00:00:00 2001 From: ItsJules Date: Fri, 18 Jul 2025 16:43:57 +0800 Subject: [PATCH 12/18] reformat according to code style --- .../image/config/noisesampler/CellularImageSamplerTemplate.java | 2 -- .../terra/addons/image/noisesampler/CellularImageSampler.java | 2 -- 2 files changed, 4 deletions(-) diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java index 303c7f86d0..3dfd5d0a33 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java @@ -3,8 +3,6 @@ import com.dfsek.tectonic.api.config.template.annotations.Default; import com.dfsek.tectonic.api.config.template.annotations.Value; import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; - - import com.dfsek.terra.addons.image.colorsampler.image.transform.Alignment; import com.dfsek.terra.addons.image.image.Image; import com.dfsek.terra.addons.image.noisesampler.CellularImageSampler; diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java index a1b981a156..7bb61861f7 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java @@ -12,8 +12,6 @@ import com.dfsek.terra.addons.image.util.KDTree; import com.dfsek.terra.api.noise.NoiseSampler; import com.dfsek.terra.api.util.vector.Vector2; - - import java.util.ArrayList; import java.util.List; import java.util.Map; From 23e014045b322232225a158db0e8004c6cff47c7 Mon Sep 17 00:00:00 2001 From: ItsJules Date: Fri, 18 Jul 2025 16:49:16 +0800 Subject: [PATCH 13/18] remove excess whitespace --- .../terra/addons/image/noisesampler/CellularImageSampler.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java index 7bb61861f7..c7ff3f7487 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/noisesampler/CellularImageSampler.java @@ -138,8 +138,6 @@ public double noise(long sl, double x, double z) { ReturnType type = returnType; - - double result = switch(type) { case Distance -> distance0 - 1; case Distance2 -> distance1 - 1; From 03d1b57ec13e667217a68f1a99b51752395a617e Mon Sep 17 00:00:00 2001 From: ItsJules Date: Fri, 18 Jul 2025 16:53:58 +0800 Subject: [PATCH 14/18] turn Neighbor into a record --- .../com/dfsek/terra/addons/image/util/KDTree.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/KDTree.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/KDTree.java index 36e75bad51..2b20cdc37f 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/KDTree.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/util/KDTree.java @@ -5,6 +5,8 @@ import java.util.*; public class KDTree { + + private static class Node { Vector2 point; Node left, right; @@ -106,13 +108,5 @@ private double getAxis(Vector2 v, int axis) { return axis == 0 ? v.getX() : v.getZ(); } - private static class Neighbor { - Vector2 point; - double distSq; - - Neighbor(Vector2 point, double distSq) { - this.point = point; - this.distSq = distSq; - } - } + private record Neighbor(Vector2 point, double distSq) {} } From b838959ad47a723f4368eb730979e5723fc4a9ef Mon Sep 17 00:00:00 2001 From: ItsJules Date: Fri, 18 Jul 2025 19:36:07 +0800 Subject: [PATCH 15/18] Seperated ReturnType and DistanceFunction logic into their own enums in the api --- .../dfsek/terra/addons/noise/NoiseAddon.java | 10 +++-- .../noise/CellularNoiseTemplate.java | 6 ++- .../noise/samplers/noise/CellularSampler.java | 44 +++++-------------- .../CellularImageSamplerTemplate.java | 12 ++++- .../noisesampler/CellularImageSampler.java | 41 ++++------------- .../api/noise/CellularDistanceFunction.java | 8 ++++ .../terra/api/noise/CellularReturnType.java | 19 ++++++++ 7 files changed, 66 insertions(+), 74 deletions(-) create mode 100644 common/api/src/main/java/com/dfsek/terra/api/noise/CellularDistanceFunction.java create mode 100644 common/api/src/main/java/com/dfsek/terra/api/noise/CellularReturnType.java diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/NoiseAddon.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/NoiseAddon.java index 2be9510552..bb4b09c779 100644 --- a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/NoiseAddon.java +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/NoiseAddon.java @@ -68,6 +68,8 @@ import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent; import com.dfsek.terra.api.event.functional.FunctionalEventHandler; import com.dfsek.terra.api.inject.annotations.Inject; +import com.dfsek.terra.api.noise.CellularDistanceFunction; +import com.dfsek.terra.api.noise.CellularReturnType; import com.dfsek.terra.api.noise.DerivativeNoiseSampler; import com.dfsek.terra.api.noise.NoiseSampler; import com.dfsek.terra.api.registry.CheckedRegistry; @@ -94,10 +96,10 @@ public void initialize() { CheckedRegistry>> noiseRegistry = event.getPack().getOrCreateRegistry( NOISE_SAMPLER_TOKEN); event.getPack() - .applyLoader(CellularSampler.DistanceFunction.class, - (type, o, loader, depthTracker) -> CellularSampler.DistanceFunction.valueOf((String) o)) - .applyLoader(CellularSampler.ReturnType.class, - (type, o, loader, depthTracker) -> CellularSampler.ReturnType.valueOf((String) o)) + .applyLoader(CellularDistanceFunction.class, + (type, o, loader, depthTracker) -> CellularDistanceFunction.valueOf((String) o)) + .applyLoader(CellularReturnType.class, + (type, o, loader, depthTracker) -> CellularReturnType.valueOf((String) o)) .applyLoader(DistanceSampler.DistanceFunction.class, (type, o, loader, depthTracker) -> DistanceSampler.DistanceFunction.valueOf((String) o)) .applyLoader(DimensionApplicableNoiseSampler.class, DimensionApplicableNoiseSampler::new) diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/CellularNoiseTemplate.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/CellularNoiseTemplate.java index 4716318a80..61d5e8d60e 100644 --- a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/CellularNoiseTemplate.java +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/CellularNoiseTemplate.java @@ -13,6 +13,8 @@ import com.dfsek.terra.addons.noise.samplers.noise.CellularSampler; import com.dfsek.terra.addons.noise.samplers.noise.simplex.OpenSimplex2Sampler; import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.noise.CellularDistanceFunction; +import com.dfsek.terra.api.noise.CellularReturnType; import com.dfsek.terra.api.noise.NoiseSampler; @@ -20,11 +22,11 @@ public class CellularNoiseTemplate extends NoiseTemplate { @Value("distance") @Default - private CellularSampler.@Meta DistanceFunction cellularDistanceFunction = CellularSampler.DistanceFunction.EuclideanSq; + private @Meta CellularDistanceFunction cellularDistanceFunction = CellularDistanceFunction.EuclideanSq; @Value("return") @Default - private CellularSampler.@Meta ReturnType cellularReturnType = CellularSampler.ReturnType.Distance; + private @Meta CellularReturnType cellularReturnType = CellularReturnType.Distance; @Value("jitter") @Default diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/CellularSampler.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/CellularSampler.java index fea5ccec56..46e4e72cd9 100644 --- a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/CellularSampler.java +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/CellularSampler.java @@ -8,6 +8,8 @@ package com.dfsek.terra.addons.noise.samplers.noise; import com.dfsek.terra.addons.noise.samplers.noise.simplex.OpenSimplex2Sampler; +import com.dfsek.terra.api.noise.CellularDistanceFunction; +import com.dfsek.terra.api.noise.CellularReturnType; import com.dfsek.terra.api.noise.NoiseSampler; @@ -192,8 +194,8 @@ public class CellularSampler extends NoiseFunction { }; - private DistanceFunction distanceFunction = DistanceFunction.EuclideanSq; - private ReturnType returnType = ReturnType.Distance; + private CellularDistanceFunction distanceFunction = CellularDistanceFunction.EuclideanSq; + private CellularReturnType returnType = CellularReturnType.Distance; private double jitterModifier = 1.0; private NoiseSampler noiseLookup; @@ -204,7 +206,7 @@ public CellularSampler() { noiseLookup = new OpenSimplex2Sampler(); } - public void setDistanceFunction(DistanceFunction distanceFunction) { + public void setDistanceFunction(CellularDistanceFunction distanceFunction) { this.distanceFunction = distanceFunction; } @@ -216,7 +218,7 @@ public void setNoiseLookup(NoiseSampler noiseLookup) { this.noiseLookup = noiseLookup; } - public void setReturnType(ReturnType returnType) { + public void setReturnType(CellularReturnType returnType) { this.returnType = returnType; } @@ -277,10 +279,10 @@ public double getNoiseRaw(long sl, double x, double y) { xPrimed += PRIME_X; } - if(distanceFunction == DistanceFunction.Euclidean && returnType != ReturnType.CellValue) { + if(distanceFunction == CellularDistanceFunction.Euclidean && returnType != CellularReturnType.CellValue) { distance0 = Math.sqrt(distance0); - if (returnType != ReturnType.Distance) { + if (returnType != CellularReturnType.Distance) { distance1 = Math.sqrt(distance1); } } @@ -369,10 +371,10 @@ public double getNoiseRaw(long sl, double x, double y, double z) { xPrimed += PRIME_X; } - if(distanceFunction == DistanceFunction.Euclidean && returnType != ReturnType.CellValue) { + if(distanceFunction == CellularDistanceFunction.Euclidean && returnType != CellularReturnType.CellValue) { distance0 = Math.sqrt(distance0); - if (returnType != ReturnType.Distance) { + if (returnType != CellularReturnType.Distance) { distance1 = Math.sqrt(distance1); } } @@ -395,30 +397,4 @@ public double getNoiseRaw(long sl, double x, double y, double z) { case Angle -> Math.atan2(y / frequency - centerY, x / frequency - centerX); }; } - - public enum DistanceFunction { - Euclidean, - EuclideanSq, - Manhattan, - Hybrid - } - - - public enum ReturnType { - CellValue, - Distance, - Distance2, - Distance2Add, - Distance2Sub, - Distance2Mul, - Distance2Div, - NoiseLookup, - LocalNoiseLookup, - Distance3, - Distance3Add, - Distance3Sub, - Distance3Mul, - Distance3Div, - Angle - } } diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java index 3dfd5d0a33..7da21ec279 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java @@ -7,6 +7,8 @@ import com.dfsek.terra.addons.image.image.Image; import com.dfsek.terra.addons.image.noisesampler.CellularImageSampler; import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.noise.CellularDistanceFunction; +import com.dfsek.terra.api.noise.CellularReturnType; import com.dfsek.terra.api.noise.NoiseSampler; @@ -17,11 +19,11 @@ public class CellularImageSamplerTemplate implements ObjectTemplate distance0 - 1; @@ -168,7 +170,7 @@ private double hashNormalized(int x, int z) { return (h & 0x7FFFFFFF) / (double) 0x7FFFFFFF * 2.0 - 1.0; } - private double applyDistanceFunction(DistanceFunction function, double distSq) { + private double applyDistanceFunction(CellularDistanceFunction function, double distSq) { return switch (function) { case Euclidean -> Math.sqrt(distSq); case EuclideanSq -> distSq; @@ -181,30 +183,5 @@ private double applyDistanceFunction(DistanceFunction function, double distSq) { public double noise(long seed, double x, double y, double z) { return noise(seed, x, z); } - - public enum DistanceFunction { - Euclidean, - EuclideanSq, - Manhattan, - Hybrid - } - - public enum ReturnType { - Distance, - Distance2, - Distance2Add, - Distance2Sub, - Distance2Mul, - Distance2Div, - NoiseLookup, - LocalNoiseLookup, - Distance3, - Distance3Add, - Distance3Sub, - Distance3Mul, - Distance3Div, - Angle, - CellValue - } } diff --git a/common/api/src/main/java/com/dfsek/terra/api/noise/CellularDistanceFunction.java b/common/api/src/main/java/com/dfsek/terra/api/noise/CellularDistanceFunction.java new file mode 100644 index 0000000000..f19ff88077 --- /dev/null +++ b/common/api/src/main/java/com/dfsek/terra/api/noise/CellularDistanceFunction.java @@ -0,0 +1,8 @@ +package com.dfsek.terra.api.noise; + +public enum CellularDistanceFunction { + Euclidean, + EuclideanSq, + Manhattan, + Hybrid +} diff --git a/common/api/src/main/java/com/dfsek/terra/api/noise/CellularReturnType.java b/common/api/src/main/java/com/dfsek/terra/api/noise/CellularReturnType.java new file mode 100644 index 0000000000..15090d3418 --- /dev/null +++ b/common/api/src/main/java/com/dfsek/terra/api/noise/CellularReturnType.java @@ -0,0 +1,19 @@ +package com.dfsek.terra.api.noise; + +public enum CellularReturnType { + CellValue, + Distance, + Distance2, + Distance2Add, + Distance2Sub, + Distance2Mul, + Distance2Div, + NoiseLookup, + LocalNoiseLookup, + Distance3, + Distance3Add, + Distance3Sub, + Distance3Mul, + Distance3Div, + Angle +} From 3a26ae8c115651534982c29157b12065ace17647 Mon Sep 17 00:00:00 2001 From: ItsJules Date: Fri, 18 Jul 2025 21:57:39 +0800 Subject: [PATCH 16/18] Tree future stuff is removed, added a new hash thing for the config. --- .../CellularImageSamplerTemplate.java | 5 +- .../noisesampler/CellularImageSampler.java | 77 ++++++++----------- 2 files changed, 35 insertions(+), 47 deletions(-) diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java index 7da21ec279..7e13ffa47a 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java @@ -36,7 +36,7 @@ public class CellularImageSamplerTemplate implements ObjectTemplate> treeFutures = new ConcurrentHashMap<>(); - + private static Map treeMap = new ConcurrentHashMap<>(); + private int hash; public void setDistanceFunction(CellularDistanceFunction distanceFunction) { @@ -56,21 +57,23 @@ public void setAlignment(Alignment alignment) { this.alignment = alignment; } - private int hash() { - return Objects.hash(alignment.name()); + private int getHash(String string){ + return Objects.hash(string); + } + + public void setHash(String string){ + this.hash = getHash(string); } - public boolean isTreeSet() { - CompletableFuture future = treeFutures.get(hash()); - return future != null && future.isDone() && !future.isCompletedExceptionally(); + public boolean hasTree(String string){ + return treeMap.containsKey(getHash(string)); } - public void doKDTree() { - treeFutures.computeIfAbsent(hash(), h -> CompletableFuture.supplyAsync(() -> { + + public KDTree doKDTree() { + return treeMap.computeIfAbsent(hash, h -> { List whitePixels = extractWhitePixels(image); return new KDTree(whitePixels); - })).thenAccept(tree -> { - this.tree = tree; }); } @@ -79,43 +82,27 @@ public List extractWhitePixels(Image image) { int width = image.getWidth(); int height = image.getHeight(); - int offsetX = 0; - int offsetZ = 0; - - if (alignment == Alignment.CENTER) { - offsetX = -width / 2; - offsetZ = -height / 2; - } - - List points = new ArrayList<>(); - - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int rgb = image.getRGB(x, y) & 0xFFFFFF; - if (rgb == 0xFFFFFF) { - Vector2 point = Vector2.of(x + offsetX, y + offsetZ); - points.add(point); + int offsetX = alignment == Alignment.CENTER ? -width / 2 : 0; + int offsetZ = alignment == Alignment.CENTER ? -height / 2 : 0; + + return IntStream.range(0, height).parallel() + .boxed() + .flatMap(y -> + IntStream.range(0, width) + .filter(x -> (image.getRGB(x, y) & 0xFFFFFF) == 0xFFFFFF) + .mapToObj(x -> { + Vector2 v = Vector2.of(x + offsetX, y + offsetZ); + return v; + }) + ) + .collect(Collectors.toList()); + } - } - } - } - return points; - } @Override public double noise(long sl, double x, double z) { - CompletableFuture future = treeFutures.get(hash()); - - if (future == null || future.isCompletedExceptionally()) { - throw new IllegalStateException("KDTree not initialized for image."); - } - - try { - tree = future.get(); - } catch (Exception e) { - throw new RuntimeException("Error retrieving KDTree", e); - } + tree = treeMap.get(hash); int xr = (int) Math.round(x); int zr = (int) Math.round(z); From 9c1722160b5ad214329fffcf34df14a4a494eec5 Mon Sep 17 00:00:00 2001 From: ItsJules Date: Fri, 18 Jul 2025 22:19:18 +0800 Subject: [PATCH 17/18] make kdtree local to the method --- .../config/noisesampler/CellularImageSamplerTemplate.java | 5 +++-- .../addons/image/noisesampler/CellularImageSampler.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java index 7e13ffa47a..f36770b288 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java @@ -11,6 +11,9 @@ import com.dfsek.terra.api.noise.CellularReturnType; import com.dfsek.terra.api.noise.NoiseSampler; +import java.util.Random; +import java.util.random.RandomGenerator; + public class CellularImageSamplerTemplate implements ObjectTemplate { @@ -33,12 +36,10 @@ public class CellularImageSamplerTemplate implements ObjectTemplate extractWhitePixels(Image image) { @Override public double noise(long sl, double x, double z) { - tree = treeMap.get(hash); + KDTree tree = treeMap.get(hash); int xr = (int) Math.round(x); int zr = (int) Math.round(z); From 52dad2bd4b9ac7862c15b86267be1aafc68ed545 Mon Sep 17 00:00:00 2001 From: ItsJules <79675118+ItsJuls@users.noreply.github.com> Date: Fri, 18 Jul 2025 22:47:22 +0800 Subject: [PATCH 18/18] remove random library --- .../config/noisesampler/CellularImageSamplerTemplate.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java index f36770b288..ecbe9f6d7c 100644 --- a/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java +++ b/common/addons/library-image/src/main/java/com/dfsek/terra/addons/image/config/noisesampler/CellularImageSamplerTemplate.java @@ -11,10 +11,6 @@ import com.dfsek.terra.api.noise.CellularReturnType; import com.dfsek.terra.api.noise.NoiseSampler; -import java.util.Random; -import java.util.random.RandomGenerator; - - public class CellularImageSamplerTemplate implements ObjectTemplate { @Value("image")