SilverlightVR  360

Back To: Silverlight Tutorials

Using the Jeff Paries Animation Engine

This tutorial will guide you through the creation of a Silverlight application that will allow a user to rotate an object that you create, a full 360 degrees along both axis. This application is based on the original SilverlightVR animation engine that was created by Jeff Paries (DesignWithSilverlight.com). Jeff assisted with the creation of this application.

What you will need:

Create the graphics

The application is composed of a series of .jpeg images. Each image represents the object at a different 10 degree angle. 36 pictures represent a full 360 degrees. 36 sets of pictures represent 360 degrees along both axis.

To create the images open Ulead Cool 3D and place an object on the design surface.

Click the Rotate button.

Then click the Add fixed transformation button (to reset the axis levels to zero).

In the Timeline, set 36 frames and 15 fps (frames per second).

Now click on the timeline bar and move it to frame 18.

Change the Y axis to 180 and click away.

The object will rotate 180 degrees

Move the timeline to 36 and enter 360 in the Y axis. The animation is now complete. Click the Play button (>) on the Navigation toolbar to preview the animation.

From the File menu on the toolbar, select Create Animation File, then Export to Macromedia Flash (SWF) then with JPEG.

Give the file the name 0_0.swf.

Edit: This example shows the starting animation which is in the "middle" of the series of pictures. This should be named 18_0.swf.

Convert the animation

Open the .swf file you just created in SWF2XAML

Drag the scroll bar at the bottom all the way to the right. this will cause the program to create a folder called images and create 36 .jpegs.

Rename the files

In the File Renamer program, click the ... button to locate the images.

Select the option to Trim the first 7 characters of the files and then click the Rename Files button.

Now, select the numerate Files (## + extension) option and enter 00 in the Base digit to box. Click Rename Files.

Next, select Add... at 0 chars from name begin option and enter 00_ in the Rename to box and then click the Rename Files button.

The files will be renamed.

Place the files in a .zip file and call the .zip file Pictures.zip. Pictures.zip is used by the Silverlight application to load the pictures.

Repeat the process, this time move the X axis 10 degrees (you have to set it for all three points on the timeline in Cool 3D). Add all the pictures to the same Pictures.zip file.

The first 2 digits of the file names should be numbered according to the X axis (for example "01_02.jpeg").

Download the code and deploy the application. Replace the Pictures.zip in the downloaded code with the Pictures.zip you just created and you will then be able to rotate your image.

How the Application works

If you look at the XAML file in Expression Blend you will see the following:

 

The bar at the top is used to show the progress of the download of the Pictures.zip file. See this post for an explanation of the code.

The big square in the middle is a image control that has an X:Name of DisplayPicture. It has "mouse events" attached to it to fire JavaScript methods when it is clicked on.

<Image x:Name="DisplayPicture" Width="288" Height="281" Canvas.Left="211" Canvas.Top="78" Stretch="Uniform" Cursor="Hand" MouseLeftButtonDown="clickSelectedMouseDown" MouseLeftButtonUp="clickSelectedMouseUp" MouseMove="clickSelectedMouseMove"/>
    
The thin bars on the left side and the bottom are scroll bars that contain a "sliderThumb" inside. The following is the XAML code for the control on the bottom:

    <Canvas x:Name="sliderCanvasX" Width="240" Height="21" Canvas.Left="233" Canvas.Top="381">
        <Canvas x:Name="sliderX" 
            Width="240" Height="21"
            Background="transparent">
            <Canvas x:Name="sliderThumbX" Width="20" Height="10" Canvas.Top="4.75"/>            
        </Canvas>
    </Canvas>    

See http://DesignWithSilverlight.com for more information on how sliders work.

Page.xaml.js

All the functionality for the application is contained in the Page.xaml.js file:

   1:  //
   2:  // Original Silverlight VR Viewer Copyright (C) 2007 Jeff Paries
   3:  //
   4:  // DesignWithSilverlight.com
   5:  //
   6:  //
   7:  // 360 degree enhancements created by Michael Washington
   8:  // ADefWebserver.com
   9:  //
  10:   
  11:  if (!window.PictureDownloader)
  12:      window.PictureDownloader = {};
  13:   
  14:  PictureDownloader.Page = function() 
  15:  {
  16:  }
  17:   
  18:  var glbPictures;
  19:  var glbsender;
  20:  var glbPictureIDX = 0;
  21:  var glbPictureIDY = 0;
  22:   
  23:  var mouseDownPosition = 0;
  24:  var mouseDownValue = -1;
  25:  var thumbCenterX;
  26:  var thumbCenterY;
  27:  var thumbWidth;    
  28:  var totalFrames = 36;
  29:  var startFrame = 117;
  30:  var lastFrame = "image0";
  31:  var nextFrameCompleted = -1;
  32:  var scalingFactor;
  33:   
  34:  PictureDownloader.Page.prototype =
  35:  {
  36:      handleLoad: function(control, userContext, rootElement) 
  37:      {
  38:          glbsender = rootElement;        
  39:                  
  40:             // calculate global variables
  41:             thumbWidth = rootElement.findName("sliderThumbX").width;
  42:             thumbCenterX = thumbWidth / 2;
  43:             
  44:             thumbWidth = rootElement.findName("sliderThumbY").width;
  45:             thumbCenterY = thumbWidth / 2;
  46:   
  47:          // set the initial position for the slider
  48:          var thumbX = rootElement.findName("sliderThumbX");
  49:          thumbX["Canvas.Left"] = startFrame;
  50:          newValueX = startFrame;
  51:          
  52:          var thumbY = rootElement.findName("sliderThumbY");
  53:          thumbY["Canvas.Top"] = startFrame;
  54:          newValueY = startFrame;
  55:   
  56:          var plugin = rootElement.getHost();
  57:          var downloader = plugin.createObject("downloader");
  58:   
  59:          downloader.addEventListener("downloadProgressChanged", onDownloadProgressChanged);
  60:          downloader.addEventListener("completed", onCompleted);
  61:   
  62:          downloader.open("GET", "Pictures.zip");
  63:          downloader.send();
  64:   
  65:      }
  66:   
  67:  }
  68:   
  69:  function doScale(sender, newValueX, newValueY) 
  70:  {
  71:      glbPictureIDX = parseInt(newValueX / scalingFactor);    
  72:      
  73:      if (glbPictureIDX > totalFrames) 
  74:      {
  75:        glbPictureIDX = totalFrames;
  76:      }
  77:      
  78:      glbPictureIDY = parseInt(newValueY / scalingFactor);    
  79:      
  80:      if (glbPictureIDY > totalFrames) 
  81:      {
  82:        glbPictureIDY = totalFrames;
  83:      }
  84:   
  85:      SHOWPICTURE();
  86:  }
  87:   
  88:  // MOUSE MOVEMENT
  89:   
  90:  function clickSelectedMouseDown(sender, mouseEventArgs) 
  91:  {    
  92:      // mouse was clicked somewhere on the slider - move
  93:      // the slider thumb appropriately.
  94:      var coordinate = mouseEventArgs.getPosition(null);
  95:      var coordinateX = coordinate.x;
  96:      var coordinateY = coordinate.y;
  97:      
  98:      var sliderX = sender.findName("sliderCanvasX");
  99:      coordinateX -= sliderX["Canvas.Left"];
 100:      var sliderY = sender.findName("sliderCanvasY");
 101:      coordinateY -= sliderY["Canvas.Top"];
 102:      
 103:      mouseDownValue = 1;
 104:      
 105:      slider_SetValue(sliderX, coordinateX - thumbCenterX, coordinateY - thumbCenterY);
 106:  }
 107:   
 108:   
 109:  function clickSelectedMouseMove(sender, mouseEventArgs) 
 110:  {
 111:         if (mouseDownValue != -1) 
 112:      {
 113:        sender.captureMouse();
 114:            var coordinate = mouseEventArgs.getPosition(null);
 115:          var coordinateX = coordinate.x;
 116:          var coordinateY = coordinate.y; 
 117:          
 118:          var sliderX = sender.findName("sliderCanvasX");
 119:          coordinateX -= sliderX["Canvas.Left"];    
 120:          var sliderY = sender.findName("sliderCanvasY");
 121:          coordinateY -= sliderY["Canvas.Top"];    
 122:          
 123:          slider_SetValue(sliderX, coordinateX - thumbCenterX, coordinateY - thumbCenterY);
 124:      }
 125:  }
 126:   
 127:  function clickSelectedMouseUp(sender) 
 128:  {
 129:      sender.releaseMouseCapture();
 130:      mouseDownValue = -1;
 131:  }
 132:   
 133:   
 134:  // SLIDER
 135:   
 136:  function slider_SetValue(sender, newValueX, newValueY) 
 137:  {    
 138:      //Check if slider is at the maximum to the right
 139:      if (newValueX > sender.findName("sliderX").width) 
 140:      {
 141:        newValueX = sender.findName("sliderX").width;
 142:      }
 143:      
 144:      //Check is slider is at the maximum to the left
 145:      if (newValueX <= 0) 
 146:      {
 147:        newValueX = 0;
 148:      }
 149:      
 150:      //Check if slider is at the maximum to the bottom
 151:      if (newValueY > sender.findName("sliderY").height) 
 152:      {
 153:        newValueY = sender.findName("sliderY").height;
 154:      }
 155:      
 156:      //Check is slider is at the maximum to the top
 157:      if (newValueY <= 0) 
 158:      {
 159:        newValueY = 0;
 160:      }
 161:          
 162:      // calculate scaling values
 163:      doScale(sender, newValueX, newValueY);
 164:  }
 165:   
 166:   
 167:   
 168:   
 169:  //SHOW PICTURE
 170:   
 171:  function SHOWPICTURE()
 172:  {
 173:   
 174:      if(glbPictureIDX == totalFrames)
 175:      {
 176:        glbPictureIDX = (totalFrames - 1);
 177:      } 
 178:      
 179:      if(glbPictureIDY == totalFrames)
 180:      {
 181:        glbPictureIDY = (totalFrames - 1);
 182:      } 
 183:   
 184:      if(glbPictures != null)
 185:      {
 186:        var DisplayPicture = glbsender.findName("DisplayPicture");
 187:        var strPicture = PadDigits(glbPictureIDY,2) + "_" + PadDigits(glbPictureIDX,2) + ".jpg"; 
 188:            
 189:            //Note: 
 190:            //We should find a way to simply check glbPictures
 191:            //and see if the image is in the .zip file  
 192:          try
 193:          {
 194:              DisplayPicture.setSource(glbPictures, strPicture); 
 195:          }
 196:          catch(errorObj)
 197:          {
 198:              //alert(errorObj.message);
 199:              DisplayPicture.Source = "NoImageFound.jpg"; 
 200:          }
 201:        
 202:      }
 203:   
 204:   
 205:  }
 206:   
 207:   
 208:  //DOWNLOADER
 209:   
 210:  function onDownloadProgressChanged(sender, eventArgs)
 211:  {
 212:      var plugin = sender.getHost();
 213:   
 214:      var percentage = Math.floor(sender.downloadProgress * 100);
 215:   
 216:      var progressText = plugin.content.findName("progressText");
 217:      var progressBar = plugin.content.findName("progressBar");
 218:   
 219:      progressText.text = percentage + "%";
 220:      progressBar.width = percentage * 4; 
 221:  }
 222:   
 223:   
 224:  function onCompleted(sender, eventArgs)
 225:  {
 226:      var plugin = sender.getHost();
 227:      var myProgressBar = plugin.content.findName("myProgressBar");
 228:      myProgressBar.Opacity = 0;
 229:   
 230:      glbPictures = sender;
 231:   
 232:      scalingFactor = sender.findName("sliderX").width / totalFrames;    
 233:      doScale(sender, newValueX, newValueY);
 234:  }
 235:   
 236:   
 237:  //Utility
 238:   
 239:      function PadDigits(n, totalDigits) 
 240:      { 
 241:          n = n.toString(); 
 242:          var pd = ''; 
 243:          if (totalDigits > n.length) 
 244:          { 
 245:              for (i=0; i < (totalDigits-n.length); i++) 
 246:              { 
 247:                  pd += '0'; 
 248:              } 
 249:          } 
 250:          return pd + n.toString(); 
 251:      } 
 252:   

The application entry point is at line 36. Global variables are initialized and starting with line 56, the downloader object is created and the pictures are downloaded.

Line 233 causes the application to create the first frame. When the mouse is clicked and dragged the methods under the "//MOUSE MOVEMENT" (line 88) are fired. They call the slider_SetValue method (line 136) that determines if either slider has been moved to the maximum level in either direction.

The slider_SetValue method calls the doScale method (line 69) that associates the slider position with a frame. This method then calls the SHOWPICTURE method (line 171) that retrieves the proper picture from the Pictures.zip file and displays it in the DisplayPicture control.

Back To: Silverlight Tutorials


http://DesignWithSilverlight.com

http://ADefwebserver.com

[About the author]