Created
May 11, 2017 16:05
-
-
Save polycarpou/b0766e459a97645cb474280053e28a67 to your computer and use it in GitHub Desktop.
Tearable Cloth
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
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Tearable Cloth</title> | |
</head> | |
<body> | |
<canvas id="c"></canvas> | |
<script type="text/javascript"> | |
document.getElementById('close').onmousedown = function(e) { | |
e.preventDefault(); | |
document.getElementById('info').style.display = 'none'; | |
return false; | |
}; | |
</script> | |
<script type="text/javascript"> | |
// settings | |
var physics_accuracy = 3, | |
mouse_influence = 20, | |
mouse_cut = 5, | |
gravity = 1200, | |
cloth_height = 30, | |
cloth_width = 70, | |
start_y = 20, | |
spacing = 9, | |
tear_distance = 160; | |
fabric_color = '#000'; | |
window.requestAnimFrame = | |
window.requestAnimationFrame || | |
window.webkitRequestAnimationFrame || | |
window.mozRequestAnimationFrame || | |
window.oRequestAnimationFrame || | |
window.msRequestAnimationFrame || | |
function (callback) { | |
window.setTimeout(callback, 1000 / 60); | |
}; | |
var canvas, | |
ctx, | |
cloth, | |
boundsx, | |
boundsy, | |
mouse = { | |
down: false, | |
button: 1, | |
x: 0, | |
y: 0, | |
px: 0, | |
py: 0 | |
}, | |
editpoint = 100; | |
var Point = function (x, y) { | |
this.x = x; | |
this.y = y; | |
this.px = x; | |
this.py = y; | |
this.vx = 0; | |
this.vy = 0; | |
this.pin_x = null; | |
this.pin_y = null; | |
this.constraints = []; | |
}; | |
Point.prototype.update = function (delta) { | |
if (mouse.down) { | |
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) ; | |
this.py = this.y - (mouse.y - mouse.py) ; | |
} | |
} else if (dist < mouse_cut) this.constraints = []; | |
} | |
// wind = 1000 | |
this.add_force(1000*Math.random(), gravity + (10 + Math.random())); | |
delta *= delta; | |
nx = this.x + ((this.x - this.px) ) + ((this.vx / 2) * delta); | |
ny = this.y + ((this.y - this.py) ) + ((this.vy / 2) * delta); | |
this.px = this.x; | |
this.py = this.y; | |
this.x = nx; | |
this.y = ny; | |
this.vy = this.vx = 0 | |
}; | |
Point.prototype.draw = function () { | |
if (this.constraints.length <= 0) return; | |
var i = this.constraints.length; | |
while (i--) this.constraints[i].draw(); | |
}; | |
Point.prototype.resolve_constraints = function () { | |
if (this.pin_x != null && this.pin_y != null) { | |
this.x = this.pin_x; | |
this.y = this.pin_y; | |
return; | |
} | |
var i = this.constraints.length; | |
while (i--) this.constraints[i].resolve(); | |
if (this.x > boundsx) { | |
this.x = 2 * boundsx - this.x; | |
} else if (this.x < 1) { | |
this.x = 2 - this.x; | |
} | |
if (this.y > boundsy) { | |
this.y = 2 * boundsy - this.y; | |
} else if (this.y < 1) { | |
this.y = 2 - this.y; | |
} | |
}; | |
Point.prototype.attach = function (point) { | |
this.constraints.push( | |
new Constraint(this, point) | |
); | |
}; | |
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 (x, y) { | |
this.vx += x; | |
this.vy += y; | |
}; | |
Point.prototype.pin = function (pinx, piny) { | |
this.pin_x = pinx; | |
this.pin_y = piny; | |
}; | |
var Constraint = function (p1, p2) { | |
this.p1 = p1; | |
this.p2 = p2; | |
this.length = spacing; | |
}; | |
Constraint.prototype.resolve = 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 > tear_distance) this.p1.remove_constraint(this); | |
var px = diff_x * diff * 0.5; | |
var py = diff_y * diff * 0.5; | |
this.p1.x += px; | |
this.p1.y += py; | |
this.p2.x -= px; | |
this.p2.y -= py; | |
}; | |
Constraint.prototype.draw = function () { | |
ctx.moveTo(this.p1.x, this.p1.y); | |
ctx.lineTo(this.p2.x, this.p2.y); | |
}; | |
var Cloth = function () { | |
this.points = []; | |
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, start_y + y * spacing); | |
(x != 0) && p.attach(this.points[this.points.length - 1]); | |
(y == 0) && p.pin(p.x, p.y); | |
(y != 0) && p.attach(this.points[x + (y - 1) * (cloth_width + 1)]) | |
this.points.push(p); | |
} | |
} | |
}; | |
Cloth.prototype.update = function () { | |
var i = physics_accuracy; | |
while (i--) { | |
var p = this.points.length; | |
while (p--) this.points[p].resolve_constraints(); | |
} | |
i = this.points.length; | |
while (i--) this.points[i].update(.016); | |
}; | |
Cloth.prototype.draw = function () { | |
ctx.beginPath(); | |
ctx.strokeStyle = fabric_color; | |
ctx.lineWidth = 8; | |
var i = cloth.points.length; | |
while (i--) cloth.points[i].draw(); | |
ctx.stroke(); | |
ctx.fillStyle="#FF0000"; | |
var pointEditor = cloth.points; | |
ctx.fillRect(pointEditor[editpoint].x,pointEditor[editpoint].y,5,5); | |
}; | |
function update() { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
cloth.update(); | |
cloth.draw(); | |
requestAnimFrame(update); | |
} | |
function touchStart(e){ | |
e.preventDefault(); | |
//mouse.button = e.which; | |
mouse.px = mouse.x; | |
mouse.py = mouse.y; | |
var rect = canvas.getBoundingClientRect(); | |
var touch = e.touches[0] | |
mouse.x = touch.clientX - rect.left, | |
mouse.y = touch.clientY - rect.top, | |
mouse.down = true; | |
}; | |
function touchEnd(e) { | |
e.preventDefault(); | |
mouse.down = false; | |
}; | |
function touchMove(e) { | |
e.preventDefault(); | |
mouse.px = mouse.x; | |
mouse.py = mouse.y; | |
var rect = canvas.getBoundingClientRect(); | |
var touch = e.touches[0] | |
mouse.x = touch.clientX - rect.left, | |
mouse.y = touch.clientY - rect.top; | |
}; | |
function start() { | |
canvas.onmousedown = function (e) { | |
mouse.button = e.which; | |
mouse.px = mouse.x; | |
mouse.py = mouse.y; | |
var rect = canvas.getBoundingClientRect(); | |
mouse.x = e.clientX - rect.left, | |
mouse.y = e.clientY - rect.top, | |
mouse.down = true; | |
e.preventDefault(); | |
}; | |
canvas.addEventListener("touchstart", touchStart, false); | |
canvas.addEventListener("touchmove", touchMove, false); | |
canvas.addEventListener("touchend", touchEnd, false); | |
canvas.onmouseup = function (e) { | |
mouse.down = false; | |
e.preventDefault(); | |
}; | |
canvas.onmousemove = function (e) { | |
mouse.px = mouse.x; | |
mouse.py = mouse.y; | |
var rect = canvas.getBoundingClientRect(); | |
mouse.x = e.clientX - rect.left, | |
mouse.y = e.clientY - rect.top, | |
e.preventDefault(); | |
}; | |
canvas.oncontextmenu = function (e) { | |
e.preventDefault(); | |
}; | |
var key = []; | |
window.onkeyup = function(event) { | |
key[event.keyCode] = event.type == 'keydown'; | |
} | |
window.onkeydown = function(event) { | |
key[event.keyCode] = event.type == 'keydown'; | |
if (!event) | |
event = window.event; | |
var code = event.keyCode; | |
if (event.charCode && code == 0) | |
code = event.charCode; | |
switch(code) { | |
case 37: | |
// Key left. | |
if (!cloth.points[editpoint - 1]) return; | |
editpoint = editpoint - 1; | |
if (key[32]) cloth.points[editpoint].constraints = []; | |
break; | |
case 38: | |
// Key up. | |
if (!cloth.points[editpoint - cloth_width - 1]) return; | |
editpoint = editpoint - cloth_width - 1; | |
if (key[32]) cloth.points[editpoint].constraints = []; | |
break; | |
case 39: | |
// Key right. | |
if (!cloth.points[editpoint + 1]) return; | |
editpoint = editpoint + 1; | |
if (key[32]) cloth.points[editpoint].constraints = []; | |
break; | |
case 40: | |
// Key down. | |
if (!cloth.points[editpoint + cloth_width + 1]) return; | |
editpoint = editpoint + cloth_width + 1; | |
if (key[32]) cloth.points[editpoint].constraints = []; | |
break; | |
case 32: | |
// Space key. | |
cloth.points[editpoint].constraints = []; | |
break; | |
} | |
event.preventDefault(); | |
}; | |
boundsx = canvas.width - 1; | |
boundsy = canvas.height - 1; | |
ctx.strokeStyle = '#000'; | |
cloth = new Cloth(); | |
update(); | |
} | |
window.onload = function () { | |
canvas = document.getElementById('c'); | |
ctx = canvas.getContext('2d'); | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
start(); | |
}; | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment