Refactoring: move out animate loop
This commit is contained in:
parent
942ab5e0ee
commit
043552f36c
@ -52,225 +52,10 @@
|
|||||||
<script src="./src/effects_dust.js"></script>
|
<script src="./src/effects_dust.js"></script>
|
||||||
<script src="./src/effects_flies.js"></script>
|
<script src="./src/effects_flies.js"></script>
|
||||||
<script src="./src/vcr-display.js"></script>
|
<script src="./src/vcr-display.js"></script>
|
||||||
|
<script src="./src/animate.js"></script>
|
||||||
<script src="./src/init.js"></script>
|
<script src="./src/init.js"></script>
|
||||||
|
|
||||||
<!-- 3D Canvas will be injected here by Three.js -->
|
<!-- 3D Canvas will be injected here by Three.js -->
|
||||||
<script>
|
|
||||||
// --- Initialization ---
|
|
||||||
function init() {
|
|
||||||
// 1. Scene Setup (Dark, Ambient)
|
|
||||||
|
|
||||||
scene = new THREE.Scene();
|
|
||||||
scene.background = new THREE.Color(0x000000);
|
|
||||||
|
|
||||||
// 2. Camera Setup
|
|
||||||
const FOV = 65;
|
|
||||||
camera = new THREE.PerspectiveCamera(FOV, window.innerWidth / window.innerHeight, 0.1, 1000);
|
|
||||||
camera.position.set(0, 1.5, 4);
|
|
||||||
|
|
||||||
|
|
||||||
// 3. Renderer Setup
|
|
||||||
renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
||||||
renderer.setPixelRatio(window.devicePixelRatio);
|
|
||||||
// Enable shadows on the renderer
|
|
||||||
renderer.shadowMap.enabled = true;
|
|
||||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // Softer shadows
|
|
||||||
|
|
||||||
|
|
||||||
container.appendChild(renderer.domElement);
|
|
||||||
|
|
||||||
// 4. Lighting (Minimal and focused)
|
|
||||||
const ambientLight = new THREE.AmbientLight(0x111111);
|
|
||||||
scene.add(ambientLight);
|
|
||||||
|
|
||||||
const roomLight = new THREE.PointLight(0xffaa55, 0.05, roomSize);
|
|
||||||
roomLight.position.set(0, 1.8, 0);
|
|
||||||
scene.add(roomLight);
|
|
||||||
|
|
||||||
|
|
||||||
// 5. Build the entire scene with TV and surrounding objects
|
|
||||||
createSceneObjects();
|
|
||||||
|
|
||||||
// 6. Create the Dust Particle System
|
|
||||||
createDust();
|
|
||||||
|
|
||||||
|
|
||||||
// 7. Create the Room Walls and Ceiling
|
|
||||||
createRoomWalls();
|
|
||||||
|
|
||||||
|
|
||||||
// --- 8. Debug Visualization Helpers ---
|
|
||||||
// Visual aids for the light source positions
|
|
||||||
if (debugLight && THREE.PointLightHelper) {
|
|
||||||
const screenHelper = new THREE.PointLightHelper(screenLight, 0.1, 0xff0000); // Red for screen
|
|
||||||
scene.add(screenHelper);
|
|
||||||
|
|
||||||
// Lamp Helper will now work since lampLight is added to the scene
|
|
||||||
const lampHelperPoint = new THREE.PointLightHelper(lampLightPoint, 0.1, 0x00ff00); // Green for lamp
|
|
||||||
scene.add(lampHelperPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 9. Event Listeners
|
|
||||||
window.addEventListener('resize', onWindowResize, false);
|
|
||||||
fileInput.addEventListener('change', loadVideoFile);
|
|
||||||
|
|
||||||
// Button logic
|
|
||||||
loadTapeButton.addEventListener('click', () => {
|
|
||||||
fileInput.click();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Auto-advance to the next video when the current one finishes.
|
|
||||||
videoElement.addEventListener('ended', playNextVideo);
|
|
||||||
|
|
||||||
// Start the animation loop
|
|
||||||
animate();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Helper function to format seconds into MM:SS ---
|
|
||||||
function formatTime(seconds) {
|
|
||||||
if (isNaN(seconds) || seconds === Infinity || seconds < 0) return '--:--';
|
|
||||||
const minutes = Math.floor(seconds / 60);
|
|
||||||
const remainingSeconds = Math.floor(seconds % 60);
|
|
||||||
const paddedMinutes = String(minutes).padStart(2, '0');
|
|
||||||
const paddedSeconds = String(remainingSeconds).padStart(2, '0');
|
|
||||||
return `${paddedMinutes}:${paddedSeconds}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// --- Animation Loop ---
|
|
||||||
function animate() {
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
|
|
||||||
// 1. Dust animation: slow downward drift
|
|
||||||
if (dust) {
|
|
||||||
const positions = dust.geometry.attributes.position.array;
|
|
||||||
for (let i = 1; i < positions.length; i += 3) {
|
|
||||||
positions[i] -= 0.001;
|
|
||||||
if (positions[i] < -2) {
|
|
||||||
positions[i] = 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dust.geometry.attributes.position.needsUpdate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Camera movement (Gentle, random hovering)
|
|
||||||
const globalTime = Date.now() * 0.00005;
|
|
||||||
const lookAtTime = Date.now() * 0.00003;
|
|
||||||
|
|
||||||
const camAmplitude = 0.7;
|
|
||||||
const lookAmplitude = 0.05;
|
|
||||||
|
|
||||||
// Base Camera Position in front of the TV
|
|
||||||
const baseX = -0.5;
|
|
||||||
const baseY = 1.5;
|
|
||||||
const baseZ = 2.5;
|
|
||||||
|
|
||||||
// Base LookAt target (Center of the screen)
|
|
||||||
const baseTargetX = -0.7;
|
|
||||||
const baseTargetY = 1.7;
|
|
||||||
const baseTargetZ = -0.3;
|
|
||||||
|
|
||||||
// Camera Position Offsets (Drift)
|
|
||||||
const camOffsetX = Math.sin(globalTime * 3.1) * camAmplitude;
|
|
||||||
const camOffsetY = Math.cos(globalTime * 2.5) * camAmplitude * 0.4;
|
|
||||||
const camOffsetZ = Math.cos(globalTime * 3.2) * camAmplitude * 1.4;
|
|
||||||
|
|
||||||
camera.position.x = baseX + camOffsetX;
|
|
||||||
camera.position.y = baseY + camOffsetY;
|
|
||||||
camera.position.z = baseZ + camOffsetZ;
|
|
||||||
|
|
||||||
// LookAt Target Offsets (Subtle Gaze Shift)
|
|
||||||
const lookOffsetX = Math.sin(lookAtTime * 1.5) * lookAmplitude;
|
|
||||||
const lookOffsetY = Math.cos(lookAtTime * 1.2) * lookAmplitude;
|
|
||||||
|
|
||||||
// Apply lookAt to the subtly shifted target
|
|
||||||
camera.lookAt(
|
|
||||||
baseTargetX + lookOffsetX,
|
|
||||||
baseTargetY + lookOffsetY,
|
|
||||||
baseTargetZ
|
|
||||||
);
|
|
||||||
|
|
||||||
// 3. Lamp Flicker Effect
|
|
||||||
const flickerChance = 0.995;
|
|
||||||
const restoreRate = 0.15;
|
|
||||||
|
|
||||||
if (Math.random() > flickerChance) {
|
|
||||||
// Flickers quickly to a dimmer random value (between 0.3 and 1.05)
|
|
||||||
let lampLightIntensity = originalLampIntensity * (0.3 + Math.random() * 0.7);
|
|
||||||
lampLightSpot.intensity = lampLightIntensity;
|
|
||||||
lampLightPoint.intensity = lampLightIntensity;
|
|
||||||
} else if (lampLightPoint.intensity < originalLampIntensity) {
|
|
||||||
// Smoothly restore original intensity
|
|
||||||
let lampLightIntensity = THREE.MathUtils.lerp(lampLightPoint.intensity, originalLampIntensity, restoreRate);
|
|
||||||
lampLightSpot.intensity = lampLightIntensity;
|
|
||||||
lampLightPoint.intensity = lampLightIntensity;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Screen Light Pulse and Movement Effect (Updated)
|
|
||||||
if (isVideoLoaded && screenLight.intensity > 0) {
|
|
||||||
// A. Pulse Effect (Intensity Fluctuation)
|
|
||||||
// Generate a small random fluctuation for the pulse (Range: 1.35 to 1.65 around base 1.5)
|
|
||||||
const pulseTarget = originalScreenIntensity + (Math.random() - 0.5) * screenIntensityPulse;
|
|
||||||
// Smoothly interpolate towards the new target fluctuation
|
|
||||||
screenLight.intensity = THREE.MathUtils.lerp(screenLight.intensity, pulseTarget, 0.1);
|
|
||||||
|
|
||||||
// B. Movement Effect (Subtle circle around the screen center - circling the room area)
|
|
||||||
const lightTime = Date.now() * 0.0001;
|
|
||||||
const radius = 0.01;
|
|
||||||
const centerX = 0;
|
|
||||||
const centerY = 1.5;
|
|
||||||
//const centerZ = 1.2; // Use the updated Z position of the light source
|
|
||||||
|
|
||||||
// Move the light in a subtle, erratic circle
|
|
||||||
screenLight.position.x = centerX + Math.cos(lightTime) * radius;
|
|
||||||
screenLight.position.y = centerY + Math.sin(lightTime * 1.5) * radius * 0.5; // Slightly different freq for Y
|
|
||||||
//screenLight.position.z = centerZ; // Keep Z constant at the screen light plane
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Update video texture (essential to grab the next frame)
|
|
||||||
if (videoTexture) {
|
|
||||||
videoTexture.needsUpdate = true;
|
|
||||||
|
|
||||||
// Update time display in the animation loop
|
|
||||||
if (isVideoLoaded && videoElement.readyState >= 3) {
|
|
||||||
const currentTime = formatTime(videoElement.currentTime);
|
|
||||||
const duration = formatTime(videoElement.duration);
|
|
||||||
console.info(`Tape ${currentVideoIndex + 1} of ${videoUrls.length}. Time: ${currentTime} / ${duration}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateFlies();
|
|
||||||
|
|
||||||
const currentTime = baseTime + videoElement.currentTime;
|
|
||||||
|
|
||||||
// Simulate playback time
|
|
||||||
if (Math.abs(currentTime - lastUpdateTime) > 0.1) {
|
|
||||||
updateVcrDisplay(currentTime);
|
|
||||||
lastUpdateTime = currentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blink the colon every second
|
|
||||||
if (currentTime - lastBlinkToggleTime > 0.5) { // Blink every 0.5 seconds
|
|
||||||
blinkState = !blinkState;
|
|
||||||
lastBlinkToggleTime = currentTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RENDER!
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Window Resize Handler ---
|
|
||||||
function onWindowResize() {
|
|
||||||
camera.aspect = window.innerWidth / window.innerHeight;
|
|
||||||
camera.updateProjectionMatrix();
|
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start everything on window load
|
|
||||||
window.onload = init;
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
<!-- textures sourced from https://animalia-life.club/ -->
|
<!-- textures sourced from https://animalia-life.club/ -->
|
||||||
|
|||||||
128
tv-player/src/animate.js
Normal file
128
tv-player/src/animate.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// --- Animation Loop ---
|
||||||
|
function animate() {
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
|
||||||
|
// 1. Dust animation: slow downward drift
|
||||||
|
if (dust) {
|
||||||
|
const positions = dust.geometry.attributes.position.array;
|
||||||
|
for (let i = 1; i < positions.length; i += 3) {
|
||||||
|
positions[i] -= 0.001;
|
||||||
|
if (positions[i] < -2) {
|
||||||
|
positions[i] = 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dust.geometry.attributes.position.needsUpdate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Camera movement (Gentle, random hovering)
|
||||||
|
const globalTime = Date.now() * 0.00005;
|
||||||
|
const lookAtTime = Date.now() * 0.00003;
|
||||||
|
|
||||||
|
const camAmplitude = 0.7;
|
||||||
|
const lookAmplitude = 0.05;
|
||||||
|
|
||||||
|
// Base Camera Position in front of the TV
|
||||||
|
const baseX = -0.5;
|
||||||
|
const baseY = 1.5;
|
||||||
|
const baseZ = 2.5;
|
||||||
|
|
||||||
|
// Base LookAt target (Center of the screen)
|
||||||
|
const baseTargetX = -0.7;
|
||||||
|
const baseTargetY = 1.7;
|
||||||
|
const baseTargetZ = -0.3;
|
||||||
|
|
||||||
|
// Camera Position Offsets (Drift)
|
||||||
|
const camOffsetX = Math.sin(globalTime * 3.1) * camAmplitude;
|
||||||
|
const camOffsetY = Math.cos(globalTime * 2.5) * camAmplitude * 0.4;
|
||||||
|
const camOffsetZ = Math.cos(globalTime * 3.2) * camAmplitude * 1.4;
|
||||||
|
|
||||||
|
camera.position.x = baseX + camOffsetX;
|
||||||
|
camera.position.y = baseY + camOffsetY;
|
||||||
|
camera.position.z = baseZ + camOffsetZ;
|
||||||
|
|
||||||
|
// LookAt Target Offsets (Subtle Gaze Shift)
|
||||||
|
const lookOffsetX = Math.sin(lookAtTime * 1.5) * lookAmplitude;
|
||||||
|
const lookOffsetY = Math.cos(lookAtTime * 1.2) * lookAmplitude;
|
||||||
|
|
||||||
|
// Apply lookAt to the subtly shifted target
|
||||||
|
camera.lookAt(
|
||||||
|
baseTargetX + lookOffsetX,
|
||||||
|
baseTargetY + lookOffsetY,
|
||||||
|
baseTargetZ
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Lamp Flicker Effect
|
||||||
|
const flickerChance = 0.995;
|
||||||
|
const restoreRate = 0.15;
|
||||||
|
|
||||||
|
if (Math.random() > flickerChance) {
|
||||||
|
// Flickers quickly to a dimmer random value (between 0.3 and 1.05)
|
||||||
|
let lampLightIntensity = originalLampIntensity * (0.3 + Math.random() * 0.7);
|
||||||
|
lampLightSpot.intensity = lampLightIntensity;
|
||||||
|
lampLightPoint.intensity = lampLightIntensity;
|
||||||
|
} else if (lampLightPoint.intensity < originalLampIntensity) {
|
||||||
|
// Smoothly restore original intensity
|
||||||
|
let lampLightIntensity = THREE.MathUtils.lerp(lampLightPoint.intensity, originalLampIntensity, restoreRate);
|
||||||
|
lampLightSpot.intensity = lampLightIntensity;
|
||||||
|
lampLightPoint.intensity = lampLightIntensity;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Screen Light Pulse and Movement Effect (Updated)
|
||||||
|
if (isVideoLoaded && screenLight.intensity > 0) {
|
||||||
|
// A. Pulse Effect (Intensity Fluctuation)
|
||||||
|
// Generate a small random fluctuation for the pulse (Range: 1.35 to 1.65 around base 1.5)
|
||||||
|
const pulseTarget = originalScreenIntensity + (Math.random() - 0.5) * screenIntensityPulse;
|
||||||
|
// Smoothly interpolate towards the new target fluctuation
|
||||||
|
screenLight.intensity = THREE.MathUtils.lerp(screenLight.intensity, pulseTarget, 0.1);
|
||||||
|
|
||||||
|
// B. Movement Effect (Subtle circle around the screen center - circling the room area)
|
||||||
|
const lightTime = Date.now() * 0.0001;
|
||||||
|
const radius = 0.01;
|
||||||
|
const centerX = 0;
|
||||||
|
const centerY = 1.5;
|
||||||
|
//const centerZ = 1.2; // Use the updated Z position of the light source
|
||||||
|
|
||||||
|
// Move the light in a subtle, erratic circle
|
||||||
|
screenLight.position.x = centerX + Math.cos(lightTime) * radius;
|
||||||
|
screenLight.position.y = centerY + Math.sin(lightTime * 1.5) * radius * 0.5; // Slightly different freq for Y
|
||||||
|
//screenLight.position.z = centerZ; // Keep Z constant at the screen light plane
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Update video texture (essential to grab the next frame)
|
||||||
|
if (videoTexture) {
|
||||||
|
videoTexture.needsUpdate = true;
|
||||||
|
|
||||||
|
// Update time display in the animation loop
|
||||||
|
if (isVideoLoaded && videoElement.readyState >= 3) {
|
||||||
|
const currentTime = formatTime(videoElement.currentTime);
|
||||||
|
const duration = formatTime(videoElement.duration);
|
||||||
|
console.info(`Tape ${currentVideoIndex + 1} of ${videoUrls.length}. Time: ${currentTime} / ${duration}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFlies();
|
||||||
|
|
||||||
|
const currentTime = baseTime + videoElement.currentTime;
|
||||||
|
|
||||||
|
// Simulate playback time
|
||||||
|
if (Math.abs(currentTime - lastUpdateTime) > 0.1) {
|
||||||
|
updateVcrDisplay(currentTime);
|
||||||
|
lastUpdateTime = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blink the colon every second
|
||||||
|
if (currentTime - lastBlinkToggleTime > 0.5) { // Blink every 0.5 seconds
|
||||||
|
blinkState = !blinkState;
|
||||||
|
lastBlinkToggleTime = currentTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RENDER!
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Window Resize Handler ---
|
||||||
|
function onWindowResize() {
|
||||||
|
camera.aspect = window.innerWidth / window.innerHeight;
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
|
}
|
||||||
@ -62,3 +62,6 @@ function init() {
|
|||||||
// Start the animation loop
|
// Start the animation loop
|
||||||
animate();
|
animate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start everything on window load
|
||||||
|
window.onload = init;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user