PltDataRenderer.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import * as THREE from 'three';
  2. export class PltDataRenderer {
  3. constructor(updateProgress, onComplete) {
  4. this.updateProgress = updateProgress || (() => {});
  5. this.onComplete = onComplete || (() => {});
  6. this.chunkSize = 5000; // 每个区域的最大三角面片数(用于分块加载)
  7. this.defaultMaterial = new THREE.MeshPhongMaterial({
  8. color: 0x4477ff,
  9. specular: 0x111111,
  10. shininess: 30,
  11. side: THREE.DoubleSide, // 关键修改:双面渲染
  12. flatShading: false, // 平滑着色
  13. transparent: true,
  14. opacity: 0.95
  15. });
  16. this.wireframeMaterial = new THREE.MeshBasicMaterial({
  17. color: 0x00ff00,
  18. wireframe: true
  19. });
  20. }
  21. async render(pltData, scene, specificZoneIndex = null) {
  22. console.log("原始PLT数据结构诊断:", {
  23. zones: pltData.zones.map(zone => ({
  24. name: zone.name,
  25. verticesType: zone.vertices?.constructor?.name,
  26. verticesLength: zone.vertices?.length,
  27. verticesSample: zone.vertices?.slice(0, 3),
  28. indicesType: zone.indices?.constructor?.name,
  29. indicesLength: zone.indices?.length,
  30. indicesSample: zone.indices?.slice(0, 3)
  31. }))
  32. });
  33. if (!pltData?.zones?.length) {
  34. console.error('Invalid PLT data');
  35. return;
  36. }
  37. // 清空现有PLT相关对象
  38. this._cleanupPreviousRender(scene);
  39. // 如果指定了具体区域索引,则只渲染该区域
  40. if (specificZoneIndex !== null) {
  41. if (specificZoneIndex < 0 || specificZoneIndex >= pltData.zones.length) {
  42. console.error(`Invalid zone index: ${specificZoneIndex} (total zones: ${pltData.zones.length})`);
  43. return;
  44. }
  45. const zone = pltData.zones[specificZoneIndex];
  46. await this._renderZone(zone, scene, specificZoneIndex, pltData.zones.length);
  47. console.log(`[DEBUG] 仅渲染了区域 ${specificZoneIndex}: ${zone.name}`);
  48. return;
  49. }
  50. // 否则渲染所有区域(原有逻辑)
  51. const totalZones = pltData.zones.length;
  52. for (const [index, zone] of pltData.zones.entries()) {
  53. try {
  54. await this._renderZone(zone, scene, index, totalZones);
  55. this.updateProgress((index + 1) / totalZones);
  56. } catch (error) {
  57. console.error(`Failed to render zone ${zone.name}:`, error);
  58. }
  59. await new Promise(resolve => setTimeout(resolve, 0));
  60. }
  61. this.onComplete();
  62. }
  63. async _renderZone(zone, scene, zoneIndex, totalZones) {
  64. return new Promise((resolve) => {
  65. // 使用requestAnimationFrame避免阻塞UI
  66. requestAnimationFrame(() => {
  67. if (!zone.vertices || !zone.indices) {
  68. console.warn(`Zone ${zone.name} has no geometry data`);
  69. return resolve();
  70. }
  71. // 创建几何体
  72. const geometry = this._createZoneGeometry(zone);
  73. // 分配材质(交替使用实体和线框材质)
  74. const material = zoneIndex % 2 === 0
  75. ? this.defaultMaterial.clone()
  76. : this.wireframeMaterial.clone();
  77. // 设置区域特定颜色
  78. if (zoneIndex % 2 === 0) {
  79. material.color.setHSL(zoneIndex / totalZones, 0.7, 0.5);
  80. } else {
  81. material.color.setHSL(zoneIndex / totalZones, 0.7, 0.7);
  82. }
  83. // 创建网格
  84. const mesh = new THREE.Mesh(geometry, material);
  85. mesh.name = `plt_zone_${zoneIndex}`;
  86. mesh.userData = {
  87. zoneName: zone.name,
  88. zoneType: zone.type,
  89. isPLT: true
  90. };
  91. // 添加到场景
  92. scene.add(mesh);
  93. // 添加区域标签(可选)
  94. this._addZoneLabel(zone, mesh, zoneIndex);
  95. console.log(`Rendered zone ${zone.name} with ${geometry.index.count / 3} triangles`);
  96. resolve();
  97. });
  98. });
  99. }
  100. _createZoneGeometry(zone) {
  101. const geometry = new THREE.BufferGeometry();
  102. // 顶点数据
  103. geometry.setAttribute(
  104. 'position',
  105. new THREE.BufferAttribute(zone.vertices, 3)
  106. );
  107. // 安全极值计算函数
  108. const getArrayExtremes = (arr) => {
  109. let min = Infinity, max = -Infinity;
  110. for (let i = 0; i < arr.length; i++) {
  111. if (arr[i] < min) min = arr[i];
  112. if (arr[i] > max) max = arr[i];
  113. }
  114. return { min, max };
  115. };
  116. // 索引处理
  117. let indicesArray = zone.indices || new Uint32Array(0);
  118. if (indicesArray.length > 0) {
  119. // 计算极值
  120. const { min: minIndex, max: maxIndex } = getArrayExtremes(indicesArray);
  121. const vertexCount = zone.vertices.length / 3;
  122. // 索引验证
  123. if (maxIndex >= vertexCount) {
  124. console.warn(`修正${maxIndex - vertexCount + 1}个超出范围的索引`);
  125. const validIndices = new Uint32Array(indicesArray.length);
  126. for (let i = 0; i < indicesArray.length; i++) {
  127. validIndices[i] = indicesArray[i] >= vertexCount ? 0 : indicesArray[i];
  128. }
  129. indicesArray = validIndices;
  130. }
  131. // 四边形转换
  132. if (indicesArray.length % 4 === 0) {
  133. console.log('四边形->三角形转换');
  134. const triCount = indicesArray.length / 4 * 6;
  135. const triangles = new Uint32Array(triCount);
  136. for (let i = 0; i < indicesArray.length / 4; i++) {
  137. const base = i * 4;
  138. triangles[i*6] = indicesArray[base];
  139. triangles[i*6+1] = indicesArray[base+1];
  140. triangles[i*6+2] = indicesArray[base+2];
  141. triangles[i*6+3] = indicesArray[base];
  142. triangles[i*6+4] = indicesArray[base+2];
  143. triangles[i*6+5] = indicesArray[base+3];
  144. }
  145. indicesArray = triangles;
  146. }
  147. geometry.setIndex(new THREE.BufferAttribute(indicesArray, 1));
  148. }
  149. // 计算边界
  150. geometry.computeBoundingSphere();
  151. geometry.computeBoundingBox();
  152. console.log('几何体验证:', {
  153. vertices: zone.vertices.length / 3,
  154. triangles: indicesArray.length / 3,
  155. radius: geometry.boundingSphere.radius.toFixed(2)
  156. });
  157. geometry.computeVertexNormals();
  158. console.log('法线检查:', {
  159. firstNormal: geometry.attributes.normal?.array.slice(0, 3)
  160. });
  161. return geometry;
  162. }
  163. _addZoneLabel(zone, mesh, zoneIndex) {
  164. // 使用CSS2DRenderer需要先初始化
  165. if (!this.labelRenderer) return;
  166. const labelDiv = document.createElement('div');
  167. labelDiv.className = 'plt-zone-label';
  168. labelDiv.textContent = `${zone.name}`;
  169. labelDiv.style.backgroundColor = `hsla(${zoneIndex * 360 / 8}, 70%, 50%, 0.7)`;
  170. const label = new THREE.CSS2DObject(labelDiv);
  171. label.position.set(0, 0, 0); // 位置会在动画中更新
  172. mesh.userData.label = label;
  173. mesh.add(label);
  174. }
  175. _cleanupPreviousRender(scene) {
  176. scene.children.forEach(obj => {
  177. if (obj.userData?.isPLT) {
  178. // 清理几何体和材质
  179. if (obj.geometry) obj.geometry.dispose();
  180. if (obj.material) {
  181. if (Array.isArray(obj.material)) {
  182. obj.material.forEach(m => m.dispose());
  183. } else {
  184. obj.material.dispose();
  185. }
  186. }
  187. // 清理标签
  188. if (obj.userData.label) {
  189. obj.remove(obj.userData.label);
  190. }
  191. scene.remove(obj);
  192. }
  193. });
  194. }
  195. // 更新标签位置(需要在动画循环中调用)
  196. updateLabels(camera) {
  197. if (!this.labelRenderer) return;
  198. this.labelRenderer.render(scene, camera);
  199. scene.children.forEach(obj => {
  200. if (obj.userData?.label) {
  201. // 将标签定位到几何中心
  202. obj.geometry.computeBoundingSphere();
  203. const center = obj.geometry.boundingSphere.center.clone();
  204. obj.localToWorld(center);
  205. obj.userData.label.position.copy(center);
  206. }
  207. });
  208. }
  209. }