mirror of
https://github.com/Winds-Studio/Leaf.git
synced 2025-12-19 15:09:25 +00:00
Update optimize entity despawn (#454)
* update partial sort in despawn map * inline get pos * cache difficulty * refactor * fix fallback extract * remove test code * cleanup * direct compare * cleanup * cleanup * fix axis and remove bucket * reduce alloc * paper vertical fallback * add 2 dimension * cleanup
This commit is contained in:
@@ -38,15 +38,17 @@ index 105423d248df2647d78e6f1a288e9b41003ad437..06294e4036991a803deefc20e35160c5
|
|||||||
final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = this.simpleRandom; // Paper - optimise random ticking // Leaf - Faster random generator - upcasting
|
final net.minecraft.world.level.levelgen.BitRandomSource simpleRandom = this.simpleRandom; // Paper - optimise random ticking // Leaf - Faster random generator - upcasting
|
||||||
ChunkPos pos = chunk.getPos();
|
ChunkPos pos = chunk.getPos();
|
||||||
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
|
diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java
|
||||||
index e563b636a05a299d860a334987594dbd5c7d3511..cddeee3de328ac736a56caad8af1177e0e6e6ad6 100644
|
index 6938961cae73d13a12f30a6ea46725af0728e11d..c18f6f3f8307ae1d221a38cd1dc2191cdc4e767a 100644
|
||||||
--- a/net/minecraft/world/entity/Entity.java
|
--- a/net/minecraft/world/entity/Entity.java
|
||||||
+++ b/net/minecraft/world/entity/Entity.java
|
+++ b/net/minecraft/world/entity/Entity.java
|
||||||
@@ -5100,6 +5100,11 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
|
@@ -5100,6 +5100,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
|
||||||
public void checkDespawn() {
|
public void checkDespawn() {
|
||||||
}
|
}
|
||||||
|
|
||||||
+ // Leaf start - optimize despawn
|
+ // Leaf start - optimize despawn
|
||||||
+ public void leafCheckDespawn() {
|
+ public void leaf$checkDespawn(org.dreeam.leaf.world.DespawnMap map) {
|
||||||
|
+ }
|
||||||
|
+ public void leaf$checkDespawnFallback(org.dreeam.leaf.world.DespawnMap map) {
|
||||||
+ }
|
+ }
|
||||||
+ // Leaf end - optimize despawn
|
+ // Leaf end - optimize despawn
|
||||||
+
|
+
|
||||||
@@ -54,23 +56,54 @@ index e563b636a05a299d860a334987594dbd5c7d3511..cddeee3de328ac736a56caad8af1177e
|
|||||||
return Leashable.createQuadLeashOffsets(this, 0.0, 0.5, 0.5, 0.0);
|
return Leashable.createQuadLeashOffsets(this, 0.0, 0.5, 0.5, 0.0);
|
||||||
}
|
}
|
||||||
diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java
|
diff --git a/net/minecraft/world/entity/Mob.java b/net/minecraft/world/entity/Mob.java
|
||||||
index 751c8c17c3c2968fad358ae8807eaca54de82a34..28523643d0789c6f472dfa92c3c0a32f0bcfc7b5 100644
|
index 751c8c17c3c2968fad358ae8807eaca54de82a34..2eafd8453a70e536bea8c125296fc49a7e3677bf 100644
|
||||||
--- a/net/minecraft/world/entity/Mob.java
|
--- a/net/minecraft/world/entity/Mob.java
|
||||||
+++ b/net/minecraft/world/entity/Mob.java
|
+++ b/net/minecraft/world/entity/Mob.java
|
||||||
@@ -762,6 +762,22 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
|
@@ -762,6 +762,53 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+ // Leaf start - optimize despawn
|
+ // Leaf start - optimize despawn
|
||||||
+ @Override
|
+ @Override
|
||||||
+ public void leafCheckDespawn() {
|
+ public void leaf$checkDespawn(org.dreeam.leaf.world.DespawnMap map) {
|
||||||
+ if (isRemoved()) {
|
+ if (isRemoved()) {
|
||||||
+ return;
|
+ } else if (map.difficultyIsPeaceful && this.shouldDespawnInPeaceful()) {
|
||||||
|
+ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
|
||||||
|
+ } else if (this.isPersistenceRequired() || this.requiresCustomPersistence()) {
|
||||||
|
+ this.noActionTime = 0;
|
||||||
|
+ } else {
|
||||||
|
+ map.checkDespawn(this);
|
||||||
+ }
|
+ }
|
||||||
+ if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) {
|
+ }
|
||||||
|
+ @Override
|
||||||
|
+ public void leaf$checkDespawnFallback(org.dreeam.leaf.world.DespawnMap map) {
|
||||||
|
+ if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldActuallyDespawnInPeaceful()) { //Paper - allow changing despawnInPeaceful
|
||||||
+ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
|
+ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
|
||||||
+ } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) {
|
+ } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) {
|
||||||
+ ((ServerLevel) level()).despawnMap.checkDespawn(this);
|
+ Entity nearestPlayer = map.checkDespawnFallback(this); // Paper - Affects Spawning API
|
||||||
|
+ if (nearestPlayer != null) {
|
||||||
|
+ // Paper start - Configurable despawn distances
|
||||||
|
+ final io.papermc.paper.configuration.WorldConfiguration.Entities.Spawning.DespawnRangePair despawnRangePair = this.level().paperConfig().entities.spawning.despawnRanges.get(this.getType().getCategory());
|
||||||
|
+ final io.papermc.paper.configuration.type.DespawnRange.Shape shape = this.level().paperConfig().entities.spawning.despawnRangeShape;
|
||||||
|
+ final double dy = Math.abs(nearestPlayer.getY() - this.getY());
|
||||||
|
+ final double dySqr = Mth.square(dy);
|
||||||
|
+ final double dxSqr = Mth.square(nearestPlayer.getX() - this.getX());
|
||||||
|
+ final double dzSqr = Mth.square(nearestPlayer.getZ() - this.getZ());
|
||||||
|
+ final double distanceSquared = dxSqr + dzSqr + dySqr;
|
||||||
|
+ // Despawn if hard/soft limit is exceeded
|
||||||
|
+ if (despawnRangePair.hard().shouldDespawn(shape, dxSqr, dySqr, dzSqr, dy) && this.removeWhenFarAway(distanceSquared)) {
|
||||||
|
+ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ if (despawnRangePair.soft().shouldDespawn(shape, dxSqr, dySqr, dzSqr, dy)) {
|
||||||
|
+ if (this.noActionTime > 600 && this.random.nextInt(800) == 0 && this.removeWhenFarAway(distanceSquared)) {
|
||||||
|
+ this.discard(EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
|
||||||
|
+ }
|
||||||
|
+ } else {
|
||||||
|
+ // Paper end - Configurable despawn distances
|
||||||
|
+ this.noActionTime = 0;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
+ } else {
|
+ } else {
|
||||||
+ this.noActionTime = 0;
|
+ this.noActionTime = 0;
|
||||||
+ }
|
+ }
|
||||||
@@ -81,16 +114,19 @@ index 751c8c17c3c2968fad358ae8807eaca54de82a34..28523643d0789c6f472dfa92c3c0a32f
|
|||||||
protected final void serverAiStep() {
|
protected final void serverAiStep() {
|
||||||
this.noActionTime++;
|
this.noActionTime++;
|
||||||
diff --git a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
|
diff --git a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
|
||||||
index de09a91b89661118e460842453e33f383ea08a94..e9fc2bf959949a8589a9ab87e00c85ffbe598a83 100644
|
index de09a91b89661118e460842453e33f383ea08a94..e3cdff55261b2ff2c3d1cb1cf46b633a340458c9 100644
|
||||||
--- a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
|
--- a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
|
||||||
+++ b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
|
+++ b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
|
||||||
@@ -956,6 +956,12 @@ public class EnderDragon extends Mob implements Enemy {
|
@@ -956,6 +956,15 @@ public class EnderDragon extends Mob implements Enemy {
|
||||||
public void checkDespawn() {
|
public void checkDespawn() {
|
||||||
}
|
}
|
||||||
|
|
||||||
+ // Leaf start - optimize despawn
|
+ // Leaf start - optimize despawn
|
||||||
+ @Override
|
+ @Override
|
||||||
+ public void leafCheckDespawn() {
|
+ public void leaf$checkDespawn(org.dreeam.leaf.world.DespawnMap map) {
|
||||||
|
+ }
|
||||||
|
+ @Override
|
||||||
|
+ public void leaf$checkDespawnFallback(org.dreeam.leaf.world.DespawnMap map) {
|
||||||
+ }
|
+ }
|
||||||
+ // Leaf end - optimize despawn
|
+ // Leaf end - optimize despawn
|
||||||
+
|
+
|
||||||
@@ -98,21 +134,32 @@ index de09a91b89661118e460842453e33f383ea08a94..e9fc2bf959949a8589a9ab87e00c85ff
|
|||||||
return this.subEntities;
|
return this.subEntities;
|
||||||
}
|
}
|
||||||
diff --git a/net/minecraft/world/entity/boss/wither/WitherBoss.java b/net/minecraft/world/entity/boss/wither/WitherBoss.java
|
diff --git a/net/minecraft/world/entity/boss/wither/WitherBoss.java b/net/minecraft/world/entity/boss/wither/WitherBoss.java
|
||||||
index e3c34d5f00ce64b3c08b10cfcb3dd14ebe8c1977..d8336fc8852b9939f341aedd17403913c7dadcf9 100644
|
index e3c34d5f00ce64b3c08b10cfcb3dd14ebe8c1977..3171868523bc9b767cb9da5f58c43890fc8214da 100644
|
||||||
--- a/net/minecraft/world/entity/boss/wither/WitherBoss.java
|
--- a/net/minecraft/world/entity/boss/wither/WitherBoss.java
|
||||||
+++ b/net/minecraft/world/entity/boss/wither/WitherBoss.java
|
+++ b/net/minecraft/world/entity/boss/wither/WitherBoss.java
|
||||||
@@ -695,6 +695,21 @@ public class WitherBoss extends Monster implements RangedAttackMob {
|
@@ -695,6 +695,32 @@ public class WitherBoss extends Monster implements RangedAttackMob {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+
|
+
|
||||||
+ // Leaf start - optimize despawn
|
+ // Leaf start - optimize despawn
|
||||||
+ @Override
|
+ @Override
|
||||||
+ public void leafCheckDespawn() {
|
+ public void leaf$checkDespawn(org.dreeam.leaf.world.DespawnMap map) {
|
||||||
+ if (isRemoved()) {
|
+ if (isRemoved()) {
|
||||||
+ return;
|
+ return;
|
||||||
+ }
|
+ }
|
||||||
+ if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) {
|
+ if (map.difficultyIsPeaceful && this.shouldDespawnInPeaceful()) {
|
||||||
|
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
|
||||||
|
+ } else {
|
||||||
|
+ this.noActionTime = 0;
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ @Override
|
||||||
|
+ public void leaf$checkDespawnFallback(org.dreeam.leaf.world.DespawnMap map) {
|
||||||
|
+ if (isRemoved()) {
|
||||||
|
+ return;
|
||||||
|
+ }
|
||||||
|
+ if (map.difficultyIsPeaceful && this.shouldDespawnInPeaceful()) {
|
||||||
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
|
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
|
||||||
+ } else {
|
+ } else {
|
||||||
+ this.noActionTime = 0;
|
+ this.noActionTime = 0;
|
||||||
@@ -124,18 +171,24 @@ index e3c34d5f00ce64b3c08b10cfcb3dd14ebe8c1977..d8336fc8852b9939f341aedd17403913
|
|||||||
public boolean addEffect(MobEffectInstance effectInstance, @Nullable Entity entity) {
|
public boolean addEffect(MobEffectInstance effectInstance, @Nullable Entity entity) {
|
||||||
return false;
|
return false;
|
||||||
diff --git a/net/minecraft/world/entity/projectile/ShulkerBullet.java b/net/minecraft/world/entity/projectile/ShulkerBullet.java
|
diff --git a/net/minecraft/world/entity/projectile/ShulkerBullet.java b/net/minecraft/world/entity/projectile/ShulkerBullet.java
|
||||||
index 00154ba80175bcb07b3378f19514fec1700c94e9..46d30c3273d31dbda06275ad098c6c2b9b2eed14 100644
|
index 00154ba80175bcb07b3378f19514fec1700c94e9..ec0a30d76df2a20ceb895b2837b0f24bfe887aba 100644
|
||||||
--- a/net/minecraft/world/entity/projectile/ShulkerBullet.java
|
--- a/net/minecraft/world/entity/projectile/ShulkerBullet.java
|
||||||
+++ b/net/minecraft/world/entity/projectile/ShulkerBullet.java
|
+++ b/net/minecraft/world/entity/projectile/ShulkerBullet.java
|
||||||
@@ -197,6 +197,16 @@ public class ShulkerBullet extends Projectile {
|
@@ -197,6 +197,22 @@ public class ShulkerBullet extends Projectile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+
|
+
|
||||||
+ // Leaf start - optimize despawn
|
+ // Leaf start - optimize despawn
|
||||||
+ @Override
|
+ @Override
|
||||||
+ public void leafCheckDespawn() {
|
+ public void leaf$checkDespawn(org.dreeam.leaf.world.DespawnMap map) {
|
||||||
+ if (!isRemoved() && this.level().getDifficulty() == Difficulty.PEACEFUL) {
|
+ if (!isRemoved() && map.difficultyIsPeaceful) {
|
||||||
|
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ @Override
|
||||||
|
+ public void leaf$checkDespawnFallback(org.dreeam.leaf.world.DespawnMap map) {
|
||||||
|
+ if (!isRemoved() && map.difficultyIsPeaceful) {
|
||||||
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
|
+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DESPAWN); // CraftBukkit - add Bukkit remove cause
|
||||||
+ }
|
+ }
|
||||||
+ }
|
+ }
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ Subject: [PATCH] Only update frozen ticks if changed
|
|||||||
|
|
||||||
|
|
||||||
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
|
diff --git a/net/minecraft/world/entity/LivingEntity.java b/net/minecraft/world/entity/LivingEntity.java
|
||||||
index fdb7e7b00dd9223f8dc0ce174203d6835fc3962e..e3d6e802fdc72afa54e22fb1e2dc6030de33f091 100644
|
index 1799cdc0b6c1e585e7e1eeab3828ea0252ae2097..20727a5abc7fad5a70eb2b98aa3699b054782a3b 100644
|
||||||
--- a/net/minecraft/world/entity/LivingEntity.java
|
--- a/net/minecraft/world/entity/LivingEntity.java
|
||||||
+++ b/net/minecraft/world/entity/LivingEntity.java
|
+++ b/net/minecraft/world/entity/LivingEntity.java
|
||||||
@@ -3715,7 +3715,13 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
|
@@ -3707,7 +3707,13 @@ public abstract class LivingEntity extends Entity implements Attackable, Waypoin
|
||||||
|
|
||||||
if (this.level() instanceof ServerLevel serverLevel) {
|
if (this.level() instanceof ServerLevel serverLevel) {
|
||||||
if ((!this.isInPowderSnow || !this.canFreeze()) && !this.freezeLocked) { // Paper - Freeze Tick Lock API
|
if ((!this.isInPowderSnow || !this.canFreeze()) && !this.freezeLocked) { // Paper - Freeze Tick Lock API
|
||||||
@@ -5,7 +5,7 @@ Subject: [PATCH] optimize despawn
|
|||||||
|
|
||||||
|
|
||||||
diff --git a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java
|
diff --git a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java
|
||||||
index 5fc9ec3e564135013622f839e59ef5a0ddff6f71..012c500c4a5a06be92fa17c141b15ca6547c8483 100644
|
index 5fc9ec3e564135013622f839e59ef5a0ddff6f71..74d0c6ed2d779b8d02870da74379887e3c460361 100644
|
||||||
--- a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java
|
--- a/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java
|
||||||
+++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java
|
+++ b/src/main/java/io/papermc/paper/configuration/WorldConfiguration.java
|
||||||
@@ -179,7 +179,7 @@ public class WorldConfiguration extends ConfigurationPart {
|
@@ -179,7 +179,7 @@ public class WorldConfiguration extends ConfigurationPart {
|
||||||
@@ -13,7 +13,7 @@ index 5fc9ec3e564135013622f839e59ef5a0ddff6f71..012c500c4a5a06be92fa17c141b15ca6
|
|||||||
public Reference2IntMap<MobCategory> spawnLimits = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1)));
|
public Reference2IntMap<MobCategory> spawnLimits = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1)));
|
||||||
@MergeMap
|
@MergeMap
|
||||||
- public Map<MobCategory, DespawnRangePair> despawnRanges = Arrays.stream(MobCategory.values()).collect(Collectors.toMap(Function.identity(), category -> DespawnRangePair.createDefault()));
|
- public Map<MobCategory, DespawnRangePair> despawnRanges = Arrays.stream(MobCategory.values()).collect(Collectors.toMap(Function.identity(), category -> DespawnRangePair.createDefault()));
|
||||||
+ public Map<MobCategory, DespawnRangePair> despawnRanges = new java.util.EnumMap<>(Arrays.stream(MobCategory.values()).collect(Collectors.toMap(Function.identity(), category -> DespawnRangePair.createDefault()))); // Leaf - replace EnumMap optimize despawn
|
+ public Map<MobCategory, DespawnRangePair> despawnRanges = new java.util.EnumMap<>(Arrays.stream(MobCategory.values()).collect(Collectors.toMap(Function.identity(), category -> DespawnRangePair.createDefault()))); // Leaf - optimize despawn - replace with EnumMap
|
||||||
public DespawnRange.Shape despawnRangeShape = DespawnRange.Shape.ELLIPSOID;
|
public DespawnRange.Shape despawnRangeShape = DespawnRange.Shape.ELLIPSOID;
|
||||||
@MergeMap
|
@MergeMap
|
||||||
public Reference2IntMap<MobCategory> ticksPerSpawn = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1)));
|
public Reference2IntMap<MobCategory> ticksPerSpawn = Util.make(new Reference2IntOpenHashMap<>(NaturalSpawner.SPAWNING_CATEGORIES.length), map -> Arrays.stream(NaturalSpawner.SPAWNING_CATEGORIES).forEach(mobCategory -> map.put(mobCategory, -1)));
|
||||||
|
|||||||
@@ -17,9 +17,8 @@ public class OptimizeDespawn extends ConfigModules {
|
|||||||
public void onLoaded() {
|
public void onLoaded() {
|
||||||
enabled = config.getBoolean(getBasePath(), enabled);
|
enabled = config.getBoolean(getBasePath(), enabled);
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
gg.pufferfish.pufferfish.simd.SIMDDetection.initialize();
|
if (!Boolean.getBoolean("Leaf.enableFMA")) {
|
||||||
if (!Boolean.getBoolean("Leaf.enableFMA") || !SIMDDetection.isEnabled()) {
|
LOGGER.info("NOTE: Recommend enabling FMA to work with optimize-mob-despawn.");
|
||||||
LOGGER.info("NOTE: Recommend enabling FMA and Vector API to work with optimize-mob-despawn.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
253
leaf-server/src/main/java/org/dreeam/leaf/util/KDTree2D.java
Normal file
253
leaf-server/src/main/java/org/dreeam/leaf/util/KDTree2D.java
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
package org.dreeam.leaf.util;
|
||||||
|
|
||||||
|
@org.jspecify.annotations.NullMarked
|
||||||
|
public final class KDTree2D {
|
||||||
|
|
||||||
|
private static final boolean FMA = Boolean.getBoolean("Leaf.enableFMA");
|
||||||
|
|
||||||
|
private static final double[] EMPTY_DOUBLES = {};
|
||||||
|
private static final int[] EMPTY_INTS = {};
|
||||||
|
private static final Node[] EMPTY_NODES = {};
|
||||||
|
|
||||||
|
private static final int INITIAL_CAPACITY = 8;
|
||||||
|
|
||||||
|
/// indicate empty on [#search]
|
||||||
|
/// indicate leaf node on [#nrl]
|
||||||
|
private static final int SENTINEL = -1;
|
||||||
|
private Node[] stack = EMPTY_NODES;
|
||||||
|
private int[] search = EMPTY_INTS;
|
||||||
|
/// Right node index for internal or [#SENTINEL] for leaf
|
||||||
|
private int[] nrl = EMPTY_INTS;
|
||||||
|
// split for internal or x coordinate for leaf
|
||||||
|
private double[] nxl = EMPTY_DOUBLES;
|
||||||
|
// y coordinate for leaf
|
||||||
|
private double[] nyl = EMPTY_DOUBLES;
|
||||||
|
// index for leaf
|
||||||
|
private int[] nil = EMPTY_INTS;
|
||||||
|
|
||||||
|
public void build(final double[][] coords, final int[] indices) {
|
||||||
|
if (indices.length == 0 || coords.length != 2) {
|
||||||
|
ensureSearch(0, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int st = 0;
|
||||||
|
ensureConstruct(st);
|
||||||
|
stack[st++] = new Node(SENTINEL, false, 0, indices.length, 0);
|
||||||
|
int nodeLen = 0;
|
||||||
|
while (st != 0) {
|
||||||
|
ensureNode(nodeLen);
|
||||||
|
final Node n = stack[--st];
|
||||||
|
final int curr = nodeLen++;
|
||||||
|
if (n.len() <= 1) {
|
||||||
|
final int p = indices[n.offset()];
|
||||||
|
nrl[curr] = SENTINEL;
|
||||||
|
nxl[curr] = coords[0][p];
|
||||||
|
nyl[curr] = coords[1][p];
|
||||||
|
nil[curr] = p;
|
||||||
|
} else {
|
||||||
|
final int axis = (n.depth()) % 2;
|
||||||
|
final int med = (n.len() - 1) / 2;
|
||||||
|
final int k = n.offset() + med;
|
||||||
|
final double[] coord = coords[axis];
|
||||||
|
PartialSort.nthElement(indices, coord, n.offset(), n.offset() + n.len() - 1, k);
|
||||||
|
|
||||||
|
nxl[curr] = coord[indices[k]];
|
||||||
|
|
||||||
|
ensureConstruct(st);
|
||||||
|
stack[st++] = new Node(curr, false, n.offset() + med + 1, n.len() - med - 1, n.depth() + 1);
|
||||||
|
stack[st++] = new Node(curr, true, n.offset(), med + 1, n.depth() + 1);
|
||||||
|
}
|
||||||
|
if (n.parent() != SENTINEL) {
|
||||||
|
nrl[n.parent()] = n.left() ? SENTINEL : curr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureSearch(indices.length, nodeLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureSearch(final int length, final int nodeLen) {
|
||||||
|
if (search.length < nodeLen + 8) {
|
||||||
|
search = new int[nodeLen + 8];
|
||||||
|
}
|
||||||
|
search[0] = length == 0 ? SENTINEL : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureConstruct(final int st) {
|
||||||
|
if (st != stack.length && st + 1 != stack.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int newLen = stack.length + 2;
|
||||||
|
final Node[] b = new Node[Math.max(INITIAL_CAPACITY, newLen + (newLen >> 1))];
|
||||||
|
System.arraycopy(stack, 0, b, 0, st);
|
||||||
|
stack = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureNode(final int preserve) {
|
||||||
|
int length = preserve + 1;
|
||||||
|
if (length < nrl.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
length += length >> 1;
|
||||||
|
if (length < INITIAL_CAPACITY) {
|
||||||
|
length = INITIAL_CAPACITY;
|
||||||
|
}
|
||||||
|
nrl = it.unimi.dsi.fastutil.ints.IntArrays.forceCapacity(nrl, length, preserve);
|
||||||
|
nxl = it.unimi.dsi.fastutil.doubles.DoubleArrays.forceCapacity(nxl, length, preserve);
|
||||||
|
nyl = it.unimi.dsi.fastutil.doubles.DoubleArrays.forceCapacity(nyl, length, preserve);
|
||||||
|
nil = it.unimi.dsi.fastutil.ints.IntArrays.forceCapacity(nil, length, preserve);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double nearestSqr(final double tx, final double ty, double dist) {
|
||||||
|
final int[] stack = this.search;
|
||||||
|
final int[] nrl = this.nrl;
|
||||||
|
final double[] nxl = this.nxl;
|
||||||
|
final double[] nyl = this.nyl;
|
||||||
|
if (stack.length == 0 || stack[0] == SENTINEL) {
|
||||||
|
return Double.POSITIVE_INFINITY;
|
||||||
|
}
|
||||||
|
int i = 0, j = 0, curr = 0;
|
||||||
|
while (true) {
|
||||||
|
final int right = nrl[j];
|
||||||
|
if (right == SENTINEL) {
|
||||||
|
final double dx = nxl[j] - tx;
|
||||||
|
final double dy = nyl[j] - ty;
|
||||||
|
dist = Math.min(dist, FMA
|
||||||
|
? Math.fma(dy, dy, dx * dx)
|
||||||
|
: dx * dx + dy * dy);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
final int next = ((curr + 1) % 2) << 31;
|
||||||
|
final int left = j + 1;
|
||||||
|
final double delta = (curr == 0 ? tx : ty) - nxl[j];
|
||||||
|
final boolean push = delta * delta < dist;
|
||||||
|
if (delta < 0.0) {
|
||||||
|
if (push) {
|
||||||
|
stack[i++] = right | next;
|
||||||
|
}
|
||||||
|
j = left;
|
||||||
|
} else {
|
||||||
|
if (push) {
|
||||||
|
stack[i++] = left | next;
|
||||||
|
}
|
||||||
|
j = right;
|
||||||
|
}
|
||||||
|
curr = ((curr + 1) % 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (i != 0) {
|
||||||
|
j = stack[--i];
|
||||||
|
final int k = j & 0x7FFF_FFFF;
|
||||||
|
final int right = nrl[k];
|
||||||
|
if (right == SENTINEL) {
|
||||||
|
final double dx = nxl[k] - tx;
|
||||||
|
final double dy = nyl[k] - ty;
|
||||||
|
dist = Math.min(dist, FMA
|
||||||
|
? Math.fma(dy, dy, dx * dx)
|
||||||
|
: dx * dx + dy * dy);
|
||||||
|
} else {
|
||||||
|
final int axis = j >>> 31;
|
||||||
|
final int next = ((axis + 1) % 2) << 31;
|
||||||
|
final int left = (k + 1) | next;
|
||||||
|
final double delta = (axis == 0 ? tx : ty) - nxl[k];
|
||||||
|
final boolean push = delta * delta < dist;
|
||||||
|
if (delta < 0.0) {
|
||||||
|
// near = left, far = right, left first
|
||||||
|
if (push) {
|
||||||
|
stack[i++] = right | next;
|
||||||
|
}
|
||||||
|
stack[i++] = left;
|
||||||
|
} else {
|
||||||
|
// near = right, far = left, right first
|
||||||
|
if (push) {
|
||||||
|
stack[i++] = left;
|
||||||
|
}
|
||||||
|
stack[i++] = right | next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int nearestIdx(final double tx, final double ty, double dist) {
|
||||||
|
final int[] stack = this.search;
|
||||||
|
final int[] nrl = this.nrl;
|
||||||
|
final double[] nxl = this.nxl;
|
||||||
|
final double[] nyl = this.nyl;
|
||||||
|
if (stack.length == 0 || stack[0] == SENTINEL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int i = 0, j = 0, curr = 0, nearest = -1;
|
||||||
|
while (true) {
|
||||||
|
final int right = nrl[j];
|
||||||
|
if (right == SENTINEL) {
|
||||||
|
final double dx = nxl[j] - tx;
|
||||||
|
final double dy = nyl[j] - ty;
|
||||||
|
final double candidate = FMA
|
||||||
|
? Math.fma(dy, dy, dx * dx)
|
||||||
|
: dx * dx + dy * dy;
|
||||||
|
if (candidate < dist) {
|
||||||
|
dist = candidate;
|
||||||
|
nearest = nil[j];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
final int next = ((curr + 1) % 2) << 31;
|
||||||
|
final int left = j + 1;
|
||||||
|
final double delta = (curr == 0 ? tx : ty) - nxl[j];
|
||||||
|
final boolean push = delta * delta < dist;
|
||||||
|
if (delta < 0.0) {
|
||||||
|
if (push) {
|
||||||
|
stack[i++] = right | next;
|
||||||
|
}
|
||||||
|
j = left;
|
||||||
|
} else {
|
||||||
|
if (push) {
|
||||||
|
stack[i++] = left | next;
|
||||||
|
}
|
||||||
|
j = right;
|
||||||
|
}
|
||||||
|
curr = ((curr + 1) % 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (i != 0) {
|
||||||
|
j = stack[--i];
|
||||||
|
final int k = j & 0x7FFF_FFFF;
|
||||||
|
final int right = nrl[k];
|
||||||
|
if (right == SENTINEL) {
|
||||||
|
final double dx = nxl[k] - tx;
|
||||||
|
final double dy = nyl[k] - ty;
|
||||||
|
final double candidate = FMA
|
||||||
|
? Math.fma(dy, dy, dx * dx)
|
||||||
|
: dx * dx + dy * dy;
|
||||||
|
if (candidate < dist) {
|
||||||
|
dist = candidate;
|
||||||
|
nearest = nil[k];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final int axis = j >>> 31;
|
||||||
|
final int next = ((axis + 1) % 2) << 31;
|
||||||
|
final int left = (k + 1) | next;
|
||||||
|
final double delta = (axis == 0 ? tx : ty) - nxl[k];
|
||||||
|
final boolean push = delta * delta < dist;
|
||||||
|
if (delta < 0.0) {
|
||||||
|
// near = left, far = right, left first
|
||||||
|
if (push) {
|
||||||
|
stack[i++] = right | next;
|
||||||
|
}
|
||||||
|
stack[i++] = left;
|
||||||
|
} else {
|
||||||
|
// near = right, far = left, right first
|
||||||
|
if (push) {
|
||||||
|
stack[i++] = left;
|
||||||
|
}
|
||||||
|
stack[i++] = right | next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nearest;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Node(int parent, boolean left, int offset, int len, int depth) {
|
||||||
|
}
|
||||||
|
}
|
||||||
263
leaf-server/src/main/java/org/dreeam/leaf/util/KDTree3D.java
Normal file
263
leaf-server/src/main/java/org/dreeam/leaf/util/KDTree3D.java
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
package org.dreeam.leaf.util;
|
||||||
|
|
||||||
|
@org.jspecify.annotations.NullMarked
|
||||||
|
public final class KDTree3D {
|
||||||
|
|
||||||
|
private static final boolean FMA = Boolean.getBoolean("Leaf.enableFMA");
|
||||||
|
|
||||||
|
private static final double[] EMPTY_DOUBLES = {};
|
||||||
|
private static final int[] EMPTY_INTS = {};
|
||||||
|
private static final Node[] EMPTY_NODES = {};
|
||||||
|
|
||||||
|
private static final int INITIAL_CAPACITY = 8;
|
||||||
|
|
||||||
|
/// indicate empty on [#search]
|
||||||
|
/// indicate leaf node on [#nrl]
|
||||||
|
private static final int SENTINEL = -1;
|
||||||
|
private Node[] stack = EMPTY_NODES;
|
||||||
|
private int[] search = EMPTY_INTS;
|
||||||
|
/// Right node index for internal or [#SENTINEL] for leaf
|
||||||
|
private int[] nrl = EMPTY_INTS;
|
||||||
|
// split for internal or x coordinate for leaf
|
||||||
|
private double[] nxl = EMPTY_DOUBLES;
|
||||||
|
// y coordinate for leaf
|
||||||
|
private double[] nyl = EMPTY_DOUBLES;
|
||||||
|
// z coordinate for leaf
|
||||||
|
private double[] nzl = EMPTY_DOUBLES;
|
||||||
|
// index for leaf
|
||||||
|
private int[] nil = EMPTY_INTS;
|
||||||
|
|
||||||
|
public void build(final double[][] coords, final int[] indices) {
|
||||||
|
if (indices.length == 0 || coords.length != 3) {
|
||||||
|
ensureSearch(0, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int st = 0;
|
||||||
|
ensureConstruct(st);
|
||||||
|
stack[st++] = new Node(SENTINEL, false, 0, indices.length, 0);
|
||||||
|
int nodeLen = 0;
|
||||||
|
while (st != 0) {
|
||||||
|
ensureNode(nodeLen);
|
||||||
|
final Node n = stack[--st];
|
||||||
|
final int curr = nodeLen++;
|
||||||
|
if (n.len() <= 1) {
|
||||||
|
final int p = indices[n.offset()];
|
||||||
|
nrl[curr] = SENTINEL;
|
||||||
|
nxl[curr] = coords[0][p];
|
||||||
|
nyl[curr] = coords[1][p];
|
||||||
|
nzl[curr] = coords[2][p];
|
||||||
|
nil[curr] = p;
|
||||||
|
} else {
|
||||||
|
final int axis = (n.depth()) % 3;
|
||||||
|
final int med = (n.len() - 1) / 2;
|
||||||
|
final int k = n.offset() + med;
|
||||||
|
final double[] coord = coords[axis];
|
||||||
|
PartialSort.nthElement(indices, coord, n.offset(), n.offset() + n.len() - 1, k);
|
||||||
|
|
||||||
|
nxl[curr] = coord[indices[k]];
|
||||||
|
|
||||||
|
ensureConstruct(st);
|
||||||
|
stack[st++] = new Node(curr, false, n.offset() + med + 1, n.len() - med - 1, n.depth() + 1);
|
||||||
|
stack[st++] = new Node(curr, true, n.offset(), med + 1, n.depth() + 1);
|
||||||
|
}
|
||||||
|
if (n.parent() != SENTINEL) {
|
||||||
|
nrl[n.parent()] = n.left() ? SENTINEL : curr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureSearch(indices.length, nodeLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureSearch(final int length, final int nodeLen) {
|
||||||
|
if (search.length < nodeLen + 8) {
|
||||||
|
search = new int[nodeLen + 8];
|
||||||
|
}
|
||||||
|
search[0] = length == 0 ? SENTINEL : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureConstruct(final int st) {
|
||||||
|
if (st != stack.length && st + 1 != stack.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int newLen = stack.length + 2;
|
||||||
|
final Node[] b = new Node[Math.max(INITIAL_CAPACITY, newLen + (newLen >> 1))];
|
||||||
|
System.arraycopy(stack, 0, b, 0, st);
|
||||||
|
stack = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureNode(final int preserve) {
|
||||||
|
int length = preserve + 1;
|
||||||
|
if (length < nrl.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
length += length >> 1;
|
||||||
|
if (length < INITIAL_CAPACITY) {
|
||||||
|
length = INITIAL_CAPACITY;
|
||||||
|
}
|
||||||
|
nrl = it.unimi.dsi.fastutil.ints.IntArrays.forceCapacity(nrl, length, preserve);
|
||||||
|
nxl = it.unimi.dsi.fastutil.doubles.DoubleArrays.forceCapacity(nxl, length, preserve);
|
||||||
|
nyl = it.unimi.dsi.fastutil.doubles.DoubleArrays.forceCapacity(nyl, length, preserve);
|
||||||
|
nzl = it.unimi.dsi.fastutil.doubles.DoubleArrays.forceCapacity(nzl, length, preserve);
|
||||||
|
nil = it.unimi.dsi.fastutil.ints.IntArrays.forceCapacity(nil, length, preserve);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double nearestSqr(final double tx, final double ty, final double tz, double dist) {
|
||||||
|
final int[] stack = this.search;
|
||||||
|
final int[] nrl = this.nrl;
|
||||||
|
final double[] nxl = this.nxl;
|
||||||
|
final double[] nyl = this.nyl;
|
||||||
|
final double[] nzl = this.nzl;
|
||||||
|
if (stack.length == 0 || stack[0] == SENTINEL) {
|
||||||
|
return Double.POSITIVE_INFINITY;
|
||||||
|
}
|
||||||
|
int i = 0, j = 0, curr = 0;
|
||||||
|
while (true) {
|
||||||
|
final int right = nrl[j];
|
||||||
|
if (right == SENTINEL) {
|
||||||
|
final double dx = nxl[j] - tx;
|
||||||
|
final double dy = nyl[j] - ty;
|
||||||
|
final double dz = nzl[j] - tz;
|
||||||
|
dist = Math.min(dist, FMA
|
||||||
|
? Math.fma(dz, dz, Math.fma(dy, dy, dx * dx))
|
||||||
|
: dx * dx + dy * dy + dz * dz);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
final int next = ((curr + 1) % 3) << 30;
|
||||||
|
final int left = j + 1;
|
||||||
|
final double delta = (curr == 0 ? tx : curr == 1 ? ty : tz) - nxl[j];
|
||||||
|
final boolean push = delta * delta < dist;
|
||||||
|
if (delta < 0.0) {
|
||||||
|
if (push) {
|
||||||
|
stack[i++] = right | next;
|
||||||
|
}
|
||||||
|
j = left;
|
||||||
|
} else {
|
||||||
|
if (push) {
|
||||||
|
stack[i++] = left | next;
|
||||||
|
}
|
||||||
|
j = right;
|
||||||
|
}
|
||||||
|
curr = ((curr + 1) % 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (i != 0) {
|
||||||
|
j = stack[--i];
|
||||||
|
final int k = j & 0x3FFF_FFFF;
|
||||||
|
final int right = nrl[k];
|
||||||
|
if (right == SENTINEL) {
|
||||||
|
final double dx = nxl[k] - tx;
|
||||||
|
final double dy = nyl[k] - ty;
|
||||||
|
final double dz = nzl[k] - tz;
|
||||||
|
dist = Math.min(dist, FMA
|
||||||
|
? Math.fma(dz, dz, Math.fma(dy, dy, dx * dx))
|
||||||
|
: dx * dx + dy * dy + dz * dz);
|
||||||
|
} else {
|
||||||
|
final int axis = j >>> 30;
|
||||||
|
final int next = ((axis + 1) % 3) << 30;
|
||||||
|
final int left = (k + 1) | next;
|
||||||
|
final double delta = (axis == 0 ? tx : axis == 1 ? ty : tz) - nxl[k];
|
||||||
|
final boolean push = delta * delta < dist;
|
||||||
|
if (delta < 0.0) {
|
||||||
|
// near = left, far = right, left first
|
||||||
|
if (push) {
|
||||||
|
stack[i++] = right | next;
|
||||||
|
}
|
||||||
|
stack[i++] = left;
|
||||||
|
} else {
|
||||||
|
// near = right, far = left, right first
|
||||||
|
if (push) {
|
||||||
|
stack[i++] = left;
|
||||||
|
}
|
||||||
|
stack[i++] = right | next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int nearestIdx(final double tx, final double ty, final double tz, double dist) {
|
||||||
|
final int[] stack = this.search;
|
||||||
|
final int[] nrl = this.nrl;
|
||||||
|
final double[] nxl = this.nxl;
|
||||||
|
final double[] nyl = this.nyl;
|
||||||
|
final double[] nzl = this.nzl;
|
||||||
|
if (stack.length == 0 || stack[0] == SENTINEL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int i = 0, j = 0, curr = 0, nearest = -1;
|
||||||
|
while (true) {
|
||||||
|
final int right = nrl[j];
|
||||||
|
if (right == SENTINEL) {
|
||||||
|
final double dx = nxl[j] - tx;
|
||||||
|
final double dy = nyl[j] - ty;
|
||||||
|
final double dz = nzl[j] - tz;
|
||||||
|
final double candidate = FMA
|
||||||
|
? Math.fma(dz, dz, Math.fma(dy, dy, dx * dx))
|
||||||
|
: dx * dx + dy * dy + dz * dz;
|
||||||
|
if (candidate < dist) {
|
||||||
|
dist = candidate;
|
||||||
|
nearest = nil[j];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
final int next = ((curr + 1) % 3) << 30;
|
||||||
|
final int left = j + 1;
|
||||||
|
final double delta = (curr == 0 ? tx : curr == 1 ? ty : tz) - nxl[j];
|
||||||
|
final boolean push = delta * delta < dist;
|
||||||
|
if (delta < 0.0) {
|
||||||
|
if (push) {
|
||||||
|
stack[i++] = right | next;
|
||||||
|
}
|
||||||
|
j = left;
|
||||||
|
} else {
|
||||||
|
if (push) {
|
||||||
|
stack[i++] = left | next;
|
||||||
|
}
|
||||||
|
j = right;
|
||||||
|
}
|
||||||
|
curr = ((curr + 1) % 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (i != 0) {
|
||||||
|
j = stack[--i];
|
||||||
|
final int k = j & 0x3FFF_FFFF;
|
||||||
|
final int right = nrl[k];
|
||||||
|
if (right == SENTINEL) {
|
||||||
|
final double dx = nxl[k] - tx;
|
||||||
|
final double dy = nyl[k] - ty;
|
||||||
|
final double dz = nzl[k] - tz;
|
||||||
|
final double candidate = FMA
|
||||||
|
? Math.fma(dz, dz, Math.fma(dy, dy, dx * dx))
|
||||||
|
: dx * dx + dy * dy + dz * dz;
|
||||||
|
if (candidate < dist) {
|
||||||
|
dist = candidate;
|
||||||
|
nearest = nil[k];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final int axis = j >>> 30;
|
||||||
|
final int next = ((axis + 1) % 3) << 30;
|
||||||
|
final int left = (k + 1) | next;
|
||||||
|
final double delta = (axis == 0 ? tx : axis == 1 ? ty : tz) - nxl[k];
|
||||||
|
final boolean push = delta * delta < dist;
|
||||||
|
if (delta < 0.0) {
|
||||||
|
// near = left, far = right, left first
|
||||||
|
if (push) {
|
||||||
|
stack[i++] = right | next;
|
||||||
|
}
|
||||||
|
stack[i++] = left;
|
||||||
|
} else {
|
||||||
|
// near = right, far = left, right first
|
||||||
|
if (push) {
|
||||||
|
stack[i++] = left;
|
||||||
|
}
|
||||||
|
stack[i++] = right | next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nearest;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Node(int parent, boolean left, int offset, int len, int depth) {
|
||||||
|
}
|
||||||
|
}
|
||||||
132
leaf-server/src/main/java/org/dreeam/leaf/util/PartialSort.java
Normal file
132
leaf-server/src/main/java/org/dreeam/leaf/util/PartialSort.java
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package org.dreeam.leaf.util;
|
||||||
|
|
||||||
|
public final class PartialSort {
|
||||||
|
|
||||||
|
private static final int INSERTION_SORT_THRESHOLD = 12;
|
||||||
|
private static final int PSEUDO_MEDIAN_REC_THRESHOLD = 64;
|
||||||
|
|
||||||
|
public static void nthElement(final int[] indices, final double[] coord, int left, int right, final int k) {
|
||||||
|
while (left < right) {
|
||||||
|
final int len = right + 1 - left;
|
||||||
|
if (len <= INSERTION_SORT_THRESHOLD) {
|
||||||
|
insertionSort(indices, coord, left, right);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectPivot(indices, coord, left, len);
|
||||||
|
final int p = partition(indices, coord, left, right + 1, coord[indices[left]]);
|
||||||
|
if (k < p) {
|
||||||
|
right = p - 1;
|
||||||
|
} else if (k != p) {
|
||||||
|
left = p + 1;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void insertionSort(final int[] indices, final double[] coord, final int left, final int right) {
|
||||||
|
for (int i = left + 1; i <= right; i++) {
|
||||||
|
final int key = indices[i];
|
||||||
|
final double val = coord[key];
|
||||||
|
int j = i - 1;
|
||||||
|
|
||||||
|
while (j >= left && coord[indices[j]] > val) {
|
||||||
|
indices[j + 1] = indices[j];
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
indices[j + 1] = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void selectPivot(final int[] indices, final double[] coord, final int a, final int len) {
|
||||||
|
final int lenDiv8 = len / 8;
|
||||||
|
final int b = a + lenDiv8 * 4;
|
||||||
|
final int c = a + lenDiv8 * 7;
|
||||||
|
if (len < PSEUDO_MEDIAN_REC_THRESHOLD) {
|
||||||
|
final double va = coord[indices[a]];
|
||||||
|
final double vb = coord[indices[b]];
|
||||||
|
final double vc = coord[indices[c]];
|
||||||
|
final boolean m = va < vb;
|
||||||
|
final boolean n = va < vc;
|
||||||
|
final int pivotIdx = m == n ? (vb < vc ^ m) ? c : b : a;
|
||||||
|
final int tmp = indices[pivotIdx];
|
||||||
|
indices[pivotIdx] = indices[a];
|
||||||
|
indices[a] = tmp;
|
||||||
|
} else {
|
||||||
|
final int pivotIdx = med3Rec(indices, coord, a, b, c, lenDiv8);
|
||||||
|
final int tmp = indices[pivotIdx];
|
||||||
|
indices[pivotIdx] = indices[a];
|
||||||
|
indices[a] = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int partition(final int[] indices, final double[] x, final int start, final int end, final double pivot) {
|
||||||
|
if (start >= end) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lt = start;
|
||||||
|
int rt = start + 1;
|
||||||
|
int gap = start;
|
||||||
|
final int base = indices[start];
|
||||||
|
|
||||||
|
final int unrollEnd = end - 1;
|
||||||
|
while (rt < unrollEnd) {
|
||||||
|
int rtVal = indices[rt];
|
||||||
|
|
||||||
|
boolean rightIsLt = x[rtVal] < pivot;
|
||||||
|
|
||||||
|
indices[gap] = indices[lt];
|
||||||
|
indices[lt] = rtVal;
|
||||||
|
|
||||||
|
gap = rt;
|
||||||
|
lt += rightIsLt ? 1 : 0;
|
||||||
|
rt++;
|
||||||
|
rtVal = indices[rt];
|
||||||
|
|
||||||
|
rightIsLt = x[rtVal] < pivot;
|
||||||
|
|
||||||
|
indices[gap] = indices[lt];
|
||||||
|
indices[lt] = rtVal;
|
||||||
|
|
||||||
|
gap = rt;
|
||||||
|
lt += rightIsLt ? 1 : 0;
|
||||||
|
rt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
final boolean isDone = rt == end;
|
||||||
|
final int rtVal = isDone ? base : indices[rt];
|
||||||
|
|
||||||
|
final boolean rightIsLt = x[rtVal] < pivot;
|
||||||
|
|
||||||
|
indices[gap] = indices[lt];
|
||||||
|
indices[lt] = rtVal;
|
||||||
|
|
||||||
|
gap = rt;
|
||||||
|
lt += rightIsLt ? 1 : 0;
|
||||||
|
rt++;
|
||||||
|
|
||||||
|
if (isDone) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int med3Rec(final int[] indices, final double[] x, int a, int b, int c, final int n) {
|
||||||
|
if (n * 8 >= PSEUDO_MEDIAN_REC_THRESHOLD) {
|
||||||
|
final int n8 = n / 8;
|
||||||
|
a = med3Rec(indices, x, a, a + n8 * 4, a + n8 * 7, n8);
|
||||||
|
b = med3Rec(indices, x, b, b + n8 * 4, b + n8 * 7, n8);
|
||||||
|
c = med3Rec(indices, x, c, c + n8 * 4, c + n8 * 7, n8);
|
||||||
|
}
|
||||||
|
final double va = x[indices[a]];
|
||||||
|
final double vb = x[indices[b]];
|
||||||
|
final double vc = x[indices[c]];
|
||||||
|
final boolean m = va < vb;
|
||||||
|
final boolean n1 = va < vc;
|
||||||
|
return m == n1 ? (vb < vc ^ m) ? c : b : a;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,72 +1,38 @@
|
|||||||
package org.dreeam.leaf.world;
|
package org.dreeam.leaf.world;
|
||||||
|
|
||||||
import gg.pufferfish.pufferfish.simd.SIMDDetection;
|
|
||||||
import io.papermc.paper.configuration.WorldConfiguration;
|
import io.papermc.paper.configuration.WorldConfiguration;
|
||||||
import it.unimi.dsi.fastutil.doubles.DoubleArrays;
|
|
||||||
import it.unimi.dsi.fastutil.longs.LongArrays;
|
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.world.Difficulty;
|
||||||
import net.minecraft.world.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
import net.minecraft.world.entity.EntitySelector;
|
import net.minecraft.world.entity.EntitySelector;
|
||||||
import net.minecraft.world.entity.Mob;
|
import net.minecraft.world.entity.Mob;
|
||||||
import net.minecraft.world.entity.MobCategory;
|
import net.minecraft.world.entity.MobCategory;
|
||||||
import net.minecraft.world.level.entity.EntityTickList;
|
import net.minecraft.world.level.entity.EntityTickList;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
import org.bukkit.event.entity.EntityRemoveEvent;
|
import org.bukkit.event.entity.EntityRemoveEvent;
|
||||||
|
import org.dreeam.leaf.util.KDTree3D;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.OptionalInt;
|
import java.util.OptionalInt;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public final class DespawnMap {
|
public final class DespawnMap implements Consumer<Entity> {
|
||||||
private static final ServerPlayer[] EMPTY_PLAYERS = {};
|
private static final ServerPlayer[] EMPTY_PLAYERS = {};
|
||||||
private static final double[] EMPTY_DOUBLES = {};
|
private final KDTree3D tree = new KDTree3D();
|
||||||
private static final long[] EMPTY_LONGS = {};
|
private static final MobCategory[] CATEGORIES = MobCategory.values();
|
||||||
private static final int[] EMPTY_INTS = {};
|
private final double[] hard = new double[CATEGORIES.length];
|
||||||
static final boolean FMA = Boolean.getBoolean("Leaf.enableFMA");
|
private final double[] sort = new double[CATEGORIES.length];
|
||||||
private static final boolean SIMD = SIMDDetection.isEnabled();
|
private final boolean fallback;
|
||||||
private static final int LEAF_THRESHOLD = SIMD ? DespawnVectorAPI.DOUBLE_VECTOR_LENGTH : 4;
|
public boolean difficultyIsPeaceful = true;
|
||||||
private static final int INITIAL_CAP = 8;
|
private ServerPlayer[] players = EMPTY_PLAYERS;
|
||||||
private static final int INSERTION_SORT = 16;
|
|
||||||
static final long LEAF = -1L;
|
|
||||||
static final long AXIS_X = 0L;
|
|
||||||
static final long AXIS_Y = 1L;
|
|
||||||
static final long AXIS_Z = 2L;
|
|
||||||
static final long LEFT_MASK = 0xfffffffcL;
|
|
||||||
static final long RIGHT_MASK = 0x3fffffff00000000L;
|
|
||||||
static final long AXIS_MASK = 0b11L;
|
|
||||||
static final long SIGN_BIT = 0x8000_0000_0000_0000L;
|
|
||||||
|
|
||||||
/// Stack for tree construction
|
|
||||||
private final Stack stack = new Stack(INITIAL_CAP);
|
|
||||||
/// Stack for tree traversal
|
|
||||||
private int[] search = EMPTY_INTS;
|
|
||||||
|
|
||||||
private int nodeLen = 0;
|
|
||||||
private int bucketLen = 0;
|
|
||||||
|
|
||||||
/// Node coordinate for each internal node
|
|
||||||
private double[] nsl = EMPTY_DOUBLES;
|
|
||||||
/// Offsets(32) Lengths(32) for each player list of leaf nodes
|
|
||||||
private long[] nbl = EMPTY_LONGS;
|
|
||||||
/// Left(30) Right(30) Axis(2) for each internal node
|
|
||||||
private long[] nll = EMPTY_LONGS;
|
|
||||||
/// Nested player X coordinates of leaf nodes
|
|
||||||
private double[] bxl = EMPTY_DOUBLES;
|
|
||||||
/// Nested player Y coordinates of leaf nodes
|
|
||||||
private double[] byl = EMPTY_DOUBLES;
|
|
||||||
/// Nested player Z coordinates of leaf nodes
|
|
||||||
private double[] bzl = EMPTY_DOUBLES;
|
|
||||||
|
|
||||||
private final double[] hard;
|
|
||||||
private final double[] sort;
|
|
||||||
|
|
||||||
public DespawnMap(WorldConfiguration worldConfiguration) {
|
public DespawnMap(WorldConfiguration worldConfiguration) {
|
||||||
MobCategory[] caps = MobCategory.values();
|
for (int i = 0; i < CATEGORIES.length; i++) {
|
||||||
hard = new double[caps.length];
|
sort[i] = CATEGORIES[i].getNoDespawnDistance();
|
||||||
sort = new double[caps.length];
|
hard[i] = CATEGORIES[i].getDespawnDistance();
|
||||||
for (int i = 0; i < caps.length; i++) {
|
|
||||||
sort[i] = caps[i].getNoDespawnDistance();
|
|
||||||
hard[i] = caps[i].getDespawnDistance();
|
|
||||||
}
|
}
|
||||||
|
boolean fallback = false;
|
||||||
for (Map.Entry<MobCategory, WorldConfiguration.Entities.Spawning.DespawnRangePair> e : worldConfiguration.entities.spawning.despawnRanges.entrySet()) {
|
for (Map.Entry<MobCategory, WorldConfiguration.Entities.Spawning.DespawnRangePair> e : worldConfiguration.entities.spawning.despawnRanges.entrySet()) {
|
||||||
OptionalInt a = e.getValue().soft().verticalLimit.value();
|
OptionalInt a = e.getValue().soft().verticalLimit.value();
|
||||||
OptionalInt b = e.getValue().soft().horizontalLimit.value();
|
OptionalInt b = e.getValue().soft().horizontalLimit.value();
|
||||||
@@ -74,12 +40,16 @@ public final class DespawnMap {
|
|||||||
OptionalInt d = e.getValue().hard().horizontalLimit.value();
|
OptionalInt d = e.getValue().hard().horizontalLimit.value();
|
||||||
if (a.isPresent() && b.isPresent() && a.getAsInt() == b.getAsInt()) {
|
if (a.isPresent() && b.isPresent() && a.getAsInt() == b.getAsInt()) {
|
||||||
sort[e.getKey().ordinal()] = a.getAsInt();
|
sort[e.getKey().ordinal()] = a.getAsInt();
|
||||||
|
} else if (a.isPresent() || b.isPresent()) {
|
||||||
|
fallback = true;
|
||||||
}
|
}
|
||||||
if (c.isPresent() && d.isPresent() && c.getAsInt() == d.getAsInt()) {
|
if (c.isPresent() && d.isPresent() && c.getAsInt() == d.getAsInt()) {
|
||||||
hard[e.getKey().ordinal()] = c.getAsInt();
|
hard[e.getKey().ordinal()] = c.getAsInt();
|
||||||
|
} else if (c.isPresent() || d.isPresent()) {
|
||||||
|
fallback = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i < caps.length; i++) {
|
for (int i = 0; i < CATEGORIES.length; i++) {
|
||||||
if (sort[i] > 0.0) {
|
if (sort[i] > 0.0) {
|
||||||
sort[i] = sort[i] * sort[i];
|
sort[i] = sort[i] * sort[i];
|
||||||
}
|
}
|
||||||
@@ -87,274 +57,49 @@ public final class DespawnMap {
|
|||||||
hard[i] = hard[i] * hard[i];
|
hard[i] = hard[i] * hard[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.fallback = fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void build(double[] coordX, double[] coordY, double[] coordZ) {
|
public void tick(final ServerLevel world, final EntityTickList entityTickList) {
|
||||||
final double[][] map = {coordX, coordY, coordZ};
|
players = world.players().toArray(EMPTY_PLAYERS);
|
||||||
final int[] data = new int[coordX.length];
|
final double[] pxl = new double[players.length];
|
||||||
for (int i = 0; i < coordX.length; i++) {
|
final double[] pyl = new double[players.length];
|
||||||
data[i] = i;
|
final double[] pzl = new double[players.length];
|
||||||
}
|
|
||||||
stack.push(new Node(-1, false, 0, data.length, 0));
|
|
||||||
while (!stack.isEmpty()) {
|
|
||||||
grow();
|
|
||||||
|
|
||||||
final Node n = stack.pop();
|
|
||||||
final int depth = n.depth;
|
|
||||||
final int offset = n.offset;
|
|
||||||
final int len = n.length;
|
|
||||||
final int curr = nodeLen++;
|
|
||||||
if (len <= LEAF_THRESHOLD) {
|
|
||||||
|
|
||||||
nll[curr] = LEAF;
|
|
||||||
nbl[curr] = (long) bucketLen << 32 | (long) len;
|
|
||||||
|
|
||||||
growBucket(len);
|
|
||||||
for (int i = offset, end = offset + len; i < end; i++) {
|
|
||||||
bxl[bucketLen] = coordX[data[i]];
|
|
||||||
byl[bucketLen] = coordY[data[i]];
|
|
||||||
bzl[bucketLen] = coordZ[data[i]];
|
|
||||||
bucketLen++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
|
|
||||||
final int axis = depth % 3 == 0 ? (int) AXIS_X : depth % 3 == 1 ? (int) AXIS_Z : (int) AXIS_Y;
|
|
||||||
final int median = (len - 1) / 2;
|
|
||||||
quickSelect(data, map[axis], offset, offset + len - 1, offset + median);
|
|
||||||
final int pivot = data[offset + median];
|
|
||||||
nsl[curr] = axis == AXIS_X ? coordX[pivot] : axis == AXIS_Y ? coordY[pivot] : coordZ[pivot];
|
|
||||||
nll[curr] = LEFT_MASK | RIGHT_MASK | (long) axis;
|
|
||||||
|
|
||||||
stack.push(new Node(curr, false, offset + median + 1, len - median - 1, depth + 1));
|
|
||||||
stack.push(new Node(curr, true, offset, median + 1, depth + 1));
|
|
||||||
}
|
|
||||||
if (n.parent >= 0) {
|
|
||||||
if (n.left) {
|
|
||||||
nll[n.parent] &= AXIS_MASK | RIGHT_MASK;
|
|
||||||
nll[n.parent] |= (long) curr << 2;
|
|
||||||
} else {
|
|
||||||
nll[n.parent] &= AXIS_MASK | LEFT_MASK;
|
|
||||||
nll[n.parent] |= (long) curr << 32;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void insertionSort(int[] indices, double[] coord, int left, int right) {
|
|
||||||
for (int i = left + 1; i <= right; i++) {
|
|
||||||
int key = indices[i];
|
|
||||||
double val = coord[key];
|
|
||||||
int j = i - 1;
|
|
||||||
|
|
||||||
while (j >= left && coord[indices[j]] > val) {
|
|
||||||
indices[j + 1] = indices[j];
|
|
||||||
j--;
|
|
||||||
}
|
|
||||||
indices[j + 1] = key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void quickSelect(int[] indices, double[] coord, int left, int right, int k) {
|
|
||||||
while (left < right) {
|
|
||||||
if (right - left < INSERTION_SORT) {
|
|
||||||
insertionSort(indices, coord, left, right);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int mid = left + (right - left) / 2;
|
|
||||||
int a = indices[left], b = indices[mid], c = indices[right];
|
|
||||||
double va = coord[a], vb = coord[b], vc = coord[c];
|
|
||||||
int pivotIdx = (va < vb)
|
|
||||||
? (vb < vc ? mid : (va < vc ? right : left))
|
|
||||||
: (va < vc ? left : (vb < vc ? right : mid));
|
|
||||||
swap(indices, pivotIdx, left);
|
|
||||||
double pivot = coord[indices[left]];
|
|
||||||
|
|
||||||
int i = left;
|
|
||||||
int j = right + 1;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
while (++i <= right && coord[indices[i]] < pivot) ;
|
|
||||||
while (--j > left && coord[indices[j]] > pivot) ;
|
|
||||||
if (i >= j) break;
|
|
||||||
swap(indices, i, j);
|
|
||||||
}
|
|
||||||
|
|
||||||
swap(indices, left, j);
|
|
||||||
int p = j;
|
|
||||||
if (p == k) {
|
|
||||||
return;
|
|
||||||
} else if (k < p) {
|
|
||||||
right = p - 1;
|
|
||||||
} else {
|
|
||||||
left = p + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void swap(int[] a, int i, int j) {
|
|
||||||
int tmp = a[i];
|
|
||||||
a[i] = a[j];
|
|
||||||
a[j] = tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reset() {
|
|
||||||
nodeLen = 0;
|
|
||||||
bucketLen = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void grow() {
|
|
||||||
int capacity = nodeLen + 1;
|
|
||||||
if (capacity < nsl.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
capacity += capacity >> 1;
|
|
||||||
if (capacity < INITIAL_CAP) {
|
|
||||||
capacity = INITIAL_CAP;
|
|
||||||
}
|
|
||||||
nsl = DoubleArrays.forceCapacity(nsl, capacity, nodeLen);
|
|
||||||
nll = LongArrays.forceCapacity(nll, capacity, nodeLen);
|
|
||||||
nbl = LongArrays.forceCapacity(nbl, capacity, nodeLen);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void growBucket(int capacity) {
|
|
||||||
capacity = bucketLen + capacity;
|
|
||||||
if (capacity < bxl.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
capacity += capacity >> 1;
|
|
||||||
if (capacity < INITIAL_CAP) {
|
|
||||||
capacity = INITIAL_CAP;
|
|
||||||
}
|
|
||||||
bxl = DoubleArrays.forceCapacity(bxl, capacity, bucketLen);
|
|
||||||
byl = DoubleArrays.forceCapacity(byl, capacity, bucketLen);
|
|
||||||
bzl = DoubleArrays.forceCapacity(bzl, capacity, bucketLen);
|
|
||||||
}
|
|
||||||
|
|
||||||
private double nearest(final double tx, final double ty, final double tz, double dist) {
|
|
||||||
if (nodeLen == 0) {
|
|
||||||
return Double.POSITIVE_INFINITY;
|
|
||||||
}
|
|
||||||
if (search.length < Math.max(64, nodeLen * 4)) {
|
|
||||||
search = new int[Math.max(64, nodeLen * 4)];
|
|
||||||
}
|
|
||||||
if (SIMD) {
|
|
||||||
return DespawnVectorAPI.nearest(search, nsl, nll, nbl, bxl, byl, bzl, tx, ty, tz, dist);
|
|
||||||
}
|
|
||||||
final int[] stack = this.search;
|
|
||||||
final double[] nsl = this.nsl;
|
|
||||||
final long[] nll = this.nll;
|
|
||||||
final double[] bxl = this.bxl;
|
|
||||||
final double[] byl = this.byl;
|
|
||||||
final double[] bzl = this.bzl;
|
|
||||||
final long[] nbl = this.nbl;
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
stack[i++] = 0;
|
for (final ServerPlayer p : players) {
|
||||||
while (i != 0) {
|
if (EntitySelector.PLAYER_AFFECTS_SPAWNING.test(p)) {
|
||||||
final int idx = stack[--i];
|
pxl[i] = p.getX();
|
||||||
final long data = nll[idx];
|
pyl[i] = p.getY();
|
||||||
if (data != LEAF) {
|
pzl[i] = p.getZ();
|
||||||
final long axis = data & AXIS_MASK;
|
i++;
|
||||||
final double delta = (axis == AXIS_X ? tx : axis == AXIS_Y ? ty : tz) - nsl[idx];
|
|
||||||
final long sign = Double.doubleToRawLongBits(delta) & SIGN_BIT;
|
|
||||||
final long sMask = sign >> 63; // -1L or 0L
|
|
||||||
final boolean leftValid = (data & LEFT_MASK) != LEFT_MASK;
|
|
||||||
final boolean rightValid = (data & RIGHT_MASK) != RIGHT_MASK;
|
|
||||||
final boolean pushNode = (sign == SIGN_BIT & leftValid) | ((sign == 0L) & rightValid);
|
|
||||||
final boolean pushOther = ((sign == 0L) & leftValid) | (sign == SIGN_BIT & rightValid);
|
|
||||||
final long node = (sMask & ((data & LEFT_MASK) >>> 2)) | (~sMask & (data >>> 32));
|
|
||||||
final long other = (sMask & (data >>> 32)) | (~sMask & ((data & LEFT_MASK) >>> 2));
|
|
||||||
if (pushNode) {
|
|
||||||
stack[i++] = (int) node;
|
|
||||||
}
|
|
||||||
if (pushOther && delta * delta < dist) {
|
|
||||||
stack[i++] = (int) other;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
final long bucket = nbl[idx];
|
|
||||||
int start = (int) (bucket >>> 32);
|
|
||||||
final int end = start + (int) (bucket & 0xffffffffL);
|
|
||||||
for (; start < end; start++) {
|
|
||||||
final double dx = bxl[start] - tx;
|
|
||||||
final double dy = byl[start] - ty;
|
|
||||||
final double dz = bzl[start] - tz;
|
|
||||||
final double d2 = FMA ? Math.fma(dz, dz, Math.fma(dy, dy, dx * dx)) : dx * dx + dy * dy + dz * dz;
|
|
||||||
dist = Math.min(dist, d2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dist;
|
final int[] indices = new int[i];
|
||||||
|
for (int j = 0; j < i; j++) {
|
||||||
|
indices[j] = j;
|
||||||
|
}
|
||||||
|
tree.build(new double[][]{pxl, pzl, pyl}, indices);
|
||||||
|
this.difficultyIsPeaceful = world.getDifficulty() == Difficulty.PEACEFUL;
|
||||||
|
if (fallback) {
|
||||||
|
entityTickList.forEach(entity -> entity.leaf$checkDespawnFallback(this));
|
||||||
|
} else {
|
||||||
|
entityTickList.forEach(this);
|
||||||
|
}
|
||||||
|
players = EMPTY_PLAYERS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private record Node(int parent, boolean left, int offset, int length, int depth) {
|
public void checkDespawn(final Mob mob) {
|
||||||
}
|
|
||||||
|
|
||||||
private static final class Stack {
|
|
||||||
|
|
||||||
private Node[] a;
|
|
||||||
private int i;
|
|
||||||
|
|
||||||
private Stack(int capacity) {
|
|
||||||
a = new Node[capacity];
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isEmpty() {
|
|
||||||
return i == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void push(Node value) {
|
|
||||||
if (i == a.length) {
|
|
||||||
grow();
|
|
||||||
}
|
|
||||||
a[i++] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Node pop() {
|
|
||||||
return a[--i];
|
|
||||||
}
|
|
||||||
|
|
||||||
private void grow() {
|
|
||||||
Node[] b = new Node[a.length << 1];
|
|
||||||
System.arraycopy(a, 0, b, 0, i);
|
|
||||||
a = b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void tick(ServerLevel world, EntityTickList entityTickList) {
|
|
||||||
final ServerPlayer[] playerArr = world.players().toArray(EMPTY_PLAYERS);
|
|
||||||
final ServerPlayer[] list = new ServerPlayer[playerArr.length];
|
|
||||||
int newSize = 0;
|
|
||||||
for (ServerPlayer player1 : playerArr) {
|
|
||||||
if (EntitySelector.PLAYER_AFFECTS_SPAWNING.test(player1)) {
|
|
||||||
list[newSize++] = player1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
double[] pxl = new double[newSize];
|
|
||||||
double[] pyl = new double[newSize];
|
|
||||||
double[] pzl = new double[newSize];
|
|
||||||
for (int i = 0; i < newSize; i++) {
|
|
||||||
pxl[i] = list[i].getX();
|
|
||||||
pyl[i] = list[i].getY();
|
|
||||||
pzl[i] = list[i].getZ();
|
|
||||||
}
|
|
||||||
build(pxl, pyl, pzl);
|
|
||||||
entityTickList.forEach(Entity::leafCheckDespawn);
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void checkDespawn(Mob mob) {
|
|
||||||
final double x = mob.getX();
|
|
||||||
final double y = mob.getY();
|
|
||||||
final double z = mob.getZ();
|
|
||||||
final int i = mob.getType().getCategory().ordinal();
|
final int i = mob.getType().getCategory().ordinal();
|
||||||
final double dist = nearest(x, y, z, hard[i]);
|
final double hardDist = this.hard[i];
|
||||||
|
final Vec3 vec3 = mob.position;
|
||||||
|
final double dist = this.tree.nearestSqr(vec3.x, vec3.z, vec3.y, hardDist);
|
||||||
if (dist == Double.POSITIVE_INFINITY) {
|
if (dist == Double.POSITIVE_INFINITY) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dist >= hard[i] && mob.removeWhenFarAway(dist)) {
|
if (dist >= hardDist && mob.removeWhenFarAway(dist)) {
|
||||||
mob.discard(EntityRemoveEvent.Cause.DESPAWN);
|
mob.discard(EntityRemoveEvent.Cause.DESPAWN);
|
||||||
} else if (dist > sort[i]) {
|
} else if (dist > this.sort[i]) {
|
||||||
if (mob.getNoActionTime() > 600 && mob.random.nextInt(800) == 0 && mob.removeWhenFarAway(dist)) {
|
if (mob.getNoActionTime() > 600 && mob.random.nextInt(800) == 0 && mob.removeWhenFarAway(dist)) {
|
||||||
mob.discard(EntityRemoveEvent.Cause.DESPAWN);
|
mob.discard(EntityRemoveEvent.Cause.DESPAWN);
|
||||||
}
|
}
|
||||||
@@ -362,4 +107,15 @@ public final class DespawnMap {
|
|||||||
mob.setNoActionTime(0);
|
mob.setNoActionTime(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ServerPlayer checkDespawnFallback(final Mob mob) {
|
||||||
|
final Vec3 vec3 = mob.position;
|
||||||
|
final int i = tree.nearestIdx(vec3.x, vec3.z, vec3.y, Double.POSITIVE_INFINITY);
|
||||||
|
return i == -1 ? null : this.players[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(final Entity entity) {
|
||||||
|
entity.leaf$checkDespawn(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
package org.dreeam.leaf.world;
|
|
||||||
import jdk.incubator.vector.*;
|
|
||||||
|
|
||||||
import static org.dreeam.leaf.world.DespawnMap.*;
|
|
||||||
|
|
||||||
public final class DespawnVectorAPI {
|
|
||||||
|
|
||||||
private DespawnVectorAPI() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final VectorSpecies<Double> DOUBLE_SPECIES = DoubleVector.SPECIES_PREFERRED;
|
|
||||||
static final int DOUBLE_VECTOR_LENGTH = DOUBLE_SPECIES.length();
|
|
||||||
|
|
||||||
static double nearest(final int[] stack,
|
|
||||||
final double[] nsl,
|
|
||||||
final long[] nll,
|
|
||||||
final long[] nbl,
|
|
||||||
final double[] bxl, final double[] byl, final double[] bzl,
|
|
||||||
final double tx, final double ty, final double tz,
|
|
||||||
double dist) {
|
|
||||||
final DoubleVector vtx = DoubleVector.broadcast(DOUBLE_SPECIES, tx);
|
|
||||||
final DoubleVector vty = DoubleVector.broadcast(DOUBLE_SPECIES, ty);
|
|
||||||
final DoubleVector vtz = DoubleVector.broadcast(DOUBLE_SPECIES, tz);
|
|
||||||
int i = 0;
|
|
||||||
stack[i++] = 0;
|
|
||||||
while (i != 0) {
|
|
||||||
final int idx = stack[--i];
|
|
||||||
final long data = nll[idx];
|
|
||||||
if (data != LEAF) {
|
|
||||||
final long axis = data & AXIS_MASK;
|
|
||||||
final double delta = (axis == AXIS_X ? tx : axis == AXIS_Y ? ty : tz) - nsl[idx];
|
|
||||||
final long sign = Double.doubleToRawLongBits(delta) & SIGN_BIT;
|
|
||||||
final long sMask = sign >> 63; // -1L or 0L
|
|
||||||
final boolean leftValid = (data & LEFT_MASK) != LEFT_MASK;
|
|
||||||
final boolean rightValid = (data & RIGHT_MASK) != RIGHT_MASK;
|
|
||||||
final boolean pushNode = (sign == SIGN_BIT & leftValid) | ((sign == 0L) & rightValid);
|
|
||||||
final boolean pushOther = ((sign == 0L) & leftValid) | (sign == SIGN_BIT & rightValid);
|
|
||||||
final long node = (sMask & ((data & LEFT_MASK) >>> 2)) | (~sMask & (data >>> 32));
|
|
||||||
final long other = (sMask & (data >>> 32)) | (~sMask & ((data & LEFT_MASK) >>> 2));
|
|
||||||
if (pushNode) {
|
|
||||||
stack[i++] = (int) node;
|
|
||||||
}
|
|
||||||
if (pushOther && delta * delta < dist) {
|
|
||||||
stack[i++] = (int) other;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
final long bucket = nbl[idx];
|
|
||||||
final int start = (int) (bucket >>> 32);
|
|
||||||
final int bucketSize = (int) (bucket & 0xffffffffL);
|
|
||||||
if (DOUBLE_VECTOR_LENGTH == bucketSize) {
|
|
||||||
final DoubleVector vdx = DoubleVector.fromArray(DOUBLE_SPECIES, bxl, start).sub(vtx);
|
|
||||||
final DoubleVector vdy = DoubleVector.fromArray(DOUBLE_SPECIES, byl, start).sub(vty);
|
|
||||||
final DoubleVector vdz = DoubleVector.fromArray(DOUBLE_SPECIES, bzl, start).sub(vtz);
|
|
||||||
final DoubleVector vDist = FMA ?
|
|
||||||
vdz.fma(vdz, vdy.fma(vdy, vdx.mul(vdx))) :
|
|
||||||
vdx.mul(vdx).add(vdy.mul(vdy)).add(vdz.mul(vdz));
|
|
||||||
dist = Math.min(dist, vDist.reduceLanes(VectorOperators.MIN));
|
|
||||||
} else if (DOUBLE_VECTOR_LENGTH > 4 && bucketSize >= 4) {
|
|
||||||
final VectorMask<Double> mask = DOUBLE_SPECIES.indexInRange(0, bucketSize);
|
|
||||||
final DoubleVector vdx = DoubleVector.fromArray(DOUBLE_SPECIES, bxl, start, mask).sub(vtx);
|
|
||||||
final DoubleVector vdy = DoubleVector.fromArray(DOUBLE_SPECIES, byl, start, mask).sub(vty);
|
|
||||||
final DoubleVector vdz = DoubleVector.fromArray(DOUBLE_SPECIES, bzl, start, mask).sub(vtz);
|
|
||||||
final DoubleVector vDist = FMA ?
|
|
||||||
vdz.fma(vdz, vdy.fma(vdy, vdx.mul(vdx))) :
|
|
||||||
vdx.mul(vdx).add(vdy.mul(vdy)).add(vdz.mul(vdz));
|
|
||||||
dist = Math.min(dist, vDist.reduceLanes(VectorOperators.MIN, mask));
|
|
||||||
} else {
|
|
||||||
final int end = start + bucketSize;
|
|
||||||
for (int j = start; j < end; j++) {
|
|
||||||
final double dx = bxl[j] - tx;
|
|
||||||
final double dy = byl[j] - ty;
|
|
||||||
final double dz = bzl[j] - tz;
|
|
||||||
final double d2 = FMA ? Math.fma(dz, dz, Math.fma(dy, dy, dx * dx)) : dx * dx + dy * dy + dz * dz;
|
|
||||||
dist = Math.min(dist, d2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dist;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user