mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 05:50:39 +00:00
Keep coarser ground in 3D views until new backdrop arrives
- Backdrop not sampled yet borrows the nearest sampled tile up to four levels coarser, so zooming in leaves no gap
This commit is contained in:
1 parent
ff6668a48b
commit
54213d4b62
3 files changed
+152
-2
No files matched your search
@@ -21,7 +21,11 @@ package art.arcane.iris.studio.map;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -30,7 +34,7 @@ import java.util.Set;
|
||||
* whole view and more right away. Preview tiles one level coarser than detail come between them: a quarter of the
|
||||
* columns, so the whole view looks nearly finished long before the detail is in. Until it is, coarser tiles stand in
|
||||
* for it, with holes where finer tiles are ready; detail left over from before zooming out stands in too, as does
|
||||
* ground from before the noise changed.
|
||||
* ground from before the noise changed, and backdrop left over from before zooming in.
|
||||
*/
|
||||
final class TerrainCover {
|
||||
private static final double MOST_COLUMN_PIXELS = 1D;
|
||||
@@ -96,9 +100,57 @@ final class TerrainCover {
|
||||
}
|
||||
|
||||
private void coverSight() {
|
||||
Map<TerrainTile, List<TerrainTile>> coveredByBackdrop = new LinkedHashMap<>();
|
||||
for (TerrainTile backdrop : sight.tilesInSight(backdropLevel, BACKDROP_MARGIN_SHARE)) {
|
||||
cover(backdrop, true);
|
||||
coveredByBackdrop.put(backdrop, cover(backdrop, true));
|
||||
}
|
||||
coverMissingBackdrop(coveredByBackdrop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Backdrop that isn't sampled yet, as right after zooming in, borrows the nearest coarser tile that is, with holes
|
||||
* where the backdrop and finer ground around it are ready. Only as far up as holes can still be cut.
|
||||
*/
|
||||
private void coverMissingBackdrop(Map<TerrainTile, List<TerrainTile>> coveredByBackdrop) {
|
||||
Set<TerrainTile> standIns = new LinkedHashSet<>();
|
||||
coveredByBackdrop.forEach((backdrop, covered) -> {
|
||||
if (!covered.equals(List.of(backdrop))) {
|
||||
nearestSampledAncestor(backdrop).ifPresent(standIns::add);
|
||||
}
|
||||
});
|
||||
for (TerrainTile standIn : standIns) {
|
||||
TerrainLayer layer = tiles.layerOf(standIn);
|
||||
if (layer != null) {
|
||||
draw(standIn, layer, fillersOf(standIn, coveredByBackdrop, standIns));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<TerrainTile> nearestSampledAncestor(TerrainTile backdrop) {
|
||||
for (int levelsUp = 1; levelsUp <= TerrainHole.MOST_LEVELS_FINER; levelsUp++) {
|
||||
TerrainTile ancestor = backdrop.ancestorAt(backdrop.level() + levelsUp);
|
||||
if (tiles.isSampled(ancestor)) {
|
||||
return Optional.of(ancestor);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
// Everything drawn inside the stand-in: the backdrop's ready ground, and finer stand-ins.
|
||||
private static List<TerrainTile> fillersOf(TerrainTile standIn, Map<TerrainTile, List<TerrainTile>> coveredByBackdrop,
|
||||
Set<TerrainTile> standIns) {
|
||||
List<TerrainTile> fillers = new ArrayList<>();
|
||||
coveredByBackdrop.forEach((backdrop, covered) -> {
|
||||
if (backdrop.ancestorAt(standIn.level()).equals(standIn)) {
|
||||
fillers.addAll(covered);
|
||||
}
|
||||
});
|
||||
for (TerrainTile finer : standIns) {
|
||||
if (finer.level() < standIn.level() && finer.ancestorAt(standIn.level()).equals(standIn)) {
|
||||
fillers.add(finer);
|
||||
}
|
||||
}
|
||||
return fillers;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -66,6 +66,14 @@ record TerrainTile(int level, int tileX, int tileZ) {
|
||||
new TerrainTile(finer, childX, childZ + 1), new TerrainTile(finer, childX + 1, childZ + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* The tile of a coarser level that this one lies inside.
|
||||
*/
|
||||
TerrainTile ancestorAt(int coarserLevel) {
|
||||
int levelsUp = coarserLevel - level;
|
||||
return new TerrainTile(coarserLevel, tileX >> levelsUp, tileZ >> levelsUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* This tile's columns that a finer tile inside it covers.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package art.arcane.iris.studio.map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.AbstractExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public final class TerrainCoverTest {
|
||||
private static final int WIDTH = 1200;
|
||||
private static final int HEIGHT = 800;
|
||||
private static final TerrainSampling FLAT = new TerrainSampling((grid, row, heights, colors) -> {
|
||||
}, 0, 64, Double.NaN);
|
||||
private static final OrbitProjection PROJECTION = NoiseTerrainScene.projectionFrom(Vec3.ZERO, NoiseTerrainScene.START,
|
||||
WIDTH, HEIGHT, 0);
|
||||
|
||||
@Test
|
||||
public void backdropNotSampledYetBorrowsTheNearestSampledCoarserTile() {
|
||||
TerrainTile coarser = TerrainTile.containing(0D, 0D, backdropLevel() + 1);
|
||||
|
||||
List<TerrainTile> drawn = drawnTiles(Map.of(coarser, flatLayer(coarser)));
|
||||
|
||||
assertTrue(drawn.contains(coarser));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tilesTooCoarseToCutHolesInDoNotStandIn() {
|
||||
TerrainTile tooCoarse = TerrainTile.containing(0D, 0D, backdropLevel() + TerrainHole.MOST_LEVELS_FINER + 1);
|
||||
|
||||
List<TerrainTile> drawn = drawnTiles(Map.of(tooCoarse, flatLayer(tooCoarse)));
|
||||
|
||||
assertFalse(drawn.contains(tooCoarse));
|
||||
}
|
||||
|
||||
private static int backdropLevel() {
|
||||
TerrainCover.Result nothingSampled = TerrainCover.of(tilesWith(Map.of()), PROJECTION, WIDTH, HEIGHT, 0);
|
||||
int detailLevel = Integer.numberOfTrailingZeros(nothingSampled.detailStep());
|
||||
return detailLevel + 3;
|
||||
}
|
||||
|
||||
private static List<TerrainTile> drawnTiles(Map<TerrainTile, TerrainLayer> sampled) {
|
||||
return TerrainCover.of(tilesWith(sampled), PROJECTION, WIDTH, HEIGHT, 0).layers().stream()
|
||||
.map(HoledLayer::tile)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static TerrainTiles tilesWith(Map<TerrainTile, TerrainLayer> sampled) {
|
||||
return new TerrainTiles(FLAT, new NeverRuns(), 1, () -> {
|
||||
}, sampled);
|
||||
}
|
||||
|
||||
private static TerrainLayer flatLayer(TerrainTile tile) {
|
||||
return TerrainLayer.of(NoiseTerrain.empty(tile.grid()), FLAT.floorY());
|
||||
}
|
||||
|
||||
// Keeps the wanted tiles unsampled, so each test decides exactly what is ready.
|
||||
private static final class NeverRuns extends AbstractExecutorService {
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Runnable> shutdownNow() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShutdown() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTerminated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean awaitTermination(long timeout, TimeUnit unit) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user