playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
336 lines (335 loc) • 10.7 kB
JavaScript
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_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);
/**
* Minimum allowed LOD index (inclusive). Clamped to the asset's valid range at use.
*
* @private
*/
__publicField(this, "_lodRangeMin", 0);
/**
* Maximum allowed LOD index (inclusive). Clamped to the asset's valid range at use.
*
* @private
*/
__publicField(this, "_lodRangeMax", 99);
/**
* 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);
/**
* Monotonically increasing counter, bumped whenever this placement's splats need to be
* re-copied to the work buffer (parameter or modifier changes, or an explicit one-shot update
* request). Each consumer (a per-camera {@link GSplatInfo}) remembers the value it last
* copied at, so a single request re-copies every consumer of a shared placement exactly once,
* and child placements (octree files, environment) fan out from their parent's counter.
*
* @type {number}
* @ignore
*/
__publicField(this, "dirtyVersion", 0);
/**
* Work buffer update mode (see WORKBUFFER_UPDATE_*). WORKBUFFER_UPDATE_ONCE is not stored as a
* mode - it is converted into a single {@link dirtyVersion} bump.
*
* @type {number}
* @private
*/
__publicField(this, "_workBufferUpdate", WORKBUFFER_UPDATE_AUTO);
/**
* 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;
}
/**
* @type {number}
*/
set lodRangeMin(value) {
if (this._lodRangeMin !== value) {
this._lodRangeMin = value;
this.lodDirty = true;
}
}
get lodRangeMin() {
return this._lodRangeMin;
}
/**
* @type {number}
*/
set lodRangeMax(value) {
if (this._lodRangeMax !== value) {
this._lodRangeMax = value;
this.lodDirty = true;
}
}
get lodRangeMax() {
return this._lodRangeMax;
}
/**
* 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.dirtyVersion++;
}
/**
* 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;
}
/**
* Sets the work buffer update mode (see WORKBUFFER_UPDATE_*). WORKBUFFER_UPDATE_ONCE is turned
* into a single {@link dirtyVersion} bump so every consumer re-copies once, rather than being
* stored as a persistent mode.
*
* @type {number}
*/
set workBufferUpdate(value) {
if (value === WORKBUFFER_UPDATE_ONCE) {
this.dirtyVersion++;
} else {
this._workBufferUpdate = value;
}
}
/**
* Gets the work buffer update mode.
*
* @type {number}
*/
get workBufferUpdate() {
return this._workBufferUpdate;
}
/**
* Marks the placement as needing a one-time re-copy to the work buffer by all of its
* consumers.
*/
markDirty() {
this.dirtyVersion++;
}
/**
* 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
};