ゲームプログラマになる前に覚えておきたい技術 Ch.2 - 三角形をクルクルさせてみた

「なんか回せそうだ」と思ったら回してみたくなったので回してみた。こんなことしてるから進まないんだよなぁ。

最初 int と float を使って cast しまくってたんだけど、三角関数な関数が double 用みたいだったんで基本 double にした。

三角形は Triangle クラスにして、頂点 a, b, c を Point クラスで持つようにして、include は Triangle の method にした。

回転させる処理は Triangle::rotate と rotateVertex で

  1. 三角形の中心座標を計算(Triangle::center)
  2. 三角形の各頂点を rotateVertex で動かす

rotateVertex でやってるのは・・・説明がむずかしい(絵を描かないと理解できない人)。三角関数の使い方なんて覚えてなかったのでまた数学のお勉強をする羽目に。

描画は drawTriangle に抜き出して、範囲チェックはここでやる。まず Triange::maxX, Triangle::maxY で三角形の頂点のうち一番大きな x, y を持ってくる。前のフレームで描画した三角形を消さなきゃならんので、この範囲が小さくなっても描画する範囲は元のままにするために static にして、元の範囲より広がったら描画する範囲を広げる。ただし window の大きさである width, height より大きかったら width, height までにしとく。三角形の頂点が window に収まらない位置であっても、window に収まる範囲だけ描画しておけば無問題。

#include "GameLib/Framework.h"
using namespace GameLib;

#define _USE_MATH_DEFINES
#include <cmath>


/** class Point */
class Point {
public:
   double x, y;
   Point (double x, double y) : x(x), y(y) {}
   Point (int x, int y) {
      this->x = static_cast<double>(x);
      this->y = static_cast<double>(y);
   }
};

/** class Line */
class Line {
public:
   double gradient;  //傾き
   double intercept; //切片
   Line (const Point a, const Point b) {
      if ( a.x == b.x ) {
         // 線が垂直な場合
         gradient = HUGE_VAL;
         intercept = a.x;
      } else {
         gradient = (a.y - b.y) / (a.x - b.x);
         intercept = a.y - (a.x * gradient);
      }
   }
};

/** class Triangle */
class Triangle {
public:
   Point a, b, c;

   Triangle (Point a, Point b, Point c) : a(a), b(b), c(c) {}

   bool include (const Point p) const;
   Point center () const;
   int maxX () const { return static_cast<int>( max(a.x, max(b.x, c.x)) ); }
   int maxY () const { return static_cast<int>( max(a.y, max(b.y, c.y)) ); }

   void rotate (double r);

};


/** function prototypes */
int relation (const Line l, const Point p);
void rotateVertex (Point* p, const Point& center, double rad);
void drawTriangle ();

/** global variables */
Triangle* gTriangle = 0;



bool Triangle::include (const Point p) const {
   "Point p が Triangle の内側なら true を返す";
   Line ab(a, b), bc(b, c), ca(c, a);
   return ( relation(ab, p) == relation(ab, c) &&
            relation(bc, p) == relation(bc, a) &&
            relation(ca, p) == relation(ca, b) );
}

Point Triangle::center () const {
   "Triangle の中心点を返す。";
   Point center( (a.x + b.x + c.x) / 3,
                 (a.y + b.y + c.y) / 3 );
   return center;
}

void Triangle::rotate (double r) {
   "Triangle を r ラジアン回転させる";
   Point p = center();

   rotateVertex(&a, p, r);
   rotateVertex(&b, p, r);
   rotateVertex(&c, p, r);
}



void rotateVertex (Point* p, const Point& center, double rad) {
   "Point p を Point center を中心に rad ラジアン回転した位置へ移動";
   double deltaX = p->x - center.x;
   double deltaY = p->y - center.y;
   double oRad = atan(deltaY / deltaX);
   double R = deltaY / sin(oRad);

   p->x = center.x + (cos(oRad + rad) * R );
   p->y = center.y + (sin(oRad + rad) * R );
}


int relation (const Line l, const Point p) {
   "Point p が Line l に対してどっち側にあるかを 1, 0, -1 で返す";
   double LineX;
   if ( l.gradient == (HUGE_VAL) ) {
      LineX = l.intercept;
   } else {
      LineX = (p.y - l.intercept) / l.gradient;
   }
   return ( (p.x >  LineX) ? 1 :
            (p.x == LineX) ? 0 :
            -1 ); // (p.x < LineX)
}


void drawTriangle () {
   "描画";
   unsigned* vram = Framework::instance().videoMemory();
   static int width = Framework::instance().width();
   static int height = Framework::instance().height();

   static int rangeX, rangeY;

   rangeX = min(max(gTriangle->maxX(), rangeX), width);
   rangeY = min(max(gTriangle->maxY(), rangeY), height);

   for ( int y = 0; y < rangeY; y++ ) {
      for ( int x = 0; x < rangeX; x++ ) {
         Point p(x, y);
         vram[y * width + x] = ( gTriangle->include(p) ) ? 0xFF6666 : 0x000000;
      }
   }
}


namespace GameLib {
   void Framework::update () {
      "更新";
      if ( gTriangle == 0 ) {
         Point a(104, 38), b(32, 83), c(13, 52);
         gTriangle = new Triangle(a, b, c);
         drawTriangle();
      } else {
         gTriangle->rotate(0.01);
         drawTriangle();
      }
   }
}