@drawcall/lod 0.0.0-stage → 0.1.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 (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -2
  3. package/dist/baked/codec.d.ts +10 -0
  4. package/dist/baked/codec.js +291 -0
  5. package/dist/baked/codec.js.map +1 -0
  6. package/dist/baked/index.d.ts +51 -0
  7. package/dist/baked/index.js +183 -0
  8. package/dist/baked/index.js.map +1 -0
  9. package/dist/baked/interior/faces.d.ts +26 -0
  10. package/dist/baked/interior/faces.js +105 -0
  11. package/dist/baked/interior/faces.js.map +1 -0
  12. package/dist/baked/interior/material.d.ts +31 -0
  13. package/dist/baked/interior/material.js +157 -0
  14. package/dist/baked/interior/material.js.map +1 -0
  15. package/dist/baked/interior/rooms.d.ts +22 -0
  16. package/dist/baked/interior/rooms.js +78 -0
  17. package/dist/baked/interior/rooms.js.map +1 -0
  18. package/dist/baked/low.d.ts +20 -0
  19. package/dist/baked/low.js +75 -0
  20. package/dist/baked/low.js.map +1 -0
  21. package/dist/baked/raster.d.ts +20 -0
  22. package/dist/baked/raster.js +71 -0
  23. package/dist/baked/raster.js.map +1 -0
  24. package/dist/baked/transfer.d.ts +36 -0
  25. package/dist/baked/transfer.js +243 -0
  26. package/dist/baked/transfer.js.map +1 -0
  27. package/dist/baked/unwrap.d.ts +9 -0
  28. package/dist/baked/unwrap.js +167 -0
  29. package/dist/baked/unwrap.js.map +1 -0
  30. package/dist/baked/xatlas.d.ts +2 -0
  31. package/dist/baked/xatlas.js +31 -0
  32. package/dist/baked/xatlas.js.map +1 -0
  33. package/dist/bounds.d.ts +33 -0
  34. package/dist/bounds.js +127 -0
  35. package/dist/bounds.js.map +1 -0
  36. package/dist/dispose.d.ts +5 -0
  37. package/dist/dispose.js +28 -0
  38. package/dist/dispose.js.map +1 -0
  39. package/dist/fade.d.ts +44 -0
  40. package/dist/fade.js +78 -0
  41. package/dist/fade.js.map +1 -0
  42. package/dist/impostor/bake/atlas.d.ts +51 -0
  43. package/dist/impostor/bake/atlas.js +190 -0
  44. package/dist/impostor/bake/atlas.js.map +1 -0
  45. package/dist/impostor/bake/index.d.ts +50 -0
  46. package/dist/impostor/bake/index.js +221 -0
  47. package/dist/impostor/bake/index.js.map +1 -0
  48. package/dist/impostor/bake/materials.d.ts +29 -0
  49. package/dist/impostor/bake/materials.js +156 -0
  50. package/dist/impostor/bake/materials.js.map +1 -0
  51. package/dist/impostor/bake/source.d.ts +3 -0
  52. package/dist/impostor/bake/source.js +23 -0
  53. package/dist/impostor/bake/source.js.map +1 -0
  54. package/dist/impostor/cache.d.ts +15 -0
  55. package/dist/impostor/cache.js +44 -0
  56. package/dist/impostor/cache.js.map +1 -0
  57. package/dist/impostor/codec.d.ts +14 -0
  58. package/dist/impostor/codec.js +137 -0
  59. package/dist/impostor/codec.js.map +1 -0
  60. package/dist/impostor/impostor.d.ts +67 -0
  61. package/dist/impostor/impostor.js +79 -0
  62. package/dist/impostor/impostor.js.map +1 -0
  63. package/dist/impostor/mesh/card.d.ts +38 -0
  64. package/dist/impostor/mesh/card.js +79 -0
  65. package/dist/impostor/mesh/card.js.map +1 -0
  66. package/dist/impostor/mesh/index.d.ts +66 -0
  67. package/dist/impostor/mesh/index.js +174 -0
  68. package/dist/impostor/mesh/index.js.map +1 -0
  69. package/dist/impostor/mesh/material.d.ts +63 -0
  70. package/dist/impostor/mesh/material.js +199 -0
  71. package/dist/impostor/mesh/material.js.map +1 -0
  72. package/dist/impostor/mesh/surface.d.ts +63 -0
  73. package/dist/impostor/mesh/surface.js +162 -0
  74. package/dist/impostor/mesh/surface.js.map +1 -0
  75. package/dist/impostor/mesh/textures.d.ts +4 -0
  76. package/dist/impostor/mesh/textures.js +37 -0
  77. package/dist/impostor/mesh/textures.js.map +1 -0
  78. package/dist/impostor/octahedral.d.ts +29 -0
  79. package/dist/impostor/octahedral.js +83 -0
  80. package/dist/impostor/octahedral.js.map +1 -0
  81. package/dist/index.d.ts +15 -0
  82. package/dist/index.js +16 -0
  83. package/dist/index.js.map +1 -0
  84. package/dist/instanced/args.d.ts +20 -0
  85. package/dist/instanced/args.js +77 -0
  86. package/dist/instanced/args.js.map +1 -0
  87. package/dist/instanced/bounds.d.ts +50 -0
  88. package/dist/instanced/bounds.js +271 -0
  89. package/dist/instanced/bounds.js.map +1 -0
  90. package/dist/instanced/dirty.d.ts +12 -0
  91. package/dist/instanced/dirty.js +61 -0
  92. package/dist/instanced/dirty.js.map +1 -0
  93. package/dist/instanced/index.d.ts +116 -0
  94. package/dist/instanced/index.js +573 -0
  95. package/dist/instanced/index.js.map +1 -0
  96. package/dist/instanced/meshes.d.ts +14 -0
  97. package/dist/instanced/meshes.js +54 -0
  98. package/dist/instanced/meshes.js.map +1 -0
  99. package/dist/instanced/select.d.ts +84 -0
  100. package/dist/instanced/select.js +264 -0
  101. package/dist/instanced/select.js.map +1 -0
  102. package/dist/lazy.d.ts +64 -0
  103. package/dist/lazy.js +187 -0
  104. package/dist/lazy.js.map +1 -0
  105. package/dist/levels.d.ts +72 -0
  106. package/dist/levels.js +146 -0
  107. package/dist/levels.js.map +1 -0
  108. package/dist/loader/cache.d.ts +15 -0
  109. package/dist/loader/cache.js +37 -0
  110. package/dist/loader/cache.js.map +1 -0
  111. package/dist/loader/dispose.d.ts +3 -0
  112. package/dist/loader/dispose.js +25 -0
  113. package/dist/loader/dispose.js.map +1 -0
  114. package/dist/loader.d.ts +99 -0
  115. package/dist/loader.js +234 -0
  116. package/dist/loader.js.map +1 -0
  117. package/dist/lod.d.ts +78 -0
  118. package/dist/lod.js +211 -0
  119. package/dist/lod.js.map +1 -0
  120. package/dist/material.d.ts +12 -0
  121. package/dist/material.js +52 -0
  122. package/dist/material.js.map +1 -0
  123. package/dist/pause.d.ts +6 -0
  124. package/dist/pause.js +20 -0
  125. package/dist/pause.js.map +1 -0
  126. package/dist/renderer.d.ts +21 -0
  127. package/dist/renderer.js +83 -0
  128. package/dist/renderer.js.map +1 -0
  129. package/dist/simplify.d.ts +24 -0
  130. package/dist/simplify.js +148 -0
  131. package/dist/simplify.js.map +1 -0
  132. package/dist/source/error.d.ts +14 -0
  133. package/dist/source/error.js +94 -0
  134. package/dist/source/error.js.map +1 -0
  135. package/dist/source/frame.d.ts +11 -0
  136. package/dist/source/frame.js +25 -0
  137. package/dist/source/frame.js.map +1 -0
  138. package/dist/source/shading.d.ts +96 -0
  139. package/dist/source/shading.js +224 -0
  140. package/dist/source/shading.js.map +1 -0
  141. package/dist/source/source.d.ts +23 -0
  142. package/dist/source/source.js +50 -0
  143. package/dist/source/source.js.map +1 -0
  144. package/dist/source/surface.d.ts +31 -0
  145. package/dist/source/surface.js +110 -0
  146. package/dist/source/surface.js.map +1 -0
  147. package/dist/source/texels.d.ts +45 -0
  148. package/dist/source/texels.js +191 -0
  149. package/dist/source/texels.js.map +1 -0
  150. package/dist/source/trace.d.ts +42 -0
  151. package/dist/source/trace.js +153 -0
  152. package/dist/source/trace.js.map +1 -0
  153. package/dist/three.d.ts +55 -0
  154. package/dist/three.js +103 -0
  155. package/dist/three.js.map +1 -0
  156. package/dist/tsl.d.ts +45 -0
  157. package/dist/tsl.js +99 -0
  158. package/dist/tsl.js.map +1 -0
  159. package/dist/view.d.ts +30 -0
  160. package/dist/view.js +75 -0
  161. package/dist/view.js.map +1 -0
  162. package/dist/wind/bounds.d.ts +3 -0
  163. package/dist/wind/bounds.js +47 -0
  164. package/dist/wind/bounds.js.map +1 -0
  165. package/dist/wind/hooks.d.ts +5 -0
  166. package/dist/wind/hooks.js +3 -0
  167. package/dist/wind/hooks.js.map +1 -0
  168. package/dist/wind/index.d.ts +57 -0
  169. package/dist/wind/index.js +196 -0
  170. package/dist/wind/index.js.map +1 -0
  171. package/dist/wind/sway.d.ts +29 -0
  172. package/dist/wind/sway.js +71 -0
  173. package/dist/wind/sway.js.map +1 -0
  174. package/package.json +69 -3
  175. package/skills/lod/SKILL.md +137 -0
@@ -0,0 +1,156 @@
1
+ import { DoubleSide, MeshStandardNodeMaterial, NoBlending } from "three/webgpu";
2
+ import { ambientOcclusion, cameraViewMatrix, Fn, clamp, diffuseColor, emissive, faceDirection, float, materialMetalness, materialOpacity, materialRoughness, max, mrt, normalView, normalize, positionView, specularColor, specularF90, step, vec4, } from "three/tsl";
3
+ import { isMesh, isNodeMaterial, isPhysical, isStandard, isStandardNode, toNodeMaterial } from "../../three.js";
4
+ import { affine, biasedTextures, isNodeOf } from "../../tsl.js";
5
+ import { originalOf } from "../../material.js";
6
+ /**
7
+ * The outputs a bake material writes, by name: one per atlas map, then the second way to
8
+ * store the normals (see Atlas) and the specular. A render target takes the ones it has
9
+ * textures of that name for, so every material writes them all and the bake decides,
10
+ * once it knows every material, which it keeps.
11
+ */
12
+ export const OUTPUTS = ["base", "normalDepth", "orm", "emissive", "turned", "specular"];
13
+ /**
14
+ * Replace the materials of every visible mesh under `root` with bake materials, which
15
+ * write the surface values unlit into the atlas maps. Returns the bake materials (to
16
+ * dispose), whether any of them emits light and whether any changes three's default specular.
17
+ */
18
+ export function swapMaterials(root, settings) {
19
+ const baked = new Map();
20
+ const plain = new Set();
21
+ root.traverseVisible((node) => {
22
+ // Lines, points and sprites would write their colour into the data targets. Layers
23
+ // rather than `visible`, which would also hide any mesh children.
24
+ if ("isLine" in node || "isPoints" in node || "isSprite" in node)
25
+ return node.layers.disableAll();
26
+ if (!isMesh(node))
27
+ return;
28
+ const swap = (material) => {
29
+ // A deferred bake may see a source already hooked by Lod or Wind. Bake its
30
+ // authored surface at rest, without the runtime fade/tint/sway wrappers.
31
+ const source = originalOf(material);
32
+ let entry = baked.get(source);
33
+ if (!entry) {
34
+ entry = bakeMaterial(source, settings);
35
+ baked.set(source, entry);
36
+ if (!isStandard(source) && !isNodeMaterial(source) && !MAPPED.has(source.type))
37
+ plain.add(source.type);
38
+ }
39
+ return entry.material;
40
+ };
41
+ node.material = Array.isArray(node.material) ? node.material.map(swap) : swap(node.material);
42
+ });
43
+ if (plain.size > 0)
44
+ console.warn(`bakeImpostor: ${[...plain].join(", ")} bake as a plain rough MeshStandardMaterial with their color/map only`);
45
+ const entries = [...baked.values()];
46
+ return { materials: entries.map((e) => e.material), emits: entries.some((e) => e.emits), specular: entries.some((e) => e.specular) };
47
+ }
48
+ const STANDARD_KEYS = [
49
+ "color",
50
+ "map",
51
+ "alphaMap",
52
+ "alphaTest",
53
+ "alphaHash",
54
+ "alphaToCoverage",
55
+ "side",
56
+ "vertexColors",
57
+ "opacity",
58
+ "transparent",
59
+ "normalMap",
60
+ "normalMapType",
61
+ "normalScale",
62
+ "bumpMap",
63
+ "bumpScale",
64
+ "aoMap",
65
+ "aoMapIntensity",
66
+ "emissive",
67
+ "emissiveMap",
68
+ "emissiveIntensity",
69
+ "flatShading",
70
+ "displacementMap",
71
+ "displacementScale",
72
+ "displacementBias",
73
+ "wireframe",
74
+ ];
75
+ /** Built-in materials whose look the standard keys carry over; others only keep their color/map. */
76
+ const MAPPED = new Set(["MeshBasicMaterial", "MeshLambertMaterial", "MeshPhongMaterial", "MeshToonMaterial", "MeshMatcapMaterial"]);
77
+ /** A node material that renders like `source`: a copy of a node or standard one, else the standard keys carried over. */
78
+ function toStandard(source) {
79
+ if (isNodeMaterial(source) || isStandard(source))
80
+ return toNodeMaterial(source);
81
+ // Unlit and Lambert/Phong materials bake as a fully rough dielectric. Their settings
82
+ // are read by name: they are whatever subclass the app used.
83
+ const values = Object.fromEntries(STANDARD_KEYS.flatMap((k) => (Reflect.get(source, k) === undefined ? [] : [[k, Reflect.get(source, k)]])));
84
+ return new MeshStandardNodeMaterial({ roughness: 1, metalness: 0, ...values });
85
+ }
86
+ /** A material's float slot (or its default), as the float it builds to. */
87
+ const scalar = (node, builder) => {
88
+ if (!isNodeOf(node, "float", builder))
89
+ throw new Error(`bakeImpostor: a material's float slot builds to ${node.getNodeType(builder)}`);
90
+ return node;
91
+ };
92
+ function bakeMaterial(source, settings) {
93
+ const material = toStandard(source);
94
+ // A material that draws no colour has no surface to bake.
95
+ material.visible = source.visible && source.colorWrite;
96
+ const emits = material.visible && emitsLight(material);
97
+ const specular = material.visible && changesSpecular(source);
98
+ // Coverage is the material's alpha where it cuts the surface out: alpha-to-coverage (no
99
+ // multisampling here, so it would cover the card), or a blended material's texture
100
+ // alpha. A blended material's uniform opacity (glass) is no hole: it folds into the
101
+ // colour, as if the pane were dark and opaque, which keeps its reflection.
102
+ const blended = material.transparent;
103
+ const coverage = blended ? diffuseColor.a.div(max(materialOpacity, 1e-4)) : material.alphaToCoverage ? diffuseColor.a : float(1);
104
+ const opacity = blended ? materialOpacity : float(1);
105
+ // Blending would mix the outputs with the cleared background; coverage carries alpha
106
+ // instead. Without normal blending three also keeps the alpha (not opaque).
107
+ material.transparent = false;
108
+ material.blending = NoBlending;
109
+ material.depthWrite = true;
110
+ material.contextNode = biasedTextures(settings.textureBias);
111
+ // Two ways to store the normals three shades with, each a normal and the share of the
112
+ // surface that shades with it rather than its opposite; the bake keeps the one whose
113
+ // normals agree more once averaged (see Atlas):
114
+ // - the surface's own normal and the share whose front faced the camera (on two-sided
115
+ // surfaces three turns the normal toward the camera; this undoes it). Painted canopy
116
+ // normals of cut-out foliage agree this way, which three turns inward on every leaf
117
+ // seen from behind.
118
+ // - the shading normal turned toward the camera and the share that faced it already.
119
+ // Real leaf normals agree this way, which three turns toward the camera on every leaf.
120
+ // The camera is orthographic, looking along -z; the object sits at the origin, so world
121
+ // space is its own.
122
+ const twoSided = material.side === DoubleSide && !(isStandardNode(material) && material.flatShading);
123
+ const own = twoSided ? normalView.mul(faceDirection) : normalView;
124
+ const front = twoSided ? faceDirection.mul(0.5).add(0.5) : float(1);
125
+ const facing = step(0, normalView.z);
126
+ const toObject = (n) => normalize(affine(cameraViewMatrix).solve(n)).mul(0.5).add(0.5);
127
+ const depth = clamp(positionView.z.add(settings.distance).div(settings.radius).mul(0.5).add(0.5), 0, 1);
128
+ // Unlit values: the material's own roughness and metalness, not three's lighting ones.
129
+ const roughness = (isStandardNode(material) && material.roughnessNode) || materialRoughness;
130
+ const metalness = (isStandardNode(material) && material.metalnessNode) || materialMetalness;
131
+ const outputs = {
132
+ base: vec4(clamp(diffuseColor.rgb.mul(opacity), 0, 1), clamp(coverage, 0, 1)),
133
+ normalDepth: vec4(toObject(own), depth),
134
+ orm: Fn((builder) => vec4(ambientOcclusion, scalar(roughness, builder), scalar(metalness, builder), front))(),
135
+ emissive: vec4(clamp(emissive, 0, 1), 1),
136
+ turned: vec4(toObject(normalView.mul(facing.mul(2).sub(1))), facing),
137
+ specular: vec4(specularColor, specularF90),
138
+ };
139
+ material.mrtNode = mrt(outputs);
140
+ return { material, emits, specular };
141
+ }
142
+ /** Whether `material` emits light: an emissive node, colour × intensity, or map. */
143
+ function emitsLight(material) {
144
+ if (!isStandardNode(material))
145
+ return false;
146
+ const { emissive: e, emissiveIntensity, emissiveMap, emissiveNode } = material;
147
+ return emissiveNode !== null || (e.r + e.g + e.b) * emissiveIntensity > 0 || emissiveMap !== null;
148
+ }
149
+ /** Whether `material` lights with other than three's dielectric F0 0.04 and F90 1 (KHR_materials_specular). */
150
+ function changesSpecular(material) {
151
+ if (!isPhysical(material))
152
+ return false;
153
+ const { ior, specularIntensity, specularColor: c, specularIntensityMap, specularColorMap } = material;
154
+ return ior !== 1.5 || specularIntensity !== 1 || c.r !== 1 || c.g !== 1 || c.b !== 1 || specularIntensityMap !== null || specularColorMap !== null;
155
+ }
156
+ //# sourceMappingURL=materials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"materials.js","sourceRoot":"","sources":["../../../src/impostor/bake/materials.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Object3D } from "three/webgpu";
2
+ /** Snapshot before validation/bounds, without copying loader-added rooms or their resources. */
3
+ export declare function sourceForBake(object: Object3D, excluded?: readonly Object3D[]): Object3D;
@@ -0,0 +1,23 @@
1
+ import { clone } from "three/addons/utils/SkeletonUtils.js";
2
+ import { forEachMesh } from "../../bounds.js";
3
+ import { isImpostorMesh } from "../../three.js";
4
+ /** Snapshot before validation/bounds, without copying loader-added rooms or their resources. */
5
+ export function sourceForBake(object, excluded = []) {
6
+ const children = object.children;
7
+ try {
8
+ // SkeletonUtils has no exclusion hook. This synchronous view emits no events and
9
+ // is restored before any await, even if validation or a custom clone throws.
10
+ // Cloning then removing rooms would allocate excluded nested LOD resources.
11
+ if (excluded.length > 0)
12
+ object.children = children.filter((child) => !excluded.includes(child));
13
+ forEachMesh(object, (mesh) => {
14
+ if (isImpostorMesh(mesh))
15
+ throw new Error(`bakeImpostor: cannot bake an ImpostorMesh ("${mesh.name}"); bake from its source instead`);
16
+ });
17
+ return clone(object);
18
+ }
19
+ finally {
20
+ object.children = children;
21
+ }
22
+ }
23
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.js","sourceRoot":"","sources":["../../../src/impostor/bake/source.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,qCAAqC,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEhD,gGAAgG;AAChG,MAAM,UAAU,aAAa,CAAC,MAAgB,EAAE,WAAgC,EAAE;IAChF,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IACjC,IAAI,CAAC;QACH,iFAAiF;QACjF,6EAA6E;QAC7E,4EAA4E;QAC5E,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QACjG,WAAW,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAC3B,IAAI,cAAc,CAAC,IAAI,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,+CAA+C,IAAI,CAAC,IAAI,kCAAkC,CAAC,CAAC;QACxI,CAAC,CAAC,CAAC;QACH,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC7B,CAAC;AACH,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { Object3D } from "three/webgpu";
2
+ import { type CacheEntryOptions, type Dependencies } from "vitecache";
3
+ import { type BakeImpostorOptions } from "./bake/index.js";
4
+ import type { Impostor } from "./impostor.js";
5
+ /** Cache location; browser basePath and Node root follow vitecache's conventions. */
6
+ export type ImpostorCacheOptions = Pick<CacheEntryOptions, "basePath" | "root">;
7
+ /**
8
+ * Read or bake an impostor with vitecache. Declare every source input as a dependency;
9
+ * bake options and the lod/three package versions are added automatically. Dependency
10
+ * names beginning with `lod:` are reserved. URL dependencies need the vitecache Vite
11
+ * plugin during development and build. Cache errors reject instead of hiding stale data.
12
+ */
13
+ export declare function cachedImpostor(source: Object3D, options: BakeImpostorOptions, dependencies: Dependencies, cacheOptions?: ImpostorCacheOptions): Promise<Impostor>;
14
+ /** Shared persistence for the public bake and the loader's source-excluding bake. */
15
+ export declare function cached(options: BakeImpostorOptions, dependencies: Dependencies, make: (options: BakeImpostorOptions) => Promise<Impostor>, cacheOptions?: ImpostorCacheOptions): Promise<Impostor>;
@@ -0,0 +1,44 @@
1
+ import { CacheEntry } from "vitecache";
2
+ import { bakeImpostor } from "./bake/index.js";
3
+ import { decodeImpostorAsync, encodeImpostor } from "./codec.js";
4
+ /**
5
+ * Read or bake an impostor with vitecache. Declare every source input as a dependency;
6
+ * bake options and the lod/three package versions are added automatically. Dependency
7
+ * names beginning with `lod:` are reserved. URL dependencies need the vitecache Vite
8
+ * plugin during development and build. Cache errors reject instead of hiding stale data.
9
+ */
10
+ export function cachedImpostor(source, options, dependencies, cacheOptions) {
11
+ return cached(options, dependencies, (settings) => bakeImpostor(source, settings), cacheOptions);
12
+ }
13
+ /** Shared persistence for the public bake and the loader's source-excluding bake. */
14
+ export async function cached(options, dependencies, make, cacheOptions) {
15
+ const settings = { ...options };
16
+ if (Object.keys(dependencies).some((name) => name.startsWith("lod:")))
17
+ throw new Error("cachedImpostor: dependency names beginning with lod: are reserved");
18
+ const entry = new CacheEntry({
19
+ ...cacheOptions,
20
+ version: "lod-impostor@1",
21
+ dependencies: {
22
+ ...dependencies,
23
+ "lod:options": {
24
+ value: {
25
+ layout: settings.layout ?? null,
26
+ frames: settings.frames ?? null,
27
+ resolution: settings.resolution ?? null,
28
+ maxPixels: settings.maxPixels ?? null,
29
+ emissive: settings.emissive ?? null,
30
+ supersample: settings.supersample ?? null,
31
+ },
32
+ },
33
+ "lod:package": { package: "@drawcall/lod" },
34
+ "lod:three": { package: "three" },
35
+ },
36
+ });
37
+ const bytes = await entry.load();
38
+ if (bytes !== undefined)
39
+ return decodeImpostorAsync(bytes);
40
+ const baked = await make(settings);
41
+ await entry.save(encodeImpostor(baked));
42
+ return baked;
43
+ }
44
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/impostor/cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAA6C,MAAM,WAAW,CAAC;AAClF,OAAO,EAAE,YAAY,EAA4B,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAMjE;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,MAAgB,EAChB,OAA4B,EAC5B,YAA0B,EAC1B,YAAmC;IAEnC,OAAO,MAAM,CAAC,OAAO,EAAE,YAAY,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,YAAY,CAAC,CAAC;AACnG,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,OAA4B,EAC5B,YAA0B,EAC1B,IAAyD,EACzD,YAAmC;IAEnC,MAAM,QAAQ,GAAG,EAAE,GAAG,OAAO,EAAE,CAAC;IAChC,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAC;IAC5J,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC;QAC3B,GAAG,YAAY;QACf,OAAO,EAAE,gBAAgB;QACzB,YAAY,EAAE;YACZ,GAAG,YAAY;YACf,aAAa,EAAE;gBACb,KAAK,EAAE;oBACL,MAAM,EAAE,QAAQ,CAAC,MAAM,IAAI,IAAI;oBAC/B,MAAM,EAAE,QAAQ,CAAC,MAAM,IAAI,IAAI;oBAC/B,UAAU,EAAE,QAAQ,CAAC,UAAU,IAAI,IAAI;oBACvC,SAAS,EAAE,QAAQ,CAAC,SAAS,IAAI,IAAI;oBACrC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,IAAI,IAAI;oBACnC,WAAW,EAAE,QAAQ,CAAC,WAAW,IAAI,IAAI;iBAC1C;aACF;YACD,aAAa,EAAE,EAAE,OAAO,EAAE,eAAe,EAAE;YAC3C,WAAW,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE;SAClC;KACF,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;IACjC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAC3D,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;IACxC,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { type Impostor } from "./impostor.js";
2
+ /**
3
+ * Serializes `impostor` to a KTX2 file: one RGBA8 UNORM 2D array texture with a layer per
4
+ * map and its baked mip levels, zlib-supercompressed, plus the metadata as JSON under the
5
+ * "impostor" key.
6
+ */
7
+ export declare function encodeImpostor(impostor: Impostor): Uint8Array;
8
+ type Input = Uint8Array | ArrayBuffer | string;
9
+ /** Parses a file written by `encodeImpostor`, given as bytes or base64. */
10
+ export declare function decodeImpostor(input: Input): Impostor;
11
+ /** Inflate cache files asynchronously with the runtime's native zlib decoder. */
12
+ export declare function decodeImpostorAsync(input: Input): Promise<Impostor>;
13
+ export declare function fromBase64(text: string): Uint8Array;
14
+ export {};
@@ -0,0 +1,137 @@
1
+ import { unzlibSync, zlibSync } from "fflate";
2
+ import { KHR_DF_CHANNEL_RGBSDA_ALPHA, KHR_DF_CHANNEL_RGBSDA_BLUE, KHR_DF_CHANNEL_RGBSDA_GREEN, KHR_DF_CHANNEL_RGBSDA_RED, KHR_DF_MODEL_RGBSDA, KHR_DF_TRANSFER_LINEAR, KHR_SUPERCOMPRESSION_ZLIB, VK_FORMAT_R8G8B8A8_UNORM, createDefaultContainer, read, write, } from "ktx-parse";
3
+ import { validateImpostor } from "./impostor.js";
4
+ /** Key/value entry holding every non-pixel field as JSON. */
5
+ const KEY = "impostor";
6
+ /**
7
+ * Serializes `impostor` to a KTX2 file: one RGBA8 UNORM 2D array texture with a layer per
8
+ * map and its baked mip levels, zlib-supercompressed, plus the metadata as JSON under the
9
+ * "impostor" key.
10
+ */
11
+ export function encodeImpostor(impostor) {
12
+ validateImpostor(impostor);
13
+ const { layers, ...meta } = impostor;
14
+ const levelCount = layers[0]?.length ?? 0;
15
+ // KTX2 stores each mip level with all its array layers, layer-major.
16
+ const levels = Array.from({ length: levelCount }, (_, m) => {
17
+ const bytes = (meta.size >> m) ** 2 * 4;
18
+ const raw = new Uint8Array(layers.length * bytes);
19
+ layers.forEach((layer, i) => raw.set(layer[m] ?? fail(`layer ${i} has no level ${m}`), i * bytes));
20
+ return { levelData: zlibSync(raw, { level: 6 }), uncompressedByteLength: raw.length };
21
+ });
22
+ const ktx = createDefaultContainer();
23
+ ktx.vkFormat = VK_FORMAT_R8G8B8A8_UNORM;
24
+ ktx.typeSize = 1;
25
+ ktx.pixelWidth = meta.size;
26
+ ktx.pixelHeight = meta.size;
27
+ ktx.layerCount = layers.length;
28
+ ktx.levelCount = levels.length;
29
+ ktx.supercompressionScheme = KHR_SUPERCOMPRESSION_ZLIB;
30
+ ktx.levels = levels;
31
+ // UNORM data must declare a linear transfer; base/emissive are sRGB-encoded but the
32
+ // renderer decodes them itself, as it does with the linear normal/orm layers.
33
+ const channels = [KHR_DF_CHANNEL_RGBSDA_RED, KHR_DF_CHANNEL_RGBSDA_GREEN, KHR_DF_CHANNEL_RGBSDA_BLUE, KHR_DF_CHANNEL_RGBSDA_ALPHA];
34
+ for (const dfd of ktx.dataFormatDescriptor) {
35
+ dfd.colorModel = KHR_DF_MODEL_RGBSDA;
36
+ dfd.transferFunction = KHR_DF_TRANSFER_LINEAR;
37
+ dfd.bytesPlane = [4, 0, 0, 0, 0, 0, 0, 0];
38
+ dfd.samples = channels.map((channelType, i) => ({
39
+ bitOffset: i * 8,
40
+ bitLength: 7, // stored as bits - 1
41
+ channelType,
42
+ samplePosition: [0, 0, 0, 0],
43
+ sampleLower: 0,
44
+ sampleUpper: 255,
45
+ }));
46
+ }
47
+ ktx.keyValue[KEY] = JSON.stringify(meta);
48
+ return write(ktx);
49
+ }
50
+ /** Parses a file written by `encodeImpostor`, given as bytes or base64. */
51
+ export function decodeImpostor(input) {
52
+ const file = parse(input);
53
+ const levels = file.levels.map(({ data, length }, m) => attempt(`level ${m} does not inflate`, () => unzlibSync(data, { out: new Uint8Array(length) })));
54
+ return decoded(file, levels);
55
+ }
56
+ /** Inflate cache files asynchronously with the runtime's native zlib decoder. */
57
+ export async function decodeImpostorAsync(input) {
58
+ if (typeof DecompressionStream === "undefined")
59
+ return decodeImpostor(input);
60
+ const file = parse(input);
61
+ const levels = await Promise.all(file.levels.map(async ({ data }, m) => {
62
+ try {
63
+ const stream = new Blob([data.slice()]).stream().pipeThrough(new DecompressionStream("deflate"));
64
+ return new Uint8Array(await new Response(stream).arrayBuffer());
65
+ }
66
+ catch (error) {
67
+ throw invalid(`level ${m} does not inflate`, error);
68
+ }
69
+ }));
70
+ return decoded(file, levels);
71
+ }
72
+ function parse(input) {
73
+ const bytes = typeof input === "string" ? attempt("not base64", () => fromBase64(input)) : input instanceof Uint8Array ? input : new Uint8Array(input);
74
+ // ktx-parse reads through `bytes.buffer` without checking the view's end, so a truncated
75
+ // view would read past it; give it a buffer of exactly the file instead.
76
+ const file = bytes.byteLength === bytes.buffer.byteLength ? bytes : bytes.slice();
77
+ const ktx = attempt("not a KTX2 container", () => read(file));
78
+ if (ktx.vkFormat !== VK_FORMAT_R8G8B8A8_UNORM)
79
+ fail(`vkFormat ${ktx.vkFormat} is not R8G8B8A8_UNORM`);
80
+ if (ktx.supercompressionScheme !== KHR_SUPERCOMPRESSION_ZLIB)
81
+ fail(`supercompression ${ktx.supercompressionScheme} is not zlib`);
82
+ if (ktx.pixelWidth !== ktx.pixelHeight)
83
+ fail(`atlas is ${ktx.pixelWidth}x${ktx.pixelHeight}, not square`);
84
+ const json = ktx.keyValue[KEY];
85
+ if (!(json instanceof Uint8Array))
86
+ fail(`missing "${KEY}" metadata`);
87
+ // ktx-parse returns values of keys not starting with "KTX" as raw bytes, NUL included.
88
+ const meta = attempt("metadata is not JSON", () => JSON.parse(new TextDecoder().decode(json).replace(/\0$/, "")));
89
+ if (typeof meta !== "object" || meta === null)
90
+ fail("metadata is not an object");
91
+ const levels = ktx.levels.map((level, m) => {
92
+ const bytes = (ktx.pixelWidth >> m) ** 2 * 4;
93
+ if (level.uncompressedByteLength !== bytes * ktx.layerCount)
94
+ fail(`level ${m} size does not match the atlas`);
95
+ return { data: level.levelData, length: level.uncompressedByteLength };
96
+ });
97
+ return { meta, layerCount: ktx.layerCount, levels };
98
+ }
99
+ function decoded(file, levels) {
100
+ levels.forEach((raw, m) => {
101
+ if (raw.length !== file.levels[m]?.length)
102
+ fail(`level ${m} inflates to the wrong size`);
103
+ });
104
+ // Layers share one inflated buffer per mip, in KTX2's layer-major order.
105
+ const layers = Array.from({ length: file.layerCount }, (_, i) => levels.map((raw) => raw.subarray((i * raw.length) / file.layerCount, ((i + 1) * raw.length) / file.layerCount)));
106
+ const impostor = { ...file.meta, layers };
107
+ validateImpostor(impostor);
108
+ return impostor;
109
+ }
110
+ function fail(why) {
111
+ throw new Error(`Invalid impostor file: ${why}`);
112
+ }
113
+ /** Runs `run`, turning any throw into an "Invalid impostor file" error. */
114
+ function attempt(why, run) {
115
+ try {
116
+ return run();
117
+ }
118
+ catch (error) {
119
+ throw invalid(why, error);
120
+ }
121
+ }
122
+ function invalid(why, error) {
123
+ return new Error(`Invalid impostor file: ${why} (${error instanceof Error ? error.message : String(error)})`, { cause: error });
124
+ }
125
+ export function fromBase64(text) {
126
+ // Native where available (Node 25+, current browsers); the fallback covers the rest.
127
+ const native = Reflect.get(Uint8Array, "fromBase64");
128
+ const decoded = typeof native === "function" ? Reflect.apply(native, Uint8Array, [text]) : undefined;
129
+ if (decoded instanceof Uint8Array)
130
+ return decoded;
131
+ const binary = atob(text);
132
+ const bytes = new Uint8Array(binary.length);
133
+ for (let i = 0; i < binary.length; i++)
134
+ bytes[i] = binary.charCodeAt(i);
135
+ return bytes;
136
+ }
137
+ //# sourceMappingURL=codec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codec.js","sourceRoot":"","sources":["../../src/impostor/codec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /** Which part of the view sphere the atlas covers. */
2
+ export type ImpostorLayout = "hemi" | "full";
3
+ /**
4
+ * The atlas maps, in the order they are stored. Each is one RGBA8 layer:
5
+ *
6
+ * - `base`: base colour (sRGB) in RGB, coverage in A.
7
+ * - `normalDepth`: an object-space normal n in RGB (`n * 0.5 + 0.5`), depth in A. The
8
+ * surface shades with n on the share of it in orm.a and with -n on the rest; the
9
+ * length of n is how far the normals it averages agree (1: all one way).
10
+ * - `orm`: occlusion, roughness, metalness in RGB (glTF / three order), and in A the
11
+ * share of the surface that shades with n.
12
+ * - `emissive`: emissive colour (sRGB) in RGB, A unused. Only when something emits.
13
+ */
14
+ export declare const MAPS: readonly ["base", "normalDepth", "orm", "emissive"];
15
+ export type ImpostorMap = (typeof MAPS)[number];
16
+ /**
17
+ * A baked octahedral impostor: plain data, no GPU resources.
18
+ *
19
+ * The atlas is `frames x frames` cells of `size / frames` pixels each. Cell (col, row)
20
+ * holds the view from `frameDirection(layout, frames, col, row)` and starts at pixel
21
+ * `(col * cell, row * cell)`; row 0 is the first row in memory, which is the bottom row
22
+ * of the texture (three's DataTexture convention).
23
+ *
24
+ * Depth is `0.5 + 0.5 * dot(p - centre, viewDirection) / boundingRadius(box)`, so 1 is
25
+ * toward the camera that baked the frame and 0 is away from it.
26
+ */
27
+ export interface Impostor {
28
+ version: 3;
29
+ layout: ImpostorLayout;
30
+ /** frames per atlas side */
31
+ frames: number;
32
+ /** atlas edge in pixels: `frames` times a power of two */
33
+ size: number;
34
+ /** bounding box of the source in its own space: min xyz, max xyz */
35
+ box: [number, number, number, number, number, number];
36
+ /** half the edge of every frame's orthographic view, in source units, centred on the box centre */
37
+ halfSize: number;
38
+ /** `base`, `normalDepth`, `orm`, then `emissive` if it was baked */
39
+ maps: ImpostorMap[];
40
+ /**
41
+ * The dielectric specular three lights the surface with, F0 (linear RGB) then F90,
42
+ * averaged over the whole object as the frames show it. Only when a material changes
43
+ * it from three's 0.04 and 1 (a MeshPhysicalMaterial's ior, specularIntensity,
44
+ * specularColor or their maps).
45
+ */
46
+ specular?: [number, number, number, number];
47
+ /**
48
+ * One per entry in `maps`: its mip chain of RGBA8 arrays, `size >> level` square, from
49
+ * `size` down to one texel per frame (`frames` square). The levels are baked, not
50
+ * filtered from level 0. An ImpostorMesh's textures share these arrays, so they stay in
51
+ * memory while the impostor is referenced, ready for `encodeImpostor`.
52
+ */
53
+ layers: Uint8Array[][];
54
+ }
55
+ export declare function boxCentre(b: Impostor["box"]): [number, number, number];
56
+ /** Half the box's edges. */
57
+ export declare function boxExtent(b: Impostor["box"]): [number, number, number];
58
+ /** Radius of the sphere around the box: the depth channel's unit. */
59
+ export declare function boundingRadius(b: Impostor["box"]): number;
60
+ /**
61
+ * On-screen height of the object at which two atlas texels span one pixel: the default
62
+ * `LodLevel.maxPixels` of an impostor. An impostor blends filtered reads of several
63
+ * views, so it keeps the source's colour detail only down to about two texels per pixel.
64
+ */
65
+ export declare function texelPixels(impostor: Pick<Impostor, "size" | "frames" | "box" | "halfSize">): number;
66
+ /** Throws unless `value` is a usable impostor. */
67
+ export declare function validateImpostor(value: unknown): asserts value is Impostor;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * The atlas maps, in the order they are stored. Each is one RGBA8 layer:
3
+ *
4
+ * - `base`: base colour (sRGB) in RGB, coverage in A.
5
+ * - `normalDepth`: an object-space normal n in RGB (`n * 0.5 + 0.5`), depth in A. The
6
+ * surface shades with n on the share of it in orm.a and with -n on the rest; the
7
+ * length of n is how far the normals it averages agree (1: all one way).
8
+ * - `orm`: occlusion, roughness, metalness in RGB (glTF / three order), and in A the
9
+ * share of the surface that shades with n.
10
+ * - `emissive`: emissive colour (sRGB) in RGB, A unused. Only when something emits.
11
+ */
12
+ export const MAPS = ["base", "normalDepth", "orm", "emissive"];
13
+ export function boxCentre(b) {
14
+ return [(b[0] + b[3]) / 2, (b[1] + b[4]) / 2, (b[2] + b[5]) / 2];
15
+ }
16
+ /** Half the box's edges. */
17
+ export function boxExtent(b) {
18
+ return [(b[3] - b[0]) / 2, (b[4] - b[1]) / 2, (b[5] - b[2]) / 2];
19
+ }
20
+ /** Radius of the sphere around the box: the depth channel's unit. */
21
+ export function boundingRadius(b) {
22
+ return Math.hypot(b[3] - b[0], b[4] - b[1], b[5] - b[2]) / 2;
23
+ }
24
+ /**
25
+ * On-screen height of the object at which two atlas texels span one pixel: the default
26
+ * `LodLevel.maxPixels` of an impostor. An impostor blends filtered reads of several
27
+ * views, so it keeps the source's colour detail only down to about two texels per pixel.
28
+ */
29
+ export function texelPixels(impostor) {
30
+ const cell = impostor.size / impostor.frames;
31
+ // A frame spans 2 × halfSize of the object; the height measured is its bounding diameter.
32
+ return (cell * boundingRadius(impostor.box)) / (2 * impostor.halfSize);
33
+ }
34
+ const isNumber = (v) => typeof v === "number" && Number.isFinite(v);
35
+ const isBox = (v) => Array.isArray(v) && v.length === 6 && v.every(isNumber);
36
+ /** Throws unless `value` is a usable impostor. */
37
+ export function validateImpostor(value) {
38
+ function fail(why) {
39
+ throw new Error(`Invalid impostor: ${why}`);
40
+ }
41
+ if (typeof value !== "object" || value === null)
42
+ fail("not an object");
43
+ const fields = value;
44
+ const { version, layout, frames, size, box, halfSize, maps, specular, layers } = fields;
45
+ if (version !== 3)
46
+ fail(`unsupported version ${String(version)}; bake it again`);
47
+ if (layout !== "hemi" && layout !== "full")
48
+ fail(`unknown layout "${String(layout)}"`);
49
+ if (!isNumber(frames) || !Number.isInteger(frames) || frames < 2)
50
+ fail("frames must be an integer >= 2");
51
+ if (!isNumber(size) || !Number.isInteger(size) || size <= 0 || size % frames !== 0)
52
+ fail(`size ${String(size)} is not a positive multiple of frames ${frames}`);
53
+ if (!isBox(box))
54
+ fail("box must be 6 finite numbers");
55
+ if (box[0] > box[3] || box[1] > box[4] || box[2] > box[5])
56
+ fail("box min must not exceed max");
57
+ if (!isNumber(halfSize) || halfSize <= 0)
58
+ fail("halfSize must be a positive number");
59
+ if (!Array.isArray(maps) || maps.length < 3 || !maps.every((map, i) => map === MAPS[i]))
60
+ fail(`maps must be base, normalDepth, orm, optionally followed by emissive; got ${String(maps)}`);
61
+ if (specular !== undefined && !(Array.isArray(specular) && specular.length === 4 && specular.every((v) => isNumber(v) && v >= 0 && v <= 1)))
62
+ fail("specular must be 4 numbers in [0, 1]");
63
+ if (!Array.isArray(layers) || layers.length !== maps.length)
64
+ fail("one layer per map");
65
+ const cell = size / frames;
66
+ if (!Number.isInteger(Math.log2(cell)))
67
+ fail(`frame size ${cell} is not a power of two`);
68
+ const levels = Math.log2(cell) + 1;
69
+ for (const layer of layers) {
70
+ if (!Array.isArray(layer) || layer.length !== levels)
71
+ fail(`every layer must be a mip chain of ${levels} levels`);
72
+ layer.forEach((level, m) => {
73
+ const bytes = (size >> m) ** 2 * 4;
74
+ if (!(level instanceof Uint8Array) || level.length !== bytes)
75
+ fail(`mip level ${m} must be a Uint8Array of ${bytes} bytes`);
76
+ });
77
+ }
78
+ }
79
+ //# sourceMappingURL=impostor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"impostor.js","sourceRoot":"","sources":["../../src/impostor/impostor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { Node } from "three/webgpu";
2
+ import type { ImpostorLayout } from "../impostor.js";
3
+ /** What every card of one impostor reads. */
4
+ export interface CardInputs {
5
+ layout: ImpostorLayout;
6
+ /** bounding box centre and half its edges, in the object's space */
7
+ centre: Node<"vec3">;
8
+ extent: Node<"vec3">;
9
+ /** bounding sphere radius: the depth channel's unit */
10
+ radius: Node<"float">;
11
+ /** half the edge of every frame's view */
12
+ halfSize: Node<"float">;
13
+ /** frames per atlas side */
14
+ frames: Node<"float">;
15
+ /** where the card sits within an instance's space; identity except under InstancedLod */
16
+ placement: Node<"mat4">;
17
+ }
18
+ /** The card as the surface reads it. */
19
+ export declare const card: {
20
+ /** the three frames around the view direction and their barycentric weights */
21
+ readonly frames: readonly [Node<"vec2">, Node<"vec2">, Node<"vec2">];
22
+ readonly weights: import("three/webgpu").PropertyNode<"vec3">;
23
+ /** the view ray through the fragment, in the object's space: from `point`, along `ray` */
24
+ readonly point: import("three/webgpu").PropertyNode<"vec3">;
25
+ readonly ray: import("three/webgpu").PropertyNode<"vec3">;
26
+ /** the same ray in world space: `ray` × s in object space is `worldRay` × s in the world */
27
+ readonly worldPoint: import("three/webgpu").PropertyNode<"vec3">;
28
+ readonly worldRay: import("three/webgpu").PropertyNode<"vec3">;
29
+ /** an object-space normal in view space, as three's normal matrix turns a mesh's */
30
+ readonly toView: (normal: Node<"vec3">) => Node<"vec3">;
31
+ };
32
+ export type Card = typeof card;
33
+ /**
34
+ * The card's vertex, in the mesh's space (after the instance matrix), for the instance
35
+ * placed by `instance`, `deform` (wind) applied in its space. Call it while the vertex
36
+ * stage builds the position: it writes the varyings `card` reads.
37
+ */
38
+ export declare function buildCard(inputs: CardInputs, instance: Node<"mat4">, deform: Node<"mat4"> | null): Node<"vec3">;