Skip to content

Commit 199cc41

Browse files
authored
Changes to Camera.screenToWorld to improve precision with large frustum depth range (playcanvas#2400)
1 parent e2a1517 commit 199cc41

File tree

2 files changed

+54
-46
lines changed

2 files changed

+54
-46
lines changed

src/math/mat4.js

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { math } from './math.js';
2+
import { Vec2 } from './vec2.js';
23
import { Vec3 } from './vec3.js';
34
import { Vec4 } from './vec4.js';
45

6+
var _halfSize = new Vec2();
7+
58
/**
69
* @class
710
* @name pc.Mat4
@@ -17,6 +20,17 @@ function Mat4() {
1720
this.data = data;
1821
}
1922

23+
// Static function which evaluates perspective projection matrix half size at the near plane
24+
Mat4._getPerspectiveHalfSize = function (halfSize, fov, aspect, znear, fovIsHorizontal) {
25+
if (fovIsHorizontal) {
26+
halfSize.x = znear * Math.tan(fov * Math.PI / 360);
27+
halfSize.y = halfSize.x / aspect;
28+
} else {
29+
halfSize.y = znear * Math.tan(fov * Math.PI / 360);
30+
halfSize.x = halfSize.y * aspect;
31+
}
32+
};
33+
2034
Object.assign(Mat4.prototype, {
2135
/**
2236
* @function
@@ -587,17 +601,8 @@ Object.assign(Mat4.prototype, {
587601
* var persp = pc.Mat4().setPerspective(45, 16 / 9, 1, 1000);
588602
*/
589603
setPerspective: function (fov, aspect, znear, zfar, fovIsHorizontal) {
590-
var xmax, ymax;
591-
592-
if (!fovIsHorizontal) {
593-
ymax = znear * Math.tan(fov * Math.PI / 360);
594-
xmax = ymax * aspect;
595-
} else {
596-
xmax = znear * Math.tan(fov * Math.PI / 360);
597-
ymax = xmax / aspect;
598-
}
599-
600-
return this.setFrustum(-xmax, xmax, -ymax, ymax, znear, zfar);
604+
Mat4._getPerspectiveHalfSize(_halfSize, fov, aspect, znear, fovIsHorizontal);
605+
return this.setFrustum(-_halfSize.x, _halfSize.x, -_halfSize.y, _halfSize.y, znear, zfar);
601606
},
602607

603608
/**

src/scene/camera.js

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import {
1313

1414
// pre-allocated temp variables
1515
var _deviceCoord = new Vec3();
16-
var _far = new Vec3();
17-
var _farW = new Vec3();
16+
var _halfSize = new Vec3();
17+
var _point = new Vec3();
1818
var _invViewProjMat = new Mat4();
1919

2020
/**
@@ -383,6 +383,15 @@ Object.assign(Camera.prototype, {
383383
this.vrDisplay = other.vrDisplay;
384384
},
385385

386+
_updateViewProjMat: function () {
387+
if (this._projMatDirty || this._viewMatDirty || this._viewProjMatDirty) {
388+
var projMat = this.projectionMatrix;
389+
var viewMat = this.viewMatrix;
390+
this._viewProjMat.mul2(projMat, viewMat);
391+
this._viewProjMatDirty = false;
392+
}
393+
},
394+
386395
/**
387396
* @private
388397
* @function
@@ -399,12 +408,7 @@ Object.assign(Camera.prototype, {
399408
screenCoord = new Vec3();
400409
}
401410

402-
if (this._projMatDirty || this._viewMatDirty || this._viewProjMatDirty) {
403-
var projMat = this.projectionMatrix;
404-
var viewMat = this.viewMatrix;
405-
this._viewProjMat.mul2(projMat, viewMat);
406-
this._viewProjMatDirty = false;
407-
}
411+
this._updateViewProjMat();
408412
this._viewProjMat.transformPoint(worldCoord, screenCoord);
409413

410414
// calculate w co-coord
@@ -438,40 +442,39 @@ Object.assign(Camera.prototype, {
438442
worldCoord = new Vec3();
439443
}
440444

441-
if (this._projMatDirty || this._viewMatDirty || this._viewProjMatDirty) {
442-
var projMat = this.projectionMatrix;
443-
var viewMat = this.viewMatrix;
444-
this._viewProjMat.mul2(projMat, viewMat);
445-
this._viewProjMatDirty = false;
446-
}
447-
_invViewProjMat.copy(this._viewProjMat).invert();
445+
// Calculate the screen click as a point on the far plane of the normalized device coordinate 'box' (z=1)
446+
var range = this._farClip - this._nearClip;
447+
_deviceCoord.set(x / cw, (ch - y) / ch, z / range);
448+
_deviceCoord.scale(2);
449+
_deviceCoord.sub(Vec3.ONE);
448450

449451
if (this._projection === PROJECTION_PERSPECTIVE) {
450-
// Calculate the screen click as a point on the far plane of the
451-
// normalized device coordinate 'box' (z=1)
452-
_far.set(x / cw * 2 - 1, (ch - y) / ch * 2 - 1, 1);
453452

454-
// Transform to world space
455-
_invViewProjMat.transformPoint(_far, _farW);
453+
// calculate half width and height at the near clip plane
454+
Mat4._getPerspectiveHalfSize(_halfSize, this._fov, this._aspectRatio, this._nearClip, this._horizontalFov);
455+
456+
// scale by normalized screen coordinates
457+
_halfSize.x *= _deviceCoord.x;
458+
_halfSize.y *= _deviceCoord.y;
456459

457-
var w = _far.x * _invViewProjMat.data[3] +
458-
_far.y * _invViewProjMat.data[7] +
459-
_far.z * _invViewProjMat.data[11] +
460-
_invViewProjMat.data[15];
460+
// transform to world space
461+
var invView = this._node.getWorldTransform();
462+
_halfSize.z = -this._nearClip;
463+
invView.transformPoint(_halfSize, _point);
461464

462-
_farW.scale(1 / w);
465+
// point along camera->_point ray at distance z from the camera
466+
var cameraPos = this._node.getPosition();
467+
worldCoord.sub2(_point, cameraPos);
468+
worldCoord.normalize();
469+
worldCoord.scale(z);
470+
worldCoord.add(cameraPos);
463471

464-
var alpha = z / this._farClip;
465-
worldCoord.lerp(this._node.getPosition(), _farW, alpha);
466472
} else {
467-
// Calculate the screen click as a point on the far plane of the
468-
// normalized device coordinate 'box' (z=1)
469-
var range = this._farClip - this._nearClip;
470-
_deviceCoord.set(x / cw, (ch - y) / ch, z / range);
471-
_deviceCoord.scale(2);
472-
_deviceCoord.sub(Vec3.ONE);
473-
474-
// Transform to world space
473+
474+
this._updateViewProjMat();
475+
_invViewProjMat.copy(this._viewProjMat).invert();
476+
477+
// Transform to world space
475478
_invViewProjMat.transformPoint(_deviceCoord, worldCoord);
476479
}
477480

0 commit comments

Comments
 (0)