Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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