279 lines
No EOL
9.4 KiB
JavaScript
279 lines
No EOL
9.4 KiB
JavaScript
// Configuration
|
|
// ---------------------------------------------------------------------------------
|
|
const config = {
|
|
logoSrc: 'img/logo.png', // Path to the logo image
|
|
logoHeight: 50, // Logo height
|
|
backgroundColor: '#090909', // Canvas background color
|
|
lineColor: '#159262', // Default dashed line color
|
|
lineThickness: 3, // Dashed line thickness
|
|
randomizeYellowLines: true, // Enable or disable random yellow lines
|
|
yellowLineColor: '#9d921d', // Yellow line color
|
|
avatarBackgroundColor: '#090909', // Background color behind the avatar
|
|
borderThickness: 5, // Border thickness around avatars
|
|
borderColor: '#1bec99', // Border color around avatars
|
|
urlText: '[ Create your own at ccc.cyber.to ]', // Text to display on the canvas
|
|
urlFont: '16px monospace', // Font for URL text
|
|
urlColor: '#159262', // URL text color
|
|
avatarScale: 75, // Avatar scale percentage
|
|
drawMazeBackground: true, // Whether to draw a random maze background
|
|
mazeColor: '#05130f', // Color of the maze lines
|
|
mazeThickness: 2, // Thickness of the maze lines
|
|
drawRedLine: true, // Enable or disable the red line
|
|
redLineColor: '#FB2735', // Red line color
|
|
consoleLogging: false, // Enable or disable console logging
|
|
};
|
|
// ---------------------------------------------------------------------------------
|
|
|
|
// Converts degrees to radians
|
|
const toRad = (x) => {
|
|
if (config.consoleLogging) console.log('Function call: toRad');
|
|
return x * (Math.PI / 180);
|
|
};
|
|
|
|
const dist = [200, 330, 450]; // Distance of each user layer from the center
|
|
const numb = [8, 15, 26]; // Number of users in each layer
|
|
const radius = [64, 58, 50]; // Radius of avatars in each layer
|
|
let userNum = 0; // Counter for the number of users rendered
|
|
let remainingImg = 0; // Counter for remaining images to load
|
|
let totalImg = 0; // Total images to load
|
|
|
|
function render(users, selfUser) {
|
|
if (config.consoleLogging) console.log('Function call: render');
|
|
userNum = 0;
|
|
remainingImg = 0;
|
|
|
|
// Get canvas element
|
|
const canvas = document.getElementById("canvas");
|
|
if (!canvas) return;
|
|
const ctx = canvas.getContext("2d"); // Get canvas context
|
|
|
|
const width = canvas.width;
|
|
const height = canvas.height;
|
|
|
|
// Draw maze if enabled
|
|
if (config.drawMazeBackground) {
|
|
drawMazeBackground(ctx, width, height);
|
|
}
|
|
|
|
// Configure canvas background
|
|
ctx.fillStyle = config.backgroundColor;
|
|
ctx.globalCompositeOperation = 'destination-over';
|
|
ctx.fillRect(0, 0, width, height);
|
|
ctx.globalCompositeOperation = 'source-over';
|
|
|
|
const userPositions = [];
|
|
const tasks = [];
|
|
const mainAvatarRadius = 110 * (config.avatarScale / 100); // Scale main avatar
|
|
|
|
totalImg += 1;
|
|
remainingImg += 1;
|
|
loadImage(
|
|
ctx,
|
|
selfUser.avatar,
|
|
(width / 2) - mainAvatarRadius,
|
|
(height / 2) - mainAvatarRadius,
|
|
mainAvatarRadius,
|
|
null,
|
|
tasks
|
|
);
|
|
|
|
// Position and render user avatars
|
|
for (let layerIndex = 0; layerIndex < 3; layerIndex++) {
|
|
const angleSize = 360 / numb[layerIndex];
|
|
|
|
for (let i = 0; i < numb[layerIndex]; i++) {
|
|
if (userNum >= users.length) break;
|
|
|
|
totalImg += 1;
|
|
remainingImg += 1;
|
|
|
|
const offset = layerIndex * 30;
|
|
const r = toRad(i * angleSize + offset);
|
|
|
|
const centerX = Math.cos(r) * dist[layerIndex] + width / 2;
|
|
const centerY = Math.sin(r) * dist[layerIndex] + height / 2;
|
|
userPositions.push({ x: centerX, y: centerY });
|
|
|
|
// Render connecting lines
|
|
ctx.beginPath();
|
|
ctx.setLineDash([10, 3]);
|
|
ctx.moveTo(width / 2, height / 2);
|
|
ctx.lineTo(centerX, centerY);
|
|
ctx.strokeStyle = config.lineColor;
|
|
ctx.lineWidth = config.lineThickness;
|
|
ctx.stroke();
|
|
ctx.setLineDash([]);
|
|
|
|
loadImage(
|
|
ctx,
|
|
users[userNum].avatar,
|
|
centerX - radius[layerIndex] * (config.avatarScale / 100),
|
|
centerY - radius[layerIndex] * (config.avatarScale / 100),
|
|
radius[layerIndex] * (config.avatarScale / 100),
|
|
null,
|
|
tasks
|
|
);
|
|
|
|
userNum++;
|
|
}
|
|
}
|
|
|
|
// Draw random yellow lines if enabled
|
|
if (config.randomizeYellowLines) {
|
|
randomizeYellowLines(ctx, userPositions, 10);
|
|
}
|
|
|
|
// Draw red line if enabled
|
|
if (config.drawRedLine) {
|
|
drawRedLine(ctx, width, height, userPositions);
|
|
}
|
|
|
|
const logo = new Image();
|
|
logo.onload = function () {
|
|
// Load and draw logo
|
|
const aspectRatio = logo.width / logo.height;
|
|
const logoWidth = config.logoHeight * aspectRatio;
|
|
ctx.drawImage(logo, 10, 10, logoWidth, config.logoHeight);
|
|
};
|
|
logo.src = config.logoSrc;
|
|
|
|
// Draw URL text
|
|
ctx.font = config.urlFont;
|
|
ctx.fillStyle = config.urlColor;
|
|
const textWidth = ctx.measureText(config.urlText).width;
|
|
const textXPosition = width - textWidth - 10;
|
|
const textYPosition = height - 10;
|
|
|
|
ctx.fillText(config.urlText, textXPosition, textYPosition);
|
|
}
|
|
|
|
// Draws a maze background
|
|
function drawMazeBackground(ctx, width, height) {
|
|
if (config.consoleLogging) console.log('Function call: drawMazeBackground');
|
|
const cellSize = 20;
|
|
ctx.strokeStyle = config.mazeColor;
|
|
ctx.lineWidth = config.mazeThickness;
|
|
for (let y = 0; y < height; y += cellSize) {
|
|
for (let x = 0; x < width; x += cellSize) {
|
|
if (Math.random() > 0.5) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(x, y);
|
|
ctx.lineTo(x + cellSize, y + cellSize);
|
|
ctx.stroke();
|
|
} else {
|
|
ctx.beginPath();
|
|
ctx.moveTo(x + cellSize, y);
|
|
ctx.lineTo(x, y + cellSize);
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw random yellow lines
|
|
function randomizeYellowLines(ctx, userPositions, maxLines) {
|
|
if (config.consoleLogging) console.log('Function call: randomizeYellowLines');
|
|
const numberOfLines = Math.min(maxLines, Math.floor(Math.random() * userPositions.length));
|
|
|
|
for (let i = 0; i < numberOfLines; i++) {
|
|
const index1 = Math.floor(Math.random() * userPositions.length);
|
|
let index2;
|
|
do {
|
|
index2 = Math.floor(Math.random() * userPositions.length);
|
|
} while (index2 === index1);
|
|
|
|
const startPos = userPositions[index1];
|
|
const endPos = userPositions[index2];
|
|
|
|
ctx.beginPath();
|
|
ctx.setLineDash([10, 3]);
|
|
ctx.moveTo(startPos.x, startPos.y);
|
|
ctx.lineTo(endPos.x, endPos.y);
|
|
ctx.strokeStyle = config.yellowLineColor;
|
|
ctx.lineWidth = config.lineThickness;
|
|
ctx.stroke();
|
|
ctx.setLineDash([]);
|
|
}
|
|
}
|
|
|
|
// Draw a random red line
|
|
function drawRedLine(ctx, width, height, userPositions) {
|
|
if (config.consoleLogging) console.log('Function call: drawRedLine');
|
|
if (userPositions.length === 0) return;
|
|
|
|
const selectedPosition = userPositions[Math.floor(Math.random() * userPositions.length)];
|
|
|
|
ctx.beginPath();
|
|
ctx.setLineDash([10, 3]);
|
|
ctx.moveTo(width / 2, height / 2);
|
|
ctx.lineTo(selectedPosition.x, selectedPosition.y);
|
|
ctx.strokeStyle = config.redLineColor;
|
|
ctx.lineWidth = config.lineThickness;
|
|
ctx.stroke();
|
|
ctx.setLineDash([]);
|
|
}
|
|
|
|
// Load and draw user images
|
|
function loadImage(ctx, url, x, y, r, name, tasks) {
|
|
if (config.consoleLogging) console.log('Function call: loadImage');
|
|
let progress = document.getElementById("outInfo");
|
|
if (!progress) return;
|
|
|
|
tasks.push(() => { /* No text addition here */ });
|
|
|
|
// Decrement image counter
|
|
const decrementRemaining = () => {
|
|
remainingImg -= 1;
|
|
progress.innerText = `Loading avatars: ${totalImg - remainingImg}/${totalImg}`;
|
|
|
|
if (remainingImg <= 0) {
|
|
progress.innerText = "";
|
|
tasks.forEach((task) => task());
|
|
}
|
|
};
|
|
|
|
const img = new Image();
|
|
|
|
// Handle image loading errors
|
|
const drawImageWithFallback = (imageSrc) => {
|
|
img.onload = function() {
|
|
ctx.save();
|
|
ctx.beginPath();
|
|
ctx.arc(x + r, y + r, r, 0, Math.PI * 2, true);
|
|
ctx.closePath();
|
|
ctx.clip();
|
|
|
|
ctx.fillStyle = config.avatarBackgroundColor;
|
|
ctx.beginPath();
|
|
ctx.arc(x + r, y + r, r, 0, Math.PI * 2, true);
|
|
ctx.fill();
|
|
ctx.closePath();
|
|
|
|
ctx.drawImage(img, x, y, r * 2, r * 2);
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(x + r, y + r, r, 0, Math.PI * 2, true);
|
|
ctx.lineWidth = config.borderThickness;
|
|
ctx.strokeStyle = config.borderColor;
|
|
ctx.stroke();
|
|
ctx.closePath();
|
|
|
|
ctx.restore();
|
|
|
|
decrementRemaining();
|
|
};
|
|
|
|
img.onerror = function() {
|
|
console.error(`IMG Error: ${imageSrc}`);
|
|
if (imageSrc !== 'img/placeholder.png') {
|
|
drawImageWithFallback('img/placeholder.png');
|
|
} else {
|
|
decrementRemaining();
|
|
}
|
|
};
|
|
|
|
img.src = imageSrc;
|
|
};
|
|
|
|
drawImageWithFallback(url);
|
|
} |