|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>3D Drawing Game (Multiplayer)</title> |
|
<style> |
|
body { |
|
margin: 0; |
|
padding: 0; |
|
display: flex; |
|
flex-direction: column; |
|
justify-content: center; |
|
align-items: center; |
|
height: 100vh; |
|
background-color: #f0f0f0; |
|
font-family: Arial, sans-serif; |
|
} |
|
#gameContainer { |
|
width: 800px; |
|
height: 600px; |
|
border: 2px solid #333; |
|
background-color: #fff; |
|
} |
|
#controls { |
|
margin-top: 10px; |
|
} |
|
button { |
|
margin: 0 5px; |
|
padding: 5px 10px; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div id="gameContainer"> |
|
<svg id="gameSvg" width="800" height="600"></svg> |
|
</div> |
|
<div id="controls"> |
|
<button id="clearBtn">Clear Canvas</button> |
|
<input type="color" id="colorPicker" value="#000000"> |
|
</div> |
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.4.1/socket.io.min.js"></script> |
|
<script> |
|
const socket = io('http://localhost:3000'); |
|
const svg = document.getElementById('gameSvg'); |
|
const clearBtn = document.getElementById('clearBtn'); |
|
const colorPicker = document.getElementById('colorPicker'); |
|
let isDrawing = false; |
|
let currentLine = null; |
|
let lines = []; |
|
let rotationX = 0; |
|
let rotationY = 0; |
|
let currentColor = '#000000'; |
|
|
|
function project3Dto2D(x, y, z) { |
|
const focalLength = 400; |
|
const scale = focalLength / (focalLength + z); |
|
return { |
|
x: x * scale + 400, |
|
y: y * scale + 300 |
|
}; |
|
} |
|
|
|
function rotatePoint(x, y, z) { |
|
const cosX = Math.cos(rotationX); |
|
const sinX = Math.sin(rotationX); |
|
const cosY = Math.cos(rotationY); |
|
const sinY = Math.sin(rotationY); |
|
|
|
const rotatedY = y * cosX - z * sinX; |
|
const rotatedZ = y * sinX + z * cosX; |
|
const rotatedX = x * cosY + rotatedZ * sinY; |
|
|
|
return { x: rotatedX, y: rotatedY, z: -x * sinY + rotatedZ * cosY }; |
|
} |
|
|
|
function startDrawing(e) { |
|
isDrawing = true; |
|
const { clientX, clientY } = e; |
|
const start = project3Dto2D(clientX - 400, clientY - 300, 0); |
|
currentLine = { points: [{ x: clientX - 400, y: clientY - 300, z: 0 }], color: currentColor }; |
|
const lineElement = document.createElementNS('http://www.w3.org/2000/svg', 'polyline'); |
|
lineElement.setAttribute('points', `${start.x},${start.y}`); |
|
lineElement.setAttribute('fill', 'none'); |
|
lineElement.setAttribute('stroke', currentColor); |
|
lineElement.setAttribute('stroke-width', '2'); |
|
svg.appendChild(lineElement); |
|
currentLine.element = lineElement; |
|
|
|
socket.emit('startLine', { x: clientX - 400, y: clientY - 300, z: 0, color: currentColor }); |
|
} |
|
|
|
function draw(e) { |
|
if (!isDrawing) return; |
|
const { clientX, clientY } = e; |
|
const point = { x: clientX - 400, y: clientY - 300, z: 0 }; |
|
currentLine.points.push(point); |
|
const projected = project3Dto2D(point.x, point.y, point.z); |
|
const points = currentLine.element.getAttribute('points'); |
|
currentLine.element.setAttribute('points', `${points} ${projected.x},${projected.y}`); |
|
|
|
socket.emit('drawLine', { x: clientX - 400, y: clientY - 300, z: 0 }); |
|
} |
|
|
|
function stopDrawing() { |
|
if (!isDrawing) return; |
|
isDrawing = false; |
|
lines.push(currentLine); |
|
currentLine = null; |
|
|
|
socket.emit('endLine'); |
|
} |
|
|
|
function updateRotation(e) { |
|
rotationY -= e.movementX * 0.01; |
|
rotationX -= e.movementY * 0.01; |
|
redrawLines(); |
|
|
|
socket.emit('updateRotation', { rotationX, rotationY }); |
|
} |
|
|
|
function redrawLines() { |
|
lines.forEach(line => { |
|
const projectedPoints = line.points.map(point => { |
|
const rotated = rotatePoint(point.x, point.y, point.z); |
|
return project3Dto2D(rotated.x, rotated.y, rotated.z); |
|
}); |
|
const pointsString = projectedPoints.map(p => `${p.x},${p.y}`).join(' '); |
|
line.element.setAttribute('points', pointsString); |
|
}); |
|
} |
|
|
|
function clearCanvas() { |
|
lines.forEach(line => svg.removeChild(line.element)); |
|
lines = []; |
|
socket.emit('clearCanvas'); |
|
} |
|
|
|
svg.addEventListener('mousedown', startDrawing); |
|
svg.addEventListener('mousemove', (e) => { |
|
if (e.buttons === 1) { |
|
draw(e); |
|
} else if (e.buttons === 2) { |
|
updateRotation(e); |
|
} |
|
}); |
|
svg.addEventListener('mouseup', stopDrawing); |
|
svg.addEventListener('mouseleave', stopDrawing); |
|
svg.addEventListener('contextmenu', (e) => e.preventDefault()); |
|
|
|
clearBtn.addEventListener('click', clearCanvas); |
|
colorPicker.addEventListener('change', (e) => { |
|
currentColor = e.target.value; |
|
}); |
|
|
|
|
|
socket.on('startLine', (data) => { |
|
const start = project3Dto2D(data.x, data.y, data.z); |
|
const lineElement = document.createElementNS('http://www.w3.org/2000/svg', 'polyline'); |
|
lineElement.setAttribute('points', `${start.x},${start.y}`); |
|
lineElement.setAttribute('fill', 'none'); |
|
lineElement.setAttribute('stroke', data.color); |
|
lineElement.setAttribute('stroke-width', '2'); |
|
svg.appendChild(lineElement); |
|
lines.push({ points: [data], element: lineElement, color: data.color }); |
|
}); |
|
|
|
socket.on('drawLine', (data) => { |
|
const line = lines[lines.length - 1]; |
|
line.points.push(data); |
|
const projected = project3Dto2D(data.x, data.y, data.z); |
|
const points = line.element.getAttribute('points'); |
|
line.element.setAttribute('points', `${points} ${projected.x},${projected.y}`); |
|
}); |
|
|
|
socket.on('updateRotation', (data) => { |
|
rotationX = data.rotationX; |
|
rotationY = data.rotationY; |
|
redrawLines(); |
|
}); |
|
|
|
socket.on('clearCanvas', clearCanvas); |
|
</script> |
|
</body> |
|
</html> |