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); } } }