Pulls the pointer onto a ray at a set angle from a base point, or onto the crossing of two such rays, and returns that point for the drawing.
How the precision is kept
Rays are unit directions from the cosine and sine of whole multiples of the angle step. The tracked point is the pointer's projection onto the ray, or the crossing of two rays by a 2 × 2 determinant, all in 64-bit. Because the directions come from rounded angles, a ray at 90° has a cosine of about 6e-17 and not 0.
Open item. Use exact 0 and ±1 for ray directions at whole multiples of 90°, so tracked points are exactly level or plumb with the base point.
Calculations under this policy
3 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.
The directions offered at every multiple of the angle step.
aᵢ = start + i × step × (π ÷ 180), i = 0 … round(360 ÷ step) − 1
Each angle is computed from i, not added up, so the error does not grow round the circle. The angles are rounded multiples of π ÷ 180: 90 × (π ÷ 180) is the 64-bit number nearest π ÷ 2.
tracking.js
lines 29 to 34
CadCraft 0.8.2
/** The angles (radians) of the rays at every multiple of inc degrees, counted from `start` (the UCS X axis, DR.8). */
function rayAngles(inc, start = 0) {
const n = Math.max(1, Math.round(360 / inc)), out = [];
for (let i = 0; i < n; i++) out.push(start + i * inc * RAD);
return out;
}
Pointer projected onto a ray
64-bit
The point of a ray nearest the pointer, and how far the pointer is from it.
t = (q − p)·u; point = p + u·t, u = (cos a, sin a)
One dot product and two products. For a ray at 90° the cosine is 6.1e-17, so the point's x is p.x + 6.1e-17 × t and not exactly p.x. Points behind the base (t ≤ 0) are not offered.
tracking.js
lines 20 to 27
CadCraft 0.8.2
/** The point on the ray from p at angle a (radians) nearest q, and how far q is from it; only ahead of p. */
function onRay(p, a, q) {
const ux = Math.cos(a), uy = Math.sin(a);
const t = (q.x - p.x) * ux + (q.y - p.y) * uy;
if (t <= 0) return null;
const x = p.x + ux * t, y = p.y + uy * t;
return { x, y, t, off: Math.hypot(q.x - x, q.y - y) };
}
Crossing of two tracking rays
64-bit
The point where rays from two acquired points cross.
den = a × b; t = ((B − A) × b) ÷ den; point = A + a·t
Rays are skipped as parallel when |den| < 1e-9, an angle of about 1e-9 rad between unit directions. The point is computed along the first ray only; the crossing must lie ahead of both base points.
tracking.js
lines 59 to 65
CadCraft 0.8.2
const den = ax * by - ay * bx;
if (Math.abs(den) < 1e-9) continue;
const t = ((B.from.x - A.from.x) * by - (B.from.y - A.from.y) * bx) / den;
const s = ((B.from.x - A.from.x) * ay - (B.from.y - A.from.y) * ax) / den;
if (t <= 0 || s <= 0) continue;
const x = A.from.x + ax * t, y = A.from.y + ay * t, off = Math.hypot(cur.x - x, cur.y - y);
if (off <= tol && (!best || off < best.off)) best = { x, y, off, rays: [A, B], kind: 'two' };
In detail
Method
Projection onto a ray by a dot product; ray crossing by Cramer's rule.
Number types
64-bit numbers.
Tolerances
Rays are parallel when |den| < 1e-9 on unit directions. The pull distance is passed in by the caller in drawing units.
Limits
A point tracked along 90°, 180° or 270° differs from exactly plumb or level by about 6e-17 to 2e-16 of the distance along the ray.
Approach: 64-bit, direct
Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.