@luxonis/visualizer-protobuf 2.22.0 → 2.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/{WorkerImageDecoder.worker-tkX9-IYo.js → WorkerImageDecoder.worker-C3ZBQ2Wk.js} +1 -1
  2. package/dist/{decodeImage-C8kB6T3V.js → decodeImage-CxUhz2gE.js} +14278 -2893
  3. package/dist/{index-P-f_cKZS.js → index-B9Zf3rrb.js} +2 -2
  4. package/dist/{index-BQ24Upp_.js → index-BJOK4X3d.js} +2 -2
  5. package/dist/{index-DMvr0-pP.js → index-BTO4og7t.js} +2 -2
  6. package/dist/{index-DTCT-lVn.js → index-BqTw2FSJ.js} +4 -4
  7. package/dist/{index-CH1TUS48.js → index-Bw0fCcF0.js} +2 -2
  8. package/dist/{index-DDVf76z9.js → index-CCWfhL1j.js} +2 -2
  9. package/dist/{index-BHXfMPMv.js → index-CFz07x1R.js} +2 -2
  10. package/dist/{index-Bvet1xE9.js → index-CM0J0Tip.js} +2 -2
  11. package/dist/{index-DtzTeqB7.js → index-D3by772J.js} +2 -2
  12. package/dist/{index-DzyYicoH.js → index-DMmaMUCD.js} +2813 -1608
  13. package/dist/{index-C-cGIa0r.js → index-DQ_hdLpb.js} +2 -2
  14. package/dist/{index-yfiGMPtK.js → index-DRmoIUFd.js} +2 -2
  15. package/dist/{index-Dcus_L6F.js → index-DWgnF3_o.js} +156 -57
  16. package/dist/{index-DYpNYj7G.js → index-Db42Qzy_.js} +2 -2
  17. package/dist/{index-C_ioBAtk.js → index-DgisSKDf.js} +2 -2
  18. package/dist/{index-CV57d9Tz.js → index-DjOkSXUO.js} +2 -2
  19. package/dist/{index-D5F-PpU5.js → index-DqqFhpKC.js} +2 -2
  20. package/dist/{index-RKZ-F77P.js → index-Wr3SUBO9.js} +2 -2
  21. package/dist/{index-DHgo3Ne_.js → index-oTzD1_p-.js} +2 -2
  22. package/dist/index.js +2 -2
  23. package/dist/lib/src/connection/foxglove-connection.d.ts +3 -1
  24. package/dist/lib/src/connection/foxglove-connection.d.ts.map +1 -1
  25. package/dist/lib/src/connection/foxglove-connection.js +16 -32
  26. package/dist/lib/src/connection/foxglove-connection.js.map +1 -1
  27. package/dist/lib/src/messaging/deserialization/pointcloud/pointcloudFromDepth.worker.js +373 -247
  28. package/dist/lib/src/messaging/deserialization/pointcloud/pointcloudFromDepth.worker.js.map +1 -1
  29. package/dist/lib/src/messaging/deserialization/pointcloud/poitcloudPoolManager.d.ts +30 -0
  30. package/dist/lib/src/messaging/deserialization/pointcloud/poitcloudPoolManager.d.ts.map +1 -0
  31. package/dist/lib/src/messaging/deserialization/pointcloud/poitcloudPoolManager.js +106 -0
  32. package/dist/lib/src/messaging/deserialization/pointcloud/poitcloudPoolManager.js.map +1 -0
  33. package/dist/lib/src/messaging/deserialization/pointcloud/utils.d.ts +0 -9
  34. package/dist/lib/src/messaging/deserialization/pointcloud/utils.d.ts.map +1 -1
  35. package/dist/lib/src/messaging/deserialization/pointcloud/utils.js +0 -16
  36. package/dist/lib/src/messaging/deserialization/pointcloud/utils.js.map +1 -1
  37. package/dist/lib/src/panels/PointCloudPanel.js +3 -3
  38. package/dist/lib/src/panels/PointCloudPanel.js.map +1 -1
  39. package/dist/lib/src/utils/poitcloud-sync.js +1 -1
  40. package/dist/lib/src/utils/poitcloud-sync.js.map +1 -1
  41. package/dist/packages/studio-base/src/panels/ThreeDeeRender/renderables/CameraStateSettings.d.ts +1 -0
  42. package/dist/packages/studio-base/src/panels/ThreeDeeRender/renderables/CameraStateSettings.d.ts.map +1 -1
  43. package/dist/packages/studio-base/src/panels/ThreeDeeRender/renderables/CameraStateSettings.js +243 -154
  44. package/dist/packages/studio-base/src/panels/ThreeDeeRender/renderables/CameraStateSettings.js.map +1 -1
  45. package/dist/pointcloudFromDepth.worker-qotYPy_e.js +450 -0
  46. package/dist/{utils-Cmsz3FxA.js → utils-Hzt3wxhG.js} +2 -20
  47. package/package.json +2 -1
  48. package/dist/pointcloudFromDepth.worker-CNKyMUU-.js +0 -326
@@ -4,45 +4,43 @@
4
4
  import { t } from "i18next";
5
5
  import * as _ from "lodash-es";
6
6
  import * as THREE from "three";
7
- import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
7
+ import CameraControls from "camera-controls";
8
8
  import { CoordinateFrame, makePose, } from "@foxglove/studio-base/panels/ThreeDeeRender/transforms";
9
9
  import { SceneExtension } from "../SceneExtension";
10
10
  import { DEFAULT_CAMERA_STATE } from "../camera";
11
11
  import { PRECISION_DEGREES, PRECISION_DISTANCE } from "../settings";
12
12
  const DISPLAY_FRAME_NOT_FOUND = "DISPLAY_FRAME_NOT_FOUND";
13
- const UNIT_X = new THREE.Vector3(1, 0, 0);
14
- const UNIT_Z = new THREE.Vector3(0, 0, 1);
15
- const PI_2 = Math.PI / 2;
16
- // used for holding unfollowPoseSnapshot in render frame every new frame
17
13
  const snapshotInRenderFrame = makePose();
18
14
  const tempVec3 = new THREE.Vector3();
19
15
  const tempSpherical = new THREE.Spherical();
20
- const tempEuler = new THREE.Euler();
21
16
  const FOLLOW_TF_PATH = ["general", "followTf"];
17
+ const AZIMUTH_SENSITIVITY = 0.25;
18
+ const POLAR_SENSITIVITY = 0.25;
19
+ const MIN_POLAR_DEG = 1;
20
+ const MAX_POLAR_DEG = 179;
22
21
  export class CameraStateSettings extends SceneExtension {
23
- // The frameId's of the fixed and render frames used to create the current unfollowPoseSnapshot
22
+ renderer;
24
23
  #unfollowSnapshotFrameIds;
25
- // The pose of the render frame in the fixed frame when following was disabled
26
- // This is used to position and orient the camera from the fixed frame in the render frame
27
24
  unfollowPoseSnapshot;
28
25
  #controls;
29
26
  #isUpdatingCameraState = false;
30
27
  #canvas;
31
- // This group is used to transform the cameras based on the Frame follow mode
32
- // quaternion is affected in stationary and position-only follow modes
33
- // both position and quaternion of the group are affected in stationary mode
34
28
  #cameraGroup;
35
29
  #perspectiveCamera;
36
30
  #orthographicCamera;
37
31
  #aspect;
32
+ #lastTargetOffset = new THREE.Vector3();
33
+ #isDraggingForRotation = false;
34
+ #lastMouseX = 1;
35
+ #lastMouseY = 1;
38
36
  constructor(renderer, canvas, aspect) {
39
37
  super("foxglove.CameraStateSettings", renderer);
40
- // for Frame settings, we need to listen to the transform tree to update settings when new possible display frames are present
41
- renderer.on("transformTreeUpdated", this.#handleTransformTreeUpdated);
42
- renderer.on("cameraMove", this.#handleCameraMove);
43
- renderer.settings.errors.on("update", this.#handleErrorChange);
44
- renderer.settings.errors.on("clear", this.#handleErrorChange);
45
- renderer.settings.errors.on("remove", this.#handleErrorChange);
38
+ this.renderer = renderer;
39
+ CameraControls.install({ THREE: THREE });
40
+ this.renderer.on("transformTreeUpdated", this.#handleTransformTreeUpdated);
41
+ this.renderer.settings.errors.on("update", this.#handleErrorChange);
42
+ this.renderer.settings.errors.on("clear", this.#handleErrorChange);
43
+ this.renderer.settings.errors.on("remove", this.#handleErrorChange);
46
44
  this.#canvas = canvas;
47
45
  this.#perspectiveCamera = new THREE.PerspectiveCamera();
48
46
  this.#orthographicCamera = new THREE.OrthographicCamera();
@@ -50,31 +48,69 @@ export class CameraStateSettings extends SceneExtension {
50
48
  this.#cameraGroup.add(this.#perspectiveCamera);
51
49
  this.#cameraGroup.add(this.#orthographicCamera);
52
50
  this.add(this.#cameraGroup);
53
- this.#controls = new OrbitControls(this.#perspectiveCamera, this.#canvas);
54
- this.#controls.screenSpacePanning = true; // only allow panning in the XY plane
55
- this.#controls.mouseButtons.LEFT = THREE.MOUSE.PAN;
56
- this.#controls.mouseButtons.RIGHT = THREE.MOUSE.ROTATE;
57
- this.#controls.touches.ONE = THREE.TOUCH.PAN;
58
- this.#controls.touches.TWO = THREE.TOUCH.DOLLY_ROTATE;
59
- this.#controls.addEventListener("change", () => {
60
- if (!this.#isUpdatingCameraState) {
61
- renderer.emit("cameraMove", renderer);
62
- }
63
- });
64
- // Make the canvas able to receive keyboard events and setup WASD controls
51
+ this.#controls = new CameraControls(this.#perspectiveCamera, this.#canvas);
52
+ this.#controls.dampingFactor = 0.05;
53
+ this.#controls.mouseButtons.left = CameraControls.ACTION.NONE;
54
+ this.#controls.mouseButtons.right = CameraControls.ACTION.TRUCK;
55
+ this.#controls.mouseButtons.wheel = CameraControls.ACTION.ZOOM;
56
+ this.#controls.touches.one = CameraControls.ACTION.TOUCH_SCREEN_PAN;
57
+ this.#controls.infinityDolly = true;
58
+ this.#perspectiveCamera.rotation.set(0, Math.PI, Math.PI);
59
+ this.#perspectiveCamera.up.set(0, -1, 0);
60
+ this.#controls.update(0);
61
+ const euler = new THREE.Euler().setFromQuaternion(this.#perspectiveCamera.quaternion, "ZYX");
62
+ this.#perspectiveCamera.quaternion.setFromEuler(euler);
63
+ this.#controls.addEventListener("update", this.#handleControlsUpdate);
64
+ const controls = this.#controls;
65
+ const animate = () => {
66
+ controls.update(1.0);
67
+ requestAnimationFrame(animate);
68
+ };
69
+ animate();
65
70
  canvas.tabIndex = 1000;
66
71
  this.#aspect = aspect;
67
- this.#controls.keys = { LEFT: "KeyA", RIGHT: "KeyD", UP: "KeyS", BOTTOM: "KeyW" };
68
- this.#controls.listenToKeyEvents(canvas);
72
+ canvas.addEventListener("mousedown", this.#handleMouseDown);
73
+ canvas.addEventListener("mousemove", this.#handleMouseMove);
74
+ window.addEventListener("mouseup", this.#handleMouseUp);
75
+ canvas.addEventListener("mouseleave", this.#handleMouseLeave);
76
+ canvas.addEventListener("contextmenu", (event) => event.preventDefault());
77
+ // Set camera state to default
78
+ setTimeout(() => {
79
+ const activeCamera = this.getActiveCamera();
80
+ activeCamera.updateMatrixWorld(true);
81
+ this.renderer.queueAnimationFrame();
82
+ this.#applyCameraStateToControls(this.renderer.config.cameraState, undefined);
83
+ this.#controls.update(0);
84
+ }, 10);
85
+ // Rotates the camera to the default position
86
+ setTimeout(() => {
87
+ const currentTarget = new THREE.Vector3();
88
+ this.#controls.getTarget(currentTarget);
89
+ const currentPosition = new THREE.Vector3();
90
+ this.#perspectiveCamera.getWorldPosition(currentPosition);
91
+ const offset = currentPosition.clone().sub(currentTarget);
92
+ const spherical = new THREE.Spherical();
93
+ spherical.setFromVector3(offset);
94
+ spherical.theta = Math.max(0.001, Math.min(Math.PI - 0.001, spherical.theta + Math.PI));
95
+ const newOffset = new THREE.Vector3().setFromSpherical(spherical);
96
+ const newCameraPosition = currentTarget.clone().add(newOffset);
97
+ this.#controls.setLookAt(newCameraPosition.x, newCameraPosition.y, newCameraPosition.z, currentTarget.x, currentTarget.y, currentTarget.z, false);
98
+ this.#controls.update(0);
99
+ this.renderer.queueAnimationFrame();
100
+ }, 100);
69
101
  }
70
102
  dispose() {
71
- // for camera settings
72
- this.renderer.off("cameraMove", this.#handleCameraMove);
73
- // for frame settings
74
103
  this.renderer.off("transformTreeUpdated", this.#handleTransformTreeUpdated);
75
104
  this.renderer.settings.errors.off("update", this.#handleErrorChange);
76
105
  this.renderer.settings.errors.off("clear", this.#handleErrorChange);
77
106
  this.renderer.settings.errors.off("remove", this.#handleErrorChange);
107
+ this.#controls.removeEventListener("update", this.#handleControlsUpdate);
108
+ this.#canvas.removeEventListener("mousedown", this.#handleMouseDown);
109
+ this.#canvas.removeEventListener("mousemove", this.#handleMouseMove);
110
+ window.removeEventListener("mouseup", this.#handleMouseUp);
111
+ this.#canvas.removeEventListener("mouseleave", this.#handleMouseLeave);
112
+ this.#canvas.removeEventListener("contextmenu", (event) => event.preventDefault());
113
+ this.#controls.dispose();
78
114
  super.dispose();
79
115
  }
80
116
  settingsNodes() {
@@ -82,13 +118,15 @@ export class CameraStateSettings extends SceneExtension {
82
118
  }
83
119
  #cameraSettingsNode() {
84
120
  const config = this.renderer.config;
85
- const { cameraState: camera } = config;
121
+ const camera = config.cameraState;
86
122
  const handler = this.handleSettingsAction;
87
123
  return {
88
124
  path: ["cameraState"],
89
125
  node: {
90
126
  label: t("threeDee:view"),
91
- actions: [{ type: "action", id: "reset-camera", label: t("threeDee:reset") }],
127
+ actions: [
128
+ { type: "action", id: "reset-camera", label: t("threeDee:reset") },
129
+ ],
92
130
  handler,
93
131
  fields: {
94
132
  syncCamera: {
@@ -103,7 +141,7 @@ export class CameraStateSettings extends SceneExtension {
103
141
  input: "number",
104
142
  step: 1,
105
143
  precision: PRECISION_DISTANCE,
106
- value: camera.distance,
144
+ value: camera.distance ?? config.cameraState.distance,
107
145
  },
108
146
  perspective: {
109
147
  label: t("threeDee:perspective"),
@@ -115,23 +153,25 @@ export class CameraStateSettings extends SceneExtension {
115
153
  input: "vec3",
116
154
  labels: ["X", "Y", "Z"],
117
155
  precision: PRECISION_DISTANCE,
118
- value: [...camera.targetOffset],
156
+ value: [
157
+ ...(camera.targetOffset ?? config.cameraState.targetOffset),
158
+ ],
119
159
  },
120
160
  thetaOffset: {
121
161
  label: t("threeDee:theta"),
122
162
  input: "number",
123
163
  step: 1,
124
164
  precision: PRECISION_DEGREES,
125
- value: camera.thetaOffset,
165
+ value: camera.thetaOffset ?? config.cameraState.thetaOffset,
166
+ },
167
+ phi: {
168
+ label: t("threeDee:phi"),
169
+ input: "number",
170
+ step: 1,
171
+ precision: PRECISION_DEGREES,
172
+ value: camera.phi ?? config.cameraState.phi,
126
173
  },
127
174
  ...(camera.perspective && {
128
- phi: {
129
- label: t("threeDee:phi"),
130
- input: "number",
131
- step: 1,
132
- precision: PRECISION_DEGREES,
133
- value: camera.phi,
134
- },
135
175
  fovy: {
136
176
  label: t("threeDee:fovy"),
137
177
  input: "number",
@@ -152,7 +192,7 @@ export class CameraStateSettings extends SceneExtension {
152
192
  input: "number",
153
193
  step: 1,
154
194
  precision: PRECISION_DISTANCE,
155
- value: camera.far,
195
+ value: camera.far ?? config.cameraState.far,
156
196
  },
157
197
  },
158
198
  },
@@ -161,16 +201,17 @@ export class CameraStateSettings extends SceneExtension {
161
201
  #frameSettingsNode() {
162
202
  const config = this.renderer.config;
163
203
  const handler = this.handleSettingsAction;
164
- // If the user-selected frame does not exist, show it in the dropdown
165
- // anyways. A settings node error will be displayed
166
204
  let followTfOptions = this.renderer.coordinateFrameList;
167
205
  const followFrameId = this.renderer.followFrameId;
168
206
  this.#updateFollowTfError();
169
- // always show current config value if it exists
170
207
  const followTfValue = config.followTf ?? followFrameId;
171
- if (followTfValue != undefined && !this.renderer.transformTree.hasFrame(followTfValue)) {
208
+ if (followTfValue != undefined &&
209
+ !this.renderer.transformTree.hasFrame(followTfValue)) {
172
210
  followTfOptions = [
173
- { label: CoordinateFrame.DisplayName(followTfValue), value: followTfValue },
211
+ {
212
+ label: CoordinateFrame.DisplayName(followTfValue),
213
+ value: followTfValue,
214
+ },
174
215
  ...followTfOptions,
175
216
  ];
176
217
  }
@@ -180,7 +221,7 @@ export class CameraStateSettings extends SceneExtension {
180
221
  { label: t("threeDee:position"), value: "follow-position" },
181
222
  { label: t("threeDee:fixed"), value: "follow-none" },
182
223
  ];
183
- const followModeValue = this.renderer.config.followMode;
224
+ const followModeValue = config.followMode;
184
225
  return {
185
226
  path: ["general"],
186
227
  node: {
@@ -208,67 +249,73 @@ export class CameraStateSettings extends SceneExtension {
208
249
  };
209
250
  }
210
251
  handleSettingsAction = (action) => {
211
- if (action.action === "perform-node-action" && action.payload.id === "reset-camera") {
252
+ if (action.action === "perform-node-action" &&
253
+ action.payload.id === "reset-camera") {
254
+ const previousState = _.cloneDeep(this.renderer.config.cameraState);
212
255
  this.renderer.updateConfig((draft) => {
213
256
  draft.cameraState = _.cloneDeep(DEFAULT_CAMERA_STATE);
214
257
  });
215
- this.updateSettingsTree();
258
+ this.#applyCameraStateToControls(this.renderer.config.cameraState, previousState);
259
+ this.renderer.emit("cameraMove", this.renderer);
216
260
  return;
217
261
  }
218
262
  if (action.action !== "update" || action.payload.path.length === 0) {
219
263
  return;
220
264
  }
221
- const { path: [category], path, value, } = action.payload;
222
- // camera settings
265
+ const { path, value } = action.payload;
266
+ const category = path[0];
223
267
  if (category === "cameraState") {
268
+ const previousState = _.cloneDeep(this.renderer.config.cameraState);
224
269
  if (path[1] === "syncCamera") {
225
- // Update the configuration. This is done manually since syncCamera is under `scene`, not `cameraState`
226
270
  this.renderer.updateConfig((draft) => {
227
271
  draft.scene.syncCamera = value;
228
272
  });
229
273
  }
230
274
  else {
231
- this.renderer.updateConfig((draft) => _.set(draft, path, value));
275
+ this.renderer.updateConfig((draft) => {
276
+ _.set(draft, path, value);
277
+ });
278
+ this.#applyCameraStateToControls(this.renderer.config.cameraState, previousState);
279
+ this.renderer.emit("cameraMove", this.renderer);
232
280
  }
233
- this.updateSettingsTree();
234
281
  }
235
- // frame settings
236
282
  if (category === "general") {
283
+ const previousState = _.cloneDeep(this.renderer.config.cameraState);
237
284
  if (path[1] === "followTf") {
238
285
  const followTf = value;
239
- // Update the configuration. This is done manually since followTf is at the top level of
240
- // config, not under `general`
241
286
  this.renderer.updateConfig((draft) => {
242
287
  draft.followTf = followTf;
243
288
  });
244
289
  this.#updateFollowFrameId();
245
- this.renderer.settings.errors.clearPath(["general", "followTf"]);
290
+ this.renderer.settings.errors.clearPath(FOLLOW_TF_PATH);
246
291
  }
247
292
  else if (path[1] === "followMode") {
248
293
  const followMode = value;
249
- // Update the configuration. This is done manually since followMode is at the top level of
250
- // config, not under `general`
251
294
  this.renderer.updateConfig((draft) => {
252
- // any follow -> stationary no clear
253
- // stationary -> any follow clear offset (center on frame)
254
- if (draft.followMode === "follow-none") {
255
- draft.cameraState.targetOffset = [...DEFAULT_CAMERA_STATE.targetOffset];
256
- draft.cameraState.thetaOffset = DEFAULT_CAMERA_STATE.thetaOffset;
295
+ if (draft.followMode === "follow-none" ||
296
+ followMode === "follow-pose") {
297
+ draft.cameraState.targetOffset = [
298
+ ...DEFAULT_CAMERA_STATE.targetOffset,
299
+ ];
257
300
  }
258
- else if (followMode === "follow-pose") {
301
+ if (followMode === "follow-pose") {
259
302
  draft.cameraState.thetaOffset = DEFAULT_CAMERA_STATE.thetaOffset;
260
303
  }
261
304
  draft.followMode = followMode;
262
305
  });
306
+ this.#applyCameraStateToControls(this.renderer.config.cameraState, previousState);
307
+ this.renderer.emit("cameraMove", this.renderer);
308
+ }
309
+ else {
310
+ this.renderer.updateConfig((draft) => {
311
+ _.set(draft, path, value);
312
+ });
263
313
  }
264
- this.updateSettingsTree();
265
314
  }
266
315
  };
267
- // this extension has NO RENDERABLES so the parent startFrame would do nothing
268
316
  startFrame(currentTime, renderFrameId, fixedFrameId) {
269
317
  const followMode = this.renderer.config.followMode;
270
318
  if (followMode === "follow-pose" ||
271
- // we don't need the unfollow pose snapshot when there are no transforms
272
319
  fixedFrameId === CoordinateFrame.FALLBACK_FRAME_ID ||
273
320
  renderFrameId === CoordinateFrame.FALLBACK_FRAME_ID) {
274
321
  this.#unfollowSnapshotFrameIds = undefined;
@@ -280,35 +327,89 @@ export class CameraStateSettings extends SceneExtension {
280
327
  const poseSnapshot = this.#getUnfollowPoseSnapshot(fixedFrameId, renderFrameId, currentTime);
281
328
  const transformTree = this.renderer.transformTree;
282
329
  if (poseSnapshot) {
283
- // transform position of snapshot in fixed frame to the render frame
284
330
  const appliedTransform = Boolean(transformTree.apply(snapshotInRenderFrame, poseSnapshot, renderFrameId, fixedFrameId, fixedFrameId, currentTime, currentTime));
285
331
  if (!appliedTransform) {
286
332
  return;
287
333
  }
288
- /**
289
- * the application of the unfollowPoseSnapshot position and orientation
290
- * components makes the camera position and rotation static relative to the fixed frame.
291
- * So when the display frame changes the angle of the camera relative
292
- * to the scene will not change because only the snapshotPose orientation is applied
293
- */
294
334
  if (followMode === "follow-position") {
295
- // only make orientation static/stationary in this mode
296
- // the position still follows the frame
297
335
  this.#cameraGroup.position.set(0, 0, 0);
298
336
  }
299
337
  else {
300
338
  this.#cameraGroup.position.set(snapshotInRenderFrame.position.x, snapshotInRenderFrame.position.y, snapshotInRenderFrame.position.z);
301
339
  }
302
- // this negates the rotation of the changes in renderFrame
303
340
  this.#cameraGroup.quaternion.set(snapshotInRenderFrame.orientation.x, snapshotInRenderFrame.orientation.y, snapshotInRenderFrame.orientation.z, snapshotInRenderFrame.orientation.w);
304
341
  }
305
342
  }
306
- #handleCameraMove = () => {
307
- this.updateSettingsTree();
343
+ #handleControlsUpdate = () => {
344
+ if (!this.#isUpdatingCameraState && !this.#isDraggingForRotation) {
345
+ const newDistance = this.#controls.distance;
346
+ this.#controls.getTarget(tempVec3);
347
+ const state = this.renderer.config.cameraState;
348
+ const distChanged = Math.abs(state.distance - newDistance) > 1e-6;
349
+ const targetChanged = !_.isEqual(state.targetOffset, [
350
+ tempVec3.x,
351
+ tempVec3.y,
352
+ tempVec3.z,
353
+ ]);
354
+ if (distChanged || targetChanged) {
355
+ this.renderer.updateConfig((draft) => {
356
+ if (distChanged)
357
+ draft.cameraState.distance = newDistance;
358
+ if (targetChanged)
359
+ draft.cameraState.targetOffset = [
360
+ tempVec3.x,
361
+ tempVec3.y,
362
+ tempVec3.z,
363
+ ];
364
+ });
365
+ this.#lastTargetOffset.copy(tempVec3);
366
+ this.renderer.emit("cameraMove", this.renderer);
367
+ }
368
+ }
369
+ };
370
+ #handleMouseDown = (event) => {
371
+ if (event.button === 0) {
372
+ event.preventDefault();
373
+ this.#isDraggingForRotation = true;
374
+ this.#lastMouseX = event.clientX;
375
+ this.#lastMouseY = event.clientY;
376
+ }
377
+ };
378
+ #handleMouseMove = (event) => {
379
+ if (!this.#isDraggingForRotation) {
380
+ return;
381
+ }
382
+ const deltaX = event.clientX - this.#lastMouseX;
383
+ const deltaY = event.clientY - this.#lastMouseY;
384
+ this.#lastMouseX = event.clientX;
385
+ this.#lastMouseY = event.clientY;
386
+ if (deltaX === 0 && deltaY === 0) {
387
+ return;
388
+ }
389
+ const deltaAzimuth = -deltaX * AZIMUTH_SENSITIVITY;
390
+ const deltaPolar = deltaY * POLAR_SENSITIVITY;
391
+ const previousState = _.cloneDeep(this.renderer.config.cameraState);
392
+ this.renderer.updateConfig((draft) => {
393
+ let currentPhi = draft.cameraState.phi + deltaPolar;
394
+ currentPhi = Math.max(MIN_POLAR_DEG, Math.min(MAX_POLAR_DEG, currentPhi));
395
+ draft.cameraState.phi = currentPhi;
396
+ draft.cameraState.thetaOffset += deltaAzimuth;
397
+ });
398
+ this.#applyCameraStateToControls(this.renderer.config.cameraState, previousState);
399
+ this.renderer.emit("cameraMove", this.renderer);
400
+ };
401
+ #handleMouseUp = (event) => {
402
+ if (event.button === 0 && this.#isDraggingForRotation) {
403
+ this.#isDraggingForRotation = false;
404
+ }
405
+ };
406
+ #handleMouseLeave = () => {
407
+ if (this.#isDraggingForRotation) {
408
+ this.#isDraggingForRotation = false;
409
+ }
308
410
  };
309
411
  #handleTransformTreeUpdated = () => {
310
412
  this.#updateFollowFrameId();
311
- this.updateSettingsTree();
312
413
  };
313
414
  #updateFollowFrameId() {
314
415
  const { followTf } = this.renderer.config;
@@ -319,37 +420,31 @@ export class CameraStateSettings extends SceneExtension {
319
420
  this.renderer.setFollowFrameId(followTf);
320
421
  return;
321
422
  }
322
- // No valid renderFrameId set, or new frames have been added, fall back to selecting the
323
- // heuristically most valid frame (if any frames are present)
324
- const followFrameId = transformTree.getDefaultFollowFrameId();
325
- this.renderer.setFollowFrameId(followFrameId);
423
+ const defaultFollowFrameId = transformTree.getDefaultFollowFrameId();
424
+ this.renderer.setFollowFrameId(defaultFollowFrameId);
326
425
  }
327
426
  #updateFollowTfError = () => {
328
427
  const { followTf } = this.renderer.config;
329
428
  const { transformTree } = this.renderer;
330
429
  if (followTf != undefined) {
331
- const followTfFrameExists = transformTree.hasFrame(followTf);
332
- if (followTfFrameExists) {
430
+ if (transformTree.hasFrame(followTf)) {
333
431
  this.renderer.settings.errors.remove(FOLLOW_TF_PATH, DISPLAY_FRAME_NOT_FOUND);
334
432
  }
335
433
  else {
336
- this.renderer.settings.errors.add(FOLLOW_TF_PATH, DISPLAY_FRAME_NOT_FOUND, t("threeDee:frameNotFound", {
337
- frameId: followTf,
338
- }));
434
+ this.renderer.settings.errors.add(FOLLOW_TF_PATH, DISPLAY_FRAME_NOT_FOUND, t("threeDee:frameNotFound", { frameId: followTf }));
339
435
  }
340
436
  }
437
+ else {
438
+ this.renderer.settings.errors.remove(FOLLOW_TF_PATH, DISPLAY_FRAME_NOT_FOUND);
439
+ }
341
440
  };
342
- #handleErrorChange = () => {
343
- this.updateSettingsTree();
344
- };
345
- // Redefine follow pose snapshot whenever renderFrame or fixedFrame changes
441
+ #handleErrorChange = () => { };
346
442
  #getUnfollowPoseSnapshot(fixedFrameId, renderFrameId, currentTime) {
347
443
  const transformTree = this.renderer.transformTree;
348
444
  if (this.#unfollowSnapshotFrameIds?.fixed !== fixedFrameId ||
349
- this.#unfollowSnapshotFrameIds.render !== renderFrameId) {
445
+ this.#unfollowSnapshotFrameIds?.render !== renderFrameId) {
350
446
  this.unfollowPoseSnapshot = makePose();
351
- // record the pose of the center of the render frame in fixed frame into the snapshot
352
- transformTree.apply(this.unfollowPoseSnapshot, this.unfollowPoseSnapshot, fixedFrameId, fixedFrameId, renderFrameId, currentTime, currentTime);
447
+ transformTree.apply(this.unfollowPoseSnapshot, makePose(), fixedFrameId, renderFrameId, fixedFrameId, currentTime, currentTime);
353
448
  this.#unfollowSnapshotFrameIds = {
354
449
  fixed: fixedFrameId,
355
450
  render: renderFrameId,
@@ -367,71 +462,65 @@ export class CameraStateSettings extends SceneExtension {
367
462
  this.setCameraState(this.renderer.config.cameraState);
368
463
  }
369
464
  getCameraState() {
370
- const config = this.renderer.config;
371
- return {
372
- perspective: config.cameraState.perspective,
373
- distance: this.#controls.getDistance(),
374
- phi: THREE.MathUtils.radToDeg(this.#controls.getPolarAngle()),
375
- thetaOffset: THREE.MathUtils.radToDeg(-this.#controls.getAzimuthalAngle()),
376
- targetOffset: [this.#controls.target.x, this.#controls.target.y, this.#controls.target.z],
377
- target: config.cameraState.target,
378
- targetOrientation: config.cameraState.targetOrientation,
379
- fovy: config.cameraState.fovy,
380
- near: config.cameraState.near,
381
- far: config.cameraState.far,
382
- };
465
+ return _.cloneDeep(this.renderer.config.cameraState);
383
466
  }
384
467
  setCameraState(cameraState) {
468
+ const previousState = _.cloneDeep(this.renderer.config.cameraState);
385
469
  this.#isUpdatingCameraState = true;
386
- this.#updateCameras(cameraState);
387
- // only active for follow pose mode because it introduces strange behavior into the other modes
388
- // due to the fact that they are manipulating the camera after update with the `cameraGroup`
389
- if (this.renderer.config.followMode === "follow-pose") {
390
- this.#controls.update();
391
- }
470
+ this.#updateRawCameras(cameraState);
471
+ this.#applyCameraStateToControls(cameraState, previousState);
392
472
  this.#isUpdatingCameraState = false;
393
473
  }
394
- /** Translate a CameraState to the three.js coordinate system */
395
- #updateCameras(cameraState) {
396
- const targetOffset = tempVec3;
397
- const config = this.renderer.config;
398
- targetOffset.fromArray(cameraState.targetOffset);
399
- const phi = THREE.MathUtils.degToRad(cameraState.phi);
400
- const theta = -THREE.MathUtils.degToRad(cameraState.thetaOffset);
401
- // Always update the perspective camera even if the current mode is orthographic. This is needed
402
- // to make the OrbitControls work properly since they track the perspective camera.
403
- // https://github.com/foxglove/studio/issues/4138
404
- // Convert the camera spherical coordinates (radius, phi, theta) to Cartesian (X, Y, Z)
405
- tempSpherical.set(cameraState.distance, phi, theta);
406
- this.#perspectiveCamera.position.setFromSpherical(tempSpherical).applyAxisAngle(UNIT_X, PI_2);
407
- this.#perspectiveCamera.position.add(targetOffset);
408
- // Convert the camera spherical coordinates (phi, theta) to a quaternion rotation
409
- this.#perspectiveCamera.quaternion.setFromEuler(tempEuler.set(phi, 0, theta, "ZYX"));
474
+ #updateRawCameras(cameraState) {
410
475
  this.#perspectiveCamera.fov = cameraState.fovy;
411
476
  this.#perspectiveCamera.near = cameraState.near;
412
477
  this.#perspectiveCamera.far = cameraState.far;
413
478
  this.#perspectiveCamera.aspect = this.#aspect;
414
479
  this.#perspectiveCamera.updateProjectionMatrix();
415
- this.#controls.target.copy(targetOffset);
480
+ if (!cameraState.perspective) {
481
+ const orthoHeight = cameraState.distance *
482
+ Math.tan(THREE.MathUtils.degToRad(cameraState.fovy * 0.5)) *
483
+ 2;
484
+ const orthoWidth = orthoHeight * this.#aspect;
485
+ this.#orthographicCamera.left = -orthoWidth / 2;
486
+ this.#orthographicCamera.right = orthoWidth / 2;
487
+ this.#orthographicCamera.top = orthoHeight / 2;
488
+ this.#orthographicCamera.bottom = -orthoHeight / 2;
489
+ this.#orthographicCamera.near = cameraState.near;
490
+ this.#orthographicCamera.far = cameraState.far;
491
+ this.#orthographicCamera.updateProjectionMatrix();
492
+ }
493
+ }
494
+ #applyCameraStateToControls(cameraState, previousState) {
495
+ const targetOffset = tempVec3.fromArray(cameraState.targetOffset);
496
+ const distance = cameraState.distance;
497
+ const phiRad = THREE.MathUtils.degToRad(cameraState.phi);
498
+ const azimuthRad = -THREE.MathUtils.degToRad(cameraState.thetaOffset);
499
+ const propsChanged = !previousState ||
500
+ Math.abs(previousState.distance - distance) > 1e-6 ||
501
+ Math.abs(previousState.phi - cameraState.phi) > 1e-6 ||
502
+ Math.abs(previousState.thetaOffset - cameraState.thetaOffset) > 1e-6 ||
503
+ !_.isEqual(previousState.targetOffset, cameraState.targetOffset);
504
+ if (propsChanged) {
505
+ tempSpherical.set(distance, phiRad, azimuthRad);
506
+ const cameraPosition = new THREE.Vector3().setFromSpherical(tempSpherical);
507
+ cameraPosition.add(targetOffset);
508
+ this.#controls.setLookAt(cameraPosition.x, cameraPosition.y, cameraPosition.z, targetOffset.x, targetOffset.y, targetOffset.z, false);
509
+ this.#lastTargetOffset.copy(targetOffset);
510
+ }
416
511
  if (cameraState.perspective) {
417
- // Unlock the polar angle (pitch axis)
418
512
  this.#controls.minPolarAngle = 0;
419
513
  this.#controls.maxPolarAngle = Math.PI;
420
514
  }
421
515
  else {
422
- // Lock the polar angle during 2D mode
423
- const curPolarAngle = THREE.MathUtils.degToRad(config.cameraState.phi);
424
- this.#controls.minPolarAngle = this.#controls.maxPolarAngle = curPolarAngle;
425
- this.#orthographicCamera.position.set(targetOffset.x, targetOffset.y, cameraState.far / 2);
426
- this.#orthographicCamera.quaternion.setFromAxisAngle(UNIT_Z, theta);
427
- this.#orthographicCamera.left = (-cameraState.distance / 2) * this.#aspect;
428
- this.#orthographicCamera.right = (cameraState.distance / 2) * this.#aspect;
429
- this.#orthographicCamera.top = cameraState.distance / 2;
430
- this.#orthographicCamera.bottom = -cameraState.distance / 2;
431
- this.#orthographicCamera.near = cameraState.near;
432
- this.#orthographicCamera.far = cameraState.far;
433
- this.#orthographicCamera.updateProjectionMatrix();
516
+ this.#controls.minPolarAngle = 0;
517
+ this.#controls.maxPolarAngle = Math.PI;
518
+ this.#controls.getPosition(this.#orthographicCamera.position);
519
+ this.#controls.getTarget(tempVec3);
520
+ this.#orthographicCamera.lookAt(tempVec3);
521
+ this.#orthographicCamera.updateMatrixWorld();
434
522
  }
523
+ this.#controls.update(0);
435
524
  }
436
525
  }
437
526
  //# sourceMappingURL=CameraStateSettings.js.map