@2112-lab/central-plant 0.1.30 → 0.1.32

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.
@@ -18183,7 +18183,7 @@ var PathManager = /*#__PURE__*/function () {
18183
18183
  }
18184
18184
 
18185
18185
  /**
18186
- * A* pathfinding algorithm
18186
+ * A* pathfinding algorithm with bend minimization
18187
18187
  * @private
18188
18188
  * @param {string} start - Start voxel key
18189
18189
  * @param {string} goal - Goal voxel key
@@ -18242,7 +18242,11 @@ var PathManager = /*#__PURE__*/function () {
18242
18242
  var _gScore$neighbor;
18243
18243
  var neighbor = _step2.value;
18244
18244
  if (occupied.has(neighbor)) continue;
18245
- var tentativeG = gScore[current] + 1;
18245
+
18246
+ // Calculate cost with bend penalty
18247
+ var tentativeG = this.calculateGScore(current, neighbor, cameFrom[current], gScore[current]);
18248
+ // const tentativeG = gScore[current] + 1;
18249
+
18246
18250
  if (tentativeG < ((_gScore$neighbor = gScore[neighbor]) !== null && _gScore$neighbor !== void 0 ? _gScore$neighbor : Infinity)) {
18247
18251
  cameFrom[neighbor] = current;
18248
18252
  gScore[neighbor] = tentativeG;
@@ -18259,6 +18263,59 @@ var PathManager = /*#__PURE__*/function () {
18259
18263
  return null;
18260
18264
  }
18261
18265
 
18266
+ /**
18267
+ * Calculate G score with bend penalty
18268
+ * @private
18269
+ * @param {string} current - Current voxel key
18270
+ * @param {string} neighbor - Neighbor voxel key
18271
+ * @param {string} parent - Parent voxel key (null if current is start)
18272
+ * @param {number} currentG - Current G score
18273
+ * @returns {number} G score with bend penalty
18274
+ */
18275
+ }, {
18276
+ key: "calculateGScore",
18277
+ value: function calculateGScore(current, neighbor, parent, currentG) {
18278
+ var cost = currentG + 1; // Base movement cost
18279
+
18280
+ if (parent) {
18281
+ // Check if this move creates a bend
18282
+ var _parent$split$map = parent.split(',').map(Number),
18283
+ _parent$split$map2 = _slicedToArray(_parent$split$map, 3),
18284
+ px = _parent$split$map2[0],
18285
+ py = _parent$split$map2[1],
18286
+ pz = _parent$split$map2[2];
18287
+ var _current$split$map = current.split(',').map(Number),
18288
+ _current$split$map2 = _slicedToArray(_current$split$map, 3),
18289
+ cx = _current$split$map2[0],
18290
+ cy = _current$split$map2[1],
18291
+ cz = _current$split$map2[2];
18292
+ var _neighbor$split$map = neighbor.split(',').map(Number),
18293
+ _neighbor$split$map2 = _slicedToArray(_neighbor$split$map, 3),
18294
+ nx = _neighbor$split$map2[0],
18295
+ ny = _neighbor$split$map2[1],
18296
+ nz = _neighbor$split$map2[2];
18297
+
18298
+ // Direction from parent to current
18299
+ var prevDir = {
18300
+ x: cx - px,
18301
+ y: cy - py,
18302
+ z: cz - pz
18303
+ };
18304
+ // Direction from current to neighbor
18305
+ var nextDir = {
18306
+ x: nx - cx,
18307
+ y: ny - cy,
18308
+ z: nz - cz
18309
+ };
18310
+
18311
+ // If directions are different, add bend penalty
18312
+ if (prevDir.x !== nextDir.x || prevDir.y !== nextDir.y || prevDir.z !== nextDir.z) {
18313
+ cost += 0.00001; // Bend penalty - adjust this value to control how much bends are avoided
18314
+ }
18315
+ }
18316
+ return cost;
18317
+ }
18318
+
18262
18319
  /**
18263
18320
  * Find a path respecting virtual segments
18264
18321
  * @private
@@ -19356,6 +19413,9 @@ var PathfindingManager = /*#__PURE__*/function () {
19356
19413
  console.log('Generated gateways:', JSON.parse(JSON.stringify(pathfindingResult.gateways)));
19357
19414
  console.log('Rewired connections:', JSON.parse(JSON.stringify(pathfindingResult.rewiredConnections)));
19358
19415
 
19416
+ // Check for duplicate direction vectors going into the same gateways
19417
+ this.checkForDuplicateGatewayDirections(pathfindingResult, sceneData, connections);
19418
+
19359
19419
  // Create gateways in the scene if requested
19360
19420
  if (createGateways && pathfindingResult.gateways) {
19361
19421
  pathfindingResult.gateways.forEach(function (gateway) {
@@ -20607,6 +20667,130 @@ var PathfindingManager = /*#__PURE__*/function () {
20607
20667
  });
20608
20668
  return optimizedPaths;
20609
20669
  }
20670
+
20671
+ /**
20672
+ * Check for duplicate direction vectors going into the same gateways
20673
+ * This helps diagnose path overlapping issues caused by incorrect connector directions after rotation
20674
+ * @param {Object} pathfindingResult - Result from pathfinder.findPaths()
20675
+ * @param {Object} sceneData - Scene data used for pathfinding
20676
+ * @param {Array} connections - Original connections array
20677
+ */
20678
+ }, {
20679
+ key: "checkForDuplicateGatewayDirections",
20680
+ value: function checkForDuplicateGatewayDirections(pathfindingResult, sceneData, connections) {
20681
+ var _this4 = this;
20682
+ if (!pathfindingResult.gateways || !pathfindingResult.rewiredConnections) {
20683
+ return; // No gateways or rewired connections to check
20684
+ }
20685
+ console.log('🔍 Checking for duplicate direction vectors going into gateways...');
20686
+
20687
+ // Group connections by gateway
20688
+ var gatewayConnections = new Map();
20689
+ pathfindingResult.rewiredConnections.forEach(function (conn) {
20690
+ // Check if this connection goes TO a gateway
20691
+ var toGateway = pathfindingResult.gateways.find(function (gw) {
20692
+ return gw.id === conn.to;
20693
+ });
20694
+ if (toGateway) {
20695
+ if (!gatewayConnections.has(conn.to)) {
20696
+ gatewayConnections.set(conn.to, []);
20697
+ }
20698
+ gatewayConnections.get(conn.to).push({
20699
+ connection: conn,
20700
+ gateway: toGateway
20701
+ });
20702
+ }
20703
+ });
20704
+
20705
+ // Check each gateway for duplicate direction vectors
20706
+ var hasErrors = false;
20707
+ gatewayConnections.forEach(function (connectionsToGateway, gatewayId) {
20708
+ if (connectionsToGateway.length < 2) {
20709
+ return; // Need at least 2 connections to have duplicates
20710
+ }
20711
+ console.log("\uD83D\uDD0D Checking gateway ".concat(gatewayId, " with ").concat(connectionsToGateway.length, " incoming connections"));
20712
+
20713
+ // Get direction vectors for each connection going into this gateway
20714
+ var directionVectors = [];
20715
+ connectionsToGateway.forEach(function (_ref) {
20716
+ var connection = _ref.connection;
20717
+ // Find the connector object in the scene data
20718
+ var connectorObj = _this4.findObjectInSceneData(sceneData, connection.from);
20719
+ if (connectorObj && connectorObj.userData && connectorObj.userData.direction) {
20720
+ var direction = connectorObj.userData.direction;
20721
+ directionVectors.push({
20722
+ connectionFrom: connection.from,
20723
+ direction: direction,
20724
+ directionString: "[".concat(direction[0], ", ").concat(direction[1], ", ").concat(direction[2], "]")
20725
+ });
20726
+ console.log(" \uD83D\uDCCD Connector ".concat(connection.from, " direction: [").concat(direction[0], ", ").concat(direction[1], ", ").concat(direction[2], "]"));
20727
+ } else {
20728
+ console.warn(" \u26A0\uFE0F Could not find direction for connector ".concat(connection.from));
20729
+ }
20730
+ });
20731
+
20732
+ // Check for duplicate directions
20733
+ var directionMap = new Map();
20734
+ directionVectors.forEach(function (_ref2) {
20735
+ var connectionFrom = _ref2.connectionFrom;
20736
+ _ref2.direction;
20737
+ var directionString = _ref2.directionString;
20738
+ if (!directionMap.has(directionString)) {
20739
+ directionMap.set(directionString, []);
20740
+ }
20741
+ directionMap.get(directionString).push(connectionFrom);
20742
+ });
20743
+
20744
+ // Find duplicates
20745
+ directionMap.forEach(function (connectors, directionString) {
20746
+ if (connectors.length > 1) {
20747
+ hasErrors = true;
20748
+ console.error("\u274C DUPLICATE DIRECTION VECTORS DETECTED!\n" + " Gateway: ".concat(gatewayId, "\n") + " Direction: ".concat(directionString, "\n") + " Connectors with same direction: ".concat(connectors.join(', '), "\n") + " This will cause path overlapping! The connectors going into the same gateway should have different direction vectors after component rotation.");
20749
+ }
20750
+ });
20751
+ });
20752
+ if (!hasErrors) {
20753
+ console.log('✅ No duplicate direction vectors detected in gateway connections');
20754
+ } else {
20755
+ console.error("\u274C PATHFINDING DIAGNOSTIC: Found duplicate direction vectors going into gateways.\n" + " This is likely caused by connector direction vectors not being properly updated after component rotation.\n" + " The rotation code should transform the connector userData.direction arrays to reflect the new orientation.");
20756
+ }
20757
+ }
20758
+
20759
+ /**
20760
+ * Helper method to find an object in scene data by UUID
20761
+ * @param {Object} sceneData - Scene data to search
20762
+ * @param {string} uuid - UUID to find
20763
+ * @returns {Object|null} Found object or null
20764
+ */
20765
+ }, {
20766
+ key: "findObjectInSceneData",
20767
+ value: function findObjectInSceneData(sceneData, uuid) {
20768
+ var _searchChildren = function searchChildren(children) {
20769
+ var _iterator2 = _createForOfIteratorHelper(children),
20770
+ _step2;
20771
+ try {
20772
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
20773
+ var child = _step2.value;
20774
+ if (child.uuid === uuid) {
20775
+ return child;
20776
+ }
20777
+ if (child.children) {
20778
+ var found = _searchChildren(child.children);
20779
+ if (found) return found;
20780
+ }
20781
+ }
20782
+ } catch (err) {
20783
+ _iterator2.e(err);
20784
+ } finally {
20785
+ _iterator2.f();
20786
+ }
20787
+ return null;
20788
+ };
20789
+ if (sceneData.object && sceneData.object.children) {
20790
+ return _searchChildren(sceneData.object.children);
20791
+ }
20792
+ return null;
20793
+ }
20610
20794
  }]);
20611
20795
  }();
20612
20796
 
@@ -26055,6 +26239,9 @@ var CentralPlant = /*#__PURE__*/function () {
26055
26239
  // Update the Three.js object position
26056
26240
  component.position[axis] += value;
26057
26241
 
26242
+ // Update world bounding boxes for the component and its children
26243
+ this.updateWorldBoundingBoxes(component);
26244
+
26058
26245
  // Update the associated JSON object in currentSceneData if it exists
26059
26246
  if (this.sceneViewer.currentSceneData && this.sceneViewer.currentSceneData.scene) {
26060
26247
  if (component.userData.associatedJsonObject) {
@@ -26168,6 +26355,14 @@ var CentralPlant = /*#__PURE__*/function () {
26168
26355
  // Apply the rotation
26169
26356
  component.rotation[axis] += radians;
26170
26357
 
26358
+ // Update direction vectors for connectors if this is a 90-degree rotation
26359
+ if (value % 90 === 0) {
26360
+ this.updateDirectionAfterRotation(component, axis, value);
26361
+ }
26362
+
26363
+ // Update world bounding boxes for the component and its children
26364
+ this.updateWorldBoundingBoxes(component);
26365
+
26171
26366
  // Update the associated JSON object in currentSceneData if it exists
26172
26367
  if (this.sceneViewer.currentSceneData && this.sceneViewer.currentSceneData.scene) {
26173
26368
  if (component.userData.associatedJsonObject) {
@@ -26215,6 +26410,173 @@ var CentralPlant = /*#__PURE__*/function () {
26215
26410
  return true;
26216
26411
  }
26217
26412
 
26413
+ /**
26414
+ * Update userData.direction for components after 90-degree rotation
26415
+ * This method handles the direction vector transformation for connectors when their parent component is rotated
26416
+ *
26417
+ * @param {THREE.Object3D} component - The component that was rotated
26418
+ * @param {string} axis - The axis of rotation ('x', 'y', or 'z')
26419
+ * @param {number} degrees - The rotation angle in degrees (should be multiple of 90)
26420
+ * @private
26421
+ */
26422
+ }, {
26423
+ key: "updateDirectionAfterRotation",
26424
+ value: function updateDirectionAfterRotation(component, axis, degrees) {
26425
+ var _this = this;
26426
+ if (!component) {
26427
+ console.warn('⚠️ updateDirectionAfterRotation: No component provided');
26428
+ return;
26429
+ }
26430
+
26431
+ // Only handle 90-degree increments
26432
+ if (degrees % 90 !== 0) {
26433
+ console.warn('⚠️ updateDirectionAfterRotation: Only 90-degree increments are supported');
26434
+ return;
26435
+ }
26436
+
26437
+ // Normalize degrees to 0-360 range
26438
+ var normalizedDegrees = (degrees % 360 + 360) % 360;
26439
+ console.log("\uD83D\uDD04 Updating direction vectors for ".concat(component.name || component.uuid, " after ").concat(degrees, "\xB0 rotation around ").concat(axis, " axis"));
26440
+
26441
+ // Traverse all children (connectors) and update their direction vectors
26442
+ component.traverse(function (child) {
26443
+ if (child.userData && Array.isArray(child.userData.direction) && child.userData.componentType === 'connector') {
26444
+ var originalDirection = _toConsumableArray(child.userData.direction);
26445
+ var newDirection = _this.rotateDirectionVector(originalDirection, axis, normalizedDegrees);
26446
+
26447
+ // Update the direction
26448
+ child.userData.direction = newDirection;
26449
+ console.log("\uD83D\uDCCD Updated connector ".concat(child.name || child.uuid, " direction:"), {
26450
+ original: originalDirection,
26451
+ new: newDirection,
26452
+ rotationAxis: axis,
26453
+ rotationDegrees: degrees
26454
+ });
26455
+ }
26456
+ });
26457
+ }
26458
+
26459
+ /**
26460
+ * Rotate a direction vector by 90-degree increments around a specific axis
26461
+ *
26462
+ * @param {Array<number>} direction - The original direction vector [x, y, z]
26463
+ * @param {string} axis - The axis of rotation ('x', 'y', or 'z')
26464
+ * @param {number} degrees - The rotation angle in degrees (must be multiple of 90)
26465
+ * @returns {Array<number>} The new direction vector [x, y, z]
26466
+ * @private
26467
+ */
26468
+ }, {
26469
+ key: "rotateDirectionVector",
26470
+ value: function rotateDirectionVector(direction, axis, degrees) {
26471
+ if (!Array.isArray(direction) || direction.length !== 3) {
26472
+ console.warn('⚠️ rotateDirectionVector: Invalid direction vector');
26473
+ return direction;
26474
+ }
26475
+ var _direction = _slicedToArray(direction, 3),
26476
+ x = _direction[0],
26477
+ y = _direction[1],
26478
+ z = _direction[2];
26479
+ var steps = degrees / 90; // Number of 90-degree steps
26480
+
26481
+ for (var i = 0; i < steps; i++) {
26482
+ var newX = x,
26483
+ newY = y,
26484
+ newZ = z;
26485
+ switch (axis) {
26486
+ case 'x':
26487
+ // Rotation around X-axis: Y becomes -Z, Z becomes Y
26488
+ newY = -z;
26489
+ newZ = y;
26490
+ break;
26491
+ case 'y':
26492
+ // Rotation around Y-axis: X becomes Z, Z becomes -X
26493
+ newX = z;
26494
+ newZ = -x;
26495
+ break;
26496
+ case 'z':
26497
+ // Rotation around Z-axis: X becomes -Y, Y becomes X
26498
+ newX = -y;
26499
+ newY = x;
26500
+ break;
26501
+ default:
26502
+ console.warn("\u26A0\uFE0F rotateDirectionVector: Invalid axis '".concat(axis, "'"));
26503
+ return direction;
26504
+ }
26505
+ x = newX;
26506
+ y = newY;
26507
+ z = newZ;
26508
+ }
26509
+ return [x, y, z];
26510
+ }
26511
+
26512
+ /**
26513
+ * Update world bounding boxes for a component and its children after transformation
26514
+ * This method recalculates bounding boxes based on the new position/rotation
26515
+ *
26516
+ * @param {THREE.Object3D} component - The component that was transformed
26517
+ * @private
26518
+ */
26519
+ }, {
26520
+ key: "updateWorldBoundingBoxes",
26521
+ value: function updateWorldBoundingBoxes(component) {
26522
+ var _this$sceneViewer, _this$sceneViewer2;
26523
+ if (!component) {
26524
+ console.warn('⚠️ updateWorldBoundingBoxes: No component provided');
26525
+ return;
26526
+ }
26527
+ console.log("\uD83D\uDCE6 Updating world bounding boxes for ".concat(component.name || component.uuid));
26528
+
26529
+ // Update bounding box for the main component and all its children
26530
+ component.traverse(function (child) {
26531
+ if (child.isMesh || child.userData.componentType) {
26532
+ try {
26533
+ // Force matrix updates to ensure accurate bounding box calculation
26534
+ child.updateMatrix();
26535
+ child.updateMatrixWorld(true);
26536
+
26537
+ // Calculate new world bounding box
26538
+ var boundingBox = new THREE__namespace.Box3().setFromObject(child);
26539
+
26540
+ // Update userData.worldBoundingBox if it exists
26541
+ if (child.userData) {
26542
+ child.userData.worldBoundingBox = {
26543
+ min: boundingBox.min.toArray(),
26544
+ max: boundingBox.max.toArray()
26545
+ };
26546
+ console.log("\uD83D\uDCE6 Updated bounding box for ".concat(child.name || child.uuid, ":"), {
26547
+ min: boundingBox.min.toArray(),
26548
+ max: boundingBox.max.toArray()
26549
+ });
26550
+ }
26551
+
26552
+ // Update associated JSON object if it exists
26553
+ if (child.userData.associatedJsonObject) {
26554
+ if (!child.userData.associatedJsonObject.userData) {
26555
+ child.userData.associatedJsonObject.userData = {};
26556
+ }
26557
+ child.userData.associatedJsonObject.userData.worldBoundingBox = {
26558
+ min: boundingBox.min.toArray(),
26559
+ max: boundingBox.max.toArray()
26560
+ };
26561
+ console.log("\uD83D\uDCE6 Updated JSON bounding box for ".concat(child.name || child.uuid));
26562
+ }
26563
+ } catch (error) {
26564
+ console.warn("\u26A0\uFE0F Error updating bounding box for ".concat(child.name || child.uuid, ":"), error);
26565
+ }
26566
+ }
26567
+ });
26568
+
26569
+ // Also trigger the pathfinding manager's recompute method for consistency
26570
+ if ((_this$sceneViewer = this.sceneViewer) !== null && _this$sceneViewer !== void 0 && _this$sceneViewer.pathfindingManager && (_this$sceneViewer2 = this.sceneViewer) !== null && _this$sceneViewer2 !== void 0 && _this$sceneViewer2.currentSceneData && typeof this.sceneViewer.pathfindingManager.recomputeWorldBoundingBoxes === 'function') {
26571
+ try {
26572
+ this.sceneViewer.pathfindingManager.recomputeWorldBoundingBoxes(this.sceneViewer.currentSceneData);
26573
+ console.log('📦 Triggered pathfinding manager bounding box recompute');
26574
+ } catch (error) {
26575
+ console.warn('⚠️ Error triggering pathfinding bounding box recompute:', error);
26576
+ }
26577
+ }
26578
+ }
26579
+
26218
26580
  /**
26219
26581
  * Update paths in the scene
26220
26582
  * @returns {boolean} True if paths were updated successfully, false otherwise
@@ -26268,7 +26630,7 @@ var CentralPlant = /*#__PURE__*/function () {
26268
26630
  }, {
26269
26631
  key: "syncSceneData",
26270
26632
  value: function syncSceneData() {
26271
- var _this = this;
26633
+ var _this2 = this;
26272
26634
  // Check if scene viewer and currentSceneData are available
26273
26635
  if (!this.sceneViewer || !this.sceneViewer.currentSceneData) {
26274
26636
  console.warn('⚠️ syncSceneData(): Scene viewer or current scene data not available');
@@ -26293,7 +26655,7 @@ var CentralPlant = /*#__PURE__*/function () {
26293
26655
  currentSceneData.scene.object.children.forEach(function (sceneDataChild, index) {
26294
26656
  // Find matching Three.js object
26295
26657
  var matchingThreeObject = null;
26296
- _this.sceneViewer.scene.traverse(function (threeObject) {
26658
+ _this2.sceneViewer.scene.traverse(function (threeObject) {
26297
26659
  var _sceneDataChild$userD, _threeObject$userData, _threeObject$userData2;
26298
26660
  // Check various UUID matching strategies
26299
26661
  if (threeObject.uuid === sceneDataChild.uuid || threeObject.uuid === ((_sceneDataChild$userD = sceneDataChild.userData) === null || _sceneDataChild$userD === void 0 ? void 0 : _sceneDataChild$userD.originalUuid) || ((_threeObject$userData = threeObject.userData) === null || _threeObject$userData === void 0 ? void 0 : _threeObject$userData.originalUuid) === sceneDataChild.uuid || ((_threeObject$userData2 = threeObject.userData) === null || _threeObject$userData2 === void 0 ? void 0 : _threeObject$userData2.hardcodedUuid) === sceneDataChild.uuid) {
@@ -26400,7 +26762,7 @@ var CentralPlant = /*#__PURE__*/function () {
26400
26762
  }, {
26401
26763
  key: "forceSyncSceneData",
26402
26764
  value: function forceSyncSceneData() {
26403
- var _this2 = this;
26765
+ var _this3 = this;
26404
26766
  // Check if scene viewer and sceneOperationsManager are available
26405
26767
  if (!this.sceneViewer || !this.sceneViewer.sceneOperationsManager) {
26406
26768
  console.warn('⚠️ forceSyncSceneData(): Scene viewer or scene operations manager not available');
@@ -26426,7 +26788,7 @@ var CentralPlant = /*#__PURE__*/function () {
26426
26788
  console.log("\uD83D\uDD04 Syncing object: ".concat(threeObject.uuid, " (").concat(threeObject.userData.componentType || 'unknown', ")"));
26427
26789
 
26428
26790
  // Use the existing updateSceneDataAfterTransform method
26429
- var success = _this2.sceneViewer.sceneOperationsManager.updateSceneDataAfterTransform(threeObject, currentSceneData);
26791
+ var success = _this3.sceneViewer.sceneOperationsManager.updateSceneDataAfterTransform(threeObject, currentSceneData);
26430
26792
  if (success) {
26431
26793
  syncedCount++;
26432
26794
  } else {
@@ -27,7 +27,7 @@ var PathManager = /*#__PURE__*/function () {
27
27
  }
28
28
 
29
29
  /**
30
- * A* pathfinding algorithm
30
+ * A* pathfinding algorithm with bend minimization
31
31
  * @private
32
32
  * @param {string} start - Start voxel key
33
33
  * @param {string} goal - Goal voxel key
@@ -86,7 +86,11 @@ var PathManager = /*#__PURE__*/function () {
86
86
  var _gScore$neighbor;
87
87
  var neighbor = _step2.value;
88
88
  if (occupied.has(neighbor)) continue;
89
- var tentativeG = gScore[current] + 1;
89
+
90
+ // Calculate cost with bend penalty
91
+ var tentativeG = this.calculateGScore(current, neighbor, cameFrom[current], gScore[current]);
92
+ // const tentativeG = gScore[current] + 1;
93
+
90
94
  if (tentativeG < ((_gScore$neighbor = gScore[neighbor]) !== null && _gScore$neighbor !== void 0 ? _gScore$neighbor : Infinity)) {
91
95
  cameFrom[neighbor] = current;
92
96
  gScore[neighbor] = tentativeG;
@@ -103,6 +107,59 @@ var PathManager = /*#__PURE__*/function () {
103
107
  return null;
104
108
  }
105
109
 
110
+ /**
111
+ * Calculate G score with bend penalty
112
+ * @private
113
+ * @param {string} current - Current voxel key
114
+ * @param {string} neighbor - Neighbor voxel key
115
+ * @param {string} parent - Parent voxel key (null if current is start)
116
+ * @param {number} currentG - Current G score
117
+ * @returns {number} G score with bend penalty
118
+ */
119
+ }, {
120
+ key: "calculateGScore",
121
+ value: function calculateGScore(current, neighbor, parent, currentG) {
122
+ var cost = currentG + 1; // Base movement cost
123
+
124
+ if (parent) {
125
+ // Check if this move creates a bend
126
+ var _parent$split$map = parent.split(',').map(Number),
127
+ _parent$split$map2 = _rollupPluginBabelHelpers.slicedToArray(_parent$split$map, 3),
128
+ px = _parent$split$map2[0],
129
+ py = _parent$split$map2[1],
130
+ pz = _parent$split$map2[2];
131
+ var _current$split$map = current.split(',').map(Number),
132
+ _current$split$map2 = _rollupPluginBabelHelpers.slicedToArray(_current$split$map, 3),
133
+ cx = _current$split$map2[0],
134
+ cy = _current$split$map2[1],
135
+ cz = _current$split$map2[2];
136
+ var _neighbor$split$map = neighbor.split(',').map(Number),
137
+ _neighbor$split$map2 = _rollupPluginBabelHelpers.slicedToArray(_neighbor$split$map, 3),
138
+ nx = _neighbor$split$map2[0],
139
+ ny = _neighbor$split$map2[1],
140
+ nz = _neighbor$split$map2[2];
141
+
142
+ // Direction from parent to current
143
+ var prevDir = {
144
+ x: cx - px,
145
+ y: cy - py,
146
+ z: cz - pz
147
+ };
148
+ // Direction from current to neighbor
149
+ var nextDir = {
150
+ x: nx - cx,
151
+ y: ny - cy,
152
+ z: nz - cz
153
+ };
154
+
155
+ // If directions are different, add bend penalty
156
+ if (prevDir.x !== nextDir.x || prevDir.y !== nextDir.y || prevDir.z !== nextDir.z) {
157
+ cost += 0.00001; // Bend penalty - adjust this value to control how much bends are avoided
158
+ }
159
+ }
160
+ return cost;
161
+ }
162
+
106
163
  /**
107
164
  * Find a path respecting virtual segments
108
165
  * @private