Merge pull request #12574 from CesiumGS/sandcastle-reborn

Sandcastle Reborn
This commit is contained in:
Gabby Getz 2025-05-09 16:39:28 -04:00 committed by GitHub
commit 0e3c6581ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
78 changed files with 3994 additions and 14 deletions

View File

@ -22,6 +22,8 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPO: ${{ github.repository }}
GITHUB_SHA: ${{ github.sha }}
BASE_URL: /cesium/${{ github.ref_name }}/
DEPLOYED_URL: https://ci-builds.cesium.com/cesium/${{ github.ref_name }}/
steps:
- uses: actions/checkout@v4
- name: install node 20
@ -40,6 +42,8 @@ jobs:
run: npm pack --workspaces &> /dev/null
- name: build apps
run: npm run build-apps
- name: build sandcastle v2
run: npm run build-ci -w packages/sandcastle -- -l warn
- uses: ./.github/actions/verify-package
- name: deploy to s3
if: ${{ env.AWS_ACCESS_KEY_ID != '' }}

3
.gitignore vendored
View File

@ -13,6 +13,7 @@ Thumbs.db
/Apps/Sandcastle/jsHintOptions.js
/Apps/Sandcastle/gallery/gallery-index.js
/Apps/Sandcastle/templates/bucket.css
/Apps/Sandcastle2
/Source/Assets/
/Source/**/*.d.ts
@ -43,4 +44,4 @@ yarn.lock
.idea/shelf
# Used in the CLA checking GitHub workflow
GoogleConfig.json
GoogleConfig.json

View File

@ -1,4 +1,5 @@
/node_modules
packages/sandcastle/node_modules
/ThirdParty
/Tools/**

View File

@ -19,6 +19,7 @@
!**/*.html
!**/*.md
!**/*.ts
!**/*.tsx
# Re-ignore a few things caught above
@ -34,6 +35,9 @@ packages/widgets/Build/**
packages/widgets/index.js
packages/widgets/Source/ThirdParty/**
packages/sandcastle/node_modules/**
Apps/Sandcastle2/**
Specs/jasmine/**
Apps/Sandcastle/ThirdParty

View File

@ -1,6 +1,9 @@
import globals from "globals";
import html from "eslint-plugin-html";
import configCesium from "@cesium/eslint-config";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";
export default [
{
@ -15,6 +18,8 @@ export default [
"Apps/HelloWorld.html",
"Apps/Sandcastle/jsHintOptions.js",
"Apps/Sandcastle/gallery/gallery-index.js",
"Apps/Sandcastle2/",
"packages/sandcastle/public/",
"packages/engine/Source/Scene/GltfPipeline/**/*",
"packages/engine/Source/Shaders/**/*",
"Specs/jasmine/*",
@ -74,6 +79,31 @@ export default [
sourceType: "module",
},
},
...[...tseslint.configs.recommended].map((config) => ({
// This is needed to restrict to a specific path unless using the tseslint.config function
// https://typescript-eslint.io/packages/typescript-eslint#config
...config,
files: ["packages/sandcastle/**/*.{ts,tsx}"],
})),
{
// This config came from the vite project generation
files: ["packages/sandcastle/**/*.{ts,tsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
},
},
{
files: ["Specs/**/*", "packages/**/Specs/**/*"],
languageOptions: {

View File

@ -43,8 +43,18 @@ if (/\.0$/.test(version)) {
version = version.substring(0, version.length - 2);
}
const karmaConfigFile = resolve("./Specs/karma.conf.cjs");
function getWorkspaces(onlyDependencies = false) {
const dependencies = Object.keys(packageJson.dependencies);
return onlyDependencies
? packageJson.workspaces.filter((workspace) => {
return dependencies.includes(
workspace.replace("packages", `@${scope}`),
);
})
: packageJson.workspaces;
}
const devDeployUrl = "https://ci-builds.cesium.com/cesium/";
const devDeployUrl = process.env.DEPLOYED_URL;
const isProduction = process.env.PROD;
//Gulp doesn't seem to have a way to get the currently running tasks for setting
@ -247,7 +257,7 @@ export async function buildTs() {
} else if (argv.workspace) {
workspaces = argv.workspace;
} else {
workspaces = packageJson.workspaces;
workspaces = getWorkspaces(true);
}
// Generate types for passed packages in order.
@ -396,7 +406,7 @@ export async function buildDocs() {
stdio: "inherit",
env: Object.assign({}, process.env, {
CESIUM_VERSION: version,
CESIUM_PACKAGES: packageJson.workspaces,
CESIUM_PACKAGES: getWorkspaces(true),
}),
},
);
@ -694,12 +704,10 @@ export async function deployStatus() {
const status = argv.status;
const message = argv.message;
const deployUrl = `${devDeployUrl + process.env.BRANCH}/`;
const deployUrl = `${devDeployUrl}`;
const zipUrl = `${deployUrl}Cesium-${version}.zip`;
const npmUrl = `${deployUrl}cesium-${version}.tgz`;
const coverageUrl = `${
devDeployUrl + process.env.BRANCH
}/Build/Coverage/index.html`;
const coverageUrl = `${devDeployUrl}Build/Coverage/index.html`;
return Promise.all([
setStatus(status, deployUrl, message, "deployment"),
@ -1484,8 +1492,8 @@ async function getLicenseDataFromThirdPartyExtra(path, discoveredDependencies) {
return result;
}
// Resursively check the workspaces
for (const workspace of packageJson.workspaces) {
// Recursively check the workspaces
for (const workspace of getWorkspaces(true)) {
const workspacePackageJson = require(`./${workspace}/package.json`);
result = await getLicenseDataFromPackage(
workspacePackageJson,

View File

@ -33,7 +33,12 @@
</li>
<li>
<a href="Apps/Sandcastle/index.html">Sandcastle</a>
(<a href="Build/Apps/Sandcastle/index.html">built version</a>)
<ul>
<li>
<a href="Build/Apps/Sandcastle/index.html">Built Sandcastle</a>
</li>
<li><a href="Apps/Sandcastle2/index.html">Sandcastle v2</a></li>
</ul>
</li>
<li>
<a href="Apps/CesiumViewer/index.html?inspector=true"

View File

@ -63,6 +63,8 @@
"esbuild": "^0.25.0",
"eslint": "^9.1.1",
"eslint-plugin-html": "^8.1.1",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"express": "^5.1.0",
"globals": "^16.0.0",
"globby": "^14.0.0",
@ -101,6 +103,7 @@
"rimraf": "^5.0.0",
"tsd-jsdoc": "^2.5.0",
"typescript": "^5.3.2",
"typescript-eslint": "^8.30.1",
"yargs": "^17.0.1"
},
"scripts": {
@ -113,12 +116,13 @@
"build-ts": "gulp buildTs",
"build-third-party": "gulp buildThirdParty",
"build-apps": "gulp buildApps",
"build-sandcastle": "npm run build-app --workspace packages/sandcastle",
"clean": "gulp clean",
"cloc": "gulp cloc",
"coverage": "gulp coverage",
"build-docs": "gulp buildDocs",
"build-docs-watch": "gulp buildDocsWatch",
"eslint": "eslint \"./**/*.*js\" \"./**/*.html\" --cache --quiet",
"eslint": "eslint \"./**/*.*js\" \"./**/*.*ts*\" \"./**/*.html\" --cache --quiet",
"make-zip": "gulp makeZip",
"markdownlint": "markdownlint \"**/*.md\"",
"release": "gulp release",
@ -146,7 +150,7 @@
"node": ">=18.18.0"
},
"lint-staged": {
"*.{js,cjs,mjs,css,html}": [
"*.{js,cjs,mjs,ts,tsx,css,html}": [
"eslint --cache --quiet",
"prettier --write"
],
@ -157,6 +161,7 @@
},
"workspaces": [
"packages/engine",
"packages/widgets"
"packages/widgets",
"packages/sandcastle"
]
}

14
packages/sandcastle/.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local

View File

@ -0,0 +1,60 @@
# CesiumJS Sandcastle
This package is the application for Sandcastle.
## Running/Building
- `npm run dev`: run the development server
- `npm run build`: alias for `npm run build-app`
- `npm run build-app`: build to static files in `/Apps/Sandcastle2` for hosting/access from the root cesium dev server
- `npm run build-ci`: build to static files in `/Apps/Sandcastle2` and configure paths as needed for CI deployment
Linting and style is managed under the project root's scripts.
## Expanding the ESLint configuration
<!-- TODO: this section was auto-generated, should figure out if we want these suggestions then remove this -->
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
```js
export default tseslint.config({
extends: [
// Remove ...tseslint.configs.recommended and replace with this
...tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
...tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
...tseslint.configs.stylisticTypeChecked,
],
languageOptions: {
// other options...
parserOptions: {
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
tsconfigRootDir: import.meta.dirname,
},
},
});
```
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
```js
// eslint.config.js
import reactX from "eslint-plugin-react-x";
import reactDom from "eslint-plugin-react-dom";
export default tseslint.config({
plugins: {
// Add the react-x and react-dom plugins
"react-x": reactX,
"react-dom": reactDom,
},
rules: {
// other rules...
// Enable its recommended typescript rules
...reactX.configs["recommended-typescript"].rules,
...reactDom.configs.recommended.rules,
},
});
```

View File

@ -0,0 +1,31 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sandcastle Reborn</title>
<style>
/* Load fonts for itwin-ui */
@font-face {
font-family: InterVariable;
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url("/fonts/InterVariable.woff2") format("woff2");
}
@font-face {
font-family: InterVariable;
font-style: italic;
font-weight: 100 900;
font-display: swap;
src: url("/fonts/InterVariable-Italic.woff2") format("woff2");
}
</style>
</head>
<body>
<div id="app-container"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,30 @@
{
"name": "@cesium/sandcastle",
"private": true,
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite --config vite.config.dev.ts",
"build": "npm run build-app",
"build-app": "tsc -b && vite build --config vite.config.app.ts",
"build-ci": "tsc -b && vite build --config vite.config.ci.ts"
},
"dependencies": {
"@itwin/itwinui-react": "^5.0.0-alpha.14",
"@monaco-editor/react": "^4.7.0",
"monaco-editor": "^0.52.2",
"pako": "^2.1.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@types/pako": "^2.0.3",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"globals": "^15.15.0",
"typescript": "~5.7.2",
"vite": "^6.2.0",
"vite-plugin-static-copy": "^2.3.1"
}
}

View File

@ -0,0 +1,203 @@
(function () {
"use strict";
window.parent.postMessage("reload", "*");
function defined(value) {
return value !== undefined;
}
function print(value) {
if (value === null) {
return "null";
} else if (defined(value)) {
return value.toString();
}
return "undefined";
}
console.originalLog = console.log;
console.log = function (d1) {
console.originalLog.apply(console, arguments);
window.parent.postMessage(
{
log: print(d1),
},
"*",
);
};
console.originalWarn = console.warn;
console.warn = function (d1) {
console.originalWarn.apply(console, arguments);
window.parent.postMessage(
{
warn: defined(d1) ? d1.toString() : "undefined",
},
"*",
);
};
console.originalError = console.error;
console.error = function (d1) {
console.originalError.apply(console, arguments);
if (!defined(d1)) {
window.parent.postMessage(
{
error: "undefined",
},
"*",
);
return;
}
// Look for d1.stack, "bucket.html:line:char"
let lineNumber = -1;
const errorMsg = d1.toString();
if (typeof d1.stack === "string") {
const stack = d1.stack;
let pos = stack.indexOf(Sandcastle.bucket);
if (pos < 0) {
pos = stack.indexOf("<anonymous>");
}
if (pos >= 0) {
const lineStart = stack.indexOf(":", pos);
if (lineStart > pos) {
let lineEnd1 = stack.indexOf(":", lineStart + 1);
const lineEnd2 = stack.indexOf("\n", lineStart + 1);
if (
lineEnd2 > lineStart &&
(lineEnd2 < lineEnd1 || lineEnd1 < lineStart)
) {
lineEnd1 = lineEnd2;
}
if (lineEnd1 > lineStart) {
/*eslint-disable no-empty*/
try {
lineNumber = parseInt(
stack.substring(lineStart + 1, lineEnd1),
10,
);
} catch (ex) {}
/*eslint-enable no-empty*/
}
}
}
}
if (lineNumber >= 0) {
window.parent.postMessage(
{
error: errorMsg,
lineNumber: lineNumber,
},
"*",
);
} else {
window.parent.postMessage(
{
error: errorMsg,
},
"*",
);
}
};
window.onerror = function (errorMsg, url, lineNumber) {
if (defined(lineNumber)) {
if (defined(url) && url.indexOf(Sandcastle.bucket) > -1) {
// if the URL is the bucket itself, ignore it
url = "";
}
if (lineNumber < 1) {
// Change lineNumber to the local one for highlighting.
/*eslint-disable no-empty*/
try {
let pos = errorMsg.indexOf(`${Sandcastle.bucket}:`);
if (pos < 0) {
pos = errorMsg.indexOf("<anonymous>");
}
if (pos >= 0) {
pos += 12;
lineNumber = parseInt(errorMsg.substring(pos), 10);
}
} catch (ex) {}
/*eslint-enable no-empty*/
}
window.parent.postMessage(
{
error: errorMsg,
url: url,
lineNumber: lineNumber,
},
"*",
);
} else {
window.parent.postMessage(
{
error: errorMsg,
url: url,
},
"*",
);
}
console.originalError.apply(console, [errorMsg]);
return false;
};
Sandcastle.declare = function (obj) {
/*eslint-disable no-empty*/
try {
//Browsers such as IE don't have a stack property until you actually throw the error.
let stack = "";
try {
throw new Error();
} catch (ex) {
stack = ex.stack.toString();
}
let needle = `${Sandcastle.bucket}:`; // Firefox
let pos = stack.indexOf(needle);
if (pos < 0) {
needle = " (<anonymous>:"; // Chrome
pos = stack.indexOf(needle);
}
if (pos < 0) {
needle = " (Unknown script code:"; // IE 11
pos = stack.indexOf(needle);
}
if (pos >= 0) {
pos += needle.length;
const lineNumber = parseInt(stack.substring(pos), 10);
Sandcastle.registered.push({
obj: obj,
lineNumber: lineNumber,
});
}
} catch (ex) {}
/*eslint-enable no-empty*/
};
Sandcastle.highlight = function (obj) {
if (typeof obj !== "undefined") {
for (let i = 0, len = Sandcastle.registered.length; i < len; ++i) {
if (
obj === Sandcastle.registered[i].obj ||
obj.primitive === Sandcastle.registered[i].obj
) {
window.parent.postMessage(
{
highlight: Sandcastle.registered[i].lineNumber,
},
"*",
);
return;
}
}
}
window.parent.postMessage(
{
highlight: 0,
},
"*",
);
};
})();

View File

@ -0,0 +1,111 @@
(function () {
"use strict";
let defaultAction;
let bucket = window.location.href;
const pos = bucket.lastIndexOf("/");
if (pos > 0 && pos < bucket.length - 1) {
bucket = bucket.substring(pos + 1);
}
window.Sandcastle = {
bucket: bucket,
declare: function () {},
highlight: function () {},
registered: [],
finishedLoading: function () {
window.Sandcastle.reset();
if (defaultAction) {
window.Sandcastle.highlight(defaultAction);
defaultAction();
defaultAction = undefined;
}
document.body.className = document.body.className.replace(
/(?:\s|^)sandcastle-loading(?:\s|$)/,
" ",
);
},
addToggleButton: function (text, checked, onchange, toolbarID) {
window.Sandcastle.declare(onchange);
const input = document.createElement("input");
input.checked = checked;
input.type = "checkbox";
input.style.pointerEvents = "none";
const label = document.createElement("label");
label.appendChild(input);
label.appendChild(document.createTextNode(text));
label.style.pointerEvents = "none";
const button = document.createElement("button");
button.type = "button";
button.className = "cesium-button";
button.appendChild(label);
button.onclick = function () {
window.Sandcastle.reset();
window.Sandcastle.highlight(onchange);
input.checked = !input.checked;
onchange(input.checked);
};
document.getElementById(toolbarID || "toolbar").appendChild(button);
},
addToolbarButton: function (text, onclick, toolbarID) {
window.Sandcastle.declare(onclick);
const button = document.createElement("button");
button.type = "button";
button.className = "cesium-button";
button.onclick = function () {
window.Sandcastle.reset();
window.Sandcastle.highlight(onclick);
onclick();
};
button.textContent = text;
document.getElementById(toolbarID || "toolbar").appendChild(button);
},
addDefaultToolbarButton: function (text, onclick, toolbarID) {
window.Sandcastle.addToolbarButton(text, onclick, toolbarID);
defaultAction = onclick;
},
addDefaultToolbarMenu: function (options, toolbarID) {
window.Sandcastle.addToolbarMenu(options, toolbarID);
defaultAction = options[0].onselect;
},
addToolbarMenu: function (options, toolbarID) {
const menu = document.createElement("select");
menu.className = "cesium-button";
menu.onchange = function () {
window.Sandcastle.reset();
const item = options[menu.selectedIndex];
if (item && typeof item.onselect === "function") {
item.onselect();
}
};
document.getElementById(toolbarID || "toolbar").appendChild(menu);
if (!defaultAction && typeof options[0].onselect === "function") {
defaultAction = options[0].onselect;
}
for (let i = 0, len = options.length; i < len; ++i) {
const option = document.createElement("option");
option.textContent = options[i].text;
option.value = options[i].value;
menu.appendChild(option);
}
},
reset: function () {},
};
if (window.location.protocol === "file:") {
if (
window.confirm(
"You must host this app on a web server.\nSee contributor's guide for more info?",
)
) {
window.location =
"https://github.com/CesiumGS/cesium/blob/main/Documentation/Contributors/BuildGuide/README.md#quickstart";
}
}
})();

View File

@ -0,0 +1,46 @@
(function () {
"use strict";
window.embedInSandcastleTemplate = function (code, addExtraLine) {
return (
`${
"window.startup = async function (Cesium) {\n" +
" 'use strict';\n" +
"//Sandcastle_Begin\n"
}${addExtraLine ? "\n" : ""}${code}//Sandcastle_End\n` +
` Sandcastle.finishedLoading();\n` +
`};\n` +
`if (typeof Cesium !== 'undefined') {\n` +
` window.startupCalled = true;\n` +
` window.startup(Cesium).catch((error) => {\n` +
` "use strict";\n` +
` console.error(error);\n` +
` });\n` +
`}\n`
);
};
window.decodeBase64Data = function (base64String, pako) {
// data stored in the hash as:
// Base64 encoded, raw DEFLATE compressed JSON array where index 0 is code, index 1 is html
// restore padding
while (base64String.length % 4 !== 0) {
base64String += "=";
}
let jsonString = pako.inflate(atob(base64String), {
raw: true,
to: "string",
});
// we save a few bytes by omitting the leading [" and trailing "] since they are always the same
jsonString = `["${jsonString}"]`;
const json = JSON.parse(jsonString);
// index 0 is code, index 1 is html
const code = json[0];
const html = json[1];
const baseHref = json[2];
return {
code: code,
html: html,
baseHref: baseHref,
};
};
})();

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

View File

@ -0,0 +1,183 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
/>
<meta name="description" content="Create 3D models using glTF." />
<meta name="cesium-sandcastle-labels" content="Tutorials,Showcases" />
<title>Cesium Demo</title>
<script type="text/javascript" src="../Sandcastle-header.js"></script>
<script
type="text/javascript"
src="../../../Build/CesiumUnminified/Cesium.js"
nomodule
></script>
<script type="module" src="../load-cesium-es6.js"></script>
</head>
<body
class="sandcastle-loading"
data-sandcastle-bucket="bucket-requirejs.html"
>
<style>
@import url(../templates/bucket.css);
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<div id="toolbar"></div>
<script id="cesium_sandcastle_script">
window.startup = async function (Cesium) {
"use strict";
//Sandcastle_Begin
const viewer = new Cesium.Viewer("cesiumContainer", {
infoBox: false,
selectionIndicator: false,
shadows: true,
shouldAnimate: true,
});
function createModel(url, height) {
viewer.entities.removeAll();
const position = Cesium.Cartesian3.fromDegrees(
-123.0744619,
44.0503706,
height,
);
const heading = Cesium.Math.toRadians(135);
const pitch = 0;
const roll = 0;
const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
const orientation = Cesium.Transforms.headingPitchRollQuaternion(
position,
hpr,
);
const entity = viewer.entities.add({
name: url,
position: position,
orientation: orientation,
model: {
uri: url,
minimumPixelSize: 128,
maximumScale: 20000,
},
});
viewer.trackedEntity = entity;
}
const options = [
{
text: "Aircraft",
onselect: function () {
createModel(
"../../SampleData/models/CesiumAir/Cesium_Air.glb",
5000.0,
);
},
},
{
text: "Drone",
onselect: function () {
createModel(
"../../SampleData/models/CesiumDrone/CesiumDrone.glb",
150.0,
);
},
},
{
text: "Ground Vehicle",
onselect: function () {
createModel(
"../../SampleData/models/GroundVehicle/GroundVehicle.glb",
0,
);
},
},
{
text: "Hot Air Balloon",
onselect: function () {
createModel(
"../../SampleData/models/CesiumBalloon/CesiumBalloon.glb",
1000.0,
);
},
},
{
text: "Milk Truck",
onselect: function () {
createModel(
"../../SampleData/models/CesiumMilkTruck/CesiumMilkTruck.glb",
0,
);
},
},
{
text: "Skinned Character",
onselect: function () {
createModel(
"../../SampleData/models/CesiumMan/Cesium_Man.glb",
0,
);
},
},
{
text: "Unlit Box",
onselect: function () {
createModel(
"../../SampleData/models/BoxUnlit/BoxUnlit.gltf",
10.0,
);
},
},
{
text: "Draco Compressed Model",
onselect: function () {
createModel(
"../../SampleData/models/DracoCompressed/CesiumMilkTruck.gltf",
0,
);
},
},
{
text: "KTX2 Compressed Balloon",
onselect: function () {
if (!Cesium.FeatureDetection.supportsBasis(viewer.scene)) {
window.alert(
"This browser does not support Basis Universal compressed textures",
);
}
createModel(
"../../SampleData/models/CesiumBalloonKTX2/CesiumBalloonKTX2.glb",
1000.0,
);
},
},
{
text: "Instanced Box",
onselect: function () {
createModel(
"../../SampleData/models/BoxInstanced/BoxInstanced.gltf",
15,
);
},
},
];
Sandcastle.addToolbarMenu(options);
//Sandcastle_End
};
if (typeof Cesium !== "undefined") {
window.startupCalled = true;
window.startup(Cesium).catch((error) => {
"use strict";
console.error(error);
});
Sandcastle.finishedLoading();
}
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,381 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
/>
<meta
name="description"
content="Add billboard images and markers to the scene."
/>
<meta name="cesium-sandcastle-labels" content="Beginner, Showcases" />
<title>Cesium Demo</title>
<script type="text/javascript" src="../Sandcastle-header.js"></script>
<script
type="text/javascript"
src="../../../Build/CesiumUnminified/Cesium.js"
nomodule
></script>
<script type="module" src="../load-cesium-es6.js"></script>
</head>
<body
class="sandcastle-loading"
data-sandcastle-bucket="bucket-requirejs.html"
>
<style>
@import url(../templates/bucket.css);
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<div id="toolbar"></div>
<script id="cesium_sandcastle_script">
window.startup = async function (Cesium) {
"use strict";
//Sandcastle_Begin
const viewer = new Cesium.Viewer("cesiumContainer");
function addBillboard() {
Sandcastle.declare(addBillboard);
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: "../images/Cesium_Logo_overlay.png",
},
});
}
function setBillboardProperties() {
Sandcastle.declare(setBillboardProperties);
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: "../images/Cesium_Logo_overlay.png", // default: undefined
show: true, // default
pixelOffset: new Cesium.Cartesian2(0, -50), // default: (0, 0)
eyeOffset: new Cesium.Cartesian3(0.0, 0.0, 0.0), // default
horizontalOrigin: Cesium.HorizontalOrigin.CENTER, // default
verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // default: CENTER
scale: 2.0, // default: 1.0
color: Cesium.Color.LIME, // default: WHITE
rotation: Cesium.Math.PI_OVER_FOUR, // default: 0.0
alignedAxis: Cesium.Cartesian3.ZERO, // default
width: 100, // default: undefined
height: 25, // default: undefined
},
});
}
function changeBillboardProperties() {
Sandcastle.declare(changeBillboardProperties);
const entity = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(
-75.59777,
40.03883,
300000.0,
),
billboard: {
image: "../images/Cesium_Logo_overlay.png",
},
});
const billboard = entity.billboard;
billboard.scale = 3.0;
billboard.color = Cesium.Color.WHITE.withAlpha(0.25);
}
function sizeBillboardInMeters() {
Sandcastle.declare(sizeBillboardInMeters);
const entity = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: "../images/Cesium_Logo_overlay.png",
sizeInMeters: true,
},
});
viewer.zoomTo(entity);
}
function addMultipleBillboards() {
Sandcastle.declare(addMultipleBillboards);
const logoUrl = "../images/Cesium_Logo_overlay.png";
const facilityUrl = "../images/facility.gif";
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: logoUrl,
},
});
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-80.5, 35.14),
billboard: {
image: facilityUrl,
},
});
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-80.12, 25.46),
billboard: {
image: facilityUrl,
},
});
}
function scaleByDistance() {
Sandcastle.declare(scaleByDistance);
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: "../images/facility.gif",
scaleByDistance: new Cesium.NearFarScalar(1.5e2, 2.0, 1.5e7, 0.5),
},
});
}
function fadeByDistance() {
Sandcastle.declare(fadeByDistance);
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: "../images/Cesium_Logo_overlay.png",
translucencyByDistance: new Cesium.NearFarScalar(
1.5e2,
2.0,
1.5e7,
0.5,
),
},
});
}
function offsetByDistance() {
Sandcastle.declare(offsetByDistance);
Promise.all([
Cesium.Resource.fetchImage("../images/Cesium_Logo_overlay.png"),
Cesium.Resource.fetchImage("../images/facility.gif"),
]).then(function (images) {
// As viewer zooms closer to facility billboard,
// increase pixelOffset on CesiumLogo billboard to this height
const facilityHeight = images[1].height;
// colocated billboards, separate as viewer gets closer
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: images[1],
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
},
});
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: images[0],
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(0.0, -facilityHeight),
pixelOffsetScaleByDistance: new Cesium.NearFarScalar(
1.0e3,
1.0,
1.5e6,
0.0,
),
translucencyByDistance: new Cesium.NearFarScalar(
1.0e3,
1.0,
1.5e6,
0.1,
),
},
});
});
}
function addMarkerBillboards() {
Sandcastle.declare(addMarkerBillboards);
// Add several billboards based on the above image in the atlas.
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: "../images/whiteShapes.png",
imageSubRegion: new Cesium.BoundingRectangle(49, 43, 18, 18),
color: Cesium.Color.LIME,
},
});
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-84.0, 39.0),
billboard: {
image: "../images/whiteShapes.png",
imageSubRegion: new Cesium.BoundingRectangle(61, 23, 18, 18),
color: new Cesium.Color(0, 0.5, 1.0, 1.0),
},
});
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-70.0, 41.0),
billboard: {
image: "../images/whiteShapes.png",
imageSubRegion: new Cesium.BoundingRectangle(67, 80, 14, 14),
color: new Cesium.Color(0.5, 0.9, 1.0, 1.0),
},
});
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-73.0, 37.0),
billboard: {
image: "../images/whiteShapes.png",
imageSubRegion: new Cesium.BoundingRectangle(27, 103, 22, 22),
color: Cesium.Color.RED,
},
});
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-79.0, 35.0),
billboard: {
image: "../images/whiteShapes.png",
imageSubRegion: new Cesium.BoundingRectangle(105, 105, 18, 18),
color: Cesium.Color.YELLOW,
},
});
}
async function disableDepthTest() {
Sandcastle.declare(disableDepthTest);
viewer.scene.globe.depthTestAgainstTerrain = true;
try {
const worldTerrainProvider = await Cesium.createWorldTerrainAsync();
// Return early in case a different option has been selected in the meantime
if (!viewer.scene.globe.depthTestAgainstTerrain) {
return;
}
viewer.terrainProvider = worldTerrainProvider;
} catch (error) {
window.alert(`Failed to load terrain. ${error}`);
}
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-122.1958, 46.1915),
billboard: {
image: "../images/facility.gif",
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
},
});
viewer.scene.camera.setView({
destination: new Cesium.Cartesian3(
-2357576.243142461,
-3744417.5604860787,
4581807.855903771,
),
orientation: new Cesium.HeadingPitchRoll(
5.9920811504170475,
-0.6032820429886212,
6.28201303164098,
),
});
}
Sandcastle.addToolbarMenu([
{
text: "Add billboard",
onselect: function () {
addBillboard();
Sandcastle.highlight(addBillboard);
},
},
{
text: "Set billboard properties at creation",
onselect: function () {
setBillboardProperties();
Sandcastle.highlight(setBillboardProperties);
},
},
{
text: "Change billboard properties",
onselect: function () {
changeBillboardProperties();
Sandcastle.highlight(changeBillboardProperties);
},
},
{
text: "Size billboard in meters",
onselect: function () {
sizeBillboardInMeters();
Sandcastle.highlight(sizeBillboardInMeters);
},
},
{
text: "Add multiple billboards",
onselect: function () {
addMultipleBillboards();
Sandcastle.highlight(addMultipleBillboards);
},
},
{
text: "Scale by viewer distance",
onselect: function () {
scaleByDistance();
Sandcastle.highlight(scaleByDistance);
},
},
{
text: "Fade by viewer distance",
onselect: function () {
fadeByDistance();
Sandcastle.highlight(fadeByDistance);
},
},
{
text: "Offset by viewer distance",
onselect: function () {
offsetByDistance();
Sandcastle.highlight(offsetByDistance);
},
},
{
text: "Add marker billboards",
onselect: function () {
addMarkerBillboards();
Sandcastle.highlight(addMarkerBillboards);
},
},
{
text: "Disable the depth test when clamped to ground",
onselect: function () {
disableDepthTest();
Sandcastle.highlight(disableDepthTest);
},
},
]);
Sandcastle.reset = async function () {
viewer.camera.flyHome(0);
viewer.entities.removeAll();
viewer.scene.terrainProvider = new Cesium.EllipsoidTerrainProvider();
viewer.scene.globe.depthTestAgainstTerrain = false;
};
//Sandcastle_End
};
if (typeof Cesium !== "undefined") {
window.startupCalled = true;
window.startup(Cesium).catch((error) => {
"use strict";
console.error(error);
});
Sandcastle.finishedLoading();
}
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,381 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
/>
<meta
name="description"
content="Moon terrain visualized using 3D Tiles, tiled and hosted by Cesium ion, and shown with points of interest and additional data layers."
/>
<meta
name="cesium-sandcastle-labels"
content="Showcases, ion Assets, 3D Tiles"
/>
<title>Cesium Moon Terrain</title>
<script type="text/javascript" src="../Sandcastle-header.js"></script>
<script
type="text/javascript"
src="../../../Build/CesiumUnminified/Cesium.js"
nomodule
></script>
<script type="module" src="../load-cesium-es6.js"></script>
</head>
<body
class="sandcastle-loading"
data-sandcastle-bucket="bucket-requirejs.html"
>
<style>
@import url(../templates/bucket.css);
#toolbar {
background: rgba(42, 42, 42, 0.8);
padding: 4px;
border-radius: 4px;
}
#toolbar input {
vertical-align: middle;
padding-top: 2px;
padding-bottom: 2px;
}
#toolbar .header {
font-weight: bold;
}
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<div id="toolbar"></div>
<script id="cesium_sandcastle_script">
window.startup = async function (Cesium) {
"use strict";
//Sandcastle_Begin
// Set the ellipsoid to be the moon before creating the viewer
Cesium.Ellipsoid.default = Cesium.Ellipsoid.MOON;
const viewer = new Cesium.Viewer("cesiumContainer", {
terrainProvider: false,
baseLayer: false,
timeline: false,
animation: false,
baseLayerPicker: false,
geocoder: false,
shadows: true,
});
const scene = viewer.scene;
// Add Moon Terrain 3D Tiles
try {
const tileset1 = await Cesium.Cesium3DTileset.fromIonAssetId(
2684829,
{
// Allow clamp to 3D Tiles
enableCollision: true,
},
);
viewer.scene.primitives.add(tileset1);
} catch (error) {
console.log(`Error loading tileset: ${error}`);
}
// Boundary data from https://wms.lroc.asu.edu/lroc/view_rdr/SHAPEFILE_LROC_GLOBAL_MARE
const boundariesResource =
await Cesium.IonResource.fromAssetId(2683530);
const boundarySource = await Cesium.GeoJsonDataSource.load(
boundariesResource,
{
clampToGround: true,
fill: Cesium.Color.fromBytes(26, 106, 113).withAlpha(0.6),
},
);
boundarySource.show = false;
viewer.dataSources.add(boundarySource);
// Possible Artemis 3 landing locations. data from https://files.actgate.com/lunar/A3_Named_regions.geojson
const artemis3resource = await Cesium.IonResource.fromAssetId(2683531);
const artemis3Source = await Cesium.GeoJsonDataSource.load(
artemis3resource,
{
clampToGround: true,
fill: Cesium.Color.fromBytes(243, 242, 99).withAlpha(0.6),
},
);
artemis3Source.show = false;
viewer.dataSources.add(artemis3Source);
// Positions courtesy of https://www.sciencedirect.com/science/article/abs/pii/S0019103516301518?via%3Dihub
const pointsOfInterest = [
{
text: "Apollo 11",
latitude: 0.67416,
longitude: 23.47315,
},
{
text: "Apollo 14",
latitude: -3.64417,
longitude: 342.52135,
},
{
text: "Apollo 15",
latitude: 26.13341,
longitude: 3.6285,
},
{
text: "Lunokhod 1",
latitude: 38.2378,
longitude: -35.0017,
},
{
text: "Lunokhod 2",
latitude: 25.83232,
longitude: 30.92215,
},
];
for (const poi of pointsOfInterest) {
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(
poi.longitude,
poi.latitude,
),
label: {
text: poi.text,
font: "14pt Verdana",
outlineColor: Cesium.Color.DARKSLATEGREY,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
pixelOffset: new Cesium.Cartesian2(0, -22),
scaleByDistance: new Cesium.NearFarScalar(1.5e2, 1.0, 1.5e7, 0.5),
translucencyByDistance: new Cesium.NearFarScalar(
2.5e7,
1.0,
4.0e7,
0.0,
),
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
disableDepthTestDistance: new Cesium.CallbackProperty(() => {
return Cesium.Cartesian3.magnitude(scene.camera.positionWC);
}, false),
},
point: {
pixelSize: 10,
color: Cesium.Color.fromBytes(243, 242, 99),
outlineColor: Cesium.Color.fromBytes(219, 218, 111),
outlineWidth: 2,
scaleByDistance: new Cesium.NearFarScalar(1.5e3, 1.0, 4.0e7, 0.1),
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
disableDepthTestDistance: new Cesium.CallbackProperty(() => {
return Cesium.Cartesian3.magnitude(scene.camera.positionWC);
}, false),
},
});
}
const seaOfTranquility = {
destination: new Cesium.Cartesian3(
2134594.9298812235,
1256488.0678322134,
379606.9284823841,
),
orientation: {
direction: new Cesium.Cartesian3(
-0.8518395698371783,
-0.5014189063342804,
-0.1514873843927112,
),
up: new Cesium.Cartesian3(
-0.13054959630640847,
-0.07684549781463353,
0.9884591910493093,
),
},
easingFunction: Cesium.EasingFunction.LINEAR_NONE,
};
const apollo11 = {
destination: new Cesium.Cartesian3(
1609100.311044896,
733266.0643925276,
53608.976740262646,
),
orientation: {
direction: new Cesium.Cartesian3(
-0.41704286323660256,
-0.7222280712427744,
-0.5517806297183315,
),
up: new Cesium.Cartesian3(
0.8621189850799429,
-0.12210806245903304,
-0.49177278965720556,
),
},
easingFunction: Cesium.EasingFunction.LINEAR_NONE,
};
const copernicus = {
destination: new Cesium.Cartesian3(
1613572.8201475781,
-677039.3827805589,
339559.7958496013,
),
orientation: {
direction: new Cesium.Cartesian3(
-0.10007925201262617,
0.8771366500325052,
-0.4696971795597116,
),
up: new Cesium.Cartesian3(
0.9948921707513932,
0.08196514973381885,
-0.058917593354560566,
),
},
easingFunction: Cesium.EasingFunction.LINEAR_NONE,
};
const tycho = {
destination: new Cesium.Cartesian3(
1368413.3560818078,
-166198.00035620513,
-1203576.7397013502,
),
orientation: {
direction: new Cesium.Cartesian3(
-0.8601315724135887,
-0.5073902275496569,
0.05223825345888711,
),
up: new Cesium.Cartesian3(
0.2639103814694499,
-0.5303301783281616,
-0.8056681776681204,
),
},
easingFunction: Cesium.EasingFunction.LINEAR_NONE,
};
const shackleton = {
destination: Cesium.Rectangle.fromBoundingSphere(
new Cesium.BoundingSphere(
new Cesium.Cartesian3(
-17505.087036391753,
38147.40236305639,
-1769721.5748224584,
),
40000.0,
),
),
orientation: {
direction: new Cesium.Cartesian3(
0.2568703591904826,
-0.6405212914728244,
0.7237058060699372,
),
up: new Cesium.Cartesian3(
0.26770932874967773,
-0.6723714327527822,
-0.6901075073627064,
),
},
easingFunction: Cesium.EasingFunction.LINEAR_NONE,
};
const camera = viewer.scene.camera;
const rotationSpeed = Cesium.Math.toRadians(0.1);
const removeRotation = viewer.scene.postRender.addEventListener(
function (scene, time) {
viewer.scene.camera.rotateRight(rotationSpeed);
},
);
const options1 = [
{
text: "Fly to...",
onselect: () => {},
},
{
text: "Sea of Tranquility",
onselect: function () {
removeRotation();
scene.camera.flyTo(seaOfTranquility);
artemis3Source.show = false;
},
},
{
text: "Apollo 11 Landing Site",
onselect: () => {
removeRotation();
scene.camera.flyTo(apollo11);
artemis3Source.show = false;
},
},
{
text: "Copernicus Crater",
onselect: () => {
removeRotation();
scene.camera.flyTo(copernicus);
artemis3Source.show = false;
},
},
{
text: "Tycho Crater",
onselect: () => {
removeRotation();
scene.camera.flyTo(tycho);
artemis3Source.show = false;
},
},
{
text: "Shackleton Crater (South Pole) and Artemis 3 landing options",
onselect: () => {
removeRotation();
scene.camera.flyTo(shackleton);
artemis3Source.show = true;
},
},
];
Sandcastle.addToolbarMenu(options1);
Sandcastle.addToggleButton(
"Show Mare Boundaries",
false,
function (checked) {
boundarySource.show = checked;
},
);
// Spin the moon on first load but disable the spinning upon any input
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(
() => removeRotation(),
Cesium.ScreenSpaceEventType.LEFT_DOWN,
);
handler.setInputAction(
() => removeRotation(),
Cesium.ScreenSpaceEventType.RIGHT_DOWN,
);
handler.setInputAction(
() => removeRotation(),
Cesium.ScreenSpaceEventType.MIDDLE_DOWN,
);
handler.setInputAction(
() => removeRotation(),
Cesium.ScreenSpaceEventType.WHEEL,
);
//Sandcastle_End
};
if (typeof Cesium !== "undefined") {
window.startupCalled = true;
window.startup(Cesium).catch((error) => {
"use strict";
console.error(error);
});
Sandcastle.finishedLoading();
}
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,206 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
/>
<meta name="description" content="Exaggerate terrain." />
<meta name="cesium-sandcastle-labels" content="Showcases" />
<title>Cesium Demo</title>
<script type="text/javascript" src="../Sandcastle-header.js"></script>
<script
type="text/javascript"
src="../../../Build/CesiumUnminified/Cesium.js"
nomodule
></script>
<script type="module" src="../load-cesium-es6.js"></script>
</head>
<body
class="sandcastle-loading"
data-sandcastle-bucket="bucket-requirejs.html"
>
<style>
@import url(../templates/bucket.css);
#toolbar {
background: rgba(42, 42, 42, 0.8);
padding: 4px;
border-radius: 4px;
}
#toolbar input {
vertical-align: middle;
padding-top: 2px;
padding-bottom: 2px;
}
#toolbar .header {
font-weight: bold;
}
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<div id="toolbar">
<table>
<tbody>
<tr>
<td>Exaggeration</td>
<td>
<input
type="range"
min="0"
max="10"
step="0.01"
data-bind="value: exaggeration, valueUpdate: 'input'"
/>
<input type="text" size="5" data-bind="value: exaggeration" />
</td>
</tr>
<tr>
<td>Relative Height</td>
<td>
<input
type="range"
min="-1000"
max="9000"
step="1"
data-bind="value: relativeHeight, valueUpdate: 'input'"
/>
<input type="text" size="5" data-bind="value: relativeHeight" />
</td>
</tr>
</tbody>
</table>
</div>
<script id="cesium_sandcastle_script">
window.startup = async function (Cesium) {
"use strict";
//Sandcastle_Begin
const viewer = new Cesium.Viewer("cesiumContainer", {
terrain: Cesium.Terrain.fromWorldTerrain(),
});
const scene = viewer.scene;
const globe = scene.globe;
scene.verticalExaggeration = 2.0;
scene.verticalExaggerationRelativeHeight = 2400.0;
scene.camera.setView({
destination: new Cesium.Cartesian3(
336567.0354790703,
5664688.047602498,
2923204.3566963132,
),
orientation: new Cesium.HeadingPitchRoll(
1.2273281382639265,
-0.32239612370237514,
0.0027207329018610338,
),
});
viewer.entities.add({
position: new Cesium.Cartesian3(
314557.3531714575,
5659723.771882165,
2923538.5417330978,
),
ellipsoid: {
radii: new Cesium.Cartesian3(400.0, 400.0, 400.0),
material: Cesium.Color.RED,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
},
});
let visualizeRelativeHeight = true;
function updateMaterial() {
if (visualizeRelativeHeight) {
const height = scene.verticalExaggerationRelativeHeight;
const exaggeration = scene.verticalExaggeration;
const alpha = Math.min(1.0, exaggeration * 0.25);
const layer = {
extendUpwards: true,
extendDownwards: true,
entries: [
{
height: height + 100.0,
color: new Cesium.Color(0.0, 1.0, 0.0, alpha * 0.25),
},
{
height: height + 50.0,
color: new Cesium.Color(1.0, 1.0, 1.0, alpha * 0.5),
},
{
height: height,
color: new Cesium.Color(1.0, 1.0, 1.0, alpha),
},
{
height: height - 50.0,
color: new Cesium.Color(1.0, 1.0, 1.0, alpha * 0.5),
},
{
height: height - 100.0,
color: new Cesium.Color(1.0, 0.0, 0.0, alpha * 0.25),
},
],
};
scene.globe.material = Cesium.createElevationBandMaterial({
scene: scene,
layers: [layer],
});
} else {
scene.globe.material = undefined;
}
}
updateMaterial();
const viewModel = {
exaggeration: scene.verticalExaggeration,
relativeHeight: scene.verticalExaggerationRelativeHeight,
};
function updateExaggeration() {
scene.verticalExaggeration = Number(viewModel.exaggeration);
scene.verticalExaggerationRelativeHeight = Number(
viewModel.relativeHeight,
);
updateMaterial();
}
Cesium.knockout.track(viewModel);
const toolbar = document.getElementById("toolbar");
Cesium.knockout.applyBindings(viewModel, toolbar);
for (const name in viewModel) {
if (viewModel.hasOwnProperty(name)) {
Cesium.knockout
.getObservable(viewModel, name)
.subscribe(updateExaggeration);
}
}
Sandcastle.addToggleButton(
"Visualize Relative Height",
visualizeRelativeHeight,
function (checked) {
visualizeRelativeHeight = checked;
updateMaterial();
},
);
Sandcastle.addToolbarButton("Remove Exaggeration", function () {
viewModel.exaggeration = 1.0;
viewModel.relativeHeight = 0.0;
});
//Sandcastle_End
};
if (typeof Cesium !== "undefined") {
window.startupCalled = true;
window.startup(Cesium).catch((error) => {
"use strict";
console.error(error);
});
Sandcastle.finishedLoading();
}
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
<circle cx="256" cy="256" r="256" fill="#fff"/>
<path d="M256 32C132.288 32 32 132.286 32 256a224 224 0 0 0 7.41 56.564c.127.114.26.237.379.336 4.128 3.41 8.94 5.16 13.8 5.16 7.43 0 14.595-4.094 20.2-11.5l70.601-93.301c11.682-15.438 28.3-24.299 45.5-24.299s33.74 8.895 45.5 24.299c10.428 13.659 45.073 59.665 67.799 89.701.932 1.232 1.947 2.4 2.92 3.6 6.16 7.594 12.7 11.5 20 11.5 7.591 0 13.505-3.656 20-11.5.993-1.2 2.038-2.36 2.98-3.6 22.922-30.159 57.355-75.935 67.801-89.701 11.703-15.422 28.3-24.299 45.5-24.299 2.437 0 4.878.3 7.287.62C440.608 95.952 354.034 32.107 256 32zm75.05 95.05a23.525 23.525 0 0 1 23.526 23.527 23.525 23.525 0 0 1-23.526 23.526 23.525 23.525 0 0 1-23.525-23.526 23.525 23.525 0 0 1 23.525-23.527z" fill="#6dabe4"/>
<path d="M478.011 228.398c-4.666-4.399-10.09-6.538-16.158-6.538-9.114 0-15.027 4.79-20.953 11.64l-70.4 93.3c-11.698 15.502-28.2 24.299-45.4 24.299h-.268c-17.2 0-33.762-8.877-45.399-24.3l-70.4-93.3c-5.596-7.416-12.7-11.5-20.1-11.5-7.36 0-14.553 4.146-20.101 11.5l-70.399 93.3c-11.56 15.32-27.885 24.329-44.976 24.3C90.3 429.673 169.216 479.895 256 479.999c123.711 0 224-100.288 224-224a224.007 224.007 0 0 0-1.989-27.601z" fill="#709c49"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -0,0 +1,9 @@
<PAMDataset>
<GeoTransform> -1.2000000000000000e+02, 5.0462573591253154e-02, 0.0000000000000000e+00, 4.0000000000000000e+01, 0.0000000000000000e+00, -6.6666666666666666e-02</GeoTransform>
<Metadata domain="IMAGE_STRUCTURE">
<MDI key="COMPRESSION">JPEG</MDI>
<MDI key="INTERLEAVE">PIXEL</MDI>
<MDI key="SOURCE_COLOR_SPACE">YCbCr</MDI>
</Metadata>
<Metadata />
</PAMDataset>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<TileMap version="1.0.0" tilemapservice="http://tms.osgeo.org/1.0.0">
<Title>Cesium_Logo_Color.jpg</Title>
<Abstract></Abstract>
<SRS>EPSG:900913</SRS>
<BoundingBox miny="19.98909517812623" minx="-119.99999999999991" maxy="39.99999999999996" maxx="-60.00762865915187"/>
<Origin y="19.98909517812623" x="-119.99999999999991"/>
<TileFormat width="256" height="256" mime-type="image/png" extension="png"/>
<TileSets profile="mercator">
<TileSet href="0" units-per-pixel="156543.03390000000945" order="0"/>
<TileSet href="1" units-per-pixel="78271.51695000000473" order="1"/>
<TileSet href="2" units-per-pixel="39135.75847500000236" order="2"/>
<TileSet href="3" units-per-pixel="19567.87923750000118" order="3"/>
<TileSet href="4" units-per-pixel="9783.93961875000059" order="4"/>
</TileSets>
</TileMap>

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,66 @@
/* @import url(/Build/CesiumUnminified/Widgets/widgets.css);
@import url(/Build/CesiumUnminified/Widgets/lighter.css); */
html {
height: 100%;
}
body {
background: #000;
color: #eee;
font-family: sans-serif;
font-size: 9pt;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.fullSize {
display: block;
position: absolute;
top: 0;
left: 0;
border: none;
width: 100%;
height: 100%;
}
#loadingOverlay {
position: absolute;
top: 0;
left: 0;
opacity: 0.9;
width: 100%;
height: 100%;
display: none;
}
#loadingOverlay h1 {
text-align: center;
position: relative;
top: 50%;
margin-top: -0.5em;
}
.sandcastle-loading #loadingOverlay {
display: block;
}
.sandcastle-loading #toolbar {
display: none;
}
#toolbar {
margin: 5px;
padding: 2px 5px;
position: absolute;
}
.infoPanel {
background: rgba(42, 42, 42, 0.8);
padding: 4px;
border: 1px solid #444;
border-radius: 4px;
}

View File

@ -0,0 +1,66 @@
@import url(../../../Source/Widgets/widgets.css);
@import url(../../../Source/Widgets/lighter.css);
html {
height: 100%;
}
body {
background: #000;
color: #eee;
font-family: sans-serif;
font-size: 9pt;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.fullSize {
display: block;
position: absolute;
top: 0;
left: 0;
border: none;
width: 100%;
height: 100%;
}
#loadingOverlay {
position: absolute;
top: 0;
left: 0;
opacity: 0.9;
width: 100%;
height: 100%;
display: none;
}
#loadingOverlay h1 {
text-align: center;
position: relative;
top: 50%;
margin-top: -0.5em;
}
.sandcastle-loading #loadingOverlay {
display: block;
}
.sandcastle-loading #toolbar {
display: none;
}
#toolbar {
margin: 5px;
padding: 2px 5px;
position: absolute;
}
.infoPanel {
background: rgba(42, 42, 42, 0.8);
padding: 4px;
border: 1px solid #444;
border-radius: 4px;
}

View File

@ -0,0 +1,63 @@
#root {
width: 100vw;
height: 100vh;
display: grid;
grid-template:
"toolbar toolbar"
"code viewer"
"gallery gallery";
grid-template-columns: 50% 50%;
grid-template-rows: max-content auto 150px;
/* TODO: this shouldn't be needed but it works for hacky code */
overflow: hidden;
}
.toolbar {
grid-area: toolbar;
display: flex;
gap: 0.5rem;
padding: 0.5rem;
background: var(--ids-color-bg-page-base);
.spacer {
flex-grow: 10;
}
}
.editor-container {
grid-area: code;
section {
border-top: 1px solid grey;
}
}
.viewer-bucket {
grid-area: viewer;
width: 50vw;
background-color: white;
background-image: var(--_rings), var(--_gradient);
--_rings: repeating-radial-gradient(
circle at center,
var(--ids-color-border-neutral-muted) 1px,
var(--ids-color-border-neutral-muted) 3px,
transparent 3px,
transparent 10px
);
--_gradient: linear-gradient(
180deg,
var(--ids-color-bg-page-base) 0%,
var(--ids-color-bg-page-depth) 100%
);
.fullFrame {
width: 100%;
height: 100%;
}
}
.gallery {
grid-area: gallery;
border-top: 1px solid grey;
background: var(--ids-color-bg-page-depth);
}

View File

@ -0,0 +1,741 @@
import { useEffect, useRef, useState } from "react";
import "./App.css";
import Editor, { Monaco } from "@monaco-editor/react";
import { editor, KeyCode } from "monaco-editor";
import pako from "pako";
import Gallery, { GalleryDemo } from "./Gallery.js";
import gallery_demos from "./gallery-index.ts";
import { Button, Root } from "@itwin/itwinui-react/bricks";
const local = {
docTypes: [],
headers: "<html><head></head><body>",
bucketName: "starter bucket",
emptyBucket: "",
};
const defaultJsCode = 'const viewer = new Cesium.Viewer("cesiumContainer");\n';
const defaultHtmlCode = `<style>
@import url(bucket.css);
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<div id="toolbar"></div>
`;
function embedInSandcastleTemplate(code: string, addExtraLine: boolean) {
console.log("embedSandcastle");
return (
`${
"window.startup = async function (Cesium) {\n" +
" 'use strict';\n" +
"//Sandcastle_Begin\n"
}${addExtraLine ? "\n" : ""}${code}//Sandcastle_End\n` +
` Sandcastle.finishedLoading();\n` +
`};\n` +
`if (typeof Cesium !== 'undefined') {\n` +
` window.startupCalled = true;\n` +
` window.startup(Cesium).catch((error) => {\n` +
` "use strict";\n` +
` console.error(error);\n` +
` });\n` +
`}\n`
);
}
function activateBucketScripts(
bucketDoc: Document,
bucketFrame: HTMLIFrameElement,
jsEditor: editor.IStandaloneCodeEditor,
htmlEditor: editor.IStandaloneCodeEditor,
) {
console.log("activateBucketScripts");
const headNodes = bucketDoc.head.childNodes;
let node;
const nodes: HTMLScriptElement[] = [];
let i, len;
for (i = 0, len = headNodes.length; i < len; ++i) {
node = headNodes[i];
// header is included in blank frame.
if (
node instanceof HTMLScriptElement &&
node.src.indexOf("Sandcastle-header.js") < 0 &&
node.src.indexOf("Cesium.js") < 0
) {
nodes.push(node);
}
}
for (i = 0, len = nodes.length; i < len; ++i) {
bucketDoc.head.removeChild(nodes[i]);
}
// Apply user HTML to bucket.
const htmlElement = bucketDoc.createElement("div");
htmlElement.innerHTML = htmlEditor.getValue();
bucketDoc.body.appendChild(htmlElement);
const onScriptTagError = function () {
if (bucketFrame.contentDocument === bucketDoc) {
// @ts-expect-error this has type any because it's from anywhere inside the bucket
appendConsole("consoleError", `Error loading ${this.src}`, true);
appendConsole(
"consoleError",
"Make sure Cesium is built, see the Contributor's Guide for details.",
true,
);
}
};
console.log("nodes", nodes);
// Load each script after the previous one has loaded.
const loadScript = function () {
if (bucketFrame.contentDocument !== bucketDoc) {
// A newer reload has happened, abort this.
return;
}
if (nodes.length > 0) {
while (nodes.length > 0) {
node = nodes.shift();
if (!node) {
continue;
}
const scriptElement = bucketDoc.createElement("script");
let hasSrc = false;
for (let j = 0, numAttrs = node.attributes.length; j < numAttrs; ++j) {
const name = node.attributes[j].name;
const val = node.attributes[j].value;
scriptElement.setAttribute(name, val);
if (name === "src" && val) {
hasSrc = true;
}
}
scriptElement.innerHTML = node.innerHTML;
if (hasSrc) {
scriptElement.onload = loadScript;
scriptElement.onerror = onScriptTagError;
bucketDoc.head.appendChild(scriptElement);
} else {
bucketDoc.head.appendChild(scriptElement);
loadScript();
}
}
} else {
// Apply user JS to bucket
const element = bucketDoc.createElement("script");
// Firefox line numbers are zero-based, not one-based.
const isFirefox = navigator.userAgent.indexOf("Firefox/") >= 0;
element.textContent = embedInSandcastleTemplate(
jsEditor.getValue(),
isFirefox,
);
bucketDoc.body.appendChild(element);
}
};
loadScript();
}
function appendConsole(
type: "consoleError" | "",
message: string,
focusPanel: boolean,
) {
// TODO:
if (type === "consoleError") {
console.error(message);
return;
}
console.log(message);
if (focusPanel) {
// TODO:
}
}
// let bucketWaiting = false;
function applyBucket(
bucketFrame: HTMLIFrameElement,
jsEditor: editor.IStandaloneCodeEditor,
htmlEditor: editor.IStandaloneCodeEditor,
) {
console.log("applyBucket");
if (
// local.emptyBucket &&
// local.bucketName &&
// typeof bucketTypes[local.bucketName] === "string"
// eslint-disable-next-line no-constant-condition
true
) {
// bucketWaiting = false;
const bucketDoc = bucketFrame.contentDocument;
if (!bucketDoc) {
console.warn(
"tried to applyBucket before the bucket content document existed",
);
return;
}
if (
local.headers.substring(0, local.emptyBucket.length) !== local.emptyBucket
) {
appendConsole(
"consoleError",
`Error, first part of ${local.bucketName} must match first part of bucket.html exactly.`,
true,
);
} else {
const bodyAttributes = local.headers.match(/<body([^>]*?)>/)?.[1] ?? "";
const attributeRegex = /([-a-z_]+)\s*="([^"]*?)"/gi;
//group 1 attribute name, group 2 attribute value. Assumes double-quoted attributes.
let attributeMatch;
while ((attributeMatch = attributeRegex.exec(bodyAttributes)) !== null) {
const attributeName = attributeMatch[1];
const attributeValue = attributeMatch[2];
if (attributeName === "class") {
bucketDoc.body.className = attributeValue;
} else {
bucketDoc.body.setAttribute(attributeName, attributeValue);
}
}
const pos = local.headers.indexOf("</head>");
const extraHeaders = local.headers.substring(
local.emptyBucket.length,
pos,
);
bucketDoc.head.innerHTML += extraHeaders;
activateBucketScripts(bucketDoc, bucketFrame, jsEditor, htmlEditor);
}
} else {
// bucketWaiting = true;
}
}
// window.addEventListener(
// "message",
// function (e) {
// let line;
// // The iframe (bucket.html) sends this message on load.
// // This triggers the code to be injected into the iframe.
// if (e.data === "reload") {
// console.log("message reload");
// const bucketDoc = bucketFrame.contentDocument;
// if (!local.bucketName) {
// // Reload fired, bucket not specified yet.
// return;
// }
// if (bucketDoc.body.getAttribute("data-sandcastle-loaded") !== "yes") {
// bucketDoc.body.setAttribute("data-sandcastle-loaded", "yes");
// logOutput.innerHTML = "";
// numberOfNewConsoleMessages = 0;
// registry.byId("logContainer").set("title", "Console");
// // This happens after a Run (F8) reloads bucket.html, to inject the editor code
// // into the iframe, causing the demo to run there.
// applyBucket();
// // if (docError) {
// // appendConsole(
// // "consoleError",
// // 'Documentation not available. Please run the "build-docs" build script to generate Cesium documentation.',
// // true,
// // );
// // // showGallery();
// // }
// // if (galleryError) {
// // appendConsole(
// // "consoleError",
// // "Error loading gallery, please run the build script.",
// // true,
// // );
// // }
// // if (deferredLoadError) {
// // appendConsole(
// // "consoleLog",
// // `Unable to load demo named ${queryObject.src.replace(
// // ".html",
// // "",
// // )}. Redirecting to HelloWorld.\n`,
// // true,
// // );
// // }
// }
// // } else if (defined(e.data.log)) {
// // // Console log messages from the iframe display in Sandcastle.
// // appendConsole("consoleLog", e.data.log, false);
// // } else if (defined(e.data.error)) {
// // // Console error messages from the iframe display in Sandcastle
// // let errorMsg = e.data.error;
// // let lineNumber = e.data.lineNumber;
// // if (defined(lineNumber)) {
// // errorMsg += " (on line ";
// // if (e.data.url) {
// // errorMsg += `${lineNumber} of ${e.data.url})`;
// // } else {
// // lineNumber = scriptLineToEditorLine(lineNumber);
// // errorMsg += `${lineNumber + 1})`;
// // line = jsEditor.setGutterMarker(
// // lineNumber,
// // "errorGutter",
// // makeLineLabel(e.data.error, "errorMarker"),
// // );
// // jsEditor.addLineClass(line, "text", "errorLine");
// // errorLines.push(line);
// // scrollToLine(lineNumber);
// // }
// // }
// // appendConsole("consoleError", errorMsg, true);
// // } else if (defined(e.data.warn)) {
// // // Console warning messages from the iframe display in Sandcastle.
// // appendConsole("consoleWarn", e.data.warn, true);
// // } else if (defined(e.data.highlight)) {
// // // Hovering objects in the embedded Cesium window.
// // highlightLine(e.data.highlight);
// }
// },
// true,
// );
const TYPES_URL = `${__PAGE_BASE_URL__}Source/Cesium.d.ts`;
// function appendCode(code, run = true) {
// const codeMirror = getJsCodeMirror();
// codeMirror.setValue(`${codeMirror.getValue()}\n${code}`);
// if (run) {
// runCesium();
// }
// }
// function appendCodeOnce(code, run = true) {
// const codeMirror = getJsCodeMirror();
// if (!codeMirror.getValue().includes(code)) {
// appendCode(code, run);
// }
// }
// function prependCode(code, run = true) {
// const codeMirror = getJsCodeMirror();
// codeMirror.setValue(`${code}\n${codeMirror.getValue()}`);
// if (run) {
// runCesium();
// }
// }
// function prependCodeOnce(code, run = true) {
// const codeMirror = getJsCodeMirror();
// if (!codeMirror.getValue().includes(code)) {
// prependCode(code, run);
// }
// }
type SandcastleSaveData = {
code: string;
html: string;
baseHref?: string;
};
function makeCompressedBase64String(data: [code: string, html: string]) {
// data stored in the hash as:
// Base64 encoded, raw DEFLATE compressed JSON array where index 0 is code, index 1 is html
let jsonString = JSON.stringify(data);
// we save a few bytes by omitting the leading [" and trailing "] since they are always the same
jsonString = jsonString.slice(2, 2 + jsonString.length - 4);
const pakoString = pako.deflate(jsonString, {
raw: true,
level: 9,
});
let base64String = btoa(
// TODO: not 100% sure why I have to do this conversion manually anymore but it works
// https://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string
String.fromCharCode(...new Uint8Array(pakoString)),
);
base64String = base64String.replace(/=+$/, ""); // remove padding
return base64String;
}
function decodeBase64Data(base64String: string): SandcastleSaveData {
// data stored in the hash as:
// Base64 encoded, raw DEFLATE compressed JSON array where index 0 is code, index 1 is html
// restore padding
while (base64String.length % 4 !== 0) {
base64String += "=";
}
// https://stackoverflow.com/questions/12710001/how-to-convert-uint8-array-to-base64-encoded-string
const dataArray = new Uint8Array(
atob(base64String)
.split("")
.map(function (c) {
return c.charCodeAt(0);
}),
);
let jsonString = pako.inflate(dataArray, {
raw: true,
to: "string",
});
// we save a few bytes by omitting the leading [" and trailing "] since they are always the same
jsonString = `["${jsonString}"]`;
const json = JSON.parse(jsonString);
// index 0 is code, index 1 is html
const code = json[0];
const html = json[1];
const baseHref = json[2];
return {
code: code,
html: html,
baseHref: baseHref,
};
}
function App() {
const jsEditorRef = useRef<editor.IStandaloneCodeEditor>(null);
const htmlEditorRef = useRef<editor.IStandaloneCodeEditor>(null);
const bucket = useRef<HTMLIFrameElement>(null);
function loadFromUrl() {
// TODO: I don't think this is the "correct" way to do on mount/load logic but it's working
if (
window.location.hash.indexOf("#c=") === 0 &&
jsEditorRef.current &&
htmlEditorRef.current
) {
const base64String = window.location.hash.substr(3);
const data = decodeBase64Data(base64String);
jsEditorRef.current.setValue(data.code);
htmlEditorRef.current.setValue(data.html);
// applyLoadedDemo(code, html);
console.log("loadFromUrl", data);
}
}
/**
* @param {IStandaloneCodeEditor} editor
*/
function handleEditorDidMount(
editor: editor.IStandaloneCodeEditor,
monaco: Monaco,
) {
jsEditorRef.current = editor;
monaco.editor.addCommand({
id: "run-cesium",
run: () => {
runCode();
},
});
// Remove some default keybindings that get in the way
// https://github.com/microsoft/monaco-editor/issues/102
monaco.editor.addKeybindingRules([
{
// disable show command center
keybinding: KeyCode.F1,
command: null,
},
{
// disable show error command
keybinding: KeyCode.F8,
command: null,
},
{
// disable toggle debugger breakpoint
keybinding: KeyCode.F9,
command: null,
},
{
// disable go to definition to allow opening dev console
keybinding: KeyCode.F12,
command: null,
},
{
keybinding: KeyCode.F8,
command: "run-cesium",
},
]);
loadFromUrl();
}
function handleEditorWillMount(monaco: Monaco) {
// here is the monaco instance
// do something before editor is mounted
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
// TODO: pick what target we want, probably newer than ES2020 but TS was upset with that
target: monaco.languages.typescript.ScriptTarget.ES2020,
allowNonTsExtensions: true,
});
setTypes(monaco);
}
function runCode() {
if (!bucket.current || !bucket.current.contentWindow) {
return;
}
// Check for a race condition in some browsers where the iframe hasn't loaded yet.
if (bucket.current.contentWindow.location.href.indexOf("bucket.html") > 0) {
bucket.current.contentWindow.location.reload();
}
// applyBucket(bucket.current, jsEditorRef.current, htmlEditorRef.current);
}
async function setTypes(monaco: Monaco) {
console.log("setTypes");
// https://microsoft.github.io/monaco-editor/playground.html?source=v0.52.2#example-extending-language-services-configure-javascript-defaults
const cesiumTypes = await (await fetch(TYPES_URL)).text();
// TODO: I don't know 100% why this declaration works for global module variable but it "does"
const cesiumTypesSource = `${cesiumTypes}\nvar Cesium: typeof import('cesium');`;
const cesiumTypesUri = "ts:filename/cesium.d.ts";
monaco.languages.typescript.javascriptDefaults.addExtraLib(
cesiumTypesSource,
cesiumTypesUri,
);
monaco.editor.createModel(
cesiumTypesSource,
"typescript",
monaco.Uri.parse(cesiumTypesUri),
);
}
useEffect(() => {
window.addEventListener("message", function (e) {
// The iframe (bucket.html) sends this message on load.
// This triggers the code to be injected into the iframe.
if (e.data === "reload") {
console.log("message reload");
if (!local.bucketName || !bucket.current) {
// Reload fired, bucket not specified yet.
return;
}
const bucketDoc = bucket.current.contentDocument;
if (!bucketDoc) {
// TODO: this whole handler probably needs to be set up better for things like this
console.warn("bucket not set up yet");
return;
}
if (!jsEditorRef.current || !htmlEditorRef.current) {
console.warn("editors not set up yet");
return;
}
if (bucketDoc.body.getAttribute("data-sandcastle-loaded") !== "yes") {
bucketDoc.body.setAttribute("data-sandcastle-loaded", "yes");
// logOutput.innerHTML = "";
// numberOfNewConsoleMessages = 0;
// registry.byId("logContainer").set("title", "Console");
// This happens after a Run (F8) reloads bucket.html, to inject the editor code
// into the iframe, causing the demo to run there.
applyBucket(
bucket.current,
jsEditorRef.current,
htmlEditorRef.current,
);
// if (docError) {
// appendConsole(
// "consoleError",
// 'Documentation not available. Please run the "build-docs" build script to generate Cesium documentation.',
// true,
// );
// // showGallery();
// }
// if (galleryError) {
// appendConsole(
// "consoleError",
// "Error loading gallery, please run the build script.",
// true,
// );
// }
// if (deferredLoadError) {
// appendConsole(
// "consoleLog",
// `Unable to load demo named ${queryObject.src.replace(
// ".html",
// "",
// )}. Redirecting to HelloWorld.\n`,
// true,
// );
// }
}
}
});
}, []);
function formatJs() {
jsEditorRef.current?.getAction("editor.action.formatDocument")?.run();
}
function nextHighestVariableName(name: string) {
if (!jsEditorRef.current) {
// can't find next highest if there's no code yet
return;
}
const codeMirror = jsEditorRef.current;
const code = codeMirror.getValue();
const otherDeclarations = [
...code.matchAll(new RegExp(`(const|let|var)\\s+${name}\\d*\\s=`, "g")),
].length;
const variableName = `${name}${otherDeclarations + 1}`;
return variableName;
}
function appendCode(code: string, run = false) {
if (!jsEditorRef.current) {
// can't append if there's no editor
return;
}
jsEditorRef.current.setValue(`${jsEditorRef.current.getValue()}\n${code}`);
if (run) {
runCode();
}
}
function addButton() {
appendCode(
`
Sandcastle.addToolbarButton("New Button", function () {
// your code here
});`,
false,
);
}
function addToggle() {
const variableName = nextHighestVariableName("toggleValue");
appendCode(
`
let ${variableName} = true;
Sandcastle.addToggleButton("Toggle", ${variableName}, function (checked) {
${variableName} = checked;
});`,
false,
);
}
function addMenu() {
const variableName = nextHighestVariableName("options");
appendCode(
`
const ${variableName} = [
{
text: "Option 1",
onselect: function () {
// your code here, the first option is always run at load
},
},
];
Sandcastle.addToolbarMenu(${variableName});`,
false,
);
}
function setCode(js: string, html: string) {
if (!jsEditorRef.current || !htmlEditorRef.current) {
// can't find next highest if there's no code yet
return;
}
jsEditorRef.current.setValue(js);
htmlEditorRef.current.setValue(html);
runCode();
}
function resetCode() {
if (!jsEditorRef.current || !htmlEditorRef.current) {
// can't find next highest if there's no code yet
return;
}
jsEditorRef.current.setValue(defaultJsCode);
htmlEditorRef.current.setValue(defaultHtmlCode);
window.history.replaceState({}, "", "/");
runCode();
}
function share() {
if (!jsEditorRef.current || !htmlEditorRef.current) {
// can't find next highest if there's no code yet
return;
}
const code = jsEditorRef.current.getValue();
const html = htmlEditorRef.current.getValue();
console.log([code, html]);
const base64String = makeCompressedBase64String([code, html]);
// const shareUrl = `${getBaseUrl()}#c=${base64String}`;
const shareUrl = `#c=${base64String}`;
window.history.replaceState({}, "", shareUrl);
}
function loadDemo(demo: GalleryDemo) {
// do stuff
setCode(demo.js ?? defaultJsCode, demo.html ?? defaultHtmlCode);
// format to account for any bad template strings, not ideal but better than not doing it
formatJs();
// TODO: this is not the right way to save these, should be able to reference by name but this works for the demo
share();
}
const [darkTheme, setDarkTheme] = useState(false);
return (
<Root colorScheme={darkTheme ? "dark" : "light"} density="dense" id="root">
<div className="toolbar">
<Button onClick={resetCode}>New</Button>
<Button onClick={runCode}>Run (F8)</Button>
<Button onClick={formatJs}>Format</Button>
<Button onClick={addButton}>Add button</Button>
<Button onClick={addToggle}>Add toggle</Button>
<Button onClick={addMenu}>Add menu</Button>
<Button onClick={share}>Share</Button>
<div className="spacer"></div>
<Button onClick={() => setDarkTheme(!darkTheme)}>Swap Theme</Button>
</div>
<div className="editor-container">
<Editor
height="70%"
defaultLanguage="javascript"
theme={darkTheme ? "vs-dark" : "light"}
defaultValue={defaultJsCode}
onMount={handleEditorDidMount}
beforeMount={handleEditorWillMount}
/>
<Editor
height="30%"
defaultLanguage="html"
theme={darkTheme ? "vs-dark" : "light"}
defaultValue={defaultHtmlCode}
onMount={(editor) => {
htmlEditorRef.current = editor;
loadFromUrl();
}}
/>
</div>
<div className="viewer-bucket">
<iframe
ref={bucket}
id="bucketFrame"
src="templates/bucket.html"
className="fullFrame"
allowFullScreen
></iframe>
</div>
<div className="gallery">
<Gallery demos={gallery_demos} loadDemo={(demo) => loadDemo(demo)} />
</div>
</Root>
);
}
export default App;

View File

@ -0,0 +1,19 @@
.gallery {
grid-area: gallery;
display: flex;
gap: 1rem;
padding: 0.5rem;
.card {
display: flex;
flex-direction: column;
background: var(--ids-color-bg-info-muted);
padding: 0.5rem;
border-radius: 5px;
cursor: pointer;
img {
height: 80%;
}
}
}

View File

@ -0,0 +1,47 @@
import { MouseEventHandler } from "react";
import "./Gallery.css";
export type GalleryDemo = {
name: string;
isNew: boolean;
img: string;
js: string;
html?: string;
};
type GalleryCardProps = {
demo: GalleryDemo;
cardClickHandler: MouseEventHandler<HTMLDivElement>;
};
function GalleryCard({ demo, cardClickHandler }: GalleryCardProps) {
return (
<div className="card" onClick={cardClickHandler}>
<div>{demo.name}</div>
<img src={`gallery/${demo.img}`} alt="" />
</div>
);
}
type GalleryProps = {
demos: GalleryDemo[];
loadDemo: (demo: GalleryDemo) => void;
};
function Gallery({ demos, loadDemo }: GalleryProps) {
return (
<>
{demos.map((demo) => {
return (
<GalleryCard
key={demo.name}
demo={demo}
cardClickHandler={() => loadDemo(demo)}
></GalleryCard>
);
})}
</>
);
}
export default Gallery;

View File

@ -0,0 +1,921 @@
import { GalleryDemo } from "./Gallery";
const gallery_demos: GalleryDemo[] = [
{
name: "3D Models",
isNew: false,
img: "3D Models.jpg",
js: `const viewer = new Cesium.Viewer("cesiumContainer", {
infoBox: false,
selectionIndicator: false,
shadows: true,
shouldAnimate: true,
});
function createModel(url, height) {
viewer.entities.removeAll();
const position = Cesium.Cartesian3.fromDegrees(
-123.0744619,
44.0503706,
height,
);
const heading = Cesium.Math.toRadians(135);
const pitch = 0;
const roll = 0;
const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
const orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
const entity = viewer.entities.add({
name: url,
position: position,
orientation: orientation,
model: {
uri: url,
minimumPixelSize: 128,
maximumScale: 20000,
},
});
viewer.trackedEntity = entity;
}
const options = [
{
text: "Aircraft",
onselect: function () {
createModel("../../SampleData/models/CesiumAir/Cesium_Air.glb", 5000.0);
},
},
{
text: "Drone",
onselect: function () {
createModel("../../SampleData/models/CesiumDrone/CesiumDrone.glb", 150.0);
},
},
{
text: "Ground Vehicle",
onselect: function () {
createModel("../../SampleData/models/GroundVehicle/GroundVehicle.glb", 0);
},
},
{
text: "Hot Air Balloon",
onselect: function () {
createModel(
"../../SampleData/models/CesiumBalloon/CesiumBalloon.glb",
1000.0,
);
},
},
{
text: "Milk Truck",
onselect: function () {
createModel(
"../../SampleData/models/CesiumMilkTruck/CesiumMilkTruck.glb",
0,
);
},
},
{
text: "Skinned Character",
onselect: function () {
createModel("../../SampleData/models/CesiumMan/Cesium_Man.glb", 0);
},
},
{
text: "Unlit Box",
onselect: function () {
createModel("../../SampleData/models/BoxUnlit/BoxUnlit.gltf", 10.0);
},
},
{
text: "Draco Compressed Model",
onselect: function () {
createModel(
"../../SampleData/models/DracoCompressed/CesiumMilkTruck.gltf",
0,
);
},
},
{
text: "KTX2 Compressed Balloon",
onselect: function () {
if (!Cesium.FeatureDetection.supportsBasis(viewer.scene)) {
window.alert(
"This browser does not support Basis Universal compressed textures",
);
}
createModel(
"../../SampleData/models/CesiumBalloonKTX2/CesiumBalloonKTX2.glb",
1000.0,
);
},
},
{
text: "Instanced Box",
onselect: function () {
createModel("../../SampleData/models/BoxInstanced/BoxInstanced.gltf", 15);
},
},
];
Sandcastle.addToolbarMenu(options);`,
},
{
name: "Billboards",
isNew: false,
img: "Billboards.jpg",
js: `const viewer = new Cesium.Viewer("cesiumContainer");
function addBillboard() {
Sandcastle.declare(addBillboard);
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: "../images/Cesium_Logo_overlay.png",
},
});
}
function setBillboardProperties() {
Sandcastle.declare(setBillboardProperties);
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: "../images/Cesium_Logo_overlay.png", // default: undefined
show: true, // default
pixelOffset: new Cesium.Cartesian2(0, -50), // default: (0, 0)
eyeOffset: new Cesium.Cartesian3(0.0, 0.0, 0.0), // default
horizontalOrigin: Cesium.HorizontalOrigin.CENTER, // default
verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // default: CENTER
scale: 2.0, // default: 1.0
color: Cesium.Color.LIME, // default: WHITE
rotation: Cesium.Math.PI_OVER_FOUR, // default: 0.0
alignedAxis: Cesium.Cartesian3.ZERO, // default
width: 100, // default: undefined
height: 25, // default: undefined
},
});
}
function changeBillboardProperties() {
Sandcastle.declare(changeBillboardProperties);
const entity = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883, 300000.0),
billboard: {
image: "../images/Cesium_Logo_overlay.png",
},
});
const billboard = entity.billboard;
billboard.scale = 3.0;
billboard.color = Cesium.Color.WHITE.withAlpha(0.25);
}
function sizeBillboardInMeters() {
Sandcastle.declare(sizeBillboardInMeters);
const entity = viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: "../images/Cesium_Logo_overlay.png",
sizeInMeters: true,
},
});
viewer.zoomTo(entity);
}
function addMultipleBillboards() {
Sandcastle.declare(addMultipleBillboards);
const logoUrl = "../images/Cesium_Logo_overlay.png";
const facilityUrl = "../images/facility.gif";
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: logoUrl,
},
});
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-80.5, 35.14),
billboard: {
image: facilityUrl,
},
});
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-80.12, 25.46),
billboard: {
image: facilityUrl,
},
});
}
function scaleByDistance() {
Sandcastle.declare(scaleByDistance);
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: "../images/facility.gif",
scaleByDistance: new Cesium.NearFarScalar(1.5e2, 2.0, 1.5e7, 0.5),
},
});
}
function fadeByDistance() {
Sandcastle.declare(fadeByDistance);
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: "../images/Cesium_Logo_overlay.png",
translucencyByDistance: new Cesium.NearFarScalar(1.5e2, 2.0, 1.5e7, 0.5),
},
});
}
function offsetByDistance() {
Sandcastle.declare(offsetByDistance);
Promise.all([
Cesium.Resource.fetchImage("../images/Cesium_Logo_overlay.png"),
Cesium.Resource.fetchImage("../images/facility.gif"),
]).then(function (images) {
// As viewer zooms closer to facility billboard,
// increase pixelOffset on CesiumLogo billboard to this height
const facilityHeight = images[1].height;
// colocated billboards, separate as viewer gets closer
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: images[1],
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
},
});
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: images[0],
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
pixelOffset: new Cesium.Cartesian2(0.0, -facilityHeight),
pixelOffsetScaleByDistance: new Cesium.NearFarScalar(
1.0e3,
1.0,
1.5e6,
0.0,
),
translucencyByDistance: new Cesium.NearFarScalar(1.0e3, 1.0, 1.5e6, 0.1),
},
});
});
}
function addMarkerBillboards() {
Sandcastle.declare(addMarkerBillboards);
// Add several billboards based on the above image in the atlas.
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
billboard: {
image: "../images/whiteShapes.png",
imageSubRegion: new Cesium.BoundingRectangle(49, 43, 18, 18),
color: Cesium.Color.LIME,
},
});
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-84.0, 39.0),
billboard: {
image: "../images/whiteShapes.png",
imageSubRegion: new Cesium.BoundingRectangle(61, 23, 18, 18),
color: new Cesium.Color(0, 0.5, 1.0, 1.0),
},
});
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-70.0, 41.0),
billboard: {
image: "../images/whiteShapes.png",
imageSubRegion: new Cesium.BoundingRectangle(67, 80, 14, 14),
color: new Cesium.Color(0.5, 0.9, 1.0, 1.0),
},
});
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-73.0, 37.0),
billboard: {
image: "../images/whiteShapes.png",
imageSubRegion: new Cesium.BoundingRectangle(27, 103, 22, 22),
color: Cesium.Color.RED,
},
});
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-79.0, 35.0),
billboard: {
image: "../images/whiteShapes.png",
imageSubRegion: new Cesium.BoundingRectangle(105, 105, 18, 18),
color: Cesium.Color.YELLOW,
},
});
}
async function disableDepthTest() {
Sandcastle.declare(disableDepthTest);
viewer.scene.globe.depthTestAgainstTerrain = true;
try {
const worldTerrainProvider = await Cesium.createWorldTerrainAsync();
// Return early in case a different option has been selected in the meantime
if (!viewer.scene.globe.depthTestAgainstTerrain) {
return;
}
viewer.terrainProvider = worldTerrainProvider;
} catch (error) {
window.alert(\`Failed to load terrain. \${error}\`);
}
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(-122.1958, 46.1915),
billboard: {
image: "../images/facility.gif",
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
},
});
viewer.scene.camera.setView({
destination: new Cesium.Cartesian3(
-2357576.243142461,
-3744417.5604860787,
4581807.855903771,
),
orientation: new Cesium.HeadingPitchRoll(
5.9920811504170475,
-0.6032820429886212,
6.28201303164098,
),
});
}
Sandcastle.addToolbarMenu([
{
text: "Add billboard",
onselect: function () {
addBillboard();
Sandcastle.highlight(addBillboard);
},
},
{
text: "Set billboard properties at creation",
onselect: function () {
setBillboardProperties();
Sandcastle.highlight(setBillboardProperties);
},
},
{
text: "Change billboard properties",
onselect: function () {
changeBillboardProperties();
Sandcastle.highlight(changeBillboardProperties);
},
},
{
text: "Size billboard in meters",
onselect: function () {
sizeBillboardInMeters();
Sandcastle.highlight(sizeBillboardInMeters);
},
},
{
text: "Add multiple billboards",
onselect: function () {
addMultipleBillboards();
Sandcastle.highlight(addMultipleBillboards);
},
},
{
text: "Scale by viewer distance",
onselect: function () {
scaleByDistance();
Sandcastle.highlight(scaleByDistance);
},
},
{
text: "Fade by viewer distance",
onselect: function () {
fadeByDistance();
Sandcastle.highlight(fadeByDistance);
},
},
{
text: "Offset by viewer distance",
onselect: function () {
offsetByDistance();
Sandcastle.highlight(offsetByDistance);
},
},
{
text: "Add marker billboards",
onselect: function () {
addMarkerBillboards();
Sandcastle.highlight(addMarkerBillboards);
},
},
{
text: "Disable the depth test when clamped to ground",
onselect: function () {
disableDepthTest();
Sandcastle.highlight(disableDepthTest);
},
},
]);
Sandcastle.reset = async function () {
viewer.camera.flyHome(0);
viewer.entities.removeAll();
viewer.scene.terrainProvider = new Cesium.EllipsoidTerrainProvider();
viewer.scene.globe.depthTestAgainstTerrain = false;
};
`,
},
{
name: "Moon",
isNew: false,
img: "Moon.jpg",
js: `// Set the ellipsoid to be the moon before creating the viewer
Cesium.Ellipsoid.default = Cesium.Ellipsoid.MOON;
const viewer = new Cesium.Viewer("cesiumContainer", {
terrainProvider: false,
baseLayer: false,
timeline: false,
animation: false,
baseLayerPicker: false,
geocoder: false,
shadows: true,
});
const scene = viewer.scene;
// Add Moon Terrain 3D Tiles
try {
const tileset1 = await Cesium.Cesium3DTileset.fromIonAssetId(2684829, {
// Allow clamp to 3D Tiles
enableCollision: true,
});
viewer.scene.primitives.add(tileset1);
} catch (error) {
console.log(\`Error loading tileset: \${error}\`);
}
// Boundary data from https://wms.lroc.asu.edu/lroc/view_rdr/SHAPEFILE_LROC_GLOBAL_MARE
const boundariesResource = await Cesium.IonResource.fromAssetId(2683530);
const boundarySource = await Cesium.GeoJsonDataSource.load(boundariesResource, {
clampToGround: true,
fill: Cesium.Color.fromBytes(26, 106, 113).withAlpha(0.6),
});
boundarySource.show = false;
viewer.dataSources.add(boundarySource);
// Possible Artemis 3 landing locations. data from https://files.actgate.com/lunar/A3_Named_regions.geojson
const artemis3resource = await Cesium.IonResource.fromAssetId(2683531);
const artemis3Source = await Cesium.GeoJsonDataSource.load(artemis3resource, {
clampToGround: true,
fill: Cesium.Color.fromBytes(243, 242, 99).withAlpha(0.6),
});
artemis3Source.show = false;
viewer.dataSources.add(artemis3Source);
// Positions courtesy of https://www.sciencedirect.com/science/article/abs/pii/S0019103516301518?via%3Dihub
const pointsOfInterest = [
{
text: "Apollo 11",
latitude: 0.67416,
longitude: 23.47315,
},
{
text: "Apollo 14",
latitude: -3.64417,
longitude: 342.52135,
},
{
text: "Apollo 15",
latitude: 26.13341,
longitude: 3.6285,
},
{
text: "Lunokhod 1",
latitude: 38.2378,
longitude: -35.0017,
},
{
text: "Lunokhod 2",
latitude: 25.83232,
longitude: 30.92215,
},
];
for (const poi of pointsOfInterest) {
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(poi.longitude, poi.latitude),
label: {
text: poi.text,
font: "14pt Verdana",
outlineColor: Cesium.Color.DARKSLATEGREY,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
pixelOffset: new Cesium.Cartesian2(0, -22),
scaleByDistance: new Cesium.NearFarScalar(1.5e2, 1.0, 1.5e7, 0.5),
translucencyByDistance: new Cesium.NearFarScalar(2.5e7, 1.0, 4.0e7, 0.0),
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
disableDepthTestDistance: new Cesium.CallbackProperty(() => {
return Cesium.Cartesian3.magnitude(scene.camera.positionWC);
}, false),
},
point: {
pixelSize: 10,
color: Cesium.Color.fromBytes(243, 242, 99),
outlineColor: Cesium.Color.fromBytes(219, 218, 111),
outlineWidth: 2,
scaleByDistance: new Cesium.NearFarScalar(1.5e3, 1.0, 4.0e7, 0.1),
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
disableDepthTestDistance: new Cesium.CallbackProperty(() => {
return Cesium.Cartesian3.magnitude(scene.camera.positionWC);
}, false),
},
});
}
const seaOfTranquility = {
destination: new Cesium.Cartesian3(
2134594.9298812235,
1256488.0678322134,
379606.9284823841,
),
orientation: {
direction: new Cesium.Cartesian3(
-0.8518395698371783,
-0.5014189063342804,
-0.1514873843927112,
),
up: new Cesium.Cartesian3(
-0.13054959630640847,
-0.07684549781463353,
0.9884591910493093,
),
},
easingFunction: Cesium.EasingFunction.LINEAR_NONE,
};
const apollo11 = {
destination: new Cesium.Cartesian3(
1609100.311044896,
733266.0643925276,
53608.976740262646,
),
orientation: {
direction: new Cesium.Cartesian3(
-0.41704286323660256,
-0.7222280712427744,
-0.5517806297183315,
),
up: new Cesium.Cartesian3(
0.8621189850799429,
-0.12210806245903304,
-0.49177278965720556,
),
},
easingFunction: Cesium.EasingFunction.LINEAR_NONE,
};
const copernicus = {
destination: new Cesium.Cartesian3(
1613572.8201475781,
-677039.3827805589,
339559.7958496013,
),
orientation: {
direction: new Cesium.Cartesian3(
-0.10007925201262617,
0.8771366500325052,
-0.4696971795597116,
),
up: new Cesium.Cartesian3(
0.9948921707513932,
0.08196514973381885,
-0.058917593354560566,
),
},
easingFunction: Cesium.EasingFunction.LINEAR_NONE,
};
const tycho = {
destination: new Cesium.Cartesian3(
1368413.3560818078,
-166198.00035620513,
-1203576.7397013502,
),
orientation: {
direction: new Cesium.Cartesian3(
-0.8601315724135887,
-0.5073902275496569,
0.05223825345888711,
),
up: new Cesium.Cartesian3(
0.2639103814694499,
-0.5303301783281616,
-0.8056681776681204,
),
},
easingFunction: Cesium.EasingFunction.LINEAR_NONE,
};
const shackleton = {
destination: Cesium.Rectangle.fromBoundingSphere(
new Cesium.BoundingSphere(
new Cesium.Cartesian3(
-17505.087036391753,
38147.40236305639,
-1769721.5748224584,
),
40000.0,
),
),
orientation: {
direction: new Cesium.Cartesian3(
0.2568703591904826,
-0.6405212914728244,
0.7237058060699372,
),
up: new Cesium.Cartesian3(
0.26770932874967773,
-0.6723714327527822,
-0.6901075073627064,
),
},
easingFunction: Cesium.EasingFunction.LINEAR_NONE,
};
const camera = viewer.scene.camera;
const rotationSpeed = Cesium.Math.toRadians(0.1);
const removeRotation = viewer.scene.postRender.addEventListener(
function (scene, time) {
viewer.scene.camera.rotateRight(rotationSpeed);
},
);
const options1 = [
{
text: "Fly to...",
onselect: () => {},
},
{
text: "Sea of Tranquility",
onselect: function () {
removeRotation();
scene.camera.flyTo(seaOfTranquility);
artemis3Source.show = false;
},
},
{
text: "Apollo 11 Landing Site",
onselect: () => {
removeRotation();
scene.camera.flyTo(apollo11);
artemis3Source.show = false;
},
},
{
text: "Copernicus Crater",
onselect: () => {
removeRotation();
scene.camera.flyTo(copernicus);
artemis3Source.show = false;
},
},
{
text: "Tycho Crater",
onselect: () => {
removeRotation();
scene.camera.flyTo(tycho);
artemis3Source.show = false;
},
},
{
text: "Shackleton Crater (South Pole) and Artemis 3 landing options",
onselect: () => {
removeRotation();
scene.camera.flyTo(shackleton);
artemis3Source.show = true;
},
},
];
Sandcastle.addToolbarMenu(options1);
Sandcastle.addToggleButton("Show Mare Boundaries", false, function (checked) {
boundarySource.show = checked;
});
// Spin the moon on first load but disable the spinning upon any input
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(
() => removeRotation(),
Cesium.ScreenSpaceEventType.LEFT_DOWN,
);
handler.setInputAction(
() => removeRotation(),
Cesium.ScreenSpaceEventType.RIGHT_DOWN,
);
handler.setInputAction(
() => removeRotation(),
Cesium.ScreenSpaceEventType.MIDDLE_DOWN,
);
handler.setInputAction(() => removeRotation(), Cesium.ScreenSpaceEventType.WHEEL);
`,
html: `<style>
@import url(../templates/bucket.css);
#toolbar {
background: rgba(42, 42, 42, 0.8);
padding: 4px;
border-radius: 4px;
}
#toolbar input {
vertical-align: middle;
padding-top: 2px;
padding-bottom: 2px;
}
#toolbar .header {
font-weight: bold;
}
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<div id="toolbar"></div>
`,
},
{
name: "Terrain Exaggeration",
isNew: false,
img: "Terrain Exaggeration.jpg",
js: `const viewer = new Cesium.Viewer("cesiumContainer", {
terrain: Cesium.Terrain.fromWorldTerrain(),
});
const scene = viewer.scene;
const globe = scene.globe;
scene.verticalExaggeration = 2.0;
scene.verticalExaggerationRelativeHeight = 2400.0;
scene.camera.setView({
destination: new Cesium.Cartesian3(
336567.0354790703,
5664688.047602498,
2923204.3566963132,
),
orientation: new Cesium.HeadingPitchRoll(
1.2273281382639265,
-0.32239612370237514,
0.0027207329018610338,
),
});
viewer.entities.add({
position: new Cesium.Cartesian3(
314557.3531714575,
5659723.771882165,
2923538.5417330978,
),
ellipsoid: {
radii: new Cesium.Cartesian3(400.0, 400.0, 400.0),
material: Cesium.Color.RED,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
},
});
let visualizeRelativeHeight = true;
function updateMaterial() {
if (visualizeRelativeHeight) {
const height = scene.verticalExaggerationRelativeHeight;
const exaggeration = scene.verticalExaggeration;
const alpha = Math.min(1.0, exaggeration * 0.25);
const layer = {
extendUpwards: true,
extendDownwards: true,
entries: [
{
height: height + 100.0,
color: new Cesium.Color(0.0, 1.0, 0.0, alpha * 0.25),
},
{
height: height + 50.0,
color: new Cesium.Color(1.0, 1.0, 1.0, alpha * 0.5),
},
{
height: height,
color: new Cesium.Color(1.0, 1.0, 1.0, alpha),
},
{
height: height - 50.0,
color: new Cesium.Color(1.0, 1.0, 1.0, alpha * 0.5),
},
{
height: height - 100.0,
color: new Cesium.Color(1.0, 0.0, 0.0, alpha * 0.25),
},
],
};
scene.globe.material = Cesium.createElevationBandMaterial({
scene: scene,
layers: [layer],
});
} else {
scene.globe.material = undefined;
}
}
updateMaterial();
const viewModel = {
exaggeration: scene.verticalExaggeration,
relativeHeight: scene.verticalExaggerationRelativeHeight,
};
function updateExaggeration() {
scene.verticalExaggeration = Number(viewModel.exaggeration);
scene.verticalExaggerationRelativeHeight = Number(viewModel.relativeHeight);
updateMaterial();
}
Cesium.knockout.track(viewModel);
const toolbar = document.getElementById("toolbar");
Cesium.knockout.applyBindings(viewModel, toolbar);
for (const name in viewModel) {
if (viewModel.hasOwnProperty(name)) {
Cesium.knockout.getObservable(viewModel, name).subscribe(updateExaggeration);
}
}
Sandcastle.addToggleButton(
"Visualize Relative Height",
visualizeRelativeHeight,
function (checked) {
visualizeRelativeHeight = checked;
updateMaterial();
},
);
Sandcastle.addToolbarButton("Remove Exaggeration", function () {
viewModel.exaggeration = 1.0;
viewModel.relativeHeight = 0.0;
});
`,
html: `<style>
@import url(../templates/bucket.css);
#toolbar {
background: rgba(42, 42, 42, 0.8);
padding: 4px;
border-radius: 4px;
}
#toolbar input {
vertical-align: middle;
padding-top: 2px;
padding-bottom: 2px;
}
#toolbar .header {
font-weight: bold;
}
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<div id="toolbar">
<table>
<tbody>
<tr>
<td>Exaggeration</td>
<td>
<input type="range" min="0" max="10" step="0.01" data-bind="value: exaggeration, valueUpdate: 'input'">
<input type="text" size="5" data-bind="value: exaggeration">
</td>
</tr>
<tr>
<td>Relative Height</td>
<td>
<input type="range" min="-1000" max="9000" step="1" data-bind="value: relativeHeight, valueUpdate: 'input'">
<input type="text" size="5" data-bind="value: relativeHeight">
</td>
</tr>
</tbody>
</table>
</div>
`,
},
];
export default gallery_demos;

View File

@ -0,0 +1,10 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./reset.css"; // TODO: this may not be needed with itwin-ui
import App from "./App.tsx";
createRoot(document.getElementById("app-container")!).render(
<StrictMode>
<App />
</StrictMode>,
);

View File

@ -0,0 +1,75 @@
/* https://www.joshwcomeau.com/css/custom-css-reset/ */
/* 1. Use a more-intuitive box-sizing model */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* 2. Remove default margin */
* {
margin: 0;
}
/* 3. Enable keyword animations */
html {
interpolate-size: allow-keywords;
}
body {
/* 4. Add accessible line-height */
line-height: 1.5;
/* 5. Improve text rendering */
-webkit-font-smoothing: antialiased;
}
/* 6. Improve media defaults */
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
}
/* 7. Inherit fonts for form controls */
input,
button,
textarea,
select {
font: inherit;
}
/* 8. Avoid text overflows */
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
}
/* 9. Improve line wrapping */
p {
text-wrap: pretty;
}
h1,
h2,
h3,
h4,
h5,
h6 {
text-wrap: balance;
}
/*
10. Create a root stacking context
*/
#root,
#__next {
isolation: isolate;
}

1
packages/sandcastle/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -0,0 +1,38 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
/>
<title>Cesium Demo</title>
<script
type="text/javascript"
src="../Sandcastle-header.js"
vite-ignore
></script>
<script src="__CESIUM_BASE_URL__/Cesium.js" vite-ignore></script>
<link
rel="stylesheet"
href="__CESIUM_BASE_URL__/Widgets/widgets.css"
vite-ignore
/>
<link
rel="stylesheet"
href="__CESIUM_BASE_URL__/Widgets/lighter.css"
vite-ignore
/>
<script>
window.CESIUM_BASE_URL = "__CESIUM_BASE_URL__";
</script>
</head>
<body>
<script
type="text/javascript"
src="../Sandcastle-client.js"
vite-ignore
></script>
</body>
</html>

View File

@ -0,0 +1,26 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src", "vite-env.d.ts"]
}

View File

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

3
packages/sandcastle/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
// <reference types="vite/client" />
const __PAGE_BASE_URL__: string;

View File

@ -0,0 +1,27 @@
import { defineConfig, UserConfig } from "vite";
import baseConfig, { cesiumPathReplace } from "./vite.config.ts";
export default defineConfig(() => {
const cesiumBaseUrl = "/Build/CesiumUnminified";
const config: UserConfig = baseConfig;
// This will make the built files point to routes in the correct nested path
// for the normal local server.js to work correctly
config.base = "/Apps/Sandcastle2";
config.build = {
...config.build,
outDir: "../../Apps/Sandcastle2",
};
config.define = {
...config.define,
__PAGE_BASE_URL__: JSON.stringify("/"),
};
const plugins = config.plugins ?? [];
config.plugins = [...plugins, cesiumPathReplace(cesiumBaseUrl)];
return config;
});

View File

@ -0,0 +1,29 @@
import { defineConfig, UserConfig } from "vite";
import baseConfig, { cesiumPathReplace } from "./vite.config.ts";
export default defineConfig(() => {
const cesiumBaseUrl = `${process.env.BASE_URL}Build/CesiumUnminified`;
console.log("Building Sandcastle with base url:", cesiumBaseUrl);
const config: UserConfig = baseConfig;
// This will make the built files point to routes in the correct nested path
// based on the ci branch path
config.base = `${process.env.BASE_URL}Apps/Sandcastle2`;
config.define = {
...config.define,
__PAGE_BASE_URL__: JSON.stringify(process.env.BASE_URL),
};
config.build = {
...config.build,
outDir: "../../Apps/Sandcastle2",
};
const plugins = config.plugins ?? [];
config.plugins = [...plugins, cesiumPathReplace(cesiumBaseUrl)];
return config;
});

View File

@ -0,0 +1,37 @@
import { defineConfig, UserConfig } from "vite";
import { viteStaticCopy } from "vite-plugin-static-copy";
import baseConfig, { cesiumPathReplace } from "./vite.config.ts";
export default defineConfig(() => {
const config: UserConfig = baseConfig;
const cesiumSource = "../../Build/CesiumUnminified";
const cesiumBaseUrl = "Build/CesiumUnminified";
const copyPlugin = viteStaticCopy({
targets: [
{ src: `${cesiumSource}/ThirdParty`, dest: cesiumBaseUrl },
{ src: `${cesiumSource}/Workers`, dest: cesiumBaseUrl },
{ src: `${cesiumSource}/Assets`, dest: cesiumBaseUrl },
{ src: `${cesiumSource}/Widgets`, dest: cesiumBaseUrl },
{ src: `${cesiumSource}/Cesium.js`, dest: cesiumBaseUrl },
{ src: `../../Source/Cesium.d.ts`, dest: "Source" },
{ src: "../../Apps/SampleData", dest: "Apps" },
{ src: "../../Apps/SampleData", dest: "" },
],
});
config.define = {
...config.define,
__PAGE_BASE_URL__: JSON.stringify("/"),
};
const plugins = config.plugins ?? [];
config.plugins = [
...plugins,
copyPlugin,
cesiumPathReplace(`/${cesiumBaseUrl}`),
];
return config;
});

View File

@ -0,0 +1,45 @@
import { fileURLToPath } from "url";
import react from "@vitejs/plugin-react";
import { PluginOption, UserConfig } from "vite";
// https://vite.dev/config/
const baseConfig: UserConfig = {
plugins: [react()],
server: {
// Given the nature of loading and constructing a CesiumJS Viewer on startup HMR can get memory intensive
// The state of the editor could also be lost when developing if the page refreshes unexpectedly
hmr: false,
},
build: {
// "the outDir may not be inside project root and will not be emptied without this setting
emptyOutDir: true,
rollupOptions: {
input: {
index: fileURLToPath(new URL("./index.html", import.meta.url)),
bucket: fileURLToPath(
new URL("./templates/bucket.html", import.meta.url),
),
},
},
},
};
export default baseConfig;
/**
* Replace path values in
* @param cesiumBaseUrl Path to use for replacement
*/
export const cesiumPathReplace = (cesiumBaseUrl: string): PluginOption => {
return {
name: "custom-cesium-path-plugin",
config(config) {
config.define = {
...config.define,
__CESIUM_BASE_URL__: JSON.stringify(cesiumBaseUrl),
};
},
transformIndexHtml(html) {
return html.replaceAll("__CESIUM_BASE_URL__", `${cesiumBaseUrl}`);
},
};
};