UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

327 lines (260 loc) • 8.01 kB
import { ClampToEdgeWrapping, DataTexture, NearestFilter } from "three"; import { assert } from "../../../../core/assert.js"; import { float_to_uint8 } from "../../../../core/binary/float_to_uint8.js"; import { uint8_to_float } from "../../../../core/binary/uint8_to_float.js"; import { array_copy } from "../../../../core/collection/array/array_copy.js"; import { isTypedArray } from "../../../../core/collection/array/typed/isTypedArray.js"; import Vector1 from "../../../../core/geom/Vector1.js"; import Vector2 from "../../../../core/geom/Vector2.js"; import Vector4 from '../../../../core/geom/Vector4.js'; import ObservedString from "../../../../core/model/ObservedString.js"; import html_canvas_to_sampler2d from "../../../graphics/texture/html_canvas_to_sampler2d.js"; import { sampler2d_scale } from "../../../graphics/texture/sampler/resize/sampler2d_scale.js"; import { Sampler2D } from "../../../graphics/texture/sampler/Sampler2D.js"; import { sampler2d_paint } from "../../../graphics/texture/sampler/sampler2d_paint.js"; import { WHITE_PIXEL_DATA_URL } from "../../../graphics/WHITE_PIXEL_DATA_URL.js"; class Context { /** * * @param {Sampler2D} sampler * @param {number} borderWidth * @param {string} tileImage * @constructor */ constructor(sampler, borderWidth, tileImage) { /** * * @type {Sampler2D} */ this.sampler = sampler; /** * * @type {number} */ this.borderWidth = borderWidth; /** * URL of the tile image * @type {string} */ this.tileImage = tileImage; } } const COLOR_UINT_8_TRANSPARENT = [0, 0, 0, 0]; export class TerrainOverlay { /** * * @param {Vector2} size * @constructor */ constructor(size) { /** * width is in fraction between 0 and 1, for example: 0.1 represents 10% border between tiles * @type {Vector1} * @readonly */ this.borderWidth = new Vector1(0.1); /** * * @type {Vector2} * @readonly */ this.size = new Vector2(size.x, size.y); /** * * @type {ObservedString} */ this.tileImage = new ObservedString(WHITE_PIXEL_DATA_URL); /** * * @type {Sampler2D} */ this.sampler = Sampler2D.uint8(4, size.x, size.y); const texture = this.texture = new DataTexture( this.sampler.data, size.x, size.y, ); texture.wrapS = ClampToEdgeWrapping; texture.wrapT = ClampToEdgeWrapping; texture.magFilter = NearestFilter; texture.minFilter = NearestFilter; texture.anisotropy = 0; texture.flipY = false; texture.repeat.set(1, 1); //texture.anisotropy = 8; texture.needsUpdate = true; texture.generateMipmaps = false; /** * * @type {Context[]} */ this.stack = []; this.size.onChanged.add((x, y) => { this.sampler.resize(x, y); this.texture.dispose(); this.texture.image.data = this.sampler.data; this.texture.image.width = x; this.texture.image.height = y; }); } /** * * @returns {string} */ get baseTileImage() { return this.tileImage.getValue(); } /** * * @param {string} v */ set baseTileImage(v) { this.tileImage.set(v); } /** * @returns {HTMLCanvasElement} */ get canvas() { throw new Error('Deprecated, used .sampler instead'); } /** * Pushes new context onto the stack, allowing you to preserve current state for later */ push() { const sampler = this.sampler.clone(); const context = new Context( sampler, this.borderWidth.getValue(), this.tileImage.getValue() ); this.stack.push(context); //clear this.clear(); } /** * Pops top context from the stack, restoring previous state */ pop() { if (this.stack.length === 0) { console.error("Can't pop overlay context, nothing on the stack"); return; } /** * @type {Context} */ const context = this.stack.pop(); const sampler = context.sampler; this.size.set(sampler.width, sampler.height); // write context onto canvas this.sampler.copy(sampler, 0, 0, 0, 0, sampler.width, sampler.height); this.borderWidth.set(context.borderWidth); this.tileImage.set(context.tileImage); this.update(); } clear() { const size = this.size; this.sampler.zeroFill(0, 0, size.x, size.y); this.update(); } update() { this.texture.needsUpdate = true; } /** * * @param {number} x * @param {number} y * @param {Vector4} result */ readPoint(x, y, result) { for (let i = 0; i < 4; i++) { const v = this.sampler.readChannel(x, y, i); result[i] = uint8_to_float(v); } } /** * * @param {number} x * @param {number} y * @param {Vector4} vec4 */ paintPointAlphaBlend(x, y, vec4) { const r = new Vector4(); this.readPoint(x, y, r); const b = 1 - vec4.w; r.set( vec4.x * vec4.w + r.x * b, vec4.y * vec4.w + r.y * b, vec4.z * vec4.w + r.z * b, vec4.w + r.w * b ); this.clearPoint(x, y); this.paintPoint(x, y, r); } /** * * @param {number} x * @param {number} y */ clearPoint(x, y) { this.sampler.write(x, y, COLOR_UINT_8_TRANSPARENT); } /** * * @param {number} x * @param {number} y * @param {Vector4} vec4 */ paintPoint(x, y, vec4) { for (let i = 0; i < 4; i++) { this.sampler.writeChannel(x, y, i, float_to_uint8(vec4[i])); } this.texture.needsUpdate = true; } /** * * @param {Uint8Array|number[]} data */ writeData(data) { assert.isArrayLike(data, 'data'); const size = this.size; if (size.x === 0 || size.y === 0) { // nothing to write return; } assert.equal(data.length, this.sampler.data.length); if (isTypedArray(this.sampler.data)) { this.sampler.data.set(data); } else { array_copy(data, 0, this.sampler.data, 0, data.length); } this.sampler.version++; this.texture.needsUpdate = true; } /** * * @param {HTMLCanvasElement} image * @param {number} dx * @param {number} dy * @param {number} [dWidth] * @param {number} [dHeight] */ paintImage(image, dx, dy, dWidth, dHeight) { const source = html_canvas_to_sampler2d(image); this.paintSampler(source, dx, dy, dWidth, dHeight); } /** * * @param {Sampler2D} source * @param {number} dx * @param {number} dy * @param {number} dWidth * @param {number} dHeight */ paintSampler(source, dx, dy, dWidth, dHeight) { // rescale source if needed const scaled_source = Sampler2D.uint8(4, dWidth, dHeight); sampler2d_scale(source, scaled_source); sampler2d_paint(this, scaled_source, 0, 0, dx, dy, dWidth, dHeight); this.texture.needsUpdate = true; } }