class SymbolManager { constructor(canvasManager) { this.canvasManager = canvasManager; this.ctx = canvasManager.ctx; // Standard architectural symbol dimensions (in pixels) this.symbolSizes = { door: { width: 40, height: 8, swing: 35 }, window: { width: 40, height: 8 }, doubleDoor: { width: 60, height: 8, swing: 50 }, slidingDoor: { width: 50, height: 8 }, bifoldDoor: { width: 45, height: 8 }, casementWindow: { width: 40, height: 8 }, slidingWindow: { width: 60, height: 8 }, bayWindow: { width: 80, height: 20 }, cornerWindow: { width: 40, height: 40 } }; } // Draw standard hinged door symbol drawDoor(element) { const { startX, startY, endX, endY } = element; const doorType = element.doorType || 'single'; const swingDirection = element.swingDirection || 'right'; const wallAngle = Math.atan2(endY - startY, endX - startX); this.ctx.save(); this.ctx.translate((startX + endX) / 2, (startY + endY) / 2); this.ctx.rotate(wallAngle); const doorWidth = this.symbolSizes.door.width; const doorThickness = this.symbolSizes.door.thickness || 4; // Draw door opening (gap in wall) this.ctx.strokeStyle = '#ffffff'; this.ctx.lineWidth = 6; this.ctx.beginPath(); this.ctx.moveTo(-doorWidth/2, 0); this.ctx.lineTo(doorWidth/2, 0); this.ctx.stroke(); // Draw door symbol this.ctx.strokeStyle = element.color || '#000000'; this.ctx.lineWidth = 2; if (doorType === 'single') { this.drawSingleDoor(doorWidth, swingDirection); } else if (doorType === 'double') { this.drawDoubleDoor(doorWidth); } else if (doorType === 'sliding') { this.drawSlidingDoor(doorWidth); } else if (doorType === 'bifold') { this.drawBifoldDoor(doorWidth); } this.ctx.restore(); } // Draw single hinged door drawSingleDoor(width, swingDirection) { const swingRadius = this.symbolSizes.door.swing; const hingeX = swingDirection === 'right' ? -width/2 : width/2; const swingMultiplier = swingDirection === 'right' ? 1 : -1; // Door panel (closed position) this.ctx.beginPath(); this.ctx.moveTo(hingeX, -4); this.ctx.lineTo(hingeX + (width * swingMultiplier), -4); this.ctx.stroke(); // Door swing arc this.ctx.setLineDash([2, 3]); this.ctx.beginPath(); this.ctx.arc(hingeX, 0, swingRadius, swingDirection === 'right' ? -Math.PI/2 : Math.PI/2, swingDirection === 'right' ? 0 : Math.PI); this.ctx.stroke(); this.ctx.setLineDash([]); // Hinge point this.ctx.fillStyle = this.ctx.strokeStyle; this.ctx.beginPath(); this.ctx.arc(hingeX, 0, 2, 0, 2 * Math.PI); this.ctx.fill(); } // Draw double door drawDoubleDoor(width) { const halfWidth = width / 2; // Left door panel this.ctx.beginPath(); this.ctx.moveTo(-width/2, -4); this.ctx.lineTo(0, -4); this.ctx.stroke(); // Right door panel this.ctx.beginPath(); this.ctx.moveTo(0, -4); this.ctx.lineTo(width/2, -4); this.ctx.stroke(); // Swing arcs this.ctx.setLineDash([2, 3]); // Left swing this.ctx.beginPath(); this.ctx.arc(-width/2, 0, halfWidth, -Math.PI/2, -Math.PI); this.ctx.stroke(); // Right swing this.ctx.beginPath(); this.ctx.arc(width/2, 0, halfWidth, -Math.PI/2, 0); this.ctx.stroke(); this.ctx.setLineDash([]); // Hinge points this.ctx.fillStyle = this.ctx.strokeStyle; this.ctx.beginPath(); this.ctx.arc(-width/2, 0, 2, 0, 2 * Math.PI); this.ctx.arc(width/2, 0, 2, 0, 2 * Math.PI); this.ctx.fill(); } // Draw sliding door drawSlidingDoor(width) { // Track this.ctx.beginPath(); this.ctx.moveTo(-width/2, -6); this.ctx.lineTo(width/2, -6); this.ctx.stroke(); // Door panels this.ctx.beginPath(); this.ctx.moveTo(-width/4, -4); this.ctx.lineTo(width/4, -4); this.ctx.stroke(); // Direction arrows this.ctx.setLineDash([1, 2]); this.drawArrow(-width/4, 2, -width/2 + 5, 2); this.drawArrow(width/4, 2, width/2 - 5, 2); this.ctx.setLineDash([]); } // Draw bifold door drawBifoldDoor(width) { const quarterWidth = width / 4; // Door panels (folded position) this.ctx.beginPath(); this.ctx.moveTo(-width/2, -4); this.ctx.lineTo(-quarterWidth, -4); this.ctx.lineTo(-quarterWidth, -2); this.ctx.lineTo(quarterWidth, -2); this.ctx.lineTo(quarterWidth, -4); this.ctx.lineTo(width/2, -4); this.ctx.stroke(); // Fold lines this.ctx.setLineDash([1, 2]); this.ctx.beginPath(); this.ctx.moveTo(-quarterWidth, -6); this.ctx.lineTo(-quarterWidth, 2); this.ctx.moveTo(quarterWidth, -6); this.ctx.lineTo(quarterWidth, 2); this.ctx.stroke(); this.ctx.setLineDash([]); } // Draw window symbols drawWindow(element) { const { startX, startY, endX, endY } = element; const windowType = element.windowType || 'casement'; const wallAngle = Math.atan2(endY - startY, endX - startX); this.ctx.save(); this.ctx.translate((startX + endX) / 2, (startY + endY) / 2); this.ctx.rotate(wallAngle); const windowWidth = this.symbolSizes.window.width; this.ctx.strokeStyle = element.color || '#0066cc'; this.ctx.lineWidth = 2; if (windowType === 'casement') { this.drawCasementWindow(windowWidth); } else if (windowType === 'sliding') { this.drawSlidingWindow(windowWidth); } else if (windowType === 'bay') { this.drawBayWindow(); } else if (windowType === 'corner') { this.drawCornerWindow(); } this.ctx.restore(); } // Draw casement window drawCasementWindow(width) { // Window frame this.ctx.strokeRect(-width/2, -4, width, 8); // Mullions (window dividers) this.ctx.beginPath(); this.ctx.moveTo(0, -4); this.ctx.lineTo(0, 4); this.ctx.stroke(); // Opening indicators this.ctx.setLineDash([1, 2]); this.ctx.beginPath(); this.ctx.moveTo(-width/4, -2); this.ctx.lineTo(-width/4 - 8, -8); this.ctx.moveTo(width/4, -2); this.ctx.lineTo(width/4 + 8, -8); this.ctx.stroke(); this.ctx.setLineDash([]); } // Draw sliding window drawSlidingWindow(width) { // Window frame this.ctx.strokeRect(-width/2, -4, width, 8); // Sliding panels this.ctx.beginPath(); this.ctx.moveTo(-width/4, -4); this.ctx.lineTo(-width/4, 4); this.ctx.moveTo(width/4, -4); this.ctx.lineTo(width/4, 4); this.ctx.stroke(); // Slide direction this.ctx.setLineDash([1, 2]); this.drawArrow(-width/4, 6, width/4, 6); this.ctx.setLineDash([]); } // Draw bay window drawBayWindow() { const size = this.symbolSizes.bayWindow; // Bay window outline (angled) this.ctx.beginPath(); this.ctx.moveTo(-size.width/2, 0); this.ctx.lineTo(-size.width/4, -size.height/2); this.ctx.lineTo(size.width/4, -size.height/2); this.ctx.lineTo(size.width/2, 0); this.ctx.stroke(); // Window divisions this.ctx.beginPath(); this.ctx.moveTo(-size.width/4, -size.height/2); this.ctx.lineTo(-size.width/4, 0); this.ctx.moveTo(size.width/4, -size.height/2); this.ctx.lineTo(size.width/4, 0); this.ctx.stroke(); } // Draw corner window drawCornerWindow() { const size = this.symbolSizes.cornerWindow; // L-shaped window this.ctx.strokeRect(-size.width/2, -4, size.width/2 + 4, 8); this.ctx.strokeRect(-4, -size.height/2, 8, size.height/2 + 4); // Corner indicator this.ctx.beginPath(); this.ctx.arc(0, 0, 3, 0, 2 * Math.PI); this.ctx.stroke(); } // Helper method to draw arrows drawArrow(startX, startY, endX, endY) { const angle = Math.atan2(endY - startY, endX - startX); const arrowLength = 5; // Arrow line this.ctx.beginPath(); this.ctx.moveTo(startX, startY); this.ctx.lineTo(endX, endY); this.ctx.stroke(); // Arrow head this.ctx.beginPath(); this.ctx.moveTo(endX, endY); this.ctx.lineTo(endX - arrowLength * Math.cos(angle - Math.PI/6), endY - arrowLength * Math.sin(angle - Math.PI/6)); this.ctx.moveTo(endX, endY); this.ctx.lineTo(endX - arrowLength * Math.cos(angle + Math.PI/6), endY - arrowLength * Math.sin(angle + Math.PI/6)); this.ctx.stroke(); } // Get symbol dimensions for placement getSymbolDimensions(symbolType, subType) { const key = subType ? `${symbolType}_${subType}` : symbolType; return this.symbolSizes[key] || this.symbolSizes[symbolType] || { width: 40, height: 8 }; } // Check if point is within symbol bounds isPointInSymbol(x, y, element) { const centerX = (element.startX + element.endX) / 2; const centerY = (element.startY + element.endY) / 2; const dimensions = this.getSymbolDimensions(element.type, element.subType); return Math.abs(x - centerX) <= dimensions.width / 2 && Math.abs(y - centerY) <= dimensions.height / 2; } // Create symbol element with proper positioning createSymbolElement(type, startX, startY, endX, endY, options = {}) { return { id: Date.now().toString(), type: type, startX: startX, startY: startY, endX: endX, endY: endY, color: options.color || (type === 'door' ? '#8B4513' : '#0066cc'), material: options.material || 'standard', // Symbol-specific properties doorType: options.doorType || 'single', windowType: options.windowType || 'casement', swingDirection: options.swingDirection || 'right', // Wall attachment info attachedToWall: options.attachedToWall || null, wallPosition: options.wallPosition || 0.5 // 0-1 position along wall }; } // Find the best wall to attach symbol to findNearestWall(x, y, walls, maxDistance = 20) { let nearestWall = null; let minDistance = maxDistance; let wallPosition = 0.5; walls.forEach(wall => { if (wall.type !== 'wall') return; // Calculate distance from point to wall line const A = y - wall.startY; const B = wall.startX - x; const C = (wall.endY - wall.startY) * x - (wall.endX - wall.startX) * y; const distance = Math.abs(A * wall.endX + B * wall.endY + C) / Math.sqrt(A * A + B * B); if (distance < minDistance) { // Calculate position along wall (0-1) const wallLength = Math.sqrt( Math.pow(wall.endX - wall.startX, 2) + Math.pow(wall.endY - wall.startY, 2) ); const distanceFromStart = Math.sqrt( Math.pow(x - wall.startX, 2) + Math.pow(y - wall.startY, 2) ); wallPosition = Math.max(0, Math.min(1, distanceFromStart / wallLength)); minDistance = distance; nearestWall = wall; } }); return nearestWall ? { wall: nearestWall, position: wallPosition, distance: minDistance } : null; } }