|  | @@ -0,0 +1,245 @@
 | 
											
												
													
														|  | 
 |  | +import * as THREE from 'three';
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +export class PltDataRenderer {
 | 
											
												
													
														|  | 
 |  | +  constructor(updateProgress, onComplete) {
 | 
											
												
													
														|  | 
 |  | +    this.updateProgress = updateProgress || (() => {});
 | 
											
												
													
														|  | 
 |  | +    this.onComplete = onComplete || (() => {});
 | 
											
												
													
														|  | 
 |  | +    this.chunkSize = 5000; // 每个区域的最大三角面片数(用于分块加载)
 | 
											
												
													
														|  | 
 |  | +    this.defaultMaterial = new THREE.MeshPhongMaterial({
 | 
											
												
													
														|  | 
 |  | +      color: 0x4477ff,
 | 
											
												
													
														|  | 
 |  | +      specular: 0x111111,
 | 
											
												
													
														|  | 
 |  | +      shininess: 30,
 | 
											
												
													
														|  | 
 |  | +      side: THREE.DoubleSide, // 关键修改:双面渲染
 | 
											
												
													
														|  | 
 |  | +      flatShading: false,     // 平滑着色
 | 
											
												
													
														|  | 
 |  | +      transparent: true,
 | 
											
												
													
														|  | 
 |  | +      opacity: 0.95
 | 
											
												
													
														|  | 
 |  | +    });
 | 
											
												
													
														|  | 
 |  | +    this.wireframeMaterial = new THREE.MeshBasicMaterial({
 | 
											
												
													
														|  | 
 |  | +      color: 0x00ff00,
 | 
											
												
													
														|  | 
 |  | +      wireframe: true
 | 
											
												
													
														|  | 
 |  | +    });
 | 
											
												
													
														|  | 
 |  | +  }
 | 
											
												
													
														|  | 
 |  | +  
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +  async render(pltData, scene, specificZoneIndex = null) {
 | 
											
												
													
														|  | 
 |  | +    console.log("原始PLT数据结构诊断:", {
 | 
											
												
													
														|  | 
 |  | +      zones: pltData.zones.map(zone => ({
 | 
											
												
													
														|  | 
 |  | +        name: zone.name,
 | 
											
												
													
														|  | 
 |  | +        verticesType: zone.vertices?.constructor?.name,
 | 
											
												
													
														|  | 
 |  | +        verticesLength: zone.vertices?.length,
 | 
											
												
													
														|  | 
 |  | +        verticesSample: zone.vertices?.slice(0, 3),
 | 
											
												
													
														|  | 
 |  | +        indicesType: zone.indices?.constructor?.name,
 | 
											
												
													
														|  | 
 |  | +        indicesLength: zone.indices?.length,
 | 
											
												
													
														|  | 
 |  | +        indicesSample: zone.indices?.slice(0, 3)
 | 
											
												
													
														|  | 
 |  | +      }))
 | 
											
												
													
														|  | 
 |  | +    });
 | 
											
												
													
														|  | 
 |  | +    if (!pltData?.zones?.length) {
 | 
											
												
													
														|  | 
 |  | +      console.error('Invalid PLT data');
 | 
											
												
													
														|  | 
 |  | +      return;
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    // 清空现有PLT相关对象
 | 
											
												
													
														|  | 
 |  | +    this._cleanupPreviousRender(scene);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    // 如果指定了具体区域索引,则只渲染该区域
 | 
											
												
													
														|  | 
 |  | +    if (specificZoneIndex !== null) {
 | 
											
												
													
														|  | 
 |  | +      if (specificZoneIndex < 0 || specificZoneIndex >= pltData.zones.length) {
 | 
											
												
													
														|  | 
 |  | +        console.error(`Invalid zone index: ${specificZoneIndex} (total zones: ${pltData.zones.length})`);
 | 
											
												
													
														|  | 
 |  | +        return;
 | 
											
												
													
														|  | 
 |  | +      }
 | 
											
												
													
														|  | 
 |  | +      
 | 
											
												
													
														|  | 
 |  | +      const zone = pltData.zones[specificZoneIndex];
 | 
											
												
													
														|  | 
 |  | +      await this._renderZone(zone, scene, specificZoneIndex, pltData.zones.length);
 | 
											
												
													
														|  | 
 |  | +      console.log(`[DEBUG] 仅渲染了区域 ${specificZoneIndex}: ${zone.name}`);
 | 
											
												
													
														|  | 
 |  | +      return;
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    // 否则渲染所有区域(原有逻辑)
 | 
											
												
													
														|  | 
 |  | +    const totalZones = pltData.zones.length;
 | 
											
												
													
														|  | 
 |  | +    for (const [index, zone] of pltData.zones.entries()) {
 | 
											
												
													
														|  | 
 |  | +      try {
 | 
											
												
													
														|  | 
 |  | +        await this._renderZone(zone, scene, index, totalZones);
 | 
											
												
													
														|  | 
 |  | +        this.updateProgress((index + 1) / totalZones);
 | 
											
												
													
														|  | 
 |  | +      } catch (error) {
 | 
											
												
													
														|  | 
 |  | +        console.error(`Failed to render zone ${zone.name}:`, error);
 | 
											
												
													
														|  | 
 |  | +      }
 | 
											
												
													
														|  | 
 |  | +      await new Promise(resolve => setTimeout(resolve, 0));
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    this.onComplete();
 | 
											
												
													
														|  | 
 |  | +  }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +  async _renderZone(zone, scene, zoneIndex, totalZones) {
 | 
											
												
													
														|  | 
 |  | +    return new Promise((resolve) => {
 | 
											
												
													
														|  | 
 |  | +      // 使用requestAnimationFrame避免阻塞UI
 | 
											
												
													
														|  | 
 |  | +      requestAnimationFrame(() => {
 | 
											
												
													
														|  | 
 |  | +        if (!zone.vertices || !zone.indices) {
 | 
											
												
													
														|  | 
 |  | +          console.warn(`Zone ${zone.name} has no geometry data`);
 | 
											
												
													
														|  | 
 |  | +          return resolve();
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        // 创建几何体
 | 
											
												
													
														|  | 
 |  | +        const geometry = this._createZoneGeometry(zone);
 | 
											
												
													
														|  | 
 |  | +        
 | 
											
												
													
														|  | 
 |  | +        // 分配材质(交替使用实体和线框材质)
 | 
											
												
													
														|  | 
 |  | +        const material = zoneIndex % 2 === 0 
 | 
											
												
													
														|  | 
 |  | +          ? this.defaultMaterial.clone()
 | 
											
												
													
														|  | 
 |  | +          : this.wireframeMaterial.clone();
 | 
											
												
													
														|  | 
 |  | +        
 | 
											
												
													
														|  | 
 |  | +        // 设置区域特定颜色
 | 
											
												
													
														|  | 
 |  | +        if (zoneIndex % 2 === 0) {
 | 
											
												
													
														|  | 
 |  | +          material.color.setHSL(zoneIndex / totalZones, 0.7, 0.5);
 | 
											
												
													
														|  | 
 |  | +        } else {
 | 
											
												
													
														|  | 
 |  | +          material.color.setHSL(zoneIndex / totalZones, 0.7, 0.7);
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        // 创建网格
 | 
											
												
													
														|  | 
 |  | +        const mesh = new THREE.Mesh(geometry, material);
 | 
											
												
													
														|  | 
 |  | +        mesh.name = `plt_zone_${zoneIndex}`;
 | 
											
												
													
														|  | 
 |  | +        mesh.userData = {
 | 
											
												
													
														|  | 
 |  | +          zoneName: zone.name,
 | 
											
												
													
														|  | 
 |  | +          zoneType: zone.type,
 | 
											
												
													
														|  | 
 |  | +          isPLT: true
 | 
											
												
													
														|  | 
 |  | +        };
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        // 添加到场景
 | 
											
												
													
														|  | 
 |  | +        scene.add(mesh);
 | 
											
												
													
														|  | 
 |  | +        
 | 
											
												
													
														|  | 
 |  | +        // 添加区域标签(可选)
 | 
											
												
													
														|  | 
 |  | +        this._addZoneLabel(zone, mesh, zoneIndex);
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +        console.log(`Rendered zone ${zone.name} with ${geometry.index.count / 3} triangles`);
 | 
											
												
													
														|  | 
 |  | +        resolve();
 | 
											
												
													
														|  | 
 |  | +      });
 | 
											
												
													
														|  | 
 |  | +    });
 | 
											
												
													
														|  | 
 |  | +  }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +  _createZoneGeometry(zone) {
 | 
											
												
													
														|  | 
 |  | +    const geometry = new THREE.BufferGeometry();
 | 
											
												
													
														|  | 
 |  | +    
 | 
											
												
													
														|  | 
 |  | +    // 顶点数据
 | 
											
												
													
														|  | 
 |  | +    geometry.setAttribute(
 | 
											
												
													
														|  | 
 |  | +      'position',
 | 
											
												
													
														|  | 
 |  | +      new THREE.BufferAttribute(zone.vertices, 3)
 | 
											
												
													
														|  | 
 |  | +    );
 | 
											
												
													
														|  | 
 |  | +  
 | 
											
												
													
														|  | 
 |  | +    // 安全极值计算函数
 | 
											
												
													
														|  | 
 |  | +    const getArrayExtremes = (arr) => {
 | 
											
												
													
														|  | 
 |  | +      let min = Infinity, max = -Infinity;
 | 
											
												
													
														|  | 
 |  | +      for (let i = 0; i < arr.length; i++) {
 | 
											
												
													
														|  | 
 |  | +        if (arr[i] < min) min = arr[i];
 | 
											
												
													
														|  | 
 |  | +        if (arr[i] > max) max = arr[i];
 | 
											
												
													
														|  | 
 |  | +      }
 | 
											
												
													
														|  | 
 |  | +      return { min, max };
 | 
											
												
													
														|  | 
 |  | +    };
 | 
											
												
													
														|  | 
 |  | +  
 | 
											
												
													
														|  | 
 |  | +    // 索引处理
 | 
											
												
													
														|  | 
 |  | +    let indicesArray = zone.indices || new Uint32Array(0);
 | 
											
												
													
														|  | 
 |  | +    if (indicesArray.length > 0) {
 | 
											
												
													
														|  | 
 |  | +      // 计算极值
 | 
											
												
													
														|  | 
 |  | +      const { min: minIndex, max: maxIndex } = getArrayExtremes(indicesArray);
 | 
											
												
													
														|  | 
 |  | +      const vertexCount = zone.vertices.length / 3;
 | 
											
												
													
														|  | 
 |  | +  
 | 
											
												
													
														|  | 
 |  | +      // 索引验证
 | 
											
												
													
														|  | 
 |  | +      if (maxIndex >= vertexCount) {
 | 
											
												
													
														|  | 
 |  | +        console.warn(`修正${maxIndex - vertexCount + 1}个超出范围的索引`);
 | 
											
												
													
														|  | 
 |  | +        const validIndices = new Uint32Array(indicesArray.length);
 | 
											
												
													
														|  | 
 |  | +        for (let i = 0; i < indicesArray.length; i++) {
 | 
											
												
													
														|  | 
 |  | +          validIndices[i] = indicesArray[i] >= vertexCount ? 0 : indicesArray[i];
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +        indicesArray = validIndices;
 | 
											
												
													
														|  | 
 |  | +      }
 | 
											
												
													
														|  | 
 |  | +  
 | 
											
												
													
														|  | 
 |  | +      // 四边形转换
 | 
											
												
													
														|  | 
 |  | +      if (indicesArray.length % 4 === 0) {
 | 
											
												
													
														|  | 
 |  | +        console.log('四边形->三角形转换');
 | 
											
												
													
														|  | 
 |  | +        const triCount = indicesArray.length / 4 * 6;
 | 
											
												
													
														|  | 
 |  | +        const triangles = new Uint32Array(triCount);
 | 
											
												
													
														|  | 
 |  | +        
 | 
											
												
													
														|  | 
 |  | +        for (let i = 0; i < indicesArray.length / 4; i++) {
 | 
											
												
													
														|  | 
 |  | +          const base = i * 4;
 | 
											
												
													
														|  | 
 |  | +          triangles[i*6]   = indicesArray[base];
 | 
											
												
													
														|  | 
 |  | +          triangles[i*6+1] = indicesArray[base+1];
 | 
											
												
													
														|  | 
 |  | +          triangles[i*6+2] = indicesArray[base+2];
 | 
											
												
													
														|  | 
 |  | +          triangles[i*6+3] = indicesArray[base];
 | 
											
												
													
														|  | 
 |  | +          triangles[i*6+4] = indicesArray[base+2];
 | 
											
												
													
														|  | 
 |  | +          triangles[i*6+5] = indicesArray[base+3];
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +        indicesArray = triangles;
 | 
											
												
													
														|  | 
 |  | +      }
 | 
											
												
													
														|  | 
 |  | +  
 | 
											
												
													
														|  | 
 |  | +      geometry.setIndex(new THREE.BufferAttribute(indicesArray, 1));
 | 
											
												
													
														|  | 
 |  | +    }
 | 
											
												
													
														|  | 
 |  | +  
 | 
											
												
													
														|  | 
 |  | +    // 计算边界
 | 
											
												
													
														|  | 
 |  | +    geometry.computeBoundingSphere();
 | 
											
												
													
														|  | 
 |  | +    geometry.computeBoundingBox();
 | 
											
												
													
														|  | 
 |  | +    
 | 
											
												
													
														|  | 
 |  | +    console.log('几何体验证:', {
 | 
											
												
													
														|  | 
 |  | +      vertices: zone.vertices.length / 3,
 | 
											
												
													
														|  | 
 |  | +      triangles: indicesArray.length / 3,
 | 
											
												
													
														|  | 
 |  | +      radius: geometry.boundingSphere.radius.toFixed(2)
 | 
											
												
													
														|  | 
 |  | +    });
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +    geometry.computeVertexNormals(); 
 | 
											
												
													
														|  | 
 |  | +    console.log('法线检查:', {
 | 
											
												
													
														|  | 
 |  | +      firstNormal: geometry.attributes.normal?.array.slice(0, 3)
 | 
											
												
													
														|  | 
 |  | +    });
 | 
											
												
													
														|  | 
 |  | +  
 | 
											
												
													
														|  | 
 |  | +    return geometry;
 | 
											
												
													
														|  | 
 |  | +  }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +  _addZoneLabel(zone, mesh, zoneIndex) {
 | 
											
												
													
														|  | 
 |  | +    // 使用CSS2DRenderer需要先初始化
 | 
											
												
													
														|  | 
 |  | +    if (!this.labelRenderer) return;
 | 
											
												
													
														|  | 
 |  | +    
 | 
											
												
													
														|  | 
 |  | +    const labelDiv = document.createElement('div');
 | 
											
												
													
														|  | 
 |  | +    labelDiv.className = 'plt-zone-label';
 | 
											
												
													
														|  | 
 |  | +    labelDiv.textContent = `${zone.name}`;
 | 
											
												
													
														|  | 
 |  | +    labelDiv.style.backgroundColor = `hsla(${zoneIndex * 360 / 8}, 70%, 50%, 0.7)`;
 | 
											
												
													
														|  | 
 |  | +    
 | 
											
												
													
														|  | 
 |  | +    const label = new THREE.CSS2DObject(labelDiv);
 | 
											
												
													
														|  | 
 |  | +    label.position.set(0, 0, 0); // 位置会在动画中更新
 | 
											
												
													
														|  | 
 |  | +    
 | 
											
												
													
														|  | 
 |  | +    mesh.userData.label = label;
 | 
											
												
													
														|  | 
 |  | +    mesh.add(label);
 | 
											
												
													
														|  | 
 |  | +  }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +  _cleanupPreviousRender(scene) {
 | 
											
												
													
														|  | 
 |  | +    scene.children.forEach(obj => {
 | 
											
												
													
														|  | 
 |  | +      if (obj.userData?.isPLT) {
 | 
											
												
													
														|  | 
 |  | +        // 清理几何体和材质
 | 
											
												
													
														|  | 
 |  | +        if (obj.geometry) obj.geometry.dispose();
 | 
											
												
													
														|  | 
 |  | +        if (obj.material) {
 | 
											
												
													
														|  | 
 |  | +          if (Array.isArray(obj.material)) {
 | 
											
												
													
														|  | 
 |  | +            obj.material.forEach(m => m.dispose());
 | 
											
												
													
														|  | 
 |  | +          } else {
 | 
											
												
													
														|  | 
 |  | +            obj.material.dispose();
 | 
											
												
													
														|  | 
 |  | +          }
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +        // 清理标签
 | 
											
												
													
														|  | 
 |  | +        if (obj.userData.label) {
 | 
											
												
													
														|  | 
 |  | +          obj.remove(obj.userData.label);
 | 
											
												
													
														|  | 
 |  | +        }
 | 
											
												
													
														|  | 
 |  | +        scene.remove(obj);
 | 
											
												
													
														|  | 
 |  | +      }
 | 
											
												
													
														|  | 
 |  | +    });
 | 
											
												
													
														|  | 
 |  | +  }
 | 
											
												
													
														|  | 
 |  | +
 | 
											
												
													
														|  | 
 |  | +  // 更新标签位置(需要在动画循环中调用)
 | 
											
												
													
														|  | 
 |  | +  updateLabels(camera) {
 | 
											
												
													
														|  | 
 |  | +    if (!this.labelRenderer) return;
 | 
											
												
													
														|  | 
 |  | +    
 | 
											
												
													
														|  | 
 |  | +    this.labelRenderer.render(scene, camera);
 | 
											
												
													
														|  | 
 |  | +    
 | 
											
												
													
														|  | 
 |  | +    scene.children.forEach(obj => {
 | 
											
												
													
														|  | 
 |  | +      if (obj.userData?.label) {
 | 
											
												
													
														|  | 
 |  | +        // 将标签定位到几何中心
 | 
											
												
													
														|  | 
 |  | +        obj.geometry.computeBoundingSphere();
 | 
											
												
													
														|  | 
 |  | +        const center = obj.geometry.boundingSphere.center.clone();
 | 
											
												
													
														|  | 
 |  | +        obj.localToWorld(center);
 | 
											
												
													
														|  | 
 |  | +        obj.userData.label.position.copy(center);
 | 
											
												
													
														|  | 
 |  | +      }
 | 
											
												
													
														|  | 
 |  | +    });
 | 
											
												
													
														|  | 
 |  | +  }
 | 
											
												
													
														|  | 
 |  | +}
 |