three-stdlib
Version:
stand-alone library of threejs examples
426 lines (423 loc) • 15.8 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);
return value;
};
import { Mesh, BufferGeometry, MathUtils, DataTexture, RGBAFormat, FloatType, BufferAttribute, Matrix4 } from "three";
const ID_ATTR_NAME = "_batch_id_";
const _identityMatrix = /* @__PURE__ */ new Matrix4();
const _zeroScaleMatrix = /* @__PURE__ */ (() => new Matrix4().set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1))();
const batchingParsVertex = (
/* glsl */
`
#ifdef BATCHING
attribute float ${ID_ATTR_NAME};
uniform highp sampler2D batchingTexture;
mat4 getBatchingMatrix( const in float i ) {
int size = textureSize( batchingTexture, 0 ).x;
int j = int( i ) * 4;
int x = j % size;
int y = j / size;
vec4 v1 = texelFetch( batchingTexture, ivec2( x, y ), 0 );
vec4 v2 = texelFetch( batchingTexture, ivec2( x + 1, y ), 0 );
vec4 v3 = texelFetch( batchingTexture, ivec2( x + 2, y ), 0 );
vec4 v4 = texelFetch( batchingTexture, ivec2( x + 3, y ), 0 );
return mat4( v1, v2, v3, v4 );
}
#endif
`
);
const batchingbaseVertex = (
/* glsl */
`
#ifdef BATCHING
mat4 batchingMatrix = getBatchingMatrix( ${ID_ATTR_NAME} );
#endif
`
);
const batchingnormalVertex = (
/* glsl */
`
#ifdef BATCHING
objectNormal = vec4( batchingMatrix * vec4( objectNormal, 0.0 ) ).xyz;
#ifdef USE_TANGENT
objectTangent = vec4( batchingMatrix * vec4( objectTangent, 0.0 ) ).xyz;
#endif
#endif
`
);
const batchingVertex = (
/* glsl */
`
#ifdef BATCHING
transformed = ( batchingMatrix * vec4( transformed, 1.0 ) ).xyz;
#endif
`
);
function copyAttributeData(src, target, targetOffset = 0) {
const itemSize = target.itemSize;
if (src.isInterleavedBufferAttribute || src.array.constructor !== target.array.constructor) {
const vertexCount = src.count;
for (let i = 0; i < vertexCount; i++) {
for (let c = 0; c < itemSize; c++) {
target.setComponent(i + targetOffset, c, src.getComponent(i, c));
}
}
} else {
target.array.set(src.array, targetOffset * itemSize);
}
target.needsUpdate = true;
}
class BatchedMesh extends Mesh {
constructor(maxGeometryCount, maxVertexCount, maxIndexCount = maxVertexCount * 2, material) {
super(new BufferGeometry(), material);
__publicField(this, "_vertexStarts");
__publicField(this, "_vertexCounts");
__publicField(this, "_indexStarts");
__publicField(this, "_indexCounts");
__publicField(this, "_reservedRanges");
__publicField(this, "_visible");
__publicField(this, "_active");
__publicField(this, "_maxGeometryCount");
__publicField(this, "_maxVertexCount");
__publicField(this, "_maxIndexCount");
__publicField(this, "_geometryInitialized");
__publicField(this, "_geometryCount");
__publicField(this, "_matrices");
__publicField(this, "_matricesTexture");
__publicField(this, "_customUniforms");
this._vertexStarts = [];
this._vertexCounts = [];
this._indexStarts = [];
this._indexCounts = [];
this._reservedRanges = [];
this._visible = [];
this._active = [];
this._maxGeometryCount = maxGeometryCount;
this._maxVertexCount = maxVertexCount;
this._maxIndexCount = maxIndexCount;
this._geometryInitialized = false;
this._geometryCount = 0;
this._matrices = [];
this._matricesTexture = null;
this.frustumCulled = false;
this._customUniforms = {
batchingTexture: { value: null }
};
this._initMatricesTexture();
this._initShader();
this.onBeforeRender = function() {
if (this.material.defines) {
this.material.defines.BATCHING = true;
}
};
this.onAfterRender = function() {
if (this.material.defines) {
this.material.defines.BATCHING = false;
}
};
}
_initMatricesTexture() {
let size = Math.sqrt(this._maxGeometryCount * 4);
size = MathUtils.ceilPowerOfTwo(size);
size = Math.max(size, 4);
const matricesArray = new Float32Array(size * size * 4);
const matricesTexture = new DataTexture(matricesArray, size, size, RGBAFormat, FloatType);
this._matricesTexture = matricesTexture;
this._customUniforms.batchingTexture.value = this._matricesTexture;
}
_initShader() {
const material = this.material;
const currentOnBeforeCompile = material.onBeforeCompile;
const customUniforms = this._customUniforms;
material.onBeforeCompile = function onBeforeCompile(parameters, renderer) {
parameters.vertexShader = parameters.vertexShader.replace("#include <skinning_pars_vertex>", "#include <skinning_pars_vertex>\n" + batchingParsVertex).replace("#include <uv_vertex>", "#include <uv_vertex>\n" + batchingbaseVertex).replace("#include <skinnormal_vertex>", "#include <skinnormal_vertex>\n" + batchingnormalVertex).replace("#include <skinning_vertex>", "#include <skinning_vertex>\n" + batchingVertex);
for (const uniformName in customUniforms) {
parameters.uniforms[uniformName] = customUniforms[uniformName];
}
currentOnBeforeCompile.call(this, parameters, renderer);
};
material.defines = material.defines || {};
material.defines.BATCHING = false;
}
_initializeGeometry(reference) {
const geometry = this.geometry;
const maxVertexCount = this._maxVertexCount;
const maxGeometryCount = this._maxGeometryCount;
const maxIndexCount = this._maxIndexCount;
if (this._geometryInitialized === false) {
for (const attributeName in reference.attributes) {
const srcAttribute = reference.getAttribute(attributeName);
const { array, itemSize, normalized } = srcAttribute;
const dstArray = new array.constructor(maxVertexCount * itemSize);
const dstAttribute = new srcAttribute.constructor(dstArray, itemSize, normalized);
dstAttribute.setUsage(srcAttribute.usage);
geometry.setAttribute(attributeName, dstAttribute);
}
if (reference.getIndex() !== null) {
const indexArray = maxVertexCount > 65536 ? new Uint32Array(maxIndexCount) : new Uint16Array(maxIndexCount);
geometry.setIndex(new BufferAttribute(indexArray, 1));
}
const idArray = maxGeometryCount > 65536 ? new Uint32Array(maxVertexCount) : new Uint16Array(maxVertexCount);
geometry.setAttribute(ID_ATTR_NAME, new BufferAttribute(idArray, 1));
this._geometryInitialized = true;
}
}
// Make sure the geometry is compatible with the existing combined geometry atributes
_validateGeometry(geometry) {
if (geometry.getAttribute(ID_ATTR_NAME)) {
throw new Error(`BatchedMesh: Geometry cannot use attribute "${ID_ATTR_NAME}"`);
}
const batchGeometry = this.geometry;
if (Boolean(geometry.getIndex()) !== Boolean(batchGeometry.getIndex())) {
throw new Error('BatchedMesh: All geometries must consistently have "index".');
}
for (const attributeName in batchGeometry.attributes) {
if (attributeName === ID_ATTR_NAME) {
continue;
}
if (!geometry.hasAttribute(attributeName)) {
throw new Error(
`BatchedMesh: Added geometry missing "${attributeName}". All geometries must have consistent attributes.`
);
}
const srcAttribute = geometry.getAttribute(attributeName);
const dstAttribute = batchGeometry.getAttribute(attributeName);
if (srcAttribute.itemSize !== dstAttribute.itemSize || srcAttribute.normalized !== dstAttribute.normalized) {
throw new Error("BatchedMesh: All attributes must have a consistent itemSize and normalized value.");
}
}
}
getGeometryCount() {
return this._geometryCount;
}
getVertexCount() {
const reservedRanges = this._reservedRanges;
if (reservedRanges.length === 0) {
return 0;
} else {
const finalRange = reservedRanges[reservedRanges.length - 1];
return finalRange.vertexStart + finalRange.vertexCount;
}
}
getIndexCount() {
const reservedRanges = this._reservedRanges;
const geometry = this.geometry;
if (geometry.getIndex() === null || reservedRanges.length === 0) {
return 0;
} else {
const finalRange = reservedRanges[reservedRanges.length - 1];
return finalRange.indexStart + finalRange.indexCount;
}
}
addGeometry(geometry, vertexCount = -1, indexCount = -1) {
this._initializeGeometry(geometry);
this._validateGeometry(geometry);
if (this._geometryCount >= this._maxGeometryCount) {
throw new Error("BatchedMesh: Maximum geometry count reached.");
}
const range = {
vertexStart: -1,
vertexCount: -1,
indexStart: -1,
indexCount: -1
};
let lastRange = null;
const reservedRanges = this._reservedRanges;
if (this._geometryCount !== 0) {
lastRange = reservedRanges[reservedRanges.length - 1];
}
if (vertexCount === -1) {
range.vertexCount = geometry.getAttribute("position").count;
} else {
range.vertexCount = vertexCount;
}
if (lastRange === null) {
range.vertexStart = 0;
} else {
range.vertexStart = lastRange.vertexStart + lastRange.vertexCount;
}
if (geometry.getIndex() !== null) {
if (indexCount === -1) {
range.indexCount = geometry.getIndex().count;
} else {
range.indexCount = indexCount;
}
if (lastRange === null) {
range.indexStart = 0;
} else {
range.indexStart = lastRange.indexStart + lastRange.indexCount;
}
}
if (range.indexStart !== -1 && range.indexStart + range.indexCount > this._maxIndexCount || range.vertexStart + range.vertexCount > this._maxVertexCount) {
throw new Error("BatchedMesh: Reserved space request exceeds the maximum buffer size.");
}
const indexCounts = this._indexCounts;
const indexStarts = this._indexStarts;
const vertexCounts = this._vertexCounts;
const vertexStarts = this._vertexStarts;
const visible = this._visible;
const active = this._active;
const matricesTexture = this._matricesTexture;
const matrices = this._matrices;
const matricesArray = this._matricesTexture.image.data;
visible.push(true);
active.push(true);
const geometryId = this._geometryCount;
this._geometryCount++;
matrices.push(new Matrix4());
_identityMatrix.toArray(matricesArray, geometryId * 16);
matricesTexture.needsUpdate = true;
reservedRanges.push(range);
vertexStarts.push(range.vertexStart);
vertexCounts.push(range.vertexCount);
if (geometry.getIndex() !== null) {
indexStarts.push(range.indexCount);
indexCounts.push(range.indexCount);
}
const idAttribute = this.geometry.getAttribute(ID_ATTR_NAME);
for (let i = 0; i < range.vertexCount; i++) {
idAttribute.setX(range.vertexStart + i, geometryId);
}
idAttribute.needsUpdate = true;
this.setGeometryAt(geometryId, geometry);
return geometryId;
}
/**
* @deprecated use `addGeometry` instead.
*/
applyGeometry(geometry) {
return this.addGeometry(geometry);
}
setGeometryAt(id, geometry) {
if (id >= this._geometryCount) {
throw new Error("BatchedMesh: Maximum geometry count reached.");
}
this._validateGeometry(geometry);
const range = this._reservedRanges[id];
if (geometry.getIndex() !== null && geometry.getIndex().count > range.indexCount || geometry.attributes.position.count > range.vertexCount) {
throw new Error("BatchedMesh: Reserved space not large enough for provided geometry.");
}
const batchGeometry = this.geometry;
const srcPositionAttribute = geometry.getAttribute("position");
const hasIndex = batchGeometry.getIndex() !== null;
const dstIndex = batchGeometry.getIndex();
const srcIndex = geometry.getIndex();
const vertexStart = range.vertexStart;
const vertexCount = range.vertexCount;
for (const attributeName in batchGeometry.attributes) {
if (attributeName === ID_ATTR_NAME) {
continue;
}
const srcAttribute = geometry.getAttribute(attributeName);
const dstAttribute = batchGeometry.getAttribute(attributeName);
copyAttributeData(srcAttribute, dstAttribute, vertexStart);
const itemSize = srcAttribute.itemSize;
for (let i = srcAttribute.count, l = vertexCount; i < l; i++) {
const index = vertexStart + i;
for (let c = 0; c < itemSize; c++) {
dstAttribute.setComponent(index, c, 0);
}
}
dstAttribute.needsUpdate = true;
}
this._vertexCounts[id] = srcPositionAttribute.count;
if (hasIndex) {
const indexStart = range.indexStart;
for (let i = 0; i < srcIndex.count; i++) {
dstIndex.setX(indexStart + i, vertexStart + srcIndex.getX(i));
}
for (let i = srcIndex.count, l = range.indexCount; i < l; i++) {
dstIndex.setX(indexStart + i, vertexStart);
}
dstIndex.needsUpdate = true;
this._indexCounts[id] = srcIndex.count;
}
return id;
}
deleteGeometry(geometryId) {
const active = this._active;
const matricesTexture = this._matricesTexture;
const matricesArray = matricesTexture.image.data;
if (geometryId >= active.length || active[geometryId] === false) {
return this;
}
active[geometryId] = false;
_zeroScaleMatrix.toArray(matricesArray, geometryId * 16);
matricesTexture.needsUpdate = true;
return this;
}
optimize() {
throw new Error("BatchedMesh: Optimize function not implemented.");
}
setMatrixAt(geometryId, matrix) {
const visible = this._visible;
const active = this._active;
const matricesTexture = this._matricesTexture;
const matrices = this._matrices;
const matricesArray = matricesTexture.image.data;
if (geometryId >= matrices.length || active[geometryId] === false) {
return this;
}
if (visible[geometryId] === true) {
matrix.toArray(matricesArray, geometryId * 16);
matricesTexture.needsUpdate = true;
}
matrices[geometryId].copy(matrix);
return this;
}
getMatrixAt(geometryId, matrix) {
const matrices = this._matrices;
const active = this._active;
if (geometryId >= matrices.length || active[geometryId] === false) {
return matrix;
}
return matrix.copy(matrices[geometryId]);
}
setVisibleAt(geometryId, value) {
const visible = this._visible;
const active = this._active;
const matricesTexture = this._matricesTexture;
const matrices = this._matrices;
const matricesArray = matricesTexture.image.data;
if (geometryId >= visible.length || active[geometryId] === false || visible[geometryId] === value) {
return this;
}
if (value === true) {
matrices[geometryId].toArray(matricesArray, geometryId * 16);
} else {
_zeroScaleMatrix.toArray(matricesArray, geometryId * 16);
}
matricesTexture.needsUpdate = true;
visible[geometryId] = value;
return this;
}
getVisibleAt(geometryId) {
const visible = this._visible;
const active = this._active;
if (geometryId >= visible.length || active[geometryId] === false) {
return false;
}
return visible[geometryId];
}
raycast() {
console.warn("BatchedMesh: Raycast function not implemented.");
}
copy() {
throw new Error("BatchedMesh: Copy function not implemented.");
}
toJSON() {
throw new Error("BatchedMesh: toJSON function not implemented.");
}
dispose() {
this.geometry.dispose();
this._matricesTexture.dispose();
this._matricesTexture = null;
return this;
}
}
export {
BatchedMesh
};
//# sourceMappingURL=BatchedMesh.js.map