mirror of
https://github.com/PolyhedralDev/Terra.git
synced 2026-02-16 10:30:42 +00:00
@@ -1,8 +1,8 @@
|
||||
preRelease(true)
|
||||
|
||||
versionProjects(":common:api", version("6.2.1"))
|
||||
versionProjects(":common:implementation", version("6.2.1"))
|
||||
versionProjects(":platforms", version("6.2.1"))
|
||||
versionProjects(":common:api", version("6.3.0"))
|
||||
versionProjects(":common:implementation", version("6.3.0"))
|
||||
versionProjects(":platforms", version("6.3.0"))
|
||||
|
||||
|
||||
allprojects {
|
||||
|
||||
@@ -4,7 +4,7 @@ object Versions {
|
||||
const val paralithic = "0.7.0"
|
||||
const val strata = "1.1.1"
|
||||
|
||||
const val cloud = "1.7.0"
|
||||
const val cloud = "1.8.0"
|
||||
|
||||
const val slf4j = "1.7.36"
|
||||
const val log4j_slf4j_impl = "2.14.1"
|
||||
@@ -19,40 +19,38 @@ object Versions {
|
||||
|
||||
object Fabric {
|
||||
const val fabricLoader = "0.14.8"
|
||||
const val fabricAPI = "0.57.0+1.19"
|
||||
const val fabricAPI = "0.78.0+1.19.4"
|
||||
}
|
||||
|
||||
object Quilt {
|
||||
const val quiltLoader = "0.17.0"
|
||||
const val fabricApi = "2.0.0-beta.4+0.57.0-1.19"
|
||||
const val fabricApi = "6.0.0-beta.3+0.76.0-1.19.4"
|
||||
}
|
||||
|
||||
object Mod {
|
||||
const val mixin = "0.11.2+mixin.0.8.5"
|
||||
|
||||
const val minecraft = "1.19"
|
||||
const val yarn = "$minecraft+build.1"
|
||||
const val minecraft = "1.19.4"
|
||||
const val yarn = "$minecraft+build.2"
|
||||
const val fabricLoader = "0.14.2"
|
||||
|
||||
const val architecuryLoom = "0.12.0.290"
|
||||
const val architecturyPlugin = "3.4-SNAPSHOT"
|
||||
|
||||
const val loomQuiltflower = "1.7.1"
|
||||
|
||||
const val lazyDfu = "0.1.2"
|
||||
}
|
||||
|
||||
object Forge {
|
||||
const val forge = "${Mod.minecraft}-41.0.63"
|
||||
const val forge = "${Mod.minecraft}-45.0.43"
|
||||
const val burningwave = "12.53.0"
|
||||
}
|
||||
|
||||
object Bukkit {
|
||||
const val paper = "1.18.2-R0.1-SNAPSHOT"
|
||||
const val paperLib = "1.0.5"
|
||||
const val minecraft = "1.19.2"
|
||||
const val minecraft = "1.19.4"
|
||||
const val reflectionRemapper = "0.1.0-SNAPSHOT"
|
||||
const val paperDevBundle = "1.19.2-R0.1-SNAPSHOT"
|
||||
const val paperDevBundle = "1.19.4-R0.1-SNAPSHOT"
|
||||
}
|
||||
|
||||
object Sponge {
|
||||
|
||||
21
common/addons/biome-provider-image-v2/LICENSE
Normal file
21
common/addons/biome-provider-image-v2/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2021 Polyhedral Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
5
common/addons/biome-provider-image-v2/README.md
Normal file
5
common/addons/biome-provider-image-v2/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# biome-provider-image-v2
|
||||
|
||||
Implements and registers the `IMAGE` biome provider, which
|
||||
utilizes various config types provided by the `library-image` addon to
|
||||
distribute biomes based on images.
|
||||
12
common/addons/biome-provider-image-v2/build.gradle.kts
Normal file
12
common/addons/biome-provider-image-v2/build.gradle.kts
Normal file
@@ -0,0 +1,12 @@
|
||||
version = version("1.0.0")
|
||||
|
||||
dependencies {
|
||||
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
|
||||
compileOnlyApi(project(":common:addons:library-image"))
|
||||
implementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
|
||||
testImplementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
|
||||
}
|
||||
|
||||
tasks.named<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar") {
|
||||
relocate("net.jafama", "com.dfsek.terra.addons.biome.image.lib.jafama")
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2021 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.biome.image.v2;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import com.dfsek.terra.addons.image.converter.ColorConverter;
|
||||
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
|
||||
import com.dfsek.terra.api.world.biome.Biome;
|
||||
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
|
||||
|
||||
|
||||
public class ImageBiomeProvider implements BiomeProvider {
|
||||
private final int resolution;
|
||||
|
||||
private final ColorConverter<Biome> colorConverter;
|
||||
|
||||
private final ColorSampler colorSampler;
|
||||
|
||||
public ImageBiomeProvider(ColorConverter<Biome> colorConverter, ColorSampler colorSampler, int resolution) {
|
||||
this.resolution = resolution;
|
||||
this.colorConverter = colorConverter;
|
||||
this.colorSampler = colorSampler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z, long seed) {
|
||||
return getBiome(x, z);
|
||||
}
|
||||
|
||||
public Biome getBiome(int x, int z) {
|
||||
x /= resolution;
|
||||
z /= resolution;
|
||||
return colorConverter.apply(colorSampler.apply(x, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Biome> getBaseBiome(int x, int z, long seed) {
|
||||
return Optional.of(getBiome(x, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Biome> getBiomes() {
|
||||
return colorConverter.getEntries();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2021 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.biome.image.v2;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.dfsek.terra.addons.biome.image.v2.config.ImageProviderTemplate;
|
||||
import com.dfsek.terra.addons.biome.image.v2.config.converter.ClosestBiomeColorConverterTemplate;
|
||||
import com.dfsek.terra.addons.biome.image.v2.config.converter.ExactBiomeColorConverterTemplate;
|
||||
import com.dfsek.terra.addons.biome.image.v2.config.converter.mapping.DefinedBiomeColorMappingTemplate;
|
||||
import com.dfsek.terra.addons.image.converter.ColorConverter;
|
||||
import com.dfsek.terra.addons.image.converter.mapping.BiomeDefinedColorMapping;
|
||||
import com.dfsek.terra.addons.image.converter.mapping.ColorMapping;
|
||||
import com.dfsek.terra.addons.manifest.api.AddonInitializer;
|
||||
import com.dfsek.terra.api.Platform;
|
||||
import com.dfsek.terra.api.addon.BaseAddon;
|
||||
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.registry.CheckedRegistry;
|
||||
import com.dfsek.terra.api.util.reflection.TypeKey;
|
||||
import com.dfsek.terra.api.world.biome.Biome;
|
||||
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
|
||||
|
||||
|
||||
public class ImageBiomeProviderAddon implements AddonInitializer {
|
||||
public static final TypeKey<Supplier<ObjectTemplate<BiomeProvider>>> PROVIDER_REGISTRY_KEY = new TypeKey<>() {
|
||||
};
|
||||
|
||||
public static final TypeKey<Supplier<ObjectTemplate<ColorConverter<Biome>>>> BIOME_COLOR_CONVERTER_REGISTRY_KEY = new TypeKey<>() {
|
||||
};
|
||||
|
||||
public static final TypeKey<Supplier<ObjectTemplate<ColorMapping<Biome>>>> BIOME_COLOR_MAPPING_REGISTRY_KEY = new TypeKey<>() {
|
||||
};
|
||||
|
||||
@Inject
|
||||
private Platform platform;
|
||||
|
||||
@Inject
|
||||
private BaseAddon addon;
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
platform.getEventManager()
|
||||
.getHandler(FunctionalEventHandler.class)
|
||||
.register(addon, ConfigPackPreLoadEvent.class)
|
||||
.priority(501)
|
||||
.then(event -> {
|
||||
CheckedRegistry<Supplier<ObjectTemplate<BiomeProvider>>> providerRegistry = event.getPack().getOrCreateRegistry(
|
||||
PROVIDER_REGISTRY_KEY);
|
||||
providerRegistry.register(addon.key("IMAGE"), ImageProviderTemplate::new);
|
||||
})
|
||||
.then(event -> {
|
||||
CheckedRegistry<Supplier<ObjectTemplate<ColorConverter<Biome>>>> biomeColorConverterRegistry = event.getPack().getOrCreateRegistry(
|
||||
BIOME_COLOR_CONVERTER_REGISTRY_KEY);
|
||||
biomeColorConverterRegistry.register(addon.key("EXACT"), ExactBiomeColorConverterTemplate::new);
|
||||
biomeColorConverterRegistry.register(addon.key("CLOSEST"), ClosestBiomeColorConverterTemplate::new);
|
||||
})
|
||||
.then(event -> {
|
||||
CheckedRegistry<Supplier<ObjectTemplate<ColorMapping<Biome>>>> biomeColorMappingRegistry = event.getPack().getOrCreateRegistry(
|
||||
BIOME_COLOR_MAPPING_REGISTRY_KEY);
|
||||
biomeColorMappingRegistry.register(addon.key("USE_BIOME_COLORS"), () -> () -> new BiomeDefinedColorMapping<>(event.getPack().getRegistry(Biome.class), b -> b));
|
||||
biomeColorMappingRegistry.register(addon.key("MAP"), DefinedBiomeColorMappingTemplate::new);
|
||||
})
|
||||
.failThrough();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2021 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.biome.image.v2.config;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Default;
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Description;
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
|
||||
|
||||
import com.dfsek.terra.addons.biome.image.v2.ImageBiomeProvider;
|
||||
import com.dfsek.terra.addons.image.converter.ColorConverter;
|
||||
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
|
||||
import com.dfsek.terra.api.world.biome.Biome;
|
||||
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
|
||||
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
public class ImageProviderTemplate implements ObjectTemplate<BiomeProvider> {
|
||||
|
||||
@Value("resolution")
|
||||
@Default
|
||||
@Description("Sets the resolution at which to sample the image.")
|
||||
private int resolution = 1;
|
||||
|
||||
@Value("color-sampler")
|
||||
private ColorSampler colorSampler;
|
||||
|
||||
@Value("color-conversion")
|
||||
private ColorConverter<Biome> colorConverter;
|
||||
|
||||
@Override
|
||||
public BiomeProvider get() {
|
||||
return new ImageBiomeProvider(colorConverter, colorSampler, resolution);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.dfsek.terra.addons.biome.image.v2.config.converter;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
|
||||
import com.dfsek.terra.addons.image.config.converter.ClosestColorConverterTemplate;
|
||||
import com.dfsek.terra.addons.image.converter.mapping.ColorMapping;
|
||||
import com.dfsek.terra.api.world.biome.Biome;
|
||||
|
||||
|
||||
public class ClosestBiomeColorConverterTemplate extends ClosestColorConverterTemplate<Biome> {
|
||||
|
||||
@Value("match")
|
||||
private ColorMapping<Biome> match;
|
||||
|
||||
@Override
|
||||
protected ColorMapping<Biome> getMapping() {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.dfsek.terra.addons.biome.image.v2.config.converter;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Default;
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
|
||||
import com.dfsek.terra.addons.image.config.converter.ExactColorConverterTemplate;
|
||||
import com.dfsek.terra.addons.image.converter.mapping.ColorMapping;
|
||||
import com.dfsek.terra.api.world.biome.Biome;
|
||||
|
||||
|
||||
public class ExactBiomeColorConverterTemplate extends ExactColorConverterTemplate<Biome> {
|
||||
|
||||
@Value("match")
|
||||
private ColorMapping<Biome> match;
|
||||
|
||||
@Value("else")
|
||||
private Biome fallback;
|
||||
|
||||
@Value("ignore-alpha")
|
||||
@Default
|
||||
private boolean ignoreAlpha = true;
|
||||
|
||||
@Override
|
||||
protected ColorMapping<Biome> getMapping() {
|
||||
return match;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Biome getFallback() {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean ignoreAlpha() {
|
||||
return ignoreAlpha;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.dfsek.terra.addons.biome.image.v2.config.converter.mapping;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.dfsek.terra.addons.image.config.ColorLoader.ColorString;
|
||||
import com.dfsek.terra.addons.image.converter.mapping.ColorMapping;
|
||||
import com.dfsek.terra.addons.image.util.MapUtil;
|
||||
import com.dfsek.terra.api.world.biome.Biome;
|
||||
|
||||
|
||||
public class DefinedBiomeColorMappingTemplate implements ObjectTemplate<ColorMapping<Biome>> {
|
||||
|
||||
@Value("map")
|
||||
Map<ColorString, Biome> map;
|
||||
|
||||
@Override
|
||||
public ColorMapping<Biome> get() {
|
||||
var map = MapUtil.mapKeys(this.map, ColorString::getColor);
|
||||
return () -> map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
schema-version: 1
|
||||
contributors:
|
||||
- Terra contributors
|
||||
id: biome-provider-image-v2
|
||||
version: @VERSION@
|
||||
entrypoints:
|
||||
- "com.dfsek.terra.addons.biome.image.v2.ImageBiomeProviderAddon"
|
||||
website:
|
||||
issues: https://github.com/PolyhedralDev/Terra/issues
|
||||
source: https://github.com/PolyhedralDev/Terra
|
||||
docs: https://terra.polydev.org
|
||||
license: MIT License
|
||||
depends:
|
||||
library-image: "1.+"
|
||||
@@ -8,6 +8,8 @@
|
||||
package com.dfsek.terra.addons.biome.image;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -24,6 +26,9 @@ import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
|
||||
|
||||
|
||||
public class ImageBiomeProviderAddon implements AddonInitializer {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ImageBiomeProviderAddon.class);
|
||||
|
||||
public static final TypeKey<Supplier<ObjectTemplate<BiomeProvider>>> PROVIDER_REGISTRY_KEY = new TypeKey<>() {
|
||||
};
|
||||
|
||||
@@ -45,5 +50,6 @@ public class ImageBiomeProviderAddon implements AddonInitializer {
|
||||
() -> new ImageProviderTemplate(event.getPack().getRegistry(Biome.class)));
|
||||
})
|
||||
.failThrough();
|
||||
logger.warn("The biome-provider-image addon is deprecated and scheduled for removal in Terra 7.0. It is recommended to use the biome-provider-image-v2 addon for future pack development instead.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import net.jafama.FastMath;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
@@ -97,6 +98,11 @@ public class PipelineBiomeProvider implements BiomeProvider {
|
||||
return biomes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Biome> getBaseBiome(int x, int z, long seed) {
|
||||
return Optional.of(getBiome(x, z, seed));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Column<Biome> getColumn(int x, int z, long seed, int min, int max) {
|
||||
return new BiomePipelineColumn(this, min, max, x, z, seed);
|
||||
|
||||
@@ -11,9 +11,10 @@ import com.dfsek.terra.addons.chunkgenerator.config.NoiseChunkGeneratorPackConfi
|
||||
import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseConfigTemplate;
|
||||
import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseProperties;
|
||||
import com.dfsek.terra.addons.chunkgenerator.config.palette.BiomePaletteTemplate;
|
||||
import com.dfsek.terra.addons.chunkgenerator.config.palette.PaletteInfo;
|
||||
import com.dfsek.terra.addons.chunkgenerator.config.palette.SlantLayer;
|
||||
import com.dfsek.terra.addons.chunkgenerator.config.palette.slant.SlantLayerTemplate;
|
||||
import com.dfsek.terra.addons.chunkgenerator.generation.NoiseChunkGenerator3D;
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.BiomePaletteInfo;
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder;
|
||||
import com.dfsek.terra.addons.manifest.api.AddonInitializer;
|
||||
import com.dfsek.terra.api.Platform;
|
||||
import com.dfsek.terra.api.addon.BaseAddon;
|
||||
@@ -36,15 +37,20 @@ public class NoiseChunkGenerator3DAddon implements AddonInitializer {
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
PropertyKey<PaletteInfo> paletteInfoPropertyKey = Context.create(PaletteInfo.class);
|
||||
PropertyKey<BiomePaletteInfo> paletteInfoPropertyKey = Context.create(BiomePaletteInfo.class);
|
||||
PropertyKey<BiomeNoiseProperties> noisePropertiesPropertyKey = Context.create(BiomeNoiseProperties.class);
|
||||
platform.getEventManager()
|
||||
.getHandler(FunctionalEventHandler.class)
|
||||
.register(addon, ConfigPackPreLoadEvent.class)
|
||||
.priority(1000)
|
||||
.then(event -> {
|
||||
|
||||
event.getPack().applyLoader(SlantHolder.CalculationMethod.class,
|
||||
(type, o, loader, depthTracker) -> SlantHolder.CalculationMethod.valueOf((String) o));
|
||||
|
||||
NoiseChunkGeneratorPackConfigTemplate config = event.loadTemplate(new NoiseChunkGeneratorPackConfigTemplate());
|
||||
|
||||
event.getPack().getContext().put(config);
|
||||
|
||||
event.getPack()
|
||||
.getOrCreateRegistry(ChunkGeneratorProvider.class)
|
||||
.register(addon.key("NOISE_3D"),
|
||||
@@ -53,7 +59,7 @@ public class NoiseChunkGenerator3DAddon implements AddonInitializer {
|
||||
config.getVerticalRes(), noisePropertiesPropertyKey,
|
||||
paletteInfoPropertyKey));
|
||||
event.getPack()
|
||||
.applyLoader(SlantLayer.class, SlantLayer::new);
|
||||
.applyLoader(SlantHolder.Layer.class, SlantLayerTemplate::new);
|
||||
})
|
||||
.failThrough();
|
||||
|
||||
@@ -62,8 +68,10 @@ public class NoiseChunkGenerator3DAddon implements AddonInitializer {
|
||||
.register(addon, ConfigurationLoadEvent.class)
|
||||
.then(event -> {
|
||||
if(event.is(Biome.class)) {
|
||||
NoiseChunkGeneratorPackConfigTemplate config = event.getPack().getContext().get(NoiseChunkGeneratorPackConfigTemplate.class);
|
||||
|
||||
event.getLoadedObject(Biome.class).getContext().put(paletteInfoPropertyKey,
|
||||
event.load(new BiomePaletteTemplate(platform)).get());
|
||||
event.load(new BiomePaletteTemplate(platform, config.getSlantCalculationMethod())).get());
|
||||
event.getLoadedObject(Biome.class).getContext().put(noisePropertiesPropertyKey,
|
||||
event.load(new BiomeNoiseConfigTemplate()).get());
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@ import com.dfsek.tectonic.api.config.template.ConfigTemplate;
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Default;
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder;
|
||||
import com.dfsek.terra.api.config.meta.Meta;
|
||||
import com.dfsek.terra.api.properties.Properties;
|
||||
|
||||
|
||||
public class NoiseChunkGeneratorPackConfigTemplate implements ConfigTemplate {
|
||||
public class NoiseChunkGeneratorPackConfigTemplate implements ConfigTemplate, Properties {
|
||||
@Value("blend.terrain.elevation")
|
||||
@Default
|
||||
private @Meta int elevationBlend = 4;
|
||||
@@ -20,6 +22,10 @@ public class NoiseChunkGeneratorPackConfigTemplate implements ConfigTemplate {
|
||||
@Default
|
||||
private @Meta int verticalRes = 2;
|
||||
|
||||
@Value("slant.calculation-method")
|
||||
@Default
|
||||
private SlantHolder.@Meta CalculationMethod slantCalculationMethod = SlantHolder.CalculationMethod.Derivative;
|
||||
|
||||
public int getElevationBlend() {
|
||||
return elevationBlend;
|
||||
}
|
||||
@@ -31,4 +37,8 @@ public class NoiseChunkGeneratorPackConfigTemplate implements ConfigTemplate {
|
||||
public int getVerticalRes() {
|
||||
return verticalRes;
|
||||
}
|
||||
|
||||
public SlantHolder.CalculationMethod getSlantCalculationMethod() {
|
||||
return slantCalculationMethod;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,25 +15,23 @@ import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder;
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolderBuilder;
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.SlantHolder;
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.BiomePaletteInfo;
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder;
|
||||
import com.dfsek.terra.api.Platform;
|
||||
import com.dfsek.terra.api.block.state.BlockState;
|
||||
import com.dfsek.terra.api.config.meta.Meta;
|
||||
import com.dfsek.terra.api.world.chunk.generation.util.Palette;
|
||||
|
||||
|
||||
public class BiomePaletteTemplate implements ObjectTemplate<PaletteInfo> {
|
||||
public class BiomePaletteTemplate implements ObjectTemplate<BiomePaletteInfo> {
|
||||
private final Platform platform;
|
||||
|
||||
@Value("slant")
|
||||
@Default
|
||||
@Description("The slant palettes to use in this biome.")
|
||||
private @Meta List<@Meta SlantLayer> slant = Collections.emptyList();
|
||||
private @Meta List<SlantHolder.@Meta Layer> slantLayers = Collections.emptyList();
|
||||
|
||||
@Value("slant-depth")
|
||||
@Default
|
||||
@@ -63,27 +61,16 @@ public class BiomePaletteTemplate implements ObjectTemplate<PaletteInfo> {
|
||||
@Default
|
||||
private @Meta boolean updatePalette = false;
|
||||
|
||||
public BiomePaletteTemplate(Platform platform) { this.platform = platform; }
|
||||
private final SlantHolder.CalculationMethod slantCalculationMethod;
|
||||
|
||||
public BiomePaletteTemplate(Platform platform, SlantHolder.CalculationMethod slantCalculationMethod) {
|
||||
this.platform = platform;
|
||||
this.slantCalculationMethod = slantCalculationMethod;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaletteInfo get() {
|
||||
PaletteHolderBuilder builder = new PaletteHolderBuilder();
|
||||
for(Map<Palette, Integer> layer : palettes) {
|
||||
for(Entry<Palette, Integer> entry : layer.entrySet()) {
|
||||
builder.add(entry.getValue(), entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
TreeMap<Double, PaletteHolder> slantLayers = new TreeMap<>();
|
||||
double minThreshold = Double.MAX_VALUE;
|
||||
|
||||
for(SlantLayer layer : slant) {
|
||||
double threshold = layer.getThreshold();
|
||||
if(threshold < minThreshold) minThreshold = threshold;
|
||||
slantLayers.put(threshold, layer.getPalette());
|
||||
}
|
||||
|
||||
return new PaletteInfo(builder.build(), SlantHolder.of(slantLayers, minThreshold), oceanPalette, seaLevel, slantDepth,
|
||||
updatePalette);
|
||||
public BiomePaletteInfo get() {
|
||||
return new BiomePaletteInfo(PaletteHolder.of(palettes), SlantHolder.of(slantLayers, slantDepth, slantCalculationMethod),
|
||||
oceanPalette, seaLevel, updatePalette);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2021 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.chunkgenerator.config.palette;
|
||||
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder;
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.SlantHolder;
|
||||
import com.dfsek.terra.api.properties.Properties;
|
||||
import com.dfsek.terra.api.world.chunk.generation.util.Palette;
|
||||
|
||||
|
||||
public record PaletteInfo(PaletteHolder paletteHolder,
|
||||
SlantHolder slantHolder,
|
||||
Palette ocean,
|
||||
int seaLevel,
|
||||
int maxSlantDepth,
|
||||
boolean updatePaletteWhenCarving) implements Properties {
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.dfsek.terra.addons.chunkgenerator.config.palette;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder;
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolderBuilder;
|
||||
import com.dfsek.terra.api.config.meta.Meta;
|
||||
import com.dfsek.terra.api.world.chunk.generation.util.Palette;
|
||||
|
||||
|
||||
public class SlantLayer implements ObjectTemplate<SlantLayer> {
|
||||
@Value("threshold")
|
||||
private @Meta double threshold;
|
||||
|
||||
@Value("palette")
|
||||
private @Meta List<@Meta Map<@Meta Palette, @Meta Integer>> palettes;
|
||||
|
||||
@Override
|
||||
public SlantLayer get() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public double getThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
public PaletteHolder getPalette() {
|
||||
PaletteHolderBuilder builder = new PaletteHolderBuilder();
|
||||
for(Map<Palette, Integer> layer : palettes) {
|
||||
for(Entry<Palette, Integer> entry : layer.entrySet()) {
|
||||
builder.add(entry.getValue(), entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.dfsek.terra.addons.chunkgenerator.config.palette.slant;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder;
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder;
|
||||
import com.dfsek.terra.api.config.meta.Meta;
|
||||
import com.dfsek.terra.api.world.chunk.generation.util.Palette;
|
||||
|
||||
|
||||
public class SlantLayerTemplate implements ObjectTemplate<SlantHolder.Layer> {
|
||||
|
||||
@Value("threshold")
|
||||
private @Meta double threshold;
|
||||
|
||||
@Value("palette")
|
||||
private @Meta List<@Meta Map<@Meta Palette, @Meta Integer>> palettes;
|
||||
|
||||
@Override
|
||||
public SlantHolder.Layer get() {
|
||||
return new SlantHolder.Layer(PaletteHolder.of(palettes), threshold);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import net.jafama.FastMath;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import com.dfsek.terra.addons.chunkgenerator.config.noise.BiomeNoiseProperties;
|
||||
import com.dfsek.terra.addons.chunkgenerator.config.palette.PaletteInfo;
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.BiomePaletteInfo;
|
||||
import com.dfsek.terra.addons.chunkgenerator.generation.math.PaletteUtil;
|
||||
import com.dfsek.terra.addons.chunkgenerator.generation.math.interpolation.LazilyEvaluatedInterpolator;
|
||||
import com.dfsek.terra.addons.chunkgenerator.generation.math.samplers.Sampler3D;
|
||||
@@ -40,13 +40,13 @@ public class NoiseChunkGenerator3D implements ChunkGenerator {
|
||||
private final int carverHorizontalResolution;
|
||||
private final int carverVerticalResolution;
|
||||
|
||||
private final PropertyKey<PaletteInfo> paletteInfoPropertyKey;
|
||||
private final PropertyKey<BiomePaletteInfo> paletteInfoPropertyKey;
|
||||
private final PropertyKey<BiomeNoiseProperties> noisePropertiesKey;
|
||||
|
||||
public NoiseChunkGenerator3D(ConfigPack pack, Platform platform, int elevationBlend, int carverHorizontalResolution,
|
||||
int carverVerticalResolution,
|
||||
PropertyKey<BiomeNoiseProperties> noisePropertiesKey,
|
||||
PropertyKey<PaletteInfo> paletteInfoPropertyKey) {
|
||||
PropertyKey<BiomePaletteInfo> paletteInfoPropertyKey) {
|
||||
this.platform = platform;
|
||||
this.air = platform.getWorldHandle().air();
|
||||
this.carverHorizontalResolution = carverHorizontalResolution;
|
||||
@@ -97,7 +97,7 @@ public class NoiseChunkGenerator3D implements ChunkGenerator {
|
||||
for(int y = world.getMaxHeight() - 1; y >= world.getMinHeight(); y--) {
|
||||
Biome biome = biomeColumn.get(y);
|
||||
|
||||
PaletteInfo paletteInfo = biome.getContext().get(paletteInfoPropertyKey);
|
||||
BiomePaletteInfo paletteInfo = biome.getContext().get(paletteInfoPropertyKey);
|
||||
|
||||
int sea = paletteInfo.seaLevel();
|
||||
Palette seaPalette = paletteInfo.ocean();
|
||||
@@ -131,7 +131,7 @@ public class NoiseChunkGenerator3D implements ChunkGenerator {
|
||||
Biome biome = biomeProvider.getBiome(x, y, z, world.getSeed());
|
||||
Sampler3D sampler = samplerCache.get(x, z, world, biomeProvider);
|
||||
|
||||
PaletteInfo paletteInfo = biome.getContext().get(paletteInfoPropertyKey);
|
||||
BiomePaletteInfo paletteInfo = biome.getContext().get(paletteInfoPropertyKey);
|
||||
|
||||
int fdX = FastMath.floorMod(x, 16);
|
||||
int fdZ = FastMath.floorMod(z, 16);
|
||||
|
||||
@@ -7,40 +7,23 @@
|
||||
|
||||
package com.dfsek.terra.addons.chunkgenerator.generation.math;
|
||||
|
||||
import com.dfsek.terra.addons.chunkgenerator.config.palette.PaletteInfo;
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.BiomePaletteInfo;
|
||||
import com.dfsek.terra.addons.chunkgenerator.generation.math.samplers.Sampler3D;
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.SlantHolder;
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder;
|
||||
import com.dfsek.terra.api.world.chunk.generation.util.Palette;
|
||||
|
||||
|
||||
public final class PaletteUtil {
|
||||
/**
|
||||
* Derivative constant.
|
||||
*/
|
||||
private static final double DERIVATIVE_DIST = 0.55;
|
||||
|
||||
public static Palette getPalette(int x, int y, int z, Sampler3D sampler, PaletteInfo paletteInfo, int depth) {
|
||||
SlantHolder slant = paletteInfo.slantHolder();
|
||||
if(!slant.isEmpty() && depth <= paletteInfo.maxSlantDepth()) {
|
||||
double slope = derivative(sampler, x, y, z);
|
||||
if(slope > slant.getMinSlope()) {
|
||||
return slant.getPalette(slope).getPalette(y);
|
||||
public static Palette getPalette(int x, int y, int z, Sampler3D sampler, BiomePaletteInfo paletteInfo, int depth) {
|
||||
SlantHolder slantHolder = paletteInfo.slantHolder();
|
||||
if(slantHolder.isAboveDepth(depth)) {
|
||||
double slant = slantHolder.calculateSlant(sampler, x, y, z);
|
||||
if(slantHolder.isInSlantThreshold(slant)) {
|
||||
return slantHolder.getPalette(slant).getPalette(y);
|
||||
}
|
||||
}
|
||||
|
||||
return paletteInfo.paletteHolder().getPalette(y);
|
||||
}
|
||||
|
||||
public static double derivative(Sampler3D sampler, double x, double y, double z) {
|
||||
double baseSample = sampler.sample(x, y, z);
|
||||
|
||||
double xVal1 = (sampler.sample(x + DERIVATIVE_DIST, y, z) - baseSample) / DERIVATIVE_DIST;
|
||||
double xVal2 = (sampler.sample(x - DERIVATIVE_DIST, y, z) - baseSample) / DERIVATIVE_DIST;
|
||||
double zVal1 = (sampler.sample(x, y, z + DERIVATIVE_DIST) - baseSample) / DERIVATIVE_DIST;
|
||||
double zVal2 = (sampler.sample(x, y, z - DERIVATIVE_DIST) - baseSample) / DERIVATIVE_DIST;
|
||||
double yVal1 = (sampler.sample(x, y + DERIVATIVE_DIST, z) - baseSample) / DERIVATIVE_DIST;
|
||||
double yVal2 = (sampler.sample(x, y - DERIVATIVE_DIST, z) - baseSample) / DERIVATIVE_DIST;
|
||||
|
||||
return Math.sqrt(((xVal2 - xVal1) * (xVal2 - xVal1)) + ((zVal2 - zVal1) * (zVal2 - zVal1)) + ((yVal2 - yVal1) * (yVal2 - yVal1)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2021 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.chunkgenerator.palette;
|
||||
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.slant.SlantHolder;
|
||||
import com.dfsek.terra.api.properties.Properties;
|
||||
import com.dfsek.terra.api.world.chunk.generation.util.Palette;
|
||||
|
||||
|
||||
public record BiomePaletteInfo(PaletteHolder paletteHolder,
|
||||
SlantHolder slantHolder,
|
||||
Palette ocean,
|
||||
int seaLevel,
|
||||
boolean updatePaletteWhenCarving) implements Properties {
|
||||
}
|
||||
@@ -7,6 +7,13 @@
|
||||
|
||||
package com.dfsek.terra.addons.chunkgenerator.palette;
|
||||
|
||||
import net.jafama.FastMath;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.dfsek.terra.api.world.chunk.generation.util.Palette;
|
||||
|
||||
|
||||
@@ -27,4 +34,43 @@ public class PaletteHolder {
|
||||
: palettes[palettes.length - 1]
|
||||
: palettes[0];
|
||||
}
|
||||
|
||||
public static PaletteHolder of(List<Map<Palette, Integer>> palettes) {
|
||||
PaletteHolderBuilder builder = new PaletteHolderBuilder();
|
||||
for(Map<Palette, Integer> layer : palettes) {
|
||||
for(Entry<Palette, Integer> entry : layer.entrySet()) {
|
||||
builder.add(entry.getValue(), entry.getKey());
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static class PaletteHolderBuilder {
|
||||
private final TreeMap<Integer, Palette> paletteMap = new TreeMap<>();
|
||||
|
||||
public PaletteHolderBuilder add(int y, Palette palette) {
|
||||
paletteMap.put(y, palette);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PaletteHolder build() {
|
||||
|
||||
int min = FastMath.min(paletteMap.keySet().stream().min(Integer::compareTo).orElse(0), 0);
|
||||
int max = FastMath.max(paletteMap.keySet().stream().max(Integer::compareTo).orElse(255), 255);
|
||||
|
||||
Palette[] palettes = new Palette[paletteMap.lastKey() + 1 - min];
|
||||
for(int y = min; y <= FastMath.max(paletteMap.lastKey(), max); y++) {
|
||||
Palette d = null;
|
||||
for(Entry<Integer, Palette> e : paletteMap.entrySet()) {
|
||||
if(e.getKey() >= y) {
|
||||
d = e.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(d == null) throw new IllegalArgumentException("No palette for Y=" + y);
|
||||
palettes[y - min] = d;
|
||||
}
|
||||
return new PaletteHolder(palettes, -min);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2021 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.chunkgenerator.palette;
|
||||
|
||||
import net.jafama.FastMath;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.dfsek.terra.api.world.chunk.generation.util.Palette;
|
||||
|
||||
|
||||
public class PaletteHolderBuilder {
|
||||
private final TreeMap<Integer, Palette> paletteMap = new TreeMap<>();
|
||||
|
||||
public PaletteHolderBuilder add(int y, Palette palette) {
|
||||
paletteMap.put(y, palette);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PaletteHolder build() {
|
||||
|
||||
int min = FastMath.min(paletteMap.keySet().stream().min(Integer::compareTo).orElse(0), 0);
|
||||
int max = FastMath.max(paletteMap.keySet().stream().max(Integer::compareTo).orElse(255), 255);
|
||||
|
||||
Palette[] palettes = new Palette[paletteMap.lastKey() + 1 - min];
|
||||
for(int y = min; y <= FastMath.max(paletteMap.lastKey(), max); y++) {
|
||||
Palette d = null;
|
||||
for(Map.Entry<Integer, Palette> e : paletteMap.entrySet()) {
|
||||
if(e.getKey() >= y) {
|
||||
d = e.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(d == null) throw new IllegalArgumentException("No palette for Y=" + y);
|
||||
palettes[y - min] = d;
|
||||
}
|
||||
return new PaletteHolder(palettes, -min);
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2021 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.chunkgenerator.palette;
|
||||
|
||||
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
|
||||
|
||||
public class SlantHolder {
|
||||
private final TreeMap<Double, PaletteHolder> layers;
|
||||
private final double minSlope;
|
||||
|
||||
private SlantHolder(TreeMap<Double, PaletteHolder> layers, double minSlope) {
|
||||
this.layers = layers;
|
||||
this.minSlope = minSlope;
|
||||
}
|
||||
|
||||
public static SlantHolder of(TreeMap<Double, PaletteHolder> layers, double minSlope) {
|
||||
if(layers.size() == 1) {
|
||||
Entry<Double, PaletteHolder> firstEntry = layers.firstEntry();
|
||||
return new Single(firstEntry.getValue(), minSlope);
|
||||
}
|
||||
return new SlantHolder(layers, minSlope);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return layers.isEmpty();
|
||||
}
|
||||
|
||||
public PaletteHolder getPalette(double slope) {
|
||||
return layers.floorEntry(slope).getValue();
|
||||
}
|
||||
|
||||
public double getMinSlope() {
|
||||
return minSlope;
|
||||
}
|
||||
|
||||
private static final class Single extends SlantHolder {
|
||||
|
||||
private final PaletteHolder layers;
|
||||
|
||||
public Single(PaletteHolder layers, double minSlope) {
|
||||
super(of(minSlope, layers), minSlope);
|
||||
this.layers = layers;
|
||||
}
|
||||
|
||||
private static TreeMap<Double, PaletteHolder> of(double v, PaletteHolder layer) {
|
||||
TreeMap<Double, PaletteHolder> map = new TreeMap<>();
|
||||
map.put(v, layer);
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaletteHolder getPalette(double slope) {
|
||||
return layers;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2020-2021 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.chunkgenerator.palette.slant;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder;
|
||||
|
||||
|
||||
public class MultipleSlantHolder extends SlantHolderImpl {
|
||||
private final NavigableMap<Double, PaletteHolder> layers;
|
||||
private final double slantThreshold;
|
||||
|
||||
MultipleSlantHolder(List<SlantHolder.Layer> slant, int slantDepth, CalculationMethod calculationMethod) {
|
||||
super(slantDepth, calculationMethod);
|
||||
NavigableMap<Double, PaletteHolder> layers = new TreeMap<>(slant.stream().collect(Collectors.toMap(SlantHolder.Layer::threshold, SlantHolder.Layer::palette)));
|
||||
Stream<Double> thresholds = layers.keySet().stream();
|
||||
double slantThreshold = floorToThreshold ?
|
||||
thresholds.min(Double::compare).orElseThrow() :
|
||||
thresholds.max(Double::compare).orElseThrow();
|
||||
this.layers = layers;
|
||||
this.slantThreshold = slantThreshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double getSlantThreshold() {
|
||||
return slantThreshold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaletteHolder getPalette(double slant) {
|
||||
return (floorToThreshold ?
|
||||
layers.floorEntry(slant) :
|
||||
layers.ceilingEntry(slant)
|
||||
).getValue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.dfsek.terra.addons.chunkgenerator.palette.slant;
|
||||
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder;
|
||||
|
||||
|
||||
final class SingleSlantHolder extends SlantHolderImpl {
|
||||
|
||||
private final SlantHolder.Layer layer;
|
||||
|
||||
public SingleSlantHolder(SlantHolder.Layer layer, int slantDepth, CalculationMethod calculationMethod) {
|
||||
super(slantDepth, calculationMethod);
|
||||
this.layer = layer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaletteHolder getPalette(double slant) {
|
||||
return layer.palette();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double getSlantThreshold() {
|
||||
return layer.threshold();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.dfsek.terra.addons.chunkgenerator.palette.slant;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.dfsek.terra.addons.chunkgenerator.generation.math.samplers.Sampler3D;
|
||||
import com.dfsek.terra.addons.chunkgenerator.palette.PaletteHolder;
|
||||
import com.dfsek.terra.api.util.vector.Vector3;
|
||||
|
||||
|
||||
public interface SlantHolder {
|
||||
|
||||
static SlantHolder of(List<SlantHolder.Layer> layers, int slantDepth, CalculationMethod calculationMethod) {
|
||||
if(layers.isEmpty()) {
|
||||
return EMPTY;
|
||||
} else if(layers.size() == 1) {
|
||||
return new SingleSlantHolder(layers.get(0), slantDepth, calculationMethod);
|
||||
}
|
||||
return new MultipleSlantHolder(layers, slantDepth, calculationMethod);
|
||||
}
|
||||
|
||||
double calculateSlant(Sampler3D sampler, double x, double y, double z);
|
||||
|
||||
boolean isAboveDepth(int depth);
|
||||
|
||||
boolean isInSlantThreshold(double slant);
|
||||
|
||||
PaletteHolder getPalette(double slant);
|
||||
|
||||
record Layer(PaletteHolder palette, double threshold) {
|
||||
}
|
||||
|
||||
enum CalculationMethod {
|
||||
DotProduct {
|
||||
private static final Vector3 DOT_PRODUCT_DIRECTION = Vector3.of(0, 1, 0);
|
||||
|
||||
private static final Vector3[] DOT_PRODUCT_SAMPLE_POINTS = {
|
||||
Vector3.of(0, 0, -DERIVATIVE_DIST),
|
||||
Vector3.of(0, 0, DERIVATIVE_DIST),
|
||||
Vector3.of(0, -DERIVATIVE_DIST, 0),
|
||||
Vector3.of(0, DERIVATIVE_DIST, 0),
|
||||
Vector3.of(-DERIVATIVE_DIST, 0, 0),
|
||||
Vector3.of(DERIVATIVE_DIST, 0, 0)
|
||||
};
|
||||
|
||||
@Override
|
||||
public double slant(Sampler3D sampler, double x, double y, double z) {
|
||||
Vector3.Mutable normalApproximation = Vector3.Mutable.of(0, 0, 0);
|
||||
for(Vector3 point : DOT_PRODUCT_SAMPLE_POINTS) {
|
||||
var scalar = -sampler.sample(x+point.getX(), y+point.getY(), z+point.getZ());
|
||||
normalApproximation.add(point.mutable().multiply(scalar));
|
||||
}
|
||||
return DOT_PRODUCT_DIRECTION.dot(normalApproximation.normalize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean floorToThreshold() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
Derivative {
|
||||
@Override
|
||||
public double slant(Sampler3D sampler, double x, double y, double z) {
|
||||
double baseSample = sampler.sample(x, y, z);
|
||||
|
||||
double xVal1 = (sampler.sample(x + DERIVATIVE_DIST, y, z) - baseSample) / DERIVATIVE_DIST;
|
||||
double xVal2 = (sampler.sample(x - DERIVATIVE_DIST, y, z) - baseSample) / DERIVATIVE_DIST;
|
||||
double zVal1 = (sampler.sample(x, y, z + DERIVATIVE_DIST) - baseSample) / DERIVATIVE_DIST;
|
||||
double zVal2 = (sampler.sample(x, y, z - DERIVATIVE_DIST) - baseSample) / DERIVATIVE_DIST;
|
||||
double yVal1 = (sampler.sample(x, y + DERIVATIVE_DIST, z) - baseSample) / DERIVATIVE_DIST;
|
||||
double yVal2 = (sampler.sample(x, y - DERIVATIVE_DIST, z) - baseSample) / DERIVATIVE_DIST;
|
||||
|
||||
return Math.sqrt(((xVal2 - xVal1) * (xVal2 - xVal1)) + ((zVal2 - zVal1) * (zVal2 - zVal1)) + ((yVal2 - yVal1) * (yVal2 - yVal1)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean floorToThreshold() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private static final double DERIVATIVE_DIST = 0.55;
|
||||
|
||||
public abstract double slant(Sampler3D sampler, double x, double y, double z);
|
||||
|
||||
/*
|
||||
* Controls whether palettes should be applied before or after their respective thresholds.
|
||||
*
|
||||
* If true, slant values will map to the palette of the next floor threshold, otherwise they
|
||||
* will map to the ceiling.
|
||||
*/
|
||||
public abstract boolean floorToThreshold();
|
||||
}
|
||||
|
||||
SlantHolder EMPTY = new SlantHolder() {
|
||||
@Override
|
||||
public double calculateSlant(Sampler3D sampler, double x, double y, double z) {
|
||||
throw new UnsupportedOperationException("Empty holder should not calculate slant");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAboveDepth(int depth) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInSlantThreshold(double slant) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaletteHolder getPalette(double slant) {
|
||||
throw new UnsupportedOperationException("Empty holder cannot return a palette");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.dfsek.terra.addons.chunkgenerator.palette.slant;
|
||||
|
||||
import com.dfsek.terra.addons.chunkgenerator.generation.math.samplers.Sampler3D;
|
||||
|
||||
|
||||
public abstract class SlantHolderImpl implements SlantHolder {
|
||||
private final SlantHolder.CalculationMethod calculationMethod;
|
||||
|
||||
private final int slantDepth;
|
||||
|
||||
protected final boolean floorToThreshold;
|
||||
|
||||
protected SlantHolderImpl(int slantDepth, CalculationMethod calculationMethod) {
|
||||
this.floorToThreshold = calculationMethod.floorToThreshold();
|
||||
this.calculationMethod = calculationMethod;
|
||||
this.slantDepth = slantDepth;
|
||||
}
|
||||
|
||||
protected abstract double getSlantThreshold();
|
||||
|
||||
@Override
|
||||
public final double calculateSlant(Sampler3D sampler, double x, double y, double z) {
|
||||
return calculationMethod.slant(sampler, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isAboveDepth(int depth) {
|
||||
return depth <= slantDepth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isInSlantThreshold(double slant) {
|
||||
return (floorToThreshold ?
|
||||
slant > getSlantThreshold() :
|
||||
slant < getSlantThreshold()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -92,9 +92,9 @@ public class NoiseAddon implements AddonInitializer {
|
||||
noiseRegistry.register(addon.key("PROBABILITY"), ProbabilityNormalizerTemplate::new);
|
||||
noiseRegistry.register(addon.key("SCALE"), ScaleNormalizerTemplate::new);
|
||||
noiseRegistry.register(addon.key("POSTERIZATION"), PosterizationNormalizerTemplate::new);
|
||||
|
||||
|
||||
noiseRegistry.register(addon.key("IMAGE"), ImageSamplerTemplate::new);
|
||||
|
||||
|
||||
noiseRegistry.register(addon.key("DOMAIN_WARP"), DomainWarpTemplate::new);
|
||||
|
||||
noiseRegistry.register(addon.key("FBM"), BrownianMotionTemplate::new);
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
package com.dfsek.terra.addons.noise.config.templates;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
@@ -19,6 +21,10 @@ import com.dfsek.terra.api.noise.NoiseSampler;
|
||||
@SuppressWarnings({ "unused", "FieldMayBeFinal" })
|
||||
public class ImageSamplerTemplate extends SamplerTemplate<ImageSampler> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ImageSamplerTemplate.class);
|
||||
|
||||
private static boolean used = false;
|
||||
|
||||
@Value("image")
|
||||
private @Meta BufferedImage image;
|
||||
|
||||
@@ -30,6 +36,12 @@ public class ImageSamplerTemplate extends SamplerTemplate<ImageSampler> {
|
||||
|
||||
@Override
|
||||
public NoiseSampler get() {
|
||||
if(!used) {
|
||||
logger.warn("The IMAGE NoiseSampler implemented by the config-noise-function addon is deprecated. " +
|
||||
"It is recommended to use the IMAGE NoiseSampler implemented by the config-noise-image " +
|
||||
"addon instead.");
|
||||
used = true;
|
||||
}
|
||||
return new ImageSampler(image, channel, frequency);
|
||||
}
|
||||
}
|
||||
|
||||
8
common/addons/library-image/build.gradle.kts
Normal file
8
common/addons/library-image/build.gradle.kts
Normal file
@@ -0,0 +1,8 @@
|
||||
version = version("1.0.0")
|
||||
|
||||
dependencies {
|
||||
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
|
||||
|
||||
implementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
|
||||
testImplementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.dfsek.terra.addons.image;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.dfsek.terra.addons.image.config.ColorLoader;
|
||||
import com.dfsek.terra.addons.image.config.ColorLoader.ColorString;
|
||||
import com.dfsek.terra.addons.image.config.noisesampler.ChannelNoiseSamplerTemplate;
|
||||
import com.dfsek.terra.addons.image.config.noisesampler.DistanceTransformNoiseSamplerTemplate;
|
||||
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.colorsampler.ConstantColorSamplerTemplate;
|
||||
import com.dfsek.terra.addons.image.config.colorsampler.image.SingleImageColorSamplerTemplate;
|
||||
import com.dfsek.terra.addons.image.config.colorsampler.image.TileImageColorSamplerTemplate;
|
||||
import com.dfsek.terra.addons.image.config.colorsampler.mutate.RotateColorSamplerTemplate;
|
||||
import com.dfsek.terra.addons.image.config.colorsampler.mutate.TranslateColorSamplerTemplate;
|
||||
import com.dfsek.terra.addons.image.image.Image;
|
||||
import com.dfsek.terra.addons.image.operator.DistanceTransform;
|
||||
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
|
||||
import com.dfsek.terra.addons.manifest.api.AddonInitializer;
|
||||
import com.dfsek.terra.api.Platform;
|
||||
import com.dfsek.terra.api.addon.BaseAddon;
|
||||
import com.dfsek.terra.api.config.ConfigPack;
|
||||
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.NoiseSampler;
|
||||
import com.dfsek.terra.api.registry.CheckedRegistry;
|
||||
import com.dfsek.terra.api.util.reflection.TypeKey;
|
||||
|
||||
|
||||
public class ImageLibraryAddon implements AddonInitializer {
|
||||
|
||||
public static final TypeKey<Supplier<ObjectTemplate<Image>>> IMAGE_REGISTRY_KEY = new TypeKey<>() {
|
||||
};
|
||||
|
||||
public static final TypeKey<Supplier<ObjectTemplate<ColorSampler>>> COLOR_PICKER_REGISTRY_KEY = new TypeKey<>() {
|
||||
};
|
||||
|
||||
public static final TypeKey<Supplier<ObjectTemplate<NoiseSampler>>> NOISE_SAMPLER_TOKEN = new TypeKey<>() {
|
||||
};
|
||||
@Inject
|
||||
private Platform platform;
|
||||
|
||||
@Inject
|
||||
private BaseAddon addon;
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
platform.getEventManager()
|
||||
.getHandler(FunctionalEventHandler.class)
|
||||
.register(addon, ConfigPackPreLoadEvent.class)
|
||||
.priority(10)
|
||||
.then(event -> {
|
||||
ConfigPack pack = event.getPack();
|
||||
CheckedRegistry<Supplier<ObjectTemplate<Image>>> imageRegistry = pack.getOrCreateRegistry(IMAGE_REGISTRY_KEY);
|
||||
imageRegistry.register(addon.key("BITMAP"), () -> new ImageTemplate(pack.getLoader(), pack));
|
||||
imageRegistry.register(addon.key("STITCHED_BITMAP"), () -> new StitchedImageTemplate(pack.getLoader(), pack));
|
||||
})
|
||||
.then(event -> {
|
||||
event.getPack()
|
||||
.applyLoader(DistanceTransform.CostFunction.class,
|
||||
(type, o, loader, depthTracker) -> DistanceTransform.CostFunction.valueOf((String) o))
|
||||
.applyLoader(DistanceTransform.Normalization.class,
|
||||
(type, o, loader, depthTracker) -> DistanceTransform.Normalization.valueOf((String) o))
|
||||
.applyLoader(ColorString.class, new ColorLoader());
|
||||
|
||||
CheckedRegistry<Supplier<ObjectTemplate<NoiseSampler>>> noiseRegistry = event.getPack().getOrCreateRegistry(
|
||||
NOISE_SAMPLER_TOKEN);
|
||||
noiseRegistry.register(addon.key("DISTANCE_TRANSFORM"), DistanceTransformNoiseSamplerTemplate::new);
|
||||
noiseRegistry.register(addon.key("CHANNEL"), ChannelNoiseSamplerTemplate::new);
|
||||
})
|
||||
.then(event -> {
|
||||
CheckedRegistry<Supplier<ObjectTemplate<ColorSampler>>> colorSamplerRegistry = event.getPack().getOrCreateRegistry(
|
||||
COLOR_PICKER_REGISTRY_KEY);
|
||||
colorSamplerRegistry.register(addon.key("SINGLE_IMAGE"), SingleImageColorSamplerTemplate::new);
|
||||
colorSamplerRegistry.register(addon.key("TILED_IMAGE"), TileImageColorSamplerTemplate::new);
|
||||
colorSamplerRegistry.register(addon.key("COLOR"), ConstantColorSamplerTemplate::new);
|
||||
colorSamplerRegistry.register(addon.key("ROTATE"), RotateColorSamplerTemplate::new);
|
||||
colorSamplerRegistry.register(addon.key("TRANSLATE"), TranslateColorSamplerTemplate::new);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.dfsek.terra.addons.image.colorsampler;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ColorSampler {
|
||||
|
||||
/**
|
||||
* @param x World x coordinate
|
||||
* @param z World z coordinate
|
||||
* @return Integer representing a web color
|
||||
*/
|
||||
int apply(int x, int z);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.dfsek.terra.addons.image.colorsampler.image;
|
||||
|
||||
import com.dfsek.terra.addons.image.image.Image;
|
||||
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
|
||||
import com.dfsek.terra.addons.image.colorsampler.image.transform.ImageTransformation;
|
||||
|
||||
|
||||
public class SingleImageColorSampler implements ColorSampler {
|
||||
|
||||
private final Image image;
|
||||
|
||||
private final ColorSampler fallback;
|
||||
|
||||
private final ImageTransformation transformation;
|
||||
|
||||
public SingleImageColorSampler(Image image, ColorSampler fallback, ImageTransformation transformation) {
|
||||
this.image = image;
|
||||
this.fallback = fallback;
|
||||
this.transformation = transformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int apply(int x, int z) {
|
||||
var nx = transformation.transformX(image, x);
|
||||
var nz = transformation.transformZ(image, z);
|
||||
if(nx < 0 || nz < 0 || nx >= image.getWidth() || nz >= image.getHeight()) return fallback.apply(x, z);
|
||||
return image.getRGB(nx, nz);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.dfsek.terra.addons.image.colorsampler.image;
|
||||
|
||||
import net.jafama.FastMath;
|
||||
|
||||
import com.dfsek.terra.addons.image.image.Image;
|
||||
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
|
||||
import com.dfsek.terra.addons.image.colorsampler.image.transform.ImageTransformation;
|
||||
|
||||
|
||||
public class TileImageColorSampler implements ColorSampler {
|
||||
|
||||
private final Image image;
|
||||
|
||||
private final ImageTransformation transformation;
|
||||
|
||||
public TileImageColorSampler(Image image, ImageTransformation transformation) {
|
||||
this.image = image;
|
||||
this.transformation = transformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int apply(int x, int z) {
|
||||
x = transformation.transformX(image, x);
|
||||
z = transformation.transformZ(image, z);
|
||||
return image.getRGB(FastMath.floorMod(x, image.getWidth()), FastMath.floorMod(z, image.getHeight()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.dfsek.terra.addons.image.colorsampler.image.transform;
|
||||
|
||||
import com.dfsek.terra.addons.image.image.Image;
|
||||
|
||||
|
||||
public enum Alignment implements ImageTransformation {
|
||||
|
||||
NONE() {
|
||||
@Override
|
||||
public int transformX(Image image, int x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int transformZ(Image image, int z) {
|
||||
return z;
|
||||
}
|
||||
},
|
||||
CENTER {
|
||||
@Override
|
||||
public int transformX(Image image, int x) {
|
||||
return x + image.getWidth() / 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int transformZ(Image image, int z) {
|
||||
return z + image.getHeight() / 2;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.dfsek.terra.addons.image.colorsampler.image.transform;
|
||||
|
||||
import com.dfsek.terra.addons.image.image.Image;
|
||||
|
||||
|
||||
public interface ImageTransformation {
|
||||
|
||||
int transformX(Image image, int x);
|
||||
|
||||
int transformZ(Image image, int z);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.dfsek.terra.addons.image.colorsampler.mutate;
|
||||
|
||||
import net.jafama.FastMath;
|
||||
|
||||
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
|
||||
|
||||
public class RotateColorSampler implements ColorSampler {
|
||||
|
||||
private final ColorSampler sampler;
|
||||
|
||||
private final double radians;
|
||||
|
||||
private final RotationMethod rotationMethod;
|
||||
|
||||
public RotateColorSampler(ColorSampler sampler, double degrees) {
|
||||
this.sampler = sampler;
|
||||
|
||||
double normalizedDegrees = degrees % 360.0;
|
||||
if (normalizedDegrees < 0) normalizedDegrees += 360.0;
|
||||
|
||||
if (normalizedDegrees == 0.0)
|
||||
rotationMethod = RotationMethod.DEG_0;
|
||||
else if (normalizedDegrees == 90.0)
|
||||
rotationMethod = RotationMethod.DEG_90;
|
||||
else if (normalizedDegrees == 180.0)
|
||||
rotationMethod = RotationMethod.DEG_180;
|
||||
else if (normalizedDegrees == 270.0)
|
||||
rotationMethod = RotationMethod.DEG_270;
|
||||
else
|
||||
rotationMethod = RotationMethod.RAD_ANY;
|
||||
|
||||
this.radians = FastMath.toRadians(degrees);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int apply(int x, int z) {
|
||||
int rx = switch(rotationMethod) {
|
||||
case DEG_0 -> x;
|
||||
case DEG_90 -> -z;
|
||||
case DEG_180 -> -x;
|
||||
case DEG_270 -> z;
|
||||
case RAD_ANY -> (int) (x * FastMath.cos(radians) - z * FastMath.sin(radians));
|
||||
};
|
||||
int rz = switch(rotationMethod) {
|
||||
case DEG_0 -> z;
|
||||
case DEG_90 -> x;
|
||||
case DEG_180 -> -z;
|
||||
case DEG_270 -> -x;
|
||||
case RAD_ANY -> (int) (z * FastMath.cos(radians) + x * FastMath.sin(radians));
|
||||
};
|
||||
return sampler.apply(rx, rz);
|
||||
}
|
||||
|
||||
private enum RotationMethod {
|
||||
DEG_0,
|
||||
DEG_90,
|
||||
DEG_180,
|
||||
DEG_270,
|
||||
RAD_ANY,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.dfsek.terra.addons.image.colorsampler.mutate;
|
||||
|
||||
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
|
||||
|
||||
|
||||
public class TranslateColorSampler implements ColorSampler {
|
||||
|
||||
private final ColorSampler sampler;
|
||||
private final int translateX, translateZ;
|
||||
|
||||
public TranslateColorSampler(ColorSampler sampler, int translateX, int translateZ) {
|
||||
this.sampler = sampler;
|
||||
this.translateX = translateX;
|
||||
this.translateZ = translateZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int apply(int x, int z) {
|
||||
return sampler.apply(x - translateX, z - translateZ);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.dfsek.terra.addons.image.config;
|
||||
|
||||
import com.dfsek.tectonic.api.depth.DepthTracker;
|
||||
import com.dfsek.tectonic.api.exception.LoadException;
|
||||
import com.dfsek.tectonic.api.loader.ConfigLoader;
|
||||
import com.dfsek.tectonic.api.loader.type.TypeLoader;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.AnnotatedType;
|
||||
|
||||
import com.dfsek.terra.addons.image.config.ColorLoader.ColorString;
|
||||
import com.dfsek.terra.addons.image.util.ColorUtil;
|
||||
|
||||
|
||||
public class ColorLoader implements TypeLoader<ColorString> {
|
||||
|
||||
@Override
|
||||
public ColorString load(@NotNull AnnotatedType annotatedType, @NotNull Object o, @NotNull ConfigLoader configLoader,
|
||||
DepthTracker depthTracker) throws LoadException {
|
||||
return new ColorString((String) o);
|
||||
}
|
||||
|
||||
public static class ColorString {
|
||||
|
||||
private final int argb;
|
||||
|
||||
public ColorString(String string) throws IllegalArgumentException {
|
||||
this.argb = parse(string);
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return argb;
|
||||
}
|
||||
|
||||
private static int parse(String string) throws IllegalArgumentException {
|
||||
if (string.length() == 0)
|
||||
throw new IllegalArgumentException("Empty string cannot be parsed as a valid color");
|
||||
|
||||
String[] split = string.split(",");
|
||||
|
||||
if (split.length == 1)
|
||||
return parseHex(string);
|
||||
else if (split.length == 3)
|
||||
return parseChannels("255", split[0], split[1], split[2]);
|
||||
else if (split.length == 4)
|
||||
return parseChannels(split[0], split[1], split[2], split[3]);
|
||||
else
|
||||
throw new IllegalArgumentException("Invalid channels provided, required format RED,GREEN,BLUE or ALPHA,RED,GREEN,BLUE");
|
||||
}
|
||||
|
||||
private static int parseHex(String hex) throws IllegalArgumentException {
|
||||
if (hex.startsWith("#"))
|
||||
hex = hex.substring(1);
|
||||
|
||||
int alpha = 255;
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
|
||||
try {
|
||||
if(hex.length() == 8) {
|
||||
alpha = Integer.parseInt(hex.substring(0, 2), 16);
|
||||
hex = hex.substring(2);
|
||||
}
|
||||
|
||||
if(hex.length() != 6)
|
||||
throw new IllegalArgumentException("Invalid color channels, required format AARRGGBB or RRGGBB");
|
||||
|
||||
red = Integer.parseInt(hex.substring(0, 2), 16);
|
||||
green = Integer.parseInt(hex.substring(2, 4), 16);
|
||||
blue = Integer.parseInt(hex.substring(4, 6), 16);
|
||||
|
||||
return ColorUtil.argbValidated(alpha, red, green, blue);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Failed to parse hex color", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static int parseChannels(String alpha, String red, String green, String blue) throws IllegalArgumentException {
|
||||
try {
|
||||
int a = Integer.decode(alpha);
|
||||
int r = Integer.decode(red);
|
||||
int g = Integer.decode(green);
|
||||
int b = Integer.decode(blue);
|
||||
|
||||
return ColorUtil.argbValidated(a, r, g, b);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Invalid channel value", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.dfsek.terra.addons.image.config.colorsampler;
|
||||
|
||||
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.ColorSampler;
|
||||
import com.dfsek.terra.addons.image.config.ColorLoader.ColorString;
|
||||
|
||||
|
||||
public class ConstantColorSamplerTemplate implements ObjectTemplate<ColorSampler> {
|
||||
|
||||
@Value("color")
|
||||
private ColorString color;
|
||||
|
||||
@Override
|
||||
public ColorSampler get() {
|
||||
return ((x, z) -> color.getColor());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.dfsek.terra.addons.image.config.colorsampler.image;
|
||||
|
||||
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.colorsampler.ColorSampler;
|
||||
import com.dfsek.terra.addons.image.colorsampler.image.transform.Alignment;
|
||||
|
||||
|
||||
public abstract class ImageColorSamplerTemplate implements ObjectTemplate<ColorSampler> {
|
||||
|
||||
@Value("image")
|
||||
protected Image image;
|
||||
|
||||
@Value("align")
|
||||
@Default
|
||||
protected Alignment alignment = Alignment.NONE;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.dfsek.terra.addons.image.config.colorsampler.image;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
|
||||
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
|
||||
import com.dfsek.terra.addons.image.colorsampler.image.SingleImageColorSampler;
|
||||
|
||||
|
||||
public class SingleImageColorSamplerTemplate extends ImageColorSamplerTemplate {
|
||||
@Value("outside-sampler")
|
||||
private ColorSampler fallback;
|
||||
|
||||
@Override
|
||||
public ColorSampler get() {
|
||||
return new SingleImageColorSampler(image, fallback, alignment);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.dfsek.terra.addons.image.config.colorsampler.image;
|
||||
|
||||
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
|
||||
import com.dfsek.terra.addons.image.colorsampler.image.TileImageColorSampler;
|
||||
|
||||
|
||||
public class TileImageColorSamplerTemplate extends ImageColorSamplerTemplate {
|
||||
|
||||
@Override
|
||||
public ColorSampler get() {
|
||||
return new TileImageColorSampler(image, alignment);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.dfsek.terra.addons.image.config.colorsampler.mutate;
|
||||
|
||||
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.ColorSampler;
|
||||
|
||||
|
||||
public abstract class MutateColorSamplerTemplate implements ObjectTemplate<ColorSampler> {
|
||||
|
||||
@Value("color-sampler")
|
||||
protected ColorSampler sampler;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.dfsek.terra.addons.image.config.colorsampler.mutate;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
|
||||
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
|
||||
import com.dfsek.terra.addons.image.colorsampler.mutate.RotateColorSampler;
|
||||
|
||||
|
||||
public class RotateColorSamplerTemplate extends MutateColorSamplerTemplate {
|
||||
|
||||
@Value("angle")
|
||||
private double degrees;
|
||||
|
||||
@Override
|
||||
public ColorSampler get() {
|
||||
return new RotateColorSampler(sampler, degrees);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.dfsek.terra.addons.image.config.colorsampler.mutate;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
|
||||
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
|
||||
import com.dfsek.terra.addons.image.colorsampler.mutate.TranslateColorSampler;
|
||||
|
||||
|
||||
public class TranslateColorSamplerTemplate extends MutateColorSamplerTemplate {
|
||||
|
||||
@Value("x")
|
||||
private int translateX;
|
||||
|
||||
@Value("z")
|
||||
private int translateZ;
|
||||
|
||||
@Override
|
||||
public ColorSampler get() {
|
||||
return new TranslateColorSampler(sampler, translateX, translateZ);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.dfsek.terra.addons.image.config.converter;
|
||||
|
||||
import com.dfsek.terra.addons.image.converter.ClosestMatchColorConverter;
|
||||
import com.dfsek.terra.addons.image.converter.ColorConverter;
|
||||
import com.dfsek.terra.addons.image.converter.mapping.ColorMapping;
|
||||
|
||||
|
||||
public abstract class ClosestColorConverterTemplate<T> implements ColorConverterTemplate<T> {
|
||||
|
||||
protected abstract ColorMapping<T> getMapping();
|
||||
|
||||
@Override
|
||||
public ColorConverter<T> get() {
|
||||
return new ClosestMatchColorConverter<T>(getMapping().get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.dfsek.terra.addons.image.config.converter;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
|
||||
|
||||
import com.dfsek.terra.addons.image.converter.ColorConverter;
|
||||
|
||||
|
||||
public interface ColorConverterTemplate<T> extends ObjectTemplate<ColorConverter<T>> {
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.dfsek.terra.addons.image.config.converter;
|
||||
|
||||
import com.dfsek.terra.addons.image.converter.ColorConverter;
|
||||
import com.dfsek.terra.addons.image.converter.ExactColorConverter;
|
||||
import com.dfsek.terra.addons.image.converter.mapping.ColorMapping;
|
||||
|
||||
|
||||
public abstract class ExactColorConverterTemplate<T> implements ColorConverterTemplate<T> {
|
||||
|
||||
protected abstract ColorMapping<T> getMapping();
|
||||
|
||||
protected abstract T getFallback();
|
||||
|
||||
protected abstract boolean ignoreAlpha();
|
||||
|
||||
@Override
|
||||
public ColorConverter<T> get() {
|
||||
return new ExactColorConverter<T>(getMapping().get(), getFallback(), ignoreAlpha());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.dfsek.terra.addons.image.config.image;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.dfsek.terra.addons.image.image.BufferedImageWrapper;
|
||||
import com.dfsek.terra.addons.image.image.Image;
|
||||
import com.dfsek.terra.api.config.ConfigPack;
|
||||
import com.dfsek.terra.api.config.Loader;
|
||||
import com.dfsek.terra.api.properties.Properties;
|
||||
|
||||
|
||||
/*
|
||||
* Cache prevents configs from loading the same image multiple times into memory
|
||||
*/
|
||||
record ImageCache(ConcurrentHashMap<String, Image> map) implements Properties {
|
||||
public static Image load(String path, ConfigPack pack, Loader files) throws IOException {
|
||||
ImageCache cache;
|
||||
if(!pack.getContext().has(ImageCache.class)) {
|
||||
cache = new ImageCache(new ConcurrentHashMap<>());
|
||||
pack.getContext().put(cache);
|
||||
} else {
|
||||
cache = pack.getContext().get(ImageCache.class);
|
||||
}
|
||||
|
||||
if(cache.map.containsKey(path)) {
|
||||
return cache.map.get(path);
|
||||
} else {
|
||||
try {
|
||||
BufferedImageWrapper image = new BufferedImageWrapper(ImageIO.read(files.get(path)));
|
||||
cache.map.put(path, image);
|
||||
return image;
|
||||
} catch(IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Unable to load image (image might be too large?)", e);
|
||||
} catch(IOException e) {
|
||||
if(e instanceof FileNotFoundException) {
|
||||
// Rethrow using nicer message
|
||||
throw new IOException("Unable to load image: No such file or directory: " + path, e);
|
||||
}
|
||||
throw new IOException("Unable to load image", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.dfsek.terra.addons.image.config.image;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.dfsek.terra.addons.image.image.Image;
|
||||
import com.dfsek.terra.api.config.ConfigPack;
|
||||
import com.dfsek.terra.api.config.Loader;
|
||||
|
||||
|
||||
public class ImageTemplate implements ObjectTemplate<Image> {
|
||||
|
||||
@Value("path")
|
||||
private String path;
|
||||
|
||||
private final Loader files;
|
||||
|
||||
private final ConfigPack pack;
|
||||
|
||||
public ImageTemplate(Loader files, ConfigPack pack) {
|
||||
this.files = files;
|
||||
this.pack = pack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image get() {
|
||||
try {
|
||||
return ImageCache.load(path, pack, files);
|
||||
} catch(IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.dfsek.terra.addons.image.config.image;
|
||||
|
||||
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 java.io.IOException;
|
||||
|
||||
import com.dfsek.terra.addons.image.image.Image;
|
||||
import com.dfsek.terra.addons.image.image.StitchedImage;
|
||||
import com.dfsek.terra.api.config.ConfigPack;
|
||||
import com.dfsek.terra.api.config.Loader;
|
||||
|
||||
|
||||
public class StitchedImageTemplate implements ObjectTemplate<Image>, ValidatedConfigTemplate {
|
||||
|
||||
@Value("path-format")
|
||||
private String path;
|
||||
|
||||
@Value("rows")
|
||||
private int rows;
|
||||
|
||||
@Value("columns")
|
||||
private int cols;
|
||||
|
||||
@Value("zero-indexed")
|
||||
@Default
|
||||
private boolean zeroIndexed = false;
|
||||
|
||||
private final Loader files;
|
||||
|
||||
private final ConfigPack pack;
|
||||
|
||||
public StitchedImageTemplate(Loader files, ConfigPack pack) {
|
||||
this.files = files;
|
||||
this.pack = pack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image get() {
|
||||
Image[][] grid = new Image[rows][cols];
|
||||
for(int i = 0; i < rows; i++) {
|
||||
for(int j = 0; j < cols; j++) {
|
||||
try {
|
||||
grid[i][j] = ImageCache.load(getFormattedPath(i, j), pack, files);
|
||||
} catch(IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new StitchedImage(grid, zeroIndexed);
|
||||
}
|
||||
|
||||
private String getFormattedPath(int row, int column) {
|
||||
if (!zeroIndexed) {
|
||||
row++;
|
||||
column++;
|
||||
}
|
||||
return path.replaceFirst("\\{row}", String.valueOf(row)).replaceFirst("\\{column}", String.valueOf(column));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate() throws ValidationException {
|
||||
if(!path.contains("{row}"))
|
||||
throw new ValidationException("Path format does not contain sequence '{row}'");
|
||||
if(!path.contains("{column}"))
|
||||
throw new ValidationException("Path format does not contain sequence '{column}'");
|
||||
if(rows < 1)
|
||||
throw new ValidationException("Must have at least one row");
|
||||
if(cols < 1)
|
||||
throw new ValidationException("Must have at least one column");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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.colorsampler.ColorSampler;
|
||||
import com.dfsek.terra.addons.image.noisesampler.ChannelNoiseSampler;
|
||||
import com.dfsek.terra.addons.image.util.ColorUtil.Channel;
|
||||
import com.dfsek.terra.api.noise.NoiseSampler;
|
||||
|
||||
|
||||
public class ChannelNoiseSamplerTemplate implements ObjectTemplate<NoiseSampler> {
|
||||
|
||||
@Value("color-sampler")
|
||||
private ColorSampler colorSampler;
|
||||
|
||||
@Value("channel")
|
||||
private Channel channel;
|
||||
|
||||
/*
|
||||
* If the channel should be normalized to range [-1, 1] or not
|
||||
*/
|
||||
@Value("normalize")
|
||||
@Default
|
||||
private boolean normalize = true;
|
||||
|
||||
/*
|
||||
* Whether to multiply color channels by the alpha channel or not. If users
|
||||
* are expecting pixel transparency to reduce the output value then this should
|
||||
* be set to true.
|
||||
*/
|
||||
@Value("premultiply")
|
||||
@Default
|
||||
private boolean premultiply = false;
|
||||
|
||||
@Override
|
||||
public NoiseSampler get() {
|
||||
return new ChannelNoiseSampler(colorSampler, channel, normalize, premultiply);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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.operator.DistanceTransform;
|
||||
import com.dfsek.terra.addons.image.operator.DistanceTransform.CostFunction;
|
||||
import com.dfsek.terra.addons.image.operator.DistanceTransform.Normalization;
|
||||
import com.dfsek.terra.addons.image.util.ColorUtil.Channel;
|
||||
import com.dfsek.terra.api.noise.NoiseSampler;
|
||||
|
||||
|
||||
public class DistanceTransformNoiseSamplerTemplate implements ObjectTemplate<NoiseSampler> {
|
||||
|
||||
@Value("image")
|
||||
private Image image;
|
||||
|
||||
/**
|
||||
* The threshold value applied to the channel specified in the 'channel' parameter that splits
|
||||
* the image into a binary image. This parameter is only used for cost functions that utilize
|
||||
* a binary image.
|
||||
*/
|
||||
@Value("threshold")
|
||||
@Default
|
||||
private int threshold = 127;
|
||||
|
||||
/**
|
||||
* If set to true, distances calculated will be clamped to stay above the largest
|
||||
* distance calculated on the edges of the image. This ensures output values do not
|
||||
* appear to be cut off at the image boundaries. It is recommended to leave padding
|
||||
* around the image if this is in use, such that larger evaluated distances do not
|
||||
* get cut out by smaller evaluated distances close to borders. Doing so will yield
|
||||
* better results.
|
||||
*/
|
||||
@Value("clamp-to-max-edge")
|
||||
@Default
|
||||
private boolean clampToEdge = false;
|
||||
|
||||
/**
|
||||
* The target channel to run distance calculations on.
|
||||
*/
|
||||
@Value("channel")
|
||||
@Default
|
||||
private Channel channel = Channel.GRAYSCALE;
|
||||
|
||||
/**
|
||||
* The method of image processing applied to the specified image prior to calculating
|
||||
* distances.
|
||||
*/
|
||||
@Value("cost-function")
|
||||
@Default
|
||||
private CostFunction costFunction = CostFunction.Channel;
|
||||
|
||||
/**
|
||||
* Inverts the resulting binary image that may be used as a cost function.
|
||||
*/
|
||||
@Value("invert-threshold")
|
||||
@Default
|
||||
private boolean invertThreshold = false;
|
||||
|
||||
/**
|
||||
* How the final distance calculation should be redistributed.
|
||||
*/
|
||||
@Value("normalization")
|
||||
@Default
|
||||
private Normalization normalization = Normalization.None;
|
||||
|
||||
@Override
|
||||
public NoiseSampler get() {
|
||||
return new DistanceTransform.Noise(new DistanceTransform(image, channel, threshold, clampToEdge, costFunction, invertThreshold), normalization);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.dfsek.terra.addons.image.converter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.dfsek.terra.addons.image.util.ColorUtil;
|
||||
|
||||
public class ClosestMatchColorConverter<T> implements ColorConverter<T> {
|
||||
|
||||
private final Map<Integer, T> map;
|
||||
|
||||
private final Integer[] colors;
|
||||
|
||||
public ClosestMatchColorConverter(Map<Integer, T> map) {
|
||||
this.map = map;
|
||||
this.colors = map.keySet().toArray(new Integer[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T apply(int color) {
|
||||
int closest = 0;
|
||||
int smallestDistance = Integer.MAX_VALUE;
|
||||
for(int compare : colors) {
|
||||
if(color == compare) {
|
||||
closest = compare;
|
||||
break;
|
||||
}
|
||||
int distance = ColorUtil.distance(color, compare);
|
||||
if(distance < smallestDistance) {
|
||||
smallestDistance = distance;
|
||||
closest = compare;
|
||||
}
|
||||
}
|
||||
return map.get(closest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<T> getEntries() {
|
||||
return map.values();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.dfsek.terra.addons.image.converter;
|
||||
|
||||
public interface ColorConverter<T> {
|
||||
|
||||
T apply(int color);
|
||||
|
||||
Iterable<T> getEntries();
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.dfsek.terra.addons.image.converter;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.dfsek.terra.addons.image.util.ColorUtil;
|
||||
import com.dfsek.terra.addons.image.util.MapUtil;
|
||||
|
||||
|
||||
public class ExactColorConverter<T> implements ColorConverter<T> {
|
||||
private final Map<Integer, T> map;
|
||||
|
||||
private final T fallback;
|
||||
|
||||
private final boolean ignoreAlpha;
|
||||
|
||||
public ExactColorConverter(Map<Integer, T> map, T fallback, boolean ignoreAlpha) {
|
||||
if (ignoreAlpha) {
|
||||
map = MapUtil.mapKeys(map, ColorUtil::zeroAlpha);
|
||||
}
|
||||
this.map = map;
|
||||
this.fallback = fallback;
|
||||
this.ignoreAlpha = ignoreAlpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T apply(int color) {
|
||||
if (ignoreAlpha) {
|
||||
color = ColorUtil.zeroAlpha(color);
|
||||
}
|
||||
T lookup = map.get(color);
|
||||
return lookup != null ? lookup : fallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<T> getEntries() {
|
||||
Set<T> entries = new HashSet<>(map.values());
|
||||
entries.add(fallback);
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.dfsek.terra.addons.image.converter.mapping;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.dfsek.terra.api.registry.Registry;
|
||||
import com.dfsek.terra.api.world.biome.Biome;
|
||||
|
||||
|
||||
public class BiomeDefinedColorMapping<T> implements ColorMapping<T> {
|
||||
|
||||
Registry<Biome> biomeRegistry;
|
||||
|
||||
Function<Biome, T> converter;
|
||||
|
||||
public BiomeDefinedColorMapping(Registry<Biome> biomeRegistry, Function<Biome, T> converter) {
|
||||
this.biomeRegistry = biomeRegistry;
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, T> get() {
|
||||
Map<Biome, Integer> colorMap = new HashSet<>(biomeRegistry.entries()).stream().collect(Collectors.toMap(b -> b, Biome::getColor));
|
||||
Map<Integer, Biome> output = new HashMap<>();
|
||||
colorMap.forEach(((biome, color) -> {
|
||||
if(!output.containsKey(color)) {
|
||||
output.put(color, biome);
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format("Biome %s has same color as %s: %x", biome.getID(), output.get(color).getID(), color));
|
||||
}
|
||||
}));
|
||||
return output.entrySet().stream().collect(Collectors.toMap(Entry::getKey, e -> converter.apply(e.getValue())));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.dfsek.terra.addons.image.converter.mapping;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
||||
public interface ColorMapping<T> extends Supplier<Map<Integer, T>> {
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.dfsek.terra.addons.image.image;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
|
||||
public class BufferedImageWrapper implements Image {
|
||||
|
||||
private final BufferedImage image;
|
||||
|
||||
public BufferedImageWrapper(BufferedImage image) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRGB(int x, int y) {
|
||||
return image.getRGB(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return image.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return image.getHeight();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.dfsek.terra.addons.image.image;
|
||||
|
||||
public interface Image {
|
||||
int getRGB(int x, int y);
|
||||
|
||||
int getWidth();
|
||||
|
||||
int getHeight();
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.dfsek.terra.addons.image.image;
|
||||
|
||||
public class StitchedImage implements Image {
|
||||
|
||||
private final Image[][] images;
|
||||
|
||||
private final int[] rowOffsets, columnOffsets;
|
||||
|
||||
private final int width, height;
|
||||
|
||||
public StitchedImage(Image[][] images, boolean zeroIndexed) throws IllegalArgumentException {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int rows = images.length;
|
||||
int columns = images[0].length;
|
||||
this.rowOffsets = new int[rows];
|
||||
this.columnOffsets = new int[columns];
|
||||
for(int i = 0; i < rows; i++) {
|
||||
int rowHeight = images[i][0].getHeight();
|
||||
rowOffsets[i] = height;
|
||||
height += rowHeight;
|
||||
for(int j = 1; j < columns; j++) {
|
||||
if(images[i][j].getHeight() != rowHeight)
|
||||
throw new IllegalArgumentException("Image heights in row " + (i + (zeroIndexed ? 0 : 1)) + " do not match");
|
||||
}
|
||||
}
|
||||
for(int i = 0; i < columns; i++) {
|
||||
int columnWidth = images[0][i].getWidth();
|
||||
columnOffsets[i] = width;
|
||||
width += columnWidth;
|
||||
for(int j = 1; j < rows; j++) {
|
||||
if(images[i][j].getWidth() != columnWidth)
|
||||
throw new IllegalArgumentException("Image widths in column " + (i + (zeroIndexed ? 0 : 1)) + " do not match");
|
||||
}
|
||||
}
|
||||
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.images = images;
|
||||
}
|
||||
|
||||
private int getColumn(int x) {
|
||||
for(int i = columnOffsets.length-1; i > 0; i--) {
|
||||
if(x >= columnOffsets[i])
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int getRow(int y) {
|
||||
for(int i = rowOffsets.length-1; i > 0; i--) {
|
||||
if(y >= rowOffsets[i])
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRGB(int x, int y) {
|
||||
int row = getRow(y);
|
||||
int column = getColumn(x);
|
||||
return images[row][column].getRGB(x-columnOffsets[column], y-rowOffsets[row]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.dfsek.terra.addons.image.noisesampler;
|
||||
|
||||
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
|
||||
import com.dfsek.terra.addons.image.util.ColorUtil;
|
||||
import com.dfsek.terra.addons.image.util.ColorUtil.Channel;
|
||||
import com.dfsek.terra.api.noise.NoiseSampler;
|
||||
|
||||
import static com.dfsek.terra.addons.image.util.MathUtil.lerp;
|
||||
|
||||
|
||||
public class ChannelNoiseSampler implements NoiseSampler {
|
||||
|
||||
private final ColorSampler colorSampler;
|
||||
|
||||
private final Channel channel;
|
||||
|
||||
private final boolean normalize;
|
||||
|
||||
private final boolean premultiply;
|
||||
|
||||
public ChannelNoiseSampler(ColorSampler colorSampler, Channel channel, boolean normalize, boolean premultiply) {
|
||||
this.colorSampler = colorSampler;
|
||||
this.channel = channel;
|
||||
this.normalize = normalize;
|
||||
this.premultiply = premultiply;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(long seed, double x, double y) {
|
||||
int sample = colorSampler.apply((int) x, (int) y);
|
||||
int premultiplied = premultiply ? ColorUtil.premultiply(sample) : sample;
|
||||
double channelValue = channel.from(premultiplied);
|
||||
return normalize ? lerp(channelValue, 0, -1, 255, 1) : channelValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(long seed, double x, double y, double z) {
|
||||
return noise(seed, x, z);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
package com.dfsek.terra.addons.image.operator;
|
||||
|
||||
import net.jafama.FastMath;
|
||||
|
||||
import com.dfsek.terra.addons.image.image.Image;
|
||||
import com.dfsek.terra.addons.image.util.ColorUtil;
|
||||
import com.dfsek.terra.addons.image.util.ColorUtil.Channel;
|
||||
import com.dfsek.terra.api.noise.NoiseSampler;
|
||||
|
||||
import static com.dfsek.terra.addons.image.util.MathUtil.lerp;
|
||||
|
||||
|
||||
/**
|
||||
* Computes a 2D distance transform of a given image and stores the result in a 2D array of distances.
|
||||
* Implementation based on the algorithm described in the paper
|
||||
* <a href="https://cs.brown.edu/people/pfelzens/papers/dt-final.pdf">Distance Transforms of Sampled Functions</a>
|
||||
* by Pedro F. Felzenszwalb and Daniel P. Huttenlocher.
|
||||
*/
|
||||
public class DistanceTransform {
|
||||
|
||||
private final double[][] distances;
|
||||
|
||||
/**
|
||||
* Size bounds matching the provided image.
|
||||
*/
|
||||
private final int width, height;
|
||||
|
||||
/**
|
||||
* Min and max distances of the distance computation. These may change after {@link #normalize(Normalization)} calls.
|
||||
*/
|
||||
private double minDistance, maxDistance;
|
||||
|
||||
private static final double MAX_DISTANCE_CAP = 10_000_000; // Arbitrarily large value, doubtful someone would
|
||||
// ever use an image large enough to exceed this.
|
||||
public DistanceTransform(Image image, Channel channel, int threshold, boolean clampToMaxEdgeDistance, CostFunction costFunction, boolean invertThreshold) {
|
||||
// Construct binary image based on threshold value
|
||||
boolean[][] binaryImage = new boolean[image.getWidth()][image.getHeight()];
|
||||
for(int x = 0; x < image.getWidth(); x++) {
|
||||
for(int y = 0; y < image.getHeight(); y++) {
|
||||
binaryImage[x][y] = ColorUtil.getChannel(image.getRGB(x, y), channel) > threshold ^ invertThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
// Get edges of binary image
|
||||
boolean[][] binaryImageEdge = new boolean[image.getWidth()][image.getHeight()];
|
||||
for(int x = 0; x < image.getWidth(); x++) {
|
||||
for(int y = 0; y < image.getHeight(); y++) {
|
||||
if(!binaryImage[x][y])
|
||||
binaryImageEdge[x][y] = false;
|
||||
else
|
||||
// If cell borders any false cell
|
||||
binaryImageEdge[x][y] = x > 0 && !binaryImage[x-1][y] ||
|
||||
y > 0 && !binaryImage[x][y-1] ||
|
||||
x < image.getWidth ()-1 && !binaryImage[x+1][y] ||
|
||||
y < image.getHeight()-1 && !binaryImage[x][y+1];
|
||||
}
|
||||
}
|
||||
|
||||
double[][] function = new double[image.getWidth()][image.getHeight()];
|
||||
for(int x = 0; x < image.getWidth(); x++) {
|
||||
for(int y = 0; y < image.getHeight(); y++) {
|
||||
function[x][y] = switch (costFunction) {
|
||||
case Channel -> ColorUtil.getChannel(image.getRGB(x, y), channel);
|
||||
case Threshold -> binaryImage[x][y] ? MAX_DISTANCE_CAP : 0;
|
||||
case ThresholdEdge, ThresholdEdgeSigned -> binaryImageEdge[x][y] ? 0 : MAX_DISTANCE_CAP;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
distances = calculateDistance2D(function);
|
||||
|
||||
if(costFunction == CostFunction.ThresholdEdgeSigned) {
|
||||
for(int x = 0; x < image.getWidth(); x++) {
|
||||
for(int y = 0; y < image.getHeight(); y++) {
|
||||
distances[x][y] *= binaryImage[x][y] ? 1 : -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(clampToMaxEdgeDistance) {
|
||||
// Find largest value on the edge of the image
|
||||
double max = Double.NEGATIVE_INFINITY;
|
||||
for(int x = 0; x < image.getWidth(); x++) {
|
||||
max = Math.max(max, distances[x][0]);
|
||||
max = Math.max(max, distances[x][image.getHeight()-1]);
|
||||
}
|
||||
for(int y = 0; y < image.getHeight(); y++) {
|
||||
max = Math.max(max, distances[0][y]);
|
||||
max = Math.max(max, distances[image.getWidth()-1][y]);
|
||||
}
|
||||
// Clamp to that largest value
|
||||
for(int x = 0; x < image.getWidth(); x++) {
|
||||
for(int y = 0; y < image.getHeight(); y++) {
|
||||
distances[x][y] = Math.max(max, distances[x][y]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.width = image.getWidth();
|
||||
this.height = image.getHeight();
|
||||
|
||||
setOutputRange();
|
||||
}
|
||||
|
||||
private double[][] calculateDistance2D(double[][] f) {
|
||||
double[][] d = new double[f.length][f[0].length];
|
||||
// Distance pass for each column
|
||||
for(int x = 0; x < f.length; x++) {
|
||||
d[x] = calculateDistance1D(f[x]);
|
||||
}
|
||||
// Distance pass for each row
|
||||
double[] row = new double[f.length];
|
||||
for(int y = 0; y < f[0].length; y++) {
|
||||
for(int x = 0; x < f[0].length; x++)
|
||||
row[x] = d[x][y];
|
||||
row = calculateDistance1D(row);
|
||||
for(int x = 0; x < f[0].length; x++) {
|
||||
d[x][y] = FastMath.sqrt(row[x]);
|
||||
}
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
private double[] calculateDistance1D(double[] f) {
|
||||
double[] d = new double[f.length];
|
||||
int[] v = new int[f.length];
|
||||
double[] z = new double[f.length+1];
|
||||
int k = 0;
|
||||
v[0] = 0;
|
||||
z[0] = Integer.MIN_VALUE;
|
||||
z[1] = Integer.MAX_VALUE;
|
||||
for(int q = 1; q <= f.length-1; q++) {
|
||||
double s = ((f[q]+FastMath.pow2(q))-(f[v[k]]+FastMath.pow2(v[k])))/(2*q-2*v[k]);
|
||||
while (s <= z[k]) {
|
||||
k--;
|
||||
s = ((f[q]+FastMath.pow2(q))-(f[v[k]]+FastMath.pow2(v[k])))/(2*q-2*v[k]);
|
||||
}
|
||||
k++;
|
||||
v[k] = q;
|
||||
z[k] = s;
|
||||
z[k+1] = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
k = 0;
|
||||
for(int q = 0; q <= f.length-1; q++) {
|
||||
while(z[k+1] < q)
|
||||
k++;
|
||||
d[q] = FastMath.pow2(q-v[k]) + f[v[k]];
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redistributes the stored distance computation according to the provided {@link Normalization} method.
|
||||
*/
|
||||
private void normalize(Normalization normalization) {
|
||||
for(int x = 0; x < width; x++) {
|
||||
for(int y = 0; y < height; y++) {
|
||||
double d = distances[x][y];
|
||||
distances[x][y] = switch(normalization) {
|
||||
case None -> distances[x][y];
|
||||
case Linear -> lerp(d, minDistance, -1, maxDistance, 1);
|
||||
case SmoothPreserveZero -> {
|
||||
if(minDistance > 0 || maxDistance < 0) {
|
||||
// Can't preserve zero if it is not contained in range so just lerp
|
||||
yield lerp(distances[x][y], minDistance, -1, maxDistance, 1);
|
||||
} else {
|
||||
if(d > 0) {
|
||||
yield FastMath.pow2(d/maxDistance);
|
||||
} else if(d < 0) {
|
||||
yield -FastMath.pow2(d/minDistance);
|
||||
} else {
|
||||
yield 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
setOutputRange();
|
||||
}
|
||||
|
||||
private void setOutputRange() {
|
||||
double minDistance = Double.POSITIVE_INFINITY;
|
||||
double maxDistance = Double.NEGATIVE_INFINITY;
|
||||
for(int x = 0; x < width; x++) {
|
||||
for(int y = 0; y < height; y++) {
|
||||
minDistance = Math.min(minDistance, distances[x][y]);
|
||||
maxDistance = Math.max(maxDistance, distances[x][y]);
|
||||
}
|
||||
}
|
||||
this.minDistance = minDistance;
|
||||
this.maxDistance = maxDistance;
|
||||
}
|
||||
|
||||
public enum CostFunction {
|
||||
Channel,
|
||||
Threshold,
|
||||
ThresholdEdge,
|
||||
ThresholdEdgeSigned,
|
||||
}
|
||||
|
||||
public enum Normalization {
|
||||
/**
|
||||
* Return the raw calculated distances.
|
||||
*/
|
||||
None,
|
||||
|
||||
/**
|
||||
* Redistribute the output values to fit in the range [-1, 1]
|
||||
*/
|
||||
Linear,
|
||||
|
||||
/**
|
||||
* Redistributes smoothly to the range [-1, 1], such that areas where distance = 0 stay 0.
|
||||
* This is only really applicable to signed distance calculations, and will fall back to linear
|
||||
* redistribution if the input range does not contain both positive and negative values.
|
||||
*/
|
||||
SmoothPreserveZero,
|
||||
}
|
||||
|
||||
public static class Noise implements NoiseSampler {
|
||||
|
||||
private final DistanceTransform transform;
|
||||
|
||||
public Noise(DistanceTransform transform, Normalization normalization) {
|
||||
this.transform = transform;
|
||||
transform.normalize(normalization);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(long seed, double x, double y) {
|
||||
if(x<0 || y<0 || x>=transform.width || y>=transform.height) return transform.minDistance;
|
||||
return transform.distances[FastMath.floorToInt(x)][FastMath.floorToInt(y)];
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(long seed, double x, double y, double z) {
|
||||
return noise(seed, x, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
package com.dfsek.terra.addons.image.util;
|
||||
|
||||
import net.jafama.FastMath;
|
||||
|
||||
|
||||
/**
|
||||
* Utility class for manipulating 8 bit ARGB colors
|
||||
*/
|
||||
public class ColorUtil {
|
||||
|
||||
private ColorUtil() {}
|
||||
|
||||
public static int distance(int a, int b) {
|
||||
return FastMath.abs(getRed(a) - getRed(b)) +
|
||||
FastMath.abs(getGreen(a) - getGreen(b)) +
|
||||
FastMath.abs(getBlue(a) - getBlue(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the red channel value of a given ARGB color value.
|
||||
*
|
||||
* @param argb the ARGB color value to extract the red channel value from
|
||||
* @return the red channel value of the given ARGB color value, in the range 0-255
|
||||
*/
|
||||
public static int getRed(int argb) {
|
||||
return argb >> 16 & 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the green channel value of a given ARGB color value.
|
||||
*
|
||||
* @param argb the ARGB color value to extract the green channel value from
|
||||
* @return the green channel value of the given ARGB color value, in the range 0-255
|
||||
*/
|
||||
public static int getGreen(int argb) {
|
||||
return argb >> 8 & 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the blue channel value of a given ARGB color value.
|
||||
*
|
||||
* @param argb the ARGB color value to extract the blue channel value from
|
||||
* @return the blue channel value of the given ARGB color value, in the range 0-255
|
||||
*/
|
||||
public static int getBlue(int argb) {
|
||||
return argb & 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the alpha channel value of a given ARGB color value.
|
||||
*
|
||||
* @param argb the ARGB color value to extract the blue channel value from
|
||||
* @return the alpha channel value of the given ARGB color value, in the range 0-255
|
||||
*/
|
||||
public static int getAlpha(int argb) {
|
||||
return argb >> 24 & 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the grayscale value of a given ARGB color value.
|
||||
*
|
||||
* @param argb the ARGB color value to convert to grayscale
|
||||
* @return the grayscale value of the given ARGB color value, in the range 0-255
|
||||
*/
|
||||
public static int getGrayscale(int argb) {
|
||||
return (getRed(argb) + getGreen(argb) + getBlue(argb)) / 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the specified channel for a given ARGB color value.
|
||||
*
|
||||
* @param argb the ARGB color value to extract the channel value from
|
||||
* @param channel the channel to extract the value from
|
||||
* @return the value of the specified channel for the given ARGB color value, in the range 0-255
|
||||
*/
|
||||
public static int getChannel(int argb, Channel channel) {
|
||||
return channel.from(argb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the red channel value of a given ARGB color value to zero.
|
||||
*
|
||||
* @param argb the ARGB color value to zero the red channel of
|
||||
* @return the resulting ARGB color value with the red channel set to zero
|
||||
*/
|
||||
public static int zeroRed(int argb) {
|
||||
return argb & ~0x00FF0000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the green channel value of a given ARGB color value to zero.
|
||||
*
|
||||
* @param argb the ARGB color value to zero the green channel of
|
||||
* @return the resulting ARGB color value with the green channel set to zero
|
||||
*/
|
||||
public static int zeroGreen(int argb) {
|
||||
return argb & ~0x0000FF00;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the blue channel value of a given ARGB color value to zero.
|
||||
*
|
||||
* @param argb the ARGB color value to zero the blue channel of
|
||||
* @return the resulting ARGB color value with the blue channel set to zero
|
||||
*/
|
||||
public static int zeroBlue(int argb) {
|
||||
return argb & ~0x000000FF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the alpha channel value of a given ARGB color value to zero.
|
||||
* This is the same as setting the color to fully transparent.
|
||||
*
|
||||
* @param argb the ARGB color value to zero the alpha channel of
|
||||
* @return the resulting ARGB color value with the alpha channel set to zero
|
||||
*/
|
||||
public static int zeroAlpha(int argb) {
|
||||
return argb & ~0xFF000000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color channels of a given ARGB color value to zero.
|
||||
* This is the same as setting the color to black, while preserving the alpha.
|
||||
*
|
||||
* @param argb the ARGB color value to zero the color channel of
|
||||
* @return the resulting ARGB color value with the color channels set to zero
|
||||
*/
|
||||
public static int zeroGrayscale(int argb) {
|
||||
return argb & ~0x00FFFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the specified channel value of a given ARGB color value to zero.
|
||||
*
|
||||
* @param argb the ARGB color value to zero the specified channel of
|
||||
* @param channel the channel to zero the value of
|
||||
* @return the resulting ARGB color value with the specified channel value set to zero
|
||||
*/
|
||||
public static int zeroChannel(int argb, Channel channel) {
|
||||
return channel.zero(argb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiply the RGB channels of a given ARGB color value by its alpha channel value.
|
||||
*
|
||||
* @param argb the ARGB color value to premultiply the RGB channels of
|
||||
* @return the resulting premultiplied ARGB color value
|
||||
*/
|
||||
public static int premultiply(int argb) {
|
||||
int alpha = getAlpha(argb);
|
||||
int red = (getRed(argb) * alpha + 127) / 255;
|
||||
int green = (getGreen(argb) * alpha + 127) / 255;
|
||||
int blue = (getBlue(argb) * alpha + 127) / 255;
|
||||
return argb(alpha, red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ARGB color value with the specified values for alpha, red, green, and blue channels.
|
||||
*
|
||||
* @param alpha the alpha value, between 0 and 255, to set in the ARGB color value
|
||||
* @param red the red value, between 0 and 255, to set in the ARGB color value
|
||||
* @param green the green value, between 0 and 255, to set in the ARGB color value
|
||||
* @param blue the blue value, between 0 and 255, to set in the ARGB color value
|
||||
* @return the resulting ARGB color value with the specified values for alpha, red, green, and blue channels
|
||||
*/
|
||||
public static int argb(int alpha, int red, int green, int blue) {
|
||||
return argbAlpha(alpha) | argbRed(red) | argbGreen(green) | argbBlue(blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ARGB color value with the specified values for alpha, red, green, and blue channels,
|
||||
* after validating that each channel value is in the range 0-255.
|
||||
*
|
||||
* @param alpha the alpha value, between 0 and 255, to set in the ARGB color value
|
||||
* @param red the red value, between 0 and 255, to set in the ARGB color value
|
||||
* @param green the green value, between 0 and 255, to set in the ARGB color value
|
||||
* @param blue the blue value, between 0 and 255, to set in the ARGB color value
|
||||
* @return the resulting ARGB color value with the specified values for alpha, red, green, and blue channels
|
||||
* @throws IllegalArgumentException if any channel value is outside the range 0-255
|
||||
*/
|
||||
public static int argbValidated(int alpha, int red, int green, int blue) throws IllegalArgumentException {
|
||||
if (alpha < 0 || alpha > 255 ||
|
||||
red < 0 || red > 255 ||
|
||||
green < 0 || green > 255 ||
|
||||
blue < 0 || blue > 255
|
||||
) throw new IllegalArgumentException("Channel values must be in range 0-255");
|
||||
return argb(alpha, red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ARGB color value with the specified alpha channel value and zero
|
||||
* for the red, green, and blue channels.
|
||||
*
|
||||
* @param alpha the alpha channel value to set in the ARGB color value
|
||||
* @return the resulting ARGB color value
|
||||
*/
|
||||
public static int argbAlpha(int alpha) { return alpha << 24; }
|
||||
|
||||
/**
|
||||
* Returns the ARGB color value with the specified red channel value and zero
|
||||
* for the alpha, green, and blue channels.
|
||||
*
|
||||
* @param red the red channel value to set in the ARGB color value
|
||||
* @return the resulting ARGB color value
|
||||
*/
|
||||
public static int argbRed(int red) { return red << 16; }
|
||||
|
||||
/**
|
||||
* Returns the ARGB color value with the specified red channel value and zero
|
||||
* for the alpha, red, and blue channels.
|
||||
*
|
||||
* @param green the green channel value to set in the ARGB color value
|
||||
* @return the resulting ARGB color value
|
||||
*/
|
||||
public static int argbGreen(int green) { return green << 8; }
|
||||
|
||||
/**
|
||||
* Returns the ARGB color value with the specified blue channel value and zero
|
||||
* for the alpha, red, and green channels.
|
||||
*
|
||||
* @param blue the blue channel value to set in the ARGB color value
|
||||
* @return the resulting ARGB color value
|
||||
*/
|
||||
public static int argbBlue(int blue) { return blue; }
|
||||
|
||||
/**
|
||||
* Returns an ARGB color value with the specified grayscale value for all four channels.
|
||||
*
|
||||
* @param value the grayscale value to set in all four channels of the ARGB color value
|
||||
* @return the resulting ARGB color value with the specified grayscale value for all four channels
|
||||
*/
|
||||
public static int argbGrayscale(int value) { return argb(value, value, value, value); }
|
||||
|
||||
public enum Channel {
|
||||
RED {
|
||||
@Override
|
||||
public int from(int argb) {
|
||||
return getRed(argb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int zero(int argb) {
|
||||
return zeroRed(argb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int argb(int value) {
|
||||
return argbRed(value);
|
||||
}
|
||||
},
|
||||
GREEN {
|
||||
@Override
|
||||
public int from(int argb) {
|
||||
return getGreen(argb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int zero(int argb) {
|
||||
return zeroGreen(argb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int argb(int value) {
|
||||
return argbGreen(value);
|
||||
}
|
||||
},
|
||||
BLUE {
|
||||
@Override
|
||||
public int from(int argb) {
|
||||
return getBlue(argb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int zero(int argb) {
|
||||
return zeroBlue(argb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int argb(int value) {
|
||||
return argbBlue(value);
|
||||
}
|
||||
},
|
||||
GRAYSCALE {
|
||||
@Override
|
||||
public int from(int argb) {
|
||||
return getGrayscale(argb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int zero(int argb) {
|
||||
return zeroGrayscale(argb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int argb(int value) {
|
||||
return argbAlpha(value);
|
||||
}
|
||||
},
|
||||
ALPHA {
|
||||
@Override
|
||||
public int from(int argb) {
|
||||
return getAlpha(argb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int zero(int argb) {
|
||||
return zeroAlpha(argb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int argb(int value) {
|
||||
return argbAlpha(value);
|
||||
}
|
||||
};
|
||||
|
||||
public abstract int from(int argb);
|
||||
|
||||
public abstract int zero(int argb);
|
||||
|
||||
public abstract int argb(int value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.dfsek.terra.addons.image.util;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
public class MapUtil {
|
||||
|
||||
private MapUtil() {}
|
||||
|
||||
/**
|
||||
* Utility method for applying transformations on a map's keys.
|
||||
*/
|
||||
public static <O, N, T> Map<N, T> mapKeys(Map<O, T> map, Function<O, N> mappingFunction) {
|
||||
return map
|
||||
.entrySet()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(
|
||||
e -> mappingFunction.apply(e.getKey()),
|
||||
Entry::getValue
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.dfsek.terra.addons.image.util;
|
||||
|
||||
public class MathUtil {
|
||||
private MathUtil() {}
|
||||
|
||||
public static double lerp(double x, double x1, double y1, double x2, double y2) {
|
||||
return (((y1-y2)*(x-x1))/(x1-x2))+y1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
schema-version: 1
|
||||
contributors:
|
||||
- Terra contributors
|
||||
id: library-image
|
||||
version: @VERSION@
|
||||
entrypoints:
|
||||
- "com.dfsek.terra.addons.image.ImageLibraryAddon"
|
||||
website:
|
||||
issues: https://github.com/PolyhedralDev/Terra/issues
|
||||
source: https://github.com/PolyhedralDev/Terra
|
||||
docs: https://terra.polydev.org
|
||||
license: MIT License
|
||||
21
common/addons/pipeline-image/LICENSE
Normal file
21
common/addons/pipeline-image/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2023 Polyhedral Development
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
7
common/addons/pipeline-image/build.gradle.kts
Normal file
7
common/addons/pipeline-image/build.gradle.kts
Normal file
@@ -0,0 +1,7 @@
|
||||
version = version("1.0.0")
|
||||
|
||||
dependencies {
|
||||
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
|
||||
compileOnlyApi(project(":common:addons:biome-provider-pipeline-v2"))
|
||||
compileOnlyApi(project(":common:addons:library-image"))
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.dfsek.terra.addons.biome.pipeline.image;
|
||||
|
||||
import com.dfsek.terra.addons.biome.pipeline.v2.api.Source;
|
||||
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
|
||||
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
|
||||
import com.dfsek.terra.addons.image.converter.ColorConverter;
|
||||
|
||||
|
||||
public class ImageSource implements Source {
|
||||
|
||||
private final ColorSampler colorSampler;
|
||||
|
||||
private final ColorConverter<PipelineBiome> colorConverter;
|
||||
|
||||
public ImageSource(ColorSampler colorSampler, ColorConverter<PipelineBiome> colorConverter) {
|
||||
this.colorSampler = colorSampler;
|
||||
this.colorConverter = colorConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PipelineBiome get(long seed, int x, int z) {
|
||||
return colorConverter.apply(colorSampler.apply(x, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<PipelineBiome> getBiomes() {
|
||||
return colorConverter.getEntries();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.dfsek.terra.addons.biome.pipeline.image;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.dfsek.terra.addons.biome.pipeline.image.config.ImageSourceTemplate;
|
||||
import com.dfsek.terra.addons.biome.pipeline.image.config.converter.ClosestPipelineBiomeColorConverterTemplate;
|
||||
import com.dfsek.terra.addons.biome.pipeline.image.config.converter.ExactPipelineBiomeColorConverterTemplate;
|
||||
import com.dfsek.terra.addons.biome.pipeline.image.config.converter.mapping.DefinedPipelineBiomeColorMappingTemplate;
|
||||
import com.dfsek.terra.addons.biome.pipeline.v2.BiomePipelineAddon;
|
||||
import com.dfsek.terra.addons.biome.pipeline.v2.api.Source;
|
||||
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.DelegatedPipelineBiome;
|
||||
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
|
||||
import com.dfsek.terra.addons.image.converter.ColorConverter;
|
||||
import com.dfsek.terra.addons.image.converter.mapping.BiomeDefinedColorMapping;
|
||||
import com.dfsek.terra.addons.image.converter.mapping.ColorMapping;
|
||||
import com.dfsek.terra.addons.manifest.api.AddonInitializer;
|
||||
import com.dfsek.terra.api.Platform;
|
||||
import com.dfsek.terra.api.addon.BaseAddon;
|
||||
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.registry.CheckedRegistry;
|
||||
import com.dfsek.terra.api.util.reflection.TypeKey;
|
||||
import com.dfsek.terra.api.world.biome.Biome;
|
||||
|
||||
|
||||
public class PipelineImageAddon implements AddonInitializer {
|
||||
|
||||
public static final TypeKey<Supplier<ObjectTemplate<ColorConverter<PipelineBiome>>>> PIPELINE_BIOME_COLOR_CONVERTER_REGISTRY_KEY = new TypeKey<>() {
|
||||
};
|
||||
|
||||
public static final TypeKey<Supplier<ObjectTemplate<ColorMapping<PipelineBiome>>>> PIPELINE_BIOME_COLOR_MAPPING_REGISTRY_KEY = new TypeKey<>() {
|
||||
};
|
||||
|
||||
@Inject
|
||||
private Platform platform;
|
||||
|
||||
@Inject
|
||||
private BaseAddon addon;
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
|
||||
platform.getEventManager()
|
||||
.getHandler(FunctionalEventHandler.class)
|
||||
.register(addon, ConfigPackPreLoadEvent.class)
|
||||
.priority(500)
|
||||
.then(event -> {
|
||||
CheckedRegistry<Supplier<ObjectTemplate<ColorConverter<PipelineBiome>>>> biomeColorConverterRegistry = event.getPack().getOrCreateRegistry(
|
||||
PIPELINE_BIOME_COLOR_CONVERTER_REGISTRY_KEY);
|
||||
biomeColorConverterRegistry.register(addon.key("EXACT"), ExactPipelineBiomeColorConverterTemplate::new);
|
||||
biomeColorConverterRegistry.register(addon.key("CLOSEST"), ClosestPipelineBiomeColorConverterTemplate::new);
|
||||
})
|
||||
.then(event -> {
|
||||
CheckedRegistry<Supplier<ObjectTemplate<Source>>> sourceRegistry = event.getPack().getOrCreateRegistry(BiomePipelineAddon.SOURCE_REGISTRY_KEY);
|
||||
sourceRegistry.register(addon.key("IMAGE"), ImageSourceTemplate::new);
|
||||
})
|
||||
.then(event -> {
|
||||
CheckedRegistry<Supplier<ObjectTemplate<ColorMapping<PipelineBiome>>>> biomeColorMappingRegistry = event.getPack().getOrCreateRegistry(
|
||||
PIPELINE_BIOME_COLOR_MAPPING_REGISTRY_KEY);
|
||||
biomeColorMappingRegistry.register(addon.key("USE_BIOME_COLORS"), () -> () -> new BiomeDefinedColorMapping<>(event.getPack().getRegistry(Biome.class), DelegatedPipelineBiome::new));
|
||||
biomeColorMappingRegistry.register(addon.key("MAP"), DefinedPipelineBiomeColorMappingTemplate::new);
|
||||
})
|
||||
.failThrough();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.dfsek.terra.addons.biome.pipeline.image.config;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
|
||||
|
||||
import com.dfsek.terra.addons.biome.pipeline.image.ImageSource;
|
||||
import com.dfsek.terra.addons.biome.pipeline.v2.api.Source;
|
||||
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
|
||||
import com.dfsek.terra.addons.image.colorsampler.ColorSampler;
|
||||
import com.dfsek.terra.addons.image.converter.ColorConverter;
|
||||
|
||||
|
||||
public class ImageSourceTemplate implements ObjectTemplate<Source> {
|
||||
|
||||
@Value("color-sampler")
|
||||
private ColorSampler colorSampler;
|
||||
|
||||
@Value("color-conversion")
|
||||
private ColorConverter<PipelineBiome> colorConverter;
|
||||
|
||||
@Override
|
||||
public Source get() {
|
||||
return new ImageSource(colorSampler, colorConverter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.dfsek.terra.addons.biome.pipeline.image.config.converter;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
|
||||
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
|
||||
import com.dfsek.terra.addons.image.config.converter.ClosestColorConverterTemplate;
|
||||
import com.dfsek.terra.addons.image.converter.mapping.ColorMapping;
|
||||
|
||||
|
||||
public class ClosestPipelineBiomeColorConverterTemplate extends ClosestColorConverterTemplate<PipelineBiome> {
|
||||
|
||||
@Value("match")
|
||||
private ColorMapping<PipelineBiome> match;
|
||||
|
||||
@Override
|
||||
protected ColorMapping<PipelineBiome> getMapping() {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.dfsek.terra.addons.biome.pipeline.image.config.converter;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Default;
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
|
||||
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
|
||||
import com.dfsek.terra.addons.image.config.converter.ExactColorConverterTemplate;
|
||||
import com.dfsek.terra.addons.image.converter.mapping.ColorMapping;
|
||||
|
||||
|
||||
public class ExactPipelineBiomeColorConverterTemplate extends ExactColorConverterTemplate<PipelineBiome> {
|
||||
|
||||
@Value("match")
|
||||
private ColorMapping<PipelineBiome> match;
|
||||
|
||||
@Value("else")
|
||||
private PipelineBiome fallback;
|
||||
|
||||
@Value("ignore-alpha")
|
||||
@Default
|
||||
private boolean ignoreAlpha = true;
|
||||
|
||||
@Override
|
||||
protected ColorMapping<PipelineBiome> getMapping() {
|
||||
return match;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PipelineBiome getFallback() {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean ignoreAlpha() {
|
||||
return ignoreAlpha;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.dfsek.terra.addons.biome.pipeline.image.config.converter.mapping;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
import com.dfsek.tectonic.api.config.template.object.ObjectTemplate;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome;
|
||||
import com.dfsek.terra.addons.image.config.ColorLoader.ColorString;
|
||||
import com.dfsek.terra.addons.image.converter.mapping.ColorMapping;
|
||||
import com.dfsek.terra.addons.image.util.MapUtil;
|
||||
|
||||
|
||||
public class DefinedPipelineBiomeColorMappingTemplate implements ObjectTemplate<ColorMapping<PipelineBiome>> {
|
||||
|
||||
@Value("map")
|
||||
Map<ColorString, PipelineBiome> map;
|
||||
|
||||
@Override
|
||||
public ColorMapping<PipelineBiome> get() {
|
||||
var map = MapUtil.mapKeys(this.map, ColorString::getColor);
|
||||
return () -> map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
schema-version: 1
|
||||
contributors:
|
||||
- Terra contributors
|
||||
id: pipeline-image
|
||||
version: @VERSION@
|
||||
entrypoints:
|
||||
- "com.dfsek.terra.addons.biome.pipeline.image.PipelineImageAddon"
|
||||
website:
|
||||
issues: https://github.com/PolyhedralDev/Terra/issues
|
||||
source: https://github.com/PolyhedralDev/Terra
|
||||
docs: https://terra.polydev.org
|
||||
license: MIT License
|
||||
depends:
|
||||
library-image: "1.+"
|
||||
biome-provider-pipeline-v2: "1.+"
|
||||
@@ -30,6 +30,7 @@ import com.dfsek.terra.api.inject.annotations.Inject;
|
||||
import com.dfsek.terra.api.registry.CheckedRegistry;
|
||||
import com.dfsek.terra.api.structure.Structure;
|
||||
import com.dfsek.terra.api.util.StringUtil;
|
||||
import com.dfsek.terra.api.util.vector.Vector3Int;
|
||||
|
||||
|
||||
public class SpongeSchematicAddon implements AddonInitializer {
|
||||
@@ -71,9 +72,36 @@ public class SpongeSchematicAddon implements AddonInitializer {
|
||||
public SpongeStructure convert(InputStream in, String id) {
|
||||
try {
|
||||
CompoundTag baseTag = (CompoundTag) new NBTDeserializer(false).fromStream(detectDecompression(in)).getTag();
|
||||
int ver = baseTag.getInt("Version");
|
||||
int wid = baseTag.getShort("Width");
|
||||
int len = baseTag.getShort("Length");
|
||||
int hei = baseTag.getShort("Height");
|
||||
|
||||
CompoundTag metadata = baseTag.getCompoundTag("Metadata");
|
||||
|
||||
Vector3Int offset = switch(ver) {
|
||||
case 2 -> {
|
||||
// Use WorldEdit defined legacy relative offset if it exists in schematic metadata
|
||||
IntTag worldEditOffsetX = metadata.getIntTag("WEOffsetX");
|
||||
IntTag worldEditOffsetY = metadata.getIntTag("WEOffsetY");
|
||||
IntTag worldEditOffsetZ = metadata.getIntTag("WEOffsetZ");
|
||||
if(worldEditOffsetX != null || worldEditOffsetY != null || worldEditOffsetZ != null) {
|
||||
if(worldEditOffsetX == null || worldEditOffsetY == null || worldEditOffsetZ == null) {
|
||||
throw new IllegalArgumentException("Failed to parse Sponge schematic: Malformed WorldEdit offset");
|
||||
}
|
||||
yield Vector3Int.of(worldEditOffsetX.asInt(), worldEditOffsetY.asInt(), worldEditOffsetZ.asInt());
|
||||
} else {
|
||||
// Relative offset handling via 'Offset' field is ambiguous in spec 2 so just apply no offset
|
||||
yield Vector3Int.zero();
|
||||
}
|
||||
}
|
||||
case 3 -> {
|
||||
// Relative offset is more concretely defined in spec 3 to use 'Offset' field
|
||||
int[] offsetArray = baseTag.getIntArray("Offset");
|
||||
yield Vector3Int.of(offsetArray[0], offsetArray[1], offsetArray[2]);
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Failed to parse Sponge schematic: Unsupported format version: " + ver);
|
||||
};
|
||||
|
||||
ByteArrayTag blocks = baseTag.getByteArrayTag("BlockData");
|
||||
|
||||
@@ -97,7 +125,7 @@ public class SpongeSchematicAddon implements AddonInitializer {
|
||||
}
|
||||
}
|
||||
|
||||
return new SpongeStructure(states, addon.key(id));
|
||||
return new SpongeStructure(states, offset, addon.key(id));
|
||||
} catch(IOException e) {
|
||||
throw new IllegalArgumentException("Failed to parse Sponge schematic: ", e);
|
||||
}
|
||||
|
||||
@@ -23,10 +23,15 @@ public class SpongeStructure implements Structure, Keyed<SpongeStructure> {
|
||||
|
||||
private final BlockState[][][] blocks;
|
||||
|
||||
private final int offsetX, offsetY, offsetZ;
|
||||
|
||||
private final RegistryKey id;
|
||||
|
||||
public SpongeStructure(BlockState[][][] blocks, RegistryKey id) {
|
||||
public SpongeStructure(BlockState[][][] blocks, Vector3Int offset, RegistryKey id) {
|
||||
this.blocks = blocks;
|
||||
this.offsetX = offset.getX();
|
||||
this.offsetY = offset.getY();
|
||||
this.offsetZ = offset.getZ();
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@@ -37,13 +42,15 @@ public class SpongeStructure implements Structure, Keyed<SpongeStructure> {
|
||||
int bZ = location.getZ();
|
||||
for(int x = 0; x < blocks.length; x++) {
|
||||
for(int z = 0; z < blocks[x].length; z++) {
|
||||
Vector2Int r = Vector2Int.of(x, z).rotate(rotation);
|
||||
int oX = x + offsetX;
|
||||
int oZ = z + offsetZ;
|
||||
Vector2Int r = Vector2Int.of(oX, oZ).rotate(rotation);
|
||||
int rX = r.getX();
|
||||
int rZ = r.getZ();
|
||||
for(int y = 0; y < blocks[x][z].length; y++) {
|
||||
BlockState state = blocks[x][z][y];
|
||||
if(state == null) continue;
|
||||
world.setBlockState(bX + rX, bY + y, bZ + rZ, state);
|
||||
world.setBlockState(bX + rX, bY + y + offsetY, bZ + rZ, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,24 +27,43 @@ import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.AnnotatedType;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.dfsek.terra.api.config.ConfigPack;
|
||||
import com.dfsek.terra.api.config.Loader;
|
||||
import com.dfsek.terra.api.properties.Properties;
|
||||
|
||||
|
||||
/*
|
||||
* @deprecated Use the Image and ImageLoader class provided by the library-image addon instead. This is subject to removal in v7.
|
||||
*/
|
||||
@Deprecated
|
||||
public class BufferedImageLoader implements TypeLoader<BufferedImage> {
|
||||
private final Loader files;
|
||||
|
||||
public BufferedImageLoader(Loader files) {
|
||||
private final ConfigPack pack;
|
||||
|
||||
public BufferedImageLoader(Loader files, ConfigPack pack) {
|
||||
this.files = files;
|
||||
this.pack = pack;
|
||||
if (!pack.getContext().has(ImageCache.class))
|
||||
pack.getContext().put(new ImageCache(new ConcurrentHashMap<>()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage load(@NotNull AnnotatedType t, @NotNull Object c, @NotNull ConfigLoader loader, DepthTracker depthTracker)
|
||||
throws LoadException {
|
||||
try {
|
||||
return ImageIO.read(files.get((String) c));
|
||||
} catch(IOException e) {
|
||||
throw new LoadException("Unable to load image", e, depthTracker);
|
||||
}
|
||||
return pack.getContext().get(ImageCache.class).map.computeIfAbsent((String) c, s -> {
|
||||
try {
|
||||
return ImageIO.read(files.get(s));
|
||||
} catch(IOException e) {
|
||||
throw new LoadException("Unable to load image", e, depthTracker);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Cache prevents configs from loading the same image multiple times into memory
|
||||
*/
|
||||
private record ImageCache(ConcurrentHashMap<String, BufferedImage> map) implements Properties {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,7 +283,7 @@ public class ConfigPackImpl implements ConfigPack {
|
||||
@Override
|
||||
public void register(TypeRegistry registry) {
|
||||
registry.registerLoader(ConfigType.class, configTypeRegistry)
|
||||
.registerLoader(BufferedImage.class, new BufferedImageLoader(loader));
|
||||
.registerLoader(BufferedImage.class, new BufferedImageLoader(loader, this));
|
||||
registryMap.forEach(registry::registerLoader);
|
||||
shortcuts.forEach(registry::registerLoader); // overwrite with delegated shortcuts if present
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ dependencies {
|
||||
shaded(project(":platforms:bukkit:common"))
|
||||
shaded(project(":platforms:bukkit:nms:v1_18_R2", configuration = "reobf"))
|
||||
shaded(project(":platforms:bukkit:nms:v1_19_R1", configuration = "reobf"))
|
||||
shaded(project(":platforms:bukkit:nms:v1_19_R2", configuration = "reobf"))
|
||||
shaded(project(":platforms:bukkit:nms:v1_19_R3", configuration = "reobf"))
|
||||
shaded("xyz.jpenilla", "reflection-remapper", Versions.Bukkit.reflectionRemapper)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.dfsek.terra.bukkit.generator;
|
||||
|
||||
import org.bukkit.generator.BlockPopulator;
|
||||
import org.bukkit.generator.LimitedRegion;
|
||||
import org.bukkit.generator.WorldInfo;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import com.dfsek.terra.api.block.state.BlockState;
|
||||
import com.dfsek.terra.api.config.ConfigPack;
|
||||
import com.dfsek.terra.bukkit.world.BukkitProtoWorld;
|
||||
|
||||
|
||||
public class BukkitBlockPopulator extends BlockPopulator {
|
||||
private ConfigPack pack;
|
||||
private final BlockState air;
|
||||
|
||||
public BukkitBlockPopulator(ConfigPack pack, BlockState air) {
|
||||
this.pack = pack;
|
||||
this.air = air;
|
||||
}
|
||||
|
||||
public void setPack(ConfigPack pack) {
|
||||
this.pack = pack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populate(@NotNull WorldInfo worldInfo, @NotNull Random random, int chunkX, int chunkZ,
|
||||
@NotNull LimitedRegion limitedRegion) {
|
||||
pack.getStages().forEach(generationStage -> generationStage.populate(new BukkitProtoWorld(limitedRegion, air, pack.getBiomeProvider())));
|
||||
}
|
||||
}
|
||||
@@ -45,12 +45,14 @@ public class BukkitChunkGeneratorWrapper extends org.bukkit.generator.ChunkGener
|
||||
private final BlockState air;
|
||||
private ChunkGenerator delegate;
|
||||
private ConfigPack pack;
|
||||
private final BukkitBlockPopulator blockPopulator;
|
||||
|
||||
|
||||
public BukkitChunkGeneratorWrapper(ChunkGenerator delegate, ConfigPack pack, BlockState air) {
|
||||
this.delegate = delegate;
|
||||
this.pack = pack;
|
||||
this.air = air;
|
||||
this.blockPopulator = new BukkitBlockPopulator(pack, air);
|
||||
}
|
||||
|
||||
public void setDelegate(ChunkGenerator delegate) {
|
||||
@@ -70,16 +72,7 @@ public class BukkitChunkGeneratorWrapper extends org.bukkit.generator.ChunkGener
|
||||
|
||||
@Override
|
||||
public @NotNull List<BlockPopulator> getDefaultPopulators(@NotNull World world) {
|
||||
return pack.getStages()
|
||||
.stream()
|
||||
.map(generationStage -> new BlockPopulator() {
|
||||
@Override
|
||||
public void populate(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z,
|
||||
@NotNull LimitedRegion limitedRegion) {
|
||||
generationStage.populate(new BukkitProtoWorld(limitedRegion, air, pack.getBiomeProvider()));
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
return List.of(blockPopulator);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,7 +6,7 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
api(project(":platforms:bukkit:common"))
|
||||
paperDevBundle(Versions.Bukkit.paperDevBundle)
|
||||
paperDevBundle("1.19-R0.1-SNAPSHOT")
|
||||
implementation("xyz.jpenilla", "reflection-remapper", "0.1.0-SNAPSHOT")
|
||||
}
|
||||
|
||||
@@ -14,4 +14,4 @@ tasks {
|
||||
assemble {
|
||||
dependsOn("reobfJar")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
17
platforms/bukkit/nms/v1_19_R2/build.gradle.kts
Normal file
17
platforms/bukkit/nms/v1_19_R2/build.gradle.kts
Normal file
@@ -0,0 +1,17 @@
|
||||
apply(plugin = "io.papermc.paperweight.userdev")
|
||||
|
||||
repositories {
|
||||
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":platforms:bukkit:common"))
|
||||
paperDevBundle("1.19.3-R0.1-SNAPSHOT")
|
||||
implementation("xyz.jpenilla", "reflection-remapper", "0.1.0-SNAPSHOT")
|
||||
}
|
||||
|
||||
tasks {
|
||||
assemble {
|
||||
dependsOn("reobfJar")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.dfsek.terra.bukkit.nms.v1_19_R2;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Holder.Reference;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import net.minecraft.core.WritableRegistry;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.dfsek.terra.bukkit.world.BukkitPlatformBiome;
|
||||
import com.dfsek.terra.registry.master.ConfigRegistry;
|
||||
|
||||
|
||||
public class AwfulBukkitHacks {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AwfulBukkitHacks.class);
|
||||
|
||||
private static final Map<ResourceLocation, List<ResourceLocation>> terraBiomeMap = new HashMap<>();
|
||||
|
||||
public static void registerBiomes(ConfigRegistry configRegistry) {
|
||||
try {
|
||||
LOGGER.info("Hacking biome registry...");
|
||||
WritableRegistry<Biome> biomeRegistry = (WritableRegistry<Biome>) RegistryFetcher.biomeRegistry();
|
||||
|
||||
Reflection.MAPPED_REGISTRY.setFrozen((MappedRegistry<?>) biomeRegistry, false);
|
||||
|
||||
configRegistry.forEach(pack -> pack.getRegistry(com.dfsek.terra.api.world.biome.Biome.class).forEach((key, biome) -> {
|
||||
try {
|
||||
BukkitPlatformBiome platformBiome = (BukkitPlatformBiome) biome.getPlatformBiome();
|
||||
NamespacedKey vanillaBukkitKey = platformBiome.getHandle().getKey();
|
||||
ResourceLocation vanillaMinecraftKey = new ResourceLocation(vanillaBukkitKey.getNamespace(), vanillaBukkitKey.getKey());
|
||||
Biome platform = NMSBiomeInjector.createBiome(biome, Objects.requireNonNull(biomeRegistry.get(vanillaMinecraftKey)));
|
||||
|
||||
ResourceKey<Biome> delegateKey = ResourceKey.create(
|
||||
Registries.BIOME,
|
||||
new ResourceLocation("terra", NMSBiomeInjector.createBiomeID(pack, key))
|
||||
);
|
||||
|
||||
Reference<Biome> holder = biomeRegistry.register(delegateKey, platform, Lifecycle.stable());
|
||||
Reflection.REFERENCE.invokeBindValue(holder, platform); // IMPORTANT: bind holder.
|
||||
|
||||
platformBiome.getContext().put(new NMSBiomeInfo(delegateKey));
|
||||
|
||||
terraBiomeMap.computeIfAbsent(vanillaMinecraftKey, i -> new ArrayList<>()).add(delegateKey.location());
|
||||
|
||||
LOGGER.debug("Registered biome: " + delegateKey);
|
||||
} catch(NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}));
|
||||
|
||||
Reflection.MAPPED_REGISTRY.setFrozen((MappedRegistry<?>) biomeRegistry, true); // freeze registry again :)
|
||||
|
||||
LOGGER.info("Doing tag garbage....");
|
||||
Map<TagKey<Biome>, List<Holder<Biome>>> collect = biomeRegistry
|
||||
.getTags() // streamKeysAndEntries
|
||||
.collect(HashMap::new,
|
||||
(map, pair) ->
|
||||
map.put(pair.getFirst(), new ArrayList<>(pair.getSecond().stream().toList())),
|
||||
HashMap::putAll);
|
||||
|
||||
terraBiomeMap
|
||||
.forEach((vb, terraBiomes) ->
|
||||
NMSBiomeInjector.getEntry(biomeRegistry, vb).ifPresentOrElse(
|
||||
vanilla -> terraBiomes.forEach(
|
||||
tb -> NMSBiomeInjector.getEntry(biomeRegistry, tb).ifPresentOrElse(
|
||||
terra -> {
|
||||
LOGGER.debug("{} (vanilla for {}): {}",
|
||||
vanilla.unwrapKey().orElseThrow().location(),
|
||||
terra.unwrapKey().orElseThrow().location(),
|
||||
vanilla.tags().toList());
|
||||
vanilla.tags()
|
||||
.forEach(tag -> collect
|
||||
.computeIfAbsent(tag, t -> new ArrayList<>())
|
||||
.add(terra));
|
||||
},
|
||||
() -> LOGGER.error("No such biome: {}", tb))),
|
||||
() -> LOGGER.error("No vanilla biome: {}", vb)));
|
||||
|
||||
biomeRegistry.resetTags();
|
||||
biomeRegistry.bindTags(ImmutableMap.copyOf(collect));
|
||||
|
||||
} catch(SecurityException | IllegalArgumentException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.dfsek.terra.bukkit.nms.v1_19_R2;
|
||||
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
|
||||
import com.dfsek.terra.api.properties.Properties;
|
||||
|
||||
|
||||
public record NMSBiomeInfo(ResourceKey<Biome> biomeKey) implements Properties {
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.dfsek.terra.bukkit.nms.v1_19_R2;
|
||||
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.BiomeSpecialEffects;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.dfsek.terra.api.config.ConfigPack;
|
||||
import com.dfsek.terra.bukkit.config.VanillaBiomeProperties;
|
||||
|
||||
|
||||
public class NMSBiomeInjector {
|
||||
|
||||
public static <T> Optional<Holder<T>> getEntry(Registry<T> registry, ResourceLocation identifier) {
|
||||
return registry.getOptional(identifier)
|
||||
.flatMap(registry::getResourceKey)
|
||||
.flatMap(registry::getHolder);
|
||||
}
|
||||
|
||||
public static Biome createBiome(com.dfsek.terra.api.world.biome.Biome biome, Biome vanilla)
|
||||
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
|
||||
Biome.BiomeBuilder builder = new Biome.BiomeBuilder();
|
||||
|
||||
builder
|
||||
.precipitation(vanilla.getPrecipitation())
|
||||
.downfall(vanilla.getDownfall())
|
||||
.temperature(vanilla.getBaseTemperature())
|
||||
.mobSpawnSettings(vanilla.getMobSettings())
|
||||
.generationSettings(vanilla.getGenerationSettings());
|
||||
|
||||
|
||||
BiomeSpecialEffects.Builder effects = new BiomeSpecialEffects.Builder();
|
||||
|
||||
effects.grassColorModifier(vanilla.getSpecialEffects().getGrassColorModifier());
|
||||
|
||||
VanillaBiomeProperties vanillaBiomeProperties = biome.getContext().get(VanillaBiomeProperties.class);
|
||||
|
||||
effects.fogColor(Objects.requireNonNullElse(vanillaBiomeProperties.getFogColor(), vanilla.getFogColor()))
|
||||
|
||||
.waterColor(Objects.requireNonNullElse(vanillaBiomeProperties.getWaterColor(), vanilla.getWaterColor()))
|
||||
|
||||
.waterFogColor(Objects.requireNonNullElse(vanillaBiomeProperties.getWaterFogColor(), vanilla.getWaterFogColor()))
|
||||
|
||||
.skyColor(Objects.requireNonNullElse(vanillaBiomeProperties.getSkyColor(), vanilla.getSkyColor()));
|
||||
|
||||
if(vanillaBiomeProperties.getFoliageColor() == null) {
|
||||
vanilla.getSpecialEffects().getFoliageColorOverride().ifPresent(effects::foliageColorOverride);
|
||||
} else {
|
||||
effects.foliageColorOverride(vanillaBiomeProperties.getFoliageColor());
|
||||
}
|
||||
|
||||
if(vanillaBiomeProperties.getGrassColor() == null) {
|
||||
vanilla.getSpecialEffects().getGrassColorOverride().ifPresent(effects::grassColorOverride);
|
||||
} else {
|
||||
// grass
|
||||
effects.grassColorOverride(vanillaBiomeProperties.getGrassColor());
|
||||
}
|
||||
|
||||
vanilla.getAmbientLoop().ifPresent(effects::ambientLoopSound);
|
||||
vanilla.getAmbientAdditions().ifPresent(effects::ambientAdditionsSound);
|
||||
vanilla.getAmbientMood().ifPresent(effects::ambientMoodSound);
|
||||
vanilla.getBackgroundMusic().ifPresent(effects::backgroundMusic);
|
||||
vanilla.getAmbientParticle().ifPresent(effects::ambientParticle);
|
||||
|
||||
builder.specialEffects(effects.build());
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static String createBiomeID(ConfigPack pack, com.dfsek.terra.api.registry.key.RegistryKey biomeID) {
|
||||
return pack.getID()
|
||||
.toLowerCase() + "/" + biomeID.getNamespace().toLowerCase(Locale.ROOT) + "/" + biomeID.getID().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.dfsek.terra.bukkit.nms.v1_19_R2;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.biome.Climate.Sampler;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
|
||||
import com.dfsek.terra.bukkit.world.BukkitPlatformBiome;
|
||||
|
||||
|
||||
public class NMSBiomeProvider extends BiomeSource {
|
||||
private final BiomeProvider delegate;
|
||||
private final long seed;
|
||||
private final Registry<Biome> biomeRegistry = RegistryFetcher.biomeRegistry();
|
||||
|
||||
public NMSBiomeProvider(BiomeProvider delegate, long seed) {
|
||||
super(delegate.stream()
|
||||
.map(biome -> RegistryFetcher.biomeRegistry()
|
||||
.getHolderOrThrow(((BukkitPlatformBiome) biome.getPlatformBiome()).getContext()
|
||||
.get(NMSBiomeInfo.class)
|
||||
.biomeKey())));
|
||||
this.delegate = delegate;
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull Codec<? extends BiomeSource> codec() {
|
||||
return BiomeSource.CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Holder<Biome> getNoiseBiome(int x, int y, int z, @NotNull Sampler sampler) {
|
||||
return biomeRegistry.getHolderOrThrow(((BukkitPlatformBiome) delegate.getBiome(x << 2, y << 2, z << 2, seed)
|
||||
.getPlatformBiome()).getContext()
|
||||
.get(NMSBiomeInfo.class)
|
||||
.biomeKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.dfsek.terra.bukkit.nms.v1_19_R2;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.WorldGenRegion;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.NoiseColumn;
|
||||
import net.minecraft.world.level.StructureManager;
|
||||
import net.minecraft.world.level.WorldGenLevel;
|
||||
import net.minecraft.world.level.biome.BiomeManager;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import net.minecraft.world.level.levelgen.Beardifier;
|
||||
import net.minecraft.world.level.levelgen.DensityFunction.SinglePointContext;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep.Carving;
|
||||
import net.minecraft.world.level.levelgen.Heightmap.Types;
|
||||
import net.minecraft.world.level.levelgen.RandomState;
|
||||
import net.minecraft.world.level.levelgen.blending.Blender;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.block.data.CraftBlockData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import com.dfsek.terra.api.config.ConfigPack;
|
||||
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
|
||||
import com.dfsek.terra.api.world.info.WorldProperties;
|
||||
import com.dfsek.terra.bukkit.config.PreLoadCompatibilityOptions;
|
||||
import com.dfsek.terra.bukkit.world.BukkitWorldProperties;
|
||||
import com.dfsek.terra.bukkit.world.block.data.BukkitBlockState;
|
||||
|
||||
|
||||
public class NMSChunkGeneratorDelegate extends ChunkGenerator {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(NMSChunkGeneratorDelegate.class);
|
||||
private final com.dfsek.terra.api.world.chunk.generation.ChunkGenerator delegate;
|
||||
|
||||
private final ChunkGenerator vanilla;
|
||||
private final ConfigPack pack;
|
||||
|
||||
private final long seed;
|
||||
|
||||
public NMSChunkGeneratorDelegate(ChunkGenerator vanilla, ConfigPack pack, NMSBiomeProvider biomeProvider, long seed) {
|
||||
super(biomeProvider);
|
||||
this.delegate = pack.getGeneratorProvider().newInstance(pack);
|
||||
this.vanilla = vanilla;
|
||||
this.pack = pack;
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull Codec<? extends ChunkGenerator> codec() {
|
||||
return ChunkGenerator.CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyCarvers(@NotNull WorldGenRegion chunkRegion, long seed, @NotNull RandomState noiseConfig, @NotNull BiomeManager world,
|
||||
@NotNull StructureManager structureAccessor, @NotNull ChunkAccess chunk, @NotNull Carving carverStep) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildSurface(@NotNull WorldGenRegion region, @NotNull StructureManager structures, @NotNull RandomState noiseConfig,
|
||||
@NotNull ChunkAccess chunk) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyBiomeDecoration(@NotNull WorldGenLevel world, @NotNull ChunkAccess chunk,
|
||||
@NotNull StructureManager structureAccessor) {
|
||||
vanilla.applyBiomeDecoration(world, chunk, structureAccessor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spawnOriginalMobs(@NotNull WorldGenRegion region) {
|
||||
vanilla.spawnOriginalMobs(region);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGenDepth() {
|
||||
return vanilla.getGenDepth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull CompletableFuture<ChunkAccess> fillFromNoise(@NotNull Executor executor, @NotNull Blender blender,
|
||||
@NotNull RandomState noiseConfig,
|
||||
@NotNull StructureManager structureAccessor, @NotNull ChunkAccess chunk) {
|
||||
return vanilla.fillFromNoise(executor, blender, noiseConfig, structureAccessor, chunk)
|
||||
.thenApply(c -> {
|
||||
LevelAccessor level = Reflection.STRUCTURE_MANAGER.getLevel(structureAccessor);
|
||||
BiomeProvider biomeProvider = pack.getBiomeProvider();
|
||||
PreLoadCompatibilityOptions compatibilityOptions = pack.getContext().get(PreLoadCompatibilityOptions.class);
|
||||
if(compatibilityOptions.isBeard()) {
|
||||
beard(structureAccessor, chunk, new BukkitWorldProperties(level.getMinecraftWorld().getWorld()), biomeProvider, compatibilityOptions);
|
||||
}
|
||||
return c;
|
||||
});
|
||||
}
|
||||
|
||||
private void beard(StructureManager structureAccessor, ChunkAccess chunk, WorldProperties world, BiomeProvider biomeProvider,
|
||||
PreLoadCompatibilityOptions compatibilityOptions) {
|
||||
Beardifier structureWeightSampler = Beardifier.forStructuresInChunk(structureAccessor, chunk.getPos());
|
||||
double threshold = compatibilityOptions.getBeardThreshold();
|
||||
double airThreshold = compatibilityOptions.getAirThreshold();
|
||||
int xi = chunk.getPos().x << 4;
|
||||
int zi = chunk.getPos().z << 4;
|
||||
for(int x = 0; x < 16; x++) {
|
||||
for(int z = 0; z < 16; z++) {
|
||||
int depth = 0;
|
||||
for(int y = world.getMaxHeight(); y >= world.getMinHeight(); y--) {
|
||||
double noise = structureWeightSampler.compute(new SinglePointContext(x + xi, y, z + zi));
|
||||
if(noise > threshold) {
|
||||
chunk.setBlockState(new BlockPos(x, y, z), ((CraftBlockData) ((BukkitBlockState) delegate
|
||||
.getPalette(x + xi, y, z + zi, world, biomeProvider)
|
||||
.get(depth, x + xi, y, z + zi, world.getSeed())).getHandle()).getState(), false);
|
||||
depth++;
|
||||
} else if(noise < airThreshold) {
|
||||
chunk.setBlockState(new BlockPos(x, y, z), Blocks.AIR.defaultBlockState(), false);
|
||||
} else {
|
||||
depth = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSeaLevel() {
|
||||
return vanilla.getSeaLevel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinY() {
|
||||
return vanilla.getMinY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBaseHeight(int x, int z, @NotNull Types heightmap, @NotNull LevelHeightAccessor world, @NotNull RandomState noiseConfig) {
|
||||
WorldProperties properties = new NMSWorldProperties(seed, world);
|
||||
int y = properties.getMaxHeight();
|
||||
BiomeProvider biomeProvider = pack.getBiomeProvider();
|
||||
while(y >= getMinY() && !heightmap.isOpaque().test(
|
||||
((CraftBlockData) delegate.getBlock(properties, x, y - 1, z, biomeProvider).getHandle()).getState())) {
|
||||
y--;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull NoiseColumn getBaseColumn(int x, int z, @NotNull LevelHeightAccessor world, @NotNull RandomState noiseConfig) {
|
||||
/*
|
||||
BlockState[] array = new BlockState[world.getHeight()];
|
||||
WorldProperties properties = new NMSWorldProperties(seed, world);
|
||||
BiomeProvider biomeProvider = pack.getBiomeProvider().caching(properties);
|
||||
for(int y = properties.getMaxHeight() - 1; y >= properties.getMinHeight(); y--) {
|
||||
array[y - properties.getMinHeight()] = ((CraftBlockData) delegate.getBlock(properties, x, y, z, biomeProvider)
|
||||
.getHandle()).getState();
|
||||
}
|
||||
return new NoiseColumn(getMinY(), array);
|
||||
|
||||
*/
|
||||
return vanilla.getBaseColumn(x, z, world, noiseConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDebugScreenInfo(@NotNull List<String> text, @NotNull RandomState noiseConfig, @NotNull BlockPos pos) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.dfsek.terra.bukkit.nms.v1_19_R2;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import com.dfsek.terra.bukkit.PlatformImpl;
|
||||
import com.dfsek.terra.bukkit.nms.Initializer;
|
||||
|
||||
|
||||
public class NMSInitializer implements Initializer {
|
||||
@Override
|
||||
public void initialize(PlatformImpl platform) {
|
||||
AwfulBukkitHacks.registerBiomes(platform.getRawConfigRegistry());
|
||||
Bukkit.getPluginManager().registerEvents(new NMSInjectListener(), platform.getPlugin());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.dfsek.terra.bukkit.nms.v1_19_R2;
|
||||
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.CraftWorld;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.WorldInitEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import com.dfsek.terra.api.config.ConfigPack;
|
||||
import com.dfsek.terra.bukkit.generator.BukkitChunkGeneratorWrapper;
|
||||
|
||||
|
||||
public class NMSInjectListener implements Listener {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(NMSInjectListener.class);
|
||||
private static final Set<World> INJECTED = new HashSet<>();
|
||||
private static final ReentrantLock INJECT_LOCK = new ReentrantLock();
|
||||
|
||||
@EventHandler
|
||||
public void onWorldInit(WorldInitEvent event) {
|
||||
if(!INJECTED.contains(event.getWorld()) &&
|
||||
event.getWorld().getGenerator() instanceof BukkitChunkGeneratorWrapper bukkitChunkGeneratorWrapper) {
|
||||
INJECT_LOCK.lock();
|
||||
INJECTED.add(event.getWorld());
|
||||
LOGGER.info("Preparing to take over the world: {}", event.getWorld().getName());
|
||||
CraftWorld craftWorld = (CraftWorld) event.getWorld();
|
||||
ServerLevel serverWorld = craftWorld.getHandle();
|
||||
|
||||
ConfigPack pack = bukkitChunkGeneratorWrapper.getPack();
|
||||
|
||||
ChunkGenerator vanilla = serverWorld.getChunkSource().getGenerator();
|
||||
NMSBiomeProvider provider = new NMSBiomeProvider(pack.getBiomeProvider(), craftWorld.getSeed());
|
||||
|
||||
serverWorld.getChunkSource().chunkMap.generator = new NMSChunkGeneratorDelegate(vanilla, pack, provider, craftWorld.getSeed());
|
||||
|
||||
LOGGER.info("Successfully injected into world.");
|
||||
|
||||
INJECT_LOCK.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.dfsek.terra.bukkit.nms.v1_19_R2;
|
||||
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
|
||||
import com.dfsek.terra.api.world.info.WorldProperties;
|
||||
|
||||
|
||||
public class NMSWorldProperties implements WorldProperties {
|
||||
private final long seed;
|
||||
private final LevelHeightAccessor height;
|
||||
|
||||
public NMSWorldProperties(long seed, LevelHeightAccessor height) {
|
||||
this.seed = seed;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHandle() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSeed() {
|
||||
return seed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHeight() {
|
||||
return height.getMaxBuildHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinHeight() {
|
||||
return height.getMinBuildHeight();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.dfsek.terra.bukkit.nms.v1_19_R2;
|
||||
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Holder.Reference;
|
||||
import net.minecraft.core.MappedRegistry;
|
||||
import net.minecraft.world.level.LevelAccessor;
|
||||
import net.minecraft.world.level.StructureManager;
|
||||
import xyz.jpenilla.reflectionremapper.ReflectionRemapper;
|
||||
import xyz.jpenilla.reflectionremapper.proxy.ReflectionProxyFactory;
|
||||
import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldGetter;
|
||||
import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldSetter;
|
||||
import xyz.jpenilla.reflectionremapper.proxy.annotation.MethodName;
|
||||
import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
public class Reflection {
|
||||
public static final MappedRegistryProxy MAPPED_REGISTRY;
|
||||
public static final StructureManagerProxy STRUCTURE_MANAGER;
|
||||
|
||||
public static final ReferenceProxy REFERENCE;
|
||||
|
||||
static {
|
||||
ReflectionRemapper reflectionRemapper = ReflectionRemapper.forReobfMappingsInPaperJar();
|
||||
ReflectionProxyFactory reflectionProxyFactory = ReflectionProxyFactory.create(reflectionRemapper,
|
||||
Reflection.class.getClassLoader());
|
||||
|
||||
MAPPED_REGISTRY = reflectionProxyFactory.reflectionProxy(MappedRegistryProxy.class);
|
||||
STRUCTURE_MANAGER = reflectionProxyFactory.reflectionProxy(StructureManagerProxy.class);
|
||||
REFERENCE = reflectionProxyFactory.reflectionProxy(ReferenceProxy.class);
|
||||
}
|
||||
|
||||
|
||||
@Proxies(MappedRegistry.class)
|
||||
public interface MappedRegistryProxy {
|
||||
@FieldSetter("frozen")
|
||||
void setFrozen(MappedRegistry<?> instance, boolean frozen);
|
||||
}
|
||||
|
||||
@Proxies(StructureManager.class)
|
||||
public interface StructureManagerProxy {
|
||||
@FieldGetter("level")
|
||||
LevelAccessor getLevel(StructureManager instance);
|
||||
}
|
||||
|
||||
@Proxies(Holder.Reference.class)
|
||||
public interface ReferenceProxy {
|
||||
@MethodName("bindValue")
|
||||
<T> void invokeBindValue(Reference<T> instance, T value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.dfsek.terra.bukkit.nms.v1_19_R2;
|
||||
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.dedicated.DedicatedServer;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureSet;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.CraftServer;
|
||||
|
||||
|
||||
public class RegistryFetcher {
|
||||
private static <T> Registry<T> getRegistry(ResourceKey<Registry<T>> key) {
|
||||
CraftServer craftserver = (CraftServer) Bukkit.getServer();
|
||||
DedicatedServer dedicatedserver = craftserver.getServer();
|
||||
return dedicatedserver
|
||||
.registryAccess()
|
||||
.registryOrThrow(key);
|
||||
}
|
||||
|
||||
public static Registry<Biome> biomeRegistry() {
|
||||
return getRegistry(Registries.BIOME);
|
||||
}
|
||||
}
|
||||
17
platforms/bukkit/nms/v1_19_R3/build.gradle.kts
Normal file
17
platforms/bukkit/nms/v1_19_R3/build.gradle.kts
Normal file
@@ -0,0 +1,17 @@
|
||||
apply(plugin = "io.papermc.paperweight.userdev")
|
||||
|
||||
repositories {
|
||||
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":platforms:bukkit:common"))
|
||||
paperDevBundle(Versions.Bukkit.paperDevBundle)
|
||||
implementation("xyz.jpenilla", "reflection-remapper", "0.1.0-SNAPSHOT")
|
||||
}
|
||||
|
||||
tasks {
|
||||
assemble {
|
||||
dependsOn("reobfJar")
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user