feat: yay, math
This commit is contained in:
parent
5eedb539da
commit
a61f7c79af
|
@ -22,7 +22,8 @@
|
|||
"@latus/core": "^2.0.0",
|
||||
"autoprefixer": "^9.8.6",
|
||||
"d3-quadtree": "^2.0.0",
|
||||
"debug": "4.3.1"
|
||||
"debug": "4.3.1",
|
||||
"graham_scan": "^1.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@neutrinojs/airbnb": "^9.4.0",
|
||||
|
|
47
packages/math/src/floodwalk.js
Normal file
47
packages/math/src/floodwalk.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
export default function floodwalk2D(data, position, w, h) {
|
||||
const n = data.length;
|
||||
const [x, y] = position;
|
||||
let i = x + w * y;
|
||||
if (i < 0 || i >= n) {
|
||||
return [];
|
||||
}
|
||||
const b = data[i];
|
||||
const points = [];
|
||||
const seen = [];
|
||||
seen[-1] = true;
|
||||
seen[i] = true;
|
||||
const todo = [i];
|
||||
while (todo.length > 0) {
|
||||
i = todo.pop();
|
||||
const v = data[i];
|
||||
if (b !== v) {
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
points.push(i);
|
||||
const xx = i % w;
|
||||
const yy = Math.floor(i / w);
|
||||
const us = yy >= 1;
|
||||
const rs = xx + 1 < w;
|
||||
const ds = yy + 1 < h;
|
||||
const ls = xx >= 1;
|
||||
const sel = [
|
||||
us ? i - w : -1,
|
||||
rs ? i + 1 : -1,
|
||||
ds ? i + w : -1,
|
||||
ls ? i - 1 : -1,
|
||||
us && rs ? i - w + 1 : -1,
|
||||
rs && ds ? i + w + 1 : -1,
|
||||
ds && ls ? i + w - 1 : -1,
|
||||
ls && us ? i - w - 1 : -1,
|
||||
];
|
||||
for (let i = 0; i < sel.length; ++i) {
|
||||
const j = sel[i];
|
||||
if (!seen[j]) {
|
||||
todo.push(j);
|
||||
seen[j] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return points;
|
||||
}
|
|
@ -4,6 +4,7 @@ import types from './types';
|
|||
import * as Vector from './vector';
|
||||
|
||||
export * from './math';
|
||||
export {default as floodwalk2D} from './floodwalk';
|
||||
export {default as QuadTree} from './quadtree';
|
||||
export {noise, noiseSeed} from './noise';
|
||||
export {default as Range} from './range';
|
||||
|
|
|
@ -142,6 +142,10 @@ export function round(v) {
|
|||
return [Math.round(v[0]), Math.round(v[1])];
|
||||
}
|
||||
|
||||
export function cross(l, r) {
|
||||
return l[0] * r[1] - l[1] * r[0];
|
||||
}
|
||||
|
||||
// Get the dot product of two vectors. If only one gievn, dupe it.
|
||||
//
|
||||
// avocado> Vector.dot [2, 3], [4, 5]
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import GrahamScan from 'graham_scan';
|
||||
|
||||
import * as Vector from './vector';
|
||||
|
||||
// Vertice operations.
|
||||
|
@ -5,6 +7,10 @@ import * as Vector from './vector';
|
|||
// **Vertice** is a utility class to help with vertice operations. A vertice
|
||||
// is implemented as a 2-element array. Element 0 is *x* and element 1 is *y*.
|
||||
|
||||
export function isCollinear(p1, p2, p3) {
|
||||
return (p1[1] - p2[1]) * (p1[0] - p3[0]) === (p1[1] - p3[1]) * (p1[0] - p2[0]);
|
||||
}
|
||||
|
||||
// Translate a vertice from an origin point using rotation and scale.
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export function translate(vertice, origin, rotation = 0, scale = 1) {
|
||||
|
@ -20,3 +26,178 @@ export function translate(vertice, origin, rotation = 0, scale = 1) {
|
|||
),
|
||||
), scale);
|
||||
}
|
||||
|
||||
export function convexHull(vertices) {
|
||||
const scanner = new GrahamScan();
|
||||
for (let i = 0; i < vertices.length; i++) {
|
||||
const [x, y] = vertices[i];
|
||||
scanner.addPoint(x, y);
|
||||
}
|
||||
return scanner.getHull()
|
||||
.map(({x, y}) => [x, y]);
|
||||
}
|
||||
|
||||
const c = [
|
||||
Vector.fromDirection(0),
|
||||
Vector.fromDirection(1),
|
||||
Vector.fromDirection(2),
|
||||
Vector.fromDirection(3),
|
||||
];
|
||||
|
||||
export function ortho(vs) {
|
||||
if (vs.length < 4) {
|
||||
throw new TypeError('Vertice.ortho(): vs.length < 4');
|
||||
}
|
||||
const _index = {};
|
||||
for (let i = 0; i < vs.length; i++) {
|
||||
const [x, y] = vs[i];
|
||||
if (!_index[y]) {
|
||||
_index[y] = {};
|
||||
}
|
||||
_index[y][x] = vs[i];
|
||||
}
|
||||
const index = ([x, y]) => _index[y]?.[x];
|
||||
const sorted = [vs[0]];
|
||||
let d = 0;
|
||||
let walk = 0;
|
||||
// eslint-disable-next-line no-labels, no-restricted-syntax
|
||||
select:
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
// eslint-disable-next-line no-bitwise
|
||||
const s = (i + d + 3) & 3;
|
||||
const v = index(Vector.add(vs[walk], c[s]));
|
||||
if (vs[0] === v) {
|
||||
return sorted;
|
||||
}
|
||||
if (v && -1 === sorted.findIndex((v2) => v === v2)) {
|
||||
d = s;
|
||||
sorted.push(v);
|
||||
walk = vs.findIndex((v2) => v === v2);
|
||||
// eslint-disable-next-line no-continue, no-labels, no-extra-label
|
||||
continue select;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function removeCollinear(vs) {
|
||||
const trimmed = [];
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
vs = vs.concat();
|
||||
let i = 0;
|
||||
if (vs.length < 3) {
|
||||
return vs;
|
||||
}
|
||||
vs.push(vs[0]);
|
||||
vs.push(vs[1]);
|
||||
while (i < vs.length - 2) {
|
||||
if (isCollinear(vs[i], vs[i + 1], vs[i + 2])) {
|
||||
vs.splice(i + 1, 1);
|
||||
}
|
||||
else {
|
||||
trimmed.push(vs[i]);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
export function smooth(vs) {
|
||||
const smoothed = [];
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
const sorted = ortho(unique(vs));
|
||||
if (0 === sorted.length) {
|
||||
return [];
|
||||
}
|
||||
sorted.push(sorted[0]);
|
||||
let i = 0;
|
||||
while (i < sorted.length - 2) {
|
||||
const f = Vector.sub(sorted[i + 1], sorted[i]);
|
||||
const g = Vector.sub(sorted[i + 2], sorted[i + 1]);
|
||||
const o = Math.atan2(Vector.cross(f, g), Vector.dot(f, g));
|
||||
if (Math.abs(o) === Math.PI / 2 || Math.abs(o) === Math.PI / 4) {
|
||||
const h = Vector.add(sorted[i + 1], Vector.scale(Vector.sub(g, f), 0.5));
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
if (0 === windingNumber(h, sorted)) {
|
||||
sorted.splice(i + 1, 1);
|
||||
i -= 1;
|
||||
}
|
||||
else {
|
||||
smoothed.push(sorted[i]);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
smoothed.push(sorted[i]);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
smoothed.push(sorted[i]);
|
||||
smoothed.push(sorted[i + 1]);
|
||||
return smoothed;
|
||||
}
|
||||
|
||||
export function sort(vs) {
|
||||
let mx = 0;
|
||||
let my = 0;
|
||||
for (let i = 0; i < vs.length; ++i) {
|
||||
const [x, y] = vs[i];
|
||||
mx += x;
|
||||
my += y;
|
||||
}
|
||||
const m = Vector.scale([mx, my], 1 / vs.length);
|
||||
return vs.sort((l, r) => {
|
||||
const ol = Vector.toRadians(Vector.sub(m, l));
|
||||
const or = Vector.toRadians(Vector.sub(m, r));
|
||||
if (ol < or) {
|
||||
return -1;
|
||||
}
|
||||
if (ol > or) {
|
||||
return 1;
|
||||
}
|
||||
return Vector.distance(vs[0], l) > Vector.distance(vs[0], r) ? -1 : 1;
|
||||
});
|
||||
}
|
||||
|
||||
export function unique(vs) {
|
||||
const record = {};
|
||||
const unique = [];
|
||||
for (let i = 0; i < vs.length; ++i) {
|
||||
const [x, y] = vs[i];
|
||||
if (!record[y]) {
|
||||
record[y] = {};
|
||||
}
|
||||
if (!record[y][x]) {
|
||||
unique.push([x, y]);
|
||||
record[y][x] = true;
|
||||
}
|
||||
}
|
||||
return unique;
|
||||
}
|
||||
|
||||
// ty http://geomalgorithms.com/a03-_inclusion.html
|
||||
const isLeft = (p0, p1, p2) => (
|
||||
(p1[0] - p0[0]) * (p2[1] - p0[1]) - (p2[0] - p0[0]) * (p1[1] - p0[1])
|
||||
);
|
||||
|
||||
export const windingNumber = (p, vs) => {
|
||||
let wn = 0;
|
||||
for (let i = 0; i < vs.length - 1; ++i) {
|
||||
if (vs[i][1] <= p[1]) {
|
||||
if (vs[i + 1][1] > p[1]) {
|
||||
if (isLeft(vs[i], vs[i + 1], p) > 0) {
|
||||
wn += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (vs[i + 1][1] <= p[1]) {
|
||||
if (isLeft(vs[i], vs[i + 1], p) < 0) {
|
||||
wn -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return wn;
|
||||
};
|
||||
|
|
27
packages/math/test/floodwalk.js
Normal file
27
packages/math/test/floodwalk.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
require('source-map-support').install();
|
||||
|
||||
import {expect} from 'chai';
|
||||
|
||||
import floodwalk from '../src/floodwalk';
|
||||
|
||||
describe('Floodwalk', () => {
|
||||
it('can walk', () => {
|
||||
const data = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 0, 1, 1, 1, 0,
|
||||
1, 1, 1, 1, 0, 1, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 1,
|
||||
];
|
||||
let correct = data.reduce((r, v, i) => r.concat(v ? [+i] : []), []);
|
||||
expect(
|
||||
floodwalk(data, [1, 1], 8, 4)
|
||||
.sort((l, r) => l < r ? -1 : 1)
|
||||
).to.deep.equal(correct);
|
||||
data[12] = 0;
|
||||
correct = [9, 10, 16, 17, 18, 19];
|
||||
expect(
|
||||
floodwalk(data, [1, 1], 8, 4)
|
||||
.sort((l, r) => l < r ? -1 : 1)
|
||||
).to.deep.equal(correct);
|
||||
});
|
||||
});
|
|
@ -62,9 +62,9 @@ describe('Rectangle', function() {
|
|||
expect(Rectangle.floor([3.14, 4.70, 5.32, 1.8])).to.deep.equal([3, 4, 5, 1]);
|
||||
});
|
||||
it('can test inside', () => {
|
||||
console.log(Rectangle.isInside(
|
||||
expect(Rectangle.isInside(
|
||||
[-22.55753963192675, -36.22420629859356, 64, 64],
|
||||
[-6.557539631926751, -20.224206298593554, 32, 32],
|
||||
));
|
||||
)).to.be.true;
|
||||
});
|
||||
});
|
||||
|
|
13
packages/math/test/vertice.js
Normal file
13
packages/math/test/vertice.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
require('source-map-support').install();
|
||||
|
||||
import {expect} from 'chai';
|
||||
|
||||
import * as Vertice from '../src/vertice';
|
||||
|
||||
describe('Vertice', () => {
|
||||
it('can compute convex hull', () => {
|
||||
const vertices = [[0, 0], [5, 0], [5, 5], [10, 5], [10, 10], [0, 10]];
|
||||
const hull = [[0, 0], [5, 0], [10, 5], [10, 10], [0, 10]];
|
||||
expect(Vertice.convexHull(vertices)).to.deep.equal(hull);
|
||||
});
|
||||
});
|
|
@ -3363,6 +3363,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
|
|||
resolved "http://npm.cha0sdev/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
||||
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
|
||||
|
||||
graham_scan@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "http://npm.cha0sdev/graham_scan/-/graham_scan-1.0.4.tgz#399651dd1f8353e1880f59ea8e5f94b9df4c928a"
|
||||
integrity sha1-OZZR3R+DU+GID1nqjl+Uud9Mkoo=
|
||||
|
||||
growl@1.10.5:
|
||||
version "1.10.5"
|
||||
resolved "http://npm.cha0sdev/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
|
||||
|
|
Loading…
Reference in New Issue
Block a user