Completely rework ResourcePack and resource-loading

This commit is contained in:
Lukas Rieger (Blue) 2022-05-28 21:55:41 +02:00
parent 4e7deb2562
commit 7389cb1a16
No known key found for this signature in database
GPG Key ID: 2D09EC5ED2687FF2
125 changed files with 3069 additions and 3932 deletions

@ -1 +1 @@
Subproject commit 13f60e6ee0ba39759d662bba5ae750a5918b564b
Subproject commit 0776e69c88ed24e2d2f0141e616d0512a5482ac9

View File

@ -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")
}

View File

@ -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);
}

View File

@ -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 {

View File

@ -13,7 +13,6 @@
@DebugDump
@ConfigSerializable
public class MapConfig implements MapSettings {
private transient Path configFile = null;
private String name = null;

View File

@ -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;

View File

@ -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();
}

View File

@ -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) {

View File

@ -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)

View File

@ -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")
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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());
}

View File

@ -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;
}

View File

@ -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]);
}

View File

@ -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]);
}

View File

@ -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;

View File

@ -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];

View File

@ -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();
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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, '/');
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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,

View File

@ -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();
}

View File

@ -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;

View File

@ -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());
}
};
}
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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));
}
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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]));
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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");

View File

@ -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){

View File

@ -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());

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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 +

View File

@ -173,7 +173,7 @@ public String toString() {
'}';
} else {
return "Block{" +
"world=" + world +
"world=null" +
", x=" + x +
", y=" + y +
", z=" + z +

View File

@ -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> {

View File

@ -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);
}
}
}

View File

@ -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;
}

View File

@ -1,5 +1,5 @@
{
"variants": {
"": { "model": "bluemap:block/missing" }
"": { "model": "bluemap:missing" }
}
}

View File

@ -1,6 +1,6 @@
{
"parent": "block/cube_all",
"textures": {
"all": "bluemap:block/missing"
"all": "bluemap:missing"
}
}

View File

@ -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
}
}

View File

@ -1,5 +1,5 @@
{
"parent": "builtin/liquid",
"parent": "bluemap:builtin/liquid",
"textures": {
"particle": "block/lava_still",
"still": "block/lava_still",

View File

@ -1,5 +1,5 @@
{
"parent": "builtin/liquid",
"parent": "bluemap:builtin/liquid",
"textures": {
"particle": "block/water_still",
"still": "block/water_still",

View File

@ -1,5 +1,5 @@
{
"variants": {
"": { "model": "bluemap:block/missing" }
"": { "model": "bluemap:missing" }
}
}

View File

@ -1,6 +1,6 @@
{
"parent": "block/cube_all",
"textures": {
"all": "bluemap:block/missing"
"all": "bluemap:missing"
}
}

View File

@ -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
}
}

View File

@ -1,5 +1,5 @@
{
"parent": "builtin/liquid",
"parent": "bluemap:builtin/liquid",
"textures": {
"particle": "block/lava_still",
"still": "block/lava_still",

View File

@ -1,5 +1,5 @@
{
"parent": "builtin/liquid",
"parent": "bluemap:builtin/liquid",
"textures": {
"particle": "block/water_still",
"still": "block/water_still",

View File

@ -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