Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
05d5f22
Added KDTree, CellularImageSampler, CellularImageSamplerTemplate and …
ItsJuls Jul 14, 2025
a458d78
remove debug messages for now
ItsJuls Jul 14, 2025
22dc342
Fix NPEs
ItsJuls Jul 14, 2025
dd175aa
make it only pull the image lazily and async i think
ItsJuls Jul 14, 2025
8e9a2b6
Update CellularImageSampler.java
ItsJuls Jul 14, 2025
1d02c66
Added a lock for the KD tree so it initializes first and blocks until…
ItsJuls Jul 15, 2025
dc1eec7
fix the KDtree not properly sorting
ItsJuls Jul 15, 2025
0096aa2
remove sout and add alignment config
ItsJuls Jul 15, 2025
4dfc0a3
bump version
ItsJuls Jul 16, 2025
fb7c302
Made it so KD-Tree initializes only at startup with a hash
ItsJuls Jul 16, 2025
35a551d
Merge remote-tracking branch 'origin/feat/cellular-image' into feat/c…
ItsJuls Jul 16, 2025
68452bf
revert version back to 1.1.0 as its minor
ItsJuls Jul 18, 2025
d123bc9
reformat according to code style
ItsJuls Jul 18, 2025
23e0140
remove excess whitespace
ItsJuls Jul 18, 2025
03d1b57
turn Neighbor into a record
ItsJuls Jul 18, 2025
b838959
Seperated ReturnType and DistanceFunction logic into their own enums …
ItsJuls Jul 18, 2025
3a26ae8
Tree future stuff is removed, added a new hash thing for the config.
ItsJuls Jul 18, 2025
9c17221
make kdtree local to the method
ItsJuls Jul 18, 2025
52dad2b
remove random library
ItsJuls Jul 18, 2025
d16947e
revert version back to 1.1.0 as its minor
ItsJuls Jul 18, 2025
b1ddc3c
reformat according to code style
ItsJuls Jul 18, 2025
b4f6efe
remove excess whitespace
ItsJuls Jul 18, 2025
8dcef54
turn Neighbor into a record
ItsJuls Jul 18, 2025
c761232
Seperated ReturnType and DistanceFunction logic into their own enums …
ItsJuls Jul 18, 2025
987e4d7
Tree future stuff is removed, added a new hash thing for the config.
ItsJuls Jul 18, 2025
b48cc46
make kdtree local to the method
ItsJuls Jul 18, 2025
80917bd
remove random library
ItsJuls Jul 18, 2025
e50abfd
Merge remote-tracking branch 'origin/feat/cellular-image' into feat/c…
ItsJuls Dec 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -94,10 +96,10 @@ public void initialize() {
CheckedRegistry<Supplier<ObjectTemplate<NoiseSampler>>> 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@
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;


@SuppressWarnings("FieldMayBeFinal")
public class CellularNoiseTemplate extends NoiseTemplate<CellularSampler> {
@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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;


Expand Down Expand Up @@ -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;
Expand All @@ -204,7 +206,7 @@ public CellularSampler() {
noiseLookup = new OpenSimplex2Sampler();
}

public void setDistanceFunction(DistanceFunction distanceFunction) {
public void setDistanceFunction(CellularDistanceFunction distanceFunction) {
this.distanceFunction = distanceFunction;
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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);
}
}
Expand All @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Supplier<ObjectTemplate<ColorSampler>>> colorSamplerRegistry = event.getPack().getOrCreateRegistry(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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.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;
import com.dfsek.terra.api.noise.CellularDistanceFunction;
import com.dfsek.terra.api.noise.CellularReturnType;
import com.dfsek.terra.api.noise.NoiseSampler;

public class CellularImageSamplerTemplate implements ObjectTemplate<NoiseSampler> {

@Value("image")
private Image image;

@Value("distance")
@Default
private @Meta CellularDistanceFunction cellularDistanceFunction = CellularDistanceFunction.EuclideanSq;

@Value("return")
@Default
private CellularReturnType cellularReturnType = CellularReturnType.Distance;

@Value("lookup")
@Default
private @Meta NoiseSampler lookup;

@Value("align")
@Default
private @Meta Alignment align;

@Value("hash")
@Default
private @Meta String hash = "";

@Override
public NoiseSampler get() {
CellularImageSampler sampler = new CellularImageSampler();
sampler.setImage(image);
sampler.setReturnType(cellularReturnType);
sampler.setDistanceFunction(cellularDistanceFunction);
sampler.setNoiseLookup(lookup);
sampler.setAlignment(align);
sampler.setHash(hash);
if(!sampler.hasTree(hash)){
sampler.doKDTree();
}
return sampler;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* 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.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.noise.CellularDistanceFunction;
import com.dfsek.terra.api.noise.CellularReturnType;
import com.dfsek.terra.api.noise.NoiseSampler;
import com.dfsek.terra.api.util.vector.Vector2;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;


/**
* 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 CellularDistanceFunction distanceFunction = CellularDistanceFunction.EuclideanSq;
private CellularReturnType returnType = CellularReturnType.Distance;
private NoiseSampler noiseLookup;
private Image image;
private KDTree tree;
private Alignment alignment = Alignment.NONE;
private static Map<Integer, KDTree> treeMap = new ConcurrentHashMap<>();
private int hash;


public void setDistanceFunction(CellularDistanceFunction distanceFunction) {
this.distanceFunction = distanceFunction;
}

public void setNoiseLookup(NoiseSampler noiseLookup) {
this.noiseLookup = noiseLookup;
}

public void setReturnType(CellularReturnType returnType) {
this.returnType = returnType;
}

public void setImage(Image image){
this.image = image;
}

public void setAlignment(Alignment alignment) {
this.alignment = alignment;
}

private int getHash(String string){
return Objects.hash(string);
}

public void setHash(String string){
this.hash = getHash(string);
}

public boolean hasTree(String string){
return treeMap.containsKey(getHash(string));
}


public KDTree doKDTree() {
return treeMap.computeIfAbsent(hash, h -> {
List<Vector2> whitePixels = extractWhitePixels(image);
return new KDTree(whitePixels);
});
}


public List<Vector2> extractWhitePixels(Image image) {
int width = image.getWidth();
int height = image.getHeight();

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());
}



@Override
public double noise(long sl, double x, double z) {
KDTree tree = treeMap.get(hash);

int xr = (int) Math.round(x);
int zr = (int) Math.round(z);
Vector2 query = Vector2.of(xr, zr);

List<Vector2> nearest = tree.kNearest(query, 3);

double distance0, distance1, distance2;

if (distanceFunction == CellularDistanceFunction.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)));
}

double distanceX = nearest.get(0).getX();
double distanceZ = nearest.get(0).getZ();

CellularReturnType type = returnType;

double result = switch(type) {
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);
};

return result;
}


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;
}

private double applyDistanceFunction(CellularDistanceFunction function, double distSq) {
return switch (function) {
case Euclidean -> Math.sqrt(distSq);
case EuclideanSq -> distSq;
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);
}
}

Loading