/**
* Attribute location used by vec3 position.
* Must match shader `layout(location = X)` declaration.
*
* @type {number}
*/
const POSITION_ATTRIBUTE_LOCATION = 0;
/**
* Number of float components per vertex position.
*
* @type {number}
*/
const POSITION_COMPONENT_COUNT = 3;
/**
* Attribute location used by vertex color.
* Must match shader `layout(location = X)` declaration.
*
* @type {number}
*/
const COLOR_ATTRIBUTE_LOCATION = 1;
/**
* Number of float components per vertex color.
*
* @type {number}
*/
const COLOR_COMPONENT_COUNT = 3;
/**
* Attribute location used by the UV coordinates.
* Must match shader `layout(location = X)` declaration.
*
* @type {number}
*/
const UV_ATTRIBUTE_LOCATION = 2;
/**
* Number of float components per UV coordinate.
*
* @type {number}
*/
const UV_COMPONENT_COUNT = 2;
/**
* Attribute location used by the normals.
* Must match shader `layout(location = X)` declaration.
*
* @type {number}
*/
const NORMAL_ATTRIBUTE_LOCATION = 3;
/**
* Number of float components per vertex normal.
*
* @type {number}
*/
const NORMAL_COMPONENT_COUNT = 3;
/**
* Flag passed to `vertexAttribPointer()` method.
* When false, attribute values are used as-is (no normalization).
*
* @type {boolean}
*/
const ATTRIBUTE_NORMALIZED = false;
/**
* Stride parameter for `vertexAttribPointer()`, when the attribute data is tightly packed.
* Zero means: compute stride automatically from attribute size and type.
*
* @type {number}
*/
const ATTRIBUTE_NO_STRIDE = 0;
/**
* Offset parameter for `vertexAttribPointer()` for attributes starting at the beginning of the buffer.
*
* @type {number}
*/
const ATTRIBUTE_NO_OFFSET = 0;
/**
* Modulo result expected for correct component alignment.
*
* @type {number}
*/
const MODULO_ALIGNED_VALUE = 0;
/**
* Component count for AABB min/max vectors.
*
* @type {number}
*/
const BOUNDING_BOX_COMPONENT_COUNT = 3;
/**
* Starting index for vertex data traversal.
*
* @type {number}
*/
const POSITION_START_INDEX = 0;
/**
* Offset for the X component in a position triplet.
*
* @type {number}
*/
const POSITION_X_OFFSET = 0;
/**
* Offset for the Y component in a position triplet.
*
* @type {number}
*/
const POSITION_Y_OFFSET = 1;
/**
* Offset for the Z component in a position triplet.
*
* @type {number}
*/
const POSITION_Z_OFFSET = 2;
/**
* Default bounding box component value for empty geometry.
*
* @type {number}
*/
const EMPTY_BOUND_COMPONENT = 0.0;
/**
* Initial value used for bounding box minimum computations.
*
* @type {number}
*/
const BOUND_MIN_INIT = Number.POSITIVE_INFINITY;
/**
* Initial value used for bounding box maximum computations.
*
* @type {number}
*/
const BOUND_MAX_INIT = Number.NEGATIVE_INFINITY;
/**
* Error message for invalid bounding box positions buffer.
*
* @type {string}
*/
const ERROR_BOUNDING_BOX_POSITIONS_TYPE = '`Geometry.#writeBoundingBox` expects positions as `Float32Array`.';
/**
* Error message for invalid bounding box minimum buffer.
*
* @type {string}
*/
const ERROR_BOUNDING_BOX_MIN_TYPE = '`Geometry.#writeBoundingBox` expects `outMin` as `Float32Array(3)`.';
/**
* Error message for invalid bounding box maximum buffer.
*
* @type {string}
*/
const ERROR_BOUNDING_BOX_MAX_TYPE = '`Geometry.#writeBoundingBox` expects `outMax` as `Float32Array(3)`.';
/**
* Number of indices, that form a single triangle.
*
* @type {number}
*/
const TRIANGLE_INDEX_COMPONENT_COUNT = 3;
/**
* Number of indices, that form a single line segment.
*
* @type {number}
*/
const LINE_INDEX_COMPONENT_COUNT = 2;
/**
* Primitive type name of the triangle meshes.
*
* @type {string}
*/
export const PRIMITIVE_TRIANGLES = 'triangles';
/**
* Primitive type name of the independent line segments.
*
* @type {string}
*/
export const PRIMITIVE_LINES = 'lines';
/**
* Primitive type name of the connected line strip.
*
* @type {string}
*/
export const PRIMITIVE_LINE_STRIP = 'line_strip';
/**
* Primitive type name of the closed line loop.
*
* @type {string}
*/
export const PRIMITIVE_LINE_LOOP = 'line_loop';
/**
* Primitive type name of the point sprites.
*
* @type {string}
*/
export const PRIMITIVE_POINTS = 'points';
/**
* Default solid primitive, used by geometry (triangles).
*
* @type {string}
*/
const DEFAULT_SOLID_PRIMITIVE = PRIMITIVE_TRIANGLES;
/**
* Default wireframe primitive, used by geometry (lines).
*
* @type {string}
*/
const DEFAULT_WIREFRAME_PRIMITIVE = PRIMITIVE_LINES;
/**
* Minimum index count for line strip/loop primitives.
*
* @type {number}
*/
const MIN_LINE_STRIP_INDEX_COUNT = 2;
/**
* Supported primitive names.
*
* @type {Set<string>}
*/
const SUPPORTED_PRIMITIVES = new Set([
PRIMITIVE_TRIANGLES,
PRIMITIVE_LINES,
PRIMITIVE_LINE_STRIP,
PRIMITIVE_LINE_LOOP,
PRIMITIVE_POINTS
]);
/**
* Error message used for invalid primitive options.
*
* @type {string}
*/
const ERROR_INVALID_PRIMITIVE = '`Geometry` expects the primitive options to use known primitive constants.';
/**
* Geometry primitive override options.
*
* @typedef {Object} GeometryPrimitiveOptions
* @property {string} [solidPrimitive=PRIMITIVE_TRIANGLES] - Solid primitive type.
* @property {string} [wireframePrimitive=PRIMITIVE_LINES] - Wireframe primitive type.
*/
/**
* Geometry represents a set of `vertex buffers + index buffers`, grouped under a VAO.
*/
export class Geometry {
/**
* WebGL2 rendering context used to create and manage GPU resources.
*
* @type {WebGL2RenderingContext}
* @private
*/
#webglContext;
/**
* Vertex Array Object (VAO) that stores vertex attribute bindings for this geometry.
*
* @type {WebGLVertexArrayObject}
* @private
*/
#vertexArrayObject;
/**
* GPU buffer that stores vertex positions.
*
* @type {WebGLBuffer}
* @private
*/
#positionBuffer;
/**
* Optional GPU buffer, that stores vertex colors.
* Used by materials that read `a_color` attribute.
*
* @type {WebGLBuffer | null}
* @private
*/
#colorBuffer;
/**
* Optional GPU buffer that stores texture coordinates.
* Used by textured materials, that read `a_uv` attribute.
*
* @type {WebGLBuffer | null}
* @private
*/
#uvBuffer;
/**
* Optional GPU buffer, that stores the vertex normals.
* Used by lit materials, that read `a_normal` attribute.
*
* @type {WebGLBuffer | null}
* @private
*/
#normalBuffer;
/**
* Index buffer for solid rendering mode (triangles).
*
* @type {WebGLBuffer}
* @private
*/
#indexBufferSolid;
/**
* Index buffer for wireframe rendering mode (lines).
*
* @type {WebGLBuffer}
* @private
*/
#indexBufferWireframe;
/**
* Number of indices in the solid index buffer.
*
* @type {number}
* @private
*/
#solidIndexCount;
/**
* Number of indices in the wireframe index buffer.
*
* @type {number}
* @private
*/
#wireframeIndexCount;
/**
* Index component type used for solid rendering.
*
* @type {number}
* @private
*/
#solidIndexComponentType;
/**
* Index component type used for wireframe rendering.
*
* @type {number}
* @private
*/
#wireframeIndexComponentType;
/**
* Local-space AABB minimum.
*
* @type {Float32Array}
* @private
*/
#boundingBoxMin;
/**
* Local-space AABB maximum.
*
* @type {Float32Array}
* @private
*/
#boundingBoxMax;
/**
* Indicates whether this geometry instance has been disposed.
* Disposed geometries must not be used for rendering.
*
* @type {boolean}
* @private
*/
#isDisposed = false;
/**
* Solid primitive type, used for rendering (triangles, lines, points, etc...).
*
* @type {string}
* @private
*/
#solidPrimitive;
/**
* Wireframe primitive type, used for rendering (lines, points, etc...).
*
* @type {string}
* @private
*/
#wireframePrimitive;
/**
* @param {WebGL2RenderingContext} webglContext - WebGL2 rendering context used to create and manage the GPU resources.
* @param {Float32Array} positions - [x, y, z] triples.
* @param {Float32Array | null} colors - [red, green, blue] triples or null.
* @param {Uint16Array | Uint32Array} indicesSolid - Indices for solid triangles.
* @param {Uint16Array | Uint32Array} indicesWireframe - Indices for wireframe lines.
* @param {Float32Array | null} [uvs = null] - [u, v] pairs or null.
* @param {Float32Array | null} [normals = null] - [x, y, z] triples or null.
* @param {GeometryPrimitiveOptions | null} [options] - Primitive overrides.
*/
constructor(
webglContext,
positions,
colors,
indicesSolid,
indicesWireframe,
uvs = null,
normals = null,
options = null
) {
if (!(webglContext instanceof WebGL2RenderingContext)) {
throw new TypeError('`Geometry` expects a `WebGL2RenderingContext`.');
}
if (!(positions instanceof Float32Array)) {
throw new TypeError('`Geometry` expects positions as `Float32Array`.');
}
if (colors !== null && !(colors instanceof Float32Array)) {
throw new TypeError('`Geometry` expects colors as `Float32Array` or null.');
}
if (uvs !== null && !(uvs instanceof Float32Array)) {
throw new TypeError('`Geometry` expects uvs as `Float32Array` or null.');
}
if (normals !== null && !(normals instanceof Float32Array)) {
throw new TypeError('`Geometry` expects normals as `Float32Array` or null.');
}
if (!Geometry.#isSupportedIndexArray(indicesSolid) || !Geometry.#isSupportedIndexArray(indicesWireframe)) {
throw new TypeError('`Geometry` expects indices as `Uint16Array` or `Uint32Array`.');
}
if (options !== null && (typeof options !== 'object' || Array.isArray(options))) {
throw new TypeError('`Geometry` expects `options` as a plain object or null.');
}
const {
solidPrimitive = DEFAULT_SOLID_PRIMITIVE,
wireframePrimitive = DEFAULT_WIREFRAME_PRIMITIVE
} = options || {};
Geometry.#assertPrimitiveName(solidPrimitive);
Geometry.#assertPrimitiveName(wireframePrimitive);
this.#validateAttributeSizes(positions, colors, uvs, normals);
this.#validateIndexSizes(indicesSolid, indicesWireframe, solidPrimitive, wireframePrimitive);
this.#webglContext = webglContext;
this.#solidIndexCount = indicesSolid.length;
this.#wireframeIndexCount = indicesWireframe.length;
this.#solidIndexComponentType = Geometry.#resolveIndexComponentType(webglContext, indicesSolid);
this.#wireframeIndexComponentType = Geometry.#resolveIndexComponentType(webglContext, indicesWireframe);
this.#vertexArrayObject = this.#createVertexArrayObject();
this.#positionBuffer = this.#createStaticArrayBuffer(positions);
this.#colorBuffer = colors ? this.#createStaticArrayBuffer(colors) : null;
this.#uvBuffer = uvs ? this.#createStaticArrayBuffer(uvs) : null;
this.#normalBuffer = normals ? this.#createStaticArrayBuffer(normals) : null;
this.#indexBufferSolid = this.#createIndexBuffer(indicesSolid);
this.#indexBufferWireframe = this.#createIndexBuffer(indicesWireframe);
this.#boundingBoxMin = new Float32Array(BOUNDING_BOX_COMPONENT_COUNT);
this.#boundingBoxMax = new Float32Array(BOUNDING_BOX_COMPONENT_COUNT);
this.#solidPrimitive = solidPrimitive;
this.#wireframePrimitive = wireframePrimitive;
Geometry.#writeBoundingBox(positions, this.#boundingBoxMin, this.#boundingBoxMax);
this.#configureVertexArray();
}
/**
* Binds the VAO of this geometry.
*/
bind() {
this.#assertNotDisposed();
this.#webglContext.bindVertexArray(this.#vertexArrayObject);
}
/**
* Binds the appropriate index buffer depending on the wireframe flag.
*
* @param {boolean} wireframe - Flag indicating whether the geometry should be drawn in wireframe mode.
*/
bindIndexBuffer(wireframe) {
this.#assertNotDisposed();
const buffer = wireframe ? this.#indexBufferWireframe : this.#indexBufferSolid;
this.#webglContext.bindBuffer(this.#webglContext.ELEMENT_ARRAY_BUFFER, buffer);
}
/**
* Returns the index count depending on the wireframe flag.
*
* @param {boolean} wireframe - Flag indicating whether the geometry should be drawn in wireframe mode.
* @returns {number}
*/
getIndexCount(wireframe) {
this.#assertNotDisposed();
return wireframe ? this.#wireframeIndexCount : this.#solidIndexCount;
}
/**
* Returns index component type constant used by `drawElements()`.
* This depends on whether the index buffer is `Uint16Array` or `Uint32Array`.
*
* @param {boolean} wireframe - When true, returns wireframe index component type.
* @returns {number} - WebGL component type constant.
*/
getIndexComponentType(wireframe) {
this.#assertNotDisposed();
return wireframe ? this.#wireframeIndexComponentType : this.#solidIndexComponentType;
}
/**
* Returns the primitive type for solid or wireframe rendering.
*
* @param {boolean} wireframe - When true, returns wireframe primitive type.
* @returns {string}
*/
getPrimitive(wireframe) {
this.#assertNotDisposed();
return wireframe ? this.#wireframePrimitive : this.#solidPrimitive;
}
/**
* Returns local-space AABB minimum.
*
* @returns {Float32Array}
*/
getBoundingBoxMin() {
this.#assertNotDisposed();
return this.#boundingBoxMin;
}
/**
* Returns local-space AABB maximum.
*
* @returns {Float32Array}
*/
getBoundingBoxMax() {
this.#assertNotDisposed();
return this.#boundingBoxMax;
}
/**
* Releases all GPU resources owned by this geometry (VAO and buffers).
* After calling dispose, this geometry instance must not be used for rendering.
*/
dispose() {
if (this.#isDisposed) {
return;
}
const webglContext = this.#webglContext;
webglContext.deleteBuffer(this.#positionBuffer);
if (this.#colorBuffer) {
webglContext.deleteBuffer(this.#colorBuffer);
this.#colorBuffer = null;
}
if (this.#uvBuffer) {
webglContext.deleteBuffer(this.#uvBuffer);
this.#uvBuffer = null;
}
if (this.#normalBuffer) {
webglContext.deleteBuffer(this.#normalBuffer);
this.#normalBuffer = null;
}
webglContext.deleteBuffer(this.#indexBufferSolid);
webglContext.deleteBuffer(this.#indexBufferWireframe);
webglContext.deleteVertexArray(this.#vertexArrayObject);
this.#isDisposed = true;
}
/**
* Creates a vertex array object (VAO).
*
* @returns {WebGLVertexArrayObject}
* @private
*/
#createVertexArrayObject() {
const vao = this.#webglContext.createVertexArray();
if (!vao) {
throw new Error('Failed to create vertex array object (VAO).');
}
return vao;
}
/**
* Creates a static `ARRAY_BUFFER` and uploads the given data.
*
* @param {Float32Array} data - Vertex attribute data stored as a flat array of numeric components.
* @returns {WebGLBuffer}
* @private
*/
#createStaticArrayBuffer(data) {
const buffer = this.#webglContext.createBuffer();
if (!buffer) {
throw new Error('Failed to create `ARRAY_BUFFER`.');
}
this.#webglContext.bindBuffer(this.#webglContext.ARRAY_BUFFER, buffer);
this.#webglContext.bufferData(this.#webglContext.ARRAY_BUFFER, data, this.#webglContext.STATIC_DRAW);
return buffer;
}
/**
* Creates an `ELEMENT_ARRAY_BUFFER` and uploads the given index data.
*
* @param {Uint16Array | Uint32Array} indices - Index data referencing vertices in the associated vertex buffers.
* @returns {WebGLBuffer}
* @private
*/
#createIndexBuffer(indices) {
const buffer = this.#webglContext.createBuffer();
if (!buffer) {
throw new Error('Failed to create `ELEMENT_ARRAY_BUFFER`.');
}
if (!Geometry.#isSupportedIndexArray(indices)) {
throw new TypeError('`Geometry` expects indices as `Uint16Array` or `Uint32Array`.');
}
this.#webglContext.bindBuffer(this.#webglContext.ELEMENT_ARRAY_BUFFER, buffer);
this.#webglContext.bufferData(this.#webglContext.ELEMENT_ARRAY_BUFFER, indices, this.#webglContext.STATIC_DRAW);
return buffer;
}
/**
* Checks whether an index array type is supported by `Geometry`.
*
* @param {unknown} indices - Value to test.
* @returns {boolean} - True if indices is `Uint16Array` or `Uint32Array`.
* @private
*/
static #isSupportedIndexArray(indices) {
return (indices instanceof Uint16Array) || (indices instanceof Uint32Array);
}
/**
* Resolves WebGL index component type constant for the given index array.
*
* @param {WebGL2RenderingContext} webglContext - WebGL2 context, that provides constants.
* @param {Uint16Array | Uint32Array} indices - Index buffer array.
* @returns {number} - `UNSIGNED_SHORT` or `UNSIGNED_INT`.
* @private
*/
static #resolveIndexComponentType(webglContext, indices) {
return (indices instanceof Uint32Array) ? webglContext.UNSIGNED_INT : webglContext.UNSIGNED_SHORT;
}
/**
* Validates vertex attribute array sizes (positions, colors, uvs, normals).
*
* @param {Float32Array} positions - Flat array of positions: `[x, y, z] * vertexCount`.
* @param {Float32Array | null} colors - Optional flat array of colors: `[red, green, blue] * vertexCount`.
* @param {Float32Array | null} uvs - Optional flat array of UVs: `[u, v] * vertexCount`.
* @param {Float32Array | null} normals - Optional flat array of normals: `[x, y, z] * vertexCount`.
* @private
*/
#validateAttributeSizes(positions, colors, uvs, normals) {
if ((positions.length % POSITION_COMPONENT_COUNT) !== MODULO_ALIGNED_VALUE) {
throw new Error('Geometry positions length must be a multiple of `POSITION_COMPONENT_COUNT`.');
}
const vertexCount = positions.length / POSITION_COMPONENT_COUNT;
if (colors !== null) {
if ((colors.length % COLOR_COMPONENT_COUNT) !== MODULO_ALIGNED_VALUE) {
throw new Error('Geometry colors length must be a multiple of `COLOR_COMPONENT_COUNT`.');
}
const colorVertexCount = colors.length / COLOR_COMPONENT_COUNT;
if (colorVertexCount !== vertexCount) {
throw new Error('Geometry colors vertex count must match positions vertex count.');
}
}
if (uvs !== null) {
if ((uvs.length % UV_COMPONENT_COUNT) !== MODULO_ALIGNED_VALUE) {
throw new Error('Geometry uvs length must be a multiple of `UV_COMPONENT_COUNT`.');
}
const uvVertexCount = uvs.length / UV_COMPONENT_COUNT;
if (uvVertexCount !== vertexCount) {
throw new Error('Geometry uvs vertex count must match positions vertex count.');
}
}
if (normals !== null) {
if ((normals.length % NORMAL_COMPONENT_COUNT) !== MODULO_ALIGNED_VALUE) {
throw new Error('Geometry normals length must be a multiple of `NORMAL_COMPONENT_COUNT`.');
}
const normalVertexCount = normals.length / NORMAL_COMPONENT_COUNT;
if (normalVertexCount !== vertexCount) {
throw new Error('Geometry normals vertex count must match positions vertex count.');
}
}
}
/**
* Validates basic index array structure (triangles + lines).
*
* @param {Uint16Array | Uint32Array} indicesSolid - Triangle index buffer data (3 indices per triangle).
* @param {Uint16Array | Uint32Array} indicesWireframe - Line index buffer data (2 indices per line segment).
* @private
*/
#validateIndexSizes(indicesSolid, indicesWireframe, solidPrimitive, wireframePrimitive) {
Geometry.#validateIndexSizeForPrimitive(indicesSolid, solidPrimitive, 'solid');
Geometry.#validateIndexSizeForPrimitive(indicesWireframe, wireframePrimitive, 'wireframe');
}
/**
* Validates the index buffer length based on the primitive type.
*
* @param {Uint16Array | Uint32Array} indices - Index buffer.
* @param {string} primitive - Primitive type name.
* @param {string} label - Buffer label for error messages.
* @private
*/
static #validateIndexSizeForPrimitive(indices, primitive, label) {
switch (primitive) {
case PRIMITIVE_TRIANGLES:
if ((indices.length % TRIANGLE_INDEX_COMPONENT_COUNT) !== MODULO_ALIGNED_VALUE) {
throw new Error(`Geometry ${label} indices length must be a multiple of TRIANGLE_INDEX_COMPONENT_COUNT.`);
}
return;
case PRIMITIVE_LINES:
if ((indices.length % LINE_INDEX_COMPONENT_COUNT) !== MODULO_ALIGNED_VALUE) {
throw new Error(`Geometry ${label} indices length must be a multiple of LINE_INDEX_COMPONENT_COUNT.`);
}
return;
case PRIMITIVE_LINE_STRIP:
case PRIMITIVE_LINE_LOOP:
if (indices.length < MIN_LINE_STRIP_INDEX_COUNT) {
throw new Error(`Geometry ${label} indices length must be at least ${MIN_LINE_STRIP_INDEX_COUNT}.`);
}
return;
case PRIMITIVE_POINTS:
return;
default:
throw new Error(ERROR_INVALID_PRIMITIVE);
}
}
/**
* Validates primitive name.
*
* @param {string} value - Primitive name.
* @private
*/
static #assertPrimitiveName(value) {
if (typeof value !== 'string' || !SUPPORTED_PRIMITIVES.has(value)) {
throw new TypeError(ERROR_INVALID_PRIMITIVE);
}
}
/**
* Writes local AABB bounds into the provided buffers.
*
* @param {Float32Array} positions - Flat vertex positions [x, y, z].
* @param {Float32Array} outMin - Output min buffer.
* @param {Float32Array} outMax - Output max buffer.
* @private
*/
static #writeBoundingBox(positions, outMin, outMax) {
if (!(positions instanceof Float32Array)) {
throw new TypeError(ERROR_BOUNDING_BOX_POSITIONS_TYPE);
}
if (!(outMin instanceof Float32Array) || outMin.length !== BOUNDING_BOX_COMPONENT_COUNT) {
throw new TypeError(ERROR_BOUNDING_BOX_MIN_TYPE);
}
if (!(outMax instanceof Float32Array) || outMax.length !== BOUNDING_BOX_COMPONENT_COUNT) {
throw new TypeError(ERROR_BOUNDING_BOX_MAX_TYPE);
}
// Handle the empty geometry, write `empty` bounds and exit early:
if (positions.length === POSITION_START_INDEX) {
outMin[POSITION_X_OFFSET] = EMPTY_BOUND_COMPONENT;
outMin[POSITION_Y_OFFSET] = EMPTY_BOUND_COMPONENT;
outMin[POSITION_Z_OFFSET] = EMPTY_BOUND_COMPONENT;
outMax[POSITION_X_OFFSET] = EMPTY_BOUND_COMPONENT;
outMax[POSITION_Y_OFFSET] = EMPTY_BOUND_COMPONENT;
outMax[POSITION_Z_OFFSET] = EMPTY_BOUND_COMPONENT;
return;
}
// Initialize min/max accumulators for the AABB computation:
let minX = BOUND_MIN_INIT;
let minY = BOUND_MIN_INIT;
let minZ = BOUND_MIN_INIT;
let maxX = BOUND_MAX_INIT;
let maxY = BOUND_MAX_INIT;
let maxZ = BOUND_MAX_INIT;
for (let index = POSITION_START_INDEX; index < positions.length; index += POSITION_COMPONENT_COUNT) {
const x = positions[index + POSITION_X_OFFSET];
const y = positions[index + POSITION_Y_OFFSET];
const z = positions[index + POSITION_Z_OFFSET];
if (x < minX) {
minX = x;
}
if (y < minY) {
minY = y;
}
if (z < minZ) {
minZ = z;
}
if (x > maxX) {
maxX = x;
}
if (y > maxY) {
maxY = y;
}
if (z > maxZ) {
maxZ = z;
}
}
// Write the computed AABB bounds to the output buffers:
outMin[POSITION_X_OFFSET] = minX;
outMin[POSITION_Y_OFFSET] = minY;
outMin[POSITION_Z_OFFSET] = minZ;
outMax[POSITION_X_OFFSET] = maxX;
outMax[POSITION_Y_OFFSET] = maxY;
outMax[POSITION_Z_OFFSET] = maxZ;
}
/**
* Configures the VAO with position (and optional color/uv) attribute pointers.
*
* @private
*/
#configureVertexArray() {
const webglContext = this.#webglContext;
webglContext.bindVertexArray(this.#vertexArrayObject);
// Positions:
webglContext.bindBuffer(webglContext.ARRAY_BUFFER, this.#positionBuffer);
webglContext.enableVertexAttribArray(POSITION_ATTRIBUTE_LOCATION);
webglContext.vertexAttribPointer(
POSITION_ATTRIBUTE_LOCATION,
POSITION_COMPONENT_COUNT,
webglContext.FLOAT,
ATTRIBUTE_NORMALIZED,
ATTRIBUTE_NO_STRIDE,
ATTRIBUTE_NO_OFFSET
);
// Colors (optional):
if (this.#colorBuffer) {
webglContext.bindBuffer(webglContext.ARRAY_BUFFER, this.#colorBuffer);
webglContext.enableVertexAttribArray(COLOR_ATTRIBUTE_LOCATION);
webglContext.vertexAttribPointer(
COLOR_ATTRIBUTE_LOCATION,
COLOR_COMPONENT_COUNT,
webglContext.FLOAT,
ATTRIBUTE_NORMALIZED,
ATTRIBUTE_NO_STRIDE,
ATTRIBUTE_NO_OFFSET
);
}
// UVs (optional):
if (this.#uvBuffer) {
webglContext.bindBuffer(webglContext.ARRAY_BUFFER, this.#uvBuffer);
webglContext.enableVertexAttribArray(UV_ATTRIBUTE_LOCATION);
webglContext.vertexAttribPointer(
UV_ATTRIBUTE_LOCATION,
UV_COMPONENT_COUNT,
webglContext.FLOAT,
ATTRIBUTE_NORMALIZED,
ATTRIBUTE_NO_STRIDE,
ATTRIBUTE_NO_OFFSET
);
}
// Normals (optional):
if (this.#normalBuffer) {
webglContext.bindBuffer(webglContext.ARRAY_BUFFER, this.#normalBuffer);
webglContext.enableVertexAttribArray(NORMAL_ATTRIBUTE_LOCATION);
webglContext.vertexAttribPointer(
NORMAL_ATTRIBUTE_LOCATION,
NORMAL_COMPONENT_COUNT,
webglContext.FLOAT,
ATTRIBUTE_NORMALIZED,
ATTRIBUTE_NO_STRIDE,
ATTRIBUTE_NO_OFFSET
);
}
// Bind the default index buffer (solid) to the VAO:
webglContext.bindBuffer(webglContext.ELEMENT_ARRAY_BUFFER, this.#indexBufferSolid);
webglContext.bindVertexArray(null);
}
/**
* @private
*/
#assertNotDisposed() {
if (this.#isDisposed) {
throw new Error('Geometry has been disposed and can no longer be used.');
}
}
}