Bläddra i källkod

添加标签,添加模型 添加沉砂池模型

BAI 4 veckor sedan
förälder
incheckning
4ac3d9c2cf

+ 152 - 3
README.md

@@ -1,5 +1,154 @@
-# Vue 3 + TypeScript + Vite
+# ThreeEngine - 三维水利场景
 
-This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
+基于 Three.js 的水利三维可视化场景组件。
 
-Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
+## 嵌入到其他 Vue 项目
+
+### 1. 安装依赖
+
+```bash
+npm install three 3d-tiles-renderer @loaders.gl/3d-tiles @loaders.gl/core @loaders.gl/gltf
+```
+
+### 2. 拷贝项目
+
+将本项目 `src/` 目录下的以下内容拷贝到目标项目中:
+
+| 路径 | 说明 |
+|---|---|
+| `src/components/` | Vue 组件(Scene3D、WaterLevelLabel 等) |
+| `src/config/` | 场景配置文件 |
+| `src/assets/` | 纹理、模型、图标等静态资源 |
+
+### 3. 使用组件
+
+```vue
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import Scene3D from '@/components/Scene3D.vue'
+
+const sceneRef = ref<InstanceType<typeof Scene3D>>()
+
+onMounted(() => {
+  // 3 秒后更新标签值
+  setTimeout(() => {
+    sceneRef.value?.setLabelValue('6602380005', 1.58)
+  }, 3000)
+})
+</script>
+
+<template>
+  <Scene3D
+    ref="sceneRef"
+    :label-values="{ '6602380005': 0.00 }"
+    :show-debug-tools="false"
+    :tileset-url="'/scene/tileset.json'"
+  />
+</template>
+```
+
+## Props 说明
+
+| Prop | 类型 | 默认值 | 说明 |
+|---|---|---|---|
+| `labelValues` | `Record<string, number>` | `{}` | 标签数据值,`{ [标签id]: 数值 }` |
+| `showDebugTools` | `boolean` | `false` | 是否显示调试面板 |
+| `tilesetUrl` | `string` | `'/scene/tileset.json'` | 3D Tiles 数据地址 |
+| `cameraPosition` | `{ x, y, z }` | 默认位置 | 初始相机位置 |
+| `cameraTarget` | `{ x, y, z }` | 默认 target | 初始相机目标点 |
+
+## Expose API(通过 ref 调用)
+
+### 更新标签值
+
+```ts
+// 更新单个标签
+sceneRef.value?.setLabelValue('6602380005', 1.58)
+
+// 批量更新标签
+sceneRef.value?.setLabelValues({ '6602380005': 1.58, 'gate002': 0.5 })
+
+// 直接修改响应式数据
+sceneRef.value?.labelValues['6602380005'] = 3.0
+```
+
+### 相机控制
+
+```ts
+// 飞行动画(自由指定位置)
+sceneRef.value?.flyTo(
+  { x: -900, y: 60, z: -2100 },  // 目标位置
+  { x: -843, y: 12, z: -2182 },  // 目标看点(可选)
+  2000                             // 动画时长(毫秒)
+)
+
+// 按预设名称跳转(配置在 sceneConfig.ts 的 cameraPresets 中)
+sceneRef.value?.flyToPreset('default')      // 默认视角,时长 1500ms
+sceneRef.value?.flyToPreset('gate', 2000)   // 闸门视角,时长 2000ms
+
+// 查看所有预设
+console.log(sceneRef.value?.cameraPresets)
+```
+
+## 镜头预设配置
+
+在 `src/config/sceneConfig.ts` 中配置镜头预设:
+
+```ts
+export const cameraPresets: CameraPreset[] = [
+  {
+    id: 'default',
+    name: '默认视角',
+    positionX: -831.56685,
+    positionY: 40.63456,
+    positionZ: -2225.321,
+    targetX: -843.0744,
+    targetY: 12.01539,
+    targetZ: -2182.06814,
+  },
+]
+```
+
+## 标签配置
+
+在 `src/config/sceneConfig.ts` 中配置标签:
+
+```ts
+export const waterLevelLabels: WaterLevelLabelConfig[] = [
+  {
+    id: '6602380005',           // 唯一标识,外部传值时使用此 id
+    name: '莫勒切河节制分水闸闸后水量监测',
+    type: 'waterLevel',          // 标签类型,决定图标和文本
+    positionX: -830.11,          // 场景中的位置(外部无需关心)
+    positionY: 14,
+    positionZ: -2156.83,
+    initialValue: 0.00,         // 初始默认值
+  },
+]
+```
+
+## 扩展标签类型
+
+在 `sceneConfig.ts` 中注册新类型:
+
+```ts
+export type LabelDataType = 'waterLevel' | 'flowRate' | '你的新类型'
+
+export const labelTypeRegistry: Record<LabelDataType, LabelTypeDisplayConfig> = {
+  waterLevel: {
+    icon: 'shuiliang.png',
+    unit: 'm³/s',
+    label: '水 量',
+    decimalPlaces: 2,
+  },
+  flowRate: {
+    icon: 'shuiliang.png',
+    unit: 'm³/s',
+    label: '流 量',
+    decimalPlaces: 2,
+  },
+  // 新类型: 在这里新增
+}
+```
+
+之后在 `WaterLevelLabel.vue` 的 `iconMap` 中映射新的图标文件即可。

BIN
public/scene/NoLod_0.glb


+ 1 - 1
public/scene/scenetree.json

@@ -1 +1 @@
-{"scenes":[{"children":[{"id":"3416a75f4cea9109507cacd8e2f2aefc","name":"闸","sphere":[-2179096.0792544605,4389622.069486236,4068399.0148601905,63.889907076501096],"type":"element"},{"id":"a1d0c6e83f027327d8461063f4ac58a6","name":"DX","sphere":[-2177719.036569016,4388733.990990708,4070059.315678389,26234.70390046183],"type":"element"}],"id":"45c48cce2e2d7fbdea1afc51c7c6ad26","name":"场景拆分版","sphere":[-2177719.036569016,4388733.990990708,4070059.315678389,26234.70390046183],"type":"node"}]}
+{"scenes":[{"children":[{"id":"a1d0c6e83f027327d8461063f4ac58a6","name":"DX","sphere":[-2177719.745781851,4388735.40600303,4070060.6347709335,26250.620994359226],"type":"element"}],"id":"45c48cce2e2d7fbdea1afc51c7c6ad26","name":"场景拆分版2","sphere":[-2177719.745781851,4388735.40600303,4070060.6347709335,26250.620994359226],"type":"node"}]}

+ 1 - 1
public/scene/tileset.json

@@ -1 +1 @@
-{"asset":{"generatetool":"cesiumlab3@www.cesiumlab.com/model2tiles","version":"1.1"},"extras":{"scenetree":"scenetree.json"},"geometricError":15385.80027201958,"properties":null,"refine":"REPLACE","root":{"boundingVolume":{"box":[-9.313225746154785e-10,3.909726194338873,-7.194744524545968,7692.90013600979,0,0,0,7387.554117603926,0,0,0,17.15758981090039]},"children":[{"boundingVolume":{"box":[0.030583225913233036,3.902148799787028,0.592862920446823,7678.030533712026,0,0,0,7387.531734532295,0,0,0,16.370561965565482]},"content":{"uri":"NoLod_0.glb"},"geometricError":0.0,"refine":"REPLACE"}],"geometricError":15385.80027201958,"transform":[-0.895782671688543,-0.4444923003861105,0.0,0.0,0.28516089565293923,-0.5746830456842482,0.7670871271201791,0.0,-0.3409643217302211,0.6871433561496029,0.6415429365260833,0.0,-2177715.920661904,4388737.85641429,4070060.9170435746,1.0]}}
+{"asset":{"generatetool":"cesiumlab3@www.cesiumlab.com/model2tiles","version":"1.1"},"extras":{"scenetree":"scenetree.json"},"geometricError":15385.826170017943,"properties":null,"refine":"REPLACE","root":{"boundingVolume":{"box":[-31.594920691335574,10.475845690118149,-15.068455416709185,7692.913085008971,0,0,0,7387.598367458908,0,0,0,25.901169101707637]},"children":[{"boundingVolume":{"box":[-31.533750000000055,10.468281250000473,-7.902099151611415,7678.0,0,0,0,7387.499999999999,0,0,0,24.15320312500004]},"content":{"uri":"NoLod_0.glb"},"geometricError":0.0,"refine":"REPLACE"}],"geometricError":15385.826170017943,"transform":[-0.8957798079969456,-0.44449807151995063,0.0,0.0,0.2851642479174557,-0.5746805028278432,0.7670877859666446,0.0,-0.3409690415486823,0.6871417496300031,0.6415421487484598,0.0,-2177749.4494034126,4388734.4144854965,4070062.242923888,1.0]}}

+ 2 - 2
src/App.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
-import TestFoamScene from './components/Scene3D.vue'
+import Scene3D from './components/Scene3D.vue'
 </script>
 
 <template>
-  <TestFoamScene />
+  <Scene3D :show-debug-tools="true" />
 </template>

BIN
src/assets/CSCwater.glb


BIN
src/assets/icon/05个.png


BIN
src/assets/icon/shuiliang.png


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 513 - 120
src/components/Scene3D.vue


+ 169 - 0
src/components/WaterLevelLabel.vue

@@ -0,0 +1,169 @@
+<script setup lang="ts">
+import { watch, computed } from 'vue'
+import * as THREE from 'three'
+import { labelTypeRegistry, type LabelDataType } from '../config/sceneConfig'
+import shuiliangIcon from '../assets/icon/shuiliang.png'
+
+const props = defineProps<{
+  labelId: string
+  type: LabelDataType
+  positionX: number
+  positionY: number
+  positionZ: number
+  value: number
+}>()
+
+const iconMap: Record<string, string> = {
+  'shuiliang.png': shuiliangIcon,
+}
+
+const displayConfig = computed(() => labelTypeRegistry[props.type])
+
+let scene: THREE.Scene | null = null
+let camera: THREE.PerspectiveCamera | null = null
+let textSprite: THREE.Sprite | null = null
+let spriteTexture: THREE.CanvasTexture | null = null
+let iconReady = false
+let spriteCreated = false
+
+const canvas = document.createElement('canvas')
+const ctx = canvas.getContext('2d')!
+
+const ICON_W = 100
+const ICON_H = 100
+const CANVAS_SCALE = 2
+canvas.width = ICON_W * CANVAS_SCALE
+canvas.height = ICON_H * CANVAS_SCALE
+
+const iconImg = new Image()
+function loadIcon() {
+  const iconPath = displayConfig.value.icon
+  const resolvedIcon = iconMap[iconPath]
+  if (resolvedIcon) {
+    iconImg.src = resolvedIcon
+  } else {
+    iconImg.src = shuiliangIcon
+  }
+}
+loadIcon()
+
+iconImg.onload = () => {
+  iconReady = true
+  if (!spriteCreated) {
+    ensureSprite()
+  } else {
+    redrawTexture()
+  }
+}
+
+function redrawTexture() {
+  ctx.setTransform(1, 0, 0, 1, 0, 0)
+  ctx.clearRect(0, 0, canvas.width, canvas.height)
+  ctx.drawImage(iconImg, 0, 0, canvas.width, canvas.height)
+  ctx.setTransform(1, 0, 0, 1, 0, 0)
+  drawText()
+
+  if (spriteTexture) {
+    spriteTexture.needsUpdate = true
+  }
+}
+
+function drawText() {
+  const cfg = displayConfig.value
+  ctx.scale(CANVAS_SCALE, CANVAS_SCALE)
+  ctx.textAlign = 'left'
+  ctx.textBaseline = 'top'
+  ctx.font = 'bold 20px "Microsoft YaHei", Arial, sans-serif'
+  ctx.fillStyle = '#ffffff'
+  ctx.fillText(`${props.value.toFixed(cfg.decimalPlaces)}`, 37, 16)
+  ctx.font = '12px "Microsoft YaHei", Arial, sans-serif'
+  ctx.fillStyle = 'rgba(255,255,255,0.8)'
+  ctx.fillText(cfg.label, 37, 39)
+  ctx.setTransform(1, 0, 0, 1, 0, 0)
+}
+
+function ensureSprite() {
+  if (!scene || spriteCreated) return
+
+  if (!iconReady) {
+    ctx.setTransform(1, 0, 0, 1, 0, 0)
+    ctx.clearRect(0, 0, canvas.width, canvas.height)
+    ctx.setTransform(1, 0, 0, 1, 0, 0)
+    drawText()
+  } else {
+    ctx.clearRect(0, 0, canvas.width, canvas.height)
+    ctx.drawImage(iconImg, 0, 0, canvas.width, canvas.height)
+    drawText()
+  }
+
+  spriteTexture = new THREE.CanvasTexture(canvas)
+  spriteTexture.colorSpace = THREE.SRGBColorSpace
+  spriteTexture.needsUpdate = true
+
+  const material = new THREE.SpriteMaterial({
+    map: spriteTexture,
+    transparent: true,
+    depthTest: false,
+    depthWrite: false,
+    sizeAttenuation: true,
+  })
+
+  textSprite = new THREE.Sprite(material)
+  textSprite.position.set(props.positionX, props.positionY + 2.5 / 2, props.positionZ)
+  textSprite.scale.set(3.0, 2.5, 1)
+  textSprite.renderOrder = 999
+  textSprite.name = `label_${props.labelId}`
+  scene.add(textSprite)
+  spriteCreated = true
+}
+
+function init(s: THREE.Scene, c: THREE.PerspectiveCamera) {
+  scene = s
+  camera = c
+  if (iconReady) {
+    ensureSprite()
+  } else {
+    iconImg.onload = () => {
+      iconReady = true
+      ensureSprite()
+    }
+  }
+}
+
+function tick() {
+  if (!textSprite || !camera) return
+  const dist = camera.position.distanceTo(textSprite.position)
+  const refDist = 50.0
+  let s = dist / refDist
+  const minScale = 2.0
+  const maxScale = 20.0
+  s = Math.max(minScale, Math.min(maxScale, s))
+  textSprite.scale.set(s * 3.0, s * 2.2, 1)
+  textSprite.position.y = props.positionY + (s * 2.2) / 2
+}
+
+function dispose() {
+  if (textSprite && scene) {
+    scene.remove(textSprite)
+    textSprite.material.map?.dispose()
+    textSprite.material.dispose()
+    textSprite = null
+  }
+  if (spriteTexture) {
+    spriteTexture = null
+  }
+  spriteCreated = false
+  iconReady = false
+}
+
+watch(() => props.value, () => {
+  if (spriteCreated) {
+    redrawTexture()
+  }
+})
+
+defineExpose({ init, tick, dispose })
+</script>
+
+<template>
+</template>

+ 1 - 1
src/components/waterFlow.ts

@@ -71,7 +71,7 @@ void main() {
 
     float edgeFade = 1.0;
     if (uEdgeFade > 0.0) {
-      edgeFade = smoothstep(0.0, uEdgeFade, vUv.y) * (1.0 - smoothstep(1.0 - uEdgeFade, 1.0, vUv.y));
+      edgeFade = 1.0 - smoothstep(1.0 - uEdgeFade, 1.0, 1.0 - vUv.y);
     }
     combinedIntensity *= edgeFade;
 

+ 5 - 56
src/components/waterNew.ts

@@ -3,7 +3,6 @@ import * as THREE from 'three'
 const lakeVertexShader = `
 uniform float iTime;
 uniform float waveHeight;
-uniform float waveFrequency;
 uniform float flowSpeed;
 uniform vec2 flowDirection;
 
@@ -23,7 +22,7 @@ void main() {
     float time = iTime * 0.5;
 
     vec4 worldPos = modelMatrix * vec4(position, 1.0);
-    vec2 wavePos = worldPos.xz * waveFrequency;
+    vec2 wavePos = worldPos.xz;
 
     float h = 0.0;
     h += calcVertexWave(wavePos, time, flowDir, 1.00, 1.0, 1.20 * flowSpeed);
@@ -54,7 +53,6 @@ uniform float cameraNear;
 uniform float cameraFar;
 uniform float waterLevel;
 uniform float waveHeight;
-uniform float waveFrequency;
 uniform float flowSpeed;
 uniform vec3 shallowColor;
 uniform vec3 deepColor;
@@ -144,26 +142,7 @@ void computeWaves(out Wave waves[NUM_WAVES]) {
     waves[7] = Wave(normalize(mainDir + vec2(0.3, 0.5)),0.05, 10.5, 4.50, 0.05);
 }
 
-float calcWaveHeight(vec2 pos, float time, Wave w) {
-    return w.amp * sin(dot(pos, w.dir) * w.freq + time * w.speed);
-}
-
-vec2 calcWaveDerivative(vec2 pos, float time, Wave w) {
-    float cosVal = cos(dot(pos, w.dir) * w.freq + time * w.speed);
-    return w.amp * w.freq * w.dir * cosVal;
-}
-
-float totalWaveHeight(vec2 pos, float time, float wh, float wf) {
-    Wave waves[NUM_WAVES];
-    computeWaves(waves);
-    float h = 0.0;
-    for (int i = 0; i < NUM_WAVES; i++) {
-        h += calcWaveHeight(pos * wf, time, waves[i]);
-    }
-    return h * wh;
-}
-
-void computeWaveData(vec2 pos, float time, float wh, float wf, out float height, out vec3 normal, out vec2 displacement) {
+void computeWaveData(vec2 pos, float time, float wh, float wf, out float height, out vec2 displacement) {
     Wave waves[NUM_WAVES];
     computeWaves(waves);
 
@@ -183,17 +162,6 @@ void computeWaveData(vec2 pos, float time, float wh, float wf, out float height,
     height *= wh;
     deriv *= wh;
     displacement = disp * wh * 0.3;
-
-    vec3 n = normalize(vec3(-deriv.x, 1.0, -deriv.y));
-    normal = n;
-}
-
-float eddyNoise(vec2 pos, float time) {
-    vec2 p = pos * 0.8 + time * 0.08;
-    float n1 = noise(p);
-    float n2 = noise(p * 2.3 + 1.7);
-    float n3 = noise(p * 4.7 + 3.2);
-    return (n1 * 0.6 + n2 * 0.3 + n3 * 0.1);
 }
 
 float getCollisionFoam(vec2 pos, float depthDiff) {
@@ -225,34 +193,16 @@ void main() {
     vec2 flowDir = normalize(flowDirection);
     float time = iTime * 0.5;
 
-    vec2 wavePos = vWorldPosition.xz * waveFrequency;
+    vec2 wavePos = vWorldPosition.xz;
 
     float waveH;
-    vec3 waveN;
     vec2 waveDisp;
-    computeWaveData(wavePos, time, waveHeight * 0.2, waveFrequency, waveH, waveN, waveDisp);
-
-    vec2 displacedPos = wavePos + waveDisp * 0.3;
-
-    float choppyH;
-    vec3 choppyN;
-    vec2 choppyDisp;
-    computeWaveData(displacedPos, time, waveHeight * 0.15, waveFrequency * 1.3, choppyH, choppyN, choppyDisp);
-
-    vec3 finalNormal = normalize(waveN + choppyN * 0.4);
-
-    float eddy = eddyNoise(vWorldPosition.xz, time);
-    float eddyAngle = eddy * 4.0 - 2.0;
-    vec2 eddyDir = vec2(cos(eddyAngle), sin(eddyAngle));
-    vec3 eddyNormal = normalize(vec3(eddyDir.x * 0.08, 1.0, eddyDir.y * 0.08));
-
-    finalNormal = normalize(finalNormal + eddyNormal * 0.15);
+    computeWaveData(wavePos, time, waveHeight * 0.2, 1.0, waveH, waveDisp);
 
     vec2 waterUV = vWorldUV * waterNormalTiling + iTime * flowDir * flowSpeed * 0.10;
     vec3 waterMap = texture2D(waterNormalMap, waterUV).rgb;
     waterMap = waterMap * 2.0 - 1.0;
-    vec3 waterPerturb = vec3(waterMap.x * 0.3, 0.0, waterMap.y * 0.3) * waterNormalStrength;
-    finalNormal = normalize(finalNormal + waterPerturb);
+    vec3 finalNormal = normalize(vec3(waterMap.x * waterNormalStrength, 1.0, waterMap.y * waterNormalStrength));
 
     vec3 viewDir = normalize(cameraPos - vWorldPosition);
     vec3 halfVec = normalize(viewDir + sunDirection);
@@ -337,7 +287,6 @@ export const StylizedWaterMaterial = new THREE.ShaderMaterial({
         cameraFar: { value: 1000.0 },
         waterLevel: { value: 0.0 },
         waveHeight: { value: 0.6 },
-        waveFrequency: { value: 1.0 },
         flowSpeed: { value: 0.5 },
         shallowColor: { value: new THREE.Color('#566c67') },
         deepColor: { value: new THREE.Color('#0a2a4a') },

+ 359 - 0
src/config/sceneConfig.ts

@@ -0,0 +1,359 @@
+/** 模型变换参数类型 */
+export interface ModelTransform {
+  positionX: number
+  positionY: number
+  positionZ: number
+  rotationX: number
+  rotationY: number
+  rotationZ: number
+  scaleX: number
+  scaleY: number
+  scaleZ: number
+}
+
+/** 水面材质参数类型(主水面用) */
+export interface WaterMaterialParams {
+  alpha: number
+  waterColor: string
+  deepColor: string
+  flowSpeed: number
+  flowDirectionX: number
+  flowDirectionY: number
+  waveHeight: number
+  foamIntensity: number
+  specIntensity: number
+  specPower: number
+  fresnelPower: number
+  fresnelIntensity: number
+  depthRange: number
+  waterNormalStrength: number
+  waterNormalTiling: number
+  collisionFoamThreshold: number
+  collisionFoamStrength: number
+}
+
+/** CSCwater 材质参数类型(独立于主水面配置) */
+export type CscwaterMaterialParams = WaterMaterialParams
+
+/** 泡沫片面材质参数类型 */
+export interface FoamMaterialParams {
+  colour: string
+  opacity: number
+  waterfallSpeed: number
+  edgeMaskTiling: number
+  edgeMaskSpeed: number
+  fresnelExponent: number
+  directionalFoamIntensity: number
+  directionalFoamContrast: number
+  directionalFoam1Intensity: number
+  directionalFoam2Intensity: number
+  directionalFoam2Tiling: number
+  directionalFoam2Speed: number
+  directionalFoam3Intensity: number
+  foamFalloff: number
+  gradientTop: number
+  gradientBottom: number
+  gradientPower: number
+}
+
+/** 流动纹理材质参数类型 */
+export interface FlowMaterialParams {
+  colour: string
+  speedX: number
+  speedY: number
+  tilingU: number
+  tilingV: number
+  rotationAngle: number
+  power: number
+  waterGate: number
+  waterGate02: number
+  edgeMaskIntensity: number
+  opaquePower: number
+  edgeFade: number
+}
+
+// ==================== 模型变换默认值 ====================
+
+export const modelTransformMap: Record<string, ModelTransform> = {
+  foam: {
+    positionX: -854.3537,
+    positionY: 14.01539,
+    positionZ: -2175.46505,
+    rotationX: 91,
+    rotationY: 0,
+    rotationZ: 52,
+    scaleX: 7.5,
+    scaleY: 16,
+    scaleZ: 1,
+  },
+  water: {
+    positionX: -854.3537,
+    positionY: 14.01539,
+    positionZ: -2175.46505,
+    rotationX: -90,
+    rotationY: 0,
+    rotationZ: 0,
+    scaleX: 200,
+    scaleY: 200,
+    scaleZ: 1,
+  },
+  flow: {
+    positionX: -2150.4,
+    positionY: 19,
+    positionZ: -789.5,
+    rotationX: 0,
+    rotationY: 12,
+    rotationZ: 0,
+    scaleX: 0.2,
+    scaleY: 0.2,
+    scaleZ: 0.2,
+  },
+  cscwater: {
+    positionX: -1591.11,
+    positionY: 18.5,
+    positionZ: -275,
+    rotationX: 0,
+    rotationY: 0,
+    rotationZ: 0,
+    scaleX: 1,
+    scaleY: 1,
+    scaleZ: 1,
+  },
+}
+
+// ==================== 材质参数默认值 ====================
+
+/** 水面材质参数默认值 */
+export const defaultWaterParams: WaterMaterialParams = {
+  alpha: 0.85,
+  waterColor: '#566c67',
+  deepColor: '#0a2a4a',
+  flowSpeed: 0.5,
+  flowDirectionX: 1.0,
+  flowDirectionY: 1.0,
+  waveHeight: 0.6,
+  foamIntensity: 0.25,
+  specIntensity: 2.0,
+  specPower: 64.0,
+  fresnelPower: 2.5,
+  fresnelIntensity: 1.0,
+  depthRange: 44.0,
+  waterNormalStrength: 2.0,
+  waterNormalTiling: 0.26,
+  collisionFoamThreshold: 0.10,
+  collisionFoamStrength: 0.5,
+}
+
+/** CSCwater 材质参数默认值 */
+export const defaultCscwaterParams: CscwaterMaterialParams = {
+  alpha: 0.82,
+  waterColor: '#29537a',
+  deepColor: '#0a2a4a',
+  flowSpeed: 0,
+  flowDirectionX: 1.0,
+  flowDirectionY: 1.0,
+  waveHeight: 0.6,
+  foamIntensity: 0.30,
+  specIntensity: 2.2,
+  specPower: 168,
+  fresnelPower: 2.5,
+  fresnelIntensity: 1.0,
+  depthRange: 44.0,
+  waterNormalStrength: 0.15,
+  waterNormalTiling: 0.07,
+  collisionFoamThreshold: 0.39,
+  collisionFoamStrength: 0.45,
+}
+
+/** 泡沫片面材质参数默认值 */
+export const defaultFoamMatParams: FoamMaterialParams = {
+  colour: '#ffffff',
+  opacity: 1.0,
+  waterfallSpeed: 0.32,
+  edgeMaskTiling: 0.52,
+  edgeMaskSpeed: 1.7,
+  fresnelExponent: 9.9,
+  directionalFoamIntensity: 3.48,
+  directionalFoamContrast: 0.3,
+  directionalFoam1Intensity: 1.10,
+  directionalFoam2Intensity: 2.03,
+  directionalFoam2Tiling: 1.0,
+  directionalFoam2Speed: 8.5,
+  directionalFoam3Intensity: 1.76,
+  foamFalloff: 0.8,
+  gradientTop: 1.0,
+  gradientBottom: 0.0,
+  gradientPower: 1.0,
+}
+
+/** 流动纹理材质参数默认值 */
+export const defaultFlowParams: FlowMaterialParams = {
+  colour: '#ffffff',
+  speedX: -0.068,
+  speedY: 0.399,
+  tilingU: -0.85,
+  tilingV: 1.87,
+  rotationAngle: -0.22,
+  power: 1.13,
+  waterGate: 0.0,
+  waterGate02: 0.00,
+  edgeMaskIntensity: 1.00,
+  opaquePower: 0.82,
+  edgeFade: 0.50,
+}
+
+// ==================== 水位标签配置 ====================
+
+/** 标签数据类型 */
+export type LabelDataType = 'waterLevel' | 'flowRate'
+
+/** 标签类型对应的展示配置 */
+export interface LabelTypeDisplayConfig {
+  icon: string
+  unit: string
+  label: string
+  decimalPlaces: number
+}
+
+/** 标签类型注册表(可扩展) */
+export const labelTypeRegistry: Record<LabelDataType, LabelTypeDisplayConfig> = {
+  waterLevel: {
+    icon: 'shuiliang.png',
+    unit: 'm³/s',
+    label: '水 量',
+    decimalPlaces: 2,
+  },
+  flowRate: {
+    icon: 'shuiliang.png',
+    unit: 'm³/s',
+    label: '流 量',
+    decimalPlaces: 2,
+  },
+}
+
+/** 水位标签配置类型 */
+export interface WaterLevelLabelConfig {
+  id: string
+  name: string
+  type: LabelDataType
+  positionX: number
+  positionY: number
+  positionZ: number
+  initialValue: number
+}
+
+/** 水位标签列表(可扩展多个) */
+export const waterLevelLabels: WaterLevelLabelConfig[] = [
+  {
+    id: '6602380005',
+    name: '莫勒切河节制分水闸闸后水量监测',
+    type: 'waterLevel',
+    positionX: -797.282,
+    positionY: 14,
+    positionZ: -2091.159,
+    initialValue: 2.80,
+  },
+  {
+    id: '6602380006',
+    name: '莫勒切河引水渠水量监测',
+    type: 'waterLevel',
+    positionX: -921.134,
+    positionY: 14,
+    positionZ: -2124.428,
+    initialValue: 3.00,
+  },
+  {
+    id: '6602380003',
+    name: '二期沉砂池入库水量监测',
+    type: 'waterLevel',
+    positionX: -2162.313,
+    positionY: 23.156,
+    positionZ: -856.39,
+    initialValue: 3.00,
+  },  
+  {
+    id: '6602380001',
+    name: '二期沉砂池库内水位监测',
+    type: 'waterLevel',
+    positionX: -781.103,
+    positionY: 27.499,
+    positionZ: -194.081,
+    initialValue: 4.00,
+  },
+  {
+    id: '6602380004',
+    name: '二期沉砂池出库水量监测',
+    type: 'waterLevel',
+    positionX: -416.521,
+    positionY: 8,
+    positionZ: -188.334,
+    initialValue: 2.80,
+  },
+  {
+    id: '6602380023',
+    name: '引水干渠水量监测103+800',
+    type: 'waterLevel',
+    positionX: -3353.116,
+    positionY: 23.293,
+    positionZ: -1657.653,
+    initialValue: 2.80,
+  },
+]
+
+// ==================== 镜头预设 ====================
+
+/** 镜头预设配置类型 */
+export interface CameraPreset {
+  id: string
+  name: string
+  positionX: number
+  positionY: number
+  positionZ: number
+  targetX: number
+  targetY: number
+  targetZ: number
+}
+
+/** 镜头预设列表 */
+export const cameraPresets: CameraPreset[] = [
+  {
+    id: '0',
+    name: '莫勒切河节制分水闸',
+    positionX: -831.56685,
+    positionY: 40.63456,
+    positionZ: -2225.321,
+    targetX: -843.0744,
+    targetY: 12.01539,
+    targetZ: -2182.06814,
+  },
+  {
+    id: '1',
+    name: '二期沉砂池入库',
+    positionX: -2337.807,
+    positionY: 129.119,
+    positionZ: -1025.484,
+    targetX: -2240.868,
+    targetY: 12.015,
+    targetZ: -908.998,
+  },
+  {
+    id: '2',
+    name: '二期沉砂池入库',
+    positionX: -2337.807,
+    positionY: 129.119,
+    positionZ: -1025.484,
+    targetX: -2240.868,
+    targetY: 12.015,
+    targetZ: -908.998,
+  },
+  {
+    id: '3',
+    name: '二期沉砂池出库',
+    positionX: -798.636,
+    positionY: 260.226,
+    positionZ: -718.195,
+    targetX: -710.424,
+    targetY: 12.015,
+    targetZ: -293.213,
+  },
+]

+ 7 - 0
src/index.ts

@@ -0,0 +1,7 @@
+import Scene3D from './components/Scene3D.vue'
+import WaterLevelLabel from './components/WaterLevelLabel.vue'
+export * from './config/sceneConfig'
+
+export { Scene3D, WaterLevelLabel }
+
+export type { WaterLevelLabelConfig } from './config/sceneConfig'

Vissa filer visades inte eftersom för många filer har ändrats