Skip to content

Commit b01b941

Browse files
committed
Reject viewer queries outside world bounds
1 parent 1eae5ba commit b01b941

3 files changed

Lines changed: 71 additions & 3 deletions

File tree

benchmark/src/main/java/com/loohp/interactionvisualizer/entities/DroppedItemBenchmarkPlugin.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ private void runBenchmarks() throws IOException {
8383
benchmarkCramping(world, itemCount, false);
8484
benchmarkCramping(world, itemCount, true);
8585
}
86-
for (String distribution : List.of("uniform", "hotspot", "no-hit")) {
86+
for (String distribution : List.of("uniform", "hotspot", "no-hit", "enclosed-no-hit")) {
8787
for (int itemCount : List.of(500, 2000, 8000)) {
8888
for (int viewerCount : List.of(1, 8, 32, 64, 96, 128, 192, 256, 384, 512, 768, 1024)) {
8989
for (int seed = 0; seed < VISIBILITY_SEEDS; seed++) {
@@ -193,6 +193,10 @@ private void benchmarkVisibility(int itemCount, int viewerCount, String distribu
193193
items = randomPoints(random, itemCount, 0.0D, 256.0D);
194194
viewers = randomPoints(random, viewerCount, 768.0D, 256.0D);
195195
}
196+
case "enclosed-no-hit" -> {
197+
items = randomPoints(random, itemCount, 448.0D, 128.0D);
198+
viewers = enclosingRingPoints(random, viewerCount);
199+
}
196200
default -> throw new IllegalArgumentException("Unknown visibility distribution: " + distribution);
197201
}
198202
LongSupplier linearReference = () -> linearActiveLabels(items, viewers, VIEW_DISTANCE);
@@ -219,9 +223,10 @@ private void reportVisibilityComparison(String benchmark, String baseline, boole
219223
"{\"benchmark\":\"%s\",\"distribution\":\"%s\",\"seed\":%d," +
220224
"\"items\":%d,\"viewers\":%d," +
221225
"\"range\":%.1f,\"baseline\":\"%s\",\"baselineUsesGrid\":%b," +
222-
"\"candidate\":\"production-primitive-viewer-index\"," +
226+
"\"candidate\":\"production-bounded-primitive-viewer-index\"," +
223227
"\"candidateStorage\":\"primitive-soa\"," +
224228
"\"indexRebuiltPerOperation\":true,\"candidateUsesGrid\":false," +
229+
"\"candidateUsesBounds\":true," +
225230
"\"sampleTargetNs\":%d,\"warmupRounds\":%d,\"measurementRounds\":%d," +
226231
"\"roundOrder\":\"%s\"," +
227232
"\"baselineMedianNs\":%d,\"baselineP95Ns\":%d," +
@@ -265,6 +270,24 @@ private static List<Point> randomPoints(Random random, int count, double offset,
265270
return points;
266271
}
267272

273+
/**
274+
* Places viewers around an empty central region. From eight viewers onward the item region is
275+
* enclosed by the viewer AABB, while every viewer remains well beyond the visibility radius.
276+
* This prevents an outer-bounds fast rejection from disguising worst-case miss scans.
277+
*/
278+
private static List<Point> enclosingRingPoints(Random random, int count) {
279+
List<Point> points = new ArrayList<>(count);
280+
double phase = random.nextDouble(2.0D * Math.PI);
281+
for (int index = 0; index < count; index++) {
282+
double angle = phase + 2.0D * Math.PI * index / Math.max(1, count);
283+
points.add(new Point(index,
284+
512.0D + Math.cos(angle) * 300.0D,
285+
64.0D + random.nextDouble(32.0D),
286+
512.0D + Math.sin(angle) * 300.0D));
287+
}
288+
return points;
289+
}
290+
268291
private static long linearActiveLabels(List<Point> items, List<Point> viewers, double range) {
269292
List<Point> viewerSnapshot = new ArrayList<>(viewers.size());
270293
for (Point viewer : viewers) {

common/src/main/java/com/loohp/interactionvisualizer/entities/DroppedItemSpatialIndex.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ boolean hasViewerWithin(UUID worldId, double x, double y, double z, double range
119119
WorldViewerBucket viewers = viewersByWorld == null
120120
? java.util.Objects.equals(singleWorldId, worldId) ? singleWorldViewers : null
121121
: viewersByWorld.get(worldId);
122-
return viewers != null && viewers.hasViewerWithin(x, y, z, range * range);
122+
return viewers != null && !viewers.isOutsideBounds(x, y, z, range)
123+
&& viewers.hasViewerWithin(x, y, z, range * range);
123124
}
124125

125126
private static final class WorldViewerBucket {
@@ -131,6 +132,12 @@ private static final class WorldViewerBucket {
131132
private double[] xCoordinates = EMPTY;
132133
private double[] yCoordinates = EMPTY;
133134
private double[] zCoordinates = EMPTY;
135+
private double minimumX = Double.POSITIVE_INFINITY;
136+
private double minimumY = Double.POSITIVE_INFINITY;
137+
private double minimumZ = Double.POSITIVE_INFINITY;
138+
private double maximumX = Double.NEGATIVE_INFINITY;
139+
private double maximumY = Double.NEGATIVE_INFINITY;
140+
private double maximumZ = Double.NEGATIVE_INFINITY;
134141
private int size;
135142

136143
private WorldViewerBucket(int initialCapacity) {
@@ -151,9 +158,21 @@ private void add(double x, double y, double z) {
151158
xCoordinates[size] = x;
152159
yCoordinates[size] = y;
153160
zCoordinates[size] = z;
161+
minimumX = Math.min(minimumX, x);
162+
minimumY = Math.min(minimumY, y);
163+
minimumZ = Math.min(minimumZ, z);
164+
maximumX = Math.max(maximumX, x);
165+
maximumY = Math.max(maximumY, y);
166+
maximumZ = Math.max(maximumZ, z);
154167
size++;
155168
}
156169

170+
private boolean isOutsideBounds(double x, double y, double z, double range) {
171+
return x < minimumX - range || x > maximumX + range
172+
|| y < minimumY - range || y > maximumY + range
173+
|| z < minimumZ - range || z > maximumZ + range;
174+
}
175+
157176
private boolean hasViewerWithin(double x, double y, double z, double rangeSquared) {
158177
for (int index = 0; index < size; index++) {
159178
double deltaX = xCoordinates[index] - x;

common/src/test/java/com/loohp/interactionvisualizer/entities/DroppedItemSpatialIndexTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,32 @@ void rejectsNegativeExpectedViewerCapacity() {
7474
() -> new DroppedItemSpatialIndex.ViewerIndex(-1));
7575
}
7676

77+
@Test
78+
void viewerBoundsKeepTouchingSphereEdgesExact() {
79+
UUID world = UUID.randomUUID();
80+
DroppedItemSpatialIndex.ViewerIndex viewers = new DroppedItemSpatialIndex.ViewerIndex(2);
81+
viewers.addViewer(world, -10.0D, 64.0D, -10.0D);
82+
viewers.addViewer(world, 10.0D, 70.0D, 10.0D);
83+
84+
assertTrue(viewers.hasViewerWithin(world, 20.0D, 70.0D, 10.0D, 10.0D));
85+
assertFalse(viewers.hasViewerWithin(world, 20.0001D, 70.0D, 10.0D, 10.0D));
86+
assertTrue(viewers.hasViewerWithin(world, -10.0D, 54.0D, -10.0D, 10.0D));
87+
assertFalse(viewers.hasViewerWithin(world, -10.0D, 53.9999D, -10.0D, 10.0D));
88+
}
89+
90+
@Test
91+
void viewerBoundsStillRunExactChecksInsideTheBox() {
92+
UUID world = UUID.randomUUID();
93+
DroppedItemSpatialIndex.ViewerIndex viewers = new DroppedItemSpatialIndex.ViewerIndex(4);
94+
viewers.addViewer(world, -100.0D, 64.0D, -100.0D);
95+
viewers.addViewer(world, -100.0D, 64.0D, 100.0D);
96+
viewers.addViewer(world, 100.0D, 64.0D, -100.0D);
97+
viewers.addViewer(world, 100.0D, 64.0D, 100.0D);
98+
99+
assertFalse(viewers.hasViewerWithin(world, 0.0D, 64.0D, 0.0D, 10.0D));
100+
assertTrue(viewers.hasViewerWithin(world, 90.0D, 64.0D, 100.0D, 10.0D));
101+
}
102+
77103
@Test
78104
void upgradesFromSingleWorldFastPathAndKeepsWorldsIsolated() {
79105
UUID firstWorld = UUID.randomUUID();

0 commit comments

Comments
 (0)