@kimap/indoor-positioning-sdk-vue2 5.7.7 → 5.7.8

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kimap/indoor-positioning-sdk-vue2",
3
- "version": "5.7.7",
3
+ "version": "5.7.8",
4
4
  "description": "Vue2自包含室内定位SDK - 完全兼容Webpack3+Babel6 | Vue2 Self-Contained Indoor Positioning SDK",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -82,9 +82,10 @@ KimapSDK.prototype.init = function() {
82
82
  self.startRenderLoop();
83
83
  self._extractWallsFromModel();
84
84
 
85
- // 多楼层默认显示第一个楼层
85
+ // 多楼层默认显示第一个楼层(单层模式),同时记录ALL模式间隙
86
86
  if (self.isMultiFloor && self.floorGroups && self.floorGroups.length > 0) {
87
87
  self.showSingleFloor(0);
88
+ self.showAllFloors(); // 立即恢复到ALL模式(含间隙),初始化后保持ALL状态
88
89
  }
89
90
 
90
91
  // 渲染内置楼层选择器 UI
@@ -607,16 +608,17 @@ KimapSDK.prototype.showSingleFloor = function(floorIndex) {
607
608
  var self = this;
608
609
  this.floorGroups.forEach(function(group, index) {
609
610
  if (index === floorIndex) {
610
- // 单层模式:将该楼层移动到贴地位置(y = 0)
611
+ // 贴地 + 清除ALL模式的XZ中心对齐
611
612
  group.visible = true;
612
- group.position.y = 0;
613
+ group.position.set(0, 0, 0);
613
614
  } else {
614
- // 隐藏其他楼层,同时移到下方避免干扰
615
615
  group.visible = false;
616
616
  var originalY = self.core.floorOriginalY[index] || 0;
617
- group.position.y = -originalY - 1; // 移到地面以下
617
+ group.position.y = -originalY - 1;
618
+ // XZ 保持中心对齐(向下移动后不再占用视觉空间)
618
619
  }
619
620
  });
621
+ this.currentFloorIndex = floorIndex;
620
622
  };
621
623
 
622
624
  /**
@@ -628,10 +630,10 @@ KimapSDK.prototype.showAllFloors = function() {
628
630
  var self = this;
629
631
  this.floorGroups.forEach(function(group, index) {
630
632
  group.visible = true;
631
- // 恢复原始世界 Y 偏移(含间隙)
632
633
  var originalY = self.core.floorOriginalY[index] || 0;
633
634
  group.position.y = originalY;
634
635
  });
636
+ this.currentFloorIndex = 0;
635
637
  };
636
638
 
637
639
  /**
@@ -888,55 +890,62 @@ KimapSDK.prototype.getConfig = function() {
888
890
 
889
891
  KimapSDK.prototype.loadKMData = function() {
890
892
  var self = this;
891
-
893
+
892
894
  if (!this.dataUrl) {
893
895
  console.warn('⚠️ 未配置dataUrl,无法加载3D家具模型');
894
896
  return Promise.resolve({ success: false, message: '未配置dataUrl' });
895
897
  }
896
-
898
+
897
899
  if (!this.config.objUrl) {
898
900
  console.warn('⚠️ 未配置objUrl,无法确定kidata文件名');
899
901
  return Promise.resolve({ success: false, message: '未配置objUrl' });
900
902
  }
901
-
902
- // 从objUrl提取文件名和目录(支持数组或字符串)
903
+
903
904
  var objUrl = this.config.objUrl;
904
905
  var isArray = Array.isArray(objUrl);
905
- var firstUrl = isArray ? objUrl[0] : objUrl;
906
+
907
+ // 多楼层模式:等 floorGroups 就绪后再加载各层 kidata
908
+ if (isArray) {
909
+ var self2 = this;
910
+ if (self2.floorGroups && self2.floorGroups.length > 0) {
911
+ // 模型已就绪,直接加载
912
+ return self2._loadAllFloorKidatas(objUrl);
913
+ }
914
+ // 模型未就绪,等回调
915
+ return new Promise(function(resolve) {
916
+ var original = self2.core.onFloorModelsLoaded;
917
+ self2.core.onFloorModelsLoaded = function() {
918
+ if (typeof original === 'function') original();
919
+ resolve();
920
+ };
921
+ }).then(function() {
922
+ return self2._loadAllFloorKidatas(objUrl);
923
+ });
924
+ }
925
+
926
+ // 单楼层模式:沿用原有逻辑
927
+ var firstUrl = objUrl;
906
928
  var lastSlashIndex = firstUrl.lastIndexOf('/');
907
929
  var directory = lastSlashIndex >= 0 ? firstUrl.substring(0, lastSlashIndex) : '';
908
930
  var fileName = firstUrl.split('/').pop().replace(/\.(obj|kimap)$/i, '');
909
931
  var kidataUrl = directory + '/' + fileName + '.kidata';
910
-
932
+
911
933
  return fetch(kidataUrl)
912
934
  .then(function(response) {
913
- if (!response.ok) {
914
- throw new Error('Kidata文件加载失败: ' + response.status);
915
- }
935
+ if (!response.ok) throw new Error('Kidata文件加载失败: ' + response.status);
916
936
  return response.text();
917
937
  })
918
938
  .then(function(encryptedData) {
919
- // 解密kidata文件
920
939
  var decrypted = self._decryptKidata(encryptedData);
921
940
  var kidataObj = JSON.parse(decrypted);
922
-
923
- // 保存kidata数据供路径规划使用
924
941
  self.kidataContent = kidataObj;
925
-
926
- // 如果kidata包含楼层数据,设置为projectLayers
927
- if (kidataObj.floors && Array.isArray(kidataObj.floors)) {
928
- // 设置第一个楼层为当前楼层(如果没有其他楼层数据)
929
- if (!self.projectLayers && kidataObj.floors.length > 0) {
930
- self.projectLayers = kidataObj.floors[0].layers;
931
- }
942
+ if (kidataObj.floors && Array.isArray(kidataObj.floors) && kidataObj.floors.length > 0) {
943
+ self.projectLayers = kidataObj.floors[0].layers;
932
944
  }
933
-
934
- // 加载3D家具模型
935
945
  return self._loadFurnitureModels(kidataObj);
936
946
  })
937
947
  .then(function(result) {
938
948
  console.log('✅ 家具加载完成:成功 ' + result.loaded + ' 个,失败 ' + result.failed + ' 个');
939
-
940
949
  return { success: true, loaded: result.loaded, failed: result.failed };
941
950
  })
942
951
  .catch(function(error) {
@@ -945,6 +954,131 @@ KimapSDK.prototype.loadKMData = function() {
945
954
  });
946
955
  };
947
956
 
957
+ /**
958
+ * 多楼层:为指定楼层加载 kidata(含家具 + 文字)
959
+ * @private
960
+ */
961
+ KimapSDK.prototype._loadKidataForFloor = function(url, floorIndex) {
962
+ var self = this;
963
+
964
+ var lastSlashIndex = url.lastIndexOf('/');
965
+ var directory = lastSlashIndex >= 0 ? url.substring(0, lastSlashIndex) : '';
966
+ var fileName = url.split('/').pop().replace(/\.(obj|kimap)$/i, '');
967
+ var kidataUrl = directory + '/' + fileName + '.kidata';
968
+
969
+ return fetch(kidataUrl)
970
+ .then(function(response) {
971
+ if (!response.ok) throw new Error('Kidata文件加载失败: ' + response.status);
972
+ return response.text();
973
+ })
974
+ .then(function(encryptedData) {
975
+ var decrypted = self._decryptKidata(encryptedData);
976
+ var kidataObj = JSON.parse(decrypted);
977
+
978
+ // 创建该楼层的家具组(作为 floorGroup 的子级,随楼层移动)
979
+ var floorFurnitureGroup = new THREE.Group();
980
+ floorFurnitureGroup.name = 'furniture_floor_' + floorIndex;
981
+ self.floorGroups[floorIndex].add(floorFurnitureGroup);
982
+
983
+ // 加载家具(传入楼层家具组)
984
+ return self._loadFurnitureModels(kidataObj, floorFurnitureGroup)
985
+ .then(function(furnitureResult) {
986
+ // 提取并渲染文字(每层独立)
987
+ self._loadTextForFloor(kidataObj, floorIndex);
988
+ return furnitureResult;
989
+ });
990
+ })
991
+ .then(function(result) {
992
+ return result;
993
+ })
994
+ .catch(function(error) {
995
+ console.error('❌ 楼层' + floorIndex + '加载失败:', error.message);
996
+ return { loaded: 0, failed: 0 };
997
+ });
998
+ };
999
+
1000
+ /**
1001
+ * 多楼层:加载所有楼层的 kidata(家具+文字)
1002
+ * @private
1003
+ */
1004
+ KimapSDK.prototype._loadAllFloorKidatas = function(objUrlArray) {
1005
+ var self = this;
1006
+ var loadPromises = objUrlArray.map(function(url, index) {
1007
+ return self._loadKidataForFloor(url, index);
1008
+ });
1009
+ return Promise.all(loadPromises)
1010
+ .then(function(results) {
1011
+ var totalLoaded = results.reduce(function(s, r) { return s + (r.loaded || 0); }, 0);
1012
+ var totalFailed = results.reduce(function(s, r) { return s + (r.failed || 0); }, 0);
1013
+ console.log('✅ 全部楼层家具加载完成:成功 ' + totalLoaded + ' 个,失败 ' + totalFailed + ' 个');
1014
+ return { success: true, loaded: totalLoaded, failed: totalFailed };
1015
+ })
1016
+ .catch(function(error) {
1017
+ console.error('❌ 楼层家具加载失败:', error.message);
1018
+ return { success: false, error: error.message };
1019
+ });
1020
+ };
1021
+
1022
+ /**
1023
+ * 多楼层:为指定楼层渲染文字
1024
+ * @private
1025
+ */
1026
+ KimapSDK.prototype._loadTextForFloor = function(kidataObj, floorIndex) {
1027
+ var self = this;
1028
+ var floorTextGroup = this.core.floorTextGroups[floorIndex];
1029
+ if (!floorTextGroup) return;
1030
+
1031
+ var textElements = [];
1032
+ if (kidataObj.floors && Array.isArray(kidataObj.floors)) {
1033
+ kidataObj.floors.forEach(function(floor) {
1034
+ if (floor.layers) {
1035
+ floor.layers.forEach(function(layer) {
1036
+ if (layer.elements) {
1037
+ layer.elements.forEach(function(element) {
1038
+ if (element.type === 'text') {
1039
+ textElements.push(element);
1040
+ }
1041
+ });
1042
+ }
1043
+ });
1044
+ }
1045
+ });
1046
+ }
1047
+
1048
+ if (textElements.length === 0) return;
1049
+
1050
+ textElements.forEach(function(textEl) {
1051
+ if (!textEl.points || textEl.points.length === 0) return;
1052
+ var point = textEl.points[0];
1053
+ var x = point.x / 100;
1054
+ var z = point.y / 100;
1055
+ var LOGICAL_WALL_HEIGHT = 3000;
1056
+ var RENDER_WALL_HEIGHT = 0.375;
1057
+ var elevationMm = textEl.elevation || 8500;
1058
+ var y = (elevationMm / LOGICAL_WALL_HEIGHT) * RENDER_WALL_HEIGHT;
1059
+ var TEXT_SCALE_FACTOR = 2.52;
1060
+
1061
+ var sprite = self.core.createTextSprite({
1062
+ content: textEl.content || '',
1063
+ fontSize: (textEl.fontSize || 16) * TEXT_SCALE_FACTOR,
1064
+ color: textEl.color || '#000000',
1065
+ fontFamily: textEl.fontFamily || 'Arial, sans-serif',
1066
+ bold: textEl.bold || false,
1067
+ italic: textEl.italic || false,
1068
+ borderColor: textEl.borderColor || '#ADD8E6',
1069
+ backgroundColor: textEl.backgroundColor || '#FFFFFF',
1070
+ backgroundOpacity: textEl.backgroundOpacity !== undefined ? textEl.backgroundOpacity : 0.8,
1071
+ padding: textEl.padding || 8
1072
+ });
1073
+
1074
+ sprite.position.set(x, y, z);
1075
+ sprite.userData = { type: 'text', elementId: textEl.id };
1076
+ floorTextGroup.add(sprite);
1077
+ });
1078
+
1079
+ console.log('✅ 楼层' + floorIndex + '文字加载完成: ' + textElements.length + ' 个');
1080
+ };
1081
+
948
1082
  KimapSDK.prototype._decryptKidata = function(encrypted) {
949
1083
  try {
950
1084
  // 1. Base64解码
@@ -964,48 +1098,51 @@ KimapSDK.prototype._decryptKidata = function(encrypted) {
964
1098
  }
965
1099
  };
966
1100
 
967
- KimapSDK.prototype._loadFurnitureModels = function(kidataObj) {
1101
+ KimapSDK.prototype._loadFurnitureModels = function(kidataObj, targetGroup) {
968
1102
  var self = this;
969
-
1103
+
970
1104
  if (!kidataObj.furnitures || kidataObj.furnitures.length === 0) {
971
1105
  return Promise.resolve({ loaded: 0, failed: 0 });
972
1106
  }
973
-
1107
+
974
1108
  // 创建家具组(如果不存在)
975
1109
  if (!this.furnitureGroup) {
976
1110
  this.furnitureGroup = new THREE.Group();
977
1111
  this.furnitureGroup.name = 'furnitures';
978
1112
  this.core.scene.add(this.furnitureGroup);
979
1113
  }
980
-
981
- // 清空现有家具
982
- while (this.furnitureGroup.children.length > 0) {
983
- var child = this.furnitureGroup.children[0];
984
- this.furnitureGroup.remove(child);
985
-
986
- // 释放资源
987
- if (child.geometry) child.geometry.dispose();
988
- if (child.material) {
989
- if (Array.isArray(child.material)) {
990
- child.material.forEach(function(m) { m.dispose(); });
991
- } else {
992
- child.material.dispose();
1114
+
1115
+ // 多楼层模式:家具加载到指定楼层的子Group(随楼层移动)
1116
+ // 单楼层模式:家具加载到场景家具组(独立存在)
1117
+ var target = targetGroup || this.furnitureGroup;
1118
+
1119
+ // 单楼层模式:清空场景家具组
1120
+ if (!targetGroup) {
1121
+ while (this.furnitureGroup.children.length > 0) {
1122
+ var child = this.furnitureGroup.children[0];
1123
+ this.furnitureGroup.remove(child);
1124
+ if (child.geometry) child.geometry.dispose();
1125
+ if (child.material) {
1126
+ if (Array.isArray(child.material)) {
1127
+ child.material.forEach(function(m) { m.dispose(); });
1128
+ } else {
1129
+ child.material.dispose();
1130
+ }
993
1131
  }
994
1132
  }
995
1133
  }
996
-
1134
+
997
1135
  // 优先使用dataUrl,如果不存在则使用serverUrl(兼容旧版本)
998
1136
  var serverUrl = kidataObj.dataUrl || kidataObj.serverUrl;
999
1137
  var loadPromises = [];
1000
1138
  var loadedCount = 0;
1001
1139
  var failedCount = 0;
1002
-
1003
- // 加载每个家具模型
1140
+
1004
1141
  kidataObj.furnitures.forEach(function(furniture) {
1005
1142
  var promise = self._loadSingleFurniture(furniture, serverUrl)
1006
1143
  .then(function(mesh) {
1007
1144
  if (mesh) {
1008
- self.furnitureGroup.add(mesh);
1145
+ target.add(mesh);
1009
1146
  loadedCount++;
1010
1147
  } else {
1011
1148
  failedCount++;
@@ -1014,10 +1151,10 @@ KimapSDK.prototype._loadFurnitureModels = function(kidataObj) {
1014
1151
  .catch(function(error) {
1015
1152
  failedCount++;
1016
1153
  });
1017
-
1154
+
1018
1155
  loadPromises.push(promise);
1019
1156
  });
1020
-
1157
+
1021
1158
  return Promise.all(loadPromises).then(function() {
1022
1159
  return { loaded: loadedCount, failed: failedCount };
1023
1160
  });
@@ -62,9 +62,12 @@ function SceneCore(config) {
62
62
 
63
63
  // 多楼层支持(不影响单楼层原有渲染路径)
64
64
  this.floorGroups = [];
65
+ this.floorFurnitureGroups = []; // 每层独立的家具Group
66
+ this.floorTextGroups = []; // 每层独立的文字Group
65
67
  this.floorHeight = 3; // 米
66
68
  this.isMultiFloor = Array.isArray(config.objUrl);
67
69
  this.floorOriginalY = []; // 每个楼层的原始世界Y偏移(含间隙)
70
+ this.onFloorModelsLoaded = null; // 回调:所有楼层模型加载完毕后触发
68
71
  }
69
72
 
70
73
  /**
@@ -97,6 +100,12 @@ SceneCore.prototype.init = function() {
97
100
  var OBJLoader = loaders[0];
98
101
  var MTLLoader = loaders[1];
99
102
  return self.loadMap(OBJLoader, MTLLoader);
103
+ })
104
+ .then(function() {
105
+ // 通知 onFloorModelsLoaded(如果已注册)
106
+ if (typeof self.onFloorModelsLoaded === 'function') {
107
+ self.onFloorModelsLoaded();
108
+ }
100
109
  });
101
110
  };
102
111
 
@@ -268,10 +277,11 @@ SceneCore.prototype.loadMap = function(OBJLoader, MTLLoader) {
268
277
  };
269
278
 
270
279
  /**
271
- * 多楼层:依次加载多个模型(新增能力,不改变单楼层逻辑)
280
+ * 多楼层:依次加载多个模型(两阶段:先加载添加,再统一算中心对齐)
272
281
  * @private
282
+ * @param {Function} onAllLoaded - 所有楼层模型加载完毕后的回调(此时 floorGroups 已就绪)
273
283
  */
274
- SceneCore.prototype._loadMultiFloorMaps = function(OBJLoader, MTLLoader, objUrls) {
284
+ SceneCore.prototype._loadMultiFloorMaps = function(OBJLoader, MTLLoader, objUrls, onAllLoaded) {
275
285
  var self = this;
276
286
  var floorIndex = 0;
277
287
 
@@ -280,8 +290,16 @@ SceneCore.prototype._loadMultiFloorMaps = function(OBJLoader, MTLLoader, objUrls
280
290
  return self._loadSingleMapWithFloorIndex(OBJLoader, MTLLoader, url, floorIndex++);
281
291
  });
282
292
  }, Promise.resolve()).then(function() {
293
+ // 阶段二:所有楼层加载完毕后,按联合包围盒中心对齐 XZ
283
294
  if (self.floorGroups.length > 1) {
284
- self._adjustCameraToAllFloors();
295
+ self._alignAllFloorsToCenter();
296
+ }
297
+ // 通知 SDK 层:楼层模型已就绪,可以加载各层 kidata 了
298
+ if (typeof onAllLoaded === 'function') {
299
+ onAllLoaded();
300
+ }
301
+ if (typeof self.onFloorModelsLoaded === 'function') {
302
+ self.onFloorModelsLoaded();
285
303
  }
286
304
  });
287
305
  };
@@ -373,15 +391,6 @@ SceneCore.prototype._loadKimapFileToGroup = function(kimapUrl, themeUrl, objLoad
373
391
  var object = objLoader.parse(objContent);
374
392
  self._processLoadedModelToGroup(object, materials, colorMap, floorGroup, floorIndex);
375
393
 
376
- // 提取文字元素(每个楼层都提取,以第一个楼层为准)
377
- if (!self._textElements || self._textElements.length === 0) {
378
- var textElements = extractTextElementsFromOBJ(objContent);
379
- if (textElements && textElements.length > 0) {
380
- self._textElements = textElements;
381
- self.renderTextElements(self._textElements);
382
- }
383
- }
384
-
385
394
  resolve();
386
395
  } catch (error) {
387
396
  reject(error);
@@ -418,26 +427,36 @@ SceneCore.prototype._processLoadedModelToGroup = function(object, materials, col
418
427
  var self = this;
419
428
  floorGroup.add(object);
420
429
 
430
+ // 为该层创建文字组(作为 floorGroup 子级,随楼层移动)
431
+ if (!self.floorTextGroups) self.floorTextGroups = [];
432
+ var floorTextGroup = new THREE.Group();
433
+ floorTextGroup.name = 'texts_floor_' + floorIndex;
434
+ floorGroup.add(floorTextGroup);
435
+ self.floorTextGroups[floorIndex] = floorTextGroup;
436
+
421
437
  var box = new THREE.Box3().setFromObject(object);
422
438
  var boxHeight = box.max.y - box.min.y;
423
439
 
440
+ // 记录每层的包围盒(供两阶段定位使用)
441
+ if (!self._floorBoxes) self._floorBoxes = [];
442
+ self._floorBoxes[floorIndex] = box;
443
+
424
444
  // 逐层累积高度:计算该楼层在 ALL 模式下的世界 Y 偏移
425
445
  var floorY = 0;
426
446
  for (var i = 0; i < floorIndex; i++) {
427
- var prevGroup = self.floorGroups[i];
428
- if (prevGroup) {
429
- var prevBox = new THREE.Box3().setFromObject(prevGroup);
430
- floorY += (prevBox.max.y - prevBox.min.y);
431
- }
447
+ var prevBox = self._floorBoxes[i];
448
+ if (prevBox) floorY += (prevBox.max.y - prevBox.min.y);
432
449
  }
433
450
 
434
- // 所有楼层的 object 底部(box.min.y)对齐到本地 Y=0,统一基准
451
+ // object.position.y = -box.min.y:对象底部(box.min.y)对齐到本地 Y=0
452
+ // 单层模式 floorGroup.position.y=0 → 对象底部世界 Y=0(贴地)
453
+ // ALL 模式 floorGroup.position.y=floorY → 对象底部世界 Y=floorY(堆叠)
435
454
  object.position.set(-box.min.x, -box.min.y, -box.min.z);
436
455
 
437
- // ALL 模式时 floorGroup.position.y 持有累积偏移(含间隙)
438
- floorGroup.position.y = floorY;
456
+ // 初始 Y 偏移(XZ 后续由 _alignAllFloorsToCenter 统一调整)
457
+ floorGroup.position.set(0, floorY, 0);
439
458
 
440
- // 记录每个楼层的原始世界 Y 偏移(单层模式时用于将选中层拽回 Y=0)
459
+ // 记录原始 Y 偏移(showSingleFloor/showAllFloors 使用)
441
460
  self.floorOriginalY[floorIndex] = floorY;
442
461
 
443
462
  // 复用 backup 的材质处理策略,不改渲染风格
@@ -452,6 +471,34 @@ SceneCore.prototype._processLoadedModelToGroup = function(object, materials, col
452
471
  });
453
472
  };
454
473
 
474
+ /**
475
+ * 多楼层:ALL 模式按每个楼层的几何中心对齐 XZ(Y 堆叠不变)
476
+ * object.position = (-box.min.x, -box.min.y, -box.min.z)
477
+ * 所以对象在 group 空间里范围是 [0~width, minY~maxY, 0~depth]
478
+ * 中心 = (width/2, (minY+maxY)/2, depth/2)
479
+ * 令 group.position.x/z = -center,即把中心对齐到世界原点
480
+ * @private
481
+ */
482
+ SceneCore.prototype._alignAllFloorsToCenter = function() {
483
+ var self = this;
484
+ if (!self._floorBoxes || self._floorBoxes.length === 0) return;
485
+
486
+ self.floorGroups.forEach(function(group, index) {
487
+ var box = self._floorBoxes[index];
488
+ if (!box) return;
489
+
490
+ var boxW = box.max.x - box.min.x;
491
+ var boxD = box.max.z - box.min.z;
492
+ var centerX = boxW / 2;
493
+ var centerZ = boxD / 2;
494
+
495
+ group.position.x = -centerX;
496
+ group.position.z = -centerZ;
497
+ });
498
+
499
+ console.log('[SceneCore] 多楼层按中心对齐 XZ 完成');
500
+ };
501
+
455
502
  /**
456
503
  * 多楼层:相机看全楼层
457
504
  * @private