Created
July 8, 2019 09:31
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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