diff --git a/src/clientSideScene/CameraControls.ts b/src/clientSideScene/CameraControls.ts index cfbf09feb..9d16d9cc6 100644 --- a/src/clientSideScene/CameraControls.ts +++ b/src/clientSideScene/CameraControls.ts @@ -29,6 +29,7 @@ import * as TWEEN from '@tweenjs/tween.js' import { isQuaternionVertical } from './helpers' import { reportRejection } from 'lib/trap' import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType' +import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models' const ORTHOGRAPHIC_CAMERA_SIZE = 20 const FRAMES_TO_ANIMATE_IN = 30 @@ -406,7 +407,7 @@ export class CameraControls { .sub(this.mouseDownPosition) this.mouseDownPosition.copy(this.mouseNewPosition) - const interaction = this.getInteractionType(event) + let interaction = this.getInteractionType(event) if (interaction === 'none') return // If there's a valid interaction and the mouse is moving, @@ -753,8 +754,6 @@ export class CameraControls { didChange = true } - this.safeLookAtTarget(this.camera.up) - // Update the camera's matrices this.camera.updateMatrixWorld() if (didChange || forceUpdate) { @@ -1189,14 +1188,24 @@ export class CameraControls { this.deferReactUpdate(this.reactCameraProperties) Object.values(this._camChangeCallbacks).forEach((cb) => cb()) } - getInteractionType = (event: MouseEvent) => - _getInteractionType( + getInteractionType = ( + event: MouseEvent + ): CameraDragInteractionType_type | 'none' => { + const initialInteractionType = _getInteractionType( this.interactionGuards, event, this.enablePan, this.enableRotate, this.enableZoom ) + if ( + initialInteractionType === 'rotate' && + this.engineCommandManager.settings.cameraOrbit === 'trackball' + ) { + return 'rotatetrackball' + } + return initialInteractionType + } } // Pure function helpers diff --git a/src/components/ModelingMachineProvider.tsx b/src/components/ModelingMachineProvider.tsx index a7fb59692..a32eb7388 100644 --- a/src/components/ModelingMachineProvider.tsx +++ b/src/components/ModelingMachineProvider.tsx @@ -119,6 +119,7 @@ export const ModelingMachineProvider = ({ cameraProjection, highlightEdges, showScaleGrid, + cameraOrbit, }, }, }, @@ -1154,6 +1155,7 @@ export const ModelingMachineProvider = ({ enableSSAO: enableSSAO.current, showScaleGrid: showScaleGrid.current, cameraProjection: cameraProjection.current, + cameraOrbit: cameraOrbit.current, }, token ) @@ -1183,6 +1185,13 @@ export const ModelingMachineProvider = ({ editorManager.selectionRanges = modelingState.context.selectionRanges }, [modelingState.context.selectionRanges]) + // When changing camera modes reset the camera to the default orientation to correct + // the up vector otherwise the conconical orientation for the camera modes will be + // wrong + useEffect(() => { + sceneInfra.camControls.resetCameraPosition().catch(reportRejection) + }, [cameraOrbit.current]) + useEffect(() => { const onConnectionStateChanged = ({ detail }: CustomEvent) => { // If we are in sketch mode we need to exit it. diff --git a/src/hooks/useSetupEngineManager.ts b/src/hooks/useSetupEngineManager.ts index cb23e666f..1b41ee5a2 100644 --- a/src/hooks/useSetupEngineManager.ts +++ b/src/hooks/useSetupEngineManager.ts @@ -16,14 +16,15 @@ export function useSetupEngineManager( streamRef: React.RefObject, modelingSend: ReturnType['send'], modelingContext: ReturnType['context'], - settings = { + settings: SettingsViaQueryString = { pool: null, theme: Themes.System, highlightEdges: true, enableSSAO: true, showScaleGrid: false, cameraProjection: 'perspective', - } as SettingsViaQueryString, + cameraOrbit: 'spherical', + }, token?: string ) { const networkContext = useNetworkContext() diff --git a/src/lang/std/engineConnection.ts b/src/lang/std/engineConnection.ts index 9ce37c119..61b2d7e48 100644 --- a/src/lang/std/engineConnection.ts +++ b/src/lang/std/engineConnection.ts @@ -1389,6 +1389,7 @@ export class EngineCommandManager extends EventTarget { enableSSAO: true, showScaleGrid: false, cameraProjection: 'perspective', + cameraOrbit: 'spherical', } } @@ -1437,6 +1438,7 @@ export class EngineCommandManager extends EventTarget { enableSSAO: true, showScaleGrid: false, cameraProjection: 'orthographic', + cameraOrbit: 'spherical', }, // When passed, use a completely separate connecting code path that simply // opens a websocket and this is a function that is called when connected. diff --git a/src/lib/settings/initialSettings.tsx b/src/lib/settings/initialSettings.tsx index d2cd06df9..362120494 100644 --- a/src/lib/settings/initialSettings.tsx +++ b/src/lib/settings/initialSettings.tsx @@ -20,6 +20,7 @@ import { toSync } from 'lib/utils' import { reportRejection } from 'lib/trap' import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType' import { OnboardingStatus } from 'wasm-lib/kcl/bindings/OnboardingStatus' +import { CameraOrbitType } from 'wasm-lib/kcl/bindings/CameraOrbitType' /** * A setting that can be set at the user or project level @@ -380,6 +381,30 @@ export function createSettings() { })), }, }), + /** + * What methodology to use for orbiting the camera + */ + cameraOrbit: new Setting({ + defaultValue: 'spherical', + hideOnLevel: 'project', + description: 'What methodology to use for orbiting the camera', + validate: (v) => ['spherical', 'trackball'].includes(v), + commandConfig: { + inputType: 'options', + defaultValueFromContext: (context) => + context.modeling.cameraOrbit.current, + options: (cmdContext, settingsContext) => + (['spherical', 'trackball'] as const).map((v) => ({ + name: v.charAt(0).toUpperCase() + v.slice(1), + value: v, + isCurrent: + settingsContext.modeling.cameraOrbit.shouldShowCurrentLabel( + cmdContext.argumentsToSubmit.level as SettingsLevel, + v + ), + })), + }, + }), /** * Whether to highlight edges of 3D objects */ diff --git a/src/lib/settings/settingsTypes.ts b/src/lib/settings/settingsTypes.ts index 1e357c4c6..a9de440eb 100644 --- a/src/lib/settings/settingsTypes.ts +++ b/src/lib/settings/settingsTypes.ts @@ -4,6 +4,7 @@ import { AtLeast, PathValue, Paths } from 'lib/types' import { CommandArgumentConfig } from 'lib/commandTypes' import { Themes } from 'lib/theme' import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType' +import { CameraOrbitType } from 'wasm-lib/kcl/bindings/CameraOrbitType' export interface SettingsViaQueryString { pool: string | null @@ -12,6 +13,7 @@ export interface SettingsViaQueryString { enableSSAO: boolean showScaleGrid: boolean cameraProjection: CameraProjectionType + cameraOrbit: CameraOrbitType } export enum UnitSystem { diff --git a/src/lib/settings/settingsUtils.ts b/src/lib/settings/settingsUtils.ts index bb1c7fe5c..b847a7afc 100644 --- a/src/lib/settings/settingsUtils.ts +++ b/src/lib/settings/settingsUtils.ts @@ -49,6 +49,7 @@ export function configurationToSettingsPayload( modeling: { defaultUnit: configuration?.settings?.modeling?.base_unit, cameraProjection: configuration?.settings?.modeling?.camera_projection, + cameraOrbit: configuration?.settings?.modeling?.camera_orbit, mouseControls: mouseControlsToCameraSystem( configuration?.settings?.modeling?.mouse_controls ), diff --git a/src/wasm-lib/kcl/src/settings/types/mod.rs b/src/wasm-lib/kcl/src/settings/types/mod.rs index 9c6af29b4..789333dfd 100644 --- a/src/wasm-lib/kcl/src/settings/types/mod.rs +++ b/src/wasm-lib/kcl/src/settings/types/mod.rs @@ -259,6 +259,9 @@ pub struct ModelingSettings { /// The projection mode the camera should use while modeling. #[serde(default, alias = "cameraProjection", skip_serializing_if = "is_default")] pub camera_projection: CameraProjectionType, + /// The methodology the camera should use to orbit around the model. + #[serde(default, alias = "cameraOrbit", skip_serializing_if = "is_default")] + pub camera_orbit: CameraOrbitType, /// The controls for how to navigate the 3D view. #[serde(default, alias = "mouseControls", skip_serializing_if = "is_default")] pub mouse_controls: MouseControlType, @@ -415,6 +418,21 @@ pub enum CameraProjectionType { Orthographic, } +/// The types of camera orbit methods. +#[derive(Debug, Default, Eq, PartialEq, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, Display, FromStr)] +#[ts(export)] +#[serde(rename_all = "snake_case")] +#[display(style = "snake_case")] +pub enum CameraOrbitType { + /// Orbit using a spherical camera movement. + #[default] + #[display("spherical")] + Spherical, + /// Orbit using a trackball camera movement. + #[display("trackball")] + Trackball, +} + /// Settings that affect the behavior of the KCL text editor. #[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Eq, Validate)] #[serde(rename_all = "snake_case")] @@ -543,6 +561,8 @@ mod tests { use pretty_assertions::assert_eq; use validator::Validate; + use crate::settings::types::CameraOrbitType; + use super::{ AppColor, AppSettings, AppTheme, AppearanceSettings, CameraProjectionType, CommandBarSettings, Configuration, ModelingSettings, OnboardingStatus, ProjectSettings, Settings, TextEditorSettings, UnitLength, @@ -594,6 +614,7 @@ textWrapping = true modeling: ModelingSettings { base_unit: UnitLength::In, camera_projection: CameraProjectionType::Orthographic, + camera_orbit: Default::default(), mouse_controls: Default::default(), highlight_edges: Default::default(), show_debug_panel: true, @@ -656,6 +677,7 @@ includeSettings = false modeling: ModelingSettings { base_unit: UnitLength::Yd, camera_projection: Default::default(), + camera_orbit: Default::default(), mouse_controls: Default::default(), highlight_edges: Default::default(), show_debug_panel: true, @@ -723,6 +745,7 @@ defaultProjectName = "projects-$nnn" modeling: ModelingSettings { base_unit: UnitLength::Yd, camera_projection: Default::default(), + camera_orbit: CameraOrbitType::Spherical, mouse_controls: Default::default(), highlight_edges: Default::default(), show_debug_panel: true, @@ -802,6 +825,7 @@ projectDirectory = "/Users/macinatormax/Documents/kittycad-modeling-projects""#; modeling: ModelingSettings { base_unit: UnitLength::Mm, camera_projection: Default::default(), + camera_orbit: Default::default(), mouse_controls: Default::default(), highlight_edges: true.into(), show_debug_panel: false, diff --git a/src/wasm-lib/kcl/src/settings/types/project.rs b/src/wasm-lib/kcl/src/settings/types/project.rs index eb28298ff..533908c9b 100644 --- a/src/wasm-lib/kcl/src/settings/types/project.rs +++ b/src/wasm-lib/kcl/src/settings/types/project.rs @@ -129,6 +129,7 @@ includeSettings = false modeling: ModelingSettings { base_unit: UnitLength::Yd, camera_projection: Default::default(), + camera_orbit: Default::default(), mouse_controls: Default::default(), highlight_edges: Default::default(), show_debug_panel: true,