InputController
Maps mouse buttons, finger counts, the wheel and pinches onto axes. On the core and in three.js it's an InputControllerDom from @pmndrs/klipp/dom that takes the axes themselves. In React, <InputController> finds them by name in the piece it's placed in. Placed directly in a <VirtualCamera>, it finds them in any of the camera's pieces.
Settings
Mapping a source
List only the sources you use. Each button and finger count takes the same config:
{
axes: { x: 'pan', y: 'tilt' }, // which axis receives the horizontal and vertical drag: names in React, the axes themselves elsewhere
gain: 0.15, // multiplier from pixels to axis units
invert: { y: true }, // flip one or both directions, or `true` for both
}
Leave x or y out to ignore that direction: axes: { x: 'pan' } turns only horizontal drags into input.
Wheel and pinch
wheel and pinch each drive a single axis:
{
axis: 'radial', // a name in React, the axis itself elsewhere
gain: 0.002,
invert: true,
}
A pinch feels the same on a touch screen and on a trackpad, and adds up to the same amount however far you are zoomed in. That suits a zoom stored as ln of its scale. The wheel moves in pixels, so it needs its own gain.
The page keeps scrolling over the canvas until wheel is mapped, and a trackpad pinch keeps zooming the page until pinch is mapped.
Pointer lock
For mouse look without holding a button, request pointer lock on the controller's inputSystem. While locked, mouse movement goes to mouseButtons.left.
Raw input
input holds this frame's raw deltas, for example wheelDeltaY, pinchDelta or leftDx, if you want to handle something yourself.
Usage
import { InputControllerDom } from '@pmndrs/klipp/dom';
const look = { axes: { x: aim.pan, y: aim.tilt }, gain: 0.15 };
const controller = new InputControllerDom({
mouseButtons: { right: look },
touches: { one: look },
});
controller.inputSystem.suppressContextMenu = true;
klipp.whileOnScreen(camera, () => {
controller.connect(canvas);
return () => controller.disconnect();
});
klipp.registerUpdate(() => controller.update());
whileOnScreen connects it only while its camera is on screen, so several cameras don't react to the same drag. controller.enabled = false keeps it connected but ignores input.
Pointer lock
// in a click handler
controller.inputSystem.requestPointerLock();
controller.inputSystem.exitPointerLock();
Raw input
klipp.registerUpdate(() => {
controller.update();
roll += controller.input.twistDelta;
});
import { InputControllerDom } from '@pmndrs/klipp/dom';
const look = { axes: { x: aim.pan, y: aim.tilt }, gain: 0.15 };
const controller = new InputControllerDom({
mouseButtons: { right: look },
touches: { one: look },
});
controller.inputSystem.suppressContextMenu = true;
klipp.whileOnScreen(camera, () => {
controller.connect(renderer.domElement);
return () => controller.disconnect();
});
klipp.registerUpdate(() => controller.update());
whileOnScreen connects it only while its camera is on screen, so several cameras don't react to the same drag. controller.enabled = false keeps it connected but ignores input.
Pointer lock
// in a click handler
controller.inputSystem.requestPointerLock();
controller.inputSystem.exitPointerLock();
Raw input
klipp.registerUpdate(() => {
controller.update();
roll += controller.input.twistDelta;
});
<Aim.PanTilt>
<InputController
mouseButtons={{ right: { axes: { x: 'pan', y: 'tilt' }, gain: 0.15 } }}
touches={{ one: { axes: { x: 'pan', y: 'tilt' }, gain: 0.15 } }}
suppressContextMenu
/>
</Aim.PanTilt>
Input is only read while its camera is on screen. enabled={false} keeps it listening but ignores input; unmount it to stop listening, for example to let the page scroll over the canvas. ref gives you the InputControllerDom.
Pointer lock
const controller = useRef<InputControllerDom>(null);
<InputController ref={controller} mouseButtons={{ left: { axes: { x: 'pan', y: 'tilt' } } }} />;
// in a click handler
controller.current.inputSystem.requestPointerLock();
controller.current.inputSystem.exitPointerLock();
Raw input
useFrame(() => {
roll += controller.current.input.twistDelta;
});