ChemPal Documentation - v1.12.0
    Preparing search index...

    Function createMoleculeScene

    • Builds a ball-and-stick scene for molecule on canvas and starts animating it.

      A structure with real 3D coordinates rotates continuously about its vertical axis. A planar structure — every 2D record, and flat 3D ones like benzene — instead wobbles through a ±PLANAR_SWING yaw arc (~40°): spinning a flat molecule a full turn would take it edge-on twice per revolution, where it briefly vanishes.

      Parameters

      Returns MoleculeSceneHandle

      A handle for resizing, retiming and disposing the scene

      const { createMoleculeScene } = await import('@/utils/molecule/scene');
      const handle = createMoleculeScene(canvas, molecule, { size: 160, spinSeconds: 8 });
      handle.setSpinSeconds(4);
      handle.dispose();
      export function createMoleculeScene(
      canvas: HTMLCanvasElement,
      molecule: Molecule,
      options: MoleculeSceneOptions,
      ): MoleculeSceneHandle {
      const atomScale = options.atomScale ?? DEFAULT_ATOM_SCALE;
      const { positions, radius } = centreAtoms(molecule, atomScale);

      const scene = new Scene();
      addLighting(scene);

      const group = new Group();
      group.rotation.x = TILT_X;
      scene.add(group);

      const atomMesh = buildAtomMesh(molecule, positions, atomScale);
      group.add(atomMesh);
      const bondMesh = buildBondMesh(molecule, positions);
      if (bondMesh) group.add(bondMesh);

      const camera = new PerspectiveCamera(CAMERA_FOV, 1, 0.1, 1000);
      camera.position.z = (radius * FIT_MARGIN) / Math.tan((CAMERA_FOV * Math.PI) / 360);

      const renderer = new WebGLRenderer({ canvas, antialias: true, alpha: true });
      renderer.setPixelRatio(Math.min(globalThis.devicePixelRatio, 2));
      renderer.setSize(options.size, options.size);

      let spinSeconds = options.spinSeconds;
      let elapsed = 0;
      let lastFrame = performance.now();
      let frameHandle = 0;

      /**
      * Advances the rotation for a frame and redraws.
      * @param now - High-resolution timestamp supplied by `requestAnimationFrame`
      * @source
      */
      function renderFrame(now: number): void {
      const delta = (now - lastFrame) / 1000;
      lastFrame = now;
      elapsed += delta;

      const turns = (elapsed * 2 * Math.PI) / Math.max(spinSeconds, 0.1);
      group.rotation.y = molecule.isPlanar ? Math.sin(turns) * PLANAR_SWING : turns;

      renderer.render(scene, camera);
      frameHandle = requestAnimationFrame(renderFrame);
      }

      if (options.reducedMotion) {
      group.rotation.y = molecule.isPlanar ? 0 : PLANAR_SWING;
      renderer.render(scene, camera);
      } else {
      frameHandle = requestAnimationFrame(renderFrame);
      }

      return {
      setSpinSeconds(seconds: number): void {
      // Rebase the clock so the molecule keeps its current angle instead of jumping.
      const turns = (elapsed * 2 * Math.PI) / Math.max(spinSeconds, 0.1);
      spinSeconds = seconds;
      elapsed = (turns * Math.max(seconds, 0.1)) / (2 * Math.PI);
      },
      setSize(size: number): void {
      renderer.setSize(size, size);
      },
      dispose(): void {
      cancelAnimationFrame(frameHandle);
      disposeMesh(atomMesh);
      if (bondMesh) disposeMesh(bondMesh);
      renderer.dispose();
      renderer.forceContextLoss();
      },
      };
      }