<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Canvas 瑜伽表演/牵线木偶/皮影戏</title>
<style>
html {
overflow: hidden;
touch-action: none;
content-zooming: none;
}
body {
position: absolute;
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: #000;
}
canvas {
position: absolute;
width: 100%;
height: 100%;
background: #c9c4b5;
}
.draggable {
cursor: pointer;
cursor: -webkit-grab;
}
.dragging {
cursor: move;
cursor: -webkit-grabbing;
}
</style>
</head>
<body>
<script>
! function() {
"use strict";
var DEBUG = false;
Math.sign = Math.sign || function(x) {
return x > 0 ? 1 : -1;
}
// main loop
function run() {
requestAnimationFrame(run);
ctx.clearRect(0, 0, canvas.width, canvas.height);
// dragging
if (pointer.drag) {
pointer.drag.x += (pointer.x - pointer.drag.x) / 20;
pointer.drag.y += (pointer.y - pointer.drag.y) / 20;
}
// static points
scene.static();
// draw scene
if (frame++ > 15) {
ctx.save();
var s = skins;
while (s) s = s.draw();
ctx.restore();
}
// verlet integration
draggable = false;
var p = points;
while (p) p = p.integrate();
canvas.elem.className = pointer.drag ? "dragging" : (draggable ? "draggable" : "default");
// solve constraints
var c = constraints;
while (c) c = c.solve();
// show constraints and points
if (DEBUG) {
var c = constraints;
while (c) c = c.draw();
}
}
// point 2D constructor
function Point(x, y, radius, mass, gravity) {
this.x = canvas.width * 0.5 + x;
this.y = y;
this.oldX = this.x;
this.oldY = y;
this.radius = radius || 1;
this.mass = mass || 1.0;
this.friction = kFriction;
this.gravity = gravity || kGravity;
this.next = null;
!points && (points = this);
last && (last.next = this);
last = this;
}
// set position
Point.prototype.position = function(x, y) {
this.x = x;
this.y = y;
}
// verlet integration
Point.prototype.integrate = function() {
var x = this.x;
var y = this.y;
this.x += (this.x - this.oldX) * this.friction;
this.y += (this.y - this.oldY) * this.friction + this.gravity;
this.oldX = x;
this.oldY = y;
// bottom + friction
if (this.y > canvas.height - this.radius) {
var d = Math.min(20, this.y - canvas.height + this.radius);
this.x -= d * (this.x - this.oldX) / 5;
this.y = canvas.height - this.radius;
}
// cursor style
var dx = this.x - pointer.x;
var dy = this.y - pointer.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < this.radius * 2) draggable = true;
return this.next;
}
// calculate distance between 2 points
Point.prototype.dist = function(p) {
var dx = this.x - p.x;
var dy = this.y - p.y;
return Math.sqrt(dx * dx + dy * dy);
}
// Angled Constraint constructor
function AngleConstraint(p1, p2, p3, angle, range, force) {
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
this.len1 = p1.dist(p2);
this.len2 = p2.dist(p3);
this.angle = angle;
this.range = range;
this.force = force || 0.2;
!constraints && (constraints = this);
last && (last.next = this);
last = this;
}
// solve 2 vectors angled (+ stick) constraint
// http://stackoverflow.com/questions/16336702/ragdoll-joint-angle-constraints
AngleConstraint.prototype.solve = function() {
var a, b, c, e, m, m1, m2, m3, x1, y1, cos, sin;
a = Math.atan2(this.p2.y - this.p1.y, this.p2.x - this.p1.x);
b = Math.atan2(this.p3.y - this.p2.y, this.p3.x - this.p2.x);
c = this.angle - (b - a);
c = c > Math.PI ? (c - 2 * Math.PI) : (c < -Math.PI ? (c + 2 * Math.PI) : c);
e = (Math.abs(c) > this.range) ? (-Math.sign(c) * this.range + c) * this.force : 0;
m = this.p1.mass + this.p2.mass;
m1 = this.p1.mass / m;
m2 = this.p2.mass / m;
cos = Math.cos(a - e);
sin = Math.sin(a - e);
x1 = this.p1.x + (this.p2.x - this.p1.x) * m2;
y1 = this.p1.y + (this.p2.y - this.p1.y) * m2;
this.p1.x = x1 - cos * this.len1 * m2;
this.p1.y = y1 - sin * this.len1 * m2;
this.p2.x = x1 + cos * this.len1 * m1;
this.p2.y = y1 + sin * this.len1 * m1;
a = Math.atan2(this.p2.y - this.p3.y, this.p2.x - this.p3.x) + e;
m = this.p2.mass + this.p3.mass;
m2 = this.p2.mass / m;
m3 = this.p3.mass / m;
cos = Math.cos(a);
sin = Math.sin(a);
x1 = this.p3.x + (this.p2.x - this.p3.x) * m2;
y1 = this.p3.y + (this.p2.y - this.p3.y) * m2;
this.p3.x = x1 - cos * this.len2 * m2;
this.p3.y = y1 - sin * this.len2 * m2;
this.p2.x = x1 + cos * this.len2 * m3;
this.p2.y = y1 + sin * this.len2 * m3;
return this.next;
}
// draw angle constraint (DEBUG mode)
AngleConstraint.prototype.draw = function() {
ctx.beginPath();
ctx.moveTo(this.p1.x, this.p1.y);
ctx.lineTo(this.p2.x, this.p2.y);
ctx.lineTo(this.p3.x, this.p3.y);
ctx.stroke();
ctx.beginPath();
ctx.arc(this.p1.x, this.p1.y, this.p1.radius * 2, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.arc(this.p2.x, this.p2.y, this.p2.radius * 2, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.arc(this.p3.x, this.p3.y, this.p3.radius * 2, 0, Math.PI * 2);
ctx.stroke();
return this.next;
}
// simple stick constraint constructor
function Constraint(p1, p2, force, len) {
this.p1 = p1;
this.p2 = p2;
this.len = len || p1.dist(p2);
this.force = force || 2;
!constraints && (constraints = this);
last && (last.next = this);
last = this;
}
// solve stick constraint
Constraint.prototype.solve = function() {
var d, dx, dy, s1, s2, tm;
dx = this.p1.x - this.p2.x;
dy = this.p1.y - this.p2.y;
d = Math.sqrt(dx * dx + dy * dy);
tm = this.p1.mass + this.p2.mass;
d = (d - (d + (this.len - d) * this.force)) / d * 0.5;
s1 = d * (this.p1.mass / tm);
s2 = d * (this.p2.mass / tm);
this.p1.x = this.p1.x - dx * s2;
this.p1.y = this.p1.y - dy * s2;
this.p2.x = this.p2.x + dx * s1;
this.p2.y = this.p2.y + dy * s1;
return this.next;
}
// draw constraint (DEBUG mode)
Constraint.prototype.draw = function() {
ctx.beginPath();
ctx.moveTo(this.p1.x, this.p1.y);
ctx.lineTo(this.p2.x, this.p2.y);
ctx.stroke();
ctx.beginPath();
ctx.arc(this.p1.x, this.p1.y, this.p1.radius * 2, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.arc(this.p2.x, this.p2.y, this.p2.radius * 2, 0, Math.PI * 2);
ctx.stroke();
return this.next;
}
// skin constructor
function Skin(img