Christmas Tree Project

Project Description

This project is a creative web application that simulates a Christmas tree using Three.js and integrates computer vision features through Google MediaPipe. The application is designed to provide an interactive and visually appealing experience for users during the holiday season.

Technologies Used

  • Three.js: For 3D rendering of the Christmas tree and particles.
  • GLSL Shaders: For custom visual effects.
  • Google MediaPipe: For integrating computer vision features.
  • HTML/CSS: For the structure and styling of the web interface.

Project Structure

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Merry Christmas</title>
    <link href="https://fonts.googleapis.com/css2?family=Cinzel&family=Times+New+Roman&display=swap" rel="stylesheet">
    <style>
        /* CSS styles will be added here */
    </style>
</head>
<body>
    <div class="loader">
        <div class="spinner"></div>
        <p>LOADING HOLIDAY MAGIC</p>
    </div>
    <h1>Merry Christmas</h1>
    <div class="upload-wrapper">
        <button>ADD MEMORIES</button>
        <p>Press 'H' to Hide Controls</p>
    </div>
    <div class="webcam-container">
        <video></video>
        <canvas></canvas>
    </div>
    <script type="importmap">
        {
            "three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
            "three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/",
            "@mediapipe/tasks-vision": "https://cdn.jsdelivr.net/npm/@mediapipe/[email protected]/+esm"
        }
    </script>
    <script type="module" src="main.js"></script>
</body>
</html>

CSS

body {
    margin: 0;
    padding: 0;
    background: #000000;
    font-family: 'Times New Roman', serif;
}

.loader {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: #000000;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    color: #d4af37;
}

.spinner {
    border: 4px solid rgba(212, 175, 55, 0.3);
    border-radius: 50%;
    border-top: 4px solid #d4af37;
    width: 40px;
    height: 40px;
    animation: spin 2s linear infinite;
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

h1 {
    font-family: 'Cinzel', serif;
    font-size: 56px;
    text-align: center;
    background: linear-gradient(to right, #ffffff, #d4af37);
    -webkit-text-shadow: 0 0 10px rgba(212, 175, 55, 0.7);
    text-shadow: 0 0 10px rgba(212, 175, 55, 0.7);
}

.upload-wrapper {
    text-align: center;
    margin-top: 20px;
}

.upload-wrapper button {
    background: rgba(255, 255, 255, 0.2);
    backdrop-filter: blur(5px);
    border: 1px solid #d4af37;
    color: #d4af37;
    padding: 10px 20px;
    border-radius: 5px;
    cursor: pointer;
}

.upload-wrapper p {
    margin-top: 10px;
    color: #ffffff;
}

.webcam-container {
    position: fixed;
    bottom: 10px;
    right: 10px;
    opacity: 0;
}

.ui-hidden {
    opacity: 0;
    pointer-events: none;
}

JavaScript

import * as THREE from 'three';
import { EffectComposer, UnrealBloomPass } from 'three/addons/postprocessing/EffectComposer.js';
import { RoomEnvironment, PMREMGenerator } from 'three/addons/environments/RoomEnvironment.js';
import { FilesetResolver, HandLandmarker } from '@mediapipe/tasks-vision';

// Global variables
const STATE = { mode: 'TREE', handData: null };

// Initialize the scene
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.toneMapping = THREE.ReinhardToneMapping;
renderer.toneMappingExposure = 2.2;
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// Lighting
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);

const pointLight = new THREE.PointLight(0xFFA500, 2);
scene.add(pointLight);

const spotLight1 = new THREE.SpotLight(0xFFD700, 1200);
spotLight1.position.set(30, 40, 40);
scene.add(spotLight1);

const spotLight2 = new THREE.SpotLight(0x0000FF, 600);
spotLight2.position.set(-30, 20, -30);
scene.add(spotLight2);

// Post-processing
const composer = new EffectComposer(renderer);
const bloomPass = new UnrealBloomPass(window.innerWidth / window.innerHeight, 0.45, 0.4, 0.7);
composer.addPass(bloomPass);

// Environment
const pmremGenerator = new PMREMGenerator(scene);
const environment = pmremGenerator.createEnvironment();
scene.environment = environment;

// Camera position
camera.position.set(0, 2, 50);

// Particle system
const particles = ;
for (let i = 0; i < 1500; i++) {
    const geometry = new THREE.BoxGeometry(0.1, 0.1, 0.1);
    const material = new THREE.MeshStandardMaterial({ color: 0xFFD700 });
    const particle = new THREE.Mesh(geometry, material);
    particles.push(particle);
    scene.add(particle);
}

// Candy cane
const candyCaneGeometry = new THREE.TubeGeometry(new THREE.CatmullRomCurve3([
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(5, 0, 0),
    new THREE.Vector3(5, 2, 0),
    new THREE.Vector3(0, 2, 0)
]), 20, 0.1, 8, false);
const candyCaneMaterial = new THREE.MeshStandardMaterial({
    color: 0xFF0000,
    map: new THREE.CanvasTexture(createCandyCaneTexture())
});
const candyCane = new THREE.Mesh(candyCaneGeometry, candyCaneMaterial);
scene.add(candyCane);

// Photo wall
const photoTexture = new THREE.CanvasTexture(createPhotoTexture());
const photoGeometry = new THREE.BoxGeometry(2, 3, 0.1);
const photoMaterial = new THREE.MeshStandardMaterial({ map: photoTexture });
const photo = new THREE.Mesh(photoGeometry, photoMaterial);
photo.position.set(0, -5, 0);
scene.add(photo);

// MediaPipe setup
const handLandmarker = await HandLandmarker.create({
    baseOptions: {
        modelAssetPath: 'https://storage.googleapis.com/mediapipe-models/hand_landmarker/float16/1/hand_landmarker.task',
        delegate: 'GPU'
    }
});

// Animation loop
function animate() {
    requestAnimationFrame(animate);
    
    // Update based on state
    if (STATE.mode === 'TREE') {
        particles.forEach((particle, i) => {
            const t = i / particles.length;
            const radius = 10 * (1 - t);
            const angle = t * 50 * Math.PI;
            particle.position.set(radius * Math.sin(angle), t * 10, radius * Math.cos(angle));
        });
    } else if (STATE.mode === 'SCATTER') {
        particles.forEach(particle => {
            particle.position.x += (Math.random() - 0.5) * 0.1;
            particle.position.y += (Math.random() - 0.5) * 0.1;
            particle.position.z += (Math.random() - 0.5) * 0.1;
            particle.rotation.x += Math.random() * 0.1;
            particle.rotation.y += Math.random() * 0.1;
        });
    } else if (STATE.mode === 'FOCUS') {
        // Focus on a photo
        photo.scale.set(4.5, 4.5, 4.5);
        photo.position.set(0, 2, 35);
    }
    
    // Handle hand gestures
    if (STATE.handData) {
        const palmCenter = STATE.handData[9];
        mainGroup.rotation.y = palmCenter.x * Math.PI * 2;
        mainGroup.rotation.x = palmCenter.y * Math.PI;
    }
    
    composer.render(scene, camera);
}

animate();

// Helper functions
function createCandyCaneTexture() {
    const canvas = document.createElement('canvas');
    canvas.width = 256;
    canvas.height = 256;
    const context = canvas.getContext('2d');
    context.fillStyle = '#FFFFFF';
    context.fillRect(0, 0, canvas.width, canvas.height);
    context.fillStyle = '#FF0000';
    for (let i = 0; i < canvas.height; i += 20) {
        context.fillRect(0, i, canvas.width, 10);
    }
    return canvas;
}

function createPhotoTexture() {
    const canvas = document.createElement('canvas');
    canvas.width = 512;
    canvas.height = 768;
    const context = canvas.getContext('2d');
    context.fillStyle = '#FFFFFF';
    context.fillRect(0, 0, canvas.width, canvas.height);
    context.fillStyle = '#FF0000';
    context.font = '48px serif';
    context.fillText('JOYEUX NOEL', 100, 400);
    return canvas;
}

// Event listeners
document.addEventListener('keydown', (event) => {
    if (event.key === 'h' || event.key === 'H') {
        document.querySelector('.upload-wrapper').classList.toggle('ui-hidden');
    }
});

// Handle photo upload
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.addEventListener('change', (event) => {
    const file = event.target.files[0];
    const reader = new FileReader();
    reader.onload = (ev) => {
        new THREE.TextureLoader().load(ev.target.result, (t) => {
            t.colorSpace = THREE.SRGBColorSpace;
            addPhotoToScene(t);
        });
    });
    reader.readAsDataURL(file);
});
document.querySelector('.upload-wrapper').appendChild(fileInput);

function addPhotoToScene(texture) {
    const photoGeometry = new THREE.BoxGeometry(2, 3, 0.1);
    const photoMaterial = new THREE.MeshStandardMaterial({ map: texture });
    const photo = new THREE.Mesh(photoGeometry, photoMaterial);
    photo.position.set(0, -5, 0);
    scene.add(photo);
}

Conclusion

This project showcases a creative use of Three.js and Google MediaPipe to create an interactive and visually appealing Christmas tree. Users can upload their own photos to the scene and interact with the tree using hand gestures. The project is a great example of how modern web technologies can be used to create engaging and immersive experiences.

标签: none

评论已关闭