直交座標と六角形グリッドについて

ゲームで六角形メッシュがどのように広く使用されているかを説明する必要はありません(それだけではありません)。 特定の六角形のセルの中心と頂点の座標を見つける方法は非常に明白です。 逆変換(つまり、座標xとyを持つ指定されたポイントが含まれるセルの検索)は、それほど簡単ではありません。 このトピックでは、このトピックについて説明します。



プロローグ



私には長年の夢があります。面白いゲームを書くことです。 私は非常にゆっくりと結果に向かって動いており、おそらく、まったく正しくありませんが、これはそれについてではありません。 次の自由時間では、六角形のグリッドに基づいて、将来のゲームの概念を考え出しました。 その国にいて、インターネットの兆候がないため、私自身は「六角形」座標と直交座標の間で変換するアルゴリズムを探すことを余儀なくされました。 その後、よく知られているアルゴリズムで起こったことを比較し、驚いた。「ピクセルから六角形」のリクエストでのGoogleの最初のリンクは、私よりもはるかに時間がかかり、混乱を招くアルゴリズムの説明を提供した。 私がここで見つけたものと同等の方法。



トピック別



グリッドの説明


はじめに、長方形の座標を基準にして六角形のセルを配置する2つの方法を区別します。

画像

私は、軸をメイン、左、右からの文字「m」、「l」、「r」と呼びました。 (当初、これはすべて紙で行われました。mはx軸と一致し、y軸は見上げました。したがって、「左」と「右」はここではかなり任意です。)したがって、m軸は長方形軸の1つと一致します。 l軸はこの長方形の軸を中心に60度回転し、2番目の長方形の軸を中心に30度回転します。 r軸は、最初の長方形軸で60度、2番目の長方形軸で150度です。 セルの番号付けは、軸mおよびlに沿って行われます。



グリッドを実装するために、「フィールドマネージャー」を作成しました。このフィールドマネージャーは、グリッドの向きと周期に関する情報をオブジェクトに格納するクラスです。



//      Qt- , , // ..      ,  //  typedef-. typedef QPointF MyPointF; typedef QPoint MyPointI; //    class FieldManager { public: //  ,     m. enum Orientation { OX, OY }; //   . FieldManager(double period = 1, Orientation orientation = OX); //       ( ). MyPointF CellCenter(int m, int l) const; //   ,     (x, y) ( ). MyPointI CellInd(double x, double y) const; private: //  . double m_period; //  . Orientation m_orientation; };
      
      







直接変換


 //    ,  //     . const double sin60 = sqrt(3.0) * 0.5; const double cos60 = 0.5; const double sin30 = cos60; const double cos30 = sin60; //  . FieldManager::FieldManager(double period, Orientation orientation) : m_period(period), m_orientation(orientation) { } //  .   . MyPointF FieldManager::CellCenter(int m, int l) const { double along_coord = m_period * (m + l * cos60); double other_coord = m_period * l * sin60; if (m_orientation == OX) return MyPointF(along_coord, other_coord); else return MyPointF(other_coord, along_coord); }
      
      







逆変換


逆変換で、私は長い間苦しみました。 かさばるサイクルや条件ステートメントをプログラムに押し込みたくありませんでした。 m、l、およびr軸の等値線(つまり、これらの軸に対応する一定の座標値を持つ点のセット)が平面を多くの三角形に分割したことは明らかでした(私は周期の半分で使用した各軸に沿ったステップとして):

画像

したがって、各三角形は3つの数字に関連付けられています。 これらの三角形を6つずつ1つの六角形にグループ化する操作を思い付くことが残っただけです。 また、変換は線形でなければならないことも明らかです。 グリッドの寸法は空間で変化しません。 その結果、私は正しい変換を見つけました:



 //  . MyPointI FieldManager::CellInd(double x, double y) const { //   . double along_coord = (m_orientation == OX) ? x : y; double other_coord = (m_orientation == OX) ? y : x; //     m, l  r (   ). //       , // , ,         //    int m = floor(2 * along_coord / m_period); int l = floor(2 * (cos60 * along_coord + sin60 * other_coord) / m_period); int r = floor(2 * (cos60 * along_coord - sin60 * other_coord) / m_period); // , ,   : return MyPointI( floor((m + r + 2) / 3.0), floor((l - r + 1) / 3.0)); }
      
      







この変換を見つけるために、次のアクションが役立ちました。紙の上にこれらの三角形のグリッドを描き、一定の値(丸めを考慮)で六角形の「線」を選択し、結果の三角形を組み合わせて見ました。 それから彼はl軸についても同じことをしました。 l軸に沿った六角形のストリップの場合、m軸とr軸はスペースをこのストリップと交差しないひし形に分割する(つまり、完全に内側または外側にある)ことは明らかでした。 さらに、mが固定されている場合、このバンドはlが異なる3つの菱形に対応し、逆も同様です。 これは、3で割ることからの丸めが発生した場所です。



おわりに



このメモで、この物語は終わります。 注意を奪わなかったすべての人に感謝します。 私は誰かを助けることができると思います。



PS

また、いつかゲームが光を見るようになり、それについての記事もここに表示されることを願っています。



All Articles