Экспорт планировок из Planner 5D

в 7:24, , рубрики: Анимация и 3D графика, домашняя автоматизация, Программирование, метки:

Недавно на Хабре был представлен сервис создания планировок квартир Planner 5D.
Вашему покорному слуге как раз нужна была 3D-модель квартиры для визуализации состояния домашней автоматики — отображать, где включен свет, а где выключен, и новый сервис пришёлся как раз кстати.

Экспорт планировок из Planner 5D

Оставалась нерешённой одна задача — как импортировать данные из Planner 5D в собственную программу. Для этого нужно было разобраться, как извлечь данные из сервиса, разобрать формат данных и сделать инструмент для проверки, правильно ли визуализируется модель.

Импорт данных

Было понятно, что для рендеринга сцены в WebGL, используемого в планировщике, в браузер должны отдельно поступать данные моделей объектов, текстуры и информация о размещении объектов на сцене. Через сетевой отладчик Chrome искомые данные были быстро найдены. Оказалось, что приложение получает данные через JSON API по адресу planner5d.com/api/project/<projectId>. Данные отдаются без аутентификации, что немного упростило задачу.

Внутри JSON перечисляется список проектов (видимо, заложено, что в одном файле могут быть сразу несколько проектов). У каждого проекта есть имя, задаваемое пользователем, и данные сцены проекта, представляющие собой ещё один JSON, завёрнутый в строку.

Для извлечения JSON-пакета и разворачивания его в отдельные файлы проектов была написана утилита fetch.pl, которая и делает указанные операции:

$ ./fetch.pl d24411aad3135fc8fef7c907bbd18af8
Writing myApartment.json...

Теперь, когда проекты извлечены, можно посмотреть в их внутренности и разобраться, как они работают. Оказалось, что структура данных представляет собой дерево объектов на сцене. На самом верху находится объект Project, в него вложены этажи (Floor), на каждом этаже есть несколько комнат (Room), в комнатах есть стены (Wall), окна (Window), двери (Door) и произвольные mesh-объекты (Ns).

Назначение полей объектов вполне понятно из из названий: x,y,z — координаты, Sx,Sy,Sz — масштаб модели, a — угол поворота, Fx,Fy — отражение по горизонтали и вертикали. Преобразования смещения и вращения, применённые к предку, также должны применяться и ко всем дочерним объектам. Преобразования масштаба и отражения применяются только к самой модели конкретного объекта и не влияют на его дочек.

С текстурами проблем не возникло совсем. Если у объекта есть поле texture, то надо сходить по адресу planner5d.com/m/t/<filename>.jpg и забрать файл.

Более хитро оказались упакованы модели объектов. Если у mesh-объекта есть поле id, то его модель можно скачать с адреса planner5d.com/m/i/<name>.png. Технически это монохромный PNG-файл, который считывается попиксельно, и затем эти данные интерпретируются как JSON. В полученном JSON-файле хранятся списки вершин, нормалей, UV-координаты текстур и прочие обычные для 3D вещи. Судя по заголовку файлов моделей, они были экспортированы из Blender.

После того, как с импортом было покончено, результатом стала Perl-библиотека для выполнения всех операций. Чтобы скачать текстуру (будет взята из кэша, если она уже скачана) и получить имя файла в локальной файловой системе, где она находится:

use Planner5D::Downloader;
my $downloader = Planner5D::Downloader->new;
my $filepath = $downloader->getTexturePath('stone_4.jpg');

Чтобы аналогичным образом скачать модель объекта и распаковать его в JSON:

use Planner5D::Downloader;
my $downloader = Planner5D::Downloader->new;
my $filepath = $downloader->getMeshPath('220');

Визуализация

Для того, чтобы убедиться, что все данные интерпретированы правильно, необходимо было собрать сцену заново из полученных данных и отрендерить её. Для этих целей выбран open-source рендерер POV-Ray, являющийся полноценным рейтрейсером и позволяющий получать картинки с фотореалистическим качеством. Его входной формат — текстовый файл на специальном языке, описывающем сцену. Для того, чтобы сформировать этот файл, нам понадобится новый модуль — Planner5D::Povray, который будет обучен экспортировать дерево сцены в формат pov.Первым делом нам понадобится конвертировать модели объектов в формат pov и сохранить их в отдельных файлах, которые потом будет подключаться через #include к основному файлу сцены:

use Planner5D::Povray;
my $povray = Planner5D::Povray->new;
my $filepath = $povray->getPovMeshPath('220');

Файлы с моделями также кешируются. Если файла mesh-220.pov ещё не существует, то он будет сформирован из 220.json, скачанного на предыдущем этапе. Если файла 220.json также нет, то он будет скачан автоматически. После обращения к getPovMeshPath вы можете быть уверены, что нужный pov-файл сформирован лежит у вас на диске.

После того, как нужные модели у нас сформированы, нам следует построить pov-файл для всей сцены. Сначала сцену надо считать из её JSON-описания и воссоздать в памяти структуру объектов. Для этого создадим объект Planner5D::Parser и считаем сцену:

use Planner5D::Parser;
my $data = join '', <STDIN>;
my $parser = Planner5D::Parser->new(debug => 1);
my $root = $parser->parse_string($data);

Ключ debug заставит программу выводить на stderr ход загрузки текстур и моделей. После вызова метода parse_string на выходе будет ссылка на корневой узел дерева объектов ($root).

Следующим шагом можно уже формировать pov-файл при помощи методов класса Planner5D::Povray. Сначала подготовим вспомогательные объекты (камеру, траву, освещение):

use Planner5D::Povray;
my $povray = Planner5D::Povray->new(debug => 1);
print $povray->povCamera($root);
print $povray->povObserverLight($root);
print $povray->povSunsetLight($root);
print $povray->povGrass($root);

Запускаем программу и рендерим сцену:

./planner2pov.pl < myApartment.json > myApartment.pov
povray myApartment.pov

Экспорт планировок из Planner 5D

Теперь можно добавить на сцену стены:

print $povray->povDefWalls($root);
print $povray->povSceneWalls($root);

Экспорт планировок из Planner 5D

Отдельно можно добавить окна и двери:

print $povray->povDefWalls($root);
print $povray->povSceneWalls($root);

Экспорт планировок из Planner 5D

Не забудем про пол, мебель и прочую утварь:

print $povray->povDefWindowsDoors($root);
print $povray->povSceneWindowsDoors($root);
print $povray->povDefObjects($root);
print $povray->povSceneObjects($root);

Экспорт планировок из Planner 5D

Чего-то не хватает… О, чёрт! Забыли для окон сделать проёмы в стенах. Нет проблем. Для этого придётся использовать хитрый приём. Для каждого окна и двери определим их bounding box и вычтем при помощи оператора POV-Ray «difference» из стен полученные bounding boxes. Для этого добавим в файл вызов povDefWindowsDoorsHoles, который определит объект с «дырками», которые будем вырезать, и заменим povSceneWalls на povSceneWallsWithHoles. Временно уберём окна и двери, чтобы посмотреть, что получилось:

print $povray->povDefWalls($root);
print $povray->povDefWindowsDoorsHoles($root);
print $povray->povSceneWallsWithHoles($root);

Экспорт планировок из Planner 5D

Отлично. Теперь поставим окна на место:

Экспорт планировок из Planner 5D

Так, осталась проблема. Солнечный свет попадает в квартиру не только через окна, а ещё и через верх. Посмотрите на правую стену в ванной. Как она может быть освещена солнечным светом? Никак. Значит надо применить ещё одну хитрость. Накроем квартиру сверху потолком, который будет повторять по форме пол, но будет обладать специальными атрибутами, которые делают объект невидимым, но позволяют ему отбрасывать тень:

print $povray->povDefCeiling($root);
print $povray->povSceneCeiling($root);

Экспорт планировок из Planner 5D

Спецэффекты

POV-Ray обладает мощнейшим арсеналом эффектов, которые можно применять при рендеринге — начиная от простых моделей освещения (ambient/diffuse/specular), заканчивая физическим моделированием поведения лучей света в материалах. Например, описав призму, можно добиться, чтобы в ней белый свет раскладывался на спектр:

Экспорт планировок из Planner 5D

Нам нужен минимальный функционал — сделать окна прозрачными, а зеркала отражающими. Описания материалов, используемых в Planner 5D, не содержат информации об отражающей способности зеркал и о прозрачности стёкол на окнах. Для решения проблемы воспользуемся хаком и переопределим метод materialTransparency у Planner5D::Model::Window:

sub materialTransparency
{
        my $self = shift;
        my $modelMat = shift;
        my $overrideMat = shift;
        # Hack. Forced transparency for glass
        return 0.8 if $overrideMat->{name} eq 'color_1';
        return Planner5D::Model::Ns::materialTransparency($modelMat, $overrideMat);
}

Аналогичный хак сделаем для отражающей способности зеркал:

sub materialReflection
{
        my $self = shift;
        my $modelMat = shift;
        my $overrideMat = shift;

        # Hack. Mirror reflection
        if ($self->{id} == 239 && $modelMat->{DbgIndex} == 1) {
                return 0.9;
        }
        if ($self->{id} == 144 && $modelMat->{DbgIndex} == 0) {
                return 0.9;
        }
        return 0;
}

Способ идентификации объектов и материалов в них крайне варварский. Будем надеяться, что авторы Planner 5D дадут человеческие названия материалам, чтобы можно было опознавать стёкла по наличию строки «glass» в имени материала, а зеркала — по строке «mirror». Но пока только так.

Попробуем сменить ракурс и посмотреть в зеркало. По умолчанию метод povCamera создаёт камеру, которая смотрит в центр квартиры откуда-то сверху. Можно передать ему два опциональных аргумента с координатами камеры и точки look_at относительно центра квартиры. Центром считается геометрический центр bounding box всего проекта, опущенный на уровень земли. X,Y — координаты на плоскости земли, Z — высота над землёй. Все размеры указываются в сантиметрах:

print $povray->povCamera($root, [300, -500, 200], [300, -200, 0]);

Экспорт планировок из Planner 5D

Если вам потребуется изменить какую-то отдельную модель, например, чтобы прямо в данных исправить визуализацию какого-то объекта, вы можете исправить непосредственно файл data/pov/mesh-<id>.pov, отвечающий за нужный объект. Если pov-файл существует, то он не будет автоматически перегенерироваться, и ваши изменения останутся нетронутыми. Ещё одно пожелание разработчикам Planner 5D — экспортировать в машиночитаемом формате названия объектов. Искать их по идентификаторам весьма неудобно.

Исходные коды

Библиотека распространяется под лицензией BSD. Скачать можно тут: https://github.com/amlinux/planner5d-parser. Надеюсь, она окажет помощь тем, кому не хватало инструмента для быстрой и удобной визуализации квартир и домов. Приятного использования.

Автор: aml

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js