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