Skip to content

Cabinet projection, display rotation and native input cut-off - #589

Merged
freezy merged 9 commits into
masterfrom
feat/cabinet-view
Oct 15, 2026
Merged

freezy merged 9 commits into
masterfrom
feat/cabinet-view

Conversation

@freezy

@freezy freezy commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Adds what the player needs for portrait and cabinet setups: a quarter-turned output for side-mounted screens, and a cabinet projection that renders the playfield as seen through a physical screen lying in a cabinet.

Changes

  • Display rotation (Game/DisplayRotation.cs): turns the camera output by ±90° for screens mounted on their side. The roll and the lens adjustment are applied in the render pipeline's context callbacks, since HDRP computes view and culling before beginCameraRendering.
  • Cabinet projection (Rendering/CabinetProjection*.cs): a solver that places the table under a physical screen (size, inclination, eye position) and builds an off-centre frustum through the screen's corners. Fit policies are physical size, fit, fit stretched and fill length (the table's top and bottom line up with the screen frame, the default). A render-pipeline adapter interface (ICabinetProjectionAdapter) lets HDRP and URP apply it. Covered by CabinetProjectionSolverTests.
  • Packaging: the balls a trough references are packaged with the table, and CabinetComponent / BackboxComponent markers are packaged, so the player can hide cabinet and backbox in the cabinet projection.
  • Native input cut-off: while the game is paused or the window isn't focused, the input polling thread relays nothing to the game. A gate epoch tags each relayed event. After a cut-off the simulation releases held actions and drops events from before it, while keeping fresh ones. Stick nudge channels return to rest and are refreshed from the native axis state when input opens again.
  • Lamps (c5e72c6d8): wake lights on a positive fading level.

Related PRs

Adds DisplayRotation, which rolls screen cameras about their view axis and
turns their lens (field of view, or sensor, lens shift and gate fit for
physical cameras) while they render, then restores them. Between frames the
cameras stay logical, with the turned image's aspect, so camera scripts are
unaffected. It hooks the context rendering callbacks because HDRP derives a
camera's view and culling before raising beginCameraRendering.

The orbit camera and the ball debug raycast convert mouse input into the
turned frame.
WindowProjectionSolver computes the camera that renders the table as if
the physical playfield screen were a window into it, seen from a fixed
eye: a camera at the eye and an off-centre frustum through the screen's
corners. The table is placed by a translation, a rotation about its
lateral axis and coupled length/height scaling, so the screen stays a
rectangle in table space and the result is an ordinary frustum. The
output's quarter turns are baked into the corner order.

WindowProjectionCamera applies a solved projection to a camera and
restores the camera's own state when disabled. Render pipelines plug in
through IWindowProjectionAdapter (metadata, history reset, effective
anti-aliasing). DisplayRotation leaves such a camera alone.

Tests cover corner mapping for all quarter turns, agreement with the
ray-intersection model in float32, the VPX axis mapping, mirroring,
VPX's scale-origin offset and invalid setups.
A SingleFading or Rgb mapping only sends levels, and the lamp player starts
non-RGB lamps Off, so a plain LightComponent mapped that way never lit.
LightApi now switches an off light on when it receives a positive level,
like InsertLightApi already does.
The balls a trough creates come from prefabs outside the table, so packaged
tables fell back to the default ball. The export now copies each prefab the
active troughs reference under the table as a template, points the troughs
at the copies while writing and restores everything afterwards, even when
the package can't be closed. BallComponent is packaged on these templates
only (radius and mass); balls placed in a scene still export as their mesh.
The trough's references carry the templates, which it deactivates on
import; the ball manager activates balls instantiated from them.

CabinetComponent and BackboxComponent are packaged as empty markers, so the
player can find the cabinet and backbox.
FillLength scales the table uniformly and moves it along the screen so
that, seen from the eye, the playfield's front and rear edges meet the
screen's front and rear edges. Both are found by bisection; a setup no
scale and shift can satisfy is rejected instead of applied.
The input polling thread relays nothing to the game while the window
isn't focused or the game is paused. A gate epoch tags every relayed
event, so the simulation releases held actions after a cut-off and drops
events from before it while keeping fresh ones, and stick nudge channels
return to rest and are refreshed from the native axis state on reopen.
@greptile-apps

greptile-apps Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no new actionable issue was found.

Summary

Adds display rotation and cabinet projection, packages trough ball templates and cabinet markers, and wakes fading lamps at positive levels.

  • Cabinet measurements shape the playfield view on screen.
  • Game cameras can turn their picture for side-mounted screens.
  • Table packages carry trough balls and cabinet markers.
  • Native input stops reaching the game while it is paused.
  • Positive fading levels turn off lamps on again.

Reviews (3) · Last reviewed commit: "input: just stop relaying native input w..." · Reviewed by Greptile

Comment thread VisualPinball.Unity/VisualPinball.Unity/Simulation/SimulationThread.cs Outdated
Comment thread VisualPinball.Unity/VisualPinball.Unity/Rendering/CabinetProjectionCamera.cs Outdated
Comment thread VisualPinball.Unity/VisualPinball.Unity.Editor/Packaging/PackageWriter.cs Outdated
…ack failed ball templates

- The simulation releases held actions when it pauses instead of on
  resume, so a flipper held when pausing doesn't stay on.
- The cabinet projection camera restores a custom aspect it found
  instead of always resetting it.
- Exporting gives the troughs their ball prefabs back and removes the
  template copies when copying a template fails.
Pausing with a flipper up is how a player keeps the ball, so nothing is
released when the input is cut off: the gate only drops events while the
game is paused or the window isn't focused. On focus loss the native
poller releases held keys itself. This replaces the gate epoch, the
made-up releases and the nudge axis refresh.
@freezy
freezy merged commit 929f360 into master Oct 15, 2026
15 checks passed
@freezy
freezy deleted the feat/cabinet-view branch October 15, 2026 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant