How to rotate 20 sided Object3D polygon so that a selected face points to the camera?

How do I rotate a 20 sided die model so that a given face points at the camera? (I need to orient the die to show the result of a random roll and I have a list of 20 face normals that I calculate in the code below)

Sorry for the longish question but I wanted to give some background and what I’ve tried so this doesn’t come off sounding like a homework question (which it isn’t, it is for a project I’m working on).

I’m using the model and vertex/face data from this repo: GitHub - evgeny-rov/dice-roller: 3D Dice Rolling App to render a set of 20 sided dice in a grid on the z=0 plane. The vertex/face data for a 20 sided polygon is visible in this file: dice-roller/icosahedron.ts at e6ad3df27df8b5c01d098a94f959209939ec1410 · evgeny-rov/dice-roller · GitHub and I’ve used that to generate the normals using this code (using r124 so that THREE.Geometry is still available):

import { vertices, faces } from "./icosahedron";

const getNormals = () => {
  const geometry = new THREE.Geometry();
  vertices.forEach(({ x, y, z }) => {
    geometry.vertices.push(new THREE.Vector3(x, y, z))
  });
  faces.forEach(([ a, b, c ]) => {
    geometry.faces.push(new THREE.Face3(a, b, c));
  })
  geometry.computeFaceNormals();
  return geometry.faces.map(face => face.normal);
};

I’ve visually verified that the normals are correct by rendering lines for each normal from the center of the model to outside the model using this code when the model loads and all the lines come out perpendicular to the center of each face (I’ve animated a rotation of += 0.01 of x, y, z so I can see all the faces which is not shown here):

const material = new THREE.LineBasicMaterial( { color: 0x0000ff } );
...
diceModel.position.set(x, y, 0);
normals.forEach(normal => {
  const points = [
    diceModel.position,
    new THREE.Vector3(150 * normal.x, 150 * normal.y, 150 * normal.z)
  ];
  const geometry = new THREE.BufferGeometry().setFromPoints(points);
  diceModel.add(new THREE.Line( geometry, material ));
});

You can see the result here (the red line is a line for the (0,0,1) “up” normal added elsewhere for debugging):

I have a perspective camera looking down using the code below and I’ve used diceModel.lookat(this.camera.position) to verify that the red “up” vector points at the camera (before I added the rotation animation).

const aspectRatio = window.innerWidth / window.innerHeight;
this.camera = new THREE.PerspectiveCamera(80, aspectRatio, 0.1, 1000);
this.camera.position.z = 30;

After researching and thinking about it my intution was to calculate the angular difference between each normal and the objects “up” normalized vector using variations of the following code (note the two ways I tried to calculate the rotation) and then find the final rotation by calling lookAt(this.camera.position) and then adding the rotation for the selected face:

const getRotations = (normals: THREE.Vector3[]) => {
  const getRotation = (v1: THREE.Vector2, v2: THREE.Vector2) => {
    // return Math.atan2(v2.y, v2.x) - Math.atan2(v1.y, v1.x)
    return v1.angle() - v2.angle();
  }

  const v = new THREE.Vector3(0, 0, 1)
  return normals.map(n => {
    return {
      x: getRotation(new THREE.Vector2(v.z, v.y), new THREE.Vector2(n.z, n.y)),
      y: getRotation(new THREE.Vector2(v.x, v.z), new THREE.Vector2(n.x, n.z)),
      z: getRotation(new THREE.Vector2(v.x, v.y), new THREE.Vector2(n.x, n.y)),
    }
  })
}

My thought was that the rotation of each axis would be the 2d rotation with that axis removed but that did not work and I’m stuck (I’ve also tried many other crazy approaches). Searching for this seems to always return answers about rotating the camera around the object and not rotating the object to face the camera.

Any help would be appreciated and I apologize if my bizarre math above caused a spit take.

2 Likes

Did you ever figure this out?

No, my math was too weak. I ended up just adding some keyboard controls to rotate the die around and then record the vector of the angles for each face and then storing those vectors in a table that I used to animate to each face (that also turned out to be a challenge because of gimbal lock).

1 Like

This is a pretty tricky problem.. especially because you actually want it to orient nicely.. not just show some number facing the camera but upside down or smth.. so a robust automated/algorithmic solution might involve looking at the UV mapping also to orient the result. Punting and using a hand generated table isn’t a bad way to go.. or perhaps animating the landing keyframes in blender and tweening to them. (There are even more tricky aspects to nice dice rolling, like synchronizing the animations between different clients/different screen sizes)
I have made a couple dice sims, but never really attempted the full deal of pulling a 20 sided die toward the screen to display the value.

1 Like