gpu-curtains
Version:
gpu-curtains is a 3D WebGPU rendering engine. It can be used as a standalone 3D engine, but also includes extra classes focused on mapping 3d objects to DOM elements; It allows users to synchronize values such as position, sizing, or scale between them.
177 lines (174 loc) • 6.39 kB
JavaScript
import { Geometry } from './Geometry.mjs';
import { Buffer } from '../buffers/Buffer.mjs';
import { Vec3 } from '../../math/Vec3.mjs';
class IndexedGeometry extends Geometry {
/**
* IndexedGeometry constructor
* @param parameters - {@link GeometryParams | parameters} used to create our IndexedGeometry
*/
constructor({
verticesOrder = "ccw",
topology = "triangle-list",
instancesCount = 1,
vertexBuffers = [],
mapBuffersAtCreation = true
} = {}) {
super({ verticesOrder, topology, instancesCount, vertexBuffers, mapBuffersAtCreation });
this.type = "IndexedGeometry";
}
/**
* Reset all the {@link vertexBuffers | vertex buffers} and {@link indexBuffer | index buffer} when the device is lost
*/
loseContext() {
super.loseContext();
if (this.indexBuffer) {
this.indexBuffer.buffer.destroy();
}
}
/**
* Restore the {@link IndexedGeometry} buffers on context restoration
* @param renderer - The {@link Renderer} used to recreate the buffers
*/
restoreContext(renderer) {
if (this.ready) return;
if (!this.indexBuffer.buffer.GPUBuffer) {
this.indexBuffer.buffer.createBuffer(renderer);
this.uploadBuffer(renderer, this.indexBuffer);
this.indexBuffer.buffer.consumers.add(this.uuid);
}
super.restoreContext(renderer);
}
/**
* Compute {@link IndexedGeometry} flat normals in case the `normal` attribute is missing.
*/
computeFlatNormals() {
const positionAttribute = this.getAttributeByName("position");
const vertex1 = new Vec3();
const vertex2 = new Vec3();
const vertex3 = new Vec3();
const edge1 = new Vec3();
const edge2 = new Vec3();
const normal = new Vec3();
const posLength = positionAttribute.array.length;
const normalArray = new Float32Array(posLength);
const nbIndices = this.indexBuffer.array.length;
for (let i = 0; i < nbIndices; i += 3) {
const i0 = this.indexBuffer.array[i] * 3;
const i1 = this.indexBuffer.array[i + 1] * 3;
const i2 = this.indexBuffer.array[i + 2] * 3;
if (posLength < i0 + 2) continue;
vertex1.set(positionAttribute.array[i0], positionAttribute.array[i0 + 1], positionAttribute.array[i0 + 2]);
if (posLength < i1 + 2) continue;
vertex2.set(positionAttribute.array[i1], positionAttribute.array[i1 + 1], positionAttribute.array[i1 + 2]);
if (posLength < i2 + 2) continue;
vertex3.set(positionAttribute.array[i2], positionAttribute.array[i2 + 1], positionAttribute.array[i2 + 2]);
this.computeNormalFromTriangle(vertex1, vertex2, vertex3, edge1, edge2, normal);
for (let j = 0; j < 3; j++) {
normalArray[this.indexBuffer.array[i + j] * 3] = normal.x;
normalArray[this.indexBuffer.array[i + j] * 3 + 1] = normal.y;
normalArray[this.indexBuffer.array[i + j] * 3 + 2] = normal.z;
}
}
this.setAttribute({
name: "normal",
type: "vec3f",
bufferFormat: "float32x3",
size: 3,
array: normalArray
});
}
/**
* If we have less than 65.536 vertices, we should use a Uin16Array to hold our index buffer values
* @readonly
*/
get useUint16IndexArray() {
return this.verticesCount < 256 * 256;
}
/**
* Set our {@link indexBuffer}
* @param parameters - {@link IndexedGeometryIndexBufferOptions | parameters} used to create our index buffer
*/
setIndexBuffer({
bufferFormat = "uint32",
array = new Uint32Array(0),
buffer = new Buffer(),
bufferOffset = 0,
bufferSize = null
}) {
this.indexBuffer = {
array,
bufferFormat,
bufferLength: array.length,
buffer,
bufferOffset,
bufferSize: bufferSize !== null ? bufferSize : array.length * array.constructor.BYTES_PER_ELEMENT
};
}
/**
* Set the {@link layoutCacheKey} and WGSL code snippet that will be appended to the vertex shader.
*/
setWGSLFragment() {
super.setWGSLFragment();
if (this.indexBuffer) {
this.layoutCacheKey += "indexFormat," + this.indexBuffer.bufferFormat + ",";
}
}
/**
* Create the {@link Geometry} {@link vertexBuffers | vertex buffers} and {@link indexBuffer | index buffer}.
* @param parameters - parameters used to create the vertex buffers.
* @param parameters.renderer - {@link Renderer} used to create the vertex buffers.
* @param parameters.label - label to use for the vertex buffers.
*/
createBuffers({ renderer, label = this.type }) {
if (!this.indexBuffer.buffer.GPUBuffer) {
this.indexBuffer.buffer.createBuffer(renderer, {
label: label + ": index buffer",
size: this.indexBuffer.array.byteLength,
usage: this.options.mapBuffersAtCreation ? ["index"] : ["copyDst", "index"],
mappedAtCreation: this.options.mapBuffersAtCreation
});
this.uploadBuffer(renderer, this.indexBuffer);
}
this.indexBuffer.buffer.consumers.add(this.uuid);
super.createBuffers({ renderer, label });
}
/** RENDER **/
/**
* First, set our render pass geometry vertex buffers
* Then, set our render pass geometry index buffer
* @param pass - current render pass
*/
setGeometryBuffers(pass) {
super.setGeometryBuffers(pass);
pass.setIndexBuffer(
this.indexBuffer.buffer.GPUBuffer,
this.indexBuffer.bufferFormat,
this.indexBuffer.bufferOffset,
this.indexBuffer.bufferSize
);
}
/**
* Draw our indexed geometry. Can use indirect drawing if {@link indirectDraw} is set up.
* @param pass - current render pass.
*/
drawGeometry(pass) {
if (this.indirectDraw && this.indirectDraw.buffer && this.indirectDraw.buffer.GPUBuffer) {
pass.drawIndexedIndirect(this.indirectDraw.buffer.GPUBuffer, this.indirectDraw.offset);
} else {
pass.drawIndexed(this.indexBuffer.bufferLength, this.instancesCount);
}
}
/**
* Destroy our indexed geometry vertex buffers and index buffer.
* @param renderer - current {@link Renderer}, in case we want to remove the {@link IndexBuffer#buffer | buffer} from the cache.
*/
destroy(renderer = null) {
super.destroy(renderer);
if (this.indexBuffer) {
this.indexBuffer.buffer.consumers.delete(this.uuid);
this.indexBuffer.buffer.destroy();
if (renderer) renderer.removeBuffer(this.indexBuffer.buffer);
}
}
}
export { IndexedGeometry };