Compare commits

...

10 Commits

Author SHA1 Message Date
Adam Beili 45b416bcd6
Merge 275219a777 into 673bd6a82f 2025-11-25 19:27:23 -05:00
Marco Hutter 673bd6a82f
Merge pull request #13026 from CesiumGS/fix-sandcastle-paths
deploy / deploy (push) Waiting to run Details
dev / lint (push) Waiting to run Details
dev / coverage (push) Waiting to run Details
dev / release-tests (push) Waiting to run Details
dev / node-20 (push) Waiting to run Details
sandcastle-dev / deploy (push) Waiting to run Details
Fix Sandcastle paths for windows
2025-11-26 00:10:13 +00:00
Don McCurdy 41a0f18ba9
Merge pull request #13018 from CesiumGS/billboard-depth-regression-fix
Fixes model-billboard depth interactions
2025-11-25 18:03:41 +00:00
Don McCurdy 8bd8ae7f63
Merge branch 'main' into billboard-depth-regression-fix 2025-11-25 13:02:57 -05:00
jjspace 0b11c10039
Merge remote-tracking branch 'origin/main' into fix-sandcastle-paths 2025-11-25 12:54:17 -05:00
jjspace 2171604b40
use node executable for windows 2025-11-25 12:50:55 -05:00
jjspace 841a4cf12a
better tsc path and fix globby paths for windows 2025-11-07 17:20:46 -05:00
Matt Schwartz 020749de32 Add to CHANGES.md 2025-11-05 09:27:19 -05:00
Matt Schwartz f4e49d7b64 Fixes model-billboard depth interactions 2025-11-05 09:12:11 -05:00
Adam Beili 275219a777 Add ScreenSpaceCameraController.collisionHeightReference 2025-10-10 23:33:30 +03:00
8 changed files with 88 additions and 38 deletions

View File

@ -1,11 +1,12 @@
# Change Log
## 1.136
## 1.136 - 2025-12-01
### @cesium/engine
#### Fixes :wrench:
- Fixed depth testing bug with billboards and labels clipping through models [#13012](https://github.com/CesiumGS/cesium/issues/13012)
- Billboards using `imageSubRegion` now render as expected. [#12585](https://github.com/CesiumGS/cesium/issues/12585)
- Improved scaling of SVGs in billboards [#13020](https://github.com/CesiumGS/cesium/issues/13020)
- Fixed unexpected outline artifacts around billboards [#13038](https://github.com/CesiumGS/cesium/issues/13038)

View File

@ -34,6 +34,7 @@ import SDFSettings from "./SDFSettings.js";
import TextureAtlas from "../Renderer/TextureAtlas.js";
import VerticalOrigin from "./VerticalOrigin.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import WebGLConstants from "../Core/WebGLConstants.js";
const SHOW_INDEX = Billboard.SHOW_INDEX;
const POSITION_INDEX = Billboard.POSITION_INDEX;
@ -2039,7 +2040,8 @@ BillboardCollection.prototype.update = function (frameState) {
) {
this._rsOpaque = RenderState.fromCache({
depthTest: {
enabled: false,
enabled: true,
func: WebGLConstants.LESS,
},
depthMask: true,
});
@ -2059,7 +2061,10 @@ BillboardCollection.prototype.update = function (frameState) {
) {
this._rsTranslucent = RenderState.fromCache({
depthTest: {
enabled: false,
enabled: true,
func: useTranslucentDepthMask
? WebGLConstants.LEQUAL
: WebGLConstants.LESS,
},
depthMask: useTranslucentDepthMask,
blending: BlendingState.ALPHA_BLEND,

View File

@ -3943,12 +3943,26 @@ function callAfterRenderFunctions(scene) {
}
}
/**
* Calculate the height of the globe at the camera position based on the value of {@link ScreenSpaceCameraController.collisionHeightReference},
* or undefined if the height cannot be determined.
*
* @param {Scene} scene
* @returns {number|undefined}
*/
function getGlobeHeight(scene) {
if (scene.mode === SceneMode.MORPHING) {
if (
scene.mode === SceneMode.MORPHING ||
scene._screenSpaceCameraController.collisionHeightReference ===
HeightReference.NONE
) {
return;
}
const cartographic = scene.camera.positionCartographic;
return scene.getHeight(cartographic);
return scene.getHeight(
cartographic,
scene._screenSpaceCameraController.collisionHeightReference,
);
}
function getMaxPrimitiveHeight(primitive, cartographic, scene) {

View File

@ -24,6 +24,7 @@ import MapMode2D from "./MapMode2D.js";
import SceneMode from "./SceneMode.js";
import SceneTransforms from "./SceneTransforms.js";
import TweenCollection from "./TweenCollection.js";
import HeightReference from "./HeightReference.js";
/**
* Modifies the camera position and orientation based on mouse input to a canvas.
@ -265,13 +266,13 @@ function ScreenSpaceCameraController(scene) {
: ellipsoid.minimumRadius * 1.175;
this._minimumTrackBallHeight = this.minimumTrackBallHeight;
/**
* When disabled, the values of <code>maximumZoomDistance</code> and <code>minimumZoomDistance</code> are ignored.
* Also used in conjunction with {@link Cesium3DTileset#enableCollision} to prevent the camera from moving through or below a 3D Tileset surface.
* This may also affect clamping behavior when using {@link HeightReference.CLAMP_TO_GROUND} on 3D Tiles.
* @type {boolean}
* @default true
* This value controls the height reference for the {@link ScreenSpaceCameraController} collision, which is used to limit the camera minimum and maximum zoom.
* When set to {@link HeightReference.NONE}, the camera can go underground, and the values of <code>maximumZoomDistance</code> and <code>minimumZoomDistance</code> are ignored.
* For all other values, the camera is constrained to be above the terrain and/or 3D Tiles (depending on {@link Cesium3DTileset#enableCollision}).
* @type {HeightReference}
* @default HeightReference.CLAMP_TO_GROUND
*/
this.enableCollisionDetection = true;
this.collisionHeightReference = HeightReference.CLAMP_TO_GROUND;
/**
* The angle, relative to the ellipsoid normal, restricting the maximum amount that the user can tilt the camera. If <code>undefined</code>, the angle of the camera tilt is unrestricted.
* @type {number|undefined}
@ -353,6 +354,27 @@ function ScreenSpaceCameraController(scene) {
this._maximumUndergroundPickDistance = 10000.0;
}
Object.defineProperties(ScreenSpaceCameraController.prototype, {
/**
* When disabled, the values of <code>maximumZoomDistance</code> and <code>minimumZoomDistance</code> are ignored.
* Also used in conjunction with {@link Cesium3DTileset#enableCollision} to prevent the camera from moving through or below a 3D Tileset surface.
* This may also affect clamping behavior when using {@link HeightReference.CLAMP_TO_GROUND} on 3D Tiles.
* @type {boolean}
* @default true
* @deprecated Use {@link ScreenSpaceCameraController#collisionHeightReference} instead.
*/
enableCollisionDetection: {
get: function () {
return this.collisionHeightReference !== HeightReference.NONE;
},
set: function (value) {
this.collisionHeightReference = value
? HeightReference.CLAMP_TO_GROUND
: HeightReference.NONE;
},
},
});
function decay(time, coefficient) {
if (time < 0) {
return 0.0;

View File

@ -131,7 +131,9 @@ void doThreePointDepthTest(float eyeDepth, bool applyTranslate) {
}
#endif
void doDepthTest() {
// Extra manual depth testing is done to allow more control over how a billboard is occluded
// by the globe when near and far from the camera.
void doDepthTest(float globeDepth) {
float temp = v_compressed.y;
temp = temp * SHIFT_RIGHT1;
float temp2 = (temp - floor(temp)) * SHIFT_LEFT1;
@ -156,14 +158,10 @@ void doDepthTest() {
}
#endif
// Automatic depth testing of billboards is disabled (@see BillboardCollection#update).
// Instead, we do one of two types of manual depth tests (potentially in addition to the test above), depending on the camera's distance to the billboard fragment.
// If we're far away, we just compare against a flat, camera-facing depth-plane at the ellipsoid's center.
// If we're close, we compare against the globe depth texture (which includes depth from the 3D tile pass).
vec2 fragSt = gl_FragCoord.xy / czm_viewport.zw;
float globeDepth = getGlobeDepthAtCoords(fragSt);
if (globeDepth == 0.0) return; // Not on globe
if (globeDepth == 0.0) return; // Not on globe
float distanceToEllipsoidCenter = -length(czm_viewerPositionWC); // depth is negative by convention
float testDistance = (eyeDepth > -u_coarseDepthTestDistance) ? globeDepth : distanceToEllipsoidCenter;
if (eyeDepth < testDistance) {
@ -175,7 +173,10 @@ void main()
{
if (v_splitDirection < 0.0 && gl_FragCoord.x > czm_splitPosition) discard;
if (v_splitDirection > 0.0 && gl_FragCoord.x < czm_splitPosition) discard;
doDepthTest();
vec2 fragSt = gl_FragCoord.xy / czm_viewport.zw;
float globeDepth = getGlobeDepthAtCoords(fragSt);
doDepthTest(globeDepth);
vec4 color = texture(u_atlas, v_textureCoordinates);
@ -243,6 +244,18 @@ void main()
out_FragColor = color;
#ifdef LOG_DEPTH
czm_writeLogDepth();
// If we've made it here, we passed our manual depth test, above. But the automatic depth test will
// still run, and some fragments of the billboard may clip against the globe. To prevent that,
// ensure the depth value we write out is in front of the globe depth.
float depthArg = v_depthFromNearPlusOne;
if (globeDepth != 0.0) { // On the globe
float globeDepthFromNearPlusOne = (-globeDepth - czm_currentFrustum.x) + 1.0;
float nudge = max(globeDepthFromNearPlusOne * 5e-6, czm_epsilon7);
float globeOnTop = max(1.0, globeDepthFromNearPlusOne - nudge);
depthArg = min(depthArg, globeOnTop);
}
czm_writeLogDepth(depthArg);
#endif
}

View File

@ -118,8 +118,17 @@ export async function buildGalleryList(options = {}) {
};
const galleryFiles = await globby(
galleryFilesPattern.map((pattern) => join(rootDirectory, pattern, "**/*")),
galleryFilesPattern.map((pattern) =>
// globby can only work with paths using '/' but node on windows uses '\'
// convert them right before passing to globby to ensure all joins work as expected
join(rootDirectory, pattern, "**/*").replaceAll("\\", "/"),
),
);
if (galleryFiles.length === 0) {
console.warn(
"Did not find any gallery files. Please check the configuration is correct",
);
}
const yamlFiles = galleryFiles.filter((path) =>
basename(path).match(galleryItemConfig),
);

View File

@ -9,10 +9,10 @@ import { fileURLToPath } from "node:url";
* @returns {number} exit code from the tsc command
*/
export default async function typescriptCompile(configPath) {
const tsPath = import.meta.resolve("typescript");
const binPath = fileURLToPath(join(tsPath, "../../bin/tsc"));
const tsPath = fileURLToPath(import.meta.resolve("typescript"));
const binPath = join(tsPath, "../../bin/tsc");
return new Promise((resolve, reject) => {
const ls = spawn(binPath, ["-p", configPath]);
const ls = spawn(process.execPath, [binPath, "-p", configPath]);
ls.stdout.on("data", (data) => {
console.log(`stdout: ${data}`);

View File

@ -10,15 +10,6 @@ import { buildGalleryList } from "../packages/sandcastle/scripts/buildGallery.js
const __dirname = dirname(fileURLToPath(import.meta.url));
const projectRoot = join(__dirname, "..");
// async function importSandcastleBuildFunctions() {
// // Import asynchronously, for now, because this script is not included or run in the release zip;
// const buildGalleryScriptPath = join(
// __dirname,
// "../packages/sandcastle/index.js",
// );
// return await import(pathToFileURL(buildGalleryScriptPath).href);
// }
/**
* Parses Sandcastle config file and returns its values.
* @returns {Promise<Record<string,any>>} A promise that resolves to the config values.
@ -52,10 +43,7 @@ export async function buildSandcastleApp({
outputToBuildDir,
includeDevelopment,
}) {
// const { join, dirname } = path;
const __dirname = dirname(fileURLToPath(import.meta.url));
// const { createSandcastleConfig, buildStatic } =
// await importSandcastleBuildFunctions();
const version = await getVersion();
let config;
if (outputToBuildDir) {
@ -168,8 +156,6 @@ export async function buildSandcastleGallery({
metadata,
} = gallery ?? {};
// const { buildGalleryList } = await importSandcastleBuildFunctions();
await buildGalleryList({
rootDirectory,
publicDirectory: outputDir,