@metagl/sdk-render 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ import{Cartesian3 as e,Color as t,DirectionalLight as n,PostProcessStage as i,Cartographic as s,Cartesian4 as o,JulianDate as a,Cartesian2 as l,Transforms as r,Matrix3 as d}from"cesium";class c{viewer;config;isEnabled=!1;originalSettings;static DEFAULT_CONFIG={enabled:!0,direction:new e(0,0,1),intensity:2.5,color:t.WHITE};constructor(e,t){this.viewer=e,this.config={...c.DEFAULT_CONFIG,...t}}initialize(){this.saveOriginalSettings()}saveOriginalSettings(){this.originalSettings={globeLighting:this.viewer.scene.globe.enableLighting,light:this.viewer.scene.light}}enable(){if(this.isEnabled)return void console.log("[Render: LightingSystem] 方向光已启用");const{direction:e,intensity:t,color:i}=this.config;if(this.viewer.scene.globe.enableLighting=!0,this.viewer.scene.light){const n=this.viewer.scene.light;n&&(n.direction=e.clone(),n.intensity=t,n.color=i)}else this.viewer.scene.light=new n({direction:e.clone(),intensity:t,color:i});this.isEnabled=!0,console.log("[Render: LightingSystem] 自定义方向光已启用"),this.viewer.scene.requestRender()}disable(){this.isEnabled?(this.originalSettings?(this.viewer.scene.globe.enableLighting=this.originalSettings.globeLighting,this.viewer.scene.light=this.originalSettings.light):(this.viewer.scene.globe.enableLighting=!1,this.viewer.scene.light=null),this.originalSettings=void 0,this.isEnabled=!1,console.log("[Render: LightingSystem] 自定义方向光已禁用"),this.viewer.scene.requestRender()):console.log("[Render: LightingSystem] 方向光已禁用")}update(e){if(this.config={...this.config,...e},this.isEnabled){const{direction:e,intensity:t,color:n}=this.config,i=this.viewer.scene.light;i&&(i.direction=e.clone(),i.intensity=t,i.color=n)}}getStatus(){return this.isEnabled}getConfig(){return{...this.config}}saveConfig(){return this.getConfig()}destroy(){this.disable(),console.log("[Render: LightingSystem] 已销毁")}}class g{viewer;config;isEnabled=!1;originalSettings;static DEFAULT_CONFIG={enabled:!0,softShadows:!0,size:4096,darkness:.51,maximumDistance:5e3,normalOffset:!0,fadingEnabled:!0,bias:1e-4};constructor(e,t){this.viewer=e,this.config={...g.DEFAULT_CONFIG,...t}}initialize(){this.saveOriginalSettings()}saveOriginalSettings(){this.originalSettings={enabled:this.viewer.scene.shadowMap.enabled,softShadows:this.viewer.scene.shadowMap.softShadows,size:this.viewer.scene.shadowMap.size,darkness:this.viewer.scene.shadowMap.darkness,maximumDistance:this.viewer.scene.shadowMap.maximumDistance,normalOffset:this.viewer.scene.shadowMap.normalOffset,fadingEnabled:this.viewer.scene.shadowMap.fadingEnabled}}enable(){if(this.isEnabled)return void console.log("[ShadowSystem] 阴影已启用");const e=this.viewer.scene.shadowMap,t=this.config;e.enabled=!0,e.softShadows=t.softShadows,e.size=t.size,e.darkness=t.darkness,e.maximumDistance=t.maximumDistance,e.normalOffset=t.normalOffset,e.fadingEnabled=t.fadingEnabled,e._pointBias=t.bias,this.isEnabled=!0,console.log("[ShadowSystem] 阴影系统已激活"),this.viewer.scene.requestRender()}disable(){this.isEnabled?(this.viewer.scene.shadowMap.enabled=!1,this.isEnabled=!1,console.log("[ShadowSystem] 阴影系统已禁用"),this.viewer.scene.requestRender()):console.log("[ShadowSystem] 阴影已禁用")}update(e){if(this.config={...this.config,...e},this.isEnabled){const e=this.viewer.scene.shadowMap,t=this.config;e.enabled=!0,e.softShadows=t.softShadows,e.size=t.size,e.darkness=t.darkness,e.maximumDistance=t.maximumDistance,e.normalOffset=t.normalOffset,e.fadingEnabled=t.fadingEnabled,e._pointBias=t.bias}console.log("[ShadowSystem] 配置已更新")}getStatus(){return this.isEnabled}getConfig(){return{...this.config}}saveConfig(){return this.getConfig()}destroy(){this.disable(),console.log("[ShadowSystem] 已销毁")}}class h{viewer;config;isEnabled=!1;stage;preRenderListener;static DEFAULT_VISIBILITY_CONFIG={minHeight:0,maxHeight:7700,transitionRange:8500,enabled:!0};static DEFAULT_CONFIG={enabled:!0,realPlanetRadius:6378137,windVector:new e(5,0,0),cloudCover:.39,cloudBase:2e3,cloudTop:6e3,cloudThickness:4e3,cloudLightIntensity:5.3,cloudIntensity:1,visibility:h.DEFAULT_VISIBILITY_CONFIG};constructor(e,t){this.viewer=e,this.config={...h.DEFAULT_CONFIG,...t,visibility:{...h.DEFAULT_VISIBILITY_CONFIG,...t?.visibility}},console.log("[VolumetricCloudsSystem] 构造函数配置:",this.config),this.config.enabled&&this.initialize()}updateCloudVisibility(){if(!this.stage||!this.stage.enabled||!this.config.visibility.enabled)return;const e=this.viewer.camera.positionCartographic.height;let t=1;const n=this.config.visibility;if(e<n.minHeight)t=1;else if(e>n.maxHeight)t=0;else if(e<n.minHeight+n.transitionRange){t=1-(e-n.minHeight)/n.transitionRange}else if(e>n.maxHeight-n.transitionRange){t=(n.maxHeight-e)/n.transitionRange}else t=.5;this.stage&&void 0!==this.stage.uniforms.cloudIntensity&&(this.stage.uniforms.cloudIntensity=t)}initialize(){console.log("[VolumetricCloudsSystem] 开始初始化..."),this.stage&&(this.viewer.scene.postProcessStages.remove(this.stage),this.stage=void 0),this.preRenderListener&&this.viewer.scene.preRender.removeEventListener(this.preRenderListener),this.stage=new i({name:"volumetric_clouds",fragmentShader:this.getShader(),uniforms:{realPlanetRadius:()=>this.getRealPlanetRadius(),windVector:this.config.windVector,cloudCover:this.config.cloudCover,cloudBase:this.config.cloudBase,cloudTop:this.config.cloudTop,cloudThickness:this.config.cloudThickness,cloudBaseRadius:()=>this.getRealPlanetRadius()+this.config.cloudBase,cloudTopRadius:()=>this.getRealPlanetRadius()+this.config.cloudTop,cloudLightIntensity:this.config.cloudLightIntensity,cloudIntensity:this.config.cloudIntensity}}),this.viewer.scene.postProcessStages.add(this.stage),this.preRenderListener=()=>{this.stage&&this.stage.enabled&&(this.updateUniforms(),this.updateCloudVisibility())},this.viewer.scene.preRender.addEventListener(this.preRenderListener),this.isEnabled=!0,this.stage&&(this.stage.enabled=!0),console.log("[VolumetricCloudsSystem] 体积云系统已初始化完成",{windVector:this.config.windVector,cloudCover:this.config.cloudCover,cloudBase:this.config.cloudBase,cloudTop:this.config.cloudTop,cloudLightIntensity:this.config.cloudLightIntensity,cloudIntensity:this.config.cloudIntensity,enabled:this.config.enabled,visibility:this.config.visibility})}getRealPlanetRadius(){const t=this.viewer.camera.positionCartographic,n=this.viewer.scene.globe.ellipsoid.cartographicToCartesian(new s(t.longitude,t.latitude,0),new e);return e.magnitude(n)}updateUniforms(){if(!this.stage)return;const e=this.getRealPlanetRadius();this.stage.uniforms.realPlanetRadius=e,this.stage.uniforms.cloudBaseRadius=e+this.config.cloudBase,this.stage.uniforms.cloudTopRadius=e+this.config.cloudTop}enable(){this.isEnabled?console.log("[VolumetricCloudsSystem] 体积云已启用"):(this.stage?this.stage.enabled=!0:this.initialize(),this.isEnabled=!0,console.log("[VolumetricCloudsSystem] 体积云系统已启用"),this.viewer.scene.requestRender())}disable(){this.isEnabled?(this.stage&&(this.stage.enabled=!1),this.isEnabled=!1,console.log("[VolumetricCloudsSystem] 体积云系统已禁用"),this.viewer.scene.requestRender()):console.log("[VolumetricCloudsSystem] 体积云已禁用")}update(e){const t=this.isEnabled;this.config={...this.config,...e,visibility:e.visibility?{...this.config.visibility,...e.visibility}:this.config.visibility},console.log("[VolumetricCloudsSystem] 更新配置:",e),this.stage&&Object.entries(e).forEach(([e,t])=>{void 0!==t&&e in this.stage.uniforms&&(this.stage.uniforms[e]=t)}),void 0!==e.enabled&&e.enabled!==t&&(e.enabled?this.enable():this.disable()),this.viewer.scene.requestRender()}getStatus(){return this.isEnabled}getConfig(){return{...this.config}}saveConfig(){return this.getConfig()}getShader(){return"\n precision highp float;\n uniform float realPlanetRadius;\n const float windSpeedRatio = 0.0002;\n uniform float cloudCover;\n uniform float cloudBase;\n uniform float cloudTop;\n uniform vec3 windVector;\n uniform float cloudThickness;\n uniform float cloudBaseRadius;\n uniform float cloudTopRadius;\n uniform float cloudLightIntensity;\n uniform float cloudIntensity;\n\n const float PI = 3.14159265359;\n const float TWO_PI = 6.28318530718;\n const float FOUR_PI = 12.5663706144;\n\n #define CLOUDS_MAX_LOD 1\n #define CLOUDS_MARCH_STEP 500.0\n #define CLOUDS_DENS_MARCH_STEP 100.0\n #define MAXIMUM_CLOUDS_STEPS 300\n #define CLOUDS_MAX_VIEWING_DISTANCE 250000.0\n\n // 射线与球体相交\n vec2 raySphereIntersect(vec3 r0, vec3 rd, float sr) {\n float a = dot(rd, rd);\n float b = 2.0 * dot(rd, r0);\n float c = dot(r0, r0) - (sr * sr);\n float d = (b * b) - 4.0 * a * c;\n\n if (d < 0.0) return vec2(-1.0, -1.0);\n float squaredD = sqrt(d);\n\n return vec2(\n (-b - squaredD) / (2.0 * a),\n (-b + squaredD) / (2.0 * a)\n );\n }\n\n float saturate (float value) {\n return clamp(value, 0.0, 1.0);\n }\n\n float isotropic() {\n return 0.07957747154594767;\n }\n\n float rayleigh(float costh) {\n return (3.0 / (16.0 * PI)) * (1.0 + pow(costh, 2.0));\n }\n\n float Schlick(float k, float costh) {\n return (1.0 - k * k) / (FOUR_PI * pow(1.0 - k * costh, 2.0));\n }\n float g = 0.9;\n\n float hash(float p) {\n p = fract(p * .1031);\n p *= p + 33.33;\n p *= p + p;\n return fract(p);\n }\n\n // 噪声\n float noise(in vec3 x) {\n vec3 p = floor(x);\n vec3 f = fract(x);\n f = f*f*(3.0 - 2.0*f);\n float n = p.x + p.y*157.0 + 113.0*p.z;\n return mix(mix(mix( hash(n+ 0.0), hash(n+ 1.0),f.x),\n mix( hash(n+157.0), hash(n+158.0),f.x),f.y),\n mix(mix( hash(n+113.0), hash(n+114.0),f.x),\n mix(hash(n+270.0), hash(n+271.0),f.x),f.y),f.z);\n }\n\n // 云的密度\n float cloudDensity(vec3 p, vec3 wind, int lod, inout float heightRatio) {\n float finalCoverage = cloudCover;\n if (finalCoverage <= 0.1) {\n return 0.0;\n }\n float height = length(p) - realPlanetRadius;\n heightRatio = (height - cloudBase) / cloudThickness;\n float positionResolution = 0.002;\n p = p * positionResolution + wind;\n float shape = noise(p * 0.3);\n float shapeHeight = noise(p * 0.05);\n float bn = 0.50000 * noise(p); p = p * 2.0;\n if( lod>=1 ) {\n bn += 0.20000 * noise(p); p = p * 2.11;\n }\n float cumuloNimbus = saturate((shapeHeight - 0.5) * 2.0);\n cumuloNimbus *= saturate(1.0 - pow(heightRatio - 0.5, 2.0) * 4.0);\n float cumulus = saturate(1.0 - pow(heightRatio - 0.25, 2.0) * 25.0) * shapeHeight;\n float stratoCumulus = saturate(1.0 - pow(heightRatio - 0.12, 2.0) * 60.0) * (1.0 - shapeHeight);\n float dens = saturate(stratoCumulus + cumulus + cumuloNimbus) * 2.0 * finalCoverage;\n dens -= 1.0 - shape;\n dens -= bn;\n return clamp(dens, 0.0, 1.0);\n }\n\n uniform sampler2D colorTexture;\n uniform sampler2D depthTexture;\n in vec2 v_textureCoordinates;\n vec3 skyAmbientColor = vec3(0.705, 0.850, 0.952);\n vec3 groundAmbientColor = vec3(0.741, 0.898, 0.823);\n float distanceQualityR = 0.00005;\n float minDistance = 10.0;\n\n vec4 calculate_clouds(\n vec3 start,\n vec3 dir,\n float maxDistance,\n vec3 light_dir,\n vec3 wind\n ) {\n vec4 cloud = vec4(0.0, 0.0, 0.0, 1.0);\n vec2 toTop = raySphereIntersect(start, dir, cloudTopRadius);\n vec2 toCloudBase = raySphereIntersect(start, dir, cloudBaseRadius);\n float startHeight = length(start) - realPlanetRadius;\n float absoluteMaxDistance = CLOUDS_MAX_VIEWING_DISTANCE;\n float tmin = minDistance;\n float tmax = maxDistance;\n\n if (startHeight > cloudTop) {\n if (toTop.x < 0.0) return vec4(0.0);\n tmin = toTop.x;\n if (toCloudBase.x > 0.0) {\n tmax = min(toCloudBase.x, maxDistance);\n }\n else {\n tmax = min(toTop.y, maxDistance);\n }\n } else if (startHeight < cloudBase) {\n tmin = toCloudBase.y;\n tmax = min(toTop.y, maxDistance);\n } else {\n if (toCloudBase.x > 0.0) {\n tmax = min(toCloudBase.x, maxDistance);\n }\n else {\n tmax = min(toTop.y, maxDistance);\n }\n }\n\n tmin = max(tmin, minDistance);\n tmax = min(tmax, absoluteMaxDistance);\n\n if (tmax < tmin) return vec4(0.0);\n\n float rayLength = tmax - tmin;\n float longMarchStep = rayLength / float(MAXIMUM_CLOUDS_STEPS);\n longMarchStep = max(longMarchStep, CLOUDS_MARCH_STEP);\n\n float shortMarchStep = CLOUDS_DENS_MARCH_STEP;\n float numberApproachSteps = (CLOUDS_MARCH_STEP / CLOUDS_DENS_MARCH_STEP) * 2.0;\n float distance = tmin;\n float dens = 0.0;\n float marchStep;\n\n float lastDensity;\n float kInScattering = 0.99;\n float dotLightRay = dot(dir, light_dir);\n float inScattering = Schlick(kInScattering, dotLightRay);\n float outScattering = isotropic();\n float sunScatteringPhase = mix(outScattering, inScattering, dotLightRay);\n float ambientScatteringPhase = isotropic();\n bool inCloud = false;\n float stepsBeforeExitingCloud = 0.0;\n\n for (int i = 0; i < MAXIMUM_CLOUDS_STEPS; i++) {\n vec3 position = start + dir * distance;\n int qualityRatio = int(distance * distanceQualityR);\n int lod = CLOUDS_MAX_LOD - qualityRatio;\n float heightRatio;\n\n if (inCloud == true) {\n marchStep = shortMarchStep;\n } else {\n marchStep = longMarchStep;\n lod = 0;\n }\n\n dens = cloudDensity(position, wind, lod, heightRatio);\n\n if(dens > 0.01) {\n if (inCloud != true) {\n inCloud = true;\n stepsBeforeExitingCloud = numberApproachSteps;\n distance = clamp(distance - CLOUDS_MARCH_STEP, tmin, tmax);\n continue;\n }\n\n float deltaDens = clamp((dens - lastDensity) * 10.0, -1.0, 1.0);\n float lighting = (abs(deltaDens - dotLightRay) / 2.0) * clamp((heightRatio - 0.02) * 20.0, 0.5, 1.0);\n lastDensity = dens;\n float scatteringCoeff = 0.15 * dens;\n float extinctionCoeff = 0.01 * dens;\n cloud.a *= exp(-extinctionCoeff * marchStep);\n float sunIntensityAtSurface = clamp(0.2 - dens, 0.0, 1.0);\n vec3 sunLight = lighting * czm_lightColor * sunIntensityAtSurface * czm_lightColor.z * cloudLightIntensity;\n vec3 ambientSun = czm_lightColor * sunIntensityAtSurface * czm_lightColor.z * isotropic() * cloudLightIntensity;\n vec3 skyAmbientLight = (skyAmbientColor * czm_lightColor.z + ambientSun);\n vec3 groundAmbientLight = (groundAmbientColor * czm_lightColor.z * 0.5 + ambientSun);\n vec3 ambientLight = mix(groundAmbientLight, skyAmbientLight, heightRatio);\n vec3 stepScattering = scatteringCoeff * marchStep * (sunScatteringPhase * sunLight + ambientScatteringPhase * ambientLight);\n cloud.rgb += cloud.a * stepScattering;\n\n if (cloud.a < 0.01) {\n cloud.a = 0.0;\n break;\n }\n } else {\n if (stepsBeforeExitingCloud > 0.0) {\n stepsBeforeExitingCloud--;\n }\n else {\n inCloud = false;\n }\n }\n\n distance += marchStep;\n\n if (distance > tmax) {\n break;\n }\n }\n cloud.a = (1.0 - cloud.a);\n return cloud;\n }\n\n void main() {\n vec4 color = texture(colorTexture, v_textureCoordinates);\n vec4 rawDepthColor = texture(depthTexture, v_textureCoordinates);\n float depth = czm_unpackDepth(rawDepthColor);\n if (depth == 0.0) {\n depth = 1.0;\n }\n vec4 positionEC = czm_windowToEyeCoordinates(gl_FragCoord.xy, depth);\n vec4 worldCoordinate = czm_inverseView * positionEC;\n vec3 vWorldPosition = worldCoordinate.xyz / worldCoordinate.w;\n vec3 posToEye = vWorldPosition - czm_viewerPositionWC;\n vec3 direction = normalize(posToEye);\n vec3 lightDirection = normalize(czm_sunPositionWC);\n float distance = length(posToEye);\n\n if (depth == 1.0) {\n distance = CLOUDS_MAX_VIEWING_DISTANCE;\n }\n vec3 wind = windVector * czm_frameNumber * windSpeedRatio;\n vec4 clouds = calculate_clouds(\n czm_viewerPositionWC,\n direction,\n distance,\n lightDirection,\n wind\n );\n \n // 应用云层强度控制\n clouds.rgb *= cloudIntensity;\n clouds.a *= cloudIntensity;\n \n color = mix(color, clouds, clouds.a * clouds.a * cloudIntensity);\n\n float exposure = 1.2;\n color = vec4(1.0 - exp(-exposure * color.rgb), color.a);\n out_FragColor = color;\n }\n "}destroy(){this.stage&&(this.viewer.scene.postProcessStages.remove(this.stage),this.stage=void 0),this.preRenderListener&&this.viewer.scene.preRender.removeEventListener(this.preRenderListener),this.isEnabled=!1,console.log("[VolumetricCloudsSystem] 已销毁")}}class u{viewer;config;listeners=[];preRenderListener;isEnabled=!1;static DEFAULT_CONFIG={enabled:!0,minHeight:0,maxHeight:3e3,transitionRange:500,intensityCallback:(e,t)=>{let n=1;if(e<t.minHeight)n=1;else if(e>t.maxHeight)n=0;else if(e<t.minHeight+t.transitionRange){n=1-(e-t.minHeight)/t.transitionRange}else if(e>t.maxHeight-t.transitionRange){n=(t.maxHeight-e)/t.transitionRange}else n=.5;return n}};constructor(e,t){this.viewer=e,this.config={...u.DEFAULT_CONFIG,...t}}initialize(){this.isEnabled||(this.preRenderListener=()=>{if(!this.config.enabled)return;const e=this.viewer.camera.positionCartographic.height,t=this.config.intensityCallback(e,this.config);this.notifyListeners(t)},this.viewer.scene.preRender.addEventListener(this.preRenderListener),this.isEnabled=!0,console.log("[CameraListener] 相机监听器已初始化"))}addListener(e){this.listeners.includes(e)||this.listeners.push(e)}removeListener(e){const t=this.listeners.indexOf(e);-1!==t&&this.listeners.splice(t,1)}notifyListeners(e){this.listeners.forEach(t=>{try{t(e)}catch(e){console.warn("[CameraListener] 监听器回调错误:",e)}})}update(e){this.config={...this.config,...e}}enable(){this.isEnabled||this.initialize(),this.config.enabled=!0,console.log("[CameraListener] 相机监听器已启用")}disable(){this.config.enabled=!1,console.log("[CameraListener] 相机监听器已禁用")}getStatus(){return this.isEnabled&&this.config.enabled}getConfig(){return{...this.config}}getCurrentIntensity(){const e=this.viewer.camera.positionCartographic.height;return this.config.intensityCallback(e,this.config)}destroy(){this.preRenderListener&&this.viewer.scene.preRender.removeEventListener(this.preRenderListener),this.listeners=[],this.isEnabled=!1,console.log("[CameraListener] 相机监听器已销毁")}}class p{viewer;config;listeners=new Map;isInitialized=!1;static DEFAULT_CONFIG={volumetricClouds:{enabled:!0,minHeight:0,maxHeight:7700,transitionRange:8500,intensityCallback:u.DEFAULT_CONFIG.intensityCallback},distanceFog:{enabled:!0,minHeight:0,maxHeight:3e3,transitionRange:500,intensityCallback:u.DEFAULT_CONFIG.intensityCallback},heightFog:{enabled:!0,minHeight:0,maxHeight:3e3,transitionRange:500,intensityCallback:u.DEFAULT_CONFIG.intensityCallback},atmosphereScattering:{enabled:!0,minHeight:0,maxHeight:1e4,transitionRange:1e3,intensityCallback:u.DEFAULT_CONFIG.intensityCallback}};constructor(e,t){this.viewer=e,this.config={...p.DEFAULT_CONFIG,...t,volumetricClouds:{...p.DEFAULT_CONFIG.volumetricClouds,...t?.volumetricClouds},distanceFog:{...p.DEFAULT_CONFIG.distanceFog,...t?.distanceFog},heightFog:{...p.DEFAULT_CONFIG.heightFog,...t?.heightFog},atmosphereScattering:{...p.DEFAULT_CONFIG.atmosphereScattering,...t?.atmosphereScattering}}}initialize(){this.isInitialized||(Object.entries(this.config).forEach(([e,t])=>{if(t.enabled){const n=new u(this.viewer,t);n.initialize(),this.listeners.set(e,n)}}),this.isInitialized=!0,console.log("[CameraListenerSystem] 相机监听系统已初始化"))}getListener(e){return this.listeners.get(e)}addListener(e,t){let n=this.listeners.get(e);if(!n){const t=this.config[e];t&&(n=new u(this.viewer,t),n.initialize(),this.listeners.set(e,n))}n&&n.addListener(t)}removeListener(e,t){const n=this.listeners.get(e);n&&n.removeListener(t)}updateSystemConfig(e,t){const n=this.listeners.get(e),i=this.config[e];if(i){const s={...i,...t};if(this.config[e]=s,n)n.update(s);else if(s.enabled){const t=new u(this.viewer,s);t.initialize(),this.listeners.set(e,t)}}}update(e){Object.entries(e).forEach(([e,t])=>{t&&this.updateSystemConfig(e,t)})}enableAll(){this.listeners.forEach(e=>e.enable())}disableAll(){this.listeners.forEach(e=>e.disable())}enableSystem(e){const t=this.listeners.get(e);if(t)t.enable();else{const t=this.config[e];if(t){const n=new u(this.viewer,t);n.initialize(),this.listeners.set(e,n)}}}disableSystem(e){const t=this.listeners.get(e);t&&t.disable()}destroy(){this.listeners.forEach(e=>e.destroy()),this.listeners.clear(),this.isInitialized=!1,console.log("[CameraListenerSystem] 相机监听系统已销毁")}getStatus(){const e={};return this.listeners.forEach((t,n)=>{e[n]=t.getStatus()}),e}getConfig(){return{...this.config}}}class m{viewer;config;isEnabled=!1;stage;preRenderListener;cameraListener;static DEFAULT_VISIBILITY_CONFIG={enabled:!0,minHeight:0,maxHeight:1e4,transitionRange:2e3,intensityCallback:(e,t)=>{let n=1;if(e<t.minHeight)n=1;else if(e>t.maxHeight)n=0;else if(e<t.minHeight+t.transitionRange){const i=(e-t.minHeight)/t.transitionRange;n=1-Math.pow(i,.8)}else if(e>t.maxHeight-t.transitionRange){const i=(t.maxHeight-e)/t.transitionRange;n=Math.pow(i,.8)}else n=1;return n}};static DEFAULT_CONFIG={enabled:!0,atmosphereIntensity:.91,rayleighIntensity:1,mieIntensity:1,bDensity:.01,bColor:t.fromCssColorString("#b6d3f5ff"),visibility:m.DEFAULT_VISIBILITY_CONFIG};updateAtmosphereVisibility(e){this.stage&&this.stage.enabled&&(this.stage&&void 0!==this.stage.uniforms.atmosphereIntensity&&(this.stage.uniforms.atmosphereIntensity=this.config.atmosphereIntensity*e),this.stage&&(this.stage.uniforms.rayleighIntensity=this.config.rayleighIntensity*e,this.stage.uniforms.mieIntensity=this.config.mieIntensity*e))}constructor(e,t){this.viewer=e,this.config={...m.DEFAULT_CONFIG,...t,visibility:{...m.DEFAULT_VISIBILITY_CONFIG,...t?.visibility}},console.log("[AtmosphereScatteringSystem] 构造函数配置:",this.config),this.config.enabled&&this.initialize()}initialize(){console.log("[AtmosphereScatteringSystem] 开始初始化..."),this.stage&&(this.viewer.scene.postProcessStages.remove(this.stage),this.stage=void 0),this.preRenderListener&&this.viewer.scene.preRender.removeEventListener(this.preRenderListener),this.stage=new i({fragmentShader:this.getShader(),uniforms:{atmosphereIntensity:this.config.atmosphereIntensity,rayleighIntensity:this.config.rayleighIntensity,mieIntensity:this.config.mieIntensity,bDensity:this.config.bDensity,bColor:this.config.bColor}}),this.viewer.scene.postProcessStages.add(this.stage),this.cameraListener=new u(this.viewer,this.config.visibility),this.cameraListener.initialize(),this.cameraListener.addListener(e=>{this.updateAtmosphereVisibility(e)}),this.isEnabled=!0,this.stage&&(this.stage.enabled=!0),console.log("[AtmosphereScatteringSystem] 大气散射系统已初始化完成",{atmosphereIntensity:this.config.atmosphereIntensity,rayleighIntensity:this.config.rayleighIntensity,mieIntensity:this.config.mieIntensity,enabled:this.config.enabled,visibility:this.config.visibility})}enable(){this.isEnabled?console.log("[AtmosphereScatteringSystem] 大气散射已启用"):(this.stage?this.stage.enabled=!0:this.initialize(),this.isEnabled=!0,console.log("[AtmosphereScatteringSystem] 大气散射系统已启用"),this.viewer.scene.requestRender())}disable(){this.isEnabled?(this.stage&&(this.stage.enabled=!1),this.isEnabled=!1,console.log("[AtmosphereScatteringSystem] 大气散射系统已禁用"),this.viewer.scene.requestRender()):console.log("[AtmosphereScatteringSystem] 大气散射已禁用")}update(e){const t=this.isEnabled;this.config={...this.config,...e,visibility:e.visibility?{...this.config.visibility,...e.visibility}:this.config.visibility},console.log("[AtmosphereScatteringSystem] 更新配置:",e),this.stage&&Object.entries(e).forEach(([e,t])=>{void 0!==t&&e in this.stage.uniforms&&(this.stage.uniforms[e]=t)}),this.cameraListener&&e.visibility&&this.cameraListener.update(e.visibility),void 0!==e.enabled&&e.enabled!==t&&(e.enabled?this.enable():this.disable()),this.viewer.scene.requestRender()}getStatus(){return this.isEnabled}getConfig(){return{...this.config}}saveConfig(){return this.getConfig()}getShader(){return"\n precision highp float; // 使用高精度浮点数\n \n // 输入纹理和参数\n uniform sampler2D colorTexture; // 场景颜色纹理\n uniform sampler2D depthTexture; // 场景深度纹理\n uniform float atmosphereIntensity; // 大气强度控制参数\n uniform float rayleighIntensity; // 瑞利散射强度\n uniform float mieIntensity; // 米氏散射强度\n uniform vec4 customParam; // 自定义参数(未使用)\n in vec2 v_textureCoordinates; // 纹理坐标\n\n // 数学常量\n const float PI = 3.14159265359;\n const float TWO_PI = PI * 2.0;\n const float FOUR_PI = PI * 4.0;\n\n // 大气散射物理参数(基于Rayleigh-Mie散射模型)\n const vec3 baseBetaR = vec3(5.8e-6, 13.5e-6, 23.1e-6); // 瑞利散射系数(RGB三通道)\n const vec3 baseBetaM = vec3(20e-6); // 米氏散射系数\n const float hR = 8.5e3; // 瑞利散射高度标准(米)\n const float hM = 3.2e3; // 米氏散射高度标准(米)\n const int num_samples = 20; // 主光线采样数量\n const int num_samples_light = 6; // 光线采样数量\n \n // OpenGL ES兼容性宏定义\n #ifdef GL_ES\n #define _in(T) const in T // 输入参数宏\n #define _inout(T) inout T // 输入输出参数宏\n #define _out(T) out T // 输出参数宏\n #define _begin(type) type ( // 结构体构造开始宏\n #define _end ) // 结构体构造结束宏\n #define mul(a, b) (a) * (b) // 矩阵乘法宏\n #endif\n\n // 光线结构体定义\n struct ray_t {\n vec3 origin; // 光线起点\n vec3 direction; // 光线方向\n };\n\n // 球体结构体定义\n struct sphere_t {\n vec3 origin; // 球心位置\n float radius; // 球体半径\n int material; // 材质ID(未使用)\n };\n\n // 平面结构体定义\n struct plane_t {\n vec3 direction; // 平面法向量\n float distance; // 到原点距离\n int material; // 材质ID(未使用)\n };\n\n plane_t plane; // 全局平面对象\n\n // 光线与球体相交测试函数\n bool isect_sphere(_in(ray_t) ray, _in(sphere_t) sphere, _inout(float) t0, _inout(float) t1) {\n vec3 rc = sphere.origin - ray.origin; // 从光线起点到球心的向量\n float radius2 = sphere.radius * sphere.radius; // 球半径的平方\n float tca = dot(rc, ray.direction); // 投影长度\n float d2 = dot(rc, rc) - tca * tca; // 距离平方\n if (d2 > radius2) return false; // 无相交\n float thc = sqrt(radius2 - d2); // 相交弦长的一半\n t0 = tca - thc; // 近交点参数\n t1 = tca + thc; // 远交点参数\n return true;\n }\n\n // 瑞利散射相位函数\n float rayleigh_phase_func(float mu) {\n return 3. * (1. + mu*mu) / (16. * PI);\n }\n \n const float g = 0.78; // 米氏散射不对称参数(g>0表示前向散射)\n\n // Henyey-Greenstein相位函数(用于米氏散射)\n float henyey_greenstein_phase_func(float mu) {\n return (1. - g*g) / ((4. * PI) * pow(1. + g*g - 2.*g*mu, 1.5));\n }\n\n const float k = 1.55*g - 0.55 * (g*g*g); // Schlick近似参数\n\n // Schlick近似相位函数(HG函数的近似版本,计算更快)\n float schlick_phase_func(float mu) {\n return (1. - k*k) / (4. * PI * (1. + k*mu) * (1. + k*mu));\n }\n\n // 大气层球体定义(半径6420km)\n const sphere_t atmosphere = _begin(sphere_t)\n vec3(0, 0, 0), 6420e3, 0 // 球心在原点,半径6420公里\n _end;\n \n // 计算太阳光到达指定点的光学深度\n bool get_sun_light(\n _in(ray_t) ray, // 从采样点到太阳的光线\n _inout(float) optical_depthR, // 瑞利散射光学深度(输出)\n _inout(float) optical_depthM // 米氏散射光学深度(输出)\n ) {\n float t0, t1;\n // 计算光线与大气层球体的交点\n isect_sphere(ray, atmosphere, t0, t1);\n\n float march_pos = 0.; // 当前步进位置\n float march_step = t1 / float(num_samples_light); // 每步的步长\n\n // 沿着太阳光线进行光线步进\n for (int i = 0; i < num_samples_light; i++) {\n // 计算当前采样点位置\n vec3 s = ray.origin + ray.direction * (march_pos + 0.5 * march_step);\n\n // 计算海拔高度(地球半径6360km)\n float height = length(s) - 6360e3;\n\n // 如果在地面以下,光线被遮挡\n if (height < 0.)\n return false;\n\n // 累积光学深度(使用指数衰减模型)\n optical_depthR += exp(-height / hR) * march_step; // 瑞利散射\n optical_depthM += exp(-height / hM) * march_step; // 米氏散射\n\n march_pos += march_step; // 前进到下一个采样点\n }\n return true; // 光线未被遮挡\n }\n \n // 基于Ray Marching的大气散射计算主函数\n vec4 get_incident_light(_in(ray_t) ray) {\n vec3 dir = ray.direction; // 视线方向\n vec3 start = ray.origin; // 视线起点\n\n // 计算光线与大气层球体的交点(求解二次方程)\n float a = dot(dir, dir);\n float b = 2.0 * dot(dir, start);\n float radius2 = atmosphere.radius * atmosphere.radius;\n float c = dot(start, start) - radius2;\n float d = (b * b) - 4.0 * a * c; // 判别式\n\n if (d < 0.0) return vec4(0.0); // 无交点,返回黑色\n\n float squaredD = sqrt(d);\n // 计算光线在大气层内的起始和结束距离\n vec2 ray_length = vec2(\n max((-b - squaredD) / (2.0 * a), 0.0), // 进入点(不能小于0)\n min((-b + squaredD) / (2.0 * a), plane.distance) // 退出点(不能超过场景深度)\n );\n\n if (ray_length.x > ray_length.y) return vec4(0.0); // 无效区间\n\n // 计算步进大小\n float march_step = (ray_length.y - ray_length.x) / float(num_samples);\n\n // 计算散射角的余弦值\n float mu = dot(ray.direction, normalize(czm_sunPositionWC));\n\n // 计算散射相位函数\n float phaseR = rayleigh_phase_func(mu); // 瑞利散射相位\n float phaseM = henyey_greenstein_phase_func(mu); // 使用HG相位函数\n\n // 初始化光学深度和散射累积变量\n float optical_depthR = 0.; // 瑞利散射光学深度\n float optical_depthM = 0.; // 米氏散射光学深度\n vec3 sumR = vec3(0); // 瑞利散射累积\n vec3 sumM = vec3(0); // 米氏散射累积\n float march_pos = 0.; // 当前步进位置\n\n // 应用强度参数到散射系数\n vec3 betaR = baseBetaR * rayleighIntensity;\n vec3 betaM = baseBetaM * mieIntensity;\n\n // 主要的光线步进循环\n for (int i = 0; i < num_samples; i++) {\n // 计算当前采样点位置\n vec3 s = ray.origin + ray.direction * (march_pos + 0.5 * march_step);\n\n // 计算海拔高度\n float height = length(s) - 6360e3;\n\n // 计算当前采样点的散射密度(考虑高度衰减)\n float hr = exp(-height / hR) * march_step; // 瑞利散射密度\n float hm = exp(-height / hM) * march_step; // 米氏散射密度\n\n // 累积观察方向的光学深度\n optical_depthR += hr;\n optical_depthM += hm;\n\n // 构造从采样点到太阳的光线\n ray_t light_ray = _begin(ray_t)\n s, normalize(czm_sunPositionWC) // 起点:采样点,方向:太阳\n _end;\n\n // 计算太阳光到达采样点时的光学深度\n float optical_depth_lightR = 0.;\n float optical_depth_lightM = 0.;\n bool overground = get_sun_light(\n light_ray,\n optical_depth_lightR,\n optical_depth_lightM);\n\n if (overground) { // 如果太阳光未被地面遮挡\n // 计算总光学深度(观察方向 + 太阳方向)\n vec3 tau =\n betaR * (optical_depthR + optical_depth_lightR) + // 瑞利散射贡献\n betaM * 1.1 * (optical_depthM + optical_depth_lightM); // 米氏散射贡献(增强系数1.1)\n\n // 计算大气衰减(比尔定律)\n vec3 attenuation = exp(-tau);\n\n // 累积散射贡献(密度 × 衰减)\n sumR += hr * attenuation; // 瑞利散射\n sumM += hm * attenuation; // 米氏散射\n }\n\n march_pos += march_step; // 前进到下一个采样点\n }\n \n // 计算总体的大气透射率(用于确定天空亮度)\n float attenuation = length(exp(-((betaM * optical_depthM) + (betaR * optical_depthR)) * 3.8)); // 调整衰减强度系数\n\n // 返回最终的大气散射颜色\n return vec4(\n 25. * atmosphereIntensity * // 大气强度放大倍数(可调节参数)\n (sumR * phaseR * betaR + // 瑞利散射贡献:累积散射 × 相位函数 × 散射系数\n sumM * phaseM * betaM), // 米氏散射贡献:累积散射 × 相位函数 × 散射系数\n 1.0 - attenuation); // Alpha通道:1 - 透射率 = 大气不透明度\n }\n\n // 大气散射着色器主函数\n void main() {\n // 获取原始场景颜色\n vec4 rawColor = texture(colorTexture, v_textureCoordinates);\n\n // 获取并解包深度值\n float depth = czm_unpackDepth(texture(depthTexture, v_textureCoordinates));\n\n // 重建世界坐标\n vec4 positionEC = czm_windowToEyeCoordinates(gl_FragCoord.xy, depth); // 屏幕坐标转视图坐标\n vec4 positionWC = czm_inverseView * positionEC; // 视图坐标转世界坐标\n positionWC.xyz = positionWC.xyz / positionWC.w; // 齐次坐标归一化\n\n // 构造从相机到像素点的光线\n vec3 lVector = positionWC.xyz - czm_viewerPositionWC;\n ray_t ray;\n ray.origin = czm_viewerPositionWC; // 光线起点:相机位置\n ray.direction = normalize(lVector); // 光线方向:归一化的视线向量\n plane.distance = length(lVector); // 设置平面距离(场景深度)\n\n // 计算大气散射颜色\n vec4 atmosphereColor = get_incident_light(ray);\n\n // 优化的大气颜色混合:大气散射 + 场景颜色 × (1 - 大气不透明度)\n rawColor = atmosphereColor + rawColor * (1.0 - atmosphereColor.a * 0.95);\n\n // 改进的色调映射:HDR到LDR转换(反向指数曲线)\n rawColor = vec4(1.0 - exp(-2.1 * rawColor.rgb), rawColor.a);\n\n // 轻微的伽马校正:增强对比度\n rawColor.rgb = pow(rawColor.rgb, vec3(0.98));\n\n // 输出最终颜色\n out_FragColor = rawColor;\n }\n "}destroy(){this.stage&&(this.viewer.scene.postProcessStages.remove(this.stage),this.stage=void 0),this.preRenderListener&&this.viewer.scene.preRender.removeEventListener(this.preRenderListener),this.cameraListener&&this.cameraListener.destroy(),this.isEnabled=!1,console.log("[AtmosphereScatteringSystem] 已销毁")}}class v{viewer;config;isEnabled=!1;stage;preRenderListener;static DEFAULT_VISIBILITY_CONFIG={minHeight:0,maxHeight:3e3,transitionRange:500,enabled:!0};static DEFAULT_CONFIG={enabled:!0,fogByDistance:new o(10,0,5e4,1),fogColor:t.GRAY,visibility:v.DEFAULT_VISIBILITY_CONFIG};updateFogVisibility(){if(!this.stage||!this.stage.enabled||!this.config.visibility.enabled)return;const e=this.viewer.camera.positionCartographic.height;let t=1;const n=this.config.visibility;if(e<n.minHeight)t=1;else if(e>n.maxHeight)t=0;else if(e<n.minHeight+n.transitionRange){t=1-(e-n.minHeight)/n.transitionRange}else if(e>n.maxHeight-n.transitionRange){t=(n.maxHeight-e)/n.transitionRange}else t=.5;if(this.stage){const e=this.stage.uniforms.fogByDistance;e&&(this.stage.uniforms.fogByDistance=new o(e.x,e.y,e.z,e.w*t))}}constructor(e,t){this.viewer=e,this.config={...v.DEFAULT_CONFIG,...t,visibility:{...v.DEFAULT_VISIBILITY_CONFIG,...t?.visibility}}}initialize(){this.stage=new i({fragmentShader:this.getShader(),uniforms:{fogByDistance:this.config.fogByDistance,fogColor:this.config.fogColor}}),this.viewer.scene.postProcessStages.add(this.stage),this.preRenderListener=()=>{this.stage&&this.stage.enabled&&this.updateFogVisibility()},this.viewer.scene.preRender.addEventListener(this.preRenderListener),console.log("距离雾效系统已初始化")}enable(){this.isEnabled?console.log("[Render: DistanceFogSystem] 距离雾已启用"):(this.stage?this.stage.enabled=!0:this.initialize(),this.isEnabled=!0,console.log("[Render: DistanceFogSystem] 距离雾系统已启用"),this.viewer.scene.requestRender())}disable(){this.isEnabled?(this.stage&&(this.stage.enabled=!1),this.isEnabled=!1,console.log("[Render: DistanceFogSystem] 距离雾系统已禁用"),this.viewer.scene.requestRender()):console.log("[Render: DistanceFogSystem] 距离雾已禁用")}update(e){this.config={...this.config,...e,visibility:e.visibility?{...this.config.visibility,...e.visibility}:this.config.visibility},this.stage&&Object.entries(e).forEach(([e,t])=>{void 0!==t&&(this.stage.uniforms[e]=t)}),console.log("[Render: DistanceFogSystem] 配置已更新"),this.viewer.scene.requestRender()}getStatus(){return this.isEnabled}getConfig(){return{...this.config}}saveConfig(){return this.getConfig()}getShader(){return"\n // 输入纹理和参数\n uniform sampler2D colorTexture; // 场景颜色纹理\n uniform sampler2D depthTexture; // 场景深度纹理\n uniform vec4 fogByDistance; // 雾效果距离参数 (起始距离, 起始值, 结束距离, 结束值)\n uniform vec4 fogColor; // 雾的颜色\n in vec2 v_textureCoordinates; // 纹理坐标\n\n // 从深度纹理获取当前像素点到相机的距离\n float getDistance(sampler2D depthTexture, vec2 texCoords)\n {\n float depth = czm_unpackDepth(texture(depthTexture, texCoords));\n if (depth == 0.0) {\n return czm_infinity; // 天空盒返回无限远\n }\n vec4 eyeCoordinate = czm_windowToEyeCoordinates(gl_FragCoord.xy, depth);\n return -eyeCoordinate.z / eyeCoordinate.w; // 相机到像素的距离\n }\n\n // 根据距离进行插值计算雾的强度\n float interpolateByDistance(vec4 nearFarScalar, float distance)\n {\n float startDistance = nearFarScalar.x; // 雾开始的距离\n float startValue = nearFarScalar.y; // 起始雾密度\n float endDistance = nearFarScalar.z; // 雾结束的距离\n float endValue = nearFarScalar.w; // 结束雾密度\n // 计算插值参数t\n float t = clamp((distance - startDistance) / (endDistance - startDistance), 0.0, 1.0);\n // 线性插值\n return mix(startValue, endValue, t);\n }\n\n // Alpha混合函数,用于混合雾颜色和场景颜色\n vec4 alphaBlend(vec4 sourceColor, vec4 destinationColor)\n {\n return sourceColor * vec4(sourceColor.aaa, 1.0) + destinationColor * (1.0 - sourceColor.a);\n }\n\n // 主着色器函数\n void main(void)\n {\n // 获取当前像素点到相机的距离\n float distance = getDistance(depthTexture, v_textureCoordinates);\n // 获取场景原有的颜色\n vec4 sceneColor = texture(colorTexture, v_textureCoordinates);\n // 根据距离计算雾的混合程度\n float blendAmount = interpolateByDistance(fogByDistance, distance);\n // 计算最终的雾颜色(包含透明度)\n vec4 finalFogColor = vec4(fogColor.rgb, fogColor.a * blendAmount);\n // 将雾颜色与场景颜色进行Alpha混合\n out_FragColor = alphaBlend(finalFogColor, sceneColor);\n }\n "}destroy(){this.stage&&(this.viewer.scene.postProcessStages.remove(this.stage),this.stage=void 0),this.preRenderListener&&this.viewer.scene.preRender.removeEventListener(this.preRenderListener),this.isEnabled=!1,console.log("[Render: DistanceFogSystem] 已销毁")}}class f{viewer;config;isEnabled=!1;stage;preRenderListener;static DEFAULT_VISIBILITY_CONFIG={minHeight:0,maxHeight:3e3,transitionRange:500,enabled:!0};static DEFAULT_CONFIG={enabled:!0,fogByHeight:new o(0,0,1138,0),fogColor:t.WHITE,visibility:f.DEFAULT_VISIBILITY_CONFIG};updateFogVisibility(){if(!this.stage||!this.stage.enabled||!this.config.visibility.enabled)return;const e=this.viewer.camera.positionCartographic.height;let t=1;const n=this.config.visibility;if(e<n.minHeight)t=1;else if(e>n.maxHeight)t=0;else if(e<n.minHeight+n.transitionRange){t=1-(e-n.minHeight)/n.transitionRange}else if(e>n.maxHeight-n.transitionRange){t=(n.maxHeight-e)/n.transitionRange}else t=.5;if(this.stage){const e=this.stage.uniforms.fogByHeight;e&&(this.stage.uniforms.fogByHeight=new o(e.x,e.y,e.z,e.w*t))}}constructor(e,t){this.viewer=e,this.config={...f.DEFAULT_CONFIG,...t,visibility:{...f.DEFAULT_VISIBILITY_CONFIG,...t?.visibility}}}initialize(){this.stage=new i({fragmentShader:this.getShader(),uniforms:{fogByHeight:this.config.fogByHeight,fogColor:this.config.fogColor,earthRadius:t=>e.magnitude(this.viewer.camera.positionWC)-this.viewer.camera.positionCartographic.height}}),this.viewer.scene.postProcessStages.add(this.stage),this.preRenderListener=()=>{this.stage&&this.stage.enabled&&this.updateFogVisibility()},this.viewer.scene.preRender.addEventListener(this.preRenderListener),console.log("高度雾效系统已初始化")}enable(){this.isEnabled?console.log("[Render: HeightFogSystem] 高度雾已启用"):(this.stage?this.stage.enabled=!0:this.initialize(),this.isEnabled=!0,console.log("[Render: HeightFogSystem] 高度雾系统已启用"),this.viewer.scene.requestRender())}disable(){this.isEnabled?(this.stage&&(this.stage.enabled=!1),this.isEnabled=!1,console.log("[Render: HeightFogSystem] 高度雾系统已禁用"),this.viewer.scene.requestRender()):console.log("[Render: HeightFogSystem] 高度雾已禁用")}update(e){this.config={...this.config,...e,visibility:e.visibility?{...this.config.visibility,...e.visibility}:this.config.visibility},this.stage&&Object.entries(e).forEach(([e,t])=>{void 0!==t&&(this.stage.uniforms[e]=t)}),console.log("[Render: HeightFogSystem] 配置已更新"),this.viewer.scene.requestRender()}getStatus(){return this.isEnabled}getConfig(){return{...this.config}}saveConfig(){return this.getConfig()}getShader(){return"\n // 输入纹理和参数\n uniform sampler2D colorTexture; // 场景颜色纹理\n uniform sampler2D depthTexture; // 场景深度纹理\n uniform vec4 fogByHeight; // 雾效果高度参数 (起始距离, 起始值, 结束距离, 结束值)\n uniform vec4 fogColor; // 雾的颜色\n in vec2 v_textureCoordinates; // 纹理坐标\n uniform float earthRadius; // 地球半径\n\n // 根据深度纹理获取当前像素点的高度\n float getHeight(sampler2D depthTexture, vec2 texCoords)\n {\n // 从深度纹理中解包深度值\n float depth = czm_unpackDepth(texture(depthTexture, texCoords));\n if (depth == 0.0) {\n return czm_infinity; // 天空盒返回无限远\n }\n // 将屏幕坐标转换为视图坐标\n vec4 eyeCoordinate4 = czm_windowToEyeCoordinates(gl_FragCoord.xy, depth);\n vec3 eyeCoordinate3 = eyeCoordinate4.xyz/eyeCoordinate4.w;\n // 将视图坐标转换为世界坐标\n vec4 worldCoordinate4 = czm_inverseView * vec4(eyeCoordinate3,1.) ;\n vec3 worldCoordinate = worldCoordinate4.xyz / worldCoordinate4.w;\n // 计算当前点的海拔高度\n float altitude = length(worldCoordinate.xyz) - earthRadius;\n return altitude;\n }\n\n // 根据距离进行插值计算雾的强度\n float interpolateByDistance(vec4 nearFarScalar, float distance)\n {\n float startDistance = nearFarScalar.x; // 雾开始的距离\n float startValue = nearFarScalar.y; // 起始雾密度\n float endDistance = nearFarScalar.z; // 雾结束的距离\n float endValue = nearFarScalar.w; // 结束雾密度\n // 计算插值参数t\n float t = clamp((distance - startDistance) / (endDistance - startDistance), 0.0, 1.0);\n // 线性插值\n return mix(startValue, endValue, t);\n }\n\n // Alpha混合函数,用于混合雾颜色和场景颜色\n vec4 alphaBlend(vec4 sourceColor, vec4 destinationColor)\n {\n return sourceColor * vec4(sourceColor.aaa, 1.0) + destinationColor * (1.0 - sourceColor.a);\n }\n\n // 主着色器函数\n void main(void)\n {\n // 获取当前像素点的高度\n float height = getHeight(depthTexture, v_textureCoordinates);\n // 获取场景原有的颜色\n vec4 sceneColor = texture(colorTexture, v_textureCoordinates);\n // 根据高度计算雾的混合程度\n float blendAmount = interpolateByDistance(fogByHeight, height);\n // 计算最终的雾颜色(包含透明度)\n vec4 finalFogColor = vec4(fogColor.rgb, fogColor.a * blendAmount);\n // 将雾颜色与场景颜色进行Alpha混合\n out_FragColor = alphaBlend(finalFogColor, sceneColor);\n }\n "}destroy(){this.stage&&(this.viewer.scene.postProcessStages.remove(this.stage),this.stage=void 0),this.preRenderListener&&this.viewer.scene.preRender.removeEventListener(this.preRenderListener),this.isEnabled=!1,console.log("[Render: HeightFogSystem] 已销毁")}}class b{viewer;config;isEnabled=!1;stages;static DEFAULT_CONFIG={enabled:!0,brightness:1.02,contrast:1,saturation:1.75,gamma:.5,temperature:6730,tint:-.39,shadowColor:new e(0,0,0),shadowBlend:.7};constructor(e,t){this.viewer=e,this.config={...b.DEFAULT_CONFIG,...t}}createStages(){return{colorAdjustment:new i({name:"color_adjustment",fragmentShader:this.getColorAdjustmentShader(),uniforms:{brightness:this.config.brightness,contrast:this.config.contrast,saturation:this.config.saturation,gamma:this.config.gamma}}),shadowColor:new i({name:"shadow_color",fragmentShader:this.getShadowColorShader(),uniforms:{shadowColor:this.config.shadowColor,shadowBlend:this.config.shadowBlend}}),whiteBalance:new i({name:"white_balance",fragmentShader:this.getWhiteBalanceShader(),uniforms:{temperature:this.config.temperature,tint:this.config.tint}})}}initialize(){this.viewer.scene.postProcessStages.add(this.stages.colorAdjustment),this.viewer.scene.postProcessStages.add(this.stages.shadowColor),this.viewer.scene.postProcessStages.add(this.stages.whiteBalance),this.viewer.scene.postProcessStages.fxaa.enabled=!0}enable(){this.isEnabled?console.log("[Render: PostProcessingSystem] 后处理已启用"):(this.stages||(this.stages=this.createStages(),this.initialize()),this.stages.colorAdjustment.enabled=!0,this.stages.shadowColor.enabled=!0,this.stages.whiteBalance.enabled=!0,this.viewer.scene.highDynamicRange=!0,this.viewer.scene.postProcessStages.fxaa.enabled=!0,this.isEnabled=!0,console.log("[Render: PostProcessingSystem] 后处理系统已启用"),this.viewer.scene.requestRender())}disable(){this.isEnabled?(this.stages.colorAdjustment.enabled=!1,this.stages.shadowColor.enabled=!1,this.stages.whiteBalance.enabled=!1,this.isEnabled=!1,console.log("[Render: PostProcessingSystem] 后处理系统已禁用"),this.viewer.scene.requestRender()):console.log("[Render: PostProcessingSystem] 后处理已禁用")}update(e){this.stages&&(this.config={...this.config,...e},Object.entries(e).forEach(([e,t])=>{if(void 0!==t)switch(e){case"brightness":case"contrast":case"saturation":case"gamma":this.stages.colorAdjustment&&(this.stages.colorAdjustment.uniforms[e]=t);break;case"shadowColor":case"shadowBlend":this.stages.shadowColor&&(this.stages.shadowColor.uniforms[e]=t);break;case"temperature":case"tint":this.stages.whiteBalance&&(this.stages.whiteBalance.uniforms[e]=t)}}),console.log("[Render: PostProcessingSystem] 配置已更新"),this.viewer.scene.requestRender())}getStatus(){return this.isEnabled}getConfig(){return{...this.config}}saveConfig(){return this.getConfig()}getColorAdjustmentShader(){return"\n uniform sampler2D colorTexture;\n uniform float brightness;\n uniform float contrast;\n uniform float saturation;\n uniform float gamma;\n in vec2 v_textureCoordinates;\n\n void main(void) {\n vec4 color = texture(colorTexture, v_textureCoordinates);\n\n // 亮度调整\n color.rgb *= brightness;\n\n // 对比度调整\n color.rgb = (color.rgb - 0.5) * contrast + 0.5;\n\n // 饱和度调整\n float luminance = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));\n vec3 gray = vec3(luminance);\n color.rgb = mix(gray, color.rgb, saturation);\n\n // 伽马校正\n color.rgb = pow(color.rgb, vec3(1.0 / gamma));\n\n out_FragColor = color;\n }\n "}getShadowColorShader(){return"\n uniform sampler2D colorTexture;\n uniform vec3 shadowColor;\n uniform float shadowBlend;\n in vec2 v_textureCoordinates;\n\n void main(void) {\n vec4 color = texture(colorTexture, v_textureCoordinates);\n\n // 计算像素亮度\n float luminance = dot(color.rgb, vec3(0.2126, 0.7152, 0.0722));\n\n // 对暗部区域应用阴影颜色\n if (luminance < 0.5) {\n float shadowFactor = (0.5 - luminance) * 2.0;\n color.rgb = mix(color.rgb, shadowColor, shadowFactor * shadowBlend);\n }\n\n out_FragColor = color;\n }\n "}getWhiteBalanceShader(){return"\n uniform sampler2D colorTexture;\n uniform float temperature;\n uniform float tint;\n in vec2 v_textureCoordinates;\n\n // 色温调整函数\n vec3 adjustTemperature(vec3 color, float temp) {\n // 将色温转换为更自然的调整\n temp = (temp - 6500.0) / 1500.0;\n\n vec3 result = color;\n\n if (temp > 0.0) {\n // 暖色调调整 (增加红色,减少蓝色)\n result.r += temp * 0.15;\n result.b -= temp * 0.15;\n } else {\n // 冷色调调整 (增加蓝色,减少红色)\n temp = abs(temp);\n result.b += temp * 0.15;\n result.r -= temp * 0.1;\n }\n\n return clamp(result, 0.0, 1.0);\n }\n\n // 色调调整函数\n vec3 adjustTint(vec3 color, float tint) {\n vec3 result = color;\n\n if (tint > 0.0) {\n // 增加绿色色调\n result.g += tint * 0.1;\n } else {\n // 增加洋红色色调\n tint = abs(tint);\n result.r += tint * 0.05;\n result.b += tint * 0.05;\n }\n\n return clamp(result, 0.0, 1.0);\n }\n\n void main(void) {\n vec4 color = texture(colorTexture, v_textureCoordinates);\n\n // 应用色温调整\n color.rgb = adjustTemperature(color.rgb, temperature);\n\n // 应用色调调整\n color.rgb = adjustTint(color.rgb, tint);\n\n out_FragColor = color;\n }\n "}destroy(){this.disable(),this.stages.colorAdjustment&&this.viewer.scene.postProcessStages.remove(this.stages.colorAdjustment),this.stages.shadowColor&&this.viewer.scene.postProcessStages.remove(this.stages.shadowColor),this.stages.whiteBalance&&this.viewer.scene.postProcessStages.remove(this.stages.whiteBalance),console.log("[Render: PostProcessingSystem] 已销毁")}}class y{static serializeColor(e){return e?{red:e.red,green:e.green,blue:e.blue,alpha:e.alpha,css:e.toCssColorString(),rgb:`rgb(${Math.round(255*e.red)}, ${Math.round(255*e.green)}, ${Math.round(255*e.blue)})`,rgba:`rgba(${Math.round(255*e.red)}, ${Math.round(255*e.green)}, ${Math.round(255*e.blue)}, ${e.alpha})`}:null}static deserializeColor(e){return e?e.css?t.fromCssColorString(e.css):void 0!==e.red&&void 0!==e.green&&void 0!==e.blue?new t(e.red,e.green,e.blue,void 0!==e.alpha?e.alpha:1):e.rgb?t.fromCssColorString(e.rgb):e.rgba?t.fromCssColorString(e.rgba):new t(1,1,1,1):new t(1,1,1,1)}static cleanConfig(e){if(null==e)return e;if(Array.isArray(e))return e.map(e=>this.cleanConfig(e));if("object"==typeof e){const t={};for(const[n,i]of Object.entries(e))void 0!==i&&(t[n]=this.cleanConfig(i));return t}return e}static deepMerge(e,t){if(!t)return e;for(const n in t)t[n]&&"object"==typeof t[n]&&!Array.isArray(t[n])?(e[n]||(e[n]={}),this.deepMerge(e[n],t[n])):Array.isArray(t[n])?e[n]=[...e[n]||[],...t[n]]:e[n]=t[n];return e}}class C{viewer;config;lightingSystem;shadowSystem;volumetricCloudsSystem;atmosphereScatteringSystem;distanceFogSystem;heightFogSystem;postProcessingSystem;cameraListenerSystem;systems=new Map;constructor(e){if(!e||!e.viewer)throw new Error("[Render] 缺少初始化参数 config || config.viewer ");e.directionalLight||(e.directionalLight={enabled:!1}),e.shadow||(e.shadow={enabled:!1}),e.volumetricClouds||(e.volumetricClouds={enabled:!1}),e.atmosphereScattering||(e.atmosphereScattering={enabled:!1}),e.distanceFog||(e.distanceFog={enabled:!1}),e.heightFog||(e.heightFog={enabled:!1}),e.postProcessing||(e.postProcessing={enabled:!1}),this.viewer=e.viewer,this.config=e;const t=new Date;t.setHours(12,0,0,0);const n=a.fromDate(t);this.viewer.clock.currentTime=n,this.viewer.clock.shouldAnimate=!1,this.cameraListenerSystem=new p(this.viewer,e.cameraListener),this.cameraListenerSystem.initialize(),this.lightingSystem=new c(this.viewer,e.directionalLight),this.shadowSystem=new g(this.viewer,e.shadow),this.volumetricCloudsSystem=new h(this.viewer,e.volumetricClouds),this.atmosphereScatteringSystem=new m(this.viewer,e.atmosphereScattering),this.distanceFogSystem=new v(this.viewer,e.distanceFog),this.heightFogSystem=new f(this.viewer,e.heightFog),this.postProcessingSystem=new b(this.viewer,e.postProcessing),this.registerSystems(),e.postProcessing?.enabled&&this.postProcessingSystem.enable(),e.directionalLight?.enabled&&this.lightingSystem.enable(),e.shadow?.enabled&&this.shadowSystem.enable(),e.volumetricClouds?.enabled&&this.volumetricCloudsSystem.enable(),e.atmosphereScattering?.enabled&&this.atmosphereScatteringSystem.enable(),e.distanceFog?.enabled&&this.distanceFogSystem.enable(),e.heightFog?.enabled&&this.heightFogSystem.enable(),this.initialize()}registerSystems(){this.systems.set("lighting",this.lightingSystem),this.systems.set("shadow",this.shadowSystem),this.systems.set("volumetricClouds",this.volumetricCloudsSystem),this.systems.set("atmosphereScattering",this.atmosphereScatteringSystem),this.systems.set("distanceFog",this.distanceFogSystem),this.systems.set("heightFog",this.heightFogSystem),this.systems.set("postProcessing",this.postProcessingSystem)}initialize(){console.log("[Render] 渲染引擎初始化..."),this.config.directionalLight?.enabled&&this.lightingSystem.enable(),this.config.shadow?.enabled&&this.shadowSystem.enable(),this.config.volumetricClouds?.enabled&&this.volumetricCloudsSystem.enable(),this.config.atmosphereScattering?.enabled&&this.atmosphereScatteringSystem.enable(),this.config.distanceFog?.enabled&&this.distanceFogSystem.enable(),this.config.heightFog?.enabled&&this.heightFogSystem.enable(),this.config.postProcessing?.enabled&&this.postProcessingSystem.enable(),this.config.enableAll&&this.enableAllSystems(),console.log("[Render] 渲染引擎初始化完成")}enableAllSystems(){this.systems.forEach((e,t)=>{e.enable()})}disableAllSystems(){this.systems.forEach((e,t)=>{e.disable()})}enableSystem(e){const t=this.systems.get(e);t?t.enable():console.warn(`[Render] 未知的系统: ${e}`)}disableSystem(e){const t=this.systems.get(e);t?t.disable():console.warn(`[Render] 未知的系统: ${e}`)}getSystemStatus(e){const t=this.systems.get(e);return t?.getStatus()}updateSystemConfig(e,t){const n=this.systems.get(e);n?n.update(t):console.warn(`[Render] 未知的系统: ${e}`)}getSystemConfig(e){const t=this.systems.get(e);return t&&"getConfig"in t?t.getConfig():(console.warn(`[Render] 未知的系统或系统不支持getConfig: ${e}`),null)}getAllSystemsStatus(){const e={};return this.systems.forEach((t,n)=>{e[n]=t.getStatus()}),e}toggleSystem(e,t){void 0===t&&(t=!this.getSystemStatus(e)),t?this.enableSystem(e):this.disableSystem(e)}getConfig(){console.log("[Render] 保存配置为 JSON...");const e={version:"1.0.0",metadata:{created:(new Date).toISOString(),application:"Render Engine",author:"User"},settings:{enableAll:this.config.enableAll||!1},systems:{}};return this.systems.forEach((t,n)=>{if(t.saveConfig)try{const i=t.saveConfig();e.systems[n]=y.cleanConfig(i)}catch(e){console.warn(`[Render] 保存系统 ${n} 配置失败:`,e)}else if(t.getConfig){const i=t.getConfig();e.systems[n]=y.cleanConfig(i)}}),e}saveConfig(e="render-config.json"){console.log("[Render] 保存配置为 JSON...");const t={version:"1.0.0",metadata:{created:(new Date).toISOString(),application:"Render Engine",author:"User"},settings:{enableAll:this.config.enableAll||!1},systems:{}};this.systems.forEach((e,n)=>{if(e.saveConfig)try{const i=e.saveConfig();t.systems[n]=y.cleanConfig(i)}catch(e){console.warn(`[Render] 保存系统 ${n} 配置失败:`,e)}else if(e.getConfig){const i=e.getConfig();t.systems[n]=y.cleanConfig(i)}});const n=JSON.stringify(t,null,2),i=new Blob([n],{type:"application/json"}),s=URL.createObjectURL(i),o=document.createElement("a");return o.href=s,o.download=e,document.body.appendChild(o),o.click(),setTimeout(()=>{document.body.removeChild(o),URL.revokeObjectURL(s)},100),t}async readConfig(e){return new Promise((t,n)=>{const i=new FileReader;i.onload=e=>{try{const n=e.target?.result,i=this.readConfigContent(n);t(i)}catch(e){n(e)}},i.onerror=e=>{n(new Error("[Render] 读取文件失败: "+e))},i.readAsText(e)})}async importConfig(e){let t;if("string"==typeof e)try{t=await this.readConfigContent(e)}catch(e){throw new Error("[Render] 导入配置失败: "+e)}else t=this.readConfigContent(e);this.applyConfig(t)}applyConfig(e){console.log("[Render] 应用配置..."),void 0!==e.enableAll&&(this.config.enableAll=e.enableAll),e.directionalLight&&this.updateSystemConfig("lighting",e.directionalLight),e.shadow&&this.updateSystemConfig("shadow",e.shadow),e.volumetricClouds&&this.updateSystemConfig("volumetricClouds",e.volumetricClouds),e.atmosphereScattering&&this.updateSystemConfig("atmosphereScattering",e.atmosphereScattering),e.distanceFog&&this.updateSystemConfig("distanceFog",e.distanceFog),e.heightFog&&this.updateSystemConfig("heightFog",e.heightFog),e.postProcessing&&this.updateSystemConfig("postProcessing",e.postProcessing),e.enableAll&&this.enableAllSystems(),console.log("[Render] 配置应用完成")}readConfigContent(e){let t;if(console.log("[Render] 读取 JSON 配置..."),"string"==typeof e)try{t=JSON.parse(e)}catch(e){throw new Error("[Render] JSON 解析错误: "+e)}else t=e;t.version&&"1.0.0"!==t.version&&console.warn(`[Render] 配置版本不匹配: 当前版本 1.0.0, 配置版本 ${t.version}`);const n={enableAll:t.settings?.enableAll||!1};if(t.systems){if(t.systems.lighting&&(n.directionalLight=this.parseSystemConfig(t.systems.lighting,"lighting")),t.systems.shadow&&(n.shadow=this.parseSystemConfig(t.systems.shadow,"shadow")),t.systems.volumetricClouds&&(n.volumetricClouds=this.parseSystemConfig(t.systems.volumetricClouds,"volumetricClouds")),t.systems.atmosphereScattering){const e=t.systems.atmosphereScattering;e.bColor&&(e.bColor=y.deserializeColor(e.bColor)),n.atmosphereScattering=e}t.systems.distanceFog&&(n.distanceFog=this.parseSystemConfig(t.systems.distanceFog,"distanceFog")),t.systems.heightFog&&(n.heightFog=this.parseSystemConfig(t.systems.heightFog,"heightFog")),t.systems.postProcessing&&(n.postProcessing=this.parseSystemConfig(t.systems.postProcessing,"postProcessing"))}return console.log("[Render] 配置读取完成",n),n}parseSystemConfig(t,n){if(!t)return null;const i={...t};switch(t.color&&(i.color=y.deserializeColor(t.color)),t.lightColor&&(i.lightColor=y.deserializeColor(t.lightColor)),t.fogColor&&(i.fogColor=y.deserializeColor(t.fogColor)),n){case"lighting":t.direction&&void 0!==t.direction.x&&void 0!==t.direction.y&&void 0!==t.direction.z&&(i.direction=new e(t.direction.x,t.direction.y,t.direction.z));break;case"shadow":t.shadowMapSize&&Array.isArray(t.shadowMapSize)&&(i.shadowMapSize=new l(t.shadowMapSize[0]||t.shadowMapSize.x||2048,t.shadowMapSize[1]||t.shadowMapSize.y||2048))}return i}destroy(){console.log("[Render] 销毁渲染引擎..."),this.systems.forEach((e,t)=>{e.destroy()}),this.systems.clear(),this.cameraListenerSystem.destroy(),console.log("[Render] 渲染引擎已销毁")}getSystems(){return{lighting:this.lightingSystem,shadow:this.shadowSystem,volumetricClouds:this.volumetricCloudsSystem,atmosphereScattering:this.atmosphereScatteringSystem,distanceFog:this.distanceFogSystem,heightFog:this.heightFogSystem,postProcessing:this.postProcessingSystem,cameraListener:this.cameraListenerSystem}}}class w{viewer;render;renderConfig;constructor(e,t,n){this.viewer=e,this.render=t,this.renderConfig=n,this.addConfigUpdateListener()}createControlSection(e){const t=document.createElement("div");t.className="control-section";const n=document.createElement("div");return n.className="section-header",n.innerHTML=`<h3 class="section-title">${e}</h3>`,t.appendChild(n),t}updateControlsEnabled(e,t,n=null){t?.forEach(t=>{const n=document.getElementById(t);n&&(n.disabled=!e)}),n?.forEach(t=>{t.disabled=!e})}updateSliderVisual(e){if(!e)return;const t=parseFloat(e.value),n=parseFloat(e.min)||0,i=parseFloat(e.max)||100,s=Math.min(100,Math.max(0,(t-n)/(i-n)*100));e.disabled?e.style.background=`linear-gradient(to right, #333 0%, #333 ${s}%, #444 ${s}%, #444 100%)`:e.style.background=`linear-gradient(to right, #4CAF50 0%, #4CAF50 ${s}%, #666 ${s}%, #666 100%)`}addConfigUpdateListener(){document.addEventListener("render-config-updated",e=>{this.onConfigUpdated(e.detail.config)})}onConfigUpdated(e){console.log(`${this.constructor.name}: 配置已更新`),this.refreshControls(e)}colorToHex(e){if("string"==typeof e)return e;if(e instanceof t)return e.toCssColorString();if(e&&void 0!==e.red&&void 0!==e.green&&void 0!==e.blue){const t=e=>{const t=Math.round(255*e).toString(16);return 1===t.length?"0"+t:t};return`#${t(e.red)}${t(e.green)}${t(e.blue)}`}return"#ffffff"}updateAllSliderVisuals(){document.querySelectorAll('input[type="range"]').forEach(e=>{this.updateSliderVisual(e)})}}class x extends w{container;cloudVisibilityParams={minHeight:0,maxHeight:7700,transitionRange:8500};disableSliders=["cloud-cover","cloud-base","cloud-top","cloud-thickness","wind-speed","cloud-light-intensity","cloud-min-height","cloud-max-height","cloud-transition-range"];constructor(e,t,n){super(e,t,n)}createPanel(e){const t=this.createControlSection("体积云系统");t.innerHTML='\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">启用体积云</label>\n <label class="toggle-switch">\n <input type="checkbox" id="clouds-enabled" class="toggle-checkbox">\n <span class="toggle-slider"></span>\n </label>\n </div>\n </div>\n \n <div class="control-group cloud-controls">\n <div class="control-row">\n <label class="control-label">云量</label>\n <span class="control-value" id="cloud-cover-value">0.39</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="cloud-cover" min="0" max="1" step="0.01" value="0.39" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group cloud-controls">\n <div class="control-row">\n <label class="control-label">云底高度 (m)</label>\n <span class="control-value" id="cloud-base-value">2000</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="cloud-base" min="0" max="10000" step="0" value="2000" class="slider">\n </div>\n </div>\n </div>\n\n <div class="control-group cloud-controls">\n <div class="control-row">\n <label class="control-label">云顶高度 (m)</label>\n <span class="control-value" id="cloud-top-value">6000</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="cloud-top" min="0" max="15000" step="0" value="6000" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group cloud-controls">\n <div class="control-row">\n <label class="control-label">云层厚度 (m)</label>\n <span class="control-value" id="cloud-thickness-value">4000</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="cloud-thickness" min="100" max="8000" step="100" value="4000" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group cloud-controls">\n <div class="control-row">\n <label class="control-label">风速</label>\n <span class="control-value" id="wind-speed-value">5</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="wind-speed" min="0" max="100" step="1" value="5" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group cloud-controls">\n <div class="control-row">\n <label class="control-label">云层光强</label>\n <span class="control-value" id="cloud-light-intensity-value">5.3</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="cloud-light-intensity" min="0.1" max="10" step="0.1" value="5.3" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group cloud-controls">\n <div class="control-row">\n <label class="control-label">云层最低高度 (m)</label>\n <span class="control-value" id="cloud-min-height-value">0</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="cloud-min-height" min="0" max="15000" step="100" value="0" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group cloud-controls">\n <div class="control-row">\n <label class="control-label">云层最高高度 (m)</label>\n <span class="control-value" id="cloud-max-height-value">7700</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="cloud-max-height" min="0" max="15000" step="100" value="7700" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group cloud-controls">\n <div class="control-row">\n <label class="control-label">云层过渡范围 (m)</label>\n <span class="control-value" id="cloud-transition-range-value">8500</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="cloud-transition-range" min="0" max="15000" step="100" value="8500" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group cloud-controls">\n <div class="control-row">\n <span class="control-label">云层预设</span>\n </div>\n <div class="preset-buttons">\n <button class="preset-btn" data-preset="cumulus">积云</button>\n <button class="preset-btn" data-preset="stratus">层云</button>\n <button class="preset-btn" data-preset="cirrus">卷云</button>\n <button class="preset-btn" data-preset="storm">风暴</button>\n <button class="preset-btn" data-preset="thin">薄云</button>\n <button class="preset-btn" data-preset="heavy">厚云</button>\n </div>\n </div>\n ',e.appendChild(t),this.container=t}initEventListeners(t){const n=document.getElementById("clouds-enabled"),i=document.getElementById("cloud-cover"),s=document.getElementById("cloud-base"),o=document.getElementById("cloud-top"),a=document.getElementById("cloud-thickness"),l=document.getElementById("wind-speed"),r=document.getElementById("cloud-light-intensity"),d=document.getElementById("cloud-min-height"),c=document.getElementById("cloud-max-height"),g=document.getElementById("cloud-transition-range"),h=this.container.querySelectorAll(".preset-btn[data-preset]");this.updateControlsEnabled(this.renderConfig.volumetricClouds.enabled,this.disableSliders,h),n.checked=this.renderConfig.volumetricClouds.enabled,n.addEventListener("change",()=>{n.checked?this.render.enableSystem("volumetricClouds"):this.render.disableSystem("volumetricClouds"),this.updateControlsEnabled(n.checked,this.disableSliders,h)}),i.addEventListener("input",()=>{document.getElementById("cloud-cover-value").textContent=i.value,this.render.updateSystemConfig("volumetricClouds",{cloudCover:parseFloat(i.value)}),this.updateSliderVisual(i)}),s.addEventListener("input",()=>{document.getElementById("cloud-base-value").textContent=s.value,this.render.updateSystemConfig("volumetricClouds",{cloudBase:parseFloat(s.value)}),this.updateSliderVisual(s)}),o.addEventListener("input",()=>{document.getElementById("cloud-top-value").textContent=o.value,this.render.updateSystemConfig("volumetricClouds",{cloudTop:parseFloat(o.value)}),this.updateSliderVisual(o)}),a.addEventListener("input",()=>{document.getElementById("cloud-thickness-value").textContent=a.value;const e=parseFloat(s.value)+parseFloat(a.value);document.getElementById("cloud-top-value").textContent=e.toString(),o.value=e.toString(),this.render.updateSystemConfig("volumetricClouds",{cloudTop:e}),this.updateSliderVisual(o),this.updateSliderVisual(a)}),l.addEventListener("input",()=>{document.getElementById("wind-speed-value").textContent=l.value,this.render.updateSystemConfig("volumetricClouds",{windVector:new e(parseFloat(l.value),0,0)}),this.updateSliderVisual(l)}),r.addEventListener("input",()=>{document.getElementById("cloud-light-intensity-value").textContent=r.value,this.render.updateSystemConfig("volumetricClouds",{cloudLightIntensity:parseFloat(r.value)}),this.updateSliderVisual(r)}),d.addEventListener("input",()=>{document.getElementById("cloud-min-height-value").textContent=d.value,this.cloudVisibilityParams.minHeight=parseFloat(d.value),this.updateSliderVisual(d)}),c.addEventListener("input",()=>{document.getElementById("cloud-max-height-value").textContent=c.value,this.cloudVisibilityParams.maxHeight=parseFloat(c.value),this.updateSliderVisual(c)}),g.addEventListener("input",()=>{document.getElementById("cloud-transition-range-value").textContent=g.value,this.cloudVisibilityParams.transitionRange=parseFloat(g.value),this.updateSliderVisual(g)});const u=this.container.querySelectorAll(".preset-btn[data-preset]");u.forEach(e=>{e.addEventListener("click",()=>{u.forEach(e=>e.classList.remove("active")),e.classList.add("active");const t=e.getAttribute("data-preset");this.applyCloudPreset(t),e.blur()})}),this.updateSliderVisual(i),this.updateSliderVisual(s),this.updateSliderVisual(a),this.updateSliderVisual(l),this.updateSliderVisual(r),this.updateSliderVisual(d),this.updateSliderVisual(c),this.updateSliderVisual(g)}applyCloudPreset(e){switch(e){case"cumulus":this.setCloudValues(.4,2e3,6e3,4e3,50,2.5),this.setCloudHeightValues(500,5e3,1e3);break;case"stratus":this.setCloudValues(.6,1500,3500,2e3,30,1.5),this.setCloudHeightValues(300,4e3,800);break;case"cirrus":this.setCloudValues(.3,6e3,1e4,4e3,200,1.2),this.setCloudHeightValues(4e3,12e3,1500);break;case"storm":this.setCloudValues(.8,1e3,5e3,4e3,300,.8),this.setCloudHeightValues(0,3e3,800);break;case"thin":this.setCloudValues(.2,2500,4500,2e3,80,1.8),this.setCloudHeightValues(1e3,6e3,1200);break;case"heavy":this.setCloudValues(.39,2e3,6e3,4e3,5,5.3),this.setCloudHeightValues(0,7700,8500)}}setCloudValues(t,n,i,s,o,a){const l=document.getElementById("cloud-cover"),r=document.getElementById("cloud-base"),d=document.getElementById("cloud-top"),c=document.getElementById("cloud-thickness"),g=document.getElementById("wind-speed"),h=document.getElementById("cloud-light-intensity");l.value=t.toString(),document.getElementById("cloud-cover-value").textContent=t.toString(),r.value=n.toString(),document.getElementById("cloud-base-value").textContent=n.toString(),d.value=i.toString(),document.getElementById("cloud-top-value").textContent=i.toString(),c.value=s.toString(),document.getElementById("cloud-thickness-value").textContent=s.toString(),g.value=o.toString(),document.getElementById("wind-speed-value").textContent=o.toString(),h.value=a.toString(),document.getElementById("cloud-light-intensity-value").textContent=a.toString(),this.render.updateSystemConfig("volumetricClouds",{cloudCover:t,cloudLightIntensity:a,windVector:new e(o,0,0)}),this.updateSliderVisual(l),this.updateSliderVisual(r),this.updateSliderVisual(d),this.updateSliderVisual(c),this.updateSliderVisual(g),this.updateSliderVisual(h)}setCloudHeightValues(e,t,n){const i=document.getElementById("cloud-min-height"),s=document.getElementById("cloud-max-height"),o=document.getElementById("cloud-transition-range");i.value=e.toString(),document.getElementById("cloud-min-height-value").textContent=e.toString(),s.value=t.toString(),document.getElementById("cloud-max-height-value").textContent=t.toString(),o.value=n.toString(),document.getElementById("cloud-transition-range-value").textContent=n.toString(),this.cloudVisibilityParams={minHeight:e,maxHeight:t,transitionRange:n},this.updateSliderVisual(i),this.updateSliderVisual(s),this.updateSliderVisual(o)}refreshControls(e){console.log("[VolumetricCloudControl] 刷新云层控制面板");let t={};e&&e.systems?e.systems.volumetricClouds&&(t=e.systems.volumetricClouds):t=e&&e.volumetricClouds?e.volumetricClouds:this.render.getSystemConfig("volumetricClouds")||{},this.updateControlsFromConfig(t)}updateControlsFromConfig(t){console.log("[VolumetricCloudControl] 从配置更新控件",t);const n=document.getElementById("clouds-enabled"),i=document.getElementById("cloud-cover"),s=document.getElementById("cloud-cover-value"),o=document.getElementById("cloud-base"),a=document.getElementById("cloud-base-value"),l=document.getElementById("cloud-top"),r=document.getElementById("cloud-top-value"),d=document.getElementById("cloud-thickness"),c=document.getElementById("cloud-thickness-value"),g=document.getElementById("wind-speed"),h=document.getElementById("wind-speed-value"),u=document.getElementById("cloud-light-intensity"),p=document.getElementById("cloud-light-intensity-value"),m=document.getElementById("cloud-min-height"),v=document.getElementById("cloud-min-height-value"),f=document.getElementById("cloud-max-height"),b=document.getElementById("cloud-max-height-value"),y=document.getElementById("cloud-transition-range"),C=document.getElementById("cloud-transition-range-value");if(this.container?.querySelectorAll(".preset-btn[data-preset]"),n){if(void 0!==t.enabled&&(n.checked=t.enabled),void 0!==t.cloudCover&&i&&(i.value=t.cloudCover.toString(),s&&(s.textContent=t.cloudCover.toFixed(2)),this.updateSliderVisual(i)),void 0!==t.cloudBase&&o&&(o.value=t.cloudBase.toString(),a&&(a.textContent=t.cloudBase.toFixed(0)),this.updateSliderVisual(o)),void 0!==t.cloudTop&&l&&(l.value=t.cloudTop.toString(),r&&(r.textContent=t.cloudTop.toFixed(0)),this.updateSliderVisual(l)),o&&l&&d){const e=parseFloat(o.value),t=parseFloat(l.value)-e;d.value=t.toString(),c&&(c.textContent=t.toFixed(0)),this.updateSliderVisual(d)}if(void 0!==t.windVector&&g&&(t.windVector instanceof e||t.windVector&&void 0!==t.windVector.x?(g.value=t.windVector.x.toString(),h&&(h.textContent=t.windVector.x.toFixed(2))):void 0!==t.windSpeed&&(g.value=t.windSpeed.toString(),h&&(h.textContent=t.windSpeed.toFixed(2))),this.updateSliderVisual(g)),void 0!==t.cloudLightIntensity&&u&&(u.value=t.cloudLightIntensity.toString(),p&&(p.textContent=t.cloudLightIntensity.toFixed(2)),this.updateSliderVisual(u)),t.detail,void 0!==t.minHeight&&m&&(this.cloudVisibilityParams.minHeight=t.minHeight,m.value=t.minHeight.toString(),v&&(v.textContent=t.minHeight.toFixed(0)),this.updateSliderVisual(m)),void 0!==t.maxHeight&&f&&(this.cloudVisibilityParams.maxHeight=t.maxHeight,f.value=t.maxHeight.toString(),b&&(b.textContent=t.maxHeight.toFixed(0)),this.updateSliderVisual(f)),void 0!==t.transitionRange&&y&&(this.cloudVisibilityParams.transitionRange=t.transitionRange,y.value=t.transitionRange.toString(),C&&(C.textContent=t.transitionRange.toFixed(0)),this.updateSliderVisual(y)),void 0!==t.cloudColor){const e=document.getElementById("cloud-color"),n=document.getElementById("cloud-color-value"),i=document.getElementById("cloud-color-preview");if(e){const s=this.colorToHex(t.cloudColor);e.value=s,n&&(n.textContent=s),i&&(i.style.backgroundColor=s)}}if(void 0!==t.cloudHighlightColor){const e=document.getElementById("cloud-highlight-color"),n=document.getElementById("cloud-highlight-color-value"),i=document.getElementById("cloud-highlight-color-preview");if(e){const s=this.colorToHex(t.cloudHighlightColor);e.value=s,n&&(n.textContent=s),i&&(i.style.backgroundColor=s)}}if(void 0!==t.cloudBaseColor){const e=document.getElementById("cloud-base-color"),n=document.getElementById("cloud-base-color-value"),i=document.getElementById("cloud-base-color-preview");if(e){const s=this.colorToHex(t.cloudBaseColor);e.value=s,n&&(n.textContent=s),i&&(i.style.backgroundColor=s)}}void 0!==t.preset&&this.updatePresetSelection(t.preset),this.updateControlsEnabledState(),this.viewer.scene.requestRender()}else console.log("[VolumetricCloudControl] 控件未初始化,跳过更新")}updateControlsEnabledState(){const e=document.getElementById("clouds-enabled"),t=this.container?.querySelectorAll(".preset-btn[data-preset]");if(e){const n=e.checked;this.updateControlsEnabled(n,this.disableSliders,t)}}updatePresetSelection(e){const t=this.container?.querySelectorAll(".preset-btn[data-preset]");t&&t.forEach(t=>{t.getAttribute("data-preset")===e?t.classList.add("active"):t.classList.remove("active")})}applyDefaultPresets(){const e=this.render.getSystemConfig("volumetricClouds")||{};this.render.updateSystemConfig("volumetricClouds",e),this.refreshControls(null)}reset(){this.applyDefaultPresets(),this.updateAllSliderVisuals()}getContainer(){return this.container}}class S extends w{container;disableSliders=["atmosphere-intensity"];constructor(e,t,n){super(e,t,n)}createPanel(e){const t=this.createControlSection("大气散射");t.innerHTML='\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">启用大气散射</label>\n <label class="toggle-switch">\n <input type="checkbox" id="atmosphere-enabled" class="toggle-checkbox">\n <span class="toggle-slider"></span>\n </label>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">大气强度</label>\n <span class="control-value" id="atmosphere-intensity-value">0.91</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="atmosphere-intensity" min="0" max="2" step="0.01" value="0.91" class="slider">\n </div>\n </div>\n </div>\n ',e.appendChild(t),this.container=t}initEventListeners(e){const t=document.getElementById("atmosphere-enabled"),n=document.getElementById("atmosphere-intensity");this.updateControlsEnabled(this.renderConfig.atmosphereScattering.enabled,this.disableSliders),t.checked=this.renderConfig.atmosphereScattering.enabled,t.addEventListener("change",()=>{t.checked?this.render.enableSystem("atmosphereScattering"):this.render.disableSystem("atmosphereScattering"),this.updateControlsEnabled(t.checked,this.disableSliders)}),n.addEventListener("input",()=>{document.getElementById("atmosphere-intensity-value").textContent=n.value,this.render.updateSystemConfig("atmosphereScattering",{atmosphereIntensity:parseFloat(n.value)}),this.updateSliderVisual(n)}),this.updateSliderVisual(n)}refreshControls(e){console.log("[AtmosphereControl] 刷新控制面板");let t={};t=e&&e.systems&&e.systems.atmosphereScattering?e.systems.atmosphereScattering:e&&e.atmosphereScattering?e.atmosphereScattering:this.render.getSystemConfig("atmosphereScattering")||{},this.updateControlsFromConfig(t)}updateControlsFromConfig(e){console.log("[AtmosphereControl] 从配置更新控件",e);const t=document.getElementById("atmosphere-enabled"),n=document.getElementById("atmosphere-intensity"),i=document.getElementById("atmosphere-intensity-value");if(t&&n){if(void 0!==e.enabled&&(t.checked=e.enabled,this.updateControlsEnabled(e.enabled,this.disableSliders)),void 0!==e.atmosphereIntensity){const t=e.atmosphereIntensity;n.value=t.toString(),i&&(i.textContent=t.toFixed(2)),this.updateSliderVisual(n)}this.updateAllSliderVisuals(),this.viewer.scene.requestRender()}else console.log("[AtmosphereControl] 控件未初始化,跳过更新")}applyDefaultPresets(){const e=this.render.getSystemConfig("atmosphereScattering")||{};this.render.updateSystemConfig("atmosphereScattering",e),this.refreshControls(null)}reset(){this.applyDefaultPresets(),this.updateAllSliderVisuals()}getContainer(){return this.container}}class E extends w{container;currentFogType="builtin";fogVisibilityParams={minHeight:0,maxHeight:3e3,transitionRange:500};disableSliders=["fog-density","fog-height","fog-color","fog-min-height","fog-max-height","fog-transition-range"];constructor(e,t,n){super(e,t,n)}createPanel(e){const t=this.createControlSection("雾效系统");t.innerHTML='\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">启用雾效</label>\n <label class="toggle-switch">\n <input type="checkbox" id="fog-enabled" class="toggle-checkbox">\n <span class="toggle-slider"></span>\n </label>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">雾效类型</label>\n </div>\n <div class="preset-buttons">\n <button class="preset-btn fog-type-btn active" data-fog-type="distance">内置距离雾</button>\n <button class="preset-btn fog-type-btn" data-fog-type="custom">距离雾</button>\n <button class="preset-btn fog-type-btn" data-fog-type="height">高度雾</button>\n <button class="preset-btn fog-type-btn" data-fog-type="both">两者结合</button>\n </div>\n </div>\n \n \x3c!-- 内置雾控制 --\x3e\n <div id="distance-fog-controls" class="distance-fog-controls fog-sub-controls">\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">雾密度</label>\n <span class="control-value" id="fog-density-value">0.0004</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="fog-density" min="0" max="0.01" step="0.0001" value="0.0004" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">雾高度 (m)</label>\n <span class="control-value" id="fog-height-value">0.3</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="fog-height" min="0.1" max="5.0" step="0.01" value="0.59" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">雾颜色</label>\n <input type="color" id="fog-color" value="#ffffff" style="margin-left: 10px;">\n <div class="color-picker" id="fog-color-preview" style="background-color: #ffffff;"></div>\n <span class="control-value" id="fog-color-value">#ffffff</span>\n </div>\n </div>\n </div>\n\n \x3c!-- 距离雾 --\x3e\n <div id="custom-distance-fog-controls" class="custom-distance-fog-controls fog-sub-controls" style="display: none;">\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">雾起始距离 (m)</label>\n <span class="control-value" id="distance-fog-start-value">10</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="distance-fog-start" min="0" max="1000" step="1" value="10" class="slider">\n </div>\n </div>\n </div>\n\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">雾起始强度</label>\n <span class="control-value" id="distance-fog-start-intensity-value">0.0</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="distance-fog-start-intensity" min="0" max="1" step="0.01" value="0.0" class="slider">\n </div>\n </div>\n </div>\n\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">雾结束距离 (m)</label>\n <span class="control-value" id="distance-fog-end-value">50000</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="distance-fog-end" min="0" max="500000" step="10" value="50000" class="slider">\n </div>\n </div>\n </div>\n\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">雾结束强度</label>\n <span class="control-value" id="distance-fog-end-intensity-value">1.0</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="distance-fog-end-intensity" min="0" max="1" step="0.01" value="1.0" class="slider">\n </div>\n </div>\n </div>\n\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">雾颜色</label>\n <input type="color" id="distance-fog-color" value="#888888" style="margin-left: 10px;">\n <div class="color-picker" id="distance-fog-color-preview" style="background-color: #888888;"></div>\n <span class="control-value" id="distance-fog-color-value">#888888</span>\n </div>\n </div>\n </div>\n \n \x3c!-- 高度雾控制 --\x3e\n <div id="height-fog-controls" class="height-fog-controls fog-sub-controls" style="display: none;">\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">雾起始高度 (m)</label>\n <span class="control-value" id="fog-start-height-value">0</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="fog-start-height" min="0" max="7000" step="0.001" value="0" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">雾结束高度 (m)</label>\n <span class="control-value" id="fog-end-height-value">1138</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="fog-end-height" min="0" max="7000" step="0.001" value="1138" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">高度雾密度</label>\n <span class="control-value" id="height-fog-density-value">0.008</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="height-fog-density" min="0" max="2" step="0.000001" value="0.008" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">高度雾颜色</label>\n <input type="color" id="height-fog-color" value="#ffffff" style="margin-left: 10px;">\n <div class="color-picker" id="height-fog-color-preview" style="background-color: #ffffff;"></div>\n <span class="control-value" id="height-fog-color-value">#ffffff</span>\n </div>\n </div>\n </div>\n \n \x3c!-- 高度控制面板(两者都可用) --\x3e\n <div class="control-group fog-height-controls">\n <div class="control-row">\n <label class="control-label">雾最低高度 (m)</label>\n <span class="control-value" id="fog-min-height-value">0</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="fog-min-height" min="0" max="5000" step="10" value="0" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group fog-height-controls">\n <div class="control-row">\n <label class="control-label">雾最高高度 (m)</label>\n <span class="control-value" id="fog-max-height-value">3000</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="fog-max-height" min="0" max="5000" step="10" value="3000" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group fog-height-controls">\n <div class="control-row">\n <label class="control-label">雾过渡范围 (m)</label>\n <span class="control-value" id="fog-transition-range-value">500</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="fog-transition-range" min="0" max="2000" step="10" value="500" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <span class="control-label">雾效预设</span>\n </div>\n <div class="preset-buttons">\n <button class="preset-btn" data-preset="light-fog">轻雾</button>\n <button class="preset-btn" data-preset="dense-fog">浓雾</button>\n <button class="preset-btn" data-preset="mountain-fog">山雾</button>\n <button class="preset-btn" data-preset="valley-fog">谷雾</button>\n <button class="preset-btn" data-preset="coastal-fog">海雾</button>\n <button class="preset-btn" data-preset="urban-fog">城市雾</button>\n </div>\n </div>\n ',e.appendChild(t),this.container=t}initEventListeners(e){const n=document.getElementById("fog-enabled"),i=document.getElementById("fog-density"),s=document.getElementById("fog-height"),a=document.getElementById("fog-color"),l=document.getElementById("distance-fog-start"),r=document.getElementById("distance-fog-start-intensity"),d=document.getElementById("distance-fog-end"),c=document.getElementById("distance-fog-end-intensity"),g=document.getElementById("distance-fog-color"),h=document.getElementById("fog-start-height"),u=document.getElementById("fog-end-height"),p=document.getElementById("height-fog-density"),m=document.getElementById("height-fog-color"),v=document.getElementById("fog-min-height"),f=document.getElementById("fog-max-height"),b=document.getElementById("fog-transition-range"),y=this.container.querySelectorAll(".preset-btn");this.updateControlsEnabled(this.renderConfig.distanceFog.enabled,this.disableSliders,y),n.checked=this.renderConfig.directionalLight.enabled,n.addEventListener("change",()=>{this.updateFogState(),this.updateControlsEnabled(n.checked,this.disableSliders,y)}),i.addEventListener("input",()=>{document.getElementById("fog-density-value").textContent=i.value,this.viewer.scene.fog.density=parseFloat(i.value),this.viewer.scene.requestRender(),this.updateSliderVisual(i)}),s.addEventListener("input",()=>{document.getElementById("fog-height-value").textContent=s.value,this.viewer.scene.fog.heightFalloff=parseFloat(s.value),this.viewer.scene.requestRender(),this.updateSliderVisual(s)}),a.addEventListener("input",()=>{const e=a.value;document.getElementById("fog-color-preview").style.backgroundColor=e,document.getElementById("fog-color-value").textContent=e;const n=e.replace("#",""),i=parseInt(n.substring(0,2),16)/255,s=parseInt(n.substring(2,4),16)/255,o=parseInt(n.substring(4,6),16)/255;console.log(i,s,o),this.viewer.scene.fog.color=new t(i,s,o,1),this.viewer.scene.requestRender()}),l.addEventListener("input",()=>{if(l.disabled)return;const e=parseFloat(l.value),t=parseFloat(d.value),n=parseFloat(r.value),i=parseFloat(c.value);document.getElementById("distance-fog-start-value").textContent=e.toString(),this.render.updateSystemConfig("distanceFog",{fogByDistance:new o(e,n,t,i)}),this.viewer.scene.requestRender(),this.updateSliderVisual(l)}),r.addEventListener("input",()=>{if(r.disabled)return;const e=parseFloat(l.value),t=parseFloat(d.value),n=parseFloat(r.value),i=parseFloat(c.value);document.getElementById("distance-fog-start-intensity-value").textContent=n.toFixed(2),this.render.updateSystemConfig("distanceFog",{fogByDistance:new o(e,n,t,i)}),this.viewer.scene.requestRender(),this.updateSliderVisual(r)}),d.addEventListener("input",()=>{if(d.disabled)return;const e=parseFloat(l.value),t=parseFloat(d.value),n=parseFloat(r.value),i=parseFloat(c.value);document.getElementById("distance-fog-end-value").textContent=t.toString(),this.render.updateSystemConfig("distanceFog",{fogByDistance:new o(e,n,t,i)}),this.viewer.scene.requestRender(),this.updateSliderVisual(d)}),c.addEventListener("input",()=>{if(c.disabled)return;const e=parseFloat(l.value),t=parseFloat(d.value),n=parseFloat(r.value),i=parseFloat(c.value);document.getElementById("distance-fog-end-intensity-value").textContent=i.toFixed(2),this.render.updateSystemConfig("distanceFog",{fogByDistance:new o(e,n,t,i)}),this.viewer.scene.requestRender(),this.updateSliderVisual(c)}),g.addEventListener("input",()=>{if(g.disabled)return;const e=g.value;document.getElementById("distance-fog-color-preview").style.backgroundColor=e,document.getElementById("distance-fog-color-value").textContent=e,this.render.updateSystemConfig("distanceFog",{fogColor:t.fromCssColorString(e)}),this.viewer.scene.requestRender()}),h.addEventListener("input",()=>{document.getElementById("fog-start-height-value").textContent=h.value,this.updateHeightFogUniforms(),this.viewer.scene.requestRender(),this.updateSliderVisual(h)}),u.addEventListener("input",()=>{document.getElementById("fog-end-height-value").textContent=u.value,this.updateHeightFogUniforms(),this.viewer.scene.requestRender(),this.updateSliderVisual(u)}),p.addEventListener("input",()=>{document.getElementById("height-fog-density-value").textContent=p.value,this.updateHeightFogUniforms(),this.viewer.scene.requestRender(),this.updateSliderVisual(p)}),m.addEventListener("input",()=>{const e=m.value;document.getElementById("height-fog-color-preview").style.backgroundColor=e,document.getElementById("height-fog-color-value").textContent=e;const n=e.replace("#",""),i=parseInt(n.substring(0,2),16)/255,s=parseInt(n.substring(2,4),16)/255,o=parseInt(n.substring(4,6),16)/255;this.render.updateSystemConfig("heightFog",{fogColor:new t(i,s,o,1)}),this.viewer.scene.requestRender()}),v.addEventListener("input",()=>{document.getElementById("fog-min-height-value").textContent=v.value,this.fogVisibilityParams.minHeight=parseFloat(v.value),this.updateSliderVisual(v)}),f.addEventListener("input",()=>{document.getElementById("fog-max-height-value").textContent=f.value,this.fogVisibilityParams.maxHeight=parseFloat(f.value),this.updateSliderVisual(f)}),b.addEventListener("input",()=>{document.getElementById("fog-transition-range-value").textContent=b.value,this.fogVisibilityParams.transitionRange=parseFloat(b.value),this.updateSliderVisual(b)});const C=document.querySelectorAll(".fog-type-btn");C.forEach(e=>{e.addEventListener("click",()=>{C.forEach(e=>e.classList.remove("active")),e.classList.add("active");const t=e.getAttribute("data-fog-type");this.switchFogType(t),e.blur()})});const w=document.querySelectorAll(".preset-btn[data-preset]");w.forEach(e=>{e.addEventListener("click",()=>{w.forEach(e=>e.classList.remove("active")),e.classList.add("active");const t=e.getAttribute("data-preset");this.applyFogPreset(t),e.blur()})}),this.updateSliderVisual(i),this.updateSliderVisual(s),this.updateSliderVisual(h),this.updateSliderVisual(u),this.updateSliderVisual(p),this.updateSliderVisual(v),this.updateSliderVisual(f),this.updateSliderVisual(b)}updateFogState(){const e=document.getElementById("fog-enabled").checked,t=document.querySelector(".fog-type-btn.active")?.getAttribute("data-fog-type");switch(t){case"distance":this.viewer.scene.fog.enabled=e,this.render.disableSystem("distanceFog"),this.render.disableSystem("heightFog");break;case"custom":this.viewer.scene.fog.enabled=!1,e?this.render.enableSystem("distanceFog"):this.render.disableSystem("distanceFog"),this.render.disableSystem("heightFog");break;case"height":e?this.render.enableSystem("heightFog"):this.render.disableSystem("heightFog"),this.viewer.scene.fog.enabled=!1,this.render.disableSystem("distanceFog");break;case"both":this.viewer.scene.fog.enabled=e,e?this.render.enableSystem("heightFog"):this.render.disableSystem("heightFog"),this.render.disableSystem("distanceFog")}this.viewer.scene.requestRender()}updateHeightFogUniforms(){const e=parseFloat(document.getElementById("fog-start-height").value),t=parseFloat(document.getElementById("fog-end-height").value),n=parseFloat(document.getElementById("height-fog-density").value);this.render.updateSystemConfig("heightFog",{fogByHeight:new o(e,n,t,0)})}switchFogType(e){const t=document.getElementById("fog-enabled").checked,n=document.getElementById("distance-fog-controls"),i=document.getElementById("custom-distance-fog-controls"),s=document.getElementById("height-fog-controls"),o=document.querySelectorAll(".fog-height-controls");switch(e){case"distance":n&&(n.style.display="block"),i&&(i.style.display="none"),s&&(s.style.display="none"),o.forEach(e=>{e.style.display="block"}),this.viewer.scene.fog.enabled=t,this.render.disableSystem("heightFog"),this.render.disableSystem("distanceFog");break;case"custom":i&&(i.style.display="block"),n&&(n.style.display="none"),s&&(s.style.display="none"),o.forEach(e=>{e.style.display="block"}),this.viewer.scene.fog.enabled=!1,this.render.disableSystem("heightFog"),t?this.render.enableSystem("distanceFog"):this.render.disableSystem("distanceFog");break;case"height":s&&(s.style.display="block"),i&&(i.style.display="none"),n&&(n.style.display="none"),o.forEach(e=>{e.style.display="block"}),this.viewer.scene.fog.enabled=!1,t?this.render.enableSystem("heightFog"):this.render.disableSystem("heightFog"),this.render.disableSystem("distanceFog");break;case"both":n&&(n.style.display="block"),s&&(s.style.display="block"),i&&(i.style.display="none"),o.forEach(e=>{e.style.display="block"}),this.viewer.scene.fog.enabled=t,t?this.render.enableSystem("heightFog"):this.render.disableSystem("heightFog"),this.render.disableSystem("distanceFog")}this.viewer.scene.requestRender()}applyFogPreset(e){switch(e){case"light-fog":this.setFogValues(5e-4,.9,"#f0f8ff"),this.setFogHeightValues(0,1500,300);break;case"dense-fog":this.setFogValues(.003,.1,"#dcdcdc"),this.setFogHeightValues(0,1e3,200);break;case"mountain-fog":this.setHeightFogValues(2447.154,5150.407,.9105,"#FFFFFF"),this.setFogHeightValues(2e3,4e3,500);break;case"valley-fog":this.setHeightFogValues(500,1500,.5,"#f5f5dc"),this.setFogHeightValues(0,2e3,300);break;case"coastal-fog":this.setFogValues(.001,.6,"#b0e0e6"),this.setFogHeightValues(0,1e3,200);break;case"urban-fog":this.setFogValues(4e-4,.7,"#FFFFFF"),this.setHeightFogValues(0,1138,.008,"#FFFFFF"),this.setFogHeightValues(0,3e3,500)}this.viewer.scene.requestRender()}setFogValues(e,n,i){const s=document.getElementById("fog-density"),o=document.getElementById("fog-height"),a=document.getElementById("fog-color");s.value=e.toString(),document.getElementById("fog-density-value").textContent=e.toString(),o.value=n.toString(),document.getElementById("fog-height-value").textContent=n.toString(),a.value=i,document.getElementById("fog-color-preview").style.backgroundColor=i,document.getElementById("fog-color-value").textContent=i,this.viewer.scene.fog.density=e,this.viewer.scene.fog.minimumBrightness=n;const l=i.replace("#",""),r=parseInt(l.substring(0,2),16)/255,d=parseInt(l.substring(2,4),16)/255,c=parseInt(l.substring(4,6),16)/255;this.viewer.scene.fog.color=new t(r,d,c,1),this.updateSliderVisual(s),this.updateSliderVisual(o)}setHeightFogValues(e,n,i,s){const a=document.getElementById("fog-start-height"),l=document.getElementById("fog-end-height"),r=document.getElementById("height-fog-density"),d=document.getElementById("height-fog-color");a.value=e.toString(),document.getElementById("fog-start-height-value").textContent=e.toString(),l.value=n.toString(),document.getElementById("fog-end-height-value").textContent=n.toString(),r.value=i.toString(),document.getElementById("height-fog-density-value").textContent=i.toString(),d.value=s,document.getElementById("height-fog-color-preview").style.backgroundColor=s,document.getElementById("height-fog-color-value").textContent=s,this.render.updateSystemConfig("heightFog",{fogByHeight:new o(e,i,n,0)});const c=s.replace("#",""),g=parseInt(c.substring(0,2),16)/255,h=parseInt(c.substring(2,4),16)/255,u=parseInt(c.substring(4,6),16)/255;this.render.updateSystemConfig("heightFog",{fogColor:new t(g,h,u,1)}),this.updateSliderVisual(a),this.updateSliderVisual(l),this.updateSliderVisual(r)}setFogHeightValues(e,t,n){const i=document.getElementById("fog-min-height"),s=document.getElementById("fog-max-height"),o=document.getElementById("fog-transition-range");i.value=e.toString(),document.getElementById("fog-min-height-value").textContent=e.toString(),s.value=t.toString(),document.getElementById("fog-max-height-value").textContent=t.toString(),o.value=n.toString(),document.getElementById("fog-transition-range-value").textContent=n.toString(),this.fogVisibilityParams={minHeight:e,maxHeight:t,transitionRange:n},this.updateSliderVisual(i),this.updateSliderVisual(s),this.updateSliderVisual(o)}refreshControls(e){console.log("[FogControl] 刷新控制面板");let t={},n={};e&&e.systems?(e.systems.distanceFog&&(t=e.systems.distanceFog),e.systems.heightFog&&(n=e.systems.heightFog)):e?(e.distanceFog&&(t=e.distanceFog),e.heightFog&&(n=e.heightFog)):(t=this.render.getSystemConfig("distanceFog")||{},n=this.render.getSystemConfig("heightFog")||{}),this.updateBuiltinFogFromConfig(t,n),this.updateControlsFromConfig(t,n),this.updateAllSliderVisuals(),this.viewer.scene.requestRender()}updateBuiltinFogFromConfig(e,t){void 0!==e.enabled&&(this.viewer.scene.fog.enabled=e.enabled),void 0!==e.fogDensity&&(this.viewer.scene.fog.density=e.fogDensity),void 0!==e.fogHeight&&(this.viewer.scene.fog.heightFalloff=e.fogHeight),e.fogColor&&(this.viewer.scene.fog.color=this.parseColor(e.fogColor))}updateControlsFromConfig(e,t){console.log("[FogControl] 从配置更新控件",{distanceConfig:e,heightConfig:t});const n=document.getElementById("fog-enabled"),i=document.getElementById("fog-density"),s=document.getElementById("fog-density-value"),o=document.getElementById("fog-height"),a=document.getElementById("fog-height-value"),l=document.getElementById("fog-color"),r=document.getElementById("fog-color-value"),d=document.getElementById("fog-color-preview"),c=document.getElementById("distance-fog-start"),g=document.getElementById("distance-fog-start-value"),h=document.getElementById("distance-fog-start-intensity"),u=document.getElementById("distance-fog-start-intensity-value"),p=document.getElementById("distance-fog-end"),m=document.getElementById("distance-fog-end-value"),v=document.getElementById("distance-fog-end-intensity"),f=document.getElementById("distance-fog-end-intensity-value"),b=document.getElementById("distance-fog-color"),y=document.getElementById("distance-fog-color-value"),C=document.getElementById("distance-fog-color-preview"),w=document.getElementById("fog-start-height"),x=document.getElementById("fog-start-height-value"),S=document.getElementById("fog-end-height"),E=document.getElementById("fog-end-height-value"),I=document.getElementById("height-fog-density"),B=document.getElementById("height-fog-density-value"),F=document.getElementById("height-fog-color"),k=document.getElementById("height-fog-color-value"),L=document.getElementById("height-fog-color-preview"),R=document.getElementById("fog-min-height"),_=document.getElementById("fog-min-height-value"),V=document.getElementById("fog-max-height"),P=document.getElementById("fog-max-height-value"),D=document.getElementById("fog-transition-range"),T=document.getElementById("fog-transition-range-value");if(n){if(void 0!==e.enabled&&(n.checked=e.enabled),void 0!==e.fogDensity&&(i.value=e.fogDensity.toString(),s&&(s.textContent=e.fogDensity.toFixed(5)),this.updateSliderVisual(i)),void 0!==e.fogHeight&&(o.value=e.fogHeight.toString(),a&&(a.textContent=e.fogHeight.toFixed(5)),this.updateSliderVisual(o)),e.fogColor){const t=this.colorToHex(e.fogColor);l.value=t,r&&(r.textContent=t),d&&(d.style.backgroundColor=t)}if(e.fogByDistance){const t=e.fogByDistance;void 0!==t.x&&(c.value=t.x.toString(),g&&(g.textContent=t.x.toFixed(0)),this.updateSliderVisual(c)),void 0!==t.y&&(h.value=t.y.toString(),u&&(u.textContent=t.y.toFixed(2)),this.updateSliderVisual(h)),void 0!==t.z&&(p.value=t.z.toString(),m&&(m.textContent=t.z.toFixed(0)),this.updateSliderVisual(p)),void 0!==t.w&&(v.value=t.w.toString(),f&&(f.textContent=t.w.toFixed(2)),this.updateSliderVisual(v))}if(e.fogColor&&b){const t=this.colorToHex(e.fogColor);b.value=t,y&&(y.textContent=t),C&&(C.style.backgroundColor=t)}if(void 0!==t.fogStartHeight&&(w.value=t.fogStartHeight.toString(),x&&(x.textContent=t.fogStartHeight.toFixed(0)),this.updateSliderVisual(w)),void 0!==t.fogEndHeight&&(S.value=t.fogEndHeight.toString(),E&&(E.textContent=t.fogEndHeight.toFixed(0)),this.updateSliderVisual(S)),void 0!==t.fogDensity&&(I.value=t.fogDensity.toString(),B&&(B.textContent=t.fogDensity.toFixed(5)),this.updateSliderVisual(I)),t.fogColor&&F){const e=this.colorToHex(t.fogColor);F.value=e,k&&(k.textContent=e),L&&(L.style.backgroundColor=e)}void 0!==t.minHeight&&(this.fogVisibilityParams.minHeight=t.minHeight,R.value=t.minHeight.toString(),_&&(_.textContent=t.minHeight.toFixed(0)),this.updateSliderVisual(R)),void 0!==t.maxHeight&&(this.fogVisibilityParams.maxHeight=t.maxHeight,V.value=t.maxHeight.toString(),P&&(P.textContent=t.maxHeight.toFixed(0)),this.updateSliderVisual(V)),void 0!==t.transitionRange&&(this.fogVisibilityParams.transitionRange=t.transitionRange,D.value=t.transitionRange.toString(),T&&(T.textContent=t.transitionRange.toFixed(0)),this.updateSliderVisual(D)),this.updateControlsEnabledState()}else console.log("[FogControl] 控件未初始化,跳过更新")}updateControlsEnabledState(){const e=document.getElementById("fog-enabled"),t=this.container.querySelectorAll(".preset-btn");if(e){const n=e.checked;this.updateControlsEnabled(n,this.disableSliders,t)}}applyDefaultPresets(){const e=this.render.getSystemConfig("distanceFog")||{},t=this.render.getSystemConfig("heightFog")||{};this.render.updateSystemConfig("distanceFog",e),this.render.updateSystemConfig("heightFog",t),this.refreshControls(null)}reset(){this.applyDefaultPresets(),this.updateAllSliderVisuals()}getContainer(){return this.container}parseColor(e){return e instanceof t?e:"string"==typeof e?t.fromCssColorString(e):e&&void 0!==e.red?new t(e.red,e.green||1,e.blue||1,e.alpha||1):t.WHITE}}class I extends w{container;sunUpdateInterval=null;disableSliders=["light-intensity","light-dir-x","light-dir-y","light-dir-z","follow-sun","ambient-intensity","direction-controls"];constructor(e,t,n){super(e,t,n)}createPanel(e){const t=this.createControlSection("光照系统");t.innerHTML='\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">启用方向光</label>\n <label class="toggle-switch">\n <input type="checkbox" id="lighting-enabled" class="toggle-checkbox">\n <span class="toggle-slider"></span>\n </label>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">光照强度</label>\n <span class="control-value" id="light-intensity-value">1.97</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="light-intensity" min="0" max="5" step="0.01" value="1.97" class="slider">\n </div>\n </div>\n </div>\n \n \x3c!-- 跟随太阳开关 --\x3e\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">跟随太阳</label>\n <label class="toggle-switch">\n <input type="checkbox" id="follow-sun" class="toggle-checkbox" checked>\n <span class="toggle-slider"></span>\n </label>\n </div>\n <div class="hint-text">开启时方向光自动跟随太阳位置,XYZ方向将不可手动调整</div>\n </div>\n \n \x3c!-- 方向控制组 --\x3e\n <div id="direction-controls" class="direction-controls">\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">方向 X</label>\n <span class="control-value" id="light-dir-x-value">1.51</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="light-dir-x" min="-2" max="2" step="0.01" value="1.51" class="slider" disabled>\n </div>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">方向 Y</label> \n <span class="control-value" id="light-dir-y-value">-0.11</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="light-dir-y" min="-2" max="2" step="0.01" value="-0.11" class="slider" disabled>\n </div>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">方向 Z</label>\n <span class="control-value" id="light-dir-z-value">0.21</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="light-dir-z" min="-2" max="2" step="0.01" value="0.21" class="slider" disabled>\n </div>\n </div>\n </div>\n </div>\n \n \x3c!-- 当跟随太阳时,显示太阳信息 --\x3e\n <div id="sun-info" class="sun-info" style="display: block;">\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">太阳高度</label>\n <span class="control-value" id="sun-elevation-value">45.2°</span>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">太阳方位</label>\n <span class="control-value" id="sun-azimuth-value">180.5°</span>\n </div>\n </div>\n \n \x3c!-- 时间控制 --\x3e\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">模拟时间</label>\n </div>\n <div class="preset-buttons">\n <button class="preset-btn" data-time="sunrise">日出</button>\n <button class="preset-btn" data-time="morning">早晨</button>\n <button class="preset-btn active" data-time="noon">正午</button>\n <button class="preset-btn" data-time="afternoon">下午</button>\n <button class="preset-btn" data-time="sunset">日落</button>\n </div>\n </div>\n </div>\n \n \x3c!-- 不跟随太阳时,显示手动控制提示 --\x3e\n <div id="manual-direction-info" class="manual-direction-info" style="display: none;">\n <div class="hint-text">已切换到手动控制模式,可通过XYZ滑块调整光照方向</div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">环境光强度</label>\n <span class="control-value" id="ambient-intensity-value">0.5</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="ambient-intensity" min="0" max="2" step="0.01" value="0.5" class="slider">\n </div>\n </div>\n </div>\n ',e.appendChild(t),this.container=t}initEventListeners(e){const t=document.getElementById("lighting-enabled"),n=document.getElementById("follow-sun"),i=document.getElementById("light-intensity"),s=document.getElementById("light-dir-x"),o=document.getElementById("light-dir-y"),a=document.getElementById("light-dir-z"),l=document.getElementById("ambient-intensity"),r=document.getElementById("direction-controls"),d=document.getElementById("sun-info"),c=document.getElementById("manual-direction-info"),g=this.container.querySelectorAll(".preset-btn");this.updateFollowSunState(n.checked,r,d,c),this.updateControlsEnabled(this.renderConfig.directionalLight.enabled,this.disableSliders,g),t.checked=this.renderConfig.directionalLight.enabled,n.checked&&t.checked&&this.startSunTracking(),t.addEventListener("change",()=>{const e=t.checked;e?(this.render.enableSystem("lighting"),n.checked?this.startSunTracking():this.updateManualDirection()):(this.render.disableSystem("lighting"),this.stopSunTracking()),this.updateControlsEnabled(e,this.disableSliders,g),this.viewer.scene.requestRender()}),n.addEventListener("change",()=>{const e=n.checked;this.updateFollowSunState(e,r,d,c),t.checked&&(e?this.startSunTracking():(this.stopSunTracking(),this.updateManualDirection())),this.viewer.scene.requestRender()}),i.addEventListener("input",()=>{i.disabled||(document.getElementById("light-intensity-value").textContent=i.value,this.applyLightingSettings(),this.updateSliderVisual(i))}),s.addEventListener("input",()=>{s.disabled||(document.getElementById("light-dir-x-value").textContent=s.value,this.applyLightingSettings(),this.updateSliderVisual(s))}),o.addEventListener("input",()=>{o.disabled||(document.getElementById("light-dir-y-value").textContent=o.value,this.applyLightingSettings(),this.updateSliderVisual(o))}),a.addEventListener("input",()=>{a.disabled||(document.getElementById("light-dir-z-value").textContent=a.value,this.applyLightingSettings(),this.updateSliderVisual(a))}),l.addEventListener("input",()=>{document.getElementById("ambient-intensity-value").textContent=l.value,this.applyLightingSettings(),this.updateSliderVisual(l)});const h=document.querySelectorAll(".preset-btn[data-time]");h.forEach(e=>{e.addEventListener("click",()=>{if(!n.checked)return;h.forEach(e=>e.classList.remove("active")),e.classList.add("active");const t=e.getAttribute("data-time");this.applySunTimePreset(t)})}),this.updateSliderVisual(i),this.updateSliderVisual(s),this.updateSliderVisual(o),this.updateSliderVisual(a),this.updateSliderVisual(l)}applyLightingSettings(){const t=parseFloat(document.getElementById("light-intensity").value),n=parseFloat(document.getElementById("ambient-intensity").value);let i,s,o;if(document.getElementById("follow-sun").checked){const e=document.getElementById("light-dir-x-value").textContent,t=document.getElementById("light-dir-y-value").textContent,n=document.getElementById("light-dir-z-value").textContent;i=parseFloat(e||"1.51"),s=parseFloat(t||"-0.11"),o=parseFloat(n||"0.21")}else i=parseFloat(document.getElementById("light-dir-x").value),s=parseFloat(document.getElementById("light-dir-y").value),o=parseFloat(document.getElementById("light-dir-z").value);this.render.updateSystemConfig("lighting",{direction:new e(i,s,o),intensity:t}),this.viewer.scene.globe.luminanceAtZenith=n,this.viewer.scene.requestRender()}updateFollowSunState(e,t,n,i){const s=document.getElementById("light-dir-x"),o=document.getElementById("light-dir-y"),a=document.getElementById("light-dir-z");e?(t.classList.add("disabled"),s.disabled=!0,o.disabled=!0,a.disabled=!0,n&&(n.style.display="block"),i&&(i.style.display="none"),this.updateSunInfoDisplay()):(t.classList.remove("disabled"),s.disabled=!1,o.disabled=!1,a.disabled=!1,n&&(n.style.display="none"),i&&(i.style.display="block")),this.updateSliderVisual(s),this.updateSliderVisual(o),this.updateSliderVisual(a)}startSunTracking(){this.stopSunTracking(),this.updateSunPosition(),this.sunUpdateInterval=window.setInterval(()=>{this.updateSunPosition()},1e3)}stopSunTracking(){this.sunUpdateInterval&&(window.clearInterval(this.sunUpdateInterval),this.sunUpdateInterval=null)}updateSunPosition(){try{const t=this.viewer.clock.currentTime,n=(a.toDate(t),window.Cesium.Simon1994PlanetaryPositions.computeSunPositionInEarthInertialFrame(t)),i=r.computeIcrfToFixedMatrix(t),s=d.multiplyByVector(i,n,new e),o=e.negate(s,new e);e.normalize(o,o);const l=this.vectorToElevationAzimuth(o),c=document.getElementById("sun-elevation-value"),g=document.getElementById("sun-azimuth-value");c&&(c.textContent=`${l.elevation.toFixed(1)}°`),g&&(g.textContent=`${l.azimuth.toFixed(1)}°`),document.getElementById("light-dir-x-value").textContent=o.x.toFixed(2),document.getElementById("light-dir-y-value").textContent=o.y.toFixed(2),document.getElementById("light-dir-z-value").textContent=o.z.toFixed(2);const h=document.getElementById("light-dir-x"),u=document.getElementById("light-dir-y"),p=document.getElementById("light-dir-z");h.value=o.x.toString(),u.value=o.y.toString(),p.value=o.z.toString(),this.updateSliderVisual(h),this.updateSliderVisual(u),this.updateSliderVisual(p),this.render.updateSystemConfig("lighting",{direction:o}),this.viewer.scene.requestRender()}catch(e){console.error("更新太阳位置失败:",e)}}vectorToElevationAzimuth(t){const n=new e;e.normalize(t,n);const i=180*Math.asin(n.z)/Math.PI,s=(180*Math.atan2(n.x,n.y)/Math.PI+360)%360;return{elevation:parseFloat(i.toFixed(1)),azimuth:parseFloat(s.toFixed(1))}}updateManualDirection(){const t=document.getElementById("light-dir-x"),n=document.getElementById("light-dir-y"),i=document.getElementById("light-dir-z");this.render.updateSystemConfig("lighting",{direction:new e(parseFloat(t.value),parseFloat(n.value),parseFloat(i.value))})}applySunTimePreset(e){const t={sunrise:{hour:6,minute:0},morning:{hour:9,minute:0},noon:{hour:12,minute:0},afternoon:{hour:15,minute:0},sunset:{hour:18,minute:0}}[e];if(!t)return;const n=a.toDate(this.viewer.clock.currentTime),i=new Date(n);i.setHours(t.hour,t.minute,0,0);const s=a.fromDate(i);this.viewer.clock.currentTime=s,this.updateSunPosition(),this.viewer.scene.requestRender()}updateSunInfoDisplay(){this.updateSunPosition()}refreshControls(e){console.log("[LightingControl] 刷新控制面板");let t={};e&&e.systems?e.systems.lighting&&(t=e.systems.lighting):t=e&&e.directionalLight?e.directionalLight:this.render.getSystemConfig("lighting")||{},this.updateControlsFromConfig(t),this.applyLightingSettings(),this.updateAllSliderVisuals(),this.viewer.scene.requestRender()}updateControlsFromConfig(e){console.log("[LightingControl] 从配置更新控件",e);const t=document.getElementById("lighting-enabled"),n=document.getElementById("follow-sun"),i=document.getElementById("light-intensity"),s=document.getElementById("light-intensity-value"),o=document.getElementById("light-dir-x"),a=document.getElementById("light-dir-x-value"),l=document.getElementById("light-dir-y"),r=document.getElementById("light-dir-y-value"),d=document.getElementById("light-dir-z"),c=document.getElementById("light-dir-z-value"),g=document.getElementById("ambient-intensity"),h=document.getElementById("ambient-intensity-value"),u=document.getElementById("direction-controls"),p=document.getElementById("sun-info"),m=document.getElementById("manual-direction-info");if(this.container?.querySelectorAll(".preset-btn"),t&&n){if(void 0!==e.enabled&&(t.checked=e.enabled),void 0!==e.followSun&&(n.checked=e.followSun,this.updateFollowSunState(e.followSun,u,p,m),e.followSun&&t.checked?this.startSunTracking():this.stopSunTracking()),void 0!==e.intensity&&(i.value=e.intensity.toString(),s&&(s.textContent=e.intensity.toFixed(2)),this.updateSliderVisual(i)),e.direction&&(o&&void 0!==e.direction.x&&(o.value=e.direction.x.toString(),a&&(a.textContent=e.direction.x.toFixed(2)),this.updateSliderVisual(o)),l&&void 0!==e.direction.y&&(l.value=e.direction.y.toString(),r&&(r.textContent=e.direction.y.toFixed(2)),this.updateSliderVisual(l)),d&&void 0!==e.direction.z&&(d.value=e.direction.z.toString(),c&&(c.textContent=e.direction.z.toFixed(2)),this.updateSliderVisual(d))),void 0!==e.ambientIntensity&&(g.value=e.ambientIntensity.toString(),h&&(h.textContent=e.ambientIntensity.toFixed(2)),this.updateSliderVisual(g)),e.lightColor){const t=this.colorToHex(e.lightColor),n=document.getElementById("light-color"),i=document.getElementById("light-color-value"),s=document.getElementById("light-color-preview");n&&(n.value=t),i&&(i.textContent=t),s&&(s.style.backgroundColor=t)}this.updateControlsEnabledState(),this.viewer.scene.requestRender()}else console.log("[LightingControl] 控件未初始化,跳过更新")}updateControlsEnabledState(){const e=document.getElementById("lighting-enabled"),t=document.getElementById("follow-sun"),n=this.container?.querySelectorAll(".preset-btn");if(e){const i=e.checked;!!t&&t.checked&&i?this.disableSliders.push("light-dir-x","light-dir-y","light-dir-z"):this.disableSliders=this.disableSliders.filter(e=>!["light-dir-x","light-dir-y","light-dir-z"].includes(e)),this.updateControlsEnabled(i,this.disableSliders,n)}}applyDefaultPresets(){const e=this.render.getSystemConfig("lighting")||{};this.render.updateSystemConfig("lighting",e),this.refreshControls(null)}reset(){this.applyDefaultPresets(),this.updateAllSliderVisuals()}getContainer(){return this.container}}class B extends w{container;disableSliders=["shadow-quality","shadow-softness","shadow-darkness","shadow-color","shadow-blend"];constructor(e,t,n){super(e,t,n)}createPanel(e){const t=this.createControlSection("阴影系统");t.innerHTML='\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">启用阴影</label>\n <label class="toggle-switch">\n <input type="checkbox" id="shadows-enabled" class="toggle-checkbox">\n <span class="toggle-slider"></span>\n </label>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">阴影质量</label>\n <span class="control-value" id="shadow-quality-value">4096</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="shadow-quality" min="256" max="8192" step="256" value="4096" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">阴影柔和度</label>\n <span class="control-value" id="shadow-softness-value">3.0</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="shadow-softness" min="0" max="5" step="0.1" value="3.0" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">阴影暗度</label>\n <span class="control-value" id="shadow-darkness-value">0.51</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="shadow-darkness" min="0" max="1" step="0.01" value="0.51" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">阴影颜色</label>\n <input type="color" id="shadow-color" value="#03143a" style="margin-left: 10px;">\n <div class="color-picker" id="shadow-color-preview" style="background-color: #03143a;"></div>\n <span class="control-value" id="shadow-color-value">#03143a</span>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">阴影混合度</label>\n <span class="control-value" id="shadow-blend-value">0.01</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="shadow-blend" min="0" max="1" step="0.01" value="0.01" class="slider">\n </div>\n </div>\n </div>\n ',e.appendChild(t),this.container=t}initEventListeners(t){const n=document.getElementById("shadows-enabled"),i=document.getElementById("shadow-quality"),s=document.getElementById("shadow-softness"),o=document.getElementById("shadow-darkness"),a=document.getElementById("shadow-color"),l=document.getElementById("shadow-blend");this.updateControlsEnabled(this.renderConfig.shadow.enabled,this.disableSliders,null),n.checked=this.renderConfig.shadow.enabled,n.addEventListener("change",()=>{n.checked?this.render.enableSystem("shadow"):this.render.disableSystem("shadow"),this.updateControlsEnabled(n.checked,this.disableSliders,null)}),i.addEventListener("input",()=>{document.getElementById("shadow-quality-value").textContent=i.value,this.render.updateSystemConfig("shadow",{size:parseInt(i.value)}),this.updateSliderVisual(i)}),s.addEventListener("input",()=>{document.getElementById("shadow-softness-value").textContent=s.value,this.render.updateSystemConfig("shadow",{softShadows:parseFloat(s.value)>0}),this.updateSliderVisual(s)}),o.addEventListener("input",()=>{document.getElementById("shadow-darkness-value").textContent=o.value,this.render.updateSystemConfig("shadow",{darkness:parseFloat(o.value)}),this.updateSliderVisual(o)}),a.addEventListener("input",()=>{const t=a.value;document.getElementById("shadow-color-preview").style.backgroundColor=t,document.getElementById("shadow-color-value").textContent=t;const n=t.replace("#",""),i=parseInt(n.substring(0,2),16)/255,s=parseInt(n.substring(2,4),16)/255,o=parseInt(n.substring(4,6),16)/255;this.render.updateSystemConfig("shadow",{shadowColor:new e(i,s,o)})}),l.addEventListener("input",()=>{document.getElementById("shadow-blend-value").textContent=l.value,this.render.updateSystemConfig("shadow",{shadowBlend:parseFloat(l.value)}),this.updateSliderVisual(l)}),this.updateSliderVisual(i),this.updateSliderVisual(s),this.updateSliderVisual(o),this.updateSliderVisual(l)}refreshControls(e){console.log("[ShadowControl] 刷新阴影控制面板");let t={};e&&e.systems?e.systems.shadow&&(t=e.systems.shadow):t=e&&e.shadow?e.shadow:this.render.getSystemConfig("shadow")||{},this.updateControlsFromConfig(t)}updateControlsFromConfig(e){console.log("[ShadowControl] 从配置更新控件",e);const t=document.getElementById("shadows-enabled"),n=document.getElementById("shadow-quality"),i=document.getElementById("shadow-quality-value"),s=document.getElementById("shadow-softness"),o=document.getElementById("shadow-softness-value"),a=document.getElementById("shadow-darkness"),l=document.getElementById("shadow-darkness-value"),r=document.getElementById("shadow-color"),d=document.getElementById("shadow-color-value"),c=document.getElementById("shadow-color-preview"),g=document.getElementById("shadow-blend"),h=document.getElementById("shadow-blend-value");if(t){if(void 0!==e.enabled&&(t.checked=e.enabled),void 0!==e.size&&n){const t=e.size;n.value=t.toString(),i&&(i.textContent=t.toString()),this.updateSliderVisual(n)}if(void 0!==e.softShadows&&s){const t=e.softShadows?1:0;s.value=t.toString(),o&&(o.textContent=t.toFixed(1)),this.updateSliderVisual(s)}else void 0!==e.softness&&s&&(s.value=e.softness.toString(),o&&(o.textContent=e.softness.toFixed(2)),this.updateSliderVisual(s));if(void 0!==e.darkness&&a&&(a.value=e.darkness.toString(),l&&(l.textContent=e.darkness.toFixed(2)),this.updateSliderVisual(a)),e.shadowColor&&r){const t=this.colorToShadowColor(e.shadowColor);r.value=t,d&&(d.textContent=t),c&&(c.style.backgroundColor=t)}void 0!==e.shadowBlend&&g&&(g.value=e.shadowBlend.toString(),h&&(h.textContent=e.shadowBlend.toFixed(2)),this.updateSliderVisual(g)),this.updateControlsEnabledState(),this.viewer.scene.requestRender()}else console.log("[ShadowControl] 控件未初始化,跳过更新")}updateControlsEnabledState(){const e=document.getElementById("shadows-enabled");if(e){const t=e.checked;this.updateControlsEnabled(t,this.disableSliders,null)}}colorToShadowColor(n){if("string"==typeof n)return n;if(n instanceof t)return n.toCssColorString();if(n instanceof e){const e=e=>{const t=Math.round(255*e).toString(16);return 1===t.length?"0"+t:t};return`#${e(n.x)}${e(n.y)}${e(n.z)}`}if(n&&void 0!==n.x){const e=e=>{const t=Math.round(255*e).toString(16);return 1===t.length?"0"+t:t};return`#${e(n.x)}${e(n.y)}${e(n.z)}`}if(n&&void 0!==n.red){const e=e=>{const t=Math.round(255*e).toString(16);return 1===t.length?"0"+t:t};return`#${e(n.red)}${e(n.green)}${e(n.blue)}`}return"#000000"}applyDefaultPresets(){const e=this.render.getSystemConfig("shadow")||{};this.render.updateSystemConfig("shadow",e),this.refreshControls(null)}reset(){this.applyDefaultPresets(),this.updateAllSliderVisuals()}getContainer(){return this.container}}class F extends w{container;post_container;filter_container;white_balance_container;preset_container;disableSliders=["hdr-enabled","fxaa-enabled","brightness","contrast","saturation","gamma","temperature","tint"];constructor(e,t,n){super(e,t,n)}createPanel(e){this.post_container=this.createPostProcessingControls(e),this.filter_container=this.createFilterControls(e),this.white_balance_container=this.createWhiteBalanceControls(e),this.preset_container=this.createPresetControls(e),this.container=e}createPostProcessingControls(e){const t=this.createControlSection("后处理");return t.innerHTML='\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">启用后处理</label>\n <label class="toggle-switch">\n <input type="checkbox" id="post-processing-enabled" class="toggle-checkbox">\n <span class="toggle-slider"></span>\n </label>\n </div>\n </div>\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">启用HDR</label>\n <label class="toggle-switch">\n <input type="checkbox" id="hdr-enabled" class="toggle-checkbox">\n <span class="toggle-slider"></span>\n </label>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">启用FXAA</label>\n <label class="toggle-switch">\n <input type="checkbox" id="fxaa-enabled" class="toggle-checkbox">\n <span class="toggle-slider"></span>\n </label>\n </div>\n </div>\n ',e.appendChild(t),t}createFilterControls(e){const t=this.createControlSection("色彩滤镜");return t.innerHTML=' \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">亮度</label>\n <span class="control-value" id="brightness-value">1.02</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="brightness" min="0.5" max="2" step="0.01" value="1.02" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">对比度</label>\n <span class="control-value" id="contrast-value">1.0</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="contrast" min="0.5" max="2" step="0.01" value="1.0" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">饱和度</label>\n <span class="control-value" id="saturation-value">1.75</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="saturation" min="0" max="3" step="0.01" value="1.75" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">伽马</label>\n <span class="control-value" id="gamma-value">0.5</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="gamma" min="0.1" max="2" step="0.01" value="0.5" class="slider">\n </div>\n </div>\n </div>\n ',e.appendChild(t),t}createWhiteBalanceControls(e){const t=this.createControlSection("白平衡");return t.innerHTML='\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">色温 (K)</label>\n <span class="control-value" id="temperature-value">6730</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="temperature" min="3000" max="12000" step="10" value="6730" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-group">\n <div class="control-row">\n <label class="control-label">色调</label>\n <span class="control-value" id="tint-value">-0.39</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="tint" min="-1" max="1" step="0.01" value="-0.39" class="slider">\n </div>\n </div>\n </div>\n ',e.appendChild(t),t}createPresetControls(e){const t=this.createControlSection("滤镜预设");return t.innerHTML='\n <div class="control-group">\n <div class="control-row">\n <span class="control-label">滤镜预设</span>\n </div>\n <div class="preset-buttons">\n <button class="preset-btn active" data-preset="normal">标准</button>\n <button class="preset-btn" data-preset="warm">暖色调</button>\n <button class="preset-btn" data-preset="cool">冷色调</button>\n <button class="preset-btn" data-preset="vibrant">鲜艳</button>\n <button class="preset-btn" data-preset="desaturated">去色</button>\n <button class="preset-btn" data-preset="high-contrast">高对比度</button>\n </div>\n </div>\n ',e.appendChild(t),t}initEventListeners(e){this.initPostProcessingEventListeners(),this.initFilterEventListeners(),this.initWhiteBalanceEventListeners(),this.initPresetEventListeners()}initPostProcessingEventListeners(){const e=document.getElementById("post-processing-enabled"),t=document.getElementById("hdr-enabled"),n=document.getElementById("fxaa-enabled"),i=this.preset_container.querySelectorAll(".preset-btn[data-preset]");this.updateControlsEnabled(this.renderConfig.postProcessing.enabled,this.disableSliders,i),e.checked=this.renderConfig.postProcessing.enabled,e.addEventListener("change",()=>{e.checked?this.render.enableSystem("postProcessing"):this.render.disableSystem("postProcessing"),this.updateControlsEnabled(e.checked,this.disableSliders,i)}),t.addEventListener("change",()=>{this.viewer.scene.highDynamicRange=t.checked,this.viewer.scene.requestRender()}),n.addEventListener("change",()=>{this.viewer.scene.postProcessStages.fxaa.enabled=n.checked,this.viewer.scene.requestRender()}),this.viewer.scene.highDynamicRange=t.checked,this.viewer.scene.postProcessStages.fxaa.enabled=n.checked}initFilterEventListeners(){const e=document.getElementById("brightness"),t=document.getElementById("contrast"),n=document.getElementById("saturation"),i=document.getElementById("gamma");e.addEventListener("input",()=>{document.getElementById("brightness-value").textContent=e.value,this.render.updateSystemConfig("postProcessing",{brightness:parseFloat(e.value)}),this.updateSliderVisual(e)}),t.addEventListener("input",()=>{document.getElementById("contrast-value").textContent=t.value,this.render.updateSystemConfig("postProcessing",{contrast:parseFloat(t.value)}),this.updateSliderVisual(t)}),n.addEventListener("input",()=>{document.getElementById("saturation-value").textContent=n.value,this.render.updateSystemConfig("postProcessing",{saturation:parseFloat(n.value)}),this.updateSliderVisual(n)}),i.addEventListener("input",()=>{document.getElementById("gamma-value").textContent=i.value,this.render.updateSystemConfig("postProcessing",{gamma:parseFloat(i.value)}),this.updateSliderVisual(i)}),this.updateSliderVisual(e),this.updateSliderVisual(t),this.updateSliderVisual(n),this.updateSliderVisual(i)}initWhiteBalanceEventListeners(){const e=document.getElementById("temperature"),t=document.getElementById("tint");e.addEventListener("input",()=>{document.getElementById("temperature-value").textContent=e.value,this.render.updateSystemConfig("postProcessing",{temperature:parseFloat(e.value)}),this.updateSliderVisual(e)}),t.addEventListener("input",()=>{document.getElementById("tint-value").textContent=t.value,this.render.updateSystemConfig("postProcessing",{tint:parseFloat(t.value)}),this.updateSliderVisual(t)}),this.updateSliderVisual(e),this.updateSliderVisual(t)}initPresetEventListeners(){const e=document.querySelectorAll(".preset-btn[data-preset]");e.forEach(t=>{t.addEventListener("click",()=>{e.forEach(e=>e.classList.remove("active")),t.classList.add("active");const n=t.getAttribute("data-preset");this.applyFilterPreset(n),t.blur()})})}applyFilterPreset(e){switch(e){case"normal":this.setFilterValues(1.02,1,1.75,.5),this.setWhiteBalanceValues(6730,-.39);break;case"warm":this.setFilterValues(1,1.1,1.1,1),this.setWhiteBalanceValues(7e3,-.1);break;case"cool":this.setFilterValues(1.1,.9,1.1,1),this.setWhiteBalanceValues(5900,.11);break;case"vibrant":this.setFilterValues(1,.8,1.7,1),this.setWhiteBalanceValues(6850,-.5);break;case"desaturated":this.setFilterValues(1,1,.3,1),this.setWhiteBalanceValues(6500,0);break;case"high-contrast":this.setFilterValues(1,1.5,1,1),this.setWhiteBalanceValues(6500,0)}this.viewer.scene.requestRender()}setFilterValues(e,t,n,i){const s=document.getElementById("brightness"),o=document.getElementById("contrast"),a=document.getElementById("saturation"),l=document.getElementById("gamma");s.value=e.toString(),document.getElementById("brightness-value").textContent=e.toString(),o.value=t.toString(),document.getElementById("contrast-value").textContent=t.toString(),a.value=n.toString(),document.getElementById("saturation-value").textContent=n.toString(),l.value=i.toString(),document.getElementById("gamma-value").textContent=i.toString(),this.render.updateSystemConfig("postProcessing",{brightness:e,contrast:t,saturation:n,gamma:i}),this.updateSliderVisual(s),this.updateSliderVisual(o),this.updateSliderVisual(a),this.updateSliderVisual(l)}setWhiteBalanceValues(e,t){const n=document.getElementById("temperature"),i=document.getElementById("tint");n.value=e.toString(),document.getElementById("temperature-value").textContent=e.toString(),i.value=t.toString(),document.getElementById("tint-value").textContent=t.toString(),this.render.updateSystemConfig("postProcessing",{temperature:e,tint:t}),this.updateSliderVisual(n),this.updateSliderVisual(i)}refreshControls(e){console.log("[PostProcessingControl] 刷新后处理控制面板");let t={};e&&e.systems?e.systems.postProcessing&&(t=e.systems.postProcessing):t=e&&e.postProcessing?e.postProcessing:this.render.getSystemConfig("postProcessing")||{},this.updateControlsFromConfig(t),this.updateScene(t),this.updateAllSliderVisuals(),this.viewer.scene.requestRender()}updateControlsFromConfig(e){console.log("[PostProcessingControl] 从配置更新控件",e);const t=document.getElementById("post-processing-enabled"),n=document.getElementById("hdr-enabled"),i=document.getElementById("fxaa-enabled"),s=document.getElementById("brightness"),o=document.getElementById("contrast"),a=document.getElementById("saturation"),l=document.getElementById("gamma"),r=document.getElementById("temperature"),d=document.getElementById("tint"),c=document.getElementById("brightness-value"),g=document.getElementById("contrast-value"),h=document.getElementById("saturation-value"),u=document.getElementById("gamma-value"),p=document.getElementById("temperature-value"),m=document.getElementById("tint-value");if(this.preset_container?.querySelectorAll(".preset-btn[data-preset]"),t){if(void 0!==e.enabled&&(t.checked=e.enabled),void 0!==e.hdrEnabled&&n&&(n.checked=e.hdrEnabled),void 0!==e.fxaaEnabled&&i&&(i.checked=e.fxaaEnabled),void 0!==e.brightness&&s&&(s.value=e.brightness.toString(),c&&(c.textContent=e.brightness.toFixed(2)),this.updateSliderVisual(s)),void 0!==e.contrast&&o&&(o.value=e.contrast.toString(),g&&(g.textContent=e.contrast.toFixed(2)),this.updateSliderVisual(o)),void 0!==e.saturation&&a&&(a.value=e.saturation.toString(),h&&(h.textContent=e.saturation.toFixed(2)),this.updateSliderVisual(a)),void 0!==e.gamma&&l&&(l.value=e.gamma.toString(),u&&(u.textContent=e.gamma.toFixed(2)),this.updateSliderVisual(l)),void 0!==e.temperature&&r&&(r.value=e.temperature.toString(),p&&(p.textContent=e.temperature.toFixed(0)),this.updateSliderVisual(r)),void 0!==e.tint&&d&&(d.value=e.tint.toString(),m&&(m.textContent=e.tint.toFixed(0)),this.updateSliderVisual(d)),void 0!==e.tonemapping){const t=document.getElementById("tonemapping");t&&(t.value=e.tonemapping)}void 0!==e.preset&&this.updatePresetSelection(e.preset),this.updateControlsEnabledState()}else console.log("[PostProcessingControl] 控件未初始化,跳过更新")}updateScene(e){void 0!==e.hdrEnabled&&(this.viewer.scene.highDynamicRange=e.hdrEnabled),void 0!==e.fxaaEnabled&&(this.viewer.scene.postProcessStages.fxaa.enabled=e.fxaaEnabled)}updatePresetSelection(e){const t=this.preset_container?.querySelectorAll(".preset-btn[data-preset]");t&&t.forEach(t=>{t.getAttribute("data-preset")===e?t.classList.add("active"):t.classList.remove("active")})}updateControlsEnabledState(){const e=document.getElementById("post-processing-enabled"),t=this.preset_container?.querySelectorAll(".preset-btn[data-preset]");if(e){const n=e.checked;this.updateControlsEnabled(n,this.disableSliders,t)}}applyDefaultPresets(){const e=this.render.getSystemConfig("postProcessing")||{};this.render.updateSystemConfig("postProcessing",e),this.refreshControls(null)}reset(){this.applyDefaultPresets(),this.updateAllSliderVisuals()}getContainer(){return this.container}}class k{viewer;render;renderConfig;panelContainer;volumetricCloudControl;atmosphereControl;fogControl;lightingControl;shadowControl;postProcessingControl;isPanelVisible=!0;systemMap=new Map;constructor(e){const{render:t,renderConfig:n}=e;if(t)this.render=t,this.renderConfig=t.config;else{if(!n||!n.viewer)return void console.warn("[Render: RenderControl] 缺少必要参数: renderConfig or viewer");this.render=new C(n),this.renderConfig=n}this.viewer=this.render.viewer,this.panelContainer=this.getOrCreatePanelContainer(),this.getSystemInstances(),this.volumetricCloudControl=new x(this.viewer,this.render,this.renderConfig),this.atmosphereControl=new S(this.viewer,this.render,this.renderConfig),this.fogControl=new E(this.viewer,this.render,this.renderConfig),this.lightingControl=new I(this.viewer,this.render,this.renderConfig),this.shadowControl=new B(this.viewer,this.render,this.renderConfig),this.postProcessingControl=new F(this.viewer,this.render,this.renderConfig),this.initPanel()}initPanel(){this.clearPanel(),this.createPanelHeader(),this.createPanelContent(),setTimeout(()=>{this.viewer.scene.highDynamicRange=!0,this.viewer.scene.requestRender()},500),setTimeout(()=>{this.applyDefaultPresets()},100)}getSystemInstances(){const e=this.render.getSystems();this.systemMap.set("lighting",e.lighting),this.systemMap.set("shadow",e.shadow),this.systemMap.set("volumetricClouds",e.volumetricClouds),this.systemMap.set("atmosphereScattering",e.atmosphereScattering),this.systemMap.set("distanceFog",e.distanceFog),this.systemMap.set("heightFog",e.heightFog),this.systemMap.set("postProcessing",e.postProcessing)}getOrCreatePanelContainer(){let e=document.getElementById("environment-control-panel");return e||(e=document.createElement("div"),e.id="environment-control-panel",e.className="environment-control-panel",document.body.appendChild(e),this.addStyles()),e}addStyles(){const e=document.createElement("style");e.textContent='\n .environment-control-panel {\n position: fixed;\n top: 20px;\n right: 20px;\n width: 350px;\n background: rgba(30, 30, 40, 0.9);\n border-radius: 10px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n z-index: 1000;\n font-family: \'Segoe UI\', Arial, sans-serif;\n color: #fff;\n overflow: hidden;\n transition: all 0.3s ease;\n }\n \n .panel-collapsed {\n width: 60px;\n height: 35px;\n }\n \n .panel-expanded {\n max-height: 80vh;\n overflow-y: auto;\n }\n \n .panel-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 15px;\n background: rgba(20, 20, 30, 0.9);\n cursor: pointer;\n user-select: none;\n }\n \n .panel-title {\n display: flex;\n align-items: center;\n gap: 10px;\n font-weight: 600;\n font-size: 16px;\n }\n \n .panel-toggle-icon {\n font-size: 12px;\n transition: transform 0.3s;\n }\n\n /* 暗色主题滚动条 */\n ::-webkit-scrollbar {\n width: 10px;\n height: 10px;\n }\n\n ::-webkit-scrollbar-track {\n background: rgba(30, 30, 40, 0.1);\n border-radius: 5px;\n margin: 4px;\n }\n\n ::-webkit-scrollbar-thumb {\n background: linear-gradient(45deg, #4a86e8, #3a76d8);\n border-radius: 5px;\n border: 2px solid rgba(255, 255, 255, 0.1);\n transition: background 0.3s ease;\n }\n\n ::-webkit-scrollbar-thumb:hover {\n background: linear-gradient(45deg, #5a96f8, #4a86e8);\n box-shadow: 0 0 8px rgba(74, 134, 232, 0.3);\n }\n\n ::-webkit-scrollbar-corner {\n background: rgba(30, 30, 40, 0.1);\n }\n\n /* Firefox 暗色主题 */\n * {\n scrollbar-width: thin;\n scrollbar-color: #4a86e8 rgba(30, 30, 40, 0.1);\n }\n \n .direction-controls.disabled {\n opacity: 0.5;\n pointer-events: none;\n }\n\n .sun-info, .manual-direction-info {\n margin: 10px 0;\n padding: 10px;\n background: rgba(255, 255, 255, 0.05);\n border-radius: 5px;\n border: 1px solid rgba(255, 255, 255, 0.1);\n }\n\n .sun-info .hint-text, .manual-direction-info .hint-text {\n font-size: 12px;\n color: #aaa;\n font-style: italic;\n }\n \n .panel-content {\n padding: 15px;\n }\n \n .control-section {\n margin-bottom: 20px;\n padding-bottom: 15px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n }\n \n .control-section:last-child {\n border-bottom: none;\n }\n \n .section-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 12px;\n }\n \n .section-title {\n font-weight: 600;\n font-size: 14px;\n color: #aaa;\n text-transform: uppercase;\n letter-spacing: 1px;\n }\n \n .control-group {\n margin-bottom: 12px;\n }\n \n .control-row {\n display: flex;\n align-items: center;\n margin-bottom: 8px;\n }\n \n .control-label {\n flex: 1;\n font-size: 13px;\n color: #ccc;\n }\n \n .control-value {\n min-width: 50px;\n text-align: right;\n font-size: 12px;\n color: #888;\n font-family: \'Consolas\', monospace;\n }\n\n .toggle-switch {\n position: relative;\n width: 40px;\n height: 20px;\n }\n\n .toggle-checkbox {\n opacity: 0;\n width: 0;\n height: 0;\n position: absolute; /* 添加绝对定位 */\n z-index: 1; /* 确保在滑块上面 */\n }\n\n .toggle-slider {\n position: absolute;\n cursor: pointer;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: #555;\n transition: .4s;\n border-radius: 20px;\n }\n\n .toggle-slider:before {\n position: absolute;\n content: "";\n height: 16px;\n width: 16px;\n left: 2px;\n bottom: 2px;\n background-color: white;\n transition: .4s;\n border-radius: 50%;\n }\n\n .custom-distance-fog-controls.disabled {\n opacity: 0.5;\n pointer-events: none;\n }\n\n .custom-distance-fog-controls.disabled .slider {\n background: linear-gradient(to right, #333, #333) !important;\n cursor: not-allowed;\n }\n\n /* 修正选择器:通过checkbox的状态来控制父级label */\n .toggle-checkbox:checked + .toggle-slider {\n background-color: #4CAF50;\n }\n\n .toggle-checkbox:checked + .toggle-slider:before {\n transform: translateX(20px);\n }\n \n .slider-container {\n flex: 2;\n margin: 0 10px;\n }\n \n .slider {\n width: 100%;\n height: 6px;\n -webkit-appearance: none;\n appearance: none;\n background: linear-gradient(to right, #333, #666);\n outline: none;\n border-radius: 3px;\n }\n \n .slider::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: #4CAF50;\n cursor: pointer;\n box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);\n }\n \n .slider::-moz-range-thumb {\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: #4CAF50;\n cursor: pointer;\n border: none;\n }\n\n /* 启用状态 - 绿色 */\n .slider:not(:disabled) {\n background: linear-gradient(to right, #333, #666);\n }\n\n .slider:not(:disabled)::-webkit-slider-thumb {\n background: #4CAF50;\n }\n\n /* 禁用状态 - 灰色 */\n .slider:disabled {\n background: linear-gradient(to right, #888, #aaa) !important;\n cursor: not-allowed;\n }\n\n .slider:disabled::-webkit-slider-thumb {\n background: #888;\n cursor: not-allowed;\n }\n \n /* 颜色输入框的基础样式 */\n input[type="color"] {\n width: 32px;\n height: 24px;\n padding: 2px;\n border: 1px solid #cccccc;\n border-radius: 3px;\n cursor: pointer;\n background: white;\n transition: border-color 0.2s ease;\n }\n\n input[type="color"]:hover:not(:disabled) {\n border-color: #4a86e8;\n box-shadow: 0 0 0 1px rgba(74, 134, 232, 0.2);\n }\n\n /* 颜色输入框的禁用样式 */\n input[type="color"]:disabled {\n cursor: not-allowed;\n opacity: 0.6;\n filter: grayscale(0.5);\n border-color: #dddddd;\n background-color: #f5f5f5;\n }\n\n /* 颜色预览框的基础样式 */\n .color-picker {\n width: 20px;\n height: 20px;\n border: 1px solid #cccccc;\n border-radius: 3px;\n margin-left: 8px;\n cursor: pointer;\n transition: transform 0.2s ease;\n display: inline-block;\n vertical-align: middle;\n }\n\n .color-picker:hover:not(:disabled) {\n transform: scale(1.1);\n border-color: #4a86e8;\n box-shadow: 0 0 4px rgba(74, 134, 232, 0.3);\n }\n\n /* 颜色预览框的禁用样式 */\n .color-picker:disabled {\n cursor: not-allowed;\n opacity: 0.6;\n filter: grayscale(0.5);\n transform: none;\n box-shadow: none;\n border-color: #dddddd;\n }\n\n /* 控制标签的禁用样式 */\n input[type="color"]:disabled ~ .control-label {\n color: #999999;\n cursor: not-allowed;\n }\n \n .preset-buttons {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 8px;\n }\n \n .preset-btn, .fog-type-btn, .reset-btn {\n padding: 6px 12px;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 4px;\n color: #ccc;\n font-size: 12px;\n cursor: pointer;\n transition: all 0.2s;\n }\n \n .preset-btn:hover, .fog-type-btn:hover, .reset-btn:hover {\n background: rgba(255, 255, 255, 0.2);\n transform: translateY(-1px);\n }\n \n .preset-btn.active, .fog-type-btn.active {\n background: #4CAF50;\n color: white;\n border-color: #4CAF50;\n }\n .preset-btn:hover:not(:disabled) {\n background: linear-gradient(to bottom, #5a96f8, #4a86e8);\n box-shadow: 0 2px 4px rgba(0,0,0,0.15);\n transform: translateY(-1px);\n }\n\n .preset-btn:active:not(:disabled) {\n background: linear-gradient(to bottom, #3a76d8, #2a66c8);\n transform: translateY(0);\n box-shadow: 0 1px 2px rgba(0,0,0,0.1);\n }\n\n /* 禁用状态样式 */\n .preset-btn:disabled {\n background: linear-gradient(to bottom, #cccccc, #bbbbbb);\n border-color: #aaaaaa;\n color: #888888;\n cursor: not-allowed;\n opacity: 0.7;\n box-shadow: none;\n transform: none;\n }\n\n /* 可选的:添加禁用手势图标 */\n .preset-btn:disabled::after {\n content: " ⛔";\n font-size: 10px;\n opacity: 0.5;\n }\n\n /* 只针对 toggle-checkbox 的禁用样式 */\n .toggle-checkbox:disabled {\n /* 禁用状态的背景和边框 */\n + .toggle-slider {\n background-color: #eeeeee;\n border-color: #dddddd;\n cursor: not-allowed;\n opacity: 0.6;\n }\n \n + .toggle-slider:before {\n background-color: #f5f5f5;\n box-shadow: none;\n }\n }\n\n /* 选中状态的禁用样式 */\n .toggle-checkbox:disabled:checked {\n + .toggle-slider {\n background-color: #a0c3ff;\n border-color: #90b3ef;\n }\n }\n \n .reset-btn {\n background: rgba(255, 100, 100, 0.2);\n border-color: rgba(255, 100, 100, 0.3);\n }\n \n .reset-btn:hover {\n background: rgba(255, 100, 100, 0.3);\n }\n \n .hint-text {\n font-size: 11px;\n color: #888;\n font-style: italic;\n margin-top: 4px;\n }\n ',document.head.appendChild(e)}createPanelContent(){const e=document.createElement("div");e.className="panel-content",e.id="env-panel-content",e.style.display=this.isPanelVisible?"block":"none";const t=window.innerHeight,n=Math.min(t-100,700);e.style.maxHeight=`${n}px`,e.style.overflowY="auto",e.style.overflowX="hidden",this.createConfigManagementSection(e),this.atmosphereControl.createPanel(e),this.volumetricCloudControl.createPanel(e),this.fogControl.createPanel(e),this.lightingControl.createPanel(e),this.shadowControl.createPanel(e),this.postProcessingControl.createPanel(e),this.panelContainer.appendChild(e),setTimeout(()=>{this.initEventListeners(),this.initConfigEventListeners(),this.volumetricCloudControl.initEventListeners(e),this.atmosphereControl.initEventListeners(e),this.fogControl.initEventListeners(e),this.lightingControl.initEventListeners(e),this.shadowControl.initEventListeners(e),this.postProcessingControl.initEventListeners(e)},100)}initEventListeners(){const e=document.getElementById("env-panel-toggle"),t=document.getElementById("env-panel-content");e?.addEventListener("click",()=>{this.isPanelVisible=!this.isPanelVisible,this.isPanelVisible?(this.panelContainer.classList.remove("panel-collapsed"),this.panelContainer.classList.add("panel-expanded"),t.style.display="block",e.querySelector(".panel-toggle-icon").textContent="▲"):(this.panelContainer.classList.remove("panel-expanded"),this.panelContainer.classList.add("panel-collapsed"),t.style.display="none",e.querySelector(".panel-toggle-icon").textContent="▼")})}applyDefaultPresets(){this.volumetricCloudControl.applyDefaultPresets(),this.atmosphereControl.applyDefaultPresets(),this.fogControl.applyDefaultPresets(),this.lightingControl.applyDefaultPresets(),this.shadowControl.applyDefaultPresets(),this.postProcessingControl.applyDefaultPresets(),console.log("[Render: EnvironmentControlPanel] 默认预设已应用")}createPanelHeader(){const e=document.createElement("div");e.className="panel-header",e.id="env-panel-toggle",e.innerHTML=`\n <div class="panel-title">\n <span>🌤️ 环境控制</span>\n <span class="panel-toggle-icon">${this.isPanelVisible?"▲":"▼"}</span>\n </div>\n `,this.panelContainer.appendChild(e)}clearPanel(){this.panelContainer.innerHTML=""}resetAll(){this.volumetricCloudControl.reset(),this.atmosphereControl.reset(),this.fogControl.reset(),this.lightingControl.reset(),this.shadowControl.reset(),this.postProcessingControl.reset()}togglePanel(e){this.panelContainer.style.display=void 0!==e?e?"block":"none":"none"===this.panelContainer.style.display?"block":"none"}createConfigManagementSection(e){const t=document.createElement("div");t.className="config-management-section",t.style.marginBottom="20px",t.style.paddingBottom="15px",t.style.borderBottom="1px solid rgba(255, 255, 255, 0.1)",t.innerHTML='\n <div class="control-group">\n <div class="control-row">\n <span class="section-title">配置文件管理</span>\n </div>\n <div class="config-buttons" style="display: flex; gap: 8px; margin-top: 10px;">\n <button id="config-import-btn" class="preset-btn" style="flex: 1;">\n 📁 导入配置\n </button>\n <button id="config-export-btn" class="preset-btn" style="flex: 1;">\n 💾 导出配置\n </button>\n <button id="config-reset-btn" class="reset-btn" style="flex: 1;">\n 🔄 重置配置\n </button>\n </div>\n <div class="config-status" style="margin-top: 8px;">\n <div id="config-status-text" class="hint-text" style="color: #aaa;">\n 上次保存: 无\n </div>\n <div id="config-filename" class="hint-text" style="color: #888; font-size: 10px;">\n 当前文件: 未加载\n </div>\n </div>\n </div>\n \n \x3c!-- 隐藏的文件输入 --\x3e\n <input type="file" id="config-file-input" accept=".json" style="display: none;">\n \n \x3c!-- 配置预览模态框 --\x3e\n <div id="config-preview-modal" class="config-modal" style="\n display: none;\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: rgba(20, 20, 30, 0.95);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 8px;\n padding: 20px;\n z-index: 2000;\n max-width: 600px;\n max-height: 80vh;\n overflow: hidden;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);\n ">\n <div style="\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 15px;\n padding-bottom: 10px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n ">\n <h3 style="margin: 0; font-size: 16px; color: #fff;">配置预览</h3>\n <button id="config-modal-close" style="\n background: none;\n border: none;\n color: #aaa;\n font-size: 20px;\n cursor: pointer;\n padding: 0 5px;\n ">&times;</button>\n </div>\n <div style="\n background: rgba(0, 0, 0, 0.3);\n border-radius: 5px;\n padding: 10px;\n font-family: \'Consolas\', \'Monaco\', monospace;\n font-size: 12px;\n color: #e0e0e0;\n max-height: 400px;\n overflow-y: auto;\n white-space: pre-wrap;\n word-break: break-all;\n " id="config-preview-content"></div>\n <div style="margin-top: 15px; text-align: right;">\n <button id="config-apply-btn" class="preset-btn" style="\n padding: 8px 20px;\n font-size: 13px;\n ">应用配置</button>\n </div>\n </div>\n \n \x3c!-- 模态框遮罩 --\x3e\n <div id="config-modal-overlay" style="\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.7);\n z-index: 1999;\n "></div>\n ',this.addConfigManagementStyles(),e.appendChild(t)}initConfigEventListeners(){const e=document.getElementById("config-file-input"),t=document.getElementById("config-import-btn"),n=document.getElementById("config-export-btn"),i=document.getElementById("config-reset-btn"),s=document.getElementById("config-modal-close"),o=document.getElementById("config-apply-btn"),a=document.getElementById("config-modal-overlay");t?.addEventListener("click",()=>{e.click()}),e?.addEventListener("change",async t=>{const n=t.target.files?.[0];n&&(await this.handleConfigImport(n),e.value="")}),n?.addEventListener("click",()=>{this.handleConfigExport()}),i?.addEventListener("click",()=>{confirm("确定要重置所有配置为默认值吗?")&&this.handleConfigReset()}),s?.addEventListener("click",()=>{this.hideConfigPreview()}),a?.addEventListener("click",()=>{this.hideConfigPreview()}),o?.addEventListener("click",()=>{this.applyConfigFromPreview()}),document.addEventListener("keydown",e=>{"Escape"===e.key&&this.hideConfigPreview()})}async handleConfigImport(e){try{this.showNotification("正在加载配置文件...","info");const t=await this.previewConfigFile(e);this.showConfigPreview(t,e.name)}catch(e){this.showNotification(`导入失败: ${e.message}`,"error"),console.error("[RenderControl] 配置导入失败:",e)}}handleConfigExport(){try{const e=`scene-config-${(new Date).toISOString().replace(/[:.]/g,"-").slice(0,19)}.json`;this.render.saveConfig(e),this.showNotification("配置导出成功!","success"),this.updateConfigStatus(e,"已保存")}catch(e){this.showNotification(`导出失败: ${e.message}`,"error"),console.error("[RenderControl] 配置导出失败:",e)}}applyConfigFromPreview(){const e=document.getElementById("config-preview-content"),t=e.currentConfig,n=e.currentFilename;if(t)try{this.render.importConfig(t).then(()=>{this.showNotification("配置应用成功!","success"),this.updateConfigStatus(n,"已加载"),this.hideConfigPreview(),this.updateAllControlPanels()}).catch(e=>{throw e})}catch(e){this.showNotification(`配置应用失败: ${e.message}`,"error"),console.error("[RenderControl] 配置应用失败:",e)}else this.showNotification("没有要应用的配置","error")}updateAllControlPanels(){const e=this.render.getConfig();console.log("[RenderControl] 配置已更新,各面板应刷新显示");const t=new CustomEvent("render-config-updated",{detail:{config:e}});document.dispatchEvent(t)}handleConfigReset(){try{this.resetAll();const e={enableAll:!1,directionalLight:{enabled:!1},shadow:{enabled:!1},volumetricClouds:{enabled:!1},atmosphereScattering:{enabled:!1},distanceFog:{enabled:!1},heightFog:{enabled:!1},postProcessing:{enabled:!1}};this.render.applyConfig(e),this.showNotification("配置已重置为默认值","success"),this.updateConfigStatus("","已重置")}catch(e){this.showNotification(`重置失败: ${e.message}`,"error"),console.error("[RenderControl] 配置重置失败:",e)}}showConfigPreview(e,t){const n=document.getElementById("config-preview-modal"),i=document.getElementById("config-modal-overlay"),s=document.getElementById("config-preview-content"),o=JSON.stringify(e,null,2),a=this.highlightJson(o);s.innerHTML=`\n <div style="margin-bottom: 10px; color: #aaa; font-size: 12px;">\n 文件名: <strong>${t}</strong> (${e.metadata?.created?new Date(e.metadata.created).toLocaleString():"未知时间"})\n </div>\n <pre>${a}</pre>\n `,s.currentConfig=e,s.currentFilename=t,n.style.display="block",i.style.display="block"}hideConfigPreview(){const e=document.getElementById("config-preview-modal"),t=document.getElementById("config-modal-overlay");e.style.display="none",t.style.display="none"}async previewConfigFile(e){return new Promise((t,n)=>{const i=new FileReader;i.onload=e=>{try{const n=e.target?.result,i=JSON.parse(n);t(i)}catch(e){n(new Error("配置文件格式错误: "+e.message))}},i.onerror=()=>{n(new Error("读取文件失败"))},i.readAsText(e)})}updateConfigStatus(e,t){const n=document.getElementById("config-status-text"),i=document.getElementById("config-filename"),s=(new Date).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});n.textContent=`${t}于 ${s}`,i.textContent=e?`当前文件: ${e}`:"当前文件: 未加载"}highlightJson(e){return e.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?)/g,e=>e.endsWith(":")?`<span class="config-preview-key">${e}</span>`:e.startsWith('"')?`<span class="config-preview-string">${e}</span>`:e).replace(/\b(true|false)\b/g,'<span class="config-preview-boolean">$&</span>').replace(/\b(null)\b/g,'<span class="config-preview-null">$&</span>').replace(/\b\d+(\.\d+)?\b/g,'<span class="config-preview-number">$&</span>')}addConfigManagementStyles(){if(document.getElementById("config-management-styles"))return;const e=document.createElement("style");e.id="config-management-styles",e.textContent="\n .config-management-section {\n animation: fadeIn 0.3s ease;\n }\n \n .config-modal pre {\n background: rgba(0, 0, 0, 0.3);\n border-radius: 5px;\n padding: 10px;\n font-family: 'Consolas', 'Monaco', monospace;\n font-size: 12px;\n color: #e0e0e0;\n max-height: 400px;\n overflow-y: auto;\n white-space: pre-wrap;\n word-break: break-all;\n }\n \n .config-snackbar {\n position: fixed;\n bottom: 20px;\n right: 20px;\n background: rgba(20, 20, 30, 0.95);\n color: white;\n padding: 12px 20px;\n border-radius: 5px;\n box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);\n z-index: 3000;\n display: flex;\n align-items: center;\n gap: 10px;\n animation: slideInUp 0.3s ease;\n }\n \n .config-snackbar.success {\n border-left: 4px solid #4CAF50;\n }\n \n .config-snackbar.error {\n border-left: 4px solid #f44336;\n }\n \n .config-snackbar.warning {\n border-left: 4px solid #ff9800;\n }\n \n .config-snackbar.info {\n border-left: 4px solid #2196F3;\n }\n \n .config-snackbar-close {\n background: none;\n border: none;\n color: #aaa;\n font-size: 18px;\n cursor: pointer;\n padding: 0 5px;\n }\n \n .config-file-name {\n font-size: 11px;\n color: #888;\n margin-top: 5px;\n }\n \n @keyframes slideInUp {\n from {\n transform: translateY(100%);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n }\n \n @keyframes fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n \n /* 配置预览代码高亮 */\n .config-preview-key { color: #569cd6; }\n .config-preview-string { color: #ce9178; }\n .config-preview-number { color: #b5cea8; }\n .config-preview-boolean { color: #569cd6; }\n .config-preview-null { color: #569cd6; }\n ",document.head.appendChild(e)}showNotification(e,t="info"){const n=document.querySelector(".config-snackbar");n&&n.remove();const i=document.createElement("div");i.className=`config-snackbar ${t}`,i.innerHTML=`\n <span>${e}</span>\n <button class="config-snackbar-close">&times;</button>\n `,document.body.appendChild(i);i.querySelector(".config-snackbar-close").addEventListener("click",()=>{i.remove()}),setTimeout(()=>{i.parentNode&&(i.style.opacity="0",i.style.transition="opacity 0.3s ease",setTimeout(()=>{i.parentNode&&i.remove()},300))},3e3)}}class L{engine;container=null;plotData=[];isExpanded=new Map;panelElement=null;configPath="";constructor(e){const{engine:t,configPath:n}=e;t&&n?(this.engine=t,this.configPath=n,this.container=this.getOrCreatePanelContainer()):console.warn("[Render: PlottingPanel] 缺少必要参数: engine or configPath")}async init(){try{await this.loadPlotData(),this.createPanel(),this.bindEvents(),console.log("标绘面板初始化完成")}catch(e){console.error("初始化标绘面板失败:",e),this.showErrorMessage("加载标绘配置失败")}}async loadPlotData(){try{const e=await fetch(this.configPath);if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);this.plotData=await e.json(),this.initExpandedState(this.plotData)}catch(e){console.error("加载标绘数据失败:",e),console.log("使用内置标绘数据"),this.plotData=this.getDefaultPlotData()}}getDefaultPlotData(){return[{label:"基本元素",icon:"fa fa-pencil-square-o",data:"Documents Folder",expandedIcon:"fa fa-folder-open",collapsedIcon:"fa fa-folder",children:[{label:"点",icon:"icon iconfont gp-yuandian",data:[{label:"图标点",tooltip:"往地球上添加一个点",params:{obj:"Point",color:"#E3C42E"},path:"gp-tubiaodian",kind:"basic",icon:"fa fa-file-word-o"}]}]}]}initExpandedState(e,t=""){e.forEach(e=>{const n=t+e.label;this.isExpanded.set(n,!0),e.children&&e.children.length>0&&e.children.forEach(e=>{const t=`${n}_${e.label}`;this.isExpanded.set(t,!0)})})}getOrCreatePanelContainer(){let e=document.getElementById("plotting-control-panel");return e||(e=document.createElement("div"),e.id="plotting-control-panel",e.className="plotting-control-panel",document.body.appendChild(e)),e}createPanel(){if(!this.container)throw new Error("容器未设置");this.loadFontAwesome(),this.loadIconfont();const e=document.createElement("div");e.className="plotting-panel",e.innerHTML='\n <div class="panel-header">\n <h2 class="panel-title">标绘</h2>\n <div class="panel-actions">\n <button class="btn-icon" id="panel-refresh" title="刷新">\n <i class="fa fa-refresh"></i>\n </button>\n <button class="btn-icon" id="panel-settings" title="设置">\n <i class="fa fa-cog"></i>\n </button>\n </div>\n </div>\n \n <div class="panel-content" id="plotting-content">\n \x3c!-- 内容将动态生成 --\x3e\n </div>\n \n <div class="panel-footer">\n <div class="status-info">\n <i class="fa fa-info-circle"></i>\n <span>已选择: <span id="selected-item-count">0</span> 个项目</span>\n </div>\n </div>\n ',this.container.appendChild(e),this.panelElement=e,this.addStyles(),this.renderCategories()}loadFontAwesome(){if(document.querySelector('link[href*="font-awesome"]'))return;const e=document.createElement("link");e.rel="stylesheet",e.href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css",document.head.appendChild(e)}loadIconfont(){if(document.querySelector('link[href*="font_2462472_ob3nj6w4ig"]'))return;const e=document.createElement("link");e.rel="stylesheet",e.href="https://at.alicdn.com/t/c/font_2462472_ob3nj6w4ig.css",document.head.appendChild(e)}addStyles(){if(document.querySelector("#plotting-control-styles"))return;const e=document.createElement("style");e.id="plotting-control-styles",e.textContent="\n .plotting-control-panel {\n position: absolute;\n overflow: hidden;\n padding: 10px;\n top: 0px;\n z-index: 1000;\n }\n \n /* 标绘面板样式 */\n .plotting-panel {\n background: linear-gradient(135deg, #1a1f29 0%, #161b22 100%);\n border-radius: 12px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);\n overflow: hidden;\n height: 85vh;\n max-height: 800px;\n display: flex;\n flex-direction: column;\n border: 1px solid #30363d;\n min-width: 320px; /* 稍微宽一点 */\n max-width: 400px; /* 限制最大宽度 */\n }\n\n /* 面板头部 */\n .panel-header {\n background: #21262d;\n padding: 12px 16px;\n border-bottom: 1px solid #30363d;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .panel-title {\n font-size: 18px;\n font-weight: 600;\n color: #f0f6fc;\n margin: 0;\n letter-spacing: 0.5px;\n }\n\n .panel-actions {\n display: flex;\n gap: 6px;\n }\n\n .btn-icon {\n background: transparent;\n border: none;\n color: #8b949e;\n width: 32px;\n height: 32px;\n border-radius: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: all 0.2s ease;\n font-size: 14px;\n }\n\n .btn-icon:hover {\n background: #30363d;\n color: #f0f6fc;\n }\n\n /* 面板内容 */\n .panel-content {\n flex: 1;\n overflow-y: auto;\n padding: 12px;\n }\n\n /* 分类区域 */\n .categories-container {\n display: flex;\n flex-direction: column;\n gap: 12px;\n }\n \n .category-section {\n width: 100%;\n background: #21262d;\n border-radius: 8px;\n border: 1px solid #30363d;\n overflow: hidden;\n }\n \n .category-header {\n background: #1a1f29;\n padding: 10px 12px;\n cursor: pointer;\n user-select: none;\n transition: background 0.2s ease;\n border-bottom: 1px solid #30363d;\n }\n\n .category-header:hover {\n background: #21262d;\n }\n\n .category-header-inner {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .category-icon {\n font-size: 14px;\n color: #8b949e;\n width: 20px;\n text-align: center;\n transition: color 0.2s ease;\n }\n\n .category-label {\n font-size: 14px;\n font-weight: 600;\n color: #f0f6fc;\n flex: 1;\n }\n\n .category-toggle {\n font-size: 10px;\n color: #8b949e;\n transition: transform 0.3s ease;\n }\n \n /* 子分类内容 */\n .category-content {\n padding: 8px;\n }\n \n .subcategory-container {\n display: flex;\n flex-wrap: wrap;\n gap: 12px; /* 分类之间的间距 */\n }\n \n /* 子分类区块 */\n .subcategory-block {\n flex: 1;\n min-width: 140px;\n background: #1c2128;\n border-radius: 6px;\n border: 1px solid #373e47;\n padding: 8px;\n }\n \n .subcategory-header {\n background: transparent;\n padding: 6px 8px;\n margin-bottom: 8px;\n border-bottom: 1px solid #30363d;\n }\n \n .subcategory-header-inner {\n display: flex;\n align-items: center;\n gap: 6px;\n }\n \n .subcategory-icon {\n font-size: 12px;\n color: #7d8590;\n width: 16px;\n text-align: center;\n }\n \n .subcategory-label {\n font-size: 13px;\n font-weight: 500;\n color: #e6edf3;\n flex: 1;\n }\n \n /* 按钮网格 - 修改为一行3个 */\n .buttons-grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr); /* 一行3个 */\n gap: 6px; /* 按钮之间间距更小 */\n }\n\n /* 标绘按钮 */\n .plot-button {\n background: #161b22;\n border: 1px solid #30363d;\n border-radius: 4px;\n padding: 8px 4px;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n cursor: pointer;\n transition: all 0.2s ease;\n min-height: 60px; /* 更小的高度 */\n position: relative;\n overflow: hidden;\n }\n\n .plot-button:hover {\n background: #1c2b41;\n border-color: #58a6ff;\n }\n\n .plot-button.active {\n background: #1c2b41;\n border-color: #1f6feb;\n }\n \n .plot-button:hover .button-icon {\n color: #58a6ff;\n }\n\n .button-icon {\n font-size: 20px; /* 图标更小 */\n color: #8b949e;\n height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s ease;\n }\n \n .button-label {\n font-size: 11px; /* 文字更小 */\n font-weight: 400;\n color: #f0f6fc;\n text-align: center;\n line-height: 1.2;\n word-break: break-word;\n max-width: 100%;\n }\n \n .button-details {\n font-size: 9px;\n color: #6e7681;\n text-align: center;\n line-height: 1.2;\n margin-top: 2px;\n }\n \n .button-path {\n font-family: monospace;\n font-size: 8px;\n color: #484f58;\n margin-top: 1px;\n }\n \n .button-tooltip {\n position: absolute;\n bottom: 100%;\n left: 50%;\n transform: translateX(-50%);\n background: #0d1117;\n border: 1px solid #30363d;\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 11px;\n color: #f0f6fc;\n white-space: nowrap;\n z-index: 1001;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s ease;\n margin-bottom: 6px;\n }\n \n .plot-button:hover .button-tooltip {\n opacity: 1;\n }\n\n /* 空状态 */\n .empty-state {\n padding: 20px;\n text-align: center;\n color: #8b949e;\n }\n\n .empty-icon {\n font-size: 32px;\n margin-bottom: 12px;\n color: #30363d;\n }\n \n .empty-category {\n padding: 16px;\n text-align: center;\n color: #6e7681;\n font-size: 12px;\n font-style: italic;\n }\n \n .empty-subcategory {\n padding: 12px;\n text-align: center;\n color: #6e7681;\n font-size: 11px;\n font-style: italic;\n }\n\n /* 面板底部 */\n .panel-footer {\n background: #21262d;\n padding: 8px 12px;\n border-top: 1px solid #30363d;\n font-size: 11px;\n color: #8b949e;\n }\n\n .status-info {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n gap: 4px;\n }\n \n .status-info i {\n font-size: 10px;\n }\n\n /* 错误消息 */\n .error-message {\n background: linear-gradient(135deg, #da3633 0%, #b62324 100%);\n color: white;\n padding: 8px 12px;\n border-radius: 6px;\n margin: 8px 12px;\n display: flex;\n align-items: center;\n gap: 8px;\n animation: slideIn 0.3s ease;\n font-size: 12px;\n }\n\n @keyframes slideIn {\n from {\n transform: translateY(-20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n }\n \n /* 滚动条样式 */\n .panel-content::-webkit-scrollbar {\n width: 4px;\n }\n \n .panel-content::-webkit-scrollbar-track {\n background: #0d1117;\n border-radius: 2px;\n }\n \n .panel-content::-webkit-scrollbar-thumb {\n background: #30363d;\n border-radius: 2px;\n }\n \n .panel-content::-webkit-scrollbar-thumb:hover {\n background: #484f58;\n }\n \n /* 折叠/展开动画 */\n .category-content, .subcategory-content {\n overflow: hidden;\n transition: height 0.3s ease;\n }\n \n .category-content.collapsed, .subcategory-content.collapsed {\n height: 0 !important;\n overflow: hidden;\n }\n \n .category-toggle.expanded, .subcategory-toggle.expanded {\n transform: rotate(180deg);\n }\n\n /* 在您的全局CSS文件中添加 */\n @font-face {\n font-family: 'iconfont';\n src: url('//at.alicdn.com/t/c/font_2462472_ob3nj6w4ig.woff2?t=1684142134795') format('woff2'),\n url('//at.alicdn.com/t/c/font_2462472_ob3nj6w4ig.woff?t=1684142134795') format('woff'),\n url('//at.alicdn.com/t/c/font_2462472_ob3nj6w4ig.ttf?t=1684142134795') format('truetype');\n }\n\n .iconfont {\n font-family: \"iconfont\" !important;\n font-size: 16px;\n font-style: normal;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n }\n\n /* 响应式设计 */\n @media (max-width: 768px) {\n .plotting-panel {\n height: 90vh;\n border-radius: 0;\n width: 100vw;\n }\n \n .subcategory-container {\n flex-direction: column;\n }\n \n .subcategory-block {\n min-width: 100%;\n }\n \n .buttons-grid {\n grid-template-columns: repeat(2, 1fr); /* 小屏幕一行2个 */\n }\n }\n \n /* SVG 图标样式 */\n .button-icon svg {\n width: 20px;\n height: 20px;\n fill: currentColor;\n stroke: currentColor;\n }\n \n /* 确保 Font Awesome 图标大小 */\n .button-icon i {\n font-size: 18px;\n }\n ",document.head.appendChild(e)}createPlotButton(e){const t=document.createElement("div");t.className="plot-button",t.dataset.plotType=e.params?.obj||"";const n=e.icon||"fas fa-question-circle";let i;return i=this.isCustomIcon(n)?this.getCustomSvgIcon(n,e.label):`<i class="${n}"></i>`,t.innerHTML=`\n <div class="button-icon">\n ${i}\n </div>\n <div class="button-label">${e.label}</div>\n ${e.tooltip?`<div class="button-details">${e.tooltip}</div>`:""}\n ${e.path?`<div class="button-path">${e.path}</div>`:""}\n ${e.tooltip?`<div class="button-tooltip">${e.tooltip}</div>`:""}\n `,t.dataset.plotData=JSON.stringify(e),t}createModelButton(e){const t=document.createElement("div");t.className="plot-button model-button",t.title=e.label;const n=e.icon||"fa fa-cube";return t.innerHTML=`\n <div class="button-icon">\n <i class="${n}"></i>\n </div>\n <div class="button-label">${e.label}</div>\n `,t.dataset.modelData=JSON.stringify(e),t}isCustomIcon(e){if(!e)return!1;return["custom-","svg-","my-icon-"].some(t=>e.includes(t))}getCustomSvgIcon(e,t){const n={"custom-radar":'\n <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">\n <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/>\n </svg>\n ',"custom-drone":'\n <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">\n <path d="M12 2C8.13 2 5 5.13 5 9c0 1.85.63 3.55 1.69 4.9L2 20l6.1-4.69C9.45 18.37 11.15 19 13 19c3.87 0 7-3.13 7-7s-3.13-7-7-7z"/>\n </svg>\n ',"custom-vehicle":'\n <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">\n <path d="M18.92 6.01C18.72 5.42 18.16 5 17.5 5h-11c-.66 0-1.21.42-1.42 1.01L3 12v8c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1h12v1c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-8l-2.08-5.99zM6.5 16c-.83 0-1.5-.67-1.5-1.5S5.67 13 6.5 13s1.5.67 1.5 1.5S7.33 16 6.5 16zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zM5 11l1.5-4.5h11L19 11H5z"/>\n </svg>\n ',"custom-line":'\n <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">\n <line x1="3" y1="12" x2="21" y2="12" stroke="currentColor" stroke-width="2"/>\n </svg>\n ',"custom-polygon":'\n <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <polygon points="12,2 19,7 19,17 12,22 5,17 5,7"/>\n </svg>\n ',"custom-circle":'\n <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <circle cx="12" cy="12" r="9"/>\n </svg>\n ',"gp-yuandian":'\n <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">\n <circle cx="12" cy="12" r="6"/>\n </svg>\n ',"gp-tubiaodian":'\n <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">\n <circle cx="12" cy="12" r="4"/>\n <path d="M12 2v2M12 20v2M2 12h2M20 12h2"/>\n </svg>\n ',"iconicon-xian":'\n <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">\n <path d="M3 12h18M12 3v18" stroke="currentColor" stroke-width="2" fill="none"/>\n </svg>\n '};for(const[t,i]of Object.entries(n))if(e.includes(t))return i;return this.getDefaultSvgIcon(t)}getDefaultSvgIcon(e){return e.includes("点")||e.includes("Point")?'\n <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">\n <circle cx="12" cy="12" r="4"/>\n </svg>\n ':e.includes("线")||e.includes("Line")?'\n <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <path d="M3 12h18"/>\n </svg>\n ':e.includes("面")||e.includes("Polygon")?'\n <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <polygon points="12,2 19,7 19,17 12,22 5,17 5,7"/>\n </svg>\n ':e.includes("雷达")||e.includes("Radar")?'\n <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">\n <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/>\n </svg>\n ':'\n <svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">\n <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>\n </svg>\n '}createCategoryElement(e){const t=e.label,n=this.isExpanded.get(t)||!0,i=document.createElement("div");i.className="category-section",i.dataset.category=t;const s=e.icon||"fa fa-folder",o=n?"fa fa-chevron-down":"fa fa-chevron-right",a=document.createElement("div");a.className="category-header",a.innerHTML=`\n <div class="category-header-inner">\n <i class="${s} category-icon"></i>\n <span class="category-label">${e.label}</span>\n <i class="${o} category-toggle ${n?"expanded":""}"></i>\n </div>\n `;const l=document.createElement("div");if(l.className="category-content",l.style.height=n?"auto":"0",n||l.classList.add("collapsed"),e.children&&e.children.length>0){const n=document.createElement("div");n.className="subcategories-container",e.children.forEach(e=>{const i=this.createSubcategoryElement(e,t);n.appendChild(i)}),l.appendChild(n)}else l.innerHTML='<div class="empty-category">暂无标绘项</div>';return i.appendChild(a),i.appendChild(l),i}createSubcategoryElement(e,t){const n=`${t}_${e.label}`,i=this.isExpanded.get(n)||!0,s=document.createElement("div");s.className="subcategory-section",s.dataset.subcategory=n;const o=document.createElement("div");o.className="subcategory-header",o.innerHTML=`\n <div class="subcategory-header-inner">\n <i class="${e.icon||"fa fa-folder"} subcategory-icon"></i>\n <span class="subcategory-label">${e.label}</span>\n </div>\n `;const a=document.createElement("div");if(a.className="subcategory-content",i||(a.classList.add("collapsed"),a.style.height="0"),Array.isArray(e.data)&&e.data.length>0){const t=this.createButtonsGrid(e.data);a.appendChild(t)}else if("model"===e.kind){const t=e;if(t.label&&t.path){const e=this.createModelButton(t);a.appendChild(e)}else a.innerHTML='<div class="empty-subcategory">暂无模型</div>'}else a.innerHTML='<div class="empty-subcategory">暂无标绘项</div>';return s.appendChild(o),s.appendChild(a),s}createButtonsGrid(e){const t=document.createElement("div");return t.className="buttons-grid",e.forEach(e=>{const n=this.createPlotButton(e);t.appendChild(n)}),t}renderCategories(){const e=this.panelElement?.querySelector("#plotting-content");if(!e)return;e.innerHTML="";const t=document.createElement("div");t.className="categories-container",this.plotData.forEach(e=>{const n=this.createCategoryElement(e);t.appendChild(n)}),0===this.plotData.length?e.innerHTML='\n <div class="empty-state">\n <i class="fa fa-database empty-icon"></i>\n <p>暂无标绘数据</p>\n </div>\n ':e.appendChild(t)}bindEvents(){if(!this.panelElement)return;this.panelElement.addEventListener("click",e=>{const t=e.target,n=t.closest(".category-header");if(n){const e=n.closest(".category-section");if(e)return void this.toggleCategory(e)}const i=t.closest(".subcategory-header");if(i){const e=i.closest(".subcategory-section");if(e)return void this.toggleSubcategory(e)}const s=t.closest(".plot-button");s&&this.handlePlotButtonClick(s)});const e=document.getElementById("panel-refresh");e?.addEventListener("click",()=>{this.refreshPanel()});const t=document.getElementById("panel-settings");t?.addEventListener("click",()=>{this.showSettingsDialog()})}toggleCategory(e){const t=e.getAttribute("data-category");if(!t)return;const n=e.querySelector(".category-content"),i=e.querySelector(".category-toggle"),s=this.plotData.find(e=>e.label===t);if(!n||!i||!s)return;const o=!!n.classList.contains("collapsed");i.className=o?"fa fa-chevron-down category-toggle expanded":"fa fa-chevron-right category-toggle",o?(n.classList.remove("collapsed"),n.style.height="auto"):(n.classList.add("collapsed"),n.style.height="0"),this.isExpanded.set(t,o)}toggleSubcategory(e){const t=e.getAttribute("data-subcategory");if(!t)return;const n=e.querySelector(".subcategory-content"),i=e.querySelector(".subcategory-toggle");if(!n||!i)return;const s=!!n.classList.contains("collapsed");i.classList.toggle("expanded",s),s?(n.classList.remove("collapsed"),n.style.height="auto"):(n.classList.add("collapsed"),n.style.height="0"),this.isExpanded.set(t,s)}handlePlotButtonClick(e){const t=e.getAttribute("data-plot-data"),n=e.getAttribute("data-model-data");if(t){const n=JSON.parse(t);this.executePlotCommand(n),this.setActiveButton(e)}else if(n){const t=JSON.parse(n);this.executeModelCommand(t),this.setActiveButton(e)}}executePlotCommand(e){this.engine.plot&&this.engine.plot.createPlot?(this.engine.plot.createPlot({gvolType:e.params?.obj||"",properties:e.params||{},label:e.label,tooltip:e.tooltip||"",path:e.path||"",kind:e.kind||"",icon:e.icon||""}),console.log("已创建标绘:",e.label,e.params)):console.warn("engine.plot.createPlot 未定义,使用模拟调用")}executeModelCommand(e){console.log("加载模型:",e.label,e.path)}setActiveButton(e){document.querySelectorAll(".plot-button").forEach(e=>{e.classList.remove("active")}),e.classList.add("active"),this.updateStatusBar()}updateStatusBar(){const e=document.querySelectorAll(".plot-button.active").length,t=document.getElementById("selected-item-count");t&&(t.textContent=e.toString())}async refreshPanel(){try{await this.loadPlotData(),this.renderCategories(),console.log("标绘面板已刷新")}catch(e){console.error("刷新面板失败:",e),this.showErrorMessage("刷新失败,请检查配置文件")}}showSettingsDialog(){console.log("显示设置对话框")}showErrorMessage(e){if(this.panelElement){const t=document.createElement("div");t.className="error-message",t.innerHTML=`\n <i class="fa fa-exclamation-triangle"></i>\n <span>${e}</span>\n `,this.panelElement.insertBefore(t,this.panelElement.firstChild),setTimeout(()=>{t.parentNode&&t.parentNode.removeChild(t)},5e3)}}destroy(){this.panelElement&&this.panelElement.parentNode&&this.panelElement.parentNode.removeChild(this.panelElement),this.panelElement=null}}window.LACDT||(window.LACDT={});const R={Render:C,RenderControl:k,PlottingControl:L};Object.assign(window.LACDT,R);export{S as AtmosphereControl,m as AtmosphereScatteringSystem,u as CameraListener,p as CameraListenerSystem,v as DistanceFogSystem,E as FogControl,f as HeightFogSystem,I as LightingControl,c as LightingSystem,L as PlottingControl,F as PostProcessingControl,b as PostProcessingSystem,C as Render,k as RenderControl,B as ShadowControl,g as ShadowSystem,x as VolumetricCloudControl,h as VolumetricCloudsSystem,R as extend};