Measures the gap, the angle and the curvature difference where two curves join or two surfaces share an edge, and classes the join as G0, G1 or G2.
How the precision is kept
Each number is computed in 64-bit on the exact curves and surfaces. Along a surface edge they are taken at a fixed number of sample points (33 by default) and the largest is reported, so the result describes the samples. The values written into a problem's text are rounded to 4 significant digits; the report rows keep the full values.
Open item. Keep the unrounded measured value in a problem's found field and round only the text shown.
Calculations under this policy
5 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.
Turns the measured gap, angle and curvature difference into G2, G1, G0 or none.
gap ≤ g0 → G0; and angle ≤ g1 → G1; and (dk ≤ g2 or rel ≤ g2rel) → G2
Plain comparisons against the tolerances carried in the report. Each test is written so that a value that is not a number fails it and gives the lower class.
nurbs/continuity.js
lines 38 to 43
ModelCraft 0.8.1
export function classify({ gap, angle, dk, rel }, tol = TOL) {
if (!(gap <= tol.g0)) return 'none';
if (!(angle <= tol.g1)) return 'G0';
if (!(dk <= tol.g2 || rel <= tol.g2rel)) return 'G1';
return 'G2';
}
Join of two curves
64-bit
The gap, the angle between tangents and the curvature difference where two curve ends meet.
The angle uses the atan2 form, which keeps digits for small angles. The relative difference is 0 when both curvatures are below 1e-300.
nurbs/continuity.js
lines 53 to 60
ModelCraft 0.8.1
export function curveJoin(a, endA, b, endB, tol = TOL) {
const A = endInfo(a, endA), Bi = endInfo(b, endB);
const tA = V.mul(A.d1, endA === 'end' ? 1 : -1), tB = V.mul(Bi.d1, endB === 'start' ? 1 : -1);
const gap = V.dist(A.p, Bi.p), angle = V.angle(tA, tB) * DEG;
const dk = V.dist(A.kv, Bi.kv), big = Math.max(V.len(A.kv), V.len(Bi.kv));
const rel = big > 1e-300 ? dk / big : 0;
return { gap, angle, dk, rel, kA: V.len(A.kv), kB: V.len(Bi.kv), cls: classify({ gap, angle, dk, rel }, tol), tol };
}
Shared edge of two surfaces
64-bit
Gap, normal angle and curvature difference at sample points along a shared edge, with the largest of each.
per sample: gap = |p_A − p_B|; angle between n_A and n_B; dk = |κ_A − κ_B| in the direction n_A × edge tangent
Samples are evenly spaced in the first surface's edge parameter and matched to the closest point of the second's edge curve. The normals are counted as flipped when more than half the samples have opposite normals, and B's direction is projected into B's tangent plane before its curvature is taken. The reported gap, angle and dk are the largest over the samples.
nurbs/continuity.js
lines 67 to 101
ModelCraft 0.8.1
export function surfaceEdge(sA, edgeA, sB, edgeB, { samples = 33, tol = TOL } = {}) {
const eA = S.edgeParams(sA, edgeA), eB = S.edgeParams(sB, edgeB), cB = S.edgeCurve(sB, edgeB), cA = S.edgeCurve(sA, edgeA);
const rows = [];
let flips = 0;
for (let k = 0; k < samples; k++) {
const t = eA.a + (eA.b - eA.a) * (samples > 1 ? k / (samples - 1) : 0.5);
const uvA = S.edgeUV(sA, edgeA, t), pA = S.point(sA, ...uvA);
const cp2 = C.closestParam(cB, pA);
const uvB = S.edgeUV(sB, edgeB, cp2.u), pB = S.point(sB, ...uvB);
const nA = S.normal(sA, ...uvA);
let nB = S.normal(sB, ...uvB);
if (V.dot(nA, nB) < 0) { flips++; }
rows.push({ t, uvA, uvB, pA, pB, nA, nB, gap: V.dist(pA, pB), te: C.derivs(cA, t, 1)[1] });
}
const flipped = flips * 2 > samples;
let gap = 0, ang = 0, dkMax = 0, dkSum = 0, relMax = 0, at = { gap: 0, angle: 0, dk: 0 };
const out = rows.map((r, k) => {
const nB = flipped ? V.mul(r.nB, -1) : r.nB;
const angle = V.angle(r.nA, nB) * DEG;
const d = crossDir(r.nA, V.len(r.te) > 1e-300 ? r.te : V.perp(r.nA));
const kA = S.normalCurvature(sA, ...r.uvA, d);
// B's normal curvature in the same direction, made a tangent of B, against B's (turned) normal
const dB = V.norm(V.sub(d, V.mul(nB, V.dot(d, nB))));
const kB = S.normalCurvature(sB, ...r.uvB, dB) * (flipped ? -1 : 1);
const dk = Math.abs(kA - kB), big = Math.max(Math.abs(kA), Math.abs(kB)), rel = big > 1e-300 ? dk / big : 0;
if (r.gap > gap) { gap = r.gap; at.gap = k; }
if (angle > ang) { ang = angle; at.angle = k; }
if (dk > dkMax) { dkMax = dk; at.dk = k; relMax = rel; }
dkSum += dk;
return { t: r.t, uvA: r.uvA, uvB: r.uvB, at: r.pA, gap: r.gap, angle, dk, kA, kB, rel };
});
const res = { rows: out, gap, angle: ang, dk: { max: dkMax, mean: dkSum / Math.max(1, out.length), at: at.dk }, rel: relMax, flipped, samples, tol, worstAt: at };
res.cls = classify({ gap, angle: ang, dk: dkMax, rel: relMax }, tol);
return res;
}
Finding shared edges
64-bit
Finds pairs of surface edges that lie on each other.
|L_A − L_B| ≤ max(10·tol, 0.05·max(L_A, L_B)); 7 points of A within tol of B
Edge lengths come from arcLength; edges shorter than 10 × the tolerance are left out. The seven test points are evenly spaced in the first edge's parameter.
nurbs/continuity.js
lines 103 to 119
ModelCraft 0.8.1
export function sharedEdges(surfaces, { tol = TOL.g0 } = {}) {
const edges = [];
surfaces.forEach((s, i) => S.EDGES.forEach((e) => {
const c = S.edgeCurve(s, e), L = C.arcLength(c);
if (L > tol * 10) edges.push({ i, e, c, L });
}));
const out = [];
for (let x = 0; x < edges.length; x++) for (let y = x + 1; y < edges.length; y++) {
const A = edges[x], Bq = edges[y];
if (A.i === Bq.i || Math.abs(A.L - Bq.L) > Math.max(tol * 10, 0.05 * Math.max(A.L, Bq.L))) continue;
const [a, b] = C.domain(A.c);
let okk = true;
for (let k = 0; k <= 6 && okk; k++) if (C.closestParam(Bq.c, C.point(A.c, a + (b - a) * k / 6)).d > tol) okk = false;
if (okk) out.push({ a: A.i, edgeA: A.e, b: Bq.i, edgeB: Bq.e });
}
return out;
}
Values written into a problem
64-bit
Rounds the measured gap, angle or curvature difference for a problem's found value and its text.
r4(x) = x rounded to 4 significant digits
toPrecision(4) is applied to the value stored in the problem's found field and to the number in its text, so the problem carries 4 digits. The report's rows and maxima are not rounded.
nurbs/continuity.js
line 132
ModelCraft 0.8.1
const r4 = (x) => +x.toPrecision(4);
In detail
Method
Gap: distance between the edge point of one surface and the closest point on the other's edge curve. Angle: between the two surface normals. Curvature: the normal curvatures of both surfaces in the direction across the edge. Curves: gap, angle between tangents, length of the difference of the curvature vectors.
Number types
64-bit numbers; angles converted with 180 ÷ π.
Tolerances
Defaults: G0 0.01, G1 0.1°, G2 0.001 absolute or 1 % of the larger curvature, whichever is looser. Shared edges are looked for at 10 × the G0 tolerance (at least 1e-6), with lengths within the larger of 10 × tolerance and 5 %, tested at 7 points.
Limits
A gap, kink or curvature jump between two sample points is not seen. The closest point on the other edge comes from a Newton iteration from sampled starts.
Approach: 64-bit, stated limit
The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.