Is it possible to calculate a matrix transform function with given points?

67 views Asked by At

I have a div that i want to project on a svg path. This path has 4 points (just as a dom-element) but is not a rectangle.

I've tried GSAP FLIP but this transforms to the boundingbox of the path.

https://codepen.io/KoentjeV/pen/mdQNgLr

 <svg fill="none" xmlns="http://www.w3.org/2000/svg" id="thesvg" width="500" height="500" viewbox="0 0 500 500">

    <path d="M0 103.85 L0 335.11L199.29 231.26 L199.29 0L0 103.85Z" fill="#6B8EFF" id="left" />
    <path d="M201.29 231.26L400.57 335.11V103.85L201.29 0V231.26Z" fill="#1242FE" id="right" />

    <path d="M1 336.11L200.29 232.26L399.57 336.11L200.29 431.56L1 336.11Z" fill="#5073FF" id="bottom" />

  </svg>

  <div class="toBeTransformed"></div>
.toBeTransformed {
  width: 300px;
  height: 300px;
  position: absolute;
  bottom: 50px;
  right: 50px;
  background: linear-gradient(180deg, #ffff00 0%, #e5e5e5 100%);
}

Flip.fit(".toBeTransformed", "#bottom", {
  scale: true,
  duration: 1.5,
  delay: 1,
  repeat: -1,
  yoyo: 1
});

1

There are 1 answers

0
herrstrietzel On BEST ANSWER

All credits to MvG's answer: "Finding the Transform matrix from 4 projected points (with Javascript)" on Math Exchange. I can't remotely explain the math behind.

The function explained will calculate a 3D matrix according to the 4 defined projection points and the target element's dimensions.

I've slightly modified the script to work with projection points in clockwise order. (original function expects an order of: top-left, top-right, bottom-left, bottom-right).

/**
 * based on "Finding the Transform matrix from 4 projected points (with Javascript)"
 * @MvG's answer
 * https://math.stackexchange.com/questions/296794/finding-the-transform-matrix-from-4-projected-points-with-javascript#339033
 * see original fiddle:
 * http://jsfiddle.net/zbh98nLv/
 */

// retrieve coordinates from polygon
let projectionRect = document.getElementById('bottom');
let projectionPoints = projectionRect.points;
let targetEl = document.getElementById('box');

// set transform origin
targetEl.style.transformOrigin = '0 0';

// calculate matrix
let matrix3d = getMatrix3dFromPoints(targetEl, projectionPoints);

// apply matrix transform
targetEl.style.transform = `matrix3d(${matrix3d.join(', ')})`;



// calculate 3d transform matrix
function getMatrix3dFromPoints(el, pts) {
  let [x1, y1, x2, y2, x4, y4, x3, y3] = [pts[0].x, pts[0].y, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y]
  let w = el.offsetWidth,
    h = el.offsetHeight;
  let t = general2DProjection(0, 0, x1, y1, w, 0, x2, y2, 0, h, x3, y3, w, h, x4, y4);
  for (let i = 0; i != 9; ++i) {
    t[i] = t[i] / t[8];
  }

  let matrix3D = [t[0], t[3], 0, t[6],
    t[1], t[4], 0, t[7],
    0, 0, 1, 0,
    t[2], t[5], 0, t[8]
  ];

  return matrix3D;
}


function general2DProjection(
  x1s, y1s, x1d, y1d,
  x2s, y2s, x2d, y2d,
  x3s, y3s, x3d, y3d,
  x4s, y4s, x4d, y4d
) {
  let s = basisToPoints(x1s, y1s, x2s, y2s, x3s, y3s, x4s, y4s);
  let d = basisToPoints(x1d, y1d, x2d, y2d, x3d, y3d, x4d, y4d);
  return multmm(d, adj(s));
}


function basisToPoints(x1, y1, x2, y2, x3, y3, x4, y4) {
  let m = [x1, x2, x3, y1, y2, y3, 1, 1, 1];
  let v = multmv(adj(m), [x4, y4, 1]);
  return multmm(m, [
    v[0], 0, 0,
    0, v[1], 0,
    0, 0, v[2]
  ]);
}

// multiply matrix and vector
function multmv(m, v) {
  return [
    m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
    m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
    m[6] * v[0] + m[7] * v[1] + m[8] * v[2]
  ];
}


// multiply two matrices
function multmm(a, b) {
  let c = Array(9);
  for (let i = 0; i != 3; ++i) {
    for (let j = 0; j != 3; ++j) {
      let cij = 0;
      for (let k = 0; k != 3; ++k) {
        cij += a[3 * i + k] * b[3 * k + j];
      }
      c[3 * i + j] = cij;
    }
  }
  return c;
}

// Compute the adjugate of m
function adj(m) {
  return [
    m[4] * m[8] - m[5] * m[7], m[2] * m[7] - m[1] * m[8], m[1] * m[5] - m[2] * m[4],
    m[5] * m[6] - m[3] * m[8], m[0] * m[8] - m[2] * m[6], m[2] * m[3] - m[0] * m[5],
    m[3] * m[7] - m[4] * m[6], m[1] * m[6] - m[0] * m[7], m[0] * m[4] - m[1] * m[3]
  ];
}
svg {
  position: absolute;
  top: 0px;
  left: 0px;
}

#box {
  position: absolute;
  top: 0px;
  left: 0px;
  width: 200px;
  height: 200px;
}

.toBeTransformed {
  width: 300px;
  height: 300px;
  position: absolute;
  left: 0;
  top: 0px;
  background: linear-gradient(180deg, #ffff00 0%, red 100%);
  opacity: 1;
}
<svg fill="none" xmlns="http://www.w3.org/2000/svg" id="thesvg" width="500" height="500" viewbox="0 0 500 500">
    <path d="M0 103.85 L0 335.11L199.29 231.26 L199.29 0L0 103.85Z" fill="#6B8EFF" id="left" />
    <path d="M201.29 231.26L400.57 335.11V103.85L201.29 0V231.26Z" fill="#1242FE" id="right" />
    <polygon points="1 336.11 
                200.29 232.26 
                399.57 336.11
                200.29 431.56
                1 336.11 " fill="#5073FF" id="bottom" />
  </svg>

<div id="container">
  <div id="box" class="toBeTransformed"></div>
</div>

To get the required 4 projection points I converted the <path> to a <polygon>.

This way we can easily get all points as a point array

let projectionRect = document.getElementById('bottom');
let projectionPoints = projectionRect.points;

3D transforms are not supported for svg elements

In your case this is not a huge deal as you're projecting a HTMl element.