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.
203 lines (202 loc) • 7.36 kB
JavaScript
import { Vec3 } from "../../math/Vec3.mjs";
import { Buffer } from "../buffers/Buffer.mjs";
import { Geometry } from "./Geometry.mjs";
//#region src/core/geometries/IndexedGeometry.ts
/**
* Used to create an {@link IndexedGeometry} which holds an index array to use as an index buffer.
*
* The index array represents the order in which the attributes should be processed. This allows to create smaller vertex, uv and normal arrays.
*
* During the {@link IndexedGeometry#render | render}, the {@link IndexedGeometry} is responsible for setting the {@link IndexedGeometry#vertexBuffers | vertexBuffers} and drawing the indexed vertices.
*
* @example
* ```javascript
* const vertices = new Float32Array([
* -1, -1, 0,
* 1, -1, 0,
* -1, 1, 0,
* 1, 1, 0
* ])
*
* // vertices index (order in which they should be drawn)
* const indexArray = new Uint16Array([0, 2, 1, 1, 2, 3])
*
* // create an indexed quad geometry made of 4 vertices
* const indexedGeometry = new IndexedGeometry()
*
* indexedGeometry.setAttribute({
* name: 'position',
* type: 'vec3f',
* bufferFormat: 'float32x3',
* size: 3,
* bufferLength: vertices.length,
* array: vertices,
* })
*
* indexedGeometry.setIndexBuffer({
* array: indexArray,
* bufferFormat: 'uint16',
* })
* ```
*/
var IndexedGeometry = class 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 }) {
const arrayBuffer = new ArrayBuffer(array.byteLength);
new Uint8Array(arrayBuffer).set(new Uint8Array(array.buffer, array.byteOffset, array.byteLength));
this.indexBuffer = {
array,
bufferFormat,
bufferLength: array.length,
buffer,
arrayBuffer,
bufferOffset,
bufferSize: bufferSize !== null ? bufferSize : array.byteLength
};
}
/**
* 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 ?? this.indexBuffer.bufferSize,
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.arrayBuffer = null;
this.indexBuffer.buffer.consumers.delete(this.uuid);
this.indexBuffer.buffer.destroy();
if (renderer) renderer.removeBuffer(this.indexBuffer.buffer);
}
}
};
//#endregion
export { IndexedGeometry };