Tutorials

Roterende 3D kube for Silverlight 1.1 med bruk av C#

Nivå : 200

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. :)
image_thumb3 
Last ned kildekoden som er lagt til denne tutorialen for å få en fullstendig versjon.

Comments

No Comments

About Einar Ingebrigtsen

I've been doing software development since forever.. :) Started off with a Commodore 64 attached to my fingers for several years, went on to Amiga, Atari and later the PC. I was involved in the demoscene on all of the above mentioned platforms under the nickname Adept in several different groups; Legion Design, Scoop, Melon Dezign to mention but a few. Started doing professional software development in 1994 for FunCom with the prime responsibility of doing the 3D rendering engie for a PC/DOS game called Deadly Skies. Went on to doing the game engine for Windows/DirectX and Playstation for Dragonheart. Did some SNES coding in between. In 1996 a bunch of us quit FunCom and started our own company called Dimaga Studios. We went on making games for Windows/DirectX, Playstation and SEGA Saturn. The company fusioned in 1997 with Innerloop and the new name was Innerloop Studios. I quit the company later that year and started doing interactive TV solutions for a company called Playbox. Carried on doing interactive TV games and services till 2002 for Playbox and later Hermes Interactive. In 2002 I went on doing something completely different; enterprise level business applications. After working with Visual Studio .net BETA in 2001, and looking at the .net framework and C# we started the process in 2002 on moving the code over to this platform. 6 years later I'm still in the same company (still moving the code to C# :) ). Every now and then I found myself missing doing games fulltime. Early 2007 I got an email from Rune Grothaug at Microsoft Norway (DPE Team - MSDN) with a link to DevelopMentals Canadian Game Camp site. 24 hours later the GameCamp.no domain was purchased and CommunityServer installed on my server. Now I get to feed my game development nerve every now and then; through working the community by creating tutorials, newsposts, forum discussions and holding Game Camp events.