...

суббота, 5 декабря 2015 г.

Alloy Navigator: второй пилот для ИТ-инфраструктуры

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

Основной задачей продукта Alloy Navigator является обеспечение эффективности, прозрачности и оперативности управления ИТ-инфраструктурой; это целостное функциональное решение управления активами, запросами пользователей, лицензированием и проч.
Часто стандартизация и своеобразная «кодификация» правил в сфере системного администрирования воспринимается в штыки. Однако отказ от рекомендованных практик начинает сильно сказываться на состоянии ИТ-инфраструктуры в период интенсивного роста, когда инженеры и и администраторы, а также ИТ-менеджеры начинают испытывать трудности в связи с тем, что парк оборудования и программного обеспечения их собственной компании разрастается или появляется несколько заказчиков с различным типом ведения бизнеса. В таких условиях каждая мелочь становится важным элементом работы и процессный подход отлично помогает уследить за всем, ничего не упустить из виду и не забыть.

Модули Alloy Navigator — краткий обзор


Разрабатывая Alloy Navigator, мы изначально приняли решение следовать рекомендациям ITIL и создать продукт таким образом, чтобы он сам погружал клиента в отлаженную парадигму подхода ITSM. В Alloy Navigator 7.0 реализованы основные модули, которые позволят управлять ИТ-инфраструктурой компании в соответствии с принципами ITIL, но при этом обеспечивающие максимальную гибкость для настройки продукта под потребности каждого конкретного пользователя.

Техническая поддержка (Service Support). В этом модуле собирается информация по инцидентам — случаям прерывания или снижения качества ИТ-услуги. Важно своевременно создавать инциденты с целью обеспечения минимального влияния неполадок на на бизнес-процесс и максимально быстрого восстановления элемента ИТ-инфраструктуры. Каждая запись содержит подробную информацию о возникшей проблеме: тип, категорию, описание, приоритет, сроки исправления, активности по инциденту, описание решения, связанные объекты инфраструктуры, историю взаимодействий.

Пользователь может создавать группировки инцидентов по исполнителям, статусу, срокам устранения, очерёдности поступления заявки и т.д… Это необходимо для того, чтобы установить порядок обработки инцидентов в соответствии с их срочностью и важностью. Кроме того, группировки по типу инцидента и затрагиваемому сервису помогают идентифицировать проблемы, которые могут оказывать негативное влияние на работу множества пользователей или даже на весь бизнес в целом.

Ещё одна важна сущность данного раздела — проблема, причина, лежащая в основе одного или нескольких инцидентов. Как правило, проблема устанавливается в ходе работы над устранением инцидента.

Все данные организованы в виде таблиц с возможностью гибкой фильтрации по набору параметров, а это значит, что можно создавать любые группировки и легко видеть решённые задачи или тикеты, требующие незамедлительного решения. Модуль технической поддержки может использоваться как для внешних клиентов в сервисных службах, так и для обслуживания внутренних заявок. Такая организация технической поддержки крайне полезна в крупных компаниях с большим офисом или распределённой сетью филиалов. Механизмы Service Support в сочетании с KPI и планировщиками, встроенными в Alloy Navigator, способны решить важные управленческие задачи:
  • спланировать загрузку сотрудников технической службы и инженеров;
  • спланировать закупки оборудования, программного обеспечения и расходных материалов;
  • определить ответственных;
  • перераспределить задачи в зависимости от сложности и важности между сотрудниками в соответствии с опытом и квалификацией;
  • автоматически рассчитать KPI (ключевые показатели деятельности), которые могут лечь в основу принятия решения о премировании или депримировании сотрудников.

Тикет создаётся в привязке к любому элементу ИТ-инфраструктуры и в полном соответствии с рекомендациями ITIL может быть создан из любого раздела.


Тикет является одной из базовых сущностей Alloy Navigator и содержит в себе всю историю взаимоотношений и действий по запросу. Фактически, тикет обеспечивает прописанный в ITSM принцип максимального внимания к потребности конечного клиента.

Модуль SLA (SLA Management). Соглашение об уровне предоставления услуги (Service Level Agreement (SLA)) — термин ITIL, обозначающий формальный договор между заказчиком услуги и её поставщиком, содержащий описание услуги, права и обязанности сторон и согласованный уровень качества предоставления данной услуги. Соглашение об уровне услуг описывает ИТ-услугу, документирует целевые показатели уровня услуги, указывает зоны ответственности сторон – поставщика ИТ-услуг и заказчика. Одно соглашение об уровне услуг может распространяться на множество ИТ-услуг или множество заказчиков.

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


Модуль управления ИТ-активами компании (Asset Management) включает в себя список материальных (оборудование) и нематериальных (софт) активов с учётом даты их введения в эксплуатацию и стоимости. Также в этом модуле есть список контрактов (договоров) по обслуживанию, сервису и гарантии.


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


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

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

Software Asset Management — раздел, созданный для реализации востребованной концепции SAM, управления лицензиями программного обеспечения. Если вы имеете дело с программным обеспечением, то вам известно, что лицензии ПО имеют несколько особенностей.
  • Лицензии бывают излишними и реально не используются в деятельности компании, в то время как их обновляют и покупают новые версии. Если компания достаточно большая, системный администратор не успевает отслеживать изменения в профилях пользователей и дорогостоящее ПО «повисает» до момента учёта, то есть примерно на год. Такое положение дел приводит к дополнительным нерациональным расходам.
  • Иногда проблему оптимизации количества лицензий можно решить с помощью покупки не именных, конкурентных лицензий (не на пользователя, а на подключение). Но для этого необходимо знать, в какое время и каким образом сотрудники используют ПО.
  • Морально устаревшие лицензии необходимо своевременно обновлять по запросу пользователей.

Модуль SAM решает эти проблемы. В основе модуля лежит каталог программного обеспечения и три связанных с ним раздела. Software Licences содержит учтённые лицензии программного обеспечения с учётом количества установок, соответствия требованиям и обновлений.

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

Tracked Software и Discovered Installations учитывают установленное программное обеспечение, сохраняют параметры его установки и отслеживают фактическое использование существующих лицензий.

Модуль Configuration Management (управление конфигурациями) содержит несколько связанных между собой разделов, которые дают представление как об отдельных элементах инфраструктуры, так и о целых узлах.


  • Computers содержит перечень рабочих станций с указанием операционной системы, категории, владельца, имени компьютера, даты аудита и прочих параметров. В этом разделе удобно создавать самые разные группировки на основании технических характеристик ПК: объёма памяти, CPU и проч. Такая возможность достигается за счёт множества параметров карточки компьютера, по которым можно производить фильтрацию. Кроме конфигурации, в каждой карточке можно видеть всё программное обеспечение, установленное на компьютере, стоимость оборудования, связанные тикеты. Для работы с каждой рабочей станцией можно применить внешние инструменты: пропинговать компьютер, воспользоваться Telnet и Remote Desktop.
  • Hardware включает в себя железо, соответствующее рабочей станции или группе рабочих станций (например, сетевой принтер), а также портативное оборудование (проекторы, мобильные телефоны и прочее). В карточках содержится информация об установленных программах, ассоциированных объектах и тикетах. Прямо из карточки можно осуществлять списание оборудование, указав основание.
  • Networks включает описание сетей с пулом IP-номеров, указанием владельца, статуса активности, организации-холдера или локации.
  • Documents содержит любые регламентирующие документы и процедуры, необходимые для управления конфигурациями.
  • Configurations включает созданные конфигурации баз данных, интранета, подключений к Интернету и проч.
  • All Cls агрегирует в себе информацию из предыдущих разделов для полного представления конфигураций внутри ИТ-инфраструктуры.

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

Такое решение позволяет не только крайне прозрачно контролировать ИТ-инфраструктуру в связке с конкретным сотрудником внешней компании или собственной компании, но и иметь простой и быстрый доступ к сведениям о каждой персоне. Это особенно важно в случае, если нужно оперативно связаться с ответственным лицом из внешней организации. Этот модуль является важным связующим звеном и завершает логику программы как полностью соответствующей ITIL.

Мелочей не бывает


Кроме основных модулей, Alloy Navigator включает ещё несколько важных элементов, которые не только необходимы логике программы, но и решают самостоятельные задачи.

Отчёты + KPI. В интерфейсе системы можно сформировать отчёты с любой вложенностью данных, создать свои кастомные представления и сгенерировать отчётность по показателям KPI за выбранный период. Система ключевых показателей деятельности KPI — важнейший элемент методологии ITIL, который, кроме всего прочего, позволяет проконтролировать результат работы как всего подразделения, так и каждого сотрудника в отдельности. Подобный мониторинг показателей очень важен именно для сервисных служб и технической поддержки, так как позволяет оценить уровень удовлетворённости внешнего/внутреннего клиента.


База знаний организована достаточно стандартно для тех, кто привык к учётным программам: это своеобразный набор карточек вопросов, который создаётся с помощью простого встроенного текстового редактора. В карточке вопроса можно оставлять комментарии, прикреплять файлы, сохранять ссылки. Некоторые компании пришли к внутрикорпоративным справочника в wiki-разметке, однако решение, встроенное в Alloy Navigator, отвечает общей логике программы и легко доступно для нужд операторов. Кстати, дополнительная ценность интегрированной базы знаний – быстрая адаптация новых сотрудников, которые во время обучения могут обращаться к накопленным статьям. Доступ к базе данных может быть предоставлен внешним пользователям — в таком случае они смогут самостоятельно решать наиболее распространённые проблемы.

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

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

В продукте Alloy Navigator все объекты взаимосвязаны, к ним можно обращаться из различных разделов и карточек. Такая архитектура решает несколько ключевых задач управления ИТ-инфраструктурой.

  • Обеспечивает комплексный, системный подход к управлению и контролю.
  • Позволяет оперативно получить доступ к карточкам всех объектов, связанных с инцидентом или тикетом: персонам, оборудованию, программному обеспечению, локации и т.д.
  • Позволяет объединять тикеты в проекты и в случае необходимости управлять всеми процессами по проектам.
  • Играет важную роль в ходе инвентаризации, когда необходимо собрать и проанализировать информацию в сжатые сроки.

В отличие от конкурентов, Alloy Navigator представляет собой комплексное программное обеспечение, включающее максимальный набор модулей для успешного управления ИТ-инфраструктурой. При разработке мы избежали попыток создать новомодный интерфейс в ущерб функциональности продукта, и у нас получилась структурированное решение с высокой скоростью работы и удобным расположением элементов. Мы приняли во внимание принципы ITIL и организовали Alloy Navigator таким образом, чтобы пользователям для эффективной работы не пришлось изучать всю методологию. Нашей целью было предоставить эффективный инструмент управления и контроля инфраструктуры. Пользователь может настроить поведение системы «под себя», таким образом совмещая базовые идеи ITIL и требования реальной жизни. Достаточно установить Alloy Navigator и начать работать. Порядок в ИТ-инфраструктуре и значительная экономия на излишних активах не заставят себя ждать.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Страсти по домену Sex.com

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

Тестируем облачную платформу IBM на примере чата


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

Предыстория


Ранее мой опыт работы с платформой был близок к нулю, поэтому чтобы разобраться в огромном количестве сервисов платформы, я снова обратился к своему знакомому из IBM. Мне казалось очевидным использовать сервис Message Hub, но в итоге оказалось, что для решения нашей задачи лучше подойдет MQ Light. Основные причины:
  • тариф в бесплатные 10 тысяч сообщений в месяц
  • хорошая масштабируемость
  • простой в использовании API

Так же рассматривали вариант использования сервиса IoT Foundation, но, как оказалось, устройства не могут подписываться на получение любых других событий кроме команд, а отправлять собственно команды они тоже не могут. Получается, отправлять данные мы можем как хотим, а получать только в виде команд.

Итак, перейдем в Dashboard IBM Bluemix.

Регистрируем облачный сервис


Так как для чата нам не потребуется никаких обёрток на стороне сервера, нужно только зарегистрировать сервис и получить данные для подключения.

Заходим в панель, нажимаем “Use services or APIs”:

Ищем MQ Light:

И берём один такой себе.

Возвращаемся в Dashboard, выбираем MQ Light – Service Credentials. Создаём себе данные для авторизации наших приложений.

Получаем:

Проверьте заранее наличие “;” в логине и пароле. Компилятору оно не очень понравится, лучше сгенерировать новую пару.

Создаём десктопное приложение


Поскольку нам нужно быстро создать рабочее приложение – используем Node.JS и устанавливаем библиотеку MQ Light.
npm install mqlight

… и открываем нашу среду разработки. Первым делом необходимо подключить библиотеку:

var mqlight = require('mqlight');

Теперь, чтобы создать реалтайм чат, нам придётся работать с вводом данных из консоли. Для этого отлично подойдёт стандартная библиотека из набора Node.JS – readline.

var readline = require('readline');

Добавим немного переменных для MQ Light.

var TOPIC = "mqlight/simplechat"; 
var SHARE_ID = "";
var opts = {
  service: 'connectionLookupURI', //Данные из предыдущего этапа
  user: 'username',
  password: 'password'
};

И не забываем про ввод/вывод:

var rl = readline.createInterface(process.stdin, process.stdout);

Далее, при запуске спрашиваем ник и подписываемся на канал с ником в share id:

Скрытый текст
mqlightClient.on('started', function() {
  rl.question("Please, choose your nickname: ", function(result) {
    SHARE_ID = result;
    console.log("Welcome,", SHARE_ID, "!");
    rl.setPrompt(SHARE_ID + '> ');
        
    mqlightClient.subscribe(TOPIC, SHARE_ID, 
    { credit : 5,
      autoConfirm : true,
      qos : 0}, 
    function(err) {
      if (err) console.error("Failed to subscribe: " + err); 
      else {
        console.log("Subscribed to " + TOPIC);
        rl.prompt();
      }
    });
  });
});

И обрабатываем получение сообщения (событие 'message' в MQ Light) и отправку сообщения (ловим событие 'line' в readline):

Скрытый текст
mqlightClient.on('message', function(data, delivery) {
  data = JSON.parse(data);
  if (data.id != SHARE_ID) {
    readline.clearLine(process.stdout, 0);
    rl.setPrompt(data.id + '> ');
    rl.prompt();
    console.log(data.message + '                 ');
    rl.setPrompt(SHARE_ID + '> ');
    rl.prompt();
  }
});

rl.on('line', function(message) {
  if(message != "") {
    var data = JSON.stringify({
      id: SHARE_ID,
      message: message
    });
    mqlightClient.send(TOPIC, data, {
      ttl: 60*60*1000
    });
    rl.prompt();
  }
});

Приложение готово. При запуске оно попытается подключится к серверам IBM, затем спросит ник, и подпишется на канал /mqlight/simplechat с ником в качестве SHARE ID. На всё ушло всего 64 строки! Кажется, весьма неплохой результат для одного вечера.

Тестирование


Проверяем, всё ли работает:

Profit! А вот так это выглядит в панели управления:

Заключение


Теперь о результатах.
  • 64 строчки кода
  • Следуя инструкции на создание чата уйдет 20-30 минут
  • Сервисы платформы сокращают ~40% времени, учитывая, что нужно поднять сервер, написать код для бекэнда
  • Дополнительным плюсом, данные передаются в зашифрованном виде из коробки, с помощью TLS

Итак, мы решили поставленную задачу: создать чат с минимальными затратами ресурсов, всего за один вечер. С помощью IBM Bluemix можно быстро создавать приложения любой сложности, используя множество уже готовых сервисов и API. Что же, облачные технологии развиваются, и в будущем работа программиста может стать гораздо проще и интереснее, а как вы считаете?

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Создатели программ-вымогателей и мошеннических сайтов техподдержки объединяются

Пока первые держат файлы в заложниках, вторые выставляют баснословные счета за устранение на компьютере несуществующих проблем


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

Некоторые веб-сайты, предлагающие весьма сомнительные услуги техподдержки, также используют программы-вымогатели, которые блокируют файлы пользователей, взымая плату за расшифровку.

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

Миллионы пользователей подверглись риску в результате подобной угрозы безопасности информации


По данным Symantec, сайты технической поддержки также запускают программы-вымогатели в фоновом режиме. Программа-вымогатель (ransomware) – вредоносное ПО, кодирующее файлы компьютера и запрашивающее оплату, часто посредством bitcoin, в обмен на предоставление ключа для их дешифровки.

«В итоге несчастным жертвам нередко приходится платить как мошенническим сайтам технической поддержки за „помощь“, так и программе-вымогателю за то, чтобы вернуть свои файлы в расшифрованном виде», — отметил в своем блоге Дипак Сингх, заместитель главного аналитика Symantec по виртуальным угрозам.

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

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

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

«Как бы то ни было, мы впервые столкнулись с ситуацией, когда мошеннические сайты техподдержки используют ресурсы Nuclear exploit kit для распространения программ-вымогателей», пишет Сингх. «Если подобное сотрудничество окажется плодотворным, боюсь, в ближайшем будущем мы столкнемся с новыми комбинациями аналогичного характера».

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Виртуальный квадрокоптер на Unity + OpenCV (Часть 3)

КПДВ

Всем привет!

Сегодня я хотел бы продолжить серию о том, как подружить Unity, C++ и OpenCV. А также, как получить виртуальную среду для тестирования алгоритмов компьютерного зрения и навигации дронов на основе Unity. В предыдущих статьях я рассказывал о том, как сделать виртуальный квадрокоптер в Unity и как подключить C++ плагин, передать туда изображение с виртуальной камеры и обработать его посредством OpenCV. В этой статье я расскажу как сделать из двух виртуальных камер на квадрокоптере стереопару и как получить карту смещений (disparity map), которую можно использовать для оценки глубины пикселей изображения.

Идея


О том, как сделать 3д реконструкцию написано не мало. Например есть замечательная статья на хабре. Очень советую ее прочитать, если вы совсем не в теме. Более математически строго можно почитать тут. Здесь же я ооочень упрощенно изложу основную идею. Техника получения карты смещений, которая будет использоваться, называется плотная 3д реконструкция (dense 3d reconstruction) по двум изображениям. Используется то, что две расположенные рядом и имеющие одну ориентацию камеры видят одну сцену с немного отличающихся точек зрения. Называется это стереопара. Мы будем использовать обычную горизонтальную стереопару, то есть камеры, смещенные перпендикулярно направлению «зрения» камеры. Если найти на первом и втором изображении одну и ту же точку сцены, то есть найти две проекции точки сцены, то можно заметить, что, в общем случае, координаты этих двух проекций не совпадают. То есть проекции смещены друг относительно друга в случае наложения снимков. Это и дает возможность вычислить глубину точки сцены по величине смещения (упрощенно: точки смещенные больше находятся ближе точек смещенных меньше).

Стереопара и глубина точек
изображение из http://ift.tt/1R0M8BI

Чтобы это сделать необходимо откалибровать камеры. Далее необходимо откалибровать стереопару, убрать дисторсию и выпрямить изображения, так чтобы те самые проекции точки лежали на одной горизонтальной прямой. Это требование алгоритма плотной 3д реконструкции из OpenCV, ускоряющее поиск соответствующих точек. Мы будем использовать самый простой и самый быстрый алгоритм из OpenCV — StereoBM. Описание АПИ находится здесь. Приступим.

Калибровка камер


Как добавить еще одну камеру на квадрокоптер и получить с нее изображение описывается в предидущей статье, поэтому сразу начнем с калибровки камер. Я использую две камеры с углом обзора (field of view) 70 градусов, текстуры изображений с камер разрешением 512 x 512 пикселей. Откалибровать камеру — это значит получить матрицу 3х3 внутренних параметров и вектор параметров ее искажений. Калибровка происходит путем получения набора калибровочных сэмплов. Один калибровочный сэмпл — это координаты точек калибровочного паттерна с заранее известной геометрией в двухмерной системе отсчета снимка камеры. От качества калибровки очень сильно зависит работа всех алгоритмов, которые ее используют, поэтому очень важно хорошо откалибровать камеры. Я калибрую по 40-50 калибровочным сэмплам, причем, важна большая вариабельность калибровочных сэмплов, то есть по возможности необходимо получить как можно больший разброс по ориентациям и позициям калибровочного паттерна относительно камеры. Предъявление почти одинаковых 50 калибровочных сэмплов даст невысокое качество калибровки камеры. Я использую этот калибровочный петтарн. Его просто можно драг-эн-дропнуть в Unity и задать его текстурой для 2д спрайта. Все размеры в программе я задаю в пикселях, размер стороны квадрата этого паттерна 167 пикселей. Для него уже есть функции поиска его на изображении в OpenCV. Для вдохновения можно использовать пример из opencv-source/samples/cpp/calibration.cpp. Собственно, я так и сделал.
Код основных функций калибровки
/** @brief Функция ищет на переданной картинке наш калибровочный паттерн и
        запоминает найденные точки паттерна.
        Size boardSize (9, 6) - это конфигурация калибровочного паттерна, количества квадратов по горизонтали и вертикали,
        в sampleFound сохраняется был ли найден паттерн на изображении
*/
void CameraCalibrator::findSample (const cv::Mat& img) {

        currentSamplePoints.clear();

        int chessBoardFlags = CALIB_CB_ADAPTIVE_THRESH | CALIB_CB_NORMALIZE_IMAGE;
        sampleFound = findChessboardCorners( img, boardSize, currentSamplePoints, chessBoardFlags);
        currentImage = &img;
}

bool CameraCalibrator::isSampleFound () {
        return sampleFound;
}

/** @brief Функция сохранения найденного сэмпла
*/
void CameraCalibrator::acceptSample () {
        // немного улучшает найденные координаты сэмпла
        Mat viewGray;
        cvtColor(*currentImage, viewGray, COLOR_BGR2GRAY);
        cornerSubPix( viewGray, currentSamplePoints, Size(11,11),
        Size(-1,-1), TermCriteria( TermCriteria::EPS+TermCriteria::COUNT, 30, 0.1 ));
        
        // рисует на изображении найденные точки (полезно видеть что именно было найдено)
        drawChessboardCorners(*currentImage, boardSize, Mat(currentSamplePoints), sampleFound);
        
        //сохраняет сэмпл
        samplesPoints.push_back(currentSamplePoints);
}

/** @brief Функция инициирующая процесс калибровки
*/
void CameraCalibrator::makeCalibration () {
        vector<Mat> rvecs, tvecs;
        vector<float> reprojErrs;
        double totalAvgErr = 0;
        //мы знаем заранее, что у нас картинка всегда квадратная
        //поэтому можно этот параметр не калибровать,
        //если есть еще какая-то информация о камерах, ее тоже
        //можно использовать с помощью других флагов, которые
        //можно здесь указать
        float aspectRatio = 1.0;
        int flags = CV_CALIB_FIX_ASPECT_RATIO;

        bool ok = runCalibration(samplesPoints, imageSize, boardSize, squareSize,
                        aspectRatio, flags, cameraMatrix, distCoeffs,
                        rvecs, tvecs, reprojErrs, totalAvgErr);
        
        // выводим в лог результат калибровки
        stringstream sstr;
        sstr << "--- calib result: " << (ok ? "Calibration succeeded" : "Calibration failed") << ". avg reprojection error = " << totalAvgErr;
        DebugLog(sstr.str());
        
        saveCameraParams(imageSize, boardSize, squareSize, aspectRatio, flags, cameraMatrix, distCoeffs, rvecs, tvecs, reprojErrs, samplesPoints, totalAvgErr);
}

/** @brief Функция рассчета геометрии калибровочного паттерна
*/
void calcChessboardCorners(Size boardSize, float squareSize, vector<Point3f>& corners)
{
        corners.resize(0);
        for( int i = 0; i < boardSize.height; ++i )
                for( int j = 0; j < boardSize.width; ++j )
                        corners.push_back(Point3f(j*squareSize, i*squareSize, 0));
}

/** @brief Функция выполняющая калибровку
*/
bool runCalibration(
        vector<vector<Point2f> > imagePoints,
        Size imageSize, Size boardSize,
        float squareSize, float aspectRatio,
        int flags, Mat& cameraMatrix, Mat& distCoeffs,
        vector<Mat>& rvecs, vector<Mat>& tvecs,
        vector<float>& reprojErrs,
        double& totalAvgErr
) {
        //инициализация матрицы внутренних параметров камеры
        cameraMatrix = Mat::eye(3, 3, CV_64F);
        if( flags & CALIB_FIX_ASPECT_RATIO )
        cameraMatrix.at<double>(0,0) = aspectRatio;

        //инициализация коэффициентов дисторсии
        distCoeffs = Mat::zeros(8, 1, CV_64F);

        //рассчитываем координаты точек на калибровочном паттерне
        //в системе калибровочного паттерна
        //это необходимо для калибровки
        vector<vector<Point3f> > objectPoints(1);
        calcChessboardCorners(boardSize, squareSize, objectPoints[0]);

        objectPoints.resize(imagePoints.size(),objectPoints[0]);

        //вызов OpenCV функции калибровки
        //objectPoints - заранее известная геометрия калировочного паттерна
        //imagePoints - калибровочные сэмплы
        //imageSize - размеры изображения камеры
        double rms = calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix,
                    distCoeffs, rvecs, tvecs, flags|CALIB_FIX_K4|CALIB_FIX_K5);
                    ///*|CALIB_FIX_K3*/|CALIB_FIX_K4|CALIB_FIX_K5);
        
        //rms - оценка качества калибровки, при хорошей калибровке,
        //при задании всех размеров в пикселах, должно около 1
        printf("RMS error reported by calibrateCamera: %g\n", rms);

        bool ok = checkRange(cameraMatrix) && checkRange(distCoeffs);

        totalAvgErr = computeReprojectionErrors(objectPoints, imagePoints,
                rvecs, tvecs, cameraMatrix, distCoeffs, reprojErrs);

        return ok;
}



Стереопара


Теперь нам необходимо сделать из наших двух камер стереопару. Для этого используется функция stereoCalibrate. Для нее нам понадобятся сэмплы обеих камер. Мы воспользуемся сэмплами взятыми с шага калибровки камер. Поэтому важно сохранять только те сэмплы, где калибровочный паттерн бал найден на двух изображениях сразу. Матрицы камер и их параметры дисторсии нам потребуются как хорошие начальные приближения.
Код калибровки стереопары
/** @brief Функция, выполняющая калибровку стереопары
*/
void StereoCalibrator::makeCalibration (
        const std::vector<std::vector<cv::Point2f>>& camera1SamplesPoints,
        const std::vector<std::vector<cv::Point2f>>& camera2SamplesPoints,
        cv::Mat& camera1Matrix,
        cv::Mat& camera1DistCoeffs,
        cv::Mat& camera2Matrix,
        cv::Mat& camera2DistCoeffs
) {

        //снова нам нужно передать геометрию калибровочного паттерна
        std::vector<vector<Point3f>> objectPoints;
        for( int i = 0; i < camera1SamplesPoints.size(); i++ ) {
                objectPoints.push_back(chessboardCorners);
        }

        double rms = stereoCalibrate(
                objectPoints, camera1SamplesPoints, camera2SamplesPoints,
                //матрицы и параметры искажений будут уточнены и перезаписаны
                camera1Matrix, camera1DistCoeffs,
                camera2Matrix, camera2DistCoeffs,
                //разрешение картинка камеры
                imageSize,
                //здесь будет рассчитанная матрица поворота второй камеры относительно первой
                rotationMatrix,
                //здесь будет вектор трансляции второй камеры относительно первой
                translationVector,
                //здесь будет существенная матрица
                essentialMatrix,
                //здесь будет фундаментальная матрица
                fundamentalMatrix,
                //флаг, указывающий что нажно использовать переданные матрицы камер
                //и параметры искажений в качестве начального приближения
                CV_CALIB_USE_INTRINSIC_GUESS,
                TermCriteria(TermCriteria::COUNT+TermCriteria::EPS, 100, 1e-5)
        );
        
        //функция также возвращает оценку качества калибровки -
        //ошибку репроекции, она, по идее, должна тоже быть в районе 1
        stringstream outs;
        outs << "--- stereo calib: done with RMS error=" << rms;
        DebugLog(outs.str());
        
}



Стереопара в Unity
Получившиеся у меня параметры калибровки
 //512, 70 deg
        Mat cam1 = (Mat_<double>(3, 3) <<
        355.3383989449604, 0, 258.0008490063121,
         0, 354.5068750418187, 255.7252273330564,
         0, 0, 1);

        Mat dist1 = (Mat_<double>(5, 1) <<
        -0.02781875153957544,
         0.05084431574408409,
         0.0003262438299225566,
         0.0005420218184546293,
         -0.06711413339515834);

        Mat cam2 = (Mat_<double>(3, 3) <<
        354.8366825622115, 0, 255.7668702403205,
         0, 353.9950515096826, 254.3218524455621,
         0, 0, 1);

        Mat dist2 = (Mat_<double>(12, 1) <<
        -0.03429254591232522,
         0.04304840389703278,
         -0.0005799461588668822,
         0.0005396568753307817,
         -0.01867317550268149);

        Mat R = (Mat_<double>(3, 3) <<
        0.9999698145104303, 3.974878365893637e-06, 0.007769816740176146,
         -3.390471048492443e-05, 0.9999925806915616, 0.003851936175643478,
         -0.00776974378253147, -0.003852083336451321, 0.9999623955607145);

        Mat T = (Mat_<double>(3, 1) <<
        498.2890078004688,
         0.3317087752736566,
         -6.137837861924672);



Карта смещений


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

из этой статьи. По поводу настройки парамертров также не советую задирать параметр TextureThreshold больше 50, так как известно, что при этом алгоритм StereoBM начинает спонтанно падать.

Код получения карты смещений
//Создание алгоритмов получения карт смещений
DisparityMapCalculator::DisparityMapCalculator () {
        bm = StereoBM::create(16,9);
}

/** @brief Задание параметров калибровки и
        инициализация параметров ректификации изображений
*/
void DisparityMapCalculator::set (
        cv::Mat camera1Matrix,
        cv::Mat camera2Matrix,
        cv::Mat camera1distCoeff,
        cv::Mat camera2distCoeff,
        cv::Mat rotationMatrix,
        cv::Mat translationVector,
        cv::Size imageSize
) {
        this->camera1Matrix = camera1Matrix;
        this->camera2Matrix = camera2Matrix;
        this->camera1distCoeff = camera1distCoeff;
        this->camera2distCoeff = camera2distCoeff;
        this->rotationMatrix = rotationMatrix;
        this->translationVector = translationVector;
        
        //вычисляет параметры ректификации (выпрямления) изображений
        stereoRectify( camera1Matrix, camera1distCoeff, camera2Matrix, camera2distCoeff, imageSize, rotationMatrix, translationVector, R1, R2, P1, P2, Q, /*CALIB_ZERO_DISPARITY*/0, -1, imageSize, &roi1, &roi2 );
        
        //инициализация преобразований ректификации изображений
        //для левого и правого изображения
        initUndistortRectifyMap(camera1Matrix, camera1distCoeff, R1, P1, imageSize, CV_16SC2, map11, map12);
        initUndistortRectifyMap(camera2Matrix, camera2distCoeff, R2, P2, imageSize, CV_16SC2, map21, map22);

        bm->setROI1(roi1);
        bm->setROI2(roi2);
}

/** @brief Функция задания параметров алгоритма рассчета карты смещений
*/
void DisparityMapCalculator::setBMParameters (
        int preFilterSize,
        int preFilterCap,
        int blockSize,
        int minDisparity,
        int numDisparities,
        int textureThreshold,
        int uniquenessRatio,
        int speckleWindowSize,
        int speckleRange,
        int disp12maxDiff
) {
        bm->setPreFilterSize(preFilterSize);
        bm->setPreFilterCap(preFilterCap);
        bm->setBlockSize(blockSize);
        bm->setMinDisparity(minDisparity);
        bm->setNumDisparities(numDisparities);
        bm->setTextureThreshold(textureThreshold);
        bm->setUniquenessRatio(uniquenessRatio);
        bm->setSpeckleWindowSize(speckleWindowSize);
        bm->setSpeckleRange(speckleRange);
        bm->setDisp12MaxDiff(disp12maxDiff);
}

/** @brief Рассчет карты смещений
*/
void DisparityMapCalculator::compute (
        const cv::Mat& image1,
        const cv::Mat& image2,
        cv::Mat& image1recified,
        cv::Mat& image2recified,
        cv::Mat& disparityMap
) {

        // ректификация изображений
        remap(image1, image1recified, map11, map12, INTER_LINEAR);
        remap(image2, image2recified, map21, map22, INTER_LINEAR);
        
        // для алгоритма очень важно не перепутать левое изображение и правое.
        // при считывании изображений из OpenGL получается так, что они
        // оказываются зеркально отображены,
        // сказываются особенности хранения текстур в OpenGL и OpenCV
        // нам важно отразить изображения по горизонтали, ниаче алгоритм не сработает
        flip(image1recified, L, 1);
        flip(image2recified, R, 1);

        // stereo bm - мне понравился больше чем sgbm,
        // проще настроился и работает быстрее
        // StereoBM принимает на входе изображения в градациях серого
        cv::cvtColor(L, image1gray, CV_RGBA2GRAY, 1);
        cv::cvtColor(R, image2gray, CV_RGBA2GRAY, 1);
        
        int numberOfDisparities = bm->getNumDisparities();
        //вычисление карты смещений
        bm->compute(image1gray, image2gray, disp);

        //конвертируем карту смещений в изображение, которое можно отобразить
        disp.convertTo(disp8bit, CV_8U, 255/(numberOfDisparities*16.));
        //отражаем результат, чтобы увидеть его правильно ориентированным в Unity
        flip (disp8bit, disp, 1);

        //текстуры у нас хранятся в 4 канальном виде
        //поэтому нужно конвертировать наше однокальаное изображение
        //в 4 канала
        cv::cvtColor(disp, disparityMap, CV_GRAY2RGBA, 4);
}



Также можно посмотреть на видео как это все работает у меня.

Алгоритм, использованный для расчета карты смещений довольно старый, поэтому результат не очень красивый получился. Я думаю, что его можно улучшить использовав какой-нибудь более современный алгоритм, но для этого одного OpenCV не достаточно.

За сцену нужно сказать спасибо ThomasKole
Код можно взять из гитхаба, ветка habr_part3_disparity_map_opencv_stereobm

Спасибо за внимание

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Как закон Мура влияет на эволюцию технологий хранения данных

В нашем блоге на Хабре мы рассматривали вопрос влияния закона Мура на появление более производительных процессоров в соперничестве между Intel и IBM, а также его актуальности для облачных сервисов. Но как обстоят дела в области технологий хранения данных? Как закон Мура влияет на их эволюцию — именно этой теме посвящен материал издания Wired. Мы представляем вашему вниманию основные тезисы этой заметки.

Каждые 10-15 лет индустрия хранения данных претерпевает серьезную трансформацию. В последний раз это произошло в конце 90-х годов, когда появились сети хранения данных (SAN, storage area network). Появление этой технологии позволило крупным компаниям получить высокую доступность, избыточность и другие важные характеристики. Теперь же, трансформация происходит вокруг технологии Flash, но дело не в ней как таковой, а в сопутствующей эволюции остальных элементов дата-центров.

Сформулированный еще в 70-х годах прошлого века закон Мура, утверждавший, что количество транзисторов, размещаемых на кристалле интегральной схемы, удваивается каждые 24 месяца, пока что работает. В 1970 году скорость процессоров составляла 740 кГц. На базе закона Мура сегодня мы имеем многоядерные процессоры, скорость которых измеряется гигагерцами. Этот драматический прорыв, осуществленный за прошедшие четыре десятилетия, способствовал развитию новых инновационных технологий, вроде той же виртуализации.

К сожалению, производительность традиционных накопителей на жестких магнитных дисках не росла темпами, сравнимыми с увеличением процессорных скоростей. В итоге образовался определенный «зазор производительности» между CPU и HDD, которые обычно используются в архитектурах SAN и NAS. Традиционный подход к преодолению этого зазора предполагал объединение значительных объемов таких HDD. Главная идея заключалась в том, что чем меньше объём диска, на котором нужно искать данные, тем выше производительность.

Однако развитие инновационных технологий, которое стало возможным благодаря повышению производительности процессоров, сделали этот традиционный подход бессмысленным. К примеру, в виртуализованных средах, часто одной из главных проблем является обеспечения производительности чтения и записи. В итоге, постоянное добавление дисков для обеспечение потребностей новых технологий, делает их применение менее выгодным для бизнеса.

В конечном итоге, это приводит к ограничению дальнейших инноваций. В итоге индустрию хранения данных подстегивает необходимость перехода к модели оценки стоимости за IOPS, а не за гигабайты. Это совершенно новый подход к финансовой оценке новых технологий, и он может способствовать в том числе и более широкому применению Flash-памяти, которая раньше считалась слишком дорогой.

Flash на порядок быстрее, чем HDD, и с его помощью можно получать в тысячу раз больше IOPS — в итоге тот самый разрыв между производительностью процессора и памяти, будет сокращаться куда быстрее. Все это будет способствовать тому, что использование сопутствующих инноваций станет более выгодным для бизнеса, а значит больше ресурсов будет направляться на дальнейшие разработки.

Flash-память всегда была более производительной технологией в сравнению с жесткими дисками, но ей недостатавало свойств масштабируемости и общей доступности данных, которые требуются в крупных SAN-средах. Стремительное развитие облачных технологий позволяет скрыть эти минусы flash, значительно расширяя возможности по построению инфраструктуры.

Закон Мура продолжает двигать вперед рынок процессоров, а революция в сфере флэш-памяти может толкнуть вперед индустрию хранения данных. Крупные компании это понимают — в частности, не так давно мы писали о разработанном Samsung крайне высокоемком жестком диске (16 ТБ), использующим технологию флэш-памяти 3D NAND:

Кроме того, новую технологию флэш-памяти разрабатывают компании Intel и Micron — она называется 3D Xpoint, которая является переосмыслением принятых понятий по работе с памятью. Вместо того, чтобы использовать транзисторы (как принято в традиционной флэш-памяти), 3D Xpoint использует микроскопические сетки проводов, связанные с помощью «селекторов». Информация хранится в пересечениях проводов (отсюда и название технологии).

По заявлениям исследователей в настоящее время они могут создавать чипы по этой технологии, которые будут примерно в 1000 раз быстрее флэш-памяти в iPhone и могут хранить до 10 раз больше данных чем DRAM-память в PC, серверах и ноутбуках. Кроме того, эта память «не волатильна» — это значит, что новая технология может сохранять данные даже в том случае, если питание компьютера будет отключено.

Внедрение таких технологий — дело довольно близкого будущего. При этом, это не единственное направление инноваций в области хранения данных. К примеру, не так давно мы публиковали материал о наиболее перспективных технологиях хранения — и среди них эксперты называли в частности, ленточные накопители (например, от Fujifilm) и генетические хранилища.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

пятница, 4 декабря 2015 г.

[Перевод] Динамические свет и тени в моей 2d игре

Я работаю над игрой в жанре стесл-экшн, где в геймплее большую роль будут играть тени. Поэтому я сделал динамическое освещение/затенение, используя WebGL шейдеры и об этом и будет пост.

Часть первая: Динамическое освещение


На его создание меня вдохновил этот пост на реддите, где aionskull использовал карты нормалей в Unity для динамического освещения своих спрайтов. А пользователь с ником gpillow запостил в комментах что он сделал что-то похожее в Love2D. Вот тут 8-мб гифка с результатами. За неё спасибо jusksmit’у.

Итак, что такое динамическое освещение? Это техника в 3D графике, где источник света освещает объекты на сцене. Динамическое потому, что обновляется в реальном времени при движении источника. Довольно стандартная штука в 3D мире и легко применимая к 2D, если, конечно, вы можете использовать преимущества шейдеров.

Ключ к динамическому освещению в том, что угол падения света на плоскость определяет её освещенность, а ключ к этому ключу — вектор нормали, который показывает куда смотрит плоскость.

На картинке выше это стрелка, торчащая из центра панели. Вы можете увидеть, что, когда лучи света идут под большим углом (к нормали), панель освещена гораздо хуже. Так вот, в конце концов, алгоритм довольно прост — чем больше угол, тем меньше света получает панель. Самый простой путь вычислить освещенность — это вычислить скалярное произведение между вектором от источника света и вектором нормали.

Ок, это всё очень здорово, но как получить вектора нормали в 2d игре? Здесь, вообще-то, нет никаких объемных объектов… Однако, здесь нам могут помочь дополнительные текстуры (те самые карты нормалей), в которых будет записана необходимая информация. Я создал 2 таких карты для двух домов в видео повыше и использовал их чтобы посчитать освещение, вот пример:

image

В начале вы видите обычный спрайт домика без затенения. На второй части картинки расположена карта нормалей этого дома, кодирующая нормали этого домика в цвет текстуры. У вектора есть (x,y,z) координаты, а у пикселя текстуры есть r,g и b компоненты, так что закодировать нормаль реально просто: Возьмем фасад дома, который направлен на юг. Его нормаль будет вектором с координатами [x:0, y:0.5, z:0]. (По хорошему, нормаль должна быть равна (0, 1, 0), но так как вектор мы определяем от -1 до +1, а кодировать надо в промежуток от 0 до 1, то, видимо, автор решил не запариваться и сразу считать нормали от -0.5 до +0.5. прим. перев.)

RGB значения не могут быть отрицательными, так что мы подвинем все значения на 0.5: [x:0.5, y:1, z:0.5]. Ну и ещё RGB обычно представляется в числе от 0 до 255, так что мы домножим на 255 и получим [x:128, y:255, z:128], или, другими словами, вектор «на юг» будет вот этим светло-зеленым на карте нормалей.

Теперь, когда у нас есть нормали, мы можем позволить графической карте сделать её магию.
Я использую ImpactJS, у него неплохая совместимость с WebGL2D. (Он платный, я рекомендую pixi.js или любая другая графическая библиотека с webgl рендерером. Если знаете ещё аналоги — пишите в комменты! прим. перев.) Используя WebGL2D мы можем легко добавить пиксельный шейдер для освещения:

#ifdef GL_ES
  precision highp float;
#endif

varying vec2 vTextureCoord;
uniform sampler2D uSampler;
uniform vec3 lightDirection;
uniform vec4 lightColor;

void main(void) {
  // Берем вектор нормали из текстуры
  vec4 rawNormal = texture2D(uSampler, vTextureCoord);

  // Если альфа-канал равен нулю, то ничего не делаем: 
  if(rawNormal.a == 0.0) {
    gl_FragColor = vec4(0, 0, 0, 0);
  } else {

    // Транслируем из RGB в вектор, а именно из 0..1 в -0.5..+0.5
    rawNormal -= 0.5;

    // Вычисляем уровень освещенности
    float lightWeight = 
      dot(normalize(rawNormal.xyz), normalize(lightDirection));

    lightWeight = max(lightWeight, 0.0);

    // И записываем в пиксель
    gl_FragColor = lightColor * lightWeight;
  }
}

Пара заметок: Это попиксельное освещение, которое немного отличается от вершинного освещения (обычного в 3d). У нас нет выбора, так как вершины в 2d бессмысленны (их всего 4 штуки для отображения плоскости на сцене). Но, вообще, это не проблема, попиксельное освещение гораздо более точное. Также следует отметить, что шейдер рендерит только освещение, без основного спрайта. Придется признать, я немного жульничаю, ведь на самом деле я не освещаю свой спрайт, а, скорее, затеняю его и в lightColor я передаю темно-серый цвет. Настоящее освещение пикселей, а именно повышение яркости, выглядит хуже, пиксели кажутся «вытертыми». У этой проблемы есть решения, но сейчас это непринципиально.

image

Часть вторая: рисование теней.


Отбрасывание теней в 3D — хорошо изученная проблема с известными решениями, типа рейтрейсинга или shadow-mapping’а. Однако, я затруднился найти какое-нибудь приемлимое готовое решение для 2d, пришлось делать самому, думаю получилось нормально, хотя и у него есть пара недостатков.

Вкратце, будем рисовать линию от пикселя на сцене к солнцу и проверять, есть ли какое-нибудь препятствие. Если есть — то пиксель в тени, если нет — на солнце, так что, впринципе, ничего сложного.

Шейдер принимает xyAngle и zAngle, которые отвечают за то, где находится солнце. Так как оно очень далеко, то лучи света будут параллельны, и, соответственно, эти два угла будут одинаковы для всех пикселей. Также, шейдер будет принимать карту высот мира. Она будет показывать высоту всех объектов, зданий, деревьев и т.д. Если пиксель принадлежит зданию, то значение пикселя будет примерно 10, и означать, что высота здания в данной точке — 10 пикселей.

Итак, шейдер начнет в пикселе, который надо осветить и, используя вектор xyAngle, будет продвигаться по направлению к солнцу мелкими шажками. На каждом из них мы будем проверять, если в данном пикселе карты высот что-нибудь.
image
Как только мы найдем препятствие, мы определим его высоту, и насколько высоким оно должно быть в данной точке чтобы преградить солнце (используя zAngle).
image
Если значение в карте высот будет больше, то всё, пиксель в тени. Если нет — мы продолжим искать. Но рано или поздно мы сдадимся и объявим, что пиксель освещен солнцем. В этом примере я захардкодил 100 шагов, пока что работает отлично.

Вот код шейдера в упрощенной/псевдо форме:

void main(void) {
  float alpha = 0.0;

  if(isInShadow()) {
    alpha = 0.5;
  }
  gl_FragColor = vec4(0, 0, 0, alpha);
}

bool isInShadow() {
  float height = getHeight(currentPixel);
  float distance = 0;

  for(int i = 0; i < 100; ++i) {
    distance += moveALittle();

    vec2 otherPixel = getPixelAt(distance);
    float otherHeight = getHeight(otherPixel);

    if(otherHeight > height) {
      float traceHeight = getTraceHeightAt(distance);
      if(traceHeight <= otherHeight) {
        return true;
      }
    }
  }
  return false;
}

А вот и весь код:

#ifdef GL_ES
  precision highp float;
#endif

vec2 extrude(vec2 other, float angle, float length) {
  float x = length * cos(angle);
  float y = length * sin(angle);

  return vec2(other.x + x, other.y + y);
}

float getHeightAt(vec2 texCoord, float xyAngle, float distance,
    sampler2D heightMap) {

  vec2 newTexCoord = extrude(texCoord, xyAngle, distance);
  return texture2D(heightMap, newTexCoord).r;
}

float getTraceHeight(float height, float zAngle, float distance) {
  return distance * tan(zAngle) + height;
}

bool isInShadow(float xyAngle, float zAngle, sampler2D heightMap,
    vec2 texCoord, float step) {

  float distance;
  float height;
  float otherHeight;
  float traceHeight;

  height = texture2D(heightMap, texCoord).r;

  for(int i = 0; i < 100; ++i) {
    distance = step * float(i);
    otherHeight = getHeightAt(texCoord, xyAngle, distance, heightMap);

    if(otherHeight > height) {
      traceHeight = getTraceHeight(height, zAngle, distance);
      if(traceHeight <= otherHeight) {
        return true;
      }
    }
  }

  return false;
}

varying vec2 vTextureCoord;
uniform sampler2D uHeightMap;
uniform float uXYAngle;
uniform float uZAngle;
uniform int uMaxShadowSteps;
uniform float uTexStep;

void main(void) {
  float alpha = 0.0;

  if(isInShadow(uXYAngle, uZAngle, uHeightMap, uMaxShadowSteps,
     vTextureCoord, uTexStep)) {

    alpha = 0.5;
  }

  gl_FragColor = vec4(0, 0, 0, alpha);
}

В uTexStep записана длина шага для проверки пикселей. Обычно это 1/heightMap.width или 1/heightMap.height, ибо в OpenGL координаты текстур это значения от 0 до 1, так что 1/разрешение как раз даст нам размер одного пикселя.

Заключение


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

image

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Из песочницы] Знакомство с языком программирования Deluge. Создание произвольной функции в системе Zoho CRM

В начале 2015 года мною была опубликована статья про Zoho CRM, в которой я описал функционал данной системы, но не рассматривал вопросы ее доработки. Сейчас я решил рассказать о доработках подробнее, так как на практике это оказалось очень актуальным. С системой я работаю уже больше года, и ее часто приходится дорабатывать под те или иные запросы пользователей.

Доработки системы Zoho CRM производятся на языке Deluge, разработанном самой компанией Zoho. Deluge интегрирован также в продукт Zoho Creator, но в данной статье мы рассмотрим использование языка именно в Zoho CRM.

Что такое Deluge?


Определение с сайта (перевод с сайта).

Deluge (Data Enriched Language for the Universal Grid Environment ) — это online язык программирования, который помогает добавлять различные типы бизнес-логики в произвольные функции и делать их более мощными.


Deluge и ZOHO CRM


В Zoho CRM язык Deluge используется при написании так называемой Custom function.

Custom function — это произвольная функция, которая позволяет дорабатывать поведение системы в соответствии с пожеланиями пользователей, не завязываясь на какие-то предустановленные настройки.


С чего я предлагаю начать знакомство с этим языком? На сайте компании Zoho есть много примеров его использования, но я решил привести реальный пример из своей практики. Очень простой и наглядный пример, в котором будут видны основные моменты работы с языком и демонстрация его возможностей.

Описание примера


Мы рассмотри, как создается функция, которая была реализована на одном из проектов. Функция называется Contacts Phone Validate. Задача функции состоит в том, чтобы проверять правильность заполнения пользователями номера телефона контактного лица в Zoho CRM.

Функция должна работать следующим образом:

  • пользователь вводит номер телефона контактного лица в Zoho CRM
  • если телефон введен в неверном формате, то система сообщает об ошибке посредством отправки электронного письма
  • если ошибки в формате ввода нет — номер телефона добавляется в систему

Телефон может быть неправильным по нескольким причинам:
  • Если он введен в неверном формате
  • Если такой номер уже имеется в системе

Итак, рассмотрим на этом примере, как происходит создание произвольной функции в системе Zoho CRM посредством языка Deluge.

Настройка функции производится непосредственно в учетной записи пользователя Zoho CRM и доступна пользователям с правами управления рабочим процессом Workflow.

Сразу хочу обратить ваше внимание на то, что ввиду неидеальной локализации системы Zoho CRM мы будем рассматривать настройку функции на английском языке.

Добавление произвольной функции


Добавление произвольной функции включает в себя этапы:
  • Программирование функции
  • Связывание функции с правилом рабочего процесса Workflow Rules

Рассмотрим подробно каждый из этапов.

1. Программирование функции


Для того, чтобы запрограммировать произвольную функцию выполните следующие действия:

1. Зайдите в Setup > Automation > Workflow > Custom Function
2. На странице Custom Functions нажмите Configure Custom Function
3. Далее нажмите Write your own

4. В открывшемся окне выполните следующие действия:

  • Укажите имя функции. Наша функция будет называться ContactsPhoneValidate. Далее нажмите на ссылку Edit Arguments.
  • Выберите модуль системы, в котором будет действовать функция. В нашем случае это модуль Contacts. Соотнесите данные функции с именем поля или с необходимым значением в модуле CRM. В нашем примере параметром функции является contactPhone, который соответствует полю Phone в модуле Contacts.

  • Нажмите Done

5. Напишите требуемую функцию в конструкторе Deluge Script.

В нашем примере функция выглядела следующим образом. Объявляем переменные:

mapVariable=map();// Создаем переменную типа   Map ( http://ift.tt/1lBomiv )
errorType=""; // В переменной храним текст ошибки


Проверяем корректность формата ввода и территориальную принадлежность:
if(input.contactPhone.startsWith(("+7"))) // Проверяем на +7, номер должен быть без +
{
        errorType="Формат Российского номера должен быть 7XXXXXXXXXX!";
}
else if(input.contactPhone.startsWith(("+89"))) // Проверяем на начало +89 
{
        errorType=("Префикс +89 запрещен в ZOHO CRM!");
}
else if(input.contactPhone.startsWith(("+84"))) // Проверяем на начало +84 
{
        errorType=("Префикс +84 запрещен в ZOHO CRM!"); // Сохраняем ошибку
}
else if(input.contactPhone.startsWith("7")) // Если начинается с 7 без плюса
{
        if(input.contactPhone.length()  !=  11) // но длина не равна 11 символам
        {
                errorType=("Длина российского номера должна быть равна 11 символам!"); 
        }
        else
        {
                phoneType="RUSSIA"; // Телефон российский
        }
}
else if(input.contactPhone.startsWith(("+"))) // Если имеется символ +, но без цифры 7, то номер международный
{
        phoneType="INTERNATIONAL";
}
else
{
        errorType="Номер телефона некорректный"; 
}


Проверяем на наличие ошибок:
if(errorType  !=  "") // Если переменная не пустая - ошибки есть
{
        mapVariable.put("Phone error","Неверный формат телефона");
        mapVariable.put("Phone","-");
        sendmail // отсылаем сообщение об ошибке на email пользователя
        [
                from:zoho.loginuserid  
                to:zoho.loginuserid    
                subject:"Phone format error | Неверный формат телефона"
                message:"Неверный формат телефона! См. инструкцию по CRM!<br>Phone format is not correct! Look through the CRM manual!<br>Error message for phone number <b>" + input.contactPhone + "</b> is: " + errorType + "!"
                content type:HTML
        ]
        updateAccount = zoho.crm.updateRecord("Contacts",input.contactId.toString(),mapVariable); // и вносим в базу данных CRM
}


Если с номером все в порядке, то проверяем на наличие дубликатов:
else // Если на данном этапе ошибки нет 
{
        contactPhone=input.contactPhone.replaceAll("\D",""); // Удаляем все не цифры
        if(phoneType  ==  "INTERNATIONAL")
        {
                contactPhone="+" + input.contactPhone;
        }
        mapVariable.put("Phone",input.contactPhone); 
        prevRecord = zoho.crm.searchRecords("Contacts","(Phone|=|" + input.contactPhone + ")",1); // Проверяем на наличие номера в базе данных

        if(prevRecord.size()  >  0) // если номер существует
        {
                i=0;
                for each account in prevRecord // проверяем принадлежит ли номер текущему контакту
                {
                        if((((account).get("CONTACTID")).toLong()  !=  input.contactId)  &&  (i  ==  0))  // если номер не принадлежит текущему контакту
                        {
                                i=1;
                                mapVariable.put("Phone error","В контакте задвоен телефон!"); 
                                mapVariable.put("Phone","-");
                                sendmail // Высылаем информацию о том что контакт с таким телефоном существует
                                [
                                        from:zoho.loginuserid
                                        to:zoho.loginuserid
                                        subject:"Duplicate Phone | Задвоен телефон"
                                        message:((((((("В контакте задвоен телефон<br>Phone in Contact(" + (account).get("CONTACTID")) + ") is duplicated<br>Phone ") + (account).get("Phone")) + "<br>Owner: <a href='http://ift.tt/1XFxXGr") + (account).get("SMOWNERID")) + "'>") + (account).get("Contact Owner")) + "</a><br>Свяжитесь с владельцем контакта"
                                        content type:HTML
                                ]
                        }
                }
        }
        else
        {
                mapVariable.put("Phone error",""); 
        }
        updateAccount = zoho.crm.updateRecord("Contacts",input.contactId.toString(),mapVariable); // Обновляем  данные контакта.
}


6. После написания функции необходимо проверить код. Нажмите Save and Execute Script желаете проверить работу скрипта введя значения аргументов в всплывающем окне. Если ошибок нет, ваш скрипт готов к ассоциированию с правилом рабочего процесса Workflow.

7. Нажмите Save для сохранения скрипта.

2. Связывание функции с правилом рабочего процесса Workflow Rules


Чтобы связать пользовательскую функцию с правилом рабочего процесса зайдите в Setup > Automation > Workflow Rules, далее нажмите Create Rule.

Выполните следующие действия:

1. Выберите необходимый модуль из раскрывающегося списка (в нашем случае это модуль Contacts), задайте название и описание правила.

2. В разделе Rule Trigger выберите пункт Create or Edit

3. В разделе Rule Criteria устанавливаем критерии
4. В разделе Actions выберите Call Custom Function

  • Нажимите +

  • На странице Configure Custom Function перейдите по ссылке Customized Functions

  • Выберите необходимую ранее созданную функцию. Мы выбираем функцию СontactsPhoneValidate

5. Нажмите Save

Теперь наша функция связана с правилом рабочего процесса Workflow, и при вводе неверного формата телефона, или при дублировании номера пользователь получает сообщение об ошибке.

Заключение


Мы увидели, как с помощью языка Deluge создать в Zoho CRM функцию и как эта функция работает внутри системы. Создание функции на языке Deluge — не сложная вещь, которая позволяет дорабатывать поведение системы Zoho CRM в соответствии с вашими пожеланиями и значительно расширяет возможности программы.

Об использовании Deluge в другом продукте компании Zoho — Zoho Creator я расскажу уже в следующей статье.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.