22nd March 2014
Sometimes it's fun just to code for the hell of it. Not for work, or a product or some greater aim, just to do something that interests you... So I thought it may be run to revisit some high school maths... obviously.
This article is a mix of a trigonometry primer and some general fun with HTML canvas. The javascript is available in the source (and on github) to see but is certainly not optimised or 'production ready' it's just here to detail the small amount of maths I delve in to, run the demos and act as a kickstarter for you.
Fig 1.1: The trigonometry everybody knows and loves.
As I'm sure you'll all remember from GCSE maths, trigonometry is the study of triangles. In the above diagram we can see the classic SOH CAH TOA mnemonic with the appropriate right angled triangles drawn. To use this mnemonic we must remember the following multiplication diagram:
Fig 1.2: Multiplication diagrams.
To calculate the length of the opposite side (Opp or O) we can use a calculator to multiply the Sine of the lower left angle by the Hypotenuse (Hyp or H). If we had the Opposite but we wanted the Hypotenuse then we would divide the Opposite by the Sine of the angle. Equally if we had the Hypotenuse but we wanted the Adjacent (Adj or A) we could multiply the Cosine of the angle by the Hypotenuse.
As an example: In the SOH CAH TOA diagram (fig1.1) the adjacent is 100 wide and the angle is 45°. In Javascript we can do the following:
var hyp = 100 / Math.cos((45).toRad()); // Outputs 141.4213562373095 console.log(hyp);
The toRad() function is required because the sin/cos/tan functions in javascript take the degree in radian units. A complete circle angle contains 2π radians (a value which we know as 360 degrees) meaning:
Radians to Degrees and back | |
---|---|
2π radians = 360 degrees | |
↓ | |
π radians = 180 degrees (dividing both by two) | |
↓ | |
1 radian = 180/π degrees (dividing both by π) | π/180 radians = 1 degrees (dividing both by 180) |
↓ | |
myRadians * (180/π) = degrees | myDegrees * (π/180) = radians |
The following diagram attempts to show how useful triangles are when we are working with circles:
Fig 2.1: Triangles in a circle.
When using a cartesian coordinate system we can use Cosine and Sine to calculate the X (Adjacent) and Y (Opposite) position values. This is because in a circle we always know the radius (which in the formulae we can substitute as the Hypotenuse) and our desired centre point (which we use as an offset to the Adjacent and Opposite to calculate the final x/y).
Html canvas allows us to draw lines from x/y to x/y and rectangles with a width and height at an x/y position. This is exactly how I'm drawing the circle above. Rendering a small rectangle at each X/Y coordinate returned for the 360 degrees in the circle. I've simply abstracted the generation of x/y points into a getCirclePoints function.
With the above information we can very easily distribute items around a circle. If we want 5 items we simply divide 360 by 5 to find the angle increments we require (0, 72, 144, 216, 288). We then use these angles with Cosine and Sine as above, using our chosen radius size (the hypotenuse), to find the 2D points.
In the below diagram I've simply drawn lines on the canvas from the centre of a circle out to the points calculated in this way.
Fig 3.1: Distributing points around a circle.
Obviously all you need to do to draw this as a polygon instead is to join the points rather than draw out from the centre.
With these points available to us we can draw more than just lines. For example images:
Fig 4.1: Our first carousel.
As the above shows it is also very easy to apply a rotation over time by simply adding an increasing offset to the degrees for each item. So with three images they are first drawn at degrees [0, 120, 240] then [1, 121, 241] and so on in an animation loop.
To assist in this animation I created a small Animation class that wraps calls to requestAnimationFrame (a built in browser function that calls back to your client code at approximately 60fps), shims it for multi browser support and allows the user to specify their own fps within that provided by the browser.
The rotating demo looks a lot like a carousel. However we don't normally view a carousel head on like this. The circle is tilted away from us into the third dimension. This next diagram should explain how we can get this effect:
Fig 5.1: How to tilt a circle.
The dashed line is the screen viewed from the side with the circle of images (green line) flat upon it. We can see how trigonometry can help us to calculate the adjacent length using the hypotenuse. The Adj shown in the diagram is the position of the bottom most point on a circle that has its top rotated back into the screen - but we need to translate all the points.
To do this we need the varying lengths of the hypotenuse for each point in the circle. Luckily we have this - it's the Y value we would have plotted the item on screen with if there were no tilt.
Remember: For an untilted circle the screen top is y position zero and the Screen Bottom is Y = Circle Diameter. Therefore:
Original 'flat' y offset from the circle centre: | yPosition = Math.sin(DegreesAroundCircle) * Radius | (Opp = Sinθ * Hyp) |
---|---|---|
Foreshortened y position: | AdjustedYPosition = Math.cos(DegreesTilt) * yPosition | (Adj = Cosθ * Hyp) |
Final plot point: | Circle.CentreY + AdjustedYPosition |
Fig 6.1: Tilted circle.
With this tilt knowledge in place we can relatively easy tilt the original basic circle information diagram.
The above will give us a carousel where the plotted images take into account a tilt away from the viewer. This is fine for a line/point diagram however to give the impression of the images on a carousel moving into the third dimension we need to scale the items in the distance. This can be achieved very simply by identifying the Z axis depth as a percentage of the diameter. At 0 we use the maxmimum scale and at 1 we use the minimum scale with proportions in between.
By drawing the items to the canvas in the correct z-order we then get the appropriate overlap. Maintaining objects with a z-index also assists us in hittesting items if we start to detect mouse events.
Fig 7.1: A tilted carousel.
The closer the angle gets to 90° the more the carousel looks like cover flow.
With these basic steps in place we can begin to add adjustments to the values. If you've been looking at the javascript code as we've gone along you may have noticed that I pass a 'yAdjustmentFunc' into a few of the functions. This is to allow me, after I've calculated the intended canvas x/y, to apply some kind of variance to the position based on the angle.
For example the below carousel displays an additional y adjustment based on a Sine Wave (and it changes the pictures.. because.. why not).
Fig 8.1. An actual carousel (Horse image from HBruton's profile on Deviant Art)
Hopefully this has been a whistle stop reminder tour of some very simple mathematics. Certainly if you wanted to do complete interactive 3D you may prefer WebGL and manipulating points/vectors is often done with matrix transformations.
This was purely an exercise in taking a simple piece of geometry and slowly developing it into something a little more complex.
You may have heard people talk about Sine Wave's and not fully understood the meaning. It relates to a regular up and down wave form that can be mathematically calculated using the sine of an ever increasing angle. Note in the diagram below that the Y position of the wave is tracking the y position of the point at the end of the drawn radius.
Fig 9.1: A sine wave explained.
All article content is licenced under a Creative Commons Attribution-Noncommercial Licence.