Search code examples
javascriptmathrotationtransformscale

Apply scale and rotation to a specific point - Javascript


We've been having some trouble with scaling AND rotating a point in relation to another div. Essentially we have 2 layers, one with and image(svg) and another layer with icons that are placed on the image.

We've been able to rotate the icons based on the image's rotation (and position), at a scale of 1. And we've been able to scale the icons based on the image's scale (and position), at a rotation of zero.

But as soon and you scale and rotate, things get all kinds of messed up...

Here's the code that translates a point (absolute) to match the image's matrix:

self.getViewportCoordinatesFromPoint = function(pointX, pointY) {

        var centerVal = {
            x: gestureValues.centerVal.x(),//scaled centerX
            y: gestureValues.centerVal.y(),//scaled centerY
            normalX: gestureValues.centerVal.normalX(),//original center
            normalY: gestureValues.centerVal.normalY()//original center
        };

        var output = {
            x:pointX,
            y:pointY
        };

        //Image rotation angle
        var angle = gestureValues.mapRotation * Math.PI / 180.0;

        //Scale no rotate
        var scaledPoint = {};

        var normX = (centerVal.normalX - output.x); //center of map to X axis
        var ratioX = normX / centerVal.normalX; //percentage of the distance compared to center point.
        var scaleX = centerVal.x * ratioX; //Scaled value of x from the center of scaled map
        scaledPoint.x = centerVal.normalX - scaleX; // subtract the original map width from scaled value

        var normY = (centerVal.normalY - output.y);
        var ratioY = normY / centerVal.normalY;
        var scaleY = centerVal.y * ratioY;
        scaledPoint.y = centerVal.normalY - scaleY;

        output = scaledPoint;

        var rotatedPoint = {};
        rotatedPoint.x = Math.cos(angle) * (output.x - centerVal.x) - Math.sin(angle) * (output.y - centerVal.y) + (centerVal.x);
        rotatedPoint.y = Math.sin(angle) * (output.x - centerVal.x) + Math.cos(angle) * (output.y - centerVal.y) + (centerVal.y);

        output = rotatedPoint;

        //Add gesture values
        output.x = ((output.x + gestureValues.mapXY[0]));
        output.y = ((output.y + gestureValues.mapXY[1]));

        return output;
    };

Any help would be greatly appreciated!

Thanks in advance!


Solution

  • Ok, check out lines 32 to 65 in this fiddle. The trick is to calculate the distance from centre * scale, then apply that to the rotation calculation!

    https://jsfiddle.net/DanielSim/sus9kgfh/

    outputElement = document.getElementById('output');
    rotationElement = document.getElementById('rotation');
    rotationElement.oninput = function () {
        rotation = parseInt(rotationElement.value, 10);
    };
    scaleElement = document.getElementById('scale');
    scaleElement.oninput = function () {
        scale = parseInt(scaleElement.value, 10) / 100;
    };
    
    
    feature = {
        x: 20,
        y: 20,
        width: 10,
        height: 10,
        el: document.getElementById('feature')
    };
    
    map = {
        x: 50,
        y: 30,
        width: 300,
        height: 300,
        el: document.getElementById('map')
    };
    
    rotation = 0;
    scale = 1;
    
    
    // Logic for correcting the coordinates of a point accounting for scale and rotation
    adjustedTransform = function (_x, _y) {    
        var x = _x + map.x;
        var y = _y + map.y;
    
        var viewportMapCentre = {
            x: map.width/2 + map.x,
            y: map.height/2 + map.y
        }
    
        var differenceFromCentre = {
            x: x - viewportMapCentre.x,
            y: y - viewportMapCentre.y
        };
    
        var differenceFromCentreAtScale = {
            x: differenceFromCentre.x * scale,
            y: differenceFromCentre.y * scale
        };
    
        var rotatedPoint = {};
        var angle = rotation * Math.PI / 180.0;
        rotatedPoint.x = Math.cos(angle) * (differenceFromCentreAtScale.x) - Math.sin(angle) * (differenceFromCentreAtScale.y) + (viewportMapCentre.x);
        rotatedPoint.y = Math.sin(angle) * (differenceFromCentreAtScale.x) + Math.cos(angle) * (differenceFromCentreAtScale.y) + (viewportMapCentre.y);
        x = rotatedPoint.x;
        y = rotatedPoint.y;
    
        var coords = {
            x:x,
            y:y
        };
    
        return coords;
    }
    
    
    // Runs on each frame:
    frame = 0;
    step = function () {
        //Apply standard transform to map
        map.el.style.webkitTransform = generateTransform(map.x, map.y, scale, rotation);
        //Apply adjusted transform to feature
        featurePosition = adjustedTransform(feature.x, feature.y);
        feature.el.style.webkitTransform = generateTransform(featurePosition.x, featurePosition.y, 1, 0);
        generateTextOutput();
        frame++;
    };
    
    displayLoop = function () {
        step();
        window.requestAnimationFrame(displayLoop);
    };
    
    
    generateTransform = function (x, y, scale, ry) {
        return 'translate3d(' + x + 'px, ' + y + 'px, 0) scale3d(' + scale + ', ' + scale + ', ' + 1 + ') rotate3d(0, 0, 1,' + ry + 'deg)';
    };
    
    generateTextOutput = function () {
        output = "";
        output += "\nFrame: " + frame;
        output += "\nRotation: " + rotation;
        output += "\nScale: " + scale;
        output += ("\nFeature Coords: " + featurePosition.x + ", " + featurePosition.y);
        outputElement.innerText = output;
    };
    
    displayLoop();