@davepagurek/flo-mat
Version:
Medial / Scale Axis Transform (MAT/SAT) Library.
197 lines (162 loc) • 7.03 kB
text/typescript
import { eSign, twoProduct, eDiff, expansionProduct, fastExpansionSum, scaleExpansion } from "big-float-ts";
import { tangentAt0, evaluate2ndDerivativeAt0, toPowerBasis_3rdDerivative } from "flo-bezier3";
/**
* Compare the curvature, κ, between two curves at t === 0.
*
* Returns a positive number if κ for psI > κ for psO, negative if κ for psI < κ
* for psO or zero if the curve extensions are identical (i.e. in same K-family).
*
* Precondition: The point psI evaluated at zero must === the point psO
* evaluated at zero.
*
* // TODO - is this correct?
* Exact: Returns the exact result if the bithlength of all
* coordinates <= 53 - 5 === 48 and are bit-aligned.
*
* @param psI An order 1, 2 or 3 bezier, e.g. [[0,0],[1,1],[2,1],[2,0]]
* representing the incoming curve
* @param psO Another bezier representing the outgoing curve
*/
function compareCurvaturesAtInterface(
psI: number[][],
psO: number[][]) {
// Get x' and y' for incoming curve evaluated at 0
const [dxI, dyI] = tangentAt0(psI); // max bitlength increase / max shift === 3
// Get x'' and y'' for incoming curve evaluated at 0
const [ddxI, ddyI] = evaluate2ndDerivativeAt0(psI); // max bitlength increase / max shift === 5
// Get x' and y' for outgoing curve evaluated at 0
const [dxO, dyO] = tangentAt0(psO); // max bitlength increase / max shift === 3
// Get x'' and y'' for outgoing curve evaluated at 0
const [ddxO, ddyO] = evaluate2ndDerivativeAt0(psO); // max bitlength increase / max shift === 5
//console.log('κI: ', κ(psI, 0));
//console.log('κO: ', κ(psO, 0));
// Remember the formula for the signed curvature of a parametric curve:
// κ = x′y′′ - y′x′′ / sqrt(x′² + y′²)³
// κ² = (x′y′′ - y′x′′)² / (x′² + y′²)³
// This allows us to do an exact comparison of curvatures
// Simplifying the above gives (denoting the incoming curve with a subscript
// of 1 and the outgoing with a 2):
// κIncoming > κOutgoing
// <=> (x₁′y₁′′ - y₁′x₁′′)²(x₂′² + y₂′²)³ > (x₂′y₂′′ - y₂′x₂′′)²(x₁′² + y₁′²)³
// <=> a²b³ > c²d³
// Note b³ > 0 and d³ > 0
// max aggregate bitlength increase (let original bitlength === p):
// a -> 2 x ((p+3)+(p+5) + 1) === 4p + 18 -> max p in double precision === 8 -> too low
//let a = (dxI*ddyI - dyI*ddxI)**2;
// b -> 3 x ((p+3) + 1) === 3p + 12
//let b = (dxO*dxO + dyO*dyO )**3;
// c -> 2 x ((p+3)+(p+5) + 1) === 4p + 18
//let c = (dxO*ddyO - dyO*ddxO)**2;
// d -> 3 x ((p+3) + 1) === 3p + 12
//let d = (dxI*dxI + dyI*dyI )**3;
// We need to resort to exact floating point arithmetic at this point
const a = eDiff(
twoProduct(dxI, ddyI),
twoProduct(dyI, ddxI)
);
const c = eDiff(
twoProduct(dxO, ddyO),
twoProduct(dyO, ddxO)
);
const signA = eSign(a);
const signC = eSign(c);
if (signA !== signC) {
//console.log('branch 3');
return signA - signC;
}
const b = fastExpansionSum(
twoProduct(dxO, dxO),
twoProduct(dyO, dyO)
);
const d = fastExpansionSum(
twoProduct(dxI, dxI),
twoProduct(dyI, dyI)
);
const b2 = expansionProduct(b, b);
const b3 = expansionProduct(b2, b);
const d2 = expansionProduct(d, d);
const d3 = expansionProduct(d2, d);
if (signA !== 0 || signC !== 0) {
//console.log('branch 4');
const a2 = expansionProduct(a, a);
const c2 = expansionProduct(c, c);
// max aggregate bitlength increase (let original bitlength === p):
// κ -> (2 x ((p+3)+(p+5) + 1)) + (3 x ((p+3) + 1)) === 7p + 30
// e.g. for bit-aligned input bitlength p of 10 we get output bitlength
// of 100, or for p === 3 (the max exact bitlength allowed to have exact
// results without resorting to infinite precision) we get 51 bits.
const κI = expansionProduct(a2,b3);
const κO = expansionProduct(c2,d3);
const δκ = eSign(eDiff(κI, κO));
if (δκ !== 0) {
//console.log('branch 5');
// At this point signA === signC, both +tive or -tive
return signA > 0 ? δκ : -δκ;
}
}
// At this point signA === signC, both +tive or -tive or 0
// Now we have to look at the change of curvature w.r.t. the parameter t,
// i.e.
// κ′ = [(x′²+y′²)(x′y′′′-y′x′′′) - 3(x′y′′-y′x′′)(x′x′′+y′y′′)] / (x′²+y′²)^(5/2)
// Therefore: (denoting the incoming curve with a subscript of 1 and the outgoing with a 2)
// κ′Incoming > κ′Outgoing
// <=> [(x₁′²+y₁′²)(x₁′y₁′′′-y₁′x₁′′′) - 3(x₁′y₁′′-y₁′x₁′′)(x₁′x₁′′+y₁′y₁′′)]²(x₂′²+y₂′²)⁵ >
// [(x₂′²+y₂′²)(x₂′y₂′′′-y₂′x₂′′′) - 3(x₂′y₂′′-y₂′x₂′′)(x₂′x₂′′+y₂′y₂′′)]²(x₁′²+y₁′²)⁵
// <=> (de - 3af)²b⁵ > (bg - 3ch)²d⁵
// <=> i²b⁵ > j²d⁵
// Get x′′′ and y′′′ for incoming curve evaluated at 1
const [[dddxI], [dddyI]] = toPowerBasis_3rdDerivative(psI); // max bitlength increase === max shift === 6
const [[dddxO], [dddyO]] = toPowerBasis_3rdDerivative(psO); // max bitlength increase === max shift === 6
const e = eDiff(
twoProduct(dxI, dddyI),
twoProduct(dyI, dddxI)
);
const f = fastExpansionSum(
twoProduct(dxI, ddxI),
twoProduct(dyI, ddyI)
);
const g = eDiff(
twoProduct(dxO, dddyO),
twoProduct(dyO, dddxO)
);
const h = fastExpansionSum(
twoProduct(dxO, ddxO),
twoProduct(dyO, ddyO)
);
// (de - 3af)²b⁵ > (bg - 3ch)²d⁵
// i²b⁵ > j²d⁵
const i = eDiff(
expansionProduct(d, e),
scaleExpansion(
expansionProduct(a, f),
3
)
);
const j = eDiff(
expansionProduct(b, g),
scaleExpansion(
expansionProduct(c, h),
3
)
);
const signI = eSign(i);
const signJ = eSign(j);
if (signA !== signC) {
return signI - signJ;
}
if (signI === 0 && signJ === 0) {
// Both curve extensions are identical, i.e. in the same K-family
return 0;
}
const i2 = expansionProduct(i,i);
const b5 = expansionProduct(b2,b3);
const j2 = expansionProduct(j,j);
const d5 = expansionProduct(d2,d3);
const dκI = expansionProduct(i2,b5);
const dκO = expansionProduct(j2,d5);
const sgn = eSign(eDiff(dκI, dκO));
return signI > 0 ? sgn : -sgn;
// If the above returned value is still zero then the two curve extensions
// are identical, i.e. in the same K-family
}
export { compareCurvaturesAtInterface }