Search code examples
javascriptthree.js3dbabylonjs

How can i wrap a text arround 3D sphere


I'm looking for a way to wrap text around sphere in babylon or threejs. And i'm open-minded for changing javascript technology


Solution

  • I would look at an example of generating text. I’d then generate each letter separately recording their individual widths and use those to compute the total width across the string I want to display

    I could then parent each mesh to an Object3D and set that Object3D’s rotation y to

    widthSoFar = 0;
    for each letter
       obj3d.rotation.y = widthSoFar / totalWidth * Math.PI * 2;
       widthSoFar += widthOfCurrentLetter;
    

    And set the letter’s position.z to some radius which would put the letters around a circle.

    What radius?

    circumference = 2 * PI * radius
    

    so

    radius = circumference / (2 * PI)
    

    We know the circumference we need, it’s the totalWidth of the string.

    You might find this tutorial helpful in understanding how to use scene graph nodes (like the Object3D node) to organize a scene to meet your needs.

    'use strict';
    
    /* global THREE */
    
    function main() {
      const canvas = document.querySelector('#c');
      const renderer = new THREE.WebGLRenderer({canvas});
    
      const fov = 40;
      const aspect = 2;  // the canvas default
      const near = 0.1;
      const far = 1000;
      const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
      camera.position.z = 70;
    
      const scene = new THREE.Scene();
      scene.background = new THREE.Color('black');
    
      function addLight(...pos) {
        const color = 0xFFFFFF;
        const intensity = 1;
        const light = new THREE.DirectionalLight(color, intensity);
        light.position.set(...pos);
        scene.add(light);
      }
      addLight(-4, 4, 4);
      addLight(5, -4, 4);
    
      const lettersTilt = new THREE.Object3D();
      scene.add(lettersTilt);
      lettersTilt.rotation.set(
         THREE.Math.degToRad(-15),
         0,
         THREE.Math.degToRad(-15));
      const lettersBase = new THREE.Object3D();
      lettersTilt.add(lettersBase);
      {
        const letterMaterial = new THREE.MeshPhongMaterial({
          color: 'red',
        });  
        const loader = new THREE.FontLoader();
        loader.load('https://threejsfundamentals.org/threejs/resources/threejs/fonts/helvetiker_regular.typeface.json', (font) => {
          const spaceSize = 1.0;
          let totalWidth = 0;
          let maxHeight = 0;
          const letterGeometries = {
            ' ': { width: spaceSize, height: 0 }, // prepopulate space ' '
          };
          const size = new THREE.Vector3();
          const str = 'threejs fundamentals ';
          const letterInfos = str.split('').map((letter, ndx) => {
            if (!letterGeometries[letter]) {
              const geometry = new THREE.TextBufferGeometry(letter, {
                font: font,
                size: 3.0,
                height: .2,
                curveSegments: 12,
                bevelEnabled: true,
                bevelThickness: 0.5,
                bevelSize: .3,
                bevelSegments: 5,
              });
              geometry.computeBoundingBox();
              geometry.boundingBox.getSize(size);
              letterGeometries[letter] = {
                geometry,
                width: size.x / 2, // no idea why size.x is double size
                height: size.y,
              };
            }
            const {geometry, width, height} = letterGeometries[letter];
            const mesh = geometry
                ? new THREE.Mesh(geometry, letterMaterial)
                : null;
            totalWidth += width;
            maxHeight = Math.max(maxHeight, height);
            return {
              mesh,
              width,
            };
          });
          let t = 0;
          const radius = totalWidth / Math.PI;
          for (const {mesh, width} of letterInfos) {
            if (mesh) {
              const offset = new THREE.Object3D();
              lettersBase.add(offset);
              offset.add(mesh);
              offset.rotation.y = t / totalWidth * Math.PI * 2;
              mesh.position.z = radius;
              mesh.position.y = -maxHeight / 2;
            }
            t += width;
          }
          {
            const geo = new THREE.SphereBufferGeometry(radius - 1, 32, 24);
            const mat = new THREE.MeshPhongMaterial({
             color: 'cyan',
            });
            const mesh = new THREE.Mesh(geo, mat);
            scene.add(mesh);
          }
          camera.position.z = radius * 3;
        });
      }
    
      function resizeRendererToDisplaySize(renderer) {
        const canvas = renderer.domElement;
        const width = canvas.clientWidth;
        const height = canvas.clientHeight;
        const needResize = canvas.width !== width || canvas.height !== height;
        if (needResize) {
          renderer.setSize(width, height, false);
        }
        return needResize;
      }
    
      function render(time) {
        time *= 0.001;
    
        if (resizeRendererToDisplaySize(renderer)) {
          const canvas = renderer.domElement;
          camera.aspect = canvas.clientWidth / canvas.clientHeight;
          camera.updateProjectionMatrix();
        }
        
        lettersBase.rotation.y = time * -0.5;
    
        renderer.render(scene, camera);
    
        requestAnimationFrame(render);
      }
    
      requestAnimationFrame(render);
    }
    
    main();
    body {
      margin: 0;
    }
    #c {
      width: 100vw;
      height: 100vh;
      display: block;
    }
    <canvas id="c"></canvas>
      <script src="https://threejsfundamentals.org/threejs/resources/threejs/r105/three.min.js"></script>