playcanvas
Version:
PlayCanvas WebGL game engine
141 lines (138 loc) • 5.98 kB
JavaScript
import { Debug } from '../../core/debug.js';
import { Mat4 } from '../../core/math/mat4.js';
import { Vec4 } from '../../core/math/vec4.js';
import { BoundingBox } from '../../core/shape/bounding-box.js';
import { GSplatIntervalTexture } from './gsplat-interval-texture.js';
/**
* @import { GraphicsDevice } from "../../platform/graphics/graphics-device.js";
* @import { GSplatResourceBase } from "../gsplat/gsplat-resource-base.js"
* @import { GSplatPlacement } from "./gsplat-placement.js"
* @import { GraphNode } from '../graph-node.js';
* @import { Vec2 } from '../../core/math/vec2.js';
*/ /** @type {Vec2[]} */ const vecs = [];
/**
* @ignore
*/ class GSplatInfo {
/**
* Create a new GSplatInfo.
*
* @param {GraphicsDevice} device - The graphics device.
* @param {GSplatResourceBase} resource - The splat resource.
* @param {GSplatPlacement} placement - The placement of the splat.
*/ constructor(device, resource, placement){
/** @type {number} */ this.activeSplats = 0;
/**
* Array of intervals for remapping of indices, each two consecutive numbers represent
* start and end of a range of splats.
*
* @type {number[]}
*/ this.intervals = [];
/** @type {number} */ this.lineStart = 0;
/** @type {number} */ this.lineCount = 0;
/** @type {number} */ this.padding = 0;
/** @type {Vec4} */ this.viewport = new Vec4();
/** @type {Mat4} */ this.previousWorldTransform = new Mat4();
/** @type {BoundingBox} */ this.aabb = new BoundingBox();
/**
* Manager for the intervals texture generation
*
* @type {GSplatIntervalTexture|null}
*/ this.intervalTexture = null;
/** @type {number} */ this.colorAccumulatedRotation = 0;
/** @type {number} */ this.colorAccumulatedTranslation = 0;
Debug.assert(resource);
Debug.assert(placement);
this.device = device;
this.resource = resource;
this.node = placement.node;
this.lodIndex = placement.lodIndex;
this.numSplats = resource.numSplats;
this.aabb.copy(placement.aabb);
this.updateIntervals(placement.intervals);
}
destroy() {
this.intervals.length = 0;
this.intervalTexture?.destroy();
}
setLines(start, count, textureSize, activeSplats) {
this.lineStart = start;
this.lineCount = count;
this.padding = textureSize * count - activeSplats;
Debug.assert(this.padding >= 0);
this.viewport.set(0, start, textureSize, count);
}
/**
* Updates the flattened intervals array and GPU texture from placement intervals.
*
* @param {Map<number, Vec2>} intervals - Map of node index to inclusive [x, y] intervals.
*/ updateIntervals(intervals) {
const resource = this.resource;
this.intervals.length = 0;
this.activeSplats = resource.numSplats;
// If placement has intervals defined
if (intervals.size > 0) {
// fast path: if the intervals cover the full range, intervals are not needed
// also collect references to inclusive Vec2 intervals into a reusable array for sorting
let totalCount = 0;
let used = 0;
for (const interval of intervals.values()){
totalCount += interval.y - interval.x + 1;
vecs[used++] = interval;
}
// not full range
if (totalCount !== this.numSplats) {
// finalize temp array length for sorting/merging
vecs.length = used;
// sort by start
vecs.sort((a, b)=>a.x - b.x);
// pre-size to the upper bound
this.intervals.length = used * 2;
// write merged intervals directly to this.intervals
let k = 0;
let currentStart = vecs[0].x;
let currentEnd = vecs[0].y;
for(let i = 1; i < used; i++){
const p = vecs[i];
if (p.x === currentEnd + 1) {
currentEnd = p.y;
} else {
this.intervals[k++] = currentStart;
this.intervals[k++] = currentEnd + 1;
currentStart = p.x;
currentEnd = p.y;
}
}
// write final half-open pair
this.intervals[k++] = currentStart;
this.intervals[k++] = currentEnd + 1;
// trim to actual merged length
this.intervals.length = k;
// update GPU texture and active splats count
this.intervalTexture = new GSplatIntervalTexture(this.device);
this.activeSplats = this.intervalTexture.update(this.intervals, totalCount);
}
// clear temp array
vecs.length = 0;
}
}
update() {
// if the object's matrix has changed, store the update version to know when it happened
const worldMatrix = this.node.getWorldTransform();
const worldMatrixChanged = !this.previousWorldTransform.equals(worldMatrix);
if (worldMatrixChanged) {
this.previousWorldTransform.copy(worldMatrix);
}
return worldMatrixChanged;
}
resetColorAccumulators(colorUpdateAngle, colorUpdateDistance) {
// Use a single random factor (0 to 1) for both accumulators to keep them synchronized
// This ensures rotation and translation thresholds trigger at similar rates
const randomFactor = Math.random();
this.colorAccumulatedRotation = randomFactor * colorUpdateAngle;
this.colorAccumulatedTranslation = randomFactor * colorUpdateDistance;
}
get hasSphericalHarmonics() {
return this.resource.gsplatData?.shBands > 0;
}
}
export { GSplatInfo };