diff --git a/src/clientSideScene/CameraControls.ts b/src/clientSideScene/CameraControls.ts index 2e45fbdc8..0eb9997d5 100644 --- a/src/clientSideScene/CameraControls.ts +++ b/src/clientSideScene/CameraControls.ts @@ -34,7 +34,7 @@ const FRAMES_TO_ANIMATE_IN = 30 const tempQuaternion = new Quaternion() // just used for maths -type interactionType = 'pan' | 'rotate' | 'zoom' +export type CameraInteractionType = 'pan' | 'rotate' | 'zoom' interface ThreeCamValues { position: Vector3 @@ -1219,8 +1219,8 @@ function _getInteractionType( enablePan: boolean, enableRotate: boolean, enableZoom: boolean -): interactionType | 'none' { - let state: interactionType | 'none' = 'none' +): CameraInteractionType | 'none' { + let state: CameraInteractionType | 'none' = 'none' if (enablePan && interactionGuards.pan.callback(event)) return 'pan' if (enableRotate && interactionGuards.rotate.callback(event)) return 'rotate' if (enableZoom && interactionGuards.zoom.dragCallback(event)) return 'zoom' diff --git a/src/components/ModelingMachineProvider.tsx b/src/components/ModelingMachineProvider.tsx index 406a922d9..527248831 100644 --- a/src/components/ModelingMachineProvider.tsx +++ b/src/components/ModelingMachineProvider.tsx @@ -105,7 +105,7 @@ export const ModelingMachineProvider = ({ settings: { context: { app: { theme, enableSSAO }, - modeling: { defaultUnit, highlightEdges, showScaleGrid }, + modeling: { defaultUnit, highlightEdges, showScaleGrid, mouseControls }, }, }, } = useSettingsAuthContext() @@ -509,6 +509,7 @@ export const ModelingMachineProvider = ({ settings: { theme: theme.current, highlightEdges: highlightEdges.current, + mouseControls: mouseControls.current, }, }).catch(reportRejection) }, diff --git a/src/components/ToastTextToCad.tsx b/src/components/ToastTextToCad.tsx index 6f148bce8..a2c3985f9 100644 --- a/src/components/ToastTextToCad.tsx +++ b/src/components/ToastTextToCad.tsx @@ -5,7 +5,7 @@ import { isDesktop } from 'lib/isDesktop' import { PATHS } from 'lib/paths' import toast from 'react-hot-toast' import { TextToCad_type } from '@kittycad/lib/dist/types/src/models' -import { useCallback, useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Box3, Color, @@ -15,6 +15,7 @@ import { LineSegments, Mesh, MeshBasicMaterial, + MOUSE, OrthographicCamera, Scene, Vector3, @@ -29,6 +30,15 @@ import { commandBarMachine } from 'machines/commandBarMachine' import { EventFrom } from 'xstate' import { fileMachine } from 'machines/fileMachine' import { reportRejection } from 'lib/trap' +import { + CameraControls, + CameraInteractionType, +} from 'clientSideScene/CameraControls' +import { + cameraMouseDragGuards, + CameraSystem, + MouseGuard, +} from 'lib/cameraControls' const CANVAS_SIZE = 128 const PROMPT_TRUNCATE_LENGTH = 128 @@ -118,8 +128,14 @@ export function ToastTextToCadSuccess({ settings: { theme: Themes highlightEdges: boolean + mouseControls: CameraSystem } }) { + const interactionGuards = useMemo( + () => cameraMouseDragGuards[settings.mouseControls], + [settings.mouseControls] + ) + const controlsRef = useRef(null) const wrapperRef = useRef(null) const canvasRef = useRef(null) const animationRequestRef = useRef() @@ -168,8 +184,59 @@ export function ToastTextToCadSuccess({ const ambientLight = new DirectionalLight(new Color('white'), 8.0) scene.add(ambientLight) const camera = createCamera() + // Because this listener is registered before the OrbitControls are created, + // it runs first and can block the OrbitControls from working. + renderer.domElement.addEventListener('pointerdown', (e) => { + if (!controlsRef.current) return + const newInteractionType = getCameraInteractionType({ + interactionGuards, + event: e, + }) + console.log('newInteractionType', newInteractionType) + + if (newInteractionType === 'none') { + e.stopImmediatePropagation() + } + + /** + * Update the OrbitControls to enable only the current interaction type. + * This is a hack to override the interaction types of the OrbitControls + * to match ours. In the future, we should roll our own class based on OrbitControls, + * which can handle interaction guards that are more complex than just mouse buttons. + */ + if (newInteractionType === 'pan') { + controlsRef.current.enablePan = true + controlsRef.current.enableZoom = false + controlsRef.current.enableRotate = false + controlsRef.current.mouseButtons = { + LEFT: MOUSE.PAN, + MIDDLE: MOUSE.PAN, + RIGHT: MOUSE.PAN, + } + } else if (newInteractionType === 'zoom') { + controlsRef.current.enablePan = false + controlsRef.current.enableZoom = true + controlsRef.current.enableRotate = false + controlsRef.current.mouseButtons = { + LEFT: MOUSE.DOLLY, + MIDDLE: MOUSE.DOLLY, + RIGHT: MOUSE.DOLLY, + } + } else if (newInteractionType === 'rotate') { + controlsRef.current.enablePan = false + controlsRef.current.enableZoom = false + controlsRef.current.enableRotate = true + controlsRef.current.mouseButtons = { + LEFT: MOUSE.ROTATE, + MIDDLE: MOUSE.ROTATE, + RIGHT: MOUSE.ROTATE, + } + } + + controls.update() + }) const controls = new OrbitControls(camera, renderer.domElement) - controls.enableDamping = true + controlsRef.current = controls const loader = new GLTFLoader() const dracoLoader = new DRACOLoader() dracoLoader.setDecoderPath('/examples/jsm/libs/draco/') @@ -270,7 +337,7 @@ export function ToastTextToCadSuccess({ }, []) return ( -
+
+const noModifiersPressed = (e: React.MouseEvent | MouseEvent) => !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey export type CameraSystem = @@ -53,14 +53,14 @@ export function mouseControlsToCameraSystem( interface MouseGuardHandler { description: string - callback: (e: React.MouseEvent) => boolean + callback: (e: React.MouseEvent | MouseEvent) => boolean lenientDragStartButton?: number } interface MouseGuardZoomHandler { description: string - dragCallback: (e: React.MouseEvent) => boolean - scrollCallback: (e: React.MouseEvent) => boolean + dragCallback: (e: React.MouseEvent | MouseEvent) => boolean + scrollCallback: (e: React.MouseEvent | MouseEvent) => boolean lenientDragStartButton?: number } @@ -70,7 +70,7 @@ export interface MouseGuard { rotate: MouseGuardHandler } -export const btnName = (e: React.MouseEvent) => ({ +export const btnName = (e: React.MouseEvent | MouseEvent) => ({ middle: !!(e.buttons & 4) || e.button === 1, right: !!(e.buttons & 2) || e.button === 2, left: !!(e.buttons & 1) || e.button === 0, diff --git a/src/lib/textToCad.ts b/src/lib/textToCad.ts index 72db6724f..44d27c58a 100644 --- a/src/lib/textToCad.ts +++ b/src/lib/textToCad.ts @@ -16,6 +16,7 @@ import { commandBarMachine } from 'machines/commandBarMachine' import { getNextFileName } from './desktopFS' import { reportRejection } from './trap' import { toSync } from './utils' +import { CameraSystem } from './cameraControls' export async function submitTextToCadPrompt( prompt: string, @@ -77,6 +78,7 @@ interface TextToKclProps { settings: { theme: Themes highlightEdges: boolean + mouseControls: CameraSystem } }