From 7018d7a427f733da1fa246a6c7c01cda79f74cd2 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Thu, 18 Jul 2024 12:58:55 +0200 Subject: [PATCH] r2: Point --- index.ts | 1 + r2/Point.ts | 70 ++++++++++++++++++++++++++++++++++++++ r2/Point_test.ts | 88 ++++++++++++++++++++++++++++++++++++++++++++++++ r2/_index.ts | 5 +++ 4 files changed, 164 insertions(+) create mode 100644 r2/Point.ts create mode 100644 r2/Point_test.ts create mode 100644 r2/_index.ts diff --git a/index.ts b/index.ts index 7fa5007..e3a4d39 100644 --- a/index.ts +++ b/index.ts @@ -1 +1,2 @@ +export * as r2 from './r2/_index' export * as s2 from './s2/_index' diff --git a/r2/Point.ts b/r2/Point.ts new file mode 100644 index 0000000..0f8b878 --- /dev/null +++ b/r2/Point.ts @@ -0,0 +1,70 @@ +/** + * Point represents a point in ℝ². + */ +export class Point { + x: number = 0.0 + y: number = 0.0 + + /** + * Returns a new Point. + * @category Constructors + */ + constructor(x: number = 0.0, y: number = 0.0) { + this.x = x + this.y = y + } + + /** Returns the sum of p and op. */ + add(op: Point): Point { + return new Point(this.x + op.x, this.y + op.y) + } + + /** Returns the difference of p and op. */ + sub(op: Point): Point { + return new Point(this.x - op.x, this.y - op.y) + } + + /** Returns the scalar product of p and m. */ + mul(m: number): Point { + return new Point(this.x * m, this.y * m) + } + + /** Returns a counterclockwise orthogonal point with the same norm. */ + ortho(): Point { + return new Point(-this.y, this.x) + } + + /** Returns the dot product between p and op. */ + dot(op: Point): number { + return this.x * op.x + this.y * op.y + } + + /** Returns the cross product of p and op. */ + cross(op: Point): number { + return this.x * op.y - this.y * op.x + } + + /** Returns the vector's norm. */ + norm(): number { + return Math.hypot(this.x, this.y) + } + + /** Returns a unit point in the same direction as p. */ + normalize(): Point { + if (this.x == 0.0 && this.y == 0.0) return this + return this.mul(1 / this.norm()) + } + + /** Truncates {x, y} floats to n digits of precision. */ + trunc(n: number = 15): Point { + const p = Number(`1e${n}`) + const trunc = (dim: number) => Math.round(dim * p) / p + return new Point(trunc(this.x), trunc(this.y)) + } + + /** Generates a human readable string. */ + toString(): string { + const t = this.trunc(12) + return `(${t.x.toFixed(12)}, ${t.y.toFixed(12)})` + } +} diff --git a/r2/Point_test.ts b/r2/Point_test.ts new file mode 100644 index 0000000..dc9f3a6 --- /dev/null +++ b/r2/Point_test.ts @@ -0,0 +1,88 @@ +import test from 'node:test' +import { equal, deepEqual } from 'node:assert/strict' +import { Point } from './Point' + +export const MAX_FLOAT32 = Math.pow(2, 127) * (2 - 1 / Math.pow(2, 23)) + +test('add', t => { + deepEqual(new Point(0, 0).add(new Point(0, 0)), new Point(0, 0)) + deepEqual(new Point(0, 1).add(new Point(0, 0)), new Point(0, 1)) + deepEqual(new Point(1, 1).add(new Point(4, 3)), new Point(5, 4)) + deepEqual(new Point(-4, 7).add(new Point(1, 5)), new Point(-3, 12)) +}) + +test('sub', t => { + deepEqual(new Point(0, 0).sub(new Point(0, 0)), new Point(0, 0)) + deepEqual(new Point(0, 1).sub(new Point(0, 0)), new Point(0, 1)) + deepEqual(new Point(1, 1).sub(new Point(4, 3)), new Point(-3, -2)) + deepEqual(new Point(-4, 7).sub(new Point(1, 5)), new Point(-5, 2)) +}) + +test('mul', t => { + deepEqual(new Point(0, 0).mul(0), new Point(0, 0)) + deepEqual(new Point(0, 1).mul(1), new Point(0, 1)) + deepEqual(new Point(1, 1).mul(5), new Point(5, 5)) +}) + +test('ortho', t => { + deepEqual(new Point(0, 0).ortho(), new Point(-0, 0)) + deepEqual(new Point(0, 1).ortho(), new Point(-1, 0)) + deepEqual(new Point(1, 1).ortho(), new Point(-1, 1)) + deepEqual(new Point(-4, 7).ortho(), new Point(-7, -4)) + deepEqual(new Point(1, Math.sqrt(3)).ortho(), new Point(-Math.sqrt(3), 1)) +}) + +test('dot', t => { + equal(new Point(0, 0).dot(new Point(0, 0)), 0) + equal(new Point(0, 1).dot(new Point(0, 0)), 0) + equal(new Point(1, 1).dot(new Point(4, 3)), 7) + equal(new Point(-4, 7).dot(new Point(1, 5)), 31) +}) + +test('cross', t => { + equal(new Point(0, 0).cross(new Point(0, 0)), 0) + equal(new Point(0, 1).cross(new Point(0, 0)), 0) + equal(new Point(1, 1).cross(new Point(-1, -1)), 0) + equal(new Point(1, 1).cross(new Point(4, 3)), -1) + equal(new Point(1, 5).cross(new Point(-2, 3)), 13) +}) + +test('norm', t => { + equal(new Point(0, 0).norm(), 0) + equal(new Point(0, 1).norm(), 1) + equal(new Point(-1, 0).norm(), 1) + equal(new Point(3, 4).norm(), 5) + equal(new Point(3, -4).norm(), 5) + equal(new Point(2, 2).norm(), 2 * Math.sqrt(2)) + equal(new Point(1, Math.sqrt(3)).norm(), 2) + equal(new Point(29, 29 * Math.sqrt(3)).norm(), 29 * 2 + 0.00000000000001) + equal(new Point(1, 1e15).norm(), 1e15) + equal(new Point(1e14, MAX_FLOAT32 - 1).norm(), MAX_FLOAT32) +}) + +test('normalize', t => { + deepEqual(new Point().normalize(), new Point(0, 0)) + deepEqual(new Point(0, 0).normalize(), new Point(0, 0)) + deepEqual(new Point(0, 1).normalize(), new Point(0, 1)) + deepEqual(new Point(-1, 0).normalize(), new Point(-1, 0)) + deepEqual(new Point(3, 4).normalize().trunc(), new Point(0.6, 0.8)) + deepEqual(new Point(3, -4).normalize().trunc(), new Point(0.6, -0.8)) + deepEqual(new Point(2, 2).normalize().trunc(), new Point(Math.sqrt(2) / 2, Math.sqrt(2) / 2).trunc()) + deepEqual(new Point(7, 7 * Math.sqrt(3)).normalize().trunc(), new Point(0.5, Math.sqrt(3) / 2).trunc()) + deepEqual(new Point(1e21, 1e21 * Math.sqrt(3)).normalize().trunc(), new Point(0.5, Math.sqrt(3) / 2).trunc()) + deepEqual(new Point(1, 1e16).normalize().trunc(), new Point(0, 1)) + deepEqual(new Point(1e4, MAX_FLOAT32 - 1).normalize().trunc(), new Point(0, 1)) +}) + +test('trunc', t => { + deepEqual(new Point().trunc(), new Point(0, 0)) + deepEqual(new Point(0.0000000000000001, 0.0000000000000001).trunc(), new Point(0, 0)) + deepEqual(new Point(0.00000000001, 0.00000000001).trunc(10), new Point(0, 0)) +}) + +test('toString', t => { + equal(new Point().toString(), '(0.000000000000, 0.000000000000)') + equal(new Point(0.0000000000000001, 0.0000000000000001).toString(), '(0.000000000000, 0.000000000000)') + equal(new Point(-1, 1).toString(), '(-1.000000000000, 1.000000000000)') + equal(new Point(-1.123456789123456789, 1).toString(), '(-1.123456789123, 1.000000000000)') +}) diff --git a/r2/_index.ts b/r2/_index.ts new file mode 100644 index 0000000..bb91d43 --- /dev/null +++ b/r2/_index.ts @@ -0,0 +1,5 @@ +/** + * Module r2 implements types and functions for working with geometry in ℝ². + * @module r2 + */ +export { Point } from './Point'