Mapping MediaElement onto a Sphere with WPF

29 ביוני 2009

תגיות: , , , ,
תגובה אחת

In my previous post I created a 3D cube, mapped a MediaElement onto each of its faces and animated the camera around the cube. In this post, I will replace the 3D cube with a sphere and wrap the video onto the surface of the sphere. I omit the camera animation for simplicity, but you can add it back, of course.


You can download the source code here.


In order to create the sphere mesh I made use of Charles Petzold’s elegant SphereMeshGenerator.


In Chapter 6 of his book (3D Programming for Windows: Three-Dimensional Graphics Programming for the Windows Presentation Foundation) Petzold explains the technique very clearly – a highly recommended read. In this post, I will just provide the highlights.


The SphereMeshGenerator class is a factory that generates the MeshGeometry3D in code. Here it is:



//—————————————————–


// SphereMeshGenerator.cs (c) 2007 by Charles Petzold


//—————————————————–


using System;


using System.Windows;


using System.Windows.Media;


using System.Windows.Media.Media3D;


namespace Petzold.SphereResourceDemo


{


    public class SphereMeshGenerator


    {


        // Four private initialized fields.


        int slices = 32;


        int stacks = 16;


        Point3D center = new Point3D();


        double radius = 1;


        // Four public properties allow access to private fields.


        public int Slices


        {


            set { slices = value; }


            get { return slices; }


        }


        public int Stacks


        {


            set { stacks = value; }


            get { return stacks; }


        }


        public Point3D Center


        {


            set { center = value; }


            get { return center; }


        }


        public double Radius


        {


            set { radius = value; }


            get { return radius; }


        }


        // Get-only property generates MeshGeometry3D.


        public MeshGeometry3D Geometry


        {


            get


            {


                // Create a MeshGeometry3D.


                MeshGeometry3D mesh = new MeshGeometry3D();


                // Fill the vertices, normals, and textures collections.


                for (int stack = 0; stack <= Stacks; stack++)


                {


                    double phi = Math.PI / 2 – stack * Math.PI / Stacks;


                    double y = Radius * Math.Sin(phi);


                    double scale = -Radius * Math.Cos(phi);


                    for (int slice = 0; slice <= Slices; slice++)


                    {


                        double theta = slice * 2 * Math.PI / Slices;


                        double x = scale * Math.Sin(theta);


                        double z = scale * Math.Cos(theta);


                        Vector3D normal = new Vector3D(x, y, z);


                        mesh.Normals.Add(normal);


                        mesh.Positions.Add(normal + Center);


                        mesh.TextureCoordinates.Add(


                                    new Point((double)slice / Slices,


                                              (double)stack / Stacks));


                    }


                }


                // Fill the indices collection.


                for (int stack = 0; stack < Stacks; stack++)


                {


                    int top = (stack + 0) * (Slices + 1);


                    int bot = (stack + 1) * (Slices + 1);


                    for (int slice = 0; slice < Slices; slice++)


                    {


                        if (stack != 0)


                        {


                            mesh.TriangleIndices.Add(top + slice);


                            mesh.TriangleIndices.Add(bot + slice);


                            mesh.TriangleIndices.Add(top + slice + 1);


                        }


                        if (stack != Stacks – 1)


                        {


                            mesh.TriangleIndices.Add(top + slice + 1);


                            mesh.TriangleIndices.Add(bot + slice);


                            mesh.TriangleIndices.Add(bot + slice + 1);


                        }


                    }


                }


                return mesh;


            }


        }


    }


}


A slice is the area of the sphere surface that lies between two lines of longitude. The lines of longitude are circles centered at the center of the sphere that pass through the North Pole and South Pole. A stack is the area of the sphere surface that lies between two lines of latitude. The lines of latitude are parallel to the plane of the equator; only the equator itself is centered about the center of the sphere.


Properties of the SphereMeshGenerator allow you to specify the center and radius of the sphere as well as the number of slices and stacks that will be used. The higher the number of slices and stacks the smoother the image and the greater the load on the CPU and GPU.


Using the SphereMeshGenerator is straightforward. Just create a static instance of the SphereMeshGenerator as a resource of the Window, and bind the Geometry property of the ModelVisual3D to the Geometry factory property of SphereMeshGenerator.


Here is the XAML I used.



<Window x:Class="MediaElementOnSphere.Window1"


       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"


       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"


       xmlns:local="clr-namespace:Petzold.SphereResourceDemo"


       Title="Window1"


       Width="500"


       Height="400"


       Loaded="Window_Loaded">


   <Window.Resources>


      <local:SphereMeshGenerator x:Key="sphereMeshGenerator"


        Center="0 0 0"


        Radius="2" />


   </Window.Resources>


   <Grid>


      <Viewport3D>


         <Viewport3D.Camera>


            <PerspectiveCamera x:Name="camera"


                              UpDirection="0,1,0"


                              LookDirection="-4,-4,-4"


                              Position="4,4,4" />


         </Viewport3D.Camera>


         <ModelVisual3D>


            <ModelVisual3D.Content>


               <AmbientLight Color="White"></AmbientLight >


            </ModelVisual3D.Content>


         </ModelVisual3D>


         <ModelVisual3D>


            <ModelVisual3D.Content>


               <GeometryModel3D Geometry=


                    "{Binding Source={StaticResource sphereMeshGenerator}, Path=Geometry}">


                  <GeometryModel3D.Material>


                     <DiffuseMaterial>


                        <DiffuseMaterial.Brush>


                           <VisualBrush>


                              <VisualBrush.Visual>


                                 <MediaElement>


                                    <MediaElement.Triggers>


                                       <EventTrigger RoutedEvent="MediaElement.Loaded">


                                          <EventTrigger.Actions>


                                             <BeginStoryboard>


                                                <Storyboard>


                                                   <MediaTimeline x:Name="mediaTimeline"


                                                                  RepeatBehavior="Forever" />


                                                </Storyboard>


                                             </BeginStoryboard>


                                          </EventTrigger.Actions>


                                       </EventTrigger>


                                    </MediaElement.Triggers>


                                 </MediaElement>


                              </VisualBrush.Visual>


                           </VisualBrush>


                        </DiffuseMaterial.Brush>


                     </DiffuseMaterial>


                  </GeometryModel3D.Material>


               </GeometryModel3D>


            </ModelVisual3D.Content>


         </ModelVisual3D>


      </Viewport3D>


   </Grid>


</Window>


Finally, here is a snapshot of my spherical cinema:


image


Summary:


In this post I demonstrated how you can map a MediaElement onto the surface of a 3D spherical mesh in WPF. I used Charles Petzold’s SphereMeshGenerator to generate the mesh and set the DiffuseMaterial.Brush property to a VisualBrush that references the MediaElement.

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. (*) שדות חובה מסומנים

תגובה אחת

  1. HLC1 בנובמבר 2011 ב 13:01

    It's a great post! I'm actually looking for a similar solution!

    Do you have any clue to map the video onto the inner surface instead of outer surface of the sphere?

    So that, if I put the camera in the middle of the sphere, I can watch the video in panorama mode!

    I look forward to hearing from you!

    להגיב