Skip to content

Commit

Permalink
refactor(geom): add tessellate() multi-fn, move/rename tessellators
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Jan 18, 2019
1 parent 1d754eb commit 499e14b
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 177 deletions.
179 changes: 179 additions & 0 deletions packages/geom3/src/internal/tessellate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { isFunction } from "@thi.ng/checks";
import {
comp,
last,
map,
mapcat,
partition,
push,
range,
reducer,
repeat,
scan,
transduce,
tuples,
wrap
} from "@thi.ng/transducers";
import {
mixN,
ReadonlyVec,
signedArea2,
Vec
} from "@thi.ng/vectors3";
import { Tessellator } from "../api";
import { centroidRaw } from "../internal/centroid";
import { pointInTriangle2 } from "../internal/triangle-point-inside";
import { polyArea } from "../internal/poly-area";

const snip = (
points: ReadonlyVec[],
u: number,
v: number,
w: number,
n: number,
ids: number[]
) => {
const a = points[ids[u]];
const b = points[ids[v]];
const c = points[ids[w]];
if (signedArea2(a, b, c) > 0) {
for (let i = 0; i < n; i++) {
if (i !== u && i !== v && i !== w) {
if (pointInTriangle2(points[ids[i]], a, b, c)) {
return;
}
}
}
return [a, b, c];
}
};

export const tesselEarCut =
(points: ReadonlyVec[]) => {
const tris: Vec[][] = [];
let n = points.length;
const ids = [
...(polyArea(points) > 0 ?
range(n) :
range(n - 1, -1, -1))
];
let count = 2 * n - 1;
let v = n - 1, u, w, t;
while (count > 0 && n > 2) {
u = n <= v ? 0 : v;
v = u + 1;
v = n <= v ? 0 : v;
w = v + 1;
w = n <= w ? 0 : w;
t = snip(points, u, v, w, n, ids);
if (t !== undefined) {
tris.push(t);
ids.splice(v, 1);
n--;
count = 2 * n;
} else {
count--;
}
}
return tris;
};

export const tesselTriFan =
(points: ReadonlyVec[]) => {
const c = centroidRaw(points);
return transduce(
comp(
partition<Vec>(2, 1),
map(([a, b]) => [a, b, c])
),
push(),
wrap(points, 1, false, true)
);
};

export const tesselQuadFan =
(points: ReadonlyVec[]) => {
const p = centroidRaw(points);
return transduce(
comp(
partition<Vec>(3, 1),
map(([a, b, c]) => [mixN([], a, b, 0.5), b, mixN([], b, c, 0.5), p])
),
push(),
wrap(points, 1, true, true)
);
};

export const tesselEdgeSplit =
(points: ReadonlyVec[]) => {
const c = centroidRaw(points);
return transduce(
comp(
partition<Vec>(2, 1),
mapcat(([a, b]) => {
const m = mixN([], a, b, 0.5);
return [[a, m, c], [m, b, c]];
})),
push(),
wrap(points, 1, false, true)
);
};

export const tesselRimTris =
(points: ReadonlyVec[]) => {
const edgeCentroids = transduce(
comp(
partition<Vec>(2, 1),
map((e) => mixN([], e[0], e[1], 0.5))
),
push(),
wrap(points, 1, false, true)
);
return transduce(
comp(
partition<Vec[]>(2, 1),
map((t) => [t[0][0], t[1][1], t[1][0]])
),
push(),
[edgeCentroids],
wrap([...tuples(edgeCentroids, points)], 1, true, false)
);
};

export const tesselInset =
(inset = 0.5, keepInterior = false) =>
(points: ReadonlyVec[]) => {
const c = centroidRaw(points);
const inner = points.map((p) => mixN([], p, c, inset));
return transduce(
comp(
partition<Vec[]>(2, 1),
map(([[a, b], [c, d]]) => [a, b, d, c])
),
push(),
keepInterior ? [inner] : [],
wrap([...tuples(points, inner)], 1, false, true)
);
};

export function tessellatePoints(points: ReadonlyVec[], tessFn: Tessellator, iter?: number): Vec[][];
export function tessellatePoints(points: ReadonlyVec[], tessFns: Iterable<Tessellator>): Vec[][];
export function tessellatePoints(...args): Vec[][] {
return transduce(
scan(
reducer(
() => [args[0]],
(acc: Vec[][], fn: Tessellator) =>
transduce(
mapcat(fn),
push(),
acc
)
)
),
last(),
isFunction(args[1]) ?
repeat(args[1], args[2] || 1) :
args[1]
);
}
185 changes: 8 additions & 177 deletions packages/geom3/src/ops/tessellate.ts
Original file line number Diff line number Diff line change
@@ -1,179 +1,10 @@
import { isFunction } from "@thi.ng/checks";
import {
comp,
last,
map,
mapcat,
partition,
push,
range,
reducer,
repeat,
scan,
transduce,
tuples,
wrap
} from "@thi.ng/transducers";
import {
mixN,
ReadonlyVec,
signedArea2,
Vec
} from "@thi.ng/vectors3";
import { Tessellator } from "../api";
import { centroidRaw } from "../internal/centroid";
import { pointInTriangle2 } from "../internal/triangle-point-inside";
import { polyArea } from "../internal/poly-area";
import { DEFAULT, defmulti } from "@thi.ng/defmulti";
import { Vec } from "@thi.ng/vectors3";
import { IShape, Tessellator } from "../api";
import { dispatch } from "../internal/dispatch";
import { tessellatePoints } from "../internal/tessellate";
import { vertices } from "./vertices";

const snip = (
points: ReadonlyVec[],
u: number,
v: number,
w: number,
n: number,
ids: number[]
) => {
const a = points[ids[u]];
const b = points[ids[v]];
const c = points[ids[w]];
if (signedArea2(a, b, c) > 0) {
for (let i = 0; i < n; i++) {
if (i !== u && i !== v && i !== w) {
if (pointInTriangle2(points[ids[i]], a, b, c)) {
return;
}
}
}
return [a, b, c];
}
};
export const tessellate = defmulti<IShape, Tessellator[], Vec[][]>(dispatch);

export const earCut =
(points: ReadonlyVec[]) => {
const tris: Vec[][] = [];
let n = points.length;
const ids = [
...(polyArea(points) > 0 ?
range(n) :
range(n - 1, -1, -1))
];
let count = 2 * n - 1;
let v = n - 1, u, w, t;
while (count > 0 && n > 2) {
u = n <= v ? 0 : v;
v = u + 1;
v = n <= v ? 0 : v;
w = v + 1;
w = n <= w ? 0 : w;
t = snip(points, u, v, w, n, ids);
if (t !== undefined) {
tris.push(t);
ids.splice(v, 1);
n--;
count = 2 * n;
} else {
count--;
}
}
return tris;
};

export const triFan =
(points: ReadonlyVec[]) => {
const c = centroidRaw(points);
return transduce(
comp(
partition<Vec>(2, 1),
map(([a, b]) => [a, b, c])
),
push(),
wrap(points, 1, false, true)
);
};

export const quadFan =
(points: ReadonlyVec[]) => {
const p = centroidRaw(points);
return transduce(
comp(
partition<Vec>(3, 1),
map(([a, b, c]) => [mixN([], a, b, 0.5), b, mixN([], b, c, 0.5), p])
),
push(),
wrap(points, 1, true, true)
);
};

export const edgeSplit =
(points: ReadonlyVec[]) => {
const c = centroidRaw(points);
return transduce(
comp(
partition<Vec>(2, 1),
mapcat(([a, b]) => {
const m = mixN([], a, b, 0.5);
return [[a, m, c], [m, b, c]];
})),
push(),
wrap(points, 1, false, true)
);
};

export const rimTris =
(points: ReadonlyVec[]) => {
const edgeCentroids = transduce(
comp(
partition<Vec>(2, 1),
map((e) => mixN([], e[0], e[1], 0.5))
),
push(),
wrap(points, 1, false, true)
);
return transduce(
comp(
partition<Vec[]>(2, 1),
map((t) => [t[0][0], t[1][1], t[1][0]])
),
push(),
[edgeCentroids],
wrap([...tuples(edgeCentroids, points)], 1, true, false)
);
};

export const inset =
(inset = 0.5, keepInterior = false) =>
(points: ReadonlyVec[]) => {
const c = centroidRaw(points);
const inner = points.map((p) => mixN([], p, c, inset));
return transduce(
comp(
partition<Vec[]>(2, 1),
map(([[a, b], [c, d]]) => [a, b, d, c])
),
push(),
keepInterior ? [inner] : [],
wrap([...tuples(points, inner)], 1, false, true)
);
};

export function tessellatePoints(points: ReadonlyVec[], tessFn: Tessellator, iter?: number): Vec[][];
export function tessellatePoints(points: ReadonlyVec[], tessFns: Iterable<Tessellator>): Vec[][];
export function tessellatePoints(...args): Vec[][] {
return transduce(
scan(
reducer(
() => [args[0]],
(acc: Vec[][], fn: Tessellator) =>
transduce(
mapcat(fn),
push(),
acc
)
)
),
last(),
isFunction(args[1]) ?
repeat(args[1], args[2] || 1) :
args[1]
);
}
tessellate.add(DEFAULT, ($, fns) => tessellatePoints(vertices($), fns));

0 comments on commit 499e14b

Please sign in to comment.