Raycast returns null on mobile

Hi.

I am trying to modify the model viewer project to rotate the model only when the player drags on the object itself. I am doing a raycast from the camera and saving a reference to the target entity and rotating it. It’s working as expected on desktop, but not on mobile. Raycast hit result is always null. I found another forum post saying that the raycast center is inaccurate on mobile, but I tried touching ever where on the screen and I am always getting null.

Here is a demo project:
https://playcanvas.com/project/1098879/overview/model-viewer

This is the only active/relevant script, attached to the camera:

var RaycastRotate = pc.createScript('raycastRotate');

RaycastRotate.attributes.add('orbitSensitivity', {
    type: 'number', 
    default: 0.3, 
    title: 'Orbit Sensitivity', 
    description: 'How fast the camera moves around the orbit. Higher is faster'
});

RaycastRotate.prototype.initialize = function() {
    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onMouseDown, this);
    this.app.mouse.on(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
    this.app.mouse.on(pc.EVENT_MOUSEUP, this.onMouseUp, this);
    
    console.log(this.app.touch);

    if (this.app.touch) {
        this.app.touch.on(pc.EVENT_TOUCHSTART, this.onTouchStart, this);
        this.app.touch.on(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
        this.app.touch.on(pc.EVENT_TOUCHEND, this.onTouchEnd, this);
    }
    
    this.on('destroy', function () {
        this.app.mouse.off(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
        this.app.mouse.off(pc.EVENT_MOUSEMOVE, this.onMouseMove, this);
        this.app.mouse.off(pc.EVENT_MOUSEUP, this.onMouseUp, this);

        if (this.app.touch) {
            this.app.touch.off(pc.EVENT_TOUCHSTART, this.onTouchStart, this);
            this.app.touch.off(pc.EVENT_TOUCHMOVE, this.onTouchMove, this);
            this.app.touch.off(pc.EVENT_TOUCHEND, this.onTouchEnd, this);        
        }
    }, this);

    this.raycastTarget = undefined;
    this.lastTouchPoint = new pc.Vec2();
};

RaycastRotate.horizontalQuat = new pc.Quat();
RaycastRotate.verticalQuat = new pc.Quat();
RaycastRotate.resultQuat = new pc.Quat();

RaycastRotate.prototype.rotate = function (dx, dy) {

    if(!this.raycastTarget) return;

    var horzQuat = RaycastRotate.horizontalQuat;
    var vertQuat = RaycastRotate.verticalQuat;
    var resultQuat = RaycastRotate.resultQuat;

    // Create a rotation around the camera's orientation in order for them to be in 
    // screen space  
    horzQuat.setFromAxisAngle(this.entity.up, dx * this.orbitSensitivity);
    vertQuat.setFromAxisAngle(this.entity.right, dy * this.orbitSensitivity);

    // Apply both the rotations to the existing entity rotation
    resultQuat.mul2(horzQuat, vertQuat);
    resultQuat.mul(this.raycastTarget.getRotation());

    this.raycastTarget.setRotation(resultQuat);
};

RaycastRotate.prototype.getRaycastTarget = function (screenPosition) {
    // The pc.Vec3 to raycast from
    var from = this.entity.getPosition();
    // The pc.Vec3 to raycast to 
    var to = this.entity.camera.screenToWorld(screenPosition.x, screenPosition.y, this.entity.camera.farClip);

    // Raycast between the two points
    var result = this.app.systems.rigidbody.raycastFirst(from, to)
    return result ? result.entity : undefined;
};

RaycastRotate.prototype.onMouseDown = function(e) {
    this.raycastTarget = this.getRaycastTarget(e);
}

RaycastRotate.prototype.onMouseMove = function(e) {
    if(!this.raycastTarget) return;
    this.rotate(e.dx, e.dy);
}

RaycastRotate.prototype.onMouseUp = function(e) {
    this.raycastTarget = undefined;
}

RaycastRotate.prototype.onTouchStart = function(e) {
    console.log("Touch Start");
    this.raycastTarget = this.getRaycastTarget(e);
    var touch = e.touches[0];
    this.lastTouchPoint.set(touch.x, touch.y);
    console.log(this.raycastTarget, this.lastTouchPoint);
}

RaycastRotate.prototype.onTouchMove = function(e) {
    console.log("Touch Move");
    if(!this.raycastTarget) return;

    var touch = e.touches[0];
    var dx = touch.x - this.lastTouchPoint.x;
    var dy = touch.y - this.lastTouchPoint.y;
    
    this.rotate(dx, dy);
    
    this.lastTouchPoint.set(touch.x, touch.y);
    console.log(dx, dy, touch);
}

RaycastRotate.prototype.onTouchEnd = function(e) {
    console.log("Touch End");
    this.raycastTarget = undefined;
    console.log(this.raycastTarget)
}

The keyboard and touch events contain different information. Study what is passed to getRaycastTarget method when used by touch to understand how to get coordinates from there.

2 Likes

I see what happened. I should have taken the values from touches[0] instead of sending the entire event.

RaycastRotate.prototype.onTouchStart = function(e) {
    var touch = e.touches[0];
    this.raycastTarget = this.getRaycastTarget(touch);
    this.lastTouchPoint.set(touch.x, touch.y);
}

Thank you so much

2 Likes

How does this work differently in AR mode? i have attached the same script to the camera in an AR project. And I am able to rotate the model before starting AR mode. After starting AR mode, I am not getting any console logs and the object is also not rotating. It is like in AR mode we are using a completely different camera. In which case, how could I go about raycasting and rotating the object in AR space?