Newer
Older
2024-Tsubasa / system / cannonjs_test / index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>cannon-es-debugger</title>
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" />
    <style>
      body {
        margin: 0;
        padding: 0;
        overflow: hidden;
        font-family: Monospace;
      }

      canvas {
        outline: none;
      }

      .page-title {
        position: fixed;
        top: 0.75rem;
        left: 0;
        right: 0;
        text-align: center;
        color: white;
      }
      .page-title span {
        color: #99ff4e;
      }
    </style>
  </head>
  <body>
    <div class="page-title">Press the <span>d</span> key to toggle the debugger</div>

    <!-- Import maps polyfill -->
    <!-- Remove this when import maps will be widely supported -->
    <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/"></script>

    <script type="importmap">
      {
        "imports": {
          "cannon-es": "https://unpkg.com/cannon-es@0.20.0/dist/cannon-es.js",
          "cannon-es-debugger": "../node_modules/cannon-es-debugger/dist/cannon-es-debugger.js",
          "three": "https://unpkg.com/three@0.163.0/build/three.module.js",
          "three/examples/jsm/controls/OrbitControls": "https://unpkg.com/three@0.163.0/examples/jsm/controls/OrbitControls.js"
        }
      }
    </script>
    <script type="module">
      import * as THREE from 'three'
      import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
      import * as CANNON from 'cannon-es'
      import CannonDebugger from 'cannon-es-debugger'

      // three.js variables
      let camera, scene, renderer, controls
      let material

      // cannon.js variables
      let world
      const mass = 7
      const timeStep = 1 / 60
      let lastCallTime
      let cannonDebugger

      // To be kept in sync
      const meshes = []
      const bodies = []

      initThree()
      initCannon()
      initCannonDebugger()

      addPlane()
      addSphere()
      addBox()
      addCylinder()
      addTrimesh()
      addHeightfield()

      animate()

      function initThree() {
        // Camera
        camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 0.5, 1000)
        camera.position.set(5, 4, 5)

        // Scene
        scene = new THREE.Scene()
        scene.fog = new THREE.Fog(0x000000, 500, 1000)

        // Renderer
        renderer = new THREE.WebGLRenderer({ antialias: true })
        renderer.setPixelRatio(window.devicePixelRatio)
        renderer.setSize(window.innerWidth, window.innerHeight)
        renderer.setClearColor(scene.fog.color)

        renderer.outputEncoding = THREE.sRGBEncoding

        renderer.shadowMap.enabled = true
        renderer.shadowMap.type = THREE.PCFSoftShadowMap

        document.body.appendChild(renderer.domElement)

        // Orbit controls
        controls = new OrbitControls(camera, renderer.domElement)
        controls.enableDamping = true
        controls.enablePan = false
        controls.dampingFactor = 0.1
        controls.minDistance = 1
        controls.maxDistance = 50

        // Generic material
        material = new THREE.MeshStandardMaterial({ color: '#ccc' })

        // Lights
        const ambientLight = new THREE.AmbientLight(0xffffff, 0.1)
        scene.add(ambientLight)

        const spotLight = new THREE.SpotLight(0xffffff, 0.3, 0, Math.PI / 3, 1)
        spotLight.position.set(0, 4, 0)
        spotLight.castShadow = true
        scene.add(spotLight)

        window.addEventListener('resize', onWindowResize)
      }

      function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight
        camera.updateProjectionMatrix()
        renderer.setSize(window.innerWidth, window.innerHeight)
      }

      function initCannon() {
        // Setup world
        world = new CANNON.World()
        world.gravity.set(0, -9.81, 0)
      }

      function initCannonDebugger() {
        cannonDebugger = new CannonDebugger(scene, world, {
          onInit(body, mesh) {
            // Toggle visibiliy on "d" press
            document.addEventListener('keydown', (event) => {
              if (event.key === 'd') {
                mesh.visible = !mesh.visible
              }
            })
          },
        })
      }

      function animate() {
        requestAnimationFrame(animate)

        // Step the physics world
        const time = performance.now() / 1000
        if (!lastCallTime) {
          world.step(timeStep)
        } else {
          const dt = time - lastCallTime
          world.step(timeStep, dt)
        }
        lastCallTime = time

        // Update the debugger
        cannonDebugger.update()

        // Update the visible meshes positions
        updateMeshPositions()

        controls.update()
        renderer.render(scene, camera)
      }

      function updateMeshPositions() {
        for (let i = 0; i < meshes.length; i++) {
          meshes[i].position.copy(bodies[i].position)
          meshes[i].quaternion.copy(bodies[i].quaternion)
        }
      }

      function addPlane() {
        // Physics
        const shape = new CANNON.Plane()
        const body = new CANNON.Body({ mass: 0 })
        body.addShape(shape)
        body.quaternion.setFromEuler(-Math.PI / 2, 0, 0)
        world.addBody(body)
        bodies.push(body)

        // Graphics
        const geometry = new THREE.PlaneGeometry(100, 100, 1, 1)
        const material = new THREE.MeshStandardMaterial({ color: '#060606' })
        const mesh = new THREE.Mesh(geometry, material)
        // position and quaternion of the mesh are set by updateMeshPositions...
        mesh.castShadow = true
        mesh.receiveShadow = true
        scene.add(mesh)
        meshes.push(mesh)
      }

      function addBox() {
        const size = 1

        // Physics
        const halfExtents = new CANNON.Vec3(size * 0.5, size * 0.5, size * 0.5)
        const shape = new CANNON.Box(halfExtents)
        const body = new CANNON.Body({ mass })
        body.addShape(shape)
        body.position.set(2, 2, 0.5)
        world.addBody(body)
        bodies.push(body)

        // Graphics
        const geometry = new THREE.BoxGeometry(size, size, size)
        const mesh = new THREE.Mesh(geometry, material)
        // position and quaternion of the mesh are set by updateMeshPositions...
        mesh.castShadow = true
        mesh.receiveShadow = true
        scene.add(mesh)
        meshes.push(mesh)
      }

      function addSphere() {
        const size = 0.5

        // Physics
        const body = new CANNON.Body({ mass })
        const shape = new CANNON.Sphere(size)
        body.addShape(shape)
        body.position.set(-0.5, 2, -1)
        world.addBody(body)
        bodies.push(body)

        // Graphics
        const geometry = new THREE.SphereGeometry(size)
        const mesh = new THREE.Mesh(geometry, material)
        // position and quaternion of the mesh are set by updateMeshPositions...
        mesh.castShadow = true
        mesh.receiveShadow = true
        scene.add(mesh)
        meshes.push(mesh)
      }

      function addCylinder() {
        const size = 1
        const radialSegments = 15

        // Physics
        const body = new CANNON.Body({ mass })
        const shape = new CANNON.Cylinder(size * 0.5, size * 0.5, size, radialSegments)
        body.addShape(shape)
        body.position.set(0, 2, 1.5)
        world.addBody(body)
        bodies.push(body)

        // Graphics
        const geometry = new THREE.CylinderGeometry(size * 0.5, size * 0.5, size, radialSegments)
        const mesh = new THREE.Mesh(geometry, material)
        // position and quaternion of the mesh are set by updateMeshPositions...
        mesh.castShadow = true
        mesh.receiveShadow = true
        scene.add(mesh)
        meshes.push(mesh)
      }

      function addTrimesh() {
        const radius = 1
        const tube = 0.3
        const radialSegments = 16

        // Physics
        const body = new CANNON.Body({ mass })
        const shape = CANNON.Trimesh.createTorus(radius, tube, radialSegments, 16)
        body.addShape(shape)
        body.position.set(-3, 2, -1)
        body.quaternion.setFromEuler(Math.PI * 0.1, 0, 0)
        world.addBody(body)
        bodies.push(body)

        // Graphics
        const geometry = new THREE.TorusGeometry(radius, tube, radialSegments, 100)
        const mesh = new THREE.Mesh(geometry, material)
        // position and quaternion of the mesh are set by updateMeshPositions...
        mesh.castShadow = true
        mesh.receiveShadow = true
        scene.add(mesh)
        meshes.push(mesh)
      }

      function addHeightfield() {
        const sizeX = 20 // number of vertices in the X axis
        const sizeY = 20 // number of vertices in the Y axis
        const elementSize = 0.3 // cell width
        const depth = 0.6

        // Physics
        const body = new CANNON.Body({ mass: 0 })
        const matrix = []
        for (let i = 0; i < sizeX; i++) {
          matrix.push([])
          for (let j = 0; j < sizeY; j++) {
            const height = Math.cos((i / (sizeX - 1)) * Math.PI * 2) * Math.cos((j / (sizeY - 1)) * Math.PI * 2) * depth
            matrix[i].push(height)
          }
        }
        const shape = new CANNON.Heightfield(matrix, { elementSize })
        body.addShape(shape, new CANNON.Vec3((-(sizeX - 1) / 2) * elementSize, (-(sizeY - 1) / 2) * elementSize, 0))
        body.position.set(0, depth, -6)
        body.quaternion.setFromEuler(-Math.PI / 2, 0, 0)
        world.addBody(body)
        bodies.push(body)

        // Graphics
        const geometry = new THREE.PlaneGeometry(
          (sizeX - 1) * elementSize,
          (sizeY - 1) * elementSize,
          sizeX - 1,
          sizeY - 1
        )
        for (let i = 0; i < sizeX; i++) {
          for (let j = 0; j < sizeY; j++) {
            const height = Math.cos((i / (sizeX - 1)) * Math.PI * 2) * Math.cos((j / (sizeY - 1)) * Math.PI * 2) * depth

            geometry.attributes.position.setZ(i * sizeX + j, height)
          }
        }
        geometry.computeBoundingSphere()
        geometry.computeVertexNormals()
        const mesh = new THREE.Mesh(geometry, material)
        // position and quaternion of the mesh are set by updateMeshPositions...
        mesh.castShadow = true
        mesh.receiveShadow = true
        scene.add(mesh)
        meshes.push(mesh)
      }
    </script>
  </body>
</html>