diff --git a/README.md b/README.md index 01ab14a..034e6ca 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ A collection of helpers and web component for working with [CesiumJS](https://ce * [cesium-first-person-mode](packages/FirstPersonCameraMode): a first person navigation mode that uses the Pointer Lock API * [cesium-sphere-camera](packages/cesium-sphere-camera): a camera mode that allows the user to rotate the camera around a position * [cesium-binoculars](packages/cesium-binoculars): a camera mode that allows the user to use binoculars with the mouse wheel +* [cesium-walk](packages/cesium-walk): a camera mode that allows the user to walk around the scene ## Online demos @@ -22,6 +23,7 @@ npx parcel serve demos/cesium-view-cube.html --open npx parcel serve demos/cesium-first-person-mode.html --open npx parcel serve demos/cesium-sphere-camera.html --open npx parcel serve demos/cesium-binoculars.html --open +npx parcel serve demos/cesium-walk.html --open ``` ## Guide diff --git a/demos/cesium-walk.html b/demos/cesium-walk.html new file mode 100644 index 0000000..0147a6d --- /dev/null +++ b/demos/cesium-walk.html @@ -0,0 +1,38 @@ + + + +
+ + + + + + + + + + + + + diff --git a/packages/cesium-walk/LICENSE b/packages/cesium-walk/LICENSE new file mode 100644 index 0000000..b30ba70 --- /dev/null +++ b/packages/cesium-walk/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2024-present, camptocamp SA +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/cesium-walk/README.md b/packages/cesium-walk/README.md new file mode 100644 index 0000000..db7aac5 --- /dev/null +++ b/packages/cesium-walk/README.md @@ -0,0 +1,27 @@ +# walk mode for CesiumJS + +Control the camera in CesiumJS as if you were walking around. +Use `w`, `a`, `s` and `d` keys to move. + +## Demo + +[Demo](https://geoblocks.github.io/cesium-helpers/cesium-walk.html) + +## Installation + +```bash +npm i --save @geoblocks/cesium-walk +``` + +### Usage + +```js +import {Viewer} from 'cesium'; +import WalkCameraMode from '@geoblocks/cesium-walk'; + +const viewer = new Viewer(...); +const mode = new WalkCameraMode(viewer.scene); + +// ... +mode.active = true; +``` diff --git a/packages/cesium-walk/cesium-walk.js b/packages/cesium-walk/cesium-walk.js new file mode 100644 index 0000000..2038308 --- /dev/null +++ b/packages/cesium-walk/cesium-walk.js @@ -0,0 +1,155 @@ +// FIXME: make key bindings configurable, add arrow keys + +export default class CesiumWalk { + /** + * @param {import('cesium').Viewer} viewer + * @param {number} [speed=1.6] Walk speed in meters per second. + * @param {number} [height=2.0] Height of the camera above the terrain. + */ + constructor(viewer, speed = 1.6, height = 2.0) { + /** + * @type {import('cesium').Viewer} + */ + this.viewer = viewer; + + /** + * @type {number} + */ + this.speed = speed; + + /** + * @type {number} + */ + this.height = height; + + /** + * @type {boolean} + */ + this.active_ = false; + + /** + * @type {number} + */ + this.lastTick; + + /** + * @type {{forward: boolean, left: boolean, backward: boolean, right: boolean}} + */ + this.buttons = { + forward: false, + left: false, + backward: false, + right: false, + }; + + /** + * @type {function(KeyboardEvent): void} + */ + this.handleKeyUpDownFunction = this.handleKeyUpDown.bind(this); + + /** + * @type {function(): void} + */ + this.handleTickFunction = this.handleTick.bind(this); + } + + get active() { + return this.active_; + } + + set active(active) { + this.active_ = active; + if (this.active_) { + document.addEventListener("keydown", this.handleKeyUpDownFunction); + document.addEventListener("keyup", this.handleKeyUpDownFunction); + this.viewer.clock.onTick.addEventListener(this.handleTickFunction); + this.clampCameraToTerrain(); + } else { + document.removeEventListener("keydown", this.handleKeyUpDownFunction); + document.removeEventListener("keyup", this.handleKeyUpDownFunction); + this.viewer.clock.onTick.removeEventListener(this.handleTickFunction); + } + this.enableNavigation(!this.active_); + } + + /** + * @param {KeyboardEvent} event + */ + handleKeyUpDown(event) { + const pressed = event.type === "keydown"; + switch (event.key) { + case "w": + this.buttons.forward = pressed; + break; + case "a": + this.buttons.left = pressed; + break; + case "s": + this.buttons.backward = pressed; + break; + case "d": + this.buttons.right = pressed; + break; + } + } + + handleTick() { + const timestamp = performance.now(); + if (this.needsTick()) { + const camera = this.viewer.camera; + const deltaTime = timestamp - this.lastTick; + + const distance = (this.speed * deltaTime) / 1000; + + if (this.buttons.forward) { + camera.moveForward(distance); + } + if (this.buttons.left) { + camera.moveLeft(distance); + } + if (this.buttons.backward) { + camera.moveBackward(distance); + } + if (this.buttons.right) { + camera.moveRight(distance); + } + this.clampCameraToTerrain(); + } + + this.lastTick = timestamp; + } + + clampCameraToTerrain() { + const camera = this.viewer.camera; + const terrainHeight = this.viewer.scene.globe.getHeight( + camera.positionCartographic + ); + const cameraHeight = camera.positionCartographic.height; + + camera.moveDown(cameraHeight - terrainHeight - this.height); + } + + /** + * @return {boolean} + */ + needsTick() { + return ( + this.buttons.forward || + this.buttons.left || + this.buttons.backward || + this.buttons.right + ); + } + + /** + * Enable or disable cesium navigation interactions. + * @param {boolean} enable + */ + enableNavigation(enable) { + const controller = this.viewer.scene.screenSpaceCameraController; + controller.enableTranslate = enable; + controller.enableZoom = enable; + controller.enableRotate = enable; + controller.enableTilt = enable; + } +} diff --git a/packages/cesium-walk/package.json b/packages/cesium-walk/package.json new file mode 100644 index 0000000..0db4c2e --- /dev/null +++ b/packages/cesium-walk/package.json @@ -0,0 +1,15 @@ +{ + "name": "@geoblocks/cesium-walk", + "version": "0.0.0", + "license": "BSD-3-Clause", + "repository": { + "type": "git", + "url": "https://github.com/geoblocks/cesium-helpers/", + "directory": "packages/cesium-walk" + }, + "main": "cesium-walk.js", + "module": "cesium-walk.js", + "publishConfig": { + "access": "public" + } +}