@expofp/renderer 0.0.0-experimental.cf063fc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -0
- package/dist/Renderer.d.ts +102 -0
- package/dist/Renderer.js +96 -0
- package/dist/controls/CameraControls.d.ts +108 -0
- package/dist/controls/CameraControls.js +203 -0
- package/dist/controls/Controls.d.ts +53 -0
- package/dist/controls/Controls.js +54 -0
- package/dist/controls/camera-api.d.ts +226 -0
- package/dist/controls/camera-api.js +179 -0
- package/dist/controls/gestures.d.ts +71 -0
- package/dist/controls/gestures.js +98 -0
- package/dist/controls/handlers/DoubleTapZoom.d.ts +23 -0
- package/dist/controls/handlers/DoubleTapZoom.js +82 -0
- package/dist/controls/handlers/Inertia.d.ts +60 -0
- package/dist/controls/handlers/Inertia.js +222 -0
- package/dist/controls/handlers/Pan.d.ts +23 -0
- package/dist/controls/handlers/Pan.js +212 -0
- package/dist/controls/handlers/Pitch.d.ts +19 -0
- package/dist/controls/handlers/Pitch.js +200 -0
- package/dist/controls/handlers/Roll.d.ts +30 -0
- package/dist/controls/handlers/Roll.js +196 -0
- package/dist/controls/handlers/Zoom.d.ts +28 -0
- package/dist/controls/handlers/Zoom.js +34 -0
- package/dist/controls/hooks.d.ts +136 -0
- package/dist/controls/hooks.js +200 -0
- package/dist/controls/index.d.ts +9 -0
- package/dist/controls/index.js +7 -0
- package/dist/controls/zoom-to.d.ts +89 -0
- package/dist/controls/zoom-to.js +91 -0
- package/dist/core/InstanceUniforms.d.ts +108 -0
- package/dist/core/InstanceUniforms.js +298 -0
- package/dist/core/SquareDataTexture.d.ts +277 -0
- package/dist/core/SquareDataTexture.js +476 -0
- package/dist/core/batch/BatchedMesh.d.ts +74 -0
- package/dist/core/batch/BatchedMesh.js +155 -0
- package/dist/core/instanced/FrustumCulling.d.ts +141 -0
- package/dist/core/instanced/FrustumCulling.js +246 -0
- package/dist/core/instanced/InstancedMesh.d.ts +302 -0
- package/dist/core/instanced/InstancedMesh.js +497 -0
- package/dist/core/instanced/Instances.d.ts +119 -0
- package/dist/core/instanced/Instances.js +230 -0
- package/dist/core/instanced/Raycasting.d.ts +118 -0
- package/dist/core/instanced/Raycasting.js +134 -0
- package/dist/core/instanced/instanced-material.d.ts +77 -0
- package/dist/core/instanced/instanced-material.js +228 -0
- package/dist/core/lines/BatchedLines.d.ts +137 -0
- package/dist/core/lines/BatchedLines.js +215 -0
- package/dist/core/lines/FrustumCullingLines.d.ts +44 -0
- package/dist/core/lines/FrustumCullingLines.js +85 -0
- package/dist/core/lines/RaycastingLines.d.ts +45 -0
- package/dist/core/lines/RaycastingLines.js +91 -0
- package/dist/core/lines/lines-material.d.ts +47 -0
- package/dist/core/lines/lines-material.js +124 -0
- package/dist/core/shader-source.d.ts +70 -0
- package/dist/core/shader-source.js +110 -0
- package/dist/core/sprites/BatchedSprites.d.ts +115 -0
- package/dist/core/sprites/BatchedSprites.js +173 -0
- package/dist/core/sprites/RaycastingSprites.d.ts +41 -0
- package/dist/core/sprites/RaycastingSprites.js +71 -0
- package/dist/core/sprites/sprite-material.d.ts +59 -0
- package/dist/core/sprites/sprite-material.js +192 -0
- package/dist/core/text/BatchedText.d.ts +233 -0
- package/dist/core/text/BatchedText.js +473 -0
- package/dist/core/text/MemberTexture.d.ts +90 -0
- package/dist/core/text/MemberTexture.js +289 -0
- package/dist/core/text/Text.d.ts +88 -0
- package/dist/core/text/Text.js +230 -0
- package/dist/core/text/batched-text-material.d.ts +56 -0
- package/dist/core/text/batched-text-material.js +123 -0
- package/dist/core/text/glyph-packing.d.ts +17 -0
- package/dist/core/text/glyph-packing.js +61 -0
- package/dist/core/typed-array.d.ts +16 -0
- package/dist/core/typed-array.js +20 -0
- package/dist/debug/components/DebugPanelProvider.d.ts +20 -0
- package/dist/debug/components/DebugPanelProvider.js +50 -0
- package/dist/debug/components/DebugTools.d.ts +22 -0
- package/dist/debug/components/DebugTools.js +28 -0
- package/dist/debug/components/Gui.d.ts +22 -0
- package/dist/debug/components/Gui.js +42 -0
- package/dist/debug/components/MemoryInfo.d.ts +22 -0
- package/dist/debug/components/MemoryInfo.js +42 -0
- package/dist/debug/components/Panel.d.ts +31 -0
- package/dist/debug/components/Panel.js +104 -0
- package/dist/debug/components/Stats.d.ts +9 -0
- package/dist/debug/components/Stats.js +24 -0
- package/dist/debug/debug-chunk.d.ts +26 -0
- package/dist/debug/debug-chunk.js +32 -0
- package/dist/debug/debug-context.d.ts +41 -0
- package/dist/debug/debug-context.js +59 -0
- package/dist/debug/index.d.ts +41 -0
- package/dist/debug/index.js +43 -0
- package/dist/events/LayoutDriver.d.ts +36 -0
- package/dist/events/LayoutDriver.js +192 -0
- package/dist/events/PointerMissed.d.ts +23 -0
- package/dist/events/PointerMissed.js +58 -0
- package/dist/events/def-culling.d.ts +46 -0
- package/dist/events/def-culling.js +69 -0
- package/dist/events/layout-targets.d.ts +151 -0
- package/dist/events/layout-targets.js +397 -0
- package/dist/events/placement/box.d.ts +59 -0
- package/dist/events/placement/box.js +96 -0
- package/dist/events/placement/grid.d.ts +161 -0
- package/dist/events/placement/grid.js +235 -0
- package/dist/events/placement/index.d.ts +2 -0
- package/dist/events/placement/index.js +1 -0
- package/dist/events/placement/pass.d.ts +70 -0
- package/dist/events/placement/pass.js +617 -0
- package/dist/events/pointer.d.ts +194 -0
- package/dist/events/pointer.js +356 -0
- package/dist/external/ExternalCanvas.d.ts +56 -0
- package/dist/external/ExternalCanvas.js +177 -0
- package/dist/external/external-camera.d.ts +18 -0
- package/dist/external/external-camera.js +103 -0
- package/dist/external/external-events.d.ts +14 -0
- package/dist/external/external-events.js +33 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +14 -0
- package/dist/objects/Background.d.ts +31 -0
- package/dist/objects/Background.js +37 -0
- package/dist/objects/containers/SlotContainer.d.ts +312 -0
- package/dist/objects/containers/SlotContainer.js +311 -0
- package/dist/objects/containers/hooks.d.ts +61 -0
- package/dist/objects/containers/hooks.js +196 -0
- package/dist/objects/containers/index.d.ts +6 -0
- package/dist/objects/containers/index.js +4 -0
- package/dist/objects/containers/keys.d.ts +10 -0
- package/dist/objects/containers/keys.js +18 -0
- package/dist/objects/containers/mesh-writes.d.ts +106 -0
- package/dist/objects/containers/mesh-writes.js +97 -0
- package/dist/objects/containers/props.d.ts +33 -0
- package/dist/objects/containers/props.js +1 -0
- package/dist/objects/images/ImageInstance.d.ts +29 -0
- package/dist/objects/images/ImageInstance.js +152 -0
- package/dist/objects/images/Images.d.ts +49 -0
- package/dist/objects/images/Images.js +394 -0
- package/dist/objects/images/atlas-packer.d.ts +129 -0
- package/dist/objects/images/atlas-packer.js +287 -0
- package/dist/objects/images/image-container.d.ts +74 -0
- package/dist/objects/images/image-container.js +100 -0
- package/dist/objects/images/index.d.ts +2 -0
- package/dist/objects/images/index.js +1 -0
- package/dist/objects/images/pack-sources.d.ts +190 -0
- package/dist/objects/images/pack-sources.js +272 -0
- package/dist/objects/images/texture-budget.d.ts +120 -0
- package/dist/objects/images/texture-budget.js +215 -0
- package/dist/objects/images/worker/atlas-worker.d.ts +56 -0
- package/dist/objects/images/worker/atlas-worker.js +123 -0
- package/dist/objects/images/worker/client.d.ts +62 -0
- package/dist/objects/images/worker/client.js +249 -0
- package/dist/objects/images/worker/index.d.ts +10 -0
- package/dist/objects/images/worker/index.js +1 -0
- package/dist/objects/layer/Layer.d.ts +28 -0
- package/dist/objects/layer/Layer.js +134 -0
- package/dist/objects/layer/index.d.ts +2 -0
- package/dist/objects/layer/index.js +1 -0
- package/dist/objects/layer/layer-context.d.ts +74 -0
- package/dist/objects/layer/layer-context.js +82 -0
- package/dist/objects/layer/mount-scheduler.d.ts +131 -0
- package/dist/objects/layer/mount-scheduler.js +289 -0
- package/dist/objects/layer/visibility-gui.d.ts +19 -0
- package/dist/objects/layer/visibility-gui.js +93 -0
- package/dist/objects/lighting/Lighting.d.ts +92 -0
- package/dist/objects/lighting/Lighting.js +384 -0
- package/dist/objects/lighting/OcclusionPasses.d.ts +23 -0
- package/dist/objects/lighting/OcclusionPasses.js +271 -0
- package/dist/objects/lighting/index.d.ts +11 -0
- package/dist/objects/lighting/index.js +10 -0
- package/dist/objects/lighting/lighting-gui.d.ts +24 -0
- package/dist/objects/lighting/lighting-gui.js +114 -0
- package/dist/objects/lighting/occluder.d.ts +34 -0
- package/dist/objects/lighting/occluder.js +62 -0
- package/dist/objects/lighting/occlusion-targets.d.ts +143 -0
- package/dist/objects/lighting/occlusion-targets.js +276 -0
- package/dist/objects/lines/LineInstance.d.ts +21 -0
- package/dist/objects/lines/LineInstance.js +73 -0
- package/dist/objects/lines/Lines.d.ts +17 -0
- package/dist/objects/lines/Lines.js +31 -0
- package/dist/objects/lines/index.d.ts +2 -0
- package/dist/objects/lines/index.js +1 -0
- package/dist/objects/lines/line-container.d.ts +35 -0
- package/dist/objects/lines/line-container.js +40 -0
- package/dist/objects/materials/ColorMaterial.d.ts +92 -0
- package/dist/objects/materials/ColorMaterial.js +124 -0
- package/dist/objects/materials/LineMaterial.d.ts +44 -0
- package/dist/objects/materials/LineMaterial.js +74 -0
- package/dist/objects/materials/ModelMaterial.d.ts +53 -0
- package/dist/objects/materials/ModelMaterial.js +111 -0
- package/dist/objects/materials/TextureMaterial.d.ts +33 -0
- package/dist/objects/materials/TextureMaterial.js +48 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-pass.d.ts +77 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-pass.js +296 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-patch.d.ts +29 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-patch.js +115 -0
- package/dist/objects/materials/effects/ao/blur-pass.d.ts +61 -0
- package/dist/objects/materials/effects/ao/blur-pass.js +101 -0
- package/dist/objects/materials/effects/ao/index.d.ts +129 -0
- package/dist/objects/materials/effects/ao/index.js +72 -0
- package/dist/objects/materials/effects/color-dim.d.ts +115 -0
- package/dist/objects/materials/effects/color-dim.js +206 -0
- package/dist/objects/materials/effects/contact-shadow.d.ts +100 -0
- package/dist/objects/materials/effects/contact-shadow.js +167 -0
- package/dist/objects/materials/effects/effects-context.d.ts +53 -0
- package/dist/objects/materials/effects/effects-context.js +24 -0
- package/dist/objects/materials/shared.d.ts +24 -0
- package/dist/objects/materials/shared.js +72 -0
- package/dist/objects/models/ModelInstance.d.ts +22 -0
- package/dist/objects/models/ModelInstance.js +33 -0
- package/dist/objects/models/Models.d.ts +22 -0
- package/dist/objects/models/Models.js +186 -0
- package/dist/objects/models/gltf-cache.d.ts +17 -0
- package/dist/objects/models/gltf-cache.js +50 -0
- package/dist/objects/models/index.d.ts +2 -0
- package/dist/objects/models/index.js +1 -0
- package/dist/objects/models/model-asset.d.ts +48 -0
- package/dist/objects/models/model-asset.js +77 -0
- package/dist/objects/models/model-container.d.ts +123 -0
- package/dist/objects/models/model-container.js +207 -0
- package/dist/objects/shapes/ShapeInstance.d.ts +26 -0
- package/dist/objects/shapes/ShapeInstance.js +39 -0
- package/dist/objects/shapes/Shapes.d.ts +21 -0
- package/dist/objects/shapes/Shapes.js +131 -0
- package/dist/objects/shapes/index.d.ts +2 -0
- package/dist/objects/shapes/index.js +1 -0
- package/dist/objects/shapes/shape-container.d.ts +141 -0
- package/dist/objects/shapes/shape-container.js +242 -0
- package/dist/objects/shapes/shape-geometry.d.ts +27 -0
- package/dist/objects/shapes/shape-geometry.js +94 -0
- package/dist/objects/text/TextInstance.d.ts +22 -0
- package/dist/objects/text/TextInstance.js +95 -0
- package/dist/objects/text/Texts.d.ts +30 -0
- package/dist/objects/text/Texts.js +43 -0
- package/dist/objects/text/index.d.ts +2 -0
- package/dist/objects/text/index.js +1 -0
- package/dist/objects/text/text-container.d.ts +121 -0
- package/dist/objects/text/text-container.js +229 -0
- package/dist/objects/text/text-layout.d.ts +92 -0
- package/dist/objects/text/text-layout.js +180 -0
- package/dist/space/Camera.d.ts +103 -0
- package/dist/space/Camera.js +321 -0
- package/dist/space/Scene.d.ts +47 -0
- package/dist/space/Scene.js +127 -0
- package/dist/space/Space.d.ts +325 -0
- package/dist/space/Space.js +359 -0
- package/dist/space/VisibleRect.d.ts +45 -0
- package/dist/space/VisibleRect.js +129 -0
- package/dist/space/camera-handle.d.ts +67 -0
- package/dist/space/camera-handle.js +63 -0
- package/dist/space/camera-kind.d.ts +26 -0
- package/dist/space/camera-kind.js +30 -0
- package/dist/space/canvas-size.d.ts +24 -0
- package/dist/space/canvas-size.js +20 -0
- package/dist/space/clipping.d.ts +69 -0
- package/dist/space/clipping.js +130 -0
- package/dist/space/layout-measure.d.ts +67 -0
- package/dist/space/layout-measure.js +175 -0
- package/dist/space/viewport.d.ts +112 -0
- package/dist/space/viewport.js +67 -0
- package/dist/types/defs.d.ts +234 -0
- package/dist/types/defs.js +1 -0
- package/dist/types/events.d.ts +198 -0
- package/dist/types/events.js +1 -0
- package/dist/types/guards.d.ts +21 -0
- package/dist/types/guards.js +52 -0
- package/dist/update/index.d.ts +3 -0
- package/dist/update/index.js +2 -0
- package/dist/update/slot-writers.d.ts +30 -0
- package/dist/update/slot-writers.js +76 -0
- package/dist/update/store.d.ts +41 -0
- package/dist/update/store.js +104 -0
- package/dist/utils/arrays.d.ts +13 -0
- package/dist/utils/arrays.js +20 -0
- package/dist/utils/colors.d.ts +19 -0
- package/dist/utils/colors.js +27 -0
- package/dist/utils/hot-state.d.ts +41 -0
- package/dist/utils/hot-state.js +37 -0
- package/dist/utils/logging.d.ts +29 -0
- package/dist/utils/logging.js +55 -0
- package/dist/utils/math.d.ts +52 -0
- package/dist/utils/math.js +76 -0
- package/package.json +79 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { type ImageDef, type ImageSource } from '../../types/defs';
|
|
2
|
+
import { type AtlasCanvas, type CreateAtlasCanvas, type SourceRect, type UvRect } from './pack-sources';
|
|
3
|
+
export { type AtlasCanvas, type CreateAtlasCanvas, isResizableSource, type SourceRect, type UvRect, } from './pack-sources';
|
|
4
|
+
/**
|
|
5
|
+
* The pixels an atlas can hold: a canvas, or a bitmap transferred out of one.
|
|
6
|
+
*
|
|
7
|
+
* `AtlasCanvas` stays narrow on purpose. `CreateAtlasCanvas` and `contextOf` both need a type they
|
|
8
|
+
* can open a 2D context on, and an `ImageBitmap` has no context to open.
|
|
9
|
+
*/
|
|
10
|
+
export type AtlasImage = AtlasCanvas | ImageBitmap;
|
|
11
|
+
/** One image, or one tile of a split image, placed in an atlas. */
|
|
12
|
+
export interface PackedImage {
|
|
13
|
+
/**
|
|
14
|
+
* The def that these pixels belong to. Defs whose sources dedupe appear as separate entries
|
|
15
|
+
* that share one region, so the list maps one-to-one onto the instances that an atlas must
|
|
16
|
+
* draw.
|
|
17
|
+
*/
|
|
18
|
+
def: ImageDef;
|
|
19
|
+
/** Where the pixels sit in the atlas. */
|
|
20
|
+
uv: UvRect;
|
|
21
|
+
/**
|
|
22
|
+
* Which part of `def.source` this entry holds. This is undefined when the source packs whole,
|
|
23
|
+
* which is the common case. It is set only on the tiles of a split source, where a consumer
|
|
24
|
+
* needs it to work out the fraction of `def.bounds` that this tile covers.
|
|
25
|
+
*/
|
|
26
|
+
source?: SourceRect;
|
|
27
|
+
/**
|
|
28
|
+
* The intrinsic pixel size of `def.source`. The packer measures this once and carries it on the
|
|
29
|
+
* placement, because a `Blob` source cannot be measured again later. A tile's consumer reads
|
|
30
|
+
* this field to find the fraction of the image the tile covers. The field spares that consumer
|
|
31
|
+
* from decoding the source a second time.
|
|
32
|
+
*/
|
|
33
|
+
size: {
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** One packed atlas: its pixels, and a record of what landed where on it. */
|
|
39
|
+
export interface Atlas {
|
|
40
|
+
/**
|
|
41
|
+
* The atlas's packed pixels: a canvas, or a bitmap a worker transferred out of one. Its
|
|
42
|
+
* `width`/`height` are the atlas's size.
|
|
43
|
+
*/
|
|
44
|
+
image: AtlasImage;
|
|
45
|
+
/** Every def placement on this atlas. */
|
|
46
|
+
images: PackedImage[];
|
|
47
|
+
/**
|
|
48
|
+
* Whether this atlas's pixels can shrink to fit a memory budget. Image and bitmap sources pack
|
|
49
|
+
* into resizable atlases. Canvas sources pack into fixed ones. An atlas never mixes the two
|
|
50
|
+
* kinds, because the code packs them as separate groups. So this one flag applies to every
|
|
51
|
+
* placement on the atlas. It lets a memory budget scale a whole resizable canvas at once,
|
|
52
|
+
* without changing a canvas source's pixels. See {@link isResizableSource}.
|
|
53
|
+
*/
|
|
54
|
+
resizable: boolean;
|
|
55
|
+
}
|
|
56
|
+
/** Options for {@link packAtlases}. */
|
|
57
|
+
export interface AtlasPackerOptions {
|
|
58
|
+
/** Creates each atlas canvas. Defaults to {@link createAtlasCanvas}. */
|
|
59
|
+
createCanvas?: CreateAtlasCanvas;
|
|
60
|
+
/**
|
|
61
|
+
* Abandons the pack. A repack that supersedes a pack still in flight aborts that pack. The
|
|
62
|
+
* worker is told to drop it and goes on running, and a result that still arrives is thrown away
|
|
63
|
+
* instead of mounted.
|
|
64
|
+
*/
|
|
65
|
+
signal?: AbortSignal;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Where a pack draws its atlases.
|
|
69
|
+
*
|
|
70
|
+
* There are two tiers and not three. Both tiers decode with `createImageBitmap`, because every
|
|
71
|
+
* build target has it (the plan's Global Constraints hold the compat data). What changes between
|
|
72
|
+
* the tiers is whether the drawing can leave the main thread. That turns on `OffscreenCanvas`,
|
|
73
|
+
* which Chrome 66 and Safari 15 do not have. The inline tier is a normal path for those targets
|
|
74
|
+
* and not an edge case. It is already around five times cheaper than the code it replaces, because
|
|
75
|
+
* it no longer decodes an `<img>`.
|
|
76
|
+
*/
|
|
77
|
+
export type PackTier =
|
|
78
|
+
/** `Worker` and `OffscreenCanvas` are present, and every source is a `Blob`: pack off-thread. */
|
|
79
|
+
'worker'
|
|
80
|
+
/** Anything else: decode and pack on the main thread. */
|
|
81
|
+
| 'inline';
|
|
82
|
+
/**
|
|
83
|
+
* Packs image defs into atlas canvases and reports where each def's pixels landed.
|
|
84
|
+
*
|
|
85
|
+
* The code first dedupes the defs. Two defs that point at the same URL, or that hold the same
|
|
86
|
+
* object, draw once and share one region. The code then decodes every source that is not drawable
|
|
87
|
+
* yet, which is what makes this function asynchronous: a `Blob` holds bytes and not pixels. The
|
|
88
|
+
* code then hands the decoded sources to {@link packSources}, which does the pixel work. The
|
|
89
|
+
* placements come back addressed by source index, and this function expands them into one entry
|
|
90
|
+
* per def.
|
|
91
|
+
*
|
|
92
|
+
* Packing reads only the sources' pixel dimensions, never `bounds`. So a def that later moves or
|
|
93
|
+
* resizes keeps its region: the quad's matrix scales it, and nothing needs repacking. A change in
|
|
94
|
+
* membership is different. This is a batch call with no incremental path, so an added or removed
|
|
95
|
+
* def needs a fresh call.
|
|
96
|
+
*
|
|
97
|
+
* A worker that fails for any reason other than an abort falls back to the inline tier once. The
|
|
98
|
+
* worker is an optimization, and no host page may cost a layer its images by refusing one.
|
|
99
|
+
* @param defs - The images to pack.
|
|
100
|
+
* @param maxDimension - The largest atlas edge, in pixels. The function floors this to a positive
|
|
101
|
+
* integer.
|
|
102
|
+
* @param options - Overrides for how atlas canvases are made, and the signal that abandons the
|
|
103
|
+
* pack.
|
|
104
|
+
* @returns One entry for each atlas, in packing order: resizable atlases first, then fixed ones.
|
|
105
|
+
*/
|
|
106
|
+
export declare function packAtlases(defs: readonly ImageDef[], maxDimension: number, options?: AtlasPackerOptions): Promise<Atlas[]>;
|
|
107
|
+
/**
|
|
108
|
+
* Picks the higher tier when this environment and this set of sources both allow it.
|
|
109
|
+
*
|
|
110
|
+
* A layer packs in the worker only when **every** source is a `Blob`. One source of any other kind
|
|
111
|
+
* keeps the whole layer on the main thread.
|
|
112
|
+
*
|
|
113
|
+
* The test names what may cross, never what must stay. A test that only excludes the known-bad
|
|
114
|
+
* kinds admits every kind nobody thought of, and this code shipped that mistake once: it asked
|
|
115
|
+
* whether a source was not a DOM canvas, which let an `<img>` through. An `<img>` neither clones
|
|
116
|
+
* nor moves, so `postMessage` threw on it and the layer drew nothing.
|
|
117
|
+
*
|
|
118
|
+
* A `Blob` is also the only source the worker can take without taking it from the def that owns it.
|
|
119
|
+
* Its handle clones, so the def keeps its bytes. Every other kind would have to move, and a moved
|
|
120
|
+
* source is dead on the main thread, so the next repack would find nothing to draw. Those sources
|
|
121
|
+
* lose nothing by packing inline. A canvas cannot cross at all — `transferControlToOffscreen` throws
|
|
122
|
+
* on a canvas that already holds a 2D context, and every floorplan canvas source holds one — and
|
|
123
|
+
* `createImageBitmap(canvas)` costs more than a pack of that canvas on the main thread (this is
|
|
124
|
+
* measured, see the design spec).
|
|
125
|
+
* @param sources - The unique sources that this pack will draw.
|
|
126
|
+
* @returns The tier to run.
|
|
127
|
+
*/
|
|
128
|
+
export declare function selectTier(sources: readonly ImageSource[]): PackTier;
|
|
129
|
+
//# sourceMappingURL=atlas-packer.d.ts.map
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { createLogger } from '../../utils/logging';
|
|
2
|
+
import { createAtlasCanvas, isBlob, packSources, } from './pack-sources';
|
|
3
|
+
import { packInWorker } from './worker';
|
|
4
|
+
const logger = createLogger('atlas-packer');
|
|
5
|
+
/**
|
|
6
|
+
* How many source pixels per square unit of `bounds` a def can carry before code calls it
|
|
7
|
+
* over-sampled. Past this limit, the extra resolution cannot reach the screen at any reasonable
|
|
8
|
+
* zoom. The atlas space and texture memory are then wasted, usually on a full-size logo that
|
|
9
|
+
* stands in for a marker icon.
|
|
10
|
+
*/
|
|
11
|
+
const MAX_AREA_RATIO = 1000;
|
|
12
|
+
export { isResizableSource, } from './pack-sources';
|
|
13
|
+
/**
|
|
14
|
+
* Packs image defs into atlas canvases and reports where each def's pixels landed.
|
|
15
|
+
*
|
|
16
|
+
* The code first dedupes the defs. Two defs that point at the same URL, or that hold the same
|
|
17
|
+
* object, draw once and share one region. The code then decodes every source that is not drawable
|
|
18
|
+
* yet, which is what makes this function asynchronous: a `Blob` holds bytes and not pixels. The
|
|
19
|
+
* code then hands the decoded sources to {@link packSources}, which does the pixel work. The
|
|
20
|
+
* placements come back addressed by source index, and this function expands them into one entry
|
|
21
|
+
* per def.
|
|
22
|
+
*
|
|
23
|
+
* Packing reads only the sources' pixel dimensions, never `bounds`. So a def that later moves or
|
|
24
|
+
* resizes keeps its region: the quad's matrix scales it, and nothing needs repacking. A change in
|
|
25
|
+
* membership is different. This is a batch call with no incremental path, so an added or removed
|
|
26
|
+
* def needs a fresh call.
|
|
27
|
+
*
|
|
28
|
+
* A worker that fails for any reason other than an abort falls back to the inline tier once. The
|
|
29
|
+
* worker is an optimization, and no host page may cost a layer its images by refusing one.
|
|
30
|
+
* @param defs - The images to pack.
|
|
31
|
+
* @param maxDimension - The largest atlas edge, in pixels. The function floors this to a positive
|
|
32
|
+
* integer.
|
|
33
|
+
* @param options - Overrides for how atlas canvases are made, and the signal that abandons the
|
|
34
|
+
* pack.
|
|
35
|
+
* @returns One entry for each atlas, in packing order: resizable atlases first, then fixed ones.
|
|
36
|
+
*/
|
|
37
|
+
export async function packAtlases(defs, maxDimension, options = {}) {
|
|
38
|
+
const startedAt = performance.now();
|
|
39
|
+
const { createCanvas = createAtlasCanvas, signal } = options;
|
|
40
|
+
const entries = collectSources(defs);
|
|
41
|
+
const sources = entries.map((entry) => entry.source);
|
|
42
|
+
let tier = selectTier(sources);
|
|
43
|
+
let packed = null;
|
|
44
|
+
if (tier === 'worker') {
|
|
45
|
+
try {
|
|
46
|
+
packed = await packWorkerTier(entries, sources, maxDimension, signal);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
// An abort is the caller's own decision, and nothing may run after it.
|
|
50
|
+
if (signal?.aborted)
|
|
51
|
+
throw error;
|
|
52
|
+
// Any other failure is the worker alone failing, and the main thread can still do the work.
|
|
53
|
+
// The case that matters is a host page whose `script-src` forbids `blob:` and `data:`:
|
|
54
|
+
// `new Worker()` throws there, this SDK runs on origins it does not control, and without
|
|
55
|
+
// this the layer would stay blank on that site.
|
|
56
|
+
logger.warn('the worker could not pack, packing inline instead %O', error);
|
|
57
|
+
tier = 'inline';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Only if the worker tier did not run, or ran and failed. `??=` does not evaluate its right side
|
|
61
|
+
// when the left side already holds the atlases.
|
|
62
|
+
packed ??= await packInlineTier(entries, sources, maxDimension, createCanvas, signal);
|
|
63
|
+
const elapsed = (performance.now() - startedAt).toFixed(1);
|
|
64
|
+
logger.debug('packed defs: %d, sources: %d, atlases: %d, tier: %s (%sms)', packed.reduce((count, atlas) => count + atlas.images.length, 0), entries.length, packed.length, tier, elapsed);
|
|
65
|
+
return packed;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Picks the higher tier when this environment and this set of sources both allow it.
|
|
69
|
+
*
|
|
70
|
+
* A layer packs in the worker only when **every** source is a `Blob`. One source of any other kind
|
|
71
|
+
* keeps the whole layer on the main thread.
|
|
72
|
+
*
|
|
73
|
+
* The test names what may cross, never what must stay. A test that only excludes the known-bad
|
|
74
|
+
* kinds admits every kind nobody thought of, and this code shipped that mistake once: it asked
|
|
75
|
+
* whether a source was not a DOM canvas, which let an `<img>` through. An `<img>` neither clones
|
|
76
|
+
* nor moves, so `postMessage` threw on it and the layer drew nothing.
|
|
77
|
+
*
|
|
78
|
+
* A `Blob` is also the only source the worker can take without taking it from the def that owns it.
|
|
79
|
+
* Its handle clones, so the def keeps its bytes. Every other kind would have to move, and a moved
|
|
80
|
+
* source is dead on the main thread, so the next repack would find nothing to draw. Those sources
|
|
81
|
+
* lose nothing by packing inline. A canvas cannot cross at all — `transferControlToOffscreen` throws
|
|
82
|
+
* on a canvas that already holds a 2D context, and every floorplan canvas source holds one — and
|
|
83
|
+
* `createImageBitmap(canvas)` costs more than a pack of that canvas on the main thread (this is
|
|
84
|
+
* measured, see the design spec).
|
|
85
|
+
* @param sources - The unique sources that this pack will draw.
|
|
86
|
+
* @returns The tier to run.
|
|
87
|
+
*/
|
|
88
|
+
export function selectTier(sources) {
|
|
89
|
+
const offthread = typeof Worker === 'function' && typeof OffscreenCanvas !== 'undefined' && sources.every(isBlob);
|
|
90
|
+
return offthread ? 'worker' : 'inline';
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Packs in a worker, off the main thread.
|
|
94
|
+
*
|
|
95
|
+
* The worker decodes every source itself and builds its own offscreen canvases, so this tier
|
|
96
|
+
* ignores {@link AtlasPackerOptions.createCanvas}. A test that passes one is testing the inline
|
|
97
|
+
* tier: {@link selectTier} never picks this tier in jsdom, which has neither `Worker` nor
|
|
98
|
+
* `OffscreenCanvas`.
|
|
99
|
+
* @param entries - The unique sources, and the defs that draw each one.
|
|
100
|
+
* @param sources - The unique sources, aligned by index with `entries`.
|
|
101
|
+
* @param maxDimension - The largest atlas edge, in pixels.
|
|
102
|
+
* @param signal - Abandons the pack. The worker is told to drop it and goes on running.
|
|
103
|
+
* @returns One entry for each atlas, in packing order.
|
|
104
|
+
*/
|
|
105
|
+
async function packWorkerTier(entries, sources, maxDimension, signal) {
|
|
106
|
+
// `selectTier` picked this tier only because every source is a `Blob`, which is what a
|
|
107
|
+
// `WorkerSource` is. The compiler cannot follow that, so the cast states it.
|
|
108
|
+
const { atlases, reports } = await packInWorker(sources, maxDimension, {
|
|
109
|
+
signal,
|
|
110
|
+
});
|
|
111
|
+
// `packInWorker` already rejects on abort, so the pack still stands here. The code checks again
|
|
112
|
+
// because an abort raised in the same turn as the reply is a real ordering.
|
|
113
|
+
if (signal?.aborted) {
|
|
114
|
+
// Nothing will mount these atlases, and an `ImageBitmap` holds its pixels outside the
|
|
115
|
+
// JavaScript heap, so close them here.
|
|
116
|
+
for (const atlas of atlases)
|
|
117
|
+
atlas.bitmap.close();
|
|
118
|
+
throw new Error('atlas-packer: the pack was aborted');
|
|
119
|
+
}
|
|
120
|
+
reportSources(entries, reports);
|
|
121
|
+
return atlases.map((atlas) => ({
|
|
122
|
+
image: atlas.bitmap,
|
|
123
|
+
resizable: atlas.resizable,
|
|
124
|
+
images: expandPlacements(atlas.placements, entries, reports),
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Packs on the main thread. This tier decodes the sources here and draws the atlases here.
|
|
129
|
+
* @param entries - The unique sources, and the defs that draw each one.
|
|
130
|
+
* @param sources - The unique sources, aligned by index with `entries`.
|
|
131
|
+
* @param maxDimension - The largest atlas edge, in pixels.
|
|
132
|
+
* @param createCanvas - Makes each atlas canvas.
|
|
133
|
+
* @param signal - Abandons the pack.
|
|
134
|
+
* @returns One entry for each atlas, in packing order.
|
|
135
|
+
*/
|
|
136
|
+
async function packInlineTier(entries, sources, maxDimension, createCanvas, signal) {
|
|
137
|
+
const decoded = await decodeSources(sources);
|
|
138
|
+
// The caller abandoned the pack while the decodes were in flight, so nothing here is wanted any
|
|
139
|
+
// more. The code reads `signal.aborted` and not `throwIfAborted()`, which Chrome 66 and Safari
|
|
140
|
+
// 15 do not have.
|
|
141
|
+
if (signal?.aborted) {
|
|
142
|
+
closeDecoded(sources, decoded);
|
|
143
|
+
throw new Error('atlas-packer: the pack was aborted');
|
|
144
|
+
}
|
|
145
|
+
const { atlases, reports } = packSources(decoded, maxDimension, createCanvas);
|
|
146
|
+
// The atlases hold the pixels now, so the decoded bitmaps are dead.
|
|
147
|
+
closeDecoded(sources, decoded);
|
|
148
|
+
reportSources(entries, reports);
|
|
149
|
+
return atlases.map((atlas) => ({
|
|
150
|
+
image: atlas.canvas,
|
|
151
|
+
resizable: atlas.resizable,
|
|
152
|
+
images: expandPlacements(atlas.placements, entries, reports),
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Groups defs by the source they draw, so the pack draws identical images once.
|
|
157
|
+
*
|
|
158
|
+
* Identity is the source object itself. Two defs share a region only when they hold the exact same
|
|
159
|
+
* object. That is why floorplan hands two booths with one logo a single `Blob`, instead of two
|
|
160
|
+
* Blobs that hold the same bytes.
|
|
161
|
+
* @param defs - The defs to group.
|
|
162
|
+
* @returns One entry per unique source, in first-seen order.
|
|
163
|
+
*/
|
|
164
|
+
function collectSources(defs) {
|
|
165
|
+
const sources = new Map();
|
|
166
|
+
for (const def of defs) {
|
|
167
|
+
let entry = sources.get(def.source);
|
|
168
|
+
if (!entry) {
|
|
169
|
+
entry = { source: def.source, defs: [], bounds: { width: 0, height: 0 } };
|
|
170
|
+
sources.set(def.source, entry);
|
|
171
|
+
}
|
|
172
|
+
entry.defs.push(def);
|
|
173
|
+
// The code copies this size instead of keeping a reference to it. `Rect` exposes an in-place
|
|
174
|
+
// `set` method. A kept reference lets a later bounds change rewrite what this function
|
|
175
|
+
// already measured.
|
|
176
|
+
const { width, height } = def.bounds.size;
|
|
177
|
+
if (width * height > entry.bounds.width * entry.bounds.height) {
|
|
178
|
+
entry.bounds = { width, height };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return [...sources.values()];
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Turns the packer's index-addressed placements into one entry per def. Deduped defs all share
|
|
185
|
+
* the one region, which is the whole point of deduping them.
|
|
186
|
+
* @param placements - One atlas's placements.
|
|
187
|
+
* @param entries - The unique sources, aligned by index with what the code gave the packer.
|
|
188
|
+
* @param reports - Per-source measurements, aligned by index with `entries`. Supplies each
|
|
189
|
+
* placement's `size`.
|
|
190
|
+
* @returns One packed image for each def at each placement.
|
|
191
|
+
*/
|
|
192
|
+
function expandPlacements(placements, entries, reports) {
|
|
193
|
+
const images = [];
|
|
194
|
+
for (const { index, uv, source } of placements) {
|
|
195
|
+
const { width, height } = reports[index];
|
|
196
|
+
const size = { width, height };
|
|
197
|
+
for (const def of entries[index].defs) {
|
|
198
|
+
images.push(source ? { def, uv, source, size } : { def, uv, size });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return images;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Reports what the pack was unable to use. This covers sources with no pixels, and sources that
|
|
205
|
+
* carry far more resolution than their bounds can ever show. The function warns once for each
|
|
206
|
+
* source, not once for each def, because deduped defs all share the one texture region.
|
|
207
|
+
* @param entries - The unique sources, aligned by index with `reports`.
|
|
208
|
+
* @param reports - What the packer measured and placed.
|
|
209
|
+
*/
|
|
210
|
+
function reportSources(entries, reports) {
|
|
211
|
+
for (let index = 0; index < entries.length; index++) {
|
|
212
|
+
const entry = entries[index];
|
|
213
|
+
const { width, height, tiles } = reports[index];
|
|
214
|
+
if (tiles === 0) {
|
|
215
|
+
logger.warn('skipping source with no pixels (%dx%d) %O', width, height, entry.source);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (tiles > 1) {
|
|
219
|
+
logger.warn('source %dx%d exceeds the atlas limit, split into %d tiles', width, height, tiles);
|
|
220
|
+
}
|
|
221
|
+
const boundsArea = entry.bounds.width * entry.bounds.height;
|
|
222
|
+
// Degenerate bounds divide out to Infinity. That triggers a warning for every such def, but
|
|
223
|
+
// the warning says nothing about resolution. It only points to a bug in the bounds, and this
|
|
224
|
+
// function does not report that kind of bug.
|
|
225
|
+
if (boundsArea <= 0)
|
|
226
|
+
continue;
|
|
227
|
+
const ratio = (width * height) / boundsArea;
|
|
228
|
+
if (ratio <= MAX_AREA_RATIO)
|
|
229
|
+
continue;
|
|
230
|
+
logger.warn('over-sampled source: %dx%d px drawn into %sx%s units — %d px per unit², limit %d', width, height,
|
|
231
|
+
// At most two decimals, trailing zeros dropped: 4 → 4, 4.1 → 4.1, 4.2999 → 4.3.
|
|
232
|
+
Number(entry.bounds.width.toFixed(2)), Number(entry.bounds.height.toFixed(2)), Math.round(ratio), MAX_AREA_RATIO);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Decodes every source that is not drawable yet, and leaves the drawable ones as they are.
|
|
237
|
+
*
|
|
238
|
+
* The code starts every decode before it waits for any one of them, so the browser decoder
|
|
239
|
+
* overlaps them. Each decode gives back a bitmap. The packer draws that bitmap, and the caller
|
|
240
|
+
* closes it.
|
|
241
|
+
*
|
|
242
|
+
* One source that does not decode must not take the layer with it. A drawing can carry an
|
|
243
|
+
* `<image>` whose bytes are not an image at all. A `Promise.all` over the decodes rejects the whole
|
|
244
|
+
* pack, so a plan with one bad graphic draws none of its graphics. A failed decode becomes `null`
|
|
245
|
+
* instead. {@link packSources} already treats `null` as a source with no pixels: it skips that
|
|
246
|
+
* source, and `reportSources` warns about it by name.
|
|
247
|
+
* @param sources - The unique sources to decode.
|
|
248
|
+
* @returns The decoded sources, aligned by index with the input. An entry is `null` where the bytes
|
|
249
|
+
* did not decode.
|
|
250
|
+
*/
|
|
251
|
+
async function decodeSources(sources) {
|
|
252
|
+
return Promise.all(sources.map((source) => decodeSource(source).catch(() => null)));
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Frees the bitmaps that this pack decoded.
|
|
256
|
+
*
|
|
257
|
+
* An `ImageBitmap` holds its pixels outside the JavaScript heap, and garbage collection does not
|
|
258
|
+
* reclaim that memory promptly. A pack of a thousand logos would otherwise sit on a thousand
|
|
259
|
+
* decoded images until it did.
|
|
260
|
+
*
|
|
261
|
+
* The pack closes only what it decoded itself, which is every source that arrived as a `Blob`. A
|
|
262
|
+
* def is free to hold an `ImageBitmap` of its own, and {@link decodeSource} hands such a source
|
|
263
|
+
* straight back. Those pixels belong to the def: the next repack draws them again, and a pack that
|
|
264
|
+
* closed them would empty the layer instead.
|
|
265
|
+
* @param sources - The unique sources, aligned by index with `decoded`.
|
|
266
|
+
* @param decoded - What each source decoded into.
|
|
267
|
+
*/
|
|
268
|
+
function closeDecoded(sources, decoded) {
|
|
269
|
+
for (let index = 0; index < decoded.length; index++) {
|
|
270
|
+
const source = decoded[index];
|
|
271
|
+
if (source && isBlob(sources[index]) && 'close' in source)
|
|
272
|
+
source.close();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Decodes one source. Anything that is not a `Blob` is drawable already and comes back as it went
|
|
277
|
+
* in. A `Blob` goes through `createImageBitmap`, which every build target has.
|
|
278
|
+
* @param source - The source to decode.
|
|
279
|
+
* @returns A source that the packer can draw.
|
|
280
|
+
*/
|
|
281
|
+
async function decodeSource(source) {
|
|
282
|
+
// A `Blob` is the only member of the union with no size and no draw. Everything else is drawable
|
|
283
|
+
// already.
|
|
284
|
+
if (!isBlob(source))
|
|
285
|
+
return source;
|
|
286
|
+
return createImageBitmap(source);
|
|
287
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type InstancedMeshImpl, type InstancedMeshImplParams } from '../../core/instanced/InstancedMesh';
|
|
2
|
+
import { type ImageDef } from '../../types/defs';
|
|
3
|
+
import { SlotContainer } from '../containers';
|
|
4
|
+
import { type PackedImage } from './atlas-packer';
|
|
5
|
+
/**
|
|
6
|
+
* The per-instance uniform each atlas mesh reads its texture region from. The container owns the
|
|
7
|
+
* name: it writes each slot's region here (`setUniformAt`) and hands it to the mesh's
|
|
8
|
+
* `TextureMaterial` as the `uvOffset` prop, so the material patches its shader to sample the region
|
|
9
|
+
* without knowing the name itself. `xy` is the region's origin in uv space and `zw` its size, so
|
|
10
|
+
* `uv * zw + xy` maps the quad's unit uv onto the region.
|
|
11
|
+
*/
|
|
12
|
+
export declare const UV_OFFSET_UNIFORM = "uvOffset";
|
|
13
|
+
/**
|
|
14
|
+
* The per-instance uniform each atlas mesh reads its opacity from. The container owns the name and
|
|
15
|
+
* hands it to the mesh's `TextureMaterial` as the `opacityUniform` prop, so the material patches its shader
|
|
16
|
+
* without knowing the name itself.
|
|
17
|
+
*/
|
|
18
|
+
export declare const OPACITY_UNIFORM = "instanceOpacity";
|
|
19
|
+
/**
|
|
20
|
+
* The constructor tuple R3F builds one atlas's mesh from, passed as `<instancedMesh args={…}>`.
|
|
21
|
+
* R3F diffs `args` element-wise and rebuilds the object when any element changes, so this is
|
|
22
|
+
* snapshotted once and never mutated.
|
|
23
|
+
*
|
|
24
|
+
* Only the options: `InstancedMeshImpl` takes no geometry or material, seeding throwaways that the
|
|
25
|
+
* element's `<planeGeometry>` and `<TextureMaterial>` children replace (R3F creates, attaches, and
|
|
26
|
+
* disposes those).
|
|
27
|
+
*/
|
|
28
|
+
export type ImageMeshArgs = [params: InstancedMeshImplParams];
|
|
29
|
+
/**
|
|
30
|
+
* One atlas's mesh bookkeeping.
|
|
31
|
+
*
|
|
32
|
+
* Simpler than a shape container, and deliberately so: an atlas draws one shared quad, so there is
|
|
33
|
+
* no geometry cache and no geometry-id juggling, and `InstancedMeshImpl` grows its own buffers, so
|
|
34
|
+
* there is no capacity pre-scan — the mesh is merely sized to the atlas's packed-image count and
|
|
35
|
+
* left to resize itself if a claim ever overflows it. What remains is a slot per packed image and
|
|
36
|
+
* the uv region each one samples, written once at claim since a def's source never changes.
|
|
37
|
+
*
|
|
38
|
+
* Paint order is incremental, like the shape container's, because packing is order-independent: a
|
|
39
|
+
* reorder of the same images keeps this container and only re-labels its slots, never repacking. A
|
|
40
|
+
* change to the *set* of images is the harder case and still repacks into a fresh container —
|
|
41
|
+
* packing has no incremental path for that — but a pure reorder does not.
|
|
42
|
+
*/
|
|
43
|
+
export declare class ImageContainer extends SlotContainer<PackedImage, InstancedMeshImpl, ImageDef> {
|
|
44
|
+
/** The constructor args R3F builds the mesh from; a frozen snapshot, never mutated. */
|
|
45
|
+
readonly args: ImageMeshArgs;
|
|
46
|
+
/**
|
|
47
|
+
* Builds one atlas's bookkeeping. The mesh, its quad, and its material are all R3F's — the atlas
|
|
48
|
+
* component renders `<instancedMesh args={container.args}>` with the geometry and material as
|
|
49
|
+
* children, and hands the mesh back through {@link SlotContainer.bind} — so R3F owns their
|
|
50
|
+
* lifetimes, including disposal on unmount and survival across StrictMode's simulated remount.
|
|
51
|
+
* @param capacity - How many instances to size the mesh for. This is the atlas's whole placement
|
|
52
|
+
* count, not one kind's share of it, so moving a def between the two meshes never resizes a
|
|
53
|
+
* buffer. The mesh grows past it on demand in any case.
|
|
54
|
+
*/
|
|
55
|
+
constructor(capacity: number);
|
|
56
|
+
/**
|
|
57
|
+
* Whether the bound mesh draws its members facing the camera.
|
|
58
|
+
*
|
|
59
|
+
* Read off the mesh rather than stored, so one field cannot disagree with another. `screenSpace` is
|
|
60
|
+
* an ordinary def field, so flipping it re-renders nothing and only an instance can notice that it
|
|
61
|
+
* is drawing from the wrong mesh.
|
|
62
|
+
* @returns Whether this container holds a sprite batch.
|
|
63
|
+
*/
|
|
64
|
+
get facesCamera(): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Writes a def's opacity into its slot, clamped, treating an absent value as fully opaque.
|
|
67
|
+
* @param slot - The instance's slot.
|
|
68
|
+
* @param opacity - The def's opacity, or `undefined` for the default.
|
|
69
|
+
*/
|
|
70
|
+
setOpacityAt(slot: number, opacity: number | undefined): void;
|
|
71
|
+
protected addInstance(packed: PackedImage): number;
|
|
72
|
+
protected removeInstance(slot: number): void;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=image-container.d.ts.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Vector4 } from 'three';
|
|
2
|
+
import { isBatchedSprites } from '../../core/sprites/BatchedSprites';
|
|
3
|
+
import { SlotContainer } from '../containers';
|
|
4
|
+
import { DIM_SCHEMA } from '../materials/effects/color-dim';
|
|
5
|
+
/**
|
|
6
|
+
* The per-instance uniform each atlas mesh reads its texture region from. The container owns the
|
|
7
|
+
* name: it writes each slot's region here (`setUniformAt`) and hands it to the mesh's
|
|
8
|
+
* `TextureMaterial` as the `uvOffset` prop, so the material patches its shader to sample the region
|
|
9
|
+
* without knowing the name itself. `xy` is the region's origin in uv space and `zw` its size, so
|
|
10
|
+
* `uv * zw + xy` maps the quad's unit uv onto the region.
|
|
11
|
+
*/
|
|
12
|
+
export const UV_OFFSET_UNIFORM = 'uvOffset';
|
|
13
|
+
/**
|
|
14
|
+
* The per-instance uniform each atlas mesh reads its opacity from. The container owns the name and
|
|
15
|
+
* hands it to the mesh's `TextureMaterial` as the `opacityUniform` prop, so the material patches its shader
|
|
16
|
+
* without knowing the name itself.
|
|
17
|
+
*/
|
|
18
|
+
export const OPACITY_UNIFORM = 'instanceOpacity';
|
|
19
|
+
/**
|
|
20
|
+
* One atlas's mesh bookkeeping.
|
|
21
|
+
*
|
|
22
|
+
* Simpler than a shape container, and deliberately so: an atlas draws one shared quad, so there is
|
|
23
|
+
* no geometry cache and no geometry-id juggling, and `InstancedMeshImpl` grows its own buffers, so
|
|
24
|
+
* there is no capacity pre-scan — the mesh is merely sized to the atlas's packed-image count and
|
|
25
|
+
* left to resize itself if a claim ever overflows it. What remains is a slot per packed image and
|
|
26
|
+
* the uv region each one samples, written once at claim since a def's source never changes.
|
|
27
|
+
*
|
|
28
|
+
* Paint order is incremental, like the shape container's, because packing is order-independent: a
|
|
29
|
+
* reorder of the same images keeps this container and only re-labels its slots, never repacking. A
|
|
30
|
+
* change to the *set* of images is the harder case and still repacks into a fresh container —
|
|
31
|
+
* packing has no incremental path for that — but a pure reorder does not.
|
|
32
|
+
*/
|
|
33
|
+
export class ImageContainer extends SlotContainer {
|
|
34
|
+
/** The constructor args R3F builds the mesh from; a frozen snapshot, never mutated. */
|
|
35
|
+
args;
|
|
36
|
+
/**
|
|
37
|
+
* Builds one atlas's bookkeeping. The mesh, its quad, and its material are all R3F's — the atlas
|
|
38
|
+
* component renders `<instancedMesh args={container.args}>` with the geometry and material as
|
|
39
|
+
* children, and hands the mesh back through {@link SlotContainer.bind} — so R3F owns their
|
|
40
|
+
* lifetimes, including disposal on unmount and survival across StrictMode's simulated remount.
|
|
41
|
+
* @param capacity - How many instances to size the mesh for. This is the atlas's whole placement
|
|
42
|
+
* count, not one kind's share of it, so moving a def between the two meshes never resizes a
|
|
43
|
+
* buffer. The mesh grows past it on demand in any case.
|
|
44
|
+
*/
|
|
45
|
+
constructor(capacity) {
|
|
46
|
+
super({ schema: IMAGE_SCHEMA });
|
|
47
|
+
this.args = [{ capacity: Math.max(1, capacity) }];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Whether the bound mesh draws its members facing the camera.
|
|
51
|
+
*
|
|
52
|
+
* Read off the mesh rather than stored, so one field cannot disagree with another. `screenSpace` is
|
|
53
|
+
* an ordinary def field, so flipping it re-renders nothing and only an instance can notice that it
|
|
54
|
+
* is drawing from the wrong mesh.
|
|
55
|
+
* @returns Whether this container holds a sprite batch.
|
|
56
|
+
*/
|
|
57
|
+
get facesCamera() {
|
|
58
|
+
return isBatchedSprites(this.mesh);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Writes a def's opacity into its slot, clamped, treating an absent value as fully opaque.
|
|
62
|
+
* @param slot - The instance's slot.
|
|
63
|
+
* @param opacity - The def's opacity, or `undefined` for the default.
|
|
64
|
+
*/
|
|
65
|
+
setOpacityAt(slot, opacity) {
|
|
66
|
+
const value = opacity === undefined ? 1 : Math.min(Math.max(opacity, 0), 1);
|
|
67
|
+
this.mesh.setUniformAt(slot, OPACITY_UNIFORM, value);
|
|
68
|
+
}
|
|
69
|
+
addInstance(packed) {
|
|
70
|
+
// `addInstances` grows the buffers itself if the atlas ever overflows its sized capacity, so
|
|
71
|
+
// there is nothing to pre-scan; the id is captured from the creation callback.
|
|
72
|
+
let slot = -1;
|
|
73
|
+
this.mesh.addInstances(1, (id) => {
|
|
74
|
+
slot = id;
|
|
75
|
+
});
|
|
76
|
+
// The uv region is fixed for the instance's life — a def's source never changes, and a set
|
|
77
|
+
// change repacks into a fresh container — so it is written once, here, rather than on every
|
|
78
|
+
// sync.
|
|
79
|
+
const { u0, v0, u1, v1 } = packed.uv;
|
|
80
|
+
this.mesh.setUniformAt(slot, UV_OFFSET_UNIFORM, _uvOffset.set(u0, v0, u1 - u0, v1 - v0));
|
|
81
|
+
return slot;
|
|
82
|
+
}
|
|
83
|
+
removeInstance(slot) {
|
|
84
|
+
this.mesh.removeInstances(slot);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The schema the mesh declares (via `addUniforms`) so it can hold a {@link UV_OFFSET_UNIFORM}, a
|
|
89
|
+
* {@link OPACITY_UNIFORM} and a dim per slot. The opacity float keeps the schema at two texels: a flat
|
|
90
|
+
* mesh is `vec4` + `float` + `float` = 6 floats, and a sprite mesh's own `anchor` brings it to 8.
|
|
91
|
+
*
|
|
92
|
+
* All in one call although they come from different places: `InstanceUniforms.add` merges and
|
|
93
|
+
* rebuilds the backing texture, so declaring them separately would build it twice per bind.
|
|
94
|
+
*/
|
|
95
|
+
const IMAGE_SCHEMA = {
|
|
96
|
+
vertex: { [UV_OFFSET_UNIFORM]: 'vec4', [OPACITY_UNIFORM]: 'float', ...DIM_SCHEMA.vertex },
|
|
97
|
+
};
|
|
98
|
+
// Scratch for the one uv-region upload a claim performs, filled and read within a single synchronous
|
|
99
|
+
// statement.
|
|
100
|
+
const _uvOffset = new Vector4();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Images } from './Images';
|