Search code examples
javascriptcanvaspaperjs

Snap to angle freehand pen/pencil tool in Canvas


What I'm currently doing

I'm trying to build a freehand pencil tool in HTML5 Canvas (using Paper.js as the Canvas wrapper).

What I want to do

I'd like to allow the user to draw straight lines (when Shift is pressed down for example) while drawing. This straight line should "snap", ideally, to an 8-directional snapping "radius".

What I tried (which is not exactly what I need).

I've tried a very simple solution where I snap the mouse point to a near rounded point. This works somehow fine but it's not exactly a snap-to-angle tool, it's more like a snap to an invisible grid kind-of-tool.

  mousedrag: function(event) {
    var snapped = {x: snap(event.point.x), y: snap(event.point.y)};

    // add "snapped" drag point
    path.add(snapped.x, snapped.y);
  }

  // convert a number to a rounded snap
  function snap(x, div) { 
    return Math.round(x/div)*div; 
  };

Here's an interactive Sketch of what I'm currently doing (holding Shift snaps to a grid, releasing resumes regular freehand drawing)


Can anyone give me an indication on how to proceed for snapping to an angle instead of a grid?

Notes:

  • Although I'm using Canvas/Paper.js, I understand that the solution to the problem is independent of the rendering tech I'm using, so any JS-based solution (either SVG or Canvas, wrapper or without) should give me some good foundations on how to proceed.

  • I'm having a feeling that the solution might involve Math.atan() or something along those lines, instead of my solution where I snap to a rounded Math.round point.


Solution

  • If you juste change one or two lines in your code, you get something which might be close to what you want:

    // ...
    
    function onMouseDrag(event) {
        // if shift is down we transform the mousepoint
        // to a "snapped point", else add the mousepoint as it is.
        if(shiftDown)
        {
            var snapPoint = new Point(snap(event.point.x), snap(event.point.y));
            myPath.lastSegment.point = snapPoint;
        }
        else 
        {
            var snapPoint = event.point;
            myPath.add(snapPoint);
        }
    }
    
    // ...
    

    You can easily modify this to snap the angle instead of the position.

    function onMouseDrag(event) {
        // if shift is down we transform the mousepoint
        // to a "snapped point", else add the mousepoint as it is.
        if(shiftDown)
        {
            var vector = event.point - myPath.lastSegment.previous.point;
            vector.angle = Math.round(vector.angle/angleSnap)*angleSnap;
            myPath.lastSegment.point = myPath.lastSegment.previous.point + vector;
        }
        else 
        {
            var snapPoint = event.point;
            myPath.add(snapPoint);
        }
    }