- 积分
- 24566
- 明经币
- 个
- 注册时间
- 2004-3-17
- 在线时间
- 小时
- 威望
-
- 金钱
- 个
- 贡献
-
- 激情
-
|
楼主 |
发表于 2009-5-16 11:30:00
|
显示全部楼层
七、在3D视图中变换视角
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:
And here's a snapshot I managed to take during the command, as the view was transitioning:
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.
|
|