UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

233 lines (176 loc) 5.32 kB
import { assert } from "../../../core/assert.js"; import { computeHashArray } from "../../../core/collection/array/computeHashArray.js"; import { isArrayEqual } from "../../../core/collection/array/isArrayEqual.js"; import { invokeObjectHash } from "../../../core/model/object/invokeObjectHash.js"; import { invokeObjectToJSON } from "../../../core/model/object/invokeObjectToJSON.js"; import { AttributeSpec } from "./AttributeSpec.js"; /** * @readonly * @type {number} */ const RESERVED_HASH = 1234567; /** * Describes a set of data attributes. A good analogy would be "all data associated with geometry vertex" * @example "uv","position" and "normal" attributes of geometry vertices */ export class AttributeGroupSpec { /** * @readonly * @type {AttributeSpec[]} */ attributes = []; /** * Cached hash for speed * @type {number} * @private */ #hash = RESERVED_HASH; /** * * @param {AttributeSpec} attributes * @returns {AttributeGroupSpec} */ static from(...attributes) { const r = new AttributeGroupSpec(); r.addMany(attributes); return r; } /** * * @param {string} name * @return {AttributeSpec|undefined} */ getAttributeByName(name) { const attributes = this.attributes; const n = attributes.length; for (let i = 0; i < n; i++) { const attribute = attributes[i]; if (attribute.name === name) { return attribute; } } return undefined; } /** * @returns {number} -1 if not found * @param {string} name */ getAttributeIndexByName(name) { const attributes = this.attributes; const n = attributes.length; for (let i = 0; i < n; i++) { const attribute = attributes[i]; if (attribute.name === name) { return i; } } return -1; } /** * * @param {AttributeSpec[]} attributes * @throws {Error} if any there are any name collisions */ addMany(attributes) { const n = attributes.length; for (let i = 0; i < n; i++) { this.add(attributes[i]); } } /** * * @param {AttributeSpec} attribute * @return {AttributeGroupSpec} * @throws {Error} if attribute with that name is already present */ add(attribute) { assert.defined(attribute, 'attribute'); assert.equal(attribute.isAttributeSpec, true, 'attribute.isAttributeSpec !== true'); //check uniqueness of name if (this.getAttributeByName(attribute.name) !== undefined) { throw new Error(`Attribute named '${attribute.name}' already exists`); } this.attributes.push(attribute); // reset hash this.#hash = RESERVED_HASH; //for chaining, return self return this; } /** * Replace existing attributes * @param {AttributeSpec[]} attributes */ setAttributes(attributes) { this.clear(); this.addMany(attributes); } clear() { const attributes = this.attributes; const count = attributes.length; attributes.splice(0, count); // reset hash to trigger hash update this.#hash = RESERVED_HASH; } /** * * @param {AttributeGroupSpec} other * @returns {boolean} */ equals(other) { if (this === other) { return true; } return isArrayEqual(this.attributes, other.attributes); } computeHash() { const hash_value = computeHashArray(this.attributes, invokeObjectHash); if (hash_value === RESERVED_HASH) { // collision with reserved hash, replace return 0; } return hash_value; } hash() { if (this.#hash === RESERVED_HASH) { this.#hash = this.computeHash(); } return this.#hash; } toJSON() { return { attributes: this.attributes.map(invokeObjectToJSON) }; } fromJSON({ attributes = [] }) { this.clear(); const n = attributes.length; for (let i = 0; i < n; i++) { const aj = attributes[i]; const attribute = AttributeSpec.fromJSON(aj); this.add(attribute); } } /** * Size of a single element of all attributes. * @example 2 attributes float32 and uin8 will produce result of 5 ( = (32 + 8) / 8 ) * @returns {number} */ getByteSize() { let result = 0; const specs = this.attributes; const n = specs.length; for (let i = 0; i < n; i++) { const spec = specs[i]; const attribute_size = spec.getByteSize(); result += attribute_size; } return result; } } /** * @readonly * @type {boolean} */ AttributeGroupSpec.prototype.isAttributeGroupSpec = true;