@kimap/indoor-positioning-sdk-vue2 5.8.0 → 5.8.2

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.8.0",
3
+ "version": "5.8.2",
4
4
  "description": "Vue2自包含室内定位SDK - 完全兼容Webpack3+Babel6 | Vue2 Self-Contained Indoor Positioning SDK",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -30,6 +30,7 @@ function KimapSDK(config) {
30
30
  this.customDOMs = {};
31
31
  this.dataUrl = config.dataUrl || '';
32
32
  this.furnitureGroup = null;
33
+ this.floorFurnitureGroups = []; // 每个楼层独立的家具组
33
34
  this.projectLayers = null;
34
35
  this.floors = [];
35
36
  this.currentFloorId = null;
@@ -82,10 +83,9 @@ KimapSDK.prototype.init = function() {
82
83
  self.startRenderLoop();
83
84
  self._extractWallsFromModel();
84
85
 
85
- // 多楼层默认显示第一个楼层(单层模式),同时记录ALL模式间隙
86
+ // 多楼层默认显示第一个楼层
86
87
  if (self.isMultiFloor && self.floorGroups && self.floorGroups.length > 0) {
87
88
  self.showSingleFloor(0);
88
- self.showAllFloors(); // 立即恢复到ALL模式(含间隙),初始化后保持ALL状态
89
89
  }
90
90
 
91
91
  // 渲染内置楼层选择器 UI
@@ -608,17 +608,16 @@ KimapSDK.prototype.showSingleFloor = function(floorIndex) {
608
608
  var self = this;
609
609
  this.floorGroups.forEach(function(group, index) {
610
610
  if (index === floorIndex) {
611
- // 贴地 + 清除ALL模式的XZ中心对齐
611
+ // 单层模式:将该楼层移动到贴地位置(y = 0)
612
612
  group.visible = true;
613
- group.position.set(0, 0, 0);
613
+ group.position.y = 0;
614
614
  } else {
615
+ // 隐藏其他楼层,同时移到下方避免干扰
615
616
  group.visible = false;
616
617
  var originalY = self.core.floorOriginalY[index] || 0;
617
- group.position.y = -originalY - 1;
618
- // XZ 保持中心对齐(向下移动后不再占用视觉空间)
618
+ group.position.y = -originalY - 1; // 移到地面以下
619
619
  }
620
620
  });
621
- this.currentFloorIndex = floorIndex;
622
621
  };
623
622
 
624
623
  /**
@@ -630,10 +629,10 @@ KimapSDK.prototype.showAllFloors = function() {
630
629
  var self = this;
631
630
  this.floorGroups.forEach(function(group, index) {
632
631
  group.visible = true;
632
+ // 恢复原始世界 Y 偏移(含间隙)
633
633
  var originalY = self.core.floorOriginalY[index] || 0;
634
634
  group.position.y = originalY;
635
635
  });
636
- this.currentFloorIndex = 0;
637
636
  };
638
637
 
639
638
  /**
@@ -901,33 +900,35 @@ KimapSDK.prototype.loadKMData = function() {
901
900
  return Promise.resolve({ success: false, message: '未配置objUrl' });
902
901
  }
903
902
 
904
- var objUrl = this.config.objUrl;
905
- var isArray = Array.isArray(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);
903
+ var objUrls = this.config.objUrl;
904
+ var isMulti = Array.isArray(objUrls);
905
+
906
+ // 多楼层:每个楼层独立加载自己的 kidata 家具 → 对应的 floorGroup
907
+ if (isMulti) {
908
+ var floorIndex = 0;
909
+ var totalLoaded = 0;
910
+ var totalFailed = 0;
911
+
912
+ return objUrls.reduce(function(promise, url) {
913
+ return promise.then(function() {
914
+ return self._loadSingleFloorKidata(url, floorIndex++);
915
+ }).then(function(result) {
916
+ totalLoaded += result.loaded;
917
+ totalFailed += result.failed;
918
+ });
919
+ }, Promise.resolve()).then(function() {
920
+ console.log('✅ 多楼层家具加载完成:成功 ' + totalLoaded + ' 个,失败 ' + totalFailed + ' 个');
921
+ return { success: true, loaded: totalLoaded, failed: totalFailed };
922
+ }).catch(function(error) {
923
+ console.error('❌ 多楼层家具加载失败:', error.message);
924
+ return { success: false, error: error.message };
923
925
  });
924
926
  }
925
927
 
926
- // 单楼层模式:沿用原有逻辑
927
- var firstUrl = objUrl;
928
- var lastSlashIndex = firstUrl.lastIndexOf('/');
929
- var directory = lastSlashIndex >= 0 ? firstUrl.substring(0, lastSlashIndex) : '';
930
- var fileName = firstUrl.split('/').pop().replace(/\.(obj|kimap)$/i, '');
928
+ // 单楼层:原逻辑
929
+ var lastSlashIndex = objUrls.lastIndexOf('/');
930
+ var directory = lastSlashIndex >= 0 ? objUrls.substring(0, lastSlashIndex) : '';
931
+ var fileName = objUrls.split('/').pop().replace(/\.(obj|kimap)$/i, '');
931
932
  var kidataUrl = directory + '/' + fileName + '.kidata';
932
933
 
933
934
  return fetch(kidataUrl)
@@ -939,10 +940,14 @@ KimapSDK.prototype.loadKMData = function() {
939
940
  var decrypted = self._decryptKidata(encryptedData);
940
941
  var kidataObj = JSON.parse(decrypted);
941
942
  self.kidataContent = kidataObj;
942
- if (kidataObj.floors && Array.isArray(kidataObj.floors) && kidataObj.floors.length > 0) {
943
- self.projectLayers = kidataObj.floors[0].layers;
943
+
944
+ if (kidataObj.floors && Array.isArray(kidataObj.floors)) {
945
+ if (!self.projectLayers && kidataObj.floors.length > 0) {
946
+ self.projectLayers = kidataObj.floors[0].layers;
947
+ }
944
948
  }
945
- return self._loadFurnitureModels(kidataObj);
949
+
950
+ return self._loadFurnitureModels(kidataObj, -1); // -1 = 使用全局 furnitureGroup
946
951
  })
947
952
  .then(function(result) {
948
953
  console.log('✅ 家具加载完成:成功 ' + result.loaded + ' 个,失败 ' + result.failed + ' 个');
@@ -955,15 +960,18 @@ KimapSDK.prototype.loadKMData = function() {
955
960
  };
956
961
 
957
962
  /**
958
- * 多楼层:为指定楼层加载 kidata(含家具 + 文字)
963
+ * 多楼层专用:加载单个楼层的 kidata 家具到对应 floorGroup
964
+ * @param {string} floorUrl - 该楼层的模型 URL
965
+ * @param {number} floorIndex - 楼层索引
966
+ * @returns {Promise}
959
967
  * @private
960
968
  */
961
- KimapSDK.prototype._loadKidataForFloor = function(url, floorIndex) {
969
+ KimapSDK.prototype._loadSingleFloorKidata = function(floorUrl, floorIndex) {
962
970
  var self = this;
963
971
 
964
- var lastSlashIndex = url.lastIndexOf('/');
965
- var directory = lastSlashIndex >= 0 ? url.substring(0, lastSlashIndex) : '';
966
- var fileName = url.split('/').pop().replace(/\.(obj|kimap)$/i, '');
972
+ var lastSlashIndex = floorUrl.lastIndexOf('/');
973
+ var directory = lastSlashIndex >= 0 ? floorUrl.substring(0, lastSlashIndex) : '';
974
+ var fileName = floorUrl.split('/').pop().replace(/\.(obj|kimap)$/i, '');
967
975
  var kidataUrl = directory + '/' + fileName + '.kidata';
968
976
 
969
977
  return fetch(kidataUrl)
@@ -975,108 +983,16 @@ KimapSDK.prototype._loadKidataForFloor = function(url, floorIndex) {
975
983
  var decrypted = self._decryptKidata(encryptedData);
976
984
  var kidataObj = JSON.parse(decrypted);
977
985
 
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
- });
986
+ // 保存第一层 kidata 供路径规划使用
987
+ if (floorIndex === 0) {
988
+ self.kidataContent = kidataObj;
989
+ if (kidataObj.floors && Array.isArray(kidataObj.floors) && !self.projectLayers) {
990
+ self.projectLayers = kidataObj.floors[0].layers;
991
+ }
1044
992
  }
1045
- });
1046
- }
1047
993
 
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
994
+ return self._loadFurnitureModels(kidataObj, floorIndex);
1072
995
  });
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
996
  };
1081
997
 
1082
998
  KimapSDK.prototype._decryptKidata = function(encrypted) {
@@ -1098,29 +1014,53 @@ KimapSDK.prototype._decryptKidata = function(encrypted) {
1098
1014
  }
1099
1015
  };
1100
1016
 
1101
- KimapSDK.prototype._loadFurnitureModels = function(kidataObj, targetGroup) {
1017
+ KimapSDK.prototype._loadFurnitureModels = function(kidataObj, floorIndex) {
1102
1018
  var self = this;
1019
+ var isMultiFloorMode = floorIndex !== undefined && floorIndex >= 0;
1103
1020
 
1104
1021
  if (!kidataObj.furnitures || kidataObj.furnitures.length === 0) {
1105
1022
  return Promise.resolve({ loaded: 0, failed: 0 });
1106
1023
  }
1107
1024
 
1108
- // 创建家具组(如果不存在)
1109
- if (!this.furnitureGroup) {
1110
- this.furnitureGroup = new THREE.Group();
1111
- this.furnitureGroup.name = 'furnitures';
1112
- this.core.scene.add(this.furnitureGroup);
1113
- }
1025
+ var furnitureGroup;
1114
1026
 
1115
- // 多楼层模式:家具加载到指定楼层的子Group(随楼层移动)
1116
- // 单楼层模式:家具加载到场景家具组(独立存在)
1117
- var target = targetGroup || this.furnitureGroup;
1027
+ if (isMultiFloorMode) {
1028
+ // 多楼层模式:家具放进对应楼层的 furnitureGroup(作为 floorGroup 子级)
1029
+ if (!this.floorFurnitureGroups[floorIndex]) {
1030
+ furnitureGroup = new THREE.Group();
1031
+ furnitureGroup.name = 'furnitures';
1032
+ this.floorFurnitureGroups[floorIndex] = furnitureGroup;
1033
+ var floorGroup = this.core.floorGroups[floorIndex];
1034
+ if (floorGroup) floorGroup.add(furnitureGroup);
1035
+ } else {
1036
+ furnitureGroup = this.floorFurnitureGroups[floorIndex];
1037
+ // 清空现有家具
1038
+ while (furnitureGroup.children.length > 0) {
1039
+ var child = furnitureGroup.children[0];
1040
+ furnitureGroup.remove(child);
1041
+ if (child.geometry) child.geometry.dispose();
1042
+ if (child.material) {
1043
+ if (Array.isArray(child.material)) {
1044
+ child.material.forEach(function(m) { m.dispose(); });
1045
+ } else {
1046
+ child.material.dispose();
1047
+ }
1048
+ }
1049
+ }
1050
+ }
1051
+ } else {
1052
+ // 单楼层模式:家具放进全局 furnitureGroup(直接加到 scene)
1053
+ if (!this.furnitureGroup) {
1054
+ this.furnitureGroup = new THREE.Group();
1055
+ this.furnitureGroup.name = 'furnitures';
1056
+ this.core.scene.add(this.furnitureGroup);
1057
+ }
1058
+ furnitureGroup = this.furnitureGroup;
1118
1059
 
1119
- // 单楼层模式:清空场景家具组
1120
- if (!targetGroup) {
1121
- while (this.furnitureGroup.children.length > 0) {
1122
- var child = this.furnitureGroup.children[0];
1123
- this.furnitureGroup.remove(child);
1060
+ // 清空现有家具
1061
+ while (furnitureGroup.children.length > 0) {
1062
+ var child = furnitureGroup.children[0];
1063
+ furnitureGroup.remove(child);
1124
1064
  if (child.geometry) child.geometry.dispose();
1125
1065
  if (child.material) {
1126
1066
  if (Array.isArray(child.material)) {
@@ -1138,11 +1078,13 @@ KimapSDK.prototype._loadFurnitureModels = function(kidataObj, targetGroup) {
1138
1078
  var loadedCount = 0;
1139
1079
  var failedCount = 0;
1140
1080
 
1081
+ // 加载每个家具模型
1082
+ var self2 = this;
1141
1083
  kidataObj.furnitures.forEach(function(furniture) {
1142
- var promise = self._loadSingleFurniture(furniture, serverUrl)
1084
+ var promise = self2._loadSingleFurniture(furniture, serverUrl)
1143
1085
  .then(function(mesh) {
1144
1086
  if (mesh) {
1145
- target.add(mesh);
1087
+ furnitureGroup.add(mesh);
1146
1088
  loadedCount++;
1147
1089
  } else {
1148
1090
  failedCount++;
@@ -62,12 +62,11 @@ function SceneCore(config) {
62
62
 
63
63
  // 多楼层支持(不影响单楼层原有渲染路径)
64
64
  this.floorGroups = [];
65
- this.floorFurnitureGroups = []; // 每层独立的家具Group
66
- this.floorTextGroups = []; // 每层独立的文字Group
65
+ this.floorTextGroups = []; // 每个楼层的文字组
66
+ this.floorTextElements = []; // 每个楼层的文字数据
67
67
  this.floorHeight = 3; // 米
68
68
  this.isMultiFloor = Array.isArray(config.objUrl);
69
69
  this.floorOriginalY = []; // 每个楼层的原始世界Y偏移(含间隙)
70
- this.onFloorModelsLoaded = null; // 回调:所有楼层模型加载完毕后触发
71
70
  }
72
71
 
73
72
  /**
@@ -100,12 +99,6 @@ SceneCore.prototype.init = function() {
100
99
  var OBJLoader = loaders[0];
101
100
  var MTLLoader = loaders[1];
102
101
  return self.loadMap(OBJLoader, MTLLoader);
103
- })
104
- .then(function() {
105
- // 通知 onFloorModelsLoaded(如果已注册)
106
- if (typeof self.onFloorModelsLoaded === 'function') {
107
- self.onFloorModelsLoaded();
108
- }
109
102
  });
110
103
  };
111
104
 
@@ -277,11 +270,10 @@ SceneCore.prototype.loadMap = function(OBJLoader, MTLLoader) {
277
270
  };
278
271
 
279
272
  /**
280
- * 多楼层:依次加载多个模型(两阶段:先加载添加,再统一算中心对齐)
273
+ * 多楼层:依次加载多个模型(新增能力,不改变单楼层逻辑)
281
274
  * @private
282
- * @param {Function} onAllLoaded - 所有楼层模型加载完毕后的回调(此时 floorGroups 已就绪)
283
275
  */
284
- SceneCore.prototype._loadMultiFloorMaps = function(OBJLoader, MTLLoader, objUrls, onAllLoaded) {
276
+ SceneCore.prototype._loadMultiFloorMaps = function(OBJLoader, MTLLoader, objUrls) {
285
277
  var self = this;
286
278
  var floorIndex = 0;
287
279
 
@@ -290,16 +282,8 @@ SceneCore.prototype._loadMultiFloorMaps = function(OBJLoader, MTLLoader, objUrls
290
282
  return self._loadSingleMapWithFloorIndex(OBJLoader, MTLLoader, url, floorIndex++);
291
283
  });
292
284
  }, Promise.resolve()).then(function() {
293
- // 阶段二:所有楼层加载完毕后,按联合包围盒中心对齐 XZ
294
285
  if (self.floorGroups.length > 1) {
295
- self._alignAllFloorsToCenter();
296
- }
297
- // 通知 SDK 层:楼层模型已就绪,可以加载各层 kidata 了
298
- if (typeof onAllLoaded === 'function') {
299
- onAllLoaded();
300
- }
301
- if (typeof self.onFloorModelsLoaded === 'function') {
302
- self.onFloorModelsLoaded();
286
+ self._adjustCameraToAllFloors();
303
287
  }
304
288
  });
305
289
  };
@@ -391,6 +375,13 @@ SceneCore.prototype._loadKimapFileToGroup = function(kimapUrl, themeUrl, objLoad
391
375
  var object = objLoader.parse(objContent);
392
376
  self._processLoadedModelToGroup(object, materials, colorMap, floorGroup, floorIndex);
393
377
 
378
+ // 每个楼层从自己的 .kimap 文件中提取并渲染文字
379
+ var textElements = extractTextElementsFromOBJ(objContent);
380
+ if (textElements && textElements.length > 0) {
381
+ self.floorTextElements[floorIndex] = textElements;
382
+ self.renderTextElementsToFloor(floorIndex, textElements);
383
+ }
384
+
394
385
  resolve();
395
386
  } catch (error) {
396
387
  reject(error);
@@ -427,38 +418,34 @@ SceneCore.prototype._processLoadedModelToGroup = function(object, materials, col
427
418
  var self = this;
428
419
  floorGroup.add(object);
429
420
 
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
-
437
421
  var box = new THREE.Box3().setFromObject(object);
438
422
  var boxHeight = box.max.y - box.min.y;
439
423
 
440
- // 记录每层的包围盒(供两阶段定位使用)
441
- if (!self._floorBoxes) self._floorBoxes = [];
442
- self._floorBoxes[floorIndex] = box;
443
-
444
424
  // 逐层累积高度:计算该楼层在 ALL 模式下的世界 Y 偏移
445
425
  var floorY = 0;
446
426
  for (var i = 0; i < floorIndex; i++) {
447
- var prevBox = self._floorBoxes[i];
448
- if (prevBox) floorY += (prevBox.max.y - prevBox.min.y);
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
+ }
449
432
  }
450
433
 
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(堆叠)
434
+ // 所有楼层的 object 底部(box.min.y)对齐到本地 Y=0,统一基准
454
435
  object.position.set(-box.min.x, -box.min.y, -box.min.z);
455
436
 
456
- // 初始 Y 偏移(XZ 后续由 _alignAllFloorsToCenter 统一调整)
457
- floorGroup.position.set(0, floorY, 0);
437
+ // ALL 模式时 floorGroup.position.y 持有累积偏移(含间隙)
438
+ floorGroup.position.y = floorY;
458
439
 
459
- // 记录原始 Y 偏移(showSingleFloor/showAllFloors 使用)
440
+ // 记录每个楼层的原始世界 Y 偏移(单层模式时用于将选中层拽回 Y=0)
460
441
  self.floorOriginalY[floorIndex] = floorY;
461
442
 
443
+ // 为该楼层创建独立的文字组
444
+ var textGroup = new THREE.Group();
445
+ textGroup.name = 'texts';
446
+ floorGroup.add(textGroup);
447
+ self.floorTextGroups[floorIndex] = textGroup;
448
+
462
449
  // 复用 backup 的材质处理策略,不改渲染风格
463
450
  object.traverse(function(child) {
464
451
  if (child.name === 'HiddenGroundPlane' || child.name.includes('InteractionLayer')) {
@@ -471,34 +458,6 @@ SceneCore.prototype._processLoadedModelToGroup = function(object, materials, col
471
458
  });
472
459
  };
473
460
 
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
-
502
461
  /**
503
462
  * 多楼层:相机看全楼层
504
463
  * @private
@@ -1052,6 +1011,57 @@ SceneCore.prototype.createTextSprite = function(options) {
1052
1011
  return sprite;
1053
1012
  };
1054
1013
 
1014
+ /**
1015
+ * 多楼层专用:渲染文字到指定楼层的 textGroup
1016
+ */
1017
+ SceneCore.prototype.renderTextElementsToFloor = function(floorIndex, textElements) {
1018
+ var self = this;
1019
+
1020
+ if (!textElements || textElements.length === 0) return;
1021
+
1022
+ var floorGroup = this.floorGroups[floorIndex];
1023
+ var textGroup = this.floorTextGroups[floorIndex];
1024
+ if (!floorGroup || !textGroup) return;
1025
+
1026
+ // 清空该楼层现有文字
1027
+ while (textGroup.children.length > 0) {
1028
+ var child = textGroup.children[0];
1029
+ textGroup.remove(child);
1030
+ if (child.material && child.material.map) child.material.map.dispose();
1031
+ if (child.material) child.material.dispose();
1032
+ }
1033
+
1034
+ textElements.forEach(function(textEl) {
1035
+ if (!textEl.points || textEl.points.length === 0) return;
1036
+
1037
+ var point = textEl.points[0];
1038
+ var x = point.x / 100;
1039
+ var z = point.y / 100;
1040
+ var LOGICAL_WALL_HEIGHT = 3000;
1041
+ var RENDER_WALL_HEIGHT = 0.375;
1042
+ var elevationMm = textEl.elevation || 8500;
1043
+ var y = (elevationMm / LOGICAL_WALL_HEIGHT) * RENDER_WALL_HEIGHT;
1044
+ var TEXT_SCALE_FACTOR = 2.52;
1045
+
1046
+ var sprite = self.createTextSprite({
1047
+ content: textEl.content || '',
1048
+ fontSize: (textEl.fontSize || 16) * TEXT_SCALE_FACTOR,
1049
+ color: textEl.color || '#000000',
1050
+ fontFamily: textEl.fontFamily || 'Arial, sans-serif',
1051
+ bold: textEl.bold || false,
1052
+ italic: textEl.italic || false,
1053
+ borderColor: textEl.borderColor || '#ADD8E6',
1054
+ backgroundColor: textEl.backgroundColor || '#FFFFFF',
1055
+ backgroundOpacity: textEl.backgroundOpacity !== undefined ? textEl.backgroundOpacity : 0.8,
1056
+ padding: textEl.padding || 8
1057
+ });
1058
+
1059
+ sprite.position.set(x, y, z);
1060
+ sprite.userData = { type: 'text', elementId: textEl.id };
1061
+ textGroup.add(sprite);
1062
+ });
1063
+ };
1064
+
1055
1065
  /**
1056
1066
  * 渲染文本元素到场景
1057
1067
  */