This repository has been archived on 2023-03-25. You can view files and clone it, but cannot push or open issues or pull requests.
mightyscape-1.1-deprecated/extensions/fablabchemnitz/papercraft/openjscad/node_modules/@jscad/csg/src/connectors.js

221 lines
9.0 KiB
JavaScript

const Vector3D = require('./math/Vector3')
const Line3D = require('./math/Line3')
const Matrix4x4 = require('./math/Matrix4')
const OrthoNormalBasis = require('./math/OrthoNormalBasis')
const Plane = require('./math/Plane')
// # class Connector
// A connector allows to attach two objects at predefined positions
// For example a servo motor and a servo horn:
// Both can have a Connector called 'shaft'
// The horn can be moved and rotated such that the two connectors match
// and the horn is attached to the servo motor at the proper position.
// Connectors are stored in the properties of a CSG solid so they are
// ge the same transformations applied as the solid
const Connector = function (point, axisvector, normalvector) {
this.point = new Vector3D(point)
this.axisvector = new Vector3D(axisvector).unit()
this.normalvector = new Vector3D(normalvector).unit()
}
Connector.prototype = {
normalized: function () {
let axisvector = this.axisvector.unit()
// make the normal vector truly normal:
let n = this.normalvector.cross(axisvector).unit()
let normalvector = axisvector.cross(n)
return new Connector(this.point, axisvector, normalvector)
},
transform: function (matrix4x4) {
let point = this.point.multiply4x4(matrix4x4)
let axisvector = this.point.plus(this.axisvector).multiply4x4(matrix4x4).minus(point)
let normalvector = this.point.plus(this.normalvector).multiply4x4(matrix4x4).minus(point)
return new Connector(point, axisvector, normalvector)
},
// Get the transformation matrix to connect this Connector to another connector
// other: a Connector to which this connector should be connected
// mirror: false: the 'axis' vectors of the connectors should point in the same direction
// true: the 'axis' vectors of the connectors should point in opposite direction
// normalrotation: degrees of rotation between the 'normal' vectors of the two
// connectors
getTransformationTo: function (other, mirror, normalrotation) {
mirror = mirror ? true : false
normalrotation = normalrotation ? Number(normalrotation) : 0
let us = this.normalized()
other = other.normalized()
// shift to the origin:
let transformation = Matrix4x4.translation(this.point.negated())
// construct the plane crossing through the origin and the two axes:
let axesplane = Plane.anyPlaneFromVector3Ds(
new Vector3D(0, 0, 0), us.axisvector, other.axisvector)
let axesbasis = new OrthoNormalBasis(axesplane)
let angle1 = axesbasis.to2D(us.axisvector).angle()
let angle2 = axesbasis.to2D(other.axisvector).angle()
let rotation = 180.0 * (angle2 - angle1) / Math.PI
if (mirror) rotation += 180.0
transformation = transformation.multiply(axesbasis.getProjectionMatrix())
transformation = transformation.multiply(Matrix4x4.rotationZ(rotation))
transformation = transformation.multiply(axesbasis.getInverseProjectionMatrix())
let usAxesAligned = us.transform(transformation)
// Now we have done the transformation for aligning the axes.
// We still need to align the normals:
let normalsplane = Plane.fromNormalAndPoint(other.axisvector, new Vector3D(0, 0, 0))
let normalsbasis = new OrthoNormalBasis(normalsplane)
angle1 = normalsbasis.to2D(usAxesAligned.normalvector).angle()
angle2 = normalsbasis.to2D(other.normalvector).angle()
rotation = 180.0 * (angle2 - angle1) / Math.PI
rotation += normalrotation
transformation = transformation.multiply(normalsbasis.getProjectionMatrix())
transformation = transformation.multiply(Matrix4x4.rotationZ(rotation))
transformation = transformation.multiply(normalsbasis.getInverseProjectionMatrix())
// and translate to the destination point:
transformation = transformation.multiply(Matrix4x4.translation(other.point))
// let usAligned = us.transform(transformation);
return transformation
},
axisLine: function () {
return new Line3D(this.point, this.axisvector)
},
// creates a new Connector, with the connection point moved in the direction of the axisvector
extend: function (distance) {
let newpoint = this.point.plus(this.axisvector.unit().times(distance))
return new Connector(newpoint, this.axisvector, this.normalvector)
}
}
const ConnectorList = function (connectors) {
this.connectors_ = connectors ? connectors.slice() : []
}
ConnectorList.defaultNormal = [0, 0, 1]
ConnectorList.fromPath2D = function (path2D, arg1, arg2) {
if (arguments.length === 3) {
return ConnectorList._fromPath2DTangents(path2D, arg1, arg2)
} else if (arguments.length === 2) {
return ConnectorList._fromPath2DExplicit(path2D, arg1)
} else {
throw new Error('call with path2D and either 2 direction vectors, or a function returning direction vectors')
}
}
/*
* calculate the connector axisvectors by calculating the "tangent" for path2D.
* This is undefined for start and end points, so axis for these have to be manually
* provided.
*/
ConnectorList._fromPath2DTangents = function (path2D, start, end) {
// path2D
let axis
let pathLen = path2D.points.length
let result = new ConnectorList([new Connector(path2D.points[0],
start, ConnectorList.defaultNormal)])
// middle points
path2D.points.slice(1, pathLen - 1).forEach(function (p2, i) {
axis = path2D.points[i + 2].minus(path2D.points[i]).toVector3D(0)
result.appendConnector(new Connector(p2.toVector3D(0), axis,
ConnectorList.defaultNormal))
}, this)
result.appendConnector(new Connector(path2D.points[pathLen - 1], end,
ConnectorList.defaultNormal))
result.closed = path2D.closed
return result
}
/*
* angleIsh: either a static angle, or a function(point) returning an angle
*/
ConnectorList._fromPath2DExplicit = function (path2D, angleIsh) {
function getAngle (angleIsh, pt, i) {
if (typeof angleIsh === 'function') {
angleIsh = angleIsh(pt, i)
}
return angleIsh
}
let result = new ConnectorList(
path2D.points.map(function (p2, i) {
return new Connector(p2.toVector3D(0),
Vector3D.Create(1, 0, 0).rotateZ(getAngle(angleIsh, p2, i)),
ConnectorList.defaultNormal)
}, this)
)
result.closed = path2D.closed
return result
}
ConnectorList.prototype = {
setClosed: function (closed) {
this.closed = !!closed // FIXME: what the hell ?
},
appendConnector: function (conn) {
this.connectors_.push(conn)
},
/*
* arguments: cagish: a cag or a function(connector) returning a cag
* closed: whether the 3d path defined by connectors location
* should be closed or stay open
* Note: don't duplicate connectors in the path
* TODO: consider an option "maySelfIntersect" to close & force union all single segments
*/
followWith: function (cagish) {
const CSG = require('./CSG') // FIXME , circular dependency connectors => CSG => connectors
this.verify()
function getCag (cagish, connector) {
if (typeof cagish === 'function') {
cagish = cagish(connector.point, connector.axisvector, connector.normalvector)
}
return cagish
}
let polygons = []
let currCag
let prevConnector = this.connectors_[this.connectors_.length - 1]
let prevCag = getCag(cagish, prevConnector)
// add walls
this.connectors_.forEach(function (connector, notFirst) {
currCag = getCag(cagish, connector)
if (notFirst || this.closed) {
polygons.push.apply(polygons, prevCag._toWallPolygons({
toConnector1: prevConnector, toConnector2: connector, cag: currCag}))
} else {
// it is the first, and shape not closed -> build start wall
polygons.push.apply(polygons,
currCag._toPlanePolygons({toConnector: connector, flipped: true}))
}
if (notFirst === this.connectors_.length - 1 && !this.closed) {
// build end wall
polygons.push.apply(polygons,
currCag._toPlanePolygons({toConnector: connector}))
}
prevCag = currCag
prevConnector = connector
}, this)
return CSG.fromPolygons(polygons).reTesselated().canonicalized()
},
/*
* general idea behind these checks: connectors need to have smooth transition from one to another
* TODO: add a check that 2 follow-on CAGs are not intersecting
*/
verify: function () {
let connI
let connI1
for (let i = 0; i < this.connectors_.length - 1; i++) {
connI = this.connectors_[i]
connI1 = this.connectors_[i + 1]
if (connI1.point.minus(connI.point).dot(connI.axisvector) <= 0) {
throw new Error('Invalid ConnectorList. Each connectors position needs to be within a <90deg range of previous connectors axisvector')
}
if (connI.axisvector.dot(connI1.axisvector) <= 0) {
throw new Error('invalid ConnectorList. No neighboring connectors axisvectors may span a >=90deg angle')
}
}
}
}
module.exports = {Connector, ConnectorList}