Last active
December 15, 2022 09:35
-
-
Save eurocat2k/9d4618e2e2eb310e0fe59e6f1939e01e to your computer and use it in GitHub Desktop.
Find shortest distance between two line segments in 3D space
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"type": "module" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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] | |
}) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Get shortest distance between two line segments in 3D space
The code above shall produce the follwing output: