🎯 Рекомендуемые коллекции

Балансированные коллекции примеров кода из различных категорий, которые вы можете исследовать

Примеры Three.js

Примеры 3D-графической библиотеки Three.js, включая сцены, меши, освещение, материалы, анимации и интерактивный 3D-контент

💻 Three.js Hello World javascript

🟢 simple

Базовая настройка Three.js со сценами, камерами, рендерерами и простыми 3D-объектами

⏱️ 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
};

💻 Материалы и освещение javascript

🟡 intermediate ⭐⭐⭐

Продвинутые материалы Three.js, настройки освещения, тени и реалистичный рендеринг поверхностей

⏱️ 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
};

💻 Анимации и элементы управления javascript

🔴 complex ⭐⭐⭐⭐

3D-анимации, управление камерой, взаимодействие с пользователем и системы анимации в 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.create MorphingShapes();
    }

    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
};