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:
