Случайная выборка с учетом веса

Один из распространенных приемов в игровой логике – использование элемента случайности. Он используется, например, при машинной генерации уровней, лабиринтов, или когда на локации появляются случайные враги или бонусы. При этом зачастую требуется выбрать случайный объект из какого-либо списка с учетом того, что некоторые из них имеют большую вероятность выбора, чем другие – то есть, имеют больший “вес”.

Я написал особую реализацию алгоритма такой выборки – она работает не с обычными массивами, а с перечислениями (enum). В шаблон функции передаются два перечисления – самих элементов и их весов. Этот пример иллюстрирует также богатые возможности метапрограммирования, CTFE и интроспекции в D.

import std.stdio;
import std.traits;
import std.random;
import std.algorithm;

T weightedRandomEnum(T, W)()
    if (isNumeric!W &&
        is(T == enum) && is(W == enum) && 
        EnumMembers!T.length == EnumMembers!W.length)
{
    enum members = [EnumMembers!T];
    enum weights = [EnumMembers!W];
    enum weightsSum = reduce!("a + b")([EnumMembers!W]);
    
    auto randomNumber = uniform(0, weightsSum);
    
    foreach(i, weight; weights)
    {
        if (randomNumber < weight)
            return members[i];
        else
            randomNumber -= weight;
    }
    
    assert(0, "Should never get here");
}

enum Color
{
    Red, Yellow, Green, Blue
}

enum Weights
{
    Red = 100, 
    Yellow = 20, 
    Green = 20, 
    Blue = 5
}

void main()
{
    foreach(i; 0..10)
        writeln(weightedRandomEnum!(Color, Weights));
}

Обновление dlib

Состоялось серьезное обновление набора библиотек dlib. В числе нововведений:

  • Появилась начальная поддержка быстрого преобразования Фурье (FFT) в dlib.image. Возможна фильтрация и свертка изображений (со стороной 2^n) в частотной области;
  • Обновлен пакет dlib.math, добавлена реализация комплексных и дуальных чисел, а также внесены исправления и дополнения в dlib.math.matrix3x3 и dlib.math.matrix4x4;
  • Обновлен пакет dlib.geometry, добавлен класс ориентированных боксов (OBB), трехмерных треугольников и полигональных мешей. Реализована проверка на пересечение между сферой и треугольником, а также сферой и OBB. Добавлен модуль dlib.geometry.bezier с реализацией кривых Безье.

Иллюстрация FFT-свертки: быстрое синтетическое боке (оптическое размытие):

Изменения доступны в ревизии r21 и выше. В ближайшем будущем ожидается первый релиз проекта.

http://code.google.com/p/dlib/

Исходники физического движка

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

physics-src-alpha0.zip (0.95 Мб).

Пример физики на движке Chipmunk

Пример рисования мышью многоугольников, которые сразу же начинают «жить» в физическом мире. В качестве физического движка используется ChipmunkD — прямой порт Chipmunk на D. Демка может быть использована в качестве основы для physics-based 2D-игры. Единственное ограничение — поддерживаются только выпуклые многоугольники, неконвексная геометрия просто отсеивается и не тесселируется до простых форм.

В архиве — исходники и сборки для Win32 и Linux x86:
polyshaper-all-platforms.zip (1.16 МБ)

Внимание! Пример писался достаточно давно — исходный код, скорее всего, не скомпилируется современными версиями DMD без дополнительных «танцев с бубном».

Обновление dlib

Была значительно обновлена коллекция библиотек dlib: добавлены новые модули в пакет geometry (реализация AABB и сфер, а также пересечений между ними), исправлен баг с нахождением обратной матрицы 4×4, добавлен модуль dlib.math.matrix3x3, а также несколько полезных функций для векторов, матриц и кватернионов. Изменения доступны в ревизии r12 и выше.

http://code.google.com/p/dlib/

Эффект затенения

«Честные» динамические тени в аркадных 3D-играх не всегда бывают уместны. Как правило, разработчики ограничиваются статическими предрассчитанными тенями от неподвижных объектов и простым темным кружочком на земле под персонажем – дешево и сердито =)  Я решил дополнить этот нехитрый метод одной простой, но важной деталью: изменение яркости персонажа в зависимости от его местоположения – в тени или на свету. 

Цвет для яркости будет считываться из карты освещения, в которой «запечены» все статические тени на карте.

Допустим, у вас есть некий блок кода, в котором вы находите точку на поверхности полигона под ногами персонажа:

// Проверяем факт пересечения
IntrStatus istatus = character.downRay.intersectTriangle(tri);
if (istatus.hit)
{
  // Извлекаем точку пересечения
  Vector3f ipt = istatus.intersectionPoint;  
  
  if (ipt.y > currentFloorHeight)
  {
    currentFloorHeight = ipt.y;
    
    // Берем материал полигона
    Material mat = materialByIndex[tri.matIndex];
    
    // Если нет карты освещения (текстура 1), то ничего не делаем
    if (mat.textures[1])
    {
      // Берем изображение карты освещения
      Image lightmap = imageByIndex[mat.textures[1].imgIndex];
      
      // Находим текстурные координаты точки пересечения
      Vector2f tc = triObjSpaceToTexSpace(tri.vertices, tri.texCoords2, ipt);
      
      // Конвертируем текстурные координаты в дискретные
      uint imgX = cast(uint)(tc.x * lightmap.width - 0.5f);
      uint imgY = cast(uint)(tc.y * lightmap.height - 0.5f);
      
      // Считываем цвет пикселя c карты освещения
      Color lumel = lightmap[imgX, imgY];
      
      // Применяем полученный цвет к материалу персонажа
      chMaterial.ambientColor = lumel;
      chMaterial.diffuseColor = lumel;
      chMaterial.specularColor = lumel;
    }
  }
}

Осталось определить функцию triObjSpaceToTexSpace. Она будет использовать барицентрические координаты:

Vector2f triObjSpaceToTexSpace(
  Vector3f[3] vertices, 
  Vector2f[3] texCoords, 
  Vector3f point)
{ 
  Vector3f v0 = vertices[2] - vertices[0];
  Vector3f v1 = vertices[1] - vertices[0];
  Vector3f v2 = point - vertices[0];

  float dot00 = dot(v0, v0);
  float dot01 = dot(v0, v1);
  float dot02 = dot(v0, v2);
  float dot11 = dot(v1, v1);
  float dot12 = dot(v1, v2);

  float invDenom = 1.0f / (dot00 * dot11 - dot01 * dot01);
  float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
  float v = (dot00 * dot12 - dot01 * dot02) * invDenom;
      
  Vector2f t2 = texCoords[1] - texCoords[0];
  Vector2f t1 = texCoords[2] - texCoords[0];

  return texCoords[0] + t1 * u + t2 * v;
}

Atrium собирается последней версией LDC

Совсем недавно вышла бета-версия готовящегося к релизу LDC 0.10.0 — компилятора D, использующего LLVM для генерации машинного кода.

Решив проверить LDC на практике, я только что собрал им Atrium. Результат очень обрадовал, при компиляции в релизном режиме и с уровнем оптимизаций -O3, LDC выдал весьма шустрый код: ~100 FPS против ~80 у DMD 2.060! При этом я всего один раз столкнулся с необходимостью поправить исходники — при использовании ассоциативного массива указателей.

Как вывод: LDC вполне созрел для использовании в качестве основного компилятора D под Linux — во всяком случае, для работы с OpenGL и графикой реального времени. К сожалению, я не в курсе, как у него обстоят дела c поддержкой Windows — надеюсь, со временем этот вопрос устаканится. 

Журнал «FPS» №21

Вышел 21 номер электронного PDF-журнала «FPS«, посвященного разработке игр, программированию, компьютерной графике и звуку. В этом номере можно отметить следующие материалы:

> Blender: обзор дополнений (выпуск 3)
> Tears of Steel: роботы тоже плачут…
> Эффект «старинного фото» в GIMP
> Журнал GIMP Magazine
> Язык D: новости
> Семь мифов о D
> Сборщик мусора — враг или друг?
> Юникод в OpenGL. Раз и навсегда
> Почему я выбираю Linux?
> Патентные войны

Номер доступен для онлайн-чтения и загрузки на сервисе Issuu.com и Документах Google. Последние новости по проекту вы можете узнать в публичной странице журнала в социальной сети Google+.

Передискретизация в dlib.image

Передискретизация (ресэмплинг) изображения — это всего-навсего изменение его разрешения в пикселях. Такую функцию должна включать любая уважающая себя библиотека обработки изображений, и с сегодняшнего дня dlib — не исключение. В пакете dlib.image.resampling доступны четыре алгоритма ресэмплинга, которые часто встречаются в графических редакторах: линейная интерполяция (Linear или Nearest Neighbor), билинейная (Bilinear), бикубическая (Bicubic) и фильтр Ланцоша (Lanczos). Ниже представлены результаты их работы на примере увеличения разрешения картинки с 64х64 до 256х256.

Оригинал




Linear
Самый простой и, следовательно, самый быстрый способ изменить размеры изображения. Известен также как интерполяция методом ближайшего соседа (Nearest Neighbor). Для промежуточных пикселей выбирается ближайшее известное значение.

Bilinear
Для получения промежуточного значения производится линейная интерполяция между четырьмя ближайшими пикселями. Это позволяет несколько сгладить переходы. Билинейный фильтр — неплохой компромисс между скоростью и качеством результата.

Bicubic
Промежуточные значения вычисляются путем свертки ядра 4×4 специальной бикубической функцией. Достаточно медленный алгоритм, но зато дает наименее пикселизированный результат.

Lanczos
У бикубической интерполяции есть недостаток — изображение получается несколько размытым. Чтобы сохранить четкость, можно воспользоваться фильтром Ланцоша. Он немного увеличивает пикселизацию, но создает контрастные области вдоль контуров для повышения четкости. В моей реализации я использовал ядро 7х7. Очень медленно, зато качественно.

Существуют и другие алгоритмы — постепенно они тоже будут добавлены в библиотеку.
Изменения доступны в dlib ревизии r5 и выше.

Начать — наполовину сделать

В этом блоге я буду публиковать свой прогресс в области разработки 3D-игр на языке программирования D с использованием OpenGL. Хочу предупредить — ничего определенного я не обещаю, геймдев для меня — всего лишь хобби. Я не из тех, кто ставит перед собой заведомо недостижимые цели — мне нравится сам процесс =)