@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
233 lines (176 loc) • 5.32 kB
JavaScript
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;