Silverlight representerer en mindre del av det som er WPF, en av de tingene som ikke er inkludert i Silverlight sin definisjon av XAML er 3D. Med slippet av Silverlight 1.1 alpha og bruk av managed code som f.eks. C# kunne jeg ikke vente med å få hendene mine skitne og se på muligheten for å allikevel støtte 3D i Silverlight. Jeg ble veldig glad når jeg så at de hadde inkludert et tegneobjekt som heter Polygon, det betyr at det finnes en optimalisert måte å tegne trekanter. Hensikten med denne tutorial er å vise hvor lite kode som skal til for å lage en veldig enkel roterende kube.
Eksempelet viser en veldig enkel fremgangsmåte og involverer ikke tung geometri matte. Hvis du ser etter en løsning som er mer avansert og benytter Matrise matematikk og annen geometry matte, ta turen til Balder prosjektet som blant annet undertegnede jobber på : http://www.codeplex.com/Balder
Hovedloopen
Det aller første vi har behov for er en måte å få en jevn loop i form av en event eller lignende. Ved å benytte animasjons-systemet i Silverlight kan vi gjøre dette. Vi lager oss et storyboard og hekter opp Completed eventet. I Page.xaml filen lager vi oss et Canvas og legger vårt Storyboard i dette :
1 2 3 4 5 |
<Canvas x:Name="spinningCubeCanvas"> <Canvas.Resources> <Storyboard x:Name="spinningCubeStorybard" Completed="spinningCubeCanvas_Render"/> </Canvas.Resources> </Canvas> |
Faktisk, så er dette all XAMLen vi trenger for vårt formål; en roterende kube. Nå kommer moroa i C#.
I Page_Loaded eventet må vi legge til en linje kode for å starte vårt storyboard.
this.spinningCubeStorybard.Begin();
Deretter trenger vi å implementere Completed eventet som vi hektet opp i XAMLen :
1 2 3 4 | public void spinningCubeCanvas_Render(object sender, EventArgs e) { this.spinningCubeStorybard.Begin(); } |
Kuben vår
For å definere kuben, trenger vi punkter i en 3D verden, disse kaller vi Vertices (flertallsform for Vertex). En enkel klasse kan representere dette. Klassen nedenfor representerer den originale punktet før vi har gjort noen kalkulasjoner, den inneholder også den ferdig kalkularte utgaven som passer på en 2D skjerm.
1 2 3 4 5 6 | private class Vertex { public double X, Y, Z; public double RotatedX, RotatedY, RotatedZ; public int TranslatedX, TranslatedY; } |
Som du kan se så inneholder vertexen X,Y og Z som er koordinatet i 3D rommet, i tillegg til dette har vi den roterte utgaven etter at vi har gjort våre rotasjonen også
inneholder den den translaterte (2D) versjonen. Når vi nå har vertex definisjonen, trenger vi noe som definerer trianglene som skal henge på koordinatene, vi kaller
disse Faces. En Face inneholder 3 integer verdier som representerer indeksen inn i en array av vertices for objektet vi roterer.
1 2 3 4 | private class Face { public int VertexA, VertexB, VertexC; } |
Så trenger vi en array av vertices for objectet :
1 2 3 4 5 6 7 8 9 10 | private Vertex[] _vertices = new Vertex[] { new Vertex() { X=-150, Y=-150, Z=-150}, new Vertex() { X=150, Y=-150, Z=-150}, new Vertex() { X=-150, Y=150, Z=-150}, new Vertex() { X=150, Y=150, Z=-150}, new Vertex() { X=-150, Y=-150, Z=150}, new Vertex() { X=150, Y=-150, Z=150}, new Vertex() { X=-150, Y=150, Z=150}, new Vertex() { X=150, Y=150, Z=150}, }; |
Også trenger vi en array av faces som koples opp mot vertices :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | private Face[] _faces = new Face[] { new Face() { VertexA=2, VertexB=1, VertexC=0}, new Face() { VertexA=1, VertexB=2, VertexC=3}, new Face() { VertexA=4, VertexB=5, VertexC=6}, new Face() { VertexA=7, VertexB=6, VertexC=5}, new Face() { VertexA=0, VertexB=4, VertexC=6}, new Face() { VertexA=0, VertexB=6, VertexC=2}, new Face() { VertexA=7, VertexB=5, VertexC=1}, new Face() { VertexA=3, VertexB=7, VertexC=1}, new Face() { VertexA=5, VertexB=4, VertexC=0}, new Face() { VertexA=1, VertexB=5, VertexC=0}, new Face() { VertexA=2, VertexB=6, VertexC=7}, new Face() { VertexA=2, VertexB=7, VertexC=3}, }; |
Magien
Da er vi klare til å implementere selve rotasjonen og visningen av en roterende kube. Først trenger vi å rotere alle vertices rundt X,Y og Z aksene.
Det gjør vi veldig enkelt uten noen matrise matematikk.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // Calculate all the vertices foreach (Vertex vertex in this._vertices) { // Rotate the vertex around the Z axis tempY1 = (vertex.X * Math.Sin(this._zRotation)) + (vertex.Y * Math.Cos(this._zRotation)); tempX1 = (vertex.X * Math.Cos(this._zRotation)) - (vertex.Y * Math.Sin(this._zRotation));
// Rotate the vertex around the Y axis vertex.RotatedX = (vertex.Z * Math.Sin(this._yRotation)) + (tempX1 * Math.Cos(this._yRotation)); tempZ1 = (vertex.Z * Math.Cos(this._yRotation)) - (tempX1 * Math.Sin(this._yRotation));
// Rotate the vertex around the X axis vertex.RotatedZ = (tempY1 * Math.Sin(this._xRotation)) + (tempZ1 * Math.Cos(this._xRotation)); vertex.RotatedY = (tempY1 * Math.Cos(this._xRotation)) - (tempZ1 * Math.Sin(this._xRotation));
// Translate the vertex into a 2D coordinate vertex.TranslatedX = ((int) ((vertex.RotatedX * focalLength) / (vertex.RotatedZ + zoom)))+xoffset; vertex.TranslatedY = ((int) ((vertex.RotatedY * focalLength) / (vertex.RotatedZ + zoom)))+yoffset; } |
Nå har vi våre punkter ferdig roterte og translaterte. Supert, da er vi klare til å få selve kuben tegnet. Den neste delen bruker punktene til å bygge opp triangler ved
å basere seg på hva som er i hvert Face sin index til Vertices, deretter lager vi et Polygon for hvert face og legger til som en Visual for Silverlight å tegne. I vårt
tilfelle legger vi til Polygons til vårt Canvas som vi definerte tidligere for vår roterende kube. Før vi legger noe til den trenger vi å fjerne det som eventuelt ligger
ved å kalle Clear på Childrens propertien. I tillegg til dette finner vi ut hva slags triangler som ikke er synlige og sørger for å ikke legge disse til
(hidden surface removal), det gjør vi ved å gjøre et mikset produkt av de tre punktene som er involvert.
Resultatet av det miksede produktet kan også brukes i vårt tilfelle for å gi hvert polygon en farge, derav fargemagien som skjer i loopen. :)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | // Create polygons for Silverlight to work with from the newly // calculated vertices this.spinningCubeCanvas.Children.Clear(); foreach (Face face in this._faces) { Vertex vertexA = this._vertices[face.VertexA]; Vertex vertexB = this._vertices[face.VertexB]; Vertex vertexC = this._vertices[face.VertexC];
// Do a mixedproduct of all vertices for hidden surface removal double mixedProduct = (vertexB.TranslatedX - vertexA.TranslatedX) * (vertexC.TranslatedY - vertexA.TranslatedY) - (vertexC.TranslatedX - vertexA.TranslatedX) * (vertexB.TranslatedY - vertexA.TranslatedY); bool visible = mixedProduct < 0; if (!visible) { continue; }
// Use the mixed product for "shading". // The larger the face, the brighter it is. double shade = -mixedProduct; // *512; shade /= 1024;
int color = (int)shade; color += 128; if (color >= 250) { color = 250; } if (color < 30) { color = 30; }
byte red = (byte)(color >> 3); byte green = (byte)(color >> 1); byte blue = (byte)(color);
// Create the polygon and initialize the point and the color Polygon polygon = new Polygon(); polygon.Points = new Point[] { new Point(vertexA.TranslatedX,vertexA.TranslatedY), new Point(vertexB.TranslatedX,vertexB.TranslatedY), new Point(vertexC.TranslatedX,vertexC.TranslatedY) }; polygon.Fill = new SolidColorBrush(Color.FromRgb(red, green, blue));
this.spinningCubeCanvas.Children.Add(polygon); } |
Det er stort sett det man trenger, nå trenger man bare å rotere den. :)
Last ned kildekoden som er lagt til denne tutorialen for å få en fullstendig versjon.