Exemples Babylon.js

Exemples du moteur 3D Babylon.js incluant scènes, meshes, matériaux, éclairage, physique et applications web 3D interactives

💻 Babylon.js Hello World typescript

🟢 simple

Configuration de base de scène Babylon.js avec moteur, scène, caméra, lumières et objets 3D simples

⏱️ 15 min 🏷️ babylonjs, 3d, webgl, engine
Prerequisites: TypeScript basics, WebGL concepts
// Babylon.js Hello World Examples

import * as BABYLON from '@babylonjs/core';

// 1. Basic Babylon.js Scene Setup
class BasicBabylonScene {
    private engine: BABYLON.Engine;
    private scene: BABYLON.Scene;
    private camera: BABYLON.UniversalCamera;

    constructor(canvas: HTMLCanvasElement) {
        // Create engine
        this.engine = new BABYLON.Engine(canvas, true);

        // Create scene
        this.scene = new BABYLON.Scene(this.engine);
        this.scene.clearColor = new BABYLON.Color4(0.2, 0.2, 0.2, 1);

        // Setup camera
        this.setupCamera();

        // Add lights
        this.addLights();

        // Create 3D objects
        this.createObjects();

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

        // Start render loop
        this.startRenderLoop();
    }

    private setupCamera(): void {
        // Create universal camera
        this.camera = new BABYLON.UniversalCamera(
            'camera',
            new BABYLON.Vector3(0, 5, -10),
            this.scene
        );

        this.camera.setTarget(BABYLON.Vector3.Zero());
        this.camera.attachControl(this.engine.getRenderingCanvas(), true);

        // Set camera controls
        this.camera.keysUp = [87, 38];    // W, Up arrow
        this.camera.keysDown = [83, 40];  // S, Down arrow
        this.camera.keysLeft = [65, 37];  // A, Left arrow
        this.camera.keysRight = [68, 39]; // D, Right arrow
    }

    private addLights(): void {
        // Ambient light for overall illumination
        const ambientLight = new BABYLON.HemisphericLight(
            'ambientLight',
            new BABYLON.Vector3(0, 1, 0),
            this.scene
        );
        ambientLight.intensity = 0.7;

        // Directional light for shadows
        const directionalLight = new BABYLON.DirectionalLight(
            'directionalLight',
            new BABYLON.Vector3(1, -1, 1),
            this.scene
        );
        directionalLight.intensity = 0.8;
        directionalLight.position = new BABYLON.Vector3(10, 10, 10);
    }

    private createObjects(): void {
        // Create ground
        const ground = BABYLON.MeshBuilder.CreateGround(
            'ground',
            { width: 20, height: 20 },
            this.scene
        );
        const groundMaterial = new BABYLON.StandardMaterial('groundMat', this.scene);
        groundMaterial.diffuseColor = new BABYLON.Color3(0.5, 0.5, 0.5);
        ground.material = groundMaterial;

        // Create rotating cube
        const cube = BABYLON.MeshBuilder.CreateBox('cube', { size: 2 }, this.scene);
        cube.position.y = 1;
        const cubeMaterial = new BABYLON.StandardMaterial('cubeMat', this.scene);
        cubeMaterial.diffuseColor = new BABYLON.Color3(1, 0, 0); // Red
        cube.material = cubeMaterial;

        // Create sphere
        const sphere = BABYLON.MeshBuilder.CreateSphere(
            'sphere',
            { diameter: 2, segments: 32 },
            this.scene
        );
        sphere.position.set(-3, 1, 0);
        const sphereMaterial = new BABYLON.StandardMaterial('sphereMat', this.scene);
        sphereMaterial.diffuseColor = new BABYLON.Color3(0, 1, 0); // Green
        sphere.material = sphereMaterial;

        // Create cylinder
        const cylinder = BABYLON.MeshBuilder.CreateCylinder(
            'cylinder',
            { height: 3, diameter: 1.5 },
            this.scene
        );
        cylinder.position.set(3, 1.5, 0);
        const cylinderMaterial = new BABYLON.StandardMaterial('cylinderMat', this.scene);
        cylinderMaterial.diffuseColor = new BABYLON.Color3(0, 0, 1); // Blue
        cylinder.material = cylinderMaterial;

        // Create torus
        const torus = BABYLON.MeshBuilder.CreateTorus(
            'torus',
            { diameter: 2, thickness: 0.5, tessellation: 32 },
            this.scene
        );
        torus.position.set(0, 1, 3);
        const torusMaterial = new BABYLON.StandardMaterial('torusMat', this.scene);
        torusMaterial.diffuseColor = new BABYLON.Color3(1, 1, 0); // Yellow
        torus.material = torusMaterial;

        // Add rotation animations
        this.scene.registerBeforeRender(() => {
            cube.rotation.x += 0.01;
            cube.rotation.y += 0.01;
            sphere.rotation.y += 0.02;
            cylinder.rotation.y += 0.015;
            torus.rotation.x += 0.01;
            torus.rotation.z += 0.01;
        });
    }

    private handleResize(): void {
        this.engine.resize();
    }

    private startRenderLoop(): void {
        this.engine.runRenderLoop(() => {
            this.scene.render();
        });
    }
}

// 2. Interactive Scene with GUI
class InteractiveScene {
    private engine: BABYLON.Engine;
    private scene: BABYLON.Scene;
    private camera: BABYLON.ArcRotateCamera;
    private meshes: BABYLON.Mesh[] = [];

    constructor(canvas: HTMLCanvasElement) {
        this.engine = new BABYLON.Engine(canvas, true);
        this.scene = new BABYLON.Scene(this.engine);
        this.scene.clearColor = new BABYLON.Color4(0.1, 0.1, 0.2, 1);

        this.setupCamera();
        this.addLights();
        this.createInteractiveObjects();
        this.setupGUI();
        this.setupInteraction();
        this.startRenderLoop();
    }

    private setupCamera(): void {
        this.camera = new BABYLON.ArcRotateCamera(
            'camera',
            0,
            Math.PI / 3,
            15,
            BABYLON.Vector3.Zero(),
            this.scene
        );
        this.camera.attachControl(canvas, true);
        this.camera.lowerRadiusLimit = 5;
        this.camera.upperRadiusLimit = 25;
    }

    private addLights(): void {
        // Multiple light sources for better illumination
        const light1 = new BABYLON.HemisphericLight(
            'light1',
            new BABYLON.Vector3(0, 1, 0),
            this.scene
        );
        light1.intensity = 0.6;

        const light2 = new BABYLON.PointLight(
            'light2',
            new BABYLON.Vector3(5, 5, 5),
            this.scene
        );
        light2.intensity = 0.8;

        const light3 = new BABYLON.PointLight(
            'light3',
            new BABYLON.Vector3(-5, 5, -5),
            this.scene
        );
        light3.intensity = 0.8;
        light3.diffuse = new BABYLON.Color3(0.5, 0.5, 1); // Blue-ish light
    }

    private createInteractiveObjects(): void {
        // Create various meshes for interaction
        const meshData = [
            { type: 'box', name: 'Box', position: new BABYLON.Vector3(0, 1, 0), color: new BABYLON.Color3(1, 0, 0) },
            { type: 'sphere', name: 'Sphere', position: new BABYLON.Vector3(-4, 1, 0), color: new BABYLON.Color3(0, 1, 0) },
            { type: 'cylinder', name: 'Cylinder', position: new BABYLON.Vector3(4, 1, 0), color: new BABYLON.Color3(0, 0, 1) },
            { type: 'torus', name: 'Torus', position: new BABYLON.Vector3(0, 1, 4), color: new BABYLON.Color3(1, 1, 0) }
        ];

        meshData.forEach(data => {
            let mesh: BABYLON.Mesh;

            switch (data.type) {
                case 'box':
                    mesh = BABYLON.MeshBuilder.CreateBox(data.name, { size: 2 }, this.scene);
                    break;
                case 'sphere':
                    mesh = BABYLON.MeshBuilder.CreateSphere(data.name, { diameter: 2 }, this.scene);
                    break;
                case 'cylinder':
                    mesh = BABYLON.MeshBuilder.CreateCylinder(data.name, { height: 2, diameter: 1.5 }, this.scene);
                    break;
                case 'torus':
                    mesh = BABYLON.MeshBuilder.CreateTorus(data.name, { diameter: 2, thickness: 0.5 }, this.scene);
                    break;
            }

            mesh.position = data.position;
            const material = new BABYLON.StandardMaterial(`${data.name}Mat`, this.scene);
            material.diffuseColor = data.color;
            mesh.material = material;

            // Store original properties for reset
            mesh.metadata = {
                originalColor: data.color,
                originalPosition: data.position.clone(),
                originalScaling: mesh.scaling.clone()
            };

            this.meshes.push(mesh);
        });

        // Create ground
        const ground = BABYLON.MeshBuilder.CreateGround('ground', { width: 20, height: 20 }, this.scene);
        const groundMaterial = new BABYLON.StandardMaterial('groundMat', this.scene);
        groundMaterial.diffuseColor = new BABYLON.Color3(0.3, 0.3, 0.3);
        groundMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
        ground.material = groundMaterial;
    }

    private setupGUI(): void {
        // Add advanced texture for 3D text
        const advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI('UI');

        // Title text
        const title = new BABYLON.GUI.TextBlock();
        title.text = 'Babylon.js Interactive Scene';
        title.color = 'white';
        title.fontSize = 24;
        title.fontWeight = 'bold';
        title.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER;
        title.textVerticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
        title.top = '20px';
        advancedTexture.addControl(title);

        // Instructions
        const instructions = new BABYLON.GUI.TextBlock();
        instructions.text = 'Click on objects to interact with them';
        instructions.color = 'white';
        instructions.fontSize = 16;
        instructions.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER;
        instructions.textVerticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
        instructions.top = '60px';
        advancedTexture.addControl(instructions);

        // Object info panel
        const infoPanel = new BABYLON.GUI.StackPanel();
        infoPanel.width = '200px';
        infoPanel.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT;
        infoPanel.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
        infoPanel.top = '20px';
        advancedTexture.addControl(infoPanel);

        const infoText = new BABYLON.GUI.TextBlock();
        infoText.text = 'Click an object';
        infoText.color = 'white';
        infoText.fontSize = 14;
        infoPanel.addControl(infoText);

        // Store reference to update info text
        this.scene.metadata = { infoText };
    }

    private setupInteraction(): void {
        this.scene.onPointerObservable.add((pointerInfo) => {
            switch (pointerInfo.type) {
                case BABYLON.PointerEventTypes.POINTERDOWN:
                    this.handlePointerDown(pointerInfo);
                    break;
            }
        });
    }

    private handlePointerDown(pointerInfo: BABYLON.PointerInfo): void {
        if (pointerInfo.pickInfo && pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh) {
            const mesh = pointerInfo.pickInfo.pickedMesh;
            this.interactWithMesh(mesh);
        }
    }

    private interactWithMesh(mesh: BABYLON.Mesh): void {
        // Update info text
        const infoText = this.scene.metadata.infoText;
        if (infoText) {
            infoText.text = `Selected: ${mesh.name}`;
        }

        // Create interaction effect
        this.createInteractionEffect(mesh);

        // Animate the mesh
        this.animateMesh(mesh);
    }

    private createInteractionEffect(mesh: BABYLON.Mesh): void {
        // Create particle effect at mesh position
        const particleSystem = new BABYLON.ParticleSystem('particles', 1000, this.scene);
        particleSystem.particleTexture = new BABYLON.Texture('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChAI9jU77yQAAAABJRU5ErkJggg==', this.scene);

        particleSystem.emitter = mesh.position;
        particleSystem.minEmitBox = new BABYLON.Vector3(-0.5, -0.5, -0.5);
        particleSystem.maxEmitBox = new BABYLON.Vector3(0.5, 0.5, 0.5);

        particleSystem.color1 = new BABYLON.Color4(1, 1, 1, 1);
        particleSystem.color2 = new BABYLON.Color4(1, 1, 0, 1);
        particleSystem.colorDead = new BABYLON.Color4(0, 0, 0, 0);

        particleSystem.minSize = 0.1;
        particleSystem.maxSize = 0.5;

        particleSystem.minLifeTime = 0.5;
        particleSystem.maxLifeTime = 2;

        particleSystem.emitRate = 500;
        particleSystem.blendMode = BABYLON.ParticleSystem.BLENDMODE_ONEONE;
        particleSystem.gravity = new BABYLON.Vector3(0, -9.81, 0);
        particleSystem.direction1 = new BABYLON.Vector3(-1, 1, -1);
        particleSystem.direction2 = new BABYLON.Vector3(1, 1, 1);
        particleSystem.minAngularSpeed = 0;
        particleSystem.maxAngularSpeed = Math.PI;
        particleSystem.minEmitPower = 0.5;
        particleSystem.maxEmitPower = 2;
        particleSystem.updateSpeed = 0.01;

        particleSystem.start();

        // Stop particles after 1 second
        setTimeout(() => {
            particleSystem.stop();
        }, 1000);
    }

    private animateMesh(mesh: BABYLON.Mesh): void {
        // Bounce animation
        BABYLON.Animation.CreateAndStartAnimation(
            'bounce',
            mesh,
            'position.y',
            30,
            20,
            mesh.position.y,
            mesh.position.y + 3,
            BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
        );

        // Rotation animation
        BABYLON.Animation.CreateAndStartAnimation(
            'rotate',
            mesh,
            'rotation.y',
            30,
            30,
            mesh.rotation.y,
            mesh.rotation.y + Math.PI * 2,
            BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
        );

        // Scale animation
        BABYLON.Animation.CreateAndStartAnimation(
            'scale',
            mesh,
            'scaling',
            30,
            15,
            mesh.scaling.clone(),
            mesh.metadata.originalScaling.scale(1.5),
            BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
        );
    }

    private startRenderLoop(): void {
        this.engine.runRenderLoop(() => {
            this.scene.render();
        });
    }
}

// 3. Physics-Based Scene
class PhysicsScene {
    private engine: BABYLON.Engine;
    private scene: BABYLON.Scene;
    private camera: BABYLON.UniversalCamera;
    private boxes: BABYLON.Mesh[] = [];

    constructor(canvas: HTMLCanvasElement) {
        this.engine = new BABYLON.Engine(canvas, true);
        this.scene = new BABYLON.Scene(this.scene);
        this.scene.clearColor = new BABYLON.Color4(0.5, 0.8, 0.9, 1); // Sky blue

        this.setupCamera();
        this.addLights();
        this.enablePhysics();
        this.createPhysicsObjects();
        this.setupControls();
        this.startRenderLoop();
    }

    private setupCamera(): void {
        this.camera = new BABYLON.UniversalCamera(
            'camera',
            new BABYLON.Vector3(0, 5, -15),
            this.scene
        );
        this.camera.setTarget(BABYLON.Vector3.Zero());
        this.camera.attachControl(canvas, true);
    }

    private addLights(): void {
        const light = new BABYLON.HemisphericLight(
            'light',
            new BABYLON.Vector3(0, 1, 0),
            this.scene
        );
        light.intensity = 1;

        const dirLight = new BABYLON.DirectionalLight(
            'dirLight',
            new BABYLON.Vector3(-1, -2, -1),
            this.scene
        );
        dirLight.position = new BABYLON.Vector3(20, 40, 20);
        dirLight.intensity = 0.5;
    }

    private enablePhysics(): void {
        // Enable physics with gravity
        this.scene.enablePhysics(
            new BABYLON.Vector3(0, -9.81, 0),
            new BABYLON.CannonJSPlugin()
        );
    }

    private createPhysicsObjects(): void {
        // Create ground
        const ground = BABYLON.MeshBuilder.CreateGround(
            'ground',
            { width: 20, height: 20 },
            this.scene
        );
        const groundMaterial = new BABYLON.StandardMaterial('groundMat', this.scene);
        groundMaterial.diffuseColor = new BABYLON.Color3(0.3, 0.7, 0.3);
        ground.material = groundMaterial;
        ground.physicsImpostor = new BABYLON.PhysicsImpostor(
            ground,
            BABYLON.PhysicsImpostor.BoxImpostor,
            { mass: 0, restitution: 0.7 },
            this.scene
        );

        // Create walls
        this.createWalls();

        // Create initial boxes
        for (let i = 0; i < 5; i++) {
            this.createBox(
                new BABYLON.Vector3(
                    (Math.random() - 0.5) * 4,
                    2 + i * 1.1,
                    (Math.random() - 0.5) * 4
                )
            );
        }

        // Create bouncing spheres
        for (let i = 0; i < 3; i++) {
            this.createSphere(
                new BABYLON.Vector3(
                    (Math.random() - 0.5) * 6,
                    5 + i * 2,
                    (Math.random() - 0.5) * 6
                )
            );
        }
    }

    private createWalls(): void {
        const wallMaterial = new BABYLON.StandardMaterial('wallMat', this.scene);
        wallMaterial.diffuseColor = new BABYLON.Color3(0.7, 0.7, 0.7);
        wallMaterial.alpha = 0.3;

        // Back wall
        const backWall = BABYLON.MeshBuilder.CreateBox(
            'backWall',
            { width: 20, height: 10, depth: 0.5 },
            this.scene
        );
        backWall.position.z = -10;
        backWall.material = wallMaterial;
        backWall.physicsImpostor = new BABYLON.PhysicsImpostor(
            backWall,
            BABYLON.PhysicsImpostor.BoxImpostor,
            { mass: 0, restitution: 0.5 },
            this.scene
        );

        // Side walls
        const leftWall = BABYLON.MeshBuilder.CreateBox(
            'leftWall',
            { width: 0.5, height: 10, depth: 20 },
            this.scene
        );
        leftWall.position.x = -10;
        leftWall.material = wallMaterial;
        leftWall.physicsImpostor = new BABYLON.PhysicsImpostor(
            leftWall,
            BABYLON.PhysicsImpostor.BoxImpostor,
            { mass: 0, restitution: 0.5 },
            this.scene
        );

        const rightWall = leftWall.clone('rightWall');
        rightWall.position.x = 10;
        this.scene.addMesh(rightWall);
    }

    private createBox(position: BABYLON.Vector3): void {
        const box = BABYLON.MeshBuilder.CreateBox(
            `box${this.boxes.length}`,
            { size: 1 },
            this.scene
        );
        box.position = position;

        const boxMaterial = new BABYLON.StandardMaterial(`boxMat${this.boxes.length}`, this.scene);
        const hue = Math.random();
        boxMaterial.diffuseColor = new BABYLON.Color3.FromHSV(hue * 360, 1, 1);
        box.material = boxMaterial;

        // Add physics impostor
        box.physicsImpostor = new BABYLON.PhysicsImpostor(
            box,
            BABYLON.PhysicsImpostor.BoxImpostor,
            { mass: 1, restitution: 0.8, friction: 0.5 },
            this.scene
        );

        this.boxes.push(box);
    }

    private createSphere(position: BABYLON.Vector3): void {
        const sphere = BABYLON.MeshBuilder.CreateSphere(
            `sphere${Date.now()}`,
            { diameter: 0.8, segments: 16 },
            this.scene
        );
        sphere.position = position;

        const sphereMaterial = new BABYLON.StandardMaterial(`sphereMat${Date.now()}`, this.scene);
        sphereMaterial.diffuseColor = new BABYLON.Color3.FromHSV(Math.random() * 360, 1, 1);
        sphere.material = sphereMaterial;

        sphere.physicsImpostor = new BABYLON.PhysicsImpostor(
            sphere,
            BABYLON.PhysicsImpostor.SphereImpostor,
            { mass: 0.5, restitution: 0.9, friction: 0.3 },
            this.scene
        );
    }

    private setupControls(): void {
        this.scene.onPointerDown = (evt, pickResult) => {
            if (pickResult.hit) {
                // Apply impulse to clicked object
                const direction = this.camera.position.subtract(pickResult.pickedPoint!).normalize();
                const force = direction.scale(10);

                if (pickResult.pickedMesh!.physicsImpostor) {
                    pickResult.pickedMesh!.physicsImpostor!.applyImpulse(
                        force,
                        pickResult.pickedPoint!
                    );
                }
            }
        };

        // Add keyboard controls
        window.addEventListener('keydown', (event) => {
            switch (event.key) {
                case ' ':
                    // Space: Add new box at camera position
                    this.createBox(
                        this.camera.position.add(
                            this.camera.getDirection(new BABYLON.Vector3(0, 0, 1)).scale(2)
                        )
                    );
                    break;
                case 's':
                    // S: Add new sphere
                    this.createSphere(
                        this.camera.position.add(
                            this.camera.getDirection(new BABYLON.Vector3(0, 0, 1)).scale(2)
                        )
                    );
                    break;
                case 'r':
                    // R: Reset scene
                    this.resetScene();
                    break;
            }
        });
    }

    private resetScene(): void {
        // Remove all physics objects except ground and walls
        this.scene.meshes.forEach(mesh => {
            if (mesh.name.startsWith('box') || mesh.name.startsWith('sphere')) {
                mesh.dispose();
            }
        });

        this.boxes = [];

        // Create new initial objects
        for (let i = 0; i < 5; i++) {
            this.createBox(
                new BABYLON.Vector3(
                    (Math.random() - 0.5) * 4,
                    2 + i * 1.1,
                    (Math.random() - 0.5) * 4
                )
            );
        }
    }

    private startRenderLoop(): void {
        // Add instructions text
        const advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI('UI');

        const text = new BABYLON.GUI.TextBlock();
        text.text = 'Click objects to apply force | Space: Add box | S: Add sphere | R: Reset';
        text.color = 'white';
        text.fontSize = 14;
        text.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER;
        text.textVerticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
        text.top = '20px';
        advancedTexture.addControl(text);

        this.engine.runRenderLoop(() => {
            this.scene.render();
        });
    }
}

// Usage examples:
// const basicScene = new BasicBabylonScene(document.getElementById('canvas1') as HTMLCanvasElement);
// const interactiveScene = new InteractiveScene(document.getElementById('canvas2') as HTMLCanvasElement);
// const physicsScene = new PhysicsScene(document.getElementById('canvas3') as HTMLCanvasElement);

export {
    BasicBabylonScene,
    InteractiveScene,
    PhysicsScene
};

💻 Matériaux et Shaders typescript

🟡 intermediate ⭐⭐⭐

Matériaux avancés Babylon.js, shaders PBR, textures, matériaux personnalisés et rendu de surfaces réalistes

⏱️ 30 min 🏷️ babylonjs, materials, pbr, shaders
Prerequisites: Babylon.js basics, 3D graphics concepts, Material properties
// Babylon.js Materials and Shaders Examples

import * as BABYLON from '@babylonjs/core';
import { GridMaterial } from '@babylonjs/materials/grid';
import '@babylonjs/loaders';

// 1. Comprehensive Material Showcase
class MaterialShowcase {
    private engine: BABYLON.Engine;
    private scene: BABYLON.Scene;
    private camera: BABYLON.ArcRotateCamera;

    constructor(canvas: HTMLCanvasElement) {
        this.engine = new BABYLON.Engine(canvas, true);
        this.scene = new BABYLON.Scene(this.engine);
        this.scene.clearColor = new BABYLON.Color4(0.05, 0.05, 0.1, 1);

        this.setupCamera();
        this.setupLighting();
        this.createMaterialSamples();
        this.setupEnvironment();
        this.startRenderLoop();
    }

    private setupCamera(): void {
        this.camera = new BABYLON.ArcRotateCamera(
            'camera',
            0,
            Math.PI / 3,
            20,
            BABYLON.Vector3.Zero(),
            this.scene
        );
        this.camera.attachControl(canvas, true);
        this.camera.lowerRadiusLimit = 8;
        this.camera.upperRadiusLimit = 30;
    }

    private setupLighting(): void {
        // Multiple light setup for PBR materials
        const hemisphericLight = new BABYLON.HemisphericLight(
            'hemiLight',
            new BABYLON.Vector3(0, 1, 0),
            this.scene
        );
        hemisphericLight.intensity = 0.3;
        hemisphericLight.groundColor = new BABYLON.Color3(0.2, 0.2, 0.3);

        const mainLight = new BABYLON.DirectionalLight(
            'mainLight',
            new BABYLON.Vector3(-1, -2, 1),
            this.scene
        );
        mainLight.intensity = 3;
        mainLight.position = new BABYLON.Vector3(20, 40, 20);

        // Colored fill lights
        const warmLight = new BABYLON.PointLight(
            'warmLight',
            new BABYLON.Vector3(10, 5, -5),
            this.scene
        );
        warmLight.intensity = 1;
        warmLight.diffuse = new BABYLON.Color3(1, 0.8, 0.4);

        const coolLight = new BABYLON.PointLight(
            'coolLight',
            new BABYLON.Vector3(-10, 5, 5),
            this.scene
        );
        coolLight.intensity = 1;
        coolLight.diffuse = new BABYLON.Color3(0.4, 0.6, 1);
    }

    private createMaterialSamples(): void {
        const materials = [
            this.createStandardMaterial(),
            this.createPBRMaterial(),
            this.createGridMaterial(),
            this.createCustomShaderMaterial(),
            this.createAnimatedMaterial(),
            this.createToonMaterial(),
            this.createNormalMaterial(),
            this.createWireframeMaterial()
        ];

        materials.forEach((material, index) => {
            const mesh = BABYLON.MeshBuilder.CreateSphere(
                `sphere${index}`,
                { diameter: 2, segments: 32 },
                this.scene
            );

            const angle = (index / materials.length) * Math.PI * 2;
            mesh.position.set(
                Math.cos(angle) * 6,
                0,
                Math.sin(angle) * 6
            );

            mesh.material = material;

            // Add subtle rotation
            this.scene.registerBeforeRender(() => {
                mesh.rotation.y += 0.005 * (index % 2 === 0 ? 1 : -1);
            });
        });
    }

    private createStandardMaterial(): BABYLON.StandardMaterial {
        const material = new BABYLON.StandardMaterial('standardMat', this.scene);
        material.diffuseColor = new BABYLON.Color3(0.8, 0.2, 0.2);
        material.specularColor = new BABYLON.Color3(1, 1, 1);
        material.specularPower = 128;
        material.emissiveColor = new BABYLON.Color3(0.1, 0, 0);
        material.backFaceCulling = false;

        // Add texture
        material.diffuseTexture = this.createProceduralTexture(256, (ctx, w, h) => {
            const gradient = ctx.createLinearGradient(0, 0, w, h);
            gradient.addColorStop(0, '#ff6666');
            gradient.addColorStop(0.5, '#cc0000');
            gradient.addColorStop(1, '#990000');
            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, w, h);

            // Add pattern
            ctx.strokeStyle = '#ffffff';
            ctx.lineWidth = 2;
            for (let i = 0; i < w; i += 16) {
                ctx.beginPath();
                ctx.moveTo(i, 0);
                ctx.lineTo(i, h);
                ctx.stroke();
            }
        });

        return material;
    }

    private createPBRMaterial(): BABYLON.PBRMaterial {
        const material = new BABYLON.PBRMaterial('pbrMat', this.scene);
        material.baseColor = new BABYLON.Color3(0.2, 0.8, 0.2);
        material.metallic = 0.7;
        material.roughness = 0.3;
        material.alpha = 1;
        material.subSurface.isRefractionEnabled = true;
        material.subSurface.refractionIntensity = 0.5;

        // Add PBR textures
        material.albedoTexture = this.createProceduralTexture(512, (ctx, w, h) => {
            // Metallic green texture
            const imageData = ctx.createImageData(w, h);
            for (let i = 0; i < imageData.data.length; i += 4) {
                const x = (i / 4) % w;
                const y = Math.floor((i / 4) / w);
                const noise = Math.sin(x * 0.1) * Math.cos(y * 0.1);
                const value = 100 + noise * 50;

                imageData.data[i] = value * 0.2;     // R
                imageData.data[i + 1] = value;      // G
                imageData.data[i + 2] = value * 0.2; // B
                imageData.data[i + 3] = 255;       // A
            }
            ctx.putImageData(imageData, 0, 0);
        });

        material.metallicTexture = this.createProceduralTexture(256, (ctx, w, h) => {
            const imageData = ctx.createImageData(w, h);
            for (let i = 0; i < imageData.data.length; i += 4) {
                const value = 200 + Math.random() * 55;
                imageData.data[i] = value;     // R
                imageData.data[i + 1] = value; // G
                imageData.data[i + 2] = value; // B
                imageData.data[i + 3] = 255;   // A
            }
            ctx.putImageData(imageData, 0, 0);
        });

        return material;
    }

    private createGridMaterial(): GridMaterial {
        const material = new GridMaterial('gridMat', this.scene);
        material.majorUnitFrequency = 5;
        material.minorUnitVisibility = 0.5;
        material.gridRatio = 1;
        material.backFaceCulling = false;
        material.mainColor = new BABYLON.Color3(0, 0.5, 1);
        material.lineColor = new BABYLON.Color3(1, 1, 1);
        material.opacity = 0.95;

        return material;
    }

    private createCustomShaderMaterial(): BABYLON.ShaderMaterial {
        // Create custom shader
        BABYLON.Effect.ShadersStore['customVertexShader'] = `
            precision highp float;

            attribute vec3 position;
            attribute vec3 normal;
            attribute vec2 uv;

            uniform mat4 world;
            uniform mat4 worldViewProjection;

            varying vec3 vNormal;
            varying vec3 vPosition;
            varying vec2 vUV;
            varying float vTime;

            uniform float time;

            void main(void) {
                vec3 newPosition = position;
                newPosition.y += sin(position.x * 5.0 + time) * 0.1;
                newPosition.y += cos(position.z * 5.0 + time) * 0.1;

                vec4 worldPosition = world * vec4(newPosition, 1.0);
                vPosition = worldPosition.xyz;
                vNormal = normalize(world * vec4(normal, 0.0)).xyz;
                vUV = uv;
                vTime = time;

                gl_Position = worldViewProjection * worldPosition;
            }
        `;

        BABYLON.Effect.ShadersStore['customFragmentShader'] = `
            precision highp float;

            varying vec3 vNormal;
            varying vec3 vPosition;
            varying vec2 vUV;
            varying float vTime;

            uniform vec3 lightPosition;
            uniform vec3 cameraPosition;

            void main(void) {
                vec3 lightDirection = normalize(lightPosition - vPosition);
                vec3 viewDirection = normalize(cameraPosition - vPosition);
                vec3 normal = normalize(vNormal);

                float diff = max(dot(normal, lightDirection), 0.0);
                vec3 diffuse = diff * vec3(1.0, 1.0, 1.0);

                // Animated color based on time and UV
                vec3 color = vec3(
                    sin(vTime + vUV.x * 10.0) * 0.5 + 0.5,
                    cos(vTime + vUV.y * 10.0) * 0.5 + 0.5,
                    sin(vTime * 2.0) * 0.5 + 0.5
                );

                vec3 finalColor = color * (0.3 + diffuse * 0.7);

                gl_FragColor = vec4(finalColor, 1.0);
            }
        `;

        const shaderMaterial = new BABYLON.ShaderMaterial(
            'customShader',
            this.scene,
            {
                vertex: 'custom',
                fragment: 'custom',
            },
            {
                attributes: ['position', 'normal', 'uv'],
                uniforms: ['world', 'worldViewProjection', 'time', 'lightPosition', 'cameraPosition']
            }
        );

        shaderMaterial.setFloat('time', 0);
        shaderMaterial.setVector3('lightPosition', new BABYLON.Vector3(10, 10, 10));
        shaderMaterial.setVector3('cameraPosition', this.camera.position);

        // Animate the shader
        this.scene.registerBeforeRender(() => {
            shaderMaterial.setFloat('time', Date.now() * 0.001);
            shaderMaterial.setVector3('cameraPosition', this.camera.position);
        });

        return shaderMaterial;
    }

    private createAnimatedMaterial(): BABYLON.StandardMaterial {
        const material = new BABYLON.StandardMaterial('animatedMat', this.scene);

        // Create animated texture
        const dynamicTexture = new BABYLON.DynamicTexture('animated', 512, this.scene);
        const context = dynamicTexture.getContext();

        let frame = 0;
        this.scene.registerBeforeRender(() => {
            context.fillStyle = '#000080';
            context.fillRect(0, 0, 512, 512);

            // Draw animated circles
            for (let i = 0; i < 5; i++) {
                const x = 256 + Math.cos((frame + i * 60) * 0.05) * 150;
                const y = 256 + Math.sin((frame + i * 60) * 0.05) * 150;
                const radius = 20 + Math.sin(frame * 0.1 + i) * 10;

                context.beginPath();
                context.arc(x, y, radius, 0, Math.PI * 2);
                context.fillStyle = `hsl(${(frame + i * 60) % 360}, 100%, 50%)`;
                context.fill();
            }

            dynamicTexture.update();
            frame++;
        });

        material.diffuseTexture = dynamicTexture;
        material.emissiveTexture = dynamicTexture;
        material.emissiveColor = new BABYLON.Color3(0.5, 0.5, 0.5);

        return material;
    }

    private createToonMaterial(): BABYLON.StandardMaterial {
        const material = new BABYLON.StandardMaterial('toonMat', this.scene);

        // Create toon shading effect with texture
        material.diffuseTexture = this.createProceduralTexture(256, (ctx, w, h) => {
            // Create stepped gradient for toon effect
            const steps = 4;
            const stepHeight = h / steps;

            for (let i = 0; i < steps; i++) {
                const brightness = 255 - (i * 60);
                ctx.fillStyle = `rgb(${brightness}, ${brightness * 0.7}, ${brightness * 0.3})`;
                ctx.fillRect(0, i * stepHeight, w, stepHeight);
            }

            // Add cell border effect
            ctx.strokeStyle = '#000000';
            ctx.lineWidth = 2;
            for (let i = 1; i < steps; i++) {
                ctx.beginPath();
                ctx.moveTo(0, i * stepHeight);
                ctx.lineTo(w, i * stepHeight);
                ctx.stroke();
            }
        });

        material.specularColor = new BABYLON.Color3(0, 0, 0); // No specular highlights
        material.roughness = 1; // Rough surface

        return material;
    }

    private createNormalMaterial(): BABYLON.StandardMaterial {
        const material = new BABYLON.StandardMaterial('normalMat', this.scene);

        // Create normal map texture
        material.bumpTexture = this.createProceduralTexture(256, (ctx, w, h) => {
            const imageData = ctx.createImageData(w, h);
            for (let i = 0; i < imageData.data.length; i += 4) {
                const x = (i / 4) % w;
                const y = Math.floor((i / 4) / w);

                // Create flowing normal pattern
                const nx = Math.sin(x * 0.05) * 127 + 128;
                const ny = Math.cos(y * 0.05) * 127 + 128;
                const nz = 255;

                imageData.data[i] = nx;     // R (X normal)
                imageData.data[i + 1] = ny; // G (Y normal)
                imageData.data[i + 2] = nz; // B (Z normal)
                imageData.data[i + 3] = 255; // A
            }
            ctx.putImageData(imageData, 0, 0);
        });

        material.diffuseColor = new BABYLON.Color3(0.7, 0.7, 0.7);
        material.specularColor = new BABYLON.Color3(1, 1, 1);
        material.specularPower = 64;

        return material;
    }

    private createWireframeMaterial(): BABYLON.StandardMaterial {
        const material = new BABYLON.StandardMaterial('wireframeMat', this.scene);
        material.wireframe = true;
        material.emissiveColor = new BABYLON.Color3(0, 1, 1);
        material.diffuseColor = new BABYLON.Color3(0, 0, 0);
        material.specularColor = new BABYLON.Color3(0, 0, 0);

        return material;
    }

    private createProceduralTexture(size: number, generator: (ctx: CanvasRenderingContext2D, w: number, h: number) => void): BABYLON.Texture {
        const canvas = document.createElement('canvas');
        canvas.width = size;
        canvas.height = size;
        const context = canvas.getContext('2d')!;

        generator(context, size, size);

        return new BABYLON.Texture(null, this.scene, false, false, BABYLON.Texture.TRILINEAR_SAMPLINGMODE, null, canvas);
    }

    private setupEnvironment(): void {
        // Create ground with reflective material
        const ground = BABYLON.MeshBuilder.CreateGround('ground', { width: 30, height: 30 }, this.scene);
        ground.position.y = -3;

        const groundMaterial = new BABYLON.StandardMaterial('groundMat', this.scene);
        groundMaterial.diffuseColor = new BABYLON.Color3(0.1, 0.1, 0.1);
        groundMaterial.specularColor = new BABYLON.Color3(1, 1, 1);
        groundMaterial.specularPower = 128;
        groundMaterial.alpha = 0.9;
        ground.material = groundMaterial;

        // Create skybox
        this.createSkybox();

        // Add particle system for atmosphere
        this.createAtmosphere();
    }

    private createSkybox(): void {
        const skybox = BABYLON.MeshBuilder.CreateBox('skyBox', { size: 100 }, this.scene);
        const skyboxMaterial = new BABYLON.StandardMaterial('skyBox', this.scene);
        skyboxMaterial.backFaceCulling = false;
        skyboxMaterial.disableLighting = true;

        // Create gradient texture for skybox
        const skyTexture = this.createProceduralTexture(512, (ctx, w, h) => {
            const gradient = ctx.createLinearGradient(0, 0, 0, h);
            gradient.addColorStop(0, '#001133');
            gradient.addColorStop(0.5, '#004488');
            gradient.addColorStop(1, '#0088ff');
            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, w, h);

            // Add stars
            ctx.fillStyle = '#ffffff';
            for (let i = 0; i < 100; i++) {
                const x = Math.random() * w;
                const y = Math.random() * h * 0.7;
                const size = Math.random() * 2;
                ctx.fillRect(x, y, size, size);
            }
        });

        skyboxMaterial.diffuseTexture = skyTexture;
        skyboxMaterial.emissiveTexture = skyTexture;
        skybox.material = skyboxMaterial;
    }

    private createAtmosphere(): void {
        const particleSystem = new BABYLON.ParticleSystem('atmosphere', 2000, this.scene);

        particleSystem.particleTexture = new BABYLON.Texture('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==', this.scene);

        particleSystem.emitter = BABYLON.Vector3.Zero();
        particleSystem.minEmitBox = new BABYLON.Vector3(-25, -25, -25);
        particleSystem.maxEmitBox = new BABYLON.Vector3(25, 25, 25);

        particleSystem.color1 = new BABYLON.Color4(1, 1, 1, 0.5);
        particleSystem.color2 = new BABYLON.Color4(0.5, 0.5, 1, 0.3);
        particleSystem.colorDead = new BABYLON.Color4(0, 0, 0, 0);

        particleSystem.minSize = 0.05;
        particleSystem.maxSize = 0.15;

        particleSystem.minLifeTime = 10;
        particleSystem.maxLifeTime = 30;

        particleSystem.emitRate = 50;
        particleSystem.blendMode = BABYLON.ParticleSystem.BLENDMODE_ADD;
        particleSystem.gravity = new BABYLON.Vector3(0, 0, 0);
        particleSystem.direction1 = new BABYLON.Vector3(-1, -1, -1);
        particleSystem.direction2 = new BABYLON.Vector3(1, 1, 1);
        particleSystem.minEmitPower = 0.01;
        particleSystem.maxEmitPower = 0.05;

        particleSystem.start();
    }

    private startRenderLoop(): void {
        this.engine.runRenderLoop(() => {
            this.scene.render();
        });
    }
}

// 2. Advanced PBR Materials
class PBRMaterialDemo {
    private engine: BABYLON.Engine;
    private scene: BABYLON.Scene;
    private camera: BABYLON.ArcRotateCamera;

    constructor(canvas: HTMLCanvasElement) {
        this.engine = new BABYLON.Engine(canvas, true);
        this.scene = new BABYLON.Scene(this.engine);
        this.scene.clearColor = new BABYLON.Color4(0.02, 0.02, 0.05, 1);

        this.setupCamera();
        this.setupHDREnvironment();
        this.createPBRMaterials();
        this.createMaterialControls();
        this.startRenderLoop();
    }

    private setupCamera(): void {
        this.camera = new BABYLON.ArcRotateCamera(
            'camera',
            Math.PI / 2,
            Math.PI / 3,
            15,
            BABYLON.Vector3.Zero(),
            this.scene
        );
        this.camera.attachControl(canvas, true);
        this.camera.wheelPrecision = 50;
    }

    private setupHDREnvironment(): void {
        // Create HDR environment for realistic lighting
        const envTexture = BABYLON.CubeTexture.CreateFromPrefilteredData(
            'https://playground.babylonjs.com/textures/environment.dds',
            this.scene
        );
        envTexture.gammaSpace = false;
        envTexture.rotationY = Math.PI;

        // Set as environment texture
        this.scene.environmentTexture = envTexture;
        this.scene.createDefaultSkybox(envTexture, true, 1000);

        // Create IBL (Image Based Lighting)
        this.scene.environmentIntensity = 1.0;
    }

    private createPBRMaterials(): void {
        const materials = [
            this.createMetalMaterial(),
            this.createGlassMaterial(),
            this.createFabricMaterial(),
            this.createWoodMaterial(),
            this.createMarbleMaterial(),
            this.createHolographicMaterial()
        ];

        // Create display spheres
        materials.forEach((material, index) => {
            const mesh = BABYLON.MeshBuilder.CreateSphere(
                `pbrSphere${index}`,
                { diameter: 2, segments: 64 },
                this.scene
            );

            const angle = (index / materials.length) * Math.PI * 2;
            mesh.position.set(
                Math.cos(angle) * 5,
                0,
                Math.sin(angle) * 5
            );

            mesh.material = material;

            // Add animation
            this.scene.registerBeforeRender(() => {
                mesh.rotation.y += 0.01;
                mesh.position.y = Math.sin(Date.now() * 0.001 + index) * 0.5;
            });
        });

        // Create ground with PBR material
        this.createPBRGround();
    }

    private createMetalMaterial(): BABYLON.PBRMetallicRoughnessMaterial {
        const material = new BABYLON.PBRMetallicRoughnessMaterial('metal', this.scene);
        material.baseColor = new BABYLON.Color3(0.7, 0.7, 0.8);
        material.metallic = 1.0;
        material.roughness = 0.1;
        material.environmentIntensity = 1.0;

        // Add normal map for surface detail
        material.normalTexture = this.createNormalMap();
        material.useParallax = true;
        material.useParallaxOcclusion = true;

        return material;
    }

    private createGlassMaterial(): BABYLON.PBRMaterial {
        const material = new BABYLON.PBRMaterial('glass', this.scene);
        material.alpha = 0.2;
        material.subSurface.isRefractionEnabled = true;
        material.subSurface.refractionIntensity = 0.8;
        material.subSurface.tintColor = new BABYLON.Color3(0.8, 0.9, 1.0);
        material.indexOfRefraction = 1.5;
        material.metallic = 0;
        material.roughness = 0;
        material.clearCoat.isEnabled = true;
        material.clearCoat.intensity = 1.0;
        material.clearCoat.roughness = 0;

        return material;
    }

    private createFabricMaterial(): BABYLON.PBRMaterial {
        const material = new BABYLON.PBRMaterial('fabric', this.scene);
        material.albedoColor = new BABYLON.Color3(0.3, 0.2, 0.1);
        material.metallic = 0;
        material.roughness = 0.9;
        material.useSubSurfaceScattering = true;
        material.subSurface.tintColor = new BABYLON.Color3(0.8, 0.4, 0.1);
        material.subSurface.translucencyIntensity = 0.8;

        // Add fabric texture
        material.albedoTexture = this.createFabricTexture();
        material.bumpTexture = this.createFabricBumpTexture();

        return material;
    }

    private createWoodMaterial(): BABYLON.PBRMetallicRoughnessMaterial {
        const material = new BABYLON.PBRMetallicRoughnessMaterial('wood', this.scene);
        material.baseColor = new BABYLON.Color3(0.4, 0.2, 0.1);
        material.metallic = 0;
        material.roughness = 0.8;

        // Create wood grain texture
        material.baseTexture = this.createWoodGrainTexture();
        material.normalTexture = this.createWoodNormalTexture();

        return material;
    }

    private createMarbleMaterial(): BABYLON.PBRMaterial {
        const material = new BABYLON.PBRMaterial('marble', this.scene);
        material.albedoColor = new BABYLON.Color3(0.95, 0.95, 0.9);
        material.metallic = 0;
        material.roughness = 0.1;
        material.subSurface.isTranslucencyEnabled = true;
        material.subSurface.translucencyIntensity = 0.2;
        material.subSurface.tintColor = new BABYLON.Color3(0.9, 0.9, 0.8);

        // Create marble texture
        material.albedoTexture = this.createMarbleTexture();
        material.bumpTexture = this.createMarbleBumpTexture();

        return material;
    }

    private createHolographicMaterial(): BABYLON.PBRMaterial {
        const material = new BABYLON.PBRMaterial('holographic', this.scene);
        material.albedoColor = new BABYLON.Color3(1, 1, 1);
        material.metallic = 1.0;
        material.roughness = 0.1;

        // Create iridescent effect
        material.emissiveTexture = this.createIridescentTexture();
        material.emissiveIntensity = 0.5;

        // Animate the holographic effect
        this.scene.registerBeforeRender(() => {
            const time = Date.now() * 0.001;
            material.emissiveColor = new BABYLON.Color3(
                Math.sin(time) * 0.5 + 0.5,
                Math.sin(time + 2) * 0.5 + 0.5,
                Math.sin(time + 4) * 0.5 + 0.5
            );
        });

        return material;
    }

    private createPBRGround(): void {
        const ground = BABYLON.MeshBuilder.CreateGround('ground', { width: 30, height: 30 }, this.scene);
        ground.position.y = -3;

        const groundMaterial = new BABYLON.PBRMetallicRoughnessMaterial('groundPBR', this.scene);
        groundMaterial.baseColor = new BABYLON.Color3(0.1, 0.1, 0.1);
        groundMaterial.metallic = 0.1;
        groundMaterial.roughness = 0.8;
        groundMaterial.baseTexture = this.createGroundTexture();
        groundMaterial.normalTexture = this.createGroundNormalTexture();

        ground.material = groundMaterial;
    }

    // Texture creation helper methods
    private createNormalMap(): BABYLON.Texture {
        return this.createProceduralTexture('normalMap', (ctx, w, h) => {
            const imageData = ctx.createImageData(w, h);
            for (let i = 0; i < imageData.data.length; i += 4) {
                const x = (i / 4) % w;
                const y = Math.floor((i / 4) / w);
                const wave = Math.sin(x * 0.1) * Math.cos(y * 0.1);

                imageData.data[i] = 128 + wave * 50;     // R
                imageData.data[i + 1] = 128 - wave * 30; // G
                imageData.data[i + 2] = 255;             // B
                imageData.data[i + 3] = 255;             // A
            }
            ctx.putImageData(imageData, 0, 0);
        });
    }

    private createFabricTexture(): BABYLON.Texture {
        return this.createProceduralTexture('fabricTexture', (ctx, w, h) => {
            ctx.fillStyle = '#8B4513';
            ctx.fillRect(0, 0, w, h);

            // Create fabric weave pattern
            ctx.strokeStyle = '#654321';
            ctx.lineWidth = 1;

            for (let x = 0; x < w; x += 4) {
                ctx.beginPath();
                ctx.moveTo(x, 0);
                ctx.lineTo(x, h);
                ctx.stroke();
            }

            for (let y = 0; y < h; y += 4) {
                ctx.beginPath();
                ctx.moveTo(0, y);
                ctx.lineTo(w, y);
                ctx.stroke();
            }
        });
    }

    private createFabricBumpTexture(): BABYLON.Texture {
        return this.createProceduralTexture('fabricBump', (ctx, w, h) => {
            const imageData = ctx.createImageData(w, h);
            for (let i = 0; i < imageData.data.length; i += 4) {
                const x = (i / 4) % w;
                const y = Math.floor((i / 4) / h);
                const weave = (Math.floor(x / 4) + Math.floor(y / 4)) % 2;
                const value = weave ? 180 : 80;

                imageData.data[i] = 128;     // R
                imageData.data[i + 1] = 128; // G
                imageData.data[i + 2] = value; // B
                imageData.data[i + 3] = 255; // A
            }
            ctx.putImageData(imageData, 0, 0);
        });
    }

    private createWoodGrainTexture(): BABYLON.Texture {
        return this.createProceduralTexture('woodGrain', (ctx, w, h) => {
            const gradient = ctx.createLinearGradient(0, 0, w, 0);
            gradient.addColorStop(0, '#4A2C17');
            gradient.addColorStop(0.3, '#6B4423');
            gradient.addColorStop(0.6, '#8B5A2B');
            gradient.addColorStop(1, '#4A2C17');

            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, w, h);

            // Add grain lines
            ctx.strokeStyle = '#2A1A0B';
            ctx.lineWidth = 1;
            for (let i = 0; i < 20; i++) {
                ctx.beginPath();
                ctx.moveTo(0, Math.random() * h);
                ctx.quadraticCurveTo(
                    w/2, Math.random() * h,
                    w, Math.random() * h
                );
                ctx.stroke();
            }
        });
    }

    private createWoodNormalTexture(): BABYLON.Texture {
        return this.createProceduralTexture('woodNormal', (ctx, w, h) => {
            const imageData = ctx.createImageData(w, h);
            for (let i = 0; i < imageData.data.length; i += 4) {
                const x = (i / 4) % w;
                const y = Math.floor((i / 4) / h);
                const grain = Math.sin(x * 0.05) * 20;

                imageData.data[i] = 128 + grain;     // R
                imageData.data[i + 1] = 128;         // G
                imageData.data[i + 2] = 255;         // B
                imageData.data[i + 3] = 255;         // A
            }
            ctx.putImageData(imageData, 0, 0);
        });
    }

    private createMarbleTexture(): BABYLON.Texture {
        return this.createProceduralTexture('marble', (ctx, w, h) => {
            ctx.fillStyle = '#F5F5DC';
            ctx.fillRect(0, 0, w, h);

            // Create marble veins
            ctx.strokeStyle = '#8B8680';
            ctx.lineWidth = 2;

            for (let i = 0; i < 15; i++) {
                ctx.beginPath();
                ctx.moveTo(Math.random() * w, 0);

                for (let y = 0; y < h; y += 10) {
                    const x = Math.random() * w;
                    ctx.lineTo(x, y);
                }

                ctx.globalAlpha = 0.3;
                ctx.stroke();
            }

            ctx.globalAlpha = 1;
        });
    }

    private createMarbleBumpTexture(): BABYLON.Texture {
        return this.createProceduralTexture('marbleBump', (ctx, w, h) => {
            const imageData = ctx.createImageData(w, h);
            for (let i = 0; i < imageData.data.length; i += 4) {
                const x = (i / 4) % w;
                const y = Math.floor((i / 4) / h);
                const noise = Math.sin(x * 0.02) * Math.cos(y * 0.02);

                imageData.data[i] = 128 + noise * 30;     // R
                imageData.data[i + 1] = 128 + noise * 30; // G
                imageData.data[i + 2] = 128 + noise * 20; // B
                imageData.data[i + 3] = 255;             // A
            }
            ctx.putImageData(imageData, 0, 0);
        });
    }

    private createIridescentTexture(): BABYLON.Texture {
        return this.createProceduralTexture('iridescent', (ctx, w, h) => {
            for (let x = 0; x < w; x++) {
                for (let y = 0; y < h; y++) {
                    const hue = (x / w) * 360;
                    const lightness = 50 + Math.sin((x / w) * Math.PI * 4) * 20;
                    ctx.fillStyle = `hsl(${hue}, 100%, ${lightness}%)`;
                    ctx.fillRect(x, y, 1, 1);
                }
            }
        });
    }

    private createGroundTexture(): BABYLON.Texture {
        return this.createProceduralTexture('ground', (ctx, w, h) => {
            ctx.fillStyle = '#1a1a1a';
            ctx.fillRect(0, 0, w, h);

            // Add concrete texture
            ctx.fillStyle = '#2a2a2a';
            for (let i = 0; i < 100; i++) {
                const x = Math.random() * w;
                const y = Math.random() * h;
                const size = Math.random() * 3;
                ctx.fillRect(x, y, size, size);
            }
        });
    }

    private createGroundNormalTexture(): BABYLON.Texture {
        return this.createProceduralTexture('groundNormal', (ctx, w, h) => {
            const imageData = ctx.createImageData(w, h);
            for (let i = 0; i < imageData.data.length; i += 4) {
                const noise = Math.random() * 20 - 10;

                imageData.data[i] = 128 + noise;     // R
                imageData.data[i + 1] = 128 + noise; // G
                imageData.data[i + 2] = 255;         // B
                imageData.data[i + 3] = 255;         // A
            }
            ctx.putImageData(imageData, 0, 0);
        });
    }

    private createProceduralTexture(name: string, generator: (ctx: CanvasRenderingContext2D, w: number, h: number) => void): BABYLON.Texture {
        const canvas = document.createElement('canvas');
        canvas.width = 512;
        canvas.height = 512;
        const context = canvas.getContext('2d')!;

        generator(context, 512, 512);

        const texture = new BABYLON.Texture(`${name}Texture`, this.scene, false, false, BABYLON.Texture.TRILINEAR_SAMPLINGMODE, null, canvas);
        texture.wrapU = BABYLON.Texture.WRAP_ADDRESSMODE;
        texture.wrapV = BABYLON.Texture.WRAP_ADDRESSMODE;

        return texture;
    }

    private createMaterialControls(): void {
        // Add UI for material controls
        const advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI('UI');

        const panel = new BABYLON.GUI.StackPanel();
        panel.width = '250px';
        panel.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT;
        panel.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
        panel.top = '20px';

        advancedTexture.addControl(panel);

        const title = new BABYLON.GUI.TextBlock();
        title.text = 'PBR Material Showcase';
        title.color = 'white';
        title.fontSize = 16;
        title.fontWeight = 'bold';
        title.height = '30px';
        panel.addControl(title);

        const info = new BABYLON.GUI.TextBlock();
        info.text = 'Rotate camera to view different PBR materials\nEach material demonstrates different properties';
        info.color = 'white';
        info.fontSize = 12;
        info.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
        info.height = '60px';
        panel.addControl(info);
    }

    private startRenderLoop(): void {
        this.engine.runRenderLoop(() => {
            this.scene.render();
        });
    }
}

export {
    MaterialShowcase,
    PBRMaterialDemo
};

💻 Animations et Interactions typescript

🔴 complex ⭐⭐⭐⭐

Animations avancées, interactions utilisateur, physique, systèmes de particules et expériences 3D interactives dans Babylon.js

⏱️ 45 min 🏷️ babylonjs, animation, physics, interaction
Prerequisites: Babylon.js basics, TypeScript ES6+, Animation principles
// Babylon.js Animations and Interactions Examples

import * as BABYLON from '@babylonjs/core';
import { GridMaterial } from '@babylonjs/materials/grid';
import '@babylonjs/loaders';

// 1. Advanced Animation System
class AdvancedAnimationSystem {
    private engine: BABYLON.Engine;
    private scene: BABYLON.Scene;
    private camera: BABYLON.ArcRotateCamera;
    private animations: Map<string, BABYLON.AnimationGroup> = new Map();

    constructor(canvas: HTMLCanvasElement) {
        this.engine = new BABYLON.Engine(canvas, true);
        this.scene = new BABYLON.Scene(this.engine);
        this.scene.clearColor = new BABYLON.Color4(0.05, 0.05, 0.15, 1);

        this.setupCamera();
        this.setupLighting();
        this.createAnimatedObjects();
        this.setupInteractionSystem();
        this.createAnimationUI();
        this.startRenderLoop();
    }

    private setupCamera(): void {
        this.camera = new BABYLON.ArcRotateCamera(
            'camera',
            0,
            Math.PI / 3,
            25,
            BABYLON.Vector3.Zero(),
            this.scene
        );
        this.camera.attachControl(canvas, true);
        this.camera.lowerRadiusLimit = 10;
        this.camera.upperRadiusLimit = 50;
    }

    private setupLighting(): void {
        const ambientLight = new BABYLON.HemisphericLight(
            'ambientLight',
            new BABYLON.Vector3(0, 1, 0),
            this.scene
        );
        ambientLight.intensity = 0.4;

        const mainLight = new BABYLON.DirectionalLight(
            'mainLight',
            new BABYLON.Vector3(-1, -2, 1),
            this.scene
        );
        mainLight.intensity = 2;
        mainLight.position = new BABYLON.Vector3(20, 40, 20);

        // Add animated point light
        const animatedLight = new BABYLON.PointLight(
            'animatedLight',
            new BABYLON.Vector3(0, 5, 0),
            this.scene
        );
        animatedLight.intensity = 1;
        animatedLight.diffuse = new BABYLON.Color3(1, 0.5, 0.2);

        // Animate light position
        const lightAnimation = BABYLON.Animation.CreateAndStartAnimation(
            'lightMove',
            animatedLight,
            'position',
            30,
            120,
            animatedLight.position.clone(),
            new BABYLON.Vector3(10, 5, 10),
            BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
        );
    }

    private createAnimatedObjects(): void {
        this.createRobotCharacter();
        this.createParticleEffects();
        this.createMorphingGeometry();
        this.createProceduralAnimations();
        this.createPhysicsAnimations();
        this.createSoundReactiveObjects();
    }

    private createRobotCharacter(): void {
        const robot = new BABYLON.TransformNode('robot');

        // Body
        const body = BABYLON.MeshBuilder.CreateBox('body', { width: 2, height: 3, depth: 1.5 }, this.scene);
        const bodyMaterial = new BABYLON.StandardMaterial('bodyMat', this.scene);
        bodyMaterial.diffuseColor = new BABYLON.Color3(0.7, 0.7, 0.8);
        bodyMaterial.specularColor = new BABYLON.Color3(0.2, 0.2, 0.3);
        body.material = bodyMaterial;
        body.parent = robot;

        // Head
        const head = BABYLON.MeshBuilder.CreateSphere('head', { diameter: 1.2 }, this.scene);
        head.position.y = 2.5;
        const headMaterial = new BABYLON.StandardMaterial('headMat', this.scene);
        headMaterial.diffuseColor = new BABYLON.Color3(0.9, 0.9, 0.9);
        head.material = headMaterial;
        head.parent = robot;

        // Eyes
        const leftEye = BABYLON.MeshBuilder.CreateSphere('leftEye', { diameter: 0.2 }, this.scene);
        leftEye.position.set(-0.3, 2.6, 0.5);
        const eyeMaterial = new BABYLON.StandardMaterial('eyeMat', this.scene);
        eyeMaterial.emissiveColor = new BABYLON.Color3(0, 1, 0);
        leftEye.material = eyeMaterial;
        leftEye.parent = robot;

        const rightEye = BABYLON.MeshBuilder.CreateSphere('rightEye', { diameter: 0.2 }, this.scene);
        rightEye.position.set(0.3, 2.6, 0.5);
        rightEye.material = eyeMaterial;
        rightEye.parent = robot;

        // Arms
        const leftArm = BABYLON.MeshBuilder.CreateBox('leftArm', { width: 0.4, height: 2.5, depth: 0.4 }, this.scene);
        leftArm.position.set(-1.5, 0.5, 0);
        const armMaterial = new BABYLON.StandardMaterial('armMat', this.scene);
        armMaterial.diffuseColor = new BABYLON.Color3(0.5, 0.5, 0.6);
        leftArm.material = armMaterial;
        leftArm.parent = robot;

        const rightArm = BABYLON.MeshBuilder.CreateBox('rightArm', { width: 0.4, height: 2.5, depth: 0.4 }, this.scene);
        rightArm.position.set(1.5, 0.5, 0);
        rightArm.material = armMaterial;
        rightArm.parent = robot;

        // Legs
        const leftLeg = BABYLON.MeshBuilder.CreateBox('leftLeg', { width: 0.5, height: 2, depth: 0.5 }, this.scene);
        leftLeg.position.set(-0.5, -2.5, 0);
        leftLeg.material = armMaterial;
        leftLeg.parent = robot;

        const rightLeg = BABYLON.MeshBuilder.CreateBox('rightLeg', { width: 0.5, height: 2, depth: 0.5 }, this.scene);
        rightLeg.position.set(0.5, -2.5, 0);
        rightLeg.material = armMaterial;
        rightLeg.parent = robot;

        robot.position.set(0, 2, 0);

        // Create walk animation group
        const walkAnimationGroup = new BABYLON.AnimationGroup('walk', this.scene);

        // Body bob animation
        const bodyBobAnimation = BABYLON.Animation.CreateAndStartAnimation(
            'bodyBob',
            robot,
            'position.y',
            30,
            30,
            2,
            2.5,
            BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
        );
        walkAnimationGroup.addTargetedAnimation(bodyBobAnimation, robot);

        // Arm swing animations
        const leftArmSwing = BABYLON.Animation.CreateAndStartAnimation(
            'leftArmSwing',
            leftArm,
            'rotation.z',
            30,
            30,
            0,
            Math.PI / 4,
            BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
        );
        walkAnimationGroup.addTargetedAnimation(leftArmSwing, leftArm);

        const rightArmSwing = BABYLON.Animation.CreateAndStartAnimation(
            'rightArmSwing',
            rightArm,
            'rotation.z',
            30,
            30,
            0,
            -Math.PI / 4,
            BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
        );
        walkAnimationGroup.addTargetedAnimation(rightArmSwing, rightArm);

        // Leg swing animations
        const leftLegSwing = BABYLON.Animation.CreateAndStartAnimation(
            'leftLegSwing',
            leftLeg,
            'rotation.x',
            30,
            30,
            0,
            Math.PI / 6,
            BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
        );
        walkAnimationGroup.addTargetedAnimation(leftLegSwing, leftLeg);

        const rightLegSwing = BABYLON.Animation.CreateAndStartAnimation(
            'rightLegSwing',
            rightLeg,
            'rotation.x',
            30,
            30,
            0,
            -Math.PI / 6,
            BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
        );
        walkAnimationGroup.addTargetedAnimation(rightLegSwing, rightLeg);

        // Head rotation
        const headLook = BABYLON.Animation.CreateAndStartAnimation(
            'headLook',
            head,
            'rotation.y',
            30,
            60,
            0,
            Math.PI * 2,
            BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
        );
        walkAnimationGroup.addTargetedAnimation(headLook, head);

        // Eye blinking
        const eyeBlink = BABYLON.Animation.CreateAndStartAnimation(
            'eyeBlink',
            leftEye,
            'scaling.y',
            30,
            90,
            1,
            0.1,
            BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
        );
        walkAnimationGroup.addTargetedAnimation(eyeBlink, leftEye);
        walkAnimationGroup.addTargetedAnimation(eyeBlink.clone(), rightEye);

        this.animations.set('walk', walkAnimationGroup);

        // Create jump animation
        const jumpAnimationGroup = new BABYLON.AnimationGroup('jump', this.scene);

        const jumpUp = BABYLON.Animation.CreateAndStartAnimation(
            'jumpUp',
            robot,
            'position.y',
            30,
            20,
            2,
            6,
            BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT
        );
        jumpAnimationGroup.addTargetedAnimation(jumpUp, robot);

        const jumpDown = BABYLON.Animation.CreateAndStartAnimation(
            'jumpDown',
            robot,
            'position.y',
            30,
            20,
            6,
            2,
            BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT
        );
        jumpDown.setKeys([
            { frame: 0, value: 2 },
            { frame: 20, value: 6 },
            { frame: 40, value: 2 }
        ]);

        jumpAnimationGroup.addTargetedAnimation(jumpDown, robot);

        this.animations.set('jump', jumpAnimationGroup);

        // Store robot reference for interaction
        robot.metadata = { type: 'robot', animations: ['walk', 'jump'] };
        this.scene.metadata = { robot, currentAnimation: 'walk' };
    }

    private createParticleEffects(): void {
        // Create multiple particle systems

        // Fire effect
        const fireSystem = new BABYLON.ParticleSystem('fire', 2000, this.scene);
        fireSystem.particleTexture = this.createFireTexture();
        fireSystem.emitter = new BABYLON.Vector3(-8, 0, 0);
        fireSystem.minEmitBox = new BABYLON.Vector3(-1, 0, -1);
        fireSystem.maxEmitBox = new BABYLON.Vector3(1, 0, 1);
        fireSystem.color1 = new BABYLON.Color4(1, 0.5, 0, 1);
        fireSystem.color2 = new BABYLON.Color4(1, 0, 0, 1);
        fireSystem.colorDead = new BABYLON.Color4(0, 0, 0, 0);
        fireSystem.minSize = 0.5;
        fireSystem.maxSize = 2;
        fireSystem.minLifeTime = 0.5;
        fireSystem.maxLifeTime = 2;
        fireSystem.emitRate = 500;
        fireSystem.blendMode = BABYLON.ParticleSystem.BLENDMODE_ADD;
        fireSystem.gravity = new BABYLON.Vector3(0, -2, 0);
        fireSystem.direction1 = new BABYLON.Vector3(-1, 2, -1);
        fireSystem.direction2 = new BABYLON.Vector3(1, 4, 1);
        fireSystem.minAngularSpeed = -1;
        fireSystem.maxAngularSpeed = 1;
        fireSystem.minEmitPower = 1;
        fireSystem.maxEmitPower = 3;
        fireSystem.updateSpeed = 0.01;
        fireSystem.start();

        // Snow effect
        const snowSystem = new BABYLON.ParticleSystem('snow', 1000, this.scene);
        snowSystem.particleTexture = new BABYLON.Texture('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==', this.scene);
        snowSystem.emitter = new BABYLON.Vector3(0, 20, 0);
        snowSystem.minEmitBox = new BABYLON.Vector3(-15, 0, -15);
        snowSystem.maxEmitBox = new BABYLON.Vector3(15, 0, 15);
        snowSystem.color1 = new BABYLON.Color4(1, 1, 1, 1);
        snowSystem.color2 = new BABYLON.Color4(0.8, 0.8, 1, 1);
        snowSystem.colorDead = new BABYLON.Color4(0, 0, 0, 0);
        snowSystem.minSize = 0.2;
        snowSystem.maxSize = 0.8;
        snowSystem.minLifeTime = 5;
        snowSystem.maxLifeTime = 15;
        snowSystem.emitRate = 50;
        snowSystem.blendMode = BABYLON.ParticleSystem.BLENDMODE_ONEONE;
        snowSystem.gravity = new BABYLON.Vector3(0, -0.5, 0);
        snowSystem.direction1 = new BABYLON.Vector3(-0.5, -1, -0.5);
        snowSystem.direction2 = new BABYLON.Vector3(0.5, -1, 0.5);
        snowSystem.minAngularSpeed = 0;
        snowSystem.maxAngularSpeed = Math.PI;
        snowSystem.minEmitPower = 0.5;
        snowSystem.maxEmitPower = 1.5;
        snowSystem.start();

        // Magic portal effect
        const portalSystem = new BABYLON.ParticleSystem('portal', 1500, this.scene);
        portalSystem.particleTexture = this.createPortalTexture();
        portalSystem.emitter = new BABYLON.Vector3(8, 2, 0);
        portalSystem.minEmitBox = new BABYLON.Vector3(-1, 0, -1);
        portalSystem.maxEmitBox = new BABYLON.Vector3(1, 4, 1);
        portalSystem.color1 = new BABYLON.Color4(0.5, 0, 1, 1);
        portalSystem.color2 = new BABYLON.Color4(1, 0, 1, 1);
        portalSystem.colorDead = new BABYLON.Color4(0, 0, 0.5, 0);
        portalSystem.minSize = 0.3;
        portalSystem.maxSize = 1;
        portalSystem.minLifeTime = 1;
        portalSystem.maxLifeTime = 3;
        portalSystem.emitRate = 300;
        portalSystem.blendMode = BABYLON.ParticleSystem.BLENDMODE_ADD;
        portalSystem.gravity = new BABYLON.Vector3(0, 0, 0);
        portalSystem.direction1 = new BABYLON.Vector3(-2, 2, -2);
        portalSystem.direction2 = new BABYLON.Vector3(2, 2, 2);
        portalSystem.minAngularSpeed = -Math.PI;
        portalSystem.maxAngularSpeed = Math.PI;
        portalSystem.minEmitPower = 0.5;
        portalSystem.maxEmitPower = 2;
        portalSystem.start();

        // Create platform for effects
        const platform1 = BABYLON.MeshBuilder.CreateCylinder(
            'firePlatform',
            { diameter: 3, height: 0.5 },
            this.scene
        );
        platform1.position.set(-8, -0.25, 0);
        const platformMaterial1 = new BABYLON.StandardMaterial('firePlatformMat', this.scene);
        platformMaterial1.diffuseColor = new BABYLON.Color3(0.3, 0.1, 0);
        platformMaterial1.emissiveColor = new BABYLON.Color3(0.2, 0.05, 0);
        platform1.material = platformMaterial1;

        const portalFrame = BABYLON.MeshBuilder.CreateTorus(
            'portalFrame',
            { diameter: 4, thickness: 0.3, tessellation: 32 },
            this.scene
        );
        portalFrame.position.set(8, 2, 0);
        const portalMaterial = new BABYLON.StandardMaterial('portalMat', this.scene);
        portalMaterial.diffuseColor = new BABYLON.Color3(0.5, 0, 1);
        portalMaterial.emissiveColor = new BABYLON.Color3(0.3, 0, 0.6);
        portalMaterial.specularColor = new BABYLON.Color3(1, 0, 1);
        portalFrame.material = portalMaterial;

        // Animate portal frame
        this.scene.registerBeforeRender(() => {
            portalFrame.rotation.y += 0.02;
            portalFrame.rotation.x = Math.sin(Date.now() * 0.001) * 0.1;
        });
    }

    private createMorphingGeometry(): void {
        // Create morphing sphere
        const sphere = BABYLON.MeshBuilder.CreateSphere(
            'morphingSphere',
            { diameter: 3, segments: 32 },
            this.scene
        );
        sphere.position.set(0, 5, 0);

        const sphereMaterial = new BABYLON.StandardMaterial('morphingSphereMat', this.scene);
        sphereMaterial.diffuseColor = new BABYLON.Color3(0.5, 0.8, 1);
        sphereMaterial.specularColor = new BABYLON.Color3(1, 1, 1);
        sphereMaterial.specularPower = 128;
        sphereMaterial.emissiveColor = new BABYLON.Color3(0.1, 0.2, 0.3);
        sphere.material = sphereMaterial;

        // Store original positions
        const positions = sphere.getVerticesData(BABYLON.VertexBuffer.PositionKind);
        const originalPositions = new Float32Array(positions!);

        // Animate morphing
        this.scene.registerBeforeRender(() => {
            const time = Date.now() * 0.001;
            const newPositions = new Float32Array(positions!.length);

            for (let i = 0; i < positions!.length; i += 3) {
                const x = originalPositions[i];
                const y = originalPositions[i + 1];
                const z = originalPositions[i + 2];

                // Apply wave deformation
                const wave = Math.sin(x * 2 + time * 2) * Math.cos(z * 2 + time * 3);
                const pulse = Math.sin(time * 2) * 0.1;

                newPositions[i] = x * (1 + pulse + wave * 0.1);
                newPositions[i + 1] = y * (1 + pulse) + Math.sin(time * 3 + x * 3) * 0.2;
                newPositions[i + 2] = z * (1 + pulse + wave * 0.1);
            }

            sphere.updateVerticesData(BABYLON.VertexBuffer.PositionKind, newPositions);
        });
    }

    private createProceduralAnimations(): void {
        // Create animated grid of cubes
        const gridSize = 5;
        const spacing = 2;
        const cubes: BABYLON.Mesh[] = [];

        for (let x = 0; x < gridSize; x++) {
            for (let z = 0; z < gridSize; z++) {
                const cube = BABYLON.MeshBuilder.CreateBox(
                    `cube${x}_${z}`,
                    { size: 1 },
                    this.scene
                );
                cube.position.set(
                    (x - gridSize / 2) * spacing,
                    0,
                    (z - gridSize / 2) * spacing
                );

                const material = new BABYLON.StandardMaterial(`cubeMat${x}_${z}`, this.scene);
                const hue = (x + z) / (gridSize * 2);
                material.diffuseColor = BABYLON.Color3.FromHSV(hue * 360, 1, 1);
                material.emissiveColor = material.diffuseColor.scale(0.3);
                cube.material = material;

                cubes.push(cube);
            }
        }

        // Animate the grid
        this.scene.registerBeforeRender(() => {
            const time = Date.now() * 0.001;

            cubes.forEach((cube, index) => {
                const x = index % gridSize;
                const z = Math.floor(index / gridSize);

                // Create wave effect
                const distance = Math.sqrt(
                    Math.pow(x - gridSize / 2, 2) +
                    Math.pow(z - gridSize / 2, 2)
                );

                const wave = Math.sin(time * 2 - distance * 0.5) * 0.5 + 0.5;
                cube.position.y = wave * 3;
                cube.rotation.y = time + index * 0.1;
                cube.scaling = cube.scaling.scale(1 + wave * 0.3);

                // Change material properties based on animation
                const mat = cube.material as BABYLON.StandardMaterial;
                mat.emissiveColor = mat.diffuseColor!.scale(wave * 0.5);
            });
        });
    }

    private createPhysicsAnimations(): void {
        // Enable physics
        this.scene.enablePhysics(
            new BABYLON.Vector3(0, -9.81, 0),
            new BABYLON.CannonJSPlugin()
        );

        // Create physics playground
        const ground = BABYLON.MeshBuilder.CreateGround('physicsGround', { width: 20, height: 20 }, this.scene);
        ground.position.set(-15, 0, 0);
        const groundMaterial = new BABYLON.StandardMaterial('physicsGroundMat', this.scene);
        groundMaterial.diffuseColor = new BABYLON.Color3(0.3, 0.3, 0.3);
        ground.material = groundMaterial;
        ground.physicsImpostor = new BABYLON.PhysicsImpostor(
            ground,
            BABYLON.PhysicsImpostor.BoxImpostor,
            { mass: 0, restitution: 0.8 },
            this.scene
        );

        // Create walls
        this.createPhysicsWalls();

        // Create physics objects periodically
        this.physicsSpawnTimer = 0;
        this.scene.registerBeforeRender(() => {
            this.physicsSpawnTimer += this.engine.getDeltaTime() / 1000;
            if (this.physicsSpawnTimer > 1) {
                this.spawnPhysicsObject();
                this.physicsSpawnTimer = 0;
            }
        });
    }

    private createPhysicsWalls(): void {
        const wallMaterial = new BABYLON.StandardMaterial('wallMat', this.scene);
        wallMaterial.diffuseColor = new BABYLON.Color3(0.6, 0.6, 0.6);
        wallMaterial.alpha = 0.5;

        // Create invisible walls
        const walls = [
            { position: new BABYLON.Vector3(-25, 5, 0), size: new BABYLON.Vector3(1, 10, 20) },
            { position: new BABYLON.Vector3(-5, 5, -10), size: new BABYLON.Vector3(20, 10, 1) },
            { position: new BABYLON.Vector3(-5, 5, 10), size: new BABYLON.Vector3(20, 10, 1) }
        ];

        walls.forEach(wallData => {
            const wall = BABYLON.MeshBuilder.CreateBox(
                `wall${walls.indexOf(wallData)}`,
                { width: wallData.size.x, height: wallData.size.y, depth: wallData.size.z },
                this.scene
            );
            wall.position = wallData.position;
            wall.material = wallMaterial;
            wall.physicsImpostor = new BABYLON.PhysicsImpostor(
                wall,
                BABYLON.PhysicsImpostor.BoxImpostor,
                { mass: 0, restitution: 0.5 },
                this.scene
            );
        });
    }

    private spawnPhysicsObject(): void {
        const shapes = ['box', 'sphere', 'cylinder'];
        const shape = shapes[Math.floor(Math.random() * shapes.length)];
        let mesh: BABYLON.Mesh;

        const position = new BABYLON.Vector3(
            -15 + (Math.random() - 0.5) * 10,
            5 + Math.random() * 5,
            (Math.random() - 0.5) * 5
        );

        switch (shape) {
            case 'box':
                mesh = BABYLON.MeshBuilder.CreateBox(`physicsBox${Date.now()}`, { size: 1 }, this.scene);
                break;
            case 'sphere':
                mesh = BABYLON.MeshBuilder.CreateSphere(`physicsSphere${Date.now()}`, { diameter: 1 }, this.scene);
                break;
            case 'cylinder':
                mesh = BABYLON.MeshBuilder.CreateCylinder(`physicsCylinder${Date.now()}`, { height: 1, diameter: 1 }, this.scene);
                break;
        }

        mesh.position = position;

        const material = new BABYLON.StandardMaterial(`physicsMat${Date.now()}`, this.scene);
        const hue = Math.random();
        material.diffuseColor = BABYLON.Color3.FromHSV(hue * 360, 1, 1);
        material.emissiveColor = material.diffuseColor.scale(0.3);
        mesh.material = material;

        // Add physics
        mesh.physicsImpostor = new BABYLON.PhysicsImpostor(
            mesh,
            shape === 'sphere' ? BABYLON.PhysicsImpostor.SphereImpostor : BABYLON.PhysicsImpostor.BoxImpostor,
            { mass: 1, restitution: 0.7, friction: 0.5 },
            this.scene
        );

        // Apply random impulse
        if (mesh.physicsImpostor) {
            const impulse = new BABYLON.Vector3(
                (Math.random() - 0.5) * 10,
                Math.random() * 5,
                (Math.random() - 0.5) * 10
            );
            mesh.physicsImpostor.applyImpulse(impulse, mesh.position);
        }
    }

    private createSoundReactiveObjects(): void {
        // Create animated spheres that react to "audio" (simulated)
        const audioReactiveSpheres: BABYLON.Mesh[] = [];
        const sphereCount = 8;

        for (let i = 0; i < sphereCount; i++) {
            const sphere = BABYLON.MeshBuilder.CreateSphere(
                `audioSphere${i}`,
                { diameter: 1.5, segments: 16 },
                this.scene
            );

            const angle = (i / sphereCount) * Math.PI * 2;
            sphere.position.set(
                Math.cos(angle) * 6,
                2,
                Math.sin(angle) * 6 + 10
            );

            const material = new BABYLON.StandardMaterial(`audioMat${i}`, this.scene);
            material.diffuseColor = BABYLON.Color3.FromHSV((i / sphereCount) * 360, 1, 1);
            material.emissiveColor = new BABYLON.Color3(0, 0, 0);
            sphere.material = material;

            audioReactiveSpheres.push(sphere);
        }

        // Simulate audio reaction
        this.scene.registerBeforeRender(() => {
            const time = Date.now() * 0.001;

            audioReactiveSpheres.forEach((sphere, index) => {
                // Simulate frequency bands
                const frequency = Math.sin(time * (index + 1) * 0.5) * 0.5 + 0.5;
                const bass = Math.sin(time * 0.8) * 0.5 + 0.5;

                // Scale based on simulated audio
                const scale = 1 + frequency * 2;
                sphere.scaling = new BABYLON.Vector3(scale, scale, scale);

                // Change color intensity
                const mat = sphere.material as BABYLON.StandardMaterial;
                const intensity = bass * 0.8;
                mat.emissiveColor = mat.diffuseColor!.scale(intensity);

                // Move up and down
                sphere.position.y = 2 + Math.sin(time * (index + 1) * 0.3) * 1.5;
            });
        });
    }

    private setupInteractionSystem(): void {
        // Ray casting for object selection
        this.scene.onPointerObservable.add((pointerInfo) => {
            if (pointerInfo.type === BABYLON.PointerEventTypes.POINTERDOWN) {
                const pickResult = this.scene.pick(pointerInfo.event.offsetX, pointerInfo.event.offsetY);

                if (pickResult && pickResult.hit && pickResult.pickedMesh) {
                    this.handleObjectInteraction(pickResult.pickedMesh);
                }
            }
        });

        // Keyboard controls
        window.addEventListener('keydown', (event) => {
            switch (event.key.toLowerCase()) {
                case '1':
                    this.switchAnimation('walk');
                    break;
                case '2':
                    this.switchAnimation('jump');
                    break;
                case ' ':
                    this.createExplosion(this.camera.position.add(this.camera.getDirection(new BABYLON.Vector3(0, 0, 1)).scale(10)));
                    break;
            }
        });
    }

    private handleObjectInteraction(mesh: BABYLON.Mesh): void {
        // Visual feedback
        this.createClickEffect(mesh.position);

        // Add impulse if physics object
        if (mesh.physicsImpostor && mesh.physicsImpostor.mass > 0) {
            const direction = this.camera.position.subtract(mesh.position).normalize();
            const impulse = direction.scale(-10);
            mesh.physicsImpostor.applyImpulse(impulse, mesh.position);
        }

        // Scale animation for non-physics objects
        if (!mesh.physicsImpostor || mesh.physicsImpostor.mass === 0) {
            BABYLON.Animation.CreateAndStartAnimation(
                'clickEffect',
                mesh,
                'scaling',
                30,
                10,
                mesh.scaling.clone(),
                mesh.scaling.scale(1.5),
                BABYLON.Animation.ANIMATIONLOOPMODE_YOYO
            );
        }
    }

    private createClickEffect(position: BABYLON.Vector3): void {
        const particleSystem = new BABYLON.ParticleSystem('clickEffect', 100, this.scene);
        particleSystem.particleTexture = new BABYLON.Texture('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==', this.scene);

        particleSystem.emitter = position;
        particleSystem.minEmitBox = new BABYLON.Vector3(-0.1, -0.1, -0.1);
        particleSystem.maxEmitBox = new BABYLON.Vector3(0.1, 0.1, 0.1);

        particleSystem.color1 = new BABYLON.Color4(1, 1, 1, 1);
        particleSystem.color2 = new BABYLON.Color4(1, 1, 0, 1);
        particleSystem.colorDead = new BABYLON.Color4(1, 0, 0, 0);

        particleSystem.minSize = 0.1;
        particleSystem.maxSize = 0.3;

        particleSystem.minLifeTime = 0.5;
        particleSystem.maxLifeTime = 1.5;

        particleSystem.emitRate = 100;
        particleSystem.blendMode = BABYLON.ParticleSystem.BLENDMODE_ADD;
        particleSystem.gravity = new BABYLON.Vector3(0, -1, 0);
        particleSystem.direction1 = new BABYLON.Vector3(-1, 1, -1);
        particleSystem.direction2 = new BABYLON.Vector3(1, 1, 1);

        particleSystem.start();

        setTimeout(() => {
            particleSystem.stop();
        }, 100);
    }

    private createExplosion(position: BABYLON.Vector3): void {
        const explosionSystem = new BABYLON.ParticleSystem('explosion', 500, this.scene);
        explosionSystem.particleTexture = this.createExplosionTexture();

        explosionSystem.emitter = position;
        explosionSystem.minEmitBox = new BABYLON.Vector3(-1, -1, -1);
        explosionSystem.maxEmitBox = new BABYLON.Vector3(1, 1, 1);

        explosionSystem.color1 = new BABYLON.Color4(1, 1, 0, 1);
        explosionSystem.color2 = new BABYLON.Color4(1, 0.5, 0, 1);
        explosionSystem.colorDead = new BABYLON.Color4(1, 0, 0, 0);

        explosionSystem.minSize = 0.5;
        explosionSystem.maxSize = 2;

        explosionSystem.minLifeTime = 0.5;
        explosionSystem.maxLifeTime = 2;

        explosionSystem.emitRate = 500;
        explosionSystem.blendMode = BABYLON.ParticleSystem.BLENDMODE_ADD;
        explosionSystem.gravity = new BABYLON.Vector3(0, -5, 0);
        explosionSystem.direction1 = new BABYLON.Vector3(-2, 2, -2);
        explosionSystem.direction2 = new BABYLON.Vector3(2, 5, 2);

        explosionSystem.start();

        // Apply explosion force to nearby physics objects
        this.scene.meshes.forEach(mesh => {
            if (mesh.physicsImpostor && mesh.physicsImpostor.mass > 0) {
                const distance = BABYLON.Vector3.Distance(mesh.position, position);
                if (distance < 10) {
                    const direction = mesh.position.subtract(position).normalize();
                    const force = direction.scale(50 / (distance + 1));
                    mesh.physicsImpostor.applyImpulse(force, mesh.position);
                }
            }
        });
    }

    private switchAnimation(animationName: string): void {
        const animation = this.animations.get(animationName);
        if (animation) {
            // Stop all other animations
            this.animations.forEach((anim) => {
                if (anim !== animation) {
                    anim.stop();
                }
            });

            // Start the requested animation
            animation.reset();
            animation.play(true);

            // Update current animation display
            if (this.scene.metadata.currentAnimationText) {
                this.scene.metadata.currentAnimationText.text = `Current Animation: ${animationName}`;
            }
        }
    }

    private createAnimationUI(): void {
        const advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI('UI');

        // Instructions panel
        const instructions = new BABYLON.GUI.TextBlock();
        instructions.text = 'Click objects to interact | 1: Walk Animation | 2: Jump Animation | Space: Explosion';
        instructions.color = 'white';
        instructions.fontSize = 14;
        instructions.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER;
        instructions.textVerticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
        instructions.top = '20px';
        advancedTexture.addControl(instructions);

        // Current animation display
        const currentAnimText = new BABYLON.GUI.TextBlock();
        currentAnimText.text = 'Current Animation: walk';
        currentAnimText.color = 'yellow';
        currentAnimText.fontSize = 16;
        currentAnimText.fontWeight = 'bold';
        currentAnimText.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_CENTER;
        currentAnimText.textVerticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
        currentAnimText.top = '50px';
        advancedTexture.addControl(currentAnimText);

        this.scene.metadata.currentAnimationText = currentAnimText;

        // Performance monitor
        const perfText = new BABYLON.GUI.TextBlock();
        perfText.text = 'FPS: --';
        perfText.color = 'white';
        perfText.fontSize = 12;
        perfText.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
        perfText.textVerticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_BOTTOM;
        perfText.left = '20px';
        perfText.top = '-40px';
        advancedTexture.addControl(perfText);

        // Update FPS counter
        let lastTime = performance.now();
        let frames = 0;

        this.scene.registerBeforeRender(() => {
            frames++;
            const currentTime = performance.now();

            if (currentTime >= lastTime + 1000) {
                const fps = Math.round((frames * 1000) / (currentTime - lastTime));
                perfText.text = `FPS: ${fps} | Meshes: ${this.scene.meshes.length} | Particles: Active`;

                frames = 0;
                lastTime = currentTime;
            }
        });
    }

    // Texture creation helper methods
    private createFireTexture(): BABYLON.Texture {
        return new BABYLON.Texture('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==', this.scene);
    }

    private createPortalTexture(): BABYLON.Texture {
        return new BABYLON.Texture('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==', this.scene);
    }

    private createExplosionTexture(): BABYLON.Texture {
        return new BABYLON.Texture('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==', this.scene);
    }

    private startRenderLoop(): void {
        this.engine.runRenderLoop(() => {
            this.scene.render();
        });
    }
}

export {
    AdvancedAnimationSystem
};