diff --git a/CHANGES.md b/CHANGES.md index 8fc2f091b94e..d92712eeca78 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,13 @@ # Change Log +## 1.130 - 2025-06-01 + +### @cesium/engine + +#### Fixes :wrench: + +- Fixed error with `DynamicEnvironmentMapManager` when `ContextLimits.maximumCubeMapSize` is zero. + ## 1.129 - 2025-05-01 ### @cesium/engine diff --git a/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js b/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js index f9ed550e6cb0..26a039ec8c93 100644 --- a/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js +++ b/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js @@ -33,7 +33,7 @@ import ConvolveSpecularMapVS from "../Shaders/ConvolveSpecularMapVS.js"; * @typedef {object} DynamicEnvironmentMapManager.ConstructorOptions * Options for the DynamicEnvironmentMapManager constructor * @property {boolean} [enabled=true] If true, the environment map and related properties will continue to update. - * @property {number} [mipmapLevels=7] The number of mipmap levels to generate for specular maps. More mipmap levels will produce a higher resolution specular reflection. + * @property {number} [mipmapLevels=7] The maximum desired number of mipmap levels to generate for specular maps. More mipmap levels will produce a higher resolution specular reflection. The actual number of mipmaps used will be bounded by the cubemap texture size supported on the client machine. The number of mipmaps must be at least one for the environment map to be generated. * @property {number} [maximumSecondsDifference=3600] The maximum amount of elapsed seconds before a new environment map is created. * @property {number} [maximumPositionEpsilon=1000] The maximum difference in position before a new environment map is created, in meters. Small differences in position will not visibly affect results. * @property {number} [atmosphereScatteringIntensity=2.0] The intensity of the scattered light emitted from the atmosphere. This should be adjusted relative to the value of {@link Scene.light} intensity. @@ -81,14 +81,21 @@ function DynamicEnvironmentMapManager(options) { options = options ?? Frozen.EMPTY_OBJECT; - const mipmapLevels = Math.min( - options.mipmapLevels ?? 7, - Math.log2(ContextLimits.maximumCubeMapSize), + const mipmapLevels = Math.max( + Math.floor( + Math.min( + options.mipmapLevels ?? 7, + Math.log2(ContextLimits.maximumCubeMapSize), + ), + ), + 0, ); this._mipmapLevels = mipmapLevels; + + const arrayLength = Math.max(mipmapLevels - 1, 0) * 6; this._radianceMapComputeCommands = new Array(6); - this._convolutionComputeCommands = new Array((mipmapLevels - 1) * 6); + this._convolutionComputeCommands = new Array(arrayLength); this._irradianceComputeCommand = undefined; this._radianceMapFS = undefined; @@ -97,7 +104,7 @@ function DynamicEnvironmentMapManager(options) { this._va = undefined; this._radianceMapTextures = new Array(6); - this._specularMapTextures = new Array((mipmapLevels - 1) * 6); + this._specularMapTextures = new Array(arrayLength); this._radianceCubeMap = undefined; this._irradianceMapTexture = undefined; @@ -105,7 +112,7 @@ function DynamicEnvironmentMapManager(options) { DynamicEnvironmentMapManager.DEFAULT_SPHERICAL_HARMONIC_COEFFICIENTS.slice(); this._lastTime = new JulianDate(); - const width = Math.pow(2, mipmapLevels - 1); + const width = Math.max(Math.pow(2, mipmapLevels - 1), 1); this._textureDimensions = new Cartesian2(width, width); this._radiiAndDynamicAtmosphereColor = new Cartesian3(); @@ -621,6 +628,28 @@ function updateSpecularMaps(manager, frameState) { const context = frameState.context; let facesCopied = 0; + const checkComplete = () => { + // All faces for each mipmap level have been copied + const length = manager._specularMapTextures.length; + if (facesCopied >= length) { + manager._irradianceCommandDirty = true; + + if (mipmapLevels > 1) { + radianceCubeMap.sampler = new Sampler({ + minificationFilter: TextureMinificationFilter.LINEAR_MIPMAP_LINEAR, + }); + + manager._shouldRegenerateShaders = true; + + // Cleanup shared resources + manager._va.destroy(); + manager._va = undefined; + manager._convolveSP.destroy(); + manager._convolveSP = undefined; + } + } + }; + const getPostExecute = (command, index, texture, face, level) => () => { if (manager.isDestroyed() || command.canceled) { DynamicEnvironmentMapManager._activeComputeCommandCount--; @@ -638,22 +667,7 @@ function updateSpecularMaps(manager, frameState) { texture.destroy(); manager._specularMapTextures[index] = undefined; - // All faces for each mipmap level have been copied - const length = manager._specularMapTextures.length; - if (facesCopied >= length) { - manager._irradianceCommandDirty = true; - radianceCubeMap.sampler = new Sampler({ - minificationFilter: TextureMinificationFilter.LINEAR_MIPMAP_LINEAR, - }); - - manager._shouldRegenerateShaders = true; - - // Cleanup shared resources - manager._va.destroy(); - manager._va = undefined; - manager._convolveSP.destroy(); - manager._convolveSP = undefined; - } + checkComplete(); }; let index = 0; @@ -721,6 +735,7 @@ function updateSpecularMaps(manager, frameState) { width /= 2; height /= 2; } + checkComplete(); } const irradianceTextureDimensions = new Cartesian2(3, 3); // 9 coefficients @@ -841,7 +856,8 @@ DynamicEnvironmentMapManager.prototype.update = function (frameState) { const isSupported = // A FrameState type works here because the function only references the context parameter. // @ts-ignore - DynamicEnvironmentMapManager.isDynamicUpdateSupported(frameState); + DynamicEnvironmentMapManager.isDynamicUpdateSupported(frameState) && + this._mipmapLevels >= 1; if ( !isSupported || diff --git a/packages/engine/Specs/Scene/DynamicEnvironmentMapManagerSpec.js b/packages/engine/Specs/Scene/DynamicEnvironmentMapManagerSpec.js index 4e4f3d5ef0e0..4a8d3c023c41 100644 --- a/packages/engine/Specs/Scene/DynamicEnvironmentMapManagerSpec.js +++ b/packages/engine/Specs/Scene/DynamicEnvironmentMapManagerSpec.js @@ -66,25 +66,33 @@ describe("Scene/DynamicEnvironmentMapManager", function () { ); }); + it("uses at a minimum of 0 mipmap levels", () => { + const manager = new DynamicEnvironmentMapManager({ + mipmapLevels: Number.NEGATIVE_INFINITY, + }); + + expect(manager._mipmapLevels).toBe(0); + }); + describe( "render tests", () => { const time = JulianDate.fromIso8601("2024-08-30T10:45:00Z"); - let scene, orginalMaximumCubeMapSize; + let scene, originalMaximumCubeMapSize, manager; beforeAll(() => { scene = createScene({ skyBox: false, }); - orginalMaximumCubeMapSize = ContextLimits.maximumCubeMapSize; + originalMaximumCubeMapSize = ContextLimits.maximumCubeMapSize; // To keep tests fast, don't throttle ContextLimits._maximumCubeMapSize = Number.POSITIVE_INFINITY; }); afterAll(() => { scene.destroyForSpecs(); - ContextLimits._maximumCubeMapSize = orginalMaximumCubeMapSize; + ContextLimits._maximumCubeMapSize = originalMaximumCubeMapSize; }); afterEach(() => { @@ -109,6 +117,7 @@ describe("Scene/DynamicEnvironmentMapManager", function () { const primitive = new EnvironmentMockPrimitive(manager); scene.primitives.add(primitive); + scene.renderForSpecs(); scene.renderForSpecs(); expect(manager.radianceCubeMap).toBeUndefined(); @@ -131,6 +140,7 @@ describe("Scene/DynamicEnvironmentMapManager", function () { const primitive = new EnvironmentMockPrimitive(manager); scene.primitives.add(primitive); + scene.renderForSpecs(); scene.renderForSpecs(); expect(manager.radianceCubeMap).toBeUndefined(); @@ -142,13 +152,10 @@ describe("Scene/DynamicEnvironmentMapManager", function () { ); }); - it("does not update if requires extensions are not available", async function () { - spyOn( - DynamicEnvironmentMapManager, - "isDynamicUpdateSupported", - ).and.returnValue(false); - - const manager = new DynamicEnvironmentMapManager(); + it("does not update if there are zero mipmap levels", async function () { + const manager = new DynamicEnvironmentMapManager({ + mipmapLevels: 0, + }); const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583); manager.position = @@ -157,6 +164,7 @@ describe("Scene/DynamicEnvironmentMapManager", function () { const primitive = new EnvironmentMockPrimitive(manager); scene.primitives.add(primitive); + scene.renderForSpecs(); scene.renderForSpecs(); expect(manager.radianceCubeMap).toBeUndefined(); @@ -168,11 +176,11 @@ describe("Scene/DynamicEnvironmentMapManager", function () { ); }); - it("creates environment map and spherical harmonics at surface in Philadelphia with static lighting", async function () { - // Skip if required WebGL extensions are not supported - if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { - return; - } + it("does not update if requires extensions are not available", async function () { + spyOn( + DynamicEnvironmentMapManager, + "isDynamicUpdateSupported", + ).and.returnValue(false); const manager = new DynamicEnvironmentMapManager(); @@ -186,1014 +194,731 @@ describe("Scene/DynamicEnvironmentMapManager", function () { scene.renderForSpecs(); scene.renderForSpecs(); - expect(manager.radianceCubeMap).toBeInstanceOf(CubeMap); - - scene.renderForSpecs(); - - expect(manager.radianceCubeMap.sampler.minificationFilter).toEqual( - TextureMinificationFilter.LINEAR_MIPMAP_LINEAR, - ); // Has mipmaps for specular maps + expect(manager.radianceCubeMap).toBeUndefined(); - scene.renderForSpecs(); scene.renderForSpecs(); - expect(manager.sphericalHarmonicCoefficients[0]).toEqualEpsilon( - new Cartesian3( - 0.11017649620771408, - 0.13869766891002655, - 0.17165547609329224, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[1]).toEqualEpsilon( - new Cartesian3( - 0.08705271780490875, - 0.11016352474689484, - 0.15077166259288788, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[2]).toEqualEpsilon( - new Cartesian3( - -0.0014497060328722, - -0.0013909616973251104, - -0.00141593546140939, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[3]).toEqualEpsilon( - new Cartesian3( - 0.00010713530355133116, - 0.00016706169117242098, - 0.00006681153899990022, - ), - CesiumMath.EPSILON2, + expect(manager.sphericalHarmonicCoefficients).toEqual( + DynamicEnvironmentMapManager.DEFAULT_SPHERICAL_HARMONIC_COEFFICIENTS, ); - - expect(manager.sphericalHarmonicCoefficients[4].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].z).toBeLessThan(0.0); - - expect(manager.sphericalHarmonicCoefficients[5].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].z).toBeGreaterThan(0.0); - - expect(manager.sphericalHarmonicCoefficients[6].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].z).toBeLessThan(0.0); - - expect(manager.sphericalHarmonicCoefficients[7].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].z).toBeGreaterThan(0.0); - - expect(manager.sphericalHarmonicCoefficients[8].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].z).toBeLessThan(0.0); }); - it("creates environment map and spherical harmonics at altitude in Philadelphia with static lighting", async function () { - // Skip if required WebGL extensions are not supported - if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { - return; - } + function itCreatesEnvironmentMap() { + it("creates environment cubemap with specular mipmaps", async function () { + // Skip if required WebGL extensions are not supported + if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { + return; + } - const manager = new DynamicEnvironmentMapManager(); + scene.renderForSpecs(); + scene.renderForSpecs(); - const cartographic = Cartographic.fromDegrees( - -75.165222, - 39.952583, - 20000.0, - ); - manager.position = - Ellipsoid.WGS84.cartographicToCartesian(cartographic); + expect(manager.radianceCubeMap).toBeInstanceOf(CubeMap); - const primitive = new EnvironmentMockPrimitive(manager); - scene.primitives.add(primitive); + scene.renderForSpecs(); - scene.renderForSpecs(); - scene.renderForSpecs(); - - expect(manager.radianceCubeMap).toBeInstanceOf(CubeMap); - - scene.renderForSpecs(); + expect(manager.radianceCubeMap.sampler.minificationFilter).toEqual( + TextureMinificationFilter.LINEAR_MIPMAP_LINEAR, + ); + }); + } - expect(manager.radianceCubeMap.sampler.minificationFilter).toEqual( - TextureMinificationFilter.LINEAR_MIPMAP_LINEAR, - ); // Has mipmaps for specular maps + // Ratio of strongest directional light to average light + function computeDirectionality(L0, L1_1, L10, L11) { + const L0Magnitude = Cartesian3.magnitude(L0); + const L1_1Magnitude = Cartesian3.magnitude(L1_1); + const L10Magnitude = Cartesian3.magnitude(L10); + const L11Magnitude = Cartesian3.magnitude(L11); + const L1AverageMagnitude = + (L0Magnitude + L1_1Magnitude + L10Magnitude + L11Magnitude) / 4.0; - scene.renderForSpecs(); - scene.renderForSpecs(); + const L1_1Directionality = L1_1Magnitude / L1AverageMagnitude; + const L10Directionality = L10Magnitude / L1AverageMagnitude; + const L11Directionality = L11Magnitude / L1AverageMagnitude; - expect(manager.sphericalHarmonicCoefficients[0]).toEqualEpsilon( - new Cartesian3( - 0.028324954211711884, - 0.03880387544631958, - 0.050429586321115494, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[1]).toEqualEpsilon( - new Cartesian3( - -0.0048759132623672485, - -0.00047372994595207274, - 0.011921915225684643, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[2]).toEqualEpsilon( - new Cartesian3( - -0.00038596082595176995, - -0.0005534383235499263, - -0.001172146643511951, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[3]).toEqualEpsilon( - new Cartesian3( - 0.0005309119587764144, - 0.00010014028521254659, - -0.0005452318582683802, - ), - CesiumMath.EPSILON2, + return Math.max( + L1_1Directionality, + L10Directionality, + L11Directionality, ); + } - expect(manager.sphericalHarmonicCoefficients[4].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].z).toBeLessThan(0.0); + function itCreatesSphericalHarmonics() { + it("computes spherical harmonic coefficients", async function () { + // Skip if required WebGL extensions are not supported + if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { + return; + } + + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + + expect(manager.sphericalHarmonicCoefficients).not.toEqual( + DynamicEnvironmentMapManager.DEFAULT_SPHERICAL_HARMONIC_COEFFICIENTS, + ); + + expect(manager.sphericalHarmonicCoefficients[0]).toEqual( + jasmine.objectContaining({ + x: jasmine.any(Number), + y: jasmine.any(Number), + z: jasmine.any(Number), + }), + ); + + expect(manager.sphericalHarmonicCoefficients[1]).toEqual( + jasmine.objectContaining({ + x: jasmine.any(Number), + y: jasmine.any(Number), + z: jasmine.any(Number), + }), + ); + + expect(manager.sphericalHarmonicCoefficients[2]).toEqual( + jasmine.objectContaining({ + x: jasmine.any(Number), + y: jasmine.any(Number), + z: jasmine.any(Number), + }), + ); + + expect(manager.sphericalHarmonicCoefficients[3]).toEqual( + jasmine.objectContaining({ + x: jasmine.any(Number), + y: jasmine.any(Number), + z: jasmine.any(Number), + }), + ); + + expect(manager.sphericalHarmonicCoefficients[4]).toEqual( + jasmine.objectContaining({ + x: jasmine.any(Number), + y: jasmine.any(Number), + z: jasmine.any(Number), + }), + ); + + expect(manager.sphericalHarmonicCoefficients[5]).toEqual( + jasmine.objectContaining({ + x: jasmine.any(Number), + y: jasmine.any(Number), + z: jasmine.any(Number), + }), + ); + + expect(manager.sphericalHarmonicCoefficients[6]).toEqual( + jasmine.objectContaining({ + x: jasmine.any(Number), + y: jasmine.any(Number), + z: jasmine.any(Number), + }), + ); + + expect(manager.sphericalHarmonicCoefficients[7]).toEqual( + jasmine.objectContaining({ + x: jasmine.any(Number), + y: jasmine.any(Number), + z: jasmine.any(Number), + }), + ); + + expect(manager.sphericalHarmonicCoefficients[8]).toEqual( + jasmine.objectContaining({ + x: jasmine.any(Number), + y: jasmine.any(Number), + z: jasmine.any(Number), + }), + ); + }); + } - expect(manager.sphericalHarmonicCoefficients[5].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].z).toBeLessThan(0.0); + describe("with only one mipmap level", function () { + beforeEach(async function () { + manager = new DynamicEnvironmentMapManager({ + mipmapLevels: 1, + }); - expect(manager.sphericalHarmonicCoefficients[6].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].z).toBeGreaterThan(0.0); + const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583); + manager.position = + Ellipsoid.WGS84.cartographicToCartesian(cartographic); - expect(manager.sphericalHarmonicCoefficients[7].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].z).toBeGreaterThan(0.0); + const primitive = new EnvironmentMockPrimitive(manager); + scene.primitives.add(primitive); + }); - expect(manager.sphericalHarmonicCoefficients[8].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].z).toBeGreaterThan(0.0); - }); + it("creates environment cubemap", async function () { + // Skip if required WebGL extensions are not supported + if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { + return; + } - it("creates environment map and spherical harmonics above Earth's atmosphere with static lighting", async function () { - // Skip if required WebGL extensions are not supported - if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { - return; - } + scene.renderForSpecs(); + scene.renderForSpecs(); - const manager = new DynamicEnvironmentMapManager(); + expect(manager.radianceCubeMap).toBeInstanceOf(CubeMap); - const cartographic = Cartographic.fromDegrees( - -75.165222, - 39.952583, - 1000000.0, - ); - manager.position = - Ellipsoid.WGS84.cartographicToCartesian(cartographic); - - const primitive = new EnvironmentMockPrimitive(manager); - scene.primitives.add(primitive); + scene.renderForSpecs(); - scene.renderForSpecs(); - scene.renderForSpecs(); + expect( + manager.radianceCubeMap.sampler.minificationFilter, + ).not.toEqual(TextureMinificationFilter.LINEAR_MIPMAP_LINEAR); + }); - expect(manager.radianceCubeMap).toBeInstanceOf(CubeMap); + itCreatesSphericalHarmonics(); + }); - scene.renderForSpecs(); + describe("with static lighting at surface", function () { + beforeEach(async function () { + manager = new DynamicEnvironmentMapManager(); - expect(manager.radianceCubeMap.sampler.minificationFilter).toEqual( - TextureMinificationFilter.LINEAR_MIPMAP_LINEAR, - ); // Has mipmaps for specular maps + const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583); + manager.position = + Ellipsoid.WGS84.cartographicToCartesian(cartographic); - scene.renderForSpecs(); - scene.renderForSpecs(); + const primitive = new EnvironmentMockPrimitive(manager); + scene.primitives.add(primitive); + }); - expect(manager.sphericalHarmonicCoefficients[0]).toEqualEpsilon( - new Cartesian3( - 0.33580833673477173, - 0.3365404009819031, - 0.3376566469669342, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[1]).toEqualEpsilon( - new Cartesian3( - 0.2528926134109497, - 0.25208908319473267, - 0.25084879994392395, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[2]).toEqualEpsilon( - new Cartesian3( - 0.001018671551719308, - 0.0009837104007601738, - 0.0008832928724586964, - ), - CesiumMath.EPSILON2, - ); + itCreatesEnvironmentMap(); + itCreatesSphericalHarmonics(); - expect(manager.sphericalHarmonicCoefficients[3]).toEqualEpsilon( - new Cartesian3( - -0.0017577273538336158, - -0.0015308377332985401, - -0.0012394117657095194, - ), - CesiumMath.EPSILON2, - ); + it("produces radiance values with mainly blue direct light", async function () { + // Skip if required WebGL extensions are not supported + if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { + return; + } - expect(manager.sphericalHarmonicCoefficients[4].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].z).toBeGreaterThan(0.0); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); - expect(manager.sphericalHarmonicCoefficients[5].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].z).toBeLessThan(0.0); + const L0 = manager.sphericalHarmonicCoefficients[0]; + const L1_1 = manager.sphericalHarmonicCoefficients[1]; + const L10 = manager.sphericalHarmonicCoefficients[2]; + const L11 = manager.sphericalHarmonicCoefficients[3]; - expect(manager.sphericalHarmonicCoefficients[6].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].z).toBeLessThan(0.0); + expect(L0).toBeGreaterThan(L1_1); + expect(L0).toBeGreaterThan(L10); + expect(L0).toBeGreaterThan(L11); - expect(manager.sphericalHarmonicCoefficients[7].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].z).toBeLessThan(0.0); + const blueness = L0.z / (L0.x + L0.y + L0.z); + expect(blueness).toBeGreaterThan(1.0 / 3.0); - expect(manager.sphericalHarmonicCoefficients[8].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].z).toBeLessThan(0.0); + const directionality = computeDirectionality(L0, L1_1, L10, L11); + expect(directionality).toBeGreaterThan(1.0); + }); }); - it("creates environment map and spherical harmonics at surface in Philadelphia with dynamic lighting", async function () { - // Skip if required WebGL extensions are not supported - if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { - return; - } - - const manager = new DynamicEnvironmentMapManager(); - - const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583); - manager.position = - Ellipsoid.WGS84.cartographicToCartesian(cartographic); - - const primitive = new EnvironmentMockPrimitive(manager); - scene.primitives.add(primitive); - - scene.atmosphere.dynamicLighting = - DynamicAtmosphereLightingType.SUNLIGHT; + describe("with static lighting at altitude", function () { + beforeEach(async function () { + manager = new DynamicEnvironmentMapManager(); - scene.renderForSpecs(time); - scene.renderForSpecs(time); + const cartographic = Cartographic.fromDegrees( + -75.165222, + 39.952583, + 20000.0, + ); + manager.position = + Ellipsoid.WGS84.cartographicToCartesian(cartographic); - expect(manager.radianceCubeMap).toBeInstanceOf(CubeMap); - - scene.renderForSpecs(time); + const primitive = new EnvironmentMockPrimitive(manager); + scene.primitives.add(primitive); + }); - expect(manager.radianceCubeMap.sampler.minificationFilter).toEqual( - TextureMinificationFilter.LINEAR_MIPMAP_LINEAR, - ); // Has mipmaps for specular maps + itCreatesEnvironmentMap(); + itCreatesSphericalHarmonics(); - scene.renderForSpecs(time); - scene.renderForSpecs(time); + it("produces radiance values with mainly blue ambient light", async function () { + // Skip if required WebGL extensions are not supported + if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { + return; + } - expect(manager.sphericalHarmonicCoefficients[0]).toEqualEpsilon( - new Cartesian3( - 0.034476835280656815, - 0.04265068098902702, - 0.04163559526205063, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[1]).toEqualEpsilon( - new Cartesian3( - 0.01569328084588051, - 0.023243442177772522, - 0.025639381259679794, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[2]).toEqualEpsilon( - new Cartesian3( - -0.003795207943767309, - -0.0033528741914778948, - -0.0031588575802743435, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[3]).toEqualEpsilon( - new Cartesian3( - 0.008755888789892197, - 0.007121194154024124, - 0.005899451207369566, - ), - CesiumMath.EPSILON2, - ); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); - expect(manager.sphericalHarmonicCoefficients[4].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].z).toBeGreaterThan(0.0); + const L0 = manager.sphericalHarmonicCoefficients[0]; + const L1_1 = manager.sphericalHarmonicCoefficients[1]; + const L10 = manager.sphericalHarmonicCoefficients[2]; + const L11 = manager.sphericalHarmonicCoefficients[3]; - expect(manager.sphericalHarmonicCoefficients[5].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].z).toBeLessThan(0.0); + expect(L0).toBeGreaterThan(L1_1); + expect(L0).toBeGreaterThan(L10); + expect(L0).toBeGreaterThan(L11); - expect(manager.sphericalHarmonicCoefficients[6].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].z).toBeGreaterThan(0.0); + const blueness = L0.z / (L0.x + L0.y + L0.z); + expect(blueness).toBeGreaterThan(1.0 / 3.0); - expect(manager.sphericalHarmonicCoefficients[7].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].z).toBeLessThan(0.0); - - expect(manager.sphericalHarmonicCoefficients[8].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].z).toBeGreaterThan(0.0); + const directionality = computeDirectionality(L0, L1_1, L10, L11); + expect(directionality).toBeLessThan(1.0); + }); }); - it("creates environment map and spherical harmonics at surface in Sydney with dynamic lighting", async function () { - // Skip if required WebGL extensions are not supported - if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { - return; - } - - const manager = new DynamicEnvironmentMapManager(); - - const cartographic = Cartographic.fromDegrees(151.2099, -33.865143); - manager.position = - Ellipsoid.WGS84.cartographicToCartesian(cartographic); - - const primitive = new EnvironmentMockPrimitive(manager); - scene.primitives.add(primitive); - - scene.atmosphere.dynamicLighting = - DynamicAtmosphereLightingType.SUNLIGHT; + describe("with static lighting above the atmopshere ", function () { + beforeEach(async function () { + manager = new DynamicEnvironmentMapManager(); - scene.renderForSpecs(time); - scene.renderForSpecs(time); + const cartographic = Cartographic.fromDegrees( + -75.165222, + 39.952583, + 1000000.0, + ); + manager.position = + Ellipsoid.WGS84.cartographicToCartesian(cartographic); - expect(manager.radianceCubeMap).toBeInstanceOf(CubeMap); - - scene.renderForSpecs(time); + const primitive = new EnvironmentMockPrimitive(manager); + scene.primitives.add(primitive); + }); - expect(manager.radianceCubeMap.sampler.minificationFilter).toEqual( - TextureMinificationFilter.LINEAR_MIPMAP_LINEAR, - ); // Has mipmaps for specular maps + itCreatesEnvironmentMap(); + itCreatesSphericalHarmonics(); - scene.renderForSpecs(time); - scene.renderForSpecs(time); + it("produces radiance values with mainly neutral ambient light", async function () { + // Skip if required WebGL extensions are not supported + if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { + return; + } - expect(manager.sphericalHarmonicCoefficients[0]).toEqualEpsilon( - new Cartesian3( - 0.0054358793422579765, - 0.0054358793422579765, - 0.0027179396711289883, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[1]).toEqualEpsilon( - new Cartesian3( - 0.0037772462237626314, - 0.0037772462237626314, - 0.0018886231118813157, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[2]).toEqualEpsilon( - new Cartesian3( - -0.000007333524990826845, - -0.000007333524990826845, - -0.0000036667624954134226, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[3]).toEqualEpsilon( - new Cartesian3( - 0.000008501945558236912, - 0.000008501945558236912, - 0.000004250972779118456, - ), - CesiumMath.EPSILON2, - ); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); - expect(manager.sphericalHarmonicCoefficients[4].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].z).toBeLessThan(0.0); + const L0 = manager.sphericalHarmonicCoefficients[0]; + const L1_1 = manager.sphericalHarmonicCoefficients[1]; + const L10 = manager.sphericalHarmonicCoefficients[2]; + const L11 = manager.sphericalHarmonicCoefficients[3]; - expect(manager.sphericalHarmonicCoefficients[5].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].z).toBeGreaterThan(0.0); + expect(L0).toBeGreaterThan(L1_1); + expect(L0).toBeGreaterThan(L10); + expect(L0).toBeGreaterThan(L11); - expect(manager.sphericalHarmonicCoefficients[6].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].z).toBeLessThan(0.0); - - expect(manager.sphericalHarmonicCoefficients[7].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].z).toBeGreaterThan(0.0); - - expect(manager.sphericalHarmonicCoefficients[8].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].z).toBeLessThan(0.0); + const blueness = L0.z / (L0.x + L0.y + L0.z); + expect(blueness).toEqualEpsilon(1.0 / 3.0, CesiumMath.EPSILON1); + }); }); - it("lighting uses atmosphere properties", async function () { - // Skip if required WebGL extensions are not supported - if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { - return; - } - - const manager = new DynamicEnvironmentMapManager(); - - const cartographic = Cartographic.fromDegrees( - -75.165222, - 39.952583, - 20000.0, - ); - manager.position = - Ellipsoid.WGS84.cartographicToCartesian(cartographic); - - scene.atmosphere.hueShift = 0.5; + describe("with dynamic lighting in philadelphia", function () { + beforeEach(async function () { + scene.atmosphere.dynamicLighting = + DynamicAtmosphereLightingType.SUNLIGHT; - const primitive = new EnvironmentMockPrimitive(manager); - scene.primitives.add(primitive); + manager = new DynamicEnvironmentMapManager(); - scene.renderForSpecs(); - scene.renderForSpecs(); - - expect(manager.radianceCubeMap).toBeInstanceOf(CubeMap); - - scene.renderForSpecs(); + const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583); + manager.position = + Ellipsoid.WGS84.cartographicToCartesian(cartographic); - expect(manager.radianceCubeMap.sampler.minificationFilter).toEqual( - TextureMinificationFilter.LINEAR_MIPMAP_LINEAR, - ); // Has mipmaps for specular maps + const primitive = new EnvironmentMockPrimitive(manager); + scene.primitives.add(primitive); + }); - scene.renderForSpecs(); - scene.renderForSpecs(); + itCreatesEnvironmentMap(); + itCreatesSphericalHarmonics(); - expect(manager.sphericalHarmonicCoefficients[0]).toEqualEpsilon( - new Cartesian3( - 0.05602460727095604, - 0.04545757919549942, - 0.02313476987183094, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[1]).toEqualEpsilon( - new Cartesian3( - 0.008652083575725555, - 0.004114487674087286, - -0.0017214358085766435, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[2]).toEqualEpsilon( - new Cartesian3( - -0.00099410570692271, - -0.0008244783966802061, - -0.00026270488160662353, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[3]).toEqualEpsilon( - new Cartesian3( - -0.000446554331574589, - -0.000012375472579151392, - 0.0005265426589176059, - ), - CesiumMath.EPSILON2, - ); + it("produces radiance values with mainly blue direct light", async function () { + // Skip if required WebGL extensions are not supported + if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { + return; + } - expect(manager.sphericalHarmonicCoefficients[4].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].z).toBeLessThan(0.0); + scene.renderForSpecs(time); + scene.renderForSpecs(time); + scene.renderForSpecs(time); + scene.renderForSpecs(time); + scene.renderForSpecs(time); - expect(manager.sphericalHarmonicCoefficients[5].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].z).toBeLessThan(0.0); + const L0 = manager.sphericalHarmonicCoefficients[0]; + const L1_1 = manager.sphericalHarmonicCoefficients[1]; + const L10 = manager.sphericalHarmonicCoefficients[2]; + const L11 = manager.sphericalHarmonicCoefficients[3]; - expect(manager.sphericalHarmonicCoefficients[6].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].z).toBeGreaterThan(0.0); + expect(L0).toBeGreaterThan(L1_1); + expect(L0).toBeGreaterThan(L10); + expect(L0).toBeGreaterThan(L11); - expect(manager.sphericalHarmonicCoefficients[7].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].z).toBeGreaterThan(0.0); + const blueness = L0.z / (L0.x + L0.y + L0.z); + expect(blueness).toBeGreaterThan(1.0 / 3.0); - expect(manager.sphericalHarmonicCoefficients[8].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].z).toBeGreaterThan(0.0); + const directionality = computeDirectionality(L0, L1_1, L10, L11); + expect(directionality).toBeGreaterThan(1.0); + }); }); - it("lighting uses atmosphereScatteringIntensity value", async function () { - // Skip if required WebGL extensions are not supported - if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { - return; - } - const manager = new DynamicEnvironmentMapManager(); - manager.atmosphereScatteringIntensity = 1.0; - - const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583); - manager.position = - Ellipsoid.WGS84.cartographicToCartesian(cartographic); + describe("with dynamic lighting in sydney", function () { + beforeEach(async function () { + scene.atmosphere.dynamicLighting = + DynamicAtmosphereLightingType.SUNLIGHT; - const primitive = new EnvironmentMockPrimitive(manager); - scene.primitives.add(primitive); - - scene.renderForSpecs(); - scene.renderForSpecs(); + manager = new DynamicEnvironmentMapManager(); - expect(manager.radianceCubeMap).toBeInstanceOf(CubeMap); + const cartographic = Cartographic.fromDegrees(151.2099, -33.865143); + manager.position = + Ellipsoid.WGS84.cartographicToCartesian(cartographic); - scene.renderForSpecs(); + const primitive = new EnvironmentMockPrimitive(manager); + scene.primitives.add(primitive); + }); - expect(manager.radianceCubeMap.sampler.minificationFilter).toEqual( - TextureMinificationFilter.LINEAR_MIPMAP_LINEAR, - ); // Has mipmaps for specular maps + itCreatesEnvironmentMap(); + itCreatesSphericalHarmonics(); - scene.renderForSpecs(); - scene.renderForSpecs(); + it("produces radiance values with mainly neutral direct light", async function () { + // Skip if required WebGL extensions are not supported + if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { + return; + } - expect(manager.sphericalHarmonicCoefficients[0]).toEqualEpsilon( - new Cartesian3( - 0.0322723351418972, - 0.039464931935071945, - 0.047749463468790054, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[1]).toEqualEpsilon( - new Cartesian3( - 0.025989927351474762, - 0.031872138381004333, - 0.04223670810461044, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[2]).toEqualEpsilon( - new Cartesian3( - -0.0008744273218326271, - -0.0008044499554671347, - -0.0008345510577782989, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[3]).toEqualEpsilon( - new Cartesian3( - -0.0000013118115020915866, - -0.000017321406630799174, - -0.000006108442903496325, - ), - CesiumMath.EPSILON2, - ); + scene.renderForSpecs(time); + scene.renderForSpecs(time); + scene.renderForSpecs(time); + scene.renderForSpecs(time); + scene.renderForSpecs(time); - expect(manager.sphericalHarmonicCoefficients[4].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].z).toBeLessThan(0.0); + const L0 = manager.sphericalHarmonicCoefficients[0]; + const L1_1 = manager.sphericalHarmonicCoefficients[1]; + const L10 = manager.sphericalHarmonicCoefficients[2]; + const L11 = manager.sphericalHarmonicCoefficients[3]; - expect(manager.sphericalHarmonicCoefficients[5].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].z).toBeGreaterThan(0.0); + expect(L0).toBeGreaterThan(L1_1); + expect(L0).toBeGreaterThan(L10); + expect(L0).toBeGreaterThan(L11); - expect(manager.sphericalHarmonicCoefficients[6].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].z).toBeLessThan(0.0); + const blueness = L0.z / (L0.x + L0.y + L0.z); + expect(blueness).toEqualEpsilon(1.0 / 3.0, CesiumMath.EPSILON1); - expect(manager.sphericalHarmonicCoefficients[7].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].z).toBeGreaterThan(0.0); - - expect(manager.sphericalHarmonicCoefficients[8].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].z).toBeLessThan(0.0); + const directionality = computeDirectionality(L0, L1_1, L10, L11); + expect(directionality).toBeGreaterThan(1.0); + }); }); - it("lighting uses gamma value", async function () { - // Skip if required WebGL extensions are not supported - if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { - return; - } + describe("with brightness property", function () { + beforeEach(async function () { + manager = new DynamicEnvironmentMapManager(); + manager.brightness = 2.0; - const manager = new DynamicEnvironmentMapManager(); - manager.gamma = 0.5; + const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583); + manager.position = + Ellipsoid.WGS84.cartographicToCartesian(cartographic); - const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583); - manager.position = - Ellipsoid.WGS84.cartographicToCartesian(cartographic); + const primitive = new EnvironmentMockPrimitive(manager); + scene.primitives.add(primitive); + }); - const primitive = new EnvironmentMockPrimitive(manager); - scene.primitives.add(primitive); + itCreatesEnvironmentMap(); + itCreatesSphericalHarmonics(); - scene.renderForSpecs(); - scene.renderForSpecs(); + it("produces brighter radiance values with higher values", async function () { + // Skip if required WebGL extensions are not supported + if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { + return; + } - expect(manager.radianceCubeMap).toBeInstanceOf(CubeMap); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); - scene.renderForSpecs(); + const L0 = manager.sphericalHarmonicCoefficients[0]; - expect(manager.radianceCubeMap.sampler.minificationFilter).toEqual( - TextureMinificationFilter.LINEAR_MIPMAP_LINEAR, - ); // Has mipmaps for specular maps + manager.brightness = 1.0; + manager.reset(); - scene.renderForSpecs(); - scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); - expect(manager.sphericalHarmonicCoefficients[0]).toEqualEpsilon( - new Cartesian3( - 0.18712928891181946, - 0.21367456018924713, - 0.23666927218437195, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[1]).toEqualEpsilon( - new Cartesian3( - 0.13568174839019775, - 0.15787045657634735, - 0.19085952639579773, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[2]).toEqualEpsilon( - new Cartesian3( - -0.0011452456237748265, - -0.0010327763156965375, - -0.001100384397432208, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[3]).toEqualEpsilon( - new Cartesian3( - 0.00025430042296648026, - 0.00028964842204004526, - 0.00021805899450555444, - ), - CesiumMath.EPSILON2, - ); - - expect(manager.sphericalHarmonicCoefficients[4].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].z).toBeLessThan(0.0); - - expect(manager.sphericalHarmonicCoefficients[5].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].z).toBeGreaterThan(0.0); - - expect(manager.sphericalHarmonicCoefficients[6].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].z).toBeLessThan(0.0); - - expect(manager.sphericalHarmonicCoefficients[7].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].z).toBeGreaterThan(0.0); - - expect(manager.sphericalHarmonicCoefficients[8].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].z).toBeLessThan(0.0); + expect(L0).toBeGreaterThan(manager.sphericalHarmonicCoefficients[0]); + }); }); - it("lighting uses brightness value", async function () { - // Skip if required WebGL extensions are not supported - if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { - return; - } - - const manager = new DynamicEnvironmentMapManager(); - manager.brightness = 0.5; - - const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583); - manager.position = - Ellipsoid.WGS84.cartographicToCartesian(cartographic); - - const primitive = new EnvironmentMockPrimitive(manager); - scene.primitives.add(primitive); - - scene.renderForSpecs(); - scene.renderForSpecs(); + describe("with atmosphereScatteringIntensity property", function () { + beforeEach(async function () { + manager = new DynamicEnvironmentMapManager(); + manager.atmosphereScatteringIntensity = 8.0; - expect(manager.radianceCubeMap).toBeInstanceOf(CubeMap); + const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583); + manager.position = + Ellipsoid.WGS84.cartographicToCartesian(cartographic); - scene.renderForSpecs(); + const primitive = new EnvironmentMockPrimitive(manager); + scene.primitives.add(primitive); + }); - expect(manager.radianceCubeMap.sampler.minificationFilter).toEqual( - TextureMinificationFilter.LINEAR_MIPMAP_LINEAR, - ); // Has mipmaps for specular maps + itCreatesEnvironmentMap(); + itCreatesSphericalHarmonics(); - scene.renderForSpecs(); - scene.renderForSpecs(); + it("produces brighter radiance values with higher values", async function () { + // Skip if required WebGL extensions are not supported + if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { + return; + } - expect(manager.sphericalHarmonicCoefficients[0]).toEqualEpsilon( - new Cartesian3( - 0.05981340631842613, - 0.07419705390930176, - 0.09077795594930649, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[1]).toEqualEpsilon( - new Cartesian3( - 0.051604993641376495, - 0.06336799263954163, - 0.08409948647022247, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[2]).toEqualEpsilon( - new Cartesian3( - -0.000745132565498352, - -0.0006284310948103666, - -0.000669674074742943, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[3]).toEqualEpsilon( - new Cartesian3( - 0.00004796023131348193, - 0.000024254957679659128, - 0.00004792874096892774, - ), - CesiumMath.EPSILON2, - ); - - expect(manager.sphericalHarmonicCoefficients[4].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].z).toBeLessThan(0.0); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); - expect(manager.sphericalHarmonicCoefficients[5].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].z).toBeGreaterThan(0.0); + const L0 = manager.sphericalHarmonicCoefficients[0]; - expect(manager.sphericalHarmonicCoefficients[6].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].z).toBeLessThan(0.0); + manager.atmosphereScatteringIntensity = 1.0; + manager.reset(); - expect(manager.sphericalHarmonicCoefficients[7].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].z).toBeGreaterThan(0.0); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); - expect(manager.sphericalHarmonicCoefficients[8].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].z).toBeLessThan(0.0); + expect(L0).toBeGreaterThan(manager.sphericalHarmonicCoefficients[0]); + }); }); - it("lighting uses saturation value", async function () { - // Skip if required WebGL extensions are not supported - if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { - return; - } - - const manager = new DynamicEnvironmentMapManager(); - manager.saturation = 0.0; + describe("with gamma property", function () { + beforeEach(async function () { + manager = new DynamicEnvironmentMapManager(); + manager.gamma = 2.0; - const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583); - manager.position = - Ellipsoid.WGS84.cartographicToCartesian(cartographic); + const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583); + manager.position = + Ellipsoid.WGS84.cartographicToCartesian(cartographic); - const primitive = new EnvironmentMockPrimitive(manager); - scene.primitives.add(primitive); - - scene.renderForSpecs(); - scene.renderForSpecs(); - - expect(manager.radianceCubeMap).toBeInstanceOf(CubeMap); - - scene.renderForSpecs(); - - expect(manager.radianceCubeMap.sampler.minificationFilter).toEqual( - TextureMinificationFilter.LINEAR_MIPMAP_LINEAR, - ); // Has mipmaps for specular maps + const primitive = new EnvironmentMockPrimitive(manager); + scene.primitives.add(primitive); + }); - scene.renderForSpecs(); - scene.renderForSpecs(); + itCreatesEnvironmentMap(); + itCreatesSphericalHarmonics(); - expect(manager.sphericalHarmonicCoefficients[0]).toEqualEpsilon( - new Cartesian3( - 0.13499368727207184, - 0.13499368727207184, - 0.13499368727207184, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[1]).toEqualEpsilon( - new Cartesian3( - 0.1081928238272667, - 0.1081928238272667, - 0.1081928238272667, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[2]).toEqualEpsilon( - new Cartesian3( - -0.0014497060328722, - -0.0013909616973251104, - -0.00141593546140939, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[3]).toEqualEpsilon( - new Cartesian3( - 0.00010713530355133116, - 0.00016706169117242098, - 0.00006681153899990022, - ), - CesiumMath.EPSILON2, - ); + it("produces wider range of radiance values with higher values", async function () { + // Skip if required WebGL extensions are not supported + if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { + return; + } - expect(manager.sphericalHarmonicCoefficients[4].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].z).toBeLessThan(0.0); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); - expect(manager.sphericalHarmonicCoefficients[5].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].z).toBeGreaterThan(0.0); + const L0 = manager.sphericalHarmonicCoefficients[0]; - expect(manager.sphericalHarmonicCoefficients[6].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].z).toBeLessThan(0.0); + manager.gamma = 1.0; + manager.reset(); - expect(manager.sphericalHarmonicCoefficients[7].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].z).toBeGreaterThan(0.0); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); - expect(manager.sphericalHarmonicCoefficients[8].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].z).toBeLessThan(0.0); + expect(L0).toBeLessThan(manager.sphericalHarmonicCoefficients[0]); + }); }); - it("lighting uses ground color value", async function () { - // Skip if required WebGL extensions are not supported - if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { - return; - } + describe("with saturation property", function () { + beforeEach(async function () { + manager = new DynamicEnvironmentMapManager(); + manager.saturation = 0.0; - const manager = new DynamicEnvironmentMapManager(); - manager.groundColor = Color.RED; - - const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583); - manager.position = - Ellipsoid.WGS84.cartographicToCartesian(cartographic); - - const primitive = new EnvironmentMockPrimitive(manager); - scene.primitives.add(primitive); - - scene.renderForSpecs(); - scene.renderForSpecs(); - - expect(manager.radianceCubeMap).toBeInstanceOf(CubeMap); - - scene.renderForSpecs(); - - expect(manager.radianceCubeMap.sampler.minificationFilter).toEqual( - TextureMinificationFilter.LINEAR_MIPMAP_LINEAR, - ); // Has mipmaps for specular maps - - scene.renderForSpecs(); - scene.renderForSpecs(); + const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583); + manager.position = + Ellipsoid.WGS84.cartographicToCartesian(cartographic); - expect(manager.sphericalHarmonicCoefficients[0]).toEqualEpsilon( - new Cartesian3( - 0.1342056840658188, - 0.11958353966474533, - 0.15991388261318207, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[1]).toEqualEpsilon( - new Cartesian3( - 0.07575193047523499, - 0.11915278434753418, - 0.15629366040229797, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[2]).toEqualEpsilon( - new Cartesian3( - -0.0011700564064085484, - -0.0016134318429976702, - -0.0015525781782343984, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[3]).toEqualEpsilon( - new Cartesian3( - 0.0002928615431301296, - 0.000019326049368828535, - -0.000023931264877319336, - ), - CesiumMath.EPSILON2, - ); + const primitive = new EnvironmentMockPrimitive(manager); + scene.primitives.add(primitive); + }); - expect(manager.sphericalHarmonicCoefficients[4].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].y).toEqualEpsilon( - 0.0, - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[4].z).toEqualEpsilon( - 0.0, - CesiumMath.EPSILON2, - ); + itCreatesEnvironmentMap(); + itCreatesSphericalHarmonics(); - expect(manager.sphericalHarmonicCoefficients[5].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].z).toBeGreaterThan(0.0); + it("produces less saturated colors with lower values", async function () { + // Skip if required WebGL extensions are not supported + if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { + return; + } - expect(manager.sphericalHarmonicCoefficients[6].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].z).toBeLessThan(0.0); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); - expect(manager.sphericalHarmonicCoefficients[7].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].z).toBeGreaterThan(0.0); + const L0 = manager.sphericalHarmonicCoefficients[0]; - expect(manager.sphericalHarmonicCoefficients[8].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].z).toBeLessThan(0.0); + const maxChannel = Math.max(L0.x, L0.y, L0.z); + expect(L0).toEqualEpsilon( + new Cartesian3(maxChannel, maxChannel, maxChannel), + CesiumMath.EPSILON5, + ); + }); }); - it("lighting uses ground albedo value", async function () { - // Skip if required WebGL extensions are not supported - if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { - return; - } + describe("with groundColor property", function () { + beforeEach(async function () { + manager = new DynamicEnvironmentMapManager(); + manager.groundColor = Color.RED; - const manager = new DynamicEnvironmentMapManager(); - manager.groundAlbedo = 1.0; - - const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583); - manager.position = - Ellipsoid.WGS84.cartographicToCartesian(cartographic); - - const primitive = new EnvironmentMockPrimitive(manager); - scene.primitives.add(primitive); + const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583); + manager.position = + Ellipsoid.WGS84.cartographicToCartesian(cartographic); - scene.renderForSpecs(); - scene.renderForSpecs(); + const primitive = new EnvironmentMockPrimitive(manager); + scene.primitives.add(primitive); + }); - expect(manager.radianceCubeMap).toBeInstanceOf(CubeMap); + itCreatesEnvironmentMap(); + itCreatesSphericalHarmonics(); + + it("produces radiance values with the reflected color", async function () { + // Skip if required WebGL extensions are not supported + if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { + return; + } + + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + + const L0 = manager.sphericalHarmonicCoefficients[0]; + + manager.groundColor = Color.BLUE; + manager.reset(); + + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + + expect(L0.x).toBeGreaterThan( + manager.sphericalHarmonicCoefficients[0].x, + ); + expect(L0.y).toEqualEpsilon( + manager.sphericalHarmonicCoefficients[0].y, + CesiumMath.EPSILON2, + ); + expect(L0.z).toBeLessThan(manager.sphericalHarmonicCoefficients[0].z); + }); + }); - scene.renderForSpecs(); + describe("with groundAlbedo property", function () { + beforeEach(async function () { + manager = new DynamicEnvironmentMapManager(); + manager.groundAlbedo = 2.0; - expect(manager.radianceCubeMap.sampler.minificationFilter).toEqual( - TextureMinificationFilter.LINEAR_MIPMAP_LINEAR, - ); // Has mipmaps for specular maps + const cartographic = Cartographic.fromDegrees(-75.165222, 39.952583); + manager.position = + Ellipsoid.WGS84.cartographicToCartesian(cartographic); - scene.renderForSpecs(); - scene.renderForSpecs(); + const primitive = new EnvironmentMockPrimitive(manager); + scene.primitives.add(primitive); + }); - expect(manager.sphericalHarmonicCoefficients[0]).toEqualEpsilon( - new Cartesian3( - 0.15277373790740967, - 0.1812949925661087, - 0.19759616255760193, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[1]).toEqualEpsilon( - new Cartesian3( - 0.0670194923877716, - 0.09013032913208008, - 0.13857196271419525, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[2]).toEqualEpsilon( - new Cartesian3( - -0.000953961513005197, - -0.000895244418643415, - -0.0011140345595777035, - ), - CesiumMath.EPSILON2, - ); - expect(manager.sphericalHarmonicCoefficients[3]).toEqualEpsilon( - new Cartesian3( - 0.00043638586066663265, - 0.0004962628008797765, - 0.0002673182752914727, - ), - CesiumMath.EPSILON2, - ); + itCreatesEnvironmentMap(); + itCreatesSphericalHarmonics(); + + it("produces radiance values with more light reflected from the ground", async function () { + // Skip if required WebGL extensions are not supported + if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { + return; + } + + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + + const L0 = manager.sphericalHarmonicCoefficients[0]; + const L1_1 = manager.sphericalHarmonicCoefficients[1]; + const L10 = manager.sphericalHarmonicCoefficients[2]; + const L11 = manager.sphericalHarmonicCoefficients[3]; + + manager.groundAlbedo = 0.0; + manager.reset(); + + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + + expect(L0).toBeGreaterThan(manager.sphericalHarmonicCoefficients[0]); + expect(L1_1).toBeLessThan(manager.sphericalHarmonicCoefficients[1]); + expect(L10).toBeGreaterThan(manager.sphericalHarmonicCoefficients[2]); + expect(L11).toBeLessThan(manager.sphericalHarmonicCoefficients[3]); + }); + }); - expect(manager.sphericalHarmonicCoefficients[4].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[4].z).toBeLessThan(0.0); + describe("with scene atmosphere properties", function () { + beforeEach(async function () { + manager = new DynamicEnvironmentMapManager(); - expect(manager.sphericalHarmonicCoefficients[5].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[5].z).toBeGreaterThan(0.0); + const cartographic = Cartographic.fromDegrees( + -75.165222, + 39.952583, + 20000.0, + ); + manager.position = + Ellipsoid.WGS84.cartographicToCartesian(cartographic); - expect(manager.sphericalHarmonicCoefficients[6].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[6].z).toBeLessThan(0.0); + scene.atmosphere.hueShift = 0.5; - expect(manager.sphericalHarmonicCoefficients[7].x).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].y).toBeGreaterThan(0.0); - expect(manager.sphericalHarmonicCoefficients[7].z).toBeGreaterThan(0.0); + const primitive = new EnvironmentMockPrimitive(manager); + scene.primitives.add(primitive); + }); - expect(manager.sphericalHarmonicCoefficients[8].x).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].y).toBeLessThan(0.0); - expect(manager.sphericalHarmonicCoefficients[8].z).toBeLessThan(0.0); + itCreatesEnvironmentMap(); + itCreatesSphericalHarmonics(); + + it("produces radiance values with hues corresponding to the atmopshere", async function () { + // Skip if required WebGL extensions are not supported + if (!DynamicEnvironmentMapManager.isDynamicUpdateSupported(scene)) { + return; + } + + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + + const L0 = manager.sphericalHarmonicCoefficients[0]; + + scene.atmosphere.hueShift = 0.0; + manager.reset(); + + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + scene.renderForSpecs(); + + expect(L0.x).toBeGreaterThan( + manager.sphericalHarmonicCoefficients[0].x, + ); + expect(L0.y).toEqualEpsilon( + manager.sphericalHarmonicCoefficients[0].y, + CesiumMath.EPSILON2, + ); + expect(L0.z).toBeLessThan(manager.sphericalHarmonicCoefficients[0].z); + }); }); it("destroys", function () {