UNPKG

playcanvas

Version:

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

293 lines (292 loc) 9.56 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { Debug } from "../../core/debug.js"; import { GSplatStreams } from "../gsplat/gsplat-streams.js"; import { WORKBUFFER_UPDATE_AUTO, WORKBUFFER_UPDATE_ALWAYS, WORKBUFFER_UPDATE_ONCE } from "../constants.js"; import { GsplatAllocId } from "./gsplat-alloc-id.js"; class GSplatPlacement { /** * 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) { /** * The resource of the splat.. * * @type {GSplatResource|GSplatOctreeResource|null} */ __publicField(this, "resource"); /** * The node that the gsplat is linked to. * * @type {GraphNode} */ __publicField(this, "node"); /** * 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>} */ __publicField(this, "intervals", /* @__PURE__ */ new Map()); /** * Unique identifier for this placement. Used by the picking system and available * for custom shader effects. */ __publicField(this, "id", 0); /** * Unique allocation identifier for persistent work buffer allocation tracking. * * @type {number} */ __publicField(this, "allocId", GsplatAllocId.get()); /** * The LOD index for this placement. */ __publicField(this, "lodIndex", 0); /** * Base distance for the first LOD transition (LOD 0 to LOD 1). * * @private */ __publicField(this, "_lodBaseDistance", 5); /** * Geometric multiplier between successive LOD distance thresholds. * Distance for LOD level i is: lodBaseDistance * lodMultiplier^i. * * @private */ __publicField(this, "_lodMultiplier", 3); /** * The axis-aligned bounding box for this placement, in local space. * Null means use resource.aabb as fallback. * * @type {BoundingBox|null} */ __publicField(this, "_aabb", null); /** * Per-instance shader parameters. Reference to the component's parameters Map. * * @type {Map<string, {scopeId: ScopeId, data: *}>|null} */ __publicField(this, "parameters", null); /** * Optional streams for instance-level textures. * * @type {GSplatStreams|null} * @private */ __publicField(this, "_streams", null); /** * Flag indicating LOD parameters have changed and LOD needs re-evaluation. */ __publicField(this, "lodDirty", false); /** * Flag indicating the splat needs to be re-rendered to work buffer. */ __publicField(this, "renderDirty", false); /** * Work buffer update mode. * * @type {number} */ __publicField(this, "workBufferUpdate", WORKBUFFER_UPDATE_AUTO); /** * Last seen format version for auto-detecting format changes. * * @private */ __publicField(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 */ __publicField(this, "_workBufferModifier", null); /** * Parent placement. Used by octree file placements to inherit workBufferModifier and * parameters from the component's placement. * * @type {GSplatPlacement|null} * @ignore */ __publicField(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; } /** * @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() { const format = ( /** @type {GSplatResourceBase} */ this.resource?.format ); if (format && this._lastFormatVersion !== format.extraStreamsVersion) { this._lastFormatVersion = format.extraStreamsVersion; this.renderDirty = true; } if (this.workBufferUpdate === WORKBUFFER_UPDATE_ALWAYS) { this.renderDirty = true; } else if (this.workBufferUpdate === WORKBUFFER_UPDATE_ONCE) { this.renderDirty = true; this.workBufferUpdate = WORKBUFFER_UPDATE_AUTO; } 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) { const resource = ( /** @type {GSplatResourceBase} */ this.resource ); if (!resource?.format) { return void 0; } 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); } } } export { GSplatPlacement };