Я пытаюсь создать визуализацию 2D-графика, используя WebGL (regl, если быть более точным). В моей текущей реализации я уже вижу раскладку силы, применяемую к каждому узлу, и это хорошо. Проблема возникает, когда я пытаюсь увеличить масштаб относительно текущей позиции мыши. Согласно моим исследованиям, для достижения такого поведения необходимо применять матричные преобразования в следующем порядке:
translate(nodePosition, mousePosition)
scale(scaleFactor)
translate(nodePosition, -mousePosition)
Таким образом, каждый раз, когда происходит событие wheel
, положение мыши пересчитывается, а матрица преобразования обновляется с учетом новой информации о положении мыши.
Нынешнее поведение странное, и я не могу понять, что не так. Вот живой пример .
Видимо, если я увеличиваю и уменьшаю масштаб, когда мышь зафиксирована в исходном положении, все работает просто отлично. Однако если я переместлю мышь и попытаюсь сфокусироваться на другом узле, произойдет сбой.
Функция для определения положения мыши:
const getMousePosition = (event) => {
var canvas = event.currentTarget
var rect = canvas.getBoundingClientRect()
var x = event.clientX - rect.left
var y = event.clientY - rect.top
var projection = mat3.create()
var pos = vec2.fromValues(x,y)
// this converts the mouse coordinates from
// pixel space to WebGL clipspace
mat3.projection(projection, canvas.clientWidth, canvas.clientHeight)
vec2.transformMat3(pos, pos, projection)
return(pos)
}
Обратный вызов прослушивателя wheel
:
var zoomFactor = 1.0
var mouse = vec2.fromValues(0.0, 0.0)
options.canvas.addEventListener("wheel", (event) => {
event.preventDefault()
mouse = getMousePosition(event)
var direction = event.deltaY < 0 ? 1 : -1
zoomFactor = 1 + direction * 0.1
updateTransform()
})
И функция, которая обновляет преобразование:
var transform = mat3.create()
function updateTransform() {
var negativeMouse = vec2.create()
vec2.negate(negativeMouse, mouse)
mat3.translate(transform, transform, mouse)
mat3.scale(transform, transform, [zoomFactor, zoomFactor])
mat3.translate(transform, transform, negativeMouse)
}
Эта матрица transform
доступна в виде униформы в вершинном шейдере:
precision highp float;
attribute vec2 position;
uniform mat3 transform;
uniform float stageWidth;
uniform float stageHeight;
vec2 normalizeCoords(vec2 position) {
float x = (position[0]+ (stageWidth / 2.0));
float y = (position[1]+ (stageHeight / 2.0));
return vec2(
2.0 * ((x / stageWidth ) - 0.5),
-(2.0 * ((y / stageHeight) - 0.5))
);
}
void main () {
gl_PointSize = 7.0;
vec3 final = transform * vec3(normalizeCoords(position), 1);
gl_Position = vec4(final.xy, 0, 1);
}
где position
- это атрибут, содержащий положение узла.
Что я уже пробовал:
- Я уже пытался изменить порядок преобразований. Результат еще более странный.
- Когда я применяю перевод или масштабирование независимо, все выглядит нормально.
Это мое первое взаимодействие с чем-то, что не является обычным SVG / canvas материалом. Решение, вероятно, очевидно, но я действительно не знаю, где искать. Что я делаю не так?
Обновление 06/11/2018
Я последовал совету @ Johan и внедрил его в live демо . Хотя объяснение было довольно убедительным, результат не совсем то, что я ожидал. Идея инвертировать преобразование, чтобы получить положение мыши в пространстве модели, имеет для меня смысл, но моя интуиция (которая, вероятно, ошибочна) говорит, что применение преобразования непосредственно в пространстве экрана также должно работать. Почему я не могу проецировать и узлы, и мышь в области экрана и применить преобразование прямо там?
Обновление 07/11/2018
Немного потрудившись, я решил выбрать другой подход и адаптировать решение из этого ответа для моего варианта использования. Хотя для зума все работает, как и ожидалось (с добавлением панорамирования), я все же считаю, что есть решения, которые вообще не зависят от d3-зума. Может быть, изолировать матрицу представления и контролировать ее независимо, чтобы достичь ожидаемого поведения, как предлагается в комментариях. Чтобы увидеть мое текущее решение, проверьте мой ответ ниже.