@thatopen/components 3.4.2 → 3.4.4

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/dist/index.d.ts CHANGED
@@ -1417,6 +1417,28 @@ export declare class Clipper extends Component implements Createable, Disposable
1417
1417
  * to the arrow gizmo as you zoom in/out. Default is true.
1418
1418
  */
1419
1419
  autoScalePlanes: boolean;
1420
+ /**
1421
+ * When true, planes created by this Clipper are flagged
1422
+ * `isLocal: true` so they stay out of `renderer.three.clippingPlanes`
1423
+ * (the WebGL-level global list). All clipping happens via per-
1424
+ * material `material.clippingPlanes`, which `Clipper.updateMaterialsAndPlanes`
1425
+ * already populates from the renderer's component-side array. The
1426
+ * global list still works for non-Clipper code that pushes its own
1427
+ * planes via `setPlane(active, plane, /*isLocal*\/ false)`.
1428
+ *
1429
+ * Why this matters: with planes only on materials (not global),
1430
+ * consumers like {@link ClipStyler} can give their section fill /
1431
+ * line meshes a *different* `material.clippingPlanes` list — one
1432
+ * that excludes the section's own plane — so the cut geometry
1433
+ * sits exactly on the cut without being discarded by it. The old
1434
+ * 1cm world-space offset workaround is no longer needed and the
1435
+ * fill stays aligned at every camera angle (issue #733).
1436
+ *
1437
+ * Default `false` to preserve existing behavior. Set this once
1438
+ * before creating planes; toggling at runtime won't reclassify
1439
+ * planes already on the renderer.
1440
+ */
1441
+ localClippingPlanes: boolean;
1420
1442
  /**
1421
1443
  * The type of clipping plane to be created.
1422
1444
  * Default is {@link SimplePlane}.
@@ -1434,7 +1456,7 @@ export declare class Clipper extends Component implements Createable, Disposable
1434
1456
  private _size;
1435
1457
  private _enabled;
1436
1458
  private _visible;
1437
- readonly onStateChanged: Event_2<("material" | "enabled" | "size" | "visibility")[]>;
1459
+ readonly onStateChanged: Event_2<("enabled" | "size" | "visibility" | "material")[]>;
1438
1460
  /** {@link Component.enabled} */
1439
1461
  get enabled(): boolean;
1440
1462
  /** {@link Component.enabled} */
@@ -1453,6 +1475,21 @@ export declare class Clipper extends Component implements Createable, Disposable
1453
1475
  set size(size: number);
1454
1476
  constructor(components: Components);
1455
1477
  private setEvents;
1478
+ private subscribeToFragmentMaterials;
1479
+ /**
1480
+ * Assign `material.clippingPlanes` to the renderer's component-
1481
+ * side `clippingPlanes` array — the only place local planes live
1482
+ * once `localClippingPlanes = true`. In legacy global-clipping
1483
+ * mode this is a no-op: the renderer's global list handles
1484
+ * fragments clipping and assigning per-material would just be
1485
+ * redundant.
1486
+ *
1487
+ * Applied to every fragment material, including LOD line
1488
+ * materials — they need the planes too, otherwise far-LOD line
1489
+ * geometry stays visible past the cut while the shell geometry
1490
+ * clips correctly.
1491
+ */
1492
+ private applyClippingToFragmentMaterial;
1456
1493
  /** {@link Disposable.dispose} */
1457
1494
  dispose(): void;
1458
1495
  /** {@link Createable.create} */
@@ -1598,7 +1635,7 @@ export declare class Components implements Disposable_2 {
1598
1635
  * Default value is false.
1599
1636
  */
1600
1637
  enabled: boolean;
1601
- private _clock;
1638
+ private _timer;
1602
1639
  /**
1603
1640
  * Event that triggers the Components instance is initialized.
1604
1641
  *
@@ -2074,7 +2111,7 @@ declare class DistanceRenderer {
2074
2111
  /**
2075
2112
  * The scene where the distance is computed.
2076
2113
  */
2077
- scene: THREE.Scene;
2114
+ scene: THREE.Scene<THREE.Object3DEventMap>;
2078
2115
  /**
2079
2116
  * The camera used to compute the distance.
2080
2117
  */
@@ -2868,125 +2905,303 @@ export declare interface ExternalDocumentReference extends DocumentReference {
2868
2905
  }
2869
2906
 
2870
2907
  /**
2871
- * A fast model picker that uses color coding to identify fragment models under the mouse cursor. This is much faster than raycasting for simple model identification.
2908
+ * GPU-readback picker that identifies what's under a screen position
2909
+ * without going through the worker raycast. Two granularities, same
2910
+ * render pass:
2911
+ *
2912
+ * - {@link getModelAt} returns the model id under the cursor.
2913
+ * - {@link getItemAt} returns the item itself (`modelId` + `localId`).
2914
+ *
2915
+ * The fragments tile geometry already carries a per-vertex `id`
2916
+ * attribute (the internal item id). We render the BIM scene to an
2917
+ * offscreen target with a shader that packs `(modelByte, itemId)` into
2918
+ * RGBA, then `readPixels` at the cursor and decode. One render plus
2919
+ * one 4-byte readback per call. No worker round-trip for the model
2920
+ * lookup; the first time we resolve a given `itemId` to a `localId`
2921
+ * we ask the worker, and afterwards that lookup is cached.
2922
+ *
2923
+ * Encoding (one pixel):
2924
+ * R = modelByte (1..254, 0 = void / unwritten)
2925
+ * G = (itemId >> 16) & 0xff
2926
+ * B = (itemId >> 8) & 0xff
2927
+ * A = (itemId) & 0xff
2928
+ *
2929
+ * `modelByte` is assigned per-pick from a `byteToModelId` map; the
2930
+ * picker re-allocates bytes each call so the assignment is stable for
2931
+ * the duration of the pick but doesn't grow over the session.
2872
2932
  */
2873
2933
  export declare class FastModelPicker implements Disposable_2 {
2874
2934
  /** {@link Component.enabled} */
2875
2935
  enabled: boolean;
2876
- /** The components instance to which this FastModelPicker belongs. */
2936
+ /** Components instance this picker belongs to. */
2877
2937
  components: Components;
2878
2938
  /** {@link Disposable.onDisposed} */
2879
2939
  readonly onDisposed: Event_2<unknown>;
2880
- /** The position of the mouse in the screen. */
2940
+ /** Position helper bound to the world's canvas. */
2881
2941
  readonly mouse: Mouse;
2882
- /**
2883
- * A reference to the world instance to which this FastModelPicker belongs.
2884
- * This is used to access the camera and scene.
2885
- */
2942
+ /** World this picker renders against. */
2886
2943
  world: World;
2887
2944
  /**
2888
- * Whether debug mode is enabled. When enabled, shows the color-coded canvas.
2945
+ * When `true`, mirrors the id render to a debug canvas pinned in the
2946
+ * top-right corner. Each model shows up as a near-uniform red band
2947
+ * (the model byte) modulated by varying greens / blues from the
2948
+ * per-item id encoding.
2889
2949
  */
2890
2950
  debugMode: boolean;
2891
2951
  /**
2892
- * Map from color (as RGB number) to model ID.
2893
- * Color is encoded as: (r << 16) | (g << 8) | b
2952
+ * Maximum models the picker can disambiguate in a single pick. Capped
2953
+ * by the 8-bit `modelByte`. In practice we never approach this.
2894
2954
  */
2895
- private colorToModelId;
2955
+ static readonly MAX_MODELS = 254;
2956
+ private _renderTarget?;
2957
+ private _renderTargetSize;
2958
+ private _debugCanvas?;
2959
+ private _debugContainer?;
2896
2960
  /**
2897
- * Map from model ID to color.
2961
+ * Single shared shader for the id pass. The same program runs across
2962
+ * every model; we flip the `modelByte` uniform between per-model
2963
+ * render passes inside one pick to disambiguate which model owns a
2964
+ * pixel.
2898
2965
  */
2899
- private modelIdToColor;
2966
+ private _idMaterial;
2900
2967
  /**
2901
- * Render target for the color-coded scene.
2968
+ * Depth-encoding shader used by {@link getPointAt}. Written via
2969
+ * `scene.overrideMaterial` for one render of the BIM scene, packs
2970
+ * `gl_FragCoord.z` into the four bytes of the color attachment using
2971
+ * three's `packing` chunk. The packed value is robust to round-trip
2972
+ * through an `UNSIGNED_BYTE` target.
2902
2973
  */
2903
- private renderTarget?;
2974
+ private _depthMaterial;
2904
2975
  /**
2905
- * Size of the render target (stored separately since getSize doesn't exist).
2976
+ * World-space normal-encoding shader used by {@link getNormalAt}.
2977
+ * Writes `(normal * 0.5 + 0.5)` into RGB, alpha = 1. Decode side maps
2978
+ * `(rgb * 2 - 1)` and renormalizes. Naive packing wastes alpha and
2979
+ * gives ~1° precision per axis, which is plenty for surface
2980
+ * alignment, orbit-around-clicked-point, and snapping. Octahedral
2981
+ * packing would buy us another bit per axis but isn't worth the
2982
+ * complexity until a consumer actually needs sub-degree accuracy.
2906
2983
  */
2907
- private renderTargetSize;
2984
+ private _normalMaterial;
2908
2985
  /**
2909
- * Debug canvas element (shown when debugMode is true).
2986
+ * Cached original materials for the swap-render-restore pass.
2987
+ * Populated in {@link applyIdMaterial}, drained in
2988
+ * {@link restoreOriginalMaterials} after the render.
2910
2989
  */
2911
- private debugCanvas?;
2990
+ private _originalMaterials;
2912
2991
  /**
2913
- * Debug container element.
2992
+ * LOD line meshes don't carry the per-vertex `id` attribute and would
2993
+ * write whatever color their normal shader produces into the id
2994
+ * buffer, faking hits. Hidden for the duration of the id render and
2995
+ * restored after.
2914
2996
  */
2915
- private debugContainer?;
2916
- /**
2917
- * Material used for color-coding models.
2918
- */
2919
- private colorMaterials;
2920
- /**
2921
- * Original materials cache (to restore after picking).
2922
- */
2923
- private originalMaterials;
2924
- private originalLodColors;
2925
- /**
2926
- * Whether colors need to be reassigned (when models change).
2927
- */
2928
- private colorsNeedUpdate;
2997
+ private _hiddenLods;
2929
2998
  constructor(components: Components, world: World);
2930
2999
  /**
2931
- * Sets up listeners for fragment model changes.
2932
- */
2933
- private setupFragmentListeners;
2934
- /**
2935
- * Sets up the render target for color-coded picking.
2936
- */
2937
- private setupRenderTarget;
2938
- /**
2939
- * Sets up the debug canvas for visualization.
2940
- */
2941
- private setupDebugCanvas;
2942
- /**
2943
- * Generates a deterministic color for a model based on its ID.
2944
- * This ensures the same model always gets the same color.
2945
- */
2946
- private generateColorForModel;
2947
- /**
2948
- * Converts a color to a numeric ID.
2949
- */
2950
- private colorToId;
2951
- /**
2952
- * Assigns unique colors to all fragment models.
2953
- * Colors are deterministic based on model ID, so the same model always gets the same color.
2954
- */
2955
- private assignColors;
2956
- /**
2957
- * Applies color materials to fragment models.
3000
+ * Returns the model id under the given screen position, or `null` if
3001
+ * the cursor is over empty space.
3002
+ *
3003
+ * Cheaper than {@link getItemAt} because we don't resolve the
3004
+ * `localId`; we just read the model byte from the same render.
3005
+ *
3006
+ * @param position - Normalized device coords. Defaults to the
3007
+ * picker's last known mouse position.
2958
3008
  */
2959
- private applyColorMaterials;
3009
+ getModelAt(position?: THREE.Vector2): Promise<string | null>;
2960
3010
  /**
2961
- * Restores original materials to fragment models.
3011
+ * Returns `{ modelId, localId }` for the item under the given screen
3012
+ * position, or `null` if the cursor is over empty space.
3013
+ *
3014
+ * Pure main-thread resolution: fragments now stores the user-facing
3015
+ * `localId` directly in each tile's per-vertex `id` attribute, so
3016
+ * the encoded RGBA pixel decodes straight to the localId. No worker
3017
+ * round-trip, no cache.
3018
+ *
3019
+ * @param position - Normalized device coords. Defaults to the
3020
+ * picker's last known mouse position.
2962
3021
  */
2963
- private restoreOriginalMaterials;
3022
+ getItemAt(position?: THREE.Vector2): Promise<{
3023
+ modelId: string;
3024
+ localId: number;
3025
+ itemId: number;
3026
+ } | null>;
2964
3027
  /**
2965
- * Renders the scene with color-coded models.
3028
+ * Returns the world-space point under the given screen position, or
3029
+ * `null` if the cursor is over empty space.
3030
+ *
3031
+ * Renders the BIM scene with a depth-encoding override material that
3032
+ * packs `gl_FragCoord.z` into the color buffer, reads four bytes at
3033
+ * the cursor pixel, and unprojects through the camera matrices to a
3034
+ * world point. One render pass, one 4-byte readback, no worker
3035
+ * round-trip. Useful for things like "set camera orbit center to
3036
+ * what the user just clicked on".
3037
+ *
3038
+ * Cheaper than {@link getItemAt} because we don't differentiate
3039
+ * models — one render covers the whole BIM scene at once. We don't
3040
+ * resolve any item id either.
3041
+ *
3042
+ * @param position - Normalized device coords. Defaults to the
3043
+ * picker's last known mouse position.
2966
3044
  */
2967
- private renderColorCoded;
3045
+ getPointAt(position?: THREE.Vector2): Promise<THREE.Vector3 | null>;
2968
3046
  /**
2969
- * Updates the debug canvas with the color-coded render.
3047
+ * Returns the world-space surface normal under the cursor, or `null`
3048
+ * if the cursor is over empty space.
3049
+ *
3050
+ * Mirrors {@link getPointAt}'s structure: one `scene.overrideMaterial`
3051
+ * render with the normal-encoding shader, one 4-byte readback,
3052
+ * decode and renormalize.
2970
3053
  */
2971
- private updateDebugCanvas;
3054
+ getNormalAt(position?: THREE.Vector2): Promise<THREE.Vector3 | null>;
2972
3055
  /**
2973
- * Gets the model ID at the given screen position.
3056
+ * One-shot pick that produces the full result shape consumers need:
3057
+ * `{ modelId, localId, point, normal, distance }`. Routes through the
3058
+ * three GPU passes (id, depth, normal) and composes the output. No
3059
+ * worker round-trip.
2974
3060
  *
2975
- * @param position - Optional screen position. If not provided, uses current mouse position.
2976
- * @returns The model ID at the position, or null if no model is found.
3061
+ * Returns `null` if the cursor is over empty space, the id pass
3062
+ * decodes to the void sentinel, or the depth round-trip yields the
3063
+ * far plane.
2977
3064
  */
2978
- getModelAt(position?: THREE.Vector2): Promise<string | null>;
3065
+ getFullPick(position?: THREE.Vector2): Promise<{
3066
+ modelId: string;
3067
+ localId: number;
3068
+ /**
3069
+ * Internal item index (FlatBuffer `sample.item()`). Exposed so
3070
+ * SnapResolver and other internal consumers can hit fragments'
3071
+ * itemId-keyed fast paths (`boxes.sampleOf`) without paying a
3072
+ * second worker round-trip to translate back from localId.
3073
+ */
3074
+ itemId: number;
3075
+ point: THREE.Vector3;
3076
+ normal: THREE.Vector3 | null;
3077
+ distance: number;
3078
+ } | null>;
2979
3079
  /**
2980
- * Enables or disables debug mode.
2981
- * When enabled, shows a canvas with the color-coded render.
3080
+ * Toggle the debug overlay. When enabled the picker mirrors its id
3081
+ * render to a small canvas pinned in the top-right corner so you can
3082
+ * see what the readback sees.
2982
3083
  */
2983
3084
  setDebugMode(enabled: boolean): void;
2984
- /**
2985
- * Removes the debug canvas.
2986
- */
2987
- private removeDebugCanvas;
2988
3085
  /** {@link Disposable.dispose} */
2989
3086
  dispose(): void;
3087
+ /**
3088
+ * Runs the id renders and returns the raw decoded
3089
+ * `(modelId, itemId)` for the cursor pixel. Two render passes:
3090
+ *
3091
+ * - Pass 1 (`_renderTarget`): writes the model byte into R via
3092
+ * the `_idMaterial`. Used solely to disambiguate which model
3093
+ * owns the picked pixel.
3094
+ * - Pass 2 (`_localIdRenderTarget`): writes the full 32-bit
3095
+ * localId across all four bytes via the same `_idMaterial`
3096
+ * with `mode = 1`.
3097
+ *
3098
+ * Two single-attachment targets instead of MRT keeps the code
3099
+ * portable and the diff small. Both passes use the same scene,
3100
+ * camera and visibility toggling so depth tests resolve to the
3101
+ * same front-most fragment per pixel.
3102
+ *
3103
+ * Both public entry points lean on this.
3104
+ */
3105
+ private runIdPass;
3106
+ /**
3107
+ * Walks every fragments model. For each shell mesh that carries the
3108
+ * per-vertex `id` attribute, stashes the original material and swaps
3109
+ * in the shared id-encoding shader. Hides LOD line meshes (which
3110
+ * lack `id`) so they don't pollute the id buffer. Returns the
3111
+ * temporary `byte → modelId` map the render pass uses to
3112
+ * disambiguate models.
3113
+ */
3114
+ private applyIdMaterial;
3115
+ /**
3116
+ * Renders the world scene to the id target one model at a time,
3117
+ * flipping the `modelByte` uniform between renders. The depth buffer
3118
+ * is shared across the per-model passes so the front-most item still
3119
+ * wins each pixel regardless of which model it belongs to.
3120
+ *
3121
+ * Non-BIM meshes in the world scene (the user's own helpers, ground
3122
+ * planes, hover proxies, anything else) are hidden for the duration
3123
+ * of the render. Without this, their normal materials write whatever
3124
+ * colour they happen to produce into the id target, which decodes as
3125
+ * a bogus `(modelByte, itemId)` pair when the cursor lands on one.
3126
+ */
3127
+ /**
3128
+ * Single id render. Iterates models in byte order, flipping the
3129
+ * `modelByte` uniform between renders so each model's pixels carry
3130
+ * its assigned byte in R while the lower 3 bytes of GBA carry
3131
+ * `itemId + 1`. Depth buffer shared across the per-model passes so
3132
+ * the front-most item still wins each pixel regardless of which
3133
+ * model it belongs to.
3134
+ */
3135
+ private renderIdPass;
3136
+ /**
3137
+ * Renders the BIM scene model-by-model into `target` with `material`,
3138
+ * scissor-clipped to a small box around the cursor. Iterates models
3139
+ * in byte order so depth tests resolve to the same front-most
3140
+ * fragment per pixel. Per-model loop sets `material.uniforms.modelByte`
3141
+ * before each render so the picked pixel carries the model byte.
3142
+ */
3143
+ private renderPickPass;
3144
+ /**
3145
+ * Renders the picker's color target with the BIM tile shells using
3146
+ * the given override-style material, swapped in **per-mesh** rather
3147
+ * than via `scene.overrideMaterial`.
3148
+ *
3149
+ * Why per-mesh swap and not `scene.overrideMaterial`: fragments runs
3150
+ * its own LOD/visibility logic on tile shells (`mesh.visible`
3151
+ * toggled internally based on current LOD stage and frustum). At
3152
+ * the moment we render, most tile shells are flagged invisible —
3153
+ * `overrideMaterial` then renders nothing and we read back a
3154
+ * cleared pixel. Per-mesh swap mirrors what {@link renderIdPass}
3155
+ * does; we also force each shell visible for the duration of the
3156
+ * render so fragments' culling decisions don't blank our output.
3157
+ *
3158
+ * Honours the renderer's clipping planes so picked depth/normal
3159
+ * match what's actually rendered on screen.
3160
+ */
3161
+ private renderWithTileMaterial;
3162
+ private renderDepthPass;
3163
+ private renderNormalPass;
3164
+ private restoreOriginalMaterials;
3165
+ private readPixelAt;
3166
+ /**
3167
+ * Single id shader, single render pass. Reads:
3168
+ * - `id`: per-vertex `vec4` — the four bytes of `itemId + 1`
3169
+ * (big-endian) supplied as a non-normalised `Uint8Array`
3170
+ * attribute by fragments. Each component is 0–255 as a float.
3171
+ * - `modelByte` uniform — the byte assigned to the currently-
3172
+ * rendered model.
3173
+ *
3174
+ * Output packs `(modelByte, idMid, idLo1, idLo0)` into one RGBA8
3175
+ * pixel: model byte in R, the lower three bytes of `itemId + 1` in
3176
+ * GBA. Capping the encoded id at 24 bits is fine because `itemId`
3177
+ * is the FlatBuffer item index — bounded by item count per model
3178
+ * (16M is plenty for any practical BIM model). The high byte of
3179
+ * the vertex attribute (`vId.x`) is therefore always 0 and we
3180
+ * discard it; that frees R for the model byte. One render → one
3181
+ * readback per pick (the previous two-target layout cost two
3182
+ * sync-stalling readPixels calls).
3183
+ */
3184
+ private buildIdMaterial;
3185
+ /**
3186
+ * Builds the depth-encoding override material used by
3187
+ * {@link renderDepthPass}. Includes three's `packing` chunk to reuse
3188
+ * the standard `packDepthToRGBA` helper, which is robust to the
3189
+ * round-trip through an `UNSIGNED_BYTE` color attachment (the chunk
3190
+ * pre-multiplies by `256/255` so `depth = 1.0` decodes back exactly
3191
+ * to `1.0`). Decode side mirrors this with the same scaling factors;
3192
+ * see {@link unpackDepthFromRGBA} below.
3193
+ */
3194
+ private buildDepthMaterial;
3195
+ /**
3196
+ * World-space normal in RGB. Handles backfaces by flipping the
3197
+ * encoded normal so consumers always get the surface they're looking
3198
+ * at. Decode side: `(rgb * 2 - 1)` → renormalize.
3199
+ */
3200
+ private buildNormalMaterial;
3201
+ private setupRenderTarget;
3202
+ private setupDebugCanvas;
3203
+ private removeDebugCanvas;
3204
+ private updateDebugCanvas;
2990
3205
  }
2991
3206
 
2992
3207
  /**
@@ -5155,13 +5370,6 @@ export declare class SimpleRaycaster implements Disposable_2 {
5155
5370
  * This is used to access the camera and meshes.
5156
5371
  */
5157
5372
  world: World;
5158
- /**
5159
- * Whether to use fast model picking to optimize raycasting.
5160
- * When enabled, the raycaster will first use FastModelPicker to identify
5161
- * which model is under the mouse, then only raycast that specific model.
5162
- * This can significantly improve performance when there are many models.
5163
- */
5164
- useFastModelPicking: boolean;
5165
5373
  constructor(components: Components, world: World);
5166
5374
  /** {@link Disposable.dispose} */
5167
5375
  dispose(): void;
@@ -5191,7 +5399,7 @@ export declare class SimpleRaycaster implements Disposable_2 {
5191
5399
  * @param items - The meshes to query. If not provided, it will query all the meshes stored in {@link World.meshes}.
5192
5400
  * @returns The first intersection found or `null` if no intersection was found.
5193
5401
  */
5194
- castRayFromVector(origin: THREE.Vector3, direction: THREE.Vector3, items?: THREE.Mesh<THREE.BufferGeometry<THREE.NormalBufferAttributes, THREE.BufferGeometryEventMap>, THREE.Material | THREE.Material[], THREE.Object3DEventMap>[]): THREE.Intersection<THREE.Object3D<THREE.Object3DEventMap>> | null;
5402
+ castRayFromVector(origin: THREE.Vector3, direction: THREE.Vector3, items?: THREE.Mesh<THREE.BufferGeometry<THREE.NormalBufferAttributes, THREE.BufferGeometryEventMap>, THREE.Material<THREE.MaterialEventMap> | THREE.Material<THREE.MaterialEventMap>[], THREE.Object3DEventMap>[]): THREE.Intersection<THREE.Object3D<THREE.Object3DEventMap>> | null;
5195
5403
  private intersect;
5196
5404
  private filterClippingPlanes;
5197
5405
  }
@@ -5226,6 +5434,48 @@ export declare class SimpleRenderer extends BaseRenderer {
5226
5434
  protected _resizeObserver: ResizeObserver | null;
5227
5435
  protected onContainerUpdated: Event_2<unknown>;
5228
5436
  private _resizing;
5437
+ private _logo;
5438
+ private _showLogo;
5439
+ /**
5440
+ * Whether the That Open Company logo is shown as a small overlay in the
5441
+ * bottom-left corner of the renderer container. Defaults to `true`.
5442
+ *
5443
+ * The logo is how people discover that the libraries powering this app
5444
+ * come from That Open Company, the team that keeps
5445
+ * `@thatopen/components`, `@thatopen/fragments`, and the rest of the stack
5446
+ * free and open source. If the logo fits your design, please consider
5447
+ * leaving it on; every visible mark helps us reach more developers and
5448
+ * keep investing in the libraries you're building on. Thank you.
5449
+ *
5450
+ * If your app needs a clean viewport (full-bleed print view, white-label
5451
+ * embed, customer-branded surface), set it to `false`:
5452
+ * ```ts
5453
+ * world.renderer.showLogo = false;
5454
+ * ```
5455
+ */
5456
+ get showLogo(): boolean;
5457
+ set showLogo(value: boolean);
5458
+ /**
5459
+ * The DOM element rendering the That Open Company wordmark, or `null`
5460
+ * before the renderer is set up. Mutate its `style` to restyle or move
5461
+ * the overlay without forking the source. Common adjustments:
5462
+ *
5463
+ * ```ts
5464
+ * const logo = world.renderer.logo;
5465
+ * if (logo) {
5466
+ * logo.style.left = "auto";
5467
+ * logo.style.right = "0.75rem"; // move to bottom-right
5468
+ * logo.style.bottom = "auto";
5469
+ * logo.style.top = "0.75rem"; // or to the top edge
5470
+ * }
5471
+ * ```
5472
+ *
5473
+ * The element also carries a `data-thatopen-logo` attribute, so app-wide
5474
+ * CSS can target it (`[data-thatopen-logo] { ... }`). External rules
5475
+ * need `!important` (or higher specificity) to win against the inline
5476
+ * defaults.
5477
+ */
5478
+ get logo(): HTMLElement | null;
5229
5479
  /**
5230
5480
  * Constructor for the SimpleRenderer class.
5231
5481
  *
@@ -5252,6 +5502,7 @@ export declare class SimpleRenderer extends BaseRenderer {
5252
5502
  setupEvents(active: boolean): void;
5253
5503
  private resizeEvent;
5254
5504
  private setupRenderer;
5505
+ private setupLogo;
5255
5506
  private onContextLost;
5256
5507
  private onContextBack;
5257
5508
  }
@@ -5327,7 +5578,7 @@ export declare class SimpleWorld<T extends BaseScene = BaseScene, U extends Base
5327
5578
  /**
5328
5579
  * All the loaded [meshes](https://threejs.org/docs/#api/en/objects/Mesh). These meshes will be taken into account in operations like raycasting.
5329
5580
  */
5330
- readonly meshes: Set<THREE.Mesh<THREE.BufferGeometry<THREE.NormalBufferAttributes, THREE.BufferGeometryEventMap>, THREE.Material | THREE.Material[], THREE.Object3DEventMap>>;
5581
+ readonly meshes: Set<THREE.Mesh<THREE.BufferGeometry<THREE.NormalBufferAttributes, THREE.BufferGeometryEventMap>, THREE.Material<THREE.MaterialEventMap> | THREE.Material<THREE.MaterialEventMap>[], THREE.Object3DEventMap>>;
5331
5582
  /** {@link Updateable.onAfterUpdate} */
5332
5583
  readonly onAfterUpdate: Event_2<unknown>;
5333
5584
  /** {@link Updateable.onBeforeUpdate} */
@@ -5470,6 +5721,146 @@ declare interface SlopeAnnotationSystem {
5470
5721
  /** How the slope value is displayed in the text label. */
5471
5722
  export declare type SlopeFormat = "percentage" | "ratio" | "degrees";
5472
5723
 
5724
+ /**
5725
+ * Snap classes the resolver supports. Mirrors fragments' `SnappingClass`.
5726
+ */
5727
+ export declare enum SnapClass {
5728
+ POINT = 0,
5729
+ LINE = 1,
5730
+ FACE = 2
5731
+ }
5732
+
5733
+ /**
5734
+ * Main-thread snap resolver: takes a world-space pick point + the
5735
+ * picked item, returns the closest face / line / point of that item
5736
+ * for any requested snap classes. The heavy work (fetching the item's
5737
+ * shell geometry through the fragments edit API and composing
5738
+ * world-space polygons) runs once per item; subsequent calls for the
5739
+ * same item are pure CPU geometry against the LRU-bounded cache.
5740
+ * Call {@link invalidate} when an item changes (e.g. via the
5741
+ * fragments edit API) so the cached snap data gets refreshed on the
5742
+ * next request.
5743
+ */
5744
+ export declare class SnapResolver implements Disposable_2 {
5745
+ /** {@link Disposable.onDisposed} */
5746
+ readonly onDisposed: Event_2<unknown>;
5747
+ /**
5748
+ * Maximum world-space distance for a snap candidate to be
5749
+ * considered valid. Vertices, edges or face planes beyond this
5750
+ * range are rejected. Set this in the same units as your model
5751
+ * (typical BIM scenes are metres, so the default of 1 means
5752
+ * "snap only when within 1 m of the cursor"). Tools that want a
5753
+ * different feel can override per-instance.
5754
+ */
5755
+ maxDistance: number;
5756
+ /**
5757
+ * Maximum number of items to keep in the snap cache. Beyond this,
5758
+ * the least-recently-used item is dropped on the next insertion.
5759
+ * Each cached item is on the order of a few KB (per-shell faces +
5760
+ * deduped edges + deduped vertices), so the default of 1000 caps
5761
+ * memory at a few MB even on huge models. Bump it for workflows
5762
+ * that revisit thousands of distinct items in a single session.
5763
+ */
5764
+ maxCacheSize: number;
5765
+ private readonly components;
5766
+ private readonly cache;
5767
+ constructor(components: Components);
5768
+ /** {@link Disposable.dispose} */
5769
+ dispose(): void;
5770
+ /**
5771
+ * Returns the best snap candidate among the requested classes for a
5772
+ * pre-picked surface point on a known item, or `null` if the item
5773
+ * has no shell geometry. When multiple classes are requested, the
5774
+ * winner is whichever produces the smallest distance to the input
5775
+ * `point`.
5776
+ *
5777
+ * @param point - Pick point in world space (typically from the GPU
5778
+ * picker's `getFullPick`).
5779
+ * @param modelId - Model the picked item belongs to.
5780
+ * @param localId - The picked item's localId.
5781
+ * @param classes - Subset of {@link SnapClass} the caller cares about.
5782
+ */
5783
+ resolve(point: THREE.Vector3, modelId: string,
5784
+ /**
5785
+ * Internal **itemId** (the FlatBuffer `sample.item()` index, key
5786
+ * for `boxes.sampleOf`), not the user-facing localId. The picker
5787
+ * exposes this on its full-pick result; passing it through here
5788
+ * lets us hit the O(1) item→samples reverse index on the worker
5789
+ * via `_getItemSnapData(itemId)`. Keying by localId would force a
5790
+ * full sample-table scan (~1 s on big models).
5791
+ */
5792
+ itemId: number, classes: Iterable<SnapClass>): Promise<SnapResult | null>;
5793
+ /**
5794
+ * Fire-and-forget prefetch of an item's snap geometry. Hoverer calls
5795
+ * this on lock-onto-new-item so by the time the user clicks, the
5796
+ * worker round-trip is already paid and `resolve` resolves
5797
+ * synchronously off the cache.
5798
+ *
5799
+ * Returns a Promise so callers who *do* want to await can; most
5800
+ * won't.
5801
+ */
5802
+ prefetch(modelId: string, itemId: number): Promise<void>;
5803
+ /** Drop a specific item from the cache (e.g. after an edit). */
5804
+ invalidate(modelId: string, itemId: number): void;
5805
+ /** Drop the entire cache. Useful when many items have changed. */
5806
+ clear(): void;
5807
+ private static keyOf;
5808
+ private storeWithLRU;
5809
+ private fetchItemSnapData;
5810
+ private collectPolygonsFromShell;
5811
+ private snap;
5812
+ private snapToPoint;
5813
+ private snapToLine;
5814
+ private snapToFace;
5815
+ }
5816
+
5817
+ /**
5818
+ * Singleton component that owns one shared {@link SnapResolver} for
5819
+ * the {@link Components} instance. Snap caches are keyed by
5820
+ * `(modelId, localId)` so a single resolver across all worlds is
5821
+ * fine — there's no per-world state.
5822
+ */
5823
+ export declare class SnapResolvers extends Component implements Disposable_2 {
5824
+ /** Components-system UUID. */
5825
+ static readonly uuid: "be9b8e6c-7f5b-4a36-8e7e-3a1f5e2a6c9d";
5826
+ /** {@link Component.enabled} */
5827
+ enabled: boolean;
5828
+ /** {@link Disposable.onDisposed} */
5829
+ readonly onDisposed: Event_2<unknown>;
5830
+ private readonly _resolver;
5831
+ constructor(components: Components);
5832
+ /** The shared {@link SnapResolver}. */
5833
+ get(): SnapResolver;
5834
+ /** {@link Disposable.dispose} */
5835
+ dispose(): void;
5836
+ }
5837
+
5838
+ /**
5839
+ * One result of a {@link SnapResolver.resolve} call. Fields match what
5840
+ * the worker raycast snap path historically produces, so consumers
5841
+ * (length / area measurements, drawing tools) keep working unchanged
5842
+ * when their input switches from worker raycast to GPU pick + this.
5843
+ */
5844
+ export declare type SnapResult = {
5845
+ /** Snapped world-space position (vertex / segment closest point /
5846
+ * point projected onto the snapped face). */
5847
+ point: THREE.Vector3;
5848
+ /** World-space surface normal where applicable (always set for FACE,
5849
+ * unset for POINT, derived from the picked surface for LINE). */
5850
+ normal?: THREE.Vector3;
5851
+ /** Which snap class this result represents. */
5852
+ snappingClass: SnapClass;
5853
+ /** Polygon vertices of the snapped face (FACE only). Flat array of
5854
+ * `[x, y, z, x, y, z, ...]`. */
5855
+ facePoints?: Float32Array;
5856
+ /** Triangulation indices into `facePoints` (FACE only). For convex
5857
+ * polygons we emit a fan; for concave the consumer can re-tessellate. */
5858
+ faceIndices?: Uint32Array;
5859
+ /** Endpoints of the snapped edge (LINE only). */
5860
+ snappedEdgeP1?: THREE.Vector3;
5861
+ snappedEdgeP2?: THREE.Vector3;
5862
+ };
5863
+
5473
5864
  /** A single technical drawing — the core spatial aggregate. */
5474
5865
  export declare class TechnicalDrawing {
5475
5866
  /** Unique identifier for this drawing instance. */
@@ -6815,6 +7206,17 @@ export declare class Views extends Component {
6815
7206
  */
6816
7207
  world: World | null;
6817
7208
  private _fragmentsUpdateEvent;
7209
+ /**
7210
+ * When true, opening a view snapshots the active camera's controls state
7211
+ * (via `controls.toJSON()`) and closing the view restores that snapshot
7212
+ * (via `controls.fromJSON(json, true)`), so exiting a view returns the
7213
+ * camera to the same pose the user had before entering it. Default true.
7214
+ *
7215
+ * Set to `false` to keep the legacy behavior, where the default camera
7216
+ * lands at whatever pose its controls drifted to during the view session.
7217
+ */
7218
+ restoreCameraOnClose: boolean;
7219
+ private _restoreState;
6818
7220
  /**
6819
7221
  * Determines whether there are any open views in this component's list.
6820
7222
  */
@@ -6885,6 +7287,14 @@ export declare class Views extends Component {
6885
7287
  * @remarks This method resets the world to use its default camera.
6886
7288
  */
6887
7289
  close(id?: string): void;
7290
+ /**
7291
+ * Routes input to a single camera by toggling `controls.enabled`. Every
7292
+ * camera in the world owns its own CameraControls instance bound to the
7293
+ * renderer's DOM element; without this, all of them would react to the
7294
+ * same wheel/pointer events in parallel, so navigating one view would
7295
+ * drift every other camera at the same time.
7296
+ */
7297
+ private setOnlyEnabledControls;
6888
7298
  }
6889
7299
 
6890
7300
  /**