Skip to content

Instantly share code, notes, and snippets.

@develax
Created July 8, 2019 09:31
Show Gist options
  • Save develax/30f6596239471cc57cbd945af26271fb to your computer and use it in GitHub Desktop.
Save develax/30f6596239471cc57cbd945af26271fb to your computer and use it in GitHub Desktop.
Forward and inverse kinematics, rendering on HTML5 canvas.Thanks https://www.youtube.com/user/codingmath for the science and neighbor Franck for the rich discussions. Playful demo: https://rawgit.com/patkap/cc1a2c3ce3ef97c5a059a35af9e0cd59/raw/f23eb041f37633fbe22e3c82993e7a255bb32a08/kinematics.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,user-scalable=no">
<title>Guidage</title>
<style type="text/css">
body {
display: flex;
min-height: 100%;
justify-content: center;
align-items: center;
flex-direction: column;
font-family: sans-serif;
margin: 0;
padding: 0;
}
.container {
/*display: flex;
justify-content: center;
align-content: center;
align-items: center;
*/
width: 100%;
height: 100%;
}
.canvas_container {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
}
canvas {
display: block;
/*border: 1px solid red;*/
width: 100% !important;
height: 100% !important;
cursor: crosshair;
}
.controls {
position: absolute;
right: 3em;
top: 3em;
z-index: 2;
}
#pointer_debug {
pointer-events: none;
}
.onoffswitch {
position: relative; width: 55px;
-webkit-user-select:none; -moz-user-select:none; -ms-user-select: none;
}
.onoffswitch-checkbox {
display: none;
}
.onoffswitch-label {
display: block; overflow: hidden; cursor: pointer;
height: 20px; padding: 0; line-height: 20px;
border: 0px solid #FFFFFF; border-radius: 30px;
background-color: #9E9E9E;
}
.onoffswitch-label:before {
content: "";
display: block; width: 30px; margin: -5px;
background: #FFFFFF;
position: absolute; top: 0; bottom: 0;
right: 31px;
border-radius: 30px;
box-shadow: 0 6px 12px 0px #757575;
}
.onoffswitch-checkbox:checked + .onoffswitch-label {
background-color: #FC6065;
}
.onoffswitch-checkbox:checked + .onoffswitch-label, .onoffswitch-checkbox:checked + .onoffswitch-label:before {
border-color: #FC6065;
}
.onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-inner {
margin-left: 0;
}
.onoffswitch-checkbox:checked + .onoffswitch-label:before {
right: 0px;
background-color: #F5232A;
box-shadow: 3px 6px 18px 0px rgba(0, 0, 0, 0.2);
}
</style>
</head>
<body>
<div class="container">
<figure class="canvas_container">
<canvas id="drawing_area"></canvas>
</figure>
<div class="controls">
<div class="onoffswitch">
<input type="checkbox" name="onoffswitch" id="grab_mouse" class="onoffswitch-checkbox">
<label class="onoffswitch-label" for="grab_mouse">
<h4>Ancrer</h4>
</label>
</div>
<pre id="pointer_debug"></pre>
</div>
</div>
<script>
function Segment(x, y, len, angle, color) {
let origin = {x: x, y: y},
parent = null;
return {
origin,
len,
angle,
parent,
color,
end_x() {
return this.origin.x + this.len * Math.cos(this.angle)
},
end_y() {
return this.origin.y + this.len * Math.sin(this.angle)
},
draw(context) {
context.strokeStyle = this.color;
context.lineWidth = 15;
context.lineCap = 'round';
context.beginPath();
context.moveTo(this.origin.x, this.origin.y);
context.lineTo(this.end_x(), this.end_y());
context.stroke();
},
move(x, y) {
let dx = x - this.origin.x ,
dy = y - this.origin.y ;
this.angle = Math.atan2(dy, dx);
this.origin.x = x - Math.cos(this.angle) * this.len;
this.origin.y = y - Math.sin(this.angle) * this.len;
if (this.parent) {
this.parent.move(this.origin.x, this.origin.y);
}
}
}
}
function Arm(x, y) {
let origin = {x: x, y: y},
segments = [],
last_segment = null;
return {
origin,
segments,
last_segment,
addSegment(len, color) {
let segment = Segment(0, 0, len, 0, color /* , constraints */);
if (this.last_segment) {
segment.origin.x = this.last_segment.end_x();
segment.origin.y = this.last_segment.end_y();
segment.parent = this.last_segment;
} else {
segment.origin.x = this.origin.x;
segment.origin.y = this.origin.y;
}
this.segments.push(segment);
this.last_segment = segment;
},
draw(context) {
this.segments.forEach( (segment) => {
segment.draw(context);
});
},
grab(x, y) {
this.last_segment.move(x, y);
},
reach(x, y) {
this.grab(x, y);
this.segments.forEach( (segment) => {
if (segment.parent) {
segment.origin.x = segment.parent.end_x();
segment.origin.y = segment.parent.end_y();
} else {
segment.origin.x = this.origin.x;
segment.origin.y = this.origin.y;
}
});
}
}
}
let canvas = document.querySelector('#drawing_area'),
context = canvas.getContext('2d');
function resizeCanvas() {
canvas.width = window.innerWidth ||
document.documentElement.clientWidth ||
document.body[0].clientWidth;
canvas.height = window.innerHeight ||
document.documentElement.clientHeight ||
document.body[0].clientHeight;
init();
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas, false);
window.addEventListener('orientationchange', resizeCanvas, false);
function init() {
let mouse = {
x: canvas.width / 2,
y: canvas.height / 2
};
let arm = Arm(canvas.width / 2, canvas.height / 2);
let colors = ['rgba(200, 0, 0, 0.8)', 'rgba(0, 200, 0, 0.8)', 'rgba(0, 0, 200, 0.8)',
'rgba(0, 232, 255, 0.8)', 'rgba(255, 0, 207, 0.8)', 'rgba(255, 230, 0, 0.91)'];
colors.forEach( (color) => {
arm.addSegment(Math.min(canvas.width, canvas.height) / (colors.length * 2), color );
});
function update() {
context.clearRect(0, 0, canvas.width, canvas.height);
if (document.querySelector("#grab_mouse").checked) {
arm.grab(mouse.x, mouse.y);
} else {
arm.reach(mouse.x, mouse.y); // fixed base.
}
arm.draw(context);
window.requestAnimationFrame(update);
}
update();
function round(key, value) {
if (typeof value === "number") {
return Math.round(value);
}
return value;
}
function handleMouseEvent(e) {
let rect = canvas.getBoundingClientRect();
mouse.x = (e.clientX - rect.left) * e.target.width / canvas.clientWidth;
mouse.y = (e.clientY - rect.top) * e.target.height / canvas.clientHeight;
// posNode.nodeValue = JSON.stringify(mouse, round, 2);
}
function handleTouchEvent(e) {
let touch = e.touches[0],
rect = canvas.getBoundingClientRect();
mouse.x = (touch.pageX - rect.left) * e.target.width / canvas.clientWidth;
mouse.y = (touch.pageY - rect.top) * e.target.height / canvas.clientHeight;
// posNode.nodeValue = JSON.stringify(mouse, round, 2);
}
function follow_mouse() {
canvas.addEventListener('mousemove', (e) => {
handleMouseEvent(e);
}, false);
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
handleTouchEvent(e);
}, false);
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
handleTouchEvent(e);
}, false);
}
follow_mouse();
}
let pointer_debug = document.createTextNode("");
document.querySelector("#pointer_debug").appendChild(pointer_debug);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment