@jfk-solutions/demo3d-file-format 1.7.0 → 1.9.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.
Files changed (86) hide show
  1. package/README.md +289 -1
  2. package/dist/examples/export-embedded-viewer-standalone.html +193 -0
  3. package/dist/examples/three-render-smoke-standalone.html +4997 -0
  4. package/dist/index.d.ts +9 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +8 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/model.d.ts +20 -3
  9. package/dist/model.d.ts.map +1 -1
  10. package/dist/model.js +89 -11
  11. package/dist/model.js.map +1 -1
  12. package/dist/parser.d.ts.map +1 -1
  13. package/dist/parser.js +135 -9
  14. package/dist/parser.js.map +1 -1
  15. package/dist/raw3d-animation.d.ts +63 -0
  16. package/dist/raw3d-animation.d.ts.map +1 -0
  17. package/dist/raw3d-animation.js +200 -0
  18. package/dist/raw3d-animation.js.map +1 -0
  19. package/dist/raw3d-effects.d.ts +66 -0
  20. package/dist/raw3d-effects.d.ts.map +1 -0
  21. package/dist/raw3d-effects.js +172 -0
  22. package/dist/raw3d-effects.js.map +1 -0
  23. package/dist/raw3d-skinning.d.ts +8 -0
  24. package/dist/raw3d-skinning.d.ts.map +1 -0
  25. package/dist/raw3d-skinning.js +37 -0
  26. package/dist/raw3d-skinning.js.map +1 -0
  27. package/dist/raw3d.d.ts +84 -4
  28. package/dist/raw3d.d.ts.map +1 -1
  29. package/dist/raw3d.js +111 -9
  30. package/dist/raw3d.js.map +1 -1
  31. package/dist/rigid-interpolation.d.ts +16 -0
  32. package/dist/rigid-interpolation.d.ts.map +1 -0
  33. package/dist/rigid-interpolation.js +52 -0
  34. package/dist/rigid-interpolation.js.map +1 -0
  35. package/dist/three/backend.d.ts +13 -0
  36. package/dist/three/backend.d.ts.map +1 -0
  37. package/dist/three/backend.js +9 -0
  38. package/dist/three/backend.js.map +1 -0
  39. package/dist/three/effects.d.ts +7 -0
  40. package/dist/three/effects.d.ts.map +1 -0
  41. package/dist/three/effects.js +127 -0
  42. package/dist/three/effects.js.map +1 -0
  43. package/dist/three/index.d.ts +5 -1
  44. package/dist/three/index.d.ts.map +1 -1
  45. package/dist/three/index.js +35 -1
  46. package/dist/three/index.js.map +1 -1
  47. package/dist/three/native-lighting.d.ts +5 -0
  48. package/dist/three/native-lighting.d.ts.map +1 -0
  49. package/dist/three/native-lighting.js +86 -0
  50. package/dist/three/native-lighting.js.map +1 -0
  51. package/dist/three/parallax.d.ts +5 -0
  52. package/dist/three/parallax.d.ts.map +1 -0
  53. package/dist/three/parallax.js +63 -0
  54. package/dist/three/parallax.js.map +1 -0
  55. package/dist/three/presentation.d.ts +31 -0
  56. package/dist/three/presentation.d.ts.map +1 -0
  57. package/dist/three/presentation.js +137 -0
  58. package/dist/three/presentation.js.map +1 -0
  59. package/dist/three/raw3d.d.ts +27 -0
  60. package/dist/three/raw3d.d.ts.map +1 -1
  61. package/dist/three/raw3d.js +536 -25
  62. package/dist/three/raw3d.js.map +1 -1
  63. package/dist/three/renderer.d.ts.map +1 -1
  64. package/dist/three/renderer.js +3 -0
  65. package/dist/three/renderer.js.map +1 -1
  66. package/dist/three/skinning.d.ts +11 -0
  67. package/dist/three/skinning.d.ts.map +1 -0
  68. package/dist/three/skinning.js +92 -0
  69. package/dist/three/skinning.js.map +1 -0
  70. package/dist/three/tsl.d.ts +60 -0
  71. package/dist/three/tsl.d.ts.map +1 -0
  72. package/dist/three/tsl.js +3 -0
  73. package/dist/three/tsl.js.map +1 -0
  74. package/dist/three/webgpu-native.d.ts +3 -0
  75. package/dist/three/webgpu-native.d.ts.map +1 -0
  76. package/dist/three/webgpu-native.js +58 -0
  77. package/dist/three/webgpu-native.js.map +1 -0
  78. package/dist/three/webgpu-relief.d.ts +3 -0
  79. package/dist/three/webgpu-relief.d.ts.map +1 -0
  80. package/dist/three/webgpu-relief.js +61 -0
  81. package/dist/three/webgpu-relief.js.map +1 -0
  82. package/dist/xml.d.ts +4 -1
  83. package/dist/xml.d.ts.map +1 -1
  84. package/dist/xml.js +14 -3
  85. package/dist/xml.js.map +1 -1
  86. package/package.json +6 -2
package/README.md CHANGED
@@ -3,7 +3,12 @@
3
3
  Browser-first TypeScript parser and Three.js adapter for Demo3D/Emulate3D
4
4
  `.demo3d` project files and render-ready `.raw3d` scene files.
5
5
 
6
- The parser accepts an `ArrayBuffer`, `Uint8Array`, or `DataView`, reads the outer ZIP package, extracts the nested XML model document, and returns a lossless object structure with typed helpers for the pieces most useful to rendering adapters.
6
+ The parser accepts an `ArrayBuffer`, `Uint8Array`, or `DataView`, reads the outer
7
+ ZIP package, extracts either the traditional nested XML model document or the
8
+ Demo3D 2026 `Model.xml`, and returns a lossless object structure with typed
9
+ helpers for the pieces most useful to rendering adapters. Demo3D 2026 external
10
+ geometry caches (`meshes/map.xml` with `v*.dat`, `i*.dat`, and `s*.dat`
11
+ buffers) are decoded automatically.
7
12
 
8
13
  ## Install
9
14
 
@@ -25,6 +30,12 @@ console.log(parsed.model.meshes.length);
25
30
  console.log(parsed.model.visuals.length);
26
31
  ```
27
32
 
33
+ Saved project views are available through `parsed.model.views`; the name stored
34
+ in `parsed.model.defaultView` identifies the project's default view. Each view
35
+ contains its Demo3D `position` and `target` vectors. The included Three.js
36
+ viewer exposes these from its **Saved views** menu and opens the default view
37
+ automatically. RAW3D saved views are exposed as `parsed.model.views` as well.
38
+
28
39
  RAW3D scenes use a separate, strongly typed API because their `Model.xml`, flat
29
40
  node hierarchy, and external vertex/index buffers differ from the editable
30
41
  Demo3D project schema:
@@ -47,6 +58,217 @@ submeshes, and shadow flags.
47
58
 
48
59
  Runtime code has no production dependencies and uses browser APIs only. ZIP method `8` entries are decompressed with `DecompressionStream("deflate-raw")`.
49
60
 
61
+ ## RAW3D animation without a renderer
62
+
63
+ The root package has no Three.js dependency. It reads animation channels and
64
+ samples native node state independently of any renderer, UI, or playback clock:
65
+
66
+ ```ts
67
+ import { parseRaw3D } from "@jfk-solutions/demo3d-file-format";
68
+
69
+ const parsed = await parseRaw3D(await file.arrayBuffer());
70
+ const animation = parsed.model.animation; // Lazy, cached typed Updates parser.
71
+ console.log(animation.duration, animation.tracks, animation.warnings);
72
+ const states = animation.sample(1.25); // Absolute simulation seconds.
73
+ const node = animation.sampleNode(0, 1.25);
74
+ console.log(node.location, node.quaternion, node.layerIndex);
75
+ ```
76
+
77
+ Supported tracks include position, rotation, scale, own-geometry visibility,
78
+ deletion, parent, mesh, layer, interaction mode, highlight, optimization metadata,
79
+ and material diffuse ARGB, texture offsets/scales, reflectivity, transparency,
80
+ luminosity, normal scale, and point size. Unknown tracks remain available through
81
+ their original XML and produce warnings; their timestamps still contribute to
82
+ the clip duration. Reading metadata does not execute its native behavior.
83
+ Invalid supported tracks fail explicitly. Duplicate key times select the last
84
+ value, and seeking before a first key restores the node's initial state.
85
+
86
+ `Raw3DInterpolationMode` matches the Emulate3D 2026 enum: `Default=0`, `None=1`,
87
+ `Linear=2`, `DualQuaternion=3`. `None` holds the previous key; `Linear` interpolates
88
+ numeric channels, with shortest-path angle interpolation. This library treats
89
+ `Default` as linear. Discrete properties and ARGB colors switch at key times.
90
+
91
+ `DualQuaternion` couples position and orientation using normalized dual-quaternion
92
+ linear blending (DLB). Scale stays separate. `quaternion` and `rotation` describe
93
+ the same sampled orientation in native coordinates. Transform keys are evaluated
94
+ on the union of the node's position/rotation key times. This original implementation
95
+ is derived from the mathematical representation and normalization in
96
+ [Kavan et al., Dual Quaternions for Rigid Transformation Blending (2006)](https://users.cs.utah.edu/~ladislav/kavan06dual/kavan06dual.pdf).
97
+ DLB follows a rigid screw path but is not constant-speed ScLERP. Exact parity
98
+ with Demo3D's interpolation variant/default policy has not yet been established.
99
+ No vendor or paper implementation code is copied.
100
+
101
+ Native node visibility, interpolation mode, highlight, user data, owner,
102
+ decal/billboard flags, material luminosity/offsets/point size, and scene floor,
103
+ lighting, ambient color and quality properties have typed accessors. Unknown XML
104
+ is still retained; these accessors do not imply full native shader, billboard,
105
+ decal, floor, interaction, or scripting behavior.
106
+
107
+ ### Optional Three.js playback
108
+
109
+ Only the `/three` entry point binds the sampled state to Three.js objects:
110
+
111
+ ```ts
112
+ import { createRaw3DThreePlayback } from "@jfk-solutions/demo3d-file-format/three";
113
+
114
+ const playback = await createRaw3DThreePlayback(parsed, { three: THREE });
115
+ scene.add(playback.group);
116
+ playback.seek(1.25);
117
+ renderer.render(scene, camera);
118
+ // The application owns its animation clock, play/pause, speed and looping.
119
+ scene.remove(playback.group);
120
+ playback.dispose();
121
+ ```
122
+
123
+ The binding applies transforms, visibility, deletion, parenting, mesh swaps,
124
+ layer-material fallback, and supported material changes. It preserves explicit
125
+ node materials when a layer changes and isolates animated materials between
126
+ nodes. Structural groups stay enabled when only their own geometry is hidden.
127
+ Retired mesh samples are released, and `dispose()` releases owned GPU resources.
128
+ Playback does not run simulation scripts, physics, or PLC connections. Both
129
+ native bone/weight buffers and baked deforming mesh updates are supported.
130
+
131
+ The demo and generated standalone viewer offer Play/Pause and an absolute-time
132
+ slider for animated RAW3D files. Embedding the original RAW3D retains animation;
133
+ the source-protected Three.js snapshot export remains a static snapshot.
134
+
135
+ ### Native effects, surfaces and skinning
136
+
137
+ Use `animation.sampleFrame(time)` for a complete renderer-independent frame:
138
+ `nodes`, `lights`, `buttons`, and `scene`. The same current frame is available as
139
+ `playback.state` after `seek()`. Extended tracks and original XML are exposed by
140
+ `animation.effects.tracks`.
141
+
142
+ - Material `Texture`, `NormalMap`/`NormalTexture` references switch discretely,
143
+ including clearing a texture with a negative index. Referenced archive textures
144
+ are preloaded even when they are only used by later animation keys. Color and
145
+ normal textures are isolated per animated node and restored on backward seeks.
146
+ - Native light channels include `LightType`, `LightEnabled`, `LightMaterial`,
147
+ `LightAR`, `LightAC`, `LightAL`, `LightAQ`, `LightSF`, `LightSIA`, `LightSOA`, and
148
+ `LightCS`. Range, constant/linear/quadratic attenuation, total inner/outer cone
149
+ angles, falloff and shadow flags are applied. WebGL hooks and the TSL node backend implement
150
+ the distance/cone equations described in [Microsoft's Direct3D lighting
151
+ documentation](https://learn.microsoft.com/en-us/windows/win32/direct3d9/attenuation-and-spotlight-factor).
152
+ `evaluateRaw3DLightAttenuation()` exposes the same calculation without Three.js.
153
+ Shadow rendering still requires the host renderer's shadow map to be enabled.
154
+ - `SceneShowDefaultFloor`, `SceneAllowTeleportOnDefaultFloor`,
155
+ `SceneUseDefaultLighting`, and `ALR`/`ALG`/`ALB` update the sampled scene.
156
+ The Three binding provides ambient/default light and a finite floor preview on
157
+ the recorded plane. Teleport permission is metadata; browser navigation is a
158
+ host concern. Host-added lighting remains under host control.
159
+ - Native button visibility and checked-state channels are sampled and displayed
160
+ by the demo. Their original native serializer names `UpdateUIVisible` and
161
+ `UpdateUIChecked`, as well as `UIVisible`/`UIChecked`, are accepted. Displayed
162
+ buttons are read-only; the library does not execute their model actions.
163
+ - Unambiguous `UserData`/`ClientOwner` string keys are sampled as metadata. The
164
+ native serializer joins string arrays with spaces without escaping boundaries.
165
+ A single key may contain spaces; multi-key values with ambiguous boundaries
166
+ remain in XML with `RAW3D_STRING_KEYS_AMBIGUOUS` instead of being guessed.
167
+ - The native `Default`, `FlatText`, and `PointCloud` material shader modes map to
168
+ lit, unlit, and vertex-colored point rendering. Packed ARGB/byte/short vertex
169
+ attributes retain their native strides. RAW3D material `A` is opacity; the
170
+ public `transparency` property is its complement. This corrects older builds
171
+ which inverted partially transparent native materials.
172
+ - Billboards orient their own drawable toward the render camera while leaving
173
+ the node hierarchy intact. Decal flags apply depth bias and suppress depth
174
+ writes on the provided surface geometry; no projection onto an unrelated mesh
175
+ is invented.
176
+ - Mesh `Bones` expose driving scene-node references, bind offsets and native
177
+ skin index/weight buffer references. `decodeRaw3DSkinInfluences()` validates and
178
+ normalizes weights without a renderer. The Three adapter constructs GPU
179
+ `SkinnedMesh`/`Skeleton` bindings for up to four influences per vertex. Larger
180
+ influence sets use CPU skinning with positions, normals and tangents; no
181
+ weights are discarded. Unweighted vertices retain their mesh position.
182
+
183
+ Native channel names, bone fields and shader enums have a checked-in fixture
184
+ generated by the installed Emulate3D 2026 SDK serializer. To regenerate it on
185
+ Windows, run `./scripts/generate-native-feature-fixture.ps1`; other platforms can
186
+ run the tests using the existing fixture without that proprietary SDK.
187
+ Synthetic rigs exercise GPU/CPU deformation, bind offsets and reversible seeks.
188
+ Comparison against a captured native skinned person remains a separate parity
189
+ check; the current Bauhaus recording contains baked human geometry.
190
+
191
+ ### Lossless strings and external resources
192
+
193
+ Native string arrays lose their boundaries when values containing spaces are
194
+ joined into a single attribute. Supply authoritative keys from a sidecar through
195
+ the renderer-independent parser option:
196
+
197
+ ```ts
198
+ const parsed = await parseRaw3D(bytes, {
199
+ resolveStringKeys: ({ nodeIndex, property, times, encoded, xml }) => {
200
+ return sidecar.lookup(nodeIndex, property, times); // string[] or undefined
201
+ },
202
+ resolveResource: async path => resources.get(path) // Uint8Array or undefined
203
+ });
204
+ ```
205
+
206
+ The resolver runs for string tracks and must return one string per timestamp,
207
+ including empty values. Incorrect lengths/types fail explicitly. Archive entries
208
+ take precedence over `resolveResource`; no filesystem/network request is made
209
+ automatically. A library-side extension also accepts `<Values><Value>...</Value>
210
+ ...</Values>` children, or `ValueEncoding="json"` with a JSON array in `Value`.
211
+ These extensions are explicit conventions for this reader, not a claim that the
212
+ native serializer emits them. The original XML stays available. Missing
213
+ authoritative data still produces `RAW3D_STRING_KEYS_AMBIGUOUS`; impossible
214
+ boundaries are not guessed.
215
+
216
+ `parsed.model.streamableTextures` exposes native index, ID, name, dimensions,
217
+ path and any archived/resolved image bytes. Material `StreamableTexture` selects
218
+ the corresponding image. For video, a live camera or a recorded frame sequence,
219
+ provide Three.js textures and use asynchronous seeking:
220
+
221
+ ```ts
222
+ const playback = await createRaw3DThreePlayback(parsed, {
223
+ three: THREE,
224
+ resolveStreamTexture: async ({ index, time, source, signal }) => {
225
+ return frames.textureAt(index, time, signal); // Texture or undefined
226
+ }
227
+ });
228
+ await playback.seekAsync(2.5); // false if superseded by a newer seek
229
+ ```
230
+
231
+ Resolvers receive clip time, stream metadata and an abort signal. A newer seek,
232
+ synchronous seek, or disposal cancels pending requests; stale responses never
233
+ replace newer imagery. Returned textures remain caller-owned. The player owns
234
+ only its clones. Returning `undefined` restores the archived fallback or clears
235
+ the unavailable frame and reports a warning. A synchronous `seek()` retains the
236
+ currently resolved stream image; use `seekAsync()` for frame-accurate seeking.
237
+ Missing video frames still need an external source; a file reader cannot
238
+ reconstruct imagery that was never recorded. No URL or camera is opened without
239
+ an explicit host resolver.
240
+
241
+ ### Relief shading and the node renderer
242
+
243
+ `NormalDepth` drives layered parallax/relief UV ray marching with 32 layers and
244
+ five binary refinements in both the WebGL shader and TSL material paths. Heights
245
+ are read from the normal image's alpha channel by default; select a different
246
+ packed channel with `parallaxHeightChannel: "r" | "g" | "b" | "a"`. Opaque alpha
247
+ represents a flat top surface. Color and normal samples share the relief offset,
248
+ and animated depth updates are reversible. Depth controls texture-coordinate
249
+ displacement; this does not add geometry or alter silhouettes. A normal image
250
+ without height data cannot supply missing surface depth by itself. The height
251
+ packing and scale may need adjustment for a particular native export.
252
+
253
+ The renderer factory automatically registers native point/spot light nodes and
254
+ node materials with `WebGPURenderer`. Native attenuation, range and cone falloff
255
+ are calculated through TSL, which supports both the WebGPU and node-renderer
256
+ WebGL backends. Existing lights and materials outside the native binding keep
257
+ their standard behavior. For a manually constructed renderer, configure it
258
+ before creating RAW3D groups:
259
+
260
+ ```ts
261
+ import * as THREE from "three/webgpu";
262
+ import { configureRaw3DWebGPURenderer } from "@jfk-solutions/demo3d-file-format/three";
263
+ await configureRaw3DWebGPURenderer(renderer, THREE);
264
+ ```
265
+
266
+ Unbundled hosts must map `three/tsl` alongside `three/webgpu` to their matching
267
+ Three.js release. Node rendering code is loaded on demand; importing the parser
268
+ still loads no renderer. Browser tests compare visible relief and attenuation
269
+ changes plus backward restoration under WebGL and the TSL WebGL backend. The
270
+ hardware WebGPU case runs only when an adapter is available.
271
+
50
272
  The default single-pass XML parser builds the Demo3D object tree directly, avoiding the time and memory cost of an intermediate browser DOM. For unusual XML inputs, callers can opt into the browser parser or inject another DOM implementation:
51
273
 
52
274
  ```ts
@@ -66,6 +288,46 @@ npm run demo:three
66
288
 
67
289
  Then open the printed `http://127.0.0.1:.../examples/three-render-smoke.html` URL and choose a `.demo3d` or `.raw3d` file.
68
290
 
291
+ To create a single-file version that can be opened directly from the file
292
+ system without a server, run:
293
+
294
+ ```bash
295
+ npm run build:test-page
296
+ ```
297
+
298
+ Open `dist/examples/three-render-smoke-standalone.html`. Three.js,
299
+ OrbitControls, and the Demo3D parser/renderer are bundled into that HTML file;
300
+ only the `.demo3d` or `.raw3d` file selected by the user remains external. The
301
+ normal `npm run build` command also generates this standalone page.
302
+
303
+ ### Export an Embedded Viewer
304
+
305
+ To create a standalone HTML viewer with one model embedded inside it, run:
306
+
307
+ ```bash
308
+ npm run demo:export
309
+ ```
310
+
311
+ Open the printed URL, choose either a `.demo3d` or `.raw3d` file, and select
312
+ an export mode:
313
+
314
+ - **Generated Three.js objects and cameras (source-protected)** is the default.
315
+ It converts the
316
+ model first and creates `<model>-three-viewer.html` containing only serialized
317
+ Three.js objects, materials, geometry, textures, layer visibility, and
318
+ cameras. The original archive, `Model.xml`, and other Demo3D project data are
319
+ not included, so the source `.demo3d` file cannot be extracted from the
320
+ exported viewer. Rendered mesh data necessarily remains inspectable in any
321
+ browser-based viewer.
322
+ - **Original Demo3D/RAW3D file (lossless)** creates `<model>-viewer.html` with
323
+ the original file embedded.
324
+
325
+ Select **Download viewer HTML** after choosing the mode. The downloaded page can
326
+ be opened directly with `file://` and presents the model immediately without a
327
+ file-open control. The exporter itself is also available as
328
+ `dist/examples/export-embedded-viewer-standalone.html` and can be opened
329
+ directly from the file system.
330
+
69
331
  The parser root remains independent of Three.js. Renderer features that require
70
332
  procedural reconstruction are opt-in through the `demo3d-file-format/three`
71
333
  subpath:
@@ -160,6 +422,31 @@ lights come from the same Three.js module as the renderer. Set
160
422
  WebGPU can improve repeated rendering of complex scenes, but it does not speed
161
423
  up archive/XML parsing and may have higher first-frame shader compilation cost.
162
424
 
425
+ ### Enhanced Render Mode
426
+
427
+ For a more presentation-ready view, apply the optional enhanced preset after
428
+ adding the parsed group to a scene. It enables soft shadows, marks opaque model
429
+ meshes as shadow casters/receivers, adds fitted key and fill lighting, and puts
430
+ a matte contact floor below the model:
431
+
432
+ ```ts
433
+ import { applyDemo3DThreeRenderMode } from "demo3d-file-format/three";
434
+
435
+ const presentation = applyDemo3DThreeRenderMode({
436
+ three: selected.three,
437
+ renderer: selected.renderer,
438
+ scene,
439
+ object: group,
440
+ mode: "enhanced"
441
+ });
442
+
443
+ // Remove the preset and restore the previous renderer and mesh settings.
444
+ presentation.dispose();
445
+ ```
446
+
447
+ Use `mode: "standard"` for the lighter-weight lighting preset. The browser
448
+ smoke-test page includes a **Better render** switch for comparing both modes.
449
+
163
450
  ## Current Scope
164
451
 
165
452
  - Read-only parser.
@@ -167,6 +454,7 @@ up archive/XML parsing and may have higher first-frame shader compilation cost.
167
454
  - Stored and DEFLATE ZIP entries.
168
455
  - Full XML tree preservation.
169
456
  - Core typed classes for project header, visuals, meshes, materials, resources, and unknown typed entries.
457
+ - Demo3D 2026 `Model.xml`, flat visual hierarchies, and external mesh caches.
170
458
  - Vendor-specific object types are preserved as `Demo3DUnknownObject` until explicit classes are added.
171
459
  - RAW3D `Model.xml` scene metadata and external geometry/texture resources.
172
460
  - RAW3D Three.js rendering with source geometry shared across scene nodes.