@aethervtt/shard-dice 0.0.0-stage → 0.2.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/audio.d.ts +19 -0
- package/dist/audio.js +187 -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 +99 -0
- package/dist/effects.js +200 -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 +32 -0
- package/dist/index.js +324 -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 +176 -0
- package/dist/preview.d.ts +32 -0
- package/dist/preview.js +135 -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 +64 -0
- package/dist/skin.js +313 -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 +227 -0
- package/dist/table.js +1818 -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/builtins.js
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { defineDie } from "./definition.js";
|
|
2
|
+
const PHI = (1 + Math.sqrt(5)) / 2;
|
|
3
|
+
function unitRadius(points) {
|
|
4
|
+
const c = [0, 1, 2].map((k) => points.reduce((s, p) => s + p[k], 0) / points.length);
|
|
5
|
+
const centered = points.map((p) => p.map((v, k) => v - c[k]));
|
|
6
|
+
const r = Math.max(...centered.map((p) => Math.sqrt(p[0] ** 2 + p[1] ** 2 + p[2] ** 2)));
|
|
7
|
+
return Float32Array.from(centered.flatMap((p) => p.map((v) => v / r)));
|
|
8
|
+
}
|
|
9
|
+
const R0 = 0.7799999713897705;
|
|
10
|
+
const R1 = 0.6310332417488098;
|
|
11
|
+
const R2 = 0.24103325605392456;
|
|
12
|
+
const Z1 = 0.45847249031066895;
|
|
13
|
+
const Z2 = 0.7418240904808044;
|
|
14
|
+
const RING = 0.10557281225919724;
|
|
15
|
+
const TRAPEZOHEDRON = Float32Array.from([
|
|
16
|
+
0,
|
|
17
|
+
1,
|
|
18
|
+
0,
|
|
19
|
+
0,
|
|
20
|
+
-1,
|
|
21
|
+
0,
|
|
22
|
+
R0,
|
|
23
|
+
RING,
|
|
24
|
+
0,
|
|
25
|
+
R1,
|
|
26
|
+
-RING,
|
|
27
|
+
Z1,
|
|
28
|
+
R2,
|
|
29
|
+
RING,
|
|
30
|
+
Z2,
|
|
31
|
+
-R2,
|
|
32
|
+
-RING,
|
|
33
|
+
Z2,
|
|
34
|
+
-R1,
|
|
35
|
+
RING,
|
|
36
|
+
Z1,
|
|
37
|
+
-R0,
|
|
38
|
+
-RING,
|
|
39
|
+
0,
|
|
40
|
+
-R1,
|
|
41
|
+
RING,
|
|
42
|
+
-Z1,
|
|
43
|
+
-R2,
|
|
44
|
+
-RING,
|
|
45
|
+
-Z2,
|
|
46
|
+
R2,
|
|
47
|
+
RING,
|
|
48
|
+
-Z2,
|
|
49
|
+
R1,
|
|
50
|
+
-RING,
|
|
51
|
+
-Z1
|
|
52
|
+
]);
|
|
53
|
+
function dodecahedron() {
|
|
54
|
+
const inverse = 1 / PHI;
|
|
55
|
+
const out = [];
|
|
56
|
+
for (const x of [-1, 1]) for (const y of [-1, 1]) for (const z of [-1, 1]) out.push([x, y, z]);
|
|
57
|
+
for (const a of [-inverse, inverse]) {
|
|
58
|
+
for (const b of [-PHI, PHI]) out.push([0, a, b], [a, b, 0], [b, 0, a]);
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
const D10_VALUES = {
|
|
63
|
+
"1:9:10:11": 8,
|
|
64
|
+
"1:7:8:9": 10,
|
|
65
|
+
"1:2:3:11": 2,
|
|
66
|
+
"1:5:6:7": 4,
|
|
67
|
+
"1:3:4:5": 6,
|
|
68
|
+
"0:8:9:10": 3,
|
|
69
|
+
"0:2:10:11": 5,
|
|
70
|
+
"0:6:7:8": 7,
|
|
71
|
+
"0:2:3:4": 9,
|
|
72
|
+
"0:4:5:6": 1
|
|
73
|
+
};
|
|
74
|
+
const D4 = {
|
|
75
|
+
version: 1,
|
|
76
|
+
id: "d4",
|
|
77
|
+
sides: 4,
|
|
78
|
+
vertices: unitRadius([
|
|
79
|
+
[1, 1, 1],
|
|
80
|
+
[-1, -1, 1],
|
|
81
|
+
[-1, 1, -1],
|
|
82
|
+
[1, -1, -1]
|
|
83
|
+
]),
|
|
84
|
+
read: "vertex",
|
|
85
|
+
// Aether numbers the d4's vertices by direction: sorted by y, then z, then x.
|
|
86
|
+
values: { "0": 4, "1": 2, "2": 3, "3": 1 },
|
|
87
|
+
collider: "hull",
|
|
88
|
+
sizeMm: 15,
|
|
89
|
+
bevel: 0.06,
|
|
90
|
+
markScale: 0.31
|
|
91
|
+
};
|
|
92
|
+
const D6 = {
|
|
93
|
+
version: 1,
|
|
94
|
+
id: "d6",
|
|
95
|
+
sides: 6,
|
|
96
|
+
vertices: unitRadius([
|
|
97
|
+
[-1, -1, -1],
|
|
98
|
+
[-1, -1, 1],
|
|
99
|
+
[-1, 1, -1],
|
|
100
|
+
[-1, 1, 1],
|
|
101
|
+
[1, -1, -1],
|
|
102
|
+
[1, -1, 1],
|
|
103
|
+
[1, 1, -1],
|
|
104
|
+
[1, 1, 1]
|
|
105
|
+
]),
|
|
106
|
+
read: "face",
|
|
107
|
+
values: {
|
|
108
|
+
"0:1:4:5": 1,
|
|
109
|
+
"0:2:4:6": 2,
|
|
110
|
+
"0:1:2:3": 3,
|
|
111
|
+
"4:5:6:7": 4,
|
|
112
|
+
"1:3:5:7": 5,
|
|
113
|
+
"2:3:6:7": 6
|
|
114
|
+
},
|
|
115
|
+
collider: "hull",
|
|
116
|
+
sizeMm: 16,
|
|
117
|
+
bevel: 0.082,
|
|
118
|
+
markScale: 0.5,
|
|
119
|
+
oppositesSum: true
|
|
120
|
+
};
|
|
121
|
+
const D8 = {
|
|
122
|
+
version: 1,
|
|
123
|
+
id: "d8",
|
|
124
|
+
sides: 8,
|
|
125
|
+
vertices: unitRadius([
|
|
126
|
+
[1, 0, 0],
|
|
127
|
+
[-1, 0, 0],
|
|
128
|
+
[0, 1, 0],
|
|
129
|
+
[0, -1, 0],
|
|
130
|
+
[0, 0, 1],
|
|
131
|
+
[0, 0, -1]
|
|
132
|
+
]),
|
|
133
|
+
read: "face",
|
|
134
|
+
values: {
|
|
135
|
+
"1:3:5": 2,
|
|
136
|
+
"0:3:5": 6,
|
|
137
|
+
"1:3:4": 8,
|
|
138
|
+
"0:3:4": 4,
|
|
139
|
+
"1:2:5": 5,
|
|
140
|
+
"0:2:5": 1,
|
|
141
|
+
"1:2:4": 3,
|
|
142
|
+
"0:2:4": 7
|
|
143
|
+
},
|
|
144
|
+
collider: "hull",
|
|
145
|
+
sizeMm: 16,
|
|
146
|
+
bevel: 0.082,
|
|
147
|
+
markScale: 0.4,
|
|
148
|
+
oppositesSum: true
|
|
149
|
+
};
|
|
150
|
+
const D10 = {
|
|
151
|
+
version: 1,
|
|
152
|
+
id: "d10",
|
|
153
|
+
sides: 10,
|
|
154
|
+
vertices: TRAPEZOHEDRON,
|
|
155
|
+
read: "face",
|
|
156
|
+
values: D10_VALUES,
|
|
157
|
+
labels: { 10: "0" },
|
|
158
|
+
collider: "hull",
|
|
159
|
+
sizeMm: 16,
|
|
160
|
+
bevel: 0.13,
|
|
161
|
+
markScale: 0.36
|
|
162
|
+
};
|
|
163
|
+
const D10_TENS = {
|
|
164
|
+
version: 1,
|
|
165
|
+
id: "d10-tens",
|
|
166
|
+
sides: 10,
|
|
167
|
+
vertices: TRAPEZOHEDRON,
|
|
168
|
+
read: "face",
|
|
169
|
+
values: D10_VALUES,
|
|
170
|
+
labels: {
|
|
171
|
+
1: "10",
|
|
172
|
+
2: "20",
|
|
173
|
+
3: "30",
|
|
174
|
+
4: "40",
|
|
175
|
+
5: "50",
|
|
176
|
+
6: "60",
|
|
177
|
+
7: "70",
|
|
178
|
+
8: "80",
|
|
179
|
+
9: "90",
|
|
180
|
+
10: "00"
|
|
181
|
+
},
|
|
182
|
+
collider: "hull",
|
|
183
|
+
sizeMm: 16,
|
|
184
|
+
bevel: 0.13,
|
|
185
|
+
markScale: 0.32
|
|
186
|
+
};
|
|
187
|
+
const D12 = {
|
|
188
|
+
version: 1,
|
|
189
|
+
id: "d12",
|
|
190
|
+
sides: 12,
|
|
191
|
+
vertices: unitRadius(dodecahedron()),
|
|
192
|
+
read: "face",
|
|
193
|
+
values: {
|
|
194
|
+
"1:5:9:11:15": 4,
|
|
195
|
+
"0:4:8:9:15": 7,
|
|
196
|
+
"4:5:13:15:19": 11,
|
|
197
|
+
"0:1:9:10:16": 5,
|
|
198
|
+
"0:2:8:10:14": 10,
|
|
199
|
+
"4:6:8:13:14": 1,
|
|
200
|
+
"1:3:11:16:17": 12,
|
|
201
|
+
"5:7:11:17:19": 3,
|
|
202
|
+
"2:3:10:12:16": 2,
|
|
203
|
+
"6:7:13:18:19": 8,
|
|
204
|
+
"2:6:12:14:18": 9,
|
|
205
|
+
"3:7:12:17:18": 6
|
|
206
|
+
},
|
|
207
|
+
collider: "hull",
|
|
208
|
+
sizeMm: 14,
|
|
209
|
+
bevel: 0.082,
|
|
210
|
+
markScale: 0.42,
|
|
211
|
+
oppositesSum: true
|
|
212
|
+
};
|
|
213
|
+
const D20 = {
|
|
214
|
+
version: 1,
|
|
215
|
+
id: "d20",
|
|
216
|
+
sides: 20,
|
|
217
|
+
vertices: unitRadius([
|
|
218
|
+
[-1, PHI, 0],
|
|
219
|
+
[1, PHI, 0],
|
|
220
|
+
[-1, -PHI, 0],
|
|
221
|
+
[1, -PHI, 0],
|
|
222
|
+
[0, -1, PHI],
|
|
223
|
+
[0, 1, PHI],
|
|
224
|
+
[0, -1, -PHI],
|
|
225
|
+
[0, 1, -PHI],
|
|
226
|
+
[PHI, 0, -1],
|
|
227
|
+
[PHI, 0, 1],
|
|
228
|
+
[-PHI, 0, -1],
|
|
229
|
+
[-PHI, 0, 1]
|
|
230
|
+
]),
|
|
231
|
+
read: "face",
|
|
232
|
+
values: {
|
|
233
|
+
"2:3:6": 1,
|
|
234
|
+
"2:3:4": 19,
|
|
235
|
+
"2:6:10": 13,
|
|
236
|
+
"3:6:8": 7,
|
|
237
|
+
"2:4:11": 10,
|
|
238
|
+
"3:4:9": 3,
|
|
239
|
+
"2:10:11": 12,
|
|
240
|
+
"3:8:9": 17,
|
|
241
|
+
"6:7:10": 6,
|
|
242
|
+
"6:7:8": 16,
|
|
243
|
+
"4:5:11": 5,
|
|
244
|
+
"4:5:9": 15,
|
|
245
|
+
"0:10:11": 4,
|
|
246
|
+
"1:8:9": 9,
|
|
247
|
+
"0:7:10": 18,
|
|
248
|
+
"1:7:8": 11,
|
|
249
|
+
"0:5:11": 14,
|
|
250
|
+
"1:5:9": 8,
|
|
251
|
+
"0:1:7": 2,
|
|
252
|
+
"0:1:5": 20
|
|
253
|
+
},
|
|
254
|
+
collider: "hull",
|
|
255
|
+
sizeMm: 18,
|
|
256
|
+
bevel: 0.082,
|
|
257
|
+
markScale: 0.38,
|
|
258
|
+
oppositesSum: true
|
|
259
|
+
};
|
|
260
|
+
function fibonacciSphere(count) {
|
|
261
|
+
const golden = Math.PI * (3 - Math.sqrt(5));
|
|
262
|
+
const out = new Float32Array(count * 3);
|
|
263
|
+
for (let i = 0; i < count; i++) {
|
|
264
|
+
const y = 1 - (i + 0.5) * 2 / count;
|
|
265
|
+
const r = Math.sqrt(1 - y * y);
|
|
266
|
+
const a = i * golden;
|
|
267
|
+
out[i * 3] = Math.cos(a) * r;
|
|
268
|
+
out[i * 3 + 1] = y;
|
|
269
|
+
out[i * 3 + 2] = Math.sin(a) * r;
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
const D100_BALL = {
|
|
274
|
+
version: 1,
|
|
275
|
+
id: "d100-ball",
|
|
276
|
+
sides: 100,
|
|
277
|
+
vertices: fibonacciSphere(100),
|
|
278
|
+
read: "face",
|
|
279
|
+
values: Object.fromEntries(Array.from({ length: 100 }, (_, i) => [String(i), i + 1])),
|
|
280
|
+
collider: "ball",
|
|
281
|
+
sizeMm: 22,
|
|
282
|
+
bevel: 0.1,
|
|
283
|
+
markScale: 0.3
|
|
284
|
+
};
|
|
285
|
+
const BUILTIN_DICE = [
|
|
286
|
+
D4,
|
|
287
|
+
D6,
|
|
288
|
+
D8,
|
|
289
|
+
D10,
|
|
290
|
+
D10_TENS,
|
|
291
|
+
D12,
|
|
292
|
+
D20,
|
|
293
|
+
D100_BALL
|
|
294
|
+
];
|
|
295
|
+
let registered = false;
|
|
296
|
+
function registerBuiltinDice() {
|
|
297
|
+
if (registered) return;
|
|
298
|
+
registered = true;
|
|
299
|
+
for (const def of BUILTIN_DICE) defineDie(def);
|
|
300
|
+
}
|
|
301
|
+
const DIE_KINDS = ["d4", "d6", "d8", "d10", "d12", "d20", "d100", "percentile"];
|
|
302
|
+
const KIND_DEFINITIONS = {
|
|
303
|
+
d4: ["d4"],
|
|
304
|
+
d6: ["d6"],
|
|
305
|
+
d8: ["d8"],
|
|
306
|
+
d10: ["d10"],
|
|
307
|
+
d12: ["d12"],
|
|
308
|
+
d20: ["d20"],
|
|
309
|
+
d100: ["d100-ball"],
|
|
310
|
+
percentile: ["d10-tens", "d10"]
|
|
311
|
+
};
|
|
312
|
+
function kindSides(kind) {
|
|
313
|
+
return kind === "percentile" || kind === "d100" ? 100 : Number(kind.slice(1));
|
|
314
|
+
}
|
|
315
|
+
function percentileValues(value) {
|
|
316
|
+
const tens = Math.floor(value % 100 / 10);
|
|
317
|
+
const units = value % 10;
|
|
318
|
+
return [tens === 0 ? 10 : tens, units === 0 ? 10 : units];
|
|
319
|
+
}
|
|
320
|
+
export {
|
|
321
|
+
BUILTIN_DICE,
|
|
322
|
+
D10,
|
|
323
|
+
D100_BALL,
|
|
324
|
+
D10_TENS,
|
|
325
|
+
D12,
|
|
326
|
+
D20,
|
|
327
|
+
D4,
|
|
328
|
+
D6,
|
|
329
|
+
D8,
|
|
330
|
+
DIE_KINDS,
|
|
331
|
+
KIND_DEFINITIONS,
|
|
332
|
+
kindSides,
|
|
333
|
+
percentileValues,
|
|
334
|
+
registerBuiltinDice
|
|
335
|
+
};
|
package/dist/cells.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { DieGeometry } from './definition.js';
|
|
2
|
+
import { type V3 } from './math.js';
|
|
3
|
+
export interface FaceCell {
|
|
4
|
+
face: number;
|
|
5
|
+
/** The value the face reads (undefined on a d4: its marks are per corner). */
|
|
6
|
+
value: number | undefined;
|
|
7
|
+
col: number;
|
|
8
|
+
row: number;
|
|
9
|
+
/** The face's frame: marks read up along `bitangent`. */
|
|
10
|
+
tangent: V3;
|
|
11
|
+
bitangent: V3;
|
|
12
|
+
center: V3;
|
|
13
|
+
/** Half the side of the square, in face units, that maps onto the cell's inner square. */
|
|
14
|
+
extent: number;
|
|
15
|
+
}
|
|
16
|
+
export interface CellLayout {
|
|
17
|
+
cols: number;
|
|
18
|
+
rows: number;
|
|
19
|
+
/** Cell side in atlas pixels. */
|
|
20
|
+
cell: number;
|
|
21
|
+
/** Margin inside each cell, as a fraction of its side. */
|
|
22
|
+
pad: number;
|
|
23
|
+
faces: FaceCell[];
|
|
24
|
+
}
|
|
25
|
+
export declare const CELL_PAD = 0.06;
|
|
26
|
+
/** Distance range of the atlas, in pixels: marks shade from ±range / 2 of their outline. */
|
|
27
|
+
export declare const MARK_RANGE = 8;
|
|
28
|
+
/** The cells of a die's faces: a near-square grid, faces in polytope order. */
|
|
29
|
+
export declare function cellLayout(g: DieGeometry): CellLayout;
|
|
30
|
+
/** A point on a face in its cell's pixels: x right, y up from the cell's bottom-left corner. */
|
|
31
|
+
export declare function cellPixel(layout: CellLayout, cell: FaceCell, p: V3): [number, number];
|
|
32
|
+
/** A point on a face as atlas UVs (v down, as textures sample). */
|
|
33
|
+
export declare function cellUv(layout: CellLayout, cell: FaceCell, p: V3, out: number[], o: number): void;
|
package/dist/cells.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { cross, dot, stableTangent, sub } from "./math.js";
|
|
2
|
+
const CELL_PAD = 0.06;
|
|
3
|
+
const MARK_RANGE = 8;
|
|
4
|
+
const layouts = /* @__PURE__ */ new WeakMap();
|
|
5
|
+
function cellLayout(g) {
|
|
6
|
+
const cached = layouts.get(g);
|
|
7
|
+
if (cached) return cached;
|
|
8
|
+
const faces = g.polytope.faces;
|
|
9
|
+
const cols = Math.ceil(Math.sqrt(faces.length));
|
|
10
|
+
const rows = Math.ceil(faces.length / cols);
|
|
11
|
+
const cell = faces.length <= 24 ? 128 : 64;
|
|
12
|
+
const out = faces.map((face, i) => {
|
|
13
|
+
const value = g.faceValues[i];
|
|
14
|
+
const frame = value === void 0 ? void 0 : g.frames[value - 1];
|
|
15
|
+
const tangent = frame ? frame.tangent : stableTangent(face.normal);
|
|
16
|
+
const bitangent = frame ? frame.bitangent : cross(face.normal, tangent);
|
|
17
|
+
let extent = 1e-6;
|
|
18
|
+
for (const v of face.vertices) {
|
|
19
|
+
const d = sub(g.polytope.points[v], face.center);
|
|
20
|
+
extent = Math.max(extent, Math.abs(dot(d, tangent)), Math.abs(dot(d, bitangent)));
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
face: i,
|
|
24
|
+
value,
|
|
25
|
+
col: i % cols,
|
|
26
|
+
row: Math.floor(i / cols),
|
|
27
|
+
tangent,
|
|
28
|
+
bitangent,
|
|
29
|
+
center: face.center,
|
|
30
|
+
extent
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
const layout = { cols, rows, cell, pad: CELL_PAD, faces: out };
|
|
34
|
+
layouts.set(g, layout);
|
|
35
|
+
return layout;
|
|
36
|
+
}
|
|
37
|
+
function cellPixel(layout, cell, p) {
|
|
38
|
+
const d = sub(p, cell.center);
|
|
39
|
+
const k = layout.cell * (1 - 2 * layout.pad) / (2 * cell.extent);
|
|
40
|
+
return [layout.cell / 2 + dot(d, cell.tangent) * k, layout.cell / 2 + dot(d, cell.bitangent) * k];
|
|
41
|
+
}
|
|
42
|
+
function cellUv(layout, cell, p, out, o) {
|
|
43
|
+
const [x, y] = cellPixel(layout, cell, p);
|
|
44
|
+
out[o] = (cell.col * layout.cell + x) / (layout.cols * layout.cell);
|
|
45
|
+
out[o + 1] = (cell.row * layout.cell + (layout.cell - y)) / (layout.rows * layout.cell);
|
|
46
|
+
}
|
|
47
|
+
export {
|
|
48
|
+
CELL_PAD,
|
|
49
|
+
MARK_RANGE,
|
|
50
|
+
cellLayout,
|
|
51
|
+
cellPixel,
|
|
52
|
+
cellUv
|
|
53
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** A die on the table. Spawned and despawned by the dice table; read-only to everyone else. */
|
|
2
|
+
export declare const DiceDie: import("@aethervtt/shard-core").ComponentDef<{
|
|
3
|
+
readonly index: import("@aethervtt/shard-core").FieldType<number, "u8">;
|
|
4
|
+
readonly kind: import("@aethervtt/shard-core").FieldType<string, "object">;
|
|
5
|
+
readonly definition: import("@aethervtt/shard-core").FieldType<string, "object">;
|
|
6
|
+
readonly value: import("@aethervtt/shard-core").FieldType<number, "u16">;
|
|
7
|
+
readonly label: import("@aethervtt/shard-core").FieldType<string, "object">;
|
|
8
|
+
readonly dropped: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
9
|
+
}>;
|
|
10
|
+
/** Scene hook: an entity with this plays its roll when it appears (for shard screenshot, tests). */
|
|
11
|
+
export declare const DiceRollRequest: import("@aethervtt/shard-core").ComponentDef<{
|
|
12
|
+
readonly roll: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").JsonValue, "object">;
|
|
13
|
+
readonly replace: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineComponent, t } from "@aethervtt/shard-core";
|
|
2
|
+
const DiceDie = defineComponent(
|
|
3
|
+
"dice/Die",
|
|
4
|
+
{
|
|
5
|
+
index: t.u8({ description: "Its body in the roll (percentile dice are two bodies)." }),
|
|
6
|
+
kind: t.string({ description: "The roll die kind: d4 \u2026 d20, d100, percentile." }),
|
|
7
|
+
definition: t.string({ description: "The die definition it shows." }),
|
|
8
|
+
value: t.u16({ description: "The definition's value on top once landed." }),
|
|
9
|
+
label: t.string({ description: "The printed mark on top." }),
|
|
10
|
+
dropped: t.bool({ description: "A dropped die: excluded from effects and attachments." })
|
|
11
|
+
},
|
|
12
|
+
{ description: "A die the dice table is presenting." }
|
|
13
|
+
);
|
|
14
|
+
const DiceRollRequest = defineComponent(
|
|
15
|
+
"dice/RollRequest",
|
|
16
|
+
{
|
|
17
|
+
roll: t.json({
|
|
18
|
+
description: "A DiceRoll: { id, dice: [{ kind, value, skin: { guid } }], seed, tray, motion, \u2026 }."
|
|
19
|
+
}),
|
|
20
|
+
replace: t.bool({ description: "Dismiss a roll that is playing instead of waiting for it." })
|
|
21
|
+
},
|
|
22
|
+
{ description: "Plays a dice roll when spawned; removed once the table takes it." }
|
|
23
|
+
);
|
|
24
|
+
export {
|
|
25
|
+
DiceDie,
|
|
26
|
+
DiceRollRequest
|
|
27
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Infer } from '@aethervtt/shard-core';
|
|
2
|
+
import type { DieDefinition } from './definition.js';
|
|
3
|
+
export declare const DieDefinitionAsset: import("@aethervtt/shard-assets").DataType<{
|
|
4
|
+
readonly version: import("@aethervtt/shard-core").FieldType<number, "u8">;
|
|
5
|
+
readonly id: import("@aethervtt/shard-core").FieldType<string, "object">;
|
|
6
|
+
readonly sides: import("@aethervtt/shard-core").FieldType<number, "u8">;
|
|
7
|
+
readonly vertices: import("@aethervtt/shard-core").FieldType<number[], "object">;
|
|
8
|
+
readonly read: import("@aethervtt/shard-core").FieldType<"face" | "vertex", "u8">;
|
|
9
|
+
readonly values: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").JsonValue, "object">;
|
|
10
|
+
readonly labels: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").JsonValue, "object">;
|
|
11
|
+
readonly collider: import("@aethervtt/shard-core").FieldType<"ball" | "hull", "u8">;
|
|
12
|
+
readonly sizeMm: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
13
|
+
readonly bevel: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
14
|
+
readonly markScale: import("@aethervtt/shard-core").FieldType<number, "f32">;
|
|
15
|
+
readonly oppositesSum: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
|
|
16
|
+
}, "dice/DieDefinition">;
|
|
17
|
+
export type DieDefinitionAssetValue = Infer<typeof DieDefinitionAsset>;
|
|
18
|
+
/** The definition a `*.die.json` describes (check it with validateDieDefinition). */
|
|
19
|
+
export declare function dieDefinitionOf(value: DieDefinitionAssetValue): DieDefinition;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { defineDataType } from "@aethervtt/shard-assets";
|
|
2
|
+
import { t } from "@aethervtt/shard-core";
|
|
3
|
+
const DieDefinitionAsset = defineDataType(
|
|
4
|
+
"dice/DieDefinition",
|
|
5
|
+
{
|
|
6
|
+
version: t.u8({ default: 1, min: 1, max: 1, description: "Format version: 1." }),
|
|
7
|
+
id: t.string({ description: "'d20', 'd10-tens', 'd100-ball'." }),
|
|
8
|
+
sides: t.u8({ min: 2, max: 100, description: "How many values it shows." }),
|
|
9
|
+
vertices: t.list(t.f32(), {
|
|
10
|
+
description: "Canonical points, x y z each, unit radius, in a fixed order: vertex ids index them."
|
|
11
|
+
}),
|
|
12
|
+
read: t.enum(["face", "vertex"], {
|
|
13
|
+
description: "The face on top reads, or (d4) the vertex on top."
|
|
14
|
+
}),
|
|
15
|
+
values: t.json({
|
|
16
|
+
default: {},
|
|
17
|
+
description: "Value by sorted vertex ids of a face ('0:1:4:5') or by a vertex id ('3')."
|
|
18
|
+
}),
|
|
19
|
+
labels: t.json({
|
|
20
|
+
default: {},
|
|
21
|
+
description: "The printed mark per value where it isn't the value ('00')."
|
|
22
|
+
}),
|
|
23
|
+
collider: t.enum(["hull", "ball"], { description: "A hull of the vertices, or a ball." }),
|
|
24
|
+
sizeMm: t.f32({ default: 16, min: 1, unit: "mm", description: "Landed footprint diameter." }),
|
|
25
|
+
bevel: t.f32({
|
|
26
|
+
default: 0.082,
|
|
27
|
+
min: 0,
|
|
28
|
+
max: 0.3,
|
|
29
|
+
description: "Chamfer toward each face center."
|
|
30
|
+
}),
|
|
31
|
+
markScale: t.f32({
|
|
32
|
+
default: 0.4,
|
|
33
|
+
min: 0.05,
|
|
34
|
+
max: 1,
|
|
35
|
+
description: "Mark height, of a face's size."
|
|
36
|
+
}),
|
|
37
|
+
oppositesSum: t.bool({ description: "Opposite faces must sum to sides + 1." })
|
|
38
|
+
},
|
|
39
|
+
{ extension: "die", description: "A die: its shape, numbering and printed labels." }
|
|
40
|
+
);
|
|
41
|
+
function dieDefinitionOf(value) {
|
|
42
|
+
return {
|
|
43
|
+
version: 1,
|
|
44
|
+
id: value.id,
|
|
45
|
+
sides: value.sides,
|
|
46
|
+
vertices: Float32Array.from(value.vertices),
|
|
47
|
+
read: value.read,
|
|
48
|
+
values: value.values,
|
|
49
|
+
labels: value.labels,
|
|
50
|
+
collider: value.collider,
|
|
51
|
+
sizeMm: value.sizeMm,
|
|
52
|
+
bevel: value.bevel,
|
|
53
|
+
markScale: value.markScale,
|
|
54
|
+
oppositesSum: value.oppositesSum
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
DieDefinitionAsset,
|
|
59
|
+
dieDefinitionOf
|
|
60
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { ShardError } from '@aethervtt/shard-core';
|
|
2
|
+
import { type Face } from './hull.js';
|
|
3
|
+
import { type Q4, type V3 } from './math.js';
|
|
4
|
+
/**
|
|
5
|
+
* A die's shape and numbering, as data (0054). Built-ins cover d4 to d20, the two percentile d10s
|
|
6
|
+
* and a d100 ball; a host can register more with `defineDie`.
|
|
7
|
+
*/
|
|
8
|
+
export interface DieDefinition {
|
|
9
|
+
version: 1;
|
|
10
|
+
/** 'd20', 'd10-tens', 'd100-ball'. */
|
|
11
|
+
id: string;
|
|
12
|
+
sides: number;
|
|
13
|
+
/**
|
|
14
|
+
* Canonical points, unit radius, in a fixed order: vertex ids are indices into it. For a ball,
|
|
15
|
+
* each point is a cell's center (its normal).
|
|
16
|
+
*/
|
|
17
|
+
vertices: Float32Array;
|
|
18
|
+
/** The face on top reads, or (d4) the vertex on top. */
|
|
19
|
+
read: 'face' | 'vertex';
|
|
20
|
+
/**
|
|
21
|
+
* Value by sorted vertex ids of a face (`'0:1:4:5'`), or by a vertex id (`'3'`) for vertex dice
|
|
22
|
+
* and ball cells. Keyed by ids, so the numbering doesn't depend on the order a hull returns.
|
|
23
|
+
*/
|
|
24
|
+
values: Record<string, number>;
|
|
25
|
+
/** The printed mark per value, where it isn't the value: `'00'` … `'90'`, or 10 → `'0'`. */
|
|
26
|
+
labels?: Record<number, string>;
|
|
27
|
+
/** A hull of the vertices, or a ball (every rotation is a symmetry). */
|
|
28
|
+
collider: 'hull' | 'ball';
|
|
29
|
+
/** Footprint diameter of a landed die, before the presentation scale. */
|
|
30
|
+
sizeMm: number;
|
|
31
|
+
/** Chamfer: how far each face corner moves toward its face's center (0..0.3). */
|
|
32
|
+
bevel: number;
|
|
33
|
+
/** Mark height as a fraction of a face's size (the atlas cell's inner square). */
|
|
34
|
+
markScale?: number;
|
|
35
|
+
/** Opposite faces must sum to `sides + 1` (checked by the validator). */
|
|
36
|
+
oppositesSum?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** A value's reading frame on the die: where it points, and which way its mark reads up. */
|
|
39
|
+
export interface ValueFrame {
|
|
40
|
+
value: number;
|
|
41
|
+
/** The direction that's up when this value shows: a face normal, or the d4's vertex. */
|
|
42
|
+
normal: V3;
|
|
43
|
+
/** Across the mark, left to right. */
|
|
44
|
+
tangent: V3;
|
|
45
|
+
/** The mark's up. `tangent × bitangent = normal`. */
|
|
46
|
+
bitangent: V3;
|
|
47
|
+
}
|
|
48
|
+
/** What the engine derives from a definition, once per content hash. */
|
|
49
|
+
export interface DieGeometry {
|
|
50
|
+
definition: DieDefinition;
|
|
51
|
+
hash: number;
|
|
52
|
+
/** Unit-radius points (a ball's cell centers). */
|
|
53
|
+
points: V3[];
|
|
54
|
+
/** The polytope that's drawn and (for hulls) collided: faces with ordered vertices. */
|
|
55
|
+
polytope: {
|
|
56
|
+
points: V3[];
|
|
57
|
+
faces: Face[];
|
|
58
|
+
};
|
|
59
|
+
/** The value each polytope face carries (face dice), or undefined (d4: marks per corner). */
|
|
60
|
+
faceValues: (number | undefined)[];
|
|
61
|
+
/** d4: the value at each point (vertex id → value). */
|
|
62
|
+
vertexValues: number[] | undefined;
|
|
63
|
+
/** Frames by value (index value − 1). */
|
|
64
|
+
frames: ValueFrame[];
|
|
65
|
+
/** Rotations mapping the die onto itself; empty for a ball (every rotation is one). */
|
|
66
|
+
symmetries: Q4[];
|
|
67
|
+
/** Landed footprint diameter at unit radius: the size a die of this definition shows. */
|
|
68
|
+
footprint: number;
|
|
69
|
+
/** Distance from the center to the floor when resting on a face (1 for a ball). */
|
|
70
|
+
restHeight: number;
|
|
71
|
+
}
|
|
72
|
+
/** 32-bit FNV-1a over the definition's canonical content. */
|
|
73
|
+
export declare function dieHash(def: DieDefinition): number;
|
|
74
|
+
/**
|
|
75
|
+
* Checks a definition and returns every problem, with a path to the field: every face (or vertex,
|
|
76
|
+
* or cell) has exactly one value, values are 1..sides with no duplicates, labels name real values,
|
|
77
|
+
* and opposite faces sum to `sides + 1` where the definition opts in.
|
|
78
|
+
*/
|
|
79
|
+
export declare function validateDieDefinition(def: DieDefinition): ShardError[];
|
|
80
|
+
/**
|
|
81
|
+
* Derives a definition's faces, value frames and symmetry group, cached by its content hash.
|
|
82
|
+
* Throws `dice/invalid-definition` naming the first problem.
|
|
83
|
+
*/
|
|
84
|
+
export declare function dieGeometry(def: DieDefinition): DieGeometry;
|
|
85
|
+
/** The value's frame, or `dice/invalid-value` if the die can't show it. */
|
|
86
|
+
export declare function frameOf(geometry: DieGeometry, value: number): ValueFrame;
|
|
87
|
+
/** The printed mark of a value: its label, or the value itself. */
|
|
88
|
+
export declare function labelOf(def: DieDefinition, value: number): string;
|
|
89
|
+
/**
|
|
90
|
+
* Registers a definition by id so skins and rolls can name it. Registering the same content again
|
|
91
|
+
* is a no-op; a different definition under a taken id throws `dice/registry-conflict`.
|
|
92
|
+
*/
|
|
93
|
+
export declare function defineDie(def: DieDefinition): DieDefinition;
|
|
94
|
+
export declare function findDie(id: string): DieDefinition | undefined;
|
|
95
|
+
export declare function requireDie(id: string): DieDefinition;
|
|
96
|
+
export declare function allDice(): DieDefinition[];
|
|
97
|
+
/** Scale of a die of `def` at presentation scale 1: its footprint is `sizeMm` relative to the d20's 18 mm. */
|
|
98
|
+
export declare function physicalScale(geometry: DieGeometry, reference: DieGeometry): number;
|