mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2025-01-24 01:01:50 +01:00
Fix black spots in lowres layer (syncronization issue)
This commit is contained in:
parent
0a4d85982c
commit
5448850eca
@ -0,0 +1,158 @@
|
||||
package de.bluecolored.bluemap.core.map.lowres;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.github.benmanes.caffeine.cache.*;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.storage.Storage;
|
||||
import de.bluecolored.bluemap.core.util.Vector2iCache;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import de.bluecolored.bluemap.core.world.Grid;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class LowresLayer {
|
||||
|
||||
private static final Vector2iCache VECTOR_2_I_CACHE = new Vector2iCache();
|
||||
|
||||
private final Storage.MapStorage mapStorage;
|
||||
|
||||
private final Grid tileGrid;
|
||||
private final int lodFactor;
|
||||
|
||||
private final int lod;
|
||||
private final LoadingCache<Vector2i, LowresTile> tileCache;
|
||||
@Nullable private final LowresLayer nextLayer;
|
||||
|
||||
public LowresLayer(
|
||||
Storage.MapStorage mapStorage, Grid tileGrid, int lodCount, int lodFactor,
|
||||
int lod, @Nullable LowresLayer nextLayer
|
||||
) {
|
||||
this.mapStorage = mapStorage;
|
||||
|
||||
this.tileGrid = tileGrid;
|
||||
this.lodFactor = lodFactor;
|
||||
|
||||
this.lod = lod;
|
||||
this.nextLayer = nextLayer;
|
||||
|
||||
// this extra cache makes sure that a tile instance is reused as long as it is still referenced somewhere ..
|
||||
// so always only one instance of the same lowres-tile exists
|
||||
LoadingCache<Vector2i, LowresTile> tileWeakInstanceCache = Caffeine.newBuilder()
|
||||
.executor(BlueMap.THREAD_POOL)
|
||||
.weakValues()
|
||||
.build(this::createTile);
|
||||
|
||||
this.tileCache = Caffeine.newBuilder()
|
||||
.executor(BlueMap.THREAD_POOL)
|
||||
.scheduler(Scheduler.systemScheduler())
|
||||
.expireAfterAccess(10, TimeUnit.SECONDS)
|
||||
.expireAfterWrite(5, TimeUnit.MINUTES)
|
||||
.removalListener(this::saveTile)
|
||||
.build(tileWeakInstanceCache::get);
|
||||
}
|
||||
|
||||
public void save() {
|
||||
Logger.global.logInfo("Saving lowres layer " + lod);
|
||||
tileCache.invalidateAll();
|
||||
tileCache.cleanUp();
|
||||
}
|
||||
|
||||
private LowresTile createTile(Vector2i tilePos) {
|
||||
try (InputStream in = mapStorage.read(lod, tilePos).orElse(null)) {
|
||||
if (in == null)
|
||||
return new LowresTile(tileGrid.getGridSize());
|
||||
return new LowresTile(tileGrid.getGridSize(), in);
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("Failed to load tile " + tilePos + " (lod: " + lod + ")", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void saveTile(Vector2i tilePos, @Nullable LowresTile tile, RemovalCause removalCause) {
|
||||
if (tile == null) return;
|
||||
|
||||
// save the tile
|
||||
try (OutputStream out = mapStorage.write(lod, tilePos)) {
|
||||
tile.save(out);
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("Failed to save tile " + tilePos + " (lod: " + lod + ")", e);
|
||||
}
|
||||
|
||||
// write to next LOD (prepare for the most confusing grid-math you will ever see)
|
||||
if (this.nextLayer == null) return;
|
||||
|
||||
Color averageColor = new Color();
|
||||
int averageHeight, averageBlockLight;
|
||||
int count;
|
||||
|
||||
Color color = new Color();
|
||||
|
||||
int nextLodTileX = Math.floorDiv(tilePos.getX(), lodFactor);
|
||||
int nextLodTileY = Math.floorDiv(tilePos.getY(), lodFactor);
|
||||
Vector2i groupCount = new Vector2i(
|
||||
Math.floorDiv(tileGrid.getGridSize().getX(), lodFactor),
|
||||
Math.floorDiv(tileGrid.getGridSize().getY(), lodFactor)
|
||||
);
|
||||
|
||||
for (int gX = 0; gX < groupCount.getX(); gX++) {
|
||||
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;
|
||||
|
||||
this.nextLayer.set(
|
||||
nextLodTileX,
|
||||
nextLodTileY,
|
||||
Math.floorMod(tilePos.getX(), lodFactor) * groupCount.getX() + gX,
|
||||
Math.floorMod(tilePos.getY(), lodFactor) * groupCount.getY() + gY,
|
||||
averageColor,
|
||||
averageHeight,
|
||||
averageBlockLight
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LowresTile getTile(int x, int z) {
|
||||
return tileCache.get(VECTOR_2_I_CACHE.get(x, z));
|
||||
}
|
||||
|
||||
void set(int cellX, int cellZ, int pixelX, int pixelZ, Color color, int height, int blockLight) {
|
||||
getTile(cellX, cellZ)
|
||||
.set(pixelX, pixelZ, color, height, blockLight);
|
||||
|
||||
// for seamless edges
|
||||
if (pixelX == 0) {
|
||||
getTile(cellX - 1, cellZ)
|
||||
.set(tileGrid.getGridSize().getX(), pixelZ, color, height, blockLight);
|
||||
}
|
||||
|
||||
if (pixelZ == 0) {
|
||||
getTile(cellX, cellZ - 1)
|
||||
.set(pixelX, tileGrid.getGridSize().getY(), color, height, blockLight);
|
||||
}
|
||||
|
||||
if (pixelX == 0 && pixelZ == 0) {
|
||||
getTile(cellX - 1, cellZ - 1)
|
||||
.set(tileGrid.getGridSize().getX(), tileGrid.getGridSize().getY(), color, height, blockLight);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -8,16 +8,17 @@
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
public class LowresTile {
|
||||
|
||||
public static final int HEIGHT_UNDEFINED = Integer.MIN_VALUE;
|
||||
|
||||
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
|
||||
private final BufferedImage texture;
|
||||
private final Vector2i size;
|
||||
|
||||
private volatile boolean closed = false;
|
||||
|
||||
public LowresTile(Vector2i tileSize) {
|
||||
this.size = tileSize.add(1, 1); // add 1 for seamless edges
|
||||
this.texture = new BufferedImage(this.size.getX(), this.size.getY() * 2, BufferedImage.TYPE_INT_ARGB);
|
||||
@ -32,14 +33,18 @@ public LowresTile(Vector2i tileSize, InputStream in) throws IOException {
|
||||
}
|
||||
}
|
||||
|
||||
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 & 0x0000FFFF) |
|
||||
((blockLight << 16) & 0x00FF0000) |
|
||||
0xFF000000
|
||||
);
|
||||
public void set(int x, int z, Color color, int height, int blockLight) {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
texture.setRGB(x, z, color.straight().getInt());
|
||||
texture.setRGB(x, size.getY() + z,
|
||||
(height & 0x0000FFFF) |
|
||||
((blockLight << 16) & 0x00FF0000) |
|
||||
0xFF000000
|
||||
);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public Color getColor(int x, int z, Color target) {
|
||||
@ -58,16 +63,11 @@ public int getBlockLight(int x, int z) {
|
||||
}
|
||||
|
||||
public void save(OutputStream out) throws IOException {
|
||||
ImageIO.write(texture, "png", out);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
closed = true;
|
||||
}
|
||||
|
||||
public static class TileClosedException extends Exception {
|
||||
public TileClosedException() {
|
||||
super("Tile is closed");
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
ImageIO.write(texture, "png", out);
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,193 +1,35 @@
|
||||
package de.bluecolored.bluemap.core.map.lowres;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
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;
|
||||
import de.bluecolored.bluemap.core.world.Grid;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class LowresTileManager implements TileMetaConsumer {
|
||||
|
||||
private final Storage.MapStorage mapStorage;
|
||||
|
||||
private final Grid tileGrid;
|
||||
private final int lodFactor, lodCount;
|
||||
|
||||
private final Vector2iCache vector2iCache;
|
||||
private final List<LoadingCache<Vector2i, LowresTile>> tileCaches;
|
||||
private final LowresLayer[] layers;
|
||||
|
||||
public LowresTileManager(Storage.MapStorage mapStorage, Grid tileGrid, int lodCount, int lodFactor) {
|
||||
this.mapStorage = mapStorage;
|
||||
|
||||
this.tileGrid = tileGrid;
|
||||
this.lodFactor = lodFactor;
|
||||
this.lodCount = lodCount;
|
||||
|
||||
this.vector2iCache = new Vector2iCache();
|
||||
List<LoadingCache<Vector2i, LowresTile>> tileCacheList = new ArrayList<>();
|
||||
for (int i = 0; i < lodCount; i++) {
|
||||
int lod = i + 1;
|
||||
tileCacheList.add(Caffeine.newBuilder()
|
||||
.executor(BlueMap.THREAD_POOL)
|
||||
.scheduler(Scheduler.systemScheduler())
|
||||
.expireAfterAccess(10, TimeUnit.SECONDS)
|
||||
.expireAfterWrite(5, TimeUnit.MINUTES)
|
||||
.writer(new CacheWriter<Vector2i, LowresTile>() {
|
||||
|
||||
@Override
|
||||
public void write(@NonNull Vector2i key, @NonNull LowresTile value) {}
|
||||
|
||||
@Override
|
||||
public void delete(@NonNull Vector2i key, @Nullable LowresTile value, @NonNull RemovalCause cause) {
|
||||
if (value != null)
|
||||
saveTile(key, lod, value, cause);
|
||||
}
|
||||
|
||||
})
|
||||
.build(key -> loadTile(key, lod))
|
||||
);
|
||||
}
|
||||
this.tileCaches = Collections.unmodifiableList(tileCacheList);
|
||||
}
|
||||
|
||||
private LowresTile loadTile(Vector2i tilePos, int lod) {
|
||||
try (InputStream in = mapStorage.read(lod, tilePos).orElse(null)) {
|
||||
if (in == null)
|
||||
return new LowresTile(tileGrid.getGridSize());
|
||||
return new LowresTile(tileGrid.getGridSize(), in);
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("Failed to load tile " + tilePos + " (lod: " + lod + ")", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void saveTile(Vector2i tilePos, int lod, LowresTile tile, RemovalCause removalCause) {
|
||||
// close the tile so it can't be edited anymore
|
||||
tile.close();
|
||||
|
||||
// save the tile
|
||||
try (OutputStream out = mapStorage.write(lod, tilePos)) {
|
||||
tile.save(out);
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("Failed to save tile " + tilePos + " (lod: " + lod + ")", e);
|
||||
}
|
||||
|
||||
if (lod >= lodCount) return;
|
||||
|
||||
// write to next LOD (prepare for the most confusing grid-math you will ever see)
|
||||
Color averageColor = new Color();
|
||||
int averageHeight, averageBlockLight;
|
||||
int count;
|
||||
|
||||
Color color = new Color();
|
||||
|
||||
int nextLodTileX = Math.floorDiv(tilePos.getX(), lodFactor);
|
||||
int nextLodTileY = Math.floorDiv(tilePos.getY(), lodFactor);
|
||||
Vector2i groupCount = new Vector2i(
|
||||
Math.floorDiv(tileGrid.getGridSize().getX(), lodFactor),
|
||||
Math.floorDiv(tileGrid.getGridSize().getY(), lodFactor)
|
||||
);
|
||||
|
||||
for (int gX = 0; gX < groupCount.getX(); gX++) {
|
||||
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,
|
||||
nextLodTileY,
|
||||
lod + 1,
|
||||
Math.floorMod(tilePos.getX(), lodFactor) * groupCount.getX() + gX,
|
||||
Math.floorMod(tilePos.getY(), lodFactor) * groupCount.getY() + gY,
|
||||
averageColor,
|
||||
averageHeight,
|
||||
averageBlockLight
|
||||
);
|
||||
}
|
||||
this.layers = new LowresLayer[lodCount];
|
||||
for (int i = lodCount - 1; i >= 0; i--) {
|
||||
this.layers[i] = new LowresLayer(mapStorage, tileGrid, lodCount, lodFactor, i + 1,
|
||||
(i == lodCount - 1) ? null : layers[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void save() {
|
||||
for (LoadingCache<Vector2i, LowresTile> cache : this.tileCaches) {
|
||||
cache.invalidateAll();
|
||||
cache.cleanUp();
|
||||
for (LowresLayer layer : this.layers) {
|
||||
layer.save();
|
||||
}
|
||||
}
|
||||
|
||||
public LowresTile getTile(int x, int z, int lod) {
|
||||
return tileCaches.get(lod - 1).get(vector2iCache.get(x, z));
|
||||
}
|
||||
|
||||
@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, blockLight);
|
||||
}
|
||||
|
||||
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;
|
||||
do {
|
||||
tries ++;
|
||||
closedException = null;
|
||||
try {
|
||||
getTile(cellX, cellZ, lod)
|
||||
.set(pixelX, pixelZ, color, height, blockLight);
|
||||
|
||||
// for seamless edges
|
||||
if (pixelX == 0) {
|
||||
getTile(cellX - 1, cellZ, lod)
|
||||
.set(tileGrid.getGridSize().getX(), pixelZ, color, height, blockLight);
|
||||
}
|
||||
|
||||
if (pixelZ == 0) {
|
||||
getTile(cellX, cellZ - 1, lod)
|
||||
.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, blockLight);
|
||||
}
|
||||
} catch (LowresTile.TileClosedException ex) {
|
||||
closedException = ex;
|
||||
}
|
||||
} while (closedException != null && tries < 10);
|
||||
|
||||
}
|
||||
|
||||
public Grid getTileGrid() {
|
||||
return tileGrid;
|
||||
}
|
||||
@ -200,4 +42,13 @@ public int getLodFactor() {
|
||||
return lodFactor;
|
||||
}
|
||||
|
||||
@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);
|
||||
layers[0].set(cellX, cellZ, localX, localZ, color, height, blockLight);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user