|
@@ -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);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|