289 lines
7.2 KiB
JavaScript
289 lines
7.2 KiB
JavaScript
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);
|
|
return Constraint;
|
|
}
|
|
|
|
this.Update = function(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});
|
|
}
|
|
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.Apply();
|
|
}
|
|
}
|
|
}
|
|
var Mass = function(X, Y, Weight)
|
|
{
|
|
this.Weight = Weight;
|
|
this.NX = this.PX = this.X = X;
|
|
this.NY = this.PY = this.Y = Y;
|
|
this.MX = this.MY = 0;
|
|
this.Active = true;
|
|
this.Update = function(TimeDelta, Bounds)
|
|
{
|
|
if (!this.Active)
|
|
{
|
|
this.NX = this.X;
|
|
this.NY = this.Y;
|
|
return;
|
|
}
|
|
// do a verlet step
|
|
var VX = this.X - this.PX;
|
|
var VY = this.Y - this.PY;
|
|
|
|
// 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.NX = this.X + VX;
|
|
this.NY = this.Y + VY;
|
|
// console.log(this);
|
|
|
|
// limit to bounds
|
|
if (this.NY < 0)
|
|
this.NY = 0;
|
|
if (this.NX < 0)
|
|
this.NX = 0;
|
|
if (this.NY > Bounds.Y)
|
|
this.NY = Bounds.Y;
|
|
if (this.X > Bounds.X)
|
|
this.NX = Bounds.X;
|
|
}
|
|
this.Apply = function()
|
|
{
|
|
this.PX = this.X;
|
|
this.PY = this.Y;
|
|
this.X = this.NX;
|
|
this.Y = this.NY;
|
|
}
|
|
}
|
|
|
|
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.NX - this.Mass2.NX, 2) + Math.pow(this.Mass1.NY - this.Mass2.NY, 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.NX = this.Mass1.NX - (this.Mass1.NX - this.Mass2.NX) * d2 * coefm1;
|
|
this.Mass2.NX = this.Mass2.NX + (this.Mass1.NX - this.Mass2.NX) * d2 * coefm2;
|
|
this.Mass1.NY = this.Mass1.NY - (this.Mass1.NY - this.Mass2.NY) * d2 * coefm1;
|
|
this.Mass2.NY = this.Mass2.NY + (this.Mass1.NY - this.Mass2.NY) * d2 * coefm2;
|
|
}
|
|
}
|
|
|
|
var Cable = function(Verlet, X1, Y1, X2, Y2, Steps)
|
|
{
|
|
Steps = Steps || 10;
|
|
var StepX = (X2 - X1) / Steps;
|
|
var StepY = (Y2 - Y1) / Steps;
|
|
|
|
this.Masses = [];
|
|
var LastMass = v.AddMass(new Mass(X1, Y1, 0.5));
|
|
this.Masses.push(LastMass);
|
|
LastMass.Active = false;
|
|
|
|
for (var i = 1; i <= Steps; i++)
|
|
{
|
|
var NewMass = v.AddMass(new Mass(X1 + StepX * i, Y1 + StepY * i, 1));
|
|
this.Masses.push(NewMass);
|
|
v.AddConstraint(new Constraint(LastMass, NewMass));
|
|
LastMass = NewMass;
|
|
}
|
|
|
|
this.BinomialCache = {}
|
|
this.FastBinomial = function(n, k)
|
|
{
|
|
var key = n + ".." + k;
|
|
if (key in this.BinomialCache)
|
|
return this.BinomialCache[key];
|
|
|
|
function Factorial(num)
|
|
{
|
|
var rval=1;
|
|
for (var i = 2; i <= num; i++)
|
|
rval = rval * i;
|
|
return rval;
|
|
}
|
|
return (Factorial(n) / (Factorial(k) * Factorial(n - k)));
|
|
}
|
|
this.Bezier = function(t)
|
|
{
|
|
var Points = this.Masses.length - 1;
|
|
var ValueX = 0;
|
|
var ValueY = 0;
|
|
for (var i = 0; i <= Points; i++)
|
|
{
|
|
var Coef = this.FastBinomial(Points, i) * Math.pow(1 -t, Points - i) * Math.pow(t, i);
|
|
ValueX += Coef * this.Masses[i].X;
|
|
ValueY += Coef * this.Masses[i].Y;
|
|
}
|
|
|
|
return {X: ValueX, Y: ValueY};
|
|
}
|
|
|
|
this.Render = function(context)
|
|
{
|
|
context.strokeStyle = '#FFFFFF';
|
|
context.beginPath();
|
|
var First = this.Bezier(0);
|
|
context.moveTo(First.X, First.Y);
|
|
for (var T = 1; T < 100; T++)
|
|
{
|
|
var t = T / 100;
|
|
var Next = this.Bezier(t);
|
|
context.lineTo(Next.X, Next.Y);
|
|
}
|
|
context.stroke();
|
|
}
|
|
}
|
|
|
|
var v = new Verlet(800, 600);
|
|
var cable = new Cable(v, 5, 5, 500, 5);
|
|
|
|
|
|
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);
|
|
|
|
cable.Render(ctx);
|
|
ctx.fillStyle = '#FFFFFF';
|
|
|
|
var First = v.Masses[0];
|
|
var Last = v.Masses[v.Masses.length - 1];
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(First.X, First.Y, 5, 0, Math.PI*2, true);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(Last.X, Last.Y, 5, 0, Math.PI*2, true);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
}, 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.X = MouseX + DragOffsetX;
|
|
m.Y = MouseY + DragOffsetY;
|
|
}
|
|
var DragEndListener = function(e)
|
|
{
|
|
c.removeEventListener("mouseup", DragEndListener);
|
|
c.removeEventListener("mousemove", DragMoveListener);
|
|
v.Masses[DragID].Active = true;
|
|
DragID = -1;
|
|
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); |