JavaScript
语言:
JaveScriptBabelCoffeeScript
确定
{
class Point {
constructor(x, y, mass) {
this.x = x;
this.y = y;
this.ox = x;
this.oy = y;
this.mass = mass;
points.push(this);
}
integrate() {
if (!this.mass) return;
const x = this.x;
const y = this.y;
this.x += this.x - this.ox;
this.y += this.y - this.oy + 0.5;
this.ox = x;
this.oy = y;
if (this.y < p1.y) this.y = p1.y;
}
link(p, visible) {
new Link(this, p, visible);
return this;
}
}
class Link {
constructor(p0, p1, visible) {
this.p0 = p0;
this.p1 = p1;
this.visible = visible;
const dx = p0.x - p1.x;
const dy = p0.y - p1.y;
this.dist = Math.sqrt(dx * dx + dy * dy);
links.push(this);
}
// solve constraint
solve() {
let dx = this.p0.x - this.p1.x;
let dy = this.p0.y - this.p1.y;
const currentDist = Math.sqrt(dx * dx + dy * dy);
if (currentDist === 0) return;
const delta = 1.0 * (currentDist - this.dist) / currentDist;
dx *= delta;
dy *= delta;
let m1 = this.p0.mass + this.p1.mass;
let m2 = this.p0.mass / m1;
m1 = this.p1.mass / m1;
this.p1.x += dx * m1;
this.p1.y += dy * m1;
this.p0.x -= dx * m2;
this.p0.y -= dy * m2;
}
// draw constraint
draw() {
if (!this.visible) return;
ctx.beginPath();
ctx.lineWidth = 3;
ctx.lineCap = "round";
ctx.moveTo(this.p0.x, this.p0.y);
ctx.lineTo(this.p1.x, this.p1.y);
ctx.strokeStyle = "#111";
ctx.stroke();
}
}
// build a new chain
const chain = (parent, len, num, mass) => {
let p = null;
let b = parent;
for (let i = 0; i < num; ++i) {
p = new Point(parent.x, b.y + len, mass).link(b, true);
b = p;
}
return p;
};
// setup canvas
const canvas = {
init: function() {
this.elem = document.getElementById("c");
const ctx = this.elem.getContext("2d", {
alpha: false
});
this.resize();
window.addEventListener("resize", () => canvas.resize(), false);
return ctx;
},
resize() {
this.width = this.elem.width = this.elem.offsetWidth;
this.height = this.elem.height = this.elem.offsetHeight;
}
};
// setup pointer
const pointer = {
init(canvas) {
this.x = this.dx = this.endX = this.startX = canvas.width * 0.5;
this.y = this.dy = this.endY = this.startY = canvas.height * 0.1;
this.isDown = false;
window.addEventListener("mousemove", e => this.move(e), false);
canvas.elem.addEventListener("touchmove", e => this.move(e), false);
window.addEventListener("mousedown", e => this.down(e), false);
window.addEventListener("touchstart", e => this.down(e), false);
window.addEventListener("mouseup", e => this.up(e), false);
window.addEventListener("touchend", e => this.up(e), false);
window.addEventListener("touchcancel", e => this.up(e), false);
},
move(e) {
let touchMode = e.targetTouches,
p;
if (touchMode) {
e.preventDefault();
p = touchMode[0];
} else p = e;
this.x = p.clientX;
this.y = p.clientY;
if (this.isDown) {
this.dx = this.endX + (this.x - this.startX);
this.dy = this.endY + (this.y - this.startY);
}
},
down(e) {
this.move(e);
this.startX = this.x;
this.startY = this.y;
this.isDown = true;
scr.style.pointerEvents = "none";
},
up(e) {
this.isDown = false;
this.endX = this.dx;
this.endY = this.dy;
scr.style.pointerEvents = "auto";
}
};
// pen setup
const ctx = canvas.init();
pointer.init(canvas);
// Screen
const scale = 0.35;
const scr = document.getElementById("screen");
scr.style.width = 2 * canvas.width * scale + "px";
scr.style.height = canvas.width * scale + "px";
// Engine
const points = [];
const links = [];
const p1 = new Point(
canvas.width * 0.5 - canvas.width * scale * 0.5,
pointer.y,
0
);
const p2 = new Point(
canvas.width * 0.5 + canvas.width * scale * 0.5,
pointer.y,
0
);
// chains
const c1 = chain(p1, canvas.height * 0.5 / 20, 20, 1);
const c2 = chain(p2, canvas.height * 0.5 / 20, 20, 1);
// box
const b1 = new Point(c1.x, c1.y, 2).link(c1, false);
const b2 = new Point(c2.x, c2.y, 2).link(c2, false).link(b1, false);
const b3 = new Point(c1.x, c1.y + canvas.width * scale * 0.5, 2)
.link(b1, false)
.link(b2, false);
const b4 = new Point(c2.x, c2.y + canvas.width * scale * 0.5, 2)
.link(b2, false)
.link(b3, false)
.link(b1, false);
// animation loop
const run = () => {
requestAnimationFrame(run);
// pointer
p1.x = pointer.dx - canvas.width * scale * 0.25;
p2.x = pointer.dx + canvas.width * scale * 0.25;
p1.y = pointer.dy;
p2.y = pointer.dy;
// verlet integration
for (let p of points) {
p.integrate();
}
// solve constraints
for (let i = 0; i < 10; ++i) {
for (let link of links) {
link.solve();
}
}
// draw things
ctx.fillStyle = "#222";
ctx.fillRect(0, 0, canvas.width, p1.y);
ctx.fillStyle = "#aaa192";
ctx.fillRect(0, p1.y, canvas.width, canvas.height - p1.y);
for (let link of links) {
link.draw();
}
ctx.beginPath();
ctx.strokeStyle = "#555";
ctx.moveTo(0, p1.y);
ctx.lineTo(canvas.width, p1.y);
ctx.stroke();
ctx.beginPath();
ctx.fillStyle = "#aaa192";
ctx.font = "150px Raleway Dots";
ctx.fillText("techbrood", p1.x - 280, p1.y - 50);
ctx.fillStyle = "#ccb9a0";
ctx.arc(p1.x, p1.y, 12, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.arc(p2.x, p2.y, 12, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
// move iFrame
const nx = c2.x - c1.x;
const ny = c2.y - c1.y;
const rot = Math.atan2(ny, nx) / Math.PI * 180;
scr.style.transform =
"translate(" +
c1.x +
"px, " +
c1.y +
"px) rotate(" +
rot +
"deg) translate(" +
-canvas.height * 0.05 +
"px, " +
-canvas.height * 0.01 +
"px) scale(0.5)";
};
run();
}