Skip to content

Commit

Permalink
update 0.0.10
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewjacobson committed Nov 6, 2024
1 parent 955e740 commit 2237df0
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 33 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
dist
.idea
.DS_STORE
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@stitchables/stitchjs",
"license": "MIT",
"version": "0.0.9",
"version": "0.0.10",
"main": "dist/stitch.js",
"module": "dist/stitch.mjs",
"types": "dist/stitch.d.ts",
Expand Down
131 changes: 131 additions & 0 deletions src/Math/DensityGrid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { Vector } from './Vector';

export class DensityGrid {
width: number;
height: number;
binSize: number;
xBinCount: number;
yBinCount: number;
bins: number[];
maxDensity: number;
constructor(width: number, height: number, binSize: number) {
this.width = width;
this.height = height;
this.binSize = binSize;
this.xBinCount = Math.ceil(this.width / this.binSize);
this.yBinCount = Math.ceil(this.height / this.binSize);
this.bins = Array(this.xBinCount * this.yBinCount).fill(0);
this.maxDensity = 0;
}
getDensityAtBin(x: number, y: number): number {
if (x < 0 || x >= this.xBinCount || y < 0 || y >= this.yBinCount) return 0;
return this.bins[Math.floor(y) * this.xBinCount + Math.floor(x)];
}
getDensityAtPoint(x: number, y: number): number {
return this.getDensityAtBin(x / this.binSize, y / this.binSize);
}
addDensityAtBin(x: number, y: number, density: number): boolean {
if (x >= 0 && x < this.xBinCount && y >= 0 && y < this.yBinCount) {
this.bins[Math.floor(y) * this.xBinCount + Math.floor(x)] += density;
if (this.bins[Math.floor(y) * this.xBinCount + Math.floor(x)] > this.maxDensity) {
this.maxDensity = this.bins[Math.floor(y) * this.xBinCount + Math.floor(x)];
}
return true;
}
return false;
}
addDensityAtPoint(x: number, y: number, density: number): boolean {
return this.addDensityAtBin(x / this.binSize, y / this.binSize, density);
}
// https://www.geeksforgeeks.org/anti-aliased-line-xiaolin-wus-algorithm/
addDensityXW(p: Vector, q: Vector, rejectionLimit: number): boolean {
function iPartOfNumber(x: number): number {
return Math.floor(x);
}
function fPartOfNumber(x: number): number {
return x > 0 ? x - iPartOfNumber(x) : x - (iPartOfNumber(x) + 1);
}
function rfPartOfNumber(x: number): number {
return 1 - fPartOfNumber(x);
}
function getDensityAdditions(
x0: number,
y0: number,
x1: number,
y1: number,
): { x: number; y: number; density: number }[] {
let densityAdditions = [];
const steep = Math.abs(y1 - y0) > Math.abs(x1 - x0);
if (steep) {
[x0, y0] = [y0, x0];
[x1, y1] = [y1, x1];
}
if (x0 > x1) {
[x0, x1] = [x1, x0];
[y0, y1] = [y1, y0];
}
const [dx, dy] = [x1 - x0, y1 - y0];
const gradient = dy / dx || 1;
let [xpxl1, xpxl2] = [Math.floor(x0), Math.floor(x1)];
let intersectY = y0;
if (steep) {
for (let x = xpxl1; x <= xpxl2; x++) {
densityAdditions.push({
x: Math.floor(intersectY),
y: x,
density: rfPartOfNumber(intersectY),
});
densityAdditions.push({
x: Math.floor(intersectY) - 1,
y: x,
density: fPartOfNumber(intersectY),
});
intersectY += gradient;
}
} else {
for (let x = xpxl1; x <= xpxl2; x++) {
densityAdditions.push({
x: x,
y: Math.floor(intersectY),
density: rfPartOfNumber(intersectY),
});
densityAdditions.push({
x: x,
y: Math.floor(intersectY) - 1,
density: fPartOfNumber(intersectY),
});
intersectY += gradient;
}
}
return densityAdditions;
}
const densityAdditions = getDensityAdditions(
p.x / this.binSize,
p.y / this.binSize,
q.x / this.binSize,
q.y / this.binSize,
);
let isRejected = false;
for (let densityAddition of densityAdditions) {
if (
this.getDensityAtBin(densityAddition.x, densityAddition.y) +
densityAddition.density >
rejectionLimit
) {
isRejected = true;
break;
}
}
if (!isRejected) {
for (let densityAddition of densityAdditions) {
this.addDensityAtBin(
densityAddition.x,
densityAddition.y,
1 - densityAddition.density,
);
}
return true;
}
return false;
}
}
65 changes: 34 additions & 31 deletions src/Math/Polyline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,21 +95,13 @@ export class Polyline {
}
getRounded(radius: number, stepAngle = 0.1): Polyline {
const roundedPolyline = new Polyline(this.isClosed);
if (!this.isClosed) roundedPolyline.addVertex(this.vertices[0].x, this.vertices[0].y);
for (
let i = this.isClosed ? 0 : 1;
this.isClosed ? i < this.vertices.length : i < this.vertices.length - 1;
i++
) {
const A =
this.vertices[(i - 1 + this.vertices.length) % this.vertices.length].copy();
const B = this.vertices[i].copy();
const C = this.vertices[(i + 1) % this.vertices.length].copy();
const getRoundedVertex = function (A: Vector, B: Vector, C: Vector): Vector[] {
const result: Vector[] = [];
const BA = A.subtract(B);
const BC = C.subtract(B);
const BAnorm = BA.copy().normalized();
const BCnorm = BC.copy().normalized();
const sinA = -BAnorm.dot(BCnorm.copy().rotate(Math.PI / 2));
const BAnorm = BA.normalized();
const BCnorm = BC.normalized();
const sinA = -BAnorm.dot(BCnorm.rotate(Math.PI / 2));
const sinA90 = BAnorm.dot(BCnorm);
let angle = Math.asin(sinA);
let radDirection = 1;
Expand Down Expand Up @@ -142,46 +134,57 @@ export class Polyline {
1) *
2 *
Math.PI;
if (Math.abs(toAngle - fromAngle) < 0.01) continue;
if (Math.abs(toAngle - fromAngle) < 0.01) return result;
if (!drawDirection) {
if (fromAngle < toAngle) {
for (let a = fromAngle; a < toAngle; a += stepAngle) {
roundedPolyline.addVertex(
cRadius * Math.cos(a) + x,
cRadius * Math.sin(a) + y,
);
result.push(new Vector(cRadius * Math.cos(a) + x, cRadius * Math.sin(a) + y));
}
} else {
for (let a = fromAngle; a < toAngle + 2 * Math.PI; a += stepAngle) {
roundedPolyline.addVertex(
cRadius * Math.cos(a) + x,
cRadius * Math.sin(a) + y,
);
result.push(new Vector(cRadius * Math.cos(a) + x, cRadius * Math.sin(a) + y));
}
}
} else {
if (fromAngle > toAngle) {
for (let a = fromAngle; a > toAngle; a -= stepAngle) {
roundedPolyline.addVertex(
cRadius * Math.cos(a) + x,
cRadius * Math.sin(a) + y,
);
result.push(new Vector(cRadius * Math.cos(a) + x, cRadius * Math.sin(a) + y));
}
} else {
for (let a = fromAngle; a > toAngle - 2 * Math.PI; a -= stepAngle) {
roundedPolyline.addVertex(
cRadius * Math.cos(a) + x,
cRadius * Math.sin(a) + y,
);
result.push(new Vector(cRadius * Math.cos(a) + x, cRadius * Math.sin(a) + y));
}
}
}
return result;
};

for (let i = 1; i < this.vertices.length - 1; i++) {
const A = this.vertices[(i - 1 + this.vertices.length) % this.vertices.length];
const B = this.vertices[i];
const C = this.vertices[(i + 1) % this.vertices.length];
let section = getRoundedVertex(A, B, C);
for (const v of section) {
roundedPolyline.addVertex(v.x, v.y);
}
}
if (!this.isClosed)

if (this.isClosed) {
let A = roundedPolyline.vertices[roundedPolyline.vertices.length - 1];
let B = this.vertices[0];
let C = roundedPolyline.vertices[0];
let section = getRoundedVertex(A, B, C);
for (const v of section) {
roundedPolyline.addVertex(v.x, v.y);
}
} else {
roundedPolyline.addVertex(this.vertices[0].x, this.vertices[0].y, true);
roundedPolyline.addVertex(
this.vertices[this.vertices.length - 1].x,
this.vertices[this.vertices.length - 1].y,
);
}

return roundedPolyline;
}
getResampled(spacing: number): Polyline {
Expand Down
100 changes: 100 additions & 0 deletions src/Math/Quadtree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { BoundingBox } from './BoundingBox';
import { Vector } from './Vector';

export class Quadtree {
boundingBox: BoundingBox;
capacity: number;
points: Vector[];
divided: boolean;
northeast: Quadtree | null = null;
northwest: Quadtree | null = null;
southeast: Quadtree | null = null;
southwest: Quadtree | null = null;

constructor(boundingBox: BoundingBox, capacity: number) {
this.boundingBox = boundingBox;
this.capacity = capacity;
this.points = [];
this.divided = false;
}

insert(x: number, y: number): boolean {
if (!this.boundingBox.contains(x, y)) {
return false;
}
const point = new Vector(x, y);
if (this.points.length < this.capacity) {
this.points.push(point);
return true;
} else {
if (!this.divided) {
this.subdivide();
}
// if (this.northeast?.insert(point.x, point.y)) {
// return true;
// }
// if (this.northwest?.insert(point.x, point.y)) {
// return true;
// }
// if (this.southeast?.insert(point.x, point.y)) {
// return true;
// }
// if (this.southwest?.insert(point.x, point.y)) {
// return true;
// }
// return false;
return <boolean>(
(this.northeast?.insert(point.x, point.y) ||
this.northwest?.insert(point.x, point.y) ||
this.southeast?.insert(point.x, point.y) ||
this.southwest?.insert(point.x, point.y))
);
}
}

subdivide(): void {
const w = new Vector(this.boundingBox.width, 0);
const h = new Vector(0, this.boundingBox.height);
const hw = new Vector(this.boundingBox.halfWidth, 0);
const hh = new Vector(0, this.boundingBox.halfHeight);
const ne = new BoundingBox(
this.boundingBox.min.add(hw),
this.boundingBox.min.add(w.add(hh)),
);
const nw = new BoundingBox(
this.boundingBox.min.copy(),
this.boundingBox.min.add(hw.add(hh)),
);
const se = new BoundingBox(
this.boundingBox.min.add(hw.add(hh)),
this.boundingBox.min.add(w.add(h)),
);
const sw = new BoundingBox(
this.boundingBox.min.add(hh),
this.boundingBox.min.add(hw.add(h)),
);
this.northeast = new Quadtree(ne, this.capacity);
this.northwest = new Quadtree(nw, this.capacity);
this.southeast = new Quadtree(se, this.capacity);
this.southwest = new Quadtree(sw, this.capacity);
this.divided = true;
}

query(range: BoundingBox, found: Vector[] = []): Vector[] {
if (!this.boundingBox.intersects(range)) {
return found;
}
for (const point of this.points) {
if (range.contains(point.x, point.y)) {
found.push(point);
}
}
if (this.divided) {
this.northeast?.query(range, found);
this.northwest?.query(range, found);
this.southeast?.query(range, found);
this.southwest?.query(range, found);
}
return found;
}
}
4 changes: 3 additions & 1 deletion src/Math/Random.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ function sfc32(uint128Hex: string): () => number {
}

export class Random {
hash: string;
useA: boolean;
prngA: () => number;
prngB: () => number;
constructor(hash: string) {
if (hash === undefined) {
if (!hash) {
hash = this.randomHash();
}
this.useA = false;
Expand All @@ -33,6 +34,7 @@ export class Random {
this.prngA();
this.prngB();
}
this.hash = hash;
}
randomHash(): string {
let hash = '0x';
Expand Down
13 changes: 13 additions & 0 deletions src/Math/Utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Vector } from './Vector';
import { Random } from './Random';

export const Utils = {
constrain: function (value: number, low: number, high: number) {
Expand Down Expand Up @@ -164,4 +165,16 @@ export const Utils = {
mmToIn: function (millimeters: number) {
return millimeters / 25.4;
},
getShuffledArray(array: any[], rng: Random): any[] {
const shuffledArray = array.slice();
let currentIndex = shuffledArray.length;
while (currentIndex !== 0) {
let randomIndex = Math.floor(rng.random_dec() * currentIndex--);
[shuffledArray[currentIndex], shuffledArray[randomIndex]] = [
shuffledArray[randomIndex],
shuffledArray[currentIndex],
];
}
return shuffledArray;
},
};
Loading

0 comments on commit 2237df0

Please sign in to comment.