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