@kimap/indoor-positioning-sdk-vue2 5.8.2 → 5.8.4
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/package.json +1 -1
- package/src/core/KimapSDK.js +100 -25
- package/src/core/SceneCore.js +252 -7
package/package.json
CHANGED
package/src/core/KimapSDK.js
CHANGED
|
@@ -43,6 +43,7 @@ function KimapSDK(config) {
|
|
|
43
43
|
this.floorToolsConfig = config.floorTools || { enabled: false };
|
|
44
44
|
this.floorToolsElement = null;
|
|
45
45
|
this.currentFloorIndex = 1; // 1=第一个楼层, 0=ALL
|
|
46
|
+
this._currentShowingFloorIndex = 0; // 当前显示的楼层索引(用于判断动画方向)
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
/**
|
|
@@ -143,23 +144,31 @@ KimapSDK.prototype.handleClick = function(event) {
|
|
|
143
144
|
*/
|
|
144
145
|
KimapSDK.prototype.startRenderLoop = function() {
|
|
145
146
|
var self = this;
|
|
146
|
-
|
|
147
|
+
var lastTime = Date.now();
|
|
148
|
+
|
|
147
149
|
function animate() {
|
|
148
150
|
self.animationFrameId = requestAnimationFrame(animate);
|
|
149
|
-
|
|
151
|
+
|
|
152
|
+
var now = Date.now();
|
|
153
|
+
var deltaMs = now - lastTime;
|
|
154
|
+
lastTime = now;
|
|
155
|
+
|
|
156
|
+
// 驱动楼层切换动画 & 相机俯仰动画
|
|
157
|
+
self.core.tickAnimations(deltaMs);
|
|
158
|
+
|
|
150
159
|
if (self.core.controls) {
|
|
151
160
|
self.core.controls.update();
|
|
152
161
|
}
|
|
153
|
-
|
|
162
|
+
|
|
154
163
|
if (self.trackingModule) {
|
|
155
164
|
self.trackingModule.update();
|
|
156
165
|
}
|
|
157
|
-
|
|
166
|
+
|
|
158
167
|
self.updateAllDOMPositions();
|
|
159
|
-
|
|
168
|
+
|
|
160
169
|
self.core.renderer.render(self.core.scene, self.core.camera);
|
|
161
170
|
}
|
|
162
|
-
|
|
171
|
+
|
|
163
172
|
animate();
|
|
164
173
|
};
|
|
165
174
|
|
|
@@ -606,18 +615,70 @@ KimapSDK.prototype.changeFloor = function(floorIdOrIndex) {
|
|
|
606
615
|
KimapSDK.prototype.showSingleFloor = function(floorIndex) {
|
|
607
616
|
if (!this.floorGroups || this.floorGroups.length === 0) return;
|
|
608
617
|
var self = this;
|
|
618
|
+
|
|
619
|
+
var ANIM_DURATION = 400; // ms
|
|
620
|
+
var startTime = Date.now();
|
|
621
|
+
var currentFloorIndex = this._currentShowingFloorIndex !== undefined
|
|
622
|
+
? this._currentShowingFloorIndex : 0;
|
|
623
|
+
|
|
624
|
+
// 判断方向:目标层是从上方滑下(低→高)还是从下方滑上(高→低)
|
|
625
|
+
// 低楼层 → 高楼层(1→2):目标层从上方来
|
|
626
|
+
// 高楼层 → 低楼层(2→1):目标层从下方来
|
|
627
|
+
var targetFromAbove = floorIndex > currentFloorIndex;
|
|
628
|
+
|
|
629
|
+
// 计算目标 Y
|
|
630
|
+
function targetY(index) {
|
|
631
|
+
if (index === floorIndex) return 0;
|
|
632
|
+
var origY = self.core.floorOriginalY[index] || 0;
|
|
633
|
+
return -origY - 1;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// 滑入起始偏移:目标层从 ±(楼层高度+内容高度) 处开始
|
|
637
|
+
var slideOffset = 8; // 一个楼层的视觉高度约 8 单位
|
|
638
|
+
function slideStartY(index) {
|
|
639
|
+
return targetFromAbove ? slideOffset : -slideOffset;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
var steps = [];
|
|
609
643
|
this.floorGroups.forEach(function(group, index) {
|
|
610
644
|
if (index === floorIndex) {
|
|
611
|
-
//
|
|
645
|
+
// 目标层:先设 visible,再从滑入起始位开始动画到 y=0
|
|
612
646
|
group.visible = true;
|
|
613
|
-
group.position.y =
|
|
647
|
+
group.position.y = slideStartY(index);
|
|
648
|
+
steps.push({ group: group, fromY: slideStartY(index), toY: 0, toVisible: true });
|
|
614
649
|
} else {
|
|
615
|
-
//
|
|
616
|
-
|
|
617
|
-
var
|
|
618
|
-
|
|
650
|
+
// 离场层:动画到隐藏位置后设 visible=false
|
|
651
|
+
var fromY = group.position.y;
|
|
652
|
+
var toY = targetY(index);
|
|
653
|
+
steps.push({ group: group, fromY: fromY, toY: toY, toVisible: false });
|
|
619
654
|
}
|
|
620
655
|
});
|
|
656
|
+
|
|
657
|
+
// 合并/覆盖已有动画:先停止当前帧再以新起止值启动
|
|
658
|
+
if (self.core.floorAnim && self.core.floorAnim.running) {
|
|
659
|
+
var cur = self.core.floorAnim;
|
|
660
|
+
var t = Math.min((Date.now() - cur.startTime) / cur.duration, 1);
|
|
661
|
+
var et = 1 - Math.pow(1 - t, 3); // easeOutCubic reverse
|
|
662
|
+
// 把每个 group 瞬移到当前动画帧的位置
|
|
663
|
+
for (var i = 0; i < cur.steps.length; i++) {
|
|
664
|
+
var cs = cur.steps[i];
|
|
665
|
+
cs.group.position.y = cs.fromY + (cs.toY - cs.fromY) * et;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
self.core.floorAnim = {
|
|
670
|
+
running: true,
|
|
671
|
+
startTime: startTime,
|
|
672
|
+
duration: ANIM_DURATION,
|
|
673
|
+
steps: steps
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
this._currentShowingFloorIndex = floorIndex;
|
|
677
|
+
// 相机对准当前楼层(带相机俯仰动画)
|
|
678
|
+
if (self.core.cameraPitchState === 1) {
|
|
679
|
+
self.core.animateCameraToFloorView(600);
|
|
680
|
+
}
|
|
681
|
+
self.core.adjustCameraToFloor(floorIndex);
|
|
621
682
|
};
|
|
622
683
|
|
|
623
684
|
/**
|
|
@@ -627,12 +688,21 @@ KimapSDK.prototype.showSingleFloor = function(floorIndex) {
|
|
|
627
688
|
KimapSDK.prototype.showAllFloors = function() {
|
|
628
689
|
if (!this.floorGroups || this.floorGroups.length === 0) return;
|
|
629
690
|
var self = this;
|
|
691
|
+
|
|
692
|
+
// 楼层组瞬间恢复到 ALL 位置(不需要动画,因为相机视角切换是主角)
|
|
630
693
|
this.floorGroups.forEach(function(group, index) {
|
|
631
694
|
group.visible = true;
|
|
632
|
-
// 恢复原始世界 Y 偏移(含间隙)
|
|
633
695
|
var originalY = self.core.floorOriginalY[index] || 0;
|
|
634
696
|
group.position.y = originalY;
|
|
635
697
|
});
|
|
698
|
+
|
|
699
|
+
// 先让相机以所有楼层合并包围盒调整一次(决定 toPos/toTarget)
|
|
700
|
+
self.core._adjustCameraToAllFloors();
|
|
701
|
+
|
|
702
|
+
// 再启动相机俯仰动画(从当前单楼层视角 → -60° 外景视角)
|
|
703
|
+
self.core.animateCameraToAllView(800);
|
|
704
|
+
|
|
705
|
+
this._currentShowingFloorIndex = -1; // ALL 状态
|
|
636
706
|
};
|
|
637
707
|
|
|
638
708
|
/**
|
|
@@ -1505,13 +1575,13 @@ KimapSDK.prototype._renderFloorSelector = function() {
|
|
|
1505
1575
|
].join(';');
|
|
1506
1576
|
|
|
1507
1577
|
// ALL 按钮
|
|
1508
|
-
var allBtn = this._createFloorButton('ALL', 0
|
|
1578
|
+
var allBtn = this._createFloorButton('ALL', 0);
|
|
1509
1579
|
selector.appendChild(allBtn);
|
|
1510
1580
|
|
|
1511
1581
|
// 各楼层按钮
|
|
1512
1582
|
var self2 = this;
|
|
1513
1583
|
floorNames.forEach(function(name, index) {
|
|
1514
|
-
var btn = self2._createFloorButton(name, index + 1
|
|
1584
|
+
var btn = self2._createFloorButton(name, index + 1);
|
|
1515
1585
|
selector.appendChild(btn);
|
|
1516
1586
|
});
|
|
1517
1587
|
|
|
@@ -1523,7 +1593,7 @@ KimapSDK.prototype._renderFloorSelector = function() {
|
|
|
1523
1593
|
* 创建楼层按钮
|
|
1524
1594
|
* @private
|
|
1525
1595
|
*/
|
|
1526
|
-
KimapSDK.prototype._createFloorButton = function(name, index
|
|
1596
|
+
KimapSDK.prototype._createFloorButton = function(name, index) {
|
|
1527
1597
|
var self = this;
|
|
1528
1598
|
var btn = document.createElement('button');
|
|
1529
1599
|
btn.textContent = name;
|
|
@@ -1540,27 +1610,32 @@ KimapSDK.prototype._createFloorButton = function(name, index, isActive) {
|
|
|
1540
1610
|
'min-width: 50px'
|
|
1541
1611
|
].join(';');
|
|
1542
1612
|
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1613
|
+
function applyActiveStyle(active) {
|
|
1614
|
+
if (active) {
|
|
1615
|
+
btn.style.backgroundColor = '#1890ff';
|
|
1616
|
+
btn.style.color = '#fff';
|
|
1617
|
+
btn.style.boxShadow = 'none';
|
|
1618
|
+
} else {
|
|
1619
|
+
btn.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
|
|
1620
|
+
btn.style.color = '#333';
|
|
1621
|
+
btn.style.boxShadow = '0 1px 4px rgba(0,0,0,0.1)';
|
|
1622
|
+
}
|
|
1550
1623
|
}
|
|
1551
1624
|
|
|
1625
|
+
applyActiveStyle(self.currentFloorIndex === index);
|
|
1626
|
+
|
|
1552
1627
|
btn.addEventListener('click', function() {
|
|
1553
1628
|
self._onFloorButtonClick(index);
|
|
1554
1629
|
});
|
|
1555
1630
|
|
|
1556
1631
|
btn.addEventListener('mouseenter', function() {
|
|
1557
|
-
if (
|
|
1632
|
+
if (self.currentFloorIndex !== index) {
|
|
1558
1633
|
btn.style.backgroundColor = '#e6f7ff';
|
|
1559
1634
|
}
|
|
1560
1635
|
});
|
|
1561
1636
|
|
|
1562
1637
|
btn.addEventListener('mouseleave', function() {
|
|
1563
|
-
if (
|
|
1638
|
+
if (self.currentFloorIndex !== index) {
|
|
1564
1639
|
btn.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
|
|
1565
1640
|
}
|
|
1566
1641
|
});
|
package/src/core/SceneCore.js
CHANGED
|
@@ -42,6 +42,7 @@ function SceneCore(config) {
|
|
|
42
42
|
this.renderer = null;
|
|
43
43
|
this.controls = null;
|
|
44
44
|
this.mapModel = null;
|
|
45
|
+
this.lookAtTarget = null; // 当前相机注视点(ALL 退场恢复用)
|
|
45
46
|
|
|
46
47
|
// 需要先加载 THREE 才能使用 THREE.Vector3
|
|
47
48
|
var mapWidth = config.maxX || 100;
|
|
@@ -67,6 +68,13 @@ function SceneCore(config) {
|
|
|
67
68
|
this.floorHeight = 3; // 米
|
|
68
69
|
this.isMultiFloor = Array.isArray(config.objUrl);
|
|
69
70
|
this.floorOriginalY = []; // 每个楼层的原始世界Y偏移(含间隙)
|
|
71
|
+
// 楼层切换动画状态
|
|
72
|
+
this.floorAnim = null; // { running: bool, startTime: ms, duration: ms, queue: [] }
|
|
73
|
+
// 相机俯仰动画(ALL 模式)
|
|
74
|
+
this.cameraPitchAnim = null; // { running: bool, startTime: ms, duration: ms }
|
|
75
|
+
this.cameraPitchState = 0; // 0=正常视角, 1=ALL外景俯视
|
|
76
|
+
this.cameraPrePitchPos = null; // 俯仰动画前的相机位置
|
|
77
|
+
this.cameraPrePitchTarget = null; // 俯仰动画前的 controls.target
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
/**
|
|
@@ -282,8 +290,9 @@ SceneCore.prototype._loadMultiFloorMaps = function(OBJLoader, MTLLoader, objUrls
|
|
|
282
290
|
return self._loadSingleMapWithFloorIndex(OBJLoader, MTLLoader, url, floorIndex++);
|
|
283
291
|
});
|
|
284
292
|
}, Promise.resolve()).then(function() {
|
|
285
|
-
|
|
286
|
-
|
|
293
|
+
// 多楼层:相机默认以第一个楼层为准(之后切换 ALL 时再重新调整)
|
|
294
|
+
if (self.floorGroups.length > 0) {
|
|
295
|
+
self.adjustCameraToFloor(0);
|
|
287
296
|
}
|
|
288
297
|
});
|
|
289
298
|
};
|
|
@@ -421,7 +430,8 @@ SceneCore.prototype._processLoadedModelToGroup = function(object, materials, col
|
|
|
421
430
|
var box = new THREE.Box3().setFromObject(object);
|
|
422
431
|
var boxHeight = box.max.y - box.min.y;
|
|
423
432
|
|
|
424
|
-
//
|
|
433
|
+
// 逐层累积高度:ALL 模式时,每层 = 前几层高度 + floorHeight 间隙
|
|
434
|
+
// 这样 ALL 有间隙,单层模式时把 group.position.y = -floorY 就贴地了
|
|
425
435
|
var floorY = 0;
|
|
426
436
|
for (var i = 0; i < floorIndex; i++) {
|
|
427
437
|
var prevGroup = self.floorGroups[i];
|
|
@@ -431,14 +441,17 @@ SceneCore.prototype._processLoadedModelToGroup = function(object, materials, col
|
|
|
431
441
|
}
|
|
432
442
|
}
|
|
433
443
|
|
|
444
|
+
// 加固定间隙高度(除第一个楼层外)
|
|
445
|
+
var totalFloorY = floorY + floorIndex * self.floorHeight;
|
|
446
|
+
|
|
434
447
|
// 所有楼层的 object 底部(box.min.y)对齐到本地 Y=0,统一基准
|
|
435
448
|
object.position.set(-box.min.x, -box.min.y, -box.min.z);
|
|
436
449
|
|
|
437
|
-
// ALL 模式时 floorGroup.position.y
|
|
438
|
-
floorGroup.position.y =
|
|
450
|
+
// ALL 模式时 floorGroup.position.y 持有含间隙的累积偏移
|
|
451
|
+
floorGroup.position.y = totalFloorY;
|
|
439
452
|
|
|
440
|
-
//
|
|
441
|
-
self.floorOriginalY[floorIndex] =
|
|
453
|
+
// 记录含间隙的总世界 Y 偏移(单层模式 showSingleFloor 用 group.y = -originalY 拽回 Y=0)
|
|
454
|
+
self.floorOriginalY[floorIndex] = totalFloorY;
|
|
442
455
|
|
|
443
456
|
// 为该楼层创建独立的文字组
|
|
444
457
|
var textGroup = new THREE.Group();
|
|
@@ -495,6 +508,48 @@ SceneCore.prototype._adjustCameraToAllFloors = function() {
|
|
|
495
508
|
}
|
|
496
509
|
};
|
|
497
510
|
|
|
511
|
+
/**
|
|
512
|
+
* 相机对准指定楼层(多楼层切换/ALL 时分别调用)
|
|
513
|
+
* @param {number} floorIndex - 楼层索引
|
|
514
|
+
* @public
|
|
515
|
+
*/
|
|
516
|
+
SceneCore.prototype.adjustCameraToFloor = function(floorIndex) {
|
|
517
|
+
try {
|
|
518
|
+
var floorGroup = this.floorGroups[floorIndex];
|
|
519
|
+
if (!floorGroup) return;
|
|
520
|
+
|
|
521
|
+
var meshes = [];
|
|
522
|
+
floorGroup.traverse(function(child) {
|
|
523
|
+
if (child.isMesh) meshes.push(child);
|
|
524
|
+
});
|
|
525
|
+
if (meshes.length === 0) return;
|
|
526
|
+
|
|
527
|
+
var box = new THREE.Box3();
|
|
528
|
+
meshes.forEach(function(mesh) { box.expandByObject(mesh); });
|
|
529
|
+
|
|
530
|
+
var center = new THREE.Vector3();
|
|
531
|
+
var size = new THREE.Vector3();
|
|
532
|
+
box.getCenter(center);
|
|
533
|
+
box.getSize(size);
|
|
534
|
+
var maxSize = Math.max(size.x, size.z);
|
|
535
|
+
var distance = maxSize * 2;
|
|
536
|
+
|
|
537
|
+
this.camera.position.set(center.x, center.y + distance * 1.5, center.z + distance);
|
|
538
|
+
var lookAtTgt = new THREE.Vector3(center.x, center.y, center.z);
|
|
539
|
+
this.camera.lookAt(lookAtTgt);
|
|
540
|
+
if (this.controls) {
|
|
541
|
+
this.controls.target.copy(lookAtTgt);
|
|
542
|
+
this.controls.update();
|
|
543
|
+
}
|
|
544
|
+
// 记录当前 lookAt 目标(ALL 退场恢复用)
|
|
545
|
+
this.camera.lookAtTarget = lookAtTgt;
|
|
546
|
+
this.coordinateSystem.maxX = size.x;
|
|
547
|
+
this.coordinateSystem.maxY = size.z;
|
|
548
|
+
} catch (e) {
|
|
549
|
+
console.warn('调整相机到楼层 ' + floorIndex + ' 失败:', e);
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
|
|
498
553
|
/**
|
|
499
554
|
* 加载 Kimap 加密文件(内部方法)
|
|
500
555
|
* @private
|
|
@@ -1158,4 +1213,194 @@ SceneCore.prototype.destroy = function() {
|
|
|
1158
1213
|
}
|
|
1159
1214
|
};
|
|
1160
1215
|
|
|
1216
|
+
// =============================================================================
|
|
1217
|
+
// 动画工具:缓动函数
|
|
1218
|
+
// =============================================================================
|
|
1219
|
+
function easeOutCubic(t) { return 1 - Math.pow(1 - t, 3); }
|
|
1220
|
+
function easeInOutCubic(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; }
|
|
1221
|
+
|
|
1222
|
+
/**
|
|
1223
|
+
* 每帧调用:驱动楼层切换动画 & 相机俯仰动画
|
|
1224
|
+
* 由 KimapSDK.renderLoop 内部调用
|
|
1225
|
+
* @param {number} deltaMs - 距上一帧的毫秒数
|
|
1226
|
+
*/
|
|
1227
|
+
SceneCore.prototype.tickAnimations = function(deltaMs) {
|
|
1228
|
+
// ---------- 楼层切换动画 ----------
|
|
1229
|
+
if (this.floorAnim && this.floorAnim.running) {
|
|
1230
|
+
var elapsed = Date.now() - this.floorAnim.startTime;
|
|
1231
|
+
var t = Math.min(elapsed / this.floorAnim.duration, 1);
|
|
1232
|
+
var et = easeOutCubic(t);
|
|
1233
|
+
|
|
1234
|
+
var steps = this.floorAnim.steps;
|
|
1235
|
+
for (var i = 0; i < steps.length; i++) {
|
|
1236
|
+
var step = steps[i];
|
|
1237
|
+
var fromY = step.fromY;
|
|
1238
|
+
var toY = step.toY;
|
|
1239
|
+
step.group.position.y = fromY + (toY - fromY) * et;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
if (t >= 1) {
|
|
1243
|
+
// 动画结束:立即到达目标,更新最终状态
|
|
1244
|
+
for (var j = 0; j < steps.length; j++) {
|
|
1245
|
+
var s = steps[j];
|
|
1246
|
+
s.group.position.y = s.toY;
|
|
1247
|
+
if (s.toVisible === false) {
|
|
1248
|
+
s.group.visible = false;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
this.floorAnim = null;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// ---------- 相机俯仰动画(ALL 外景) ----------
|
|
1256
|
+
if (this.cameraPitchAnim && this.cameraPitchAnim.running) {
|
|
1257
|
+
var elapsed2 = Date.now() - this.cameraPitchAnim.startTime;
|
|
1258
|
+
var t2 = Math.min(elapsed2 / this.cameraPitchAnim.duration, 1);
|
|
1259
|
+
var et2 = easeInOutCubic(t2);
|
|
1260
|
+
|
|
1261
|
+
var fromPos = this.cameraPitchAnim.fromPos;
|
|
1262
|
+
var toPos = this.cameraPitchAnim.toPos;
|
|
1263
|
+
var fromTarget = this.cameraPitchAnim.fromTarget;
|
|
1264
|
+
var toTarget = this.cameraPitchAnim.toTarget;
|
|
1265
|
+
|
|
1266
|
+
this.camera.position.lerpVectors(fromPos, toPos, et2);
|
|
1267
|
+
this.camera.lookAt(
|
|
1268
|
+
fromTarget.x + (toTarget.x - fromTarget.x) * et2,
|
|
1269
|
+
fromTarget.y + (toTarget.y - fromTarget.y) * et2,
|
|
1270
|
+
fromTarget.z + (toTarget.z - fromTarget.z) * et2
|
|
1271
|
+
);
|
|
1272
|
+
if (this.controls) {
|
|
1273
|
+
this.controls.target.copy(this.camera);
|
|
1274
|
+
this.controls.target.y = fromTarget.y + (toTarget.y - fromTarget.y) * et2;
|
|
1275
|
+
this.controls.update();
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
if (t2 >= 1) {
|
|
1279
|
+
this.camera.position.copy(toPos);
|
|
1280
|
+
this.camera.lookAt(toTarget);
|
|
1281
|
+
if (this.controls) {
|
|
1282
|
+
this.controls.target.copy(toTarget);
|
|
1283
|
+
this.controls.update();
|
|
1284
|
+
}
|
|
1285
|
+
this.cameraPitchAnim = null;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
};
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* 计算整栋建筑的包围盒中心(ALL 相机动画用)
|
|
1292
|
+
* @returns {{center: THREE.Vector3, maxSize: number}}
|
|
1293
|
+
*/
|
|
1294
|
+
SceneCore.prototype._getBuildingBounds = function() {
|
|
1295
|
+
var allMeshes = [];
|
|
1296
|
+
this.floorGroups.forEach(function(group) {
|
|
1297
|
+
group.traverse(function(child) {
|
|
1298
|
+
if (child.isMesh) allMeshes.push(child);
|
|
1299
|
+
});
|
|
1300
|
+
});
|
|
1301
|
+
if (allMeshes.length === 0) return null;
|
|
1302
|
+
|
|
1303
|
+
var box = new THREE.Box3();
|
|
1304
|
+
allMeshes.forEach(function(mesh) { box.expandByObject(mesh); });
|
|
1305
|
+
var center = new THREE.Vector3();
|
|
1306
|
+
var size = new THREE.Vector3();
|
|
1307
|
+
box.getCenter(center);
|
|
1308
|
+
box.getSize(size);
|
|
1309
|
+
return { center: center, maxSize: Math.max(size.x, size.z) };
|
|
1310
|
+
};
|
|
1311
|
+
|
|
1312
|
+
/**
|
|
1313
|
+
* ALL 入口:相机降至低俯角(-60°),模拟建筑外观视角
|
|
1314
|
+
* @param {number} duration - 动画时长(ms)
|
|
1315
|
+
*/
|
|
1316
|
+
SceneCore.prototype.animateCameraToAllView = function(duration) {
|
|
1317
|
+
duration = duration || 800;
|
|
1318
|
+
var self = this;
|
|
1319
|
+
|
|
1320
|
+
// 已有动画在跑 → 停止,先瞬移到当前帧再启动新动画
|
|
1321
|
+
if (this.cameraPitchAnim && this.cameraPitchAnim.running) {
|
|
1322
|
+
var cur = this.cameraPitchAnim;
|
|
1323
|
+
var tCur = Math.min((Date.now() - cur.startTime) / cur.duration, 1);
|
|
1324
|
+
var etCur = easeInOutCubic(tCur);
|
|
1325
|
+
this.camera.position.lerpVectors(cur.fromPos, cur.toPos, etCur);
|
|
1326
|
+
this.camera.lookAt(
|
|
1327
|
+
cur.fromTarget.x + (cur.toTarget.x - cur.fromTarget.x) * etCur,
|
|
1328
|
+
cur.fromTarget.y + (cur.toTarget.y - cur.fromTarget.y) * etCur,
|
|
1329
|
+
cur.fromTarget.z + (cur.toTarget.z - cur.fromTarget.z) * etCur
|
|
1330
|
+
);
|
|
1331
|
+
if (this.controls) this.controls.update();
|
|
1332
|
+
this.cameraPitchAnim = null;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
var bounds = this._getBuildingBounds();
|
|
1336
|
+
if (!bounds) return;
|
|
1337
|
+
|
|
1338
|
+
var center = bounds.center;
|
|
1339
|
+
var maxSize = bounds.maxSize;
|
|
1340
|
+
|
|
1341
|
+
// 保存退场时需恢复的位置(单楼层相机视角)
|
|
1342
|
+
this.cameraPrePitchPos = this.camera.position.clone();
|
|
1343
|
+
this.cameraPrePitchTarget = this.controls ? this.controls.target.clone() : center.clone();
|
|
1344
|
+
|
|
1345
|
+
// 低俯角 -60°:camera.y - center.y = distance × sin(-60°) = -distance × 0.866
|
|
1346
|
+
// camera.z - center.z = distance × cos(-60°) = distance × 0.5
|
|
1347
|
+
var dist = maxSize * 2.5;
|
|
1348
|
+
var fromPos = this.camera.position.clone();
|
|
1349
|
+
var fromTarget = this.controls ? this.controls.target.clone() : center.clone();
|
|
1350
|
+
var toPos = new THREE.Vector3(center.x, center.y - dist * 0.866, center.z + dist * 0.5);
|
|
1351
|
+
var toTarget = new THREE.Vector3(center.x, center.y, center.z);
|
|
1352
|
+
|
|
1353
|
+
this.cameraPitchAnim = {
|
|
1354
|
+
running: true,
|
|
1355
|
+
startTime: Date.now(),
|
|
1356
|
+
duration: duration,
|
|
1357
|
+
fromPos: fromPos,
|
|
1358
|
+
toPos: toPos,
|
|
1359
|
+
fromTarget: fromTarget,
|
|
1360
|
+
toTarget: toTarget
|
|
1361
|
+
};
|
|
1362
|
+
this.cameraPitchState = 1;
|
|
1363
|
+
};
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* ALL 出口:相机从外景视角恢复到单楼层视角
|
|
1367
|
+
* @param {number} duration - 动画时长(ms)
|
|
1368
|
+
*/
|
|
1369
|
+
SceneCore.prototype.animateCameraToFloorView = function(duration) {
|
|
1370
|
+
duration = duration || 800;
|
|
1371
|
+
var self = this;
|
|
1372
|
+
|
|
1373
|
+
// 先停止当前相机动画并瞬移到当前帧
|
|
1374
|
+
if (this.cameraPitchAnim && this.cameraPitchAnim.running) {
|
|
1375
|
+
var cur = this.cameraPitchAnim;
|
|
1376
|
+
var tCur = Math.min((Date.now() - cur.startTime) / cur.duration, 1);
|
|
1377
|
+
var etCur = easeInOutCubic(tCur);
|
|
1378
|
+
this.camera.position.lerpVectors(cur.fromPos, cur.toPos, etCur);
|
|
1379
|
+
this.camera.lookAt(
|
|
1380
|
+
cur.fromTarget.x + (cur.toTarget.x - cur.fromTarget.x) * etCur,
|
|
1381
|
+
cur.fromTarget.y + (cur.toTarget.y - cur.fromTarget.y) * etCur,
|
|
1382
|
+
cur.fromTarget.z + (cur.toTarget.z - cur.fromTarget.z) * etCur
|
|
1383
|
+
);
|
|
1384
|
+
if (this.controls) this.controls.update();
|
|
1385
|
+
this.cameraPitchAnim = null;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
// 退场恢复点
|
|
1389
|
+
var toPos = this.cameraPrePitchPos || this.camera.position.clone();
|
|
1390
|
+
var toTarget = this.cameraPrePitchTarget || this.camera.position.clone();
|
|
1391
|
+
|
|
1392
|
+
this.cameraPitchAnim = {
|
|
1393
|
+
running: true,
|
|
1394
|
+
startTime: Date.now(),
|
|
1395
|
+
duration: duration,
|
|
1396
|
+
fromPos: this.camera.position.clone(),
|
|
1397
|
+
toPos: toPos,
|
|
1398
|
+
fromTarget: this.camera.lookAtTarget ? this.camera.lookAtTarget.clone() : toTarget.clone(),
|
|
1399
|
+
toTarget: toTarget
|
|
1400
|
+
};
|
|
1401
|
+
this.cameraPitchState = 0;
|
|
1402
|
+
this.cameraPrePitchPos = null;
|
|
1403
|
+
this.cameraPrePitchTarget = null;
|
|
1404
|
+
};
|
|
1405
|
+
|
|
1161
1406
|
module.exports = SceneCore;
|