moved to worker (gets backed up, but no impact on main thread)
This commit is contained in:
parent
a0e00e9657
commit
db42811ea2
|
|
@ -29,8 +29,21 @@ const linkGenerator = (): Plugin => {
|
|||
};
|
||||
};
|
||||
|
||||
const configureResponseHeaders = (): Plugin => {
|
||||
return {
|
||||
name: 'configure-response-headers',
|
||||
configureServer: (server) => {
|
||||
server.middlewares.use((_req, res, next) => {
|
||||
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
|
||||
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
|
||||
next();
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [linkGenerator()],
|
||||
plugins: [linkGenerator(), configureResponseHeaders()],
|
||||
build: {
|
||||
target: 'esnext',
|
||||
rollupOptions: { input },
|
||||
|
|
@ -38,4 +51,12 @@ export default defineConfig({
|
|||
polyfill: false,
|
||||
},
|
||||
},
|
||||
worker: {
|
||||
format: 'es',
|
||||
},
|
||||
server: {
|
||||
fs: {
|
||||
allow: [resolve(__dirname, '..')],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import type { FolkGeometry } from '../canvas/fc-geometry.ts';
|
||||
import type { Vector2 } from '../utils/Vector2.ts';
|
||||
import { Fields } from './fields.ts';
|
||||
|
||||
export class DistanceField extends HTMLElement {
|
||||
static tagName = 'distance-field';
|
||||
|
|
@ -11,12 +10,14 @@ export class DistanceField extends HTMLElement {
|
|||
|
||||
private canvas!: HTMLCanvasElement;
|
||||
private ctx: CanvasRenderingContext2D;
|
||||
private offscreenCanvas: HTMLCanvasElement;
|
||||
private offscreenCtx: CanvasRenderingContext2D;
|
||||
private fields: Fields;
|
||||
private resolution: number;
|
||||
private imageSmoothing: boolean;
|
||||
private worker!: Worker;
|
||||
private geometryShapeIds: Map<HTMLElement, string> = new Map();
|
||||
|
||||
// Get all geometry elements and create points for the distance field
|
||||
// Get all geometry elements
|
||||
private geometries = document.querySelectorAll('fc-geometry');
|
||||
|
||||
constructor() {
|
||||
|
|
@ -24,9 +25,8 @@ export class DistanceField extends HTMLElement {
|
|||
|
||||
this.resolution = 800; // default resolution
|
||||
this.imageSmoothing = true;
|
||||
this.fields = new Fields(this.resolution);
|
||||
|
||||
const { ctx, offscreenCtx } = this.createCanvas(
|
||||
const { ctx, offscreenCtx, offscreenCanvas } = this.createCanvas(
|
||||
window.innerWidth,
|
||||
window.innerHeight,
|
||||
this.resolution,
|
||||
|
|
@ -35,6 +35,16 @@ export class DistanceField extends HTMLElement {
|
|||
|
||||
this.ctx = ctx;
|
||||
this.offscreenCtx = offscreenCtx;
|
||||
this.offscreenCanvas = offscreenCanvas;
|
||||
|
||||
// Initialize the Web Worker
|
||||
try {
|
||||
this.worker = new Worker(new URL('./distance-field.worker.ts', import.meta.url).href, { type: 'module' });
|
||||
this.worker.onmessage = this.handleWorkerMessage;
|
||||
this.worker.postMessage({ type: 'initialize', data: { resolution: this.resolution } });
|
||||
} catch (error) {
|
||||
console.error('Error initializing worker', error);
|
||||
}
|
||||
|
||||
this.renderDistanceField();
|
||||
}
|
||||
|
|
@ -48,17 +58,19 @@ export class DistanceField extends HTMLElement {
|
|||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
// Update distance field when geometries move or resize
|
||||
// Remove event listeners and terminate the worker
|
||||
this.geometries.forEach((geometry) => {
|
||||
geometry.removeEventListener('move', this.handleGeometryUpdate);
|
||||
geometry.removeEventListener('resize', this.handleGeometryUpdate);
|
||||
});
|
||||
this.worker.terminate();
|
||||
}
|
||||
|
||||
attributeChangedCallback(name: string, _oldValue: string, newValue: string) {
|
||||
if (name === 'resolution') {
|
||||
this.resolution = parseInt(newValue, 10);
|
||||
this.fields = new Fields(this.resolution);
|
||||
// Re-initialize the worker with the new resolution
|
||||
this.worker.postMessage({ type: 'initialize', data: { resolution: this.resolution } });
|
||||
} else if (name === 'image-smoothing') {
|
||||
this.imageSmoothing = newValue === 'true';
|
||||
if (this.ctx) {
|
||||
|
|
@ -68,30 +80,39 @@ export class DistanceField extends HTMLElement {
|
|||
}
|
||||
|
||||
private renderDistanceField() {
|
||||
// Get the computed ImageData from Fields
|
||||
const imageData = this.fields.generateImageData();
|
||||
|
||||
// Put the ImageData onto the offscreen canvas
|
||||
this.offscreenCtx.putImageData(imageData, 0, 0);
|
||||
|
||||
// Draw scaled version to main canvas
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
this.ctx.drawImage(
|
||||
this.offscreenCtx.canvas,
|
||||
0,
|
||||
0,
|
||||
this.resolution,
|
||||
this.resolution,
|
||||
0,
|
||||
0,
|
||||
this.canvas.width,
|
||||
this.canvas.height
|
||||
);
|
||||
// Request the worker to generate ImageData
|
||||
this.worker.postMessage({ type: 'generateImageData' });
|
||||
}
|
||||
|
||||
// Public methods
|
||||
// Handle messages from the worker
|
||||
private handleWorkerMessage = (event: MessageEvent) => {
|
||||
const { type, imageData } = event.data;
|
||||
|
||||
if (type === 'imageData') {
|
||||
// Reconstruct ImageData from the transferred buffer
|
||||
const imgData = new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height);
|
||||
|
||||
// Update the canvas with the new image data
|
||||
this.offscreenCtx.putImageData(imgData, 0, 0);
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
this.ctx.drawImage(
|
||||
this.offscreenCanvas,
|
||||
0,
|
||||
0,
|
||||
this.resolution,
|
||||
this.resolution,
|
||||
0,
|
||||
0,
|
||||
this.canvas.width,
|
||||
this.canvas.height
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Public method to reset fields
|
||||
reset() {
|
||||
this.fields = new Fields(this.resolution);
|
||||
// Reset the fields in the worker
|
||||
this.worker.postMessage({ type: 'initialize', data: { resolution: this.resolution } });
|
||||
}
|
||||
|
||||
private transformToFieldCoordinates(point: Vector2): Vector2 {
|
||||
|
|
@ -103,14 +124,15 @@ export class DistanceField extends HTMLElement {
|
|||
}
|
||||
|
||||
addShape(points: Vector2[]) {
|
||||
// Transform each point from screen coordinates to field coordinates
|
||||
// Transform and send points to the worker
|
||||
const transformedPoints = points.map((point) => this.transformToFieldCoordinates(point));
|
||||
this.fields.addShape(transformedPoints);
|
||||
this.worker.postMessage({ type: 'addShape', data: { points: transformedPoints } });
|
||||
this.renderDistanceField();
|
||||
}
|
||||
|
||||
removeShape(index: number) {
|
||||
this.fields.removeShape(index);
|
||||
// Inform the worker to remove a shape
|
||||
this.worker.postMessage({ type: 'removeShape', data: { index } });
|
||||
this.renderDistanceField();
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +140,7 @@ export class DistanceField extends HTMLElement {
|
|||
this.canvas = document.createElement('canvas');
|
||||
const offscreenCanvas = document.createElement('canvas');
|
||||
|
||||
// Set canvas styles to ensure it stays behind other elements
|
||||
// Set canvas styles
|
||||
this.canvas.style.position = 'absolute';
|
||||
this.canvas.style.top = '0';
|
||||
this.canvas.style.left = '0';
|
||||
|
|
@ -140,14 +162,12 @@ export class DistanceField extends HTMLElement {
|
|||
ctx.imageSmoothingEnabled = this.imageSmoothing;
|
||||
|
||||
this.appendChild(this.canvas);
|
||||
return { ctx, offscreenCtx };
|
||||
return { ctx, offscreenCtx, offscreenCanvas };
|
||||
}
|
||||
|
||||
handleGeometryUpdate = (event: Event) => {
|
||||
const geometry = event.target as HTMLElement;
|
||||
// TODO: store as array from getgo
|
||||
const index = Array.from(this.geometries).indexOf(geometry as FolkGeometry);
|
||||
if (index === -1) return;
|
||||
const shapeId = this.geometryShapeIds.get(geometry);
|
||||
|
||||
const rect = geometry.getBoundingClientRect();
|
||||
const points = [
|
||||
|
|
@ -157,10 +177,20 @@ export class DistanceField extends HTMLElement {
|
|||
{ x: rect.x, y: rect.y + rect.height },
|
||||
];
|
||||
|
||||
if (index < this.fields.shapes.length) {
|
||||
this.fields.updateShape(index, this.transformPoints(points));
|
||||
const transformedPoints = this.transformPoints(points);
|
||||
|
||||
if (shapeId) {
|
||||
this.worker.postMessage({
|
||||
type: 'updateShape',
|
||||
data: { id: shapeId, points: transformedPoints },
|
||||
});
|
||||
} else {
|
||||
this.fields.addShape(this.transformPoints(points));
|
||||
const newId = crypto.randomUUID();
|
||||
this.geometryShapeIds.set(geometry, newId);
|
||||
this.worker.postMessage({
|
||||
type: 'addShape',
|
||||
data: { id: newId, points: transformedPoints },
|
||||
});
|
||||
}
|
||||
|
||||
this.renderDistanceField();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
/// <reference lib="webworker" />
|
||||
import { Fields } from './fields.ts';
|
||||
|
||||
declare const self: DedicatedWorkerGlobalScope;
|
||||
|
||||
// Initialize the Fields instance
|
||||
let fields: Fields;
|
||||
|
||||
// Listen for messages from the main thread
|
||||
self.onmessage = (event) => {
|
||||
const { type, data } = event.data;
|
||||
|
||||
switch (type) {
|
||||
case 'initialize':
|
||||
fields = new Fields(data.resolution);
|
||||
break;
|
||||
|
||||
case 'addShape':
|
||||
fields.addShape(data.id, data.points, data.color);
|
||||
break;
|
||||
|
||||
case 'removeShape':
|
||||
fields.removeShape(data.id);
|
||||
break;
|
||||
|
||||
case 'updateShape':
|
||||
fields.updateShape(data.id, data.points);
|
||||
break;
|
||||
|
||||
case 'generateImageData': {
|
||||
const imageData = fields.generateImageData();
|
||||
// Post the ImageData back to the main thread
|
||||
postMessage({ type: 'imageData', imageData }, [imageData.data.buffer]);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
console.warn(`Unknown message type: ${type}`);
|
||||
}
|
||||
};
|
||||
|
|
@ -1,6 +1,12 @@
|
|||
import type { Vector2 } from '../utils/Vector2.ts';
|
||||
import { computeCPT } from './cpt.ts';
|
||||
|
||||
interface Shape {
|
||||
id: string;
|
||||
points: Vector2[];
|
||||
color: number;
|
||||
}
|
||||
|
||||
export class Fields {
|
||||
private edt: Float32Array[] = [];
|
||||
private cpt: Vector2[][] = [];
|
||||
|
|
@ -8,15 +14,11 @@ export class Fields {
|
|||
private xcoords: Float32Array[] = [];
|
||||
private ycoords: Float32Array[] = [];
|
||||
private resolution: number;
|
||||
shapes: Array<{
|
||||
points: Vector2[];
|
||||
color: number;
|
||||
}> = [];
|
||||
private shapes: Map<string, Shape> = new Map();
|
||||
|
||||
constructor(resolution: number) {
|
||||
this.resolution = resolution + 1;
|
||||
this.initializeArrays();
|
||||
this.updateFields();
|
||||
}
|
||||
|
||||
private initializeArrays() {
|
||||
|
|
@ -39,15 +41,24 @@ export class Fields {
|
|||
return this.colorField[x][y];
|
||||
}
|
||||
|
||||
addShape(points: Vector2[], color?: number) {
|
||||
addShape(id: string, points: Vector2[], color?: number) {
|
||||
const shapeColor = color ?? Math.floor(Math.random() * 255);
|
||||
this.shapes.push({ points, color: shapeColor });
|
||||
this.shapes.set(id, { id, points, color: shapeColor });
|
||||
this.updateFields();
|
||||
}
|
||||
|
||||
removeShape(index: number) {
|
||||
this.shapes.splice(index, 1);
|
||||
this.updateFields();
|
||||
removeShape(id: string) {
|
||||
if (this.shapes.delete(id)) {
|
||||
this.updateFields();
|
||||
}
|
||||
}
|
||||
|
||||
updateShape(id: string, points: Vector2[]) {
|
||||
const shape = this.shapes.get(id);
|
||||
if (shape) {
|
||||
shape.points = points;
|
||||
this.updateFields();
|
||||
}
|
||||
}
|
||||
|
||||
updateFields() {
|
||||
|
|
@ -66,7 +77,7 @@ export class Fields {
|
|||
}
|
||||
}
|
||||
|
||||
boolifyFields(distanceField: Float32Array[], colorField: Float32Array[]): void {
|
||||
private boolifyFields(distanceField: Float32Array[], colorField: Float32Array[]): void {
|
||||
const LARGE_NUMBER = 1000000000000;
|
||||
const size = distanceField.length;
|
||||
const cellSize = 1;
|
||||
|
|
@ -126,9 +137,8 @@ export class Fields {
|
|||
}
|
||||
};
|
||||
|
||||
for (const shape of this.shapes) {
|
||||
for (const shape of this.shapes.values()) {
|
||||
const { points, color } = shape;
|
||||
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const start = points[i];
|
||||
const end = points[(i + 1) % points.length];
|
||||
|
|
@ -137,14 +147,6 @@ export class Fields {
|
|||
}
|
||||
}
|
||||
|
||||
updateShape(index: number, points: Vector2[]) {
|
||||
if (index >= 0 && index < this.shapes.length) {
|
||||
const existingColor = this.shapes[index].color;
|
||||
this.shapes[index] = { points, color: existingColor };
|
||||
this.updateFields();
|
||||
}
|
||||
}
|
||||
|
||||
private renderer(
|
||||
pixelRenderer: (
|
||||
distance: number,
|
||||
|
|
|
|||
Loading…
Reference in New Issue