Camera and Xbox controller controls
Model Studio mouse and keyboard
The Camera panel selects what a left-mouse drag does: Orbit model, Pan camera, or Free look. Orbit moves around the current target. Pan translates the camera and its target together. Free look changes the target and pointing direction while keeping the camera position fixed. Switching modes does not jump the view.
- Shift + left drag, right drag, or middle drag: pan regardless of selected mode.
- Alt + left drag: free look regardless of selected mode.
- Scroll: zoom/dolly along the viewing direction.
- Click the 3D viewport, then use W/S forward/back, A/D left/right, Q/E down/up, and arrow keys to look.
- F: frame the selected model and return to orbit mode. C: toggle orbit/free look.
- Space: pause/resume animation. Camera navigation remains available while paused.
Camera movement is independent of the model's Rotation and Scale controls. Scaling a model now changes its visible size without automatically moving the camera; use Frame selected afterward if needed.
Connect and activate
Use the Xbox controller already paired with the computer. Open the workshop through localhost or HTTPS, focus the browser tab, and press/release a controller button so the browser exposes the device. The Xbox Controller panel reports detection and readiness. Click Focus 3D view for control when returning from a form field. Release sticks, triggers and buttons after reconnecting or changing focus; neutral controls re-arm input.
The implementation uses the browser's W3C standard gamepad layout. It does not pair Bluetooth devices or assume raw axis/button indices for unrecognized layouts. The Xbox system/Guide button remains managed by the browser/operating system. Controller controls & settings lists all assigned controls, live input values, sensitivity and deadzone settings. The default radial stick deadzone is 15%; a small trigger deadzone also removes noise. Settings persist between scenes for the current page session.
| Input | Model Studio | Flight |
|---|---|---|
| Left stick | Pan camera | Bank; pull back to raise nose |
| Right stick | Orbit, or free look in Free look mode | Look around pilot/chase view |
| LT / RT | Zoom out / in | Decrease / increase throttle |
| LB / RB | Rotate selected model | Left / right rudder |
| D-pad left / right | Select previous / next existing model | Toggle navigation / landing lights |
| D-pad up / down | Increase / decrease model scale | Retract / extend flaps |
| A / B | First / second model action, when available | Switch view / center look |
| X | Frame selected | Toggle gear |
| Y | Toggle orbit/free look | Toggle guided/manual |
| Hold left-stick click | Precision camera/model movement | Click levels wings |
| Right-stick click | Frame selected | Center look |
| Menu | Pause / resume | Pause / resume |
| View | Show / hide controller guide | Show / hide controller guide |
In flight, moving flight axes or throttle takes manual control from the guided tour. Right-stick looking alone does not. Reverse flight pitch direction is available in controller settings. During landscape inspection, the sticks pan/orbit and triggers zoom; A returns to flight. See FLIGHT_CONTROLLER.md for exact flight integration.
Model loading and arbitrary model-specific property editing remain in the library/inspector panel. Controller A/B invoke actions supplied by the selected package; no action is invented for a package that exposes none. Camera/model transforms remain available for every compatible model.
Reuse in another application
OrbitControls remains in the engine with default keyboard input disabled. Enable {keyboard:true} for a focused viewport and call tick(realDt) from the consumer's input loop. Public methods are setMode(mode), orbitBy(yawRadians, pitchRadians), lookBy(yawRadians, pitchRadians), panBy(right, up, forward=0) in world units relative to the camera, and zoomBy(logAmount) (positive out). Positive look yaw turns left; positive look pitch aims upward. Movement methods return whether they changed the view and invoke onChange. Disposal clears keys, captures and listeners.
GamepadInput is a separate reusable engine module. It supplies neutral, deadzone-filtered left, right, lt, rt, down, and rising-edge pressed values. poll({active}) returns connection/mapping/arming metadata plus input state. Unsupported mappings and inactive input return neutral commands. select(index) supports explicit device selection in other consumers; the workshop automatically retains its current controller or chooses a standard-mapped one. Injecting a provider is supported for deterministic tests.
The shell owns the mappings and a lightweight input animation loop separate from simulation time. This permits Menu to resume a paused renderer and permits paused camera movement. Blur, hidden-page and disconnect events clear stored commands immediately, even when animation-frame polling is suspended. The next active input requires a neutral pose. Editing input/select fields suspends controller actions. Aircraft simulation time and commands remain paused during camera inspection.
Verification scope
tests/gamepad-input.test.mjs, camera-controls.test.mjs and flight-controls.test.mjs cover 22 focused cases. tests/controller.html exercises 13 integration checks with actual rendered Studio/Flight scenes and synthetic controller snapshots, including the UI polling loop while paused. These are not physical wireless-controller tests. See VERIFICATION.md and verification/controller-v022.json for the final device status and packaged scope.