mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2024-12-25 18:37:41 +01:00
Completely rework ResourcePack and resource-loading
This commit is contained in:
parent
4e7deb2562
commit
7389cb1a16
@ -1 +1 @@
|
||||
Subproject commit 13f60e6ee0ba39759d662bba5ae750a5918b564b
|
||||
Subproject commit 0776e69c88ed24e2d2f0141e616d0512a5482ac9
|
@ -41,6 +41,8 @@ dependencies {
|
||||
api ("de.bluecolored.bluemap.core:BlueMapCore")
|
||||
api ("de.bluecolored.bluemap.api:BlueMapAPI")
|
||||
|
||||
compileOnly ("org.jetbrains:annotations:16.0.2")
|
||||
|
||||
testImplementation ("org.junit.jupiter:junit-jupiter:5.8.2")
|
||||
testRuntimeOnly ("org.junit.jupiter:junit-jupiter-engine:5.8.2")
|
||||
}
|
||||
|
@ -34,23 +34,24 @@
|
||||
import de.bluecolored.bluemap.common.web.WebSettings;
|
||||
import de.bluecolored.bluemap.core.MinecraftVersion;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.debug.StateDumper;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.mca.MCAWorld;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.util.AtomicFileHelper;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* This is the attempt to generalize as many actions as possible to have CLI and Plugins run on the same general setup-code.
|
||||
@ -74,6 +75,8 @@ public BlueMapService(ServerInterface serverInterface) throws ConfigurationExcep
|
||||
|
||||
this.worldIds = new HashMap<>();
|
||||
this.storages = new HashMap<>();
|
||||
|
||||
StateDumper.global().register(this);
|
||||
}
|
||||
|
||||
public synchronized String getWorldId(Path worldFolder) throws IOException {
|
||||
@ -130,7 +133,7 @@ public synchronized void createOrUpdateWebApp(boolean force) throws Configuratio
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized WebSettings updateWebAppSettings() throws ConfigurationException, InterruptedException {
|
||||
public synchronized void updateWebAppSettings() throws ConfigurationException, InterruptedException {
|
||||
try {
|
||||
WebSettings webSettings = new WebSettings(configs.getWebappConfig().getWebroot().resolve("data").resolve("settings.json"));
|
||||
|
||||
@ -147,8 +150,6 @@ public synchronized WebSettings updateWebAppSettings() throws ConfigurationExcep
|
||||
webSettings.setFrom(entry.getValue(), entry.getKey());
|
||||
}
|
||||
webSettings.save();
|
||||
|
||||
return webSettings;
|
||||
} catch (IOException ex) {
|
||||
throw new ConfigurationException("Failed to update web-app settings!", ex);
|
||||
}
|
||||
@ -254,14 +255,13 @@ public synchronized ResourcePack getResourcePack() throws ConfigurationException
|
||||
if (resourcePack == null) {
|
||||
MinecraftVersion minecraftVersion = serverInterface.getMinecraftVersion();
|
||||
|
||||
File defaultResourceFile = new File(configs.getCoreConfig().getData().toFile(), "minecraft-client-" + minecraftVersion.getResource().getVersion().getVersionString() + ".jar");
|
||||
File resourceExtensionsFile = new File(configs.getCoreConfig().getData().toFile(), "resourceExtensions.zip");
|
||||
Path defaultResourceFile = configs.getCoreConfig().getData().resolve("minecraft-client-" + minecraftVersion.getResource().getVersion().getVersionString() + ".jar");
|
||||
Path resourceExtensionsFile = configs.getCoreConfig().getData().resolve("resourceExtensions.zip");
|
||||
|
||||
File textureExportFile = configs.getWebappConfig().getWebroot().resolve("data").resolve("textures.json").toFile();
|
||||
Path resourcePackFolder = serverInterface.getConfigFolder().resolve("resourcepacks");
|
||||
|
||||
File resourcePackFolder = serverInterface.getConfigFolder().resolve("resourcepacks").toFile();
|
||||
try {
|
||||
FileUtils.forceMkdir(resourcePackFolder);
|
||||
Files.createDirectories(resourcePackFolder);
|
||||
} catch (IOException ex) {
|
||||
throw new ConfigurationException(
|
||||
"BlueMap failed to create this folder:\n" +
|
||||
@ -270,17 +270,17 @@ public synchronized ResourcePack getResourcePack() throws ConfigurationException
|
||||
ex);
|
||||
}
|
||||
|
||||
if (!defaultResourceFile.exists()) {
|
||||
if (!Files.exists(defaultResourceFile)) {
|
||||
if (configs.getCoreConfig().isAcceptDownload()) {
|
||||
//download file
|
||||
try {
|
||||
Logger.global.logInfo("Downloading " + minecraftVersion.getResource().getClientUrl() + " to " + defaultResourceFile + " ...");
|
||||
|
||||
FileUtils.forceMkdirParent(defaultResourceFile);
|
||||
File tempResourceFile = new File(defaultResourceFile.getParentFile(), defaultResourceFile.getName() + ".filepart");
|
||||
if (tempResourceFile.exists()) FileUtils.forceDelete(tempResourceFile);
|
||||
FileUtils.copyURLToFile(new URL(minecraftVersion.getResource().getClientUrl()), tempResourceFile, 10000, 10000);
|
||||
AtomicFileHelper.move(tempResourceFile.toPath(), defaultResourceFile.toPath());
|
||||
Files.createDirectories(defaultResourceFile.getParent());
|
||||
Path tempResourceFile = defaultResourceFile.getParent().resolve(defaultResourceFile.getFileName() + ".filepart");
|
||||
Files.deleteIfExists(tempResourceFile);
|
||||
FileUtils.copyURLToFile(new URL(minecraftVersion.getResource().getClientUrl()), tempResourceFile.toFile(), 10000, 10000);
|
||||
AtomicFileHelper.move(tempResourceFile, defaultResourceFile);
|
||||
} catch (IOException ex) {
|
||||
throw new ConfigurationException("Failed to download resources!", ex);
|
||||
}
|
||||
@ -290,17 +290,15 @@ public synchronized ResourcePack getResourcePack() throws ConfigurationException
|
||||
}
|
||||
}
|
||||
|
||||
Logger.global.logInfo("Loading resources...");
|
||||
|
||||
try {
|
||||
if (resourceExtensionsFile.exists()) FileUtils.forceDelete(resourceExtensionsFile);
|
||||
FileUtils.forceMkdirParent(resourceExtensionsFile);
|
||||
Files.deleteIfExists(resourceExtensionsFile);
|
||||
Files.createDirectories(resourceExtensionsFile.getParent());
|
||||
URL resourceExtensionsUrl = Objects.requireNonNull(
|
||||
Plugin.class.getResource(
|
||||
"/de/bluecolored/bluemap/" + minecraftVersion.getResource().getResourcePrefix() +
|
||||
"/resourceExtensions.zip")
|
||||
);
|
||||
FileUtils.copyURLToFile(resourceExtensionsUrl, resourceExtensionsFile, 10000, 10000);
|
||||
FileUtils.copyURLToFile(resourceExtensionsUrl, resourceExtensionsFile.toFile(), 10000, 10000);
|
||||
} catch (IOException ex) {
|
||||
throw new ConfigurationException(
|
||||
"Failed to create resourceExtensions.zip!\n" +
|
||||
@ -308,22 +306,31 @@ public synchronized ResourcePack getResourcePack() throws ConfigurationException
|
||||
ex);
|
||||
}
|
||||
|
||||
//find more resource packs
|
||||
File[] resourcePacks = resourcePackFolder.listFiles();
|
||||
if (resourcePacks == null) resourcePacks = new File[0];
|
||||
Arrays.sort(resourcePacks); //load resource packs in alphabetical order so you can reorder them by renaming
|
||||
|
||||
List<File> resources = new ArrayList<>(resourcePacks.length + 1);
|
||||
resources.add(defaultResourceFile);
|
||||
resources.addAll(Arrays.asList(resourcePacks));
|
||||
resources.add(resourceExtensionsFile);
|
||||
|
||||
try {
|
||||
Logger.global.logInfo("Loading resources...");
|
||||
resourcePack = new ResourcePack();
|
||||
if (textureExportFile.exists()) resourcePack.loadTextureFile(textureExportFile);
|
||||
resourcePack.load(resources);
|
||||
resourcePack.saveTextureFile(textureExportFile);
|
||||
} catch (IOException | ParseResourceException e) {
|
||||
|
||||
// load from resourcepack folder
|
||||
try (Stream<Path> resourcepackFiles = Files.list(resourcePackFolder)) {
|
||||
resourcepackFiles
|
||||
.sorted(Comparator.reverseOrder())
|
||||
.forEach(resourcepackFile -> {
|
||||
try {
|
||||
resourcePack.loadResources(resourcepackFile);
|
||||
} catch (IOException e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
resourcePack.loadResources(resourceExtensionsFile);
|
||||
resourcePack.loadResources(defaultResourceFile);
|
||||
|
||||
resourcePack.bake();
|
||||
|
||||
StateDumper.global().dump(Path.of("dump.json"));
|
||||
|
||||
} catch (IOException | RuntimeException e) {
|
||||
throw new ConfigurationException("Failed to parse resources!\n" +
|
||||
"Is one of your resource-packs corrupted?", e);
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@DebugDump
|
||||
public class BlueMapConfigs {
|
||||
@ -214,8 +215,8 @@ private synchronized Map<String, MapConfig> loadMapConfigs() throws Configuratio
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
for (var configFile : Files.list(mapConfigFolder).toArray(Path[]::new)) {
|
||||
try (Stream<Path> configFiles = Files.list(mapConfigFolder)) {
|
||||
for (var configFile : configFiles.toArray(Path[]::new)) {
|
||||
if (!configManager.isConfigFile(configFile)) continue;
|
||||
Path rawConfig = configManager.getRaw(configFile);
|
||||
String id = sanitiseMapId(rawConfig.getFileName().toString());
|
||||
@ -267,8 +268,8 @@ private synchronized Map<String, StorageConfig> loadStorageConfigs() throws Conf
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
for (var configFile : Files.list(storageConfigFolder).toArray(Path[]::new)) {
|
||||
try (Stream<Path> configFiles = Files.list(storageConfigFolder)) {
|
||||
for (var configFile : configFiles.toArray(Path[]::new)) {
|
||||
if (!configManager.isConfigFile(configFile)) continue;
|
||||
Path rawConfig = configManager.getRaw(configFile);
|
||||
String id = rawConfig.getFileName().toString();
|
||||
@ -289,7 +290,7 @@ private synchronized Map<String, StorageConfig> loadStorageConfigs() throws Conf
|
||||
}
|
||||
|
||||
private String sanitiseMapId(String id) {
|
||||
return id.replaceAll("[^a-zA-Z0-9_]", "_");
|
||||
return id.replaceAll("\\W", "_");
|
||||
}
|
||||
|
||||
private ConfigTemplate createOverworldMapTemplate(String name, Path worldFolder) throws IOException {
|
||||
|
@ -13,7 +13,6 @@
|
||||
@DebugDump
|
||||
@ConfigSerializable
|
||||
public class MapConfig implements MapSettings {
|
||||
private transient Path configFile = null;
|
||||
|
||||
private String name = null;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package de.bluecolored.bluemap.common.config.typeserializer;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
import org.spongepowered.configurate.serialize.TypeSerializer;
|
||||
|
@ -44,7 +44,6 @@
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.metrics.Metrics;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
import org.spongepowered.configurate.gson.GsonConfigurationLoader;
|
||||
import org.spongepowered.configurate.serialize.SerializationException;
|
||||
@ -92,7 +91,7 @@ public Plugin(String implementationType, ServerInterface serverInterface) {
|
||||
StateDumper.global().register(this);
|
||||
}
|
||||
|
||||
public void load() throws IOException, ParseResourceException {
|
||||
public void load() throws IOException {
|
||||
try {
|
||||
loadingLock.lock();
|
||||
synchronized (this) {
|
||||
@ -345,7 +344,7 @@ public void unload() {
|
||||
}
|
||||
}
|
||||
|
||||
public void reload() throws IOException, ParseResourceException {
|
||||
public void reload() throws IOException {
|
||||
unload();
|
||||
load();
|
||||
}
|
||||
|
@ -26,9 +26,9 @@
|
||||
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.storage.file.FileStorage;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.util.FileUtils;
|
||||
import de.bluecolored.bluemap.core.storage.file.FileStorage;
|
||||
import de.bluecolored.bluemap.core.util.DeletingPathVisitor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@ -36,6 +36,7 @@
|
||||
import java.util.LinkedList;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public abstract class MapPurgeTask implements RenderTask {
|
||||
|
||||
@ -74,8 +75,9 @@ public MapFilePurgeTask(BmMap map, FileStorage fileStorage) throws IOException {
|
||||
private MapFilePurgeTask(BmMap map, Path directory) throws IOException {
|
||||
this.map = map;
|
||||
this.directory = directory;
|
||||
this.subFiles = Files.walk(directory, 3)
|
||||
.collect(Collectors.toCollection(LinkedList::new));
|
||||
try (Stream<Path> pathStream = Files.walk(directory, 3)) {
|
||||
this.subFiles = pathStream.collect(Collectors.toCollection(LinkedList::new));
|
||||
}
|
||||
this.subFilesCount = subFiles.size();
|
||||
this.hasMoreWork = true;
|
||||
this.cancelled = false;
|
||||
@ -92,13 +94,13 @@ public void doWork() throws Exception {
|
||||
// delete subFiles first to be able to track the progress and cancel
|
||||
while (!subFiles.isEmpty()) {
|
||||
Path subFile = subFiles.getLast();
|
||||
FileUtils.delete(subFile.toFile());
|
||||
Files.walkFileTree(subFile, DeletingPathVisitor.INSTANCE);
|
||||
subFiles.removeLast();
|
||||
if (this.cancelled) return;
|
||||
}
|
||||
|
||||
// make sure everything is deleted
|
||||
FileUtils.delete(directory.toFile());
|
||||
Files.walkFileTree(directory, DeletingPathVisitor.INSTANCE);
|
||||
} finally {
|
||||
// reset map render state
|
||||
if (this.map != null) {
|
||||
|
@ -46,7 +46,10 @@ public class WebSettings {
|
||||
private ConfigurationNode rootNode;
|
||||
|
||||
public WebSettings(Path settingsFile) throws IOException {
|
||||
if (!Files.exists(settingsFile)) Files.createFile(settingsFile);
|
||||
if (!Files.exists(settingsFile)) {
|
||||
Files.createDirectories(settingsFile.getParent());
|
||||
Files.createFile(settingsFile);
|
||||
}
|
||||
|
||||
configLoader = GsonConfigurationLoader.builder()
|
||||
.path(settingsFile)
|
||||
|
@ -41,6 +41,8 @@ dependencies {
|
||||
api ("com.github.Querz:NBT:4.0")
|
||||
api ("org.apache.commons:commons-dbcp2:2.9.0")
|
||||
|
||||
compileOnly ("org.jetbrains:annotations:16.0.2")
|
||||
|
||||
testImplementation ("org.junit.jupiter:junit-jupiter:5.8.2")
|
||||
testRuntimeOnly ("org.junit.jupiter:junit-jupiter-engine:5.8.2")
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ private void dumpInstance(Object instance, ConfigurationOptions options, Configu
|
||||
Class<?> type = instance.getClass();
|
||||
|
||||
if (!alreadyDumped.add(instance)) {
|
||||
node.set("<<" + instance.toString() + ">>");
|
||||
node.set("<<" + instance + ">>");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -99,8 +99,8 @@ private void dumpInstance(Object instance, ConfigurationOptions options, Configu
|
||||
}
|
||||
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
if (++count > 20) {
|
||||
node.appendListNode().set("<<" + (map.size() - 20) + " more elements>>");
|
||||
if (++count > 100) {
|
||||
node.appendListNode().set("<<" + (map.size() - 100) + " more elements>>");
|
||||
break;
|
||||
}
|
||||
|
||||
@ -119,8 +119,8 @@ private void dumpInstance(Object instance, ConfigurationOptions options, Configu
|
||||
|
||||
int count = 0;
|
||||
for (Object entry : (Collection<?>) instance) {
|
||||
if (++count > 20) {
|
||||
node.appendListNode().set("<<" + (((Collection<?>) instance).size() - 20) + " more elements>>");
|
||||
if (++count > 100) {
|
||||
node.appendListNode().set("<<" + (((Collection<?>) instance).size() - 100) + " more elements>>");
|
||||
break;
|
||||
}
|
||||
|
||||
@ -137,8 +137,8 @@ private void dumpInstance(Object instance, ConfigurationOptions options, Configu
|
||||
|
||||
int count = 0;
|
||||
for (Object entry : (Object[]) instance) {
|
||||
if (++count > 20) {
|
||||
node.appendListNode().set("<<" + (((Object[]) instance).length - 20) + " more elements>>");
|
||||
if (++count > 100) {
|
||||
node.appendListNode().set("<<" + (((Object[]) instance).length - 100) + " more elements>>");
|
||||
break;
|
||||
}
|
||||
|
||||
@ -161,53 +161,55 @@ private void dumpInstance(Object instance, ConfigurationOptions options, Configu
|
||||
return;
|
||||
}
|
||||
|
||||
boolean allFields = type.isAnnotationPresent(DebugDump.class);
|
||||
|
||||
boolean foundSomething = false;
|
||||
for (Field field : type.getDeclaredFields()) {
|
||||
DebugDump dd = field.getAnnotation(DebugDump.class);
|
||||
if (dd == null){
|
||||
if (!allFields) continue;
|
||||
if (Modifier.isStatic(field.getModifiers())) continue;
|
||||
if (Modifier.isTransient(field.getModifiers())) continue;
|
||||
do {
|
||||
boolean allFields = type.isAnnotationPresent(DebugDump.class);
|
||||
|
||||
for (Field field : type.getDeclaredFields()) {
|
||||
DebugDump dd = field.getAnnotation(DebugDump.class);
|
||||
if (dd == null) {
|
||||
if (!allFields) continue;
|
||||
if (Modifier.isStatic(field.getModifiers())) continue;
|
||||
if (Modifier.isTransient(field.getModifiers())) continue;
|
||||
}
|
||||
foundSomething = true;
|
||||
|
||||
String key = "";
|
||||
if (dd != null) key = dd.value();
|
||||
if (key.isEmpty()) key = field.getName();
|
||||
|
||||
if (options.acceptsType(field.getType())) {
|
||||
field.setAccessible(true);
|
||||
node.node(key).set(field.get(instance));
|
||||
} else {
|
||||
field.setAccessible(true);
|
||||
dumpInstance(field.get(instance), options, node.node(key), alreadyDumped);
|
||||
}
|
||||
}
|
||||
foundSomething = true;
|
||||
|
||||
String key = "";
|
||||
if (dd != null) key = dd.value();
|
||||
if (key.isEmpty()) key = field.getName();
|
||||
for (Method method : type.getDeclaredMethods()) {
|
||||
DebugDump dd = method.getAnnotation(DebugDump.class);
|
||||
if (dd == null) continue;
|
||||
foundSomething = true;
|
||||
|
||||
if (options.acceptsType(field.getType())) {
|
||||
field.setAccessible(true);
|
||||
node.node(key).set(field.get(instance));
|
||||
} else {
|
||||
field.setAccessible(true);
|
||||
dumpInstance(field.get(instance), options, node.node(key), alreadyDumped);
|
||||
String key = dd.value();
|
||||
if (key.isEmpty()) key = method.toGenericString().replace(' ', '_');
|
||||
|
||||
if (options.acceptsType(method.getReturnType())) {
|
||||
method.setAccessible(true);
|
||||
node.node(key).set(method.invoke(instance));
|
||||
} else {
|
||||
method.setAccessible(true);
|
||||
dumpInstance(method.invoke(instance), options, node.node(key), alreadyDumped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Method method : type.getDeclaredMethods()) {
|
||||
DebugDump dd = method.getAnnotation(DebugDump.class);
|
||||
if (dd == null) continue;
|
||||
foundSomething = true;
|
||||
|
||||
String key = dd.value();
|
||||
if (key.isEmpty()) key = method.toGenericString().replace(' ', '_');
|
||||
|
||||
if (options.acceptsType(method.getReturnType())) {
|
||||
method.setAccessible(true);
|
||||
node.node(key).set(method.invoke(instance));
|
||||
} else {
|
||||
method.setAccessible(true);
|
||||
dumpInstance(method.invoke(instance), options, node.node(key), alreadyDumped);
|
||||
}
|
||||
}
|
||||
} while ((type = type.getSuperclass()) != null);
|
||||
|
||||
if (!foundSomething) {
|
||||
node.set(instance.toString());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
node.set("Error: " + ex.toString());
|
||||
node.set("Error: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.logger;
|
||||
|
||||
import de.bluecolored.bluemap.core.util.FileUtils;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
||||
import java.io.File;
|
||||
@ -64,7 +64,7 @@ public static LoggerLogger getInstance() {
|
||||
public void addFileHandler(String filename, boolean append) {
|
||||
try {
|
||||
File file = new File(filename);
|
||||
FileUtils.mkDirsParent(file);
|
||||
FileUtils.forceMkdirParent(file);
|
||||
FileHandler fHandler = new FileHandler(filename, append);
|
||||
fHandler.setFormatter(formatter);
|
||||
this.logger.addHandler(fHandler);
|
||||
|
@ -30,7 +30,7 @@
|
||||
import de.bluecolored.bluemap.core.map.hires.HiresModelManager;
|
||||
import de.bluecolored.bluemap.core.map.hires.HiresTileMeta;
|
||||
import de.bluecolored.bluemap.core.map.lowres.LowresModelManager;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.storage.CompressedInputStream;
|
||||
import de.bluecolored.bluemap.core.storage.MetaType;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
@ -55,7 +55,9 @@ public class BmMap {
|
||||
private final Storage storage;
|
||||
private final MapSettings mapSettings;
|
||||
|
||||
private final ResourcePack resourcePack;
|
||||
private final MapRenderState renderState;
|
||||
private final TextureGallery textureGallery;
|
||||
|
||||
private final HiresModelManager hiresModelManager;
|
||||
private final LowresModelManager lowresModelManager;
|
||||
@ -73,22 +75,19 @@ public BmMap(String id, String name, String worldId, World world, Storage storag
|
||||
this.storage = Objects.requireNonNull(storage);
|
||||
this.mapSettings = Objects.requireNonNull(settings);
|
||||
|
||||
Objects.requireNonNull(resourcePack);
|
||||
this.resourcePack = Objects.requireNonNull(resourcePack);
|
||||
|
||||
this.renderState = new MapRenderState();
|
||||
loadRenderState();
|
||||
|
||||
Optional<CompressedInputStream> rstateData = storage.readMeta(id, MetaType.RENDER_STATE);
|
||||
if (rstateData.isPresent()) {
|
||||
try (InputStream in = rstateData.get().decompress()){
|
||||
this.renderState.load(in);
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logWarning("Failed to load render-state for map '" + getId() + "': " + ex);
|
||||
}
|
||||
}
|
||||
this.textureGallery = loadTextureGallery();
|
||||
this.textureGallery.put(resourcePack);
|
||||
saveTextureGallery();
|
||||
|
||||
this.hiresModelManager = new HiresModelManager(
|
||||
storage.tileStorage(id, TileType.HIRES),
|
||||
resourcePack,
|
||||
this.resourcePack,
|
||||
this.textureGallery,
|
||||
settings,
|
||||
new Grid(settings.getHiresTileSize(), 2)
|
||||
);
|
||||
@ -122,7 +121,21 @@ public void renderTile(Vector2i tile) {
|
||||
|
||||
public synchronized void save() {
|
||||
lowresModelManager.save();
|
||||
saveRenderState();
|
||||
}
|
||||
|
||||
private void loadRenderState() throws IOException {
|
||||
Optional<CompressedInputStream> rstateData = storage.readMeta(id, MetaType.RENDER_STATE);
|
||||
if (rstateData.isPresent()) {
|
||||
try (InputStream in = rstateData.get().decompress()){
|
||||
this.renderState.load(in);
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logWarning("Failed to load render-state for map '" + getId() + "': " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveRenderState() {
|
||||
try (OutputStream out = storage.writeMeta(id, MetaType.RENDER_STATE)) {
|
||||
this.renderState.save(out);
|
||||
} catch (IOException ex){
|
||||
@ -130,6 +143,27 @@ public synchronized void save() {
|
||||
}
|
||||
}
|
||||
|
||||
private TextureGallery loadTextureGallery() throws IOException {
|
||||
TextureGallery gallery = null;
|
||||
Optional<CompressedInputStream> texturesData = storage.readMeta(id, MetaType.TEXTURES);
|
||||
if (texturesData.isPresent()) {
|
||||
try (InputStream in = texturesData.get().decompress()){
|
||||
gallery = TextureGallery.readTexturesFile(in);
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to load textures for map '" + getId() + "'!", ex);
|
||||
}
|
||||
}
|
||||
return gallery != null ? gallery : new TextureGallery();
|
||||
}
|
||||
|
||||
private void saveTextureGallery() {
|
||||
try (OutputStream out = storage.writeMeta(id, MetaType.TEXTURES)) {
|
||||
this.textureGallery.writeTexturesFile(this.resourcePack, out);
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to save textures for map '" + getId() + "'!", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
@ -0,0 +1,69 @@
|
||||
package de.bluecolored.bluemap.core.map;
|
||||
|
||||
import com.google.gson.JsonIOException;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.resources.ResourcePath;
|
||||
import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.texture.Texture;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@DebugDump
|
||||
public class TextureGallery {
|
||||
|
||||
private final Map<ResourcePath<Texture>, Integer> ordinalMap;
|
||||
private int nextId;
|
||||
|
||||
public TextureGallery() {
|
||||
this.ordinalMap = new HashMap<>();
|
||||
this.nextId = 0;
|
||||
}
|
||||
|
||||
public int get(ResourcePath<Texture> textureResourcePath) {
|
||||
Integer ordinal = ordinalMap.get(textureResourcePath);
|
||||
return ordinal != null ? ordinal : 0;
|
||||
}
|
||||
|
||||
public synchronized int put(ResourcePath<Texture> textureResourcePath) {
|
||||
Integer ordinal = ordinalMap.putIfAbsent(textureResourcePath, nextId);
|
||||
if (ordinal == null) return nextId++;
|
||||
return ordinal;
|
||||
}
|
||||
|
||||
public synchronized void put(ResourcePack resourcePack) {
|
||||
resourcePack.getTextures().keySet().forEach(this::put);
|
||||
}
|
||||
|
||||
public void writeTexturesFile(ResourcePack resourcePack, OutputStream out) throws IOException {
|
||||
Texture[] textures = new Texture[nextId];
|
||||
ordinalMap.forEach((textureResourcePath, ordinal) -> {
|
||||
Texture texture = textureResourcePath.getResource(resourcePack::getTexture);
|
||||
if (texture == null) texture = Texture.MISSING;
|
||||
textures[ordinal] = texture;
|
||||
});
|
||||
|
||||
try (Writer writer = new OutputStreamWriter(out)) {
|
||||
ResourcesGson.INSTANCE.toJson(textures, Texture[].class, writer);
|
||||
} catch (JsonIOException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static TextureGallery readTexturesFile(InputStream in) throws IOException {
|
||||
TextureGallery gallery = new TextureGallery();
|
||||
try (Reader reader = new InputStreamReader(in)) {
|
||||
Texture[] textures = ResourcesGson.INSTANCE.fromJson(reader, Texture[].class);
|
||||
gallery.nextId = textures.length;
|
||||
for (int ordinal = 0; ordinal < textures.length; ordinal++) {
|
||||
gallery.ordinalMap.put(textures[ordinal].getResourcePath(), ordinal);
|
||||
}
|
||||
} catch (JsonIOException ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
return gallery;
|
||||
}
|
||||
|
||||
}
|
@ -27,12 +27,14 @@
|
||||
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.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.map.TextureGallery;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.world.Grid;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class HiresModelManager {
|
||||
|
||||
@ -40,8 +42,8 @@ public class HiresModelManager {
|
||||
private final HiresModelRenderer renderer;
|
||||
private final Grid tileGrid;
|
||||
|
||||
public HiresModelManager(Storage.TileStorage storage, ResourcePack resourcePack, RenderSettings renderSettings, Grid tileGrid) {
|
||||
this(storage, new HiresModelRenderer(resourcePack, renderSettings), tileGrid);
|
||||
public HiresModelManager(Storage.TileStorage storage, ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings, Grid tileGrid) {
|
||||
this(storage, new HiresModelRenderer(resourcePack, textureGallery, renderSettings), tileGrid);
|
||||
}
|
||||
|
||||
public HiresModelManager(Storage.TileStorage storage, HiresModelRenderer renderer, Grid tileGrid) {
|
||||
|
@ -25,22 +25,23 @@
|
||||
package de.bluecolored.bluemap.core.map.hires;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import de.bluecolored.bluemap.core.map.TextureGallery;
|
||||
import de.bluecolored.bluemap.core.map.hires.blockmodel.BlockStateModelFactory;
|
||||
import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
|
||||
public class HiresModelRenderer {
|
||||
|
||||
private final ResourcePack resourcePack;
|
||||
private final TextureGallery textureGallery;
|
||||
private final RenderSettings renderSettings;
|
||||
|
||||
public HiresModelRenderer(ResourcePack resourcePack, RenderSettings renderSettings) {
|
||||
this.renderSettings = renderSettings;
|
||||
public HiresModelRenderer(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
|
||||
this.resourcePack = resourcePack;
|
||||
this.textureGallery = textureGallery;
|
||||
this.renderSettings = renderSettings;
|
||||
}
|
||||
|
||||
public HiresTileMeta render(World world, Vector3i modelMin, Vector3i modelMax, HiresTileModel model) {
|
||||
@ -51,7 +52,7 @@ public HiresTileMeta render(World world, Vector3i modelMin, Vector3i modelMax, H
|
||||
HiresTileMeta tileMeta = new HiresTileMeta(modelMin.getX(), modelMin.getZ(), modelMax.getX(), modelMax.getZ()); //TODO: recycle tilemeta instances?
|
||||
|
||||
// create new for each tile-render since the factory is not threadsafe
|
||||
BlockStateModelFactory modelFactory = new BlockStateModelFactory(resourcePack, renderSettings);
|
||||
BlockStateModelFactory modelFactory = new BlockStateModelFactory(resourcePack, textureGallery, renderSettings);
|
||||
|
||||
int maxHeight, minY, maxY;
|
||||
Color columnColor = new Color(), blockColor = new Color();
|
||||
@ -76,16 +77,7 @@ public HiresTileMeta render(World world, Vector3i modelMin, Vector3i modelMax, H
|
||||
blockColor.set(0, 0, 0, 0, true);
|
||||
blockModel.initialize();
|
||||
|
||||
try {
|
||||
modelFactory.render(block, blockModel, blockColor);
|
||||
} catch (NoSuchResourceException e) {
|
||||
try {
|
||||
modelFactory.render(block, BlockState.MISSING, blockModel.reset(), blockColor);
|
||||
} catch (NoSuchResourceException e2) {
|
||||
e.addSuppressed(e2);
|
||||
}
|
||||
//Logger.global.noFloodDebug(block.getBlockState().getFullId() + "-hiresModelRenderer-blockmodelerr", "Failed to create BlockModel for BlockState: " + block.getBlockState() + " (" + e.toString() + ")");
|
||||
}
|
||||
modelFactory.render(block, blockModel, blockColor);
|
||||
|
||||
// skip empty blocks
|
||||
if (blockModel.getSize() <= 0) continue;
|
||||
|
@ -24,12 +24,12 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.map.hires.blockmodel;
|
||||
|
||||
import de.bluecolored.bluemap.core.map.TextureGallery;
|
||||
import de.bluecolored.bluemap.core.map.hires.BlockModelView;
|
||||
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
|
||||
import de.bluecolored.bluemap.core.resourcepack.blockstate.BlockStateResource;
|
||||
import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resourcepack.blockmodel.TransformedBlockModelResource;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.BlockModel;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.blockstate.Variant;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
@ -43,22 +43,20 @@ public class BlockStateModelFactory {
|
||||
private final ResourceModelBuilder resourceModelBuilder;
|
||||
private final LiquidModelBuilder liquidModelBuilder;
|
||||
|
||||
private final Collection<TransformedBlockModelResource> bmrs;
|
||||
private final Collection<Variant> variants = new ArrayList<>();
|
||||
|
||||
public BlockStateModelFactory(ResourcePack resourcePack, RenderSettings renderSettings) {
|
||||
public BlockStateModelFactory(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
|
||||
this.resourcePack = resourcePack;
|
||||
|
||||
this.resourceModelBuilder = new ResourceModelBuilder(resourcePack, renderSettings);
|
||||
this.liquidModelBuilder = new LiquidModelBuilder(resourcePack, renderSettings);
|
||||
|
||||
this.bmrs = new ArrayList<>();
|
||||
this.resourceModelBuilder = new ResourceModelBuilder(resourcePack, textureGallery, renderSettings);
|
||||
this.liquidModelBuilder = new LiquidModelBuilder(resourcePack, textureGallery, renderSettings);
|
||||
}
|
||||
|
||||
public void render(BlockNeighborhood<?> block, BlockModelView blockModel, Color blockColor) throws NoSuchResourceException {
|
||||
public void render(BlockNeighborhood<?> block, BlockModelView blockModel, Color blockColor) {
|
||||
render(block, block.getBlockState(), blockModel, blockColor);
|
||||
}
|
||||
|
||||
public void render(BlockNeighborhood<?> block, BlockState blockState, BlockModelView blockModel, Color blockColor) throws NoSuchResourceException {
|
||||
public void render(BlockNeighborhood<?> block, BlockState blockState, BlockModelView blockModel, Color blockColor) {
|
||||
|
||||
//shortcut for air
|
||||
if (blockState.isAir()) return;
|
||||
@ -76,18 +74,22 @@ public void render(BlockNeighborhood<?> block, BlockState blockState, BlockModel
|
||||
blockModel.initialize(modelStart);
|
||||
}
|
||||
|
||||
private void renderModel(BlockNeighborhood<?> block, BlockState blockState, BlockModelView blockModel, Color blockColor) throws NoSuchResourceException {
|
||||
private void renderModel(BlockNeighborhood<?> block, BlockState blockState, BlockModelView blockModel, Color blockColor) {
|
||||
int modelStart = blockModel.getStart();
|
||||
|
||||
BlockStateResource resource = resourcePack.getBlockStateResource(blockState);
|
||||
for (TransformedBlockModelResource bmr : resource.getModels(blockState, block.getX(), block.getY(), block.getZ(), bmrs)){
|
||||
switch (bmr.getModel().getType()){
|
||||
case LIQUID:
|
||||
liquidModelBuilder.build(block, blockState, bmr, blockModel.initialize(), blockColor);
|
||||
break;
|
||||
default:
|
||||
resourceModelBuilder.build(block, bmr, blockModel.initialize(), blockColor);
|
||||
break;
|
||||
var stateResource = resourcePack.getBlockState(blockState);
|
||||
if (stateResource == null) return;
|
||||
|
||||
variants.clear();
|
||||
stateResource.forEach(blockState, block.getX(), block.getY(), block.getZ(), variants::add);
|
||||
for (Variant variant : variants) {
|
||||
BlockModel modelResource = variant.getModel().getResource(resourcePack::getBlockModel);
|
||||
if (modelResource == null) continue;
|
||||
|
||||
if (modelResource.isLiquid()) {
|
||||
liquidModelBuilder.build(block, blockState, variant, blockModel.initialize(), blockColor);
|
||||
} else {
|
||||
resourceModelBuilder.build(block, variant, blockModel.initialize(), blockColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,13 +26,17 @@
|
||||
|
||||
import com.flowpowered.math.TrigMath;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import de.bluecolored.bluemap.core.map.TextureGallery;
|
||||
import de.bluecolored.bluemap.core.map.hires.BlockModelView;
|
||||
import de.bluecolored.bluemap.core.map.hires.HiresTileModel;
|
||||
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
|
||||
import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculatorFactory;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resourcepack.blockmodel.TransformedBlockModelResource;
|
||||
import de.bluecolored.bluemap.core.resourcepack.texture.Texture;
|
||||
import de.bluecolored.bluemap.core.resources.BlockColorCalculatorFactory;
|
||||
import de.bluecolored.bluemap.core.resources.ResourcePath;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.BlockModel;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.TextureVariable;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.blockstate.Variant;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.texture.Texture;
|
||||
import de.bluecolored.bluemap.core.util.Direction;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import de.bluecolored.bluemap.core.util.math.MatrixM3f;
|
||||
@ -45,6 +49,7 @@
|
||||
/**
|
||||
* A model builder for all liquid blocks
|
||||
*/
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public class LiquidModelBuilder {
|
||||
private static final float BLOCK_SCALE = 1f / 16f;
|
||||
private static final MatrixM3f FLOWING_UV_SCALE = new MatrixM3f()
|
||||
@ -53,21 +58,25 @@ public class LiquidModelBuilder {
|
||||
.scale(0.5f, 0.5f, 1)
|
||||
.translate(0.5f, 0.5f);
|
||||
|
||||
private final BlockColorCalculatorFactory.BlockColorCalculator blockColorCalculator;
|
||||
private final ResourcePack resourcePack;
|
||||
private final TextureGallery textureGallery;
|
||||
private final RenderSettings renderSettings;
|
||||
private final BlockColorCalculatorFactory.BlockColorCalculator blockColorCalculator;
|
||||
|
||||
private final VectorM3f[] corners;
|
||||
private final VectorM2f[] uvs = new VectorM2f[4];
|
||||
|
||||
private BlockNeighborhood<?> block;
|
||||
private BlockState blockState;
|
||||
private TransformedBlockModelResource blockModelResource;
|
||||
private BlockModel modelResource;
|
||||
private BlockModelView blockModel;
|
||||
private Color blockColor;
|
||||
|
||||
public LiquidModelBuilder(ResourcePack resourcePack, RenderSettings renderSettings) {
|
||||
this.blockColorCalculator = resourcePack.getBlockColorCalculatorFactory().createCalculator();
|
||||
public LiquidModelBuilder(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
|
||||
this.resourcePack = resourcePack;
|
||||
this.textureGallery = textureGallery;
|
||||
this.renderSettings = renderSettings;
|
||||
this.blockColorCalculator = resourcePack.getColorCalculatorFactory().createCalculator();
|
||||
|
||||
corners = new VectorM3f[]{
|
||||
new VectorM3f( 0, 0, 0 ),
|
||||
@ -83,10 +92,10 @@ public LiquidModelBuilder(ResourcePack resourcePack, RenderSettings renderSettin
|
||||
for (int i = 0; i < uvs.length; i++) uvs[i] = new VectorM2f(0, 0);
|
||||
}
|
||||
|
||||
public void build(BlockNeighborhood<?> block, BlockState blockState, TransformedBlockModelResource bmr, BlockModelView blockModel, Color color) {
|
||||
public void build(BlockNeighborhood<?> block, BlockState blockState, Variant variant, BlockModelView blockModel, Color color) {
|
||||
this.block = block;
|
||||
this.blockState = blockState;
|
||||
this.blockModelResource = bmr;
|
||||
this.modelResource = variant.getModel().getResource();
|
||||
this.blockModel = blockModel;
|
||||
this.blockColor = color;
|
||||
|
||||
@ -115,11 +124,15 @@ private void build() {
|
||||
corners[7].y = 16f;
|
||||
}
|
||||
|
||||
Texture stillTexture = blockModelResource.getModel().getTexture("still");
|
||||
Texture flowTexture = blockModelResource.getModel().getTexture("flow");
|
||||
TextureVariable stillVariable = modelResource.getTextures().get("still");
|
||||
TextureVariable flowVariable = modelResource.getTextures().get("flow");
|
||||
ResourcePath<Texture> stillTexturePath = stillVariable == null ? null : stillVariable
|
||||
.getTexturePath(modelResource.getTextures()::get);
|
||||
ResourcePath<Texture> flowTexturePath = flowVariable == null ? null : flowVariable
|
||||
.getTexturePath(modelResource.getTextures()::get);
|
||||
|
||||
int stillTextureId = stillTexture.getId();
|
||||
int flowTextureId = flowTexture.getId();
|
||||
int stillTextureId = textureGallery.get(stillTexturePath);
|
||||
int flowTextureId = textureGallery.get(flowTexturePath);
|
||||
|
||||
tintcolor.set(1f, 1f, 1f, 1f, true);
|
||||
if (blockState.isWater()) {
|
||||
@ -144,15 +157,19 @@ private void build() {
|
||||
|
||||
//calculate mapcolor
|
||||
if (upFaceRendered) {
|
||||
blockColor.set(stillTexture.getColorPremultiplied());
|
||||
blockColor.multiply(tintcolor);
|
||||
Texture stillTexture = stillTexturePath == null ? null : stillTexturePath.getResource(resourcePack::getTexture);
|
||||
|
||||
// apply light
|
||||
float combinedLight = Math.max(block.getSunLightLevel() / 15f, block.getBlockLightLevel() / 15f);
|
||||
combinedLight = (renderSettings.getAmbientLight() + combinedLight) / (renderSettings.getAmbientLight() + 1f);
|
||||
blockColor.r *= combinedLight;
|
||||
blockColor.g *= combinedLight;
|
||||
blockColor.b *= combinedLight;
|
||||
if (stillTexture != null) {
|
||||
blockColor.set(stillTexture.getColorPremultiplied());
|
||||
blockColor.multiply(tintcolor);
|
||||
|
||||
// apply light
|
||||
float combinedLight = Math.max(block.getSunLightLevel() / 15f, block.getBlockLightLevel() / 15f);
|
||||
combinedLight = (renderSettings.getAmbientLight() + combinedLight) / (renderSettings.getAmbientLight() + 1f);
|
||||
blockColor.r *= combinedLight;
|
||||
blockColor.g *= combinedLight;
|
||||
blockColor.b *= combinedLight;
|
||||
}
|
||||
} else {
|
||||
blockColor.set(0, 0, 0, 0, true);
|
||||
}
|
||||
@ -203,7 +220,7 @@ private boolean isLiquidBlockingBlock(BlockState blockState){
|
||||
}
|
||||
|
||||
private boolean isSameLiquid(ExtendedBlock<?> block){
|
||||
if (block.getBlockState().getFullId().equals(this.blockState.getFullId())) return true;
|
||||
if (block.getBlockState().getFormatted().equals(this.blockState.getFormatted())) return true;
|
||||
return this.blockState.isWater() && (block.getBlockState().isWaterlogged() || block.getProperties().isAlwaysWaterlogged());
|
||||
}
|
||||
|
||||
|
@ -25,18 +25,21 @@
|
||||
package de.bluecolored.bluemap.core.map.hires.blockmodel;
|
||||
|
||||
import com.flowpowered.math.TrigMath;
|
||||
import com.flowpowered.math.vector.Vector2f;
|
||||
import com.flowpowered.math.vector.Vector3f;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import com.flowpowered.math.vector.Vector4f;
|
||||
import de.bluecolored.bluemap.core.map.TextureGallery;
|
||||
import de.bluecolored.bluemap.core.map.hires.BlockModelView;
|
||||
import de.bluecolored.bluemap.core.map.hires.HiresTileModel;
|
||||
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
|
||||
import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculatorFactory;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resourcepack.blockmodel.BlockModelResource;
|
||||
import de.bluecolored.bluemap.core.resourcepack.blockmodel.TransformedBlockModelResource;
|
||||
import de.bluecolored.bluemap.core.resourcepack.texture.Texture;
|
||||
import de.bluecolored.bluemap.core.resources.BlockColorCalculatorFactory;
|
||||
import de.bluecolored.bluemap.core.resources.ResourcePath;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.BlockModel;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.Element;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.Face;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.blockstate.Variant;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.texture.Texture;
|
||||
import de.bluecolored.bluemap.core.util.Direction;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import de.bluecolored.bluemap.core.util.math.MatrixM4f;
|
||||
@ -46,14 +49,19 @@
|
||||
import de.bluecolored.bluemap.core.world.ExtendedBlock;
|
||||
import de.bluecolored.bluemap.core.world.LightData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This model builder creates a BlockStateModel using the information from parsed resource-pack json files.
|
||||
*/
|
||||
@SuppressWarnings("DuplicatedCode")
|
||||
public class ResourceModelBuilder {
|
||||
private static final float BLOCK_SCALE = 1f / 16f;
|
||||
|
||||
private final BlockColorCalculatorFactory.BlockColorCalculator blockColorCalculator;
|
||||
private final ResourcePack resourcePack;
|
||||
private final TextureGallery textureGallery;
|
||||
private final RenderSettings renderSettings;
|
||||
private final BlockColorCalculatorFactory.BlockColorCalculator blockColorCalculator;
|
||||
|
||||
private final VectorM3f[] corners = new VectorM3f[8];
|
||||
private final VectorM2f[] rawUvs = new VectorM2f[4];
|
||||
@ -62,34 +70,41 @@ public class ResourceModelBuilder {
|
||||
private final Color mapColor = new Color();
|
||||
|
||||
private BlockNeighborhood<?> block;
|
||||
private TransformedBlockModelResource blockModelResource;
|
||||
private Variant variant;
|
||||
private BlockModel modelResource;
|
||||
private BlockModelView blockModel;
|
||||
private Color blockColor;
|
||||
private float blockColorOpacity;
|
||||
|
||||
public ResourceModelBuilder(ResourcePack resourcePack, RenderSettings renderSettings) {
|
||||
this.blockColorCalculator = resourcePack.getBlockColorCalculatorFactory().createCalculator();
|
||||
public ResourceModelBuilder(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
|
||||
this.resourcePack = resourcePack;
|
||||
this.textureGallery = textureGallery;
|
||||
this.renderSettings = renderSettings;
|
||||
this.blockColorCalculator = resourcePack.getColorCalculatorFactory().createCalculator();
|
||||
|
||||
for (int i = 0; i < corners.length; i++) corners[i] = new VectorM3f(0, 0, 0);
|
||||
for (int i = 0; i < uvs.length; i++) rawUvs[i] = new VectorM2f(0, 0);
|
||||
}
|
||||
|
||||
private final MatrixM4f modelTransform = new MatrixM4f();
|
||||
public void build(BlockNeighborhood<?> block, TransformedBlockModelResource bmr, BlockModelView blockModel, Color color) {
|
||||
public void build(BlockNeighborhood<?> block, Variant variant, BlockModelView blockModel, Color color) {
|
||||
this.block = block;
|
||||
this.blockModel = blockModel;
|
||||
this.blockColor = color;
|
||||
this.blockColorOpacity = 0f;
|
||||
this.blockModelResource = bmr;
|
||||
this.variant = variant;
|
||||
this.modelResource = variant.getModel().getResource();
|
||||
|
||||
this.tintColor.set(0, 0, 0, -1, true);
|
||||
|
||||
// render model
|
||||
int modelStart = blockModel.getStart();
|
||||
|
||||
for (BlockModelResource.Element element : blockModelResource.getModel().getElements()) {
|
||||
buildModelElementResource(element, blockModel.initialize());
|
||||
List<Element> elements = modelResource.getElements();
|
||||
if (elements != null) {
|
||||
for (Element element : elements) {
|
||||
buildModelElementResource(element, blockModel.initialize());
|
||||
}
|
||||
}
|
||||
|
||||
if (color.a > 0) {
|
||||
@ -100,10 +115,10 @@ public void build(BlockNeighborhood<?> block, TransformedBlockModelResource bmr,
|
||||
blockModel.initialize(modelStart);
|
||||
|
||||
// apply model-rotation
|
||||
if (blockModelResource.hasRotation()) {
|
||||
if (variant.isRotated()) {
|
||||
blockModel.transform(modelTransform.identity()
|
||||
.translate(-0.5f, -0.5f, -0.5f)
|
||||
.multiplyTo(blockModelResource.getRotationMatrix())
|
||||
.multiplyTo(variant.getRotationMatrix())
|
||||
.translate(0.5f, 0.5f, 0.5f)
|
||||
);
|
||||
}
|
||||
@ -118,11 +133,11 @@ public void build(BlockNeighborhood<?> block, TransformedBlockModelResource bmr,
|
||||
}
|
||||
|
||||
private final MatrixM4f modelElementTransform = new MatrixM4f();
|
||||
private void buildModelElementResource(BlockModelResource.Element bmer, BlockModelView blockModel) {
|
||||
private void buildModelElementResource(Element element, BlockModelView blockModel) {
|
||||
|
||||
//create faces
|
||||
Vector3f from = bmer.getFrom();
|
||||
Vector3f to = bmer.getTo();
|
||||
Vector3f from = element.getFrom();
|
||||
Vector3f to = element.getTo();
|
||||
|
||||
float
|
||||
minX = Math.min(from.getX(), to.getX()),
|
||||
@ -143,24 +158,24 @@ private void buildModelElementResource(BlockModelResource.Element bmer, BlockMod
|
||||
c[7].x = maxX; c[7].y = maxY; c[7].z = maxZ;
|
||||
|
||||
int modelStart = blockModel.getStart();
|
||||
createElementFace(bmer, Direction.DOWN, c[0], c[2], c[3], c[1]);
|
||||
createElementFace(bmer, Direction.UP, c[5], c[7], c[6], c[4]);
|
||||
createElementFace(bmer, Direction.NORTH, c[2], c[0], c[4], c[6]);
|
||||
createElementFace(bmer, Direction.SOUTH, c[1], c[3], c[7], c[5]);
|
||||
createElementFace(bmer, Direction.WEST, c[0], c[1], c[5], c[4]);
|
||||
createElementFace(bmer, Direction.EAST, c[3], c[2], c[6], c[7]);
|
||||
createElementFace(element, Direction.DOWN, c[0], c[2], c[3], c[1]);
|
||||
createElementFace(element, Direction.UP, c[5], c[7], c[6], c[4]);
|
||||
createElementFace(element, Direction.NORTH, c[2], c[0], c[4], c[6]);
|
||||
createElementFace(element, Direction.SOUTH, c[1], c[3], c[7], c[5]);
|
||||
createElementFace(element, Direction.WEST, c[0], c[1], c[5], c[4]);
|
||||
createElementFace(element, Direction.EAST, c[3], c[2], c[6], c[7]);
|
||||
blockModel.initialize(modelStart);
|
||||
|
||||
//rotate and scale down
|
||||
blockModel.transform(modelElementTransform
|
||||
.copy(bmer.getRotationMatrix())
|
||||
.copy(element.getRotation().getMatrix())
|
||||
.scale(BLOCK_SCALE, BLOCK_SCALE, BLOCK_SCALE)
|
||||
);
|
||||
}
|
||||
|
||||
private final VectorM3f faceRotationVector = new VectorM3f(0, 0, 0);
|
||||
private void createElementFace(BlockModelResource.Element element, Direction faceDir, VectorM3f c0, VectorM3f c1, VectorM3f c2, VectorM3f c3) {
|
||||
BlockModelResource.Element.Face face = element.getFaces().get(faceDir);
|
||||
private void createElementFace(Element element, Direction faceDir, VectorM3f c0, VectorM3f c1, VectorM3f c2, VectorM3f c3) {
|
||||
Face face = element.getFaces().get(faceDir);
|
||||
if (face == null) return;
|
||||
|
||||
Vector3i faceDirVector = faceDir.toVector();
|
||||
@ -206,8 +221,8 @@ private void createElementFace(BlockModelResource.Element element, Direction fac
|
||||
);
|
||||
|
||||
// ####### texture
|
||||
Texture texture = face.getTexture();
|
||||
int textureId = texture.getId();
|
||||
ResourcePath<Texture> texturePath = face.getTexture().getTexturePath(modelResource.getTextures()::get);
|
||||
int textureId = textureGallery.get(texturePath);
|
||||
tileModel.setMaterialIndex(face1, textureId);
|
||||
tileModel.setMaterialIndex(face2, textureId);
|
||||
|
||||
@ -232,15 +247,13 @@ private void createElementFace(BlockModelResource.Element element, Direction fac
|
||||
|
||||
// UV-Lock counter-rotation
|
||||
float uvRotation = 0f;
|
||||
if (blockModelResource.isUVLock() && blockModelResource.hasRotation()) {
|
||||
Vector2f rotation = blockModelResource.getRotation();
|
||||
|
||||
float xRotSin = TrigMath.sin(rotation.getX() * TrigMath.DEG_TO_RAD);
|
||||
float xRotCos = TrigMath.cos(rotation.getX() * TrigMath.DEG_TO_RAD);
|
||||
if (variant.isUvlock() && variant.isRotated()) {
|
||||
float xRotSin = TrigMath.sin(variant.getX() * TrigMath.DEG_TO_RAD);
|
||||
float xRotCos = TrigMath.cos(variant.getX() * TrigMath.DEG_TO_RAD);
|
||||
|
||||
uvRotation =
|
||||
rotation.getY() * (faceDirVector.getY() * xRotCos + faceDirVector.getZ() * xRotSin) +
|
||||
rotation.getX() * (1 - faceDirVector.getY());
|
||||
variant.getY() * (faceDirVector.getY() * xRotCos + faceDirVector.getZ() * xRotSin) +
|
||||
variant.getX() * (1 - faceDirVector.getY());
|
||||
}
|
||||
|
||||
// rotate uv's
|
||||
@ -268,7 +281,7 @@ private void createElementFace(BlockModelResource.Element element, Direction fac
|
||||
|
||||
|
||||
// ####### face-tint
|
||||
if (face.isTinted()) {
|
||||
if (face.getTintindex() >= 0) {
|
||||
if (tintColor.a < 0) {
|
||||
blockColorCalculator.getBlockColor(block, tintColor);
|
||||
}
|
||||
@ -290,7 +303,7 @@ private void createElementFace(BlockModelResource.Element element, Direction fac
|
||||
|
||||
// ######## AO
|
||||
float ao0 = 1f, ao1 = 1f, ao2 = 1f, ao3 = 1f;
|
||||
if (blockModelResource.getModel().isAmbientOcclusion()){
|
||||
if (modelResource.isAmbientocclusion()){
|
||||
ao0 = testAo(c0, faceDir);
|
||||
ao1 = testAo(c1, faceDir);
|
||||
ao2 = testAo(c2, faceDir);
|
||||
@ -307,26 +320,29 @@ private void createElementFace(BlockModelResource.Element element, Direction fac
|
||||
faceDirVector.getZ()
|
||||
);
|
||||
makeRotationRelative(faceRotationVector);
|
||||
faceRotationVector.rotateAndScale(element.getRotationMatrix());
|
||||
faceRotationVector.rotateAndScale(element.getRotation().getMatrix());
|
||||
|
||||
float a = faceRotationVector.y;
|
||||
if (a > 0){
|
||||
mapColor.set(texture.getColorPremultiplied());
|
||||
if (tintColor.a >= 0) {
|
||||
mapColor.multiply(tintColor);
|
||||
if (a > 0 && texturePath != null){
|
||||
Texture texture = texturePath.getResource(resourcePack::getTexture);
|
||||
if (texture != null) {
|
||||
mapColor.set(texture.getColorPremultiplied());
|
||||
if (tintColor.a >= 0) {
|
||||
mapColor.multiply(tintColor);
|
||||
}
|
||||
|
||||
// apply light
|
||||
float combinedLight = Math.max(sunLight / 15f, blockLight / 15f);
|
||||
combinedLight = (1 - renderSettings.getAmbientLight()) * combinedLight + renderSettings.getAmbientLight();
|
||||
mapColor.r *= combinedLight;
|
||||
mapColor.g *= combinedLight;
|
||||
mapColor.b *= combinedLight;
|
||||
|
||||
if (mapColor.a > blockColorOpacity)
|
||||
blockColorOpacity = mapColor.a;
|
||||
|
||||
blockColor.add(mapColor);
|
||||
}
|
||||
|
||||
// apply light
|
||||
float combinedLight = Math.max(sunLight / 15f, blockLight / 15f);
|
||||
combinedLight = (1 - renderSettings.getAmbientLight()) * combinedLight + renderSettings.getAmbientLight();
|
||||
mapColor.r *= combinedLight;
|
||||
mapColor.g *= combinedLight;
|
||||
mapColor.b *= combinedLight;
|
||||
|
||||
if (mapColor.a > blockColorOpacity)
|
||||
blockColorOpacity = mapColor.a;
|
||||
|
||||
blockColor.add(mapColor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -355,7 +371,7 @@ private ExtendedBlock<?> getRotationRelativeBlock(int dx, int dy, int dz){
|
||||
}
|
||||
|
||||
private void makeRotationRelative(VectorM3f direction){
|
||||
direction.transform(blockModelResource.getRotationMatrix());
|
||||
direction.transform(variant.getRotationMatrix());
|
||||
}
|
||||
|
||||
private float testAo(VectorM3f vertex, Direction dir){
|
||||
@ -405,7 +421,7 @@ private float testAo(VectorM3f vertex, Direction dir){
|
||||
}
|
||||
|
||||
private static float hashToFloat(int x, int z, long seed) {
|
||||
final long hash = x * 73428767 ^ z * 4382893 ^ seed * 457;
|
||||
final long hash = x * 73428767L ^ z * 4382893L ^ seed * 457;
|
||||
return (hash * (hash + 456149) & 0x00ffffff) / (float) 0x01000000;
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ public String getBiome(int x, int y, int z) {
|
||||
z = z & 0xF;
|
||||
int biomeIntIndex = z * 16 + x;
|
||||
|
||||
if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT.getFullId();
|
||||
if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT.getFormatted();
|
||||
|
||||
return LegacyBiomes.idFor(biomes[biomeIntIndex]);
|
||||
}
|
||||
|
@ -124,8 +124,8 @@ public String getBiome(int x, int y, int z) {
|
||||
y = y / 4;
|
||||
int biomeIntIndex = y * 16 + z * 4 + x;
|
||||
|
||||
if (biomeIntIndex < 0) return Biome.DEFAULT.getFullId();
|
||||
if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT.getFullId();
|
||||
if (biomeIntIndex < 0) return Biome.DEFAULT.getFormatted();
|
||||
if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT.getFormatted();
|
||||
|
||||
return LegacyBiomes.idFor(biomes[biomeIntIndex]);
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
|
||||
@Override
|
||||
public String getBiome(int x, int y, int z) {
|
||||
if (biomes.length < 16) return Biome.DEFAULT.getFullId();
|
||||
if (biomes.length < 16) return Biome.DEFAULT.getFormatted();
|
||||
|
||||
x = (x & 0xF) / 4; // Math.floorMod(pos.getX(), 16)
|
||||
z = (z & 0xF) / 4;
|
||||
|
@ -30,10 +30,7 @@
|
||||
import de.bluecolored.bluemap.core.world.LightData;
|
||||
import net.querz.nbt.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
@ -125,7 +122,7 @@ public String getBiome(int x, int y, int z) {
|
||||
int sectionY = y >> 4;
|
||||
|
||||
Section section = getSection(sectionY);
|
||||
if (section == null) return Biome.DEFAULT.getFullId();
|
||||
if (section == null) return Biome.DEFAULT.getFormatted();
|
||||
|
||||
return section.getBiome(x, y, z);
|
||||
}
|
||||
@ -211,7 +208,7 @@ private BlockState readBlockStatePaletteEntry(CompoundTag paletteEntry) {
|
||||
String id = paletteEntry.getString("Name"); //shortcut to save time and memory
|
||||
if (AIR_ID.equals(id)) return BlockState.AIR;
|
||||
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
Map<String, String> properties = new LinkedHashMap<>();
|
||||
|
||||
if (paletteEntry.containsKey("Properties")) {
|
||||
CompoundTag propertiesTag = paletteEntry.getCompoundTag("Properties");
|
||||
@ -259,7 +256,7 @@ public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
}
|
||||
|
||||
public String getBiome(int x, int y, int z) {
|
||||
if (biomePalette.length == 0) return Biome.DEFAULT.getId();
|
||||
if (biomePalette.length == 0) return Biome.DEFAULT.getValue();
|
||||
if (biomes.length == 0) return biomePalette[0];
|
||||
|
||||
x = (x & 0xF) / 4; // Math.floorMod(pos.getX(), 16) / 4
|
||||
@ -270,7 +267,7 @@ public String getBiome(int x, int y, int z) {
|
||||
long value = MCAMath.getValueFromLongArray(biomes, biomeIndex, bitsPerBiome);
|
||||
if (value >= biomePalette.length) {
|
||||
Logger.global.noFloodWarning("biomepalettewarning", "Got biome-palette value " + value + " but palette has size of " + biomePalette.length + "! (Future occasions of this error will not be logged)");
|
||||
return Biome.DEFAULT.getId();
|
||||
return Biome.DEFAULT.getValue();
|
||||
}
|
||||
|
||||
return biomePalette[(int) value];
|
||||
|
@ -49,7 +49,7 @@ public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
|
||||
@Override
|
||||
public String getBiome(int x, int y, int z) {
|
||||
return Biome.DEFAULT.getFullId();
|
||||
return Biome.DEFAULT.getFormatted();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,7 +31,6 @@
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
import de.bluecolored.bluemap.core.world.Grid;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
import net.querz.nbt.CompoundTag;
|
||||
@ -99,10 +98,6 @@ public MCAWorld(Path worldFolder, int skyLight, boolean ignoreMissingLightData)
|
||||
}
|
||||
}
|
||||
|
||||
public BlockState getBlockState(Vector3i pos) {
|
||||
return getChunk(pos.getX() >> 4, pos.getZ() >> 4).getBlockState(pos.getX(), pos.getY(), pos.getZ());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MCAChunk getChunkAtBlock(int x, int y, int z) {
|
||||
return getChunk(x >> 4, z >> 4);
|
||||
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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 de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
@DebugDump
|
||||
public class BiomeConfig {
|
||||
|
||||
private final Map<String, Biome> biomes;
|
||||
|
||||
public BiomeConfig() {
|
||||
biomes = new HashMap<>();
|
||||
}
|
||||
|
||||
public void load(ConfigurationNode node) {
|
||||
for (Entry<Object, ? extends ConfigurationNode> e : node.childrenMap().entrySet()){
|
||||
String id = e.getKey().toString();
|
||||
Biome biome = Biome.create(id, e.getValue());
|
||||
biomes.put(id, biome);
|
||||
}
|
||||
}
|
||||
|
||||
public Biome getBiome(String id) {
|
||||
return biomes.getOrDefault(id, Biome.DEFAULT);
|
||||
}
|
||||
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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;
|
||||
|
||||
public class NoSuchResourceException extends Exception {
|
||||
private static final long serialVersionUID = -8545428385061010089L;
|
||||
|
||||
public NoSuchResourceException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NoSuchResourceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public NoSuchResourceException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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;
|
||||
|
||||
public class ParseResourceException extends Exception {
|
||||
private static final long serialVersionUID = -2857915193389089307L;
|
||||
|
||||
public ParseResourceException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ParseResourceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ParseResourceException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
@ -1,321 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.resourcepack.blockmodel.BlockModelResource;
|
||||
import de.bluecolored.bluemap.core.resourcepack.blockmodel.TransformedBlockModelResource;
|
||||
import de.bluecolored.bluemap.core.resourcepack.blockstate.BlockStateResource;
|
||||
import de.bluecolored.bluemap.core.resourcepack.blockstate.BlockStateResource.Builder;
|
||||
import de.bluecolored.bluemap.core.resourcepack.fileaccess.BluemapAssetOverrideFileAccess;
|
||||
import de.bluecolored.bluemap.core.resourcepack.fileaccess.CaseInsensitiveFileAccess;
|
||||
import de.bluecolored.bluemap.core.resourcepack.fileaccess.CombinedFileAccess;
|
||||
import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess;
|
||||
import de.bluecolored.bluemap.core.resourcepack.texture.Texture;
|
||||
import de.bluecolored.bluemap.core.resourcepack.texture.TextureGallery;
|
||||
import de.bluecolored.bluemap.core.util.Tristate;
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
import de.bluecolored.bluemap.core.world.BlockProperties;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
import org.apache.commons.io.output.ByteArrayOutputStream;
|
||||
import org.spongepowered.configurate.gson.GsonConfigurationLoader;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents all resources (BlockStates / BlockModels and Textures) that are loaded and used to generate map-models.
|
||||
*/
|
||||
@DebugDump
|
||||
public class ResourcePack {
|
||||
|
||||
private final Map<String, BlockStateResource> blockStateResources;
|
||||
private final Map<String, BlockModelResource> blockModelResources;
|
||||
private final TextureGallery textures;
|
||||
|
||||
private final BlockPropertiesConfig blockPropertiesConfig;
|
||||
private final BiomeConfig biomeConfig;
|
||||
private final BlockColorCalculatorFactory blockColorCalculatorFactory;
|
||||
|
||||
private final LoadingCache<BlockState, BlockProperties> blockPropertiesCache;
|
||||
|
||||
public ResourcePack() {
|
||||
blockStateResources = new HashMap<>();
|
||||
blockModelResources = new HashMap<>();
|
||||
textures = new TextureGallery();
|
||||
|
||||
blockPropertiesConfig = new BlockPropertiesConfig();
|
||||
biomeConfig = new BiomeConfig();
|
||||
blockColorCalculatorFactory = new BlockColorCalculatorFactory();
|
||||
|
||||
blockPropertiesCache = Caffeine.newBuilder()
|
||||
.executor(BlueMap.THREAD_POOL)
|
||||
.maximumSize(10000)
|
||||
.build(this::getBlockPropertiesNoCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.<br>
|
||||
* <br>
|
||||
* 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<File> sources) throws IOException, InterruptedException {
|
||||
load(sources.toArray(new File[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.<br>
|
||||
* <br>
|
||||
* 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) throws InterruptedException {
|
||||
try (CombinedFileAccess combinedSources = new CombinedFileAccess()){
|
||||
for (File file : sources) {
|
||||
if (Thread.interrupted()) throw new InterruptedException();
|
||||
|
||||
try {
|
||||
combinedSources.addFileAccess(FileAccess.of(file));
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("Failed to read ResourcePack: " + file, e);
|
||||
}
|
||||
}
|
||||
|
||||
FileAccess sourcesAccess = new CaseInsensitiveFileAccess(new BluemapAssetOverrideFileAccess(combinedSources));
|
||||
|
||||
textures.reloadAllTextures(sourcesAccess);
|
||||
|
||||
Builder builder = BlockStateResource.builder(sourcesAccess, this);
|
||||
|
||||
Collection<String> namespaces = sourcesAccess.listFolders("assets");
|
||||
int i = 0;
|
||||
for (String namespaceRoot : namespaces) {
|
||||
if (Thread.interrupted()) throw new InterruptedException();
|
||||
|
||||
i++;
|
||||
|
||||
//load blockstates
|
||||
String namespace = namespaceRoot.substring("assets/".length());
|
||||
Logger.global.logInfo("Loading " + namespace + " assets (" + i + "/" + namespaces.size() + ")...");
|
||||
|
||||
String blockstatesRootPath = namespaceRoot + "/blockstates";
|
||||
Collection<String> blockstateFiles = sourcesAccess.listFiles(blockstatesRootPath, true);
|
||||
for (String blockstateFile : blockstateFiles) {
|
||||
if (Thread.interrupted()) throw new InterruptedException();
|
||||
|
||||
String filename = blockstateFile.substring(blockstatesRootPath.length() + 1);
|
||||
if (!filename.endsWith(".json")) continue;
|
||||
|
||||
String jsonFileName = filename.substring(0, filename.length() - 5);
|
||||
try {
|
||||
blockStateResources.put(namespace + ":" + jsonFileName, builder.build(blockstateFile));
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to load blockstate: " + namespace + ":" + jsonFileName, ex);
|
||||
}
|
||||
}
|
||||
|
||||
//load biomes
|
||||
try {
|
||||
GsonConfigurationLoader loader = GsonConfigurationLoader.builder()
|
||||
.source(() -> new BufferedReader(new InputStreamReader(sourcesAccess.readFile(
|
||||
"assets/" + namespace + "/biomes.json"))))
|
||||
.build();
|
||||
biomeConfig.load(loader.load());
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to load biomes.conf from: " + namespace, ex);
|
||||
}
|
||||
|
||||
//load block properties
|
||||
try {
|
||||
GsonConfigurationLoader loader = GsonConfigurationLoader.builder()
|
||||
.source(() -> new BufferedReader(new InputStreamReader(sourcesAccess.readFile(
|
||||
"assets/" + namespace + "/blockProperties.json"))))
|
||||
.build();
|
||||
blockPropertiesConfig.load(loader.load());
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to load biomes.conf from: " + namespace, ex);
|
||||
}
|
||||
|
||||
//load block colors
|
||||
try {
|
||||
GsonConfigurationLoader loader = GsonConfigurationLoader.builder()
|
||||
.source(() -> new BufferedReader(new InputStreamReader(sourcesAccess.readFile(
|
||||
"assets/" + namespace + "/blockColors.json"))))
|
||||
.build();
|
||||
blockColorCalculatorFactory.load(loader.load());
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to load biomes.conf from: " + namespace, ex);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
blockColorCalculatorFactory.setFoliageMap(
|
||||
ImageIO.read(sourcesAccess.readFile("assets/minecraft/textures/colormap/foliage.png"))
|
||||
);
|
||||
} catch (IOException | ArrayIndexOutOfBoundsException ex) {
|
||||
Logger.global.logError("Failed to load foliagemap!", ex);
|
||||
}
|
||||
|
||||
try {
|
||||
blockColorCalculatorFactory.setGrassMap(
|
||||
ImageIO.read(sourcesAccess.readFile("assets/minecraft/textures/colormap/grass.png"))
|
||||
);
|
||||
} catch (IOException | ArrayIndexOutOfBoundsException ex) {
|
||||
Logger.global.logError("Failed to load grassmap!", ex);
|
||||
}
|
||||
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to close FileAccess!", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link TextureGallery#loadTextureFile(File)}
|
||||
* @see TextureGallery#loadTextureFile(File)
|
||||
*/
|
||||
public void loadTextureFile(File file) throws IOException, ParseResourceException {
|
||||
textures.loadTextureFile(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link TextureGallery#saveTextureFile(File)}
|
||||
* @see TextureGallery#saveTextureFile(File)
|
||||
*/
|
||||
public void saveTextureFile(File file) throws IOException {
|
||||
textures.saveTextureFile(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 BlockProperties getBlockProperties(BlockState state) {
|
||||
return blockPropertiesCache.get(state);
|
||||
}
|
||||
|
||||
private BlockProperties getBlockPropertiesNoCache(BlockState state) {
|
||||
BlockProperties.Builder props = blockPropertiesConfig.getBlockProperties(state).toBuilder();
|
||||
|
||||
if (props.isOccluding() == Tristate.UNDEFINED || props.isCulling() == Tristate.UNDEFINED) {
|
||||
try {
|
||||
BlockStateResource resource = getBlockStateResource(state);
|
||||
for (TransformedBlockModelResource bmr : resource.getModels(state, new ArrayList<>())) {
|
||||
if (props.isOccluding() == Tristate.UNDEFINED) props.occluding(bmr.getModel().isOccluding());
|
||||
if (props.isCulling() == Tristate.UNDEFINED) props.culling(bmr.getModel().isCulling());
|
||||
}
|
||||
} catch (NoSuchResourceException ignore) {}
|
||||
}
|
||||
|
||||
return props.build();
|
||||
}
|
||||
|
||||
public Biome getBiome(String id) {
|
||||
return biomeConfig.getBiome(id);
|
||||
}
|
||||
|
||||
public Map<String, BlockStateResource> getBlockStateResources() {
|
||||
return blockStateResources;
|
||||
}
|
||||
|
||||
public Map<String, BlockModelResource> getBlockModelResources() {
|
||||
return blockModelResources;
|
||||
}
|
||||
|
||||
public TextureGallery getTextures() {
|
||||
return textures;
|
||||
}
|
||||
|
||||
public BlockColorCalculatorFactory getBlockColorCalculatorFactory() {
|
||||
return blockColorCalculatorFactory;
|
||||
}
|
||||
|
||||
public static String namespacedToAbsoluteResourcePath(String namespacedPath, String resourceTypeFolder) {
|
||||
String path = namespacedPath;
|
||||
|
||||
resourceTypeFolder = FileAccess.normalize(resourceTypeFolder);
|
||||
|
||||
int namespaceIndex = path.indexOf(':');
|
||||
String namespace = "minecraft";
|
||||
if (namespaceIndex != -1) {
|
||||
namespace = path.substring(0, namespaceIndex);
|
||||
path = path.substring(namespaceIndex + 1);
|
||||
}
|
||||
|
||||
if (resourceTypeFolder.isEmpty()) {
|
||||
path = "assets/" + namespace + "/" + FileAccess.normalize(path);
|
||||
} else {
|
||||
path = "assets/" + namespace + "/" + resourceTypeFolder + "/" + FileAccess.normalize(path);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Caches a full InputStream in a byte-array that can be read later
|
||||
*/
|
||||
public static class Resource {
|
||||
|
||||
private final byte[] data;
|
||||
|
||||
public Resource(InputStream data) throws IOException {
|
||||
try (ByteArrayOutputStream bout = new ByteArrayOutputStream()) {
|
||||
bout.write(data);
|
||||
this.data = bout.toByteArray();
|
||||
} finally {
|
||||
data.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new InputStream to read this resource
|
||||
*/
|
||||
public InputStream read() {
|
||||
return new ByteArrayInputStream(this.data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,493 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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.blockmodel;
|
||||
|
||||
import com.flowpowered.math.TrigMath;
|
||||
import com.flowpowered.math.vector.Vector3f;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import com.flowpowered.math.vector.Vector4f;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resourcepack.texture.Texture;
|
||||
import de.bluecolored.bluemap.core.resourcepack.blockmodel.BlockModelResource.Element.Face;
|
||||
import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess;
|
||||
import de.bluecolored.bluemap.core.util.Direction;
|
||||
import de.bluecolored.bluemap.core.util.math.Axis;
|
||||
import de.bluecolored.bluemap.core.util.math.MatrixM4f;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.gson.GsonConfigurationLoader;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class BlockModelResource {
|
||||
private static final double FIT_TO_BLOCK_SCALE_MULTIPLIER = 2 - Math.sqrt(2);
|
||||
|
||||
private ModelType modelType = ModelType.NORMAL;
|
||||
|
||||
private boolean culling = false;
|
||||
private boolean occluding = false;
|
||||
|
||||
private final boolean ambientOcclusion = true; //TODO: wat?
|
||||
private final Collection<Element> elements = new ArrayList<>();
|
||||
private final Map<String, Texture> textures = new HashMap<>();
|
||||
|
||||
private BlockModelResource() {}
|
||||
|
||||
public ModelType getType() {
|
||||
return modelType;
|
||||
}
|
||||
|
||||
public boolean isAmbientOcclusion() {
|
||||
return ambientOcclusion;
|
||||
}
|
||||
|
||||
public boolean isCulling() {
|
||||
return culling;
|
||||
}
|
||||
|
||||
public boolean isOccluding() {
|
||||
return occluding;
|
||||
}
|
||||
|
||||
public Collection<Element> getElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
public Texture getTexture(String key) {
|
||||
return textures.get(key);
|
||||
}
|
||||
|
||||
public class Element {
|
||||
|
||||
private Vector3f from = Vector3f.ZERO, to = new Vector3f(16f, 16f, 16f);
|
||||
private Rotation rotation = new Rotation();
|
||||
private MatrixM4f rotationMatrix;
|
||||
private boolean shade = true;
|
||||
private EnumMap<Direction, Face> faces = new EnumMap<>(Direction.class);
|
||||
private boolean fullCube = false;
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public BlockModelResource getModel() {
|
||||
return BlockModelResource.this;
|
||||
}
|
||||
|
||||
public Vector3f getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public Vector3f getTo() {
|
||||
return to;
|
||||
}
|
||||
|
||||
public Rotation getRotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
public MatrixM4f getRotationMatrix() {
|
||||
return rotationMatrix;
|
||||
}
|
||||
|
||||
public boolean isShade() {
|
||||
return shade;
|
||||
}
|
||||
|
||||
public boolean isFullCube() {
|
||||
return fullCube;
|
||||
}
|
||||
|
||||
public EnumMap<Direction, Face> 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 static final String JSON_COMMENT = "__comment";
|
||||
private static final Vector3f FULL_CUBE_FROM = Vector3f.ZERO;
|
||||
private static final Vector3f FULL_CUBE_TO = new Vector3f(16f, 16f, 16f);
|
||||
|
||||
private FileAccess sourcesAccess;
|
||||
private ResourcePack resourcePack;
|
||||
|
||||
private HashMap<String, String> 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 {
|
||||
return build(modelPath, Collections.emptyMap());
|
||||
}
|
||||
|
||||
public synchronized BlockModelResource build(String modelPath, Map<String, String> textures) throws IOException, ParseResourceException {
|
||||
this.textures.clear();
|
||||
this.textures.putAll(textures);
|
||||
return buildNoReset(modelPath, true, modelPath);
|
||||
}
|
||||
|
||||
private BlockModelResource buildNoReset(String modelPath, boolean renderElements, String topModelPath) throws IOException, ParseResourceException {
|
||||
BlockModelResource blockModel = new BlockModelResource();
|
||||
|
||||
InputStream fileIn = sourcesAccess.readFile(modelPath);
|
||||
ConfigurationNode config = GsonConfigurationLoader.builder()
|
||||
.source(() -> new BufferedReader(new InputStreamReader(fileIn, StandardCharsets.UTF_8)))
|
||||
.build()
|
||||
.load();
|
||||
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : config.node("textures").childrenMap().entrySet()) {
|
||||
if (entry.getKey().equals(JSON_COMMENT)) continue;
|
||||
|
||||
String key = entry.getKey().toString();
|
||||
String value = entry.getValue().getString();
|
||||
|
||||
if (("#" + key).equals(value)) continue; // skip direct loop
|
||||
|
||||
textures.putIfAbsent(key, value);
|
||||
}
|
||||
|
||||
String parentPath = config.node("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, renderElements && config.node("elements").virtual(), topModelPath);
|
||||
} catch (IOException ex) {
|
||||
throw new ParseResourceException("Failed to load parent model " + parentPath + " of model " + topModelPath, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (renderElements) {
|
||||
for (ConfigurationNode elementNode : config.node("elements").childrenList()) {
|
||||
blockModel.elements.add(buildElement(blockModel, elementNode, topModelPath));
|
||||
}
|
||||
}
|
||||
|
||||
for (String key : textures.keySet()) {
|
||||
try {
|
||||
blockModel.textures.put(key, getTexture("#" + key));
|
||||
} catch (NoSuchElementException | FileNotFoundException ignore) {
|
||||
// ignore this so unused textures can remain unresolved. See issue #147
|
||||
|
||||
//throw new ParseResourceException("Failed to map Texture key '" + key + "' for model '" + topModelPath + "': " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
//check block properties
|
||||
for (Element element : blockModel.elements) {
|
||||
if (element.isFullCube()) {
|
||||
blockModel.occluding = true;
|
||||
|
||||
blockModel.culling = true;
|
||||
for (Direction dir : Direction.values()) {
|
||||
Face face = element.faces.get(dir);
|
||||
if (face == null) {
|
||||
blockModel.culling = false;
|
||||
break;
|
||||
}
|
||||
|
||||
Texture texture = face.getTexture();
|
||||
if (texture == null) {
|
||||
blockModel.culling = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (texture.getColorStraight().a < 1) {
|
||||
blockModel.culling = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return blockModel;
|
||||
}
|
||||
|
||||
private Element buildElement(BlockModelResource model, ConfigurationNode node, String topModelPath) throws ParseResourceException {
|
||||
Element element = model.new Element();
|
||||
|
||||
element.from = readVector3f(node.node("from"));
|
||||
element.to = readVector3f(node.node("to"));
|
||||
|
||||
element.shade = node.node("shade").getBoolean(true);
|
||||
|
||||
boolean fullElement = element.from.equals(FULL_CUBE_FROM) && element.to.equals(FULL_CUBE_TO);
|
||||
|
||||
if (!node.node("rotation").virtual()) {
|
||||
element.rotation.angle = node.node("rotation", "angle").getFloat(0);
|
||||
element.rotation.axis = Axis.fromString(node.node("rotation", "axis").getString("x"));
|
||||
if (!node.node("rotation", "origin").virtual()) element.rotation.origin = readVector3f(node.node("rotation", "origin"));
|
||||
element.rotation.rescale = node.node("rotation", "rescale").getBoolean(false);
|
||||
|
||||
// rotation matrix
|
||||
float angle = element.rotation.angle;
|
||||
Vector3i axis = element.rotation.axis.toVector();
|
||||
Vector3f origin = element.rotation.origin;
|
||||
boolean rescale = element.rotation.rescale;
|
||||
|
||||
MatrixM4f rot = new MatrixM4f();
|
||||
if (angle != 0f) {
|
||||
rot.translate(-origin.getX(), -origin.getY(), -origin.getZ());
|
||||
rot.rotate(
|
||||
angle,
|
||||
axis.getX(),
|
||||
axis.getY(),
|
||||
axis.getZ()
|
||||
);
|
||||
|
||||
if (rescale) {
|
||||
float scale = (float) (Math.abs(TrigMath.sin(angle * TrigMath.DEG_TO_RAD)) * FIT_TO_BLOCK_SCALE_MULTIPLIER);
|
||||
rot.scale(
|
||||
(1 - axis.getX()) * scale + 1,
|
||||
(1 - axis.getY()) * scale + 1,
|
||||
(1 - axis.getZ()) * scale + 1
|
||||
);
|
||||
}
|
||||
|
||||
rot.translate(origin.getX(), origin.getY(), origin.getZ());
|
||||
}
|
||||
element.rotationMatrix = rot;
|
||||
} else {
|
||||
element.rotationMatrix = new MatrixM4f();
|
||||
}
|
||||
|
||||
boolean allDirs = true;
|
||||
for (Direction direction : Direction.values()) {
|
||||
ConfigurationNode faceNode = node.node("faces", direction.name().toLowerCase());
|
||||
if (!faceNode.virtual()) {
|
||||
try {
|
||||
Face face = buildFace(element, direction, faceNode);
|
||||
element.faces.put(direction, face);
|
||||
} catch (ParseResourceException | IOException ex) {
|
||||
throw new ParseResourceException("Failed to parse an " + direction + " face for the model " + topModelPath + "!", ex);
|
||||
}
|
||||
} else {
|
||||
allDirs = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (fullElement && allDirs) element.fullCube = true;
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
private Face buildFace(Element element, Direction direction, ConfigurationNode node) throws ParseResourceException, IOException {
|
||||
try {
|
||||
Face face = element.new Face(direction);
|
||||
|
||||
if (!node.node("uv").virtual()) face.uv = readVector4f(node.node("uv"));
|
||||
face.texture = getTexture(node.node("texture").getString(""));
|
||||
face.tinted = node.node("tintindex").getInt(-1) >= 0;
|
||||
face.rotation = node.node("rotation").getInt(0);
|
||||
|
||||
if (!node.node("cullface").virtual()) {
|
||||
String dirString = node.node("cullface").getString("");
|
||||
if (dirString.equals("bottom")) dirString = "down";
|
||||
if (dirString.equals("top")) dirString = "up";
|
||||
|
||||
try {
|
||||
face.cullface = Direction.fromString(dirString);
|
||||
} catch (IllegalArgumentException ignore) {}
|
||||
}
|
||||
|
||||
return face;
|
||||
} catch (FileNotFoundException ex) {
|
||||
throw new ParseResourceException("There is no texture with the path: " + node.node("texture").getString(), ex);
|
||||
} catch (NoSuchElementException ex) {
|
||||
throw new ParseResourceException("Texture key '" + node.node("texture").getString() + "' has no texture assigned!", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3f readVector3f(ConfigurationNode node) throws ParseResourceException {
|
||||
List<? extends ConfigurationNode> nodeList = node.childrenList();
|
||||
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<? extends ConfigurationNode> nodeList = node.childrenList();
|
||||
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 {
|
||||
return getTexture(key, 0);
|
||||
}
|
||||
|
||||
private Texture getTexture(String key, int depth) throws NoSuchElementException, FileNotFoundException, IOException {
|
||||
if (key.isEmpty() || key.equals("#")) throw new NoSuchElementException("Empty texture key or name!");
|
||||
|
||||
if (depth > 10) throw new NoSuchElementException("Recursive texture-variable!");
|
||||
|
||||
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, depth + 1);
|
||||
}
|
||||
|
||||
String path = ResourcePack.namespacedToAbsoluteResourcePath(key, "textures") + ".png";
|
||||
|
||||
Texture texture;
|
||||
try {
|
||||
texture = resourcePack.getTextures().get(path);
|
||||
} catch (NoSuchElementException ex) {
|
||||
texture = resourcePack.getTextures().loadTexture(sourcesAccess, path);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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.blockmodel;
|
||||
|
||||
public enum ModelType {
|
||||
|
||||
NORMAL,
|
||||
LIQUID;
|
||||
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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.blockmodel;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2f;
|
||||
import de.bluecolored.bluemap.core.util.math.MatrixM3f;
|
||||
|
||||
public class TransformedBlockModelResource {
|
||||
|
||||
private final Vector2f rotation;
|
||||
private final boolean uvLock;
|
||||
private final BlockModelResource model;
|
||||
|
||||
private final boolean hasRotation;
|
||||
private final MatrixM3f rotationMatrix;
|
||||
|
||||
public TransformedBlockModelResource(Vector2f rotation, boolean uvLock, BlockModelResource model) {
|
||||
this.model = model;
|
||||
this.rotation = rotation;
|
||||
this.uvLock = uvLock;
|
||||
|
||||
this.hasRotation = !rotation.equals(Vector2f.ZERO);
|
||||
this.rotationMatrix = new MatrixM3f()
|
||||
.rotate(
|
||||
-rotation.getX(),
|
||||
-rotation.getY(),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
public Vector2f getRotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
public boolean hasRotation() {
|
||||
return hasRotation;
|
||||
}
|
||||
|
||||
public MatrixM3f getRotationMatrix() {
|
||||
return rotationMatrix;
|
||||
}
|
||||
|
||||
public boolean isUVLock() {
|
||||
return uvLock;
|
||||
}
|
||||
|
||||
public BlockModelResource getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
}
|
@ -1,418 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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.blockstate;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2f;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resourcepack.blockmodel.BlockModelResource;
|
||||
import de.bluecolored.bluemap.core.resourcepack.blockmodel.TransformedBlockModelResource;
|
||||
import de.bluecolored.bluemap.core.resourcepack.blockstate.PropertyCondition.All;
|
||||
import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.gson.GsonConfigurationLoader;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class BlockStateResource {
|
||||
|
||||
private final List<Variant> variants = new ArrayList<>(0);
|
||||
private final Collection<Variant> multipart = new ArrayList<>(0);
|
||||
|
||||
private BlockStateResource() {}
|
||||
|
||||
public Collection<TransformedBlockModelResource> getModels(BlockState blockState, Collection<TransformedBlockModelResource> targetCollection) {
|
||||
return getModels(blockState, 0, 0, 0, targetCollection);
|
||||
}
|
||||
|
||||
public Collection<TransformedBlockModelResource> getModels(BlockState blockState, int x, int y, int z, Collection<TransformedBlockModelResource> targetCollection) {
|
||||
targetCollection.clear();
|
||||
|
||||
Variant allMatch = null;
|
||||
for (Variant variant : variants) {
|
||||
if (variant.condition.matches(blockState)) {
|
||||
if (variant.condition instanceof All) { //only use "all" condition if nothing else matched
|
||||
if (allMatch == null) allMatch = variant;
|
||||
continue;
|
||||
}
|
||||
|
||||
targetCollection.add(variant.getModel(x, y, z));
|
||||
return targetCollection;
|
||||
}
|
||||
}
|
||||
|
||||
if (allMatch != null) {
|
||||
targetCollection.add(allMatch.getModel(x, y, z));
|
||||
return targetCollection;
|
||||
}
|
||||
|
||||
for (Variant variant : multipart) {
|
||||
if (variant.condition.matches(blockState)) {
|
||||
targetCollection.add(variant.getModel(x, y, z));
|
||||
}
|
||||
}
|
||||
|
||||
//fallback to first variant
|
||||
if (targetCollection.isEmpty() && !variants.isEmpty()) {
|
||||
targetCollection.add(variants.get(0).getModel(x, y, z));
|
||||
}
|
||||
|
||||
return targetCollection;
|
||||
}
|
||||
|
||||
public static Builder builder(FileAccess sourcesAccess, ResourcePack resourcePack) {
|
||||
return new Builder(sourcesAccess, resourcePack);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private static final String JSON_COMMENT = "__comment";
|
||||
|
||||
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 {
|
||||
|
||||
InputStream fileIn = sourcesAccess.readFile(blockstateFile);
|
||||
ConfigurationNode config = GsonConfigurationLoader.builder()
|
||||
.source(() -> new BufferedReader(new InputStreamReader(fileIn, StandardCharsets.UTF_8)))
|
||||
.build()
|
||||
.load();
|
||||
|
||||
if (!config.node("forge_marker").virtual()) {
|
||||
return buildForge(config, blockstateFile);
|
||||
}
|
||||
|
||||
BlockStateResource blockState = new BlockStateResource();
|
||||
|
||||
// create variants
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : config.node("variants").childrenMap().entrySet()) {
|
||||
if (entry.getKey().equals(JSON_COMMENT)) continue;
|
||||
|
||||
try {
|
||||
String conditionString = entry.getKey().toString();
|
||||
ConfigurationNode transformedModelNode = entry.getValue();
|
||||
|
||||
//some exceptions in 1.12 resource packs that we ignore
|
||||
if (conditionString.equals("all") || conditionString.equals("map")) continue;
|
||||
|
||||
Variant variant = new Variant();
|
||||
variant.condition = parseConditionString(conditionString);
|
||||
variant.models = loadModels(transformedModelNode, blockstateFile, null);
|
||||
|
||||
variant.updateTotalWeight();
|
||||
variant.checkValid();
|
||||
|
||||
blockState.variants.add(variant);
|
||||
} catch (ParseResourceException | RuntimeException e) {
|
||||
logParseError("Failed to parse a variant of " + blockstateFile, e);
|
||||
}
|
||||
}
|
||||
|
||||
// create multipart
|
||||
for (ConfigurationNode partNode : config.node("multipart").childrenList()) {
|
||||
try {
|
||||
Variant variant = new Variant();
|
||||
ConfigurationNode whenNode = partNode.node("when");
|
||||
if (!whenNode.virtual()) {
|
||||
variant.condition = parseCondition(whenNode);
|
||||
}
|
||||
variant.models = loadModels(partNode.node("apply"), blockstateFile, null);
|
||||
|
||||
variant.updateTotalWeight();
|
||||
variant.checkValid();
|
||||
|
||||
blockState.multipart.add(variant);
|
||||
} catch (ParseResourceException | RuntimeException e) {
|
||||
logParseError("Failed to parse a multipart-part of " + blockstateFile, e);
|
||||
}
|
||||
}
|
||||
|
||||
return blockState;
|
||||
}
|
||||
|
||||
private Collection<Weighted<TransformedBlockModelResource>> loadModels(ConfigurationNode node, String blockstateFile, Map<String, String> overrideTextures) {
|
||||
Collection<Weighted<TransformedBlockModelResource>> models = new ArrayList<>();
|
||||
|
||||
if (node.isList()) {
|
||||
for (ConfigurationNode modelNode : node.childrenList()) {
|
||||
try {
|
||||
models.add(loadModel(modelNode, overrideTextures));
|
||||
} catch (ParseResourceException ex) {
|
||||
logParseError("Failed to load a model trying to parse " + blockstateFile, ex);
|
||||
}
|
||||
}
|
||||
} else if (node.isMap()) {
|
||||
try {
|
||||
models.add(loadModel(node, overrideTextures));
|
||||
} catch (ParseResourceException ex) {
|
||||
logParseError("Failed to load a model trying to parse " + blockstateFile, ex);
|
||||
}
|
||||
}
|
||||
|
||||
return models;
|
||||
}
|
||||
|
||||
private Weighted<TransformedBlockModelResource> loadModel(ConfigurationNode node, Map<String, String> overrideTextures) throws ParseResourceException {
|
||||
String namespacedModelPath = node.node("model").getString();
|
||||
if (namespacedModelPath == null)
|
||||
throw new ParseResourceException("No model defined!");
|
||||
|
||||
String modelPath = ResourcePack.namespacedToAbsoluteResourcePath(namespacedModelPath, "models") + ".json";
|
||||
BlockModelResource model = resourcePack.getBlockModelResources().get(modelPath);
|
||||
if (model == null) {
|
||||
BlockModelResource.Builder builder = BlockModelResource.builder(sourcesAccess, resourcePack);
|
||||
try {
|
||||
if (overrideTextures != null) model = builder.build(modelPath, overrideTextures);
|
||||
else model = builder.build(modelPath);
|
||||
} catch (IOException e) {
|
||||
throw new ParseResourceException("Failed to load model " + modelPath, e);
|
||||
}
|
||||
|
||||
resourcePack.getBlockModelResources().put(modelPath, model);
|
||||
}
|
||||
|
||||
Vector2f rotation = new Vector2f(node.node("x").getFloat(0), node.node("y").getFloat(0));
|
||||
boolean uvLock = node.node("uvlock").getBoolean(false);
|
||||
|
||||
TransformedBlockModelResource transformedModel = new TransformedBlockModelResource(rotation, uvLock, model);
|
||||
return new Weighted<>(transformedModel, node.node("weight").getDouble(1d));
|
||||
}
|
||||
|
||||
private PropertyCondition parseCondition(ConfigurationNode conditionNode) {
|
||||
List<PropertyCondition> andConditions = new ArrayList<>();
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : conditionNode.childrenMap().entrySet()) {
|
||||
String key = entry.getKey().toString();
|
||||
if (key.equals(JSON_COMMENT)) continue;
|
||||
|
||||
if (key.equals("OR")) {
|
||||
List<PropertyCondition> orConditions = new ArrayList<>();
|
||||
for (ConfigurationNode orConditionNode : entry.getValue().childrenList()) {
|
||||
orConditions.add(parseCondition(orConditionNode));
|
||||
}
|
||||
andConditions.add(
|
||||
PropertyCondition.or(orConditions.toArray(new PropertyCondition[0])));
|
||||
} else {
|
||||
String[] values = StringUtils.split(entry.getValue().getString(""), '|');
|
||||
andConditions.add(PropertyCondition.property(key, values));
|
||||
}
|
||||
}
|
||||
|
||||
return PropertyCondition.and(andConditions.toArray(new PropertyCondition[0]));
|
||||
}
|
||||
|
||||
private PropertyCondition parseConditionString(String conditionString) throws IllegalArgumentException {
|
||||
List<PropertyCondition> conditions = new ArrayList<>();
|
||||
if (!conditionString.isEmpty() && !conditionString.equals("default") && !conditionString.equals("normal")) {
|
||||
String[] conditionSplit = StringUtils.split(conditionString, ',');
|
||||
for (String element : conditionSplit) {
|
||||
String[] keyval = StringUtils.split(element, "=", 2);
|
||||
if (keyval.length < 2)
|
||||
throw new IllegalArgumentException("Condition-String '" + conditionString + "' is invalid!");
|
||||
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[0]));
|
||||
}
|
||||
|
||||
return condition;
|
||||
}
|
||||
|
||||
private BlockStateResource buildForge(ConfigurationNode config, String blockstateFile) {
|
||||
ConfigurationNode modelDefaults = config.node("defaults");
|
||||
|
||||
List<ForgeVariant> variants = new ArrayList<>();
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : config.node("variants").childrenMap().entrySet()) {
|
||||
if (entry.getKey().equals(JSON_COMMENT)) continue;
|
||||
if (isForgeStraightVariant(entry.getValue())) continue;
|
||||
|
||||
// create variants for single property
|
||||
List<ForgeVariant> propertyVariants = new ArrayList<>();
|
||||
String key = entry.getKey().toString();
|
||||
for (Entry<Object, ? extends ConfigurationNode> value : entry.getValue().childrenMap().entrySet()) {
|
||||
if (value.getKey().equals(JSON_COMMENT)) continue;
|
||||
|
||||
ForgeVariant variant = new ForgeVariant();
|
||||
variant.properties.put(key, value.getKey().toString());
|
||||
variant.node = value.getValue();
|
||||
propertyVariants.add(variant);
|
||||
}
|
||||
|
||||
// join variants
|
||||
if (variants.isEmpty()){
|
||||
variants = propertyVariants;
|
||||
} else {
|
||||
List<ForgeVariant> oldVariants = variants;
|
||||
variants = new ArrayList<>(oldVariants.size() * propertyVariants.size());
|
||||
for (ForgeVariant oldVariant : oldVariants) {
|
||||
for (ForgeVariant addVariant : propertyVariants) {
|
||||
variants.add(oldVariant.createMerge(addVariant));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//create all possible property-variants
|
||||
BlockStateResource blockState = new BlockStateResource();
|
||||
for (ForgeVariant forgeVariant : variants) {
|
||||
Variant variant = new Variant();
|
||||
|
||||
ConfigurationNode modelNode = forgeVariant.node.mergeFrom(modelDefaults);
|
||||
|
||||
Map<String, String> textures = new HashMap<>();
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : modelNode.node("textures").childrenMap().entrySet()) {
|
||||
if (entry.getKey().equals(JSON_COMMENT)) continue;
|
||||
|
||||
textures.putIfAbsent(entry.getKey().toString(), entry.getValue().getString());
|
||||
}
|
||||
|
||||
List<PropertyCondition> conditions = new ArrayList<>(forgeVariant.properties.size());
|
||||
for (Entry<String, String> property : forgeVariant.properties.entrySet()) {
|
||||
conditions.add(PropertyCondition.property(property.getKey(), property.getValue()));
|
||||
}
|
||||
variant.condition = PropertyCondition.and(conditions.toArray(new PropertyCondition[0]));
|
||||
|
||||
variant.models.addAll(loadModels(modelNode, blockstateFile, textures));
|
||||
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : modelNode.node("submodel").childrenMap().entrySet()) {
|
||||
if (entry.getKey().equals(JSON_COMMENT)) continue;
|
||||
|
||||
variant.models.addAll(loadModels(entry.getValue(), blockstateFile, textures));
|
||||
}
|
||||
|
||||
variant.updateTotalWeight();
|
||||
|
||||
try {
|
||||
variant.checkValid();
|
||||
blockState.variants.add(variant);
|
||||
} catch (ParseResourceException ex) {
|
||||
logParseError("Failed to parse a variant (forge/property) of " + blockstateFile, ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//create default straight variant
|
||||
ConfigurationNode normalNode = config.node("variants", "normal");
|
||||
if (normalNode.virtual() || isForgeStraightVariant(normalNode)) {
|
||||
normalNode.mergeFrom(modelDefaults);
|
||||
|
||||
Map<String, String> textures = new HashMap<>();
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : normalNode.node("textures").childrenMap().entrySet()) {
|
||||
if (entry.getKey().equals(JSON_COMMENT)) continue;
|
||||
|
||||
textures.putIfAbsent(entry.getKey().toString(), entry.getValue().getString());
|
||||
}
|
||||
|
||||
Variant variant = new Variant();
|
||||
variant.condition = PropertyCondition.all();
|
||||
variant.models.addAll(loadModels(normalNode, blockstateFile, textures));
|
||||
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : normalNode.node("submodel").childrenMap().entrySet()) {
|
||||
if (entry.getKey().equals(JSON_COMMENT)) continue;
|
||||
|
||||
variant.models.addAll(loadModels(entry.getValue(), blockstateFile, textures));
|
||||
}
|
||||
|
||||
variant.updateTotalWeight();
|
||||
|
||||
try {
|
||||
variant.checkValid();
|
||||
blockState.variants.add(variant);
|
||||
} catch (ParseResourceException ex) {
|
||||
logParseError("Failed to parse a variant (forge/straight) of " + blockstateFile, ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return blockState;
|
||||
}
|
||||
|
||||
private boolean isForgeStraightVariant(ConfigurationNode node) {
|
||||
if (node.isList())
|
||||
return true;
|
||||
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : node.childrenMap().entrySet()) {
|
||||
if (entry.getKey().equals(JSON_COMMENT)) continue;
|
||||
if (!entry.getValue().isMap()) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class ForgeVariant {
|
||||
public Map<String, String> properties = new HashMap<>();
|
||||
public ConfigurationNode node = GsonConfigurationLoader.builder().build().createNode();
|
||||
|
||||
public ForgeVariant createMerge(ForgeVariant other) {
|
||||
ForgeVariant merge = new ForgeVariant();
|
||||
|
||||
merge.properties.putAll(this.properties);
|
||||
merge.properties.putAll(other.properties);
|
||||
|
||||
merge.node.mergeFrom(this.node);
|
||||
merge.node.mergeFrom(other.node);
|
||||
|
||||
return merge;
|
||||
}
|
||||
}
|
||||
|
||||
private static void logParseError(String message, Throwable throwable) {
|
||||
Logger.global.logDebug(message);
|
||||
while (throwable != null){
|
||||
String errorMessage = throwable.getMessage();
|
||||
if (errorMessage == null) errorMessage = throwable.toString();
|
||||
Logger.global.logDebug(" > " + errorMessage);
|
||||
|
||||
//Logger.global.logError("DETAIL: ", throwable);
|
||||
|
||||
throwable = throwable.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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.blockstate;
|
||||
|
||||
public class Conditional<T> {
|
||||
|
||||
private final PropertyCondition condition;
|
||||
private final T value;
|
||||
|
||||
public Conditional(PropertyCondition condition, T value) {
|
||||
this.condition = condition;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public PropertyCondition getCondition() {
|
||||
return condition;
|
||||
}
|
||||
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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.blockstate;
|
||||
|
||||
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
||||
import de.bluecolored.bluemap.core.resourcepack.blockmodel.TransformedBlockModelResource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
public class Variant {
|
||||
|
||||
PropertyCondition condition = PropertyCondition.all();
|
||||
Collection<Weighted<TransformedBlockModelResource>> models = new ArrayList<>(0);
|
||||
|
||||
private double totalWeight;
|
||||
|
||||
Variant() {}
|
||||
|
||||
public TransformedBlockModelResource getModel(int x, int y, int z) {
|
||||
if (models.isEmpty()) throw new IllegalStateException("A variant must have at least one model!");
|
||||
|
||||
double selection = hashToFloat(x, y, z) * totalWeight; // random based on position
|
||||
for (Weighted<TransformedBlockModelResource> w : models) {
|
||||
selection -= w.getWeight();
|
||||
if (selection <= 0) return w.getValue();
|
||||
}
|
||||
|
||||
throw new RuntimeException("This line should never be reached!");
|
||||
}
|
||||
|
||||
public void checkValid() throws ParseResourceException {
|
||||
if (models.isEmpty()) throw new ParseResourceException("A variant must have at least one model!");
|
||||
}
|
||||
|
||||
public void updateTotalWeight() {
|
||||
totalWeight = 0d;
|
||||
for (Weighted<?> w : models) {
|
||||
totalWeight += w.getWeight();
|
||||
}
|
||||
}
|
||||
|
||||
private static float hashToFloat(int x, int y, int z) {
|
||||
final long hash = x * 73438747 ^ y * 9357269 ^ z * 4335792;
|
||||
return (hash * (hash + 456149) & 0x00ffffff) / (float) 0x01000000;
|
||||
}
|
||||
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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.blockstate;
|
||||
|
||||
public class Weighted<T> {
|
||||
|
||||
private final T value;
|
||||
private final double weight;
|
||||
|
||||
public Weighted(T value, double weight) {
|
||||
this.value = value;
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
public double getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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.Collection;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* This {@link FileAccess} maps its parent {@link FileAccess} to first look in assets/[namespace]/bluemap/... instead of assets/[namespace]/...
|
||||
*/
|
||||
public class BluemapAssetOverrideFileAccess implements FileAccess {
|
||||
|
||||
public FileAccess parent;
|
||||
|
||||
public BluemapAssetOverrideFileAccess(FileAccess parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return parent.getName() + "*";
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream readFile(String path) throws FileNotFoundException, IOException {
|
||||
String[] pathParts = StringUtils.split(path, "/");
|
||||
if (pathParts.length < 3 || !pathParts[0].equals("assets")) return parent.readFile(path);
|
||||
|
||||
String[] newParts = new String[pathParts.length + 1];
|
||||
System.arraycopy(pathParts, 0, newParts, 0, 2);
|
||||
System.arraycopy(pathParts, 2, newParts, 3, pathParts.length - 2);
|
||||
|
||||
newParts[2] = "bluemap";
|
||||
String newPath = String.join("/", newParts);
|
||||
|
||||
try {
|
||||
return parent.readFile(newPath);
|
||||
} catch (FileNotFoundException ex) {
|
||||
return parent.readFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> listFiles(String path, boolean recursive) {
|
||||
return parent.listFiles(path, recursive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> listFolders(String path) {
|
||||
return parent.listFolders(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
parent.close();
|
||||
}
|
||||
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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.Collection;
|
||||
|
||||
/**
|
||||
* This {@link FileAccess} maps its parent {@link FileAccess} to first look in assets/[namespace]/bluemap/... instead of assets/[namespace]/...
|
||||
*/
|
||||
public class CaseInsensitiveFileAccess implements FileAccess {
|
||||
|
||||
public FileAccess parent;
|
||||
|
||||
public CaseInsensitiveFileAccess(FileAccess parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return parent.getName() + "(CI)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream readFile(String path) throws FileNotFoundException, IOException {
|
||||
try {
|
||||
return parent.readFile(path);
|
||||
} catch (FileNotFoundException ex) {
|
||||
try {
|
||||
return parent.readFile(path.toLowerCase());
|
||||
} catch (FileNotFoundException ex2) {
|
||||
path = correctPathCase(path);
|
||||
return parent.readFile(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String correctPathCase(String path) throws FileNotFoundException, IOException {
|
||||
path = FileAccess.normalize(path);
|
||||
String[] pathParts = path.split("/");
|
||||
|
||||
String correctPath = "";
|
||||
for (int i = 0; i < pathParts.length; i++) {
|
||||
String part = correctPath + pathParts[i];
|
||||
|
||||
boolean found = false;
|
||||
for(String folder : listFolders(correctPath)) {
|
||||
if (!folder.equalsIgnoreCase(part)) continue;
|
||||
|
||||
part = folder;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found && i == pathParts.length - 1) {
|
||||
for(String folder : listFiles(correctPath, false)) {
|
||||
if (!folder.equalsIgnoreCase(part)) continue;
|
||||
|
||||
part = folder;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) throw new FileNotFoundException();
|
||||
|
||||
correctPath = part + "/";
|
||||
}
|
||||
|
||||
correctPath = FileAccess.normalize(correctPath);
|
||||
return correctPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> listFiles(String path, boolean recursive) {
|
||||
return parent.listFiles(path, recursive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> listFolders(String path) {
|
||||
return parent.listFolders(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
parent.close();
|
||||
}
|
||||
|
||||
}
|
@ -1,181 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
public class CombinedFileAccess implements FileAccess {
|
||||
|
||||
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
|
||||
|
||||
public List<FileAccess> sources;
|
||||
private Map<String, FileAccess> sourceMap;
|
||||
|
||||
private Set<String> allFiles;
|
||||
|
||||
public CombinedFileAccess() {
|
||||
sources = new ArrayList<>();
|
||||
sourceMap = new HashMap<>();
|
||||
allFiles = new HashSet<>();
|
||||
}
|
||||
|
||||
public void addFileAccess(FileAccess source) {
|
||||
rwLock.writeLock().lock();
|
||||
|
||||
try {
|
||||
sources.add(source);
|
||||
|
||||
Collection<String> sourceFiles = source.listFiles("", true);
|
||||
for (String path : sourceFiles) {
|
||||
sourceMap.put(FileAccess.normalize(path), source);
|
||||
}
|
||||
|
||||
allFiles.addAll(sourceFiles);
|
||||
} finally {
|
||||
rwLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "CombinedFileAccess(" + sources.size() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream readFile(String path) throws FileNotFoundException, IOException {
|
||||
rwLock.readLock().lock();
|
||||
try {
|
||||
FileAccess source = sourceMap.get(FileAccess.normalize(path));
|
||||
if (source != null) return source.readFile(path);
|
||||
} finally {
|
||||
rwLock.readLock().unlock();
|
||||
}
|
||||
|
||||
throw new FileNotFoundException("File " + path + " does not exist in any of the sources!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> listFiles(String path, boolean recursive) {
|
||||
path = normalizeFolderPath(path);
|
||||
Collection<String> files = new ArrayList<String>();
|
||||
|
||||
rwLock.readLock().lock();
|
||||
try {
|
||||
for (String file : allFiles) {
|
||||
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);
|
||||
}
|
||||
} finally {
|
||||
rwLock.readLock().unlock();
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
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 Collection<String> listFiles(String path, boolean recursive) {
|
||||
Set<String> files = new HashSet<>();
|
||||
|
||||
rwLock.readLock().lock();
|
||||
try {
|
||||
for (int i = 0; i < sources.size(); i++) {
|
||||
files.addAll(sources.get(i).listFiles(path, recursive));
|
||||
}
|
||||
} finally {
|
||||
rwLock.readLock().unlock();
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public Collection<String> listFolders(String path) {
|
||||
Set<String> folders = new HashSet<>();
|
||||
|
||||
rwLock.readLock().lock();
|
||||
try {
|
||||
for (int i = 0; i < sources.size(); i++) {
|
||||
folders.addAll(sources.get(i).listFolders(path));
|
||||
}
|
||||
} finally {
|
||||
rwLock.readLock().unlock();
|
||||
}
|
||||
|
||||
return folders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
IOException exception = null;
|
||||
|
||||
rwLock.writeLock().lock();
|
||||
try {
|
||||
for (FileAccess source : sources) {
|
||||
try {
|
||||
source.close();
|
||||
} catch (IOException ex) {
|
||||
if (exception == null) exception = ex;
|
||||
else exception.addSuppressed(ex);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
rwLock.writeLock().unlock();
|
||||
}
|
||||
|
||||
if (exception != null) throw exception;
|
||||
}
|
||||
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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 {
|
||||
|
||||
String getName();
|
||||
|
||||
InputStream readFile(String path) throws FileNotFoundException, IOException;
|
||||
|
||||
Collection<String> listFiles(String path, boolean recursive);
|
||||
|
||||
Collection<String> 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.isEmpty()) return path;
|
||||
if (path.charAt(path.length() - 1) == '/') path = path.substring(0, path.length() - 1);
|
||||
if (path.isEmpty()) return path;
|
||||
if (path.charAt(0) == '/') path = path.substring(1);
|
||||
return path;
|
||||
}
|
||||
|
||||
}
|
@ -1,144 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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<WeakReference<InputStream>> openedStreams;
|
||||
|
||||
public FolderFileAccess(File folder) {
|
||||
this.folder = folder;
|
||||
openedStreams = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return folder.getName();
|
||||
}
|
||||
|
||||
@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<String> listFiles(String path, boolean recursive) {
|
||||
File subFolder = resolve(path).toFile();
|
||||
List<String> paths = new ArrayList<>();
|
||||
listFiles(subFolder, paths, recursive);
|
||||
return paths;
|
||||
}
|
||||
|
||||
private void listFiles(File folder, Collection<String> 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<String> listFolders(String path) {
|
||||
File subFolder = resolve(path).toFile();
|
||||
List<String> 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<InputStream> 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<WeakReference<InputStream>> iterator = openedStreams.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
WeakReference<InputStream> ref = iterator.next();
|
||||
if (ref.get() == null) iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private Path resolve(String path) {
|
||||
if (path.isEmpty() || "/".equals(path)) return folder.toPath();
|
||||
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, '/');
|
||||
}
|
||||
|
||||
}
|
@ -1,146 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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.HashSet;
|
||||
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 String getName() {
|
||||
return file.getName();
|
||||
}
|
||||
|
||||
@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<String> listFiles(String path, boolean recursive) {
|
||||
path = normalizeFolderPath(path);
|
||||
|
||||
Collection<String> files = new ArrayList<String>();
|
||||
for (Enumeration<? extends ZipEntry> 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<String> listFolders(String path) {
|
||||
path = normalizeFolderPath(path);
|
||||
|
||||
Collection<String> folders = new HashSet<String>();
|
||||
for (Enumeration<? extends ZipEntry> entries = file.entries(); entries.hasMoreElements();) {
|
||||
ZipEntry entry = entries.nextElement();
|
||||
|
||||
String file = entry.getName();
|
||||
if (!entry.isDirectory()) {
|
||||
int nameSplit = file.lastIndexOf('/');
|
||||
if (nameSplit == -1) continue;
|
||||
file = file.substring(0, nameSplit);
|
||||
}
|
||||
file = normalizeFolderPath(file);
|
||||
|
||||
//strip last /
|
||||
file = file.substring(0, file.length() - 1);
|
||||
|
||||
int nameSplit = file.lastIndexOf('/');
|
||||
String filePath = "/";
|
||||
if (nameSplit != -1) {
|
||||
filePath = file.substring(0, nameSplit);
|
||||
}
|
||||
filePath = normalizeFolderPath(filePath);
|
||||
|
||||
if (!filePath.startsWith(path)) continue;
|
||||
|
||||
int subFolderMark = file.indexOf('/', path.length());
|
||||
if (subFolderMark != -1) {
|
||||
file = file.substring(0, subFolderMark);
|
||||
}
|
||||
|
||||
file = normalizeFolderPath(file);
|
||||
|
||||
//strip last /
|
||||
file = file.substring(0, file.length() - 1);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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.texture;
|
||||
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
|
||||
@DebugDump
|
||||
public class Texture {
|
||||
|
||||
private final int id;
|
||||
private final String path;
|
||||
private final Color color, colorPremultiplied;
|
||||
private final boolean isHalfTransparent;
|
||||
private final String texture;
|
||||
|
||||
protected Texture(int id, String path, Color color, boolean halfTransparent, String texture) {
|
||||
this.id = id;
|
||||
this.path = path;
|
||||
this.color = new Color().set(color).straight();
|
||||
this.colorPremultiplied = new Color().set(color).premultiplied();
|
||||
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 Color getColorStraight() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the calculated median color of the {@link Texture} (premultiplied).
|
||||
* @return The (premultiplied) median color of this {@link Texture}
|
||||
*/
|
||||
public Color getColorPremultiplied() {
|
||||
return colorPremultiplied;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the {@link Texture} has half-transparent pixels or not.
|
||||
* @return <code>true</code> if the {@link Texture} has half-transparent pixels, <code>false</code> 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;
|
||||
}
|
||||
|
||||
}
|
@ -1,292 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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.texture;
|
||||
|
||||
import com.google.gson.*;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
||||
import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess;
|
||||
import de.bluecolored.bluemap.core.util.FileUtils;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A {@link TextureGallery} is managing {@link Texture}s and their id's and path's.<br>
|
||||
* I can also load and generate the texture.json file, or load new {@link Texture}s from a {@link FileAccess}.
|
||||
*/
|
||||
@DebugDump
|
||||
public class TextureGallery {
|
||||
|
||||
private static final String EMPTY_BASE64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAEUlEQVR42mNkIAAYRxWMJAUAE5gAEdz4t9QAAAAASUVORK5CYII=";
|
||||
|
||||
private final Map<String, Texture> textureMap;
|
||||
private final List<Texture> 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());
|
||||
|
||||
Color color = texture.getColorStraight();
|
||||
JsonArray colorNode = new JsonArray();
|
||||
colorNode.add(color.r);
|
||||
colorNode.add(color.g);
|
||||
colorNode.add(color.b);
|
||||
colorNode.add(color.a);
|
||||
|
||||
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);
|
||||
|
||||
if (file.exists()) FileUtils.delete(file);
|
||||
FileUtils.createFile(file);
|
||||
|
||||
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", new Color(), false, EMPTY_BASE64));
|
||||
}
|
||||
|
||||
try {
|
||||
JsonObject texture = textures.get(i).getAsJsonObject();
|
||||
String path = texture.get("id").getAsString();
|
||||
boolean transparent = texture.get("transparent").getAsBoolean();
|
||||
Color color = readColor(texture.get("color").getAsJsonArray());
|
||||
textureList.set(i, new Texture(i, path, color, transparent, texture.get("texture").getAsString()));
|
||||
} catch (ParseResourceException | RuntimeException ex) {
|
||||
Logger.global.logWarning("Failed to load texture with id " + i + " from texture file " + file + "!");
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException 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.<br>
|
||||
* 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.<br>
|
||||
* 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()){
|
||||
image = image.getSubimage(0, 0, image.getWidth(), image.getWidth());
|
||||
}
|
||||
|
||||
//check halfTransparency
|
||||
boolean halfTransparent = checkHalfTransparent(image);
|
||||
|
||||
//calculate color
|
||||
Color 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}<br>
|
||||
* <br>
|
||||
* 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 ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
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 Color readColor(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 Color().set(r, g, b, a, false);
|
||||
}
|
||||
|
||||
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 Color calculateColor(BufferedImage image){
|
||||
float alpha = 0f, red = 0f, green = 0f, blue = 0f;
|
||||
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);
|
||||
float pixelAlpha = ((pixel >> 24) & 0xff) / 255f;
|
||||
float pixelRed = ((pixel >> 16) & 0xff) / 255f;
|
||||
float pixelGreen = ((pixel >> 8) & 0xff) / 255f;
|
||||
float pixelBlue = (pixel & 0xff) / 255f;
|
||||
|
||||
count++;
|
||||
alpha += pixelAlpha;
|
||||
red += pixelRed * pixelAlpha;
|
||||
green += pixelGreen * pixelAlpha;
|
||||
blue += pixelBlue * pixelAlpha;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0 || alpha == 0) return new Color();
|
||||
|
||||
red /= alpha;
|
||||
green /= alpha;
|
||||
blue /= alpha;
|
||||
alpha /= count;
|
||||
|
||||
return new Color().set(red, green, blue, alpha, false);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package de.bluecolored.bluemap.core.resources;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public abstract class AbstractTypeAdapterFactory<T> implements TypeAdapterFactory {
|
||||
protected static final String JSON_COMMENT = "__comment";
|
||||
|
||||
private final Class<T> type;
|
||||
|
||||
public AbstractTypeAdapterFactory(Class<T> type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public void write(JsonWriter out, T value, Gson gson) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public abstract T read(JsonReader in, Gson gson) throws IOException;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <U> TypeAdapter<U> create(Gson gson, TypeToken<U> type) {
|
||||
if (!type.getRawType().isAssignableFrom(this.type)) return null;
|
||||
return (TypeAdapter<U>) new Adapter(gson);
|
||||
}
|
||||
|
||||
public class Adapter extends TypeAdapter<T> {
|
||||
|
||||
private final Gson gson;
|
||||
|
||||
public Adapter(Gson gson) {
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, T value) throws IOException {
|
||||
AbstractTypeAdapterFactory.this.write(out, value, gson);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T read(JsonReader in) throws IOException {
|
||||
return AbstractTypeAdapterFactory.this.read(in, gson);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -22,21 +22,23 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.resourcepack;
|
||||
package de.bluecolored.bluemap.core.resources;
|
||||
|
||||
import com.flowpowered.math.GenericMath;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.util.ConfigUtils;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
@DebugDump
|
||||
public class BlockColorCalculatorFactory {
|
||||
@ -50,33 +52,43 @@ public BlockColorCalculatorFactory() {
|
||||
this.blockColorMap = new HashMap<>();
|
||||
}
|
||||
|
||||
public void load(ConfigurationNode colorConfig) {
|
||||
for (Entry<Object, ? extends ConfigurationNode> entry : colorConfig.childrenMap().entrySet()){
|
||||
String key = entry.getKey().toString();
|
||||
String value = entry.getValue().getString("");
|
||||
public void load(Path configFile) throws IOException {
|
||||
try (BufferedReader reader = Files.newBufferedReader(configFile)) {
|
||||
JsonReader json = new JsonReader(reader);
|
||||
json.setLenient(true);
|
||||
|
||||
ColorFunction colorFunction;
|
||||
switch (value) {
|
||||
case "@foliage":
|
||||
colorFunction = BlockColorCalculator::getFoliageAverageColor;
|
||||
break;
|
||||
case "@grass":
|
||||
colorFunction = BlockColorCalculator::getGrassAverageColor;
|
||||
break;
|
||||
case "@water":
|
||||
colorFunction = BlockColorCalculator::getWaterAverageColor;
|
||||
break;
|
||||
case "@redstone":
|
||||
colorFunction = BlockColorCalculator::getRedstoneColor;
|
||||
break;
|
||||
default:
|
||||
final Color color = new Color();
|
||||
color.set(ConfigUtils.readColorInt(entry.getValue())).premultiplied();
|
||||
colorFunction = (calculator, block, target) -> target.set(color);
|
||||
break;
|
||||
json.beginObject();
|
||||
while (json.hasNext()) {
|
||||
|
||||
String key = json.nextName();
|
||||
String value = json.nextString();
|
||||
|
||||
ColorFunction colorFunction;
|
||||
switch (value) {
|
||||
case "@foliage":
|
||||
colorFunction = BlockColorCalculator::getFoliageAverageColor;
|
||||
break;
|
||||
case "@grass":
|
||||
colorFunction = BlockColorCalculator::getGrassAverageColor;
|
||||
break;
|
||||
case "@water":
|
||||
colorFunction = BlockColorCalculator::getWaterAverageColor;
|
||||
break;
|
||||
case "@redstone":
|
||||
colorFunction = BlockColorCalculator::getRedstoneColor;
|
||||
break;
|
||||
default:
|
||||
final Color color = new Color();
|
||||
color.set(ConfigUtils.parseColorFromString(value)).premultiplied();
|
||||
colorFunction = (calculator, block, target) -> target.set(color);
|
||||
break;
|
||||
}
|
||||
|
||||
// don't overwrite already present values, higher priority resources are loaded first
|
||||
blockColorMap.putIfAbsent(key, colorFunction);
|
||||
}
|
||||
|
||||
blockColorMap.put(key, colorFunction);
|
||||
json.endObject();
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,15 +106,15 @@ public BlockColorCalculator createCalculator() {
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ColorFunction {
|
||||
Color invoke(BlockColorCalculator calculator, BlockNeighborhood block, Color target);
|
||||
Color invoke(BlockColorCalculator calculator, BlockNeighborhood<?> block, Color target);
|
||||
}
|
||||
|
||||
public class BlockColorCalculator {
|
||||
|
||||
private final Color tempColor = new Color();
|
||||
|
||||
public Color getBlockColor(BlockNeighborhood block, Color target) {
|
||||
String blockId = block.getBlockState().getFullId();
|
||||
public Color getBlockColor(BlockNeighborhood<?> block, Color target) {
|
||||
String blockId = block.getBlockState().getFormatted();
|
||||
|
||||
ColorFunction colorFunction = blockColorMap.get(blockId);
|
||||
if (colorFunction == null) colorFunction = blockColorMap.get("default");
|
||||
@ -111,7 +123,7 @@ public Color getBlockColor(BlockNeighborhood block, Color target) {
|
||||
return colorFunction.invoke(this, block, target);
|
||||
}
|
||||
|
||||
public Color getRedstoneColor(BlockNeighborhood block, Color target) {
|
||||
public Color getRedstoneColor(BlockNeighborhood<?> block, Color target) {
|
||||
String powerString = block.getBlockState().getProperties().get("power");
|
||||
|
||||
int power = 15;
|
||||
@ -125,7 +137,7 @@ public Color getRedstoneColor(BlockNeighborhood block, Color target) {
|
||||
);
|
||||
}
|
||||
|
||||
public Color getWaterAverageColor(BlockNeighborhood block, Color target) {
|
||||
public Color getWaterAverageColor(BlockNeighborhood<?> block, Color target) {
|
||||
target.set(0, 0, 0, 0, true);
|
||||
|
||||
int x, y, z,
|
||||
@ -149,7 +161,7 @@ public Color getWaterAverageColor(BlockNeighborhood block, Color target) {
|
||||
return target.flatten();
|
||||
}
|
||||
|
||||
public Color getFoliageAverageColor(BlockNeighborhood block, Color target) {
|
||||
public Color getFoliageAverageColor(BlockNeighborhood<?> block, Color target) {
|
||||
target.set(0, 0, 0, 0, true);
|
||||
|
||||
int x, y, z,
|
||||
@ -178,7 +190,7 @@ public Color getFoliageColor(Biome biome, Color target) {
|
||||
return target.overlay(biome.getOverlayFoliageColor());
|
||||
}
|
||||
|
||||
public Color getGrassAverageColor(BlockNeighborhood block, Color target) {
|
||||
public Color getGrassAverageColor(BlockNeighborhood<?> block, Color target) {
|
||||
target.set(0, 0, 0, 0, true);
|
||||
|
||||
int x, y, z,
|
@ -22,21 +22,22 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.resourcepack;
|
||||
package de.bluecolored.bluemap.core.resources;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.world.BlockProperties;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@DebugDump
|
||||
public class BlockPropertiesConfig {
|
||||
@ -47,32 +48,40 @@ public BlockPropertiesConfig() {
|
||||
mappings = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
public void load(ConfigurationNode node) {
|
||||
for (Entry<Object, ? extends ConfigurationNode> e : node.childrenMap().entrySet()){
|
||||
String key = e.getKey().toString();
|
||||
try {
|
||||
BlockState bsKey = BlockState.fromString(key);
|
||||
public void load(Path configFile) throws IOException {
|
||||
try (BufferedReader reader = Files.newBufferedReader(configFile)) {
|
||||
JsonReader json = new JsonReader(reader);
|
||||
json.setLenient(true);
|
||||
|
||||
json.beginObject();
|
||||
while (json.hasNext()) {
|
||||
String formatted = json.nextName();
|
||||
BlockState bsKey = BlockState.fromString(formatted);
|
||||
BlockProperties.Builder bsValueBuilder = BlockProperties.builder();
|
||||
|
||||
readBool(e.getValue().node("culling"), bsValueBuilder::culling);
|
||||
readBool(e.getValue().node("occluding"), bsValueBuilder::occluding);
|
||||
readBool(e.getValue().node("alwaysWaterlogged"), bsValueBuilder::alwaysWaterlogged);
|
||||
readBool(e.getValue().node("randomOffset"), bsValueBuilder::randomOffset);
|
||||
json.beginObject();
|
||||
while (json.hasNext()) {
|
||||
switch (json.nextName()) {
|
||||
case "culling": bsValueBuilder.culling(json.nextBoolean()); break;
|
||||
case "occluding": bsValueBuilder.occluding(json.nextBoolean()); break;
|
||||
case "alwaysWaterlogged": bsValueBuilder.alwaysWaterlogged(json.nextBoolean()); break;
|
||||
case "randomOffset": bsValueBuilder.randomOffset(json.nextBoolean()); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
json.endObject();
|
||||
|
||||
BlockStateMapping<BlockProperties> mapping = new BlockStateMapping<>(bsKey, bsValueBuilder.build());
|
||||
mappings.computeIfAbsent(bsKey.getFullId(), k -> new LinkedList<>()).add(0, mapping);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Logger.global.logWarning("Loading BlockPropertiesConfig: Failed to parse BlockState from key '" + key + "'");
|
||||
|
||||
// don't overwrite already present values, higher priority resources are loaded first
|
||||
mappings.computeIfAbsent(bsKey.getFormatted(), k -> new LinkedList<>()).add(0, mapping);
|
||||
}
|
||||
json.endObject();
|
||||
}
|
||||
}
|
||||
|
||||
private void readBool(ConfigurationNode node, Consumer<Boolean> target) {
|
||||
if (!node.virtual()) target.accept(node.getBoolean());
|
||||
}
|
||||
|
||||
public BlockProperties getBlockProperties(BlockState from){
|
||||
for (BlockStateMapping<BlockProperties> bm : mappings.getOrDefault(from.getFullId(), Collections.emptyList())){
|
||||
for (BlockStateMapping<BlockProperties> bm : mappings.getOrDefault(from.getFormatted(), Collections.emptyList())){
|
||||
if (bm.fitsTo(from)){
|
||||
return bm.getMapping();
|
||||
}
|
@ -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.resourcepack;
|
||||
package de.bluecolored.bluemap.core.resources;
|
||||
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
@ -31,8 +31,8 @@
|
||||
|
||||
@DebugDump
|
||||
class BlockStateMapping<T> {
|
||||
private BlockState blockState;
|
||||
private T mapping;
|
||||
private final BlockState blockState;
|
||||
private final T mapping;
|
||||
|
||||
public BlockStateMapping(BlockState blockState, T mapping) {
|
||||
this.blockState = blockState;
|
||||
@ -44,7 +44,7 @@ public BlockStateMapping(BlockState blockState, T mapping) {
|
||||
* Properties that are not defined in this Mapping are ignored on the provided BlockState.<br>
|
||||
*/
|
||||
public boolean fitsTo(BlockState blockState){
|
||||
if (!this.blockState.getId().equals(blockState.getId())) return false;
|
||||
if (!this.blockState.getValue().equals(blockState.getValue())) return false;
|
||||
for (Entry<String, String> e : this.blockState.getProperties().entrySet()){
|
||||
if (!e.getValue().equals(blockState.getProperties().get(e.getKey()))){
|
||||
return false;
|
@ -0,0 +1,91 @@
|
||||
package de.bluecolored.bluemap.core.resources;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Function;
|
||||
|
||||
@DebugDump
|
||||
@JsonAdapter(ResourcePath.Adapter.class)
|
||||
public class ResourcePath<T> extends Key {
|
||||
|
||||
private T resource = null;
|
||||
|
||||
public ResourcePath(String formatted) {
|
||||
super(formatted.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
public ResourcePath(String namespace, String value) {
|
||||
super(namespace.toLowerCase(Locale.ROOT), value.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
public ResourcePath(Path filePath) {
|
||||
super(parsePath(filePath).toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public T getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public T getResource(Function<ResourcePath<T>, T> supplier) {
|
||||
if (resource == null) resource = supplier.apply(this);
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void setResource(T resource) {
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
private static String parsePath(Path filePath) {
|
||||
if (filePath.getNameCount() < 4)
|
||||
throw new IllegalArgumentException("The provided filePath has less than 4 segments!");
|
||||
|
||||
if (!filePath.getName(0).toString().equalsIgnoreCase("assets"))
|
||||
throw new IllegalArgumentException("The provided filePath doesn't start with 'assets'!");
|
||||
|
||||
String namespace = filePath.getName(1).toString();
|
||||
String path = filePath.subpath(3, filePath.getNameCount()).toString().replace(filePath.getFileSystem().getSeparator(), "/");
|
||||
|
||||
// remove file-ending
|
||||
int dotIndex = path.lastIndexOf('.');
|
||||
if (dotIndex != -1) path = path.substring(0, dotIndex);
|
||||
|
||||
return namespace + ":" + path;
|
||||
}
|
||||
|
||||
static class Adapter implements TypeAdapterFactory {
|
||||
|
||||
@Override
|
||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
||||
if (!type.getRawType().isAssignableFrom(ResourcePath.class))
|
||||
return null;
|
||||
|
||||
return new TypeAdapter<>() {
|
||||
@Override
|
||||
public void write(JsonWriter out, T value) throws IOException {
|
||||
out.value(((ResourcePath<?>) value).getFormatted());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T read(JsonReader in) throws IOException {
|
||||
return (T) new ResourcePath<>(in.nextString());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package de.bluecolored.bluemap.core.resources.adapter;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import de.bluecolored.bluemap.core.util.math.Axis;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
public class AxisAdapter extends TypeAdapter<Axis> {
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, Axis value) throws IOException {
|
||||
out.value(value.name().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Axis read(JsonReader in) throws IOException {
|
||||
return Axis.fromString(in.nextString());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package de.bluecolored.bluemap.core.resources.adapter;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import de.bluecolored.bluemap.core.util.ConfigUtils;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ColorAdapter extends TypeAdapter<Color> {
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, Color value) throws IOException {
|
||||
value.straight();
|
||||
out.beginArray();
|
||||
out.value(value.r);
|
||||
out.value(value.g);
|
||||
out.value(value.b);
|
||||
out.value(value.a);
|
||||
out.endArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color read(JsonReader in) throws IOException {
|
||||
Color value = new Color();
|
||||
JsonToken token = in.peek();
|
||||
switch (token) {
|
||||
case BEGIN_ARRAY:
|
||||
in.beginArray();
|
||||
value.set(
|
||||
(float) in.nextDouble(),
|
||||
(float) in.nextDouble(),
|
||||
(float) in.nextDouble(),
|
||||
in.hasNext() ? (float) in.nextDouble() : 1f,
|
||||
false
|
||||
);
|
||||
in.endArray();
|
||||
break;
|
||||
case BEGIN_OBJECT:
|
||||
value.a = 1f;
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
String n = in.nextName();
|
||||
float v = (float) in.nextDouble();
|
||||
|
||||
switch (n) {
|
||||
case "r": value.r = v; break;
|
||||
case "g": value.g = v; break;
|
||||
case "b": value.b = v; break;
|
||||
case "a": value.a = v; break;
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
break;
|
||||
case STRING:
|
||||
value.set(ConfigUtils.parseColorFromString(in.nextString()));
|
||||
break;
|
||||
case NUMBER:
|
||||
value.set(in.nextInt());
|
||||
break;
|
||||
case NULL:
|
||||
break;
|
||||
default: throw new IOException("Unexpected token while parsing Color:" + token);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package de.bluecolored.bluemap.core.resources.adapter;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import de.bluecolored.bluemap.core.util.Direction;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
public class DirectionAdapter extends TypeAdapter<Direction> {
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, Direction value) throws IOException {
|
||||
out.value(value.name().toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Direction read(JsonReader in) throws IOException {
|
||||
String name = in.nextString();
|
||||
if (name.equalsIgnoreCase("bottom")) return Direction.DOWN;
|
||||
return Direction.fromString(name);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package de.bluecolored.bluemap.core.resources.adapter;
|
||||
|
||||
import com.google.gson.InstanceCreator;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.EnumMap;
|
||||
|
||||
public class EnumMapInstanceCreator<K extends Enum<K>, V> implements InstanceCreator<EnumMap<K, V>> {
|
||||
|
||||
private final Class<K> type;
|
||||
|
||||
public EnumMapInstanceCreator(Class<K> type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumMap<K, V> createInstance(Type type) {
|
||||
return new EnumMap<>(this.type);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package de.bluecolored.bluemap.core.resources.adapter;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3d;
|
||||
import com.flowpowered.math.vector.Vector3f;
|
||||
import com.flowpowered.math.vector.Vector4d;
|
||||
import com.flowpowered.math.vector.Vector4f;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.Face;
|
||||
import de.bluecolored.bluemap.core.util.Direction;
|
||||
import de.bluecolored.bluemap.core.util.math.Axis;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
|
||||
import java.util.EnumMap;
|
||||
|
||||
public class ResourcesGson {
|
||||
|
||||
public static final Gson INSTANCE = createGson();
|
||||
|
||||
private static Gson createGson() {
|
||||
|
||||
return new GsonBuilder()
|
||||
.setLenient()
|
||||
.registerTypeAdapter(Axis.class, new AxisAdapter())
|
||||
.registerTypeAdapter(Color.class, new ColorAdapter())
|
||||
.registerTypeAdapter(Direction.class, new DirectionAdapter())
|
||||
.registerTypeAdapter(Vector3d.class, new Vector3dAdapter())
|
||||
.registerTypeAdapter(Vector3f.class, new Vector3fAdapter())
|
||||
.registerTypeAdapter(Vector4d.class, new Vector4dAdapter())
|
||||
.registerTypeAdapter(Vector4f.class, new Vector4fAdapter())
|
||||
.registerTypeAdapter(
|
||||
new TypeToken<EnumMap<Direction, Face>>(){}.getType(),
|
||||
new EnumMapInstanceCreator<Direction, Face>(Direction.class)
|
||||
)
|
||||
.create();
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package de.bluecolored.bluemap.core.resources.adapter;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3d;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Vector3dAdapter extends TypeAdapter<Vector3d> {
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, Vector3d value) throws IOException {
|
||||
out.beginArray();
|
||||
out.value(value.getX());
|
||||
out.value(value.getY());
|
||||
out.value(value.getZ());
|
||||
out.endArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3d read(JsonReader in) throws IOException {
|
||||
in.beginArray();
|
||||
Vector3d value = new Vector3d(
|
||||
in.nextDouble(),
|
||||
in.nextDouble(),
|
||||
in.nextDouble()
|
||||
);
|
||||
in.endArray();
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package de.bluecolored.bluemap.core.resources.adapter;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3f;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Vector3fAdapter extends TypeAdapter<Vector3f> {
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, Vector3f value) throws IOException {
|
||||
out.beginArray();
|
||||
out.value(value.getX());
|
||||
out.value(value.getY());
|
||||
out.value(value.getZ());
|
||||
out.endArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3f read(JsonReader in) throws IOException {
|
||||
in.beginArray();
|
||||
Vector3f value = new Vector3f(
|
||||
in.nextDouble(),
|
||||
in.nextDouble(),
|
||||
in.nextDouble()
|
||||
);
|
||||
in.endArray();
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package de.bluecolored.bluemap.core.resources.adapter;
|
||||
|
||||
import com.flowpowered.math.vector.Vector4d;
|
||||
import com.flowpowered.math.vector.Vector4f;
|
||||
import com.flowpowered.math.vector.Vector4i;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Vector4dAdapter extends TypeAdapter<Vector4d> {
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, Vector4d value) throws IOException {
|
||||
out.beginArray();
|
||||
out.value(value.getX());
|
||||
out.value(value.getY());
|
||||
out.value(value.getZ());
|
||||
out.value(value.getW());
|
||||
out.endArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector4d read(JsonReader in) throws IOException {
|
||||
in.beginArray();
|
||||
Vector4d value = new Vector4d(
|
||||
in.nextDouble(),
|
||||
in.nextDouble(),
|
||||
in.nextDouble(),
|
||||
in.nextDouble()
|
||||
);
|
||||
in.endArray();
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package de.bluecolored.bluemap.core.resources.adapter;
|
||||
|
||||
import com.flowpowered.math.vector.Vector4f;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Vector4fAdapter extends TypeAdapter<Vector4f> {
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, Vector4f value) throws IOException {
|
||||
out.beginArray();
|
||||
out.value(value.getX());
|
||||
out.value(value.getY());
|
||||
out.value(value.getZ());
|
||||
out.value(value.getW());
|
||||
out.endArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector4f read(JsonReader in) throws IOException {
|
||||
in.beginArray();
|
||||
Vector4f value = new Vector4f(
|
||||
in.nextDouble(),
|
||||
in.nextDouble(),
|
||||
in.nextDouble(),
|
||||
in.nextDouble()
|
||||
);
|
||||
in.endArray();
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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.resources.biome;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson;
|
||||
import de.bluecolored.bluemap.core.resources.biome.datapack.DpBiome;
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@DebugDump
|
||||
public class BiomeConfig {
|
||||
|
||||
private final Map<String, Biome> biomes;
|
||||
|
||||
public BiomeConfig() {
|
||||
biomes = new HashMap<>();
|
||||
}
|
||||
|
||||
public void load(Path configFile) throws IOException {
|
||||
try (BufferedReader reader = Files.newBufferedReader(configFile)) {
|
||||
JsonReader json = new JsonReader(reader);
|
||||
json.setLenient(true);
|
||||
|
||||
json.beginObject();
|
||||
|
||||
while (json.hasNext()) {
|
||||
String formatted = json.nextName();
|
||||
BiomeConfigEntry entry = ResourcesGson.INSTANCE.fromJson(json, BiomeConfigEntry.class);
|
||||
Biome biome = entry.createBiome(formatted);
|
||||
|
||||
// don't overwrite already present values, higher priority resources are loaded first
|
||||
biomes.putIfAbsent(biome.getFormatted(), biome);
|
||||
}
|
||||
|
||||
json.endObject();
|
||||
}
|
||||
}
|
||||
|
||||
public void loadDatapackBiome(String namespace, Path biomeFile) throws IOException {
|
||||
try (BufferedReader reader = Files.newBufferedReader(biomeFile)) {
|
||||
JsonReader json = new JsonReader(reader);
|
||||
json.setLenient(true);
|
||||
DpBiome dpBiome = ResourcesGson.INSTANCE.fromJson(json, DpBiome.class);
|
||||
|
||||
String formatted = namespace + ":" + biomeFile.getFileName().toString();
|
||||
int fileEndingDot = formatted.lastIndexOf('.');
|
||||
if (fileEndingDot != -1) formatted = formatted.substring(0, fileEndingDot);
|
||||
|
||||
Biome biome = dpBiome.createBiome(formatted);
|
||||
|
||||
// don't overwrite already present values, higher priority resources are loaded first
|
||||
biomes.putIfAbsent(biome.getFormatted(), biome);
|
||||
}
|
||||
}
|
||||
|
||||
public Biome getBiome(String formatted) {
|
||||
return biomes.getOrDefault(formatted, Biome.DEFAULT);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package de.bluecolored.bluemap.core.resources.biome;
|
||||
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
public class BiomeConfigEntry {
|
||||
|
||||
private float humidity = Biome.DEFAULT.getHumidity();
|
||||
private float temperature = Biome.DEFAULT.getTemp();
|
||||
private Color watercolor = Biome.DEFAULT.getWaterColor();
|
||||
private Color grasscolor = new Color();
|
||||
private Color foliagecolor = new Color();
|
||||
|
||||
public Biome createBiome(String formatted) {
|
||||
return new Biome(
|
||||
formatted,
|
||||
humidity,
|
||||
temperature,
|
||||
watercolor.premultiplied(),
|
||||
foliagecolor.premultiplied(),
|
||||
grasscolor.premultiplied()
|
||||
);
|
||||
}
|
||||
|
||||
public float getHumidity() {
|
||||
return humidity;
|
||||
}
|
||||
|
||||
public float getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
public Color getWatercolor() {
|
||||
return watercolor;
|
||||
}
|
||||
|
||||
public Color getGrasscolor() {
|
||||
return grasscolor;
|
||||
}
|
||||
|
||||
public Color getFoliagecolor() {
|
||||
return foliagecolor;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package de.bluecolored.bluemap.core.resources.biome.datapack;
|
||||
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
public class DpBiome {
|
||||
|
||||
private DpBiomeEffects effects = new DpBiomeEffects();
|
||||
private float temperature = Biome.DEFAULT.getTemp();
|
||||
private float downfall = Biome.DEFAULT.getHumidity();
|
||||
|
||||
public Biome createBiome(String formatted) {
|
||||
return new Biome(
|
||||
formatted,
|
||||
downfall,
|
||||
temperature,
|
||||
effects.getWaterColor(),
|
||||
effects.getFoliageColor(),
|
||||
effects.getGrassColor()
|
||||
);
|
||||
}
|
||||
|
||||
public DpBiomeEffects getEffects() {
|
||||
return effects;
|
||||
}
|
||||
|
||||
public double getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
public double getDownfall() {
|
||||
return downfall;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package de.bluecolored.bluemap.core.resources.biome.datapack;
|
||||
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
public class DpBiomeEffects {
|
||||
|
||||
private Color water_color = Biome.DEFAULT.getWaterColor();
|
||||
private Color foliage_color = Biome.DEFAULT.getOverlayFoliageColor();
|
||||
private Color grass_color = Biome.DEFAULT.getOverlayGrassColor();
|
||||
|
||||
public Color getWaterColor() {
|
||||
return water_color;
|
||||
}
|
||||
|
||||
public Color getFoliageColor() {
|
||||
return foliage_color;
|
||||
}
|
||||
|
||||
public Color getGrassColor() {
|
||||
return grass_color;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,335 @@
|
||||
package de.bluecolored.bluemap.core.resources.resourcepack;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.resources.BlockColorCalculatorFactory;
|
||||
import de.bluecolored.bluemap.core.resources.BlockPropertiesConfig;
|
||||
import de.bluecolored.bluemap.core.resources.ResourcePath;
|
||||
import de.bluecolored.bluemap.core.resources.adapter.ResourcesGson;
|
||||
import de.bluecolored.bluemap.core.resources.biome.BiomeConfig;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.BlockModel;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.blockstate.BlockState;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.texture.Texture;
|
||||
import de.bluecolored.bluemap.core.util.Tristate;
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
import de.bluecolored.bluemap.core.world.BlockProperties;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@DebugDump
|
||||
public class ResourcePack {
|
||||
public static final ResourcePath<BlockState> MISSING_BLOCK_STATE = new ResourcePath<>("bluemap", "missing");
|
||||
public static final ResourcePath<BlockModel> MISSING_BLOCK_MODEL = new ResourcePath<>("bluemap", "missing");
|
||||
public static final ResourcePath<Texture> MISSING_TEXTURE = new ResourcePath<>("bluemap", "missing");
|
||||
|
||||
private final Map<String, ResourcePath<BlockState>> blockStatePaths;
|
||||
private final Map<ResourcePath<BlockState>, BlockState> blockStates;
|
||||
private final Map<ResourcePath<BlockModel>, BlockModel> blockModels;
|
||||
private final Map<ResourcePath<Texture>, Texture> textures;
|
||||
private final Map<ResourcePath<BufferedImage>, BufferedImage> colormaps;
|
||||
|
||||
private final BlockColorCalculatorFactory colorCalculatorFactory;
|
||||
private final BiomeConfig biomeConfig;
|
||||
private final BlockPropertiesConfig blockPropertiesConfig;
|
||||
|
||||
private final LoadingCache<de.bluecolored.bluemap.core.world.BlockState, BlockProperties> blockPropertiesCache;
|
||||
|
||||
public ResourcePack() {
|
||||
this.blockStatePaths = new HashMap<>();
|
||||
this.blockStates = new HashMap<>();
|
||||
this.blockModels = new HashMap<>();
|
||||
this.textures = new HashMap<>();
|
||||
this.colormaps = new HashMap<>();
|
||||
|
||||
this.colorCalculatorFactory = new BlockColorCalculatorFactory();
|
||||
this.biomeConfig = new BiomeConfig();
|
||||
this.blockPropertiesConfig = new BlockPropertiesConfig();
|
||||
|
||||
this.blockPropertiesCache = Caffeine.newBuilder()
|
||||
.executor(BlueMap.THREAD_POOL)
|
||||
.maximumSize(10000)
|
||||
.build(this::loadBlockProperties);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockState getBlockState(de.bluecolored.bluemap.core.world.BlockState blockState) {
|
||||
ResourcePath<BlockState> path = blockStatePaths.get(blockState.getFormatted());
|
||||
return path != null ? path.getResource(this::getBlockState) : MISSING_BLOCK_STATE.getResource(this::getBlockState);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockState getBlockState(ResourcePath<BlockState> path) {
|
||||
BlockState blockState = blockStates.get(path);
|
||||
return blockState != null ? blockState : MISSING_BLOCK_STATE.getResource(blockStates::get);
|
||||
}
|
||||
|
||||
public Map<ResourcePath<BlockState>, BlockState> getBlockStates() {
|
||||
return blockStates;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BlockModel getBlockModel(ResourcePath<BlockModel> path) {
|
||||
BlockModel blockModel = blockModels.get(path);
|
||||
return blockModel != null ? blockModel : MISSING_BLOCK_MODEL.getResource(blockModels::get);
|
||||
}
|
||||
|
||||
public Map<ResourcePath<BlockModel>, BlockModel> getBlockModels() {
|
||||
return blockModels;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Texture getTexture(ResourcePath<Texture> path) {
|
||||
Texture texture = textures.get(path);
|
||||
return texture != null ? texture : MISSING_TEXTURE.getResource(textures::get);
|
||||
}
|
||||
|
||||
public Map<ResourcePath<Texture>, Texture> getTextures() {
|
||||
return textures;
|
||||
}
|
||||
|
||||
public BlockColorCalculatorFactory getColorCalculatorFactory() {
|
||||
return colorCalculatorFactory;
|
||||
}
|
||||
|
||||
public Biome getBiome(String formatted) {
|
||||
return biomeConfig.getBiome(formatted);
|
||||
}
|
||||
|
||||
public BlockProperties getBlockProperties(de.bluecolored.bluemap.core.world.BlockState state) {
|
||||
return blockPropertiesCache.get(state);
|
||||
}
|
||||
|
||||
private BlockProperties loadBlockProperties(de.bluecolored.bluemap.core.world.BlockState state) {
|
||||
BlockProperties.Builder props = blockPropertiesConfig.getBlockProperties(state).toBuilder();
|
||||
|
||||
if (props.isOccluding() == Tristate.UNDEFINED || props.isCulling() == Tristate.UNDEFINED) {
|
||||
BlockState resource = getBlockState(state);
|
||||
if (resource != null) {
|
||||
resource.forEach(state,0, 0, 0, variant -> {
|
||||
BlockModel model = variant.getModel().getResource(this::getBlockModel);
|
||||
if (model != null) {
|
||||
if (props.isOccluding() == Tristate.UNDEFINED) props.occluding(model.isOccluding());
|
||||
if (props.isCulling() == Tristate.UNDEFINED) props.culling(model.isCulling());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return props.build();
|
||||
}
|
||||
|
||||
public synchronized void loadResources(Path root) throws IOException {
|
||||
Logger.global.logInfo("Loading resources from: " + root);
|
||||
|
||||
if (!Files.isDirectory(root)) {
|
||||
try (FileSystem fileSystem = FileSystems.newFileSystem(root, (ClassLoader) null)) {
|
||||
for (Path fsRoot : fileSystem.getRootDirectories()) {
|
||||
if (!Files.isDirectory(fsRoot)) continue;
|
||||
this.loadResources(fsRoot);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// do those in parallel
|
||||
CompletableFuture.allOf(
|
||||
|
||||
// load blockstates
|
||||
CompletableFuture.runAsync(() -> {
|
||||
list(root.resolve("assets"))
|
||||
.map(path -> path.resolve("blockstates"))
|
||||
.filter(Files::isDirectory)
|
||||
.flatMap(ResourcePack::walk)
|
||||
.filter(path -> path.getFileName().toString().endsWith(".json"))
|
||||
.filter(Files::isRegularFile)
|
||||
.forEach(file -> loadResource(root, file, () -> {
|
||||
try (BufferedReader reader = Files.newBufferedReader(file)) {
|
||||
return ResourcesGson.INSTANCE.fromJson(reader, BlockState.class);
|
||||
}
|
||||
}, blockStates));
|
||||
}),
|
||||
|
||||
// load blockmodels
|
||||
CompletableFuture.runAsync(() -> {
|
||||
list(root.resolve("assets"))
|
||||
.map(path -> path.resolve("models").resolve("block"))
|
||||
.filter(Files::isDirectory)
|
||||
.flatMap(ResourcePack::walk)
|
||||
.filter(path -> path.getFileName().toString().endsWith(".json"))
|
||||
.filter(Files::isRegularFile)
|
||||
.forEach(file -> loadResource(root, file, () -> {
|
||||
try (BufferedReader reader = Files.newBufferedReader(file)) {
|
||||
return ResourcesGson.INSTANCE.fromJson(reader, BlockModel.class);
|
||||
}
|
||||
}, blockModels));
|
||||
}),
|
||||
|
||||
// load textures
|
||||
CompletableFuture.runAsync(() -> {
|
||||
list(root.resolve("assets"))
|
||||
.map(path -> path.resolve("textures").resolve("block"))
|
||||
.filter(Files::isDirectory)
|
||||
.flatMap(ResourcePack::walk)
|
||||
.filter(path -> path.getFileName().toString().endsWith(".png"))
|
||||
.filter(Files::isRegularFile)
|
||||
.forEach(file -> loadResource(root, file, () -> {
|
||||
ResourcePath<Texture> resourcePath = new ResourcePath<>(root.relativize(file));
|
||||
try (InputStream in = Files.newInputStream(file)) {
|
||||
return Texture.from(resourcePath, ImageIO.read(in));
|
||||
}
|
||||
}, textures));
|
||||
}),
|
||||
|
||||
// load colormaps
|
||||
CompletableFuture.runAsync(() -> {
|
||||
walk(root.resolve("assets").resolve("minecraft").resolve("textures").resolve("colormap"))
|
||||
.filter(path -> path.getFileName().toString().endsWith(".png"))
|
||||
.filter(Files::isRegularFile)
|
||||
.forEach(file -> loadResource(root, file, () -> {
|
||||
try (InputStream in = Files.newInputStream(file)) {
|
||||
return ImageIO.read(in);
|
||||
}
|
||||
}, colormaps));
|
||||
}),
|
||||
|
||||
// load block-color configs
|
||||
CompletableFuture.runAsync(() -> {
|
||||
list(root.resolve("assets"))
|
||||
.map(path -> path.resolve("blockColors.json"))
|
||||
.filter(Files::isRegularFile)
|
||||
.forEach(file -> {
|
||||
try {
|
||||
colorCalculatorFactory.load(file);
|
||||
} catch (Exception ex) {
|
||||
Logger.global.logDebug("Failed to parse resource-file '" + file + "': " + ex);
|
||||
}
|
||||
});
|
||||
}),
|
||||
|
||||
// load biome configs
|
||||
CompletableFuture.runAsync(() -> {
|
||||
list(root.resolve("assets"))
|
||||
.map(path -> path.resolve("biomes.json"))
|
||||
.filter(Files::isRegularFile)
|
||||
.forEach(file -> {
|
||||
try {
|
||||
biomeConfig.load(file);
|
||||
} catch (Exception ex) {
|
||||
Logger.global.logDebug("Failed to parse resource-file '" + file + "': " + ex);
|
||||
}
|
||||
});
|
||||
|
||||
list(root.resolve("data"))
|
||||
.filter(Files::isDirectory)
|
||||
.forEach(namespace -> list(namespace.resolve("worldgen").resolve("biome"))
|
||||
.filter(path -> path.getFileName().toString().endsWith(".json"))
|
||||
.filter(Files::isRegularFile)
|
||||
.forEach(file -> {
|
||||
try {
|
||||
biomeConfig.loadDatapackBiome(namespace.getFileName().toString(), file);
|
||||
} catch (Exception ex) {
|
||||
Logger.global.logDebug("Failed to parse resource-file '" + file + "': " + ex);
|
||||
}
|
||||
})
|
||||
);
|
||||
}),
|
||||
|
||||
// load block-properties configs
|
||||
CompletableFuture.runAsync(() -> {
|
||||
list(root.resolve("assets"))
|
||||
.map(path -> path.resolve("blockProperties.json"))
|
||||
.filter(Files::isRegularFile)
|
||||
.forEach(file -> {
|
||||
try {
|
||||
blockPropertiesConfig.load(file);
|
||||
} catch (Exception ex) {
|
||||
Logger.global.logDebug("Failed to parse resource-file '" + file + "': " + ex);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
).join();
|
||||
|
||||
} catch (RuntimeException ex) {
|
||||
Throwable cause = ex.getCause();
|
||||
if (cause instanceof IOException) throw (IOException) cause;
|
||||
if (cause != null) throw new IOException(cause);
|
||||
throw new IOException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void bake() throws IOException {
|
||||
Logger.global.logInfo("Baking resources...");
|
||||
|
||||
for (ResourcePath<BlockState> path : blockStates.keySet()) {
|
||||
blockStatePaths.put(path.getFormatted(), path);
|
||||
}
|
||||
|
||||
for (BlockModel model : blockModels.values()) {
|
||||
model.applyParent(this);
|
||||
model.calculateProperties(this);
|
||||
}
|
||||
|
||||
BufferedImage foliage = new ResourcePath<BufferedImage>("minecraft:colormap/foliage").getResource(colormaps::get);
|
||||
if (foliage == null) throw new IOException("Failed to bake resource-pack: No foliage-colormap found!");
|
||||
this.colorCalculatorFactory.setFoliageMap(foliage);
|
||||
|
||||
BufferedImage grass = new ResourcePath<BufferedImage>("minecraft:colormap/grass").getResource(colormaps::get);
|
||||
if (grass == null) throw new IOException("Failed to bake resource-pack: No grass-colormap found!");
|
||||
this.colorCalculatorFactory.setGrassMap(grass);
|
||||
}
|
||||
|
||||
private <T> void loadResource(Path root, Path file, Loader<T> loader, Map<ResourcePath<T>, T> resultMap) {
|
||||
try {
|
||||
ResourcePath<T> resourcePath = new ResourcePath<>(root.relativize(file));
|
||||
if (resultMap.containsKey(resourcePath)) return; // don't load already present resources
|
||||
|
||||
T resource = loader.load();
|
||||
resourcePath.setResource(resource);
|
||||
resultMap.put(resourcePath, resource);
|
||||
} catch (Exception ex) {
|
||||
Logger.global.logDebug("Failed to parse resource-file '" + file + "': " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<Path> list(Path root) {
|
||||
if (!Files.isDirectory(root)) return Stream.empty();
|
||||
try {
|
||||
return Files.list(root);
|
||||
} catch (IOException ex) {
|
||||
throw new CompletionException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<Path> walk(Path root) {
|
||||
if (!Files.isDirectory(root)) return Stream.empty();
|
||||
try {
|
||||
return Files.walk(root);
|
||||
} catch (IOException ex) {
|
||||
throw new CompletionException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private interface Loader<T> {
|
||||
T load() throws IOException;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package de.bluecolored.bluemap.core.resources.resourcepack.blockmodel;
|
||||
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.resources.ResourcePath;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.texture.Texture;
|
||||
import de.bluecolored.bluemap.core.util.Direction;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
||||
@DebugDump
|
||||
public class BlockModel {
|
||||
|
||||
private ResourcePath<BlockModel> parent;
|
||||
private Map<String, TextureVariable> textures = new HashMap<>();
|
||||
private List<Element> elements;
|
||||
private boolean ambientocclusion = true;
|
||||
|
||||
private transient boolean liquid = false;
|
||||
private transient boolean culling = false;
|
||||
private transient boolean occluding = false;
|
||||
|
||||
private BlockModel(){}
|
||||
|
||||
@Nullable
|
||||
public ResourcePath<BlockModel> getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public Map<String, TextureVariable> getTextures() {
|
||||
return textures;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<Element> getElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
public boolean isAmbientocclusion() {
|
||||
return ambientocclusion;
|
||||
}
|
||||
|
||||
public boolean isLiquid() {
|
||||
return liquid;
|
||||
}
|
||||
|
||||
public boolean isCulling() {
|
||||
return culling;
|
||||
}
|
||||
|
||||
public boolean isOccluding() {
|
||||
return occluding;
|
||||
}
|
||||
|
||||
public synchronized void applyParent(ResourcePack resourcePack) {
|
||||
if (this.parent == null) return;
|
||||
|
||||
// set parent to null early to avoid trying to resolve reference-loops
|
||||
ResourcePath<BlockModel> parentPath = this.parent;
|
||||
this.parent = null;
|
||||
|
||||
if (parentPath.getFormatted().equals("bluemap:builtin/liquid")) {
|
||||
this.liquid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
BlockModel parent = parentPath.getResource(resourcePack::getBlockModel);
|
||||
if (parent != null) {
|
||||
parent.applyParent(resourcePack);
|
||||
|
||||
parent.textures.forEach(this.textures::putIfAbsent);
|
||||
if (this.elements == null) this.elements = parent.elements;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void calculateProperties(ResourcePack resourcePack) {
|
||||
if (elements == null) return;
|
||||
for (Element element : elements) {
|
||||
if (element.isFullCube()) {
|
||||
occluding = true;
|
||||
|
||||
culling = true;
|
||||
for (Direction dir : Direction.values()) {
|
||||
Face face = element.getFaces().get(dir);
|
||||
if (face == null) {
|
||||
culling = false;
|
||||
break;
|
||||
}
|
||||
|
||||
ResourcePath<Texture> textureResourcePath = face.getTexture().getTexturePath(textures::get);
|
||||
if (textureResourcePath == null) {
|
||||
culling = false;
|
||||
break;
|
||||
}
|
||||
|
||||
Texture texture = textureResourcePath.getResource(resourcePack::getTexture);
|
||||
if (texture == null || texture.getColorStraight().a < 1) {
|
||||
culling = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
package de.bluecolored.bluemap.core.resources.resourcepack.blockmodel;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3f;
|
||||
import com.flowpowered.math.vector.Vector4f;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.resources.AbstractTypeAdapterFactory;
|
||||
import de.bluecolored.bluemap.core.util.Direction;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.EnumMap;
|
||||
|
||||
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
||||
@DebugDump
|
||||
@JsonAdapter(Element.Adapter.class)
|
||||
public class Element {
|
||||
private static final Vector3f FULL_BLOCK_MIN = Vector3f.ZERO;
|
||||
private static final Vector3f FULL_BLOCK_MAX = new Vector3f(16, 16, 16);
|
||||
|
||||
private Vector3f from = FULL_BLOCK_MIN, to = FULL_BLOCK_MAX;
|
||||
private Rotation rotation = Rotation.ZERO;
|
||||
private boolean shade = true;
|
||||
private EnumMap<Direction, Face> faces = new EnumMap<>(Direction.class);
|
||||
|
||||
private Element(){}
|
||||
|
||||
private void init() {
|
||||
faces.forEach((direction, face) -> face.init(direction, this::calculateDefaultUV));
|
||||
}
|
||||
|
||||
private Vector4f calculateDefaultUV(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
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3f getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public Vector3f getTo() {
|
||||
return to;
|
||||
}
|
||||
|
||||
public Rotation getRotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
public boolean isShade() {
|
||||
return shade;
|
||||
}
|
||||
|
||||
public EnumMap<Direction, Face> getFaces() {
|
||||
return faces;
|
||||
}
|
||||
|
||||
boolean isFullCube() {
|
||||
if (!(FULL_BLOCK_MIN.equals(from) && FULL_BLOCK_MAX.equals(to))) return false;
|
||||
for (Direction dir : Direction.values()) {
|
||||
if (!faces.containsKey(dir)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static class Adapter extends AbstractTypeAdapterFactory<Element> {
|
||||
|
||||
public Adapter() {
|
||||
super(Element.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Element read(JsonReader in, Gson gson) throws IOException {
|
||||
Element element = gson.getDelegateAdapter(this, TypeToken.get(Element.class)).read(in);
|
||||
element.init();
|
||||
return element;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package de.bluecolored.bluemap.core.resources.resourcepack.blockmodel;
|
||||
|
||||
import com.flowpowered.math.vector.Vector4f;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.resources.ResourcePath;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.texture.Texture;
|
||||
import de.bluecolored.bluemap.core.util.Direction;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
||||
@DebugDump
|
||||
public class Face {
|
||||
|
||||
private static final TextureVariable DEFAULT_TEXTURE = new TextureVariable((ResourcePath<Texture>) null);
|
||||
|
||||
private Vector4f uv;
|
||||
private TextureVariable texture = DEFAULT_TEXTURE;
|
||||
private Direction cullface;
|
||||
private int rotation = 0;
|
||||
private int tintindex = -1;
|
||||
|
||||
private Face(){}
|
||||
|
||||
void init(Direction direction, Function<Direction, Vector4f> defaultUvCalculator) {
|
||||
if (cullface == null) cullface = direction;
|
||||
if (uv == null) uv = defaultUvCalculator.apply(direction);
|
||||
}
|
||||
|
||||
public Vector4f getUv() {
|
||||
return uv;
|
||||
}
|
||||
|
||||
public TextureVariable getTexture() {
|
||||
return texture;
|
||||
}
|
||||
|
||||
public Direction getCullface() {
|
||||
return cullface;
|
||||
}
|
||||
|
||||
public int getRotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
public int getTintindex() {
|
||||
return tintindex;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
package de.bluecolored.bluemap.core.resources.resourcepack.blockmodel;
|
||||
|
||||
import com.flowpowered.math.TrigMath;
|
||||
import com.flowpowered.math.vector.Vector3f;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.resources.AbstractTypeAdapterFactory;
|
||||
import de.bluecolored.bluemap.core.util.math.Axis;
|
||||
import de.bluecolored.bluemap.core.util.math.MatrixM4f;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
@DebugDump
|
||||
@JsonAdapter(Rotation.Adapter.class)
|
||||
public class Rotation {
|
||||
private static final Vector3f DEFAULT_ORIGIN = new Vector3f(8, 8, 8);
|
||||
private static final double FIT_TO_BLOCK_SCALE_MULTIPLIER = 2 - Math.sqrt(2);
|
||||
|
||||
public static final Rotation ZERO = new Rotation();
|
||||
static {
|
||||
ZERO.init();
|
||||
}
|
||||
|
||||
private Vector3f origin = DEFAULT_ORIGIN;
|
||||
private Axis axis = Axis.Y;
|
||||
private float angle = 0;
|
||||
private boolean rescale = false;
|
||||
|
||||
private transient MatrixM4f matrix;
|
||||
|
||||
private Rotation(){}
|
||||
|
||||
private void init() {
|
||||
Vector3i axisAngle = axis.toVector();
|
||||
|
||||
matrix = new MatrixM4f();
|
||||
if (angle != 0f) {
|
||||
matrix.translate(-origin.getX(), -origin.getY(), -origin.getZ());
|
||||
matrix.rotate(
|
||||
angle,
|
||||
axisAngle.getX(),
|
||||
axisAngle.getY(),
|
||||
axisAngle.getZ()
|
||||
);
|
||||
|
||||
if (rescale) {
|
||||
float scale = (float) (Math.abs(TrigMath.sin(angle * TrigMath.DEG_TO_RAD)) * FIT_TO_BLOCK_SCALE_MULTIPLIER);
|
||||
matrix.scale(
|
||||
(1 - axisAngle.getX()) * scale + 1,
|
||||
(1 - axisAngle.getY()) * scale + 1,
|
||||
(1 - axisAngle.getZ()) * scale + 1
|
||||
);
|
||||
}
|
||||
|
||||
matrix.translate(origin.getX(), origin.getY(), origin.getZ());
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3f getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
public Axis getAxis() {
|
||||
return axis;
|
||||
}
|
||||
|
||||
public double getAngle() {
|
||||
return angle;
|
||||
}
|
||||
|
||||
public boolean isRescale() {
|
||||
return rescale;
|
||||
}
|
||||
|
||||
public MatrixM4f getMatrix() {
|
||||
return matrix;
|
||||
}
|
||||
|
||||
static class Adapter extends AbstractTypeAdapterFactory<Rotation> {
|
||||
|
||||
public Adapter() {
|
||||
super(Rotation.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rotation read(JsonReader in, Gson gson) throws IOException {
|
||||
Rotation rotation = gson.getDelegateAdapter(this, TypeToken.get(Rotation.class)).read(in);
|
||||
rotation.init();
|
||||
return rotation;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package de.bluecolored.bluemap.core.resources.resourcepack.blockmodel;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.resources.ResourcePath;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.texture.Texture;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Function;
|
||||
|
||||
@DebugDump
|
||||
@JsonAdapter(TextureVariable.Adapter.class)
|
||||
public class TextureVariable {
|
||||
|
||||
private String referenceName;
|
||||
private ResourcePath<Texture> texturePath;
|
||||
|
||||
private transient volatile boolean isReference, isResolving;
|
||||
|
||||
public TextureVariable(String referenceName) {
|
||||
this.referenceName = referenceName;
|
||||
this.texturePath = null;
|
||||
this.isReference = true;
|
||||
this.isResolving = false;
|
||||
}
|
||||
|
||||
public TextureVariable(ResourcePath<Texture> texturePath) {
|
||||
this.referenceName = null;
|
||||
this.texturePath = texturePath;
|
||||
this.isReference = false;
|
||||
this.isResolving = false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getReferenceName() {
|
||||
return referenceName;
|
||||
}
|
||||
|
||||
public void setReferenceName(String referenceName) {
|
||||
this.referenceName = referenceName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ResourcePath<Texture> getTexturePath() {
|
||||
return texturePath;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ResourcePath<Texture> getTexturePath(Function<String, TextureVariable> supplier) {
|
||||
if (this.isReference) return resolveTexturePath(supplier);
|
||||
return this.texturePath;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ResourcePath<Texture> resolveTexturePath(Function<String, TextureVariable> supplier) {
|
||||
synchronized (TextureVariable.class) {
|
||||
if (this.isReference && !this.isResolving) {
|
||||
this.isResolving = true; // set to avoid trying to resolve reference-loops
|
||||
|
||||
// resolve
|
||||
TextureVariable referenced = supplier.apply(this.referenceName);
|
||||
if (referenced != null) {
|
||||
this.texturePath = referenced.getTexturePath(supplier);
|
||||
}
|
||||
|
||||
this.isReference = false;
|
||||
}
|
||||
return this.texturePath;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTexturePath(ResourcePath<Texture> texturePath) {
|
||||
this.texturePath = texturePath;
|
||||
}
|
||||
|
||||
public boolean isReference() {
|
||||
return isReference;
|
||||
}
|
||||
|
||||
static class Adapter extends TypeAdapter<TextureVariable> {
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, TextureVariable value) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextureVariable read(JsonReader in) throws IOException {
|
||||
String value = in.nextString();
|
||||
if (value.isEmpty()) throw new IOException("Can't parse an empty String into a TextureVariable");
|
||||
if (value.charAt(0) == '#') {
|
||||
return new TextureVariable(value.substring(1));
|
||||
} else {
|
||||
return new TextureVariable(new ResourcePath<>(value));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package de.bluecolored.bluemap.core.resources.resourcepack.blockstate;
|
||||
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
@DebugDump
|
||||
public class BlockState {
|
||||
|
||||
private Variants variants = null;
|
||||
private Multipart multipart = null;
|
||||
|
||||
private BlockState(){}
|
||||
|
||||
@Nullable
|
||||
public Variants getVariants() {
|
||||
return variants;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Multipart getMultipart() {
|
||||
return multipart;
|
||||
}
|
||||
|
||||
public void forEach(de.bluecolored.bluemap.core.world.BlockState blockState, int x, int y, int z, Consumer<Variant> consumer) {
|
||||
if (variants != null) variants.forEach(blockState, x, y, z, consumer);
|
||||
if (multipart != null) multipart.forEach(blockState, x, y, z, consumer);
|
||||
}
|
||||
|
||||
}
|
@ -22,30 +22,34 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.resourcepack.blockstate;
|
||||
package de.bluecolored.bluemap.core.resources.resourcepack.blockstate;
|
||||
|
||||
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.util.Preconditions;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface PropertyCondition {
|
||||
public interface BlockStateCondition {
|
||||
|
||||
PropertyCondition MATCH_ALL = new All();
|
||||
PropertyCondition MATCH_NONE = new None();
|
||||
BlockStateCondition MATCH_ALL = new All();
|
||||
BlockStateCondition MATCH_NONE = new None();
|
||||
|
||||
boolean matches(BlockState state);
|
||||
|
||||
class Property implements PropertyCondition {
|
||||
@DebugDump
|
||||
class Property implements BlockStateCondition {
|
||||
|
||||
private final String key;
|
||||
private final String value;
|
||||
|
||||
private Property (String key, String value) {
|
||||
this.key = key.toLowerCase();
|
||||
this.value = value.toLowerCase();
|
||||
private Property(String key, String value) {
|
||||
this.key = key.toLowerCase(Locale.ROOT);
|
||||
this.value = value.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -57,19 +61,55 @@ public boolean matches(BlockState state) {
|
||||
|
||||
}
|
||||
|
||||
class And implements PropertyCondition {
|
||||
@DebugDump
|
||||
class PropertySet implements BlockStateCondition {
|
||||
|
||||
private final PropertyCondition[] conditions;
|
||||
private final String key;
|
||||
private final Set<String> possibleValues;
|
||||
|
||||
private And (PropertyCondition... conditions) {
|
||||
Preconditions.checkArgument(conditions.length > 0, "Must be at least one condition!");
|
||||
|
||||
this.conditions = conditions;
|
||||
private PropertySet(String key, String... possibleValues) {
|
||||
this.key = key.toLowerCase(Locale.ROOT);
|
||||
this.possibleValues = new HashSet<>();
|
||||
for (String value : possibleValues) this.possibleValues.add(value.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(BlockState state) {
|
||||
for (PropertyCondition condition : conditions) {
|
||||
String value = state.getProperties().get(this.key);
|
||||
if (value == null) return false;
|
||||
return possibleValues.contains(value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@DebugDump
|
||||
class And implements BlockStateCondition {
|
||||
|
||||
final BlockStateCondition[] conditions;
|
||||
final int distinctProperties;
|
||||
|
||||
private And (BlockStateCondition... conditions) {
|
||||
Preconditions.checkArgument(conditions.length > 0, "Must be at least one condition!");
|
||||
|
||||
this.conditions = conditions;
|
||||
|
||||
// Optimization: count distinct properties
|
||||
Set<String> distinctPropertiesSet = new HashSet<>();
|
||||
for (BlockStateCondition condition : this.conditions) {
|
||||
if (condition instanceof Property) {
|
||||
distinctPropertiesSet.add(((Property) condition).key);
|
||||
}
|
||||
}
|
||||
this.distinctProperties = distinctPropertiesSet.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(BlockState state) {
|
||||
// fast exit
|
||||
if (state.getProperties().size() < this.distinctProperties) return false;
|
||||
|
||||
// check all
|
||||
for (BlockStateCondition condition : conditions) {
|
||||
if (!condition.matches(state)) return false;
|
||||
}
|
||||
return true;
|
||||
@ -77,11 +117,12 @@ public boolean matches(BlockState state) {
|
||||
|
||||
}
|
||||
|
||||
class Or implements PropertyCondition {
|
||||
@DebugDump
|
||||
class Or implements BlockStateCondition {
|
||||
|
||||
private final PropertyCondition[] conditions;
|
||||
private final BlockStateCondition[] conditions;
|
||||
|
||||
private Or (PropertyCondition... conditions) {
|
||||
private Or (BlockStateCondition... conditions) {
|
||||
Preconditions.checkArgument(conditions.length > 0, "Must be at least one condition!");
|
||||
|
||||
this.conditions = conditions;
|
||||
@ -89,7 +130,7 @@ private Or (PropertyCondition... conditions) {
|
||||
|
||||
@Override
|
||||
public boolean matches(BlockState state) {
|
||||
for (PropertyCondition condition : conditions) {
|
||||
for (BlockStateCondition condition : conditions) {
|
||||
if (condition.matches(state)) return true;
|
||||
}
|
||||
return false;
|
||||
@ -97,7 +138,7 @@ public boolean matches(BlockState state) {
|
||||
|
||||
}
|
||||
|
||||
class All implements PropertyCondition {
|
||||
class All implements BlockStateCondition {
|
||||
|
||||
@Override
|
||||
public boolean matches(BlockState state) {
|
||||
@ -106,7 +147,7 @@ public boolean matches(BlockState state) {
|
||||
|
||||
}
|
||||
|
||||
class None implements PropertyCondition {
|
||||
class None implements BlockStateCondition {
|
||||
|
||||
@Override
|
||||
public boolean matches(BlockState state) {
|
||||
@ -115,56 +156,34 @@ public boolean matches(BlockState state) {
|
||||
|
||||
}
|
||||
|
||||
static PropertyCondition all() {
|
||||
static BlockStateCondition all() {
|
||||
return MATCH_ALL;
|
||||
}
|
||||
|
||||
static PropertyCondition none() {
|
||||
static BlockStateCondition none() {
|
||||
return MATCH_NONE;
|
||||
}
|
||||
|
||||
static PropertyCondition and(PropertyCondition... conditions) {
|
||||
static BlockStateCondition and(BlockStateCondition... conditions) {
|
||||
Preconditions.checkArgument(conditions.length > 0, "Must be at least one condition!");
|
||||
|
||||
if (conditions.length == 1) return conditions[0];
|
||||
return new And(conditions);
|
||||
}
|
||||
|
||||
static PropertyCondition or(PropertyCondition... conditions) {
|
||||
static BlockStateCondition or(BlockStateCondition... conditions) {
|
||||
Preconditions.checkArgument(conditions.length > 0, "Must be at least one condition!");
|
||||
|
||||
if (conditions.length == 1) return conditions[0];
|
||||
return new Or(conditions);
|
||||
}
|
||||
|
||||
static PropertyCondition property(String key, String value) {
|
||||
static BlockStateCondition property(String key, String value) {
|
||||
return new Property(key, value);
|
||||
}
|
||||
|
||||
static PropertyCondition property(String key, String... possibleValues) {
|
||||
static BlockStateCondition 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);
|
||||
if (possibleValues.length == 1)return property(key, possibleValues[0]);
|
||||
return new PropertySet(key, possibleValues);
|
||||
}
|
||||
|
||||
static PropertyCondition blockState(BlockState state) {
|
||||
Map<String, String> props = state.getProperties();
|
||||
if (props.isEmpty()) return all();
|
||||
|
||||
PropertyCondition[] conditions = new Property[props.size()];
|
||||
int i = 0;
|
||||
for (Map.Entry<String, String> prop : props.entrySet()) {
|
||||
conditions[i++] = property(prop.getKey(), prop.getValue());
|
||||
}
|
||||
|
||||
return and(conditions);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package de.bluecolored.bluemap.core.resources.resourcepack.blockstate;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.resources.AbstractTypeAdapterFactory;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
@DebugDump
|
||||
@JsonAdapter(Multipart.Adapter.class)
|
||||
public class Multipart {
|
||||
|
||||
private List<VariantSet> parts = new ArrayList<>();
|
||||
|
||||
private Multipart(){}
|
||||
|
||||
public List<VariantSet> getParts() {
|
||||
return parts;
|
||||
}
|
||||
|
||||
public void forEach(BlockState blockState, int x, int y, int z, Consumer<Variant> consumer) {
|
||||
for (VariantSet part : parts) {
|
||||
if (part.getCondition().matches(blockState)) {
|
||||
part.forEach(x, y, z, consumer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class Adapter extends AbstractTypeAdapterFactory<Multipart> {
|
||||
|
||||
public Adapter() {
|
||||
super(Multipart.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Multipart read(JsonReader in, Gson gson) throws IOException {
|
||||
Multipart result = new Multipart();
|
||||
|
||||
in.beginArray();
|
||||
while (in.hasNext()) {
|
||||
VariantSet variantSet = null;
|
||||
BlockStateCondition condition = null;
|
||||
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
String key = in.nextName();
|
||||
if (key.equals("when")) condition = readCondition(in);
|
||||
if (key.equals("apply")) variantSet = gson.fromJson(in, VariantSet.class);
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
if (variantSet == null) continue;
|
||||
if (condition != null) variantSet.setCondition(condition);
|
||||
result.parts.add(variantSet);
|
||||
}
|
||||
in.endArray();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public BlockStateCondition readCondition(JsonReader in) throws IOException {
|
||||
List<BlockStateCondition> andConditions = new ArrayList<>();
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
String name = in.nextName();
|
||||
if (name.equals(JSON_COMMENT)) continue;
|
||||
|
||||
if (name.equals("OR")) {
|
||||
List<BlockStateCondition> orConditions = new ArrayList<>();
|
||||
in.beginArray();
|
||||
while (in.hasNext()) {
|
||||
orConditions.add(readCondition(in));
|
||||
}
|
||||
in.endArray();
|
||||
andConditions.add(
|
||||
BlockStateCondition.or(orConditions.toArray(new BlockStateCondition[0])));
|
||||
} else {
|
||||
String[] values = StringUtils.split(in.nextString(), '|');
|
||||
andConditions.add(BlockStateCondition.property(name, values));
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
return BlockStateCondition.and(andConditions.toArray(new BlockStateCondition[0]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package de.bluecolored.bluemap.core.resources.resourcepack.blockstate;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.resources.AbstractTypeAdapterFactory;
|
||||
import de.bluecolored.bluemap.core.resources.ResourcePath;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.BlockModel;
|
||||
import de.bluecolored.bluemap.core.util.math.MatrixM3f;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
||||
@DebugDump
|
||||
@JsonAdapter(Variant.Adapter.class)
|
||||
public class Variant {
|
||||
|
||||
private ResourcePath<BlockModel> model = ResourcePack.MISSING_BLOCK_MODEL;
|
||||
private float x = 0, y = 0;
|
||||
private boolean uvlock = false;
|
||||
private double weight = 1;
|
||||
|
||||
private transient boolean rotated;
|
||||
private transient MatrixM3f rotationMatrix;
|
||||
|
||||
private Variant(){}
|
||||
|
||||
private void init() {
|
||||
this.rotated = x != 0 || y != 0;
|
||||
this.rotationMatrix = new MatrixM3f().rotate(-x, -y, 0);
|
||||
}
|
||||
|
||||
public ResourcePath<BlockModel> getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public float getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public float getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public boolean isUvlock() {
|
||||
return uvlock;
|
||||
}
|
||||
|
||||
public double getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
public boolean isRotated() {
|
||||
return rotated;
|
||||
}
|
||||
|
||||
public MatrixM3f getRotationMatrix() {
|
||||
return rotationMatrix;
|
||||
}
|
||||
|
||||
static class Adapter extends AbstractTypeAdapterFactory<Variant> {
|
||||
|
||||
public Adapter() {
|
||||
super(Variant.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Variant read(JsonReader in, Gson gson) throws IOException {
|
||||
Variant variant = gson.getDelegateAdapter(this, TypeToken.get(Variant.class)).read(in);
|
||||
variant.init();
|
||||
return variant;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package de.bluecolored.bluemap.core.resources.resourcepack.blockstate;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.resources.AbstractTypeAdapterFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
@DebugDump
|
||||
@JsonAdapter(VariantSet.Adapter.class)
|
||||
public class VariantSet {
|
||||
|
||||
private BlockStateCondition condition;
|
||||
private List<Variant> variants;
|
||||
|
||||
private transient double totalWeight;
|
||||
|
||||
public VariantSet(List<Variant> variants) {
|
||||
this(BlockStateCondition.all(), variants);
|
||||
}
|
||||
|
||||
public VariantSet(BlockStateCondition condition, List<Variant> variants) {
|
||||
this.condition = condition;
|
||||
this.variants = variants;
|
||||
|
||||
this.totalWeight = summarizeWeights();
|
||||
}
|
||||
|
||||
public BlockStateCondition getCondition() {
|
||||
return condition;
|
||||
}
|
||||
|
||||
public void setCondition(BlockStateCondition condition) {
|
||||
this.condition = condition;
|
||||
}
|
||||
|
||||
public List<Variant> getVariants() {
|
||||
return variants;
|
||||
}
|
||||
|
||||
private double summarizeWeights() {
|
||||
return variants.stream()
|
||||
.mapToDouble(Variant::getWeight)
|
||||
.sum();
|
||||
}
|
||||
|
||||
public void forEach(int x, int y, int z, Consumer<Variant> consumer) {
|
||||
double selection = hashToFloat(x, y, z) * totalWeight; // random based on position
|
||||
for (Variant variant : variants) {
|
||||
selection -= variant.getWeight();
|
||||
if (selection <= 0) {
|
||||
consumer.accept(variant);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static float hashToFloat(int x, int y, int z) {
|
||||
final long hash = x * 73438747 ^ y * 9357269 ^ z * 4335792;
|
||||
return (hash * (hash + 456149) & 0x00ffffff) / (float) 0x01000000;
|
||||
}
|
||||
|
||||
static class Adapter extends AbstractTypeAdapterFactory<VariantSet> {
|
||||
|
||||
public Adapter() {
|
||||
super(VariantSet.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VariantSet read(JsonReader in, Gson gson) throws IOException {
|
||||
List<Variant> variants;
|
||||
if (in.peek() == JsonToken.BEGIN_ARRAY) {
|
||||
variants = gson.fromJson(in, new TypeToken<List<Variant>>(){}.getType());
|
||||
} else {
|
||||
variants = Collections.singletonList(gson.fromJson(in, Variant.class));
|
||||
}
|
||||
|
||||
return new VariantSet(variants);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
package de.bluecolored.bluemap.core.resources.resourcepack.blockstate;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.resources.AbstractTypeAdapterFactory;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
@DebugDump
|
||||
@JsonAdapter(Variants.Adapter.class)
|
||||
public class Variants {
|
||||
|
||||
private List<VariantSet> variants = new ArrayList<>();
|
||||
private VariantSet defaultVariant;
|
||||
|
||||
private Variants(){}
|
||||
|
||||
public List<VariantSet> getVariants() {
|
||||
return variants;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public VariantSet getDefaultVariant() {
|
||||
return defaultVariant;
|
||||
}
|
||||
|
||||
public void forEach(BlockState blockState, int x, int y, int z, Consumer<Variant> consumer) {
|
||||
for (VariantSet variant : variants){
|
||||
if (variant.getCondition().matches(blockState)){
|
||||
variant.forEach(x, y, z, consumer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// still here? do default
|
||||
if (defaultVariant != null) {
|
||||
defaultVariant.forEach(x, y, z, consumer);
|
||||
}
|
||||
}
|
||||
|
||||
static class Adapter extends AbstractTypeAdapterFactory<Variants> {
|
||||
|
||||
public Adapter() {
|
||||
super(Variants.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Variants read(JsonReader in, Gson gson) throws IOException {
|
||||
Variants result = new Variants();
|
||||
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
String name = in.nextName();
|
||||
if (name.equals(JSON_COMMENT)) continue;
|
||||
|
||||
BlockStateCondition condition = parseConditionString(name);
|
||||
VariantSet variantSet = gson.fromJson(in, VariantSet.class);
|
||||
variantSet.setCondition(condition);
|
||||
|
||||
if (variantSet.getCondition() == BlockStateCondition.all()) {
|
||||
result.defaultVariant = variantSet;
|
||||
} else {
|
||||
result.variants.add(variantSet);
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private BlockStateCondition parseConditionString(String conditionString) {
|
||||
List<BlockStateCondition> conditions = new ArrayList<>();
|
||||
if (!conditionString.isEmpty() && !conditionString.equals("default") && !conditionString.equals("normal")) {
|
||||
String[] conditionSplit = StringUtils.split(conditionString, ',');
|
||||
for (String element : conditionSplit) {
|
||||
String[] keyval = StringUtils.split(element, "=", 2);
|
||||
if (keyval.length < 2)
|
||||
throw new IllegalArgumentException("Condition-String '" + conditionString + "' is invalid!");
|
||||
conditions.add(BlockStateCondition.property(keyval[0], keyval[1]));
|
||||
}
|
||||
}
|
||||
|
||||
BlockStateCondition condition;
|
||||
if (conditions.isEmpty()) {
|
||||
condition = BlockStateCondition.all();
|
||||
} else if (conditions.size() == 1) {
|
||||
condition = conditions.get(0);
|
||||
} else {
|
||||
condition = BlockStateCondition.and(conditions.toArray(new BlockStateCondition[0]));
|
||||
}
|
||||
|
||||
return condition;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
package de.bluecolored.bluemap.core.resources.resourcepack.texture;
|
||||
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.resources.ResourcePath;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
|
||||
@DebugDump
|
||||
public class Texture {
|
||||
|
||||
public static final Texture MISSING = new Texture(
|
||||
new ResourcePath<>("bluemap", "missing"),
|
||||
new Color().set(0.5f, 0f, 0.5f, 1.0f, false),
|
||||
false,
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAPklEQVR4Xu3MsQkAMAwDQe2/tFPnBB4gpLhG8MpkZpNkZ6AKZKAKZKAKZKAKZKAKZKAKZKAKWg0XD/UPnjg4MbX+EDdeTUwAAAAASUVORK5CYII\u003d"
|
||||
);
|
||||
|
||||
private ResourcePath<Texture> resourcePath;
|
||||
private Color color;
|
||||
private boolean halfTransparent;
|
||||
private String texture;
|
||||
|
||||
private transient Color colorPremultiplied;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private Texture() {}
|
||||
|
||||
private Texture(ResourcePath<Texture> resourcePath, Color color, boolean halfTransparent, String texture) {
|
||||
this.resourcePath = resourcePath;
|
||||
this.color = color.straight();
|
||||
this.halfTransparent = halfTransparent;
|
||||
this.texture = texture;
|
||||
}
|
||||
|
||||
public ResourcePath<Texture> getResourcePath() {
|
||||
return resourcePath;
|
||||
}
|
||||
|
||||
public Color getColorStraight() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public boolean isHalfTransparent() {
|
||||
return halfTransparent;
|
||||
}
|
||||
|
||||
public Color getColorPremultiplied() {
|
||||
if (colorPremultiplied == null && color != null) {
|
||||
colorPremultiplied = new Color()
|
||||
.set(color)
|
||||
.premultiplied();
|
||||
}
|
||||
|
||||
return colorPremultiplied;
|
||||
}
|
||||
|
||||
public String getTexture() {
|
||||
return texture;
|
||||
}
|
||||
|
||||
public void unloadImageData() {
|
||||
texture = null;
|
||||
}
|
||||
|
||||
public static Texture from(ResourcePath<Texture> resourcePath, BufferedImage image) throws IOException {
|
||||
//crop off animation frames
|
||||
if (image.getHeight() > image.getWidth()){
|
||||
image = image.getSubimage(0, 0, image.getWidth(), image.getWidth());
|
||||
}
|
||||
|
||||
//check halfTransparency
|
||||
boolean halfTransparent = checkHalfTransparent(image);
|
||||
|
||||
//calculate color
|
||||
Color 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());
|
||||
|
||||
return new Texture(resourcePath, color, halfTransparent, base64);
|
||||
}
|
||||
|
||||
private static 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 static Color calculateColor(BufferedImage image){
|
||||
float alpha = 0f, red = 0f, green = 0f, blue = 0f;
|
||||
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);
|
||||
float pixelAlpha = ((pixel >> 24) & 0xff) / 255f;
|
||||
float pixelRed = ((pixel >> 16) & 0xff) / 255f;
|
||||
float pixelGreen = ((pixel >> 8) & 0xff) / 255f;
|
||||
float pixelBlue = (pixel & 0xff) / 255f;
|
||||
|
||||
count++;
|
||||
alpha += pixelAlpha;
|
||||
red += pixelRed * pixelAlpha;
|
||||
green += pixelGreen * pixelAlpha;
|
||||
blue += pixelBlue * pixelAlpha;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0 || alpha == 0) return new Color();
|
||||
|
||||
red /= alpha;
|
||||
green /= alpha;
|
||||
blue /= alpha;
|
||||
alpha /= count;
|
||||
|
||||
return new Color().set(red, green, blue, alpha, false);
|
||||
}
|
||||
|
||||
}
|
@ -26,7 +26,7 @@
|
||||
|
||||
public enum MetaType {
|
||||
|
||||
//TEXTURES ("textures", "textures.json", "application/json"),
|
||||
TEXTURES ("textures", "textures.json", "application/json"),
|
||||
//SETTINGS ("settings", "settings.json", "application/json"),
|
||||
//MARKERS ("markers", "markers.json", "application/json"),
|
||||
RENDER_STATE ("render_state", ".rstate", "application/octet-stream");
|
||||
|
@ -28,13 +28,15 @@
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.storage.*;
|
||||
import de.bluecolored.bluemap.core.util.AtomicFileHelper;
|
||||
import de.bluecolored.bluemap.core.util.FileUtils;
|
||||
import de.bluecolored.bluemap.core.util.DeletingPathVisitor;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@DebugDump
|
||||
public class FileStorage extends Storage {
|
||||
@ -123,7 +125,7 @@ public long getLastModified() {
|
||||
@Override
|
||||
public void deleteMapTile(String mapId, TileType tileType, Vector2i tile) throws IOException {
|
||||
Path file = getFilePath(mapId, tileType, tile);
|
||||
FileUtils.delete(file.toFile());
|
||||
Files.deleteIfExists(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -151,12 +153,12 @@ public Optional<CompressedInputStream> readMeta(String mapId, MetaType metaType)
|
||||
@Override
|
||||
public void deleteMeta(String mapId, MetaType metaType) throws IOException {
|
||||
Path file = getFilePath(mapId).resolve(metaType.getFilePath());
|
||||
FileUtils.delete(file.toFile());
|
||||
Files.deleteIfExists(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void purgeMap(String mapId) throws IOException {
|
||||
FileUtils.delete(getFilePath(mapId).toFile());
|
||||
Files.walkFileTree(getFilePath(mapId), DeletingPathVisitor.INSTANCE);
|
||||
}
|
||||
|
||||
public Path getFilePath(String mapId, TileType tileType, Vector2i tile){
|
||||
|
@ -24,7 +24,6 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@ -32,10 +31,6 @@
|
||||
|
||||
public class AtomicFileHelper {
|
||||
|
||||
public static OutputStream createFilepartOutputStream(final File file) throws IOException {
|
||||
return createFilepartOutputStream(file.toPath());
|
||||
}
|
||||
|
||||
public static OutputStream createFilepartOutputStream(final Path file) throws IOException {
|
||||
final Path partFile = getPartFile(file);
|
||||
Files.createDirectories(partFile.getParent());
|
||||
|
@ -0,0 +1,26 @@
|
||||
package de.bluecolored.bluemap.core.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
|
||||
public class DeletingPathVisitor extends SimpleFileVisitor<Path> {
|
||||
|
||||
public static final DeletingPathVisitor INSTANCE = new DeletingPathVisitor();
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
Files.delete(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
/*
|
||||
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||
* 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.util;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
private FileUtils(){}
|
||||
|
||||
public static void delete(File file) throws IOException {
|
||||
if (file.exists()) org.apache.commons.io.FileUtils.forceDelete(file);
|
||||
}
|
||||
|
||||
public static void mkDirs(File directory) throws IOException {
|
||||
org.apache.commons.io.FileUtils.forceMkdir(directory);
|
||||
}
|
||||
|
||||
public static void mkDirsParent(File file) throws IOException {
|
||||
org.apache.commons.io.FileUtils.forceMkdirParent(file);
|
||||
}
|
||||
|
||||
public static void createFile(File file) throws IOException {
|
||||
if (!file.exists()) {
|
||||
org.apache.commons.io.FileUtils.forceMkdirParent(file);
|
||||
if (!file.createNewFile()) throw new IOException("Could not create file '" + file + "'!");
|
||||
} else {
|
||||
if (!file.isFile()) throw new IOException("File '" + file + "' exists but is not a normal file!");
|
||||
}
|
||||
}
|
||||
|
||||
public static File coordsToFile(Path root, Vector2i coords, String fileType){
|
||||
String path = "x" + coords.getX() + "z" + coords.getY();
|
||||
char[] cs = path.toCharArray();
|
||||
List<String> folders = new ArrayList<>();
|
||||
String folder = "";
|
||||
for (char c : cs){
|
||||
folder += c;
|
||||
if (c >= '0' && c <= '9'){
|
||||
folders.add(folder);
|
||||
folder = "";
|
||||
}
|
||||
}
|
||||
String fileName = folders.remove(folders.size() - 1);
|
||||
|
||||
Path p = root;
|
||||
for (String s : folders){
|
||||
p = p.resolve(s);
|
||||
}
|
||||
|
||||
return p.resolve(fileName + "." + fileType).toFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* The path-elements are being matched to the pattern-elements,
|
||||
* each pattern-element can be a regex pattern to match against one path-element or "*" to represent any number of arbitrary elements (lazy: until the next pattern matches).
|
||||
*/
|
||||
public static boolean matchPath(Path path, String... pattern) {
|
||||
int p = 0;
|
||||
for (int i = 0; i < path.getNameCount(); i++) {
|
||||
while (pattern[p].equals("*")) {
|
||||
p++;
|
||||
|
||||
if (pattern.length >= p) return true;
|
||||
}
|
||||
|
||||
if (Pattern.matches(pattern[p], path.getName(i).toString())) {
|
||||
p++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p > 0 && pattern[p-1].equals("*")) continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package de.bluecolored.bluemap.core.util;
|
||||
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.resources.ResourcePath;
|
||||
|
||||
@DebugDump
|
||||
public class Key {
|
||||
|
||||
private static final String MINECRAFT_NAMESPACE = "minecraft";
|
||||
|
||||
private final String namespace;
|
||||
private final String value;
|
||||
private final String formatted;
|
||||
|
||||
public Key(String formatted) {
|
||||
String namespace = MINECRAFT_NAMESPACE;
|
||||
String path = formatted;
|
||||
int namespaceSeparator = formatted.indexOf(':');
|
||||
if (namespaceSeparator > 0) {
|
||||
namespace = formatted.substring(0, namespaceSeparator);
|
||||
path = formatted.substring(namespaceSeparator + 1);
|
||||
}
|
||||
|
||||
this.namespace = namespace.intern();
|
||||
this.value = path.intern();
|
||||
this.formatted = (this.namespace + ":" + this.value).intern();
|
||||
}
|
||||
|
||||
public Key(String namespace, String value) {
|
||||
this.namespace = namespace.intern();
|
||||
this.value = value.intern();
|
||||
this.formatted = (this.namespace + ":" + this.value).intern();
|
||||
}
|
||||
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getFormatted() {
|
||||
return formatted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ResourcePath<?> that = (ResourcePath<?>) o;
|
||||
return getFormatted().equals(that.getFormatted());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getFormatted().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return formatted;
|
||||
}
|
||||
}
|
@ -107,9 +107,10 @@ public Color flatten() {
|
||||
if (this.a == 1f) return this;
|
||||
|
||||
if (premultiplied) {
|
||||
this.r /= this.a;
|
||||
this.g /= this.a;
|
||||
this.b /= this.a;
|
||||
float m = 1f / this.a;
|
||||
this.r *= m;
|
||||
this.g *= m;
|
||||
this.b *= m;
|
||||
}
|
||||
|
||||
this.a = 1f;
|
||||
|
@ -25,18 +25,14 @@
|
||||
package de.bluecolored.bluemap.core.world;
|
||||
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.util.ConfigUtils;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
|
||||
@DebugDump
|
||||
public class Biome {
|
||||
public class Biome extends Key {
|
||||
|
||||
public static final Biome DEFAULT = new Biome("minecraft:ocean");
|
||||
|
||||
private final String namespace;
|
||||
private final String id;
|
||||
private final String fullId;
|
||||
private float humidity = 0.5f;
|
||||
private float temp = 0.5f;
|
||||
private final Color waterColor = new Color().set(4159204).premultiplied();
|
||||
@ -44,43 +40,21 @@ public class Biome {
|
||||
private final Color overlayFoliageColor = new Color().premultiplied();
|
||||
private final Color overlayGrassColor = new Color().premultiplied();
|
||||
|
||||
public Biome(String id) {
|
||||
//resolve namespace
|
||||
String namespace = "minecraft";
|
||||
int namespaceSeperator = id.indexOf(':');
|
||||
if (namespaceSeperator > 0) {
|
||||
namespace = id.substring(0, namespaceSeperator);
|
||||
id = id.substring(namespaceSeperator + 1);
|
||||
}
|
||||
|
||||
this.id = id;
|
||||
this.namespace = namespace;
|
||||
this.fullId = namespace + ":" + id;
|
||||
public Biome(String formatted) {
|
||||
super(formatted);
|
||||
}
|
||||
|
||||
public Biome(String id, float humidity, float temp, Color waterColor) {
|
||||
this(id);
|
||||
public Biome(String formatted, float humidity, float temp, Color waterColor) {
|
||||
this(formatted);
|
||||
this.humidity = humidity;
|
||||
this.temp = temp;
|
||||
this.waterColor.set(waterColor);
|
||||
this.waterColor.set(waterColor).premultiplied();
|
||||
}
|
||||
|
||||
public Biome(String id, float humidity, float temp, Color waterColor, Color overlayFoliageColor, Color overlayGrassColor) {
|
||||
this (id, humidity, temp, waterColor);
|
||||
this.overlayFoliageColor.set(overlayFoliageColor);
|
||||
this.overlayGrassColor.set(overlayGrassColor);
|
||||
}
|
||||
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getFullId() {
|
||||
return fullId;
|
||||
public Biome(String formatted, float humidity, float temp, Color waterColor, Color overlayFoliageColor, Color overlayGrassColor) {
|
||||
this(formatted, humidity, temp, waterColor);
|
||||
this.overlayFoliageColor.set(overlayFoliageColor).premultiplied();
|
||||
this.overlayGrassColor.set(overlayGrassColor).premultiplied();
|
||||
}
|
||||
|
||||
public float getHumidity() {
|
||||
@ -103,23 +77,12 @@ public Color getOverlayGrassColor() {
|
||||
return overlayGrassColor;
|
||||
}
|
||||
|
||||
public static Biome create(String id, ConfigurationNode node) {
|
||||
Biome biome = new Biome(id);
|
||||
biome.humidity = (float) node.node("humidity").getDouble(biome.humidity);
|
||||
biome.temp = (float) node.node("temp").getDouble(biome.temp);
|
||||
try { biome.waterColor.set(ConfigUtils.readColorInt(node.node("watercolor"))).premultiplied(); } catch (NumberFormatException ignored) {}
|
||||
try { biome.overlayFoliageColor.set(ConfigUtils.readColorInt(node.node("foliagecolor"))).premultiplied(); } catch (NumberFormatException ignored) {}
|
||||
try { biome.overlayGrassColor.set(ConfigUtils.readColorInt(node.node("grasscolor"))).premultiplied(); } catch (NumberFormatException ignored) {}
|
||||
|
||||
return biome;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Biome{" +
|
||||
"id='" + id + '\'' +
|
||||
", namespace=" + namespace +
|
||||
", fullId=" + fullId +
|
||||
"value='" + getValue() + '\'' +
|
||||
", namespace=" + getNamespace() +
|
||||
", formatted=" + getFormatted() +
|
||||
", humidity=" + humidity +
|
||||
", temp=" + temp +
|
||||
", waterColor=" + waterColor +
|
||||
|
@ -173,7 +173,7 @@ public String toString() {
|
||||
'}';
|
||||
} else {
|
||||
return "Block{" +
|
||||
"world=" + world +
|
||||
"world=null" +
|
||||
", x=" + x +
|
||||
", y=" + y +
|
||||
", z=" + z +
|
||||
|
@ -25,7 +25,7 @@
|
||||
package de.bluecolored.bluemap.core.world;
|
||||
|
||||
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
|
||||
public class BlockNeighborhood<T extends BlockNeighborhood<T>> extends ExtendedBlock<T> {
|
||||
|
||||
|
@ -25,6 +25,8 @@
|
||||
package de.bluecolored.bluemap.core.world;
|
||||
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.util.Key;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
@ -38,7 +40,7 @@
|
||||
* <i>The implementation of this class has to be thread-save!</i><br>
|
||||
*/
|
||||
@DebugDump
|
||||
public class BlockState {
|
||||
public class BlockState extends Key {
|
||||
|
||||
private static final Pattern BLOCKSTATE_SERIALIZATION_PATTERN = Pattern.compile("^(.+?)(?:\\[(.*)])?$");
|
||||
|
||||
@ -48,69 +50,38 @@ public class BlockState {
|
||||
private boolean hashed;
|
||||
private int hash;
|
||||
|
||||
private final String namespace;
|
||||
private final String id;
|
||||
private final String fullId;
|
||||
private final Map<String, String> properties;
|
||||
private final Property[] propertiesArray;
|
||||
|
||||
private final boolean isAir, isWater, isWaterlogged;
|
||||
|
||||
public BlockState(String id) {
|
||||
this(id, Collections.emptyMap());
|
||||
public BlockState(String value) {
|
||||
this(value, Collections.emptyMap());
|
||||
}
|
||||
|
||||
public BlockState(String id, Map<String, String> properties) {
|
||||
public BlockState(String value, Map<String, String> properties) {
|
||||
super(value);
|
||||
|
||||
this.hashed = false;
|
||||
this.hash = 0;
|
||||
|
||||
//this.properties = Collections.unmodifiableMap(new HashMap<>(properties)); // <- not doing this to reduce object-creation
|
||||
this.properties = properties;
|
||||
|
||||
//resolve namespace
|
||||
String namespace = "minecraft";
|
||||
int namespaceSeperator = id.indexOf(':');
|
||||
if (namespaceSeperator > 0) {
|
||||
namespace = id.substring(0, namespaceSeperator);
|
||||
id = id.substring(namespaceSeperator + 1);
|
||||
}
|
||||
|
||||
this.id = id;
|
||||
this.namespace = namespace;
|
||||
this.fullId = namespace + ":" + id;
|
||||
this.propertiesArray = properties.entrySet().stream()
|
||||
.map(e -> new Property(e.getKey(), e.getValue()))
|
||||
.sorted()
|
||||
.toArray(Property[]::new);
|
||||
|
||||
// special fast-access properties
|
||||
this.isAir =
|
||||
"minecraft:air".equals(this.fullId) ||
|
||||
"minecraft:cave_air".equals(this.fullId) ||
|
||||
"minecraft:void_air".equals(this.fullId);
|
||||
"minecraft:air".equals(this.getFormatted()) ||
|
||||
"minecraft:cave_air".equals(this.getFormatted()) ||
|
||||
"minecraft:void_air".equals(this.getFormatted());
|
||||
|
||||
this.isWater = "minecraft:water".equals(this.fullId);
|
||||
this.isWater = "minecraft:water".equals(this.getFormatted());
|
||||
this.isWaterlogged = "true".equals(properties.get("waterlogged"));
|
||||
}
|
||||
|
||||
/**
|
||||
* The namespace of this blockstate,<br>
|
||||
* this is always "minecraft" in vanilla.<br>
|
||||
*/
|
||||
public String getNamespace() {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* The id of this blockstate,<br>
|
||||
* also the name of the resource-file without the filetype that represents this block-state <i>(found in mineceraft in assets/minecraft/blockstates)</i>.<br>
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the namespaced id of this blockstate
|
||||
*/
|
||||
public String getFullId() {
|
||||
return fullId;
|
||||
}
|
||||
|
||||
/**
|
||||
* An immutable map of all properties of this block.<br>
|
||||
* <br>
|
||||
@ -142,14 +113,14 @@ public boolean equals(Object obj) {
|
||||
|
||||
if (!(obj instanceof BlockState)) return false;
|
||||
BlockState b = (BlockState) obj;
|
||||
if (!Objects.equals(getFullId(), b.getFullId())) return false;
|
||||
return Objects.equals(getProperties(), b.getProperties());
|
||||
if (!Objects.equals(getFormatted(), b.getFormatted())) return false;
|
||||
return Arrays.equals(propertiesArray, b.propertiesArray);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (!hashed){
|
||||
hash = Objects.hash( getFullId(), getProperties() );
|
||||
hash = Objects.hash( getFormatted(), getProperties() );
|
||||
hashed = true;
|
||||
}
|
||||
|
||||
@ -163,7 +134,7 @@ public String toString() {
|
||||
sj.add(e.getKey() + "=" + e.getValue());
|
||||
}
|
||||
|
||||
return getFullId() + "[" + sj.toString() + "]";
|
||||
return getFormatted() + "[" + sj + "]";
|
||||
}
|
||||
|
||||
public static BlockState fromString(String serializedBlockState) throws IllegalArgumentException {
|
||||
@ -191,4 +162,35 @@ public static BlockState fromString(String serializedBlockState) throws IllegalA
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Property implements Comparable<Property> {
|
||||
private final String key, value;
|
||||
|
||||
public Property(String key, String value) {
|
||||
this.key = key.intern();
|
||||
this.value = value.intern();
|
||||
}
|
||||
|
||||
@SuppressWarnings("StringEquality")
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Property property = (Property) o;
|
||||
return key == property.key && value == property.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return key.hashCode() * 31 ^ value.hashCode();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull BlockState.Property o) {
|
||||
int keyCompare = key.compareTo(o.key);
|
||||
return keyCompare != 0 ? keyCompare : value.compareTo(o.value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,7 +25,7 @@
|
||||
package de.bluecolored.bluemap.core.world;
|
||||
|
||||
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@ -34,7 +34,8 @@ public class ExtendedBlock<T extends ExtendedBlock<T>> extends Block<T> {
|
||||
private final RenderSettings renderSettings;
|
||||
private BlockProperties properties;
|
||||
private Biome biome;
|
||||
private Boolean insideRenderBounds;
|
||||
|
||||
private boolean insideRenderBoundsCalculated, insideRenderBounds;
|
||||
|
||||
public ExtendedBlock(ResourcePack resourcePack, RenderSettings renderSettings, World world, int x, int y, int z) {
|
||||
super(world, x, y, z);
|
||||
@ -48,19 +49,20 @@ protected void reset() {
|
||||
|
||||
this.properties = null;
|
||||
this.biome = null;
|
||||
this.insideRenderBounds = null;
|
||||
|
||||
this.insideRenderBoundsCalculated = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState() {
|
||||
if (!isInsideRenderBounds() && renderSettings.isRenderEdges()) return BlockState.AIR;
|
||||
if (renderSettings.isRenderEdges() && !isInsideRenderBounds()) return BlockState.AIR;
|
||||
return super.getBlockState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LightData getLightData() {
|
||||
LightData ld = super.getLightData();
|
||||
if (!isInsideRenderBounds() && renderSettings.isRenderEdges()) ld.set(getWorld().getSkyLight(), ld.getBlockLight());
|
||||
if (renderSettings.isRenderEdges() && !isInsideRenderBounds()) ld.set(getWorld().getSkyLight(), ld.getBlockLight());
|
||||
return ld;
|
||||
}
|
||||
|
||||
@ -78,8 +80,13 @@ public RenderSettings getRenderSettings() {
|
||||
return renderSettings;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean isInsideRenderBounds() {
|
||||
if (insideRenderBounds == null) insideRenderBounds = renderSettings.isInsideRenderBoundaries(getX(), getY(), getZ());
|
||||
if (!insideRenderBoundsCalculated) {
|
||||
insideRenderBounds = renderSettings.isInsideRenderBoundaries(getX(), getY(), getZ());
|
||||
insideRenderBoundsCalculated = true;
|
||||
}
|
||||
|
||||
return insideRenderBounds;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"variants": {
|
||||
"": { "model": "bluemap:block/missing" }
|
||||
"": { "model": "bluemap:missing" }
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"parent": "block/cube_all",
|
||||
"textures": {
|
||||
"all": "bluemap:block/missing"
|
||||
"all": "bluemap:missing"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
@ -1,470 +1,395 @@
|
||||
{
|
||||
"minecraft:ocean": {
|
||||
"id": 0,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:plains": {
|
||||
"id": 1,
|
||||
"humidity": 0.4,
|
||||
"temp": 0.8,
|
||||
"temperature": 0.8,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:desert": {
|
||||
"id": 2,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:mountains": {
|
||||
"id": 3,
|
||||
"humidity": 0.3,
|
||||
"temp": 0.2,
|
||||
"temperature": 0.2,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:forest": {
|
||||
"id": 4,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.7,
|
||||
"temperature": 0.7,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:taiga": {
|
||||
"id": 5,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.25,
|
||||
"temperature": 0.25,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:swamp": {
|
||||
"id": 6,
|
||||
"humidity": 0.9,
|
||||
"temp": 0.8,
|
||||
"temperature": 0.8,
|
||||
"foliagecolor": "#6A7039",
|
||||
"grasscolor": "#6A7039",
|
||||
"watercolor": 6388580
|
||||
},
|
||||
"minecraft:river": {
|
||||
"id": 7,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:nether": {
|
||||
"id": 8,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:the_end": {
|
||||
"id": 9,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:frozen_ocean": {
|
||||
"id": 10,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.0,
|
||||
"temperature": 0.0,
|
||||
"watercolor": 3750089
|
||||
},
|
||||
"minecraft:frozen_river": {
|
||||
"id": 11,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.0,
|
||||
"temperature": 0.0,
|
||||
"watercolor": 3750089
|
||||
},
|
||||
"minecraft:snowy_tundra": {
|
||||
"id": 12,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.0,
|
||||
"temperature": 0.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:snowy_mountains": {
|
||||
"id": 13,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.0,
|
||||
"temperature": 0.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:mushroom_fields": {
|
||||
"id": 14,
|
||||
"humidity": 1.0,
|
||||
"temp": 0.9,
|
||||
"temperature": 0.9,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:mushroom_field_shore": {
|
||||
"id": 15,
|
||||
"humidity": 1.0,
|
||||
"temp": 0.9,
|
||||
"temperature": 0.9,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:beach": {
|
||||
"id": 16,
|
||||
"humidity": 0.4,
|
||||
"temp": 0.8,
|
||||
"temperature": 0.8,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:desert_hills": {
|
||||
"id": 17,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:wooded_hills": {
|
||||
"id": 18,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.7,
|
||||
"temperature": 0.7,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:taiga_hills": {
|
||||
"id": 19,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.25,
|
||||
"temperature": 0.25,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:mountain_edge": {
|
||||
"id": 20,
|
||||
"humidity": 0.3,
|
||||
"temp": 0.2,
|
||||
"temperature": 0.2,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:jungle": {
|
||||
"id": 21,
|
||||
"humidity": 0.9,
|
||||
"temp": 0.95,
|
||||
"temperature": 0.95,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:jungle_hills": {
|
||||
"id": 22,
|
||||
"humidity": 0.9,
|
||||
"temp": 0.95,
|
||||
"temperature": 0.95,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:jungle_edge": {
|
||||
"id": 23,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.95,
|
||||
"temperature": 0.95,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:deep_ocean": {
|
||||
"id": 24,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:stone_shore": {
|
||||
"id": 25,
|
||||
"humidity": 0.3,
|
||||
"temp": 0.2,
|
||||
"temperature": 0.2,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:snowy_beach": {
|
||||
"id": 26,
|
||||
"humidity": 0.3,
|
||||
"temp": 0.05,
|
||||
"temperature": 0.05,
|
||||
"watercolor": 4020182
|
||||
},
|
||||
"minecraft:birch_forest": {
|
||||
"id": 27,
|
||||
"humidity": 0.6,
|
||||
"temp": 0.6,
|
||||
"temperature": 0.6,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:birch_forest_hills": {
|
||||
"id": 28,
|
||||
"humidity": 0.6,
|
||||
"temp": 0.6,
|
||||
"temperature": 0.6,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:dark_forest": {
|
||||
"id": 29,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.7,
|
||||
"temperature": 0.7,
|
||||
"foliagecolor": "#5528340a",
|
||||
"grasscolor": "#8828340a",
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:snowy_taiga": {
|
||||
"id": 30,
|
||||
"humidity": 0.4,
|
||||
"temp": -0.5,
|
||||
"temperature": -0.5,
|
||||
"watercolor": 4020182
|
||||
},
|
||||
"minecraft:snowy_taiga_hills": {
|
||||
"id": 31,
|
||||
"humidity": 0.4,
|
||||
"temp": -0.5,
|
||||
"temperature": -0.5,
|
||||
"watercolor": 4020182
|
||||
},
|
||||
"minecraft:giant_tree_taiga": {
|
||||
"id": 32,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.3,
|
||||
"temperature": 0.3,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:giant_tree_taiga_hills": {
|
||||
"id": 33,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.3,
|
||||
"temperature": 0.3,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:wooded_mountains": {
|
||||
"id": 34,
|
||||
"humidity": 0.3,
|
||||
"temp": 0.2,
|
||||
"temperature": 0.2,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:savanna": {
|
||||
"id": 35,
|
||||
"humidity": 0.0,
|
||||
"temp": 1.2,
|
||||
"temperature": 1.2,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:savanna_plateau": {
|
||||
"id": 36,
|
||||
"humidity": 0.0,
|
||||
"temp": 1.0,
|
||||
"temperature": 1.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:badlands": {
|
||||
"id": 37,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"foliagecolor": "#9e814d",
|
||||
"grasscolor": "#90814d",
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:wooded_badlands_plateau": {
|
||||
"id": 38,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"foliagecolor": "#9e814d",
|
||||
"grasscolor": "#90814d",
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:badlands_plateau": {
|
||||
"id": 39,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"foliagecolor": "#9e814d",
|
||||
"grasscolor": "#90814d",
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:small_end_islands": {
|
||||
"id": 40,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:end_midlands": {
|
||||
"id": 41,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:end_highlands": {
|
||||
"id": 42,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:end_barrens": {
|
||||
"id": 43,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:warm_ocean": {
|
||||
"id": 44,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4445678
|
||||
},
|
||||
"minecraft:lukewarm_ocean": {
|
||||
"id": 45,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4566514
|
||||
},
|
||||
"minecraft:cold_ocean": {
|
||||
"id": 46,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4020182
|
||||
},
|
||||
"minecraft:deep_warm_ocean": {
|
||||
"id": 47,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4445678
|
||||
},
|
||||
"minecraft:deep_lukewarm_ocean": {
|
||||
"id": 48,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4566514
|
||||
},
|
||||
"minecraft:deep_cold_ocean": {
|
||||
"id": 49,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4020182
|
||||
},
|
||||
"minecraft:deep_frozen_ocean": {
|
||||
"id": 50,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 3750089
|
||||
},
|
||||
"minecraft:the_void": {
|
||||
"id": 127,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:sunflower_plains": {
|
||||
"id": 129,
|
||||
"humidity": 0.4,
|
||||
"temp": 0.8,
|
||||
"temperature": 0.8,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:desert_lakes": {
|
||||
"id": 130,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:gravelly_mountains": {
|
||||
"id": 131,
|
||||
"humidity": 0.3,
|
||||
"temp": 0.2,
|
||||
"temperature": 0.2,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:flower_forest": {
|
||||
"id": 132,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.7,
|
||||
"temperature": 0.7,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:taiga_mountains": {
|
||||
"id": 133,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.25,
|
||||
"temperature": 0.25,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:swamp_hills": {
|
||||
"id": 134,
|
||||
"humidity": 0.9,
|
||||
"temp": 0.8,
|
||||
"temperature": 0.8,
|
||||
"foliagecolor": "#6A7039",
|
||||
"grasscolor": "#6A7039",
|
||||
"watercolor": 6388580
|
||||
},
|
||||
"minecraft:ice_spikes": {
|
||||
"id": 140,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.0,
|
||||
"temperature": 0.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:modified_jungle": {
|
||||
"id": 149,
|
||||
"humidity": 0.9,
|
||||
"temp": 0.95,
|
||||
"temperature": 0.95,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:modified_jungle_edge": {
|
||||
"id": 151,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.95,
|
||||
"temperature": 0.95,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:tall_birch_forest": {
|
||||
"id": 155,
|
||||
"humidity": 0.6,
|
||||
"temp": 0.6,
|
||||
"temperature": 0.6,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:tall_birch_hills": {
|
||||
"id": 156,
|
||||
"humidity": 0.6,
|
||||
"temp": 0.6,
|
||||
"temperature": 0.6,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:dark_forest_hills": {
|
||||
"id": 157,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.7,
|
||||
"temperature": 0.7,
|
||||
"foliagecolor": "#5528340a",
|
||||
"grasscolor": "#8828340a",
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:snowy_taiga_mountains": {
|
||||
"id": 158,
|
||||
"humidity": 0.4,
|
||||
"temp": -0.5,
|
||||
"temperature": -0.5,
|
||||
"watercolor": 4020182
|
||||
},
|
||||
"minecraft:giant_spruce_taiga": {
|
||||
"id": 160,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.25,
|
||||
"temperature": 0.25,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:giant_spruce_taiga_hills": {
|
||||
"id": 161,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.25,
|
||||
"temperature": 0.25,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:modified_gravelly_mountains": {
|
||||
"id": 162,
|
||||
"humidity": 0.3,
|
||||
"temp": 0.2,
|
||||
"temperature": 0.2,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:shattered_savanna": {
|
||||
"id": 163,
|
||||
"humidity": 0.0,
|
||||
"temp": 1.1,
|
||||
"temperature": 1.1,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:shattered_savanna_plateau": {
|
||||
"id": 164,
|
||||
"humidity": 0.0,
|
||||
"temp": 1.0,
|
||||
"temperature": 1.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:eroded_badlands": {
|
||||
"id": 165,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"foliagecolor": "#9e814d",
|
||||
"grasscolor": "#90814d",
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:modified_wooded_badlands_plateau": {
|
||||
"id": 166,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:modified_badlands_plateau": {
|
||||
"id": 167,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"foliagecolor": "#9e814d",
|
||||
"grasscolor": "#90814d",
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:bamboo_jungle": {
|
||||
"id": 168,
|
||||
"humidity": 0.9,
|
||||
"temp": 0.95,
|
||||
"temperature": 0.95,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:bamboo_jungle_hills": {
|
||||
"id": 169,
|
||||
"humidity": 0.9,
|
||||
"temp": 0.95,
|
||||
"temperature": 0.95,
|
||||
"watercolor": 4159204
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"parent": "builtin/liquid",
|
||||
"parent": "bluemap:builtin/liquid",
|
||||
"textures": {
|
||||
"particle": "block/lava_still",
|
||||
"still": "block/lava_still",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"parent": "builtin/liquid",
|
||||
"parent": "bluemap:builtin/liquid",
|
||||
"textures": {
|
||||
"particle": "block/water_still",
|
||||
"still": "block/water_still",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"variants": {
|
||||
"": { "model": "bluemap:block/missing" }
|
||||
"": { "model": "bluemap:missing" }
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"parent": "block/cube_all",
|
||||
"textures": {
|
||||
"all": "bluemap:block/missing"
|
||||
"all": "bluemap:missing"
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
@ -1,470 +1,395 @@
|
||||
{
|
||||
"minecraft:ocean": {
|
||||
"id": 0,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:plains": {
|
||||
"id": 1,
|
||||
"humidity": 0.4,
|
||||
"temp": 0.8,
|
||||
"temperature": 0.8,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:desert": {
|
||||
"id": 2,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:mountains": {
|
||||
"id": 3,
|
||||
"humidity": 0.3,
|
||||
"temp": 0.2,
|
||||
"temperature": 0.2,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:forest": {
|
||||
"id": 4,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.7,
|
||||
"temperature": 0.7,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:taiga": {
|
||||
"id": 5,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.25,
|
||||
"temperature": 0.25,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:swamp": {
|
||||
"id": 6,
|
||||
"humidity": 0.9,
|
||||
"temp": 0.8,
|
||||
"temperature": 0.8,
|
||||
"foliagecolor": "#6A7039",
|
||||
"grasscolor": "#6A7039",
|
||||
"watercolor": 6388580
|
||||
},
|
||||
"minecraft:river": {
|
||||
"id": 7,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:nether": {
|
||||
"id": 8,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:the_end": {
|
||||
"id": 9,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:frozen_ocean": {
|
||||
"id": 10,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.0,
|
||||
"temperature": 0.0,
|
||||
"watercolor": 3750089
|
||||
},
|
||||
"minecraft:frozen_river": {
|
||||
"id": 11,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.0,
|
||||
"temperature": 0.0,
|
||||
"watercolor": 3750089
|
||||
},
|
||||
"minecraft:snowy_tundra": {
|
||||
"id": 12,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.0,
|
||||
"temperature": 0.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:snowy_mountains": {
|
||||
"id": 13,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.0,
|
||||
"temperature": 0.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:mushroom_fields": {
|
||||
"id": 14,
|
||||
"humidity": 1.0,
|
||||
"temp": 0.9,
|
||||
"temperature": 0.9,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:mushroom_field_shore": {
|
||||
"id": 15,
|
||||
"humidity": 1.0,
|
||||
"temp": 0.9,
|
||||
"temperature": 0.9,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:beach": {
|
||||
"id": 16,
|
||||
"humidity": 0.4,
|
||||
"temp": 0.8,
|
||||
"temperature": 0.8,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:desert_hills": {
|
||||
"id": 17,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:wooded_hills": {
|
||||
"id": 18,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.7,
|
||||
"temperature": 0.7,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:taiga_hills": {
|
||||
"id": 19,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.25,
|
||||
"temperature": 0.25,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:mountain_edge": {
|
||||
"id": 20,
|
||||
"humidity": 0.3,
|
||||
"temp": 0.2,
|
||||
"temperature": 0.2,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:jungle": {
|
||||
"id": 21,
|
||||
"humidity": 0.9,
|
||||
"temp": 0.95,
|
||||
"temperature": 0.95,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:jungle_hills": {
|
||||
"id": 22,
|
||||
"humidity": 0.9,
|
||||
"temp": 0.95,
|
||||
"temperature": 0.95,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:jungle_edge": {
|
||||
"id": 23,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.95,
|
||||
"temperature": 0.95,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:deep_ocean": {
|
||||
"id": 24,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:stone_shore": {
|
||||
"id": 25,
|
||||
"humidity": 0.3,
|
||||
"temp": 0.2,
|
||||
"temperature": 0.2,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:snowy_beach": {
|
||||
"id": 26,
|
||||
"humidity": 0.3,
|
||||
"temp": 0.05,
|
||||
"temperature": 0.05,
|
||||
"watercolor": 4020182
|
||||
},
|
||||
"minecraft:birch_forest": {
|
||||
"id": 27,
|
||||
"humidity": 0.6,
|
||||
"temp": 0.6,
|
||||
"temperature": 0.6,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:birch_forest_hills": {
|
||||
"id": 28,
|
||||
"humidity": 0.6,
|
||||
"temp": 0.6,
|
||||
"temperature": 0.6,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:dark_forest": {
|
||||
"id": 29,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.7,
|
||||
"temperature": 0.7,
|
||||
"foliagecolor": "#5528340a",
|
||||
"grasscolor": "#8828340a",
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:snowy_taiga": {
|
||||
"id": 30,
|
||||
"humidity": 0.4,
|
||||
"temp": -0.5,
|
||||
"temperature": -0.5,
|
||||
"watercolor": 4020182
|
||||
},
|
||||
"minecraft:snowy_taiga_hills": {
|
||||
"id": 31,
|
||||
"humidity": 0.4,
|
||||
"temp": -0.5,
|
||||
"temperature": -0.5,
|
||||
"watercolor": 4020182
|
||||
},
|
||||
"minecraft:giant_tree_taiga": {
|
||||
"id": 32,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.3,
|
||||
"temperature": 0.3,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:giant_tree_taiga_hills": {
|
||||
"id": 33,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.3,
|
||||
"temperature": 0.3,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:wooded_mountains": {
|
||||
"id": 34,
|
||||
"humidity": 0.3,
|
||||
"temp": 0.2,
|
||||
"temperature": 0.2,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:savanna": {
|
||||
"id": 35,
|
||||
"humidity": 0.0,
|
||||
"temp": 1.2,
|
||||
"temperature": 1.2,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:savanna_plateau": {
|
||||
"id": 36,
|
||||
"humidity": 0.0,
|
||||
"temp": 1.0,
|
||||
"temperature": 1.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:badlands": {
|
||||
"id": 37,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"foliagecolor": "#9e814d",
|
||||
"grasscolor": "#90814d",
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:wooded_badlands_plateau": {
|
||||
"id": 38,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"foliagecolor": "#9e814d",
|
||||
"grasscolor": "#90814d",
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:badlands_plateau": {
|
||||
"id": 39,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"foliagecolor": "#9e814d",
|
||||
"grasscolor": "#90814d",
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:small_end_islands": {
|
||||
"id": 40,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:end_midlands": {
|
||||
"id": 41,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:end_highlands": {
|
||||
"id": 42,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:end_barrens": {
|
||||
"id": 43,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:warm_ocean": {
|
||||
"id": 44,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4445678
|
||||
},
|
||||
"minecraft:lukewarm_ocean": {
|
||||
"id": 45,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4566514
|
||||
},
|
||||
"minecraft:cold_ocean": {
|
||||
"id": 46,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4020182
|
||||
},
|
||||
"minecraft:deep_warm_ocean": {
|
||||
"id": 47,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4445678
|
||||
},
|
||||
"minecraft:deep_lukewarm_ocean": {
|
||||
"id": 48,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4566514
|
||||
},
|
||||
"minecraft:deep_cold_ocean": {
|
||||
"id": 49,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4020182
|
||||
},
|
||||
"minecraft:deep_frozen_ocean": {
|
||||
"id": 50,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 3750089
|
||||
},
|
||||
"minecraft:the_void": {
|
||||
"id": 127,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.5,
|
||||
"temperature": 0.5,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:sunflower_plains": {
|
||||
"id": 129,
|
||||
"humidity": 0.4,
|
||||
"temp": 0.8,
|
||||
"temperature": 0.8,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:desert_lakes": {
|
||||
"id": 130,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:gravelly_mountains": {
|
||||
"id": 131,
|
||||
"humidity": 0.3,
|
||||
"temp": 0.2,
|
||||
"temperature": 0.2,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:flower_forest": {
|
||||
"id": 132,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.7,
|
||||
"temperature": 0.7,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:taiga_mountains": {
|
||||
"id": 133,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.25,
|
||||
"temperature": 0.25,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:swamp_hills": {
|
||||
"id": 134,
|
||||
"humidity": 0.9,
|
||||
"temp": 0.8,
|
||||
"temperature": 0.8,
|
||||
"foliagecolor": "#6A7039",
|
||||
"grasscolor": "#6A7039",
|
||||
"watercolor": 6388580
|
||||
},
|
||||
"minecraft:ice_spikes": {
|
||||
"id": 140,
|
||||
"humidity": 0.5,
|
||||
"temp": 0.0,
|
||||
"temperature": 0.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:modified_jungle": {
|
||||
"id": 149,
|
||||
"humidity": 0.9,
|
||||
"temp": 0.95,
|
||||
"temperature": 0.95,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:modified_jungle_edge": {
|
||||
"id": 151,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.95,
|
||||
"temperature": 0.95,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:tall_birch_forest": {
|
||||
"id": 155,
|
||||
"humidity": 0.6,
|
||||
"temp": 0.6,
|
||||
"temperature": 0.6,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:tall_birch_hills": {
|
||||
"id": 156,
|
||||
"humidity": 0.6,
|
||||
"temp": 0.6,
|
||||
"temperature": 0.6,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:dark_forest_hills": {
|
||||
"id": 157,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.7,
|
||||
"temperature": 0.7,
|
||||
"foliagecolor": "#5528340a",
|
||||
"grasscolor": "#8828340a",
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:snowy_taiga_mountains": {
|
||||
"id": 158,
|
||||
"humidity": 0.4,
|
||||
"temp": -0.5,
|
||||
"temperature": -0.5,
|
||||
"watercolor": 4020182
|
||||
},
|
||||
"minecraft:giant_spruce_taiga": {
|
||||
"id": 160,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.25,
|
||||
"temperature": 0.25,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:giant_spruce_taiga_hills": {
|
||||
"id": 161,
|
||||
"humidity": 0.8,
|
||||
"temp": 0.25,
|
||||
"temperature": 0.25,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:modified_gravelly_mountains": {
|
||||
"id": 162,
|
||||
"humidity": 0.3,
|
||||
"temp": 0.2,
|
||||
"temperature": 0.2,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:shattered_savanna": {
|
||||
"id": 163,
|
||||
"humidity": 0.0,
|
||||
"temp": 1.1,
|
||||
"temperature": 1.1,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:shattered_savanna_plateau": {
|
||||
"id": 164,
|
||||
"humidity": 0.0,
|
||||
"temp": 1.0,
|
||||
"temperature": 1.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:eroded_badlands": {
|
||||
"id": 165,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"foliagecolor": "#9e814d",
|
||||
"grasscolor": "#90814d",
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:modified_wooded_badlands_plateau": {
|
||||
"id": 166,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:modified_badlands_plateau": {
|
||||
"id": 167,
|
||||
"humidity": 0.0,
|
||||
"temp": 2.0,
|
||||
"temperature": 2.0,
|
||||
"foliagecolor": "#9e814d",
|
||||
"grasscolor": "#90814d",
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:bamboo_jungle": {
|
||||
"id": 168,
|
||||
"humidity": 0.9,
|
||||
"temp": 0.95,
|
||||
"temperature": 0.95,
|
||||
"watercolor": 4159204
|
||||
},
|
||||
"minecraft:bamboo_jungle_hills": {
|
||||
"id": 169,
|
||||
"humidity": 0.9,
|
||||
"temp": 0.95,
|
||||
"temperature": 0.95,
|
||||
"watercolor": 4159204
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"parent": "builtin/liquid",
|
||||
"parent": "bluemap:builtin/liquid",
|
||||
"textures": {
|
||||
"particle": "block/lava_still",
|
||||
"still": "block/lava_still",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"parent": "builtin/liquid",
|
||||
"parent": "bluemap:builtin/liquid",
|
||||
"textures": {
|
||||
"particle": "block/water_still",
|
||||
"still": "block/water_still",
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"variants": {
|
||||
"": { "model": "bluemap:block/missing" }
|
||||
"": { "model": "bluemap:missing" }
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user