music-video-gen/tv-player/bugs_v2.html
2025-11-08 16:05:55 +01:00

381 lines
15 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gloomy Room Shadow Scene</title>
<!-- Load Tailwind CSS for utility styling -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Load Three.js library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
/* Custom styles for the 3D canvas */
body {
margin: 0;
overflow: hidden;
font-family: 'Inter', sans-serif;
background-color: #0d0d10; /* Very dark background */
}
canvas {
display: block;
}
</style>
</head>
<body>
<div id="info" class="absolute top-4 left-1/2 transform -translate-x-1/2 p-2 bg-gray-900 bg-opacity-70 text-white rounded-lg shadow-xl text-xs sm:text-sm z-10">
Spiders scattering in a gloomy, shadow-casting room. (Use mouse to look around)
</div>
<script>
// --- Global Variables ---
let scene, camera, renderer;
let ambientLight, lampLight, lampMesh;
let pointer = new THREE.Vector2();
let targetRotation = { x: 0, y: 0 };
const BUGS_COUNT = 30;
const bugs = [];
const floorHeight = 0;
// --- Configuration ---
const ROOM_SIZE = 15;
const LAMP_HEIGHT = 4;
const BUG_SPEED = 0.007; // Slightly faster for the larger bugs
const DAMPING_FACTOR = 0.05;
// --- Utility Functions ---
/**
* Converts degrees to radians.
* @param {number} degrees
* @returns {number}
*/
function degToRad(degrees) {
return degrees * (Math.PI / 180);
}
/**
* Initializes the Three.js scene, camera, and renderer.
*/
function init() {
// 1. Scene Setup
scene = new THREE.Scene();
scene.background = new THREE.Color(0x101015);
// 2. Renderer Setup (Enable Shadows)
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // Softer shadows
document.body.appendChild(renderer.domElement);
// 3. Camera Setup (First-person perspective near the floor)
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 1.5, 5); // Stand a bit above the floor, facing into the room
camera.lookAt(0, 1.5, 0);
// 4. Lighting Setup
setupLighting();
// 5. Environment Setup
setupRoom();
setupTable();
// 6. Bugs Setup
setupBugs();
// 7. Event Listeners
window.addEventListener('resize', onWindowResize);
document.addEventListener('mousemove', onMouseMove);
console.log("Three.js Scene Initialized.");
animate();
}
/**
* Sets up the lighting: Ambient and the central PointLight (lamp).
*/
function setupLighting() {
// Very dim ambient light for general visibility
ambientLight = new THREE.AmbientLight(0x444444, 0.5);
scene.add(ambientLight);
// Point Light (The Lamp)
const lampColor = 0xffe9a0; // Warm, dim color
const lampIntensity = 1.8;
const lampDistance = 15;
lampLight = new THREE.PointLight(lampColor, lampIntensity, lampDistance);
lampLight.position.set(0, LAMP_HEIGHT, 0);
// Enable shadow casting for the lamp
lampLight.castShadow = true;
// Configure shadow camera properties for better shadow quality
lampLight.shadow.mapSize.width = 1024;
lampLight.shadow.mapSize.height = 1024;
lampLight.shadow.camera.near = 0.5;
lampLight.shadow.camera.far = 10;
lampLight.shadow.bias = -0.0001; // Tiny bias to prevent shadow artifacts
scene.add(lampLight);
// Visual mesh for the light source (the bulb)
const bulbGeometry = new THREE.SphereGeometry(0.1, 8, 8);
const bulbMaterial = new THREE.MeshBasicMaterial({ color: lampColor });
lampMesh = new THREE.Mesh(bulbGeometry, bulbMaterial);
lampMesh.position.copy(lampLight.position);
scene.add(lampMesh);
}
/**
* Sets up the room (walls and floor).
*/
function setupRoom() {
const wallMaterial = new THREE.MeshPhongMaterial({
color: 0x333333,
side: THREE.BackSide, // Render walls from the inside
shininess: 5,
});
// Floor
const floorGeometry = new THREE.PlaneGeometry(ROOM_SIZE * 2, ROOM_SIZE * 2);
const floorMesh = new THREE.Mesh(floorGeometry, wallMaterial);
floorMesh.rotation.x = degToRad(-90);
floorMesh.position.y = floorHeight;
floorMesh.receiveShadow = true;
scene.add(floorMesh);
// Walls (Simple box around the camera position)
const wallGeometry = new THREE.BoxGeometry(ROOM_SIZE * 2, ROOM_SIZE * 2, ROOM_SIZE * 2);
const wallsMesh = new THREE.Mesh(wallGeometry, wallMaterial);
wallsMesh.position.y = ROOM_SIZE; // Center the box vertically
wallsMesh.receiveShadow = true;
scene.add(wallsMesh);
}
/**
* Adds a simple table object for more complex shadows.
*/
function setupTable() {
const tableMaterial = new THREE.MeshPhongMaterial({
color: 0x5a3d2b, // Dark wood color
shininess: 10,
});
// Table top
const tableTop = new THREE.Mesh(
new THREE.BoxGeometry(3, 0.1, 1.5),
tableMaterial
);
tableTop.position.set(2, 1.5 + 0.05, 0);
tableTop.castShadow = true;
tableTop.receiveShadow = true;
scene.add(tableTop);
// Table legs (using a loop for simplicity)
const legGeometry = new THREE.BoxGeometry(0.1, 1.5, 0.1);
const positions = [
[3.4, 0.75, 0.65], [-0.4, 0.75, 0.65],
[3.4, 0.75, -0.65], [-0.4, 0.75, -0.65]
];
positions.forEach(pos => {
const leg = new THREE.Mesh(legGeometry, tableMaterial);
leg.position.set(pos[0], pos[1], pos[2]);
leg.castShadow = true;
leg.receiveShadow = true;
scene.add(leg);
});
}
/**
* Creates a single, more detailed spider mesh (sphere body and 8 legs).
* @returns {THREE.Group}
*/
function createBugMesh() {
const bugGroup = new THREE.Group();
// Material for all parts
const bugMaterial = new THREE.MeshPhongMaterial({
color: 0x0a0a0a, // Almost black
shininess: 30,
});
// --- 1. Body (Sphere) ---
const bodyRadius = 0.15;
const bodyGeometry = new THREE.SphereGeometry(bodyRadius, 16, 16);
const body = new THREE.Mesh(bodyGeometry, bugMaterial);
// Center the sphere body vertically (half its height)
body.position.y = bodyRadius;
body.castShadow = true;
body.receiveShadow = true;
bugGroup.add(body);
// --- 2. Legs (8 thin cylinders) ---
const legLength = 0.3; // Made longer for spider look
const legWidth = 0.005;
const legGeometry = new THREE.CylinderGeometry(legWidth, legWidth, legLength, 4);
const numLegs = 8;
// Slightly offset starting angle so legs aren't perfectly aligned with world axes
const legAngleStart = degToRad(45 / 2);
// Radial distance from the center (0, 0) where the legs pivot on the sphere's base
const legRadialPivot = 0.08;
const legTiltDown = degToRad(15); // Tilt angle for a natural spider stance
for (let i = 0; i < numLegs; i++) {
const leg = new THREE.Mesh(legGeometry, bugMaterial);
// Calculate the angle for this leg
const angle = legAngleStart + (i * degToRad(360 / numLegs));
// 1. Calculate the pivot point on the sphere's base surface (X/Z plane)
const pivotX = legRadialPivot * Math.sin(angle);
const pivotZ = legRadialPivot * Math.cos(angle);
// 2. Set the initial position to the attachment point near the base of the sphere
leg.position.set(pivotX, bodyRadius - 0.03, pivotZ);
// 3. Offset the leg center so the end point (pivot point) rests at the calculated position
const halfLength = legLength / 2;
// Move the center of the leg (halfLength distance) along its rotation angle
leg.position.x += halfLength * Math.sin(angle);
leg.position.z += halfLength * Math.cos(angle);
// --- Rotation Calculation ---
// Rotation 1: Align the vertical cylinder horizontally
leg.rotation.x = degToRad(90);
// Rotation 2: Orient the leg outward (around Y axis)
leg.rotation.y = angle;
// Rotation 3: Tilt the leg downwards on its local axis (Z) for a crawling look
if (i % 2 === 0) {
leg.rotation.z = legTiltDown; // Inward tilt
} else {
leg.rotation.z = -legTiltDown; // Outward tilt
}
leg.castShadow = true;
leg.receiveShadow = true;
bugGroup.add(leg);
}
bugGroup.position.y = floorHeight; // Group origin is on the floor
return bugGroup;
}
/**
* Creates and places the 'bugs' meshes.
*/
function setupBugs() {
for (let i = 0; i < BUGS_COUNT; i++) {
// Use the new detailed bug mesh
const bug = createBugMesh();
// Random position within the room limits
bug.position.x = (Math.random() - 0.5) * (ROOM_SIZE - 2);
bug.position.z = (Math.random() - 0.5) * (ROOM_SIZE - 2);
// Random initial rotation
bug.rotation.y = Math.random() * Math.PI * 2;
// Add velocity/state data to the bug object
bug.velocity = new THREE.Vector3(
(Math.random() - 0.5) * BUG_SPEED,
0,
(Math.random() - 0.5) * BUG_SPEED
);
scene.add(bug);
bugs.push(bug);
}
}
/**
* Main animation loop.
*/
function animate() {
requestAnimationFrame(animate);
// Update bug positions and rotation
bugs.forEach(bug => {
// Apply current velocity
bug.position.add(bug.velocity);
// Simple collision detection with room boundaries (bounce back)
const limit = ROOM_SIZE / 2 - 0.2;
if (bug.position.x > limit || bug.position.x < -limit) {
bug.velocity.x *= -1;
// Adjust bug rotation to face the new direction
bug.rotation.y = Math.atan2(bug.velocity.x, bug.velocity.z);
}
if (bug.position.z > limit || bug.position.z < -limit) {
bug.velocity.z *= -1;
// Adjust bug rotation to face the new direction
bug.rotation.y = Math.atan2(bug.velocity.x, bug.velocity.z);
}
// Small random change in direction for erratic movement (like a real bug)
bug.velocity.x += (Math.random() - 0.5) * 0.0005;
bug.velocity.z += (Math.random() - 0.5) * 0.0005;
// Clamp velocity to prevent running too fast
bug.velocity.clampLength(0, BUG_SPEED * 1.5);
// Smooth rotation towards the direction of travel
const targetYRotation = Math.atan2(bug.velocity.x, bug.velocity.z);
bug.rotation.y += (targetYRotation - bug.rotation.y) * 0.1;
});
// Camera Look Around (Dampened rotation)
camera.rotation.y += (targetRotation.y - camera.rotation.y) * DAMPING_FACTOR;
renderer.render(scene, camera);
}
// --- Event Handlers ---
/**
* Handles window resize to maintain aspect ratio and prevent distortion.
*/
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
/**
* Handles mouse movement to control the camera's rotation (look around).
* @param {MouseEvent} event
*/
function onMouseMove(event) {
// Calculate normalized device coordinates (-1 to +1)
pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
pointer.y = - (event.clientY / window.innerHeight) * 2 + 1;
// Simple map of mouse position to target rotation angles
targetRotation.y = -pointer.x * degToRad(30);
// targetRotation.x = -pointer.y * degToRad(10); // Disabled X rotation for a simpler floor-view
}
// Wait for the window to load before initializing the 3D scene
window.onload = function() {
try {
init();
} catch (e) {
console.error("Three.js initialization failed:", e);
document.body.innerHTML = `
<div class="flex items-center justify-center h-screen bg-gray-900 text-white">
<p class="text-xl p-4 bg-red-700 rounded-lg">Error loading 3D scene. Console has more details.</p>
</div>
`;
}
};
</script>
</body>
</html>