@kimap/indoor-positioning-sdk-vue2 5.2.6 → 5.2.7

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/core/KimapSDK.js +524 -54
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kimap/indoor-positioning-sdk-vue2",
3
- "version": "5.2.6",
3
+ "version": "5.2.7",
4
4
  "description": "Vue2自包含室内定位SDK - 完全兼容Webpack3+Babel6 | Vue2 Self-Contained Indoor Positioning SDK",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -35,6 +35,10 @@ function KimapSDK(config) {
35
35
  this.currentFloorId = null;
36
36
  this.currentFloor = null;
37
37
 
38
+ // 楼层工具配置
39
+ this.floorToolsConfig = config.floorTools || { enabled: false };
40
+ this.floorToolsElement = null;
41
+
38
42
  // 多楼层相关配置
39
43
  this.floorNames = config.floorNames || []; // 楼层名称列表
40
44
  this.currentFloorIndex = 0; // 当前楼层索引(0=ALL)
@@ -84,11 +88,17 @@ KimapSDK.prototype.init = function() {
84
88
  self.floorGroups = self.core.floorGroups;
85
89
  }
86
90
 
87
- // 渲染楼层选择器
88
- if (self.isMultiFloor || self.floorNames.length > 0) {
91
+ // 渲染楼层选择器(使用 floorTools 配置)
92
+ if (this.floorToolsConfig.enabled !== false && (this.isMultiFloor || this.floorNames.length > 0)) {
89
93
  self._renderFloorSelector();
90
94
  }
91
95
 
96
+ // 创建科技感地面背景
97
+ self._createTechGround();
98
+
99
+ // 启动道路动画循环
100
+ self._startRoadAnimationLoop();
101
+
92
102
  // 加载背景建筑
93
103
  if (self.backgroundBuildingsConfig.enabled !== false) {
94
104
  self._loadBackgroundBuildings();
@@ -169,31 +179,57 @@ KimapSDK.prototype.startRenderLoop = function() {
169
179
  */
170
180
  KimapSDK.prototype._extractWallsFromModel = function() {
171
181
  var self = this;
172
-
173
- if (!self.core || !self.core.mapModel) {
174
- console.warn('KimapSDK: 3D模型未加载,无法提取墙面数据');
182
+
183
+ if (!self.core) {
184
+ console.warn('KimapSDK: SceneCore未初始化,无法提取墙面数据');
175
185
  return;
176
186
  }
177
-
187
+
188
+ // 优先从 mapModel 提取(单楼层模式)
189
+ if (self.core.mapModel) {
190
+ self._extractWallsFromGroup(self.core.mapModel);
191
+ return;
192
+ }
193
+
194
+ // 从 floorGroups 提取(多楼层模式)
195
+ if (self.floorGroups && self.floorGroups.length > 0) {
196
+ self.floorGroups.forEach(function(group) {
197
+ self._extractWallsFromGroup(group);
198
+ });
199
+ return;
200
+ }
201
+
202
+ console.warn('KimapSDK: 3D模型未加载,无法提取墙面数据');
203
+ };
204
+
205
+ /**
206
+ * 从Group中提取墙面数据
207
+ * @private
208
+ */
209
+ KimapSDK.prototype._extractWallsFromGroup = function(group) {
210
+ var THREE = window.THREE || global.THREE;
211
+ if (!THREE) return;
212
+
178
213
  var walls = [];
179
214
  var floorGroups = {};
180
-
215
+ var self = this;
216
+
181
217
  // 遍历场景中的所有对象,查找墙面
182
- self.core.mapModel.traverse(function(child) {
218
+ group.traverse(function(child) {
183
219
  if (child.name && child.name.startsWith('Wall_')) {
184
220
  var box = new THREE.Box3().setFromObject(child);
185
221
  var min = box.min;
186
222
  var max = box.max;
187
-
223
+
188
224
  var centerX = (min.x + max.x) / 2;
189
225
  var centerZ = (min.z + max.z) / 2;
190
226
  var width = max.x - min.x;
191
227
  var depth = max.z - min.z;
192
228
  var height = max.y - min.y;
193
-
229
+
194
230
  var isHorizontal = width > depth;
195
231
  var thickness = isHorizontal ? depth : width;
196
-
232
+
197
233
  var points;
198
234
  if (isHorizontal) {
199
235
  points = [
@@ -206,9 +242,9 @@ KimapSDK.prototype._extractWallsFromModel = function() {
206
242
  { x: centerX * 100, y: max.z * 100 }
207
243
  ];
208
244
  }
209
-
245
+
210
246
  var floorLevel = Math.floor(min.y / 3);
211
-
247
+
212
248
  var wallData = {
213
249
  type: 'wall',
214
250
  id: child.name,
@@ -219,23 +255,23 @@ KimapSDK.prototype._extractWallsFromModel = function() {
219
255
  height: height * 1000,
220
256
  floorLevel: floorLevel
221
257
  };
222
-
258
+
223
259
  walls.push(wallData);
224
-
260
+
225
261
  if (!floorGroups[floorLevel]) {
226
262
  floorGroups[floorLevel] = [];
227
263
  }
228
264
  floorGroups[floorLevel].push(wallData);
229
265
  }
230
266
  });
231
-
267
+
232
268
  console.log('✅ 从3D模型中提取墙面数据:', walls.length, '个墙面');
233
-
269
+
234
270
  var floors = [];
235
271
  Object.keys(floorGroups).sort().forEach(function(level) {
236
272
  var floorId = 'floor_' + level;
237
273
  var floorName = (parseInt(level) + 1) + '楼';
238
-
274
+
239
275
  floors.push({
240
276
  id: floorId,
241
277
  name: floorName,
@@ -253,10 +289,12 @@ KimapSDK.prototype._extractWallsFromModel = function() {
253
289
  ]
254
290
  });
255
291
  });
256
-
257
- if (floors.length > 0) {
258
- self.setFloors(floors);
292
+
293
+ if (floors.length > 0 && this.floors.length === 0) {
294
+ this.setFloors(floors);
259
295
  console.log('✅ 已自动设置楼层数据(从3D模型提取)');
296
+ } else if (floors.length > 0) {
297
+ console.log('✅ 已提取墙面数据(楼层已设置)');
260
298
  } else {
261
299
  console.warn('⚠️ 未从3D模型中提取到墙面数据');
262
300
  }
@@ -556,6 +594,11 @@ KimapSDK.prototype._renderFloorSelector = function() {
556
594
  existingSelector.remove();
557
595
  }
558
596
 
597
+ // 如果楼层工具未启用,不渲染
598
+ if (this.floorToolsConfig.enabled === false) {
599
+ return;
600
+ }
601
+
559
602
  // 获取楼层名称列表
560
603
  var floorNames = this.floorNames;
561
604
  if (!floorNames || floorNames.length === 0) {
@@ -569,15 +612,33 @@ KimapSDK.prototype._renderFloorSelector = function() {
569
612
  // 创建选择器容器
570
613
  var selector = document.createElement('div');
571
614
  selector.id = 'kimap-floor-selector';
615
+ this.floorToolsElement = selector;
616
+
617
+ // 根据配置设置位置
618
+ var position = this.floorToolsConfig.position || 'left-bottom';
619
+ var offsetX = this.floorToolsConfig.offsetX || 20;
620
+ var offsetY = this.floorToolsConfig.offsetY || 20;
621
+
622
+ var positionStyles = {
623
+ 'left-top': { top: offsetY + 'px', left: offsetX + 'px', bottom: 'auto', right: 'auto' },
624
+ 'left-bottom': { bottom: offsetY + 'px', left: offsetX + 'px', top: 'auto', right: 'auto' },
625
+ 'right-top': { top: offsetY + 'px', right: offsetX + 'px', bottom: 'auto', left: 'auto' },
626
+ 'right-bottom': { bottom: offsetY + 'px', right: offsetX + 'px', top: 'auto', left: 'auto' }
627
+ };
628
+
629
+ var posStyle = positionStyles[position] || positionStyles['left-bottom'];
630
+
572
631
  selector.style.cssText = [
573
632
  'position: absolute',
574
- 'bottom: 20px',
575
- 'left: 20px',
576
633
  'display: flex',
577
634
  'flex-direction: column',
578
635
  'gap: 4px',
579
636
  'z-index: 1000',
580
- 'font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
637
+ 'font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
638
+ 'top: ' + posStyle.top,
639
+ 'left: ' + posStyle.left,
640
+ 'bottom: ' + posStyle.bottom,
641
+ 'right: ' + posStyle.right
581
642
  ].join(';');
582
643
 
583
644
  // 创建ALL按钮
@@ -687,16 +748,19 @@ KimapSDK.prototype._onFloorButtonClick = function(index) {
687
748
  KimapSDK.prototype._showAllFloors = function() {
688
749
  var self = this;
689
750
 
690
- // 显示所有楼层组
751
+ // 淡出当前楼层(如果有)
691
752
  if (this.floorGroups && this.floorGroups.length > 0) {
692
753
  this.floorGroups.forEach(function(group) {
693
754
  group.visible = true;
755
+ // 淡入楼层
756
+ self._fadeInGroup(group, 400);
694
757
  });
695
758
  }
696
759
 
697
- // 显示背景建筑
760
+ // 淡入背景建筑
698
761
  if (this.backgroundGroup) {
699
762
  this.backgroundGroup.visible = true;
763
+ this._fadeInBackgroundBuildings(600);
700
764
  }
701
765
 
702
766
  // 隐藏家具(ALL模式不显示家具)
@@ -704,10 +768,57 @@ KimapSDK.prototype._showAllFloors = function() {
704
768
  this.furnitureGroup.visible = false;
705
769
  }
706
770
 
707
- // 调整相机位置到能看到所有楼层
771
+ // 调整相机位置到能看到所有楼层(带动画)
708
772
  this._zoomToShowAllFloors();
709
773
 
710
- console.log('KimapSDK: 已切换到ALL模式,显示所有楼层和背景建筑');
774
+ console.log('KimapSDK: 已切换到ALL模式');
775
+ };
776
+
777
+ /**
778
+ * 淡入Group
779
+ * @private
780
+ */
781
+ KimapSDK.prototype._fadeInGroup = function(group, duration) {
782
+ var self = this;
783
+ var startTime = performance.now();
784
+
785
+ // 记录初始透明度
786
+ group.traverse(function(child) {
787
+ if (child.material) {
788
+ if (Array.isArray(child.material)) {
789
+ child.material.forEach(function(mat) {
790
+ if (mat.transparent !== undefined) mat.transparent = true;
791
+ });
792
+ } else if (child.material.transparent !== undefined) {
793
+ child.material.transparent = true;
794
+ }
795
+ }
796
+ });
797
+
798
+ function fadeAnimate() {
799
+ var elapsed = performance.now() - startTime;
800
+ var progress = Math.min(elapsed / duration, 1);
801
+
802
+ group.traverse(function(child) {
803
+ if (child.material) {
804
+ if (Array.isArray(child.material)) {
805
+ child.material.forEach(function(mat) {
806
+ if (mat.opacity !== undefined) {
807
+ mat.opacity = progress;
808
+ }
809
+ });
810
+ } else if (child.material.opacity !== undefined) {
811
+ child.material.opacity = progress;
812
+ }
813
+ }
814
+ });
815
+
816
+ if (progress < 1) {
817
+ requestAnimationFrame(fadeAnimate);
818
+ }
819
+ }
820
+
821
+ fadeAnimate();
711
822
  };
712
823
 
713
824
  /**
@@ -717,10 +828,15 @@ KimapSDK.prototype._showAllFloors = function() {
717
828
  KimapSDK.prototype._showSingleFloor = function(floorIndex) {
718
829
  var self = this;
719
830
 
720
- // 显示/隐藏楼层组
831
+ // 淡出所有楼层,然后只显示目标楼层
721
832
  if (this.floorGroups && this.floorGroups.length > 0) {
722
833
  this.floorGroups.forEach(function(group, index) {
723
- group.visible = (index === floorIndex);
834
+ if (index === floorIndex) {
835
+ group.visible = true;
836
+ self._fadeInGroup(group, 300);
837
+ } else {
838
+ group.visible = false;
839
+ }
724
840
  });
725
841
  }
726
842
 
@@ -734,14 +850,14 @@ KimapSDK.prototype._showSingleFloor = function(floorIndex) {
734
850
  this.furnitureGroup.visible = true;
735
851
  }
736
852
 
737
- // 调整相机到该楼层
853
+ // 调整相机到该楼层(带动画)
738
854
  this._adjustCameraToFloor(floorIndex);
739
855
 
740
856
  console.log('KimapSDK: 已切换到楼层:', (floorIndex + 1) + 'F');
741
857
  };
742
858
 
743
859
  /**
744
- * 调整相机到能看到所有楼层
860
+ * 调整相机到能看到所有楼层(带动画)
745
861
  * @private
746
862
  */
747
863
  KimapSDK.prototype._zoomToShowAllFloors = function() {
@@ -773,25 +889,87 @@ KimapSDK.prototype._zoomToShowAllFloors = function() {
773
889
  var maxSize = Math.max(size.x, size.z);
774
890
  var distance = maxSize * 2;
775
891
 
776
- // 调整相机位置(更高视角)
777
- this.core.camera.position.set(
892
+ // 计算目标位置
893
+ var targetPosition = new THREE.Vector3(
778
894
  center.x,
779
895
  center.y + distance * 1.5,
780
896
  center.z + distance
781
897
  );
782
- this.core.camera.lookAt(center);
783
898
 
784
- if (this.core.controls) {
785
- this.core.controls.target.copy(center);
786
- this.core.controls.update();
787
- }
899
+ // 使用动画移动相机
900
+ var self = this;
901
+ this._animateCamera({
902
+ position: targetPosition,
903
+ lookAt: center
904
+ }, 800, function() {
905
+ console.log('KimapSDK: 已切换到ALL模式(动画完成)');
906
+ });
907
+
908
+ // 淡入背景建筑
909
+ this._fadeInBackgroundBuildings(600);
788
910
  } catch (e) {
789
911
  console.warn('调整相机到ALL模式失败:', e);
790
912
  }
791
913
  };
792
914
 
793
915
  /**
794
- * 调整相机到指定楼层
916
+ * 淡入背景建筑
917
+ * @private
918
+ */
919
+ KimapSDK.prototype._fadeInBackgroundBuildings = function(duration) {
920
+ if (!this.backgroundGroup) return;
921
+
922
+ this.backgroundGroup.visible = true;
923
+
924
+ // 设置初始透明度
925
+ this.backgroundGroup.traverse(function(child) {
926
+ if (child.material) {
927
+ if (Array.isArray(child.material)) {
928
+ child.material.forEach(function(mat) {
929
+ if (mat.opacity !== undefined) {
930
+ mat._originalOpacity = mat.opacity;
931
+ mat.opacity = 0;
932
+ }
933
+ });
934
+ } else if (child.material.opacity !== undefined) {
935
+ child.material._originalOpacity = child.material.opacity;
936
+ child.material.opacity = 0;
937
+ }
938
+ }
939
+ });
940
+
941
+ var self = this;
942
+ var startTime = performance.now();
943
+
944
+ function fadeAnimate() {
945
+ var elapsed = performance.now() - startTime;
946
+ var progress = Math.min(elapsed / duration, 1);
947
+
948
+ self.backgroundGroup.traverse(function(child) {
949
+ if (child.material) {
950
+ var originalOpacity = child.material._originalOpacity !== undefined ? child.material._originalOpacity : 1;
951
+ if (Array.isArray(child.material)) {
952
+ child.material.forEach(function(mat) {
953
+ if (mat.opacity !== undefined) {
954
+ mat.opacity = originalOpacity * progress;
955
+ }
956
+ });
957
+ } else if (child.material.opacity !== undefined) {
958
+ child.material.opacity = originalOpacity * progress;
959
+ }
960
+ }
961
+ });
962
+
963
+ if (progress < 1) {
964
+ requestAnimationFrame(fadeAnimate);
965
+ }
966
+ }
967
+
968
+ fadeAnimate();
969
+ };
970
+
971
+ /**
972
+ * 调整相机到指定楼层(带动画)
795
973
  * @private
796
974
  */
797
975
  KimapSDK.prototype._adjustCameraToFloor = function(floorIndex) {
@@ -808,17 +986,21 @@ KimapSDK.prototype._adjustCameraToFloor = function(floorIndex) {
808
986
  var maxSize = Math.max(size.x, size.z);
809
987
  var distance = maxSize * 1.5;
810
988
 
811
- this.core.camera.position.set(
989
+ // 计算目标位置
990
+ var targetPosition = new THREE.Vector3(
812
991
  center.x,
813
992
  center.y + distance,
814
993
  center.z + distance * 0.5
815
994
  );
816
- this.core.camera.lookAt(center);
817
995
 
818
- if (this.core.controls) {
819
- this.core.controls.target.copy(center);
820
- this.core.controls.update();
821
- }
996
+ // 使用动画移动相机
997
+ var self = this;
998
+ this._animateCamera({
999
+ position: targetPosition,
1000
+ lookAt: center
1001
+ }, 600, function() {
1002
+ console.log('KimapSDK: 已切换到楼层 ' + (floorIndex + 1) + 'F(动画完成)');
1003
+ });
822
1004
  } catch (e) {
823
1005
  console.warn('调整相机到楼层失败:', e);
824
1006
  }
@@ -926,6 +1108,7 @@ KimapSDK.prototype._createBackgroundBuildings = function(GLTFLoader, models, cou
926
1108
 
927
1109
  /**
928
1110
  * 创建备用几何体建筑(当没有模型文件时)
1111
+ * 增强版:科技蓝风格,窗户细节
929
1112
  * @private
930
1113
  */
931
1114
  KimapSDK.prototype._createFallbackBuilding = function(x, z) {
@@ -933,17 +1116,226 @@ KimapSDK.prototype._createFallbackBuilding = function(x, z) {
933
1116
  var width = 15 + Math.random() * 25;
934
1117
  var depth = 15 + Math.random() * 25;
935
1118
 
1119
+ var buildingGroup = new THREE.Group();
1120
+ buildingGroup.position.set(x, 0, z);
1121
+
1122
+ // 主楼体 - 科技蓝渐变
936
1123
  var geometry = new THREE.BoxGeometry(width, height, depth);
1124
+
1125
+ // 科技蓝基础色
1126
+ var baseColor = new THREE.Color().setHSL(0.6 + Math.random() * 0.05, 0.8, 0.25 + Math.random() * 0.1);
937
1127
  var material = new THREE.MeshStandardMaterial({
938
- color: new THREE.Color().setHSL(0.6, 0.1, 0.3 + Math.random() * 0.2),
939
- roughness: 0.8,
940
- metalness: 0.1
1128
+ color: baseColor,
1129
+ roughness: 0.3,
1130
+ metalness: 0.6,
1131
+ emissive: baseColor.clone().multiplyScalar(0.1)
941
1132
  });
942
1133
 
943
1134
  var building = new THREE.Mesh(geometry, material);
944
- building.position.set(x, height / 2, z);
1135
+ building.position.y = height / 2;
1136
+ buildingGroup.add(building);
1137
+
1138
+ // 添加窗户细节
1139
+ this._addWindowsToBuilding(buildingGroup, width, height, depth);
1140
+
1141
+ // 添加发光边缘
1142
+ this._addGlowingEdges(buildingGroup, width, height, depth);
1143
+
1144
+ return buildingGroup;
1145
+ };
1146
+
1147
+ /**
1148
+ * 为建筑添加窗户细节
1149
+ * @private
1150
+ */
1151
+ KimapSDK.prototype._addWindowsToBuilding = function(buildingGroup, width, height, depth) {
1152
+ var windowWidth = 1.5;
1153
+ var windowHeight = 2;
1154
+ var windowSpacingX = 3;
1155
+ var windowSpacingY = 4;
1156
+
1157
+ var windowMaterial = new THREE.MeshBasicMaterial({
1158
+ color: new THREE.Color().setHSL(0.58, 0.9, 0.7), // 淡蓝色窗户
1159
+ transparent: true,
1160
+ opacity: 0.8
1161
+ });
1162
+
1163
+ var windowGeometry = new THREE.PlaneGeometry(windowWidth, windowHeight);
1164
+
1165
+ // 前后面窗户
1166
+ for (var y = 3; y < height - 2; y += windowSpacingY) {
1167
+ for (var xOff = -width / 2 + 2; xOff < width / 2 - 1; xOff += windowSpacingX) {
1168
+ // 前面
1169
+ var windowFront = new THREE.Mesh(windowGeometry, windowMaterial.clone());
1170
+ windowFront.position.set(xOff, y, depth / 2 + 0.1);
1171
+ buildingGroup.add(windowFront);
1172
+
1173
+ // 后面
1174
+ var windowBack = new THREE.Mesh(windowGeometry, windowMaterial.clone());
1175
+ windowBack.position.set(xOff, y, -depth / 2 - 0.1);
1176
+ windowBack.rotation.y = Math.PI;
1177
+ buildingGroup.add(windowBack);
1178
+ }
1179
+ }
1180
+
1181
+ // 左右面窗户
1182
+ for (y = 3; y < height - 2; y += windowSpacingY) {
1183
+ for (var zOff = -depth / 2 + 2; zOff < depth / 2 - 1; zOff += windowSpacingX) {
1184
+ // 右面
1185
+ var windowRight = new THREE.Mesh(windowGeometry, windowMaterial.clone());
1186
+ windowRight.position.set(width / 2 + 0.1, y, zOff);
1187
+ windowRight.rotation.y = Math.PI / 2;
1188
+ buildingGroup.add(windowRight);
1189
+
1190
+ // 左面
1191
+ var windowLeft = new THREE.Mesh(windowGeometry, windowMaterial.clone());
1192
+ windowLeft.position.set(-width / 2 - 0.1, y, zOff);
1193
+ windowLeft.rotation.y = -Math.PI / 2;
1194
+ buildingGroup.add(windowLeft);
1195
+ }
1196
+ }
1197
+ };
1198
+
1199
+ /**
1200
+ * 为建筑添加发光边缘
1201
+ * @private
1202
+ */
1203
+ KimapSDK.prototype._addGlowingEdges = function(buildingGroup, width, height, depth) {
1204
+ var edgeMaterial = new THREE.LineBasicMaterial({
1205
+ color: new THREE.Color().setHSL(0.58, 1, 0.5), // 科技蓝
1206
+ linewidth: 2
1207
+ });
1208
+
1209
+ var edgesGeometry = new THREE.EdgesGeometry(new THREE.BoxGeometry(width, height, depth));
1210
+ var edges = new THREE.LineSegments(edgesGeometry, edgeMaterial);
1211
+ edges.position.y = height / 2;
1212
+ buildingGroup.add(edges);
1213
+ };
1214
+
1215
+ /**
1216
+ * 创建科技感地面背景
1217
+ * @private
1218
+ */
1219
+ KimapSDK.prototype._createTechGround = function() {
1220
+ if (!this.core) return;
1221
+
1222
+ var groundSize = 500;
1223
+
1224
+ // 创建网格地面
1225
+ var gridHelper = new THREE.GridHelper(groundSize, 50, 0x0066aa, 0x003355);
1226
+ gridHelper.position.y = -0.1;
1227
+ gridHelper.material.opacity = 0.3;
1228
+ gridHelper.material.transparent = true;
1229
+ this.core.scene.add(gridHelper);
1230
+
1231
+ // 创建地面平面
1232
+ var groundGeometry = new THREE.PlaneGeometry(groundSize, groundSize);
1233
+ var groundMaterial = new THREE.MeshBasicMaterial({
1234
+ color: 0x0a1628,
1235
+ transparent: true,
1236
+ opacity: 0.8
1237
+ });
1238
+ var ground = new THREE.Mesh(groundGeometry, groundMaterial);
1239
+ ground.rotation.x = -Math.PI / 2;
1240
+ ground.position.y = -0.2;
1241
+ this.core.scene.add(ground);
1242
+
1243
+ // 创建道路线条
1244
+ this._createRoadLines(groundSize);
1245
+ };
1246
+
1247
+ /**
1248
+ * 创建道路流光线条
1249
+ * @private
1250
+ */
1251
+ KimapSDK.prototype._createRoadLines = function(size) {
1252
+ var roadGroup = new THREE.Group();
1253
+ roadGroup.name = 'roadLines';
1254
+ this.roadLinesGroup = roadGroup;
1255
+
1256
+ var lineCount = 20;
1257
+ var roadWidth = 8;
1258
+
1259
+ for (var i = 0; i < lineCount; i++) {
1260
+ var angle = Math.random() * Math.PI * 2;
1261
+ var length = size * (0.3 + Math.random() * 0.5);
1262
+ var x = Math.cos(angle) * size * 0.4;
1263
+ var z = Math.sin(angle) * size * 0.4;
1264
+
1265
+ // 创建道路
1266
+ var roadGeometry = new THREE.PlaneGeometry(length, roadWidth);
1267
+ var roadMaterial = new THREE.MeshBasicMaterial({
1268
+ color: 0x112233,
1269
+ transparent: true,
1270
+ opacity: 0.6
1271
+ });
1272
+ var road = new THREE.Mesh(roadGeometry, roadMaterial);
1273
+ road.rotation.x = -Math.PI / 2;
1274
+ road.rotation.z = angle;
1275
+ road.position.set(x, 0, z);
1276
+ roadGroup.add(road);
1277
+
1278
+ // 创建中心虚线
1279
+ var dashCount = Math.floor(length / 5);
1280
+ for (var d = 0; d < dashCount; d++) {
1281
+ var dashGeometry = new THREE.PlaneGeometry(2, 0.3);
1282
+ var dashMaterial = new THREE.MeshBasicMaterial({
1283
+ color: 0x00aaff,
1284
+ transparent: true,
1285
+ opacity: 0.8
1286
+ });
1287
+ var dash = new THREE.Mesh(dashGeometry, dashMaterial);
1288
+ dash.rotation.x = -Math.PI / 2;
1289
+ dash.rotation.z = angle;
1290
+ dash.position.set(
1291
+ x + Math.cos(angle) * (d * 5 - length / 2 + 1),
1292
+ 0.05,
1293
+ z + Math.sin(angle) * (d * 5 - length / 2 + 1)
1294
+ );
1295
+ dash.userData.baseOpacity = 0.4 + Math.random() * 0.4;
1296
+ dash.userData.phase = Math.random() * Math.PI * 2;
1297
+ dash.userData.speed = 0.5 + Math.random() * 1.5;
1298
+ roadGroup.add(dash);
1299
+ }
1300
+ }
1301
+
1302
+ this.core.scene.add(roadGroup);
1303
+ };
1304
+
1305
+ /**
1306
+ * 更新道路流光动画(在render loop中调用)
1307
+ * @private
1308
+ */
1309
+ KimapSDK.prototype._updateRoadAnimation = function(time) {
1310
+ if (!this.roadLinesGroup) return;
945
1311
 
946
- return building;
1312
+ this.roadLinesGroup.traverse(function(child) {
1313
+ if (child.userData && child.userData.baseOpacity !== undefined) {
1314
+ var pulse = Math.sin(time * child.userData.speed + child.userData.phase) * 0.3 + 0.5;
1315
+ child.material.opacity = child.userData.baseOpacity * pulse;
1316
+ }
1317
+ });
1318
+ };
1319
+
1320
+ /**
1321
+ * 在渲染循环中添加道路动画更新
1322
+ * @private
1323
+ */
1324
+ KimapSDK.prototype._startRoadAnimationLoop = function() {
1325
+ var self = this;
1326
+ var originalStartRenderLoop = this.startRenderLoop.bind(this);
1327
+
1328
+ this.startRenderLoop = function() {
1329
+ originalStartRenderLoop();
1330
+
1331
+ // 添加道路动画更新
1332
+ var animationLoop = function() {
1333
+ requestAnimationFrame(animationLoop);
1334
+ var time = performance.now() * 0.001;
1335
+ self._updateRoadAnimation(time);
1336
+ };
1337
+ animationLoop();
1338
+ };
947
1339
  };
948
1340
 
949
1341
  /**
@@ -1013,6 +1405,66 @@ KimapSDK.prototype.hasProjectLayers = function() {
1013
1405
 
1014
1406
  // ==================== 工具方法 ====================
1015
1407
 
1408
+ /**
1409
+ * 相机动画状态
1410
+ */
1411
+ KimapSDK.prototype._cameraAnimating = false;
1412
+ KimapSDK.prototype._cameraAnimationId = null;
1413
+
1414
+ /**
1415
+ * 动画相机移动到目标位置
1416
+ * @param {Object} target - 目标位置 { position, lookAt }
1417
+ * @param {number} duration - 动画持续时间(毫秒)
1418
+ * @param {Function} onComplete - 完成回调
1419
+ * @private
1420
+ */
1421
+ KimapSDK.prototype._animateCamera = function(target, duration, onComplete) {
1422
+ var self = this;
1423
+ if (!this.core || !this.core.camera) return;
1424
+
1425
+ var startPosition = this.core.camera.position.clone();
1426
+ var startTarget = this.core.controls ? this.core.controls.target.clone() : new THREE.Vector3();
1427
+
1428
+ var endPosition = target.position || startPosition;
1429
+ var endTarget = target.lookAt || startTarget;
1430
+
1431
+ var startTime = performance.now();
1432
+ this._cameraAnimating = true;
1433
+
1434
+ function easeOutCubic(t) {
1435
+ return 1 - Math.pow(1 - t, 3);
1436
+ }
1437
+
1438
+ function animate() {
1439
+ var elapsed = performance.now() - startTime;
1440
+ var progress = Math.min(elapsed / duration, 1);
1441
+ var easedProgress = easeOutCubic(progress);
1442
+
1443
+ // 插值相机位置
1444
+ self.core.camera.position.lerpVectors(startPosition, endPosition, easedProgress);
1445
+
1446
+ // 插值目标点
1447
+ if (self.core.controls) {
1448
+ self.core.controls.target.lerpVectors(startTarget, endTarget, easedProgress);
1449
+ self.core.controls.update();
1450
+ }
1451
+
1452
+ if (progress < 1) {
1453
+ self._cameraAnimationId = requestAnimationFrame(animate);
1454
+ } else {
1455
+ self._cameraAnimating = false;
1456
+ if (onComplete) onComplete();
1457
+ }
1458
+ }
1459
+
1460
+ // 取消之前的动画
1461
+ if (this._cameraAnimationId) {
1462
+ cancelAnimationFrame(this._cameraAnimationId);
1463
+ }
1464
+
1465
+ animate();
1466
+ };
1467
+
1016
1468
  KimapSDK.prototype.getScene = function() {
1017
1469
  return this.core ? this.core.scene : null;
1018
1470
  };
@@ -1143,19 +1595,37 @@ KimapSDK.prototype.getConfig = function() {
1143
1595
 
1144
1596
  KimapSDK.prototype.loadKMData = function() {
1145
1597
  var self = this;
1146
-
1598
+
1147
1599
  if (!this.dataUrl) {
1148
1600
  console.warn('⚠️ 未配置dataUrl,无法加载3D家具模型');
1149
1601
  return Promise.resolve({ success: false, message: '未配置dataUrl' });
1150
1602
  }
1151
-
1603
+
1152
1604
  if (!this.config.objUrl) {
1153
1605
  console.warn('⚠️ 未配置objUrl,无法确定kidata文件名');
1154
1606
  return Promise.resolve({ success: false, message: '未配置objUrl' });
1155
1607
  }
1156
-
1608
+
1157
1609
  // 从objUrl提取文件名和目录
1158
1610
  var objUrl = this.config.objUrl;
1611
+
1612
+ // 处理 objUrl 可能是数组的情况
1613
+ if (Array.isArray(objUrl)) {
1614
+ // 多楼层模式:使用第一个 URL 或跳过
1615
+ if (objUrl.length === 0) {
1616
+ console.warn('⚠️ objUrl数组为空,无法加载kidata');
1617
+ return Promise.resolve({ success: false, message: 'objUrl数组为空' });
1618
+ }
1619
+ objUrl = objUrl[0];
1620
+ console.log('KimapSDK: 使用第一个地图URL加载kidata:', objUrl);
1621
+ }
1622
+
1623
+ // 确保 objUrl 是字符串
1624
+ if (typeof objUrl !== 'string') {
1625
+ console.warn('⚠️ objUrl类型错误,无法加载kidata');
1626
+ return Promise.resolve({ success: false, message: 'objUrl类型错误' });
1627
+ }
1628
+
1159
1629
  var lastSlashIndex = objUrl.lastIndexOf('/');
1160
1630
  var directory = lastSlashIndex >= 0 ? objUrl.substring(0, lastSlashIndex) : '';
1161
1631
  var fileName = objUrl.split('/').pop().replace(/\.(obj|kimap)$/i, '');