Предыстория


image
Некоторое время назад, в связи с наличием свободного времени, я задумался над применением карт для решения каких-либо интересных и нестандартных задач. Одна из идей, которая меня заинтересовала, была идея применения карт для рендеринга мира в игровом движке c возможностью интерактивного взаимодействия: разрушения Макдональдсов в выбранном городе, локальный апокалипсис у соседей в огороде и тому подобные приятные, но только в случае виртуального мира, мелочи.
Однако несмотря на примитивность идеи, не было найдено каких-то готовых решений под сформулированные мной условия:
  • Открытый исходный код
  • Реал тайм рендеринг мира в игровом движке
  • Поддержка основных платформ (mobile, web, desktop)
  • Желательно C# как основной язык разработки


Конечно, идея рендеринга карт в 3D абсолютно не нова и есть готовые решения как с открытым кодом, так и проприетарные (ни в коем случае не претендую на полный список):

Как правило, подобные решения используют OpenStreetMap данные для рендеринга мира в 3D, т.к. данная возможность в картах есть и активно развивается. Также огромным плюсом OSM является возможность скачать полный дамп данных всей планеты или какой-то определенной части/города (например geofabrik или metro-extracts), притом без жестких лицензионных ограничений.

ActionStreetMap


Таким образом, решено было начать разрабатывать свой проект с нуля под названием ActionStreetMap, что, по идее, должно напоминать о природе исходных данных и способе их использования. В качестве игрового движка был выбран Unity3d, что абсолютно неудивительно из-за последнего условия (язык разработки C#) и развитого сообщества, поскольку это первый мой опыт в области разработки игр.
image
Проект стартовал больше года назад и находится в активной стадии разработки, даже несмотря на то, что я по-прежнему единственный разработчик. Ниже перечислены основные возможности, которые реализованы на текущий момент:
  • Flat shading стиль
  • Рендеринг основных объектов OSM (здания, дороги, заборы, деревья, реки, парки, POI...)
  • Использование SRTM данных для создания карты высот
  • Автозагрузка карт с сервера OSM и SRTM данных с сервера NASA
  • Оффлайн карты (поддерживается импорт из основных форматов данных OSM – pbf, xml, o5m)
  • Поддержка примитивного поиска мест в оффлайн режиме
  • Поддержка модификации некоторых объектов (экспериментальная возможность)
  • Асинхронная загрузка всего и всея (с использованием Reactive Extensions)
  • Возможность кастомизации

На первом и последнем пунктах хотелось бы остановиться по-подробнее, несмотря на то, что я не ставлю цели описать внутреннее устройство реализации проекта в данной статье.

Flat shading


После экспериментов с текстурами зданий и unity terrain было решено отказаться от попытки срендерить «правдоподобный» город. Причины тривиальны:
  • Отсутствие реальных данных о фасадах зданий и других объектах (текстура хрущевки на фасаде Рейхстага смотрится забавно поначалу, но со временем начинает напрягать..)
  • Производительность

В результате был выбран подход «с треугольниками», хотя возможность срендерить что-то свое в другом стиле по-прежнему присутствует.

Кастомизация


При знакомстве с OSM картами, меня заинтересовала идея применения CSS подобного языка для описания какие объекты должно быть срендерины и как. В результате, подобный подход с некоторыми особенностями был использован и в ASM. Например, здания частично определяются следующими правилами:
area[building]                      { builder: building;  height:12; min_height: 0; levels: 5; fill-color:gradient(#c0c0c0, #a9a9a9 50%, #808080); facade-builder:empty; material:Materials/Buildings/Building; }
area[building:levels]               { height: eval(num(tag('building:levels')) * num(3.2)); }
area[min_height]                    { min_height: eval(num(tag('min_height'))); }
area[building:height]               { height: eval(num(tag('building:height'))); }
area[building:height][roof:height]  { height: eval(num(tag('building:height')) - num(tag('roof:height'))); }
area[building:cladding=brick]       { fill-color:gradient(#0fff96, #0cc775 50%, #066139); }
area[roof:material=brick]           { roof-color:gradient(#0fff96, #0cc775 50%, #066139); }

Этот подход убрал логику парсинга правил из C# классов, а в сочетании с Composition Root и Dependency Injection паттернами, используемыми в проекте, сильно упростил задачу кастомизации рендеринга OSM объектов и добавил возможность создания «тем» (например, зимняя, летняя тема и т.п.), которые можно менять без перекомпиляции исходного кода.

Исходники


В настоящий момент доступны исходники демо проекта в Unity Editor, а также web build с небольшой частью карты Москвы.
Сам фреймворк пока не на github, но при заинтересованности unity сообщества в его развитии может быть опубликован.

Комментарии (17)


  1. pehat
    27.04.2015 20:45

    А 3D-модели тоже можно с OSM скачать?


    1. eiskalt Автор
      27.04.2015 20:53

      В OSM данных нет 3D моделей. Все объекты строятся из простеших примитивов — node (точка), полилиния (way) + группа примитивов (relation). Эти примитивы имеют специфичные тэги, как, например, высота (height), высота над землей (min_height), для зданий тип крыши (dome, mansard, gabled,..) и т.п. Таким образом можно построить достаточно сложные модели (см демо билд с Кремлем).


      1. pehat
        27.04.2015 21:22

        А где можно раздобыть сами модели? Есть какой-то открытый источник или пришлось это всё ваять руками?


        1. eiskalt Автор
          27.04.2015 21:37

          Да, можно сказать, что пришлось «все ваять руками»: вот, как пример, из чего состоит Храм Василия Блаженного.

          По поводу самих моделей не могу ничего сказать точно. Вроде, некоторую полезную информацию можно найти здесь и здесь.


      1. Moskus
        28.04.2015 01:42

        Строго говоря, кое-какие модели там все же имеются. Иногда — достаточно грубые, иногда — весьма детальные (учитывая, что OSM для этого не особенно-то и предназначен).



        1. Woodroof
          28.04.2015 22:34

          А где на втором примере модель?


          1. Moskus
            29.04.2015 00:45

            Здание с секциями переменной этажности крупным планом в центре. Во всяком случае, это не в меньшей степени «модель», чем то, что видно на третьем примере.
            Это все весьма условно можно называть моделями, как уже сказано выше — здания могут быть составлены из сечений путем экструзии и смещения относительно нулевого уровня, не более того (ну плюс еще разные формы крыши).


            1. Woodroof
              29.04.2015 08:18

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


              1. Moskus
                29.04.2015 19:24

                Дома с треугольной крышей строятся по атрибутам, которые описывают крышу (тип, угол, ориентация относительно здания). Трубы (как и теплообменники ТЭЦ) — экструзия основания (когда оно есть) с учетом типа объекта. Это фича конкретного способа визуализации.


            1. eiskalt Автор
              29.04.2015 10:41

              Я так понимаю, что под моделью понимался файл (obj, fdx и т.п.), который можно открыть с помощью Blender, 3D Studio Max, Unity Editor и т.п., чтобы реиспользовать его в других приложениях без необходимости обработки «сырых» OSM данных.

              В OSM сложные объекты составлены из примитивных объектов в своем «формате» и скачивание, например, xml с описанием только определенного здания, в целом, бесполезно — нужно писать свой конвертер в известный формат, если есть необходимость редактировать или визуализировать в популярных программах.

              Скриншоты выше, похоже, из f4map. Насколько я понимаю, у них для определенных OSM объектов (труба, памятник, стадион) заготовлены 3D модели (либо они их генерируют налету). Но это специфика визуализации данных конкретного проекта.


              1. Moskus
                29.04.2015 19:27

                Вы неправильно понимаете. Модель — это любое описание геометрии, более подробное чем bounding box или проекция на горизонтальную плоскость. Так что модель — это не обязательно данные в принятом трехмерном формате.
                В f4map действительно есть встроенные модели в вашем понимании этого термина — например, краны, которые этот визуализатор ставит на территориях, которые отмечены как строительные площадки. Но многое другое строится по куда более подробным описаниям. См., например, Останкинскую телебашню.


  1. EINSAM_KONSTANTIN
    28.04.2015 11:06

    Очень интересный проект, так как я работаю с 3D ГИС в Unity, для меня было только мечтой интеграция OSM или Google earth, но теперь вижу, что все иначе. Спасибо автору. Как вы планируете развивать проект?


    1. EINSAM_KONSTANTIN
      28.04.2015 11:18

      И кстати в демо проекте столкнулся со следующей ошибкой компилятора:

      Internal compiler error. See the console log for more information. output was:
      Internal compiler error. See the console log for more information. output was:
      Unhandled Exception: System.Reflection.ReflectionTypeLoadException: The classes in the module cannot be loaded.

      at (wrapper managed-to-native) System.Reflection.Assembly:GetTypes (bool)

      at System.Reflection.Assembly.GetTypes () [0x00000] in :0

      at Mono.CSharp.RootNamespace.ComputeNamespaces (System.Reflection.Assembly assembly, System.Type extensionType) [0x00000] in :0

      at Mono.CSharp.RootNamespace.ComputeNamespace (Mono.CSharp.CompilerContext ctx, System.Type extensionType) [0x00000] in :0

      at Mono.CSharp.GlobalRootNamespace.ComputeNamespaces (Mono.CSharp.CompilerContext ctx) [0x00000] in :0

      at Mono.CSharp.Driver.LoadReferences () [0x00000] in :0

      at Mono.CSharp.Driver.Compile () [0x00000] in :0

      at Mono.CSharp.Driver.Main (System.String[] args) [0x00000] in :0

      Что я мог сделать не так?


      1. eiskalt Автор
        28.04.2015 11:28

        Возможно, проблема в версии Unity? Я использую 5.0.1f
        Дело в том, что сами библиотеки ASM собраны в Visual Studio с помощью компилятора от Microsoft, с добавлением UnityEngine.dll в зависимости. Данный подход очень сильно ускоряет разработку, позволяет тестировать логику вне контекста Unity процесса и использовать встроенный в VS профайлер…
        Но вот такие ошибки возможны при попытке собрать проект в другой среде.


        1. EINSAM_KONSTANTIN
          28.04.2015 12:11

          Спасибо, действительно в Unity 5 все работает отлично! Вы не планируете вовлечь в разработку сторонних разработчиков? Создать сообщество?


          1. eiskalt Автор
            28.04.2015 12:18

            На самом деле, было бы неплохо. Главная проблема здесь, что это требует огромного приложения сил, особенно в начале (необходимо подготовить исходники хоть минимально, объяснить основные принципы), а т.к. проект разрабатывается в свободное от работы время, то с «силами» бывают проблемы.
            Однако, сообщество заинтересованных людей может достигнуть чего-то большего :)


    1. eiskalt Автор
      28.04.2015 11:21

      Сейчас я работаю над добавлением простейшего UI, чтобы продемонстрировать некоторые скрытые возможности (например, можно использовать не только оффлайн карты, но и тянуть данные с сервера osm по мере надобности небольшими порциями; reverse geocoding с помощью nominatim; импорт карт с pbf, o5m, xml форматов).

      Следующий шаг — исправление ошибок и улучшение рендеринга зданий (иногда здания «врастают» в землю либо висят над ней) и других объектов. А также генерация специфичных объектов для разных POI (разные типы деревьев, модель фонтана, памятника, светофора и т.п. вместо текущей модели куба с текстурой).

      Далее: мне сильно не нравится как проработана сама сцена — освещение, использование самого примитивного vertex шейдера. Все это необходимо сильно улучшать, чтобы сделать результат поприличние (как ориентир — PolyWorld проект для Unity). К сожалению, пока у меня нет необходимых знаний, как это сделать.

      Ну и где-то затем можно подумать об добавлении AI и поддержки мультиплейера…

      Вообщем, хватает, что делать на годы вперед :)