@zephyr3d/scene 0.9.25 → 0.9.27

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 (299) hide show
  1. package/dist/animation/animationset.js +92 -30
  2. package/dist/animation/animationset.js.map +1 -1
  3. package/dist/animation/hair/gpu_hair_simulation.js +1090 -0
  4. package/dist/animation/hair/gpu_hair_simulation.js.map +1 -0
  5. package/dist/animation/joint_dynamics/controller.js +7 -5
  6. package/dist/animation/joint_dynamics/controller.js.map +1 -1
  7. package/dist/animation/joint_dynamics/convex_collider.js +320 -0
  8. package/dist/animation/joint_dynamics/convex_collider.js.map +1 -0
  9. package/dist/animation/joint_dynamics/joint_dynamics_system.js +81 -5
  10. package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -1
  11. package/dist/animation/morphtrack.js +3 -4
  12. package/dist/animation/morphtrack.js.map +1 -1
  13. package/dist/animation/skeleton.js +79 -25
  14. package/dist/animation/skeleton.js.map +1 -1
  15. package/dist/animation/spring/multi_chain_spring_system.js +577 -179
  16. package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
  17. package/dist/animation/spring/spring_collider.js +4 -3
  18. package/dist/animation/spring/spring_collider.js.map +1 -1
  19. package/dist/animation/spring/spring_node_pose_tracker.js +40 -0
  20. package/dist/animation/spring/spring_node_pose_tracker.js.map +1 -0
  21. package/dist/animation/spring/spring_particle.js +2 -0
  22. package/dist/animation/spring/spring_particle.js.map +1 -1
  23. package/dist/animation/spring/spring_system.js +11 -3
  24. package/dist/animation/spring/spring_system.js.map +1 -1
  25. package/dist/animation/spring_modifier.js +10 -0
  26. package/dist/animation/spring_modifier.js.map +1 -1
  27. package/dist/app/engine.js +53 -43
  28. package/dist/app/engine.js.map +1 -1
  29. package/dist/app/scriptingsystem.js +119 -6
  30. package/dist/app/scriptingsystem.js.map +1 -1
  31. package/dist/app/scriptregistry.js +3 -2
  32. package/dist/app/scriptregistry.js.map +1 -1
  33. package/dist/asset/assetmanager.js +284 -67
  34. package/dist/asset/assetmanager.js.map +1 -1
  35. package/dist/asset/loaders/zhair/zhair_format.js +288 -0
  36. package/dist/asset/loaders/zhair/zhair_format.js.map +1 -0
  37. package/dist/asset/loaders/zhair/zhair_loader.js +221 -0
  38. package/dist/asset/loaders/zhair/zhair_loader.js.map +1 -0
  39. package/dist/asset/model.js +184 -69
  40. package/dist/asset/model.js.map +1 -1
  41. package/dist/camera/camera.js +268 -44
  42. package/dist/camera/camera.js.map +1 -1
  43. package/dist/camera/perspectivecamera.js +3 -1
  44. package/dist/camera/perspectivecamera.js.map +1 -1
  45. package/dist/index.d.ts +9044 -5139
  46. package/dist/index.js +17 -6
  47. package/dist/index.js.map +1 -1
  48. package/dist/material/eye.js +932 -0
  49. package/dist/material/eye.js.map +1 -0
  50. package/dist/material/hair.js +917 -0
  51. package/dist/material/hair.js.map +1 -0
  52. package/dist/material/hairstrand.js +842 -0
  53. package/dist/material/hairstrand.js.map +1 -0
  54. package/dist/material/hairstrand_data.js +162 -0
  55. package/dist/material/hairstrand_data.js.map +1 -0
  56. package/dist/material/lambert.js +5 -2
  57. package/dist/material/lambert.js.map +1 -1
  58. package/dist/material/material.js +48 -10
  59. package/dist/material/material.js.map +1 -1
  60. package/dist/material/meshmaterial.js +120 -19
  61. package/dist/material/meshmaterial.js.map +1 -1
  62. package/dist/material/mixins/albedocolor.js +1 -1
  63. package/dist/material/mixins/albedocolor.js.map +1 -1
  64. package/dist/material/mixins/lightmodel/blinnphong.js +5 -3
  65. package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
  66. package/dist/material/mixins/lightmodel/lambert.js +5 -4
  67. package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
  68. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +3 -3
  69. package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
  70. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +5 -5
  71. package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
  72. package/dist/material/mixins/lightmodel/pbrspecularglossness.js +3 -3
  73. package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
  74. package/dist/material/mixins/lit.js +106 -19
  75. package/dist/material/mixins/lit.js.map +1 -1
  76. package/dist/material/mixins/pbr/common.js +4 -4
  77. package/dist/material/mixins/pbr/common.js.map +1 -1
  78. package/dist/material/mixins/texture.js +48 -11
  79. package/dist/material/mixins/texture.js.map +1 -1
  80. package/dist/material/mtoon.js +6 -5
  81. package/dist/material/mtoon.js.map +1 -1
  82. package/dist/material/pbrblueprint.js +146 -28
  83. package/dist/material/pbrblueprint.js.map +1 -1
  84. package/dist/material/pbrblueprint_instance.js +501 -0
  85. package/dist/material/pbrblueprint_instance.js.map +1 -0
  86. package/dist/material/pbrmr.js +15 -16
  87. package/dist/material/pbrmr.js.map +1 -1
  88. package/dist/material/shader/helper.js +734 -23
  89. package/dist/material/shader/helper.js.map +1 -1
  90. package/dist/material/shader/terrain_blend.js +55 -0
  91. package/dist/material/shader/terrain_blend.js.map +1 -0
  92. package/dist/material/skin.js +192 -32
  93. package/dist/material/skin.js.map +1 -1
  94. package/dist/material/spriteblueprint.js +16 -1
  95. package/dist/material/spriteblueprint.js.map +1 -1
  96. package/dist/material/subsurfaceprofile.js +86 -23
  97. package/dist/material/subsurfaceprofile.js.map +1 -1
  98. package/dist/material/water.js +2059 -72
  99. package/dist/material/water.js.map +1 -1
  100. package/dist/posteffect/bloom.js +167 -7
  101. package/dist/posteffect/bloom.js.map +1 -1
  102. package/dist/posteffect/coloradjust.js +2 -1
  103. package/dist/posteffect/coloradjust.js.map +1 -1
  104. package/dist/posteffect/compositor.js.map +1 -1
  105. package/dist/posteffect/fxaa.js +2 -1
  106. package/dist/posteffect/fxaa.js.map +1 -1
  107. package/dist/posteffect/grayscale.js +3 -1
  108. package/dist/posteffect/grayscale.js.map +1 -1
  109. package/dist/posteffect/motionblur.js +664 -56
  110. package/dist/posteffect/motionblur.js.map +1 -1
  111. package/dist/posteffect/posteffect.js +12 -1
  112. package/dist/posteffect/posteffect.js.map +1 -1
  113. package/dist/posteffect/sao.js +46 -44
  114. package/dist/posteffect/sao.js.map +1 -1
  115. package/dist/posteffect/skinsss.js +368 -69
  116. package/dist/posteffect/skinsss.js.map +1 -1
  117. package/dist/posteffect/ssgi.js +16 -9
  118. package/dist/posteffect/ssgi.js.map +1 -1
  119. package/dist/posteffect/ssr.js +3 -0
  120. package/dist/posteffect/ssr.js.map +1 -1
  121. package/dist/posteffect/sss.js +121 -36
  122. package/dist/posteffect/sss.js.map +1 -1
  123. package/dist/posteffect/taa.js.map +1 -1
  124. package/dist/posteffect/tonemap.js +44 -1
  125. package/dist/posteffect/tonemap.js.map +1 -1
  126. package/dist/render/abuffer_oit.js +8 -2
  127. package/dist/render/abuffer_oit.js.map +1 -1
  128. package/dist/render/clipmap.js +156 -5
  129. package/dist/render/clipmap.js.map +1 -1
  130. package/dist/render/cull_visitor.js +20 -0
  131. package/dist/render/cull_visitor.js.map +1 -1
  132. package/dist/render/depthpass.js +36 -2
  133. package/dist/render/depthpass.js.map +1 -1
  134. package/dist/render/drawable_mixin.js +26 -1
  135. package/dist/render/drawable_mixin.js.map +1 -1
  136. package/dist/render/dualdepthpeeling_oit.js +3 -2
  137. package/dist/render/dualdepthpeeling_oit.js.map +1 -1
  138. package/dist/render/envlight.js +1 -1
  139. package/dist/render/envlight.js.map +1 -1
  140. package/dist/render/fbm_wavegenerator.js +6 -3
  141. package/dist/render/fbm_wavegenerator.js.map +1 -1
  142. package/dist/render/fft_wavegenerator.js +57 -15
  143. package/dist/render/fft_wavegenerator.js.map +1 -1
  144. package/dist/render/gerstner_wavegenerator.js +130 -20
  145. package/dist/render/gerstner_wavegenerator.js.map +1 -1
  146. package/dist/render/globalbindgroup_allocator.js +3 -0
  147. package/dist/render/globalbindgroup_allocator.js.map +1 -1
  148. package/dist/render/hzb.js +80 -22
  149. package/dist/render/hzb.js.map +1 -1
  150. package/dist/render/lightpass.js +6 -3
  151. package/dist/render/lightpass.js.map +1 -1
  152. package/dist/render/oit.js +29 -0
  153. package/dist/render/oit.js.map +1 -0
  154. package/dist/render/render_queue.js +39 -0
  155. package/dist/render/render_queue.js.map +1 -1
  156. package/dist/render/renderer.js +4 -0
  157. package/dist/render/renderer.js.map +1 -1
  158. package/dist/render/rendergraph/blackboard.js +1 -0
  159. package/dist/render/rendergraph/blackboard.js.map +1 -1
  160. package/dist/render/rendergraph/device_pool_allocator.js.map +1 -1
  161. package/dist/render/rendergraph/executor.js +2 -0
  162. package/dist/render/rendergraph/executor.js.map +1 -1
  163. package/dist/render/rendergraph/forward_plus_builder.js +379 -10
  164. package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
  165. package/dist/render/rendergraph/frame_resource_requirements.js +1 -0
  166. package/dist/render/rendergraph/frame_resource_requirements.js.map +1 -1
  167. package/dist/render/rendergraph/history_resources.js +8 -1
  168. package/dist/render/rendergraph/history_resources.js.map +1 -1
  169. package/dist/render/rendergraph/rendergraph.js.map +1 -1
  170. package/dist/render/rendergraph/texture_affinity_cache.js +1 -1
  171. package/dist/render/rendergraph/texture_affinity_cache.js.map +1 -1
  172. package/dist/render/rendergraph/types.js.map +1 -1
  173. package/dist/render/shadow_mask_pass.js +73 -8
  174. package/dist/render/shadow_mask_pass.js.map +1 -1
  175. package/dist/render/sky.js +166 -126
  176. package/dist/render/sky.js.map +1 -1
  177. package/dist/render/underwater.js +624 -0
  178. package/dist/render/underwater.js.map +1 -0
  179. package/dist/render/water_caustics.js +1380 -0
  180. package/dist/render/water_caustics.js.map +1 -0
  181. package/dist/render/weightedblended_oit.js +3 -2
  182. package/dist/render/weightedblended_oit.js.map +1 -1
  183. package/dist/scene/basesprite.js +8 -0
  184. package/dist/scene/basesprite.js.map +1 -1
  185. package/dist/scene/batchgroup.js +3 -0
  186. package/dist/scene/batchgroup.js.map +1 -1
  187. package/dist/scene/environment.js +4 -1
  188. package/dist/scene/environment.js.map +1 -1
  189. package/dist/scene/hair.js +963 -0
  190. package/dist/scene/hair.js.map +1 -0
  191. package/dist/scene/light.js +5 -1
  192. package/dist/scene/light.js.map +1 -1
  193. package/dist/scene/mesh.js +481 -47
  194. package/dist/scene/mesh.js.map +1 -1
  195. package/dist/scene/meshdrawable.js +6 -0
  196. package/dist/scene/meshdrawable.js.map +1 -1
  197. package/dist/scene/msdftext.js +3 -0
  198. package/dist/scene/msdftext.js.map +1 -1
  199. package/dist/scene/msdftextsprite.js +3 -0
  200. package/dist/scene/msdftextsprite.js.map +1 -1
  201. package/dist/scene/particlesys.js +6 -0
  202. package/dist/scene/particlesys.js.map +1 -1
  203. package/dist/scene/scene.js +3 -0
  204. package/dist/scene/scene.js.map +1 -1
  205. package/dist/scene/scene_node.js +6 -1
  206. package/dist/scene/scene_node.js.map +1 -1
  207. package/dist/scene/terrain-cm/grass.js +3 -0
  208. package/dist/scene/terrain-cm/grass.js.map +1 -1
  209. package/dist/scene/terrain-cm/grassmaterial.js +3 -0
  210. package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
  211. package/dist/scene/terrain-cm/terrain-cm.js +6 -0
  212. package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
  213. package/dist/scene/terrain-cm/texture_cache.js +309 -0
  214. package/dist/scene/terrain-cm/texture_cache.js.map +1 -0
  215. package/dist/scene/water.js +546 -4
  216. package/dist/scene/water.js.map +1 -1
  217. package/dist/shaders/atmosphere.js.map +1 -1
  218. package/dist/shaders/diffusion.js +54 -0
  219. package/dist/shaders/diffusion.js.map +1 -0
  220. package/dist/shaders/shadow.js +33 -21
  221. package/dist/shaders/shadow.js.map +1 -1
  222. package/dist/shaders/temporal.js +71 -33
  223. package/dist/shaders/temporal.js.map +1 -1
  224. package/dist/shaders/water.js +22 -19
  225. package/dist/shaders/water.js.map +1 -1
  226. package/dist/shaders/water_medium.js +56 -0
  227. package/dist/shaders/water_medium.js.map +1 -0
  228. package/dist/shadow/dom.js +546 -0
  229. package/dist/shadow/dom.js.map +1 -0
  230. package/dist/shadow/pcf_opt.js +3 -5
  231. package/dist/shadow/pcf_opt.js.map +1 -1
  232. package/dist/shadow/pcf_pd.js +1 -1
  233. package/dist/shadow/pcf_pd.js.map +1 -1
  234. package/dist/shadow/receiver_bias.js +41 -0
  235. package/dist/shadow/receiver_bias.js.map +1 -0
  236. package/dist/shadow/shader.js +20 -60
  237. package/dist/shadow/shader.js.map +1 -1
  238. package/dist/shadow/shadow_impl.js +89 -0
  239. package/dist/shadow/shadow_impl.js.map +1 -1
  240. package/dist/shadow/shadow_region.js +1 -1
  241. package/dist/shadow/shadow_region.js.map +1 -1
  242. package/dist/shadow/shadowmapper.js +150 -37
  243. package/dist/shadow/shadowmapper.js.map +1 -1
  244. package/dist/shapes/shape.js +11 -2
  245. package/dist/shapes/shape.js.map +1 -1
  246. package/dist/shapes/sphere.js +50 -9
  247. package/dist/shapes/sphere.js.map +1 -1
  248. package/dist/utility/blueprint/common/constants.js +112 -2
  249. package/dist/utility/blueprint/common/constants.js.map +1 -1
  250. package/dist/utility/blueprint/material/func.js +142 -49
  251. package/dist/utility/blueprint/material/func.js.map +1 -1
  252. package/dist/utility/blueprint/material/ir.js +490 -25
  253. package/dist/utility/blueprint/material/ir.js.map +1 -1
  254. package/dist/utility/blueprint/material/texture.js +898 -4
  255. package/dist/utility/blueprint/material/texture.js.map +1 -1
  256. package/dist/utility/serialization/manager.js +434 -101
  257. package/dist/utility/serialization/manager.js.map +1 -1
  258. package/dist/utility/serialization/scene/animation.js +401 -5
  259. package/dist/utility/serialization/scene/animation.js.map +1 -1
  260. package/dist/utility/serialization/scene/batch.js +4 -0
  261. package/dist/utility/serialization/scene/batch.js.map +1 -1
  262. package/dist/utility/serialization/scene/camera.js +186 -26
  263. package/dist/utility/serialization/scene/camera.js.map +1 -1
  264. package/dist/utility/serialization/scene/common.js +26 -5
  265. package/dist/utility/serialization/scene/common.js.map +1 -1
  266. package/dist/utility/serialization/scene/hair.js +1141 -0
  267. package/dist/utility/serialization/scene/hair.js.map +1 -0
  268. package/dist/utility/serialization/scene/light.js +84 -4
  269. package/dist/utility/serialization/scene/light.js.map +1 -1
  270. package/dist/utility/serialization/scene/material.js +1867 -166
  271. package/dist/utility/serialization/scene/material.js.map +1 -1
  272. package/dist/utility/serialization/scene/mesh.js +231 -14
  273. package/dist/utility/serialization/scene/mesh.js.map +1 -1
  274. package/dist/utility/serialization/scene/node.js +141 -7
  275. package/dist/utility/serialization/scene/node.js.map +1 -1
  276. package/dist/utility/serialization/scene/particle.js +4 -0
  277. package/dist/utility/serialization/scene/particle.js.map +1 -1
  278. package/dist/utility/serialization/scene/primitive.js +18 -0
  279. package/dist/utility/serialization/scene/primitive.js.map +1 -1
  280. package/dist/utility/serialization/scene/sprite.js +4 -0
  281. package/dist/utility/serialization/scene/sprite.js.map +1 -1
  282. package/dist/utility/serialization/scene/terrain.js +4 -0
  283. package/dist/utility/serialization/scene/terrain.js.map +1 -1
  284. package/dist/utility/serialization/scene/text.js +4 -0
  285. package/dist/utility/serialization/scene/text.js.map +1 -1
  286. package/dist/utility/serialization/scene/water.js +1105 -21
  287. package/dist/utility/serialization/scene/water.js.map +1 -1
  288. package/dist/utility/serialization/types.js.map +1 -1
  289. package/dist/utility/shprojector.js +9 -8
  290. package/dist/utility/shprojector.js.map +1 -1
  291. package/dist/values.js +49 -2
  292. package/dist/values.js.map +1 -1
  293. package/package.json +3 -3
  294. package/dist/render/rendergraph/frame_graph_context.js +0 -41
  295. package/dist/render/rendergraph/frame_graph_context.js.map +0 -1
  296. package/dist/shaders/ssgi.js +0 -63
  297. package/dist/shaders/ssgi.js.map +0 -1
  298. package/dist/shaders/ssgi_reprojection.js +0 -118
  299. package/dist/shaders/ssgi_reprojection.js.map +0 -1
@@ -0,0 +1,288 @@
1
+ /**
2
+ * The `.zhair` container: strand geometry, ready to upload.
3
+ *
4
+ * @remarks
5
+ * Hair reaches the engine as an Alembic curve archive or a `.hair` model, and
6
+ * neither is cheap to open: the sample XGen archive is 84 MB of Ogawa container
7
+ * holding two million control points, and parsing it means walking an object
8
+ * tree and cross-checking property blocks before a single strand appears. Doing
9
+ * that every time a scene loads is not viable, so the editor converts once and
10
+ * writes this instead.
11
+ *
12
+ * The layout follows `.zabc`, the geometry cache container already in the tree:
13
+ * a magic, a version, a JSON manifest, and then every large array concatenated
14
+ * into one payload the manifest points into. The manifest stays readable for
15
+ * debugging while the arrays are copied out by offset rather than decoded.
16
+ *
17
+ * What is stored is {@link StrandCurveSet} - the format-neutral shape both
18
+ * importers already converge on - at **full authored density**. Thinning is a
19
+ * property of the node that draws the hair, not of the file, so an artist can
20
+ * pull strand count down for a distant character and back up for a close-up
21
+ * without returning to the source archive.
22
+ */ /** The only version this module writes. @public */ const ZHAIR_VERSION = 1;
23
+ /** Bytes before the manifest: magic, version, manifest length. @internal */ const ZHAIR_HEADER_SIZE = 12;
24
+ /**
25
+ * Packs strand sets into a `.zhair` file.
26
+ *
27
+ * @param sets - Strand sets to write, one per source curve object.
28
+ * @param options - Unit scale and source provenance.
29
+ * @returns The complete file.
30
+ *
31
+ * @public
32
+ */ function encodeZHair(sets, options) {
33
+ if (sets.length === 0) {
34
+ throw new Error('encodeZHair: no strand sets given');
35
+ }
36
+ // Two passes: the manifest has to carry the offset of every array, so the
37
+ // payload is laid out first and the arrays are copied only once the total size
38
+ // is known.
39
+ const chunks = [];
40
+ let payloadLength = 0;
41
+ const place = (data)=>{
42
+ // Each array starts on a 4-byte boundary so a reader may view the payload
43
+ // in place rather than copying, should it ever want to.
44
+ payloadLength = payloadLength + 3 & -4;
45
+ const ref = {
46
+ offset: payloadLength,
47
+ length: data.byteLength
48
+ };
49
+ chunks.push({
50
+ data,
51
+ ref
52
+ });
53
+ payloadLength += data.byteLength;
54
+ return ref;
55
+ };
56
+ const strandSets = sets.map((set, index)=>{
57
+ const counts = set.pointCounts;
58
+ const strandCount = counts.length;
59
+ let pointCount = 0;
60
+ for(let i = 0; i < strandCount; i++){
61
+ pointCount += counts[i];
62
+ }
63
+ if (strandCount === 0 || pointCount === 0) {
64
+ throw new Error(`encodeZHair: strand set ${index} is empty`);
65
+ }
66
+ if (set.positions.length < pointCount * 3) {
67
+ throw new Error(`encodeZHair: strand set ${index} holds ${set.positions.length / 3} points but its topology needs ${pointCount}`);
68
+ }
69
+ const json = {
70
+ name: set.name || `Strands${index}`,
71
+ strandCount,
72
+ pointCount,
73
+ bounds: computeBounds(set.positions, pointCount),
74
+ // Counts are stored as uint32 whatever the source used, so a reader has
75
+ // one type to handle.
76
+ positions: place(exactFloat32(set.positions, pointCount * 3)),
77
+ pointCounts: place(toUint32(counts))
78
+ };
79
+ if (set.widths && set.widths.length > 0) {
80
+ const expected = set.widthPerStrand ? strandCount : pointCount;
81
+ json.widths = place(exactFloat32(set.widths, expected));
82
+ if (set.widthPerStrand) {
83
+ json.widthPerStrand = true;
84
+ }
85
+ }
86
+ if (set.uv && set.uv.length > 0) {
87
+ const perPoint = !!set.uvPerPoint;
88
+ const expected = (perPoint ? pointCount : strandCount) * 2;
89
+ json.uv = place(exactFloat32(set.uv, expected));
90
+ if (perPoint) {
91
+ json.uvPerPoint = true;
92
+ }
93
+ }
94
+ if (set.colors && set.colors.length > 0) {
95
+ json.colors = place(exactFloat32(set.colors, pointCount * 3));
96
+ }
97
+ if (set.transparency && set.transparency.length > 0) {
98
+ json.transparency = place(exactFloat32(set.transparency, pointCount));
99
+ }
100
+ return json;
101
+ });
102
+ const manifest = {
103
+ version: ZHAIR_VERSION,
104
+ unitScale: options?.unitScale ?? 1,
105
+ strandSets
106
+ };
107
+ if (options?.sourcePath) {
108
+ manifest.sourcePath = options.sourcePath;
109
+ }
110
+ if (options?.sourceFormat) {
111
+ manifest.sourceFormat = options.sourceFormat;
112
+ }
113
+ const manifestBytes = new TextEncoder().encode(JSON.stringify(manifest));
114
+ // The payload is aligned too, so every array inside it keeps the alignment the
115
+ // layout pass gave it.
116
+ const payloadOffset = ZHAIR_HEADER_SIZE + manifestBytes.byteLength + 3 & -4;
117
+ const buffer = new ArrayBuffer(payloadOffset + payloadLength);
118
+ const view = new DataView(buffer);
119
+ const bytes = new Uint8Array(buffer);
120
+ bytes[0] = 0x5a; // 'Z'
121
+ bytes[1] = 0x48; // 'H'
122
+ bytes[2] = 0x52; // 'R'
123
+ bytes[3] = 0x00;
124
+ view.setUint32(4, ZHAIR_VERSION, true);
125
+ view.setUint32(8, manifestBytes.byteLength, true);
126
+ bytes.set(manifestBytes, ZHAIR_HEADER_SIZE);
127
+ for (const chunk of chunks){
128
+ bytes.set(new Uint8Array(chunk.data.buffer, chunk.data.byteOffset, chunk.data.byteLength), payloadOffset + chunk.ref.offset);
129
+ }
130
+ return buffer;
131
+ }
132
+ /**
133
+ * Opens a `.zhair` file.
134
+ *
135
+ * @remarks
136
+ * Reads the manifest and keeps the buffer; the arrays themselves are not touched
137
+ * until {@link readZHairPositions} and its siblings are called for them.
138
+ *
139
+ * @param buffer - The complete file.
140
+ * @returns The manifest and the payload it indexes into.
141
+ *
142
+ * @public
143
+ */ function parseZHair(buffer) {
144
+ if (!isZHair(buffer)) {
145
+ throw new Error('parseZHair: not a zhair file');
146
+ }
147
+ const view = new DataView(buffer);
148
+ const version = view.getUint32(4, true);
149
+ if (version !== ZHAIR_VERSION) {
150
+ throw new Error(`parseZHair: unsupported zhair version ${version}`);
151
+ }
152
+ const manifestLength = view.getUint32(8, true);
153
+ const manifestEnd = ZHAIR_HEADER_SIZE + manifestLength;
154
+ if (manifestEnd > buffer.byteLength) {
155
+ throw new Error('parseZHair: truncated manifest');
156
+ }
157
+ const manifestText = new TextDecoder().decode(new Uint8Array(buffer, ZHAIR_HEADER_SIZE, manifestLength));
158
+ const content = JSON.parse(manifestText);
159
+ if (!Array.isArray(content.strandSets) || content.strandSets.length === 0) {
160
+ throw new Error('parseZHair: manifest holds no strand sets');
161
+ }
162
+ return {
163
+ content,
164
+ payload: buffer,
165
+ payloadOffset: manifestEnd + 3 & -4
166
+ };
167
+ }
168
+ /**
169
+ * Whether a buffer starts with the `.zhair` magic.
170
+ *
171
+ * @param buffer - Bytes to test.
172
+ * @returns True when the buffer looks like a `.zhair` file.
173
+ *
174
+ * @public
175
+ */ function isZHair(buffer) {
176
+ if (buffer.byteLength < ZHAIR_HEADER_SIZE) {
177
+ return false;
178
+ }
179
+ const magic = new Uint8Array(buffer, 0, 4);
180
+ return magic[0] === 0x5a && magic[1] === 0x48 && magic[2] === 0x52 && magic[3] === 0x00;
181
+ }
182
+ /**
183
+ * Copies a float32 array out of the payload.
184
+ *
185
+ * @param parsed - The opened file.
186
+ * @param ref - Where the array sits, from the manifest.
187
+ * @returns A copy of the array, or null when `ref` is absent.
188
+ *
189
+ * @public
190
+ */ function readZHairFloats(parsed, ref) {
191
+ if (!ref) {
192
+ return null;
193
+ }
194
+ const start = parsed.payloadOffset + ref.offset;
195
+ const end = start + ref.length;
196
+ if (end > parsed.payload.byteLength) {
197
+ throw new Error('readZHairFloats: buffer reference runs past the end of the file');
198
+ }
199
+ return new Float32Array(parsed.payload.slice(start, end));
200
+ }
201
+ /**
202
+ * Copies a uint32 array out of the payload.
203
+ *
204
+ * @param parsed - The opened file.
205
+ * @param ref - Where the array sits, from the manifest.
206
+ * @returns A copy of the array.
207
+ *
208
+ * @public
209
+ */ function readZHairUints(parsed, ref) {
210
+ const start = parsed.payloadOffset + ref.offset;
211
+ const end = start + ref.length;
212
+ if (end > parsed.payload.byteLength) {
213
+ throw new Error('readZHairUints: buffer reference runs past the end of the file');
214
+ }
215
+ return new Uint32Array(parsed.payload.slice(start, end));
216
+ }
217
+ /** Bounds of the first `pointCount` points. @internal */ function computeBounds(positions, pointCount) {
218
+ let minX = Infinity;
219
+ let minY = Infinity;
220
+ let minZ = Infinity;
221
+ let maxX = -Infinity;
222
+ let maxY = -Infinity;
223
+ let maxZ = -Infinity;
224
+ for(let i = 0; i < pointCount; i++){
225
+ const x = positions[i * 3];
226
+ const y = positions[i * 3 + 1];
227
+ const z = positions[i * 3 + 2];
228
+ if (x < minX) {
229
+ minX = x;
230
+ }
231
+ if (y < minY) {
232
+ minY = y;
233
+ }
234
+ if (z < minZ) {
235
+ minZ = z;
236
+ }
237
+ if (x > maxX) {
238
+ maxX = x;
239
+ }
240
+ if (y > maxY) {
241
+ maxY = y;
242
+ }
243
+ if (z > maxZ) {
244
+ maxZ = z;
245
+ }
246
+ }
247
+ return [
248
+ minX,
249
+ minY,
250
+ minZ,
251
+ maxX,
252
+ maxY,
253
+ maxZ
254
+ ];
255
+ }
256
+ /**
257
+ * Narrows a float array to exactly `length` values.
258
+ *
259
+ * @remarks
260
+ * Sources routinely hand over arrays longer than the topology needs - a view
261
+ * into a larger buffer, or trailing values a container padded. Writing the
262
+ * excess would make the file disagree with its own point count.
263
+ * @internal
264
+ */ function exactFloat32(data, length) {
265
+ if (data.length === length && data.byteOffset === 0 && data.buffer.byteLength === data.byteLength) {
266
+ return data;
267
+ }
268
+ if (data.length < length) {
269
+ throw new Error(`encodeZHair: expected ${length} floats but the source holds ${data.length}`);
270
+ }
271
+ return data.slice(0, length);
272
+ }
273
+ /** Converts a count array to uint32, copying only when the type differs. @internal */ function toUint32(counts) {
274
+ if (counts instanceof Uint32Array && counts.byteOffset === 0) {
275
+ return counts;
276
+ }
277
+ const out = new Uint32Array(counts.length);
278
+ for(let i = 0; i < counts.length; i++){
279
+ if (counts[i] < 0) {
280
+ throw new Error(`encodeZHair: strand ${i} has a negative point count`);
281
+ }
282
+ out[i] = counts[i];
283
+ }
284
+ return out;
285
+ }
286
+
287
+ export { ZHAIR_VERSION, encodeZHair, isZHair, parseZHair, readZHairFloats, readZHairUints };
288
+ //# sourceMappingURL=zhair_format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zhair_format.js","sources":["../../../../src/asset/loaders/zhair/zhair_format.ts"],"sourcesContent":["/**\r\n * The `.zhair` container: strand geometry, ready to upload.\r\n *\r\n * @remarks\r\n * Hair reaches the engine as an Alembic curve archive or a `.hair` model, and\r\n * neither is cheap to open: the sample XGen archive is 84 MB of Ogawa container\r\n * holding two million control points, and parsing it means walking an object\r\n * tree and cross-checking property blocks before a single strand appears. Doing\r\n * that every time a scene loads is not viable, so the editor converts once and\r\n * writes this instead.\r\n *\r\n * The layout follows `.zabc`, the geometry cache container already in the tree:\r\n * a magic, a version, a JSON manifest, and then every large array concatenated\r\n * into one payload the manifest points into. The manifest stays readable for\r\n * debugging while the arrays are copied out by offset rather than decoded.\r\n *\r\n * What is stored is {@link StrandCurveSet} - the format-neutral shape both\r\n * importers already converge on - at **full authored density**. Thinning is a\r\n * property of the node that draws the hair, not of the file, so an artist can\r\n * pull strand count down for a distant character and back up for a close-up\r\n * without returning to the source archive.\r\n */\r\nimport type { Nullable } from '@zephyr3d/base';\r\n\r\n/** The only version this module writes. @public */\r\nexport const ZHAIR_VERSION = 1;\r\n/** Bytes before the manifest: magic, version, manifest length. @internal */\r\nconst ZHAIR_HEADER_SIZE = 12;\r\n\r\n/**\r\n * Where one array sits inside the binary payload.\r\n *\r\n * @remarks\r\n * Both fields are byte counts, and `offset` is relative to the start of the\r\n * payload rather than the file, so the manifest does not have to be rewritten\r\n * if the header ever grows.\r\n * @public\r\n */\r\nexport type ZHairBufferRef = {\r\n /** Byte offset from the start of the payload. */\r\n offset: number;\r\n /** Length in bytes. */\r\n length: number;\r\n};\r\n\r\n/**\r\n * One strand set as described by the manifest.\r\n *\r\n * @remarks\r\n * Mirrors {@link StrandCurveSet}, with the per-point colour and transparency\r\n * channels a `.hair` file may carry. Control points are contiguous: strand `i`\r\n * owns the `pointCounts[i]` points following the running sum of the counts\r\n * before it.\r\n * @public\r\n */\r\nexport type ZHairStrandSetJSON = {\r\n /** Name carried over from the source object. */\r\n name: string;\r\n /** Number of strands. */\r\n strandCount: number;\r\n /** Total control points across all strands. */\r\n pointCount: number;\r\n /**\r\n * Axis-aligned bounds of the control points, as `[minX, minY, minZ, maxX,\r\n * maxY, maxZ]`.\r\n *\r\n * @remarks\r\n * In the file's own units, before `unitScale` is applied, so that a reader\r\n * can size a node without touching the payload.\r\n */\r\n bounds: [number, number, number, number, number, number];\r\n /** Control point positions, 3 float32 per point. */\r\n positions: ZHairBufferRef;\r\n /** Control point count per strand, 1 uint32 each. */\r\n pointCounts: ZHairBufferRef;\r\n /** Strand width as a diameter, in the same unit as `positions`. */\r\n widths?: ZHairBufferRef;\r\n /** True when `widths` holds one value per strand rather than per point. */\r\n widthPerStrand?: boolean;\r\n /** Root UV, 2 float32 each. */\r\n uv?: ZHairBufferRef;\r\n /** True when `uv` holds one pair per point rather than per strand. */\r\n uvPerPoint?: boolean;\r\n /** Per-point colour, 3 float32 each. Only `.hair` sources carry this. */\r\n colors?: ZHairBufferRef;\r\n /** Per-point transparency, 1 float32 each. Only `.hair` sources carry this. */\r\n transparency?: ZHairBufferRef;\r\n};\r\n\r\n/**\r\n * The manifest at the head of a `.zhair` file.\r\n * @public\r\n */\r\nexport type ZHairFileJSON = {\r\n /** Format version; {@link ZHAIR_VERSION} is the only one written. */\r\n version: number;\r\n /** Path of the archive this was converted from, for reimport. */\r\n sourcePath?: string;\r\n /** Container the strands came out of. */\r\n sourceFormat?: 'alembic' | 'hair';\r\n /**\r\n * Scale that converts the stored units to metres.\r\n *\r\n * @remarks\r\n * Decided at conversion time, because only the importer knows what the source\r\n * meant: Alembic from Maya is authored in centimetres, while `.hair` records\r\n * no unit at all and gets a scale fitted from its bounds. Positions are stored\r\n * unscaled so the number stays visible and adjustable.\r\n */\r\n unitScale: number;\r\n /** Strand sets, one per source curve object. */\r\n strandSets: ZHairStrandSetJSON[];\r\n};\r\n\r\n/**\r\n * A `.zhair` file, opened.\r\n * @public\r\n */\r\nexport type ParsedZHair = {\r\n /** The decoded manifest. */\r\n content: ZHairFileJSON;\r\n /** The whole file, which the buffer refs index into. */\r\n payload: ArrayBuffer;\r\n /** Byte offset of the payload within {@link ParsedZHair.payload}. */\r\n payloadOffset: number;\r\n};\r\n\r\n/**\r\n * One strand set on its way into a file.\r\n *\r\n * @remarks\r\n * Structurally a `StrandCurveSet` plus the two optional `.hair` channels, but\r\n * declared here rather than imported so this module stays free of a dependency\r\n * on the loaders package.\r\n * @public\r\n */\r\nexport type ZHairStrandSetSource = {\r\n /** Name for the strand set. */\r\n name?: string;\r\n /** Control point positions, 3 floats per point. */\r\n positions: Float32Array;\r\n /** Control point count per strand. */\r\n pointCounts: Int32Array | Uint32Array;\r\n /** Strand width as a diameter, or null when the source has none. */\r\n widths?: Nullable<Float32Array>;\r\n /** True when `widths` holds one value per strand. */\r\n widthPerStrand?: boolean;\r\n /** Root UV, 2 floats each, or null when absent. */\r\n uv?: Nullable<Float32Array>;\r\n /** True when `uv` holds one pair per point. */\r\n uvPerPoint?: boolean;\r\n /** Per-point colour, 3 floats each. */\r\n colors?: Nullable<Float32Array>;\r\n /** Per-point transparency. */\r\n transparency?: Nullable<Float32Array>;\r\n};\r\n\r\n/**\r\n * File-level facts that are not derivable from the strands themselves.\r\n * @public\r\n */\r\nexport type ZHairEncodeOptions = {\r\n /** Scale converting the stored units to metres. Defaults to 1. */\r\n unitScale?: number;\r\n /** Path of the source archive, recorded for reimport. */\r\n sourcePath?: string;\r\n /** Container the strands came from. */\r\n sourceFormat?: 'alembic' | 'hair';\r\n};\r\n\r\n/**\r\n * Packs strand sets into a `.zhair` file.\r\n *\r\n * @param sets - Strand sets to write, one per source curve object.\r\n * @param options - Unit scale and source provenance.\r\n * @returns The complete file.\r\n *\r\n * @public\r\n */\r\nexport function encodeZHair(sets: ZHairStrandSetSource[], options?: ZHairEncodeOptions): ArrayBuffer {\r\n if (sets.length === 0) {\r\n throw new Error('encodeZHair: no strand sets given');\r\n }\r\n // Two passes: the manifest has to carry the offset of every array, so the\r\n // payload is laid out first and the arrays are copied only once the total size\r\n // is known.\r\n const chunks: { data: ArrayBufferView; ref: ZHairBufferRef }[] = [];\r\n let payloadLength = 0;\r\n const place = (data: ArrayBufferView): ZHairBufferRef => {\r\n // Each array starts on a 4-byte boundary so a reader may view the payload\r\n // in place rather than copying, should it ever want to.\r\n payloadLength = (payloadLength + 3) & ~3;\r\n const ref: ZHairBufferRef = { offset: payloadLength, length: data.byteLength };\r\n chunks.push({ data, ref });\r\n payloadLength += data.byteLength;\r\n return ref;\r\n };\r\n\r\n const strandSets: ZHairStrandSetJSON[] = sets.map((set, index) => {\r\n const counts = set.pointCounts;\r\n const strandCount = counts.length;\r\n let pointCount = 0;\r\n for (let i = 0; i < strandCount; i++) {\r\n pointCount += counts[i];\r\n }\r\n if (strandCount === 0 || pointCount === 0) {\r\n throw new Error(`encodeZHair: strand set ${index} is empty`);\r\n }\r\n if (set.positions.length < pointCount * 3) {\r\n throw new Error(\r\n `encodeZHair: strand set ${index} holds ${\r\n set.positions.length / 3\r\n } points but its topology needs ${pointCount}`\r\n );\r\n }\r\n const json: ZHairStrandSetJSON = {\r\n name: set.name || `Strands${index}`,\r\n strandCount,\r\n pointCount,\r\n bounds: computeBounds(set.positions, pointCount),\r\n // Counts are stored as uint32 whatever the source used, so a reader has\r\n // one type to handle.\r\n positions: place(exactFloat32(set.positions, pointCount * 3)),\r\n pointCounts: place(toUint32(counts))\r\n };\r\n if (set.widths && set.widths.length > 0) {\r\n const expected = set.widthPerStrand ? strandCount : pointCount;\r\n json.widths = place(exactFloat32(set.widths, expected));\r\n if (set.widthPerStrand) {\r\n json.widthPerStrand = true;\r\n }\r\n }\r\n if (set.uv && set.uv.length > 0) {\r\n const perPoint = !!set.uvPerPoint;\r\n const expected = (perPoint ? pointCount : strandCount) * 2;\r\n json.uv = place(exactFloat32(set.uv, expected));\r\n if (perPoint) {\r\n json.uvPerPoint = true;\r\n }\r\n }\r\n if (set.colors && set.colors.length > 0) {\r\n json.colors = place(exactFloat32(set.colors, pointCount * 3));\r\n }\r\n if (set.transparency && set.transparency.length > 0) {\r\n json.transparency = place(exactFloat32(set.transparency, pointCount));\r\n }\r\n return json;\r\n });\r\n\r\n const manifest: ZHairFileJSON = {\r\n version: ZHAIR_VERSION,\r\n unitScale: options?.unitScale ?? 1,\r\n strandSets\r\n };\r\n if (options?.sourcePath) {\r\n manifest.sourcePath = options.sourcePath;\r\n }\r\n if (options?.sourceFormat) {\r\n manifest.sourceFormat = options.sourceFormat;\r\n }\r\n const manifestBytes = new TextEncoder().encode(JSON.stringify(manifest));\r\n // The payload is aligned too, so every array inside it keeps the alignment the\r\n // layout pass gave it.\r\n const payloadOffset = (ZHAIR_HEADER_SIZE + manifestBytes.byteLength + 3) & ~3;\r\n const buffer = new ArrayBuffer(payloadOffset + payloadLength);\r\n const view = new DataView(buffer);\r\n const bytes = new Uint8Array(buffer);\r\n bytes[0] = 0x5a; // 'Z'\r\n bytes[1] = 0x48; // 'H'\r\n bytes[2] = 0x52; // 'R'\r\n bytes[3] = 0x00;\r\n view.setUint32(4, ZHAIR_VERSION, true);\r\n view.setUint32(8, manifestBytes.byteLength, true);\r\n bytes.set(manifestBytes, ZHAIR_HEADER_SIZE);\r\n for (const chunk of chunks) {\r\n bytes.set(\r\n new Uint8Array(chunk.data.buffer, chunk.data.byteOffset, chunk.data.byteLength),\r\n payloadOffset + chunk.ref.offset\r\n );\r\n }\r\n return buffer;\r\n}\r\n\r\n/**\r\n * Opens a `.zhair` file.\r\n *\r\n * @remarks\r\n * Reads the manifest and keeps the buffer; the arrays themselves are not touched\r\n * until {@link readZHairPositions} and its siblings are called for them.\r\n *\r\n * @param buffer - The complete file.\r\n * @returns The manifest and the payload it indexes into.\r\n *\r\n * @public\r\n */\r\nexport function parseZHair(buffer: ArrayBuffer): ParsedZHair {\r\n if (!isZHair(buffer)) {\r\n throw new Error('parseZHair: not a zhair file');\r\n }\r\n const view = new DataView(buffer);\r\n const version = view.getUint32(4, true);\r\n if (version !== ZHAIR_VERSION) {\r\n throw new Error(`parseZHair: unsupported zhair version ${version}`);\r\n }\r\n const manifestLength = view.getUint32(8, true);\r\n const manifestEnd = ZHAIR_HEADER_SIZE + manifestLength;\r\n if (manifestEnd > buffer.byteLength) {\r\n throw new Error('parseZHair: truncated manifest');\r\n }\r\n const manifestText = new TextDecoder().decode(new Uint8Array(buffer, ZHAIR_HEADER_SIZE, manifestLength));\r\n const content = JSON.parse(manifestText) as ZHairFileJSON;\r\n if (!Array.isArray(content.strandSets) || content.strandSets.length === 0) {\r\n throw new Error('parseZHair: manifest holds no strand sets');\r\n }\r\n return {\r\n content,\r\n payload: buffer,\r\n payloadOffset: (manifestEnd + 3) & ~3\r\n };\r\n}\r\n\r\n/**\r\n * Whether a buffer starts with the `.zhair` magic.\r\n *\r\n * @param buffer - Bytes to test.\r\n * @returns True when the buffer looks like a `.zhair` file.\r\n *\r\n * @public\r\n */\r\nexport function isZHair(buffer: ArrayBuffer): boolean {\r\n if (buffer.byteLength < ZHAIR_HEADER_SIZE) {\r\n return false;\r\n }\r\n const magic = new Uint8Array(buffer, 0, 4);\r\n return magic[0] === 0x5a && magic[1] === 0x48 && magic[2] === 0x52 && magic[3] === 0x00;\r\n}\r\n\r\n/**\r\n * Copies a float32 array out of the payload.\r\n *\r\n * @param parsed - The opened file.\r\n * @param ref - Where the array sits, from the manifest.\r\n * @returns A copy of the array, or null when `ref` is absent.\r\n *\r\n * @public\r\n */\r\nexport function readZHairFloats(\r\n parsed: ParsedZHair,\r\n ref: Nullable<ZHairBufferRef> | undefined\r\n): Nullable<Float32Array> {\r\n if (!ref) {\r\n return null;\r\n }\r\n const start = parsed.payloadOffset + ref.offset;\r\n const end = start + ref.length;\r\n if (end > parsed.payload.byteLength) {\r\n throw new Error('readZHairFloats: buffer reference runs past the end of the file');\r\n }\r\n return new Float32Array(parsed.payload.slice(start, end));\r\n}\r\n\r\n/**\r\n * Copies a uint32 array out of the payload.\r\n *\r\n * @param parsed - The opened file.\r\n * @param ref - Where the array sits, from the manifest.\r\n * @returns A copy of the array.\r\n *\r\n * @public\r\n */\r\nexport function readZHairUints(parsed: ParsedZHair, ref: ZHairBufferRef): Uint32Array {\r\n const start = parsed.payloadOffset + ref.offset;\r\n const end = start + ref.length;\r\n if (end > parsed.payload.byteLength) {\r\n throw new Error('readZHairUints: buffer reference runs past the end of the file');\r\n }\r\n return new Uint32Array(parsed.payload.slice(start, end));\r\n}\r\n\r\n/** Bounds of the first `pointCount` points. @internal */\r\nfunction computeBounds(\r\n positions: Float32Array,\r\n pointCount: number\r\n): [number, number, number, number, number, number] {\r\n let minX = Infinity;\r\n let minY = Infinity;\r\n let minZ = Infinity;\r\n let maxX = -Infinity;\r\n let maxY = -Infinity;\r\n let maxZ = -Infinity;\r\n for (let i = 0; i < pointCount; i++) {\r\n const x = positions[i * 3];\r\n const y = positions[i * 3 + 1];\r\n const z = positions[i * 3 + 2];\r\n if (x < minX) {\r\n minX = x;\r\n }\r\n if (y < minY) {\r\n minY = y;\r\n }\r\n if (z < minZ) {\r\n minZ = z;\r\n }\r\n if (x > maxX) {\r\n maxX = x;\r\n }\r\n if (y > maxY) {\r\n maxY = y;\r\n }\r\n if (z > maxZ) {\r\n maxZ = z;\r\n }\r\n }\r\n return [minX, minY, minZ, maxX, maxY, maxZ];\r\n}\r\n\r\n/**\r\n * Narrows a float array to exactly `length` values.\r\n *\r\n * @remarks\r\n * Sources routinely hand over arrays longer than the topology needs - a view\r\n * into a larger buffer, or trailing values a container padded. Writing the\r\n * excess would make the file disagree with its own point count.\r\n * @internal\r\n */\r\nfunction exactFloat32(data: Float32Array, length: number): Float32Array {\r\n if (data.length === length && data.byteOffset === 0 && data.buffer.byteLength === data.byteLength) {\r\n return data;\r\n }\r\n if (data.length < length) {\r\n throw new Error(`encodeZHair: expected ${length} floats but the source holds ${data.length}`);\r\n }\r\n return data.slice(0, length);\r\n}\r\n\r\n/** Converts a count array to uint32, copying only when the type differs. @internal */\r\nfunction toUint32(counts: Int32Array | Uint32Array): Uint32Array {\r\n if (counts instanceof Uint32Array && counts.byteOffset === 0) {\r\n return counts;\r\n }\r\n const out = new Uint32Array(counts.length);\r\n for (let i = 0; i < counts.length; i++) {\r\n if (counts[i] < 0) {\r\n throw new Error(`encodeZHair: strand ${i} has a negative point count`);\r\n }\r\n out[i] = counts[i];\r\n }\r\n return out;\r\n}\r\n"],"names":["ZHAIR_VERSION","ZHAIR_HEADER_SIZE","encodeZHair","sets","options","length","Error","chunks","payloadLength","place","data","ref","offset","byteLength","push","strandSets","map","set","index","counts","pointCounts","strandCount","pointCount","i","positions","json","name","bounds","computeBounds","exactFloat32","toUint32","widths","expected","widthPerStrand","uv","perPoint","uvPerPoint","colors","transparency","manifest","version","unitScale","sourcePath","sourceFormat","manifestBytes","TextEncoder","encode","JSON","stringify","payloadOffset","buffer","ArrayBuffer","view","DataView","bytes","Uint8Array","setUint32","chunk","byteOffset","parseZHair","isZHair","getUint32","manifestLength","manifestEnd","manifestText","TextDecoder","decode","content","parse","Array","isArray","payload","magic","readZHairFloats","parsed","start","end","Float32Array","slice","readZHairUints","Uint32Array","minX","Infinity","minY","minZ","maxX","maxY","maxZ","x","y","z","out"],"mappings":"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"}
@@ -0,0 +1,221 @@
1
+ import { readZHairUints, readZHairFloats } from './zhair_format.js';
2
+
3
+ /**
4
+ * Decodes a `.zhair` file into strand sources.
5
+ *
6
+ * @param parsed - The opened file.
7
+ * @param options - Decimation and scaling.
8
+ * @returns One source per strand set, ready for `HairStrandData`.
9
+ *
10
+ * @public
11
+ */ function loadZHairStrandSources(parsed, options) {
12
+ const sets = parsed.content.strandSets;
13
+ const scale = options?.scale ?? parsed.content.unitScale ?? 1;
14
+ const widthScale = options?.widthScale ?? 1;
15
+ const stride = resolveStride(sets, options);
16
+ const sources = [];
17
+ for (const set of sets){
18
+ const source = decodeStrandSet(parsed, set, stride, scale, widthScale);
19
+ if (source) {
20
+ sources.push(source);
21
+ }
22
+ }
23
+ return sources;
24
+ }
25
+ /**
26
+ * Merges strand sets into one source.
27
+ *
28
+ * @remarks
29
+ * A `.zhair` file holds one set per source curve object, and an XGen archive
30
+ * routinely exports several spline descriptions for a single hairstyle. They
31
+ * share a material and differ only in which patch of scalp they grew from, so
32
+ * merging lets the whole head draw in one call.
33
+ *
34
+ * @param sources - Sources to merge.
35
+ * @returns A single source, or null when there is nothing to merge.
36
+ *
37
+ * @public
38
+ */ function mergeHairStrandSources(sources) {
39
+ if (sources.length === 0) {
40
+ return null;
41
+ }
42
+ if (sources.length === 1) {
43
+ return sources[0];
44
+ }
45
+ let strandCount = 0;
46
+ let pointCount = 0;
47
+ for (const source of sources){
48
+ strandCount += source.pointCounts.length;
49
+ for(let i = 0; i < source.pointCounts.length; i++){
50
+ pointCount += source.pointCounts[i];
51
+ }
52
+ }
53
+ const positions = new Float32Array(pointCount * 3);
54
+ const pointCounts = new Uint32Array(strandCount);
55
+ // A channel is only carried into the merge if every set has it: a partially
56
+ // filled width or UV array would read as zero for the sets that lacked it,
57
+ // which is worse than falling back to the defaults uniformly.
58
+ const hasWidths = sources.every((s)=>!!s.widths);
59
+ const hasUV = sources.every((s)=>!!s.uv);
60
+ // Per-point width and UV are the general case; a per-strand array is expanded
61
+ // rather than tracked, so the merged source has one layout to describe.
62
+ const widths = hasWidths ? new Float32Array(pointCount) : null;
63
+ const uv = hasUV ? new Float32Array(pointCount * 2) : null;
64
+ let strandOffset = 0;
65
+ let pointOffset = 0;
66
+ for (const source of sources){
67
+ const counts = source.pointCounts;
68
+ const scale = source.scale ?? 1;
69
+ const sourceWidthScale = source.widthScale ?? 1;
70
+ let sourcePoints = 0;
71
+ for(let i = 0; i < counts.length; i++){
72
+ sourcePoints += counts[i];
73
+ }
74
+ // `HairStrandSource` carries no per-point UV flag; it is inferred from the
75
+ // array length, and the inference has to match the one `HairStrandData`
76
+ // makes or the merged UVs would be read against a different layout.
77
+ const sourceUVPerPoint = !!source.uv && source.uv.length >= sourcePoints * 2;
78
+ let localPoint = 0;
79
+ for(let s = 0; s < counts.length; s++){
80
+ const count = counts[s];
81
+ pointCounts[strandOffset + s] = count;
82
+ for(let i = 0; i < count; i++){
83
+ const src = (localPoint + i) * 3;
84
+ const dst = (pointOffset + i) * 3;
85
+ // Per-source scale is baked in here, because the merged source carries a
86
+ // single scale and the sets may have come from files with different units.
87
+ positions[dst] = source.positions[src] * scale;
88
+ positions[dst + 1] = source.positions[src + 1] * scale;
89
+ positions[dst + 2] = source.positions[src + 2] * scale;
90
+ if (widths) {
91
+ const w = source.widthPerStrand ? source.widths[s] : source.widths[localPoint + i];
92
+ widths[pointOffset + i] = w * scale * sourceWidthScale;
93
+ }
94
+ if (uv) {
95
+ const uvSrc = source.uv;
96
+ // Expanding a per-strand UV to per-point costs a little memory and
97
+ // removes a flag from everything downstream.
98
+ const o = sourceUVPerPoint ? (localPoint + i) * 2 : s * 2;
99
+ uv[(pointOffset + i) * 2] = uvSrc[o];
100
+ uv[(pointOffset + i) * 2 + 1] = uvSrc[o + 1];
101
+ }
102
+ }
103
+ localPoint += count;
104
+ pointOffset += count;
105
+ }
106
+ strandOffset += counts.length;
107
+ }
108
+ return {
109
+ positions,
110
+ pointCounts,
111
+ widths,
112
+ widthPerStrand: false,
113
+ uv,
114
+ // Everything has been converted already, so the merged source must not be
115
+ // scaled a second time.
116
+ scale: 1,
117
+ widthScale: 1,
118
+ defaultWidth: sources[0].defaultWidth
119
+ };
120
+ }
121
+ /**
122
+ * Picks a stride that honours both `strandStride` and `maxStrands`.
123
+ * @internal
124
+ */ function resolveStride(sets, options) {
125
+ let stride = Math.max(1, Math.floor(options?.strandStride ?? 1));
126
+ const maxStrands = options?.maxStrands ?? 0;
127
+ if (maxStrands > 0) {
128
+ let total = 0;
129
+ for (const set of sets){
130
+ total += Math.ceil(set.strandCount / stride);
131
+ }
132
+ if (total > maxStrands) {
133
+ let authored = 0;
134
+ for (const set of sets){
135
+ authored += set.strandCount;
136
+ }
137
+ stride = Math.max(stride, Math.ceil(authored / maxStrands));
138
+ }
139
+ }
140
+ return stride;
141
+ }
142
+ /**
143
+ * Decodes one strand set, keeping one strand in every `stride`.
144
+ * @internal
145
+ */ function decodeStrandSet(parsed, set, stride, scale, widthScale) {
146
+ const allCounts = readZHairUints(parsed, set.pointCounts);
147
+ const allPositions = readZHairFloats(parsed, set.positions);
148
+ const allWidths = readZHairFloats(parsed, set.widths);
149
+ const allUV = readZHairFloats(parsed, set.uv);
150
+ const widthPerStrand = !!set.widthPerStrand;
151
+ const uvPerPoint = !!set.uvPerPoint;
152
+ if (stride <= 1) {
153
+ return {
154
+ positions: allPositions,
155
+ pointCounts: allCounts,
156
+ widths: allWidths,
157
+ widthPerStrand,
158
+ uv: allUV,
159
+ scale,
160
+ widthScale
161
+ };
162
+ }
163
+ // Walking the whole count array is unavoidable even when most strands are
164
+ // dropped, because a strand's first point index is the sum of all counts
165
+ // before it and the file stores no index.
166
+ const keptStrands = [];
167
+ const firstPoints = [];
168
+ let first = 0;
169
+ let keptPoints = 0;
170
+ for(let s = 0; s < allCounts.length; s++){
171
+ if (s % stride === 0) {
172
+ keptStrands.push(s);
173
+ firstPoints.push(first);
174
+ keptPoints += allCounts[s];
175
+ }
176
+ first += allCounts[s];
177
+ }
178
+ if (keptStrands.length === 0 || keptPoints === 0) {
179
+ return null;
180
+ }
181
+ const positions = new Float32Array(keptPoints * 3);
182
+ const pointCounts = new Uint32Array(keptStrands.length);
183
+ const widths = allWidths ? new Float32Array(widthPerStrand ? keptStrands.length : keptPoints) : null;
184
+ const uv = allUV ? new Float32Array((uvPerPoint ? keptPoints : keptStrands.length) * 2) : null;
185
+ let dstPoint = 0;
186
+ for(let k = 0; k < keptStrands.length; k++){
187
+ const s = keptStrands[k];
188
+ const count = allCounts[s];
189
+ const srcFirst = firstPoints[k];
190
+ pointCounts[k] = count;
191
+ positions.set(allPositions.subarray(srcFirst * 3, (srcFirst + count) * 3), dstPoint * 3);
192
+ if (widths) {
193
+ if (widthPerStrand) {
194
+ widths[k] = allWidths[s];
195
+ } else {
196
+ widths.set(allWidths.subarray(srcFirst, srcFirst + count), dstPoint);
197
+ }
198
+ }
199
+ if (uv) {
200
+ if (uvPerPoint) {
201
+ uv.set(allUV.subarray(srcFirst * 2, (srcFirst + count) * 2), dstPoint * 2);
202
+ } else {
203
+ uv[k * 2] = allUV[s * 2];
204
+ uv[k * 2 + 1] = allUV[s * 2 + 1];
205
+ }
206
+ }
207
+ dstPoint += count;
208
+ }
209
+ return {
210
+ positions,
211
+ pointCounts,
212
+ widths,
213
+ widthPerStrand,
214
+ uv,
215
+ scale,
216
+ widthScale
217
+ };
218
+ }
219
+
220
+ export { loadZHairStrandSources, mergeHairStrandSources };
221
+ //# sourceMappingURL=zhair_loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zhair_loader.js","sources":["../../../../src/asset/loaders/zhair/zhair_loader.ts"],"sourcesContent":["/**\r\n * Turns a parsed `.zhair` file into strand sources ready for the GPU.\r\n *\r\n * @remarks\r\n * The file stores hair at full authored density, which for a production groom is\r\n * around 69,000 strands of 30 control points. Drawing all of them is not always\r\n * wanted, so decimation happens here rather than at conversion time: the node\r\n * owning the hair exposes the knobs, and turning one re-runs this against the\r\n * same file. Going back to the source archive is never required.\r\n */\r\nimport type { Nullable } from '@zephyr3d/base';\r\nimport type { HairStrandSource } from '../../../material/hairstrand_data';\r\nimport type { ParsedZHair, ZHairStrandSetJSON } from './zhair_format';\r\nimport { readZHairFloats, readZHairUints } from './zhair_format';\r\n\r\n/**\r\n * How much of a `.zhair` file to keep.\r\n *\r\n * @public\r\n */\r\nexport type ZHairLoadOptions = {\r\n /**\r\n * Keep one strand in every `strandStride`.\r\n *\r\n * @remarks\r\n * Decimating by a stride rather than by taking a leading slice keeps the kept\r\n * strands spread over the whole scalp; a slice would leave a bald patch,\r\n * because strands are stored in the order the groom generated them.\r\n * Defaults to 1, which keeps everything.\r\n */\r\n strandStride?: number;\r\n /**\r\n * Upper bound on the number of strands kept, across all sets.\r\n *\r\n * @remarks\r\n * Applied on top of `strandStride` by widening the stride further, so the\r\n * result stays evenly spread instead of stopping partway through the groom.\r\n */\r\n maxStrands?: number;\r\n /**\r\n * Scale applied to positions and widths alike.\r\n *\r\n * @remarks\r\n * Defaults to the file's own `unitScale`. Width is a length in the same unit\r\n * as position, so the two must convert together - leaving width unscaled makes\r\n * strands a hundred times too thick and they read as ribbons rather than hair.\r\n */\r\n scale?: number;\r\n /** Extra multiplier on width only, for art direction. Defaults to 1. */\r\n widthScale?: number;\r\n};\r\n\r\n/**\r\n * Decodes a `.zhair` file into strand sources.\r\n *\r\n * @param parsed - The opened file.\r\n * @param options - Decimation and scaling.\r\n * @returns One source per strand set, ready for `HairStrandData`.\r\n *\r\n * @public\r\n */\r\nexport function loadZHairStrandSources(parsed: ParsedZHair, options?: ZHairLoadOptions): HairStrandSource[] {\r\n const sets = parsed.content.strandSets;\r\n const scale = options?.scale ?? parsed.content.unitScale ?? 1;\r\n const widthScale = options?.widthScale ?? 1;\r\n const stride = resolveStride(sets, options);\r\n const sources: HairStrandSource[] = [];\r\n for (const set of sets) {\r\n const source = decodeStrandSet(parsed, set, stride, scale, widthScale);\r\n if (source) {\r\n sources.push(source);\r\n }\r\n }\r\n return sources;\r\n}\r\n\r\n/**\r\n * Merges strand sets into one source.\r\n *\r\n * @remarks\r\n * A `.zhair` file holds one set per source curve object, and an XGen archive\r\n * routinely exports several spline descriptions for a single hairstyle. They\r\n * share a material and differ only in which patch of scalp they grew from, so\r\n * merging lets the whole head draw in one call.\r\n *\r\n * @param sources - Sources to merge.\r\n * @returns A single source, or null when there is nothing to merge.\r\n *\r\n * @public\r\n */\r\nexport function mergeHairStrandSources(sources: HairStrandSource[]): Nullable<HairStrandSource> {\r\n if (sources.length === 0) {\r\n return null;\r\n }\r\n if (sources.length === 1) {\r\n return sources[0];\r\n }\r\n let strandCount = 0;\r\n let pointCount = 0;\r\n for (const source of sources) {\r\n strandCount += source.pointCounts.length;\r\n for (let i = 0; i < source.pointCounts.length; i++) {\r\n pointCount += source.pointCounts[i];\r\n }\r\n }\r\n const positions = new Float32Array(pointCount * 3);\r\n const pointCounts = new Uint32Array(strandCount);\r\n // A channel is only carried into the merge if every set has it: a partially\r\n // filled width or UV array would read as zero for the sets that lacked it,\r\n // which is worse than falling back to the defaults uniformly.\r\n const hasWidths = sources.every((s) => !!s.widths);\r\n const hasUV = sources.every((s) => !!s.uv);\r\n // Per-point width and UV are the general case; a per-strand array is expanded\r\n // rather than tracked, so the merged source has one layout to describe.\r\n const widths = hasWidths ? new Float32Array(pointCount) : null;\r\n const uv = hasUV ? new Float32Array(pointCount * 2) : null;\r\n let strandOffset = 0;\r\n let pointOffset = 0;\r\n for (const source of sources) {\r\n const counts = source.pointCounts;\r\n const scale = source.scale ?? 1;\r\n const sourceWidthScale = source.widthScale ?? 1;\r\n let sourcePoints = 0;\r\n for (let i = 0; i < counts.length; i++) {\r\n sourcePoints += counts[i];\r\n }\r\n // `HairStrandSource` carries no per-point UV flag; it is inferred from the\r\n // array length, and the inference has to match the one `HairStrandData`\r\n // makes or the merged UVs would be read against a different layout.\r\n const sourceUVPerPoint = !!source.uv && source.uv.length >= sourcePoints * 2;\r\n let localPoint = 0;\r\n for (let s = 0; s < counts.length; s++) {\r\n const count = counts[s];\r\n pointCounts[strandOffset + s] = count;\r\n for (let i = 0; i < count; i++) {\r\n const src = (localPoint + i) * 3;\r\n const dst = (pointOffset + i) * 3;\r\n // Per-source scale is baked in here, because the merged source carries a\r\n // single scale and the sets may have come from files with different units.\r\n positions[dst] = source.positions[src] * scale;\r\n positions[dst + 1] = source.positions[src + 1] * scale;\r\n positions[dst + 2] = source.positions[src + 2] * scale;\r\n if (widths) {\r\n const w = source.widthPerStrand ? source.widths![s] : source.widths![localPoint + i];\r\n widths[pointOffset + i] = w * scale * sourceWidthScale;\r\n }\r\n if (uv) {\r\n const uvSrc = source.uv!;\r\n // Expanding a per-strand UV to per-point costs a little memory and\r\n // removes a flag from everything downstream.\r\n const o = sourceUVPerPoint ? (localPoint + i) * 2 : s * 2;\r\n uv[(pointOffset + i) * 2] = uvSrc[o];\r\n uv[(pointOffset + i) * 2 + 1] = uvSrc[o + 1];\r\n }\r\n }\r\n localPoint += count;\r\n pointOffset += count;\r\n }\r\n strandOffset += counts.length;\r\n }\r\n return {\r\n positions,\r\n pointCounts,\r\n widths,\r\n widthPerStrand: false,\r\n uv,\r\n // Everything has been converted already, so the merged source must not be\r\n // scaled a second time.\r\n scale: 1,\r\n widthScale: 1,\r\n defaultWidth: sources[0].defaultWidth\r\n };\r\n}\r\n\r\n/**\r\n * Picks a stride that honours both `strandStride` and `maxStrands`.\r\n * @internal\r\n */\r\nfunction resolveStride(sets: ZHairStrandSetJSON[], options?: ZHairLoadOptions): number {\r\n let stride = Math.max(1, Math.floor(options?.strandStride ?? 1));\r\n const maxStrands = options?.maxStrands ?? 0;\r\n if (maxStrands > 0) {\r\n let total = 0;\r\n for (const set of sets) {\r\n total += Math.ceil(set.strandCount / stride);\r\n }\r\n if (total > maxStrands) {\r\n let authored = 0;\r\n for (const set of sets) {\r\n authored += set.strandCount;\r\n }\r\n stride = Math.max(stride, Math.ceil(authored / maxStrands));\r\n }\r\n }\r\n return stride;\r\n}\r\n\r\n/**\r\n * Decodes one strand set, keeping one strand in every `stride`.\r\n * @internal\r\n */\r\nfunction decodeStrandSet(\r\n parsed: ParsedZHair,\r\n set: ZHairStrandSetJSON,\r\n stride: number,\r\n scale: number,\r\n widthScale: number\r\n): Nullable<HairStrandSource> {\r\n const allCounts = readZHairUints(parsed, set.pointCounts);\r\n const allPositions = readZHairFloats(parsed, set.positions)!;\r\n const allWidths = readZHairFloats(parsed, set.widths);\r\n const allUV = readZHairFloats(parsed, set.uv);\r\n const widthPerStrand = !!set.widthPerStrand;\r\n const uvPerPoint = !!set.uvPerPoint;\r\n\r\n if (stride <= 1) {\r\n return {\r\n positions: allPositions,\r\n pointCounts: allCounts,\r\n widths: allWidths,\r\n widthPerStrand,\r\n uv: allUV,\r\n scale,\r\n widthScale\r\n };\r\n }\r\n\r\n // Walking the whole count array is unavoidable even when most strands are\r\n // dropped, because a strand's first point index is the sum of all counts\r\n // before it and the file stores no index.\r\n const keptStrands: number[] = [];\r\n const firstPoints: number[] = [];\r\n let first = 0;\r\n let keptPoints = 0;\r\n for (let s = 0; s < allCounts.length; s++) {\r\n if (s % stride === 0) {\r\n keptStrands.push(s);\r\n firstPoints.push(first);\r\n keptPoints += allCounts[s];\r\n }\r\n first += allCounts[s];\r\n }\r\n if (keptStrands.length === 0 || keptPoints === 0) {\r\n return null;\r\n }\r\n\r\n const positions = new Float32Array(keptPoints * 3);\r\n const pointCounts = new Uint32Array(keptStrands.length);\r\n const widths = allWidths ? new Float32Array(widthPerStrand ? keptStrands.length : keptPoints) : null;\r\n const uv = allUV ? new Float32Array((uvPerPoint ? keptPoints : keptStrands.length) * 2) : null;\r\n let dstPoint = 0;\r\n for (let k = 0; k < keptStrands.length; k++) {\r\n const s = keptStrands[k];\r\n const count = allCounts[s];\r\n const srcFirst = firstPoints[k];\r\n pointCounts[k] = count;\r\n positions.set(allPositions.subarray(srcFirst * 3, (srcFirst + count) * 3), dstPoint * 3);\r\n if (widths) {\r\n if (widthPerStrand) {\r\n widths[k] = allWidths![s];\r\n } else {\r\n widths.set(allWidths!.subarray(srcFirst, srcFirst + count), dstPoint);\r\n }\r\n }\r\n if (uv) {\r\n if (uvPerPoint) {\r\n uv.set(allUV!.subarray(srcFirst * 2, (srcFirst + count) * 2), dstPoint * 2);\r\n } else {\r\n uv[k * 2] = allUV![s * 2];\r\n uv[k * 2 + 1] = allUV![s * 2 + 1];\r\n }\r\n }\r\n dstPoint += count;\r\n }\r\n return {\r\n positions,\r\n pointCounts,\r\n widths,\r\n widthPerStrand,\r\n uv,\r\n scale,\r\n widthScale\r\n };\r\n}\r\n"],"names":["loadZHairStrandSources","parsed","options","sets","content","strandSets","scale","unitScale","widthScale","stride","resolveStride","sources","set","source","decodeStrandSet","push","mergeHairStrandSources","length","strandCount","pointCount","pointCounts","i","positions","Float32Array","Uint32Array","hasWidths","every","s","widths","hasUV","uv","strandOffset","pointOffset","counts","sourceWidthScale","sourcePoints","sourceUVPerPoint","localPoint","count","src","dst","w","widthPerStrand","uvSrc","o","defaultWidth","Math","max","floor","strandStride","maxStrands","total","ceil","authored","allCounts","readZHairUints","allPositions","readZHairFloats","allWidths","allUV","uvPerPoint","keptStrands","firstPoints","first","keptPoints","dstPoint","k","srcFirst","subarray"],"mappings":"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"}