@aethervtt/shard-dice 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +2 -2
- package/dist/attachments.d.ts +79 -0
- package/dist/attachments.js +43 -0
- package/dist/builtins.d.ts +25 -0
- package/dist/builtins.js +335 -0
- package/dist/cells.d.ts +33 -0
- package/dist/cells.js +53 -0
- package/dist/components.d.ts +14 -0
- package/dist/components.js +27 -0
- package/dist/definition-asset.d.ts +19 -0
- package/dist/definition-asset.js +60 -0
- package/dist/definition.d.ts +98 -0
- package/dist/definition.js +320 -0
- package/dist/dice-worker.d.ts +1 -0
- package/dist/dice-worker.js +3 -0
- package/dist/effects.d.ts +97 -0
- package/dist/effects.js +196 -0
- package/dist/entrances.d.ts +48 -0
- package/dist/entrances.js +54 -0
- package/dist/families.d.ts +17 -0
- package/dist/families.js +131 -0
- package/dist/glyphs.d.ts +29 -0
- package/dist/glyphs.js +270 -0
- package/dist/golden.json +17 -0
- package/dist/hull.d.ts +32 -0
- package/dist/hull.js +154 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +322 -0
- package/dist/landing.d.ts +29 -0
- package/dist/landing.js +93 -0
- package/dist/layout.d.ts +75 -0
- package/dist/layout.js +137 -0
- package/dist/marks.d.ts +54 -0
- package/dist/marks.js +543 -0
- package/dist/material.d.ts +80 -0
- package/dist/material.js +343 -0
- package/dist/math.d.ts +39 -0
- package/dist/math.js +136 -0
- package/dist/mesh.d.ts +9 -0
- package/dist/mesh.js +105 -0
- package/dist/methods.d.ts +2 -0
- package/dist/methods.js +42 -0
- package/dist/numerals.d.ts +9 -0
- package/dist/numerals.js +47 -0
- package/dist/plugin.d.ts +41 -0
- package/dist/plugin.js +174 -0
- package/dist/preview.d.ts +32 -0
- package/dist/preview.js +123 -0
- package/dist/quality.d.ts +12 -0
- package/dist/quality.js +18 -0
- package/dist/resources.d.ts +85 -0
- package/dist/resources.js +191 -0
- package/dist/roll.d.ts +68 -0
- package/dist/roll.js +97 -0
- package/dist/settle.d.ts +53 -0
- package/dist/settle.js +96 -0
- package/dist/skin.d.ts +61 -0
- package/dist/skin.js +303 -0
- package/dist/sound.d.ts +33 -0
- package/dist/sound.js +296 -0
- package/dist/studio.d.ts +3 -0
- package/dist/studio.js +48 -0
- package/dist/table.d.ts +225 -0
- package/dist/table.js +1757 -0
- package/dist/test-worker.d.ts +1 -0
- package/dist/test-worker.js +12 -0
- package/dist/track.d.ts +70 -0
- package/dist/track.js +304 -0
- package/dist/tray.d.ts +2 -0
- package/dist/tray.js +43 -0
- package/dist/variants.d.ts +9 -0
- package/dist/variants.js +46 -0
- package/dist/windows.d.ts +25 -0
- package/dist/windows.js +77 -0
- package/dist/worker.d.ts +6 -0
- package/dist/worker.js +9 -0
- package/package.json +47 -4
package/dist/glyphs.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { ShardError } from "@aethervtt/shard-core";
|
|
2
|
+
const glyphs = /* @__PURE__ */ new Map();
|
|
3
|
+
function parseError(name, message) {
|
|
4
|
+
return new ShardError("dice/invalid-glyph", `Glyph "${name}": ${message}`, {
|
|
5
|
+
hint: "Glyph paths are SVG path data (M, L, H, V, C, S, Q, T, A, Z)."
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
function arcToCubics(out, x1, y1, rxIn, ryIn, angle, large, sweep, x2, y2) {
|
|
9
|
+
if (x1 === x2 && y1 === y2) return;
|
|
10
|
+
let rx = Math.abs(rxIn);
|
|
11
|
+
let ry = Math.abs(ryIn);
|
|
12
|
+
if (rx === 0 || ry === 0) {
|
|
13
|
+
out.push({ type: "L", x: x2, y: y2 });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const phi = angle * Math.PI / 180;
|
|
17
|
+
const cos = Math.cos(phi);
|
|
18
|
+
const sin = Math.sin(phi);
|
|
19
|
+
const dx = (x1 - x2) / 2;
|
|
20
|
+
const dy = (y1 - y2) / 2;
|
|
21
|
+
const xp = cos * dx + sin * dy;
|
|
22
|
+
const yp = -sin * dx + cos * dy;
|
|
23
|
+
const lambda = xp * xp / (rx * rx) + yp * yp / (ry * ry);
|
|
24
|
+
if (lambda > 1) {
|
|
25
|
+
rx *= Math.sqrt(lambda);
|
|
26
|
+
ry *= Math.sqrt(lambda);
|
|
27
|
+
}
|
|
28
|
+
const num = rx * rx * ry * ry - rx * rx * yp * yp - ry * ry * xp * xp;
|
|
29
|
+
const den = rx * rx * yp * yp + ry * ry * xp * xp;
|
|
30
|
+
const k = (large === sweep ? -1 : 1) * Math.sqrt(Math.max(0, num / den));
|
|
31
|
+
const cxp = k * rx * yp / ry;
|
|
32
|
+
const cyp = -k * ry * xp / rx;
|
|
33
|
+
const cx = cos * cxp - sin * cyp + (x1 + x2) / 2;
|
|
34
|
+
const cy = sin * cxp + cos * cyp + (y1 + y2) / 2;
|
|
35
|
+
const vAngle = (ux, uy, vx, vy) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
|
|
36
|
+
const t1 = vAngle(1, 0, (xp - cxp) / rx, (yp - cyp) / ry);
|
|
37
|
+
let dt = vAngle((xp - cxp) / rx, (yp - cyp) / ry, (-xp - cxp) / rx, (-yp - cyp) / ry);
|
|
38
|
+
if (!sweep && dt > 0) dt -= Math.PI * 2;
|
|
39
|
+
if (sweep && dt < 0) dt += Math.PI * 2;
|
|
40
|
+
const segments = Math.ceil(Math.abs(dt) / (Math.PI / 2));
|
|
41
|
+
const step = dt / segments;
|
|
42
|
+
const kappa = 4 / 3 * Math.tan(step / 4);
|
|
43
|
+
const point = (t) => {
|
|
44
|
+
const ex = rx * Math.cos(t);
|
|
45
|
+
const ey = ry * Math.sin(t);
|
|
46
|
+
return [cos * ex - sin * ey + cx, sin * ex + cos * ey + cy];
|
|
47
|
+
};
|
|
48
|
+
const deriv = (t) => {
|
|
49
|
+
const ex = -rx * Math.sin(t);
|
|
50
|
+
const ey = ry * Math.cos(t);
|
|
51
|
+
return [cos * ex - sin * ey, sin * ex + cos * ey];
|
|
52
|
+
};
|
|
53
|
+
for (let i = 0; i < segments; i++) {
|
|
54
|
+
const a = t1 + i * step;
|
|
55
|
+
const b = a + step;
|
|
56
|
+
const [ax, ay] = point(a);
|
|
57
|
+
const [bx, by] = point(b);
|
|
58
|
+
const [dax, day] = deriv(a);
|
|
59
|
+
const [dbx, dby] = deriv(b);
|
|
60
|
+
out.push({
|
|
61
|
+
type: "C",
|
|
62
|
+
x1: ax + kappa * dax,
|
|
63
|
+
y1: ay + kappa * day,
|
|
64
|
+
x2: bx - kappa * dbx,
|
|
65
|
+
y2: by - kappa * dby,
|
|
66
|
+
x: bx,
|
|
67
|
+
y: by
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function parseSvgPath(d, name = "path") {
|
|
72
|
+
const tokens = d.match(/[a-zA-Z]|-?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?/g) ?? [];
|
|
73
|
+
const out = [];
|
|
74
|
+
let i = 0;
|
|
75
|
+
let cmd = "";
|
|
76
|
+
let x = 0;
|
|
77
|
+
let y = 0;
|
|
78
|
+
let sx = 0;
|
|
79
|
+
let sy = 0;
|
|
80
|
+
let cx = 0;
|
|
81
|
+
let cy = 0;
|
|
82
|
+
let prev = "";
|
|
83
|
+
const num = () => {
|
|
84
|
+
const t = tokens[i++];
|
|
85
|
+
if (t === void 0 || /[a-zA-Z]/.test(t))
|
|
86
|
+
throw parseError(name, `expected a number after ${cmd}`);
|
|
87
|
+
return Number(t);
|
|
88
|
+
};
|
|
89
|
+
const flag = () => num() !== 0;
|
|
90
|
+
while (i < tokens.length) {
|
|
91
|
+
const t = tokens[i];
|
|
92
|
+
if (/[a-zA-Z]/.test(t)) {
|
|
93
|
+
cmd = t;
|
|
94
|
+
i++;
|
|
95
|
+
} else if (!cmd) throw parseError(name, "path data must start with a command");
|
|
96
|
+
const rel = cmd === cmd.toLowerCase();
|
|
97
|
+
const ox = rel ? x : 0;
|
|
98
|
+
const oy = rel ? y : 0;
|
|
99
|
+
switch (cmd.toUpperCase()) {
|
|
100
|
+
case "M": {
|
|
101
|
+
x = ox + num();
|
|
102
|
+
y = oy + num();
|
|
103
|
+
sx = x;
|
|
104
|
+
sy = y;
|
|
105
|
+
out.push({ type: "M", x, y });
|
|
106
|
+
cmd = rel ? "l" : "L";
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case "L":
|
|
110
|
+
x = ox + num();
|
|
111
|
+
y = oy + num();
|
|
112
|
+
out.push({ type: "L", x, y });
|
|
113
|
+
break;
|
|
114
|
+
case "H":
|
|
115
|
+
x = ox + num();
|
|
116
|
+
out.push({ type: "L", x, y });
|
|
117
|
+
break;
|
|
118
|
+
case "V":
|
|
119
|
+
y = oy + num();
|
|
120
|
+
out.push({ type: "L", x, y });
|
|
121
|
+
break;
|
|
122
|
+
case "C": {
|
|
123
|
+
const x1 = ox + num();
|
|
124
|
+
const y1 = oy + num();
|
|
125
|
+
cx = ox + num();
|
|
126
|
+
cy = oy + num();
|
|
127
|
+
x = ox + num();
|
|
128
|
+
y = oy + num();
|
|
129
|
+
out.push({ type: "C", x1, y1, x2: cx, y2: cy, x, y });
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case "S": {
|
|
133
|
+
const reflect = prev === "C" || prev === "S";
|
|
134
|
+
const x1 = reflect ? 2 * x - cx : x;
|
|
135
|
+
const y1 = reflect ? 2 * y - cy : y;
|
|
136
|
+
cx = ox + num();
|
|
137
|
+
cy = oy + num();
|
|
138
|
+
x = ox + num();
|
|
139
|
+
y = oy + num();
|
|
140
|
+
out.push({ type: "C", x1, y1, x2: cx, y2: cy, x, y });
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case "Q":
|
|
144
|
+
cx = ox + num();
|
|
145
|
+
cy = oy + num();
|
|
146
|
+
x = ox + num();
|
|
147
|
+
y = oy + num();
|
|
148
|
+
out.push({ type: "Q", x1: cx, y1: cy, x, y });
|
|
149
|
+
break;
|
|
150
|
+
case "T": {
|
|
151
|
+
const reflect = prev === "Q" || prev === "T";
|
|
152
|
+
cx = reflect ? 2 * x - cx : x;
|
|
153
|
+
cy = reflect ? 2 * y - cy : y;
|
|
154
|
+
x = ox + num();
|
|
155
|
+
y = oy + num();
|
|
156
|
+
out.push({ type: "Q", x1: cx, y1: cy, x, y });
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case "A": {
|
|
160
|
+
const rx = num();
|
|
161
|
+
const ry = num();
|
|
162
|
+
const angle = num();
|
|
163
|
+
const large = flag();
|
|
164
|
+
const sweep = flag();
|
|
165
|
+
const ex = ox + num();
|
|
166
|
+
const ey = oy + num();
|
|
167
|
+
arcToCubics(out, x, y, rx, ry, angle, large, sweep, ex, ey);
|
|
168
|
+
x = ex;
|
|
169
|
+
y = ey;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case "Z":
|
|
173
|
+
out.push({ type: "Z" });
|
|
174
|
+
x = sx;
|
|
175
|
+
y = sy;
|
|
176
|
+
break;
|
|
177
|
+
default:
|
|
178
|
+
throw parseError(name, `unknown command ${cmd}`);
|
|
179
|
+
}
|
|
180
|
+
prev = cmd.toUpperCase();
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
function transform(cmds, a, b, c, d) {
|
|
185
|
+
return cmds.map((k) => {
|
|
186
|
+
switch (k.type) {
|
|
187
|
+
case "M":
|
|
188
|
+
case "L":
|
|
189
|
+
return { type: k.type, x: a * k.x + b, y: c * k.y + d };
|
|
190
|
+
case "Q":
|
|
191
|
+
return { type: "Q", x1: a * k.x1 + b, y1: c * k.y1 + d, x: a * k.x + b, y: c * k.y + d };
|
|
192
|
+
case "C":
|
|
193
|
+
return {
|
|
194
|
+
type: "C",
|
|
195
|
+
x1: a * k.x1 + b,
|
|
196
|
+
y1: c * k.y1 + d,
|
|
197
|
+
x2: a * k.x2 + b,
|
|
198
|
+
y2: c * k.y2 + d,
|
|
199
|
+
x: a * k.x + b,
|
|
200
|
+
y: c * k.y + d
|
|
201
|
+
};
|
|
202
|
+
default:
|
|
203
|
+
return k;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function defineDiceGlyph(name, def) {
|
|
208
|
+
const [vx, vy, vw, vh] = def.viewBox ?? [0, 0, 100, 100];
|
|
209
|
+
const existing = glyphs.get(name);
|
|
210
|
+
const commands = transform(
|
|
211
|
+
parseSvgPath(def.path, name),
|
|
212
|
+
1 / vh,
|
|
213
|
+
-(vx + vw / 2) / vh,
|
|
214
|
+
-1 / vh,
|
|
215
|
+
(vy + vh / 2) / vh
|
|
216
|
+
);
|
|
217
|
+
const glyph = { name, commands, aspect: vw / vh };
|
|
218
|
+
if (existing) {
|
|
219
|
+
if (JSON.stringify(existing.commands) === JSON.stringify(commands)) return existing;
|
|
220
|
+
throw new ShardError(
|
|
221
|
+
"dice/registry-conflict",
|
|
222
|
+
`Glyph "${name}" is already defined, differently`,
|
|
223
|
+
{
|
|
224
|
+
hint: "Give the new glyph its own name."
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
glyphs.set(name, glyph);
|
|
229
|
+
return glyph;
|
|
230
|
+
}
|
|
231
|
+
function findDiceGlyph(name) {
|
|
232
|
+
return glyphs.get(name);
|
|
233
|
+
}
|
|
234
|
+
function allDiceGlyphs() {
|
|
235
|
+
return [...glyphs.keys()].sort();
|
|
236
|
+
}
|
|
237
|
+
const star = (points, outer, inner, turn = 0) => {
|
|
238
|
+
const out = [];
|
|
239
|
+
for (let i = 0; i < points * 2; i++) {
|
|
240
|
+
const a = -Math.PI / 2 + turn + i * Math.PI / points;
|
|
241
|
+
const r = i % 2 === 0 ? outer : inner;
|
|
242
|
+
out.push(`${(50 + Math.cos(a) * r).toFixed(2)} ${(50 + Math.sin(a) * r).toFixed(2)}`);
|
|
243
|
+
}
|
|
244
|
+
return `M${out.join("L")}Z`;
|
|
245
|
+
};
|
|
246
|
+
const BUILTIN_GLYPHS = {
|
|
247
|
+
star: { path: star(5, 48, 20) },
|
|
248
|
+
sun: { path: star(12, 48, 33) },
|
|
249
|
+
moon: { path: "M62 6A44 44 0 1 0 94 70A36 36 0 1 1 62 6Z" },
|
|
250
|
+
heart: {
|
|
251
|
+
path: "M50 90C22 68 4 52 4 32C4 16 16 6 29 6C39 6 46 12 50 20C54 12 61 6 71 6C84 6 96 16 96 32C96 52 78 68 50 90Z"
|
|
252
|
+
},
|
|
253
|
+
crown: { path: "M6 82L12 26L32 52L50 14L68 52L88 26L94 82Z" },
|
|
254
|
+
diamond: { path: "M50 2L90 50L50 98L10 50Z" }
|
|
255
|
+
};
|
|
256
|
+
let registered = false;
|
|
257
|
+
function registerBuiltinGlyphs() {
|
|
258
|
+
if (registered) return;
|
|
259
|
+
registered = true;
|
|
260
|
+
for (const [name, def] of Object.entries(BUILTIN_GLYPHS)) defineDiceGlyph(name, def);
|
|
261
|
+
}
|
|
262
|
+
export {
|
|
263
|
+
BUILTIN_GLYPHS,
|
|
264
|
+
allDiceGlyphs,
|
|
265
|
+
defineDiceGlyph,
|
|
266
|
+
findDiceGlyph,
|
|
267
|
+
parseSvgPath,
|
|
268
|
+
registerBuiltinGlyphs,
|
|
269
|
+
transform as transformOutline
|
|
270
|
+
};
|
package/dist/golden.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"note": "0054's golden dice track: this roll (default tray 5.4 x 3.15, seed = its id) records this trackHash inline, in a worker, in Node and in Chromium.",
|
|
3
|
+
"roll": {
|
|
4
|
+
"id": "golden-roll",
|
|
5
|
+
"dice": [
|
|
6
|
+
{ "kind": "d4", "value": 3 },
|
|
7
|
+
{ "kind": "d6", "value": 5 },
|
|
8
|
+
{ "kind": "d8", "value": 2 },
|
|
9
|
+
{ "kind": "d10", "value": 7 },
|
|
10
|
+
{ "kind": "d12", "value": 11 },
|
|
11
|
+
{ "kind": "d20", "value": 20, "dropped": true },
|
|
12
|
+
{ "kind": "percentile", "value": 42 },
|
|
13
|
+
{ "kind": "d100", "value": 63 }
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"hash": "bf6ea022"
|
|
17
|
+
}
|
package/dist/hull.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Q4, type V3 } from './math.js';
|
|
2
|
+
export interface Face {
|
|
3
|
+
/** Vertex indices, counter-clockwise seen from outside. */
|
|
4
|
+
vertices: number[];
|
|
5
|
+
normal: V3;
|
|
6
|
+
center: V3;
|
|
7
|
+
}
|
|
8
|
+
export interface Polytope {
|
|
9
|
+
points: V3[];
|
|
10
|
+
faces: Face[];
|
|
11
|
+
}
|
|
12
|
+
export declare function pointsOf(flat: ArrayLike<number>): V3[];
|
|
13
|
+
/**
|
|
14
|
+
* The faces of the convex hull of `points`: every plane through three points with all the others
|
|
15
|
+
* on one side, its coplanar points gathered into one polygon. Faces are sorted by normal, so the
|
|
16
|
+
* result doesn't depend on the order of `points` beyond their indices.
|
|
17
|
+
*/
|
|
18
|
+
export declare function convexFaces(points: readonly V3[]): Face[];
|
|
19
|
+
/**
|
|
20
|
+
* The polytope whose faces are the tangent planes at unit `normals` (distance 1 from the center):
|
|
21
|
+
* a ball die's flattened cells. Each face's vertices are the corners where three planes meet,
|
|
22
|
+
* one per triangle of the points' hull.
|
|
23
|
+
*/
|
|
24
|
+
export declare function tangentPolytope(normals: readonly V3[]): Polytope;
|
|
25
|
+
/** Whether rotating `points` by `q` gives the same set. */
|
|
26
|
+
export declare function isSymmetry(points: readonly V3[], q: Q4): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Every proper rotation that maps the point set onto itself, sorted by their canonical form. An
|
|
29
|
+
* anchor triple is sent to every triple of matching pair distances; the rotations that also map
|
|
30
|
+
* every other point onto the set are the group.
|
|
31
|
+
*/
|
|
32
|
+
export declare function symmetryGroup(points: readonly V3[]): Q4[];
|
package/dist/hull.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import {
|
|
2
|
+
add,
|
|
3
|
+
cross,
|
|
4
|
+
dot,
|
|
5
|
+
EPSILON,
|
|
6
|
+
length,
|
|
7
|
+
normalize,
|
|
8
|
+
qConj,
|
|
9
|
+
qFromBasis,
|
|
10
|
+
qKey,
|
|
11
|
+
qMul,
|
|
12
|
+
qRotate,
|
|
13
|
+
round,
|
|
14
|
+
scale,
|
|
15
|
+
stableTangent,
|
|
16
|
+
sub
|
|
17
|
+
} from "./math.js";
|
|
18
|
+
function pointsOf(flat) {
|
|
19
|
+
const out = [];
|
|
20
|
+
for (let i = 0; i + 2 < flat.length; i += 3) out.push([flat[i], flat[i + 1], flat[i + 2]]);
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
const compareVector = (a, b) => a[1] - b[1] || a[2] - b[2] || a[0] - b[0];
|
|
24
|
+
function convexFaces(points) {
|
|
25
|
+
const faces = /* @__PURE__ */ new Map();
|
|
26
|
+
const n = points.length;
|
|
27
|
+
for (let a = 0; a < n - 2; a++) {
|
|
28
|
+
for (let b = a + 1; b < n - 1; b++) {
|
|
29
|
+
for (let c = b + 1; c < n; c++) {
|
|
30
|
+
let normal = cross(sub(points[b], points[a]), sub(points[c], points[a]));
|
|
31
|
+
if (dot(normal, normal) < 1e-10) continue;
|
|
32
|
+
normal = normalize(normal);
|
|
33
|
+
const d = dot(normal, points[a]);
|
|
34
|
+
let positive = false;
|
|
35
|
+
let negative = false;
|
|
36
|
+
for (const p of points) {
|
|
37
|
+
const side = dot(normal, p) - d;
|
|
38
|
+
if (side > EPSILON) positive = true;
|
|
39
|
+
else if (side < -EPSILON) negative = true;
|
|
40
|
+
if (positive && negative) break;
|
|
41
|
+
}
|
|
42
|
+
if (positive && negative) continue;
|
|
43
|
+
if (positive) normal = scale(normal, -1);
|
|
44
|
+
const plane = dot(normal, points[a]);
|
|
45
|
+
const members = [];
|
|
46
|
+
for (let i = 0; i < n; i++) {
|
|
47
|
+
if (Math.abs(dot(normal, points[i]) - plane) < EPSILON * 3) members.push(i);
|
|
48
|
+
}
|
|
49
|
+
const key = members.join(":");
|
|
50
|
+
if (faces.has(key)) continue;
|
|
51
|
+
let center = [0, 0, 0];
|
|
52
|
+
for (const i of members) center = add(center, points[i]);
|
|
53
|
+
center = scale(center, 1 / members.length);
|
|
54
|
+
faces.set(key, { vertices: orderAround(members, points, normal, center), normal, center });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return [...faces.values()].sort((l, r) => compareVector(l.normal, r.normal));
|
|
59
|
+
}
|
|
60
|
+
function orderAround(members, points, normal, center) {
|
|
61
|
+
const t = stableTangent(normal);
|
|
62
|
+
const b = cross(normal, t);
|
|
63
|
+
const angle = (i) => {
|
|
64
|
+
const d = sub(points[i], center);
|
|
65
|
+
return Math.atan2(dot(d, b), dot(d, t));
|
|
66
|
+
};
|
|
67
|
+
return [...members].sort((l, r) => angle(l) - angle(r));
|
|
68
|
+
}
|
|
69
|
+
function tangentPolytope(normals) {
|
|
70
|
+
const triangles = convexFaces(normals);
|
|
71
|
+
const points = [];
|
|
72
|
+
const cells = normals.map(() => []);
|
|
73
|
+
for (const tri of triangles) {
|
|
74
|
+
const [a, b, c] = tri.vertices;
|
|
75
|
+
const na = normals[a];
|
|
76
|
+
const nb = normals[b];
|
|
77
|
+
const nc = normals[c];
|
|
78
|
+
const det = dot(na, cross(nb, nc));
|
|
79
|
+
const p = scale(add(add(cross(nb, nc), cross(nc, na)), cross(na, nb)), 1 / det);
|
|
80
|
+
const index = points.push(p) - 1;
|
|
81
|
+
for (const i of tri.vertices) cells[i].push(index);
|
|
82
|
+
}
|
|
83
|
+
const faces = normals.map((normal, i) => {
|
|
84
|
+
let center = [0, 0, 0];
|
|
85
|
+
for (const v of cells[i]) center = add(center, points[v]);
|
|
86
|
+
center = scale(center, 1 / cells[i].length);
|
|
87
|
+
return { vertices: orderAround(cells[i], points, normal, center), normal, center };
|
|
88
|
+
});
|
|
89
|
+
return { points, faces };
|
|
90
|
+
}
|
|
91
|
+
function hasPoint(points, p) {
|
|
92
|
+
for (const q of points) {
|
|
93
|
+
const dx = q[0] - p[0];
|
|
94
|
+
const dy = q[1] - p[1];
|
|
95
|
+
const dz = q[2] - p[2];
|
|
96
|
+
if (dx * dx + dy * dy + dz * dz < EPSILON * EPSILON * 9) return true;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
function isSymmetry(points, q) {
|
|
101
|
+
for (const p of points) if (!hasPoint(points, qRotate(q, p))) return false;
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
function basisOf(a, b, c) {
|
|
105
|
+
const x = normalize(sub(b, a));
|
|
106
|
+
const ac = sub(c, a);
|
|
107
|
+
const yRaw = sub(ac, scale(x, dot(ac, x)));
|
|
108
|
+
if (length(yRaw) < 1e-6) return void 0;
|
|
109
|
+
const y = normalize(yRaw);
|
|
110
|
+
return [x, y, cross(x, y)];
|
|
111
|
+
}
|
|
112
|
+
function symmetryGroup(points) {
|
|
113
|
+
const n = points.length;
|
|
114
|
+
let anchor;
|
|
115
|
+
for (let b = 1; b < n && !anchor; b++) {
|
|
116
|
+
for (let c = b + 1; c < n && !anchor; c++) {
|
|
117
|
+
if (length(cross(sub(points[b], points[0]), sub(points[c], points[0]))) > 1e-4)
|
|
118
|
+
anchor = [0, b, c];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (!anchor) return [];
|
|
122
|
+
const [p0, p1, p2] = anchor.map((i) => points[i]);
|
|
123
|
+
const d01 = length(sub(p1, p0));
|
|
124
|
+
const d02 = length(sub(p2, p0));
|
|
125
|
+
const d12 = length(sub(p2, p1));
|
|
126
|
+
const source = basisOf(p0, p1, p2);
|
|
127
|
+
const fromSource = qConj(qFromBasis(...source));
|
|
128
|
+
const found = /* @__PURE__ */ new Map();
|
|
129
|
+
for (let a = 0; a < n; a++) {
|
|
130
|
+
for (let b = 0; b < n; b++) {
|
|
131
|
+
if (b === a || Math.abs(length(sub(points[b], points[a])) - d01) > EPSILON * 10) continue;
|
|
132
|
+
for (let c = 0; c < n; c++) {
|
|
133
|
+
if (c === a || c === b) continue;
|
|
134
|
+
if (Math.abs(length(sub(points[c], points[a])) - d02) > EPSILON * 10) continue;
|
|
135
|
+
if (Math.abs(length(sub(points[c], points[b])) - d12) > EPSILON * 10) continue;
|
|
136
|
+
const target = basisOf(points[a], points[b], points[c]);
|
|
137
|
+
if (!target) continue;
|
|
138
|
+
const q = qMul(qFromBasis(...target), fromSource);
|
|
139
|
+
const moved = qRotate(q, p0);
|
|
140
|
+
if (length(sub(moved, points[a])) > EPSILON * 10) continue;
|
|
141
|
+
if (!isSymmetry(points, q)) continue;
|
|
142
|
+
found.set(qKey(q), q.map(round));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return [...found.entries()].sort(([l], [r]) => l.localeCompare(r)).map(([, q]) => q);
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
convexFaces,
|
|
150
|
+
isSymmetry,
|
|
151
|
+
pointsOf,
|
|
152
|
+
symmetryGroup,
|
|
153
|
+
tangentPolytope
|
|
154
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export { allDiceAttachments, DICE_BUDGETS, type DiceAttachmentContext, type DiceAttachmentDef, type DiceSceneContext, defineDiceAttachment, findDiceAttachment, MAX_ATTACHMENTS, setDiceBudgets, } from './attachments.js';
|
|
2
|
+
export { BUILTIN_DICE, D4, D6, D8, D10, D10_TENS, D12, D20, D100_BALL, DIE_KINDS, type DieKind, KIND_DEFINITIONS, kindSides, percentileValues, registerBuiltinDice, } from './builtins.js';
|
|
3
|
+
export { CELL_PAD, type CellLayout, cellLayout, type FaceCell, MARK_RANGE } from './cells.js';
|
|
4
|
+
export { DiceDie, DiceRollRequest } from './components.js';
|
|
5
|
+
export { allDice, type DieDefinition, type DieGeometry, defineDie, dieGeometry, dieHash, findDie, frameOf, labelOf, physicalScale, requireDie, type ValueFrame, validateDieDefinition, } from './definition.js';
|
|
6
|
+
export { DieDefinitionAsset, type DieDefinitionAssetValue, dieDefinitionOf, } from './definition-asset.js';
|
|
7
|
+
export { type DiceCondition, type DiceEffect, DiceEffectRecipe, type DiceEffectRecipeValue, diceEffectRecipe, type MatchedRecipe, matchRecipes, RECIPE_LIMITS, recipeProblems, } from './effects.js';
|
|
8
|
+
export { allDiceEntrances, type DiceEntranceContext, type DiceEntranceDef, defineDiceEntrance, findDiceEntrance, MAX_ENTRANCES, } from './entrances.js';
|
|
9
|
+
export { BUILTIN_FAMILIES, GLASS, METAL, RESIN, SOLID } from './families.js';
|
|
10
|
+
export { allDiceGlyphs, BUILTIN_GLYPHS, type DiceGlyph, type DiceGlyphDef, defineDiceGlyph, findDiceGlyph, parseSvgPath, registerBuiltinGlyphs, } from './glyphs.js';
|
|
11
|
+
export { landingCorrection, markAngle, naturalValue, restingHeight, restRotation, topAlignment, } from './landing.js';
|
|
12
|
+
export { ambiguousLabel, defaultLayoutFor, FaceLayout, type FaceLayoutValue, type FaceMark, layoutProblems, NUMBERS_LAYOUT, PIPS_LAYOUT, type ResolvedMark, resolveMark, } from './layout.js';
|
|
13
|
+
export { type BakeMarksOptions, bakeMarks, clearMarkCache, MARK_MARGIN, type MarkAtlas, markAtlasKey, markPlacements, type Placement as MarkPlacement, } from './marks.js';
|
|
14
|
+
export { allDiceFamilies, DICE_FIELDS, DICE_SHADERS, type DiceFamily, type DiceFamilyOptions, defineDiceFamily, findDiceFamily, isDiceMaterial, } from './material.js';
|
|
15
|
+
export { dieMesh, floorQuad } from './mesh.js';
|
|
16
|
+
export { diceMethods } from './methods.js';
|
|
17
|
+
export { NUMERAL_HEIGHT } from './numerals.js';
|
|
18
|
+
export { type DicePluginOptions, dicePlugin, TOP_DOWN } from './plugin.js';
|
|
19
|
+
export { type DiceThumbnail, type DiceThumbnailOptions, renderDiceThumbnail } from './preview.js';
|
|
20
|
+
export { type DiceQuality, type DiceQualityChoice, resolveDiceQuality } from './quality.js';
|
|
21
|
+
export { type DiceLookKey, type DiceResourceEntry, DiceResources, RELEASE_AFTER_MS, } from './resources.js';
|
|
22
|
+
export { type DiceQualityPreference, type DiceRoll, type DiceRollDie, expandRoll, type PhysicalDie, presentationScale, rollTrackRequest, viewportTray, } from './roll.js';
|
|
23
|
+
export { BALL_DAMPING, DICE_CLEANUP_STEP, DICE_GROUPS, DICE_SETTLE_RULE, type DiceSettleParams, type DiceTray, diceSettle, outsideTray, restingFlat, } from './settle.js';
|
|
24
|
+
export { BUILTIN_SKINS, DICE_SKINS, DiceSkin, type DiceSkinValue, type DiceSkinVariant, diceSkin, IMPACT_SOUNDS, type ImpactSound, MAX_SKIN_EFFECTS, type SkinContext, skinVariant, validateDiceSkin, } from './skin.js';
|
|
25
|
+
export { ACCENT_CUES, type AccentCue, accentSamples, DiceSoundBank, IMPACT_LAYERS, IMPACT_VARIATIONS, impactLayer, impactSamples, impactStrength, wavBytes, } from './sound.js';
|
|
26
|
+
export { studioEnvironment } from './studio.js';
|
|
27
|
+
export { ANIMATED_DEMAND, type DiceOutcome, type DicePhase, type DicePlayOptions, DiceTable, type DiceTableOptions, DiceTableState, ENTRANCE_DEMAND, ENTRANCE_WAIT_MS, PRESENTATION_DEMAND, PREVIEW_TRAY, } from './table.js';
|
|
28
|
+
export { DICE_CONTACTS, DICE_GRAVITY, DICE_MAX_STEPS, DICE_STEP, type DiceTrackDie, type DiceTrackRequest, diceSettleParams, diceTrackScene, LAUNCH_EDGES, type LaunchEdge, laneLayout, launchEdgeOf, MAX_DICE, PLACED_DROP_STEPS, placeDie, placedFrom, placementSpots, seedOf, type UnlandedReason, unlandedDice, } from './track.js';
|
|
29
|
+
export { DiceTray as DiceTrayMaterial, TRAY_SHADERS } from './tray.js';
|
|
30
|
+
export { type DiceVariantEntry, diceVariants } from './variants.js';
|
|
31
|
+
export { type DiceWindow, spawnDiceWindow } from './windows.js';
|