@2112-lab/central-plant 0.3.49 → 0.3.51

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.
@@ -1,11 +1,12 @@
1
1
  import { inherits as _inherits, createClass as _createClass, typeof as _typeof, classCallCheck as _classCallCheck, callSuper as _callSuper, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator, objectSpread2 as _objectSpread2 } from '../../_virtual/_rollupPluginBabelHelpers.js';
2
- import 'three';
2
+ import * as THREE from 'three';
3
3
  import { BaseDisposable } from './baseDisposable.js';
4
4
  import { DisposalUtilities } from '../utils/DisposalUtilities.js';
5
5
  import { CentralPlantInternals } from './centralPlantInternals.js';
6
6
  import '../rendering/modelPreloader.js';
7
7
  import { scanSceneIoEndpoints, applyCrossComponentBehaviors, loadCrossComponentBehaviors, reregisterSceneBehaviors } from '../utils/behaviorSceneUtils.js';
8
8
  import { getScopedAttachmentKey } from '../utils/behaviorDispatch.js';
9
+ import { computeFilteredBoundingBox } from '../utils/boundingBoxUtils.js';
9
10
 
10
11
  // ─────────────────────────────────────────────────────────────────────────────
11
12
  // Flow-direction compatibility helper (module-level, no class dependency)
@@ -33,7 +34,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
33
34
  * Initialize the CentralPlant manager
34
35
  *
35
36
  * @constructor
36
- * @version 0.3.49
37
+ * @version 0.3.51
37
38
  * @updated 2025-10-22
38
39
  *
39
40
  * @description Creates a new CentralPlant instance and initializes internal managers and utilities.
@@ -1909,6 +1910,98 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
1909
1910
  }
1910
1911
  }
1911
1912
 
1913
+ /**
1914
+ * Get the world-space axis-aligned bounding box for a scene object.
1915
+ * @param {string|THREE.Object3D} objectOrId - The object's UUID/name, or the Three.js object itself
1916
+ * @param {Object} [options={}] - Bounding box options
1917
+ * @param {boolean} [options.filtered=true] - When true, exclude connector and io-device
1918
+ * subtrees so the box reflects the component body footprint. When false, the box
1919
+ * envelops all descendants (equivalent to THREE.Box3.setFromObject).
1920
+ * @param {string[]} [options.excludeTypes] - Override the userData.objectType values to
1921
+ * exclude (defaults to ['io-device', 'connector'] when filtered is true).
1922
+ * @returns {{min: {x,y,z}, max: {x,y,z}, size: {x,y,z}, center: {x,y,z}}|null}
1923
+ * The bounding box descriptor in world space, or null if the object can't be found
1924
+ * or has no geometry.
1925
+ * @description Computes a tight world-space bounding box for a component (or any scene
1926
+ * object). By default the component body is measured independently of its attached
1927
+ * connectors and io-devices, which is useful for layout and spacing operations.
1928
+ * @example
1929
+ * // Component body bounding box (excludes connectors / io-devices)
1930
+ * const bbox = centralPlant.getBoundingBox('PUMP-1');
1931
+ * console.log(bbox.size.x, bbox.size.y, bbox.size.z);
1932
+ *
1933
+ * @example
1934
+ * // Full bounding box including connectors and io-devices
1935
+ * const fullBox = centralPlant.getBoundingBox('PUMP-1', { filtered: false });
1936
+ *
1937
+ * @since 0.3.50
1938
+ */
1939
+ }, {
1940
+ key: "getBoundingBox",
1941
+ value: function getBoundingBox(objectOrId) {
1942
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
1943
+ if (!this.sceneViewer || !this.sceneViewer.scene) {
1944
+ console.warn('⚠️ getBoundingBox(): Scene viewer or scene not available');
1945
+ return null;
1946
+ }
1947
+ var _options$filtered = options.filtered,
1948
+ filtered = _options$filtered === void 0 ? true : _options$filtered,
1949
+ excludeTypes = options.excludeTypes;
1950
+
1951
+ // Resolve the target object
1952
+ var object = objectOrId;
1953
+ if (typeof objectOrId === 'string') {
1954
+ object = this.sceneViewer.scene.getObjectByProperty('uuid', objectOrId) || this.sceneViewer.scene.getObjectByProperty('name', objectOrId);
1955
+ if (!object) {
1956
+ // Fall back to a full traverse (matches translate's lookup by originalUuid)
1957
+ this.sceneViewer.scene.traverse(function (child) {
1958
+ var _child$userData2;
1959
+ if (!object && ((_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.originalUuid) === objectOrId) {
1960
+ object = child;
1961
+ }
1962
+ });
1963
+ }
1964
+ }
1965
+ if (!object || typeof object.traverse !== 'function') {
1966
+ console.warn("\u26A0\uFE0F getBoundingBox(): Object '".concat(objectOrId, "' not found in scene"));
1967
+ return null;
1968
+ }
1969
+ try {
1970
+ var typesToExclude = filtered ? excludeTypes || ['io-device', 'connector'] : [];
1971
+ var box = computeFilteredBoundingBox(object, typesToExclude);
1972
+ if (box.isEmpty()) {
1973
+ return null;
1974
+ }
1975
+ var size = box.getSize(new THREE.Vector3());
1976
+ var center = box.getCenter(new THREE.Vector3());
1977
+ return {
1978
+ min: {
1979
+ x: box.min.x,
1980
+ y: box.min.y,
1981
+ z: box.min.z
1982
+ },
1983
+ max: {
1984
+ x: box.max.x,
1985
+ y: box.max.y,
1986
+ z: box.max.z
1987
+ },
1988
+ size: {
1989
+ x: size.x,
1990
+ y: size.y,
1991
+ z: size.z
1992
+ },
1993
+ center: {
1994
+ x: center.x,
1995
+ y: center.y,
1996
+ z: center.z
1997
+ }
1998
+ };
1999
+ } catch (error) {
2000
+ console.error('❌ getBoundingBox(): Error computing bounding box:', error);
2001
+ return null;
2002
+ }
2003
+ }
2004
+
1912
2005
  /**
1913
2006
  * Get available component categories
1914
2007
  * @returns {Array<Object>} Array of category objects with id, label, icon, and description
@@ -2143,8 +2236,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
2143
2236
  var componentDictionary = ((_this$managers$compon = this.managers.componentDataManager) === null || _this$managers$compon === void 0 ? void 0 : _this$managers$compon.componentDictionary) || {};
2144
2237
  var missingIds = [];
2145
2238
  sceneData.scene.children.forEach(function (child) {
2146
- var _child$userData2;
2147
- var libraryId = (_child$userData2 = child.userData) === null || _child$userData2 === void 0 ? void 0 : _child$userData2.libraryId;
2239
+ var _child$userData3;
2240
+ var libraryId = (_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.libraryId;
2148
2241
  if (libraryId && !componentDictionary[libraryId]) {
2149
2242
  // Only add unique IDs
2150
2243
  if (!missingIds.includes(libraryId)) {
@@ -2222,8 +2315,8 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
2222
2315
  // If still not found, try finding by originalUuid in userData
2223
2316
  if (!targetObject) {
2224
2317
  this.sceneViewer.scene.traverse(function (child) {
2225
- var _child$userData3;
2226
- if (((_child$userData3 = child.userData) === null || _child$userData3 === void 0 ? void 0 : _child$userData3.originalUuid) === objectId) {
2318
+ var _child$userData4;
2319
+ if (((_child$userData4 = child.userData) === null || _child$userData4 === void 0 ? void 0 : _child$userData4.originalUuid) === objectId) {
2227
2320
  targetObject = child;
2228
2321
  return;
2229
2322
  }
@@ -24,7 +24,7 @@ export { SnapshotManager } from './managers/pathfinding/SnapshotManager.js';
24
24
  export { ComponentDataManager } from './managers/components/componentDataManager.js';
25
25
  export { createTransformControls } from './managers/controls/transformControlsManager.js';
26
26
  export { KeyboardControlsManager } from './managers/controls/keyboardControlsManager.js';
27
- export { CameraControlsManager } from './managers/controls/cameraControlsManager.js';
27
+ export { CameraControlsManager, DEFAULT_HOME_VIEW_DIRECTION, HOME_EMPTY_CAMERA_HEIGHT_ABOVE_GROUND } from './managers/controls/cameraControlsManager.js';
28
28
  export { ComponentDragManager } from './managers/controls/componentDragManager.js';
29
29
  export { EnvironmentManager } from './managers/environment/environmentManager.js';
30
30
  export { loadTextureSetAndCreateMaterial } from './managers/environment/textureConfig.js';
@@ -1,10 +1,28 @@
1
- import { createClass as _createClass, classCallCheck as _classCallCheck } from '../../../_virtual/_rollupPluginBabelHelpers.js';
1
+ import { createClass as _createClass, objectSpread2 as _objectSpread2, classCallCheck as _classCallCheck, createForOfIteratorHelper as _createForOfIteratorHelper } from '../../../_virtual/_rollupPluginBabelHelpers.js';
2
+ import * as THREE from 'three';
2
3
 
3
- /**
4
- * CameraControlsManager
5
- * Handles camera movement, rotation, and other camera-specific controls
6
- */
4
+ /** Default horizontal orbit bearing (target → camera, XY plane). */
5
+ var DEFAULT_HOME_VIEW_DIRECTION = {
6
+ x: -8,
7
+ y: -9,
8
+ z: 0
9
+ };
7
10
 
11
+ /** Downward pitch from camera to the bottom-center look-at target (degrees). */
12
+ var HOME_DOWNWARD_PITCH_DEG = 13;
13
+
14
+ /** Minimum camera height above the bbox floor (world units). */
15
+ var HOME_CAMERA_MIN_HEIGHT = 0.8;
16
+
17
+ /** Ground plane height in world space (Z-up). */
18
+ var WORLD_GROUND_Z = 0;
19
+
20
+ /** Camera height above the ground when the scene has no components. */
21
+ var HOME_EMPTY_CAMERA_HEIGHT_ABOVE_GROUND = 2.5;
22
+
23
+ /** Default horizontal distance from world origin when the scene is empty. */
24
+ var HOME_EMPTY_SCENE_DISTANCE = Math.hypot(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y);
25
+ var WORLD_ORIGIN = new THREE.Vector3(0, 0, 0);
8
26
  var CameraControlsManager = /*#__PURE__*/function () {
9
27
  function CameraControlsManager(component) {
10
28
  _classCallCheck(this, CameraControlsManager);
@@ -14,11 +32,317 @@ var CameraControlsManager = /*#__PURE__*/function () {
14
32
  }
15
33
 
16
34
  /**
17
- * Toggle camera auto-rotation on/off
18
- *
19
- * @returns {boolean} The new auto-rotation state
35
+ * Frame the camera so that every component in the scene fits perfectly in view.
36
+ *
37
+ * Computes the combined bounding box of all scene components, then moves the
38
+ * (perspective) camera along its current viewing direction to the distance
39
+ * required to fit that bounding sphere within the frustum. The orbit target,
40
+ * clipping planes and distance limits are updated to match.
41
+ *
42
+ * @param {Object} [options]
43
+ * @param {number} [options.padding=1.04] - Multiplier on the fit distance (>1 leaves margin).
44
+ * @param {THREE.Vector3|{x:number,y:number,z:number}} [options.direction] - Override viewing
45
+ * direction (target -> camera). When omitted, the current orbit angle is preserved.
46
+ * @param {boolean} [options.groundLevel] - Pin camera low with a downward pitch toward
47
+ * the bottom-center of the assembly. Defaults to true when a home direction is used.
48
+ * @returns {boolean} True if the camera was reframed, false if there was nothing to frame.
20
49
  */
21
50
  return _createClass(CameraControlsManager, [{
51
+ key: "fitCameraToScene",
52
+ value: function fitCameraToScene() {
53
+ var _options$padding, _options$groundLevel;
54
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
55
+ var component = this.sceneViewer;
56
+ var camera = component === null || component === void 0 ? void 0 : component.camera;
57
+ var scene = component === null || component === void 0 ? void 0 : component.scene;
58
+ var controls = component === null || component === void 0 ? void 0 : component.controls;
59
+ if (!camera || !scene || !camera.isPerspectiveCamera) {
60
+ console.warn('⚠️ fitCameraToScene: missing perspective camera or scene');
61
+ return false;
62
+ }
63
+ var padding = (_options$padding = options.padding) !== null && _options$padding !== void 0 ? _options$padding : 1.04;
64
+
65
+ // Make sure world matrices are current before measuring bounds
66
+ scene.updateMatrixWorld(true);
67
+ var box = this._collectSceneBounds(scene);
68
+ if (!box) {
69
+ return this._aimCameraAtWorldCenter(camera, controls, options);
70
+ }
71
+ var useGroundLevel = (_options$groundLevel = options.groundLevel) !== null && _options$groundLevel !== void 0 ? _options$groundLevel : this._isHomeDirection(options.direction);
72
+ if (useGroundLevel) {
73
+ return this._fitCameraGroundLevel(box, camera, controls, options, padding);
74
+ }
75
+ return this._fitCameraFromCenter(box, camera, controls, options, padding);
76
+ }
77
+
78
+ /**
79
+ * Aim the camera at world origin when there are no components to frame.
80
+ * @private
81
+ */
82
+ }, {
83
+ key: "_aimCameraAtWorldCenter",
84
+ value: function _aimCameraAtWorldCenter(camera, controls) {
85
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
86
+ var frameTarget = WORLD_ORIGIN.clone();
87
+ var distance = HOME_EMPTY_SCENE_DISTANCE;
88
+ var horizDir = new THREE.Vector3();
89
+ if (options.direction) {
90
+ horizDir.set(options.direction.x, options.direction.y, 0);
91
+ } else {
92
+ horizDir.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, 0);
93
+ }
94
+ if (horizDir.lengthSq() < 1e-6) {
95
+ horizDir.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, 0);
96
+ }
97
+ horizDir.normalize();
98
+ var cameraHeight = WORLD_GROUND_Z + HOME_EMPTY_CAMERA_HEIGHT_ABOVE_GROUND;
99
+ var camPos = new THREE.Vector3(frameTarget.x + horizDir.x * distance, frameTarget.y + horizDir.y * distance, cameraHeight);
100
+ camera.position.copy(camPos);
101
+ camera.near = 0.01;
102
+ camera.far = 1000;
103
+ camera.updateProjectionMatrix();
104
+ camera.lookAt(frameTarget);
105
+ if (controls) {
106
+ controls.target.copy(frameTarget);
107
+ controls.minDistance = 1;
108
+ controls.maxDistance = 20;
109
+ controls.update();
110
+ }
111
+ console.log('🎥 Camera aimed at world origin (empty scene)');
112
+ return true;
113
+ }
114
+
115
+ /**
116
+ * @private
117
+ */
118
+ }, {
119
+ key: "_collectSceneBounds",
120
+ value: function _collectSceneBounds(scene) {
121
+ var includeTypes = ['component', 'gateway', 'segment'];
122
+ var box = new THREE.Box3();
123
+ var found = false;
124
+ scene.traverse(function (obj) {
125
+ var _obj$userData;
126
+ if (includeTypes.includes((_obj$userData = obj.userData) === null || _obj$userData === void 0 ? void 0 : _obj$userData.objectType)) {
127
+ var objBox = new THREE.Box3().setFromObject(obj);
128
+ if (!objBox.isEmpty()) {
129
+ box.union(objBox);
130
+ found = true;
131
+ }
132
+ }
133
+ });
134
+ return found && !box.isEmpty() ? box : null;
135
+ }
136
+
137
+ /**
138
+ * @private
139
+ */
140
+ }, {
141
+ key: "_isHomeDirection",
142
+ value: function _isHomeDirection(direction) {
143
+ if (!direction) return false;
144
+ var horiz = Math.hypot(direction.x, direction.y);
145
+ if (horiz < 1e-6) return false;
146
+ var dx = direction.x / horiz - DEFAULT_HOME_VIEW_DIRECTION.x / Math.hypot(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y);
147
+ var dy = direction.y / horiz - DEFAULT_HOME_VIEW_DIRECTION.y / Math.hypot(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y);
148
+ return Math.hypot(dx, dy) < 0.05 && Math.abs(direction.z) < 0.5;
149
+ }
150
+
151
+ /**
152
+ * Ground-hugging camera: horizontal offset with a fixed downward pitch toward
153
+ * the bottom-center of the component group.
154
+ * @private
155
+ */
156
+ }, {
157
+ key: "_fitCameraGroundLevel",
158
+ value: function _fitCameraGroundLevel(box, camera, controls, options, padding) {
159
+ var size = box.getSize(new THREE.Vector3());
160
+ var frameTarget = this._getBoxBottomCenter(box);
161
+ var downwardPitch = THREE.MathUtils.degToRad(HOME_DOWNWARD_PITCH_DEG);
162
+ var horizDir = new THREE.Vector3();
163
+ if (options.direction) {
164
+ horizDir.set(options.direction.x, options.direction.y, 0);
165
+ } else {
166
+ horizDir.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, 0);
167
+ }
168
+ if (horizDir.lengthSq() < 1e-6) {
169
+ horizDir.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, 0);
170
+ }
171
+ horizDir.normalize();
172
+ var corners = this._getBoxCorners(box);
173
+ var vFov = THREE.MathUtils.degToRad(camera.fov);
174
+ var aspect = camera.aspect || 1;
175
+ var tanHalfV = Math.tan(vFov / 2);
176
+ var tanHalfH = tanHalfV * aspect;
177
+ var fitsAtDistance = function fitsAtDistance(distance) {
178
+ var cameraHeight = Math.max(frameTarget.z + distance * Math.tan(downwardPitch), box.min.z + HOME_CAMERA_MIN_HEIGHT);
179
+ var camPos = new THREE.Vector3(frameTarget.x + horizDir.x * distance, frameTarget.y + horizDir.y * distance, cameraHeight);
180
+ var forward = new THREE.Vector3().subVectors(frameTarget, camPos).normalize();
181
+ var worldUp = camera.up.clone().normalize();
182
+ var right = new THREE.Vector3().crossVectors(forward, worldUp);
183
+ if (right.lengthSq() < 1e-6) {
184
+ right = new THREE.Vector3().crossVectors(forward, new THREE.Vector3(1, 0, 0));
185
+ }
186
+ right.normalize();
187
+ var up = new THREE.Vector3().crossVectors(right, forward).normalize();
188
+ var _iterator = _createForOfIteratorHelper(corners),
189
+ _step;
190
+ try {
191
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
192
+ var corner = _step.value;
193
+ var rel = new THREE.Vector3().subVectors(corner, camPos);
194
+ var depth = rel.dot(forward);
195
+ if (depth <= 0.01) return false;
196
+ if (Math.abs(rel.dot(right)) / depth > tanHalfH) return false;
197
+ if (Math.abs(rel.dot(up)) / depth > tanHalfV) return false;
198
+ }
199
+ } catch (err) {
200
+ _iterator.e(err);
201
+ } finally {
202
+ _iterator.f();
203
+ }
204
+ return true;
205
+ };
206
+ var fitDistance = 0.5;
207
+ while (!fitsAtDistance(fitDistance) && fitDistance < 500) {
208
+ fitDistance *= 1.15;
209
+ }
210
+ if (fitDistance >= 500) {
211
+ console.warn('⚠️ fitCameraToScene: could not frame scene from ground level');
212
+ return false;
213
+ }
214
+
215
+ // Tighten to the closest distance that still fits, then apply padding.
216
+ var lo = 0.01;
217
+ var hi = fitDistance;
218
+ while (hi - lo > 0.1) {
219
+ var mid = (lo + hi) / 2;
220
+ if (fitsAtDistance(mid)) hi = mid;else lo = mid;
221
+ }
222
+ fitDistance = hi * padding;
223
+ var cameraHeight = Math.max(frameTarget.z + fitDistance * Math.tan(downwardPitch), box.min.z + HOME_CAMERA_MIN_HEIGHT);
224
+ var camPos = new THREE.Vector3(frameTarget.x + horizDir.x * fitDistance, frameTarget.y + horizDir.y * fitDistance, cameraHeight);
225
+ camera.position.copy(camPos);
226
+ var span = size.length();
227
+ camera.near = Math.max(fitDistance / 1000, 0.01);
228
+ camera.far = Math.max(fitDistance + span * 4, 1000);
229
+ camera.updateProjectionMatrix();
230
+ camera.lookAt(frameTarget);
231
+ if (controls) {
232
+ controls.target.copy(frameTarget);
233
+ controls.minDistance = Math.max(span * 0.1, 0.1);
234
+ controls.maxDistance = fitDistance * 4;
235
+ controls.update();
236
+ }
237
+ console.log("\uD83C\uDFA5 Camera framed to scene at ground level (distance: ".concat(fitDistance.toFixed(2), ", height: ").concat(cameraHeight.toFixed(2), ")"));
238
+ return true;
239
+ }
240
+
241
+ /**
242
+ * Bottom-center of a bounding box (centered in X/Y, at the floor in Z).
243
+ * @private
244
+ */
245
+ }, {
246
+ key: "_getBoxBottomCenter",
247
+ value: function _getBoxBottomCenter(box) {
248
+ var center = box.getCenter(new THREE.Vector3());
249
+ return new THREE.Vector3(center.x, center.y, box.min.z);
250
+ }
251
+
252
+ /**
253
+ * @private
254
+ */
255
+ }, {
256
+ key: "_getBoxCorners",
257
+ value: function _getBoxCorners(box) {
258
+ var min = box.min,
259
+ max = box.max;
260
+ var corners = [];
261
+ for (var i = 0; i < 8; i++) {
262
+ corners.push(new THREE.Vector3(i & 1 ? max.x : min.x, i & 2 ? max.y : min.y, i & 4 ? max.z : min.z));
263
+ }
264
+ return corners;
265
+ }
266
+
267
+ /**
268
+ * @private
269
+ */
270
+ }, {
271
+ key: "_fitCameraFromCenter",
272
+ value: function _fitCameraFromCenter(box, camera, controls, options, padding) {
273
+ var center = box.getCenter(new THREE.Vector3());
274
+ var target = controls !== null && controls !== void 0 && controls.target ? controls.target.clone() : new THREE.Vector3();
275
+ var direction = new THREE.Vector3();
276
+ if (options.direction) {
277
+ direction.set(options.direction.x, options.direction.y, options.direction.z);
278
+ } else {
279
+ direction.subVectors(camera.position, target);
280
+ }
281
+ if (direction.lengthSq() < 1e-6) {
282
+ direction.set(DEFAULT_HOME_VIEW_DIRECTION.x, DEFAULT_HOME_VIEW_DIRECTION.y, DEFAULT_HOME_VIEW_DIRECTION.z);
283
+ }
284
+ direction.normalize();
285
+ var forward = direction.clone().negate();
286
+ var worldUp = camera.up.clone().normalize();
287
+ var right = new THREE.Vector3().crossVectors(forward, worldUp);
288
+ if (right.lengthSq() < 1e-6) {
289
+ right = new THREE.Vector3().crossVectors(forward, new THREE.Vector3(1, 0, 0));
290
+ }
291
+ right.normalize();
292
+ var up = new THREE.Vector3().crossVectors(right, forward).normalize();
293
+ var vFov = THREE.MathUtils.degToRad(camera.fov);
294
+ var aspect = camera.aspect || 1;
295
+ var tanHalfV = Math.tan(vFov / 2);
296
+ var tanHalfH = tanHalfV * aspect;
297
+ var min = box.min;
298
+ var max = box.max;
299
+ var fitDistance = 0.01;
300
+ for (var i = 0; i < 8; i++) {
301
+ var v = new THREE.Vector3(i & 1 ? max.x : min.x, i & 2 ? max.y : min.y, i & 4 ? max.z : min.z).sub(center);
302
+ var vForward = v.dot(forward);
303
+ var vRight = Math.abs(v.dot(right));
304
+ var vUp = Math.abs(v.dot(up));
305
+ var cornerDistance = Math.max(vRight / tanHalfH - vForward, vUp / tanHalfV - vForward, -vForward + 0.01);
306
+ fitDistance = Math.max(fitDistance, cornerDistance);
307
+ }
308
+ fitDistance *= padding;
309
+ camera.position.copy(center).addScaledVector(direction, fitDistance);
310
+ var span = box.getSize(new THREE.Vector3()).length();
311
+ camera.near = Math.max(fitDistance / 1000, 0.01);
312
+ camera.far = Math.max(fitDistance + span * 4, 1000);
313
+ camera.updateProjectionMatrix();
314
+ camera.lookAt(center);
315
+ if (controls) {
316
+ controls.target.copy(center);
317
+ controls.minDistance = Math.max(span * 0.1, 0.1);
318
+ controls.maxDistance = fitDistance * 4;
319
+ controls.update();
320
+ }
321
+ console.log("\uD83C\uDFA5 Camera framed to scene (distance: ".concat(fitDistance.toFixed(2), ")"));
322
+ return true;
323
+ }
324
+
325
+ /**
326
+ * Frame the scene using the default low, ground-level home viewing angle.
327
+ * @param {Object} [options] - Passed to fitCameraToScene (padding, etc.)
328
+ * @returns {boolean}
329
+ */
330
+ }, {
331
+ key: "fitCameraToSceneHome",
332
+ value: function fitCameraToSceneHome() {
333
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
334
+ return this.fitCameraToScene(_objectSpread2(_objectSpread2({}, options), {}, {
335
+ direction: DEFAULT_HOME_VIEW_DIRECTION,
336
+ groundLevel: true
337
+ }));
338
+ }
339
+
340
+ /**
341
+ * Toggle camera auto-rotation on/off
342
+ *
343
+ * @returns {boolean} The new auto-rotation state
344
+ */
345
+ }, {
22
346
  key: "toggleCameraRotation",
23
347
  value: function toggleCameraRotation() {
24
348
  var component = this.sceneViewer;
@@ -76,4 +400,4 @@ var CameraControlsManager = /*#__PURE__*/function () {
76
400
  }]);
77
401
  }();
78
402
 
79
- export { CameraControlsManager };
403
+ export { CameraControlsManager, DEFAULT_HOME_VIEW_DIRECTION, HOME_EMPTY_CAMERA_HEIGHT_ABOVE_GROUND };
@@ -1,4 +1,4 @@
1
- import { inherits as _inherits, createClass as _createClass, superPropGet as _superPropGet, classCallCheck as _classCallCheck, callSuper as _callSuper, objectSpread2 as _objectSpread2, toConsumableArray as _toConsumableArray, createForOfIteratorHelper as _createForOfIteratorHelper } from '../../../_virtual/_rollupPluginBabelHelpers.js';
1
+ import { defineProperty as _defineProperty, inherits as _inherits, createClass as _createClass, objectSpread2 as _objectSpread2, createForOfIteratorHelper as _createForOfIteratorHelper, superPropGet as _superPropGet, classCallCheck as _classCallCheck, callSuper as _callSuper } from '../../../_virtual/_rollupPluginBabelHelpers.js';
2
2
  import * as THREE from 'three';
3
3
 
4
4
  // Reusable objects to avoid garbage collection
@@ -64,6 +64,11 @@ var transformControls = /*#__PURE__*/function (_THREE$Object3D) {
64
64
  _this.showY = true;
65
65
  _this.showZ = true;
66
66
 
67
+ // Axis interactivity (pickers). Visual handles can remain visible when false.
68
+ _this.pickX = true;
69
+ _this.pickY = true;
70
+ _this.pickZ = true;
71
+
67
72
  // Click timing for interaction delays
68
73
  _this.clickDelay = null;
69
74
  _this.lastInteractionTime = 0;
@@ -217,6 +222,9 @@ var transformControls = /*#__PURE__*/function (_THREE$Object3D) {
217
222
  this.lastInteractionTime = currentTime;
218
223
  }
219
224
  if (this.axis !== null) {
225
+ if (!this._isAxisPickable(this.axis)) {
226
+ return;
227
+ }
220
228
  _raycaster.setFromCamera(pointer, this.camera);
221
229
  var planeIntersect = this._intersectObjectWithRay(this._plane, _raycaster, true);
222
230
  if (planeIntersect) {
@@ -487,10 +495,19 @@ var transformControls = /*#__PURE__*/function (_THREE$Object3D) {
487
495
  value: function getMode() {
488
496
  return this.mode;
489
497
  }
498
+ }, {
499
+ key: "_isAxisPickable",
500
+ value: function _isAxisPickable(axisName) {
501
+ if (!axisName) return false;
502
+ if (axisName.indexOf('X') !== -1) return this.pickX !== false;
503
+ if (axisName.indexOf('Y') !== -1) return this.pickY !== false;
504
+ if (axisName.indexOf('Z') !== -1) return this.pickZ !== false;
505
+ return true;
506
+ }
490
507
  }, {
491
508
  key: "setMode",
492
509
  value: function setMode(mode) {
493
- if (mode !== 'translate') {
510
+ if (mode !== 'translate' && mode !== 'rotate') {
494
511
  console.warn("transformControls: ".concat(mode, " mode is disabled. Locking to translate."));
495
512
  mode = 'translate';
496
513
  }
@@ -731,6 +748,7 @@ var SimpleTransformGizmo = /*#__PURE__*/function (_THREE$Object3D2) {
731
748
  }, {
732
749
  key: "updateGizmoState",
733
750
  value: function updateGizmoState(controls) {
751
+ var _this$gizmo$controls$, _this$gizmo$controls$2, _this$picker$controls, _this$picker$controls2;
734
752
  // Show only the gizmo for current mode
735
753
  this.gizmo['translate'].visible = controls.mode === 'translate';
736
754
  this.gizmo['rotate'].visible = controls.mode === 'rotate';
@@ -748,43 +766,62 @@ var SimpleTransformGizmo = /*#__PURE__*/function (_THREE$Object3D2) {
748
766
  // Force maximum render priority for all gizmo objects
749
767
  this._forceMaxRenderOrder();
750
768
 
751
- // Update all handles for the current mode
752
- var handles = [].concat(_toConsumableArray(this.picker[controls.mode].children), _toConsumableArray(this.gizmo[controls.mode].children));
769
+ // Scale based on camera distance
770
+ var factor;
771
+ if (controls.camera.isOrthographicCamera) {
772
+ factor = (controls.camera.top - controls.camera.bottom) / controls.camera.zoom;
773
+ } else {
774
+ factor = controls.worldPosition.distanceTo(controls.cameraPosition) * Math.min(1.9 * Math.tan(Math.PI * controls.camera.fov / 360) / controls.camera.zoom, 7);
775
+ }
776
+ this.scale.setScalar(factor * controls.size / 4);
777
+ this._updateModeHandles(controls, (_this$gizmo$controls$ = (_this$gizmo$controls$2 = this.gizmo[controls.mode]) === null || _this$gizmo$controls$2 === void 0 ? void 0 : _this$gizmo$controls$2.children) !== null && _this$gizmo$controls$ !== void 0 ? _this$gizmo$controls$ : [], false);
778
+ this._updateModeHandles(controls, (_this$picker$controls = (_this$picker$controls2 = this.picker[controls.mode]) === null || _this$picker$controls2 === void 0 ? void 0 : _this$picker$controls2.children) !== null && _this$picker$controls !== void 0 ? _this$picker$controls : [], true);
779
+ }
780
+ }, {
781
+ key: "_isAxisShown",
782
+ value: function _isAxisShown(controls, axisName) {
783
+ if (axisName.indexOf('X') !== -1 && !controls.showX) return false;
784
+ if (axisName.indexOf('Y') !== -1 && !controls.showY) return false;
785
+ if (axisName.indexOf('Z') !== -1 && !controls.showZ) return false;
786
+ return true;
787
+ }
788
+ }, {
789
+ key: "_isAxisPickable",
790
+ value: function _isAxisPickable(controls, axisName) {
791
+ if (axisName.indexOf('X') !== -1) return controls.pickX !== false;
792
+ if (axisName.indexOf('Y') !== -1) return controls.pickY !== false;
793
+ if (axisName.indexOf('Z') !== -1) return controls.pickZ !== false;
794
+ return true;
795
+ }
796
+ }, {
797
+ key: "_updateModeHandles",
798
+ value: function _updateModeHandles(controls, handles, isPicker) {
753
799
  var _iterator = _createForOfIteratorHelper(handles),
754
800
  _step;
755
801
  try {
756
802
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
803
+ var _handle$material$_ori;
757
804
  var handle = _step.value;
758
- handle.visible = true;
759
-
760
- // Scale based on camera distance
761
- var factor = void 0;
762
- if (controls.camera.isOrthographicCamera) {
763
- factor = (controls.camera.top - controls.camera.bottom) / controls.camera.zoom;
764
- } else {
765
- factor = controls.worldPosition.distanceTo(controls.cameraPosition) * Math.min(1.9 * Math.tan(Math.PI * controls.camera.fov / 360) / controls.camera.zoom, 7);
805
+ var axisName = handle.name;
806
+ var shown = this._isAxisShown(controls, axisName);
807
+ var pickable = this._isAxisPickable(controls, axisName);
808
+ if (isPicker) {
809
+ handle.visible = shown && pickable;
810
+ continue;
766
811
  }
767
-
768
- // Apply scale to the entire gizmo, not individual handles
769
- this.scale.setScalar(factor * controls.size / 4);
770
-
771
- // Apply axis visibility constraints
772
- if (handle.name.indexOf('X') !== -1 && !controls.showX) handle.visible = false;
773
- if (handle.name.indexOf('Y') !== -1 && !controls.showY) handle.visible = false;
774
- if (handle.name.indexOf('Z') !== -1 && !controls.showZ) handle.visible = false;
775
-
776
- // Highlight selected axis
777
- if (handle.material) {
778
- handle.material._originalColor = handle.material._originalColor || handle.material.color.clone();
779
- handle.material._originalOpacity = handle.material._originalOpacity || handle.material.opacity;
812
+ handle.visible = shown;
813
+ if (!handle.material) continue;
814
+ handle.material._originalColor = handle.material._originalColor || handle.material.color.clone();
815
+ handle.material._originalOpacity = (_handle$material$_ori = handle.material._originalOpacity) !== null && _handle$material$_ori !== void 0 ? _handle$material$_ori : handle.material.opacity;
816
+ if (shown && pickable && controls.enabled && controls.axis === axisName) {
817
+ handle.material.color.setHex(0xffff00);
818
+ handle.material.opacity = 1.0;
819
+ } else if (shown && !pickable) {
820
+ handle.material.color.copy(handle.material._originalColor);
821
+ handle.material.opacity = SimpleTransformGizmo.DISABLED_AXIS_OPACITY;
822
+ } else {
780
823
  handle.material.color.copy(handle.material._originalColor);
781
824
  handle.material.opacity = handle.material._originalOpacity;
782
- if (controls.enabled && controls.axis) {
783
- if (handle.name === controls.axis) {
784
- handle.material.color.setHex(0xffff00);
785
- handle.material.opacity = 1.0;
786
- }
787
- }
788
825
  }
789
826
  }
790
827
  } catch (err) {
@@ -834,6 +871,7 @@ var SimpleTransformGizmo = /*#__PURE__*/function (_THREE$Object3D2) {
834
871
  * Optimized plane for transformation interactions
835
872
  * Simplified geometry and material for better performance
836
873
  */
874
+ _defineProperty(SimpleTransformGizmo, "DISABLED_AXIS_OPACITY", 0.3);
837
875
  var SimpleTransformPlane = /*#__PURE__*/function (_THREE$Mesh) {
838
876
  function SimpleTransformPlane() {
839
877
  var _this4;