Dragging an Object in 3D Space


#1

Hi,

I’m working on a scenario in which I’ll have an object fixed on a particular axis (e.g. x axis). I want the user to be able to click and drag the object along that axis.

Obviously if the object was perfectly aligned flat to the screen, I could just use the mouse’s x coordinates to control the x translation of the object. But if the camera is rotated/positioned differently, the object may not be perfectly aligned with the screen. Moving the object along the x axis may be more of a diagonal line as the object moves further away/closer/etc. In that respect, it would no longer be accurate to just look at the mouse’s x movement.

I’m wondering how I could have the mouse movement consider the 3d orientation of the model to correctly calculate how to progress as the user clicks/drags.

Let me know if that doesn’t make sense.


#2

Let’s say the axis was the world X axis. In that case, I would:

  • Create a ray that passes through the view point (the camera’s world position returned by this.entity.getPosition() for the camera entity) and the mouse position in world space (found using the CameraComponent#screenToWorld function).
  • Intersect that ray with the world X-Z plane.

The x coordinate of the intersection point will be the new x coordinate for the dragged entity.


#3

Great! Can you explain the second point a little bit more; what do you mean by intersecting it with the world X-Z plane?

Also…what if I’m dealing with a local axis? Is that possible?


#4

We have a tutorial sample that does raycasts to shapes. We have a few samples regarding raycasting:
http://developer.playcanvas.com/en/tutorials/?tags=raycast

I’ve just created a project that will be added to the sample list which is more streamlined. In this case, look at using the pc.Plane shape instead of pc.BoundingBox.

https://playcanvas.com/project/457922/overview/shape-raycasting


#5

Thanks so much, @steven ! That totally helps with understanding the intersect aspect of Raycasting. I’m still having trouble understanding how I could apply this knowledge to allow me to click and drag an object on a single axis in local space.

For example, is it possible to have a scene–like the image below–where no matter how I rotate or zoom the camera, I can predictably drag this object along its local z axis?


#6

So the idea is that you have a plane that is always aligned on the XZ axis of the box (shown in the image below by the grey plane). Once you have that, you can do a raycast on that plane and get the intersection point (shown by the black ‘X’).

This is where it gets a little math heavy. At this point you can project the vector from the position of the box to the intersection point onto the Z axis of the box (shown by the yellow arrow) to get the position on where you should move the box to. (YouTube video on vector projection: https://www.youtube.com/watch?v=fqPiDICPkj8)

If you are still having trouble, drop another message here and if we get some time, we may be able quickly put together an example for you.


#7

Thanks a ton, @steven! That makes a lot more sense. It hadn’t even occurred to me to have a plan solely for the purpose of calculating the movement.

I’ve worked on this a little more, but I’m still a little bit hung up on a couple things. Here’s the PlayCanvas project I’ve been using to experiment (forked from the example you sent me before).

I think my problem is that I’m using the drag object’s position rather than merely the Z axis (as you said, “the Z axis of the box”). But I’m not really sure how to do it only on the Z axis. Here’s the code I’m using:

var Raycast = pc.createScript('raycast');

Raycast.attributes.add('cameraEntity', {type: 'entity', title: 'Camera Entity'});
Raycast.attributes.add('dragEntity', {type: 'entity', title: 'Drag Entity'});
Raycast.attributes.add('projectSurface', {type: 'entity', title: 'Project Surface Entity'});

// initialize code called once per entity
Raycast.prototype.initialize = function() {
    // More information about pc.ray: http://developer.playcanvas.com/en/api/pc.Ray.html
    this.ray = new pc.Ray();
    
    // Register the mouse down and touch start event so we know when the user has clicked
    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
    this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
    this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
    
    this.hitPosition = new pc.Vec3();
    
    // More information about pc.BoundingBox: http://developer.playcanvas.com/en/api/pc.BoundingBox.html
    this.aabbShape = new pc.BoundingBox(this.projectSurface.getPosition().clone(), this.projectSurface.getLocalScale().clone().scale(0.5));
};


Raycast.prototype.doRayCast = function (screenPosition) {
    // Initialise the ray and work out the direction of the ray from the a screen position
    this.cameraEntity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.cameraEntity.camera.farClip, this.ray.direction); 
    this.ray.origin.copy(this.cameraEntity.getPosition());
    this.ray.direction.sub(this.ray.origin).normalize();
    
    var isIntersecting = this.aabbShape.intersectsRay(this.ray, this.hitPosition);        
    if ( isIntersecting ) {
        
        var projectionPoint = this.hitPosition.project( this.dragEntity.getPosition() );
        return projectionPoint;
    }  
};


Raycast.prototype.onMouseDown = function(event) {
    if (event.button == pc.MOUSEBUTTON_LEFT) {
        this.state = 'dragging';
    }
};

Raycast.prototype.onMouseUp = function(event) {
    this.state = 'stationary';
};

Raycast.prototype.onMouseMove = function(event) {
    if ( this.state == 'dragging' ) {
        var position = this.doRayCast(event);
        this.dragEntity.setPosition( position );
    }
};

Also, thanks a TON for all of your help so far.


#8

Almost!

A couple of changes needed.

  1. Use the pc.Plane (https://github.com/playcanvas/engine/blob/master/src/shape/plane.js) shape rather than the pc.BoundingBox as mentioned above otherwise your intersection point will be in a very different place
  2. You need to calculate the vector from box to the intersection point and then project that onto the object’s forward (-Z) axis to get the distance to move it by

I’ve forked your project with the changes made which should help:
https://playcanvas.com/project/458132/overview/dragging-in-local-space-forums


Drag to rotate an object in local space
#9

Amazing! Thanks SO much.


#10

Hi, I realize it is an old post, but I was wondering if it’s quite the same to drag an entity in 3D space but in 2 axis at the same time so it gives a free move around in XZ for instance ?

Thanks :smiley:


#11

Should be roughly the same if not easier as you don’t have to do the projection of the vector to restrict it to the one axis.