UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

337 lines (253 loc) • 9.56 kB
import { DataTexture, NearestFilter } from "three"; import { assert } from "../../../core/assert.js"; import { compute_typed_array_constructor_from_data_type } from "../../../core/binary/type/DataType2TypedArrayConstructorMapping.js"; import { DataTypeByteSizes } from "../../../core/binary/type/DataTypeByteSizes.js"; import { typed_array_copy } from "../../../core/collection/array/typed/typed_array_copy.js"; import { attribute_spec_to_three_format } from "./attribute_spec_to_three_format.js"; import { computeThreeTextureInternalFormatFromDataType } from "./computeThreeTextureInternalFormatFromDataType.js"; import { computeThreeTextureTypeFromDataType } from "./computeThreeTextureTypeFromDataType.js"; import { normalized_internal_format } from "./normalized_internal_format.js"; /** * * @param {AttributeSpec} spec * @returns {number} */ function attribute_spec_to_unpack_alignment(spec) { const byte_size = spec.getByteSize(); if (byte_size % 8 === 0) { return 8; } else if (byte_size % 4 === 0) { return 4; } else if (byte_size % 2 === 0) { return 2; } else { return 1; } } export class AttributeDataTexture { /** * * @param {AttributeSpec} spec * @param {number} column_count * @param {number} slot_width number of data points stored per slot */ constructor(spec, column_count, slot_width) { assert.defined(spec, 'spec'); assert.equal(spec.isAttributeSpec, true, 'spec.isAttributeSpec !== true'); assert.isNonNegativeInteger(column_count, 'column_count'); assert.greaterThan(column_count, 0, 'column_count > 0'); assert.isNonNegativeInteger(slot_width, 'slot_width'); assert.greaterThan(slot_width, 0, 'slot_width > 0'); /** * * @type {AttributeSpec} * @private */ this.__spec = spec; /** * In slots * @type {number} * @private */ this.__capacity = 16; /** * How many patches are written per texture row * @type {number} * @private */ this.__column_count = column_count; /** * * @type {DataTexture|null} * @private */ this.__texture = null; /** * Number of data points stored per slot * @type {number} * @private */ this.__slot_width = slot_width; this.build(); } /** * * @return {AttributeSpec} */ get spec() { return this.__spec; } clear() { const texture = this.__texture; if (texture === null) { return; } texture.image.data.fill(0); texture.needsUpdate = true; } computeSlotSize() { return this.__spec.itemSize * this.computeSlotWidth(); } computeSlotWidth() { return this.__slot_width; } /** * * @param {number} slot_count */ resize(slot_count) { assert.isNonNegativeInteger(slot_count, 'slot_count'); this.__capacity = slot_count; const spec = this.__spec; const column_count = this.__column_count; const width = this.computeSlotWidth() * column_count; const height = Math.ceil(this.__capacity / column_count); const texture = this.__texture; const image = texture.image; if (height === image.height) { // already right height return; } // console.log(`#AttributeDataTexture.resize(${slot_count})`); // DEBUG const old_data = image.data; const TypedArray = compute_typed_array_constructor_from_data_type(spec.type); const new_data = new TypedArray(width * height * spec.itemSize); // retain data typed_array_copy(old_data, new_data); // dispose of old bound data texture.dispose(); image.data = new_data; image.height = height; texture.needsUpdate = true; } build() { const spec = this.__spec; const TypedArray = compute_typed_array_constructor_from_data_type(spec.type); const width = this.computeSlotWidth() * this.__column_count; const height = Math.ceil(this.__capacity / this.__column_count); const format = attribute_spec_to_three_format(spec); const type = computeThreeTextureTypeFromDataType(spec.type); const array = new TypedArray(width * height * spec.itemSize); const texture = new DataTexture(array, width, height, format, type); // larger unpack alignments typically yield better performance, so we pick the largest possible texture.unpackAlignment = attribute_spec_to_unpack_alignment(spec); texture.name = spec.name; texture.generateMipmaps = false; texture.minFilter = texture.magFilter = NearestFilter; texture.format = format; texture.type = type; if (spec.normalized) { texture.internalFormat = normalized_internal_format(spec.type, spec.itemSize); } else { texture.internalFormat = computeThreeTextureInternalFormatFromDataType(spec.type, spec.itemSize); } texture.needsUpdate = true; this.__texture = texture; } /** * * @param {number} slot * @param {number[]} indices * @param {number[]} source * @returns {boolean} */ is_slot_data_equal(slot, indices, source) { const attribute_patch_slot_size = this.computeSlotSize(); const destination_attribute_address = attribute_patch_slot_size * slot; const item_size = this.__spec.itemSize; const attribute_target_array = this.data; const index_count = indices.length; let j, vertex_offset; for (vertex_offset = 0; vertex_offset < index_count; vertex_offset++) { const vertex_index = indices[vertex_offset]; const vertex_source_offset = vertex_index * item_size; const destination_vertex_offset = destination_attribute_address + vertex_offset * item_size; for (j = 0; j < item_size; j++) { const source_address = vertex_source_offset + j; const destination_address = destination_vertex_offset + j; const slot_value = attribute_target_array[destination_address]; const source_value = source[source_address]; if (slot_value !== source_value) { return false; } } } return true; } /** * * @param {number} slot * @param {number[]} indices triangle indices (index buffer) * @param {number[]} source vertex data */ write_slot_from_indexed(slot, indices, source) { assert.isNonNegativeInteger(slot, 'index'); assert.lessThan(slot, this.__capacity, 'overflow'); const attribute_patch_slot_size = this.computeSlotSize(); const destination_attribute_address = attribute_patch_slot_size * slot; const item_size = this.__spec.itemSize; const attribute_target_array = this.data; const index_count = indices.length; let j, vertex_offset; for (vertex_offset = 0; vertex_offset < index_count; vertex_offset++) { const vertex_index = indices[vertex_offset]; const vertex_source_offset = vertex_index * item_size; const destination_vertex_offset = destination_attribute_address + vertex_offset * item_size; for (j = 0; j < item_size; j++) { const source_address = vertex_source_offset + j; const destination_address = destination_vertex_offset + j; attribute_target_array[destination_address] = source[source_address]; } } // request update this.__texture.needsUpdate = true; } /** * * @param {number} index */ zero_fill_slot(index) { assert.isNonNegativeInteger(index, 'index'); assert.lessThan(index, this.__capacity, 'overflow'); // slot size const slot_size = this.computeSlotSize(); const start_index = slot_size * index; const end_index = slot_size * (index + 1); const texture = this.__texture; texture.image.data.fill(0, start_index, end_index); texture.needsUpdate = true; } /** * * @return {Uint8Array|Float32Array} */ get data() { return this.__texture.image.data; } /** * * @return {DataTexture} */ get texture() { return this.__texture; } /** * * @return {number} */ estimateByteSize() { return this.computeSlotSize() * this.__capacity * DataTypeByteSizes[this.__spec.type]; } /** * * @return {AttributeDataTexture} * @param {AttributeSpec} spec * @param {number} column_count * @param {number} slot_width */ static from(spec, column_count, slot_width) { return new AttributeDataTexture(spec, column_count, slot_width); } }