@kimap/indoor-positioning-sdk-vue2 4.2.9 → 4.3.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": "4.2.9",
3
+ "version": "4.3.2",
4
4
  "description": "Vue2自包含室内定位SDK - 完全兼容Webpack3+Babel6 | Vue2 Self-Contained Indoor Positioning SDK",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -193,6 +193,90 @@
193
193
  return colorMap;
194
194
  }
195
195
 
196
+ // 从OBJ内容中提取文本元素
197
+ function extractTextElementsFromOBJ(objContent) {
198
+ var textElements = [];
199
+ var lines = objContent.split('\n');
200
+ var currentText = null;
201
+
202
+ for (var i = 0; i < lines.length; i++) {
203
+ var line = lines[i].trim();
204
+
205
+ if (line.indexOf('# ===== Text Element =====') === 0) {
206
+ currentText = {};
207
+ } else if (line.indexOf('# ===== End Text Element =====') === 0 && currentText) {
208
+ textElements.push(currentText);
209
+ currentText = null;
210
+ } else if (currentText) {
211
+ if (line.indexOf('# TEXT_ID') === 0) {
212
+ currentText.id = line.split(' ')[2];
213
+ } else if (line.indexOf('# TEXT_CONTENT') === 0) {
214
+ currentText.content = line.substring(line.indexOf('TEXT_CONTENT') + 13).trim();
215
+ } else if (line.indexOf('# TEXT_POSITION') === 0) {
216
+ var parts = line.split(' ');
217
+ currentText.points = [{ x: parseFloat(parts[2]), y: parseFloat(parts[3]) }];
218
+ } else if (line.indexOf('# TEXT_ELEVATION') === 0) {
219
+ currentText.elevation = parseFloat(line.split(' ')[2]);
220
+ } else if (line.indexOf('# TEXT_FONT_SIZE') === 0) {
221
+ currentText.fontSize = parseFloat(line.split(' ')[2]);
222
+ } else if (line.indexOf('# TEXT_COLOR') === 0) {
223
+ currentText.color = line.split(' ')[2];
224
+ } else if (line.indexOf('# TEXT_FONT_FAMILY') === 0) {
225
+ currentText.fontFamily = line.substring(line.indexOf('TEXT_FONT_FAMILY') + 17).trim();
226
+ } else if (line.indexOf('# TEXT_BOLD') === 0) {
227
+ currentText.bold = line.split(' ')[2] === 'true';
228
+ } else if (line.indexOf('# TEXT_ITALIC') === 0) {
229
+ currentText.italic = line.split(' ')[2] === 'true';
230
+ } else if (line.indexOf('# TEXT_BORDER_COLOR') === 0) {
231
+ currentText.borderColor = line.split(' ')[2];
232
+ } else if (line.indexOf('# TEXT_BG_COLOR') === 0) {
233
+ currentText.backgroundColor = line.split(' ')[2];
234
+ } else if (line.indexOf('# TEXT_BG_OPACITY') === 0) {
235
+ currentText.backgroundOpacity = parseFloat(line.split(' ')[2]);
236
+ } else if (line.indexOf('# TEXT_PADDING') === 0) {
237
+ currentText.padding = parseFloat(line.split(' ')[2]);
238
+ }
239
+ }
240
+ }
241
+
242
+ console.log('[Parsers] 提取到的文本元素:', textElements.length, '个');
243
+ return textElements;
244
+ }
245
+
246
+ // 从OBJ内容中提取墙体透明度信息
247
+ function extractWallOpacityFromOBJ(objContent) {
248
+ var wallOpacity = {};
249
+ var lines = objContent.split('\n');
250
+ var currentWallId = null;
251
+
252
+ for (var i = 0; i < lines.length; i++) {
253
+ var line = lines[i].trim();
254
+
255
+ if (line.indexOf('o Wall_') === 0) {
256
+ // 提取墙体ID(格式:Wall_<id>_seg<n>)
257
+ var match = line.match(/Wall_([^_]+)_seg/);
258
+ if (match) {
259
+ currentWallId = match[1];
260
+ }
261
+ } else if (currentWallId && line.indexOf('# WALL_OPACITY') === 0) {
262
+ var opacity = parseFloat(line.split(' ')[2]);
263
+ if (!wallOpacity[currentWallId]) {
264
+ wallOpacity[currentWallId] = {};
265
+ }
266
+ wallOpacity[currentWallId].opacity = opacity;
267
+ } else if (currentWallId && line.indexOf('# WALL_COLOR') === 0) {
268
+ var color = line.split(' ')[2];
269
+ if (!wallOpacity[currentWallId]) {
270
+ wallOpacity[currentWallId] = {};
271
+ }
272
+ wallOpacity[currentWallId].color = color;
273
+ }
274
+ }
275
+
276
+ console.log('[Parsers] 提取到的墙体透明度:', Object.keys(wallOpacity).length, '个墙体');
277
+ return wallOpacity;
278
+ }
279
+
196
280
  /**
197
281
  * 3D 场景核心
198
282
  */
@@ -548,9 +632,28 @@
548
632
  console.log('提取到颜色信息:', colorMap);
549
633
  }
550
634
 
635
+ // 提取文本元素
636
+ var textElements = extractTextElementsFromOBJ(objContent);
637
+ if (textElements && textElements.length > 0) {
638
+ console.log('✅ 从OBJ提取到文本元素:', textElements.length, '个');
639
+ self._textElements = textElements;
640
+ }
641
+
642
+ // 提取墙体透明度信息
643
+ var wallOpacity = extractWallOpacityFromOBJ(objContent);
644
+ if (wallOpacity && Object.keys(wallOpacity).length > 0) {
645
+ console.log('✅ 从OBJ提取到墙体透明度:', Object.keys(wallOpacity).length, '个墙体');
646
+ self._wallOpacity = wallOpacity;
647
+ }
648
+
551
649
  var object = objLoader.parse(objContent);
552
650
  self.processModel(object, colorMap);
553
651
 
652
+ // 渲染文本元素
653
+ if (self._textElements && self._textElements.length > 0) {
654
+ self.renderTextElements(self._textElements);
655
+ }
656
+
554
657
  console.log('=== Kimap模型加载成功 ===');
555
658
  self.logModelInfo();
556
659
  resolve();
@@ -595,6 +698,7 @@
595
698
  };
596
699
 
597
700
  SceneCore.prototype.processModel = function(object, colorMap) {
701
+ var self = this;
598
702
  this.mapModel = object;
599
703
  colorMap = colorMap || {};
600
704
 
@@ -632,6 +736,16 @@
632
736
  } else if (child.material.name.startsWith('Wall_')) {
633
737
  roughness = 0.25;
634
738
  metalness = 0;
739
+
740
+ // 从OBJ提取的墙体透明度数据中读取
741
+ if (self._wallOpacity) {
742
+ var wallId = child.material.name.replace('Wall_', '');
743
+ if (self._wallOpacity[wallId]) {
744
+ opacity = self._wallOpacity[wallId].opacity;
745
+ transparent = opacity < 1.0;
746
+ console.log('[SceneCore] 应用墙体透明度:', wallId, '=', opacity);
747
+ }
748
+ }
635
749
  } else if (child.material.name.startsWith('Shape_') ||
636
750
  child.material.name.startsWith('Stair_')) {
637
751
  roughness = 0.25;
@@ -854,6 +968,183 @@
854
968
  this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
855
969
  };
856
970
 
971
+ /**
972
+ * 创建文本精灵(用于3D场景中显示文本)
973
+ */
974
+ SceneCore.prototype.createTextSprite = function(options) {
975
+ var content = options.content || '';
976
+ var fontSize = options.fontSize || 16;
977
+ var color = options.color || '#000000';
978
+ var fontFamily = options.fontFamily || 'Arial, sans-serif';
979
+ var bold = options.bold || false;
980
+ var italic = options.italic || false;
981
+ var borderColor = options.borderColor || '#87CEEB';
982
+ var backgroundColor = options.backgroundColor || '#FFFFFF';
983
+ var backgroundOpacity = options.backgroundOpacity !== undefined ? options.backgroundOpacity : 0.8;
984
+ var padding = options.padding || 8;
985
+
986
+ // 创建高分辨率canvas用于文本纹理
987
+ var canvas = document.createElement('canvas');
988
+ var context = canvas.getContext('2d');
989
+
990
+ // 使用更大的canvas尺寸以提高清晰度
991
+ canvas.width = 1024;
992
+ canvas.height = 512;
993
+
994
+ // 配置字体(放大4倍以提高质量)
995
+ var scaledFontSize = fontSize * 4;
996
+ var fontStyle = (italic ? 'italic ' : '') + (bold ? 'bold ' : '') + scaledFontSize + 'px ' + fontFamily;
997
+ context.font = fontStyle;
998
+ context.textAlign = 'center';
999
+ context.textBaseline = 'middle';
1000
+
1001
+ // 测量文本
1002
+ var metrics = context.measureText(content);
1003
+ var textWidth = metrics.width;
1004
+ var textHeight = scaledFontSize * 1.2;
1005
+
1006
+ // 计算背景尺寸
1007
+ var scaledPadding = padding * 4;
1008
+ var bgWidth = textWidth + scaledPadding * 2;
1009
+ var bgHeight = textHeight + scaledPadding * 2;
1010
+
1011
+ // 计算居中位置
1012
+ var centerX = canvas.width / 2;
1013
+ var centerY = canvas.height / 2;
1014
+ var bgX = centerX - bgWidth / 2;
1015
+ var bgY = centerY - bgHeight / 2;
1016
+
1017
+ // 绘制阴影效果以增强可读性
1018
+ context.shadowColor = 'rgba(0, 0, 0, 0.5)';
1019
+ context.shadowBlur = 8;
1020
+ context.shadowOffsetX = 2;
1021
+ context.shadowOffsetY = 2;
1022
+
1023
+ // 绘制背景
1024
+ context.fillStyle = backgroundColor;
1025
+ context.globalAlpha = Math.max(backgroundOpacity, 0.9); // 确保背景足够不透明
1026
+ context.fillRect(bgX, bgY, bgWidth, bgHeight);
1027
+
1028
+ // 重置阴影
1029
+ context.shadowColor = 'transparent';
1030
+ context.shadowBlur = 0;
1031
+ context.shadowOffsetX = 0;
1032
+ context.shadowOffsetY = 0;
1033
+
1034
+ // 绘制边框(加粗以提高可见性)
1035
+ context.strokeStyle = borderColor;
1036
+ context.lineWidth = 6;
1037
+ context.globalAlpha = 1;
1038
+ context.strokeRect(bgX, bgY, bgWidth, bgHeight);
1039
+
1040
+ // 绘制文本轮廓以增强对比度
1041
+ context.strokeStyle = color === '#000000' ? '#FFFFFF' : '#000000';
1042
+ context.lineWidth = 2;
1043
+ context.strokeText(content, centerX, centerY);
1044
+
1045
+ // 绘制文本
1046
+ context.fillStyle = color;
1047
+ context.fillText(content, centerX, centerY);
1048
+
1049
+ // 创建纹理和sprite
1050
+ var texture = new THREE.Texture(canvas);
1051
+ texture.needsUpdate = true;
1052
+ texture.minFilter = THREE.LinearFilter;
1053
+ texture.magFilter = THREE.LinearFilter;
1054
+
1055
+ var material = new THREE.SpriteMaterial({
1056
+ map: texture,
1057
+ transparent: true,
1058
+ depthTest: true,
1059
+ depthWrite: false,
1060
+ sizeAttenuation: true // 启用距离衰减,使文本大小随距离变化
1061
+ });
1062
+
1063
+ var sprite = new THREE.Sprite(material);
1064
+
1065
+ // 增大缩放以提高可读性(与编辑器一致)
1066
+ var scale = 3;
1067
+ sprite.scale.set(scale, scale / 2, 1);
1068
+
1069
+ // 设置渲染顺序为最大值,确保文本始终在最上层
1070
+ sprite.renderOrder = 999999;
1071
+
1072
+ return sprite;
1073
+ };
1074
+
1075
+ /**
1076
+ * 渲染文本元素到场景
1077
+ */
1078
+ SceneCore.prototype.renderTextElements = function(textElements) {
1079
+ var self = this;
1080
+
1081
+ if (!textElements || textElements.length === 0) {
1082
+ return;
1083
+ }
1084
+
1085
+ // 创建文本组(如果不存在)
1086
+ if (!this.textGroup) {
1087
+ this.textGroup = new THREE.Group();
1088
+ this.textGroup.name = 'texts';
1089
+ this.scene.add(this.textGroup);
1090
+ }
1091
+
1092
+ // 清空现有文本
1093
+ while (this.textGroup.children.length > 0) {
1094
+ var child = this.textGroup.children[0];
1095
+ this.textGroup.remove(child);
1096
+
1097
+ // 释放资源
1098
+ if (child.material && child.material.map) {
1099
+ child.material.map.dispose();
1100
+ }
1101
+ if (child.material) {
1102
+ child.material.dispose();
1103
+ }
1104
+ }
1105
+
1106
+ // 渲染每个文本元素
1107
+ textElements.forEach(function(textEl) {
1108
+ if (!textEl.points || textEl.points.length === 0) return;
1109
+
1110
+ var point = textEl.points[0];
1111
+ var x = point.x / 100; // Canvas pixels to 3D meters
1112
+ var z = point.y / 100;
1113
+
1114
+ // 文本elevation单位是mm,需要按照墙面比例缩放(与编辑器预览一致)
1115
+ var LOGICAL_WALL_HEIGHT = 3000; // 逻辑墙高3000mm(3米)
1116
+ var RENDER_WALL_HEIGHT = 0.375; // 渲染墙高0.375米
1117
+ var elevationMm = textEl.elevation || 8500; // 默认8500mm(850cm)
1118
+ var y = (elevationMm / LOGICAL_WALL_HEIGHT) * RENDER_WALL_HEIGHT;
1119
+
1120
+ console.log('[SceneCore] 渲染文本元素:', {
1121
+ content: textEl.content,
1122
+ elevationMm: elevationMm,
1123
+ calculatedY: y,
1124
+ position: { x: x, y: y, z: z }
1125
+ });
1126
+
1127
+ var sprite = self.createTextSprite({
1128
+ content: textEl.content || '',
1129
+ fontSize: textEl.fontSize || 16,
1130
+ color: textEl.color || '#000000',
1131
+ fontFamily: textEl.fontFamily || 'Arial, sans-serif',
1132
+ bold: textEl.bold || false,
1133
+ italic: textEl.italic || false,
1134
+ borderColor: textEl.borderColor || '#87CEEB',
1135
+ backgroundColor: textEl.backgroundColor || '#FFFFFF',
1136
+ backgroundOpacity: textEl.backgroundOpacity !== undefined ? textEl.backgroundOpacity : 0.8,
1137
+ padding: textEl.padding || 8
1138
+ });
1139
+
1140
+ sprite.position.set(x, y, z);
1141
+ sprite.userData = { type: 'text', elementId: textEl.id };
1142
+ self.textGroup.add(sprite);
1143
+ });
1144
+
1145
+ console.log('[SceneCore] ✅ 文本元素渲染完成:', textElements.length, '个');
1146
+ };
1147
+
857
1148
  SceneCore.prototype.destroy = function() {
858
1149
  if (this.scene) {
859
1150
  this.scene.traverse(function(object) {
@@ -1575,6 +1866,7 @@
1575
1866
  })
1576
1867
  .then(function(result) {
1577
1868
  console.log('✅ 3D家具模型加载完成:', result);
1869
+
1578
1870
  return { success: true, loaded: result.loaded, failed: result.failed };
1579
1871
  })
1580
1872
  .catch(function(error) {
@@ -2127,6 +2419,173 @@
2127
2419
  }
2128
2420
  };
2129
2421
 
2422
+ /**
2423
+ * 解析并渲染文本元素
2424
+ * @private
2425
+ */
2426
+ KimapSDK.prototype._parseAndRenderTextElements = function(kidataObj) {
2427
+ var textElements = [];
2428
+
2429
+ // 从floors中提取文本元素
2430
+ if (kidataObj.floors && Array.isArray(kidataObj.floors)) {
2431
+ kidataObj.floors.forEach(function(floor) {
2432
+ if (floor.layers && Array.isArray(floor.layers)) {
2433
+ floor.layers.forEach(function(layer) {
2434
+ if (layer.elements && Array.isArray(layer.elements)) {
2435
+ layer.elements.forEach(function(element) {
2436
+ if (element.type === 'text' && element.visible !== false) {
2437
+ textElements.push(element);
2438
+ }
2439
+ });
2440
+ }
2441
+ });
2442
+ }
2443
+ });
2444
+ }
2445
+
2446
+ console.log('[KimapSDK] 找到', textElements.length, '个文本元素');
2447
+
2448
+ // 调用SceneCore的渲染方法
2449
+ if (textElements.length > 0 && this.core) {
2450
+ this.core.renderTextElements(textElements);
2451
+ }
2452
+ };
2453
+
2454
+ /**
2455
+ * 应用墙面透明度
2456
+ * @private
2457
+ */
2458
+ KimapSDK.prototype._applyWallOpacity = function(kidataObj) {
2459
+ if (!kidataObj || !kidataObj.floors || !this.core || !this.core.scene) {
2460
+ console.log('[KimapSDK] _applyWallOpacity: 缺少必要数据');
2461
+ return;
2462
+ }
2463
+
2464
+ console.log('[KimapSDK] 开始应用墙面透明度...');
2465
+
2466
+ var wallCount = 0;
2467
+ var wallsInKidata = [];
2468
+ var self = this;
2469
+
2470
+ // 收集kidata中的所有墙体数据
2471
+ kidataObj.floors.forEach(function(floor) {
2472
+ if (floor.layers) {
2473
+ floor.layers.forEach(function(layer) {
2474
+ if (layer.elements) {
2475
+ layer.elements.forEach(function(element) {
2476
+ if (element.type === 'wall') {
2477
+ wallsInKidata.push({
2478
+ id: element.id,
2479
+ opacity: element.opacity !== undefined ? element.opacity : 0.85,
2480
+ color: element.color
2481
+ });
2482
+ }
2483
+ });
2484
+ }
2485
+ });
2486
+ }
2487
+ });
2488
+
2489
+ console.log('[KimapSDK] Kidata中墙体数量:', wallsInKidata.length);
2490
+
2491
+ // 如果kidata中没有墙体数据,不需要继续
2492
+ if (wallsInKidata.length === 0) {
2493
+ console.log('[KimapSDK] Kidata中没有墙体数据');
2494
+ return;
2495
+ }
2496
+
2497
+ // 遍历场景中的所有对象,通过材质名称识别墙体
2498
+ this.core.scene.traverse(function(object) {
2499
+ if (object instanceof THREE.Mesh && object.material) {
2500
+ var materialName = object.material.name || '';
2501
+
2502
+ // 通过材质名称识别墙体(Wall_前缀)
2503
+ if (materialName.startsWith('Wall_')) {
2504
+ // 从材质名称中提取墙体ID(格式:Wall_<id>)
2505
+ var wallId = materialName.replace('Wall_', '');
2506
+
2507
+ // 在kidata中查找对应的墙体数据
2508
+ var wallData = wallsInKidata.find(function(w) {
2509
+ return w.id === wallId;
2510
+ });
2511
+
2512
+ if (wallData) {
2513
+ var opacity = wallData.opacity;
2514
+
2515
+ console.log('[KimapSDK] 应用墙体透明度:', {
2516
+ id: wallId,
2517
+ opacity: opacity,
2518
+ materialName: materialName
2519
+ });
2520
+
2521
+ // 应用透明度到材质
2522
+ if (Array.isArray(object.material)) {
2523
+ object.material.forEach(function(mat) {
2524
+ mat.transparent = opacity < 1;
2525
+ mat.opacity = opacity;
2526
+ mat.needsUpdate = true;
2527
+ });
2528
+ } else {
2529
+ object.material.transparent = opacity < 1;
2530
+ object.material.opacity = opacity;
2531
+ object.material.needsUpdate = true;
2532
+ }
2533
+
2534
+ wallCount++;
2535
+ }
2536
+ }
2537
+ }
2538
+ });
2539
+
2540
+ if (wallCount > 0) {
2541
+ console.log('[KimapSDK] ✅ 应用墙面透明度完成:', wallCount, '个墙体');
2542
+ } else {
2543
+ console.log('[KimapSDK] ⚠️ 未找到匹配的墙体对象');
2544
+ }
2545
+ };
2546
+
2547
+ /**
2548
+ * 从kidata内容加载家具模型(不处理文本和墙体透明度)
2549
+ * @param {string} kidataContent - kidata文件内容(加密或未加密)
2550
+ */
2551
+ KimapSDK.prototype.loadKidataForFurniture = function(kidataContent) {
2552
+ var self = this;
2553
+
2554
+ try {
2555
+ // 解密kidata文件
2556
+ var decrypted = self._decryptKidata(kidataContent);
2557
+ var kidataObj = JSON.parse(decrypted);
2558
+
2559
+ // 保存kidata数据供路径规划使用
2560
+ self.kidataContent = kidataObj;
2561
+
2562
+ // 如果kidata包含楼层数据,设置为projectLayers
2563
+ if (kidataObj.floors && Array.isArray(kidataObj.floors)) {
2564
+ console.log('[KimapSDK] kidata包含楼层数据,楼层数量:', kidataObj.floors.length);
2565
+ if (!self.projectLayers && kidataObj.floors.length > 0) {
2566
+ self.projectLayers = kidataObj.floors[0].layers;
2567
+ console.log('[KimapSDK] 从kidata设置楼层数据,图层数量:', self.projectLayers.length);
2568
+ }
2569
+ }
2570
+
2571
+ console.log('[KimapSDK] ✅ Kidata文件解密成功:', {
2572
+ version: kidataObj.version,
2573
+ furnitureCount: kidataObj.furnitures ? kidataObj.furnitures.length : 0,
2574
+ floorCount: kidataObj.floors ? kidataObj.floors.length : 0
2575
+ });
2576
+
2577
+ // 只加载3D家具模型
2578
+ return self._loadFurnitureModels(kidataObj)
2579
+ .then(function(result) {
2580
+ console.log('[KimapSDK] ✅ 3D家具模型加载完成:', result);
2581
+ return { success: true, loaded: result.loaded, failed: result.failed };
2582
+ });
2583
+ } catch (error) {
2584
+ console.error('[KimapSDK] ❌ Kidata加载失败:', error);
2585
+ return Promise.reject(error);
2586
+ }
2587
+ };
2588
+
2130
2589
  KimapSDK.prototype.destroy = function() {
2131
2590
  if (this.animationFrameId) {
2132
2591
  cancelAnimationFrame(this.animationFrameId);
@@ -723,6 +723,7 @@ KimapSDK.prototype.loadKMData = function() {
723
723
  })
724
724
  .then(function(result) {
725
725
  console.log('✅ 3D家具模型加载完成:', result);
726
+
726
727
  return { success: true, loaded: result.loaded, failed: result.failed };
727
728
  })
728
729
  .catch(function(error) {
@@ -967,6 +968,48 @@ KimapSDK.prototype._loadSingleFurniture = function(furniture, serverUrl) {
967
968
  });
968
969
  };
969
970
 
971
+ /**
972
+ * 从kidata内容加载家具模型(不处理文本和墙体透明度)
973
+ * @param {string} kidataContent - kidata文件内容(加密或未加密)
974
+ */
975
+ KimapSDK.prototype.loadKidataForFurniture = function(kidataContent) {
976
+ var self = this;
977
+
978
+ try {
979
+ // 解密kidata文件
980
+ var decrypted = self._decryptKidata(kidataContent);
981
+ var kidataObj = JSON.parse(decrypted);
982
+
983
+ // 保存kidata数据供路径规划使用
984
+ self.kidataContent = kidataObj;
985
+
986
+ // 如果kidata包含楼层数据,设置为projectLayers
987
+ if (kidataObj.floors && Array.isArray(kidataObj.floors)) {
988
+ console.log('[KimapSDK] kidata包含楼层数据,楼层数量:', kidataObj.floors.length);
989
+ if (!self.projectLayers && kidataObj.floors.length > 0) {
990
+ self.projectLayers = kidataObj.floors[0].layers;
991
+ console.log('[KimapSDK] 从kidata设置楼层数据,图层数量:', self.projectLayers.length);
992
+ }
993
+ }
994
+
995
+ console.log('[KimapSDK] ✅ Kidata文件解密成功:', {
996
+ version: kidataObj.version,
997
+ furnitureCount: kidataObj.furnitures ? kidataObj.furnitures.length : 0,
998
+ floorCount: kidataObj.floors ? kidataObj.floors.length : 0
999
+ });
1000
+
1001
+ // 只加载3D家具模型
1002
+ return self._loadFurnitureModels(kidataObj)
1003
+ .then(function(result) {
1004
+ console.log('[KimapSDK] ✅ 3D家具模型加载完成:', result);
1005
+ return { success: true, loaded: result.loaded, failed: result.failed };
1006
+ });
1007
+ } catch (error) {
1008
+ console.error('[KimapSDK] ❌ Kidata加载失败:', error);
1009
+ return Promise.reject(error);
1010
+ }
1011
+ };
1012
+
970
1013
  /**
971
1014
  * 销毁 SDK
972
1015
  */
@@ -26,6 +26,8 @@ var decryptTheme = crypto.decryptTheme;
26
26
  var extractBorderFromOBJ = parsers.extractBorderFromOBJ;
27
27
  var extractMapConfigFromOBJ = parsers.extractMapConfigFromOBJ;
28
28
  var extractColorsFromOBJ = parsers.extractColorsFromOBJ;
29
+ var extractTextElementsFromOBJ = parsers.extractTextElementsFromOBJ;
30
+ var extractWallOpacityFromOBJ = parsers.extractWallOpacityFromOBJ;
29
31
 
30
32
  /**
31
33
  * SceneCore 构造函数
@@ -369,12 +371,32 @@ SceneCore.prototype._loadKimapFile = function(kimapUrl, themeUrl, objLoader, mtl
369
371
  if (!materials) {
370
372
  console.log('[SceneCore] 提取到颜色信息:', colorMap);
371
373
  }
374
+
375
+ // 提取文本元素
376
+ var textElements = extractTextElementsFromOBJ(objContent);
377
+ if (textElements && textElements.length > 0) {
378
+ console.log('[SceneCore] ✅ 从OBJ提取到文本元素:', textElements.length, '个');
379
+ self._textElements = textElements;
380
+ }
381
+
382
+ // 提取墙体透明度信息
383
+ var wallOpacity = extractWallOpacityFromOBJ(objContent);
384
+ if (wallOpacity && Object.keys(wallOpacity).length > 0) {
385
+ console.log('[SceneCore] ✅ 从OBJ提取到墙体透明度:', Object.keys(wallOpacity).length, '个墙体');
386
+ self._wallOpacity = wallOpacity;
387
+ }
372
388
 
373
389
  // 解析OBJ内容
374
390
  var object = objLoader.parse(objContent);
375
391
  console.log('[SceneCore] ✓ OBJ解析完成,处理模型...');
376
392
 
377
393
  self._processLoadedModel(object, materials, colorMap);
394
+
395
+ // 渲染文本元素
396
+ if (self._textElements && self._textElements.length > 0) {
397
+ self.renderTextElements(self._textElements);
398
+ }
399
+
378
400
  resolve();
379
401
  } catch (error) {
380
402
  console.error('[SceneCore] ✗ Kimap处理失败:', error.message);
@@ -512,6 +534,8 @@ SceneCore.prototype._processLoadedModel = function(object, materials, colorMap)
512
534
  * @private
513
535
  */
514
536
  SceneCore.prototype._processMaterial = function(child, materials, colorMap) {
537
+ var self = this;
538
+
515
539
  if (child.material && child.material.name) {
516
540
  // 已有材质,转换为PBR材质
517
541
  var mtlColor = child.material.color ? child.material.color.getHex() : 0xdddddd;
@@ -529,6 +553,16 @@ SceneCore.prototype._processMaterial = function(child, materials, colorMap) {
529
553
  } else if (child.material.name.startsWith('Wall_')) {
530
554
  roughness = 0.25;
531
555
  metalness = 0;
556
+
557
+ // 从OBJ提取的墙体透明度数据中读取
558
+ if (self._wallOpacity) {
559
+ var wallId = child.material.name.replace('Wall_', '');
560
+ if (self._wallOpacity[wallId]) {
561
+ opacity = self._wallOpacity[wallId].opacity;
562
+ transparent = opacity < 1.0;
563
+ console.log('[SceneCore] 应用墙体透明度:', wallId, '=', opacity);
564
+ }
565
+ }
532
566
  } else if (child.material.name.startsWith('Shape_') || child.material.name.startsWith('Stair_')) {
533
567
  roughness = 0.25;
534
568
  metalness = 0;
@@ -611,6 +645,183 @@ SceneCore.prototype.onWindowResize = function() {
611
645
  this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
612
646
  };
613
647
 
648
+ /**
649
+ * 创建文本精灵(用于3D场景中显示文本)
650
+ */
651
+ SceneCore.prototype.createTextSprite = function(options) {
652
+ var content = options.content || '';
653
+ var fontSize = options.fontSize || 16;
654
+ var color = options.color || '#000000';
655
+ var fontFamily = options.fontFamily || 'Arial, sans-serif';
656
+ var bold = options.bold || false;
657
+ var italic = options.italic || false;
658
+ var borderColor = options.borderColor || '#87CEEB';
659
+ var backgroundColor = options.backgroundColor || '#FFFFFF';
660
+ var backgroundOpacity = options.backgroundOpacity !== undefined ? options.backgroundOpacity : 0.8;
661
+ var padding = options.padding || 8;
662
+
663
+ // 创建高分辨率canvas用于文本纹理
664
+ var canvas = document.createElement('canvas');
665
+ var context = canvas.getContext('2d');
666
+
667
+ // 使用更大的canvas尺寸以提高清晰度
668
+ canvas.width = 1024;
669
+ canvas.height = 512;
670
+
671
+ // 配置字体(放大4倍以提高质量)
672
+ var scaledFontSize = fontSize * 4;
673
+ var fontStyle = (italic ? 'italic ' : '') + (bold ? 'bold ' : '') + scaledFontSize + 'px ' + fontFamily;
674
+ context.font = fontStyle;
675
+ context.textAlign = 'center';
676
+ context.textBaseline = 'middle';
677
+
678
+ // 测量文本
679
+ var metrics = context.measureText(content);
680
+ var textWidth = metrics.width;
681
+ var textHeight = scaledFontSize * 1.2;
682
+
683
+ // 计算背景尺寸
684
+ var scaledPadding = padding * 4;
685
+ var bgWidth = textWidth + scaledPadding * 2;
686
+ var bgHeight = textHeight + scaledPadding * 2;
687
+
688
+ // 计算居中位置
689
+ var centerX = canvas.width / 2;
690
+ var centerY = canvas.height / 2;
691
+ var bgX = centerX - bgWidth / 2;
692
+ var bgY = centerY - bgHeight / 2;
693
+
694
+ // 绘制阴影效果以增强可读性
695
+ context.shadowColor = 'rgba(0, 0, 0, 0.5)';
696
+ context.shadowBlur = 8;
697
+ context.shadowOffsetX = 2;
698
+ context.shadowOffsetY = 2;
699
+
700
+ // 绘制背景
701
+ context.fillStyle = backgroundColor;
702
+ context.globalAlpha = Math.max(backgroundOpacity, 0.9); // 确保背景足够不透明
703
+ context.fillRect(bgX, bgY, bgWidth, bgHeight);
704
+
705
+ // 重置阴影
706
+ context.shadowColor = 'transparent';
707
+ context.shadowBlur = 0;
708
+ context.shadowOffsetX = 0;
709
+ context.shadowOffsetY = 0;
710
+
711
+ // 绘制边框(加粗以提高可见性)
712
+ context.strokeStyle = borderColor;
713
+ context.lineWidth = 6;
714
+ context.globalAlpha = 1;
715
+ context.strokeRect(bgX, bgY, bgWidth, bgHeight);
716
+
717
+ // 绘制文本轮廓以增强对比度
718
+ context.strokeStyle = color === '#000000' ? '#FFFFFF' : '#000000';
719
+ context.lineWidth = 2;
720
+ context.strokeText(content, centerX, centerY);
721
+
722
+ // 绘制文本
723
+ context.fillStyle = color;
724
+ context.fillText(content, centerX, centerY);
725
+
726
+ // 创建纹理和sprite
727
+ var texture = new THREE.Texture(canvas);
728
+ texture.needsUpdate = true;
729
+ texture.minFilter = THREE.LinearFilter;
730
+ texture.magFilter = THREE.LinearFilter;
731
+
732
+ var material = new THREE.SpriteMaterial({
733
+ map: texture,
734
+ transparent: true,
735
+ depthTest: true,
736
+ depthWrite: false,
737
+ sizeAttenuation: true // 启用距离衰减,使文本大小随距离变化
738
+ });
739
+
740
+ var sprite = new THREE.Sprite(material);
741
+
742
+ // 增大缩放以提高可读性(与编辑器一致)
743
+ var scale = 3;
744
+ sprite.scale.set(scale, scale / 2, 1);
745
+
746
+ // 设置渲染顺序为最大值,确保文本始终在最上层
747
+ sprite.renderOrder = 999999;
748
+
749
+ return sprite;
750
+ };
751
+
752
+ /**
753
+ * 渲染文本元素到场景
754
+ */
755
+ SceneCore.prototype.renderTextElements = function(textElements) {
756
+ var self = this;
757
+
758
+ if (!textElements || textElements.length === 0) {
759
+ return;
760
+ }
761
+
762
+ // 创建文本组(如果不存在)
763
+ if (!this.textGroup) {
764
+ this.textGroup = new THREE.Group();
765
+ this.textGroup.name = 'texts';
766
+ this.scene.add(this.textGroup);
767
+ }
768
+
769
+ // 清空现有文本
770
+ while (this.textGroup.children.length > 0) {
771
+ var child = this.textGroup.children[0];
772
+ this.textGroup.remove(child);
773
+
774
+ // 释放资源
775
+ if (child.material && child.material.map) {
776
+ child.material.map.dispose();
777
+ }
778
+ if (child.material) {
779
+ child.material.dispose();
780
+ }
781
+ }
782
+
783
+ // 渲染每个文本元素
784
+ textElements.forEach(function(textEl) {
785
+ if (!textEl.points || textEl.points.length === 0) return;
786
+
787
+ var point = textEl.points[0];
788
+ var x = point.x / 100; // Canvas pixels to 3D meters
789
+ var z = point.y / 100;
790
+
791
+ // 文本elevation单位是mm,需要按照墙面比例缩放(与编辑器预览一致)
792
+ var LOGICAL_WALL_HEIGHT = 3000; // 逻辑墙高3000mm(3米)
793
+ var RENDER_WALL_HEIGHT = 0.375; // 渲染墙高0.375米
794
+ var elevationMm = textEl.elevation || 8500; // 默认8500mm(850cm)
795
+ var y = (elevationMm / LOGICAL_WALL_HEIGHT) * RENDER_WALL_HEIGHT;
796
+
797
+ console.log('[SceneCore] 文本元素属性:', {
798
+ content: textEl.content,
799
+ backgroundColor: textEl.backgroundColor,
800
+ borderColor: textEl.borderColor,
801
+ color: textEl.color
802
+ });
803
+
804
+ var sprite = self.createTextSprite({
805
+ content: textEl.content || '',
806
+ fontSize: textEl.fontSize || 16,
807
+ color: textEl.color || '#000000',
808
+ fontFamily: textEl.fontFamily || 'Arial, sans-serif',
809
+ bold: textEl.bold || false,
810
+ italic: textEl.italic || false,
811
+ borderColor: textEl.borderColor || '#ADD8E6',
812
+ backgroundColor: textEl.backgroundColor || '#FFFFFF',
813
+ backgroundOpacity: textEl.backgroundOpacity !== undefined ? textEl.backgroundOpacity : 0.8,
814
+ padding: textEl.padding || 8
815
+ });
816
+
817
+ sprite.position.set(x, y, z);
818
+ sprite.userData = { type: 'text', elementId: textEl.id };
819
+ self.textGroup.add(sprite);
820
+ });
821
+
822
+ console.log('[SceneCore] ✅ 文本元素渲染完成:', textElements.length, '个');
823
+ };
824
+
614
825
  /**
615
826
  * 销毁场景
616
827
  */
@@ -114,9 +114,110 @@ function extractCoordinateSystem(config) {
114
114
  };
115
115
  }
116
116
 
117
+ /**
118
+ * 从OBJ内容中提取文本元素
119
+ * 格式示例:
120
+ * # ===== Text Element =====
121
+ * # TEXT_ID text_123
122
+ * # TEXT_CONTENT Hello World
123
+ * # TEXT_POSITION 1000 2000
124
+ * # TEXT_ELEVATION 8500
125
+ * # ===== End Text Element =====
126
+ */
127
+ function extractTextElementsFromOBJ(objContent) {
128
+ var textElements = [];
129
+ var lines = objContent.split('\n');
130
+ var currentText = null;
131
+
132
+ for (var i = 0; i < lines.length; i++) {
133
+ var line = lines[i].trim();
134
+
135
+ if (line.indexOf('# ===== Text Element =====') === 0) {
136
+ currentText = {};
137
+ } else if (line.indexOf('# ===== End Text Element =====') === 0 && currentText) {
138
+ textElements.push(currentText);
139
+ currentText = null;
140
+ } else if (currentText) {
141
+ if (line.indexOf('# TEXT_ID') === 0) {
142
+ currentText.id = line.split(' ')[2];
143
+ } else if (line.indexOf('# TEXT_CONTENT') === 0) {
144
+ currentText.content = line.substring(line.indexOf('TEXT_CONTENT') + 13).trim();
145
+ } else if (line.indexOf('# TEXT_POSITION') === 0) {
146
+ var parts = line.split(' ');
147
+ currentText.points = [{ x: parseFloat(parts[2]), y: parseFloat(parts[3]) }];
148
+ } else if (line.indexOf('# TEXT_ELEVATION') === 0) {
149
+ currentText.elevation = parseFloat(line.split(' ')[2]);
150
+ } else if (line.indexOf('# TEXT_FONT_SIZE') === 0) {
151
+ currentText.fontSize = parseFloat(line.split(' ')[2]);
152
+ } else if (line.indexOf('# TEXT_COLOR') === 0) {
153
+ currentText.color = line.split(' ')[2];
154
+ } else if (line.indexOf('# TEXT_FONT_FAMILY') === 0) {
155
+ currentText.fontFamily = line.substring(line.indexOf('TEXT_FONT_FAMILY') + 17).trim();
156
+ } else if (line.indexOf('# TEXT_BOLD') === 0) {
157
+ currentText.bold = line.split(' ')[2] === 'true';
158
+ } else if (line.indexOf('# TEXT_ITALIC') === 0) {
159
+ currentText.italic = line.split(' ')[2] === 'true';
160
+ } else if (line.indexOf('# TEXT_BORDER_COLOR') === 0) {
161
+ currentText.borderColor = line.split(' ')[2];
162
+ } else if (line.indexOf('# TEXT_BG_COLOR') === 0) {
163
+ currentText.backgroundColor = line.split(' ')[2];
164
+ } else if (line.indexOf('# TEXT_BG_OPACITY') === 0) {
165
+ currentText.backgroundOpacity = parseFloat(line.split(' ')[2]);
166
+ } else if (line.indexOf('# TEXT_PADDING') === 0) {
167
+ currentText.padding = parseFloat(line.split(' ')[2]);
168
+ }
169
+ }
170
+ }
171
+
172
+ console.log('[Parsers] 提取到的文本元素:', textElements.length, '个');
173
+ return textElements;
174
+ }
175
+
176
+ /**
177
+ * 从OBJ内容中提取墙体透明度信息
178
+ * 格式示例:
179
+ * o Wall_wall123_seg0
180
+ * # WALL_OPACITY 0.85
181
+ * # WALL_COLOR #d1d5db
182
+ */
183
+ function extractWallOpacityFromOBJ(objContent) {
184
+ var wallOpacity = {};
185
+ var lines = objContent.split('\n');
186
+ var currentWallId = null;
187
+
188
+ for (var i = 0; i < lines.length; i++) {
189
+ var line = lines[i].trim();
190
+
191
+ if (line.indexOf('o Wall_') === 0) {
192
+ // 提取墙体ID(格式:Wall_<id>_seg<n>)
193
+ var match = line.match(/Wall_([^_]+)_seg/);
194
+ if (match) {
195
+ currentWallId = match[1];
196
+ }
197
+ } else if (currentWallId && line.indexOf('# WALL_OPACITY') === 0) {
198
+ var opacity = parseFloat(line.split(' ')[2]);
199
+ if (!wallOpacity[currentWallId]) {
200
+ wallOpacity[currentWallId] = {};
201
+ }
202
+ wallOpacity[currentWallId].opacity = opacity;
203
+ } else if (currentWallId && line.indexOf('# WALL_COLOR') === 0) {
204
+ var color = line.split(' ')[2];
205
+ if (!wallOpacity[currentWallId]) {
206
+ wallOpacity[currentWallId] = {};
207
+ }
208
+ wallOpacity[currentWallId].color = color;
209
+ }
210
+ }
211
+
212
+ console.log('[Parsers] 提取到的墙体透明度:', Object.keys(wallOpacity).length, '个墙体');
213
+ return wallOpacity;
214
+ }
215
+
117
216
  module.exports = {
118
217
  extractBorderFromOBJ: extractBorderFromOBJ,
119
218
  extractMapConfigFromOBJ: extractMapConfigFromOBJ,
120
219
  extractColorsFromOBJ: extractColorsFromOBJ,
121
- extractCoordinateSystem: extractCoordinateSystem
220
+ extractCoordinateSystem: extractCoordinateSystem,
221
+ extractTextElementsFromOBJ: extractTextElementsFromOBJ,
222
+ extractWallOpacityFromOBJ: extractWallOpacityFromOBJ
122
223
  };