xxxxxxxxxx
234
// Press any key to generate a new random pose (upper body)
// Parameter:
let playerBodyInfo = { // Initialised with some default values (roughly mine)
"nose y-value": 165,
"nose-shoulder midpoint length": 50,
"shoulder midpoint-hip midpoint length": 150,
"shoulder-shoulder length": 90,
"shoulder-elbow length": 70,
"elbow-wrist length": 60
}
// Global variables (not parameters, so please don't change)
let pose;
let skeletalConnectionsNames = [
["nose", "left_eye"],
["nose", "right_eye"],
["left_eye", "left_ear"],
["right_eye", "right_ear"],
["left_shoulder", "right_shoulder"],
["left_shoulder", "left_elbow"],
["left_shoulder", "left_hip"],
["right_shoulder", "right_elbow"],
["right_shoulder", "right_hip"],
["left_elbow", "left_wrist"],
["right_elbow", "right_wrist"],
["left_hip", "right_hip"],
["left_hip", "left_knee"],
["right_hip", "right_knee"],
["left_knee", "left_ankle"],
["right_knee", "right_ankle"]
]
function generateRandomPose(attempts = 0) {
// Constraints / Ideas / Assumptions:
// - Nose should be in the middle 25% (horizontally) of the screen, and near the height of the player's nose originally (similar y-value)
// - 0 deg <= midpoint-shoulder-elbow angle (inside one) <= 180 deg (basically, the elbow should be outside the body, extending upwards)
// - 45 deg <= shoulder-elbow-wrist angle (inside one) <= 180 deg
// - Lengths can be slightly changed if wanted (especially the elbow-wrist one), but maybe I shouldn't (won't for now)
// - All parts should be within the center 80% (the nose and shoulders don't need to be tested, since they can't reach there anyways)
// - Also, parts shouldn't be too close to each other (realistically the thing we need to check for is wrists to each other and the nose)
// - First generate nose position (center of head), then shoulders, then so on. (though I think going for the midpoint of the nose and shoulder first (aka neck, but that doesn't exist) might be a better approach (as it would naturally tilt the head lower if the rotation is greater), but we might revisit this later)
let outerMargin = 0.1; // 10%, so points should be in the middle 80% of the detection area (webcam feed)
let minX = width * outerMargin
let maxX = width * (1 - outerMargin)
let minY = height * outerMargin
let maxY = height * (1 - outerMargin)
let partAttempts, leftShoulderToElbowAngle, rightShoulderToElbowAngle, leftElbowToWristAngle, rightElbowToWristAngle
// Initialised with some default values (roughly my measurements)
let pose = {
nose: {x: 320, y: 165},
left_shoulder: {x: 275, y: 215},
right_shoulder: {x: 365, y: 215},
left_hip: {x: 295, y: 365},
right_hip: {x: 345, y: 365},
left_elbow: {x: 220, y: 255},
right_elbow: {x: 420, y: 255},
left_wrist: {x: 200, y: 200},
right_wrist: {x: 440, y: 200}
}
// If it takes too many attempts to generate a pose, just give up and output the default pose
if (attempts > 100) {
print('Pose generation took too many attempts, returning default pose.')
return pose
}
// Nose
pose.nose.x = random(0.375, 0.625) * width // center 25%
pose.nose.y = random(-25, 25) + playerBodyInfo["nose y-value"] // y-value +- 25px of player's nose height
// Shoulders
let shoulderAngle = random(-PI/6, PI/6) // The angle from the nose to the shoulder's midpoint with origin below (think of a unit circle, but rotated clockwise 90 deg) (also equivalently, the angle from the left to right shoulder, on a normal unit circle). From -30 to 30 degrees
let shoulderMidpoint = {
x: pose.nose.x + sin(shoulderAngle) * playerBodyInfo["nose-shoulder midpoint length"],
y: pose.nose.y + cos(shoulderAngle) * playerBodyInfo["nose-shoulder midpoint length"]
}
pose.left_shoulder.x = shoulderMidpoint.x - cos(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
pose.left_shoulder.y = shoulderMidpoint.y + sin(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
pose.right_shoulder.x = shoulderMidpoint.x + cos(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
pose.right_shoulder.y = shoulderMidpoint.y - sin(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
// Hips
let hipMidpoint = { // The hip's midpoint is really just the shoulder's midpoint, but extended further, so we can calculate it in a similar fashion
x: pose.nose.x + sin(shoulderAngle) * (playerBodyInfo["nose-shoulder midpoint length"] + playerBodyInfo["shoulder midpoint-hip midpoint length"] + 50*0), // [Nvm, disabled for now] Added 50 in the end, to ensure it's long enough (I'm not using the hips for accuracy or points, but rather just to draw the outline)
y: pose.nose.y + cos(shoulderAngle) * (playerBodyInfo["nose-shoulder midpoint length"] + playerBodyInfo["shoulder midpoint-hip midpoint length"] + 50*0) // (as above ^)
}
pose.left_hip.x = hipMidpoint.x - cos(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
pose.left_hip.y = hipMidpoint.y + sin(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
pose.right_hip.x = hipMidpoint.x + cos(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
pose.right_hip.y = hipMidpoint.y - sin(shoulderAngle) * 0.5 * playerBodyInfo["shoulder-shoulder length"]
// Elbows
partAttempts = 0;
do {
if (++partAttempts > 10) return generateRandomPose(attempts + 1); // If it takes too many attempts to generate this part, just give up and start from scratch
leftShoulderToElbowAngle = random(PI/2, 3 * PI/2) + shoulderAngle // From 90 to 270 (-90) degrees on a normal unit circle (basically 0 to 180 degrees, with the left half of a circle (imagine the unit circle rotated anticlockwise 90 deg))
pose.left_elbow.x = pose.left_shoulder.x + cos(leftShoulderToElbowAngle) * playerBodyInfo["shoulder-elbow length"]
pose.left_elbow.y = pose.left_shoulder.y - sin(leftShoulderToElbowAngle) * playerBodyInfo["shoulder-elbow length"]
} while (
minX > pose.left_elbow.x || pose.left_elbow.x > maxX || // Check if it's within the acceptable horizontal range
minY > pose.left_elbow.y || pose.left_elbow.y > maxY // Check if it's within the acceptable verticle range
);
partAttempts = 0;
do {
if (++partAttempts > 10) return generateRandomPose(attempts + 1); // If it takes too many attempts to generate this part, just give up and start from scratch
rightShoulderToElbowAngle = random(-PI/2, PI/2) + shoulderAngle // From 270 (-90) to 90 degrees on a normal unit circle (basically 0 to 180 degrees, with the right half of a circle)
pose.right_elbow.x = pose.right_shoulder.x + cos(rightShoulderToElbowAngle) * playerBodyInfo["shoulder-elbow length"]
pose.right_elbow.y = pose.right_shoulder.y - sin(rightShoulderToElbowAngle) * playerBodyInfo["shoulder-elbow length"]
} while (
minX > pose.right_elbow.x || pose.right_elbow.x > maxX || // Check if it's within the acceptable horizontal range
minY > pose.right_elbow.y || pose.right_elbow.y > maxY // Check if it's within the acceptable verticle range
);
// Wrists
partAttempts = 0;
do {
if (++partAttempts > 10) return generateRandomPose(attempts + 1); // If it takes too many attempts to generate this part, just give up and start from scratch
leftElbowToWristAngle = random(1.25*PI, 2*PI) + leftShoulderToElbowAngle // random(PI/4, PI) // From 45 to 180 degrees on a normal unit circle. Will be rotated to account for the elbow's existing rotation
pose.left_wrist.x = pose.left_elbow.x + cos(leftElbowToWristAngle) * playerBodyInfo["elbow-wrist length"]
pose.left_wrist.y = pose.left_elbow.y - sin(leftElbowToWristAngle) * playerBodyInfo["elbow-wrist length"]
} while (
minX > pose.left_wrist.x || pose.left_wrist.x > maxX || // Check if it's within the acceptable horizontal range
minY > pose.left_wrist.y || pose.left_wrist.y > maxY || // Check if it's within the acceptable verticle range
dist(pose.nose.x, pose.nose.y, pose.left_wrist.x, pose.left_wrist.y) < 50 // Check if the wrist is too close to the nose
);
partAttempts = 0;
do {
if (++partAttempts > 10) return generateRandomPose(attempts + 1); // If it takes too many attempts to generate this part, just give up and start from scratch
rightElbowToWristAngle = random(0, 3/4 * PI) + rightShoulderToElbowAngle // From 270 (-90) to 90 degrees on a normal unit circle (basically 0 to 180 degrees, with the right half of a circle)
pose.right_wrist.x = pose.right_elbow.x + cos(rightElbowToWristAngle) * playerBodyInfo["elbow-wrist length"]
pose.right_wrist.y = pose.right_elbow.y - sin(rightElbowToWristAngle) * playerBodyInfo["elbow-wrist length"]
} while (
minX > pose.right_wrist.x || pose.right_wrist.x > maxX || // Check if it's within the acceptable horizontal range
minY > pose.right_wrist.y || pose.right_wrist.y > maxY || // Check if it's within the acceptable verticle range
dist(pose.nose.x, pose.nose.y, pose.right_wrist.x, pose.right_wrist.y) < 50 || // Check if the wrist is too close to the nose
dist(pose.left_wrist.x, pose.left_wrist.y, pose.right_wrist.x, pose.right_wrist.y) < 50 // Check if the wrist is too close to the other wrist
);
return pose;
}
function drawPoseOutline() {
// Draw the skeleton connections
for (let connection of skeletalConnectionsNames) {
if (!(connection[0] in pose) || !(connection[1] in pose)) continue;
let pointA = pose[connection[0]];
let pointB = pose[connection[1]];
stroke(255);
strokeWeight(10);
line(pointA.x, pointA.y, pointB.x, pointB.y);
}
// Draw keypoints
for (let partKey in pose) {
let part = pose[partKey]
// Draw circles at points
fill(255)
stroke(0)
strokeWeight(5)
circle(part.x, part.y, partKey === "nose" ? 25*2.5 : 25)
// Write the keypoint's name
fill(0);
stroke(128);
strokeWeight(2);
textStyle(BOLD)
textAlign(CENTER, CENTER);
text(partKey, part.x, part.y);
}
}
function setup() {
createCanvas(640, 480);
pose = generateRandomPose();
}
function draw() {
background(32);
drawPoseOutline();
}
function keyPressed() {
pose = generateRandomPose();
}