Skip to content

Instantly share code, notes, and snippets.

@simonsarris
Created April 17, 2013 15:35
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save simonsarris/5405304 to your computer and use it in GitHub Desktop.
// settings
var physics_accuracy = 3,
mouse_influence = 20,
mouse_cut = 6,
gravity = 2900,
cloth_height = 30,
cloth_width = 200,
start_y = 20,
spacing = 4,
tear_distance = 60;
window.requestAnimFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
var canvas,
ctx,
points,
physics,
mouse = {
down: false,
button: 1,
x: 0,
y: 0,
px: 0,
py: 0
};
window.onload = function() {
canvas = document.getElementById('c');
ctx = canvas.getContext('2d');
canvas.width = 960;//window.innerWidth;
canvas.height = 600;//window.innerHeight;
canvas.onmousedown = function(e) {
mouse.button = e.which;
mouse.px = mouse.x;
mouse.py = mouse.y;
mouse.x = e.clientX || e.layerX;
mouse.y = e.clientY || e.layerY;
mouse.down = true;
e.preventDefault();
};
canvas.onmouseup = function(e) {
mouse.down = false;
e.preventDefault();
};
canvas.onmousemove = function(e) {
mouse.px = mouse.x;
mouse.py = mouse.y;
mouse.x = e.clientX || e.layerX;
mouse.y = e.clientY || e.layerY;
e.preventDefault();
};
canvas.oncontextmenu = function(e) {
e.preventDefault();
};
init();
};
var Constraint = function(p1, p2, spacing, tear_distance) {
this.p1 = p1;
this.p2 = p2;
this.length = spacing;
this.tear_distance = tear_distance;
};
Constraint.prototype.solve = function() {
var diff_x = this.p1.x - this.p2.x,
diff_y = this.p1.y - this.p2.y,
dist = Math.sqrt(diff_x * diff_x + diff_y * diff_y),
diff = (this.length - dist) / dist;
if (dist > this.tear_distance) this.p1.remove_constraint(this);
var scalar_1 = ((1 / this.p1.mass) / ((1 / this.p1.mass) + (1 / this.p2.mass))),
scalar_2 = 1 - scalar_1;
this.p1.x += diff_x * scalar_1 * diff;
this.p1.y += diff_y * scalar_1 * diff;
this.p2.x -= diff_x * scalar_2 * diff;
this.p2.y -= diff_y * scalar_2 * diff;
};
Constraint.prototype.draw = function() {
ctx.beginPath();
ctx.strokeStyle = 'red'
ctx.moveTo(this.p1.x, this.p1.y);
ctx.lineTo(this.p2.x, this.p2.y);
ctx.stroke();
};
// !!! new super awesome draw routine! So cool we skipped naming it draw2!
Constraint.prototype.draw3 = function(otherP2) {
// NOW dear friends consider what we have. Each box is made out of two lines,
// the bottom and rightmost ones.
// From these lines we can deduce the topleft and bottom-right points
// From these points we can deduce rectangles
// From the skewed rectangle vs the original rectangle we can "stretch"
// an image, using drawImage's overloaded goodness.
// AND WE'RE OFF:
// destination rect has 2 points:
//top left: Math.min(this.p2.x, otherP2.x), Math.min(this.p2.y, otherP2.y)
//bottom right: (this.p1.x, this.p1.y)
// image destination rectangle, a rect made from the two points
var dx = Math.min(this.p1.x, Math.min(this.p2.x, otherP2.x));
var dy = Math.min(this.p1.y, Math.min(this.p2.y, otherP2.y));
var dw = Math.abs(this.p1.x - Math.min(this.p2.x, otherP2.x));
var dh = Math.abs(this.p1.y - Math.min(this.p2.y, otherP2.y));
// DEBUG: IF THERE IS NO IMAGE TURN THIS ON:
//ctx.strokeStyle = 'lime';
//ctx.strokeRect(dx, dy, dw, dh);
// source rect 2 points:
//top left: Math.min(this.p2.sx, otherP2.sx), Math.min(this.p2.sy, otherP2.sy)
//bottom right: (this.p1.sx, this.p1.sy)
// these do NOT need to be caluclated every time,
// they never change for a given constraint
// calculate them the first time only. I could do this earlier but I'm lazy
// and its past midnight. See also: http://www.youtube.com/watch?v=FwaQxDkpcHY#t=64s
if (this.sx === undefined) {
this.sx = Math.min(this.p1.sx, Math.min(this.p2.sx, otherP2.sx));
this.sy = Math.min(this.p1.sy, Math.min(this.p2.sy, otherP2.sy));
this.sw = Math.abs(this.p1.sx - Math.min(this.p2.sx, otherP2.sx));
this.sh = Math.abs(this.p1.sy - Math.min(this.p2.sy, otherP2.sy));
}
var sx = this.sx;
var sy = this.sy;
var sw = this.sw;
var sh = this.sh;
// DEBUG: IF THERE IS NO IMAGE TURN THIS ON:
//ctx.strokeStyle = 'red';
//ctx.strokeRect(sx, sy, sw, sh);
// IF we have a source and destination rectangle, then we can map an image
// piece using drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh)
// Only problem, we're not exactly dealing with rectangles....
// But we'll deal. Transformations have kooties anyways.
ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
};
var Point = function(x, y) {
this.x = x;
this.y = y;
this.px = x;
this.py = y;
this.ax = 0;
this.ay = 0;
this.mass = 1;
this.constraints = [];
this.pinned = false;
this.pin_x;
this.pin_y;
// !!! the original x/y saved
this.sx = x;
this.sy = y;
};
Point.prototype.update = function(delta) {
this.add_force(0, this.mass * gravity);
var vx = this.x - this.px,
vy = this.y - this.py;
delta *= delta;
nx = this.x + 0.99 * vx + 0.5 * this.ax * delta;
ny = this.y + 0.99 * vy + 0.5 * this.ay * delta;
this.px = this.x;
this.py = this.y;
this.x = nx;
this.y = ny;
this.ay = this.ax = 0
};
Point.prototype.update_mouse = function() {
if (!mouse.down) return;
var diff_x = this.x - mouse.x,
diff_y = this.y - mouse.y,
dist = Math.sqrt(diff_x * diff_x + diff_y * diff_y);
if (mouse.button == 1) {
if(dist < mouse_influence) {
this.px = this.x - (mouse.x - mouse.px) * 1.8;
this.py = this.y - (mouse.y - mouse.py) * 1.8;
}
} else if (dist < mouse_cut) this.constraints = [];
};
Point.prototype.draw = function() {
// !!! So this thing wants to draw constraints.
// But it wants to draw ones with even only 1 line, the top and left most bits
// lets just forget about those
if (this.constraints.length <= 1) return;
// there we go:
this.constraints[0].draw3(this.constraints[1].p2);
};
Point.prototype.solve_constraints = function() {
var i = this.constraints.length;
while(i--) this.constraints[i].solve();
if (this.y < 1) this.y = 2 * (1) - this.y;
else if (this.y > canvas.height-1) this.y = 2 * (canvas.height - 1) - this.y;
if (this.x > canvas.width-1) this.x = 2 * (canvas.width - 1) - this.x;
else if (this.x < 1) this.x = 2 * (1) - this.x;
if (this.pinned) {
this.x = this.pin_x;
this.y = this.pin_y;
}
};
Point.prototype.attach = function(P, spacing, tear_distance) {
this.constraints.push(
new Constraint(this, P, spacing, tear_distance)
);
};
Point.prototype.remove_constraint = function(lnk) {
var i = this.constraints.length;
while(i--) if(this.constraints[i] == lnk) this.constraints.splice(i, 1);
};
Point.prototype.add_force = function(fX, fY) {
this.ax += fX/this.mass;
this.ay += fY/this.mass;
};
Point.prototype.pin = function(pX, pY) {
this.pinned = true;
this.pin_x = pX;
this.pin_y = pY;
};
var Physics = function() {
this.delta_sec = 16 / 1000;
this.accuracy = physics_accuracy;
};
Physics.prototype.update = function() {
var i = this.accuracy;
while(i--) {
var p = points.length;
while(p--) points[p].solve_constraints();
}
i = points.length;
while(i--) {
points[i].update_mouse();
points[i].update(this.delta_sec);
}
};
function init() {
physics = new Physics();
points = [];
build_cloth();
update();
}
function update() {
// !!! change img to a global variable because I am lazy
window.img = new Image();
img.src = 'http://free-textures.got3d.com/architectural/free-stone-wall-textures/images/free-stone-wall-texture-002.jpg';
img.onload = function(){
// create pattern
var ptrn = ctx.createPattern(img,'repeat');
ctx.clearRect(0, 0, canvas.width, canvas.height);
physics.update();
ctx.strokeStyle = ptrn;
var i = points.length;
while(i--) points[i].draw();
requestAnimFrame(update);
}
}
function build_cloth() {
var start_x = canvas.width / 2 - cloth_width * spacing / 2;
for(var y = 0; y <= cloth_height; y++) {
for(var x = 0; x <= cloth_width; x++) {
var p = new Point(start_x + x * spacing, y * spacing + start_y);
0 != x &&
p.attach(points[points.length - 1], spacing, tear_distance);
0 != y &&
p.attach(points[(y - 1) * (cloth_width + 1) + x], spacing, tear_distance);
0 == y &&
p.pin(p.x, p.y);
points.push(p)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment