<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>弹性双摆</title>
<style>
html, body {
width: 100%;
height: 100%;
overflow: hidden;
}
body {
background: black;
}
p {
color: white;
position: absolute;
text-align: center;
bottom: 20px;
width: 100%;
font-size: 12px;
font-family: Arial, sans-serif;
animation: fadeInOut 800ms infinite alternate;
}
@keyframes fadeInOut {
from { opacity: 0.3; }
to { opacity: 0.7; }
}
</style>
</head>
<body>
<script type="text/javascript" src="js/underscore-min.js"></script>
<script type="text/javascript">
var App = {};
document.addEventListener("DOMContentLoaded", function(event) {
// Setup canvas and app
App.setup();
// Launch animation loop
App.frame = function() {
App.update();
window.requestAnimationFrame(App.frame);
};
App.frame();
});
App.setup = function() {
// Setup canvas and get canvas context
var canvas = document.createElement('canvas');
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
canvas.id = 'ourCanvas';
document.body.appendChild(canvas);
this.ctx = canvas.getContext('2d');
this.ctx.rect(0, 0, this.width, this.height);
this.ctx.fillStyle = 'black';
this.ctx.fill();
this.width = canvas.width;
this.height = canvas.height;
// Add event liteners
canvas.addEventListener('mousemove', function(event) {
App.target.x = event.pageX;
App.target.y = event.pageY;
});
canvas.addEventListener('mouseleave', function(event) {
App.target = {
x: App.xC,
y: App.yC
};
});
canvas.addEventListener('mousedown', function(event) {
App.showArms = !App.showArms;
});
// Define a few useful elements
this.stepCount = 0;
this.xC = canvas.width / 2;
this.yC = canvas.height / 2;
this.target = {
x: undefined,
y: undefined
};
this.grid = [];
this.h = 80;// Step size (px)
this.gridXnodes = 0;
this.gridYnodes = 0;
this.springStiffnessBase = 40;
this.springStiffness = this.springStiffnessBase;// Might be altered on the fly, huehuehuehue
this.springLength = 80;// Default spring Length (no constraint)
this.viscosity = 0.00001;
this.viscositude = 0.00001;// Some kind of super viscosity I invented that will slow down particles based on their speed^3, helps prevent system explosion
this.gravity = 2;
this.tau = 0.7;
this.pendulumInitialXspeed = 50;
this.pendulumYdepth = 2;
this.pendulumXdepth = 0;// Try playing with that one
this.showArms = false;// Gets toggled on click
// Release madness
this.setupGrid(this.h);
};
App.setupGrid = function(h) {
// Create some nodes, initially a squared mesh
for (var x = 0.5 * this.width; x <= 0.5 * this.width + this.pendulumXdepth * h; x += h) {
var col = [];
for (var y = 0.4 * this.height; y <= 0.4 * this.height + this.pendulumYdepth * h; y += h) {
var particle = {
x: x,
y: y,
xLast: x,
yLast: y,
xSpeed: 0,
ySpeed: 0,
m: 250,
hue: x * y / 100,
fixed: false
}
col.push(particle);
}
// Set horizontal speed to bottommost particle
col[col.length - 1].xSpeed = this.pendulumInitialXspeed;
this.grid.push(col);
}
// Update grid node size
this.gridXnodes = this.grid.length;
this.gridYnodes = this.grid[0].length;
// Stabilize some particles (fixed contraits)
/*var leftWall = this.grid[0],
rightWall = this.grid[this.grid.length - 1],
bottomWall = [];
for (var c = 0; c < this.grid.length; c++) {
// Add last particle on the column (bottom)
bottomWall.push(this.grid[c][this.grid[c].length - 1]);
}
var walls = _.union(leftWall, rightWall, bottomWall);*/
var walls = [this.grid[0][0]];// Just the first particle (pendulum)
_(walls).each(function(particle) {
particle.fixed = true;
});
};
App.update = function() {
// Evolve system
this.evolve();
// Move particles
this.move();
// Draw particles
this.draw();
};
App.evolve = function() {
this.stepCount++;
// Hahahaha this is not just a couple o'springs, but EVOLVING SPRINGS! Eww I'm so evil.
this.springStiffness = this.springStiffnessBase * (2 + Math.sin(this.stepCount / 200));
};
App.birth = function() {
// No one's born tho
var particle = {
x: this.width * Math.random(),
y: this.height * (0.5 + 0.5 * Math.random()),
xSpeed: 0,
ySpeed: 0,
name: 'seed' + this.stepCount
};
this.particles.push(particle);
};
App.kill = function(particleName) {
// No one dies tho
this.particles = _(this.particles).reject(function(seed) {
return (seed.name == particleName);
});
};
App.move = function() {
for (var i = 0; i < this.grid.length; i++) {
for (var j = 0; j < this.grid[0].length; j++) {
var particle = this.grid[i][j];
// Save previous position
particle.xLast = particle.x;
particle.yLast = particle.y;
// Calculate forces from neighbours
var particleWest = undefined,
particleEast = undefined,
particleNorth = undefined,
particleSouth = undefined;
var k = this.springStiffness,
l = this.springLength;
if (i > 0) {// Western neighbour
particleWest = this.grid[i-1][j];
var toWest = { x: particleWest.xLast - particle.x, y: particleWest.yLast - particle.y };
var distWest = Math.sqrt(Math.pow(toWest.x, 2) + Math.pow(toWest.y, 2));
var FwestAmp = this.springForce(distWest, l, k);
var FwestAngle = segmentAngleRad(particle.x, particle.y, particleWest.xLast, particleWest.yLast, true);
var FwestX = FwestAmp * Math.cos(FwestAngle);
var FwestY = FwestAmp * Math.sin(FwestAngle);
}
if (i < this.grid.length - 1) {// Eastern neighbour
particleEast = this.grid[i+1][j];
var toEast = { x: particleEast.x - particle.x, y: particleEast.y - particle.y };
var distEast = Math.sqrt(Math.pow(toEast.x, 2) + Math.pow(toEast.y, 2));
var FeastAmp = this.springForce(distEast, l, k);
var FeastAngle = segmentAngleRad(particle.x, particle.y, particleEast.xLast, particleEast.yLast, true);
var FeastX = FeastAmp * Math.cos(FeastAngle);
var FeastY = FeastAmp * Math.sin(FeastAngle);
}
if (j > 0) {
particleNorth = this.grid[i][j-1];
var toNorth = { x: particleNorth.xLast - particle.x, y: particleNorth.yLast - particle.y };
var distNorth = Math.sqrt(Math.pow(toNorth.x, 2) + Math.pow(toNorth.y, 2));
var FnorthAmp = this.springForce(distNorth, l, k);
var FnorthAngle = segmentAngleRad(particle.x, particle.y, particleNorth.xLast, particleNorth.yLast, true);
var FnorthX = FnorthAmp * Math.cos(FnorthAngle);
var FnorthY = FnorthAmp * Math.sin(FnorthAngle);
}
if (j < this.grid[0].length - 1) {
particleSouth = this.grid[i][j+1];
var toSouth = { x: particleSouth.x - particle.x, y: particleSouth.y - particle.y };
var distSouth = Math.sqrt(Math.pow(toSouth.x, 2) + Math.pow(toSouth.y, 2));
var FsouthAmp = this.springForce(distSouth, l, k);
var FsouthAngle = segmentAngleRad(particle.x, particle.y, particleSouth.xLast, particleSouth.yLast, true);
var FsouthX = FsouthAmp * Math.cos(FsouthAngle);
var FsouthY = FsouthAmp * Math.sin(FsouthAngle);
}
var Fx = (!!particleWest ? FwestX : 0) + (!!particleEast ? FeastX : 0) + (!!particleNorth ? FnorthX : 0) + (!!particleSouth ? FsouthX : 0);
var Fy = (!!particleWest ? FwestY : 0) + (!!particleEast ? FeastY : 0) + (!!particleNorth ? FnorthY : 0) + (!!particleSouth ? FsouthY : 0);
// Newton second law
var xAcc = Fx / par