<head>
  <link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@500&display=swap" rel="stylesheet">
  <title>Bell Curve Simution</title>
  <!-- Babylon.js -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.2/dat.gui.min.js"></script>
  <script src="https://preview.babylonjs.com/ammo.js"></script>
  <script src="https://preview.babylonjs.com/cannon.js"></script>
  <script src="https://preview.babylonjs.com/Oimo.js"></script>
  <script src="https://preview.babylonjs.com/earcut.min.js"></script>
  <script src="https://preview.babylonjs.com/babylon.js"></script>
  <script src="https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.min.js"></script>
  <script src="https://preview.babylonjs.com/proceduralTexturesLibrary/babylonjs.proceduralTextures.min.js"></script>
  <script src="https://preview.babylonjs.com/postProcessesLibrary/babylonjs.postProcess.min.js"></script>
  <script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.js"></script>
  <script src="https://preview.babylonjs.com/serializers/babylonjs.serializers.min.js"></script>
  <script src="https://preview.babylonjs.com/gui/babylon.gui.min.js"></script>
  <script src="https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js"></script>

</head>

<body>
  <div class="container">
    <div class="container"><canvas id="renderCanvas" class="container" /></div>
    <div class="footer"><a href="https://hiteshsahu.com/lab">See More in Lab 🧪</a></div>
    <div class="header">
      <h2>Ammo JS Physics Swing<h2>
    </div>
  </div>

  <div id="fps">0</div>

</body>
<link href="https://fonts.googleapis.com/css2?family=Lobster&display=swap" rel="stylesheet">

#renderCanvas {
  position: absolute;
  width: 100%;
  height: 100%;
  touch-action: none;
  left: 0%;
  right: 0%;
  bottom: 0%;
  top: 0%;
}

html,
body {
  overflow: hidden;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  font-family: "Rajdhani", sans-serif;
  color: white;
  font-size: large;
}

a:link,
a:visited {
  background-color: #f44336;
  color: white;
  padding: 14px 25px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
}

.container {
  position: relative;
  height: 100%;
  width: 100%;
}

.header {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  padding-left: 10px;
  text-align: center;
}

.footer {
  position: absolute;
  right: 0;
  bottom: 0;
  margin: 5px;
  padding: 5px;
}

a:hover,
a:active {
  background-color: red;
}

.center {
  position: absolute;
  background-color: #00bfa5;
  left: 0%;
  right: 0%;
  bottom: 0%;
  top: 0%;
  margin: 10px;
  padding: 10px;
}

#fps {
  position: absolute;
  background-color: black;
  border: 2px solid red;
  text-align: center;
  font-size: 16px;
  color: white;
  top: 15px;
  right: 10px;
  width: 60px;
  height: 20px;
}
const TEXTURE_FOLDER = "https://www.babylonjs-playground.com/textures/";

const GROUND_SIZE = 50;
const SKYBOX_SIZE = GROUND_SIZE * 10.0;

const nbPoints = 10;
const ropeLength = 35;
const radius = 0.2;

// States
var g_timer = null;
var shouldAnimateCamera = true;
let config = {
  animateCamera: true
};

// Views
const divFps = document.getElementById("fps");
const canvas = document.getElementById("renderCanvas");

async function loadDemo() {
  // Essential
  const engine = new BABYLON.Engine(canvas, true, {
    preserveDrawingBuffer: true,
    stencil: true
  });
  const scene = new BABYLON.Scene(engine);
  scene.clearColor = new BABYLON.Color3.Black();

  await Ammo();

  var plugin = new BABYLON.AmmoJSPlugin(false);
  plugin.setTimeStep(1 / 60);
  plugin.setFixedTimeStep(1 / 60);
  plugin.setMaxSteps(1);
  scene.enablePhysics(new BABYLON.Vector3(0, -100, 0), plugin);

  var skybox = BABYLON.MeshBuilder.CreateBox(
    "skyBox",
    { size: SKYBOX_SIZE },
    scene
  );
  var skyboxMaterial = new BABYLON.StandardMaterial("skyBox", scene);
  skyboxMaterial.backFaceCulling = false;
  var files = [
    TEXTURE_FOLDER + "Space/space_left.jpg",
    TEXTURE_FOLDER + "Space/space_up.jpg",
    TEXTURE_FOLDER + "Space/space_front.jpg",
    TEXTURE_FOLDER + "Space/space_right.jpg",
    TEXTURE_FOLDER + "Space/space_down.jpg",
    TEXTURE_FOLDER + "Space/space_back.jpg"
  ];
  skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture(
    TEXTURE_FOLDER + "skybox",
    scene
  );
  skyboxMaterial.reflectionTexture.coordinatesMode =
    BABYLON.Texture.SKYBOX_MODE;
  skyboxMaterial.disableLighting = true;
  skybox.material = skyboxMaterial;

  var spotLight = new BABYLON.SpotLight(
    "spotLight",
    new BABYLON.Vector3(0, GROUND_SIZE * 3, 0),
    new BABYLON.Vector3(0, -1, 0),
    Math.PI,
    1,
    scene
  );
  spotLight.range = GROUND_SIZE * 5;
  spotLight.diffuse = BABYLON.Color3.FromHexString("#e25822");
  spotLight.diffuse = new BABYLON.Color3.FromHexString("#F96229"); //(1, 1, 1);
  spotLight.specular = new BABYLON.Color3.FromHexString("#FCE13D");

  var ambientLight = new BABYLON.HemisphericLight(
    "ambientLight",
    new BABYLON.Vector3(0, -1, 0),
    scene
  );
  ambientLight.diffuse = new BABYLON.Color3.FromHexString("#F96229");
  ambientLight.specular = new BABYLON.Color3.FromHexString("#FCE13D");
  ambientLight.intensity = 5;

  var camera = new BABYLON.ArcRotateCamera(
    "camera",
    0,
    0,
    10,
    BABYLON.Vector3.Zero(),
    scene
  );
  camera.setTarget(new BABYLON.Vector3(0, ropeLength / 2, 0));
  camera.attachControl(canvas, true);
  camera.setPosition(new BABYLON.Vector3(0, GROUND_SIZE, -GROUND_SIZE));

  camera.upperRadiusLimit = SKYBOX_SIZE / 2;
  var light = new BABYLON.HemisphericLight(
    "hemi",
    new BABYLON.Vector3(1, 1, 0),
    scene
  );

  var wood = new BABYLON.StandardMaterial("wood", scene);
  wood.diffuseTexture = new BABYLON.Texture(
    "https://images.pexels.com/photos/172289/pexels-photo-172289.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2g"
  );

  var blackMat = new BABYLON.StandardMaterial("blackMat", scene);
  blackMat.diffuseTexture = new BABYLON.Texture(
    "https://images.pexels.com/photos/172292/pexels-photo-172292.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2"
  );

  var grass = new BABYLON.StandardMaterial("", scene);
  grass.diffuseTexture = new BABYLON.Texture(
    "https://images.pexels.com/photos/1587548/pexels-photo-1587548.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2"
  );

  var ropeMat = new BABYLON.StandardMaterial("rope", scene);
  ropeMat.diffuseColor = new BABYLON.Color3(228 / 255, 108 / 255, 10 / 255);

  var ground = BABYLON.MeshBuilder.CreateBox(
    "ground",
    { width: GROUND_SIZE * 2, depth: GROUND_SIZE * 2, height: 1 },
    scene
  );
  ground.physicsImpostor = new BABYLON.PhysicsImpostor(
    ground,
    BABYLON.PhysicsImpostor.BoxImpostor,
    { mass: 0, friction: 0, restitution: 0 },
    scene
  );
  ground.material = grass;

  var base = BABYLON.MeshBuilder.CreateCylinder(
    "base",
    { height: 3, diameterTop: 4, diameterBottom: 6 },
    scene
  );
  base.position = new BABYLON.Vector3(-18, 2, 0);
  base.material = wood;
  base.physicsImpostor = new BABYLON.PhysicsImpostor(
    base,
    BABYLON.PhysicsImpostor.BoxImpostor,
    { mass: 0, friction: 0, restitution: 0 },
    scene
  );

  var upright = BABYLON.MeshBuilder.CreateCylinder(
    "upright",
    { height: ropeLength, diameter: 3 },
    scene
  );
  upright.position = new BABYLON.Vector3(-18, ropeLength / 2, 0);
  upright.material = wood;
  upright.physicsImpostor = new BABYLON.PhysicsImpostor(
    upright,
    BABYLON.PhysicsImpostor.BoxImpostor,
    { mass: 0, friction: 0, restitution: 0 },
    scene
  );

  var arm = BABYLON.MeshBuilder.CreateCylinder(
    "arm",
    { height: 40, diameter: 1 },
    scene
  );
  arm.rotation.z = Math.PI / 2;
  arm.position = new BABYLON.Vector3(0, ropeLength, 0);
  arm.material = wood;
  arm.physicsImpostor = new BABYLON.PhysicsImpostor(
    arm,
    BABYLON.PhysicsImpostor.BoxImpostor,
    { mass: 0, friction: 0, restitution: 0 },
    scene
  );

  var sphereSize = 6;
  let ropeShape = [];
  for (var i = 0; i < 2 * Math.PI + 0.1; i += Math.PI / 8) {
    ropeShape.push(
      new BABYLON.Vector3(Math.cos(i), Math.sin(i), 0).scale(radius)
    );
  }

  let ropePoints = [];
  for (var i = 0; i < nbPoints; i++) {
    ropePoints.push(
      new BABYLON.Vector3(0, ropeLength, 0),
      new BABYLON.Vector3(
        -2,
        ropeLength - ((ropeLength - sphereSize) * i) / nbPoints,
        0
      )
    );
  }

  var ropeLeft = BABYLON.MeshBuilder.ExtrudeShape(
    "ropeLeft",
    { shape: ropeShape, path: ropePoints },
    scene
  );
  ropeLeft.material = ropeMat;

  ropeLeft.physicsImpostor = new BABYLON.PhysicsImpostor(
    ropeLeft,
    BABYLON.PhysicsImpostor.RopeImpostor,
    { mass: 0.1, shape: ropeShape, path: ropePoints },
    scene
  );
  ropeLeft.physicsImpostor.velocityIterations = 10;
  ropeLeft.physicsImpostor.positionIterations = 10;
  ropeLeft.physicsImpostor.stiffness = 1;

  ropePoints = [];
  for (var i = 0; i < nbPoints; i++) {
    ropePoints.push(
      new BABYLON.Vector3(8, ropeLength, 0),
      new BABYLON.Vector3(
        10,
        ropeLength - ((ropeLength - sphereSize) * i) / nbPoints,
        0
      )
    );
  }

  var ropeRight = BABYLON.MeshBuilder.ExtrudeShape(
    "ropeRight",
    { shape: ropeShape, path: ropePoints },
    scene
  );
  ropeRight.material = ropeMat;

  ropeRight.physicsImpostor = new BABYLON.PhysicsImpostor(
    ropeRight,
    BABYLON.PhysicsImpostor.RopeImpostor,
    { mass: 0.1, shape: ropeShape, path: ropePoints },
    scene
  );
  ropeRight.physicsImpostor.velocityIterations = 10;
  ropeRight.physicsImpostor.positionIterations = 10;
  ropeRight.physicsImpostor.stiffness = 1;

  var swingBase = BABYLON.MeshBuilder.CreateBox(
    "swingBase",
    { width: 15, depth: 8, height: 1 },
    scene
  );
  swingBase.position = new BABYLON.Vector3(4, 8, 0);
  swingBase.material = blackMat;

  swingBase.physicsImpostor = new BABYLON.PhysicsImpostor(
    swingBase,
    BABYLON.PhysicsImpostor.BoxImpostor,
    { mass: 10 },
    scene
  );

  ropeRight.physicsImpostor.addHook(arm.physicsImpostor, 0, 1);
  ropeLeft.physicsImpostor.addHook(arm.physicsImpostor, 0, 1);

  ropeRight.physicsImpostor.addHook(swingBase.physicsImpostor, 1, 1);
  ropeLeft.physicsImpostor.addHook(swingBase.physicsImpostor, 1, 1);

  var impulseDirection = new BABYLON.Vector3(1, 0, -1);
  var impulseMagnitude = 100;
  swingBase.physicsImpostor.applyImpulse(
    impulseDirection.scale(impulseMagnitude),
    swingBase.getAbsolutePosition()
  );

  swingBase.actionManager = new BABYLON.ActionManager(scene);

  var hl = new BABYLON.HighlightLayer("hl1", scene);

  swingBase.actionManager.registerAction(
    new BABYLON.ExecuteCodeAction(
      BABYLON.ActionManager.OnPickTrigger,
      function (bjsevt) {
        var impulseDirection = new BABYLON.Vector3(
          0, // randomBetween(-1, 1),
          0,
          randomBetween(-1, 1)
        );
        var impulseMagnitude = randomBetween(100, 1000);
        swingBase.physicsImpostor.applyImpulse(
          impulseDirection.scale(impulseMagnitude),
          swingBase.getAbsolutePosition()
        );

        hl.addMesh(swingBase, BABYLON.Color3.Random());
      }
    )
  );

  var ticker = 0;
  scene.registerBeforeRender(() => {});

  var advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI(
    "UI"
  );

  var panel = new BABYLON.GUI.StackPanel();
  panel.width = "220px";
  panel.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT;
  panel.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_CENTER;
  advancedTexture.addControl(panel);

  var header = new BABYLON.GUI.TextBlock();
  header.height = "30px";
  header.color = "red";
  header.text = "Control";
  panel.addControl(header);

  var rotateCameraHeader = new BABYLON.GUI.TextBlock();
  rotateCameraHeader.height = "19px";
  rotateCameraHeader.color = "white";
  rotateCameraHeader.text = "Animate Camera: ";
  panel.addControl(rotateCameraHeader);

  var checkbox = new BABYLON.GUI.Checkbox();
  checkbox.width = "20px";
  checkbox.height = "20px";
  checkbox.isChecked = config.animateCamera;
  checkbox.color = "green";
  checkbox.onIsCheckedChangedObservable.add((value) => {
    config.animateCamera = value;
  });
  panel.addControl(checkbox);

  scene.registerBeforeRender(() => {
    skybox.rotation.y += 0.0005;
    if (shouldAnimateCamera && config.animateCamera) {
      camera.alpha += 0.0016;
    }
  });

  scene.onPointerObservable.add((pointerInfo) => {
    switch (pointerInfo.type) {
      case BABYLON.PointerEventTypes.POINTERDOWN:
        if (config.animateCamera && shouldAnimateCamera) {
          resetTimer();
        }
        break;
      case BABYLON.PointerEventTypes.POINTERUP:
        if (config.animateCamera && shouldAnimateCamera) {
          startTimer();
        }
        break;
      case BABYLON.PointerEventTypes.POINTERMOVE:
        break;
      default:
        break;
    }
  });

  engine.runRenderLoop(() => {
    if (scene) scene.render();
    divFps.innerHTML = engine.getFps().toFixed() + " fps";
  });

  window.addEventListener("resize", () => {
    engine.resize();
  });
}

loadDemo();

startTimer = () => {
  g_timer = window.setTimeout(() => {
    shouldAnimateCamera = true;
  }, 5000);
};

resetTimer = () => {
  clearTimeout(g_timer);
  shouldAnimateCamera = false;
};

randomBetween = (min, max) => {
  return min + Math.random() * (max + 1 - min);
};
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.