-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0c6a956
commit 69149e1
Showing
4 changed files
with
255 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import r1 from './r1/_index' | ||
import r2 from './r2/_index' | ||
import s2 from './s2/_index' | ||
|
||
export default { r2, s2 } | ||
export default { r1, r2, s2 } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
export class Interval { | ||
lo: number | ||
hi: number | ||
|
||
constructor(lo: number = 0.0, hi: number = 0.0) { | ||
this.lo = lo | ||
this.hi = hi | ||
} | ||
|
||
// isEmpty reports whether the interval is empty | ||
isEmpty(): boolean { | ||
return this.lo > this.hi | ||
} | ||
|
||
// equal returns true iff the interval contains the same points as oi | ||
equal(oi: Interval): boolean { | ||
return (this.lo == oi.lo && this.hi == oi.hi) || (this.isEmpty() && oi.isEmpty()) | ||
} | ||
|
||
// center returns the midpoint of the interval | ||
// It is undefined for empty intervals | ||
center(): number { | ||
return 0.5 * (this.lo + this.hi) | ||
} | ||
|
||
// length returns the length of the interval | ||
// The length of an empty interval is negative | ||
length(): number { | ||
return this.hi - this.lo | ||
} | ||
|
||
// contains returns true iff the interval contains p | ||
contains(p: number): boolean { | ||
return this.lo <= p && p <= this.hi | ||
} | ||
|
||
// containsInterval returns true iff the interval contains oi | ||
containsInterval(oi: Interval): boolean { | ||
if (oi.isEmpty()) return true | ||
return this.lo <= oi.lo && oi.hi <= this.hi | ||
} | ||
|
||
// interiorContains returns true iff the interval strictly contains p | ||
interiorContains(p: number): boolean { | ||
return this.lo < p && p < this.hi | ||
} | ||
|
||
// interiorContainsInterval returns true iff the interval strictly contains oi | ||
interiorContainsInterval(oi: Interval): boolean { | ||
if (oi.isEmpty()) return true | ||
return this.lo < oi.lo && oi.hi < this.hi | ||
} | ||
|
||
// intersects returns true iff the interval contains any points in common with oi. | ||
intersects(oi: Interval): boolean { | ||
if (this.lo <= oi.lo) return oi.lo <= this.hi && oi.lo <= oi.hi // oi.lo ∈ i and oi is not empty | ||
return this.lo <= oi.hi && this.lo <= this.hi // i.lo ∈ oi and i is not empty | ||
} | ||
|
||
// interiorIntersects returns true iff the interior of the interval contains any points in common with oi, including the latter's boundary. | ||
interiorIntersects(oi: Interval): boolean { | ||
return oi.lo < this.hi && this.lo < oi.hi && this.lo < this.hi && oi.lo <= oi.hi | ||
} | ||
|
||
// intersection returns the interval containing all points common to i and j. | ||
intersection(j: Interval): Interval { | ||
// Empty intervals do not need to be special-cased. | ||
return new Interval(Math.max(this.lo, j.lo), Math.min(this.hi, j.hi)) | ||
} | ||
|
||
// addPoint returns the interval expanded so that it contains the given point. | ||
addPoint(p: number): Interval { | ||
if (this.isEmpty()) return new Interval(p, p) | ||
if (p < this.lo) return new Interval(p, this.hi) | ||
if (p > this.hi) return new Interval(this.lo, p) | ||
return this | ||
} | ||
|
||
// clampPoint returns the closest point in the interval to the given point "p". | ||
// The interval must be non-empty. | ||
clampPoint(p: number): number { | ||
return Math.max(this.lo, Math.min(this.hi, p)) | ||
} | ||
|
||
// expanded returns an interval that has been expanded on each side by margin. | ||
// If margin is negative, then the function shrinks the interval on | ||
// each side by margin instead. The resulting interval may be empty. Any | ||
// expansion of an empty interval remains empty. | ||
expanded(margin: number): Interval { | ||
if (this.isEmpty()) return this | ||
return new Interval(this.lo - margin, this.hi + margin) | ||
} | ||
|
||
// union returns the smallest interval that contains this interval and the given interval. | ||
union(other: Interval): Interval { | ||
if (this.isEmpty()) return other | ||
if (other.isEmpty()) return this | ||
return new Interval(Math.min(this.lo, other.lo), Math.max(this.hi, other.hi)) | ||
} | ||
|
||
// trunc truncates {lo, hi} floats to n digits of precision | ||
trunc(n: number = 15): Interval { | ||
const p = Number(`1e${n}`) | ||
const trunc = (dim: number) => Math.round(dim * p) / p | ||
return new Interval(trunc(this.lo), trunc(this.hi)) | ||
} | ||
|
||
// toString generates a human readable string | ||
toString(): string { | ||
const t = this.trunc(7) | ||
return `[${t.lo.toFixed(7)}, ${t.hi.toFixed(7)}]` | ||
} | ||
|
||
// Constructors | ||
|
||
// empty returns an empty interval. | ||
static empty(): Interval { | ||
return new Interval(1, 0) | ||
} | ||
|
||
// IntervalFromPoint returns an interval representing a single point. | ||
static fromPoint(p: number): Interval { | ||
return new Interval(p, p) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import test from 'node:test' | ||
import { ok, equal } from 'node:assert/strict' | ||
import { Interval } from './Interval' | ||
|
||
// Some standard intervals for use throughout the tests. | ||
const UNIT = new Interval(0, 1) | ||
const NEG_UNIT = new Interval(-1, 0) | ||
const HALF = new Interval(0.5, 0.5) | ||
const EMPTY = Interval.empty() | ||
|
||
test('isEmpty', t => { | ||
ok(!UNIT.isEmpty(), 'should not be empty') | ||
ok(!NEG_UNIT.isEmpty(), 'should not be empty') | ||
ok(!HALF.isEmpty(), 'should not be empty') | ||
ok(EMPTY.isEmpty(), 'should not empty') | ||
}) | ||
|
||
test('center', t => { | ||
equal(UNIT.center(), 0.5) | ||
equal(NEG_UNIT.center(), -0.5) | ||
equal(HALF.center(), 0.5) | ||
}) | ||
|
||
test('length', t => { | ||
equal(UNIT.length(), 1) | ||
equal(NEG_UNIT.length(), 1) | ||
equal(HALF.length(), 0) | ||
}) | ||
|
||
test('contains', t => { | ||
ok(UNIT.contains(0.5)) | ||
ok(UNIT.interiorContains(0.5)) | ||
|
||
ok(UNIT.contains(0)) | ||
ok(!UNIT.interiorContains(0)) | ||
|
||
ok(UNIT.contains(1)) | ||
ok(!UNIT.interiorContains(1)) | ||
}) | ||
|
||
test('operations', t => { | ||
ok(EMPTY.containsInterval(EMPTY)) | ||
ok(EMPTY.interiorContainsInterval(EMPTY)) | ||
ok(!EMPTY.intersects(EMPTY)) | ||
ok(!EMPTY.interiorIntersects(EMPTY)) | ||
|
||
ok(!EMPTY.containsInterval(UNIT)) | ||
ok(!EMPTY.interiorContainsInterval(UNIT)) | ||
ok(!EMPTY.intersects(UNIT)) | ||
ok(!EMPTY.interiorIntersects(UNIT)) | ||
|
||
ok(UNIT.containsInterval(HALF)) | ||
ok(UNIT.interiorContainsInterval(HALF)) | ||
ok(UNIT.intersects(HALF)) | ||
ok(UNIT.interiorIntersects(HALF)) | ||
|
||
ok(UNIT.containsInterval(UNIT)) | ||
ok(!UNIT.interiorContainsInterval(UNIT)) | ||
ok(UNIT.intersects(UNIT)) | ||
ok(UNIT.interiorIntersects(UNIT)) | ||
|
||
ok(UNIT.containsInterval(EMPTY)) | ||
ok(UNIT.interiorContainsInterval(EMPTY)) | ||
ok(!UNIT.intersects(EMPTY)) | ||
ok(!UNIT.interiorIntersects(EMPTY)) | ||
|
||
ok(!UNIT.containsInterval(NEG_UNIT)) | ||
ok(!UNIT.interiorContainsInterval(NEG_UNIT)) | ||
ok(UNIT.intersects(NEG_UNIT)) | ||
ok(!UNIT.interiorIntersects(NEG_UNIT)) | ||
|
||
const i = new Interval(0, 0.5) | ||
ok(UNIT.containsInterval(i)) | ||
ok(!UNIT.interiorContainsInterval(i)) | ||
ok(UNIT.intersects(i)) | ||
ok(UNIT.interiorIntersects(i)) | ||
|
||
ok(!HALF.containsInterval(i)) | ||
ok(!HALF.interiorContainsInterval(i)) | ||
ok(HALF.intersects(i)) | ||
ok(!HALF.interiorIntersects(i)) | ||
}) | ||
|
||
test('intersection', t => { | ||
ok(UNIT.intersection(HALF).equal(HALF)) | ||
ok(UNIT.intersection(NEG_UNIT).equal(new Interval(0, 0))) | ||
ok(NEG_UNIT.intersection(HALF).equal(EMPTY)) | ||
ok(UNIT.intersection(EMPTY).equal(EMPTY)) | ||
ok(EMPTY.intersection(UNIT).equal(EMPTY)) | ||
}) | ||
|
||
test('union', t => { | ||
ok(new Interval(99, 100).union(EMPTY).equal(new Interval(99, 100))) | ||
ok(EMPTY.union(new Interval(99, 100)).equal(new Interval(99, 100))) | ||
ok(new Interval(5, 3).union(new Interval(0, -2)).equal(EMPTY)) | ||
ok(new Interval(0, -2).union(new Interval(5, 3)).equal(EMPTY)) | ||
ok(UNIT.union(UNIT).equal(UNIT)) | ||
ok(UNIT.union(NEG_UNIT).equal(new Interval(-1, 1))) | ||
ok(NEG_UNIT.union(UNIT).equal(new Interval(-1, 1))) | ||
ok(HALF.union(UNIT).equal(UNIT)) | ||
}) | ||
|
||
test('addPoint', t => { | ||
ok(EMPTY.addPoint(5).equal(new Interval(5, 5))) | ||
ok(new Interval(5, 5).addPoint(-1).equal(new Interval(-1, 5))) | ||
ok(new Interval(-1, 5).addPoint(0).equal(new Interval(-1, 5))) | ||
ok(new Interval(-1, 5).addPoint(6).equal(new Interval(-1, 6))) | ||
}) | ||
|
||
test('clampPoint', t => { | ||
equal(new Interval(0.1, 0.4).clampPoint(0.3), 0.3) | ||
equal(new Interval(0.1, 0.4).clampPoint(-7.0), 0.1) | ||
equal(new Interval(0.1, 0.4).clampPoint(0.6), 0.4) | ||
}) | ||
|
||
test('expanded', t => { | ||
ok(EMPTY.expanded(0.45).equal(EMPTY)) | ||
ok(UNIT.expanded(0.5).equal(new Interval(-0.5, 1.5))) | ||
ok(UNIT.expanded(-0.5).equal(new Interval(0.5, 0.5))) | ||
ok(UNIT.expanded(-0.51).equal(EMPTY)) | ||
}) | ||
|
||
test('toString', t => { | ||
equal(new Interval(2, 4.5).toString(), '[2.0000000, 4.5000000]') | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import * as Interval from './Interval' | ||
|
||
export default { Interval } |