Is it possible to create a THREE.Box3 from a THREE.Fustum

frustum

#1

Hi.

I am trying to create a Box3 object from an existing Frustum object using its 6 defined planes but I don’t know how to do it.

Could anybody help me?

Best regards


#2

You can compute the corner points of the frustum and then use Box3.expandByPoint() in order to compute the AABB. The following thread at stackoverflow tells you more about the computation of the corner points:


#3

Hi Mugen.

Many thanks, I am on it.

I will keep you informed.

; )

Have a nice weekend.

Best regards


#4

Hi Mugen.

FInally I could dedicate some time to trying solve this issue but after some days I am still stuck.

To be frank with you, in the link to sent me, despites the title of the topic is related to Frustum and its geometry, the functions described are using camera settings to compute near and far planes and not using Frustum at all

I need to compute the vertices of the corners of my Frustum extracted from a Camera or from Clipping Planes of my WEBGLRenderer.

Searching in multiple forums I found some functions that I thought they could help me.

In fact the first 2 ones intersectPlanes and solveIntersectingPoint I think work very well.

My main problem is with Plane-Line intersection point . Even it seems that threejs has the object Line3 that supports this method, the problem is that Line3 is not really a Line but a LineSegment.
I found multiple functions in order to treat this kind of intersections and I think lineIntersection was the best one. I extracted it from stackoverflow. here the link:
https://stackoverflow.com/questions/5666222/3d-line-plane-intersection

The first 2 functions were extracted also from stackoverflow:
https://stackoverflow.com/questions/16025620/finding-the-line-along-the-intersection-of-two-planes

   function intersectPlanes(p1, p2) {
	var direction = new THREE.Vector3().crossVectors(p1.normal, p2.normal)
	var magnitude = direction.distanceTo(new THREE.Vector3(0, 0, 0))
	//console.log("magnitude:", magnitude);
	if (magnitude === 0) {
		return [null, null];
	}
	var X = Math.abs(direction.x)
	var Y = Math.abs(direction.y)
	var Z = Math.abs(direction.z)
	var point
	if (Z >= X && Z >= Y) {
		point = solveIntersectingPoint('z', 'x', 'y', p1, p2)
	} else if (Y >= Z && Y >= X){
		point = solveIntersectingPoint('y', 'z', 'x', p1, p2)
	} else {
		point = solveIntersectingPoint('x', 'y', 'z', p1, p2)
	}
	return [point, direction]
   }

   function solveIntersectingPoint(zeroCoord, A, B, p1, p2) {
	var a1 = p1.normal[A]
	var b1 = p1.normal[B]
	var d1 = p1.constant
	var a2 = p2.normal[A]
	var b2 = p2.normal[B]
	var d2 = p2.constant
	var A0 = ((b2 * d1) - (b1 * d2)) / ((a1 * b2 - a2 * b1))
	var B0 = ((a1 * d2) - (a2 * d1)) / ((a1 * b2 - a2 * b1))
	var point = new THREE.Vector3()
	point[zeroCoord] = 0
	point[A] = A0
	point[B] = B0
	return point
}

function lineIntersection(planePoint, planeNormal, linePoint, lineDirection)  {
	//ax × bx + ay × by
	var dot = Math.floor(planeNormal.x * lineDirection.x + planeNormal.y * lineDirection.y);
	//var dot = planeNormal.clone().dot(lineDirection);
	if (dot == 0) {
		return null;
	}
	// Ref for dot product calculation: https://www.mathsisfun.com/algebra/vectors-dot-product.html
	var dot2 = Math.floor(planeNormal.x * planePoint.x + planeNormal.y * planePoint.y);
	var dot3 = Math.floor(planeNormal.x * linePoint.x + planeNormal.y * linePoint.y);
	var dot4 = Math.floor(planeNormal.x * lineDirection.x + planeNormal.y * lineDirection.y);
	var t = (dot2 - dot3) / dot4;
	var xs = (lineDirection.x * t);
	var ys = (lineDirection.y * t);
	var zs = (lineDirection.z * t);
	var lineDirectionScale = new THREE.Vector3( xs, ys, zs);
	var xa = (linePoint.x + lineDirectionScale.x);
	var ya = (linePoint.y + lineDirectionScale.y);
	var za = (linePoint.z + lineDirectionScale.z);
	console.log(xa, ya, za);
	return new THREE.Vector3(xa, ya, za);
}

My idea was the following:

My Frustum Planes or my Clipping Planes follow this order:
0: -YZ Left
1: YZ Right
2: XY Top
3: -XY Bottom
4: -XZ Near
5: XZ Far

My idea was the following:

  • First compute Plane2Plane intersection between LEFT and BOTTOM planes, This should return a definition Line composed by a point and a direction --> Line1
  • Compute the Plane2Line intersection between Line1 and Plane Near. This should return the Box.min point
  • Compute Plane2Plane intersection between RIGHT and TOP planes, This should return a definition Line composed by a point and a direction --> Line2
  • Compute the Plane2Line intersection between Line2 and Plane Far. This should return the Box.max point

In order to obtain a valid point of my Planes I used .coplanarPoint method.

The problem is that there is something wrong in these functions that I could not find.

Here an extract of my logic:
aCamera.updateMatrix();
aCamera.updateMatrixWorld();
aCamera.matrixWorldInverse.getInverse( aCamera.matrixWorld );
var frustum = new THREE.Frustum();
frustum.setFromMatrix( new THREE.Matrix4().multiplyMatrices( aCamera.projectionMatrix, aCamera.matrixWorldInverse ) );
var [linePoint1, lineDirection1] = intersectPlanes(frustum.planes[0], frustum.planes[3]);
var [linePoint2, lineDirection2] = intersectPlanes(frustum.planes[1], frustum.planes[2]);
var boxMin = lineIntersection(planePoint4, frustum.planes[4].normal, linePoint1, lineDirection1);
var boxMax = lineIntersection(planePoint5, frustum.planes[5].normal, linePoint2, lineDirection2);

The problem is that the box is always very big. I am sure I am missing something but no idea about what.

Best regards


#5

Only checking for LEFT and BOTTOM and RIGHT and TOP planes might be insufficient if the planes are not axis aligned and they have normals asymmetrical to each other.

I tried this algorithm to get the corner points of a frustum.


// takes two planes and returns a Line3 which represents their intersection
// use len to specify how long the line should be
function intersectPlanes(pl1, pl2, len=100000) {
  
  let EXTENSION = 2000000;
  
  let cp1 = pl1.normal.clone();
  cp1.normalize();
  cp1.negate();
  cp1.multiplyScalar(pl1.constant);
  
  let cp2 = pl2.normal.clone();
  cp2.normalize();
  cp2.negate();
  cp2.multiplyScalar(pl2.constant);
  
  // calculate cross product to derive direction of intersection line
  let crPl1 = pl1.normal.clone();
  let crPl2 = pl2.normal.clone();
  let cr = crPl1.cross(crPl2);
  cr.normalize();
  
  let dtPl1 = pl1.normal.clone();
  let dtPl2 = pl2.normal.clone();
  let dot = dtPl1.dot(dtPl2);
  let ang = Math.acos(dot) * (180/Math.PI);
  // return null if planes are identical or parallel
  if (ang == 0 || ang == 180) return null;
  // project point directly if planes are perpendicular
  if (ang == 90 || ang == 270) {
    let intPt = new THREE.Vector3();
    pl2.projectPoint(cp1, intPt);
    let intA = intPt.clone();
    let intB = intPt.clone();
    intA.addScaledVector(cr, len/2);
    intB.addScaledVector(cr, -len/2);

    let intLn = new THREE.Line3(intA, intB);
    return intLn;
  }
  
  // project origin of first plane on to second plane
  let projLnAStart = new THREE.Vector3(cp1.x, cp1.y, cp1.z).normalize();
  projLnAStart.multiplyScalar(EXTENSION);
  let projLnAEnd = new THREE.Vector3(-projLnAStart.x, -projLnAStart.y, -projLnAStart.z);
  let int1 = new THREE.Vector3();
  let projLnA = new THREE.Line3(projLnAStart, projLnAEnd);
  pl2.intersectLine(projLnA, int1);

  // project the intersection point back to first plane along the second plane
  let projVcB = new THREE.Vector3();
  projVcB.subVectors( int1, cp2 ).normalize();
  projVcB.multiplyScalar(EXTENSION);
  let projLnBStart = cp2.clone();
  projLnBStart.add(projVcB);
  let projLnBEnd = cp2.clone();
  projLnBEnd.add(projVcB.negate());
  let int2 = new THREE.Vector3();
  let projLnB = new THREE.Line3(projLnBStart, projLnBEnd);
  pl1.intersectLine(projLnB, int2);
  
  // use cross vector and intersecttion point to create line
  let intA = int2.clone();
  let intB = int2.clone();
  intA.addScaledVector(cr, len/2);
  intB.addScaledVector(cr, -len/2);
  
  let intLn = new THREE.Line3(intA, intB);
  return intLn;
  
}


// -------------


function getCorners(frus) {
  
  let lnLT = intersectPlanes(frus.planes[0], frus.planes[1]);
  let lnRT = intersectPlanes(frus.planes[1], frus.planes[2]);
  let lnRB = intersectPlanes(frus.planes[2], frus.planes[3]);
  let lnLB = intersectPlanes(frus.planes[3], frus.planes[0]);
  
  
  
  let int;
  
  int = new THREE.Vector3();
  frus.planes[4].intersectLine(lnLT, int);
  let ptLTN = int.clone();
  int = new THREE.Vector3();
  frus.planes[5].intersectLine(lnLT, int);
  let ptLTF = int.clone();
  
  int = new THREE.Vector3();
  frus.planes[4].intersectLine(lnRT, int);
  let ptRTN = int.clone();
  int = new THREE.Vector3();
  frus.planes[5].intersectLine(lnRT, int);
  let ptRTF = int.clone();
  
  int = new THREE.Vector3();
  frus.planes[4].intersectLine(lnLB, int);
  let ptLBN = int.clone();
  int = new THREE.Vector3();
  frus.planes[5].intersectLine(lnLB, int);
  let ptLBF = int.clone();
  
  int = new THREE.Vector3();
  frus.planes[4].intersectLine(lnRB, int);
  let ptRBN = int.clone();
  int = new THREE.Vector3();
  frus.planes[5].intersectLine(lnRB, int);
  let ptRBF = int.clone();
  
  
  let pts = [ ptLTN, ptLTF, ptRTN, ptRTF, 
              ptLBN, ptLBF, ptRBN, ptRBF ];
  
  
  return pts;
  
  
}



// -----------------------



let offset = 7;
let frus = new THREE.Frustum (
                                new THREE.Plane(new THREE.Vector3(  -1,    0,    0), offset),
                                new THREE.Plane(new THREE.Vector3(   0,    1,    0), offset),
                                new THREE.Plane(new THREE.Vector3(   1,    0,    0), offset),
                                new THREE.Plane(new THREE.Vector3(   0,   -1,    0), offset),
                                new THREE.Plane(new THREE.Vector3(   0,    0,   -1), offset),
                                new THREE.Plane(new THREE.Vector3(   0,    0,    1), offset)
                              );

let pts = getCorners(frus);

let box3 = new THREE.Box3();
box3.setFromPoints(pts);

  

Sample here.
(Camera far clipping plane is set to be very short).

I was having some trouble with the plane.coplanarPoint() method. It was returning points with bigger than expected constant for the plane. Not sure if it might be a bug with Plane. I calculated it from the normal and constant values in above example.

If you have access to the camera, why not use a simpler way?
CameraFrustum


#6

Hi.

First let me thank you for your answer and support.

: )

I would preffer to use Frustum planes because I wanted to combine those planes with 6 clippling planes matching with the 6 Scene BoundingBox faces.

I am using very complex scenes and using instancing and I wanted to implement my frustum method in order to increase performance when rendering. The idea is that objects clipped by defined clipping planes or not in the current frustum camera will not be added to the scene by modifying maxInstancedCount in my InstancedBufferGeometry objects.

Another problem I have is that my Scene scale ( Biggest Object / Smallest Object )is very big and it is giving me lot of nightmares. I tried to use logarithmic depth buffer in my WEBGLRenderer but it is not accurate. You can see the issue at:

So I decided to dynamic change|update near and far planes of my camera depending on the frustum combined with my Clipping Planes. Currently in my render loop I am doing this:

// In my global Var Section (outside of render loop)
var sceneBox = is the combined Box3() of all instances (once is computed never changes anymore)
var sceneBoxCenter = sceneBox.getCenter(new THREE.Vector3()) (once is computed never changes anymore)

 function render() {
if (sceneBox != undefined) {
	d1 = sceneBox.distanceToPoint(camera.position);
	d2 = sceneBoxCenter.distanceTo(camera.position);
	camera.far = 2 * d2 - d1;
	camera.near = camera.far / 10000; // Trying multiple values I think is the maximum difference set by far/near without distorsion (but not sure)
	if (aCamera.far < sceneBoxSize) {
		aCamera.far = sceneBoxSize;
		aCamera.near = aCamera.far / 10000;
	}
}
....

 }

But I am not happy at all with this solution. This was the reason to use the box intersection taken from Clipping Planes and Camera Frustum.

At the end, I think I have decided using the points extracted from my camera as Mugen suggested me.

I was just goint to close this topic.

My only concern was to add a really Line3 object methods and not just a Line3Segment. I thought it could be usefull. I dont understand why infinite Line object was not included in THREEJS objects in the same way Plane was. Current Line3 object in my opinion has some lacks.

Anyway, I am not an expert and probably adding|extending infinite Line3 object is not a need at all.

: )

Many thanks for your suppport.

At the end, I will follow Mugen’s and your recommendation and use camera extracted values.

Best regards