Sunday, May 17, 2009

primitives: the disk

De komende posts ga ik het over iets hebben waar ik me mee amuseer: mooie wiskunde.

De meeste mensen denken bij wiskunde meteen aan die vreselijk saaie rekenoefeningen die m'n moest maken op school, maar dat is dan ook dat: rekenen, en dat is geen wiskunde J

Wiskunde is iets dat prachtige dingen te voorschijn kan toveren (oké, het is eigenlijk geen toveren, maar alles dat complex genoeg is voor de toeschouwer is magie), zoals geometrie (zoals we in de eerste posts zullen zien), fractalen, nurbs oppervlakten etc.

Zoals gezegd, zal ik in de eerste posts enkele geometrische figuren bespreken. Meer concreet, hoe we ze kunnen maken. De figuren die we hier zullen zien, zijn wat we "primitieven" noemen. Het zijn enkele van de eenvoudigste vormen die we in de wereld tegenkomen.

De eerste die ik zal omschrijven, is wellicht ook de eenvoudigste om te maken (behalve dan de kubus, maar die is dan ook zo evident dat ik hem niet zal bespreken): de disk.

De disk is de figuur die je ziet als je een cd-rom ziet: een platte schijf met in het midden een gat. Dat gat kan straal van 0 hebben, waardoor het uiteindelijk geen gat heeft, maar in andere gevallen (zoals onze cd), zal het gat wel zichtbaar zijn.

Een disk is eigenlijk niet meer dan twee cirkels (een buitenste en een binnenste cirkel), en een cirkel, zo weten we allemaal, is perfect rond. Dat betekend dat ze een oneindig aantal hoeken heeft (in tegenstelling tot een vierhoek, die er, u raadt het al, vier heeft). Die oneindigheid vormt een serieus probleem als we die disk willen weergeven op een computer. Om dat te overbruggen, geven we onze disk een nieuwe eigenschap, namelijk de "resolutie", ofte hoeveel hoeken dat de cirkel zal hebben. Zeggen we de disk dat zijn resolutie vijf is, dan krijgen we een vijfhoek, 30 een dertighoek enzovoort.

Nu we weten hoeveel hoeken de disk zal hebben, weten we ook hoeveel punten er nodig zullen zijn als we hem willen definiëren (zo werken 3D-programma's nu eenmaal. Je geeft de verschillende punten op die een object weergeven en verbindt die met elkaar (zeer vereenvoudigd).). Maar hoe weten we de locatie van die punten?

Dit is waar de wiskunde langskomt. Herinner je nog de cosinus en de sinus? Wellicht nooit gedacht dat die dingen eigenlijk ergens goed voor waren, maar hier brengen ze onze redding.

De volgende afbeelding toont wat ze eigenlijk betekenen.

Image Hosted by ImageShack.us

Wat we zien is een cirkel met een straal van lengte 1. Als we hierin een hoek tekenen, en we kijken naar de x- en coördinaat van het punt waar de lijn de cirkel raakt, geeft ons dat respectievelijk de cosinus en de sinus van die hoek.

Om de positie van onze punten te weten, moeten we dus voor ieder punt weten onder welke hoek ze zich bevinden.
Die hoek berekenen we door het nummer van het punt te delen door het aantal punten (op die manier bekomen we een getal tussen 0 en 1), en dat vermenigvuldigen we met twee keer pi (3.14…). Waarom 2 keer pi? Omdat in goniometrie, hoeken worden weergegeven als radialen. 180° is gelijk aan pi, 360° is dus gelijk aan 2pi, en door hiermee te vermenigvuldigen zorgen we er voor dat al onze hoekpunten tussen 0 en 360° komen.

We krijgen dus het volgende:

X = cos(i / res * pi * 2)
Y = sin(i / res * pi * 2)

Waar i het nummer van het punt is en res de resolutie van de disk (dus het maximaal aantal punten.
Het probleem nu is echter dat alle punten op een afstand van 1 van het nulpunt liggen, en als we een binnen en buitencirkel hebben, zouden die dus op elkaar komen te liggen.
Vermenigvuldigen we x en y echter nog eens met de straal van de cirkel, is ook dit probleem opgelost.

In code ziet het er uiteindelijk zo uit:

nrOfVertices = res * 2;

int nrOfQuads = res;

nrOfFaces = nrOfQuads * 2;

int nrOfIndices = nrOfFaces * 3;

vertices = new
VertexPositionNormalColor[nrOfVertices];

for (int i = 0; i < res; ++i)
{
double angle = (double)i / res * Math.PI * 2;
var pos1 = new Vector3((float)Math.Cos(angle) * radOuter, 0, (float)Math.Sin(angle) * radOuter);
var pos2 = new
Vector3((float)Math.Cos(angle) * radInner, 0, (float)Math.Sin(angle) * radInner);

vertices[i * 2] = new
VertexPositionNormalColor(pos1, Vector3.UnitY, Color.DimGray);

vertices[i * 2 + 1] = new
VertexPositionNormalColor(pos2, Vector3.UnitY, Color.DimGray);

}

Hier is "res" het aantal hoeken van de cirkel en "vertices" is een lijst die de posities bijhoudt met een lengte van "nrOfVertices".
De disk bestaat uit vierkante vlakken die tussen de punten worden getekend. Het aantal van die vlakken is even veel als de resolutie. Het aantal punten dat we nodig hebben is hier het dubbele van, aangezien we zowel de punten voor de buitenste cirkel (pos1) en die voor de binnenste cirkel (pos2) moeten weten. (het double en float gedoe moet je je niet te veel van aantrekken, dat betreft conversies tussen verschillende datatypes. Vector3 is een vector in een driedimensionale ruimte, dus met een x, y en z coördinaat. In deze wereld is Y naar boven, dus laten we deze op 0 staan, zodat de disk plat op de grond ligt).
radOuter en radInner zijn de radius (straal) van de buitenste en binnenste cirkel. (in het programmeren noemen we de dingen in het Engels, aangezien dit zo wat de standaard is in dit wereldje, dan begrijpt iedereen waar je het over hebt).
Het lijntje for(int i = 0; i < res; ++i) vraagt misschien wat uitleg, het is iets dat we in de komende posts nog veel zullen zien.
Wat we hier doen is een geheel getal (integer in het Engels) aanmaken dat we "i" noemen en we zeggen dat het de waarde 0 heeft. Vervolgens geven we een conditie op, in dit geval i < res. Dat betekend dat, zo lang dat i kleiner is dan res (het aantal punten op de cirkel, remember?), we de code binnen de accolades gaan uitvoeren, waarna het derde deeltje van de for-statement uit wordt gevoerd, namelijk ++i. dat betekend zo veel als: neem het getal i en voeg er 1 aan toe. Na de eerste keer dat de code wordt uitgevoerd zal het getal i (dan nog 0) vermeerderd worden met 1, en wordt de code opnieuw uitgevoerd, maar overal waar er i staat, staat er nu 1 in plaats van 0. De volgende keer zal op al die plekken 2 staan, en zo voort, totdat i niet meer kleiner zou zijn dan het aantal punten, waarop er uit de lus wordt gegaan en de rest van de code zal worden uitgevoerd.

De laatste twee regels vragen ook wat uitleg:
vertices[i * 2] = new
VertexPositionNormalColor(pos1, Vector3.UnitY, Color.DimGray);

Betekend zo veel als: de positie i * 2 in de lijst vertices is een punt (punten die geometrie beschrijven worden vertices, enkelvoud vertex, genoemd) met de volgende eigenschappen: een positie die gelijk is aan pos1, een normaal (welke richting is "naar boven" voor het punt? Dit is belangrijk voor de belichting), en een kleur (als we het punt of het vlak dat dit punt gebruikt tekenen, wat is de kleur daarvan?).

De tweede regel is hetzelfde, maar beschrijft die voor de tweede cirkel.

Het resultaat van dit zijn twee cirkels van punten, de ene met een straal van radOuter, de ander met een straal van radInner. (hieronder met een resolutie van 10 en de belichting af)

Image Hosted by ImageShack.us

Image Hosted by ImageShack.us

Volgende keer: de cilinder.

No comments: