How to properly rotate a virtual sphere?

I create points based on the fibonacci sphere, then using TextGeometry I create a virtual sphere from the resulting positions.How i can rotate this virtual sphere with no effect on text nodes?

function fibonacciSphere(numPoints, point) {
  const rnd = 1;
  const offset = 2 / numPoints;
  const increment = Math.PI * (3 - Math.sqrt(5));

  const y = point * offset - 1 + offset / 2;
  const r = Math.sqrt(1 - Math.pow(y, 2));

  const phi = ((point + rnd) % numPoints) * increment;

  const x = Math.cos(phi) * r;
  const z = Math.sin(phi) * r;

  return new THREE.Vector3(x, y, z);
}

const loader = new FontLoader();

const group = new THREE.Group();

function main() {
  const camera = new THREE.PerspectiveCamera(75, 2, 0.1, 100);
  camera.position.z = 2;

  const scene = new THREE.Scene();

  function addTextNode(text, vert) {
    loader.load("./fonts/Fira.json", function (font) {
      const geometry = new TextGeometry(text, {
        font: font,
        size: 0.16,
        height: 0.01,
      });
      const material = new THREE.MeshBasicMaterial({ color: 0xffff00 });
      const mesh = new THREE.Mesh(geometry, material);
      mesh.position.x = vert.x;
      mesh.position.y = vert.y;
      mesh.position.z = vert.z;

      group.add(mesh);
      scene.add(group);
    });
  }

  const renderer = new THREE.WebGLRenderer();
  const container = document.querySelector("#root");
  container.appendChild(renderer.domElement);

  const numPoints = 50;
  for (let i = 0; i < numPoints; ++i) {
    addTextNode(`p${i}`, fibonacciSphere(numPoints, i));
  }

  function render(time) {
    time *= 0.001;
    // group.rotation.x += 0.01;

    scene.traverse((child) => {
      child.children.map((el) => {
        el.quaternion.copy(camera.quaternion);
      });
    });

    renderer.setSize(container.clientWidth, container.clientHeight);
    const canvas = renderer.domElement;
    camera.aspect = canvas.clientWidth / canvas.clientHeight;
    camera.updateProjectionMatrix();

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
}

main();

I direct them to face the camera, but when I try to rotate this group of elements, it stands still if I don’t remove the rotation to the camera.

if you put this code in the render function - everything works as it should, but gives an error
THREE.Quaternion: .setFromEuler() encountered an unknown order: undefined

    scene.traverse((child) => {
      let position = new THREE.Vector3();
      let direction = new THREE.Vector3();

      child.children.map((el) => {
        el.getWorldPosition(position);
        el.position.copy(position);

        child.getWorldDirection(direction);
        child.rotation.copy(direction);
      });
    });

I’m not sure what do you consider as “proper rotation”.

Is it something like this:

https://codepen.io/boytchev/full/abQKpLX

image

(1) use the mouse to rotate the virtual sphere;
(2) the orientation only adheres to the OrbitControls


There are some things in your code that you might consider fixing:

  • load the font file only once and reuse it for all labels
  • it is sufficient to add the group to the scene just once
  • when traversing the labels, you do not need to traverse the whole scene (that may include other objects), but just the children of the group – alternatively, you may collect the labels in an array and process this array
  • the error, that you mention, might be caused by trying to use Euler angles with an object that is not a mesh. You may try if(child.isMesh){...} to filter out all non-meshes
2 Likes

figured it out with your help, thanks!

1 Like

Please help, and if i want to rotate the sphere on the x-axis, how can i make the elements face the camera?

Here you go:

function animationLoop( t )
{
	group.rotation.x = t/3000;
	
	for( var label of group.children )
	{
		label.rotation.set( -group.rotation.x, controls.getAzimuthalAngle(), 0, 'XYZ' );
		label.rotateX( controls.getPolarAngle()-Math.PI/2 );
	}
	
    controls.update( );
    renderer.render( scene, camera );
}
1 Like

sorry for my question, but if you need to rotate in all planes at the same time, how to achieve this, I don’t quite understand what logic it works.
The idea is to rotate the sphere towards the mouse

Something related :thinking:

2 Likes

Well, the first thing to do is to fix the set of motions that you want to support. Then, find the simplest (or fastest) solution that supports this set. If you have a solution for a specific set of motions, and you add another motion, the solution might not work …

Having this said, here is a simplified version of a code that supports rotation of the group around X, Y and Z axes, as well as rotation of the camera with OrbitControls. What this code does is to undo all rotations in a specific order, because rotations are too sensitive.

function animationLoop( t )
{
	// rotate the group in some complex way
	group.rotation.x = 10*Math.sin(t/13000);
	group.rotation.y = 10*Math.sin(t/17000);;
	group.rotation.z = 10*Math.cos(-t/21000);
	
	// turn the labels towards the camera
	for( var label of group.children )
	{
		// clear rotation of a label
		label.rotation.set( 0, 0, 0 );
			
		// undo rotation caused by rotation of the group
		label.rotateZ( -group.rotation.z );
		label.rotateY( -group.rotation.y );
		label.rotateX( -group.rotation.x );
			
		// undo rotation caused by OrbitControls
		label.rotateY( controls.getAzimuthalAngle() );
		label.rotateX( controls.getPolarAngle()-Math.PI/2 );
	}
		
	controls.update( );
	renderer.render( scene, camera );
}

The code above is not too fast, although for simple scenes with a few hundreds texts it will work well. One way to optimize it is to pack individual rotations into one. The following code has the same effect, but four commands are packed into one.

function animationLoop( t )
{
	// rotate the group in some complex way
	group.rotation.x = 10*Math.sin(t/13000);
	group.rotation.y = 10*Math.sin(t/17000);;
	group.rotation.z = 10*Math.cos(-t/21000);
	
	// turn the labels towards the camera
	for( var label of group.children )
	{
		// undo rotation caused by rotation of the group
		label.rotation.set( -group.rotation.x, -group.rotation.y, -group.rotation.z, 'ZYX' );			
			
		// undo rotation caused by OrbitControls
		label.rotateY( controls.getAzimuthalAngle() );
		label.rotateX( controls.getPolarAngle()-Math.PI/2 );
	}
	
	controls.update( );
	renderer.render( scene, camera );
}

Of course, this approach has its limitations. For example, if you have the rotating group inside another rotating group inside a third rotating group … then it will not work. It may not work if the groups are translated in another locations. For the most general case you may want to play with matrices, as all transformations (incl. rotation) are converted into matrices. Or, you might want to partly decouple the labels from the group (so that labels take position from the group, but are no affected by its rotation).

1 Like

Thank you so much for your help, you helped me a lot!