UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

326 lines (302 loc) 11.1 kB
/** * Creates a pan/zoom controls for a camera * * */ import {Camera, MOUSE, TOUCH} from 'three'; import {TypedCameraControlsEventNode} from './_BaseCameraControls'; import {NodeParamsConfig, ParamConfig} from '../utils/params/ParamsConfig'; import {EventConnectionPoint, EventConnectionPointType} from '../utils/io/connections/Event'; import {CameraControlsNodeType} from '../../poly/NodeContext'; import {isBooleanTrue} from '../../../core/BooleanValue'; import {PanZoomControls} from '../../../modules/core/controls/PanZoomControls'; const OUTPUT_START = 'start'; const OUTPUT_CHANGE = 'change'; const OUTPUT_END = 'end'; export enum MouseControl { PAN = 'pan', DOLLY = 'dolly', NO_ACTION = 'no action', } export const MOUSE_CONTROLS: MouseControl[] = [MouseControl.DOLLY, MouseControl.PAN, MouseControl.NO_ACTION]; enum TouchControl { PAN = 'pan', DOLLY_PAN = 'dolly + pan', NO_ACTION = 'no action', } const TOUCH_CONTROLS: TouchControl[] = [TouchControl.PAN, TouchControl.DOLLY_PAN, TouchControl.NO_ACTION]; type ThreeMouseControl = 0 | 1 | 2; type ThreeTouchControl = 0 | 1 | 2 | 3; const THREE_MOUSE_BY_MOUSE_CONTROL: Record<MouseControl, ThreeMouseControl | null> = { [MouseControl.DOLLY]: MOUSE.DOLLY, [MouseControl.PAN]: MOUSE.PAN, [MouseControl.NO_ACTION]: null, }; const THREE_TOUCH_BY_TOUCH_CONTROL: Record<TouchControl, ThreeTouchControl | null> = { [TouchControl.PAN]: TOUCH.PAN, [TouchControl.DOLLY_PAN]: TOUCH.DOLLY_PAN, [TouchControl.NO_ACTION]: null, }; class CameraPanZoomEventParamsConfig extends NodeParamsConfig { main = ParamConfig.FOLDER(); /** @param enable/disable */ enabled = ParamConfig.BOOLEAN(1); /** @param toggle on to allow pan */ allowPan = ParamConfig.BOOLEAN(1); /** @param minimum distance the cursor must travel before the camera is actually moved */ panThreshold = ParamConfig.FLOAT(0, { range: [0, 10], rangeLocked: [true, false], visibleIf: { allowPan: 1, }, }); /** @param toggle on to allow zoom */ allowZoom = ParamConfig.BOOLEAN(1); /** @param zoom speed */ zoomSpeed = ParamConfig.FLOAT(1, { range: [0, 2], rangeLocked: [true, false], visibleIf: { allowZoom: 1, }, }); /** @param zoom to cursor */ zoomToCursor = ParamConfig.BOOLEAN(1, { visibleIf: { allowZoom: 1, }, }); /** @param minimum distance the cursor must travel before the camera starts zooming */ zoomThreshold = ParamConfig.FLOAT(0, { range: [0, 10], rangeLocked: [true, false], visibleIf: { allowZoom: 1, }, }); /** @param toggle on to have damping */ tdamping = ParamConfig.BOOLEAN(1); /** @param damping value */ damping = ParamConfig.FLOAT(0.1, { visibleIf: {tdamping: true}, }); /** @param toggle on to have the pan in screen space */ screenSpacePanning = ParamConfig.BOOLEAN(1); limits = ParamConfig.FOLDER(); /** @param smallest distance the camera can go to the target (perspective cameras only) */ // minDistance = ParamConfig.FLOAT(0.1, { // range: [0.1, 100], // rangeLocked: [true, false], // }); // /** @param max distance the camera can go away the target (perspective cameras only) */ // maxDistance = ParamConfig.FLOAT(50, { // range: [0, 100], // rangeLocked: [true, false], // }); /** @param min zoom (orthographic cameras only) */ minZoom = ParamConfig.FLOAT(0.01, { range: [0.01, 100], rangeLocked: [true, false], }); /** @param max zoom (orthographic cameras only) */ maxZoom = ParamConfig.FLOAT(50, { range: [0, 100], rangeLocked: [true, false], }); /** @param clamp position */ clampPosition = ParamConfig.BOOLEAN(false); /** @param min position */ positionMin = ParamConfig.VECTOR3([-10, -10, -10], { visibleIf: {clampPosition: 1}, }); /** @param max position */ positionMax = ParamConfig.VECTOR3([10, 10, 10], { visibleIf: {clampPosition: 1}, }); controls = ParamConfig.FOLDER(); /** @param leftMouseButton */ leftMouseButton = ParamConfig.INTEGER(MOUSE_CONTROLS.indexOf(MouseControl.PAN), { menu: { entries: MOUSE_CONTROLS.map((name, value) => ({name, value})), }, }); /** @param leftMouseButton */ middleMouseButton = ParamConfig.INTEGER(MOUSE_CONTROLS.indexOf(MouseControl.DOLLY), { menu: { entries: MOUSE_CONTROLS.map((name, value) => ({name, value})), }, }); /** @param leftMouseButton */ rightMouseButton = ParamConfig.INTEGER(MOUSE_CONTROLS.indexOf(MouseControl.PAN), { menu: { entries: MOUSE_CONTROLS.map((name, value) => ({name, value})), }, }); /** @param 1 finger touch */ oneFingerTouch = ParamConfig.INTEGER(TOUCH_CONTROLS.indexOf(TouchControl.PAN), { menu: { entries: TOUCH_CONTROLS.map((name, value) => ({name, value})), }, separatorBefore: true, }); /** @param 2 fingers touch */ twoFingersTouch = ParamConfig.INTEGER(TOUCH_CONTROLS.indexOf(TouchControl.DOLLY_PAN), { menu: { entries: TOUCH_CONTROLS.map((name, value) => ({name, value})), }, }); } const ParamsConfig = new CameraPanZoomEventParamsConfig(); export class CameraPanZoomControlsEventNode extends TypedCameraControlsEventNode<CameraPanZoomEventParamsConfig> { override paramsConfig = ParamsConfig; static override type() { return CameraControlsNodeType.PAN_ZOOM; } endEventName() { return 'end'; } override initializeNode() { this.io.outputs.setNamedOutputConnectionPoints([ new EventConnectionPoint(OUTPUT_START, EventConnectionPointType.BASE), new EventConnectionPoint(OUTPUT_CHANGE, EventConnectionPointType.BASE), new EventConnectionPoint(OUTPUT_END, EventConnectionPointType.BASE), ]); } private _controlsByElementId: Map<string, PanZoomControls> = new Map(); private _firstControls: PanZoomControls | undefined; protected _createControls(camera: Camera, element: HTMLElement) { return new PanZoomControls(camera, element); } async createControlsInstance(camera: Camera, element: HTMLElement) { const controls = this._createControls(camera, element); // controls.addEventListener('end', () => { // this._on_controls_end(controls); // }); this._controlsByElementId.set(element.id, controls); this._updateCache(); this._bind_listeners_to_controls_instance(controls); return controls; } protected _bind_listeners_to_controls_instance(controls: PanZoomControls) { controls.addEventListener('start', () => { this.dispatchEventToOutput(OUTPUT_START, {}); }); controls.addEventListener('change', () => { this.dispatchEventToOutput(OUTPUT_CHANGE, {}); }); controls.addEventListener('end', () => { this.dispatchEventToOutput(OUTPUT_END, {}); }); } setupControls(controls: PanZoomControls) { controls.enabled = isBooleanTrue(this.pv.enabled); controls.enablePan = isBooleanTrue(this.pv.allowPan); controls.panThreshold = this.pv.panThreshold; controls.enableZoom = isBooleanTrue(this.pv.allowZoom); controls.zoomSpeed = this.pv.zoomSpeed; controls.zoomToCursor = isBooleanTrue(this.pv.zoomToCursor); controls.dollyThreshold = this.pv.zoomThreshold; controls.enableDamping = isBooleanTrue(this.pv.tdamping); controls.dampingFactor = this.pv.damping; // controls.rotateSpeed = this.pv.rotateSpeed; controls.screenSpacePanning = isBooleanTrue(this.pv.screenSpacePanning); // controls.minDistance = this.pv.minDistance; // controls.maxDistance = this.pv.maxDistance; controls.minZoom = this.pv.minZoom; controls.maxZoom = this.pv.maxZoom; controls.clampPosition = this.pv.clampPosition; controls.positionBounds.min.copy(this.pv.positionMin); controls.positionBounds.max.copy(this.pv.positionMax); // this._set_azimuth_angle(controls); // controls.minPolarAngle = this.pv.polarAngleRange.x; // controls.maxPolarAngle = this.pv.polarAngleRange.y; // controls.target.copy(this.pv.target); if (controls.enabled) { controls.update(null); // necessary if target is not 0,0,0 } // overrides controls.mouseButtons.LEFT = THREE_MOUSE_BY_MOUSE_CONTROL[MOUSE_CONTROLS[this.pv.leftMouseButton]]; controls.mouseButtons.MIDDLE = THREE_MOUSE_BY_MOUSE_CONTROL[MOUSE_CONTROLS[this.pv.middleMouseButton]]; controls.mouseButtons.RIGHT = THREE_MOUSE_BY_MOUSE_CONTROL[MOUSE_CONTROLS[this.pv.rightMouseButton]]; controls.touches.ONE = THREE_TOUCH_BY_TOUCH_CONTROL[TOUCH_CONTROLS[this.pv.oneFingerTouch]]; controls.touches.TWO = THREE_TOUCH_BY_TOUCH_CONTROL[TOUCH_CONTROLS[this.pv.twoFingersTouch]]; // controls.touches.ONE = TOUCH.ROTATE; // controls.touches.TWO = TOUCH.DOLLY_PAN; // controls.enableKeys = isBooleanTrue(this.pv.enableKeys); // if (controls.enableKeys) { // controls.keyMode = KEYS_MODES[this.pv.keysMode]; // controls.keyRotateSpeedVertical = this.pv.keysRotateSpeedVertical; // controls.keyRotateSpeedHorizontal = this.pv.keysRotateSpeedHorizontal; // controls.keyPanSpeed = this.pv.keysPanSpeed; // } } // private _set_azimuth_angle(controls: OrbitControls) { // if (isBooleanTrue(this.pv.limitAzimuthAngle)) { // controls.minAzimuthAngle = this.pv.azimuthAngleRange.x; // controls.maxAzimuthAngle = this.pv.azimuthAngleRange.y; // } else { // controls.minAzimuthAngle = Infinity; // controls.maxAzimuthAngle = Infinity; // } // } updateRequired(): boolean { return false; // return isBooleanTrue(this.pv.tdamping); } // set_from_camera_node(controls: CameraControls, camera_node: BaseCameraObjNodeType): void { // const target = camera_node.params.vector3('target'); // controls.target.copy(target); // console.warn('set from camera node'); // } // private _on_controls_end(controls: OrbitControls) { // if (!isBooleanTrue(this.pv.updateTargetEndMoveEnd)) { // return; // } // if (!isBooleanTrue(this.pv.allowPan)) { // // target should not be updated if pan is not allowed // return; // } // controls.target.toArray(_targetArray); // this.p.target.set(_targetArray); // } // static PARAM_CALLBACK_updateTarget(node: CameraPanZoomControlsEventNode) { // node._updateTarget(); // } // private _updateTarget() { // this.setTarget(this.pv.target); // } // target(target: Vector3) { // if (!this._firstControls) { // return; // } // target.copy(this._firstControls.target); // } // setTarget(newTarget: Vector3) { // this._controlsByElementId.forEach((control, element_id) => { // const destTarget = control.target; // if (!destTarget.equals(newTarget)) { // destTarget.copy(newTarget); // control.update(); // } // }); // } disposeControlsForHtmlElementId(html_element_id: string) { // this method is important so that we can do the following steps: // 1. assign an orbit_controls to the camera // 2. remove the controls // 3. update the target param of the controls, and this doesn't affect the camera (nor should it!) const controls = this._controlsByElementId.get(html_element_id); if (controls) { // controls.dispose(); // no need to dispose here, as it is done by the viewer for now this._controlsByElementId.delete(html_element_id); } this._updateCache(); } private _updateCache() { this._firstControls = undefined; this._controlsByElementId.forEach((controls) => { this._firstControls = this._firstControls || controls; }); } }