I am trying to convert my C# XNA project to WebGL using Three.js.
So far its going very well, but I am currently working on my camera. With XNA you can easily send the view matrix to the shader.
I'm sure I'm just not seeing what I am supposed to be seeing in the documentation.
My CameraManager class has a MouseMoved event handler.
CameraManager.prototype.MouseMoved = function( changeVector ) {
var q1 = new THREE.Quaternion();
q1.setFromAxisAngle(this.left, (( Math.PI / 4 ) / 200) * changeVector.y);
var q2 = new THREE.Quaternion();
q2.setFromAxisAngle(this.left, (( -Math.PI / 4 ) / 200) * changeVector.x);
var q = new THREE.Quaternion();
q.multiply(q1, q2);
q.multiplyVector3(this.direction);
q.multiplyVector3(this.left);
}
The CameraManager also has an update method that updates the View matrix.
CameraManager.prototype.CreateLookAt = function() {
var target = this.position.clone();
target.addSelf(this.direction);
this.view = THREE.Matrix4.CreateLookAt(this.position, target, this.up);
}
THREE.Matrix4.CreateLookAt = function(cameraPosition, cameraTarget, upVector) {
var zAxis = new THREE.Vector3();
zAxis.sub(cameraPosition, cameraTarget);
zAxis.normalize();
var xAxis = new THREE.Vector3();
xAxis.cross(upVector, zAxis);
xAxis.normalize();
var yAxis = new THREE.Vector3();
yAxis.cross(zAxis, xAxis);
return new THREE.Matrix4(
xAxis.x, yAxis.x, zAxis.x, 0,
xAxis.y, yAxis.y, zAxis.y, 0,
xAxis.z, yAxis.z, zAxis.z, 0,
-xAxis.dot(cameraPosition), -yAxis.dot(cameraPosition), -zAxis.dot(cameraPosition), l
);
}
Just wondering how I can set the view matrix for the camera.
Thanks!
Why dont you just set your matrix properties by apart like so:
var m = getYourNewMatrix();
camera.position = m.decompose()[ 0 ];
camera.rotation = m.decompose()[ 1 ];
camera.updateMatrix();
Related
I'm trying to implement instancing in Aframe using the ThreeJs InstancedMesh based on the example here: https://github.com/mrdoob/three.js/blob/master/examples/webgl_instancing_dynamic.html
Relevant section of code here:
init: function() {
const {count, radius, scale, colors, positions} = this.data;
this.start = true;
this.dummy = new THREE.Object3D();
this.count = count;
this.startObject = new THREE.Object3D();
this.endObject = new THREE.Object3D();
this.instanceColors = new Float32Array(count * 3);
this.instanceColorsBase = new Float32Array(this.instanceColors.length);
this.vertices = [];
this.rotations = [];
for ( var i = 0; i < this.data.count; i ++ ) {
var x = this.data.positions[i][0] * this.data.scale;
var y = this.data.positions[i][1] * this.data.scale;
var z = this.data.positions[i][2] * this.data.scale;
var xEnd = x + this.data.endPositions[i][0] * this.data.scale;
var yEnd = y + this.data.endPositions[i][1] * this.data.scale;
var zEnd = z + this.data.endPositions[i][2] * this.data.scale;
this.vertices.push( x, y, z );
const rotation = this.getDirection({'x':x,'y':y,'z':z},
{'x':xEnd,'y':yEnd,'z':zEnd});
this.rotations.push(rotation.x, rotation.y, rotation.z);
}
let mesh;
let geometry;
let material;
const loader = new THREE.GLTFLoader();
const el = this.el;
loader.load("/assets/arrow/arrow.gltf", function ( model ) {
geometry = model.scene.children[0].children[0].geometry;
geometry.computeVertexNormals();
geometry.scale( 0.03, 0.03, 0.03 );
material = new THREE.MeshNormalMaterial();
mesh = new THREE.InstancedMesh( geometry, material, count );
mesh.instanceMatrix.setUsage( THREE.DynamicDrawUsage );
el.object3D.add(mesh);
} );
this.el.setAttribute("id", "cells");
},
setMatrix: function (start) {
if (this.mesh) {
for ( let i = 0; i < this.count; i ++ ) {
var x = this.data.positions[i][0] * this.data.scale;
var y = this.data.positions[i][1] * this.data.scale;
var z = this.data.positions[i][2] * this.data.scale;
var xEnd = x + this.data.endPositions[i][0] * this.data.scale;
var yEnd = y + this.data.endPositions[i][1] * this.data.scale;
var zEnd = z + this.data.endPositions[i][2] * this.data.scale;
if (start) {
this.dummy.position.set(xEnd, yEnd, zEnd);
} else {
this.dummy.position.set(x, y, z);
}
this.dummy.rotation.x = this.rotations[i][0];
this.dummy.rotation.y = this.rotations[i][1];
this.dummy.rotation.z = this.rotations[i][2];
this.dummy.updateMatrix();
this.mesh.setMatrixAt( i, this.dummy.matrix );
}
this.mesh.instanceMatrix.needsUpdate = true;
}
}
tick: function() {
this.setMatrix(this.start);
this.start = !this.start;
},
No errors or relevant messages that I can see, but none of the instanced objects are rendering. I don't really have a good way to post an example unfortunately. Anyone know what I'm doing wrong? Thanks in advance!
Note: It seems that the objects are being rendered because the number of triangles being drawn increases drastically when I add this component. However, they are not visible anywhere and I can't find them in the aframe inspector either
It's a very case specific question with a quite extensive topic, so:
In general, using THREE.InstancedMeshes is simple, and you got it right:
// create an instanced mesh
let iMesh = new THREE.InstancedMesh(geometry, material, count)
element.object3D.add(iMesh)
// manipulate the instances
let mtx = new Matrix4()
// set the position, rotation, scale of the matrix
// ...
// update the instance
iMesh.setMatrixAt(index, mtx);
iMesh.instanceMatrix.needsUpdate = true;
Example of an instanced gltf model here
Your code is doing a lot, and it would be easier if it could be stripped to a bare minimum. Yet I think there is only one major issue - this.model isn't set anywhere, so the setMatrix function does nothing. Other than that you may need to disable frustum culling (mesh.frustumCulling = false), or set a bounding sphere - otherwise the objects may dissapear when the base object is out of sight.
Once it's set, your code seems to be working
I am able to display a THREE.TubeGeometry figure as follows
Code below, link to jsbin
<html>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r75/three.js"></script>
<script>
// global variables
var renderer;
var scene;
var camera;
var geometry;
var control;
var count = 0;
var animationTracker;
init();
drawSpline();
function init()
{
// create a scene, that will hold all our elements such as objects, cameras and lights.
scene = new THREE.Scene();
// create a camera, which defines where we're looking at.
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// create a render, sets the background color and the size
renderer = new THREE.WebGLRenderer();
renderer.setClearColor('lightgray', 1.0);
renderer.setSize(window.innerWidth, window.innerHeight);
// position and point the camera to the center of the scene
camera.position.x = 0;
camera.position.y = 40;
camera.position.z = 40;
camera.lookAt(scene.position);
// add the output of the renderer to the html element
document.body.appendChild(renderer.domElement);
}
function drawSpline(numPoints)
{
var numPoints = 100;
// var start = new THREE.Vector3(-5, 0, 20);
var start = new THREE.Vector3(-5, 0, 20);
var middle = new THREE.Vector3(0, 35, 0);
var end = new THREE.Vector3(5, 0, -20);
var curveQuad = new THREE.QuadraticBezierCurve3(start, middle, end);
var tube = new THREE.TubeGeometry(curveQuad, numPoints, 0.5, 20, false);
var mesh = new THREE.Mesh(tube, new THREE.MeshNormalMaterial({
opacity: 0.9,
transparent: true
}));
scene.add(mesh);
renderer.render(scene, camera);
}
</script>
</body>
</html>
However, I would like to display incrementally, as in, like an arc that is loading, such that it starts as the start point, draws incrementally and finally looks the below arc upon completion.
I have been putting in some effort, and was able to do this by storing all the points/coordinates covered by the arc, and drawing lines between the consecutive coordinates, such that I get the 'arc loading incrementally' feel. However, is there a better way to achieve this? This is the link to jsbin
Adding the code here as well
<!DOCTYPE html>
<html>
<head>
<title>Incremental Spline Curve</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r75/three.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<script>
// global variables
var renderer;
var scene;
var camera;
var splineGeometry;
var control;
var count = 0;
var animationTracker;
// var sphereCamera;
var sphere;
var light;
function init() {
// create a scene, that will hold all our elements such as objects, cameras and lights.
scene = new THREE.Scene();
// create a camera, which defines where we're looking at.
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
// create a render, sets the background color and the size
renderer = new THREE.WebGLRenderer();
// renderer.setClearColor(0x000000, 1.0);
renderer.setClearColor( 0xffffff, 1 );
renderer.setSize(window.innerWidth, window.innerHeight);
// position and point the camera to the center of the scene
camera.position.x = 0;
camera.position.y = 40;
camera.position.z = 40;
camera.lookAt(scene.position);
// add the output of the renderer to the html element
document.body.appendChild(renderer.domElement);
// //init for sphere
// sphereCamera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
// sphereCamera.position.y = -400;
// sphereCamera.position.z = 400;
// sphereCamera.rotation.x = .70;
sphere = new THREE.Mesh(new THREE.SphereGeometry(0.8,31,31), new THREE.MeshLambertMaterial({
color: 'yellow',
}));
light = new THREE.DirectionalLight('white', 1);
// light.position.set(0,-400,400).normalize();
light.position.set(0,10,10).normalize();
//get points covered by Spline
getSplineData();
}
//save points in geometry.vertices
function getSplineData() {
var curve = new THREE.CubicBezierCurve3(
new THREE.Vector3( -5, 0, 10 ),
new THREE.Vector3(0, 20, 0 ),
new THREE.Vector3(0, 20, 0 ),
new THREE.Vector3( 2, 0, -25 )
);
splineGeometry = new THREE.Geometry();
splineGeometry.vertices = curve.getPoints( 50 );
animate();
}
//scheduler loop
function animate() {
if(count == 50)
{
cancelAnimationFrame(animationTracker);
return;
}
//add line to the scene
drawLine();
renderer.render(scene, camera);
// renderer.render(scene, sphereCamera);
count += 1;
// camera.position.z -= 0.25;
// camera.position.y -= 0.25;
animationTracker = requestAnimationFrame(animate);
}
function drawLine() {
var lineGeometry = new THREE.Geometry();
var lineMaterial = new THREE.LineBasicMaterial({
color: 0x0000ff
});
console.log(splineGeometry.vertices[count]);
console.log(splineGeometry.vertices[count+1]);
lineGeometry.vertices.push(
splineGeometry.vertices[count],
splineGeometry.vertices[count+1]
);
var line = new THREE.Line( lineGeometry, lineMaterial );
scene.add( line );
}
// calls the init function when the window is done loading.
window.onload = init;
</script>
<body>
</body>
</html>
Drawback : The drawback of doing it the above way is that, end of the day, I'm drawing a line between consecutive points, and so I lose out on a lot of the effects possible in TubeGeometry such as, thickness, transparency etc.
Please suggest me an alternative way to get a smooth incremental load for the TubeGeometry.
THREE.TubeGeometry returns a THREE.BufferGeometry.
With THREE.BufferGeometry, you have access to a property drawRange that you can set to animate the drawing of the mesh:
let nEnd = 0, nMax, nStep = 90; // 30 faces * 3 vertices/face
...
const geometry = new THREE.TubeGeometry( path, pathSegments, tubeRadius, radiusSegments, closed );
nMax = geometry.attributes.position.count;
...
function animate() {
requestAnimationFrame( animate );
nEnd = ( nEnd + nStep ) % nMax;
mesh.geometry.setDrawRange( 0, nEnd );
renderer.render( scene, camera );
}
EDIT: For another approach, see this SO answer.
three.js r.144
Normally you would be able to use the method .getPointAt() to "get a vector for point at relative position in curve according to arc length" to get a point at a certain percentage of the length of the curve.
So normally if you want to draw 70% of the curve and a full curve is drawn in 100 segments. Then you could do:
var percentage = 70;
var curvePath = new THREE.CurvePath();
var end, start = curveQuad.getPointAt( 0 );
for(var i = 1; i < percentage; i++){
end = curveQuad.getPointAt( percentage / 100 );
lineCurve = new THREE.LineCurve( start, end );
curvePath.add( lineCurve );
start = end;
}
But I think this is not working for your curveQuad since the getPointAt method is not implemented for this type. A work around is to get a 100 points for your curve in an array like this:
points = curve.getPoints(100);
And then you can do almost the same:
var percentage = 70;
var curvePath = new THREE.CurvePath();
var end, start = points[ 0 ];
for(var i = 1; i < percentage; i++){
end = points[ percentage ]
lineCurve = new THREE.LineCurve( start, end );
curvePath.add( lineCurve );
start = end;
}
now your curvePath holds the line segments you want to use for drawing the tube:
// draw the geometry
var radius = 5, radiusSegments = 8, closed = false;
var geometry = new THREE.TubeGeometry(curvePath, percentage, radius, radiusSegments, closed);
Here a fiddle with a demonstration on how to use this dynamically
I'm not really that familiar with three.js. But I think I can be of assistance. I have two solutions for you. Both based on the same principle: build a new TubeGeometry or rebuild the current one, around a new curve.
Solution 1 (Simple):
var CurveSection = THREE.Curve.create(function(base, from, to) {
this.base = base;
this.from = from;
this.to = to;
}, function(t) {
return this.base.getPoint((1 - t) * this.from + t * this.to);
});
You define a new type of curve which just selects a segment out of a given curve. Usage:
var curve = new CurveSection(yourCurve, 0, .76); // Where .76 is your percentage
Now you can build a new tube.
Solution 2 (Mathematics!):
You are using for your arc a quadratic bezier curve, that's awesome! This curve is a parabola. You want just a segment of that parabola and that is again a parabola, just with other bounds.
What we need is a section of the bezier curve. Let's say the curve is defined by A (start), B (direction), C (end). If we want to change the start to a point D and the end to a point F we need the point E that is the direction of the curve in D and F. So the tangents to our parabola in D and F have to intersect in E. So the following code will give us the desired result:
// Calculates the instersection point of Line3 l1 and Line3 l2.
function intersection(l1, l2) {
var A = l1.start;
var P = l2.closestPointToPoint(A);
var Q = l1.closestPointToPoint(P);
var l = P.distanceToSquared(A) / Q.distanceTo(A);
var d = (new THREE.Vector3()).subVectors(Q, A);
return d.multiplyScalar(l / d.length()).add(A);
}
// Calculate the tangentVector of the bezier-curve
function tangentQuadraticBezier(bezier, t) {
var s = bezier.v0,
m = bezier.v1,
e = bezier.v2;
return new THREE.Vector3(
THREE.CurveUtils.tangentQuadraticBezier(t, s.x, m.x, e.x),
THREE.CurveUtils.tangentQuadraticBezier(t, s.y, m.y, e.y),
THREE.CurveUtils.tangentQuadraticBezier(t, s.z, m.z, e.z)
);
}
// Returns a new QuadraticBezierCurve3 with the new bounds.
function sectionInQuadraticBezier(bezier, from, to) {
var s = bezier.v0,
m = bezier.v1,
e = bezier.v2;
var ns = bezier.getPoint(from),
ne = bezier.getPoint(to);
var nm = intersection(
new THREE.Line3(ns, tangentQuadraticBezier(bezier, from).add(ns)),
new THREE.Line3(ne, tangentQuadraticBezier(bezier, to).add(ne))
);
return new THREE.QuadraticBezierCurve3(ns, nm, ne);
}
This is a very mathematical way, but if you should need the special properties of a Bezier curve, this is the way to go.
Note: The first solution is the simplest. I am not familiar with Three.js so I wouldn't know what the most efficient way to implement the animation is. Three.js doesn't seem to use the special properties of a bezier curve so maybe solution 2 isn't that useful.
I hope you have gotten something useful out of this.
I am working with the threex domevents by Jeromeetienne: https://github.com/jeromeetienne/threex.domevents to handle my hover over of rendered in objects in my canvas
I am having some issue when updating the perspective view when the user changes the type of object, my dom sets the event listener but it seems to never get called. Here is the overview:
When user first loads the page, we set our camera:
// setup local vars
var camera = new THREE.PerspectiveCamera(40,window.innerWidth / window.innerHeight,1,10000);
camera.position.set(500, 800, 1300);// 1st = swing (left or right) 2nd = up or down 3rd = zoom
camera.lookAt( new THREE.Vector3());
camera.positionID = 1;// default
camera.positionType = 'sideView';// default
this.cam = camera;
Than we set our renderer:
renderer = new THREE.CanvasRenderer();
renderer.setClearColor( 0xf0f0f0 );
renderer.setSize( window.innerWidth, window.innerHeight );
Now when a user clicks the canvas, an object is added. We first get our positioning and call a intersectofobjects to see if we are actually on the canvas (mousedown):
event.preventDefault();
this.mouse.x = ( event.clientX / this.renderer.domElement.width ) * 2 - 1;
this.mouse.y = - ( event.clientY / this.renderer.domElement.height ) * 2 + 1;
this.raycaster.setFromCamera( this.mouse, this.camera.cam );
var intersects = this.raycaster.intersectObjects( this.blocks );
After we actually figure out that we are on the plane, we setup our object:
var voxel = new THREE.Mesh( this.room.cubeGeometry.clone(), this.room.cubeMaterial.clone() );
voxel.geometry.computeBoundingBox();
voxel.position.copy( intersect.point ).add( intersect.face.normal );
voxel.position.divideScalar( this.room.divideScalar ).floor().multiplyScalar( this.room.multiplyScalar ).addScalar( this.room.addScalar );
After we setup our object we setup the event listeners on the object
// get our dom event to attach to object
var domEvents = new THREEx.DomEvents(this.camera.cam, this.renderer.domElement);
// DOM events for inside 3d rendering
domEvents.addEventListener(voxel, 'mouseover', this.onDocumentMouseOverCube.bind(this),false);
domEvents.addEventListener(voxel, 'mouseout', this.onDocumentMouseOutCube.bind(this),false);
this.scene.add( voxel );
this.blocks.push( voxel );
this.rooms.push(voxel);
this.render();
This works perfect on first use. It only seems to stop working when the user changes the object that they want to place on the screen, here is how.
We have a gui object that handles our connection with the UI and the core, when someone chooses to add a different object, we call:
jQuery(".changeRoomType.active").removeClass('active');
jQuery(event.srcElement).toggleClass('active');
this.canvas.room.updateRoomType(event.srcElement.id);
Now when room.updateRoomType...() is called it initiates an object like this:
var cubeGeometry = new THREE.BoxGeometry( 100, 50, 100,2,1,2 );// moving to roomObj
var cubeMaterial = new THREE.MeshLambertMaterial( { color: 0x00ff80,overdraw: 1 } ); // moving to roomObj
var cubeTmpHoverMaterial = new THREE.MeshLambertMaterial({ color: 0x00ff80, transparent: true, overdraw: 0, opacity:0.5});// used to give a transparency to the cube tmp view // moving to roomObj
var divideScalar = 50;
var multiplyScalar = 50;
var addScalar = 25;
var additionalZ = 25;
var additionalX = 25;
this.divideScalar = divideScalar;
this.multiplyScalar = multiplyScalar;
this.addScalar = addScalar;
var colorNormal = 0x00ff80;
var colorHover = 0xE4E4E4;
var colorTmpHover = 0xEAEAEA;
var colorRemove = 0xE23434;
this.colorNormal = colorNormal;
this.colorHover = colorHover;
this.colorTmpHover = colorTmpHover;
this.colorRemove = colorRemove;
this.additionalZ = additionalZ;
this.additionalX = additionalX;
this.cubeGeometry = cubeGeometry;
this.cubeMaterial = cubeMaterial;
this.cubeTmpHoverMaterial = cubeTmpHoverMaterial;
Now after this point the mouseover and mouseout on the object seem to never work anymore. My question is why is this? Am I not updating my camera perspective after changing the object type to be displayed?
EDIT:
Please note that our initiated object that is first selected on page load is the following:
var cubeGeometry = new THREE.BoxGeometry( 50, 50, 50,1,1,1 );// moving to roomObj
var cubeMaterial = new THREE.MeshLambertMaterial( { color: 0x00ff80, overdraw: 1 } ); // moving to roomObj
var cubeTmpHoverMaterial = new THREE.MeshLambertMaterial({ color: 0x00ff80, transparent: true, overdraw: 0,opacity:0.5});// used to give a transparency to the cube tmp view // moving to roomObj
var divideScalar = 50;
var multiplyScalar = 50;
var addScalar = 25;
var additionalZ = 0;
var additionalX = 0;
this.additionalZ = additionalZ;
this.additionalX = additionalX;
var colorNormal = 0x00ff80;
var colorHover = 0xE4E4E4;
var colorTmpHover = 0xEAEAEA;
var colorRemove = 0xE23434;
this.colorNormal = colorNormal;
this.colorHover = colorHover;
this.colorTmpHover = colorTmpHover;
this.colorRemove = colorRemove;
this.cubeGeometry = cubeGeometry;
this.cubeMaterial = cubeMaterial;
this.cubeTmpHoverMaterial = cubeTmpHoverMaterial;
The other object that was called above shown in the original question demonstrates our other object size. After this is called it seems to stop recognizing the location of our mouseover and mouseout on the object and or its being placed on someplace else it should not be.
I am working on a project in three.js, where the user can change dynamically the dimension of the objects inside it. The objects are two boxes with different dimensions and one box, should be always located on top of the other one.
For instance, if the height of cube is 10, chimney.position.y should be 10. I tried different possibilities, but it is not working.
Here is a snippet of my code:
var cubeHeight = 0.1;
var boxGeometry = new THREE.BoxGeometry(0.1, cubeHeight, 0.1);
var boxMaterial = new THREE.MeshLambertMaterial({color: 0x000088});
cube = new THREE.Mesh(boxGeometry, boxMaterial);
cube.position.set(0, cubeHeight/2, 0);
scene.add(cube);
var chimneyGeometry = new THREE.BoxGeometry(5, 0.1, 5);
var chimneyMaterial = new THREE.MeshLambertMaterial({color: 0x000088});
chimney = new THREE.Mesh(chimneyGeometry, chimneyMaterial);
chimney.position.set(0,cubeHeight,-12.5);
scene.add(chimney);
// Now the GUI panel for the interaction is defined
gui = new dat.GUI();
parameters = {
length: 1, height: 1, width: 1,
tall: 1
}
// Define the second folder which takes care of the scaling of the cube
var folder1 = gui.addFolder("House dimensions (cm)");
var cubeX = folder1.add(parameters,"length").min(1).max(2000).step(1).listen();
var cubeY = folder1.add(parameters, "height").min(1).max(2000).step(1).listen();
var cubeZ = folder1.add(parameters, "width").min(1).max(2000).step(1).listen();
var chimneyHeight = folder1.add(parameters, "tall").min(1).max(700).step(1).listen();
folder1.open();
// Function taking care of the cube changes
// cubeY has a different value to keep the solid fixed to the floor
cubeX.onChange(function(value){cube.scale.x = value; roof.scale.x = value;});
cubeY.onChange(function(value){cube.scale.y = value; cube.position.y = (cubeHeight * value) / 2;});
cubeZ.onChange(function(value){cube.scale.z = value;});
chimneyHeight.onChange(function(value){chimney.scale.y = value; chimney.position.y = ((cubeHeight * value) / 2) + cubeY;});
Do you have an idea of how to solve this issue? Thanks in advance for your answers!
Here is one solution:
cubeY.onChange(function(value){
cube.scale.y = value;
cube.position.y = (cubeHeight * value) / 2;
chimney.position.y = (chimneyHeight * chimney.scale.y) / 2 + cube.position.y * 2
});
chimneyY.onChange(function(value){
chimney.scale.y = value;
chimney.position.y = (chimneyHeight * value) / 2 + cube.position.y * 2;
});
and here is fiddle for it: http://jsfiddle.net/z1yd342b/
three.js r71
I need some help to deal with THREE.Frustum object.
My problem:
I need to calculate near/far plane vertices; I've taken a look at these tutorials
http://www.lighthouse3d.com/tutorials/view-frustum-culling/view-frustums-shape/
http://www.lighthouse3d.com/tutorials/view-frustum-culling/geometric-approach-extracting-the-planes/
and I've sketched this function implementing exactly (I hope so) the procedure explained (just to get top-left/right vertices, assuming the camera can only look left and right):
// Near Plane dimensions
hNear = 2 * Math.tan(camera.fov / 2) * camera.near; // height
wNear = hNear * camera.aspect; // width
// Far Plane dimensions
hFar = 2 * Math.tan(camera.fov / 2) * camera.far; // height
wFar = hFar * camera.aspect; // width
getVertices : function() {
var p = camera.position.clone();
var l = getCurrentTarget(); // see below
var u = new THREE.Vector3(0, 1, 0);
var d = new THREE.Vector3();
d.sub(l, p);
d.normalize();
var r = new THREE.Vector3();
r.cross(u, d);
r.normalize();
// Near Plane center
var dTmp = d.clone();
var nc = new THREE.Vector3();
nc.add(p, dTmp.multiplyScalar(camera.near));
// Near Plane top-right and top-left vertices
var uTmp = u.clone();
var rTmp = r.clone();
var ntr = new THREE.Vector3();
ntr.add(nc, uTmp.multiplyScalar(hNear / 2));
ntr.subSelf(rTmp.multiplyScalar(wNear / 2));
uTmp.copy(u);
rTmp.copy(r);
var ntl = new THREE.Vector3();
ntl.add(nc, uTmp.multiplyScalar(hNear / 2));
ntl.addSelf(rTmp.multiplyScalar(wNear / 2));
// Far Plane center
dTmp.copy(d);
var fc = new THREE.Vector3();
fc.add(p, dTmp.multiplyScalar(camera.far));
// Far Plane top-right and top-left vertices
uTmp.copy(u);
rTmp.copy(r);
var ftr = new THREE.Vector3();
ftr.add(fc, uTmp.multiplyScalar(hFar / 2));
ftr.subSelf(rTmp.multiplyScalar(wFar / 2));
uTmp.copy(u);
rTmp.copy(r);
var ftl = new THREE.Vector3();
ftl.add(fc, uTmp.multiplyScalar(hFar / 2));
ftl.addSelf(rTmp.multiplyScalar(wFar / 2));
getCurrentTarget : function() {
var l = new THREE.Vector3(0, 0, -100);
this.camera.updateMatrixWorld();
this.camera.matrixWorld.multiplyVector3(l);
return l;
}
This seems to work but...
My Question:
Can I obtain the same result in a more elegant (maybe more correct) way, using a THREE.Frustum object?
Three.Frustum is not really going to help you -- it is a set of planes. The good news is your solution appears correct, but there is an easier way to think about this.
The upper right corner of the near plane is a point in camera space with these coordinates:
var ntr = new THREE.Vector3( wNear / 2, hNear / 2, -camera.near );
using your definition of wNear and hNear, which are correct.
Now, making sure that camera.matrixWorld is updated, you convert that point to world coordinates like so:
camera.updateMatrixWorld();
ntr.applyMatrix4( camera.matrixWorld );
Now, flip the signs to get the other three corners, and then repeat the calculation for the far plane.
See, you had it right; you just took a more complicated route. :-)
EDIT: updated to three.js r.66