Dragging in 3D – The Right Way



world.png

Several months ago I did a blog post titled Dragging in 3D - The Easy Way. It was an no-math solution to being able to drag an object in 3D in multiple directions using the tools already built into Papervision. By creating an invisible Plane object, you could detect where it was hit by the mouse in 3D space - then move your object to that position.

While that was cool and all - it definitely was just a hack and heavy approach to dragging objects in 3D. This is much more elegant.

I introduced some new functions that make dragging in 3D possible. They have alot greater use than JUST for dragging, so make good use of them! The main function, which is essential to being able drag in 3D - is CameraObject3D.unproject. #unproject does just what the name says: it unprojects coordinates. It takes 2D screen coordinates, and gives you back a vector in world space. In lay terms - this basically shoots a ray from the camera, through where the mouse is, into 3D space. You can then do whatever you want knowing where that ray is. I use it in this demo to find where the ray hits a plane i want to be dragging on. When i find the intersection of the unprojected ray with the plane - i move my object there. Its really that easy. And all of it is built into Papervision3D for you.

So lets look at some code. First thing to note is the unproject function. It only takes 2 parameters which are screenX and screenY. Just pass in the mouse coordinates (relative to viewport.containerSprite) and you will get a Number3D that represents your ray back.

Actionscript:
  1. //get the ray shooting from the camera through the mouse position
  2. var ray:Number3D = v.camera.unproject(v.viewport.containerSprite.mouseX, v.viewport.containerSprite.mouseY);

Now that we know the direction the ray is shooting - we can check for an intersection with our plane. To do this, we have to do a few things. First, we must convert our ray into a point in 3D space. This is done by simply adding it to our camera. We then need a Plane3D object. Plane3D is a math utility class in Papervision that lets you perform various operations on "planes" in 3D space. One of the operations is finding where a line intersects the plane. Once we have that intersection, we can move our object to that position. Check it out:

Actionscript:
  1. //make our camera position accessible
  2. var cameraPosition:Number3D = new Number3D(v.camera.x, v.camera.y, v.camera.z);
  3.  
  4. //get the world position of our ray
  5. ray = Number3D.add(ray, cameraPosition);
  6.  
  7. //create a plane3D object
  8. var p:Plane3D = new Plane3D();
  9.  
  10. //define the plane by a normal and a point.  In this case, we want the plane to be going through (0, 0, 0) - with its normal going (0, 1, 0).  This will make the plane an XZ plane, facing upwards.
  11. p.setNormalAndPoint(new Number3D(0, 1, 0), new Number3D(0, 0, 0));
  12.  
  13. //find where the plane intersects.  this function takes 2 points to define a line - we will define it with our ray and camera.
  14. var intersect:Number3D = p.getIntersectionLineNumbers(cameraPosition, ray);
  15.  
  16. //set object position based on results
  17. object.x = intersect.x;
  18. object.y = intersect.y;
  19. object.z = intersect.z;

And thats it! All you need to do is project your ray - add it to your camera to get a real point and not just a direction, then find the intersection with your plane! If you don't understand Plane3D.setNormalAndPoint(), imagine it like this: a normal is basically the axis that is perpendicular to your object (plane). Imagine it as a spoke sticking out of the middle of it. If it is 0, 1, 0 - the spoke goes 0 in the x direction, 1 in the y direction, and 0 in the z direction. That means the spoke sticks straight up, so the plane must be horizontal. If the normal was 1, 0, 0 - the spoke would be going down the x-axis - and the plane would be vertical facing down the x-axis as well.

The demo you see here is a little more complex than the code you see above - instead of just setting the object to the intersection, it adds the difference between them to a velocity, which is then added to the object. This gives it that nice flow. Click and drag the sphere to roll it. Hold CONTROL/CMD to switch to a YZ plane (drag the sphere up). I might do a separate blog post on getting the sphere to roll properly - if anyone is interested in that...

Check out the demo here.

and

Get the source


73 Comments, Comment or Ping

  1. Andy, you never cease to amaze me. Amazingly simple and infinitely useful. Kudos to you!

    June 24th, 2008

  2. Brilliant!

    June 24th, 2008

  3. Dynamic! I can’t wait to figure this stuff out :)

    I echo the possibilities

    Trackback to Mr. Lindquist at pv3d.org – Taught me so much today!

    June 25th, 2008

  4. Codejockey

    Hey, yet another great piece of work! I’d love to see how you got the sphere to roll properly!

    June 25th, 2008

  5. this is amazing andy! amazing job! and so simple! thanx!

    June 25th, 2008

  6. Makes me want to build Katamari Damacy (http://en.wikipedia.org/wiki/Katamari_Damacy) in Flash!

    June 25th, 2008

  7. That is a big step for 3D-interactivity! And great demo too. Thanks!

    June 25th, 2008

  8. Sueki

    which branch should I use?
    I got error message when I used GreatWhite version 2.0
    thanks.

    June 25th, 2008

  9. greeting. could you post source code about unproject() function, runing error undefined

    June 26th, 2008

  10. make sure you have the latest revision of pv3d. it is using the Great White branch.

    June 26th, 2008

  11. Papervision3D Public Alpha 2.0 – Great White (24.03.08)

    TypeError: Error #1007: Instantiation attempted on a non-constructor.
    at Dragging3D()

    private function onKeyDown(e:KeyboardEvent):void…………..
    private function onKeyUp(e:KeyboardEvent):void…………..

    can you help descript how to solven

    SDK:trunk\branches\GreatWhite\src

    June 26th, 2008

  12. I found out all comment in your site. then i know . you use flex develop,really thanks. well done

    June 27th, 2008

  13. krishnan

    Really super 3d effect

    June 27th, 2008

  14. Guys, where I can downloading classes, missing in org.papervision3d: .core.layers.RenderLayer?
    Thanks for patience,

    June 27th, 2008

  15. william t

    I have tried a few different versions of papervision and effects. I cannot compile this thing. It complains about getIntersectionLineNumbers which does not exist in any version of PaperVision3d that I can find.

    June 29th, 2008

  16. william t

    Sorry all. I have figured out the path that is necessary. It is in the GreatWhite http://papervision3d.googlecode.com/svn/trunk/branches/GreatWhite . This resolves compile but I do get

    Papervision3D Public Alpha 2.0 – Great White (24.03.08)

    TypeError: Error #1007: Instantiation attempted on a non-constructor.
    at Dragging3D$iinit()

    June 29th, 2008

  17. It looks awsome! Just a question: Can’t you use the renderHitData to acomplish the same as with unproject()?

    var rhd:RenderHitData = v.viewport.hitTestMouse();

    Cheers

    June 30th, 2008

  18. @Bjorn – If you take a look at the link at the beginning of the post – you will see that that is how I accomplished dragging with the old technique. But to pull it off – you have to have a separate plane with multiple segments which are projected, AND you have to update the plane every render. This method lets you accomplish the same thing in 3 lines of code. Plus unproject gives you a ray in real 3D space – not just a hit on a projected 2D surface – which has other applications as well – dragging was just one way to show it off.

    June 30th, 2008

  19. golum

    please,could you post source code about unproject() function for add it in your source for running it in flash cs3 (not flex) ?

    thanks

    July 3rd, 2008

  20. golum

    and getIntersectionLineNumbers(), please

    ;)

    July 3rd, 2008

  21. Thank you from Poland

    July 8th, 2008

  22. If the camera is lower on y, there is a situation where the ray no longer collides with the plane and there are numerical errors. Do you know if it’s possible to clamp the point on the plane to a region that guarantees and intersection?

    July 18th, 2008

  23. I’m using Papervision3D Public Alpha 2.0 – Great White (24.03.08) the latest revision I hope, but when I compile I get the error- 1172: Definition org.papervision3d.cameras:FreeCamera3D could not be found. I only have one Camera3D class file in that folder. Am I missing some files or am I missing something much more major? thanks for any advice.

    July 28th, 2008

  24. I was using your old way of dragging on an invisible plane and updated to this new way. Works great! Althought i think the y coordinates might be inverted on Plane3D. Because i was setting my invisible plane y to 120 but i had to set the plane3D y to -120 for it to work the same. So something is probably inverted somewhere.

    July 29th, 2008

  25. Awesome man! Keep up the great work. I really appreciate your willingness to share your experience…
    Titus

    July 29th, 2008

  26. how to get this stuff working? I’ve downloaded the source code, and it’s an .as file. Where is the .fla? Do we need the .fla too ?

    Please explain…i’m very new to all this, it’s so cool i wanna be able to do something like that for my project.

    August 13th, 2008

  27. I’ve blogged about your experiment in our blog. Good job!

    August 13th, 2008

  28. Confused

    I am trying this out in Flash CS3. I have used the Classpath to Papervision3D Public Alpha 2.0 – Great White as well as Caurina for Tweener.

    I get these three errors:
    1045: Interface IEventDispatcher was not found.
    5000: The class ‘Dragging3D’ must subclass ‘flash.display.MovieClip’ since it is linked to a library symbol of that type.
    5000: The class ‘Dragging3D_texture’ must subclass ‘flash.display.BitmapData’ since it is linked to a library symbol of that type.

    How do I fix this?

    August 19th, 2008

  29. Confused

    I just searched my machine for IEventDispatcher.as and it does not even exist.

    Can you or someone please explain how to properly get it working in Flash CS3?

    August 19th, 2008

  30. Michael

    You will need and “old” branch of GreatWhite. I found it here:http://www.nabble.com/Camera3D-bugs-in-latest-GW-build-td18899867.html
    Download the ZIP and use the GW inside the “FreeCamera3D lookAt” folder
    :-)

    September 1st, 2008

  31. Matt Pelham

    Hey there, the idea behind unproject and the ray is absolute genius! I added the unproject method to an older revision of CameraObject3D and am grabbing a ray now, there were no issues in the addition.

    I am wondering if you can get the X,Y,Z position of anything, rather than the intersection on a specific plane. So far, a quick hack job gets me a PLACER that follows the mouse at about 50% the Mouse Position and cannot follow the Z axis. I am currently using PaperBase:

    override protected function processFrame():void {
    // Process any movement or animation here.
    // influence the camera with the hover method
    default_camera.hover(0, ((stage.stageWidth/2)-mouseX)*.01, ((stage.stageHeight/2)-mouseY)*.01);

    //update camera position
    var cameraPosition:Number3D = new Number3D(default_camera.x, default_camera.y, default_camera.z);
    //get the direction vector of the mouse position
    var ray:Number3D = default_camera.unproject(current_viewport.containerSprite.mouseX, current_viewport.containerSprite.mouseY);
    //convert ray to a 3d point in the ray direction from the camera
    ray = Number3D.add(ray, cameraPosition);

    PLACER.x = ray.x;
    PLACER.y = ray.y;
    PLACER.z = ray.z;

    //find the intersection of the line defined by the camera and the ray position with the plane3D
    /*for each (var c in default_scene.children){
    //var intersect:Number3D = c.getIntersectionLineNumbers(cameraPosition, ray);
    for each (var cc in c.children){
    ///trace(cc.extra.p3D+”:”+cameraPosition);
    var intersect:Number3D = cc.extra.p3D.getIntersectionLineNumbers(cameraPosition, ray);
    //trace(intersect);
    }
    }*/

    }

    September 5th, 2008

  32. Ann

    Andy: thanks for the elegant solution!

    September 15th, 2008

  33. Mike

    I am using Flash CS3…. How do you fix this error?

    TypeError: Error #1007: Instantiation attempted on a non-constructor.

    What’s in Flex that’s not in Flash?

    thanks, Mike

    September 15th, 2008

  34. This is a great piece of code. It doesn’t work in the generic case, for instance when your object is a child of another object. I made some modifications to convert from parent relative coordinates to world coordinates. Here are the changes needed to drag a Cube along the plane formed by its front face

    private function onStartDrag( e:InteractiveScene3DEvent ):void {
    ………
    // get plane3D from the front face of the item
    var nFace:Number = 0;

    // ??? why it’s all minuses still remains a mystery to me
    var point:Number3D = new Number3D(-dragObject.sceneX, -dragObject..sceneY, -dragObject.sceneZ);

    var normal:Number3D = dragObject.geometry.faces[nFace].faceNormal.clone();

    Matrix3D.rotateAxis(dragObject.world, normal);
    plane = Plane3D.fromNormalAndPoint(normal, point);

    v.viewport.containerSprite.sortMode = ViewportLayerSortMode.INDEX_SORT;
    ………
    }

    October 6th, 2008

  35. BeechyBoy

    Papervision3D Public Alpha 2.0 – Great White (24.03.08)

    TypeError: Error #1007: Instantiation attempted on a non-constructor.
    at Dragging3D$iinit()

    Getting the above error and I’m working in Flash. Is this example workable in Flash? What should I be doing?

    Many thanks

    October 23rd, 2008

  36. David

    I find that when import the PV3D:
    There is some version difference appear, between PV1.5 and PV2.0.
    I discover that you use PV2.0, but put in FreeCamera3D.as(is in PV1.5).
    However this cause other error, I cannot fix it. Such as the Matrix3D.as and the Number3D.as. All this need to import other src from the PV2.0 it makes me mess up.
    Can you provide yr src of PV2.0 with yr modification for me. Thanks.
    David.
    BTW I am new for PV.

    October 25th, 2008

  37. David

    Dragging in 3D. It is Cool!
    However, I cannot find the PV3D src code that you use in this work.
    Because they changed their version. I cannot get the one b4.
    So can you let me know where I can find it. Thanks

    October 26th, 2008

  38. Ruben

    That’s awesome man. Thak you very muy for helping us to learn

    December 6th, 2008

  39. Icek

    Great work, but how to access stage, viewport and camera from within do3d?
    Do I need to do everything inside BasicView/IView?

    My problem is: I created do3d named Book. Inside Book, there are several BookPages (also do3d), and inside BookPages i got another do3d, and inside this do3d i want to do some dragging actions. Now I need to transmit variables (stage, viewport and camera) via each do3d constructor, but i think, that this method sucks. Other way is to use Singleton as Variable Locators, but this also doesn’t sound good.
    So, how to this?

    PS. Sorry for my bad english.

    December 10th, 2008

  40. So to make this work with Flash I made an fla, put Dragging3D in the Document class field. Then removed every line with Embed in it, and replaced this line

    var bmat:BitmapMaterial = new BitmapMaterial(Bitmap(new texture()).bitmapData, true);

    with

    var shape:Shape = new Shape()
    shape.graphics.beginFill(0×000000)
    shape.graphics.drawCircle(0, 0, 40)

    var bd:BitmapData = new BitmapData(40,40)
    bd.draw(shape)

    var bmat:BitmapMaterial = new BitmapMaterial(bd, true)

    and added import flash.display.Shape; on top

    To use the latest build used TortoiseSVN to download http://papervision3d.googlecode.com/svn/trunk/branches/

    I really like the interactivity in this example. Much more fun than any commercial crap I saw on FWA. It’s funny how developers put together better interactive things than “experience” designers.

    What’s wrong with the rolling? I can’t tell.

    January 1st, 2009

  41. Really useful, great work :-)

    February 1st, 2009

  42. Indyaner

    I reely like the Example. Too bad its for an old Version of PV3D and so its a pain in the Ass (literally, because of the hours of sitting) to figure it out how to use it in 2.0.

    February 19th, 2009

  43. Indyaner

    Maybe this is interresting for everyone who have Problems with the 1.5 Version

    http://blog.reyco1.com/dragging-objects-in-papervision3d/

    “A few months ago, Andy Zupko added some methods to Papervision3D which facilitated the ability to drag objects in 3d space. I collected those methods and placed them in a static class called RayTracer. The class currently has only one method which returns a Number3D object with the coordinates in 3D space based on the position of your mouse on a Plane3D object.”

    February 23rd, 2009

  44. strata

    is it possible to drag a collada file????if it is, what i have to change in source code???

    February 24th, 2009

  45. Ok, it’s been awhile and I think this is the best way to handle a drag.
    /*This function gets the intersection on a temporary plane, change the normals on KeyDown or other Event to change the Axis of Drag */
    public function getIntersection(vw:Viewport3D, c:Camera3D, normal:Number3D, origin:Number3D = 0,0,0):Number3D
    { var plane3D:Plane3D = new Plane3D(normal, origin);

    var cameraPosition:Number3D = new Number3D(c.x, c.y, c.z);
    var ray:Number3D = c.unproject(vw.containerSprite.mouseX, vw.containerSprite.mouseY);
    ray = Number3D.add(ray, cameraPosition);
    var intersect:Number3D = plane3D.getIntersectionLineNumbers(cameraPosition, ray);
    return intersect;
    }

    /*CameraProperties.pitch refers to the new pitch of the orbit function of a Camera3D, this simulates a normal ( AXIS ) change based on where the camera is. Maybe the next version will blend the two AXIS based on the percentage of Pitch to MinPitch and MaxPitch */
    if(CameraProperties.pitch<55){//Camera is below Isometric 45 degree angle
    var intersect = getIntersection(default_viewport, default_camera, new Number3D(0,1,0), new Number3D(0,0,0)); //This locks drag on X and Z axis.
    }else{
    intersect = getIntersection(default_viewport, default_camera, new Number3D(0,0,1), new Number3D(target.x,target.y,target.z)); //This locks drag on Y axis and origin on dragging object.
    }

    April 28th, 2009

  46. shalimar

    Hi, i know this is about a year old. but does someone have a fully functional FLA file for this? Or maybe just a list of what is needed. I have been trying to get my papervision (2.0) folder to have all the necessary files but it wont compile without errors.

    Im getting an error in the FreeCamera3D.as
    1023: Incompatible override.

    June 16th, 2009

  47. Diyafury

    Hi, I’m a complete novice when it comes to papervision…

    Basically what I’ve got is a drag object which acts as a handle for a scrub bar which sits in the material for a plane.

    I add the plane as a child of a displayObject3D (added to the scene), and add the handle as a child of the plane, hoping that somehow the dragging will be restricted to the width and the height of the plane… which ofcourse it isn’t…so my questions are:

    1. how do i set boundaries for my handle so that it will only drag along the x between 2 specified points on my plane?

    2. how can i get the 3D coords from a movieclip inside the plane’s material (ie. the scrub bar’s x,y,z) to use as boundaries?

    Hope this makes sense… think ‘spoon’ if you are able to help ;)

    July 2nd, 2009

  48. simply brilliant!

    August 31st, 2009

  49. Dan

    I can’t get this to work with an orthographic camera. Any ideas? I think orthoScale is throwing it off.

    September 14th, 2009

  50. Works great, fantastic and thanks.

    What i cant figure out is how to calculate the position in space thats not at y=0. Meaning if i wanted to click within a Papervision3d scene and get the intersect for a plane thats 150px above zero.

    Moving the point of the plane3d does not seem to do it. If i am wanting to drag an object thats not on the ground plane, the ray goes right through the shape and the shadow is behind and below it.

    December 1st, 2009

  51. I created a utility class based idea here,

    package {

    import org.papervision3d.core.math.Plane3D;
    import org.papervision3d.core.math.Number3D;
    import org.papervision3d.core.math.Number2D;
    import org.papervision3d.cameras.Camera3D;

    /**
    * @author alinakipoglu
    */
    public class CoordinateUtil {

    /*
    * @Public
    */

    public static const XZ_PLANE :Number3D = new Number3D(1, 0, 0);
    public static const YZ_PLANE :Number3D = new Number3D(0, 1, 0);
    public static const XY_PLANE :Number3D = new Number3D(0, 0, 1);

    /*
    * @Private
    */
    private static var plane :P lane3D;
    private static var ray :Number3D;
    private static var cameraPos :Number3D;

    /*
    * Initialize static class members immediately.
    */

    private static var initialize :Boolean = staticInitializer();

    private static function staticInitializer() : Boolean
    {
    plane = new Plane3D();
    cameraPos = new Number3D();

    return true;
    }

    /*
    * @Public
    */

    /*
    * Returns 3D Position based Screen Coordiates, Camera Rotation and Intersection Plane
    *
    * @Param $camera Set camera object EG: this.cameraAsCamera3D.
    * @Param $screenCoordinate Set Coordinates you wish to get 3d position EG: MouseX and MouseY
    *
    * @Example
    *
    * This will position object3D where containerSprite mouse position using 3D space
    *
    * var position3D :Number3D = CoordinateUtil.d3PositionBasedScreenCoordinate(this.cameraAsCamera3D, new Number2D(viewport.containerSprite.mouseX, viewport.containerSprite.mouseY), CoordinateUtil.XY_PLANE);
    *
    * object3D.x = position3D.x;
    * object3D.y = position3D.y;
    * object3D.z = position3D.z;
    */

    public static function d3PositionBasedScreenCoordinate($camera:Camera3D, $screenCoordinate:Number2D, $planeOrientation:Number3D) : Number3D
    {
    cameraPos.x = $camera.x;
    cameraPos.y = $camera.y;
    cameraPos.z = $camera.z;

    ray = $camera.unproject($screenCoordinate.x, $screenCoordinate.y);
    ray = Number3D.add(ray, cameraPos);

    plane.setNormalAndPoint($planeOrientation, Number3D.ZERO);

    return plane.getIntersectionLineNumbers(cameraPos, ray);;
    }
    }
    }

    December 6th, 2009

  52. Ryan W

    This is fantastic. Thank you so much!

    July 1st, 2010

  53. great tutorial Andy…..

    can you do a good article on how matrix and vector calculations are carried on 3D graphics? please..

    August 23rd, 2010

  54. Impressive!
    Have you considered adding some live ActionScript class diagrams to your code? Please check my address and think about.

    September 28th, 2010

  55. Erdem

    I’ve a question;

    I’m trying to calculate 3D position of a mouse click position by detected marker’s plane. Is there any easy way to find 3D postion on the marker’s plane? I’ve tried to intersect 2D position with marker’s plane. I can get the ray vector fo the mouse click position by flarCamera3D’s unproject method. I also can get the marker’s 3D position. But I need to find the normal vector of the marker’s plane. So I can build a plane3D with setNormalAndPoint method. Then I can intersect the ray with this plane and the result will be the 3D position of the 2D mouse position.
    My codes are here:

    var ray:Number3D = flarCamera3D.unproject(mouse.x,mouse.y);
    var p:Plane3D = new Plane3D();
    var normal: Number3D = I DONT KNOW HOW TO FIND??
    p.setNormalAndPoint(normal, markerPosition);
    var intersect:Number3D = p.getIntersectionLineNumbers(flarCamera3D.position, ray);

    August 16th, 2011

Reply to “Dragging in 3D – The Right Way”