How does transforming points with a transformMatrix work in fabricJS?

I'm trying to place points (made via fabric.Circle) on the corners of a fabric.Polygon. The polygon may be moved, scaled or rotated by the user. However, after each modification I want to have the new coordinates of the polygon to place my circles there.

While digging deeper into this topic I found this great explanation of transformation matrices. I thought it's the perfect solution for what I want to achieve. But as you can see in the Fiddle, my points are always way off my polygon.

As I'm not firm with geometric transformation etc. I hope someone can find my error and tell me what I'm missing :) Thanks.

var canvas = new fabric.Canvas("c", {selection: false});

var polygon = new fabric.Polygon([
  new fabric.Point(200, 50),
  new fabric.Point(250, 150),
  new fabric.Point(150, 150)
]);

polygon.on("modified", function () {
  var matrix = this.calcTransformMatrix();
  var transformedPoints = this.get("points").map(function(p){
    return fabric.util.transformPoint(p, matrix);
  });
  var circles = transformedPoints.map(function(p){
    return new fabric.Circle({
      left: p.x,
      top: p.y,
      radius: 3,
      fill: "red",
      originX: "center",
      originY: "center",
      hasControls: false,
      hasBorders: false,
      selectable: false
    });
  });
  
  this.canvas.clear().add(this).add.apply(this.canvas, circles).setActiveObject(this).renderAll();
});

canvas.add(polygon).renderAll();
canvas {
  border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.3/fabric.js"></script>
<p>
Move, scale and rotate the polygon. The three red dots should match with the corners of the polygon after each modification.
</p>
<canvas id="c" width="600" height="400"></canvas>

Related Questions on SO:

  • How to get polygon points in Fabric.js
  • Truly rotate center of equilateral triangle in Fabric.js
  • How do I transform a particular point which has been modified and update the points array of polygon?
  • FabricJs and Polygon transformed coordinates
  • How to multiply each point with object's transform matrix?
  • How do i get point coordinates after object modified?

Solution 1:

Not really a geometric problem. The geometric part was solve by you. If you would look at internal polygon class from fabricjs you would notice that polygon as a calcDimension function where every point gets an offset: http://fabricjs.com/docs/fabric.Polygon.html

To calculate canvas position you have to add that offset back before transforming.

var canvas = new fabric.Canvas("c", {selection: false});

var polygon = new fabric.Polygon([
  new fabric.Point(200, 50),
  new fabric.Point(250, 150),
  new fabric.Point(150, 150)
]);

polygon.on("modified", function () {
  var matrix = this.calcTransformMatrix();
  var transformedPoints = this.get("points")
  .map(function(p){
    return new fabric.Point(p.x - polygon.minX -polygon.width/2, p.y - polygon.minY - polygon.height/2);
    })
  .map(function(p){
    return fabric.util.transformPoint(p, matrix);
  });
  var circles = transformedPoints.map(function(p){
    return new fabric.Circle({
      left: p.x,
      top: p.y,
      radius: 3,
      fill: "red",
      originX: "center",
      originY: "center",
      hasControls: false,
      hasBorders: false,
      selectable: false
    });
  });
  
  this.canvas.clear().add(this).add.apply(this.canvas, circles).setActiveObject(this).renderAll();
});

canvas.add(polygon).renderAll();
canvas {
  border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.3/fabric.js"></script>
<p>
Move, scale and rotate the polygon. The three red dots should match with the corners of the polygon after each modification.
</p>
<canvas id="c" width="600" height="400"></canvas>

Solution 2:

It looks like the solution provided above doesn't work with the latest version of fabricjs. I'm not sure, but during the investigation, I've read that from version 2.0 they changed the coordinates of the polygon. Before 2.0 points relative to the center of the polygon; after 2.0 they are absolute to the canvas;

I made a few changes to the code snippet and it starts working with fabricjs v2.4.3

So, it works fine not only with moving but with transformations: such as resizing, rotation, flip

var canvas = new fabric.Canvas("c", {selection: false});

var polygon = new fabric.Polygon([
  new fabric.Point(200, 50),
  new fabric.Point(250, 150),
  new fabric.Point(150, 150)
]);

polygon.on("modified", function () {
  var matrix = this.calcTransformMatrix();
  var transformedPoints = this.get("points")
    .map(function(p){
      return new fabric.Point(
         p.x - polygon.pathOffset.x,
         p.y - polygon.pathOffset.y);
    })
  .map(function(p){
    return fabric.util.transformPoint(p, matrix);
  });
  var circles = transformedPoints.map(function(p){
    return new fabric.Circle({
      left: p.x,
      top: p.y,
      radius: 3,
      fill: "red",
      originX: "center",
      originY: "center",
      hasControls: false,
      hasBorders: false,
      selectable: false
    });
  });
  
  this.canvas.clear().add(this).add.apply(this.canvas, circles).setActiveObject(this).renderAll();
});

canvas.add(polygon).renderAll();
canvas {
  border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.3/fabric.js"></script>
<p>
Move, scale and rotate the polygon. The three red dots should match with the corners of the polygon after each modification.
</p>
<canvas id="c" width="600" height="400"></canvas>