Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Generating a sphere-mesh in XAML

0.00/5 (No votes)
11 Jun 2006 1  
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):

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:

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:

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:

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:

<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