@dice-o-rolla/dice-renderer-three 0.3.1 → 0.5.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/README.md +13 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/mesh-factory.d.ts +6 -3
- package/dist/mesh-factory.js +46 -9
- package/dist/renderer-common.d.ts +9 -0
- package/dist/renderer-common.js +39 -0
- package/dist/renderer-options.d.ts +10 -0
- package/dist/renderer-options.js +0 -0
- package/dist/three-dice-renderer.d.ts +2 -9
- package/dist/three-dice-renderer.js +6 -41
- package/dist/top-down-dice-renderer.d.ts +2 -4
- package/dist/top-down-dice-renderer.js +16 -41
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -7,6 +7,19 @@ renderer and `TopDownDiceRenderer` for overhead application surfaces.
|
|
|
7
7
|
import { ThreeDiceRenderer, TopDownDiceRenderer } from '@dice-o-rolla/dice-renderer-three';
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
+
Both renderers accept the same `materialProvider` option, either as an existing
|
|
11
|
+
`ThreeFaceMaterialProvider` or as a factory receiving the initialized `WebGLRenderer`. This keeps
|
|
12
|
+
KTX2 capability detection and GPU-backed asset setup available in perspective and top-down layouts.
|
|
13
|
+
Their common theme, resizing, framebuffer-limit, antialiasing, and material options are exported as
|
|
14
|
+
`ThreeRendererOptions`; top-down options only extend that contract with camera and tray framing.
|
|
15
|
+
|
|
16
|
+
A material provider may implement `getSurfaceUvs(definition, preset)` to return a complete
|
|
17
|
+
`SurfaceUvMap` keyed by physical face value. Coordinates follow each face's vertex order. The
|
|
18
|
+
mesh factory validates them before allocation and writes them to `uv1`, preserving the original
|
|
19
|
+
`uv` channel for labels. Assign `texture.channel = 1` for maps that use the surface unwrap.
|
|
20
|
+
`createPolyhedronGeometry(definition, scale, surfaceUvs)` also accepts the map directly;
|
|
21
|
+
`validateSurfaceUvs` is available for early validation. Omitting the map preserves existing UVs.
|
|
22
|
+
|
|
10
23
|
Most browser applications should use the preassembled entry point from
|
|
11
24
|
`@dice-o-rolla/dice-engine/browser`.
|
|
12
25
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { ThreeMaterialFactory, type DiceMaterialStyle, type FaceLabel, type FaceMaterialOptions, } from './material-factory.js';
|
|
2
|
-
export { createPolyhedronGeometry, createFaceUvs, DEFAULT_THREE_THEME, getFaceLabel, ThreeDiceMeshFactory, type ThreeDiceMesh, type FaceMaterialContext, type FaceMaterialResource, type ThreeFaceMaterialProvider, } from './mesh-factory.js';
|
|
2
|
+
export { createPolyhedronGeometry, validateSurfaceUvs, type SurfaceUvMap, type Vector2Tuple, createFaceUvs, DEFAULT_THREE_THEME, getFaceLabel, ThreeDiceMeshFactory, type ThreeDiceMesh, type FaceMaterialContext, type FaceMaterialResource, type ThreeFaceMaterialProvider, } from './mesh-factory.js';
|
|
3
|
+
export type { ThreeRendererOptions } from './renderer-options.js';
|
|
3
4
|
export { ThreeCamera, ThreeLighting, ThreeScene } from './scene.js';
|
|
4
5
|
export { TopDownCamera, type TopDownCameraOptions } from './top-down-camera.js';
|
|
5
6
|
export { TopDownDiceRenderer, type TopDownDiceRendererOptions } from './top-down-dice-renderer.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ThreeMaterialFactory, } from './material-factory.js';
|
|
2
|
-
export { createPolyhedronGeometry, createFaceUvs, DEFAULT_THREE_THEME, getFaceLabel, ThreeDiceMeshFactory, } from './mesh-factory.js';
|
|
2
|
+
export { createPolyhedronGeometry, validateSurfaceUvs, createFaceUvs, DEFAULT_THREE_THEME, getFaceLabel, ThreeDiceMeshFactory, } from './mesh-factory.js';
|
|
3
3
|
export { ThreeCamera, ThreeLighting, ThreeScene } from './scene.js';
|
|
4
4
|
export { TopDownCamera } from './top-down-camera.js';
|
|
5
5
|
export { TopDownDiceRenderer } from './top-down-dice-renderer.js';
|
package/dist/mesh-factory.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PolygonDefinition, PolyhedronDefinition } from '@dice-o-rolla/dice-geometry';
|
|
2
2
|
import type { RendererTheme, VisualPresetDescriptor } from '@dice-o-rolla/dice-renderer';
|
|
3
3
|
import { BufferGeometry, Mesh, type MeshStandardMaterial } from 'three';
|
|
4
|
-
type Vector2Tuple = readonly [u: number, v: number];
|
|
4
|
+
export type Vector2Tuple = readonly [u: number, v: number];
|
|
5
5
|
export declare const DEFAULT_THREE_THEME: RendererTheme;
|
|
6
6
|
export interface ThreeDiceMesh {
|
|
7
7
|
readonly mesh: Mesh<BufferGeometry, MeshStandardMaterial[]>;
|
|
@@ -18,11 +18,13 @@ export interface FaceMaterialResource {
|
|
|
18
18
|
readonly material: MeshStandardMaterial;
|
|
19
19
|
dispose(): void;
|
|
20
20
|
}
|
|
21
|
+
export type SurfaceUvMap = Readonly<Record<number, readonly Vector2Tuple[]>>;
|
|
21
22
|
export interface ThreeFaceMaterialProvider {
|
|
23
|
+
getSurfaceUvs?(definition: PolyhedronDefinition, preset?: VisualPresetDescriptor): SurfaceUvMap | undefined;
|
|
22
24
|
createFace(context: FaceMaterialContext): FaceMaterialResource;
|
|
23
25
|
dispose?(): void;
|
|
24
26
|
}
|
|
25
|
-
export declare function createPolyhedronGeometry(definition: PolyhedronDefinition, scale?: number): BufferGeometry;
|
|
27
|
+
export declare function createPolyhedronGeometry(definition: PolyhedronDefinition, scale?: number, surfaceUvs?: SurfaceUvMap): BufferGeometry;
|
|
26
28
|
export declare class ThreeDiceMeshFactory {
|
|
27
29
|
#private;
|
|
28
30
|
constructor(materials?: ThreeFaceMaterialProvider);
|
|
@@ -31,4 +33,5 @@ export declare class ThreeDiceMeshFactory {
|
|
|
31
33
|
}
|
|
32
34
|
export declare function createFaceUvs(definition: PolyhedronDefinition, face: PolygonDefinition): readonly Vector2Tuple[];
|
|
33
35
|
export declare function getFaceLabel(definition: PolyhedronDefinition, face: PolygonDefinition, faceLabels?: Readonly<Record<number, string | number>>): string | number | readonly number[];
|
|
34
|
-
|
|
36
|
+
/** Validate complete per-corner UVs before allocating geometry or materials. */
|
|
37
|
+
export declare function validateSurfaceUvs(definition: PolyhedronDefinition, uvs: SurfaceUvMap): void;
|
package/dist/mesh-factory.js
CHANGED
|
@@ -10,10 +10,13 @@ export const DEFAULT_THREE_THEME = Object.freeze({
|
|
|
10
10
|
roughness: 0.28,
|
|
11
11
|
metalness: 0,
|
|
12
12
|
});
|
|
13
|
-
export function createPolyhedronGeometry(definition, scale = 1) {
|
|
13
|
+
export function createPolyhedronGeometry(definition, scale = 1, surfaceUvs) {
|
|
14
14
|
if (!Number.isFinite(scale) || scale <= 0) {
|
|
15
15
|
throw new RangeError('scale must be a positive finite number');
|
|
16
16
|
}
|
|
17
|
+
if (surfaceUvs !== undefined)
|
|
18
|
+
validateSurfaceUvs(definition, surfaceUvs);
|
|
19
|
+
const surface = [];
|
|
17
20
|
const positions = [];
|
|
18
21
|
const uvs = [];
|
|
19
22
|
const geometry = new BufferGeometry();
|
|
@@ -30,12 +33,15 @@ export function createPolyhedronGeometry(definition, scale = 1) {
|
|
|
30
33
|
}
|
|
31
34
|
positions.push(vertex[0] * scale, vertex[1] * scale, vertex[2] * scale);
|
|
32
35
|
uvs.push(uv[0], uv[1]);
|
|
36
|
+
const mapped = surfaceUvs?.[face.value]?.[faceIndex] ?? uv;
|
|
37
|
+
surface.push(mapped[0], mapped[1]);
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
geometry.addGroup(start, positions.length / 3 - start, materialIndex);
|
|
36
41
|
});
|
|
37
42
|
geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
|
|
38
43
|
geometry.setAttribute('uv', new Float32BufferAttribute(uvs, 2));
|
|
44
|
+
geometry.setAttribute('uv1', new Float32BufferAttribute(surface, 2));
|
|
39
45
|
geometry.computeVertexNormals();
|
|
40
46
|
geometry.computeBoundingSphere();
|
|
41
47
|
return geometry;
|
|
@@ -46,14 +52,24 @@ export class ThreeDiceMeshFactory {
|
|
|
46
52
|
this.#materials = materials;
|
|
47
53
|
}
|
|
48
54
|
create(definition, theme = DEFAULT_THREE_THEME, scale = 1, faceLabels, preset) {
|
|
49
|
-
const geometry = createPolyhedronGeometry(definition, scale);
|
|
50
|
-
const resources =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
const geometry = createPolyhedronGeometry(definition, scale, this.#materials.getSurfaceUvs?.(definition, preset));
|
|
56
|
+
const resources = [];
|
|
57
|
+
try {
|
|
58
|
+
for (const face of definition.faces)
|
|
59
|
+
resources.push(this.#materials.createFace({
|
|
60
|
+
label: getFaceLabel(definition, face, faceLabels),
|
|
61
|
+
faceValue: face.value,
|
|
62
|
+
theme,
|
|
63
|
+
...(preset === undefined ? {} : { preset }),
|
|
64
|
+
...(definition.id === 'd10' ? { labelScale: D10_LABEL_SCALE } : {}),
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
geometry.dispose();
|
|
69
|
+
for (const resource of resources)
|
|
70
|
+
resource.dispose();
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
57
73
|
const materials = resources.map(({ material }) => material);
|
|
58
74
|
const mesh = new Mesh(geometry, materials);
|
|
59
75
|
mesh.castShadow = true;
|
|
@@ -169,3 +185,24 @@ export function getFaceLabel(definition, face, faceLabels) {
|
|
|
169
185
|
return 0;
|
|
170
186
|
return face.value;
|
|
171
187
|
}
|
|
188
|
+
/** Validate complete per-corner UVs before allocating geometry or materials. */
|
|
189
|
+
export function validateSurfaceUvs(definition, uvs) {
|
|
190
|
+
const fail = (face) => {
|
|
191
|
+
throw new RangeError(`Geometry "${definition.id}" face ${face}: invalid surface UVs`);
|
|
192
|
+
};
|
|
193
|
+
for (const key of Object.keys(uvs)) {
|
|
194
|
+
if (!definition.faces.some((face) => String(face.value) === key))
|
|
195
|
+
fail(Number(key));
|
|
196
|
+
}
|
|
197
|
+
for (const face of definition.faces) {
|
|
198
|
+
const coordinates = uvs[face.value];
|
|
199
|
+
if (!Array.isArray(coordinates) || coordinates.length !== face.indices.length)
|
|
200
|
+
fail(face.value);
|
|
201
|
+
for (const uv of coordinates) {
|
|
202
|
+
if (!Array.isArray(uv) ||
|
|
203
|
+
uv.length !== 2 ||
|
|
204
|
+
!uv.every((value) => Number.isFinite(value) && value >= 0 && value <= 1))
|
|
205
|
+
fail(face.value);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RenderDieState, RendererTheme, VisualPresetDescriptor } from '@dice-o-rolla/dice-renderer';
|
|
2
|
+
import type { ThreeDiceMesh, ThreeDiceMeshFactory } from './mesh-factory.js';
|
|
3
|
+
export interface ThreeRenderEntry {
|
|
4
|
+
resource: ThreeDiceMesh;
|
|
5
|
+
state: RenderDieState;
|
|
6
|
+
}
|
|
7
|
+
export declare function copyRenderState(state: RenderDieState): RenderDieState;
|
|
8
|
+
export declare function haveEqualFaceLabels(left: RenderDieState['faceLabels'], right: RenderDieState['faceLabels']): boolean;
|
|
9
|
+
export declare function createDiceMeshResource(state: RenderDieState, presets: ReadonlyMap<string, VisualPresetDescriptor>, meshFactory: ThreeDiceMeshFactory, theme: RendererTheme): ThreeDiceMesh;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getDieGeometry, getRegisteredDieTypes } from '@dice-o-rolla/dice-geometry';
|
|
2
|
+
export function copyRenderState(state) {
|
|
3
|
+
return {
|
|
4
|
+
id: state.id,
|
|
5
|
+
presetId: state.presetId,
|
|
6
|
+
geometryId: state.geometryId,
|
|
7
|
+
scale: state.scale,
|
|
8
|
+
...(state.faceLabels === undefined ? {} : { faceLabels: { ...state.faceLabels } }),
|
|
9
|
+
previous: {
|
|
10
|
+
position: { ...state.previous.position },
|
|
11
|
+
quaternion: { ...state.previous.quaternion },
|
|
12
|
+
},
|
|
13
|
+
current: {
|
|
14
|
+
position: { ...state.current.position },
|
|
15
|
+
quaternion: { ...state.current.quaternion },
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function haveEqualFaceLabels(left, right) {
|
|
20
|
+
if (left === right)
|
|
21
|
+
return true;
|
|
22
|
+
if (left === undefined || right === undefined)
|
|
23
|
+
return false;
|
|
24
|
+
const leftEntries = Object.entries(left);
|
|
25
|
+
return (leftEntries.length === Object.keys(right).length &&
|
|
26
|
+
leftEntries.every(([face, label]) => right[Number(face)] === label));
|
|
27
|
+
}
|
|
28
|
+
export function createDiceMeshResource(state, presets, meshFactory, theme) {
|
|
29
|
+
const preset = presets.get(state.presetId);
|
|
30
|
+
if (preset === undefined)
|
|
31
|
+
throw new Error(`Unknown visual preset: ${state.presetId}`);
|
|
32
|
+
if (preset.geometryId !== state.geometryId || (preset.scale ?? 1) !== state.scale) {
|
|
33
|
+
throw new Error(`Render state does not match visual preset "${state.presetId}"`);
|
|
34
|
+
}
|
|
35
|
+
const type = getRegisteredDieTypes().find((registered) => registered === state.geometryId);
|
|
36
|
+
if (type === undefined)
|
|
37
|
+
throw new Error(`Unsupported geometry: ${state.geometryId}`);
|
|
38
|
+
return meshFactory.create(getDieGeometry(type), theme, state.scale, state.faceLabels, preset);
|
|
39
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RendererTheme } from '@dice-o-rolla/dice-renderer';
|
|
2
|
+
import type { WebGLRenderer } from 'three';
|
|
3
|
+
import type { ThreeFaceMaterialProvider } from './mesh-factory.js';
|
|
4
|
+
import type { ViewportLimitOptions } from './viewport-limits.js';
|
|
5
|
+
/** Options shared by every official Three.js dice renderer. */
|
|
6
|
+
export interface ThreeRendererOptions extends Partial<RendererTheme>, ViewportLimitOptions {
|
|
7
|
+
readonly antialias?: boolean;
|
|
8
|
+
readonly observeResize?: boolean;
|
|
9
|
+
readonly materialProvider?: ThreeFaceMaterialProvider | ((renderer: WebGLRenderer) => ThreeFaceMaterialProvider);
|
|
10
|
+
}
|
|
File without changes
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import type { DiceRenderer, RenderDieState, RendererTheme, RendererViewport, VisualPresetDescriptor } from '@dice-o-rolla/dice-renderer';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
export interface ThreeDiceRendererOptions extends Partial<RendererTheme> {
|
|
5
|
-
readonly antialias?: boolean;
|
|
6
|
-
readonly observeResize?: boolean;
|
|
7
|
-
readonly maxPixelRatio?: number;
|
|
8
|
-
readonly maxViewportDimension?: number;
|
|
9
|
-
readonly maxFramebufferPixels?: number;
|
|
10
|
-
readonly materialProvider?: ThreeFaceMaterialProvider | ((renderer: WebGLRenderer) => ThreeFaceMaterialProvider);
|
|
2
|
+
import type { ThreeRendererOptions } from './renderer-options.js';
|
|
3
|
+
export interface ThreeDiceRendererOptions extends ThreeRendererOptions {
|
|
11
4
|
}
|
|
12
5
|
export declare class ThreeDiceRenderer implements DiceRenderer {
|
|
13
6
|
#private;
|
|
@@ -1,26 +1,9 @@
|
|
|
1
|
-
import { getDieGeometry, getRegisteredDieTypes } from '@dice-o-rolla/dice-geometry';
|
|
2
1
|
import { PCFShadowMap, WebGLRenderer } from 'three';
|
|
3
2
|
import { DEFAULT_THREE_THEME, ThreeDiceMeshFactory, } from './mesh-factory.js';
|
|
3
|
+
import { copyRenderState, createDiceMeshResource, haveEqualFaceLabels, } from './renderer-common.js';
|
|
4
4
|
import { ThreeCamera, ThreeScene } from './scene.js';
|
|
5
5
|
import { applyInterpolatedTransform } from './transform.js';
|
|
6
6
|
import { fitViewportToLimits, resolveViewportLimits, validateViewport, } from './viewport-limits.js';
|
|
7
|
-
function copyState(state) {
|
|
8
|
-
return {
|
|
9
|
-
id: state.id,
|
|
10
|
-
presetId: state.presetId,
|
|
11
|
-
geometryId: state.geometryId,
|
|
12
|
-
scale: state.scale,
|
|
13
|
-
...(state.faceLabels === undefined ? {} : { faceLabels: { ...state.faceLabels } }),
|
|
14
|
-
previous: {
|
|
15
|
-
position: { ...state.previous.position },
|
|
16
|
-
quaternion: { ...state.previous.quaternion },
|
|
17
|
-
},
|
|
18
|
-
current: {
|
|
19
|
-
position: { ...state.current.position },
|
|
20
|
-
quaternion: { ...state.current.quaternion },
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
7
|
export class ThreeDiceRenderer {
|
|
25
8
|
#container;
|
|
26
9
|
#options;
|
|
@@ -96,7 +79,7 @@ export class ThreeDiceRenderer {
|
|
|
96
79
|
if (this.#entries.has(state.id))
|
|
97
80
|
throw new Error(`A render die with id "${state.id}" already exists`);
|
|
98
81
|
const resource = this.#createResource(state);
|
|
99
|
-
const ownedState =
|
|
82
|
+
const ownedState = copyRenderState(state);
|
|
100
83
|
applyInterpolatedTransform(resource.mesh, ownedState, 1);
|
|
101
84
|
this.#scene?.value.add(resource.mesh);
|
|
102
85
|
this.#entries.set(state.id, { resource, state: ownedState });
|
|
@@ -115,7 +98,7 @@ export class ThreeDiceRenderer {
|
|
|
115
98
|
if (!haveEqualFaceLabels(state.faceLabels, entry.state.faceLabels)) {
|
|
116
99
|
throw new Error('Die face labels cannot be changed after creation');
|
|
117
100
|
}
|
|
118
|
-
entry.state =
|
|
101
|
+
entry.state = copyRenderState(state);
|
|
119
102
|
}
|
|
120
103
|
removeDie(id) {
|
|
121
104
|
this.#assertAlive();
|
|
@@ -190,16 +173,7 @@ export class ThreeDiceRenderer {
|
|
|
190
173
|
}, this.#viewportLimits));
|
|
191
174
|
}
|
|
192
175
|
#createResource(state) {
|
|
193
|
-
|
|
194
|
-
if (preset === undefined)
|
|
195
|
-
throw new Error(`Unknown visual preset: ${state.presetId}`);
|
|
196
|
-
if (preset.geometryId !== state.geometryId || (preset.scale ?? 1) !== state.scale) {
|
|
197
|
-
throw new Error(`Render state does not match visual preset "${state.presetId}"`);
|
|
198
|
-
}
|
|
199
|
-
const type = getRegisteredDieTypes().find((registered) => registered === state.geometryId);
|
|
200
|
-
if (type === undefined)
|
|
201
|
-
throw new Error(`Unsupported geometry: ${state.geometryId}`);
|
|
202
|
-
return this.#meshFactory.create(getDieGeometry(type), this.#theme, state.scale, state.faceLabels, preset);
|
|
176
|
+
return createDiceMeshResource(state, this.#presets, this.#meshFactory, this.#theme);
|
|
203
177
|
}
|
|
204
178
|
#releasePendingPreset(presetId) {
|
|
205
179
|
if (!this.#pendingPresetRemovals.has(presetId))
|
|
@@ -215,17 +189,8 @@ export class ThreeDiceRenderer {
|
|
|
215
189
|
}
|
|
216
190
|
#assertInitialized() {
|
|
217
191
|
this.#assertAlive();
|
|
218
|
-
if (this.#renderer === undefined)
|
|
192
|
+
if (this.#scene === undefined || this.#camera === undefined || this.#renderer === undefined) {
|
|
219
193
|
throw new Error('Renderer has not been initialized');
|
|
194
|
+
}
|
|
220
195
|
}
|
|
221
196
|
}
|
|
222
|
-
function haveEqualFaceLabels(left, right) {
|
|
223
|
-
if (left === right)
|
|
224
|
-
return true;
|
|
225
|
-
if (left === undefined || right === undefined)
|
|
226
|
-
return false;
|
|
227
|
-
const leftEntries = Object.entries(left);
|
|
228
|
-
const rightEntries = Object.entries(right);
|
|
229
|
-
return (leftEntries.length === rightEntries.length &&
|
|
230
|
-
leftEntries.every(([face, label]) => right[Number(face)] === label));
|
|
231
|
-
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import type { DiceRenderer, RenderDieState, RendererTheme, RendererViewport, VisualPresetDescriptor } from '@dice-o-rolla/dice-renderer';
|
|
2
|
+
import type { ThreeRendererOptions } from './renderer-options.js';
|
|
2
3
|
import { type TopDownCameraOptions } from './top-down-camera.js';
|
|
3
|
-
|
|
4
|
-
export interface TopDownDiceRendererOptions extends Partial<RendererTheme>, ViewportLimitOptions, TopDownCameraOptions {
|
|
5
|
-
readonly antialias?: boolean;
|
|
6
|
-
readonly observeResize?: boolean;
|
|
4
|
+
export interface TopDownDiceRendererOptions extends ThreeRendererOptions, TopDownCameraOptions {
|
|
7
5
|
}
|
|
8
6
|
export declare class TopDownDiceRenderer implements DiceRenderer {
|
|
9
7
|
#private;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { getDieGeometry, getRegisteredDieTypes } from '@dice-o-rolla/dice-geometry';
|
|
2
1
|
import { AmbientLight, DirectionalLight, Mesh, PCFShadowMap, PlaneGeometry, Scene, ShadowMaterial, WebGLRenderer, } from 'three';
|
|
3
|
-
import { DEFAULT_THREE_THEME, ThreeDiceMeshFactory } from './mesh-factory.js';
|
|
2
|
+
import { DEFAULT_THREE_THEME, ThreeDiceMeshFactory, } from './mesh-factory.js';
|
|
3
|
+
import { copyRenderState, createDiceMeshResource, haveEqualFaceLabels, } from './renderer-common.js';
|
|
4
4
|
import { TopDownCamera } from './top-down-camera.js';
|
|
5
5
|
import { applyInterpolatedTransform } from './transform.js';
|
|
6
6
|
import { fitViewportToLimits, resolveViewportLimits, validateViewport, } from './viewport-limits.js';
|
|
7
7
|
export class TopDownDiceRenderer {
|
|
8
8
|
#container;
|
|
9
9
|
#options;
|
|
10
|
-
#meshFactory
|
|
10
|
+
#meshFactory;
|
|
11
|
+
#materialProvider;
|
|
11
12
|
#entries = new Map();
|
|
12
13
|
#presets = new Map();
|
|
13
14
|
#pendingPresetRemovals = new Set();
|
|
@@ -26,6 +27,9 @@ export class TopDownDiceRenderer {
|
|
|
26
27
|
constructor(container, options = {}) {
|
|
27
28
|
this.#container = container;
|
|
28
29
|
this.#options = options;
|
|
30
|
+
this.#materialProvider =
|
|
31
|
+
typeof options.materialProvider === 'function' ? undefined : options.materialProvider;
|
|
32
|
+
this.#meshFactory = new ThreeDiceMeshFactory(this.#materialProvider);
|
|
29
33
|
this.#viewportLimits = resolveViewportLimits(options);
|
|
30
34
|
this.#trayWidth = options.trayWidth ?? 10;
|
|
31
35
|
this.#trayDepth = options.trayDepth ?? 10;
|
|
@@ -65,6 +69,10 @@ export class TopDownDiceRenderer {
|
|
|
65
69
|
alpha: true,
|
|
66
70
|
powerPreference: 'high-performance',
|
|
67
71
|
});
|
|
72
|
+
if (typeof this.#options.materialProvider === 'function') {
|
|
73
|
+
this.#materialProvider = this.#options.materialProvider(renderer);
|
|
74
|
+
this.#meshFactory = new ThreeDiceMeshFactory(this.#materialProvider);
|
|
75
|
+
}
|
|
68
76
|
renderer.setClearColor(0x000000, 0);
|
|
69
77
|
renderer.shadowMap.enabled = true;
|
|
70
78
|
renderer.shadowMap.type = PCFShadowMap;
|
|
@@ -100,7 +108,7 @@ export class TopDownDiceRenderer {
|
|
|
100
108
|
throw new Error(`A render die with id "${state.id}" already exists`);
|
|
101
109
|
}
|
|
102
110
|
const resource = this.#createResource(state);
|
|
103
|
-
const ownedState =
|
|
111
|
+
const ownedState = copyRenderState(state);
|
|
104
112
|
applyInterpolatedTransform(resource.mesh, ownedState, 1);
|
|
105
113
|
this.#scene?.add(resource.mesh);
|
|
106
114
|
this.#entries.set(state.id, { resource, state: ownedState });
|
|
@@ -119,7 +127,7 @@ export class TopDownDiceRenderer {
|
|
|
119
127
|
if (!haveEqualFaceLabels(entry.state.faceLabels, state.faceLabels)) {
|
|
120
128
|
throw new Error('Die face labels cannot be changed after creation');
|
|
121
129
|
}
|
|
122
|
-
entry.state =
|
|
130
|
+
entry.state = copyRenderState(state);
|
|
123
131
|
}
|
|
124
132
|
removeDie(id) {
|
|
125
133
|
this.#assertAlive();
|
|
@@ -185,6 +193,8 @@ export class TopDownDiceRenderer {
|
|
|
185
193
|
this.#resizeObserver = undefined;
|
|
186
194
|
this.#presets.clear();
|
|
187
195
|
this.#pendingPresetRemovals.clear();
|
|
196
|
+
this.#materialProvider?.dispose?.();
|
|
197
|
+
this.#materialProvider = undefined;
|
|
188
198
|
this.#destroyed = true;
|
|
189
199
|
}
|
|
190
200
|
#resizeToContainer() {
|
|
@@ -198,16 +208,7 @@ export class TopDownDiceRenderer {
|
|
|
198
208
|
}, this.#viewportLimits));
|
|
199
209
|
}
|
|
200
210
|
#createResource(state) {
|
|
201
|
-
|
|
202
|
-
if (preset === undefined)
|
|
203
|
-
throw new Error(`Unknown visual preset: ${state.presetId}`);
|
|
204
|
-
if (preset.geometryId !== state.geometryId || (preset.scale ?? 1) !== state.scale) {
|
|
205
|
-
throw new Error(`Render state does not match visual preset "${state.presetId}"`);
|
|
206
|
-
}
|
|
207
|
-
const type = getRegisteredDieTypes().find((registered) => registered === state.geometryId);
|
|
208
|
-
if (type === undefined)
|
|
209
|
-
throw new Error(`Unsupported geometry: ${state.geometryId}`);
|
|
210
|
-
return this.#meshFactory.create(getDieGeometry(type), this.#theme, state.scale, state.faceLabels);
|
|
211
|
+
return createDiceMeshResource(state, this.#presets, this.#meshFactory, this.#theme);
|
|
211
212
|
}
|
|
212
213
|
#releasePendingPreset(presetId) {
|
|
213
214
|
if (!this.#pendingPresetRemovals.has(presetId))
|
|
@@ -228,29 +229,3 @@ export class TopDownDiceRenderer {
|
|
|
228
229
|
}
|
|
229
230
|
}
|
|
230
231
|
}
|
|
231
|
-
function copyState(state) {
|
|
232
|
-
return {
|
|
233
|
-
id: state.id,
|
|
234
|
-
presetId: state.presetId,
|
|
235
|
-
geometryId: state.geometryId,
|
|
236
|
-
scale: state.scale,
|
|
237
|
-
...(state.faceLabels === undefined ? {} : { faceLabels: { ...state.faceLabels } }),
|
|
238
|
-
previous: {
|
|
239
|
-
position: { ...state.previous.position },
|
|
240
|
-
quaternion: { ...state.previous.quaternion },
|
|
241
|
-
},
|
|
242
|
-
current: {
|
|
243
|
-
position: { ...state.current.position },
|
|
244
|
-
quaternion: { ...state.current.quaternion },
|
|
245
|
-
},
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
function haveEqualFaceLabels(left, right) {
|
|
249
|
-
if (left === right)
|
|
250
|
-
return true;
|
|
251
|
-
if (left === undefined || right === undefined)
|
|
252
|
-
return false;
|
|
253
|
-
const leftEntries = Object.entries(left);
|
|
254
|
-
return (leftEntries.length === Object.keys(right).length &&
|
|
255
|
-
leftEntries.every(([face, label]) => right[Number(face)] === label));
|
|
256
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dice-o-rolla/dice-renderer-three",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Three.js WebGL renderers for animated physical dice.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dice",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"registry": "https://registry.npmjs.org/"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@dice-o-rolla/dice-geometry": "0.
|
|
44
|
-
"@dice-o-rolla/dice-renderer": "0.
|
|
43
|
+
"@dice-o-rolla/dice-geometry": "0.5.0",
|
|
44
|
+
"@dice-o-rolla/dice-renderer": "0.5.0",
|
|
45
45
|
"@types/three": "^0.185.0",
|
|
46
46
|
"three": "^0.185.0"
|
|
47
47
|
},
|