Click here to Skip to main content
16,016,022 members
Articles / Desktop Programming / WPF
Article

Generating a sphere-mesh in XAML

Rate me:
Please Sign up or sign in to vote.
4.57/5 (38 votes)
11 Jun 20062 min read 151.6K   5.2K   47   16
This article shows a way how to create a 3D sphere in C# and XAML

Sample Image - XamlUVSphere.jpg

Introduction

A few days ago, I thought it would be funny to play around with WPF's new 3D capabilities. By then, I had only looked at 2D graphics and animations with XAML. Of course, my starting point was MSDN. You can find quite a good introduction in 3D graphics with XAML at MSDN. There is also another article on CodeProject: 3D in XAML, that gives you a good start for 3D in XAML. I will not go into the basics of cameras, meshes, lights, etc. in my article.

I was surprised when I read in MSDN that WPF "does not currently support predefined 3-D primitives like spheres and cubic forms". It gives you the MeshGeometry3D class which allows to build any geometry as a list of triangles. Therefore, I decided that my first 3D mini-project in WPF will be an algorithm that generates a mesh that represents a sphere.

Unfortunately, I am no specialist in writing 3D graphics code. Therefore, I decided to implement quite a simple algorithm that generates a sphere from a mesh of triangles: I do quite what the open-source 3D modeler Blender does with its UVSphere mesh:

Blender 3D UVSphere

(Source: Wiki: Grundkörper)

As you can see from the picture above, I split the sphere into segments and rings. The result is a list of squares (that can easily be split into two triangles), and triangles at the top and the bottom. Blender's Icosphere (see Wiki: Ikosaeder (German) for more details) would have been even more suitable for XAML meshes. However, I decided to start with UVSphere.

A sphere is not the only round mesh that can be generated by splitting a circle into segments. Therefore, I decided to write an abstract base class that can also be used for, e.g., a disc (a circle in 3D space):

C#
using System;
using System.Windows.Media;
using System.Windows.Media.Media3D;

namespace Sphere3D
{
    abstract class RoundMesh3D
    {
        protected int n = 10;
        protected int r = 20;
        protected Point3DCollection points;
        protected Int32Collection triangleIndices;

        public virtual int Radius
        {
            get { return r; }
            set { r = value; CalculateGeometry(); }
        }

        public virtual int Separators
        {
            get { return n; }
            set { n = value; CalculateGeometry(); }
        }

        public Point3DCollection Points
        {
            get { return points; }
        }

        public Int32Collection TriangleIndices
        {
            get { return triangleIndices; }
        }

        protected abstract void CalculateGeometry();
    }
}

r stands for the radius of the mesh, and n stands for the number of segments into which I split the circle (4*n+4 is the number of points that I equally distribute on the circle).

My first test was the implementation of a disc. Here is the code. It is not very complex, just some trigonometric functions:

C#
using System;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Diagnostics;

namespace Sphere3D
{
    class DiscGeometry3D : RoundMesh3D
    {
        protected override void  CalculateGeometry()
        {
            int numberOfSeparators = 4 * n + 4;

            points = new Point3DCollection(numberOfSeparators + 1);
            triangleIndices = new Int32Collection((numberOfSeparators + 1) * 3);

            points.Add(new Point3D(0, 0, 0));
            for (int divider = 0; divider < numberOfSeparators; divider++)
            {
                double alpha = Math.PI / 2 / (n + 1) * divider;
                points.Add(new Point3D(r * Math.Cos(alpha), 
                           0, -1 * r * Math.Sin(alpha)));

                triangleIndices.Add(0);
                triangleIndices.Add(divider + 1);
                triangleIndices.Add((divider == 
                  (numberOfSeparators-1)) ? 1 : (divider + 2));
            }
        }

        public DiscGeometry3D()
        { }
    }
}

The code for generating the sphere is a little bit longer. Distributing the points on the sphere is the simple part. I found it harder to generate the triangles correctly:

C#
using System;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Diagnostics;

namespace Sphere3D
{
    class SphereGeometry3D : RoundMesh3D
    {
        protected override void CalculateGeometry()
        {
            int e;
            double segmentRad = Math.PI / 2 / (n + 1);
            int numberOfSeparators = 4 * n + 4;

            points = new Point3DCollection();
            triangleIndices = new Int32Collection();

            for (e = -n; e <= n; e++)
            {
                double r_e = r * Math.Cos(segmentRad * e);
                double y_e = r * Math.Sin(segmentRad * e);

                for (int s = 0; s <= (numberOfSeparators - 1); s++)
                {
                    double z_s = r_e * Math.Sin(segmentRad * s) * (-1);
                    double x_s = r_e * Math.Cos(segmentRad * s);
                    points.Add(new Point3D(x_s, y_e, z_s));
                }
            }
            points.Add(new Point3D(0, r, 0));
            points.Add(new Point3D(0, -1 * r, 0));

            for (e = 0; e < 2 * n; e++)
            {
                for (int i = 0; i < numberOfSeparators; i++)
                {
                    triangleIndices.Add(e * numberOfSeparators + i);
                    triangleIndices.Add(e * numberOfSeparators + i + 
                                        numberOfSeparators);
                    triangleIndices.Add(e * numberOfSeparators + (i + 1) % 
                                        numberOfSeparators + numberOfSeparators);

                    triangleIndices.Add(e * numberOfSeparators + (i + 1) % 
                                        numberOfSeparators + numberOfSeparators);
                    triangleIndices.Add(e * numberOfSeparators + 
                                       (i + 1) % numberOfSeparators);
                    triangleIndices.Add(e * numberOfSeparators + i);
                }
            }

            for (int i = 0; i < numberOfSeparators; i++)
            {
                triangleIndices.Add(e * numberOfSeparators + i);
                triangleIndices.Add(e * numberOfSeparators + (i + 1) % 
                                    numberOfSeparators);
                triangleIndices.Add(numberOfSeparators * (2 * n + 1));
            }

            for (int i = 0; i < numberOfSeparators; i++)
            {
                triangleIndices.Add(i);
                triangleIndices.Add((i + 1) % numberOfSeparators);
                triangleIndices.Add(numberOfSeparators * (2 * n + 1) + 1);
            }
        }

        public SphereGeometry3D()
        { }
    }
}

For my sample, I wanted to display two spheres and a nice picture in the background (see the image at the top of the article). Therefore, I decided to create two descendent classes from SphereGeometry3D:

C#
namespace Sphere3D
{
    class BigPlanet : SphereGeometry3D
    {
        BigPlanet()
        {
            Radius = 30;
            Separators = 5;
        }
    }

    class SmallPlanet : SphereGeometry3D
    {
        SmallPlanet()
        {
            Radius = 5;
            Separators = 5;
        }
    }
}

Finally, I used XAML's data binding mechanisms to bind the properties Positions and TriangleIndices of MeshGeometry3D to the algorithms shown above:

XML
<Window x:Class="Sphere3D.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Sphere3D" 
    Title="Labyrinth3d" Height="600" Width="600"
    >
    <Window.Background>
        <ImageBrush Stretch="UniformToFill" 
                    ImageSource="Images/Pleiades.jpg"/>
    </Window.Background>
    <Grid VerticalAlignment="Stretch" 
             HorizontalAlignment="Stretch" x:Name="Grid1">
        <Grid.Resources>
            <local:BigPlanet x:Key="SphereGeometrySource1"/>
            <local:SmallPlanet x:Key="SphereGeometrySource2"/>
            <MeshGeometry3D x:Key="SphereGeometry1" 
                  Positions="{Binding Source={StaticResource 
                             SphereGeometrySource1}, Path=Points}"
                TriangleIndices="{Binding Source={StaticResource 
                                  SphereGeometrySource1}, 
                                  Path=TriangleIndices}"/>
            <MeshGeometry3D x:Key="SphereGeometry2" 
                    Positions="{Binding Source={StaticResource 
                               SphereGeometrySource2}, Path=Points}"
                TriangleIndices="{Binding Source={StaticResource 
                                 SphereGeometrySource2}, 
                                 Path=TriangleIndices}"/>
        </Grid.Resources>
        
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="20"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="20"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="20"/>
        </Grid.RowDefinitions>

        <Viewport3D Grid.Column="1" Grid.Row="1" 
                    VerticalAlignment="Stretch" 
                    HorizontalAlignment="Stretch" Name="Viewport1">

            <Viewport3D.Camera>
                <PerspectiveCamera x:Name="myCamera" Position="100 30 0" 
                      LookDirection="-50 -33 0" 
                      UpDirection="0,1,0" FieldOfView="90"/>
                <!--<OrthographicCamera x:Name="myCamera" 
                      Position="200 0 0" LookDirection="-1 0 0" 
                      Width="180" UpDirection="0,1,0"/>-->
            </Viewport3D.Camera>
            <ModelVisual3D>
                <ModelVisual3D.Content>
                    <Model3DGroup>
                        <DirectionalLight Color="#FFFFFF" 
                                 Direction="0 -30 0" />
                        <DirectionalLight Color="#FFFFFF" 
                                 Direction="0 +30 0" />
                        <GeometryModel3D 
                               Geometry="{StaticResource SphereGeometry1}">
                            <GeometryModel3D.Material>
                                <MaterialGroup>
                                    <DiffuseMaterial>
                                        <DiffuseMaterial.Brush>
                                            <SolidColorBrush Color="Orange"/>
                                        </DiffuseMaterial.Brush>
                                    </DiffuseMaterial>
                                </MaterialGroup>
                            </GeometryModel3D.Material>
                        </GeometryModel3D>
                        <GeometryModel3D 
                              Geometry="{StaticResource SphereGeometry2}">
                            <GeometryModel3D.Material>
                                <DiffuseMaterial>
                                    <DiffuseMaterial.Brush>
                                        <SolidColorBrush Color="Yellow"/>
                                    </DiffuseMaterial.Brush>
                                </DiffuseMaterial>
                            </GeometryModel3D.Material>
                            <GeometryModel3D.Transform>
                                <TranslateTransform3D 
                                     x:Name="Sphere2Translation" OffsetZ="50" />
                            </GeometryModel3D.Transform>
                        </GeometryModel3D>
                    </Model3DGroup>
                </ModelVisual3D.Content>
            </ModelVisual3D>
        </Viewport3D>
    </Grid>
</Window>

If my implementation of the 3D sphere for XAML is helpful for you, I would be happy if you could vote for my article here at CodeProject. If you have questions, feel free to send me an email.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
software architects
Austria Austria
Hi, my name is Rainer Stropek. I am living a small city named Traun in Austria. Since 1993 I have worked as a developer and IT consultant focusing on building database oriented solutions. After being a freelancer for more than six years I founded a small IT consulting company together with some partners in 1999. In 2007 my friend Karin and I decided that we wanted to build a business based on COTS (component off-the-shelf) software. As a result we founded "software architects" and developed the time tracking software "time cockpit" (http://www.timecockpit.com). If you want to know more about our companies check out my blogs at http://www.software-architects.com and http://www.timecockpit.com or take a look at my profile in XING (http://www.openbc.com/hp/Rainer_Stropek2/).

I graduated the Higher Technical School for MIS at Leonding (A) in 1993. After that I started to study MIS at the Johannes Kepler University Linz (A). Unfortunately I had to stop my study because at that time it was incompatible with my work. In 2005 I finally finished my BSc (Hons) in Computing at the University of Derby (UK). Currently I focus on IT consulting, development, training and giving speeches in the area of .NET and WPF, SQL Server and Data Warehousing.

Comments and Discussions

 
QuestionInside Out and could be Smoother Pin
Tom Robson23-Dec-20 20:48
Tom Robson23-Dec-20 20:48 
QuestionHow to add the sphere dynamically? Pin
ahmed-itani21-Oct-10 9:47
professionalahmed-itani21-Oct-10 9:47 
QuestionSmooth ? Pin
Xmen Real 23-Jun-10 14:35
professional Xmen Real 23-Jun-10 14:35 
GeneralLicense Pin
fatho112-May-09 7:53
fatho112-May-09 7:53 
AnswerRe: License Pin
Zlotto11-Sep-09 5:35
Zlotto11-Sep-09 5:35 
GeneralRe: License Pin
fatho111-Sep-09 7:12
fatho111-Sep-09 7:12 
GeneralBackface culling bug Pin
GuinnessKMF8-May-09 9:13
GuinnessKMF8-May-09 9:13 
GeneralWishing I paid more attention in geometry class.... Pin
dosborn2783-Mar-09 14:51
dosborn2783-Mar-09 14:51 
GeneralRe: Wishing I paid more attention in geometry class.... Pin
GuinnessKMF8-May-09 5:40
GuinnessKMF8-May-09 5:40 
GeneralGreat Pin
Josh Smith30-Aug-06 6:05
Josh Smith30-Aug-06 6:05 
GeneralRe: Great Pin
Paul Conrad17-Nov-06 11:52
professionalPaul Conrad17-Nov-06 11:52 
QuestionCannot compile Pin
Claton13-Jun-06 4:07
Claton13-Jun-06 4:07 
AnswerRe: Cannot compile Pin
r.stropek13-Jun-06 4:12
r.stropek13-Jun-06 4:12 
GeneralRe: Cannot compile Pin
Claton13-Jun-06 4:15
Claton13-Jun-06 4:15 
GeneralRe: Cannot compile Pin
r.stropek13-Jun-06 4:37
r.stropek13-Jun-06 4:37 
Since WinFX is in beta you can never be sure. At my company a lot of people (including me) have installed it in parallel to .NET 2. So far we didn't have problems.

Regards,
Rainer.


Rainer Stropek
Visit my blog at http://www.cubido.at/rainers

GeneralRe: Cannot compile Pin
Ri Qen-Sin18-Nov-06 2:53
Ri Qen-Sin18-Nov-06 2:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.