var Verlet = function(Width, Height) { this.Masses = []; this.Constraints = []; this.Width = Width; this.Height = Height; this.AddMass = function(Mass) { this.Masses.push(Mass); return Mass; } this.AddConstraint = function(Constraint) { this.Constraints.push(Constraint); } this.Update = function(TimeDelta) { for (k in this.Constraints) { var c = this.Constraints[k]; if (typeof(c) == "function") continue; c.Update(TimeDelta); } for (k in this.Masses) { var m = this.Masses[k]; if (typeof(m) == "function") continue; m.Update(TimeDelta, {X: this.Width, Y: this.Height}); } } } var Mass = function(X, Y, Weight) { this.Weight = Weight; this.PX = this.X = X; this.PY = this.Y = Y; this.Active = true; this.Update = function(TimeDelta, Bounds) { if (!this.Active) return; // do a verlet step var VX = this.X - this.PX; var VY = this.Y - this.PY; this.PX = this.X; this.PY = this.Y; // shitty air friction if (VY > 0) VY = VY - TimeDelta else VY = VY + TimeDelta if (VX > 0) VX = VX - TimeDelta else VX = VX + TimeDelta // add gravity (mgt^2/2) VY = VY + (this.Weight * (9.98 * (TimeDelta*TimeDelta)) / 2) * 100; // add speed to X, Y this.X = this.X + VX; this.Y = this.Y + VY; // console.log(this); // limit to bounds if (this.Y < 0) { this.Y = 0; this.VY = 0; } if (this.X < 0) { this.X = 0; this.VX = 0; } if (this.Y > Bounds.Y) { this.Y = Bounds.Y; this.VY = 0; } if (this.X > Bounds.X) { this.X = Bounds.X; this.VX = 0; } } } var Constraint = function(Mass1, Mass2) { this.Mass1 = Mass1; this.Mass2 = Mass2; this.Distance = Math.sqrt(Math.pow(Mass1.X - Mass2.X, 2) + Math.pow(Mass1.Y - Mass2.Y, 2)); this.Update = function(TimeDelta) { var d1 = Math.sqrt(Math.pow(this.Mass1.X - this.Mass2.X, 2) + Math.pow(this.Mass1.Y - this.Mass2.Y, 2)); var d2 = (d1 - this.Distance) / d1; var coefm1; var coefm2; if (!this.Mass1.Active) { coefm1 = 0; coefm2 = 1; } else if (!this.Mass2.Active) { coefm1 = 1; coefm2 = 0; } else { coefm1 = (this.Mass1.Weight / (this.Mass1.Weight + this.Mass2.Weight)); coefm2 = (this.Mass2.Weight / (this.Mass1.Weight + this.Mass2.Weight)); } this.Mass1.X = this.Mass1.X - (this.Mass1.X - this.Mass2.X) * d2 * coefm1; this.Mass2.X = this.Mass2.X + (this.Mass1.X - this.Mass2.X) * d2 * coefm2; this.Mass1.Y = this.Mass1.Y - (this.Mass1.Y - this.Mass2.Y) * d2 * coefm1; this.Mass2.Y = this.Mass2.Y + (this.Mass1.Y - this.Mass2.Y) * d2 * coefm2; } } var v = new Verlet(800, 600); var m1 = v.AddMass(new Mass(5, 5, 1)); var m2 = v.AddMass(new Mass(55, 100, 1)); var m3 = v.AddMass(new Mass(105, 5, 1)); v.AddConstraint(new Constraint(m1, m2)); v.AddConstraint(new Constraint(m2, m3)); v.AddConstraint(new Constraint(m1, m3)); var c = document.getElementById("main"); var ctx = c.getContext('2d'); c.width = 800; c.height = 600; setInterval(function() { ctx.fillStyle = '#000000'; ctx.beginPath(); ctx.rect(0, 0, 800, 600); ctx.closePath(); ctx.fill(); v.Update(1/50); ctx.fillStyle = '#FFFFFF'; for (k in v.Masses) { var m = v.Masses[k]; if (typeof(m) == "function") continue; ctx.beginPath(); ctx.arc(m.X, m.Y, 5, 0, Math.PI*2, true); ctx.closePath(); ctx.fill(); } ctx.strokeStyle = '#FFFFFF'; for (k in v.Constraints) { var c = v.Constraints[k]; if (typeof(c) == "function") continue; ctx.beginPath(); ctx.moveTo(c.Mass1.X, c.Mass1.Y); ctx.lineTo(c.Mass2.X, c.Mass2.Y); ctx.stroke(); } }, 1000/50); var DragID = -1; var DragOffsetX = 0; var DragOffsetY = 0; var DragMoveListener = function(e) { var MouseX = e.layerX - c.offsetLeft; var MouseY = e.layerY - c.offsetTop; var m = v.Masses[DragID] m.PX = m.X; m.PY = m.Y; m.X = MouseX + DragOffsetX; m.Y = MouseY + DragOffsetY; } var DragEndListener = function(e) { v.Masses[DragID].Active = true; DragID = -1; c.removeEventListener("mouseup", DragEndListener); c.removeEventListener("mousemove", DragMoveListener); c.addEventListener("mousedown", DragStartListener); } var DragStartListener = function(e) { var MouseX = e.layerX - c.offsetLeft; var MouseY = e.layerY - c.offsetTop; for (k in v.Masses) { var m = v.Masses[k]; if (typeof(m) == "function") continue; var DX = MouseX - m.X; var DY = MouseY - m.Y; if (Math.sqrt((DX*DX) + (DY*DY)) < 5) { DragID = k; DragOffsetX = DX; DragOffsetY = DY; m.Active = false; c.removeEventListener("mousedown", DragStartListener); c.addEventListener("mousemove", DragMoveListener); c.addEventListener("mouseup", DragEndListener); } } } c.addEventListener("mousedown", DragStartListener);