Add initial MultiTileArea

This commit is contained in:
2026-03-22 15:21:27 +01:00
parent a7b41eb4e8
commit 268f450ee8
@@ -0,0 +1,188 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2026 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.region.dynamic;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import de.steamwar.bausystem.region.Point;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.shared.Pair;
import lombok.NonNull;
import org.bukkit.Location;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.BiConsumer;
public abstract class MultiTileArea implements Region.Area {
private final Set<Tile> tiles = new HashSet<>();
public boolean addTile(Tile tile) {
boolean result = tiles.add(tile);
quantize();
return result;
}
public boolean removeTile(Tile tile) {
boolean result = tiles.remove(tile);
quantize();
return result;
}
public void quantize() {
long time = System.currentTimeMillis();
Quantizer quantizer = new Quantizer(tiles);
List<Pair<Tile, Tile>> results = new ArrayList<>();
while (true) {
Pair<Tile, Tile> pair = quantizer.next();
if (pair == null) break;
results.add(pair);
}
// [{"min_tile_x":0,"min_tile_z":0,"max_tile_x":0,"max_tile_z":0}]
time = System.currentTimeMillis() - time;
System.out.println(tiles.size() + ": " + results.size() + " in " + time + "ms");
}
@Override
public @NonNull Point getMinPoint(boolean extension) {
return Point.ZERO;
}
@Override
public @NonNull Point getMaxPoint(boolean extension) {
return Point.ZERO;
}
@Override
public @NonNull Point getCopyPoint() {
return Point.ZERO;
}
@Override
public boolean inRegion(Location location, boolean extension) {
return inRegion(location.getBlockX(), location.getBlockZ(), extension);
}
@Override
public boolean inRegion(int x, int z, boolean extension) {
Tile tile = Tile.fromXZ(x, z).orElse(null);
return tiles.contains(tile);
}
@Override
public @Nullable Clipboard copy(boolean extension) {
return null;
}
@Override
public void forEachChunk(BiConsumer<Integer, Integer> executor) {
}
@Override
public boolean isChunkOutside(int chunkX, int chunkZ) {
return true;
}
private static class Quantizer {
private Map<Tile, Integer> tileToNeighbourCount = new HashMap<>();
public Quantizer(Set<Tile> tiles) {
for (Tile tile : tiles) {
int count = (int) tile.neighboursRing()
.filter(tiles::contains)
.count();
tileToNeighbourCount.put(tile, count);
}
}
public Pair<Tile, Tile> next() {
Map.Entry<Tile, Integer> entry = tileToNeighbourCount.entrySet()
.stream()
.max(Map.Entry.comparingByValue())
.orElse(null);
if (entry == null) return null;
Tile minTile = entry.getKey();
Tile maxTile = entry.getKey();
tileToNeighbourCount.remove(entry.getKey());
while (true) {
Pair<Set<Tile>, Integer> neg_x = tryExpand(minTile, maxTile, -1, 0);
Pair<Set<Tile>, Integer> pos_x = tryExpand(minTile, maxTile, 1, 0);
Pair<Set<Tile>, Integer> neg_z = tryExpand(minTile, maxTile, 0, -1);
Pair<Set<Tile>, Integer> pos_z = tryExpand(minTile, maxTile, 0, 1);
if (neg_x.getValue() == 0 && pos_x.getValue() == 0 && neg_z.getValue() == 0 && pos_z.getValue() == 0) {
break;
}
Set<Tile> remove;
if (neg_x.getValue() >= pos_x.getValue() && neg_x.getValue() >= neg_z.getValue() && neg_x.getValue() >= pos_z.getValue()) {
minTile = minTile.add(-1, 0).orElseThrow();
remove = neg_x.getKey();
} else if (pos_x.getValue() >= neg_x.getValue() && pos_x.getValue() >= neg_z.getValue() && pos_x.getValue() >= pos_z.getValue()) {
maxTile = maxTile.add(1, 0).orElseThrow();
remove = pos_x.getKey();
} else if (neg_z.getValue() >= neg_x.getValue() && neg_z.getValue() >= pos_x.getValue() && neg_z.getValue() >= pos_z.getValue()) {
minTile = minTile.add(0, -1).orElseThrow();
remove = neg_z.getKey();
} else {
maxTile = maxTile.add(0, 1).orElseThrow();
remove = pos_z.getKey();
}
remove.forEach(tileToNeighbourCount::remove);
}
return new Pair<>(minTile, minTile.equals(maxTile) ? null : maxTile);
}
private static final Pair<Set<Tile>, Integer> EMPTY = new Pair<>(Collections.emptySet(), 0);
private Pair<Set<Tile>, Integer> tryExpand(Tile minTile, Tile maxTile, int dx, int dz) {
Set<Tile> expaned = new HashSet<>();
if (dx == 0) {
for (int x = minTile.getTileX(); x <= maxTile.getTileX(); x++) {
int z = (dz < 0 ? minTile.getTileZ() : maxTile.getTileZ()) + dz;
Tile tile = Tile.fromTile(x, z).orElse(null);
if (tile == null) return EMPTY;
if (!tileToNeighbourCount.containsKey(tile)) return EMPTY;
expaned.add(tile);
}
} else if (dz == 0) {
for (int z = minTile.getTileZ(); z <= maxTile.getTileZ(); z++) {
int x = (dx < 0 ? minTile.getTileX() : maxTile.getTileX()) + dx;
Tile tile = Tile.fromTile(x, z).orElse(null);
if (tile == null) return EMPTY;
if (!tileToNeighbourCount.containsKey(tile)) return EMPTY;
expaned.add(tile);
}
} else {
throw new IllegalStateException();
}
int total = expaned.stream()
.mapToInt(tileToNeighbourCount::get)
.sum();
return new Pair<>(expaned, total);
}
}
}