@2112-lab/central-plant 0.1.45 → 0.1.46
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 +174 -52
- package/dist/cjs/src/core/centralPlant.js +1 -1
- package/dist/cjs/src/managers/components/transformOperationsManager.js +48 -4
- package/dist/cjs/src/managers/pathfinding/PathRenderingManager.js +125 -47
- package/dist/esm/src/core/centralPlant.js +1 -1
- package/dist/esm/src/managers/components/transformOperationsManager.js +48 -4
- package/dist/esm/src/managers/pathfinding/PathRenderingManager.js +125 -47
- package/package.json +1 -1
package/dist/bundle/index.js
CHANGED
|
@@ -19579,6 +19579,7 @@ var TransformOperationsManager = /*#__PURE__*/function () {
|
|
|
19579
19579
|
return null;
|
|
19580
19580
|
}
|
|
19581
19581
|
var collisionRadius = 0.5; // Radius around connector that triggers collision
|
|
19582
|
+
var endpointTolerance = 0.55; // Tolerance for determining if connector is at segment endpoint
|
|
19582
19583
|
|
|
19583
19584
|
// Get segment endpoints in world coordinates
|
|
19584
19585
|
var endpoints = this.calculateSegmentEndpoints(segment);
|
|
@@ -19597,6 +19598,13 @@ var TransformOperationsManager = /*#__PURE__*/function () {
|
|
|
19597
19598
|
var connectorWorldPos = new THREE__namespace.Vector3();
|
|
19598
19599
|
child.getWorldPosition(connectorWorldPos);
|
|
19599
19600
|
|
|
19601
|
+
// Skip connectors that are at the segment's endpoints (legitimate connection points)
|
|
19602
|
+
var distanceToStart = connectorWorldPos.distanceTo(startPoint);
|
|
19603
|
+
var distanceToEnd = connectorWorldPos.distanceTo(endPoint);
|
|
19604
|
+
if (distanceToStart <= endpointTolerance || distanceToEnd <= endpointTolerance) {
|
|
19605
|
+
return; // Skip this connector - it's at an endpoint
|
|
19606
|
+
}
|
|
19607
|
+
|
|
19600
19608
|
// Find the closest point on the segment to the connector
|
|
19601
19609
|
var segmentVector = new THREE__namespace.Vector3().subVectors(endPoint, startPoint);
|
|
19602
19610
|
var pointVector = new THREE__namespace.Vector3().subVectors(connectorWorldPos, startPoint);
|
|
@@ -19795,6 +19803,37 @@ var TransformOperationsManager = /*#__PURE__*/function () {
|
|
|
19795
19803
|
return c1.distanceTo(c2);
|
|
19796
19804
|
}
|
|
19797
19805
|
|
|
19806
|
+
/**
|
|
19807
|
+
* Constrain a position to maintain orthogonal alignment with a reference position
|
|
19808
|
+
* Movement is restricted to only along the segment's direction vector
|
|
19809
|
+
* @param {THREE.Vector3} newPosition - The proposed new position
|
|
19810
|
+
* @param {THREE.Vector3} referencePosition - The stationary reference position
|
|
19811
|
+
* @param {THREE.Object3D} segment - The segment being adjusted
|
|
19812
|
+
* @returns {THREE.Vector3} Constrained position that maintains orthogonality
|
|
19813
|
+
* @private
|
|
19814
|
+
*/
|
|
19815
|
+
}, {
|
|
19816
|
+
key: "constrainPositionToOrthogonal",
|
|
19817
|
+
value: function constrainPositionToOrthogonal(newPosition, referencePosition, segment) {
|
|
19818
|
+
// Get the segment's direction vector
|
|
19819
|
+
var direction = new THREE__namespace.Vector3(0, 1, 0);
|
|
19820
|
+
direction.applyQuaternion(segment.quaternion);
|
|
19821
|
+
direction.normalize();
|
|
19822
|
+
|
|
19823
|
+
// Calculate vector from reference position to new position
|
|
19824
|
+
var moveVector = new THREE__namespace.Vector3().subVectors(newPosition, referencePosition);
|
|
19825
|
+
|
|
19826
|
+
// Project the move vector onto the segment's direction
|
|
19827
|
+
// This gives us the component of movement along the segment's axis only
|
|
19828
|
+
var projectionLength = moveVector.dot(direction);
|
|
19829
|
+
|
|
19830
|
+
// Constrained position = reference + (projection along segment direction)
|
|
19831
|
+
var constrainedPos = new THREE__namespace.Vector3().copy(referencePosition).add(direction.clone().multiplyScalar(projectionLength));
|
|
19832
|
+
console.log("\uD83D\uDD12 Constrained movement along segment direction: [".concat(direction.x.toFixed(3), ", ").concat(direction.y.toFixed(3), ", ").concat(direction.z.toFixed(3), "]"));
|
|
19833
|
+
console.log(" Projection length: ".concat(projectionLength.toFixed(3)));
|
|
19834
|
+
return constrainedPos;
|
|
19835
|
+
}
|
|
19836
|
+
|
|
19798
19837
|
/**
|
|
19799
19838
|
* Update adjacent segment connectors to follow the moved segment's new position
|
|
19800
19839
|
* Finds segments that were connected to the moved segment BEFORE the move,
|
|
@@ -19913,18 +19952,23 @@ var TransformOperationsManager = /*#__PURE__*/function () {
|
|
|
19913
19952
|
var stationaryWorldPos = new THREE__namespace.Vector3();
|
|
19914
19953
|
stationaryConnector.getWorldPosition(stationaryWorldPos);
|
|
19915
19954
|
|
|
19955
|
+
// Constrain movement to maintain orthogonal alignment
|
|
19956
|
+
// Horizontal segments: only adjust X and Y, keep Z constant
|
|
19957
|
+
// Vertical segments: only adjust Z, keep X and Y constant
|
|
19958
|
+
var constrainedPosition = _this2.constrainPositionToOrthogonal(pair.newPosition, stationaryWorldPos, adjacentSegment);
|
|
19959
|
+
|
|
19916
19960
|
// Recreate the segment mesh with new length using explicit endpoint positions
|
|
19917
19961
|
// Pass the intended positions, not the connector objects (which may have temporary positions)
|
|
19918
|
-
_this2.recreateSegmentMeshWithNewLength(adjacentSegment, adjacentConnectors,
|
|
19962
|
+
_this2.recreateSegmentMeshWithNewLength(adjacentSegment, adjacentConnectors, constrainedPosition, stationaryWorldPos);
|
|
19919
19963
|
|
|
19920
19964
|
// CRITICAL: After recreating the mesh, BOTH connectors need to be repositioned
|
|
19921
19965
|
// because the segment's center and rotation have changed
|
|
19922
19966
|
|
|
19923
|
-
// Position moving connector at the new position
|
|
19924
|
-
var movingLocalPos = adjacentSegment.worldToLocal(
|
|
19967
|
+
// Position moving connector at the constrained position (not the raw new position)
|
|
19968
|
+
var movingLocalPos = adjacentSegment.worldToLocal(constrainedPosition.clone());
|
|
19925
19969
|
movingConnector.position.copy(movingLocalPos);
|
|
19926
19970
|
movingConnector.updateMatrixWorld(true);
|
|
19927
|
-
_this2.updateConnectorPositionInSceneData(movingConnector,
|
|
19971
|
+
_this2.updateConnectorPositionInSceneData(movingConnector, constrainedPosition, adjacentSegment);
|
|
19928
19972
|
|
|
19929
19973
|
// Position stationary connector at its original world position
|
|
19930
19974
|
var stationaryLocalPos = adjacentSegment.worldToLocal(stationaryWorldPos.clone());
|
|
@@ -24665,10 +24709,6 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
24665
24709
|
|
|
24666
24710
|
var pipeRadius = 0.1;
|
|
24667
24711
|
var pipeMaterial = this.createPipeMaterial(crosscubeTextureSet);
|
|
24668
|
-
|
|
24669
|
-
// Shared geometry for all connector caps to improve performance
|
|
24670
|
-
var capGeometry = new THREE__namespace.SphereGeometry(pipeRadius, 16, 16);
|
|
24671
|
-
this.registerDisposable(capGeometry);
|
|
24672
24712
|
paths.forEach(function (pathData, index) {
|
|
24673
24713
|
if (pathData.path && pathData.path.length >= 2) {
|
|
24674
24714
|
// Convert path points to Vector3 objects for consistent handling
|
|
@@ -24722,14 +24762,15 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
24722
24762
|
pathTo: pathData.to
|
|
24723
24763
|
};
|
|
24724
24764
|
|
|
24725
|
-
// Create caps at both ends of every segment
|
|
24726
|
-
_this3.createSegmentCaps(cylinder, capGeometry, pipeMaterial, globalSegmentIndex);
|
|
24727
|
-
|
|
24728
24765
|
// Increment global segment counter
|
|
24729
24766
|
globalSegmentIndex++;
|
|
24730
24767
|
|
|
24731
24768
|
// Add segment directly to scene instead of to polyline
|
|
24732
24769
|
sceneViewer.scene.add(cylinder);
|
|
24770
|
+
|
|
24771
|
+
// Add smooth elbow joints only at actual direction changes (not at every point)
|
|
24772
|
+
_this3.createAndAddElbowIfNeeded(pathPoints, j, pipeRadius, pipeMaterial, pathData, index, sceneViewer.scene // Pass scene instead of polyline
|
|
24773
|
+
);
|
|
24733
24774
|
}
|
|
24734
24775
|
console.log("\u2705 Created pipe path ".concat(pathData.from, "-").concat(pathData.to, " with ").concat(pathPoints.length - 1, " segments"));
|
|
24735
24776
|
}
|
|
@@ -24737,49 +24778,130 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
24737
24778
|
}
|
|
24738
24779
|
|
|
24739
24780
|
/**
|
|
24740
|
-
* Create
|
|
24741
|
-
*
|
|
24742
|
-
* @param {
|
|
24743
|
-
* @param {
|
|
24744
|
-
* @param {THREE.Material}
|
|
24745
|
-
* @param {
|
|
24781
|
+
* Create and add elbow joint if there's a direction change at the current segment
|
|
24782
|
+
* @param {Array<THREE.Vector3>} pathPoints - Array of path points
|
|
24783
|
+
* @param {number} j - Current segment index
|
|
24784
|
+
* @param {number} pipeRadius - Radius of the pipe
|
|
24785
|
+
* @param {THREE.Material} pipeMaterial - Material for the elbow
|
|
24786
|
+
* @param {Object} pathData - Path data object with from/to information
|
|
24787
|
+
* @param {number} index - Path index for naming
|
|
24788
|
+
* @param {THREE.Scene} scene - Scene object to add elbow to directly
|
|
24746
24789
|
*/
|
|
24747
24790
|
}, {
|
|
24748
|
-
key: "
|
|
24749
|
-
value: function
|
|
24750
|
-
|
|
24751
|
-
|
|
24752
|
-
|
|
24753
|
-
|
|
24754
|
-
|
|
24755
|
-
|
|
24756
|
-
|
|
24757
|
-
|
|
24758
|
-
|
|
24759
|
-
|
|
24760
|
-
|
|
24761
|
-
|
|
24762
|
-
|
|
24763
|
-
|
|
24764
|
-
|
|
24765
|
-
|
|
24766
|
-
|
|
24767
|
-
|
|
24768
|
-
|
|
24769
|
-
|
|
24770
|
-
|
|
24771
|
-
|
|
24772
|
-
|
|
24773
|
-
|
|
24774
|
-
|
|
24775
|
-
|
|
24776
|
-
|
|
24777
|
-
|
|
24791
|
+
key: "createAndAddElbowIfNeeded",
|
|
24792
|
+
value: function createAndAddElbowIfNeeded(pathPoints, j, pipeRadius, pipeMaterial, pathData, index, scene) {
|
|
24793
|
+
// Only check if there are at least 2 more points ahead
|
|
24794
|
+
if (j >= pathPoints.length - 2) {
|
|
24795
|
+
return;
|
|
24796
|
+
}
|
|
24797
|
+
var currentSegment = pathPoints[j + 1].clone().sub(pathPoints[j]).normalize();
|
|
24798
|
+
var nextSegment = pathPoints[j + 2].clone().sub(pathPoints[j + 1]).normalize();
|
|
24799
|
+
|
|
24800
|
+
// Check if there's actually a direction change (not just continuing straight)
|
|
24801
|
+
var angle = currentSegment.angleTo(nextSegment);
|
|
24802
|
+
var minAngle = Math.PI / 36; // 5 degrees minimum for creating an elbow
|
|
24803
|
+
|
|
24804
|
+
if (angle <= minAngle) {
|
|
24805
|
+
return; // No significant direction change
|
|
24806
|
+
}
|
|
24807
|
+
var elbowGeometry = this.createElbowGeometry(pathPoints[j], pathPoints[j + 1], pathPoints[j + 2], pipeRadius);
|
|
24808
|
+
if (!elbowGeometry) {
|
|
24809
|
+
return; // Failed to create geometry
|
|
24810
|
+
}
|
|
24811
|
+
var elbow = new THREE__namespace.Mesh(elbowGeometry, pipeMaterial);
|
|
24812
|
+
elbow.castShadow = true;
|
|
24813
|
+
elbow.receiveShadow = true;
|
|
24814
|
+
|
|
24815
|
+
// Make elbows selectable as well
|
|
24816
|
+
var elbowId = "pipe-elbow-".concat(pathData.from, "-").concat(pathData.to, "-").concat(j);
|
|
24817
|
+
elbow.uuid = "Pipe Elbow ".concat(j + 1, ": ").concat(pathData.from, "-").concat(pathData.to);
|
|
24818
|
+
|
|
24819
|
+
// Add userData for elbows too
|
|
24820
|
+
elbow.userData = {
|
|
24821
|
+
isPipeElbow: true,
|
|
24822
|
+
elbowId: elbowId,
|
|
24823
|
+
elbowIndex: j,
|
|
24824
|
+
pathFrom: pathData.from,
|
|
24825
|
+
pathTo: pathData.to,
|
|
24826
|
+
angle: (angle * 180 / Math.PI).toFixed(1),
|
|
24827
|
+
// Add component data for tooltips
|
|
24828
|
+
component: {
|
|
24829
|
+
type: 'PipeElbow',
|
|
24830
|
+
attributes: {
|
|
24831
|
+
angle: {
|
|
24832
|
+
key: 'Bend Angle',
|
|
24833
|
+
value: (angle * 180 / Math.PI).toFixed(1),
|
|
24834
|
+
unit: '°'
|
|
24835
|
+
}
|
|
24836
|
+
}
|
|
24837
|
+
}
|
|
24778
24838
|
};
|
|
24779
|
-
|
|
24780
|
-
|
|
24781
|
-
|
|
24782
|
-
|
|
24839
|
+
scene.add(elbow);
|
|
24840
|
+
}
|
|
24841
|
+
|
|
24842
|
+
/**
|
|
24843
|
+
* Create smooth elbow geometry to connect two pipe segments (optimized for 90-degree angles)
|
|
24844
|
+
* @param {THREE.Vector3} point1 - First point (before the joint)
|
|
24845
|
+
* @param {THREE.Vector3} point2 - Joint point (center of the elbow)
|
|
24846
|
+
* @param {THREE.Vector3} point3 - Third point (after the joint)
|
|
24847
|
+
* @param {number} radius - Pipe radius
|
|
24848
|
+
* @returns {THREE.BufferGeometry} Elbow geometry
|
|
24849
|
+
*/
|
|
24850
|
+
}, {
|
|
24851
|
+
key: "createElbowGeometry",
|
|
24852
|
+
value: function createElbowGeometry(point1, point2, point3, radius) {
|
|
24853
|
+
try {
|
|
24854
|
+
// Fixed elbow radius for 90-degree bends (simplified)
|
|
24855
|
+
var elbowRadius = radius * 3;
|
|
24856
|
+
|
|
24857
|
+
// Create a curve for the 90-degree elbow
|
|
24858
|
+
var curve = this.createElbowCurve(point1, point2, point3, elbowRadius);
|
|
24859
|
+
|
|
24860
|
+
// Fixed tubular segments for 90-degree bends (quarter circle)
|
|
24861
|
+
var tubularSegments = 12; // Good balance for smooth 90° curves
|
|
24862
|
+
var radialSegments = 16;
|
|
24863
|
+
var closed = false;
|
|
24864
|
+
var elbowGeometry = new THREE__namespace.TubeGeometry(curve, tubularSegments, radius, radialSegments, closed);
|
|
24865
|
+
return elbowGeometry;
|
|
24866
|
+
} catch (error) {
|
|
24867
|
+
console.warn('Failed to create elbow geometry:', error);
|
|
24868
|
+
return null;
|
|
24869
|
+
}
|
|
24870
|
+
}
|
|
24871
|
+
|
|
24872
|
+
/**
|
|
24873
|
+
* Create a smooth curve for the 90-degree elbow joint (simplified)
|
|
24874
|
+
* @param {THREE.Vector3} point1 - First point
|
|
24875
|
+
* @param {THREE.Vector3} point2 - Joint point
|
|
24876
|
+
* @param {THREE.Vector3} point3 - Third point
|
|
24877
|
+
* @param {number} elbowRadius - Radius of the elbow curve
|
|
24878
|
+
* @returns {THREE.Curve} Curve for the elbow
|
|
24879
|
+
*/
|
|
24880
|
+
}, {
|
|
24881
|
+
key: "createElbowCurve",
|
|
24882
|
+
value: function createElbowCurve(point1, point2, point3, elbowRadius) {
|
|
24883
|
+
// Calculate direction vectors
|
|
24884
|
+
var dir1 = point2.clone().sub(point1).normalize();
|
|
24885
|
+
var dir2 = point3.clone().sub(point2).normalize();
|
|
24886
|
+
|
|
24887
|
+
// For 90-degree bends, we can use a simpler approach with a quarter circle
|
|
24888
|
+
// The curve radius is proportional to the elbow radius
|
|
24889
|
+
var curveRadius = elbowRadius * 0.001;
|
|
24890
|
+
|
|
24891
|
+
// Calculate the offset distance from the joint point
|
|
24892
|
+
var offset = curveRadius;
|
|
24893
|
+
|
|
24894
|
+
// Calculate start and end points of the curve (offset from the joint)
|
|
24895
|
+
var curveStart = point2.clone().sub(dir1.clone().multiplyScalar(offset));
|
|
24896
|
+
var curveEnd = point2.clone().add(dir2.clone().multiplyScalar(offset));
|
|
24897
|
+
|
|
24898
|
+
// For a 90-degree bend, the control point is at the corner
|
|
24899
|
+
// We can use a quadratic bezier curve for simplicity
|
|
24900
|
+
var controlPoint = point2.clone();
|
|
24901
|
+
|
|
24902
|
+
// Create a quadratic bezier curve (simpler than cubic for 90° bends)
|
|
24903
|
+
var curve = new THREE__namespace.QuadraticBezierCurve3(curveStart, controlPoint, curveEnd);
|
|
24904
|
+
return curve;
|
|
24783
24905
|
}
|
|
24784
24906
|
|
|
24785
24907
|
/**
|
|
@@ -32446,7 +32568,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
32446
32568
|
* Initialize the CentralPlant manager
|
|
32447
32569
|
*
|
|
32448
32570
|
* @constructor
|
|
32449
|
-
* @version 0.1.
|
|
32571
|
+
* @version 0.1.46
|
|
32450
32572
|
* @updated 2025-10-22
|
|
32451
32573
|
*
|
|
32452
32574
|
* @description Creates a new CentralPlant instance and initializes internal managers and utilities.
|
|
@@ -19,7 +19,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
19
19
|
* Initialize the CentralPlant manager
|
|
20
20
|
*
|
|
21
21
|
* @constructor
|
|
22
|
-
* @version 0.1.
|
|
22
|
+
* @version 0.1.46
|
|
23
23
|
* @updated 2025-10-22
|
|
24
24
|
*
|
|
25
25
|
* @description Creates a new CentralPlant instance and initializes internal managers and utilities.
|
|
@@ -940,6 +940,7 @@ var TransformOperationsManager = /*#__PURE__*/function () {
|
|
|
940
940
|
return null;
|
|
941
941
|
}
|
|
942
942
|
var collisionRadius = 0.5; // Radius around connector that triggers collision
|
|
943
|
+
var endpointTolerance = 0.55; // Tolerance for determining if connector is at segment endpoint
|
|
943
944
|
|
|
944
945
|
// Get segment endpoints in world coordinates
|
|
945
946
|
var endpoints = this.calculateSegmentEndpoints(segment);
|
|
@@ -958,6 +959,13 @@ var TransformOperationsManager = /*#__PURE__*/function () {
|
|
|
958
959
|
var connectorWorldPos = new THREE__namespace.Vector3();
|
|
959
960
|
child.getWorldPosition(connectorWorldPos);
|
|
960
961
|
|
|
962
|
+
// Skip connectors that are at the segment's endpoints (legitimate connection points)
|
|
963
|
+
var distanceToStart = connectorWorldPos.distanceTo(startPoint);
|
|
964
|
+
var distanceToEnd = connectorWorldPos.distanceTo(endPoint);
|
|
965
|
+
if (distanceToStart <= endpointTolerance || distanceToEnd <= endpointTolerance) {
|
|
966
|
+
return; // Skip this connector - it's at an endpoint
|
|
967
|
+
}
|
|
968
|
+
|
|
961
969
|
// Find the closest point on the segment to the connector
|
|
962
970
|
var segmentVector = new THREE__namespace.Vector3().subVectors(endPoint, startPoint);
|
|
963
971
|
var pointVector = new THREE__namespace.Vector3().subVectors(connectorWorldPos, startPoint);
|
|
@@ -1156,6 +1164,37 @@ var TransformOperationsManager = /*#__PURE__*/function () {
|
|
|
1156
1164
|
return c1.distanceTo(c2);
|
|
1157
1165
|
}
|
|
1158
1166
|
|
|
1167
|
+
/**
|
|
1168
|
+
* Constrain a position to maintain orthogonal alignment with a reference position
|
|
1169
|
+
* Movement is restricted to only along the segment's direction vector
|
|
1170
|
+
* @param {THREE.Vector3} newPosition - The proposed new position
|
|
1171
|
+
* @param {THREE.Vector3} referencePosition - The stationary reference position
|
|
1172
|
+
* @param {THREE.Object3D} segment - The segment being adjusted
|
|
1173
|
+
* @returns {THREE.Vector3} Constrained position that maintains orthogonality
|
|
1174
|
+
* @private
|
|
1175
|
+
*/
|
|
1176
|
+
}, {
|
|
1177
|
+
key: "constrainPositionToOrthogonal",
|
|
1178
|
+
value: function constrainPositionToOrthogonal(newPosition, referencePosition, segment) {
|
|
1179
|
+
// Get the segment's direction vector
|
|
1180
|
+
var direction = new THREE__namespace.Vector3(0, 1, 0);
|
|
1181
|
+
direction.applyQuaternion(segment.quaternion);
|
|
1182
|
+
direction.normalize();
|
|
1183
|
+
|
|
1184
|
+
// Calculate vector from reference position to new position
|
|
1185
|
+
var moveVector = new THREE__namespace.Vector3().subVectors(newPosition, referencePosition);
|
|
1186
|
+
|
|
1187
|
+
// Project the move vector onto the segment's direction
|
|
1188
|
+
// This gives us the component of movement along the segment's axis only
|
|
1189
|
+
var projectionLength = moveVector.dot(direction);
|
|
1190
|
+
|
|
1191
|
+
// Constrained position = reference + (projection along segment direction)
|
|
1192
|
+
var constrainedPos = new THREE__namespace.Vector3().copy(referencePosition).add(direction.clone().multiplyScalar(projectionLength));
|
|
1193
|
+
console.log("\uD83D\uDD12 Constrained movement along segment direction: [".concat(direction.x.toFixed(3), ", ").concat(direction.y.toFixed(3), ", ").concat(direction.z.toFixed(3), "]"));
|
|
1194
|
+
console.log(" Projection length: ".concat(projectionLength.toFixed(3)));
|
|
1195
|
+
return constrainedPos;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1159
1198
|
/**
|
|
1160
1199
|
* Update adjacent segment connectors to follow the moved segment's new position
|
|
1161
1200
|
* Finds segments that were connected to the moved segment BEFORE the move,
|
|
@@ -1274,18 +1313,23 @@ var TransformOperationsManager = /*#__PURE__*/function () {
|
|
|
1274
1313
|
var stationaryWorldPos = new THREE__namespace.Vector3();
|
|
1275
1314
|
stationaryConnector.getWorldPosition(stationaryWorldPos);
|
|
1276
1315
|
|
|
1316
|
+
// Constrain movement to maintain orthogonal alignment
|
|
1317
|
+
// Horizontal segments: only adjust X and Y, keep Z constant
|
|
1318
|
+
// Vertical segments: only adjust Z, keep X and Y constant
|
|
1319
|
+
var constrainedPosition = _this2.constrainPositionToOrthogonal(pair.newPosition, stationaryWorldPos, adjacentSegment);
|
|
1320
|
+
|
|
1277
1321
|
// Recreate the segment mesh with new length using explicit endpoint positions
|
|
1278
1322
|
// Pass the intended positions, not the connector objects (which may have temporary positions)
|
|
1279
|
-
_this2.recreateSegmentMeshWithNewLength(adjacentSegment, adjacentConnectors,
|
|
1323
|
+
_this2.recreateSegmentMeshWithNewLength(adjacentSegment, adjacentConnectors, constrainedPosition, stationaryWorldPos);
|
|
1280
1324
|
|
|
1281
1325
|
// CRITICAL: After recreating the mesh, BOTH connectors need to be repositioned
|
|
1282
1326
|
// because the segment's center and rotation have changed
|
|
1283
1327
|
|
|
1284
|
-
// Position moving connector at the new position
|
|
1285
|
-
var movingLocalPos = adjacentSegment.worldToLocal(
|
|
1328
|
+
// Position moving connector at the constrained position (not the raw new position)
|
|
1329
|
+
var movingLocalPos = adjacentSegment.worldToLocal(constrainedPosition.clone());
|
|
1286
1330
|
movingConnector.position.copy(movingLocalPos);
|
|
1287
1331
|
movingConnector.updateMatrixWorld(true);
|
|
1288
|
-
_this2.updateConnectorPositionInSceneData(movingConnector,
|
|
1332
|
+
_this2.updateConnectorPositionInSceneData(movingConnector, constrainedPosition, adjacentSegment);
|
|
1289
1333
|
|
|
1290
1334
|
// Position stationary connector at its original world position
|
|
1291
1335
|
var stationaryLocalPos = adjacentSegment.worldToLocal(stationaryWorldPos.clone());
|
|
@@ -184,10 +184,6 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
184
184
|
|
|
185
185
|
var pipeRadius = 0.1;
|
|
186
186
|
var pipeMaterial = this.createPipeMaterial(crosscubeTextureSet);
|
|
187
|
-
|
|
188
|
-
// Shared geometry for all connector caps to improve performance
|
|
189
|
-
var capGeometry = new THREE__namespace.SphereGeometry(pipeRadius, 16, 16);
|
|
190
|
-
this.registerDisposable(capGeometry);
|
|
191
187
|
paths.forEach(function (pathData, index) {
|
|
192
188
|
if (pathData.path && pathData.path.length >= 2) {
|
|
193
189
|
// Convert path points to Vector3 objects for consistent handling
|
|
@@ -241,14 +237,15 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
241
237
|
pathTo: pathData.to
|
|
242
238
|
};
|
|
243
239
|
|
|
244
|
-
// Create caps at both ends of every segment
|
|
245
|
-
_this3.createSegmentCaps(cylinder, capGeometry, pipeMaterial, globalSegmentIndex);
|
|
246
|
-
|
|
247
240
|
// Increment global segment counter
|
|
248
241
|
globalSegmentIndex++;
|
|
249
242
|
|
|
250
243
|
// Add segment directly to scene instead of to polyline
|
|
251
244
|
sceneViewer.scene.add(cylinder);
|
|
245
|
+
|
|
246
|
+
// Add smooth elbow joints only at actual direction changes (not at every point)
|
|
247
|
+
_this3.createAndAddElbowIfNeeded(pathPoints, j, pipeRadius, pipeMaterial, pathData, index, sceneViewer.scene // Pass scene instead of polyline
|
|
248
|
+
);
|
|
252
249
|
}
|
|
253
250
|
console.log("\u2705 Created pipe path ".concat(pathData.from, "-").concat(pathData.to, " with ").concat(pathPoints.length - 1, " segments"));
|
|
254
251
|
}
|
|
@@ -256,49 +253,130 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
256
253
|
}
|
|
257
254
|
|
|
258
255
|
/**
|
|
259
|
-
* Create
|
|
260
|
-
*
|
|
261
|
-
* @param {
|
|
262
|
-
* @param {
|
|
263
|
-
* @param {THREE.Material}
|
|
264
|
-
* @param {
|
|
256
|
+
* Create and add elbow joint if there's a direction change at the current segment
|
|
257
|
+
* @param {Array<THREE.Vector3>} pathPoints - Array of path points
|
|
258
|
+
* @param {number} j - Current segment index
|
|
259
|
+
* @param {number} pipeRadius - Radius of the pipe
|
|
260
|
+
* @param {THREE.Material} pipeMaterial - Material for the elbow
|
|
261
|
+
* @param {Object} pathData - Path data object with from/to information
|
|
262
|
+
* @param {number} index - Path index for naming
|
|
263
|
+
* @param {THREE.Scene} scene - Scene object to add elbow to directly
|
|
265
264
|
*/
|
|
266
265
|
}, {
|
|
267
|
-
key: "
|
|
268
|
-
value: function
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
266
|
+
key: "createAndAddElbowIfNeeded",
|
|
267
|
+
value: function createAndAddElbowIfNeeded(pathPoints, j, pipeRadius, pipeMaterial, pathData, index, scene) {
|
|
268
|
+
// Only check if there are at least 2 more points ahead
|
|
269
|
+
if (j >= pathPoints.length - 2) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
var currentSegment = pathPoints[j + 1].clone().sub(pathPoints[j]).normalize();
|
|
273
|
+
var nextSegment = pathPoints[j + 2].clone().sub(pathPoints[j + 1]).normalize();
|
|
274
|
+
|
|
275
|
+
// Check if there's actually a direction change (not just continuing straight)
|
|
276
|
+
var angle = currentSegment.angleTo(nextSegment);
|
|
277
|
+
var minAngle = Math.PI / 36; // 5 degrees minimum for creating an elbow
|
|
278
|
+
|
|
279
|
+
if (angle <= minAngle) {
|
|
280
|
+
return; // No significant direction change
|
|
281
|
+
}
|
|
282
|
+
var elbowGeometry = this.createElbowGeometry(pathPoints[j], pathPoints[j + 1], pathPoints[j + 2], pipeRadius);
|
|
283
|
+
if (!elbowGeometry) {
|
|
284
|
+
return; // Failed to create geometry
|
|
285
|
+
}
|
|
286
|
+
var elbow = new THREE__namespace.Mesh(elbowGeometry, pipeMaterial);
|
|
287
|
+
elbow.castShadow = true;
|
|
288
|
+
elbow.receiveShadow = true;
|
|
289
|
+
|
|
290
|
+
// Make elbows selectable as well
|
|
291
|
+
var elbowId = "pipe-elbow-".concat(pathData.from, "-").concat(pathData.to, "-").concat(j);
|
|
292
|
+
elbow.uuid = "Pipe Elbow ".concat(j + 1, ": ").concat(pathData.from, "-").concat(pathData.to);
|
|
293
|
+
|
|
294
|
+
// Add userData for elbows too
|
|
295
|
+
elbow.userData = {
|
|
296
|
+
isPipeElbow: true,
|
|
297
|
+
elbowId: elbowId,
|
|
298
|
+
elbowIndex: j,
|
|
299
|
+
pathFrom: pathData.from,
|
|
300
|
+
pathTo: pathData.to,
|
|
301
|
+
angle: (angle * 180 / Math.PI).toFixed(1),
|
|
302
|
+
// Add component data for tooltips
|
|
303
|
+
component: {
|
|
304
|
+
type: 'PipeElbow',
|
|
305
|
+
attributes: {
|
|
306
|
+
angle: {
|
|
307
|
+
key: 'Bend Angle',
|
|
308
|
+
value: (angle * 180 / Math.PI).toFixed(1),
|
|
309
|
+
unit: '°'
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
297
313
|
};
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
314
|
+
scene.add(elbow);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Create smooth elbow geometry to connect two pipe segments (optimized for 90-degree angles)
|
|
319
|
+
* @param {THREE.Vector3} point1 - First point (before the joint)
|
|
320
|
+
* @param {THREE.Vector3} point2 - Joint point (center of the elbow)
|
|
321
|
+
* @param {THREE.Vector3} point3 - Third point (after the joint)
|
|
322
|
+
* @param {number} radius - Pipe radius
|
|
323
|
+
* @returns {THREE.BufferGeometry} Elbow geometry
|
|
324
|
+
*/
|
|
325
|
+
}, {
|
|
326
|
+
key: "createElbowGeometry",
|
|
327
|
+
value: function createElbowGeometry(point1, point2, point3, radius) {
|
|
328
|
+
try {
|
|
329
|
+
// Fixed elbow radius for 90-degree bends (simplified)
|
|
330
|
+
var elbowRadius = radius * 3;
|
|
331
|
+
|
|
332
|
+
// Create a curve for the 90-degree elbow
|
|
333
|
+
var curve = this.createElbowCurve(point1, point2, point3, elbowRadius);
|
|
334
|
+
|
|
335
|
+
// Fixed tubular segments for 90-degree bends (quarter circle)
|
|
336
|
+
var tubularSegments = 12; // Good balance for smooth 90° curves
|
|
337
|
+
var radialSegments = 16;
|
|
338
|
+
var closed = false;
|
|
339
|
+
var elbowGeometry = new THREE__namespace.TubeGeometry(curve, tubularSegments, radius, radialSegments, closed);
|
|
340
|
+
return elbowGeometry;
|
|
341
|
+
} catch (error) {
|
|
342
|
+
console.warn('Failed to create elbow geometry:', error);
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Create a smooth curve for the 90-degree elbow joint (simplified)
|
|
349
|
+
* @param {THREE.Vector3} point1 - First point
|
|
350
|
+
* @param {THREE.Vector3} point2 - Joint point
|
|
351
|
+
* @param {THREE.Vector3} point3 - Third point
|
|
352
|
+
* @param {number} elbowRadius - Radius of the elbow curve
|
|
353
|
+
* @returns {THREE.Curve} Curve for the elbow
|
|
354
|
+
*/
|
|
355
|
+
}, {
|
|
356
|
+
key: "createElbowCurve",
|
|
357
|
+
value: function createElbowCurve(point1, point2, point3, elbowRadius) {
|
|
358
|
+
// Calculate direction vectors
|
|
359
|
+
var dir1 = point2.clone().sub(point1).normalize();
|
|
360
|
+
var dir2 = point3.clone().sub(point2).normalize();
|
|
361
|
+
|
|
362
|
+
// For 90-degree bends, we can use a simpler approach with a quarter circle
|
|
363
|
+
// The curve radius is proportional to the elbow radius
|
|
364
|
+
var curveRadius = elbowRadius * 0.001;
|
|
365
|
+
|
|
366
|
+
// Calculate the offset distance from the joint point
|
|
367
|
+
var offset = curveRadius;
|
|
368
|
+
|
|
369
|
+
// Calculate start and end points of the curve (offset from the joint)
|
|
370
|
+
var curveStart = point2.clone().sub(dir1.clone().multiplyScalar(offset));
|
|
371
|
+
var curveEnd = point2.clone().add(dir2.clone().multiplyScalar(offset));
|
|
372
|
+
|
|
373
|
+
// For a 90-degree bend, the control point is at the corner
|
|
374
|
+
// We can use a quadratic bezier curve for simplicity
|
|
375
|
+
var controlPoint = point2.clone();
|
|
376
|
+
|
|
377
|
+
// Create a quadratic bezier curve (simpler than cubic for 90° bends)
|
|
378
|
+
var curve = new THREE__namespace.QuadraticBezierCurve3(curveStart, controlPoint, curveEnd);
|
|
379
|
+
return curve;
|
|
302
380
|
}
|
|
303
381
|
|
|
304
382
|
/**
|
|
@@ -15,7 +15,7 @@ var CentralPlant = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
15
15
|
* Initialize the CentralPlant manager
|
|
16
16
|
*
|
|
17
17
|
* @constructor
|
|
18
|
-
* @version 0.1.
|
|
18
|
+
* @version 0.1.46
|
|
19
19
|
* @updated 2025-10-22
|
|
20
20
|
*
|
|
21
21
|
* @description Creates a new CentralPlant instance and initializes internal managers and utilities.
|
|
@@ -916,6 +916,7 @@ var TransformOperationsManager = /*#__PURE__*/function () {
|
|
|
916
916
|
return null;
|
|
917
917
|
}
|
|
918
918
|
var collisionRadius = 0.5; // Radius around connector that triggers collision
|
|
919
|
+
var endpointTolerance = 0.55; // Tolerance for determining if connector is at segment endpoint
|
|
919
920
|
|
|
920
921
|
// Get segment endpoints in world coordinates
|
|
921
922
|
var endpoints = this.calculateSegmentEndpoints(segment);
|
|
@@ -934,6 +935,13 @@ var TransformOperationsManager = /*#__PURE__*/function () {
|
|
|
934
935
|
var connectorWorldPos = new THREE.Vector3();
|
|
935
936
|
child.getWorldPosition(connectorWorldPos);
|
|
936
937
|
|
|
938
|
+
// Skip connectors that are at the segment's endpoints (legitimate connection points)
|
|
939
|
+
var distanceToStart = connectorWorldPos.distanceTo(startPoint);
|
|
940
|
+
var distanceToEnd = connectorWorldPos.distanceTo(endPoint);
|
|
941
|
+
if (distanceToStart <= endpointTolerance || distanceToEnd <= endpointTolerance) {
|
|
942
|
+
return; // Skip this connector - it's at an endpoint
|
|
943
|
+
}
|
|
944
|
+
|
|
937
945
|
// Find the closest point on the segment to the connector
|
|
938
946
|
var segmentVector = new THREE.Vector3().subVectors(endPoint, startPoint);
|
|
939
947
|
var pointVector = new THREE.Vector3().subVectors(connectorWorldPos, startPoint);
|
|
@@ -1132,6 +1140,37 @@ var TransformOperationsManager = /*#__PURE__*/function () {
|
|
|
1132
1140
|
return c1.distanceTo(c2);
|
|
1133
1141
|
}
|
|
1134
1142
|
|
|
1143
|
+
/**
|
|
1144
|
+
* Constrain a position to maintain orthogonal alignment with a reference position
|
|
1145
|
+
* Movement is restricted to only along the segment's direction vector
|
|
1146
|
+
* @param {THREE.Vector3} newPosition - The proposed new position
|
|
1147
|
+
* @param {THREE.Vector3} referencePosition - The stationary reference position
|
|
1148
|
+
* @param {THREE.Object3D} segment - The segment being adjusted
|
|
1149
|
+
* @returns {THREE.Vector3} Constrained position that maintains orthogonality
|
|
1150
|
+
* @private
|
|
1151
|
+
*/
|
|
1152
|
+
}, {
|
|
1153
|
+
key: "constrainPositionToOrthogonal",
|
|
1154
|
+
value: function constrainPositionToOrthogonal(newPosition, referencePosition, segment) {
|
|
1155
|
+
// Get the segment's direction vector
|
|
1156
|
+
var direction = new THREE.Vector3(0, 1, 0);
|
|
1157
|
+
direction.applyQuaternion(segment.quaternion);
|
|
1158
|
+
direction.normalize();
|
|
1159
|
+
|
|
1160
|
+
// Calculate vector from reference position to new position
|
|
1161
|
+
var moveVector = new THREE.Vector3().subVectors(newPosition, referencePosition);
|
|
1162
|
+
|
|
1163
|
+
// Project the move vector onto the segment's direction
|
|
1164
|
+
// This gives us the component of movement along the segment's axis only
|
|
1165
|
+
var projectionLength = moveVector.dot(direction);
|
|
1166
|
+
|
|
1167
|
+
// Constrained position = reference + (projection along segment direction)
|
|
1168
|
+
var constrainedPos = new THREE.Vector3().copy(referencePosition).add(direction.clone().multiplyScalar(projectionLength));
|
|
1169
|
+
console.log("\uD83D\uDD12 Constrained movement along segment direction: [".concat(direction.x.toFixed(3), ", ").concat(direction.y.toFixed(3), ", ").concat(direction.z.toFixed(3), "]"));
|
|
1170
|
+
console.log(" Projection length: ".concat(projectionLength.toFixed(3)));
|
|
1171
|
+
return constrainedPos;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1135
1174
|
/**
|
|
1136
1175
|
* Update adjacent segment connectors to follow the moved segment's new position
|
|
1137
1176
|
* Finds segments that were connected to the moved segment BEFORE the move,
|
|
@@ -1250,18 +1289,23 @@ var TransformOperationsManager = /*#__PURE__*/function () {
|
|
|
1250
1289
|
var stationaryWorldPos = new THREE.Vector3();
|
|
1251
1290
|
stationaryConnector.getWorldPosition(stationaryWorldPos);
|
|
1252
1291
|
|
|
1292
|
+
// Constrain movement to maintain orthogonal alignment
|
|
1293
|
+
// Horizontal segments: only adjust X and Y, keep Z constant
|
|
1294
|
+
// Vertical segments: only adjust Z, keep X and Y constant
|
|
1295
|
+
var constrainedPosition = _this2.constrainPositionToOrthogonal(pair.newPosition, stationaryWorldPos, adjacentSegment);
|
|
1296
|
+
|
|
1253
1297
|
// Recreate the segment mesh with new length using explicit endpoint positions
|
|
1254
1298
|
// Pass the intended positions, not the connector objects (which may have temporary positions)
|
|
1255
|
-
_this2.recreateSegmentMeshWithNewLength(adjacentSegment, adjacentConnectors,
|
|
1299
|
+
_this2.recreateSegmentMeshWithNewLength(adjacentSegment, adjacentConnectors, constrainedPosition, stationaryWorldPos);
|
|
1256
1300
|
|
|
1257
1301
|
// CRITICAL: After recreating the mesh, BOTH connectors need to be repositioned
|
|
1258
1302
|
// because the segment's center and rotation have changed
|
|
1259
1303
|
|
|
1260
|
-
// Position moving connector at the new position
|
|
1261
|
-
var movingLocalPos = adjacentSegment.worldToLocal(
|
|
1304
|
+
// Position moving connector at the constrained position (not the raw new position)
|
|
1305
|
+
var movingLocalPos = adjacentSegment.worldToLocal(constrainedPosition.clone());
|
|
1262
1306
|
movingConnector.position.copy(movingLocalPos);
|
|
1263
1307
|
movingConnector.updateMatrixWorld(true);
|
|
1264
|
-
_this2.updateConnectorPositionInSceneData(movingConnector,
|
|
1308
|
+
_this2.updateConnectorPositionInSceneData(movingConnector, constrainedPosition, adjacentSegment);
|
|
1265
1309
|
|
|
1266
1310
|
// Position stationary connector at its original world position
|
|
1267
1311
|
var stationaryLocalPos = adjacentSegment.worldToLocal(stationaryWorldPos.clone());
|
|
@@ -160,10 +160,6 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
160
160
|
|
|
161
161
|
var pipeRadius = 0.1;
|
|
162
162
|
var pipeMaterial = this.createPipeMaterial(crosscubeTextureSet);
|
|
163
|
-
|
|
164
|
-
// Shared geometry for all connector caps to improve performance
|
|
165
|
-
var capGeometry = new THREE.SphereGeometry(pipeRadius, 16, 16);
|
|
166
|
-
this.registerDisposable(capGeometry);
|
|
167
163
|
paths.forEach(function (pathData, index) {
|
|
168
164
|
if (pathData.path && pathData.path.length >= 2) {
|
|
169
165
|
// Convert path points to Vector3 objects for consistent handling
|
|
@@ -217,14 +213,15 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
217
213
|
pathTo: pathData.to
|
|
218
214
|
};
|
|
219
215
|
|
|
220
|
-
// Create caps at both ends of every segment
|
|
221
|
-
_this3.createSegmentCaps(cylinder, capGeometry, pipeMaterial, globalSegmentIndex);
|
|
222
|
-
|
|
223
216
|
// Increment global segment counter
|
|
224
217
|
globalSegmentIndex++;
|
|
225
218
|
|
|
226
219
|
// Add segment directly to scene instead of to polyline
|
|
227
220
|
sceneViewer.scene.add(cylinder);
|
|
221
|
+
|
|
222
|
+
// Add smooth elbow joints only at actual direction changes (not at every point)
|
|
223
|
+
_this3.createAndAddElbowIfNeeded(pathPoints, j, pipeRadius, pipeMaterial, pathData, index, sceneViewer.scene // Pass scene instead of polyline
|
|
224
|
+
);
|
|
228
225
|
}
|
|
229
226
|
console.log("\u2705 Created pipe path ".concat(pathData.from, "-").concat(pathData.to, " with ").concat(pathPoints.length - 1, " segments"));
|
|
230
227
|
}
|
|
@@ -232,49 +229,130 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
|
|
|
232
229
|
}
|
|
233
230
|
|
|
234
231
|
/**
|
|
235
|
-
* Create
|
|
236
|
-
*
|
|
237
|
-
* @param {
|
|
238
|
-
* @param {
|
|
239
|
-
* @param {THREE.Material}
|
|
240
|
-
* @param {
|
|
232
|
+
* Create and add elbow joint if there's a direction change at the current segment
|
|
233
|
+
* @param {Array<THREE.Vector3>} pathPoints - Array of path points
|
|
234
|
+
* @param {number} j - Current segment index
|
|
235
|
+
* @param {number} pipeRadius - Radius of the pipe
|
|
236
|
+
* @param {THREE.Material} pipeMaterial - Material for the elbow
|
|
237
|
+
* @param {Object} pathData - Path data object with from/to information
|
|
238
|
+
* @param {number} index - Path index for naming
|
|
239
|
+
* @param {THREE.Scene} scene - Scene object to add elbow to directly
|
|
241
240
|
*/
|
|
242
241
|
}, {
|
|
243
|
-
key: "
|
|
244
|
-
value: function
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
242
|
+
key: "createAndAddElbowIfNeeded",
|
|
243
|
+
value: function createAndAddElbowIfNeeded(pathPoints, j, pipeRadius, pipeMaterial, pathData, index, scene) {
|
|
244
|
+
// Only check if there are at least 2 more points ahead
|
|
245
|
+
if (j >= pathPoints.length - 2) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
var currentSegment = pathPoints[j + 1].clone().sub(pathPoints[j]).normalize();
|
|
249
|
+
var nextSegment = pathPoints[j + 2].clone().sub(pathPoints[j + 1]).normalize();
|
|
250
|
+
|
|
251
|
+
// Check if there's actually a direction change (not just continuing straight)
|
|
252
|
+
var angle = currentSegment.angleTo(nextSegment);
|
|
253
|
+
var minAngle = Math.PI / 36; // 5 degrees minimum for creating an elbow
|
|
254
|
+
|
|
255
|
+
if (angle <= minAngle) {
|
|
256
|
+
return; // No significant direction change
|
|
257
|
+
}
|
|
258
|
+
var elbowGeometry = this.createElbowGeometry(pathPoints[j], pathPoints[j + 1], pathPoints[j + 2], pipeRadius);
|
|
259
|
+
if (!elbowGeometry) {
|
|
260
|
+
return; // Failed to create geometry
|
|
261
|
+
}
|
|
262
|
+
var elbow = new THREE.Mesh(elbowGeometry, pipeMaterial);
|
|
263
|
+
elbow.castShadow = true;
|
|
264
|
+
elbow.receiveShadow = true;
|
|
265
|
+
|
|
266
|
+
// Make elbows selectable as well
|
|
267
|
+
var elbowId = "pipe-elbow-".concat(pathData.from, "-").concat(pathData.to, "-").concat(j);
|
|
268
|
+
elbow.uuid = "Pipe Elbow ".concat(j + 1, ": ").concat(pathData.from, "-").concat(pathData.to);
|
|
269
|
+
|
|
270
|
+
// Add userData for elbows too
|
|
271
|
+
elbow.userData = {
|
|
272
|
+
isPipeElbow: true,
|
|
273
|
+
elbowId: elbowId,
|
|
274
|
+
elbowIndex: j,
|
|
275
|
+
pathFrom: pathData.from,
|
|
276
|
+
pathTo: pathData.to,
|
|
277
|
+
angle: (angle * 180 / Math.PI).toFixed(1),
|
|
278
|
+
// Add component data for tooltips
|
|
279
|
+
component: {
|
|
280
|
+
type: 'PipeElbow',
|
|
281
|
+
attributes: {
|
|
282
|
+
angle: {
|
|
283
|
+
key: 'Bend Angle',
|
|
284
|
+
value: (angle * 180 / Math.PI).toFixed(1),
|
|
285
|
+
unit: '°'
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
273
289
|
};
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
290
|
+
scene.add(elbow);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Create smooth elbow geometry to connect two pipe segments (optimized for 90-degree angles)
|
|
295
|
+
* @param {THREE.Vector3} point1 - First point (before the joint)
|
|
296
|
+
* @param {THREE.Vector3} point2 - Joint point (center of the elbow)
|
|
297
|
+
* @param {THREE.Vector3} point3 - Third point (after the joint)
|
|
298
|
+
* @param {number} radius - Pipe radius
|
|
299
|
+
* @returns {THREE.BufferGeometry} Elbow geometry
|
|
300
|
+
*/
|
|
301
|
+
}, {
|
|
302
|
+
key: "createElbowGeometry",
|
|
303
|
+
value: function createElbowGeometry(point1, point2, point3, radius) {
|
|
304
|
+
try {
|
|
305
|
+
// Fixed elbow radius for 90-degree bends (simplified)
|
|
306
|
+
var elbowRadius = radius * 3;
|
|
307
|
+
|
|
308
|
+
// Create a curve for the 90-degree elbow
|
|
309
|
+
var curve = this.createElbowCurve(point1, point2, point3, elbowRadius);
|
|
310
|
+
|
|
311
|
+
// Fixed tubular segments for 90-degree bends (quarter circle)
|
|
312
|
+
var tubularSegments = 12; // Good balance for smooth 90° curves
|
|
313
|
+
var radialSegments = 16;
|
|
314
|
+
var closed = false;
|
|
315
|
+
var elbowGeometry = new THREE.TubeGeometry(curve, tubularSegments, radius, radialSegments, closed);
|
|
316
|
+
return elbowGeometry;
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.warn('Failed to create elbow geometry:', error);
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Create a smooth curve for the 90-degree elbow joint (simplified)
|
|
325
|
+
* @param {THREE.Vector3} point1 - First point
|
|
326
|
+
* @param {THREE.Vector3} point2 - Joint point
|
|
327
|
+
* @param {THREE.Vector3} point3 - Third point
|
|
328
|
+
* @param {number} elbowRadius - Radius of the elbow curve
|
|
329
|
+
* @returns {THREE.Curve} Curve for the elbow
|
|
330
|
+
*/
|
|
331
|
+
}, {
|
|
332
|
+
key: "createElbowCurve",
|
|
333
|
+
value: function createElbowCurve(point1, point2, point3, elbowRadius) {
|
|
334
|
+
// Calculate direction vectors
|
|
335
|
+
var dir1 = point2.clone().sub(point1).normalize();
|
|
336
|
+
var dir2 = point3.clone().sub(point2).normalize();
|
|
337
|
+
|
|
338
|
+
// For 90-degree bends, we can use a simpler approach with a quarter circle
|
|
339
|
+
// The curve radius is proportional to the elbow radius
|
|
340
|
+
var curveRadius = elbowRadius * 0.001;
|
|
341
|
+
|
|
342
|
+
// Calculate the offset distance from the joint point
|
|
343
|
+
var offset = curveRadius;
|
|
344
|
+
|
|
345
|
+
// Calculate start and end points of the curve (offset from the joint)
|
|
346
|
+
var curveStart = point2.clone().sub(dir1.clone().multiplyScalar(offset));
|
|
347
|
+
var curveEnd = point2.clone().add(dir2.clone().multiplyScalar(offset));
|
|
348
|
+
|
|
349
|
+
// For a 90-degree bend, the control point is at the corner
|
|
350
|
+
// We can use a quadratic bezier curve for simplicity
|
|
351
|
+
var controlPoint = point2.clone();
|
|
352
|
+
|
|
353
|
+
// Create a quadratic bezier curve (simpler than cubic for 90° bends)
|
|
354
|
+
var curve = new THREE.QuadraticBezierCurve3(curveStart, controlPoint, curveEnd);
|
|
355
|
+
return curve;
|
|
278
356
|
}
|
|
279
357
|
|
|
280
358
|
/**
|