This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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