> For the complete documentation index, see [llms.txt](https://dante-3.gitbook.io/dante-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://dante-3.gitbook.io/dante-docs/indicator-system-plugin/projection.md).

# Projection

`SActorCanvas` is the Slate widget at the heart of `UIndicatorLayer`. It renders all active indicators by projecting their world-space anchor points to screen space every frame.

***

## Architecture

```
UIndicatorLayer (UWidget)
  └─ SActorCanvas (SPanel)
       ├─ Indicator slot 0  (UUserWidget instance)
       ├─ Indicator slot 1
       └─ ...
```

`UIndicatorLayer` is added to your HUD widget tree. It fills the viewport and calls `SActorCanvas::OnPaint` every frame to reposition each child indicator widget according to its descriptor's projection mode.\ <br>

<figure><img src="https://4216330531-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYsGXprPiDJ68FN8HnOaN%2Fuploads%2FREQjgqkOx9Z0ebSH4isg%2FEkran%20g%C3%B6r%C3%BCnt%C3%BCs%C3%BC%202026-03-28%20102100.png?alt=media&amp;token=7aea3817-b778-4d82-821a-0c392b69f631" alt=""><figcaption></figcaption></figure>

***

## Projection Modes

Defined by `EActorCanvasProjectionMode`. Set via `UIndicatorAction_ProjectionMode` or directly on `UIndicatorDescriptor`.

### `ComponentPoint`

**Default mode.** Projects the exact world location of the scene component (`UIndicatorDescriptor::SceneComponent`) to a screen pixel.

```
World component origin  →  Project()  →  (X, Y) screen position
```

**Best for:** Minimaps, simple dot indicators, ping markers.\
**Cost:** Very low — one `ProjectWorldLocationToScreen` call.

***

### `ComponentBoundingBox`

Computes the 3D bounding box of the scene component, applies a normalized `BoundingBoxAnchor` within it, then projects the resulting world point.

```
Component AABB  →  Lerp(min, max, Anchor)  →  Project()  →  (X, Y)
```

**Best for:** Characters (track the top of the head via `Anchor=(0.5, 0.5, 1.0)`).\
**Cost:** Low — one bounding box query + one project call.

***

### `ComponentScreenBoundingBox`

Projects all 8 corners of the component's 3D bounding box to screen space, computes the 2D screen-space bounding box, then lerps within it using `BoundingBoxAnchor`.

```
Component AABB corners[8]  →  Project each  →  2D screen AABB
  →  Lerp(screenMin, screenMax, Anchor.xy)  →  (X, Y)
```

**Best for:** Indicators that should stay within the on-screen footprint of a large mesh.\
**Cost:** Medium — 8 project calls per frame per indicator.

***

### `ActorBoundingBox`

Same as `ComponentBoundingBox` but uses the owning actor's combined bounding box (all components) instead of a single component.

**Best for:** Vehicles, multi-component actors where the single component origin is not representative.\
**Cost:** Low.

***

### `ActorScreenBoundingBox`

Same as `ComponentScreenBoundingBox` but uses the full actor bounding box.

**Best for:** Large actors where you want screen-space alignment (e.g., center the indicator on the actor's visible screen footprint).\
**Cost:** Highest — 8 project calls using the full actor AABB per frame per indicator.

***

## Mode Selection Guide

| Scenario                         | Recommended Mode                                |
| -------------------------------- | ----------------------------------------------- |
| Simple world marker / ping       | `ComponentPoint`                                |
| Enemy character head indicator   | `ComponentBoundingBox` (`Anchor=(0.5,0.5,1.0)`) |
| Objective on a large static mesh | `ActorBoundingBox`                              |
| Screen-anchored nameplate        | `ComponentScreenBoundingBox`                    |

***

## SActorCanvas: How Indicators Are Rendered

`SActorCanvas` derives from `SPanel` (a Slate container). Key responsibilities:

1. **Slot management** — Each active `UIndicatorDescriptor` gets a slot with its instantiated `UUserWidget`.
2. **Per-frame projection** — In `OnArrangeChildren`, each slot queries its descriptor for the current projection mode, calls the appropriate variant of `FIndicatorProjection::Project()`, and positions the slot at the resulting screen coordinate.
3. **Clamp to screen** — If `bClampToScreen=true`, the projected point is clamped to the viewport rectangle. The delta between the raw and clamped position is used to rotate the arrow brush.
4. **Visibility culling** — If `bVisible=false` on the descriptor, the slot is collapsed (zero size, no render).
5. **Distance culling** — If `MaxVisibleDistance > 0`, the indicator is hidden beyond that distance. If `MinVisibleDistance > 0`, it is hidden below that distance.
6. **Overlap prevention** — If `bPreventOverlap=true`, slots are displaced by `OverlapPadding` to avoid stacking.

***

## FIndicatorProjection

Internal struct that wraps the projection math. Not exposed to Blueprint.

```cpp
// Pseudocode
struct FIndicatorProjection
{
    static bool Project(
        const UIndicatorDescriptor& Descriptor,
        const FSceneViewProjectionData& ProjectionData,
        const FVector2D& ScreenSize,
        FVector2D& OutScreenPosition,
        float& OutDepth
    );
};
```

Returns `true` if the target is in front of the camera (positive depth). `SActorCanvas` hides the slot if `Project()` returns `false` and `bClampToScreen=false`.

***

## Offsets and Scale

After projection, these descriptor values are applied before the slot is placed:

| Property              | Applied                                                    |
| --------------------- | ---------------------------------------------------------- |
| `ScreenSpaceOffset`   | Added to projected `(X, Y)` after clamping                 |
| `WorldPositionOffset` | Added to the world anchor point before projection          |
| `Scale`               | Uniform scale applied to the indicator widget              |
| `Scale2D`             | Per-axis scale applied after `Scale`                       |
| `Priority`            | Higher priority slots are rendered on top (higher Z-order) |

***

## Performance Notes

* In a scene with many enemies, `ComponentBoundingBox` is recommended over `ComponentScreenBoundingBox` — it makes 1 projection call vs. 8.
* `bPreventOverlap` adds an O(n²) overlap-resolution pass per frame. Disable it when many indicators are visible simultaneously.
* The `MaxVisibleDistance` / `MinVisibleDistance` culling happens before projection, which avoids the projection call entirely for out-of-range actors.
* `UIndicatorLayer` uses `OnPaint` (Slate immediate mode) — there are no UMG invalidation boxes involved. Each frame is a full repaint of indicator positions.

***

## Arrow Rendering

When `bClampToScreen=true` and `bShowClampToScreenArrow=true`, `UIndicatorLayer` draws `ArrowBrush` at the clamped position. The brush is rotated to point from the clamped edge position toward the raw (off-screen) projected point.

The arrow brush is configured via `UIndicatorAction_ArrowStyle`. It is a `FSlateBrush` — set a `UTexture2D` or `UMaterialInstance` as the resource.
