Finalize new lowres tiles implementation

This commit is contained in:
Lukas Rieger (Blue) 2022-08-12 18:11:11 +02:00
parent 18d71cb74d
commit 562deb17a3
No known key found for this signature in database
GPG Key ID: 2D09EC5ED2687FF2
16 changed files with 130 additions and 49 deletions

@ -1 +1 @@
Subproject commit 9b137de8bd2e56c8ae6332793bfd4131f53cc510
Subproject commit ad82eb61d20c83b1b286c8226e905b296afe72b6

View File

@ -133,6 +133,9 @@ private static class Settings {
private float resolutionDefault = 1;
private int minZoomDistance = 5;
private int maxZoomDistance = 100000;
private int hiresSliderMax = 500;
private int hiresSliderDefault = 200;
private int hiresSliderMin = 50;
@ -148,6 +151,9 @@ public void setFrom(WebappConfig config) {
this.enableFreeFlight = config.isEnableFreeFlight();
this.startLocation = config.getStartLocation().orElse(null);
this.resolutionDefault = config.getResolutionDefault();
this.minZoomDistance = config.getMinZoomDistance();
this.maxZoomDistance = config.getMaxZoomDistance();
this.hiresSliderMax = config.getHiresSliderMax();
this.hiresSliderDefault = config.getHiresSliderDefault();

View File

@ -54,8 +54,9 @@ public class MapConfig implements MapSettings {
// hidden config fields
private int hiresTileSize = 32;
private int lowresPointsPerHiresTile = 4;
private int lowresPointsPerLowresTile = 50;
private int lowresTileSize = 500;
private int lodCount = 3;
private int lodFactor = 5;
@Nullable
public String getName() {
@ -126,12 +127,16 @@ public int getHiresTileSize() {
return hiresTileSize;
}
public int getLowresPointsPerHiresTile() {
return lowresPointsPerHiresTile;
public int getLowresTileSize() {
return lowresTileSize;
}
public int getLowresPointsPerLowresTile() {
return lowresPointsPerLowresTile;
public int getLodCount() {
return lodCount;
}
public int getLodFactor() {
return lodFactor;
}
}

View File

@ -24,6 +24,9 @@ public class WebappConfig {
private float resolutionDefault = 1;
private int minZoomDistance = 5;
private int maxZoomDistance = 100000;
private int hiresSliderMax = 500;
private int hiresSliderDefault = 200;
private int hiresSliderMin = 50;
@ -60,6 +63,14 @@ public float getResolutionDefault() {
return resolutionDefault;
}
public int getMinZoomDistance() {
return minZoomDistance;
}
public int getMaxZoomDistance() {
return maxZoomDistance;
}
public int getHiresSliderMax() {
return hiresSliderMax;
}

View File

@ -108,7 +108,10 @@ public HttpResponse handle(HttpRequest request) {
response.addHeader("ETag", eTag);
if (lastModified > 0)
response.addHeader("Last-Modified", timestampToString(lastModified));
response.addHeader("Content-Type", "application/json");
if (lod == 0) response.addHeader("Content-Type", "application/json");
else response.addHeader("Content-Type", "image/png");
writeToResponse(compressedIn, response, request);
return response;
}
@ -159,6 +162,7 @@ private void writeToResponse(CompressedInputStream data, HttpResponse response,
response.setData(data);
} else if (
compression != Compression.GZIP &&
!response.getHeader("Content-Type").contains("image/png") &&
request.getLowercaseHeader("Accept-Encoding").contains(Compression.GZIP.getTypeId())
) {
response.addHeader("Content-Encoding", Compression.GZIP.getTypeId());

View File

@ -9,10 +9,13 @@
name: "${name}"
# The path to the save-folder of the world to render.
# (If this is not defined (commented out or removed), the map will be only registered to the web-server and the web-app
# but not rendered or loaded by BlueMap. This can be used to display a map that has been rendered somewhere else.)
world: "${world}"
# A lower value makes the map sorted first (in lists and menus), a higher value makes it sorted later.
# The value needs to be an integer but it can be negative.
# You can change this at any time.
# Default is 0
sorting: ${sorting}
@ -51,6 +54,7 @@ remove-caves-below-y: ${remove-caves-below-y}
# With this value set to true, BlueMap uses the block-light value instead of the sky-light value to "detect caves".
# (See: remove-caves-below-y)
# Changing this value requires a re-render of the map.
# Default is false
cave-detection-uses-block-light: false
@ -74,6 +78,7 @@ render-edges: true
# This defines the storage-config that will be used to save this map.
# You can find your storage configs next to this config file in the 'storages'-folder.
# Changing this value requires a re-render of the map. The map in the old storage will not be deleted.
# Default is "file"
storage: "file"
@ -88,6 +93,7 @@ storage: "file"
ignore-missing-light-data: false
# Here you can define any static marker-sets with markers that should be displayed on the map.
# You can change this at any time.
# If you need dynamic markers, you can use any plugin that integrates with BlueMap's API.
# Here is a list: https://bluemap.bluecolored.de/wiki/customization/3rdPartySupport.html
marker-sets: {

View File

@ -31,6 +31,10 @@ enable-free-flight: true
# Default is "no anchor" -> The camera will start with the topmost map and at that map's starting point.
#start-location: "overworld:0:16:-32:390:0.1:0.19:0:0:perspective"
# The minimum (closest) and maximum (furthest) distance (in blocks) that the camera can be from the ground
min-zoom-distance: 5
max-zoom-distance: 100000
# The default value of the resolution (settings-menu)
# Possible values are: 0.5, 1, 2
# Default is 1

View File

@ -25,6 +25,7 @@
package de.bluecolored.bluemap.core.map;
import com.flowpowered.math.vector.Vector2i;
import com.google.gson.GsonBuilder;
import de.bluecolored.bluemap.api.debug.DebugDump;
import de.bluecolored.bluemap.api.gson.MarkerGson;
import de.bluecolored.bluemap.api.markers.MarkerSet;
@ -96,9 +97,9 @@ public BmMap(String id, String name, String worldId, World world, Storage storag
this.lowresTileManager = new LowresTileManager(
storage.mapStorage(id),
new Grid(new Vector2i(500, 500)),
3,
5
new Grid(settings.getLowresTileSize()),
settings.getLodCount(),
settings.getLodFactor()
);
this.tileFilter = t -> true;
@ -183,7 +184,10 @@ private void saveMapSettings() {
OutputStream out = storage.writeMeta(id, MetaType.SETTINGS);
Writer writer = new OutputStreamWriter(out)
) {
ResourcesGson.INSTANCE.toJson(this, writer);
ResourcesGson.addAdapter(new GsonBuilder())
.registerTypeAdapter(BmMap.class, new MapSettingsSerializer())
.create()
.toJson(this, writer);
} catch (Exception ex) {
Logger.global.logError("Failed to save settings for map '" + getId() + "'!", ex);
}

View File

@ -39,8 +39,10 @@ public interface MapSettings extends RenderSettings {
int getHiresTileSize();
int getLowresPointsPerLowresTile();
int getLowresTileSize();
int getLowresPointsPerHiresTile();
int getLodCount();
int getLodFactor();
}

View File

@ -1,11 +1,11 @@
package de.bluecolored.bluemap.core.resources.adapter;
package de.bluecolored.bluemap.core.map;
import com.flowpowered.math.vector.Vector2i;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.core.map.lowres.LowresTileManager;
import de.bluecolored.bluemap.core.util.ConfigUtils;
import de.bluecolored.bluemap.core.util.math.Color;
@ -26,9 +26,6 @@ public JsonElement serialize(BmMap map, Type typeOfSrc, JsonSerializationContext
// hires
Vector2i hiresTileSize = map.getHiresModelManager().getTileGrid().getGridSize();
Vector2i gridOrigin = map.getHiresModelManager().getTileGrid().getOffset();
//Vector2i lowresTileSize = map.getLowresModelManager().getTileSize();
//Vector2i lowresPointsPerHiresTile = map.getLowresModelManager().getPointsPerHiresTile();
//TODO
JsonObject hires = new JsonObject();
hires.add("tileSize", context.serialize(hiresTileSize));
@ -37,16 +34,13 @@ public JsonElement serialize(BmMap map, Type typeOfSrc, JsonSerializationContext
root.add("hires", hires);
// lowres
/*
Vector2i pointSize = hiresTileSize.div(lowresPointsPerHiresTile);
Vector2i tileSize = pointSize.mul(lowresTileSize);
LowresTileManager lowresTileManager = map.getLowresTileManager();
JsonObject lowres = new JsonObject();
lowres.add("tileSize", context.serialize(tileSize));
lowres.add("scale", context.serialize(pointSize));
lowres.add("translate", context.serialize(pointSize.div(2)));
lowres.add("tileSize", context.serialize(lowresTileManager.getTileGrid().getGridSize()));
lowres.add("lodFactor", context.serialize(lowresTileManager.getLodFactor()));
lowres.add("lodCount", context.serialize(lowresTileManager.getLodCount()));
root.add("lowres", lowres);
*/
// startPos
Vector2i startPos = map.getMapSettings().getStartPos()

View File

@ -0,0 +1,10 @@
package de.bluecolored.bluemap.core.map;
import de.bluecolored.bluemap.core.util.math.Color;
@FunctionalInterface
public interface TileMetaConsumer {
void set(int x, int z, Color color, int height, int blockLight);
}

View File

@ -28,7 +28,7 @@
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.map.TextureGallery;
import de.bluecolored.bluemap.core.map.lowres.LowresTileManager;
import de.bluecolored.bluemap.core.map.TileMetaConsumer;
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.storage.Storage;
import de.bluecolored.bluemap.core.world.Grid;
@ -57,7 +57,7 @@ public HiresModelManager(Storage.TileStorage storage, HiresModelRenderer rendere
/**
* Renders the given world tile with the provided render-settings
*/
public void render(World world, Vector2i tile, LowresTileManager lowresTileManager) {
public void render(World world, Vector2i tile, TileMetaConsumer tileMetaConsumer) {
Vector2i tileMin = tileGrid.getCellMin(tile);
Vector2i tileMax = tileGrid.getCellMax(tile);
@ -66,7 +66,7 @@ public void render(World world, Vector2i tile, LowresTileManager lowresTileManag
HiresTileModel model = HiresTileModel.instancePool().claimInstance();
renderer.render(world, modelMin, modelMax, model, lowresTileManager);
renderer.render(world, modelMin, modelMax, model, tileMetaConsumer);
save(model, tile);
HiresTileModel.instancePool().recycleInstance(model);

View File

@ -26,8 +26,8 @@
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.map.TextureGallery;
import de.bluecolored.bluemap.core.map.TileMetaConsumer;
import de.bluecolored.bluemap.core.map.hires.blockmodel.BlockStateModelFactory;
import de.bluecolored.bluemap.core.map.lowres.LowresTileManager;
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.util.math.Color;
import de.bluecolored.bluemap.core.world.BlockNeighborhood;
@ -45,7 +45,11 @@ public HiresModelRenderer(ResourcePack resourcePack, TextureGallery textureGalle
this.renderSettings = renderSettings;
}
public void render(World world, Vector3i modelMin, Vector3i modelMax, HiresTileModel model, LowresTileManager lowresTileManager) {
public void render(World world, Vector3i modelMin, Vector3i modelMax, HiresTileModel model) {
render(world, modelMin, modelMax, model, (x, z, c, h, l) -> {});
}
public void render(World world, Vector3i modelMin, Vector3i modelMax, HiresTileModel model, TileMetaConsumer tileMetaConsumer) {
Vector3i min = modelMin.max(renderSettings.getMinPos());
Vector3i max = modelMax.min(renderSettings.getMaxPos());
Vector3i modelAnchor = new Vector3i(modelMin.getX(), 0, modelMin.getZ());
@ -54,6 +58,7 @@ public void render(World world, Vector3i modelMin, Vector3i modelMax, HiresTileM
BlockStateModelFactory modelFactory = new BlockStateModelFactory(resourcePack, textureGallery, renderSettings);
int maxHeight, minY, maxY;
double topBlockLight;
Color columnColor = new Color(), blockColor = new Color();
BlockNeighborhood<?> block = new BlockNeighborhood<>(resourcePack, renderSettings, world, 0, 0, 0);
BlockModelView blockModel = new BlockModelView(model);
@ -63,6 +68,8 @@ public void render(World world, Vector3i modelMin, Vector3i modelMax, HiresTileM
for (z = min.getZ(); z <= max.getZ(); z++){
maxHeight = 0;
topBlockLight = 0f;
columnColor.set(0, 0, 0, 1, true);
if (renderSettings.isInsideRenderBoundaries(x, z)) {
@ -88,11 +95,15 @@ public void render(World world, Vector3i modelMin, Vector3i modelMax, HiresTileM
if (blockColor.a > 0) {
maxHeight = y;
columnColor.overlay(blockColor.premultiplied());
topBlockLight = Math.floor(topBlockLight * (1 - blockColor.a));
}
//update topBlockLight
topBlockLight = Math.max(topBlockLight, block.getBlockLightLevel());
}
}
lowresTileManager.set(x, z, columnColor, maxHeight);
tileMetaConsumer.set(x, z, columnColor, maxHeight, (int) topBlockLight);
}
}
}

View File

@ -32,10 +32,14 @@ public LowresTile(Vector2i tileSize, InputStream in) throws IOException {
}
}
public void set(int x, int z, Color color, int height) throws TileClosedException {
public void set(int x, int z, Color color, int height, int blockLight) throws TileClosedException {
if (closed) throw new TileClosedException();
texture.setRGB(x, z, color.straight().getInt());
texture.setRGB(x, size.getY() + z, (height & 0x00FFFFFF) | 0xFF000000);
texture.setRGB(x, size.getY() + z,
(height & 0x0000FFFF) |
((blockLight << 16) & 0x00FF0000) |
0xFF000000
);
}
public Color getColor(int x, int z, Color target) {
@ -43,12 +47,16 @@ public Color getColor(int x, int z, Color target) {
}
public int getHeight(int x, int z) {
int height = texture.getRGB(x, size.getY() + z) & 0x00FFFFFF;
if (height > 0x00800000)
return height | 0xFF000000;
int height = texture.getRGB(x, size.getY() + z) & 0x0000FFFF;
if (height > 0x00008000)
return height | 0xFFFF0000;
return height;
}
public int getBlockLight(int x, int z) {
return (texture.getRGB(x, size.getY() + z) & 0x00FF0000) >> 16;
}
public void save(OutputStream out) throws IOException {
ImageIO.write(texture, "png", out);
}

View File

@ -4,6 +4,7 @@
import com.github.benmanes.caffeine.cache.*;
import de.bluecolored.bluemap.core.BlueMap;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.map.TileMetaConsumer;
import de.bluecolored.bluemap.core.storage.Storage;
import de.bluecolored.bluemap.core.util.Vector2iCache;
import de.bluecolored.bluemap.core.util.math.Color;
@ -19,7 +20,7 @@
import java.util.List;
import java.util.concurrent.TimeUnit;
public class LowresTileManager {
public class LowresTileManager implements TileMetaConsumer {
private final Storage.MapStorage mapStorage;
@ -90,7 +91,7 @@ private void saveTile(Vector2i tilePos, int lod, LowresTile tile, RemovalCause r
// write to next LOD (prepare for the most confusing grid-math you will ever see)
Color averageColor = new Color();
int averageHeight;
int averageHeight, averageBlockLight;
int count;
Color color = new Color();
@ -106,16 +107,19 @@ private void saveTile(Vector2i tilePos, int lod, LowresTile tile, RemovalCause r
for (int gY = 0; gY < groupCount.getY(); gY++) {
averageColor.set(0, 0, 0, 0, true);
averageHeight = 0;
averageBlockLight = 0;
count = 0;
for (int x = 0; x < lodFactor; x++) {
for (int y = 0; y < lodFactor; y++) {
count++;
averageColor.add(tile.getColor(gX * lodFactor + x, gY * lodFactor + y, color).premultiplied());
averageHeight += tile.getHeight(gX * lodFactor + x, gY * lodFactor + y);
averageBlockLight += tile.getBlockLight(gX * lodFactor + x, gY * lodFactor + y);
}
}
averageColor.div(count);
averageHeight /= count;
averageBlockLight /= count;
set(
nextLodTileX,
@ -124,7 +128,8 @@ private void saveTile(Vector2i tilePos, int lod, LowresTile tile, RemovalCause r
Math.floorMod(tilePos.getX(), lodFactor) * groupCount.getX() + gX,
Math.floorMod(tilePos.getY(), lodFactor) * groupCount.getY() + gY,
averageColor,
averageHeight
averageHeight,
averageBlockLight
);
}
}
@ -141,15 +146,16 @@ public LowresTile getTile(int x, int z, int lod) {
return tileCaches.get(lod - 1).get(vector2iCache.get(x, z));
}
public void set(int x, int z, Color color, int height) {
@Override
public void set(int x, int z, Color color, int height, int blockLight) {
int cellX = tileGrid.getCellX(x);
int cellZ = tileGrid.getCellY(z);
int localX = tileGrid.getLocalX(x);
int localZ = tileGrid.getLocalY(z);
set(cellX, cellZ, 1, localX, localZ, color, height);
set(cellX, cellZ, 1, localX, localZ, color, height, blockLight);
}
private void set(int cellX, int cellZ, int lod, int pixelX, int pixelZ, Color color, int height) {
private void set(int cellX, int cellZ, int lod, int pixelX, int pixelZ, Color color, int height, int blockLight) {
int tries = 0;
LowresTile.TileClosedException closedException;
@ -158,22 +164,22 @@ private void set(int cellX, int cellZ, int lod, int pixelX, int pixelZ, Color co
closedException = null;
try {
getTile(cellX, cellZ, lod)
.set(pixelX, pixelZ, color, height);
.set(pixelX, pixelZ, color, height, blockLight);
// for seamless edges
if (pixelX == 0) {
getTile(cellX - 1, cellZ, lod)
.set(tileGrid.getGridSize().getX(), pixelZ, color, height);
.set(tileGrid.getGridSize().getX(), pixelZ, color, height, blockLight);
}
if (pixelZ == 0) {
getTile(cellX, cellZ - 1, lod)
.set(pixelX, tileGrid.getGridSize().getY(), color, height);
.set(pixelX, tileGrid.getGridSize().getY(), color, height, blockLight);
}
if (pixelX == 0 && pixelZ == 0) {
getTile(cellX - 1, cellZ - 1, lod)
.set(tileGrid.getGridSize().getX(), tileGrid.getGridSize().getY(), color, height);
.set(tileGrid.getGridSize().getX(), tileGrid.getGridSize().getY(), color, height, blockLight);
}
} catch (LowresTile.TileClosedException ex) {
closedException = ex;
@ -182,4 +188,16 @@ private void set(int cellX, int cellZ, int lod, int pixelX, int pixelZ, Color co
}
public Grid getTileGrid() {
return tileGrid;
}
public int getLodCount() {
return lodCount;
}
public int getLodFactor() {
return lodFactor;
}
}

View File

@ -6,7 +6,6 @@
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.Face;
import de.bluecolored.bluemap.core.util.Direction;
import de.bluecolored.bluemap.core.util.math.Axis;
@ -31,7 +30,6 @@ public static GsonBuilder addAdapter(GsonBuilder builder) {
.registerTypeAdapter(Vector3f.class, new Vector3fAdapter())
.registerTypeAdapter(Vector4d.class, new Vector4dAdapter())
.registerTypeAdapter(Vector4f.class, new Vector4fAdapter())
.registerTypeAdapter(BmMap.class, new MapSettingsSerializer())
.registerTypeAdapter(
new TypeToken<EnumMap<Direction, Face>>(){}.getType(),
new EnumMapInstanceCreator<Direction, Face>(Direction.class)