Exemples Babylon.js

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

Key Facts

Category
3D Graphics
Items
3
Format Families
text

Sample Overview

Exemples du moteur 3D Babylon.js incluant scènes, meshes, matériaux, éclairage, physique et applications web 3D interactives This sample set belongs to 3D Graphics and can be used to test related workflows inside Elysia Tools.

💻 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(this.engine.getRenderingCanvas(), 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(this.engine.getRenderingCanvas(), 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(this.engine.getRenderingCanvas(), 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(this.engine.getRenderingCanvas(), 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(this.engine.getRenderingCanvas(), 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
};