Friday night trigonometry!

I’ve been playing around with HTML5’s canvas again today by building a Studio layout app. The app just lets you draw certain shapes on a canvas as we would currently on paper for a setup diagram. More of a proof of concept and a coding exercise for me than anything massively useful (yet!).

Anywho, a problem came up when I was trying to make it so that users could draw a line that would represent a sound screen, a sort of sound baffle on wheels if you will. Obviously these are of a fixed size and so it would make sense that the line the user would draw on the diagram would be a fixed size and to scale.

Drawing a line on a canvas between 2 user clicked points is easy, drawing a line of fixed length between the users clicks while maintaining the angle the user has mapped out is a little trickier. Every line has to be say 100px no matter whether the user has clicked 10px apart or 2000px apart. Let’s have a go.

Drawing a line between 2 points can be achieved as follows;

var drawing = false, startX, startY; function drawScreen(x, y){ if (!drawing){ ctx.beginPath(); startX = x; startY = y; ctx.moveTo(startX, startY); drawing=true; canvas.style.cursor="crosshair"; }else{ ctx.lineTo(x,y); ctx.stroke(); drawing=false; canvas.style.cursor="default"; }

This is a very basic example and not entirely a good one as it relies on the user to click in 2 points before doing anything else. It would be much better to use a mousedown and then a mouseup event to signal the start point and the end point of the line, this would also be more intuitive for the user as it’s perhaps more of a natural way to draw a line with a mouse. Either way for now the example works.

The problem with this line is that it’s not the fixed length line we need, the user can make it as long or as short as they want and thus it won’t be to scale for us.

To limit the line length we need to do some trigonometry and use some Pythagoras theorem.

We can use Pythagoras Theorem to work out the length of the line we’ve drawn in the above example.

Here we can see 2 points on a canvas (MP1 & 2) these are where the user has clicked. We can make a right angled triangle from these points and we know the length of the X and Y sides as both points would have an X and Y co ordinate depending on where the mouse was clicked. The length of X is MP2 (x) – MP1 (x) and the length of Y is MP2 (y) – MP1 (y);

I assume you know how to find points on a canvas already if you are looking at this post so I won’t go into detail there. So far so good. Now we could work out the length of the side labelled ‘?’ , this is the hypotenuse but we aren’t really interested in it yet. All we know is that we want to limit the length of that line, whether it’s to a larger or smaller size, it can be any length we want, we just want every line we draw to be the same length. Obviously if we want to limit the length of that line we are going to have to modify X and Y lengths to correctly plot the 2nd point and keep ‘?’ constant.

This is where good old trigonometry comes in.

SOH CAH TOA

Sin Opposite / Hypotenuse

Cos Adjacent / Hypotenuse

Tan Opposite / Adjacent

Now as we know the length of the opposite side and adjacent side we can see that we need to use Tan. We nee to find the angle of the corner nearest to MP1 in the above diagram as that will tell us the direction of the MP2 click but more importantly it will allow us to refactor X and Y once we’ve decided on a length for ‘?’.

So lets get javascript going.

var newPosX = x - startX; var newPosY = y - startY;

This is just me setting about working out the length of the X and Y sides

var theta = newPosY/newPosX; theta = Math.atan(theta);

This gives us that angle but don’t forget this is in radians as that’s how javascript works so if you want to check the angle out in degrees you will need to do a little converting. We are using atan here too as its the same as ‘inverse tan’, or arctan as it’s known, which we need to use to work out the angle at MP1.

You can convert from radians to degrees as shown below.

//unnecessary unless you want to console.log or use the degree value, you will need to convert back to radians for the next step. theta = theta*180/Math.PI;

So now we know the angle in that corner and we have chosen a maximum length for our line (‘?’ the hypotenuse) we are able to use cos to work out the new value for the X co-ordinate.

// multiply theta by Math.PI/180 if you converted to degrees above. var cosX = Math.cos(theta); newPosX = cosX*screenLength;

Above we have worked out a value for Cos and we have chosen a value for the hypotenuse so we use cos*hypotenuse = adjacent. The X side is our adjacent and so as you can see this will give us an X offset value or length of X that we can use to plot our new point! We still need a new value for Y though so that we can then give the co ordinates to our script to enable it to draw the 2nd point in the correct place.

We can use Pythagoras for this X² + Y² = ?². Because we know the new value of X and we know our fixed line length ‘?’ we can easily work out our new Y length.

In the example below lineLength is our fixed line length that we have chosen.

newPosX *= newPosX; lineLength*= lineLength; newPosY = lineLength - newPosX; newPosY = Math.sqrt(newPosY); newPosX = Math.sqrt(newPosX);

Now we have our correct values, we can add our new X and Y lengths to the original X and Y co ordinates of MP1 and this should let us draw a line that will only ever be a fixed length but will still be directionally correct as defined by where the user has clicked for a second time.

ctx.lineTo(startX + newPosX, startY + newPosY); ctx.stroke();

We still have one problem though if you try this. It currently only works in a 90 degree quadrant i.e. when the second mouse click results in a positive X value and a positive Y value it will work correctly if either X or Y or indeed if both are negative values to start with it will still draw a line but it will appear to be mirrored. (I’ll try to explain this better but just test it out and you will see.)

To overcome this problem we need to store a reminder right at the top of this script block to indicate whether we were dealing with a negative X or negative Y value at the beginning and then restore this to our new co ordinates once we’ve calculated them.

var newPosX = x - startX; var newPosY = y - startY; //Top of our script where we initialised newPosX and newPosY if (newPosX < 0){ negX = true; }else{ negX = false; } if (newPosY < 0){ negY = true; }else{ negY = false; }

Very simply I’m using an if statement that just stores whether X or Y was negative when we first worked out where the users second click had fallen in relation to the first and then at the bottom of the script just before we draw the new points to the canvas we can restore this polarity with another simple if statement.

(negX === true) ? newPosX *= -1: newPosX; (negY === true) ? newPosY *= -1: newPosY;

That should be it, now wherever the user clicks on the canvas the line that is drawn will only ever be of a fixed length but will still be directionally correct.

If you’ve read this far without getting confused with me I commend you, I will no doubt find a better way to do this or a better way to explain it in which case I’ll update this post.

Also I’m 99.9% sure that someone is going to come and tell me that there is a simple method for this that would have saved me a lot of time and effort and a lot of code but to be honest I’m just happy that this works and it hopefully gives you some better understanding too, after all that’s what coding is all about and it served as a rewarding exercise!