Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(modeling): rework orthonormal formula #1214

Merged
merged 8 commits into from
Mar 25, 2023
215 changes: 44 additions & 171 deletions packages/modeling/src/maths/OrthoNormalBasis.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,202 +4,75 @@ import * as vec2 from './vec2/index.js'
import * as vec3 from './vec3/index.js'

/*
* Class OrthoNormalBasis
* Reprojects points on a 3D plane onto a 2D plane
* or from a 2D plane back onto the 3D plane
* @param {plane} plane
* @param {vec3} rightvector
* Class that defines the formula for convertion to/from orthonomal basis vectors.
* @see https://www.kristakingmath.com/blog/orthonormal-basis-for-a-vector-set
*/
export const OrthoNormalBasis = function (plane, rightvector) {
if (arguments.length < 2) {
// choose an arbitrary right hand vector, making sure it is somewhat orthogonal to the plane normal:
rightvector = vec3.orthogonal(vec3.create(), plane)
}
this.v = vec3.normalize(vec3.create(), vec3.cross(vec3.create(), plane, rightvector))
this.u = vec3.cross(vec3.create(), this.v, plane)
this.plane = plane
this.planeorigin = vec3.scale(vec3.create(), plane, plane[3])
}
export class OrthoNormalBasis {
/**
* Construct the standard basis formula from the given plane.
* @param {plane} the plane of which to convert vertices to/from the orthonormal basis
*/
constructor (plane) {
// plane normal is one component
this.plane = plane
// orthognal vector to plane normal is one component
z3dev marked this conversation as resolved.
Show resolved Hide resolved
const rightvector = vec3.orthogonal(vec3.create(), plane)
z3dev marked this conversation as resolved.
Show resolved Hide resolved
this.v = vec3.normalize(vec3.create(), vec3.cross(vec3.create(), plane, rightvector))
// cross between plane normal and orthogonal vector is one component
this.u = vec3.cross(vec3.create(), this.v, plane)

// Get an orthonormal basis for the standard XYZ planes.
// Parameters: the names of two 3D axes. The 2d x axis will map to the first given 3D axis, the 2d y
// axis will map to the second.
// Prepend the axis with a "-" to invert the direction of this axis.
// For example: OrthoNormalBasis.GetCartesian("-Y","Z")
// will return an orthonormal basis where the 2d X axis maps to the 3D inverted Y axis, and
// the 2d Y axis maps to the 3D Z axis.
OrthoNormalBasis.GetCartesian = function (xaxisid, yaxisid) {
const axisid = xaxisid + '/' + yaxisid
let planenormal, rightvector
if (axisid === 'X/Y') {
planenormal = [0, 0, 1]
rightvector = [1, 0, 0]
} else if (axisid === 'Y/-X') {
planenormal = [0, 0, 1]
rightvector = [0, 1, 0]
} else if (axisid === '-X/-Y') {
planenormal = [0, 0, 1]
rightvector = [-1, 0, 0]
} else if (axisid === '-Y/X') {
planenormal = [0, 0, 1]
rightvector = [0, -1, 0]
} else if (axisid === '-X/Y') {
planenormal = [0, 0, -1]
rightvector = [-1, 0, 0]
} else if (axisid === '-Y/-X') {
planenormal = [0, 0, -1]
rightvector = [0, -1, 0]
} else if (axisid === 'X/-Y') {
planenormal = [0, 0, -1]
rightvector = [1, 0, 0]
} else if (axisid === 'Y/X') {
planenormal = [0, 0, -1]
rightvector = [0, 1, 0]
} else if (axisid === 'X/Z') {
planenormal = [0, -1, 0]
rightvector = [1, 0, 0]
} else if (axisid === 'Z/-X') {
planenormal = [0, -1, 0]
rightvector = [0, 0, 1]
} else if (axisid === '-X/-Z') {
planenormal = [0, -1, 0]
rightvector = [-1, 0, 0]
} else if (axisid === '-Z/X') {
planenormal = [0, -1, 0]
rightvector = [0, 0, -1]
} else if (axisid === '-X/Z') {
planenormal = [0, 1, 0]
rightvector = [-1, 0, 0]
} else if (axisid === '-Z/-X') {
planenormal = [0, 1, 0]
rightvector = [0, 0, -1]
} else if (axisid === 'X/-Z') {
planenormal = [0, 1, 0]
rightvector = [1, 0, 0]
} else if (axisid === 'Z/X') {
planenormal = [0, 1, 0]
rightvector = [0, 0, 1]
} else if (axisid === 'Y/Z') {
planenormal = [1, 0, 0]
rightvector = [0, 1, 0]
} else if (axisid === 'Z/-Y') {
planenormal = [1, 0, 0]
rightvector = [0, 0, 1]
} else if (axisid === '-Y/-Z') {
planenormal = [1, 0, 0]
rightvector = [0, -1, 0]
} else if (axisid === '-Z/Y') {
planenormal = [1, 0, 0]
rightvector = [0, 0, -1]
} else if (axisid === '-Y/Z') {
planenormal = [-1, 0, 0]
rightvector = [0, -1, 0]
} else if (axisid === '-Z/-Y') {
planenormal = [-1, 0, 0]
rightvector = [0, 0, -1]
} else if (axisid === 'Y/-Z') {
planenormal = [-1, 0, 0]
rightvector = [0, 1, 0]
} else if (axisid === 'Z/Y') {
planenormal = [-1, 0, 0]
rightvector = [0, 0, 1]
} else {
throw new Error('OrthoNormalBasis.GetCartesian: invalid combination of axis identifiers. Should pass two string arguments from [X,Y,Z,-X,-Y,-Z], being two different axes.')
this.planeorigin = vec3.scale(vec3.create(), plane, plane[3])
}
return new OrthoNormalBasis(new Plane(new Vector3D(planenormal), 0), new Vector3D(rightvector))
}

/*
// test code for OrthoNormalBasis.GetCartesian()
OrthoNormalBasis.GetCartesian_Test=function() {
let axisnames=["X","Y","Z","-X","-Y","-Z"];
let axisvectors=[[1,0,0], [0,1,0], [0,0,1], [-1,0,0], [0,-1,0], [0,0,-1]];
for(let axis1=0; axis1 < 3; axis1++) {
for(let axis1inverted=0; axis1inverted < 2; axis1inverted++) {
let axis1name=axisnames[axis1+3*axis1inverted];
let axis1vector=axisvectors[axis1+3*axis1inverted];
for(let axis2=0; axis2 < 3; axis2++) {
if(axis2 != axis1) {
for(let axis2inverted=0; axis2inverted < 2; axis2inverted++) {
let axis2name=axisnames[axis2+3*axis2inverted];
let axis2vector=axisvectors[axis2+3*axis2inverted];
let orthobasis=OrthoNormalBasis.GetCartesian(axis1name, axis2name);
let test1=orthobasis.to3D(new Vector2D([1,0]));
let test2=orthobasis.to3D(new Vector2D([0,1]));
let expected1=new Vector3D(axis1vector);
let expected2=new Vector3D(axis2vector);
let d1=test1.distanceTo(expected1);
let d2=test2.distanceTo(expected2);
if( (d1 > 0.01) || (d2 > 0.01) ) {
throw new Error("Wrong!");
}}}}}}
throw new Error("OK");
};
*/

// The z=0 plane, with the 3D x and y vectors mapped to the 2D x and y vector
OrthoNormalBasis.Z0Plane = function () {
const plane = new Plane(new Vector3D([0, 0, 1]), 0)
return new OrthoNormalBasis(plane, new Vector3D([1, 0, 0]))
}

OrthoNormalBasis.prototype = {

getProjectionMatrix: function () {
/**
* Convert the basis formula to a projection matrix.
* return {mat4} matrix which can be used to convert 3D vertices to 2D points
*/
getProjectionMatrix () {
return mat4.fromValues(
this.u[0], this.v[0], this.plane[0], 0,
this.u[1], this.v[1], this.plane[1], 0,
this.u[2], this.v[2], this.plane[2], 0,
0, 0, -this.plane[3], 1
)
},
}

getInverseProjectionMatrix: function () {
/**
* Convert the basis formula to an inverse projection matrix.
* return {mat4} matrix which can be used to convert 2D points to 3D vertices
*/
getInverseProjectionMatrix () {
// FIXME optimize this code
const p = vec3.scale(vec3.create(), this.plane, this.plane[3])
return mat4.fromValues(
this.u[0], this.u[1], this.u[2], 0,
this.v[0], this.v[1], this.v[2], 0,
this.plane[0], this.plane[1], this.plane[2], 0,
p[0], p[1], p[2], 1
)
},
}

to2D: function (point) {
return vec2.fromValues(vec3.dot(point, this.u), vec3.dot(point, this.v))
},
/**
* Convert the given 3D vertex to a 2D point which exists in the orthonormal basis
* @param {vec3} - 3D vertex which lies within the original basis (set)
* @return {vec2} - 2D point which lies within the orthonormal basis
*/
to2D (vertex) {
return vec2.fromValues(vec3.dot(vertex, this.u), vec3.dot(vertex, this.v))
}

to3D: function (point) {
/**
* Convert the given 2D point to a 3D vertex which exists in the original basis (set)
* @param {vec2} - 2D point which lies within the orthonormal basis
* @return {vec3} - 3D vertex which lies within the original basis (set)
*/
to3D (point) {
// FIXME optimize this code
const v1 = vec3.scale(vec3.create(), this.u, point[0])
const v2 = vec3.scale(vec3.create(), this.v, point[1])

const v3 = vec3.add(v1, v1, this.planeorigin)
const v4 = vec3.add(v2, v2, v3)
return v4
},

line3Dto2D: function (line3d) {
const a = line3d.point
const b = line3d.direction.plus(a)
const a2d = this.to2D(a)
const b2d = this.to2D(b)
return Line2D.fromPoints(a2d, b2d)
},

line2Dto3D: function (line2d) {
const a = line2d.origin()
const b = line2d.direction().plus(a)
const a3d = this.to3D(a)
const b3d = this.to3D(b)
return Line3D.fromPoints(a3d, b3d)
},

transform: function (matrix4x4) {
// todo: this may not work properly in case of mirroring
const newplane = this.plane.transform(matrix4x4)
const rightpointTransformed = this.u.transform(matrix4x4)
const originTransformed = new Vector3D(0, 0, 0).transform(matrix4x4)
const newrighthandvector = rightpointTransformed.minus(originTransformed)
const newbasis = new OrthoNormalBasis(newplane, newrighthandvector)
return newbasis
}
}