Skip to content

Instantly share code, notes, and snippets.

@archonic
Created March 13, 2015 21:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save archonic/928b1aabb65cd4989648 to your computer and use it in GitHub Desktop.
Save archonic/928b1aabb65cd4989648 to your computer and use it in GitHub Desktop.
Paper.js Fluid Simulation with particles (doesn't do a great job of it)
<html>
<head>
<title>Fluid simulation with balls</title>
<script type="text/javascript" src="paper-full.js"></script>
<!-- Define inlined PaperScript associate it with myCanvas -->
<script type="text/paperscript" canvas="myCanvas">
// Ball variables
radius = 20;
totalBalls = 100;
gravity = 0.3;
decay = 0.5;
function Ball(r, p, v) {
this.radius = r;
this.point = p;
this.vector = v;
this.maxVec = 15;
this.numSegment = Math.floor(r / 3 + 2);
this.boundOffset = [];
this.boundOffsetBuff = [];
this.sidePoints = [];
this.path = new Path({
fillColor: {
hue: Math.random() * 360,
saturation: 1,
brightness: 1
},
blendMode: 'screen'
});
for (var i = 0; i < this.numSegment; i ++) {
this.boundOffset.push(this.radius);
this.boundOffsetBuff.push(this.radius);
this.path.add(new Point());
this.sidePoints.push(new Point({
angle: 360 / this.numSegment * i,
length: 1
}));
}
}
function getVectorAwayFrom(ball, wall) {
var size = view.size;
switch(wall) {
case "top":
wallPoint = new Point(ball.point.x, 0);
break;
case "bottom":
wallPoint = new Point(ball.point.x, size.height);
break;
case "left":
wallPoint = new Point(0, ball.point.y)
break;
case "right":
wallPoint = new Point(size.width, ball.point.y);
break;
}
//drawPoint(wallPoint);
var distFromWall = ball.point.getDistance(wallPoint);
var overlap = ball.radius - distFromWall;
var vectorAway = ( ball.point - wallPoint ).normalize(overlap * 0.95);
//console.log('vectorAway: ' + vectorAway);
return vectorAway;
}
Ball.prototype = {
iterate: function() {
this.checkBorders();
// Limit top speed
if (this.vector.length > this.maxVec)
this.vector.length = this.maxVec;
// apply gravity to Y vector
this.vector.y += gravity;
// Apply the vector to the point to have it animate
this.point += this.vector;
//console.log('Current vector: ' + this.vector);
this.updateShape();
},
checkBorders: function() {
var size = view.size;
// Left border
if (this.point.x < this.radius) {
this.vector += getVectorAwayFrom(this, "left");
this.vector.x *= decay;
}
// Right border
if (this.point.x > size.width - this.radius) {
this.vector += getVectorAwayFrom(this, "right");
this.vector.x *= decay;
}
// Top border
if (this.point.y < this.radius) {
this.vector += getVectorAwayFrom(this, "top");
this.vector.y *= decay;
}
// Bottom border
if (this.point.y > size.height - this.radius) {
this.vector += getVectorAwayFrom(this, "bottom");
this.vector.y *= decay;
}
},
updateShape: function() {
var segments = this.path.segments;
for (var i = 0; i < this.numSegment; i ++)
segments[i].point = this.getSidePoint(i);
this.path.smooth();
for (var i = 0; i < this.numSegment; i ++) {
if (this.boundOffset[i] < this.radius / 4)
this.boundOffset[i] = this.radius / 4;
var next = (i + 1) % this.numSegment;
var prev = (i > 0) ? i - 1 : this.numSegment - 1;
var offset = this.boundOffset[i];
offset += (this.radius - offset) / 15;
offset += ((this.boundOffset[next] + this.boundOffset[prev]) / 2 - offset) / 3;
this.boundOffsetBuff[i] = this.boundOffset[i] = offset;
}
},
react: function(b) {
var dist = this.point.getDistance(b.point);
if (dist < this.radius + b.radius && dist != 0) {
var overlap = this.radius + b.radius - dist;
var direc = (this.point - b.point).normalize(overlap * 1);
this.vector += direc;
this.vector.x *= decay;
this.vector.y *= decay;
b.vector -= direc;
b.vector.x *= decay;
b.vector.y *= decay;
}
},
getBoundOffset: function(b) {
var diff = this.point - b;
var angle = (diff.angle + 180) % 360;
return this.boundOffset[Math.floor(angle / 360 * this.boundOffset.length)];
},
getSidePoint: function(index) {
return this.point + this.sidePoints[index] * this.boundOffset[index];
}
}
// Container which holds all balls
var allBalls = [];
for(var i = 0; i < totalBalls; i++) {
var position = new Point.random() * view.size;
var vector = new Point({
angle: 360 * Math.random(),
length: Math.random() * 10
});
allBalls.push(new Ball(radius, position, vector));
}
console.log('Finished creating all balls');
// Define and fill our cursor
//cursor = new Path.Circle(new Point(0,0), 25);
//cursor.fillColor = 'blue';
// This function is called whenever the user
// move the mouse in the view
function onMouseMove(event) {
// Have our "circle cursor" follow the mouse
//cursor.position = event.point;
//checkForCollisions();
}
function showIntersections(path1, path2) {
var intersections = path1.getIntersections(path2);
for (var i = 0; i < intersections.length; i++) {
drawPoint(intersections[i]);
}
}
function drawPoint(point) {
new Path.Circle({
center: point,
radius: 5,
fillColor: '#009dec'
}).removeOnMove();
}
function onFrame() {
for (var i = 0; i < allBalls.length - 1; i++) {
for (var j = i + 1; j < allBalls.length; j++) {
allBalls[i].react(allBalls[j]);
}
}
for (var i = 0, l = allBalls.length; i < l; i++) {
allBalls[i].iterate();
//checkForCollisions();
}
//console.log('Finished frame');
}
function checkForCollisions() {
// Are there any circles that intersect with our mouse?
for (var i = 0, l = allBalls.length; i < l; i++) {
showIntersections(allBalls[i].path, cursor);
}
}
</script>
</head>
<body style="margin:0;padding:0;">
<canvas id="myCanvas" resize style="width:99%;height:90%;border:1px #000 solid;"></canvas>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment