Files

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

113 lines
7.3 KiB
Markdown
Raw Permalink Normal View History

2026-09-05 10:41:17 +02:00
# Testing occlusion from Java
2026-09-05 00:48:38 +02:00
[API index](../API.md)
2026-10-15 16:39:36 +02:00
`TileMultipart.testOcclusion(Collection<? extends TMultiPart>, TMultiPart)` tests a supplied collection of parts
through the tile's existing occlusion hook, including generated partial-occlusion checks. For raw geometry,
`NormalOcclusionTest.testBoxes(Iterable<? extends Cuboid6>, Iterable<? extends Cuboid6>)` tests two groups of bounding
boxes without constructing parts. See [box-versus-box queries](#box-versus-box-queries).
2026-09-05 00:48:38 +02:00
## Example
Use a `NormallyOccludedPart` to test a normal bounding shape against a supplied group of parts:
```java
public static boolean fitsShape(TileMultipart tile, Collection<? extends TMultiPart> parts, Cuboid6 bounds) {
return tile.testOcclusion(parts, new NormallyOccludedPart(bounds));
}
```
2026-10-15 16:39:36 +02:00
See the [complete Java example](../../src/test/java/codechicken/multipart/examples/OcclusionExample.java).
Collections of part subclasses, such as `List<NormallyOccludedPart>`, are accepted without a cast.
2026-09-05 00:48:38 +02:00
Pass `tile.jPartList()` to test against the tile's currently stored parts, or pass a separate collection for a preview
or selected subset. To exercise a real candidate's custom occlusion behavior, pass that part instead of the shape
wrapper. The wrapper represents normal bounding geometry, not all the capabilities of a custom part.
## What the query checks
The base tile tests each supplied part against the candidate, then tests the candidate against that same part. It
proceeds in input iteration order and stops immediately when either direction rejects. Unbound/detached parts and
duplicate entries participate; the candidate is not automatically excluded if it is also in the collection.
Generated partial-occlusion tiles retain their extra aggregate check. When the candidate implements `JPartialOcclusion`,
the existing trait checks the group plus the candidate before the ordinary pair callbacks. A plain `TileMultipart`
does not gain that capability just because the supplied parts need it. Use the appropriate composite tile; this method
does not generate or select tile traits.
This is an occlusion query, not full placement approval. It does not check placement permissions, duplicate membership,
slot occupancy or world replaceability, and does not bind/store parts or send notifications. For ordinary placement
and replacement, continue using `canPlacePart`, `canAddPart` and `canReplacePart` as appropriate. In particular,
`canReplacePart(outgoing, candidate)` performs its existing outgoing-part exclusion; this collection query does not.
## Ownership, callbacks and failures
The Java entry copies the input into an immutable sequence before calling the legacy hook. Order, duplicates, null
entries and part identities are preserved. Callback edits to the caller's collection do not change the current
query's sequence. Part objects are shared, so mutations of their state can affect later callbacks. A nested query
takes its own copy.
Copying failures, including a null collection, propagate before the legacy hook is called. There is no eager
candidate/null-entry validation: with the base implementation, an empty collection accepts even a null candidate,
and an early rejection can avoid a later null dereference. Callback exceptions propagate unchanged and stop the query;
callback side effects already performed are not undone. Overrides may add checks or customize these boundaries.
Use the game thread when querying live parts. A geometric query may invoke arbitrary part callbacks, including ones
that read or modify world/part state; it is not a thread-safety guarantee.
## Overrides and migration
Dispatch is `testOcclusion(Collection, candidate)` → virtual `occlusionTest(scala.collection.Seq, candidate)`.
The old method keeps its descriptor and body, and is deprecated for callers. Existing subclasses and generated traits
continue to override it. `canAddPart` and `canReplacePart` still call that hook directly; overriding only the new
Java method does not intercept their checks. The distinct Java name avoids introducing another Scala/Java overload
pair, following the [loader compilation finding](PART_LOADING.md#overrides-and-reflection).
Consumers such as ProjectRed and ForgeRelocationFMP already use the Java-typed `canReplacePart` API and need no rename
for that contract. Part-level `occlusionTest(TMultiPart)` remains supported as-is. The Scala `NormalOcclusionTest.apply`
2026-09-05 10:41:17 +02:00
box-versus-box entry is a separate, still-used contract with the Java replacement below.
## Box-versus-box queries
Use `NormalOcclusionTest.testBoxes` when you already have box iterables, including `getOcclusionBoxes()` results:
```java
public static boolean fitsBounds(Iterable<? extends Cuboid6> occupied, Cuboid6 candidate) {
return NormalOcclusionTest.testBoxes(occupied, Collections.singletonList(candidate));
}
```
2026-10-15 16:39:36 +02:00
See the [complete example](../../src/test/java/codechicken/multipart/examples/BoxOcclusionExample.java). Both
arguments accept box subclasses and need only implement Java `Iterable`.
2026-09-05 10:41:17 +02:00
The query fully consumes the first input, then the second, requesting each input's iterator once. It takes shallow
snapshots before testing any pairs, even when one input is empty or the first pair will overlap. Iteration failures
therefore take precedence over geometry results; a failure in the first input prevents reading the second. Input
collections can change during intersection callbacks without changing the captured membership, but boxes themselves
are shared: coordinate changes remain visible. Supply finite iterables and use the game thread for live geometry.
Each first-group box calls `intersects` on second-group boxes in encounter order. Duplicates participate; the first
intersection returns `false`. No intersections returns `true`. The existing `Cuboid6.intersects` tolerance is retained:
touching faces and sub-tolerance overlaps are allowed. Only cross-group pairs are tested, not pairs within one group.
Neither input nor its boxes is modified by the helper; custom iterators or `intersects` overrides can have side effects.
Null inputs fail during copying. Null box entries are not eagerly rejected: an empty opposite group never uses them,
and an earlier intersection may avoid them. A reached intersection involving an ordinary null box fails. Iterator and
intersection exceptions propagate unchanged, without undoing callback side effects.
This does not run part callbacks, generated aggregate partial-occlusion checks, slot checks or placement logic.
Use `tile.testOcclusion` for the tile's occlusion protocol, and the placement/replacement APIs for their wider checks.
The existing `NormalOcclusionTest.apply(JNormalOcclusion, TMultiPart)` part helper remains unchanged.
Both Scala box-list `apply(Traversable, Traversable)` entries, static and companion, are deprecated with their original
descriptors and bodies retained. OpenComputers' cable/network checks can pass `getOcclusionBoxes()` directly as the
second argument; their Scala `ownBounds` collection needs a Java adapter:
```scala
import scala.collection.JavaConverters._
NormalOcclusionTest.testBoxes(ownBounds.asJava, otherBounds)
```
ForgeRelocationFMP can similarly adapt its assembled Scala `boxes` sequence and pass its existing Java
`getOcclusionBoxes` iterable directly. Keep its combined normal, partial and collision boxes and caller order intact.
2026-10-15 16:39:36 +02:00
Scala consumers can adopt this FMP API while keeping their own Scala code.