playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
218 lines (217 loc) • 6.91 kB
JavaScript
import { Mat4 } from "../../core/math/mat4.js";
import { Vec2 } from "../../core/math/vec2.js";
import { BoundingBox } from "../../core/shape/bounding-box.js";
import { PIXELFORMAT_RGBA32U } from "../../platform/graphics/constants.js";
import { Texture } from "../../platform/graphics/texture.js";
import { TextureUtils } from "../../platform/graphics/texture-utils.js";
const tmpSize = new Vec2();
let subDrawDataArray = new Uint32Array(0);
const _fullRangeInterval = [0, 0];
class GSplatInfo {
device;
resource;
node;
lodIndex;
placementId;
allocId;
parentPlacementId;
numSplats;
activeSplats = 0;
intervals = [];
intervalOffsets = [];
intervalAllocIds = [];
intervalNodeIndices = [];
previousWorldTransform = new Mat4();
aabb = new BoundingBox();
subDrawTexture = null;
subDrawCount = 0;
numBoundsEntries = 0;
boundsBaseIndex = 0;
octreeNodes = null;
nodeInfos = null;
colorAccumulatedTranslation = 0;
parameters = null;
getWorkBufferModifier = null;
getInstanceStreams = null;
_consumeRenderDirty = null;
constructor(device, resource, placement, consumeRenderDirty = null, octreeNodes = null, nodeInfos = null) {
this.device = device;
this.resource = resource;
this.node = placement.node;
this.lodIndex = placement.lodIndex;
this.placementId = placement.id;
this.allocId = placement.allocId;
this.parentPlacementId = octreeNodes && placement.parentPlacement ? placement.parentPlacement.allocId : placement.allocId;
this.numSplats = resource.numSplats;
this.aabb.copy(placement.aabb);
this.parameters = placement.parameters;
this.getWorkBufferModifier = () => placement.workBufferModifier;
this.getInstanceStreams = () => placement.streams;
this._consumeRenderDirty = consumeRenderDirty;
this.octreeNodes = octreeNodes;
this.nodeInfos = nodeInfos;
this.updateIntervals(placement.intervals);
}
destroy() {
this.intervals.length = 0;
this.intervalOffsets.length = 0;
this.intervalAllocIds.length = 0;
this.intervalNodeIndices.length = 0;
this.subDrawTexture?.destroy();
this.subDrawTexture = null;
this.subDrawCount = 0;
}
setLayout(intervalOffsets) {
this.intervalOffsets = intervalOffsets;
this.subDrawTexture?.destroy();
this.subDrawTexture = null;
this.subDrawCount = 0;
}
ensureSubDrawTexture(textureWidth) {
if (!this.subDrawTexture && textureWidth > 0) {
this.updateSubDraws(textureWidth);
}
}
updateIntervals(intervals) {
const resource = this.resource;
this.intervals.length = 0;
this.intervalAllocIds.length = 0;
this.intervalNodeIndices.length = 0;
this.activeSplats = resource.numSplats;
if (intervals.size > 0) {
let totalCount = 0;
let k = 0;
this.intervals.length = intervals.size * 2;
for (const [nodeIndex, interval] of intervals) {
this.intervals[k++] = interval.x;
this.intervals[k++] = interval.y + 1;
totalCount += interval.y - interval.x + 1;
if (this.nodeInfos) {
this.intervalAllocIds.push(this.nodeInfos[nodeIndex].allocId);
this.intervalNodeIndices.push(nodeIndex);
}
}
if (this.octreeNodes) {
this.activeSplats = totalCount;
this.numBoundsEntries = this.octreeNodes.length;
} else if (totalCount === this.numSplats) {
this.intervals.length = 0;
} else {
this.activeSplats = totalCount;
}
} else {
this.numBoundsEntries = 1;
this.intervalAllocIds.push(this.allocId);
const totalCapacity = resource.maxSplats;
if (totalCapacity && this.activeSplats < totalCapacity) {
this.intervals[0] = 0;
this.intervals[1] = this.activeSplats;
}
}
}
appendSubDraws(subDrawData, subDrawCount, sourceBase, size, targetOffset, textureWidth) {
let remaining = size;
let row = targetOffset / textureWidth | 0;
const col = targetOffset % textureWidth;
if (col > 0) {
const count = Math.min(remaining, textureWidth - col);
const idx = subDrawCount * 4;
subDrawData[idx] = row | 1 << 16;
subDrawData[idx + 1] = col;
subDrawData[idx + 2] = col + count;
subDrawData[idx + 3] = sourceBase;
subDrawCount++;
sourceBase += count;
remaining -= count;
row++;
}
const fullRows = remaining / textureWidth | 0;
if (fullRows > 0) {
const idx = subDrawCount * 4;
subDrawData[idx] = row | fullRows << 16;
subDrawData[idx + 1] = 0;
subDrawData[idx + 2] = textureWidth;
subDrawData[idx + 3] = sourceBase;
subDrawCount++;
sourceBase += fullRows * textureWidth;
remaining -= fullRows * textureWidth;
row += fullRows;
}
if (remaining > 0) {
const idx = subDrawCount * 4;
subDrawData[idx] = row | 1 << 16;
subDrawData[idx + 1] = 0;
subDrawData[idx + 2] = remaining;
subDrawData[idx + 3] = sourceBase;
subDrawCount++;
}
return subDrawCount;
}
updateSubDraws(textureWidth) {
let intervals = this.intervals;
let numIntervals = intervals.length / 2;
if (numIntervals === 0) {
_fullRangeInterval[0] = 0;
_fullRangeInterval[1] = this.activeSplats;
intervals = _fullRangeInterval;
numIntervals = 1;
}
const maxSubDraws = numIntervals * 3;
const requiredSize = maxSubDraws * 4;
if (subDrawDataArray.length < requiredSize) {
subDrawDataArray = new Uint32Array(requiredSize);
}
const subDrawData = subDrawDataArray;
let subDrawCount = 0;
for (let i = 0; i < numIntervals; i++) {
subDrawCount = this.appendSubDraws(
subDrawData,
subDrawCount,
intervals[i * 2],
intervals[i * 2 + 1] - intervals[i * 2],
this.intervalOffsets[i],
textureWidth
);
}
this.subDrawCount = subDrawCount;
const { x: texWidth, y: texHeight } = TextureUtils.calcTextureSize(subDrawCount, tmpSize);
this.subDrawTexture = Texture.createDataTexture2D(this.device, "subDrawData", texWidth, texHeight, PIXELFORMAT_RGBA32U);
const texData = this.subDrawTexture.lock();
texData.set(subDrawData.subarray(0, subDrawCount * 4));
this.subDrawTexture.unlock();
}
update() {
const worldMatrix = this.node.getWorldTransform();
const worldMatrixChanged = !this.previousWorldTransform.equals(worldMatrix);
if (worldMatrixChanged) {
this.previousWorldTransform.copy(worldMatrix);
}
const renderDirty = this._consumeRenderDirty ? this._consumeRenderDirty() : false;
return worldMatrixChanged || renderDirty;
}
writeBoundsSpheres(data, offset) {
if (this.octreeNodes) {
for (let i = 0; i < this.octreeNodes.length; i++) {
const s = this.octreeNodes[i].boundingSphere;
data[offset++] = s.x;
data[offset++] = s.y;
data[offset++] = s.z;
data[offset++] = s.w;
}
} else {
const aabb = this.resource.aabb;
const he = aabb.halfExtents;
const r = Math.sqrt(he.x * he.x + he.y * he.y + he.z * he.z);
data[offset++] = aabb.center.x;
data[offset++] = aabb.center.y;
data[offset++] = aabb.center.z;
data[offset++] = r;
}
}
get hasSphericalHarmonics() {
return this.resource.gsplatData?.shBands > 0;
}
}
export {
GSplatInfo
};