computer hardware interface PC Interface rs232 serial port com port computer interface profibus modbus interface protocol pc interfacing rs485 rs422

Computer Hardware Interface I/O Input/Output RS232 RS485 RS422 Modbus Protocol Profibus Serial port parallel port usb Free Software Free Ebook Free Resources

Computer Hardware PC Interface knowledge base site

| HOME | RS232 to Ethernet | RS485 | Computer Interface Books | How-TO-SERIAL PORTs | How-To-PARALLEL PORTs
| How to access serial and parallel ports (Port Programming) | Computer Interface LINK | Link to us | Sitemap |

Quick Menu
Computer Interface Directory
Hardware interface Store
Free Interface Software
Hardware Interface Chip
RS422/RS485 IC
USB IC
Interface Protocol
Free Library (PDF)
USB Library (PDF)
Link Exchange
Hardware Interface Software

Advertising

HVAC Systems Design

Microsoft Zune MP3 Player

Web Hosting Compare

High Definition


Hardware interface Feed

 

PC Hardware interface knowledge update
  Brian Peek www.brianpeek.com Code It: Download  Run It: Download Difficulty: Advanced Time Required: Many hours Cost: About $250 for required hardware  Software Needed:  Visual Basic or Visual C# Express, XNA Game Studio, WiimoteLib, ThinkGearNET Hardware: Wiimote controller, MindSet headset by Neurosky, IR LED or Wireless Sensor Bar

Introduction

Back in July of 2009 I learned of Neurosky’s MindSet device via Engadget.com.  The MindSet is a a brain-wave sensing headset costing only $200.  I contacted the company and was placed on a short list to receive hardware when available to build a demo for PDC 2009.  This is the result of that project. MindBlaster is a pretty simple game written using XNA which runs on a Windows PC in conjunction with the MindSet headset and a Nintendo Wiimote.  The object of the game is to move your head, which moves a targeting reticule on the screen, to aim at enemy alien ships, then focus your concentration on the selected ship to heat it up and eventually make it explode.  The more you focus, the faster the alien ship explodes.  As more enemies are destroyed, they faster they fire back at you, causing a pretty frenetic gameplay experience until the onslaught becomes too much and you are destroyed. So let’s take a look at some of the components that went into creating MindBlaster and how the integrate into the final game.

MindSet Headset

As stated, the Mindset is a headset from Neurosky that is capable of reading alpha, beta, theta and gamma brain waves via 4 dry sensors, 3 located on the left earpiece, and one on an arm that touches the forehead.  In addition, the device contains a microphone and ear speakers, making it a full-on audio headset as well. This device connects to a PC via Bluetooth and shows up as a Bluetooth serial port.  This means we can communicate with the device extremely easily.  However, Neurosky also includes their own API with a simple C# wrapper that makes .NET development even easier.  On top of that, I wrote a simple API which somewhat mimics my Managed Wiimote Library, which is available as a separate download from this application to make .NET development with the Mindset a bit easier. One of the interesting things about the MindSet is that you don’t actually need to know what brain-waves are or how they work.  I don’t know the difference between an alpha wave and a theta wave, but I was still able to produce a decent game using brain waves.  Neurosky has developed a proprietary algorithm which takes your brain-wave values and turns them into a single score from 1 to 100 of “attention” and “meditation” allowing you can very easily determine how hard a person is concentrating or relaxing. Using the provided API is pretty simple.  Here is a chunk of code that connects to the headset and pulls brain-wave values in a loop.  You wouldn’t want to use this directly in your application, but it covers the important points: // get a connection ID int connectionId = ThinkGear.TG_GetNewConnectionId(); if (connectionId < 0) return false ;   // connect to a COM port int connect = ThinkGear.TG_Connect(_connectionId, @"\.\COM1" , baud, ThinkGear.STREAM_PACKETS); if (connect < 0) return false ;   while ( true ) { // read data packets int packetsRead = ThinkGear.TG_ReadPackets(_connectionId, -1);   if (packetsRead > 0) { // get the attention value if (ThinkGear.TG_GetValueStatus(_connectionId, ThinkGear.DATA_ATTENTION) != 0) int attention = ThinkGear.TG_GetValue(connectionId, ThinkGear.DATA_ATTENTION);   // get the meditation (relaxation) value if (ThinkGear.TG_GetValueStatus(_connectionId, ThinkGear.DATA_MEDITATION) != 0) int attention = ThinkGear.TG_GetValue(connectionId, ThinkGear.DATA_MEDITATION); } }   This opens a connection ID with the API, opens a connection to the headset on a provided COM port, then, in a loop, reads all available packets from the headset and retrieves the current state of the values requested. I have created a very simple library which interfaces with the headset and put it up as a separate download.  If you’re just interested in using this headset with .NET, please see the article on the library at <blah>.

Wiimote

A Nintendo Wiimote is strapped upside-down to the top of the headset and its IR camera is used for 2D head tracking.  For PDC, I removed the Wiimote’s guts from its case and mounted them directly to the headset, adding a battery holder box from Radio Shack.  This isn’t really necessary, and you can just mount your own cased Wiimote on top. CIMG1285 The Wimote contains a 1024x768 camera which can detect the positions of up to 4 IR sources.  For this application, I only needed a single IR source since rotation is unimportant.  I used the Nyko Wireless Sensor Bar for this source with one side covered in electrical tape. Using my Managed Wiimote Library, I am able to poll the current state of the IR camera to pull the XY position of the IR source and translate that to the position of the targeting reticule on the screen. WiimoteState wmState = MindBlasterGame.Wiimote.WiimoteState;   if (wmState.IRState.IRSensors[0].Found) { _reticule.Position.X = (wmState.IRState.IRSensors[0].Position.X) * MindBlasterGame.ScreenSize.X; _reticule.Position.Y = MindBlasterGame.ScreenSize.Y - (wmState.IRState.IRSensors[0].Position.Y) * MindBlasterGame.ScreenSize.Y; }

Game Architecture

The game uses parts of the XNA Creators Club Game State Management sample for the overall game and screen architecture.  Each screen is broken out into its own object with its own Update , Draw , LoadContent , etc. methods.  This allows the title screen, settings screen, gameplay screen, and final scoring screen to remain separate “entities” for easier development. Let’s talk a little bit about each screen and important points for each.

Settings Screen

While developing at home and demonstrating this at PDC, I needed a very quick and dirty settings screen to easily enable and disable various features of the game.  For example, I really didn’t want to wear the headset the entire time I was developing, so I wrote some code which allowed me to use the mouse in place of the actual hardware.  The settings screen allowed me to easily toggle those types of things.  This was accomplished by creating an object that inherits from the MenuScreen class from the Game State Management sample.  Menu entries can be then created as follows: private readonly MenuEntry _menuEntry; _menuEntry = new MenuEntry( "My menu entry" ); _menuEntry.Selected += _menuEntry_Selected; MenuEntries.Add(_menuEntry); This creates the entry, sets its event handler, and adds it to the list.  In the event handler, you can do whatever might be required; toggle an option on or off, increase or decrease a value, etc.

Title Screen

The title screen doesn’t do much other than display the title graphic and the “Press Start” text, or any additional text noting if the headset is detached, the Wiimote is not connected, or other error conditions. 

Gameplay Screen

This is where the magic happens.  Perhaps surprisingly, there isn’t a ton of code in the GamePlayScreen.cs file itself.  The logic for the game is quite simple:  move the targetting reticule based on the Wiimote IR position, detect if it’s over an enemy, if it is, remove hitpoints from the enemy, and if the hitpoints drop below 0, blow up the enemy.  In addition, the enemies fly around the screen and fire missiles at the player.  Let’s break down some of these elements.
Enemies
Every drawable object in the game derives from the Drawable2D or Drawable3D base classes.  These classes define some simple properties like position, origin, scale, etc. and provide some methods which must be implemented by the derived classes, like LoadContent , Update and Draw , matching the architecture of a standard XNA application.  Here is what the Drawable2D class looks like: public abstract class Drawable2D { public Vector2 Position; public Vector2 Origin; public float Rotation; public float Scale;   public Drawable2D() { Scale = 1.0f; Visible = true ; }   public Drawable2D(Vector2 position) { Position = position; }   // Update/Draw/Load which needs to be implemented by all Drawable types public virtual void LoadContent(ContentManager contentManager) {} public virtual void Update(GameTime gameTime) {} public virtual void Draw(GameTime gameTime) {}   public bool Visible { get; set; } } The Drawable3D class looks almost identical to this, but moves everything to 3D space using Vector3 to denote positions.  It also includes a Model property to hold the geometry of the object, and a property to hold the current World transformation matrix.  public class Drawable3D { public Vector3 Position; public Vector3 Rotation; public float Scale;   protected Model Model; protected Matrix World;   private Vector2 _position2D;   protected Drawable3D() { Scale = 1.0f; Visible = true ; }   public Drawable3D(Vector3 position) { Position = position; }   public Vector2 GetPosition2D(Camera camera) { Vector3 position = MindBlasterGame.ScreenManager.GraphicsDevice.Viewport.Project( this .Position, camera.Projection, camera.View, Matrix.Identity); _position2D.X = position.X; _position2D.Y = position.Y; return _position2D; }   // Update/Draw/Load which needs to be implemented by all Drawable types public virtual void LoadContent(ContentManager contentManager) {} public virtual void Update(GameTime gameTime, Camera camera) {} public virtual void Draw(GameTime gameTime, Camera camera) {}   public bool Visible { get; set; }
The enemy ships and missiles were modeled in 3D Studio Max and exported to the .X file format to be used with the XNA Content Pipeline by kW X-port, a free plugin for 3D Studio Max that can export models and animations to the .X format with a variety of user-controllable options.
Rotate and fly
If you’ve played the game or watched the video above, you’ve seen how the enemies rotate toward a position, fly to that position, and then rotate back toward the camera.  To make the ships behave in this way, a random position in 3D space is calculated.  Then the following code is run to rotate the ship to that position: protected Matrix RotateToTarget(Vector3 position, Vector3 target) { // get direction from position to target Vector3 vecToTarget = Vector3.Normalize(target - position);   // get angle to rotate double angle = Math.Acos(Vector3.Dot(vecToTarget, Vector3.Backward));   // get axis to rotate on Vector3 axisToRotate = Vector3.Cross(Vector3.Backward, vecToTarget); axisToRotate.Normalize();   // create a rotation matrix from the calculated axis and angle return Matrix.CreateFromAxisAngle(axisToRotate, ( float )angle); }   This code uses some vector math to get the direction the random point is in relation to the current ship orientation.  The rotation angle is then calculated, the axis to actually rotate on depending on the location, and finally a rotation matrix is calculated and returned. This returned rotation matrix is used to rotate the ship over a short period of time using the Vector3.SmoothStep method, which interpolates between two values using a cubic equation.  Once it has reached the target rotation, the ship then “smooth steps” to its destination position, then “smooth steps” back to being rotated toward the camera, using that same RotateToTarget method above, this time with the target position being the camera position.

Using XNA Samples

I used a few samples from the XNA Creators’ Club in MindBlaster.  I think there are some fabulous samples available on the site that make life easier for all XNA developers.  I can’t speak for everyone, but I’d rather pull out a known working, tested particle engine than spend the time to create one from scratch, especially on a deadline like I had for this project.  Earlier in the article I explained how I used the Game State Management sample to provide the base screen architecture for my application.  I also used its MenuScreen class to build my settings screen as described above.
Particle Engine
I also used the 2D particle engine from the NetRumble sample.  While my game is really in 3D, I was able to use some trickery to throw a particle effect up on the screen in 2D space on top of the ship’s location in 3D space which provide a reasonable looking effect. The particle engine provides sample effects for various explosions and trails.  I used these effects for the enemy ship exploding, the enemy ship firing missiles, and the enemy ship spawning into view. To get the 2D screen-space position of a 3D object in world-space, I used the following method, which is part of the Drawable3D class: public Vector2 GetPosition2D(Camera camera) { Vector3 position = MindBlasterGame.ScreenManager.GraphicsDevice.Viewport.Project( this .Position, camera.Projection, camera.View, Matrix.Identity); _position2D.X = position.X; _position2D.Y = position.Y; return _position2D; } This uses XNA’s Viewport.Project method to return the 2D position of this 3D object using the current 3D position, the current camera projection matrix, and the camera’s view matrix and turns it into a 2D point with no additional effort. So, when an enemy ship explodes or a missile is fired, the 3D object’s 2D position is calculated, and the 2D particle effect is displayed at that position. The background Starfield was also taken from NetRumble.  This is a single class file that can be was easily pulled right out of the sample code and plopped into any XNA application to give a very nice moveable background.
Bloom effect
The Bloom Postprocessor sample on the XNA Creators Club site provides a very easy to use bloom DrawableGameComponent .  A DrawableGameComponent is a class which implements a few methods such as Update and Draw , and then added to the Game object’s Component collection.  During each frame, the appropriate methods are called and the object updates and draws. Blooming is a neat effect that produces some artifacts caused by bright lights.  Think of how your vision reacts when you walk from a dark room out into a bright sunny day.  I use this effect throughout the game to give the ships a neat glowing effect as they heat up.  Also, when an enemy missile hits the player’s screen, I flash the screen white for a few milliseconds, and the bloom effect in combination with this produces a nice effect similar to staring at a bright light and then looking quickly looking at something dark as your eyes adjust to the light difference. Using the BloomComponent is amazingly simple.  In the game’s Initialize method: Bloom = new BloomComponent( this ); Components.Add(Bloom); Bloom.Settings = BloomSettings.PresetSettings[3]; With this in place, the object does the rest by hooking into the drawing surfaces and applying the effect every frame.  The sample includes several different bloom effects with varying parameters, so its easy to experiment with these values and see what works best for your game.

How to Install and Play

Ensure you have either XNA Game Studio 3.1 installed, or the XNA 3.1 redistributable package. To play, start the executable and configure the settings that are appropriate to your setup.  For example, you can turn off the Wiimote or the headset if you don’t have the hardware to support it. Up/Down/Left/Right - navigate setup menus 1 - Turn on debug info in game F1 - Back F2 - Start Page Up - Reset the game If you have turned off Wiimote support in the setup screen, the mouse can be used to move the targetting reticule. If you have turned off headset support in the setup screen, the attention level can be increased/decreased with the left/right mouse buttons. Move the reticule over an enemy and focus your attention on the enemy to make your attention score increase.  The higher your attention score, the faster the enemy heats up and explodes.  As you play, enemies will start to fire back at you.  Move the reticule over the missiles and focus your attention to blow them up before they hit you.

Conclusion

There is obviously more to the game than what is seen here, however it would be impossible to cover every single bit of the code in an article here.  I have tried to cover the important and more difficult parts here and show how it’s very easy to leverage existing samples to get an XNA game up and running quickly. If you have any questions or would like further explanation on how a certain portion of the game works, please contact us and I will be happy to answer the questions and perhaps expand the article further.

Thanks

  • Joey Buczek for all of the 3d models and artwork in the game
  • David Wallimann for all of the sound effects and music in the game
  • Rick Barraza for the idea of moving the game into a space environment.  Better than the road I was heading down… :)
  • Greg Hyver and Johnny Liu from Neurosky for getting me set up with headsets and providing loaners for PDC
  • Michelle Leavitt for an infinite amount of playtesting
  • The XNA folks who created the various tutorials and starter kits whose code saved me a ton of time in creating the game.  Without these, I’d probably still be writing particle engines, bloom shaders and various other pieces from scratch….these are a huge time saver!

Bio

Brian is a Microsoft C# MVP who has been actively developing in .NET since its early betas in 2000, and who has been developing solutions using Microsoft technologies and platforms for even longer. Along with .NET, Brian is particularly skilled in the languages of C, C++ and assembly language for a variety of CPUs. He is also well-versed in a wide variety of technologies including web development, document imaging, GIS, graphics, game development, and hardware interfacing. Brian has a strong background in developing applications for the health-care industry, as well as developing solutions for portable devices, such as tablet PCs and PDAs. Additionally, Brian has co-authored the book "Coding4Fun: 10 .NET Programming Projects for Wiimote, YouTube, World of Warcraft, and More" published O'Reilly. He previously co-authored the book "Debugging ASP.NET" published by New Riders.  Brian is also an author for MSDN's Coding4Fun website.

WiiEarthVR – A Fully Immersive 3D Experience with Virtual Earth 3D
globe4 In this article, Brian Peek will demonstrate how to use a Nintendo Wii Remote (Wiimote), a Wii Fit Balance Board, and Vuzix VR920 glasses as input devices for Microsoft Virtual Earth 3D, providing a fully immersive, 3D experience. Brian PeekASPSOFT, Inc. Difficulty: Intermediate Time Required: 2 -3 hours Cost: $60 for Wiimote and Nunchuk, $90 for Wii Fit (which includes Balance Board), $400 for Vuzix VR920 glasses Software: Managed Library for Nintendo's Wiimote, Visual Basic or Visual C# Express Editions Hardware: Nintendo Wii Remote (Wiimote) with Nunchuk, Wii Fit Balance Board, Vuzix VR920 glasses, a compatible PC Bluetooth adapter and stack Download: DownloadDiscussion Forum: Forum  

Introduction

Virtual Earth is the 3D interface to Microsoft's Live Maps service.  Normally this control is loaded via the web browser and allows interaction with a keyboard, mouse, and Xbox 360 controller.  In this article, we will take the Virtual Earth 3D control out of the web browser, use it in a WinForms application, and control it with a Nintendo Wii Remote (Wiimote) and a pair of Vuzix VR920 glasses, while also providing a stereoscopic 3D image to the glasses, creating the illusion of a fully three dimensional environment.  Note that use of the Virtual Earth 3D control in this way is undocumented and unsupported at the moment.  Because of this, some of the descriptions in this article are educated guesses and may not be 100% accurate… Originally, this project started as a simple Wiimote interface to Virtual Earth 3D as shown in the video below.  Since I wrote that application, I learned of the VR920 glasses and the Wii Fit Balance Board was released, so I’ve decided to create a more immersive experience using all of these controls which was demonstrated at PDC2008, shown here: Get Microsoft Silverlight  

Setup

Before we get started, you will need to install the Virtual Earth 3D control.  If you haven't done this already, browse to http://maps.live.com/ and click on the 3D link to install the control and supporting software. captured_Image.png Additionally, if you haven't already, please review my Managed Library for Nintendo's Wiimote article on this site.  We will be using the library in this article, but I will not repeat the basic information that is located in the original article.  You will also need to have the Vuzix VR920 glasses installed and setup according to its own user manual.  That will also not be covered here.

Implementation

The Virtual Earth 3D Control

The Virtual Earth 3D (VE3D) control is intended to be used through a well documented JavaScript interface from a web page, however we would not be able to access the Wiimote or VR920 glasses from JavaScript.  Therefore, we will be using the VE3D control through its native, but wholly undocumented interface. Start by creating a new Windows Forms application named WiiEarthVR in C# or VB.  As with all controls and 3rd party libraries, a reference needs to be set to the Virtual Earth 3D libraries.  Add references to the following items: 
  • Microsoft.MapPoint.Data
  • Microsoft.MapPoint.Data.VirtualEarthTileDataSource
  • Microsoft.MapPoint.Geometry
  • Microsoft.MapPoint.Rendering3D
  • Microsoft.MapPoint.Rendering3D.Utility
If they do not show up in the .NET references, they can be found by selecting the Browse tab and navigating to C:\Program Files\Virtual Earth 3D\ or C:\Program Files (x86)\Virtual Earth 3D\ .  With the references in place, the project file can now be opened and the references will be seen in the References folder in the Solution Explorer as usual. captured_Image.png[11] Creating an instance of the control can be done in code just like any other control.  Used in the constructor or load event of the form, the following code will create a VE3D control and add it to the form as fully docked: C# private GlobeControl _globeControl;   public MainForm() { InitializeComponent();   _globeControl = new GlobeControl(); SuspendLayout(); _globeControl.Location = new System.Drawing.Point(0, 0); _globeControl.Name = "_globeControl" ; _globeControl.Size = ClientSize; _globeControl.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom; _globeControl.TabIndex = 0; _globeControl.SendToBack(); // we want the button to be on top   pnlGlobe.Controls.Add(_globeControl); ResumeLayout( false ); } VB Private _globeControl As GlobeControl   Public Sub New () InitializeComponent()   _globeControl = New GlobeControl() SuspendLayout() _globeControl.Location = New System.Drawing.Point(0, 0) _globeControl.Name = "_globeControl" _globeControl.Size = ClientSize _globeControl.Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top Or AnchorStyles.Bottom _globeControl.TabIndex = 0 _globeControl.SendToBack() ' we want the button to be on top   pnlGlobe.Controls.Add(_globeControl) ResumeLayout( False ) End Sub This sets up the VE3D control in its default state.  If you were to run an application with only this code, you would see nothing but the earth.  The navigation controls and other extras would be missing. We can start adding items to the VE3D control by listening for the FirstFrameRendered event of the GlobeControl and then setting the appropriate properties.  Setting these properties prior to this point can lead to some unexpected results. In the FirstFrameRendered event handler, if you wish to add the default navigation controls to the screen, the PlugInLoader object is used.  The PlugInLoader is created by using the CreateLoader static method, passing in an instance of the GlobeControl 's Host object.  Then, the NavigationPlugIn can be loaded and activated as shown: C# // load all the spiffy UI navigation goodies PlugInLoader loader = PlugInLoader.CreateLoader( this .globeControl.Host); loader.LoadPlugIn( typeof (NavigationPlugIn)); loader.ActivatePlugIn( typeof (NavigationPlugIn).GUID, null ); VB ' load all the spiffy UI navigation goodies Dim loader As PlugInLoader = PlugInLoader.CreateLoader( Me .globeControl.Host) loader.LoadPlugIn( GetType (NavigationPlugIn)) loader.ActivatePlugIn( GetType (NavigationPlugIn).GUID, Nothing ) The last thing to be added for basic functionality is the data.  As it stands, the only data that will appear on the globe is the image of the continents.  Zooming in only produces a blurry representation of that base image. Data layers are created from specially formatted data sources provided by maps.live.com known as content manifests.  These are XML files which tell the VE3D control how to load the data required for any view.  Content layers can be added by adding them to the DataSources object of the GlobeControl.  We can add any of the following layers (note that there may be other content manifests provided by maps.live.com, but these are the only 5 that I am aware of): URL DataSourceUsage Type Description http://local.live.com/Manifests/HD.xml ElevationMap Terrain data http://local.live.com/Manifests/MO.xml Model 3D buildings http://local.live.com/Manifests/AT.xml TextureMap Unlabeled aerial http://local.live.com/Manifests/HT.xml TextureMap Labeled aerial http://local.live.com/Manifests/RT.xml TextureMap Roads only For the best display, add the ElevationMap , Model and Aerial TextureMap layers as shown: C# // set various data sources, here for elevation data, terrain data, and model data. _globeControl.Host.DataSources.Add( new DataSourceLayerData( "Elevation" , "Elevation" , @"http://maps.live.com//Manifests/HD.xml" , DataSourceUsage.ElevationMap)); _globeControl.Host.DataSources.Add( new DataSourceLayerData( "Texture" , "Texture" , @"http://maps.live.com//Manifests/AT.xml" , DataSourceUsage.TextureMap)); _globeControl.Host.DataSources.Add( new DataSourceLayerData( "Models" , "Models" , @"http://maps.live.com//Manifests/MO.xml" , DataSourceUsage.Model)); VB ' set various data sources, here for elevation data, terrain data, and model data. _globeControl.Host.DataSources.Add( New DataSourceLayerData( "Elevation" , "Elevation" , "http://maps.live.com//Manifests/HD.xml" , DataSourceUsage.ElevationMap)) _globeControl.Host.DataSources.Add( New DataSourceLayerData( "Texture" , "Texture" , "http://maps.live.com//Manifests/AT.xml" , DataSourceUsage.TextureMap)) _globeControl.Host.DataSources.Add( New DataSourceLayerData( "Models" , "Models" , "http://maps.live.com//Manifests/MO.xml" , DataSourceUsage.Model)) By passing the URL of the content manifest, a name for the layer, and what the manifest represents, a new DataSource is created, which is in turn used to create a DataSourceLayerData object which is then given to the VE3D control to consume.  This should be done in the FirstFrameRendered event handler as well. We also need to setup VE3D to turn off any UI elements, turn on the atmosphere effects, and ensure we have a full unobstructed view. Again, in the FirstFrameRendered event handler, we can use the following code to achieve this: C# // turn on the nice atmosphere _globeControl.Host.WorldEngine.Environment.AtmosphereDisplay = Microsoft.MapPoint.Rendering3D.Atmospherics.EnvironmentManager.AtmosphereStyle.Scattering;   // default to all off _globeControl.Host.WorldEngine.Display3DCursor = false ; _globeControl.Host.WorldEngine.SetWindowsCursor( null ); _globeControl.Host.WorldEngine.ShowNavigationControl = false ; _globeControl.Host.WorldEngine.ShowCursorLocationInformation = false ; _globeControl.Host.WorldEngine.ShowScale = false ; _globeControl.Host.WorldEngine.ShowUI = false ; _globeControl.Host.WorldEngine.Environment.SunPosition = null ; _globeControl.Host.WorldEngine.Environment.LocalWeatherEnabled = true ; _globeControl.Host.WorldEngine.BaseCopyrightText = " " ; // workaround for a display issue VB ' turn on the nice atmosphere _globeControl.Host.WorldEngine.Environment.AtmosphereDisplay = Microsoft.MapPoint.Rendering3D.Atmospherics.EnvironmentManager.AtmosphereStyle.Scattering   ' default to all off _globeControl.Host.WorldEngine.Display3DCursor = False _globeControl.Host.WorldEngine.SetWindowsCursor( Nothing ) _globeControl.Host.WorldEngine.ShowNavigationControl = False _globeControl.Host.WorldEngine.ShowCursorLocationInformation = False _globeControl.Host.WorldEngine.ShowScale = False _globeControl.Host.WorldEngine.ShowUI = False _globeControl.Host.WorldEngine.Environment.SunPosition = Nothing _globeControl.Host.WorldEngine.Environment.LocalWeatherEnabled = True _globeControl.Host.WorldEngine.BaseCopyrightText = " " ' workaround for a display issue If you were to run the application at this point, you would see a fully functioning Virtual Earth 3D control with proper data and navigation.

Control Scheme and Bindings

The user will control VE3D with the Wiimote by holding the nunchuk in the left hand, which will move the user forward/back/left/right using the joystick.  The C and Z buttons on the front of the nunchuk will be used to raise and lower the altitude of the camera.  The Wiimote, held in the right hand, will be used to toggle various things on or off and interact with menus.  The user will also stand on the Balance Board which will use their center of gravity to turn them in the environment. VE3D bindings allow you to change or create new control schemes for VE3D by placing an XML file in a specific directory as follows:
  • Vista: C:\Users\<username>\AppData\LocalLow\Microsoft\Virtual Earth 3D
  • XP: C:\Documents and Settings\<username>\Local Settings\Microsoft\Virtual Earth 3D
In this directory you will find a Bindings.xml file.  This XML schema defines the default keyboard, mouse, Gamepad and other input device properties.  Open the file to see the schema used to define events and parameters. By default, VE3D will load any file named Bindings*.xml from this directory.  For the Wiimote control scheme, create a new file named BindingsWiiEarthVR.xml in this directory.  Set the contents of the file to the following: <? xml version ="1.0" encoding ="utf-8" ? > < Bindings > < BindingSet Name ="WiiEarthVRBindings" AutoUse ="True" Cursor ="Drag" > <!-- Nunchuk joystick --> < Bind Event ="Wiimote.NunchukX" Factor ="0.5" >< Action Name ="Strafe" /></ Bind > < Bind Event ="Wiimote.NunchukY" Factor ="1" >< Action Name ="Move" /></ Bind >   <!-- Nunchuk buttons --> < Bind Event ="Wiimote.NunchukC" Factor ="0.20" >< Action Name ="Ascend" /></ Bind > < Bind Event ="Wiimote.NunchukZ" Factor ="-0.20" >< Action Name ="Ascend" /></ Bind >   <!-- Balance Board --> < Bind Event ="Wiimote.BalanceBoardX" Factor ="-0.0009" >< Action Name ="Turn" /></ Bind > < Bind Event ="Wiimote.BalanceBoardY" Factor ="0.0009" >< Action Name ="Ascend" /></ Bind >   <!-- Wiimote buttons --> < Bind Event ="Wiimote.Home" >< Action Name ="ResetOnCenter" /></ Bind > < Bind Event ="Wiimote.A" Factor ="1" >< Action Name ="Locations, WiiEarthVR, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /></ Bind >   < Bind Event ="Wiimote.Left" Factor ="-1" >< Action Name ="Locations, WiiEarthVR, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /></ Bind > < Bind Event ="Wiimote.Up" Factor ="-1" >< Action Name ="LocationsMove, WiiEarthVR, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /></ Bind > < Bind Event ="Wiimote.Down" Factor ="1" >< Action Name ="LocationsMove, WiiEarthVR, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /></ Bind >   <!-- FPS-style keyboard controls in case we don't have a nunchuk --> < Bind Event ="Key.W" Factor ="22" >< Action Name ="Move" /></ Bind > < Bind Event ="Key.S" Factor ="-22" >< Action Name ="Move" /></ Bind > < Bind Event ="Key.D" Factor ="22" >< Action Name ="Strafe" /></ Bind > < Bind Event ="Key.A" Factor ="-22" >< Action Name ="Strafe" /></ Bind > < Bind Event ="Key.Space" Factor ="20" >< Action Name ="Ascend" /></ Bind > < Bind Event ="Key.C" Factor ="-20" >< Action Name ="Ascend" /></ Bind >   <!-- Other keys --> < Bind Event ="Key.F1" >< Action Name ="VR920SetZero, WiiEarthVR, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /></ Bind > < Bind Event ="Key.F2" >< Action Name ="BalanceBoardSetZero, WiiEarthVR, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /></ Bind > < Bind Event ="Key.F3" >< Action Name ="ToggleVR920Stereo, WiiEarthVR, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /></ Bind >   < Bind Event ="Wiimote.Minus" Factor ="-0.1" >< Action Name ="VR920SetEyeDistance, WiiEarthVR, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /></ Bind > < Bind Event ="Wiimote.Plus" Factor ="0.1" >< Action Name ="VR920SetEyeDistance, WiiEarthVR, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /></ Bind >   < Bind Event ="Wiimote.One" >< Action Name ="ToggleBalanceBoard, WiiEarthVR, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /></ Bind > < Bind Event ="Wiimote.Two" >< Action Name ="ToggleVR920, WiiEarthVR, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /></ Bind > < Bind Event ="Key.B" >< Action Name ="ToggleBalanceBoard, WiiEarthVR, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /></ Bind > < Bind Event ="Key.V" >< Action Name ="ToggleVR920, WiiEarthVR, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /></ Bind > < Bind Event ="Key.F" >< Action Name ="FullScreen, WiiEarthVR, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /></ Bind > </ BindingSet > </ Bindings > The <BindingSet> tags wrap groups of control bindings.  It requires a Name and optionally a Cursor .  If the binding set is to be used automatically, as it would be in most cases, set the AutoUse parameter to True .  Inside of that are <Bind> tags.  The tag requires the Event parameter and optionally the Factor parameter.  The Event parameter will be used to match the binding to its handler which will be written later.  The syntax is <Handler Name>.<Event Name>.  The Factor parameter is optional and can be used to scale the data value up or down to increase or decrease sensitivity of the input method.  The Action tag inside the Bind tag is used to map the specific binding to a particular method.  Once the handler is written, these will make more sense. The bindings above create the control scheme described above:  NunchukX/Y describe what happens when the analog joystick is moved, NunchukC/Z describe what happens with the C/Z buttons are pressed, and so on. The bindings also allow for several variations.  Bindings are defined for both the IR position ( IRX , IRY ) and accelerometer values ( AX , AY ).  If an IR sensor bar is not available, the accelerometer values of the Wiimote can be used instead.  Additionally, keyboard bindings are created in the style of a first person shooter using WASD.  These can be used if a Nunchuk is not available. Note that some bindings append two Event s together with a + sign.  This allows for button combinations.  In this case, for the accelerometer and/or IR sensor, we only want to register the action if a button is pressed down.  So, those events which require the button to be held down contain Wiimote.B+ and the event it is combined with. For those events which require a custom action that will be written separately and not part of the VE3D control, the Action parameter must contain the action name, followed by a comma, and then the full assembly name: < Bind Event ="Wiimote.One" > < Action Name ="ToggleBalanceBoard, WiiEarthVR, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> </ Bind > You can change any of these button bindings simply by changing this XML file and deploying to the directory above.

Event Source

An EventSource is needed which will grab data from the Wiimote and pass it along to VE3D as defined by the bindings file above.  Create a new class named WiimoteEventSource which derives from Microsoft.MapPoint.Binding.EventSource   as follows: Next, add an enumeration named WiimoteEvent (the name isn't important) which contains all of the Name items from the bindings XML file above.  It should look like this: C# // all events handled by this event source from XML file public enum WiimoteEvent { IRX, // IR X position IRY, // IR Y position NunchukX, // Nunchuk joystick X position NunchukY, // Nunchuk joystick Y position NunchukC, // Nunchuk C button NunchukZ, // Nunchuk Z button AX, // Wiimote accelerometer X AY, // Wiimote accelerometer Y Up, // Dpad up Down, // Dpad down Left, // Dpad left Right, // Dpad right A, // A button B, // B button Minus, // Minus button Home, // Wiimote Home button Plus, // Plus button One, // Wiimote One button Two, // Wiimote Two button BalanceBoardX, // Balance Board COG X BalanceBoardY // Balance Board COG Y } VB ' all events handled by this event source from XML file Public Enum WiimoteEvent IRX ' IR X position IRY ' IR Y position NunchukX ' Nunchuk joystick X position NunchukY ' Nunchuk joystick Y position NunchukC ' Nunchuk C button NunchukZ ' Nunchuk Z button AX ' Wiimote accelerometer X AY ' Wiimote accelerometer Y Up ' Dpad up Down ' Dpad down Left ' Dpad left Right ' Dpad right A ' A button B ' B button Minus ' Minus button Home ' Wiimote Home button Plus ' Plus button One ' Wiimote One button Two ' Wiimote Two button BalanceBoardX ' Balance Board COG X BalanceBoardY ' Balance Board COG Y End Enum Next, several methods from the EventSource object need to be overridden:  GetEventData , IsModifier , CanModify , TryGetEventId , TryGetEventName , Name .  The methods do the following: Method/Property Description GetEventData Unsure at the moment...does not need to be implemented? IsModifier Returns a boolean stating whether the passed in event ID is a modifier (such as the Wiimote.B event above) CanModify Returns a boolean stating whether the current event is allowed as a modifier TryGetEventID Maps a string event name from the bindings file to the integer value in the enumeration above TryGetEventName Maps an integer event ID to the string name in the enumeration above Name (property) Returns the name of the handler which must match the name in the XML file above (Wiimote in this case) The code for these methods is presented below: C# // return out value of the passed enum public override bool TryGetEventId( string eventName, out int eventId) { eventId = ( int )Enum.Parse( typeof (WiimoteEvent), eventName); return true ; }   // return out the string name of the passed in enum value public override bool TryGetEventName( int eventId, out string eventName) { eventName = ((WiimoteEvent)eventId).ToString(); return true ; }   // unknown public override EventData GetEventData( int eventId, EventActivateState state) { throw new NotImplementedException(); }   // can the event be used as a modifier? public override bool IsModifier( int eventId) { // yes to all for now return true ; }   // can the supplied event be used as a modifier? public override bool CanModify( int eventId, EventKey other) { // only if it's from us return (other.Source == this ); }   // this must match the Source name in the bindings XML file public override string Name { get { return "Wiimote" ; } } VB ' return out value of the passed enum Public Overrides Function TryGetEventId( ByVal eventName As String , <System.Runtime.InteropServices.Out()> ByRef eventId As Integer ) As Boolean eventId = CInt (Fix(System. Enum .Parse( GetType (WiimoteEvent), eventName))) Return True End Function   ' return out the string name of the passed in enum value Public Overrides Function TryGetEventName( ByVal eventId As Integer , <System.Runtime.InteropServices.Out()> ByRef eventName As String ) As Boolean eventName = ( CType (eventId, WiimoteEvent)).ToString() Return True End Function   ' unknown Public Overrides Function GetEventData( ByVal eventId As Integer , ByVal state As EventActivateState) As EventData Throw New NotImplementedException() End Function   ' can the event be used as a modifier? Public Overrides Function IsModifier( ByVal eventId As Integer ) As Boolean ' yes to all for now Return True End Function   ' can the supplied event be used as a modifier? Public Overrides Function CanModify( ByVal eventId As Integer , ByVal other As EventKey) As Boolean ' only if it's from us Return (other.Source Is Me ) End Function   ' this must match the Source name in the bindings XML file Public Overrides ReadOnly Property Name() As String Get Return "Wiimote" End Get End Property With that in place, the constructor can be implemented which will call the base constructor and connect to the Wiimote.  It is assumed you read the Wiimote article above and know how the library works. The constructor must take one argument passed from the main from:  an instance of the GlobeControl 's ActionSystem .  This just gets passed directly to the parent object's constructor untouched.  The constructor code looks like the following: C# public WiimoteEventSource(ActionSystem actionSystem) : base (actionSystem) { // get all connected Wiimotes WiimoteCollection wc = new WiimoteCollection(); wc.FindAllWiimotes(); // setup wiimotes and event handlers foreach (Wiimote wm in wc) { wm.WiimoteChanged += new EventHandler<WiimoteChangedEventArgs>(OnWiimoteChanged); wm.WiimoteExtensionChanged += new EventHandler<WiimoteExtensionChangedEventArgs>(OnWiimoteExtensionChanged); wm.Connect(); // if we don't have an extension, set the report type to IR and accel's only if (!wm.WiimoteState.Extension && wm.WiimoteState.ExtensionType != ExtensionType.BalanceBoard) wm.SetReportType(InputReport.IRAccel, true ); if (wm.WiimoteState.ExtensionType == ExtensionType.BalanceBoard) _bb = wm; else _wm = wm; // turn off all LEDs wm.SetLEDs(0x00); } } VB Public Sub New ( ByVal actionSystem As ActionSystem) MyBase . New (actionSystem) ' get all connected Wiimotes Dim wc As New WiimoteCollection() wc.FindAllWiimotes() ' setup wiimotes and event handlers For Each wm As Wiimote In wc AddHandler wm.WiimoteChanged, AddressOf OnWiimoteChanged AddHandler wm.WiimoteExtensionChanged, AddressOf OnWiimoteExtensionChanged wm.Connect() ' if we don't have an extension, set the report type to IR and accel's only If ( Not wm.WiimoteState.Extension) AndAlso wm.WiimoteState.ExtensionType <> ExtensionType.BalanceBoard Then wm.SetReportType(InputReport.IRAccel, True ) End If If wm.WiimoteState.ExtensionType = ExtensionType.BalanceBoard Then _bb = wm Else _wm = wm End If ' turn off all LEDs wm.SetLEDs(&H00) Next wm End Sub The OnWiimoteExtensionChanged method simply sets the report mode for the Wiimote based on whether or not a Nunchuk is inserted as shown: C# private void OnWiimoteExtensionChanged( object sender, WiimoteExtensionChangedEventArgs args) { if (_wm == null ) return ; // if nunchuk inserted, set the report type to return extension data if (args.ExtensionType == ExtensionType.Nunchuk && args.Inserted) _wm.SetReportType(InputReport.IRExtensionAccel, true ); else // in all other cases, set it to the default IR and accel's _wm.SetReportType(InputReport.IRAccel, true ); } VB Private Sub OnWiimoteExtensionChanged( ByVal sender As Object , ByVal args As WiimoteExtensionChangedEventArgs) If _wm Is Nothing Then Return End If ' if nunchuk inserted, set the report type to return extension data If args.ExtensionType = ExtensionType.Nunchuk AndAlso args.Inserted Then _wm.SetReportType(InputReport.IRExtensionAccel, True ) Else ' in all other cases, set it to the default IR and accel's _wm.SetReportType(InputReport.IRAccel, True ) End If End Sub The OnWiimoteChanged event handler is where the Wiimote data is handled and sent off to the VE3D control to reflect the changes.  First, we handle the Balance Board.  If the Balance Board is the controller reporting data, we take the center of gravity values and pass them to VE3D.  This code looks at the appropriate values, determines if they are beyond the specified thresholds for the dead zones, and, if they are, activates the event for that value using the Execute method.  Execute is a method in the base EventSource class.  This method will activate the event specified from the enumeration (which, remember, is contained in the bindings XML file) with the value associated with that event.  An EventData object of some type must be created and passed to the Execute method.  There are two EventData types to know about:  AxisEventData and ButtonEventData .  AxisEventData should be used when an event is activated that will modify the map position in some way.  That is, if the map is being turned, elevation is changing, etc.  ButtonEventData should be used if the event is a simple toggle like pressing a button down and releasing it. The Balance Board code can be seen below: C# if (ws.ExtensionType == ExtensionType.BalanceBoard && this .BalanceBoardEnabled) { float x1 = ws.BalanceBoardState.CenterOfGravity.X; if (x1 > Properties.Settings.Default.BBDeadX || x1 < -Properties.Settings.Default.BBDeadX) this .Execute( new AxisEventData( new EventKey( this , ( int )WiimoteEvent.BalanceBoardX), x1 - _zero.X)); float y1 = ws.BalanceBoardState.CenterOfGravity.Y; if (y1 > Properties.Settings.Default.BBDeadY || y1 < -Properties.Settings.Default.BBDeadY) this .Execute( new AxisEventData( new EventKey( this , ( int )WiimoteEvent.BalanceBoardY), y1 - _zero.Y)); } VB If ws.ExtensionType = ExtensionType.BalanceBoard AndAlso Me .BalanceBoardEnabled Then Dim x1 As Single = ws.BalanceBoardState.CenterOfGravity.X If x1 > My.Settings. Default .BBDeadX OrElse x1 < -My.Settings. Default .BBDeadX Then Me .Execute( New AxisEventData( New EventKey( Me , CInt (Fix(WiimoteEvent.BalanceBoardX))), x1 - _zero.X)) End If Dim y1 As Single = ws.BalanceBoardState.CenterOfGravity.Y If y1 > My.Settings. Default .BBDeadY OrElse y1 < -My.Settings. Default .BBDeadY Then Me .Execute( New AxisEventData( New EventKey( Me , CInt (Fix(WiimoteEvent.BalanceBoardY))), y1 - _zero.Y)) End If Next, let's handle the IR and accelerometer data.  The IR midpoint of the X and Y axes will be used from the WiimoteState object to activate the IRX and IRY events we defined above in the bindings XML file.  The accelerometer X and Y values will be used to activate the AX and AY events. This snippet assumes that there is a boolean property named UseIR created in the project to determine whether IR or motion values are used.  Additionally, it assumes there are property settings created which contain values for the X/Y "dead zones" for the IR and accelerometers.  These dead zones are used as a way to only activate the event when the values are pushed beyond the thresholds.  This allows there to be a margin where the user's hand will not be read as movement, allowing the user to not have to worry about keeping a steady hand. The application linked above uses the following default values for dead zones:
  • NunchukDeadX/Y -> 0.025
  • WiimoteDeadX/Y -> 0.15
  • IRDeadX/Y -> 0.1
  • BBDeadX/Y –> 3
C# // if we're using the IR if (Properties.Settings.Default.UseIR) { // and both LEDs are found if (ws.IRState.Found1 && ws.IRState.Found2) { // normalize the midpoints to -0.5 to 0.5 (from 0 to 1.0) float x = ws.IRState.MidX - 0.5f; float y = ws.IRState.MidY - 0.5f;   // if we're beyond the thresholds, activate the events if (x > Properties.Settings.Default.IRDeadX || x < -Properties.Settings.Default.IRDeadX) this .Execute( new AxisEventData( new EventKey( this , ( int )WiimoteEvent.IRX), x)); if (y > Properties.Settings.Default.IRDeadY || y < -Properties.Settings.Default.IRDeadY) this .Execute( new AxisEventData( new EventKey( this , ( int )WiimoteEvent.IRY), y));   // save the last IR settings...these get used if we go beyond the range of the IRs. // in that case, the last used positions will be used until the Wiimote comes back in range this ._lastIRX = x; this ._lastIRY = y; } else // one or both LEDs aren't seen { // activate events based on the last known positions if ( this ._lastIRX > Properties.Settings.Default.IRDeadX || this ._lastIRX < -Properties.Settings.Default.IRDeadX) this .Execute( new AxisEventData( new EventKey( this , ( int )WiimoteEvent.IRX), this ._lastIRX)); if ( this ._lastIRY > Properties.Settings.Default.IRDeadY || this ._lastIRY < -Properties.Settings.Default.IRDeadY) this .Execute( new AxisEventData( new EventKey( this , ( int )WiimoteEvent.IRY), this ._lastIRY)); } } else // we're using motion controls { // activate the events based on the accelerometer values if (ws.AccelState.X > Properties.Settings.Default.WiimoteDeadX || ws.AccelState.X < -Properties.Settings.Default.WiimoteDeadX) this .Execute( new AxisEventData( new EventKey( this , ( int )WiimoteEvent.AX), ws.AccelState.X)); if (ws.AccelState.Y > Properties.Settings.Default.WiimoteDeadY || ws.AccelState.Y < -Properties.Settings.Default.WiimoteDeadY) this .Execute( new AxisEventData( new EventKey( this , ( int )WiimoteEvent.AY), ws.AccelState.Y)); } VB ' if we're using the IR If My.Settings. Default .UseIR Then ' and both LEDs are found If ws.IRState.Found1 AndAlso ws.IRState.Found2 Then ' normalize the midpoints to -0.5 to 0.5 (from 0 to 1.0) Dim x As Single = ws.IRState.MidX - 0.5f Dim y As Single = ws.IRState.MidY - 0.5f   ' if we're beyond the thresholds, activate the events If x > My.Settings. Default .IRDeadX OrElse x < -My.Settings. Default .IRDeadX Then Me .Execute( New AxisEventData( New EventKey( Me , CInt (Fix(WiimoteEvent.IRX))), x)) End If If y > My.Settings. Default .IRDeadY OrElse y < -My.Settings. Default .IRDeadY Then Me .Execute( New AxisEventData( New EventKey( Me , CInt (Fix(WiimoteEvent.IRY))), y)) End If   ' save the last IR settings...these get used if we go beyond the range of the IRs. ' in that case, the last used positions will be used until the Wiimote comes back in range Me ._lastIRX = x Me ._lastIRY = y Else ' one or both LEDs aren't seen ' activate events based on the last known positions If Me ._lastIRX > My.Settings. Default .IRDeadX OrElse Me ._lastIRX < -My.Settings. Default .IRDeadX Then Me .Execute( New AxisEventData( New EventKey( Me , CInt (Fix(WiimoteEvent.IRX))), Me ._lastIRX)) End If If Me ._lastIRY > My.Settings. Default .IRDeadY OrElse Me ._lastIRY < -My.Settings. Default .IRDeadY Then Me .Execute( New AxisEventData( New EventKey( Me , CInt (Fix(WiimoteEvent.IRY))), Me ._lastIRY)) End If End If Else ' we're using motion controls ' activate the events based on the accelerometer values If ws.AccelState.X > My.Settings. Default .WiimoteDeadX OrElse ws.AccelState.X < -My.Settings. Default .WiimoteDeadX Then Me .Execute( New AxisEventData( New EventKey( Me , CInt (Fix(WiimoteEvent.AX))), ws.AccelState.X)) End If If ws.AccelState.Y > My.Settings. Default .WiimoteDeadY OrElse ws.AccelState.Y < -My.Settings. Default .WiimoteDeadY Then Me .Execute( New AxisEventData( New EventKey( Me , CInt (Fix(WiimoteEvent.AY))), ws.AccelState.Y)) End If End If Next, the nunchuk values need to be read and the associated events activated.  This is done as follows: C# // if the nunchuk is connected if (ws.Extension && ws.ExtensionType == ExtensionType.Nunchuk) { // activate the nunchuk-based events if (ws.NunchukState.X > Properties.Settings.Default.NunchukDeadX || ws.NunchukState.X < -Properties.Settings.Default.NunchukDeadX) this .Execute( new AxisEventData( new EventKey( this , ( int )WiimoteEvent.NunchukX), ws.NunchukState.X)); if (ws.NunchukState.Y > Properties.Settings.Default.NunchukDeadY || ws.NunchukState.Y < -Properties.Settings.Default.NunchukDeadY) this .Execute( new AxisEventData( new EventKey( this , ( int )WiimoteEvent.NunchukY), ws.NunchukState.Y)); if (ws.NunchukState.C) this .Execute( new AxisEventData( new EventKey( this , ( int )WiimoteEvent.NunchukC), 1.0f)); if (ws.NunchukState.Z) this .Execute( new AxisEventData( new EventKey( this , ( int )WiimoteEvent.NunchukZ), 1.0f)); } VB ' if the nunchuk is connected If ws.Extension AndAlso ws.ExtensionType = ExtensionType.Nunchuk Then ' activate the nunchuk-based events If ws.NunchukState.X > My.Settings. Default .NunchukDeadX OrElse ws.NunchukState.X < -My.Settings. Default .NunchukDeadX Then Me .Execute( New AxisEventData( New EventKey( Me , CInt (Fix(WiimoteEvent.NunchukX))), ws.NunchukState.X)) End If If ws.NunchukState.Y > My.Settings. Default .NunchukDeadY OrElse ws.NunchukState.Y < -My.Settings. Default .NunchukDeadY Then Me .Execute( New AxisEventData( New EventKey( Me , CInt (Fix(WiimoteEvent.NunchukY))), ws.NunchukState.Y)) End If If ws.NunchukState.C Then Me .Execute( New AxisEventData( New EventKey( Me , CInt (Fix(WiimoteEvent.NunchukC))), 1.0f)) End If If ws.NunchukState.Z Then Me .Execute( New AxisEventData( New EventKey( Me , CInt (Fix(WiimoteEvent.NunchukZ))), 1.0f)) End If End If Finally, the button events need to be activated.  A helper method which will check the current button state will be used for determining which button of all the Wiimote buttons is pressed.  For those that are, the appropriate event is activated with a call to Execute . C# private void HandleButton(WiimoteEvent we, bool buttonState, bool lastButtonState) { if (buttonState == lastButtonState) return ; else { if (buttonState) this .Execute( new ButtonEventData( new EventKey( this , ( int )we), EventActivateState.Activate)); else this .Execute( new ButtonEventData( new EventKey( this , ( int )we), EventActivateState.Deactivate)); } }   // handle all the Wiimote buttons HandleButton(WiimoteEvent.Up, ws.ButtonState.Up, _lastBS.Up); HandleButton(WiimoteEvent.Down, ws.ButtonState.Down, _lastBS.Down); HandleButton(WiimoteEvent.Left, ws.ButtonState.Left, _lastBS.Left); HandleButton(WiimoteEvent.Right, ws.ButtonState.Right, _lastBS.Right); HandleButton(WiimoteEvent.A, ws.ButtonState.A, _lastBS.A); HandleButton(WiimoteEvent.B, ws.ButtonState.B, _lastBS.B); HandleButton(WiimoteEvent.Minus, ws.ButtonState.Minus, _lastBS.Minus); HandleButton(WiimoteEvent.Home, ws.ButtonState.Home, _lastBS.Home); HandleButton(WiimoteEvent.Plus, ws.ButtonState.Plus, _lastBS.Plus); HandleButton(WiimoteEvent.One, ws.ButtonState.One, _lastBS.One); HandleButton(WiimoteEvent.Two, ws.ButtonState.Two, _lastBS.Two);   ...   // save off the current button state for next time _lastBS = ws.ButtonState; _lastNunchuk = ws.NunchukState; VB Private Sub HandleButton( ByVal we As WiimoteEvent, ByVal buttonState As Boolean , ByVal lastButtonState As Boolean ) If buttonState = lastButtonState Then Return Else If buttonState Then Me .Execute( New ButtonEventData( New EventKey( Me , CInt (Fix(we))), EventActivateState.Activate)) Else Me .Execute( New ButtonEventData( New EventKey( Me , CInt (Fix(we))), EventActivateState.Deactivate)) End If End If End Sub   ...   ' handle all the Wiimote buttons HandleButton(WiimoteEvent.Up, ws.ButtonState.Up, _lastBS.Up) HandleButton(WiimoteEvent.Down, ws.ButtonState.Down, _lastBS.Down) HandleButton(WiimoteEvent.Left, ws.ButtonState.Left, _lastBS.Left) HandleButton(WiimoteEvent.Right, ws.ButtonState.Right, _lastBS.Right) HandleButton(WiimoteEvent.A, ws.ButtonState.A, _lastBS.A) HandleButton(WiimoteEvent.B, ws.ButtonState.B, _lastBS.B) HandleButton(WiimoteEvent.Minus, ws.ButtonState.Minus, _lastBS.Minus) HandleButton(WiimoteEvent.Home, ws.ButtonState.Home, _lastBS.Home) HandleButton(WiimoteEvent.Plus, ws.ButtonState.Plus, _lastBS.Plus) HandleButton(WiimoteEvent.One, ws.ButtonState.One, _lastBS.One) HandleButton(WiimoteEvent.Two, ws.ButtonState.Two, _lastBS.Two)   ' save off the current button state for next time _lastBS = ws.ButtonState _lastNunchuk = ws.NunchukState And, the current button values are stored away to check on the next event so button events are only fired once. Now that the event source object is written, it needs to be hooked up to the GlobeControl so it can be used.  This can be done by creating an instance of the WiimoteEventSource object, passing in the VE3D's ActionSystem from the BindingsManager object.  Then, the event source instance is passed to the ActionSystem 's EventSourceManager and registered using the RegisterEventSource method.  Event sources should re registered before the control is added to the form. C# // wiimote events private WiimoteEventSource _wiimoteEventSource;   ...   // create a new instance of the Wiimote event handler _wiimoteEventSource = new WiimoteEventSource( this .globeControl.Host.BindingsManager.ActionSystem, this );   // register it in the event source list _globeControl.Host.BindingsManager.ActionSystem.EventSourceManager.RegisterEventSource( this ._wiimoteEventSource); VB ' wiimote events Private _wiimoteEventSource As WiimoteEventSource   ...   ' create a new instance of the Wiimote event handler _wiimoteEventSource = New WiimoteEventSource( Me .globeControl.Host.BindingsManager.ActionSystem, Me )   ' register it in the event source list _globeControl.Host.BindingsManager.ActionSystem.EventSourceManager.RegisterEventSource( Me ._wiimoteEventSource)

Actions and the BindingManager

Our binding list contains several action types that are not defined by the default VE3D actions.  These actions and their handlers must be registered with the VE3D control.  The actions can be registered as follows in the FirstFrameRendered event handler: C# BindingsSource bs = new BindingsSource( base .GetType()); _globeControl.Host.BindingsManager.RegisterAction(bs, "Locations" , LocationsHandler); _globeControl.Host.BindingsManager.RegisterAction(bs, "LocationsMove" , LocationsMoveHandler); _globeControl.Host.BindingsManager.RegisterAction(bs, "ToggleVR920" , ToggleVR920Handler); _globeControl.Host.BindingsManager.RegisterAction(bs, "VR920SetZero" , VR920SetZero); _globeControl.Host.BindingsManager.RegisterAction(bs, "BalanceBoardSetZero" , BalanceBoardSetZero); _globeControl.Host.BindingsManager.RegisterAction(bs, "ToggleBalanceBoard" , ToggleBalanceBoardHandler); _globeControl.Host.BindingsManager.RegisterAction(bs, "FullScreen" , FullScreenHandler); _globeControl.Host.BindingsManager.RegisterAction(bs, "ToggleVR920Stereo" , ToggleVR920Stereo); _globeControl.Host.BindingsManager.RegisterAction(bs, "VR920SetEyeDistance" , VR920SetEyeDistance); VB BindingsSource bs = new BindingsSource(base. GetType ()); _globeControl.Host.BindingsManager.RegisterAction(bs, "Locations" , LocationsHandler); _globeControl.Host.BindingsManager.RegisterAction(bs, "LocationsMove" , LocationsMoveHandler); _globeControl.Host.BindingsManager.RegisterAction(bs, "ToggleVR920" , ToggleVR920Handler); _globeControl.Host.BindingsManager.RegisterAction(bs, "VR920SetZero" , VR920SetZero); _globeControl.Host.BindingsManager.RegisterAction(bs, "BalanceBoardSetZero" , BalanceBoardSetZero); _globeControl.Host.BindingsManager.RegisterAction(bs, "ToggleBalanceBoard" , ToggleBalanceBoardHandler); _globeControl.Host.BindingsManager.RegisterAction(bs, "FullScreen" , FullScreenHandler); _globeControl.Host.BindingsManager.RegisterAction(bs, "ToggleVR920Stereo" , ToggleVR920Stereo); _globeControl.Host.BindingsManager.RegisterAction(bs, "VR920SetEyeDistance" , VR920SetEyeDistance); With the actions registered and handlers associated with them, the actual handlers need to be implemented.  All event handler methods must be of the following signature: C# public bool EventHandler(EventData cause) VB Public Function EventHandler( ByVal cause As EventData) As Boolean Let’s take a look at the FullScreen binding which simply turns the status bar at the bottom of the window on and off: C# private bool FullScreenHandler(EventData eventData) { if (eventData.Activate) BeginInvoke( new UIEventHandlerDelegate(FullScreen), eventData); return true ; } VB Private Function FullScreenHandler( ByVal eventData As EventData) As Boolean If eventData.Activate Then BeginInvoke( New UIEventHandlerDelegate( AddressOf FullScreen), eventData) End If Return True End Function Private Sub FullScreen( ByVal eventData As EventData) statusStrip1.Visible = Not statusStrip1.Visible End Sub Because we are in the VE3D thread when this handler is called, we need to use BeginInvoke to call the real method on the UI thread. Be sure to check the source code for the full demo linked above for the location handler methods.  I omitted them here since it is just more of the same type of code above.

VR920 Head Tracker

Once the VR920 glasses and driver are installed on your PC, getting the data required from the glasses is quite easy.  We are going to create an object named VR920Tracker which will pull data from the device and send it to VE3D. First we need to setup a few P/Invoke signatures to talk to the glasses as follows: C# [DllImport( "IWEARDRV.dll" , SetLastError = true , CharSet = CharSet. Auto )] private static extern int IWROpenTracker();   [DllImport( "IWEARDRV.dll" , SetLastError = true , CharSet = CharSet. Auto )] private static extern void IWRZeroSet();   [DllImport( "IWEARDRV.dll" , SetLastError = true , CharSet = CharSet. Auto )] private static extern int IWRGetTracking(out int yaw, out int pitch, out int roll);   private const int ERROR_SUCCESS = 0; VB <DllImport( "IWEARDRV.dll" , SetLastError := True , CharSet := CharSet. Auto )> _ Private Shared Function IWROpenTracker() As Integer End Function   <DllImport( "IWEARDRV.dll" , SetLastError := True , CharSet := CharSet. Auto )> _ Private Shared Sub IWRZeroSet() End Sub   <DllImport( "IWEARDRV.dll" , SetLastError := True , CharSet := CharSet. Auto )> _ Private Shared Function IWRGetTracking(<System.Runtime.InteropServices.Out()> ByRef yaw As Integer , <System.Runtime.InteropServices.Out()> ByRef pitch As Integer , <System.Runtime.InteropServices.Out()> ByRef roll As Integer ) As Integer End Function   private const int ERROR_SUCCESS = 0; These 3 methods will allows to open access to the glasses, set its zero position, and get the roll, pitch and yaw information from the sensors. In the constructor for this object, we will open a handle to the glasses and startup a timer that will poll the glasses at a regular interval to get the sensor data as shown below: C# private const int TimerPeriod = 1; private MainForm _mainForm; private Timer _timer;   public VR920Tracker(MainForm mainForm) { _mainForm = mainForm;   int openResult = IWROpenTracker(); if (openResult != ERROR_SUCCESS) throw new ApplicationException( "Could not connect to VR920: " + openResult);   _timer = new Timer(VR920Poller, null, Timeout.Infinite, TimerPeriod); } VB Private Const TimerPeriod As Integer = 1 Private _mainForm As MainForm Private _timer As Timer   Public Sub New ( ByVal mainForm As MainForm) _mainForm = mainForm   Dim openResult As Integer = IWROpenTracker() If openResult <> ERROR_SUCCESS Then Throw New ApplicationException( "Could not connect to VR920: " & openResult) End If   _timer = New Timer( AddressOf VR920Poller, Nothing , Timeout.Infinite, TimerPeriod) End Sub The VR920Poller method referenced above will be called at a specific interval and read the values from the glasses as shown below: C# // collection of values for averaging private List< double > _yawValues = new List< double >(); private List< double > _rollValues = new List< double >(); private List< double > _pitchValues = new List< double >();   // last calculated values private double _lastYaw; private double _lastPitch; private double _lastRoll;   private void VR920Poller( object state) { lock(this) { int yaw, pitch, roll;   int result = IWRGetTracking(out yaw, out pitch, out roll);   if (result != ERROR_SUCCESS) throw new ApplicationException( "Could not get VR920 tracking information: " + result);   _yawValues.Add(VR920ToRadians(yaw)); _rollValues.Add(VR920ToRadians(roll)); _pitchValues.Add(VR920ToRadians(pitch));   if (_yawValues.Count == 5) { double y = Average(_yawValues, _lastYaw); if (Math.Abs(y - _lastYaw) > 0.026) _lastYaw = y;   double p = Average(_pitchValues, _lastPitch); if (Math.Abs(p - _lastPitch) > 0.017) _lastPitch = p;   _lastRoll = Average(_rollValues, _lastRoll);   _yawValues.Clear(); _pitchValues.Clear(); _rollValues.Clear();   RollPitchYaw rollPitchYaw = new RollPitchYaw(_lastRoll, _lastPitch, _lastYaw); _mainForm.SetRollPitchYaw(rollPitchYaw);   _mainForm.lblAxes.Text = VR920ToDegrees(yaw) + ", " + VR920ToDegrees(pitch) + ", " + VR920ToDegrees(roll); } } }   private double Average(List< double > values, double last) { double total = 0;   foreach( double value in values) total += value;   total += last;   return total / (values.Count+1); }   private double VR920ToRadians(int vr920Value) { return (vr920Value * .00549) * (Math.PI/180); }   private double VR920ToDegrees(int vr920Value) { return (vr920Value * .00549); } VB ' collection of values for averaging Private _yawValues As List(Of Double ) = New List(Of Double )() Private _rollValues As List(Of Double ) = New List(Of Double )() Private _pitchValues As List(Of Double ) = New List(Of Double )()   ' last calculated values Private _lastYaw As Double Private _lastPitch As Double Private _lastRoll As Double   Private Sub VR920Poller( ByVal state As Object ) SyncLock Me Dim yaw, pitch, roll As Integer   Dim result As Integer = IWRGetTracking(yaw, pitch, roll)   If result <> ERROR_SUCCESS Then Throw New ApplicationException( "Could not get VR920 tracking information: " & result) End If   _yawValues.Add(VR920ToRadians(yaw)) _rollValues.Add(VR920ToRadians(roll)) _pitchValues.Add(VR920ToRadians(pitch))   If _yawValues.Count = 5 Then Dim y As Double = Average(_yawValues, _lastYaw) If Math.Abs(y - _lastYaw) > 0.026 Then _lastYaw = y End If   Dim p As Double = Average(_pitchValues, _lastPitch) If Math.Abs(p - _lastPitch) > 0.017 Then _lastPitch = p End If   _lastRoll = Average(_rollValues, _lastRoll)   _yawValues.Clear() _pitchValues.Clear() _rollValues.Clear()   Dim rollPitchYaw As New RollPitchYaw(_lastRoll, _lastPitch, _lastYaw) _mainForm.SetRollPitchYaw(rollPitchYaw)   _mainForm.lblAxes.Text = VR920ToDegrees(yaw) & ", " & VR920ToDegrees(pitch) & ", " & VR920ToDegrees(roll) End If End SyncLock End Sub   Private Function Average( ByVal values As List(Of Double ), ByVal last As Double ) As Double Dim total As Double = 0   For Each value As Double In values total += value Next value   total += last   Return total / (values.Count+1) End Function   Private Function VR920ToRadians( ByVal vr920Value As Integer ) As Double Return (vr920Value *.00549) * (Math.PI/180) End Function   Private Function VR920ToDegrees( ByVal vr920Value As Integer ) As Double Return (vr920Value *.00549) End Function This chunk calls the IWRGetTracking method and tosses the results in 3 lists for roll, pitch and yaw.  then, when the list has 5 members in it, the values are averaged and, if that resulting values is past a certain threshold, the values are passed back to the MainForm’s SetRollPitchYaw method as shown below: C# private double _lastYaw;   public void SetRollPitchYaw(RollPitchYaw rollPitchYaw) { if (!_initialized || _globeControl.IsDisposed || (_globeControl.Host) == null || _globeControl.Host.CameraControllers.Current == null ) return ;   double y = ((_globeControl.Host.CameraControllers.Current as ActionCameraController).LastReportedViewpoint.LocalOrientation.Yaw - _lastYaw) + rollPitchYaw.Yaw;   RollPitchYaw rpw = new RollPitchYaw(rollPitchYaw.Roll, rollPitchYaw.Pitch, y); (_globeControl.Host.CameraControllers.Current as ActionCameraController).LastReportedViewpoint.LocalOrientation.RollPitchYaw = rpw;   _lastYaw = rollPitchYaw.Yaw; } VB Private _lastYaw As Double   Public Sub SetRollPitchYaw( ByVal rollPitchYaw As RollPitchYaw) If ( Not _initialized) OrElse _globeControl.IsDisposed OrElse (_globeControl.Host) Is Nothing OrElse _globeControl.Host.CameraControllers.Current Is Nothing Then Return End If   Dim y As Double = (( TryCast (_globeControl.Host.CameraControllers.Current, ActionCameraController)).LastReportedViewpoint.LocalOrientation.Yaw - _lastYaw) + rollPitchYaw.Yaw   Dim rpw As New RollPitchYaw(rollPitchYaw.Roll, rollPitchYaw.Pitch, y) TryCast (_globeControl.Host.CameraControllers.Current, ActionCameraController).LastReportedViewpoint.LocalOrientation.RollPitchYaw = rpw   _lastYaw = rollPitchYaw.Yaw End Sub This method takes the roll, pitch and yaw values provided and applies them to the current CameraController used by VE3D (which, by default, is an ActionCameraController). So, this code ultimately takes the roll, yaw and pitch of the user’s head and translates that directly to the roll, yaw and pitch of the camera in VE3D providing an accurate, real-time view into the VE3D world.

Stereoscopic Images

The final piece of the puzzle is drawing frames to the VR920 glasses in such a way that the final view to the user will be three dimensional.  This works similarly to those Magic Eye puzzles you may have seen.  Essentially, for every frame, we want to take the current VE3D camera position and move it several units to the left, render that to the left eye of the glasses, and then move the camera several units to the right and render that to the right eye of the glasses.  To handle this we,  can create an object named VR920StereoStep which derives from the Step class provided by VE3D and add this to the StepManager.  When this is done, the VR920StereoStep will be called at the end of every frame drawn by VE3D. As with the VR920Tracker class, we will need to setup a few P/Invoke method signatures to talk to the stereo driver as shown below: C# [DllImport( "iWrstDrv.dll" , EntryPoint = "IWRSTEREO_Open" , SetLastError= true )] public static extern IntPtr OpenStereo();   [DllImport( "iWrstDrv.dll" , EntryPoint = "IWRSTEREO_SetStereo" )] public static extern Boolean SetStereoEnabled(IntPtr handle, Boolean enabled);   [DllImport( "iWrstDrv.dll" , EntryPoint = "IWRSTEREO_SetLR" )] public static extern Boolean SetStereoLR(IntPtr handle, Boolean eye);   [DllImport( "iWrstDrv.dll" , EntryPoint = "IWRSTEREO_WaitForAck" )] public static extern Byte WaitForOpenFrame(IntPtr handle, Boolean eye); VB <DllImport( "iWrstDrv.dll" , EntryPoint := "IWRSTEREO_Open" , SetLastError:= True )> _ Public Shared Function OpenStereo() As IntPtr End Function   <DllImport( "iWrstDrv.dll" , EntryPoint := "IWRSTEREO_SetLR" )> _ Public Shared Function SetStereoLR( ByVal handle As IntPtr, ByVal eye As Boolean ) As Boolean End Function   <DllImport( "iWrstDrv.dll" , EntryPoint := "IWRSTEREO_SetStereo" )> _ Public Shared Function SetStereoEnabled( ByVal handle As IntPtr, ByVal enabled As Boolean ) As Boolean End Function   <DllImport( "iWrstDrv.dll" , EntryPoint := "IWRSTEREO_WaitForAck" )> _ Public Shared Function WaitForOpenFrame( ByVal handle As IntPtr, ByVal eye As Boolean ) As Byte End Function In the constructor for this object, we will open a handle to the stereo driver and turn on the stereo functionality of the glasses as shown below: C# private static readonly IntPtr INVALID_FILE_HANDLE = (IntPtr)(-1);   private IntPtr _hStereo = INVALID_FILE_HANDLE; private Host _host; private bool _stereoEnabled = true ;   public double EyeDistance { get ; set ; }   public VR920StereoStep(StepManager manager, Host host) : base(manager) { EyeDistance = 10; _host = host;   _hStereo = OpenStereo(); if (_hStereo != INVALID_FILE_HANDLE) SetStereoEnabled(_hStereo, true ); else _stereoEnabled = false ; } VB Private Shared ReadOnly INVALID_FILE_HANDLE As IntPtr = CType (-1, IntPtr)   Private _hStereo As IntPtr = INVALID_FILE_HANDLE Private _host As Host Private _stereoEnabled As Boolean = True   Private privateEyeDistance As Double Public Property EyeDistance() As Double Get Return privateEyeDistance End Get Set ( ByVal value As Double ) privateEyeDistance = value End Set End Property   Public Sub New ( ByVal manager As StepManager, ByVal host As Host) MyBase . New (manager) EyeDistance = 10 _host = host   _hStereo = OpenStereo() If _hStereo <> INVALID_FILE_HANDLE Then SetStereoEnabled(_hStereo, True ) Else _stereoEnabled = False End If End Sub Next, we need to override the OnExecute method provided by the base Step class as shown below: C# private bool _rightEye = true ; private Vector3D _position;   public override void OnExecute(SceneState state) { if (_hStereo != INVALID_FILE_HANDLE && _stereoEnabled && (_host.CameraControllers.Current as ActionCameraController).LastReportedViewpoint != null ) { if (!_rightEye) { _position = (_host.CameraControllers.Current as ActionCameraController).LastReportedViewpoint.Position.Vector; (_host.CameraControllers.Current as ActionCameraController).LastReportedViewpoint.Position.Vector = _position + new Vector3D(-EyeDistance, 0, 0); } else { _position = (_host.CameraControllers.Current as ActionCameraController).LastReportedViewpoint.Position.Vector; (_host.CameraControllers.Current as ActionCameraController).LastReportedViewpoint.Position.Vector = _position + new Vector3D(EyeDistance, 0, 0);; }   SetStereoLR(_hStereo, _rightEye); WaitForOpenFrame(_hStereo, _rightEye); _rightEye = !_rightEye; } } VB Private _rightEye As Boolean = True Private _position As Vector3D   Public Overrides Sub OnExecute( ByVal state As SceneState) If _hStereo <> INVALID_FILE_HANDLE AndAlso _stereoEnabled AndAlso ( TryCast (_host.CameraControllers.Current, ActionCameraController)).LastReportedViewpoint IsNot Nothing Then If ( Not _rightEye) Then _position = ( TryCast (_host.CameraControllers.Current, ActionCameraController)).LastReportedViewpoint.Position.Vector TryCast (_host.CameraControllers.Current, ActionCameraController).LastReportedViewpoint.Position.Vector = _position + New Vector3D(-EyeDistance, 0, 0) Else _position = ( TryCast (_host.CameraControllers.Current, ActionCameraController)).LastReportedViewpoint.Position.Vector TryCast (_host.CameraControllers.Current, ActionCameraController).LastReportedViewpoint.Position.Vector = _position + New Vector3D(EyeDistance, 0, 0)   End If   SetStereoLR(_hStereo, _rightEye) WaitForOpenFrame(_hStereo, _rightEye) _rightEye = Not _rightEye End If End Sub OnExecute will be called at the end of each VE3D frame rendered.  In this method, we obtain the current position of the VE3D camera and then set its position several units to the left or right (- or + EyeDistance).  SetStereoLR from the VR920 API is called, setting the appropriate eye to be active (left = false, right = true).  Then WaitForOpenFrame is called which will pause the action long enough for the glasses to draw the entire frame to eye to avoid image tearing.  Essentially, this call “sits and spins” at this location until the glasses report back that the entire frame is drawn in the proper eye and that we can continue on drawing the opposite eye for the next frame.  Finally, the _rightEye member variable is togged to the opposite value, so the next time through this method the opposite eye is drawn. With this class in place, back in our MainForm in the FirstFrameRendered event handler, we can instantiate the object and add it to the StepManager as shown below: C# private VR920StereoStep _vr920StereoStep;   _vr920StereoStep = new VR920StereoStep(_globeControl.Host.RenderEngine.StepManager, _globeControl.Host); _globeControl.Host.RenderEngine.StepManager.Add(_vr920StereoStep); VB Private _vr920StereoStep As VR920StereoStep   _vr920StereoStep = New VR920StereoStep(_globeControl.Host.RenderEngine.StepManager, _globeControl.Host) _globeControl.Host.RenderEngine.StepManager.Add(_vr920StereoStep)  

Running the Application

To run the demo, do the following:
  1. Copy BindingsWiimote.xml to the appropriate directory listed above
  2. Pair the Wiimote and Balance Board to the computer.  See the WiimoteLib article for more information on how to do that
  3. Run the executable
  4. Stand on the Balance Board
  5. Put on the glasses
  6. Zero both the Balance Board and glasses
  7. Toggle everything on or off at will
Controls
  • F1 – Set VR920 glasses to zero position
  • F2 – Set Balance Board to zero position
  • F3 – Toggle stereo mode on and off
  • Wiimote 1 or B – Toggle Balance Board on or off
  • Wiimote 2 or V – Toggle VR920 head tracking on or off
  • F – Toggle full-screen on or off
  • Nunchuk joystick X/Y – Strafe/Move
  • Nunchuk C/Z buttons – Raise/lower altitude
  • Wiimote A – Open location menu/Select location (note that if a new location is selected, the Balance Board is turned off and must be re-enabled after “landing”
  • Wiimote Dpad Up/Down – Move through List
  • Balance Board – Lean your body left or right to turn in the VE3D environment

Conclusion

With the above code, we have created a very interesting interface for Virtual Earth 3D.  The demo and source code linked above contain a few more features and bindings which enhance the application a bit more.  Be sure to give the full demo a try and check out the full source code for a few more implementation details.

Additional Information

Bio

Brian is a Microsoft C# MVP who has been actively developing in .NET since its early betas in 2000, and who has been developing solutions using Microsoft technologies and platforms for even longer. Along with .NET, Brian is particularly skilled in the languages of C, C++ and assembly language for a variety of CPUs. He is also well-versed in a wide variety of technologies including web development, document imaging, GIS, graphics, game development, and hardware interfacing. Brian has a strong background in developing applications for the health-care industry, as well as developing solutions for portable devices, such as tablet PCs and PDAs. Additionally, Brian has co-authored the book "Debugging ASP.NET" published by New Riders, and is currently co-authoring a book titled "Coding4Fun: 10 .NET Programming Projects for Wiimote, YouTube, World of Warcraft, and More" to be published by O'Reilly in December 2008. Brian is also an author for MSDN's Coding4Fun website.  You can reach Brian via vis website at http://www.brianpeek.com/ .

Video ambient lighting effects
Fun3 MD created a DIY version with an Arduino of the AtmoLight, which is a clone of the Philip Amblight system.  Awesome job of leveraging a pre-existing technology instead of re-inventing the wheel.

Want Windows 7 Sensors that are Arduino powered?
TCL310_On_AtMega8[1]Am I the only one warming up their soldering iron?   I posted a bit ago on being able to do a Windows 7 sensor with a parallax board, but Brian Jepson (@bjepson) of MAKE and O’Reilly helped port that driver into a more generic driver that works with Arduino! Right now there is one Arduino sketch in there from @skobalczyk; but they plan to add more soon. Head over to http://win7sensorserial.codeplex.com to get the driver and how to do more!

Rss Feed Reader
Increase Online Traffic
Coding4Fun : hardware

MindBlaster
In this article, Brian Peek describes his PDC 2009 project, an XNA game named MindBlaster which uses a Nintendo Wii Remote in conjunction with MindSet brain-wave sensing headset from Neurosky.

 

 

Discount Computer and Hardware Interface Books (US FREE Shipping)

PC Hardware Interfaces: A Developer's Reference save 18%

USB Complete: Everything You Need to Develop Custom USB Peripherals save 37%

Serial Port Complete: COM Ports, USB Virtual COM Ports, and Ports for Embedded Systems (Complete Guides series)

See all Computer interface Books




 

Computer Hardware Interface I/O Devices RS232 RS485 RS422 USB Serial Parallel Interface protocol Com Port Test Comport Checking hardware interfacing