Масштабируем объекты

Следующим на очереди будет масштабирование (то есть растягивание и сжатие) объекта сцены без использования матриц. При этом мы не собираемся терять функционал перетягивания объектов. Пусть масштабирование будет происходить с зажатой клавишей Shift.

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

  • X’ = X * sX + tX
  • Y’ = Y * sY + tY

Здесь мы сначала умножаем координату на коэффициент (префикс s — scale, масштабировать), а затем добавляем смещение (t — translate, переносить). Так мы получаем итоговые координаты. Шейдер, который отражает эту формулу, имеет следующий вид:


    attribute vec3 aVertexPosition;

    uniform vec2 uTranslation;
    uniform vec2 uScale;

    void main(void) {
        vec3 scaled = vec3(aVertexPosition.xy * uScale, aVertexPosition.z);
        gl_Position = vec4(scaled, 1.0) + vec4(uTranslation, 0.0, 0.0);
    }

Пока для простоты мы работаем в двумерном пространстве, поэтому масштабирование происходит только по оси X и Y. Координату Z мы оставляем без изменений. Причем когда один из коэффициентов принимает отрицательное значение, объект зеркально отражается относительно соответствующей оси. В примере с переносом я взял симметричную относительно Y фигуру, поэтому переворот объекта не был заметен. Для наглядности в этот раз была использована буква Б, по которой мы сразу можем определить, перевернута ли она по какой-нибудь оси или нет.

Если значения uniform-переменных не заданы, они принимают значение 0. Для переноса это было именно то, что нам было нужно, чтобы изначально никакого переноса не было. А в случае масштабирования наш объект сожмется в невидимую точку. Поэтому необходимо установить начальные коэффициенты [1, 1] в функции initShaders:


        shaderProgram.scaleUniform = gl.getUniformLocation(shaderProgram, "uScale");
        
        gl.uniform2fv(shaderProgram.scaleUniform, [1, 1]);

Переходим от шейдеров к обработке событий. Для начала добавим несколько глобальных переменных:


    var scale = [1, 1];
    
    var currentEvent;
    var eventType = {
        drag: 1,
        scale: 2
    };

scale — текущие коэффициенты масштабирования, в currentEvent мы отмечаем текущее действие (перетягиваем мы сейчас или масштабируем), перечень которых описан в eventType.

В функции onMouseDown мы теперь смотрим, зажат ли Shift при нажатии кнопки мыши, и устанавливаем соответствующее событие:


    function onMouseDown(evt){
        if (evt.shiftKey) {
            currentEvent = eventType.scale;
        } else {
            currentEvent = eventType.drag;
        }
        dragStartX = evt.clientX;
        dragStartY = evt.clientY;
        dragOffset = [0, 0];
        mousePressed = true;
    }

Функция onMouseMove изменилась более значительно, поэтому в ней я не буду выделять новый код:


    function onMouseMove(evt) {
        if (mousePressed) {
            var diffX = evt.clientX - dragStartX;
            var diffY = dragStartY - evt.clientY;
            if (currentEvent === eventType.drag) {
                dragOffset = [diffX * 2 / 500, diffY * 2 / 500];
                var finalTranslation = [translation[0] + dragOffset[0], translation[1] + dragOffset[1]]
                gl.uniform2fv(shaderProgram.translationUniform, finalTranslation);
            } else if (currentEvent === eventType.scale) {
                dragOffset = [diffX / 100, diffY / 100];
                var finalScale = [scale[0] + dragOffset[0], scale[1] + dragOffset[1]]
                gl.uniform2fv(shaderProgram.scaleUniform, finalScale);
            }
        }
    }

При рассчете dragOffset мы делим на 100, этот коэффициент я подобрал простым подбором. И, наконец, функция onMouseUp:


    function onMouseUp(evt){
        if (mousePressed) {
            if (currentEvent === eventType.drag) {
                translation = [translation[0] + dragOffset[0], translation[1] + dragOffset[1]];
            } else if (currentEvent === eventType.scale) {
                scale = [scale[0] + dragOffset[0], scale[1] + dragOffset[1]]
            }
            mousePressed = false;
        }
    }

…где мы обновляем значение переноса или масштабирования в зависимости от текущего события.

Вот и все! Теперь мы можем и перемещать объект, и масштабировать его. Нам осталось лишь добавить повороты, чем мы и займемся в следующий раз. Исходный код можно скачать на GitHub, демонстрация онлайн расположена здесь, а результат работы можно посмотреть ниже (оригинал слева, а растянутое и смещенное изображение справа):

Масштабируем объекты
Метки:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *