In some of my previous WP7 blogs, such as Basic XNA Graphics for the WP7, I go over how to build basic 3D models for your applications. What happens when you want something more complex? You need a good 3D tool to generate those models for you. I started exploring Google SketchUp as a possibility for generating 3D models for the WP7. I found Jim’s Blog on how to load a Google SketchUp model into a XNA game. Jim used the DirectX/XNA Exporter Plugin. So I installed the plugin and started playing around. I thought it would be cool to come up with some type of compass for my magnetometer applications. Now, I’m not a Google SketchUp expert, so what I came up with is not what I’d call a great work of art. However, it does clearly indicate North, South, East, West.
The model consists of just a disk, with 3D text indicating the directions. Within SketchUp, I choose the menu options, Plugins –> DirectX –> Export Model and saved the file to SimpleWheel.x.
I then created a new Silverlight/XNA WP7 application called SLXnaApp2. I included the SimpleWheel.x file in the SLXnaApp2LibContent project. I then included some basic code from my magnetometer blogs for the alignment of the 3D model with WP7. I also included some code to handle the loading of the data in the SimpleWheel.x file.
The entire GamePage.xaml.cs file looks like this.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Devices.Sensors;
namespace SlXnaApp2
{ public partial class GamePage : PhoneApplicationPage
{ ContentManager contentManager;
GameTimer timer;
SpriteBatch spriteBatch;
Motion motion;
bool useMotion = false;
Matrix matrix;
Model model;
BasicEffect basicShader;
public GamePage()
{ InitializeComponent();
// Get the content manager from the application
contentManager = (Application.Current as App).Content;
// Create a timer for this page
timer = new GameTimer();
timer.UpdateInterval = TimeSpan.FromTicks(333333);
timer.Update += OnUpdate;
timer.Draw += OnDraw;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{ // Set the sharing mode of the graphics device to turn on XNA rendering
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(true);
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(SharedGraphicsDeviceManager.Current.GraphicsDevice);
// Motion
if (Motion.IsSupported)
{ motion = new Motion();
motion.Start();
useMotion = true;
}
model = contentManager.Load<Model>("SimpleWheel");
// Make the projection matrix
float aspectRatio =
(float)SharedGraphicsDeviceManager.Current.GraphicsDevice.Viewport.Width /
(float)SharedGraphicsDeviceManager.Current.GraphicsDevice.Viewport.Height;
float fov = MathHelper.PiOver4 * aspectRatio * 3 / 4;
Matrix projectionMatrix =
Matrix.CreatePerspectiveFieldOfView(fov, aspectRatio, 5f, 1000f);
// Setup the camera vectors objects and view matrix
Vector3 camPos = new Vector3(0.0f, 0.9f, 600.0f);
Vector3 camView = new Vector3(0.0f, 0.9f, -15.0f);
Vector3 camUp = new Vector3(0.0f, 1.0f, 0.0f);
Matrix viewMatrix = Matrix.CreateLookAt(camPos, camView, camUp);
// Initialize the basic shader
basicShader = new BasicEffect(SharedGraphicsDeviceManager.Current.GraphicsDevice);
basicShader.Alpha = 1.0f;
basicShader.LightingEnabled = true;
basicShader.DiffuseColor = new Vector3(0.95f, 0.95f, 0.95f);
basicShader.SpecularColor = new Vector3(0.25f, 0.25f, 0.25f);
basicShader.SpecularPower = 5.0f;
basicShader.AmbientLightColor = new Vector3(0.70f, 0.70f, 0.70f);
basicShader.World = Matrix.Identity;
basicShader.View = viewMatrix;
basicShader.Projection = projectionMatrix;
// Start the timer
timer.Start();
base.OnNavigatedTo(e);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{ // Stop the timer
timer.Stop();
// Stop Accelerameter
if (useMotion) motion.Stop();
// Set the sharing mode of the graphics device to turn off XNA rendering
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(false);
base.OnNavigatedFrom(e);
}
/// <summary>
/// Allows the page to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
private void OnUpdate(object sender, GameTimerEventArgs e)
{ if (useMotion)
{ var pitch = motion.CurrentValue.Attitude.Pitch;
var roll = motion.CurrentValue.Attitude.Roll;
var yaw = motion.CurrentValue.Attitude.Yaw;
matrix = Matrix.CreateRotationX(-pitch);
matrix = Matrix.CreateRotationY(-roll) * matrix;
matrix = Matrix.CreateRotationZ(-yaw) * matrix;
// Set a new world position
basicShader.World = matrix;
}
}
/// <summary>
/// Allows the page to draw itself.
/// </summary>
private void OnDraw(object sender, GameTimerEventArgs e)
{ SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Color.Black);
RasterizerState rasterizerState = new RasterizerState();
rasterizerState.CullMode = CullMode.CullCounterClockwiseFace;
SharedGraphicsDeviceManager.Current.GraphicsDevice.RasterizerState =
rasterizerState;
SharedGraphicsDeviceManager.Current.GraphicsDevice.DepthStencilState =
DepthStencilState.Default;
if (model != null)
{ spriteBatch.Begin();
DrawSampleModel(model);
spriteBatch.End();
}
}
private void DrawSampleModel(Model m)
{ if (m == null)
return;
foreach (ModelMesh mesh in m.Meshes)
{ foreach (ModelMeshPart meshPart in mesh.MeshParts)
{ // set the vertex source to the mesh part's vertex buffer @ offset
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetVertexBuffer(
meshPart.VertexBuffer, meshPart.VertexOffset);
// set the index buffer to the mesh part's
SharedGraphicsDeviceManager.Current.GraphicsDevice.Indices =
meshPart.IndexBuffer;
foreach (EffectPass effectPass in basicShader.CurrentTechnique.Passes)
{ effectPass.Apply();
SharedGraphicsDeviceManager.Current.GraphicsDevice.DrawIndexedPrimitives(
PrimitiveType.TriangleList,
0,
0,
meshPart.NumVertices,
meshPart.StartIndex,
meshPart.PrimitiveCount);
}
}
}
}
}
}
There are a couple of important parts related to the loading of the model. The first is in the OnNavigateTo event handler.
model = contentManager.Load<Model>("SimpleWheel");
// Make the projection matrix
float aspectRatio =
(float)SharedGraphicsDeviceManager.Current.GraphicsDevice.Viewport.Width /
(float)SharedGraphicsDeviceManager.Current.GraphicsDevice.Viewport.Height;
float fov = MathHelper.PiOver4 * aspectRatio * 3 / 4;
Matrix projectionMatrix =
Matrix.CreatePerspectiveFieldOfView(fov, aspectRatio, 5f, 1000f);
// Setup the camera vectors objects and view matrix
Vector3 camPos = new Vector3(0.0f, 0.9f, 600.0f);
Vector3 camView = new Vector3(0.0f, 0.9f, -15.0f);
Vector3 camUp = new Vector3(0.0f, 1.0f, 0.0f);
Matrix viewMatrix = Matrix.CreateLookAt(camPos, camView, camUp);
// Initialize the basic shader
basicShader = new BasicEffect(SharedGraphicsDeviceManager.Current.GraphicsDevice);
basicShader.Alpha = 1.0f;
basicShader.LightingEnabled = true;
basicShader.DiffuseColor = new Vector3(0.95f, 0.95f, 0.95f);
basicShader.SpecularColor = new Vector3(0.25f, 0.25f, 0.25f);
basicShader.SpecularPower = 5.0f;
basicShader.AmbientLightColor = new Vector3(0.70f, 0.70f, 0.70f);
basicShader.World = Matrix.Identity;
basicShader.View = viewMatrix;
basicShader.Projection = projectionMatrix;
This is where the Google SketchUp model is loaded, the camera view is set up, and the model effects object (basicShader) is set up for the lighting and positioning.
The next bit of important code is in the OnUpdate event handler.
if (useMotion)
{ var pitch = motion.CurrentValue.Attitude.Pitch;
var roll = motion.CurrentValue.Attitude.Roll;
var yaw = motion.CurrentValue.Attitude.Yaw;
matrix = Matrix.CreateRotationX(-pitch);
matrix = Matrix.CreateRotationY(-roll) * matrix;
matrix = Matrix.CreateRotationZ(-yaw) * matrix;
// Set a new world position
basicShader.World = matrix;
}
This is where the phone’s orientation is taken and the model is set so that it will always point North. Details on this are located here.
The next bit of important code is in the OnDraw event handler.
RasterizerState rasterizerState = new RasterizerState();
rasterizerState.CullMode = CullMode.CullCounterClockwiseFace;
SharedGraphicsDeviceManager.Current.GraphicsDevice.RasterizerState =
rasterizerState;
SharedGraphicsDeviceManager.Current.GraphicsDevice.DepthStencilState =
DepthStencilState.Default;
It turns out that you need to set the CullMode and the DepthStencilState to get the model to show up correctly.
The last bit of important code is the DrawSampleModel method.
private void DrawSampleModel(Model m)
{ if (m == null)
return;
foreach (ModelMesh mesh in m.Meshes)
{ foreach (ModelMeshPart meshPart in mesh.MeshParts)
{ // set the vertex source to the mesh part's vertex buffer @ offset
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetVertexBuffer(
meshPart.VertexBuffer, meshPart.VertexOffset);
// set the index buffer to the mesh part's
SharedGraphicsDeviceManager.Current.GraphicsDevice.Indices =
meshPart.IndexBuffer;
foreach (EffectPass effectPass in basicShader.CurrentTechnique.Passes)
{ effectPass.Apply();
SharedGraphicsDeviceManager.Current.GraphicsDevice.DrawIndexedPrimitives(
PrimitiveType.TriangleList,
0,
0,
meshPart.NumVertices,
meshPart.StartIndex,
meshPart.PrimitiveCount);
}
}
}
}
This method iterates through the model parts and draws each part.
What you end up with is this.

Google SketchUp WP7 model in action
You can have both Visual Studio and Google SketchUp opened at the same time. When you export new edits to your Google SketchUp model directly to the file included in your content project, you can see the results of your edit on your phone within seconds with just a rebuild of that project.
The only other gotcha is the orientation of the model within Google SketchUp with your phone. With the alignment code used in all of my XNA blogs, I’ve found that when working within Google SketchUp the blue axis points directly out the top of the phone, the red axis points directly out the back of the phone, and the green axis points directly out of the left side of the phone.
What’s next? Importing textures from Google SketchUp and maybe a better looking compass.