Scene3D.vue 89 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496
  1. <script setup lang="ts">
  2. import { onMounted, onUnmounted, ref, reactive, watch } from 'vue'
  3. import * as THREE from 'three'
  4. import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
  5. import { Sky } from 'three/examples/jsm/objects/Sky.js'
  6. import { StylizedWaterMaterial } from '../materials/waterNew'
  7. import { createWaterFoamUEMaterial } from '../materials/waterFoamUE'
  8. import { createWaterFlowMaterial } from '../materials/waterFlow'
  9. import foamTexUrl from '../assets/texture/T_Waterfall_Foam.PNG'
  10. import directionalFoamTexUrl from '../assets/texture/T_Waterfall_Foam_Directional.PNG'
  11. import flowTexUrl from '../assets/texture/FlowTexture/T_FlowTexture_BC.PNG'
  12. import foamMacroTexUrl from '../assets/texture/FlowTexture/T_FoamMacro_BC.PNG'
  13. import maskTexUrl from '../assets/texture/FlowTexture/MASK_003.PNG'
  14. import flowNormalTexUrl from '../assets/texture/FlowTexture/T_FlowTexture_BC_NORM.PNG'
  15. import foamMacroNormalTexUrl from '../assets/texture/FlowTexture/T_FoamMacro_BC_NORM.PNG'
  16. import langGLBUrl from '../assets/lang.glb'
  17. import cscwaterGLBUrl from '../assets/CSCwater.glb'
  18. import water02GLBUrl from '../assets/water02.glb'
  19. import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
  20. import { TilesRenderer } from '3d-tiles-renderer'
  21. import { ReorientationPlugin } from '3d-tiles-renderer/plugins'
  22. import WaterLevelLabel from './WaterLevelLabel.vue'
  23. import {
  24. modelTransformMap as configModelTransformMap,
  25. defaultWaterParams,
  26. defaultCscwaterParams,
  27. defaultFoamMatParams,
  28. defaultFlowParams,
  29. type ModelTransform,
  30. type WaterMaterialParams,
  31. type CscwaterMaterialParams,
  32. type FoamMaterialParams,
  33. type FlowMaterialParams,
  34. sceneLabels,
  35. type WaterLevelLabelConfig,
  36. cameraPresets,
  37. labelToPresetMap,
  38. } from '../config/sceneConfig'
  39. // ==================== 外部 Props(嵌入其他项目时使用)====================
  40. const props = withDefaults(defineProps<{
  41. labelValues?: Record<string, number>
  42. showDebugTools?: boolean
  43. tilesetUrl?: string
  44. cameraPosition?: { x: number; y: number; z: number }
  45. cameraTarget?: { x: number; y: number; z: number }
  46. }>(), {
  47. labelValues: () => ({}),
  48. showDebugTools: false,
  49. tilesetUrl: '/scene/tileset.json',
  50. cameraPosition: () => ({ x: -831.56685, y: 40.63456, z: -2225.321 }),
  51. cameraTarget: () => ({ x: -843.0744, y: 12.01539, z: -2182.06814 }),
  52. })
  53. // 继承 3D Tiles 渲染器,修复 asset 字段缺失的兼容性问题
  54. class SuperMapTilesRenderer extends TilesRenderer {
  55. preprocessTileset(json: any, url: string, parent = null) {
  56. if (!json.asset) {
  57. json.asset = { version: '1.0' }
  58. }
  59. const parentProto = Object.getPrototypeOf(Object.getPrototypeOf(this))
  60. parentProto.preprocessTileset.call(this, json, url, parent)
  61. }
  62. }
  63. // 3D 场景挂载点的 DOM 引用
  64. const containerRef = ref<HTMLDivElement>()
  65. // 底部提示消息弹窗
  66. function showToast(msg: string) {
  67. const el = document.createElement('div')
  68. el.className = 'toast-message'
  69. el.textContent = msg
  70. document.body.appendChild(el)
  71. setTimeout(() => {
  72. el.classList.add('toast-fade')
  73. setTimeout(() => el.remove(), 300)
  74. }, 1500)
  75. }
  76. // 鼠标拾取到的坐标
  77. const pickedPosition = ref<{ x: number; y: number; z: number } | null>({ x: 0, y: 0, z: 0 })
  78. // 相机位置/目标/距离等信息
  79. const cameraInfo = ref({
  80. position: { x: 0, y: 0, z: 0 },
  81. target: { x: 0, y: 0, z: 0 },
  82. distance: 0,
  83. minDistance: 0,
  84. maxDistance: 0,
  85. })
  86. // 调试面板的显示开关
  87. const showCoordinatePanel = ref(false)
  88. const showCameraPanel = ref(false)
  89. const showModelPanel = ref(false)
  90. const showMaterialPanel = ref(false)
  91. const showCameraPresetPanel = ref(false)
  92. // ========== 水面材质参数 ==========
  93. const waterParams = ref<WaterMaterialParams>({ ...defaultWaterParams })
  94. // ========== water02(一期沉砂池水面)独立材质参数 ==========
  95. const water02Params = ref<WaterMaterialParams>({ ...defaultWaterParams })
  96. // ========== CSCwater 独立材质参数(与主水面分开调节)==========
  97. const cscwaterParams = ref<CscwaterMaterialParams>({ ...defaultCscwaterParams })
  98. // ========== 泡沫片面材质参数 ==========
  99. const foamMatParams = ref<FoamMaterialParams>({ ...defaultFoamMatParams })
  100. // ========== 流动纹理材质参数 ==========
  101. const flowParams = ref<FlowMaterialParams>({ ...defaultFlowParams })
  102. // ---- 从 localStorage 恢复/保存材质默认值 ----
  103. const FLOW_DEFAULTS_KEY = 'flowMatParams_defaults'
  104. function loadFlowDefaults() {
  105. const saved = localStorage.getItem(FLOW_DEFAULTS_KEY)
  106. if (saved) {
  107. try {
  108. const parsed = JSON.parse(saved)
  109. Object.assign(flowParams.value, parsed)
  110. } catch (e) {
  111. console.warn('Failed to parse flow defaults:', e)
  112. }
  113. }
  114. }
  115. function saveFlowDefaults() {
  116. localStorage.setItem(FLOW_DEFAULTS_KEY, JSON.stringify(flowParams.value))
  117. showToast('流动纹理材质默认值已保存')
  118. }
  119. // 将 flow 材质参数同步到着色器的 uniform 变量
  120. function syncFlowParams() {
  121. if (!flowMaterial) return
  122. const p = flowParams.value
  123. flowMaterial.uniforms.uColor.value.set(p.colour)
  124. flowMaterial.uniforms.uSpeedX.value = p.speedX
  125. flowMaterial.uniforms.uSpeedY.value = p.speedY
  126. flowMaterial.uniforms.uTilingU.value = p.tilingU
  127. flowMaterial.uniforms.uTilingV.value = p.tilingV
  128. flowMaterial.uniforms.uRotationAngle.value = p.rotationAngle
  129. flowMaterial.uniforms.uPower.value = p.power
  130. flowMaterial.uniforms.uWaterGate.value = p.waterGate
  131. flowMaterial.uniforms.uWaterGate02.value = p.waterGate02
  132. flowMaterial.uniforms.uEdgeMaskIntensity.value = p.edgeMaskIntensity
  133. flowMaterial.uniforms.uOpaquePower.value = p.opaquePower
  134. flowMaterial.uniforms.uEdgeFade.value = p.edgeFade
  135. }
  136. // 将 cscwater 材质参数同步到着色器的 uniform 变量
  137. function syncCscwaterParams() {
  138. if (!cscwaterMaterial) return
  139. const p = cscwaterParams.value
  140. cscwaterMaterial.uniforms.alpha.value = p.alpha
  141. cscwaterMaterial.uniforms.flowSpeed.value = p.flowSpeed
  142. cscwaterMaterial.uniforms.flowDirection.value.set(p.flowDirectionX, p.flowDirectionY)
  143. cscwaterMaterial.uniforms.normalRotation.value = p.normalRotation
  144. cscwaterMaterial.uniforms.waveHeight.value = p.waveHeight
  145. cscwaterMaterial.uniforms.shallowColor.value.set(p.waterColor)
  146. cscwaterMaterial.uniforms.deepColor.value.set(p.deepColor)
  147. cscwaterMaterial.uniforms.foamIntensity.value = p.foamIntensity
  148. cscwaterMaterial.uniforms.specIntensity.value = p.specIntensity
  149. cscwaterMaterial.uniforms.specPower.value = p.specPower
  150. cscwaterMaterial.uniforms.fresnelPower.value = p.fresnelPower
  151. cscwaterMaterial.uniforms.fresnelIntensity.value = p.fresnelIntensity
  152. cscwaterMaterial.uniforms.depthRange.value = p.depthRange
  153. cscwaterMaterial.uniforms.waterNormalStrength.value = p.waterNormalStrength
  154. cscwaterMaterial.uniforms.waterNormalTiling.value = p.waterNormalTiling
  155. cscwaterMaterial.uniforms.collisionFoamThreshold.value = p.collisionFoamThreshold
  156. cscwaterMaterial.uniforms.collisionFoamStrength.value = p.collisionFoamStrength
  157. }
  158. const FOAM_DEFAULTS_KEY = 'foamMatParams_defaults'
  159. function loadFoamDefaults() {
  160. const saved = localStorage.getItem(FOAM_DEFAULTS_KEY)
  161. if (saved) {
  162. try {
  163. const parsed = JSON.parse(saved)
  164. Object.assign(foamMatParams.value, parsed)
  165. } catch (e) {
  166. console.warn('Failed to parse foam defaults:', e)
  167. }
  168. }
  169. }
  170. function saveFoamDefaults() {
  171. localStorage.setItem(FOAM_DEFAULTS_KEY, JSON.stringify(foamMatParams.value))
  172. showToast('泡沫材质默认值已保存')
  173. }
  174. const WATER_DEFAULTS_KEY = 'waterParams_defaults'
  175. function loadWaterDefaults() {
  176. const saved = localStorage.getItem(WATER_DEFAULTS_KEY)
  177. if (saved) {
  178. try {
  179. const parsed = JSON.parse(saved)
  180. Object.assign(waterParams.value, parsed)
  181. } catch (e) {
  182. console.warn('Failed to parse water defaults:', e)
  183. }
  184. }
  185. }
  186. function saveWaterDefaults() {
  187. localStorage.setItem(WATER_DEFAULTS_KEY, JSON.stringify(waterParams.value))
  188. showToast('水面材质默认值已保存')
  189. }
  190. const WATER02_DEFAULTS_KEY = 'water02Params_defaults'
  191. function loadWater02Defaults() {
  192. const saved = localStorage.getItem(WATER02_DEFAULTS_KEY)
  193. if (saved) {
  194. try {
  195. const parsed = JSON.parse(saved)
  196. Object.assign(water02Params.value, parsed)
  197. } catch (e) {
  198. console.warn('Failed to parse water02 defaults:', e)
  199. }
  200. }
  201. }
  202. function saveWater02Defaults() {
  203. localStorage.setItem(WATER02_DEFAULTS_KEY, JSON.stringify(water02Params.value))
  204. showToast('一期沉砂池水面材质默认值已保存')
  205. }
  206. const CSCWATER_DEFAULTS_KEY = 'cscwaterParams_defaults'
  207. function loadCscwaterDefaults() {
  208. const saved = localStorage.getItem(CSCWATER_DEFAULTS_KEY)
  209. if (saved) {
  210. try {
  211. const parsed = JSON.parse(saved)
  212. Object.assign(cscwaterParams.value, parsed)
  213. } catch (e) {
  214. console.warn('Failed to parse cscwater defaults:', e)
  215. }
  216. }
  217. }
  218. function saveCscwaterDefaults() {
  219. localStorage.setItem(CSCWATER_DEFAULTS_KEY, JSON.stringify(cscwaterParams.value))
  220. showToast('CSCwater 材质默认值已保存')
  221. }
  222. const MODEL_DEFAULTS_KEY = 'modelTransform_defaults'
  223. function loadModelDefaults() {
  224. const saved = localStorage.getItem(MODEL_DEFAULTS_KEY)
  225. if (saved) {
  226. try {
  227. const data = JSON.parse(saved)
  228. for (const key in data) {
  229. if (modelTransformMap[key]) {
  230. Object.assign(modelTransformMap[key], data[key])
  231. }
  232. }
  233. } catch (e) {
  234. console.warn('Failed to parse model transform defaults:', e)
  235. }
  236. }
  237. }
  238. function saveModelDefaults() {
  239. const t = modelTransform.value
  240. modelTransformMap[selectedModelKey.value] = {
  241. positionX: t.positionX,
  242. positionY: t.positionY,
  243. positionZ: t.positionZ,
  244. rotationX: t.rotationX,
  245. rotationY: t.rotationY,
  246. rotationZ: t.rotationZ,
  247. scaleX: t.scaleX,
  248. scaleY: t.scaleY,
  249. scaleZ: t.scaleZ,
  250. }
  251. const data: Record<string, typeof modelTransformMap.foam> = {}
  252. for (const key in modelTransformMap) {
  253. data[key] = { ...modelTransformMap[key] }
  254. }
  255. localStorage.setItem(MODEL_DEFAULTS_KEY, JSON.stringify(data))
  256. showToast('模型变换默认值已保存')
  257. }
  258. // 场景中可调试的模型列表,按 key 索引
  259. const modelList: Record<string, THREE.Object3D | null> = {}
  260. const selectedModelKey = ref('foam')
  261. const materialList: Record<string, THREE.Material | null> = {}
  262. const selectedMaterialKey = ref('water')
  263. // 各模型的初始变换参数(位置/旋转/缩放)
  264. const modelTransformMap: Record<string, ModelTransform> = {}
  265. for (const key of Object.keys(configModelTransformMap)) {
  266. modelTransformMap[key] = { ...configModelTransformMap[key] }
  267. }
  268. // 加载保存的模型变换默认值
  269. loadModelDefaults()
  270. // 当前选中的模型变换值(双向绑定)
  271. const modelTransform = ref({ ...modelTransformMap.foam })
  272. // 将 modelTransform 的数值应用到 Three.js 物体上
  273. function applyModelTransform(key: string) {
  274. const obj = modelList[key]
  275. if (!obj) return
  276. const t = modelTransform.value
  277. if ((key === 'foam' || key === 'foam2') && 'isMesh' in obj) {
  278. const mesh = obj as THREE.Mesh
  279. mesh.position.set(t.positionX, t.positionY, t.positionZ)
  280. mesh.rotation.order = 'YXZ'
  281. mesh.rotation.set(
  282. THREE.MathUtils.degToRad(t.rotationX),
  283. THREE.MathUtils.degToRad(t.rotationY),
  284. THREE.MathUtils.degToRad(t.rotationZ)
  285. )
  286. if (mesh.geometry.type === 'PlaneGeometry') {
  287. const geom = mesh.geometry as THREE.PlaneGeometry
  288. geom.dispose()
  289. mesh.geometry = new THREE.PlaneGeometry(t.scaleX, t.scaleY)
  290. }
  291. } else if (key === 'water') {
  292. obj.position.set(t.positionX, t.positionY, t.positionZ)
  293. obj.rotation.order = 'YXZ'
  294. obj.rotation.set(
  295. THREE.MathUtils.degToRad(t.rotationX),
  296. THREE.MathUtils.degToRad(t.rotationY),
  297. THREE.MathUtils.degToRad(t.rotationZ)
  298. )
  299. } else if (key === 'water2') {
  300. obj.position.set(t.positionX, t.positionY, t.positionZ)
  301. obj.rotation.order = 'YXZ'
  302. obj.rotation.set(
  303. THREE.MathUtils.degToRad(t.rotationX),
  304. THREE.MathUtils.degToRad(t.rotationY),
  305. THREE.MathUtils.degToRad(t.rotationZ)
  306. )
  307. } else if (key === 'flow') {
  308. obj.position.set(t.positionX, t.positionY, t.positionZ)
  309. obj.rotation.order = 'YXZ'
  310. obj.rotation.set(
  311. THREE.MathUtils.degToRad(t.rotationX),
  312. THREE.MathUtils.degToRad(t.rotationY),
  313. THREE.MathUtils.degToRad(t.rotationZ)
  314. )
  315. obj.scale.set(t.scaleX, t.scaleY, t.scaleZ)
  316. } else if (key === 'cscwater') {
  317. obj.position.set(t.positionX, t.positionY, t.positionZ)
  318. obj.rotation.order = 'YXZ'
  319. obj.rotation.set(
  320. THREE.MathUtils.degToRad(t.rotationX),
  321. THREE.MathUtils.degToRad(t.rotationY),
  322. THREE.MathUtils.degToRad(t.rotationZ)
  323. )
  324. obj.scale.set(t.scaleX, t.scaleY, t.scaleZ)
  325. } else if (key === 'water02') {
  326. obj.position.set(t.positionX, t.positionY, t.positionZ)
  327. obj.rotation.order = 'YXZ'
  328. obj.rotation.set(
  329. THREE.MathUtils.degToRad(t.rotationX),
  330. THREE.MathUtils.degToRad(t.rotationY),
  331. THREE.MathUtils.degToRad(t.rotationZ)
  332. )
  333. obj.scale.set(t.scaleX, t.scaleY, t.scaleZ)
  334. }
  335. }
  336. // 切换模型时,加载该模型保存的变换参数
  337. watch(selectedModelKey, (key) => {
  338. const saved = modelTransformMap[key]
  339. if (saved) {
  340. modelTransform.value = { ...saved }
  341. }
  342. })
  343. // 变换参数变化时实时应用到场景
  344. watch(modelTransform, () => {
  345. applyModelTransform(selectedModelKey.value)
  346. }, { deep: true })
  347. // 水位标签
  348. interface LabelData {
  349. config: WaterLevelLabelConfig
  350. componentRef: ReturnType<typeof ref<InstanceType<typeof WaterLevelLabel> | null>>
  351. }
  352. const labelDataList: LabelData[] = []
  353. const labelValues = reactive<Record<string, number>>({})
  354. function initLabelData() {
  355. labelDataList.length = 0
  356. for (const cfg of sceneLabels.main) {
  357. const externalValue = props.labelValues[cfg.id]
  358. labelValues[cfg.id] = externalValue !== undefined ? externalValue : cfg.initialValue
  359. labelDataList.push({
  360. config: cfg,
  361. componentRef: ref<InstanceType<typeof WaterLevelLabel> | null>(null),
  362. })
  363. }
  364. }
  365. initLabelData()
  366. // ========== Three.js 核心变量声明 ==========
  367. let scene: THREE.Scene
  368. let camera: THREE.PerspectiveCamera
  369. let renderer: THREE.WebGLRenderer
  370. let controls: OrbitControls
  371. let sky: Sky
  372. let waterMesh: THREE.Mesh
  373. let water2Mesh: THREE.Mesh | null = null
  374. let foamMesh: THREE.Mesh | null = null
  375. let foamMaterial: THREE.ShaderMaterial | null = null
  376. let foam2Mesh: THREE.Mesh | null = null
  377. let foam2Material: THREE.ShaderMaterial | null = null
  378. let flowMesh: THREE.Mesh | null = null
  379. let flowMaterial: THREE.ShaderMaterial | null = null
  380. let cscwaterModel: THREE.Object3D | null = null
  381. let cscwaterMaterial: THREE.ShaderMaterial | null = null
  382. let water02Model: THREE.Object3D | null = null
  383. let water02Material: THREE.ShaderMaterial | null = null
  384. let sunDirection: THREE.Vector3
  385. let animationId: number
  386. let tilesRenderer: SuperMapTilesRenderer | null = null
  387. let raycaster: THREE.Raycaster
  388. let mouse: THREE.Vector2
  389. let depthRenderTarget: THREE.WebGLRenderTarget
  390. let sceneInitialized = false
  391. // 创建天空背景(含体积云效果)
  392. function createSky() {
  393. sky = new Sky()
  394. sky.scale.setScalar(50000)
  395. scene.add(sky)
  396. const uniforms = sky.material.uniforms
  397. uniforms.turbidity.value = 6
  398. uniforms.rayleigh.value = 0.1
  399. uniforms.mieCoefficient.value = 0.005
  400. uniforms.mieDirectionalG.value = 0.7
  401. uniforms.sunPosition.value.set(20, 30, 10)
  402. uniforms.cloudScale.value = 0.0008
  403. uniforms.cloudSpeed.value = 0.00015
  404. uniforms.cloudCoverage.value = 0.6
  405. uniforms.cloudDensity.value = 0.6
  406. uniforms.cloudElevation.value = 0.9
  407. }
  408. // 创建水面网格(使用自定义风格化水材质)
  409. function createWaterSurface() {
  410. const t = modelTransformMap.water
  411. waterMesh = new THREE.Mesh(
  412. new THREE.PlaneGeometry(t.scaleX, t.scaleY, 120, 120),
  413. StylizedWaterMaterial
  414. )
  415. waterMesh.rotation.order = 'YXZ'
  416. waterMesh.rotation.set(
  417. THREE.MathUtils.degToRad(t.rotationX),
  418. THREE.MathUtils.degToRad(t.rotationY),
  419. THREE.MathUtils.degToRad(t.rotationZ)
  420. )
  421. waterMesh.position.set(t.positionX, t.positionY, t.positionZ)
  422. waterMesh.receiveShadow = true
  423. waterMesh.name = 'water'
  424. waterMesh.renderOrder = 0
  425. scene.add(waterMesh)
  426. modelList['water'] = waterMesh
  427. materialList['water'] = StylizedWaterMaterial
  428. StylizedWaterMaterial.uniforms.cameraPos.value.copy(camera.position)
  429. StylizedWaterMaterial.uniforms.sunDirection.value.copy(sunDirection)
  430. StylizedWaterMaterial.uniforms.cameraNear.value = camera.near
  431. StylizedWaterMaterial.uniforms.cameraFar.value = camera.far
  432. const container = containerRef.value!
  433. StylizedWaterMaterial.uniforms.iResolution.value.set(container.clientWidth, container.clientHeight)
  434. }
  435. // 创建第二片面(使用与主水面相同的风格化水材质)
  436. function createWater2Surface() {
  437. const t = modelTransformMap.water2
  438. water2Mesh = new THREE.Mesh(
  439. new THREE.PlaneGeometry(t.scaleX, t.scaleY, 120, 120),
  440. StylizedWaterMaterial
  441. )
  442. water2Mesh.rotation.order = 'YXZ'
  443. water2Mesh.rotation.set(
  444. THREE.MathUtils.degToRad(t.rotationX),
  445. THREE.MathUtils.degToRad(t.rotationY),
  446. THREE.MathUtils.degToRad(t.rotationZ)
  447. )
  448. water2Mesh.position.set(t.positionX, t.positionY, t.positionZ)
  449. water2Mesh.receiveShadow = true
  450. water2Mesh.name = 'water2'
  451. water2Mesh.renderOrder = 0
  452. scene.add(water2Mesh)
  453. modelList['water2'] = water2Mesh
  454. }
  455. // 创建泡沫片面(瀑布泡沫效果)
  456. function createWaterFoamSurface() {
  457. const textureLoader = new THREE.TextureLoader()
  458. const foamTexture = textureLoader.load(foamTexUrl)
  459. foamTexture.wrapS = THREE.RepeatWrapping
  460. foamTexture.wrapT = THREE.RepeatWrapping
  461. const directionalFoamTexture = textureLoader.load(directionalFoamTexUrl)
  462. directionalFoamTexture.wrapS = THREE.RepeatWrapping
  463. directionalFoamTexture.wrapT = THREE.RepeatWrapping
  464. foamMaterial = createWaterFoamUEMaterial({
  465. colour: new THREE.Color(foamMatParams.value.colour),
  466. opacity: foamMatParams.value.opacity,
  467. waterfallSpeed: foamMatParams.value.waterfallSpeed,
  468. edgeMaskTiling: foamMatParams.value.edgeMaskTiling,
  469. edgeMaskSpeed: foamMatParams.value.edgeMaskSpeed,
  470. fresnelExponent: foamMatParams.value.fresnelExponent,
  471. directionalFoamIntensity: foamMatParams.value.directionalFoamIntensity,
  472. directionalFoamContrast: foamMatParams.value.directionalFoamContrast,
  473. directionalFoam1Intensity: foamMatParams.value.directionalFoam1Intensity,
  474. directionalFoam2Intensity: foamMatParams.value.directionalFoam2Intensity,
  475. directionalFoam2Tiling: foamMatParams.value.directionalFoam2Tiling,
  476. directionalFoam2Speed: foamMatParams.value.directionalFoam2Speed,
  477. directionalFoam3Intensity: foamMatParams.value.directionalFoam3Intensity,
  478. foamFalloff: foamMatParams.value.foamFalloff,
  479. gradientTop: foamMatParams.value.gradientTop,
  480. gradientBottom: foamMatParams.value.gradientBottom,
  481. gradientPower: foamMatParams.value.gradientPower,
  482. foamTexture,
  483. directionalFoamTexture,
  484. })
  485. const t = modelTransformMap.foam
  486. const geometry = new THREE.PlaneGeometry(t.scaleX, t.scaleY)
  487. foamMesh = new THREE.Mesh(geometry, foamMaterial)
  488. foamMesh.rotation.order = 'YXZ'
  489. foamMesh.rotation.set(
  490. THREE.MathUtils.degToRad(t.rotationX),
  491. THREE.MathUtils.degToRad(t.rotationY),
  492. THREE.MathUtils.degToRad(t.rotationZ)
  493. )
  494. foamMesh.position.set(t.positionX, t.positionY, t.positionZ)
  495. foamMesh.name = 'foam'
  496. foamMesh.renderOrder = 1
  497. scene.add(foamMesh)
  498. modelList['foam'] = foamMesh
  499. materialList['foam'] = foamMaterial
  500. }
  501. // 创建第二个泡沫片面(一期沉砂池泡沫效果)
  502. function createWaterFoamSurface2() {
  503. const textureLoader = new THREE.TextureLoader()
  504. const foamTexture = textureLoader.load(foamTexUrl)
  505. foamTexture.wrapS = THREE.RepeatWrapping
  506. foamTexture.wrapT = THREE.RepeatWrapping
  507. const directionalFoamTexture = textureLoader.load(directionalFoamTexUrl)
  508. directionalFoamTexture.wrapS = THREE.RepeatWrapping
  509. directionalFoamTexture.wrapT = THREE.RepeatWrapping
  510. foam2Material = createWaterFoamUEMaterial({
  511. colour: new THREE.Color(foamMatParams.value.colour),
  512. opacity: foamMatParams.value.opacity,
  513. waterfallSpeed: foamMatParams.value.waterfallSpeed,
  514. edgeMaskTiling: foamMatParams.value.edgeMaskTiling,
  515. edgeMaskSpeed: foamMatParams.value.edgeMaskSpeed,
  516. fresnelExponent: foamMatParams.value.fresnelExponent,
  517. directionalFoamIntensity: foamMatParams.value.directionalFoamIntensity,
  518. directionalFoamContrast: foamMatParams.value.directionalFoamContrast,
  519. directionalFoam1Intensity: foamMatParams.value.directionalFoam1Intensity,
  520. directionalFoam2Intensity: foamMatParams.value.directionalFoam2Intensity,
  521. directionalFoam2Tiling: foamMatParams.value.directionalFoam2Tiling,
  522. directionalFoam2Speed: foamMatParams.value.directionalFoam2Speed,
  523. directionalFoam3Intensity: foamMatParams.value.directionalFoam3Intensity,
  524. foamFalloff: foamMatParams.value.foamFalloff,
  525. gradientTop: foamMatParams.value.gradientTop,
  526. gradientBottom: foamMatParams.value.gradientBottom,
  527. gradientPower: foamMatParams.value.gradientPower,
  528. foamTexture,
  529. directionalFoamTexture,
  530. })
  531. const t = modelTransformMap.foam2
  532. const geometry = new THREE.PlaneGeometry(t.scaleX, t.scaleY)
  533. foam2Mesh = new THREE.Mesh(geometry, foam2Material)
  534. foam2Mesh.rotation.order = 'YXZ'
  535. foam2Mesh.rotation.set(
  536. THREE.MathUtils.degToRad(t.rotationX),
  537. THREE.MathUtils.degToRad(t.rotationY),
  538. THREE.MathUtils.degToRad(t.rotationZ)
  539. )
  540. foam2Mesh.position.set(t.positionX, t.positionY, t.positionZ)
  541. foam2Mesh.name = 'foam2'
  542. foam2Mesh.renderOrder = 1
  543. scene.add(foam2Mesh)
  544. modelList['foam2'] = foam2Mesh
  545. materialList['foam2'] = foam2Material
  546. }
  547. // 加载流动水纹理模型(GLB 模型+流动纹理材质)
  548. function loadWaterFlowModel() {
  549. const textureLoader = new THREE.TextureLoader()
  550. const flowTexture = textureLoader.load(flowTexUrl)
  551. flowTexture.wrapS = THREE.RepeatWrapping
  552. flowTexture.wrapT = THREE.RepeatWrapping
  553. const foamMacroTexture = textureLoader.load(foamMacroTexUrl)
  554. foamMacroTexture.wrapS = THREE.RepeatWrapping
  555. foamMacroTexture.wrapT = THREE.RepeatWrapping
  556. const maskTexture = textureLoader.load(maskTexUrl)
  557. maskTexture.wrapS = THREE.RepeatWrapping
  558. maskTexture.wrapT = THREE.RepeatWrapping
  559. const flowNormalTexture = textureLoader.load(flowNormalTexUrl)
  560. flowNormalTexture.wrapS = THREE.RepeatWrapping
  561. flowNormalTexture.wrapT = THREE.RepeatWrapping
  562. const foamMacroNormalTexture = textureLoader.load(foamMacroNormalTexUrl)
  563. foamMacroNormalTexture.wrapS = THREE.RepeatWrapping
  564. foamMacroNormalTexture.wrapT = THREE.RepeatWrapping
  565. flowMaterial = createWaterFlowMaterial({
  566. flowTexture,
  567. foamMacroTexture,
  568. maskTexture,
  569. flowNormalTexture,
  570. foamMacroNormalTexture,
  571. })
  572. syncFlowParams()
  573. const loader = new GLTFLoader()
  574. loader.load(langGLBUrl, (gltf) => {
  575. const object = gltf.scene
  576. object.traverse((child) => {
  577. if ((child as THREE.Mesh).isMesh) {
  578. const mesh = child as THREE.Mesh
  579. mesh.material = flowMaterial!
  580. mesh.castShadow = true
  581. mesh.receiveShadow = true
  582. mesh.frustumCulled = false
  583. mesh.renderOrder = 1
  584. flowMesh = mesh
  585. }
  586. })
  587. const t = modelTransformMap.flow
  588. object.position.set(t.positionX, t.positionY, t.positionZ)
  589. object.rotation.order = 'YXZ'
  590. object.rotation.set(
  591. THREE.MathUtils.degToRad(t.rotationX),
  592. THREE.MathUtils.degToRad(t.rotationY),
  593. THREE.MathUtils.degToRad(t.rotationZ)
  594. )
  595. object.scale.set(t.scaleX, t.scaleY, t.scaleZ)
  596. object.name = 'flow'
  597. scene.add(object)
  598. modelList['flow'] = object
  599. materialList['flow'] = flowMaterial
  600. })
  601. }
  602. // 加载 CSCwater.glb 模型
  603. function loadCSCWaterModel() {
  604. cscwaterMaterial = StylizedWaterMaterial.clone()
  605. cscwaterMaterial.uniforms = THREE.UniformsUtils.clone(StylizedWaterMaterial.uniforms)
  606. syncCscwaterParams()
  607. const loader = new GLTFLoader()
  608. loader.load(cscwaterGLBUrl, (gltf) => {
  609. const object = gltf.scene
  610. object.traverse((child) => {
  611. if ((child as THREE.Mesh).isMesh) {
  612. const mesh = child as THREE.Mesh
  613. mesh.material = cscwaterMaterial!
  614. mesh.castShadow = true
  615. mesh.receiveShadow = true
  616. mesh.frustumCulled = false
  617. mesh.renderOrder = 1
  618. }
  619. })
  620. const t = modelTransformMap.cscwater
  621. object.position.set(t.positionX, t.positionY, t.positionZ)
  622. object.rotation.order = 'YXZ'
  623. object.rotation.set(
  624. THREE.MathUtils.degToRad(t.rotationX),
  625. THREE.MathUtils.degToRad(t.rotationY),
  626. THREE.MathUtils.degToRad(t.rotationZ)
  627. )
  628. object.scale.set(t.scaleX, t.scaleY, t.scaleZ)
  629. object.name = 'cscwater'
  630. scene.add(object)
  631. modelList['cscwater'] = object
  632. cscwaterModel = object
  633. materialList['cscwater'] = cscwaterMaterial
  634. })
  635. }
  636. // 加载 water02.glb 模型
  637. function loadWater02Model() {
  638. water02Material = StylizedWaterMaterial.clone()
  639. water02Material.uniforms = THREE.UniformsUtils.clone(StylizedWaterMaterial.uniforms)
  640. water02Material.polygonOffset = true
  641. water02Material.polygonOffsetFactor = 5
  642. water02Material.polygonOffsetUnits = 5
  643. const loader = new GLTFLoader()
  644. loader.load(water02GLBUrl, (gltf) => {
  645. const object = gltf.scene
  646. object.traverse((child) => {
  647. if ((child as THREE.Mesh).isMesh) {
  648. const mesh = child as THREE.Mesh
  649. mesh.material = water02Material!
  650. mesh.castShadow = true
  651. mesh.receiveShadow = true
  652. mesh.frustumCulled = false
  653. mesh.renderOrder = 1
  654. mesh.material.polygonOffset = true
  655. mesh.material.polygonOffsetFactor = 1
  656. mesh.material.polygonOffsetUnits = 1
  657. }
  658. })
  659. const t = modelTransformMap.water02
  660. object.position.set(t.positionX, t.positionY, t.positionZ)
  661. object.rotation.order = 'YXZ'
  662. object.rotation.set(
  663. THREE.MathUtils.degToRad(t.rotationX),
  664. THREE.MathUtils.degToRad(t.rotationY),
  665. THREE.MathUtils.degToRad(t.rotationZ)
  666. )
  667. object.scale.set(t.scaleX, t.scaleY, t.scaleZ)
  668. object.name = 'water02'
  669. scene.add(object)
  670. modelList['water02'] = object
  671. water02Model = object
  672. materialList['water02'] = water02Material
  673. })
  674. }
  675. // 加载超图 3D Tiles 瓦片数据(大范围三维场景)
  676. async function load3DTiles() {
  677. const tilesetUrl = props.tilesetUrl
  678. tilesRenderer = new SuperMapTilesRenderer(tilesetUrl)
  679. tilesRenderer.setCamera(camera)
  680. tilesRenderer.setResolutionFromRenderer(camera, renderer)
  681. tilesRenderer.registerPlugin(new ReorientationPlugin())
  682. scene.add(tilesRenderer.group)
  683. scene.fog = null
  684. tilesRenderer.addEventListener('load-error', (evt) => {
  685. console.error('3D Tiles load error:', evt.error, 'URL:', evt.url)
  686. })
  687. tilesRenderer.addEventListener('load-tile', (evt) => {
  688. console.log('Tile loaded:', evt.tile.content?.uri)
  689. })
  690. tilesRenderer.addEventListener('error-tile', (evt) => {
  691. console.error('Tile error:', evt.tile.content?.uri, evt.error)
  692. })
  693. console.log('3D Tiles renderer initialized:', tilesetUrl)
  694. }
  695. // 释放所有 Three.js 资源(可在 initScene 之前和 onUnmounted 时反复调用)
  696. function disposeScene() {
  697. cancelAnimationFrame(animationId)
  698. animationId = 0
  699. labelDataList.forEach(d => d.componentRef.value?.dispose())
  700. if (tilesRenderer) {
  701. if (scene) scene.remove(tilesRenderer.group)
  702. tilesRenderer.dispose?.()
  703. tilesRenderer = null
  704. }
  705. if (foamMesh) {
  706. if (scene) scene.remove(foamMesh)
  707. foamMesh.geometry.dispose()
  708. if (foamMaterial) {
  709. for (const key of Object.keys(foamMaterial.uniforms)) {
  710. const val = foamMaterial.uniforms[key].value
  711. if (val instanceof THREE.Texture) val.dispose()
  712. }
  713. foamMaterial.dispose()
  714. }
  715. foamMesh = null
  716. foamMaterial = null
  717. }
  718. if (flowMesh) {
  719. const parent = flowMesh.parent
  720. if (parent && scene) scene.remove(parent)
  721. flowMesh.geometry.dispose()
  722. if (flowMaterial) {
  723. for (const key of Object.keys(flowMaterial.uniforms)) {
  724. const val = flowMaterial.uniforms[key].value
  725. if (val instanceof THREE.Texture) val.dispose()
  726. }
  727. flowMaterial.dispose()
  728. }
  729. flowMesh = null
  730. flowMaterial = null
  731. }
  732. if (cscwaterModel) {
  733. if (scene) scene.remove(cscwaterModel)
  734. cscwaterModel.traverse((child) => {
  735. const mesh = child as THREE.Mesh
  736. if (mesh.isMesh) {
  737. mesh.geometry?.dispose()
  738. if (Array.isArray(mesh.material)) {
  739. mesh.material.forEach(m => m.dispose())
  740. } else {
  741. mesh.material?.dispose()
  742. }
  743. }
  744. })
  745. cscwaterModel = null
  746. }
  747. if (cscwaterMaterial) {
  748. cscwaterMaterial.dispose()
  749. cscwaterMaterial = null
  750. }
  751. if (water02Model) {
  752. if (scene) scene.remove(water02Model)
  753. water02Model.traverse((child) => {
  754. const mesh = child as THREE.Mesh
  755. if (mesh.isMesh) {
  756. mesh.geometry?.dispose()
  757. if (Array.isArray(mesh.material)) {
  758. mesh.material.forEach(m => m.dispose())
  759. } else {
  760. mesh.material?.dispose()
  761. }
  762. }
  763. })
  764. water02Model = null
  765. }
  766. if (water02Material) {
  767. water02Material.dispose()
  768. water02Material = null
  769. }
  770. if (waterMesh) {
  771. if (scene) scene.remove(waterMesh)
  772. waterMesh.geometry.dispose()
  773. waterMesh = null
  774. }
  775. if (water2Mesh) {
  776. if (scene) scene.remove(water2Mesh)
  777. water2Mesh.geometry.dispose()
  778. water2Mesh = null
  779. }
  780. if (StylizedWaterMaterial) {
  781. StylizedWaterMaterial.dispose()
  782. }
  783. if (sky) {
  784. if (scene) scene.remove(sky)
  785. sky.material.dispose()
  786. sky = null
  787. }
  788. if (depthRenderTarget) {
  789. depthRenderTarget.depthTexture?.dispose()
  790. depthRenderTarget.dispose()
  791. depthRenderTarget = null
  792. }
  793. if (controls) {
  794. controls.dispose()
  795. controls = null
  796. }
  797. if (renderer) {
  798. const domEl = renderer.domElement
  799. if (domEl && domEl.parentNode) {
  800. domEl.parentNode.removeChild(domEl)
  801. }
  802. renderer.forceContextLoss()
  803. renderer.dispose()
  804. renderer = null
  805. }
  806. if (scene) {
  807. while (scene.children.length > 0) {
  808. scene.remove(scene.children[0])
  809. }
  810. scene = null
  811. }
  812. camera = null
  813. sceneInitialized = false
  814. }
  815. // 初始化整个 Three.js 场景(带防重入保护)
  816. function initScene() {
  817. if (sceneInitialized) {
  818. disposeScene()
  819. }
  820. const container = containerRef.value!
  821. scene = new THREE.Scene()
  822. camera = new THREE.PerspectiveCamera(60, container.clientWidth / container.clientHeight, 0.1, 100000)
  823. camera.position.set(props.cameraPosition.x, props.cameraPosition.y, props.cameraPosition.z)
  824. renderer = new THREE.WebGLRenderer({ antialias: true })
  825. renderer.setSize(container.clientWidth, container.clientHeight)
  826. renderer.domElement.style.width = '100%'
  827. renderer.domElement.style.height = '100%'
  828. renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
  829. renderer.toneMapping = THREE.ACESFilmicToneMapping
  830. renderer.toneMappingExposure = 1.0
  831. renderer.shadowMap.enabled = true
  832. renderer.shadowMap.type = THREE.PCFShadowMap
  833. container.appendChild(renderer.domElement)
  834. const pixelWidth = Math.floor(container.clientWidth * window.devicePixelRatio)
  835. const pixelHeight = Math.floor(container.clientHeight * window.devicePixelRatio)
  836. depthRenderTarget = new THREE.WebGLRenderTarget(pixelWidth, pixelHeight)
  837. depthRenderTarget.depthTexture = new THREE.DepthTexture(pixelWidth, pixelHeight)
  838. controls = new OrbitControls(camera, renderer.domElement)
  839. controls.enableDamping = false
  840. controls.screenSpacePanning = false
  841. controls.minDistance = 0
  842. controls.maxDistance = Infinity
  843. controls.mouseButtons = {
  844. LEFT: THREE.MOUSE.PAN,
  845. MIDDLE: THREE.MOUSE.DOLLY,
  846. RIGHT: THREE.MOUSE.ROTATE,
  847. }
  848. controls.target.set(props.cameraTarget.x, props.cameraTarget.y, props.cameraTarget.z)
  849. controls.maxPolarAngle = Math.PI / 2.1
  850. controls.minDistance = 5
  851. controls.maxDistance = 500
  852. createSky()
  853. const hemisphereLight = new THREE.HemisphereLight(0xd4d4d4, 0x3d6b4a, 0.6)
  854. scene.add(hemisphereLight)
  855. sunDirection = new THREE.Vector3(20, 30, 10).normalize()
  856. const sunLight = new THREE.DirectionalLight(0xffeedd, 2.0)
  857. sunLight.position.set(20, 30, 10)
  858. sunLight.castShadow = true
  859. sunLight.shadow.mapSize.width = 2048
  860. sunLight.shadow.mapSize.height = 2048
  861. sunLight.shadow.camera.near = 0.5
  862. sunLight.shadow.camera.far = 60
  863. sunLight.shadow.camera.left = -20
  864. sunLight.shadow.camera.right = 20
  865. sunLight.shadow.camera.top = 20
  866. sunLight.shadow.camera.bottom = -20
  867. scene.add(sunLight)
  868. createWaterSurface()
  869. createWater2Surface()
  870. createWaterFoamSurface()
  871. createWaterFoamSurface2()
  872. loadWaterFlowModel()
  873. loadCSCWaterModel()
  874. loadWater02Model()
  875. load3DTiles()
  876. labelDataList.forEach(d => d.componentRef.value?.init(scene, camera))
  877. initRaycaster()
  878. sceneInitialized = true
  879. animate()
  880. }
  881. // 初始化鼠标射线拾取
  882. function initRaycaster() {
  883. raycaster = new THREE.Raycaster()
  884. mouse = new THREE.Vector2()
  885. const container = containerRef.value!
  886. container.addEventListener('click', onMouseClick)
  887. }
  888. // 鼠标点击获取 3D 场景中的坐标
  889. function onMouseClick(event: MouseEvent) {
  890. const target = event.target as HTMLElement
  891. if (target.closest('.panel') || target.closest('.toolbar')) return
  892. const container = containerRef.value!
  893. const rect = container.getBoundingClientRect()
  894. mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
  895. mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1
  896. raycaster.setFromCamera(mouse, camera)
  897. // 首先检测标签精灵(图标)点击
  898. const labelSprites: THREE.Object3D[] = []
  899. labelDataList.forEach(d => {
  900. const sprite = d.componentRef.value?.getSprite()
  901. if (sprite) {
  902. labelSprites.push(sprite)
  903. }
  904. })
  905. const labelIntersects = raycaster.intersectObjects(labelSprites, true)
  906. if (labelIntersects.length > 0) {
  907. const clickedSprite = labelIntersects[0].object as THREE.Sprite
  908. const labelId = clickedSprite.name.replace('label_', '')
  909. const presetId = labelToPresetMap[labelId]
  910. if (presetId) {
  911. flyToPreset(presetId)
  912. return
  913. }
  914. }
  915. // 然后检测其他物体
  916. const intersectObjects: THREE.Object3D[] = []
  917. if (tilesRenderer?.group) {
  918. intersectObjects.push(tilesRenderer.group)
  919. }
  920. if (waterMesh) {
  921. intersectObjects.push(waterMesh)
  922. }
  923. const intersects = raycaster.intersectObjects(intersectObjects, true)
  924. if (intersects.length > 0) {
  925. const point = intersects[0].point
  926. pickedPosition.value = {
  927. x: Number(point.x.toFixed(3)),
  928. y: Number(point.y.toFixed(3)),
  929. z: Number(point.z.toFixed(3)),
  930. }
  931. }
  932. }
  933. // 每帧渲染循环
  934. function animate() {
  935. animationId = requestAnimationFrame(animate)
  936. sky.material.uniforms.time.value += 0.001
  937. controls.update()
  938. tilesRenderer?.update()
  939. // 将水面的深度写入深度纹理(用于水面透明交互动效)
  940. waterMesh.visible = false
  941. const prevRT = renderer.getRenderTarget()
  942. renderer.setRenderTarget(depthRenderTarget)
  943. renderer.render(scene, camera)
  944. renderer.setRenderTarget(prevRT)
  945. waterMesh.visible = true
  946. // 更新各材质的时间 uniform 实现动画
  947. StylizedWaterMaterial.uniforms.depthSampler.value = depthRenderTarget.depthTexture
  948. StylizedWaterMaterial.uniforms.iTime.value += 0.016
  949. StylizedWaterMaterial.uniforms.collisionFoamTime.value += 0.016
  950. if (foamMaterial) {
  951. foamMaterial.uniforms.uTime.value += 0.016
  952. foamMaterial.uniforms.uCameraPos.value.copy(camera.position)
  953. }
  954. if (foam2Material) {
  955. foam2Material.uniforms.uTime.value += 0.016
  956. foam2Material.uniforms.uCameraPos.value.copy(camera.position)
  957. }
  958. if (flowMaterial) {
  959. flowMaterial.uniforms.uTime.value += 0.016
  960. }
  961. if (cscwaterMaterial) {
  962. cscwaterMaterial.uniforms.depthSampler.value = depthRenderTarget.depthTexture
  963. cscwaterMaterial.uniforms.iTime.value += 0.016
  964. cscwaterMaterial.uniforms.collisionFoamTime.value += 0.016
  965. cscwaterMaterial.uniforms.cameraPos.value.copy(camera.position)
  966. }
  967. if (water02Material) {
  968. water02Material.uniforms.depthSampler.value = depthRenderTarget.depthTexture
  969. water02Material.uniforms.iTime.value += 0.016
  970. water02Material.uniforms.collisionFoamTime.value += 0.016
  971. water02Material.uniforms.cameraPos.value.copy(camera.position)
  972. }
  973. labelDataList.forEach(d => d.componentRef.value?.tick())
  974. StylizedWaterMaterial.uniforms.cameraPos.value.copy(camera.position)
  975. // 更新相机信息面板
  976. cameraInfo.value.position = {
  977. x: Number(camera.position.x.toFixed(3)),
  978. y: Number(camera.position.y.toFixed(3)),
  979. z: Number(camera.position.z.toFixed(3)),
  980. }
  981. cameraInfo.value.target = {
  982. x: Number(controls.target.x.toFixed(3)),
  983. y: Number(controls.target.y.toFixed(3)),
  984. z: Number(controls.target.z.toFixed(3)),
  985. }
  986. cameraInfo.value.distance = Number(camera.position.distanceTo(controls.target).toFixed(3))
  987. cameraInfo.value.minDistance = controls.minDistance
  988. cameraInfo.value.maxDistance = controls.maxDistance
  989. renderer.render(scene, camera)
  990. }
  991. // 窗口大小变化自适应
  992. function onResize() {
  993. if (!containerRef.value) return
  994. const width = containerRef.value.clientWidth
  995. const height = containerRef.value.clientHeight
  996. camera.aspect = width / height
  997. camera.updateProjectionMatrix()
  998. renderer.setSize(width, height)
  999. const pixelWidth = Math.floor(width * window.devicePixelRatio)
  1000. const pixelHeight = Math.floor(height * window.devicePixelRatio)
  1001. depthRenderTarget.setSize(pixelWidth, pixelHeight)
  1002. StylizedWaterMaterial.uniforms.iResolution.value.set(width, height)
  1003. if (tilesRenderer) {
  1004. tilesRenderer.setResolutionFromRenderer(camera, renderer)
  1005. }
  1006. }
  1007. onMounted(() => {
  1008. initScene()
  1009. window.addEventListener('resize', onResize)
  1010. })
  1011. // 组件销毁时释放所有 Three.js 资源
  1012. onUnmounted(() => {
  1013. window.removeEventListener('resize', onResize)
  1014. if (containerRef.value) {
  1015. containerRef.value.removeEventListener('click', onMouseClick)
  1016. }
  1017. disposeScene()
  1018. })
  1019. // ========== 材质参数的响应式监听,实时同步到着色器 ==========
  1020. function syncWaterParams() {
  1021. if (!StylizedWaterMaterial || !StylizedWaterMaterial.uniforms) return
  1022. const p = waterParams.value
  1023. StylizedWaterMaterial.uniforms.alpha.value = p.alpha
  1024. StylizedWaterMaterial.uniforms.flowSpeed.value = p.flowSpeed
  1025. StylizedWaterMaterial.uniforms.flowDirection.value.set(p.flowDirectionX, p.flowDirectionY)
  1026. StylizedWaterMaterial.uniforms.normalRotation.value = p.normalRotation
  1027. StylizedWaterMaterial.uniforms.waveHeight.value = p.waveHeight
  1028. StylizedWaterMaterial.uniforms.shallowColor.value.set(p.waterColor)
  1029. StylizedWaterMaterial.uniforms.deepColor.value.set(p.deepColor)
  1030. StylizedWaterMaterial.uniforms.foamIntensity.value = p.foamIntensity
  1031. StylizedWaterMaterial.uniforms.specIntensity.value = p.specIntensity
  1032. StylizedWaterMaterial.uniforms.specPower.value = p.specPower
  1033. StylizedWaterMaterial.uniforms.fresnelPower.value = p.fresnelPower
  1034. StylizedWaterMaterial.uniforms.fresnelIntensity.value = p.fresnelIntensity
  1035. StylizedWaterMaterial.uniforms.depthRange.value = p.depthRange
  1036. StylizedWaterMaterial.uniforms.waterNormalStrength.value = p.waterNormalStrength
  1037. StylizedWaterMaterial.uniforms.waterNormalTiling.value = p.waterNormalTiling
  1038. StylizedWaterMaterial.uniforms.collisionFoamThreshold.value = p.collisionFoamThreshold
  1039. StylizedWaterMaterial.uniforms.collisionFoamStrength.value = p.collisionFoamStrength
  1040. StylizedWaterMaterial.uniforms.fresnelDistanceNear.value = p.fresnelDistanceNear
  1041. StylizedWaterMaterial.uniforms.fresnelDistanceFar.value = p.fresnelDistanceFar
  1042. }
  1043. watch(() => waterParams.value, syncWaterParams, { deep: true })
  1044. syncWaterParams()
  1045. function syncWater02Params() {
  1046. if (!water02Material || !water02Material.uniforms) return
  1047. const p = water02Params.value
  1048. water02Material.uniforms.alpha.value = p.alpha
  1049. water02Material.uniforms.flowSpeed.value = p.flowSpeed
  1050. water02Material.uniforms.flowDirection.value.set(p.flowDirectionX, p.flowDirectionY)
  1051. water02Material.uniforms.normalRotation.value = p.normalRotation
  1052. water02Material.uniforms.waveHeight.value = p.waveHeight
  1053. water02Material.uniforms.shallowColor.value.set(p.waterColor)
  1054. water02Material.uniforms.deepColor.value.set(p.deepColor)
  1055. water02Material.uniforms.foamIntensity.value = p.foamIntensity
  1056. water02Material.uniforms.specIntensity.value = p.specIntensity
  1057. water02Material.uniforms.specPower.value = p.specPower
  1058. water02Material.uniforms.fresnelPower.value = p.fresnelPower
  1059. water02Material.uniforms.fresnelIntensity.value = p.fresnelIntensity
  1060. water02Material.uniforms.depthRange.value = p.depthRange
  1061. water02Material.uniforms.waterNormalStrength.value = p.waterNormalStrength
  1062. water02Material.uniforms.waterNormalTiling.value = p.waterNormalTiling
  1063. water02Material.uniforms.collisionFoamThreshold.value = p.collisionFoamThreshold
  1064. water02Material.uniforms.collisionFoamStrength.value = p.collisionFoamStrength
  1065. water02Material.uniforms.fresnelDistanceNear.value = p.fresnelDistanceNear
  1066. water02Material.uniforms.fresnelDistanceFar.value = p.fresnelDistanceFar
  1067. }
  1068. watch(() => water02Params.value, syncWater02Params, { deep: true })
  1069. watch(() => cscwaterParams.value, () => {
  1070. syncCscwaterParams()
  1071. }, { deep: true })
  1072. watch(() => foamMatParams.value, (p) => {
  1073. if (foamMaterial) {
  1074. foamMaterial.uniforms.uColour.value.set(p.colour)
  1075. foamMaterial.uniforms.uOpacity.value = p.opacity
  1076. foamMaterial.uniforms.uWaterfallSpeed.value = p.waterfallSpeed
  1077. foamMaterial.uniforms.uEdgeMaskTiling.value = p.edgeMaskTiling
  1078. foamMaterial.uniforms.uEdgeMaskSpeed.value = p.edgeMaskSpeed
  1079. foamMaterial.uniforms.uFresnelExponent.value = p.fresnelExponent
  1080. foamMaterial.uniforms.uDirectionalFoamIntensity.value = p.directionalFoamIntensity
  1081. foamMaterial.uniforms.uDirectionalFoamContrast.value = p.directionalFoamContrast
  1082. foamMaterial.uniforms.uDirectionalFoam1Intensity.value = p.directionalFoam1Intensity
  1083. foamMaterial.uniforms.uDirectionalFoam2Intensity.value = p.directionalFoam2Intensity
  1084. foamMaterial.uniforms.uDirectionalFoam2Tiling.value = p.directionalFoam2Tiling
  1085. foamMaterial.uniforms.uDirectionalFoam2Speed.value = p.directionalFoam2Speed
  1086. foamMaterial.uniforms.uDirectionalFoam3Intensity.value = p.directionalFoam3Intensity
  1087. foamMaterial.uniforms.uFoamFalloff.value = p.foamFalloff
  1088. foamMaterial.uniforms.uGradientTop.value = p.gradientTop
  1089. foamMaterial.uniforms.uGradientBottom.value = p.gradientBottom
  1090. foamMaterial.uniforms.uGradientPower.value = p.gradientPower
  1091. }
  1092. if (foam2Material) {
  1093. foam2Material.uniforms.uColour.value.set(p.colour)
  1094. foam2Material.uniforms.uOpacity.value = p.opacity
  1095. foam2Material.uniforms.uWaterfallSpeed.value = p.waterfallSpeed
  1096. foam2Material.uniforms.uEdgeMaskTiling.value = p.edgeMaskTiling
  1097. foam2Material.uniforms.uEdgeMaskSpeed.value = p.edgeMaskSpeed
  1098. foam2Material.uniforms.uFresnelExponent.value = p.fresnelExponent
  1099. foam2Material.uniforms.uDirectionalFoamIntensity.value = p.directionalFoamIntensity
  1100. foam2Material.uniforms.uDirectionalFoamContrast.value = p.directionalFoamContrast
  1101. foam2Material.uniforms.uDirectionalFoam1Intensity.value = p.directionalFoam1Intensity
  1102. foam2Material.uniforms.uDirectionalFoam2Intensity.value = p.directionalFoam2Intensity
  1103. foam2Material.uniforms.uDirectionalFoam2Tiling.value = p.directionalFoam2Tiling
  1104. foam2Material.uniforms.uDirectionalFoam2Speed.value = p.directionalFoam2Speed
  1105. foam2Material.uniforms.uDirectionalFoam3Intensity.value = p.directionalFoam3Intensity
  1106. foam2Material.uniforms.uFoamFalloff.value = p.foamFalloff
  1107. foam2Material.uniforms.uGradientTop.value = p.gradientTop
  1108. foam2Material.uniforms.uGradientBottom.value = p.gradientBottom
  1109. foam2Material.uniforms.uGradientPower.value = p.gradientPower
  1110. }
  1111. }, { deep: true })
  1112. watch(() => flowParams.value, () => {
  1113. syncFlowParams()
  1114. }, { deep: true })
  1115. watch(() => props.labelValues, (levels) => {
  1116. for (const [id, value] of Object.entries(levels)) {
  1117. if (labelValues[id] !== undefined) {
  1118. labelValues[id] = value
  1119. }
  1120. }
  1121. }, { deep: true })
  1122. // ==================== 对外暴露的 API(供父组件/外部系统调用)====================
  1123. function setLabelValue(id: string, value: number) {
  1124. labelValues[id] = value
  1125. }
  1126. function setLabelValues(levels: Record<string, number>) {
  1127. for (const [id, value] of Object.entries(levels)) {
  1128. labelValues[id] = value
  1129. }
  1130. }
  1131. function flyTo(
  1132. position: { x: number; y: number; z: number },
  1133. target?: { x: number; y: number; z: number },
  1134. durationMs: number = 1500,
  1135. ) {
  1136. const startPos = camera.position.clone()
  1137. const endPos = new THREE.Vector3(position.x, position.y, position.z)
  1138. const startTarget = controls.target.clone()
  1139. const endTarget = target
  1140. ? new THREE.Vector3(target.x, target.y, target.z)
  1141. : controls.target.clone()
  1142. const startTime = performance.now()
  1143. function animateFly() {
  1144. const elapsed = performance.now() - startTime
  1145. const t = Math.min(elapsed / durationMs, 1)
  1146. const ease = t * t * (3 - 2 * t)
  1147. camera.position.lerpVectors(startPos, endPos, ease)
  1148. controls.target.lerpVectors(startTarget, endTarget, ease)
  1149. controls.update()
  1150. if (t < 1) requestAnimationFrame(animateFly)
  1151. }
  1152. animateFly()
  1153. }
  1154. function flyToPreset(presetId: string, durationMs: number = 1500) {
  1155. const preset = cameraPresets.find(p => p.id === presetId)
  1156. if (!preset) {
  1157. console.warn(`Camera preset "${presetId}" not found`)
  1158. return
  1159. }
  1160. flyTo(
  1161. { x: preset.positionX, y: preset.positionY, z: preset.positionZ },
  1162. { x: preset.targetX, y: preset.targetY, z: preset.targetZ },
  1163. durationMs,
  1164. )
  1165. }
  1166. defineExpose({
  1167. labelDataList,
  1168. labelValues,
  1169. setLabelValue,
  1170. setLabelValues,
  1171. flyTo,
  1172. flyToPreset,
  1173. cameraPresets,
  1174. })
  1175. </script>
  1176. <template>
  1177. <!-- 3D 渲染容器 -->
  1178. <div ref="containerRef" class="scene-container" />
  1179. <!-- 水位标签(Sprite + 指针) -->
  1180. <WaterLevelLabel
  1181. v-for="data in labelDataList"
  1182. :key="data.config.id"
  1183. :ref="(el: any) => { if (el) data.componentRef.value = el }"
  1184. :label-id="data.config.id"
  1185. :type="data.config.type"
  1186. :position-x="data.config.positionX"
  1187. :position-y="data.config.positionY"
  1188. :position-z="data.config.positionZ"
  1189. :value="labelValues[data.config.id]"
  1190. />
  1191. <!-- 右上角调试工具栏 -->
  1192. <div v-if="props.showDebugTools" class="toolbar">
  1193. <button class="toolbar-btn" :class="{ active: showCoordinatePanel }" @click="showCoordinatePanel = !showCoordinatePanel">坐标</button>
  1194. <button class="toolbar-btn" :class="{ active: showCameraPanel }" @click="showCameraPanel = !showCameraPanel">相机</button>
  1195. <button class="toolbar-btn" :class="{ active: showModelPanel }" @click="showModelPanel = !showModelPanel">模型</button>
  1196. <button class="toolbar-btn" :class="{ active: showMaterialPanel }" @click="showMaterialPanel = !showMaterialPanel">材质</button>
  1197. <button class="toolbar-btn" :class="{ active: showCameraPresetPanel }" @click="showCameraPresetPanel = !showCameraPresetPanel">视角</button>
  1198. </div>
  1199. <!-- 拾取坐标面板 -->
  1200. <div v-if="props.showDebugTools && showCoordinatePanel && pickedPosition" class="panel coordinate-panel">
  1201. <div class="panel-header">
  1202. <span class="panel-title">拾取坐标 (m)</span>
  1203. <button class="toggle-btn" @click="showCoordinatePanel = false">×</button>
  1204. </div>
  1205. <div class="coordinate-item">
  1206. <span class="coordinate-label">X:</span>
  1207. <span class="coordinate-value">{{ pickedPosition.x }}</span>
  1208. </div>
  1209. <div class="coordinate-item">
  1210. <span class="coordinate-label">Y:</span>
  1211. <span class="coordinate-value">{{ pickedPosition.y }}</span>
  1212. </div>
  1213. <div class="coordinate-item">
  1214. <span class="coordinate-label">Z:</span>
  1215. <span class="coordinate-value">{{ pickedPosition.z }}</span>
  1216. </div>
  1217. </div>
  1218. <!-- 相机信息面板 -->
  1219. <div v-if="props.showDebugTools && showCameraPanel" class="panel camera-panel">
  1220. <div class="panel-header">
  1221. <span class="panel-title">相机信息</span>
  1222. <button class="toggle-btn" @click="showCameraPanel = false">×</button>
  1223. </div>
  1224. <div class="camera-section">
  1225. <div class="section-label">位置 (m)</div>
  1226. <div class="camera-item">
  1227. <span class="camera-label">X:</span>
  1228. <span class="camera-value">{{ cameraInfo.position.x }}</span>
  1229. </div>
  1230. <div class="camera-item">
  1231. <span class="camera-label">Y:</span>
  1232. <span class="camera-value">{{ cameraInfo.position.y }}</span>
  1233. </div>
  1234. <div class="camera-item">
  1235. <span class="camera-label">Z:</span>
  1236. <span class="camera-value">{{ cameraInfo.position.z }}</span>
  1237. </div>
  1238. </div>
  1239. <div class="camera-section">
  1240. <div class="section-label">目标点 (m)</div>
  1241. <div class="camera-item">
  1242. <span class="camera-label">X:</span>
  1243. <span class="camera-value">{{ cameraInfo.target.x }}</span>
  1244. </div>
  1245. <div class="camera-item">
  1246. <span class="camera-label">Y:</span>
  1247. <span class="camera-value">{{ cameraInfo.target.y }}</span>
  1248. </div>
  1249. <div class="camera-item">
  1250. <span class="camera-label">Z:</span>
  1251. <span class="camera-value">{{ cameraInfo.target.z }}</span>
  1252. </div>
  1253. </div>
  1254. <div class="camera-section">
  1255. <div class="section-label">缩放距离 (m)</div>
  1256. <div class="camera-item">
  1257. <span class="camera-label">当前:</span>
  1258. <span class="camera-value">{{ cameraInfo.distance }}</span>
  1259. </div>
  1260. <div class="camera-item">
  1261. <span class="camera-label">最近:</span>
  1262. <span class="camera-value">{{ cameraInfo.minDistance }}m</span>
  1263. </div>
  1264. <div class="camera-item">
  1265. <span class="camera-label">最远:</span>
  1266. <span class="camera-value">{{ cameraInfo.maxDistance }}m</span>
  1267. </div>
  1268. </div>
  1269. </div>
  1270. <!-- 模型变换调试面板 -->
  1271. <div v-if="props.showDebugTools && showModelPanel" class="panel model-panel">
  1272. <div class="panel-header">
  1273. <span class="panel-title">模型变换</span>
  1274. <button class="toggle-btn" @click="showModelPanel = false">×</button>
  1275. </div>
  1276. <div class="model-section">
  1277. <div class="section-label">选择模型</div>
  1278. <select v-model="selectedModelKey" class="model-select">
  1279. <option value="water">水面</option>
  1280. <option value="water2">水面2</option>
  1281. <option value="water02">一期沉砂池水面</option>
  1282. <option value="foam">泡沫片面</option>
  1283. <option value="foam2">一期沉砂池泡沫</option>
  1284. <option value="flow">流动纹理模型</option>
  1285. <option value="cscwater">CSCwater</option>
  1286. </select>
  1287. </div>
  1288. <div class="model-section">
  1289. <div class="section-label">位置</div>
  1290. <div class="input-item">
  1291. <span class="input-label">X</span>
  1292. <input type="number" v-model.number="modelTransform.positionX" step="0.1" class="number-input" />
  1293. </div>
  1294. <div class="input-item">
  1295. <span class="input-label">Y</span>
  1296. <input type="number" v-model.number="modelTransform.positionY" step="0.1" class="number-input" />
  1297. </div>
  1298. <div class="input-item">
  1299. <span class="input-label">Z</span>
  1300. <input type="number" v-model.number="modelTransform.positionZ" step="0.1" class="number-input" />
  1301. </div>
  1302. </div>
  1303. <div class="model-section">
  1304. <div class="section-label">旋转</div>
  1305. <div class="input-item">
  1306. <span class="input-label">X (°)</span>
  1307. <input type="number" v-model.number="modelTransform.rotationX" step="1" class="number-input" />
  1308. </div>
  1309. <div class="input-item">
  1310. <span class="input-label">Y (°)</span>
  1311. <input type="number" v-model.number="modelTransform.rotationY" step="1" class="number-input" />
  1312. </div>
  1313. <div class="input-item">
  1314. <span class="input-label">Z (°)</span>
  1315. <input type="number" v-model.number="modelTransform.rotationZ" step="1" class="number-input" />
  1316. </div>
  1317. </div>
  1318. <div class="model-section">
  1319. <div class="section-label">缩放</div>
  1320. <div class="input-item">
  1321. <span class="input-label">X</span>
  1322. <input type="number" v-model.number="modelTransform.scaleX" step="0.5" class="number-input" />
  1323. </div>
  1324. <div class="input-item">
  1325. <span class="input-label">Y</span>
  1326. <input type="number" v-model.number="modelTransform.scaleY" step="0.5" class="number-input" />
  1327. </div>
  1328. <div class="input-item">
  1329. <span class="input-label">Z</span>
  1330. <input type="number" v-model.number="modelTransform.scaleZ" step="0.5" class="number-input" />
  1331. </div>
  1332. </div>
  1333. <div class="model-section">
  1334. <button class="default-btn" @click="saveModelDefaults">设置默认值</button>
  1335. </div>
  1336. </div>
  1337. <!-- 材质参数调试面板 -->
  1338. <div v-if="props.showDebugTools && showMaterialPanel" class="panel material-panel">
  1339. <div class="panel-header">
  1340. <span class="panel-title">材质参数</span>
  1341. <button class="toggle-btn" @click="showMaterialPanel = false">×</button>
  1342. </div>
  1343. <div class="material-section">
  1344. <div class="section-label">选择材质</div>
  1345. <select v-model="selectedMaterialKey" class="model-select">
  1346. <option value="water">水材质</option>
  1347. <option value="water02">一期沉砂池水面材质</option>
  1348. <option value="foam">泡沫材质</option>
  1349. <option value="flow">流动纹理材质</option>
  1350. <option value="cscwater">CSCwater 材质</option>
  1351. </select>
  1352. </div>
  1353. <template v-if="selectedMaterialKey === 'water'">
  1354. <div class="material-section">
  1355. <div class="section-label">透明度</div>
  1356. <div class="slider-item">
  1357. <input type="range" v-model.number="waterParams.alpha" min="0" max="1" step="0.01" />
  1358. <span class="slider-value">{{ waterParams.alpha.toFixed(2) }}</span>
  1359. </div>
  1360. </div>
  1361. <div class="material-section">
  1362. <div class="section-label">浪高</div>
  1363. <div class="slider-item">
  1364. <input type="range" v-model.number="waterParams.waveHeight" min="0" max="2" step="0.05" />
  1365. <span class="slider-value">{{ waterParams.waveHeight.toFixed(2) }}</span>
  1366. </div>
  1367. </div>
  1368. <div class="material-section">
  1369. <div class="section-label">水流速度</div>
  1370. <div class="slider-item">
  1371. <input type="range" v-model.number="waterParams.flowSpeed" min="0" max="3" step="0.1" />
  1372. <span class="slider-value">{{ waterParams.flowSpeed.toFixed(1) }}</span>
  1373. </div>
  1374. </div>
  1375. <div class="material-section">
  1376. <div class="section-label">流向 X</div>
  1377. <div class="slider-item">
  1378. <input type="range" v-model.number="waterParams.flowDirectionX" min="-2" max="2" step="0.1" />
  1379. <span class="slider-value">{{ waterParams.flowDirectionX.toFixed(1) }}</span>
  1380. </div>
  1381. </div>
  1382. <div class="material-section">
  1383. <div class="section-label">流向 Z</div>
  1384. <div class="slider-item">
  1385. <input type="range" v-model.number="waterParams.flowDirectionY" min="-2" max="2" step="0.1" />
  1386. <span class="slider-value">{{ waterParams.flowDirectionY.toFixed(1) }}</span>
  1387. </div>
  1388. </div>
  1389. <div class="material-section">
  1390. <div class="section-label">法线旋转</div>
  1391. <div class="slider-item">
  1392. <input type="range" v-model.number="waterParams.normalRotation" min="-180" max="180" step="1" />
  1393. <span class="slider-value">{{ waterParams.normalRotation.toFixed(0) }}°</span>
  1394. </div>
  1395. </div>
  1396. <div class="material-section">
  1397. <div class="section-label">浅水颜色</div>
  1398. <div class="color-item">
  1399. <input type="color" v-model="waterParams.waterColor" />
  1400. <span class="color-value">{{ waterParams.waterColor }}</span>
  1401. </div>
  1402. </div>
  1403. <div class="material-section">
  1404. <div class="section-label">深水颜色</div>
  1405. <div class="color-item">
  1406. <input type="color" v-model="waterParams.deepColor" />
  1407. <span class="color-value">{{ waterParams.deepColor }}</span>
  1408. </div>
  1409. </div>
  1410. <div class="material-section">
  1411. <div class="section-label">水深范围</div>
  1412. <div class="slider-item">
  1413. <input type="range" v-model.number="waterParams.depthRange" min="1" max="50" step="0.5" />
  1414. <span class="slider-value">{{ waterParams.depthRange.toFixed(1) }}</span>
  1415. </div>
  1416. </div>
  1417. <div class="material-section">
  1418. <div class="section-label">水法线强度</div>
  1419. <div class="slider-item">
  1420. <input type="range" v-model.number="waterParams.waterNormalStrength" min="0" max="2" step="0.05" />
  1421. <span class="slider-value">{{ waterParams.waterNormalStrength.toFixed(2) }}</span>
  1422. </div>
  1423. </div>
  1424. <div class="material-section">
  1425. <div class="section-label">水纹平铺</div>
  1426. <div class="slider-item">
  1427. <input type="range" v-model.number="waterParams.waterNormalTiling" min="0.01" max="5" step="0.01" />
  1428. <span class="slider-value">{{ waterParams.waterNormalTiling.toFixed(2) }}</span>
  1429. </div>
  1430. </div>
  1431. <div class="material-section">
  1432. <div class="section-label">高光强度</div>
  1433. <div class="slider-item">
  1434. <input type="range" v-model.number="waterParams.specIntensity" min="0" max="5" step="0.1" />
  1435. <span class="slider-value">{{ waterParams.specIntensity.toFixed(1) }}</span>
  1436. </div>
  1437. </div>
  1438. <div class="material-section">
  1439. <div class="section-label">高光锐度</div>
  1440. <div class="slider-item">
  1441. <input type="range" v-model.number="waterParams.specPower" min="1" max="256" step="1" />
  1442. <span class="slider-value">{{ waterParams.specPower.toFixed(0) }}</span>
  1443. </div>
  1444. </div>
  1445. <div class="material-section">
  1446. <div class="section-label">菲涅尔功率</div>
  1447. <div class="slider-item">
  1448. <input type="range" v-model.number="waterParams.fresnelPower" min="0.1" max="10" step="0.1" />
  1449. <span class="slider-value">{{ waterParams.fresnelPower.toFixed(1) }}</span>
  1450. </div>
  1451. </div>
  1452. <div class="material-section">
  1453. <div class="section-label">菲涅尔强度</div>
  1454. <div class="slider-item">
  1455. <input type="range" v-model.number="waterParams.fresnelIntensity" min="0" max="3" step="0.1" />
  1456. <span class="slider-value">{{ waterParams.fresnelIntensity.toFixed(1) }}</span>
  1457. </div>
  1458. </div>
  1459. <div class="material-section">
  1460. <div class="section-label">泡沫强度</div>
  1461. <div class="slider-item">
  1462. <input type="range" v-model.number="waterParams.foamIntensity" min="0" max="2" step="0.05" />
  1463. <span class="slider-value">{{ waterParams.foamIntensity.toFixed(2) }}</span>
  1464. </div>
  1465. </div>
  1466. <div class="material-section">
  1467. <div class="section-label">碰撞泡沫阈值</div>
  1468. <div class="slider-item">
  1469. <input type="range" v-model.number="waterParams.collisionFoamThreshold" min="0" max="2" step="0.05" />
  1470. <span class="slider-value">{{ waterParams.collisionFoamThreshold.toFixed(2) }}</span>
  1471. </div>
  1472. </div>
  1473. <div class="material-section">
  1474. <div class="section-label">碰撞泡沫强度</div>
  1475. <div class="slider-item">
  1476. <input type="range" v-model.number="waterParams.collisionFoamStrength" min="0" max="3" step="0.1" />
  1477. <span class="slider-value">{{ waterParams.collisionFoamStrength.toFixed(1) }}</span>
  1478. </div>
  1479. </div>
  1480. <div class="material-section">
  1481. <button class="default-btn" @click="saveWaterDefaults">设置默认值</button>
  1482. </div>
  1483. </template>
  1484. <template v-if="selectedMaterialKey === 'water02'">
  1485. <div class="material-section">
  1486. <div class="section-label">透明度</div>
  1487. <div class="slider-item">
  1488. <input type="range" v-model.number="water02Params.alpha" min="0" max="1" step="0.01" />
  1489. <span class="slider-value">{{ water02Params.alpha.toFixed(2) }}</span>
  1490. </div>
  1491. </div>
  1492. <div class="material-section">
  1493. <div class="section-label">浪高</div>
  1494. <div class="slider-item">
  1495. <input type="range" v-model.number="water02Params.waveHeight" min="0" max="2" step="0.05" />
  1496. <span class="slider-value">{{ water02Params.waveHeight.toFixed(2) }}</span>
  1497. </div>
  1498. </div>
  1499. <div class="material-section">
  1500. <div class="section-label">水流速度</div>
  1501. <div class="slider-item">
  1502. <input type="range" v-model.number="water02Params.flowSpeed" min="0" max="3" step="0.1" />
  1503. <span class="slider-value">{{ water02Params.flowSpeed.toFixed(1) }}</span>
  1504. </div>
  1505. </div>
  1506. <div class="material-section">
  1507. <div class="section-label">流向 X</div>
  1508. <div class="slider-item">
  1509. <input type="range" v-model.number="water02Params.flowDirectionX" min="-2" max="2" step="0.1" />
  1510. <span class="slider-value">{{ water02Params.flowDirectionX.toFixed(1) }}</span>
  1511. </div>
  1512. </div>
  1513. <div class="material-section">
  1514. <div class="section-label">流向 Z</div>
  1515. <div class="slider-item">
  1516. <input type="range" v-model.number="water02Params.flowDirectionY" min="-2" max="2" step="0.1" />
  1517. <span class="slider-value">{{ water02Params.flowDirectionY.toFixed(1) }}</span>
  1518. </div>
  1519. </div>
  1520. <div class="material-section">
  1521. <div class="section-label">法线旋转</div>
  1522. <div class="slider-item">
  1523. <input type="range" v-model.number="water02Params.normalRotation" min="-180" max="180" step="1" />
  1524. <span class="slider-value">{{ water02Params.normalRotation.toFixed(0) }}°</span>
  1525. </div>
  1526. </div>
  1527. <div class="material-section">
  1528. <div class="section-label">浅水颜色</div>
  1529. <div class="color-item">
  1530. <input type="color" v-model="water02Params.waterColor" />
  1531. <span class="color-value">{{ water02Params.waterColor }}</span>
  1532. </div>
  1533. </div>
  1534. <div class="material-section">
  1535. <div class="section-label">深水颜色</div>
  1536. <div class="color-item">
  1537. <input type="color" v-model="water02Params.deepColor" />
  1538. <span class="color-value">{{ water02Params.deepColor }}</span>
  1539. </div>
  1540. </div>
  1541. <div class="material-section">
  1542. <div class="section-label">水深范围</div>
  1543. <div class="slider-item">
  1544. <input type="range" v-model.number="water02Params.depthRange" min="1" max="50" step="0.5" />
  1545. <span class="slider-value">{{ water02Params.depthRange.toFixed(1) }}</span>
  1546. </div>
  1547. </div>
  1548. <div class="material-section">
  1549. <div class="section-label">水法线强度</div>
  1550. <div class="slider-item">
  1551. <input type="range" v-model.number="water02Params.waterNormalStrength" min="0" max="2" step="0.05" />
  1552. <span class="slider-value">{{ water02Params.waterNormalStrength.toFixed(2) }}</span>
  1553. </div>
  1554. </div>
  1555. <div class="material-section">
  1556. <div class="section-label">水纹平铺</div>
  1557. <div class="slider-item">
  1558. <input type="range" v-model.number="water02Params.waterNormalTiling" min="0.01" max="5" step="0.01" />
  1559. <span class="slider-value">{{ water02Params.waterNormalTiling.toFixed(2) }}</span>
  1560. </div>
  1561. </div>
  1562. <div class="material-section">
  1563. <div class="section-label">高光强度</div>
  1564. <div class="slider-item">
  1565. <input type="range" v-model.number="water02Params.specIntensity" min="0" max="5" step="0.1" />
  1566. <span class="slider-value">{{ water02Params.specIntensity.toFixed(1) }}</span>
  1567. </div>
  1568. </div>
  1569. <div class="material-section">
  1570. <div class="section-label">高光锐度</div>
  1571. <div class="slider-item">
  1572. <input type="range" v-model.number="water02Params.specPower" min="1" max="256" step="1" />
  1573. <span class="slider-value">{{ water02Params.specPower.toFixed(0) }}</span>
  1574. </div>
  1575. </div>
  1576. <div class="material-section">
  1577. <div class="section-label">菲涅尔功率</div>
  1578. <div class="slider-item">
  1579. <input type="range" v-model.number="water02Params.fresnelPower" min="0.1" max="10" step="0.1" />
  1580. <span class="slider-value">{{ water02Params.fresnelPower.toFixed(1) }}</span>
  1581. </div>
  1582. </div>
  1583. <div class="material-section">
  1584. <div class="section-label">菲涅尔强度</div>
  1585. <div class="slider-item">
  1586. <input type="range" v-model.number="water02Params.fresnelIntensity" min="0" max="3" step="0.1" />
  1587. <span class="slider-value">{{ water02Params.fresnelIntensity.toFixed(1) }}</span>
  1588. </div>
  1589. </div>
  1590. <div class="material-section">
  1591. <div class="section-label">泡沫强度</div>
  1592. <div class="slider-item">
  1593. <input type="range" v-model.number="water02Params.foamIntensity" min="0" max="2" step="0.05" />
  1594. <span class="slider-value">{{ water02Params.foamIntensity.toFixed(2) }}</span>
  1595. </div>
  1596. </div>
  1597. <div class="material-section">
  1598. <div class="section-label">碰撞泡沫阈值</div>
  1599. <div class="slider-item">
  1600. <input type="range" v-model.number="water02Params.collisionFoamThreshold" min="0" max="2" step="0.05" />
  1601. <span class="slider-value">{{ water02Params.collisionFoamThreshold.toFixed(2) }}</span>
  1602. </div>
  1603. </div>
  1604. <div class="material-section">
  1605. <div class="section-label">碰撞泡沫强度</div>
  1606. <div class="slider-item">
  1607. <input type="range" v-model.number="water02Params.collisionFoamStrength" min="0" max="3" step="0.1" />
  1608. <span class="slider-value">{{ water02Params.collisionFoamStrength.toFixed(1) }}</span>
  1609. </div>
  1610. </div>
  1611. <div class="material-section">
  1612. <button class="default-btn" @click="saveWater02Defaults">设置默认值</button>
  1613. </div>
  1614. </template>
  1615. <template v-if="selectedMaterialKey === 'foam'">
  1616. <div class="material-section">
  1617. <div class="section-label">颜色</div>
  1618. <div class="color-item">
  1619. <input type="color" v-model="foamMatParams.colour" />
  1620. <span class="color-value">{{ foamMatParams.colour }}</span>
  1621. </div>
  1622. </div>
  1623. <div class="material-section">
  1624. <div class="section-label">不透明度</div>
  1625. <div class="slider-item">
  1626. <input type="range" v-model.number="foamMatParams.opacity" min="0" max="1" step="0.01" />
  1627. <span class="slider-value">{{ foamMatParams.opacity.toFixed(2) }}</span>
  1628. </div>
  1629. </div>
  1630. <div class="material-section">
  1631. <div class="section-label">瀑布流速</div>
  1632. <div class="slider-item">
  1633. <input type="range" v-model.number="foamMatParams.waterfallSpeed" min="0" max="2" step="0.01" />
  1634. <span class="slider-value">{{ foamMatParams.waterfallSpeed.toFixed(2) }}</span>
  1635. </div>
  1636. </div>
  1637. <div class="material-section">
  1638. <div class="section-label">菲涅尔指数</div>
  1639. <div class="slider-item">
  1640. <input type="range" v-model.number="foamMatParams.fresnelExponent" min="0" max="20" step="0.1" />
  1641. <span class="slider-value">{{ foamMatParams.fresnelExponent.toFixed(1) }}</span>
  1642. </div>
  1643. </div>
  1644. <div class="material-section">
  1645. <div class="section-label">边缘遮罩平铺</div>
  1646. <div class="slider-item">
  1647. <input type="range" v-model.number="foamMatParams.edgeMaskTiling" min="0.1" max="2" step="0.01" />
  1648. <span class="slider-value">{{ foamMatParams.edgeMaskTiling.toFixed(2) }}</span>
  1649. </div>
  1650. </div>
  1651. <div class="material-section">
  1652. <div class="section-label">边缘遮罩速度</div>
  1653. <div class="slider-item">
  1654. <input type="range" v-model.number="foamMatParams.edgeMaskSpeed" min="0" max="10" step="0.1" />
  1655. <span class="slider-value">{{ foamMatParams.edgeMaskSpeed.toFixed(1) }}</span>
  1656. </div>
  1657. </div>
  1658. <div class="material-section">
  1659. <div class="section-label">方向泡沫强度</div>
  1660. <div class="slider-item">
  1661. <input type="range" v-model.number="foamMatParams.directionalFoamIntensity" min="0" max="5" step="0.01" />
  1662. <span class="slider-value">{{ foamMatParams.directionalFoamIntensity.toFixed(2) }}</span>
  1663. </div>
  1664. </div>
  1665. <div class="material-section">
  1666. <div class="section-label">方向泡沫对比度</div>
  1667. <div class="slider-item">
  1668. <input type="range" v-model.number="foamMatParams.directionalFoamContrast" min="0.1" max="10" step="0.1" />
  1669. <span class="slider-value">{{ foamMatParams.directionalFoamContrast.toFixed(1) }}</span>
  1670. </div>
  1671. </div>
  1672. <div class="material-section">
  1673. <div class="section-label">层1强度</div>
  1674. <div class="slider-item">
  1675. <input type="range" v-model.number="foamMatParams.directionalFoam1Intensity" min="0" max="5" step="0.01" />
  1676. <span class="slider-value">{{ foamMatParams.directionalFoam1Intensity.toFixed(2) }}</span>
  1677. </div>
  1678. </div>
  1679. <div class="material-section">
  1680. <div class="section-label">层2强度</div>
  1681. <div class="slider-item">
  1682. <input type="range" v-model.number="foamMatParams.directionalFoam2Intensity" min="0" max="5" step="0.01" />
  1683. <span class="slider-value">{{ foamMatParams.directionalFoam2Intensity.toFixed(2) }}</span>
  1684. </div>
  1685. </div>
  1686. <div class="material-section">
  1687. <div class="section-label">层2平铺</div>
  1688. <div class="slider-item">
  1689. <input type="range" v-model.number="foamMatParams.directionalFoam2Tiling" min="0.1" max="5" step="0.01" />
  1690. <span class="slider-value">{{ foamMatParams.directionalFoam2Tiling.toFixed(2) }}</span>
  1691. </div>
  1692. </div>
  1693. <div class="material-section">
  1694. <div class="section-label">层2速度</div>
  1695. <div class="slider-item">
  1696. <input type="range" v-model.number="foamMatParams.directionalFoam2Speed" min="0" max="50" step="0.5" />
  1697. <span class="slider-value">{{ foamMatParams.directionalFoam2Speed.toFixed(1) }}</span>
  1698. </div>
  1699. </div>
  1700. <div class="material-section">
  1701. <div class="section-label">层3强度</div>
  1702. <div class="slider-item">
  1703. <input type="range" v-model.number="foamMatParams.directionalFoam3Intensity" min="0" max="5" step="0.01" />
  1704. <span class="slider-value">{{ foamMatParams.directionalFoam3Intensity.toFixed(2) }}</span>
  1705. </div>
  1706. </div>
  1707. <div class="material-section">
  1708. <div class="section-label">泡沫衰减</div>
  1709. <div class="slider-item">
  1710. <input type="range" v-model.number="foamMatParams.foamFalloff" min="0.1" max="10" step="0.1" />
  1711. <span class="slider-value">{{ foamMatParams.foamFalloff.toFixed(1) }}</span>
  1712. </div>
  1713. </div>
  1714. <div class="material-section">
  1715. <div class="section-label">渐变顶部</div>
  1716. <div class="slider-item">
  1717. <input type="range" v-model.number="foamMatParams.gradientTop" min="0" max="1" step="0.01" />
  1718. <span class="slider-value">{{ foamMatParams.gradientTop.toFixed(2) }}</span>
  1719. </div>
  1720. </div>
  1721. <div class="material-section">
  1722. <div class="section-label">渐变底部</div>
  1723. <div class="slider-item">
  1724. <input type="range" v-model.number="foamMatParams.gradientBottom" min="0" max="1" step="0.01" />
  1725. <span class="slider-value">{{ foamMatParams.gradientBottom.toFixed(2) }}</span>
  1726. </div>
  1727. </div>
  1728. <div class="material-section">
  1729. <div class="section-label">渐变曲线</div>
  1730. <div class="slider-item">
  1731. <input type="range" v-model.number="foamMatParams.gradientPower" min="0.1" max="5" step="0.1" />
  1732. <span class="slider-value">{{ foamMatParams.gradientPower.toFixed(1) }}</span>
  1733. </div>
  1734. </div>
  1735. <div class="material-section">
  1736. <button class="default-btn" @click="saveFoamDefaults">设置默认值</button>
  1737. </div>
  1738. </template>
  1739. <template v-if="selectedMaterialKey === 'flow'">
  1740. <div class="material-section">
  1741. <div class="section-label">颜色</div>
  1742. <div class="color-item">
  1743. <input type="color" v-model="flowParams.colour" />
  1744. <span class="color-value">{{ flowParams.colour }}</span>
  1745. </div>
  1746. </div>
  1747. <div class="material-section">
  1748. <div class="section-label">流动速度 X</div>
  1749. <div class="slider-item">
  1750. <input type="range" v-model.number="flowParams.speedX" min="-2" max="2" step="0.001" />
  1751. <span class="slider-value">{{ flowParams.speedX.toFixed(3) }}</span>
  1752. </div>
  1753. </div>
  1754. <div class="material-section">
  1755. <div class="section-label">流动速度 Y</div>
  1756. <div class="slider-item">
  1757. <input type="range" v-model.number="flowParams.speedY" min="-2" max="2" step="0.001" />
  1758. <span class="slider-value">{{ flowParams.speedY.toFixed(3) }}</span>
  1759. </div>
  1760. </div>
  1761. <div class="material-section">
  1762. <div class="section-label">平铺 U</div>
  1763. <div class="slider-item">
  1764. <input type="range" v-model.number="flowParams.tilingU" min="-5" max="5" step="0.01" />
  1765. <span class="slider-value">{{ flowParams.tilingU.toFixed(2) }}</span>
  1766. </div>
  1767. </div>
  1768. <div class="material-section">
  1769. <div class="section-label">平铺 V</div>
  1770. <div class="slider-item">
  1771. <input type="range" v-model.number="flowParams.tilingV" min="-5" max="5" step="0.01" />
  1772. <span class="slider-value">{{ flowParams.tilingV.toFixed(2) }}</span>
  1773. </div>
  1774. </div>
  1775. <div class="material-section">
  1776. <div class="section-label">旋转角度</div>
  1777. <div class="slider-item">
  1778. <input type="range" v-model.number="flowParams.rotationAngle" min="-1" max="1" step="0.01" />
  1779. <span class="slider-value">{{ flowParams.rotationAngle.toFixed(2) }}</span>
  1780. </div>
  1781. </div>
  1782. <div class="material-section">
  1783. <div class="section-label">泡沫强度</div>
  1784. <div class="slider-item">
  1785. <input type="range" v-model.number="flowParams.power" min="0" max="5" step="0.01" />
  1786. <span class="slider-value">{{ flowParams.power.toFixed(2) }}</span>
  1787. </div>
  1788. </div>
  1789. <div class="material-section">
  1790. <div class="section-label">水闸强度</div>
  1791. <div class="slider-item">
  1792. <input type="range" v-model.number="flowParams.waterGate" min="0" max="200" step="0.5" />
  1793. <span class="slider-value">{{ flowParams.waterGate.toFixed(1) }}</span>
  1794. </div>
  1795. </div>
  1796. <div class="material-section">
  1797. <div class="section-label">水闸翻转</div>
  1798. <div class="slider-item">
  1799. <input type="range" v-model.number="flowParams.waterGate02" min="0" max="1" step="0.01" />
  1800. <span class="slider-value">{{ flowParams.waterGate02.toFixed(2) }}</span>
  1801. </div>
  1802. </div>
  1803. <div class="material-section">
  1804. <div class="section-label">边缘遮罩强度</div>
  1805. <div class="slider-item">
  1806. <input type="range" v-model.number="flowParams.edgeMaskIntensity" min="0" max="1" step="0.01" />
  1807. <span class="slider-value">{{ flowParams.edgeMaskIntensity.toFixed(2) }}</span>
  1808. </div>
  1809. </div>
  1810. <div class="material-section">
  1811. <div class="section-label">不透明度功率</div>
  1812. <div class="slider-item">
  1813. <input type="range" v-model.number="flowParams.opaquePower" min="0" max="20" step="0.01" />
  1814. <span class="slider-value">{{ flowParams.opaquePower.toFixed(2) }}</span>
  1815. </div>
  1816. </div>
  1817. <div class="material-section">
  1818. <div class="section-label">边缘渐变淡出</div>
  1819. <div class="slider-item">
  1820. <input type="range" v-model.number="flowParams.edgeFade" min="0" max="0.5" step="0.01" />
  1821. <span class="slider-value">{{ flowParams.edgeFade.toFixed(2) }}</span>
  1822. </div>
  1823. </div>
  1824. <div class="material-section">
  1825. <button class="default-btn" @click="saveFlowDefaults">设置默认值</button>
  1826. </div>
  1827. </template>
  1828. <template v-if="selectedMaterialKey === 'cscwater'">
  1829. <div class="material-section">
  1830. <div class="section-label">透明度</div>
  1831. <div class="slider-item">
  1832. <input type="range" v-model.number="cscwaterParams.alpha" min="0" max="1" step="0.01" />
  1833. <span class="slider-value">{{ cscwaterParams.alpha.toFixed(2) }}</span>
  1834. </div>
  1835. </div>
  1836. <div class="material-section">
  1837. <div class="section-label">浪高</div>
  1838. <div class="slider-item">
  1839. <input type="range" v-model.number="cscwaterParams.waveHeight" min="0" max="2" step="0.05" />
  1840. <span class="slider-value">{{ cscwaterParams.waveHeight.toFixed(2) }}</span>
  1841. </div>
  1842. </div>
  1843. <div class="material-section">
  1844. <div class="section-label">水流速度</div>
  1845. <div class="slider-item">
  1846. <input type="range" v-model.number="cscwaterParams.flowSpeed" min="0" max="3" step="0.1" />
  1847. <span class="slider-value">{{ cscwaterParams.flowSpeed.toFixed(1) }}</span>
  1848. </div>
  1849. </div>
  1850. <div class="material-section">
  1851. <div class="section-label">流向 X</div>
  1852. <div class="slider-item">
  1853. <input type="range" v-model.number="cscwaterParams.flowDirectionX" min="-2" max="2" step="0.1" />
  1854. <span class="slider-value">{{ cscwaterParams.flowDirectionX.toFixed(1) }}</span>
  1855. </div>
  1856. </div>
  1857. <div class="material-section">
  1858. <div class="section-label">流向 Z</div>
  1859. <div class="slider-item">
  1860. <input type="range" v-model.number="cscwaterParams.flowDirectionY" min="-2" max="2" step="0.1" />
  1861. <span class="slider-value">{{ cscwaterParams.flowDirectionY.toFixed(1) }}</span>
  1862. </div>
  1863. </div>
  1864. <div class="material-section">
  1865. <div class="section-label">法线旋转</div>
  1866. <div class="slider-item">
  1867. <input type="range" v-model.number="cscwaterParams.normalRotation" min="-180" max="180" step="1" />
  1868. <span class="slider-value">{{ cscwaterParams.normalRotation.toFixed(0) }}°</span>
  1869. </div>
  1870. </div>
  1871. <div class="material-section">
  1872. <div class="section-label">浅水颜色</div>
  1873. <div class="color-item">
  1874. <input type="color" v-model="cscwaterParams.waterColor" />
  1875. <span class="color-value">{{ cscwaterParams.waterColor }}</span>
  1876. </div>
  1877. </div>
  1878. <div class="material-section">
  1879. <div class="section-label">深水颜色</div>
  1880. <div class="color-item">
  1881. <input type="color" v-model="cscwaterParams.deepColor" />
  1882. <span class="color-value">{{ cscwaterParams.deepColor }}</span>
  1883. </div>
  1884. </div>
  1885. <div class="material-section">
  1886. <div class="section-label">水深范围</div>
  1887. <div class="slider-item">
  1888. <input type="range" v-model.number="cscwaterParams.depthRange" min="1" max="50" step="0.5" />
  1889. <span class="slider-value">{{ cscwaterParams.depthRange.toFixed(1) }}</span>
  1890. </div>
  1891. </div>
  1892. <div class="material-section">
  1893. <div class="section-label">水法线强度</div>
  1894. <div class="slider-item">
  1895. <input type="range" v-model.number="cscwaterParams.waterNormalStrength" min="0" max="2" step="0.05" />
  1896. <span class="slider-value">{{ cscwaterParams.waterNormalStrength.toFixed(2) }}</span>
  1897. </div>
  1898. </div>
  1899. <div class="material-section">
  1900. <div class="section-label">水纹平铺</div>
  1901. <div class="slider-item">
  1902. <input type="range" v-model.number="cscwaterParams.waterNormalTiling" min="0.01" max="5" step="0.01" />
  1903. <span class="slider-value">{{ cscwaterParams.waterNormalTiling.toFixed(2) }}</span>
  1904. </div>
  1905. </div>
  1906. <div class="material-section">
  1907. <div class="section-label">高光强度</div>
  1908. <div class="slider-item">
  1909. <input type="range" v-model.number="cscwaterParams.specIntensity" min="0" max="5" step="0.1" />
  1910. <span class="slider-value">{{ cscwaterParams.specIntensity.toFixed(1) }}</span>
  1911. </div>
  1912. </div>
  1913. <div class="material-section">
  1914. <div class="section-label">高光锐度</div>
  1915. <div class="slider-item">
  1916. <input type="range" v-model.number="cscwaterParams.specPower" min="1" max="256" step="1" />
  1917. <span class="slider-value">{{ cscwaterParams.specPower.toFixed(0) }}</span>
  1918. </div>
  1919. </div>
  1920. <div class="material-section">
  1921. <div class="section-label">菲涅尔功率</div>
  1922. <div class="slider-item">
  1923. <input type="range" v-model.number="cscwaterParams.fresnelPower" min="0.1" max="10" step="0.1" />
  1924. <span class="slider-value">{{ cscwaterParams.fresnelPower.toFixed(1) }}</span>
  1925. </div>
  1926. </div>
  1927. <div class="material-section">
  1928. <div class="section-label">菲涅尔强度</div>
  1929. <div class="slider-item">
  1930. <input type="range" v-model.number="cscwaterParams.fresnelIntensity" min="0" max="3" step="0.1" />
  1931. <span class="slider-value">{{ cscwaterParams.fresnelIntensity.toFixed(1) }}</span>
  1932. </div>
  1933. </div>
  1934. <div class="material-section">
  1935. <div class="section-label">泡沫强度</div>
  1936. <div class="slider-item">
  1937. <input type="range" v-model.number="cscwaterParams.foamIntensity" min="0" max="2" step="0.05" />
  1938. <span class="slider-value">{{ cscwaterParams.foamIntensity.toFixed(2) }}</span>
  1939. </div>
  1940. </div>
  1941. <div class="material-section">
  1942. <div class="section-label">碰撞泡沫阈值</div>
  1943. <div class="slider-item">
  1944. <input type="range" v-model.number="cscwaterParams.collisionFoamThreshold" min="0" max="1" step="0.01" />
  1945. <span class="slider-value">{{ cscwaterParams.collisionFoamThreshold.toFixed(2) }}</span>
  1946. </div>
  1947. </div>
  1948. <div class="material-section">
  1949. <div class="section-label">碰撞泡沫强度</div>
  1950. <div class="slider-item">
  1951. <input type="range" v-model.number="cscwaterParams.collisionFoamStrength" min="0" max="2" step="0.05" />
  1952. <span class="slider-value">{{ cscwaterParams.collisionFoamStrength.toFixed(2) }}</span>
  1953. </div>
  1954. </div>
  1955. <div class="material-section">
  1956. <button class="default-btn" @click="saveCscwaterDefaults">设置默认值</button>
  1957. </div>
  1958. </template>
  1959. </div>
  1960. <!-- 视角选择面板 -->
  1961. <div v-if="props.showDebugTools && showCameraPresetPanel" class="panel camera-preset-panel">
  1962. <div class="panel-header">
  1963. <span class="panel-title">视角选择</span>
  1964. <button class="toggle-btn" @click="showCameraPresetPanel = false">×</button>
  1965. </div>
  1966. <div class="preset-list">
  1967. <div
  1968. v-for="preset in cameraPresets"
  1969. :key="preset.id"
  1970. class="preset-item"
  1971. @click="showCameraPresetPanel = false; flyToPreset(preset.id)"
  1972. >
  1973. <span class="preset-name">{{ preset.name }}</span>
  1974. </div>
  1975. </div>
  1976. </div>
  1977. </template>
  1978. <style scoped>
  1979. /* 全屏 3D 渲染容器 */
  1980. .scene-container {
  1981. position: fixed;
  1982. top: 0;
  1983. left: 0;
  1984. width: 100vw;
  1985. height: 100vh;
  1986. overflow: hidden;
  1987. }
  1988. /* 右上角调试工具栏 */
  1989. .toolbar {
  1990. position: fixed;
  1991. top: 20px;
  1992. right: 20px;
  1993. display: flex;
  1994. flex-direction: column;
  1995. gap: 8px;
  1996. z-index: 1001;
  1997. }
  1998. .toolbar-btn {
  1999. padding: 10px 16px;
  2000. border: none;
  2001. border-radius: 8px;
  2002. background: rgba(0, 0, 0, 0.85);
  2003. color: #888;
  2004. font-size: 13px;
  2005. font-weight: bold;
  2006. cursor: pointer;
  2007. transition: all 0.2s;
  2008. min-width: 70px;
  2009. }
  2010. .toolbar-btn:hover {
  2011. background: rgba(30, 30, 30, 0.9);
  2012. color: #fff;
  2013. }
  2014. .toolbar-btn.active {
  2015. color: #fff;
  2016. box-shadow: 0 0 10px rgba(255, 255, 255, 0.2);
  2017. }
  2018. .toolbar-btn:nth-child(1).active { color: #4fc3f7; }
  2019. .toolbar-btn:nth-child(2).active { color: #ff9800; }
  2020. .toolbar-btn:nth-child(3).active { color: #ff4081; }
  2021. .toolbar-btn:nth-child(4).active { color: #4dd0e1; }
  2022. .toolbar-btn:nth-child(5).active { color: #ffeb3b; }
  2023. /* 调试面板通用样式 */
  2024. .panel {
  2025. position: fixed;
  2026. top: 20px;
  2027. left: 20px;
  2028. background: rgba(0, 0, 0, 0.85);
  2029. color: white;
  2030. padding: 15px;
  2031. border-radius: 8px;
  2032. font-family: 'Courier New', monospace;
  2033. z-index: 1000;
  2034. max-height: calc(100vh - 40px);
  2035. overflow-y: auto;
  2036. }
  2037. .coordinate-panel { min-width: 200px; }
  2038. .camera-panel { min-width: 200px; }
  2039. .model-panel { min-width: 260px; }
  2040. .material-panel { min-width: 260px; }
  2041. .camera-preset-panel {
  2042. min-width: 200px;
  2043. left: auto;
  2044. right: 20px;
  2045. top: auto;
  2046. bottom: 20px;
  2047. }
  2048. .preset-list {
  2049. display: flex;
  2050. flex-direction: column;
  2051. gap: 4px;
  2052. margin-top: 8px;
  2053. }
  2054. .preset-item {
  2055. padding: 8px 12px;
  2056. border-radius: 6px;
  2057. background: rgba(255, 255, 255, 0.08);
  2058. color: #ccc;
  2059. font-size: 13px;
  2060. cursor: pointer;
  2061. transition: all 0.2s;
  2062. }
  2063. .preset-item:hover {
  2064. background: rgba(255, 235, 59, 0.2);
  2065. color: #fff;
  2066. }
  2067. .preset-name {
  2068. font-family: 'Microsoft YaHei', sans-serif;
  2069. }
  2070. .panel-header {
  2071. display: flex;
  2072. justify-content: space-between;
  2073. align-items: center;
  2074. margin-bottom: 12px;
  2075. padding-bottom: 8px;
  2076. border-bottom: 1px solid rgba(255, 255, 255, 0.3);
  2077. }
  2078. .panel-title {
  2079. font-weight: bold;
  2080. font-size: 14px;
  2081. }
  2082. .coordinate-panel .panel-title { color: #4fc3f7; }
  2083. .camera-panel .panel-title { color: #ff9800; }
  2084. .model-panel .panel-title { color: #ff4081; }
  2085. .material-panel .panel-title { color: #4dd0e1; }
  2086. .camera-preset-panel .panel-title { color: #ffeb3b; }
  2087. /* 关闭按钮 */
  2088. .toggle-btn {
  2089. width: 22px;
  2090. height: 22px;
  2091. border: none;
  2092. border-radius: 4px;
  2093. background: rgba(255, 255, 255, 0.15);
  2094. color: white;
  2095. font-size: 14px;
  2096. line-height: 1;
  2097. cursor: pointer;
  2098. display: flex;
  2099. align-items: center;
  2100. justify-content: center;
  2101. transition: background 0.2s;
  2102. }
  2103. .toggle-btn:hover {
  2104. background: rgba(255, 255, 255, 0.3);
  2105. }
  2106. /* 坐标列表 */
  2107. .coordinate-item {
  2108. display: flex;
  2109. justify-content: space-between;
  2110. margin: 5px 0;
  2111. }
  2112. .coordinate-label {
  2113. color: #81c784;
  2114. font-weight: bold;
  2115. }
  2116. .coordinate-value {
  2117. color: #fff;
  2118. }
  2119. /* 相机信息分组 */
  2120. .camera-section {
  2121. margin-bottom: 10px;
  2122. }
  2123. .section-label {
  2124. color: #81c784;
  2125. font-size: 11px;
  2126. margin-bottom: 5px;
  2127. font-weight: bold;
  2128. }
  2129. .camera-item {
  2130. display: flex;
  2131. justify-content: space-between;
  2132. margin: 3px 0;
  2133. }
  2134. .camera-label {
  2135. color: #4fc3f7;
  2136. }
  2137. .camera-value {
  2138. color: #fff;
  2139. }
  2140. /* 模型/材质分组 */
  2141. .model-section,
  2142. .material-section {
  2143. margin-bottom: 12px;
  2144. }
  2145. .model-section .section-label,
  2146. .material-section .section-label {
  2147. color: #81c784;
  2148. font-size: 11px;
  2149. margin-bottom: 6px;
  2150. font-weight: bold;
  2151. }
  2152. /* 下拉选择器 */
  2153. .model-select {
  2154. width: 100%;
  2155. padding: 6px 8px;
  2156. border: 1px solid rgba(255, 255, 255, 0.2);
  2157. border-radius: 6px;
  2158. background: rgba(255, 255, 255, 0.08);
  2159. color: #fff;
  2160. font-size: 13px;
  2161. font-family: inherit;
  2162. cursor: pointer;
  2163. outline: none;
  2164. }
  2165. .model-select option {
  2166. background: #222;
  2167. color: #fff;
  2168. }
  2169. /* 滑条控件 */
  2170. .slider-item {
  2171. display: flex;
  2172. align-items: center;
  2173. gap: 10px;
  2174. margin-bottom: 4px;
  2175. }
  2176. .slider-label {
  2177. font-size: 12px;
  2178. color: #aaa;
  2179. min-width: 32px;
  2180. }
  2181. .slider-item input[type="range"] {
  2182. flex: 1;
  2183. height: 4px;
  2184. -webkit-appearance: none;
  2185. appearance: none;
  2186. background: rgba(255, 255, 255, 0.15);
  2187. border-radius: 2px;
  2188. outline: none;
  2189. }
  2190. .slider-item input[type="range"]::-webkit-slider-thumb {
  2191. -webkit-appearance: none;
  2192. appearance: none;
  2193. width: 12px;
  2194. height: 12px;
  2195. border-radius: 50%;
  2196. background: #ff4081;
  2197. cursor: pointer;
  2198. }
  2199. .slider-value {
  2200. min-width: 50px;
  2201. text-align: right;
  2202. color: #fff;
  2203. font-size: 11px;
  2204. font-family: 'Consolas', monospace;
  2205. }
  2206. /* 数字输入框 */
  2207. .input-item {
  2208. display: flex;
  2209. align-items: center;
  2210. gap: 10px;
  2211. margin-bottom: 4px;
  2212. }
  2213. .input-label {
  2214. font-size: 12px;
  2215. color: #aaa;
  2216. min-width: 32px;
  2217. }
  2218. .number-input {
  2219. flex: 1;
  2220. padding: 4px 8px;
  2221. border: 1px solid rgba(255, 255, 255, 0.2);
  2222. border-radius: 6px;
  2223. background: rgba(255, 255, 255, 0.08);
  2224. color: #fff;
  2225. font-size: 13px;
  2226. font-family: 'Consolas', monospace;
  2227. outline: none;
  2228. }
  2229. .number-input:focus {
  2230. border-color: #ff4081;
  2231. }
  2232. /* 颜色拾取器 */
  2233. .color-item {
  2234. display: flex;
  2235. align-items: center;
  2236. gap: 10px;
  2237. }
  2238. .color-item input[type="color"] {
  2239. width: 40px;
  2240. height: 28px;
  2241. border: none;
  2242. border-radius: 4px;
  2243. cursor: pointer;
  2244. background: transparent;
  2245. }
  2246. .color-item input[type="color"]::-webkit-color-swatch-wrapper {
  2247. padding: 0;
  2248. }
  2249. .color-item input[type="color"]::-webkit-color-swatch {
  2250. border: none;
  2251. border-radius: 4px;
  2252. }
  2253. .color-value {
  2254. color: #fff;
  2255. font-size: 11px;
  2256. font-family: 'Consolas', monospace;
  2257. }
  2258. /* 材质面板滑条颜色(区别于模型的粉色,材质用青色) */
  2259. .material-section .slider-item input[type="range"] {
  2260. background: rgba(255, 255, 255, 0.15);
  2261. }
  2262. .material-section .slider-item input[type="range"]::-webkit-slider-thumb {
  2263. background: #4dd0e1;
  2264. }
  2265. /* 保存默认值按钮 */
  2266. .default-btn {
  2267. width: 100%;
  2268. padding: 8px 16px;
  2269. border: 1px solid rgba(255, 255, 255, 0.2);
  2270. border-radius: 6px;
  2271. background: rgba(255, 255, 255, 0.08);
  2272. color: #4dd0e1;
  2273. font-size: 13px;
  2274. cursor: pointer;
  2275. transition: all 0.2s;
  2276. }
  2277. .default-btn:hover {
  2278. background: rgba(77, 208, 225, 0.15);
  2279. border-color: #4dd0e1;
  2280. }
  2281. /* 底部提示消息 */
  2282. .toast-message {
  2283. position: fixed;
  2284. bottom: 40px;
  2285. left: 50%;
  2286. transform: translateX(-50%);
  2287. background: rgba(0, 0, 0, 0.85);
  2288. color: #4dd0e1;
  2289. padding: 10px 24px;
  2290. border-radius: 8px;
  2291. font-size: 14px;
  2292. font-family: 'Microsoft YaHei', sans-serif;
  2293. z-index: 9999;
  2294. pointer-events: none;
  2295. transition: opacity 0.3s ease;
  2296. border: 1px solid rgba(77, 208, 225, 0.3);
  2297. }
  2298. .toast-message.toast-fade {
  2299. opacity: 0;
  2300. }
  2301. </style>