mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 14:00:26 +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.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
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
|
* 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
|
* 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
|
* 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 {
|
final class TerrainCover {
|
||||||
private static final double MOST_COLUMN_PIXELS = 1D;
|
private static final double MOST_COLUMN_PIXELS = 1D;
|
||||||
@@ -96,9 +100,57 @@ final class TerrainCover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void coverSight() {
|
private void coverSight() {
|
||||||
|
Map<TerrainTile, List<TerrainTile>> coveredByBackdrop = new LinkedHashMap<>();
|
||||||
for (TerrainTile backdrop : sight.tilesInSight(backdropLevel, BACKDROP_MARGIN_SHARE)) {
|
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));
|
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.
|
* 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