Three.js Samples

Three.js 3D graphics library examples including scenes, meshes, lighting, materials, animations, and interactive 3D content

Key Facts

Category
3D Graphics
Items
3
Format Families
text

Sample Overview

Three.js 3D graphics library examples including scenes, meshes, lighting, materials, animations, and interactive 3D content This sample set belongs to 3D Graphics and can be used to test related workflows inside Elysia Tools.

💻 Three.js Hello World javascript

🟢 simple

Basic Three.js setup with scenes, cameras, renderers, and simple 3D objects

⏱️ 15 min 🏷️ threejs, 3d, webgl, graphics
Prerequisites: JavaScript basics, WebGL concepts
// Three.js Hello World Examples

import * as THREE from 'three';

// 1. Basic Three.js Scene Setup
class BasicScene {
    constructor(containerId) {
        this.container = document.getElementById(containerId);
        this.init();
    }

    init() {
        // Create scene
        this.scene = new THREE.Scene();
        this.scene.background = new THREE.Color(0x87CEEB);

        // Create camera
        this.camera = new THREE.PerspectiveCamera(
            75,
            window.innerWidth / window.innerHeight,
            0.1,
            1000
        );
        this.camera.position.z = 5;

        // Create renderer
        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.renderer.shadowMap.enabled = true;
        this.container.appendChild(this.renderer.domElement);

        // Add lights
        this.addLights();

        // Add objects
        this.addObjects();

        // Handle window resize
        window.addEventListener('resize', this.onWindowResize.bind(this));

        // Start animation loop
        this.animate();
    }

    addLights() {
        // Ambient light
        const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
        this.scene.add(ambientLight);

        // Directional light
        const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
        directionalLight.position.set(10, 10, 5);
        directionalLight.castShadow = true;
        this.scene.add(directionalLight);
    }

    addObjects() {
        // Add a rotating cube
        const geometry = new THREE.BoxGeometry(1, 1, 1);
        const material = new THREE.MeshPhongMaterial({
            color: 0x00ff00,
            specular: 0x444444,
            shininess: 100
        });
        this.cube = new THREE.Mesh(geometry, material);
        this.cube.castShadow = true;
        this.scene.add(this.cube);

        // Add a sphere
        const sphereGeometry = new THREE.SphereGeometry(0.7, 32, 32);
        const sphereMaterial = new THREE.MeshPhongMaterial({
            color: 0xff0000,
            specular: 0x444444,
            shininess: 100
        });
        this.sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
        this.sphere.position.x = -2;
        this.sphere.castShadow = true;
        this.scene.add(this.sphere);

        // Add a torus
        const torusGeometry = new THREE.TorusGeometry(0.6, 0.2, 16, 100);
        const torusMaterial = new THREE.MeshPhongMaterial({
            color: 0x0000ff,
            specular: 0x444444,
            shininess: 100
        });
        this.torus = new THREE.Mesh(torusGeometry, torusMaterial);
        this.torus.position.x = 2;
        this.torus.castShadow = true;
        this.scene.add(this.torus);

        // Add ground plane
        const planeGeometry = new THREE.PlaneGeometry(20, 20);
        const planeMaterial = new THREE.MeshStandardMaterial({
            color: 0x808080,
            roughness: 0.8,
            metalness: 0.2
        });
        const plane = new THREE.Mesh(planeGeometry, planeMaterial);
        plane.rotation.x = -Math.PI / 2;
        plane.position.y = -2;
        plane.receiveShadow = true;
        this.scene.add(plane);
    }

    onWindowResize() {
        this.camera.aspect = window.innerWidth / window.innerHeight;
        this.camera.updateProjectionMatrix();
        this.renderer.setSize(window.innerWidth, window.innerHeight);
    }

    animate() {
        requestAnimationFrame(this.animate.bind(this));

        // Rotate objects
        this.cube.rotation.x += 0.01;
        this.cube.rotation.y += 0.01;

        this.sphere.rotation.y += 0.02;

        this.torus.rotation.x += 0.01;
        this.torus.rotation.y += 0.02;

        this.renderer.render(this.scene, this.camera);
    }
}

// 2. Text and Labels in 3D Scene
class Text3D {
    constructor(containerId) {
        this.container = document.getElementById(containerId);
        this.init();
    }

    init() {
        this.scene = new THREE.Scene();
        this.camera = new THREE.PerspectiveCamera(
            75,
            window.innerWidth / window.innerHeight,
            0.1,
            1000
        );
        this.camera.position.z = 10;

        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.container.appendChild(this.renderer.domElement);

        this.addLights();
        this.createHelloWorldText();
        this.animate();
    }

    addLights() {
        const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
        this.scene.add(ambientLight);

        const pointLight = new THREE.PointLight(0xffffff, 0.8);
        pointLight.position.set(10, 10, 10);
        this.scene.add(pointLight);
    }

    createHelloWorldText() {
        // Create text using sprite (simpler approach)
        const loader = new THREE.FontLoader();

        // Create text with canvas (fallback method)
        const canvas = document.createElement('canvas');
        const context = canvas.getContext('2d');
        canvas.width = 512;
        canvas.height = 128;

        context.fillStyle = '#ffffff';
        context.font = 'Bold 60px Arial';
        context.textAlign = 'center';
        context.fillText('Hello Three.js!', 256, 80);

        const texture = new THREE.CanvasTexture(canvas);
        const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
        const sprite = new THREE.Sprite(spriteMaterial);
        sprite.scale.set(8, 2, 1);
        sprite.position.set(0, 2, 0);
        this.scene.add(sprite);

        // Add decorative objects around text
        const geometry = new THREE.SphereGeometry(0.3, 32, 32);
        const colors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff];

        for (let i = 0; i < 6; i++) {
            const material = new THREE.MeshPhongMaterial({ color: colors[i] });
            const sphere = new THREE.Mesh(geometry, material);
            const angle = (i / 6) * Math.PI * 2;
            sphere.position.set(
                Math.cos(angle) * 4,
                2,
                Math.sin(angle) * 4
            );
            this.scene.add(sphere);
        }
    }

    animate() {
        requestAnimationFrame(this.animate.bind(this));
        this.renderer.render(this.scene, this.camera);
    }
}

// 3. Interactive Scene with Mouse Controls
class InteractiveScene {
    constructor(containerId) {
        this.container = document.getElementById(containerId);
        this.mouse = new THREE.Vector2();
        this.raycaster = new THREE.Raycaster();
        this.intersectedObject = null;
        this.init();
    }

    init() {
        this.scene = new THREE.Scene();
        this.scene.background = new THREE.Color(0x000033);

        this.camera = new THREE.PerspectiveCamera(
            75,
            window.innerWidth / window.innerHeight,
            0.1,
            1000
        );
        this.camera.position.z = 8;

        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.container.appendChild(this.renderer.domElement);

        this.addLights();
        this.createInteractiveObjects();
        this.addEventListeners();
        this.animate();
    }

    addLights() {
        const ambientLight = new THREE.AmbientLight(0x404040, 0.4);
        this.scene.add(ambientLight);

        const pointLight = new THREE.PointLight(0xffffff, 1);
        this.camera.add(pointLight);
        this.scene.add(this.camera);
    }

    createInteractiveObjects() {
        this.objects = [];

        // Create multiple clickable objects
        const geometries = [
            new THREE.BoxGeometry(1, 1, 1),
            new THREE.SphereGeometry(0.6, 32, 32),
            new THREE.ConeGeometry(0.6, 1, 32),
            new THREE.TorusGeometry(0.6, 0.2, 16, 100),
            new THREE.DodecahedronGeometry(0.8),
            new THREE.OctahedronGeometry(0.7)
        ];

        const colors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff];

        for (let i = 0; i < 6; i++) {
            const material = new THREE.MeshPhongMaterial({
                color: colors[i],
                specular: 0x444444,
                shininess: 100
            });
            const mesh = new THREE.Mesh(geometries[i], material);

            const angle = (i / 6) * Math.PI * 2;
            mesh.position.set(
                Math.cos(angle) * 3,
                Math.sin(angle) * 0.5,
                Math.sin(angle) * 3
            );

            mesh.userData = {
                originalColor: colors[i],
                originalScale: mesh.scale.x
            };

            this.objects.push(mesh);
            this.scene.add(mesh);
        }
    }

    addEventListeners() {
        window.addEventListener('mousemove', (event) => {
            this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
            this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
        });

        window.addEventListener('click', () => {
            if (this.intersectedObject) {
                // Spin the clicked object
                this.tweens = this.tweens || [];

                const tween = {
                    object: this.intersectedObject,
                    rotation: { x: 0, y: 0 },
                    target: { y: Math.PI * 2 },
                    duration: 1000,
                    startTime: Date.now()
                };

                this.tweens.push(tween);
            }
        });
    }

    animate() {
        requestAnimationFrame(this.animate.bind(this));

        // Update raycasting
        this.raycaster.setFromCamera(this.mouse, this.camera);
        const intersects = this.raycaster.intersectObjects(this.objects);

        // Reset all objects
        this.objects.forEach(obj => {
            obj.material.color.setHex(obj.userData.originalColor);
            obj.scale.setScalar(obj.userData.originalScale);
        });

        // Highlight intersected object
        if (intersects.length > 0) {
            this.intersectedObject = intersects[0].object;
            this.intersectedObject.material.color.setHex(0xffffff);
            this.intersectedObject.scale.setScalar(1.2);
        } else {
            this.intersectedObject = null;
        }

        // Update tweens
        if (this.tweens) {
            this.tweens = this.tweens.filter(tween => {
                const elapsed = Date.now() - tween.startTime;
                const progress = Math.min(elapsed / tween.duration, 1);

                tween.object.rotation.y = tween.rotation.y + (tween.target.y - tween.rotation.y) * progress * 0.1;

                return progress < 1;
            });
        }

        // Rotate all objects slowly
        this.objects.forEach((obj, index) => {
            obj.rotation.x += 0.005;
            obj.rotation.z += 0.003;
        });

        this.renderer.render(this.scene, this.camera);
    }
}

// 4. Particle System
class ParticleSystem {
    constructor(containerId) {
        this.container = document.getElementById(containerId);
        this.init();
    }

    init() {
        this.scene = new THREE.Scene();
        this.scene.background = new THREE.Color(0x000000);

        this.camera = new THREE.PerspectiveCamera(
            75,
            window.innerWidth / window.innerHeight,
            0.1,
            1000
        );
        this.camera.position.z = 50;

        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.container.appendChild(this.renderer.domElement);

        this.createParticles();
        this.animate();
    }

    createParticles() {
        const particleCount = 10000;
        const geometry = new THREE.BufferGeometry();

        const positions = new Float32Array(particleCount * 3);
        const colors = new Float32Array(particleCount * 3);
        const sizes = new Float32Array(particleCount);

        for (let i = 0; i < particleCount; i++) {
            const i3 = i * 3;

            // Position
            positions[i3] = (Math.random() - 0.5) * 100;
            positions[i3 + 1] = (Math.random() - 0.5) * 100;
            positions[i3 + 2] = (Math.random() - 0.5) * 100;

            // Color
            const color = new THREE.Color();
            color.setHSL(Math.random(), 0.7, 0.5);
            colors[i3] = color.r;
            colors[i3 + 1] = color.g;
            colors[i3 + 2] = color.b;

            // Size
            sizes[i] = Math.random() * 2;
        }

        geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
        geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));

        const material = new THREE.PointsMaterial({
            size: 1,
            vertexColors: true,
            blending: THREE.AdditiveBlending,
            transparent: true,
            opacity: 0.8
        });

        this.particles = new THREE.Points(geometry, material);
        this.scene.add(this.particles);
    }

    animate() {
        requestAnimationFrame(this.animate.bind(this));

        this.particles.rotation.x += 0.001;
        this.particles.rotation.y += 0.002;

        const positions = this.particles.geometry.attributes.position.array;
        const time = Date.now() * 0.001;

        for (let i = 0; i < positions.length; i += 3) {
            positions[i + 1] += Math.sin(time + positions[i]) * 0.01;
        }

        this.particles.geometry.attributes.position.needsUpdate = true;
        this.renderer.render(this.scene, this.camera);
    }
}

// 5. Wireframe Scene
class WireframeScene {
    constructor(containerId) {
        this.container = document.getElementById(containerId);
        this.init();
    }

    init() {
        this.scene = new THREE.Scene();
        this.scene.background = new THREE.Color(0x111111);

        this.camera = new THREE.PerspectiveCamera(
            75,
            window.innerWidth / window.innerHeight,
            0.1,
            1000
        );
        this.camera.position.z = 5;

        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.container.appendChild(this.renderer.domElement);

        this.createWireframeObjects();
        this.animate();
    }

    createWireframeObjects() {
        // Create wireframe sphere
        const sphereGeometry = new THREE.SphereGeometry(1, 32, 32);
        const sphereMaterial = new THREE.MeshBasicMaterial({
            color: 0x00ff00,
            wireframe: true
        });
        this.sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
        this.sphere.position.x = -2;
        this.scene.add(this.sphere);

        // Create wireframe cube
        const cubeGeometry = new THREE.BoxGeometry(1.5, 1.5, 1.5);
        const cubeMaterial = new THREE.MeshBasicMaterial({
            color: 0xff0000,
            wireframe: true
        });
        this.cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
        this.cube.position.x = 0;
        this.scene.add(this.cube);

        // Create wireframe torus
        const torusGeometry = new THREE.TorusGeometry(1, 0.4, 16, 100);
        const torusMaterial = new THREE.MeshBasicMaterial({
            color: 0x0000ff,
            wireframe: true
        });
        this.torus = new THREE.Mesh(torusGeometry, torusMaterial);
        this.torus.position.x = 2;
        this.scene.add(this.torus);

        // Add grid helper
        const gridHelper = new THREE.GridHelper(20, 20, 0x444444, 0x222222);
        gridHelper.position.y = -2;
        this.scene.add(gridHelper);
    }

    animate() {
        requestAnimationFrame(this.animate.bind(this));

        this.sphere.rotation.y += 0.01;
        this.sphere.rotation.z += 0.005;

        this.cube.rotation.x += 0.01;
        this.cube.rotation.y += 0.01;

        this.torus.rotation.x += 0.005;
        this.torus.rotation.y += 0.01;

        this.renderer.render(this.scene, this.camera);
    }
}

// Usage examples:
// const basicScene = new BasicScene('container1');
// const text3D = new Text3D('container2');
// const interactiveScene = new InteractiveScene('container3');
// const particleSystem = new ParticleSystem('container4');
// const wireframeScene = new WireframeScene('container5');

export {
    BasicScene,
    Text3D,
    InteractiveScene,
    ParticleSystem,
    WireframeScene
};

💻 Materials and Lighting javascript

🟡 intermediate ⭐⭐⭐

Advanced Three.js materials, lighting setups, shadows, and realistic surface rendering

⏱️ 30 min 🏷️ threejs, materials, lighting, pbr
Prerequisites: Three.js basics, 3D graphics concepts, Material properties
// Three.js Materials and Lighting Examples

import * as THREE from 'three';

// 1. Comprehensive Material Showcase
class MaterialShowcase {
    constructor(containerId) {
        this.container = document.getElementById(containerId);
        this.init();
    }

    init() {
        this.scene = new THREE.Scene();
        this.scene.background = new THREE.Color(0x1a1a1a);

        this.camera = new THREE.PerspectiveCamera(
            75,
            window.innerWidth / window.innerHeight,
            0.1,
            1000
        );
        this.camera.position.set(0, 5, 10);

        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.renderer.shadowMap.enabled = true;
        this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
        this.container.appendChild(this.renderer.domElement);

        this.setupLighting();
        this.createMaterialSamples();
        this.createGround();
        this.animate();

        // Add orbit controls for better viewing
        this.addOrbitControls();
    }

    setupLighting() {
        // Ambient light for base illumination
        this.ambientLight = new THREE.AmbientLight(0x404040, 0.3);
        this.scene.add(this.ambientLight);

        // Main directional light (sun)
        this.directionalLight = new THREE.DirectionalLight(0xffffff, 1);
        this.directionalLight.position.set(10, 10, 5);
        this.directionalLight.castShadow = true;
        this.directionalLight.shadow.mapSize.width = 2048;
        this.directionalLight.shadow.mapSize.height = 2048;
        this.directionalLight.shadow.camera.near = 0.5;
        this.directionalLight.shadow.camera.far = 50;
        this.scene.add(this.directionalLight);

        // Fill light
        this.fillLight = new THREE.DirectionalLight(0x87ceeb, 0.5);
        this.fillLight.position.set(-5, 3, -5);
        this.scene.add(this.fillLight);

        // Rim light
        this.rimLight = new THREE.DirectionalLight(0xff6b6b, 0.3);
        this.rimLight.position.set(0, 5, -10);
        this.scene.add(this.rimLight);

        // Point light for dramatic lighting
        this.pointLight = new THREE.PointLight(0xffaa00, 1, 20);
        this.pointLight.position.set(0, 8, 0);
        this.pointLight.castShadow = true;
        this.scene.add(this.pointLight);
    }

    createMaterialSamples() {
        this.materialObjects = [];
        const geometry = new THREE.SphereGeometry(0.8, 64, 64);

        // Basic Materials
        const materials = [
            // Basic Material
            new THREE.MeshBasicMaterial({
                color: 0xff0000,
                name: 'Basic'
            }),

            // Lambert Material
            new THREE.MeshLambertMaterial({
                color: 0x00ff00,
                name: 'Lambert'
            }),

            // Phong Material
            new THREE.MeshPhongMaterial({
                color: 0x0000ff,
                specular: 0x444444,
                shininess: 100,
                name: 'Phong'
            }),

            // Standard Material (PBR)
            new THREE.MeshStandardMaterial({
                color: 0xffd700,
                metalness: 0.5,
                roughness: 0.5,
                name: 'Standard PBR'
            }),

            // Physical Material (Advanced PBR)
            new THREE.MeshPhysicalMaterial({
                color: 0xff1493,
                metalness: 0.7,
                roughness: 0.3,
                clearcoat: 1.0,
                clearcoatRoughness: 0.0,
                name: 'Physical PBR'
            }),

            // Toon Material
            new THREE.MeshToonMaterial({
                color: 0x00ff7f,
                gradientMap: this.createGradientMap(),
                name: 'Toon'
            }),

            // Normal Material (shows normals)
            new THREE.MeshNormalMaterial({
                name: 'Normal'
            }),

            // Depth Material
            new THREE.MeshDepthMaterial({
                name: 'Depth'
            })
        ];

        // Create spheres with different materials
        materials.forEach((material, index) => {
            const mesh = new THREE.Mesh(geometry, material);
            const row = Math.floor(index / 4);
            const col = index % 4;

            mesh.position.set(
                (col - 1.5) * 2.5,
                2,
                (row - 1) * 2.5
            );

            mesh.castShadow = true;
            mesh.receiveShadow = true;
            mesh.userData = {
                originalPosition: mesh.position.clone(),
                material: material
            };

            this.materialObjects.push(mesh);
            this.scene.add(mesh);

            // Add label
            this.addLabel(mesh.position, material.name);
        });
    }

    createGradientMap() {
        const canvas = document.createElement('canvas');
        canvas.width = 256;
        const context = canvas.getContext('2d');

        const gradient = context.createLinearGradient(0, 0, 0, 256);
        gradient.addColorStop(0, '#ffffff');
        gradient.addColorStop(0.5, '#888888');
        gradient.addColorStop(1, '#000000');

        context.fillStyle = gradient;
        context.fillRect(0, 0, 256, 256);

        const texture = new THREE.CanvasTexture(canvas);
        return texture;
    }

    addLabel(position, text) {
        const canvas = document.createElement('canvas');
        const context = canvas.getContext('2d');
        canvas.width = 256;
        canvas.height = 64;

        context.fillStyle = '#ffffff';
        context.font = '20px Arial';
        context.textAlign = 'center';
        context.fillText(text, 128, 40);

        const texture = new THREE.CanvasTexture(canvas);
        const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
        const sprite = new THREE.Sprite(spriteMaterial);

        sprite.position.copy(position);
        sprite.position.y -= 1.5;
        sprite.scale.set(2, 0.5, 1);

        this.scene.add(sprite);
    }

    createGround() {
        const groundGeometry = new THREE.PlaneGeometry(30, 30);
        const groundMaterial = new THREE.MeshStandardMaterial({
            color: 0x333333,
            roughness: 0.8,
            metalness: 0.2
        });

        this.ground = new THREE.Mesh(groundGeometry, groundMaterial);
        this.ground.rotation.x = -Math.PI / 2;
        this.ground.receiveShadow = true;
        this.scene.add(this.ground);
    }

    addOrbitControls() {
        // Simple mouse controls
        this.mouseDown = false;
        this.mouseX = 0;
        this.mouseY = 0;

        this.renderer.domElement.addEventListener('mousedown', (e) => {
            this.mouseDown = true;
            this.mouseX = e.clientX;
            this.mouseY = e.clientY;
        });

        this.renderer.domElement.addEventListener('mouseup', () => {
            this.mouseDown = false;
        });

        this.renderer.domElement.addEventListener('mousemove', (e) => {
            if (!this.mouseDown) return;

            const deltaX = e.clientX - this.mouseX;
            const deltaY = e.clientY - this.mouseY;

            this.camera.position.x = this.camera.position.x * Math.cos(deltaX * 0.01) -
                                     this.camera.position.z * Math.sin(deltaX * 0.01);
            this.camera.position.z = this.camera.position.x * Math.sin(deltaX * 0.01) +
                                     this.camera.position.z * Math.cos(deltaX * 0.01);
            this.camera.position.y += deltaY * 0.01;

            this.camera.lookAt(0, 0, 0);

            this.mouseX = e.clientX;
            this.mouseY = e.clientY;
        });
    }

    animate() {
        requestAnimationFrame(this.animate.bind(this));

        // Animate point light
        const time = Date.now() * 0.001;
        this.pointLight.position.x = Math.sin(time) * 8;
        this.pointLight.position.z = Math.cos(time) * 8;

        // Animate material objects
        this.materialObjects.forEach((obj, index) => {
            const offset = index * Math.PI / 4;
            obj.position.y = obj.userData.originalPosition.y + Math.sin(time + offset) * 0.2;
            obj.rotation.y += 0.01;
            obj.rotation.x = Math.sin(time + offset) * 0.1;
        });

        this.camera.lookAt(0, 0, 0);
        this.renderer.render(this.scene, this.camera);
    }
}

// 2. Advanced Lighting Techniques
class LightingDemo {
    constructor(containerId) {
        this.container = document.getElementById(containerId);
        this.init();
    }

    init() {
        this.scene = new THREE.Scene();
        this.scene.background = new THREE.Color(0x000000);

        this.camera = new THREE.PerspectiveCamera(
            75,
            window.innerWidth / window.innerHeight,
            0.1,
            1000
        );
        this.camera.position.set(0, 8, 15);

        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.renderer.shadowMap.enabled = true;
        this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
        this.container.appendChild(this.renderer.domElement);

        this.setupScene();
        this.setupLightingTypes();
        this.animate();
    }

    setupScene() {
        // Create demo objects
        const sphereGeometry = new THREE.SphereGeometry(1, 64, 64);
        const boxGeometry = new THREE.BoxGeometry(1.5, 1.5, 1.5);
        const torusGeometry = new THREE.TorusGeometry(1, 0.3, 16, 100);

        const material = new THREE.MeshStandardMaterial({
            color: 0xffffff,
            metalness: 0.7,
            roughness: 0.3
        });

        this.demoObjects = [
            new THREE.Mesh(sphereGeometry, material.clone()),
            new THREE.Mesh(boxGeometry, material.clone()),
            new THREE.Mesh(torusGeometry, material.clone())
        ];

        this.demoObjects.forEach((obj, index) => {
            const angle = (index / this.demoObjects.length) * Math.PI * 2;
            obj.position.set(
                Math.cos(angle) * 4,
                0,
                Math.sin(angle) * 4
            );
            obj.castShadow = true;
            obj.receiveShadow = true;
            this.scene.add(obj);
        });

        // Create ground
        const groundGeometry = new THREE.PlaneGeometry(50, 50);
        const groundMaterial = new THREE.MeshStandardMaterial({
            color: 0x222222,
            roughness: 0.8
        });
        this.ground = new THREE.Mesh(groundGeometry, groundMaterial);
        this.ground.rotation.x = -Math.PI / 2;
        this.ground.receiveShadow = true;
        this.scene.add(this.ground);

        // Create light indicators (small spheres)
        this.lightIndicators = [];
    }

    setupLightingTypes() {
        // Ambient light
        this.ambientLight = new THREE.AmbientLight(0x404040, 0.4);
        this.scene.add(this.ambientLight);
        this.addLightIndicator(this.ambientLight, 0x404040);

        // Directional light with shadow
        this.directionalLight = new THREE.DirectionalLight(0xffffff, 1);
        this.directionalLight.position.set(10, 10, 5);
        this.directionalLight.castShadow = true;
        this.directionalLight.shadow.camera.left = -10;
        this.directionalLight.shadow.camera.right = 10;
        this.directionalLight.shadow.camera.top = 10;
        this.directionalLight.shadow.camera.bottom = -10;
        this.scene.add(this.directionalLight);
        this.addLightIndicator(this.directionalLight, 0xffffff);

        // Point light
        this.pointLight1 = new THREE.PointLight(0xff0000, 1, 20);
        this.pointLight1.position.set(-5, 5, 0);
        this.pointLight1.castShadow = true;
        this.scene.add(this.pointLight1);
        this.addLightIndicator(this.pointLight1, 0xff0000);

        this.pointLight2 = new THREE.PointLight(0x0000ff, 1, 20);
        this.pointLight2.position.set(5, 5, 0);
        this.pointLight2.castShadow = true;
        this.scene.add(this.pointLight2);
        this.addLightIndicator(this.pointLight2, 0x0000ff);

        // Spot light
        this.spotLight = new THREE.SpotLight(0x00ff00, 1);
        this.spotLight.position.set(0, 8, 0);
        this.spotLight.angle = Math.PI / 6;
        this.spotLight.penumbra = 0.3;
        this.spotLight.decay = 2;
        this.spotLight.distance = 30;
        this.spotLight.castShadow = true;
        this.scene.add(this.spotLight);

        // Add spot light helper
        const spotLightHelper = new THREE.SpotLightHelper(this.spotLight);
        this.scene.add(spotLightHelper);

        // RectAreaLight (if supported)
        if (THREE.RectAreaLight) {
            this.areaLight = new THREE.RectAreaLight(0xffffff, 2, 10, 10);
            this.areaLight.position.set(0, 10, 5);
            this.areaLight.lookAt(0, 0, 0);
            this.scene.add(this.areaLight);
        }

        // Hemisphere light
        this.hemisphereLight = new THREE.HemisphereLight(0x87ceeb, 0x8b4513, 0.5);
        this.scene.add(this.hemisphereLight);
    }

    addLightIndicator(light, color) {
        const geometry = new THREE.SphereGeometry(0.2, 16, 16);
        const material = new THREE.MeshBasicMaterial({ color: color });
        const indicator = new THREE.Mesh(geometry, material);

        if (light.position) {
            indicator.position.copy(light.position);
        }

        this.lightIndicators.push({ indicator, light });
        this.scene.add(indicator);
    }

    animate() {
        requestAnimationFrame(this.animate.bind(this));

        const time = Date.now() * 0.001;

        // Animate demo objects
        this.demoObjects.forEach((obj, index) => {
            obj.rotation.x += 0.01;
            obj.rotation.y += 0.02;
            obj.position.y = Math.sin(time + index) * 0.5;
        });

        // Animate point lights
        this.pointLight1.position.x = Math.sin(time) * 7;
        this.pointLight1.position.z = Math.cos(time) * 7;

        this.pointLight2.position.x = Math.sin(time + Math.PI) * 7;
        this.pointLight2.position.z = Math.cos(time + Math.PI) * 7;

        // Animate spot light
        this.spotLight.position.x = Math.sin(time * 0.5) * 5;
        this.spotLight.position.z = Math.cos(time * 0.5) * 5;
        this.spotLight.target.position.x = Math.sin(time * 0.3) * 3;
        this.spotLight.target.position.z = Math.cos(time * 0.3) * 3;

        // Update light indicators
        this.lightIndicators.forEach(({ indicator, light }) => {
            if (light.position) {
                indicator.position.copy(light.position);
            }
        });

        // Animate camera
        this.camera.position.x = Math.sin(time * 0.1) * 15;
        this.camera.position.z = Math.cos(time * 0.1) * 15;
        this.camera.lookAt(0, 0, 0);

        this.renderer.render(this.scene, this.camera);
    }
}

// 3. Texture and Material Mapping
class TextureMaterials {
    constructor(containerId) {
        this.container = document.getElementById(containerId);
        this.init();
    }

    init() {
        this.scene = new THREE.Scene();
        this.scene.background = new THREE.Color(0x222222);

        this.camera = new THREE.PerspectiveCamera(
            75,
            window.innerWidth / window.innerHeight,
            0.1,
            1000
        );
        this.camera.position.set(0, 2, 8);

        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.container.appendChild(this.renderer.domElement);

        this.setupLighting();
        this.createTexturedMaterials();
        this.animate();
    }

    setupLighting() {
        const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
        this.scene.add(ambientLight);

        const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
        directionalLight.position.set(5, 10, 5);
        this.scene.add(directionalLight);
    }

    createTexturedMaterials() {
        // Create procedural textures
        const textures = this.createProceduralTextures();

        const geometry = new THREE.BoxGeometry(1.5, 1.5, 1.5);

        // Different material types with textures
        const materials = [
            // Basic material with color
            new THREE.MeshBasicMaterial({
                map: textures.checkerboard,
                name: 'Basic + Texture'
            }),

            // Lambert material
            new THREE.MeshLambertMaterial({
                map: textures.noise,
                name: 'Lambert + Texture'
            }),

            // Phong material with specular map
            new THREE.MeshPhongMaterial({
                map: textures.wood,
                specularMap: textures.checkerboard,
                shininess: 100,
                name: 'Phong + Maps'
            }),

            // Standard material with PBR textures
            new THREE.MeshStandardMaterial({
                map: textures.metal,
                metalnessMap: textures.checkerboard,
                roughnessMap: textures.noise,
                normalMap: textures.normal,
                name: 'PBR + Maps'
            }),

            // Material with emissive map
            new THREE.MeshStandardMaterial({
                map: textures.noise,
                emissiveMap: textures.checkerboard,
                emissive: new THREE.Color(0x444444),
                name: 'Emissive Map'
            }),

            // Material with alpha map
            new THREE.MeshStandardMaterial({
                map: textures.checkerboard,
                alphaMap: textures.alpha,
                transparent: true,
                name: 'Alpha Map'
            })
        ];

        materials.forEach((material, index) => {
            const mesh = new THREE.Mesh(geometry.clone(), material);
            const angle = (index / materials.length) * Math.PI * 2;

            mesh.position.set(
                Math.cos(angle) * 4,
                0,
                Math.sin(angle) * 4
            );

            this.scene.add(mesh);

            // Add label
            this.addLabel(mesh.position, material.name);
        });
    }

    createProceduralTextures() {
        const textures = {};

        // Checkerboard texture
        const checkerSize = 64;
        const checkerCanvas = document.createElement('canvas');
        checkerCanvas.width = checkerSize;
        checkerCanvas.height = checkerSize;
        const checkerContext = checkerCanvas.getContext('2d');

        const tileSize = checkerSize / 8;
        for (let i = 0; i < 8; i++) {
            for (let j = 0; j < 8; j++) {
                checkerContext.fillStyle = (i + j) % 2 === 0 ? '#ffffff' : '#000000';
                checkerContext.fillRect(i * tileSize, j * tileSize, tileSize, tileSize);
            }
        }
        textures.checkerboard = new THREE.CanvasTexture(checkerCanvas);

        // Noise texture
        const noiseSize = 256;
        const noiseCanvas = document.createElement('canvas');
        noiseCanvas.width = noiseSize;
        noiseCanvas.height = noiseSize;
        const noiseContext = noiseCanvas.getContext('2d');

        for (let i = 0; i < noiseSize; i++) {
            for (let j = 0; j < noiseSize; j++) {
                const value = Math.random() * 255;
                noiseContext.fillStyle = `rgb(${value}, ${value}, ${value})`;
                noiseContext.fillRect(i, j, 1, 1);
            }
        }
        textures.noise = new THREE.CanvasTexture(noiseCanvas);

        // Wood texture
        const woodCanvas = document.createElement('canvas');
        woodCanvas.width = 256;
        woodCanvas.height = 256;
        const woodContext = woodCanvas.getContext('2d');

        for (let y = 0; y < 256; y++) {
            const darkness = 50 + Math.sin(y * 0.05) * 50;
            woodContext.fillStyle = `rgb(${darkness}, ${darkness * 0.5}, 0)`;
            woodContext.fillRect(0, y, 256, 1);
        }
        textures.wood = new THREE.CanvasTexture(woodCanvas);

        // Metal texture
        const metalCanvas = document.createElement('canvas');
        metalCanvas.width = 256;
        metalCanvas.height = 256;
        const metalContext = metalCanvas.getContext('2d');

        for (let y = 0; y < 256; y++) {
            const brightness = 180 + Math.sin(y * 0.1) * 20 + Math.random() * 10;
            metalContext.fillStyle = `rgb(${brightness}, ${brightness}, ${brightness})`;
            metalContext.fillRect(0, y, 256, 1);
        }
        textures.metal = new THREE.CanvasTexture(metalCanvas);

        // Normal map (simplified)
        const normalCanvas = document.createElement('canvas');
        normalCanvas.width = 256;
        normalCanvas.height = 256;
        const normalContext = normalCanvas.getContext('2d');

        for (let i = 0; i < 256; i++) {
            for (let j = 0; j < 256; j++) {
                const x = (Math.sin(i * 0.05) * 127 + 128);
                const y = (Math.cos(j * 0.05) * 127 + 128);
                const z = 255;
                normalContext.fillStyle = `rgb(${x}, ${y}, ${z})`;
                normalContext.fillRect(i, j, 1, 1);
            }
        }
        textures.normal = new THREE.CanvasTexture(normalCanvas);

        // Alpha map
        const alphaCanvas = document.createElement('canvas');
        alphaCanvas.width = 256;
        alphaCanvas.height = 256;
        const alphaContext = alphaCanvas.getContext('2d');

        for (let i = 0; i < 256; i++) {
            for (let j = 0; j < 256; j++) {
                const value = Math.random() > 0.5 ? 255 : 0;
                alphaContext.fillStyle = `rgb(${value}, ${value}, ${value})`;
                alphaContext.fillRect(i, j, 1, 1);
            }
        }
        textures.alpha = new THREE.CanvasTexture(alphaCanvas);

        return textures;
    }

    addLabel(position, text) {
        const canvas = document.createElement('canvas');
        const context = canvas.getContext('2d');
        canvas.width = 256;
        canvas.height = 64;

        context.fillStyle = '#ffffff';
        context.font = '16px Arial';
        context.textAlign = 'center';
        context.fillText(text, 128, 32);

        const texture = new THREE.CanvasTexture(canvas);
        const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
        const sprite = new THREE.Sprite(spriteMaterial);

        sprite.position.copy(position);
        sprite.position.y -= 2;
        sprite.scale.set(3, 0.8, 1);

        this.scene.add(sprite);
    }

    animate() {
        requestAnimationFrame(this.animate.bind(this));

        // Rotate all objects
        this.scene.children.forEach(child => {
            if (child.isMesh) {
                child.rotation.y += 0.01;
            }
        });

        this.camera.lookAt(0, 0, 0);
        this.renderer.render(this.scene, this.camera);
    }
}

export {
    MaterialShowcase,
    LightingDemo,
    TextureMaterials
};

💻 Animations and Controls javascript

🔴 complex ⭐⭐⭐⭐

3D animations, camera controls, user interactions, and animation systems in Three.js

⏱️ 45 min 🏷️ threejs, animation, controls, interaction
Prerequisites: Three.js basics, JavaScript ES6+, Animation principles
// Three.js Animations and Controls Examples

import * as THREE from 'three';

// 1. Advanced Animation System
class AnimationSystem {
    constructor(containerId) {
        this.container = document.getElementById(containerId);
        this.animations = [];
        this.mixer = null;
        this.clock = new THREE.Clock();
        this.init();
    }

    init() {
        this.scene = new THREE.Scene();
        this.scene.background = new THREE.Color(0x0a0a0a);

        this.camera = new THREE.PerspectiveCamera(
            75,
            window.innerWidth / window.innerHeight,
            0.1,
            1000
        );
        this.camera.position.set(0, 5, 10);

        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.renderer.shadowMap.enabled = true;
        this.container.appendChild(this.renderer.domElement);

        this.setupLighting();
        this.createAnimatedObjects();
        this.setupControls();
        this.createAnimationUI();
        this.animate();
    }

    setupLighting() {
        const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
        this.scene.add(ambientLight);

        const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
        directionalLight.position.set(10, 10, 5);
        directionalLight.castShadow = true;
        this.scene.add(directionalLight);

        const pointLight = new THREE.PointLight(0xff6b6b, 1, 20);
        pointLight.position.set(-5, 5, -5);
        this.scene.add(pointLight);
    }

    createAnimatedObjects() {
        this.animatableObjects = [];

        // Create multiple animated objects
        this.createPulsatingSphere();
        this.createRotatingCube();
        this.createBouncingBall();
        this.createWavingPlane();
        this.createOrbitalSystem();
        this.createCharacterAnimation();
        this.createParticleSystem();
        this.createMorphingShapes();
    }

    createPulsatingSphere() {
        const geometry = new THREE.SphereGeometry(1, 32, 32);
        const material = new THREE.MeshPhongMaterial({
            color: 0xff0000,
            emissive: 0x440000,
            shininess: 100
        });

        const sphere = new THREE.Mesh(geometry, material);
        sphere.position.set(-6, 2, 0);
        sphere.castShadow = true;

        // Add pulsating animation
        const animation = {
            object: sphere,
            type: 'pulsate',
            baseScale: 1,
            amplitude: 0.3,
            frequency: 2,
            phase: 0
        };

        this.animatableObjects.push(sphere);
        this.animations.push(animation);
        this.scene.add(sphere);
    }

    createRotatingCube() {
        const geometry = new THREE.BoxGeometry(2, 2, 2);
        const material = new THREE.MeshPhongMaterial({
            color: 0x00ff00,
            specular: 0x444444,
            shininess: 100
        });

        const cube = new THREE.Mesh(geometry, material);
        cube.position.set(-2, 2, 0);
        cube.castShadow = true;

        // Add complex rotation animation
        const animation = {
            object: cube,
            type: 'complex-rotation',
            rotationSpeed: { x: 0.01, y: 0.02, z: 0.015 },
            oscillation: { axis: 'x', amplitude: 0.5, frequency: 1 }
        };

        this.animatableObjects.push(cube);
        this.animations.push(animation);
        this.scene.add(cube);
    }

    createBouncingBall() {
        const geometry = new THREE.SphereGeometry(0.8, 32, 32);
        const material = new THREE.MeshPhongMaterial({
            color: 0x0000ff,
            emissive: 0x000044,
            shininess: 100
        });

        const ball = new THREE.Mesh(geometry, material);
        ball.position.set(2, 3, 0);
        ball.castShadow = true;

        // Add bouncing animation
        const animation = {
            object: ball,
            type: 'bounce',
            baseY: 3,
            amplitude: 2,
            frequency: 2,
            gravity: 0.01,
            squash: 0.8
        };

        this.animatableObjects.push(ball);
        this.animations.push(animation);
        this.scene.add(ball);
    }

    createWavingPlane() {
        const geometry = new THREE.PlaneGeometry(4, 4, 32, 32);
        const material = new THREE.MeshPhongMaterial({
            color: 0xffff00,
            side: THREE.DoubleSide,
            wireframe: false
        });

        const plane = new THREE.Mesh(geometry, material);
        plane.position.set(6, 0, 0);
        plane.rotation.x = -Math.PI / 2;
        plane.castShadow = true;

        // Add waving animation
        const animation = {
            object: plane,
            type: 'wave',
            amplitude: 0.5,
            frequency: 2,
            waveSpeed: 0.02,
            geometry: geometry
        };

        this.animatableObjects.push(plane);
        this.animations.push(animation);
        this.scene.add(plane);
    }

    createOrbitalSystem() {
        // Central sun
        const sunGeometry = new THREE.SphereGeometry(1.5, 32, 32);
        const sunMaterial = new THREE.MeshBasicMaterial({
            color: 0xffa500,
            emissive: 0xffa500
        });

        const sun = new THREE.Mesh(sunGeometry, sunMaterial);
        sun.position.set(0, -2, 0);

        // Planets
        const planets = [];
        const planetData = [
            { radius: 0.3, distance: 3, speed: 1, color: 0x8b4513 }, // Mercury
            { radius: 0.5, distance: 4, speed: 0.8, color: 0xffa500 }, // Venus
            { radius: 0.5, distance: 5.5, speed: 0.6, color: 0x0000ff }, // Earth
            { radius: 0.4, distance: 7, speed: 0.5, color: 0xff0000 }  // Mars
        ];

        planetData.forEach((data, index) => {
            const planetGeometry = new THREE.SphereGeometry(data.radius, 16, 16);
            const planetMaterial = new THREE.MeshPhongMaterial({
                color: data.color,
                shininess: 50
            });

            const planet = new THREE.Mesh(planetGeometry, planetMaterial);
            planets.push({ mesh: planet, ...data });

            // Create orbit line
            const orbitGeometry = new THREE.RingGeometry(data.distance - 0.1, data.distance + 0.1, 64);
            const orbitMaterial = new THREE.MeshBasicMaterial({
                color: 0x444444,
                side: THREE.DoubleSide,
                transparent: true,
                opacity: 0.3
            });
            const orbit = new THREE.Mesh(orbitGeometry, orbitMaterial);
            orbit.rotation.x = -Math.PI / 2;
            orbit.position.set(0, -2, 0);
            this.scene.add(orbit);
        });

        // Add orbital animation
        const animation = {
            type: 'orbital',
            sun: sun,
            planets: planets,
            center: new THREE.Vector3(0, -2, 0)
        };

        this.animations.push(animation);
        this.scene.add(sun);
        planets.forEach(planet => this.scene.add(planet.mesh));
    }

    createCharacterAnimation() {
        // Simple character made of multiple parts
        const character = new THREE.Group();

        // Body
        const bodyGeometry = new THREE.BoxGeometry(1, 2, 0.5);
        const bodyMaterial = new THREE.MeshPhongMaterial({ color: 0x888888 });
        const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
        character.add(body);

        // Head
        const headGeometry = new THREE.SphereGeometry(0.5, 16, 16);
        const headMaterial = new THREE.MeshPhongMaterial({ color: 0xffdbac });
        const head = new THREE.Mesh(headGeometry, headMaterial);
        head.position.y = 1.5;
        character.add(head);

        // Arms
        const armGeometry = new THREE.BoxGeometry(0.2, 1.5, 0.2);
        const armMaterial = new THREE.MeshPhongMaterial({ color: 0x888888 });

        const leftArm = new THREE.Mesh(armGeometry, armMaterial);
        leftArm.position.set(-0.7, 0.5, 0);
        character.add(leftArm);

        const rightArm = new THREE.Mesh(armGeometry, armMaterial);
        rightArm.position.set(0.7, 0.5, 0);
        character.add(rightArm);

        // Legs
        const legGeometry = new THREE.BoxGeometry(0.3, 1.5, 0.3);
        const legMaterial = new THREE.MeshPhongMaterial({ color: 0x4444ff });

        const leftLeg = new THREE.Mesh(legGeometry, legMaterial);
        leftLeg.position.set(-0.3, -1.5, 0);
        character.add(leftLeg);

        const rightLeg = new THREE.Mesh(legGeometry, legMaterial);
        rightLeg.position.set(0.3, -1.5, 0);
        character.add(rightLeg);

        character.position.set(0, 0, 4);

        // Add walking animation
        const animation = {
            type: 'character-walk',
            character: character,
            parts: { head, leftArm, rightArm, leftLeg, rightLeg },
            walkSpeed: 2,
            bobAmount: 0.1,
            armSwingAmount: 0.5,
            legSwingAmount: 0.5
        };

        this.animations.push(animation);
        this.scene.add(character);
    }

    createParticleSystem() {
        const particleCount = 1000;
        const geometry = new THREE.BufferGeometry();

        const positions = new Float32Array(particleCount * 3);
        const colors = new Float32Array(particleCount * 3);
        const velocities = new Float32Array(particleCount * 3);

        for (let i = 0; i < particleCount; i++) {
            const i3 = i * 3;

            // Initial positions (fountain effect)
            positions[i3] = 0;
            positions[i3 + 1] = -2;
            positions[i3 + 2] = 0;

            // Random colors
            const color = new THREE.Color();
            color.setHSL(Math.random(), 0.7, 0.5);
            colors[i3] = color.r;
            colors[i3 + 1] = color.g;
            colors[i3 + 2] = color.b;

            // Initial velocities
            velocities[i3] = (Math.random() - 0.5) * 2;
            velocities[i3 + 1] = Math.random() * 5 + 2;
            velocities[i3 + 2] = (Math.random() - 0.5) * 2;
        }

        geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
        geometry.setAttribute('velocity', new THREE.BufferAttribute(velocities, 3));

        const material = new THREE.PointsMaterial({
            size: 0.1,
            vertexColors: true,
            blending: THREE.AdditiveBlending,
            transparent: true,
            opacity: 0.8
        });

        const particles = new THREE.Points(geometry, material);
        particles.position.set(0, -2, 0);

        const animation = {
            type: 'particle-fountain',
            particles: particles,
            geometry: geometry,
            gravity: -0.02,
            life: 3,
            spawnRate: 50
        };

        this.animations.push(animation);
        this.scene.add(particles);
    }

    createMorphingShapes() {
        // Create geometry that can morph between different shapes
        const baseGeometry = new THREE.IcosahedronGeometry(1, 2);
        const material = new THREE.MeshPhongMaterial({
            color: 0xff00ff,
            wireframe: false,
            flatShading: true
        });

        const morphMesh = new THREE.Mesh(baseGeometry, material);
        morphMesh.position.set(-3, -2, 0);

        // Create different shape targets
        const sphereGeometry = new THREE.SphereGeometry(1, 16, 16);
        const boxGeometry = new THREE.BoxGeometry(1.5, 1.5, 1.5);
        const torusGeometry = new THREE.TorusGeometry(1, 0.3, 16, 100);

        const animation = {
            type: 'morph-shapes',
            mesh: morphMesh,
            shapes: [baseGeometry, sphereGeometry, boxGeometry, torusGeometry],
            currentIndex: 0,
            targetIndex: 1,
            morphSpeed: 0.02,
            morphProgress: 0
        };

        this.animations.push(animation);
        this.scene.add(morphMesh);
    }

    setupControls() {
        this.mouse = new THREE.Vector2();
        this.raycaster = new THREE.Raycaster();

        // Mouse controls for camera
        this.renderer.domElement.addEventListener('mousemove', (event) => {
            this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
            this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

            // Update camera position based on mouse
            this.camera.position.x = this.mouse.x * 5;
            this.camera.position.y = 5 + this.mouse.y * 2;
            this.camera.lookAt(0, 0, 0);
        });

        // Click to trigger animations
        this.renderer.domElement.addEventListener('click', () => {
            this.triggerRandomAnimation();
        });
    }

    createAnimationUI() {
        // Create simple UI overlay
        const ui = document.createElement('div');
        ui.style.position = 'absolute';
        ui.style.top = '10px';
        ui.style.left = '10px';
        ui.style.color = 'white';
        ui.style.fontFamily = 'Arial, sans-serif';
        ui.style.fontSize = '14px';
        ui.style.backgroundColor = 'rgba(0,0,0,0.7)';
        ui.style.padding = '10px';
        ui.style.borderRadius = '5px';

        ui.innerHTML = `
            <h3>Animation Controls</h3>
            <p>Move mouse to control camera</p>
            <p>Click to trigger effects</p>
            <div id="animation-info">
                <p>Active animations: 0</p>
            </div>
        `;

        this.container.appendChild(ui);
        this.uiInfo = document.getElementById('animation-info');
    }

    triggerRandomAnimation() {
        // Create explosion effect at random position
        const position = new THREE.Vector3(
            (Math.random() - 0.5) * 10,
            Math.random() * 5,
            (Math.random() - 0.5) * 10
        );

        this.createExplosion(position);
    }

    createExplosion(position) {
        const particleCount = 100;
        const geometry = new THREE.BufferGeometry();

        const positions = new Float32Array(particleCount * 3);
        const colors = new Float32Array(particleCount * 3);
        const velocities = new Float32Array(particleCount * 3);

        for (let i = 0; i < particleCount; i++) {
            const i3 = i * 3;

            positions[i3] = position.x;
            positions[i3 + 1] = position.y;
            positions[i3 + 2] = position.z;

            const color = new THREE.Color();
            color.setHSL(Math.random() * 0.1, 1, 0.5); // Red-yellow explosion
            colors[i3] = color.r;
            colors[i3 + 1] = color.g;
            colors[i3 + 2] = color.b;

            const speed = Math.random() * 5 + 2;
            const theta = Math.random() * Math.PI * 2;
            const phi = Math.random() * Math.PI;

            velocities[i3] = speed * Math.sin(phi) * Math.cos(theta);
            velocities[i3 + 1] = speed * Math.cos(phi);
            velocities[i3 + 2] = speed * Math.sin(phi) * Math.sin(theta);
        }

        geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
        geometry.setAttribute('velocity', new THREE.BufferAttribute(velocities, 3));

        const material = new THREE.PointsMaterial({
            size: 0.2,
            vertexColors: true,
            blending: THREE.AdditiveBlending,
            transparent: true,
            opacity: 1
        });

        const particles = new THREE.Points(geometry, material);

        const animation = {
            type: 'explosion',
            particles: particles,
            geometry: geometry,
            life: 2,
            maxLife: 2
        };

        this.animations.push(animation);
        this.scene.add(particles);
    }

    updateAnimations(deltaTime) {
        this.animations.forEach((animation, index) => {
            const time = this.clock.getElapsedTime();

            switch (animation.type) {
                case 'pulsate':
                    const scale = animation.baseScale +
                                Math.sin(time * animation.frequency * Math.PI * 2 + animation.phase) *
                                animation.amplitude;
                    animation.object.scale.setScalar(scale);
                    break;

                case 'complex-rotation':
                    animation.object.rotation.x += animation.rotationSpeed.x;
                    animation.object.rotation.y += animation.rotationSpeed.y;
                    animation.object.rotation.z += animation.rotationSpeed.z;

                    if (animation.oscillation) {
                        const oscillationValue = Math.sin(time * animation.oscillation.frequency) *
                                              animation.oscillation.amplitude;
                        animation.object.position[animation.oscillation.axis] = oscillationValue;
                    }
                    break;

                case 'bounce':
                    const bounceHeight = Math.abs(Math.sin(time * animation.frequency)) *
                                       animation.amplitude;
                    animation.object.position.y = animation.baseY + bounceHeight;

                    // Squash and stretch
                    const squashFactor = 1 - (bounceHeight / animation.amplitude) *
                                        (1 - animation.squash);
                    animation.object.scale.y = squashFactor;
                    animation.object.scale.x = animation.object.scale.z = 1 / squashFactor;
                    break;

                case 'wave':
                    const positions = animation.geometry.attributes.position.array;
                    for (let i = 0; i < positions.length; i += 3) {
                        const x = positions[i];
                        const y = positions[i + 1];
                        positions[i + 2] = Math.sin(x * 0.5 + time * animation.waveSpeed * 10) *
                                          animation.amplitude;
                    }
                    animation.geometry.attributes.position.needsUpdate = true;
                    break;

                case 'orbital':
                    animation.planets.forEach((planet) => {
                        const angle = time * planet.speed;
                        planet.mesh.position.x = animation.center.x +
                                                Math.cos(angle) * planet.distance;
                        planet.mesh.position.z = animation.center.z +
                                                Math.sin(angle) * planet.distance;
                        planet.mesh.rotation.y += 0.02;
                    });
                    animation.sun.rotation.y += 0.005;
                    break;

                case 'character-walk':
                    const walkCycle = Math.sin(time * animation.walkSpeed);

                    // Body bob
                    animation.character.position.y = walkCycle * animation.bobAmount;

                    // Arm swing
                    animation.parts.leftArm.rotation.z = walkCycle * animation.armSwingAmount;
                    animation.parts.rightArm.rotation.z = -walkCycle * animation.armSwingAmount;

                    // Leg swing
                    animation.parts.leftLeg.rotation.x = -walkCycle * animation.legSwingAmount;
                    animation.parts.rightLeg.rotation.x = walkCycle * animation.legSwingAmount;

                    // Character movement
                    animation.character.position.x = Math.sin(time * 0.5) * 2;
                    animation.character.rotation.y = -Math.cos(time * 0.5) > 0 ? 0 : Math.PI;
                    break;

                case 'particle-fountain':
                    const particlePositions = animation.geometry.attributes.position.array;
                    const particleVelocities = animation.geometry.attributes.velocity.array;

                    for (let i = 0; i < particlePositions.length; i += 3) {
                        // Update positions
                        particlePositions[i] += particleVelocities[i] * deltaTime;
                        particlePositions[i + 1] += particleVelocities[i + 1] * deltaTime;
                        particlePositions[i + 2] += particleVelocities[i + 2] * deltaTime;

                        // Apply gravity
                        particleVelocities[i + 1] += animation.gravity;

                        // Reset particles that fall below ground
                        if (particlePositions[i + 1] < -2) {
                            particlePositions[i] = 0;
                            particlePositions[i + 1] = -2;
                            particlePositions[i + 2] = 0;

                            particleVelocities[i] = (Math.random() - 0.5) * 2;
                            particleVelocities[i + 1] = Math.random() * 5 + 2;
                            particleVelocities[i + 2] = (Math.random() - 0.5) * 2;
                        }
                    }

                    animation.geometry.attributes.position.needsUpdate = true;
                    break;

                case 'morph-shapes':
                    animation.morphProgress += animation.morphSpeed;

                    if (animation.morphProgress >= 1) {
                        animation.morphProgress = 0;
                        animation.currentIndex = animation.targetIndex;
                        animation.targetIndex = (animation.targetIndex + 1) % animation.shapes.length;
                    }

                    // Simple morphing by lerping vertices (simplified version)
                    animation.mesh.scale.x = 1 + Math.sin(animation.morphProgress * Math.PI) * 0.2;
                    animation.mesh.scale.y = 1 + Math.cos(animation.morphProgress * Math.PI) * 0.2;
                    break;

                case 'explosion':
                    animation.life -= deltaTime;
                    const explosionPositions = animation.geometry.attributes.position.array;
                    const explosionVelocities = animation.geometry.attributes.velocity.array;

                    for (let i = 0; i < explosionPositions.length; i += 3) {
                        explosionPositions[i] += explosionVelocities[i] * deltaTime;
                        explosionPositions[i + 1] += explosionVelocities[i + 1] * deltaTime;
                        explosionPositions[i + 2] += explosionVelocities[i + 2] * deltaTime;

                        // Apply drag
                        explosionVelocities[i] *= 0.98;
                        explosionVelocities[i + 1] *= 0.98;
                        explosionVelocities[i + 2] *= 0.98;
                    }

                    animation.geometry.attributes.position.needsUpdate = true;
                    animation.particles.material.opacity = animation.life / animation.maxLife;

                    if (animation.life <= 0) {
                        this.scene.remove(animation.particles);
                        this.animations.splice(index, 1);
                    }
                    break;
            }
        });

        // Update UI
        if (this.uiInfo) {
            this.uiInfo.innerHTML = `<p>Active animations: ${this.animations.length}</p>`;
        }
    }

    animate() {
        requestAnimationFrame(this.animate.bind(this));

        const deltaTime = this.clock.getDelta();
        this.updateAnimations(deltaTime);

        this.renderer.render(this.scene, this.camera);
    }
}

// 2. Camera Control System
class CameraControls {
    constructor(containerId) {
        this.container = document.getElementById(containerId);
        this.init();
    }

    init() {
        this.scene = new THREE.Scene();
        this.scene.background = new THREE.Color(0x87CEEB);

        this.camera = new THREE.PerspectiveCamera(
            75,
            window.innerWidth / window.innerHeight,
            0.1,
            1000
        );
        this.camera.position.set(0, 5, 15);

        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.container.appendChild(this.renderer.domElement);

        this.setupScene();
        this.setupCameraControls();
        this.createControlUI();
        this.animate();
    }

    setupScene() {
        // Add lights
        const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
        this.scene.add(ambientLight);

        const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
        directionalLight.position.set(10, 10, 5);
        this.scene.add(directionalLight);

        // Create reference objects
        this.createReferenceObjects();
    }

    createReferenceObjects() {
        // Create grid of objects
        const geometries = [
            new THREE.BoxGeometry(1, 1, 1),
            new THREE.SphereGeometry(0.6, 32, 32),
            new THREE.ConeGeometry(0.6, 1, 32),
            new THREE.TorusGeometry(0.6, 0.2, 16, 100)
        ];

        const colors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00];

        for (let i = 0; i < 4; i++) {
            for (let j = 0; j < 4; j++) {
                const geometry = geometries[j];
                const material = new THREE.MeshPhongMaterial({
                    color: colors[j],
                    shininess: 100
                });

                const mesh = new THREE.Mesh(geometry, material);
                mesh.position.set(
                    (i - 1.5) * 3,
                    0,
                    (j - 1.5) * 3
                );

                this.scene.add(mesh);
            }
        }

        // Add ground plane
        const planeGeometry = new THREE.PlaneGeometry(20, 20);
        const planeMaterial = new THREE.MeshStandardMaterial({
            color: 0x808080,
            roughness: 0.8
        });
        const plane = new THREE.Mesh(planeGeometry, planeMaterial);
        plane.rotation.x = -Math.PI / 2;
        plane.position.y = -2;
        this.scene.add(plane);

        // Add coordinate axes helper
        this.axesHelper = new THREE.AxesHelper(5);
        this.scene.add(this.axesHelper);
    }

    setupCameraControls() {
        this.controls = {
            mode: 'orbit',
            speed: 5,
            zoom: 1
        };

        this.keys = {};
        this.mouse = { x: 0, y: 0, down: false, startX: 0, startY: 0 };

        // Keyboard controls
        window.addEventListener('keydown', (e) => {
            this.keys[e.key.toLowerCase()] = true;

            // Switch control modes
            if (e.key === '1') this.controls.mode = 'orbit';
            if (e.key === '2') this.controls.mode = 'fly';
            if (e.key === '3') this.controls.mode = 'fps';
        });

        window.addEventListener('keyup', (e) => {
            this.keys[e.key.toLowerCase()] = false;
        });

        // Mouse controls
        this.renderer.domElement.addEventListener('mousedown', (e) => {
            this.mouse.down = true;
            this.mouse.startX = e.clientX;
            this.mouse.startY = e.clientY;
        });

        window.addEventListener('mouseup', () => {
            this.mouse.down = false;
        });

        window.addEventListener('mousemove', (e) => {
            if (this.mouse.down) {
                const deltaX = e.clientX - this.mouse.startX;
                const deltaY = e.clientY - this.mouse.startY;
                this.handleMouseMovement(deltaX, deltaY);
                this.mouse.startX = e.clientX;
                this.mouse.startY = e.clientY;
            }
        });

        // Wheel for zoom
        this.renderer.domElement.addEventListener('wheel', (e) => {
            e.preventDefault();
            this.controls.zoom *= e.deltaY > 0 ? 1.1 : 0.9;
            this.controls.zoom = Math.max(0.1, Math.min(10, this.controls.zoom));
        });
    }

    handleMouseMovement(deltaX, deltaY) {
        switch (this.controls.mode) {
            case 'orbit':
                // Orbit around origin
                const spherical = new THREE.Spherical();
                spherical.setFromVector3(this.camera.position);
                spherical.theta -= deltaX * 0.01;
                spherical.phi += deltaY * 0.01;
                spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, spherical.phi));
                spherical.radius *= this.controls.zoom;
                this.camera.position.setFromSpherical(spherical);
                this.camera.lookAt(0, 0, 0);
                break;

            case 'fly':
                // Look around
                this.camera.rotation.y -= deltaX * 0.005;
                this.camera.rotation.x -= deltaY * 0.005;
                this.camera.rotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, this.camera.rotation.x));
                break;

            case 'fps':
                // FPS-style mouse look
                this.camera.rotation.y -= deltaX * 0.002;
                this.camera.rotation.x -= deltaY * 0.002;
                this.camera.rotation.x = Math.max(-Math.PI/2, Math.min(Math.PI/2, this.camera.rotation.x));
                break;
        }
    }

    updateCameraFromKeyboard() {
        const speed = this.controls.speed * 0.1;
        const forward = new THREE.Vector3(0, 0, -1);
        const right = new THREE.Vector3(1, 0, 0);

        switch (this.controls.mode) {
            case 'fly':
            case 'fps':
                forward.applyQuaternion(this.camera.quaternion);
                right.applyQuaternion(this.camera.quaternion);

                if (this.keys['w']) {
                    this.camera.position.addScaledVector(forward, speed);
                }
                if (this.keys['s']) {
                    this.camera.position.addScaledVector(forward, -speed);
                }
                if (this.keys['a']) {
                    this.camera.position.addScaledVector(right, -speed);
                }
                if (this.keys['d']) {
                    this.camera.position.addScaledVector(right, speed);
                }
                if (this.keys[' ']) { // Space
                    this.camera.position.y += speed;
                }
                if (this.keys['shift']) {
                    this.camera.position.y -= speed;
                }
                break;

            case 'orbit':
                // Zoom in/out with keys
                if (this.keys['w'] || this.keys['+']) {
                    this.controls.zoom *= 0.95;
                }
                if (this.keys['s'] || this.keys['-']) {
                    this.controls.zoom *= 1.05;
                }
                this.controls.zoom = Math.max(0.1, Math.min(10, this.controls.zoom));

                // Update camera position
                const spherical = new THREE.Spherical();
                spherical.setFromVector3(this.camera.position);
                spherical.radius *= this.controls.zoom;
                this.camera.position.setFromSpherical(spherical);
                this.camera.lookAt(0, 0, 0);
                this.controls.zoom = 1; // Reset zoom after applying
                break;
        }
    }

    createControlUI() {
        const ui = document.createElement('div');
        ui.style.position = 'absolute';
        ui.style.top = '10px';
        ui.style.right = '10px';
        ui.style.color = 'white';
        ui.style.fontFamily = 'Arial, sans-serif';
        ui.style.fontSize = '14px';
        ui.style.backgroundColor = 'rgba(0,0,0,0.7)';
        ui.style.padding = '10px';
        ui.style.borderRadius = '5px';

        ui.innerHTML = `
            <h3>Camera Controls</h3>
            <p><strong>1:</strong> Orbit Mode</p>
            <p><strong>2:</strong> Fly Mode</p>
            <p><strong>3:</strong> FPS Mode</p>
            <p><strong>W/A/S/D:</strong> Move</p>
            <p><strong>Space/Shift:</strong> Up/Down</p>
            <p><strong>Mouse:</strong> Look</p>
            <p><strong>Scroll:</strong> Zoom</p>
            <div id="mode-display" style="margin-top: 10px; font-weight: bold;">
                Current Mode: Orbit
            </div>
        `;

        this.container.appendChild(ui);
        this.modeDisplay = document.getElementById('mode-display');

        // Update mode display
        setInterval(() => {
            const modeNames = { orbit: 'Orbit', fly: 'Fly', fps: 'FPS' };
            this.modeDisplay.textContent = `Current Mode: ${modeNames[this.controls.mode]}`;
        }, 100);
    }

    animate() {
        requestAnimationFrame(this.animate.bind(this));

        this.updateCameraFromKeyboard();

        // Rotate reference objects
        this.scene.children.forEach(child => {
            if (child.isMesh && child.position.y === 0) {
                child.rotation.x += 0.01;
                child.rotation.y += 0.02;
            }
        });

        this.renderer.render(this.scene, this.camera);
    }
}

export {
    AnimationSystem,
    CameraControls
};