Raycasting off when using fovHorizontal and changing setPerspective

Ok long struggle…
I have this script on my camera to make the view expand in both vertical and horizontal while preserving a given aspect which is not always 1:1. It works! the script is made from reading this post: Issue with non square aspect ratio and resizing - #2 by yaustar - Thanks to @Mal_Duffin and @yaustar :slight_smile:

CameraFrameWithinAspectRatio.attributes.add('minAspectRatio', { type: 'number' });


CameraFrameWithinAspectRatio.prototype.initialize = function () {
    var self = this;
    var onWindowResize = function () {
        self._checkAspectRatio();
    };
    this._checkAspectRatio();

    window.addEventListener('resize', onWindowResize, false);
    // this.app.graphicsDevice.on('resizecanvas', onWindowResize);

    var requiredAspectRatio = this.minAspectRatio;

    pc.Mat4.prototype.setPerspective = function (fov, aspect, znear, zfar, fovIsHorizontal) {
        var xmax, ymax;
        if (!fovIsHorizontal) {
            ymax = znear * Math.tan(fov * Math.PI / 360);
            xmax = ymax * aspect;
        }
        else {
            xmax = znear * Math.tan(fov * Math.PI / 360);
            xmax *= requiredAspectRatio;
            ymax = xmax / aspect;
        }

        return this.setFrustum(-xmax, xmax, -ymax, ymax, znear, zfar);
    };

};

CameraFrameWithinAspectRatio.prototype._checkAspectRatio = function () {
    this.entity.camera.horizontalFov = this.app.graphicsDevice.width / this.app.graphicsDevice.height < this.minAspectRatio;
};

Now my issue is that raycasting doesn’t work right. It is the combination of setPerspective and setting fovHorizontal true, that leads the raycasting being far off in the sides of the screen.
When ever the screen is wider than my desired 1.6 aspect ratio raycasting works great. Below the 1.6 (narrower screen) raycasting is off.

My raycasting code is attached to the root and look like this:

ClickObject.prototype.mouseDown = function (e) {
    console.log(e)
    this.doRaycast(e.x, e.y);
};
ClickObject.prototype.touchStart = function (e) {
    // Only perform the raycast if there is one finger on the screen
    if (e.touches.length === 1) {
        this.doRaycast(e.touches[0].x, e.touches[0].y);
    }
    e.event.preventDefault();
};


ClickObject.prototype.doRaycast = function (screenX, screenY) {
    if (this.active) {

        // The pc.Vec3 to raycast from (the position of the camera)
        var from = this.camera.getPosition();

        // The pc.Vec3 to raycast to (the click position projected onto the camera's far clip plane)
        var to = this.camera.camera.screenToWorld(screenX, screenY, this.camera.camera.farClip);

        // Raycast between the two points and return the closest hit result
        var result = this.app.systems.rigidbody.raycastFirst(from, to);

        // If there was a hit, store the entity
        if (result) {
            var hitEntity = result.entity;
            window.postMessage({
                type: "CAMERA_CHANGE", value: {
                    id: hitEntity.name
                }
            }, "*")
        }
    }
};

Any ideas on what I’ve missed?

Without trying to understand your math, I’d say that if you modified the width or height of the original perspective, then the default screenToWorld method probably won’t work correctly. You would need to apply the same aspect changes there, I’d presume.

1 Like

I solved it by expanding the camera fov proportional with the aspect ratio difference. Seem to me like a healthier way than changin camera perspective. And raycasting works now in any aspect :sunny:

var CameraFrameWithinAspectRatio = pc.createScript('cameraFrameWithinAspectRatio');
CameraFrameWithinAspectRatio.attributes.add('minAspectRatio', { type: 'number' });


CameraFrameWithinAspectRatio.prototype.initialize = function () {
   
    this.fov = this.entity.camera.fov;
    this._checkAspectRatio();
    
    var self = this;
    var onWindowResize = function () {
        self._checkAspectRatio();
    };
  
    window.addEventListener('resize', onWindowResize, false);

};


CameraFrameWithinAspectRatio.prototype._checkAspectRatio = function () {
   var ratio = this.app.graphicsDevice.width / this.app.graphicsDevice.height;
   if (ratio < this.minAspectRatio){
      this.entity.camera.fov =  this.fov  / (ratio/this.minAspectRatio)
      } else {
           this.entity.camera.fov =  this.fov 
      }
};


1 Like