@dice-o-rolla/dice-renderer-three 0.4.0 → 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 CHANGED
@@ -13,6 +13,13 @@ KTX2 capability detection and GPU-backed asset setup available in perspective an
13
13
  Their common theme, resizing, framebuffer-limit, antialiasing, and material options are exported as
14
14
  `ThreeRendererOptions`; top-down options only extend that contract with camera and tray framing.
15
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
+
16
23
  Most browser applications should use the preassembled entry point from
17
24
  `@dice-o-rolla/dice-engine/browser`.
18
25
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
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
3
  export type { ThreeRendererOptions } from './renderer-options.js';
4
4
  export { ThreeCamera, ThreeLighting, ThreeScene } from './scene.js';
5
5
  export { TopDownCamera, type TopDownCameraOptions } from './top-down-camera.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';
@@ -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
- export {};
36
+ /** Validate complete per-corner UVs before allocating geometry or materials. */
37
+ export declare function validateSurfaceUvs(definition: PolyhedronDefinition, uvs: SurfaceUvMap): void;
@@ -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 = definition.faces.map((face) => this.#materials.createFace({
51
- label: getFaceLabel(definition, face, faceLabels),
52
- faceValue: face.value,
53
- theme,
54
- ...(preset === undefined ? {} : { preset }),
55
- ...(definition.id === 'd10' ? { labelScale: D10_LABEL_SCALE } : {}),
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dice-o-rolla/dice-renderer-three",
3
- "version": "0.4.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.4.0",
44
- "@dice-o-rolla/dice-renderer": "0.4.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
  },