Add bluemap:missing block and basic unit tests.

The bluemap:missing block will be used as fallback if bluemap cant render another block for some reason.
The unit tests are just the setup, they will need to catch up over time ^^
This commit is contained in:
Blue (Lukas Rieger) 2020-01-05 18:52:30 +01:00
parent d20e0843ab
commit bfdf7fa7ce
18 changed files with 220 additions and 38 deletions

View File

@ -11,6 +11,8 @@ jobs:
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Test with Gradle
run: ./gradlew test
- name: Build with Gradle
run: ./gradlew shadowJar
- uses: actions/upload-artifact@v1

View File

@ -90,7 +90,8 @@ public class BlueMapCLI {
config.getWebDataPath().toFile().mkdirs();
Map<String, MapType> maps = new HashMap<>();
configManager.getBlockPropertiesConfig().setResourcePack(resourcePack);
for (MapConfig mapConfig : config.getMapConfigs()) {
File mapPath = new File(mapConfig.getWorldPath());
if (!mapPath.exists() || !mapPath.isDirectory()) {

View File

@ -134,7 +134,9 @@ public class RenderTask {
long ert = (long)((time / pct) * (1d - pct));
String ertDurationString = DurationFormatUtils.formatDurationWords(ert, true, true);
Logger.global.logInfo("Rendered " + renderedTiles + " of " + tileCount + " tiles in " + durationString);
double tps = renderedTiles / (time / 1000.0);
Logger.global.logInfo("Rendered " + renderedTiles + " of " + tileCount + " tiles in " + durationString + " | " + GenericMath.round(tps, 3) + " tiles/s");
Logger.global.logInfo(GenericMath.round(pct * 100, 3) + "% | Estimated remaining time: " + ertDurationString);
}

View File

@ -8,6 +8,8 @@ dependencies {
compile 'ninja.leaping.configurate:configurate-gson:3.3'
compile 'ninja.leaping.configurate:configurate-yaml:3.3'
compile 'com.github.Querz:NBT:4.0'
testCompile 'junit:junit:4.12'
}
task zipWebroot(type: Zip) {

View File

@ -84,7 +84,7 @@ public class BlockIdConfig implements BlockIdMapper {
BlockState state = mappings.get(idmeta);
if (state == null) {
state = mappings.getOrDefault(new BlockIDMeta(id, 0), BlockState.AIR); //meta-fallback
state = mappings.getOrDefault(new BlockIDMeta(id, 0), BlockState.MISSING); //meta-fallback
if (autopoulationConfigLoader != null) {
mappings.put(idmeta, state);

View File

@ -31,11 +31,14 @@ import java.util.concurrent.ExecutionException;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.mca.mapping.BlockPropertiesMapper;
import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException;
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.resourcepack.TransformedBlockModelResource;
import de.bluecolored.bluemap.core.world.BlockProperties;
import de.bluecolored.bluemap.core.world.BlockState;
import ninja.leaping.configurate.ConfigurationNode;
@ -48,6 +51,8 @@ public class BlockPropertiesConfig implements BlockPropertiesMapper {
private Multimap<String, BlockStateMapping<BlockProperties>> mappings;
private LoadingCache<BlockState, BlockProperties> mappingCache;
private ResourcePack resourcePack = null;
public BlockPropertiesConfig(ConfigurationNode node) throws IOException {
this(node, null);
}
@ -55,7 +60,7 @@ public class BlockPropertiesConfig implements BlockPropertiesMapper {
public BlockPropertiesConfig(ConfigurationNode node, ConfigurationLoader<? extends ConfigurationNode> autopoulationConfigLoader) throws IOException {
this.autopoulationConfigLoader = autopoulationConfigLoader;
mappings = HashMultimap.create();
mappings = MultimapBuilder.hashKeys().arrayListValues().build();
for (Entry<Object, ? extends ConfigurationNode> e : node.getChildrenMap().entrySet()){
String key = e.getKey().toString();
@ -81,6 +86,14 @@ public class BlockPropertiesConfig implements BlockPropertiesMapper {
});
}
/**
* Sets the {@link ResourcePack} of this PropertyMapper, so it can generate better defaults if the mapping is missing
* @param resourcePack the {@link ResourcePack}
*/
public void setResourcePack(ResourcePack resourcePack) {
this.resourcePack = resourcePack;
}
@Override
public BlockProperties get(BlockState from){
try {
@ -98,16 +111,32 @@ public class BlockPropertiesConfig implements BlockPropertiesMapper {
}
}
BlockProperties generated = BlockProperties.DEFAULT;
if (resourcePack != null) {
try {
boolean culling = false;
boolean occluding = false;
for(TransformedBlockModelResource model : resourcePack.getBlockStateResource(bs).getModels(bs)) {
culling = culling || model.getModel().isCulling();
occluding = occluding || model.getModel().isOccluding();
if (culling && occluding) break;
}
generated = new BlockProperties(culling, occluding, generated.isFlammable());
} catch (NoSuchResourceException ignore) {} //ignoring this because it will be logged later again if we try to render that block
}
mappings.put(bs.getFullId(), new BlockStateMapping<BlockProperties>(new BlockState(bs.getFullId()), generated));
if (autopoulationConfigLoader != null) {
mappings.put(bs.getFullId(), new BlockStateMapping<BlockProperties>(new BlockState(bs.getFullId()), BlockProperties.DEFAULT));
synchronized (autopoulationConfigLoader) {
try {
ConfigurationNode node = autopoulationConfigLoader.load();
ConfigurationNode bpNode = node.getNode(bs.getFullId());
bpNode.getNode("culling").setValue(false);
bpNode.getNode("occluding").setValue(false);
bpNode.getNode("flammable").setValue(false);
bpNode.getNode("culling").setValue(generated.isCulling());
bpNode.getNode("occluding").setValue(generated.isOccluding());
bpNode.getNode("flammable").setValue(generated.isFlammable());
autopoulationConfigLoader.save(node);
} catch (IOException ex) {
Logger.global.noFloodError("blockpropsconf-autopopulate-ioex", "Failed to auto-populate BlockPropertiesConfig!", ex);
@ -115,7 +144,7 @@ public class BlockPropertiesConfig implements BlockPropertiesMapper {
}
}
return BlockProperties.DEFAULT;
return generated;
}
}

View File

@ -188,7 +188,7 @@ class ChunkAnvil113 extends Chunk {
if (value >= palette.length) {
Logger.global.noFloodWarning("palettewarning", "Got palette value " + value + " but palette has size of " + palette.length + " (Future occasions of this error will not be logged)");
return BlockState.AIR;
return BlockState.MISSING;
}
return palette[(int) value];

View File

@ -139,7 +139,7 @@ public class MCAWorld implements World {
return chunk.getBlockState(pos);
} catch (Exception ex) {
return BlockState.AIR;
return BlockState.MISSING;
}
}

View File

@ -31,6 +31,7 @@ import com.flowpowered.math.vector.Vector4f;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.render.RenderSettings;
import de.bluecolored.bluemap.core.render.WorldTile;
import de.bluecolored.bluemap.core.render.context.ExtendedBlockContext;
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;
@ -39,6 +40,7 @@ import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.util.AABB;
import de.bluecolored.bluemap.core.util.MathUtils;
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.WorldChunk;
@ -79,11 +81,19 @@ public class HiresModelRenderer {
maxHeight = y;
ExtendedBlockContext context = new SlicedWorldChunkBlockContext(chunk, new Vector3i(x, y, z), renderSettings.getSliceY());
BlockStateModel blockModel;
try {
blockModel = modelFactory.createFrom(block.getBlock(), new SlicedWorldChunkBlockContext(chunk, new Vector3i(x, y, z), renderSettings.getSliceY()), renderSettings);
blockModel = modelFactory.createFrom(block.getBlock(), context, renderSettings);
} catch (NoSuchResourceException e) {
blockModel = new BlockStateModel();
try {
blockModel = modelFactory.createFrom(BlockState.MISSING, context, renderSettings);
} catch (NoSuchResourceException e2) {
e.addSuppressed(e2);
blockModel = new BlockStateModel();
}
Logger.global.noFloodDebug(block.getBlock().getFullId() + "-hiresModelRenderer-blockmodelerr", "Failed to create BlockModel for BlockState: " + block.getBlock() + " (" + e.toString() + ")");
}

View File

@ -54,6 +54,9 @@ public class BlockModelResource {
private ModelType modelType = ModelType.NORMAL;
private boolean culling = false;
private boolean occluding = false;
private boolean ambientOcclusion = true;
private Collection<Element> elements = new ArrayList<>();
private Map<String, Texture> textures = new HashMap<>();
@ -67,6 +70,14 @@ public class BlockModelResource {
public boolean isAmbientOcclusion() {
return ambientOcclusion;
}
public boolean isCulling() {
return culling;
}
public boolean isOccluding() {
return occluding;
}
public Collection<Element> getElements() {
return elements;
@ -82,7 +93,8 @@ public class BlockModelResource {
private Rotation rotation = new Rotation();
private boolean shade = true;
private EnumMap<Direction, Face> faces = new EnumMap<>(Direction.class);
private boolean fullCube = false;
private Element() {}
public Vector4f getDefaultUV(Direction face) {
@ -137,6 +149,10 @@ public class BlockModelResource {
public boolean isShade() {
return shade;
}
public boolean isFullCube() {
return fullCube;
}
public EnumMap<Direction, Face> getFaces() {
return faces;
@ -216,6 +232,8 @@ public class BlockModelResource {
public static class Builder {
private static final String JSON_COMMENT = "__comment";
private static final Vector3f FULL_CUBE_FROM = Vector3f.ZERO;
private static final Vector3f FULL_CUBE_TO = new Vector3f(16f, 16f, 16f);
private FileAccess sourcesAccess;
private ResourcePack resourcePack;
@ -288,6 +306,35 @@ public class BlockModelResource {
}
}
//check block properties
for (Element element : blockModel.elements) {
if (element.isFullCube()) {
blockModel.occluding = true;
blockModel.culling = true;
for (Direction dir : Direction.values()) {
Face face = element.faces.get(dir);
if (face == null) {
blockModel.culling = false;
break;
}
Texture texture = face.getTexture();
if (texture == null) {
blockModel.culling = false;
break;
}
if (texture.getColor().getW() < 1) {
blockModel.culling = false;
break;
}
}
break;
}
}
return blockModel;
}
@ -297,7 +344,9 @@ public class BlockModelResource {
element.from = readVector3f(node.getNode("from"));
element.to = readVector3f(node.getNode("to"));
element.shade = node.getNode("shade").getBoolean(false);
element.shade = node.getNode("shade").getBoolean(true);
boolean fullElement = element.from.equals(FULL_CUBE_FROM) && element.to.equals(FULL_CUBE_TO);
if (!node.getNode("rotation").isVirtual()) {
element.rotation.angle = node.getNode("rotation", "angle").getFloat(0);
@ -306,6 +355,7 @@ public class BlockModelResource {
element.rotation.rescale = node.getNode("rotation", "rescale").getBoolean(false);
}
boolean allDirs = true;
for (Direction direction : Direction.values()) {
ConfigurationNode faceNode = node.getNode("faces", direction.name().toLowerCase());
if (!faceNode.isVirtual()) {
@ -315,9 +365,13 @@ public class BlockModelResource {
} catch (ParseResourceException | IOException ex) {
Logger.global.logDebug("Failed to parse an " + direction + " face for the model " + topModelPath + "! " + ex);
}
} else {
allDirs = false;
}
}
if (fullElement && allDirs) element.fullCube = true;
return element;
}

View File

@ -26,7 +26,7 @@ package de.bluecolored.bluemap.core.world;
public class BlockProperties {
public static final BlockProperties DEFAULT = new BlockProperties(false, false, false);
public static final BlockProperties DEFAULT = new BlockProperties(true, true, false);
private final boolean culling, occluding, flammable;

View File

@ -42,9 +42,10 @@ import java.util.StringJoiner;
*/
public class BlockState {
private static Pattern BLOCKSTATE_SERIALIZATION_PATTERN = Pattern.compile("^(.+?)(?:\\[(.+)\\])?$");
private static Pattern BLOCKSTATE_SERIALIZATION_PATTERN = Pattern.compile("^(.+?)(?:\\[(.*)\\])?$");
public static final BlockState AIR = new BlockState("minecraft:air", Collections.emptyMap());
public static final BlockState MISSING = new BlockState("bluemap:missing", Collections.emptyMap());
private boolean hashed;
private int hash;
@ -133,24 +134,6 @@ public class BlockState {
return new BlockState(this, property, value);
}
public final boolean checkVariantCondition(String condition){
if (condition.isEmpty() || condition.equals("normal")) return true;
Map<String, String> blockProperties = getProperties();
String[] conditions = condition.split(",");
for (String c : conditions){
String[] kv = c.split("=", 2);
String key = kv[0];
String value = kv[1];
if (!value.equals(blockProperties.get(key))){
return false;
}
}
return true;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof BlockState)) return false;
@ -187,7 +170,7 @@ public class BlockState {
Map<String, String> pt = new HashMap<>();
String g2 = m.group(2);
if (g2 != null){
if (g2 != null && !g2.isEmpty()){
String[] propertyStrings = g2.trim().split(",");
for (String s : propertyStrings){
String[] kv = s.split("=", 2);

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,86 @@
/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class BlockStateTest {
@Test
public void testIdNamespace() {
BlockState blockState = new BlockState("someblock");
assertEquals("minecraft:someblock", blockState.getFullId());
assertEquals("minecraft", blockState.getNamespace());
assertEquals("someblock", blockState.getId());
blockState = new BlockState("somemod:someblock");
assertEquals("somemod:someblock", blockState.getFullId());
assertEquals("somemod", blockState.getNamespace());
assertEquals("someblock", blockState.getId());
}
@Test
public void testToString() {
BlockState blockState = new BlockState("someblock");
assertEquals("minecraft:someblock[]", blockState.toString());
blockState = blockState.with("testProp", "testVal");
assertEquals("minecraft:someblock[testProp=testVal]", blockState.toString());
blockState = blockState.with("testProp2", "testVal2");
String toString = blockState.toString();
assertTrue(
toString.equals("minecraft:someblock[testProp=testVal,testProp2=testVal2]") ||
toString.equals("minecraft:someblock[testProp2=testVal2,testProp=testVal]")
);
}
@Test
public void testFromString() {
BlockState blockState = BlockState.fromString("somemod:someblock");
assertEquals("somemod:someblock", blockState.getFullId());
assertEquals("somemod", blockState.getNamespace());
assertEquals("someblock", blockState.getId());
assertTrue(blockState.getProperties().isEmpty());
blockState = BlockState.fromString("somemod:someblock[]");
assertEquals("somemod:someblock", blockState.getFullId());
assertEquals("somemod", blockState.getNamespace());
assertEquals("someblock", blockState.getId());
assertTrue(blockState.getProperties().isEmpty());
blockState = BlockState.fromString("somemod:someblock[testProp=testVal,testProp2=testVal2]");
assertEquals("somemod:someblock", blockState.getFullId());
assertEquals("somemod", blockState.getNamespace());
assertEquals("someblock", blockState.getId());
assertEquals("testVal", blockState.getProperties().get("testProp"));
assertEquals("testVal2", blockState.getProperties().get("testProp2"));
}
}

View File

@ -179,6 +179,8 @@ public class SpongePlugin {
resourcePack.loadBlockColorConfig(blockColorsConfigFile);
resourcePack.saveTextureFile(textureExportFile);
configManager.getBlockPropertiesConfig().setResourcePack(resourcePack);
//load maps
for (MapConfig mapConfig : config.getMapConfigs()) {
String id = mapConfig.getId();