Skip to content

Conversation

TheBlek
Copy link
Contributor

@TheBlek TheBlek commented Jul 8, 2025

Fixed #31368

Description

Adds function to find distance between two Line3-s: closestDistanceToLine. I wanted to create granularity in the API as it is with distance to point. Hence shortestSegmentToLine and shortestSegmentToLineParameters which return shortest segment and shortest segment point's parameters.

Also adds helper function distanceToPoint on Line3 to more easily get distance to point.

Copy link

github-actions bot commented Jul 8, 2025

📦 Bundle size

Full ESM build, minified and gzipped.

Before After Diff
WebGL 338.36
78.95
338.36
78.95
+0 B
+0 B
WebGPU 557.72
154.42
557.72
154.42
+0 B
+0 B
WebGPU Nodes 556.64
154.2
556.64
154.2
+0 B
+0 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Before After Diff
WebGL 469.6
113.66
469.6
113.66
+0 B
+0 B
WebGPU 633.39
171.45
633.39
171.45
+0 B
+0 B
WebGPU Nodes 588.52
160.8
588.52
160.8
+0 B
+0 B

@Mugen87
Copy link
Collaborator

Mugen87 commented Jul 9, 2025

Real-Time Collision Detection by Christer Ericson is a really good source for these kind of algorithms. I've used the book in other projects extensively^^.

I had a go on this myself and ported the source function almost 1:1 which makes it way easier to stick to the reference and thus improves maintainance. The idea is to have just a single function that returns the squared distance and optionally the closest points on both line segments. Clamping is always done since I think something else does not really make sense in context of line segments.

Would this work for you as well?

const _d1 = /*@__PURE__*/ new Vector3();
const _d2 = /*@__PURE__*/ new Vector3();
const _r = /*@__PURE__*/ new Vector3();
const _c1 = /*@__PURE__*/ new Vector3();
const _c2 = /*@__PURE__*/ new Vector3();


/**
 * Returns the closest squared distance between this line segment and the given one.
 *
 * @param {Line3} line - The line segment to compute the closest squared distance to.
 * @param {?Vector3} c1 - The closest point on this line segment.
 * @param {?Vector3} c2 - The closest point on the given line segment.
 * @return {number} The squared distance between this line segment and the given one.
 */
distanceSqToLine3( line, c1 = _c1, c2 = _c2 ) {

	// from Real-Time Collision Detection by Christer Ericson, chapter 5.1.9

	// Computes closest points C1 and C2 of S1(s)=P1+s*(Q1-P1) and
	// S2(t)=P2+t*(Q2-P2), returning s and t. Function result is squared
	// distance between between S1(s) and S2(t)

	const EPSILON = 1e-8 * 1e-8; // must be squared since we compare squared length
	let s, t;

	const p1 = this.start;
	const p2 = line.start;
	const q1 = this.end;
	const q2 = line.end;

	_d1.subVectors( q1, p1 ); // Direction vector of segment S1
	_d2.subVectors( q2, p2 ); // Direction vector of segment S2
	_r.subVectors( p1, p2 );

	const a = _d1.dot( _d1 ); // Squared length of segment S1, always nonnegative
	const e = _d2.dot( _d2 ); // Squared length of segment S2, always nonnegative
	const f = _d2.dot( _r );

	// Check if either or both segments degenerate into points

	if ( a <= EPSILON && e <= EPSILON ) {

		// Both segments degenerate into points

		c1.copy( p1 );
		c2.copy( p2 );

		c1.sub( c2 );

		return c1.dot( c1 );

	}

	if ( a <= EPSILON ) {

		// First segment degenerates into a point

		s = 0;
		t = f / e; // s = 0 => t = (b*s + f) / e = f / e
		t = clamp( t, 0, 1 );


	} else {

		const c = _d1.dot( _r );

		if ( e <= EPSILON ) {

			// Second segment degenerates into a point

			t = 0;
			s = clamp( - c / a, 0, 1 ); // t = 0 => s = (b*t - c) / a = -c / a

		} else {

			// The general nondegenerate case starts here

			const b = _d1.dot( _d2 );
			const denom = a * e - b * b; // Always nonnegative

			// If segments not parallel, compute closest point on L1 to L2 and
			// clamp to segment S1. Else pick arbitrary s (here 0)

			if ( denom !== 0 ) {

				s = clamp( ( b * f - c * e ) / denom, 0, 1 );

			} else {

				s = 0;

			}

			// Compute point on L2 closest to S1(s) using
			// t = Dot((P1 + D1*s) - P2,D2) / Dot(D2,D2) = (b*s + f) / e

			t = ( b * s + f ) / e;

			// If t in [0,1] done. Else clamp t, recompute s for the new value
			// of t using s = Dot((P2 + D2*t) - P1,D1) / Dot(D1,D1)= (t*b - c) / a
			// and clamp s to [0, 1]

			if ( t < 0 ) {

				t = 0.;
				s = clamp( - c / a, 0, 1 );

			} else if ( t > 1 ) {

				t = 1;
				s = clamp( ( b - c ) / a, 0, 1 );

			}

		}

	}

	c1.copy( p1 ).add( _d1.multiplyScalar( s ) );
	c2.copy( p2 ).add( _d2.multiplyScalar( t ) );

	c1.sub( c2 );

	return c1.dot( c1 );

}

@Mugen87 Mugen87 changed the title Distance between Line3-s Line3: Add method for computing closest squared distance between line segments. Jul 9, 2025
@TheBlek
Copy link
Contributor Author

TheBlek commented Jul 9, 2025

The idea is to have just a single function that returns the squared distance and optionally the closest points on both line segments.

Agreed, that looks great.

Clamping is always done since I think something else does not really make sense in context of line segments.

Not clamping in that context would mean finding distance between lines. Similar to closestPointToPoint, which without clamping finds the closest point on line, not segment. I dont think its hard to modify the algorithm slightly to not clamp points.

Distance between lines could be another function but I thought that would make the api a bit inconsistent, that's all.

I will be happy if your code merges as is.

@Mugen87 Mugen87 added this to the r179 milestone Jul 9, 2025
@Mugen87 Mugen87 merged commit 963882d into mrdoob:dev Jul 10, 2025
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Segment-Segment intersection
2 participants