Skip to content

Instantly share code, notes, and snippets.

@eurocat2k
Last active December 15, 2022 09:35
Show Gist options
  • Save eurocat2k/9d4618e2e2eb310e0fe59e6f1939e01e to your computer and use it in GitHub Desktop.
Save eurocat2k/9d4618e2e2eb310e0fe59e6f1939e01e to your computer and use it in GitHub Desktop.
Find shortest distance between two line segments in 3D space
{
"type": "module"
}
// Get shortest distance between two - non paralell - line segments
import { Point3D, Vector3D } from './Vector3D.js'
// Prerequisits
// Given two points - r1, and r2 - and two vectors - e1, e2 (not unit vector).
// This computation shall be applied to non paralell lines / line segments /
let r1 = new Vector3D(2,6,-9)
let r2 = new Vector3D(-1,-2, 3)
let e1 = new Vector3D(3,4,-4)
let e2 = new Vector3D(2,-6,1)
// (1)
// p1 = r1 + t1 * e1
// p2 = r2 + t2 * e2
let p1, p2
let t1, t2
// (2)
// n = e1 x e2
let n = Vector3D.cross(e1, e2)
if (n.zero()) {
console.log(`The segments are paralell`)
} else {
// (3)
let m = n.mag()
let k = Vector3D.dot(n, (Vector3D.subtract(r1, r2)))
// (4)
let d = m !== 0 ? k / m : 0
// (5)
// get t1, t2 having functions in (1): '.' means dot product
//
// (e2 x n) . (r2 - r1)
// t1 = --------------------
// n . n
//
// (e1 x n) . (r2 - r1)
// t2 = --------------------
// n . n
//
t1 = Vector3D.dot(Vector3D.cross(e2, n), Vector3D.subtract(r2, r1)) / Vector3D.dot(n, n)
t2 = Vector3D.dot(Vector3D.cross(e1, n), Vector3D.subtract(r2, r1)) / Vector3D.dot(n, n)
// (6) get p1, p2 having r1,r2 and e1,e2 and t1,t2 - see in (1)
p1 = Vector3D.add(r1, Vector3D.scale(e1, t1))
p2 = Vector3D.add(r2, Vector3D.scale(e2, t2))
console.log({
r1: [...r1._vector],
r2: [...r2._vector],
e1: [...e1._vector],
e2: [...e2._vector],
n: [...n._vector],
Point3D,
Vector3D,
zero: n.zero(), // not paralell if not zero: [0,0,0]
"||n||": m, // magnitude of cross product
"n . (r1 - r2)": k, // dot product of n and r1 - r2 (vector)
"distance": d,
t1,
t2,
p1: [...p1._vector],
p2: [...p2._vector]
})
}
class Point3D {
constructor(x = 0, y = 0, z = 0) {
this._vector = new Float32Array(3)
this._vector[0] = x
this._vector[1] = y
this._vector[2] = z
}
set(x=0, y=0, z=0) {
this._vector[0] = x
this._vector[1] = y
this._vector[2] = z
return this
}
createFrom(from) {
if (from instanceof Array && from.length == 3) {
this._vector = new Float32Array(3)
this._vector[0] = from[0]
this._vector[1] = from[1]
this._vector[2] = from[2]
return this
}
}
distance(p2) {
//console.log(p2)
if (p2 instanceof Point3D || p2 instanceof Vector3D) {
var dx = this._vector[0] - p2._vector[0]
var dy = this._vector[1] - p2._vector[1]
var dz = this._vector[2] - p2._vector[2]
return Math.sqrt(dx * dx + dy * dy + dz * dz)
}
return false
}
static distance(p1, p2) {
if (p1 instanceof Point3D || p1 instanceof Vector3D) {
if (p2 instanceof Point3D || p2 instanceof Vector3D) {
var dx = p1._vector[0] - p2._vector[0]
var dy = p1._vector[1] - p2._vector[1]
var dz = p1._vector[2] - p2._vector[2]
return Math.sqrt(dx * dx + dy * dy + dz * dz)
}
}
return false
}
getMethods(){
let properties = new Set()
let currentObj = this
do {
Object.getOwnPropertyNames(currentObj).map(item => properties.add(item))
} while ((currentObj = Object.getPrototypeOf(currentObj)))
return {methods: [...properties.keys()].sort().filter(item => typeof this[item] === 'function')};
}
static getMethods(p){
if (p instanceof Point3D || p instanceof Vector3D) {
let properties = new Set()
let currentObj = p
do {
Object.getOwnPropertyNames(currentObj).map(item => properties.add(item))
} while ((currentObj = Object.getPrototypeOf(currentObj)))
return {methods: [...properties.keys()].sort().filter(item => typeof p[item] === 'function')};
}
}
getAttrs(){
let properties = new Set()
let currentObj = this
do {
Object.getOwnPropertyNames(currentObj).map(item => properties.add(item))
} while ((currentObj = Object.getPrototypeOf(currentObj)))
return {attributes: [...properties.keys()].sort().filter(item => typeof this[item] !== 'function')};
}
static getAttrs(p) {
if (p instanceof Point3D || p instanceof Vector3D) {
let properties = new Set()
let currentObj = p
do {
Object.getOwnPropertyNames(currentObj).map(item => properties.add(item))
} while ((currentObj = Object.getPrototypeOf(currentObj)))
return {attributes: [...properties.keys()].sort().filter(item => typeof p[item] !== 'function')};
}
}
}
class Vector3D extends Point3D {
/** ---------------------------------------------------------------------
* @constructor Create a new 3-component vector.
* @param dx Number The change in x of the vector.
* @param dy Number The change in y of the vector.
* @param dz Number The change in z of the vector.
* @return Float32Array A new 3-component vector
*/
constructor(dx = 0, dy = 0, dz = 0) {
super(dx, dy, dz);
this._vector = new Float32Array(3);
if (arguments.length >= 1) { this._vector[0] = dx; }
if (arguments.length >= 2) { this._vector[1] = dy; }
if (arguments.length >= 3) { this._vector[2] = dz; }
}
_typeOf(value) {
var type = typeof value;
switch (type) {
case 'object':
return value === null ? 'null' : Object.prototype.toString.toLowerCase().call(value).match(/^\[object (.*)\]$/)[1];
case 'function':
return 'Function'.toLowerCase();
default:
return type.toLowerCase();
}
}
static _typeOf(value) {
var type = typeof value;
switch (type) {
case 'object':
return value === null ? 'null' : Object.prototype.toString.toLowerCase().call(value).match(/^\[object (.*)\]$/)[1];
case 'function':
return 'Function'.toLowerCase();
default:
return type.toLowerCase();
}
}
mag() {
return Math.sqrt((this._vector[0] * this._vector[0]) + (this._vector[1] * this._vector[1]) + (this._vector[2] * this._vector[2]));
}
static mag(v1) {
try {
if ((v1 instanceof Array && v1.length == 3) || v1 instanceof Vector3D || v1 instanceof Point3D) {
return Math.sqrt(v1._vector[0] * v1._vector[0] + v1._vector[1] * v1._vector[1] + v1._vector[2] * v1._vector[2]);
}
} catch (error) {
console.log(error);
}
}
/** -----------------------------------------------------------------
* @return number Convert the input angle in degrees to radians
*/
_toRadians(angleInDegrees) {
return angleInDegrees * Math.PI / 180;
}
static _toRadians(angleInDegrees) {
return angleInDegrees * Math.PI / 180;
}
/** -----------------------------------------------------------------
* @return number Convert the input angle in radians to degrees
*/
_toDegrees(angleInRadians) {
return angleInRadians * 180 / Math.PI;
}
static _toDegrees(angleInRadians) {
return angleInRadians * 180 / Math.PI;
}
/** ---------------------------------------------------------------------
* Create a new 3-component vector and set its components equal to an existing vector.
* @param from Float32Array An existing vector.
* @return Float32Array A new 3-component vector with the same values as "from"
*/
static from(from) {
let res = new Vector3D();
if ((from instanceof Array && from.length == 3) || from instanceof Vector3D || from instanceof Point3D) {
res._vector[0] = from._vector[0];
res._vector[1] = from._vector[1];
res._vector[2] = from._vector[2];
}
return res;
}
/** ---------------------------------------------------------------------
* Create a vector using two existing points.
* @param tail Float32Array A 3-component point.
* @param head Float32Array A 3-component point.
* @return Float32Array A new 3-component vector defined by 2 points
*/
createFrom2Points(tail, head) {
try {
if (((tail instanceof Array && tail.length == 3) || tail instanceof Vector3D || tail instanceof Point3D) && ((head instanceof Array && head.length == 3) || head instanceof Vector3D || head instanceof Point3D)) {
this.subtract(head, tail);
}
} catch (error) {
console.log(error);
}
}
/** ---------------------------------------------------------------------
* Copy a 3-component vector into another 3-component vector
* @param to Float32Array A 3-component vector that you want changed.
* @param from Float32Array A 3-component vector that is the source of data
* @returns Float32Array The "to" 3-component vector
*/
copy(from) {
if ((from instanceof Array && from.length == 3) || from instanceof Vector3D || from instanceof Point3D) {
this._vector[0] = from._vector[0];
this._vector[1] = from._vector[1];
this._vector[2] = from._vector[2];
return this;
}
}
static copy(v1, from) {
if (((v1 instanceof Array && v1.length == 3) || v1 instanceof Vector3D || v1 instanceof Point3D) && ((from instanceof Array && from.length == 3) || from instanceof Vector3D || from instanceof Point3D)) {
v1._vector[0] = from._vector[0];
v1._vector[1] = from._vector[1];
v1._vector[2] = from._vector[2];
return v1;
}
}
/** ---------------------------------------------------------------------
* Set the components of a 3-component vector.
* @param v Float32Array The vector to change.
* @param dx Number The change in x of the vector.
* @param dy Number The change in y of the vector.
* @param dz Number The change in z of the vector.
*/
set(dx = 0, dy = 0, dz = 0) {
this._vector[0] = dx;
this._vector[1] = dy;
this._vector[2] = dz;
return this;
}
static set(v1, dx = 0, dy = 0, dz = 0) {
if ((v1 instanceof Array && v1.length == 3) || v1 instanceof Vector3D || v1 instanceof Point3D) {
v1._vector[0] = dx;
v1._vector[1] = dy;
v1._vector[2] = dz;
return v1;
}
}
/** ---------------------------------------------------------------------
* Calculate the length of a vector.
* @param v Float32Array A 3-component vector.
* @return Number The length of a vector
*/
length() {
return Math.sqrt(this._vector[0] * this._vector[0] + this._vector[1] * this._vector[1] + this._vector[2] * this._vector[2]);
}
static length(v1) {
if ((v1 instanceof Array && v1.length == 3) || v1 instanceof Vector3D || v1 instanceof Point3D)
return Math.sqrt(v1._vector[0] * v1._vector[0] + v1._vector[1] * v1._vector[1] + v1._vector[2] * v1._vector[2]);
}
/** --------------------------------------------------------------------
* toAngles
*/
toAngles() {
return {
theta: Math.atan2(this._vector[2], this._vector[0]),
phi: Math.asin(this._vector[1] / this.length())
};
}
static toAngles(v) {
return {
theta: Math.atan2(v._vector[2], v._vector[0]),
phi: Math.asin(v._vector[1] / v.length())
};
}
angleTo(a) {
return Math.acos(this.dotProduct(a) / (this.length() * a.length()));
}
static angleTo(v, a) {
return Math.acos(v.dotProduct(a) / (v.length() * a.length()));
}
/** ---------------------------------------------------------------------
* Make a vector have a length of 1.
* @param v Float32Array A 3-component vector.
* @return Float32Array The input vector normalized to unit length. Or null if the vector is zero length.
*/
normalize() {
var length, percent;
length = this._vector.length();
if (Math.abs(length) < 0.9e-12) {
return null; // Invalid vector
}
percent = 1.0 / length;
this._vector[0] = this._vector[0] * percent;
this._vector[1] = this._vector[1] * percent;
this._vector[2] = this._vector[2] * percent;
return this;
}
static normalize(v1) {
if ((v1 instanceof Array && v1.length == 3) || v1 instanceof Vector3D || v1 instanceof Point3D) {
var length, percent;
length = v1._vector.length();
if (Math.abs(length) < 0.9e-12) {
return null; // Invalid vector
}
percent = 1.0 / length;
v1._vector[0] = v1._vector[0] * percent;
v1._vector[1] = v1._vector[1] * percent;
v1._vector[2] = v1._vector[2] * percent;
return v1;
}
}
/** ---------------------------------------------------------------------
* Add two vectors: result = V0 + v1
* @param result Float32Array A 3-component vector.
* @param v0 Float32Array A 3-component vector.
* @param v1 Float32Array A 3-component vector.
*/
add(v1) {
if ((v1 instanceof Array && v1.length == 3) || v1 instanceof Vector3D || v1 instanceof Point3D) {
this._vector[0] += v1[0];
this._vector[1] += v1[1];
this._vector[2] += v1[2];
}
if (v1 instanceof Vector3D) {
this._vector[0] += v1._vector[0];
this._vector[1] += v1._vector[1];
this._vector[2] += v1._vector[2];
return this;
}
}
static add(v0, v1) {
if ((v0 instanceof Array && v0.length == 3) || v0 instanceof Vector3D || v0 instanceof Point3D) {
let res = new Vector3D();
if ((v1 instanceof Array && v1.length == 3) || v1 instanceof Vector3D || v1 instanceof Point3D) {
res._vector[0] += v0._vector[0];
res._vector[0] += v1._vector[0];
res._vector[1] += v0._vector[1];
res._vector[1] += v1._vector[1];
res._vector[2] += v0._vector[2];
res._vector[2] += v1._vector[2];
}
return res;
}
}
/** ---------------------------------------------------------------------
* Subtract two vectors: result = v0 - v1
* @param result Float32Array A 3-component vector.
* @param v0 Float32Array A 3-component vector.
* @param v1 Float32Array A 3-component vector.
*/
subtract(v1) {
if ((v1 instanceof Array && v1.length == 3) || v1 instanceof Vector3D || v1 instanceof Point3D) {
this._vector[0] -= v1._vector[0];
this._vector[1] -= v1._vector[1];
this._vector[2] -= v1._vector[2];
}
return this;
}
static subtract(v0, v1) {
try {
if ((v1 instanceof Array && v1.length == 3) || v1 instanceof Vector3D || v1 instanceof Point3D) {
let res = new Vector3D();
if ((v0 instanceof Array && v0.length == 3) || v0 instanceof Vector3D || v0 instanceof Point3D) {
res._vector[0] = v0._vector[0];
res._vector[0] -= v1._vector[0];
res._vector[1] = v0._vector[1];
res._vector[1] -= v1._vector[1];
res._vector[2] = v0._vector[2];
res._vector[2] -= v1._vector[2];
}
return res;
}
} catch (error) {
console.log(error);
}
return false;
}
/** ---------------------------------------------------------------------
* Scale a vector: result = s * v0
* @param result Float32Array A 3-component vector.
* @param v0 Float32Array A 3-component vector.
* @param s Number A scale factor.
*/
scale(s = 1) {
if (this._typeOf(s) == 'number') {
this._vector[0] *= s;
this._vector[1] *= s;
this._vector[2] *= s;
return this;
}
}
static scale(v0, s = 1) {
if ((v0 instanceof Array && v0.length == 3) || v0 instanceof Vector3D || v0 instanceof Point3D) {
if (Vector3D._typeOf(s) == 'number') {
let res = Vector3D.from(v0)
res._vector[0] *= s;
res._vector[1] *= s;
res._vector[2] *= s;
return res;
}
}
}
/** ---------------------------------------------------------------------
* Calculate the cross product of 2 vectors: result = v0 x v1 (order matters)
* @param result Float32Array A 3-component vector.
* @param v0 Float32Array A 3-component vector.
* @param v1 Float32Array A 3-component vector.
*/
cross(v1) {
if ((v1 instanceof Array && v1.length == 3) || v1 instanceof Vector3D || v1 instanceof Point3D) {
let vr = new Vector3D;
vr._vector[0] = this._vector[1] * v1._vector[2] - this._vector[2] * v1._vector[1];
vr._vector[1] = this._vector[2] * v1._vector[0] - this._vector[0] * v1._vector[2];
vr._vector[2] = this._vector[0] * v1._vector[1] - this._vector[1] * v1._vector[0];
return vr;
}
}
static cross(v0, v1) {
try {
if ((v0 instanceof Array && v0.length == 3) || v0 instanceof Vector3D || v0 instanceof Point3D) {
if ((v1 instanceof Array && v1.length == 3) || v1 instanceof Vector3D || v1 instanceof Point3D) {
let vr = new Vector3D;
vr._vector[0] = v0._vector[1] * v1._vector[2] - v0._vector[2] * v1._vector[1];
vr._vector[1] = v0._vector[2] * v1._vector[0] - v0._vector[0] * v1._vector[2];
vr._vector[2] = v0._vector[0] * v1._vector[1] - v0._vector[1] * v1._vector[0];
return vr;
}
}
} catch (error) {
console.log(error)
}
return false
}
/** ---------------------------------------------------------------------
* Calculate the dot product of 2 vectors
* @param v0 Float32Array A 3-component vector.
* @param v1 Float32Array A 3-component vector.
* @return Number Float32Array The dot product of v0 and v1
*/
dot(v1) {
if ((v1 instanceof Array && v1.length == 3) || v1 instanceof Vector3D || v1 instanceof Point3D) {
return this._vector[0] * v1._vector[0] + this._vector[1] * v1._vector[1] + this._vector[2] * v1._vector[2];
}
}
static dot(v0, v1) {
if ((v0 instanceof Array && v0.length == 3) || v0 instanceof Vector3D || v0 instanceof Point3D) {
if ((v1 instanceof Array && v1.length == 3) || v1 instanceof Vector3D || v1 instanceof Point3D) {
return v0._vector[0] * v1._vector[0] + v0._vector[1] * v1._vector[1] + v0._vector[2] * v1._vector[2];
}
}
}
/** ---------------------------------------------------------------------
* Print a vector on the console.
* @param name String A description of the vector to be printed.
* @param v Float32Array A 3-component vector.
*/
print(name = "Vector3D") {
var maximum, order, digits;
maximum = Math.max(this._vector[0], this._vector[1], this._vector[2]);
order = Math.floor(Math.log(maximum) / Math.LN10 + 0.000000001);
digits = (order <= 0) ? 5 : (order > 5) ? 0 : (5 - order);
console.log("Vector3: " + name + ": " + this._vector[0].toFixed(digits) + " "
+ this._vector[1].toFixed(digits) + " "
+ this._vector[2].toFixed(digits));
}
static print(name = "Vector3D", v1) {
if ((v1 instanceof Array && v1.length == 3) || v1 instanceof Vector3D || v1 instanceof Point3D) {
var maximum, order, digits;
maximum = Math.max(v1._vector[0], v1._vector[1], v1._vector[2]);
order = Math.floor(Math.log(maximum) / Math.LN10 + 0.000000001);
digits = (order <= 0) ? 5 : (order > 5) ? 0 : (5 - order);
console.log("Vector3: " + name + ": " + v1._vector[0].toFixed(digits) + " "
+ v1._vector[1].toFixed(digits) + " "
+ v1._vector[2].toFixed(digits));
}
}
/**
* List all methods of current class
* @return Array of method names
*/
getMethods() {
let properties = new Set();
let currentObj = this;
do {
Object.getOwnPropertyNames(currentObj).map(item => properties.add(item));
} while ((currentObj = Object.getPrototypeOf(currentObj)));
return { methods: [...properties.keys()].sort().filter(item => typeof this[item] === 'function') };
}
static getMethods(v1) {
if ((v1 instanceof Array && v1.length == 3) || v1 instanceof Vector3D || v1 instanceof Point3D) {
let properties = new Set();
let currentObj = v1;
do {
Object.getOwnPropertyNames(currentObj).map(item => properties.add(item));
} while ((currentObj = Object.getPrototypeOf(currentObj)));
return { methods: [...properties.keys()].sort().filter(item => typeof p[item] === 'function') };
}
}
/**
* List all attributes of current class
* @return Array of attribute names
*/
getAttrs() {
let properties = new Set();
let currentObj = this;
do {
Object.getOwnPropertyNames(currentObj).map(item => properties.add(item));
} while ((currentObj = Object.getPrototypeOf(currentObj)));
return { attributes: [...properties.keys()].sort().filter(item => typeof this[item] !== 'function') };
}
static getAttrs(v1) {
if ((v1 instanceof Array && v1.length == 3) || v1 instanceof Vector3D || v1 instanceof Point3D) {
let properties = new Set();
let currentObj = v1;
do {
Object.getOwnPropertyNames(currentObj).map(item => properties.add(item));
} while ((currentObj = Object.getPrototypeOf(currentObj)));
return { attributes: [...properties.keys()].sort().filter(item => typeof p[item] !== 'function') };
}
}
zero() {
return (this._vector[0] === 0 && this._vector[1] === 0 && this._vector[2] === 0);
}
static zero(v1) {
if ((v1 instanceof Array && v1.length == 3) || v1 instanceof Vector3D || v1 instanceof Point3D)
return (v1._vector[0] === 0 && v1._vector[1] === 0 && v1._vector[2] === 0);
}
}
export { Point3D, Vector3D }
@eurocat2k
Copy link
Author

Get shortest distance between two line segments in 3D space

The code above shall produce the follwing output:

{
  r1: [ 2, 6, -9 ],
  r2: [ -1, -2, 3 ],
  e1: [ -6.167919635772705, -8.223893165588379, 8.223893165588379 ],
  e2: [ -0.4277360141277313, 1.2832080125808716, -0.21386800706386566 ],
  n: [ -20, -11, -26 ],
  Point3D: [class Point3D],
  Vector3D: [class Vector3D extends Point3D],
  zero: false,
  '||n||': 34.597687784012386,
  'n . (r1 - r2)': 164,
  distance: 4.740201166731856,
  t1: -2.0559732664995822,
  t2: -0.21386800334168754,
  p1: [ -4.167919635772705, -2.223893165588379, -0.7761068344116211 ],
  p2: [ -1.4277360439300537, -0.7167919874191284, 2.7861320972442627 ]
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment