I am developing an 3D engine from scratch and I am trying to unproject a vector. I use my own Math library called ALMath.js. As far as I know, to convert 2d screen coordinates into 3d world coordinates I need multiply the vector formed by x and y coordinates in the canvas by the inverse of ViewProjection Matrix. This is the code for unproject:
unproject : function (vector){
var viewMatrix = camera.viewMatrix;
var projectionMatrix = camera.projectionMatrix;
var viewProjection = viewMatrix.multiply(projectionMatrix);
var inverseViewProjection = viewProjection.getInverse();
var x = ((vector.x -0) / (AL3D.width)) *2 -1;
var y = ((vector.y -0) / (AL3D.height)) * 2 -1;
var z = 2*vector.z-1;
var w = 1;
var vec4 = new ALMath.Vector4(x,y,z,w);
var transformedVector = inverseViewProjection.multiplyByVector4(vec4);
var wordCoords = new ALMath.Vector3(transformedVector.x/transformedVector.w,transformedVector.y/transformedVector.w,transformedVector.z/transformedVector.w);
return wordCoords;
}
The ALMath library works fine. I use it around all engine (compute the model-view-projection, create projection matrix, do the inverse ...) and works well. In fact I check the result of the operations using Octave (alternative to matlab) and the result is the same with ALMath.
The problem is that if I click in the upper left corner:
canvas.addEventListener('click', function(event) {
var rect = canvas.getBoundingClientRect();
var x = event.pageX - rect.left,
y = event.pageY - rect.top;
var vector = camera.unproject(new ALMath.Vector3(x,y,0));
});
with x = 0 and y = 2 I get a vector = (-0.12131, -0.25894, -0.79409) and I know that it is wrong because If I set the cube mesh in that position I see that this is not the upper left corner.
I have programmed a lookAt function in the camera class
lookAt : function (eye, target, up)
As a example I show the operations for x = 0 and y = 2 with octave.
Firs I set my camera as follow:
camera = new AL3D.PerspectiveCamera(40, window.innerWidth/window.innerHeight);
camera.lookAt(new ALMath.Vector3(), new ALMath.Vector3(0,-0.5,-2), new ALMath.Vector3(0,1,0));
And this is step by step calculations in octave that match with javascript code result
viewMatrix =
1.00000 0.00000 0.00000 0.00000
0.00000 0.97014 0.24254 0.00000
0.00000 -0.24254 0.97014 0.00000
0.00000 0.00000 0.00000 1.00000
projectionMatrix =
1.37374 0.00000 0.00000 0.00000
0.00000 2.82610 0.00000 0.00000
0.00000 0.00000 -1.00020 -0.20002
0.00000 0.00000 -1.00000 0.00000
octave:7> viewProjectionMatrix = viewMatrix * projectionMatrix
viewProjectionMatrix =
1.37374 0.00000 0.00000 0.00000
0.00000 2.74171 -0.24258 -0.04851
0.00000 -0.68543 -0.97034 -0.19405
0.00000 0.00000 -1.00000 0.00000
octave:8> inverseViewProjectionMatrix = inv(viewProjectionMatrix)
inverseViewProjectionMatrix =
0.72794 0.00000 0.00000 -0.00000
0.00000 0.34328 -0.08582 0.00000
0.00000 0.00000 0.00000 -1.00000
0.00000 -1.21256 -4.85023 5.00050
AL3D.width = 1366
AL3D.height = 664
x = -1
y = -0.9939759036144579
z = -1
w = 1
octave:9> vector = [ -1 -0.9939759036144579 -1 1]
vector =
-1.00000 -0.99398 -1.00000 1.00000
octave:10> transformedVector = vector * inverseViewProjectionMatrix
transformedVector =
-0.72794 -1.55377 -4.76492 6.00050
// Perspective division
octave:12> result = [ transformedVector(1)/transformedVector(4) transformedVector(2)/transformedVector(4) transformedVector(3)/transformedVector(4)]
result =
-0.12131 -0.25894 -0.79409
Maybe I am forgeting something, but I don't know. What is wrong in my logic. Thanks.
Edit: The problem seem to be the view matrix. This is the code for my view matrix:
lookAt : function(eye, target, up){
var eye = eye || new ALMath.Vector3();
var up = up || new ALMath.Vector3();
var target = target || new ALMath.Vector3();
var c = this.components;
var z = target.sub(eye);
z = z.normalize();
var x = z.cross(up);
x = x.normalize();
var y = x.cross(z);
y = y.normalize();
c[0] = x.x; c[1] = x.y; c[2] = x.z;
c[4] = y.x; c[5] = y.y; c[6] = y.z;
c[8] = -z.x; c[9] = -z.y; c[10] = -z.z;
c[12] = -x.dot(eye); c[13] = -y.dot(eye); c[14] = z.dot(eye);
return this;
},
and this is for projection:
perspectiveProjection : function ( fov, aspect, zNear, zFar ) {
var a = aspect;
var tan=Math.tan(ALMath.degToRad(0.5*fov)),
A=-(zFar+zNear)/(zFar-zNear),
B=(-2*zFar*zNear)/(zFar-zNear);
var c = this.components;
c[ 0 ] = 0.5/tan; c[ 4 ] = 0; c[ 8 ] = 0; c[ 12 ] = 0;
c[ 1 ] = 0; c[ 5 ] = (0.5*a/tan); c[ 9 ] = 0; c[ 13 ] = 0;
c[ 2 ] = 0; c[ 6 ] = 0; c[ 10 ] = A; c[ 14 ] = B;
c[ 3 ] = 0; c[ 7 ] = 0; c[ 11 ] =-1; c[ 15 ] = 0;
return this;
},
My projection matrix is fine. But my view matrix is not the same that the view matrix compute in the gman library: https://github.com/greggman/twgl.js/blob/master/src/m4.js in m4.js the matrix is computed
c[0] = x.x; c[1] = x.y; c[2] = x.z; c[3] = 0;
c[4] = y.x; c[5] = y.y; c[6] = y.z; c[7] = 0;
c[8] = -z.x; c[9] = -z.y; c[10] = -z.z; c[11] = 0;
c[12] = eye.x; c[13] = eye.y; c[14] = eye.z; c[15] = 1;
instead of
c[0] = x.x; c[1] = x.y; c[2] = x.z;
c[4] = y.x; c[5] = y.y; c[6] = y.z;
c[8] = -z.x; c[9] = -z.y; c[10] = -z.z;
c[12] = -x.dot(eye); c[13] = -y.dot(eye); c[14] = z.dot(eye);
Note that in my math library I computed it with dot between axis and eye as said here: Calculating a LookAt matrix
So,that thread is wrong? I should use eye directly instead dot product between axis and eye?
If I run the script posted by gman I get the following ouput:
frustum points
0 -0.414 -0.207 -0.500
1 0.414 -0.207 -0.500
2 -0.414 0.207 -0.500
3 0.414 0.207 -0.500
4 -82.843 -41.421 -100.000
5 82.843 -41.421 -100.000
6 -82.843 41.421 -100.000
7 82.843 41.421 -100.000
camerafrustum points
0 1.666 2.120 3.080
1 1.080 2.120 3.666
2 1.497 2.458 2.911
3 0.911 2.458 3.497
4 134.224 25.915 19.067
5 17.067 25.915 136.224
6 100.403 93.555 -14.754
7 -16.754 93.555 102.403
screen points (should match width, height)
0 148.858 -47.653 4.029
1 111.806 -38.903 3.734
2 147.454 -72.303 4.217
3 108.845 -59.000 3.876
4 951.911 101.710 9.651
5 61.823 20.354 3.229
6 -833.522 732.104 -10.661
7 25.094 -97.340 4.035
unprojected (should match cameraFrustum points)
0 1.666 2.120 3.080
1 1.080 2.120 3.666
2 1.497 2.458 2.911
3 0.911 2.458 3.497
4 134.224 25.915 19.067
5 17.067 25.915 136.226
6 100.404 93.556 -14.754
7 -16.754 93.557 102.405
Is the same that result posted by gman but it differs in the screen points (should match width, height) section.
If I run the gman script in my pc with this directive: <script src="https://twgljs.org/dist/2.x/twgl-full.min.js"></script> the output is the same that gman posted
screen points (should match width, height)
0 -0.000 -0.000 -1.000
1 300.000 -0.000 -1.000
2 -0.000 150.000 -1.000
3 300.000 150.000 -1.000
4 0.000 0.000 1.000
5 300.000 0.000 1.000
6 -0.000 150.000 1.000
7 300.000 150.000 1.000
But If I download https://twgljs.org/dist/2.x/twgl-full.min.js and store in the same directory that html file and use the directive <script src="twgl.js"></script> in the html file, the output is like my Math library, this is:
screen points (should match width, height)
0 148.858 -47.653 4.029
1 111.806 -38.903 3.734
2 147.454 -72.303 4.217
3 108.845 -59.000 3.876
4 951.911 101.710 9.651
5 61.823 20.354 3.229
6 -833.522 732.104 -10.661
7 25.094 -97.340 4.035
The script adapted to my library is the following:
function main()
{
var width = 300;
var height = 150;
var aspect = width / height
var fieldOfView = Math.PI * 0.25; // 45 degrees
var zNear = 0.5;
var zFar = 100;
var projection = new ALMath.Matrix4();
projection = projection.perspectiveProjection(45, aspect, zNear, zFar);
var eye = new ALMath.Vector3(1, 2, 3);
var target = new ALMath.Vector3(4, 5, 6);
var up = new ALMath.Vector3(0, 1, 0);
var camera = new ALMath.Matrix4();
camera = camera.lookAt(eye, target, up);
var view = camera.getInverse();
var viewProjection = view.multiply(projection);
var inverseViewProjection = viewProjection.getInverse();
function getFrustumPoints(fieldOfView, aspect, zNear, zFar) {
var f = 1 / Math.tan(fieldOfView / 2);
var nearY = zNear / f;
var nearX = nearY * aspect;
var farY = zFar / f;
var farX = farY * aspect;
return [
new ALMath.Vector3(-nearX, -nearY, -zNear),
new ALMath.Vector3( nearX, -nearY, -zNear),
new ALMath.Vector3(-nearX, nearY, -zNear),
new ALMath.Vector3( nearX, nearY, -zNear),
new ALMath.Vector3(-farX, -farY, -zFar),
new ALMath.Vector3( farX, -farY, -zFar),
new ALMath.Vector3(-farX, farY, -zFar),
new ALMath.Vector3( farX, farY, -zFar),
];
}
function projectScreenPoint(width, height, projection, point) {
var c = projection.transformPoint(point);
return new ALMath.Vector3((c.x * 0.5 + 0.5) * width,(c.y * 0.5 + 0.5) * height, c.z);
}
function unproject(width, height, inverseViewProjection, p) {
return inverseViewProjection.transformPoint(new ALMath.Vector3(p.x / width * 2 - 1, p.y / height * 2 - 1, p.z));
}
function showPoints(label, points) {
log(label);
points.forEach((p, ndx) => log(ndx, p.x.toFixed(3), p.y.toFixed(3), p.z.toFixed(3)));
}
var frustumPoints = getFrustumPoints(fieldOfView, aspect, zNear, zFar);
showPoints("frustum points", frustumPoints);
var cameraFrustumPoints = frustumPoints.map(
p => camera.transformPoint(p));
showPoints("camerafrustum points", cameraFrustumPoints);
var screenPoints = cameraFrustumPoints.map(
p => projectScreenPoint(width, height, viewProjection, p));
showPoints("screen points (should match width, height)", screenPoints);
var unprojectedPoints = screenPoints.map(
p => unproject(width, height, inverseViewProjection, p));
showPoints("unprojected (should match cameraFrustum points)",
unprojectedPoints);
function log(...args) {
var elem = document.createElement("pre");
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
}
So the questions now are:
If I were you I'd write some tests. You have a frustum and a camera. You should easily be able to compute the corners of your frustum. Then using those corners you should be able to project them to get screen coordinates. Then check if you unproject those screen coordinates that you get the frustum points back.
Since you didn't post your math library I'll use my own
var m4 = twgl.m4;
// Plug in your math lib here
var m = {
multiply: (a, b) => m4.multiply(a, b),
inverse: (a) => m4.inverse(a),
identity: () => m4.identity(),
lookAt: (eye, target, up) => m4.lookAt(eye, target, up),
perspective: (fov, aspect, zNear, zFar) => m4.perspective(fov, aspect, zNear, zFar),
transformPoint: (m, p) => m4.transformPoint(m, p),
};
var width = 300;
var height = 150;
var aspect = width / height
var fieldOfView = Math.PI * 0.25; // 45 degrees
var zNear = 0.5;
var zFar = 100;
var projection = m.perspective(fieldOfView, aspect, zNear, zFar);
var eye = [1, 2, 3];
var target = [4, 5, 6];
var up = [0, 1, 0];
var camera = m.lookAt(eye, target, up);
var view = m.inverse(camera);
var viewProjection = m.multiply(projection, view);
var inverseViewProjection = m.inverse(viewProjection);
function getFrustumPoints(fieldOfView, aspect, zNear, zFar) {
var f = 1 / Math.tan(fieldOfView / 2);
var nearY = zNear / f;
var nearX = nearY * aspect;
var farY = zFar / f;
var farX = farY * aspect;
return [
[-nearX, -nearY, -zNear],
[ nearX, -nearY, -zNear],
[-nearX, nearY, -zNear],
[ nearX, nearY, -zNear],
[-farX, -farY, -zFar],
[ farX, -farY, -zFar],
[-farX, farY, -zFar],
[ farX, farY, -zFar],
];
}
function projectScreenPoint(width, height, projection, point) {
var c = m.transformPoint(projection, point);
return [
(c[0] * 0.5 + 0.5) * width,
(c[1] * 0.5 + 0.5) * height,
c[2],
];
}
function unproject(width, height, inverseViewProjection, p) {
return m.transformPoint(
inverseViewProjection,
[
p[0] / width * 2 - 1,
p[1] / height * 2 - 1,
p[2],
]);
}
function showPoints(label, points) {
log(label);
points.forEach((p, ndx) => log(ndx, p[0].toFixed(3), p[1].toFixed(3), p[2].toFixed(3)));
}
var frustumPoints = getFrustumPoints(fieldOfView, aspect, zNear, zFar);
showPoints("frustum points", frustumPoints);
var cameraFrustumPoints = frustumPoints.map(
p => m.transformPoint(camera, p));
showPoints("camerafrustum points", cameraFrustumPoints);
var screenPoints = cameraFrustumPoints.map(
p => projectScreenPoint(width, height, viewProjection, p));
showPoints("screen points (should match width, height)", screenPoints);
var unprojectedPoints = screenPoints.map(
p => unproject(width, height, inverseViewProjection, p));
showPoints("unprojected (should match cameraFrustum points)",
unprojectedPoints);
function log(...args) {
var elem = document.createElement("pre");
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
pre { margin: 0 };
<script src="https://twgljs.org/dist/2.x/twgl-full.min.js"></script>
Note: m4.transformPoint does the divide by w for the result
Plug your math lib in above?
Here's an example of plugging in glMatrix
// Plug in your math lib here
var m = {
multiply: (a, b) => mat4.multiply(mat4.create(), a, b),
inverse: (a) => mat4.invert(mat4.create(), a),
identity: () => mat4.create(),
lookAt: (eye, target, up) => mat4.invert(
mat4.create(),
mat4.lookAt(mat4.create(), eye, target, up)),
perspective: (fov, aspect, zNear, zFar) => mat4.perspective(
mat4.create(), fov, aspect, zNear, zFar),
transformPoint: (m, p) => vec3.transformMat4(vec3.create(), p, m),
};
var width = 300;
var height = 150;
var aspect = width / height
var fieldOfView = Math.PI * 0.25; // 45 degrees
var zNear = 0.5;
var zFar = 100;
var projection = m.perspective(fieldOfView, aspect, zNear, zFar);
var eye = [1, 2, 3];
var target = [4, 5, 6];
var up = [0, 1, 0];
var camera = m.lookAt(eye, target, up);
var view = m.inverse(camera);
var viewProjection = m.multiply(projection, view);
var inverseViewProjection = m.inverse(viewProjection);
function getFrustumPoints(fieldOfView, aspect, zNear, zFar) {
var f = 1 / Math.tan(fieldOfView / 2);
var nearY = zNear / f;
var nearX = nearY * aspect;
var farY = zFar / f;
var farX = farY * aspect;
return [
[-nearX, -nearY, -zNear],
[ nearX, -nearY, -zNear],
[-nearX, nearY, -zNear],
[ nearX, nearY, -zNear],
[-farX, -farY, -zFar],
[ farX, -farY, -zFar],
[-farX, farY, -zFar],
[ farX, farY, -zFar],
];
}
function projectScreenPoint(width, height, projection, point) {
var c = m.transformPoint(projection, point);
return [
(c[0] * 0.5 + 0.5) * width,
(c[1] * 0.5 + 0.5) * height,
c[2],
];
}
function unproject(width, height, inverseViewProjection, p) {
return m.transformPoint(
inverseViewProjection,
[
p[0] / width * 2 - 1,
p[1] / height * 2 - 1,
p[2],
]);
}
function showPoints(label, points) {
log(label);
points.forEach((p, ndx) => log(ndx, p[0].toFixed(3), p[1].toFixed(3), p[2].toFixed(3)));
}
var frustumPoints = getFrustumPoints(fieldOfView, aspect, zNear, zFar);
showPoints("frustum points", frustumPoints);
var cameraFrustumPoints = frustumPoints.map(
p => m.transformPoint(camera, p));
showPoints("camerafrustum points", cameraFrustumPoints);
var screenPoints = cameraFrustumPoints.map(
p => projectScreenPoint(width, height, viewProjection, p));
showPoints("screen points (should match width, height)", screenPoints);
var unprojectedPoints = screenPoints.map(
p => unproject(width, height, inverseViewProjection, p));
showPoints("unprojected (should match cameraFrustum points)",
unprojectedPoints);
function log(...args) {
var elem = document.createElement("pre");
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
pre { margin: 0 };
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.2/gl-matrix-min.js"></script>
As for your example you're passing on 0 for z which is somewhere in the middle of the frustum depth wise. Also I see code like this
transformedVector(1)/transformedVector(4)
I don't know your math library but mine would be zero based indices so
transformedVector(0)/transformedVector(3)
Here's the code you added to your example. It's working for me. I filled in the missing math functions.
const m4 = twgl.m4;
class Vector3 {
constructor(x, y, z) {
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
}
sub(v) {
return new Vector3(this.x - v.x, this.y - v.y, this.z - v.z);
}
cross(v) {
return new Vector3(
this.y * v.z - this.z * v.y,
this.z * v.x - this.x * v.z,
this.x * v.y - this.y * v.x);
}
dot(v) {
return (this.x * v.x) + (this.y * v.y) + (this.z * v.z);
}
normalize() {
var lenSq = this.x * this.x + this.y * this.y + this.z * this.z;
var len = Math.sqrt(lenSq);
if (len > 0.00001) {
return new Vector3(this.x / len, this.y / len, this.z / len);
} else {
return new Vector3();
}
}
}
class Vector4 {
constructor(x, y, z, w) {
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
this.w = w || 0;
}
}
class Matrix4 {
constructor(components) {
this.components = components || m4.identity();
}
multiply(m) {
return new Matrix4(m4.multiply(m.components, this.components));
}
getInverse() {
return new Matrix4(m4.inverse(this.components));
}
multiplyByVector4(v) {
const m = this.components;
const x = v.x * m[0 * 4 + 0] + v.y * m[1 * 4 + 0] + v.z * m[2 * 4 + 0] + v.w * m[3 * 4 + 0];
const y = v.x * m[0 * 4 + 1] + v.y * m[1 * 4 + 1] + v.z * m[2 * 4 + 1] + v.w * m[3 * 4 + 1];
const z = v.x * m[0 * 4 + 2] + v.y * m[1 * 4 + 2] + v.z * m[2 * 4 + 2] + v.w * m[3 * 4 + 2];
const w = v.x * m[0 * 4 + 3] + v.y * m[1 * 4 + 3] + v.z * m[2 * 4 + 3] + v.w * m[3 * 4 + 3];
return new Vector4(x, y, z, w);
}
transformPoint(v) {
const v4 = this.multiplyByVector4(new Vector4(v.x, v.y, v.z, 1));
return new Vector3(v4.x / v4.w, v4.y / v4.w, v4.z / v4.w);
}
lookAt(eye, target, up) {
var eye = eye || new ALMath.Vector3();
var up = up || new ALMath.Vector3();
var target = target || new ALMath.Vector3();
var c = this.components;
var z = target.sub(eye);
z = z.normalize();
var x = z.cross(up);
x = x.normalize();
var y = x.cross(z);
y = y.normalize();
c[0] = x.x; c[1] = x.y; c[2] = x.z;
c[4] = y.x; c[5] = y.y; c[6] = y.z;
c[8] = -z.x; c[9] = -z.y; c[10] = -z.z;
c[12] = -x.dot(eye); c[13] = -y.dot(eye); c[14] = z.dot(eye);
return this;
}
perspectiveProjection( fov, aspect, zNear, zFar ) {
var a = aspect;
var tan=Math.tan(ALMath.degToRad(0.5*fov)),
A=-(zFar+zNear)/(zFar-zNear),
B=(-2*zFar*zNear)/(zFar-zNear);
var c = this.components;
c[ 0 ] = 0.5/tan; c[ 4 ] = 0; c[ 8 ] = 0; c[ 12 ] = 0;
c[ 1 ] = 0; c[ 5 ] = (0.5*a/tan); c[ 9 ] = 0; c[ 13 ] = 0;
c[ 2 ] = 0; c[ 6 ] = 0; c[ 10 ] = A; c[ 14 ] = B;
c[ 3 ] = 0; c[ 7 ] = 0; c[ 11 ] =-1; c[ 15 ] = 0;
return this;
}
}
class PerspectiveCamera {
constructor(fieldOfViewDegrees, aspect, zNear, zFar) {
this.fieldOfViewDegrees = fieldOfViewDegrees || 45;
this.aspect = aspect || 1;
this.zNear = zNear || 0.5;
this.zFar = zFar || 100;
this.projectionMatrix = new Matrix4();
this.viewMatrix = new Matrix4();
this.updateProjection();
}
updateProjection() {
this.projectionMatrix.perspectiveProjection(
this.fieldOfViewDegrees, this.aspect, this.zNear, this.zFar);
}
lookAt(eye, target, up) {
//this.viewMatrix.lookAt(eye, target, up);
this.cameraMatrix = this.viewMatrix.getInverse();
}
transformPoint(v) {
// note this tranasforms by the camera matrix
// (which is the inverse view matrix)
// and not the perspective matrix
return this.cameraMatrix.transformPoint(v);
}
}
const ALMath = {
Vector3: Vector3,
Matrix4: Matrix4,
degToRad: d => d * Math.PI / 180,
};
const AL3D = {
width: 300,
height: 150,
PerspectiveCamera: PerspectiveCamera,
};
const camera = new AL3D.PerspectiveCamera(40, AL3D.width/AL3D.height);
camera.lookAt(
new ALMath.Vector3(),
new ALMath.Vector3(0,-0.5,-2),
new ALMath.Vector3(0,1,0));
function getFrustumPoints(fieldOfView, aspect, zNear, zFar) {
var f = 1 / Math.tan(ALMath.degToRad(fieldOfView) / 2);
var nearY = zNear / f;
var nearX = nearY * aspect;
var farY = zFar / f;
var farX = farY * aspect;
return [
new ALMath.Vector3(-nearX, -nearY, -zNear),
new ALMath.Vector3( nearX, -nearY, -zNear),
new ALMath.Vector3(-nearX, nearY, -zNear),
new ALMath.Vector3( nearX, nearY, -zNear),
new ALMath.Vector3(-farX, -farY, -zFar),
new ALMath.Vector3( farX, -farY, -zFar),
new ALMath.Vector3(-farX, farY, -zFar),
new ALMath.Vector3( farX, farY, -zFar),
];
}
const projectionMatrix = camera.projectionMatrix;
const viewMatrix = camera.viewMatrix;
const viewProjection = viewMatrix.multiply(projectionMatrix);
const inverseViewProjection = viewProjection.getInverse();
function projectScreenPoint(width, height, projection, point) {
var c = projectionMatrix.transformPoint(point);
return new ALMath.Vector3(
(c.x * 0.5 + 0.5) * width,
(c.y * 0.5 + 0.5) * height,
c.z);
}
function unproject(width, height, inverseViewProjection, p) {
return inverseViewProjection.transformPoint(new ALMath.Vector3(
p.x / width * 2 - 1,
p.y / height * 2 - 1,
p.z));
}
function showPoints(label, points) {
log(label);
points.forEach((p, ndx) => log(ndx, p.x.toFixed(3), p.y.toFixed(3), p.z.toFixed(3)));
}
var frustumPoints = getFrustumPoints(camera.fieldOfViewDegrees, camera.aspect, camera.zNear, camera.zFar);
showPoints("frustum points", frustumPoints);
var cameraFrustumPoints = frustumPoints.map(
p => camera.transformPoint(p));
showPoints("camerafrustum points", cameraFrustumPoints);
var screenPoints = cameraFrustumPoints.map(
p => projectScreenPoint(AL3D.width, AL3D.height, viewProjection, p));
showPoints("screen points (should match width, height)", screenPoints);
var unprojectedPoints = screenPoints.map(
p => unproject(AL3D.width, AL3D.height, inverseViewProjection, p));
showPoints("unprojected (should match cameraFrustum points)",
unprojectedPoints);
function log(...args) {
var elem = document.createElement("pre");
elem.textContent = [...args].join(' ');
document.body.appendChild(elem);
}
pre { margin: 0; }
<script src="https://twgljs.org/dist/2.x/twgl-full.min.js"></script>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With