Add a trackball camera setting (#4764)

* Add a setting that does nothing

* Make the setting actually change the interaction type

* fmt

* Bump `@kittycad/lib` to get the proper camera drag interaction types

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Fix camera orientation bugs to support proper camera resetting on "camera orbit" setting change (#5031)

* Add a setting that does nothing

* Make the setting actually change the interaction type

* fmt

* fix: up vector bug fix and camera reset fix. Pushing code to cleanup after debugging

* fix: deleting debugging code

* fix: removing debugging code

* fix: removing debugging console log

* fix: removing console log debugs

* fix: adding comment, restoring code from debugging

* fix: removed lookAt when the orientation is already set from the engine.. I do not think we should be recomputing it?

* fix: this fixes the bug because I was pointing to the getter not the value

* Remove unused imports

* Fix lint for unawaited Promise

* Remove pointless change

---------

Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Frank Noirot <frankjohnson1993@gmail.com>

* Re-run CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Re-run CI

* Add display attributes to try to fix cargo test

* Remove backwards compat test case

it's failing because I didn't add cameraOrbit to that type and I don't
want to

* Fix test value (prev user value would have been Spherical before Trackball)

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Kevin Nadro <nadr0@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierre@zoo.dev>
This commit is contained in:
Frank Noirot
2025-02-01 15:03:04 -05:00
committed by GitHub
parent 229433126d
commit f1a458f124
9 changed files with 81 additions and 7 deletions

View File

@ -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

View File

@ -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.

View File

@ -16,14 +16,15 @@ export function useSetupEngineManager(
streamRef: React.RefObject<HTMLDivElement>,
modelingSend: ReturnType<typeof useModelingContext>['send'],
modelingContext: ReturnType<typeof useModelingContext>['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()

View File

@ -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.

View File

@ -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<CameraOrbitType>({
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
*/

View File

@ -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 {

View File

@ -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
),

View File

@ -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,

View File

@ -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,