SimplePlane и динамическое изменение геометрии

SimplePlane — специализированный класс библиотеки PixiJS, предназначенный для отображения текстуры на настраиваемой сетке вершин. В отличие от Sprite, который представляет собой прямоугольник из двух треугольников (4 вершины), SimplePlane позволяет задать произвольное количество сегментов по горизонтали и вертикали, формируя более сложную геометрию.

Базовый сценарий применения:

  • деформация изображений;
  • имитация волн, ткани, флагов;
  • перспективные искажения;
  • анимация поверхности;
  • эффекты «живого» интерфейса.

SimplePlane строится поверх меша (Mesh) и использует геометрию типа grid, автоматически создавая вершины и UV-координаты.


Создание SimplePlane

Синтаксис конструктора:

const plane = new PIXI.SimplePlane(texture, verticesX, verticesY);

Параметры:

  • texture — объект PIXI.Texture;
  • verticesX — количество вершин по оси X;
  • verticesY — количество вершин по оси Y.

Пример:

const texture = PIXI.Texture.from('image.jpg');
const plane = new PIXI.SimplePlane(texture, 10, 10);

app.stage.addChild(plane);

В результате создаётся сетка 10×10 вершин, распределённых по прямоугольной области текстуры.


Внутренняя структура геометрии

SimplePlane автоматически создаёт:

  • массив позиций вершин (aVertexPosition);
  • массив UV-координат;
  • индексный буфер треугольников.

Общее количество вершин:

verticesX * verticesY

Каждая вершина содержит:

(x, y)

Данные хранятся в буфере:

plane.geometry.getBuffer('aVertexPosition')

Тип данных — Float32Array.


Принцип построения сетки

Сетка создаётся равномерно:

  • по ширине текстуры;
  • по высоте текстуры.

Если текстура имеет размеры 400×300, а сетка 4×3, то расстояние между вершинами:

stepX = width / (verticesX - 1)
stepY = height / (verticesY - 1)

Каждая вершина рассчитывается как:

x = column * stepX
y = row * stepY

UV-координаты распределяются аналогично в диапазоне [0, 1].


Динамическое изменение геометрии

Основная ценность SimplePlane — возможность изменять положение вершин в реальном времени.

Доступ к буферу вершин

const buffer = plane.geometry.getBuffer('aVertexPosition');
const vertices = buffer.data;

vertices — это Float32Array, содержащий координаты:

[x0, y0, x1, y1, x2, y2, ...]

Изменение координат:

vertices[0] += 10; // смещение первой вершины по X
vertices[1] += 5;  // смещение первой вершины по Y

После изменения обязательно:

buffer.update();

Без вызова upd ate() изменения не будут отправлены в GPU.


Пример: эффект волны

const texture = PIXI.Texture.from('image.jpg');
const plane = new PIXI.SimplePlane(texture, 20, 20);
app.stage.addChild(plane);

app.ticker.add((delta) => {
    const buffer = plane.geometry.getBuffer('aVertexPosition');
    const vertices = buffer.data;

    const time = performance.now() * 0.005;

    for (let i = 0; i < vertices.length; i += 2) {
        const x = vertices[i];
        vertices[i + 1] = Math.sin(x * 0.05 + time) * 10 + (i / 2);
    }

    buffer.update();
});

Принцип работы:

  • координаты X остаются постоянными;
  • координаты Y изменяются синусоидально;
  • создаётся эффект колебания поверхности.

Изменение геометрии при изменении размеров

При масштабировании plane.scale.se t(...) изменяется трансформация объекта, но не геометрия.

Если требуется пересчитать саму сетку под новую текстуру или размеры, используется метод:

plane.texture = newTexture;
plane.width = newWidth;
plane.height = newHeight;

Однако изменение width и height не меняет количество сегментов. Для изменения плотности сетки необходимо создать новый SimplePlane.


Перестройка сетки вручную

Иногда требуется полный контроль над геометрией.

Алгоритм:

  1. Получить текущий буфер.
  2. Пересчитать координаты.
  3. Обновить буфер.

Пример пересчёта всей сетки:

const buffer = plane.geometry.getBuffer('aVertexPosition');
const vertices = buffer.data;

const cols = 10;
const rows = 10;
const width = plane.texture.width;
const height = plane.texture.height;

const stepX = width / (cols - 1);
const stepY = height / (rows - 1);

let index = 0;

for (let y = 0; y < rows; y++) {
    for (let x = 0; x < cols; x++) {
        vertices[index++] = x * stepX;
        vertices[index++] = y * stepY;
    }
}

buffer.update();

Работа с индексами вершин

Номер вершины можно вычислить так:

index = row * verticesX + column

Позиция в массиве:

bufferIndex = index * 2

Пример изменения конкретной вершины:

function moveVertex(col, row, dx, dy) {
    const verticesX = 20;
    const index = row * verticesX + col;
    const bufferIndex = index * 2;

    vertices[bufferIndex] += dx;
    vertices[bufferIndex + 1] += dy;
}

Производительность и оптимизация

1. Минимизация вызовов upd ate()

buffer.update() — дорогостоящая операция. Оптимально:

  • изменять все вершины;
  • вызывать update() один раз за кадр.

2. Размер сетки

Чем больше сегментов:

  • тем плавнее деформация;
  • тем выше нагрузка на GPU.

Рекомендуемые значения:

  • UI-эффекты: 10×10
  • волны/ткань: 20×20
  • сложная деформация: 40×40 и выше

3. Использование ticker

Для анимации применяется app.ticker, который синхронизирован с requestAnimationFrame.


Взаимодействие с пользовательским вводом

Динамическая геометрия может реагировать на курсор:

plane.interactive = true;

plane.on('pointermove', (event) => {
    const pos = event.data.getLocalPosition(plane);

    const buffer = plane.geometry.getBuffer('aVertexPosition');
    const vertices = buffer.data;

    for (let i = 0; i < vertices.length; i += 2) {
        const dx = vertices[i] - pos.x;
        const dy = vertices[i + 1] - pos.y;
        const dist = Math.sqrt(dx * dx + dy * dy);

        if (dist < 50) {
            vertices[i + 1] -= 10;
        }
    }

    buffer.update();
});

Создаётся эффект продавливания поверхности.


Отличие SimplePlane от Mesh

Характеристика SimplePlane Mesh
Автогенерация сетки Да Нет
Ручное задание индексов Нет Да
Простота использования Высокая Средняя
Гибкость Средняя Максимальная

SimplePlane удобен для регулярных сеток. Для произвольной геометрии используется PIXI.Mesh.


Ограничения

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

Расширенные техники

1. Изменение UV-координат

Получение буфера:

const uvBuffer = plane.geometry.getBuffer('aTextureCoord');

Изменяя UV, можно:

  • создавать эффект прокрутки текстуры;
  • реализовать параллакс;
  • делать анимацию воды без смещения вершин.

2. Комбинирование с фильтрами

SimplePlane совместим с фильтрами:

plane.filters = [new PIXI.filters.BlurFilter()];

В сочетании с деформацией создаются:

  • эффекты теплового искажения;
  • подводные сцены;
  • голографические поверхности.

3. Использование шейдеров

Поскольку SimplePlane основан на Mesh, возможно подключение кастомных шейдеров:

const shader = PIXI.Shader.from(vertexSrc, fragmentSrc);
plane.shader = shader;

Это позволяет:

  • перенести расчёт деформации на GPU;
  • реализовать сложные волновые алгоритмы;
  • минимизировать нагрузку на CPU.

Архитектурные рекомендации

  • Логику деформации отделять от рендеринга.
  • Использовать математику, независимую от FPS.
  • Хранить исходные координаты в отдельном массиве для корректных перерасчётов.
  • Не модифицировать вершины накопительным способом без сохранения базового состояния.

Типичный паттерн хранения исходной геометрии

const originalVertices = new Float32Array(vertices.length);
originalVertices.se t(vertices);

app.ticker.add(() => {
    for (let i = 0; i < vertices.length; i += 2) {
        const x = originalVertices[i];
        const y = originalVertices[i + 1];

        vertices[i] = x;
        vertices[i + 1] = y + Math.sin(x * 0.05 + time) * 10;
    }

    buffer.update();
});

Подобный подход предотвращает накопление ошибок и искажений.


Практическое значение

SimplePlane — мощный инструмент для создания интерактивной графики в PixiJS, позволяющий работать напрямую с геометрией без написания низкоуровневых WebGL-структур. Управление вершинами в реальном времени открывает доступ к широкому спектру визуальных эффектов, сочетающих простоту API и высокую производительность GPU.