Scenario: I'm programming a module to draw a deck of cards on a canvas as though they were being held by a person.
Edit:
I've cleaned up the question as best I can to be clearer.
What I'm looking for now:
The X,Y coords of each star, which represent the center of each card, as well as its rotation from that point to form a semi-circle.
To make it simpler than before, you can now also assume:
- The total angle the cards cover is 90º.
- There are 5 cards.
- The middle card will always be at X = 0
- Each end card will be on a rotation of 30º (the left side will be -30º)
- The canvas is a fixed size. Note: I put 200 as the height, though this is somewhat of an arbitrary number and I don't even know if it helps or not.
I should also mention that drawing is not exactly to scale, I did the best I can with a somewhat primitive tool for drawing geometric shapes
Solution:
The source for my solution can be found here: https://github.com/tcrosen/playing-cards
A working demo can be found here: http://tcrosen.github.com/playing-cards/demo.html
function drawHand(el, originX, originY, numberOfCards, cardWidth, cardHeight, showOrigin) { // The angle off the origin that each card will be referenced from // The +1 is added because we want the first card to be positioned above the origin var angle = 180 / (numberOfCards + 1); // How far each card will be from the origin of the hand. // This is proportional to the size of the card so that larger cards avoid too much overlap var radius = cardWidth * 1.2; // Through trial & error I determined a small hand (3-5 cards) looks most realistic // when the end cards are at a rotation of 30 degrees (90 - 5 * 12). However when larger hands are created // the end cards must be rotated at a larger angle in order to be "held" properly. Anything that would // calculate to an angle > 30 (6 cards or more) is simply capped at 45 degrees. var endRotation = 12 * numberOfCards > 60 ? 45 : 90 - 12 * numberOfCards; // Find an equal angle to split the cards across the entire hand var rotationIncrement = endRotation * 2 / (numberOfCards + 1); // If the user wants to see the origin for debugging/design purposes, show an X there if (showOrigin) { $(el).append($('X').css('color', 'red').css('position', 'absolute').css('top', originY + 'px').css('left', originX + 'px')); } // Loop through each card // *Note: I start at 1 (instead of 0) in order to avoid multiplying by 0 and ending up with flat angles. // If you are using an array of cards (eventual scenario) you would need to account for the 0 index for (var i = 1; i <= numberOfCards; i++) { // Set the card rotation - always rotate from the end point so use the card number as a multiplier var rotation = endRotation - rotationIncrement * i; // The X,Y coordinates of each card. // Note that the origin X,Y is added to each coordinate as a hand would almost never be generated from 0,0 // on an HTML canvas. var x = radius * Math.cos(toRadians(angle * i)) + originX; var y = radius * Math.sin(toRadians(-angle * i)) + originY; // This next algorithm is used to push the cards "up" by a larger amount the further you get from the middle of the hand. // This is done because a higher number of cards will start to push down and form a sharper circle. // By moving them up it evens out the semi-circle to appear like something that would be more realistically held by a human. // And as usual, this value is affected by existing variables to always position the hand based on its previous metrics. y = y - Math.abs(Math.round(numberOfCards / 2) - i) * rotationIncrement; // HTML positions elements relative to the top left corner, but the CSS3 "rotation" property uses the center. // So I cut the values in half and use the center to position the cards. // *Note: I realize both this and the previous line could have been included in the first X,Y calculation. They are separated for clarity. x = x - cardWidth / 2; y = y - cardHeight / 2; // Create the card using my jQuery plugin var $card = $('').card({ width: cardWidth, text: i, rotation: rotation, top: y, left: x }); // Draw it in the parent element $(el).append($card); } } // Helper function to convert to radians from degrees since Javascript's Math library defaults to radians. function toRadians(degrees) { return degrees * Math.PI / 180; }
Here are some screenshots of the results in action with different values: