UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

334 lines (333 loc) 13.7 kB
export class GSplatOctreeInstance { /** * @param {GraphicsDevice} device - The graphics device. * @param {GSplatOctree} octree - The octree. * @param {GSplatPlacement} placement - The placement. */ constructor(device: GraphicsDevice, octree: GSplatOctree, placement: GSplatPlacement); /** @type {GSplatOctree} */ octree: GSplatOctree; /** @type {GSplatPlacement} */ placement: GSplatPlacement; /** @type {Set<GSplatPlacement>} */ activePlacements: Set<GSplatPlacement>; /** @type {boolean} */ dirtyModifiedPlacements: boolean; /** * Set to true when placements are added or removed, signaling that the manager needs to * create a new world state and trigger a full work buffer rebuild. */ dirtyPlacementSetChanged: boolean; /** @type {GraphicsDevice} */ device: GraphicsDevice; /** * Array of NodeInfo instances, one per octree node. * * @type {NodeInfo[]} */ nodeInfos: NodeInfo[]; /** * Array of current placements per file. Index is fileIndex, value is GSplatPlacement or null. * Value null indicates file is not used / no placement. * * @type {(GSplatPlacement|null)[]} */ filePlacements: (GSplatPlacement | null)[]; /** * Set of pending file loads (file indices). * * @type {Set<number>} */ pending: Set<number>; /** * Map of nodeIndex -> { oldFileIndex, newFileIndex } that needs to be decremented when the * new LOD resource loads. This ensures we decrement even if the node switches LOD again * before the new resource arrives. * * @type {Map<number, { oldFileIndex: number, newFileIndex: number }>} */ pendingDecrements: Map<number, { oldFileIndex: number; newFileIndex: number; }>; /** * Files that became unused by this instance this update. Each entry represents a single decRef. * * @type {Set<number>} */ removedCandidates: Set<number>; /** * Minimum allowed LOD index for this instance, clamped to valid octree bounds. */ rangeMin: number; /** * Maximum allowed LOD index for this instance, clamped to valid octree bounds. */ rangeMax: number; /** * Previous node position at which LOD was last updated. This is used to determine if LOD needs * to be updated as the octree splat moves. */ previousPosition: Vec3; /** * Set when a resource has completed loading and LOD should be re-evaluated. */ needsLodUpdate: boolean; /** * Tracks prefetched file indices that are being loaded without active placements. * When any completes, we trigger LOD re-evaluation to allow promotion. * * @type {Set<number>} */ prefetchPending: Set<number>; /** * Tracks invisible->visible pending adds per node: nodeIndex -> fileIndex. * Ensures only a single pending placement exists for a node while it's not yet displayed. * * @type {Map<number, number>} */ pendingVisibleAdds: Map<number, number>; /** * Returns the count of resources pending load or prefetch, including environment if loading. * * @type {number} */ get pendingLoadCount(): number; /** * Environment placement. * * @type {GSplatPlacement|null} */ environmentPlacement: GSplatPlacement | null; /** * Event handle for device lost event. * * @type {EventHandle|null} * @private */ private _deviceLostEvent; /** * Reusable scratch for LOD distance thresholds. * * @type {Float32Array|null} * @private */ private _lodMinDistThresholds; /** * Destroys this octree instance and clears internal references. * * @param {boolean} [skipRefCounting] - When true, skip decrementing file ref counts * on the octree. Used when the caller handles ref counting externally via pendingReleases * (e.g. during world state updates where decrements must be deferred). */ destroy(skipRefCounting?: boolean): void; /** * Handles device lost event by releasing all loaded resources. * * @private */ private _onDeviceLost; /** * Returns the file indices currently referenced by this instance that should be decremented * when the instance is destroyed. * * @returns {number[]} Array of file indices to decRef. */ getFileDecrements(): number[]; /** * Selects desired LOD index for a node using the underfill strategy. When underfill is enabled, * it prefers already-loaded LODs within [optimalLodIndex .. optimalLodIndex + lodUnderfillLimit]. * If none are loaded, it selects the coarsest available LOD within the range. * * @param {import('./gsplat-octree-node.js').GSplatOctreeNode} node - The octree node. * @param {number} optimalLodIndex - Optimal LOD index based on camera/distance. * @param {number} maxLod - Maximum LOD index. * @param {number} lodUnderfillLimit - Allowed coarse range above optimal. * @returns {number} Desired LOD index to display. */ selectDesiredLodIndex(node: import("./gsplat-octree-node.js").GSplatOctreeNode, optimalLodIndex: number, maxLod: number, lodUnderfillLimit: number): number; /** * Prefetch only the next-better LOD toward optimal. This stages loading in steps across all * nodes, avoiding intermixing requests before coarse is present. * * @param {import('./gsplat-octree-node.js').GSplatOctreeNode} node - The octree node. * @param {number} desiredLodIndex - Currently selected LOD for display (may be coarser than optimal). * @param {number} optimalLodIndex - Target optimal LOD. */ prefetchNextLod(node: import("./gsplat-octree-node.js").GSplatOctreeNode, desiredLodIndex: number, optimalLodIndex: number): void; /** * Updates the octree instance when LOD needs to be updated. * * @param {GraphNode} cameraNode - The camera node. * @param {import('./gsplat-params.js').GSplatParams} params - Global gsplat parameters. */ updateLod(cameraNode: GraphNode, params: import("./gsplat-params.js").GSplatParams): void; /** * Ensures the reusable threshold buffer can store indices 1 through maxLod and fills * buf[k] = d0 * m^(k-1) for k from 1 to maxLod (same distance bands as truncating 1 + log(d/d0) / log(m)). * * @param {number} maxLod - Maximum LOD index (>= 1). * @param {number} d0 - lodBaseDistance in FOV-adjusted distance space. * @param {number} m - lodMultiplier. * @returns {Float32Array} Buffer; index 0 unused; entries 1..maxLod set. * @private */ private _ensureLodMinDistThresholds; /** * Evaluates optimal LOD indices for all nodes based on camera position and parameters. * This is Pass 1 of the LOD update process. Results are stored in nodeInfos array. * * Uses geometric LOD distances (lodBaseDistance * lodMultiplier^i) with FOV compensation * so that LOD transitions are perceptually uniform under perspective projection. * * @param {GraphNode} cameraNode - The camera node. * @param {number} maxLod - Maximum LOD index (lodLevels - 1). * @param {number} lodBaseDistance - Base distance for first LOD transition. * @param {number} lodMultiplier - Geometric ratio between successive LOD thresholds. * @param {number} rangeMin - Minimum allowed LOD index. * @param {number} rangeMax - Maximum allowed LOD index. * @param {import('./gsplat-params.js').GSplatParams} params - Global gsplat parameters. * @param {number} uniformScale - Uniform scale of the octree transform for world-space conversion. * @param {boolean} [accumulateSplats] - When true (default), sum splat counts for the chosen LOD per node and return the total (budget path). When false, skip counting (faster; return value unused). * @param {number} [globalMaxDistanceForBuckets] - When > 0, writes {@link NodeInfo.budgetBucket} using the same sqrt mapping as the budget balancer. Omit or pass 0 when not enforcing global budget. * @returns {number} Total number of splats that would be used by optimal LODs when accumulateSplats is true; otherwise 0. * @private */ private evaluateNodeLods; /** * Evaluates optimal LOD for all nodes without applying changes. * Called by GSplatManager during phased global budget enforcement. * * @param {GraphNode} cameraNode - The camera node. * @param {import('./gsplat-params.js').GSplatParams} params - Global gsplat parameters. * @param {number} [budgetScale] - Dynamic scale applied to LOD parameters to shift * boundaries closer to the budget target. Applied to lodBaseDistance directly, and * gently to lodMultiplier via pow(budgetScale, -0.2). Defaults to 1. * @param {number} [globalMaxDistanceForBuckets] - When > 0, {@link NodeInfo.budgetBucket} is populated during LOD evaluation for budget balancing. * @returns {number} Total optimal splat count. */ evaluateOptimalLods(cameraNode: GraphNode, params: import("./gsplat-params.js").GSplatParams, budgetScale?: number, globalMaxDistanceForBuckets?: number): number; /** * Applies calculated LOD changes and manages file placements. * This is Pass 2 of the LOD update process. Reads from nodeInfos array populated by evaluateNodeLods(). * * @param {number} maxLod - Maximum LOD index (lodLevels - 1). * @param {import('./gsplat-params.js').GSplatParams} params - Global gsplat parameters. */ applyLodChanges(maxLod: number, params: import("./gsplat-params.js").GSplatParams): void; /** * Increments reference count for a file and creates placement immediately. * * @param {number} fileIndex - The file index. * @param {number} nodeIndex - The octree node index. * @param {number} lodIndex - The LOD index for this node. */ incrementFileRef(fileIndex: number, nodeIndex: number, lodIndex: number): void; /** * Decrements reference count for a file and removes placement if needed. * * @param {number} fileIndex - The file index. * @param {number} nodeIndex - The octree node index. */ decrementFileRef(fileIndex: number, nodeIndex: number): void; /** * Updates existing placement with loaded resource and adds to manager. * * @param {number} fileIndex - The file index. * @returns {boolean} True if placement was updated and added to manager, false otherwise. */ addFilePlacement(fileIndex: number): boolean; /** * Tests if the octree instance has moved by more than the provided LOD update distance. * * @param {number} threshold - Distance threshold to trigger an update. * @returns {boolean} True if the octree instance has moved by more than the threshold, false otherwise. */ testMoved(threshold: number): boolean; /** * Updates the previous position of the octree instance. */ updateMoved(): void; /** * Updates the octree instance each frame. * * @returns {boolean} True if octree instance is dirty, false otherwise. */ update(): boolean; /** * Consumes and returns whether the active placement set membership changed (add/remove). * * @returns {boolean} True if placements were added or removed since last call. */ consumePlacementSetChanged(): boolean; debugRender(scene: any): void; /** * Returns true if this instance requests LOD re-evaluation and resets the flag. * * @returns {boolean} True if LOD should be re-evaluated. */ consumeNeedsLodUpdate(): boolean; /** * Polls prefetched file indices for completion and updates state. */ pollPrefetchCompletions(): void; } /** * Stores LOD state for a single octree node. * * @ignore */ export class NodeInfo { /** * Current LOD index being rendered. -1 indicates node is not visible. */ currentLod: number; /** * Optimal LOD index based on distance/visibility (before underfill). */ optimalLod: number; /** * World-space distance from camera to this node. * Used for non-linear bucket mapping in budget enforcement. */ worldDistance: number; /** * Accumulated camera translation for SH color update threshold tracking. */ colorAccumulatedTranslation: number; /** * Back-reference to owning GSplatOctreeInstance. * * @type {GSplatOctreeInstance|null} */ inst: GSplatOctreeInstance | null; /** * Cached reference to this node's LOD array for fast budget balancing. * * @type {Array|null} */ lods: any[] | null; /** * Distance bucket index [0, NUM_BUCKETS - 1] for global budget balancing (sqrt mapping). * Written during {@link GSplatOctreeInstance.evaluateNodeLods} when a global max distance * is supplied (budget enforcement path only). * * @type {number} */ budgetBucket: number; /** * Unique allocation identifier for persistent work buffer allocation tracking. * * @type {number} */ allocId: number; /** * Resets all LOD values to -1 (invisible/uninitialized). */ resetLod(): void; } import type { GSplatOctree } from './gsplat-octree.js'; import { GSplatPlacement } from './gsplat-placement.js'; import type { GraphicsDevice } from '../../platform/graphics/graphics-device.js'; import { Vec3 } from '../../core/math/vec3.js'; import type { GraphNode } from '../graph-node.js';