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