views:

501

answers:

2

In Javascript we often render graphics by rotating and translating the coordinate plane before sending things

ctx.save();
ctx.translate(someX, someY);
ctx.rotate(someAngle * Math.PI / 180);

ctx.beginPath();
ctx.moveTo(x1, y1);    // What's the actual (x1,y1)?
ctx.lineTo(x2, y2);    // What's the actual (x2,y2)?
ctx.stroke();

ctx.restore();

So having done this, how do I figure out the actual values of the endpoints of that line segment I've drawn? Because after that translating and rotating, (x1,y1) and (x2,y2) are somewhere far away from where they'd be without the translating and rotating. Is there an easy way to find out what their actual values are?

+1  A: 

I think the only way to find that would be to apply the same transformations as you did on the rendering context to the points you want to know the actual coordinates. A few libraries provide matrix operations, like : http://sylvester.jcoglan.com/ You can try to perform rotation operations to the cartesian coordinates

Fabien Ménager
+2  A: 

There's no way right now to get the current transformation matrix, so you would need to keep track of any rotations/translations/scaling yourself.

To actually perform the transformation, you need to multiply the transformation matrix by the point (as a column vector).

You could override the methods that affect the transformation to store your own copy of the matrix. I haven't tested this code, but something like this should work:

var contextPrototype = CanvasRenderingContext2D.prototype;

contextPrototype.xform = Matrix.I(3);

contextPrototype.realSave = contextPrototype.save;
contextPrototype.save = function() {
    if (!this.xformStack) {
        this.xformStack = [];
    }
    this.xformStack.push(this.xform.dup());
    this.realSave();
}

contextPrototype.realRestore = contextPrototype.restore;
contextPrototype.restore = function() {
    if (this.xformStack && this.xformStack.length > 0) {
        this.xform = this.xformStack.pop();
    }
    this.realRestore();
}

contextPrototype.realScale = contextPrototype.scale;
contextPrototype.scale = function(x, y) {
    this.xform = this.xform.multiply($M([
        [x, 0, 0],
        [0, y, 0],
        [0, 0, 1]
    ]));
    this.realScale(x, y);
}

contextPrototype.realRotate = contextPrototype.rotate;
contextPrototype.rotate = function(angle) {
    var sin = Math.sin(angle);
    var cos = Math.cos(angle);
    this.xform = this.xform.multiply($M([
        [cos, -sin, 0],
        [sin,  cos, 0],
        [   0,   0, 1]
    ]));
    this.realRotate(angle);
}

contextPrototype.realTranslate = contextPrototype.translate;
contextPrototype.translate = function(x, y) {
    this.xform = this.xform.multiply($M([
        [1, 0, x],
        [0, 1, y],
        [0, 0, 1]
    ]));
    this.realTranslate(x, y);
}

contextPrototype.realTransform = contextPrototype.transform;
contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
    this.xform = this.xform.multiply($M([
        [m11, m21, dx],
        [m12, m22, dy],
        [  0,   0,  1]
    ]));
    this.realTransform(m11, m12, m21, m22, dx, dy);
}

contextPrototype.realSetTransform = contextPrototype.setTransform;
contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
    this.xform = $M([
        [m11, m21, dx],
        [m12, m22, dy],
        [  0,   0,  1]
    ]);
    this.realSetTransform(m11, m12, m21, m22, dx, dy);
}

I used the Sylvester matrix library for convenience, but you could do your own multiplication.

To get the transformed point, just multiply the transformation matrix by the point:

// Get the transformed point as [x, y]
contextPrototype.getTransformedPoint = function(x, y) {
    var point = this.xform.multiply($V([x, y, 1]));
    return [point.e(1), point.e(2)];
}
Matthew Crumley
This needs to implement save() and restore() as well, but it looks just right and should get the check mark.
Jason Orendorff
Good point about save() and restore(). I'll add those.
Matthew Crumley