customizable palette noise

This commit is contained in:
dfsek 2021-01-11 13:31:19 -07:00
parent 462b6f4198
commit 18d7408f53
11 changed files with 75 additions and 84 deletions

View File

@ -1,12 +1,11 @@
package com.dfsek.terra.api.math; package com.dfsek.terra.api.math;
import com.dfsek.terra.api.math.noise.samplers.NoiseSampler; import com.dfsek.terra.api.math.noise.samplers.NoiseSampler;
import com.dfsek.terra.api.world.biome.NormalizationUtil; import net.jafama.FastMath;
import java.util.HashSet; import java.util.HashSet;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class ProbabilityCollection<E> { public class ProbabilityCollection<E> {
@ -25,19 +24,19 @@ public class ProbabilityCollection<E> {
return this; return this;
} }
public E get() {
if(array.length == 0) return null;
return (E) array[ThreadLocalRandom.current().nextInt(array.length)];
}
public E get(Random r) { public E get(Random r) {
if(array.length == 0) return null; if(array.length == 0) return null;
return (E) array[r.nextInt(array.length)]; return (E) array[r.nextInt(array.length)];
} }
private static double getNoise(double x, double z, NoiseSampler sampler) {
double n = sampler.getNoise(x, z);
return FastMath.min(FastMath.max(n, -1), 1);
}
public E get(NoiseSampler n, double x, double z) { public E get(NoiseSampler n, double x, double z) {
if(array.length == 0) return null; if(array.length == 0) return null;
return (E) array[NormalizationUtil.normalize(n.getNoise(x, z), array.length, 1)]; return (E) array[FastMath.min(FastMath.floorToInt(((getNoise(x, z, n) + 1D) / 2D) * array.length), array.length - 1)];
} }
public int getTotalProbability() { public int getTotalProbability() {

View File

@ -1615,25 +1615,32 @@ public class FastNoiseLite implements NoiseSampler {
return lerp(xf0, xf1, ys) * 1.4247691104677813f; return lerp(xf0, xf1, ys) * 1.4247691104677813f;
} }
private int doubleCast2Int(double f) { private static long hash(long in) {
int i = Float.floatToRawIntBits((float) f); in = (in + 0x7ed55d16) + (in << 12);
in = (in ^ 0xc761c23c) ^ (in >> 19);
return i ^ (i >> 16); in = (in + 0x165667b1) + (in << 5);
in = (in + 0xd3a2646c) ^ (in << 9);
in = (in + 0xfd7046c5) + (in << 3);
in = (in ^ 0xb55a4f09) ^ (in >> 16);
return in;
} }
private double singleWhiteNoise(int seed, double x, double y, double z) { private double singleWhiteNoise(int seed, double x, double y, double z) {
int xi = doubleCast2Int(x); long hashX = hash(Double.doubleToRawLongBits(x) ^ seed);
int yi = doubleCast2Int(y); long hashZ = hash(Double.doubleToRawLongBits(y) ^ seed);
int zi = doubleCast2Int(z); long hash = (((hashX ^ (hashX >>> 32)) + ((hashZ ^ (hashZ >>> 32)) << 32)) ^ seed) + Double.doubleToRawLongBits(z);
long base = ((hash(hash)) & 0x000fffffffffffffL)
return valCoord(seed, xi, yi, zi); + (0b01111111111L << 52); // Sign and exponent
return (Double.longBitsToDouble(base) - 1.5) * 2;
} }
private double singleWhiteNoise(int seed, double x, double y) { private double singleWhiteNoise(int seed, double x, double y) {
int xi = doubleCast2Int(x); long hashX = hash(Double.doubleToRawLongBits(x) ^ seed);
int yi = doubleCast2Int(y); long hashZ = hash(Double.doubleToRawLongBits(y) ^ seed);
long hash = ((hashX ^ (hashX >>> 32)) + ((hashZ ^ (hashZ >>> 32)) << 32)) ^ seed;
return valCoord(seed, xi, yi); long base = (hash(hash) & 0x000fffffffffffffL)
+ (0b01111111111L << 52); // Sign and exponent
return (Double.longBitsToDouble(base) - 1.5) * 2;
} }
// Simplex/OpenSimplex2 Noise // Simplex/OpenSimplex2 Noise

View File

@ -4,10 +4,10 @@ import com.dfsek.terra.api.math.noise.samplers.NoiseSampler;
import java.util.List; import java.util.List;
public class SimplexPalette<E> extends Palette<E> { public class NoisePalette<E> extends Palette<E> {
private final NoiseSampler r; private final NoiseSampler r;
public SimplexPalette(NoiseSampler r) { public NoisePalette(NoiseSampler r) {
this.r = r; this.r = r;
} }

View File

@ -15,28 +15,28 @@ public class TreeGeometry {
} }
public static Vector3 getPerpendicular(Vector3 v) { public static Vector3 getPerpendicular(Vector3 v) {
return v.getZ() < v.getX() ? new Vector3(v.getY(), - v.getX(), 0) : new Vector3(0, - v.getZ(), v.getY()); return v.getZ() < v.getX() ? new Vector3(v.getY(), -v.getX(), 0) : new Vector3(0, -v.getZ(), v.getY());
} }
public FractalTree getTree() { public FractalTree getTree() {
return tree; return tree;
} }
public void generateSphere(Location l, BlockData m, int radius, boolean overwrite) { public void generateSphere(Location l, BlockData m, int radius, boolean overwrite, Random r) {
generateSphere(l, new ProbabilityCollection<BlockData>().add(m, 1), radius, overwrite); generateSphere(l, new ProbabilityCollection<BlockData>().add(m, 1), radius, overwrite, r);
} }
public void generateCylinder(Location l, BlockData m, int radius, int height, boolean overwrite) { public void generateCylinder(Location l, BlockData m, int radius, int height, boolean overwrite, Random r) {
generateCylinder(l, new ProbabilityCollection<BlockData>().add(m, 1), radius, height, overwrite); generateCylinder(l, new ProbabilityCollection<BlockData>().add(m, 1), radius, height, overwrite, r);
} }
public void generateSphere(Location l, ProbabilityCollection<BlockData> m, int radius, boolean overwrite) { public void generateSphere(Location l, ProbabilityCollection<BlockData> m, int radius, boolean overwrite, Random r) {
for(int x = - radius; x <= radius; x++) { for(int x = -radius; x <= radius; x++) {
for(int y = - radius; y <= radius; y++) { for(int y = -radius; y <= radius; y++) {
for(int z = - radius; z <= radius; z++) { for(int z = -radius; z <= radius; z++) {
Vector3 position = l.toVector().clone().add(new Vector3(x, y, z)); Vector3 position = l.toVector().clone().add(new Vector3(x, y, z));
if(l.toVector().distance(position) <= radius + 0.5 && (overwrite || position.toLocation(l.getWorld()).getBlock().isEmpty())) if(l.toVector().distance(position) <= radius + 0.5 && (overwrite || position.toLocation(l.getWorld()).getBlock().isEmpty()))
tree.setBlock(position.toLocation(l.getWorld()), m.get()); tree.setBlock(position.toLocation(l.getWorld()), m.get(r));
} }
} }
} }
@ -48,19 +48,19 @@ public class TreeGeometry {
for(int z = -radius; z <= radius; z++) { for(int z = -radius; z <= radius; z++) {
Vector3 position = l.toVector().clone().add(new Vector3(x, y, z)); Vector3 position = l.toVector().clone().add(new Vector3(x, y, z));
if(r.nextInt(100) < sponginess && l.toVector().distance(position) <= radius + 0.5 && (overwrite || position.toLocation(l.getWorld()).getBlock().isEmpty())) if(r.nextInt(100) < sponginess && l.toVector().distance(position) <= radius + 0.5 && (overwrite || position.toLocation(l.getWorld()).getBlock().isEmpty()))
tree.setBlock(position.toLocation(l.getWorld()), m.get()); tree.setBlock(position.toLocation(l.getWorld()), m.get(r));
} }
} }
} }
} }
public void generateCylinder(Location l, ProbabilityCollection<BlockData> m, int radius, int height, boolean overwrite) { public void generateCylinder(Location l, ProbabilityCollection<BlockData> m, int radius, int height, boolean overwrite, Random r) {
for(int x = - radius; x <= radius; x++) { for(int x = -radius; x <= radius; x++) {
for(int y = 0; y <= height; y++) { for(int y = 0; y <= height; y++) {
for(int z = - radius; z <= radius; z++) { for(int z = -radius; z <= radius; z++) {
Vector3 position = l.toVector().clone().add(new Vector3(x, 0, z)); Vector3 position = l.toVector().clone().add(new Vector3(x, 0, z));
if(l.toVector().distance(position) <= radius + 0.5 && (overwrite || position.toLocation(l.getWorld()).getBlock().isEmpty())) if(l.toVector().distance(position) <= radius + 0.5 && (overwrite || position.toLocation(l.getWorld()).getBlock().isEmpty()))
tree.setBlock(position.toLocation(l.getWorld()), m.get()); tree.setBlock(position.toLocation(l.getWorld()), m.get(r));
} }
} }
} }

View File

@ -41,13 +41,13 @@ public class OakTree extends FractalTree {
BlockData wood = getMain().getWorldHandle().createBlockData("minecraft:oak_wood"); BlockData wood = getMain().getWorldHandle().createBlockData("minecraft:oak_wood");
BlockData leaves = getMain().getWorldHandle().createBlockData("minecraft:oak_leave"); BlockData leaves = getMain().getWorldHandle().createBlockData("minecraft:oak_leave");
if(recursions > 1) { if(recursions > 1) {
geo.generateSphere(l1, leaves, 1 + r.nextInt(2) + (3 - recursions), false); geo.generateSphere(l1, leaves, 1 + r.nextInt(2) + (3 - recursions), false, r);
if(recursions > 2) return; if(recursions > 2) return;
} }
if(diff.getY() < 0) diff.rotateAroundAxis(TreeGeometry.getPerpendicular(diff.clone()).normalize(), FastMath.PI); if(diff.getY() < 0) diff.rotateAroundAxis(TreeGeometry.getPerpendicular(diff.clone()).normalize(), FastMath.PI);
int d = (int) diff.length(); int d = (int) diff.length();
for(int i = 0; i < d; i++) { for(int i = 0; i < d; i++) {
geo.generateSphere(l1.clone().add(diff.clone().multiply((double) i / d)), wood, FastMath.max((int) d1, 0), true); geo.generateSphere(l1.clone().add(diff.clone().multiply((double) i / d)), wood, FastMath.max((int) d1, 0), true, r);
} }
double runningAngle = (double) 45 / (recursions + 1); double runningAngle = (double) 45 / (recursions + 1);
growBranch(l1.clone().add(diff), diff.clone().multiply(0.75).rotateAroundX(FastMath.toRadians(runningAngle + getNoise(r))).rotateAroundZ(FastMath.toRadians(getNoise(r))), growBranch(l1.clone().add(diff), diff.clone().multiply(0.75).rotateAroundX(FastMath.toRadians(runningAngle + getNoise(r))).rotateAroundZ(FastMath.toRadians(getNoise(r))),

View File

@ -50,13 +50,13 @@ public class ShatteredTree extends FractalTree {
private void growBranch(Location l1, Vector3 diff, double d1, int recursions, Random r) { private void growBranch(Location l1, Vector3 diff, double d1, int recursions, Random r) {
if(recursions > 2) { if(recursions > 2) {
geo.generateSphere(l1, leaves, 1 + r.nextInt(2), false); geo.generateSphere(l1, leaves, 1 + r.nextInt(2), false, r);
return; return;
} }
if(diff.getY() < 0) diff.rotateAroundAxis(TreeGeometry.getPerpendicular(diff.clone()).normalize(), FastMath.PI); if(diff.getY() < 0) diff.rotateAroundAxis(TreeGeometry.getPerpendicular(diff.clone()).normalize(), FastMath.PI);
int d = (int) diff.length(); int d = (int) diff.length();
for(int i = 0; i < d; i++) { for(int i = 0; i < d; i++) {
geo.generateSphere(l1.clone().add(diff.clone().multiply((double) i / d)), bark, FastMath.max((int) d1, 0), true); geo.generateSphere(l1.clone().add(diff.clone().multiply((double) i / d)), bark, FastMath.max((int) d1, 0), true, r);
} }
double runningAngle = (double) 45 / (recursions + 1); double runningAngle = (double) 45 / (recursions + 1);
growBranch(l1.clone().add(diff), diff.clone().multiply(0.9).rotateAroundX(FastMath.toRadians(runningAngle + getNoise(r))).rotateAroundZ(FastMath.toRadians(getNoise(r))), growBranch(l1.clone().add(diff), diff.clone().multiply(0.9).rotateAroundX(FastMath.toRadians(runningAngle + getNoise(r))).rotateAroundZ(FastMath.toRadians(getNoise(r))),

View File

@ -50,13 +50,13 @@ public class SmallShatteredTree extends FractalTree {
private void growBranch(Location l1, Vector3 diff, double d1, int recursions, Random r) { private void growBranch(Location l1, Vector3 diff, double d1, int recursions, Random r) {
if(recursions > 2) { if(recursions > 2) {
geo.generateSphere(l1, leaves, 1 + r.nextInt(2) + (3 - recursions), false); geo.generateSphere(l1, leaves, 1 + r.nextInt(2) + (3 - recursions), false, r);
return; return;
} }
if(diff.getY() < 0) diff.rotateAroundAxis(TreeGeometry.getPerpendicular(diff.clone()).normalize(), FastMath.PI); if(diff.getY() < 0) diff.rotateAroundAxis(TreeGeometry.getPerpendicular(diff.clone()).normalize(), FastMath.PI);
int d = (int) diff.length(); int d = (int) diff.length();
for(int i = 0; i < d; i++) { for(int i = 0; i < d; i++) {
geo.generateSphere(l1.clone().add(diff.clone().multiply((double) i / d)), bark, FastMath.max((int) d1, 0), true); geo.generateSphere(l1.clone().add(diff.clone().multiply((double) i / d)), bark, FastMath.max((int) d1, 0), true, r);
} }
double runningAngle = (double) 45 / (recursions + 1); double runningAngle = (double) 45 / (recursions + 1);
growBranch(l1.clone().add(diff), diff.clone().multiply(0.7).rotateAroundX(FastMath.toRadians(runningAngle + getNoise(r))).rotateAroundZ(FastMath.toRadians(getNoise(r))), growBranch(l1.clone().add(diff), diff.clone().multiply(0.7).rotateAroundX(FastMath.toRadians(runningAngle + getNoise(r))).rotateAroundZ(FastMath.toRadians(getNoise(r))),

View File

@ -33,19 +33,19 @@ public class SpruceTree extends FractalTree {
*/ */
@Override @Override
public void grow(Location origin, Random random) { public void grow(Location origin, Random random) {
growTrunk(origin.clone(), new Vector3(0, 16 + random.nextInt(5), 0)); growTrunk(origin.clone(), new Vector3(0, 16 + random.nextInt(5), 0), random);
} }
private void growTrunk(Location l1, Vector3 diff) { private void growTrunk(Location l1, Vector3 diff, Random r) {
if(diff.getY() < 0) diff.rotateAroundAxis(TreeGeometry.getPerpendicular(diff.clone()).normalize(), FastMath.PI); if(diff.getY() < 0) diff.rotateAroundAxis(TreeGeometry.getPerpendicular(diff.clone()).normalize(), FastMath.PI);
int d = (int) diff.length(); int d = (int) diff.length();
int rad = 7; int rad = 7;
BlockData wood = getMain().getWorldHandle().createBlockData("minecraft:spruce_wood"); BlockData wood = getMain().getWorldHandle().createBlockData("minecraft:spruce_wood");
BlockData leaves = getMain().getWorldHandle().createBlockData("minecraft:spruce_leave"); BlockData leaves = getMain().getWorldHandle().createBlockData("minecraft:spruce_leave");
for(int i = 0; i < d; i++) { for(int i = 0; i < d; i++) {
geo.generateSphere(l1.clone().add(diff.clone().multiply((double) i / d)), wood, (int) ((i > d * 0.65) ? 0.5 : 1.5), true); geo.generateSphere(l1.clone().add(diff.clone().multiply((double) i / d)), wood, (int) ((i > d * 0.65) ? 0.5 : 1.5), true, r);
if(i > 3) if(i > 3)
geo.generateCylinder(l1.clone().add(diff.clone().multiply((double) i / d)), leaves, (int) (((6 - (i % 4))) * (1.25 - ((double) i / d))), 1, false); geo.generateCylinder(l1.clone().add(diff.clone().multiply((double) i / d)), leaves, (int) (((6 - (i % 4))) * (1.25 - ((double) i / d))), 1, false, r);
} }
setBlock(l1.clone().add(diff), leaves); setBlock(l1.clone().add(diff), leaves);
setBlock(l1.clone().add(diff).add(0, 1, 0), leaves); setBlock(l1.clone().add(diff).add(0, 1, 0), leaves);

View File

@ -1,25 +1,16 @@
package com.dfsek.terra.config.factories; package com.dfsek.terra.config.factories;
import com.dfsek.terra.api.math.noise.samplers.FastNoiseLite;
import com.dfsek.terra.api.platform.TerraPlugin; import com.dfsek.terra.api.platform.TerraPlugin;
import com.dfsek.terra.api.platform.block.BlockData; import com.dfsek.terra.api.platform.block.BlockData;
import com.dfsek.terra.api.util.FastRandom; import com.dfsek.terra.api.world.palette.NoisePalette;
import com.dfsek.terra.api.world.palette.Palette; import com.dfsek.terra.api.world.palette.Palette;
import com.dfsek.terra.api.world.palette.RandomPalette;
import com.dfsek.terra.api.world.palette.SimplexPalette;
import com.dfsek.terra.biome.palette.PaletteLayer; import com.dfsek.terra.biome.palette.PaletteLayer;
import com.dfsek.terra.config.templates.PaletteTemplate; import com.dfsek.terra.config.templates.PaletteTemplate;
public class PaletteFactory implements TerraFactory<PaletteTemplate, Palette<BlockData>> { public class PaletteFactory implements TerraFactory<PaletteTemplate, Palette<BlockData>> {
@Override @Override
public Palette<BlockData> build(PaletteTemplate config, TerraPlugin main) { public Palette<BlockData> build(PaletteTemplate config, TerraPlugin main) {
Palette<BlockData> palette; Palette<BlockData> palette = new NoisePalette<>(config.getNoise().build(2403));
if(config.isSimplex()) {
FastNoiseLite noise = new FastNoiseLite((int) config.getSeed());
noise.setFrequency(config.getFrequency());
palette = new SimplexPalette<>(noise);
} else palette = new RandomPalette<>(new FastRandom(config.getSeed()));
for(PaletteLayer layer : config.getPalette()) { for(PaletteLayer layer : config.getPalette()) {
palette.add(layer.getLayer(), layer.getSize()); palette.add(layer.getLayer(), layer.getSize());
} }

View File

@ -3,12 +3,19 @@ package com.dfsek.terra.config.templates;
import com.dfsek.tectonic.annotations.Abstractable; import com.dfsek.tectonic.annotations.Abstractable;
import com.dfsek.tectonic.annotations.Default; import com.dfsek.tectonic.annotations.Default;
import com.dfsek.tectonic.annotations.Value; import com.dfsek.tectonic.annotations.Value;
import com.dfsek.terra.api.math.noise.samplers.FastNoiseLite;
import com.dfsek.terra.biome.palette.PaletteLayer; import com.dfsek.terra.biome.palette.PaletteLayer;
import com.dfsek.terra.generation.config.NoiseBuilder;
import java.util.List; import java.util.List;
@SuppressWarnings({"FieldMayBeFinal", "unused"}) @SuppressWarnings({"FieldMayBeFinal", "unused"})
public class PaletteTemplate extends AbstractableTemplate { public class PaletteTemplate extends AbstractableTemplate {
@Value("noise")
@Abstractable
@Default
private NoiseBuilder noise = new NoiseBuilder();
@Value("id") @Value("id")
private String id; private String id;
@ -16,38 +23,19 @@ public class PaletteTemplate extends AbstractableTemplate {
@Abstractable @Abstractable
private List<PaletteLayer> palette; private List<PaletteLayer> palette;
@Value("simplex") public PaletteTemplate() {
@Abstractable noise.setType(FastNoiseLite.NoiseType.WhiteNoise);
@Default }
private boolean simplex = false;
@Value("frequency")
@Abstractable
@Default
private double frequency = 0.02D;
@Value("seed")
@Abstractable
@Default
private long seed = 0;
public String getID() { public String getID() {
return id; return id;
} }
public double getFrequency() {
return frequency;
}
public long getSeed() {
return seed;
}
public List<PaletteLayer> getPalette() { public List<PaletteLayer> getPalette() {
return palette; return palette;
} }
public boolean isSimplex() { public NoiseBuilder getNoise() {
return simplex; return noise;
} }
} }

View File

@ -6,6 +6,7 @@ import com.dfsek.tectonic.config.ConfigTemplate;
import com.dfsek.terra.api.math.noise.samplers.FastNoiseLite; import com.dfsek.terra.api.math.noise.samplers.FastNoiseLite;
import com.dfsek.terra.api.math.noise.samplers.NoiseSampler; import com.dfsek.terra.api.math.noise.samplers.NoiseSampler;
@SuppressWarnings("FieldMayBeFinal")
public class NoiseBuilder implements ConfigTemplate { public class NoiseBuilder implements ConfigTemplate {
@Value("type") @Value("type")
@Default @Default
@ -39,7 +40,7 @@ public class NoiseBuilder implements ConfigTemplate {
@Default @Default
private double weightedStrength = 0.0D; private double weightedStrength = 0.0D;
@Value("offset") @Value("salt")
@Default @Default
private int seedOffset = 0; private int seedOffset = 0;
@ -71,6 +72,10 @@ public class NoiseBuilder implements ConfigTemplate {
@Default @Default
private int dimensions = 2; private int dimensions = 2;
@Value("cellular.lookup")
@Default
private NoiseBuilder lookup = new NoiseBuilder();
public NoiseSampler build(int seed) { public NoiseSampler build(int seed) {
FastNoiseLite noise = new FastNoiseLite(seed + seedOffset); FastNoiseLite noise = new FastNoiseLite(seed + seedOffset);
if(!fractalType.equals(FastNoiseLite.FractalType.None)) { if(!fractalType.equals(FastNoiseLite.FractalType.None)) {
@ -85,6 +90,7 @@ public class NoiseBuilder implements ConfigTemplate {
noise.setCellularDistanceFunction(cellularDistanceFunction); noise.setCellularDistanceFunction(cellularDistanceFunction);
noise.setCellularReturnType(cellularReturnType); noise.setCellularReturnType(cellularReturnType);
noise.setCellularJitter(cellularJitter); noise.setCellularJitter(cellularJitter);
noise.setCellularNoiseLookup(lookup.build(seed));
} }
noise.setNoiseType(type); noise.setNoiseType(type);