217 lines
7.5 KiB
JavaScript
217 lines
7.5 KiB
JavaScript
let vcrDisplayTexture;
|
|
let blinkState = false;
|
|
let lastBlinkToggleTime = 0;
|
|
// --- 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 (isVideoLoaded && 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, 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 ---
|
|
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);
|
|
|
|
vcrDisplayTexture = new THREE.CanvasTexture(canvas);
|
|
vcrDisplayTexture.needsUpdate = true;
|
|
|
|
const displayGeometry = new THREE.PlaneGeometry(0.45, 0.1); // Adjust geometry width for new canvas size
|
|
const displayMaterial = new THREE.MeshBasicMaterial({
|
|
map: vcrDisplayTexture,
|
|
side: THREE.FrontSide,
|
|
color: 0xffffff,
|
|
transparent: true,
|
|
emissive: 0x00ff44,
|
|
emissiveIntensity: 0.1
|
|
});
|
|
|
|
const displayMesh = new THREE.Mesh(displayGeometry, displayMaterial);
|
|
return displayMesh;
|
|
}
|
|
|
|
function updateVcrDisplay(time) {
|
|
if (!vcrDisplayTexture) return;
|
|
|
|
const canvas = 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);
|
|
|
|
vcrDisplayTexture.needsUpdate = true;
|
|
}
|