UNPKG

playcanvas

Version:

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

268 lines (265 loc) 10.3 kB
import { Debug } from '../../core/debug.js'; import { GSplatStreams } from '../gsplat/gsplat-streams.js'; import { WORKBUFFER_UPDATE_ALWAYS, WORKBUFFER_UPDATE_ONCE, WORKBUFFER_UPDATE_AUTO } from '../constants.js'; import { GsplatAllocId } from './gsplat-alloc-id.js'; /** * @import { BoundingBox } from '../../core/shape/bounding-box.js' * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' * @import { GraphNode } from '../graph-node.js' * @import { GSplatResource } from '../gsplat/gsplat-resource.js' * @import { GSplatResourceBase } from '../gsplat/gsplat-resource-base.js' * @import { GSplatOctreeResource } from './gsplat-octree.resource.js' * @import { ScopeId } from '../../platform/graphics/scope-id.js' * @import { Texture } from '../../platform/graphics/texture.js' * @import { Vec2 } from '../../core/math/vec2.js' */ /** * Class representing a placement of a gsplat resource. * * @ignore */ class GSplatPlacement { /** * @type {number} */ set lodBaseDistance(value) { if (this._lodBaseDistance !== value) { this._lodBaseDistance = value; this.lodDirty = true; } } get lodBaseDistance() { return this._lodBaseDistance; } /** * @type {number} */ set lodMultiplier(value) { if (this._lodMultiplier !== value) { this._lodMultiplier = value; this.lodDirty = true; } } get lodMultiplier() { return this._lodMultiplier; } /** * Destroys this placement and releases all resources. */ destroy() { this._streams?.destroy(); this._streams = null; this.intervals.clear(); this.resource = null; } /** * Sets the work buffer modifier for this placement. Triggers work buffer re-render. * Must provide all three functions: modifySplatCenter, modifySplatRotationScale, modifySplatColor. * * @type {{ code: string, hash: number }|null} */ set workBufferModifier(value) { this._workBufferModifier = value; this.renderDirty = true; } /** * Gets the work buffer modifier for this placement. * Delegates to parent placement if available (for octree file placements). * * @type {{ code: string, hash: number }|null} */ get workBufferModifier() { return this.parentPlacement?.workBufferModifier ?? this._workBufferModifier; } /** * Returns and clears the render dirty flag. Also checks for format version changes * and handles render mode. * * @returns {boolean} True if the splat needed re-rendering. */ consumeRenderDirty() { // Auto-detect format version changes // Cast to access format property (GSplatOctreeResource doesn't have format) const format = /** @type {GSplatResourceBase} */ this.resource?.format; if (format && this._lastFormatVersion !== format.extraStreamsVersion) { this._lastFormatVersion = format.extraStreamsVersion; this.renderDirty = true; } // Handle work buffer update mode if (this.workBufferUpdate === WORKBUFFER_UPDATE_ALWAYS) { this.renderDirty = true; } else if (this.workBufferUpdate === WORKBUFFER_UPDATE_ONCE) { this.renderDirty = true; this.workBufferUpdate = WORKBUFFER_UPDATE_AUTO; // Auto-reset } const dirty = this.renderDirty; this.renderDirty = false; return dirty; } /** * Sets a custom AABB for this placement. Pass null to use resource.aabb as fallback. * * @param {BoundingBox|null} aabb - The bounding box to set, or null to clear. */ set aabb(aabb) { this._aabb = aabb?.clone() ?? null; } /** * Gets the AABB for this placement. Returns custom AABB if set, otherwise resource.aabb. * * @returns {BoundingBox} The bounding box. */ get aabb() { const aabb = this._aabb ?? this.resource?.aabb; Debug.assert(aabb, 'GSplatPlacement.aabb is null - resource.aabb must be set'); return /** @type {BoundingBox} */ aabb; } /** * Computes the LOD distance threshold for a given level using the geometric progression. * * @param {number} level - The LOD level index. * @returns {number} The distance threshold for the given LOD level. */ getLodDistance(level) { return this.lodBaseDistance * Math.pow(this.lodMultiplier, level); } /** * Gets an instance-level texture by name. Creates the streams container on first access * if the format has instance streams defined. * * @param {string} name - The name of the texture to get. * @param {GraphicsDevice} device - The graphics device (required for lazy initialization). * @returns {Texture|undefined} The texture, or undefined if not found. */ getInstanceTexture(name, device) { // Cast to access GSplatResourceBase properties (GSplatOctreeResource doesn't have format/streams) const resource = /** @type {GSplatResourceBase} */ this.resource; if (!resource?.format) { return undefined; } // Lazy-initialize streams if format has instance streams if (!this._streams && resource.format.instanceStreams.length > 0) { this._streams = new GSplatStreams(device, true); this._streams.textureDimensions.copy(resource.streams.textureDimensions); this._streams.syncWithFormat(resource.format); } return this._streams?.getTexture(name); } /** * Gets the instance streams container, or null if not initialized. * Delegates to parent placement if available (for octree file placements). * * @type {GSplatStreams|null} * @ignore */ get streams() { return this.parentPlacement?.streams ?? this._streams; } /** * Ensures instance streams container exists if format has instance streams. * * @param {GraphicsDevice} device - The graphics device. * @ignore */ ensureInstanceStreams(device) { const resource = /** @type {GSplatResourceBase} */ this.resource; if (!resource?.format) { return; } if (!this._streams && resource.format.instanceStreams.length > 0) { this._streams = new GSplatStreams(device, true); this._streams.textureDimensions.copy(resource.streams.textureDimensions); this._streams.syncWithFormat(resource.format); } } /** * Create a new GSplatPlacement. * * @param {GSplatResource|null} resource - The resource of the splat. * @param {GraphNode} node - The node that the gsplat is linked to. * @param {number} [lodIndex] - The LOD index for this placement. * @param {Map<string, {scopeId: ScopeId, data: *}>|null} [parameters] - Per-instance shader parameters. * @param {GSplatPlacement|null} [parentPlacement] - Parent placement for shader config delegation. * @param {number|null} [id] - Unique identifier for picking. If not provided, inherits from parentPlacement. */ constructor(resource, node, lodIndex = 0, parameters = null, parentPlacement = null, id = null){ /** * Map of intervals for octree nodes using this placement. * Key is octree node index, value is Vec2 representing start and end index (inclusive). * * @type {Map<number, Vec2>} */ this.intervals = new Map(); /** * Unique identifier for this placement. Used by the picking system and available * for custom shader effects. * * @type {number} */ this.id = 0; /** * Unique allocation identifier for persistent work buffer allocation tracking. * * @type {number} */ this.allocId = GsplatAllocId.get(); /** * The LOD index for this placement. * * @type {number} */ this.lodIndex = 0; /** * Base distance for the first LOD transition (LOD 0 to LOD 1). * * @type {number} * @private */ this._lodBaseDistance = 5; /** * Geometric multiplier between successive LOD distance thresholds. * Distance for LOD level i is: lodBaseDistance * lodMultiplier^i. * * @type {number} * @private */ this._lodMultiplier = 3; /** * The axis-aligned bounding box for this placement, in local space. * Null means use resource.aabb as fallback. * * @type {BoundingBox|null} */ this._aabb = null; /** * Per-instance shader parameters. Reference to the component's parameters Map. * * @type {Map<string, {scopeId: ScopeId, data: *}>|null} */ this.parameters = null; /** * Optional streams for instance-level textures. * * @type {GSplatStreams|null} * @private */ this._streams = null; /** * Flag indicating LOD parameters have changed and LOD needs re-evaluation. * * @type {boolean} */ this.lodDirty = false; /** * Flag indicating the splat needs to be re-rendered to work buffer. * * @type {boolean} */ this.renderDirty = false; /** * Work buffer update mode. * * @type {number} */ this.workBufferUpdate = WORKBUFFER_UPDATE_AUTO; /** * Last seen format version for auto-detecting format changes. * * @type {number} * @private */ this._lastFormatVersion = -1; /** * Custom work buffer modifier code for this placement (object with code and pre-computed hash). * * @type {{ code: string, hash: number }|null} * @private */ this._workBufferModifier = null; /** * Parent placement. Used by octree file placements to inherit workBufferModifier and * parameters from the component's placement. * * @type {GSplatPlacement|null} * @ignore */ this.parentPlacement = null; this.id = id ?? parentPlacement?.id ?? 0; this.resource = resource; this.node = node; this.lodIndex = lodIndex; this.parameters = parameters ?? parentPlacement?.parameters ?? null; this.parentPlacement = parentPlacement; } } export { GSplatPlacement };