Search code examples
javascriptsvgdrawingpaperjs

paperjs draw 0 to 360º arc using mouse events


I´m trying to draw an arc using mouse events with paper.js

The user must be able to draw an arc from 0 degree to 360 degree. The issue that I´m facing is that I can draw 270 degree arc but more than 270, the arc flips to another quadrant.

Start point must be located anywhere

A sketch can be found here:

http://sketch.paperjs.org/#V/0.12.7/S/nVTBjpswEP2VEZeQXYcG2l6IOFTpZQ+RVruHHkpVeR1nQSE2MoZNFPHvtbHdeCGtorWEsOf5zTyex5wDhg80SIPnPZWkCFBA+FavOywAC/KbNHSVs5zptcDbsm2yZLlcmQChTFKRMfoGj7xkMvyyXCL1zC3eSCzkCP5qYJeTxJDBsAPLIlqXglQ0POcM1DDpU/tGJmhEpJDY9a6sqjWvuNo3e6kw2c9y1s9XoAvYD/AquNR6NFLwPXVcQbczNAZ/lFtZpBBPgDWuNYe3TLEADKzLAgyV4TJyJjmvIs42vG3ohndaz65lRJachbRTHzeHsyNpT2rPsgGPaj2PjshfnbSNjtLF2WD2wnjlI0lWT6OYvVY0C7skGmYP7EnZilmz6OJRxFXxuIJ0uMq0ueun7+GEgSZZ0bBE9hzNCb7Pa08qEvSgDAodaMNeh3wTJDQC9J5e2+a8BKcIV3WBYzS8kqu1TRfYhqKyFQy8xtgJfkj9gB5H14fREe5tF95ttCTCG1tyjt5zTn85pxGnKZnjXCi9R5eFaq7X4iYZcAcjIQoys2Rhq3xKbhHnMl3kXc30D8n8Q6bdj/J/xMRJjusKb7/xn/974+11t03Um7+Z+ne+CIr3w+1sgvTnr/4P

and this is the implemented code:

var arc_cse;

var radius=200;
var center=new Point(400,400);
var start=new Point(400,500);


var c1 = new Path.Circle({
    center: center,
    radius: 2,
    fillColor: 'black'
}); 

arc_cse = new Path({
        strokeColor: 'red',
        strokeWidth: 1,
        strokeCap: 'round',  
    });    

           
tool.onMouseMove = function(event) {

    var p=new Point(event.point.x,event.point.y);
    var v1=start-center;
    var v2=p-center;
    var angle=(v2.angleInRadians-v1.angleInRadians);
    
    var arcval=arc_CRD(v1.angleInRadians,v2.angleInRadians,angle,center,radius);
    
    arc_cse.remove();
    arc_cse= new Path.Arc(arcval);
}

function arc_CRD(alpha1,alpha2,angle,center,radius){
   
    return {
        from: {
            x: center.x + radius*Math.cos(alpha1),
            y: center.y + radius*Math.sin(alpha1)
        },
        through: {
            x: center.x + radius * Math.cos(alpha1 + (alpha2-alpha1)/2),
            y: center.y + radius * Math.sin(alpha1 + (alpha2-alpha1)/2)
        },
        to: {
            x: center.x + radius*Math.cos(alpha1+(alpha2-alpha1)),
            y: center.y + radius*Math.sin(alpha1+(alpha2-alpha1))
        },
        strokeColor: 'red',
        strokeWidth: 3,
        strokeCap: 'round'
    }
   
}

Thanks in advance


Solution

  • There are certainly tons of ways to do this but here's how I would do it: sketch.
    This should help you finding the proper solution to your own use case.

    function dot(point, color) {
        const item = new Path.Circle({ center: point, radius: 5, fillColor: color });
        item.removeOnMove();
    }
    
    function drawArc(from, center, mousePoint) {
        const radius = (from - center).length;
        const circle = new Path.Circle(center, radius);
        const to = circle.getNearestPoint(mousePoint);
        const middle = (from + to) / 2;
    
        const throughVector = (middle - center).normalize(radius);
        const angle = (from - center).getDirectedAngle(to - center);
        const through = angle <= 0
            ? center + throughVector
            : center - throughVector;
    
        const arc = new Path.Arc({
            from,
            through,
            to,
            strokeColor: 'red',
            strokeWidth: 2
        });
    
        circle.removeOnMove();
        arc.removeOnMove();
    
        // Visual helpers
        dot(from, 'orange');
        dot(center, 'black');
        dot(mousePoint, 'red');
        dot(to, 'blue');
        dot(middle, 'lime');
        dot(through, 'purple');
        circle.strokeColor = 'black';
    
        return arc;
    }
    
    function onMouseMove(event) {
        drawArc(view.center + 100, view.center, event.point);
    }
    

    Edit

    In answer to your comment, here is a more mathematical approach: sketch.
    This is based on your code and has exactly the same behavior so you should have no difficulty using it.
    The key of both implementation is the Point.getDirectedAngle() method which allow you to adapt the behavior depending on the through point side.

    const center = new Point(400, 400);
    const start = new Point(400, 500);
    
    const c1 = new Path.Circle({
        center: center,
        radius: 2,
        fillColor: 'black'
    });
    
    let arc;
    
    function getArcPoint(from, center, angle) {
        return center + (from - center).rotate(angle);
    }
    
    function drawArc(from, center, mousePoint) {
        const directedAngle = (from - center).getDirectedAngle(mousePoint - center);
        const counterClockwiseAngle = directedAngle < 0
            ? directedAngle
            : directedAngle - 360;
    
        const through = getArcPoint(from, center, counterClockwiseAngle / 2);
        const to = getArcPoint(from, center, counterClockwiseAngle);
    
        return new Path.Arc({
            from,
            through,
            to,
            strokeColor: 'red',
            strokeWidth: 1,
            strokeCap: 'round'
        });
    }
    
    function onMouseMove(event) {
        if (arc) {
            arc.remove();
        }
        arc = drawArc(start, center, event.point);
    }