Обработка событий мыши

Система обработки событий мыши в CesiumJS построена вокруг абстракции экранных событий, которые преобразуются в взаимодействие с 3D-сценой, примитивами и сущностями. Основной задачей этой системы является связывание координат указателя на canvas с объектами в виртуальном глобусе и сцене.


CesiumJS работает не с DOM-событиями напрямую, а с собственной системой ScreenSpaceEventHandler, которая интерпретирует действия мыши и сенсора в контексте WebGL-контента.

Ключевой принцип:

экранные координаты → луч в сцене → пересечение с объектами

Каждое событие мыши преобразуется в:

  • позицию на canvas (x, y)
  • тип взаимодействия (click, move, down, up, double click)
  • контекст сцены (камера, объекты, террейн)

ScreenSpaceEventHandler

Основной объект для обработки мыши:

const handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);

Он привязывается к HTML canvas, на котором рендерится сцена.

Регистрация события выполняется через:

handler.setInputAction(function (event) {
    console.log(event.position);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

Внутренняя архитектура:

  • обработка DOM событий
  • нормализация координат
  • сопоставление с типом ScreenSpaceEventType
  • передача в callback

Типы событий мыши

CesiumJS поддерживает набор стандартных событий:

Клики

  • LEFT_CLICK
  • RIGHT_CLICK
  • MIDDLE_CLICK
Cesium.ScreenSpaceEventType.LEFT_CLICK
Cesium.ScreenSpaceEventType.RIGHT_CLICK

Движение мыши

  • MOUSE_MOVE

Используется для hover-интеракций, подсветки объектов и динамического выбора.

handler.setInputAction(function (movement) {
    console.log(movement.endPosition);
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

Нажатие и отпускание

  • LEFT_DOWN
  • LEFT_UP
  • RIGHT_DOWN
  • RIGHT_UP

Эти события применяются для реализации drag-and-drop логики.


Дополнительные события

  • WHEEL — прокрутка колеса мыши
handler.setInputAction(function (delta) {
    console.log(delta);
}, Cesium.ScreenSpaceEventType.WHEEL);

Координаты и структура event

Каждое событие содержит экранные координаты:

{
    position: Cesium.Cartesian2,
    startPosition: Cesium.Cartesian2,
    endPosition: Cesium.Cartesian2
}

Cartesian2

x, y // координаты на canvas

Важно учитывать:

  • начало координат в левом верхнем углу canvas
  • Y увеличивается вниз

Преобразование координат в 3D

Для работы с реальным миром требуется преобразование экранных координат в географические.

Ray picking через camera

const ray = viewer.camera.getPickRay(event.position);
const position = viewer.scene.globe.pick(ray, viewer.scene);

Результат — Cartesian3 (мировые координаты).


Преобразование в географические координаты

const cartographic = Cesium.Cartographic.fromCartesian(position);

const longitude = Cesium.Math.toDegrees(cartographic.longitude);
const latitude = Cesium.Math.toDegrees(cartographic.latitude);
const height = cartographic.height;

Picking объектов сцены

CesiumJS предоставляет механизм выбора объектов под курсором.

scene.pick

const pickedObject = viewer.scene.pick(event.position);

Возвращает:

  • Entity
  • Primitive
  • Cesium3DTileFeature

Проверка результата

if (Cesium.defined(pickedObject)) {
    console.log(pickedObject.id);
}

drillPick (множественный выбор)

const objects = viewer.scene.drillPick(event.position);

Используется при перекрывающихся объектах.


Работа с Entity

Entity API тесно интегрирован с системой событий.

Пример выбора Entity по клику

handler.setInputAction(function (event) {
    const picked = viewer.scene.pick(event.position);

    if (Cesium.defined(picked) && picked.id) {
        console.log(picked.id);
    }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

Подсветка при наведении

let lastPicked;

handler.setInputAction(function (movement) {
    const picked = viewer.scene.pick(movement.endPosition);

    if (Cesium.defined(lastPicked)) {
        lastPicked.color = Cesium.Color.WHITE;
    }

    if (Cesium.defined(picked) && picked.id) {
        lastPicked = picked.id;
        lastPicked.color = Cesium.Color.YELLOW;
    }
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

Drag-интеракции

Реализация перетаскивания строится на комбинации событий:

  • LEFT_DOWN
  • MOUSE_MOVE
  • LEFT_UP

Базовый паттерн

let isDragging = false;

handler.setInputAction(function (event) {
    isDragging = true;
}, Cesium.ScreenSpaceEventType.LEFT_DOWN);

handler.setInputAction(function (event) {
    if (!isDragging) return;

    const ray = viewer.camera.getPickRay(event.endPosition);
    const position = viewer.scene.globe.pick(ray, viewer.scene);

    if (Cesium.defined(position)) {
        console.log("drag position", position);
    }
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

handler.setInputAction(function () {
    isDragging = false;
}, Cesium.ScreenSpaceEventType.LEFT_UP);

Работа с камерой при событиях

Часто события мыши связаны с управлением камерой.

Отключение стандартного поведения

viewer.scene.screenSpaceCameraController.enableRotate = false;
viewer.scene.screenSpaceCameraController.enableTranslate = false;
viewer.scene.screenSpaceCameraController.enableZoom = false;

Это необходимо при реализации кастомных инструментов взаимодействия.


Координатные системы в обработке событий

В CesiumJS участвуют три основных системы:

  • Canvas coordinates (Cartesian2)
  • World coordinates (Cartesian3)
  • Geodetic coordinates (Cartographic)

Цепочка преобразования:

Screen (x, y)
   ↓
Pick ray
   ↓
Cartesian3
   ↓
Cartographic (lon, lat, height)

Обработка событий над 3D Tiles

3D Tiles требуют особого подхода:

const feature = viewer.scene.pick(event.position);

if (feature instanceof Cesium.Cesium3DTileFeature) {
    const name = feature.getProperty("name");
    console.log(name);
}

Обработка пересечений с terrain

Для рельефа используется:

const ray = viewer.camera.getPickRay(event.position);
const position = viewer.scene.globe.pick(ray, viewer.scene);

Если включен terrainProvider, вычисление учитывает высоту поверхности.


Ограничения и особенности производительности

Обработка событий мыши в CesiumJS может становиться узким местом при высокой частоте MOUSE_MOVE.

Типичные проблемы:

  • частый вызов scene.pick
  • пересчет лучей камеры
  • обновление Entity в каждом кадре

Оптимизация:

  • throttling обработчиков
  • кэширование результатов pick
  • использование requestAnimationFrame

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

let lastTime = 0;

handler.setInputAction(function (movement) {
    const now = performance.now();
    if (now - lastTime < 50) return;
    lastTime = now;

    viewer.scene.pick(movement.endPosition);
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

Обработка событий на уровне canvas

Низкоуровневый доступ возможен через DOM:

viewer.canvas.addEventListener("contextmenu", function (e) {
    e.preventDefault();
});

Однако такой подход не учитывает сцену Cesium и применяется только для вспомогательной логики.


Совмещение событий с инструментами рисования

При создании редакторов геометрии взаимодействие мыши комбинируется с Entity API:

  • добавление точек по клику
  • завершение рисования по двойному клику
  • редактирование вершин через drag

Пример добавления точек

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

    if (Cesium.defined(position)) {
        viewer.entities.add({
            position: position,
            point: {
                pixelSize: 10,
                color: Cesium.Color.RED
            }
        });
    }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

Управление жизненным циклом обработчиков

Обработчики требуют явного удаления:

handler.destroy();

или:

handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);

Игнорирование этого приводит к накоплению обработчиков и утечкам памяти.


Приоритеты обработки событий

CesiumJS не использует стандартную систему bubbling DOM. Вместо этого применяется:

  • последовательная обработка ScreenSpaceEventHandler
  • приоритет определяется порядком регистрации
  • конфликтующие события обрабатываются последним зарегистрированным handler’ом

Обработка multi-touch (сенсорные события)

На мобильных устройствах ScreenSpaceEventHandler интерпретирует:

  • pinch zoom
  • pan
  • tap

Эти события мапятся на те же ScreenSpaceEventType, но с иной семантикой входных данных.


Взаимодействие с производительностью рендера

События мыши тесно связаны с render loop Cesium:

  • каждый pick вызывает обращение к сцене
  • изменения Entity триггерят перерисовку
  • MOUSE_MOVE может приводить к постоянному render throttle bypass

Поэтому обработка событий часто проектируется как:

  • минимальная логика в callback
  • перенос вычислений в отдельный слой состояния