@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
1,098 lines (857 loc) • 31.2 kB
JavaScript
import { assert } from "../../../../core/assert.js";
import { Base64 } from "../../../../core/binary/base64/Base64.js";
import {
compute_typed_array_constructor_from_data_type
} from "../../../../core/binary/type/DataType2TypedArrayConstructorMapping.js";
import { array_buffer_hash } from "../../../../core/collection/array/typed/array_buffer_hash.js";
import {
compute_binary_data_type_from_typed_array
} from "../../../../core/collection/array/typed/compute_binary_data_type_from_typed_array.js";
import { is_typed_array_equals } from "../../../../core/collection/array/typed/is_typed_array_equals.js";
import { isTypedArray } from "../../../../core/collection/array/typed/isTypedArray.js";
import {
typedArrayConstructorByInstance
} from "../../../../core/collection/array/typed/typedArrayConstructorByInstance.js";
import { clamp } from "../../../../core/math/clamp.js";
import { lerp } from "../../../../core/math/lerp.js";
import { max2 } from "../../../../core/math/max2.js";
import { min2 } from "../../../../core/math/min2.js";
import { interpolate_bicubic } from "../../../../core/math/spline/interpolate_bicubic.js";
import { computeHashFloat } from "../../../../core/primitives/numbers/computeHashFloat.js";
/**
* Data Texture class, providing an abstraction around 2d numerical arrays, mostly for sampling purposes
* Inspired by OpenGL's glsl sampler2d API
* @class
* @author Alex Goldring
* @copyright Company Named Limited (c) 2025
*/
export class Sampler2D {
/**
*
* @param {ArrayLike<number>|number[]|Uint8ClampedArray|Uint8Array|Uint16Array|Uint32Array|Int8Array|Int16Array|Int32Array|Float32Array|Float64Array} data
* @param {number} [itemSize]
* @param {number} [width]
* @param {number} [height]
* @constructor
*/
constructor(data = [], itemSize = 1, width = 0, height = 0) {
if (!Number.isInteger(itemSize) || itemSize < 0) {
throw new Error(`itemSize must be a non-negative integer, instead was ${itemSize}`);
}
if (!Number.isInteger(width) || width < 0) {
throw new Error(`width must be a non-negative integer, instead was ${width}`);
}
if (!Number.isInteger(height) || width < 0) {
throw new Error(`height must be a non-negative integer, instead was ${height}`);
}
if (data === undefined) {
throw new Error("data was undefined");
}
if (data.length < width * height * itemSize) {
throw new Error(`Buffer underflow, data.length(=${data.length}) is too small. Expected at least ${width * height * itemSize}`);
}
/**
*
* @type {number}
*/
this.width = width;
/**
*
* @type {number}
*/
this.height = height;
/**
* Number of channels
* @type {number}
*/
this.itemSize = itemSize;
/**
*
* @type {number[]|Uint8ClampedArray|Uint8Array|Uint16Array|Uint32Array|Int8Array|Int16Array|Int32Array|Float32Array|Float64Array}
*/
this.data = data;
/**
* Used to tracking changes
* @type {number}
*/
this.version = 0;
}
/**
*
* @param {number} u
* @param {number} v
* @param {number[]} result
*/
sampleCatmullRomUV(u, v, result) {
const itemSize = this.itemSize;
for (let i = 0; i < itemSize; i++) {
result[i] = this.sampleChannelCatmullRomUV(u, v, i);
}
}
/**
*
* @param {number} u
* @param {number} v
* @param {number} channel
* @returns {number}
*/
sampleChannelCatmullRomUV(u, v, channel) {
const x = u * this.width - 0.5;
const y = v * this.height - 0.5;
return this.sampleChannelCatmullRom(x, y, channel);
}
/**
*
* @see https://gist.github.com/TheRealMJP/c83b8c0f46b63f3a88a5986f4fa982b1
* @param {number} x
* @param {number} y
* @param {number} channel
* @returns {number}
*/
sampleChannelCatmullRom(x, y, channel) {
// We're going to sample a 4x4 grid of texels surrounding the target UV coordinate. We'll do this by rounding
// down the sample location to get the exact center of our "starting" texel. The starting texel will be at
// location [1, 1] in the grid, where [0, 0] is the top-left corner.
const texPos1_x = Math.floor(x);
const texPos1_y = Math.floor(y);
// Compute the fractional offset from our starting texel to our original sample location, which we'll
// feed into the Catmull-Rom spline function to get our filter weights.
const f_x = x - texPos1_x;
const f_y = y - texPos1_y;
// Compute the Catmull-Rom weights using the fractional offset that we calculated earlier.
// These equations are pre-expanded based on our knowledge of where the texels will be located,
// which lets us avoid having to evaluate a piece-wise function.
const w0_x = f_x * (-0.5 + f_x * (1.0 - 0.5 * f_x));
const w0_y = f_y * (-0.5 + f_y * (1.0 - 0.5 * f_y));
const w1_x = 1.0 + f_x * f_x * (-2.5 + 1.5 * f_x);
const w1_y = 1.0 + f_y * f_y * (-2.5 + 1.5 * f_y);
const w2_x = f_x * (0.5 + f_x * (2.0 - 1.5 * f_x));
const w2_y = f_y * (0.5 + f_y * (2.0 - 1.5 * f_y));
const w3_x = f_x * f_x * (-0.5 + 0.5 * f_x);
const w3_y = f_y * f_y * (-0.5 + 0.5 * f_y);
// Work out weighting factors and sampling offsets that will let us use bilinear filtering to
// simultaneously evaluate the middle 2 samples from the 4x4 grid.
const w12_x = w1_x + w2_x;
const w12_y = w1_y + w2_y;
const offset12_x = w2_x / w12_x;
const offset12_y = w2_y / w12_y;
// Compute the final coordinates we'll use for sampling the texture
const texPos0_x = texPos1_x - 1;
const texPos0_y = texPos1_y - 1;
const texPos3_x = texPos1_x + 2;
const texPos3_y = texPos1_y + 2;
const texPos12_x = texPos1_x + offset12_x;
const texPos12_y = texPos1_y + offset12_y;
let result = 0.0;
result += this.sampleChannelBilinear(texPos0_x, texPos0_y, channel) * w0_x * w0_y;
result += this.sampleChannelBilinear(texPos12_x, texPos0_y, channel) * w12_x * w0_y;
result += this.sampleChannelBilinear(texPos3_x, texPos0_y, channel) * w3_x * w0_y;
result += this.sampleChannelBilinear(texPos0_x, texPos12_y, channel) * w0_x * w12_y;
result += this.sampleChannelBilinear(texPos12_x, texPos12_y, channel) * w12_x * w12_y;
result += this.sampleChannelBilinear(texPos3_x, texPos12_y, channel) * w3_x * w12_y;
result += this.sampleChannelBilinear(texPos0_x, texPos3_y, channel) * w0_x * w3_y;
result += this.sampleChannelBilinear(texPos12_x, texPos3_y, channel) * w12_x * w3_y;
result += this.sampleChannelBilinear(texPos3_x, texPos3_y, channel) * w3_x * w3_y;
return result;
}
/**
*
* @param {number} u
* @param {number} v
* @param {number[]} result
*/
sampleBicubicUV(u, v, result) {
const itemSize = this.itemSize;
for (let i = 0; i < itemSize; i++) {
result[i] = this.sampleChannelBicubicUV(u, v, i);
}
}
/**
*
* @param {number} x
* @param {number} y
* @param {number[]|Float32Array|Float64Array} result
* @param {number} result_offset
*/
sampleBicubic(x, y, result, result_offset) {
const itemSize = this.itemSize;
for (let i = 0; i < itemSize; i++) {
result[i + result_offset] = this.sampleChannelBicubic(x, y, i);
}
}
/**
*
* @param {number} u
* @param {number} v
* @param {number} channel
* @returns {number}
*/
sampleChannelBicubicUV(u, v, channel) {
const x = u * (this.width);
const y = v * (this.height);
return this.sampleChannelBicubic(x - 0.5, y - 0.5, channel);
}
/**
*
* Bicubic-filtered sampling, note values can be negative due to the nature of the cubic curve
* @param {number} x
* @param {number} y
* @param {number} channel
* @returns {number}
*/
sampleChannelBicubic(x, y, channel) {
const itemSize = this.itemSize;
const width = this.width;
const height = this.height;
const data = this.data;
const rowSize = width * itemSize;
const x_max = width - 1;
const y_max = height - 1;
const clamped_x = clamp(x, 0, x_max)
const clamped_y = clamp(y, 0, y_max)
// fractional texel position (from the nearest low texel)
const x1 = clamped_x | 0;
const y1 = clamped_y | 0;
const xd = clamped_x - x1;
const yd = clamped_y - y1;
const x0 = max2(0, x1 - 1);
const y0 = max2(0, y1 - 1);
const x2 = min2(x_max, x1 + 1);
const y2 = min2(y_max, y1 + 1);
const x3 = min2(x_max, x2 + 1);
const y3 = min2(y_max, y2 + 1);
// compute row offsets
const row0 = y0 * rowSize;
const row1 = y1 * rowSize;
const row2 = y2 * rowSize;
const row3 = y3 * rowSize;
const row0_address = row0 + channel;
const row1_address = row1 + channel;
const row2_address = row2 + channel;
const row3_address = row3 + channel;
const col0_offset = x0 * itemSize;
const col1_offset = x1 * itemSize;
const col2_offset = x2 * itemSize;
const col3_offset = x3 * itemSize;
// read samples
const vi0 = data[row0_address + col0_offset];
const vi1 = data[row0_address + col1_offset];
const vi2 = data[row0_address + col2_offset];
const vi3 = data[row0_address + col3_offset];
const vj0 = data[row1_address + col0_offset];
const vj1 = data[row1_address + col1_offset];
const vj2 = data[row1_address + col2_offset];
const vj3 = data[row1_address + col3_offset];
const vk0 = data[row2_address + col0_offset];
const vk1 = data[row2_address + col1_offset];
const vk2 = data[row2_address + col2_offset];
const vk3 = data[row2_address + col3_offset];
const vl0 = data[row3_address + col0_offset];
const vl1 = data[row3_address + col1_offset];
const vl2 = data[row3_address + col2_offset];
const vl3 = data[row3_address + col3_offset];
// perform filtering in X (rows)
const s0 = interpolate_bicubic(xd, vi0, vi1, vi2, vi3);
const s1 = interpolate_bicubic(xd, vj0, vj1, vj2, vj3);
const s2 = interpolate_bicubic(xd, vk0, vk1, vk2, vk3);
const s3 = interpolate_bicubic(xd, vl0, vl1, vl2, vl3);
// filter in Y (columns)
return interpolate_bicubic(yd, s0, s1, s2, s3);
}
/**
*
* @param {number} u
* @param {number} v
* @param {number[]|Float32Array} result
* @param {number} result_offset
*/
sampleBilinearUV(u, v, result, result_offset = 0) {
const itemSize = this.itemSize;
for (let i = 0; i < itemSize; i++) {
result[i + result_offset] = this.sampleChannelBilinearUV(u, v, i);
}
}
/**
*
* @param {number} x
* @param {number} y
* @param {number[]|Float32Array|Float64Array} result
* @param {number} result_offset
*/
sampleBilinear(x, y, result, result_offset = 0) {
const itemSize = this.itemSize;
for (let i = 0; i < itemSize; i++) {
//TODO this can be optimized greatly
result[i + result_offset] = this.sampleChannelBilinear(x, y, i);
}
}
/**
*
* @param {number} u
* @param {number} v
* @param {number} channel
* @return {number}
*/
sampleChannelBilinearUV(u, v, channel) {
const x = u * this.width - 0.5;
const y = v * this.height - 0.5;
return this.sampleChannelBilinear(x, y, channel);
}
/**
*
* @param {number} x
* @param {number} y
* @param {number} channel
* @returns {number}
*/
sampleChannelBilinear(x, y, channel) {
assert.isNumber(x, 'x');
assert.notNaN(x, 'x');
assert.isNumber(y, 'y');
assert.notNaN(y, 'y');
assert.isNonNegativeInteger(channel, 'channel');
const itemSize = this.itemSize;
const width = this.width;
const height = this.height;
const rowSize = width * itemSize;
//sample 4 points
const x_max = width - 1;
const y_max = height - 1;
const clamped_x = clamp(x, 0, x_max);
const clamped_y = clamp(y, 0, y_max);
const x0 = clamped_x >>> 0;
const y0 = clamped_y >>> 0;
//
const row0 = y0 * rowSize;
const col0_offset = x0 * itemSize + channel;
const i0 = row0 + col0_offset;
//
let x1, y1;
if (clamped_x === x0) {
x1 = x0;
} else {
x1 = x0 + 1;
}
if (clamped_y === y0) {
y1 = y0;
} else {
y1 = y0 + 1;
}
const data = this.data;
const q0 = data[i0];
if (x0 === x1 && y0 === y1) {
// exactly sampled in the center of the pixel, no interpolation required
return q0;
}
//
const xd = clamped_x - x0;
const yd = clamped_y - y0;
const col1_offset = x1 * itemSize + channel;
const i1 = row0 + col1_offset;
const row1 = y1 * rowSize;
const j0 = row1 + col0_offset;
const j1 = row1 + col1_offset;
const q1 = data[i1];
const p0 = data[j0];
const p1 = data[j1];
// perform Bi-Linear interpolation
const s0 = lerp(q0, q1, xd);
const s1 = lerp(p0, p1, xd);
return lerp(s0, s1, yd);
}
/**
*
* @param {number} u
* @param {number} v
* @param {number[]|ArrayLike<number>} result
*/
sampleNearestUV(u, v, result) {
const w = this.width;
const h = this.height;
const x = Math.round(u * w - 0.5);
const y = Math.round(v * h - 0.5);
this.read(
clamp(x, 0, w - 1),
clamp(y, 0, h - 1),
result
);
}
/**
*
* @param {number} x
* @param {number} y
* @param {number} channel
* @returns {number}
*/
readChannel(x, y, channel) {
assert.isNumber(x, "x");
assert.isNumber(y, "y");
assert.isNumber(channel, "channel");
assert.isNonNegativeInteger(channel, 'channel');
assert.lessThan(channel, this.itemSize);
const index = (y * this.width + x) * this.itemSize + channel;
return this.data[index];
}
/**
*
* @param {number} x
* @param {number} y
* @param {number[]} result
*/
read(x, y, result) {
const width = this.width;
const itemSize = this.itemSize;
const i0 = (y * width + x) * itemSize;
for (let i = 0; i < itemSize; i++) {
result[i] = this.data[i0 + i];
}
}
/**
*
* @param {number} x
* @param {number} y
* @param {number[]|ArrayLike<number>} texel
*/
write(x, y, texel) {
const width = this.width;
const itemSize = this.itemSize;
const i0 = (y * width + x) * itemSize;
for (let i = 0; i < itemSize; i++) {
this.data[i0 + i] = texel[i];
}
this.version++;
}
/**
*
* @param {number} x
* @param {number} y
* @returns {number}
*/
point2index(x, y) {
return x + y * this.width;
}
/**
*
* @param {number} index
* @param {Vector2} result
*/
index2point(index, result) {
const width = this.width;
const x = index % width;
const y = (index / width) | 0;
result.set(x, y);
}
/**
* Copy a patch from another sampler
* @param {Sampler2D} source where to copy from
* @param {Number} sourceX where to start reading from, X coordinate
* @param {Number} sourceY where to start reading from, X coordinate
* @param {Number} destinationX where to start writing to, X coordinate
* @param {Number} destinationY where to start writing to, X coordinate
* @param {Number} width size of the patch that is to be copied
* @param {Number} height size of the patch that is to be copied
*/
copy(
source, sourceX, sourceY,
destinationX, destinationY, width, height
) {
assert.isNumber(sourceX, 'sourceX');
assert.isNumber(sourceY, 'sourceY');
assert.isNumber(destinationX, 'destinationX');
assert.isNumber(destinationY, 'destinationY');
assert.isNumber(width, 'width');
assert.isNumber(height, 'height');
const _w = Math.min(width, source.width - sourceX, this.width - destinationX);
const _h = Math.min(height, source.height - sourceY, this.height - destinationY);
const dItemSize = this.itemSize;
const sItemSize = source.itemSize;
const _itemSize = Math.min(dItemSize, sItemSize);
const dRowSize = dItemSize * this.width;
const sRowSize = sItemSize * source.width;
const sData = source.data;
const dData = this.data;
let x, y, i;
for (y = 0; y < _h; y++) {
const dA = (y + destinationY) * dRowSize;
const sA = (y + sourceY) * sRowSize;
for (x = 0; x < _w; x++) {
const dOffset = dA + (x + destinationX) * dItemSize;
const sOffset = sA + (x + sourceX) * sItemSize;
for (i = 0; i < _itemSize; i++) {
dData[dOffset + i] = sData[sOffset + i];
}
}
}
this.version++;
}
/**
* Fill data values with zeros for a given area
* Specialized version of `fill` method, optimized for speed
* @param {Number} x
* @param {Number} y
* @param {Number} width
* @param {Number} height
*/
zeroFill(x, y, width, height) {
assert.isNonNegativeInteger(width, 'width');
assert.isNonNegativeInteger(height, 'height');
const x0 = clamp(x, 0, this.width);
const y0 = clamp(y, 0, this.height);
const x1 = clamp(x + width, 0, this.width);
const y1 = clamp(y + height, 0, this.height);
// console.log(`#zerofill x:${x}, y:${y}, width:${width}, height:${height} \t -> \t x0:${x0}, y0:${y0}, x1:${x1}, y1:${y1}`); // DEBUG
const data = this.data;
const itemSize = this.itemSize;
const rowSize = itemSize * this.width;
const clearRowOffset0 = x0 * itemSize;
const clearRowOffset1 = x1 * itemSize;
let _y;
for (_y = y0; _y < y1; _y++) {
const a = _y * rowSize;
data.fill(0, a + clearRowOffset0, a + clearRowOffset1);
}
this.version++;
}
/**
*
* @param {number} channel_index
* @param {number} value
*/
channelFill(channel_index, value) {
const itemSize = this.itemSize;
const data = this.data;
const length = data.length;
for (let i = channel_index; i < length; i += itemSize) {
data[i] = value;
}
this.version++;
}
/**
*
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
* @param {Array.<number>} value
*/
fill(x, y, width, height, value) {
const _w = this.width;
const _h = this.height;
const x0 = clamp(x, 0, _w);
const y0 = clamp(y, 0, _h);
const x1 = clamp(x + width, 0, _w);
const y1 = clamp(y + height, 0, _h);
const data = this.data;
const itemSize = this.itemSize;
const rowSize = itemSize * _w;
let _y, _x, i;
for (_y = y0; _y < y1; _y++) {
const a = _y * rowSize;
for (_x = x0; _x < x1; _x++) {
const offset = a + _x * itemSize;
for (i = 0; i < itemSize; i++) {
data[offset + i] = value[i];
}
}
}
this.version++;
}
/**
* Set channel value of a specific texel
* @param {number} x
* @param {number} y
* @param {number} channel
* @param {number} value
*/
writeChannel(x, y, channel, value) {
assert.isNumber(x, "x");
assert.isNumber(y, "y");
assert.greaterThanOrEqual(x, 0);
assert.greaterThanOrEqual(y, 0);
assert.lessThan(x, this.width);
assert.lessThan(y, this.height);
assert.isNonNegativeInteger(channel, 'channel');
assert.lessThan(channel, this.itemSize);
const pointIndex = y * this.width + x;
const pointAddress = pointIndex * this.itemSize;
const channelAddress = pointAddress + channel;
this.data[channelAddress] = value;
this.version++;
}
/**
* Traverses area inside a circle
* NOTE: Based on palm3d answer on stack overflow: https://stackoverflow.com/questions/1201200/fast-algorithm-for-drawing-filled-circles
* @param {number} centerX
* @param {number} centerY
* @param {number} radius
* @param {function(x:number,y:number, sampler:Sampler2D)} visitor
*/
traverseCircle(centerX, centerY, radius, visitor) {
let x, y;
//convert offsets to integers for safety
const offsetX = centerX | 0;
const offsetY = centerY | 0;
const r2 = radius * radius;
const radiusCeil = Math.ceil(radius);
for (y = -radiusCeil; y <= radiusCeil; y++) {
const y2 = y * y;
for (x = -radiusCeil; x <= radiusCeil; x++) {
if (x * x + y2 <= r2) {
visitor(offsetX + x, offsetY + y, this);
}
}
}
}
/**
*
* @param {number} x
* @param {number} y
* @param {boolean} [preserveData=true]
*/
resize(x, y, preserveData = true) {
assert.isNonNegativeInteger(x, 'x');
assert.isNonNegativeInteger(y, 'y');
const _w = this.width;
const _h = this.height;
if (_w === x && _h === y) {
// size didn't change
return;
}
const itemSize = this.itemSize;
const length = x * y * itemSize;
const oldData = this.data;
const Constructor = typedArrayConstructorByInstance(oldData);
const newData = new Constructor(length);
if (preserveData) {
//copy old data
if (x === _w) {
// number of columns is preserved, we can just copy the old data across
newData.set(oldData.subarray(0, Math.min(oldData.length, length)));
} else {
//we need to copy new data row-by-row
const rowCount = min2(y, _h);
const columnCount = min2(x, _w);
for (let i = 0; i < rowCount; i++) {
for (let j = 0; j < columnCount; j++) {
const targetItemAddress = (i * x + j) * itemSize;
const sourceItemAddress = (i * _w + j) * itemSize;
for (let k = 0; k < itemSize; k++) {
newData[targetItemAddress + k] = oldData[sourceItemAddress + k];
}
}
}
}
}
this.width = x;
this.height = y;
this.data = newData;
this.version++;
}
/**
* Estimate memory requirement of the object
* @return {number}
*/
computeByteSize() {
let dataSize;
const data = this.data;
if (Array.isArray(data)) {
// Assume IEEE float 64
dataSize = 8 * data.length;
} else {
dataSize = data.byteLength;
}
return dataSize + 280;
}
/**
*
* @param {Sampler2D} other
* @return {boolean}
*/
equals(other) {
if (this === other) {
// special case
return true;
}
if (
this.width !== other.width
|| this.height !== other.height
|| this.itemSize !== other.itemSize
) {
return false;
}
return is_typed_array_equals(this.data, other.data);
}
/**
*
* @return {number}
*/
hash() {
const item_size = this.itemSize;
const width = this.width;
const height = this.height;
const data = this.data;
let hash = (((width & 0xFFFF) << 16) | (height & 0xFFFF)) ^ item_size;
const texel_count = width * height;
const element_count = texel_count * item_size;
// we want to hash all channels for each chosen texel
if (isTypedArray(data)) {
hash ^= array_buffer_hash(data.buffer, data.byteOffset, data.byteLength);
} else {
// floating point texture
for (let i = 0; i < element_count; ++i) {
const channel_value = data[i];
hash = ((hash << 5) - hash) + computeHashFloat(channel_value);
}
}
return hash;
}
/**
* @returns {Sampler2D}
*/
clone() {
let data_clone;
if (Array.isArray(this.data)) {
data_clone = this.data.slice();
} else {
// storage is a typed array
const T = this.data.constructor;
data_clone = new T(this.data);
}
return new Sampler2D(data_clone, this.itemSize, this.width, this.height);
}
toJSON() {
const encoded = Base64.encode(this.data.buffer);
return {
height: this.height,
width: this.width,
itemSize: this.itemSize,
type: compute_binary_data_type_from_typed_array(this.data),
data: encoded
}
}
fromJSON({ height, width, itemSize, type, data }) {
const CTOR = compute_typed_array_constructor_from_data_type(type);
if (typeof data === "string") {
const decoded = Base64.decode(data);
this.data = new CTOR(decoded);
} else if (Array.isArray(data)) {
// deprecated
console.warn('Array JSON format is deprecated, please upgrade your data as soon as possible');
this.data = new CTOR(data);
} else {
throw new Error('Unsupported data format');
}
this.height = height;
this.width = width;
this.itemSize = itemSize;
}
/**
*
* @param {int} itemSize
* @param {int} width
* @param {int} height
* @return {Sampler2D}
*/
static uint8clamped(itemSize, width, height) {
const data = new Uint8ClampedArray(width * height * itemSize);
return new Sampler2D(data, itemSize, width, height);
}
/**
*
* @param {int} itemSize
* @param {int} width
* @param {int} height
* @return {Sampler2D}
*/
static uint8(itemSize, width, height) {
const data = new Uint8Array(width * height * itemSize);
return new Sampler2D(data, itemSize, width, height);
}
/**
*
* @param {int} itemSize
* @param {int} width
* @param {int} height
* @return {Sampler2D}
*/
static uint16(itemSize, width, height) {
const data = new Uint16Array(width * height * itemSize);
return new Sampler2D(data, itemSize, width, height);
}
/**
*
* @param {int} itemSize
* @param {int} width
* @param {int} height
* @return {Sampler2D}
*/
static uint32(itemSize, width, height) {
const data = new Uint32Array(width * height * itemSize);
return new Sampler2D(data, itemSize, width, height);
}
/**
*
* @param {int} itemSize
* @param {int} width
* @param {int} height
* @return {Sampler2D}
*/
static int8(itemSize, width, height) {
const data = new Int8Array(width * height * itemSize);
return new Sampler2D(data, itemSize, width, height);
}
/**
*
* @param {int} itemSize
* @param {int} width
* @param {int} height
* @return {Sampler2D}
*/
static int16(itemSize, width, height) {
const data = new Int16Array(width * height * itemSize);
return new Sampler2D(data, itemSize, width, height);
}
/**
*
* @param {int} itemSize
* @param {int} width
* @param {int} height
* @return {Sampler2D}
*/
static int32(itemSize, width, height) {
const data = new Int32Array(width * height * itemSize);
return new Sampler2D(data, itemSize, width, height);
}
/**
*
* @param {int} itemSize
* @param {int} width
* @param {int} height
* @return {Sampler2D}
*/
static float32(itemSize, width, height) {
const data = new Float32Array(width * height * itemSize);
return new Sampler2D(data, itemSize, width, height);
}
/**
*
* @param {int} itemSize
* @param {int} width
* @param {int} height
* @return {Sampler2D}
*/
static float64(itemSize, width, height) {
const data = new Float64Array(width * height * itemSize);
return new Sampler2D(data, itemSize, width, height);
}
}
/**
* @readonly
* @type {boolean}
*/
Sampler2D.prototype.isSampler2D = true;
/**
* @readonly
* @type {string}
*/
Sampler2D.typeName = "Sampler2D";