Class SphereSpiralIterator

java.lang.Object
com.hypixel.hytale.math.iterator.SphereSpiralIterator

public final class SphereSpiralIterator extends Object
Iterates the chunk sections inside a sphere centred on a player, in ascending 3D Euclidean distance² order. The 3D analog of CircleSpiralIterator.

Backed by a shared SphereOffsets list pre-built at the server's maximum allowed radius. Each player iterator records its own (smaller-or-equal) radius and the upfront binary search in init(int, int, int, int, int) clamps the cursor to the player's sphere; everything in the resulting cursor range is guaranteed inside the player's sphere, so advance() walks linearly without re-testing each cell.

Typical usage

// At server startup (once):
SphereOffsets shared = SphereOffsets.build(maxR);

// Per player:
SphereSpiralIterator iter = new SphereSpiralIterator(shared);
iter.configure(playerR);     // also when the slider moves

// Per tick, walk only the newly-visible distance band:
iter.init(playerCx, playerCy, playerCz, lastSentDistSq + 1, currentMaxDistSq);
while (iter.advance()) {
    SpherePosition p = iter.position();
    // queue (p.x, p.y, p.z) for section load
}

Range semantics

  • The 5-arg init(int, int, int, int, int) restricts iteration to offsets whose distSq lies in [startDistSq, endDistSq] (inclusive both ends).
  • endDistSq is internally clamped to the player's playerR²; passing Integer.MAX_VALUE is therefore safe and means "walk to the player's edge".
  • The 3-arg init(int, int, int) is a shortcut for [0, MAX_VALUE].

Cost

  • configure(int): a multiply and a volatile write; cheap, can be called whenever the player's view-radius slider changes.
  • init(int, int, int, int, int): two binary searches into the shared distSq array, O(log shared.count).
  • advance(): O(1) per emitted cell, one array triplet read and the center offset add; no containment test in the hot loop.

Thread safety

Not thread-safe. The shared SphereOffsets is immutable and may be referenced by many iterators concurrently, but each iterator instance holds mutable cursor and position state and must be used from a single thread.
  • Constructor Summary

    Constructors
    Constructor
    Description
     
  • Method Summary

    Modifier and Type
    Method
    Description
    boolean
    Advances the iterator to the next cell in the current [startDistSq, endDistSq] range.
    void
    configure(int playerR)
    Sets the player's sphere radius.
    boolean
    containsOffset(int dx, int dy, int dz)
    Tests whether the given offset from the sphere center is inside the configured sphere.
    int
    Returns the largest distSq value such that every offset in this iterator's configured [startDistSq, endDistSq] range with distSq <= return has been considered (yielded) since the last init(int, int, int) call.
    void
    init(int cx, int cy, int cz)
    Begins iteration from the origin out to the player's edge.
    void
    init(int cx, int cy, int cz, int startDistSq, int endDistSq)
    Begins iteration over offsets in the player's sphere whose distSq lies in [startDistSq, endDistSq].
    Returns the position written by the most recent successful advance() call.
     

    Methods inherited from class Object

    equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Constructor Details

    • SphereSpiralIterator

      public SphereSpiralIterator(@Nonnull SphereOffsets shared)
      Parameters:
      shared - the pre-built offset list; must outlive the iterator. Typically the server-wide instance built from the configured maximum radius.
  • Method Details

    • configure

      public void configure(int playerR)
      Sets the player's sphere radius. Must be called at least once before init(int, int, int) and may be called again whenever the player's view-radius slider changes; no allocations.
      Parameters:
      playerR - radius, 0 <= playerR <= shared.maxR
      Throws:
      IllegalArgumentException - if playerR is negative or exceeds the shared maximum
    • init

      public void init(int cx, int cy, int cz)
      Begins iteration from the origin out to the player's edge. Equivalent to init(cx, cy, cz, 0, Integer.MAX_VALUE).
    • init

      public void init(int cx, int cy, int cz, int startDistSq, int endDistSq)
      Begins iteration over offsets in the player's sphere whose distSq lies in [startDistSq, endDistSq].

      endDistSq is internally clamped to playerR².

      Parameters:
      cx - world-space chunk X of the center
      cy - world-space chunk Y of the center
      cz - world-space chunk Z of the center
      startDistSq - inclusive lower bound on offset distance² (use 0 for "from the player out")
      endDistSq - inclusive upper bound on offset distance² (use Integer.MAX_VALUE for "to the player's edge")
    • containsOffset

      public boolean containsOffset(int dx, int dy, int dz)
      Tests whether the given offset from the sphere center is inside the configured sphere.

      Uses the radius set by configure(int); independent of init(int, int, int) state. Provided so unload checks elsewhere can use the exact same predicate the iterator uses for load. Keeping load and unload as a single source of truth avoids any shape mismatch.

      Parameters:
      dx - horizontal X offset from center
      dy - vertical Y offset from center
      dz - horizontal Z offset from center
      Returns:
      true if the offset is inside the configured sphere
    • advance

      public boolean advance()
      Advances the iterator to the next cell in the current [startDistSq, endDistSq] range.

      No containment test in the loop: the cursor range was clamped to the player's sphere at init, so every offset in [cursor, endCursor) is guaranteed inside.

      Returns:
      true if a cell was found and position() now holds it; false if iteration is exhausted
    • position

      @Nonnull public SpherePosition position()
      Returns the position written by the most recent successful advance() call. The same instance is reused across calls, so copy fields out before the next advance() if you need them later.
    • getCompletedDistSq

      public int getCompletedDistSq()
      Returns the largest distSq value such that every offset in this iterator's configured [startDistSq, endDistSq] range with distSq <= return has been considered (yielded) since the last init(int, int, int) call.

      The 3D analog of CircleSpiralIterator.getCompletedRadius(). Suitable for storing in a tracker's "sent up to" cursor: pass result + 1 as startDistSq on the next init call to resume.

      Conservative on partial levels: when the iterator is partway through a distSq value that has multiple offsets (e.g. the six face cells of shell 1 all sharing distSq == 1, and only some have been visited), the level is reported as in-progress, where the return is one less than that level's distSq. A resume from return + 1 will re-walk the partial level, which the tracker's "already tracked" check makes cheap.

      Special cases:

      • If the iterator was init'd with an empty range (no offsets in [startDistSq, endDistSq]), or has been advanced to exhaustion, the configured endDistSq is returned, meaning the range is fully processed.
      • If advance() has not been called at all since init, the configured startDistSq - 1 is returned, meaning no progress yet.
    • sharedOffsets

      @Nonnull public SphereOffsets sharedOffsets()
      Returns:
      the shared offset list backing this iterator (immutable)