@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.
- package/README.md +172 -0
- package/README_EN.md +172 -0
- package/RenderPreview.png +0 -0
- package/amd/lacdt.render.amd.js +1 -0
- package/cjs/lacdt.render.cjs.js +1 -0
- package/cjs/lacdt.render.d.ts +3743 -0
- package/es/lacdt.render.es.js +1 -0
- package/esm/lacdt.render.d.ts +3743 -0
- package/esm/lacdt.render.js +1 -0
- package/lacdt.render.d.ts +3743 -0
- package/lacdt.render.js +1 -0
- package/package.json +20 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Cartesian3 as e,Color as t,DirectionalLight as n,PostProcessStage as i,Cartographic as s,Cartesian4 as a,Primitive as o,GeometryInstance as r,EllipsoidSurfaceAppearance as l,Material as d,PolygonGeometry as c,GeoJsonDataSource as h,PolygonHierarchy as p,HeadingPitchRange as u,Math as g,Cesium3DTileStyle as m,EllipsoidTerrainProvider as b,ImageryLayer as f,Cesium3DTileset as y,Cartesian2 as v,ShadowMode as x,CustomShader as w,LightingModel as C,viewerCesium3DTilesInspectorMixin as E,TileCoordinatesImageryProvider as S,GeographicTilingScheme as k,LabelStyle as I,HorizontalOrigin as B,VerticalOrigin as L,HeightReference as P,ScreenSpaceEventHandler as T,ScreenSpaceEventType as _,ClippingPolygon as F,ClippingPolygonCollection as M,JulianDate as A,Transforms as R,Matrix3 as z}from"cesium";class D{viewer;config;isEnabled=!1;originalSettings;static DEFAULT_CONFIG={enabled:!0,followSun:!1,direction:new e(-.07,-.74,.51),intensity:1.37,color:t.fromCssColorString("#ffffff"),ambientIntensity:.32};constructor(e,t){this.viewer=e,this.config={...D.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;const{direction:e,intensity:t,color:i,followSun:s,ambientIntensity:a}=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.viewer.scene.globe.luminanceAtZenith=a,this.isEnabled=!0,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,this.viewer.scene.requestRender())}update(e){const t=this.config.followSun;if(this.config={...this.config,...e},this.isEnabled){const{direction:i,intensity:s,color:a,followSun:o}=this.config;let r=this.viewer.scene.light;r||(r=new n({direction:i.clone(),intensity:s,color:a}),this.viewer.scene.light=r),void 0!==e.intensity&&(r.intensity=s),void 0!==e.color&&(r.color=a),void 0!==e.ambientIntensity&&(this.viewer.scene.globe.luminanceAtZenith=e.ambientIntensity),t&&!o?r.direction=i.clone():o||(r.direction=i.clone()),this.viewer.scene.requestRender()}else void 0!==e.ambientIntensity&&(this.viewer.scene.globe.luminanceAtZenith=e.ambientIntensity)}getStatus(){return this.isEnabled}getConfig(){return{...this.config,enabled:this.isEnabled}}saveConfig(){return this.getConfig()}destroy(){this.disable()}}class ${viewer;config;isEnabled=!1;originalSettings;static DEFAULT_CONFIG={enabled:!0,softShadows:!0,size:8192,darkness:.29,maximumDistance:5e3,normalOffset:!0,fadingEnabled:!0,bias:1e-4};constructor(e,t){this.viewer=e,this.config={...$.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;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,this.viewer.scene.requestRender()}disable(){this.isEnabled&&(this.viewer.scene.shadowMap.enabled=!1,this.isEnabled=!1,this.viewer.scene.requestRender())}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}}getStatus(){return this.isEnabled}getConfig(){return{...this.config,enabled:this.isEnabled}}saveConfig(){return this.getConfig()}destroy(){this.disable()}}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}},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(){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)}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||(this.stage?this.stage.enabled=!0:this.initialize(),this.isEnabled=!0,this.viewer.scene.requestRender())}disable(){this.isEnabled&&(this.stage&&(this.stage.enabled=!1),this.isEnabled=!1,this.viewer.scene.requestRender())}update(e){const t=this.isEnabled;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&&e in this.stage.uniforms&&(this.stage.uniforms[e]=t)}),!0!==e.enabled||t?!1===e.enabled&&t&&this.disable():this.enable(),this.viewer.scene.requestRender()}getStatus(){return this.isEnabled}getConfig(){return{...this.config,enabled:this.isEnabled}}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 \n // 改进的光照计算 - 确保云层有足够的亮度\n float sunIntensityAtSurface = clamp(0.3 - dens * 0.5, 0.1, 1.0);\n vec3 sunLight = lighting * czm_lightColor * sunIntensityAtSurface * cloudLightIntensity;\n vec3 ambientSun = czm_lightColor * sunIntensityAtSurface * isotropic() * cloudLightIntensity * 0.5;\n vec3 skyAmbientLight = skyAmbientColor * 0.8 + ambientSun;\n vec3 groundAmbientLight = groundAmbientColor * 0.4 + 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 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}}class N{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={...N.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)}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){}})}update(e){this.config={...this.config,...e}}enable(){this.isEnabled||this.initialize(),this.config.enabled=!0}disable(){this.config.enabled=!1}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}}class V{viewer;config;listeners=new Map;isInitialized=!1;static DEFAULT_CONFIG={volumetricClouds:{enabled:!0,minHeight:0,maxHeight:7700,transitionRange:8500,intensityCallback:N.DEFAULT_CONFIG.intensityCallback},distanceFog:{enabled:!0,minHeight:0,maxHeight:3e3,transitionRange:500,intensityCallback:N.DEFAULT_CONFIG.intensityCallback},heightFog:{enabled:!0,minHeight:0,maxHeight:3e3,transitionRange:500,intensityCallback:N.DEFAULT_CONFIG.intensityCallback},atmosphereScattering:{enabled:!0,minHeight:0,maxHeight:1e4,transitionRange:1e3,intensityCallback:N.DEFAULT_CONFIG.intensityCallback}};constructor(e,t){this.viewer=e,this.config={...V.DEFAULT_CONFIG,...t,volumetricClouds:{...V.DEFAULT_CONFIG.volumetricClouds,...t?.volumetricClouds},distanceFog:{...V.DEFAULT_CONFIG.distanceFog,...t?.distanceFog},heightFog:{...V.DEFAULT_CONFIG.heightFog,...t?.heightFog},atmosphereScattering:{...V.DEFAULT_CONFIG.atmosphereScattering,...t?.atmosphereScattering}}}initialize(){this.isInitialized||(Object.entries(this.config).forEach(([e,t])=>{if(t.enabled){const n=new N(this.viewer,t);n.initialize(),this.listeners.set(e,n)}}),this.isInitialized=!0)}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 N(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 N(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 N(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}getStatus(){const e={};return this.listeners.forEach((t,n)=>{e[n]=t.getStatus()}),e}getConfig(){return{...this.config}}}class q{viewer;config;isEnabled=!1;stage;preRenderListener;cameraListener;defaultSkyAtmosphereShow=!0;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:1.2,rayleighIntensity:.4,mieIntensity:.15,absorptionIntensity:.7,bDensity:.01,bColor:t.fromCssColorString("#b6d3f5ff"),visibility:q.DEFAULT_VISIBILITY_CONFIG,baseBetaR_R:4e-7,baseBetaR_G:49e-7,baseBetaR_B:102e-7,baseBetaM_R:208e-7,baseBetaM_G:182e-7,baseBetaM_B:26e-6,baseBetaA_R:364e-7,baseBetaA_G:416e-7,baseBetaA_B:485e-8,hR:7200,hM:1400,hA:16e3,absorption_falloff:6e3,groundHeight:613e4,atmosphereThickness:49e3,enableGroundMix:!0};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,this.stage.uniforms.absorptionIntensity=this.config.absorptionIntensity*e))}constructor(e,t){this.viewer=e,this.defaultSkyAtmosphereShow=this.viewer.scene.skyAtmosphere.show,this.config={...q.DEFAULT_CONFIG,...t,visibility:{...q.DEFAULT_VISIBILITY_CONFIG,...t?.visibility}},this.config.enabled&&this.initialize()}initialize(){this.viewer.scene.globe.depthTestAgainstTerrain=!0,this.viewer.scene.skyAtmosphere.show=!1,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,absorptionIntensity:this.config.absorptionIntensity,bDensity:this.config.bDensity,bColor:this.config.bColor,baseBetaR:new e(this.config.baseBetaR_R,this.config.baseBetaR_G,this.config.baseBetaR_B),baseBetaM:new e(this.config.baseBetaM_R,this.config.baseBetaM_G,this.config.baseBetaM_B),baseBetaA:new e(this.config.baseBetaA_R,this.config.baseBetaA_G,this.config.baseBetaA_B),hR:this.config.hR,hM:this.config.hM,hA:this.config.hA,absorption_falloff:this.config.absorption_falloff,groundHeight:this.config.groundHeight,atmosphereThickness:this.config.atmosphereThickness,enableGroundMix:this.config.enableGroundMix}}),this.viewer.scene.postProcessStages.add(this.stage),this.cameraListener=new N(this.viewer,this.config.visibility),this.cameraListener.initialize(),this.cameraListener.addListener(e=>{this.updateAtmosphereVisibility(1)}),this.isEnabled=!0,this.stage&&(this.stage.enabled=!0)}enable(){this.isEnabled||(this.stage?(this.viewer.scene.skyAtmosphere.show=!1,this.stage.enabled=!0):this.initialize(),this.isEnabled=!0,this.viewer.scene.requestRender())}disable(){this.isEnabled&&(this.stage&&(this.stage.enabled=!1),this.viewer.scene.skyAtmosphere.show=this.defaultSkyAtmosphereShow,this.isEnabled=!1,this.viewer.scene.requestRender())}update(t){const n=this.isEnabled;this.config={...this.config,...t,visibility:t.visibility?{...this.config.visibility,...t.visibility}:this.config.visibility},this.stage&&Object.entries(t).forEach(([t,n])=>{void 0!==n&&("baseBetaR_R"===t||"baseBetaR_G"===t||"baseBetaR_B"===t?this.stage.uniforms.baseBetaR=new e(this.config.baseBetaR_R,this.config.baseBetaR_G,this.config.baseBetaR_B):"baseBetaM_R"===t||"baseBetaM_G"===t||"baseBetaM_B"===t?this.stage.uniforms.baseBetaM=new e(this.config.baseBetaM_R,this.config.baseBetaM_G,this.config.baseBetaM_B):"baseBetaA_R"===t||"baseBetaA_G"===t||"baseBetaA_B"===t?this.stage.uniforms.baseBetaA=new e(this.config.baseBetaA_R,this.config.baseBetaA_G,this.config.baseBetaA_B):("enableGroundMix"===t||t in this.stage.uniforms)&&(this.stage.uniforms[t]=n))}),this.cameraListener&&t.visibility&&this.cameraListener.update(t.visibility),void 0!==t.enabled&&t.enabled!==n&&(t.enabled?this.enable():this.disable()),this.viewer.scene.requestRender()}getStatus(){return this.isEnabled}getConfig(){return{...this.config,enabled:this.isEnabled}}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 float absorptionIntensity; // 吸收强度\n uniform vec4 customParam; // 自定义参数(未使用)\n in vec2 v_textureCoordinates; // 纹理坐标\n \n // 大气散射物理参数\n uniform vec3 baseBetaR; // 瑞利散射系数(RGB三通道)\n uniform vec3 baseBetaM; // 米氏散射系数(RGB三通道)\n uniform vec3 baseBetaA; // 吸收系数(RGB三通道)\n uniform float hR; // 瑞利散射高度标准(米)\n uniform float hM; // 米氏散射高度标准(米)\n uniform float hA; // 吸收高度标准(米)\n uniform float absorption_falloff; // 吸收衰减系数\n uniform float groundHeight; // 地球半径(米)\n uniform float atmosphereThickness; // 大气厚度(米)\n uniform bool enableGroundMix; // 是否启用地面混合\n const int num_samples = 20; // 主光线采样数量\n const int num_samples_light = 6; // 光线采样数量\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 // 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 intersect_sphere(_in(ray_t) ray, float radius, _inout(float) t0, _inout(float) t1) {\n vec3 rc = -ray.origin; // 从光线起点到球心的向量\n float radius2 = radius * 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 bool intersect_ellipsoid(_in(ray_t) ray, vec3 radii, _inout(float) t0, _inout(float) t1) {\n vec3 invRadii = 1.0 / radii;\n vec3 ro = ray.origin * invRadii;\n vec3 rd = ray.direction * invRadii;\n\n float a = dot(rd, rd);\n float b = 2.0 * dot(ro, rd);\n float c = dot(ro, ro) - 1.0;\n\n float discriminant = b * b - 4.0 * a * c;\n if (discriminant < 0.0) {\n return false; // 无相交\n }\n\n float sqrtDiscriminant = sqrt(discriminant);\n t0 = (-b - sqrtDiscriminant) / (2.0 * a);\n t1 = (-b + sqrtDiscriminant) / (2.0 * a);\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 // 计算太阳光到达指定点的光学深度\n void get_sun_light(\n _in(ray_t) ray, // 从采样点到太阳的光线\n _inout(float) optical_depthR, // 瑞利散射光学深度(输出)\n _inout(float) optical_depthM, // 米氏散射光学深度(输出)\n _inout(float) optical_depthA // 吸收光学深度(输出)\n ) {\n float t0, t1;\n // 计算光线与大气层球体的交点\n intersect_sphere(ray, groundHeight + atmosphereThickness, 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 // 高度\n float height = max(0.0, length(s) - groundHeight);\n\n // 累积光学深度(使用指数衰减模型)\n optical_depthR += exp(-height / hR) * march_step; // 瑞利散射\n optical_depthM += exp(-height / hM) * march_step; // 米氏散射\n float denom = (hA - height) / absorption_falloff;\n float ha = (1.0 / (denom * denom + 1.0)) * exp(-height / hR); // 吸收\n optical_depthA += ha * march_step;\n\n march_pos += march_step; // 前进到下一个采样点\n }\n }\n \n // 基于Ray Marching的大气散射计算主函数\n vec4 get_incident_light(_inout(ray_t) ray, bool inSky) {\n vec3 dir = ray.direction; // 视线方向\n vec3 start = ray.origin; // 视线起点\n\n // 计算视线与大气层球体的交点(求解二次方程)\n float startA, stopA;\n bool atmosphereIntersect = intersect_sphere(ray, groundHeight + atmosphereThickness, startA, stopA);\n\n if (!atmosphereIntersect || stopA <= 0.0) return vec4(0.0); // 无交点,返回黑色\n\n // 计算视线在大气层内的起始和结束距离\n vec2 ray_length = vec2(max(startA, 0.0), inSky ? stopA : min(stopA, plane.distance));\n // 将视线起点移动到大气层内\n ray.origin += ray.direction * ray_length.x;\n ray_length = ray_length - ray_length.x;\n \n // 计算视线与地面的交点\n float startG, stopG;\n bool groundIntersect = intersect_sphere(ray, groundHeight, startG, stopG);\n \n // 如果与地面相交,更新视线结束距离\n if (groundIntersect && startG > 0.0) {\n if (startG > 0.0) ray_length.y = min(ray_length.y, startG);\n }\n\n // 计算步进大小\n float march_step = ray_length.y / 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 float optical_depthA = 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 vec3 betaA = baseBetaA * absorptionIntensity;\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 = max(0.0, length(s) - groundHeight);\n\n // 计算当前采样点的散射密度(考虑高度衰减)\n float hr = exp(-height / hR) * march_step; // 瑞利散射密度\n float hm = exp(-height / hM) * march_step; // 米氏散射密度\n float denom = (hA - height) / absorption_falloff;\n float ha = (1.0 / (denom * denom + 1.0)) * hr; // 吸收密度\n\n // 累积观察方向的光学深度\n optical_depthR += hr;\n optical_depthM += hm;\n optical_depthA += ha;\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 float optical_depth_lightA = 0.;\n get_sun_light(\n light_ray,\n optical_depth_lightR,\n optical_depth_lightM,\n optical_depth_lightA\n );\n\n // 计算总光学深度(观察方向 + 太阳方向)\n vec3 tau =\n betaR * (optical_depthR + optical_depth_lightR) + // 瑞利散射贡献\n betaM * 1.1 * (optical_depthM + optical_depth_lightM) + // 米氏散射贡献(增强系数1.1)\n betaA * (optical_depthA + optical_depth_lightA); // 吸收贡献\n // 计算大气衰减(比尔定律)\n vec3 attenuation = exp(-tau);\n\n // 累积散射贡献(密度 × 衰减)\n sumR += hr * attenuation; // 瑞利散射\n sumM += hm * attenuation; // 米氏散射\n\n march_pos += march_step; // 前进到下一个采样点\n }\n \n // 计算总体的大气透射率(用于确定天空亮度)\n vec3 attenuation = exp(-(betaM * optical_depthM) - (betaR * optical_depthR) - (betaA * optical_depthA)); // 调整衰减强度系数\n\n // 返回最终的大气散射颜色\n return vec4(\n 25. * atmosphereIntensity * // 大气强度放大倍数(可调节参数)\n (sumR * phaseR * betaR + // 瑞利散射贡献:累积散射 × 相位函数 × 散射系数\n sumM * phaseM * betaM), // 米氏散射贡献:累积散射 × 相位函数 × 散射系数\n 1.0 - length(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 rayDirection = positionWC.xyz - czm_viewerPositionWC;\n ray_t ray;\n ray.origin = czm_viewerPositionWC; // 光线起点:相机位置\n ray.direction = normalize(rayDirection); // 光线方向:归一化的视线向量\n plane.distance = length(rayDirection); // 设置平面距离(场景深度)\n \n // 使用深度值、椭球交点和enableGroundMix判断是否绘制地面光照\n float t0, t1;\n bool intersectGround = intersect_ellipsoid(ray, czm_ellipsoidRadii, t0, t1);\n \n bool inSky = true;\n \n if (intersectGround && t1 > 0.0) inSky = false;\n else if (depth < 0.99999999) inSky = false;\n \n // 相机椭球坐标转换为正球坐标\n ray.origin = czm_viewerPositionWC * czm_ellipsoidInverseRadii * groundHeight;\n \n if (enableGroundMix || inSky) {\n // 计算大气散射颜色\n vec4 atmosphereColor = get_incident_light(ray, inSky);\n \n // 优化的大气颜色混合:大气散射 + 场景颜色 × (1 - 大气不透明度)\n rawColor = vec4(atmosphereColor.rgb, atmosphereColor.a * 0.95) + rawColor * (1.0 - atmosphereColor.a * 0.95);\n }\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.viewer.scene.skyAtmosphere.show=this.defaultSkyAtmosphereShow,this.isEnabled=!1}}class O{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 a(10,.003,1138,.1),fogColor:t.fromCssColorString("#dcdcdc"),visibility:O.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 a(e.x,e.y,e.z,e.w*t))}}constructor(e,t){this.viewer=e,this.config={...O.DEFAULT_CONFIG,...t,visibility:{...O.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)}enable(){this.isEnabled||(this.stage?this.stage.enabled=!0:this.initialize(),this.isEnabled=!0,this.viewer.scene.requestRender())}disable(){this.isEnabled&&(this.stage&&(this.stage.enabled=!1),this.isEnabled=!1,this.viewer.scene.requestRender())}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)}),this.viewer.scene.requestRender()}getStatus(){return this.isEnabled}getConfig(){return{...this.config,enabled:this.isEnabled}}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}}class G{viewer;config;isEnabled=!1;stage;preRenderListener;static DEFAULT_VISIBILITY_CONFIG={minHeight:0,maxHeight:1e3,transitionRange:200,enabled:!0};static DEFAULT_CONFIG={enabled:!0,fogByHeight:new a(545.455,0,1138,.008),fogColor:t.WHITE,visibility:G.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 a(e.x,e.y,e.z,e.w*t))}}constructor(e,t){this.viewer=e,this.config={...G.DEFAULT_CONFIG,...t,visibility:{...G.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)}enable(){this.isEnabled||(this.stage?this.stage.enabled=!0:this.initialize(),this.isEnabled=!0,this.viewer.scene.requestRender())}disable(){this.isEnabled&&(this.stage&&(this.stage.enabled=!1),this.isEnabled=!1,this.viewer.scene.requestRender())}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)}),this.viewer.scene.requestRender()}getStatus(){return this.isEnabled}getConfig(){return{...this.config,enabled:this.isEnabled}}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}}class j{viewer;config;isEnabled=!1;stages;static DEFAULT_CONFIG={enabled:!0,brightness:1.02,contrast:1,saturation:1.75,gamma:.5,temperature:6820,tint:-.28,shadowColor:new e(.012,.078,.227),shadowBlend:.01};constructor(e,t){this.viewer=e,this.config={...j.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||(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.viewer.scene.msaaSupported&&(this.viewer.scene.msaaSamples=8),this.isEnabled=!0,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,this.viewer.scene.requestRender())}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)}}),this.viewer.scene.requestRender())}getStatus(){return this.isEnabled}getConfig(){return{...this.config,enabled:this.isEnabled}}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 // 色调映射 (ACES Filmic Tone Mapping 简化版)\n float a = 2.51;\n float b = 0.03;\n float c = 2.43;\n float d = 0.59;\n float e = 0.14;\n color.rgb = clamp((color.rgb * (a * color.rgb + b)) / (color.rgb * (c * color.rgb + d) + e), 0.0, 1.0);\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)}}class U{_primitive=null;_polygonHierarchy;_extrudedHeight;_maxHeight;_baseWaterColor;_frequency;_animationSpeed;_amplitude;_specularIntensity;constructor(e){this._polygonHierarchy=e.polygonHierarchy,this._extrudedHeight=e.extrudedHeight||100,this._maxHeight=e.maxHeight||800,this._baseWaterColor=e.baseWaterColor||new t(64/255,157/255,253/255,.5),this._frequency=e.frequency||1200,this._animationSpeed=e.animationSpeed||.01,this._amplitude=e.amplitude||12,this._specularIntensity=e.specularIntensity||8,void 0!==e.opacity&&(this._baseWaterColor.alpha=e.opacity),this.init()}get extrudedHeight(){return this._extrudedHeight}set extrudedHeight(e){if("number"==typeof e&&this._primitive){const t=this.getGeometry();this._primitive=new o({releaseGeometryInstances:!1,geometryInstances:new r({geometry:t}),asynchronous:!1,appearance:this.createAppearance()}),this._extrudedHeight=e}}get baseWaterColor(){return this._baseWaterColor}set baseWaterColor(e){this._baseWaterColor=e,this.updateMaterial()}get frequency(){return this._frequency}set frequency(e){this._frequency=e,this.updateMaterial()}get animationSpeed(){return this._animationSpeed}set animationSpeed(e){this._animationSpeed=e,this.updateMaterial()}get amplitude(){return this._amplitude}set amplitude(e){this._amplitude=e,this.updateMaterial()}get specularIntensity(){return this._specularIntensity}set specularIntensity(e){this._specularIntensity=e,this.updateMaterial()}get opacity(){return this._baseWaterColor.alpha}set opacity(e){this._baseWaterColor.alpha=e,this.updateMaterial()}setOpacity(e){this.opacity=e}createAppearance(){return new l({material:new d({fabric:{type:"Water",uniforms:{baseWaterColor:this._baseWaterColor,normalMap:"/Textures/waterNormals.jpg",frequency:this._frequency,animationSpeed:this._animationSpeed,amplitude:this._amplitude,specularIntensity:this._specularIntensity}}})})}init(){const e=this.getGeometry();e&&(this._primitive=new o({releaseGeometryInstances:!1,geometryInstances:new r({geometry:e}),asynchronous:!1,appearance:this.createAppearance()}))}getGeometry(){return new c({polygonHierarchy:this._polygonHierarchy,extrudedHeight:this._extrudedHeight})}updateMaterial(){this._primitive&&(this._primitive.appearance=this.createAppearance())}update(){this._primitive&&this._primitive.update()}destroy(){this._primitive&&(this._primitive.destroy(),this._primitive=null)}isDestroyed(){return null===this._primitive}_isGrowing=!1;animationController=null;grow(){this._isGrowing||(this._isGrowing=!0,this.growInternal())}growInternal(){!this._isGrowing||this.extrudedHeight>=this._maxHeight?this._isGrowing=!1:(this.extrudedHeight+=1,window.requestAnimationFrame(()=>this.growInternal()))}stopGrowth(){this._isGrowing=!1}stop(){this.stopGrowth()}stopAnimation(){this.stopGrowth()}get primitive(){return this._primitive}}const W={enabled:!1,color:t.fromCssColorString("#4a90e2"),opacity:.8,reflectivity:.5,refractivity:.75,waveStrength:.1,waveFrequency:10,waveSpeed:.5,normalRepeat:8,shallowWaterDepth:2,depthAttenuation:.1};class Y{viewer;config;waterPrimitive;waterPrimitives=[];isInitialized=!1;isEnabled=!1;morphCompleteListener=null;postRenderListener=null;lastWaterPrimitiveParams=null;lastWaterPrimitiveConfig=null;constructor(e,t){this.viewer=e,this.config={...W,...t}}initialize(){this.isInitialized||(this.createWaterGeometry(),this.setupWaterStyling(),this.setupEventListeners(),this.isInitialized=!0)}createWaterGeometry(){this.loadWaterData().then(()=>{}).catch(e=>{})}async loadWaterData(){try{const t=(await h.load("/data/water.geojson")).entities.values;if(t.forEach(e=>{if(e.polygon&&e.polygon.hierarchy){const t=e.polygon.hierarchy.getValue();if(t){const e=new U({polygonHierarchy:t,extrudedHeight:51,maxHeight:100});e.primitive&&(this.viewer.scene.primitives.add(e.primitive),e.grow(),this.waterPrimitives.push(e))}}}),t.length>0){t[0].position&&this.viewer.camera.flyTo({destination:e.fromDegrees(116.25,39.25,5e3),orientation:{heading:0,pitch:-Math.PI/4,roll:0}})}}catch(e){this.createDebugWater()}}createDebugWater(){const t=new U({polygonHierarchy:new p(e.fromDegreesArray([116,39,116.5,39,116.5,39.5,116,39.5])),extrudedHeight:100,maxHeight:800});this.waterPrimitive=t,this.waterPrimitive.primitive&&(this.viewer.scene.primitives.add(this.waterPrimitive.primitive),this.waterPrimitive.grow()),this.viewer.camera.flyTo({destination:e.fromDegrees(116.25,39.25,5e3),orientation:{heading:0,pitch:-Math.PI/4,roll:0}})}setupWaterStyling(){this.viewer.scene.globe.depthTestAgainstTerrain=!0}setupEventListeners(){this.morphCompleteListener=()=>{this.isEnabled},this.postRenderListener=()=>{this.updateWaterAnimation()},this.viewer.scene.morphComplete.addEventListener(this.morphCompleteListener),this.viewer.scene.postRender.addEventListener(this.postRenderListener)}updateWaterAnimation(){this.isEnabled&&this.waterPrimitive}enable(){if(this.isEnabled)return;const e={...this.config};this.isInitialized?this.recreateWaterPrimitives(e):this.initialize(),this.isEnabled=!0}cleanupRemnants(){this.isEnabled=!1,this.morphCompleteListener&&(this.viewer.scene.morphComplete.removeEventListener(this.morphCompleteListener),this.morphCompleteListener=null),this.postRenderListener&&(this.viewer.scene.postRender.removeEventListener(this.postRenderListener),this.postRenderListener=null),this.waterPrimitives=[],this.waterPrimitive=void 0}async reloadWaterData(e){try{const t=await h.load("/data/water.geojson");t.entities.values.forEach(t=>{if(t.polygon&&t.polygon.hierarchy){const n=t.polygon.hierarchy.getValue();if(n){const t=new U({polygonHierarchy:n,extrudedHeight:51,maxHeight:100,baseWaterColor:e?.color,frequency:e?.waveFrequency,animationSpeed:e?.waveSpeed,amplitude:e?.waveStrength,specularIntensity:e?.reflectivity});e&&void 0!==e.opacity&&t.setOpacity(e.opacity),t.primitive&&(this.viewer.scene.primitives.add(t.primitive),t.grow(),this.waterPrimitives.push(t))}}}),0===this.waterPrimitives.length&&this.createDebugWaterWithConfig(e)}catch(t){this.createDebugWaterWithConfig(e)}}applyWaterConfig(e,t){t.color&&(e.baseWaterColor=t.color),void 0!==t.waveFrequency&&(e.frequency=t.waveFrequency),void 0!==t.waveSpeed&&(e.animationSpeed=t.waveSpeed),void 0!==t.waveStrength&&(e.amplitude=t.waveStrength),void 0!==t.reflectivity&&(e.specularIntensity=t.reflectivity),void 0!==t.opacity&&e.setOpacity&&e.setOpacity(t.opacity)}recreateWaterPrimitives(e){this.destroyWaterPrimitives(),this.loadWaterData().then(()=>{this.updateWaterStyle(),this.setupEventListeners(),this.viewer.scene.requestRender()}).catch(t=>{this.createDebugWaterWithConfig(e)})}createDebugWaterWithConfig(t){const n=this.lastWaterPrimitiveParams||{polygonHierarchy:new p(e.fromDegreesArray([116,39,116.5,39,116.5,39.5,116,39.5])),extrudedHeight:100,maxHeight:800},i=new U(n);this.lastWaterPrimitiveParams=n,this.waterPrimitive=i,this.waterPrimitive.primitive&&(this.viewer.scene.primitives.add(this.waterPrimitive.primitive),t&&this.applyWaterConfig(this.waterPrimitive,t),this.waterPrimitive.grow()),this.viewer.camera.flyTo({destination:e.fromDegrees(116.25,39.25,5e3),orientation:{heading:0,pitch:-Math.PI/4,roll:0}})}destroyWaterPrimitives(){this.waterPrimitives.forEach(e=>{e&&e.primitive&&(this.viewer.scene.primitives.remove(e.primitive),"function"==typeof e.destroy&&e.destroy())}),this.waterPrimitive&&this.waterPrimitive.primitive&&(this.viewer.scene.primitives.remove(this.waterPrimitive.primitive),"function"==typeof this.waterPrimitive.destroy&&this.waterPrimitive.destroy()),this.waterPrimitives=[],this.waterPrimitive=void 0}disable(){this.isEnabled&&(this.stopAllAnimations(),this.removeAllWaterPrimitives(),this.clearAllReferences(),this.triggerGarbageCollection(),this.isEnabled=!1,this.viewer.scene.requestRender(),this.verifyShutdown())}stopAllAnimations(){if(this.waterPrimitives.forEach(e=>{try{e&&("function"==typeof e.stop&&e.stop(),"function"==typeof e.stopAnimation&&e.stopAnimation(),"function"==typeof e.stopGrowth&&e.stopGrowth(),e.animationController&&e.animationController.stop())}catch(e){}}),this.waterPrimitive)try{"function"==typeof this.waterPrimitive.stop&&this.waterPrimitive.stop()}catch(e){}}removeAllWaterPrimitives(){this.waterPrimitives.forEach(e=>{try{e&&e.primitive&&this.viewer.scene.primitives.remove(e.primitive)}catch(e){}});try{this.waterPrimitive&&this.waterPrimitive.primitive&&this.viewer.scene.primitives.remove(this.waterPrimitive.primitive)}catch(e){}setTimeout(()=>{try{const e=[],t=this.viewer.scene.primitives.length;for(let n=0;n<t;n++)e.push(this.viewer.scene.primitives.get(n));const n=[];for(let t=0;t<e.length;t++){const i=e[t];this.isWaterPrimitive(i)&&n.push(i)}n.forEach(e=>{try{this.viewer.scene.primitives.remove(e)}catch(e){}})}catch(e){}},100)}isWaterPrimitive(e){return!!e&&("WaterPrimitive"===e.className||"WaterPrimitive"===e.constructor.name||!0===e._isWater||!0===e._waterPrimitive||e.appearance&&e.appearance.material&&e.appearance.material.type&&e.appearance.material.type.includes("Water")||e._shaderSource&&e._shaderSource.includes("water")||e.geometry&&e.geometry.attributes&&e.geometry.attributes.waterHeight)}clearAllReferences(){this.waterPrimitives.forEach(e=>{try{e&&"function"==typeof e.destroy&&e.destroy()}catch(e){}}),this.waterPrimitives=[];try{this.waterPrimitive&&"function"==typeof this.waterPrimitive.destroy&&this.waterPrimitive.destroy()}catch(e){}if(this.waterPrimitive=void 0,this.viewer.scene.globe)try{const e=this.viewer.scene.globe;e._surface&&delete e._surface._waterTiles}catch(e){}}triggerGarbageCollection(){"function"==typeof window.gc&&window.gc(),setTimeout(()=>{const e=new Array(1e4).fill(null);for(let t=0;t<1e4;t++)e[t]={data:Math.random()};e.length=0},50)}verifyShutdown(){setTimeout(()=>{const e=[],t=this.viewer.scene.primitives.length;for(let n=0;n<t;n++){const t=this.viewer.scene.primitives.get(n);this.isWaterPrimitive(t)&&e.push(t)}e.length>0&&e.forEach(e=>{this.viewer.scene.primitives.remove(e)}),this.viewer.scene.requestRender()},200)}update(e){this.config={...this.config,...e},this.isEnabled&&this.updateWaterStyle()}updateWaterStyle(){this.waterPrimitives.forEach(e=>{e&&(this.config.color&&(e.baseWaterColor=this.config.color),void 0!==this.config.waveFrequency&&(e.frequency=this.config.waveFrequency),void 0!==this.config.waveSpeed&&(e.animationSpeed=this.config.waveSpeed),void 0!==this.config.waveStrength&&(e.amplitude=this.config.waveStrength),void 0!==this.config.reflectivity&&(e.specularIntensity=this.config.reflectivity))}),this.waterPrimitive&&(this.config.color&&(this.waterPrimitive.baseWaterColor=this.config.color),void 0!==this.config.waveFrequency&&(this.waterPrimitive.frequency=this.config.waveFrequency),void 0!==this.config.waveSpeed&&(this.waterPrimitive.animationSpeed=this.config.waveSpeed),void 0!==this.config.waveStrength&&(this.waterPrimitive.amplitude=this.config.waveStrength),void 0!==this.config.reflectivity&&(this.waterPrimitive.specularIntensity=this.config.reflectivity))}getStatus(){return this.isEnabled}getConfig(){return{...this.config}}saveConfig(){return this.getConfig()}getDebugInfo(){return{isInitialized:this.isInitialized,isEnabled:this.isEnabled,waterPrimitivesCount:this.waterPrimitives.length,hasWaterPrimitive:!!this.waterPrimitive,viewerScenePrimitivesCount:this.viewer.scene.primitives.length,waterPrimitivesInScene:this.waterPrimitives.map(e=>({hasPrimitive:!!e.primitive,inScene:!!e.primitive&&this.isPrimitiveInScene(e.primitive)})),config:this.config}}isPrimitiveInScene(e){try{const t=this.viewer.scene.primitives,n=t.length;for(let i=0;i<n;i++){if(t.get(i)===e)return!0}return!1}catch(e){return!1}}forceCleanup(){const e=[],t=this.viewer.scene.primitives;for(let n=t.length-1;n>=0;n--){const i=t.get(n);i&&(i._waterPrimitive||i._waterMaterial||"WaterPrimitive"===i.className)&&e.push(i)}e.forEach(e=>{this.viewer.scene.primitives.remove(e)}),this.waterPrimitives=[],this.waterPrimitive=void 0,this.isEnabled=!1}destroy(){this.morphCompleteListener&&(this.viewer.scene.morphComplete.removeEventListener(this.morphCompleteListener),this.morphCompleteListener=null),this.postRenderListener&&(this.viewer.scene.postRender.removeEventListener(this.postRenderListener),this.postRenderListener=null),this.waterPrimitives.forEach(e=>{e&&e.primitive&&(this.viewer.scene.primitives.remove(e.primitive),e.destroy())}),this.waterPrimitive&&this.waterPrimitive.primitive&&(this.viewer.scene.primitives.remove(this.waterPrimitive.primitive),this.waterPrimitive.destroy()),this.waterPrimitives=[],this.waterPrimitive=void 0,this.isInitialized=!1,this.isEnabled=!1}}class J{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 X{viewer;layers=new Map;entities=new Map;eventListeners=new Map;constructor(e){if(!e&&!e.viewer)throw new Error("Render instance is required.");this.viewer=e.viewer,this.initializeEvents(),this.loadExistingLayers(),this.loadExistingEntities()}initialize(){}enable(){}disable(){}update(e){}getStatus(){return!0}getConfig(){}saveConfig(){}loadExistingLayers(){this.viewer.imageryLayers._layers.forEach((e,t)=>{const n={id:`imagery_layer_${t}`,name:`影像图层${t+1}`,type:"imagery",url:e.imageryProvider&&e.imageryProvider.url||"Unknown",visible:e.show,opacity:e.alpha,options:{},cesiumLayer:e};this.layers.set(n.id,n)}),this.viewer.scene.primitives._primitives.forEach((e,t)=>{if("Cesium3DTileset"===e.constructor.name){const n={id:`3dtiles_layer_${t}`,name:`3D Tiles图层${t+1}`,type:"3dtiles",url:e._url,visible:e.show,opacity:e.alpha||1,options:{},cesiumLayer:e};this.layers.set(n.id,n)}}),this.viewer.dataSources._dataSources.forEach((e,t)=>{const n={id:`datasource_layer_${t}`,name:e.name||`数据源图层${t+1}`,type:"vector",url:"Unknown",visible:!0,opacity:1,options:{},cesiumLayer:e};this.layers.set(n.id,n)})}loadExistingEntities(){if(this.viewer.entities.values){this.viewer.entities.values.forEach((e,t)=>{const n={id:`entity_${t}`,name:e.name||`实体${t+1}`,type:"entity",visible:e.show,opacity:1,cesiumObject:e};e.children&&e.children.length>0&&(n.children=[],this.loadChildEntities(e.children,n.children)),this.entities.set(n.id,n)})}this.viewer.scene.primitives._primitives.forEach((e,t)=>{if("Cesium3DTileset"!==e.constructor.name){const n={id:`primitive_${t}`,name:e.name||`图元${t+1}`,type:"primitive",visible:e.show,opacity:e.alpha||1,cesiumObject:e};this.entities.set(n.id,n)}})}loadChildEntities(e,t){e.forEach((e,n)=>{const i={id:`child_${Date.now()}_${n}`,name:e.name||`子实体${n+1}`,type:"entity",visible:e.show,opacity:1,cesiumObject:e};e.children&&e.children.length>0&&(i.children=[],this.loadChildEntities(e.children,i.children)),t.push(i)})}getAllEntities(){return Array.from(this.entities.values())}getEntity(e){return this.entities.get(e)||null}setEntityVisible(e,t){const n=this.entities.get(e);return!!n&&(n.visible=t,n.cesiumObject&&void 0!==n.cesiumObject.show&&(n.cesiumObject.show=t),n.children&&n.children.forEach(e=>{this.setEntityVisible(e.id,t)}),!0)}setEntityOpacity(e,t){if(t<0||t>1)return!1;const n=this.entities.get(e);return!!n&&(n.opacity=t,n.cesiumObject&&void 0!==n.cesiumObject.alpha&&(n.cesiumObject.alpha=t),n.children&&n.children.forEach(e=>{this.setEntityOpacity(e.id,t)}),!0)}flyToEntity(t){const n=this.entities.get(t);return n?new Promise(t=>{try{const i=n.cesiumObject;if(i&&i.gvolObject){let t=i.gvolObject.longitude,n=i.gvolObject.latitude,s=1e6,a=e.fromDegrees(t,n,s);this.viewer.camera.flyTo({destination:a})}else i?this.viewer.flyTo(i,{offset:new u(0,g.toRadians(-30),1e6)}).then(e=>{t(e)}).catch(e=>{t(!1)}):t(!1)}catch(e){t(!1)}}):Promise.resolve(!1)}flyToLayer(e){const t=this.layers.get(e);return t?new Promise(e=>{try{if("3dtiles"===t.type){const n=t.cesiumLayer;n?this.viewer.zoomTo(n):e(!1)}else if("imagery"===t.type){const n=this.getImageryLayerBounds(t.cesiumLayer);n?this.viewer.flyToBoundingSphere(n).then(t=>{e(t)}).catch(t=>{e(!1)}):e(!1)}else if("vector"===t.type||"geojson"===t.type||"kml"===t.type)this.viewer.flyTo(t.cesiumLayer).then(t=>{e(t)}).catch(t=>{e(!1)});else if("custom"===t.type){const n=t.cesiumLayer;n?this.viewer.flyTo(n).then(t=>{e(t)}).catch(t=>{e(!1)}):e(!1)}else e(!1)}catch(t){e(!1)}}):Promise.resolve(!1)}getImageryLayerBounds(e){const t=window.Cesium;if(!e||!e.imageryProvider)return null;try{return e.imageryProvider.tileBounds?e.imageryProvider.tileBounds:e.imageryProvider.rectangle?e.imageryProvider.rectangle:t.Rectangle.fromDegrees(-180,-90,180,90)}catch(e){return null}}initializeEvents(){["layer:add","layer:remove","layer:visible","layer:opacity","layer:error","entity:add","entity:remove","entity:visible","entity:opacity"].forEach(e=>{this.eventListeners.set(e,[])})}async addLayer(e){try{const t={id:e.id||this.generateLayerId(),name:e.name,type:e.type,url:e.url,visible:void 0===e.visible||e.visible,opacity:void 0!==e.opacity?e.opacity:1,options:e.options||{}},n=await this.createCesiumLayer(t);return t.cesiumLayer=n,this.layers.set(t.id,t),this.emitEvent("layer:add",t),t}catch(e){throw e}}async createCesiumLayer(e){const{type:t,url:n,options:i}=e;try{let s;switch(t){case"imagery":s=this.createImageryLayer(n,i);break;case"terrain":s=this.createTerrainLayer(n,i);break;case"vector":s=await this.createVectorLayer(n,i);break;case"3dtiles":s=this.create3DTilesLayer(n,i);break;case"geojson":s=await this.createGeoJsonLayer(n,i);break;case"kml":s=await this.createKmlLayer(n,i);break;case"custom":s=await this.createCustomLayer(n,i);break;default:throw new Error(`不支持的图层类型: ${t}`)}return s&&(this.setLayerVisibility(s,e.visible),this.setLayerOpacity(s,e.opacity)),s}catch(e){throw e}}createImageryLayer(e,t){const n=window.Cesium;if(!n)throw new Error("Cesium未加载");let i;return i=e.includes("arcgisonline.com")||e.includes("arcgis.com")?new n.ArcGisMapServerImageryProvider({url:e,...t}):e.includes("tile.openstreetmap.org")?new n.OpenStreetMapImageryProvider({url:e,...t}):e.includes("google.com/maps/vt")||e.includes("mt.google.com")||e.includes("maps.wikimedia.org")||e.includes("{x}/{y}/{z}")?new n.UrlTemplateImageryProvider({url:e,...t}):e.includes("wms")?new n.WebMapServiceImageryProvider({url:e,...t}):e.includes("wmts")?new n.WebMapTileServiceImageryProvider({url:e,...t}):new n.SingleTileImageryProvider({url:e,...t}),this.viewer.imageryLayers.addImageryProvider(i)}createTerrainLayer(e,t){const n=window.Cesium;if(!n)throw new Error("Cesium未加载");let i;return i=(e.includes("cesium.com"),new n.CesiumTerrainProvider({url:e,...t})),this.viewer.terrainProvider=i,i}async createVectorLayer(e,t){const n=window.Cesium;if(!n)throw new Error("Cesium未加载");const i=await n.CzmlDataSource.load(e,t);return this.viewer.dataSources.add(i)}async create3DTilesLayer(e,t){const n=window.Cesium;if(!n)throw new Error("Cesium未加载");let i;return i=n.Cesium3DTileset.fromUrl?await n.Cesium3DTileset.fromUrl(e,{...t}):new n.Cesium3DTileset({url:e,...t}),i.show=!0,i.style=new m({color:"color('default')"}),this.viewer.scene.primitives.add(i),await this.viewer.zoomTo(i),i}async createGeoJsonLayer(e,t){const n=window.Cesium;if(!n)throw new Error("Cesium未加载");const i=await n.GeoJsonDataSource.load(e,{stroke:n.Color.HOTPINK,fill:n.Color.PINK.withAlpha(.5),strokeWidth:3,...t});return this.viewer.dataSources.add(i)}async createKmlLayer(e,t){const n=window.Cesium;if(!n)throw new Error("Cesium未加载");const i=await n.KmlDataSource.load(e,{camera:this.viewer.scene.camera,canvas:this.viewer.scene.canvas,...t});return this.viewer.dataSources.add(i)}async createCustomLayer(e,t){const n=await fetch(e),i=await n.json();if("imagery"===i.type)return this.createImageryLayer(i.url,{...i.options,...t});if("geojson"===i.type)return this.createGeoJsonLayer(i.url,{...i.options,...t});throw new Error("不支持的自定义图层类型")}removeLayer(e){const t=this.layers.get(e);if(!t)return!1;try{return this.removeFromCesium(t),this.layers.delete(e),this.emitEvent("layer:remove",t),!0}catch(e){return!1}}removeFromCesium(e){if(!e.cesiumLayer)return;const{cesiumLayer:t,type:n}=e;switch(n){case"imagery":this.viewer.imageryLayers.remove(t,!0);break;case"terrain":this.viewer.terrainProvider=new b;break;case"vector":case"geojson":case"kml":this.viewer.dataSources.remove(t,!0);break;case"3dtiles":this.viewer.scene.primitives.remove(t)}}setLayerVisible(e,t){const n=this.layers.get(e);return!!n&&(n.visible=t,this.setLayerVisibility(n.cesiumLayer,t),this.emitEvent("layer:visible",n),!0)}setLayerVisibility(e,t){e&&(void 0!==e.show||e instanceof f)&&(e.show=t)}setLayerOpacity(e,t){if(t<0||t>1)return!1;const n=this.layers.get(e);return!!n&&(n.opacity=t,"3dtiles"===n.type?this.set3dTileLayerOpacityInternal(n.cesiumLayer,t):this.setLayerOpacityInternal(n.cesiumLayer,t),this.emitEvent("layer:opacity",n),!0)}setLayerOpacityInternal(e,t){e&&(void 0!==e.alpha||e instanceof f)&&(e.alpha=t)}set3dTileLayerOpacityInternal(e,t){e&&(e.style.color=`color('white', ${t})`)}getAllLayers(){return Array.from(this.layers.values())}getLayer(e){return this.layers.get(e)||null}findLayersByName(e){return Array.from(this.layers.values()).filter(t=>t.name.includes(e))}getLayersByType(e){return Array.from(this.layers.values()).filter(t=>t.type===e)}generateLayerId(){return`layer_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}on(e,t){this.eventListeners.has(e)||this.eventListeners.set(e,[]),this.eventListeners.get(e).push(t)}off(e,t){if(!this.eventListeners.has(e))return;const n=this.eventListeners.get(e),i=n.indexOf(t);i>-1&&n.splice(i,1)}emitEvent(e,t,n,i){this.eventListeners.has(e)&&this.eventListeners.get(e).forEach(e=>{try{e(t,n,i)}catch(e){}})}addEntity(e){this.entities.set(e.id,e),this.emitEvent("entity:add",void 0,e)}removeEntity(e){const t=this.entities.get(e);return!!t&&(this.entities.delete(e),this.emitEvent("entity:remove",void 0,t),!0)}removeEntities(){Array.from(this.entities.keys()).forEach(e=>{this.removeEntity(e)})}destroy(){Array.from(this.layers.keys()).forEach(e=>{this.removeLayer(e)}),Array.from(this.entities.keys()).forEach(e=>{this.removeEntity(e)}),this.eventListeners.clear(),this.layers.clear(),this.viewer=null}}class K{engine;measure;eventListeners=new Map;constructor(e){this.engine=e,e&&(this.measure=e.analysisModule.measureService)}enable(){}disable(){}update(e){}getStatus(){return!0}getConfig(){}saveConfig(){}initialize(){this.measure.measureShow=!0}startMeasurement(e){const t=this.getItemByAlias(e);t&&(this.measure.begin(t),this.emitEvent("measurement:start",t))}endMeasurement(e){const t=this.getItemByAlias(e);t&&(this.measure.end(t),this.emitEvent("measurement:end",t))}clearMeasurement(){this.measure.initStatus(),this.emitEvent("measurement:clear")}getMeasureResultData(){return this.measure.measureResultData}getSelectedItem(){return this.measure.selectedItem}getItemByAlias(e){return[{icon:"iconfont gp-celiangzhixian",label:"直线距离",alias:"zhixianjuli"},{icon:"iconfont gp-tiedijuli",label:"贴地距离",alias:"tiedijuli"},{icon:"iconfont gp-celiangmianji",label:"面积量测",alias:"mianji"},{icon:"iconfont gp-celianggaodu",label:"高度量测",alias:"gaodu"},{icon:"iconfont gp-celiangfangwei",label:"方位量测",alias:"fangxiangjiao"},{icon:"iconfont gp-shanchu2",label:"清空",alias:"shanchu"}].find(t=>t.alias===e)}on(e,t){this.eventListeners.has(e)||this.eventListeners.set(e,[]),this.eventListeners.get(e).push(t)}off(e,t){if(this.eventListeners.has(e)){const n=this.eventListeners.get(e),i=n.indexOf(t);i>-1&&n.splice(i,1)}}emitEvent(e,t){this.eventListeners.has(e)&&this.eventListeners.get(e).forEach(e=>{try{e(t)}catch(e){}})}destroy(){this.measure.closeMeasureMethod(),this.eventListeners.clear()}}class Z{tools=new Map;eventListeners=new Map;measurementSystem;render;currentTileset=null;constructor(e){if(!e&&!e.engine)throw new Error("Render instance is required.");this.render=e,e.engine&&(this.measurementSystem=new K(e.engine),this.measurementSystem.initialize()),this.initializeDefaultTools(),this.setupEventListeners()}initialize(){}enable(){}disable(){}update(e){}getStatus(){return!0}destroy(){}getConfig(){}saveConfig(){}initializeDefaultTools(){[{id:"measure-line",name:"直线距离",icon:"iconfont gp-celiangzhixian",tooltip:"直线距离测量",alias:"zhixianjuli"},{id:"measure-terrain",name:"贴地距离",icon:"iconfont gp-tiedijuli",tooltip:"贴地距离测量",alias:"tiedijuli"},{id:"measure-area",name:"面积测量",icon:"iconfont gp-celiangmianji",tooltip:"面积测量",alias:"mianji"},{id:"measure-height",name:"高度测量",icon:"iconfont gp-celianggaodu",tooltip:"高度测量",alias:"gaodu"},{id:"measure-azimuth",name:"方位测量",icon:"iconfont gp-celiangfangwei",tooltip:"方位测量",alias:"fangxiangjiao"},{id:"measure-clear",name:"清空",icon:"iconfont gp-shanchu2",tooltip:"清空所有测量",alias:"shanchu"}].forEach(e=>{this.addTool(e)})}setupEventListeners(){this.on("tool:execute",e=>{this.handleToolExecute(e)})}handleToolExecute(e){if(this.measurementSystem)switch(e.alias){case"zhixianjuli":this.measurementSystem.startMeasurement("zhixianjuli");break;case"tiedijuli":this.measurementSystem.startMeasurement("tiedijuli");break;case"mianji":this.measurementSystem.startMeasurement("mianji");break;case"gaodu":this.measurementSystem.startMeasurement("gaodu");break;case"fangxiangjiao":this.measurementSystem.startMeasurement("fangxiangjiao");break;case"shanchu":this.measurementSystem.clearMeasurement()}}addTool(e){const t={id:e.id||this.generateId(),name:e.name,icon:e.icon||"fa fa-question-circle",tooltip:e.tooltip||e.name,enabled:!0,alias:e.alias};return this.tools.set(t.id,t),this.emitEvent("tool:add",t),t}removeTool(e){const t=this.tools.get(e);return!!t&&(this.tools.delete(e),this.emitEvent("tool:remove",t),!0)}getAllTools(){return Array.from(this.tools.values())}getMeasurementTools(){return this.getAllTools().filter(e=>e.alias&&["zhixianjuli","tiedijuli","mianji","gaodu","fangxiangjiao","shanchu"].includes(e.alias))}executeTool(e){const t=this.tools.get(e);return!!t&&(this.emitEvent("tool:execute",t),!0)}async loadTileset(e){try{const t=await y.fromUrl(e,{maximumScreenSpaceError:80});this.currentTileset=t,this.render.viewer.scene.primitives.add(t),this.render.viewer.zoomTo(t)}catch(e){alert("加载3DTiles失败,请检查地址是否正确")}}applyClipping(e,t){if(this.currentTileset){var n=this.render.getSystem("tileset");t?n.loadClippingData(e,t):n.loadClippingData(e,this.currentTileset)}}on(e,t){this.eventListeners.has(e)||this.eventListeners.set(e,[]),this.eventListeners.get(e).push(t)}emitEvent(e,t){this.eventListeners.has(e)&&this.eventListeners.get(e).forEach(e=>{try{e(t)}catch(e){}})}generateId(){return`tool_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}}function Q(e){if("string"==typeof e)switch(e){case"checkbox":case"checkboxSub":return"checkbox";case"number":case"number1":case"number2":case"number3":case"number4":case"block":case"alt":case"numberNew":case"angle":case"angleGradient":case"zoom":case"pyblock":case"whirl":case"slider":return"number";case"color":return"color";case"options":return"select";default:return"text"}return"text"}class ee{viewer;searchResults=[];searchHistory=[];maxHistorySize=50;constructor(e){if(!e||!e.viewer)throw new Error("Render instance is required.");this.viewer=e.viewer,this.loadSearchHistory()}initialize(){}enable(){}disable(){}update(e){}getStatus(){return!0}destroy(){}getConfig(){}saveConfig(){}async getLocationFromPointName(e){try{const t=`https://api.tianditu.gov.cn/geocoder?ds={"keyWord":"${e}"}&type=search&tk=923387da9c04ca5774690c17b71136a1`,n=await fetch(t),i=await n.json();if(i.location)return i.location}catch(e){}return!1}async searchLocationByName(e){if(!e||e.trim().length<1)return[];const t=await this.getLocationFromPointName(e);if(t){const n={id:`result_${Date.now()}`,name:e.trim(),address:e.trim(),location:{lon:t.lon,lat:t.lat}};return this.searchResults=[n],this.addToSearchHistory(e.trim()),[n]}return[]}async fuzzySearch(e){return!e||e.trim().length<1?[]:await this.searchLocationByName(e)}getTileRange(e,t,n,i="4326"){e=Number(e),t=Number(t),n=Number(n);const s=Math.pow(2,n);let a,o,r,l,d,c;return"4326"==i&&(a=180*e/s-180,l=90-180*t/s,r=180*(e+1)/s-180,o=90-180*(t+1)/s),"3857"==i&&(a=e/s*360-180,l=180*Math.atan(Math.sinh(Math.PI*(1-2*t/s)))/Math.PI,r=(e+1)/s*360-180,o=180*Math.atan(Math.sinh(Math.PI*(1-2*(t+1)/s)))/Math.PI),d=(a+r)/2,c=(o+l)/2,{lon_min:a,lat_min:o,lon_max:r,lat_max:l,center_lon:d,center_lat:c}}flyToLocation(t,n,i=5e3){const s=e.fromDegrees(t,n,i);this.viewer.camera.flyTo({destination:s,orientation:{heading:g.toRadians(0),pitch:g.toRadians(-45),roll:0},duration:2})}flyToTile(t,n,i){const s=this.getTileRange(t,n,i),a=this.calculateZoomLevelToDistance(i-2);this.viewer.camera.flyTo({destination:e.fromDegrees(s.center_lon,s.center_lat,a),orientation:{heading:g.toRadians(0),pitch:g.toRadians(-45),roll:0},duration:2})}calculateZoomLevelToDistance(e){const t=1e7/Math.pow(2,e-1);return Math.max(100,Math.min(1e7,t))}addToSearchHistory(e){const t=this.searchHistory.findIndex(t=>t.toLowerCase()===e.toLowerCase());-1!==t&&this.searchHistory.splice(t,1),this.searchHistory.unshift(e),this.searchHistory.length>this.maxHistorySize&&this.searchHistory.pop(),this.saveSearchHistory()}loadSearchHistory(){const e=localStorage.getItem("sceneSearchHistory");if(e)try{this.searchHistory=JSON.parse(e)}catch(e){this.searchHistory=[]}}saveSearchHistory(){localStorage.setItem("sceneSearchHistory",JSON.stringify(this.searchHistory))}getSearchHistory(){return[...this.searchHistory]}clearSearchHistory(){this.searchHistory=[],this.saveSearchHistory()}getSearchResults(){return[...this.searchResults]}getObjectProperties(e){if(!e)return[];return function(e){if(!e||!e.__meta__)return[];const t={},n=e.__meta__;for(const[i,s]of Object.entries(n)){const n=s;if(n.hidden)continue;const a=n.group||"通用";t[a]||(t[a]={name:a,expanded:!0,items:[]});const o={key:n.key||i,name:n.name,type:Q(n.type),value:e[i],min:n.min,max:n.max,step:n.step,readonly:!1,placeholder:n.description};t[a].items.push(o)}return Object.values(t)}(e)}}class te{cities=[];currentCity=null;viewer=null;tileset=null;eventListeners=new Map;token="";tilesetMaps=new Map;constructor(e){if(!e&&!e.viewer)throw new Error("Render instance is required.");this.viewer=e.viewer}initialize(){}enable(){}disable(){}update(e){}getStatus(){return!0}destroy(){}getConfig(){}saveConfig(){}setToken(e){this.token=e}setViewer(e){this.viewer=e}async fetchCityData(){try{const e=await fetch("https://metaverseds.geovisearth.com/api/v1/city/status/success");if(!e.ok)throw new Error("网络响应不正常");let t=await e.json();return t.sort((e,t)=>e.adcode-t.adcode),this.cities=t,this.emitEvent("data:loaded",t),t}catch(e){return this.emitEvent("data:error",e),[]}}getCities(){return this.cities}searchCities(e){if(!e.trim())return this.cities;const t=e.toLowerCase();return this.cities.filter(e=>e.name.toLowerCase().includes(t)||e.p_name_alias&&e.p_name_alias.toLowerCase().includes(t))}async getProductionStats(){try{const e=await fetch("https://metaverseds.geovisearth.com/api/v1/city/stats");if(!e.ok)throw new Error("网络响应不正常");let t=await e.json(),n=JSON.parse(t);return{total_tile_count:n.features[0].properties.total_tile_count,total_area:n.features[0].properties.total_area,total_buia_count:n.features[0].properties.total_buia_count,city_count:this.cities.length}}catch(e){return this.emitEvent("data:error",e),{total_tile_count:0,total_area:0,total_buia_count:0,city_count:0}}}async addAreaGeoJsonLayer(e){if(!this.viewer)throw new Error("Cesium Viewer未设置");const t=window.Cesium;if(!t)throw new Error("Cesium未加载");try{const n=await t.GeoJsonDataSource.load(e,{stroke:t.Color.CYAN,strokeWidth:3,fill:t.Color.fromCssColorString("#22CACB").withAlpha(.2),clampToGround:!0});this.viewer.dataSources.add(n);const i=n.entities.values;for(let e=0;e<i.length;e++){const n=i[e];n.polygon&&(n.polygon.outline=new t.ConstantProperty(!0),n.polygon.outlineColor=new t.ConstantProperty(t.Color.CYAN),n.polygon.outlineWidth=new t.ConstantProperty(3),n.polygon.height=new t.ConstantProperty(0),n.polygon.extrudedHeight=new t.ConstantProperty(5e3),n.polygon.material=new t.ColorMaterialProperty(t.Color.fromCssColorString("#22CACB").withAlpha(.2)))}this.emitEvent("area:added",n)}catch(e){throw this.emitEvent("area:error",e),e}}async loadCityModel(e){try{if(this.currentCity=e,this.emitEvent("city:select",e),!this.viewer)throw new Error("Cesium Viewer未设置");if(this.tileset&&(this.viewer.scene.primitives.remove(this.tileset),this.tileset=null),e.publish_url)for(const t in e.publish_url){const n=e.publish_url[t];await this.load3DTiles(n)}else{if(!e.url)throw new Error("该城市模型暂不可用");await this.load3DTiles(e.url)}this.emitEvent("model:loaded",e)}catch(t){throw this.emitEvent("model:error",{city:e,error:t}),t}}getCurrentCity(){return this.currentCity}async load3DTiles(e){if(!this.viewer)return;let t={clampToGround:!0,luminanceAtZenith:.5,imageBasedLightingFactor:new v(1,.6),shadows:x.ENABLED,maximumScreenSpaceError:80,customShader:new w({lightingModel:C.PBR,fragmentShaderText:"\n void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {\n vec3 normal = normalize(fsInput.attributes.normalEC) * 1.2;\n float NdotL = max(dot(czm_lightDirectionEC, normal), 0.0);\n float backfaceFactor = smoothstep(0.0, 0.5, 1.0 - NdotL);\n float ambientBoost = 0.6;\n float backlightBoost = backfaceFactor * 0.5;\n float globalBoost = 0.2;\n material.diffuse = material.diffuse * (1.0 + ambientBoost + backlightBoost + globalBoost);\n material.specular *= mix(1.0, 0.3, backfaceFactor);\n vec3 skyLight = vec3(1.0,1.0,1.0) * 0.15 * backfaceFactor;\n material.diffuse += skyLight;\n }\n "})};this.tileset=await y.fromUrl(e,t),this.tileset.maximumMemoryUsage=8192,this.tileset.cacheBytes=4294967296,this.tileset.style=new m({color:"color('default')"}),this.tileset.shadows=x.ENABLED,this.viewer.scene.primitives.add(this.tileset),await this.viewer.zoomTo(this.tileset)}async loadModelByCityName(e,t){let n=await this.getTilesetUrlByCityName(e,t);if(0===n.size)return;const i=[];for(const[e,t]of n.entries())if(this.tilesetMaps.has(e)){const t=this.tilesetMaps.get(e);this.viewer.scene.primitives.contains(t)&&i.push(t)}else try{const n=await y.fromUrl(t,{maximumScreenSpaceError:80,cacheBytes:1073741824,skipLevelOfDetail:!0,enableDebugWireframe:!0});this.viewer.scene.primitives.add(n),this.tilesetMaps.set(e,n),i.push(n)}catch(e){}i.length>0&&this.viewer.zoomTo(i[0])}async loadModelByADCode(e,t){let n=await this.getTilesetUrlByADCode(e,t);if(0===n.size)return;const i=[];for(const[e,t]of n.entries())if(this.tilesetMaps.has(e)){const t=this.tilesetMaps.get(e);this.viewer.scene.primitives.contains(t)&&i.push(t)}else try{const n=await y.fromUrl(t,{maximumScreenSpaceError:80,cacheBytes:1073741824,skipLevelOfDetail:!0,enableDebugWireframe:!0});this.viewer.scene.primitives.add(n),this.tilesetMaps.set(e,n),i.push(n)}catch(e){}i.length>0&&this.viewer.zoomTo(i[0])}async getTilesetUrlByCityName(e,t){try{const i=await fetch(`https://metaverseds.geovisearth.com/api/v1/city/info?name=${e}`);if(!i.ok)throw new Error("网络响应不正常");let s=await i.json();if(0===s.length)return new Map;var n;if(t&&t.length>0){const e=new Set;n=s.filter(n=>{const i=this.getTypeByFeature(n.feature);return!!t.includes(i)&&(e.add(i),!0)});t.filter(t=>!e.has(t)).forEach(e=>{}),n=s.filter(e=>t.includes(this.getTypeByFeature(e.feature)))}else n=s;let a=new Map;return n.forEach(e=>{var t=`https://io-qos.geovisearth.com/getfile/46/brainsim-3dtiles/${e.adcode}/${e.version}/lod${e.lod}${e.feature}${e.standard}/tileset.json`;a.set(`${e.name}_${e.adcode}_${this.getTypeByFeature(e.feature)}`,t)}),a}catch(e){return new Map}}async getTilesetUrlByADCode(e,t){try{const i=await fetch(`https://metaverseds.geovisearth.com/api/v1/city/info?adcode=${e}`);if(!i.ok)throw new Error("网络响应不正常");let s=await i.json();if(0===s.length)return new Map;var n;if(t&&t.length>0){const e=new Set;n=s.filter(n=>{const i=this.getTypeByFeature(n.feature);return!!t.includes(i)&&(e.add(i),!0)});t.filter(t=>!e.has(t)).forEach(e=>{})}else n=s;let a=new Map;return n.forEach(e=>{var t=`https://io-qos.geovisearth.com/getfile/46/brainsim-3dtiles/${e.adcode}/${e.version}/lod${e.lod}${e.feature}${e.standard}/tileset.json`;a.set(`${e.name}_${e.adcode}_${this.getTypeByFeature(e.feature)}`,t)}),a}catch(e){return new Map}}getTypeByFeature(e){return{b:"buia",r:"road",v:"vega",h:"hyda",g:"ground",i:"image",e:"elevation",t:"tree"}[e]||""}removeModelByNameOrADCode(e){let t=!1;for(const[n,i]of this.tilesetMaps.entries())n.includes(e.toString())&&(this.viewer.scene.primitives.remove(i),i.destroy(),this.tilesetMaps.delete(n),t=!0);return t}removeAllModels(){for(const[e,t]of this.tilesetMaps.entries())this.viewer.scene.primitives.remove(t),t.destroy();this.tilesetMaps.clear()}async loadOpenModelByCityName(e,t){if(0===this.token.trim().length)return;let n=await this.getOpenUrlByCityName(e,t);if(0===n.size)return;const i=[];for(const[e,t]of n.entries())if(this.tilesetMaps.has(e)){const t=this.tilesetMaps.get(e);this.viewer.scene.primitives.contains(t)&&i.push(t)}else try{const n=await y.fromUrl(`${t}?token=${this.token}`,{maximumScreenSpaceError:80,cacheBytes:1073741824,skipLevelOfDetail:!0,enableDebugWireframe:!0});this.viewer.scene.primitives.add(n),this.tilesetMaps.set(e,n),i.push(n)}catch(e){}i.length>0&&this.viewer.zoomTo(i[0])}async loadOpenModelByADCode(e,t){if(0===this.token.trim().length)return;let n=await this.getOpenUrlByADCode(e,t);if(0===n.size)return;const i=[];for(const[e,t]of n.entries())if(this.tilesetMaps.has(e)){const t=this.tilesetMaps.get(e);this.viewer.scene.primitives.contains(t)&&i.push(t)}else try{const n=await y.fromUrl(`${t}?token=${this.token}`,{maximumScreenSpaceError:80,cacheBytes:1073741824,skipLevelOfDetail:!0,enableDebugWireframe:!0});this.viewer.scene.primitives.add(n),this.tilesetMaps.set(e,n),i.push(n)}catch(e){}i.length>0&&this.viewer.zoomTo(i[0])}async getOpenUrlByADCode(e,t){if(t&&t.length>0){t.filter(e=>"buia"!==e&&"tree"!==e).forEach(e=>{}),t=t.filter(e=>"buia"===e||"tree"===e)}try{const s=await fetch(`https://metaverseds.geovisearth.com/api/v1/city/info?adcode=${e}`);if(!s.ok)throw new Error("网络响应不正常");let a=await s.json();if(0===a.length)return new Map;var n;if(t&&t.length>0){const e=new Set;n=a.filter(n=>{const i=this.getTypeByFeature(n.feature);return!!t.includes(i)&&(e.add(i),!0)});t.filter(t=>!e.has(t)).forEach(e=>{})}else n=a.filter(e=>{const t=this.getTypeByFeature(e.feature);return"buia"===t||"tree"===t});var i=new Map;return n.forEach(e=>{const t=this.getTypeByFeature(e.feature);var n;"buia"===t?n=`https://api.open.geovisearth.com/pj/getfile/46/brainsim-3dtiles/${e.adcode}/V3/lod/bm/tileset.json`:"tree"===t&&(n=`https://api.open.geovisearth.com/pj/getfile/46/brainsim-3dtiles/${e.adcode}/V3/instance/tree/tileset.json`),i.set(`${e.name}_${e.adcode}_${t}`,n)}),i}catch(e){return new Map}}async getOpenUrlByCityName(e,t){if(t&&t.length>0){t.filter(e=>"buia"!==e&&"tree"!==e).forEach(e=>{}),t=t.filter(e=>"buia"===e||"tree"===e)}try{const s=await fetch(`https://metaverseds.geovisearth.com/api/v1/city/info?name=${e}`);if(!s.ok)throw new Error("网络响应不正常");let a=await s.json();if(0===a.length)return new Map;var n;if(t&&t.length>0){const e=new Set;n=a.filter(n=>{const i=this.getTypeByFeature(n.feature);return!!t.includes(i)&&(e.add(i),!0)});t.filter(t=>!e.has(t)).forEach(e=>{})}else n=a.filter(e=>{const t=this.getTypeByFeature(e.feature);return"buia"===t||"tree"===t});var i=new Map;return n.forEach(e=>{const t=this.getTypeByFeature(e.feature);var n;"buia"===t?n=`https://api.open.geovisearth.com/pj/getfile/46/brainsim-3dtiles/${e.adcode}/V3/lod/bm/tileset.json`:"tree"===t&&(n=`https://api.open.geovisearth.com/pj/getfile/46/brainsim-3dtiles/${e.adcode}/V3/instance/tree/tileset.json`),i.set(`${e.name}_${e.adcode}_${t}`,n)}),i}catch(e){return new Map}}on(e,t){this.eventListeners.has(e)||this.eventListeners.set(e,[]),this.eventListeners.get(e).push(t)}emitEvent(e,t){this.eventListeners.has(e)&&this.eventListeners.get(e).forEach(e=>{try{e(t)}catch(e){}})}}class ne{viewer;tileset3dUrl="";currentTileset=null;container=null;statsContainer=null;cityList=[];tileCoordinatesLayer=null;render;constructor(e){if(!e&&!e.viewer)throw new Error("Render instance is required.");this.render=e,this.viewer=e.viewer,this.loadCityList().catch(e=>{})}initialize(){}enable(){}disable(){}update(e){}getStatus(){return!0}getConfig(){}saveConfig(){}async loadCityList(){try{const e=await fetch("https://metaverseds.geovisearth.com/api/v1/city/list");if(!e.ok)throw new Error(`HTTP error! status: ${e.status}`);const t=await e.json();Array.isArray(t)?this.cityList=t:200===t.code&&t.data&&(this.cityList=t.data,this.cityList.length)}catch(e){}}async getCityInfo(e){try{const t=await fetch(`https://metaverseds.geovisearth.com/api/v1/city/info?adcode=${e}`);if(!t.ok)throw new Error(`HTTP error! status: ${t.status}`);const n=await t.json();if(Array.isArray(n)&&n.length>0){return n[0]}if(200===n.code&&n.data)return n.data}catch(e){}return null}matchCity(e){e=e.trim().toLowerCase();const t=this.cityList.find(t=>t.adcode===e);if(t)return t;const n=this.cityList.find(t=>t.name.toLowerCase()===e);if(n)return n;const i=this.cityList.find(t=>t.name.toLowerCase().includes(e));return i||null}buildTilesetUrl(e){return`https://io-qos.geovisearth.com/getfile/46/brainsim-3dtiles/${e.adcode}/${e.version}/lod${e.lod}bm/tileset.json`}async loadTileset(e,t){try{this.removePreviousTileset(),this.tileset3dUrl=e;const n=await window.Cesium.Cesium3DTileset.fromUrl(e,{maximumScreenSpaceError:t?.maximumScreenSpaceError??80,maximumMemoryUsage:t?.maximumMemoryUsage??8192,cacheBytes:t?.cacheBytes??4294967296});this.viewer.scene.primitives.add(n),this.currentTileset=n,this.viewer.zoomTo(n)}catch(e){alert("加载3DTiles失败,请检查地址是否正确")}}async loadCityTileset(e,t){try{const n=this.matchCity(e);if(!n)return void alert("未找到匹配的城市,请检查输入的城市名称或编码");const i=await this.getCityInfo(n.adcode);if(!i||!i.version||!i.lod)return void alert("该城市暂无3DTiles数据");const s=this.buildTilesetUrl(i);await this.loadTileset(s,t)}catch(e){alert("加载城市3DTiles失败,请稍后重试")}}async loadByType(e,t,n){t.trim()?"url"===e?await this.loadTileset(t.trim(),n):await this.loadCityTileset(t.trim(),n):alert("请输入有效的值")}removePreviousTileset(){this.currentTileset&&(this.viewer.scene.primitives.remove(this.currentTileset),this.currentTileset=null)}getCurrentTileset(){return this.currentTileset}getCurrentUrl(){return this.tileset3dUrl}setContainer(e){this.container=e}setStatsContainer(e){this.statsContainer=e}showControl(){this.container&&(this.container.style.display="block")}hideControl(){this.container&&(this.container.style.display="none")}showStatsPanel(){this.statsContainer&&(this.statsContainer.style.display="block")}hideStatsPanel(){this.statsContainer&&(this.statsContainer.style.display="none")}isControlVisible(){return this.container&&"none"!==this.container.style.display}isStatsPanelVisible(){return this.statsContainer&&"none"!==this.statsContainer.style.display}setTilesetStyle(e){if(this.currentTileset)try{let t;t="string"==typeof e?this.getPredefinedStyle(e):e,this.currentTileset.style=new window.Cesium.Cesium3DTileStyle(t),this.currentTileset.style}catch(e){}}setStyleTest(e,t,n){this.render.getSystem("tileset").applyPropertyFilterStyle(this.currentTileset,e,n,t,'color("red")','color("white")')}getPredefinedStyle(e){const t={default:{},grayscale:{color:"rgb(178, 178, 178)"},blue:{color:"rgb(102, 153, 255)"},red:{color:"rgb(255, 128, 128)"},green:{color:"rgb(128, 204, 128)"},highlight:{color:"rgb(255, 230, 128)"}};return t[e]||t.default}getAvailableStyles(){return["default","grayscale","heatmap","blue","red","green","highlight"]}resetToDefaultStyle(){this.setTilesetStyle("default")}openInner3dtilesInspector(e){if(e)if(this.viewer.cesium3DTilesInspector&&this.viewer.cesium3DTilesInspector.viewModel){document.querySelectorAll(".cesium-viewer-cesium3DTilesInspectorContainer").forEach(e=>{e.style.display="block"})}else this.viewer.extend(E);else if(this.viewer.cesium3DTilesInspector&&this.viewer.cesium3DTilesInspector.viewModel){document.querySelectorAll(".cesium-viewer-cesium3DTilesInspectorContainer").forEach(e=>{e.style.display="none"})}}openInnerGrid(e){if(e){let e=new S({tilingScheme:new k});this.tileCoordinatesLayer=this.viewer.imageryLayers.addImageryProvider(e),this.viewer.imageryLayers.raiseToTop(this.tileCoordinatesLayer)}else if(this.tileCoordinatesLayer)try{this.viewer.imageryLayers.remove(this.tileCoordinatesLayer)}catch(e){}finally{this.tileCoordinatesLayer=null}}openLonLatGrid(e){}changeGridSize(e){}destroy(){this.removePreviousTileset()}}class ie{viewer;labelEntity=null;currentTile=null;mouseHandler=null;clippingPolygons=null;onBuildingClick;config;isEnabled;constructor(t,n={}){if(!t&&!t.viewer)throw new Error("Render instance is required.");this.viewer=t.viewer,this.config={enabled:!0,enableMouseMove:!0,enableLeftClick:!0,maxLabelLines:10,labelOffset:new e(0,0,10),showBillboard:!0,autoFlyTo:!1,onBuildingClick:()=>{},...n},this.isEnabled=this.config.enabled,this.onBuildingClick=this.config.onBuildingClick,this.init()}init(){this.createLabelEntity(),this.isEnabled&&this.setupMouseEvents()}createLabelEntity(){this.labelEntity=this.viewer.entities.add({name:"建筑信息标牌",label:{text:"",font:'14px "Microsoft YaHei", sans-serif',fillColor:t.WHITE,backgroundColor:new t(.08,.08,.08,.95),style:I.FILL_AND_OUTLINE,outlineWidth:3,outlineColor:t.BLACK,pixelOffset:new v(0,-25),horizontalOrigin:B.LEFT,verticalOrigin:L.BOTTOM,showBackground:!0,scale:1,heightReference:P.CLAMP_TO_GROUND,disableDepthTestDistance:Number.POSITIVE_INFINITY,backgroundPadding:new v(12,8)},billboard:{image:"data:image/svg+xml;base64,"+btoa('\n <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg">\n <rect width="24" height="24" fill="#1890ff" rx="4"/>\n </svg>\n '),width:24,height:24,verticalOrigin:L.BOTTOM,heightReference:P.CLAMP_TO_GROUND,show:this.config.showBillboard},show:!1})}setupMouseEvents(){this.mouseHandler&&(this.mouseHandler.destroy(),this.mouseHandler=null),this.mouseHandler=new T(this.viewer.scene.canvas),this.config.enableMouseMove&&this.mouseHandler.setInputAction(e=>{this.handleMouseMove(e)},_.MOUSE_MOVE),this.config.enableLeftClick&&this.mouseHandler.setInputAction(e=>{this.handleMouseClick(e)},_.LEFT_CLICK),this.mouseHandler.setInputAction(e=>{this.handleMouseLeave(e)},_.LEFT_UP)}handleMouseMove(e){if(!this.isEnabled||!this.config.enableMouseMove)return void this.hideLabel();const t=this.viewer.scene.drillPick(e.endPosition,10);if(t.length>0)for(let e of t)if(e.primitive&&this.isBuildingTile(e))return this.currentTile=e.primitive,void this.showBuildingLabel(e);this.hideLabel()}handleMouseClick(e){if(!this.isEnabled||!this.config.enableLeftClick)return;const t=this.viewer.scene.drillPick(e.position,10);if(t.length>0)for(let e of t)if(e.primitive&&this.isBuildingTile(e))return void this.onInnerBuildingClick(e);this.onEmptyClick()}handleMouseLeave(e){this.hideLabel()}onInnerBuildingClick(e){const t=e.primitive,n=this.extractBuildingProperties(t,e);this.showBuildingDetails(n);const i={};for(const[e,t]of Object.entries(n))"string"==typeof t?i[e]=t.replace(/^f_/,""):"number"==typeof t||"boolean"==typeof t?i[e]=t:Array.isArray(t)?i[e]=t.map(e=>"string"==typeof e?e.replace(/^f_/,""):e):i[e]=t;this.onBuildingClick(i,e)}onEmptyClick(){}showBuildingDetails(e){}isBuildingTile(e){return e.content&&(e.content.tile||e.content.batchTable||e.id?.includes("building")||e.id?.includes("B3DM"))}showBuildingLabel(e){if(!this.isEnabled||!this.config.enableMouseMove)return void this.hideLabel();const t=e.primitive,n=this.extractBuildingProperties(t,e),i=this.getTilePosition(t,e);i?(this.labelEntity.position=i,this.labelEntity.label.text=this.formatProperties(n),this.labelEntity.billboard.show=this.config.showBillboard,this.labelEntity.label.show=!0,this.labelEntity.show=!0,this.viewer.scene.requestRender()):this.hideLabel()}getTilePosition(t,n){try{if(n){const t=n.getProperty("lat"),i=n.getProperty("lon"),s=n.getProperty("f_height");if(t&&i){const n=e.fromDegrees(i,t,s||0);return new e(n.x,n.y,n.z)}}return null}catch(e){return null}}extractBuildingProperties(e,t){const n={};try{if(void 0!==t._batchId&&t.tileset){const e=t.tileset.properties;if(e&&"object"==typeof e){Object.keys(e).forEach(e=>{try{const i=t.getProperty(e);null!=i&&(n[e]=i)}catch(e){}})}}if(0===Object.keys(n).length&&e.content&&e.content.metadata){const t=e.content.metadata;t.getPropertyNames&&t.getPropertyNames().forEach(e=>{n[e]=t.getProperty(e)})}n.height=this.getBuildingHeight(e),n.area=this.getBuildingArea(e)}catch(e){}return n}getBuildingHeight(e){return e.boundingSphere?2*e.boundingSphere.radius:0}getBuildingArea(e){if(e.boundingSphere){const t=e.boundingSphere.radius;return Math.PI*t*t}return 0}formatProperties(e){if(!e||0===Object.keys(e).length)return"🏢 建筑信息";let t="";const n=this.config.maxLabelLines;let i=0;const s=Object.entries(e).sort(([e],[t])=>{const n=this.getPropertyPriority(e);return this.getPropertyPriority(t)-n});for(const[e,a]of s){if(i>=n)break;t+=`${this.formatPropertyName(e)}: ${this.formatPropertyValue(a)}\n`,i++}return Object.keys(e).length>n&&(t+=`... 还有${Object.keys(e).length-n}个属性`),t.trim()}getPropertyPriority(e){return{id:100,_id:95,f_name:90,class:85,f_height:80,lon:70,lat:60,feature_type:50}[e]||0}formatPropertyName(e){return{_id:"矢量ID",f_name:"名称",f_height:"高度",height:"海拔",area:"面积",class:"类型"}[e]||e}formatPropertyValue(e){if(null==e)return"-";if("number"==typeof e)return e>1e3?Math.round(e).toLocaleString():e.toString();if("boolean"==typeof e)return e?"是":"否";const t=String(e);return t.length>15?t.substring(0,15)+"...":t}hideLabel(){this.labelEntity&&(this.labelEntity.show=!1,this.labelEntity.label.show=!1,this.labelEntity.billboard.show=!1),this.currentTile=null}async loadClippingData(t,n){try{let i;if("string"==typeof t){const e=await fetch(t);i=await e.json()}else i=t;if(!i.features||0===i.features.length)return[];const s=[],a=[];for(const t of i.features)if(t.geometry&&"Polygon"===t.geometry.type){const n=t.geometry.coordinates;s.push(n);const i=n[0].map(t=>e.fromDegrees(t[0],t[1],0));a.push(new F({positions:i}))}return this.clippingPolygons=new M({polygons:a}),n.clippingPolygons=this.clippingPolygons,s}catch(e){return[]}}removeClippingPolygons(e){e&&e.clippingPolygons&&(e.clippingPolygons=null),this.clippingPolygons=null}getClippingPolygons(){return this.clippingPolygons}setEnabled(e){this.isEnabled!==e&&(this.isEnabled=e,e?this.setupMouseEvents():(this.mouseHandler&&(this.mouseHandler.destroy(),this.mouseHandler=null),this.hideLabel()))}getEnabled(){return this.isEnabled}toggleEnabled(){return this.setEnabled(!this.isEnabled),this.isEnabled}updateConfig(e){this.config={...this.config,...e},this.isEnabled&&this.setupMouseEvents(),this.labelEntity&&(this.labelEntity.billboard.show=this.config.showBillboard)}getConfig(){return{...this.config}}applyPropertyFilterStyle(e,t,n,i=">",s='color("red")',a='color("white")'){const o=`(\${${t}} !== undefined && \${${t}} ${i} ${n})`;e.style=new m({color:{conditions:[[o,s],["true",a]]}})}applyRangeFilterStyle(e,t,n,i,s='color("red")',a='color("white")'){const o=`(\${${t}} !== undefined && \${${t}} >= ${n}) && (\${${t}} <= ${i})`;e.style=new m({color:{conditions:[[o,s],["true",a]]}})}removeStyle(e){e.style=null}destroy(){this.mouseHandler&&(this.mouseHandler.destroy(),this.mouseHandler=null),this.labelEntity&&this.viewer.entities.remove(this.labelEntity),this.clippingPolygons=null}}class se{engine;viewer;config;lightingSystem;shadowSystem;volumetricCloudsSystem;atmosphereScatteringSystem;distanceFogSystem;heightFogSystem;postProcessingSystem;cameraListenerSystem;waterSystem;layerSystem;toolboxSystem;measurementSystem;sceneSystem;buildingSystem;tilesetSearchSystem;tilesetManager;systems=new Map;constructor(e){if(!e||!e.token)throw new Error("[Render] token 校验失败: 缺少访问 LACDT 资源的 token");if("da7ef77cc10ff030a35835f4f7a0936b056339ce5ce9e795e417f2bf86a42172"==e.token)this.init(e);else{fetch("https://metaverse144.geovisearth.com/user/ck/wbtk",{headers:{Authorization:`Bearer ${e.token}`}}).then(e=>{if(!e.ok)throw new Error("[Render] token 校验失败: HTTP 错误 "+e.status);return e.json()}).then(t=>{"ok"!==t.data.status||this.init(e)}).catch(e=>{})}}init(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}),e.water||(e.water={enabled:!1}),null==e.visible&&(e.visible=!0),this.engine=e.engine,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 V(this.viewer,e.cameraListener),this.cameraListenerSystem.initialize(),this.lightingSystem=new D(this.viewer,e.directionalLight),this.shadowSystem=new $(this.viewer,e.shadow),this.volumetricCloudsSystem=new H(this.viewer,e.volumetricClouds),this.atmosphereScatteringSystem=new q(this.viewer,e.atmosphereScattering),this.distanceFogSystem=new O(this.viewer,e.distanceFog),this.heightFogSystem=new G(this.viewer,e.heightFog),this.postProcessingSystem=new j(this.viewer,e.postProcessing),this.waterSystem=new Y(this.viewer,e.water),this.sceneSystem=new ee(this),this.layerSystem=new X(this),this.buildingSystem=new te(this),this.tilesetSearchSystem=new ne(this),this.tilesetManager=new ie(this,{enabled:!0,enableLeftClick:!0}),this.engine&&(this.toolboxSystem=new Z(this),this.measurementSystem=new K(this.engine)),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(),e.water?.enabled&&this.waterSystem.enable(),this.initialize()}registerSystems(){[{key:"lighting",system:this.lightingSystem},{key:"shadow",system:this.shadowSystem},{key:"volumetricClouds",system:this.volumetricCloudsSystem},{key:"atmosphereScattering",system:this.atmosphereScatteringSystem},{key:"distanceFog",system:this.distanceFogSystem},{key:"heightFog",system:this.heightFogSystem},{key:"postProcessing",system:this.postProcessingSystem},{key:"water",system:this.waterSystem},{key:"layer",system:this.layerSystem},{key:"toolbox",system:this.toolboxSystem},{key:"measurement",system:this.measurementSystem},{key:"scene",system:this.sceneSystem},{key:"building",system:this.buildingSystem},{key:"tilesetSearch",system:this.tilesetSearchSystem},{key:"tileset",system:this.tilesetManager}].forEach(e=>{e.system&&this.systems.set(e.key,e.system)})}initialize(){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.water?.enabled&&this.waterSystem.enable(),this.config.enableAll&&this.enableAllSystems()}enableAllSystems(){this.systems.forEach((e,t)=>{e.enable()})}disableAllSystems(){this.systems.forEach((e,t)=>{e.disable()})}getSystem(e){const t=this.systems.get(e);return t}enableSystem(e){const t=this.systems.get(e);t&&t.enable()}disableSystem(e){const t=this.systems.get(e);t&&t.disable()}getSystemStatus(e){const t=this.systems.get(e);return t?.getStatus()}updateSystemConfig(e,t){const n=this.systems.get(e);n&&(!0===t.enabled?n.enable():!1===t.enabled&&n.disable(),n.update(t))}getSystemConfig(e){const t=this.systems.get(e);return t&&"getConfig"in t?t.getConfig():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(){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]=J.cleanConfig(i)}catch(e){}else if(t.getConfig){const i=t.getConfig();e.systems[n]=J.cleanConfig(i)}}),e}saveConfig(e="render-config.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]=J.cleanConfig(i)}catch(e){}else if(e.getConfig){const i=e.getConfig();t.systems[n]=J.cleanConfig(i)}});const n=JSON.stringify(t,null,2),i=new Blob([n],{type:"application/json"}),s=URL.createObjectURL(i),a=document.createElement("a");return a.href=s,a.download=e,document.body.appendChild(a),a.click(),setTimeout(()=>{document.body.removeChild(a),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){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.water&&this.updateSystemConfig("water",e.water),e.enableAll&&this.enableAllSystems()}readConfigContent(e){let t;if("string"==typeof e)try{t=JSON.parse(e)}catch(e){throw new Error("[Render] JSON 解析错误: "+e)}else t=e;t.version&&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=J.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")),t.systems.water&&(n.water=this.parseSystemConfig(t.systems.water,"water"))}return n}parseSystemConfig(t,n){if(!t)return null;const i={...t};switch(t.color&&(i.color=J.deserializeColor(t.color)),t.lightColor&&(i.lightColor=J.deserializeColor(t.lightColor)),t.fogColor&&(i.fogColor=J.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 v(t.shadowMapSize[0]||t.shadowMapSize.x||2048,t.shadowMapSize[1]||t.shadowMapSize.y||2048))}return i}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,water:this.waterSystem,layer:this.layerSystem,toolbox:this.toolboxSystem,measurement:this.measurementSystem,scene:this.sceneSystem,building:this.buildingSystem,tilesetSearch:this.tilesetSearchSystem}}destroy(){this.systems.forEach((e,t)=>{e.destroy&&e.destroy()}),this.systems.clear(),this.cameraListenerSystem.destroy()}}const ae=[{id:"morning-clear",name:"清晨晴朗",description:"清新的早晨,阳光温和,无云无雾",config:{time:{hour:9,minute:0},lighting:{enabled:!0,followSun:!0,intensity:1.2,ambientIntensity:.35,color:"#fff5e6"},shadow:{enabled:!0,softShadows:!0,size:4096,darkness:.25,softness:3},atmosphere:{enabled:!0,intensity:1.2,rayleighIntensity:.4,mieIntensity:.12,absorptionIntensity:.7},clouds:{enabled:!1,cover:.1,base:2e3,top:4e3,windSpeed:10,lightIntensity:3},fog:{enabled:!1,type:"none",distanceIntensity:0,heightIntensity:0,color:"#ffffff"},postProcessing:{enabled:!0,brightness:1,contrast:1.05,saturation:1.2,gamma:.5},water:{enabled:!1,color:"#4A90E2",opacity:.8,waveStrength:.5,waveSpeed:1}}},{id:"afternoon-cloudy",name:"午后多云",description:"午后的天空,云层丰富,光影变化",config:{time:{hour:14,minute:0},lighting:{enabled:!0,followSun:!0,intensity:1.5,ambientIntensity:.4,color:"#ffffff"},shadow:{enabled:!0,softShadows:!0,size:8192,darkness:.3,softness:4},atmosphere:{enabled:!0,intensity:1.4,rayleighIntensity:.35,mieIntensity:.2,absorptionIntensity:.75},clouds:{enabled:!0,cover:.5,base:1500,top:5e3,windSpeed:30,lightIntensity:5.5},fog:{enabled:!1,type:"none",distanceIntensity:0,heightIntensity:0,color:"#e8e8e8"},postProcessing:{enabled:!0,brightness:1.05,contrast:1.1,saturation:1.3,gamma:.5},water:{enabled:!1,color:"#4A90E2",opacity:.8,waveStrength:.5,waveSpeed:1}}},{id:"sunset-foggy",name:"黄昏薄雾",description:"夕阳西下,薄雾笼罩,温暖静谧",config:{time:{hour:17,minute:30},lighting:{enabled:!0,followSun:!0,intensity:1,ambientIntensity:.3,color:"#ffcc80"},shadow:{enabled:!0,softShadows:!0,size:4096,darkness:.35,softness:5},atmosphere:{enabled:!0,intensity:1.5,rayleighIntensity:.5,mieIntensity:.3,absorptionIntensity:.8},clouds:{enabled:!0,cover:.3,base:1e3,top:3e3,windSpeed:15,lightIntensity:4.5},fog:{enabled:!0,type:"distance",distanceIntensity:.4,heightIntensity:0,color:"#f5e6d3"},postProcessing:{enabled:!0,brightness:.95,contrast:1.15,saturation:1.4,gamma:.55},water:{enabled:!1,color:"#4A90E2",opacity:.8,waveStrength:.5,waveSpeed:1}}},{id:"night-starry",name:"夜间星空",description:"晴朗的夜晚,星空璀璨",config:{time:{hour:21,minute:0},lighting:{enabled:!0,followSun:!0,intensity:.3,ambientIntensity:.1,color:"#b0c4de"},shadow:{enabled:!1,softShadows:!1,size:2048,darkness:.5,softness:2},atmosphere:{enabled:!0,intensity:.5,rayleighIntensity:.2,mieIntensity:.1,absorptionIntensity:.5},clouds:{enabled:!1,cover:0,base:2e3,top:4e3,windSpeed:5,lightIntensity:1},fog:{enabled:!1,type:"none",distanceIntensity:0,heightIntensity:0,color:"#2a2a3a"},postProcessing:{enabled:!0,brightness:.8,contrast:1.2,saturation:.8,gamma:.6},water:{enabled:!1,color:"#2a4a6a",opacity:.6,waveStrength:.3,waveSpeed:.5}}},{id:"noon-bright",name:"正午阳光",description:"明亮的中午,阳光强烈",config:{time:{hour:12,minute:0},lighting:{enabled:!0,followSun:!0,intensity:1.8,ambientIntensity:.5,color:"#ffffff"},shadow:{enabled:!0,softShadows:!0,size:8192,darkness:.2,softness:2},atmosphere:{enabled:!0,intensity:1.3,rayleighIntensity:.4,mieIntensity:.2,absorptionIntensity:.7},clouds:{enabled:!1,cover:0,base:2e3,top:4e3,windSpeed:20,lightIntensity:5},fog:{enabled:!1,type:"none",distanceIntensity:0,heightIntensity:0,color:"#ffffff"},postProcessing:{enabled:!0,brightness:1.1,contrast:1,saturation:1.5,gamma:.45},water:{enabled:!1,color:"#4A90E2",opacity:.85,waveStrength:.6,waveSpeed:1.2}}},{id:"overcast-rainy",name:"阴天雨天",description:"阴沉的天空,厚云低垂",config:{time:{hour:10,minute:0},lighting:{enabled:!0,followSun:!0,intensity:.8,ambientIntensity:.45,color:"#d3d3d3"},shadow:{enabled:!1,softShadows:!0,size:2048,darkness:.4,softness:3},atmosphere:{enabled:!0,intensity:1.2,rayleighIntensity:.3,mieIntensity:.4,absorptionIntensity:.95},clouds:{enabled:!0,cover:.85,base:800,top:2500,windSpeed:50,lightIntensity:4},fog:{enabled:!0,type:"both",distanceIntensity:.5,heightIntensity:.3,color:"#c8c8c8"},postProcessing:{enabled:!0,brightness:.9,contrast:.95,saturation:.85,gamma:.55},water:{enabled:!1,color:"#5a7a9a",opacity:.9,waveStrength:.8,waveSpeed:2}}}],oe="morning-clear";class re{static instance;currentConfig;currentPresetId=null;listeners=new Set;customPresets=[];constructor(){const e=ae.find(e=>e.id===oe);e?(this.currentConfig=this.deepClone(e.config),this.currentPresetId=oe):(this.currentConfig=this.deepClone(ae[0].config),this.currentPresetId=ae[0].id)}static getInstance(){return re.instance||(re.instance=new re),re.instance}static resetInstance(){re.instance=null}getAllPresets(){return[...ae,...this.customPresets]}getBuiltinPresets(){return[...ae]}getCustomPresets(){return[...this.customPresets]}getPresetById(e){return this.getAllPresets().find(t=>t.id===e)}applyPreset(e){const t=this.getPresetById(e);return!!t&&(this.currentConfig=this.deepClone(t.config),this.currentPresetId=e,this.notifyListeners(),!0)}getCurrentConfig(){return this.deepClone(this.currentConfig)}getCurrentConfigRef(){return this.currentConfig}getCurrentPresetId(){return this.currentPresetId}getCurrentPreset(){return this.currentPresetId&&this.getPresetById(this.currentPresetId)||null}isPresetMode(){return null!==this.currentPresetId}updateSystemConfig(e,t){this.currentConfig[e]={...this.currentConfig[e],...t},this.currentPresetId=null,this.notifyListeners()}updateConfig(e){Object.keys(e).forEach(t=>{this.currentConfig[t]&&e[t]&&(this.currentConfig[t]={...this.currentConfig[t],...e[t]})}),this.currentPresetId=null,this.notifyListeners()}setFullConfig(e,t=null){this.currentConfig=this.deepClone(e),this.currentPresetId=t,this.notifyListeners()}exportToJson(){const e={version:"1.0.0",presetId:this.currentPresetId,config:this.currentConfig,exportedAt:(new Date).toISOString()};return JSON.stringify(e,null,2)}exportToBlob(){const e=this.exportToJson();return new Blob([e],{type:"application/json"})}importFromJson(e){try{const t=JSON.parse(e);return!!t.config&&(!!this.validateConfig(t.config)&&(this.currentConfig=this.deepClone(t.config),this.currentPresetId=t.presetId||null,this.notifyListeners(),!0))}catch(e){return!1}}validateConfig(e){const t=["lighting","shadow","atmosphere","clouds","fog","postProcessing","water"];for(const n of t)if(!e[n]||"object"!=typeof e[n])return!1;return!0}reset(e=oe){return this.applyPreset(e)}resetToDefault(){this.applyPreset(oe)}enableAllEffects(){this.updateConfig({lighting:{...this.currentConfig.lighting,enabled:!0},shadow:{...this.currentConfig.shadow,enabled:!0},atmosphere:{...this.currentConfig.atmosphere,enabled:!0},clouds:{...this.currentConfig.clouds,enabled:!0},fog:{...this.currentConfig.fog,enabled:!0},postProcessing:{...this.currentConfig.postProcessing,enabled:!0}})}disableAllEffects(){this.updateConfig({lighting:{...this.currentConfig.lighting,enabled:!1},shadow:{...this.currentConfig.shadow,enabled:!1},atmosphere:{...this.currentConfig.atmosphere,enabled:!1},clouds:{...this.currentConfig.clouds,enabled:!1},fog:{...this.currentConfig.fog,enabled:!1},postProcessing:{...this.currentConfig.postProcessing,enabled:!1}})}addListener(e){return this.listeners.add(e),()=>this.listeners.delete(e)}removeListener(e){this.listeners.delete(e)}clearListeners(){this.listeners.clear()}notifyListeners(){const e=this.getCurrentConfig(),t=this.currentPresetId;this.listeners.forEach(n=>{try{n(e,t)}catch(e){}})}deepClone(e){return JSON.parse(JSON.stringify(e))}addCustomPreset(e){this.getPresetById(e.id)||this.customPresets.push(e)}removeCustomPreset(e){const t=this.customPresets.findIndex(t=>t.id===e);return t>-1&&(this.customPresets.splice(t,1),!0)}saveAsPreset(e,t=""){const n=`custom-${Date.now()}`,i={id:n,name:e,description:t,config:this.getCurrentConfig()};return this.addCustomPreset(i),this.currentPresetId=n,i}}const le=re.getInstance(),de={enabled:!0,followSun:!0,intensity:1.37,ambientIntensity:.32,color:t.fromCssColorString("#ffffff"),direction:new e(-.07,-.74,.51)},ce={enabled:!0,size:8192,softShadows:!0,softness:3,darkness:.29,shadowColor:new e(.012,.078,.227),shadowBlend:.01,maximumDistance:5e3,normalOffset:!0,fadingEnabled:!0,bias:1e-4},he={enabled:!0,enableGroundMix:!0,atmosphereIntensity:1.64,rayleighIntensity:.49,baseBetaR_R:4e-7,baseBetaR_G:49e-7,baseBetaR_B:102e-7,hR:7200,mieIntensity:.19,baseBetaM_R:208e-7,baseBetaM_G:182e-7,baseBetaM_B:26e-6,hM:1400,absorptionIntensity:.87,baseBetaA_R:364e-7,baseBetaA_G:416e-7,baseBetaA_B:485e-8,hA:16e3,absorption_falloff:6e3,groundHeight:613e4,atmosphereThickness:49e3,bDensity:.01,bColor:t.fromCssColorString("#b6d3f5")};new e(5,0,0);const pe={density:4e-4,heightFalloff:.3,color:t.fromCssColorString("#ffffff")},ue={enabled:!0,startDistance:10,startIntensity:0,endDistance:5e4,endIntensity:1,fogByDistance:new a(10,.003,1138,.1),fogColor:t.fromCssColorString("#dcdcdc")},ge={enabled:!0,startHeight:0,endHeight:1138,density:.008,fogByHeight:new a(545.455,0,1138,.008),fogColor:t.WHITE,visibility:{enabled:!0,minHeight:0,maxHeight:1e3,transitionRange:200}},me={enabled:!0,hdrEnabled:!0,fxaaEnabled:!0,msaaEnabled:!0,msaa:8,brightness:1.02,contrast:1,saturation:1.75,gamma:.5,temperature:6820,tint:-.28,shadowColor:new e(.012,.078,.227),shadowBlend:.01},be={normal:{brightness:1.02,contrast:1,saturation:1.75,gamma:.5,temperature:6730,tint:-.39},warm:{brightness:1,contrast:1.1,saturation:1.1,gamma:1,temperature:7e3,tint:-.1},cool:{brightness:1.1,contrast:.9,saturation:1.1,gamma:1,temperature:5900,tint:.11},vibrant:{brightness:1,contrast:.8,saturation:1.7,gamma:1,temperature:6850,tint:-.5},desaturated:{brightness:1,contrast:1,saturation:.3,gamma:1,temperature:6500,tint:0},"high-contrast":{brightness:1,contrast:1.5,saturation:1,gamma:1,temperature:6500,tint:0}};t.fromCssColorString("#4A90E2");const fe={sunrise:{hour:6,minute:0,label:"日出 06:00"},morning:{hour:9,minute:0,label:"早晨 09:00"},noon:{hour:12,minute:0,label:"正午 12:00"},afternoon:{hour:15,minute:0,label:"下午 15:00"},sunset:{hour:18,minute:0,label:"日落 18:00"}};function ye(e){if("string"==typeof e)return e;if(e instanceof t){const t=e=>Math.round(255*e).toString(16).padStart(2,"0");return`#${t(e.red)}${t(e.green)}${t(e.blue)}`}if(e&&"red"in e){const t=e=>Math.round(255*e).toString(16).padStart(2,"0");return`#${t(e.red)}${t(e.green)}${t(e.blue)}`}return"#ffffff"}function ve(e){const t=e=>Math.round(255*e).toString(16).padStart(2,"0");return`#${t(e.x)}${t(e.y)}${t(e.z)}`}function xe(t){const n=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(t);return n?new e(parseInt(n[1],16)/255,parseInt(n[2],16)/255,parseInt(n[3],16)/255):new e(1,1,1)}function we(e){return e.toExponential(2)}class Ce{viewer;render;renderConfig;configUnsubscribe=null;constructor(e,t,n){this.viewer=e,this.render=t,this.renderConfig=n,this.setupConfigListener()}setupConfigListener(){this.configUnsubscribe=le.addListener((e,t)=>{this.onConfigUpdated(e)}),document.addEventListener("render-config-updated",e=>{this.onConfigUpdated(e.detail.config)})}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}createToggleControl(e,t,n,i){const s=document.createElement("div");return s.className="control-row",s.innerHTML=`\n <span class="control-label">${t}</span>\n <label class="toggle-switch">\n <input type="checkbox" id="${e}" class="toggle-checkbox" ${n?"checked":""}>\n <span class="toggle-slider"></span>\n </label>\n `,s}createSliderControl(e,t,n,i,s,a=.01){const o=document.createElement("div");return o.className="control-row",o.innerHTML=`\n <span class="control-label">${t}</span>\n <div class="slider-container">\n <input type="range" id="${e}" class="slider" \n value="${n}" min="${i}" max="${s}" step="${a}">\n </div>\n <span class="control-value" id="${e}-value">${n.toFixed(2)}</span>\n `,o}updateControlsEnabled(e,t,n=null){t?.forEach(t=>{const n=document.getElementById(t);n&&(n.disabled=!e,this.updateSliderVisual(n))}),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%)`}onConfigUpdated(e){this.renderConfig=this.render.config,this.refreshControls(e)}getCurrentConfig(){return le.getCurrentConfig()}updateSystemConfig(e,t){le.updateSystemConfig(e,t)}colorToHex(e){return ye(e)}cartesian3ToHex(e){return ve(e)}hexToCartesian3(e){return xe(e)}updateAllSliderVisuals(){document.querySelectorAll('input[type="range"]').forEach(e=>{this.updateSliderVisual(e)})}destroy(){this.configUnsubscribe&&(this.configUnsubscribe(),this.configUnsubscribe=null)}}class Ee extends Ce{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"),a=document.getElementById("cloud-top"),o=document.getElementById("cloud-thickness"),r=document.getElementById("wind-speed"),l=document.getElementById("cloud-light-intensity"),d=document.getElementById("cloud-min-height"),c=document.getElementById("cloud-max-height"),h=document.getElementById("cloud-transition-range"),p=this.container.querySelectorAll(".preset-btn[data-preset]");this.updateControlsEnabled(this.renderConfig.volumetricClouds.enabled,this.disableSliders,p),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,p)}),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)}),a.addEventListener("input",()=>{document.getElementById("cloud-top-value").textContent=a.value,this.render.updateSystemConfig("volumetricClouds",{cloudTop:parseFloat(a.value)}),this.updateSliderVisual(a)}),o.addEventListener("input",()=>{document.getElementById("cloud-thickness-value").textContent=o.value;const e=parseFloat(s.value)+parseFloat(o.value);document.getElementById("cloud-top-value").textContent=e.toString(),a.value=e.toString(),this.render.updateSystemConfig("volumetricClouds",{cloudTop:e}),this.updateSliderVisual(a),this.updateSliderVisual(o)}),r.addEventListener("input",()=>{document.getElementById("wind-speed-value").textContent=r.value,this.render.updateSystemConfig("volumetricClouds",{windVector:new e(parseFloat(r.value),0,0)}),this.updateSliderVisual(r)}),l.addEventListener("input",()=>{document.getElementById("cloud-light-intensity-value").textContent=l.value,this.render.updateSystemConfig("volumetricClouds",{cloudLightIntensity:parseFloat(l.value)}),this.updateSliderVisual(l)}),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)}),h.addEventListener("input",()=>{document.getElementById("cloud-transition-range-value").textContent=h.value,this.cloudVisibilityParams.transitionRange=parseFloat(h.value),this.updateSliderVisual(h)});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(o),this.updateSliderVisual(r),this.updateSliderVisual(l),this.updateSliderVisual(d),this.updateSliderVisual(c),this.updateSliderVisual(h)}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,a,o){const r=document.getElementById("cloud-cover"),l=document.getElementById("cloud-base"),d=document.getElementById("cloud-top"),c=document.getElementById("cloud-thickness"),h=document.getElementById("wind-speed"),p=document.getElementById("cloud-light-intensity");r.value=t.toString(),document.getElementById("cloud-cover-value").textContent=t.toString(),l.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(),h.value=a.toString(),document.getElementById("wind-speed-value").textContent=a.toString(),p.value=o.toString(),document.getElementById("cloud-light-intensity-value").textContent=o.toString(),this.render.updateSystemConfig("volumetricClouds",{cloudCover:t,cloudLightIntensity:o,windVector:new e(a,0,0)}),this.updateSliderVisual(r),this.updateSliderVisual(l),this.updateSliderVisual(d),this.updateSliderVisual(c),this.updateSliderVisual(h),this.updateSliderVisual(p)}setCloudHeightValues(e,t,n){const i=document.getElementById("cloud-min-height"),s=document.getElementById("cloud-max-height"),a=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(),a.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(a)}refreshControls(e){let t=null;e&&(e.clouds?t={enabled:e.clouds.enabled,cloudCover:e.clouds.cover,cloudBase:e.clouds.base,cloudTop:e.clouds.top,cloudLightIntensity:e.clouds.lightIntensity,windSpeed:e.clouds.windSpeed}:e.systems&&e.systems.volumetricClouds?t=e.systems.volumetricClouds:e.volumetricClouds&&(t=e.volumetricClouds)),t||(t=this.render.getSystemConfig("volumetricClouds")||{}),this.updateControlsFromConfig(t)}updateControlsFromConfig(t){const n=document.getElementById("clouds-enabled"),i=document.getElementById("cloud-cover"),s=document.getElementById("cloud-cover-value"),a=document.getElementById("cloud-base"),o=document.getElementById("cloud-base-value"),r=document.getElementById("cloud-top"),l=document.getElementById("cloud-top-value"),d=document.getElementById("cloud-thickness"),c=document.getElementById("cloud-thickness-value"),h=document.getElementById("wind-speed"),p=document.getElementById("wind-speed-value"),u=document.getElementById("cloud-light-intensity"),g=document.getElementById("cloud-light-intensity-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&&a&&(a.value=t.cloudBase.toString(),o&&(o.textContent=t.cloudBase.toFixed(0)),this.updateSliderVisual(a)),void 0!==t.cloudTop&&r&&(r.value=t.cloudTop.toString(),l&&(l.textContent=t.cloudTop.toFixed(0)),this.updateSliderVisual(r)),a&&r&&d){const e=parseFloat(a.value),t=parseFloat(r.value)-e;d.value=t.toString(),c&&(c.textContent=t.toFixed(0)),this.updateSliderVisual(d)}if(h){let n=5;t.windVector?(t.windVector instanceof e||void 0!==t.windVector.x)&&(n=t.windVector.x):void 0!==t.windSpeed&&(n=t.windSpeed),h.value=n.toString(),p&&(p.textContent=n.toFixed(0)),this.updateSliderVisual(h)}void 0!==t.cloudLightIntensity&&u&&(u.value=t.cloudLightIntensity.toString(),g&&(g.textContent=t.cloudLightIntensity.toFixed(1)),this.updateSliderVisual(u)),this.updateControlsEnabledState(),this.viewer.scene.requestRender()}}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 t={enabled:!0,windVector:new e(5,0,0),cloudCover:.39,cloudBase:2e3,cloudTop:6e3,cloudThickness:4e3,cloudLightIntensity:5.3,cloudIntensity:1,visibility:{minHeight:0,maxHeight:7700,transitionRange:8500,enabled:!0}};this.render.updateSystemConfig("volumetricClouds",t),this.refreshControls(null)}reset(){this.applyDefaultPresets(),this.updateAllSliderVisuals()}getContainer(){return this.container}}class Se extends Ce{container;disableSliders=["atmosphere-intensity","rayleigh-intensity","mie-intensity","absorption-intensity","baseBetaR-R","baseBetaR-G","baseBetaR-B","baseBetaM-R","baseBetaM-G","baseBetaM-B","baseBetaA-R","baseBetaA-G","baseBetaA-B","hR","hM","hA","absorption-falloff","groundHeight","atmosphereThickness","enableGroundMix"];constructor(e,t,n){super(e,t,n)}createPanel(e){const t=this.createControlSection("大气散射"),n=he;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 <label class="toggle-switch">\n <input type="checkbox" id="enableGroundMix" 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">${n.atmosphereIntensity.toFixed(2)}</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="${n.atmosphereIntensity}" 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="rayleigh-intensity-value">${n.rayleighIntensity.toFixed(2)}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="rayleigh-intensity" min="0" max="2" step="0.01" value="${n.rayleighIntensity}" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-row">\n <label class="control-label">瑞利散射系数-R通道</label>\n <span class="control-value" id="baseBetaR-R-value">${we(n.baseBetaR_R)}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="baseBetaR-R" min="0" max="1e-5" step="1e-7" value="${n.baseBetaR_R}" class="slider">\n </div>\n </div>\n <div class="control-row">\n <label class="control-label">瑞利散射系数-G通道</label>\n <span class="control-value" id="baseBetaR-G-value">${we(n.baseBetaR_G)}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="baseBetaR-G" min="0" max="3e-5" step="1e-7" value="${n.baseBetaR_G}" class="slider">\n </div>\n </div>\n <div class="control-row">\n <label class="control-label">瑞利散射系数-B通道</label>\n <span class="control-value" id="baseBetaR-B-value">${we(n.baseBetaR_B)}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="baseBetaR-B" min="0" max="5e-5" step="1e-7" value="${n.baseBetaR_B}" class="slider">\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="hR-value">${n.hR}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="hR" min="1000" max="20000" step="100" value="${n.hR}" 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="mie-intensity-value">${n.mieIntensity.toFixed(2)}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="mie-intensity" min="0" max="2" step="0.01" value="${n.mieIntensity}" class="slider">\n </div>\n </div>\n </div>\n \n <div class="control-row">\n <label class="control-label">米氏散射系数-R通道</label>\n <span class="control-value" id="baseBetaM-R-value">${we(n.baseBetaM_R)}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="baseBetaM-R" min="0" max="1e-4" step="1e-7" value="${n.baseBetaM_R}" class="slider">\n </div>\n </div>\n <div class="control-row">\n <label class="control-label">米氏散射系数-G通道</label>\n <span class="control-value" id="baseBetaM-G-value">${we(n.baseBetaM_G)}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="baseBetaM-G" min="0" max="1e-4" step="1e-7" value="${n.baseBetaM_G}" class="slider">\n </div>\n </div>\n <div class="control-row">\n <label class="control-label">米氏散射系数-B通道</label>\n <span class="control-value" id="baseBetaM-B-value">${we(n.baseBetaM_B)}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="baseBetaM-B" min="0" max="1e-4" step="1e-7" value="${n.baseBetaM_B}" class="slider">\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="hM-value">${n.hM}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="hM" min="500" max="10000" step="100" value="${n.hM}" 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="absorption-intensity-value">${n.absorptionIntensity.toFixed(2)}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="absorption-intensity" min="0" max="2" step="0.01" value="${n.absorptionIntensity}" class="slider">\n </div>\n </div>\n </div>\n <div class="control-row">\n <label class="control-label">吸收系数-R通道</label>\n <span class="control-value" id="baseBetaA-R-value">${we(n.baseBetaA_R)}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="baseBetaA-R" min="0" max="1e-4" step="1e-7" value="${n.baseBetaA_R}" class="slider">\n </div>\n </div>\n <div class="control-row">\n <label class="control-label">吸收系数-G通道</label>\n <span class="control-value" id="baseBetaA-G-value">${we(n.baseBetaA_G)}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="baseBetaA-G" min="0" max="1e-4" step="1e-7" value="${n.baseBetaA_G}" class="slider">\n </div>\n </div>\n <div class="control-row">\n <label class="control-label">吸收系数-B通道</label>\n <span class="control-value" id="baseBetaA-B-value">${we(n.baseBetaA_B)}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="baseBetaA-B" min="0" max="1e-5" step="1e-8" value="${n.baseBetaA_B}" class="slider">\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="absorption-falloff-value">${n.absorption_falloff}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="absorption-falloff" min="1000" max="10000" step="100" value="${n.absorption_falloff}" 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="hA-value">${n.hA}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="hA" min="10000" max="60000" step="1000" value="${n.hA}" 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="groundHeight-value">${n.groundHeight}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="groundHeight" min="6000000" max="6500000" step="10000" value="${n.groundHeight}" 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="atmosphereThickness-value">${n.atmosphereThickness}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="atmosphereThickness" min="10000" max="100000" step="1000" value="${n.atmosphereThickness}" 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"),i=document.getElementById("rayleigh-intensity"),s=document.getElementById("mie-intensity"),a=document.getElementById("absorption-intensity"),o=document.getElementById("baseBetaR-R"),r=document.getElementById("baseBetaR-G"),l=document.getElementById("baseBetaR-B"),d=document.getElementById("baseBetaM-R"),c=document.getElementById("baseBetaM-G"),h=document.getElementById("baseBetaM-B"),p=document.getElementById("baseBetaA-R"),u=document.getElementById("baseBetaA-G"),g=document.getElementById("baseBetaA-B"),m=document.getElementById("hR"),b=document.getElementById("hM"),f=document.getElementById("hA"),y=document.getElementById("absorption-falloff"),v=document.getElementById("groundHeight"),x=document.getElementById("atmosphereThickness"),w=document.getElementById("enableGroundMix"),C=this.renderConfig.atmosphereScattering?.enabled??!1;t.checked=C,w.checked=this.renderConfig.atmosphereScattering?.enableGroundMix??!0,this.updateControlsEnabled(C,this.disableSliders),t.addEventListener("change",()=>{t.checked?this.render.enableSystem("atmosphereScattering"):this.render.disableSystem("atmosphereScattering"),this.updateControlsEnabled(t.checked,this.disableSliders)}),this.addSliderListener(n,"atmosphere-intensity-value","atmosphereIntensity",!0),this.addSliderListener(i,"rayleigh-intensity-value","rayleighIntensity",!0),this.addSliderListener(s,"mie-intensity-value","mieIntensity",!0),this.addSliderListener(a,"absorption-intensity-value","absorptionIntensity",!0),this.addSliderListener(o,"baseBetaR-R-value","baseBetaR_R",!1,!0),this.addSliderListener(r,"baseBetaR-G-value","baseBetaR_G",!1,!0),this.addSliderListener(l,"baseBetaR-B-value","baseBetaR_B",!1,!0),this.addSliderListener(d,"baseBetaM-R-value","baseBetaM_R",!1,!0),this.addSliderListener(c,"baseBetaM-G-value","baseBetaM_G",!1,!0),this.addSliderListener(h,"baseBetaM-B-value","baseBetaM_B",!1,!0),this.addSliderListener(p,"baseBetaA-R-value","baseBetaA_R",!1,!0),this.addSliderListener(u,"baseBetaA-G-value","baseBetaA_G",!1,!0),this.addSliderListener(g,"baseBetaA-B-value","baseBetaA_B",!1,!0),this.addSliderListener(m,"hR-value","hR",!1),this.addSliderListener(b,"hM-value","hM",!1),this.addSliderListener(f,"hA-value","hA",!1),this.addSliderListener(y,"absorption-falloff-value","absorption_falloff",!1),this.addSliderListener(v,"groundHeight-value","groundHeight",!1),this.addSliderListener(x,"atmosphereThickness-value","atmosphereThickness",!1),w.addEventListener("change",()=>{this.render.updateSystemConfig("atmosphereScattering",{enableGroundMix:w.checked})}),this.updateAllSliderVisuals()}addSliderListener(e,t,n,i=!1,s=!1){e.addEventListener("input",()=>{const a=parseFloat(e.value);document.getElementById(t).textContent=s?we(a):i?a.toFixed(2):a.toFixed(0),this.render.updateSystemConfig("atmosphereScattering",{[n]:a}),this.updateSliderVisual(e)})}refreshControls(e){let t=null;e?.systems?.atmosphereScattering?t=e.systems.atmosphereScattering:e?.atmosphereScattering&&(t=e.atmosphereScattering),t||(t=this.render.getSystemConfig("atmosphereScattering")||{}),this.updateControlsFromConfig(t)}updateControlsFromConfig(e){const t=document.getElementById("atmosphere-enabled"),n=document.getElementById("enableGroundMix");t&&(void 0!==e.enabled&&(t.checked=e.enabled,this.updateControlsEnabled(e.enabled,this.disableSliders)),void 0!==e.enableGroundMix&&(n.checked=e.enableGroundMix),this.updateSliderFromConfig("atmosphere-intensity","atmosphereIntensity",e),this.updateSliderFromConfig("rayleigh-intensity","rayleighIntensity",e),this.updateSliderFromConfig("mie-intensity","mieIntensity",e),this.updateSliderFromConfig("absorption-intensity","absorptionIntensity",e),this.updateSliderFromConfig("baseBetaR-R","baseBetaR_R",e,!0),this.updateSliderFromConfig("baseBetaR-G","baseBetaR_G",e,!0),this.updateSliderFromConfig("baseBetaR-B","baseBetaR_B",e,!0),this.updateSliderFromConfig("baseBetaM-R","baseBetaM_R",e,!0),this.updateSliderFromConfig("baseBetaM-G","baseBetaM_G",e,!0),this.updateSliderFromConfig("baseBetaM-B","baseBetaM_B",e,!0),this.updateSliderFromConfig("baseBetaA-R","baseBetaA_R",e,!0),this.updateSliderFromConfig("baseBetaA-G","baseBetaA_G",e,!0),this.updateSliderFromConfig("baseBetaA-B","baseBetaA_B",e,!0),this.updateSliderFromConfig("hR","hR",e),this.updateSliderFromConfig("hM","hM",e),this.updateSliderFromConfig("hA","hA",e),this.updateSliderFromConfig("absorption-falloff","absorption_falloff",e),this.updateSliderFromConfig("groundHeight","groundHeight",e),this.updateSliderFromConfig("atmosphereThickness","atmosphereThickness",e),this.updateAllSliderVisuals(),this.viewer.scene.requestRender())}updateSliderFromConfig(e,t,n,i=!1){if(void 0===n[t])return;const s=document.getElementById(e),a=document.getElementById(`${e}-value`);if(!s)return;const o=n[t];s.value=o.toString(),a&&(a.textContent=i?we(o):o.toFixed(0)),this.updateSliderVisual(s)}applyDefaultPresets(){const e=he;this.render.updateSystemConfig("atmosphereScattering",{enabled:e.enabled,enableGroundMix:e.enableGroundMix,atmosphereIntensity:e.atmosphereIntensity,rayleighIntensity:e.rayleighIntensity,mieIntensity:e.mieIntensity,absorptionIntensity:e.absorptionIntensity,baseBetaR_R:e.baseBetaR_R,baseBetaR_G:e.baseBetaR_G,baseBetaR_B:e.baseBetaR_B,baseBetaM_R:e.baseBetaM_R,baseBetaM_G:e.baseBetaM_G,baseBetaM_B:e.baseBetaM_B,baseBetaA_R:e.baseBetaA_R,baseBetaA_G:e.baseBetaA_G,baseBetaA_B:e.baseBetaA_B,hR:e.hR,hM:e.hM,hA:e.hA,absorption_falloff:e.absorption_falloff,groundHeight:e.groundHeight,atmosphereThickness:e.atmosphereThickness,bDensity:e.bDensity,bColor:e.bColor}),this.refreshControls(null)}reset(){this.applyDefaultPresets(),this.updateAllSliderVisuals()}getContainer(){return this.container}}class ke extends Ce{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("雾效系统"),n=pe,i=ue,s=ge;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="builtin">内置距离雾</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="builtin-fog-controls" class="builtin-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">${n.density.toFixed(4)}</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="${n.density}" 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="fog-height-value">${n.heightFalloff.toFixed(2)}</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="${n.heightFalloff}" 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="${ye(n.color)}" style="margin-left: 10px;">\n <span class="control-value" id="fog-color-value">${ye(n.color)}</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">${i.startDistance}</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="${i.startDistance}" 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">${i.startIntensity.toFixed(2)}</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="${i.startIntensity}" 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">${i.endDistance}</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="${i.endDistance}" 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">${i.endIntensity.toFixed(2)}</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="${i.endIntensity}" 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="${ye(i.fogColor)}" style="margin-left: 10px;">\n <span class="control-value" id="distance-fog-color-value">${ye(i.fogColor)}</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">${s.startHeight}</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="1" value="${s.startHeight}" 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">${s.endHeight}</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="1" value="${s.endHeight}" 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">${s.density.toFixed(3)}</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.001" value="${s.density}" 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="${ye(s.fogColor)}" style="margin-left: 10px;">\n <span class="control-value" id="height-fog-color-value">${ye(s.fogColor)}</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 `,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"),o=document.getElementById("distance-fog-start"),r=document.getElementById("distance-fog-start-intensity"),l=document.getElementById("distance-fog-end"),d=document.getElementById("distance-fog-end-intensity"),c=document.getElementById("distance-fog-color"),h=document.getElementById("fog-start-height"),p=document.getElementById("fog-end-height"),u=document.getElementById("height-fog-density"),g=document.getElementById("height-fog-color"),m=document.getElementById("fog-min-height"),b=document.getElementById("fog-max-height"),f=document.getElementById("fog-transition-range"),y=this.container.querySelectorAll(".preset-btn"),v=this.renderConfig.distanceFog?.enabled??!1,x=this.renderConfig.heightFog?.enabled??!1,w=v||x||this.viewer.scene.fog.enabled;n.checked=w,this.updateControlsEnabled(w,this.disableSliders,y),n.addEventListener("change",()=>{this.updateFogState(),this.updateControlsEnabled(n.checked,this.disableSliders,y)}),i.addEventListener("input",()=>{document.getElementById("fog-density-value").textContent=parseFloat(i.value).toFixed(4),this.viewer.scene.fog.density=parseFloat(i.value),this.viewer.scene.requestRender(),this.updateSliderVisual(i)}),s.addEventListener("input",()=>{document.getElementById("fog-height-value").textContent=parseFloat(s.value).toFixed(2),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-value").textContent=e,this.viewer.scene.fog.color=t.fromCssColorString(e),this.viewer.scene.requestRender()}),o.addEventListener("input",()=>{o.disabled||(document.getElementById("distance-fog-start-value").textContent=o.value,this.updateDistanceFogConfig(),this.updateSliderVisual(o))}),r.addEventListener("input",()=>{r.disabled||(document.getElementById("distance-fog-start-intensity-value").textContent=parseFloat(r.value).toFixed(2),this.updateDistanceFogConfig(),this.updateSliderVisual(r))}),l.addEventListener("input",()=>{l.disabled||(document.getElementById("distance-fog-end-value").textContent=l.value,this.updateDistanceFogConfig(),this.updateSliderVisual(l))}),d.addEventListener("input",()=>{d.disabled||(document.getElementById("distance-fog-end-intensity-value").textContent=parseFloat(d.value).toFixed(2),this.updateDistanceFogConfig(),this.updateSliderVisual(d))}),c.addEventListener("input",()=>{c.disabled||(document.getElementById("distance-fog-color-value").textContent=c.value,this.render.updateSystemConfig("distanceFog",{fogColor:t.fromCssColorString(c.value)}),this.viewer.scene.requestRender())}),h.addEventListener("input",()=>{document.getElementById("fog-start-height-value").textContent=h.value,this.updateHeightFogConfig(),this.updateSliderVisual(h)}),p.addEventListener("input",()=>{document.getElementById("fog-end-height-value").textContent=p.value,this.updateHeightFogConfig(),this.updateSliderVisual(p)}),u.addEventListener("input",()=>{document.getElementById("height-fog-density-value").textContent=parseFloat(u.value).toFixed(3),this.updateHeightFogConfig(),this.updateSliderVisual(u)}),g.addEventListener("input",()=>{const e=g.value;document.getElementById("height-fog-color-value").textContent=e,this.render.updateSystemConfig("heightFog",{fogColor:t.fromCssColorString(e)}),this.viewer.scene.requestRender()}),m.addEventListener("input",()=>{document.getElementById("fog-min-height-value").textContent=m.value,this.fogVisibilityParams.minHeight=parseFloat(m.value),this.updateSliderVisual(m)}),b.addEventListener("input",()=>{document.getElementById("fog-max-height-value").textContent=b.value,this.fogVisibilityParams.maxHeight=parseFloat(b.value),this.updateSliderVisual(b)}),f.addEventListener("input",()=>{document.getElementById("fog-transition-range-value").textContent=f.value,this.fogVisibilityParams.transitionRange=parseFloat(f.value),this.updateSliderVisual(f)});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()})}),this.updateAllSliderVisuals()}updateDistanceFogConfig(){const e=parseFloat(document.getElementById("distance-fog-start").value),t=parseFloat(document.getElementById("distance-fog-start-intensity").value),n=parseFloat(document.getElementById("distance-fog-end").value),i=parseFloat(document.getElementById("distance-fog-end-intensity").value);this.render.updateSystemConfig("distanceFog",{fogByDistance:new a(e,t,n,i)}),this.viewer.scene.requestRender()}updateHeightFogConfig(){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 a(e,n,t,0)}),this.viewer.scene.requestRender()}updateFogState(){const e=document.getElementById("fog-enabled").checked;switch(this.currentFogType){case"builtin":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":this.viewer.scene.fog.enabled=!1,e?this.render.enableSystem("heightFog"):this.render.disableSystem("heightFog"),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()}switchFogType(e){this.currentFogType=e;const t=document.getElementById("fog-enabled").checked,n=document.getElementById("builtin-fog-controls"),i=document.getElementById("custom-distance-fog-controls"),s=document.getElementById("height-fog-controls");switch(n&&(n.style.display="none"),i&&(i.style.display="none"),s&&(s.style.display="none"),e){case"builtin":n&&(n.style.display="block"),this.viewer.scene.fog.enabled=t,this.render.disableSystem("distanceFog"),this.render.disableSystem("heightFog");break;case"custom":i&&(i.style.display="block"),this.viewer.scene.fog.enabled=!1,t?this.render.enableSystem("distanceFog"):this.render.disableSystem("distanceFog"),this.render.disableSystem("heightFog");break;case"height":s&&(s.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"),this.viewer.scene.fog.enabled=t,t?this.render.enableSystem("heightFog"):this.render.disableSystem("heightFog"),this.render.disableSystem("distanceFog")}this.viewer.scene.requestRender()}refreshControls(e){let t=null,n=null;e?.systems?(t=e.systems.distanceFog,n=e.systems.heightFog):e&&(t=e.distanceFog,n=e.heightFog),t||(t=this.render.getSystemConfig("distanceFog")||{}),n||(n=this.render.getSystemConfig("heightFog")||{}),this.updateControlsFromConfig(t,n),this.updateAllSliderVisuals(),this.viewer.scene.requestRender()}updateControlsFromConfig(e,t){const n=document.getElementById("fog-enabled");if(!n)return;const i=e?.enabled||t?.enabled||this.viewer.scene.fog.enabled;if(n.checked=i,void 0!==e?.density){const t=document.getElementById("fog-density");t&&(t.value=e.density.toString(),document.getElementById("fog-density-value").textContent=e.density.toFixed(4),this.updateSliderVisual(t))}if(void 0!==e?.heightFalloff){const t=document.getElementById("fog-height");t&&(t.value=e.heightFalloff.toString(),document.getElementById("fog-height-value").textContent=e.heightFalloff.toFixed(2),this.updateSliderVisual(t))}this.updateControlsEnabled(i,this.disableSliders,this.container.querySelectorAll(".preset-btn"))}applyDefaultPresets(){const e=pe,t=ue,n=ge;this.viewer.scene.fog.density=e.density,this.viewer.scene.fog.heightFalloff=e.heightFalloff,this.viewer.scene.fog.color=e.color,this.render.updateSystemConfig("distanceFog",{enabled:t.enabled,fogByDistance:t.fogByDistance,fogColor:t.fogColor}),this.render.updateSystemConfig("heightFog",{enabled:n.enabled,fogByHeight:n.fogByHeight,fogColor:n.fogColor}),this.refreshControls(null)}reset(){this.applyDefaultPresets(),this.updateAllSliderVisuals()}getContainer(){return this.container}}class Ie extends Ce{container;sunUpdateInterval=null;disableSliders=[];constructor(e,t,n){super(e,t,n)}createPanel(e){const t=this.createControlSection("光照控制"),n=de;t.innerHTML=`\n \x3c!-- 三态模式选择 --\x3e\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">光照模式</label>\n </div>\n <div class="radio-group">\n <label class="radio-option">\n <input type="radio" name="lighting-mode" value="off" class="radio-input">\n <span class="radio-custom"></span>\n <span class="radio-label">关闭光照</span>\n </label>\n <label class="radio-option">\n <input type="radio" name="lighting-mode" value="manual" class="radio-input">\n <span class="radio-custom"></span>\n <span class="radio-label">方向光控制</span>\n </label>\n <label class="radio-option">\n <input type="radio" name="lighting-mode" value="auto" class="radio-input" checked>\n <span class="radio-custom"></span>\n <span class="radio-label">自动跟随太阳</span>\n </label>\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 </div>\n <div class="control-row">\n <label class="control-label">高度角</label>\n <span class="control-value" id="sun-elevation-value">--</span>\n </div>\n <div class="control-row">\n <label class="control-label">方位角</label>\n <span class="control-value" id="sun-azimuth-value">--</span>\n </div>\n </div>\n \n \x3c!-- 时间预设 --\x3e\n <div id="time-presets" class="time-presets">\n <div class="control-group">\n <div class="control-row">\n <label class="control-label">⏰ 时间预设</label>\n </div>\n \n \x3c!-- 自定义时间选择器 --\x3e\n <div class="time-picker-container">\n <div class="time-picker-row">\n <label class="time-picker-label">自定义时间 <span class="timezone-hint">(北京时间 UTC+8)</span></label>\n <div class="time-picker-inputs">\n <input type="number" id="time-hour" class="time-input" min="0" max="23" value="9" placeholder="时">\n <span class="time-separator">:</span>\n <input type="number" id="time-minute" class="time-input" min="0" max="59" value="0" placeholder="分">\n <button id="apply-custom-time" class="time-apply-btn">应用</button>\n </div>\n </div>\n </div>\n \n <div class="preset-buttons">\n ${Object.entries(fe).map(([e,t])=>`\n <button class="preset-btn ${"morning"===e?"active":""}" data-time="${e}">${t.label}</button>\n `).join("")}\n </div>\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 <span class="control-value" id="light-intensity-value">${n.intensity.toFixed(2)}</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="${n.intensity}" 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="ambient-intensity-value">${n.ambientIntensity.toFixed(2)}</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="${n.ambientIntensity}" 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="light-color-value">${this.colorToHex(n.color)}</span>\n <input type="color" id="light-color" value="${this.colorToHex(n.color)}" class="color-picker">\n </div>\n </div>\n \n \x3c!-- 方向控制(仅手动模式可见) --\x3e\n <div id="direction-controls" class="direction-controls" style="display: none;">\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">${n.direction.x.toFixed(2)}</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="${n.direction.x}" class="slider">\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">${n.direction.y.toFixed(2)}</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="${n.direction.y}" class="slider">\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">${n.direction.z.toFixed(2)}</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="${n.direction.z}" class="slider">\n </div>\n </div>\n </div>\n </div>\n `,e.appendChild(t),this.container=t}initEventListeners(e){const t=document.querySelectorAll('input[name="lighting-mode"]'),n=document.getElementById("light-intensity"),i=document.getElementById("light-dir-x"),s=document.getElementById("light-dir-y"),a=document.getElementById("light-dir-z"),o=document.getElementById("ambient-intensity"),r=document.getElementById("light-color"),l=document.getElementById("direction-controls"),d=document.getElementById("sun-info"),c=document.getElementById("time-presets"),h=this.container.querySelectorAll(".preset-btn"),p=this.renderConfig.directionalLight?.enabled?this.renderConfig.directionalLight?.followSun?"auto":"manual":"off",u=document.querySelector(`input[name="lighting-mode"][value="${p}"]`);u&&(u.checked=!0),this.updateModeState(p,l,d,c,h),t.forEach(e=>{e.addEventListener("change",()=>{const t=e;if(t.checked){const e=t.value;this.updateModeState(e,l,d,c,h),this.applyModeSettings(e)}})}),n.addEventListener("input",()=>{n.disabled||(document.getElementById("light-intensity-value").textContent=parseFloat(n.value).toFixed(2),this.applyLightingSettings(),this.updateSliderVisual(n))}),i.addEventListener("input",()=>{i.disabled||(document.getElementById("light-dir-x-value").textContent=parseFloat(i.value).toFixed(2),this.applyLightingSettings(),this.updateSliderVisual(i))}),s.addEventListener("input",()=>{s.disabled||(document.getElementById("light-dir-y-value").textContent=parseFloat(s.value).toFixed(2),this.applyLightingSettings(),this.updateSliderVisual(s))}),a.addEventListener("input",()=>{a.disabled||(document.getElementById("light-dir-z-value").textContent=parseFloat(a.value).toFixed(2),this.applyLightingSettings(),this.updateSliderVisual(a))}),o.addEventListener("input",()=>{document.getElementById("ambient-intensity-value").textContent=parseFloat(o.value).toFixed(2),this.applyLightingSettings(),this.updateSliderVisual(o)}),r.addEventListener("input",()=>{document.getElementById("light-color-value").textContent=r.value,this.applyLightingSettings()});const g=document.querySelectorAll(".preset-btn[data-time]");g.forEach(e=>{e.addEventListener("click",()=>{const t=document.querySelector('input[name="lighting-mode"]:checked'),n=t?.value;if("auto"!==n)return;g.forEach(e=>e.classList.remove("active")),e.classList.add("active");const i=e.getAttribute("data-time");this.applySunTimePreset(i),e.blur()})});const m=document.getElementById("time-hour"),b=document.getElementById("time-minute"),f=document.getElementById("apply-custom-time");m?.addEventListener("input",()=>{let e=parseInt(m.value);isNaN(e)&&(e=0),e<0&&(e=0),e>23&&(e=23),m.value=e.toString()}),b?.addEventListener("input",()=>{let e=parseInt(b.value);isNaN(e)&&(e=0),e<0&&(e=0),e>59&&(e=59),b.value=e.toString()}),f?.addEventListener("click",()=>{const e=document.querySelector('input[name="lighting-mode"]:checked'),t=e?.value;if("auto"!==t)return;const n=parseInt(m.value)||0,i=parseInt(b.value)||0;this.applyCustomTime(n,i),g.forEach(e=>e.classList.remove("active"))}),m?.addEventListener("keypress",e=>{"Enter"===e.key&&f?.click()}),b?.addEventListener("keypress",e=>{"Enter"===e.key&&f?.click()}),this.syncTimePickerFromClock(),this.updateAllSliderVisuals(),"auto"===p&&this.startSunTracking()}updateModeState(e,t,n,i,s){const a=document.getElementById("light-dir-x"),o=document.getElementById("light-dir-y"),r=document.getElementById("light-dir-z"),l=document.getElementById("light-intensity"),d=document.getElementById("ambient-intensity"),c=document.getElementById("light-color");switch(e){case"off":this.disableSliders=["light-intensity","light-dir-x","light-dir-y","light-dir-z","ambient-intensity","light-color"],t.style.display="none",n.style.display="none",i.style.display="none";break;case"manual":this.disableSliders=[],t.style.display="block",n.style.display="none",i.style.display="none",a.disabled=!1,o.disabled=!1,r.disabled=!1,l.disabled=!1,d.disabled=!1,c.disabled=!1;break;case"auto":this.disableSliders=["light-dir-x","light-dir-y","light-dir-z"],t.style.display="none",n.style.display="block",i.style.display="block",a.disabled=!0,o.disabled=!0,r.disabled=!0,l.disabled=!1,d.disabled=!1,c.disabled=!1}const h="off"!==e;this.updateControlsEnabled(h,this.disableSliders,s)}applyModeSettings(e){switch(e){case"off":this.render.disableSystem("lighting"),this.stopSunTracking();break;case"manual":this.render.disableSystem("lighting"),this.stopSunTracking(),this.render.enableSystem("lighting"),this.render.updateSystemConfig("lighting",{followSun:!1}),this.updateManualDirection();break;case"auto":this.render.enableSystem("lighting"),this.render.updateSystemConfig("lighting",{followSun:!0}),this.startSunTracking()}this.viewer.scene.requestRender()}applyLightingSettings(){const n=parseFloat(document.getElementById("light-intensity").value),i=parseFloat(document.getElementById("ambient-intensity").value),s=document.getElementById("light-color").value,a=document.querySelector('input[name="lighting-mode"]:checked').value;let o,r,l;if("auto"===a){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;o=parseFloat(e||"0"),r=parseFloat(t||"0"),l=parseFloat(n||"0")}else o=parseFloat(document.getElementById("light-dir-x").value),r=parseFloat(document.getElementById("light-dir-y").value),l=parseFloat(document.getElementById("light-dir-z").value);this.render.updateSystemConfig("lighting",{direction:new e(o,r,l),intensity:n,color:t.fromCssColorString(s),followSun:"auto"===a}),this.viewer.scene.globe.luminanceAtZenith=i,this.viewer.scene.requestRender()}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=window.Cesium.Simon1994PlanetaryPositions.computeSunPositionInEarthInertialFrame(t),i=R.computeIcrfToFixedMatrix(t),s=z.multiplyByVector(i,n,new e),a=e.negate(s,new e);e.normalize(a,a);const o=this.vectorToElevationAzimuth(a),r=document.getElementById("sun-elevation-value"),l=document.getElementById("sun-azimuth-value");r&&(r.textContent=`${o.elevation.toFixed(1)}°`),l&&(l.textContent=`${o.azimuth.toFixed(1)}°`),document.getElementById("light-dir-x-value").textContent=a.x.toFixed(2),document.getElementById("light-dir-y-value").textContent=a.y.toFixed(2),document.getElementById("light-dir-z-value").textContent=a.z.toFixed(2);const d=document.getElementById("light-dir-x"),c=document.getElementById("light-dir-y"),h=document.getElementById("light-dir-z");d.value=a.x.toString(),c.value=a.y.toString(),h.value=a.z.toString(),this.updateSliderVisual(d),this.updateSliderVisual(c),this.updateSliderVisual(h),this.render.updateSystemConfig("lighting",{direction:a}),this.viewer.scene.requestRender()}catch(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))}),this.applyLightingSettings()}applySunTimePreset(e){const t=fe[e];if(!t)return;const n=t.hour-8,i=A.toDate(this.viewer.clock.currentTime),s=new Date(i);s.setUTCHours(n,t.minute,0,0),this.viewer.clock.currentTime=A.fromDate(s);const a=document.getElementById("time-hour"),o=document.getElementById("time-minute");a&&(a.value=t.hour.toString()),o&&(o.value=t.minute.toString().padStart(2,"0")),this.updateSunPosition(),this.viewer.scene.requestRender()}applyCustomTime(e,t){const n=e-8,i=A.toDate(this.viewer.clock.currentTime),s=new Date(i);s.setUTCHours(n,t,0,0),this.viewer.clock.currentTime=A.fromDate(s),this.updateSunPosition(),this.viewer.scene.requestRender()}syncTimePickerFromClock(){const e=A.toDate(this.viewer.clock.currentTime),t=e.getUTCHours(),n=e.getUTCMinutes();let i=t+8;i>=24&&(i-=24),i<0&&(i+=24);const s=document.getElementById("time-hour"),a=document.getElementById("time-minute");s&&(s.value=i.toString()),a&&(a.value=n.toString().padStart(2,"0"))}refreshControls(e){let t=null;e?.systems?.lighting?t=e.systems.lighting:e?.directionalLight&&(t=e.directionalLight),t||(t=this.render.getSystemConfig("lighting")||{}),this.updateControlsFromConfig(t),this.updateAllSliderVisuals(),this.viewer.scene.requestRender()}updateControlsFromConfig(e){const t=document.querySelectorAll('input[name="lighting-mode"]'),n=document.getElementById("light-intensity"),i=document.getElementById("light-intensity-value"),s=document.getElementById("light-dir-x"),a=document.getElementById("light-dir-x-value"),o=document.getElementById("light-dir-y"),r=document.getElementById("light-dir-y-value"),l=document.getElementById("light-dir-z"),d=document.getElementById("light-dir-z-value"),c=document.getElementById("ambient-intensity"),h=document.getElementById("ambient-intensity-value"),p=document.getElementById("light-color"),u=document.getElementById("light-color-value"),g=document.getElementById("direction-controls"),m=document.getElementById("sun-info"),b=document.getElementById("time-presets"),f=this.container?.querySelectorAll(".preset-btn");if(!t||0===t.length)return;let y;y=!1===e.enabled?"off":!0===e.followSun?"auto":"manual";const v=document.querySelector(`input[name="lighting-mode"][value="${y}"]`);if(v&&(v.checked=!0),this.updateModeState(y,g,m,b,f),"auto"===y?this.startSunTracking():this.stopSunTracking(),void 0!==e.intensity&&(n.value=e.intensity.toString(),i&&(i.textContent=e.intensity.toFixed(2)),this.updateSliderVisual(n)),e.direction&&(s&&void 0!==e.direction.x&&(s.value=e.direction.x.toString(),a&&(a.textContent=e.direction.x.toFixed(2)),this.updateSliderVisual(s)),o&&void 0!==e.direction.y&&(o.value=e.direction.y.toString(),r&&(r.textContent=e.direction.y.toFixed(2)),this.updateSliderVisual(o)),l&&void 0!==e.direction.z&&(l.value=e.direction.z.toString(),d&&(d.textContent=e.direction.z.toFixed(2)),this.updateSliderVisual(l))),void 0!==e.ambientIntensity&&(c.value=e.ambientIntensity.toString(),h&&(h.textContent=e.ambientIntensity.toFixed(2)),this.updateSliderVisual(c)),e.color){const t=this.colorToHex(e.color);p&&(p.value=t),u&&(u.textContent=t)}this.viewer.scene.requestRender()}applyDefaultPresets(){const t=de;this.render.updateSystemConfig("lighting",{enabled:t.enabled,followSun:t.followSun,direction:new e(t.direction.x,t.direction.y,t.direction.z),intensity:t.intensity,ambientIntensity:t.ambientIntensity,color:t.color}),this.refreshControls(null)}reset(){this.applyDefaultPresets(),this.updateAllSliderVisuals()}getContainer(){return this.container}}class Be extends Ce{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("阴影系统"),n=ce,i=ve(n.shadowColor);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">${n.size}</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="${n.size}" 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">${n.softness.toFixed(1)}</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="${n.softness}" 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">${n.darkness.toFixed(2)}</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="${n.darkness}" 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="${i}" style="margin-left: 10px;">\n <div class="color-picker" id="shadow-color-preview" style="background-color: ${i};"></div>\n <span class="control-value" id="shadow-color-value">${i}</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">${n.shadowBlend.toFixed(2)}</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="${n.shadowBlend}" class="slider">\n </div>\n </div>\n </div>\n `,e.appendChild(t),this.container=t}initEventListeners(e){const t=document.getElementById("shadows-enabled"),n=document.getElementById("shadow-quality"),i=document.getElementById("shadow-softness"),s=document.getElementById("shadow-darkness"),a=document.getElementById("shadow-color"),o=document.getElementById("shadow-blend"),r=this.renderConfig.shadow?.enabled??!1;t.checked=r,this.updateControlsEnabled(r,this.disableSliders,null),t.addEventListener("change",()=>{t.checked?this.render.enableSystem("shadow"):this.render.disableSystem("shadow"),this.updateControlsEnabled(t.checked,this.disableSliders,null)}),n.addEventListener("input",()=>{document.getElementById("shadow-quality-value").textContent=n.value,this.render.updateSystemConfig("shadow",{size:parseInt(n.value)}),this.updateSliderVisual(n)}),i.addEventListener("input",()=>{const e=parseFloat(i.value);document.getElementById("shadow-softness-value").textContent=e.toFixed(1),this.render.updateSystemConfig("shadow",{softShadows:e>0}),this.updateSliderVisual(i)}),s.addEventListener("input",()=>{document.getElementById("shadow-darkness-value").textContent=parseFloat(s.value).toFixed(2),this.render.updateSystemConfig("shadow",{darkness:parseFloat(s.value)}),this.updateSliderVisual(s)}),a.addEventListener("input",()=>{const e=a.value;document.getElementById("shadow-color-preview").style.backgroundColor=e,document.getElementById("shadow-color-value").textContent=e,this.render.updateSystemConfig("shadow",{shadowColor:xe(e)})}),o.addEventListener("input",()=>{document.getElementById("shadow-blend-value").textContent=parseFloat(o.value).toFixed(2),this.render.updateSystemConfig("shadow",{shadowBlend:parseFloat(o.value)}),this.updateSliderVisual(o)}),this.updateAllSliderVisuals()}refreshControls(e){let t=null;e?.systems?.shadow?t=e.systems.shadow:e?.shadow&&(t=e.shadow),t||(t=this.render.getSystemConfig("shadow")||{}),this.updateControlsFromConfig(t)}updateControlsFromConfig(e){const t=document.getElementById("shadows-enabled"),n=document.getElementById("shadow-quality"),i=document.getElementById("shadow-quality-value"),s=document.getElementById("shadow-softness"),a=document.getElementById("shadow-softness-value"),o=document.getElementById("shadow-darkness"),r=document.getElementById("shadow-darkness-value"),l=document.getElementById("shadow-color"),d=document.getElementById("shadow-color-value"),c=document.getElementById("shadow-color-preview"),h=document.getElementById("shadow-blend"),p=document.getElementById("shadow-blend-value");if(t){if(void 0!==e.enabled&&(t.checked=e.enabled),void 0!==e.size&&n&&(n.value=e.size.toString(),i&&(i.textContent=e.size.toString()),this.updateSliderVisual(n)),void 0!==e.softShadows&&s){const t=e.softShadows?ce.softness:0;s.value=t.toString(),a&&(a.textContent=t.toFixed(1)),this.updateSliderVisual(s)}if(void 0!==e.darkness&&o&&(o.value=e.darkness.toString(),r&&(r.textContent=e.darkness.toFixed(2)),this.updateSliderVisual(o)),e.shadowColor&&l){const t=this.shadowColorToHex(e.shadowColor);l.value=t,d&&(d.textContent=t),c&&(c.style.backgroundColor=t)}void 0!==e.shadowBlend&&h&&(h.value=e.shadowBlend.toString(),p&&(p.textContent=e.shadowBlend.toFixed(2)),this.updateSliderVisual(h)),this.updateControlsEnabledState(),this.viewer.scene.requestRender()}}updateControlsEnabledState(){const e=document.getElementById("shadows-enabled");e&&this.updateControlsEnabled(e.checked,this.disableSliders,null)}shadowColorToHex(n){return"string"==typeof n?n:n instanceof t?this.colorToHex(n):n instanceof e?ve(n):void 0!==n?.x?ve(new e(n.x,n.y,n.z)):void 0!==n?.red?this.colorToHex(n):"#000000"}applyDefaultPresets(){const t=ce;this.render.updateSystemConfig("shadow",{enabled:t.enabled,size:t.size,softShadows:t.softShadows,darkness:t.darkness,shadowColor:new e(t.shadowColor.x,t.shadowColor.y,t.shadowColor.z),shadowBlend:t.shadowBlend,maximumDistance:t.maximumDistance,normalOffset:t.normalOffset,fadingEnabled:t.fadingEnabled,bias:t.bias}),this.refreshControls(null)}reset(){this.applyDefaultPresets(),this.updateAllSliderVisuals()}getContainer(){return this.container}}class Le extends Ce{container;preset_container;disableSliders=["hdr-enabled","fxaa-enabled","msaa","brightness","contrast","saturation","gamma","temperature","tint"];constructor(e,t,n){super(e,t,n)}createPanel(e){const t=me,n=this.createControlSection("后处理");n.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 <div class="control-group">\n <div class="control-row">\n <label class="control-label">启用MSAA</label>\n <label class="toggle-switch">\n <input type="checkbox" id="msaa-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">MSAA系数</label>\n <span class="control-value" id="msaa-value">${t.msaa}</span>\n </div>\n <div class="control-row">\n <div class="slider-container">\n <input type="range" id="msaa" min="1" max="8" step="1" value="${t.msaa}" class="slider">\n </div>\n </div>\n </div>\n `;const i=this.createControlSection("色彩滤镜");i.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">${t.brightness.toFixed(2)}</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="${t.brightness}" 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">${t.contrast.toFixed(2)}</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="${t.contrast}" 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">${t.saturation.toFixed(2)}</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="${t.saturation}" 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">${t.gamma.toFixed(2)}</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="${t.gamma}" class="slider">\n </div>\n </div>\n </div>\n `;const s=this.createControlSection("白平衡");s.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">${t.temperature}</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="${t.temperature}" 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">${t.tint.toFixed(2)}</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="${t.tint}" class="slider">\n </div>\n </div>\n </div>\n `;const a=this.createControlSection("滤镜预设");a.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 ${Object.entries(be).map(([e])=>`\n <button class="preset-btn ${"normal"===e?"active":""}" data-preset="${e}">${this.getPresetLabel(e)}</button>\n `).join("")}\n </div>\n </div>\n `,e.appendChild(n),e.appendChild(i),e.appendChild(s),e.appendChild(a),this.container=e,this.preset_container=a}getPresetLabel(e){return{normal:"标准",warm:"暖色调",cool:"冷色调",vibrant:"鲜艳",desaturated:"去色","high-contrast":"高对比度"}[e]||e}initEventListeners(e){const t=document.getElementById("post-processing-enabled"),n=document.getElementById("hdr-enabled"),i=document.getElementById("fxaa-enabled"),s=document.getElementById("msaa-enabled"),a=document.getElementById("msaa"),o=document.getElementById("brightness"),r=document.getElementById("contrast"),l=document.getElementById("saturation"),d=document.getElementById("gamma"),c=document.getElementById("temperature"),h=document.getElementById("tint"),p=this.preset_container.querySelectorAll(".preset-btn[data-preset]"),u=this.renderConfig.postProcessing?.enabled??!1;t.checked=u,n.checked=this.renderConfig.postProcessing?.hdrEnabled??me.hdrEnabled,i.checked=this.renderConfig.postProcessing?.fxaaEnabled??me.fxaaEnabled,s.checked=this.renderConfig.postProcessing?.msaaEnabled??me.msaaEnabled,this.updateControlsEnabled(u,this.disableSliders,p),t.addEventListener("change",()=>{t.checked?this.render.enableSystem("postProcessing"):this.render.disableSystem("postProcessing"),this.updateControlsEnabled(t.checked,this.disableSliders,p)}),n.addEventListener("change",()=>{this.viewer.scene.highDynamicRange=n.checked,this.viewer.scene.requestRender()}),i.addEventListener("change",()=>{this.viewer.scene.postProcessStages.fxaa.enabled=i.checked,this.viewer.scene.requestRender()}),s.addEventListener("change",()=>{this.viewer.scene.msaaSupported?(s.checked?(a.disabled=!1,this.viewer.scene.msaaSamples=parseInt(a.value)):(a.disabled=!0,this.viewer.scene.msaaSamples=1),this.updateSliderVisual(a),this.viewer.scene.requestRender()):a.disabled=!0}),a.addEventListener("input",()=>{document.getElementById("msaa-value").textContent=a.value,this.viewer.scene.msaaSamples=parseInt(a.value),this.updateSliderVisual(a),this.viewer.scene.requestRender()}),o.addEventListener("input",()=>{document.getElementById("brightness-value").textContent=parseFloat(o.value).toFixed(2),this.render.updateSystemConfig("postProcessing",{brightness:parseFloat(o.value)}),this.updateSliderVisual(o)}),r.addEventListener("input",()=>{document.getElementById("contrast-value").textContent=parseFloat(r.value).toFixed(2),this.render.updateSystemConfig("postProcessing",{contrast:parseFloat(r.value)}),this.updateSliderVisual(r)}),l.addEventListener("input",()=>{document.getElementById("saturation-value").textContent=parseFloat(l.value).toFixed(2),this.render.updateSystemConfig("postProcessing",{saturation:parseFloat(l.value)}),this.updateSliderVisual(l)}),d.addEventListener("input",()=>{document.getElementById("gamma-value").textContent=parseFloat(d.value).toFixed(2),this.render.updateSystemConfig("postProcessing",{gamma:parseFloat(d.value)}),this.updateSliderVisual(d)}),c.addEventListener("input",()=>{document.getElementById("temperature-value").textContent=c.value,this.render.updateSystemConfig("postProcessing",{temperature:parseFloat(c.value)}),this.updateSliderVisual(c)}),h.addEventListener("input",()=>{document.getElementById("tint-value").textContent=parseFloat(h.value).toFixed(2),this.render.updateSystemConfig("postProcessing",{tint:parseFloat(h.value)}),this.updateSliderVisual(h)}),p.forEach(e=>{e.addEventListener("click",()=>{p.forEach(e=>e.classList.remove("active")),e.classList.add("active");const t=e.getAttribute("data-preset");this.applyFilterPreset(t),e.blur()})}),this.updateAllSliderVisuals()}applyFilterPreset(e){const t=be[e];if(!t)return;const n=document.getElementById("brightness"),i=document.getElementById("contrast"),s=document.getElementById("saturation"),a=document.getElementById("gamma"),o=document.getElementById("temperature"),r=document.getElementById("tint");n.value=t.brightness.toString(),document.getElementById("brightness-value").textContent=t.brightness.toFixed(2),i.value=t.contrast.toString(),document.getElementById("contrast-value").textContent=t.contrast.toFixed(2),s.value=t.saturation.toString(),document.getElementById("saturation-value").textContent=t.saturation.toFixed(2),a.value=t.gamma.toString(),document.getElementById("gamma-value").textContent=t.gamma.toFixed(2),o.value=t.temperature.toString(),document.getElementById("temperature-value").textContent=t.temperature.toString(),r.value=t.tint.toString(),document.getElementById("tint-value").textContent=t.tint.toFixed(2),this.render.updateSystemConfig("postProcessing",{brightness:t.brightness,contrast:t.contrast,saturation:t.saturation,gamma:t.gamma,temperature:t.temperature,tint:t.tint}),this.updateSliderVisual(n),this.updateSliderVisual(i),this.updateSliderVisual(s),this.updateSliderVisual(a),this.updateSliderVisual(o),this.updateSliderVisual(r),this.viewer.scene.requestRender()}refreshControls(e){let t=null;e?.systems?.postProcessing?t=e.systems.postProcessing:e?.postProcessing&&(t=e.postProcessing),t||(t=this.render.getSystemConfig("postProcessing")||{}),this.updateControlsFromConfig(t),this.updateAllSliderVisuals(),this.viewer.scene.requestRender()}updateControlsFromConfig(e){const t=document.getElementById("post-processing-enabled"),n=document.getElementById("hdr-enabled"),i=document.getElementById("fxaa-enabled"),s=document.getElementById("msaa-enabled"),a=document.getElementById("msaa"),o=document.getElementById("brightness"),r=document.getElementById("contrast"),l=document.getElementById("saturation"),d=document.getElementById("gamma"),c=document.getElementById("temperature"),h=document.getElementById("tint"),p=this.preset_container?.querySelectorAll(".preset-btn[data-preset]");t&&(void 0!==e.enabled&&(t.checked=e.enabled),void 0!==e.hdrEnabled&&n&&(n.checked=e.hdrEnabled,this.viewer.scene.highDynamicRange=e.hdrEnabled),void 0!==e.fxaaEnabled&&i&&(i.checked=e.fxaaEnabled,this.viewer.scene.postProcessStages.fxaa.enabled=e.fxaaEnabled),void 0!==e.msaaEnabled&&s&&(s.checked=e.msaaEnabled),void 0!==e.msaa&&a&&(a.value=e.msaa.toString(),document.getElementById("msaa-value").textContent=e.msaa.toString(),this.updateSliderVisual(a)),this.updateSliderFromConfig(o,"brightness-value",e.brightness),this.updateSliderFromConfig(r,"contrast-value",e.contrast),this.updateSliderFromConfig(l,"saturation-value",e.saturation),this.updateSliderFromConfig(d,"gamma-value",e.gamma),this.updateSliderFromConfig(c,"temperature-value",e.temperature,!1),this.updateSliderFromConfig(h,"tint-value",e.tint),this.updateControlsEnabled(t.checked,this.disableSliders,p))}updateSliderFromConfig(e,t,n,i=!0){if(void 0===n||!e)return;e.value=n.toString();const s=document.getElementById(t);s&&(s.textContent=i?parseFloat(n).toFixed(2):n.toString()),this.updateSliderVisual(e)}applyDefaultPresets(){const t=me;this.render.updateSystemConfig("postProcessing",{enabled:t.enabled,brightness:t.brightness,contrast:t.contrast,saturation:t.saturation,gamma:t.gamma,temperature:t.temperature,tint:t.tint,shadowColor:new e(t.shadowColor.x,t.shadowColor.y,t.shadowColor.z),shadowBlend:t.shadowBlend}),this.refreshControls(null)}reset(){this.applyDefaultPresets(),this.updateAllSliderVisuals()}getContainer(){return this.container}}class Pe{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;this.render=new se(n),this.renderConfig=n}this.viewer=this.render.viewer,this.panelContainer=this.getOrCreatePanelContainer(),this.getSystemInstances(),this.volumetricCloudControl=new Ee(this.viewer,this.render,this.renderConfig),this.atmosphereControl=new Se(this.viewer,this.render,this.renderConfig),this.fogControl=new ke(this.viewer,this.render,this.renderConfig),this.lightingControl=new Ie(this.viewer,this.render,this.renderConfig),this.shadowControl=new Be(this.viewer,this.render,this.renderConfig),this.postProcessingControl=new Le(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(){}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 ">×</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"),a=document.getElementById("config-apply-btn"),o=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()}),o?.addEventListener("click",()=>{this.hideConfigPreview()}),a?.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")}}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")}}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")}else this.showNotification("没有要应用的配置","error")}updateAllControlPanels(){const e=this.render.getConfig(),t=new CustomEvent("render-config-updated",{detail:{config:e}});document.dispatchEvent(t)}handleConfigReset(){try{const n={enableAll:!0,directionalLight:{enabled:!0,direction:new e(1.51,-.11,.21),intensity:1.97,color:t.WHITE},shadow:{enabled:!0,size:4096,softShadows:!0,darkness:.51,numBlurSamples:24,shadowColor:new t(.012,.078,.227,1),shadowBlend:.01},atmosphereScattering:{enabled:!0,atmosphereIntensity:.91,rayleighIntensity:1,mieIntensity:1,absorptionIntensity:1,bDensity:.01,bColor:t.fromCssColorString("#b6d3f5ff"),baseBetaR_R:58e-7,baseBetaR_G:135e-7,baseBetaR_B:231e-7,baseBetaM_R:2e-5,baseBetaM_G:2e-5,baseBetaM_B:2e-5,baseBetaA_R:204e-7,baseBetaA_G:497e-7,baseBetaA_B:195e-8,hR:8500,hM:3200,hA:3e4,absorption_falloff:4e3,groundHeight:636e4,atmosphereThickness:6e4,enableGroundMix:!0},volumetricClouds:{enabled:!0,windVector:new e(5,0,0),cloudCover:.39,cloudBase:2e3,cloudTop:6e3,cloudLightIntensity:5.3,cloudIntensity:1},distanceFog:{enabled:!1},heightFog:{enabled:!1},postProcessing:{enabled:!0,hdrEnabled:!0,fxaaEnabled:!0,brightness:1.02,contrast:1,saturation:1.75,gamma:.5,temperature:6730,tint:-.39,shadowColor:new e(.012,.078,.227),shadowBlend:.01}};this.render.applyConfig(n),this.resetAll(),this.showNotification("配置已重置为默认值","success"),this.updateConfigStatus("","已重置")}catch(e){this.showNotification(`重置失败: ${e.message}`,"error")}}showConfigPreview(e,t){const n=document.getElementById("config-preview-modal"),i=document.getElementById("config-modal-overlay"),s=document.getElementById("config-preview-content"),a=JSON.stringify(e,null,2),o=this.highlightJson(a);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>${o}</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">×</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 Te{viewer;render;renderConfig;container=null;config;constructor(e,t,n){this.viewer=e,this.render=t,this.renderConfig=n,this.config=n.water||{...W}}createPanel(e){this.container=e,this.container.innerHTML=this.createPanelContent()}createPanelContent(){const e=this.config;return!e.color||(Math.floor(255*e.color.red),Math.floor(255*e.color.green),Math.floor(255*e.color.blue),e.color.alpha),`\n <div class="control-section">\n <div class="section-header">\n <span class="section-title">水体系统</span>\n </div>\n \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" class="toggle-checkbox" id="water-enabled" ${e.enabled?"checked":""}>\n <span class="toggle-slider"></span>\n </label>\n </div>\n </div>\n\n <div class="control-group">\n <div class="section-header">\n <span class="section-title">外观设置</span>\n </div>\n \n <div class="control-row">\n <label class="control-label">水体颜色</label>\n <input type="color" id="water-color" class="color-picker" value="${this.rgbToHex(e.color||W.color)}">\n </div>\n\n <div class="control-row">\n <label class="control-label">透明度</label>\n <div class="slider-container">\n <input type="range" id="water-opacity" class="slider" min="0" max="1" step="0.01" value="${e.opacity||W.opacity}">\n </div>\n <span class="control-value" id="water-opacity-value">${(100*(e.opacity||W.opacity)).toFixed(0)}%</span>\n </div>\n\n <div class="control-row">\n <label class="control-label">反射强度</label>\n <div class="slider-container">\n <input type="range" id="water-reflectivity" class="slider" min="0" max="1" step="0.01" value="${e.reflectivity||W.reflectivity}">\n </div>\n <span class="control-value" id="water-reflectivity-value">${(100*(e.reflectivity||W.reflectivity)).toFixed(0)}%</span>\n </div>\n\n <div class="control-row">\n <label class="control-label">折射强度</label>\n <div class="slider-container">\n <input type="range" id="water-refractivity" class="slider" min="0" max="1" step="0.01" value="${e.refractivity||W.refractivity}">\n </div>\n <span class="control-value" id="water-refractivity-value">${(100*(e.refractivity||W.refractivity)).toFixed(0)}%</span>\n </div>\n </div>\n\n <div class="control-group">\n <div class="section-header">\n <span class="section-title">波浪设置</span>\n </div>\n \n <div class="control-row">\n <label class="control-label">波浪强度</label>\n <div class="slider-container">\n <input type="range" id="water-wave-strength" class="slider" min="0" max="1" step="0.01" value="${e.waveStrength||W.waveStrength}">\n </div>\n <span class="control-value" id="water-wave-strength-value">${(100*(e.waveStrength||W.waveStrength)).toFixed(0)}%</span>\n </div>\n\n <div class="control-row">\n <label class="control-label">波浪频率</label>\n <div class="slider-container">\n <input type="range" id="water-wave-frequency" class="slider" min="1" max="50" step="1" value="${e.waveFrequency||W.waveFrequency}">\n </div>\n <span class="control-value" id="water-wave-frequency-value">${e.waveFrequency||W.waveFrequency}</span>\n </div>\n\n <div class="control-row">\n <label class="control-label">波浪速度</label>\n <div class="slider-container">\n <input type="range" id="water-wave-speed" class="slider" min="0" max="2" step="0.01" value="${e.waveSpeed||W.waveSpeed}">\n </div>\n <span class="control-value" id="water-wave-speed-value">${e.waveSpeed||W.waveSpeed}</span>\n </div>\n\n <div class="control-row">\n <label class="control-label">法线纹理重复</label>\n <div class="slider-container">\n <input type="range" id="water-normal-repeat" class="slider" min="1" max="20" step="0.5" value="${e.normalRepeat||W.normalRepeat}">\n </div>\n <span class="control-value" id="water-normal-repeat-value">${e.normalRepeat||W.normalRepeat}</span>\n </div>\n </div>\n\n <div class="control-group">\n <div class="section-header">\n <span class="section-title">深度设置</span>\n </div>\n \n <div class="control-row">\n <label class="control-label">浅滩深度</label>\n <div class="slider-container">\n <input type="range" id="water-shallow-depth" class="slider" min="0.1" max="10" step="0.1" value="${e.shallowWaterDepth||W.shallowWaterDepth}">\n </div>\n <span class="control-value" id="water-shallow-depth-value">${e.shallowWaterDepth||W.shallowWaterDepth}m</span>\n </div>\n\n <div class="control-row">\n <label class="control-label">深度衰减</label>\n <div class="slider-container">\n <input type="range" id="water-depth-attenuation" class="slider" min="0.01" max="1" step="0.01" value="${e.depthAttenuation||W.depthAttenuation}">\n </div>\n <span class="control-value" id="water-depth-attenuation-value">${(100*(e.depthAttenuation||W.depthAttenuation)).toFixed(0)}%</span>\n </div>\n </div>\n\n <div class="control-group">\n <div class="config-buttons">\n <button id="water-reset-btn" class="reset-btn">\n <span>🔄</span>\n <span>重置</span>\n </button>\n </div>\n </div>\n </div>\n `}initEventListeners(e){const t=e.querySelector("#water-enabled"),n=e.querySelector("#water-color"),i=e.querySelector("#water-opacity"),s=e.querySelector("#water-opacity-value"),a=e.querySelector("#water-reflectivity"),o=e.querySelector("#water-reflectivity-value"),r=e.querySelector("#water-refractivity"),l=e.querySelector("#water-refractivity-value"),d=e.querySelector("#water-wave-strength"),c=e.querySelector("#water-wave-strength-value"),h=e.querySelector("#water-wave-frequency"),p=e.querySelector("#water-wave-frequency-value"),u=e.querySelector("#water-wave-speed"),g=e.querySelector("#water-wave-speed-value"),m=e.querySelector("#water-normal-repeat"),b=e.querySelector("#water-normal-repeat-value"),f=e.querySelector("#water-shallow-depth"),y=e.querySelector("#water-shallow-depth-value"),v=e.querySelector("#water-depth-attenuation"),x=e.querySelector("#water-depth-attenuation-value"),w=e.querySelector("#water-reset-btn");t.addEventListener("change",e=>{const t=e.target.checked;this.config.enabled=t,this.render.updateSystemConfig("water",{enabled:t}),setTimeout(()=>{this.render.getSystemStatus("water")!==t&&(t?this.render.enableSystem("water"):this.render.disableSystem("water"))},100)}),n.addEventListener("change",e=>{const t=e.target.value,n=this.hexToRgb(t);n&&(this.config.color=n,this.render.updateSystemConfig("water",{color:n}))}),i.addEventListener("input",e=>{const t=parseFloat(e.target.value);this.config.opacity=t,s.textContent=`${(100*t).toFixed(0)}%`,this.render.updateSystemConfig("water",{opacity:t})}),a.addEventListener("input",e=>{const t=parseFloat(e.target.value);this.config.reflectivity=t,o.textContent=`${(100*t).toFixed(0)}%`,this.render.updateSystemConfig("water",{reflectivity:t})}),r.addEventListener("input",e=>{const t=parseFloat(e.target.value);this.config.refractivity=t,l.textContent=`${(100*t).toFixed(0)}%`,this.render.updateSystemConfig("water",{refractivity:t})}),d.addEventListener("input",e=>{const t=parseFloat(e.target.value);this.config.waveStrength=t,c.textContent=`${(100*t).toFixed(0)}%`,this.render.updateSystemConfig("water",{waveStrength:t})}),h.addEventListener("input",e=>{const t=parseFloat(e.target.value);this.config.waveFrequency=t,p.textContent=t.toFixed(0),this.render.updateSystemConfig("water",{waveFrequency:t})}),u.addEventListener("input",e=>{const t=parseFloat(e.target.value);this.config.waveSpeed=t,g.textContent=t.toFixed(2),this.render.updateSystemConfig("water",{waveSpeed:t})}),m.addEventListener("input",e=>{const t=parseFloat(e.target.value);this.config.normalRepeat=t,b.textContent=t.toFixed(1),this.render.updateSystemConfig("water",{normalRepeat:t})}),f.addEventListener("input",e=>{const t=parseFloat(e.target.value);this.config.shallowWaterDepth=t,y.textContent=`${t.toFixed(1)}m`,this.render.updateSystemConfig("water",{shallowWaterDepth:t})}),v.addEventListener("input",e=>{const t=parseFloat(e.target.value);this.config.depthAttenuation=t,x.textContent=`${(100*t).toFixed(0)}%`,this.render.updateSystemConfig("water",{depthAttenuation:t})}),w.addEventListener("click",()=>{this.reset()})}reset(){this.config={...W},this.container&&(this.createPanel(this.container),this.initEventListeners(this.container)),this.render.updateSystemConfig("water",this.config)}applyDefaultPresets(){this.reset()}rgbToHex(e){return"#"+((1<<24)+(Math.floor(255*e.red)<<16)+(Math.floor(255*e.green)<<8)+Math.floor(255*e.blue)).toString(16).slice(1).toUpperCase()}hexToRgb(e){const n=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return n?new t(parseInt(n[1],16)/255,parseInt(n[2],16)/255,parseInt(n[3],16)/255,1):null}}class _e{render;viewer;renderConfig;panelContainer;volumetricCloudControl;atmosphereControl;fogControl;lightingControl;shadowControl;postProcessingControl;waterControl;isPanelVisible=!1;activeTab="config";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;this.render=new se(n),this.renderConfig=n}this.viewer=this.render.viewer,this.isPanelVisible=!this.renderConfig.visible,this.panelContainer=this.getOrCreatePanelContainer(),this.getSystemInstances(),this.volumetricCloudControl=new Ee(this.viewer,this.render,this.renderConfig),this.atmosphereControl=new Se(this.viewer,this.render,this.renderConfig),this.fogControl=new ke(this.viewer,this.render,this.renderConfig),this.lightingControl=new Ie(this.viewer,this.render,this.renderConfig),this.shadowControl=new Be(this.viewer,this.render,this.renderConfig),this.postProcessingControl=new Le(this.viewer,this.render,this.renderConfig),this.waterControl=new Te(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),this.systemMap.set("water",e.water)}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: 420px;\n bottom: 20px;\n background: rgba(25, 29, 38, 0.6);\n backdrop-filter: blur(2px);\n -webkit-backdrop-filter: blur(2px);\n border-radius: 16px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);\n z-index: 1000;\n font-family: 'Segoe UI', Arial, sans-serif;\n color: #e2e8f0;\n overflow: hidden;\n transition: all 0.3s ease;\n border: 1px solid rgba(59, 130, 246, 0.3);\n }\n\n .environment-control-panel:hover {\n background: rgba(25, 29, 38, 1);\n backdrop-filter: blur(5px);\n -webkit-backdrop-filter: blur(5px);\n }\n\n /* 标题栏联动样式 */\n .environment-control-panel .panel-header {\n background: rgba(25, 29, 38, 0.6);\n transition: all 0.3s ease;\n }\n\n .environment-control-panel:hover .panel-header {\n background: rgba(25, 29, 38, 1);\n }\n \n .panel-collapsed {\n width: 80px;\n height: 40px;\n }\n \n .panel-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 16px 20px;\n background: #191d26;\n cursor: pointer;\n user-select: none;\n border-bottom: 1px solid rgba(59, 130, 246, 0.2);\n }\n \n .panel-title {\n display: flex;\n align-items: center;\n gap: 12px;\n font-weight: 700;\n font-size: 18px;\n color: #60a5fa;\n }\n \n .panel-toggle-icon {\n font-size: 14px;\n transition: transform 0.3s;\n color: #93c5fd;\n }\n\n .panel-content-wrapper {\n display: flex;\n height: calc(100% - 40px);\n }\n\n .tab-navigation {\n width: 80px;\n background: #191d268c; border-right: 1px solid rgba(59, 130, 246, 0.2);\n display: flex;\n flex-direction: column;\n overflow-y: auto;\n }\n\n .tab-button {\n padding: 16px 8px;\n background: transparent;\n border: none;\n color: #94a3b8;\n text-align: center;\n cursor: pointer;\n transition: all 0.2s ease;\n border-left: 3px solid transparent;\n font-size: 11px;\n font-weight: 500;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n min-height: 70px;\n justify-content: center;\n }\n\n .tab-button:hover {\n background: rgba(59, 130, 246, 0.1);\n color: #60a5fa;\n border-left-color: rgba(59, 130, 246, 0.5);\n }\n\n .tab-button.active {\n background: rgba(59, 130, 246, 0.15);\n color: #3b82f6;\n border-left-color: #3b82f6;\n font-weight: 600;\n }\n\n .tab-button .tab-icon {\n font-size: 20px;\n width: 24px;\n text-align: center;\n line-height: 1;\n }\n\n .tab-content {\n flex: 1;\n padding: 16px;\n overflow-y: auto;\n background: #191d268c;\n }\n\n .tab-panel {\n display: none;\n }\n\n .tab-panel.active {\n display: block;\n animation: fadeIn 0.3s ease;\n }\n\n @keyframes fadeIn {\n from { opacity: 0; transform: translateY(10px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n .control-section {\n margin-bottom: 16px;\n padding: 12px;\n background: rgba(30, 64, 175, 0.05);\n border-radius: 8px;\n border: 1px solid rgba(59, 130, 246, 0.1);\n }\n\n .section-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n padding-bottom: 8px;\n border-bottom: 1px solid rgba(59, 130, 246, 0.2);\n }\n\n .section-title {\n font-weight: 600;\n font-size: 13px;\n color: #60a5fa;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n .control-group {\n margin-bottom: 16px;\n }\n\n .control-row {\n display: flex;\n align-items: center;\n margin-bottom: 12px;\n }\n\n .control-label {\n flex: 1;\n font-size: 14px;\n color: #cbd5e1;\n font-weight: 500;\n }\n\n .control-value {\n min-width: 60px;\n text-align: right;\n font-size: 13px;\n color: #94a3b8;\n font-family: 'Consolas', monospace;\n background: rgba(15, 23, 42, 0.5);\n padding: 4px 8px;\n border-radius: 4px;\n border: 1px solid rgba(59, 130, 246, 0.2);\n }\n\n .toggle-switch {\n position: relative;\n width: 48px;\n height: 24px;\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: #334155;\n transition: .3s;\n border-radius: 24px;\n border: 1px solid rgba(59, 130, 246, 0.2);\n }\n\n .toggle-slider:before {\n position: absolute;\n content: \"\";\n height: 20px;\n width: 20px;\n left: 2px;\n bottom: 2px;\n background-color: #94a3b8;\n transition: .3s;\n border-radius: 50%;\n }\n\n .toggle-checkbox:checked + .toggle-slider {\n background-color: #3b82f6;\n border-color: #3b82f6;\n }\n\n .toggle-checkbox:checked + .toggle-slider:before {\n transform: translateX(24px);\n background-color: #ffffff;\n }\n\n .slider-container {\n flex: 2;\n margin: 0 12px;\n }\n\n .slider {\n width: 100%;\n height: 6px;\n -webkit-appearance: none;\n appearance: none;\n background: linear-gradient(to right, #334155, #475569);\n outline: none;\n border-radius: 3px;\n border: 1px solid rgba(59, 130, 246, 0.2);\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: #3b82f6;\n cursor: pointer;\n box-shadow: 0 2px 8px rgba(59, 130, 246, 0.4);\n transition: all 0.2s;\n }\n\n .slider::-webkit-slider-thumb:hover {\n background: #60a5fa;\n transform: scale(1.1);\n }\n\n .slider::-moz-range-thumb {\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: #3b82f6;\n cursor: pointer;\n border: none;\n box-shadow: 0 2px 8px rgba(59, 130, 246, 0.4);\n }\n\n .preset-buttons {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 6px;\n margin-top: 10px;\n }\n\n .preset-btn, .reset-btn {\n padding: 6px 12px;\n background: rgba(59, 130, 246, 0.1);\n border: 1px solid rgba(59, 130, 246, 0.3);\n border-radius: 4px;\n color: #93c5fd;\n font-size: 12px;\n cursor: pointer;\n transition: all 0.2s;\n font-weight: 500;\n }\n\n .preset-btn:hover, .reset-btn:hover {\n background: rgba(59, 130, 246, 0.2);\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);\n }\n\n .preset-btn.active {\n background: #3b82f6;\n color: white;\n border-color: #3b82f6;\n }\n\n /* 三态模式选择样式 */\n .radio-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 8px;\n }\n\n .radio-option {\n display: flex;\n align-items: center;\n gap: 12px;\n cursor: pointer;\n padding: 8px 12px;\n border-radius: 6px;\n transition: all 0.2s;\n user-select: none;\n }\n\n .radio-option:hover {\n background: rgba(59, 130, 246, 0.1);\n }\n\n .radio-input {\n opacity: 0;\n width: 0;\n height: 0;\n position: absolute;\n }\n\n .radio-custom {\n width: 20px;\n height: 20px;\n border: 2px solid rgba(59, 130, 246, 0.3);\n border-radius: 50%;\n position: relative;\n transition: all 0.2s;\n flex-shrink: 0;\n }\n\n .radio-input:checked + .radio-custom {\n border-color: #3b82f6;\n background: rgba(59, 130, 246, 0.1);\n }\n\n .radio-input:checked + .radio-custom::after {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 8px;\n height: 8px;\n background: #3b82f6;\n border-radius: 50%;\n }\n\n .radio-label {\n font-size: 14px;\n color: #cbd5e1;\n font-weight: 500;\n }\n\n .radio-input:checked ~ .radio-label {\n color: #60a5fa;\n font-weight: 600;\n }\n\n .reset-btn {\n background: rgba(239, 68, 68, 0.1);\n border-color: rgba(239, 68, 68, 0.3);\n color: #fca5a5;\n }\n\n .reset-btn:hover {\n background: rgba(239, 68, 68, 0.2);\n }\n\n .hint-text {\n font-size: 12px;\n color: #94a3b8;\n font-style: italic;\n margin-top: 6px;\n }\n\n ::-webkit-scrollbar {\n width: 8px;\n height: 8px;\n }\n\n ::-webkit-scrollbar-track {\n background: rgba(15, 23, 42, 0.5);\n border-radius: 4px;\n }\n\n ::-webkit-scrollbar-thumb {\n background: linear-gradient(45deg, #3b82f6, #60a5fa);\n border-radius: 4px;\n border: 1px solid rgba(15, 23, 42, 0.5);\n }\n\n ::-webkit-scrollbar-thumb:hover {\n background: linear-gradient(45deg, #60a5fa, #93c5fd);\n box-shadow: 0 0 8px rgba(59, 130, 246, 0.3);\n }\n\n * {\n scrollbar-width: thin;\n scrollbar-color: #3b82f6 rgba(15, 23, 42, 0.5);\n }\n\n .config-buttons {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 10px;\n margin-top: 16px;\n }\n\n .config-btn {\n padding: 12px 20px;\n background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(59, 130, 246, 0.2));\n border: 1px solid rgba(59, 130, 246, 0.3);\n border-radius: 8px;\n color: #60a5fa;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n }\n\n .config-btn:hover {\n background: linear-gradient(135deg, rgba(59, 130, 246, 0.2), rgba(59, 130, 246, 0.3));\n transform: translateY(-1px);\n box-shadow: 0 4px 16px rgba(59, 130, 246, 0.2);\n }\n\n .config-status {\n margin-top: 16px;\n padding: 12px;\n background: rgba(15, 23, 42, 0.5);\n border-radius: 8px;\n border: 1px solid rgba(59, 130, 246, 0.1);\n }\n\n .config-status-text {\n font-size: 12px;\n color: #94a3b8;\n margin-bottom: 4px;\n }\n\n .config-filename {\n font-size: 11px;\n color: #64748b;\n font-family: 'Consolas', monospace;\n }\n\n /* 颜色选择器样式 */\n .color-picker {\n width: 40px;\n height: 28px;\n border: 2px solid rgba(59, 130, 246, 0.3);\n border-radius: 6px;\n cursor: pointer;\n background: none;\n padding: 2px;\n transition: all 0.2s;\n }\n\n .color-picker:hover {\n border-color: #3b82f6;\n transform: scale(1.05);\n }\n\n.color-picker:focus {\n outline: none;\n border-color: #3b82f6;\n box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);\n }\n\n /* 预设卡片样式 */\n .preset-grid {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 10px;\n margin-top: 12px;\n }\n\n .preset-card {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 14px 10px;\n background: rgba(30, 64, 175, 0.08);\n border: 2px solid rgba(59, 130, 246, 0.2);\n border-radius: 10px;\n cursor: pointer;\n transition: all 0.25s ease;\n font-family: 'Segoe UI', Arial, sans-serif;\n }\n\n .preset-card:hover {\n background: rgba(59, 130, 246, 0.15);\n transform: translateY(-2px);\n border-color: rgba(59, 130, 246, 0.4);\n }\n\n .preset-card.active {\n border-color: #3b82f6;\n background: rgba(59, 130, 246, 0.2);\n box-shadow: 0 0 12px rgba(59, 130, 246, 0.3);\n }\n\n .preset-icon {\n font-size: 26px;\n margin-bottom: 6px;\n }\n\n .preset-name {\n font-size: 12px;\n font-weight: 500;\n color: #e2e8f0;\n }\n\n .preset-card.active .preset-name {\n color: #60a5fa;\n font-weight: 600;\n }\n\n .preset-hint {\n margin-top: 10px;\n font-size: 11px;\n color: #64748b;\n text-align: center;\n padding: 6px;\n background: rgba(15, 23, 42, 0.3);\n border-radius: 4px;\n }\n\n /* 快捷操作按钮 */\n .quick-actions {\n display: flex;\n gap: 10px;\n margin-top: 12px;\n }\n\n .action-btn {\n flex: 1;\n padding: 12px;\n border: none;\n border-radius: 8px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n }\n\n .action-btn.success {\n background: linear-gradient(135deg, #10b981, #059669);\n color: white;\n }\n\n .action-btn.success:hover {\n box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);\n transform: translateY(-1px);\n }\n\n .action-btn.danger {\n background: linear-gradient(135deg, #ef4444, #dc2626);\n color: white;\n }\n\n .action-btn.danger:hover {\n box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);\n transform: translateY(-1px);\n }\n\n /* 文件操作按钮 */\n .file-actions {\n display: flex;\n gap: 8px;\n margin-top: 12px;\n }\n\n .file-btn {\n flex: 1;\n padding: 10px 12px;\n background: rgba(59, 130, 246, 0.1);\n border: 1px solid rgba(59, 130, 246, 0.3);\n border-radius: 6px;\n color: #93c5fd;\n font-size: 12px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n }\n\n .file-btn:hover {\n background: rgba(59, 130, 246, 0.2);\n transform: translateY(-1px);\n }\n\n .file-btn.warning {\n background: rgba(239, 68, 68, 0.1);\n border-color: rgba(239, 68, 68, 0.3);\n color: #fca5a5;\n }\n\n .file-btn.warning:hover {\n background: rgba(239, 68, 68, 0.2);\n }\n\n /* 时间选择器样式 */\n .time-picker-container {\n margin-bottom: 12px;\n padding: 10px;\n background: rgba(15, 23, 42, 0.4);\n border-radius: 8px;\n border: 1px solid rgba(59, 130, 246, 0.15);\n }\n\n .time-picker-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n }\n\n .time-picker-label {\n font-size: 13px;\n color: #94a3b8;\n white-space: nowrap;\n }\n\n .timezone-hint {\n font-size: 11px;\n color: #64748b;\n font-weight: normal;\n }\n\n .time-picker-inputs {\n display: flex;\n align-items: center;\n gap: 4px;\n }\n\n .time-input {\n width: 45px;\n height: 32px;\n padding: 4px 6px;\n background: rgba(30, 64, 175, 0.15);\n border: 1px solid rgba(59, 130, 246, 0.3);\n border-radius: 6px;\n color: #e2e8f0;\n font-size: 14px;\n font-weight: 500;\n text-align: center;\n outline: none;\n transition: all 0.2s;\n }\n\n .time-input:focus {\n border-color: #3b82f6;\n box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);\n }\n\n .time-input::-webkit-inner-spin-button,\n .time-input::-webkit-outer-spin-button {\n opacity: 0;\n }\n\n .time-separator {\n color: #64748b;\n font-size: 16px;\n font-weight: bold;\n }\n\n .time-apply-btn {\n height: 32px;\n padding: 0 12px;\n margin-left: 6px;\n background: linear-gradient(135deg, #3b82f6, #2563eb);\n border: none;\n border-radius: 6px;\n color: white;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.2s;\n }\n\n .time-apply-btn:hover {\n background: linear-gradient(135deg, #60a5fa, #3b82f6);\n transform: translateY(-1px);\n box-shadow: 0 2px 8px rgba(59, 130, 246, 0.4);\n }\n\n .time-apply-btn:active {\n transform: translateY(0);\n }\n ",document.head.appendChild(e)}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)}createPanelContent(){const e=document.createElement("div");e.className="panel-content-wrapper",e.id="env-panel-content",e.style.display=this.isPanelVisible?"flex":"none",e.innerHTML=`\n <div class="tab-navigation">\n <button class="tab-button active" data-tab="config">\n <span class="tab-icon">⚙️</span>\n <span>配置管理</span>\n </button>\n <button class="tab-button" data-tab="lighting">\n <span class="tab-icon">☀️</span>\n <span>光照系统</span>\n </button>\n <button class="tab-button" data-tab="shadow">\n <span class="tab-icon">🌑</span>\n <span>阴影系统</span>\n </button>\n <button class="tab-button" data-tab="atmosphere">\n <span class="tab-icon">🌤️</span>\n <span>大气系统</span>\n </button>\n <button class="tab-button" data-tab="clouds">\n <span class="tab-icon">☁️</span>\n <span>云层系统</span>\n </button>\n <button class="tab-button" data-tab="fog">\n <span class="tab-icon">🌫️</span>\n <span>雾效系统</span>\n </button>\n <button class="tab-button" data-tab="postprocessing">\n <span class="tab-icon">🎨</span>\n <span>后处理</span>\n </button>\n <button class="tab-button" data-tab="water">\n <span class="tab-icon">💧</span>\n <span>水体系统</span>\n </button>\n </div>\n <div class="tab-content">\n <div id="config-tab" class="tab-panel active">\n ${this.createConfigManagementSection()}\n </div>\n <div id="lighting-tab" class="tab-panel">\n <div id="lighting-content"></div>\n </div>\n <div id="shadow-tab" class="tab-panel">\n <div id="shadow-content"></div>\n </div>\n <div id="atmosphere-tab" class="tab-panel">\n <div id="atmosphere-content"></div>\n </div>\n <div id="clouds-tab" class="tab-panel">\n <div id="clouds-content"></div>\n </div>\n <div id="fog-tab" class="tab-panel">\n <div id="fog-content"></div>\n </div>\n <div id="postprocessing-tab" class="tab-panel">\n <div id="postprocessing-content"></div>\n </div>\n <div id="water-tab" class="tab-panel">\n <div id="water-content"></div>\n </div>\n </div>\n `,this.panelContainer.appendChild(e),setTimeout(()=>{this.initEventListeners(),this.initConfigEventListeners(),this.lightingControl.createPanel(document.getElementById("lighting-content")),this.shadowControl.createPanel(document.getElementById("shadow-content")),this.atmosphereControl.createPanel(document.getElementById("atmosphere-content")),this.volumetricCloudControl.createPanel(document.getElementById("clouds-content")),this.fogControl.createPanel(document.getElementById("fog-content")),this.postProcessingControl.createPanel(document.getElementById("postprocessing-content")),this.waterControl.createPanel(document.getElementById("water-content")),this.lightingControl.initEventListeners(document.getElementById("lighting-content")),this.shadowControl.initEventListeners(document.getElementById("shadow-content")),this.atmosphereControl.initEventListeners(document.getElementById("atmosphere-content")),this.volumetricCloudControl.initEventListeners(document.getElementById("clouds-content")),this.fogControl.initEventListeners(document.getElementById("fog-content")),this.postProcessingControl.initEventListeners(document.getElementById("postprocessing-content")),this.waterControl.initEventListeners(document.getElementById("water-content"))},100)}createConfigManagementSection(){const e=le.getBuiltinPresets(),t=le.getCurrentPresetId();return`\n <div class="control-section">\n <div class="section-header">\n <span class="section-title">环境预设</span>\n </div>\n <div class="preset-grid">\n ${e.map(e=>{const n=e.id===t,i=this.getPresetIcon(e.id);return`\n <button class="preset-card ${n?"active":""}" data-preset-id="${e.id}">\n <span class="preset-icon">${i}</span>\n <span class="preset-name">${e.name}</span>\n </button>\n `}).join("")}\n </div>\n <div class="preset-hint" id="preset-hint">\n ${t?"当前: "+e.find(e=>e.id===t)?.name:"自定义配置"}\n </div>\n </div>\n\n <div class="control-section">\n <div class="section-header">\n <span class="section-title">快捷操作</span>\n </div>\n <div class="quick-actions">\n <button id="config-enable-all-btn" class="action-btn success">\n <span>✓</span> 全部启用\n </button>\n <button id="config-disable-all-btn" class="action-btn danger">\n <span>✕</span> 全部关闭\n </button>\n </div>\n </div>\n\n <div class="control-section">\n <div class="section-header">\n <span class="section-title">配置文件</span>\n </div>\n <div class="file-actions">\n <button id="config-import-btn" class="file-btn">\n <span>📥</span> 导入\n </button>\n <button id="config-export-btn" class="file-btn">\n <span>📤</span> 导出\n </button>\n <button id="config-reset-btn" class="file-btn warning">\n <span>🔄</span> 重置\n </button>\n </div>\n <input type="file" id="config-file-input" accept=".json" style="display:none">\n </div>\n `}getPresetIcon(e){return{"morning-clear":"🌅","afternoon-cloudy":"⛅","sunset-foggy":"🌇","night-starry":"🌃","noon-bright":"☀️","overcast-rainy":"🌧️"}[e]||"🌍"}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"),t.style.display="flex",e.querySelector(".panel-toggle-icon").textContent="▼"):(this.panelContainer.classList.add("panel-collapsed"),t.style.display="none",e.querySelector(".panel-toggle-icon").textContent="▲")});document.querySelectorAll(".tab-button").forEach(e=>{e.addEventListener("click",()=>{const t=e.getAttribute("data-tab");t&&this.switchTab(t)})}),setTimeout(()=>{e&&e.click()},0)}switchTab(e){const t=document.querySelectorAll(".tab-button"),n=document.querySelectorAll(".tab-panel");t.forEach(t=>{t.classList.remove("active"),t.getAttribute("data-tab")===e&&t.classList.add("active")}),n.forEach(t=>{t.classList.remove("active"),t.id===`${e}-tab`&&t.classList.add("active")}),this.activeTab=e}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-enable-all-btn"),a=document.getElementById("config-disable-all-btn");this.initPresetCardListeners(),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.handleEnableAllEffects()}),a?.addEventListener("click",()=>{this.handleDisableAllEffects()})}initPresetCardListeners(){document.querySelectorAll(".preset-card").forEach(e=>{e.addEventListener("click",()=>{const t=e.getAttribute("data-preset-id");t&&this.handlePresetSelect(t)})})}handlePresetSelect(e){const t=le.getPresetById(e);if(!t)return;le.applyPreset(e);const n=le.getCurrentConfig();this.applyPresetConfig(n),document.querySelectorAll(".preset-card").forEach(t=>{t.classList.remove("active"),t.getAttribute("data-preset-id")===e&&t.classList.add("active")});const i=document.getElementById("preset-hint");i&&(i.textContent=`当前: ${t.name}`),this.showNotification(`已应用预设: ${t.name}`,"success")}applyPresetConfig(t){if(t.time){const e=t.time.hour-8,n=new Date;n.setUTCHours(e,t.time.minute,0,0),this.viewer.clock.currentTime=A.fromDate(n)}t.lighting&&(t.lighting.enabled?(this.render.enableSystem("lighting"),this.render.updateSystemConfig("lighting",{intensity:t.lighting.intensity,ambientIntensity:t.lighting.ambientIntensity,followSun:t.lighting.followSun})):this.render.disableSystem("lighting")),t.shadow&&(t.shadow.enabled?(this.render.enableSystem("shadow"),this.render.updateSystemConfig("shadow",{darkness:t.shadow.darkness,softShadows:t.shadow.softShadows})):this.render.disableSystem("shadow")),t.atmosphere&&(t.atmosphere.enabled?(this.render.enableSystem("atmosphereScattering"),this.render.updateSystemConfig("atmosphereScattering",{atmosphereIntensity:t.atmosphere.intensity,rayleighIntensity:t.atmosphere.rayleighIntensity,mieIntensity:t.atmosphere.mieIntensity,absorptionIntensity:t.atmosphere.absorptionIntensity})):this.render.disableSystem("atmosphereScattering")),t.clouds&&(t.clouds.enabled?(this.render.updateSystemConfig("volumetricClouds",{cloudCover:t.clouds.cover,cloudBase:t.clouds.base,cloudTop:t.clouds.top,cloudLightIntensity:t.clouds.lightIntensity||5.3,cloudThickness:(t.clouds.top||6e3)-(t.clouds.base||2e3),windVector:new e(t.clouds.windSpeed||5,0,0)}),this.render.enableSystem("volumetricClouds")):this.render.disableSystem("volumetricClouds")),t.fog&&(t.fog.enabled?("distance"!==t.fog.type&&"both"!==t.fog.type||this.render.enableSystem("distanceFog"),"height"!==t.fog.type&&"both"!==t.fog.type||this.render.enableSystem("heightFog")):(this.render.disableSystem("distanceFog"),this.render.disableSystem("heightFog"))),t.postProcessing&&(t.postProcessing.enabled?(this.render.enableSystem("postProcessing"),this.render.updateSystemConfig("postProcessing",{brightness:t.postProcessing.brightness,contrast:t.postProcessing.contrast,saturation:t.postProcessing.saturation,gamma:t.postProcessing.gamma})):this.render.disableSystem("postProcessing")),this.updateAllControlPanels(),this.viewer.scene.requestRender()}async handleConfigImport(e){try{const t=await e.text();if(le.importFromJson(t)){const e=le.getCurrentConfig();this.applyPresetConfig(e),this.showNotification("配置导入成功!","success"),document.querySelectorAll(".preset-card").forEach(e=>{e.classList.remove("active")});const t=document.getElementById("preset-hint");t&&(t.textContent="当前: 自定义配置")}else this.showNotification("配置格式无效","error")}catch(e){this.showNotification(`导入失败: ${e.message}`,"error")}}handleConfigExport(){try{const e=le.exportToJson(),t=`environment-config-${(new Date).toISOString().replace(/[:.]/g,"-").slice(0,19)}.json`,n=new Blob([e],{type:"application/json"}),i=URL.createObjectURL(n),s=document.createElement("a");s.href=i,s.download=t,document.body.appendChild(s),s.click(),document.body.removeChild(s),URL.revokeObjectURL(i),this.showNotification("配置导出成功!","success")}catch(e){this.showNotification(`导出失败: ${e.message}`,"error")}}updateAllControlPanels(){this.renderConfig=this.render.config;const e=new CustomEvent("render-config-updated",{detail:{config:le.getCurrentConfig()}});document.dispatchEvent(e)}handleEnableAllEffects(){le.enableAllEffects();const e=le.getCurrentConfig();this.applyPresetConfig(e),document.querySelectorAll(".preset-card").forEach(e=>{e.classList.remove("active")});const t=document.getElementById("preset-hint");t&&(t.textContent="当前: 自定义配置(全部启用)"),this.showNotification("所有效果已开启!","success")}handleDisableAllEffects(){le.disableAllEffects();const e=le.getCurrentConfig();this.applyPresetConfig(e),document.querySelectorAll(".preset-card").forEach(e=>{e.classList.remove("active")});const t=document.getElementById("preset-hint");t&&(t.textContent="当前: 自定义配置(全部关闭)"),this.showNotification("所有效果已关闭!","success")}handleConfigReset(){le.resetToDefault();const e=le.getCurrentConfig();this.applyPresetConfig(e),document.querySelectorAll(".preset-card").forEach(e=>{e.classList.remove("active"),"morning-clear"===e.getAttribute("data-preset-id")&&e.classList.add("active")});const t=document.getElementById("preset-hint");t&&(t.textContent="当前: 清晨晴朗"),this.showNotification("已重置为默认预设","success")}showNotification(e,t="info"){const n=document.querySelector(".config-snackbar");n&&n.remove();const i=document.createElement("div");i.className=`config-snackbar ${t}`,i.style.cssText=`\n position: fixed;\n bottom: 20px;\n right: 20px;\n background: rgba(15, 23, 42, 0.95);\n color: #e2e8f0;\n padding: 16px 24px;\n border-radius: 8px;\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);\n z-index: 3000;\n display: flex;\n align-items: center;\n gap: 12px;\n animation: slideInUp 0.3s ease;\n border-left: 4px solid ${"success"===t?"#10b981":"error"===t?"#ef4444":"warning"===t?"#f59e0b":"#3b82f6"};\n `,i.innerHTML=`\n <span style="font-weight: 500;">${e}</span>\n <button class="config-snackbar-close" style="\n background: none;\n border: none;\n color: #94a3b8;\n font-size: 20px;\n cursor: pointer;\n padding: 0 8px;\n transition: color 0.2s;\n ">×</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)}applyDefaultPresets(){if(le.getPresetById("morning-clear")){le.applyPreset("morning-clear");const e=le.getCurrentConfig();this.applyPresetConfig(e)}}resetAll(){this.volumetricCloudControl.reset(),this.atmosphereControl.reset(),this.fogControl.reset(),this.lightingControl.reset(),this.shadowControl.reset(),this.postProcessingControl.reset(),this.waterControl.reset()}togglePanel(e){this.panelContainer.style.display=void 0!==e?e?"block":"none":"none"===this.panelContainer.style.display?"block":"none"}clearPanel(){this.panelContainer.innerHTML=""}}class Fe{container=null;panelElement=null;isVisible=!1;panelId="";onClose;config={position:"right",width:400,height:"85vh",minWidth:300,maxWidth:600};constructor(e){e&&(this.config={...this.config,...e})}show(){this.panelElement&&!this.isVisible&&(this.isVisible=!0,this.updateVisibility(),this.onShow())}hide(){this.panelElement&&this.isVisible&&(this.isVisible=!1,this.updateVisibility(),this.onHide())}toggle(){return this.isVisible?this.hide():this.show(),this.isVisible}updateVisibility(){this.panelElement&&(this.isVisible?(this.panelElement.style.display="flex",this.panelElement.classList.add("visible"),this.panelElement.classList.remove("hidden")):(this.panelElement.style.display="none",this.panelElement.classList.add("hidden"),this.panelElement.classList.remove("visible")))}setPosition(e,t=0){this.container&&(this.container.style.left=`${e}px`,this.container.style.top=`${t}px`)}setSize(e,t){this.panelElement&&(this.panelElement.style.width=`${e}px`,this.panelElement.style.height="number"==typeof t?`${t}px`:t)}getState(){return{isVisible:this.isVisible,panelId:this.panelId,config:this.config}}getOrCreateContainer(e){let t=document.getElementById(e);return t||(t=document.createElement("div"),t.id=e,t.className="panel-container",document.body.appendChild(t)),t}getOrCreateContainerWithClass(e){let t=document.getElementById(e);return t||(t=document.createElement("div"),t.id=e,t.className=e,document.body.appendChild(t)),t}onShow(){}onHide(){}destroy(){this.panelElement&&this.panelElement.parentNode&&this.panelElement.parentNode.removeChild(this.panelElement),this.panelElement=null,this.container=null}}class Me extends Fe{layerSystem;engine;plotData=[];isExpanded=new Map;configPath="";constructor(e,t){if(super({position:"right",width:400,height:"85vh",minWidth:320,maxWidth:500,zIndex:1e3,...t}),!e)return;const{configPath:n}=t;e&&e.engine&&n&&(this.layerSystem=e.getSystem("layer"),this.layerSystem&&(this.panelId="plotting-control",this.engine=e.engine,this.configPath=n,this.init()))}async init(){try{this.container=this.getOrCreatePanelContainer(),await this.loadPlotData(),this.createPanel(),this.bindEvents(),this.isVisible=!0,this.hide()}catch(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){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-clear" title="清空">\n <i class="fa fa-trash"></i>\n </button>\n <button class="btn-icon" id="panel-refresh" title="刷新">\n <i class="fa fa-refresh"></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: 0px;\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 8px;\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: 80px;\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: 35px;\n color: #8b949e;\n height: 35px;\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.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",a=n?"fa fa-chevron-down":"fa fa-chevron-right",o=document.createElement("div");o.className="category-header",o.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="${a} category-toggle ${n?"expanded":""}"></i>\n </div>\n `;const r=document.createElement("div");if(r.className="category-content",r.style.height=n?"auto":"0",n||r.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)}),r.appendChild(n)}else r.innerHTML='<div class="empty-category">暂无标绘项</div>';return i.appendChild(o),i.appendChild(r),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 a=document.createElement("div");a.className="subcategory-header",a.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 o=document.createElement("div");if(o.className="subcategory-content",i||(o.classList.add("collapsed"),o.style.height="0"),Array.isArray(e.data)&&e.data.length>0){const t=this.createButtonsGrid(e.data);o.appendChild(t)}else if("model"===e.kind){const t=e;if(t.label&&t.path){const e=this.createModelButton(t);o.appendChild(e)}else o.innerHTML='<div class="empty-subcategory">暂无模型</div>'}else o.innerHTML='<div class="empty-subcategory">暂无标绘项</div>';return s.appendChild(a),s.appendChild(o),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-clear");t?.addEventListener("click",()=>{this.clearPanel()})}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 a=!!n.classList.contains("collapsed");i.className=a?"fa fa-chevron-down category-toggle expanded":"fa fa-chevron-right category-toggle",a?(n.classList.remove("collapsed"),n.style.height="auto"):(n.classList.add("collapsed"),n.style.height="0"),this.isExpanded.set(t,a)}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){if(this.engine.plot&&this.engine.plot.createPlot){let t=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||""});const n={id:`entity_${t.gvolID}`,name:t.name||`对象_${t.gvolID}`,type:"entity",visible:t.show,opacity:1,cesiumObject:t};this.layerSystem.addEntity(n)}}executeModelCommand(e){}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()}catch(e){this.showErrorMessage("刷新失败,请检查配置文件")}}clearPanel(){this.engine.graphicLayer.removeAll(),this.layerSystem.removeEntities()}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)}}onShow(){super.onShow()}onHide(){super.onHide()}destroy(){this.panelElement&&this.panelElement.parentNode&&this.panelElement.parentNode.removeChild(this.panelElement),this.panelElement=null}}class Ae extends Fe{system;statsContainer=null;performanceData=[];maxDataPoints=60;animationFrameId=null;currentFPS=0;constructor(e,t){super({position:"right",width:400,height:"85vh",minWidth:320,maxWidth:500,zIndex:1e3,...t}),e&&(this.system=e.getSystem("tilesetSearch"),this.system&&(this.panelId="tilset-control",this.init(),this.createStatsPanel(),this.startPerformanceMonitoring(),this.system.hideControl()))}async init(){if(this.container=document.getElementById("tileset-input-container"),this.container)return void this.system.setContainer(this.container);this.container=document.createElement("div"),this.container.id="tileset-input-container",this.container.className="tileset-search-control",this.container.style.cssText="\n position: fixed;\n top: 20px;\n left: 20px;\n width: 280px;\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 ",this.addStyles();const e=document.createElement("div");e.className="panel-header",e.style.cssText="\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 user-select: none;\n ",e.innerHTML='\n <div class="panel-title">\n <span>🗺️ 3D模型加载</span>\n </div>\n ';const t=document.createElement("div");t.className="panel-content",t.style.cssText="\n padding: 15px;\n ";const n=document.createElement("label");n.textContent="输入类型:",n.style.cssText="\n display: block;\n margin-bottom: 8px;\n font-size: 13px;\n color: #aaa;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n ";const i=document.createElement("select");i.id="tileset-type-select",i.style.cssText="\n width: 100%;\n padding: 10px 25px 10px 12px;\n margin-bottom: 12px;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 6px;\n color: #fff;\n font-size: 13px;\n box-sizing: border-box;\n transition: all 0.2s;\n cursor: pointer;\n appearance: none;\n -webkit-appearance: none;\n -moz-appearance: none;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23fff' d='M6 9L1 4h10z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 8px center;\n ";[{value:"url",text:"URL地址"},{value:"city",text:"城市名称&城市编码"}].forEach(e=>{const t=document.createElement("option");t.value=e.value,t.textContent=e.text,i.appendChild(t)});const s=document.createElement("label");s.id="tileset-input-label",s.textContent="3DTiles URL:",s.style.cssText="\n display: block;\n margin-bottom: 8px;\n font-size: 13px;\n color: #aaa;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n ";const a=document.createElement("input");a.type="text",a.id="tileset-url-input",a.placeholder="请输入3DTiles地址",a.style.cssText="\n width: 100%;\n padding: 8px 12px;\n margin-bottom: 12px;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 6px;\n color: #fff;\n font-size: 13px;\n box-sizing: border-box;\n transition: all 0.2s;\n ",i.onchange=()=>{const e=i.value,t=document.getElementById("tileset-input-label"),n=document.getElementById("tileset-url-input");"url"===e?(t.textContent="3DTiles URL:",n.placeholder="请输入3DTiles地址"):"city"===e&&(t.textContent="城市名称或编码:",n.placeholder="请输入城市名称或编码")},a.onfocus=()=>{a.style.borderColor="rgba(76, 175, 80, 0.6)",a.style.boxShadow="0 0 0 2px rgba(76, 175, 80, 0.2)"},a.onblur=()=>{a.style.borderColor="rgba(255, 255, 255, 0.2)",a.style.boxShadow="none"};const o={"高度":"f_height","楼层":"f_floor","层数":"f_floor","面积":"f_area","类型":"f_type","功能类型":"f_type","名称":"f_name","建筑名称":"f_name","底商":"f_shop","颜色":"f_color"},r=document.createElement("div");r.style.cssText="\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n margin-top: 8px;\n width: 100%;\n box-sizing: border-box;\n ";const l=document.createElement("input");l.type="text",l.id="field-name-input",l.value="高度",l.placeholder="字段",l.style.cssText="\n flex: 1;\n width: 80px;\n padding: 6px 8px;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 4px;\n color: #fff;\n font-size: 12px;\n box-sizing: border-box;\n ",l.onfocus=()=>{l.style.borderColor="rgba(76, 152, 175, 0.6)",l.style.boxShadow="0 0 0 2px rgba(76, 152, 175, 0.2)"},l.onblur=()=>{l.style.borderColor="rgba(255, 255, 255, 0.2)",l.style.boxShadow="none"};const d=document.createElement("input");d.type="text",d.id="operator-input",d.value="<",d.placeholder="符",d.style.cssText="\n width: 40px;\n padding: 6px 4px;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 4px;\n color: #fff;\n font-size: 12px;\n box-sizing: border-box;\n text-align: center;\n ",d.onfocus=()=>{d.style.borderColor="rgba(76, 152, 175, 0.6)",d.style.boxShadow="0 0 0 2px rgba(76, 152, 175, 0.2)"},d.onblur=()=>{d.style.borderColor="rgba(255, 255, 255, 0.2)",d.style.boxShadow="none"};const c=document.createElement("input");c.type="number",c.id="height-threshold-input",c.value="10",c.min="0",c.placeholder="值",c.style.cssText="\n flex: 1;\n width: 40px;\n padding: 6px 8px;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 4px;\n color: #fff;\n font-size: 12px;\n box-sizing: border-box;\n ",c.onfocus=()=>{c.style.borderColor="rgba(76, 152, 175, 0.6)",c.style.boxShadow="0 0 0 2px rgba(76, 152, 175, 0.2)"},c.onblur=()=>{c.style.borderColor="rgba(255, 255, 255, 0.2)",c.style.boxShadow="none"};const h=document.createElement("button");h.textContent="高亮",h.style.cssText="\n padding: 6px 12px;\n background: rgba(76, 152, 175, 0.9);\n color: white;\n border: 1px solid rgba(34, 81, 119, 0.8);\n border-radius: 4px;\n cursor: pointer;\n font-size: 12px;\n font-weight: 600;\n transition: all 0.3s;\n white-space: nowrap;\n ";const p=document.createElement("div");p.id="highlight-error-message",p.style.cssText="\n margin-top: 6px;\n padding: 6px 10px;\n background: rgba(220, 53, 69, 0.15);\n border: 1px solid rgba(220, 53, 69, 0.4);\n border-radius: 4px;\n color: #ff6b7a;\n font-size: 11px;\n display: none;\n word-wrap: break-word;\n ";const u=e=>{p.textContent=e,p.style.display="block"};h.onclick=()=>{const e=l.value,t=d.value||">",n=parseFloat(c.value);p.style.display="none";const i=(e=>{const t=e.trim();return t.startsWith("f_")?t:o[t]?o[t]:null})(e);if(!i)return void u("无效的字段名,请输入有效的字段名(如:高度、楼层、面积、类型等)");[">","<",">=","<=","==","!="].includes(t)?isNaN(n)?u("请输入有效的数值"):this.system.setStyleTest(i,t,n):u("无效的操作符,请输入 >, <, >=, <=, ==, != 之一")},h.onmouseover=()=>{h.style.backgroundColor="rgba(160, 220, 238, 0.9)",h.style.transform="translateY(-1px)",h.style.boxShadow="0 2px 8px rgba(76, 175, 80, 0.3)"},h.onmouseout=()=>{h.style.backgroundColor="rgba(76, 152, 175, 0.9)",h.style.transform="translateY(0)",h.style.boxShadow="none"},r.appendChild(l),r.appendChild(d),r.appendChild(c),r.appendChild(h),h.onmouseover=()=>{h.style.backgroundColor="rgba(160, 220, 238, 0.9)",h.style.transform="translateY(-1px)",h.style.boxShadow="0 2px 8px rgba(76, 175, 80, 0.3)"},h.onmouseout=()=>{h.style.backgroundColor="rgba(76, 152, 175, 0.9)",h.style.transform="translateY(0)",h.style.boxShadow="none"};const g=document.createElement("button");g.textContent="加载",g.style.cssText="\n width: 100%;\n padding: 8px;\n background: rgba(76, 175, 80, 0.9);\n color: white;\n border: 1px solid rgba(76, 175, 80, 0.8);\n border-radius: 6px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 600;\n transition: all 0.3s;\n ",g.onmouseover=()=>{g.style.backgroundColor="rgba(76, 175, 80, 1)",g.style.transform="translateY(-1px)",g.style.boxShadow="0 2px 8px rgba(76, 175, 80, 0.3)"},g.onmouseout=()=>{g.style.backgroundColor="rgba(76, 175, 80, 0.9)",g.style.transform="translateY(0)",g.style.boxShadow="none"},g.onclick=()=>{const e=document.getElementById("tileset-type-select").value,t=a.value.trim();if(t){const n={maximumScreenSpaceError:parseInt(document.getElementById("maximum-screen-space-error").value),maximumMemoryUsage:parseInt(document.getElementById("maximum-memory-usage").value),cacheBytes:1024*parseInt(document.getElementById("cache-bytes").value)*1024};this.system.loadByType(e,t,n).then(()=>{const e=document.getElementById("tileset-style-select").value;this.system.setTilesetStyle(e)})}else alert("请输入有效的值")},a.onkeypress=e=>{"Enter"===e.key&&g.click()};const m=document.createElement("label");m.textContent="调试参数:",m.style.cssText="\n display: block;\n margin-bottom: 8px;\n font-size: 13px;\n color: #aaa;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n ";const b=document.createElement("div");b.className="params-container",b.style.cssText="\n background: rgba(255, 255, 255, 0.05);\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: 6px;\n padding: 12px;\n margin-bottom: 12px;\n ";const f=document.createElement("div");f.style.cssText="\n margin-bottom: 10px;\n ";const y=document.createElement("label");y.textContent="最大屏幕空间误差:",y.style.cssText="\n display: block;\n margin-bottom: 4px;\n font-size: 12px;\n color: #ccc;\n ";const v=document.createElement("input");v.type="number",v.id="maximum-screen-space-error",v.value="80",v.min="1",v.max="200",v.style.cssText="\n width: 100%;\n padding: 6px 8px;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 4px;\n color: #fff;\n font-size: 12px;\n box-sizing: border-box;\n ",f.appendChild(y),f.appendChild(v);const x=document.createElement("div");x.style.cssText="\n margin-bottom: 10px;\n ";const w=document.createElement("label");w.textContent="最大内存使用(MB):",w.style.cssText="\n display: block;\n margin-bottom: 4px;\n font-size: 12px;\n color: #ccc;\n ";const C=document.createElement("input");C.type="number",C.id="maximum-memory-usage",C.value="8192",C.min="1024",C.max="32768",C.step="1024",C.style.cssText="\n width: 100%;\n padding: 6px 8px;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 4px;\n color: #fff;\n font-size: 12px;\n box-sizing: border-box;\n ",x.appendChild(w),x.appendChild(C);const E=document.createElement("div");E.style.cssText="\n margin-bottom: 0;\n ";const S=document.createElement("label");S.textContent="缓存大小(MB):",S.style.cssText="\n display: block;\n margin-bottom: 4px;\n font-size: 12px;\n color: #ccc;\n ";const k=document.createElement("input");k.type="number",k.id="cache-bytes",k.value="4096",k.min="512",k.max="16384",k.step="512",k.style.cssText="\n width: 100%;\n padding: 6px 8px;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 4px;\n color: #fff;\n font-size: 12px;\n box-sizing: border-box;\n ";const I=document.createElement("div");I.className="checkboxes-container",I.style.cssText="\n margin-top: 12px;\n padding-top: 12px;\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n ";const B=this.createCheckboxGroup({id:"toggle-3dtile-inspector",label:"打开内置3DTileInspector",checked:!1}),L=this.createCheckboxGroup({id:"toggle-latlong-grid",label:"打开内置经纬网格",checked:!1});this.createCheckboxGroup({id:"toggle-grid",label:"打开网格",checked:!1});const P=document.createElement("div");P.id="grid-size-control",P.style.cssText="\n margin-top: 8px;\n margin-left: 24px;\n display: none; /* 默认隐藏 */\n ";const T=document.createElement("label");T.textContent="网格大小:",T.style.cssText="\n display: block;\n margin-bottom: 4px;\n font-size: 12px;\n color: #ccc;\n ";const _=document.createElement("div");_.style.cssText="\n display: flex;\n align-items: center;\n gap: 8px;\n ";const F=document.createElement("input");F.type="range",F.id="grid-size-slider",F.min="1",F.max="100",F.value="10",F.step="1",F.style.cssText="\n flex: 1;\n height: 4px;\n background: rgba(255, 255, 255, 0.1);\n border-radius: 2px;\n outline: none;\n opacity: 0.7;\n transition: opacity 0.2s;\n ",F.oninput=()=>{M.textContent=`${F.value} m`,this.onGridSizeChange(parseInt(F.value))},F.onmouseover=()=>{F.style.opacity="1"},F.onmouseout=()=>{F.style.opacity="0.7"};const M=document.createElement("span");M.id="grid-size-value",M.textContent="10 m",M.style.cssText="\n font-size: 12px;\n color: #fff;\n min-width: 50px;\n text-align: right;\n ",_.appendChild(F),_.appendChild(M),P.appendChild(T),P.appendChild(_),I.appendChild(B),I.appendChild(L),I.appendChild(P),E.appendChild(S),E.appendChild(k),b.appendChild(f),b.appendChild(x),b.appendChild(E),b.appendChild(I);const A=document.createElement("label");A.textContent="样式选择:",A.style.cssText="\n display: block;\n margin-bottom: 8px;\n font-size: 13px;\n color: #aaa;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n ";const R=document.createElement("select");R.id="tileset-style-select",R.style.cssText="\n width: 100%;\n padding: 10px 25px 10px 12px;\n margin-bottom: 12px;\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 6px;\n color: #fff;\n font-size: 13px;\n box-sizing: border-box;\n transition: all 0.2s;\n cursor: pointer;\n appearance: none;\n -webkit-appearance: none;\n -moz-appearance: none;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23fff' d='M6 9L1 4h10z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 8px center;\n ";[{value:"default",text:"默认样式"},{value:"grayscale",text:"灰度样式"},{value:"blue",text:"蓝色调样式"},{value:"red",text:"红色调样式"},{value:"green",text:"绿色调样式"},{value:"highlight",text:"高亮样式"}].forEach(e=>{const t=document.createElement("option");t.value=e.value,t.textContent=e.text,R.appendChild(t)}),R.onchange=()=>{const e=R.value;this.system.setTilesetStyle(e)},t.appendChild(n),t.appendChild(i),t.appendChild(s),t.appendChild(a),t.appendChild(m),t.appendChild(b),t.appendChild(A),t.appendChild(R),t.appendChild(g),t.appendChild(r),t.appendChild(p),this.container.appendChild(e),this.container.appendChild(t),document.body.appendChild(this.container),this.system.setContainer(this.container),this.panelElement=this.container,setTimeout(()=>{this.bindEvents()},100),this.isVisible=!0,this.hide()}createStatsPanel(){if(this.statsContainer=document.getElementById("tileset-stats-container"),this.statsContainer)return;this.statsContainer=document.createElement("div"),this.statsContainer.id="tileset-stats-container",this.statsContainer.className="tileset-stats-control",this.statsContainer.style.cssText="\n position: fixed;\n top: 20px;\n right: 20px;\n display: flex;\n flex-direction: row;\n gap: 10px;\n z-index: 1000;\n ";[{id:"fps",label:"FPS",color:"#3b82f6",canvasId:"fps-chart"},{id:"cpu",label:"CPU (ms)",color:"#4CAF50",canvasId:"cpu-chart"},{id:"gpu",label:"GPU (ms)",color:"#ffc107",canvasId:"gpu-chart"}].forEach(e=>{const t=document.createElement("div");t.className="stat-item",t.style.cssText="\n width: 130px;\n height: 70px;\n background: rgba(30, 30, 40, 0.9);\n border-radius: 8px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);\n padding: 8px;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n transition: all 0.3s ease;\n ";const n=document.createElement("div");n.className="stat-header",n.style.cssText="\n display: flex;\n justify-content: space-between;\n align-items: center;\n ";const i=document.createElement("span");i.className="stat-label",i.style.cssText="\n font-size: 11px;\n color: #aaa;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n ",i.textContent=e.label;const s=document.createElement("span");s.id=`${e.id}-value`,s.className="stat-value",s.style.cssText=`\n font-size: 16px;\n font-weight: 700;\n color: ${e.color};\n font-family: 'Consolas', monospace;\n `,s.textContent="0";const a=document.createElement("div");a.className="canvas-container",a.style.cssText="\n position: relative;\n height: 35px;\n width: 100%;\n ";const o=document.createElement("canvas");o.id=e.canvasId,o.className="stat-chart",o.width=120,o.height=35,o.style.cssText="\n width: 100%;\n height: 100%;\n ",a.appendChild(o),n.appendChild(i),n.appendChild(s),t.appendChild(n),t.appendChild(a),this.statsContainer.appendChild(t)}),document.body.appendChild(this.statsContainer),this.statsContainer.style.display="none"}addStyles(){const e=document.createElement("style");e.textContent='\n .tileset-search-control input::placeholder {\n color: rgba(255, 255, 255, 0.4);\n }\n \n .tileset-search-control input:focus {\n outline: none;\n }\n \n .tileset-search-control button:active {\n transform: translateY(0);\n box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);\n }\n \n .tileset-search-control .panel-title {\n font-weight: 600;\n font-size: 14px;\n color: #fff;\n }\n \n .tileset-stats-control .panel-title {\n font-weight: 600;\n font-size: 14px;\n color: #fff;\n }\n \n .stat-chart {\n background: rgba(0, 0, 0, 0.2);\n border-radius: 3px;\n }\n \n .stat-item:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 25px rgba(0, 0, 0, 0.4);\n }\n \n .tileset-search-control .params-container input:focus {\n outline: none;\n border-color: rgba(76, 175, 80, 0.6);\n box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);\n }\n \n .tileset-search-control .params-container input:hover {\n border-color: rgba(255, 255, 255, 0.3);\n }\n \n /* 下拉框样式 */\n .tileset-search-control select {\n appearance: none;\n -webkit-appearance: none;\n -moz-appearance: none;\n background-image: url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' width=\'12\' height=\'12\' viewBox=\'0 0 12 12\'%3E%3Cpath fill=\'%23fff\' d=\'M6 9L1 4h10z\'/%3E%3C/svg%3E");\n background-repeat: no-repeat;\n background-position: right 8px center;\n padding-right: 25px;\n cursor: pointer;\n }\n \n .tileset-search-control select:hover {\n background-color: rgba(255, 255, 255, 0.15);\n border-color: rgba(255, 255, 255, 0.3);\n }\n \n .tileset-search-control select:focus {\n outline: none;\n border-color: rgba(76, 175, 80, 0.6);\n box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);\n background-color: rgba(255, 255, 255, 0.15);\n }\n \n /* 下拉框选项样式 */\n .tileset-search-control select option {\n background-color: #1a1a2e;\n color: #fff;\n padding: 8px;\n }\n \n .tileset-search-control select option:hover {\n background-color: #16213e;\n }\n \n .tileset-search-control select option:checked {\n background-color: #0f3460;\n }\n \n /* 样式选择相关样式 */\n .tileset-search-control #tileset-style-select:hover {\n background-color: rgba(255, 255, 255, 0.15);\n border-color: rgba(255, 255, 255, 0.3);\n }\n \n .tileset-search-control #tileset-style-select:focus {\n outline: none;\n border-color: rgba(76, 175, 80, 0.6);\n box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);\n background-color: rgba(255, 255, 255, 0.15);\n }\n\n /* 勾选框样式 */\n .checkbox-group {\n margin-bottom: 8px;\n }\n \n .checkbox-container {\n display: flex;\n align-items: center;\n cursor: pointer;\n user-select: none;\n }\n \n .checkbox-container input[type="checkbox"] {\n position: absolute;\n opacity: 0;\n cursor: pointer;\n }\n \n .checkmark {\n position: relative;\n height: 16px;\n width: 16px;\n background-color: rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.2);\n border-radius: 3px;\n transition: all 0.2s;\n flex-shrink: 0;\n }\n \n .checkbox-container:hover .checkmark {\n background-color: rgba(255, 255, 255, 0.15);\n border-color: rgba(76, 175, 80, 0.6);\n }\n \n .checkbox-container input:checked ~ .checkmark {\n background-color: rgba(76, 175, 80, 0.8);\n border-color: rgba(76, 175, 80, 1);\n }\n \n .checkmark:after {\n content: "";\n position: absolute;\n display: none;\n }\n \n .checkbox-container input:checked ~ .checkmark:after {\n display: block;\n }\n \n .checkbox-container .checkmark:after {\n left: 5px;\n top: 2px;\n width: 4px;\n height: 8px;\n border: solid white;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n }\n \n .checkbox-label {\n margin-left: 8px;\n font-size: 12px;\n color: #ccc;\n line-height: 1.4;\n }\n \n /* 滑块样式 */\n input[type="range"] {\n -webkit-appearance: none;\n appearance: none;\n }\n \n input[type="range"]::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n width: 16px;\n height: 16px;\n background: #4CAF50;\n cursor: pointer;\n border-radius: 50%;\n border: 2px solid rgba(255, 255, 255, 0.8);\n }\n \n input[type="range"]::-moz-range-thumb {\n width: 16px;\n height: 16px;\n background: #4CAF50;\n cursor: pointer;\n border-radius: 50%;\n border: 2px solid rgba(255, 255, 255, 0.8);\n }\n \n input[type="range"]::-webkit-slider-thumb:hover {\n background: #45a049;\n box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3);\n }\n ',document.head.appendChild(e)}bindEvents(){const e=document.getElementById("grid-size-control"),t=document.getElementById("toggle-grid");t&&(t.onchange=t=>{const n=t.target.checked;e.style.display=n?"block":"none",this.onToggleGrid(n)});const n=document.getElementById("toggle-3dtile-inspector");n&&(n.onchange=e=>{this.onToggle3DTileInspector(e.target.checked)});const i=document.getElementById("toggle-latlong-grid");i&&(i.onchange=e=>{this.onToggleLatLongGrid(e.target.checked)})}startPerformanceMonitoring(){let e=performance.now(),t=0,n=0,i=performance.now();const s=()=>{const a=performance.now();t=a-e,n=t*(.8+.4*Math.random());const o=Math.max(t,n);this.currentFPS=o>0?1e3/o:0,e=a,this.performanceData.push({fps:this.currentFPS,cpu:t,gpu:n,timestamp:a}),this.performanceData.length>this.maxDataPoints&&this.performanceData.shift(),a-i>=160?(this.updateStatsDisplay(),i=a):this.updateStatsValuesOnly(),this.animationFrameId=requestAnimationFrame(s)};this.animationFrameId=requestAnimationFrame(s)}updateStatsValuesOnly(){if(!this.statsContainer)return;const e=document.getElementById("fps-value");e&&(e.textContent=this.currentFPS.toFixed(0));const t=document.getElementById("cpu-value"),n=document.getElementById("gpu-value");if(this.performanceData.length>0){const e=this.performanceData[this.performanceData.length-1];t&&(t.textContent=e.cpu.toFixed(1)),n&&(n.textContent=e.gpu.toFixed(1))}}updateStatsDisplay(){this.statsContainer&&(this.updateStatsValuesOnly(),"undefined"!=typeof requestIdleCallback?requestIdleCallback(()=>this.drawCharts(),{timeout:50}):setTimeout(()=>this.drawCharts(),0))}drawCharts(){if(!this.statsContainer||"none"===this.statsContainer.style.display)return;[{id:"fps-chart",color:"#3b82f6",dataKey:"fps",maxValue:60},{id:"cpu-chart",color:"#4CAF50",dataKey:"cpu",maxValue:50},{id:"gpu-chart",color:"#ffc107",dataKey:"gpu",maxValue:50}].forEach(e=>{const t=document.getElementById(e.id);if(!t)return;const n=t.getContext("2d");if(!n)return;const i=document.createElement("canvas");i.width=t.width,i.height=t.height;const s=i.getContext("2d");if(!s)return;s.clearRect(0,0,i.width,i.height),s.strokeStyle="rgba(255, 255, 255, 0.1)",s.lineWidth=1;for(let e=0;e<=3;e++){const t=i.height/3*e;s.beginPath(),s.moveTo(0,t),s.lineTo(i.width,t),s.stroke()}s.beginPath();const a=i.width/(this.maxDataPoints-1);this.performanceData.forEach((t,n)=>{const o=n*a,r=i.height-t[e.dataKey]/e.maxValue*i.height;0===n?(s.moveTo(o,i.height),s.lineTo(o,r)):s.lineTo(o,r)}),s.lineTo(i.width,i.height),s.lineTo(0,i.height),s.closePath();const o=s.createLinearGradient(0,0,0,i.height);o.addColorStop(0,e.color+"90"),o.addColorStop(1,e.color+"30"),s.fillStyle=o,s.fill(),s.beginPath(),this.performanceData.forEach((t,n)=>{const o=n*a,r=i.height-t[e.dataKey]/e.maxValue*i.height;0===n?s.moveTo(o,r):s.lineTo(o,r)}),s.strokeStyle=e.color,s.lineWidth=2,s.stroke(),n.clearRect(0,0,t.width,t.height),n.drawImage(i,0,0)})}createPanel(){}getSystem(){return this.system}setTilesetStyle(e){this.system.setTilesetStyle(e)}resetToDefaultStyle(){this.system.resetToDefaultStyle()}getAvailableStyles(){return this.system.getAvailableStyles()}createCheckboxGroup(e){const t=document.createElement("div");t.className="checkbox-group";const n=document.createElement("label");n.className="checkbox-container";const i=document.createElement("input");i.type="checkbox",i.id=e.id,i.checked=e.checked;const s=document.createElement("span");s.className="checkmark";const a=document.createElement("span");return a.className="checkbox-label",a.textContent=e.label,n.appendChild(i),n.appendChild(s),n.appendChild(a),t.appendChild(n),t}onToggle3DTileInspector(e){this.system.openInner3dtilesInspector(e)}onToggleLatLongGrid(e){this.system.openInnerGrid(e)}onToggleGrid(e){this.system.openLonLatGrid(e)}onGridSizeChange(e){this.system.changeGridSize(e)}show(){super.show(),this.system.showControl(),this.statsContainer&&(this.statsContainer.style.display="flex")}onShow(){super.onShow(),this.system.isControlVisible(),this.statsContainer&&(this.statsContainer.style.display="flex")}onHide(){super.onHide(),this.system.hideControl(),this.statsContainer&&(this.statsContainer.style.display="none")}destroy(){null!==this.animationFrameId&&(cancelAnimationFrame(this.animationFrameId),this.animationFrameId=null),this.system.destroy(),this.statsContainer&&this.statsContainer.parentNode&&(this.statsContainer.parentNode.removeChild(this.statsContainer),this.statsContainer=null),this.container&&this.container.parentNode&&(this.container.parentNode.removeChild(this.container),this.container=null)}}class Re{container=null;sidebarElement=null;isExpanded=!0;activeTool="scene";isHovering=!1;panels=new Map;activePanel=null;toolbarData=[{id:"scene",label:"场景",icon:"icon iconfont gp-changjing",tooltip:"场景管理",hasPanel:!0},{id:"building",label:"表亲",icon:"icon iconfont gp-gongsixinxi-",tooltip:"表亲在线",hasPanel:!0},{id:"layer",label:"图层",icon:"icon iconfont gp-tucengguanlikaobei",tooltip:"图层管理",hasPanel:!0},{id:"plotting",label:"标绘",icon:"icon iconfont gp-biaohui",tooltip:"标绘工具",hasPanel:!0},{id:"3DTile",label:"3DTile",icon:"icon iconfont gp-sucaiguangchang",tooltip:"3DTile加载",hasPanel:!0},{id:"effect",label:"特效",icon:"icon iconfont gp-texiao",tooltip:"特效管理",hasPanel:!0},{id:"toolbox",label:"工具",icon:"icon iconfont gp-gongjuxiang",tooltip:"工具",hasPanel:!0}];panelConfig={defaultWidth:400,defaultHeight:"85vh",sidebarExpandedWidth:200,sidebarCollapsedWidth:72,zIndex:1e3};constructor(e){const{containerId:t="sidebar-manager",defaultActive:n="scene",initiallyExpanded:i=!0,panelConfig:s={}}=e||{};this.activeTool=n,this.isExpanded=i,this.panelConfig={...this.panelConfig,...s},this.container=this.getOrCreateContainer(t),this.init()}async init(){try{this.loadFontAwesome(),this.loadIconfont(),this.createSidebar(),this.bindEvents(),await this.initAllPanels()}catch(e){this.showErrorMessage("初始化侧边栏失败")}}async initAllPanels(){for(const[e,t]of this.panels)try{await t.init()}catch(e){}}registerPanel(e,t){this.panels.set(e,t),t.onClose=()=>{this.updateSidebarOnPanelClose(e)}}unregisterPanel(e){return this.panels.delete(e)}getPanel(e){return this.panels.get(e)}getAllPanels(){return new Map(this.panels)}showPanel(e){this.activePanel&&this.activePanel!==e&&this.hidePanel(this.activePanel);const t=this.panels.get(e);t?this.activePanel===e?this.hidePanel(e):(t.show(),this.activePanel=e,this.activeTool=e,this.updatePanelPosition(t),this.updateUI(),this.dispatchEvent("panel:show",{toolId:e,panel:t})):this.showNotification(`未找到 ${this.getToolLabel(e)} 面板`,"warning")}hidePanel(e){const t=this.panels.get(e);t&&t.hide(),this.activePanel===e&&(this.activePanel=null),this.updateUI(),this.dispatchEvent("panel:hide",{toolId:e})}hideAllPanels(){for(const[e,t]of this.panels)t.hide();this.activePanel=null,this.updateUI()}updateSidebarOnPanelClose(e){this.activePanel===e&&(this.activePanel=null,this.updateUI(),this.dispatchEvent("panel:close",{toolId:e}))}updatePanelPosition(e){const t=this.isExpanded?this.panelConfig.sidebarExpandedWidth:this.panelConfig.sidebarCollapsedWidth;e.setPosition(t,0)}toggleSidebar(){if(this.isExpanded=!this.isExpanded,this.updateUI(),this.activePanel){const e=this.panels.get(this.activePanel);e&&this.updatePanelPosition(e)}this.dispatchEvent("sidebar:toggle",{expanded:this.isExpanded})}getOrCreateContainer(e){let t=document.getElementById(e);return t||(t=document.createElement("div"),t.id=e,t.className="sidebar-manager-container",document.body.appendChild(t)),t}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/6.4.0/css/all.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)}createSidebar(){if(!this.container)throw new Error("容器未设置");const e=document.createElement("div");e.className="sidebar "+(this.isExpanded?"expanded":"collapsed");const t=this.isExpanded?"icon iconfont gp-arrow-left":"icon iconfont gp-arrow-right";e.innerHTML=`\n <div class="sidebar-toggle-btn" id="sidebar-toggle-btn">\n <i class="${t}"></i> \x3c!-- 使用正确的初始图标 --\x3e\n </div>\n \n <div class="sidebar-content">\n ${this.toolbarData.map(e=>this.createToolbarItem(e)).join("")}\n </div>\n \n <div class="sidebar-footer">\n \x3c!-- 预留底部区域 --\x3e\n </div>\n `,this.container.appendChild(e),this.sidebarElement=e,this.addStyles(),this.updateUI()}createToolbarItem(e){const t=e.id===this.activeTool,n=this.activePanel===e.id;let i="";return e.hasPanel&&this.panels.has(e.id)&&(i='<div class="sidebar-item-badge"></div>'),`\n <div class="sidebar-item ${t?"active":""} ${n?"panel-active":""}" \n data-tool="${e.id}"\n title="${this.isExpanded?"":e.tooltip}">\n <div class="sidebar-item-icon">\n <i class="${e.icon}"></i>\n </div>\n <div class="sidebar-item-label">${e.label}</div>\n ${i}\n <div class="sidebar-tooltip">${e.tooltip}</div>\n <div class="sidebar-status-indicator"></div>\n </div>\n `}selectTool(e){const t=this.toolbarData.find(t=>t.id===e);if(t)if(t.hasPanel)this.showPanel(e);else{if(this.activeTool===e)return;this.activePanel&&this.activePanel!==e&&this.hidePanel(this.activePanel),this.activeTool=e,this.updateUI(),this.dispatchEvent("tool:select",{toolId:e,toolLabel:t.label,hasPanel:t.hasPanel})}}getToolLabel(e){const t=this.toolbarData.find(t=>t.id===e);return t?.label||e}bindEvents(){this.sidebarElement&&(this.sidebarElement.addEventListener("click",e=>{const t=e.target,n=t.closest(".sidebar-item");if(n){const e=n.getAttribute("data-tool");return void(e&&this.selectTool(e))}t.closest(".sidebar-toggle-btn")&&this.toggleSidebar()}),this.sidebarElement.addEventListener("mouseenter",()=>{this.isHovering=!0}),this.sidebarElement.addEventListener("mouseleave",()=>{this.isHovering=!1}))}updateUI(){if(!this.sidebarElement)return;this.sidebarElement.className="sidebar "+(this.isExpanded?"expanded":"collapsed");const e=this.sidebarElement.querySelector(".sidebar-toggle-btn i");e&&(e.className=this.isExpanded?"icon iconfont gp-arrow-left":"icon iconfont gp-arrow-right");this.sidebarElement.querySelectorAll(".sidebar-item").forEach(e=>{const t=e.getAttribute("data-tool");e.classList.remove("active","panel-active"),t===this.activeTool&&e.classList.add("active"),t===this.activePanel&&e.classList.add("panel-active")})}dispatchEvent(e,t={}){const n=new CustomEvent(e,{detail:t,bubbles:!0,cancelable:!0});this.sidebarElement&&this.sidebarElement.dispatchEvent(n)}showNotification(e,t="info"){const n=document.createElement("div");n.className=`sidebar-notification sidebar-notification-${t}`,n.textContent=e,n.style.cssText=`\n position: fixed;\n top: 20px;\n right: 20px;\n padding: 12px 16px;\n background: ${"error"===t?"#da3633":"warning"===t?"#daa520":"#238636"};\n color: white;\n border-radius: 6px;\n z-index: 10000;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n animation: slideInRight 0.3s ease;\n `,document.body.appendChild(n),setTimeout(()=>{n.parentNode&&n.parentNode.removeChild(n)},3e3)}showErrorMessage(e){this.showNotification(e,"error")}addStyles(){if(document.querySelector("#sidebar-manager-styles"))return;const e=document.createElement("style");e.id="sidebar-manager-styles",e.textContent="\n /* 侧边栏管理器容器 */\n .sidebar-manager-container {\n position: fixed;\n left: 0;\n top: 0;\n height: 100vh;\n z-index: 9999;\n pointer-events: none;\n }\n \n /* 侧边栏主体 */\n .sidebar {\n position: relative;\n width: 72px;\n height: 100%;\n background: linear-gradient(135deg, #1a1f29 0%, #161b22 100%);\n border-right: 1px solid #30363d;\n display: flex;\n flex-direction: column;\n padding: 16px 0;\n transition: width 0.3s ease, transform 0.3s ease;\n pointer-events: auto;\n box-shadow: 2px 0 8px rgba(0, 0, 0, 0.2);\n }\n \n .sidebar.collapsed {\n width: 72px;\n transform: translateX(0);\n }\n \n .sidebar.expanded {\n width: 200px;\n transform: translateX(0);\n }\n \n .sidebar.hidden {\n transform: translateX(-72px);\n }\n \n /* 侧边栏内容 */\n .sidebar-content {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n padding: 0 8px;\n overflow-y: auto;\n overflow-x: hidden;\n }\n \n /* 展开/收起按钮 */\n .sidebar-toggle-btn {\n position: absolute;\n right: -12px;\n top: 20px;\n width: 24px;\n height: 24px;\n background: #21262d;\n border: 1px solid #30363d;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n color: #8b949e;\n transition: all 0.2s ease;\n z-index: 100;\n opacity: 0;\n pointer-events: none;\n }\n\n .sidebar:hover .sidebar-toggle-btn {\n opacity: 1;\n pointer-events: auto;\n }\n\n .sidebar-toggle-btn:hover {\n background: #30363d;\n color: #f0f6fc;\n border-color: #58a6ff;\n transform: scale(1.1);\n }\n\n .sidebar-toggle-btn i {\n font-size: 12px;\n transition: transform 0.3s ease;\n }\n\n /* 箭头旋转逻辑 */\n .sidebar.expanded .sidebar-toggle-btn i {\n transform: rotate(0deg); /* 展开时箭头向左,不需要旋转 */\n }\n\n .sidebar.collapsed .sidebar-toggle-btn i {\n transform: rotate(0deg); /* 收起时箭头向右,旋转180度 */\n }\n \n /* 工具栏项 */\n .sidebar-item {\n width: 100%;\n display: flex;\n align-items: center;\n padding: 12px 16px;\n margin: 4px 0;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s ease;\n position: relative;\n overflow: hidden;\n user-select: none;\n }\n \n .sidebar-item:hover {\n background: rgba(88, 166, 255, 0.1);\n }\n \n .sidebar-item.active {\n background: rgba(88, 166, 255, 0.15);\n }\n \n .sidebar-item.panel-active {\n background: rgba(88, 166, 255, 0.2);\n border-left: 3px solid #58a6ff;\n }\n \n /* 角标 */\n .sidebar-item-badge {\n position: absolute;\n top: 8px;\n right: 8px;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #58a6ff;\n opacity: 0.8;\n animation: pulse 2s infinite;\n }\n \n @keyframes pulse {\n 0% { opacity: 0.3; }\n 50% { opacity: 0.8; }\n 100% { opacity: 0.3; }\n }\n \n /* 图标 */\n .sidebar-item-icon {\n width: 24px;\n height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n color: #8b949e;\n transition: color 0.2s ease;\n flex-shrink: 0;\n }\n \n .sidebar-item:hover .sidebar-item-icon {\n color: #58a6ff;\n }\n \n .sidebar-item.active .sidebar-item-icon {\n color: #58a6ff;\n }\n \n /* 标签文字 */\n .sidebar-item-label {\n white-space: nowrap;\n font-size: 14px;\n font-weight: 500;\n color: #f0f6fc;\n margin-left: 12px;\n transition: opacity 0.3s ease, transform 0.3s ease;\n opacity: 1;\n transform: translateX(0);\n }\n \n .sidebar.collapsed .sidebar-item-label {\n opacity: 0;\n width: 0;\n overflow: hidden;\n transform: translateX(-10px);\n }\n \n /* 状态指示器 */\n .sidebar-status-indicator {\n position: absolute;\n right: 12px;\n top: 50%;\n transform: translateY(-50%);\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: transparent;\n transition: background 0.2s ease;\n }\n \n .sidebar-item.active .sidebar-status-indicator {\n background: #58a6ff;\n }\n \n /* 工具提示 */\n .sidebar-tooltip {\n position: absolute;\n left: calc(100% + 12px);\n top: 50%;\n transform: translateY(-50%);\n background: #161b22;\n border: 1px solid #30363d;\n padding: 6px 10px;\n border-radius: 4px;\n white-space: nowrap;\n z-index: 1000;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.2s ease;\n font-size: 12px;\n color: #f0f6fc;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n }\n \n .sidebar.collapsed .sidebar-item:hover .sidebar-tooltip {\n opacity: 1;\n }\n \n /* 侧边栏底部 */\n .sidebar-footer {\n padding: 16px;\n border-top: 1px solid #30363d;\n margin-top: auto;\n }\n \n /* 错误消息 */\n .sidebar-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;\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: translateX(-20px);\n opacity: 0;\n }\n to {\n transform: translateX(0);\n opacity: 1;\n }\n }\n \n /* 滚动条样式 */\n .sidebar-content::-webkit-scrollbar {\n width: 4px;\n }\n \n .sidebar-content::-webkit-scrollbar-track {\n background: transparent;\n border-radius: 2px;\n }\n \n .sidebar-content::-webkit-scrollbar-thumb {\n background: #30363d;\n border-radius: 2px;\n }\n \n .sidebar-content::-webkit-scrollbar-thumb:hover {\n background: #484f58;\n }\n \n /* 响应式设计 */\n @media (max-width: 768px) {\n .sidebar {\n width: 60px;\n }\n \n .sidebar.expanded {\n width: 180px;\n }\n \n .sidebar-item {\n padding: 10px 12px;\n }\n \n .sidebar-item-icon {\n font-size: 16px;\n }\n }\n \n /* 动画效果 */\n @keyframes fadeIn {\n from {\n opacity: 0;\n transform: translateX(-10px);\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n }\n \n .sidebar.expanded .sidebar-item-label {\n animation: fadeIn 0.3s ease forwards;\n }\n \n @keyframes slideInRight {\n from {\n transform: translateX(100%);\n opacity: 0;\n }\n to {\n transform: translateX(0);\n opacity: 1;\n }\n }\n ",document.head.appendChild(e)}on(e,t){this.sidebarElement&&this.sidebarElement.addEventListener(e,t)}off(e,t){this.sidebarElement&&this.sidebarElement.removeEventListener(e,t)}getState(){return{isExpanded:this.isExpanded,activeTool:this.activeTool,activePanel:this.activePanel,toolbarData:this.toolbarData,registeredPanels:Array.from(this.panels.keys())}}setState(e){void 0!==e.isExpanded&&(this.isExpanded=e.isExpanded),void 0!==e.activeTool&&(this.activeTool=e.activeTool),this.updateUI()}getSidebarWidth(){return this.isExpanded?this.panelConfig.sidebarExpandedWidth:this.panelConfig.sidebarCollapsedWidth}getActivePanel(){if(this.activePanel)return this.panels.get(this.activePanel)}hasPanel(e){return this.panels.has(e)}destroy(){for(const[,e]of this.panels)e.destroy();this.panels.clear(),this.sidebarElement&&this.sidebarElement.parentNode&&this.sidebarElement.parentNode.removeChild(this.sidebarElement),this.sidebarElement=null;const e=document.getElementById("sidebar-manager-styles");e&&e.parentNode&&e.parentNode.removeChild(e)}}class ze extends Fe{layerSystem;folders=[];searchTerm="";entityExpandedMap=new Map;constructor(e,t){super({position:"right",width:350,height:"85vh",minWidth:300,maxWidth:450,...t}),e&&(this.layerSystem=e.getSystem("layer"),this.layerSystem&&(this.panelId="layer-control-panel",this.onClose=t.onClose,this.initializeDefaultFolders(),this.setupLayerListeners(),this.init()))}initializeDefaultFolders(){this.folders.push({id:"default",name:"默认图层",parentId:null,expanded:!0,order:0}),this.folders.push({id:"objects",name:"对象层",parentId:null,expanded:!0,order:1})}async init(){try{this.container=this.getOrCreateContainer("layer-control-container"),this.createPanel(),this.renderContent(),this.isVisible=!0,this.hide()}catch(e){}}createPanel(){if(!this.container)return;const e=document.createElement("div");e.className="layer-control-panel",e.id=this.panelId,e.innerHTML='\n <div class="layer-panel-header">\n <h2 class="panel-title">\n <i class="fa fa-layer-group"></i>\n <span>图层管理</span>\n </h2>\n <div class="panel-actions">\n <button class="btn-icon" id="layer-add" title="添加图层">\n <i class="fa fa-plus"></i>\n </button>\n <button class="btn-icon" id="layer-close" title="关闭面板">\n <i class="fa fa-times"></i>\n </button>\n </div>\n </div>\n \n <div class="layer-panel-toolbar">\n <div class="search-box">\n <i class="fa fa-search"></i>\n <input type="text" \n id="layer-search" \n placeholder="搜索图层..." \n class="search-input">\n </div>\n </div>\n \n <div class="layer-panel-content" id="layer-content">\n \x3c!-- 图层内容将动态生成 --\x3e\n </div>\n \n <div class="layer-panel-footer">\n <button class="btn-add-folder" id="add-folder">\n <i class="fa fa-folder-plus"></i>\n <span>新建目录</span>\n </button>\n </div>\n ',this.container.appendChild(e),this.panelElement=e,this.addStyles(),this.bindEvents()}addStyles(){if(document.querySelector("#layer-control-styles"))return;const e=document.createElement("style");e.id="layer-control-styles",e.textContent="\n /* 图层控制面板样式 */\n .panel-container {\n position: absolute;\n overflow: hidden;\n padding: 0px;\n top: 0px;\n z-index: 1000;\n }\n .layer-control-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 .layer-panel-header {\n background: #21262d;\n padding: 16px 20px;\n border-bottom: 1px solid #30363d;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n .layer-panel-header .panel-title {\n font-size: 18px;\n font-weight: 600;\n color: #f0f6fc;\n margin: 0;\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .layer-panel-header .panel-actions {\n display: flex;\n gap: 8px;\n }\n\n /* 工具栏 */\n .layer-panel-toolbar {\n background: #1a1f29;\n padding: 12px 20px;\n border-bottom: 1px solid #30363d;\n }\n\n .search-box {\n position: relative;\n }\n\n .search-box .fa-search {\n position: absolute;\n left: 12px;\n top: 50%;\n transform: translateY(-50%);\n color: #8b949e;\n font-size: 14px;\n }\n\n .search-input {\n width: 100%;\n padding: 8px 12px 8px 32px;\n background: #0d1117;\n border: 1px solid #30363d;\n border-radius: 6px;\n color: #f0f6fc;\n font-size: 13px;\n }\n\n .search-input:focus {\n outline: none;\n border-color: #58a6ff;\n box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.3);\n }\n\n /* 面板内容 */\n .layer-panel-content {\n flex: 1;\n overflow-y: auto;\n padding: 20px;\n }\n\n /* 目录 */\n .layer-folder {\n margin-bottom: 12px;\n }\n\n .folder-header {\n background: linear-gradient(135deg, #1a1f29 0%, #21262d 100%);\n padding: 12px 16px;\n cursor: pointer;\n user-select: none;\n transition: background 0.2s ease;\n display: flex;\n align-items: center;\n gap: 10px;\n border-radius: 8px;\n border: 1px solid #30363d;\n }\n\n .folder-header:hover {\n background: linear-gradient(135deg, #1f2632 0%, #252b36 100%);\n }\n\n .folder-icon {\n font-size: 14px;\n color: #8b949e;\n }\n\n .folder-label {\n font-size: 14px;\n font-weight: 600;\n color: #f0f6fc;\n flex: 1;\n }\n\n .folder-toggle {\n font-size: 10px;\n color: #8b949e;\n transition: transform 0.3s ease;\n }\n\n .folder-toggle.expanded {\n transform: rotate(180deg);\n }\n\n .folder-content {\n padding-left: 20px;\n margin-top: 8px;\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.3s ease;\n }\n\n .folder-content.expanded {\n max-height: 1000px;\n }\n\n /* 图层项 */\n .layer-item {\n background: #1c2128;\n border: 1px solid #373e47;\n border-radius: 6px;\n margin-bottom: 8px;\n padding: 12px;\n display: flex;\n align-items: center;\n gap: 10px;\n transition: all 0.2s ease;\n }\n\n .layer-item:hover {\n background: #21262d;\n border-color: #58a6ff;\n }\n\n .layer-checkbox {\n width: 16px;\n height: 16px;\n cursor: pointer;\n }\n\n .layer-icon {\n font-size: 14px;\n color: #8b949e;\n width: 20px;\n text-align: center;\n }\n\n .layer-info {\n flex: 1;\n min-width: 0;\n }\n\n .layer-name {\n font-size: 13px;\n font-weight: 500;\n color: #f0f6fc;\n margin-bottom: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .layer-meta {\n font-size: 11px;\n color: #8b949e;\n }\n\n .layer-url {\n display: block;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 200px;\n }\n\n .layer-actions {\n display: flex;\n gap: 4px;\n opacity: 0;\n transition: opacity 0.2s ease;\n }\n\n .layer-item:hover .layer-actions {\n opacity: 1;\n }\n\n .btn-layer-action {\n background: transparent;\n border: 1px solid #444c56;\n color: #8b949e;\n width: 24px;\n height: 24px;\n border-radius: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n font-size: 12px;\n transition: all 0.2s ease;\n }\n\n .btn-layer-action:hover {\n background: #3c444d;\n color: #f0f6fc;\n border-color: #58a6ff;\n }\n\n /* 透明度滑块 */\n .layer-opacity {\n width: 60px;\n height: 4px;\n background: #30363d;\n border-radius: 2px;\n position: relative;\n margin-top: 6px;\n cursor: pointer;\n }\n\n .layer-opacity-thumb {\n position: absolute;\n top: 50%;\n transform: translate(-50%, -50%);\n width: 12px;\n height: 12px;\n background: #f0f6fc;\n border-radius: 50%;\n cursor: pointer;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n\n /* 空状态 */\n .layer-empty {\n padding: 20px;\n text-align: center;\n color: #8b949e;\n font-size: 13px;\n }\n\n /* 面板底部 */\n .layer-panel-footer {\n background: #21262d;\n padding: 12px 20px;\n border-top: 1px solid #30363d;\n }\n\n .btn-add-folder {\n width: 100%;\n background: #30363d;\n border: 1px solid #444c56;\n color: #8b949e;\n padding: 8px 12px;\n border-radius: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n cursor: pointer;\n transition: all 0.2s ease;\n font-size: 13px;\n }\n\n .btn-add-folder:hover {\n background: #3c444d;\n color: #f0f6fc;\n border-color: #58a6ff;\n }\n\n /* 按钮样式 */\n .btn-icon {\n background: #30363d;\n border: 1px solid #444c56;\n color: #8b949e;\n width: 32px;\n height: 32px;\n border-radius: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: all 0.2s ease;\n font-size: 13px;\n }\n\n .btn-icon:hover {\n background: #3c444d;\n color: #f0f6fc;\n border-color: #58a6ff;\n }\n\n /* 右键菜单 */\n .context-menu {\n position: fixed; /* 改为 fixed */\n background: #21262d;\n border: 1px solid #30363d;\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n z-index: 1001;\n min-width: 150px;\n display: none;\n }\n\n .context-menu.visible {\n display: block;\n }\n\n .context-menu-item {\n padding: 8px 12px;\n color: #f0f6fc;\n font-size: 13px;\n cursor: pointer;\n transition: background 0.2s ease;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .context-menu-item:hover {\n background: #3c444d;\n }\n\n .context-menu-item i {\n width: 16px;\n text-align: center;\n }\n \n /* 对话框样式 */\n .dialog-overlay {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n justify-content: center;\n align-items: center;\n z-index: 2000;\n }\n \n .layer-dialog {\n background: #21262d;\n border: 1px solid #30363d;\n border-radius: 8px;\n padding: 20px;\n width: 400px;\n max-width: 90%;\n }\n \n .layer-dialog h3 {\n color: #f0f6fc;\n margin: 0 0 20px 0;\n }\n \n .form-group {\n margin-bottom: 16px;\n }\n \n .form-group label {\n display: block;\n color: #8b949e;\n font-size: 12px;\n margin-bottom: 6px;\n }\n \n .form-group input,\n .form-group select {\n width: 100%;\n padding: 8px 12px;\n background: #0d1117;\n border: 1px solid #30363d;\n border-radius: 6px;\n color: #f0f6fc;\n font-size: 13px;\n }\n \n .dialog-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 20px;\n }\n \n .btn-cancel,\n .btn-confirm {\n padding: 8px 16px;\n border-radius: 6px;\n border: 1px solid #444c56;\n cursor: pointer;\n font-size: 13px;\n }\n \n .btn-cancel {\n background: #30363d;\n color: #8b949e;\n }\n \n .btn-confirm {\n background: #238636;\n color: white;\n border-color: #238636;\n }\n\n /* 滚动条样式 */\n .layer-panel-content::-webkit-scrollbar {\n width: 6px;\n }\n\n .layer-panel-content::-webkit-scrollbar-track {\n background: #0d1117;\n border-radius: 3px;\n }\n\n .layer-panel-content::-webkit-scrollbar-thumb {\n background: #30363d;\n border-radius: 3px;\n }\n\n .layer-panel-content::-webkit-scrollbar-thumb:hover {\n background: #484f58;\n }\n ",document.head.appendChild(e)}setupLayerListeners(){this.layerSystem.on("layer:add",()=>this.refresh()),this.layerSystem.on("layer:remove",()=>this.refresh()),this.layerSystem.on("layer:visible",()=>this.refresh()),this.layerSystem.on("layer:opacity",e=>{const t=document.querySelector(`.layer-opacity[data-layer-id="${e.id}"]`);if(t){const n=t.querySelector(".layer-opacity-thumb");if(n){const t=Math.round(100*e.opacity);n.style.left=`${t}%`}}}),this.layerSystem.on("entity:add",()=>this.refresh()),this.layerSystem.on("entity:remove",()=>this.refresh()),this.layerSystem.on("entity:visible",()=>this.refresh()),this.layerSystem.on("entity:opacity",()=>this.refresh())}bindEvents(){if(!this.panelElement)return;const e=document.getElementById("layer-search");e?.addEventListener("input",e=>{this.searchTerm=e.target.value.trim(),this.renderContent()}),document.getElementById("layer-add")?.addEventListener("click",()=>{this.showAddLayerDialog()}),document.getElementById("add-folder")?.addEventListener("click",()=>{this.showAddFolderDialog()}),document.getElementById("layer-close")?.addEventListener("click",()=>{this.hide(),this.onClose&&this.onClose()}),this.panelElement.addEventListener("contextmenu",e=>{e.preventDefault();const t=e;this.showContextMenu(t.clientX,t.clientY)})}renderContent(){const e=this.panelElement?.querySelector("#layer-content");if(!e)return;const t=this.layerSystem.getAllLayers(),n=this.layerSystem.getAllEntities();if(0===this.folders.length&&0===t.length&&0===n.length)return void(e.innerHTML='<div class="layer-empty">暂无图层和对象</div>');let i="";this.folders.filter(e=>!e.parentId).forEach(e=>{if("objects"===e.id){const t=this.filterEntities(n);i+=this.renderObjectFolder(e,t)}else{const n=t.filter(t=>this.getLayerFolder(t.id)===e.id),s=this.filterLayers(n);i+=this.renderFolder(e,s)}});const s=t.filter(e=>!this.getLayerFolder(e.id)),a=this.filterLayers(s);a.length>0&&(i+=`\n <div class="layer-folder">\n <div class="folder-header">\n <i class="fa fa-globe folder-icon"></i>\n <span class="folder-label">未分类</span>\n </div>\n <div class="folder-content expanded">\n ${this.renderLayerList(a)}\n </div>\n </div>\n `),e.innerHTML=i||'<div class="layer-empty">未找到匹配的图层或对象</div>',this.bindContentEvents()}renderObjectFolder(e,t){const n=e.expanded&&(t.length>0||""!==this.searchTerm);return`\n <div class="layer-folder" data-folder-id="${e.id}">\n <div class="folder-header">\n <i class="fa fa-cube folder-icon"></i>\n <span class="folder-label">${e.name}</span>\n <i class="fa fa-chevron-down folder-toggle ${n?"expanded":""}"></i>\n </div>\n <div class="folder-content ${n?"expanded":""}">\n ${t.length>0?this.renderEntityList(t):'<div class="layer-empty">暂无对象</div>'}\n </div>\n </div>\n `}renderFolder(e,t){const n=e.expanded&&(t.length>0||""!==this.searchTerm);return`\n <div class="layer-folder" data-folder-id="${e.id}">\n <div class="folder-header">\n <i class="fa fa-folder${n?"-open":""} folder-icon"></i>\n <span class="folder-label">${e.name}</span>\n <i class="fa fa-chevron-down folder-toggle ${n?"expanded":""}"></i>\n </div>\n <div class="folder-content ${n?"expanded":""}">\n ${t.length>0?this.renderLayerList(t):'<div class="layer-empty">暂无图层</div>'}\n </div>\n </div>\n `}renderLayerList(e){return e.map(e=>{const t=Math.round(100*e.opacity);return`\n <div class="layer-item" data-layer-id="${e.id}">\n <input type="checkbox" \n class="layer-checkbox" \n ${e.visible?"checked":""}>\n <i class="${this.getLayerIcon(e.type)} layer-icon"></i>\n <div class="layer-info">\n <div class="layer-name" title="${e.name}">${e.name}</div>\n <div class="layer-meta">\n <span class="layer-url" title="${e.url}">${e.url}</span>\n </div>\n <div class="layer-opacity" data-layer-id="${e.id}">\n <div class="layer-opacity-thumb" \n style="left: ${t}%"></div>\n </div>\n </div>\n <div class="layer-actions">\n <button class="btn-layer-action" title="删除" data-action="delete">\n <i class="fa fa-trash"></i>\n </button>\n </div>\n </div>\n `}).join("")}filterLayers(e){if(!this.searchTerm)return e;const t=this.searchTerm.toLowerCase();return e.filter(e=>e.name.toLowerCase().includes(t)||e.url.toLowerCase().includes(t)||e.type.toLowerCase().includes(t))}getLayerFolder(e){return"default"}getEntityIcon(e){return{entity:"fa fa-cube",primitive:"fa fa-cube"}[e]||"fa fa-cube"}getLayerIcon(e){return{imagery:"fa fa-image",terrain:"fa fa-mountain",vector:"fa fa-vector-square","3dtiles":"fa fa-cube",geojson:"fa fa-map",kml:"fa fa-location-arrow",custom:"fa fa-layer-group"}[e]||"fa fa-layer-group"}filterEntities(e){if(!this.searchTerm)return e;const t=this.searchTerm.toLowerCase(),n=e=>!(!e.name.toLowerCase().includes(t)&&!e.type.toLowerCase().includes(t))||!!e.children&&e.children.some(n);return e.filter(n).map(e=>{if(e.children){const t=e.children.filter(n);return{...e,children:t.length>0?t.map(e=>{if(e.children){const t=e.children.filter(n);return{...e,children:t.length>0?t:void 0}}return e}):void 0}}return e})}renderEntityList(e,t=0){const n=20*t;return e.map(e=>{const i=e.children&&e.children.length>0,s=this.entityExpandedMap.get(e.id)||!1;let a=`\n <div class="entity-item" data-entity-id="${e.id}" style="margin-left: ${n}px;">\n <input type="checkbox" \n class="entity-checkbox" \n ${e.visible?"checked":""}>\n ${i?`\n <i class="fa fa-chevron-right entity-toggle ${s?"expanded":""}" title="展开/收起"></i>\n `:'\n <span class="entity-toggle-placeholder"></span>\n '}\n <i class="${this.getEntityIcon(e.type)} entity-icon"></i>\n <div class="entity-info">\n <div class="entity-name" title="${e.name}">${e.name}</div>\n </div>\n </div>\n `;return i&&s&&(a+=this.renderEntityList(e.children,t+1)),a}).join("")}addEntityStyles(){if(document.querySelector("#entity-item-styles"))return;const e=document.createElement("style");e.id="entity-item-styles",e.textContent="\n /* 实体/图元条目样式 */\n .entity-item {\n background: linear-gradient(135deg, #2d3436 0%, #282c34 100%);\n border: 1px solid #4a5568;\n border-radius: 6px;\n margin-bottom: 8px;\n padding: 12px;\n display: flex;\n align-items: center;\n gap: 10px;\n transition: all 0.2s ease;\n position: relative;\n }\n\n .entity-item:hover {\n background: linear-gradient(135deg, #374140 0%, #303841 100%);\n border-color: #58a6ff;\n }\n\n .entity-checkbox {\n width: 16px;\n height: 16px;\n cursor: pointer;\n }\n\n .entity-toggle {\n font-size: 10px;\n color: #8b949e;\n cursor: pointer;\n transition: transform 0.3s ease;\n width: 16px;\n text-align: center;\n }\n\n .entity-toggle.expanded {\n transform: rotate(90deg);\n }\n\n .entity-toggle-placeholder {\n width: 16px;\n }\n\n .entity-icon {\n font-size: 14px;\n color: #e63946;\n width: 20px;\n text-align: center;\n }\n\n .entity-info {\n flex: 1;\n min-width: 0;\n }\n\n .entity-name {\n font-size: 13px;\n font-weight: 500;\n color: #f0f6fc;\n margin-bottom: 4px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .entity-meta {\n font-size: 11px;\n color: #8b949e;\n }\n\n .entity-type {\n font-size: 10px;\n padding: 2px 6px;\n background: #4a5568;\n border-radius: 3px;\n color: #e2e8f0;\n }\n ",document.head.appendChild(e)}bindContentEvents(){this.addEntityStyles(),document.querySelectorAll(".folder-header").forEach(e=>{e.addEventListener("click",()=>{const t=e.closest(".layer-folder"),n=t?.getAttribute("data-folder-id");n&&this.toggleFolder(n)})}),document.querySelectorAll(".entity-toggle").forEach(e=>{e.addEventListener("click",e=>{e.stopPropagation();const t=e.target.closest(".entity-item"),n=t?.getAttribute("data-entity-id");n&&this.toggleEntityExpanded(n)})}),document.querySelectorAll(".entity-checkbox").forEach(e=>{e.addEventListener("change",e=>{const t=e.target.closest(".entity-item"),n=t?.getAttribute("data-entity-id");n&&this.layerSystem.setEntityVisible(n,e.target.checked)})}),document.querySelectorAll(".layer-checkbox").forEach(e=>{e.addEventListener("change",e=>{const t=e.target.closest(".layer-item"),n=t?.getAttribute("data-layer-id");n&&this.layerSystem.setLayerVisible(n,e.target.checked)})}),document.querySelectorAll(".layer-opacity").forEach(e=>{e.addEventListener("click",t=>{const n=t,i=e.getBoundingClientRect(),s=n.clientX-i.left,a=Math.max(0,Math.min(1,s/i.width)),o=e.getAttribute("data-layer-id");o&&this.layerSystem.setLayerOpacity(o,a)});let t=!1;const n=e.querySelector(".layer-opacity-thumb");n?.addEventListener("mousedown",e=>{e.preventDefault(),t=!0}),document.addEventListener("mousemove",n=>{if(!t)return;const i=e.getBoundingClientRect(),s=Math.max(0,Math.min(n.clientX-i.left,i.width))/i.width,a=e.getAttribute("data-layer-id");a&&this.layerSystem.setLayerOpacity(a,s)}),document.addEventListener("mouseup",()=>{t=!1}),document.addEventListener("mouseleave",()=>{t=!1})}),document.querySelectorAll(".btn-layer-action").forEach(e=>{e.addEventListener("click",t=>{t.stopPropagation();const n=t.target.closest(".layer-item"),i=n?.getAttribute("data-layer-id"),s=e.getAttribute("data-action");i&&"delete"===s&&confirm("确定要删除这个图层吗?")&&this.layerSystem.removeLayer(i)})}),document.querySelectorAll(".layer-item").forEach(e=>{e.addEventListener("dblclick",t=>{t.stopPropagation();const n=e.getAttribute("data-layer-id");n&&this.layerSystem.flyToLayer(n).then(e=>{}).catch(e=>{})})}),document.querySelectorAll(".entity-item").forEach(e=>{e.addEventListener("dblclick",t=>{t.stopPropagation();const n=e.getAttribute("data-entity-id");n&&this.layerSystem.flyToEntity(n).then(e=>{}).catch(e=>{})})})}show(){super.show(),this.refresh()}toggleFolder(e){const t=this.folders.find(t=>t.id===e);t&&(t.expanded=!t.expanded,this.renderContent())}showAddLayerDialog(){const e=document.createElement("div");e.className="dialog-overlay",e.innerHTML='\n <div class="layer-dialog">\n <h3>添加新图层</h3>\n <div class="dialog-content">\n <div class="form-group">\n <label>图层名称</label>\n <input type="text" id="layer-name" placeholder="请输入图层名称" value="新图层">\n </div>\n <div class="form-group">\n <label>图层类型</label>\n <select id="layer-type">\n <option value="imagery">影像图层</option>\n <option value="vector">矢量图层</option>\n <option value="3dtiles">3D Tiles</option>\n <option value="geojson">GeoJSON</option>\n <option value="kml">KML</option>\n <option value="custom">自定义</option>\n </select>\n </div>\n <div class="form-group">\n <label>服务地址 (URL)</label>\n <input type="text" id="layer-url" placeholder="请输入图层服务地址">\n </div>\n <div class="dialog-actions">\n <button class="btn-cancel">取消</button>\n <button class="btn-confirm">确定</button>\n </div>\n </div>\n </div>\n ',document.body.appendChild(e);const t=document.createElement("style");t.textContent="\n .dialog-overlay {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n justify-content: center;\n align-items: center;\n z-index: 2000;\n }\n .layer-dialog {\n background: #21262d;\n border: 1px solid #30363d;\n border-radius: 8px;\n padding: 20px;\n width: 400px;\n max-width: 90%;\n }\n .layer-dialog h3 {\n color: #f0f6fc;\n margin: 0 0 20px 0;\n }\n .form-group {\n margin-bottom: 16px;\n }\n .form-group label {\n display: block;\n color: #8b949e;\n font-size: 12px;\n margin-bottom: 6px;\n }\n .form-group input,\n .form-group select {\n width: 100%;\n padding: 8px 12px;\n background: #0d1117;\n border: 1px solid #30363d;\n border-radius: 6px;\n color: #f0f6fc;\n font-size: 13px;\n }\n .dialog-actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n margin-top: 20px;\n }\n .btn-cancel,\n .btn-confirm {\n padding: 8px 16px;\n border-radius: 6px;\n border: 1px solid #444c56;\n cursor: pointer;\n font-size: 13px;\n }\n .btn-cancel {\n background: #30363d;\n color: #8b949e;\n }\n .btn-confirm {\n background: #238636;\n color: white;\n border-color: #238636;\n }\n ",document.head.appendChild(t);const n=e.querySelector(".btn-confirm"),i=e.querySelector(".btn-cancel");n?.addEventListener("click",async()=>{const n=e.querySelector("#layer-name"),i=e.querySelector("#layer-type"),s=e.querySelector("#layer-url");if(n.value.trim())if(s.value.trim())try{const a={name:n.value.trim(),type:i.value,url:s.value.trim(),visible:!0};await this.layerSystem.addLayer(a),e.remove(),t.remove()}catch(e){alert(`添加图层失败: ${e}`)}else alert("请输入服务地址");else alert("请输入图层名称")}),i?.addEventListener("click",()=>{e.remove(),t.remove()})}showAddFolderDialog(){const e=prompt("请输入目录名称:","新建目录");e&&e.trim()&&this.addFolder(e.trim())}addFolder(e,t=null){const n={id:`folder_${Date.now()}_${Math.random().toString(36).substr(2,9)}`,name:e,parentId:t,expanded:!0,order:this.folders.length};return this.folders.push(n),this.renderContent(),n}showContextMenu(e,t){const n=document.querySelector(".context-menu");n&&n.remove();const i=`\n <div class="context-menu visible" style="left: ${e}px; top: ${t}px;">\n <div class="context-menu-item" data-action="add-layer">\n <i class="fa fa-plus"></i>\n <span>添加图层</span>\n </div>\n <div class="context-menu-item" data-action="add-folder">\n <i class="fa fa-folder-plus"></i>\n <span>添加目录</span>\n </div>\n </div>\n `,s=document.createElement("div");s.innerHTML=i,document.body.appendChild(s.firstChild);const a=document.querySelector(".context-menu");a?.addEventListener("click",e=>{const t=e.target.closest(".context-menu-item"),n=t?.getAttribute("data-action");"add-layer"===n?this.showAddLayerDialog():"add-folder"===n&&this.showAddFolderDialog(),a?.remove()});const o=e=>{a?.contains(e.target)||(a?.remove(),document.removeEventListener("click",o))};setTimeout(()=>{document.addEventListener("click",o)},100)}toggleEntityExpanded(e){const t=this.entityExpandedMap.get(e)||!1;this.entityExpandedMap.set(e,!t),this.renderContent()}refresh(){this.renderContent()}}class De extends Fe{toolboxSystem;isExpanded=new Map;constructor(e,t){super({position:"left",width:280,height:"85vh",minWidth:250,maxWidth:350,zIndex:1e3,...t}),e&&(this.toolboxSystem=e.getSystem("toolbox"),this.toolboxSystem&&(this.panelId="toolbox-control",this.onClose=t?.onClose,this.init()))}async init(){try{this.container=this.getOrCreateContainer("toolbox-control-container"),this.createPanel(),this.renderContent(),this.isVisible=!0,this.hide()}catch(e){}}createPanel(){if(!this.container)return;const e=document.createElement("div");e.className="toolbox-panel",e.id=this.panelId,e.innerHTML='\n <div class="toolbox-header">\n <h2 class="panel-title">\n <i class="fa fa-tools"></i>\n <span>工具箱</span>\n </h2>\n <div class="panel-actions">\n <button class="btn-icon" id="toolbox-close" title="关闭面板">\n <i class="fa fa-times"></i>\n </button>\n </div>\n </div>\n \n <div class="toolbox-content" id="toolbox-content">\n \x3c!-- 内容将动态生成 --\x3e\n </div>\n ',this.container.appendChild(e),this.panelElement=e,this.addStyles(),this.loadIconfont(),setTimeout(()=>{this.bindEvents()},100)}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("#toolbox-control-styles"))return;const e=document.createElement("style");e.id="toolbox-control-styles",e.textContent="\n .panel-container {\n position: absolute;\n overflow: hidden;\n padding: 0px;\n top: 0px;\n z-index: 1000;\n }\n \n .toolbox-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 display: flex;\n flex-direction: column;\n border: 1px solid #30363d;\n width: 280px;\n }\n\n .toolbox-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 .toolbox-header .panel-title {\n font-size: 18px;\n font-weight: 600;\n color: #f0f6fc;\n margin: 0;\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .toolbox-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 /* 按钮网格 */\n .buttons-grid {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 8px;\n }\n\n /* 工具按钮 */\n .tool-button {\n background: #161b22;\n border: 1px solid #30363d;\n border-radius: 6px;\n padding: 12px 8px;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n cursor: pointer;\n transition: all 0.2s ease;\n min-height: 80px;\n position: relative;\n overflow: hidden;\n }\n\n .tool-button:hover {\n background: #1c2b41;\n border-color: #58a6ff;\n }\n\n .tool-button.active {\n background: #1c2b41;\n border-color: #1f6feb;\n }\n \n .tool-button:hover .button-icon {\n color: #58a6ff;\n }\n\n .button-icon {\n font-size: 30px;\n color: #8b949e;\n height: 30px;\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: 12px;\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-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 .tool-button:hover .button-tooltip {\n opacity: 1;\n }\n\n /* 裁切工具样式 */\n .clipping-controls {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 8px 0;\n }\n \n .clipping-input-container {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n \n .clipping-input-label {\n font-size: 12px;\n color: #8b949e;\n font-weight: 500;\n }\n \n .clipping-textarea {\n background: #0d1117;\n border: 1px solid #30363d;\n border-radius: 6px;\n padding: 8px 10px;\n color: #f0f6fc;\n font-size: 12px;\n font-family: 'Courier New', monospace;\n resize: vertical;\n min-height: 120px;\n outline: none;\n transition: border-color 0.2s ease;\n }\n \n .clipping-textarea:focus {\n border-color: #58a6ff;\n }\n \n .clipping-textarea::placeholder {\n color: #484f58;\n }\n \n .clipping-button {\n background: #238636;\n border: 1px solid #2ea043;\n border-radius: 6px;\n padding: 10px 16px;\n color: #f0f6fc;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s ease;\n text-align: center;\n }\n \n .clipping-button:hover {\n background: #2ea043;\n border-color: #3fb950;\n }\n \n .clipping-button:disabled {\n background: #21262d;\n border-color: #30363d;\n color: #8b949e;\n cursor: not-allowed;\n }\n\n .tileset-load-button {\n background: #1f6feb;\n border: 1px solid #388bfd;\n border-radius: 6px;\n padding: 10px 16px;\n color: #f0f6fc;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s ease;\n text-align: center;\n margin-bottom: 12px;\n }\n\n .tileset-load-button:hover {\n background: #388bfd;\n border-color: #58a6ff;\n }\n\n .tileset-url-input {\n background: #0d1117;\n border: 1px solid #30363d;\n border-radius: 6px;\n padding: 8px 10px;\n color: #f0f6fc;\n font-size: 12px;\n outline: none;\n transition: border-color 0.2s ease;\n margin-bottom: 8px;\n }\n\n .tileset-url-input:focus {\n border-color: #58a6ff;\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 /* 滚动条样式 */\n .toolbox-content::-webkit-scrollbar {\n width: 4px;\n }\n \n .toolbox-content::-webkit-scrollbar-track {\n background: #0d1117;\n border-radius: 2px;\n }\n \n .toolbox-content::-webkit-scrollbar-thumb {\n background: #30363d;\n border-radius: 2px;\n }\n \n .toolbox-content::-webkit-scrollbar-thumb:hover {\n background: #484f58;\n }\n \n /* 折叠/展开动画 */\n .category-content {\n overflow: hidden;\n transition: height 0.3s ease;\n }\n \n .category-content.collapsed {\n height: 0 !important;\n overflow: hidden;\n }\n \n .category-toggle.expanded {\n transform: rotate(180deg);\n }\n\n /* iconfont 字体 */\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 .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 ",document.head.appendChild(e)}renderContent(){const e=this.panelElement?.querySelector("#toolbox-content");if(!e)return;e.innerHTML="";const t=document.createElement("div");t.className="categories-container";const n=this.createMeasurementCategory();t.appendChild(n);const i=this.createClippingCategory();t.appendChild(i),e.appendChild(t)}createMeasurementCategory(){const e="measurement-tools",t=this.isExpanded.get(e)||!0,n=document.createElement("div");n.className="category-section",n.dataset.category=e;const i=document.createElement("div");i.className="category-header";const s=t?"fa fa-chevron-down":"fa fa-chevron-right";i.innerHTML=`\n <div class="category-header-inner">\n <i class="fa fa-ruler category-icon"></i>\n <span class="category-label">测量工具</span>\n <i class="${s} category-toggle ${t?"expanded":""}"></i>\n </div>\n `;const a=document.createElement("div");a.className="category-content",a.style.height=t?"auto":"0",t||a.classList.add("collapsed");const o=this.toolboxSystem.getMeasurementTools();if(o.length>0){const e=this.createButtonsGrid(o);a.appendChild(e)}else a.innerHTML='<div class="empty-state">暂无测量工具</div>';return n.appendChild(i),n.appendChild(a),n}createClippingCategory(){const e="clipping-tools",t=this.isExpanded.get(e)||!0,n=document.createElement("div");n.className="category-section",n.dataset.category=e;const i=document.createElement("div");i.className="category-header";const s=t?"fa fa-chevron-down":"fa fa-chevron-right";i.innerHTML=`\n <div class="category-header-inner">\n <i class="fa fa-cut category-icon"></i>\n <span class="category-label">裁切工具</span>\n <i class="${s} category-toggle ${t?"expanded":""}"></i>\n </div>\n `;const a=document.createElement("div");a.className="category-content",a.style.height=t?"auto":"0",t||a.classList.add("collapsed");const o=this.createClippingControls();return a.appendChild(o),n.appendChild(i),n.appendChild(a),n}createClippingControls(){const e=document.createElement("div");return e.className="clipping-controls",e.innerHTML='\n <div class="clipping-input-container">\n <label class="clipping-input-label">3D Tiles URL</label>\n <input \n type="text" \n class="tileset-url-input" \n placeholder="请输入3D Tiles的URL..."\n id="clipping-tileset-url"\n />\n </div>\n <button class="tileset-load-button" id="clipping-load-tileset-btn">加载</button>\n \n <div class="clipping-input-container">\n <label class="clipping-input-label">GeoJSON 数据</label>\n <textarea \n class="clipping-textarea" \n placeholder="请输入GeoJSON格式的数据..."\n id="clipping-geojson-input"\n ></textarea>\n </div>\n <button class="clipping-button" id="clipping-apply-btn">裁切</button>\n ',e}createButtonsGrid(e){const t=document.createElement("div");return t.className="buttons-grid",e.forEach(e=>{const n=this.createToolButton(e);t.appendChild(n)}),t}createToolButton(e){const t=document.createElement("div");return t.className="tool-button",t.setAttribute("data-tool-id",e.id),t.innerHTML=`\n <div class="button-icon">\n <i class="${e.icon}"></i>\n </div>\n <div class="button-label">${e.name}</div>\n ${e.tooltip?`<div class="button-tooltip">${e.tooltip}</div>`:""}\n `,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(".tool-button");i&&this.handleToolButtonClick(i);t.closest("#clipping-apply-btn")&&this.handleClippingApply();t.closest("#clipping-load-tileset-btn")&&this.handleLoadTileset()});const e=document.getElementById("toolbox-close");e?.addEventListener("click",()=>{this.hide(),this.onClose&&this.onClose()})}handleLoadTileset(){const e=document.getElementById("clipping-tileset-url");if(!e)return;const t=e.value.trim();if(t)try{this.loadTileset(t)}catch(e){this.showNotification("加载3D Tiles失败","error")}else this.showNotification("请输入3D Tiles URL","warning")}loadTileset(e){this.toolboxSystem.loadTileset(e),this.showNotification("3D Tiles 加载中...","success")}toggleCategory(e){const t=e.getAttribute("data-category");if(!t)return;const n=e.querySelector(".category-content"),i=e.querySelector(".category-toggle");if(!n||!i)return;const s=!!n.classList.contains("collapsed");i.className=s?"fa fa-chevron-down category-toggle expanded":"fa fa-chevron-right category-toggle",s?(n.classList.remove("collapsed"),n.style.height="auto"):(n.classList.add("collapsed"),n.style.height="0"),this.isExpanded.set(t,s)}handleToolButtonClick(e){const t=e.getAttribute("data-tool-id");t&&(this.toolboxSystem.executeTool(t),this.setActiveButton(e))}handleClippingApply(){const e=document.getElementById("clipping-geojson-input");if(!e)return;const t=e.value.trim();if(t)try{const e=JSON.parse(t);this.applyClipping(e)}catch(e){this.showNotification("GeoJSON格式错误,请检查输入","error")}else this.showNotification("请输入GeoJSON数据","warning")}applyClipping(e){this.toolboxSystem.applyClipping(e,null),this.showNotification("裁切已应用","success")}setActiveButton(e){document.querySelectorAll(".tool-button").forEach(e=>{e.classList.remove("active")}),e.classList.add("active")}showNotification(e,t="success"){const n=document.createElement("div");n.style.cssText=`\n position: fixed;\n top: 20px;\n right: 20px;\n background: ${"success"===t?"#238636":"error"===t?"#da3633":"#9e6a03"};\n color: white;\n padding: 12px 20px;\n border-radius: 6px;\n font-size: 14px;\n z-index: 9999;\n box-shadow: 0 4px 12px rgba(0,0,0,0.3);\n `,n.textContent=e,document.body.appendChild(n),setTimeout(()=>{n.parentNode&&n.parentNode.removeChild(n)},3e3)}refresh(){this.renderContent()}}class $e extends Fe{buildingSystem;searchTerm="";allCityData=[];constructor(e,t){super({position:"left",width:320,height:"85vh",minWidth:280,maxWidth:400,zIndex:1e3,...t}),e&&(this.buildingSystem=e.getSystem("building"),this.buildingSystem&&(this.panelId="building-control",this.onClose=t.onClose,this.init()))}async init(){try{this.container=this.getOrCreateContainer("building-control-container"),this.createPanel(),await this.loadData(),this.isVisible=!0,this.hide()}catch(e){}}createPanel(){if(!this.container)return;const e=document.createElement("div");e.className="building-panel",e.id=this.panelId,e.innerHTML='\n <div class="building-header">\n <h2 class="panel-title">\n <i class="fa fa-building"></i>\n <span>城市建筑</span>\n </h2>\n <div class="panel-actions">\n <button class="btn-icon" id="building-close" title="关闭面板">\n <i class="fa fa-times"></i>\n </button>\n </div>\n </div>\n \n <div class="building-content" id="building-content">\n \x3c!-- 内容将动态生成 --\x3e\n </div>\n ',this.container.appendChild(e),this.panelElement=e,this.addStyles(),this.bindEvents()}async loadData(){try{this.showLoading(),this.allCityData=await this.buildingSystem.fetchCityData(),await this.loadStats(),this.renderContent()}catch(e){this.showError("加载数据失败")}}async loadStats(){const e=await this.buildingSystem.getProductionStats();this.updateStats(e)}updateStats(e){const t=this.panelElement?.querySelector("#building-content");if(!t)return;const n=`\n \x3c!-- 生产统计看板 --\x3e\n <div class="stats-panel">\n <div class="stats-title">\n <span>📊 生产统计看板</span>\n </div>\n <div class="stats-grid">\n <div class="stat-item">\n <div class="stat-label">瓦片数量</div>\n <div class="stat-value"><span id="total_tile_count">${e.total_tile_count}</span><span class="stat-unit">块</span></div>\n </div>\n <div class="stat-item">\n <div class="stat-label">覆盖面积</div>\n <div class="stat-value"><span id="total_area">${e.total_area}</span><span class="stat-unit">km²</span></div>\n </div>\n <div class="stat-item">\n <div class="stat-label">建筑数量</div>\n <div class="stat-value"><span id="total_buia_count">${e.total_buia_count}</span><span class="stat-unit">栋</span></div>\n </div>\n <div class="stat-item">\n <div class="stat-label">区域数量</div>\n <div class="stat-value"><span id="city_count">${e.city_count}</span><span class="stat-unit">个</span></div>\n </div>\n </div>\n </div>\n `;t.innerHTML=`\n <div class="content-container">\n <div id="city-list">\n ${n}\n \n \x3c!-- 城市列表 --\x3e\n <div class="city-header">\n <span>🏣 城市列表</span>\n <div class="search-container">\n <i class="fa fa-search search-icon"></i>\n <input type="text" class="search-input" placeholder="搜索城市..." id="city-search">\n <i class="fa fa-times clear-icon" id="clear-search" style="display: none;"></i>\n </div>\n </div>\n\n <div class="loading">加载中...</div>\n <div id="city-buttons-container"></div>\n \n </div>\n </div>\n `}async renderContent(){await this.loadStats(),await this.createCityButtons()}async createCityButtons(){const e=document.getElementById("city-buttons-container"),t=document.querySelector(".loading");t&&t.remove(),e&&(e.innerHTML="",0!==this.allCityData.length?(this.renderCityButtons(this.allCityData),this.initSearch()):e.innerHTML='<div class="no-results">没有找到城市数据</div>')}renderCityButtons(e){const t=document.getElementById("city-buttons-container");t&&(t.innerHTML="",0!==e.length?e.forEach((e,n)=>{const i=document.createElement("div");i.className="city-btn-container";const s=document.createElement("button");s.className="city-btn";const a=document.createElement("span");if(a.className="city-index",a.textContent=(n+1).toString(),s.appendChild(a),s.appendChild(document.createTextNode(e.name)),e.url||e.publish_url?s.onclick=async()=>{try{await this.buildingSystem.loadCityModel(e)}catch(e){}}:(s.classList.add("disabled"),s.title="该城市暂未开放"),!["测试","北京市","上海市","天津市","重庆市","香港特别行政区","澳门特别行政区"].includes(e.name)){const t=document.createElement("span");t.className="province-tag",t.textContent=e.p_name_alias||"",i.appendChild(t)}const o=[{name:"🖼️",value:"image"},{name:"⛰️",value:"elevation"},{name:"🏠",value:"buia"},{name:"🛣️",value:"road"},{name:"🌿",value:"vega"},{name:"💧",value:"hyda"},{name:"🚆",value:"lrrl"},{name:"🧩",value:"funa"},{name:"🏘️",value:"resa"},{name:"📍",value:"poi"},{name:"🌳",value:"tree"},{name:"🚧",value:"boundary"},{name:"🗼",value:"power-tower"}];if(e.publish_type){let t=Object.entries(e.publish_type).filter(([e,t])=>!0===t).map(([e])=>{const t=o.find(t=>t.value===e);return t?t.name:""}).filter(e=>e);if(t.length>0){const e=document.createElement("span");e.className="features-tag",e.textContent=t.join(" "),s.appendChild(e)}}i.appendChild(s),t.appendChild(i)}):t.innerHTML='<div class="no-results">没有找到匹配的城市</div>')}initSearch(){const e=document.getElementById("city-search"),t=document.getElementById("clear-search");e&&t&&(e.addEventListener("input",()=>{const n=e.value.trim().toLowerCase();t.style.display=n.length>0?"block":"none";const i=this.buildingSystem.searchCities(n);this.renderCityButtons(i)}),t.addEventListener("click",()=>{e.value="",e.focus(),t.style.display="none",this.renderCityButtons(this.allCityData)}),e.addEventListener("keyup",e=>{if("Enter"===e.key){const e=document.querySelector(".city-btn:not(.disabled)");e&&e.click()}}))}showLoading(){const e=this.panelElement?.querySelector("#building-content");e&&(e.innerHTML='<div class="loading">加载中...</div>')}showError(e){const t=this.panelElement?.querySelector("#building-content");t&&(t.innerHTML=`<div class="error">${e}</div>`)}addStyles(){if(document.querySelector("#building-control-styles"))return;const e=document.createElement("style");e.id="building-control-styles",e.textContent="\n .building-container {\n position: absolute;\n overflow: hidden;\n padding: 0px;\n top: 0px;\n z-index: 1000;\n }\n \n .building-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 display: flex;\n flex-direction: column;\n border: 1px solid #30363d;\n width: 380px;\n }\n\n .building-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 .building-header .panel-title {\n font-size: 18px;\n font-weight: 600;\n color: #f0f6fc;\n margin: 0;\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .building-content {\n flex: 1;\n overflow-y: auto;\n padding: 16px;\n }\n\n .content-container {\n display: flex;\n flex: 1;\n overflow: hidden;\n }\n\n /* 统计面板样式 */\n .stats-panel {\n background: #1c2128;\n border-radius: 8px;\n padding: 16px;\n margin-bottom: 20px;\n border: 1px solid #30363d;\n }\n\n .stats-title {\n font-size: 16px;\n font-weight: 600;\n color: #f0f6fc;\n margin-bottom: 16px;\n }\n\n .stats-grid {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 12px;\n }\n\n .stat-item {\n background: #0d1117;\n border-radius: 6px;\n padding: 12px;\n border: 1px solid #30363d;\n }\n\n .stat-label {\n font-size: 12px;\n color: #8b949e;\n margin-bottom: 6px;\n }\n\n .stat-value {\n font-size: 20px;\n font-weight: 700;\n color: #f0f6fc;\n }\n\n .stat-unit {\n font-size: 12px;\n color: #8b949e;\n margin-left: 4px;\n }\n\n /* 城市列表样式 */\n .city-header {\n display: flex;\n align-items: center;\n margin-bottom: 16px;\n font-size: 16px;\n font-weight: 600;\n color: #f0f6fc;\n }\n\n .search-container {\n position: relative;\n width: 168px;\n margin-left: 16px;\n }\n\n .search-icon {\n position: absolute;\n left: 10px;\n top: 50%;\n transform: translateY(-50%);\n color: #8b949e;\n font-size: 12px;\n }\n\n .search-input {\n width: 100%;\n padding: 8px 30px 8px 30px;\n background: #0d1117;\n border: 1px solid #30363d;\n border-radius: 6px;\n color: #f0f6fc;\n font-size: 13px;\n }\n\n .clear-icon {\n position: absolute;\n right: 10px;\n top: 50%;\n transform: translateY(-50%);\n color: #8b949e;\n cursor: pointer;\n font-size: 12px;\n }\n\n .clear-icon:hover {\n color: #f0f6fc;\n }\n\n /* 城市按钮样式 */\n .city-btn-container {\n position: relative;\n margin-bottom: 8px;\n }\n\n .city-btn {\n width: 100%;\n padding: 12px 16px;\n background: #1c2128;\n border: 1px solid #30363d;\n border-radius: 6px;\n color: #f0f6fc;\n text-align: left;\n cursor: pointer;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n font-size: 13px;\n }\n\n .city-btn:hover:not(.disabled) {\n background: #21262d;\n border-color: #58a6ff;\n }\n\n .city-btn.active {\n background: #1c2b41;\n border-color: #1f6feb;\n }\n\n .city-btn.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .city-index {\n width: 24px;\n height: 24px;\n background: #30363d;\n border-radius: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-right: 10px;\n font-size: 12px;\n color: #8b949e;\n }\n\n .province-tag {\n position: absolute;\n top: -6px;\n right: 8px;\n background: #238636;\n color: white;\n font-size: 10px;\n padding: 2px 6px;\n border-radius: 3px;\n }\n\n .features-tag {\n margin-left: auto;\n font-size: 12px;\n color: #8b949e;\n }\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 .loading, .no-results, .error {\n padding: 40px 20px;\n text-align: center;\n color: #8b949e;\n font-size: 14px;\n }\n ",document.head.appendChild(e)}bindEvents(){if(!this.panelElement)return;const e=document.getElementById("building-close");e?.addEventListener("click",()=>{this.hide(),this.onClose&&this.onClose()})}async refresh(){await this.loadData()}}class He extends Fe{groups=[];propertyValues=new Map;customRenderers=new Map;onPropertyChange;constructor(e){super({position:"right",width:320,height:"85vh",minWidth:280,maxWidth:400,zIndex:1e3,...e}),this.config=e,this.groups=e.groups||[],this.onPropertyChange=e.onPropertyChange,this.onClose=e.onClose,this.panelId="property-grid-panel",e.customRenderers&&Object.entries(e.customRenderers).forEach(([e,t])=>{this.customRenderers.set(e,t)}),this.initDefaultRenderers(),this.init()}initDefaultRenderers(){this.customRenderers.set("text",e=>{const t=document.createElement("input");return t.type="text",t.className="property-input property-input-text",t.value=e.value||"",t.placeholder=e.placeholder||"",t.readOnly=!!e.readonly,t.style.width="100%",t.addEventListener("change",t=>{this.handlePropertyChange(e.key,t.target.value)}),t}),this.customRenderers.set("number",e=>{const t=document.createElement("input");return t.type="number",t.className="property-input property-input-number",t.value=e.value||0,t.min=e.min?.toString()||"",t.max=e.max?.toString()||"",t.step=e.step?.toString()||"1",t.readOnly=!!e.readonly,t.style.width="100%",t.addEventListener("change",t=>{this.handlePropertyChange(e.key,parseFloat(t.target.value))}),t}),this.customRenderers.set("checkbox",e=>{const t=document.createElement("label");t.className="property-checkbox-container",t.style.display="flex",t.style.alignItems="center",t.style.gap="8px";const n=document.createElement("input");n.type="checkbox",n.className="property-input property-input-checkbox",n.checked=!!e.value,n.disabled=!!e.readonly;const i=document.createElement("span");return i.textContent=e.name,i.style.color="#c9d1d9",n.addEventListener("change",t=>{this.handlePropertyChange(e.key,t.target.checked)}),t.appendChild(n),t.appendChild(i),t}),this.customRenderers.set("select",e=>{const t=document.createElement("select");return t.className="property-input property-input-select",t.style.width="100%",t.disabled=!!e.readonly,e.options?.forEach(n=>{const i=document.createElement("option");i.value=n.value,i.textContent=n.label,i.selected=n.value===e.value,t.appendChild(i)}),t.addEventListener("change",t=>{this.handlePropertyChange(e.key,t.target.value)}),t}),this.customRenderers.set("color",e=>{const t=document.createElement("div");t.className="property-color-container",t.style.display="flex",t.style.gap="8px",t.style.alignItems="center";const n=document.createElement("input");n.type="color",n.className="property-input property-input-color",n.value=e.value||"#ffffff",n.disabled=!!e.readonly,n.style.width="40px",n.style.height="30px",n.style.padding="0",n.style.border="1px solid #30363d",n.style.borderRadius="4px";const i=document.createElement("input");i.type="text",i.className="property-input property-input-text",i.value=e.value||"#ffffff",i.placeholder="#HEX",i.readOnly=!!e.readonly,i.style.flex="1";const s=t=>{n.value=t,i.value=t,this.handlePropertyChange(e.key,t)};return n.addEventListener("change",e=>{s(e.target.value)}),i.addEventListener("change",e=>{const t=e.target.value;/^#[0-9A-F]{6}$/i.test(t)&&s(t)}),t.appendChild(n),t.appendChild(i),t}),this.customRenderers.set("label",e=>{const t=document.createElement("div");return t.className="property-label",t.textContent=e.value||"",t.style.color="#8b949e",t.style.padding="8px 0",t})}handlePropertyChange(e,t){const n=this.propertyValues.get(e);t!==n&&(this.propertyValues.set(e,t),this.onPropertyChange&&this.onPropertyChange(e,t,n))}async init(){try{this.container=this.getOrCreateContainerWithClass("property-grid-container"),this.createPanel(),this.renderContent(),this.isVisible=!0,this.show()}catch(e){}}createPanel(){if(!this.container)return;const e=document.createElement("div");e.className="property-grid-panel",e.id=this.panelId,e.innerHTML=`\n <div class="property-grid-header">\n <h2 class="panel-title">\n <i class="fa fa-sliders-h"></i>\n <span>${this.config.title||"属性面板"}</span>\n </h2>\n <div class="panel-actions">\n <button class="btn-icon" id="property-grid-close" title="关闭面板">\n <i class="fa fa-times"></i>\n </button>\n </div>\n </div>\n \n <div class="property-grid-content" id="property-grid-content">\n \x3c!-- 属性内容将动态生成 --\x3e\n </div>\n \n <div class="property-grid-footer" id="property-grid-footer">\n \x3c!-- 页脚内容 --\x3e\n </div>\n `,this.container.appendChild(e),this.panelElement=e,this.addStyles(),setTimeout(()=>{this.bindEvents()},100)}addStyles(){if(document.querySelector("#property-grid-styles"))return;const e=document.createElement("style");e.id="property-grid-styles",e.textContent=`\n .property-grid-container {\n position: absolute;\n overflow: hidden;\n padding: 0px;\n top: 0px;\n right: 0px;\n z-index: 1000;\n }\n .property-grid-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 display: flex;\n flex-direction: column;\n border: 1px solid #30363d;\n width: 100%;\n max-width: 400px;\n }\n\n .property-grid-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 flex-shrink: 0;\n }\n\n .property-grid-header .panel-title {\n font-size: 18px;\n font-weight: 600;\n color: #f0f6fc;\n margin: 0;\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .property-grid-content {\n flex: 1;\n overflow-y: auto;\n padding: 0;\n color: #c9d1d9;\n }\n\n .property-grid-footer {\n background: #21262d;\n padding: 12px 16px;\n border-top: 1px solid #30363d;\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n flex-shrink: 0;\n }\n\n .property-group {\n border-bottom: 1px solid #30363d;\n }\n\n .property-group:last-child {\n border-bottom: none;\n }\n\n .property-group-header {\n padding: 12px 16px;\n background: rgba(255, 255, 255, 0.05);\n cursor: pointer;\n display: flex;\n justify-content: space-between;\n align-items: center;\n transition: background 0.2s ease;\n }\n\n .property-group-header:hover {\n background: rgba(255, 255, 255, 0.1);\n }\n\n .property-group-name {\n font-weight: 600;\n color: #f0f6fc;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .property-group-icon {\n transition: transform 0.3s ease;\n font-size: 12px;\n }\n\n .property-group-icon.expanded {\n transform: rotate(90deg);\n }\n\n .property-group-content {\n padding: 8px 16px 16px 16px;\n display: none;\n }\n\n .property-group-content.expanded {\n display: block;\n }\n\n .property-item {\n margin-bottom: 12px;\n display: flex;\n align-items: center;\n }\n\n .property-item:last-child {\n margin-bottom: 0;\n }\n\n .property-item.hidden {\n display: none;\n }\n\n .property-label {\n width: ${this.config.labelWidth||"120px"};\n font-size: 13px;\n color: #8b949e;\n flex-shrink: 0;\n }\n\n .property-control {\n flex: 1;\n }\n\n .property-input {\n background: rgba(255, 255, 255, 0.1);\n border: 1px solid #30363d;\n border-radius: 4px;\n color: #c9d1d9;\n padding: 6px 0px;\n font-size: 13px;\n outline: none;\n transition: all 0.2s ease;\n }\n\n .property-input:focus {\n border-color: #58a6ff;\n box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.2);\n }\n\n .property-input:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .property-input-text,\n .property-input-number,\n .property-input-select {\n height: 32px;\n }\n\n .property-input-color {\n cursor: pointer;\n }\n\n .property-checkbox-container {\n cursor: pointer;\n user-select: none;\n }\n\n .property-checkbox-container input[type="checkbox"] {\n margin: 0;\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 .btn-primary {\n background: #238636;\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n transition: background 0.2s ease;\n }\n\n .btn-primary:hover {\n background: #2ea043;\n }\n\n .btn-secondary {\n background: transparent;\n color: #8b949e;\n border: 1px solid #30363d;\n padding: 8px 16px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n transition: all 0.2s ease;\n }\n\n .btn-secondary:hover {\n background: #30363d;\n color: #f0f6fc;\n }\n `,document.head.appendChild(e)}renderContent(){const e=this.panelElement?.querySelector("#property-grid-content");e&&(e.innerHTML="",this.groups.forEach((t,n)=>{const i=this.createGroupElement(t,n);e.appendChild(i)}),0===this.groups.length&&(e.innerHTML='\n <div style="text-align: center; padding: 40px 20px; color: #8b949e;">\n <i class="fa fa-sliders-h" style="font-size: 48px; margin-bottom: 20px; display: block; opacity: 0.5;"></i>\n <div style="font-size: 16px; margin-bottom: 8px;">暂无属性</div>\n <div style="font-size: 12px;">请选择对象以查看属性</div>\n </div>\n '))}createGroupElement(e,t){const n=document.createElement("div");n.className="property-group",n.dataset.groupIndex=t.toString();const i=document.createElement("div");i.className="property-group-header";const s=document.createElement("div");s.className="property-group-name";const a=document.createElement("i");a.className="fa fa-chevron-right property-group-icon",!1!==e.expanded&&a.classList.add("expanded");const o=document.createElement("span");o.textContent=e.name,s.appendChild(a),s.appendChild(o);const r=document.createElement("div");return r.className="property-group-content",!1!==e.expanded&&r.classList.add("expanded"),e.items.forEach(e=>{if(!e.hidden){const t=this.createPropertyItemElement(e);r.appendChild(t)}}),i.addEventListener("click",()=>{r.classList.contains("expanded")?(r.classList.remove("expanded"),a.classList.remove("expanded")):(r.classList.add("expanded"),a.classList.add("expanded"))}),i.appendChild(s),n.appendChild(i),n.appendChild(r),n}createPropertyItemElement(e){const t=document.createElement("div");t.className="property-item",t.dataset.propertyKey=e.key,e.hidden&&t.classList.add("hidden");const n=document.createElement("div");n.className="property-label",n.textContent=e.name,n.title=e.key;const i=document.createElement("div");i.className="property-control";const s=this.customRenderers.get(e.type);let a;return s?a=s(e):(a=document.createElement("input"),a.className="property-input property-input-text",a.type="text",a.value=e.value||""),this.propertyValues.set(e.key,e.value),i.appendChild(a),t.appendChild(n),t.appendChild(i),t}bindEvents(){if(!this.panelElement)return;const e=this.panelElement.querySelector("#property-grid-close");e?.addEventListener("click",()=>{this.hide(),this.onClose&&this.onClose()})}updateProperty(e,t){this.propertyValues.set(e,t);const n=this.panelElement?.querySelector(`[data-property-key="${e}"] input, [data-property-key="${e}"] select`);n&&(n instanceof HTMLInputElement?"checkbox"===n.type?n.checked=!!t:n.value=t:n instanceof HTMLSelectElement&&(n.value=t))}getProperty(e){return this.propertyValues.get(e)}setGroups(e){this.groups=e,this.renderContent()}addGroup(e){this.groups.push(e),this.renderContent()}clear(){this.groups=[],this.propertyValues.clear(),this.renderContent()}registerRenderer(e,t){this.customRenderers.set(e,t)}refresh(){this.renderContent()}}class Ne{viewer;enabled;autoUpdate;debounceDelay;updateTimeout=null;eventListener=null;defaultLon;defaultLat;defaultHeight;defaultHeading;defaultPitch;defaultRoll;onCameraChange;constructor(e){this.viewer=e.viewer,this.enabled=!1!==e.enabled,this.autoUpdate=!1!==e.autoUpdate,this.debounceDelay=e.debounceDelay||300,this.defaultLon=e.defaultLon??103.84,this.defaultLat=e.defaultLat??31.15,this.defaultHeight=e.defaultHeight??2e7,this.defaultHeading=e.defaultHeading??0,this.defaultPitch=e.defaultPitch??-60,this.defaultRoll=e.defaultRoll??0,this.onCameraChange=e.onCameraChange,this.enabled&&this.init()}init(){this.setCameraFromUrl(),this.autoUpdate&&this.setupEventListener()}setupEventListener(){this.removeEventListener(),this.eventListener=this.viewer.camera.moveEnd.addEventListener(()=>{this.handleCameraMoveEnd()})}removeEventListener(){this.eventListener&&(this.eventListener(),this.eventListener=null),this.updateTimeout&&(clearTimeout(this.updateTimeout),this.updateTimeout=null)}handleCameraMoveEnd(){this.autoUpdate&&(clearTimeout(this.updateTimeout),this.updateTimeout=setTimeout(()=>{this.updateUrlParams()},this.debounceDelay))}setCameraFromUrl(){const e=new URLSearchParams(window.location.search),t=parseFloat(e.get("lon")||""),n=parseFloat(e.get("lat")||""),i=parseFloat(e.get("h")||""),s=parseFloat(e.get("hd")||""),a=parseFloat(e.get("p")||""),o=parseFloat(e.get("r")||"");if(!isNaN(t)&&!isNaN(n)&&!isNaN(i)){const e=window.Cesium||window.CESIUM;e&&this.viewer.camera.setView({destination:e.Cartesian3.fromDegrees(t,n,i),orientation:{heading:isNaN(s)?e.Math.toRadians(this.defaultHeading):e.Math.toRadians(s),pitch:isNaN(a)?e.Math.toRadians(this.defaultPitch):e.Math.toRadians(a),roll:isNaN(o)?e.Math.toRadians(this.defaultRoll):e.Math.toRadians(o)}}),this.onCameraChange&&this.onCameraChange({lon:t,lat:n,height:i,heading:isNaN(s)?this.defaultHeading:s,pitch:isNaN(a)?this.defaultPitch:a,roll:isNaN(o)?this.defaultRoll:o})}else this.setDefaultCamera()}setDefaultCamera(){const e=window.Cesium||window.CESIUM;e&&this.viewer.camera.setView({destination:e.Cartesian3.fromDegrees(this.defaultLon,this.defaultLat,this.defaultHeight),orientation:{heading:e.Math.toRadians(this.defaultHeading),pitch:e.Math.toRadians(this.defaultPitch),roll:e.Math.toRadians(this.defaultRoll)}})}updateUrlParams(){const e=this.viewer.camera,t=e.positionCartographic,n=window.Cesium||window.CESIUM;if(!n)return;const i=n.Math.toDegrees(t.longitude).toFixed(6),s=n.Math.toDegrees(t.latitude).toFixed(6),a=t.height.toFixed(2),o=n.Math.toDegrees(e.heading).toFixed(4),r=n.Math.toDegrees(e.pitch).toFixed(4),l=n.Math.toDegrees(e.roll).toFixed(4),d=new URLSearchParams(window.location.search);d.set("lon",i),d.set("lat",s),d.set("h",a),d.set("hd",o),d.set("p",r),d.set("r",l);let c=window.location.pathname+"?",h=[];for(let[e,t]of d.entries())"url"===e?h.push(e+"="+t):h.push(e+"="+encodeURIComponent(t));c+=h.join("&"),window.history.replaceState({},"",c),this.onCameraChange&&this.onCameraChange({lon:parseFloat(i),lat:parseFloat(s),height:parseFloat(a),heading:parseFloat(o),pitch:parseFloat(r),roll:parseFloat(l)})}getCameraParams(){const e=this.viewer.camera,t=e.positionCartographic,n=window.Cesium||window.CESIUM;return{lon:n?n.Math.toDegrees(t.longitude):0,lat:n?n.Math.toDegrees(t.latitude):0,height:t.height,heading:n?n.Math.toDegrees(e.heading):0,pitch:n?n.Math.toDegrees(e.pitch):0,roll:n?n.Math.toDegrees(e.roll):0}}setCamera(e){const t=window.Cesium||window.CESIUM;if(!t)return;const n=e.lon??this.defaultLon,i=e.lat??this.defaultLat,s=e.height??this.defaultHeight,a=e.heading??this.defaultHeading,o=e.pitch??this.defaultPitch,r=e.roll??this.defaultRoll;this.viewer.camera.setView({destination:t.Cartesian3.fromDegrees(n,i,s),orientation:{heading:t.Math.toRadians(a),pitch:t.Math.toRadians(o),roll:t.Math.toRadians(r)}})}enable(){this.enabled||(this.enabled=!0,this.init())}disable(){this.enabled&&(this.enabled=!1,this.removeEventListener())}setAutoUpdate(e){this.autoUpdate=e,this.enabled&&(this.removeEventListener(),e&&this.setupEventListener())}destroy(){this.removeEventListener(),this.enabled=!1}}window.LACDT||(window.LACDT={});const Ve={Render:se,RenderControl:Pe,EnvironmentControl:_e,SidebarControl:Re,PlottingControl:Me,TilesetSearchControl:Ae,LayerSystem:X,LayerControl:ze,ToolboxControl:De,ToolboxSystem:Z,BuildingControl:$e,BuildingSystem:te,SceneControl:class extends Fe{sceneSystem;propertyGridPanel=null;isPropertyPanelEnabled=!1;selectObject=null;constructor(e,t){if(super({position:"left",width:280,height:"85vh",minWidth:250,maxWidth:320,zIndex:1e3,...t}),!e)return;if(this.sceneSystem=e.getSystem("scene"),!this.sceneSystem)return;this.panelId="scene-control",this.onClose=t.onClose;const n=localStorage.getItem("propertyPanelEnabled");this.isPropertyPanelEnabled="true"===n,this.init()}async init(){try{this.container=this.getOrCreateContainer("scene-control-container"),this.createPanel(),this.renderContent(),this.isPropertyPanelEnabled&&this.createPropertyGridPanel(),this.isVisible=!0,this.hide()}catch(e){}}createPanel(){if(!this.container)return;const e=document.createElement("div");e.className="scene-panel",e.id=this.panelId,e.innerHTML=`\n <div class="scene-header">\n <h2 class="panel-title">\n <i class="fa fa-map-marked-alt"></i>\n <span>场景管理</span>\n </h2>\n <div class="panel-actions">\n <button class="btn-icon" id="scene-close" title="关闭面板">\n <i class="fa fa-times"></i>\n </button>\n </div>\n </div>\n \n <div class="scene-content" id="scene-content">\n \x3c!-- 内容将动态生成 --\x3e\n </div>\n \x3c!-- 属性面板控制区域 --\x3e\n <div class="property-panel-control" id="property-panel-control">\n <div class="control-item">\n <label class="checkbox-label">\n <input type="checkbox" id="toggle-property-panel" \n ${this.isPropertyPanelEnabled?"checked":""}>\n <span>显示属性面板</span>\n </label>\n </div>\n </div>\n `,this.container.appendChild(e),this.panelElement=e,this.addStyles(),setTimeout(()=>{this.bindEvents()},100)}addStyles(){if(document.querySelector("#scene-control-styles"))return;const e=document.createElement("style");e.id="scene-control-styles",e.textContent='\n .panel-container {\n position: absolute;\n overflow: hidden;\n padding: 0px;\n top: 0px;\n z-index: 1000;\n }\n \n .scene-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 display: flex;\n flex-direction: column;\n border: 1px solid #30363d;\n width: 300px;\n }\n\n .scene-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 .scene-header .panel-title {\n font-size: 18px;\n font-weight: 600;\n color: #f0f6fc;\n margin: 0;\n display: flex;\n align-items: center;\n gap: 10px;\n }\n\n .scene-content {\n flex: 1;\n overflow-y: auto;\n padding: 30px;\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 .search-section {\n margin-bottom: 20px;\n }\n\n .search-section h3 {\n color: #f0f6fc;\n font-size: 14px;\n margin-bottom: 12px;\n padding-bottom: 8px;\n border-bottom: 1px solid #30363d;\n }\n\n .search-input-group {\n margin-bottom: 12px;\n }\n\n .search-input-group label {\n display: block;\n color: #8b949e;\n font-size: 12px;\n margin-bottom: 6px;\n }\n\n .search-input-group input {\n width: 100%;\n padding: 8px 0px;\n background: #0d1117;\n border: 1px solid #30363d;\n border-radius: 6px;\n color: #f0f6fc;\n font-size: 13px;\n }\n\n .search-input-group input:focus {\n outline: none;\n border-color: #58a6ff;\n box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.3);\n }\n\n /* 按钮样式 */\n .search-btn {\n width: 100%;\n padding: 8px 12px;\n background: #238636;\n border: 1px solid #238636;\n border-radius: 6px;\n color: white;\n font-size: 13px;\n cursor: pointer;\n transition: all 0.2s ease;\n }\n\n .search-btn:hover {\n background: #2ea043;\n border-color: #2ea043;\n }\n\n .search-btn:disabled {\n background: #30363d;\n border-color: #30363d;\n cursor: not-allowed;\n }\n\n /* 搜索结果样式 */\n .search-results {\n margin-top: 16px;\n }\n\n .search-result-item {\n background: #1c2128;\n border: 1px solid #373e47;\n border-radius: 6px;\n padding: 12px;\n margin-bottom: 8px;\n cursor: pointer;\n transition: all 0.2s ease;\n }\n\n .search-result-item:hover {\n background: #21262d;\n border-color: #58a6ff;\n }\n\n .search-result-item .result-name {\n font-size: 13px;\n color: #f0f6fc;\n margin-bottom: 4px;\n }\n\n .search-result-item .result-address {\n font-size: 11px;\n color: #8b949e;\n }\n\n /* 瓦片查询样式 */\n .tile-search-section {\n margin-bottom: 20px;\n padding-top: 20px;\n border-top: 1px solid #30363d;\n }\n\n .tile-search-section h3 {\n color: #f0f6fc;\n font-size: 14px;\n margin-bottom: 12px;\n padding-bottom: 8px;\n border-bottom: 1px solid #30363d;\n }\n\n .tile-input-group {\n margin-bottom: 12px;\n }\n\n .tile-input-group input {\n width: 100%;\n padding: 8px 0px;\n background: #0d1117;\n border: 1px solid #30363d;\n border-radius: 6px;\n color: #f0f6fc;\n font-size: 13px;\n }\n\n .tile-input-group input:focus {\n outline: none;\n border-color: #58a6ff;\n box-shadow: 0 0 0 2px rgba(31, 111, 235, 0.3);\n }\n\n /* 搜索历史样式 */\n .search-history {\n margin-top: 16px;\n }\n\n .search-history h4 {\n color: #8b949e;\n font-size: 12px;\n margin-bottom: 8px;\n font-weight: 500;\n }\n\n .history-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 0;\n cursor: pointer;\n transition: all 0.2s ease;\n font-size: 13px;\n color: #8b949e;\n }\n\n .history-item:hover {\n color: #f0f6fc;\n }\n\n .history-item .history-icon {\n font-size: 11px;\n color: #58a6ff;\n }\n\n /* 空状态样式 */\n .empty-state {\n text-align: center;\n padding: 40px 20px;\n color: #8b949e;\n font-size: 13px;\n }\n\n .empty-state i {\n font-size: 32px;\n margin-bottom: 12px;\n color: #30363d;\n }\n\n /* 属性面板控制区域样式 */\n .property-panel-control {\n padding: 16px;\n border-top: 1px solid #30363d;\n background: #21262d;\n }\n\n .control-item {\n display: flex;\n align-items: center;\n }\n\n .checkbox-label {\n display: flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n color: #c9d1d9;\n font-size: 13px;\n user-select: none;\n }\n\n .checkbox-label input[type="checkbox"] {\n width: 16px;\n height: 16px;\n margin: 0;\n cursor: pointer;\n }\n\n .checkbox-label:hover {\n color: #f0f6fc;\n }\n ',document.head.appendChild(e)}renderContent(){const e=this.panelElement?.querySelector("#scene-content");e&&(e.innerHTML='\n \x3c!-- 地名搜索区域 --\x3e\n <div class="search-section">\n <h3><i class="fa fa-search"></i> 地名搜索</h3>\n <div class="search-input-group">\n <label for="location-search">地点名称</label>\n <input type="text" id="location-search" placeholder="请输入地名...">\n </div>\n <button class="search-btn" id="search-btn">搜索</button>\n <div id="search-results" class="search-results"></div>\n </div>\n\n \x3c!-- 瓦片查询区域 --\x3e\n <div class="tile-search-section">\n <h3><i class="fa fa-th-large"></i> 瓦片号查询</h3>\n <div class="tile-input-group">\n <input type="text" id="tile-input" placeholder="格式:zoom-x-y (如:14-26969-4552)">\n </div>\n <button class="search-btn" id="tile-search-btn">查询定位</button>\n </div>\n\n \x3c!-- 搜索历史 --\x3e\n <div class="search-history">\n <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">\n <h4>搜索历史</h4>\n <button id="clear-history-btn" style="font-size: 11px; background: transparent; border: none; color: #8b949e; cursor: pointer; padding: 0;">\n 清空\n </button>\n </div>\n <div id="search-history-list"></div>\n </div>\n ',this.renderSearchHistory())}renderSearchHistory(){const e=document.getElementById("search-history-list");if(!e)return;const t=this.sceneSystem.getSearchHistory();0!==t.length?e.innerHTML=t.map(e=>`\n <div class="history-item" data-keyword="${e}">\n <i class="fa fa-history history-icon"></i>\n <span>${e}</span>\n </div>\n `).join(""):e.innerHTML='<div class="empty-state">暂无搜索历史</div>'}bindEvents(){if(!this.panelElement)return;const e=document.getElementById("scene-close");e?.addEventListener("click",()=>{this.hide(),this.onClose&&this.onClose()});const t=document.getElementById("search-btn");t?.addEventListener("click",()=>{this.handleSearch()});const n=document.getElementById("location-search");n?.addEventListener("keypress",e=>{"Enter"===e.key&&this.handleSearch()});const i=document.getElementById("tile-search-btn");i?.addEventListener("click",()=>{this.handleTileSearch()});const s=document.getElementById("tile-input");s?.addEventListener("keypress",e=>{"Enter"===e.key&&this.handleTileSearch()});const a=document.getElementById("search-history-list");a?.addEventListener("click",e=>{const t=e.target.closest(".history-item");if(t){const e=t.getAttribute("data-keyword");if(e){document.getElementById("location-search").value=e,this.handleSearch()}}});const o=document.getElementById("clear-history-btn");o?.addEventListener("click",()=>{this.sceneSystem.clearSearchHistory(),this.renderSearchHistory()});const r=document.getElementById("toggle-property-panel");r?.addEventListener("change",e=>{this.handlePropertyPanelToggle(e.target.checked)})}async handleSearch(){const e=document.getElementById("location-search").value.trim();if(!e)return void this.showNotification("请输入搜索关键词","warning");const t=document.getElementById("search-btn");t.disabled=!0,t.textContent="搜索中...";try{const t=await this.sceneSystem.searchLocationByName(e);t.length>0?(this.renderSearchResults(t),this.renderSearchHistory()):(this.renderSearchResults([]),this.showNotification("未找到匹配结果","warning"))}catch(e){this.showNotification("搜索失败,请重试","error"),this.renderSearchResults([])}finally{t.disabled=!1,t.textContent="搜索"}}renderSearchResults(e){const t=document.getElementById("search-results");t&&(0!==e.length?(t.innerHTML=e.map(e=>`\n <div class="search-result-item" data-lon="${e.location.lon}" data-lat="${e.location.lat}">\n <div class="result-name">\n <i class="fa fa-map-marker-alt"></i> ${e.name}\n </div>\n <div class="result-address">${e.address}</div>\n </div>\n `).join(""),t.querySelectorAll(".search-result-item").forEach(e=>{e.addEventListener("click",()=>{const t=parseFloat(e.getAttribute("data-lon")||"0"),n=parseFloat(e.getAttribute("data-lat")||"0");this.sceneSystem.flyToLocation(t,n)})})):t.innerHTML='\n <div class="empty-state">\n <i class="fa fa-inbox"></i>\n <div>未找到结果</div>\n </div>\n ')}handleTileSearch(){const e=document.getElementById("tile-input").value.trim().split("-");if(3===e.length){const t=parseInt(e[0]),n=parseInt(e[1]),i=parseInt(e[2]);if(!isNaN(n)&&!isNaN(i)&&!isNaN(t)&&t>=0&&t<=20){const e=document.getElementById("tile-search-btn");e.disabled=!0,e.textContent="定位中...";try{this.sceneSystem.flyToTile(n,i,t),this.showNotification("定位成功","success")}catch(e){this.showNotification("定位失败,请重试","error")}finally{e.disabled=!1,e.textContent="查询定位"}}else this.showNotification("请输入有效的瓦片坐标格式","warning")}else this.showNotification("请输入有效的瓦片坐标格式:zoom-x-y","warning")}handlePropertyPanelToggle(e){this.isPropertyPanelEnabled=e,localStorage.setItem("propertyPanelEnabled",e.toString()),e?this.createPropertyGridPanel():this.destroyPropertyGridPanel()}createPropertyGridPanel(){this.propertyGridPanel||(this.propertyGridPanel=new He({title:"属性",width:320,height:"85vh",labelWidth:"140px",position:"right",zIndex:999,groups:this.getDefaultPropertyGroups(),onPropertyChange:(e,t,n)=>{this.handlePropertyUpdate(e,t,n)},onClose:()=>{this.handlePropertyPanelClosed()}})),this.propertyGridPanel.show()}handlePropertyPanelClosed(){const e=document.getElementById("toggle-property-panel");e&&(e.checked=!1),this.isPropertyPanelEnabled=!1,localStorage.setItem("propertyPanelEnabled","false"),this.propertyGridPanel=null}handlePropertyUpdate(e,t,n){this.selectObject&&(this.selectObject[e]=t)}destroyPropertyGridPanel(){this.propertyGridPanel&&(this.propertyGridPanel.destroy(),this.propertyGridPanel=null)}getDefaultPropertyGroups(){return[{name:"场景信息",expanded:!0,items:[{key:"sceneName",name:"场景名称",type:"text",value:"默认场景",placeholder:"请输入场景名称"},{key:"showLabels",name:"显示标签",type:"checkbox",value:!0},{key:"animationSpeed",name:"动画速度",type:"number",value:1,min:.1,max:5,step:.1}]}]}showNotification(e,t="warning"){const n=document.querySelector(".scene-notification");n&&n.remove();const i=document.createElement("div");i.className=`scene-notification scene-notification-${t}`,i.textContent=e,i.style.cssText=`\n position: fixed;\n top: 20px;\n right: 20px;\n padding: 12px 16px;\n background: ${"success"===t?"#238636":"error"===t?"#da3633":"#d29922"};\n color: white;\n border-radius: 6px;\n z-index: 10000;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n animation: slideInRight 0.3s ease;\n `,document.body.appendChild(i),setTimeout(()=>{i.parentNode&&(i.style.animation="slideOutRight 0.3s ease",setTimeout(()=>{i.parentNode&&i.parentNode.removeChild(i)},300))},3e3)}updatePropertyPanel(e,t){this.selectObject=e,this.propertyGridPanel?.setGroups(t)}refresh(){this.renderContent()}},SceneSystem:ee,TilesetManager:ie,CameraUrlManager:Ne};Object.assign(window.LACDT,Ve);export{Se as AtmosphereControl,q as AtmosphereScatteringSystem,$e as BuildingControl,te as BuildingSystem,N as CameraListener,V as CameraListenerSystem,Ne as CameraUrlManager,O as DistanceFogSystem,_e as EnvironmentControl,ke as FogControl,G as HeightFogSystem,ze as LayerControl,X as LayerSystem,Ie as LightingControl,D as LightingSystem,Me as PlottingControl,Le as PostProcessingControl,j as PostProcessingSystem,se as Render,Pe as RenderControl,Be as ShadowControl,$ as ShadowSystem,Re as SidebarControl,ie as TilesetManager,Ae as TilesetSearchControl,De as ToolboxControl,Z as ToolboxSystem,Ee as VolumetricCloudControl,H as VolumetricCloudsSystem,Te as WaterControl,Y as WaterSystem,Ve as render};
|