Resources Numerical accuracy Calculation policy

User coordinate systems

Converts points, directions and angles between a user coordinate system and world coordinates, and sets a coordinate system from points or from an object.

How the precision is kept

A coordinate system is an origin and one angle. Conversions are a rotation by the cosine and sine of that angle and a shift, in 64-bit; angles convert by adding or subtracting the system's angle. A system within 1e-12 of the world is stored as the world.

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.

ucs.js CadCraft 0.8.2 64-bit 5 calculations Reviewed

Tests bin/test-cadcraft.mjs

User coordinates to world

64-bit

A point or direction typed in the user coordinate system, in world coordinates.

x = u.x + pₓ·cos − p_y·sin; y = u.y + pₓ·sin + p_y·cos

One cosine and one sine of the stored angle per call, then two products and a sum per coordinate. A system turned by exactly 90° still has a cosine of about 6e-17, so an axis-aligned point can pick up a term of that size times its other coordinate.

  • ucs.js
  • lines 38 to 52
  • CadCraft 0.8.2
export function vecToWorld(u, v) {
  if (!u) return { x: v.x, y: v.y };
  const c = Math.cos(u.ang), s = Math.sin(u.ang);
  return { x: v.x * c - v.y * s, y: v.x * s + v.y * c };
}
export function vecToUcs(u, v) {
  if (!u) return { x: v.x, y: v.y };
  const c = Math.cos(u.ang), s = Math.sin(u.ang);
  return { x: v.x * c + v.y * s, y: -v.x * s + v.y * c };
}
export function toWorld(u, p) {
  if (!u) return { x: p.x, y: p.y };
  const w = vecToWorld(u, p);
  return { x: u.x + w.x, y: u.y + w.y };
}

World coordinates to user

64-bit

A world point in the user coordinate system, as shown in coordinates and prompts.

d = p − origin; x = dₓ·cos + d_y·sin; y = −dₓ·sin + d_y·cos

The origin is subtracted first, then the difference is rotated back. Going to user coordinates and back is not exact to the last bit: each way rounds its products.

  • ucs.js
  • lines 53 to 56
  • CadCraft 0.8.2
export function toUcs(u, p) {
  if (!u) return { x: p.x, y: p.y };
  return vecToUcs(u, { x: p.x - u.x, y: p.y - u.y });
}

Is a coordinate system the world

64-bit

Normalizes a coordinate system and stores it as the world when it is the same.

world when |x| < 1e-12, |y| < 1e-12 and angle within 1e-12 of 0 or 2π

The three limits are absolute. The angle is wrapped into [0, 2π) by the remainder of a division by 2π.

  • ucs.js
  • lines 29 to 36
  • CadCraft 0.8.2
/** null for the world; else the UCS with its angle in [0, 2π). */
export function cleanUcs(v) {
  if (!v || typeof v !== 'object') return null;
  const x = +v.x, y = +v.y, ang = +v.ang;
  if (![x, y, ang].every(Number.isFinite) || Math.abs(x) > 1e15 || Math.abs(y) > 1e15) return null;
  const u = { x, y, ang: normAngle(ang) };
  return isWorld(u) ? null : u;
}

Coordinate system from an origin and an X-axis point

64-bit

The coordinate system whose X axis runs from one picked point through another.

angle = atan2(p_y − o_y, pₓ − oₓ)

One atan2 of the difference of the two points; points closer than 1e-9 (EPS of geom.js, absolute) are refused.

  • ucs.js
  • lines 67 to 71
  • CadCraft 0.8.2
/** UCS 3point (in 2D): the origin and a point on the positive X axis. */
export function threePoint(o, px) {
  if (dist(o, px) < EPS) return null;
  return cleanUcs({ x: o.x, y: o.y, ang: Math.atan2(px.y - o.y, px.x - o.x) }) || { x: 0, y: 0, ang: 0 };
}

Coordinate system from a polyline segment

64-bit

The coordinate system at the start of the polyline segment picked, with X along it.

angle = atan2 of (point at 0.001 of the segment − its start)

The direction is a chord over the first thousandth of the segment. For a straight segment that is its direction; for an arc segment it is a short chord, which is close to but not the tangent at the start.

  • ucs.js
  • lines 89 to 95
  • CadCraft 0.8.2
case 'poly': {
  const ps = prims(e, blocks);
  if (!ps.length) return null;
  let best = 0, bd = Infinity;
  if (pick) ps.forEach((q, i) => { const d = dist(primPoint(q, primParam(q, pick)), pick); if (d < bd) { bd = d; best = i; } });
  const q = ps[best], s = primPoint(q, q.rev ? 1 : 0), t = primPoint(q, q.rev ? 0.999 : 0.001);
  return mk(s.x, s.y, Math.atan2(t.y - s.y, t.x - s.x));

In detail

Method
2D rotation and translation; direction by atan2.
Number types
64-bit numbers.
Tolerances
World when |x|, |y| and the angle are each under 1e-12 (absolute). Origin and X-axis point closer than 1e-9 are refused. Origins beyond 1e15 are refused.
Limits
For a polyline arc segment the X axis is the chord over the first thousandth of the segment, not its tangent.

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.

Every policy that takes this approach