In [5]:
from IPython.core.display import HTML
from string import Template
def jsConfig():
    src = """
    <script>require.config({ baseUrl: 'https://rawgit.com/kompgraf/course-material/master/assets/' });</script>
    """
    return HTML(src)
def addScript(script, identifier):
    src = Template("""
    <div id="${identifier}-container"></div>
    <script>require(['${script}'], main => main($$("#${identifier}-container"), '${identifier}'));</script>
    """)
    return HTML(src.substitute(script = script, identifier = identifier))
jsConfig()


Out[5]:

Doo-Sabin subdivision

Áttekintés

A feladat egy Doo-Sabin subdivision sémát alkalmazó program elkészítése, mely lehetőséget biztosít többszöri finomítás végrehajtására.

Elvárt jellemzők

Általános elvárások

A házi feladat megvédése csak akkor lehet sikeres, ha a program a jellemzőit tekintve hiánytalan. Az elvárt jellemzőket teljesítő program

  • megjeleníti a finomításnak alávetett mesht,
  • megjeleníti a mesh lapjait határoló éleket,
  • lehetőséget biztosít a mesh körbejárására egy hengeren mozgó kamera segítségével,
  • a lapokat láthatóság szerint helyesen rajzolja ki,
  • tetszőleges sok finomítás végrehajtására ad lehetőséget,
  • a mesh reprezentációjához a Half-Edge adatszerkezetet használja,
  • a megfelelő transzformációkat alkalmazza.

Mesh

A kiinduló mesh tetszőleges lehet, az egyetlen követelmény, hogy Half-Edge struktúrával legyen leírva.

Transzformációk és láthatóság

A megjelenítéshez a Bevezetés a számítógépi grafikába tárgy keretein belül megismert technikákat kell alkalmazni. Szükséges

  • Window to Viewport transzformáció,
  • hengeren mozgó kamera,
  • centrális vetítés,
  • festő algoritmus

használata. Fontos, hogy ellentétben a Bézier-felülettel, ebben a házi feladatban gondoskodni kell a hátsó lapok eldobásáról!

A kamera, a láthatóság és a transzformációs lánc megvalósítását a következő kiírásban szereplő módon tegyük:

https://arato.inf.unideb.hu/kunkli.roland/hf6.pdf

Az algoritmus váza

Áttekintés

A vázolt algoritmus a Doo-Sabin subdivision egy finomítását hajtja végre. Ellentétben az előző házi feladat algoritmusával, a mostani leírás inkább csak iránymutatásul szolgál, teret engedve az egyedi megoldások számára. Ennek következménye az is, hogy nincs szigorúan megkötve, hogy milyen módon kell implementálni a sémát.

Az itt leírtak az előző házi feladat algoritmusának a leírására, valamint a Half-Edge jegyzetre támaszkodnak.

Felhasznált adatszerkezetek

Ezúttal is érdemes mutatók helyett indexeket alkalmazni, azonban annak következtében, hogy a lapok tetszőleges számú csúcsból állhatnak, nem tudunk olyan erőteljes kapcsolatot létrehozni a mutatók segítségével, mint a Loop subdivision esetén. A mesh tárolásához használt tömbök a következőek lesznek:

  • Vertices Array (VA): Ez a tömb tárolja a csúcsokat. Minden csúcsot három koordináta és egy félél indexe alkot, mely félél az adott a csúcsból indul ki.
struct Vertex {
    float x, y, z;
    int edge; // egy olyan félél az indexe, mely ebből a csúcsból indul
};
  • Faces Array (FA): A lapokat tároló tömb. A lapokat leíró struktúra mindössze egy indexet tartalmaz, mely egy, az adott lapot határoló félélt azonosít.
struct Face {
    int halfEdge;
}
  • Half-Edge Array (HA): A félélek tömbje. A féléleket reprezentáló adatszerkezet indexek formájában tartalmazza az adott félél párját és rákövetkezőjét, azt a csúcsot, melybe a félél fut, valamint a lapot, melyet határol.
struct HalfEdge {
    int vertex
    int next;
    int pair;
    int face;
}

További mezők is hozzáadhatók az egyes struktúrákhoz az algoritmus hatékony megvalósításának érdekében.

Az algoritmus lépései

0. Inicializáció

Mielőtt megkezdenénk a finomítást, hozzunk létre néhány segédtömböt:

  • EP (Edge Points) - Az élek felezőpontjait tároló tömb.
  • FP (Face Points) - A lapok súlypontjait tároló tömb.
  • NF (New Faces) - Új lapok tömbje.
  • NE (New Edges) - Az új félélek tömbje.
  • NV (New Vertices) - Új csúcsok tömbje.

Az NF tömbbe rögtön másoljuk át a régi lapokat, az NE tömbbe pedig a régi féléleket. Ennek az az oka, hogy az F-face-ek tárolását így már meg is valósítottuk, mindössze a félélek párjainak módosítására lesz szükség. Ez abból következik, hogy minden régi lap egy új F-face-t generál, ami pontosan ugyanannyi csúcsból és félélből áll, mint az eredeti lap.

1. Edge Points

Töltsük fel az EP tömböt az élek által generált pontokkal! Ehhez menjünk végig a félélek tömbjén, és keressük meg minden félél kezdő- és végpontját. Utána vegyük ezek pozíciójának átlagát, megkapva e módon az él felezőpontját. Az előállított pontot helyezzük be az EP tömbbe.

Mivel egy félél és a párja ugyanazt a pontot generálják, itt kisebb optimalizációra is lehetőség van, ha nem szeretnénk a szükséges pontok kétszeresét eltárolni.

2. Face Points

A lapok súlypontjait a csúcsaik pozíciójának átlagaként számolhatjuk ki. Ezt úgy tudjuk megvalósítani, hogy az egyes lapokat alkotó féléleken cirkulálunk, melynek során összegezzük a csúcsok pozícióit, s az így kapott összeget végül elosztjuk az érintett csúcsok számával. A kapott pontokat tároljuk el a FP tömbben!

3. Új csúcsok előállítása

Egy új csúcs pozíciójának előállításához négy pontra lesz szükség: $2$ edge point, $1$ face point és $1$ csúcs. Mivel egy csúcs minden olyan lapon generál egy új csúcsot, amiben részt vesz, érdemes a lapokon és a lapokon belül a csúcsokon végigfutni. Ennek megvalósításához ezúttal is a féléleken való cirkulálás lesz a megfelelő eszköz.

Ha tehát adott egy $f$ indexű lap, cirkuláljunk végig az összes félélen, mely a laphoz tartozik, és vegyük az adott lap $s$ súlypontját, a félél $v$ végpontját és a félél $f$, valamint a félél rákövetkezőjének $f^{\prime}$ felezőpontját. Az új csúcs $u$ pozíciója ezeknek átlaga lesz:

$$ u = \frac{1}{4}(v + f + f^{\prime} +s ) $$

4. E-faces

Minden él egy négyszöglapot fog képezni, melynek négy csúcsát az adott élben részt vevő két félél által képzett csúcsok adják. Ezekből tehát felépíthető egy új E-face, aminek két szomszédja az eredeti él két oldalán fekvő két F-face, másik két szomszédja pedig az él két végpontjából képzett két V-face.

Az előbbiek, azaz az F-face-ek szinte teljes egészében elkészültek az inicializációs lépésben szereplő másolása során, itt csak a félélek párjainak beállítására kell ügyelnünk.

5. V-faces

Az eredeti csúcsokból képzett lapok összeállítása a legnehezebb feladat. Egy adott csúcs esetén végig kell mennünk az összes félélen, mely érintette a csúcsot. Minden ilyen félélből az előző lépés során lett egy E-face, mely a szomszédja lesz az újonnan létrehozott V-face-nek. A szomszédos E-face-ek segítségével be tudjuk állítani az új lapot alkotó félélek párjait és csúcsait.

6. Befejezés

A finomítás végrehajtásának utolsó lépése, hogy a mesht tároló adatszerkezetünkben a VA, FA és HA tömböket kicseréljük az NV, NF és NE tömbökre, azaz a régi mesht kicseréljük az új, simább változatra. A következő iterációban már ezt fogjuk tovább finomítani.

Demonstráció

A demonstráció a séma működését mutatja be egy kockán. A kamerát a már ismert módon kezelhetjük (leírás itt). Újabb finomítást az ENTER leütésével hajthatunk végre.


In [7]:
addScript('js/doo-sabin-subdivision', 'doo-sabin-subdivision')


Out[7]:

In [6]:
def styling():
    styles = open("../../styles/custom.html", "r").read()
    return HTML(styles)
styling()


Out[6]: