<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
     <title>Babylon.js Tetris Demo by Hitesh Sahu</title>
    </head>
<body>
    <canvas id="renderCanvas"></canvas>
</body>
html, body {
  overflow: hidden;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

#renderCanvas {
  width: 100%;
  height: 100%;
  touch-action: none;
}
 const BOX_SIZE = 1
 const BOX_DEPTH = 4
 const GROUND_SIZE = 30
 const colors = {
   seaFoam: BABYLON.Color3.FromHexString("#16a085"),
   green: BABYLON.Color3.FromHexString("#27ae60"),
   blue: BABYLON.Color3.FromHexString("#2980b9"),
   purple: BABYLON.Color3.FromHexString("#8e44ad"),
   navy: BABYLON.Color3.FromHexString("#2c3e50"),
   yellow: BABYLON.Color3.FromHexString("#f39c12"),
   orange: BABYLON.Color3.FromHexString("#d35400"),
   red: BABYLON.Color3.FromHexString("#c0392b"),
   white: BABYLON.Color3.FromHexString("#bdc3c7"),
   gray: BABYLON.Color3.FromHexString("#7f8c8d")
 }

 const SHAPES = ["I", "O", "S", "T", "L"]
 var BODIES = { "I": {}, "O": {}, "S": {}, "T": {}, "L": {} }
           
var canvas = document.getElementById("renderCanvas");
        var engine = null;
        var scene = null;
        var sceneToRender = null;
        var createDefaultEngine = ()=>{ 
          return new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil:true}); };
       
        var createScene = async  ()=> {
            var scene = new BABYLON.Scene(engine);
            scene.clearColor = colors.navy;
          
                        await Ammo();

            scene.enablePhysics(null, new BABYLON.AmmoJSPlugin());
            //CAMERA
            var camera = new BABYLON.ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 2.5, 25, BABYLON.Vector3.Zero(), scene); 
            camera.attachControl(canvas, true);
            camera.setPosition(new BABYLON.Vector3(GROUND_SIZE / 2, GROUND_SIZE / 2, GROUND_SIZE / 2))
            //LIGHT
            var light = new BABYLON.DirectionalLight("dir02", new BABYLON.Vector3(0.2, -1, 0), scene);
            light.position = new BABYLON.Vector3(0, GROUND_SIZE, 0);
            light.intensity = 0.9
            shadowGenerator = new BABYLON.ShadowGenerator(2048, light);
        
           //MATERIAL
            BODIES["O"].material = createMat(scene, colors.purple)
            BODIES["I"].material = createMat(scene, colors.red)
            BODIES["L"].material = createMat(scene, colors.yellow)
            BODIES["T"].material = createMat(scene, colors.green)
            BODIES["S"].material = createMat(scene, colors.orange)
        
            var grass = new BABYLON.StandardMaterial("grass", scene);
            grass.diffuseTexture = new BABYLON.Texture("https://images.pexels.com/photos/3038740/pexels-photo-3038740.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500");
            grass.diffuseTexture.uScale = 5;
            grass.diffuseTexture.vScale = 5;
        
            var ground = BABYLON.MeshBuilder.CreateBox("ground", { width: GROUND_SIZE, depth: GROUND_SIZE, height: 1 }, scene);
            ground.material = grass;
            ground.receiveShadows = true;
            ground.physicsImpostor = new BABYLON.PhysicsImpostor(ground, BABYLON.PhysicsImpostor.BoxImpostor, { mass: 0, friction: 0.5, restitution: 0 }, scene);
      
              var ticker = 0;
        
                scene.registerBeforeRender(function() {
                    if((ticker++ % 60)) return;
                    for (var ii = 0; ii < 10; ii++) {
                     let nextChar = SHAPES[randomInteger(0, SHAPES.length)]
                                getBody(nextChar, scene, new BABYLON.Vector3(randomInteger(-GROUND_SIZE/2,GROUND_SIZE/2), 
                                GROUND_SIZE/2, 
                                randomInteger(-GROUND_SIZE/2,GROUND_SIZE/2)))
                    }
                });
                scene.registerBeforeRender( ()=> {
                    scene.meshes.forEach( (m)=> {
                        if (m.name=="s" && m.position.y < -10) {
                            m.dispose();
                        }
                    })
                });
          
            
          scene.onKeyboardObservable.add((kbInfo) => {
            switch (kbInfo.type) {
              case BABYLON.KeyboardEventTypes.KEYDOWN:
                switch (kbInfo.event.keyCode) {
                  case 65:  //A
                    // nextShape.position.z -= 0.1;
                    break
                  case 68: //D
                    // nextShape.position.z += 0.1;
                    break
                  case 87://"W":
                    // nextShape.position.y += 0.1;
                    break
                  case 83: // "S":
                    // nextShape.position.y -= 0.1;
                    break
                  case 32: // "SPACE":
                    let nextChar = SHAPES[randomInteger(0, SHAPES.length)]
                    let nextShape = getBody(nextChar, scene, new BABYLON.Vector3(randomInteger(-GROUND_SIZE/4,GROUND_SIZE/4), 
                                                                                 50, randomInteger(-GROUND_SIZE/4,GROUND_SIZE/4)))
                    // nextShape.rotation.x += Math.PI / 2;
                    break
                }
                break;
            }
          });

            return scene;
        };
        
        var getBody = (nextChar, scene, position = new BABYLON.Vector3(0, 50, 0)) => {
        
            let body
            if (BODIES[nextChar].mesh) {
                body = BODIES[nextChar].mesh.clone("s")
            } else {
                let shape = getShape(nextChar, scene)
                BODIES[nextChar].mesh = shape
                body = BODIES[nextChar].mesh.clone("s")
                body.isVisible = true
            }
  
            body.position = position
            shadowGenerator.addShadowCaster(body);
            return body
        }
        
        var getShape = (shape, scene) => {
        
            switch (shape) {
                case "O":
                    var boxO = BABYLON.MeshBuilder.CreateBox("boxO", { height: BOX_DEPTH/2, width: BOX_SIZE, depth: BOX_DEPTH/2 }, scene);
                    boxO.physicsImpostor = new BABYLON.PhysicsImpostor(boxO, BABYLON.PhysicsImpostor.BoxImpostor, { mass: 1 }, scene);
                    boxO.material = BODIES[shape].material
                    return boxO
        
                case "I":
                    var boxI = BABYLON.MeshBuilder.CreateBox("boxI", { height: BOX_DEPTH, width: BOX_SIZE, depth: BOX_SIZE }, scene);
                    boxI.physicsImpostor = new BABYLON.PhysicsImpostor(boxI, BABYLON.PhysicsImpostor.BoxImpostor, { mass: 1 }, scene);
                    boxI.material = BODIES[shape].material
        
                    return boxI
        
                case "L":
        
                    var boxL = BABYLON.MeshBuilder.CreateBox("boxL", { height: BOX_SIZE, width: BOX_SIZE, depth: BOX_DEPTH }, scene);
                    var boxLTop = BABYLON.MeshBuilder.CreateBox("boxLTop", { height: BOX_SIZE, width: BOX_SIZE, depth: BOX_SIZE }, scene);
                    boxLTop.position.y = BOX_SIZE
                    boxLTop.position.z = BOX_SIZE * 2
                    boxLTop.parent = boxL
        
                    boxLTop.physicsImpostor = new BABYLON.PhysicsImpostor(boxLTop, BABYLON.PhysicsImpostor.BoxImpostor, { mass: 1 }, scene);
                    boxL.physicsImpostor = new BABYLON.PhysicsImpostor(boxL, BABYLON.PhysicsImpostor.BoxImpostor, { mass: 1 }, scene);
                    boxL.material = BODIES[shape].material
                    boxLTop.material = BODIES[shape].material
        
                    return boxL
                    /** 
                    return  BABYLON.Mesh.MergeMeshes([boxL, boxLTop])
                    */
        
                    var subCSG = BABYLON.CSG.FromMesh(boxL).subtract(BABYLON.CSG.FromMesh(boxLTop));
                    var newMesh = subCSG.toMesh("csg", materials[shape], scene);
                    newMesh.position = new BABYLON.Vector3(10, 0, 0);
                    return newMesh
        
                case "T":
                    var boxT = BABYLON.MeshBuilder.CreateBox("boxT", { height: BOX_SIZE, width: BOX_SIZE, depth: BOX_DEPTH }, scene);
                    var boxTBottom = BABYLON.MeshBuilder.CreateBox("box", { height: BOX_SIZE, width: BOX_SIZE, depth: BOX_SIZE }, scene);
                    boxTBottom.position.y = -BOX_SIZE
                    boxTBottom.parent = boxT
        
                    boxT.physicsImpostor = new BABYLON.PhysicsImpostor(boxT, BABYLON.PhysicsImpostor.BoxImpostor, { mass: 1 }, scene);
                    boxTBottom.physicsImpostor = new BABYLON.PhysicsImpostor(boxTBottom, BABYLON.PhysicsImpostor.BoxImpostor, { mass: 1 }, scene);
        
                    boxT.material = BODIES[shape].material
                    boxTBottom.material = BODIES[shape].material
                    return boxT //BABYLON.Mesh.MergeMeshes([boxT, boxTBottom])
        
                case "S":
                    var boxS = BABYLON.MeshBuilder.CreateBox("boxS", { height: BOX_SIZE, width: BOX_SIZE, depth: BOX_DEPTH / 2 }, scene);
                    var boxSTop = BABYLON.MeshBuilder.CreateBox("boxS", { height: BOX_SIZE, width: BOX_SIZE, depth: BOX_DEPTH / 2 }, scene);
                    boxSTop.position.y = BOX_SIZE
                    boxSTop.position.z = BOX_SIZE
                    boxSTop.parent = boxS
        
                    boxS.physicsImpostor = new BABYLON.PhysicsImpostor(boxS, BABYLON.PhysicsImpostor.BoxImpostor, { mass: 1 }, scene);
                    boxSTop.physicsImpostor = new BABYLON.PhysicsImpostor(boxSTop, BABYLON.PhysicsImpostor.BoxImpostor, { mass: 1 }, scene);
        
                    boxS.material = BODIES[shape].material
                    boxSTop.material = BODIES[shape].material
                    return boxS
        
                //return BABYLON.Mesh.MergeMeshes([boxS, boxSTop])
            }
        }
        
         randomInteger=(min, max)=> {
            return Math.floor(Math.random() * (max-min)) + min
        }
        
        var createMat = (scene, color) => {
            var mat = new BABYLON.StandardMaterial("", scene);
            mat.diffuseColor = color;
            mat.specularColor = BABYLON.Color3.FromHexString("#555555");
            mat.specularPower = 1;
            mat.emissiveColor = color // color.clone().scale(0.7);
            mat.backFaceCulling = false;
            return mat;
        }
        
        
    var engine;
    try {
    engine = createDefaultEngine();
    } catch(e) {
    console.log("the available createEngine function failed. Creating the default engine instead");
    engine = createDefaultEngine();
    }
        if (!engine) throw 'engine should not be null.';
        scene = createScene();;
        scene.then(returnedScene => { sceneToRender = returnedScene; });
        

        engine.runRenderLoop(function () {
            if (sceneToRender && sceneToRender.activeCamera) {
                sceneToRender.render();
            }
        });

        // Resize
        window.addEventListener("resize", function () {
            engine.resize();
        });

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://code.jquery.com/pep/0.4.2/pep.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js
  3. https://preview.babylonjs.com/ammo.js
  4. https://preview.babylonjs.com/cannon.js
  5. https://preview.babylonjs.com/Oimo.js
  6. https://preview.babylonjs.com/earcut.min.js
  7. https://preview.babylonjs.com/babylon.js
  8. https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js
  9. https://preview.babylonjs.com/proceduralTexturesLibrary/babylonjs.proceduralTextures.min.js
  10. https://preview.babylonjs.com/postProcessesLibrary/babylonjs.postProcess.min.js
  11. https://preview.babylonjs.com/loaders/babylonjs.loaders.js
  12. https://preview.babylonjs.com/serializers/babylonjs.serializers.min.js
  13. https://preview.babylonjs.com/gui/babylon.gui.min.js
  14. https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js