События

Простейшие манипуляции в трехмерном пространстве

2016-08-02

SDK Blend4Web содержит большое количество демонстрационных проектов, благодаря которым можно многому научиться. Однако начинающим разработчикам не так просто разобраться в хитросплетениях кода и найти ответы на возникающие вопросы. Подчас требуется немало времени для уяснения базовых принципов. Надеюсь, что мой накопленный опыт поможет новичкам быстрее разрабатывать собственные приложения.

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

Начнем с базиса. С точки зрения Blend4Web объекты в сцене подразделяются на два типа: динамические и статические. По умолчанию, в сцене любой объект является статическим. И это сделано не зря.

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

Динамические же объекты полностью подчиняются вашей воле, но предварительно их нужно подготовить. Это выполняется на стадии создания сцены в Blender. Достаточно выделить объект и включить опцию Force Dynamic Object, которая располагается в панели Object.

Просто включите эту опцию и объект «к вашим услугам»

Blend4Web предлагает большое количество функций, которые объединены в тематические группы-модули. Для манипуляции объектами в трехмерном пространстве предназначены модули: transform и physics.

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

Работа с функциями API подразумевает первоначальное подключение необходимого модуля в скрипт, где планируется его использование. Это делается так:

var m_trans = require("transform");
Теперь, используя созданную переменную m_trans, можно обращаться к методам transform.

Перемещение объекта

Начнем с самого легкого. Встречайте эффект «Телепортация»!

Прыг-скок. Портал в действии

Сама процедура мгновенного переноса объекта в другое место выглядит до безобразия просто. Берем новые координаты и назначаем их объекту. Для этого имеется специальная функция set_translation_v(). Но, сначала, нужно сделать некоторые действия: найти объект в сцене и создать вектор перемещения.

Удобнее всего искать объект по его имени. Для этого понадобится функция get_object_by_name(), которая находится в модуле scenes.

С учетом всего сказанного, процедура переноса объекта может выглядеть так:

// import modules
var m_trans = require("transform");
var m_scene = require("scenes");

// move
var obj = m_scene.get_object_by_name("My object");
var trans = [0,1,0];
m_trans.set_translation_v(obj, trans);

Выполнение этого кода приведет к мгновенному перемещению объекта в новые координаты указанные в переменной trans. Если вначале его координаты [-1,1,0], то он переместится в направлении оси X, то есть направо. Здесь важно помнить об отличиях координатных осей в Blender и Blend4Web. Они подробно описаны здесь.

Простое движение

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

Идеально для этого подходят сенсоры и манифолды Blend4Web. Под этими страшными словами скрывается чрезвычайно мощная система событий, созданная разработчиками движка. Но в этом уроке мы ограничимся только готовой конструкцией с использованием elapsed sensor.

// import modules
var m_trans  = require("transform");
var m_scene  = require("scenes");
var m_ctl    = require("controls");

var obj = m_scene.get_object_by_name("My object");

//create sensor
var elapsed_sensor = m_ctl.create_elapsed_sensor();
m_ctl.create_sensor_manifold(obj, "MAIN", m_ctl.CT_CONTINUOUS, [elapsed_sensor], null, main_cb);

function main_cb (obj, id) {
    var elapsed = m_ctl.get_sensor_value(obj, id, 0);

    //move
}

В этом коде постоянно генерируется событие main_cb после каждого рендера кадра. Эта заготовка универсальная и отлично подходит для циклично выполняемых задач. Берите её на заметку!

Для перемещения объекта по одной из координатной оси с неизменной скоростью можно добавить в функцию main_cb следующий код:

//variables initialization
var speed = 0.1;
var obj = m_scene.get_object_by_name("My object");

function main_cb (obj, id) {
    var elapsed = m_ctl.get_sensor_value(obj, id, 0); 

    //move    
    m_trans.move_local(obj, speed*elapsed, 0, 0);	
}

В данной процедуре происходит изменение значения одной из оси в соответствие с шагом speed и корректирующей величины elapsed. Попробуйте этот код в действии и увидите, как объект покорно ползет по экрану с неизменной скоростью. Обратите внимание, что функция move_local() работает в локальной системе координат.

Эй, постой! Я не успеваю…

А давайте-ка теперь заставим объекты играть в «Догонялки» и немного вспомним школьную алгебру. Вектор направления к объекту равен… мммм… Вобщем, вот готовые формулы:

delta_trans = (target - position) * speed
position = position + delta_trans

Нам понадобятся выполнять различные математические вычисления для векторов, поэтому список модулей становится уже внушительней:

// import modules
var m_trans = require("transform");
var m_scene = require("scenes");
var m_ctl   = require("controls");
var m_vec3  = require("vec3");

В модуле transform имеется функция get_translation(), которая полярная по действию set_translation_v(). Она возвращает координаты объекта. Добавим еще несколько математических вычислений, и указанные формулы превратятся в рабочий код:

//variables initialization
var delta_trans = new Float32Array(3); 
var speed = 0.7; 
var obj = m_scene.get_object_by_name("My object"); 
var obj_pos = new Float32Array(3);
var target = m_scene.get_object_by_name("Target");
var target_pos = new Float32Array(3);

function main_cb (obj, id) {
    var elapsed = m_ctl.get_sensor_value(obj, id, 0); 

    //move
    m_trans.get_translation(obj, obj_pos); 
    m_trans.get_translation(target, target_pos); 
    m_vec3.subtract(target_pos, obj_pos, delta_trans); 
    m_vec3.normalize(delta_trans,delta_trans); 
    m_vec3.scale(delta_trans, elapsed * speed, delta_trans);
    m_vec3.add(obj_pos, delta_trans, obj_pos); 
    m_trans.set_translation_v(obj, obj_pos); 
}

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

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

Вращение и масштабирование

Когда разговор заходит о вращении объекта, многие начинают теряться. Эйлеровые углы, градусы, радианы и даже... о, Боже мой... кватернионы! Но это урок для начинающих, поэтому рассмотрим самый простой и эффективный способ вращения.

Начну с того, что все функции поворота принимают значения в радианах. Однако я человек простой и ещё со школы люблю привычные угловые градусы. Соответственно, требуется выполнить конвертацию данных в нужную систему счисления. К счастью, в наборе модулей API Blend4Web имеется модуль util, а в его составе столь необходимые конверторы. Рассмотрим всё это дело на конкретной задаче.

Допустим, нужно повернуть объект на 45 градусов по оси X. Есть функция rotate_x_local(), которая чем-то напоминает ранее рассмотренную move_local(). Она больше применяется для постоянного вращения в составе какого-либо цикла, но её прекрасно можно использовать для единовременного поворота объекта на нужный угол и вот пример кода:

var angle_deg = 45;
var angle_rad = m_util.deg_to_rad(angle_deg);
var obj = m_scene.get_object_by_name("My object");

m_trans.rotate_x_local(obj, angle_rad); 

Я думаю, вы уже догадались, что для поворота по другим осям имеются функции-близнецы: rotate_y_local() и rotate_z_local(). И конечно, ничто вам не мешает использовать другие способы, даже приснопамятные кватернионы.

Осталось рассмотреть последнее действие из разряда манипуляций в 3D пространстве, а именно масштабирование. Для этого имеются функции: get_scale() и set_scale(). Так, например, можно удвоить размер нужного объекта:

var obj = m_scene.get_object_by_name("My object");
var scale = m_trans.get_scale(obj);
m_trans.set_scale(obj, scale*2);

Учтите, что изменение масштаба возможно только пропорционально по всем осям.

Вот и все. Вы уже достаточно знаете, чтобы свободно управлять объектами в сцене. До встречи!

Комментарии
28 июл. 2017 15:22
Добрый день,

Статья хорошая. Использовал ее в своем обучении. Но у меня есть вопрос. Где можно прочитать про все углы, измерения про вращения? Всякие кватернионы, радианы, градусы и то, что в Blender.
А как получить градус поворота объекта? Разницу между двумя значениями по кватернионам? А еще выполнить плавное вращения объекта по кватернионам. Углы в начале не 60 градусов, как в документации о Кватернионах, а случайные.

Спасибо.
01 авг. 2017 12:13
Здравствуйте,

Где можно прочитать про все углы, измерения про вращения? Всякие кватернионы, радианы, градусы и то, что в Blender.
- сейчас информация об этом есть, пожалуй, только в разделе про кватернионы, на который вы ссылаетесь.
Вообще, в движке здесь 2 принципа: для вращений использовать кватернионы, а углы представлять в радианах - API также на этом завязано. Если вращение нужно получить в другой форме, то можно использовать специальные методы для преобразования, например: util.quat_to_euler и util.euler_to_quat для перевода кватернионов в углы Эйлера и наоборот, также util.rad_to_deg и util.deg_to_rad для перевода из радианов в градусы.
А как получить градус поворота объекта?
Вот как-то так:
var m_trans = require("transform");
var m_util = require("util");

var quat = m_trans.get_rotation(my_obj);
var euler = m_util.quat_to_euler(quat);
var x = m_util.rad_to_deg(euler[0]);
var y = m_util.rad_to_deg(euler[1]);
var z = m_util.rad_to_deg(euler[2]);


Разницу между двумя значениями по кватернионам?
Тут надо перемножить кватернион на обратный второго:
var m_quat = require("quat");

// quat1 - quat2
var quat_diff = m_quat.mul(quat1, m_quat.invert(quat2, quat2));


А еще выполнить плавное вращения объекта по кватернионам.
Есть функция quat.slerp для интерполяции кватернионов - её можно использовать анимируя входной параметр t от 0 до 1:
var m_time = require("time");
var m_quat = require("quat");

// в течение 5 сек.
m_time.animate(0, 1, 5000, function(value) {
    m_quat.slerp(quat1, quat2, value, res_quat);
    m_trans.set_rotation_v(my_obj, res_quat);
});


Вообще для проведения различных преобразований есть много полезных методов в модулях vec3/vec4/mat3/mat4/quat - это по сути популярная библиотека glMatrix.
01 авг. 2017 22:18
Вот это ответ

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