@2112-lab/central-plant 0.3.48 → 0.3.50
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.
- package/dist/bundle/index.js +836 -264
- package/dist/cjs/src/core/centralPlant.js +119 -6
- package/dist/cjs/src/index.js +2 -0
- package/dist/cjs/src/managers/controls/cameraControlsManager.js +353 -7
- package/dist/cjs/src/managers/pathfinding/pathfindingManager.js +285 -145
- package/dist/cjs/src/managers/scene/sceneExportManager.js +19 -37
- package/dist/cjs/src/managers/scene/sceneInitializationManager.js +2 -1
- package/dist/cjs/src/managers/scene/sceneOperationsManager.js +35 -3
- package/dist/esm/src/core/centralPlant.js +99 -6
- package/dist/esm/src/index.js +1 -1
- package/dist/esm/src/managers/controls/cameraControlsManager.js +333 -9
- package/dist/esm/src/managers/pathfinding/pathfindingManager.js +286 -146
- package/dist/esm/src/managers/scene/sceneExportManager.js +20 -36
- package/dist/esm/src/managers/scene/sceneInitializationManager.js +2 -1
- package/dist/esm/src/managers/scene/sceneOperationsManager.js +35 -3
- package/dist/esm/src/utils/boundingBoxUtils.js +1 -1
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ var THREE = require('three');
|
|
|
7
7
|
var textureConfig = require('../environment/textureConfig.js');
|
|
8
8
|
var modelManager = require('./modelManager.js');
|
|
9
9
|
var sceneClearingUtility = require('../../utils/sceneClearingUtility.js');
|
|
10
|
+
var cameraControlsManager = require('../controls/cameraControlsManager.js');
|
|
10
11
|
|
|
11
12
|
function _interopNamespace(e) {
|
|
12
13
|
if (e && e.__esModule) return e;
|
|
@@ -890,6 +891,19 @@ var SceneOperationsManager = /*#__PURE__*/function () {
|
|
|
890
891
|
if (!(data !== null && data !== void 0 && (_data$scene2 = data.scene) !== null && _data$scene2 !== void 0 && _data$scene2.children) || !componentDictionary) {
|
|
891
892
|
return;
|
|
892
893
|
}
|
|
894
|
+
|
|
895
|
+
// Collect every connector uuid referenced by the scene's connections so we
|
|
896
|
+
// can inject connectors under the SAME uuid scheme the scene actually uses.
|
|
897
|
+
// Two schemes exist in the wild:
|
|
898
|
+
// - Preferred (runtime add path + minimal export): `${componentUuid}_${dictConnectorUuid}`
|
|
899
|
+
// - Legacy (older hand-authored scenes): `${componentUuid}-CONNECTOR-${index+1}`
|
|
900
|
+
var connectionEndpoints = new Set();
|
|
901
|
+
if (Array.isArray(data.connections)) {
|
|
902
|
+
data.connections.forEach(function (conn) {
|
|
903
|
+
if (conn !== null && conn !== void 0 && conn.from) connectionEndpoints.add(conn.from);
|
|
904
|
+
if (conn !== null && conn !== void 0 && conn.to) connectionEndpoints.add(conn.to);
|
|
905
|
+
});
|
|
906
|
+
}
|
|
893
907
|
var componentsProcessed = 0;
|
|
894
908
|
var connectorsInjected = 0;
|
|
895
909
|
data.scene.children.forEach(function (child) {
|
|
@@ -912,9 +926,18 @@ var SceneOperationsManager = /*#__PURE__*/function () {
|
|
|
912
926
|
|
|
913
927
|
// Deep clone the connector children from the dictionary
|
|
914
928
|
child.children = dictEntry.children.map(function (connector, index) {
|
|
915
|
-
//
|
|
916
|
-
//
|
|
917
|
-
|
|
929
|
+
// Choose the connector uuid that matches the scheme the scene's
|
|
930
|
+
// connections reference. Prefer `${componentUuid}_${dictConnectorUuid}`
|
|
931
|
+
// (sandbox add path + minimal export); fall back to the legacy
|
|
932
|
+
// index-based scheme `${componentUuid}-CONNECTOR-${index+1}` when the
|
|
933
|
+
// connections reference that form. If neither is referenced (e.g. an
|
|
934
|
+
// unconnected connector), default to the preferred scheme.
|
|
935
|
+
var legacyUuid = "".concat(child.uuid, "-CONNECTOR-").concat(index + 1);
|
|
936
|
+
var preferredUuid = connector.uuid ? "".concat(child.uuid, "_").concat(connector.uuid) : legacyUuid;
|
|
937
|
+
var connectorUuid = preferredUuid;
|
|
938
|
+
if (!connectionEndpoints.has(preferredUuid) && connectionEndpoints.has(legacyUuid)) {
|
|
939
|
+
connectorUuid = legacyUuid;
|
|
940
|
+
}
|
|
918
941
|
|
|
919
942
|
// Clone connector with deep copy of userData
|
|
920
943
|
var clonedConnector = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, connector), {}, {
|
|
@@ -1120,6 +1143,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
|
|
|
1120
1143
|
}, {
|
|
1121
1144
|
key: "_finalizeScene",
|
|
1122
1145
|
value: function _finalizeScene(data, crosscubeTextureSet, isImported) {
|
|
1146
|
+
var _component$cameraCont;
|
|
1123
1147
|
var component = this.sceneViewer;
|
|
1124
1148
|
component.currentSceneData = data;
|
|
1125
1149
|
component.crosscubeTextureSet = crosscubeTextureSet;
|
|
@@ -1131,6 +1155,14 @@ var SceneOperationsManager = /*#__PURE__*/function () {
|
|
|
1131
1155
|
}
|
|
1132
1156
|
this._setTransformControlsState(true, false); // Enable but keep hidden initially
|
|
1133
1157
|
}
|
|
1158
|
+
|
|
1159
|
+
// Frame the camera so all components fit perfectly in view (low, ground-level angle)
|
|
1160
|
+
if ((_component$cameraCont = component.cameraControlsManager) !== null && _component$cameraCont !== void 0 && _component$cameraCont.fitCameraToScene) {
|
|
1161
|
+
component.cameraControlsManager.fitCameraToScene({
|
|
1162
|
+
direction: cameraControlsManager.DEFAULT_HOME_VIEW_DIRECTION,
|
|
1163
|
+
groundLevel: true
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1134
1166
|
}
|
|
1135
1167
|
|
|
1136
1168
|
/**
|
|
@@ -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.
|
|
37
|
+
* @version 0.3.50
|
|
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$
|
|
2147
|
-
var libraryId = (_child$
|
|
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$
|
|
2226
|
-
if (((_child$
|
|
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
|
}
|
package/dist/esm/src/index.js
CHANGED
|
@@ -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
|
-
|
|
5
|
-
|
|
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
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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 };
|