🪧 2-Sided Sign & Hanging Sign Text Culling (Minecraft 26.3)
In Minecraft 26.3, signs and hanging signs feature two-sided text capabilities (SignTextSlot.FRONT and SignTextSlot.BACK). By default, the game's font rendering engine parses, kerns, and renders text quads, dyes, and glowing outlines for both sides simultaneously—even when the player is looking directly at the front of a wall-mounted sign.
Camera Culling solves this by calculating normal vector dot products ($\vec{N} \cdot \vec{V}$) and empty-text fast passes to unrender unviewed or blank sign text faces.
📋 Sign Culling Infobox
| Property | Value |
|---|---|
| Target Pipeline | BlockEntityRenderDispatcher.tryExtractRenderState @At("RETURN") |
| API Hook | signState.frontText = null; / signState.backText = null; |
| Supported Types | Wall Signs, Standing Signs, Wall Hanging Signs, Ceiling Hanging Signs |
| Empty Fast-Pass | Automatically skips blank faces with zero non-whitespace characters |
| Dot Product Margin | Tolerance of $\pm 0.05$ prevents edge-angle pop-in |
📐 Vector Normal Dot-Product Mathematics
To determine whether the front or back face of a sign is pointing towards the camera, Camera Culling calculates the dot product between the sign face normal vector $\vec{N} = (N_x, N_z)$ and the vector from sign center to camera $\vec{V} = (V_x, V_z)$:
$$V_x = X_{\text{cam} - (X_{\text{sign} + 0.5), \quad V_z = Z_{\text{cam} - (Z_{\text{sign} + 0.5)$$
1. Wall Signs (WallSignBlock.FACING) & Wall Hanging Signs (WallHangingSignBlock.FACING)
The normal vector is derived directly from the block's Direction step offsets: $$N_x = \text{facing.getStepX()}, \quad N_z = \text{facing.getStepZ()}$$
2. Standing Signs (StandingSignBlock.ROTATION) & Ceiling Hanging Signs (CeilingHangingSignBlock.ROTATION)
Rotation is represented as an integer $0 \dots 15$. The angle $\theta$ in radians is computed: $$\theta = \left(\frac{\text{rotation} \times 360^\circ}{16}\right) \times \frac{\pi}{180}$$ $$N_x = -\sin\theta, \quad N_z = \cos\theta$$
3. Visibility Determination
The dot product $D$ evaluates the angle of observation: $$D = N_x V_x + N_z V_z$$
- Front Face Evaluation: Culled when $D < -0.05$ (Camera is behind the sign face).
- Back Face Evaluation: Culled when $D > 0.05$ (Camera is in front of the sign face).
⚡ Empty-Side Fast-Pass
If a player has only written text on one side of a sign (or placed a sign without text as a barrier/decoration), Camera Culling inspects the 4 text lines:
public static boolean isTextEmpty(SignText text) {
if (text == null) return true;
for (Component msg : text.getMessages(false)) {
if (msg != null && !msg.getString().trim().isEmpty()) {
return false;
}
}
return true;
}Blank faces are nulled out immediately without running trigonometry or vector dot products.
