@solidrt/3d 0.0.58 → 0.0.59

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 (70) hide show
  1. package/AGENTS.md +734 -264
  2. package/README.md +4 -2
  3. package/examples/README.md +50 -4
  4. package/examples/bloom.tsx +113 -0
  5. package/examples/cascades.tsx +4 -4
  6. package/examples/collision.tsx +6 -6
  7. package/examples/exits.tsx +96 -0
  8. package/examples/first-person.tsx +5 -5
  9. package/examples/fleet.tsx +4 -4
  10. package/examples/fog.tsx +5 -5
  11. package/examples/instanced.tsx +70 -50
  12. package/examples/lamps.tsx +4 -4
  13. package/examples/lod.tsx +125 -0
  14. package/examples/materials.tsx +9 -9
  15. package/examples/morph.tsx +174 -0
  16. package/examples/normals.tsx +118 -0
  17. package/examples/{lit.tsx → phong.tsx} +9 -9
  18. package/examples/polyhedra.tsx +61 -0
  19. package/examples/scene-atlas.tsx +70 -0
  20. package/examples/scene-views.tsx +5 -5
  21. package/examples/shadows.tsx +9 -9
  22. package/examples/sky-lit.tsx +3 -3
  23. package/examples/skybox.tsx +5 -5
  24. package/examples/standard.tsx +2 -2
  25. package/examples/streams.tsx +122 -0
  26. package/examples/sweep-paths.tsx +5 -5
  27. package/examples/wireframe.tsx +24 -2
  28. package/package.json +3 -3
  29. package/src/clip.ts +18 -5
  30. package/src/color.ts +11 -2
  31. package/src/components/context.tsx +11 -2
  32. package/src/components/directional-light.tsx +2 -2
  33. package/src/components/first-person-camera.tsx +4 -4
  34. package/src/components/group.tsx +9 -5
  35. package/src/components/hemisphere-light.tsx +2 -2
  36. package/src/components/index.ts +4 -1
  37. package/src/components/instanced-mesh.tsx +28 -10
  38. package/src/components/lod.tsx +102 -0
  39. package/src/components/mesh.tsx +24 -6
  40. package/src/components/node-props.ts +35 -5
  41. package/src/components/orbit-camera.tsx +15 -12
  42. package/src/components/point-light.tsx +2 -2
  43. package/src/components/record-mesh.tsx +12 -7
  44. package/src/components/scene.tsx +55 -2
  45. package/src/components/spot-light.tsx +2 -2
  46. package/src/components/sprite.tsx +2 -2
  47. package/src/components/view3d.tsx +18 -1
  48. package/src/environment.ts +9 -8
  49. package/src/first-person.ts +125 -38
  50. package/src/geometry-gpu.ts +172 -39
  51. package/src/geometry.ts +1573 -239
  52. package/src/glsl.ts +398 -113
  53. package/src/gltf.ts +363 -141
  54. package/src/index.ts +31 -21
  55. package/src/light.ts +7 -8
  56. package/src/lod.ts +148 -0
  57. package/src/material.ts +316 -202
  58. package/src/mesh.ts +490 -256
  59. package/src/mixer.ts +27 -14
  60. package/src/model-file.ts +54 -15
  61. package/src/model.ts +86 -21
  62. package/src/motion.ts +17 -0
  63. package/src/node.ts +421 -22
  64. package/src/orbit.ts +378 -116
  65. package/src/profile.ts +4 -25
  66. package/src/resolve.ts +181 -0
  67. package/src/scene-shadows.ts +94 -93
  68. package/src/scene.ts +605 -185
  69. package/src/sweep.ts +27 -8
  70. package/tools/model.ts +3 -1
package/src/index.ts CHANGED
@@ -5,11 +5,13 @@
5
5
  // without Solid components) and the component face (Scene/Mesh/Group/
6
6
  // PerspectiveCamera) on top. See AGENTS.md for the model and the traps.
7
7
 
8
- export { add, createGroup, getRotation, getTransform, lookAt, remove, setTransform, setTransition, setVisible, worldPosition } from "./node.ts"
9
- export type { NodePointerEvent, NodeTapEvent, NodeWheelEvent, SceneEventBase, SceneNode, ScenePointerEvent, ScenePointerListener, SceneTapEvent, SceneWheelEvent, TransformUpdate, TransitionEndEvent } from "./node.ts"
10
- export { addInstance, createInstancedMesh, createMesh, createRecordMesh, createSprite, disposeInstances, removeInstance, setCastShadow, setCulling, setGeometry, setInstanceStyle, setLayers, setMaterial, setMeshParams, setRecordCount, setRecords, setRenderOrder, INSTANCE_FLOATS } from "./mesh.ts"
11
- export type { InstancedMesh as InstancedMeshNode, InstancedMeshOptions, InstanceNode, InstanceSlots, InstanceStyle, Mesh as MeshNode, MeshInstances, RecordMesh as RecordMeshNode, RecordMeshOptions } from "./mesh.ts"
12
- export { createDirectionalLight, createHemisphereLight, createPointLight, createSpotLight, setLight, MAX_SHADOWS } from "./light.ts"
8
+ export { add, createGroup, destroy, getMorphNames, getMorphWeights, getRotation, getTransform, lookAt, remove, setMorphWeights, setTransform, setTransition, setVisible, worldPosition } from "./node.ts"
9
+ export type { LodConfig, MorphWeights, NodePointerEvent, NodeTapEvent, NodeWheelEvent, SceneEventBase, SceneNode, ScenePointerEvent, ScenePointerListener, SceneTapEvent, SceneWheelEvent, TransformUpdate, TransitionEndEvent } from "./node.ts"
10
+ export { addInstance, createInstancedMesh, createMesh, createRecordMesh, createSprite, disposeInstances, setCastShadow, setCulling, setDrawRange, setGeometry, setInstanceStyle, setLayers, setMaterial, setMeshParams, setRecordCount, setRecords, setRenderOrder, instanceAttribute, updateRecords, INSTANCE_FLOATS } from "./mesh.ts"
11
+ export type { InstancedMesh as InstancedMeshNode, InstancedMeshOptions, InstanceNode, InstanceSlots, InstanceStream, Mesh as MeshNode, MeshInstances, RecordMesh as RecordMeshNode, RecordMeshOptions, UpdateRecordsOptions } from "./mesh.ts"
12
+ export { createDirectionalLight, createHemisphereLight, createPointLight, createSpotLight, setLight } from "./light.ts"
13
+ // The shader-source caps, also on the /glsl subpath next to the sources they size.
14
+ export { MAX_CASCADES, MAX_LIGHTS, MAX_SHADOW_MAPS } from "./glsl.ts"
13
15
  export type {
14
16
  DirectionalLight as DirectionalLightNode,
15
17
  DirectionalLightOptions,
@@ -25,36 +27,44 @@ export type {
25
27
  SpotLightOptions,
26
28
  SpotShadowOptions,
27
29
  } from "./light.ts"
28
- export { createScene } from "./scene.ts"
30
+ export { createInstancedLod, createLod, setLod } from "./lod.ts"
31
+ export type { InstancedLodLevel, InstancedLodOptions, LodLevel, LodOptions } from "./lod.ts"
32
+ export { createScene, resolveParams } from "./scene.ts"
29
33
  export { feedPointer } from "./scene-pointer.ts"
30
- export type { Capsule, EnvironmentOptions, FogOptions, Hit, Impact, MoveOptions, MoveResult, OrientedBox, Overlap, QueryOptions, ReflectionProbe, ReflectionProbeOptions, Scene as SceneHandle, SceneHandlers, SceneOptions, ScreenRay, SkyboxOptions, Sphere, ToneMapping, ViewHandle, ViewOptions, Volume } from "./scene.ts"
34
+ export type { BloomOptions, Capsule, EnvironmentOptions, FogOptions, Hit, Impact, MoveOptions, MoveResult, OrientedBox, Overlap, QueryOptions, ReflectionProbe, ReflectionProbeOptions, ResolveInput, ResolveOptions, Scene as SceneHandle, SceneHandlers, SceneOptions, ScreenRay, SkyboxOptions, Sphere, ToneMapping, ViewHandle, ViewOptions, Volume } from "./scene.ts"
31
35
  export { moveAndSlide } from "./collision.ts"
32
36
  export type { MoveScene } from "./collision.ts"
33
- export { equirectToCube, loadEnvironment } from "./environment.ts"
34
- export { linearColor, linearToSrgb, srgbToLinear } from "./color.ts"
37
+ export { bufferFormat, equirectToCube, loadEnvironment } from "./environment.ts"
38
+ export type { BufferFormat } from "./environment.ts"
39
+ export { linearColor, linearToSrgb, premultipliedColor, srgbToLinear } from "./color.ts"
35
40
  export type { CameraState, CameraUpdate, OrthoExtent } from "./camera.ts"
36
- export type { NodeTransition, NodeTransitionSpec } from "flux:spatial"
37
- export { disposeGeometry } from "./geometry-gpu.ts"
38
- export { box, circle, cone, cylinder, fillAttribute, fillColors, edgesGeometry, geometryBounds, geometryTopology, layoutAttributes, layoutKey, layoutSlot, layoutStride, mergeGeometries, packGeometry, plane, ring, sphere, torus, torusKnot, transformGeometry, validateGeometry, wireframeGeometry, withAttribute, withColors, STANDARD_FLOATS, VERTEX_LAYOUTS } from "./geometry.ts"
39
- export type { AttributeFill, BoxOptions, CircleOptions, ColorFill, ConeOptions, CylinderOptions, Geometry, GeometryOptions, PlaneOptions, RingOptions, SphereOptions, TorusKnotOptions, TorusOptions, VertexLayout } from "./geometry.ts"
40
- export { fillet, roundRect, shape, triangulate } from "./profile.ts"
41
+ export type { NodeMotionSpec, NodeTransition, NodeTransitionSpec } from "flux:spatial"
42
+ export { computeVertexNormals, disposeGeometry, updateVertices } from "./geometry-gpu.ts"
43
+ export type { GeometryBuffers, UpdateVerticesOptions } from "./geometry-gpu.ts"
44
+ export { arrowHelper, axesHelper, box, box3Helper, capsule, capsuleHelper, circle, cone, cylinder, dodecahedron, fillAttribute, fillColors, edgesGeometry, mergeVertices, normalsHelper, toNonIndexed, withNormals, geometryBounds, geometryTopology, gridHelper, icosahedron, attributeAccess, geometryAttribute, geometryKey, geometryLayouts, geometrySlot, geometryStreams, geometryVertexCount, isFloatFormat, isFloatLayout, layoutAttributes, layoutKey, layoutSlot, layoutStride, mergeGeometries, octahedron, packGeometry, packMorphTargets, plane, planeHelper, polyhedron, ring, sphere, tetrahedron, torus, torusKnot, transformGeometry, validateGeometry, wireframeGeometry, vertexBytes, vertexCount, vertexView, withAttribute, withColors, withMorphTargets, MORPH_ENTRY_TEXELS, MORPH_TEXEL_FLOATS, BASE_FLOATS, VERTEX_FORMATS, VERTEX_LAYOUTS } from "./geometry.ts"
45
+ export type { ArrowHelperOptions, AttributeAccess, AttributeFill, VertexStream, WithAttributeOptions, AxesHelperOptions, BoxOptions, CapsuleHelperOptions, CapsuleOptions, CircleOptions, ColorFill, ConeOptions, CylinderOptions, FormatCodec, Geometry, GeometryOptions, GridHelperOptions, MorphTarget, MorphTargets, NormalsHelperOptions, PlaneHelperOptions, PlaneOptions, PolyhedronOptions, RingOptions, SphereOptions, TorusKnotOptions, TorusOptions, VertexLayout } from "./geometry.ts"
46
+ export { fillet, roundRect, triangulate } from "./profile.ts"
41
47
  export type { Profile, ProfilePoint } from "./profile.ts"
42
- export { extrude, lathe, pathFrames, sweep, tube } from "./sweep.ts"
48
+ export { extrude, lathe, pathFrames, polygon, sweep, tube } from "./sweep.ts"
43
49
  export type { ExtrudeOptions, LatheOptions, PathFrames, PathPoint, SweepPath, TubeOptions } from "./sweep.ts"
44
- export { lit, shaderMaterial, shaderMaterialClass, sprite, standard, unlit } from "./material.ts"
45
- export type { LitOptions, Material, ShaderMaterialClass, ShaderMaterialClassOptions, ShaderMaterialInstanceOptions, ShaderMaterialOptions, SpriteOptions, StandardOptions, UnlitOptions } from "./material.ts"
46
- export { DirectionalLight, FirstPersonCamera, Group, HemisphereLight, Instance, InstancedMesh, Mesh, OrbitCamera, PerspectiveCamera, PointLight, RecordMesh, Scene, SpotLight, Sprite, View3d, useScene } from "./components/index.ts"
50
+ export { phong, shaderMaterial, shaderMaterialClass, sprite, standard, unlit } from "./material.ts"
51
+ export type { LitOptions, Material, PhongOptions, ShaderMaterialClass, ShaderMaterialClassOptions, ShaderMaterialInstanceOptions, ShaderMaterialOptions, SpriteOptions, StandardOptions, UnlitOptions } from "./material.ts"
52
+ export { DirectionalLight, FirstPersonCamera, Group, HemisphereLight, Instance, InstancedLod, InstancedMesh, Lod, Mesh, OrbitCamera, PerspectiveCamera, PointLight, RecordMesh, Scene, SpotLight, Sprite, View3d, useScene } from "./components/index.ts"
47
53
  export type {
48
54
  CameraTarget,
49
55
  DirectionalLightProps,
50
56
  FirstPersonCameraProps,
51
57
  HemisphereLightProps,
58
+ InstancedLodProps,
52
59
  InstancedMeshProps,
53
60
  InstanceProps,
61
+ LodProps,
54
62
  MeshProps,
55
63
  OrbitCameraProps,
56
64
  PerspectiveCameraProps,
57
65
  PointerEventProps,
66
+ BubblingPointerEventProps,
67
+ GroupProps,
58
68
  PointLightProps,
59
69
  RecordMeshProps,
60
70
  ScenePointerProps,
@@ -72,11 +82,11 @@ export type { Model, ModelMaps, ModelOptions } from "./model.ts"
72
82
  export { bindSkeleton } from "./skeleton.ts"
73
83
  export { createMixer } from "./mixer.ts"
74
84
  export type { Mixer, MixerOptions, MixerPlayOptions } from "./mixer.ts"
75
- export { sampleChannel } from "./clip.ts"
85
+ export { channelElements, sampleChannel } from "./clip.ts"
76
86
  export { createOrbitCamera } from "./orbit.ts"
77
- export type { OrbitAxes, OrbitCamera as OrbitCameraHandle, OrbitCameraOptions, OrbitPose, OrbitTarget } from "./orbit.ts"
87
+ export type { OrbitAxes, OrbitCamera as OrbitCameraHandle, OrbitCameraOptions, OrbitPose, OrbitPoseState, OrbitTarget } from "./orbit.ts"
78
88
  export { createFirstPersonCamera } from "./first-person.ts"
79
- export type { FirstPersonAxes, FirstPersonCamera as FirstPersonCameraHandle, FirstPersonCameraOptions, FirstPersonPose, FirstPersonTarget } from "./first-person.ts"
89
+ export type { FirstPersonAxes, FirstPersonCamera as FirstPersonCameraHandle, FirstPersonCameraOptions, FirstPersonPose, FirstPersonPoseState, FirstPersonTarget } from "./first-person.ts"
80
90
  export { firstPersonActions, firstPersonBindings, orbitActions, orbitBindings } from "./input.ts"
81
91
  export type { CameraDevices } from "./input.ts"
82
92
  // math's lookAt (the camera view matrix) stays on the /math subpath: the
package/src/light.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // hemisphere ambient, their constructors and the setLight write path.
3
3
  // Scene reactions go through the node's SceneHooks (node.ts).
4
4
 
5
- import { MAX_CASCADES, MAX_LIGHTS } from "./glsl.ts"
5
+ import { MAX_CASCADES } from "./glsl.ts"
6
6
  import type { Vec3 } from "./math.ts"
7
7
  import { makeNode } from "./node.ts"
8
8
  import type { SceneNode } from "./node.ts"
@@ -204,13 +204,12 @@ export type PointLightOptions = {
204
204
  }
205
205
  export type HemisphereLightOptions = { sky?: Vec3; ground?: Vec3; intensity?: number }
206
206
 
207
- /** Every light may cast; the real bound is the shadow-slot budget
208
- * (MAX_SHADOW_MAPS: a directional light claims `shadow.cascades` slots,
209
- * a point light six, a spot one, and a caster past the budget throws at
210
- * attach). Every map is a tile of the scene's one shadow atlas, so the
211
- * pass count never follows the caster count, the fill does. */
212
- export const MAX_SHADOWS = MAX_LIGHTS
213
- export { MAX_CASCADES }
207
+ // Every light may cast; the bound is the shadow-slot budget
208
+ // (MAX_SHADOW_MAPS in glsl.ts, exported from the root with MAX_LIGHTS and
209
+ // MAX_CASCADES: a directional light claims `shadow.cascades` slots, a
210
+ // point light six, a spot one). Every map is a tile of the scene's one
211
+ // shadow atlas, so the pass count never follows the caster count, the
212
+ // fill does.
214
213
 
215
214
  // The spot cone bounds: a half-angle in (0, 90] degrees, penumbra a
216
215
  // 0..1 fraction of it.
package/src/lod.ts ADDED
@@ -0,0 +1,148 @@
1
+ // Level of detail: a group whose children are variants of one object,
2
+ // nearest first, with the projected size below which each hands over to
3
+ // the next; and the instanced form, a population whose every instance
4
+ // picks its own level. The choice is the spatial core's, per target,
5
+ // after each flush (okf/done/3d-lod.md): a thousand groups cost no JS
6
+ // per frame. Three's LOD vocabulary over Unity's screen-relative model.
7
+ import { add, createGroup } from "./node.ts"
8
+ import type { LodConfig, SceneNode } from "./node.ts"
9
+ import { createInstancedMesh, setCastShadow } from "./mesh.ts"
10
+ import type { InstancedMesh, InstancedMeshOptions } from "./mesh.ts"
11
+ import type { Geometry } from "./geometry.ts"
12
+ import type { Material } from "./material.ts"
13
+
14
+ /**
15
+ * One level of a LOD group: the node drawn at this level and `size`, the
16
+ * group's projected size - the diameter of the sphere around its levels'
17
+ * boxes (radius = half the box diagonal) as a fraction of the viewport
18
+ * height - BELOW which the level hands over to the next (Unity's screen
19
+ * relative transition height). The last level's size is the cull
20
+ * threshold: 0 keeps it drawn at any distance. For a perspective camera
21
+ * `size = radius / (distance * tan(fov / 2))`, so a tree whose box is 4
22
+ * across (radius 3.5) at 30 units under a 60 degree camera measures about
23
+ * 0.2.
24
+ */
25
+ export type LodLevel = { node: SceneNode; size: number }
26
+
27
+ export type LodOptions = {
28
+ /** The cross-fade band as a fraction of each threshold (default 0, a
29
+ * hard switch with hysteresis): a boundary at size `s` widens to
30
+ * `[s, s * (1 + fade))`, in which both levels draw with complementary
31
+ * screen-hash dithers (the stock materials; a custom class composes
32
+ * LOD_FADE). Shadow tiles never fade: their depth pass switches hard
33
+ * at the band's midpoint. */
34
+ fade?: number
35
+ }
36
+
37
+ // A level's size and the band fraction, checked where the mistake is
38
+ // made; the core re-checks with the same rules.
39
+ function checkSizes(levels: { size: number }[], fade: number | undefined, site: string): number {
40
+ levels.forEach((l, k) => {
41
+ if (!(Number.isFinite(l.size) && l.size >= 0)) throw new Error(site + ": level " + k + " size must be a finite number >= 0, got " + l.size)
42
+ if (k > 0 && l.size >= levels[k - 1]!.size) throw new Error(site + ": level sizes must be strictly descending, nearest first (level " + k + ": " + l.size + " after " + levels[k - 1]!.size + ")")
43
+ })
44
+ let f = fade ?? 0
45
+ if (!(Number.isFinite(f) && f >= 0)) throw new Error(site + ": fade must be a finite fraction >= 0, got " + f)
46
+ return f
47
+ }
48
+
49
+ /**
50
+ * A LOD group (Three's LOD, Unity's LODGroup): a group node whose levels
51
+ * are its children, one drawn at a time by projected size on every
52
+ * target - the scene and each view by their own camera, shadow tiles by
53
+ * the scene camera, so a caster's shadow matches the level the camera
54
+ * sees. The choice is the spatial core's after each flush; zero JS per
55
+ * frame however many groups. Picking and collision through the scene see
56
+ * the level the scene draws (a view's pick the view's). The group is
57
+ * measured by the sphere around its levels' boxes; nested groups chain.
58
+ * Re-declare with setLod; `setLod(group, [])` makes it a plain group.
59
+ */
60
+ export function createLod(levels: LodLevel[], opts?: LodOptions): SceneNode {
61
+ let group = createGroup()
62
+ setLod(group, levels, opts)
63
+ return group
64
+ }
65
+
66
+ /**
67
+ * Declare (or with an empty list clear) a node's LOD levels. A level is
68
+ * a direct child of the node: a parentless node is adopted as one here,
69
+ * a node parented elsewhere is refused (move it with `add` first - a
70
+ * declaration never re-parents behind your back). On the population of
71
+ * createInstancedLod the levels are sizes only (`{ size }`): its level
72
+ * meshes are fixed at creation.
73
+ */
74
+ export function setLod(node: SceneNode, levels: (LodLevel | { size: number })[], opts?: LodOptions): void {
75
+ if (node._destroyed) throw new Error("setLod: the node was destroyed")
76
+ let fade = checkSizes(levels, opts?.fade, "setLod")
77
+ let population = node.kind === "mesh" && (node as InstancedMesh)._instances?.levels !== null && (node as InstancedMesh)._instances !== null
78
+ let config: LodConfig | null = null
79
+ if (levels.length > 0) {
80
+ let nodes = levels.map(l => ("node" in l ? l.node : null))
81
+ if (population) {
82
+ if (nodes.some(n => n !== null)) throw new Error("setLod: an instanced LOD's levels are its meshes, fixed at creation - pass sizes only")
83
+ let count = (node as InstancedMesh)._instances.levels!.length
84
+ if (levels.length !== count) throw new Error("setLod: an instanced LOD with " + count + " level meshes takes " + count + " sizes, got " + levels.length)
85
+ } else {
86
+ if (nodes.some(n => n === null)) throw new Error("setLod: every level names a node")
87
+ for (let n of nodes as SceneNode[]) {
88
+ if (n === node) throw new Error("setLod: a level cannot be the group itself")
89
+ if (n.kind === "instance") throw new Error("setLod: an instance is slot-bound to its mesh and cannot be a level")
90
+ if (n._destroyed) throw new Error("setLod: a level node was destroyed")
91
+ if (nodes.indexOf(n) !== nodes.lastIndexOf(n)) throw new Error("setLod: a node is listed twice")
92
+ if (n.parent !== null && n.parent !== node) throw new Error("setLod: a level must be a direct child of the group (or parentless, to be adopted); add it first")
93
+ }
94
+ for (let n of nodes as SceneNode[]) if (n.parent !== node) add(node, n)
95
+ }
96
+ config = { levels: levels.map((l, k) => ({ node: nodes[k]!, size: l.size })), fade }
97
+ }
98
+ node._lod = config
99
+ if (node._scene !== null) node._scene._bindLod(node)
100
+ }
101
+
102
+ /** One level of an instanced LOD: what the population draws at this
103
+ * level, and the projected size (per INSTANCE) below which it hands
104
+ * over - see LodLevel. */
105
+ export type InstancedLodLevel = { geometry: Geometry; material: Material; size: number }
106
+
107
+ export type InstancedLodOptions = InstancedMeshOptions &
108
+ LodOptions & {
109
+ /** Every level draws into the scene's shadow map (setCastShadow on
110
+ * each); default false. */
111
+ castShadow?: boolean
112
+ }
113
+
114
+ /**
115
+ * An instanced LOD: one population whose every instance draws at the
116
+ * level its OWN projected size picks - a forest as three draw entries
117
+ * (mesh, simplified mesh, card) whatever the spread, the near trees full
118
+ * and the far ones cards. Returns the first level's mesh, which is the
119
+ * population: addInstance / `<Instance>` on it, its instance nodes place
120
+ * and pick as on any InstancedMesh, and the other levels are its
121
+ * children at identity, each with a matrix buffer of its own the core
122
+ * stages an instance's record into (`_instances.levels`, nearest first;
123
+ * their per-level style streams take setInstanceStyle together). A pick
124
+ * or pointer event on an instance names that first mesh whatever level
125
+ * the instance is drawn at (the instance is the population's). Records
126
+ * are target-agnostic, so instances pick by the SCENE camera and every
127
+ * view and shadow tile draws that choice; instanced levels switch hard
128
+ * with hysteresis (`fade` is refused). Layers and castShadow set on the
129
+ * population reach every level.
130
+ */
131
+ export function createInstancedLod(levels: InstancedLodLevel[], opts?: InstancedLodOptions): InstancedMesh {
132
+ if (levels.length === 0) throw new Error("createInstancedLod: at least one level")
133
+ let fade = checkSizes(levels, opts?.fade, "createInstancedLod")
134
+ if (fade !== 0) throw new Error("createInstancedLod: instanced levels switch hard (a per-instance fade would need a per-record value); drop fade")
135
+ let meshes = levels.map((l, k) =>
136
+ createInstancedMesh(l.geometry, l.material, { capacity: opts?.capacity, bounds: opts?.bounds, label: opts?.label !== undefined ? opts.label + "-lod" + k : undefined }),
137
+ )
138
+ let population = meshes[0]!
139
+ let shared = population._instances.nodes
140
+ for (let m of meshes) {
141
+ m._instances.nodes = shared
142
+ m._instances.levels = meshes
143
+ }
144
+ for (let m of meshes.slice(1)) add(population, m)
145
+ population._lod = { levels: levels.map(l => ({ node: null, size: l.size })), fade }
146
+ if (opts?.castShadow === true) setCastShadow(population, true)
147
+ return population
148
+ }