Skip to content

Instantly share code, notes, and snippets.

@bricof
Last active November 21, 2016 16:12
  • 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 bricof/2c7bdf4394f603e6902e5f0111675c3f to your computer and use it in GitHub Desktop.
Bottled Gas Animation
license: gpl-3.0
function dist_btwn_pts(pt1, pt2) {
return Math.pow(Math.pow(pt1.x - pt2.x,2) + Math.pow(pt1.y - pt2.y,2) , 0.5);
}
function angle_btwn_lines(line1, line2){
var vector1 = {x: line1.x2 - line1.x1, y: line1.y2 - line1.y1};
var vector2 = {x: line2.x2 - line2.x1, y: line2.y2 - line2.y1};
var angle = (Math.atan2(vector2.y, vector2.x) - Math.atan2(vector1.y, vector1.x));
if (angle < 0) { angle = angle + 2 * Math.PI; }
return angle;
}
function btwn(a, b1, b2) {
var tol = 1e-6;
if ((a >= b1 - tol) && (a <= b2 + tol)) { return true; }
if ((a >= b2 - tol) && (a <= b1 + tol)) { return true; }
return false;
}
function line_line_intersect(line1, line2) {
var x1 = line1.x1, x2 = line1.x2, x3 = line2.x1, x4 = line2.x2;
var y1 = line1.y1, y2 = line1.y2, y3 = line2.y1, y4 = line2.y2;
var pt_denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
var pt_x_num = (x1*y2 - y1*x2) * (x3 - x4) - (x1 - x2) * (x3*y4 - y3*x4);
var pt_y_num = (x1*y2 - y1*x2) * (y3 - y4) - (y1 - y2) * (x3*y4 - y3*x4);
if (pt_denom == 0) { return "parallel"; }
else {
var pt = {'x': pt_x_num / pt_denom, 'y': pt_y_num / pt_denom};
if (btwn(pt.x, x1, x2) && btwn(pt.y, y1, y2) && btwn(pt.x, x3, x4) && btwn(pt.y, y3, y4)) { return pt; }
else { return "not in range"; }
}
}
function path_line_intersections(pathEl, line2, n_segments) {
var pts = [];
var int_line_segs = [];
for (var i=0; i<n_segments; i++) {
var pos1 = pathEl.getPointAtLength(pathLength * i / n_segments);
var pos2 = pathEl.getPointAtLength(pathLength * (i+1) / n_segments);
var line1 = {x1: pos1.x, x2: pos2.x, y1: pos1.y, y2: pos2.y};
var pt = line_line_intersect(line1, line2);
if (typeof(pt) != "string") {
pts.push(pt);
int_line_segs.push(line1);
}
}
return d3.zip(pts,int_line_segs);
}
function first_path_line_intersection(pathEl, line, n_segments) {
var start_pt = {x: line.x1, y: line.y1};
var int_pts_and_segs = path_line_intersections(pathEl, line, n_segments);
if (int_pts_and_segs.length > 0) {
var distances = int_pts_and_segs.map(function(pt) { return dist_btwn_pts(start_pt, pt[0]); });
var min_pt = d3.zip(int_pts_and_segs,distances).sort(function(a,b){ return a[1] - b[1]; })[0][0];
return min_pt;
} else {
return 'none';
}
}
function compute_reflection(pt, angle, int_seg ) {
var pt2 = {x: pt.x + int_seg.x2 - int_seg.x1,
y: pt.y + int_seg.y2 - int_seg.y1}
var rot_pt_x = pt.x + (pt2.x - pt.x) * Math.cos(angle) - (pt2.y - pt.y) * Math.sin(angle);
var rot_pt_y = pt.y + (pt2.x - pt.x) * Math.sin(angle) + (pt2.y - pt.y) * Math.cos(angle);
var l = dist_btwn_pts(pt, pt2);
return { dx: (rot_pt_x - pt.x ) / l, dy: (rot_pt_y - pt.y) / l };
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.molecule {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.button {
float: right;
}
.controls {
position: absolute;
width: 940px;
padding: 10px;
z-index: 1;
}
</style>
<body>
<div class="controls">
<input id="slider" type="range" min="0.1" max="10" value="2.5" step="0.1">Speed
<input class="button" id="button" type="button" name="updateButton" value="Add Molecule">
</div>
<svg width="960px" height="500px">
<path fill="none" stroke="#000000" stroke-miterlimit="10" d="M512.049,82.693c-8.46,3.561-5.522,11.094-5.522,20.17
c0,7.092,0.71,14.147,4.609,20.213c9.838,15.304,21.579,22.363,35.181,33.957c22.201,18.925,20.957,44.126,20.957,70.669
c0,47.12,0,94.24,0,141.36c0,18.958,0,37.916,0,56.874c0,5.832,2.606,22.086-7.904,22.086c-26.991,0-134.957,0-161.948,0
c-10.51,0-7.904-16.254-7.904-22.086c0-18.958,0-37.916,0-56.874c0-47.12,0-94.24,0-141.36c0-26.544-1.244-51.745,20.957-70.669
c13.601-11.594,25.343-18.654,35.181-33.957c3.899-6.066,4.609-13.121,4.609-20.213c0-9.077,2.938-16.609-5.522-20.17"/>
</svg>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="geometry_methods.js"></script>
<script>
var speed = 2.5;
var n_molecules = 17;
var n_segments = 100;
var svg = d3.select("svg");
var molecules_g = svg.append("g");
var highlights = svg.append("g");
// bottle path
var path = svg.select("path");
var pathEl = path.node();
var pathLength = pathEl.getTotalLength();
// uses functions in geoemetry_methods.js
// to find where and how the molecule will bounce next
function compute_next_bounce(d, pathEl, n_segments) {
var init_pt = {x: d.x, y: d.y};
var dir_line = {x1: d.x + 0.0001 * d.dx, y1: d.y + 0.0001 * d.dy,
x2: d.x + 2000 * d.dx, y2: d.y + 2000 * d.dy};
var intersection = first_path_line_intersection(pathEl, dir_line, n_segments);
if (typeof(intersection) != "string") {
var int_pt = intersection[0];
var int_seg = intersection[1];
var angle = angle_btwn_lines(dir_line, int_seg);
var reflection_vector = compute_reflection( int_pt, angle, int_seg );
var dir_length = dist_btwn_pts(init_pt, int_pt);
} else {
var int_pt = {x: 'out', y: 'out'};
var reflection_vector = {dx: 'out', dy: 'out'};
var dir_length = 1000;
}
d.int_x = int_pt.x;
d.int_y = int_pt.y;
d.refl_dx = reflection_vector.dx;
d.refl_dy = reflection_vector.dy;
d.dir_length = dir_length;
d.dir_length_run = 0;
}
// adding a new molecule and setting its initial values
// (for animation convenience, we also pre-compute its next bounce)
var molecule_count = 0;
function add_molecule() {
var init_pt = {x: 400 + 150 * Math.random(),
y: 200 + 150 * Math.random()};
var init_dir = {x: -1 + 2 * Math.random(),
y: -1 + 2 * Math.random()};
var init_dir_length = dist_btwn_pts({x: 0, y: 0}, init_dir);
init_dir.x = init_dir.x / init_dir_length;
init_dir.y = init_dir.y / init_dir_length;
var d = {'id': molecule_count,
'x': init_pt.x, 'y': init_pt.y,
'dx': init_dir.x, 'dy': init_dir.y};
compute_next_bounce(d, pathEl, n_segments);
molecule_data.push(d);
molecule_count += 1;
}
// initial molecule data
var molecule_data = [];
for (var i=0; i<n_molecules; i++) {
add_molecule();
}
// animation with d3.timer
var previous_time = 0;
d3.timer(function(elapsed) {
var t = elapsed - previous_time;
// update data
molecule_data.forEach(function(d) {
d.dir_length_run += speed;
if (d.dir_length_run >= d.dir_length) {
d.x = d.int_x;
d.y = d.int_y;
d.dx = d.refl_dx;
d.dy = d.refl_dy;
if (d.y > 0) {
compute_next_bounce(d, pathEl, n_segments);
}
}
d.x = d.x + speed * d.dx;
d.y = d.y + speed * d.dy;
});
molecule_data = molecule_data.filter(function(d){
return ((d.y > 0) && (d.x > 0) && (d.x < 960));
});
// update circles
var m = molecules_g.selectAll(".molecule")
.data(molecule_data, function(d){ return d.id; });
m.enter().append("circle")
.attr("class", "molecule")
.attr("r", 3);
m
.attr("cx", function(d){ return d.x; })
.attr("cy", function(d){ return d.y; });
m.exit().remove();
// loop
previous_time = elapsed;
});
// slider and button updates
d3.select("#slider").on("change", function() {
speed = +this.value;
});
d3.select("#button").on("click", function() {
add_molecule(molecule_count);
molecule_count += 1;
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment