import * as THREE from 'three'; import { state } from '../state.js'; import { formatTime } from '../utils.js'; // --- Segment Display Definitions --- // Define which segments (indexed 0-6: A, B, C, D, E, F, G) are active for each digit // A=Top, B=TR, C=BR, D=Bottom, E=BL, F=TL, G=Middle const SEGMENTS = { '0': [1, 1, 1, 1, 1, 1, 0], '1': [0, 1, 1, 0, 0, 0, 0], '2': [1, 1, 0, 1, 1, 0, 1], '3': [1, 1, 1, 1, 0, 0, 1], '4': [0, 1, 1, 0, 0, 1, 1], '5': [1, 0, 1, 1, 0, 1, 1], '6': [1, 0, 1, 1, 1, 1, 1], '7': [1, 1, 1, 0, 0, 0, 0], '8': [1, 1, 1, 1, 1, 1, 1], '9': [1, 1, 1, 1, 0, 1, 1], ' ': [0, 0, 0, 0, 0, 0, 0] }; const SEG_THICKNESS = 3; // Thickness of the segment line in canvas pixels const SEG_PADDING = 2; // Padding within a digit segment's box // Colors for active and inactive segments const COLOR_ACTIVE = '#00ff44'; // Bright Fluorescent Green const COLOR_INACTIVE = '#1a1a1a'; // Dim dark gray for 'ghost' segments /** * Draws a single 7-segment digit by drawing active segments. * Now includes drawing of inactive (ghost) segments for better readability. * @param {CanvasRenderingContext2D} ctx * @param {string} digit The digit character (0-9). * @param {number} x Left position of the digit area. * @param {number} y Top position of the digit area. * @param {number} H Total height of the digit area. */ function drawSegmentDigit(ctx, digit, x, y, H) { const segments = SEGMENTS[digit] || SEGMENTS[' ']; const W = H / 2; // Width is half the height for standard aspect ratio // Segment dimensions relative to W and H const hLength = W - 2 * SEG_PADDING; // Vertical length calculation: (Total height - 2 paddings - 3 horizontal thicknesses) / 2 const vLength = (H - (2 * SEG_PADDING) - (3 * SEG_THICKNESS)) / 2; // Helper to draw horizontal segment (A, G, D) const drawH = (index, x_start, y_start) => { ctx.fillStyle = segments[index] ? COLOR_ACTIVE : COLOR_INACTIVE; ctx.fillRect(x_start + SEG_PADDING, y_start, hLength, SEG_THICKNESS); }; // Helper to draw vertical segment (F, B, E, C) const drawV = (index, x_start, y_start) => { ctx.fillStyle = segments[index] ? COLOR_ACTIVE : COLOR_INACTIVE; ctx.fillRect(x_start, y_start, SEG_THICKNESS, vLength); }; // Define segment positions // Horizontal segments // A (Top) - index 0 drawH(0, x, y + SEG_PADDING); // G (Middle) - index 6 drawH(6, x, y + H/2 - SEG_THICKNESS/2); // D (Bottom) - index 3 drawH(3, x, y + H - SEG_PADDING - SEG_THICKNESS); // Vertical segments (Top Half) const topVStart = y + SEG_PADDING + SEG_THICKNESS; const rightVStart = x + W - SEG_PADDING - SEG_THICKNESS; // F (Top-Left) - index 5 drawV(5, x + SEG_PADDING, topVStart); // B (Top-Right) - index 1 drawV(1, rightVStart, topVStart); // Vertical segments (Bottom Half) const bottomVStart = y + H/2 + SEG_THICKNESS/2; // E (Bottom-Left) - index 4 drawV(4, x + SEG_PADDING, bottomVStart); // C (Bottom-Right) - index 2 drawV(2, rightVStart, bottomVStart); } // Function to draw the colon (two dots), now with blinking logic function drawColon(ctx, x, y, H, isVisible) { const dotSize = 4; ctx.fillStyle = COLOR_ACTIVE; if (isVisible) { // Top dot ctx.fillRect(x, y + H * 0.3 - dotSize / 2, dotSize, dotSize); // Bottom dot ctx.fillRect(x, y + H * 0.7 - dotSize / 2, dotSize, dotSize); } else { // Draw inactive colon if not visible, for consistency ctx.fillStyle = COLOR_INACTIVE; ctx.fillRect(x, y + H * 0.3 - dotSize / 2, dotSize, dotSize); ctx.fillRect(x, y + H * 0.7 - dotSize / 2, dotSize, dotSize); } } /** * Draws a simple playback arrow (triangle) * @param {CanvasRenderingContext2D} ctx * @param {number} x Left position of the arrow area. * @param {number} y Top position of the arrow area. * @param {number} H Total height of the arrow area. */ function drawPlaybackArrow(ctx, x, y, H) { const arrowWidth = H * 0.4; // Arrow width relative to digit height const arrowHeight = H * 0.4; // Arrow height relative to digit height ctx.fillStyle = COLOR_ACTIVE; ctx.beginPath(); ctx.moveTo(x, y + H * 0.5 - arrowHeight / 2); // Top point ctx.lineTo(x + arrowWidth, y + H * 0.5); // Right point (center) ctx.lineTo(x, y + H * 0.5 + arrowHeight / 2); // Bottom point ctx.closePath(); ctx.fill(); } // Main function to render the entire time string using segments function drawSegmentDisplay(ctx, timeString) { const canvasWidth = ctx.canvas.width; const canvasHeight = ctx.canvas.height; const timeStringLength = timeString.length; // Clear display to dark background ctx.fillStyle = '#0a0a0a'; ctx.fillRect(0, 0, canvasWidth, canvasHeight); // Constants for layout const charSpacing = 8; // Spacing between digits const digitHeight = canvasHeight - 2 * SEG_PADDING; const digitWidth = digitHeight / 2 + SEG_PADDING; // Total width slot for one digit const colonWidth = 6; const arrowWidth = digitHeight * 0.7; // Approx width for the arrow const arrowPadding = 10; // Space between arrow and first digit // Calculate total display width including arrow and spaces const totalDisplayWidth = arrowWidth + arrowPadding + (4 * digitWidth) + colonWidth + ((timeStringLength - 1) * charSpacing); // Calculate starting X to center the display let currentX = (canvasWidth - totalDisplayWidth) / 2; const currentY = SEG_PADDING; // Draw Playback Arrow if (state.isVideoLoaded && state.videoElement.readyState >= 3) { drawPlaybackArrow(ctx, currentX, currentY, digitHeight); } currentX += arrowWidth + arrowPadding; // Move X after arrow and its padding for (let i = 0; i < timeStringLength; i++) { const char = timeString[i]; if (char === ':') { drawColon(ctx, currentX, currentY, digitHeight, state.blinkState); // Pass blinkState currentX += colonWidth; } else if (char >= '0' && char <= '9') { drawSegmentDigit(ctx, char, currentX, currentY, digitHeight); currentX += digitWidth; } // Add spacing only if it's not the last element if (i < timeStringLength - 1) { currentX += charSpacing; } } } // --- VCR Display Functions --- export function createVcrDisplay() { const canvas = document.createElement('canvas'); canvas.width = 160; // Increased width for arrow and better spacing canvas.height = 32; const ctx = canvas.getContext('2d'); ctx.fillStyle = '#0a0a0a'; ctx.fillRect(0, 0, canvas.width, canvas.height); state.vcrDisplayTexture = new THREE.CanvasTexture(canvas); state.vcrDisplayTexture.needsUpdate = true; const displayGeometry = new THREE.PlaneGeometry(0.45, 0.1); // Adjust geometry width for new canvas size const displayMaterial = new THREE.MeshPhongMaterial({ map: state.vcrDisplayTexture, side: THREE.FrontSide, color: 0xffffff, transparent: true, emissive: 0x00ff44, emissiveIntensity: 0.05, shininess: 0 }); const displayMesh = new THREE.Mesh(displayGeometry, displayMaterial); return displayMesh; } export function updateVcrDisplay(time) { if (!state.vcrDisplayTexture) return; const canvas = state.vcrDisplayTexture.image; const ctx = canvas.getContext('2d'); const timeString = formatTime(time); // Uses the new segment drawing function with ghosting, including blinkState for colon drawSegmentDisplay(ctx, timeString); state.vcrDisplayTexture.needsUpdate = true; }