From ff00f3f1b66b4057a31f83cfe4a97ab8eebce84f Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Sun, 29 Dec 2019 16:23:43 +0100 Subject: [PATCH] Complete rewrite of resource loading, render-optimizations and fixes for resource-packs --- .../bluecolored/bluemap/cli/BlueMapCLI.java | 49 ++- BlueMapCore/build.gradle | 8 + .../bluemap/core/logger/AbstractLogger.java | 37 +- .../core/logger/PrintStreamLogger.java | 29 +- .../bluecolored/bluemap/core/mca/Chunk.java | 5 +- .../bluemap/core/mca/ChunkAnvil112.java | 5 +- .../bluemap/core/mca/ChunkAnvil113.java | 7 +- .../bluemap/core/mca/MCABlock.java | 9 +- .../bluemap/core/mca/MCAWorld.java | 5 +- .../core/mca/mapping/BiomeIdMapper.java | 25 +- .../render/context/EmptyBlockContext.java | 5 +- .../core/render/hires/HiresModelRenderer.java | 6 +- .../blockmodel/BlockStateModelFactory.java | 56 ++- .../hires/blockmodel/LiquidModelBuilder.java | 123 +++--- .../blockmodel/ResourceModelBuilder.java | 160 +++---- .../resourcepack/BlockColorCalculator.java | 197 +++++++++ .../core/resourcepack/BlockColorProvider.java | 239 ---------- .../BlockModelElementFaceResource.java | 142 ------ .../BlockModelElementResource.java | 144 ------ .../core/resourcepack/BlockModelResource.java | 412 ++++++++++++++---- .../core/resourcepack/BlockStateResource.java | 292 +++++++++---- ...clarationException.java => ModelType.java} | 17 +- .../resourcepack/NoSuchResourceException.java | 15 +- ...ption.java => ParseResourceException.java} | 17 +- .../core/resourcepack/PropertyCondition.java | 137 ++++++ .../core/resourcepack/ResourcePack.java | 271 ++++++------ .../bluemap/core/resourcepack/Texture.java | 87 ++++ .../core/resourcepack/TextureGallery.java | 315 +++++++++++++ .../core/resourcepack/TextureProvider.java | 236 ---------- .../TransformedBlockModelResource.java} | 62 +-- .../fileaccess/CombinedFileAccess.java | 97 +++++ .../resourcepack/fileaccess/FileAccess.java | 65 +++ .../fileaccess/FolderFileAccess.java | 138 ++++++ .../fileaccess/ZipFileAccess.java | 124 ++++++ .../bluemap/core/util/ConfigUtils.java | 24 + .../bluemap/core/util/MathUtils.java | 73 +++- .../bluemap/core/web/WebSettings.java | 2 +- .../bluecolored/bluemap/core/world/Biome.java | 108 +++++ .../bluecolored/bluemap/core/world/Block.java | 2 +- .../bluemap/core/world/BlockState.java | 5 +- .../bluemap/core/world/CachedBlock.java | 4 +- .../{mca/mapping => world}/LightData.java | 2 +- .../assets/minecraft/blockstates/lava.json | 5 + .../assets/minecraft/blockstates/water.json | 5 + .../assets/minecraft/models/block/lava.json | 8 + .../assets/minecraft/models/block/water.json | 8 + .../src/main/resources/blockColors.json | 19 +- .../src/main/resources/resourceExtensions.zip | Bin 0 -> 1552 bytes .../bluemap/sponge/Slf4jLogger.java | 12 +- .../bluemap/sponge/SpongePlugin.java | 25 +- 50 files changed, 2436 insertions(+), 1402 deletions(-) create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorProvider.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelElementFaceResource.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelElementResource.java rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/{InvalidResourceDeclarationException.java => ModelType.java} (75%) rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/{NoSuchTextureException.java => ParseResourceException.java} (80%) create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/PropertyCondition.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/Texture.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureGallery.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureProvider.java rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/{util/WeighedArrayList.java => resourcepack/TransformedBlockModelResource.java} (55%) create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/fileaccess/CombinedFileAccess.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/fileaccess/FileAccess.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/fileaccess/FolderFileAccess.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/fileaccess/ZipFileAccess.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Biome.java rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/{mca/mapping => world}/LightData.java (97%) create mode 100644 BlueMapCore/src/main/resourceExtensions/assets/minecraft/blockstates/lava.json create mode 100644 BlueMapCore/src/main/resourceExtensions/assets/minecraft/blockstates/water.json create mode 100644 BlueMapCore/src/main/resourceExtensions/assets/minecraft/models/block/lava.json create mode 100644 BlueMapCore/src/main/resourceExtensions/assets/minecraft/models/block/water.json create mode 100644 BlueMapCore/src/main/resources/resourceExtensions.zip diff --git a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java index a45e289c..24217d4f 100644 --- a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java +++ b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java @@ -45,6 +45,7 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; +import org.apache.commons.io.FileUtils; import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3i; @@ -59,7 +60,7 @@ import de.bluecolored.bluemap.core.render.TileRenderer; import de.bluecolored.bluemap.core.render.hires.HiresModelManager; import de.bluecolored.bluemap.core.render.lowres.LowresModelManager; -import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException; +import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; import de.bluecolored.bluemap.core.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.web.BlueMapWebServer; import de.bluecolored.bluemap.core.web.WebSettings; @@ -69,17 +70,19 @@ public class BlueMapCLI { private ConfigurationFile configFile; private Configuration config; + private File configFolder; private ResourcePack resourcePack; private boolean forceRender; - public BlueMapCLI(ConfigurationFile configFile, boolean forceRender) { + public BlueMapCLI(ConfigurationFile configFile, File configFolder, boolean forceRender) { this.configFile = configFile; this.config = configFile.getConfig(); + this.configFolder = configFolder; this.forceRender = forceRender; this.resourcePack = null; } - public void renderMaps() throws IOException, NoSuchResourceException { + public void renderMaps() throws IOException { Preconditions.checkNotNull(resourcePack); config.getWebDataPath().toFile().mkdirs(); @@ -126,6 +129,10 @@ public void renderMaps() throws IOException, NoSuchResourceException { } webSettings.save(); + Logger.global.logInfo("Writing textures.json ..."); + File textureExportFile = config.getWebDataPath().resolve("textures.json").toFile(); + resourcePack.saveTextureFile(textureExportFile); + for (MapType map : maps.values()) { Logger.global.logInfo("Rendering map '" + map.getId() + "' ..."); Logger.global.logInfo("Collecting tiles to render..."); @@ -187,14 +194,25 @@ public void startWebserver() throws IOException { webserver.start(); } - private boolean loadResources() throws IOException, NoSuchResourceException { + private boolean loadResources() throws IOException, ParseResourceException { + Logger.global.logInfo("Loading resources..."); + File defaultResourceFile = config.getDataPath().resolve("minecraft-client-" + ResourcePack.MINECRAFT_CLIENT_VERSION + ".jar").toFile(); + File resourceExtensionsFile = config.getDataPath().resolve("resourceExtensions.zip").toFile(); File textureExportFile = config.getWebDataPath().resolve("textures.json").toFile(); if (!defaultResourceFile.exists()) { if (!handleMissingResources(defaultResourceFile)) return false; } + resourceExtensionsFile.delete(); + FileUtils.copyURLToFile(BlueMapCLI.class.getResource("/resourceExtensions.zip"), resourceExtensionsFile, 10000, 10000); + + File blockColorsConfigFile = new File(configFolder, "blockColors.json"); + if (!blockColorsConfigFile.exists()) { + FileUtils.copyURLToFile(BlueMapCLI.class.getResource("/blockColors.json"), blockColorsConfigFile, 10000, 10000); + } + //find more resource packs File resourcePackFolder = configFile.getFile().toPath().resolveSibling("resourcepacks").toFile(); resourcePackFolder.mkdirs(); @@ -204,8 +222,13 @@ private boolean loadResources() throws IOException, NoSuchResourceException { List resources = new ArrayList<>(resourcePacks.length + 1); resources.add(defaultResourceFile); for (File file : resourcePacks) resources.add(file); + resources.add(resourceExtensionsFile); - resourcePack = new ResourcePack(resources, textureExportFile); + resourcePack = new ResourcePack(); + if (textureExportFile.exists()) resourcePack.loadTextureFile(textureExportFile); + resourcePack.load(resources); + resourcePack.loadBlockColorConfig(blockColorsConfigFile); + resourcePack.saveTextureFile(textureExportFile); return true; } @@ -228,7 +251,7 @@ private boolean handleMissingResources(File resourceFile) { } } - public static void main(String[] args) throws IOException, NoSuchResourceException { + public static void main(String[] args) throws IOException, ParseResourceException { CommandLineParser parser = new DefaultParser(); try { @@ -241,12 +264,14 @@ public static void main(String[] args) throws IOException, NoSuchResourceExcepti } //load config - File configFile = new File("bluemap.conf").getAbsoluteFile(); + File configFolder = new File("."); if (cmd.hasOption("c")) { - configFile = new File(cmd.getOptionValue("c")); - configFile.getParentFile().mkdirs(); + configFolder = new File(cmd.getOptionValue("c")); + configFolder.mkdirs(); } + File configFile = new File(configFolder, "bluemap.conf"); + boolean configCreated = !configFile.exists(); ConfigurationFile config = ConfigurationFile.loadOrCreate(configFile, BlueMapCLI.class.getResource("/bluemap-cli.conf")); @@ -256,7 +281,7 @@ public static void main(String[] args) throws IOException, NoSuchResourceExcepti return; } - BlueMapCLI bluemap = new BlueMapCLI(config, cmd.hasOption("f")); + BlueMapCLI bluemap = new BlueMapCLI(config, configFolder, cmd.hasOption("f")); if (config.getConfig().isWebserverEnabled()) { //start webserver @@ -294,8 +319,8 @@ private static Options createOptions() { Option.builder("c") .longOpt("config") .hasArg() - .argName("config-file") - .desc("Sets path of the configuration file to use") + .argName("config-folder") + .desc("Sets path of the folder containing the configuration-files to use (configurations will be generated here if they don't exist)") .build() ); diff --git a/BlueMapCore/build.gradle b/BlueMapCore/build.gradle index a47a7703..7deaa3dd 100644 --- a/BlueMapCore/build.gradle +++ b/BlueMapCore/build.gradle @@ -16,5 +16,13 @@ task zipWebroot(type: Zip) { outputs.upToDateWhen { false } } +task zipResourceExtensions(type: Zip) { + from fileTree('src/main/resourceExtensions') + archiveName 'resourceExtensions.zip' + destinationDir(file('/src/main/resources/')) + outputs.upToDateWhen { false } +} + //always update the zip before build compileJava.dependsOn(zipWebroot) +compileJava.dependsOn(zipResourceExtensions) diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/AbstractLogger.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/AbstractLogger.java index 40a8dcb0..027c9ee6 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/AbstractLogger.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/AbstractLogger.java @@ -24,46 +24,63 @@ */ package de.bluecolored.bluemap.core.logger; -import java.util.Set; +import java.util.concurrent.TimeUnit; -import com.google.common.collect.Sets; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; public abstract class AbstractLogger extends Logger { - private Set noFloodLog; + private static final Object DUMMY = new Object(); + + private Cache noFloodCache; public AbstractLogger() { - noFloodLog = Sets.newConcurrentHashSet(); + noFloodCache = CacheBuilder.newBuilder() + .concurrencyLevel(4) + .expireAfterWrite(1, TimeUnit.HOURS) + .maximumSize(10000) + .build(); } @Override public void noFloodError(String key, String message, Throwable throwable){ - if (noFloodLog.add(key)) logError(message, throwable); + if (check(key)) logError(message, throwable); } @Override public void noFloodWarning(String key, String message){ - if (noFloodLog.add(key)) logWarning(message); + if (check(key)) logWarning(message); } @Override public void noFloodInfo(String key, String message){ - if (noFloodLog.add(key)) logInfo(message); + if (check(key)) logInfo(message); } @Override public void noFloodDebug(String key, String message){ - if (noFloodLog.add(key)) logDebug(message); + if (check(key)) logDebug(message); } @Override public void clearNoFloodLog() { - noFloodLog.clear(); + noFloodCache.invalidateAll(); + noFloodCache.cleanUp(); } @Override public void removeNoFloodKey(String key) { - noFloodLog.remove(key); + noFloodCache.invalidate(key); + } + + private boolean check(String key) { + if (noFloodCache.getIfPresent(key) == null) { + noFloodCache.put(key, DUMMY); + return true; + } + + return false; } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/PrintStreamLogger.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/PrintStreamLogger.java index 724e8904..bd00d9fa 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/PrintStreamLogger.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/PrintStreamLogger.java @@ -30,9 +30,26 @@ public class PrintStreamLogger extends AbstractLogger { private PrintStream out, err; + boolean isDebug; + public PrintStreamLogger(PrintStream out, PrintStream err) { this.out = out; this.err = err; + this.isDebug = true; + } + + public PrintStreamLogger(PrintStream out, PrintStream err, boolean debug) { + this.out = out; + this.err = err; + this.isDebug = debug; + } + + public boolean isDebug() { + return isDebug; + } + + public void setDebug(boolean debug) { + this.isDebug = debug; } @Override @@ -53,7 +70,17 @@ public void logInfo(String message) { @Override public void logDebug(String message) { - out.println("[DEBUG] " + message); + if (isDebug) out.println("[DEBUG] " + message); + } + + @Override + public void noFloodDebug(String key, String message) { + if (isDebug) super.noFloodDebug(key, message); + } + + @Override + public void noFloodDebug(String message) { + if (isDebug) super.noFloodDebug(message); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/Chunk.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/Chunk.java index 7db25414..93bceddb 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/Chunk.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/Chunk.java @@ -29,8 +29,9 @@ import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3i; -import de.bluecolored.bluemap.core.mca.mapping.LightData; +import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.BlockState; +import de.bluecolored.bluemap.core.world.LightData; import net.querz.nbt.CompoundTag; public abstract class Chunk { @@ -63,7 +64,7 @@ public MCAWorld getWorld() { public abstract LightData getLightData(Vector3i pos); - public abstract String getBiomeId(Vector3i pos); + public abstract Biome getBiome(Vector3i pos); public static Chunk create(MCAWorld world, CompoundTag chunkTag) throws IOException { int version = chunkTag.getInt("DataVersion"); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil112.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil112.java index 8f1f315f..2d2b925c 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil112.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil112.java @@ -28,8 +28,9 @@ import de.bluecolored.bluemap.core.mca.mapping.BiomeIdMapper; import de.bluecolored.bluemap.core.mca.mapping.BlockIdMapper; -import de.bluecolored.bluemap.core.mca.mapping.LightData; +import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.BlockState; +import de.bluecolored.bluemap.core.world.LightData; import net.querz.nbt.CompoundTag; import net.querz.nbt.ListTag; import net.querz.nbt.mca.MCAUtil; @@ -90,7 +91,7 @@ public LightData getLightData(Vector3i pos) { } @Override - public String getBiomeId(Vector3i pos) { + public Biome getBiome(Vector3i pos) { int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16) int z = pos.getZ() & 0xF; int biomeByteIndex = z * 16 + x; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java index e97056be..c9c960a9 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java @@ -32,8 +32,9 @@ import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.mca.mapping.BiomeIdMapper; -import de.bluecolored.bluemap.core.mca.mapping.LightData; +import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.BlockState; +import de.bluecolored.bluemap.core.world.LightData; import net.querz.nbt.ByteArrayTag; import net.querz.nbt.CompoundTag; import net.querz.nbt.IntArrayTag; @@ -111,7 +112,7 @@ public LightData getLightData(Vector3i pos) { } @Override - public String getBiomeId(Vector3i pos) { + public Biome getBiome(Vector3i pos) { int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16) int z = pos.getZ() & 0xF; int biomeByteIndex = z * 16 + x; @@ -148,7 +149,7 @@ public Section(CompoundTag sectionData) { if (stateTag.containsKey("Properties")) { CompoundTag propertiesTag = stateTag.getCompoundTag("Properties"); for (Entry> property : propertiesTag) { - properties.put(property.getKey(), ((StringTag) property.getValue()).getValue()); + properties.put(property.getKey().toLowerCase(), ((StringTag) property.getValue()).getValue().toLowerCase()); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCABlock.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCABlock.java index dd3672ed..b2580dc0 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCABlock.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCABlock.java @@ -27,20 +27,21 @@ import com.flowpowered.math.vector.Vector3i; import de.bluecolored.bluemap.core.mca.mapping.BlockProperties; -import de.bluecolored.bluemap.core.mca.mapping.LightData; +import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.Block; import de.bluecolored.bluemap.core.world.BlockState; +import de.bluecolored.bluemap.core.world.LightData; public class MCABlock extends Block { private MCAWorld world; private BlockState blockState; private LightData lightData; - private String biome; + private Biome biome; private BlockProperties properties; private Vector3i pos; - public MCABlock(MCAWorld world, BlockState blockState, LightData lightData, String biome, BlockProperties properties, Vector3i pos) { + public MCABlock(MCAWorld world, BlockState blockState, LightData lightData, Biome biome, BlockProperties properties, Vector3i pos) { this.world = world; this.blockState = blockState; this.lightData = lightData; @@ -85,7 +86,7 @@ public boolean isOccludingNeighborFaces() { } @Override - public String getBiome() { + public Biome getBiome() { return biome; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java index ad34fad7..ad4631aa 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java @@ -64,11 +64,12 @@ import de.bluecolored.bluemap.core.mca.mapping.BlockIdMapper; import de.bluecolored.bluemap.core.mca.mapping.BlockProperties; import de.bluecolored.bluemap.core.mca.mapping.BlockPropertyMapper; -import de.bluecolored.bluemap.core.mca.mapping.LightData; import de.bluecolored.bluemap.core.util.AABB; +import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.Block; import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException; +import de.bluecolored.bluemap.core.world.LightData; import de.bluecolored.bluemap.core.world.World; import de.bluecolored.bluemap.core.world.WorldChunk; import net.querz.nbt.CompoundTag; @@ -162,7 +163,7 @@ public Block getBlock(Vector3i pos) throws ChunkNotGeneratedException { Chunk chunk = getChunk(chunkPos); BlockState blockState = getExtendedBlockState(chunk, pos); LightData lightData = chunk.getLightData(pos); - String biome = chunk.getBiomeId(pos); + Biome biome = chunk.getBiome(pos); BlockProperties properties = blockPropertyMapper.map(blockState); return new MCABlock(this, blockState, lightData, biome, properties, pos); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BiomeIdMapper.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BiomeIdMapper.java index 4427cac9..3a2f685a 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BiomeIdMapper.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BiomeIdMapper.java @@ -25,21 +25,19 @@ package de.bluecolored.bluemap.core.mca.mapping; import java.io.IOException; +import java.util.HashMap; import java.util.Map.Entry; +import de.bluecolored.bluemap.core.world.Biome; import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.gson.GsonConfigurationLoader; public class BiomeIdMapper { - private static final String DEFAULT_BIOME = "ocean"; - private String[] biomes; + private HashMap biomes; public BiomeIdMapper() throws IOException { - biomes = new String[256]; - for (int i = 0; i < biomes.length; i++) { - biomes[i] = DEFAULT_BIOME; - } + biomes = new HashMap<>(); GsonConfigurationLoader loader = GsonConfigurationLoader.builder() .setURL(getClass().getResource("/biomes.json")) @@ -48,18 +46,17 @@ public BiomeIdMapper() throws IOException { ConfigurationNode node = loader.load(); for (Entry e : node.getChildrenMap().entrySet()){ - String biome = e.getKey().toString(); - int id = e.getValue().getNode("id").getInt(-1); - if (id >= 0 && id < biomes.length) { - biomes[id] = biome; - } + String id = e.getKey().toString(); + Biome biome = Biome.create(id, e.getValue()); + biomes.put(biome.getOrdinal(), biome); } } - public String get(int id) { - if (id < 0 || id >= biomes.length) return DEFAULT_BIOME; - return biomes[id]; + public Biome get(int id) { + Biome biome = biomes.get(id); + if (biome == null) return Biome.DEFAULT; + return biome; } public static BiomeIdMapper create() throws IOException { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/EmptyBlockContext.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/EmptyBlockContext.java index 0fa3ec13..6fe3ca7e 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/EmptyBlockContext.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/EmptyBlockContext.java @@ -33,6 +33,7 @@ import com.flowpowered.math.vector.Vector3i; import de.bluecolored.bluemap.core.util.AABB; +import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.Block; import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.World; @@ -95,8 +96,8 @@ public boolean isCullingNeighborFaces() { } @Override - public String getBiome() { - return "ocean"; + public Biome getBiome() { + return Biome.DEFAULT; } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelRenderer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelRenderer.java index d9c47f84..3e8aaab2 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelRenderer.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelRenderer.java @@ -34,9 +34,7 @@ import de.bluecolored.bluemap.core.render.context.SlicedWorldChunkBlockContext; import de.bluecolored.bluemap.core.render.hires.blockmodel.BlockStateModel; import de.bluecolored.bluemap.core.render.hires.blockmodel.BlockStateModelFactory; -import de.bluecolored.bluemap.core.resourcepack.InvalidResourceDeclarationException; import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException; -import de.bluecolored.bluemap.core.resourcepack.NoSuchTextureException; import de.bluecolored.bluemap.core.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.util.AABB; import de.bluecolored.bluemap.core.util.MathUtils; @@ -84,9 +82,9 @@ public HiresModel render(WorldTile tile, AABB region, RenderSettings renderSetti BlockStateModel blockModel; try { blockModel = modelFactory.createFrom(block.getBlock(), new SlicedWorldChunkBlockContext(chunk, new Vector3i(x, y, z), renderSettings.getSliceY()), renderSettings); - } catch (NoSuchResourceException | InvalidResourceDeclarationException | NoSuchTextureException e) { + } catch (NoSuchResourceException e) { blockModel = new BlockStateModel(); - Logger.global.noFloodDebug(block.getBlock().getId() + "-hiresModelRenderer-blockmodelerr", "Failed to create BlockModel for BlockState: " + block.getBlock() + " (" + e.toString() + ")"); + Logger.global.noFloodDebug(block.getBlock().getFullId() + "-hiresModelRenderer-blockmodelerr", "Failed to create BlockModel for BlockState: " + block.getBlock() + " (" + e.toString() + ")"); } blockModel.translate(new Vector3f(x, y, z).sub(min.toFloat())); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/BlockStateModelFactory.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/BlockStateModelFactory.java index 9d0d1c80..b530c564 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/BlockStateModelFactory.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/BlockStateModelFactory.java @@ -27,11 +27,11 @@ import de.bluecolored.bluemap.core.render.RenderSettings; import de.bluecolored.bluemap.core.render.context.EmptyBlockContext; import de.bluecolored.bluemap.core.render.context.ExtendedBlockContext; +import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculator; import de.bluecolored.bluemap.core.resourcepack.BlockStateResource; -import de.bluecolored.bluemap.core.resourcepack.InvalidResourceDeclarationException; import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException; -import de.bluecolored.bluemap.core.resourcepack.NoSuchTextureException; import de.bluecolored.bluemap.core.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.resourcepack.TransformedBlockModelResource; import de.bluecolored.bluemap.core.world.BlockState; public class BlockStateModelFactory { @@ -42,7 +42,7 @@ public BlockStateModelFactory(ResourcePack resources) { this.resourcePack = resources; } - public BlockStateModel createFrom(BlockState blockState) throws NoSuchResourceException, InvalidResourceDeclarationException, NoSuchTextureException { + public BlockStateModel createFrom(BlockState blockState) throws NoSuchResourceException { return createFrom(blockState, EmptyBlockContext.instance(), new RenderSettings() { @Override public float getLightShadeMultiplier() { @@ -61,37 +61,49 @@ public float getAmbientOcclusionStrenght() { }); } - public BlockStateModel createFrom(BlockState blockState, ExtendedBlockContext context, RenderSettings renderSettings) throws NoSuchResourceException, InvalidResourceDeclarationException, NoSuchTextureException { - - //air won't be rendered + public BlockStateModel createFrom(BlockState blockState, ExtendedBlockContext context, RenderSettings renderSettings) throws NoSuchResourceException { + + //shortcut for air if ( - blockState.getId().equals("air") || - blockState.getId().equals("cave_air") || - blockState.getId().equals("void_air") + blockState.getFullId().equals("minecraft:air") || + blockState.getFullId().equals("minecraft:cave_air") || + blockState.getFullId().equals("minecraft:void_air") ) { return new BlockStateModel(); } - // if it is a liquid, use the LiquidModelBuilder - if ( - blockState.getId().equals("water") || - blockState.getId().equals("lava") - ){ - return new LiquidModelBuilder(blockState, context, resourcePack, renderSettings).build(); - } - - // if no other model builder matched try to find a model definition from the resource-packs and use the default ResourceModelBuilder - BlockStateResource resource = resourcePack.getBlockStateResource(blockState); - BlockStateModel model = new ResourceModelBuilder(resource, context, resourcePack, renderSettings).build(); + BlockStateModel model = createModel(blockState, context, renderSettings); // if block is waterlogged if (LiquidModelBuilder.isWaterlogged(blockState)) { - BlockStateModel watermodel = new LiquidModelBuilder(WATERLOGGED_BLOCKSTATE, context, resourcePack, renderSettings).build(); - model.merge(watermodel); + model.merge(createModel(WATERLOGGED_BLOCKSTATE, context, renderSettings)); } return model; } + + private BlockStateModel createModel(BlockState blockState, ExtendedBlockContext context, RenderSettings renderSettings) throws NoSuchResourceException { + + BlockStateResource resource = resourcePack.getBlockStateResource(blockState); + BlockStateModel model = new BlockStateModel(); + BlockColorCalculator colorCalculator = resourcePack.getBlockColorCalculator(); + ResourceModelBuilder modelBuilder = new ResourceModelBuilder(renderSettings, context, colorCalculator); + LiquidModelBuilder liquidBuilder = new LiquidModelBuilder(renderSettings, context, blockState, colorCalculator); + + for (TransformedBlockModelResource bmr : resource.getModels(blockState, context.getPosition())){ + switch (bmr.getModel().getType()){ + case LIQUID: + model.merge(liquidBuilder.build(blockState, bmr.getModel())); + break; + default: + model.merge(modelBuilder.build(bmr)); + break; + } + } + + return model; + + } private BlockState WATERLOGGED_BLOCKSTATE = new BlockState("minecraft:water"); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/LiquidModelBuilder.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/LiquidModelBuilder.java index 42cfa7d7..f86edfb6 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/LiquidModelBuilder.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/LiquidModelBuilder.java @@ -36,8 +36,9 @@ import de.bluecolored.bluemap.core.model.Model; import de.bluecolored.bluemap.core.render.RenderSettings; import de.bluecolored.bluemap.core.render.context.ExtendedBlockContext; -import de.bluecolored.bluemap.core.resourcepack.NoSuchTextureException; -import de.bluecolored.bluemap.core.resourcepack.ResourcePack; +import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculator; +import de.bluecolored.bluemap.core.resourcepack.BlockModelResource; +import de.bluecolored.bluemap.core.resourcepack.Texture; import de.bluecolored.bluemap.core.util.Direction; import de.bluecolored.bluemap.core.world.Block; import de.bluecolored.bluemap.core.world.BlockState; @@ -50,43 +51,73 @@ public class LiquidModelBuilder { private static final HashSet DEFAULT_WATERLOGGED_BLOCK_IDS = Sets.newHashSet( "minecraft:seagrass", "minecraft:tall_seagrass", - "minecraft:kelp" + "minecraft:kelp", + "minecraft:bubble_column" ); - private BlockState blockState; + private BlockState liquidBlockState; private ExtendedBlockContext context; - private ResourcePack resourcePack; private RenderSettings renderSettings; + private BlockColorCalculator colorCalculator; - private float[] heights; - - public LiquidModelBuilder(BlockState blockState, ExtendedBlockContext context, ResourcePack resourcePack, RenderSettings renderSettings) { - this.blockState = blockState; + public LiquidModelBuilder(RenderSettings renderSettings, ExtendedBlockContext context, BlockState liquidBlockState, BlockColorCalculator colorCalculator) { this.context = context; - this.resourcePack = resourcePack; this.renderSettings = renderSettings; - - this.heights = new float[]{14f, 14f, 14f, 14f}; + this.liquidBlockState = liquidBlockState; + this.colorCalculator = colorCalculator; } - public BlockStateModel build() throws NoSuchTextureException { + public BlockStateModel build(BlockState blockState, BlockModelResource bmr) { if (this.renderSettings.isExcludeFacesWithoutSunlight() && context.getRelativeBlock(0, 0, 0).getSunLightLevel() == 0) return new BlockStateModel(); int level = getLiquidLevel(blockState); + float[] heights = new float[]{16f, 16f, 16f, 16f}; - if (level >= 8 ||level == 0 && isLiquid(context.getRelativeBlock(0, 1, 0))){ - this.heights = new float[]{16f, 16f, 16f, 16f}; - return buildModel(); + if (level < 8 && !(level == 0 && isLiquid(context.getRelativeBlock(0, 1, 0)))){ + heights = new float[]{ + getLiquidCornerHeight(-1, 0, -1), + getLiquidCornerHeight(-1, 0, 0), + getLiquidCornerHeight(0, 0, -1), + getLiquidCornerHeight(0, 0, 0) + }; } - this.heights = new float[]{ - getLiquidCornerHeight(-1, 0, -1), - getLiquidCornerHeight(-1, 0, 0), - getLiquidCornerHeight(0, 0, -1), - getLiquidCornerHeight(0, 0, 0) - }; + BlockStateModel model = new BlockStateModel(); + Texture texture = bmr.getTexture("still"); - return buildModel(); + Vector3f[] c = new Vector3f[]{ + new Vector3f( 0, 0, 0 ), + new Vector3f( 0, 0, 16 ), + new Vector3f( 16, 0, 0 ), + new Vector3f( 16, 0, 16 ), + new Vector3f( 0, heights[0], 0 ), + new Vector3f( 0, heights[1], 16 ), + new Vector3f( 16, heights[2], 0 ), + new Vector3f( 16, heights[3], 16 ), + }; + + int textureId = texture.getId(); + Vector3f tintcolor = Vector3f.ONE; + if (liquidBlockState.getFullId().equals("minecraft:water")) { + tintcolor = colorCalculator.getWaterAverageColor(context); + } + + createElementFace(model, Direction.DOWN, c[0], c[2], c[3], c[1], tintcolor, textureId); + createElementFace(model, Direction.UP, c[5], c[7], c[6], c[4], tintcolor, textureId); + createElementFace(model, Direction.NORTH, c[2], c[0], c[4], c[6], tintcolor, textureId); + createElementFace(model, Direction.SOUTH, c[1], c[3], c[7], c[5], tintcolor, textureId); + createElementFace(model, Direction.WEST, c[0], c[1], c[5], c[4], tintcolor, textureId); + createElementFace(model, Direction.EAST, c[3], c[2], c[6], c[7], tintcolor, textureId); + + //scale down + model.transform(Matrix3f.createScaling(1f / 16f)); + + //calculate mapcolor + Vector4f mapcolor = texture.getColor(); + mapcolor = mapcolor.mul(tintcolor.toVector4(0.5)); + model.setMapColor(mapcolor); + + return model; } private float getLiquidCornerHeight(int x, int y, int z){ @@ -133,9 +164,9 @@ private boolean isLiquid(Block block){ return isLiquid(block.getBlock()); } - private boolean isLiquid(BlockState blockstate){ - if (blockstate.getId().equals(blockState.getId())) return true; - return LiquidModelBuilder.isWaterlogged(blockstate); + private boolean isLiquid(BlockState blockState){ + if (blockState.getFullId().equals(liquidBlockState.getFullId())) return true; + return LiquidModelBuilder.isWaterlogged(blockState); } private float getLiquidBaseHeight(BlockState block){ @@ -151,45 +182,7 @@ private int getLiquidLevel(BlockState block){ return 0; } - private BlockStateModel buildModel() throws NoSuchTextureException { - BlockStateModel model = new BlockStateModel(); - - Vector3f[] c = new Vector3f[]{ - new Vector3f( 0, 0, 0 ), - new Vector3f( 0, 0, 16 ), - new Vector3f( 16, 0, 0 ), - new Vector3f( 16, 0, 16 ), - new Vector3f( 0, heights[0], 0 ), - new Vector3f( 0, heights[1], 16 ), - new Vector3f( 16, heights[2], 0 ), - new Vector3f( 16, heights[3], 16 ), - }; - - int textureId = resourcePack.getTextureProvider().getTextureIndex("block/" + blockState.getId() + "_still"); - Vector3f tintcolor = Vector3f.ONE; - if (blockState.getId().equals("water")) { - tintcolor = resourcePack.getBlockColorProvider().getBiomeWaterAverageColor(context); - } - - createElementFace(model, Direction.DOWN, c[0], c[2], c[3], c[1], tintcolor, textureId); - createElementFace(model, Direction.UP, c[5], c[7], c[6], c[4], tintcolor, textureId); - createElementFace(model, Direction.NORTH, c[2], c[0], c[4], c[6], tintcolor, textureId); - createElementFace(model, Direction.SOUTH, c[1], c[3], c[7], c[5], tintcolor, textureId); - createElementFace(model, Direction.WEST, c[0], c[1], c[5], c[4], tintcolor, textureId); - createElementFace(model, Direction.EAST, c[3], c[2], c[6], c[7], tintcolor, textureId); - - //scale down - model.transform(Matrix3f.createScaling(1f / 16f)); - - //calculate mapcolor - Vector4f mapcolor = resourcePack.getTextureProvider().getTexture("block/" + blockState.getId() + "_still").getColor(); - mapcolor = mapcolor.mul(tintcolor.toVector4(1)); - model.setMapColor(mapcolor); - - return model; - } - - private void createElementFace(Model model, Direction faceDir, Vector3f c0, Vector3f c1, Vector3f c2, Vector3f c3, Vector3f color, int textureId) throws NoSuchTextureException { + private void createElementFace(Model model, Direction faceDir, Vector3f c0, Vector3f c1, Vector3f c2, Vector3f c3, Vector3f color, int textureId) { //face culling Block bl = context.getRelativeBlock(faceDir); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/ResourceModelBuilder.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/ResourceModelBuilder.java index 6580b576..80b97ffa 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/ResourceModelBuilder.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/ResourceModelBuilder.java @@ -29,6 +29,7 @@ import com.flowpowered.math.imaginary.Quaternionf; import com.flowpowered.math.matrix.Matrix3f; import com.flowpowered.math.vector.Vector2f; +import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3f; import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector4f; @@ -37,16 +38,12 @@ import de.bluecolored.bluemap.core.render.RenderSettings; import de.bluecolored.bluemap.core.render.context.BlockContext; import de.bluecolored.bluemap.core.render.context.ExtendedBlockContext; -import de.bluecolored.bluemap.core.resourcepack.BlockModelElementFaceResource; -import de.bluecolored.bluemap.core.resourcepack.BlockModelElementResource; +import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculator; import de.bluecolored.bluemap.core.resourcepack.BlockModelResource; -import de.bluecolored.bluemap.core.resourcepack.BlockStateResource; -import de.bluecolored.bluemap.core.resourcepack.NoSuchTextureException; -import de.bluecolored.bluemap.core.resourcepack.ResourcePack; -import de.bluecolored.bluemap.core.resourcepack.TextureProvider.Texture; +import de.bluecolored.bluemap.core.resourcepack.BlockModelResource.Element.Rotation; +import de.bluecolored.bluemap.core.resourcepack.Texture; +import de.bluecolored.bluemap.core.resourcepack.TransformedBlockModelResource; import de.bluecolored.bluemap.core.util.Direction; -import de.bluecolored.bluemap.core.util.MathUtils; -import de.bluecolored.bluemap.core.util.WeighedArrayList; import de.bluecolored.bluemap.core.world.Block; /** @@ -58,49 +55,37 @@ public class ResourceModelBuilder { private static final Vector3f NEG_HALF_3F = HALF_3F.negate(); private static final Vector2f HALF_2F = Vector2f.ONE.mul(0.5); - private BlockStateResource resource; private ExtendedBlockContext context; - private ResourcePack resourcePack; private RenderSettings renderSettings; + private BlockColorCalculator colorCalculator; - public ResourceModelBuilder(BlockStateResource resource, ExtendedBlockContext context, ResourcePack resourcePack, RenderSettings renderSettings) { - this.resource = resource; + public ResourceModelBuilder(RenderSettings renderSettings, ExtendedBlockContext context, BlockColorCalculator colorCalculator) { this.context = context; - this.resourcePack = resourcePack; this.renderSettings = renderSettings; + this.colorCalculator = colorCalculator; } - - public BlockStateModel build() throws NoSuchTextureException { + + public BlockStateModel build(TransformedBlockModelResource bmr) { BlockStateModel model = new BlockStateModel(); - for (WeighedArrayList bmrList : resource.getModelResources()){ - BlockModelResource bmr = bmrList.get((int) Math.floor(MathUtils.hashToFloat(context.getPosition(), 23489756) * bmrList.size())); - - model.merge(fromModelResource(bmr)); + for (BlockModelResource.Element element : bmr.getModel().getElements()){ + model.merge(fromModelElementResource(element, bmr)); + } + + if (!bmr.getRotation().equals(Vector2i.ZERO)) { + model.translate(NEG_HALF_3F); + model.rotate(Quaternionf.fromAxesAnglesDeg( + -bmr.getRotation().getX(), + -bmr.getRotation().getY(), + 0 + )); + model.translate(HALF_3F); } return model; } - private BlockStateModel fromModelResource(BlockModelResource bmr) throws NoSuchTextureException { - BlockStateModel model = new BlockStateModel(); - - for (BlockModelElementResource bmer : bmr.getElements()){ - model.merge(fromModelElementResource(bmer)); - } - - model.translate(NEG_HALF_3F); - model.rotate(Quaternionf.fromAxesAnglesDeg( - -bmr.getXRot(), - -bmr.getYRot(), - 0 - )); - model.translate(HALF_3F); - - return model; - } - - private BlockStateModel fromModelElementResource(BlockModelElementResource bmer) throws NoSuchTextureException { + private BlockStateModel fromModelElementResource(BlockModelResource.Element bmer, TransformedBlockModelResource bmr) { BlockStateModel model = new BlockStateModel(); //create faces @@ -118,28 +103,31 @@ private BlockStateModel fromModelElementResource(BlockModelElementResource bmer) new Vector3f( max .getX(), max .getY(), max .getZ()), }; - createElementFace(model, bmer.getDownFace(), Direction.DOWN, c[0], c[2], c[3], c[1]); - createElementFace(model, bmer.getUpFace(), Direction.UP, c[5], c[7], c[6], c[4]); - createElementFace(model, bmer.getNorthFace(), Direction.NORTH, c[2], c[0], c[4], c[6]); - createElementFace(model, bmer.getSouthFace(), Direction.SOUTH, c[1], c[3], c[7], c[5]); - createElementFace(model, bmer.getWestFace(), Direction.WEST, c[0], c[1], c[5], c[4]); - createElementFace(model, bmer.getEastFace(), Direction.EAST, c[3], c[2], c[6], c[7]); + createElementFace(model, bmr, bmer, Direction.DOWN, c[0], c[2], c[3], c[1]); + createElementFace(model, bmr, bmer, Direction.UP, c[5], c[7], c[6], c[4]); + createElementFace(model, bmr, bmer, Direction.NORTH, c[2], c[0], c[4], c[6]); + createElementFace(model, bmr, bmer, Direction.SOUTH, c[1], c[3], c[7], c[5]); + createElementFace(model, bmr, bmer, Direction.WEST, c[0], c[1], c[5], c[4]); + createElementFace(model, bmr, bmer, Direction.EAST, c[3], c[2], c[6], c[7]); //rotate - if (bmer.isRotation()){ - Vector3f translation = bmer.getRotationOrigin(); + Rotation rotation = bmer.getRotation(); + if (rotation.getAngle() != 0f){ + Vector3f translation = rotation.getOrigin(); model.translate(translation.negate()); + Vector3f rotAxis = rotation.getAxis().toVector().toFloat(); + model.rotate(Quaternionf.fromAngleDegAxis( - bmer.getRotationAngle(), - bmer.getRotationAxis().toVector().toFloat() + rotation.getAngle(), + rotAxis )); - if (bmer.isRotationRescale()){ + if (rotation.isRescale()){ Vector3f scale = Vector3f.ONE - .sub(bmer.getRotationAxis().toVector().toFloat()) - .mul(Math.abs(TrigMath.sin(bmer.getRotationAngle() * TrigMath.DEG_TO_RAD))) + .sub(rotAxis) + .mul(Math.abs(TrigMath.sin(rotation.getAngle() * TrigMath.DEG_TO_RAD))) .mul(1 - (TrigMath.SQRT_OF_TWO - 1)) .add(Vector3f.ONE); model.transform(Matrix3f.createScaling(scale)); @@ -155,19 +143,20 @@ private BlockStateModel fromModelElementResource(BlockModelElementResource bmer) return model; } - private void createElementFace(BlockStateModel model, BlockModelElementFaceResource face, Direction faceDir, Vector3f c0, Vector3f c1, Vector3f c2, Vector3f c3) throws NoSuchTextureException { + private void createElementFace(BlockStateModel model, TransformedBlockModelResource modelResource, BlockModelResource.Element element, Direction faceDir, Vector3f c0, Vector3f c1, Vector3f c2, Vector3f c3) { + BlockModelResource.Element.Face face = element.getFaces().get(faceDir); + if (face == null) return; - BlockModelResource m = face.getElement().getModel(); //face culling - if (face.isCullface()){ - Block b = getRotationRelativeBlock(m, face.getCullface()); + if (face.getCullface() != null){ + Block b = getRotationRelativeBlock(modelResource, face.getCullface()); if (b.isCullingNeighborFaces()) return; } //light calculation - Block b = getRotationRelativeBlock(m, faceDir); - BlockContext bContext = context.getRelativeView(getRotationRelativeDirectionVector(m, faceDir.toVector().toFloat()).toInt()); + Block b = getRotationRelativeBlock(modelResource, faceDir); + BlockContext bContext = context.getRelativeView(getRotationRelativeDirectionVector(modelResource, faceDir.toVector().toFloat()).toInt()); float skyLight = b.getPassedSunLight(bContext); //filter out faces that are not skylighted @@ -186,12 +175,13 @@ private void createElementFace(BlockStateModel model, BlockModelElementFaceResou //UV-Lock counter-rotation int uvLockAngle = 0; - if (m.isUvLock()){ - Quaternionf rot = Quaternionf.fromAxesAnglesDeg(m.getXRot(), m.getYRot(), 0); + Vector2i rotation = modelResource.getRotation(); + if (modelResource.isUVLock()){ + Quaternionf rot = Quaternionf.fromAxesAnglesDeg(rotation.getX(), rotation.getY(), 0); uvLockAngle = (int) rot.getAxesAnglesDeg().dot(faceDir.toVector().toFloat()); //TODO: my math has stopped working, there has to be a more consistent solution - if (m.getXRot() >= 180 && m.getYRot() != 90 && m.getYRot() != 270) uvLockAngle += 180; + if (rotation.getX() >= 180 && rotation.getY() != 90 && rotation.getY() != 270) uvLockAngle += 180; } //create both triangles @@ -205,27 +195,25 @@ private void createElementFace(BlockStateModel model, BlockModelElementFaceResou uvs = rotateUVOuter(uvs, uvLockAngle); uvs = rotateUVInner(uvs, face.getRotation()); - String textureName = face.getResolvedTexture(); - if (textureName == null) throw new NoSuchTextureException("There is no Texture-Definition for a face: " + faceDir + " of block: " + resource.getBlock()); - - int textureId = resourcePack.getTextureProvider().getTextureIndex(textureName); + Texture texture = face.getTexture(); + int textureId = texture.getId(); Face f1 = new Face(c0, c1, c2, uvs[0], uvs[1], uvs[2], textureId); Face f2 = new Face(c0, c2, c3, uvs[0], uvs[2], uvs[3], textureId); //calculate ao double ao0 = 1d, ao1 = 1d, ao2 = 1d, ao3 = 1d; - if (renderSettings.getAmbientOcclusionStrenght() > 0f && m.isAmbientOcclusion()){ - ao0 = testAo(m, c0, faceDir); - ao1 = testAo(m, c1, faceDir); - ao2 = testAo(m, c2, faceDir); - ao3 = testAo(m, c3, faceDir); + if (renderSettings.getAmbientOcclusionStrenght() > 0f && modelResource.getModel().isAmbientOcclusion()){ + ao0 = testAo(modelResource, c0, faceDir); + ao1 = testAo(modelResource, c1, faceDir); + ao2 = testAo(modelResource, c2, faceDir); + ao3 = testAo(modelResource, c3, faceDir); } //tint the face Vector3f color = Vector3f.ONE; if (face.isTinted()){ - color = resourcePack.getBlockColorProvider().getBlockColor(context); + color = colorCalculator.getBlockColor(context); //TODO: cache this so we don't recalculate the tint color again for each face? } color = color.mul(light); @@ -251,50 +239,46 @@ private void createElementFace(BlockStateModel model, BlockModelElementFaceResou model.addFace(f2); //if is top face set model-color - Vector3f dir = getRotationRelativeDirectionVector(m, faceDir.toVector().toFloat()); + Vector3f dir = getRotationRelativeDirectionVector(modelResource, faceDir.toVector().toFloat()); - BlockModelElementResource bmer = face.getElement(); - if (bmer.isRotation()){ + if (element.getRotation().getAngle() > 0){ Quaternionf rot = Quaternionf.fromAngleDegAxis( - bmer.getRotationAngle(), - bmer.getRotationAxis().toVector().toFloat() + element.getRotation().getAngle(), + element.getRotation().getAxis().toVector().toFloat() ); dir = rot.rotate(dir); } float a = dir.getY(); if (a > 0){ - Texture t = resourcePack.getTextureProvider().getTexture(textureId); - if (t != null){ - Vector4f c = t.getColor(); - c = c.mul(color.toVector4(1f)); - c = new Vector4f(c.getX(), c.getY(), c.getZ(), c.getW() * a); - model.mergeMapColor(c); - } + Vector4f c = texture.getColor(); + c = c.mul(color.toVector4(1f)); + c = new Vector4f(c.getX(), c.getY(), c.getZ(), c.getW() * a); + model.mergeMapColor(c); } } - private Block getRotationRelativeBlock(BlockModelResource model, Direction direction){ + private Block getRotationRelativeBlock(TransformedBlockModelResource model, Direction direction){ return getRotationRelativeBlock(model, direction.toVector()); } - private Block getRotationRelativeBlock(BlockModelResource model, Vector3i direction){ + private Block getRotationRelativeBlock(TransformedBlockModelResource model, Vector3i direction){ Vector3i dir = getRotationRelativeDirectionVector(model, direction.toFloat()).round().toInt(); return context.getRelativeBlock(dir); } - private Vector3f getRotationRelativeDirectionVector(BlockModelResource model, Vector3f direction){ + private Vector3f getRotationRelativeDirectionVector(TransformedBlockModelResource model, Vector3f direction){ Quaternionf rot = Quaternionf.fromAxesAnglesDeg( - -model.getXRot(), - -model.getYRot(), + -model.getRotation().getX(), + -model.getRotation().getY(), 0 ); Vector3f dir = rot.rotate(direction); return dir; } - private double testAo(BlockModelResource model, Vector3f vertex, Direction dir){ + private double testAo(TransformedBlockModelResource model, Vector3f vertex, Direction dir){ int occluding = 0; int x = 0; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java new file mode 100644 index 00000000..89af92e7 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java @@ -0,0 +1,197 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * 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. + */ +package de.bluecolored.bluemap.core.resourcepack; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; + +import com.flowpowered.math.GenericMath; +import com.flowpowered.math.vector.Vector2f; +import com.flowpowered.math.vector.Vector2i; +import com.flowpowered.math.vector.Vector3f; + +import de.bluecolored.bluemap.core.render.context.BlockContext; +import de.bluecolored.bluemap.core.util.ConfigUtils; +import de.bluecolored.bluemap.core.util.MathUtils; +import de.bluecolored.bluemap.core.world.Biome; +import de.bluecolored.bluemap.core.world.Block; +import ninja.leaping.configurate.ConfigurationNode; +import ninja.leaping.configurate.gson.GsonConfigurationLoader; + +public class BlockColorCalculator { + + private BufferedImage foliageMap; + private BufferedImage grassMap; + + private Map> blockColorMap; + + public BlockColorCalculator(BufferedImage foliageMap, BufferedImage grassMap) { + this.foliageMap = foliageMap; + this.grassMap = grassMap; + + this.blockColorMap = new HashMap<>(); + } + + public void loadColorConfig(File configFile) throws IOException { + blockColorMap.clear(); + + ConfigurationNode colorConfig = GsonConfigurationLoader.builder() + .setFile(configFile) + .build() + .load(); + + for (Entry entry : colorConfig.getChildrenMap().entrySet()){ + String key = entry.getKey().toString(); + String value = entry.getValue().getString(); + + Function colorFunction; + switch (value) { + case "@foliage": + colorFunction = this::getFoliageAverageColor; + break; + case "@grass": + colorFunction = this::getGrassAverageColor; + break; + case "@water": + colorFunction = this::getWaterAverageColor; + break; + default: + final Vector3f color = MathUtils.color3FromInt(ConfigUtils.readInt(entry.getValue())); + colorFunction = context -> color; + break; + } + + blockColorMap.put(key, colorFunction); + } + } + + public Vector3f getBlockColor(BlockContext context){ + Block block = context.getRelativeBlock(0, 0, 0); + String blockId = block.getBlock().getFullId(); + + Function colorFunction = blockColorMap.get(blockId); + if (colorFunction == null) colorFunction = blockColorMap.get("default"); + if (colorFunction == null) colorFunction = this::getFoliageAverageColor; + + return colorFunction.apply(context); + } + + public Vector3f getWaterAverageColor(BlockContext context){ + Vector3f color = Vector3f.ZERO; + + for (int x = -1; x <= 1; x++){ + for (int z = -1; z <= 1; z++){ + color = color.add(context.getRelativeBlock(x, 0, z).getBiome().getWaterColor()); + } + } + + return color.div(9f); + } + + public Vector3f getFoliageAverageColor(BlockContext context){ + Vector3f color = Vector3f.ZERO; + + for (int x = -1; x <= 1; x++){ + for (int z = -1; z <= 1; z++){ + color = color.add(getFoliageColor(context.getRelativeBlock(x, 0, z))); + } + } + + return color.div(9f); + } + + public Vector3f getFoliageColor(Block block){ + int blocksAboveSeaLevel = Math.max(block.getPosition().getY() - block.getWorld().getSeaLevel(), 0); + return getFoliageColor(block.getBiome(), blocksAboveSeaLevel); + } + + public Vector3f getFoliageColor(Biome biome, int blocksAboveSeaLevel){ + Vector3f mapColor = getColorFromMap(biome, blocksAboveSeaLevel, foliageMap); + Vector3f overlayColor = biome.getOverlayFoliageColor().toVector3(); + float overlayAlpha = biome.getOverlayFoliageColor().getW(); + return mapColor.mul(1f - overlayAlpha).add(overlayColor.mul(overlayAlpha)); + } + + public Vector3f getGrassAverageColor(BlockContext context){ + Vector3f color = Vector3f.ZERO; + + for (int x = -1; x <= 1; x++){ + for (int z = -1; z <= 1; z++){ + color = color.add(getGrassColor(context.getRelativeBlock(x, 0, z))); + } + } + + return color.div(9f); + } + + public Vector3f getGrassColor(Block block){ + int blocksAboveSeaLevel = Math.max(block.getPosition().getY() - block.getWorld().getSeaLevel(), 0); + return getGrassColor(block.getBiome(), blocksAboveSeaLevel); + } + + public Vector3f getGrassColor(Biome biome, int blocksAboveSeaLevel){ + Vector3f mapColor = getColorFromMap(biome, blocksAboveSeaLevel, grassMap); + Vector3f overlayColor = biome.getOverlayGrassColor().toVector3(); + float overlayAlpha = biome.getOverlayGrassColor().getW(); + return mapColor.mul(1f - overlayAlpha).add(overlayColor.mul(overlayAlpha)); + } + + private Vector3f getColorFromMap(Biome biome, int blocksAboveSeaLevel, BufferedImage map){ + Vector2i pixel = getColorMapPosition(biome, blocksAboveSeaLevel).mul(map.getWidth(), map.getHeight()).floor().toInt(); + int cValue = map.getRGB(GenericMath.clamp(pixel.getX(), 0, map.getWidth() - 1), GenericMath.clamp(pixel.getY(), 0, map.getHeight() - 1)); + Color color = new Color(cValue, false); + return new Vector3f(color.getRed(), color.getGreen(), color.getBlue()).div(0xff); + + } + + private Vector2f getColorMapPosition(Biome biome, int blocksAboveSeaLevel){ + float adjTemp = (float) GenericMath.clamp(biome.getTemp() - (0.00166667 * blocksAboveSeaLevel), 0d, 1d); + float adjHumidity = (float) GenericMath.clamp(biome.getHumidity(), 0d, 1d) * adjTemp; + return new Vector2f(1 - adjTemp, 1 - adjHumidity); + } + + public BufferedImage getFoliageMap() { + return foliageMap; + } + + public void setFoliageMap(BufferedImage foliageMap) { + this.foliageMap = foliageMap; + } + + public BufferedImage getGrassMap() { + return grassMap; + } + + public void setGrassMap(BufferedImage grassMap) { + this.grassMap = grassMap; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorProvider.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorProvider.java deleted file mode 100644 index 9ba74f87..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorProvider.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * 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. - */ -package de.bluecolored.bluemap.core.resourcepack; - -import java.awt.Color; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.nio.file.Paths; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NoSuchElementException; -import java.util.concurrent.ConcurrentHashMap; - -import javax.imageio.ImageIO; - -import com.flowpowered.math.GenericMath; -import com.flowpowered.math.vector.Vector2f; -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3f; - -import de.bluecolored.bluemap.core.render.context.ExtendedBlockContext; -import de.bluecolored.bluemap.core.world.Block; -import ninja.leaping.configurate.ConfigurationNode; -import ninja.leaping.configurate.gson.GsonConfigurationLoader; - -public class BlockColorProvider { - - private BufferedImage foliageMap; - private BufferedImage grassMap; - private Map biomeInfos; - private Map blockColors; - - public BlockColorProvider(ResourcePack resourcePack) throws IOException, NoSuchResourceException { - - this.foliageMap = ImageIO.read(resourcePack.getResource(Paths.get("assets", "minecraft", "textures", "colormap", "foliage.png"))); - this.grassMap = ImageIO.read(resourcePack.getResource(Paths.get("assets", "minecraft", "textures", "colormap", "grass.png"))); - - - this.biomeInfos = new ConcurrentHashMap<>(); - GsonConfigurationLoader loader = GsonConfigurationLoader.builder() - .setURL(getClass().getResource("/biomes.json")) - .build(); - ConfigurationNode biomesConfig = loader.load(); - - for (Entry n : biomesConfig.getChildrenMap().entrySet()){ - String key = n.getKey().toString(); - BiomeInfo value = new BiomeInfo(); - value.humidity = n.getValue().getNode("humidity").getFloat(0.4f); - value.temp = n.getValue().getNode("temp").getFloat(0.8f); - value.watercolor = n.getValue().getNode("watercolor").getInt(4159204); - - biomeInfos.put(key, value); - } - - this.blockColors = new ConcurrentHashMap<>(); - loader = GsonConfigurationLoader.builder() - .setURL(getClass().getResource("/blockColors.json")) - .build(); - ConfigurationNode blockConfig = loader.load(); - - for (Entry n : blockConfig.getChildrenMap().entrySet()){ - String blockId = n.getKey().toString(); - String color = n.getValue().getString(); - blockColors.put(blockId, color); - } - - } - - public Vector3f getBlockColor(ExtendedBlockContext context){ - Block block = context.getRelativeBlock(0, 0, 0); - String blockId = block.getBlock().getId(); - - // water color - if (blockId.equals("water")) { - return getBiomeWaterAverageColor(context); - } - - String colorDef = blockColors.get(blockId); - if (colorDef == null) colorDef = blockColors.get("default"); - if (colorDef == null) colorDef = "#foliage"; - - // grass map - if (colorDef.equals("#grass")){ - return getBiomeGrassAverageColor(context); - } - - // foliage map - if (colorDef.equals("#foliage")){ - return getBiomeFoliageAverageColor(context); - } - - int cValue = Integer.parseInt(colorDef, 16); - return colorFromInt(cValue); - } - - public Vector3f getBiomeFoliageAverageColor(ExtendedBlockContext context){ - Vector3f color = Vector3f.ZERO; - - for (int x = -1; x <= 1; x++){ - for (int z = -1; z <= 1; z++){ - color = color.add(getBiomeFoliageColor(context.getRelativeBlock(x, 0, z))); - } - } - - return color.div(9f); - } - - private Vector3f getBiomeFoliageColor(Block block){ - Vector3f color = Vector3f.ONE; - - if (block.getBiome().contains("mesa")){ - return colorFromInt(0x9e814d); - } - - if (block.getBiome().contains("swamp")) { - return colorFromInt(0x6A7039); - } - - int blocksAboveSeaLevel = Math.max(block.getPosition().getY() - block.getWorld().getSeaLevel(), 0); - color = getFoliageColor(block.getBiome(), blocksAboveSeaLevel); - - //improvised to match the original better - if (block.getBiome().contains("roofed_forest")){ - color = color.mul(2f).add(colorFromInt(0x28340a)).div(3f); - } - - return color; - } - - public Vector3f getBiomeGrassAverageColor(ExtendedBlockContext context){ - Vector3f color = Vector3f.ZERO; - - for (int x = -1; x <= 1; x++){ - for (int z = -1; z <= 1; z++){ - color = color.add(getBiomeGrassColor(context.getRelativeBlock(x, 0, z))); - } - } - - return color.div(9f); - } - - private Vector3f getBiomeGrassColor(Block block){ - Vector3f color = Vector3f.ONE; - - if (block.getBiome().contains("mesa")){ - return colorFromInt(0x90814d); - } - - if (block.getBiome().contains("swamp")) { - return colorFromInt(0x6A7039); - } - - int blocksAboveSeaLevel = Math.max(block.getPosition().getY() - block.getWorld().getSeaLevel(), 0); - color = getGrassColor(block.getBiome(), blocksAboveSeaLevel); - - if (block.getBiome().contains("roofed_forest")){ - color = color.add(colorFromInt(0x28340a)).div(2f); - } - - return color; - } - - public Vector3f getBiomeWaterAverageColor(ExtendedBlockContext context){ - Vector3f color = Vector3f.ZERO; - - for (int x = -1; x <= 1; x++){ - for (int z = -1; z <= 1; z++){ - color = color.add(getBiomeWaterColor(context.getRelativeBlock(x, 0, z))); - } - } - - return color.div(9f); - } - - private Vector3f getBiomeWaterColor(Block block){ - return colorFromInt(biomeInfos.get(block.getBiome()).watercolor); - } - - private Vector3f colorFromInt(int cValue){ - Color c = new Color(cValue, false); - return new Vector3f(c.getRed(), c.getGreen(), c.getBlue()).div(0xff); - } - - private Vector3f getFoliageColor(String biomeId, int blocksAboveSeaLevel){ - return getColorFromMap(biomeId, blocksAboveSeaLevel, foliageMap); - } - - private Vector3f getGrassColor(String biomeId, int blocksAboveSeaLevel){ - return getColorFromMap(biomeId, blocksAboveSeaLevel, grassMap); - } - - private Vector3f getColorFromMap(String biomeId, int blocksAboveSeaLevel, BufferedImage map){ - Vector2i pixel = getColorMapPosition(biomeId, blocksAboveSeaLevel).mul(map.getWidth(), map.getHeight()).floor().toInt(); - int cValue = map.getRGB(GenericMath.clamp(pixel.getX(), 0, map.getWidth() - 1), GenericMath.clamp(pixel.getY(), 0, map.getHeight() - 1)); - Color color = new Color(cValue, false); - return new Vector3f(color.getRed(), color.getGreen(), color.getBlue()).div(0xff); - - } - - private Vector2f getColorMapPosition(String biomeId, int blocksAboveSeaLevel){ - BiomeInfo bi = biomeInfos.get(biomeId); - - if (bi == null){ - throw new NoSuchElementException("No biome found with id: " + biomeId); - } - - float adjTemp = (float) GenericMath.clamp(bi.temp - (0.00166667 * blocksAboveSeaLevel), 0d, 1d); - float adjHumidity = (float) GenericMath.clamp(bi.humidity, 0d, 1d) * adjTemp; - return new Vector2f(1 - adjTemp, 1 - adjHumidity); - } - - class BiomeInfo { - float humidity; - float temp; - int watercolor; - } -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelElementFaceResource.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelElementFaceResource.java deleted file mode 100644 index 13b323a1..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelElementFaceResource.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * 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. - */ -package de.bluecolored.bluemap.core.resourcepack; - -import com.flowpowered.math.vector.Vector3f; -import com.flowpowered.math.vector.Vector4f; - -import de.bluecolored.bluemap.core.util.ConfigUtils; -import de.bluecolored.bluemap.core.util.Direction; -import ninja.leaping.configurate.ConfigurationNode; - -public class BlockModelElementFaceResource { - - private BlockModelElementResource element; - - private Vector4f uv; - private String texture; - private String resolvedTexture; - private Direction cullface; - private int rotation; - private int tintIndex; - - protected BlockModelElementFaceResource(BlockModelElementResource element, ConfigurationNode declaration) throws InvalidResourceDeclarationException { - this.element = element; - - try { - this.uv = getDefaultUV(declaration.getKey().toString(), element.getFrom(), element.getTo()); - - ConfigurationNode uv = declaration.getNode("uv"); - if (!uv.isVirtual()) this.uv = ConfigUtils.readVector4f(declaration.getNode("uv")); - - this.texture = declaration.getNode("texture").getString(); - this.resolvedTexture = null; - - this.cullface = null; - ConfigurationNode cf = declaration.getNode("cullface"); - if (!cf.isVirtual()) this.cullface = Direction.fromString(cf.getString()); - - this.rotation = declaration.getNode("rotation").getInt(0); - this.tintIndex = declaration.getNode("tintindex").getInt(-1); - - } catch (NullPointerException | IllegalArgumentException e){ - throw new InvalidResourceDeclarationException(e); - } - } - - public Vector4f getDefaultUV(String faceId, Vector3f from, Vector3f to){ - switch (faceId){ - - case "down" : - case "up" : - return new Vector4f( - from.getX(), from.getZ(), - to.getX(), to.getZ() - ); - - case "north" : - case "south" : - return new Vector4f( - from.getX(), from.getY(), - to.getX(), to.getY() - ); - - case "west" : - case "east" : - return new Vector4f( - from.getZ(), from.getY(), - to.getZ(), to.getY() - ); - - default : - return new Vector4f( - 0, 0, - 16, 16 - ); - - } - } - - public BlockModelElementResource getElement(){ - return element; - } - - public Vector4f getUv() { - return uv; - } - - public String getTexture() { - return texture; - } - - public String getResolvedTexture() { - if (resolvedTexture == null){ - resolvedTexture = getElement().getModel().resolveTexture(getTexture()); - } - - return resolvedTexture; - } - - public boolean isCullface() { - return cullface != null; - } - - public Direction getCullface() { - return cullface; - } - - public int getRotation() { - return rotation; - } - - public boolean isTinted(){ - return tintIndex >= 0; - } - - public int getTintIndex() { - return tintIndex; - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelElementResource.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelElementResource.java deleted file mode 100644 index baf6281c..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelElementResource.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * 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. - */ -package de.bluecolored.bluemap.core.resourcepack; - -import com.flowpowered.math.vector.Vector3f; - -import de.bluecolored.bluemap.core.util.Axis; -import de.bluecolored.bluemap.core.util.ConfigUtils; -import ninja.leaping.configurate.ConfigurationNode; - -public class BlockModelElementResource { - - private BlockModelResource model; - - private Vector3f from, to; - - private Vector3f rotOrigin; - private Axis rotAxis; - private float rotAngle; - private boolean rotRescale; - - private boolean shade; - - private BlockModelElementFaceResource down, up, north, south, west, east; - - protected BlockModelElementResource(BlockModelResource model, ConfigurationNode declaration) throws InvalidResourceDeclarationException { - this.model = model; - - try { - this.from = ConfigUtils.readVector3f(declaration.getNode("from")); - this.to = ConfigUtils.readVector3f(declaration.getNode("to")); - - this.rotAngle = 0f; - ConfigurationNode rotation = declaration.getNode("rotation"); - if (!rotation.isVirtual()){ - this.rotOrigin = ConfigUtils.readVector3f(rotation.getNode("origin")); - this.rotAxis = Axis.fromString(rotation.getNode("axis").getString()); - this.rotAngle = rotation.getNode("angle").getFloat(); - this.rotRescale = rotation.getNode("rescale").getBoolean(false); - } - - this.shade = declaration.getNode("shade").getBoolean(true); - - ConfigurationNode faces = declaration.getNode("faces"); - this.down = loadFace(faces.getNode("down")); - this.up = loadFace(faces.getNode("up")); - this.north = loadFace(faces.getNode("north")); - this.south = loadFace(faces.getNode("south")); - this.west = loadFace(faces.getNode("west")); - this.east = loadFace(faces.getNode("east")); - - } catch (NullPointerException e){ - throw new InvalidResourceDeclarationException(e); - } - } - - private BlockModelElementFaceResource loadFace(ConfigurationNode faceNode) throws InvalidResourceDeclarationException { - if (faceNode.isVirtual()) return null; - return new BlockModelElementFaceResource(this, faceNode); - } - - public BlockModelResource getModel(){ - return model; - } - - public Vector3f getFrom() { - return from; - } - - public Vector3f getTo() { - return to; - } - - public boolean isRotation(){ - return rotAngle != 0f; - } - - public Vector3f getRotationOrigin() { - return rotOrigin; - } - - public Axis getRotationAxis() { - return rotAxis; - } - - public float getRotationAngle() { - return rotAngle; - } - - public boolean isRotationRescale() { - return rotRescale; - } - - public boolean isShade() { - return shade; - } - - public BlockModelElementFaceResource getDownFace() { - return down; - } - - public BlockModelElementFaceResource getUpFace() { - return up; - } - - public BlockModelElementFaceResource getNorthFace() { - return north; - } - - public BlockModelElementFaceResource getSouthFace() { - return south; - } - - public BlockModelElementFaceResource getWestFace() { - return west; - } - - public BlockModelElementFaceResource getEastFace() { - return east; - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelResource.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelResource.java index 3dc7a1d0..6673ff0d 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelResource.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelResource.java @@ -25,118 +25,358 @@ package de.bluecolored.bluemap.core.resourcepack; import java.io.BufferedReader; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.Vector; -import java.util.concurrent.ConcurrentHashMap; +import java.util.NoSuchElementException; +import com.flowpowered.math.vector.Vector3f; +import com.flowpowered.math.vector.Vector4f; + +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.resourcepack.BlockModelResource.Element.Face; +import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess; +import de.bluecolored.bluemap.core.util.Axis; +import de.bluecolored.bluemap.core.util.Direction; import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.gson.GsonConfigurationLoader; public class BlockModelResource { - private BlockStateResource blockState; + private ModelType modelType = ModelType.NORMAL; - private int xRot, yRot; - private boolean uvLock; - private boolean ambientOcclusion; - private Collection elements; - private Map textures; + private boolean ambientOcclusion = true; + private Collection elements = new ArrayList<>(); + private Map textures = new HashMap<>(); - protected BlockModelResource(BlockStateResource blockState, ConfigurationNode declaration, ResourcePack resources) throws InvalidResourceDeclarationException { - this.blockState = blockState; - - this.xRot = declaration.getNode("x").getInt(0); - this.yRot = declaration.getNode("y").getInt(0); - this.uvLock = declaration.getNode("uvlock").getBoolean(false); - this.ambientOcclusion = true; - this.elements = new Vector<>(); - this.textures = new ConcurrentHashMap<>(); - - try { - loadModelResource(declaration.getNode("model").getString(), resources); - } catch (IOException e) { - throw new InvalidResourceDeclarationException("Model not found: " + declaration.getNode("model").getString(), e); - } + private BlockModelResource() {} + + public ModelType getType() { + return modelType; } - private void loadModelResource(String modelId, ResourcePack resources) throws IOException, InvalidResourceDeclarationException { - Path resourcePath = Paths.get("assets", "minecraft", "models", modelId + ".json"); - - ConfigurationNode data = GsonConfigurationLoader.builder() - .setSource(() -> new BufferedReader(new InputStreamReader(resources.getResource(resourcePath), StandardCharsets.UTF_8))) - .build() - .load(); - - //load parent first - ConfigurationNode parent = data.getNode("parent"); - if (!parent.isVirtual()){ - loadModelResource(parent.getString(), resources); - } - - for (Entry texture : data.getNode("textures").getChildrenMap().entrySet()){ - String key = texture.getKey().toString(); - String value = texture.getValue().getString(); - textures.put(key, value); - } - - ambientOcclusion = data.getNode("ambientocclusion").getBoolean(ambientOcclusion); - - if (!data.getNode("elements").isVirtual()){ - elements.clear(); - for (ConfigurationNode e : data.getNode("elements").getChildrenList()){ - elements.add(new BlockModelElementResource(this, e)); - } - } - } - - public BlockStateResource getBlockState(){ - return blockState; - } - - public int getXRot() { - return xRot; - } - - public int getYRot() { - return yRot; - } - - public boolean isUvLock() { - return uvLock; - } - public boolean isAmbientOcclusion() { return ambientOcclusion; } - public Collection getElements() { - return Collections.unmodifiableCollection(elements); + public Collection getElements() { + return elements; } - public String resolveTexture(String key){ - if (key == null) return null; - if (!key.startsWith("#")) return key; - String texture = textures.get(key.substring(1)); - if (texture == null) return key; - return resolveTexture(texture); + public Texture getTexture(String key) { + return textures.get(key); } - - public Collection getAllTextureIds(){ - List list = new ArrayList<>(); - for (String tex : textures.values()){ - if (!tex.startsWith("#")) list.add(tex); + + public class Element { + + private Vector3f from = Vector3f.ZERO, to = new Vector3f(16f, 16f, 16f); + private Rotation rotation = new Rotation(); + private boolean shade = true; + private EnumMap faces = new EnumMap<>(Direction.class); + + private Element() {} + + public Vector4f getDefaultUV(Direction face) { + switch (face){ + + case DOWN : + case UP : + return new Vector4f( + from.getX(), from.getZ(), + to.getX(), to.getZ() + ); + + case NORTH : + case SOUTH : + return new Vector4f( + from.getX(), from.getY(), + to.getX(), to.getY() + ); + + case WEST : + case EAST : + return new Vector4f( + from.getZ(), from.getY(), + to.getZ(), to.getY() + ); + + default : + return new Vector4f( + 0, 0, + 16, 16 + ); + + } } - return list; + + public BlockModelResource getModel() { + return BlockModelResource.this; + } + + public Vector3f getFrom() { + return from; + } + + public Vector3f getTo() { + return to; + } + + public Rotation getRotation() { + return rotation; + } + + public boolean isShade() { + return shade; + } + + public EnumMap getFaces() { + return faces; + } + + public class Face { + + private Vector4f uv; + private Texture texture; + private Direction cullface; + private int rotation = 0; + private boolean tinted = false; + + private Face(Direction dir) { + uv = getDefaultUV(dir); + } + + public Element getElement() { + return Element.this; + } + + public Vector4f getUv() { + return uv; + } + + public Texture getTexture() { + return texture; + } + + public Direction getCullface() { + return cullface; + } + + public int getRotation() { + return rotation; + } + + public boolean isTinted() { + return tinted; + } + + } + + public class Rotation { + + private Vector3f origin = new Vector3f(8, 8, 8); + private Axis axis = Axis.Y; + private float angle = 0; + private boolean rescale = false; + + private Rotation() {} + + public Vector3f getOrigin() { + return origin; + } + + public Axis getAxis() { + return axis; + } + + public float getAngle() { + return angle; + } + + public boolean isRescale() { + return rescale; + } + + } + + } + + public static Builder builder(FileAccess sourcesAccess, ResourcePack resourcePack) { + return new Builder(sourcesAccess, resourcePack); + } + + public static class Builder { + + private FileAccess sourcesAccess; + private ResourcePack resourcePack; + + private HashMap textures; + + private Builder(FileAccess sourcesAccess, ResourcePack resourcePack) { + this.sourcesAccess = sourcesAccess; + this.resourcePack = resourcePack; + + this.textures = new HashMap<>(); + } + + public synchronized BlockModelResource build(String modelPath) throws IOException, ParseResourceException { + textures.clear(); + return buildNoReset(modelPath, true, modelPath); + } + + private BlockModelResource buildNoReset(String modelPath, boolean renderElements, String topModelPath) throws IOException, ParseResourceException { + BlockModelResource blockModel = new BlockModelResource(); + ConfigurationNode config = GsonConfigurationLoader.builder() + .setSource(() -> new BufferedReader(new InputStreamReader(sourcesAccess.readFile(modelPath), StandardCharsets.UTF_8))) + .build() + .load(); + + for (Entry entry : config.getNode("textures").getChildrenMap().entrySet()) { + textures.putIfAbsent(entry.getKey().toString(), entry.getValue().getString(null)); + } + + String parentPath = config.getNode("parent").getString(); + if (parentPath != null) { + if (parentPath.startsWith("builtin")) { + switch (parentPath) { + case "builtin/liquid": + blockModel.modelType = ModelType.LIQUID; + break; + } + } else { + try { + parentPath = ResourcePack.namespacedToAbsoluteResourcePath(parentPath, "models") + ".json"; + blockModel = this.buildNoReset(parentPath, config.getNode("elements").isVirtual(), topModelPath); + } catch (IOException ex) { + Logger.global.logWarning("Failed to load parent model " + parentPath + " of model " + topModelPath + ": " + ex); + } + } + } + + if (renderElements) { + for (ConfigurationNode elementNode : config.getNode("elements").getChildrenList()) { + try { + blockModel.elements.add(buildElement(blockModel, elementNode, topModelPath)); + } catch (ParseResourceException ex) { + Logger.global.logWarning("Failed to parse element of model " + modelPath + " (" + topModelPath + "): " + ex); + } + } + } + + for (String key : textures.keySet()) { + try { + blockModel.textures.put(key, getTexture("#" + key)); + } catch (NoSuchElementException | FileNotFoundException ex) { + Logger.global.logDebug("Failed to map Texture key '" + key + "': " + ex); + } + } + + return blockModel; + } + + private Element buildElement(BlockModelResource model, ConfigurationNode node, String topModelPath) throws ParseResourceException { + Element element = model.new Element(); + + element.from = readVector3f(node.getNode("from")); + element.to = readVector3f(node.getNode("to")); + + element.shade = node.getNode("shade").getBoolean(false); + + if (!node.getNode("rotation").isVirtual()) { + element.rotation.angle = node.getNode("rotation", "angle").getFloat(0); + element.rotation.axis = Axis.fromString(node.getNode("rotation", "axis").getString("x")); + if (!node.getNode("rotation", "origin").isVirtual()) element.rotation.origin = readVector3f(node.getNode("rotation", "origin")); + element.rotation.rescale = node.getNode("rotation", "rescale").getBoolean(false); + } + + for (Direction direction : Direction.values()) { + ConfigurationNode faceNode = node.getNode("faces", direction.name().toLowerCase()); + if (!faceNode.isVirtual()) { + try { + Face face = buildFace(element, direction, faceNode); + element.faces.put(direction, face); + } catch (ParseResourceException | IOException ex) { + Logger.global.logDebug("Failed to parse an " + direction + " face for the model " + topModelPath + "! " + ex); + } + } + } + + return element; + } + + private Face buildFace(Element element, Direction direction, ConfigurationNode node) throws ParseResourceException, IOException { + try { + Face face = element.new Face(direction); + + if (!node.getNode("uv").isVirtual()) face.uv = readVector4f(node.getNode("uv")); + face.texture = getTexture(node.getNode("texture").getString()); + face.tinted = node.getNode("tintindex").getInt(-1) >= 0; + face.rotation = node.getNode("rotation").getInt(0); + + if (!node.getNode("cullface").isVirtual()) { + String dirString = node.getNode("cullface").getString(); + if (dirString.equals("bottom")) dirString = "down"; + if (dirString.equals("top")) dirString = "up"; + face.cullface = Direction.fromString(dirString); + } + + return face; + } catch (FileNotFoundException ex) { + throw new ParseResourceException("There is no texture with the path: " + node.getNode("texture").getString(), ex); + } catch (NoSuchElementException ex) { + throw new ParseResourceException("Texture key '" + node.getNode("texture").getString() + "' has no texture assigned!", ex); + } + } + + private Vector3f readVector3f(ConfigurationNode node) throws ParseResourceException { + List nodeList = node.getChildrenList(); + if (nodeList.size() < 3) throw new ParseResourceException("Failed to load Vector3: Not enough values in list-node!"); + + return new Vector3f( + nodeList.get(0).getFloat(0), + nodeList.get(1).getFloat(0), + nodeList.get(2).getFloat(0) + ); + } + + private Vector4f readVector4f(ConfigurationNode node) throws ParseResourceException { + List nodeList = node.getChildrenList(); + if (nodeList.size() < 4) throw new ParseResourceException("Failed to load Vector4: Not enough values in list-node!"); + + return new Vector4f( + nodeList.get(0).getFloat(0), + nodeList.get(1).getFloat(0), + nodeList.get(2).getFloat(0), + nodeList.get(3).getFloat(0) + ); + } + + private Texture getTexture(String key) throws NoSuchElementException, FileNotFoundException, IOException { + if (key.charAt(0) == '#') { + String value = textures.get(key.substring(1)); + if (value == null) throw new NoSuchElementException("There is no texture defined for the key " + key); + return getTexture(value); + } + + String path = ResourcePack.namespacedToAbsoluteResourcePath(key, "textures") + ".png"; + + Texture texture; + try { + texture = resourcePack.textures.get(path); + } catch (NoSuchElementException ex) { + texture = resourcePack.textures.loadTexture(sourcesAccess, path); + } + + return texture; + } + } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockStateResource.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockStateResource.java index ca8803c1..52f97c9c 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockStateResource.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockStateResource.java @@ -28,127 +28,231 @@ import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Map.Entry; -import java.util.Vector; -import com.google.common.base.Preconditions; +import org.apache.commons.lang3.StringUtils; -import de.bluecolored.bluemap.core.util.WeighedArrayList; +import com.flowpowered.math.vector.Vector2i; +import com.flowpowered.math.vector.Vector3i; + +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess; +import de.bluecolored.bluemap.core.util.MathUtils; import de.bluecolored.bluemap.core.world.BlockState; import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.gson.GsonConfigurationLoader; public class BlockStateResource { - private BlockState block; - private Collection> modelResources; + + private List variants = new ArrayList<>(); + private Collection multipart = new ArrayList<>(); - protected BlockStateResource(BlockState block, ResourcePack resources) throws NoSuchResourceException, InvalidResourceDeclarationException { - this.block = Preconditions.checkNotNull(block); - this.modelResources = new Vector<>(); + private BlockStateResource() {} + + public Collection getModels(BlockState blockState){ + return getModels(blockState, Vector3i.ZERO); + } + + public Collection getModels(BlockState blockState, Vector3i pos){ + Collection models = new ArrayList<>(); + for (Variant variant : variants) { + if (variant.condition.matches(blockState)) { + models.add(variant.getModel(pos)); + return models; + } + } - try { - ConfigurationNode data = GsonConfigurationLoader.builder() - .setSource(() -> new BufferedReader(new InputStreamReader(resources.getResource(getResourcePath()), StandardCharsets.UTF_8))) + for (Variant variant : multipart) { + if (variant.condition.matches(blockState)) { + models.add(variant.getModel(pos)); + } + } + + return models; + } + + private class Variant { + + private PropertyCondition condition = PropertyCondition.all(); + private Collection> models = new ArrayList<>(); + + private double totalWeight; + + private Variant() {} + + public TransformedBlockModelResource getModel(Vector3i pos) { + double selection = MathUtils.hashToFloat(pos, 827364) * totalWeight; //random based on position + for (Weighted w : models) { + selection -= w.weight; + if (selection < 0) return w.value; + } + + throw new RuntimeException("This line should never be reached!"); + } + + public void updateTotalWeight() { + totalWeight = 0d; + for (Weighted w : models) { + totalWeight += w.weight; + } + } + + } + + private static class Weighted { + + private T value; + private double weight; + + public Weighted(T value, double weight) { + this.value = value; + this.weight = weight; + } + + } + + public static Builder builder(FileAccess sourcesAccess, ResourcePack resourcePack) { + return new Builder(sourcesAccess, resourcePack); + } + + public static class Builder { + private final FileAccess sourcesAccess; + private final ResourcePack resourcePack; + + private Builder(FileAccess sourcesAccess, ResourcePack resourcePack) { + this.sourcesAccess = sourcesAccess; + this.resourcePack = resourcePack; + } + + public BlockStateResource build(String blockstateFile) throws IOException { + BlockStateResource blockState = new BlockStateResource(); + ConfigurationNode config = GsonConfigurationLoader.builder() + .setSource(() -> new BufferedReader(new InputStreamReader(sourcesAccess.readFile(blockstateFile), StandardCharsets.UTF_8))) .build() .load(); - load(data, resources); - } catch (IOException e) { - throw new NoSuchResourceException("There is no definition for resource-id: " + block.getId(), e); - } catch (NullPointerException e){ - throw new InvalidResourceDeclarationException(e); - } - - this.modelResources = Collections.unmodifiableCollection(this.modelResources); - } - - private void load(ConfigurationNode data, ResourcePack resources) throws InvalidResourceDeclarationException { - - //load variants - ConfigurationNode variants = data.getNode("variants"); - for (Entry e : variants.getChildrenMap().entrySet()){ - if (getBlock().checkVariantCondition(e.getKey().toString())){ - addModelResource(e.getValue(), resources); - break; - } - } - - //load multipart - ConfigurationNode multipart = data.getNode("multipart"); - for (ConfigurationNode part : multipart.getChildrenList()){ - - ConfigurationNode when = part.getNode("when"); - if (when.isVirtual() || checkMultipartCondition(when)){ - addModelResource(part.getNode("apply"), resources); - } - } - - } - - private void addModelResource(ConfigurationNode n, ResourcePack resources) throws InvalidResourceDeclarationException { - WeighedArrayList models = new WeighedArrayList<>(); - - if (n.hasListChildren()){ - - //if it is a weighted list of alternative models, select one by random and weight - List cList = n.getChildrenList(); - for (ConfigurationNode c : cList){ - int weight = c.getNode("weight").getInt(1); - models.add(new BlockModelResource(this, c, resources), weight); + //create variants + for (Entry entry : config.getNode("variants").getChildrenMap().entrySet()) { + String conditionString = entry.getKey().toString(); + ConfigurationNode transformedModelNode = entry.getValue(); + + Variant variant = blockState.new Variant(); + variant.condition = parseConditionString(conditionString); + variant.models = loadModels(transformedModelNode, blockstateFile); + + variant.updateTotalWeight(); + + blockState.variants.add(variant); } - } else { - models.add(new BlockModelResource(this, n, resources)); - } - - modelResources.add(models); - } - - private boolean checkMultipartCondition(ConfigurationNode when){ - ConfigurationNode or = when.getNode("OR"); - if (!or.isVirtual()){ - for (ConfigurationNode condition : or.getChildrenList()){ - if (checkMultipartCondition(condition)) return true; + //create multipart + for (ConfigurationNode partNode : config.getNode("multipart").getChildrenList()) { + Variant variant = blockState.new Variant(); + ConfigurationNode whenNode = partNode.getNode("when"); + if (!whenNode.isVirtual()) { + variant.condition = parseCondition(whenNode); + } + variant.models = loadModels(partNode.getNode("apply"), blockstateFile); + + variant.updateTotalWeight(); + + blockState.multipart.add(variant); } - return false; + return blockState; } - Map blockProperties = getBlock().getProperties(); - for (Entry e : when.getChildrenMap().entrySet()){ - String key = e.getKey().toString(); - String[] values = e.getValue().getString().split("\\|"); + private Collection> loadModels(ConfigurationNode node, String blockstateFile) { + Collection> models = new ArrayList<>(); - boolean found = false; - for (String value : values){ - if (value.equals(blockProperties.get(key))){ - found = true; - break; + if (node.hasListChildren()) { + for (ConfigurationNode modelNode : node.getChildrenList()) { + try { + models.add(loadModel(modelNode)); + } catch (ParseResourceException ex) { + Logger.global.logWarning("Failed to load a model trying to parse " + blockstateFile + ": " + ex); + } + } + } else if (node.hasMapChildren()) { + try { + models.add(loadModel(node)); + } catch (ParseResourceException ex) { + Logger.global.logWarning("Failed to load a model trying to parse " + blockstateFile + ": " + ex); } } - if (!found) return false; + return models; } - return true; - } + private Weighted loadModel(ConfigurationNode node) throws ParseResourceException { + String modelPath = node.getNode("model").getString(); + if (modelPath == null) throw new ParseResourceException("No model defined!"); + + modelPath = ResourcePack.namespacedToAbsoluteResourcePath(modelPath, "models") + ".json"; + + BlockModelResource model = resourcePack.blockModelResources.get(modelPath); + if (model == null) { + try { + model = BlockModelResource.builder(sourcesAccess, resourcePack).build(modelPath); + } catch (IOException e) { + throw new ParseResourceException("Failed to load model " + modelPath, e); + } + + resourcePack.blockModelResources.put(modelPath, model); + } + + Vector2i rotation = new Vector2i( + node.getNode("x").getInt(0), + node.getNode("y").getInt(0) + ); + boolean uvLock = node.getNode("uvlock").getBoolean(false); + + TransformedBlockModelResource transformedModel = new TransformedBlockModelResource(rotation, uvLock, model); + return new Weighted(transformedModel, node.getNode("weight").getDouble(1d)); + } - public BlockState getBlock() { - return block; + private PropertyCondition parseCondition(ConfigurationNode conditionNode) { + List andConditions = new ArrayList<>(); + for (Entry entry : conditionNode.getChildrenMap().entrySet()) { + String key = entry.getKey().toString(); + if (key.equals("OR")) { + List orConditions = new ArrayList<>(); + for (ConfigurationNode orConditionNode : entry.getValue().getChildrenList()) { + orConditions.add(parseCondition(orConditionNode)); + } + andConditions.add(PropertyCondition.or(orConditions.toArray(new PropertyCondition[orConditions.size()]))); + } else { + String[] values = StringUtils.split(entry.getValue().getString(""), '|'); + andConditions.add(PropertyCondition.property(key, values)); + } + } + + return PropertyCondition.and(andConditions.toArray(new PropertyCondition[andConditions.size()])); + } + + private PropertyCondition parseConditionString(String conditionString) { + List conditions = new ArrayList<>(); + if (!conditionString.isEmpty() && !conditionString.equals("default")) { + String[] conditionSplit = StringUtils.split(conditionString, ','); + for (String element : conditionSplit) { + String[] keyval = StringUtils.split(element, "=", 2); //TODO what if it is wrong formatted? + conditions.add(PropertyCondition.property(keyval[0], keyval[1])); + } + } + + PropertyCondition condition; + if (conditions.isEmpty()) { + condition = PropertyCondition.all(); + } else if (conditions.size() == 1) { + condition = conditions.get(0); + } else { + condition = PropertyCondition.and(conditions.toArray(new PropertyCondition[conditions.size()])); + } + + return condition; + } } - - public Collection> getModelResources(){ - return modelResources; - } - - private Path getResourcePath(){ - return Paths.get("assets", block.getNamespace(), "blockstates", block.getId() + ".json"); - } - } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/InvalidResourceDeclarationException.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ModelType.java similarity index 75% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/InvalidResourceDeclarationException.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ModelType.java index c9bb0a86..26a13a94 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/InvalidResourceDeclarationException.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ModelType.java @@ -24,20 +24,9 @@ */ package de.bluecolored.bluemap.core.resourcepack; -public class InvalidResourceDeclarationException extends Exception { - private static final long serialVersionUID = 0L; +public enum ModelType { - public InvalidResourceDeclarationException() {} + NORMAL, + LIQUID; - public InvalidResourceDeclarationException(Throwable e) { - super(e); - } - - public InvalidResourceDeclarationException(String message){ - super(message); - } - - public InvalidResourceDeclarationException(String message, Throwable e) { - super(message, e); - } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/NoSuchResourceException.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/NoSuchResourceException.java index f6fc55ef..549558cd 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/NoSuchResourceException.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/NoSuchResourceException.java @@ -25,19 +25,18 @@ package de.bluecolored.bluemap.core.resourcepack; public class NoSuchResourceException extends Exception { - private static final long serialVersionUID = 0L; + private static final long serialVersionUID = -8545428385061010089L; - public NoSuchResourceException() {} - - public NoSuchResourceException(Throwable e) { - super(e); + public NoSuchResourceException() { + super(); } - public NoSuchResourceException(String message){ + public NoSuchResourceException(String message) { super(message); } - public NoSuchResourceException(String message, Throwable e) { - super(message, e); + public NoSuchResourceException(String message, Throwable cause) { + super(message, cause); } + } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/NoSuchTextureException.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ParseResourceException.java similarity index 80% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/NoSuchTextureException.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ParseResourceException.java index db69169d..3a1b4cf8 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/NoSuchTextureException.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ParseResourceException.java @@ -24,20 +24,19 @@ */ package de.bluecolored.bluemap.core.resourcepack; -public class NoSuchTextureException extends Exception { - private static final long serialVersionUID = 0L; +public class ParseResourceException extends Exception { + private static final long serialVersionUID = -2857915193389089307L; - public NoSuchTextureException() {} - - public NoSuchTextureException(Throwable e) { - super(e); + public ParseResourceException() { + super(); } - public NoSuchTextureException(String message){ + public ParseResourceException(String message) { super(message); } - public NoSuchTextureException(String message, Throwable e) { - super(message, e); + public ParseResourceException(String message, Throwable cause) { + super(message, cause); } + } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/PropertyCondition.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/PropertyCondition.java new file mode 100644 index 00000000..e4f3c8f9 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/PropertyCondition.java @@ -0,0 +1,137 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * 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. + */ +package de.bluecolored.bluemap.core.resourcepack; + +import com.google.common.base.Preconditions; + +import de.bluecolored.bluemap.core.world.BlockState; + +@FunctionalInterface +public interface PropertyCondition { + + static final PropertyCondition MATCH_ALL = state -> true; + static final PropertyCondition MATCH_NONE = state -> false; + + boolean matches(BlockState state); + + public class Property implements PropertyCondition { + + private String key; + private String value; + + private Property (String key, String value) { + this.key = key.toLowerCase(); + this.value = value.toLowerCase(); + } + + @Override + public boolean matches(BlockState state) { + String value = state.getProperties().get(this.key); + if (value == null) return false; + return value.equals(this.value); + } + + } + + class And implements PropertyCondition { + + private PropertyCondition[] conditions; + + private And (PropertyCondition... conditions) { + Preconditions.checkArgument(conditions.length > 0, "Must be at least one condition!"); + + this.conditions = conditions; + } + + @Override + public boolean matches(BlockState state) { + for (PropertyCondition condition : conditions) { + if (!condition.matches(state)) return false; + } + return true; + } + + } + + class Or implements PropertyCondition { + + private PropertyCondition[] conditions; + + private Or (PropertyCondition... conditions) { + Preconditions.checkArgument(conditions.length > 0, "Must be at least one condition!"); + + this.conditions = conditions; + } + + @Override + public boolean matches(BlockState state) { + for (PropertyCondition condition : conditions) { + if (condition.matches(state)) return true; + } + return false; + } + + } + + static PropertyCondition all() { + return MATCH_ALL; + } + + static PropertyCondition none() { + return MATCH_NONE; + } + + static PropertyCondition and(PropertyCondition... conditions) { + Preconditions.checkArgument(conditions.length > 0, "Must be at least one condition!"); + + return new And(conditions); + } + + static PropertyCondition or(PropertyCondition... conditions) { + Preconditions.checkArgument(conditions.length > 0, "Must be at least one condition!"); + + return new Or(conditions); + } + + static PropertyCondition property(String key, String value) { + return new Property(key, value); + } + + static PropertyCondition property(String key, String... possibleValues) { + Preconditions.checkArgument(possibleValues.length > 0, "Must be at least one value!"); + + if (possibleValues.length == 1) { + return property(key, possibleValues[0]); + } + + PropertyCondition[] conditions = new PropertyCondition[possibleValues.length]; + for (int i = 0; i < possibleValues.length; i++) { + conditions[i] = property(key, possibleValues[i]); + } + + return or(conditions); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ResourcePack.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ResourcePack.java index 488174dc..8a9d57ce 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ResourcePack.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ResourcePack.java @@ -24,174 +24,175 @@ */ package de.bluecolored.bluemap.core.resourcepack; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Collections; -import java.util.Enumeration; +import java.util.Collection; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; +import javax.imageio.ImageIO; import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.util.FileUtils; +import de.bluecolored.bluemap.core.resourcepack.BlockStateResource.Builder; +import de.bluecolored.bluemap.core.resourcepack.fileaccess.CombinedFileAccess; +import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess; import de.bluecolored.bluemap.core.world.BlockState; +/** + * Represents all resources (BlockStates / BlockModels and Textures) that are loaded and used to generate map-models. + */ public class ResourcePack { public static final String MINECRAFT_CLIENT_VERSION = "1.14.4"; public static final String MINECRAFT_CLIENT_URL = "https://launcher.mojang.com/v1/objects/8c325a0c5bd674dd747d6ebaa4c791fd363ad8a9/client.jar"; - private Map resources; + protected Map blockStateResources; + protected Map blockModelResources; + protected TextureGallery textures; - private TextureProvider textureProvider; - private BlockColorProvider blockColorProvider; - private Cache blockStateResourceCache; + private BlockColorCalculator blockColorCalculator; - public ResourcePack(List dataSources, File textureExportFile) throws IOException, NoSuchResourceException { - this.resources = new HashMap<>(); - - //load resources in order - for (File resource : dataSources) overrideResourcesWith(resource); - - blockStateResourceCache = CacheBuilder.newBuilder() - .maximumSize(10000) - .build(); - - textureProvider = new TextureProvider(); - - if (textureExportFile.exists()){ - textureProvider.load(textureExportFile); - } - - textureProvider.generate(this); //if loaded add missing textures - textureProvider.save(textureExportFile); - - blockColorProvider = new BlockColorProvider(this); + private BufferedImage foliageMap; + private BufferedImage grassMap; + + public ResourcePack() { + blockStateResources = new HashMap<>(); + blockModelResources = new HashMap<>(); + textures = new TextureGallery(); + foliageMap = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + foliageMap.setRGB(0, 0, 0xFF00FF00); + grassMap = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + grassMap.setRGB(0, 0, 0xFF00FF00); + blockColorCalculator = new BlockColorCalculator(foliageMap, grassMap); } - private void overrideResourcesWith(File resource){ - if (resource.isFile() && resource.getName().endsWith(".zip") || resource.getName().endsWith(".jar")){ - overrideResourcesWithZipFile(resource); - } else { - overrideResourcesWith(resource, Paths.get("")); - } + public void loadBlockColorConfig(File file) throws IOException { + blockColorCalculator.loadColorConfig(file); } - private void overrideResourcesWith(File resource, Path resourcePath){ - if (resource.isDirectory()){ - for (File childFile : resource.listFiles()){ - overrideResourcesWith(childFile, resourcePath.resolve(childFile.getName())); - } - return; - } - - if (resource.isFile() && isActualResourcePath(resourcePath)){ - try { - byte[] bytes = Files.readAllBytes(resource.toPath()); - resources.put(resourcePath, new Resource(bytes)); - } catch (IOException e) { - Logger.global.logError("Failed to load resource: " + resource, e); - } - } + /** + * See {@link TextureGallery#loadTextureFile(File)} + * @see TextureGallery#loadTextureFile(File) + */ + public void loadTextureFile(File file) throws IOException, ParseResourceException { + textures.loadTextureFile(file); } - private void overrideResourcesWithZipFile(File resourceFile){ - try ( - ZipFile zipFile = new ZipFile(resourceFile); - ){ - Enumeration files = zipFile.entries(); - byte[] buffer = new byte[1024]; - while (files.hasMoreElements()){ - ZipEntry file = files.nextElement(); - if (file.isDirectory()) continue; - - Path resourcePath = Paths.get("", file.getName().split("/")); - if (!isActualResourcePath(resourcePath)) continue; - - InputStream fileInputStream = zipFile.getInputStream(file); - ByteArrayOutputStream bos = new ByteArrayOutputStream(Math.max(8, (int) file.getSize())); - int bytesRead; - while ((bytesRead = fileInputStream.read(buffer)) != -1){ - bos.write(buffer, 0, bytesRead); + /** + * See {@link TextureGallery#saveTextureFile(File)} + * @see TextureGallery#saveTextureFile(File) + */ + public void saveTextureFile(File file) throws IOException { + textures.saveTextureFile(file); + } + + /** + * Loads and generates all {@link BlockStateResource}s from the listed sources. + * Resources from sources that are "later" (more to the end) in the list are overriding resources from sources "earlier" (more to the start/head) in the list.
+ *
+ * Any exceptions occurred while loading the resources are logged and ignored. + * + * @param sources The list of {@link File} sources. Each can be a folder or any zip-compressed file. (E.g. .zip or .jar) + */ + public void load(Collection sources) throws IOException { + load(sources.toArray(new File[sources.size()])); + } + + /** + * Loads and generates all {@link BlockStateResource}s and {@link Texture}s from the listed sources. + * Resources from sources that are "later" (more to the end) in the list are overriding resources from sources "earlier" (more to the start/head) in the list.
+ *
+ * Any exceptions occurred while loading the resources are logged and ignored. + * + * @param sources The list of {@link File} sources. Each can be a folder or any zip-compressed file. (E.g. .zip or .jar) + */ + public void load(File... sources) { + try (CombinedFileAccess sourcesAccess = new CombinedFileAccess()){ + for (File file : sources) { + try { + sourcesAccess.addFileAccess(FileAccess.of(file)); + } catch (IOException e) { + Logger.global.logError("Failed to read ResourcePack: " + file, e); } - - resources.put(resourcePath, new Resource(bos.toByteArray())); } - } catch (IOException e) { - Logger.global.logError("Failed to load resource: " + resourceFile, e); - } - } - - private boolean isActualResourcePath(Path path) { - String[] blockstatesPattern = {"assets", ".*", "blockstates", "*"}; - String[] modelsPattern = {"assets", ".*", "models", "blocks?", "*"}; - String[] texturesPattern = {"assets", ".*", "textures", "block|colormap", "*"}; - - return - FileUtils.matchPath(path, blockstatesPattern) || - FileUtils.matchPath(path, modelsPattern) || - FileUtils.matchPath(path, texturesPattern); - } - - public BlockStateResource getBlockStateResource(BlockState block) throws NoSuchResourceException, InvalidResourceDeclarationException { - BlockStateResource bsr = blockStateResourceCache.getIfPresent(block); - - if (bsr == null){ - bsr = new BlockStateResource(block, this); - blockStateResourceCache.put(block, bsr); - } - - return bsr; - } - - public TextureProvider getTextureProvider(){ - return textureProvider; - } - - public BlockColorProvider getBlockColorProvider(){ - return blockColorProvider; - } + + textures.reloadAllTextures(sourcesAccess); + + Builder builder = BlockStateResource.builder(sourcesAccess, this); + + Collection namespaces = sourcesAccess.listFolders("assets"); + for (String namespaceRoot : namespaces) { + String namespace = namespaceRoot.substring("assets/".length()); + Collection blockstateFiles = sourcesAccess.listFiles(namespaceRoot + "/blockstates", true); + for (String blockstateFile : blockstateFiles) { + String filename = FileAccess.getFileName(blockstateFile); + if (!filename.endsWith(".json")) continue; - public Map getAllResources() { - return Collections.unmodifiableMap(resources); - } - - public InputStream getResource(Path resourcePath) throws NoSuchResourceException { - Resource resource = resources.get(resourcePath); - if (resource == null) throw new NoSuchResourceException("There is no resource with that path: " + resourcePath); - return resource.getStream(); - } - - public class Resource { - - private byte[] data; - - public Resource(byte[] data) { - this.data = data; + try { + blockStateResources.put(namespace + ":" + filename.substring(0, filename.length() - 5), builder.build(blockstateFile)); + } catch (IOException ex) { + Logger.global.logError("Failed to load blockstate: " + namespace + ":" + filename.substring(0, filename.length() - 5), ex); + } + } + } + + try { + foliageMap = ImageIO.read(sourcesAccess.readFile("assets/minecraft/textures/colormap/foliage.png")); + grassMap = ImageIO.read(sourcesAccess.readFile("assets/minecraft/textures/colormap/grass.png")); + + blockColorCalculator.setFoliageMap(foliageMap); + blockColorCalculator.setGrassMap(grassMap); + } catch (IOException ex) { + Logger.global.logError("Failed to load foliage- or grass-map!", ex); + } + + } catch (IOException ex) { + Logger.global.logError("Failed to close FileAccess!", ex); } - - public InputStream getStream(){ - return new ByteArrayInputStream(data); - } - } + /** + * Returns a {@link BlockStateResource} for the given {@link BlockState} if found. + * @param state The {@link BlockState} + * @return The {@link BlockStateResource} + * @throws NoSuchResourceException If no resource is loaded for this {@link BlockState} + */ + public BlockStateResource getBlockStateResource(BlockState state) throws NoSuchResourceException { + BlockStateResource resource = blockStateResources.get(state.getFullId()); + if (resource == null) throw new NoSuchResourceException("No resource for blockstate: " + state.getFullId()); + return resource; + } + + public BlockColorCalculator getBlockColorCalculator() { + return blockColorCalculator; + } + + /** + * Synchronously downloads the default minecraft resources from the mojang-servers. + * @param file The file to save the downloaded resources to + * @throws IOException If an IOException occurs during the download + */ public static void downloadDefaultResource(File file) throws IOException { if (file.exists()) file.delete(); file.getParentFile().mkdirs(); org.apache.commons.io.FileUtils.copyURLToFile(new URL(MINECRAFT_CLIENT_URL), file, 10000, 10000); } + protected static String namespacedToAbsoluteResourcePath(String namespacedPath, String resourceTypeFolder) { + String path = namespacedPath; + + int namespaceIndex = path.indexOf(':'); + String namespace = "minecraft"; + if (namespaceIndex != -1) { + namespace = path.substring(0, namespaceIndex); + path = path.substring(namespaceIndex + 1); + } + + path = "assets/" + namespace + "/" + resourceTypeFolder + "/" + FileAccess.normalize(path); + + return path; + } + } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/Texture.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/Texture.java new file mode 100644 index 00000000..14c3985a --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/Texture.java @@ -0,0 +1,87 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * 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. + */ +package de.bluecolored.bluemap.core.resourcepack; + +import com.flowpowered.math.vector.Vector4f; + +public class Texture { + + private final int id; + private final String path; + private Vector4f color; + private boolean isHalfTransparent; + private String texture; + + protected Texture(int id, String path, Vector4f color, boolean halfTransparent, String texture) { + this.id = id; + this.path = path; + this.color = color; + this.isHalfTransparent = halfTransparent; + this.texture = texture; + } + + public int getId() { + return id; + } + + public String getPath() { + return path; + } + + /** + * Returns the calculated median color of the {@link Texture}. + * @return The median color of this {@link Texture} + */ + public Vector4f getColor() { + return color; + } + + /** + * Returns whether the {@link Texture} has half-transparent pixels or not. + * @return true if the {@link Texture} has half-transparent pixels, false if not + */ + public boolean isHalfTransparent() { + return isHalfTransparent; + } + + public String getTexture() { + return texture; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Texture) { + return ((Texture) obj).getId() == id; + } + + return false; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureGallery.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureGallery.java new file mode 100644 index 00000000..671134eb --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureGallery.java @@ -0,0 +1,315 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * 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. + */ +package de.bluecolored.bluemap.core.resourcepack; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import javax.imageio.ImageIO; + +import com.flowpowered.math.vector.Vector4f; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonStreamParser; + +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess; + +/** + * A {@link TextureGallery} is managing {@link Texture}s and their id's and path's.
+ * I can also load and generate the texture.json file, or load new {@link Texture}s from a {@link FileAccess}. + */ +public class TextureGallery { + + private static final String EMPTY_BASE64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAEUlEQVR42mNkIAAYRxWMJAUAE5gAEdz4t9QAAAAASUVORK5CYII="; + + private Map textureMap; + private List textureList; + + public TextureGallery() { + textureMap = new HashMap<>(); + textureList = new ArrayList<>(); + } + + /** + * Returns a {@link Texture} by its id, there can always be only one texture per id in a gallery. + * @param id The texture id + * @return The {@link Texture} + */ + public Texture get(int id) { + return textureList.get(id); + } + + /** + * Returns a {@link Texture} by its path, there can always be only one texture per path in a gallery. + * @param path The texture-path + * @return The {@link Texture} + */ + public Texture get(String path) { + Texture texture = textureMap.get(path); + if (texture == null) throw new NoSuchElementException("There is no texture with the path " + path + " in this gallery!"); + return texture; + } + + /** + * The count of {@link Texture}s managed by this gallery + * @return The count of textures + */ + public int size() { + return textureList.size(); + } + + /** + * Generates a texture.json file with all the {@link Texture}s in this gallery + * @param file The file to save the json in + * @throws IOException If an IOException occurs while writing + */ + public void saveTextureFile(File file) throws IOException { + + JsonArray textures = new JsonArray(); + for (int i = 0; i < textureList.size(); i++) { + Texture texture = textureList.get(i); + + JsonObject textureNode = new JsonObject(); + textureNode.addProperty("id", texture.getPath()); + textureNode.addProperty("texture", texture.getTexture()); + textureNode.addProperty("transparent", texture.isHalfTransparent()); + + Vector4f color = texture.getColor(); + JsonArray colorNode = new JsonArray(); + colorNode.add(color.getX()); + colorNode.add(color.getY()); + colorNode.add(color.getZ()); + colorNode.add(color.getW()); + + textureNode.add("color", colorNode); + + textures.add(textureNode); + } + + JsonObject root = new JsonObject(); + root.add("textures", textures); + + Gson gson = new GsonBuilder() + .setPrettyPrinting() + .create(); + String json = gson.toJson(root); + + file.delete(); + file.getParentFile().mkdirs(); + file.createNewFile(); + + try (FileWriter fileWriter = new FileWriter(file)) { + fileWriter.append(json); + fileWriter.flush(); + } + + } + + /** + * Loads all the {@link Texture}s from the provided texture.json file, removes any existing {@link Texture}s from this gallery. + * @param file The texture.json file. + * @throws IOException If an IOException occurs while reading the file. + * @throws ParseResourceException If the whole file can not be read. Errors with single textures are logged and ignored. + */ + public synchronized void loadTextureFile(File file) throws IOException, ParseResourceException { + textureList.clear(); + textureMap.clear(); + + try (FileReader fileReader = new FileReader(file)){ + JsonStreamParser jsonFile = new JsonStreamParser(fileReader); + JsonArray textures = jsonFile.next().getAsJsonObject().getAsJsonArray("textures"); + int size = textures.size(); + for (int i = 0; i < size; i++) { + while (i >= textureList.size()) { //prepopulate with placeholder so we don't get an IndexOutOfBounds below + textureList.add(new Texture(textureList.size(), "empty", Vector4f.ZERO, false, EMPTY_BASE64)); + } + + try { + JsonObject texture = textures.get(i).getAsJsonObject(); + String path = texture.get("id").getAsString(); + boolean transparent = texture.get("transparent").getAsBoolean(); + Vector4f color = readVector4f(texture.get("color").getAsJsonArray()); + textureList.set(i, new Texture(i, path, color, transparent, EMPTY_BASE64)); + } catch (Exception ex) { + Logger.global.logWarning("Failed to load texture with id " + i + " from texture file " + file + "!"); + } + } + } catch (IOException ex) { + throw ex; + } catch (Exception ex) { + throw new ParseResourceException("Invalid texture file format!", ex); + } finally { + regenerateMap(); + } + } + + /** + * Loads a {@link Texture} from the {@link FileAccess} and the given path and returns it.
+ * If there is already a {@link Texture} with this path in this Gallery it replaces the {@link Texture} with the new one + * and the new one will have the same id as the old one.
+ * Otherwise the {@link Texture} will be added to the end of this gallery with the next available id. + * @param fileAccess The {@link FileAccess} to load the image from. + * @param path The path of the image on the {@link FileAccess} + * @return The loaded {@link Texture} + * @throws FileNotFoundException If there is no image in that FileAccess on that path + * @throws IOException If an IOException occurred while loading the file + */ + public synchronized Texture loadTexture(FileAccess fileAccess, String path) throws FileNotFoundException, IOException { + try (InputStream input = fileAccess.readFile(path)) { + BufferedImage image = ImageIO.read(input); + if (image == null) throw new IOException("Failed to read image: " + path); + + //crop off animation frames + if (image.getHeight() > image.getWidth()){ + BufferedImage cropped = new BufferedImage(image.getWidth(), image.getWidth(), BufferedImage.TYPE_INT_ARGB); + Graphics2D g = cropped.createGraphics(); + g.drawImage(image, 0, 0, null); + image = cropped; + } + + //check halfTransparency + boolean halfTransparent = checkHalfTransparent(image); + + //calculate color + Vector4f color = calculateColor(image); + + //write to Base64 + ByteArrayOutputStream os = new ByteArrayOutputStream(); + ImageIO.write(image, "png", os); + String base64 = "data:image/png;base64," + Base64.getEncoder().encodeToString(os.toByteArray()); + + //replace if texture with this path already exists + Texture texture = textureMap.get(path); + if (texture != null) { + texture = new Texture(texture.getId(), path, color, halfTransparent, base64); + textureMap.put(path, texture); + textureList.set(texture.getId(), texture); + } else { + texture = new Texture(textureList.size(), path, color, halfTransparent, base64); + textureMap.put(path, texture); + textureList.add(texture); + } + + return texture; + } + } + + /** + * Tries to reload all {@link Texture}s from the given {@link FileAccess}
+ *
+ * Exceptions are being logged and ignored. + * @param fileAccess The {@link FileAccess} to load the {@link Texture}s from + */ + public synchronized void reloadAllTextures(FileAccess fileAccess) { + for (Texture texture : textureList.toArray(new Texture[textureList.size()])) { + try { + loadTexture(fileAccess, texture.getPath()); + } catch (IOException e) { + Logger.global.logWarning("Failed to reload texture: " + texture.getPath()); + Logger.global.noFloodWarning("This happens if the resource-packs have changed, but you have not deleted your generated maps. This might result in broken map-models!"); + } + } + } + + private synchronized void regenerateMap() { + textureMap.clear(); + for (int i = 0; i < textureList.size(); i++) { + Texture texture = textureList.get(i); + textureMap.put(texture.getPath(), texture); + } + } + + private Vector4f readVector4f(JsonArray jsonArray) throws ParseResourceException { + if (jsonArray.size() < 4) throw new ParseResourceException("Failed to load Vector4: Not enough values in list-node!"); + + float r = jsonArray.get(0).getAsFloat(); + float g = jsonArray.get(1).getAsFloat(); + float b = jsonArray.get(2).getAsFloat(); + float a = jsonArray.get(3).getAsFloat(); + + return new Vector4f(r, g, b, a); + } + + private boolean checkHalfTransparent(BufferedImage image){ + for (int x = 0; x < image.getWidth(); x++){ + for (int y = 0; y < image.getHeight(); y++){ + int pixel = image.getRGB(x, y); + int alpha = (pixel >> 24) & 0xff; + if (alpha > 0x00 && alpha < 0xff){ + return true; + } + } + } + + return false; + } + + private Vector4f calculateColor(BufferedImage image){ + double alpha = 0d, red = 0d, green = 0d, blue = 0d; + int count = 0; + + for (int x = 0; x < image.getWidth(); x++){ + for (int y = 0; y < image.getHeight(); y++){ + int pixel = image.getRGB(x, y); + double pixelAlpha = (double)((pixel >> 24) & 0xff) / (double) 0xff; + double pixelRed = (double)((pixel >> 16) & 0xff) / (double) 0xff; + double pixelGreen = (double)((pixel >> 8) & 0xff) / (double) 0xff; + double pixelBlue = (double)((pixel >> 0) & 0xff) / (double) 0xff; + + count++; + alpha += pixelAlpha; + red += pixelRed * pixelAlpha; + green += pixelGreen * pixelAlpha; + blue += pixelBlue * pixelAlpha; + } + } + + if (count == 0 || alpha == 0) return Vector4f.ZERO; + + red /= alpha; + green /= alpha; + blue /= alpha; + alpha /= count; + + return new Vector4f(red, green, blue, alpha); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureProvider.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureProvider.java deleted file mode 100644 index 36e65e84..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureProvider.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * 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. - */ -package de.bluecolored.bluemap.core.resourcepack; - -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Base64; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Vector; -import java.util.concurrent.ConcurrentHashMap; - -import javax.imageio.ImageIO; - -import com.flowpowered.math.vector.Vector4f; - -import de.bluecolored.bluemap.core.resourcepack.ResourcePack.Resource; -import de.bluecolored.bluemap.core.util.ConfigUtils; -import de.bluecolored.bluemap.core.util.FileUtils; -import de.bluecolored.bluemap.core.util.MathUtils; -import ninja.leaping.configurate.ConfigurationNode; -import ninja.leaping.configurate.gson.GsonConfigurationLoader; - -public class TextureProvider { - - private Map indexMap; - private List textures; - - public TextureProvider() throws IOException { - this.indexMap = new ConcurrentHashMap<>(); - this.textures = new Vector<>(); - } - - public int getTextureIndex(String textureId) throws NoSuchTextureException { - Integer tex = indexMap.get(textureId); - - if (tex == null){ - throw new NoSuchTextureException("There is no texture with id: " + textureId); - } - - return tex.intValue(); - } - - public Texture getTexture(String textureId) throws NoSuchTextureException { - return getTexture(getTextureIndex(textureId)); - } - - public Texture getTexture(int index){ - return textures.get(index); - } - - public void generate(ResourcePack resources) throws IOException { - String[] texturesPathPattern = {"assets", ".*", "textures", "block", "*"}; - - for (Entry entry : resources.getAllResources().entrySet()){ - Path key = entry.getKey(); - if (FileUtils.matchPath(key, texturesPathPattern) && key.toString().endsWith(".png")){ - String path = key.subpath(3, key.getNameCount()).normalize().toString(); - String id = path - .substring(0, path.length() - ".png".length()) - .replace(File.separatorChar, '/'); - - BufferedImage image = ImageIO.read(entry.getValue().getStream()); - if (image == null) throw new IOException("Failed to read Image: " + key); - - Texture texture = new Texture(id, image); - - //update if existing else add new - if (indexMap.containsKey(id)) { - int index = indexMap.get(id); - textures.set(index, texture); - } else { - textures.add(texture); - indexMap.put(id, textures.size() - 1); - } - } - } - } - - public void load(File file) throws IOException { - indexMap.clear(); - textures.clear(); - - GsonConfigurationLoader loader = GsonConfigurationLoader.builder().setFile(file).build(); - ConfigurationNode node = loader.load(); - - int i = 0; - for(ConfigurationNode n : node.getNode("textures").getChildrenList()){ - Texture t = new Texture( - n.getNode("id").getString(), - n.getNode("texture").getString(), - n.getNode("transparent").getBoolean(false), - ConfigUtils.readVector4f(n.getNode("color")) - ); - - textures.add(t); - indexMap.put(t.getId(), i++); - } - } - - public void save(File file) throws IOException { - - if (!file.exists()) { - file.getParentFile().mkdirs(); - file.createNewFile(); - } - - GsonConfigurationLoader loader = GsonConfigurationLoader.builder().setFile(file).build(); - ConfigurationNode node = loader.createEmptyNode(); - - for (Texture t : textures){ - ConfigurationNode n = node.getNode("textures").getAppendedNode(); - n.getNode("id").setValue(t.getId()); - n.getNode("texture").setValue(t.getBase64()); - n.getNode("transparent").setValue(t.isHalfTransparent()); - ConfigUtils.writeVector4f(n.getNode("color"), t.getColor()); - } - - loader.save(node); - } - - public class Texture { - - private String id; - private String base64; - private boolean halfTransparent; - private Vector4f color; - - public Texture(String id, String base64, boolean halfTransparent, Vector4f color){ - this.id = id; - this.halfTransparent = halfTransparent; - this.base64 = base64; - this.color = color; - } - - public Texture(String id, BufferedImage image) throws IOException { - this.id = id; - - //crop off animation frames - if (image.getHeight() > image.getWidth()){ - BufferedImage cropped = new BufferedImage(image.getWidth(), image.getWidth(), image.getType()); - Graphics2D g = cropped.createGraphics(); - g.drawImage(image, 0, 0, null); - image = cropped; - } - - //check halfTransparency - this.halfTransparent = checkHalfTransparent(image); - - //calculate color - this.color = calculateColor(image); - - //write to Base64 - ByteArrayOutputStream os = new ByteArrayOutputStream(); - ImageIO.write(image, "png", os); - this.base64 = "data:image/png;base64," + Base64.getEncoder().encodeToString(os.toByteArray()); - } - - private Vector4f calculateColor(BufferedImage image){ - Vector4f color = Vector4f.ZERO; - - for (int x = 0; x < image.getWidth(); x++){ - for (int y = 0; y < image.getHeight(); y++){ - int pixel = image.getRGB(x, y); - double alpha = (double)((pixel >> 24) & 0xff) / (double) 0xff; - double red = (double)((pixel >> 16) & 0xff) / (double) 0xff; - double green = (double)((pixel >> 8) & 0xff) / (double) 0xff; - double blue = (double)((pixel >> 0) & 0xff) / (double) 0xff; - - color = MathUtils.blendColors(new Vector4f(red, green, blue, alpha), color); - } - } - - return color; - } - - private boolean checkHalfTransparent(BufferedImage image){ - for (int x = 0; x < image.getWidth(); x++){ - for (int y = 0; y < image.getHeight(); y++){ - int pixel = image.getRGB(x, y); - int alpha = (pixel >> 24) & 0xff; - if (alpha > 0x00 && alpha < 0xff){ - return true; - } - } - } - - return false; - } - - public String getId() { - return id; - } - - public String getBase64() { - return base64; - } - - public boolean isHalfTransparent() { - return halfTransparent; - } - - public Vector4f getColor(){ - return color; - } - - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WeighedArrayList.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TransformedBlockModelResource.java similarity index 55% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WeighedArrayList.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TransformedBlockModelResource.java index f9df25a2..33670be7 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WeighedArrayList.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TransformedBlockModelResource.java @@ -22,55 +22,33 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.util; +package de.bluecolored.bluemap.core.resourcepack; -import java.util.ArrayList; -import java.util.List; +import com.flowpowered.math.vector.Vector2i; -public class WeighedArrayList extends ArrayList implements List { - private static final long serialVersionUID = 1L; +public class TransformedBlockModelResource { + + private Vector2i rotation = Vector2i.ZERO; + private boolean uvLock = false; - public WeighedArrayList() {} - - public WeighedArrayList(int capacity) { - super(capacity); + private BlockModelResource model; + + public TransformedBlockModelResource(Vector2i rotation, boolean uvLock, BlockModelResource model) { + this.model = model; + this.rotation = rotation; + this.uvLock = uvLock; } - /** - * Adds the element weight times to this list. - * @return Always true - */ - public void add(E e, int weight) { - for (int i = 0; i < weight; i++){ - add(e); - } + public Vector2i getRotation() { + return rotation; } - /** - * Removes the first weight number of items that equal o from this list.
- * @return The number of elements removed. - */ - public int remove(Object o, int weight) { - int removed = 0; - if (o == null){ - for (int i = 0; i < size(); i++){ - if (get(i) == null){ - remove(i); - removed++; - if (removed >= weight) break; - } - } - } else { - for (int i = 0; i < size(); i++){ - if (o.equals(get(i))){ - remove(i); - removed++; - if (removed >= weight) break; - } - } - } - - return removed; + public boolean isUVLock() { + return uvLock; + } + + public BlockModelResource getModel() { + return model; } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/fileaccess/CombinedFileAccess.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/fileaccess/CombinedFileAccess.java new file mode 100644 index 00000000..4177f03e --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/fileaccess/CombinedFileAccess.java @@ -0,0 +1,97 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * 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. + */ +package de.bluecolored.bluemap.core.resourcepack.fileaccess; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class CombinedFileAccess implements FileAccess { + + public List sources; + + public CombinedFileAccess() { + sources = new ArrayList<>(); + } + + public void addFileAccess(FileAccess source) { + sources.add(source); + } + + @Override + public InputStream readFile(String path) throws FileNotFoundException, IOException { + for (int i = sources.size() - 1; i >= 0; i--) { //reverse order because later sources override earlier ones + try { + return sources.get(i).readFile(path); + } catch (FileNotFoundException ex) {} + } + + throw new FileNotFoundException("File " + path + " does not exist in any of the sources!"); + } + + @Override + public Collection listFiles(String path, boolean recursive) { + Set files = new HashSet<>(); + + for (int i = 0; i < sources.size(); i++) { + files.addAll(sources.get(i).listFiles(path, recursive)); + } + + return files; + } + + @Override + public Collection listFolders(String path) { + Set folders = new HashSet<>(); + + for (int i = 0; i < sources.size(); i++) { + folders.addAll(sources.get(i).listFolders(path)); + } + + return folders; + } + + @Override + public void close() throws IOException { + IOException exception = null; + + for (FileAccess source : sources) { + try { + source.close(); + } catch (IOException ex) { + if (exception == null) exception = ex; + else exception.addSuppressed(ex); + } + } + + if (exception != null) throw exception; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/fileaccess/FileAccess.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/fileaccess/FileAccess.java new file mode 100644 index 00000000..92e5a262 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/fileaccess/FileAccess.java @@ -0,0 +1,65 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * 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. + */ +package de.bluecolored.bluemap.core.resourcepack.fileaccess; + +import java.io.Closeable; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; + +public interface FileAccess extends Closeable, AutoCloseable { + + InputStream readFile(String path) throws FileNotFoundException, IOException; + + Collection listFiles(String path, boolean recursive); + + Collection listFolders(String path); + + static FileAccess of(File file) throws IOException { + if (file.isDirectory()) return new FolderFileAccess(file); + if (file.isFile()) return new ZipFileAccess(file); + throw new IOException("Unsupported file!"); + } + + static String getFileName(String path) { + String filename = path; + + int nameSplit = path.lastIndexOf('/'); + if (nameSplit > -1) { + filename = path.substring(nameSplit + 1); + } + + return filename; + } + + static String normalize(String path) { + if (path.charAt(path.length() - 1) == '/') path = path.substring(0, path.length() - 1); + if (path.charAt(0) == '/') path = path.substring(1); + return path; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/fileaccess/FolderFileAccess.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/fileaccess/FolderFileAccess.java new file mode 100644 index 00000000..60bd29a6 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/fileaccess/FolderFileAccess.java @@ -0,0 +1,138 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * 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. + */ +package de.bluecolored.bluemap.core.resourcepack.fileaccess; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.WeakReference; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +public class FolderFileAccess implements FileAccess { + + private File folder; + private Collection> openedStreams; + + public FolderFileAccess(File folder) { + this.folder = folder; + openedStreams = new ArrayList<>(); + } + + @Override + public synchronized InputStream readFile(String path) throws FileNotFoundException { + InputStream stream = new FileInputStream(resolve(path).toFile()); + tidy(); + openedStreams.add(new WeakReference<>(stream)); + return stream; + } + + @Override + public Collection listFiles(String path, boolean recursive) { + File subFolder = resolve(path).toFile(); + List paths = new ArrayList<>(); + listFiles(subFolder, paths, recursive); + return paths; + } + + private void listFiles(File folder, Collection paths, boolean recursive) { + if (!folder.isDirectory()) return; + for (File file : folder.listFiles()) { + if (recursive && file.isDirectory()) listFiles(file, paths, true); + if (file.isFile()) paths.add(toPath(file)); + } + } + + @Override + public Collection listFolders(String path) { + File subFolder = resolve(path).toFile(); + List paths = new ArrayList<>(); + + if (subFolder.isDirectory()) { + for (File file : subFolder.listFiles()) { + if (file.isDirectory()) paths.add(toPath(file)); + } + } + + return paths; + } + + @Override + public synchronized void close() throws IOException { + IOException exception = null; + + for (WeakReference streamRef : openedStreams) { + try { + InputStream stream = streamRef.get(); + if (stream != null) stream.close(); + streamRef.clear(); + } catch (IOException ex) { + if (exception == null) exception = ex; + else exception.addSuppressed(ex); + } + } + + if (exception != null) throw exception; + + openedStreams.clear(); //only clear if no exception is thrown + } + + private synchronized void tidy() { + Iterator> iterator = openedStreams.iterator(); + while (iterator.hasNext()) { + WeakReference ref = iterator.next(); + if (ref.get() == null) iterator.remove(); + } + } + + private Path resolve(String path) { + if (File.separatorChar != '/') path = path.replace('/', File.separatorChar); + if (path.charAt(0) == '/') path = path.substring(1); + Path resolve = folder.toPath(); + for (String s : path.split("/")) { + resolve = resolve.resolve(s); + } + return resolve; + } + + private String toPath(File file) { + return toPath(file.toPath()); + } + + private String toPath(Path path) { + return folder + .toPath() + .relativize(path) + .normalize() + .toString() + .replace(File.separatorChar, '/'); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/fileaccess/ZipFileAccess.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/fileaccess/ZipFileAccess.java new file mode 100644 index 00000000..4ba703b3 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/fileaccess/ZipFileAccess.java @@ -0,0 +1,124 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * 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. + */ +package de.bluecolored.bluemap.core.resourcepack.fileaccess; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +public class ZipFileAccess implements FileAccess { + + private ZipFile file; + + public ZipFileAccess(File file) throws ZipException, IOException { + this.file = new ZipFile(file); + } + + @Override + public InputStream readFile(String path) throws FileNotFoundException, IOException { + ZipEntry entry = file.getEntry(path); + if (entry == null) throw new FileNotFoundException("File " + path + " does not exist in this zip-file!"); + + return file.getInputStream(entry); + } + + @Override + public Collection listFiles(String path, boolean recursive) { + path = normalizeFolderPath(path); + + Collection files = new ArrayList(); + for (Enumeration entries = file.entries(); entries.hasMoreElements();) { + ZipEntry entry = entries.nextElement(); + + if (entry.isDirectory()) continue; + + String file = entry.getName(); + int nameSplit = file.lastIndexOf('/'); + String filePath = ""; + if (nameSplit != -1) { + filePath = file.substring(0, nameSplit); + } + filePath = normalizeFolderPath(filePath); + + if (recursive) { + if (!filePath.startsWith(path) && !path.equals(filePath)) continue; + } else { + if (!path.equals(filePath)) continue; + } + + files.add(file); + } + + return files; + } + + @Override + public Collection listFolders(String path) { + path = normalizeFolderPath(path); + + Collection folders = new ArrayList(); + for (Enumeration entries = file.entries(); entries.hasMoreElements();) { + ZipEntry entry = entries.nextElement(); + + if (!entry.isDirectory()) continue; + + String file = entry.getName(); + file = file.substring(0, file.length() - 1); //strip last / + + int nameSplit = file.lastIndexOf('/'); + String filePath = "/"; + if (nameSplit != -1) { + filePath = file.substring(0, nameSplit); + } + filePath = normalizeFolderPath(filePath); + + if (!path.equals(filePath)) continue; + + folders.add(file); + } + + return folders; + } + + private String normalizeFolderPath(String path) { + if (path.isEmpty()) return path; + if (path.charAt(path.length() - 1) != '/') path = path + "/"; + if (path.charAt(0) == '/') path = path.substring(1); + return path; + } + + @Override + public void close() throws IOException { + file.close(); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ConfigUtils.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ConfigUtils.java index 82a12213..569e5070 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ConfigUtils.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ConfigUtils.java @@ -132,4 +132,28 @@ public static void writeVector4f(ConfigurationNode vectorNode, Vector4f v){ vectorNode.getAppendedNode().setValue(v.getW()); } + /** + * Returns an integer The value can be a normal integer, an integer in String-Format, or a string in hexadecimal format prefixed with #. + * @param node The Configuration Node with the value + * @return The parsed Integer + * @throws NumberFormatException If the value is not formatted correctly or if there is no value present. + */ + public static int readInt(ConfigurationNode node) throws NumberFormatException { + Object value = node.getValue(); + + if (value == null) throw new NumberFormatException("No value!"); + + if (value instanceof Number) { + return ((Number) value).intValue(); + } + + String val = value.toString(); + + if (val.charAt(0) == '#') { + return Integer.parseInt(val.substring(1), 16); + } + + return Integer.parseInt(val); + } + } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MathUtils.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MathUtils.java index 447c2422..db9ff38b 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MathUtils.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MathUtils.java @@ -33,6 +33,13 @@ public class MathUtils { private MathUtils() {} + /** + * Calculates the surface-normal of a plane spanned between three vectors. + * @param p1 The first vector + * @param p2 The second vector + * @param p3 The third vector + * @return The calculated normal + */ public static Vector3d getSurfaceNormal(Vector3d p1, Vector3d p2, Vector3d p3){ Vector3d u = p2.sub(p1); Vector3d v = p3.sub(p1); @@ -44,6 +51,13 @@ public static Vector3d getSurfaceNormal(Vector3d p1, Vector3d p2, Vector3d p3){ return new Vector3d(nX, nY, nZ); } + /** + * Calculates the surface-normal of a plane spanned between three vectors. + * @param p1 The first vector + * @param p2 The second vector + * @param p3 The third vector + * @return The calculated normal + */ public static Vector3f getSurfaceNormal(Vector3f p1, Vector3f p2, Vector3f p3) { Vector3f u = p2.sub(p1); Vector3f v = p3.sub(p1); @@ -58,18 +72,42 @@ public static Vector3f getSurfaceNormal(Vector3f p1, Vector3f p2, Vector3f p3) { return n; } + + /** + * Hashes the provided position to a random float between 0 and 1.
+ *
+ * (Implementation adapted from https://github.com/SpongePowered/SpongeAPI/blob/ecd761a70219e467dea47a09fc310e8238e9911f/src/main/java/org/spongepowered/api/extra/skylands/SkylandsUtil.java) + * + * @param pos The position to hash + * @param seed A seed for the hashing + * @return The hashed value between 0 and 1 + */ public static float hashToFloat(Vector3i pos, long seed) { return hashToFloat(pos.getX(), pos.getY(), pos.getZ(), seed); } /** - * Adapted from https://github.com/SpongePowered/SpongeAPI/blob/ecd761a70219e467dea47a09fc310e8238e9911f/src/main/java/org/spongepowered/api/extra/skylands/SkylandsUtil.java + * Hashes the provided position to a random float between 0 and 1.
+ *
+ * (Implementation adapted from https://github.com/SpongePowered/SpongeAPI/blob/ecd761a70219e467dea47a09fc310e8238e9911f/src/main/java/org/spongepowered/api/extra/skylands/SkylandsUtil.java) + * + * @param x The x component of the position + * @param y The y component of the position + * @param z The z component of the position + * @param seed A seed for the hashing + * @return The hashed value between 0 and 1 */ public static float hashToFloat(int x, int y, int z, long seed) { final long hash = x * 73428767 ^ y * 9122569 ^ z * 4382893 ^ seed * 457; return (hash * (hash + 456149) & 0x00ffffff) / (float) 0x01000000; } + /** + * Blends two colors, taking into account the alpha component + * @param top The top color + * @param bottom The bottom color + * @return The merged color + */ public static Vector4f blendColors(Vector4f top, Vector4f bottom){ if (top.getW() > 0 && bottom.getW() > 0){ float a = 1 - (1 - top.getW()) * (1 - bottom.getW()); @@ -84,6 +122,12 @@ public static Vector4f blendColors(Vector4f top, Vector4f bottom){ } } + /** + * Overlays two colors, taking into account the alpha component + * @param top The top color + * @param bottom The bottom color + * @return The merged color + */ public static Vector4f overlayColors(Vector4f top, Vector4f bottom){ if (top.getW() > 0 && bottom.getW() > 0){ float p = (1 - top.getW()) * bottom.getW(); @@ -99,4 +143,31 @@ public static Vector4f overlayColors(Vector4f top, Vector4f bottom){ } } + /** + * Creates a {@link Vector3f} representing the color from the integer + * @param color The color-int + * @return The color-Vector + */ + public static Vector3f color3FromInt(int color){ + return new Vector3f( + (color >> 16) & 0xFF, + (color >> 8) & 0xFF, + color & 0xFF + ).div(0xFF); + } + + /** + * Creates a {@link Vector4f} representing the color from the integer + * @param color The color-int + * @return The color-Vector + */ + public static Vector4f color4FromInt(int color){ + return new Vector4f( + (color >> 16) & 0xFF, + (color >> 8) & 0xFF, + color & 0xFF, + (color >> 24) & 0xFF + ).div(0xFF); + } + } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebSettings.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebSettings.java index 1314d3a8..7c1e3eb6 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebSettings.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebSettings.java @@ -50,7 +50,7 @@ public WebSettings(File settingsFile) throws IOException { .setFile(settingsFile) .build(); - load(); + rootNode = configLoader.createEmptyNode(); } public void load() throws IOException { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Biome.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Biome.java new file mode 100644 index 00000000..d8425fc9 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Biome.java @@ -0,0 +1,108 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * Copyright (c) contributors + * + * 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. + */ +package de.bluecolored.bluemap.core.world; + +import com.flowpowered.math.vector.Vector3f; +import com.flowpowered.math.vector.Vector4f; + +import de.bluecolored.bluemap.core.util.ConfigUtils; +import de.bluecolored.bluemap.core.util.MathUtils; +import ninja.leaping.configurate.ConfigurationNode; + +public class Biome { + + public static final Biome DEFAULT = new Biome(); + + private String id = "ocean"; + private int ordinal = 0; + private float humidity = 0.5f; + private float temp = 0.5f; + private Vector3f waterColor = MathUtils.color3FromInt(4159204); + + private Vector4f overlayFoliageColor = Vector4f.ZERO; + private Vector4f overlayGrassColor = Vector4f.ZERO; + + private Biome() {} + + public Biome(String id, int ordinal, float humidity, float temp, Vector3f waterColor) { + this.id = id; + this.ordinal = ordinal; + this.humidity = humidity; + this.temp = temp; + this.waterColor = waterColor; + } + + public Biome(String id, int ordinal, float humidity, float temp, Vector3f waterColor, Vector4f overlayFoliageColor, Vector4f overlayGrassColor) { + this (id, ordinal, humidity, temp, waterColor); + + this.overlayFoliageColor = overlayFoliageColor; + this.overlayGrassColor = overlayGrassColor; + } + + public String getId() { + return id; + } + + public int getOrdinal() { + return ordinal; + } + + public float getHumidity() { + return humidity; + } + + public float getTemp() { + return temp; + } + + public Vector3f getWaterColor() { + return waterColor; + } + + public Vector4f getOverlayFoliageColor() { + return overlayFoliageColor; + } + + public Vector4f getOverlayGrassColor() { + return overlayGrassColor; + } + + public static Biome create(String id, ConfigurationNode node) { + Biome biome = new Biome(); + + biome.id = id; + biome.ordinal = node.getNode("id").getInt(biome.ordinal); + biome.humidity = node.getNode("humidity").getFloat(biome.humidity); + biome.temp = node.getNode("temp").getFloat(biome.temp); + try { biome.waterColor = MathUtils.color3FromInt(ConfigUtils.readInt(node.getNode("watercolor"))); } catch (NumberFormatException ignored) {} + try { biome.overlayFoliageColor = MathUtils.color4FromInt(ConfigUtils.readInt(node.getNode("foliagecolor"))); } catch (NumberFormatException ignored) {} + try { biome.overlayGrassColor = MathUtils.color4FromInt(ConfigUtils.readInt(node.getNode("grasscolor"))); } catch (NumberFormatException ignored) {} + + return biome; + } + + + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Block.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Block.java index 22c8dad4..eea48d07 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Block.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Block.java @@ -56,7 +56,7 @@ public boolean isOccludingNeighborFaces(){ return isCullingNeighborFaces(); } - public abstract String getBiome(); + public abstract Biome getBiome(); /** * This is internally used for light rendering diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/BlockState.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/BlockState.java index 6eb256f5..d4f6a65a 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/BlockState.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/BlockState.java @@ -51,6 +51,7 @@ public class BlockState { private final String namespace; private final String id; + private final String fullId; private final Map properties; public BlockState(String id) { @@ -73,6 +74,7 @@ public BlockState(String id, Map properties) { this.id = id; this.namespace = namespace; + this.fullId = namespace + ":" + id; } private BlockState(BlockState blockState, String withKey, String withValue) { @@ -84,6 +86,7 @@ private BlockState(BlockState blockState, String withKey, String withValue) { this.id = blockState.getId(); this.namespace = blockState.getNamespace(); + this.fullId = namespace + ":" + id; this.properties = Collections.unmodifiableMap(props); } @@ -107,7 +110,7 @@ public String getId() { * Returns the namespaced id of this blockstate */ public String getFullId() { - return getNamespace() + ":" + getId(); + return fullId; } /** diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/CachedBlock.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/CachedBlock.java index 298beeb6..677cd13e 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/CachedBlock.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/CachedBlock.java @@ -38,7 +38,7 @@ public class CachedBlock extends Block { private World world; private Vector3i position; private double sunLight, blockLight; - private String biome; + private Biome biome; private boolean isCullingCached; private boolean isCulling; @@ -113,7 +113,7 @@ public boolean isOccludingNeighborFaces() { } @Override - public String getBiome() { + public Biome getBiome() { if (biome == null){ biome = block.getBiome(); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/LightData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/LightData.java similarity index 97% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/LightData.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/LightData.java index 2d801889..e42bfa41 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/LightData.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/LightData.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.mca.mapping; +package de.bluecolored.bluemap.core.world; public class LightData { diff --git a/BlueMapCore/src/main/resourceExtensions/assets/minecraft/blockstates/lava.json b/BlueMapCore/src/main/resourceExtensions/assets/minecraft/blockstates/lava.json new file mode 100644 index 00000000..d6adbba4 --- /dev/null +++ b/BlueMapCore/src/main/resourceExtensions/assets/minecraft/blockstates/lava.json @@ -0,0 +1,5 @@ +{ + "variants": { + "": { "model": "block/lava" } + } +} diff --git a/BlueMapCore/src/main/resourceExtensions/assets/minecraft/blockstates/water.json b/BlueMapCore/src/main/resourceExtensions/assets/minecraft/blockstates/water.json new file mode 100644 index 00000000..48ff0ea4 --- /dev/null +++ b/BlueMapCore/src/main/resourceExtensions/assets/minecraft/blockstates/water.json @@ -0,0 +1,5 @@ +{ + "variants": { + "": { "model": "block/water" } + } +} diff --git a/BlueMapCore/src/main/resourceExtensions/assets/minecraft/models/block/lava.json b/BlueMapCore/src/main/resourceExtensions/assets/minecraft/models/block/lava.json new file mode 100644 index 00000000..6ab3b5c3 --- /dev/null +++ b/BlueMapCore/src/main/resourceExtensions/assets/minecraft/models/block/lava.json @@ -0,0 +1,8 @@ +{ + "parent": "builtin/liquid", + "textures": { + "particle": "block/lava_still", + "still": "block/lava_still", + "flow": "block/lava_flow" + } +} \ No newline at end of file diff --git a/BlueMapCore/src/main/resourceExtensions/assets/minecraft/models/block/water.json b/BlueMapCore/src/main/resourceExtensions/assets/minecraft/models/block/water.json new file mode 100644 index 00000000..a44af2f1 --- /dev/null +++ b/BlueMapCore/src/main/resourceExtensions/assets/minecraft/models/block/water.json @@ -0,0 +1,8 @@ +{ + "parent": "builtin/liquid", + "textures": { + "particle": "block/water_still", + "still": "block/water_still", + "flow": "block/water_flow" + } +} \ No newline at end of file diff --git a/BlueMapCore/src/main/resources/blockColors.json b/BlueMapCore/src/main/resources/blockColors.json index b2a2e945..08001cf4 100644 --- a/BlueMapCore/src/main/resources/blockColors.json +++ b/BlueMapCore/src/main/resources/blockColors.json @@ -1,11 +1,12 @@ { - "default": "#foliage", - "grass_block" : "#grass", - "grass" : "#grass", - "tall_grass" : "#grass", - "fern" : "#grass", - "large_fern" : "#grass", - "redstone_wire" : "ff0000", - "birch_leaves" : "86a863", - "spruce_leaves" : "51946b" + "default": "@foliage", + "minecraft:water" : "@water", + "minecraft:grass_block" : "@grass", + "minecraft:grass" : "@grass", + "minecraft:tall_grass" : "@grass", + "minecraft:fern" : "@grass", + "minecraft:large_fern" : "@grass", + "minecraft:redstone_wire" : "#ff0000", + "minecraft:birch_leaves" : "#86a863", + "minecraft:spruce_leaves" : "#51946b" } \ No newline at end of file diff --git a/BlueMapCore/src/main/resources/resourceExtensions.zip b/BlueMapCore/src/main/resources/resourceExtensions.zip new file mode 100644 index 0000000000000000000000000000000000000000..6b820c882492e46abcd7db15f1e86d8515ad621d GIT binary patch literal 1552 zcmWIWW@h1HVBp|j$WET^&j18WAOZ;3fjF_aIJKl$pP3;5uIvb!GC_p0+|0bxSs@b z7?_rBI93Fk(5J!n9FJM^z-@kEp3Q*S~AjaxrsI3s)$btPRM9V|R>#TO*rl0^Xt+Qt| zPoBAa{-lo1S)ag555Z9}=}On6ON*`~Etmw3i52Trj4jng*BGnYTSnWfZ`3i|w?oBP zoqfsl$-$h5j_J;yHaWgejaRJcM$qZgq1;GOV+wTW&Kr?$Qh|YB8Ww8JR?wac3)_dx1cJ;jJTx zhB_Tt6ShnSQVP+8HDZvpW6N>~?GZpGMsOi#KxCt^Wk85g3=B&erxG#>o resources = new ArrayList<>(resourcePacks.length + 1); resources.add(defaultResourceFile); for (File file : resourcePacks) resources.add(file); - - resourcePack = new ResourcePack(resources, textureExportFile); + resources.add(resourceExtensionsFile); + + resourcePack = new ResourcePack(); + if (textureExportFile.exists()) resourcePack.loadTextureFile(textureExportFile); + resourcePack.load(resources); + resourcePack.loadBlockColorConfig(blockColorsConfigFile); + resourcePack.saveTextureFile(textureExportFile); //load maps for (MapConfig mapConfig : config.getMapConfigs()) { @@ -319,7 +334,7 @@ public synchronized void unload() { loaded = false; } - public synchronized void reload() throws IOException, NoSuchResourceException, ExecutionException, InterruptedException { + public synchronized void reload() throws IOException, ExecutionException, InterruptedException, ParseResourceException { unload(); load(); }