Kean Walmsley


  • About the Author
    Kean on Google+

July 2014

Sun Mon Tue Wed Thu Fri Sat
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    








« Creating your own custom RSS feeds with Yahoo! Pipes | Main | Reminder: F# programming contest deadline looming... »

February 25, 2009

Smoothly transitioning between 3D AutoCAD views using .NET - Part 1

This inspiration for this post came during the research for this previous post, where we looked at implementing LookAt inside AutoCAD. It also has roots in the need to perform smooth transitions when zooming inside AutoCAD, which the ZOOM command manages for transitions between 2D views. Fenton Webb, from our DevTech Americas team, kindly volunteered to put together an ObjectARX sample that formed the basis for the code in this post. A huge thanks to Fents for his hard work on this!

This code presents a technique that allows smooth transitioning between 3D views in AutoCAD: you set up the parameters of the view to which you wish to change, and the function takes care of the heavy lifting. The technique follows more-or-less the same approach than that used to implement the ViewCube's smooth view transitions. It creates a GraphicsSystem.View object and manipulates it to transition smoothly to the new view.

Here's the C# code:

using System;

using System.Threading;

using System.Drawing;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.GraphicsInterface;

using Autodesk.AutoCAD.GraphicsSystem;

using Autodesk.AutoCAD.Interop;

 

namespace ViewTransitions

{

  public class MyView

  {

    public Point3d position;

    public Point3d target;

    public Vector3d upVector;

    public double fieldWidth;

    public double fieldHeight;

 

    // Default constructor

 

    public MyView(){}

 

    // For constant defines below SWIso etc

 

    public MyView(

      double x1, double y1, double z1,

      double x2, double y2, double z2,

      double x3, double y3, double z3,

      double x4, double y4

    )

    {

      position = new Point3d(x1, y1, z1);

      target = new Point3d(x2, y2, z2);

      upVector = new Vector3d(x3, y3, z3);

      fieldWidth = x4;

      fieldHeight= y4;

    }

 

    public MyView(

      Point3d position, Point3d target, Vector3d upVector,

      double fieldWidth, double fieldHeight

    )

    {

      this.position = position;

      this.target = target;

      this.upVector = upVector;

      this.fieldWidth = fieldWidth;

      this.fieldHeight = fieldHeight;

    }

  };

 

  public class Commands

  {

    static MyView defaultView =

      new MyView(

        1930.1,1339.3,4399.3, 1930.1,1339.3,0.0,

        0.0,1.0,0.0, 3279.8, 1702.6

      );

    static MyView topView =

      new MyView(

        1778.1,1108.2,635.7, 1778.1,1108.2,0.0,

        0.0,1.0,0.0, 474.0, 246.0

      );

    static MyView bottomView =

      new MyView(

        1778.1,1108.2,-635.7, 1778.1,1108.2,0.0,

        0.0,1.0,0.0, 474.0, 246.0

      );

    static MyView leftView =

      new MyView(

        -344.1,1108.2,66.1, 0.0,1108.2,66.1,

        0.0,0.0,1.0, 256.5, 133.2

      );

    static MyView rightView =

      new MyView(

        344.1,1108.2,66.1, 0.0,1108.2,66.1,

        0.0,0.0,1.0, 256.5, 133.2

      );

    static MyView SWIso =

      new MyView(

        265.1,-404.7,1579.0, 838.0,168.2,1006.2,

        0.4,0.4,0.8, 739.7, 384.0

      );

    static MyView SEIso =

      new MyView(

        2105.6,780.7,393.7, 1532.7,1353.5,-179.2,

        -0.4,0.4,0.8, 739.7, 384.0

      );

    static MyView NEIso =

      new MyView(

        1366.8,697.0,-345.2, 793.9,124.1,-918.0,

        -0.4,-0.4,0.8, 739.7, 384.0

      );

    static MyView NWIso =

      new MyView(

        1003.9, 1882.3, 840.2, 1576.8, 1309.5,

        267.3, 0.4, -0.4, 0.8, 739.7, 384.0

      );

 

    // Enacts a smooth transition from the current view

    // to a new view

 

    static Matrix3d SmoothViewTo(

      MyView nv, double timeToTake

    )

    {

      Matrix3d newViewMatrix = Matrix3d.Identity;

 

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db =

        doc.Database;

      Manager gsm =

        doc.GraphicsManager;

 

      // Get the current viewport

 

      int vpn =

        Convert.ToInt32(

          Application.GetSystemVariable("CVPORT")

        );

 

      View view = gsm.GetGsView(vpn, true);

      using (view)

      {

        // Set the frame rate to the standard eye FPS

 

        view.BeginInteractivity(24);

 

        Matrix3d viewMatrix = view.ViewingMatrix;

 

        // Get the current view settings

 

        MyView cv =

          new MyView(

            view.Position, view.Target, view.UpVector,

            view.FieldWidth, view.FieldHeight

          );

 

        // Set up the start positions

 

        Point3d intPos = cv.position;

        Point3d intTgt = cv.target;

        Vector3d intUpVec = cv.upVector;

        double intWid = cv.fieldWidth;

        double intHgt = cv.fieldHeight;

 

        // Now animate the view change between the

        // currentview and the viewToChangeTo

 

        for (float mu = 0; mu <= 1; mu += 0.01F)

        {

          // Interpolate position

 

          intPos =

            new Point3d(

              CosInterp(cv.position.X, nv.position.X, mu),

              CosInterp(cv.position.Y, nv.position.Y, mu),

              CosInterp(cv.position.Z, nv.position.Z, mu)

            );

 

          // Interpolate target

 

          intTgt =

            new Point3d(

              CosInterp(cv.target.X, nv.target.X, mu),

              CosInterp(cv.target.Y, nv.target.Y, mu),

              CosInterp(cv.target.Z, nv.target.Z, mu)

            );

 

          // Interpolate upVector

 

          intUpVec =

            new Vector3d(

              CosInterp(cv.upVector.X, nv.upVector.X, mu),

              CosInterp(cv.upVector.Y, nv.upVector.Y, mu),

              CosInterp(cv.upVector.Z, nv.upVector.Z, mu)

            );

 

          // Interpolate Width

 

          intWid =

            CosInterp(cv.fieldWidth, nv.fieldWidth, mu);

 

          // Interpolate Height

 

          intHgt =

            CosInterp(cv.fieldHeight, nv.fieldHeight, mu);

 

          // Now set the interpolated view

 

          view.SetView(intPos, intTgt, intUpVec, intWid, intHgt);

 

          // Update the control

 

          view.Update();

 

          // Decrease the sleep time, or rather increase the

          // speed of the view change as we work

 

          double sleepTime = timeToTake - (mu * 10);

          Thread.Sleep((int)(sleepTime > 50 ? 0 : sleepTime));

        }

 

        view.EndInteractivity();

 

        // Finally set the new view 

 

        gsm.SetViewportFromView(vpn, view, true, true, false);

 

        System.Windows.Forms.Application.DoEvents();

      }

 

      return newViewMatrix;

    }

 

    // Cosine interpolation

 

    static double CosInterp(double y1, double y2, double mu)

    {

      double mu2;

 

      mu2 = (1-Math.Cos(mu*Math.PI))/2;

      return(y1*(1-mu2)+y2*mu2);

    }

 

    // Function to create a solid background of the same

    // colour as the background of our 2D modelspace view

    // (reduces the visual shock as the colour would

    // otherwise switch to grey and back)

 

    private static ObjectId CreateBackground()

    {

      const string bgKey = "TTIF_BG";

 

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db =

        doc.Database;

 

      ObjectId vtId = ObjectId.Null;

 

      // Get the current viewport number

 

      int vpn =

        Convert.ToInt32(

          Application.GetSystemVariable("CVPORT")

        );

 

      // No need to set the background if a corresponding

      // 3D view already exists

 

      View view =

        doc.GraphicsManager.GetGsView(vpn, false);

      if (view == null)

      {

        Transaction tr =

          db.TransactionManager.StartTransaction();

        using (tr)

        {

          ObjectId bgId = ObjectId.Null;

 

          // Get or create our background dictionary

 

          ObjectId bgdId =

            Background.GetBackgroundDictionaryId(db, true);

 

          DBDictionary bgd =

            (DBDictionary)tr.GetObject(

              bgdId,

              OpenMode.ForRead

            );

 

          if (bgd.Contains(bgKey))

          {

            bgId = bgd.GetAt(bgKey);

          }

          else

          {

            // If our background doesn't exist...

 

            // Get the 2D modelspace background colour

 

            AcadPreferences prefs =

              (AcadPreferences)Application.Preferences;

            int rawCol =

              (int)prefs.Display.GraphicsWinModelBackgrndColor;

 

            // Create a background with the corresponding RGB

 

            SolidBackground sb = new SolidBackground();

            sb.Color =

              new Autodesk.AutoCAD.Colors.EntityColor(

                (byte)(rawCol & 0x000000FF),

                (byte)((rawCol & 0x0000FF00) >> 8),

                (byte)((rawCol & 0x00FF0000) >> 16)

              );

 

            // Add it to the background dictionary

 

            bgd.UpgradeOpen();

            bgId = bgd.SetAt(bgKey, sb);

            tr.AddNewlyCreatedDBObject(sb, true);

          }

 

          // Set the background on the active modelspace viewport

 

          ViewportTable vt =

            (ViewportTable)tr.GetObject(

              db.ViewportTableId,

              OpenMode.ForRead

            );

          foreach (ObjectId id in vt)

          {

            ViewportTableRecord vtr =

              (ViewportTableRecord)tr.GetObject(

                id,

                OpenMode.ForRead

              );

            if (vtr.Name == "*Active")

            {

              vtId = id;

              vtr.UpgradeOpen();

              vtr.Background = bgId;

            }

          }

          tr.Commit();

        }

      }

      else

        view.Dispose();

 

      return vtId;

    }

 

    private static void RemoveBackground(ObjectId vtId)

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db =

        doc.Database;

 

      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        // Open up the previously-modified viewport

 

        ViewportTableRecord vtr =

          (ViewportTableRecord)tr.GetObject(

            vtId,

            OpenMode.ForWrite

          );

 

        // And set its previous background

 

        ObjectId obgId =

          vtr.GetPreviousBackground(

            DrawableType.SolidBackground

          );

        vtr.Background = obgId;

 

        tr.Commit();

      }

    }

 

    [CommandMethod("TV")]

    static public void TransitionView()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db =

        doc.Database;

 

      ObjectId vtId = CreateBackground();

 

      SmoothViewTo(defaultView, 10);

      SmoothViewTo(SWIso, 10);

      SmoothViewTo(topView, 10);

      SmoothViewTo(SEIso, 10);

      SmoothViewTo(bottomView, 10);

      SmoothViewTo(NEIso, 10);

      SmoothViewTo(leftView, 10);

      SmoothViewTo(NWIso, 10);

      SmoothViewTo(rightView, 10);

      SmoothViewTo(defaultView, 10);

 

      if (vtId != ObjectId.Null)

        RemoveBackground(vtId);

    }

  }

}

A few comments on the code...

Fenton interpolates between views using his own "secret sauce", the CosInterp() function. This does some clever stuff to interpolate between the values provided. It's used to interpolate between the beginning and end states of the individual members of the co-ordinates of the various points and vectors - and the field width and height - that define a view.

I added some functionality to create a temporary background image attached to the active ViewportTableRecord with the same colour as the drawing canvas background (if in a standard 2D view). This allows the 3D view that gets created to have the same background colour, avoiding the shock of it flashing to grey and back. I admit that this code (in CreateBackground() and RemoveBackground()) doesn't feel especially elegant - I tried various different approaches such as modifying the view and the device attached to the view, but none of them worked in the way I wanted. So this is what I ended up with. I'd be very happy to hear from people who have found a better way to address this issue...

To see how the function works, draw some 3D geometry, load the application and run the TV command.

Here's a sample view prior to running the command:

2D view before transitions

And here's a snapshot I managed to take during the command, as the view was transitioning:

3D view in mid transition

It's hard to do it justice with a static image, so the best is to give it a try.

I can see a few changes that people might want to make to the code:

  • the view definitions (to change their parameters or even to generate them dynamically).
  • the formula in the CosInterp() function.
  • the code towards the end of the SmoothViewTo() which pauses for a variable amount of time, depending on how close the view is from being transitioned.

When Fenton shared his original code within the DevTech team, Jeremy Tammik mentioned another interpolation algorithm based on quaternion mathematics, spherical linear interpolation (Slerp). In the next post in this series we'll take a look at a version implementing Slerp to do something very similar to the code in this post.

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/services/trackback/6a00d83452464869e201116897035f970c

Listed below are links to weblogs that reference Smoothly transitioning between 3D AutoCAD views using .NET - Part 1:

blog comments powered by Disqus

10 Random Posts