2D transform matrix to map 4 source points to 4 destination points

Hi all,
I’m trying to make a 2D perspective transform matrix that maps 4 source points to 4 destination points.

I think I’m doing something backward but I can’t pinpoint my error.
I’m using mathjs for solving the system. here is my situation: I have followed this topic to set up the equations math - Perspective projection, 4 points - Stack Overflow
and have come up with the following function.

export function getTransformMatrix(
  s1: THREE.Vector2, s2: THREE.Vector2, s3: THREE.Vector2, s4: THREE.Vector2,
  d1: THREE.Vector2, d2: THREE.Vector2, d3: THREE.Vector2, d4: THREE.Vector2,
) {

  const A = [
    [s1.x, s1.y, 1, 0, 0, 0, -d1.x * s1.x, -d1.x * s1.y],
    [0, 0, 0, s1.x, s1.y, 1, -d1.y * s1.x, -d1.y * s1.y],
    [s2.x, s2.y, 1, 0, 0, 0, -d2.x * s2.x, -d2.x * s2.y],
    [0, 0, 0, s2.x, s2.y, 1, -d2.y * s2.x, -d2.y * s2.y],
    [s3.x, s3.y, 1, 0, 0, 0, -d3.x * s3.x, -d3.x * s3.y],
    [0, 0, 0, s3.x, s3.y, 1, -d3.y * s3.x, -d3.y * s3.y],
    [s4.x, s4.y, 1, 0, 0, 0, -d4.x * s4.x, -d4.x * s4.y],
    [0, 0, 0, s4.x, s4.y, 1, -d4.y * s4.x, -d4.y * s4.y],
  ];

  const B = [
    d1.x, d1.y,
    d2.x, d2.y,
    d3.x, d3.y,
    d4.x, d4.y
  ];

  const A_matrix = mathjs.matrix(A);
  const B_matrix = mathjs.matrix(B);

  //  A = H * B
  // H = A^-1 * B
  const A_inv = (mathjs.inv(A_matrix));
  const H_matrix = (mathjs.multiply(A_inv, B_matrix));
  const H = H_matrix.valueOf().flat();

  // Re-order the 3x3 matrix for Three.js
  const res = new THREE.Matrix3();
  res.set(
    H[0], H[3], H[6],
    H[1], H[4], H[7],
    H[2], H[5], 0
  );
  return res;
}

the problem is that when I apply the transform, it is wrong.

function applyTransform1(matrix: THREE.Matrix3, vertices :THREE.Vector2[]) {
    vertices.forEach(vertice => {
      vertice.applyMatrix3(matrix);
    })
  }

So I tried to make my own version applyTransform2 to try and understand what is hapenning inside. Here it is :

  function applyTransform2(matrix: THREE.Matrix3, vertices: THREE.Vector2) {    
    vertices.forEach(vertex => {
      const x = vertex.x;
      const y = vertex.y;
      const w = matrix.elements[6] * x + matrix.elements[7] * y + 1; // Homogeneous coordinate
      const newX = (matrix.elements[0] * x + matrix.elements[1] * y + matrix.elements[2]) / w;
      const newY = (matrix.elements[3] * x + matrix.elements[4] * y + matrix.elements[5]) / w;

      vertex.setX(newX)
      vertex.setY(newY)
    });
  }

applyTransform2 makes the correct transform with the matrices out of getTransformMatrix but if I use it with some standard THREE js transform matrix such as new THREE.Matrix3().translate(...).matrix.scale(...) it makes a wrong transform.

So in the end my functions work well together but obviously there is something wrong since vertice.applyMatrix3(M) doesn’t give the expected result.

Here is an example of result:

const s1 = new THREE.Vector2(0, 0);
const s2 = new THREE.Vector2(80, 0);
const s3 = new THREE.Vector2(0, 80);
const s4 = new THREE.Vector2(80, 80);
let d1 = new THREE.Vector2(-200, 100)
let d2 = new THREE.Vector2(0, 200)
let d3 = new THREE.Vector2(0, 0)
let d4 = new THREE.Vector2(30, 100)

expected result (made using applyTransform2): red source, blue destination
image

wrong result (using applyTransform1): red source, blue destination
image

I guess I am solving for a right hand multiplication instead of left hand multiplication or I am missing a transpose somewhere… I’ve been at it for a few days but I feel like I’m running in circles now.
Help and pointers welcome :slight_smile:

So it seems I was missing some transpose but more importantly, I went to check the source code for Vector2.applyMatrix and there is no handling of the homogenous coordinate!
which is why the transformation is stuck in a paralellogram and not a correct perspective transform…