This bottle of bubbles demonstrates the use of d3.timer for animation. The molecule reflections are computed using functions in the geoemetry_methods.js
file. See the previous Path-Line Intersection and Path-Line Reflection blocks for details.
Last active
November 21, 2016 16:12
-
-
Save bricof/2c7bdf4394f603e6902e5f0111675c3f to your computer and use it in GitHub Desktop.
Bottled Gas Animation
This file contains hidden or 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
license: gpl-3.0 |
This file contains hidden or 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
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 }; | |
} |
This file contains hidden or 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> | |
<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