mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 21:40:05 +00:00
Keep huge procedural trees from stalling
Faster leaf support; height capped at 128.
This commit is contained in:
1 parent
2659837007
commit
03fd5e4bf3
3 files changed
+175
-38
No files matched your search
@@ -33,6 +33,8 @@ public final class ResolvedTree extends ResolvedProceduralPlacement {
|
|||||||
private static final long VARIANT_SEED_STRIDE = 7919L;
|
private static final long VARIANT_SEED_STRIDE = 7919L;
|
||||||
private static final double HEIGHT_JITTER_FRACTION = 0.3;
|
private static final double HEIGHT_JITTER_FRACTION = 0.3;
|
||||||
private static final int MINIMUM_HEIGHT = 2;
|
private static final int MINIMUM_HEIGHT = 2;
|
||||||
|
/** Nearly three times the tallest tree in the packs. Taller settings are clamped so a typo bakes in seconds, not hours. */
|
||||||
|
static final int TALLEST_TREE_BLOCKS = 128;
|
||||||
|
|
||||||
private final Tree settings;
|
private final Tree settings;
|
||||||
|
|
||||||
@@ -55,8 +57,8 @@ public final class ResolvedTree extends ResolvedProceduralPlacement {
|
|||||||
protected List<IrisObject> bake(ObjectBuilder blocks) {
|
protected List<IrisObject> bake(ObjectBuilder blocks) {
|
||||||
List<IrisObject> baked = new ArrayList<>();
|
List<IrisObject> baked = new ArrayList<>();
|
||||||
int count = Math.max(1, settings.variants());
|
int count = Math.max(1, settings.variants());
|
||||||
int lo = Math.min(settings.heightMin(), settings.heightMax());
|
int lo = Math.min(Math.min(settings.heightMin(), settings.heightMax()), TALLEST_TREE_BLOCKS);
|
||||||
int hi = Math.max(settings.heightMin(), settings.heightMax());
|
int hi = Math.min(Math.max(settings.heightMin(), settings.heightMax()), TALLEST_TREE_BLOCKS);
|
||||||
RNG heightRng = new RNG(settings.seed());
|
RNG heightRng = new RNG(settings.seed());
|
||||||
|
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
|
|||||||
@@ -104,10 +104,12 @@ public final class Tree {
|
|||||||
private TreeProfile profile = TreeProfile.OAK;
|
private TreeProfile profile = TreeProfile.OAK;
|
||||||
|
|
||||||
@MinNumber(1)
|
@MinNumber(1)
|
||||||
|
@MaxNumber(ResolvedTree.TALLEST_TREE_BLOCKS)
|
||||||
@Desc("Minimum trunk height in blocks.")
|
@Desc("Minimum trunk height in blocks.")
|
||||||
private int heightMin = 8;
|
private int heightMin = 8;
|
||||||
|
|
||||||
@MinNumber(1)
|
@MinNumber(1)
|
||||||
|
@MaxNumber(ResolvedTree.TALLEST_TREE_BLOCKS)
|
||||||
@Desc("Maximum trunk height in blocks.")
|
@Desc("Maximum trunk height in blocks.")
|
||||||
private int heightMax = 12;
|
private int heightMax = 12;
|
||||||
|
|
||||||
|
|||||||
+169
-36
@@ -19,7 +19,10 @@
|
|||||||
package com.volmit.iris.engine.object.procedural.tree;
|
package com.volmit.iris.engine.object.procedural.tree;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public final class TreeSupport {
|
public final class TreeSupport {
|
||||||
private static final int LEGAL_DISTANCE = 6;
|
private static final int LEGAL_DISTANCE = 6;
|
||||||
@@ -28,46 +31,65 @@ public final class TreeSupport {
|
|||||||
private TreeSupport() {
|
private TreeSupport() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grows wood toward the leaves farthest from any, one tendril at a time, until every leaf is close enough to wood
|
||||||
|
* not to decay or {@code maxTendrils} have grown. Wood only ever gets added, so each leaf's nearest wood is found
|
||||||
|
* once and afterwards only compared against the newest tendril.
|
||||||
|
*/
|
||||||
public static void ensureLeavesSupported(TreeBlockCanvas canvas, int maxTendrils) {
|
public static void ensureLeavesSupported(TreeBlockCanvas canvas, int maxTendrils) {
|
||||||
for (int iter = 0; iter < maxTendrils; iter++) {
|
if (canvas.getTrunk().isEmpty()) {
|
||||||
Map<TreeBlockCanvas.Vec, Integer> dist = TreePlausibility.computeDistances(canvas.getTrunk(), canvas.getLeaf());
|
return;
|
||||||
|
}
|
||||||
TreeBlockCanvas.Vec worstLeaf = null;
|
List<TreeBlockCanvas.Vec> unsupported = unsupportedLeaves(canvas);
|
||||||
TreeBlockCanvas.Vec worstWood = null;
|
Map<TreeBlockCanvas.Vec, Nearest> nearestWoodByLeaf = NearestWood.of(canvas.getTrunk()).nearestTo(unsupported);
|
||||||
int worstGap = -1;
|
for (int tendril = 0; tendril < maxTendrils && !unsupported.isEmpty(); tendril++) {
|
||||||
|
TreeBlockCanvas.Vec farthestLeaf = farthestFromWood(unsupported, nearestWoodByLeaf);
|
||||||
for (TreeBlockCanvas.Vec leaf : new ArrayList<>(canvas.getLeaf())) {
|
List<TreeBlockCanvas.Vec> tendrilWood = growTendril(canvas, nearestWoodByLeaf.get(farthestLeaf).wood(), farthestLeaf);
|
||||||
Integer d = dist.get(leaf);
|
unsupported = unsupportedLeaves(canvas);
|
||||||
if (d != null && d <= LEGAL_DISTANCE) {
|
moveTowardNewWood(nearestWoodByLeaf, unsupported, tendrilWood);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
TreeBlockCanvas.Vec nearest = null;
|
|
||||||
int best = Integer.MAX_VALUE;
|
|
||||||
for (TreeBlockCanvas.Vec wood : canvas.getTrunk()) {
|
|
||||||
int cd = chebyshev(leaf, wood);
|
|
||||||
if (cd < best) {
|
|
||||||
best = cd;
|
|
||||||
nearest = wood;
|
|
||||||
if (cd <= 1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nearest != null && best > worstGap) {
|
|
||||||
worstGap = best;
|
|
||||||
worstLeaf = leaf;
|
|
||||||
worstWood = nearest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (worstLeaf == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
growTendril(canvas, worstWood, worstLeaf);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void growTendril(TreeBlockCanvas canvas, TreeBlockCanvas.Vec from, TreeBlockCanvas.Vec to) {
|
private static List<TreeBlockCanvas.Vec> unsupportedLeaves(TreeBlockCanvas canvas) {
|
||||||
|
Map<TreeBlockCanvas.Vec, Integer> distance = TreePlausibility.computeDistances(canvas.getTrunk(), canvas.getLeaf());
|
||||||
|
List<TreeBlockCanvas.Vec> unsupported = new ArrayList<>();
|
||||||
|
for (TreeBlockCanvas.Vec leaf : canvas.getLeaf()) {
|
||||||
|
Integer d = distance.get(leaf);
|
||||||
|
if (d == null || d > LEGAL_DISTANCE) {
|
||||||
|
unsupported.add(leaf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unsupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TreeBlockCanvas.Vec farthestFromWood(List<TreeBlockCanvas.Vec> leaves, Map<TreeBlockCanvas.Vec, Nearest> nearestWoodByLeaf) {
|
||||||
|
TreeBlockCanvas.Vec farthest = null;
|
||||||
|
int farthestGap = -1;
|
||||||
|
for (TreeBlockCanvas.Vec leaf : leaves) {
|
||||||
|
int gap = nearestWoodByLeaf.get(leaf).distance();
|
||||||
|
if (gap > farthestGap) {
|
||||||
|
farthestGap = gap;
|
||||||
|
farthest = leaf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return farthest;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void moveTowardNewWood(Map<TreeBlockCanvas.Vec, Nearest> nearestWoodByLeaf, List<TreeBlockCanvas.Vec> leaves,
|
||||||
|
List<TreeBlockCanvas.Vec> newWood) {
|
||||||
|
for (TreeBlockCanvas.Vec leaf : leaves) {
|
||||||
|
Nearest nearest = nearestWoodByLeaf.get(leaf);
|
||||||
|
for (TreeBlockCanvas.Vec wood : newWood) {
|
||||||
|
int distance = chebyshev(leaf, wood);
|
||||||
|
if (distance < nearest.distance()) {
|
||||||
|
nearest = new Nearest(wood, distance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nearestWoodByLeaf.put(leaf, nearest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<TreeBlockCanvas.Vec> growTendril(TreeBlockCanvas canvas, TreeBlockCanvas.Vec from, TreeBlockCanvas.Vec to) {
|
||||||
int dx = to.x() - from.x();
|
int dx = to.x() - from.x();
|
||||||
int dy = to.y() - from.y();
|
int dy = to.y() - from.y();
|
||||||
int dz = to.z() - from.z();
|
int dz = to.z() - from.z();
|
||||||
@@ -75,16 +97,127 @@ public final class TreeSupport {
|
|||||||
int steps = Math.max(1, length - REACH_GAP);
|
int steps = Math.max(1, length - REACH_GAP);
|
||||||
TreeBlockCanvas.Axis axis = TreeFunctions.logAxis(dx, dy, dz);
|
TreeBlockCanvas.Axis axis = TreeFunctions.logAxis(dx, dy, dz);
|
||||||
|
|
||||||
|
List<TreeBlockCanvas.Vec> grown = new ArrayList<>(steps);
|
||||||
for (int i = 1; i <= steps; i++) {
|
for (int i = 1; i <= steps; i++) {
|
||||||
double t = i / (double) length;
|
double t = i / (double) length;
|
||||||
int x = from.x() + (int) Math.round(dx * t);
|
int x = from.x() + (int) Math.round(dx * t);
|
||||||
int y = from.y() + (int) Math.round(dy * t);
|
int y = from.y() + (int) Math.round(dy * t);
|
||||||
int z = from.z() + (int) Math.round(dz * t);
|
int z = from.z() + (int) Math.round(dz * t);
|
||||||
canvas.setTrunk(x, y, z, TreeBlockCanvas.Role.TRUNK, axis);
|
canvas.setTrunk(x, y, z, TreeBlockCanvas.Role.TRUNK, axis);
|
||||||
|
grown.add(new TreeBlockCanvas.Vec(x, y, z));
|
||||||
}
|
}
|
||||||
|
return grown;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int chebyshev(TreeBlockCanvas.Vec a, TreeBlockCanvas.Vec b) {
|
private static int chebyshev(TreeBlockCanvas.Vec a, TreeBlockCanvas.Vec b) {
|
||||||
return Math.max(Math.max(Math.abs(a.x() - b.x()), Math.abs(a.y() - b.y())), Math.abs(a.z() - b.z()));
|
return Math.max(Math.max(Math.abs(a.x() - b.x()), Math.abs(a.y() - b.y())), Math.abs(a.z() - b.z()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the closest wood block to a leaf, by the largest of the x, y and z gaps, searching outward through a coarse
|
||||||
|
* grid of wood one shell of buckets at a time. Ties go to the wood that comes first in the trunk, as a plain scan
|
||||||
|
* would pick.
|
||||||
|
*/
|
||||||
|
private static final class NearestWood {
|
||||||
|
private static final int BUCKET_SIZE_BLOCKS = 8;
|
||||||
|
|
||||||
|
private final Map<Long, List<Integer>> woodIndicesByBucket = new HashMap<>();
|
||||||
|
private final TreeBlockCanvas.Vec[] wood;
|
||||||
|
private int lowestBucket = Integer.MAX_VALUE;
|
||||||
|
private int highestBucket = Integer.MIN_VALUE;
|
||||||
|
|
||||||
|
private NearestWood(TreeBlockCanvas.Vec[] wood) {
|
||||||
|
this.wood = wood;
|
||||||
|
for (int index = 0; index < wood.length; index++) {
|
||||||
|
TreeBlockCanvas.Vec block = wood[index];
|
||||||
|
woodIndicesByBucket.computeIfAbsent(bucketKey(bucket(block.x()), bucket(block.y()), bucket(block.z())), ignored -> new ArrayList<>()).add(index);
|
||||||
|
for (int coordinate : new int[]{block.x(), block.y(), block.z()}) {
|
||||||
|
lowestBucket = Math.min(lowestBucket, bucket(coordinate));
|
||||||
|
highestBucket = Math.max(highestBucket, bucket(coordinate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static NearestWood of(Set<TreeBlockCanvas.Vec> trunk) {
|
||||||
|
return new NearestWood(trunk.toArray(TreeBlockCanvas.Vec[]::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<TreeBlockCanvas.Vec, Nearest> nearestTo(List<TreeBlockCanvas.Vec> leaves) {
|
||||||
|
Map<TreeBlockCanvas.Vec, Nearest> nearestByLeaf = new HashMap<>();
|
||||||
|
for (TreeBlockCanvas.Vec leaf : leaves) {
|
||||||
|
nearestByLeaf.put(leaf, nearestTo(leaf));
|
||||||
|
}
|
||||||
|
return nearestByLeaf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Nearest nearestTo(TreeBlockCanvas.Vec leaf) {
|
||||||
|
int leafBucketX = bucket(leaf.x());
|
||||||
|
int leafBucketY = bucket(leaf.y());
|
||||||
|
int leafBucketZ = bucket(leaf.z());
|
||||||
|
Search search = new Search(leaf);
|
||||||
|
int lastRing = ringReachingAllWood(leafBucketX, leafBucketY, leafBucketZ);
|
||||||
|
for (int ring = 0; ring <= lastRing; ring++) {
|
||||||
|
for (int dx = -ring; dx <= ring; dx++) {
|
||||||
|
for (int dy = -ring; dy <= ring; dy++) {
|
||||||
|
boolean onShellAlready = Math.abs(dx) == ring || Math.abs(dy) == ring;
|
||||||
|
int dzStep = onShellAlready || ring == 0 ? 1 : 2 * ring;
|
||||||
|
for (int dz = -ring; dz <= ring; dz += dzStep) {
|
||||||
|
search.consider(woodIndicesByBucket.get(bucketKey(leafBucketX + dx, leafBucketY + dy, leafBucketZ + dz)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
boolean fartherRingsCannotBeCloser = search.found() && search.bestDistance <= ring * BUCKET_SIZE_BLOCKS;
|
||||||
|
if (fartherRingsCannotBeCloser) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Nearest(wood[search.bestIndex], search.bestDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ringReachingAllWood(int... leafBuckets) {
|
||||||
|
int ring = 0;
|
||||||
|
for (int leafBucket : leafBuckets) {
|
||||||
|
ring = Math.max(ring, Math.max(Math.abs(leafBucket - lowestBucket), Math.abs(leafBucket - highestBucket)));
|
||||||
|
}
|
||||||
|
return ring;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int bucket(int coordinate) {
|
||||||
|
return Math.floorDiv(coordinate, BUCKET_SIZE_BLOCKS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long bucketKey(int x, int y, int z) {
|
||||||
|
return ((long) x & 0x1FFFFF) | (((long) y & 0x1FFFFF) << 21) | (((long) z & 0x1FFFFF) << 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class Search {
|
||||||
|
private final TreeBlockCanvas.Vec leaf;
|
||||||
|
private int bestDistance = Integer.MAX_VALUE;
|
||||||
|
private int bestIndex = -1;
|
||||||
|
|
||||||
|
private Search(TreeBlockCanvas.Vec leaf) {
|
||||||
|
this.leaf = leaf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void consider(List<Integer> woodIndices) {
|
||||||
|
if (woodIndices == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int index : woodIndices) {
|
||||||
|
int distance = chebyshev(leaf, wood[index]);
|
||||||
|
if (distance < bestDistance || (distance == bestDistance && index < bestIndex)) {
|
||||||
|
bestDistance = distance;
|
||||||
|
bestIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean found() {
|
||||||
|
return bestIndex >= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Nearest(TreeBlockCanvas.Vec wood, int distance) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user