mirror of
https://github.com/PolyhedralDev/Terra.git
synced 2026-05-20 08:40:26 +00:00
Compare commits
164 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 772675639e | |||
| 13300861ee | |||
| 719b9a06f4 | |||
| f5b115e618 | |||
| e1e4a63517 | |||
| 0dc1492c4d | |||
| a184fe40d0 | |||
| f462b8198b | |||
| de3b213deb | |||
| be444f75b7 | |||
| d98238c262 | |||
| 8e96745a85 | |||
| 802bce40c8 | |||
| 76728fe593 | |||
| f3d1751c87 | |||
| 81e354f91c | |||
| aab28ff4f9 | |||
| 0e3a756011 | |||
| 02198e1b88 | |||
| 00aeb98419 | |||
| 1a784b51ac | |||
| 34c0895c1f | |||
| 379fa601a3 | |||
| fcbf51d80b | |||
| 9d83dfd164 | |||
| 72686601ee | |||
| 73baaec6cd | |||
| 0be7213ee5 | |||
| 3f3e2fe97c | |||
| 549edd11ea | |||
| 36f89946d4 | |||
| 18644d6100 | |||
| 9d38ee4329 | |||
| b75a8f85e4 | |||
| aad58f5968 | |||
| a548c30484 | |||
| 9ba46ae3a5 | |||
| 49efbed6f5 | |||
| 4001a56100 | |||
| f46f35d2ad | |||
| 70dbd2f2c0 | |||
| bf1be62d54 | |||
| a5cbce3667 | |||
| d0591f292e | |||
| 27874ce0a5 | |||
| 170687abdb | |||
| 46b61d841d | |||
| 41b7021121 | |||
| 183255140b | |||
| bea8f97179 | |||
| 60fec05e12 | |||
| e79cc21c11 | |||
| 5188477a6d | |||
| 58b743e6e8 | |||
| 9fa5307a60 | |||
| b4ea09929c | |||
| 09d847bc5a | |||
| dacddef5d6 | |||
| 3c593c7013 | |||
| 36d0ef77fb | |||
| 105be0c346 | |||
| 39d21fbe08 | |||
| 50fc589001 | |||
| ffa55cb7a3 | |||
| ff0985bd31 | |||
| 622fed96e5 | |||
| c219eff149 | |||
| 514e7065e2 | |||
| 0a16453f98 | |||
| ca2fe27fb3 | |||
| 6f2c01ceb3 | |||
| 8b74a5dee0 | |||
| 460a7651bc | |||
| b7a6b839e6 | |||
| 75bff93ecd | |||
| 57a45f08f0 | |||
| ba35b56016 | |||
| 8fd10956e4 | |||
| 9a5c1302ac | |||
| f918f1ef66 | |||
| 2afcee28a6 | |||
| 6efff02c19 | |||
| d3e0831d9e | |||
| 345012810a | |||
| 73e0899e7c | |||
| 8deae0480c | |||
| 7b87498751 | |||
| 33f1aa07d3 | |||
| 3ab671827d | |||
| 4d17edef80 | |||
| 9d5b33f130 | |||
| 97c0dcad9d | |||
| ef4fe4eb7a | |||
| 9514641e1e | |||
| e6c51bcfd0 | |||
| 084ecb9ad8 | |||
| 5cc58babca | |||
| b10130c5c6 | |||
| 5bc34eb626 | |||
| 7d74245109 | |||
| 11b6080413 | |||
| 4df23e464b | |||
| 7ea5747f8e | |||
| caad76f6dd | |||
| ba2f24f1f5 | |||
| 57bb6bca94 | |||
| 4bb09b126a | |||
| ae96d8f526 | |||
| 4a918d00a3 | |||
| cd65785de4 | |||
| 51cd4cd4b7 | |||
| e7efdd61a6 | |||
| 0006762ff3 | |||
| 4e4627d11d | |||
| 228b26f7c4 | |||
| ef846d53ad | |||
| f6f7529cb5 | |||
| 606315ea64 | |||
| 46f7c95314 | |||
| 4d826c880c | |||
| 6da3acc8a1 | |||
| 8fff27fddd | |||
| 75673b5b8e | |||
| 5ded3552d3 | |||
| ded308c01c | |||
| ee336b01a6 | |||
| 66465f27ff | |||
| 764a4fa535 | |||
| 5dd5c37055 | |||
| 2e0f892fff | |||
| cad0e4105c | |||
| b10898b837 | |||
| 6255ac7379 | |||
| c90ca076ab | |||
| 73af05bf09 | |||
| 84eab0de4a | |||
| 393a868e6a | |||
| 8955e4bb81 | |||
| bae2af80c8 | |||
| 6826f44770 | |||
| 1b5095dd36 | |||
| 95992cc49b | |||
| 7b0185ba7c | |||
| 6b7fb82202 | |||
| f246c8ada3 | |||
| a97273f358 | |||
| 878bede60b | |||
| b771e108b6 | |||
| 5c916f7758 | |||
| e9db14f52c | |||
| b3f072d689 | |||
| 4fdef98bd9 | |||
| 8670c4cdf3 | |||
| 64c2a41d19 | |||
| 4e225a6592 | |||
| c491ac5b24 | |||
| 68875cc17b | |||
| f1bf3990c1 | |||
| 4334b16ded | |||
| 274f864d6a | |||
| 329d94ba9c | |||
| 03ab463723 | |||
| 3580267532 | |||
| 358e09d05b |
+3
-3
@@ -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.4.0"))
|
||||
versionProjects(":common:implementation", version("6.4.0"))
|
||||
versionProjects(":platforms", version("6.4.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.83.1+1.20.1"
|
||||
}
|
||||
|
||||
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 fabricLoader = "0.14.2"
|
||||
const val minecraft = "1.20.1"
|
||||
const val yarn = "$minecraft+build.2"
|
||||
const val fabricLoader = "0.14.21"
|
||||
|
||||
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}-47.0.3"
|
||||
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.20.1"
|
||||
const val reflectionRemapper = "0.1.0-SNAPSHOT"
|
||||
const val paperDevBundle = "1.19.2-R0.1-SNAPSHOT"
|
||||
const val paperDevBundle = "1.20.1-R0.1-SNAPSHOT"
|
||||
}
|
||||
|
||||
object Sponge {
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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")
|
||||
}
|
||||
+51
@@ -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();
|
||||
}
|
||||
}
|
||||
+74
@@ -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();
|
||||
}
|
||||
}
|
||||
+40
@@ -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);
|
||||
}
|
||||
}
|
||||
+19
@@ -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;
|
||||
}
|
||||
}
|
||||
+37
@@ -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;
|
||||
}
|
||||
}
|
||||
+24
@@ -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.+"
|
||||
+6
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -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;
|
||||
|
||||
@@ -36,7 +37,7 @@ public class PipelineBiomeProvider implements BiomeProvider {
|
||||
this.noiseAmp = noiseAmp;
|
||||
this.chunkSize = pipeline.getChunkSize();
|
||||
this.biomeChunkCache = Caffeine.newBuilder()
|
||||
.maximumSize(1024)
|
||||
.maximumSize(64)
|
||||
.build(pipeline::generateChunk);
|
||||
|
||||
Set<PipelineBiome> biomeSet = new HashSet<>();
|
||||
@@ -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);
|
||||
|
||||
+1
-1
@@ -54,6 +54,6 @@ public class BiomePipelineTemplate implements ObjectTemplate<BiomeProvider> {
|
||||
|
||||
@Override
|
||||
public BiomeProvider get() {
|
||||
return new PipelineBiomeProvider(new PipelineImpl(source, stages, resolution, 500), resolution, blendSampler, blendAmplitude);
|
||||
return new PipelineBiomeProvider(new PipelineImpl(source, stages, resolution, 128), resolution, blendSampler, blendAmplitude);
|
||||
}
|
||||
}
|
||||
|
||||
+14
-6
@@ -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());
|
||||
}
|
||||
|
||||
+11
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+13
-26
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
-22
@@ -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 {
|
||||
}
|
||||
-42
@@ -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();
|
||||
}
|
||||
}
|
||||
+27
@@ -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);
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -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);
|
||||
|
||||
+8
-25
@@ -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)));
|
||||
}
|
||||
}
|
||||
+20
@@ -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 {
|
||||
}
|
||||
+46
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-45
@@ -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);
|
||||
}
|
||||
}
|
||||
-64
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -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();
|
||||
}
|
||||
}
|
||||
+24
@@ -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();
|
||||
}
|
||||
}
|
||||
+116
@@ -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");
|
||||
}
|
||||
};
|
||||
}
|
||||
+38
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
version = version("1.0.0")
|
||||
version = version("1.1.0")
|
||||
|
||||
dependencies {
|
||||
compileOnlyApi(project(":common:addons:manifest-addon-loader"))
|
||||
|
||||
+12
-3
@@ -21,8 +21,10 @@ import com.dfsek.terra.addons.noise.config.templates.FunctionTemplate;
|
||||
import com.dfsek.terra.addons.noise.config.templates.ImageSamplerTemplate;
|
||||
import com.dfsek.terra.addons.noise.config.templates.KernelTemplate;
|
||||
import com.dfsek.terra.addons.noise.config.templates.LinearHeightmapSamplerTemplate;
|
||||
import com.dfsek.terra.addons.noise.config.templates.TranslateSamplerTemplate;
|
||||
import com.dfsek.terra.addons.noise.config.templates.noise.CellularNoiseTemplate;
|
||||
import com.dfsek.terra.addons.noise.config.templates.noise.ConstantNoiseTemplate;
|
||||
import com.dfsek.terra.addons.noise.config.templates.noise.DistanceSamplerTemplate;
|
||||
import com.dfsek.terra.addons.noise.config.templates.noise.ExpressionFunctionTemplate;
|
||||
import com.dfsek.terra.addons.noise.config.templates.noise.GaborNoiseTemplate;
|
||||
import com.dfsek.terra.addons.noise.config.templates.noise.SimpleNoiseTemplate;
|
||||
@@ -30,6 +32,7 @@ import com.dfsek.terra.addons.noise.config.templates.noise.fractal.BrownianMotio
|
||||
import com.dfsek.terra.addons.noise.config.templates.noise.fractal.PingPongTemplate;
|
||||
import com.dfsek.terra.addons.noise.config.templates.noise.fractal.RidgedFractalTemplate;
|
||||
import com.dfsek.terra.addons.noise.config.templates.normalizer.ClampNormalizerTemplate;
|
||||
import com.dfsek.terra.addons.noise.config.templates.normalizer.ExpressionNormalizerTemplate;
|
||||
import com.dfsek.terra.addons.noise.config.templates.normalizer.LinearNormalizerTemplate;
|
||||
import com.dfsek.terra.addons.noise.config.templates.normalizer.NormalNormalizerTemplate;
|
||||
import com.dfsek.terra.addons.noise.config.templates.normalizer.PosterizationNormalizerTemplate;
|
||||
@@ -42,6 +45,7 @@ import com.dfsek.terra.addons.noise.samplers.arithmetic.MinSampler;
|
||||
import com.dfsek.terra.addons.noise.samplers.arithmetic.MultiplicationSampler;
|
||||
import com.dfsek.terra.addons.noise.samplers.arithmetic.SubtractionSampler;
|
||||
import com.dfsek.terra.addons.noise.samplers.noise.CellularSampler;
|
||||
import com.dfsek.terra.addons.noise.samplers.noise.DistanceSampler;
|
||||
import com.dfsek.terra.addons.noise.samplers.noise.random.GaussianNoiseSampler;
|
||||
import com.dfsek.terra.addons.noise.samplers.noise.random.PositiveWhiteNoiseSampler;
|
||||
import com.dfsek.terra.addons.noise.samplers.noise.random.WhiteNoiseSampler;
|
||||
@@ -83,6 +87,8 @@ public class NoiseAddon implements AddonInitializer {
|
||||
(type, o, loader, depthTracker) -> CellularSampler.DistanceFunction.valueOf((String) o))
|
||||
.applyLoader(CellularSampler.ReturnType.class,
|
||||
(type, o, loader, depthTracker) -> CellularSampler.ReturnType.valueOf((String) o))
|
||||
.applyLoader(DistanceSampler.DistanceFunction.class,
|
||||
(type, o, loader, depthTracker) -> DistanceSampler.DistanceFunction.valueOf((String) o))
|
||||
.applyLoader(DimensionApplicableNoiseSampler.class, DimensionApplicableNoiseSampler::new)
|
||||
.applyLoader(FunctionTemplate.class, FunctionTemplate::new);
|
||||
|
||||
@@ -92,9 +98,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);
|
||||
@@ -116,12 +122,15 @@ public class NoiseAddon implements AddonInitializer {
|
||||
noiseRegistry.register(addon.key("WHITE_NOISE"), () -> new SimpleNoiseTemplate(WhiteNoiseSampler::new));
|
||||
noiseRegistry.register(addon.key("POSITIVE_WHITE_NOISE"), () -> new SimpleNoiseTemplate(PositiveWhiteNoiseSampler::new));
|
||||
noiseRegistry.register(addon.key("GAUSSIAN"), () -> new SimpleNoiseTemplate(GaussianNoiseSampler::new));
|
||||
|
||||
noiseRegistry.register(addon.key("DISTANCE"), DistanceSamplerTemplate::new);
|
||||
|
||||
noiseRegistry.register(addon.key("CONSTANT"), ConstantNoiseTemplate::new);
|
||||
|
||||
noiseRegistry.register(addon.key("KERNEL"), KernelTemplate::new);
|
||||
|
||||
noiseRegistry.register(addon.key("LINEAR_HEIGHTMAP"), LinearHeightmapSamplerTemplate::new);
|
||||
noiseRegistry.register(addon.key("TRANSLATE"), TranslateSamplerTemplate::new);
|
||||
|
||||
noiseRegistry.register(addon.key("ADD"), () -> new BinaryArithmeticTemplate<>(AdditionSampler::new));
|
||||
noiseRegistry.register(addon.key("SUB"), () -> new BinaryArithmeticTemplate<>(SubtractionSampler::new));
|
||||
@@ -134,7 +143,7 @@ public class NoiseAddon implements AddonInitializer {
|
||||
Map<String, DimensionApplicableNoiseSampler> packSamplers = new LinkedHashMap<>();
|
||||
Map<String, FunctionTemplate> packFunctions = new LinkedHashMap<>();
|
||||
noiseRegistry.register(addon.key("EXPRESSION"), () -> new ExpressionFunctionTemplate(packSamplers, packFunctions));
|
||||
|
||||
noiseRegistry.register(addon.key("EXPRESSION_NORMALIZER"), () -> new ExpressionNormalizerTemplate(packSamplers, packFunctions));
|
||||
|
||||
NoiseConfigPackTemplate template = event.loadTemplate(new NoiseConfigPackTemplate());
|
||||
packSamplers.putAll(template.getSamplers());
|
||||
|
||||
+12
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -4,6 +4,7 @@ import com.dfsek.tectonic.api.config.template.annotations.Default;
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
|
||||
import com.dfsek.terra.addons.noise.samplers.LinearHeightmapSampler;
|
||||
import com.dfsek.terra.api.config.meta.Meta;
|
||||
import com.dfsek.terra.api.noise.NoiseSampler;
|
||||
|
||||
|
||||
@@ -11,14 +12,14 @@ import com.dfsek.terra.api.noise.NoiseSampler;
|
||||
public class LinearHeightmapSamplerTemplate extends SamplerTemplate<LinearHeightmapSampler> {
|
||||
@Value("sampler")
|
||||
@Default
|
||||
private NoiseSampler sampler = NoiseSampler.zero();
|
||||
private @Meta NoiseSampler sampler = NoiseSampler.zero();
|
||||
|
||||
@Value("base")
|
||||
private double base;
|
||||
private @Meta double base;
|
||||
|
||||
@Value("scale")
|
||||
@Default
|
||||
private double scale = 1;
|
||||
private @Meta double scale = 1;
|
||||
|
||||
@Override
|
||||
public NoiseSampler get() {
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.dfsek.terra.addons.noise.config.templates;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Default;
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
|
||||
import com.dfsek.terra.addons.noise.samplers.TranslateSampler;
|
||||
import com.dfsek.terra.api.config.meta.Meta;
|
||||
import com.dfsek.terra.api.noise.NoiseSampler;
|
||||
|
||||
|
||||
public class TranslateSamplerTemplate extends SamplerTemplate<TranslateSampler> {
|
||||
|
||||
@Value("sampler")
|
||||
private NoiseSampler sampler;
|
||||
|
||||
@Value("x")
|
||||
@Default
|
||||
private @Meta double x = 0;
|
||||
|
||||
@Value("y")
|
||||
@Default
|
||||
private @Meta double y = 0;
|
||||
|
||||
@Value("z")
|
||||
@Default
|
||||
private @Meta double z = 0;
|
||||
|
||||
@Override
|
||||
public NoiseSampler get() {
|
||||
return new TranslateSampler(sampler, x, y ,z);
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.dfsek.terra.addons.noise.config.templates.noise;
|
||||
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Default;
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
|
||||
import com.dfsek.terra.addons.noise.config.templates.SamplerTemplate;
|
||||
import com.dfsek.terra.addons.noise.samplers.noise.DistanceSampler;
|
||||
import com.dfsek.terra.addons.noise.samplers.noise.DistanceSampler.DistanceFunction;
|
||||
import com.dfsek.terra.api.config.meta.Meta;
|
||||
|
||||
|
||||
public class DistanceSamplerTemplate extends SamplerTemplate<DistanceSampler> {
|
||||
|
||||
@Value("distance-function")
|
||||
@Default
|
||||
private DistanceSampler.@Meta DistanceFunction distanceFunction = DistanceFunction.Euclidean;
|
||||
|
||||
@Value("point.x")
|
||||
@Default
|
||||
private @Meta double x = 0;
|
||||
|
||||
@Value("point.y")
|
||||
@Default
|
||||
private @Meta double y = 0;
|
||||
|
||||
@Value("point.z")
|
||||
@Default
|
||||
private @Meta double z = 0;
|
||||
|
||||
@Value("normalize")
|
||||
@Default
|
||||
private @Meta boolean normalize = false;
|
||||
|
||||
@Value("radius")
|
||||
@Default
|
||||
private @Meta double normalizeRadius = 100;
|
||||
|
||||
@Override
|
||||
public DistanceSampler get() {
|
||||
return new DistanceSampler(distanceFunction, x, y, z, normalize, normalizeRadius);
|
||||
}
|
||||
}
|
||||
+9
-36
@@ -8,7 +8,6 @@
|
||||
package com.dfsek.terra.addons.noise.config.templates.noise;
|
||||
|
||||
import com.dfsek.paralithic.eval.tokenizer.ParseException;
|
||||
import com.dfsek.paralithic.functions.Function;
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Default;
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
|
||||
@@ -19,17 +18,16 @@ import java.util.Map;
|
||||
import com.dfsek.terra.addons.noise.config.DimensionApplicableNoiseSampler;
|
||||
import com.dfsek.terra.addons.noise.config.templates.FunctionTemplate;
|
||||
import com.dfsek.terra.addons.noise.config.templates.SamplerTemplate;
|
||||
import com.dfsek.terra.addons.noise.paralithic.defined.UserDefinedFunction;
|
||||
import com.dfsek.terra.addons.noise.paralithic.noise.NoiseFunction2;
|
||||
import com.dfsek.terra.addons.noise.paralithic.noise.NoiseFunction3;
|
||||
import com.dfsek.terra.addons.noise.samplers.noise.ExpressionFunction;
|
||||
import com.dfsek.terra.api.config.meta.Meta;
|
||||
import com.dfsek.terra.api.noise.NoiseSampler;
|
||||
|
||||
import static com.dfsek.terra.addons.noise.paralithic.FunctionUtil.convertFunctionsAndSamplers;
|
||||
|
||||
|
||||
@SuppressWarnings({ "FieldMayBeFinal", "unused" })
|
||||
public class ExpressionFunctionTemplate extends SamplerTemplate<ExpressionFunction> {
|
||||
private final Map<String, DimensionApplicableNoiseSampler> otherFunctions;
|
||||
private final Map<String, DimensionApplicableNoiseSampler> globalSamplers;
|
||||
private final Map<String, FunctionTemplate> globalFunctions;
|
||||
@Value("variables")
|
||||
@Default
|
||||
@@ -43,44 +41,19 @@ public class ExpressionFunctionTemplate extends SamplerTemplate<ExpressionFuncti
|
||||
@Default
|
||||
private @Meta LinkedHashMap<String, @Meta FunctionTemplate> functions = new LinkedHashMap<>();
|
||||
|
||||
public ExpressionFunctionTemplate(Map<String, DimensionApplicableNoiseSampler> otherFunctions, Map<String, FunctionTemplate> samplers) {
|
||||
this.otherFunctions = otherFunctions;
|
||||
this.globalFunctions = samplers;
|
||||
public ExpressionFunctionTemplate(Map<String, DimensionApplicableNoiseSampler> globalSamplers, Map<String, FunctionTemplate> globalFunctions) {
|
||||
this.globalSamplers = globalSamplers;
|
||||
this.globalFunctions = globalFunctions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NoiseSampler get() {
|
||||
var mergedFunctions = new HashMap<>(globalFunctions); mergedFunctions.putAll(functions);
|
||||
var mergedSamplers = new HashMap<>(globalSamplers); mergedSamplers.putAll(samplers);
|
||||
try {
|
||||
Map<String, Function> noiseFunctionMap = generateFunctions();
|
||||
return new ExpressionFunction(noiseFunctionMap, expression, vars);
|
||||
return new ExpressionFunction(convertFunctionsAndSamplers(mergedFunctions, mergedSamplers), expression, vars);
|
||||
} catch(ParseException e) {
|
||||
throw new RuntimeException("Failed to parse expression.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Function> generateFunctions() throws ParseException {
|
||||
Map<String, Function> noiseFunctionMap = new HashMap<>();
|
||||
|
||||
for(Map.Entry<String, FunctionTemplate> entry : globalFunctions.entrySet()) {
|
||||
noiseFunctionMap.put(entry.getKey(), UserDefinedFunction.newInstance(entry.getValue()));
|
||||
}
|
||||
|
||||
for(Map.Entry<String, FunctionTemplate> entry : functions.entrySet()) {
|
||||
noiseFunctionMap.put(entry.getKey(), UserDefinedFunction.newInstance(entry.getValue()));
|
||||
}
|
||||
|
||||
otherFunctions.forEach((id, function) -> {
|
||||
if(function.getDimensions() == 2) {
|
||||
noiseFunctionMap.put(id, new NoiseFunction2(function.getSampler()));
|
||||
} else noiseFunctionMap.put(id, new NoiseFunction3(function.getSampler()));
|
||||
});
|
||||
|
||||
samplers.forEach((id, function) -> {
|
||||
if(function.getDimensions() == 2) {
|
||||
noiseFunctionMap.put(id, new NoiseFunction2(function.getSampler()));
|
||||
} else noiseFunctionMap.put(id, new NoiseFunction3(function.getSampler()));
|
||||
});
|
||||
|
||||
return noiseFunctionMap;
|
||||
}
|
||||
}
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.noise.config.templates.normalizer;
|
||||
|
||||
import com.dfsek.paralithic.eval.tokenizer.ParseException;
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Default;
|
||||
import com.dfsek.tectonic.api.config.template.annotations.Value;
|
||||
|
||||
import com.dfsek.terra.addons.noise.config.DimensionApplicableNoiseSampler;
|
||||
import com.dfsek.terra.addons.noise.config.templates.FunctionTemplate;
|
||||
import com.dfsek.terra.addons.noise.normalizer.ExpressionNormalizer;
|
||||
import com.dfsek.terra.api.config.meta.Meta;
|
||||
import com.dfsek.terra.api.noise.NoiseSampler;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.dfsek.terra.addons.noise.paralithic.FunctionUtil.convertFunctionsAndSamplers;
|
||||
|
||||
|
||||
@SuppressWarnings({ "unused", "FieldMayBeFinal" })
|
||||
public class ExpressionNormalizerTemplate extends NormalizerTemplate<ExpressionNormalizer> {
|
||||
|
||||
private final Map<String, DimensionApplicableNoiseSampler> globalSamplers;
|
||||
private final Map<String, FunctionTemplate> globalFunctions;
|
||||
|
||||
@Value("expression")
|
||||
private @Meta String expression;
|
||||
|
||||
@Value("variables")
|
||||
@Default
|
||||
private @Meta Map<String, @Meta Double> vars = new HashMap<>();
|
||||
|
||||
@Value("samplers")
|
||||
@Default
|
||||
private @Meta LinkedHashMap<String, @Meta DimensionApplicableNoiseSampler> samplers = new LinkedHashMap<>();
|
||||
|
||||
@Value("functions")
|
||||
@Default
|
||||
private @Meta LinkedHashMap<String, @Meta FunctionTemplate> functions = new LinkedHashMap<>();
|
||||
|
||||
public ExpressionNormalizerTemplate(Map<String, DimensionApplicableNoiseSampler> globalSamplers, Map<String, FunctionTemplate> globalFunctions) {
|
||||
this.globalSamplers = globalSamplers;
|
||||
this.globalFunctions = globalFunctions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NoiseSampler get() {
|
||||
var mergedFunctions = new HashMap<>(globalFunctions); mergedFunctions.putAll(functions);
|
||||
var mergedSamplers = new HashMap<>(globalSamplers); mergedSamplers.putAll(samplers);
|
||||
try {
|
||||
return new ExpressionNormalizer(function, convertFunctionsAndSamplers(mergedFunctions, mergedSamplers), expression, vars);
|
||||
} catch(ParseException e) {
|
||||
throw new RuntimeException("Failed to parse expression.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.dfsek.terra.addons.noise.normalizer;
|
||||
|
||||
import com.dfsek.paralithic.Expression;
|
||||
import com.dfsek.paralithic.eval.parser.Parser;
|
||||
import com.dfsek.paralithic.eval.parser.Scope;
|
||||
import com.dfsek.paralithic.eval.tokenizer.ParseException;
|
||||
import com.dfsek.paralithic.functions.Function;
|
||||
|
||||
import com.dfsek.terra.api.noise.NoiseSampler;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class ExpressionNormalizer extends Normalizer {
|
||||
|
||||
private final Expression expression;
|
||||
|
||||
public ExpressionNormalizer(NoiseSampler sampler, Map<String, Function> functions, String eq, Map<String, Double> vars)
|
||||
throws ParseException {
|
||||
super(sampler);
|
||||
Parser p = new Parser();
|
||||
Scope scope = new Scope();
|
||||
scope.addInvocationVariable("in");
|
||||
vars.forEach(scope::create);
|
||||
functions.forEach(p::registerFunction);
|
||||
expression = p.parse(eq, scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double normalize(double in) {
|
||||
return expression.evaluate(in);
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.dfsek.terra.addons.noise.paralithic;
|
||||
|
||||
import com.dfsek.paralithic.eval.tokenizer.ParseException;
|
||||
import com.dfsek.paralithic.functions.Function;
|
||||
|
||||
import com.dfsek.terra.addons.noise.config.DimensionApplicableNoiseSampler;
|
||||
import com.dfsek.terra.addons.noise.config.templates.FunctionTemplate;
|
||||
import com.dfsek.terra.addons.noise.paralithic.defined.UserDefinedFunction;
|
||||
import com.dfsek.terra.addons.noise.paralithic.noise.NoiseFunction2;
|
||||
import com.dfsek.terra.addons.noise.paralithic.noise.NoiseFunction3;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class FunctionUtil {
|
||||
private FunctionUtil() {}
|
||||
|
||||
public static Map<String, Function> convertFunctionsAndSamplers(Map<String, FunctionTemplate> functions,
|
||||
Map<String, DimensionApplicableNoiseSampler> samplers) throws ParseException {
|
||||
Map<String, Function> functionMap = new HashMap<>();
|
||||
for(Map.Entry<String, FunctionTemplate> entry : functions.entrySet()) {
|
||||
functionMap.put(entry.getKey(), UserDefinedFunction.newInstance(entry.getValue()));
|
||||
}
|
||||
samplers.forEach((id, sampler) -> functionMap.put(id,
|
||||
sampler.getDimensions() == 2 ?
|
||||
new NoiseFunction2(sampler.getSampler()) :
|
||||
new NoiseFunction3(sampler.getSampler())));
|
||||
return functionMap;
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.dfsek.terra.addons.noise.samplers;
|
||||
|
||||
import com.dfsek.terra.api.noise.NoiseSampler;
|
||||
|
||||
|
||||
public class TranslateSampler implements NoiseSampler {
|
||||
|
||||
private final NoiseSampler sampler;
|
||||
private final double dx, dy, dz;
|
||||
|
||||
public TranslateSampler(NoiseSampler sampler, double dx, double dy, double dz) {
|
||||
this.sampler = sampler;
|
||||
this.dx = dx;
|
||||
this.dy = dy;
|
||||
this.dz = dz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(long seed, double x, double y) {
|
||||
return sampler.noise(seed, x - dx, y - dz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(long seed, double x, double y, double z) {
|
||||
return sampler.noise(seed, x - dx, y - dy, z - dz);
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package com.dfsek.terra.addons.noise.samplers.noise;
|
||||
|
||||
|
||||
public class DistanceSampler extends NoiseFunction {
|
||||
|
||||
private final DistanceFunction distanceFunction;
|
||||
private final double ox, oy, oz;
|
||||
private final boolean normalize;
|
||||
private final double radius;
|
||||
|
||||
private final double distanceAtRadius;
|
||||
|
||||
public DistanceSampler(DistanceFunction distanceFunction, double ox, double oy, double oz, boolean normalize, double radius) {
|
||||
frequency = 1;
|
||||
this.distanceFunction = distanceFunction;
|
||||
this.ox = ox;
|
||||
this.oy = oy;
|
||||
this.oz = oz;
|
||||
this.normalize = normalize;
|
||||
this.radius = radius;
|
||||
this.distanceAtRadius = distance2d(distanceFunction, radius, 0); // distance2d and distance3d should return the same value
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getNoiseRaw(long seed, double x, double y) {
|
||||
double dx = x - ox;
|
||||
double dy = y - oz;
|
||||
if (normalize && (fastAbs(dx) > radius || fastAbs(dy) > radius)) return 1;
|
||||
double dist = distance2d(distanceFunction, dx, dy);
|
||||
if (normalize) return fastMin(((2*dist)/distanceAtRadius)-1, 1);
|
||||
return dist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getNoiseRaw(long seed, double x, double y, double z) {
|
||||
double dx = x - ox;
|
||||
double dy = y - oy;
|
||||
double dz = z - oz;
|
||||
if(normalize && (fastAbs(dx) > radius || fastAbs(dy) > radius || fastAbs(dz) > radius)) return 1;
|
||||
double dist = distance3d(distanceFunction, dx, dy, dz);
|
||||
if (normalize) return fastMin(((2*dist)/distanceAtRadius)-1, 1);
|
||||
return dist;
|
||||
}
|
||||
|
||||
private static double distance2d(DistanceFunction distanceFunction, double x, double z) {
|
||||
return switch(distanceFunction) {
|
||||
case Euclidean -> fastSqrt(x*x + z*z);
|
||||
case EuclideanSq -> x*x + z*z;
|
||||
case Manhattan -> fastAbs(x) + fastAbs(z);
|
||||
};
|
||||
}
|
||||
|
||||
private static double distance3d(DistanceFunction distanceFunction, double x, double y, double z) {
|
||||
return switch(distanceFunction) {
|
||||
case Euclidean -> fastSqrt(x*x + y*y + z*z);
|
||||
case EuclideanSq -> x*x + y*y + z*z;
|
||||
case Manhattan -> fastAbs(x) + fastAbs(y) + fastAbs(z);
|
||||
};
|
||||
}
|
||||
|
||||
public enum DistanceFunction {
|
||||
Euclidean,
|
||||
EuclideanSq,
|
||||
Manhattan
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
+84
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
+12
@@ -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);
|
||||
}
|
||||
+29
@@ -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);
|
||||
}
|
||||
}
|
||||
+27
@@ -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()));
|
||||
}
|
||||
}
|
||||
+30
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
+11
@@ -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);
|
||||
}
|
||||
+61
@@ -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,
|
||||
}
|
||||
}
|
||||
+21
@@ -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);
|
||||
}
|
||||
}
|
||||
+92
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -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());
|
||||
}
|
||||
}
|
||||
+21
@@ -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;
|
||||
|
||||
}
|
||||
+17
@@ -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);
|
||||
}
|
||||
}
|
||||
+13
@@ -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);
|
||||
}
|
||||
}
|
||||
+13
@@ -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;
|
||||
}
|
||||
+18
@@ -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);
|
||||
}
|
||||
}
|
||||
+21
@@ -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);
|
||||
}
|
||||
}
|
||||
+16
@@ -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());
|
||||
}
|
||||
}
|
||||
+9
@@ -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>> {
|
||||
}
|
||||
+20
@@ -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());
|
||||
}
|
||||
}
|
||||
+46
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+76
@@ -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;
|
||||
}
|
||||
}
|
||||
+41
@@ -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);
|
||||
}
|
||||
}
|
||||
+74
@@ -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);
|
||||
}
|
||||
}
|
||||
+40
@@ -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();
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.dfsek.terra.addons.image.converter;
|
||||
|
||||
public interface ColorConverter<T> {
|
||||
|
||||
T apply(int color);
|
||||
|
||||
Iterable<T> getEntries();
|
||||
}
|
||||
+42
@@ -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;
|
||||
}
|
||||
}
|
||||
+38
@@ -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())));
|
||||
}
|
||||
}
|
||||
+8
@@ -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>> {
|
||||
}
|
||||
+28
@@ -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();
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.dfsek.terra.addons.image.image;
|
||||
|
||||
public interface Image {
|
||||
int getRGB(int x, int y);
|
||||
|
||||
int getWidth();
|
||||
|
||||
int getHeight();
|
||||
}
|
||||
+74
@@ -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;
|
||||
}
|
||||
}
|
||||
+40
@@ -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);
|
||||
}
|
||||
}
|
||||
+242
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+322
@@ -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);
|
||||
}
|
||||
}
|
||||
+25
@@ -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
|
||||
));
|
||||
}
|
||||
}
|
||||
+9
@@ -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
|
||||
@@ -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.
|
||||
@@ -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"))
|
||||
}
|
||||
+29
@@ -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();
|
||||
}
|
||||
}
|
||||
+68
@@ -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();
|
||||
}
|
||||
}
|
||||
+25
@@ -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);
|
||||
}
|
||||
}
|
||||
+19
@@ -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;
|
||||
}
|
||||
}
|
||||
+37
@@ -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;
|
||||
}
|
||||
}
|
||||
+24
@@ -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.+"
|
||||
+29
-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);
|
||||
}
|
||||
|
||||
+10
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-14
@@ -5,18 +5,19 @@
|
||||
* reference the LICENSE file in this module's root directory.
|
||||
*/
|
||||
|
||||
package com.dfsek.terra.addons.terrascript.tokenizer;
|
||||
package com.dfsek.terra.addons.terrascript.lexer;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
public class Char {
|
||||
private final char character;
|
||||
private final int index;
|
||||
private final int line;
|
||||
private final SourcePosition position;
|
||||
|
||||
|
||||
public Char(char character, int index, int line) {
|
||||
public Char(char character, SourcePosition position) {
|
||||
this.character = character;
|
||||
this.index = index;
|
||||
this.line = line;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public boolean is(char... tests) {
|
||||
@@ -33,18 +34,23 @@ public class Char {
|
||||
return Character.toString(character);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if(this == o) return true;
|
||||
if(o == null || getClass() != o.getClass()) return false;
|
||||
Char other = (Char) o;
|
||||
return character == other.character && Objects.equals(position, other.position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(character, position);
|
||||
}
|
||||
|
||||
public char getCharacter() {
|
||||
return character;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
public boolean isWhitespace() {
|
||||
return Character.isWhitespace(character);
|
||||
}
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* 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.terrascript.lexer;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
|
||||
import com.dfsek.terra.addons.terrascript.lexer.Token.TokenType;
|
||||
import com.dfsek.terra.addons.terrascript.lexer.exceptions.EOFException;
|
||||
import com.dfsek.terra.addons.terrascript.lexer.exceptions.FormatException;
|
||||
import com.dfsek.terra.addons.terrascript.lexer.exceptions.TokenizerException;
|
||||
import com.dfsek.terra.addons.terrascript.parser.exceptions.ParseException;
|
||||
|
||||
|
||||
public class Lexer {
|
||||
public static final Set<Character> syntaxSignificant = Sets.newHashSet(';', '(', ')', '"', ',', '\\', '=', '{', '}', '+', '-', '*', '/',
|
||||
'>', '<', '!'); // Reserved chars
|
||||
private final LookaheadStream reader;
|
||||
private final Stack<Token> bracketStack = new Stack<>();
|
||||
private Token current;
|
||||
|
||||
public Lexer(String data) {
|
||||
reader = new LookaheadStream(data + '\0');
|
||||
current = tokenize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first token.
|
||||
*
|
||||
* @return First token
|
||||
*
|
||||
* @throws ParseException If token does not exist
|
||||
*/
|
||||
public Token current() {
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume (get and remove) the first token.
|
||||
*
|
||||
* @return First token
|
||||
*
|
||||
* @throws ParseException If token does not exist
|
||||
*/
|
||||
public Token consume(String wrongTypeMessage, TokenType expected, TokenType... more) {
|
||||
if(!current.isType(expected) && Arrays.stream(more).noneMatch(t -> t == current.getType())) throw new ParseException(wrongTypeMessage, current.getPosition());
|
||||
return consumeUnchecked();
|
||||
}
|
||||
|
||||
public Token consumeUnchecked() {
|
||||
if(current.getType() == TokenType.END_OF_FILE) return current;
|
||||
Token temp = current;
|
||||
current = tokenize();
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this {@code Tokenizer} contains additional tokens.
|
||||
*
|
||||
* @return {@code true} if more tokens are present, otherwise {@code false}
|
||||
*/
|
||||
public boolean hasNext() {
|
||||
return current.getType() != TokenType.END_OF_FILE;
|
||||
}
|
||||
|
||||
private Token tokenize() throws TokenizerException {
|
||||
consumeWhitespace();
|
||||
SourcePosition position = reader.getPosition();
|
||||
|
||||
// Skip line if comment
|
||||
while(reader.matchesString("//", true)) skipLine();
|
||||
|
||||
// Skip multi line comment
|
||||
if(reader.matchesString("/*", true)) skipTo("*/");
|
||||
|
||||
// Reached end of file
|
||||
if(reader.current().isEOF()) {
|
||||
if(!bracketStack.isEmpty()) throw new ParseException("Dangling open brace", bracketStack.peek().getPosition());
|
||||
return new Token(reader.consume().toString(), TokenType.END_OF_FILE, position);
|
||||
}
|
||||
|
||||
// Check if operator token
|
||||
if(reader.matchesString("==", true))
|
||||
return new Token("==", TokenType.EQUALS_EQUALS, position);
|
||||
if(reader.matchesString("!=", true))
|
||||
return new Token("!=", TokenType.BANG_EQUALS, position);
|
||||
if(reader.matchesString(">=", true))
|
||||
return new Token(">=", TokenType.GREATER_EQUAL, position);
|
||||
if(reader.matchesString("<=", true))
|
||||
return new Token("<=", TokenType.LESS_EQUALS, position);
|
||||
if(reader.matchesString(">", true))
|
||||
return new Token(">", TokenType.GREATER, position);
|
||||
if(reader.matchesString("<", true))
|
||||
return new Token("<", TokenType.LESS, position);
|
||||
|
||||
// Check if logical operator
|
||||
if(reader.matchesString("||", true))
|
||||
return new Token("||", TokenType.BOOLEAN_OR, position);
|
||||
if(reader.matchesString("&&", true))
|
||||
return new Token("&&", TokenType.BOOLEAN_AND, position);
|
||||
|
||||
// Check if number
|
||||
if(isNumberStart()) {
|
||||
StringBuilder num = new StringBuilder();
|
||||
while(!reader.current().isEOF() && isNumberLike()) {
|
||||
num.append(reader.consume().getCharacter());
|
||||
}
|
||||
return new Token(num.toString(), TokenType.NUMBER, position);
|
||||
}
|
||||
|
||||
// Check if string literal
|
||||
if(reader.current().is('"')) {
|
||||
reader.consume(); // Consume first quote
|
||||
StringBuilder string = new StringBuilder();
|
||||
boolean ignoreNext = false;
|
||||
while((!reader.current().is('"')) || ignoreNext) {
|
||||
if(reader.current().is('\\') && !ignoreNext) {
|
||||
ignoreNext = true;
|
||||
reader.consume();
|
||||
continue;
|
||||
} else ignoreNext = false;
|
||||
if(reader.current().isEOF())
|
||||
throw new FormatException("No end of string literal found. ", position);
|
||||
string.append(reader.consume());
|
||||
}
|
||||
reader.consume(); // Consume last quote
|
||||
|
||||
return new Token(string.toString(), TokenType.STRING, position);
|
||||
}
|
||||
|
||||
if(reader.current().is('('))
|
||||
return new Token(reader.consume().toString(), TokenType.OPEN_PAREN, position);
|
||||
if(reader.current().is(')'))
|
||||
return new Token(reader.consume().toString(), TokenType.CLOSE_PAREN, position);
|
||||
if(reader.current().is(';'))
|
||||
return new Token(reader.consume().toString(), TokenType.STATEMENT_END, position);
|
||||
if(reader.current().is(','))
|
||||
return new Token(reader.consume().toString(), TokenType.SEPARATOR, position);
|
||||
|
||||
if(reader.current().is('{')) {
|
||||
Token token = new Token(reader.consume().toString(), TokenType.BLOCK_BEGIN, position);
|
||||
bracketStack.push(token);
|
||||
return token;
|
||||
}
|
||||
if(reader.current().is('}')) {
|
||||
if(bracketStack.isEmpty()) throw new ParseException("Dangling close brace", position);
|
||||
bracketStack.pop();
|
||||
return new Token(reader.consume().toString(), TokenType.BLOCK_END, position);
|
||||
}
|
||||
|
||||
if(reader.current().is('='))
|
||||
return new Token(reader.consume().toString(), TokenType.ASSIGNMENT, position);
|
||||
if(reader.current().is('+'))
|
||||
return new Token(reader.consume().toString(), TokenType.PLUS, position);
|
||||
if(reader.current().is('-'))
|
||||
return new Token(reader.consume().toString(), TokenType.MINUS,
|
||||
position);
|
||||
if(reader.current().is('*'))
|
||||
return new Token(reader.consume().toString(), TokenType.STAR,
|
||||
position);
|
||||
if(reader.current().is('/'))
|
||||
return new Token(reader.consume().toString(), TokenType.FORWARD_SLASH, position);
|
||||
if(reader.current().is('%'))
|
||||
return new Token(reader.consume().toString(), TokenType.MODULO_OPERATOR, position);
|
||||
if(reader.current().is('!'))
|
||||
return new Token(reader.consume().toString(), TokenType.BANG, position);
|
||||
|
||||
// Read word
|
||||
StringBuilder token = new StringBuilder();
|
||||
while(!reader.current().isEOF() && !isSyntaxSignificant(reader.current().getCharacter())) {
|
||||
Char c = reader.consume();
|
||||
if(c.isWhitespace()) break;
|
||||
token.append(c.getCharacter());
|
||||
}
|
||||
String tokenString = token.toString();
|
||||
|
||||
// Check if word is a keyword
|
||||
if(tokenString.equals("true"))
|
||||
return new Token(tokenString, TokenType.BOOLEAN, position);
|
||||
if(tokenString.equals("false"))
|
||||
return new Token(tokenString, TokenType.BOOLEAN, position);
|
||||
|
||||
if(tokenString.equals("num"))
|
||||
return new Token(tokenString, TokenType.TYPE_NUMBER, position);
|
||||
if(tokenString.equals("str"))
|
||||
return new Token(tokenString, TokenType.TYPE_STRING, position);
|
||||
if(tokenString.equals("bool"))
|
||||
return new Token(tokenString, TokenType.TYPE_BOOLEAN, position);
|
||||
if(tokenString.equals("void"))
|
||||
return new Token(tokenString, TokenType.TYPE_VOID, position);
|
||||
|
||||
if(tokenString.equals("if"))
|
||||
return new Token(tokenString, TokenType.IF_STATEMENT, position);
|
||||
if(tokenString.equals("else"))
|
||||
return new Token(tokenString, TokenType.ELSE, position);
|
||||
if(tokenString.equals("while"))
|
||||
return new Token(tokenString, TokenType.WHILE_LOOP, position);
|
||||
if(tokenString.equals("for"))
|
||||
return new Token(tokenString, TokenType.FOR_LOOP, position);
|
||||
|
||||
if(tokenString.equals("return"))
|
||||
return new Token(tokenString, TokenType.RETURN, position);
|
||||
if(tokenString.equals("continue"))
|
||||
return new Token(tokenString, TokenType.CONTINUE, position);
|
||||
if(tokenString.equals("break"))
|
||||
return new Token(tokenString, TokenType.BREAK, position);
|
||||
if(tokenString.equals("fail"))
|
||||
return new Token(tokenString, TokenType.FAIL, position);
|
||||
|
||||
// If not keyword, assume it is an identifier
|
||||
return new Token(tokenString, TokenType.IDENTIFIER, position);
|
||||
}
|
||||
|
||||
private void skipLine() {
|
||||
while(!reader.current().isEOF() && !reader.current().isNewLine()) reader.consume();
|
||||
consumeWhitespace();
|
||||
}
|
||||
|
||||
private void consumeWhitespace() {
|
||||
while(!reader.current().isEOF() && reader.current().isWhitespace()) reader.consume(); // Consume whitespace.
|
||||
}
|
||||
|
||||
private void skipTo(String s) throws EOFException {
|
||||
SourcePosition begin = reader.getPosition();
|
||||
while(!reader.current().isEOF()) {
|
||||
if(reader.matchesString(s, true)) {
|
||||
consumeWhitespace();
|
||||
return;
|
||||
}
|
||||
reader.consume();
|
||||
}
|
||||
throw new EOFException("No end of expression found.", begin);
|
||||
}
|
||||
|
||||
private boolean isNumberLike() {
|
||||
return reader.current().isDigit()
|
||||
|| reader.current().is('_', '.', 'E');
|
||||
}
|
||||
|
||||
private boolean isNumberStart() {
|
||||
return reader.current().isDigit()
|
||||
|| reader.current().is('.') && reader.peek().isDigit();
|
||||
}
|
||||
|
||||
public boolean isSyntaxSignificant(char c) {
|
||||
return syntaxSignificant.contains(c);
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package com.dfsek.terra.addons.terrascript.lexer;
|
||||
|
||||
|
||||
|
||||
public class LookaheadStream {
|
||||
|
||||
private final String source;
|
||||
|
||||
private int index;
|
||||
|
||||
private SourcePosition position = new SourcePosition(1, 1);
|
||||
|
||||
public LookaheadStream(String source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current character without consuming it.
|
||||
*
|
||||
* @return current character
|
||||
*/
|
||||
public Char current() {
|
||||
return new Char(source.charAt(index), position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume and return one character.
|
||||
*
|
||||
* @return Character that was consumed.
|
||||
*/
|
||||
public Char consume() {
|
||||
Char consumed = current();
|
||||
incrementIndex(1);
|
||||
return consumed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The next character in sequence.
|
||||
*/
|
||||
public Char peek() {
|
||||
int index = this.index + 1;
|
||||
if (index + 1 >= source.length()) return null;
|
||||
return new Char(source.charAt(index), getPositionAfter(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the contained sequence of characters matches the string
|
||||
*
|
||||
* @param check Input string to check against
|
||||
* @param consumeIfMatched Whether to consume the string if there is a match
|
||||
* @return If the string matches
|
||||
*/
|
||||
public boolean matchesString(String check, boolean consumeIfMatched) {
|
||||
boolean matches = check.equals(source.substring(index, Math.min(index + check.length(), source.length())));
|
||||
if (matches && consumeIfMatched) incrementIndex(check.length());
|
||||
return matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Current position within the source file
|
||||
*/
|
||||
public SourcePosition getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
private void incrementIndex(int amount) {
|
||||
position = getPositionAfter(amount);
|
||||
index = Math.min(index + amount, source.length() - 1);
|
||||
}
|
||||
|
||||
private SourcePosition getPositionAfter(int chars) {
|
||||
if (chars < 0) throw new IllegalArgumentException("Negative values are not allowed");
|
||||
int line = position.line();
|
||||
int column = position.column();
|
||||
for (int i = index; i < Math.min(index + chars, source.length() - 1); i++) {
|
||||
if (source.charAt(i) == '\n') {
|
||||
line++;
|
||||
column = 0;
|
||||
}
|
||||
column++;
|
||||
}
|
||||
return new SourcePosition(line, column);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.terrascript.lexer;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
public record SourcePosition(int line, int column) {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "line " + line + ", column " + column;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if(this == o) return true;
|
||||
if(o == null || getClass() != o.getClass()) return false;
|
||||
SourcePosition that = (SourcePosition) o;
|
||||
return line == that.line && column == that.column;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(line, column);
|
||||
}
|
||||
}
|
||||
+72
-59
@@ -5,14 +5,14 @@
|
||||
* reference the LICENSE file in this module's root directory.
|
||||
*/
|
||||
|
||||
package com.dfsek.terra.addons.terrascript.tokenizer;
|
||||
package com.dfsek.terra.addons.terrascript.lexer;
|
||||
|
||||
public class Token {
|
||||
private final String content;
|
||||
private final Type type;
|
||||
private final Position start;
|
||||
private final TokenType type;
|
||||
private final SourcePosition start;
|
||||
|
||||
public Token(String content, Type type, Position start) {
|
||||
public Token(String content, TokenType type, SourcePosition start) {
|
||||
this.content = content;
|
||||
this.type = type;
|
||||
this.start = start;
|
||||
@@ -23,7 +23,7 @@ public class Token {
|
||||
return type + ": '" + content + "'";
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
public TokenType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -31,63 +31,68 @@ public class Token {
|
||||
return content;
|
||||
}
|
||||
|
||||
public Position getPosition() {
|
||||
public SourcePosition getPosition() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public boolean isConstant() {
|
||||
return this.type.equals(Type.NUMBER) || this.type.equals(Type.STRING) || this.type.equals(Type.BOOLEAN);
|
||||
return this.type.equals(TokenType.NUMBER) || this.type.equals(TokenType.STRING) || this.type.equals(TokenType.BOOLEAN);
|
||||
}
|
||||
|
||||
public boolean isType(TokenType type) {
|
||||
return type == getType();
|
||||
}
|
||||
|
||||
public boolean isType(TokenType... types) {
|
||||
for (TokenType t : types) if (isType(t)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isBinaryOperator() {
|
||||
return type.equals(Type.ADDITION_OPERATOR)
|
||||
|| type.equals(Type.SUBTRACTION_OPERATOR)
|
||||
|| type.equals(Type.MULTIPLICATION_OPERATOR)
|
||||
|| type.equals(Type.DIVISION_OPERATOR)
|
||||
|| type.equals(Type.EQUALS_OPERATOR)
|
||||
|| type.equals(Type.NOT_EQUALS_OPERATOR)
|
||||
|| type.equals(Type.LESS_THAN_OPERATOR)
|
||||
|| type.equals(Type.GREATER_THAN_OPERATOR)
|
||||
|| type.equals(Type.LESS_THAN_OR_EQUALS_OPERATOR)
|
||||
|| type.equals(Type.GREATER_THAN_OR_EQUALS_OPERATOR)
|
||||
|| type.equals(Type.BOOLEAN_OR)
|
||||
|| type.equals(Type.BOOLEAN_AND)
|
||||
|| type.equals(Type.MODULO_OPERATOR);
|
||||
return type.equals(TokenType.PLUS)
|
||||
|| type.equals(TokenType.MINUS)
|
||||
|| type.equals(TokenType.STAR)
|
||||
|| type.equals(TokenType.FORWARD_SLASH)
|
||||
|| type.equals(TokenType.EQUALS_EQUALS)
|
||||
|| type.equals(TokenType.BANG_EQUALS)
|
||||
|| type.equals(TokenType.LESS)
|
||||
|| type.equals(TokenType.GREATER)
|
||||
|| type.equals(TokenType.LESS_EQUALS)
|
||||
|| type.equals(TokenType.GREATER_EQUAL)
|
||||
|| type.equals(TokenType.BOOLEAN_OR)
|
||||
|| type.equals(TokenType.BOOLEAN_AND)
|
||||
|| type.equals(TokenType.MODULO_OPERATOR);
|
||||
}
|
||||
|
||||
public boolean isStrictNumericOperator() {
|
||||
return type.equals(Type.SUBTRACTION_OPERATOR)
|
||||
|| type.equals(Type.MULTIPLICATION_OPERATOR)
|
||||
|| type.equals(Type.DIVISION_OPERATOR)
|
||||
|| type.equals(Type.GREATER_THAN_OPERATOR)
|
||||
|| type.equals(Type.LESS_THAN_OPERATOR)
|
||||
|| type.equals(Type.LESS_THAN_OR_EQUALS_OPERATOR)
|
||||
|| type.equals(Type.GREATER_THAN_OR_EQUALS_OPERATOR)
|
||||
|| type.equals(Type.MODULO_OPERATOR);
|
||||
return type.equals(TokenType.MINUS)
|
||||
|| type.equals(TokenType.STAR)
|
||||
|| type.equals(TokenType.FORWARD_SLASH)
|
||||
|| type.equals(TokenType.GREATER)
|
||||
|| type.equals(TokenType.LESS)
|
||||
|| type.equals(TokenType.LESS_EQUALS)
|
||||
|| type.equals(TokenType.GREATER_EQUAL)
|
||||
|| type.equals(TokenType.MODULO_OPERATOR);
|
||||
}
|
||||
|
||||
public boolean isStrictBooleanOperator() {
|
||||
return type.equals(Type.BOOLEAN_AND)
|
||||
|| type.equals(Type.BOOLEAN_OR);
|
||||
return type.equals(TokenType.BOOLEAN_AND)
|
||||
|| type.equals(TokenType.BOOLEAN_OR);
|
||||
}
|
||||
|
||||
public boolean isVariableDeclaration() {
|
||||
return type.equals(Type.STRING_VARIABLE)
|
||||
|| type.equals(Type.BOOLEAN_VARIABLE)
|
||||
|| type.equals(Type.NUMBER_VARIABLE);
|
||||
return type.equals(TokenType.TYPE_STRING)
|
||||
|| type.equals(TokenType.TYPE_BOOLEAN)
|
||||
|| type.equals(TokenType.TYPE_NUMBER);
|
||||
}
|
||||
|
||||
public boolean isLoopLike() {
|
||||
return type.equals(Type.IF_STATEMENT)
|
||||
|| type.equals(Type.WHILE_LOOP)
|
||||
|| type.equals(Type.FOR_LOOP);
|
||||
public boolean isControlStructure() {
|
||||
return type.equals(TokenType.IF_STATEMENT)
|
||||
|| type.equals(TokenType.WHILE_LOOP)
|
||||
|| type.equals(TokenType.FOR_LOOP);
|
||||
}
|
||||
|
||||
public boolean isIdentifier() {
|
||||
return type.equals(Type.IDENTIFIER);
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
public enum TokenType {
|
||||
/**
|
||||
* Function identifier or language keyword
|
||||
*/
|
||||
@@ -108,11 +113,11 @@ public class Token {
|
||||
/**
|
||||
* Beginning of group
|
||||
*/
|
||||
GROUP_BEGIN,
|
||||
OPEN_PAREN,
|
||||
/**
|
||||
* Ending of group
|
||||
*/
|
||||
GROUP_END,
|
||||
CLOSE_PAREN,
|
||||
/**
|
||||
* End of statement
|
||||
*/
|
||||
@@ -136,43 +141,43 @@ public class Token {
|
||||
/**
|
||||
* Boolean equals operator
|
||||
*/
|
||||
EQUALS_OPERATOR,
|
||||
EQUALS_EQUALS,
|
||||
/**
|
||||
* Boolean not equals operator
|
||||
*/
|
||||
NOT_EQUALS_OPERATOR,
|
||||
BANG_EQUALS,
|
||||
/**
|
||||
* Boolean greater than operator
|
||||
*/
|
||||
GREATER_THAN_OPERATOR,
|
||||
GREATER,
|
||||
/**
|
||||
* Boolean less than operator
|
||||
*/
|
||||
LESS_THAN_OPERATOR,
|
||||
LESS,
|
||||
/**
|
||||
* Boolean greater than or equal to operator
|
||||
*/
|
||||
GREATER_THAN_OR_EQUALS_OPERATOR,
|
||||
GREATER_EQUAL,
|
||||
/**
|
||||
* Boolean less than or equal to operator
|
||||
*/
|
||||
LESS_THAN_OR_EQUALS_OPERATOR,
|
||||
LESS_EQUALS,
|
||||
/**
|
||||
* Addition/concatenation operator
|
||||
*/
|
||||
ADDITION_OPERATOR,
|
||||
PLUS,
|
||||
/**
|
||||
* Subtraction operator
|
||||
*/
|
||||
SUBTRACTION_OPERATOR,
|
||||
MINUS,
|
||||
/**
|
||||
* Multiplication operator
|
||||
*/
|
||||
MULTIPLICATION_OPERATOR,
|
||||
STAR,
|
||||
/**
|
||||
* Division operator
|
||||
*/
|
||||
DIVISION_OPERATOR,
|
||||
FORWARD_SLASH,
|
||||
/**
|
||||
* Modulo operator.
|
||||
*/
|
||||
@@ -180,7 +185,7 @@ public class Token {
|
||||
/**
|
||||
* Boolean not operator
|
||||
*/
|
||||
BOOLEAN_NOT,
|
||||
BANG,
|
||||
/**
|
||||
* Boolean or
|
||||
*/
|
||||
@@ -192,15 +197,19 @@ public class Token {
|
||||
/**
|
||||
* Numeric variable declaration
|
||||
*/
|
||||
NUMBER_VARIABLE,
|
||||
TYPE_NUMBER,
|
||||
/**
|
||||
* String variable declaration
|
||||
*/
|
||||
STRING_VARIABLE,
|
||||
TYPE_STRING,
|
||||
/**
|
||||
* Boolean variable declaration
|
||||
*/
|
||||
BOOLEAN_VARIABLE,
|
||||
TYPE_BOOLEAN,
|
||||
/**
|
||||
* Void type declaration
|
||||
*/
|
||||
TYPE_VOID,
|
||||
/**
|
||||
* If statement declaration
|
||||
*/
|
||||
@@ -232,6 +241,10 @@ public class Token {
|
||||
/**
|
||||
* Else keyword
|
||||
*/
|
||||
ELSE
|
||||
ELSE,
|
||||
/**
|
||||
* End of file
|
||||
*/
|
||||
END_OF_FILE
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -5,11 +5,11 @@
|
||||
* reference the LICENSE file in this module's root directory.
|
||||
*/
|
||||
|
||||
package com.dfsek.terra.addons.terrascript.tokenizer.exceptions;
|
||||
package com.dfsek.terra.addons.terrascript.lexer.exceptions;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
|
||||
import com.dfsek.terra.addons.terrascript.lexer.SourcePosition;
|
||||
|
||||
|
||||
public class EOFException extends TokenizerException {
|
||||
@@ -17,11 +17,11 @@ public class EOFException extends TokenizerException {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 3980047409902809440L;
|
||||
|
||||
public EOFException(String message, Position position) {
|
||||
public EOFException(String message, SourcePosition position) {
|
||||
super(message, position);
|
||||
}
|
||||
|
||||
public EOFException(String message, Position position, Throwable cause) {
|
||||
public EOFException(String message, SourcePosition position, Throwable cause) {
|
||||
super(message, position, cause);
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -5,11 +5,11 @@
|
||||
* reference the LICENSE file in this module's root directory.
|
||||
*/
|
||||
|
||||
package com.dfsek.terra.addons.terrascript.tokenizer.exceptions;
|
||||
package com.dfsek.terra.addons.terrascript.lexer.exceptions;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
import com.dfsek.terra.addons.terrascript.tokenizer.Position;
|
||||
import com.dfsek.terra.addons.terrascript.lexer.SourcePosition;
|
||||
|
||||
|
||||
public class FormatException extends TokenizerException {
|
||||
@@ -17,11 +17,11 @@ public class FormatException extends TokenizerException {
|
||||
@Serial
|
||||
private static final long serialVersionUID = -791308012940744455L;
|
||||
|
||||
public FormatException(String message, Position position) {
|
||||
public FormatException(String message, SourcePosition position) {
|
||||
super(message, position);
|
||||
}
|
||||
|
||||
public FormatException(String message, Position position, Throwable cause) {
|
||||
public FormatException(String message, SourcePosition position, Throwable cause) {
|
||||
super(message, position, cause);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user