@dice-o-rolla/dice-renderer-three 0.3.0 → 0.4.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 CHANGED
@@ -7,6 +7,12 @@ 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
+
10
16
  Most browser applications should use the preassembled entry point from
11
17
  `@dice-o-rolla/dice-engine/browser`.
12
18
 
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
2
  export { createPolyhedronGeometry, 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';
@@ -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 { WebGLRenderer } from 'three';
3
- import { type ThreeFaceMaterialProvider } from './mesh-factory.js';
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 = copyState(state);
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 = copyState(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
- const preset = this.#presets.get(state.presetId);
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
- import { type ViewportLimitOptions } from './viewport-limits.js';
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 = new ThreeDiceMeshFactory();
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 = copyState(state);
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 = copyState(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
- const preset = this.#presets.get(state.presetId);
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.0",
3
+ "version": "0.4.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.3.0",
44
- "@dice-o-rolla/dice-renderer": "0.3.0",
43
+ "@dice-o-rolla/dice-geometry": "0.4.0",
44
+ "@dice-o-rolla/dice-renderer": "0.4.0",
45
45
  "@types/three": "^0.185.0",
46
46
  "three": "^0.185.0"
47
47
  },