7 Mar 2023

Synchronizing camera between models

synced models

Introduction

Imagine that you have to compare designs from completely different sources side by side, but their coordinate systems are completely different.

That's what we'll cover in this blog post.

Here we'll demonstrate a way to synchronize two Viewers rendering the same project but originating from different sources. It can be useful for a side-by-side comparison between a nwd, rvt, 3ds max design, or a mesh, for instance.

Thanks to Nichollas Pavloski for bringing this case to us.

Premises

As our base assumption for this sample, we're going to assume that the models are compatible, i.e they refer to the same scene, differing in scale, rotation and translation.

Our challenge will be to find a way to synchronize the camera between compatible scenes. Let's suppose we have two scenes rendering a table: If the tables are compatible, they both share the same proportions (length, width, and height) so that we can define an equivalence of coordinates from them.

How to find the equivalence?

When we move from one scene to the other, there are a few transformations that we need to consider.

First of them is rotation, as the tables can be misaligned:

rotation

The other transformation that we need to consider is regarding scale:

scale

Lastly, we also need to consider the different origins of the coordinate systems of those scenes.

With a combination of all these conversions (rotation, scale, and translation) we can synchronize the two scenes.

The math behind the sync

Before jumping into the math for our calculations, let's begin with some contextualization.

Both scenes represent each a vector space, with its own coordinate system.

coordinate systems

Each space will have its own basis, that we'll use to find the proper transformations. So first of all, we need to find those.

- Finding the basis

Each Viewer scene has its own coordinate system with a defined basis, but we can't simply use those, as they don't "see" the model the same way.

If we have two bases representing the models in the scene with the same relative orientation, we can define a transformation between those. This transformation will be useful to convert coordinates between these two spaces.

To find this basis, we can query the user to pick 4 points (in each scene).

These points need to be compatible between both scenes, and can't be coplanar

Let's use an example:

- 1st point = front end of the table base, below the 4 drawers
- 2nd point = front end of the table base, below the 2 drawers
- 3rd point = front end of the table top, above the 2 drawers
- 4th point = rear end of the table top, above the 2 drawers

pick points

From these points, we can define our base with the snippet below:

let v12 = this.points[1].clone().sub(this.points[0]);
let v13 = this.points[2].clone().sub(this.points[0]);
this.basis1 = this.points[1].clone().sub(this.points[0]);

let line12 = new THREE.Line3(this.points[0], this.points[1]);
let plane123 = (new THREE.Plane()).setFromCoplanarPoints(this.points[0], this.points[1], this.points[2]);
let auxPoint = line12.closestPointToPoint(this.points[2], false);
this.basis2 = this.points[2].clone().sub(auxPoint);

let auxDistance = plane123.distanceToPoint(this.points[3]);
let auxVector = v12.cross(v13);
this.basis3 = auxVector.normalize().multiplyScalar(auxDistance);

let auxbaseMatrix = new THREE.Matrix4();
this.baseOrigin = this.points[0].clone();

this.obliqueVector = this.basis1.clone().add(this.basis2.clone()).add(this.basis3.clone());

this.spaceBaseNormal = auxbaseMatrix.clone().makeBasis(this.basis1.clone().normalize(), this.basis2.clone().normalize(), this.basis3.clone().normalize());

First vector of our base will be the vector from point 1 to point 2:

first vector

Second vector of our base will be the height of the triangle formed by points 1, 2 and 3 (taking as base the line passing by points 1 and 2):

second vector

Third vector will be perpendicular to the previous bases and its module will be the distance between the plane 123 and point 4:

third vector

And that's it!
Our space base will be formed by these normalized vectors, and it'll help us to take care of the rotation between the two scenes.

We also defined an oblique vector (summing the three basis vectors without normalization) that we'll use to take care of the scaling.

These two spaces' basis origins will help us figure out the translation between coordinates.

- Handling rotation

The transformation between the two spaces basis can be found using the snippet below:

function findRotation(targetViewerBase, originViewerBase) {
    return (new THREE.Matrix4()).multiplyMatrices(targetViewerBase, originViewerBase.transpose());
}

In our case, we'll use the base created with the normalized vectors, so it returns a matrix that we'll use to transform any vector orientation from originViewerBase to targetViewerBase.

- Handling scale

For scaling, we are going to assume that we have the same scale factor for all the axis (x, y, and z). If we apply different scale factors, that would mean that we have a distortion among the scenes (i.e. the proportions weren't respected in one specific scene).
The scale can be found using the function below:

function findScale(targetViewerVector, originViewerVector) {
  let scaleFactor = targetViewerVector.length() / originViewerVector.length();
  return (new THREE.Matrix4()).makeScale(scaleFactor, scaleFactor, scaleFactor);
}

In our case, we'll use the oblique vector ratio (targetViewerVector divided by originViewerVector) to apply the scale.

- Handling translation

Regarding translation, we have to consider the two basis origins.

For any point that we want to transform, we need to subtract the origin of its original base and sum the origin of the target base.

Putting everything together

For any point conversion between scenes, we need to follow the same order below:

 - Subtract the base origin of the origin scene
 - Apply the rotation transformation to the vector
 - Apply the scale transformation to the vector
 - Sum the base origin of the target scene

Just like in the following snippet:

let targetViewerPoint = originViewerPoint.clone().sub(originBaseOrigin).applyMatrix4(rotationMatrix).applyMatrix4(scaleMatrix).add(targetBaseOrigin);

With all of that together, we can sync the two scenes smoothly.
Every time the camera changes, we apply the full transformation for the camera target and camera position. For the camera up vector, we only need to apply the rotation transformation.

Also note that for two specific models, this needs to be done only in the first loading. After the first calibration, you can store the required information such as matrices and vectors in an external DB to be loaded every time the models are compared.

Feel free to reach the links below for the demo and source code:

DEMO

SOURCE

Related Article