Пользовательские контроллеры

В основе системы управления в CesiumJS лежит камера, связанная с виртуальной моделью Земли. Камера описывается через позицию в мировых координатах, ориентацию (heading, pitch, roll) и набор ограничений, накладываемых сценой. Управление пользователем не взаимодействует напрямую с WebGL-сценой — оно транслируется в изменения параметров камеры через слой контроллеров.

Контроллер камеры включает:

  • обработку ввода (мышь, клавиатура, сенсорные события);
  • преобразование экранных координат в мировые;
  • применение ограничений (глубина, коллизии, лимиты наклона);
  • инерционные эффекты.

Базовая архитектура построена вокруг ScreenSpaceCameraController, который по умолчанию подключён к Viewer.

const viewer = new Cesium.Viewer("cesiumContainer");
const controller = viewer.scene.screenSpaceCameraController;

Этот объект является точкой расширения для большинства задач кастомизации управления.


Перехват и модификация стандартного ввода

Каждое действие пользователя в CesiumJS проходит через систему событий сцены. Основной механизм — ScreenSpaceEventHandler.

Он позволяет перехватывать:

  • нажатия мыши;
  • перемещение курсора;
  • жесты на тач-устройствах;
  • колесо прокрутки;
  • двойные клики и удержания.
const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);

handler.setInputAction((movement) => {
    const cartesian = viewer.camera.pickEllipsoid(
        movement.position,
        viewer.scene.globe.ellipsoid
    );

    if (cartesian) {
        console.log("Координаты мира:", cartesian);
    }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

Этот слой позволяет внедрять собственную логику до того, как управление попадёт в стандартный контроллер камеры.


Отключение стандартных режимов управления

Кастомизация часто начинается с отключения встроенных взаимодействий. CesiumJS позволяет детально управлять тем, какие типы ввода активны.

const controller = viewer.scene.screenSpaceCameraController;

controller.enableRotate = false;
controller.enableTranslate = false;
controller.enableZoom = false;
controller.enableTilt = false;
controller.enableLook = false;

Такой подход полностью блокирует стандартную навигацию, оставляя разработчику возможность построить собственную модель управления.


Построение собственного контроллера камеры

Создание пользовательского контроллера обычно основано на прямом управлении объектом Camera.

Камера предоставляет методы:

  • moveForward
  • moveBackward
  • moveLeft
  • moveRight
  • lookAt
  • flyTo
  • setView

Простейшая модель WASD-навигации:

const camera = viewer.camera;

const movement = {
    forward: false,
    backward: false,
    left: false,
    right: false
};

document.addEventListener("keydown", (e) => {
    if (e.code === "KeyW") movement.forward = true;
    if (e.code === "KeyS") movement.backward = true;
    if (e.code === "KeyA") movement.left = true;
    if (e.code === "KeyD") movement.right = true;
});

document.addEventListener("keyup", (e) => {
    if (e.code === "KeyW") movement.forward = false;
    if (e.code === "KeyS") movement.backward = false;
    if (e.code === "KeyA") movement.left = false;
    if (e.code === "KeyD") movement.right = false;
});

viewer.scene.preUpdate.addEventListener(() => {
    const speed = 10.0;

    if (movement.forward) camera.moveForward(speed);
    if (movement.backward) camera.moveBackward(speed);
    if (movement.left) camera.moveLeft(speed);
    if (movement.right) camera.moveRight(speed);
});

Использование preUpdate гарантирует синхронизацию с рендер-циклом сцены.


Преобразование экранных координат в мировые

Одной из ключевых задач пользовательских контроллеров является корректное преобразование координат.

CesiumJS работает с несколькими системами:

  • Cartesian3 (мировые координаты);
  • Cartographic (широта/долгота/высота);
  • экранные координаты (пиксели canvas).
const scene = viewer.scene;

handler.setInputAction((movement) => {
    const ray = viewer.camera.getPickRay(movement.position);
    const position = scene.globe.pick(ray, scene);

    if (position) {
        const cartographic = Cesium.Cartographic.fromCartesian(position);
        console.log(
            Cesium.Math.toDegrees(cartographic.longitude),
            Cesium.Math.toDegrees(cartographic.latitude)
        );
    }
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);

Этот механизм лежит в основе всех кастомных навигационных систем, включая selection tools, measurement tools и terrain interaction.


Реализация инерции движения

Плавность управления часто достигается через инерционную модель, где скорость постепенно затухает.

let velocity = new Cesium.Cartesian3(0, 0, 0);

viewer.scene.preUpdate.addEventListener(() => {
    const damping = 0.9;

    camera.position = Cesium.Cartesian3.add(
        camera.position,
        velocity,
        new Cesium.Cartesian3()
    );

    velocity = Cesium.Cartesian3.multiplyByScalar(
        velocity,
        damping,
        new Cesium.Cartesian3()
    );
});

Инерция позволяет отделить момент ввода от реакции камеры, создавая независимую модель движения.


Кастомная обработка вращения камеры

Стандартное вращение камеры основано на изменении heading и pitch. При кастомизации часто требуется заменить эту модель на сферическую или орбитальную.

let isRotating = false;
let lastPosition;

handler.setInputAction((movement) => {
    isRotating = true;
    lastPosition = movement.position;
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);

handler.setInputAction(() => {
    isRotating = false;
}, Cesium.ScreenSpaceEventType.LEFT_UP);

handler.setInputAction((movement) => {
    if (!isRotating) return;

    const deltaX = movement.position.x - lastPosition.x;
    const deltaY = movement.position.y - lastPosition.y;

    camera.heading += deltaX * 0.005;
    camera.pitch += deltaY * 0.005;

    lastPosition = movement.position;
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

Такая модель даёт полный контроль над углами обзора и позволяет внедрять ограничения на вертикальный угол.


Орбитальный контроллер вокруг точки интереса

Частый сценарий — камера вращается вокруг фиксированной цели.

const target = Cesium.Cartesian3.fromDegrees(30, 50, 0);

viewer.scene.preUpdate.addEventListener(() => {
    const radius = 100000.0;

    const offset = new Cesium.Cartesian3(
        radius * Math.cos(camera.heading),
        radius * Math.sin(camera.heading),
        radius * Math.sin(camera.pitch)
    );

    camera.position = Cesium.Cartesian3.add(target, offset, new Cesium.Cartesian3());
    camera.lookAt(target);
});

Подобная схема используется в аналитических и визуализационных интерфейсах, где важно удерживать фокус на объекте.


Обработка жестов на мобильных устройствах

Сенсорное управление в CesiumJS обрабатывается через те же абстракции событий, но с дополнительными типами ввода.

Основные жесты:

  • pinch (масштабирование);
  • two-finger drag (панорамирование);
  • single touch drag (вращение).

Кастомизация жестов обычно требует отключения встроенного поведения:

controller.enableZoom = false;
controller.enableRotate = false;

Далее реализуется собственная интерпретация касаний через ScreenSpaceEventHandler.


Ограничение движения камеры

Контроллеры часто включают ограничения, чтобы предотвратить выход камеры за допустимые границы.

Пример ограничения высоты:

viewer.scene.preUpdate.addEventListener(() => {
    const minHeight = 1000;
    const cartographic = Cesium.Cartographic.fromCartesian(camera.position);

    if (cartographic.height < minHeight) {
        cartographic.height = minHeight;
        camera.position = Cesium.Cartesian3.fromRadians(
            cartographic.longitude,
            cartographic.latitude,
            cartographic.height
        );
    }
});

Ограничения применяются как на позицию, так и на углы обзора.


Построение событийной архитектуры контроллера

Пользовательский контроллер в CesiumJS обычно организуется как слой над стандартным API:

  • слой ввода (events);
  • слой состояния (state machine);
  • слой вычислений (movement logic);
  • слой применения (camera mutation).

Состояния позволяют отделить режимы:

  • свободное перемещение;
  • орбитальный режим;
  • режим выбора;
  • режим измерений.
const modes = {
    FREE: 0,
    ORBIT: 1,
    PICK: 2
};

let currentMode = modes.FREE;

Переключение режимов меняет интерпретацию одинаковых событий ввода.


Интеграция пользовательских контроллеров в сцену

Контроллер должен синхронизироваться с жизненным циклом рендера CesiumJS. Основные точки интеграции:

  • scene.preUpdate — до рендера;
  • scene.postUpdate — после обновления сцены;
  • Clock.onTick — синхронизация с временем.

Использование preUpdate обеспечивает стабильность при изменении состояния камеры:

viewer.scene.preUpdate.addEventListener((scene, time) => {
    // обновление состояния контроллера
});

Переписывание поведения ScreenSpaceCameraController

В более сложных сценариях стандартный контроллер не используется вовсе, а заменяется собственной реализацией логики, где ScreenSpaceCameraController остаётся отключённым.

Такой подход характерен для:

  • симуляторов полёта;
  • GIS-редакторов;
  • аналитических 3D-платформ;
  • кастомных 3D-игровых интерфейсов на CesiumJS.

В этих системах камера становится полностью управляемым объектом без встроенной интерпретации пользовательского ввода.