Skip to content

Commit

Permalink
Merge release-2.19.0
Browse files Browse the repository at this point in the history
Release 2.19.0
  • Loading branch information
axelpale authored Jul 7, 2024
2 parents e88a602 + c01c351 commit 31039bd
Show file tree
Hide file tree
Showing 9 changed files with 792 additions and 46 deletions.
151 changes: 110 additions & 41 deletions docs/API.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<a name="top"></a>
# Affineplane API Documentation v2.18.0
# Affineplane API Documentation v2.19.0

Welcome to affineplane API reference documentation. These docs are generated with [yamdog](https://axelpale.github.io/yamdog/).

Expand Down Expand Up @@ -9934,6 +9934,8 @@ Aliases: [affineplane.circle2](#affineplanecircle2)
- [affineplane.sphere2.rotateBy](#affineplanesphere2rotateby)
- [affineplane.sphere2.scaleBy](#affineplanesphere2scaleby)
- [affineplane.sphere2.size](#affineplanesphere2size)
- [affineplane.sphere2.tangentCircle](#affineplanesphere2tangentcircle)
- [affineplane.sphere2.tangentCircles](#affineplanesphere2tangentcircles)
- [affineplane.sphere2.transitFrom](#affineplanesphere2transitfrom)
- [affineplane.sphere2.transitTo](#affineplanesphere2transitto)
- [affineplane.sphere2.translate](#affineplanesphere2translate)
Expand Down Expand Up @@ -10310,6 +10312,73 @@ Get the rectangular size of the circle.

Source: [size.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere2/size.js)

<a name="affineplanesphere2tangentcircle"></a>
## [affineplane](#affineplane).[sphere2](#affineplanesphere2).[tangentCircle](#affineplanesphere2tangentcircle)(ca, cb, r, righthand)

Find a circle C that is left-hand tangent to the circles A and B.
Use the fourth parameter to switch to right-hand tangent.
Note the coordinate system: x-axis points right and y-axis points down.

If the gap between A and B is too large for C to be tangent to both
then the resulting circle is tangent with A and as close to B as possible.
Under the righthand flag, the preference is reversed.

<p style="margin-bottom: 0"><strong>Parameters:</strong></p>

- *ca*
- a [sphere2](#affineplanesphere2) {x,y,r}, the circle A.
- *cb*
- a [sphere2](#affineplanesphere2) {x,y,r}, the circle B.
- *r*
- a number, a [dist2](#affineplanedist2), the radius of the circle C.
- *righthand*
- a boolean, default false. Set true to find right-hand tangent circle.


<p style="margin-bottom: 0"><strong>Returns:</strong></p>

- a [sphere2](#affineplanesphere2) `{x,y,r}`


See also [sphere2](#affineplanesphere2).tangentCircles for efficient computation of both hands with just one function call.

Source: [tangentCircle.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere2/tangentCircle.js)

<a name="affineplanesphere2tangentcircles"></a>
## [affineplane](#affineplane).[sphere2](#affineplanesphere2).[tangentCircles](#affineplanesphere2tangentcircles)(ca, cb, r)

Find circles of radius r that are externally tangent to the given circles A and B.
Usually there are two such circles, one at the left-hand side and one at the right-hand side
with respect to the vector from A to B. The function returns the two circles in this left-right order.

If the gap between A and B is too large for the circles to be tangent to both A and B
then the function compromises and returns two circles: the first is tangent to A at the direction of B
and the second is tangent to B at the direction of A.

If the gap between A and B just fits a circle of radius r, the function returns only one circle.

If the circles A and B are nested, the function compromises and returns only one circle that is externally
tangent to the larger one so that the returned circle is still as close to the smaller one as possible.

<p style="margin-bottom: 0"><strong>Parameters:</strong></p>

- *ca*
- a circle {x,y,r}, the circle A.
- *cb*
- a circle {x,y,r}, the circle B.
- *r*
- a number, the radius of the circles to find.


<p style="margin-bottom: 0"><strong>Returns:</strong></p>

- an array of [sphere2](#affineplanesphere2). The array contains either one or two [sphere2](#affineplanesphere2).


See also [sphere2](#affineplanesphere2).tangentCircle if you only need either the left-hand or right-hand result.

Source: [tangentCircles.js](https://github.com/axelpale/affineplane/blob/main/lib/sphere2/tangentCircles.js)

<a name="affineplanesphere2transitfrom"></a>
## [affineplane](#affineplane).[sphere2](#affineplanesphere2).[transitFrom](#affineplanesphere2transitfrom)(sphere, source)

Expand Down Expand Up @@ -11640,6 +11709,46 @@ Translation of the plane does not affect the vector.

Source: [transitTo.js](https://github.com/axelpale/affineplane/blob/main/lib/vec2/transitTo.js)

<a name="affineplanevec2unit"></a>
## [affineplane](#affineplane).[vec2](#affineplanevec2).[unit](#affineplanevec2unit)(v)

Get unit vector parallel to the given vector.
The magnitude of unit vector is equal to one.
If zero vector is given, assume direction towards positive x.

<p style="margin-bottom: 0"><strong>Parameters:</strong></p>

- *v*
- a [vec2](#affineplanevec2)


<p style="margin-bottom: 0"><strong>Returns:</strong></p>

- a [vec2](#affineplanevec2), magnitude of one.


Aliases: [affineplane.vec2.normalize](#affineplanevec2normalize)

Source: [unit.js](https://github.com/axelpale/affineplane/blob/main/lib/vec2/unit.js)

<a name="affineplanevec2validate"></a>
## [affineplane](#affineplane).[vec2](#affineplanevec2).[validate](#affineplanevec2validate)(v)

Check if object is a valid [vec2](#affineplanevec2).

<p style="margin-bottom: 0"><strong>Parameters:</strong></p>

- *v*
- an object


<p style="margin-bottom: 0"><strong>Returns:</strong></p>

- a boolean


Source: [validate.js](https://github.com/axelpale/affineplane/blob/main/lib/vec2/validate.js)

<a name="affineplanevec3"></a>
## [affineplane](#affineplane).[vec3](#affineplanevec3)

Expand Down Expand Up @@ -11709,46 +11818,6 @@ The zero vector in 4D

Source: [vec4/index.js](https://github.com/axelpale/affineplane/blob/main/lib/vec4/index.js)

<a name="affineplanevec2unit"></a>
## [affineplane](#affineplane).[vec2](#affineplanevec2).[unit](#affineplanevec2unit)(v)

Get unit vector parallel to the given vector.
The magnitude of unit vector is equal to one.
If zero vector is given, assume direction towards positive x.

<p style="margin-bottom: 0"><strong>Parameters:</strong></p>

- *v*
- a [vec2](#affineplanevec2)


<p style="margin-bottom: 0"><strong>Returns:</strong></p>

- a [vec2](#affineplanevec2), magnitude of one.


Aliases: [affineplane.vec2.normalize](#affineplanevec2normalize)

Source: [unit.js](https://github.com/axelpale/affineplane/blob/main/lib/vec2/unit.js)

<a name="affineplanevec2validate"></a>
## [affineplane](#affineplane).[vec2](#affineplanevec2).[validate](#affineplanevec2validate)(v)

Check if object is a valid [vec2](#affineplanevec2).

<p style="margin-bottom: 0"><strong>Parameters:</strong></p>

- *v*
- an object


<p style="margin-bottom: 0"><strong>Returns:</strong></p>

- a boolean


Source: [validate.js](https://github.com/axelpale/affineplane/blob/main/lib/vec2/validate.js)

<a name="affineplanevec3add"></a>
## [affineplane](#affineplane).[vec3](#affineplanevec3).[add](#affineplanevec3add)(v, w)

Expand Down
2 changes: 2 additions & 0 deletions lib/sphere2/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ exports.polarOffset = require('./polarOffset')
exports.rotateBy = require('./rotateBy')
exports.scaleBy = exports.homothety
exports.size = require('./size')
exports.tangentCircle = require('./tangentCircle')
exports.tangentCircles = require('./tangentCircles')
exports.transitFrom = require('./transitFrom')
exports.transitTo = require('./transitTo')
exports.translate = require('./translate')
Expand Down
135 changes: 135 additions & 0 deletions lib/sphere2/tangentCircle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
module.exports = (ca, cb, r, righthand) => {
// @affineplane.sphere2.tangentCircle(ca, cb, r, righthand)
//
// Find a circle C that is left-hand tangent to the circles A and B.
// Use the fourth parameter to switch to right-hand tangent.
// Note the coordinate system: x-axis points right and y-axis points down.
//
// If the gap between A and B is too large for C to be tangent to both
// then the resulting circle is tangent with A and as close to B as possible.
// Under the righthand flag, the preference is reversed.
//
// Parameters:
// ca
// a sphere2 {x,y,r}, the circle A.
// cb
// a sphere2 {x,y,r}, the circle B.
// r
// a number, a dist2, the radius of the circle C.
// righthand
// a boolean, default false. Set true to find right-hand tangent circle.
//
// Returns:
// a sphere2 {x,y,r}
//
// See also sphere2.tangentCircles for efficient computation of both hands with just one function call.
//

// Sort circles largest first. This avoids special treatment of cases
// where the point H (the point C projected on the AB line) is outside AB.
// However, we need to maintain handedness with respect to the original argument order.
let swapped = false
let hand = (righthand ? 1 : -1)
if (ca.r < cb.r) {
// Swap
const ct = cb
cb = ca
ca = ct
swapped = true
hand = -hand
}

// Vector from A to B, of length c.
const vab = {
x: cb.x - ca.x,
y: cb.y - ca.y
}
// Triangle sides
const a = cb.r + r // length of BC
const b = ca.r + r // length of AC
const c = Math.sqrt(vab.x * vab.x + vab.y * vab.y) // ca.r + cb.r for tangent A and B
// Check special cases.
const epsilon = 1000 * Number.EPSILON
if (-epsilon < c && c < epsilon) {
// The circles A and B are concentric i.e. the distance between their centers is zero.
// The circle C can be tangent to both only if A and B have equal radius.
// The best compromise is to let C be tangent to the one with the largest radius (A).
// The tangent position for the circle C is found at an arbitrary angle (choose 0 deg).
return {
x: ca.x + b, // because b = ca.r + r and ca.r is always >= cb.r.
y: ca.y,
r
}
}
if (c + cb.r <= ca.r) {
// The circle A fully covers the circle B.
// In other words, the circle B is completely inside the circle A.
// The best compromise is to let the circle C be tangent to the circle A
// at a position closest to the circle B.
const vac = {
x: vab.x * b / c,
y: vab.y * b / c
}
// The center point of the circle C: vc = va + vac
return {
x: ca.x + vac.x,
y: ca.y + vac.y,
r
}
}
if (a + b < c) {
// The circles A and B are so far away that the circle C cannot connect them.
// Find a circle that is tangent with A and has center point along AB.
// To maintain the original circle order regardless of their radii,
// we treat both orders separately.
if (!swapped !== !righthand) { // Coerce to bool, then XOR
// Swapped xor right-handed order.
// Prefer the circle B.
const vbc = {
x: -vab.x * a / c,
y: -vab.y * a / c
}
// The center point of the circle C: vc = vb + vbc
return {
x: cb.x + vbc.x,
y: cb.y + vbc.y,
r
}
} // else
// Original order. Prefer the circle A.
const vac = {
x: vab.x * b / c,
y: vab.y * b / c
}
// The center point of the circle C: vc = va + vac
return {
x: ca.x + vac.x,
y: ca.y + vac.y,
r
}
}
// After excluding the special cases above,
// it is now possible to find a circle that is
// tangent to both circles A and B.
const p = (a + b + c) / 2
const A = Math.sqrt(p * (p - a) * (p - b) * (p - c))
const h = 2 * A / c
const w = Math.sqrt(b * b - h * h)
// Vector from A to H, of length w.
const vw = {
x: vab.x * w / c,
y: vab.y * w / c
}
// Vector from H to C, of length h.
const vh = {
x: hand * -vw.y * h / w,
y: hand * vw.x * h / w
}
// Find the center point of C.
// A + vw + vh
return {
x: ca.x + vw.x + vh.x,
y: ca.y + vw.y + vh.y,
r
}
}
Loading

0 comments on commit 31039bd

Please sign in to comment.