Files
web-cad/frontend/src/canvas/RenderEngine.ts
T
Agent Zero 05ccebad8d Replace frontend with deployed TS version from a08dd73
- Restore TS frontend source (canvas, tools, CRDT, components, services)
- Keep 5 JSX components that TS code depends on (CADCanvas, LayerPanel, BlockLibrary, PluginRegistry, Toolbar)
- Keep JS services (api.js, blockService.js) that components depend on
- Fix vite/plugin-react version mismatch (downgrade to v4 for vite 6)
- Add axios dependency
- Skip tsc in build script (vite/esbuild handles transpilation)
- Fix index.html lang=de, favicon path
- Add vite proxy config for /api and /ws
- Backend unchanged (already from deployed containers)
2026-06-28 02:16:58 +02:00

241 lines
6.8 KiB
TypeScript

import RBush from 'rbush';
import { SpatialIndex } from './SpatialIndex';
import { LayerManager } from './LayerManager';
import { ZoomPanController } from './ZoomPanController';
type RenderElement = {
id: string;
type: 'line' | 'circle' | 'polyline' | 'polygon' | 'rect' | 'arc' | 'text' | 'dimension' | 'block_instance';
layerId: string;
bbox: { minX: number; minY: number; maxX: number; maxY: number };
// Additional properties based on type
};
type Layer = {
id: string;
name: string;
visible: boolean;
locked: boolean;
};
export class RenderEngine {
private canvas: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;
private spatialIndex: SpatialIndex;
private layerManager: LayerManager;
private zoomPanController: ZoomPanController;
private animationFrameId: number | null = null;
private elements: RenderElement[] = [];
private layers: Layer[] = [];
private isRendering = false;
constructor(canvas: HTMLCanvasElement) {
this.canvas = canvas;
const ctx = canvas.getContext('2d');
if (!ctx) {
throw new Error('Failed to get 2D context');
}
this.ctx = ctx;
this.spatialIndex = new SpatialIndex();
this.layerManager = new LayerManager();
this.zoomPanController = new ZoomPanController(canvas);
}
public start(): void {
if (this.isRendering) return;
this.isRendering = true;
this.render();
}
public stop(): void {
if (this.animationFrameId) {
cancelAnimationFrame(this.animationFrameId);
}
this.isRendering = false;
}
private render(): void {
if (!this.isRendering) return;
// Clear canvas
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Apply zoom/pan transforms
const transform = this.zoomPanController.getTransform();
this.ctx.save();
this.ctx.transform(transform.a, transform.b, transform.c, transform.d, transform.e, transform.f);
// Render grid
this.renderGrid();
// Get visible elements using viewport culling
const viewport = this.zoomPanController.getViewport();
const visibleElements = this.spatialIndex.search(viewport);
// Render elements by layer order
const visibleLayers = this.layerManager.getVisibleLayers();
for (const layer of visibleLayers) {
const layerElements = visibleElements.filter(el => el.layerId === layer.id);
for (const element of layerElements) {
this.renderElement(element);
}
}
this.ctx.restore();
// Continue render loop
this.animationFrameId = requestAnimationFrame(() => this.render());
}
private renderGrid(): void {
const gridSize = 50; // pixels
const viewport = this.zoomPanController.getViewport();
this.ctx.strokeStyle = '#e0e0e0';
this.ctx.lineWidth = 1;
// Vertical lines
for (let x = Math.floor(viewport.minX / gridSize) * gridSize; x < viewport.maxX; x += gridSize) {
this.ctx.beginPath();
this.ctx.moveTo(x, viewport.minY);
this.ctx.lineTo(x, viewport.maxY);
this.ctx.stroke();
}
// Horizontal lines
for (let y = Math.floor(viewport.minY / gridSize) * gridSize; y < viewport.maxY; y += gridSize) {
this.ctx.beginPath();
this.ctx.moveTo(viewport.minX, y);
this.ctx.lineTo(viewport.maxX, y);
this.ctx.stroke();
}
}
private renderElement(element: RenderElement): void {
switch (element.type) {
case 'line':
this.renderLine(element);
break;
case 'circle':
this.renderCircle(element);
break;
case 'polyline':
this.renderPolyline(element);
break;
case 'polygon':
this.renderPolygon(element);
break;
case 'rect':
this.renderRect(element);
break;
case 'arc':
this.renderArc(element);
break;
case 'text':
this.renderText(element);
break;
case 'dimension':
this.renderDimension(element);
break;
case 'block_instance':
this.renderBlockInstance(element);
break;
}
}
private renderLine(element: RenderElement): void {
// Implementation for line rendering
this.ctx.beginPath();
this.ctx.moveTo(0, 0); // Placeholder
this.ctx.lineTo(100, 100); // Placeholder
this.ctx.stroke();
}
private renderCircle(element: RenderElement): void {
// Implementation for circle rendering
this.ctx.beginPath();
this.ctx.arc(50, 50, 30, 0, Math.PI * 2); // Placeholder
this.ctx.stroke();
}
private renderPolyline(element: RenderElement): void {
// Implementation for polyline rendering
this.ctx.beginPath();
this.ctx.moveTo(0, 0); // Placeholder
this.ctx.lineTo(50, 50); // Placeholder
this.ctx.lineTo(100, 0); // Placeholder
this.ctx.stroke();
}
private renderPolygon(element: RenderElement): void {
// Implementation for polygon rendering
this.ctx.beginPath();
this.ctx.moveTo(0, 0); // Placeholder
this.ctx.lineTo(50, 50); // Placeholder
this.ctx.lineTo(100, 0); // Placeholder
this.ctx.closePath();
this.ctx.stroke();
}
private renderRect(element: RenderElement): void {
// Implementation for rectangle rendering
this.ctx.strokeRect(10, 10, 100, 50); // Placeholder
}
private renderArc(element: RenderElement): void {
// Implementation for arc rendering
this.ctx.beginPath();
this.ctx.arc(50, 50, 30, 0, Math.PI); // Placeholder
this.ctx.stroke();
}
private renderText(element: RenderElement): void {
// Implementation for text rendering
this.ctx.fillText('Sample Text', 20, 20); // Placeholder
}
private renderDimension(element: RenderElement): void {
// Implementation for dimension rendering
this.ctx.beginPath();
this.ctx.moveTo(0, 0); // Placeholder
this.ctx.lineTo(100, 0); // Placeholder
this.ctx.stroke();
// Draw dimension lines and text
this.ctx.fillText('100', 50, -5); // Placeholder
}
private renderBlockInstance(element: RenderElement): void {
// Implementation for block instance rendering
this.ctx.strokeRect(20, 20, 60, 40); // Placeholder
this.ctx.fillText('BLOCK', 30, 45); // Placeholder
}
public addElement(element: RenderElement): void {
this.elements.push(element);
this.spatialIndex.insert(element);
}
public removeElement(id: string): void {
const index = this.elements.findIndex(el => el.id === id);
if (index !== -1) {
const element = this.elements[index];
this.elements.splice(index, 1);
this.spatialIndex.remove(element);
}
}
public addLayer(layer: Layer): void {
this.layers.push(layer);
this.layerManager.addLayer(layer);
}
public updateLayer(layer: Layer): void {
const index = this.layers.findIndex(l => l.id === layer.id);
if (index !== -1) {
this.layers[index] = layer;
this.layerManager.updateLayer(layer);
}
}
}