Переглянути джерело

色卡云图控制部分修改

lichunyang 2 місяців тому
батько
коміт
b27611fea0

+ 6 - 2
src/components/ThreeScene/ThreeScene.vue

@@ -71,7 +71,9 @@ const {
   updateColorMapping,
   generateContour,
   generateContoursForAllZones,
-  removeContour
+  removeContour,
+  updateColorBar,
+  setSmoothShading
 } = useThree(canvasRef, props)
 
 onMounted(() => {
@@ -106,7 +108,9 @@ defineExpose({
   updateColorMapping,
   generateContour,
   generateContoursForAllZones,
-  removeContour
+  removeContour,
+  updateColorBar,
+  setSmoothShading
 })
 </script>
 

+ 108 - 45
src/components/cloudChart/dialog/CloudMapDialog.vue

@@ -17,7 +17,7 @@
           </template>
           <el-form label-position="left">
             <el-form-item label="名称:" :label-width="formLabelWidth1">
-              <el-input v-model="cloudConfig.name"></el-input>
+              <el-input v-model="cloudConfig.scalarname" disabled></el-input>
             </el-form-item>
             <el-form-item label="类型:" :label-width="formLabelWidth1">
               <el-input v-model="cloudConfig.type" disabled></el-input>
@@ -60,14 +60,19 @@
           </template>
           <el-form label-position="left">
             <el-form-item label="名称:" :label-width="formLabelWidth1">
-              <el-input v-model="cloudConfig.name2"></el-input>
+              <el-input-number
+                v-model="cloudConfig.intervalCount"
+                :min="2"
+                :max="20"
+                @change="updateColorBarIntervals"
+              />
             </el-form-item>
             <el-form-item label=" " :label-width="formLabelWidth1">
               <el-row>
                 <el-col :span="24">
                   <el-checkbox
                     label="平滑云图"
-                    v-model="cloudConfig.check3"
+                    v-model="cloudConfig.smooth"
                   ></el-checkbox>
                 </el-col>
               </el-row>
@@ -106,18 +111,18 @@
           <el-form label-position="left">
             <el-form-item label="最大值:" :label-width="formLabelWidth1">
               <el-row style="width: 100%">
-                <el-col :span="22"
-                  ><el-input v-model="cloudConfig.maxcv"></el-input
-                ></el-col>
+                <el-col :span="22">
+                  <el-input v-model="cloudConfig.maxcv"></el-input>
+                </el-col>
                 <el-col :span="2"
-                  ><el-color-picker v-model="color1" @change="updateMaxValue"
-                /></el-col>
+                  ><el-color-picker v-model="color1" @change="updateMaxValue" />
+                </el-col>
               </el-row>
             </el-form-item>
             <el-form-item label="最小值:" :label-width="formLabelWidth1">
               <el-row style="width: 100%">
-                <el-col :span="22"
-                  ><el-input v-model="cloudConfig.mincv"></el-input
+                <el-col :span="22">
+                  <el-input v-model="cloudConfig.mincv"></el-input
                 ></el-col>
                 <el-col :span="2"
                   ><el-color-picker v-model="color2" @change="updateMinValue"
@@ -135,6 +140,7 @@
 import { ref, computed, watch, onMounted } from "vue"
 import SubDialog from "./SubDialog.vue"
 import * as THREE from "three"
+import { debounce } from "lodash-es"
 
 const props = defineProps({
   modelValue: Boolean,
@@ -155,8 +161,8 @@ const formLabelWidth1 = "120px"
 const activeNames = ref(["1", "2", "3", "4"])
 
 // 颜色选择器
-const color1 = ref("#2267B1")
-const color2 = ref("#E80000")
+const color1 = ref("#ff0000")
+const color2 = ref("#0000ff")
 
 // 可用标量选项
 const availableScalars = ref([
@@ -181,13 +187,13 @@ const cloudConfig = ref({
   scalarname: "CoefPressure",
   jzcheck: false,
   dycheck: false,
-  name2: "10",
-  check3: true,
+  intervalCount: 3, // 默认3等分
+  smooth: true,
   dataAreaType: "当前时间步",
   max: "0.00",
   min: "0.00",
-  maxcv: "34, 103, 177, 1",
-  mincv: "232, 0, 0, 1"
+  maxcv: "(1.00, 0.00, 0.00)",
+  mincv: "(0.00, 0.00, 1.00)"
 })
 
 // 计算当前可用的标量字段
@@ -245,47 +251,104 @@ const updateDataRange = () => {
 }
 
 // 更新云图显示
-const updateCloudMap = () => {
+const updateCloudMap = debounce(() => {
   if (!props.threeSceneRef || !cloudConfig.value.scalarname) return
-  
-  // 获取当前选择的标量名和配置
-  const scalarName = cloudConfig.value.scalarname
-  const min = parseFloat(cloudConfig.value.min)
-  const max = parseFloat(cloudConfig.value.max)
-  const colorMap = "rainbow"
-  const showExtremes = cloudConfig.value.jzcheck
-
-  // 调用PltHandler的更新方法
-  props.threeSceneRef.updateColorMapping(scalarName, {
-    colorMap,
-    min,
-    max,
-    showExtremes
+
+  // 先更新平滑设置
+  props.threeSceneRef.setSmoothShading(cloudConfig.value.smooth)
+
+  // 再更新颜色映射
+  props.threeSceneRef.updateColorMapping(cloudConfig.value.scalarname, {
+    colorMap: "rainbow",
+    min: parseFloat(cloudConfig.value.min),
+    max: parseFloat(cloudConfig.value.max),
+    showExtremes: cloudConfig.value.jzcheck,
+    smooth: cloudConfig.value.smooth // 传递平滑参数
   })
-}
+}, 300)
 
 // 更新最大值颜色
 const updateMaxValue = () => {
-  cloudConfig.value.maxcv = hexToRgba(color1.value)
-  updateCloudMap()
+  const rgb = hexToRgb(color1.value)
+  cloudConfig.value.maxcv = `(${rgb.r}, ${rgb.g}, ${rgb.b})`
+  updateColorBarIntervals()
 }
 
 // 更新最小值颜色
 const updateMinValue = () => {
-  cloudConfig.value.mincv = hexToRgba(color2.value)
-  updateCloudMap()
+  const rgb = hexToRgb(color2.value)
+  cloudConfig.value.mincv = `(${rgb.r}, ${rgb.g}, ${rgb.b})`
+  updateColorBarIntervals()
+}
+
+// 更新颜色条间隔
+const updateColorBarIntervals = () => {
+  if (!props.threeSceneRef) return
+
+  // 解析颜色范围字符串 (1, 0, 0) => [1, 0, 0]
+  const parseColor = (str) => {
+    const matches = str.match(/\(([^)]+)\)/)
+    if (!matches) return [1, 0, 0] // 默认红色
+    return matches[1].split(",").map((v) => parseFloat(v.trim()))
+  }
+
+  props.threeSceneRef.updateColorBar({
+    intervals: calculateIntervals(),
+    colorRange: {
+      min: parseFloat(cloudConfig.value.min),
+      max: parseFloat(cloudConfig.value.max),
+      minColor: parseColor(cloudConfig.value.mincv), // 新增最小值颜色
+      maxColor: parseColor(cloudConfig.value.maxcv) // 新增最大值颜色
+    },
+    variable: cloudConfig.value.scalarname,
+    title: getScalarLabel(cloudConfig.value.scalarname)
+  })
+}
+
+// 添加辅助函数获取变量标签
+const getScalarLabel = (value) => {
+  return (
+    filteredScalarOptions.value.find((item) => item.value === value)?.label ||
+    value
+  )
 }
 
-// 16进制颜色转RGBA字符串
-const hexToRgba = (hex) => {
-  const r = parseInt(hex.slice(1, 3), 16)
-  const g = parseInt(hex.slice(3, 5), 16)
-  const b = parseInt(hex.slice(5, 7), 16)
-  return `${r}, ${g}, ${b}, 1`
+// 解析颜色字符串 "(r, g, b)" 为数组 [r, g, b]
+const parseColorString = (str) => {
+  const matches = str.match(/\(([^)]+)\)/)
+  if (!matches) return [0, 0, 1] // 默认蓝色
+
+  return matches[1].split(",").map(Number)
+}
+
+// 计算等分点
+const calculateIntervals = () => {
+  const count = cloudConfig.value.intervalCount
+  const min = parseFloat(cloudConfig.value.min)
+  const max = parseFloat(cloudConfig.value.max)
+
+  const intervals = []
+  const step = (max - min) / (count - 1)
+  for (let i = 0; i < count; i++) {
+    intervals.push((min + i * step).toFixed(2))
+  }
+  return intervals
+}
+
+// 16进制颜色转RGB对象
+const hexToRgb = (hex) => {
+  const r = parseInt(hex.slice(1, 3), 16) / 255
+  const g = parseInt(hex.slice(3, 5), 16) / 255
+  const b = parseInt(hex.slice(5, 7), 16) / 255
+  return { r, g, b }
 }
 
 // 确认操作
 const handleConfirm = () => {
+  // 先更新颜色条
+  updateColorBarIntervals()
+
+  // 再提交配置
   emit("confirm", cloudConfig.value)
   emit("update:modelValue", false)
 }
@@ -327,8 +390,8 @@ watch(
 
 <style>
 .extreme-label {
-    transform: translate(-50%, 0);
-    white-space: nowrap;
-    pointer-events: none;
+  transform: translate(-50%, 0);
+  white-space: nowrap;
+  pointer-events: none;
 }
 </style>

+ 28 - 1
src/composables/useThree.js

@@ -289,6 +289,25 @@ export function useThree(canvasRef, props) {
     return false;
   };
 
+const updateColorBar = (params) => {
+  if (colorBar.value) {
+    colorBar.value.update(
+      params.colorRange.min,
+      params.colorRange.max,
+      params.title,
+      {
+        intervals: params.intervals,
+        colorRange: { // 保持参数结构一致
+          min: params.colorRange.min,
+          max: params.colorRange.max,
+          minColor: params.colorRange.minColor,
+          maxColor: params.colorRange.maxColor
+        }
+      }
+    );
+  }
+};
+
   const generateContour = (zoneName, variableName, options) => {
     if (dataHandlers.value.plt) {
       return dataHandlers.value.plt.generateContour(zoneName, variableName, options);
@@ -308,6 +327,12 @@ export function useThree(canvasRef, props) {
 
   };
 
+  const setSmoothShading = (smooth) => {
+    if (dataHandlers.value.plt) {
+      return dataHandlers.value.plt.setSmoothShading(smooth);
+    }
+  }
+
 
   const animate = () => {
     requestAnimationFrame(animate);
@@ -381,6 +406,8 @@ export function useThree(canvasRef, props) {
     updateColorMapping,
     generateContour,
     removeContour,
-    generateContoursForAllZones
+    generateContoursForAllZones,
+    updateColorBar,
+    setSmoothShading
   };
 }

+ 49 - 17
src/utils/three/controls/ColorBar.js

@@ -88,14 +88,24 @@ export class ColorBar {
         }
     }
 
+    // 修改渐变生成以考虑颜色范围
     createGradient() {
-        const stops = [];
+        const stops = []
+        const rangeMin = this.options.min
+        const rangeMax = this.options.max
+        const dataMin = this.options.dataMin || rangeMin
+        const dataMax = this.options.dataMax || rangeMax
+
         for (let i = 0; i <= 100; i += 10) {
-            const t = i / 100;
-            const color = this.getColor(t);
-            stops.push(`${color} ${i}%`);
+            // 计算在数据范围内的相对位置
+            const t = i / 100
+            const dataValue = dataMin + t * (dataMax - dataMin)
+            // 计算在颜色范围内的相对位置
+            const colorT = (dataValue - rangeMin) / (rangeMax - rangeMin)
+            const color = this.getColor(Math.max(0, Math.min(1, colorT)))
+            stops.push(`${color} ${i}%`)
         }
-        return `linear-gradient(to top, ${stops.join(', ')})`; // 从下到上渐变
+        return `linear-gradient(to top, ${stops.join(', ')})`
     }
 
     getColor(t) {
@@ -156,18 +166,40 @@ export class ColorBar {
         this.container.style.top = `${y}px`;
     }
 
-    update(min, max, title) {
-        this.options.min = min;
-        this.options.max = max;
-        if (title) this.options.title = title;
-
-        this.barElement.style.background = this.createGradient();
-        this.labelsElement.innerHTML = `
-            <span>${max.toFixed(2)}</span>
-            <span>${((min + max) / 2).toFixed(2)}</span>
-            <span>${min.toFixed(2)}</span>
-        `;
-        this.titleElement.textContent = title || this.options.title;
+update(min, max, title, options = {}) {
+  // 更新基础属性
+  this.options.min = min;
+  this.options.max = max;
+  this.options.title = title || this.options.title;
+  this.titleElement.textContent = this.options.title;
+
+  // 更新颜色范围(从options.colorRange获取)
+  if (options.colorRange) {
+    this.options.minColor = options.colorRange.minColor || [0, 0, 1];
+    this.options.maxColor = options.colorRange.maxColor || [1, 0, 0];
+  }
+
+  // 更新渐变和标签
+  this.barElement.style.background = this.createGradient();
+  this.updateLabels(options.intervals);
+}
+
+    // 支持自定义间隔
+    updateLabels(intervals) {
+        if (intervals && intervals.length > 0) {
+            // 使用传入的间隔
+            this.labelsElement.innerHTML = intervals
+                .map(val => `<span>${Number(val).toFixed(2)}</span>`)
+                .join('')
+        } else {
+            // 默认三等分
+            const mid = (this.options.min + this.options.max) / 2
+            this.labelsElement.innerHTML = `
+        <span>${this.options.max.toFixed(2)}</span>
+        <span>${mid.toFixed(2)}</span>
+        <span>${this.options.min.toFixed(2)}</span>
+      `
+        }
     }
 
     enableDrag() {

+ 267 - 223
src/utils/three/dataHandlers/PltHandler.js

@@ -99,7 +99,7 @@ export class PltHandler extends BaseDataHandler {
                 specular: 0x111111,
                 shininess: 30,
                 side: THREE.DoubleSide,
-                flatShading: false,
+                flatShading: options.smooth === false,
                 vertexColors: true, // 必须启用
                 transparent: true,
                 opacity: 1.0
@@ -310,6 +310,20 @@ export class PltHandler extends BaseDataHandler {
                 return;
             }
 
+            // 添加平滑处理
+            if (mesh.material) {
+                mesh.material.flatShading = options.smooth === false;
+                mesh.material.needsUpdate = true;
+
+                // 添加边缘高亮增强视觉效果
+                if (options.smooth === false) {
+                    mesh.material.wireframe = true;
+                    mesh.material.wireframeLinewidth = 1;
+                } else {
+                    mesh.material.wireframe = false;
+                }
+            }
+
             const colors = this.generateVertexColors(
                 zone.variables[variableName],
                 options.colorMap || 'rainbow',
@@ -340,6 +354,36 @@ export class PltHandler extends BaseDataHandler {
         return updated;
     }
 
+    /**
+ * 切换平滑渲染模式
+ * @param {boolean} smooth - 是否启用平滑
+ */
+    setSmoothShading(smooth) {
+        let changed = false;
+        this.objectMap.forEach(mesh => {
+            if (mesh.material) {
+                // 主要切换flatShading
+                mesh.material.flatShading = !smooth;
+
+                // 非平滑模式下添加wireframe增强效果
+                mesh.material.wireframe = !smooth;
+
+                // 非平滑模式下使用更简单的材质
+                if (!smooth) {
+                    mesh.material.shininess = 0;
+                    mesh.material.specular.set(0x000000);
+                } else {
+                    mesh.material.shininess = 30;
+                    mesh.material.specular.set(0x111111);
+                }
+
+                mesh.material.needsUpdate = true;
+                changed = true;
+            }
+        });
+        return changed;
+    }
+
 
     // 域控制
     setZoneVisibility(zoneName, visible) {
@@ -426,24 +470,24 @@ export class PltHandler extends BaseDataHandler {
  * @param {Object} options 配置
  * @returns {boolean} 是否全部成功
  */
-generateContoursForAllZones(variableName, options = {}) {
-    console.group("批量生成等值线调试信息");
-    let allSuccess = true;
-
-    this.objectMap.forEach((mesh, zoneName) => {
-      if (mesh.visible) {
-        const success = this.generateContour(zoneName, variableName, options);
-        if (!success) {
-          console.warn(`域 ${zoneName} 的等值线生成失败`);
-          allSuccess = false;
-        }
-      }
-    });
+    generateContoursForAllZones(variableName, options = {}) {
+        console.group("批量生成等值线调试信息");
+        let allSuccess = true;
+
+        this.objectMap.forEach((mesh, zoneName) => {
+            if (mesh.visible) {
+                const success = this.generateContour(zoneName, variableName, options);
+                if (!success) {
+                    console.warn(`域 ${zoneName} 的等值线生成失败`);
+                    allSuccess = false;
+                }
+            }
+        });
 
-    console.log("批量生成等值线完成", { allSuccess });
-    console.groupEnd();
-    return allSuccess;
-  }
+        console.log("批量生成等值线完成", { allSuccess });
+        console.groupEnd();
+        return allSuccess;
+    }
 
     /**
    * 生成等值线
@@ -452,140 +496,140 @@ generateContoursForAllZones(variableName, options = {}) {
    * @param {Object} options 配置
    */
     generateContour(zoneName, variableName, options = {}) {
-    console.group("生成等值线调试信息");
+        console.group("生成等值线调试信息");
 
-    const mesh = this.objectMap.get(zoneName);
-    if (!mesh) {
-      console.error("找不到指定区域的网格");
-      console.groupEnd();
-      return false;
-    }
+        const mesh = this.objectMap.get(zoneName);
+        if (!mesh) {
+            console.error("找不到指定区域的网格");
+            console.groupEnd();
+            return false;
+        }
 
-    const cacheKey = `${variableName}_${options.levels}_${options.minValue}_${options.maxValue}`;
-    if (mesh.userData.contourCache?.[cacheKey]) {
-      console.log(`使用缓存的等值线: ${zoneName}, ${variableName}`);
-      this.scene.add(mesh.userData.contourCache[cacheKey].group);
-      mesh.visible = false;
-      console.groupEnd();
-      return true;
-    }
+        const cacheKey = `${variableName}_${options.levels}_${options.minValue}_${options.maxValue}`;
+        if (mesh.userData.contourCache?.[cacheKey]) {
+            console.log(`使用缓存的等值线: ${zoneName}, ${variableName}`);
+            this.scene.add(mesh.userData.contourCache[cacheKey].group);
+            mesh.visible = false;
+            console.groupEnd();
+            return true;
+        }
 
-    const zoneData = mesh.userData.originalZone;
-    if (!zoneData?.variables?.[variableName]) {
-      console.error(`变量不存在`, {
-        zone: zoneName,
-        requestedVar: variableName,
-        availableVars: Object.keys(zoneData.variables || {})
-      });
-      console.groupEnd();
-      return false;
-    }
+        const zoneData = mesh.userData.originalZone;
+        if (!zoneData?.variables?.[variableName]) {
+            console.error(`变量不存在`, {
+                zone: zoneName,
+                requestedVar: variableName,
+                availableVars: Object.keys(zoneData.variables || {})
+            });
+            console.groupEnd();
+            return false;
+        }
 
-    const positions = mesh.geometry.attributes.position.array;
-    const scalarData = zoneData.variables[variableName];
+        const positions = mesh.geometry.attributes.position.array;
+        const scalarData = zoneData.variables[variableName];
 
-    if (positions.length / 3 !== scalarData.length) {
-      console.error("顶点数与标量数据长度不匹配", {
-        positionsLength: positions.length / 3,
-        scalarDataLength: scalarData.length
-      });
-      console.groupEnd();
-      return false;
-    }
+        if (positions.length / 3 !== scalarData.length) {
+            console.error("顶点数与标量数据长度不匹配", {
+                positionsLength: positions.length / 3,
+                scalarDataLength: scalarData.length
+            });
+            console.groupEnd();
+            return false;
+        }
 
-    // 数据清洗
-    const validIndices = [];
-    for (let i = 0; i < scalarData.length; i++) {
-      const x = positions[i * 3];
-      const y = positions[i * 3 + 1];
-      const z = positions[i * 3 + 2];
-      if (
-        !isNaN(scalarData[i]) && isFinite(scalarData[i]) &&
-        !isNaN(x) && !isNaN(y) && !isNaN(z) &&
-        isFinite(x) && isFinite(y) && isFinite(z)
-      ) {
-        validIndices.push(i);
-      }
-    }
+        // 数据清洗
+        const validIndices = [];
+        for (let i = 0; i < scalarData.length; i++) {
+            const x = positions[i * 3];
+            const y = positions[i * 3 + 1];
+            const z = positions[i * 3 + 2];
+            if (
+                !isNaN(scalarData[i]) && isFinite(scalarData[i]) &&
+                !isNaN(x) && !isNaN(y) && !isNaN(z) &&
+                isFinite(x) && isFinite(y) && isFinite(z)
+            ) {
+                validIndices.push(i);
+            }
+        }
 
-    if (validIndices.length === 0) {
-      console.error("无有效数据点");
-      console.groupEnd();
-      return false;
-    }
+        if (validIndices.length === 0) {
+            console.error("无有效数据点");
+            console.groupEnd();
+            return false;
+        }
 
-    // 创建清洁几何体
-    const cleanGeometry = new THREE.BufferGeometry();
-    const cleanPositions = new Float32Array(validIndices.length * 3);
-    const cleanScalars = new Float32Array(validIndices.length);
-    const cleanIndices = [];
-
-    const indexMap = new Map();
-    validIndices.forEach((origIdx, newIdx) => {
-      indexMap.set(origIdx, newIdx);
-      cleanPositions[newIdx * 3] = positions[origIdx * 3];
-      cleanPositions[newIdx * 3 + 1] = positions[origIdx * 3 + 1];
-      cleanPositions[newIdx * 3 + 2] = positions[origIdx * 3 + 2];
-      cleanScalars[newIdx] = scalarData[origIdx];
-    });
-
-    const originalIndices = mesh.geometry.index?.array || [];
-    for (let i = 0; i < originalIndices.length; i += 3) {
-      const i0 = indexMap.get(originalIndices[i]);
-      const i1 = indexMap.get(originalIndices[i + 1]);
-      const i2 = indexMap.get(originalIndices[i + 2]);
-      if (i0 !== undefined && i1 !== undefined && i2 !== undefined) {
-        cleanIndices.push(i0, i1, i2);
-      }
-    }
+        // 创建清洁几何体
+        const cleanGeometry = new THREE.BufferGeometry();
+        const cleanPositions = new Float32Array(validIndices.length * 3);
+        const cleanScalars = new Float32Array(validIndices.length);
+        const cleanIndices = [];
 
-    cleanGeometry.setAttribute('position', new THREE.BufferAttribute(cleanPositions, 3));
-    cleanGeometry.setIndex(new THREE.BufferAttribute(new Uint32Array(cleanIndices), 1));
-    cleanGeometry.computeVertexNormals();
-    cleanGeometry.computeBoundingSphere();
+        const indexMap = new Map();
+        validIndices.forEach((origIdx, newIdx) => {
+            indexMap.set(origIdx, newIdx);
+            cleanPositions[newIdx * 3] = positions[origIdx * 3];
+            cleanPositions[newIdx * 3 + 1] = positions[origIdx * 3 + 1];
+            cleanPositions[newIdx * 3 + 2] = positions[origIdx * 3 + 2];
+            cleanScalars[newIdx] = scalarData[origIdx];
+        });
 
-    if (isNaN(cleanGeometry.boundingSphere.radius)) {
-      console.error("边界球计算失败,使用默认值");
-      cleanGeometry.boundingSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0), 1);
-    }
+        const originalIndices = mesh.geometry.index?.array || [];
+        for (let i = 0; i < originalIndices.length; i += 3) {
+            const i0 = indexMap.get(originalIndices[i]);
+            const i1 = indexMap.get(originalIndices[i + 1]);
+            const i2 = indexMap.get(originalIndices[i + 2]);
+            if (i0 !== undefined && i1 !== undefined && i2 !== undefined) {
+                cleanIndices.push(i0, i1, i2);
+            }
+        }
 
-    // 生成等值线
-    const contours = this.calculateContours(cleanGeometry, cleanScalars, {
-      levels: options.levels || 5,
-      minValue: options.minValue ?? Math.min(...cleanScalars),
-      maxValue: options.maxValue ?? Math.max(...cleanScalars)
-    });
-
-    if (contours.length === 0) {
-      console.error("未生成有效等值线");
-      console.groupEnd();
-      return false;
-    }
+        cleanGeometry.setAttribute('position', new THREE.BufferAttribute(cleanPositions, 3));
+        cleanGeometry.setIndex(new THREE.BufferAttribute(new Uint32Array(cleanIndices), 1));
+        cleanGeometry.computeVertexNormals();
+        cleanGeometry.computeBoundingSphere();
+
+        if (isNaN(cleanGeometry.boundingSphere.radius)) {
+            console.error("边界球计算失败,使用默认值");
+            cleanGeometry.boundingSphere = new THREE.Sphere(new THREE.Vector3(0, 0, 0), 1);
+        }
 
-    // 创建等值线组
-    const contourGroup = new THREE.Group();
-    contourGroup.name = `${zoneName}_contour`;
+        // 生成等值线
+        const contours = this.calculateContours(cleanGeometry, cleanScalars, {
+            levels: options.levels || 5,
+            minValue: options.minValue ?? Math.min(...cleanScalars),
+            maxValue: options.maxValue ?? Math.max(...cleanScalars)
+        });
+
+        if (contours.length === 0) {
+            console.error("未生成有效等值线");
+            console.groupEnd();
+            return false;
+        }
 
-    contours.forEach(points => {
-      const lineGeometry = new THREE.BufferGeometry().setFromPoints(points);
-      contourGroup.add(new THREE.Line(lineGeometry, this.contourMaterial));
-    });
+        // 创建等值线组
+        const contourGroup = new THREE.Group();
+        contourGroup.name = `${zoneName}_contour`;
 
-    mesh.visible = false;
-    this.removeContour(zoneName);
-    mesh.userData.contourGroup = contourGroup;
-    this.scene.add(contourGroup);
+        contours.forEach(points => {
+            const lineGeometry = new THREE.BufferGeometry().setFromPoints(points);
+            contourGroup.add(new THREE.Line(lineGeometry, this.contourMaterial));
+        });
 
-    mesh.userData.contourCache = mesh.userData.contourCache || {};
-    mesh.userData.contourCache[cacheKey] = { group: contourGroup };
+        mesh.visible = false;
+        this.removeContour(zoneName);
+        mesh.userData.contourGroup = contourGroup;
+        this.scene.add(contourGroup);
 
-    console.log("等值线生成成功", {
-      points: cleanPositions.length / 3,
-      contours: contours.length
-    });
-    console.groupEnd();
-    return true;
-  }
+        mesh.userData.contourCache = mesh.userData.contourCache || {};
+        mesh.userData.contourCache[cacheKey] = { group: contourGroup };
+
+        console.log("等值线生成成功", {
+            points: cleanPositions.length / 3,
+            contours: contours.length
+        });
+        console.groupEnd();
+        return true;
+    }
 
     createCleanGeometry(originalGeometry, scalarData) {
         const positions = originalGeometry.attributes.position.array;
@@ -634,109 +678,109 @@ generateContoursForAllZones(variableName, options = {}) {
      * 计算等值线
      */
     calculateContours(geometry, scalarData, options) {
-    console.group("计算等值线调试信息");
-    const positions = geometry.attributes.position.array;
-    const indices = geometry.index?.array || [];
-    const { levels, minValue, maxValue } = options;
-    const contours = [];
-
-    try {
-      const step = (maxValue - minValue) / (levels || 1); // 防止除零
-
-      // 遍历三角面
-      for (let i = 0; i < indices.length; i += 3) {
-        const i0 = indices[i];
-        const i1 = indices[i + 1];
-        const i2 = indices[i + 2];
-
-        const v0 = new THREE.Vector3(positions[i0 * 3], positions[i0 * 3 + 1], positions[i0 * 3 + 2]);
-        const v1 = new THREE.Vector3(positions[i1 * 3], positions[i1 * 3 + 1], positions[i1 * 3 + 2]);
-        const v2 = new THREE.Vector3(positions[i2 * 3], positions[i2 * 3 + 1], positions[i2 * 3 + 2]);
-        const s0 = scalarData[i0];
-        const s1 = scalarData[i1];
-        const s2 = scalarData[i2];
-
-        if (isNaN(s0) || isNaN(s1) || isNaN(s2) || !isFinite(s0) || !isFinite(s1) || !isFinite(s2)) {
-          continue;
-        }
+        console.group("计算等值线调试信息");
+        const positions = geometry.attributes.position.array;
+        const indices = geometry.index?.array || [];
+        const { levels, minValue, maxValue } = options;
+        const contours = [];
+
+        try {
+            const step = (maxValue - minValue) / (levels || 1); // 防止除零
+
+            // 遍历三角面
+            for (let i = 0; i < indices.length; i += 3) {
+                const i0 = indices[i];
+                const i1 = indices[i + 1];
+                const i2 = indices[i + 2];
+
+                const v0 = new THREE.Vector3(positions[i0 * 3], positions[i0 * 3 + 1], positions[i0 * 3 + 2]);
+                const v1 = new THREE.Vector3(positions[i1 * 3], positions[i1 * 3 + 1], positions[i1 * 3 + 2]);
+                const v2 = new THREE.Vector3(positions[i2 * 3], positions[i2 * 3 + 1], positions[i2 * 3 + 2]);
+                const s0 = scalarData[i0];
+                const s1 = scalarData[i1];
+                const s2 = scalarData[i2];
+
+                if (isNaN(s0) || isNaN(s1) || isNaN(s2) || !isFinite(s0) || !isFinite(s1) || !isFinite(s2)) {
+                    continue;
+                }
+
+                for (let level = 0; level <= levels; level++) {
+                    const threshold = minValue + level * step;
+                    const points = this.calculateTriangleContour(v0, v1, v2, s0, s1, s2, threshold);
+                    if (points.length >= 2) { // 确保至少有 2 个点形成线段
+                        contours.push(points);
+                    }
+                }
+            }
 
-        for (let level = 0; level <= levels; level++) {
-          const threshold = minValue + level * step;
-          const points = this.calculateTriangleContour(v0, v1, v2, s0, s1, s2, threshold);
-          if (points.length >= 2) { // 确保至少有 2 个点形成线段
-            contours.push(points);
-          }
+            console.log("总等值线数量", contours.length);
+        } catch (error) {
+            console.error("等值线计算失败", error);
+        } finally {
+            console.groupEnd();
         }
-      }
 
-      console.log("总等值线数量", contours.length);
-    } catch (error) {
-      console.error("等值线计算失败", error);
-    } finally {
-      console.groupEnd();
+        return contours;
     }
 
-    return contours;
-  }
-
     calculateTriangleContour(v0, v1, v2, s0, s1, s2, threshold) {
-    const points = [];
+        const points = [];
 
-    // 验证标量值范围
-    const minScalar = Math.min(s0, s1, s2);
-    const maxScalar = Math.max(s0, s1, s2);
-    if (threshold < minScalar || threshold > maxScalar || !isFinite(threshold)) {
-      return points; // 阈值超出范围或无效
-    }
+        // 验证标量值范围
+        const minScalar = Math.min(s0, s1, s2);
+        const maxScalar = Math.max(s0, s1, s2);
+        if (threshold < minScalar || threshold > maxScalar || !isFinite(threshold)) {
+            return points; // 阈值超出范围或无效
+        }
 
-    // 检查三角形是否退化
-    const v01 = v1.clone().sub(v0).length();
-    const v12 = v2.clone().sub(v1).length();
-    const v20 = v0.clone().sub(v2).length();
-    if (v01 < 1e-6 || v12 < 1e-6 || v20 < 1e-6) {
-      return points; // 跳过退化三角形
-    }
+        // 检查三角形是否退化
+        const v01 = v1.clone().sub(v0).length();
+        const v12 = v2.clone().sub(v1).length();
+        const v20 = v0.clone().sub(v2).length();
+        if (v01 < 1e-6 || v12 < 1e-6 || v20 < 1e-6) {
+            return points; // 跳过退化三角形
+        }
 
-    // 计算交点
-    const edges = [
-      { vStart: v0, vEnd: v1, sStart: s0, sEnd: s1 }, // 边 0-1
-      { vStart: v1, vEnd: v2, sStart: s1, sEnd: s2 }, // 边 1-2
-      { vStart: v2, vEnd: v0, sStart: s2, sEnd: s0 }  // 边 2-0
-    ];
-
-    const edgePoints = [];
-    edges.forEach(({ vStart, vEnd, sStart, sEnd }) => {
-      if ((sStart <= threshold && sEnd >= threshold) || (sStart >= threshold && sEnd <= threshold)) {
-        const delta = sEnd - sStart;
-        if (Math.abs(delta) < 1e-6) return; // 防止除零
-        const t = (threshold - sStart) / delta;
-        if (t >= 0 && t <= 1) { // 确保插值参数有效
-          const point = vStart.clone().lerp(vEnd, t);
-          edgePoints.push(point);
+        // 计算交点
+        const edges = [
+            { vStart: v0, vEnd: v1, sStart: s0, sEnd: s1 }, // 边 0-1
+            { vStart: v1, vEnd: v2, sStart: s1, sEnd: s2 }, // 边 1-2
+            { vStart: v2, vEnd: v0, sStart: s2, sEnd: s0 }  // 边 2-0
+        ];
+
+        const edgePoints = [];
+        edges.forEach(({ vStart, vEnd, sStart, sEnd }) => {
+            if ((sStart <= threshold && sEnd >= threshold) || (sStart >= threshold && sEnd <= threshold)) {
+                const delta = sEnd - sStart;
+                if (Math.abs(delta) < 1e-6) return; // 防止除零
+                const t = (threshold - sStart) / delta;
+                if (t >= 0 && t <= 1) { // 确保插值参数有效
+                    const point = vStart.clone().lerp(vEnd, t);
+                    edgePoints.push(point);
+                }
+            }
+        });
+
+        // 每条等值线应有 2 个点
+        if (edgePoints.length === 2) {
+            points.push(...edgePoints);
         }
-      }
-    });
 
-    // 每条等值线应有 2 个点
-    if (edgePoints.length === 2) {
-      points.push(...edgePoints);
+        return points;
     }
 
-    return points;
-  }
-
     /**
      * 移除等值线
      */
     removeContour(zoneName) {
-    const mesh = this.objectMap.get(zoneName);
-    if (mesh?.userData?.contourGroup) {
-      this.scene.remove(mesh.userData.contourGroup);
-      mesh.userData.contourGroup.traverse(child => {
-        if (child.isLine) child.geometry.dispose();
-      });
-      delete mesh.userData.contourGroup;
-      delete mesh.userData.contourCache;
+        const mesh = this.objectMap.get(zoneName);
+        if (mesh?.userData?.contourGroup) {
+            this.scene.remove(mesh.userData.contourGroup);
+            mesh.userData.contourGroup.traverse(child => {
+                if (child.isLine) child.geometry.dispose();
+            });
+            delete mesh.userData.contourGroup;
+            delete mesh.userData.contourCache;
+        }
     }
-  }
 }