In three.js, how can we make a face face the camera as much as possible under certain constraints?

Suppose our face is on the x-y plane, and there is a constraint that the axis of this face must be (0,1,0). I hope that when the camera moves, this face will face the camera as much as possible. It can only rotate around the axis, which is equivalent to adding an axis constraint to the sprite model.

I simply implemented the following code.

/////init
let planeGeometry;
let planeMaterial;
let planeMesh;
let rotationAngle = 0;
planeGeometry = new THREE.PlaneGeometry( 100, 100 );
planeMaterial = new THREE.MeshBasicMaterial( { color: 0xffff00, side: THREE.DoubleSide } );
planeMesh = new THREE.Mesh( planeGeometry, planeMaterial );
/////handle
console.log( camera.position )

//Get the normal vector of the face in world coordinates.
const localNormals = planegeometry.attributes.normal.array;
const localNormal = new THREE.Vector3();
localNormal.fromArray( localNormals, 0 );
planemesh.updateMatrixWorld( true );
const worldNormal = localNormal.clone().applyMatrix4( planemesh.matrixWorld ).normalize();

console.log( worldNormal );

//Get the view vector of the camera looking at this face.
const cameraViewVector = new THREE.Vector3( camera.position.x, 0, camera.position.z ).normalize();

//The angle between the two vectors above cannot actually be calculated this way, 
//because if the surface is not so special (on the x-y plane), 
//the calculation of this angle depends on three vectors instead of two 
//(here we take the shortcut of setting the y value of the view vector to 0, so it appears simpler).
const angle = cameraViewVector.angleTo( worldNormal );

const rotationAxis = new THREE.Vector3( 0, 1, 0 ).normalize();
rotationAngle += angle;
rotationAngle = rotationAngle % ( Math.PI * 2 );

console.log( angle )
console.log( rotationAngle )

const quaternion = new THREE.Quaternion().setFromAxisAngle( rotationAxis, rotationAngle );
planeMesh.setRotationFromQuaternion( quaternion );

This code works fine when I move the camera counterclockwise, and the face will follow as closely as possible, but it doesn’t work when I move the camera clockwise, because the angle calculation becomes erratic. How can I solve this problem, or is there another way to achieve what I want? Thank you everyone.

Can you use THREE.Sprite?

I also want to use THREE.Sprite :cry::cry::cry:, but in my actual project, the two points need to be fixed, so they should face the camera “as much as possible”.

It’s tricky to come up with a solution that will work for your case without understanding why the constraint exists.

Angle based solutions are usually prone to problems like you describe.

Sometimes you can solve these things by putting the thing under another node, and using .lookAt(camera.position) with that node. You can control “up” by setting the node.up = new THREE.Vector3() with whatever up vector you want.

Thank you for your advice. My main work during this period is to learn three.js and add an arrow function to an existing 3D visualization project (the original idea was similar to word), so this is why constraints are needed, because the arrow has a direction, but this is in three-dimensional space, so it is different from the arrow in word. I get the start and end of the mouse click point to create an arrow in three-dimensional space, but I hope that the arrow plane is as oriented as possible to the observer, which is my intention.
In the end, I actually used another method to implement a weakened version, because I magically found that in LineMaterial, if the linewidth is set to a large value and dashed is true, it is actually the effect I want, which is magical.

  const lineGeometry = new LineGeometry();
  lineGeometry.setPositions( [ -100, 100, 0, 100, -100, 0 ] );
  const matLine = new LineMaterial( {
    color: 0xffff00,
    linewidth: 50,
    vertexColors: true,
    dashed: true,
  } );
  matLine.uniforms.dashSize = 100000;
  matLine.uniforms.gapSize = 100000;
  const line = new Line2( lineGeometry, matLine );
  line.computeLineDistances();
  line.scale.set( 1, 1, 1 );
  scene.add( line );

But I still want to solve the problem I encountered at the beginning, how to make an arbitrary plane achieve this effect, because LineMaterial can only form a rectangle at most.

1 Like