Condições De Alinhamento De Três Pontos - Condição de alinhamento de três pontos: aprenda a verificar (com ...
Condição de alinhamento de três pontos: aprenda a verificar (com ...

Aligning objects with three points in Three.js is not as straightforward as it sounds

Most tutorials skip the math details and just paste a code snippet they found on Stack Overflow. The problem is that snippet usually doesn't handle edge cases, and when your points aren't perfectly behaved in production, things look wrong and you spend hours debugging. I'm going to walk through how this actually works, why the naive approach fails, and what I ended up using after burning through a few weekends on it.

O que são condições de alinhamento de três pontos

You have three points in 3D space — let's call them A, B, and C. They define a plane, assuming they're not collinear. The goal is to construct a transformation matrix that positions an object so its local coordinate axes align with the geometry implied by those three points. Point A becomes the origin. The vector from A to B defines the forward direction (usually -Z in Three.js). The cross product of that forward vector with the normal of the plane gives you the right vector, and the cross of forward and right gives you up. Multiply them together and you have your rotation matrix. Here's the core of it in code:

```javascript
function alignToThreePoints(p1, p2, p3) {
const origin = p1.clone();
const zAxis = new THREE.Vector3().subVectors(p1, p2).normalize();
const yAxis = new THREE.Vector3().crossVectors(
new THREE.Vector3().subVectors(p2, p3),
zAxis
).normalize();
const xAxis = new THREE.Vector3().crossVectors(yAxis, zAxis).normalize();

const matrix = new THREE.Matrix4();
matrix.makeBasis(xAxis, yAxis, zAxis);
matrix.setPosition(origin);

return new THREE.Object3D().applyMatrix4(matrix);
}
``` This is the basic version. It works for clean data. In the real world, the data is rarely clean.

The most common mistake people make is using the wrong cross product order. If you flip the order of the vectors in a cross product, your entire orientation flips 180 degrees on one axis. You won't know it immediately because the object will still look somewhat aligned — it'll just be upside down or mirrored. I spent an afternoon chasing this on a project where we were instancing trees along a terrain curve. The trees looked fine from one angle and completely wrong from another. The fix was literally changing one line: cross(a, b) instead of cross(b, a). Another thing that trips people up is that Three.js uses a left-handed coordinate system for its rendering but a right-handed one for its math. Your cross products will produce the opposite direction if you're not aware of which convention you're working in. If the object is rotating the wrong way, check your handedness first before rewriting everything.

A case that actually broke my pipeline

I was building a tool that placed lampposts along a road defined by a Catmull-Rom spline. The road had segments where three consecutive points were nearly collinear — not exactly, but close enough that the cross product produced a very small vector. When I normalized that tiny vector, floating point precision issues kicked in and the resulting basis matrix had skewed angles. The lampposts would suddenly rotate into weird orientations at certain positions along the road. It happened maybe once every twenty instances, so it was easy to miss during testing. The workaround was to detect when the three points were too close to collinear and fall back to a smoothed orientation. I computed the angle between the segments, and if it was below a threshold, I used the average of the previous and next segment directions instead of computing from the degenerate triplet. It added about ten lines of code and eliminated the issue entirely.

👉 Clique no botão abaixo para saber mais sobre o assunto!

Countering-intuitive things beginners miss

First: you don't actually need all three points to define a useful orientation. Two points give you a forward direction. A third point only matters if you need the object to also respect the plane's tilt. If you're just aligning something to face along a path, two points are enough and it's more stable. The third point introduces unnecessary degrees of freedom that can amplify errors. Second: makeBasis assumes your vectors are already orthogonal. If your three input points don't form perfectly perpendicular axes, the resulting matrix will skew. Gram-Schmidt orthogonalization fixes this, but it's computationally more expensive and can introduce drift over repeated applications. In practice, for one-off alignments it's fine. For something that runs every frame on thousands of objects, it adds up.

How to use this in a real project

Three.js doesn't ship with a built-in utility for this, so you either write it yourself or pull in a helper library. There are a few npm packages that wrap this functionality. My personal recommendation is to write it yourself once so you understand what's happening, then decide whether you want abstraction later. The full function with the collinearity guard looks like this:

```javascript
function alignToThreePoints(p1, p2, p3, collinearityThreshold = 0.001) {
const origin = p1.clone();
const forward = new THREE.Vector3().subVectors(p1, p2);
const forwardLen = forward.length();

if (forwardLen < collinearityThreshold) {
console.warn('Points are too close: A and B are nearly identical');
return null;
}

forward.normalize();
const planeNormal = new THREE.Vector3().crossVectors(
new THREE.Vector3().subVectors(p2, p3),
forward
);

if (planeNormal.length() < collinearityThreshold) {
console.warn('Points A, B, and C are nearly collinear');
return null;
}

planeNormal.normalize();
const right = new THREE.Vector3().crossVectors(forward, planeNormal).normalize();
const up = new THREE.Vector3().crossVectors(right, forward).normalize();

const matrix = new THREE.Matrix4();
matrix.makeBasis(right, up, forward);
matrix.setPosition(origin);

return matrix;
}
``` The collinearity threshold value matters. If you set it too high, you'll reject valid configurations. Too low and you'll still get the skew problem. 0.001 works for most world-scale scenes in Three.js. If your scene uses different units — say, kilometers instead of meters — scale this proportionally.

When this approach completely fails

There are scenarios where three-point alignment simply cannot work and you need a different strategy. If all three points lie on a single line, there is no unique plane to orient against. No amount of math will help you — the answer is undefined. You have to either add a fourth point, use an external reference like world up, or interpolate from neighboring valid configurations. Another failure mode is when the points span a huge distance relative to their local detail. Floating point precision degrades as coordinate values grow. If your points are in the millions of units from the origin, even well-formed cross products can produce garbage. Move your coordinate system closer to the origin using a floating-origin approach, or switch to float64 vectors if your Three.js build supports it.

For terrain-aligned placement where the surface is noisy or scanned data, three-point alignment produces jittery results because the input points themselves are unstable. In those cases, computing a surface normal from a heightmap or using a physics-based orientation solver gives far more consistent results than trying to make three noisy points agree. One final note: if you're doing this for camera alignment specifically, Three.js has Object3D.lookAt(), which only needs two points — the camera position and the target. Using three points for a camera is usually overkill unless you need to control roll as well, and in that case setLookAt with an explicit up vector is cleaner than building a full basis by hand.