@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
328 lines (228 loc) • 9.08 kB
JavaScript
import { ParameterLookupTable } from "../../../particles/particular/engine/parameter/ParameterLookupTable.js";
import { MouseEvents } from "../../../../input/devices/events/MouseEvents.js";
import { readPositionFromMouseEvent } from "../../../../input/devices/PointerDevice.js";
import Vector2 from "../../../../../core/geom/Vector2.js";
import { VisualTip } from "../../../../../view/tooltip/VisualTip.js";
import Rectangle from "../../../../../core/geom/2d/Rectangle.js";
import LabelView from "../../../../../view/common/LabelView.js";
import { CanvasView } from "../../../../../view/elements/CanvasView.js";
import EmptyView from "../../../../../view/elements/EmptyView.js";
import { Color } from "../../../../../core/color/Color.js";
import { halton_sequence } from "../../../../../core/math/statistics/halton_sequence.js";
import { lerp } from "../../../../../core/math/lerp.js";
/**
*
* @param {TooltipManager} tooltips
* @param {View} view
* @param {LightManager} lm
*/
function make_tooltips({
tooltips,
view,
lm
}) {
let tip = null;
view.css({ pointerEvents: 'auto' });
/**
*
* @param {number} x
* @param {number} y
* @param {number} z
*/
function build_cluster_tip(x, y, z) {
const image = lm.getTextureClusters().image;
const lookup_image = lm.getTextureLookup().image;
const light_data_size = lm.__light_data.size;
const texel_index = image.width * image.height * z + image.width * y + x;
// RGB format
const texel_address = texel_index * 3;
const lookup_index = image.data[texel_address + 0];
const light_count = image.data[texel_address + 1];
const vContainer = new EmptyView();
vContainer.css({
display: 'flex',
flexDirection: 'column',
padding: '2px',
borderColor: 'rgba(255,255,255,0.1)',
borderWidth: '1px',
borderStyle: 'solid'
});
vContainer.addChild(new LabelView(`${x}:${y}:${z}`));
for (let i = 0; i < light_count; i++) {
const lookup_offset = lookup_index + i;
const light_descriptor = lookup_image.data[lookup_offset];
const light_type = light_descriptor & 0x3;
const light_address = light_descriptor >> 2;
const light_address_normalized = light_address / light_data_size;
const vLight = new LabelView(`${light_type}:${light_address}`);
const hue = light_address_normalized;
const color = new Color();
const saturation_phase = halton_sequence(4, Math.floor(light_address / 4) % 4)
color.setHSV(hue, lerp(0.4, 0.7, saturation_phase), 1);
vLight.css({
color: color.toHex(),
textShadow: '0 0 1px black'
});
vContainer.addChild(vLight);
}
return vContainer;
}
function clear_tip() {
if (tip !== null) {
tooltips.remove(tip);
tip = null;
}
}
const click_handler = (mouseEvent) => {
console.log('Click');
clear_tip();
const resolution = lm.getResolution();
const p = new Vector2();
readPositionFromMouseEvent(p, mouseEvent, view.el);
const tile_w = view.size.x / resolution.x;
const tile_h = view.size.y / resolution.y;
const tile_x = Math.floor(p.x / tile_w);
const tile_y = Math.floor(p.y / tile_h);
tip = new VisualTip(new Rectangle(
tile_x * tile_w, tile_y * tile_h, tile_w, tile_h
), () => {
const vContainer = new EmptyView();
vContainer.css({
display: 'flex',
flexDirection: 'row'
});
for (let i = 0; i < resolution.z; i++) {
const vCluster = build_cluster_tip((resolution.x - 1) - tile_x, (resolution.y - 1) - tile_y, i);
vContainer.addChild(vCluster);
}
return vContainer;
});
tooltips.add(tip);
};
view.on.linked.add(() => {
view.el.addEventListener(MouseEvents.Click, click_handler);
});
view.on.unlinked.add(() => {
view.el.removeEventListener(MouseEvents.Click, click_handler);
clear_tip();
});
}
/**
*
* @param {number} width
* @param {number} height
* @param {LightManager} lm
* @param {boolean} [heatmap]
* @param {TooltipManager} [tooltips]
* @returns {{domElement: HTMLCanvasElement, view:View}}
*/
export function createScreenGrid({
width,
height,
lm,
heatmap = true,
tooltips
}) {
const heatmap_lut = new ParameterLookupTable(4);
heatmap_lut.write([
0, 0, 0, 255,
0, 0, 255, 255,
0, 179, 179, 255,
0, 255, 0, 255,
255, 255, 0, 255,
255, 5, 5, 255
]);
heatmap_lut.computeUniformPositions();
const canvasView = new CanvasView();
canvasView.size.set(width, height);
const canvasElement = canvasView.el;
const ctx = canvasView.context2d;
canvasElement.style.width = `${width}px`;
canvasElement.style.height = `${height}px`;
canvasElement.style.position = 'absolute';
canvasElement.style.left = '0';
canvasElement.style.top = '0';
canvasElement.style.zIndex = '1';
// canvasElement.style.mixBlendMode = 'difference';
if (tooltips !== undefined) {
make_tooltips({
lm,
tooltips,
width,
height,
view: canvasView
});
} else {
canvasElement.style.pointerEvents = 'none';
}
function draw_grid() {
ctx.strokeStyle = "rgba(255,255,255,0.2)";
ctx.lineWidth = 1;
ctx.beginPath();
const x_divisions = lm.getResolution().x;
const y_divisions = lm.getResolution().y;
const x_spacing = width / x_divisions;
const y_spacing = height / y_divisions;
for (let i = 0; i < x_divisions; i++) {
ctx.moveTo(i * x_spacing, 0);
ctx.lineTo(i * x_spacing, height);
}
ctx.stroke();
ctx.beginPath();
for (let i = 0; i < y_divisions; i++) {
ctx.moveTo(0, i * y_spacing);
ctx.lineTo(width, i * y_spacing);
}
ctx.stroke();
}
function draw_cluster() {
const textureTiles = lm.getTextureClusters();
const image = textureTiles.image;
const data = image.data;
const resolution = lm.getResolution();
const x_divisions = resolution.x;
const y_divisions = resolution.y;
const x_spacing = width / x_divisions;
const y_spacing = height / y_divisions;
ctx.font = '10px Tahoma';
ctx.lineWidth = 1;
const color = [];
for (let i_x = 0; i_x < resolution.x + 1; i_x++) {
for (let i_y = 0; i_y < resolution.y + 1; i_y++) {
let found_matches = 0;
for (let i_z = 0; i_z < resolution.z + 1; i_z++) {
const index = i_z * resolution.x * resolution.y + (resolution.y - 1 - i_y) * resolution.x + (resolution.x - 1 - i_x);
const address = index * 3;
const point_light_count = data[address + 1];
if (point_light_count > 0) {
found_matches++;
}
}
const z_light_saturation = found_matches / resolution.z;
heatmap_lut.sample(z_light_saturation, color)
const p_x = i_x * x_spacing;
const p_y = i_y * y_spacing;
ctx.fillStyle = 'white';
ctx.strokeStyle = `rgb(${color[0]},${color[1]},${color[2]})`;
ctx.strokeText(found_matches, p_x, p_y + 10);
ctx.fillText(found_matches, p_x, p_y + 10);
if (heatmap) {
ctx.fillStyle = `rgba(${color[0]},${color[1]},${color[2]},0.2)`;
ctx.fillRect(p_x, p_y, x_spacing, y_spacing);
}
}
}
}
function update() {
ctx.clearRect(0, 0, width, height);
draw_grid();
draw_cluster();
}
update();
canvasView.on.linked.add(update);
return {
update,
domElement: canvasElement,
view: canvasView
};
}