|
@@ -1,245 +1,133 @@
|
|
|
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;
|
|
|
+ constructor(updateProgress, onComplete) {
|
|
|
+ this.updateProgress = updateProgress;
|
|
|
+ this.onComplete = onComplete;
|
|
|
+ this.meshGroup = new THREE.Group();
|
|
|
+
|
|
|
+ this.defaultMaterial = new THREE.MeshPhongMaterial({
|
|
|
+ color: 0xffffff, // 改为白色基础色
|
|
|
+ specular: 0x111111, // 适当的高光
|
|
|
+ shininess: 30,
|
|
|
+ side: THREE.DoubleSide,
|
|
|
+ flatShading: false, // 改为false获得更平滑的 shading
|
|
|
+ vertexColors: true,
|
|
|
+ transparent: true,
|
|
|
+ opacity: 1.0 // 不透明度设为1
|
|
|
+ });
|
|
|
+ this.defaultLights = [
|
|
|
+ new THREE.AmbientLight(0xffffff, 1.0), // 环境光(强度增强)
|
|
|
+ new THREE.DirectionalLight(0xffffff, 1.5), // 平行光(强度增强)
|
|
|
+ new THREE.HemisphereLight(0xffffbb, 0x080820, 0.8) // 天光
|
|
|
+ ];
|
|
|
}
|
|
|
|
|
|
- // 否则渲染所有区域(原有逻辑)
|
|
|
- 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));
|
|
|
+ async render(pltData, scene) {
|
|
|
+ // 清除旧光照
|
|
|
+ scene.traverse(obj => {
|
|
|
+ if (obj.isLight) scene.remove(obj);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 添加新光照
|
|
|
+ this.defaultLights.forEach(light => {
|
|
|
+ if (light.isDirectionalLight) {
|
|
|
+ light.position.set(1, 1, 1).normalize();
|
|
|
+ }
|
|
|
+ scene.add(light);
|
|
|
+ });
|
|
|
+ this.clearScene(scene);
|
|
|
+ scene.add(this.meshGroup);
|
|
|
+
|
|
|
+ try {
|
|
|
+ await this.updateProgressAsync('开始渲染PLT模型...');
|
|
|
+
|
|
|
+ // 处理每个zone
|
|
|
+ for (const zone of pltData.zones) {
|
|
|
+ await this.updateProgressAsync(`正在处理区域: ${zone.name}...`);
|
|
|
+
|
|
|
+ // 创建几何体
|
|
|
+ const geometry = new THREE.BufferGeometry();
|
|
|
+
|
|
|
+ // 设置顶点属性
|
|
|
+ geometry.setAttribute(
|
|
|
+ 'position',
|
|
|
+ new THREE.BufferAttribute(new Float32Array(zone.vertices), 3)
|
|
|
+ );
|
|
|
+
|
|
|
+ // 设置索引
|
|
|
+ if (zone.indices && zone.indices.length > 0) {
|
|
|
+ geometry.setIndex(
|
|
|
+ new THREE.BufferAttribute(new Uint32Array(zone.indices), 1)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置顶点颜色(使用CoefPressure变量)
|
|
|
+ if (zone.variables && zone.variables.CoefPressure) {
|
|
|
+ const colors = this.generateVertexColors(zone.variables.CoefPressure);
|
|
|
+ geometry.setAttribute(
|
|
|
+ 'color',
|
|
|
+ new THREE.BufferAttribute(new Float32Array(colors), 3)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ await this.updateProgressAsync('优化几何体...');
|
|
|
+ geometry.computeVertexNormals();
|
|
|
+ geometry.computeBoundingSphere();
|
|
|
+
|
|
|
+ await this.updateProgressAsync('创建网格...');
|
|
|
+ const mesh = new THREE.Mesh(geometry, this.defaultMaterial);
|
|
|
+ mesh.name = zone.name;
|
|
|
+ this.meshGroup.add(mesh);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.onComplete) this.onComplete();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('PLT渲染错误:', error);
|
|
|
+ if (this.onComplete) this.onComplete(error);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- 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();
|
|
|
+ generateVertexColors(data) {
|
|
|
+ // 添加数据验证
|
|
|
+ if (!data || !Array.isArray(data) || data.length === 0) {
|
|
|
+ console.warn('无效的顶点颜色数据,使用默认颜色');
|
|
|
+ return new Array(data.length * 3).fill(0.5); // 返回灰色
|
|
|
}
|
|
|
|
|
|
- // 创建几何体
|
|
|
- const geometry = this._createZoneGeometry(zone);
|
|
|
+ const min = Math.min(...data);
|
|
|
+ const max = Math.max(...data);
|
|
|
+ const range = max - min || 1; // 避免除以0
|
|
|
|
|
|
- // 分配材质(交替使用实体和线框材质)
|
|
|
- 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);
|
|
|
+ const colors = new Array(data.length * 3);
|
|
|
|
|
|
- // 添加区域标签(可选)
|
|
|
- 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];
|
|
|
+ for (let i = 0; i < data.length; i++) {
|
|
|
+ const normalized = (data[i] - min) / range;
|
|
|
+
|
|
|
+ // 热力图颜色映射
|
|
|
+ colors[i * 3] = normalized; // R
|
|
|
+ colors[i * 3 + 1] = 0; // G
|
|
|
+ colors[i * 3 + 2] = 1 - normalized; // B
|
|
|
}
|
|
|
- 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));
|
|
|
+ return colors;
|
|
|
}
|
|
|
-
|
|
|
- // 计算边界
|
|
|
- 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);
|
|
|
+ async updateProgressAsync(message) {
|
|
|
+ if (this.updateProgress) {
|
|
|
+ this.updateProgress(message);
|
|
|
+ await new Promise(resolve => requestAnimationFrame(resolve));
|
|
|
}
|
|
|
- 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);
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
+ clearScene(scene) {
|
|
|
+ this.meshGroup.traverse(child => {
|
|
|
+ if (child.isMesh) {
|
|
|
+ child.geometry.dispose();
|
|
|
+ child.material.dispose();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ scene.remove(this.meshGroup);
|
|
|
+ this.meshGroup = new THREE.Group();
|
|
|
+ }
|
|
|
}
|