Merge branch 'main' into version-1.133.1

This commit is contained in:
Gabby Getz 2025-09-08 16:44:40 -04:00 committed by GitHub
commit a61ff9149e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 462 additions and 229 deletions

6
.git-blame-ignore-revs Normal file
View File

@ -0,0 +1,6 @@
# Format all code with prettier
2fd0e8f7e4212bd1e7084299187f70597a6bbfd8
# var -> const/let
8143df4436b79260e9861f63c6ac134d7910a818
# run prettier v3
09a719b8fb4616ecbcd7370e81dcdc998b64b6e2

View File

@ -6,6 +6,7 @@
"cesium.gltf-vscode",
"bierner.github-markdown-preview",
"DavidAnson.vscode-markdownlint",
"streetsidesoftware.code-spell-checker"
"streetsidesoftware.code-spell-checker",
"eamodio.gitlens"
]
}

View File

@ -29,5 +29,9 @@
"javascript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets": false,
"javascript.format.placeOpenBraceOnNewLineForFunctions": false,
"javascript.format.placeOpenBraceOnNewLineForControlBlocks": false,
"glTF.defaultV2Engine": "Cesium"
"glTF.defaultV2Engine": "Cesium",
"gitlens.advanced.blame.customArguments": [
"--ignore-revs-file",
"${workspaceFolder}/.git-blame-ignore-revs"
],
}

View File

@ -1,5 +1,19 @@
# Change Log
## 1.134 - 2025-10-01
### @cesium/engine
#### Fixes :wrench:
- Materials loaded from type now respect submaterials present in the referenced material type. [#10566](https://github.com/CesiumGS/cesium/issues/10566)
- Fix flickering artifact in Gaussian splat models caused by incorrect sorting results. [#12662](https://github.com/CesiumGS/cesium/issues/12662)
#### Additions :tada:
- Adds an async factory method for the Material class that allows callers to wait on resource loading. [#10566](https://github.com/CesiumGS/cesium/issues/10566)
## 1.133.1 - 2025-09-08
This is an npm-only release to fix a dependency issue published in 1.133.0

View File

@ -429,3 +429,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu
- [Matt Schwartz](https://github.com/mzschwartz5)
- [Easy Mahaffey](https://github.com/easymaahffey)
- [Pamela Augustine](https://github.com/pamelaAugustine)
- [宋时旺](https://github.com/BlockCnFuture)

View File

@ -39,6 +39,7 @@
- Double-check your settings for name and email: `git config --get-regexp user.*`.
- Recommended Git settings:
- `git config --global fetch.prune true` - when fetching remote changes, remove any remote branches that no longer exist on the remote.
- `git config blame.ignoreRevsFile .git-blame-ignore-revs` - uses the ignore file to skip certain noisy revisions (like formatting) when running git blame. Alternatively, for VSCode users, install the GitLens extension, which will automatically use the ignore file.
- Have [commit access](https://github.com/CesiumGS/cesium/blob/main/Documentation/Contributors/CommittersGuide/README.md) to CesiumJS?
- No
- Fork [cesium](https://github.com/CesiumGS/cesium).

View File

@ -566,7 +566,7 @@ GaussianSplatPrimitive.generateSplatTexture = function (primitive, frameState) {
},
});
}
primitive._vertexArray = undefined;
primitive._lastTextureHeight = splatTextureData.height;
primitive._lastTextureWidth = splatTextureData.width;
@ -652,8 +652,9 @@ GaussianSplatPrimitive.buildGSplatDrawCommand = function (
const uniformMap = renderResources.uniformMap;
const textureCache = primitive.gaussianSplatTexture;
uniformMap.u_splatAttributeTexture = function () {
return primitive.gaussianSplatTexture;
return textureCache;
};
if (primitive._sphericalHarmonicsDegree > 0) {
@ -768,12 +769,13 @@ GaussianSplatPrimitive.buildGSplatDrawCommand = function (
scratchMatrix4B,
);
const vertexArrayCache = primitive._vertexArray;
const command = new DrawCommand({
boundingVolume: tileset.boundingSphere,
modelMatrix: modelMatrix,
uniformMap: uniformMap,
renderState: renderState,
vertexArray: primitive._vertexArray,
vertexArray: vertexArrayCache,
shaderProgram: shaderProgram,
cull: renderStateOptions.cull.enabled,
pass: Pass.GAUSSIAN_SPLATS,

View File

@ -327,12 +327,58 @@ function Material(options) {
this._defaultTexture = undefined;
/**
* Any and all promises that are created when initializing the material.
* Examples: loading images and cubemaps.
*
* @type {Promise[]}
* @private
*/
this._initializationPromises = [];
/**
* An error that occurred in async operations during material initialization.
* Only one error is stored.
*
* @type {Error|undefined}
* @private
*/
this._initializationError = undefined;
initializeMaterial(options, this);
Object.defineProperties(this, {
type: {
value: this.type,
writable: false,
},
/**
* The {@link TextureMinificationFilter} to apply to this material's textures.
* @type {TextureMinificationFilter}
* @default TextureMinificationFilter.LINEAR
*/
minificationFilter: {
get: function () {
return this._minificationFilter;
},
set: function (value) {
this._minificationFilter = value;
},
},
/**
* The {@link TextureMagnificationFilter} to apply to this material's textures.
* @type {TextureMagnificationFilter}
* @default TextureMagnificationFilter.LINEAR
*/
magnificationFilter: {
get: function () {
return this._magnificationFilter;
},
set: function (value) {
this._magnificationFilter = value;
},
},
});
if (!defined(Material._uniformList[this.type])) {
@ -384,6 +430,68 @@ Material.fromType = function (type, uniforms) {
return material;
};
/**
* Creates a new material using an existing material type and returns a promise that resolves when
* all of the material's resources have been loaded.
*
* @param {string} type The base material type.
* @param {object} [uniforms] Overrides for the default uniforms.
* @returns {Promise<Material>} A promise that resolves to a new material object when all resources are loaded.
*
* @exception {DeveloperError} material with that type does not exist.
*
* @example
* const material = await Cesium.Material.fromTypeAsync('Image', {
* image: '../Images/Cesium_Logo_overlay.png'
* });
*/
Material.fromTypeAsync = async function (type, uniforms) {
//>>includeStart('debug', pragmas.debug);
if (!defined(Material._materialCache.getMaterial(type))) {
throw new DeveloperError(`material with type '${type}' does not exist.`);
}
//>>includeEnd('debug');
const initializationPromises = [];
// Unlike Material.fromType, we need to specify the uniforms in the Material constructor up front,
// or else anything that needs to be async loaded won't be kicked off until the next Update call.
const material = new Material({
fabric: {
type: type,
uniforms: uniforms,
},
});
// Recursively collect initialization promises for this material and its submaterials.
getInitializationPromises(material, initializationPromises);
await Promise.all(initializationPromises);
initializationPromises.length = 0;
if (defined(material._initializationError)) {
throw material._initializationError;
}
return material;
};
/**
* Recursively traverses the material and its submaterials to collect all initialization promises.
* @param {Material} material The material to traverse.
* @param {Promise[]} initializationPromises The array to collect promises into.
*
* @private
*/
function getInitializationPromises(material, initializationPromises) {
initializationPromises.push(...material._initializationPromises);
const submaterials = material.materials;
for (const name in submaterials) {
if (submaterials.hasOwnProperty(name)) {
const submaterial = submaterials[name];
getInitializationPromises(submaterial, initializationPromises);
}
}
}
/**
* Gets whether or not this material is translucent.
* @returns {boolean} <code>true</code> if this material is translucent, <code>false</code> otherwise.
@ -586,6 +694,7 @@ function initializeMaterial(options, result) {
result._strict = options.strict ?? false;
result._count = options.count ?? 0;
result._template = clone(options.fabric ?? Frozen.EMPTY_OBJECT);
result.fabric = clone(options.fabric ?? Frozen.EMPTY_OBJECT);
result._template.uniforms = clone(
result._template.uniforms ?? Frozen.EMPTY_OBJECT,
);
@ -616,15 +725,15 @@ function initializeMaterial(options, result) {
// Make sure the template has no obvious errors. More error checking happens later.
checkForTemplateErrors(result);
createMethodDefinition(result);
createUniforms(result);
createSubMaterials(result);
// If the material has a new type, add it to the cache.
if (!defined(cachedMaterial)) {
Material._materialCache.addMaterial(result.type, result);
}
createMethodDefinition(result);
createUniforms(result);
createSubMaterials(result);
const defaultTranslucent =
result._translucentFunctions.length === 0 ? true : undefined;
translucent = translucent ?? defaultTranslucent;
@ -858,10 +967,10 @@ function createTexture2DUpdateFunction(uniformId) {
texture.destroy();
}
texture = undefined;
material._texturePaths[uniformId] = undefined;
}
if (!defined(texture)) {
material._texturePaths[uniformId] = undefined;
texture = material._textures[uniformId] = material._defaultTexture;
uniformDimensionsName = `${uniformId}Dimensions`;
@ -876,59 +985,90 @@ function createTexture2DUpdateFunction(uniformId) {
return;
}
// When using the entity layer, the Resource objects get recreated on getValue because
// they are clonable. That's why we check the url property for Resources
// because the instances aren't the same and we keep trying to load the same
// image if it fails to load.
const isResource = uniformValue instanceof Resource;
if (
!defined(material._texturePaths[uniformId]) ||
(isResource &&
uniformValue.url !== material._texturePaths[uniformId].url) ||
(!isResource && uniformValue !== material._texturePaths[uniformId])
) {
if (typeof uniformValue === "string" || isResource) {
const resource = isResource
? uniformValue
: Resource.createIfNeeded(uniformValue);
let promise;
if (ktx2Regex.test(resource.url)) {
promise = loadKTX2(resource.url);
} else {
promise = resource.fetchImage();
}
Promise.resolve(promise)
.then(function (image) {
material._loadedImages.push({
id: uniformId,
image: image,
});
})
.catch(function () {
if (defined(texture) && texture !== material._defaultTexture) {
texture.destroy();
}
material._textures[uniformId] = material._defaultTexture;
});
} else if (
uniformValue instanceof HTMLCanvasElement ||
(uniformValue instanceof HTMLCanvasElement ||
uniformValue instanceof HTMLImageElement ||
uniformValue instanceof ImageBitmap ||
uniformValue instanceof OffscreenCanvas
) {
material._loadedImages.push({
id: uniformId,
image: uniformValue,
});
}
uniformValue instanceof OffscreenCanvas) &&
uniformValue !== material._texturePaths[uniformId]
) {
material._loadedImages.push({
id: uniformId,
image: uniformValue,
});
material._texturePaths[uniformId] = uniformValue;
return;
}
// If we get to this point, the image should be a string URL or Resource.
// Don't wait on the promise to resolve, just start loading the image and poll status from the update loop.
loadTexture2DImageForUniform(material, uniformId);
};
}
/**
* For a given uniform ID, potentially loads a texture image for the material, if the uniform value is a Resource or string URL,
* and has changed since the last time this was called (either on construction or update).
*
* @param {Material} material The material to load the texture for.
* @param {string} uniformId The ID of the uniform of the image.
* @returns A promise that resolves when the image is loaded, or a resolved promise if image loading is not necessary.
*
* @private
*/
function loadTexture2DImageForUniform(material, uniformId) {
const uniforms = material.uniforms;
const uniformValue = uniforms[uniformId];
if (uniformValue === Material.DefaultImageId) {
return Promise.resolve();
}
// Attempt to make a resource from the uniform value. If it's not already a resource or string, this returns the original object.
const resource = Resource.createIfNeeded(uniformValue);
if (!(resource instanceof Resource)) {
return Promise.resolve();
}
// When using the entity layer, the Resource objects get recreated on getValue because
// they are clonable. That's why we check the url property for Resources
// because the instances aren't the same and we keep trying to load the same
// image if it fails to load.
const oldResource = Resource.createIfNeeded(
material._texturePaths[uniformId],
);
const uniformHasChanged =
!defined(oldResource) || oldResource.url !== resource.url;
if (!uniformHasChanged) {
return Promise.resolve();
}
let promise;
if (ktx2Regex.test(resource.url)) {
promise = loadKTX2(resource.url);
} else {
promise = resource.fetchImage();
}
Promise.resolve(promise)
.then(function (image) {
material._loadedImages.push({
id: uniformId,
image: image,
});
})
.catch(function (error) {
material._initializationError = error;
const texture = material._textures[uniformId];
if (defined(texture) && texture !== material._defaultTexture) {
texture.destroy();
}
material._textures[uniformId] = material._defaultTexture;
});
material._texturePaths[uniformId] = uniformValue;
return promise;
}
function createCubeMapUpdateFunction(uniformId) {
return function (material, context) {
const uniformValue = material.uniforms[uniformId];
@ -944,44 +1084,66 @@ function createCubeMapUpdateFunction(uniformId) {
}
if (!defined(material._textures[uniformId])) {
material._texturePaths[uniformId] = undefined;
material._textures[uniformId] = context.defaultCubeMap;
}
if (uniformValue === Material.DefaultCubeMapId) {
return;
}
const path =
uniformValue.positiveX +
uniformValue.negativeX +
uniformValue.positiveY +
uniformValue.negativeY +
uniformValue.positiveZ +
uniformValue.negativeZ;
if (path !== material._texturePaths[uniformId]) {
const promises = [
Resource.createIfNeeded(uniformValue.positiveX).fetchImage(),
Resource.createIfNeeded(uniformValue.negativeX).fetchImage(),
Resource.createIfNeeded(uniformValue.positiveY).fetchImage(),
Resource.createIfNeeded(uniformValue.negativeY).fetchImage(),
Resource.createIfNeeded(uniformValue.positiveZ).fetchImage(),
Resource.createIfNeeded(uniformValue.negativeZ).fetchImage(),
];
Promise.all(promises).then(function (images) {
material._loadedCubeMaps.push({
id: uniformId,
images: images,
});
});
material._texturePaths[uniformId] = path;
}
loadCubeMapImagesForUniform(material, uniformId);
};
}
/**
* Loads the images for a cubemap uniform, if it has changed since the last time this was called.
*
* @param {Material} material The material to load the cubemap images for.
* @param {string} uniformId The ID of the uniform that corresponds to the cubemap images.
* @returns A promise that resolves when the images are loaded, or a resolved promise if image loading is not necessary.
*/
function loadCubeMapImagesForUniform(material, uniformId) {
const uniforms = material.uniforms;
const uniformValue = uniforms[uniformId];
if (uniformValue === Material.DefaultCubeMapId) {
return Promise.resolve();
}
const path =
uniformValue.positiveX +
uniformValue.negativeX +
uniformValue.positiveY +
uniformValue.negativeY +
uniformValue.positiveZ +
uniformValue.negativeZ;
// The uniform value is unchanged, no update / image load necessary.
if (path === material._texturePaths[uniformId]) {
return Promise.resolve();
}
const promises = [
Resource.createIfNeeded(uniformValue.positiveX).fetchImage(),
Resource.createIfNeeded(uniformValue.negativeX).fetchImage(),
Resource.createIfNeeded(uniformValue.positiveY).fetchImage(),
Resource.createIfNeeded(uniformValue.negativeY).fetchImage(),
Resource.createIfNeeded(uniformValue.positiveZ).fetchImage(),
Resource.createIfNeeded(uniformValue.negativeZ).fetchImage(),
];
const allPromise = Promise.all(promises);
allPromise
.then(function (images) {
material._loadedCubeMaps.push({
id: uniformId,
images: images,
});
})
.catch(function (error) {
material._initializationError = error;
});
material._texturePaths[uniformId] = path;
return allPromise;
}
function createUniforms(material) {
const uniforms = material._template.uniforms;
for (const uniformId in uniforms) {
@ -1059,11 +1221,17 @@ function createUniform(material, uniformId) {
return material._textures[uniformId];
};
material._updateFunctions.push(createTexture2DUpdateFunction(uniformId));
material._initializationPromises.push(
loadTexture2DImageForUniform(material, uniformId),
);
} else if (uniformType === "samplerCube") {
material._uniforms[newUniformId] = function () {
return material._textures[uniformId];
};
material._updateFunctions.push(createCubeMapUpdateFunction(uniformId));
material._initializationPromises.push(
loadCubeMapImagesForUniform(material, uniformId),
);
} else if (uniformType.indexOf("mat") !== -1) {
const scratchMatrix = new matrixMap[uniformType]();
material._uniforms[newUniformId] = function () {

View File

@ -14,6 +14,7 @@ import {
Primitive,
TextureMagnificationFilter,
TextureMinificationFilter,
DeveloperError,
} from "../../index.js";
import createScene from "../../../../Specs/createScene.js";
@ -24,7 +25,6 @@ describe(
function () {
let scene;
const rectangle = Rectangle.fromDegrees(-10.0, -10.0, 10.0, 10.0);
let polygon;
const backgroundColor = [0, 0, 128, 255];
let polylines;
@ -40,7 +40,9 @@ describe(
scene.backgroundColor,
);
scene.primitives.destroyPrimitives = false;
scene.camera.setView({ destination: rectangle });
scene.camera.setView({
destination: Rectangle.fromDegrees(-10.0, -10.0, 10.0, 10.0),
});
});
afterAll(function () {
@ -54,7 +56,7 @@ describe(
geometryInstances: new GeometryInstance({
geometry: new RectangleGeometry({
vertexFormat: vertexFormat,
rectangle: rectangle,
rectangle: Rectangle.fromDegrees(-10.0, -10.0, 10.0, 10.0),
}),
}),
asynchronous: false,
@ -82,6 +84,19 @@ describe(
polylines = polylines && polylines.destroy();
});
function itRenders(initialColor = backgroundColor) {
it("renders", function () {
expect(scene).toRender(initialColor);
scene.primitives.removeAll();
scene.primitives.add(polygon);
expect(scene).toRenderAndCall(function (rgba) {
expect(rgba).not.toEqual(backgroundColor);
});
});
}
function renderMaterial(material, ignoreBackground, callback) {
ignoreBackground = ignoreBackground ?? false;
polygon.appearance.material = material;
@ -100,116 +115,63 @@ describe(
});
}
function renderPolylineMaterial(material) {
polyline.material = material;
expect(scene).toRender(backgroundColor);
scene.primitives.removeAll();
scene.primitives.add(polylines);
let result;
expect(scene).toRenderAndCall(function (rgba) {
result = rgba;
expect(rgba).not.toEqual(backgroundColor);
});
return result;
}
function verifyMaterial(type) {
const material = new Material({
strict: true,
fabric: {
type: type,
},
describe(`${type} built-in material`, function () {
beforeEach(function () {
const material = new Material({
strict: true,
fabric: {
type: type,
},
});
polygon.appearance.material = material;
});
itRenders();
});
renderMaterial(material);
}
function verifyPolylineMaterial(type) {
const material = new Material({
strict: true,
fabric: {
type: type,
},
describe(`${type} built-in material`, function () {
it("renders", function () {
const material = new Material({
strict: true,
fabric: {
type: type,
},
});
polyline.material = material;
expect(scene).toRender(backgroundColor);
scene.primitives.removeAll();
scene.primitives.add(polylines);
expect(scene).notToRender(backgroundColor);
});
});
renderPolylineMaterial(material);
}
it("draws Color built-in material", function () {
verifyMaterial("Color");
});
verifyMaterial("Color");
verifyMaterial("Image");
verifyMaterial("DiffuseMap");
verifyMaterial("AlphaMap");
verifyMaterial("SpecularMap");
verifyMaterial("EmissionMap");
verifyMaterial("BumpMap");
verifyMaterial("NormalMap");
verifyMaterial("Grid");
verifyMaterial("Stripe");
verifyMaterial("Checkerboard");
verifyMaterial("Dot");
verifyMaterial("Water");
verifyMaterial("RimLighting");
verifyMaterial("Fade");
it("draws Image built-in material", function () {
verifyMaterial("Image");
});
it("draws DiffuseMap built-in material", function () {
verifyMaterial("DiffuseMap");
});
it("draws AlphaMap built-in material", function () {
verifyMaterial("AlphaMap");
});
it("draws SpecularMap built-in material", function () {
verifyMaterial("SpecularMap");
});
it("draws EmissionMap built-in material", function () {
verifyMaterial("EmissionMap");
});
it("draws BumpMap built-in material", function () {
verifyMaterial("BumpMap");
});
it("draws NormalMap built-in material", function () {
verifyMaterial("NormalMap");
});
it("draws Grid built-in material", function () {
verifyMaterial("Grid");
});
it("draws Stripe built-in material", function () {
verifyMaterial("Stripe");
});
it("draws Checkerboard built-in material", function () {
verifyMaterial("Checkerboard");
});
it("draws Dot built-in material", function () {
verifyMaterial("Dot");
});
it("draws Water built-in material", function () {
verifyMaterial("Water");
});
it("draws RimLighting built-in material", function () {
verifyMaterial("RimLighting");
});
it("draws Fade built-in material", function () {
verifyMaterial("Fade");
});
it("draws PolylineArrow built-in material", function () {
verifyPolylineMaterial("PolylineArrow");
});
it("draws PolylineDash built-in material", function () {
verifyPolylineMaterial("PolylineDash");
});
it("draws PolylineGlow built-in material", function () {
verifyPolylineMaterial("PolylineGlow");
});
it("draws PolylineOutline built-in material", function () {
verifyPolylineMaterial("PolylineOutline");
});
verifyPolylineMaterial("PolylineArrow");
verifyPolylineMaterial("PolylineDash");
verifyPolylineMaterial("PolylineGlow");
verifyPolylineMaterial("PolylineOutline");
it("gets the material type", function () {
const material = new Material({
@ -630,55 +592,117 @@ describe(
});
});
it("creates material with custom texture filter", function () {
const materialLinear = new Material({
it("creates a material using fromTypeAsync", async function () {
const material = await Material.fromTypeAsync("Color");
renderMaterial(material);
});
it("loads a 2D texture image synchronously when awaiting fromTypeAsync", async function () {
const imageMaterial = await Material.fromTypeAsync("Image", {
image: "./Data/Images/Blue.png",
});
renderMaterial(imageMaterial, false, function (rgba) {
expect(rgba).toEqual([0, 0, 255, 255]);
});
});
it("loads cubemap images synchronously when awaiting fromTypeAsync", async function () {
// First make a material with a cubemap, then use its type to make a second cubemap material asynchronously.
const material = new Material({
strict: true,
fabric: {
type: "DiffuseMap",
uniforms: {
image: "./Data/Images/BlueOverRed.png",
cubeMap: {
positiveX: "./Data/Images/Blue.png",
negativeX: "./Data/Images/Blue.png",
positiveY: "./Data/Images/Blue.png",
negativeY: "./Data/Images/Blue.png",
positiveZ: "./Data/Images/Blue.png",
negativeZ: "./Data/Images/Blue.png",
},
},
source:
"uniform samplerCube cubeMap;\n" +
"czm_material czm_getMaterial(czm_materialInput materialInput)\n" +
"{\n" +
" czm_material material = czm_getDefaultMaterial(materialInput);\n" +
" material.diffuse = czm_textureCube(cubeMap, vec3(1.0)).xyz;\n" +
" return material;\n" +
"}\n",
},
minificationFilter: TextureMinificationFilter.LINEAR,
magnificationFilter: TextureMagnificationFilter.LINEAR,
});
const materialNearest = new Material({
fabric: {
type: "DiffuseMap",
uniforms: {
image: "./Data/Images/BlueOverRed.png",
const materialFromTypeAsync = await Material.fromTypeAsync(
material.type,
{
cubeMap: {
positiveX: "./Data/Images/Green.png",
negativeX: "./Data/Images/Green.png",
positiveY: "./Data/Images/Green.png",
negativeY: "./Data/Images/Green.png",
positiveZ: "./Data/Images/Green.png",
negativeZ: "./Data/Images/Green.png",
},
},
);
renderMaterial(materialFromTypeAsync);
});
it("loads sub-materials synchronously when awaiting fromTypeAsync", async function () {
// First make a material with submaterials, then use its type to make a second material asynchronously.
const material = new Material({
strict: true,
fabric: {
materials: {
greenMaterial: {
type: "Image",
uniforms: {
image: "./Data/Images/Green.png", // Green image
},
},
blueMaterial: {
type: "Image",
uniforms: {
image: "./Data/Images/Blue.png", // Blue image
},
},
},
components: {
diffuse:
"clamp(greenMaterial.diffuse + blueMaterial.diffuse, 0.0, 1.0)",
},
},
minificationFilter: TextureMinificationFilter.NEAREST,
magnificationFilter: TextureMagnificationFilter.NEAREST,
});
const materialFromTypeAsync = await Material.fromTypeAsync(material.type);
renderMaterial(materialFromTypeAsync, false, function (rgba) {
expect(rgba).toEqual([0, 255, 255, 255]); // Expect cyan from green + blue
});
});
it("creates material with custom texture filter", async function () {
const materialLinear = await Material.fromTypeAsync("DiffuseMap", {
image: "./Data/Images/BlueOverRed.png",
});
materialLinear.minificationFilter = TextureMinificationFilter.LINEAR;
materialLinear.magnificationFilter = TextureMagnificationFilter.LINEAR;
const materialNearest = await Material.fromTypeAsync("DiffuseMap", {
image: "./Data/Images/BlueOverRed.png",
});
materialNearest.minificationFilter = TextureMinificationFilter.NEAREST;
materialNearest.magnificationFilter = TextureMagnificationFilter.NEAREST;
const purple = [127, 0, 127, 255];
const ignoreBackground = true;
renderMaterial(materialLinear, ignoreBackground); // Populate the scene with the primitive prior to updating
return pollToPromise(function () {
const imageLoaded = materialLinear._loadedImages.length !== 0;
scene.renderForSpecs();
return imageLoaded;
})
.then(function () {
renderMaterial(materialLinear, ignoreBackground, function (rgba) {
expect(rgba).toEqualEpsilon(purple, 1);
});
})
.then(function () {
renderMaterial(materialNearest, ignoreBackground); // Populate the scene with the primitive prior to updating
return pollToPromise(function () {
const imageLoaded = materialNearest._loadedImages.length !== 0;
scene.renderForSpecs();
return imageLoaded;
}).then(function () {
renderMaterial(materialNearest, ignoreBackground, function (rgba) {
expect(rgba).not.toEqualEpsilon(purple, 1);
});
});
});
renderMaterial(materialLinear, ignoreBackground, function (rgba) {
expect(rgba).toEqualEpsilon(purple, 1);
});
renderMaterial(materialNearest, ignoreBackground, function (rgba) {
expect(rgba).not.toEqualEpsilon(purple, 1);
});
});
it("handles when material image is undefined", function () {
@ -1085,6 +1109,18 @@ describe(
renderMaterial(material);
material.destroy();
});
it("throws when loaded async and image loading fails", async function () {
spyOn(Resource.prototype, "fetchImage").and.callFake(function () {
return Promise.reject(new DeveloperError("Image loading failed"));
});
await expectAsync(
Material.fromTypeAsync("DiffuseMap", {
image: "i_dont_exist.png",
}),
).toBeRejectedWithDeveloperError("Image loading failed");
});
},
"WebGL",
);

View File

@ -1,5 +1,5 @@
legacyId: Mars.html
title: Mars Terrain
title: Mars
description: Mars terrain visualized using 3D Tiles, tiled and hosted by Cesium ion, with atmospheric effects, points of interest, and rover paths of NASA's Curiosity and Perseverance. Combine a 3D dataset with CZML/GeoJSON to create an interactive Mars exploration app.
labels:
- Showcases

View File

@ -1,5 +1,5 @@
legacyId: Moon.html
title: Moon Terrain
title: Moon
description: High-resolution lunar terrain using 3D Tiles from Cesium ion, with Apollo landing sites, craters, mare boundaries, and potential Artemis landing regions. Combining 3D data with GeoJSON overlays and interactive camera flights to create an immersive Moon app.
labels:
- Showcases