Grasshopperでルービックキューブを作成してみました。pythonやC#は使用せず、Anemoneというプラグインを使用しております。インストールしていない方はこちらからインストールできますので、インストールしてから望んでください。難易度的にはデータツリーを理解していないとかなり苦しいかと思います。Panelコンポーネントでデータの中身を確認しながら、進めていただければと思います。こちらのチュートリアルは動画化しております、動画の方がよい方は以下のリンクからどうぞ!また、完成版のgrasshopper fileをgithubにpushしましたので、必要であればこちらからダウンロードできます。

目次
概要

上画像がプログラムの全体像となっております。各ブロックごとに、説明していこうと思います。
各キューブの中心点を作成

- Squareコンポーネントで2×2のグリッドを作成し、頂点を取得します。
- Linear Arrayコンポーネントで取得した頂点をZ方向に複製します。3点×3点×3点=27点生成されたかと思います。これを各キューブの中心点としていきます。
回転させる点を囲うBoxを生成

- XY平面・YZ平面・ZX平面、それぞれ3面回転させる面があるかと思います。1面を回転させるのに、9ブロックを回転させていくかと思いますが、それぞれグループ化していきます。
- List Itemコンポーネントで適当に、中心とその両隣の3点を取得します。Index番号で[10, 13, 16]番目を取得しています。
- もう1つList Itemコンポーネントを用意し、全体の中心点、Index番号で13番目を取得します。
- Center Boxコンポーネントで取得した3点を中心としたBoxを3つ生成します。0.3×0.3×3のBoxを生成しております。
- Rotateコンポーネントを2つ用意し生成した3ボックスを回転させます。上画像の様に、9ボックス生成します。各ボックス内に、回転させる際の9点が入っている状態です。
Stream Filterで生成したBoxを選択化

- 先ほど生成したBoxをList Itemコンポーネントで取り出します。
- Stream FilterコンポーネントでBoxを選べるようにします。Number Sliderコンポーネントの値によって、選べるようになってます。
- Custom PreviewコンポーネントとColour Swatchコンポーネントで色付けしておきましょう。透明に色を付けたものを使用しております。
選んだBoxに対する回転平面を取得

- Stream Filterコンポーネントで選んだBoxに対して、回転させる平面を取得していきます。上画像の例ですと、XY平面に広い面が向いているBoxはルービックキューブ全体の中心とするXY平面に平行に回転させていくかと思います。なので、Stream Filterコンポーネントを更に用意し、選んだBoxに対して平行な平面を出力するようにしていきます。
- Plane Originコンポーネントで出力した平面の中心点を、ルービックキューブの中心点とした平面を取得します。
ブロックの生成

- 次に、ブロックを生成していきます。Center Boxコンポーネントで最初に生成した27点を中心としたBoxを生成し、各ブロックを作成します。各辺は0.48として、隣り合うブロック同士が若干隙間が空くようにしております。
- Deconstruct Brepコンポーネントで各ブロックを分解し面を取得後、Areaコンポーネントで面の中心点を取得します。
面を色ごとにグループ化

- 面を色ごとにグループ化していきます。6面(赤・青・白・黄・緑・オレンジ)+外部に面しない(黒)の7色7グループに分けていきます。
- DeconstructコンポーネントでAreaコンポーネントで取得した各面の中心点の座標をXYZ成分に分けます。
- BoundsコンポーネントでXYZ成分の最大値と最小値のドメインを取得し、Deconstruct Domainコンポーネントで最大値・最小値を取得します。
- Member Indexコンポーネントで最大値・最小値があるIndex番号を取得し、List Itemコンポーネントで面のリストから該当するIndex番号を取得することで、各色に該当する面を取得できます。
- MergeコンポーネントでMember Indexコンポーネントから取得した、黒以外の面があるIndex番号をまとめ、Cull Indexコンポーネントでそれを引くことで外部に面しない黒部分の面を取得できます。
- Entwineコンポーネントでそれぞれの色ごとにまとめます。
色付け

- Simple Meshコンポーネントで各面をメッシュ化します。
- Mesh Coloursコンポーネントで色付けしていきます。それぞれの色をEntwineコンポーネントでまとめ、接続していきます。Mesh Coloursコンポーネントの出力端子はFlattenしておきます。
各ブロックごとにブランチ化

- 各ブロックの6面でブランチをまとめていきます。Areaコンポーネントで各面の中心点を取得します。
- Member Indexコンポーネントで、上で取得した面の中心点が、以前取得した面の中心点の並びだと何番目なのかを取得します。
- List ItemコンポーネントでMesh Coloursコンポーネントから取得できる各面をMember Indexコンポーネントから取得できるIndex番号をつなぐことで、以前取得した面の中心点の並びで面を並べ替えることができます。
- Partition Listコンポーネントで6面ずつブランチ化することで、ブロックごとにブランチ化されます。
(※この項目非常にわかりにくいかと思います。Panelコンポーネントでデータの中身を確認しながら進めてみてください。)
各ブロックの中心点を取得

- 各ブロック中心点を取得します。Areaコンポーネントで各面の中心点を取得します。
- List Itemコンポーネントで適当に、向かいあう2点を取得します。
- 取得した2点を足して2で割れば中心点が取得できます。
ブロックを回転させる処理の実装

- anemoneのLoop Start /Loop Endコンポーネントを用意しつなぎます。
- ループさせる値を接続します。D0に各ブロックの中心点・D1に色付けしたメッシュを、D2には0を入力しておきます、この値は後々使用します。
- まずは各ブロックの中心点から、以前作成したBox(上画像の薄い水色の直方体)の中に納まる点を取得します。以前作成したStream Filterコンポーネントから、回転させたい面のBoxを選択します。
- PointはGraft・Simplifyしておきます。Point in Brepコンポーネントで選択したBoxの内部に点が属しているかをBool値で取得します。※Point in Brepコンポーネントに接続しているBrepコンポーネントには、Stream Filterコンポーネントの出力端子から得られる、Boxが接続されております。
- Dispatchコンポーネントで、Box内部の点とそれ以外を分けます。
- 内部に属する点のみ、Rotateコンポーネントで回転させます。入力端子Aの角度については次項で説明します。入力端子Pは回転中心となる平面なので、以前作成したPlane Originコンポーネントから取得してきます。
- Mergeコンポーネントで回転させた点とさせなかった点をまとめます。
- 余計なnullを含んでいるので、Clean Treeコンポーネントでnullを削除して、D0に返してあげます。
- 続いてブロックを回転させていきます。
- D2をMeshコンポーネントにつなぎ、Simplifyしておきます。Split Treeコンポーネントから、回転させる9つのブロックを取得していきます。
- Point in Brepコンポーネントから、回転させる点がBool値で取得できてました。これをFlattenしてMember Indexコンポーネントに接続し、TrueのIndex番号を取得し、その番号のブランチが回転させるブロックのブランチとなります。Construct PathコンポーネントでPathを取得し、Split Treeコンポーネントに接続することで、回転させたいブロックだけが取得できます。
- 先ほどと同様にRotateコンポーネントで回転させ、Mergeコンポーネントで回転させたものとさせなかったものをまとめ、D1に返してあげます。
回転角の作成

- 上画像の緑色になっているNumber Sliderコンポーネントで回転させていき、緑色になっているPanelコンポーネントから実際に回転させる角度が出てくるようになっております。やや複雑なので、とりあえずプログラムを組んでから説明をしていきます。
- Multiplicationコンポーネントで回転させるためのNumber Sliderコンポーネントに10を掛けていきます。回転させるためのNumber Sliderコンポーネントは‐36~36まで移動できるようになっております。10を掛けているのは10度ずつ回転させるためです。この掛け算により、-360~360までの値が取得できます。この値をD2に返してあげます。
- D2の値に、+180度、-180度してConstruct Domainコンポーネントでドメインを生成します。これをSourceに接続します。Targetには‐180度~180度までのドメインを接続します。
- 先ほどMultiplicationコンポーネントで取得した値をRemap Numbersコンポーネントでリマップします。ここから取得できるResultが実際に回転させる角度になるので、Rotateコンポーネントの入力端子Aに接続します。
Anemoneで組んだLoop処理の解説

とりあえず必要な変数をまとめます。
- 上画像の1) : selectBlockToRotateのNumber Sliderコンポーネントは、上画像の薄い透明で水色の直方体で、どの面を回転させるかを選ぶためのものです。
- 2) : rotateBlockのNumber Sliderコンポーネントを左右に移動させると、ブロックが回転しますが、その横のPanelコンポーネントで実際何度回転させているかの値が出てきます。
- 3) : nextRotateのNumber Sliderコンポーネントは、1手終了ごとに、1増やしていきます。これは、Loop StartコンポーネントのN端子に接続されています。
- resetGameのButtonコンポーネントはLoop StartコンポーネントのTriggerに接続されており、Gameをリセットするためのものです。
『AnemoneでのLoop処理を解説していきます。』
- まず初めに回転させる面を選びます。回転させるべくブロックとその中心点が取得できます。
- rotateBlockのスライダーを移動させることで、実際回転していきます。例えば、90度回転させたとしましょう。
- D0・D1には指定した面が90度回転されLoop Startに戻されます。D2には、90度がLoop Startに返されます。
- nextRotateを1増やすことで、次のLoopに手動で移行させます。この時、D0には回転させた状態の点・D1には回転させた状態のブロック・D2には回転させた角度90度が入力されている状態です。
- 次のLoopで再度回転させる面を選び、1手目で回転させた状態の点から回転させる点・ブロックを抽出し回転させD0・D1に返します。
- この時、回転させる角度に工夫する必要があります。D2からは、1回目に回転させた90度が出力されている状態で、これを直接Rotateコンポーネントのアングルに接続すると、回転する面を動かすと、選ぶ度に90度回転されてしまいます。なので、前回入力した角度の際に0度の時が0度となり、その角度から相対的に何度回転させるかという処理を加えないといけません。
- 相対的な角度を算出するために、D2から取得してきた角度に±180度し、それを―180度~180度にすることで、1回目の入力90度を0度とし、そこから更に回転させていきます。
これの繰り返しとなります。文章では中々理解できないかと思いますので、1手1手確認しながら進めてみてください。
完成

以上になります。かなりわかりにくい箇所あったかと思います。他にもやり方はいろいろあるかと思います。そもそもGrasshopperでなくていいじゃんって意見もあるかと思います。自分なりの方法でも実装してみてください。