Úloha 096: Rubikova kostka

Úkolem je implementovat jednoduchý 3D simulátor Rubikovy kostky. Uživatel bude manipulovat s virtuální kostkou na obrazovce, může si ji prohlížet pomocí systému "Trackball" a při stisku druhého tlačítka myši na kostičku se provede otočení dané vrstvy o 90 stupňů.

Rubik's Cube

Základ

Jako základ poslouží projekt 096puzzle z repository grcis. Je připravena aplikace, která umí v reálném čase vykreslovat 3D scénu (ovládání systémem "Trackball"), mezi vykreslováním se volá simulace kostky (Puzzle.Simulate()). Pro řízení Vaší simulace jsou připravena tlačítka Start / stop, Reset sim a Update sim. Při inicializaci/update simulace je možné použít uživatelem zadaný řetězec Param.

Animace, ovládání

Postačí implementovat jednoduché otáčení konstantní úhlovou rychlostí, podle zájmu můžete implementovat i nějaká vylepšení (rozjezd a zpomalování), turbo režim apod.

Ovládání: levé tlačítko myši se používá pro otáčení celou scénou (Trackball). Pravé tlačítko byste měli použít k otáčení vrstvami kostky ("tahy"). Návrh konkrétních detailů ovládání je zcela na Vás, např.: klikneme-li na malý čtvereček, vyvolá se tah (otočení vrstvy). Bude třeba použít nějaká jednoduchá gesta ("cuknutí") pro rozlišení nejednoznačných příkazů na rohových kostičkách. Při kliknutí na středové čtverečky se nemusí dělat nic..

Technické detaily

Hlavolam je implementován třídou Cube (potomek DefaultRenderObject), celá simulace je uzavřena ve třídě Puzzle.

Simulace se počítá v diskrétních časových okamžicích, jednou v každém zobrazovacím cyklu. Všechny simulované objekty (v pilotním řešení pouze Cube) mají pro tento účel deklarovanou funkci Simulate ( double time ). Objekty se musí přepočítat tak, aby jejich stav odpovídal požadovanému času time v sekundách.

Framework má vestavěné některé mechanismy pro ladění/podrobné zkoumání: tlačítka Start / stop, Reset sim a Update sim. Dále je zde checkbox Slow umožňující zpomalit simulovaný čas zadaným faktorem (implicitní nastavení: slow=0.25).

Vykreslování

Univerzální systém (interface IRenderObject) se používá k vykreslování všech komponent simulovaného světa. Jednotlivé objekty mohou definovat body (GL_POINTS), úsečky (GL_LINES) a trojúhelníky (GL_TRIANGLES), pomocí kterých budou zobrazeny. V našem případě je systém zjednodušen, používají se pouze trojúhelníky. Je implementováno obecné dvoufázové plnění datových bufferů:
1. poprvé se zavolá metoda TriangleVertices() (TriangleIndices(), LineVertices(), LineIndices() nebo PointVertices()) bez zadání datového pointeru, aby se jen zjistila velikost vertex-bufferu (index-bufferu) v bytech
2. podruhé se již příslušné metody používají k vlastnímu naplnění dat, VBO buffer z grafické karty se přitom namapuje do operační paměti aplikace metodou GL.MapBuffer()

Globální simulační objekt Puzzle implementuje vykreslování za pomoci trojúhelníků (FillTriangleData()). Stačí, když všechny Vaše simulační objekty budou korektně implementovat interface IRenderObject a nebudete muset do vykreslování více zasahovat. Pro jednoduchost projektu se nepoužívají shadery, ale lze přepínat mezi mapováním textury a barvou jednotlivých stěn (checkbox Tex).

Funkce UnProject()

Při ukazování myší do zobrazované 3D scény se používá klasický koncept UnProject (původně gluUnProject()). 2D souřadnice z obrazovky (vracené v událostech obsluhy myši MouseEventArgs) se doplní Z-ovou složkou a funkce Geometry.UnProject() spočítá odpovídající bod ve světovém 3D souřadnicovém systému. Z-složka může nabývat hodnot od 0.0 (bod na blízké ořezávací rovině near) do 1.0 (bod na vzdálené ořezávací rovině far nebo v nekonečnu).

Aplikace musí sama rozhodnout, na který objekt uživatel ukázal. Typicky se počítá nejbližší objekt v simulované 3D scéně, na který narazí paprek vedený obrazovkou v místě polohy myši (takový paprsek dostaneme, když spojíme "near" a "far" bod). Naše aplikace obsahuje ukázkový kód: v režimu Debug se po stisku pravého tlačítka myši spočítá takový testovací paprsek a spočte se jeho průsečík s kostkou. Krajní body úsečky jsou barevné, bod průsečíku se kreslí bílou barvou. Pro názornost je možné stiskem klávesy F (Frustum) vygenerovat 3D zobrazení zorného pole, které platilo v daný okamžik (pro jeho prohlédnutí musíme samozřejmě změnit úhel pohledu a nejlépe i zoom). Ukázka implementace této funkcionality: metody screenToWorld(), glControl1_MouseDown() a kód na konci Application_Idle(). Pilotní implementace kostky obsahuje funkci Cube.Intersect() procházející všechny trojúhelníky z modelu (cachované polohy + transformace aktuální modelovací maticí objectMatrix) a hledající nejbližší průsečík.

Obdobnou funkci jsem přidal i do projektu 086shader, kde si můžete vyzkoušet ukazování do scén načtených z disku.

Stručný návod k řešení

Navrhuji Vám zde systém, kterým se můžete řídit při implementaci logiky simulátoru Rubikovy kostky. Nebudu se zabývat interaktivním ovládáním, tam stačí detekovat, na kterou kostičku uživatel klepne a kterým směrem ji potáhne. Doporučuji přijímat GUI vstup jenom tehdy, když se neprovádí žádná animace, bude to jednodušší.

  1. budeme se zabývat pouze transformací "Model", pohledová transformace nás nemusí zajímat, o tu se postará systém prohlížení scény Trackball
  2. každá z kostiček (kromě té středové – neviditelné – kterou asi ani nemusíte implementovat) má přiřazenou celkovou transformační matici, do které se složily všechny provedené dokončené tahy. Když tahy označíme Rk,i (rotační matice i-tého tahu pro k-tou kostičku), bude celková dokončená matice k-té kostičky po n-tém tahu Fk,n = Rk,1 · Rk,2 · … · Rk,n
  3. při provádění i-tého kroku (animované otáčení jednou vrstvou Rubikovy kostky) definujeme, které kostičky se toho tahu účastní a jaká bude rotační matice Rk,i. Pro účely animace si představíme, že je tato rotační matice závislá na čase 0.0 ≤ t ≤ 1.0: Rk,i(t). Rk,i(0.0) = 1 (jednotková matice), Rk,i(1.0) = Rk,i je plná rotace i-tého tahu (obvykle rotace kolem některé základní osy o 90°)
  4. během i-tého tahu se zúčastněné kostičky transformují maticí Fk,i-1 · Rk,i(t), nezúčastněné se nehýbou, můžeme jim formálně definovat konstantní jednotkovou Rk,i(t) = 1
  5. Po dokončení i-tého tahu se všem kostičkám upraví Fk,i = Fk,i-1 · Rk,i, chcete-li optimalizovat, nezúčastněným kostičkám nic přepočítávat nemusíte (Fk,i = Fk,i-1)

Pro zajímavost: lze do toho zapojit i systém hlavolamu Rubikovy kostky. Tj. složená kostka znamená, že všechny její kostičky mají shodnou transformační matici (rotaci), triviálně třeba jednotkovou matici.
Rozházení (zamíchání) kostky znamená konzistentní nastavení transformací jednotlivým kostičkám. Podmínka konzistence = "konfigurace musí být dosažitelná z iniciálníého stavu posloupností regulérních tahů".
Proto je nejjednodušší kostku "míchat" tak, že se vygeneruje nějaká dlouhá posloupnost náhodných tahů a ta se virtuálně provede – bez zobrazení a animace.

Pak můžete nechat uživatele skládat kostku a snadno po každém tahu zkontrolujete, zda je složená: transformace všech kostiček by musely být stejné! (nemusí to být jednotková matice, na natočení složené kostky v prostoru nezáleží).

Termín

Odevzdat do: 28. 2. 2022

Body

Základ: 11 bodů (jednoduchá ale funkční simulace, možnost zadat interaktivně všechny tahy),
dalších až 9 bodů: bonus za zajímavá rozšíření (pěkný vzhled kostky /i uvnitř vypadá stejně jako originál/, bohatší animace, míchání kostky, automatické skládání, obecnější kostka, apod.)

Projekt

Visual Studio projekt: 096puzzle

Zdrojový soubor

Modifikujte a odevzdejte soubor: Puzzle.cs
V příslušném parametru funkce Form1.InitParams() vraťte své celé jméno.


Copyright (C) 2016-2022 J.Pelikán, last change: 2022-02-02 01:03:41 +0100 (Wed, 02 Feb 2022)