1
- import Hammer from 'hammerjs'
2
1
import type {
3
2
CameraDragInteractionType_type ,
4
3
CameraViewState_type ,
5
4
} from '@kittycad/lib/dist/types/src/models'
5
+ import { isArray } from '@src/lib/utils'
6
+
6
7
import type { EngineStreamActor } from '@src/machines/engineStreamMachine'
7
8
import * as TWEEN from '@tweenjs/tween.js'
9
+ import Hammer from 'hammerjs'
8
10
import {
9
11
Euler ,
10
12
MathUtils ,
@@ -34,7 +36,9 @@ import type {
34
36
} from '@src/lang/std/engineConnection'
35
37
import type { MouseGuard } from '@src/lib/cameraControls'
36
38
import { cameraMouseDragGuards } from '@src/lib/cameraControls'
39
+ import type { SettingsType } from '@src/lib/settings/initialSettings'
37
40
import { reportRejection } from '@src/lib/trap'
41
+ import { err } from '@src/lib/trap'
38
42
import {
39
43
getNormalisedCoordinates ,
40
44
isReducedMotion ,
@@ -44,12 +48,18 @@ import {
44
48
uuidv4 ,
45
49
} from '@src/lib/utils'
46
50
import { deg2Rad } from '@src/lib/utils2d'
47
- import type { SettingsType } from '@src/lib/settings/initialSettings'
48
51
import { degToRad } from 'three/src/math/MathUtils'
49
52
50
53
const ORTHOGRAPHIC_CAMERA_SIZE = 20
51
54
const FRAMES_TO_ANIMATE_IN = 30
52
55
const ORTHOGRAPHIC_MAGIC_FOV = 4
56
+ const EXPECTED_WORLD_COORD_SYSTEM = 'right_handed_up_z'
57
+
58
+ // Partial declaration; the front/back/left/right are handled differently.
59
+ enum StandardView {
60
+ TOP = 'top' ,
61
+ BOTTOM = 'bottom' ,
62
+ }
53
63
54
64
const tempQuaternion = new Quaternion ( ) // just used for maths
55
65
@@ -919,28 +929,123 @@ export class CameraControls {
919
929
} )
920
930
}
921
931
932
+ async getCameraView ( ) : Promise < CameraViewState_type | Error > {
933
+ const response = await this . engineCommandManager . sendSceneCommand ( {
934
+ type : 'modeling_cmd_req' ,
935
+ cmd_id : uuidv4 ( ) ,
936
+ cmd : { type : 'default_camera_get_view' } ,
937
+ } )
938
+
939
+ // Check valid response from the engine.
940
+ const singleResponse = isArray ( response ) ? response [ 0 ] : response
941
+ const noValidResponse =
942
+ ! singleResponse ?. success || ! ( 'resp' in singleResponse )
943
+
944
+ if ( noValidResponse ) {
945
+ return new Error ( 'Failed to get camera view state: no valid response.' )
946
+ }
947
+
948
+ // Check we actually have the 'modeling_response' field.
949
+ const data = singleResponse . resp . data
950
+ const noModelingResponse = ! ( 'modeling_response' in data )
951
+
952
+ if ( noModelingResponse ) {
953
+ return new Error (
954
+ 'Failed to get camera view state: no `modeling_response`.'
955
+ )
956
+ }
957
+
958
+ // Check that we have the expected response type and the nested data.
959
+ const modelingResponse = data . modeling_response
960
+ const noData = ! ( 'data' in modelingResponse )
961
+ const wrongResponseType =
962
+ modelingResponse . type !== 'default_camera_get_view'
963
+
964
+ if ( noData || wrongResponseType ) {
965
+ return new Error ( 'Failed to get camera view state: invalid response.' )
966
+ }
967
+
968
+ return modelingResponse . data . view
969
+ }
970
+
971
+ async setCameraViewAlongZ ( direction : StandardView ) {
972
+ // Sets the camera view along the Z axis, giving us a top-down or bottom-up view.
973
+ // The approach first retrieves current camera setup, then alters pivot params,
974
+ // ultimately preserving other camera settings, e.g., ortho vs. perspective projection.
975
+
976
+ // Note: this assumes right handed, Z-up, X positive to the right.
977
+ const Z_AXIS_QUATERNIONS = {
978
+ [ StandardView . TOP ] : { x : 0 , y : 0 , z : 0 , w : 1 } ,
979
+ [ StandardView . BOTTOM ] : { x : 1 , y : 0 , z : 0 , w : 0 } ,
980
+ } as const
981
+
982
+ const cameraView = await this . getCameraView ( )
983
+
984
+ if ( err ( cameraView ) ) {
985
+ return
986
+ }
987
+
988
+ // Handle unexpected world coordinate system; should delete this eventually.
989
+ if ( cameraView . world_coord_system !== EXPECTED_WORLD_COORD_SYSTEM ) {
990
+ console . warn (
991
+ `Camera is not in the expected ${ EXPECTED_WORLD_COORD_SYSTEM } world coordinate system.
992
+ Resulting view may not match expectations.`
993
+ )
994
+ }
995
+
996
+ const cameraViewTarget : CameraViewState_type = {
997
+ ...cameraView ,
998
+ pivot_rotation : Z_AXIS_QUATERNIONS [ direction ] ,
999
+ pivot_position : {
1000
+ x : this . target . x ,
1001
+ y : this . target . y ,
1002
+ z : this . target . z ,
1003
+ } ,
1004
+ }
1005
+
1006
+ await this . engineCommandManager . sendSceneCommand ( {
1007
+ type : 'modeling_cmd_req' ,
1008
+ cmd_id : uuidv4 ( ) ,
1009
+ cmd : {
1010
+ type : 'default_camera_set_view' ,
1011
+ view : cameraViewTarget ,
1012
+ } ,
1013
+ } )
1014
+
1015
+ await this . engineCommandManager . sendSceneCommand ( {
1016
+ type : 'modeling_cmd_req' ,
1017
+ cmd_id : uuidv4 ( ) ,
1018
+ cmd : {
1019
+ type : 'default_camera_get_settings' ,
1020
+ } ,
1021
+ } )
1022
+ }
1023
+
922
1024
async updateCameraToAxis (
923
1025
axis : 'x' | 'y' | 'z' | '-x' | '-y' | '-z'
924
1026
) : Promise < void > {
1027
+ // TODO: We currently use both `default_camera_look_at` and `default_camera_set_view`
1028
+ // (via `setCameraViewAlongZ`). We should unify these during future camera work.
1029
+
925
1030
const distance = this . camera . position . distanceTo ( this . target )
926
1031
927
1032
const vantage = this . target . clone ( )
928
- let up = { x : 0 , y : 0 , z : 1 }
1033
+ const up = { x : 0 , y : 0 , z : 1 }
929
1034
930
1035
if ( axis === 'x' ) {
931
1036
vantage . x += distance
932
1037
} else if ( axis === 'y' ) {
933
1038
vantage . y += distance
934
1039
} else if ( axis === 'z' ) {
935
- vantage . z += distance
936
- up = { x : - 1 , y : 0 , z : 0 }
1040
+ await this . setCameraViewAlongZ ( StandardView . TOP )
1041
+ return
937
1042
} else if ( axis === '-x' ) {
938
1043
vantage . x -= distance
939
1044
} else if ( axis === '-y' ) {
940
1045
vantage . y -= distance
941
1046
} else if ( axis === '-z' ) {
942
- vantage . z -= distance
943
- up = { x : - 1 , y : 0 , z : 0 }
1047
+ await this . setCameraViewAlongZ ( StandardView . BOTTOM )
1048
+ return
944
1049
}
945
1050
946
1051
await this . engineCommandManager . sendSceneCommand ( {
0 commit comments