Нынче в интернетах только и говорят об облаках, как они бесконечны и прекрасны… о серверах, которые они там видели… А ты? Вот и я решил поделиться с читателями своим опытом разработки онлайн сервиса проектирования помещений и интерьеров в 3D. Здесь я постараюсь рассказать об архитектуре проекта в целом и о деталях реализации.
Что такое облачная система 3D проектирования? Поскольку в последнее время термин “облачные вычисления” очень популярен и используется к месту и не к месту, я начну с определения. Облачное 3D проектирование в моем понимании и моей реализации — это такая архитектура программного обеспечения, при которой все данные о 3D модели и действия по её обработке расположены на удаленных серверах (т.е. в облаках), а клиентские устройства запрашивают ту или иную часть данных или результатов расчетов по сети интернет. Другими словами, подобные системы отличаются от классических систем проектирования тем, что производят большинство расчетных операциях на серверах, а не на клиентских устройствах и передают только небольшую часть данных для визуализации модели и ее параметров клиенту. Архитектура подобных систем оказывается разделённой на тесно взаимодействующие, но удаленно расположенные, серверную и клиентскую части, что требует особого подхода для обеспечения их взаимодействия незаметно для пользователя продукта.
Следующий вопрос: какие преимущества имеет подобная архитектура? Несомненно, облачная архитектура сложнее классической, в которой пользователь, его данные и их обработка находятся и производятся в одном месте. Тем не менее, для моего проекта облачная архитектура имеет ряд неоспоримых преимуществ как с точки зрения разработки, так и с точки зрения использования, делая подобное усложнение архитектуры приложения целесообразным. Попробую их сформулировать:
- Клиентским приложением является веб-браузер. В наше время это означает кроссплатформенность приложения и возможность пользоваться сервисом с любого устройства.
- Быстрый старт приложения без установки снижает порог входа для будущих пользователей.
- Нет необходимости сохранять документы и перемещать их между устройствами, поскольку все данные одновременно доступны со всех устройств.
- Возможность одновременного редактирования или просмотра отдельных документов и целых проектов несколькими пользователями и удобная коммуникация создают единое рабочее пространство между удаленно расположенными клиентами.
- Организация непрерывного процесса разработки и мгновенной доставки обновлений, что позволяет клиентам использовать последнюю версию ПО и способствует активному использованию техник экстремального программирования при разработке.
Разумеется, подобный подход имеет и свои недостатки. Из них я выделяю следующие:
- Необходимость иметь хороший интернет-канал для комфортной работы с приложением.
- Усложнение программного обеспечения и, как следствие, увеличение времени и стоимости разработки.
- Необходимость развертывания и последующей поддержки сетевой инфраструктуры, необходимой для работы ПО.
Архитектура проекта
При выборе архитектуры я старался учесть возможность масштабирования в будущем и разделил проект на части таким образом, чтобы было легко распараллелить самые нагруженные части. При расчете я использовал следующие предположения, основанные на имеющемся опыте по разработке CAD и уточненные при создании прототипа:
Для загрузки среднестатистической квартиры с мебелью мне потребуется примерно 25 Мб несжатых геометрических данных и дополнительных атрибутов (5 Мб сжатых) + 10Мб текстур. Время генерации данных от 0.2 сек. до 5 сек. (в самых сложных случаях). Я планирую ограничить объем модели на уровне 3-5 млн треугольников.
Во время работы по проектированию плана и расстановке различных изделий пользователем, на одну операцию (вставка и редактирование изделий, регенерация плана) приходится в среднем 100 — 500 Кб исходящего трафика. Время выполнения каждой операции на сервере в среднем составляет 0,1-0.5 сек.
Активность пользователей находится на уровне открытия одной модели в минуту или выполнения 5 — 10 операций редактирования в минуту.
Исходя из этого, стало понятно, что удержать на одном сервере больше сотни активных пользователей будет проблематично, поэтому нужно будет обеспечить обработку геометрических запросов на разных серверах, и каким-то образом распределять 3D модели между ними.
В выборе средств разработки я изначально был связан несколькими ограничениями. Во-первых, использование в качестве геометрического ядра C3D и высокие требования по скорости геометрических расчетов при работе с моделью предопределило использование С++ на стороне сервера. Во-вторых, запуск клиентской части на браузере также сузило выбор языков до тех, что поддерживают компиляцию в JavaScript.
В результате на данном этапе проект состоит из четырех независимых частей: два внутренних (backend) сервиса и два внешних Web приложения. Главный внутренний сервис отвечает за геометрическое моделирование и расчеты. Он написан на С++ с использованием библиотек C3D и Qt Core. Вспомогательный сервис отвечает за управлением файлами, каталогами пользователей и обработкой текстур. Он написан на ASP.NET Core. Веб приложения разделены аналогично. Одно отвечает непосредственно за моделирование и написано на TypeScript + WebGL, а второе предоставляет интерфейс пользователя для управлением проектами и каталогами пользователя и написано на связке Angular 2 + TypeScript. Взаимодействие между клиентской и серверной частями идёт с помощью простых HTTP запросов. В части, отвечающей за интерактивное моделирование помещений, используются WebSocket соединения, по которым передаются сжатые бинарные данные. Во избежание дублирования кода между серверными сервисами они также обмениваются необходимой информацией по HTTP протоколу.
Серверная часть
Главная изюминка проекта — это комбинация серверной и клиентской части, отвечающая за геометрическое моделирование, визуализацию и сохранение истории редактирования моделей. К этой части проекта предъявляется высокие требования по производительности, потреблению оперативной памяти, распараллеливанию и масштабируемости, т.к. геометрическое моделирование само по себе достаточно затратная вычислительная задача, а выполнение запросов построения моделей от множества активных пользователей усложняет её еще больше. В качестве “сердца” системы для выполнения задач геометрического моделирования на сервере было выбрано ядро C3D от компании C3D Labs, причины выбора которого описаны в моей предыдущей статье “Ядерные технологии в CAD” [1]. Для реализации функционала по управлению комплексными 3D проектами была разработана собственная система хранения данных 3D модели, в основу которой взята иерархическая ECS (Entity Component System), популяризированная разработчиками игр. Она представляет собой древовидную структуру модели, состоящую из разных элементов(сущностей), где у каждого элемента есть разные наборы данных (компоненты), такие как геометрические параметры, BREP оболочки, треугольные сетки, пользовательские данные и т.п. Для того чтобы система отвечала необходимым требованиям, её реализация имеет целый ряд отличительных особенностей:
При загрузке модели загружается лишь её структура, а все её данные (компоненты) хранятся в NoSQL базе данных и автоматически подгружаются в оперативную память при обращениям к компонентам, а также автоматически выгружаются из памяти по мере необходимости. Это позволяет работать на сервере с тысячами одновременно открытых моделей при небольших затратах оперативной памяти.
Внутри компонентов хранятся связи на компоненты в других сущностях в виде пары “ID сущности — ТИП компонента”. При операциях копирования элементов модели все элементы получают новые ID из старого ID и случайного кода операции с помощью симметричной хеш-функции, поэтому в сущностях вместо подмены всех измененных ID внутри компонентов запоминается код преобразования старых ID в новые. Это позволяет копировать данные компонентов простым и быстрым побайтным копированием, не теряя ссылочной целостности в структуре модели. В результате можно копировать огромные модели без чтения структуры их компонентов, что, в свою, очередь обеспечивает мгновенное копирование больших сборок внутри проектируемого помещения.
Благодаря предыдущему механизму реализован специальный компонент-транзакция, в котором автоматически сохраняется история изменения структуры модели во время того, как различные команды редактируют её содержимое. Разделение сущности на относительно небольшие компоненты позволило поставить “слушатель” на обращение к каждому компоненту, и отслеживать, тем самым, его изменение автоматически. Это позволяет хранить всю историю изменений модели и возвращаться к любому моменту её создания, даже сделанному много месяцев назад (т.к. вся история хранится также в компонентах, которые без необходимости не загружаются в оперативную память). С точки зрения разработчика это означает наличие у геометрической модели аналога транзакций, аналогичных существующим в СУБД.
Версионность каждого элемента и компонента модели обеспечивает быстрое формирование специальных файлов-патчей, в которых содержится информация о том, какие сущности и компоненты нужно скорректировать на клиентской модели, чтобы синхронизировать её с версией на сервере. Вкупе с использованием бинарной версии протокола WebSocket это обеспечивает эффективную синхронизацию данных модели на всех подключенных клиентах в реальном времени.
На практике подобная система работает достаточно быстро, обеспечивая время обработки большинства запросов к модели в пределах 5-50 мс. Однако у системы образовалось узкое место: открытие модели требует передачи всех данных для её визуализации, что в случае массивных моделей требует множества обращений к БД для извлечения данных компонентов, приводя к значительным задержкам, достигающим нескольких секунд на моделях из десятков тысяч элементов. Побороть эту проблему позволило кэширование файлов-патчей в Redis. Поскольку патчи не теряют своей актуальности (патчи с версии 0 до версии сто + патч с версии 100 до версии 200 эквиваленты единственному патчу с версии 0 до версии 200), это легко решает проблему инвалидации кэша: его можно обновлять в фоновом режиме, не заботясь о потери актуальности данных.
Клиентская часть
Проектирование клиентской части началось с выбора движка для визуализации. Были перепробованы почти все популярные WebGL движки, однако остановиться ни на одном из них не удалось по следующим причинам:
- Слабая поддержка CAD режимов визуализации, таких как удаление невидимых линий и обрисовка силуэтов криволинейных поверхностей.
- Слабое развитие инструментов для качественного вывода текста небольших размеров в 3D режиме в сочетании с рисованием линий произвольной толщины (эта комбинация нужна для прорисовки планов на 3D модели)
- Отсутствие эффективной техники batching. Модели зачастую состоят из десятков тысяч небольших элементов с разными материалами, и пользователь может изменить любой объект в любой момент времени, поэтому необходимы эффективные техники по динамическому склеиванию маленьких объектов в большие вершинные буферы в видеопамяти для достижения приемлемого уровня производительности.
- Необходимость написания специфичных компонентов управления камерой, наложения материалов и анимации, т.к. предлагаемые “коробочные” варианты не подходят под нужды системы проектирования.
В результате анализа пришло понимание того, что написать свой “велосипед” будет и быстрее, и лучше, и значительно легче в поддержке. За за основу была выбрана великолепная библиотека https://twgljs.org
Вот примеры отрисовки плана помещения и 3D вида на WebGL:
Следующим вопросом был выбор языка программирования и платформы в целом. У меня уже был опыт разработки JavaScript приложения размером порядка 10 тысяч строк. Исходя из этого опыта, идея разработки на JavaScript нечто большего лично мне внушала благоговейный ужас. Очередной релиз TypeScript и тот факт, что за ним стоит Андерс Хейлсберг, предопределило выбор языка. Выбор платформы для Web пал на Angular 2 ( который теперь уже 4): мне и так предстояло собрать проект из немалого количества разношерстных библиотек, а собирать свой комбайн для Web-приложения не было ни малейшего желания. Хотелось иметь именно framework, в котором “все включено”. Развитые возможности отложенной загрузки модулей системы, эффективная кодогенерация (AOT) и возможности интернационализации только укрепили мой выбор. Единственное, что меня все еще смущает на данный момент — это отсутствие локализации сообщений в исходных файлах, но я искренно надеюсь, что уж к четвертой версии они реализуют эту функциональность))
Реализация замыслов
Проект начался с реализации на С++ прототипа структуры будущей модели и экспериментальной визуализации на OpenGL. После нескольких месяцев отладки я занялся переводом приложения на клиент-серверную модель. Первоначально я сделал это следующим образом: написал совмещенный REST+WebSocket сервер на C# и подключил геометрический сервис, как динамическую библиотеку с C-интерфейсом для работы с моделями, которая будет вызываться для геометрических запросов. Крайнее неудобство отладки такого гибридного приложения и ненужные накладки при копировании данных из C++ в С, а затем и в C# вынудили меня искать альтернативные решения. В конце концов я включил WebSocket сервер внутрь С++ части и маршрутизировал все запросы к нему через прокси-сервер. При этом для аутентификации клиентов геометрический сервис делает внутренние запросы к основному REST-сервису.
Следующим этапом стала реализация алгоритма синхронизации модели, изменяемой на сервере, с моделью, отображаемой на клиенте. Первоначальные идеи слежения сервером за состоянием клиента, либо отправки клиентом своего текущего состояния на сервер перед синхронизацией пришлось отмести, как не очень надежные и сложные в реализации. Остановился я на следующей реализации: в каждом компоненте хранится целочисленная версия компонента. Таким образом, версия модели в целом определяется максимальной версией среди всех её компонентов и компонентов дочерних сущностей. При синхронизации клиент отправляет на сервер запрос, содержащий версию его модели, в ответ на который сервер отправляет данные всех компонентов, версия которых старше клиентской версии. Это обеспечило синхронизацию древовидной модели между клиентами и сервером с минимально возможным трафиком (запрос синхронизации — одно число, а в ответе содержатся лишь измененные компоненты).
После написания прототипов клиентской и серверной частей я занялся поиском оптимального формата данных для передачи геометрической модели между клиентом и сервером. В этом формате я хотел иметь следующие возможности:
- Удобство записи и чтения формата как из C++, так и из TypeScript кода
- Поддержка схемы данных
- Минимально возможное время упаковки и распаковки данных
- Передача чисел с плавающей точкой без потери точности
- Эффективность работы при среднем размере пакета данных в диапазоне 100Кб — 10Мб
- Версионность формата для удобства развития в будущем и возможности поэтапного обновления разных частей системы
Использованный в экспериментах JSON пришлось отмести сразу, а дальше был выбор между MessagePack, Google Protocol Buffers, Apache Thrift, BSON и аналогичных им библиотек. Мой выбор остановился на Google Protocol Buffers по причине лучшей производительности, хорошего сжатия и удобного кодогенератора. Важным фактором также стала распространенность библиотеки и надежда, что она не будет заброшена в долгосрочной перспективе. В результате я использую родной protobuf на С++, protobufjs — для чтения и записи на клиенте, proto2typescript — для использования единой схемы между C++ и TypeScript. Кроме того, данные дополнительно сжимаются zlib при передачи через WebSockets. Это схема позволила очень комфортно и быстро передавать все необходимые данные по модели.
PS: не так давно наткнулся на библиотеку FlatBuffers от того же разработчика, и у меня появилась мысль, что этот вариант будет еще лучше, однако времени попробовать эту библиотеку совершенно не хватает, кроме того, поддержка TypeScript пока отсутствует в основной ветке.
После устаканивания формата данных и первых экспериментов над каждой частью системы стало приблизительно понятно, как будет функционировать сервис в целом. Помимо этого были оценены узкие места и сделаны наброски вариантов масштабирования в будущем. Затем были созданы первые варианты программы, показывающие работу связки “действие пользователя — запрос к серверу моделирования — визуализация действия пользователя”. На этом этапе наступило первое разочарование: такая схема работы обеспечивает обновление данных в стиле “потянул за маркер, отпустил мышку, объект перестроился”. Это было слишком медленно для интерактивного отображения действий пользователя при перемещении курсора.
Данный результат потребовал переосмыслить границу между серверной и клиентской частью и сделать клиент более обширным, а также продублировать функционал между сервером и клиентом таким образом, чтобы клиент проводил предварительные расчеты для интерактивной полигональной визуализации, а сервер производил финальные действия над BREP моделью и синхронизировал всех клиентов между собой. Это заставило меня глубоко задуматься о проекте WebAssembly, который теоретически позволил бы иметь единую кодовую базу для клиентской и серверной части и оперативно управлять выполнением расчетов между клиентом и сервером, перераспределяя нагрузку по мере необходимости. Но пока это всё мечты…
Следующим этапом стала реализация полноценного WebGL рендера. Пока его возможности достаточно скромные, однако и над ними пришлось изрядно попотеть. Перечислю основные момента реализации:
На текущем этапе для отрисовки использую классический Forward-rendering и несколько проходов. На перспективу планирую реализовать затенение на основе Screen Space Ambient Occlusion или Scalable Ambient Obscurance.
Для достижения приемлемой производительности склеиваю мелкие объекты в большие буферы вершин в глобальной системе координат и отправляю в видеокарту. При изменении объектов все необходимые буферы опять пересчитываются на процессоре. Это может выглядеть диковато, но отправлять матрицу объекта в дополнительных атрибутах еще накладнее.
Отрисовка в 3D линий толщиной отличной от 1 пикселя крайне в WebGL нетривиальна. Реализовал её через отрисовку двух треугольников, все вершины которых лежат на одной линии, а толщина хранится в атрибутах — тангенсных векторах. Конечные вершины треугольников рассчитываются в вершинном шейдере путем перевода точек в систему координат экрана (для учета соотношения ширины и высоты экрана), прибавки необходимой толщины линий и перевода в нормализованные координаты. Сглаживание линий реализуется через альфа-канал во фрагментном шейдере.
Отрисовка текста сделана через технику SDF, опубликованной компанией Valve. Для подготовки шрифтов использовалась утилита Hiero от libgdx. Полученный мной результат — удовлетворительный: при размере шрифта 14-16 пикселов текст выглядит неплохо, если же размер меньше, и текст расположен под острым углом к плоскости экрана, то он практически нечитаемый. Возможно, я просто не умею готовить SDF, но потеряв много времени, кардинального улучшения результатов получить не удалось. В перспективе планирую попробовать эту технику.
Хочу также отметить, что поддержка WebGL в современных браузерах отличная по сравнению с OpenGL под Windows. Это, видимо, благодаря проекту Angle, эмулирующему вызовы WebGL через DirectX. Код без костылей отлично работает даже под IE 11. С другой стороны, в приложении, оперирующим большими объемами данных со сложными структурами, остро стоит проблема утечек памяти, с которыми бороться очень непросто.
Эпилог
Следующим шагом в разработке стала реализация предметной области программы — моделирование зданий, планировка помещений и различных конструктивных элементов, расстановка предметов интерьера и создание каталогов пользователя. Конечно же, требуют внимания и времени множество сопутствующих веб-сервису вещей в виде авторизации и аутентификации, обеспечения резервного копирования, масштабирования различных частей, непрерывной интеграции всего процесса разработки. В этих направлениях впереди немалый фронт работы, прежде чем проект можно будет открыть для публичного пользования. Несмотря на это, проведенная работа позволила мне приобрести огромный опыт. Надеюсь, мои измышления будут полезными кому-то из читателей. Если кто-то имеет опыт и готов, в свою очередь, поделиться советами по реализации веб-сервиса, мне будет очень интересно их услышать. Пишите в личку, или оставляйте комментарии здесь.
Комментарии (13)
Andrus_Trash
26.01.2017 21:41Из статьи не очень понятно, кто в конце концов будет конечным пользователем продукта?
Профессиональный архитектор, уже имеющий опыт работы, например, с ArchiCAD, или новичек, которому надо быстро накидать план квартиры прямо в браузере, не парясь со специализированным ПО?x512
27.01.2017 09:13Нет, нет архикад это совсем другой уровень))) Моя цель — продукт для новичков.
Andrus_Trash
27.01.2017 12:29Новички тоже разные бывают ;)
Мне ваш проект интересен по двум причинам. Во-первых, по образрванию я сам архитектор (правда, вовремя ушел в ИТ). И во-вторых, мы несколько лет назад хотели сделать подобный сервис для интерьерной студии. Но забросили еще на сдалии прототипирования и работе с фокус-группой. Потому что оказалось что новички тоже бывают разные:
- Совсем нубам даже интерфейс Sketch-Up кажется мудреным.
- Тем, кому не кажется, в нем и нарисуют.
- Мужикам проще нарисовать план в блокноте и отправить фотку.
- Многим мысли проще выразить словами. Вобще, для клиентов ценность — общение с консультатном. Для того, чтобы понять чего они хотят, им нужно проговорить проект. А бездушная машина плохой психоаналитик.
- И конечно-же, все клиенты хотят увидеть фотореалистичный рендер.
В итоге, не написав ни строчки кода проект свернули в пользу расширения штата визуализаторов.
x512
27.01.2017 13:271)В Sketchup набросать план получится за час, у меня, скажем, за 10 мин. в спец программе намного быстрее работать. Там вы работаете с полигонами, а тут с розетками, окнами и стенами
2)Моя фишка — точные размеры и быстрое редактирование, увы Sketchup — Paint по меркам САПР
3)С веб-версией идёт куча плюшек вроде оценки нужных стройматериалов прямо с телефона в магазинеAndrus_Trash
27.01.2017 14:36В Sketchup набросать план получится за час, у меня, скажем, за 10 мин.
А сколько времени это займет у матери двух детей без В.О, котороая хочет ремонт в детской подешевле? Час или два, не важно, если она может заказать этот ремонт по телефону за 5 минут.
На самом деле, очень интересно было бы услышать от вас рассказ о ЦА вашео продукта. Если вы разрабатывете его для своей команды, или как b2b решение, то нет вопросов.
Если же это продукт для массового пользователя, то, мне кажется, вы в процессе разработки упускаете огромный пласт UX-дизайна.
VCheese
28.01.2017 16:31+1Моя фишка — точные размеры и быстрое редактирование, увы Sketchup — Paint по меркам САПР
Архитекторы с вами не согласны. Всё сделано в Скетчапе:
Пример
VDG
27.01.2017 01:27Мне тоже интересно зачем вы всё это делаете? Аналогичный редактор я закончил год назад. Довёл скорость создания 3D-плана до 8м2/мин, даже как-то с фаундером планоплана на скорость посоревновались. ) У них так же быстро. Моим клиентам нужен реализм, и в этом сейчас вся сложность из-за слабости современного железа.
Поэтому мой вопрос, как и выше задали, зачем сейчас кто-то ещё делает то, что уже неоднократно сделали другие (и многие разорились и забросили)? В чём будет ваша «фишка»?x512
27.01.2017 10:02Довёл скорость создания 3D-плана до 8м2/мин,
А что это за параметр и как вы его измеряете?) У меня скорость создания 100 гигакирпичей в секунду))))
А если серьезно, то дьявол кроется в деталях и если вы занимались этой темой, то сможете увидеть между строчек кое какие особенности. Я для примера покажу одну вещь: моделирование планировки, в сравнении с указанным вами Планопланом:
1) В планоплане задется размер стен, у меня — размер комнат, вы можете заметить это на иллюстрациях. Разница принципиальная: в купленой квартире измерить абстрактный размер между центрами стен не представляется возможным. И когда вы проектируете сложное многокомнатное помещение с кривыми стенами построить модель по размерам, которые вы измеряете — не самая простая вещь.
2) Возможность моделирования единообразно на видах и в 3d. Т.е. для меня разница между видом и 3d лишь в положении камеры, в большистве аналогов моделирование возможно лишь в плане. У меня все функции доступны на любых проекциях.
3) Из предыдущего пункта легко вытекает проектирование многоэтажных конструкций именно в 3D
4) Динамическое редактирование всех размеров прямо на модели и уже сейчас реализовано 7 различных спобов
Ведь самое интересное начинается, когда построив план вы построили и обнаружили, что пару размеров нужно поменять и эти инструменты будут востребованы.
5) Корректный расчет площадей всех помещений, с учетом монолитных колонн. В планоплане я вижу расчет абстрактной площади по серединам толщины стен
6) Полноценное моделирование стен, без разбивание их на части касательными стенами
Это я на вскидку перечислил возможности только касательно проектирования плана помещения. В других частях тоже есть немало интресных моментов.VDG
29.01.2017 22:18| А что это за параметр и как вы его измеряете?)
Странно конечно слышать, но ладно. ) Это скорость векторизации плана из БТИ, который делают все пользователи планоплана и остальных подобных сервисов прежде чем начать подбирать интерьер.
opanas
27.01.2017 15:21Интересно будет посмотреть на финальный продукт, не забудьте, пожалуйста, статьей о себе напомнить.
А с конкурентами свой будущий продукт сравнивали? Что в существующих решения не устроило?
В частности по https://planner5d.com/ интересно мнение, еще пару лет назад у них был вполне юзабельный продукт и он мне помог жене план ремонта показать и согласовать :)
lash
А что-нибудь типа http://parallax3d.org не смотрели для отрисовки?
x512
да, видел, но не понимаю к чему вы его упомянули? Он не адресует ни одну из описанных мной проблем — я там не увидел ни текста, ни толстых линий, ни нормального wireframe. Cсылок на batching я тоже не нашел. Плюс к этому зависимость от Java мне не очень подходит.