Javascript cloth simulation.
A Pen by dissimulate on CodePen.
<canvas id="c"></canvas> | |
<div id="info"> | |
<div id="top"> | |
<a id="close" href="">×</a> | |
</div> | |
<p> | |
<br>- Tear the cloth with your mouse. | |
<br> | |
<br>- Right click and drag to cut the cloth | |
<br> | |
<br> | |
<span id="irc"> | |
Question? Comment? Chat? <a target="_blank" href="https://kiwiirc.com/client/irc.snoonet.org?channel=#reddit">Talk to me on IRC</a>! | |
</span> | |
<br> | |
<span id="irc-info">Pick a nickname, join and type "/msg diss hi" | |
</span> | |
<br><a id="github" target="_blank" href="http://github.com/suffick/Tearable-Cloth">View on GitHub</a> | |
<br> | |
</p> | |
</div> |
/* | |
Copyright (c) 2013 dissimulate at Codepen | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in | |
all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
THE SOFTWARE. | |
*/ | |
document.getElementById('close').onmousedown = function(e) { | |
e.preventDefault(); | |
document.getElementById('info').style.display = 'none'; | |
return false; | |
}; | |
// settings | |
var physics_accuracy = 3, | |
mouse_influence = 20, | |
mouse_cut = 5, | |
gravity = 1200, | |
cloth_height = 30, | |
cloth_width = 50, | |
start_y = 20, | |
spacing = 7, | |
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, | |
cloth, | |
boundsx, | |
boundsy, | |
mouse = { | |
down: false, | |
button: 1, | |
x: 0, | |
y: 0, | |
px: 0, | |
py: 0 | |
}; | |
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) * 1.8; | |
this.py = this.y - (mouse.y - mouse.py) * 1.8; | |
} | |
} else if (dist < mouse_cut) this.constraints = []; | |
} | |
this.add_force(0, gravity); | |
delta *= delta; | |
nx = this.x + ((this.x - this.px) * .99) + ((this.vx / 2) * delta); | |
ny = this.y + ((this.y - this.py) * .99) + ((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) 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(); | |
this.x > boundsx ? this.x = 2 * boundsx - this.x : 1 > this.x && (this.x = 2 - this.x); | |
this.y < 1 ? this.y = 2 - this.y : this.y > boundsy && (this.y = 2 * boundsy - this.y); | |
}; | |
Point.prototype.attach = function (point) { | |
this.constraints.push( | |
new Constraint(this, point) | |
); | |
}; | |
Point.prototype.remove_constraint = function (constraint) { | |
this.constraints.splice(this.constraints.indexOf(constraint), 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(); | |
var i = cloth.points.length; | |
while (i--) cloth.points[i].draw(); | |
ctx.stroke(); | |
}; | |
function update() { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
cloth.update(); | |
cloth.draw(); | |
requestAnimFrame(update); | |
} | |
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.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(); | |
}; | |
boundsx = canvas.width - 1; | |
boundsy = canvas.height - 1; | |
ctx.strokeStyle = '#888'; | |
cloth = new Cloth(); | |
update(); | |
} | |
window.onload = function () { | |
canvas = document.getElementById('c'); | |
ctx = canvas.getContext('2d'); | |
canvas.width = 560; | |
canvas.height = 350; | |
start(); | |
}; |
* { | |
margin: 0; | |
overflow: hidden; | |
-webkit-user-select: none; | |
-moz-user-select: none; | |
-ms-user-select: none; | |
-o-user-select: none; | |
user-select: none; | |
} | |
body { | |
background: #F2F2F2; | |
} | |
#c { | |
display: block; | |
margin: 20px auto 0; | |
} | |
#info { | |
position: absolute; | |
left: -1px; | |
top: -1px; | |
width: auto; | |
max-width: 420px; | |
height: auto; | |
background: #f2f2f2; | |
border-bottom-right-radius: 10px; | |
border:1px solid #333; | |
} | |
#top { | |
background: #fff; | |
width: 100%; | |
height: auto; | |
position: relative; | |
border-bottom: 1px solid #eee; | |
} | |
p { | |
font-family: Arial, sans-serif; | |
color: #666; | |
text-align: justify; | |
font-size: 16px; | |
margin: 10px 16px; | |
} | |
#github { | |
color:#3377ee; | |
font-family: Helvetica, Arial, sans-serif; | |
font-size: 19px; | |
display: block; | |
margin: 0 auto; | |
text-align: center; | |
text-decoration:none; | |
} | |
#net { | |
text-align:center; | |
white-space:nowrap; | |
font-size:19px; | |
background:rgba(0,0,0,0.1); | |
padding:8px 12px; | |
border-radius:8px; | |
display:block; | |
color:#888; | |
} | |
#net > span { | |
color:#3377ee; | |
font-family: Helvetica, Arial, sans-serif; | |
font-size: 14px; | |
display: block; | |
margin: 0 auto; | |
text-align: center; | |
text-decoration:none; | |
} | |
a { | |
font-family: sans-serif; | |
color: #444; | |
text-decoration: none; | |
font-size: 20px; | |
} | |
#site { | |
float: left; | |
margin: 10px; | |
color: #ff9900; | |
border-bottom: 1px dashed #ccc; | |
padding-bottom:3px | |
} | |
#site:hover { | |
color: #ffaa11; | |
} | |
#close { | |
float: right; | |
margin: 10px; | |
} | |
#p { | |
font-family: Verdana, sans-serif; | |
position: absolute; | |
right: 10px; | |
bottom: 10px; | |
color: #4099ff; | |
border: 1px dashed #4099ff; | |
padding: 4px 8px; | |
} | |
#irc { | |
display: block; | |
text-align: center; | |
margin-top:4px; | |
color: #333; | |
} | |
#irc a { | |
color: #3377ee; | |
font-size: 17px | |
} | |
#irc-info { | |
display: block; | |
text-align: center; | |
font-size: 14px; | |
margin-top: -10px; | |
color: #828282; | |
margin-bottom:4px | |
} |
Javascript cloth simulation.
A Pen by dissimulate on CodePen.