Initial work on supporting chunk based bending and interpolation in Layered Generator

This commit is contained in:
Zoë Gidiere 2025-06-07 01:15:39 -06:00
parent 619c2156cd
commit 3269f9ec8b
15 changed files with 335 additions and 32 deletions

View File

@ -1,6 +1,10 @@
package com.dfsek.terra.addons.chunkgenerator;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.terra.addons.chunkgenerator.config.sampler.ElevationLayerSamplerTemplate;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.ElevationLayerSampler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -34,7 +38,7 @@ import com.dfsek.terra.addons.chunkgenerator.config.predicate.SamplerListLayerPr
import com.dfsek.terra.addons.chunkgenerator.config.resolve.PaletteLayerResolverTemplate;
import com.dfsek.terra.addons.chunkgenerator.config.resolve.PredicateLayerResolverTemplate;
import com.dfsek.terra.addons.chunkgenerator.config.sampler.BiomeDefinedLayerSamplerTemplate;
import com.dfsek.terra.addons.chunkgenerator.config.sampler.SimpleLayerSamplerTemplate;
import com.dfsek.terra.addons.chunkgenerator.config.sampler.DensityLayerSamplerTemplate;
import com.dfsek.terra.addons.chunkgenerator.generation.LayeredChunkGenerator;
import com.dfsek.terra.addons.chunkgenerator.layer.palette.BiomeDefinedLayerPalette;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.BiomeDefinedLayerSampler;
@ -55,7 +59,7 @@ import com.dfsek.terra.api.world.chunk.generation.util.provider.ChunkGeneratorPr
public class LayeredChunkGeneratorAddon implements AddonInitializer {
private static final Logger logger = LoggerFactory.getLogger(LayeredChunkGenerator.class);
private static final Logger logger = LoggerFactory.getLogger( LayeredChunkGeneratorAddon.class);
public static final TypeKey<Supplier<ObjectTemplate<PointSet>>> POINT_SET_TYPE_TOKEN = new TypeKey<>() {
};
@ -116,7 +120,8 @@ public class LayeredChunkGeneratorAddon implements AddonInitializer {
.then(event -> {
CheckedRegistry<Supplier<ObjectTemplate<LayerSampler>>> samplerTypeRegistry = event.getPack().getOrCreateRegistry(LAYER_SAMPLER_TYPE_TOKEN);
CheckedRegistry<InstanceWrapper<LayerSampler>> samplerRegistry = event.getPack().getOrCreateRegistry(LAYER_SAMPLER_TOKEN);
samplerTypeRegistry.register(addon.key("SIMPLE"), SimpleLayerSamplerTemplate::new);
samplerTypeRegistry.register(addon.key("DENSITY"), DensityLayerSamplerTemplate::new);
samplerTypeRegistry.register(addon.key("ELEVATION"), ElevationLayerSamplerTemplate::new);
samplerTypeRegistry.register(addon.key("BIOME_DEFINED"), BiomeDefinedLayerSamplerTemplate::new);
event.loadTemplate(new LayerSamplerPackConfigTemplate()).getSamplers().forEach((key, sampler) -> {

View File

@ -1,9 +1,12 @@
package com.dfsek.terra.addons.chunkgenerator.api;
import com.dfsek.terra.addons.chunkgenerator.api.chunk.ChunkLayerSampler;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.info.WorldProperties;
public interface LayerSampler {
double sample(int x, int y, int z, WorldProperties world, BiomeProvider biomeProvider);
ChunkLayerSampler getChunk(int chunkX, int chunkZ, WorldProperties world, BiomeProvider biomeProvider);
double getBlendWeight();
}

View File

@ -0,0 +1,9 @@
package com.dfsek.terra.addons.chunkgenerator.api.chunk;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.info.WorldProperties;
public interface ChunkLayerSampler {
double sample(int fmX, int y, int fmZ);
}

View File

@ -10,7 +10,7 @@ import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.seismic.type.sampler.Sampler;
public class BiomeDefinedLayerSamplerTemplate implements ObjectTemplate<LayerSampler> {
public class BiomeDefinedLayerSamplerTemplate extends LayerSamplerTemplate {
@Value("default")
@Default
@ -18,6 +18,6 @@ public class BiomeDefinedLayerSamplerTemplate implements ObjectTemplate<LayerSam
@Override
public LayerSampler get() {
return new BiomeDefinedLayerSampler(defaultSampler);
return new BiomeDefinedLayerSampler(defaultSampler, blend);
}
}

View File

@ -1,21 +1,20 @@
package com.dfsek.terra.addons.chunkgenerator.config.sampler;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
import com.dfsek.terra.addons.chunkgenerator.api.LayerSampler;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.SimpleLayerSampler;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.DensityLayerSampler;
import com.dfsek.terra.api.config.meta.Meta;
import com.dfsek.seismic.type.sampler.Sampler;
public class SimpleLayerSamplerTemplate implements ObjectTemplate<LayerSampler> {
public class DensityLayerSamplerTemplate extends LayerSamplerTemplate {
@Value("sampler")
private @Meta Sampler sampler;
@Override
public LayerSampler get() {
return new SimpleLayerSampler(sampler);
return new DensityLayerSampler(sampler, blend);
}
}

View File

@ -0,0 +1,21 @@
package com.dfsek.terra.addons.chunkgenerator.config.sampler;
import com.dfsek.seismic.type.sampler.Sampler;
import com.dfsek.tectonic.api.config.template.annotations.Value;
import com.dfsek.terra.addons.chunkgenerator.api.LayerSampler;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.DensityLayerSampler;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.ElevationLayerSampler;
import com.dfsek.terra.api.config.meta.Meta;
public class ElevationLayerSamplerTemplate extends LayerSamplerTemplate {
@Value("sampler")
private @Meta Sampler sampler;
@Override
public LayerSampler get() {
return new ElevationLayerSampler(sampler, blend);
}
}

View File

@ -0,0 +1,15 @@
package com.dfsek.terra.addons.chunkgenerator.config.sampler;
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.chunkgenerator.api.LayerSampler;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.blend.BlendProperties;
import com.dfsek.terra.api.config.meta.Meta;
public abstract class LayerSamplerTemplate implements ObjectTemplate<LayerSampler> {
@Value("blend")
protected @Meta BlendProperties blend;
}

View File

@ -0,0 +1,33 @@
package com.dfsek.terra.addons.chunkgenerator.config.sampler.blend;
import com.dfsek.seismic.type.sampler.DerivativeSampler;
import com.dfsek.tectonic.api.config.template.ValidatedConfigTemplate;
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.tectonic.api.exception.ValidationException;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.blend.BlendProperties;
import com.dfsek.terra.api.config.meta.Meta;
public class BlendPropertiesConfig implements ValidatedConfigTemplate, ObjectTemplate<BlendProperties> {
@Value("density")
@Default
private @Meta int density = 3;
@Value("weight")
@Default
private @Meta double weight = 1;
@Override
public boolean validate() throws ValidationException {
return density > 1 && weight > 1 && density % 18 == 0;
}
@Override
public BlendProperties get() {
return BlendProperties.of(density, weight);
}
}

View File

@ -3,6 +3,9 @@ package com.dfsek.terra.addons.chunkgenerator.layer.sampler;
import com.dfsek.tectonic.api.config.template.dynamic.DynamicTemplate;
import com.dfsek.tectonic.api.config.template.dynamic.DynamicValue;
import com.dfsek.terra.addons.chunkgenerator.api.chunk.ChunkLayerSampler;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.blend.BlendProperties;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
@ -24,9 +27,11 @@ import com.dfsek.terra.api.world.info.WorldProperties;
public class BiomeDefinedLayerSampler implements LayerSampler {
private final Sampler defaultSampler;
private final BlendProperties blendProperties;
public BiomeDefinedLayerSampler(@Nullable Sampler defaultSampler) {
public BiomeDefinedLayerSampler(@Nullable Sampler defaultSampler, BlendProperties blendProperties) {
this.defaultSampler = defaultSampler;
this.blendProperties = blendProperties;
}
@Override
@ -39,6 +44,16 @@ public class BiomeDefinedLayerSampler implements LayerSampler {
.getSample(world.getSeed(), x, y, z);
}
@Override
public ChunkLayerSampler getChunk(int chunkX, int chunkZ, WorldProperties world, BiomeProvider biomeProvider) {
return null;
}
@Override
public double getBlendWeight() {
return blendProperties.weight();
}
private Optional<Sampler> getDefaultSampler() {
return Optional.ofNullable(defaultSampler);
}

View File

@ -0,0 +1,41 @@
package com.dfsek.terra.addons.chunkgenerator.layer.sampler;
import com.dfsek.seismic.math.floatingpoint.FloatingPointFunctions;
import com.dfsek.seismic.math.numericanalysis.interpolation.InterpolationFunctions;
import com.dfsek.terra.addons.chunkgenerator.api.LayerSampler;
import com.dfsek.seismic.type.sampler.Sampler;
import com.dfsek.terra.addons.chunkgenerator.api.chunk.ChunkLayerSampler;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.blend.BlendProperties;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.chunk.DensityChunkLayerSampler;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.info.WorldProperties;
public class DensityLayerSampler implements LayerSampler {
private final Sampler sampler;
private final BlendProperties blendProperties;
public DensityLayerSampler(Sampler sampler, BlendProperties blendProperties) {
this.sampler = sampler;
this.blendProperties = blendProperties;
}
@Override
public double sample(int x, int y, int z, WorldProperties world, BiomeProvider biomeProvider) {
//TODO if needed make this match chunk impl
return sampler.getSample(world.getSeed(), x, y, z);
}
@Override
public ChunkLayerSampler getChunk(int chunkX, int chunkZ, WorldProperties world, BiomeProvider biomeProvider) {
return new DensityChunkLayerSampler(chunkX, chunkZ, world, biomeProvider, this, blendProperties);
}
@Override
public double getBlendWeight() {
return blendProperties.weight();
}
}

View File

@ -0,0 +1,39 @@
package com.dfsek.terra.addons.chunkgenerator.layer.sampler;
import com.dfsek.seismic.type.sampler.Sampler;
import com.dfsek.terra.addons.chunkgenerator.api.LayerSampler;
import com.dfsek.terra.addons.chunkgenerator.api.chunk.ChunkLayerSampler;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.blend.BlendProperties;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.chunk.DensityChunkLayerSampler;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.chunk.ElevationChunkLayerSampler;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.info.WorldProperties;
public class ElevationLayerSampler implements LayerSampler {
private final Sampler sampler;
private final BlendProperties blendProperties;
public ElevationLayerSampler(Sampler sampler, BlendProperties blendProperties) {
this.sampler = sampler;
this.blendProperties = blendProperties;
}
@Override
public double sample(int x, int y, int z, WorldProperties world, BiomeProvider biomeProvider) {
//TODO if needed make this match chunk impl
return sampler.getSample(world.getSeed(), x, z);
}
@Override
public ChunkLayerSampler getChunk(int chunkX, int chunkZ, WorldProperties world, BiomeProvider biomeProvider) {
return new ElevationChunkLayerSampler(chunkX, chunkZ, world, biomeProvider, this, blendProperties);
}
@Override
public double getBlendWeight() {
return blendProperties.weight();
}
}

View File

@ -1,21 +0,0 @@
package com.dfsek.terra.addons.chunkgenerator.layer.sampler;
import com.dfsek.terra.addons.chunkgenerator.api.LayerSampler;
import com.dfsek.seismic.type.sampler.Sampler;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.info.WorldProperties;
public class SimpleLayerSampler implements LayerSampler {
private Sampler sampler;
public SimpleLayerSampler(Sampler sampler) {
this.sampler = sampler;
}
@Override
public double sample(int x, int y, int z, WorldProperties world, BiomeProvider biomeProvider) {
return sampler.getSample(world.getSeed(), x, y, z);
}
}

View File

@ -0,0 +1,7 @@
package com.dfsek.terra.addons.chunkgenerator.layer.sampler.blend;
public record BlendProperties(int density, double weight, int extent) {
public static BlendProperties of(int density, double weight) {
return new BlendProperties(density, weight, Math.max((18 / density) + 1, 1));
}
}

View File

@ -0,0 +1,100 @@
package com.dfsek.terra.addons.chunkgenerator.layer.sampler.chunk;
import com.dfsek.seismic.math.floatingpoint.FloatingPointFunctions;
import com.dfsek.seismic.math.numericanalysis.interpolation.InterpolationFunctions;
import com.dfsek.terra.addons.chunkgenerator.api.LayerSampler;
import com.dfsek.terra.addons.chunkgenerator.api.chunk.ChunkLayerSampler;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.DensityLayerSampler;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.blend.BlendProperties;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.info.WorldProperties;
public class DensityChunkLayerSampler implements ChunkLayerSampler {
double[] samples;
private final int blendExtent;
private final int min;
private final int blendExtentYExtent;
private final int blendExtentMinus2;
private final int blendYExtendMinus2;
private final int blendDensity;
public DensityChunkLayerSampler(int chunkX, int chunkZ, WorldProperties world, BiomeProvider biomeProvider, DensityLayerSampler layerSampler, BlendProperties blendProperties) {
this.min = world.getMinHeight() - 1;
int worldMax = world.getMaxHeight() + 1;
blendDensity = blendProperties.density();
int blendYRange = worldMax - min + 1;
int max;
int blendYExtend;
if (blendYRange % blendDensity == 0) {
blendYExtend = blendYRange / blendDensity;
max = worldMax;
} else {
blendYExtend = (blendYRange / blendDensity) + 1;
max = worldMax + 1;
}
blendExtent = blendProperties.extent();
blendExtentYExtent = blendYExtend * blendExtent;
blendExtentMinus2 = blendExtent - 2;
blendYExtendMinus2 = blendYExtend - 2;
samples = new double[blendExtentYExtent * blendExtent];
int xOrigin = chunkX << 4;
int zOrigin = chunkZ << 4;
for (int x = 0; x < 18; x += blendDensity) {
for (int z = 0; z < 18; z += blendDensity) {
int cx = xOrigin + x;
int cz = zOrigin + z;
for (int y = this.min; y <= max; y++) {
int yi = y - this.min;
int index = x * blendExtentYExtent + yi * blendExtent + z;
samples[index] = layerSampler.sample(cx, y, cz, world, biomeProvider);
}
}
}
}
private double getSample(int x, int y, int z) {
int index = x * blendExtentYExtent + y * blendExtent + z;
return samples[index];
}
@Override
public double sample(int fmX, int y, int fmZ) {
double gx = (double)(fmX + 1) / blendDensity;
double gz = (double)(fmZ + 1) / blendDensity;
double gy = (double)(y - min) / blendDensity;
int x0 = Math.max(0, Math.min(blendExtentMinus2, FloatingPointFunctions.floor(gx)));
int z0 = Math.max(0, Math.min(blendExtentMinus2, FloatingPointFunctions.floor(gz)));
int y0 = Math.max(0, Math.min(blendYExtendMinus2, FloatingPointFunctions.floor(gy)));
int x1 = x0 + 1;
int z1 = z0 + 1;
int y1 = y0 + 1;
double tx = gx - x0;
double tz = gz - z0;
double ty = gy - y0;
// Fetch 8 corners
double c000 = getSample(x0, y0, z0);
double c100 = getSample(x1, y0, z0);
double c010 = getSample(x0, y1, z0);
double c110 = getSample(x1, y1, z0);
double c001 = getSample(x0, y0, z1);
double c101 = getSample(x1, y0, z1);
double c011 = getSample(x0, y1, z1);
double c111 = getSample(x1, y1, z1);
return InterpolationFunctions.triLerp(c000, c100, c010, c110, c001, c101, c011, c111, tx, ty, tz);
}
}

View File

@ -0,0 +1,37 @@
package com.dfsek.terra.addons.chunkgenerator.layer.sampler.chunk;
import com.dfsek.terra.addons.chunkgenerator.api.LayerSampler;
import com.dfsek.terra.addons.chunkgenerator.api.chunk.ChunkLayerSampler;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.ElevationLayerSampler;
import com.dfsek.terra.addons.chunkgenerator.layer.sampler.blend.BlendProperties;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.info.WorldProperties;
public class ElevationChunkLayerSampler implements ChunkLayerSampler {
double[] samples;
public ElevationChunkLayerSampler(int chunkX, int chunkZ, WorldProperties world, BiomeProvider biomeProvider, ElevationLayerSampler layerSampler,
BlendProperties blendProperties) {
//I see no reason to implement sparse blending here, elevation is inexpensive. If that changes, it can be easily implemented here.
samples = new double[16 * 16];
int xOrigin = chunkX << 4;
int zOrigin = chunkZ << 4;
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
int cx = xOrigin + x;
int cz = zOrigin + z;
int index = x * 16 + z;
samples[index] = layerSampler.sample(cx, 0, cz, world, biomeProvider);
}
}
}
@Override
public double sample(int fmX, int y, int fmZ) {
int index = fmX * 16 + fmZ;
return samples[index];
}
}