Как бросить кости без OpenGL

  • Tutorial
image

Необязательное вступление
Разработчики приложений под iOS зарабатывают не на собственных творениях, а на сторонних заказах. Создав себе имя славного парня, который творит чудеса с iPad-ом, рано или поздно Вы будете получать предложения от знакомых своих знакомых.
-Алло! Напиши что-нибудь под iOS.


Позвольте несколько советов.
Если, по Вашему мнению, работы более чем на две недели, отказывайтесь.
Если на неделю — соглашайтесь за $5000.
Если на 2 дня — за $1000.

Еще одно правило — чем ближе круг знакомств с заказчиком — тем выше гонорар. С близкими друзьями — 100% предоплата.
Поверьте, в этом случае число мусорных проектов резко уменьшится, а уважение к Вам резко возрастет.


Один хороший человек захотел сделать электронную книгу под iOS, коллекцию афоризмов. Фразы вылетают случайно, данные предоставлены в формате комма сепарейтед валью. С флешкой и устным ТЗ он пришел к другу-программисту. Программист оценил примерный объем работы
  • Конвертируем данные в sqlite;
  • Заводим три UIView (левый, правый и центральный);
  • В каждый UIView добавляем UITextView и UILabel;
  • Обрабатываем нажатие touchesBegin для листания афоризмов вправо-влево;
  • Добавляем кнопку — показать случайный афоризм.
  • Добавляем закладки.
  • Получаем 1000 долларов США


Работы на 2 дня, программист согласился.
Однако в ТЗ было еще одно условие — при случайном выборе афоризма по экрану должен кататься игральный кубик. Самый обыкновенный, из шести граней.

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

Стон программиста
-OpenGL,- с тоской подумал программист. 150 строк кода только для инициализации GLKView и чтения текстур, а если возиться с шейдерами, то лучше Вольфенштейн 2048 доделать. Неужели нельзя простой трехмерный кубик нарисовать без OpenGL? Использовать обыкновенный UIImageView?..
Оказалось, можно.


Как нарисовать трехмерный кубик без использования OpenGL


В обычных играх UIImageView *p использует собственный набор функций и полей для

  • перемещения в точку (x,y), например p.center = CGPoint(x,y);
  • масштабирования, например p.transform = CGAffineTransformMakeScale(sx,sy);
  • вращения, например p.transform = CGAffineTransformMakeRotate(alpha);

Однако, существует прямой доступ к матрице двумерного преобразования UIImageView

             UIImageView *p = [dice objectAtIndex:i];
             CGAffineTransform diceTransform = CGAffineTransformMake(a, b, c, d, tx, ty);
             p.transform = diceTransform;


Матрица двумерного преобразования CGAffineTransform имеет 6 параметров

struct CGAffineTransform {
  CGFloat a, b, c, d;
  CGFloat tx, ty;
};


Любая точка изображения преобразуется согласно формуле

CGPointApplyAffineTransform(CGPoint point, CGAffineTransform t)
{
  CGPoint p;
  p.x = a * point.x + c * point.y + tx;
  p.y = b * point.x + d * point.y + ty;
  return p;
}


Для примера, чтобы повернуть картинку на угол angle, матрица преобразования примет вид


   t' = [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] 


Для перемещения картинки-изображения на вектор (tx,ty), матрица преобразования примет вид

  t' = [ 1 0 0 1 tx ty ] 


И так далее.

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

Рассмотрим грань кубика. Квадратную картинку необходимо разбить на два треугольника. Пустые пикселы делаем прозрачными. Сохраняем изображения в файлы dice_1.png и dice_2.png
image

В проекте делаем создаем две переменные с указанными картинками.
UIImageView *p1 =  [[UIImageView alloc]  initWithImage:[UIImage imageNamed:@"dice_1"] ];
UIImageView *p2 =  [[UIImageView alloc]  initWithImage:[UIImage imageNamed:@"dice_2"] ];


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

Рассмотрим верхний треугольник
image

который должен преобразоваться на экране в треугольник произвольного вида с вершинами в точках (x1,y1), (x2,y2), (x3,y3)
image

Таким образом, мы имеем 6 линейных уравнений с 6-тью неизвестными.
В результате несложных преобразований получаем решение для 6-ти неизвестных a,b,c,d,tx,ty;

                float ddt = 1.0/xsize;
 
                float a = ddt*(x3-x2);
                float b = ddt*(y3-y2);
                float c= ddt*(x2-x1);
                float d= ddt*(y2-y1);
                
                float tx = 0.5*(x1+x3);
                float ty = 0.5*(y1+y3);


Здесь xsize — линейный размер в пикселах исходной картинки. Например xsize = 100 для картинки размером 100 на 100.
Задача решена.

Короткое видео
Видео приложения, демонстрирующее алгоритм.
Кубик отображается при помощи 6*2 = 12 треугольников.



А вот версия алгоритма, внедренная в старую, классическую игру для сглаженных кубиков.
Каждый кубик отображается при помощи 18*2 + 4 = 40 треугольников (сглажены грани и вершины).



AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 22

    +10
    Это очено здорово. А просто несколько анимированных png нельзя было использовать?
      +2
      Теоретически можно. Как это сделано в Talking Tom — с первого взгляда трехмерная игрушка. На самом деле набор подготовленных картинок. Однако при этом объем приложения возрастает катастрофически. Для кубика надо 36 * 18 = 700 картинок сформировать, с шагом 10 градусов по каждому углу.
        +1
        сделать 20, но анимацию кубика по всему полю ^_^
        Слишком подло.
      0
      Очень здорово! Может кто-то сталкивался с аналогичной задачей по бросанию кубиков под html5 мобильные приложения? Как решали?
      +5
      Прикольно. Я заметил, что у Вас во втором видео для пущей объемности еще и некое подобие теней. А вот как это достигалось?
        0
        Точно так же- рисуем в режиме полупрозрачности 6 треугольников черного цвета. Со стороны наблюдателя (источника света) всегда видно три грани кубика.
        0
        Хех, мы подобным способом в универе крутили всякие фигуры, прям живо вспомнился катающийся дорожный знак. =)
          +3
          Интересное, хотя в общем-то логичное решение.

          P.S.: Если уж игра делалась для мобильных устройств, можно было сделать так, чтобы кубики катались от положения устройства и ускорения. Потряс планшет — кубики перемешались. Повернул — скатились в угол и т.д.
            +3
            А почему Вы не сделали все на CALayer, ведь все то, что можно представить плоскостями отлично анимируется с помощью слоев «без» openGL. К примеру

            www.cocoanetics.com/2012/08/cubed-coreanimation-conundrum/
            weblog.invasivecode.com/post/29307073330/core-animation-transform-layer


            Более того, я даже помню wwdc шное видео, где Apple рассказывала о одной своей «кубической» анимации в месенджере и проблемах, с которыми они столкнулись
              0
              Спасибо, что упомянули про слои. Но, фактически, CALayer — это прослойка opengl-ная, много подготовительного кода требовалось, в отличие от ImageView.
                0
                Но UIImageView не далеко отошел от CALayer. Без исходного кода сложно судить, но вроде как Вы модифицировали матрицу преобразований, вот только вместо банальных CGAffineTransformRotate сами высчитывали коэффициенты матрицы преобразований. Как академическая задача — шикарно, но как практическая… Не хотел бы я этот код поддерживать :)
                  +1
                  Код действительно сложно поддерживать — он состоит из двух строчек инициализации
                      IBOutlet UIImageView *p;  // картинка уже в XIB файле
                      float xs = 1.0/100.0;  // размер картинки по ширине
                  


                  и одной строчки в цикле для картинки

                      p.transform = CGAffineTransformMake(xs*(x3-x2), xs*(y3-y2), xs*(x2-x1), xs*(y2-y1), 0.5*(x1+x3), 0.5*(y1+y3));
                  

                    +4
                    повторюсь, обожаю иронию
                    Но тем не менее, сложность поддержки определяется не количеством строк. ++i + ++i менее понятен, чем его развернутый вариант

                  0
                  Какого кода, если к любому UIView можно прицепить неограниченое число CALayer и задать каждому свой CA3DTransform? Прослойка-то понятно, весь CoreAnimation на GL сделан. В целом ваш фокус родом из 90х, тогда без аппартной акселерации приходилось имитацию 3д делать деформацией отображаемой картинки. Наверняка вы тоже баловались трюком с пропуском каждого n-го столбца и/или строки для имитации повернутой на сколько-то градусов плоскости.
                +11
                Так вот для чего нужна математика в программировании! :)
                  0
                  Далеко не только это.
                  +4
                  Если проекция изометрическая (не перспективная), то квадратные грани перейдут в параллелограмы. В этом случае не нужно резать грань на два треугольника, достаточно применить одно аффинное преобразование ко всему квадрату, так как аффинное преобразование переводит параллелограмм в параллелограмм.

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

                  Еще в формулах где-то ошибка. Если подставить точку (0, 0), на выходе получится (0.5*(x1+x3), 0.5*(y1+y3)).

                    0
                    Формулы первого курса, умышленно перевёрнуты — молодец, что заметил. Данный алгоритм я использовал для более общего случая отрисовки трапеций (для трассы бобслея) — поэтому выбрал треугольники.
                      0
                      Мой предыдущий ответ на Ваш комментарий прошу считать недействительным. Центр исходной картинки имеет координаты (0,0). Поэтому вершины смещены на -50 пикселов. Соответствующее изображение в статье исправил.
                      0
                      Один в один мои первые потуги с 3d анимацией на бейсике :)
                        0
                        Эх, помнится, делал я как-то 3D-тетрис на плоскости, простыми примитивными drawLine, drawRect,… :)

                        Only users with full accounts can post comments. Log in, please.