class HippieCrosshair { constructor(canvas, options = {}) { this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.debug = options.debug || false; // Crosshair options this.size = options.size || 16; this.thickness = options.thickness || 2; this.color = options.color || '#000'; this.gapSize = options.gapSize || 8; this.style = options.style || 'cross'; // Line options this.lineColor = options.lineColor || '#fff'; this.lineWidth = options.lineWidth || 1; this.showConnector = options.showConnector !== false; // Symbol options this.symbolDistance = options.symbolDistance || 128; // Distance to draw next symbol this.symbolSpacing = options.symbolSpacing || 64; // Space between symbols this.symbolSize = options.symbolSize || 8; this.symbolColor = options.symbolColor || '#000'; this.symbolStyle = options.symbolStyle || 'arrow'; this.mouseX = canvas.width / 2; this.mouseY = canvas.height / 2; // Animation control this.isAnimating = true; this.animationFrameId = null; this.setupEventListeners(); this.animate(); } setupEventListeners() { document.addEventListener('mousemove', (event) => { this.mouseX = event.clientX; this.mouseY = event.clientY; }); } animate() { if (!this.isAnimating) { this.animationFrameId = requestAnimationFrame(() => this.animate()); return; } this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); if (this.showConnector) { if (this.debug) this.drawLine(); this.drawConnector(); } // TODO: Autom. Zug zum Zentrum hin ermöglichen this.drawCrosshair(); this.animationFrameId = requestAnimationFrame(() => this.animate()); } drawLine() { const centerX = this.canvas.width / 2; const centerY = this.canvas.height / 2; this.ctx.strokeStyle = this.lineColor; this.ctx.lineWidth = this.lineWidth; this.ctx.beginPath(); this.ctx.moveTo(centerX, centerY); this.ctx.lineTo(this.mouseX, this.mouseY); this.ctx.stroke(); } // TODO: Ausblenden nach Distanz drawConnector() { const centerX = this.canvas.width / 2; const centerY = this.canvas.height / 2; const dx = this.mouseX - centerX; const dy = this.mouseY - centerY; const distance = Math.sqrt(dx * dx + dy * dy); // Only draw symbols if cursor is far enough from center if (distance < this.symbolDistance) { return; } const angle = Math.atan2(dy, dx); // Calculate how many symbols to draw const symbolCount = Math.floor((distance - this.symbolDistance) / this.symbolSpacing); for (let i = 0; i < symbolCount; i++) { const symbolDistance = this.symbolDistance + (i * this.symbolSpacing); const symbolX = centerX + Math.cos(angle) * symbolDistance; const symbolY = centerY + Math.sin(angle) * symbolDistance; this.drawSymbol(symbolX, symbolY, angle); } } drawSymbol(x, y, angle = 0) { this.ctx.fillStyle = this.symbolColor; this.ctx.strokeStyle = this.symbolColor; this.ctx.lineWidth = 1; switch (this.symbolStyle) { case 'circle': this.ctx.beginPath(); this.ctx.arc(x, y, this.symbolSize / 2, 0, Math.PI * 2); this.ctx.fill(); break; case 'diamond': const size = this.symbolSize - (this.symbolSize / 4); this.ctx.beginPath(); this.ctx.moveTo(x, y - size); this.ctx.lineTo(x + size, y); this.ctx.lineTo(x, y + size); this.ctx.lineTo(x - size, y); this.ctx.closePath(); this.ctx.fill(); break; case 'square': this.ctx.fillRect( x - this.symbolSize / 2, y - this.symbolSize / 2, this.symbolSize, this.symbolSize ); break; case 'arrow': this.arrow(x, y, angle); break; } } arrow(x, y, angle) { const size = this.symbolSize - (this.symbolSize / 4); this.ctx.save(); this.ctx.translate(x, y); this.ctx.rotate(angle); // Arrow pointing right this.ctx.beginPath(); this.ctx.moveTo(size, 0); // Tip this.ctx.lineTo(-size, -size); // Back left // this.ctx.lineTo(-size * 0.4, 0); // Middle this.ctx.lineTo(-size, size); // Back right this.ctx.closePath(); this.ctx.fill(); this.ctx.restore(); } drawCrosshair() { this.ctx.strokeStyle = this.color; this.ctx.lineWidth = this.thickness; this.ctx.lineCap = 'round'; switch (this.style) { case 'cross': this.cross(); break; case 'circle': this.circle(); break; case 'dot': this.dot(); break; } } cross() { // Horizontal line this.ctx.beginPath(); this.ctx.moveTo(this.mouseX - this.size, this.mouseY); this.ctx.lineTo(this.mouseX - this.gapSize, this.mouseY); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.moveTo(this.mouseX + this.gapSize, this.mouseY); this.ctx.lineTo(this.mouseX + this.size, this.mouseY); this.ctx.stroke(); // Vertical line this.ctx.beginPath(); this.ctx.moveTo(this.mouseX, this.mouseY - this.size); this.ctx.lineTo(this.mouseX, this.mouseY - this.gapSize); this.ctx.stroke(); this.ctx.beginPath(); this.ctx.moveTo(this.mouseX, this.mouseY + this.gapSize); this.ctx.lineTo(this.mouseX, this.mouseY + this.size); this.ctx.stroke(); } circle() { // Outer circle this.ctx.beginPath(); this.ctx.arc(this.mouseX, this.mouseY, this.size, 0, Math.PI * 2); this.ctx.stroke(); // Inner dot this.ctx.fillStyle = this.color; this.ctx.beginPath(); this.ctx.arc(this.mouseX, this.mouseY, this.thickness * 1.5, 0, Math.PI * 2); this.ctx.fill(); } dot() { this.ctx.fillStyle = this.color; this.ctx.beginPath(); this.ctx.arc(this.mouseX, this.mouseY, this.size / 4, 0, Math.PI * 2); this.ctx.fill(); } setCrosshairStyle(style) { this.style = style; } setColor(color) { this.color = color; } setSymbolStyle(style) { this.symbolStyle = style; } setSymbolColor(color) { this.symbolColor = color; } setLineVisible(visible) { this.showConnector = visible; } startAnimation() { this.isAnimating = true; } stopAnimation() { this.isAnimating = false; } toggleAnimation() { this.isAnimating = !this.isAnimating; } }