Skip to content

Instantly share code, notes, and snippets.

@bricof
Last active June 25, 2016 19:40
  • 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/af78d8d38d48d8f00f253a0e8947ea21 to your computer and use it in GitHub Desktop.
Path-Line Reflection
license: gpl-3.0

This example builds on the previous Path-Line Intersection block by picking the path-line intersection that is closest to the line's starting point, computing the angle of intersection and the angle of reflection, and then drawing a reflection line. See the Bottled Gas Animation block for an example that uses this functionality.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
.polygon line {
fill: none;
stroke: #000;
stroke-width: 0.5;
}
.polygon circle {
fill: #000;
stroke: #fff;
cursor: move;
}
.reflection {
fill: none;
stroke: #000;
}
</style>
<body>
<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>
var n_segments = 100;
var svg = d3.select("svg");
var path = svg.select("path");
var polygon = svg.append("g")
.attr("class", "polygon")
.datum([[300, 350], [660, 200]]);
var highlights = svg.append("g");
polygon.append("line")
.attr("stroke-dasharray", "5, 5")
.call(positionLine);
polygon.selectAll("circle")
.data(function(d) { return d; })
.enter().append("circle")
.call(positionCircle)
.attr("r", 4.5)
.call(d3.behavior.drag()
.origin(function(d) { return {x: d[0], y: d[1]}; })
.on("drag", function(d) {
d[0] = d3.event.x, d[1] = d3.event.y;
d3.select(this).call(positionCircle);
polygon.select("line").call(positionLine);
compute_angle();
}));
var pathEl = path.node();
var pathLength = pathEl.getTotalLength();
var line = polygon.select("line");
function positionCircle(circle) {
circle
.attr("cx", function(d) { return d[0]; })
.attr("cy", function(d) { return d[1]; });
}
function positionLine(line) {
line
.attr("x1", function(d) { return d[0][0]; })
.attr("y1", function(d) { return d[0][1]; })
.attr("x2", function(d) { return d[1][0]; })
.attr("y2", function(d) { return d[1][1]; });
}
var dist_btwn_pts = function(pt1, pt2) {
return Math.pow(Math.pow(pt1.x - pt2.x,2) + Math.pow(pt1.y - pt2.y,2) , 0.5);
}
var angle_btwn_lines = function(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};
return (Math.atan2(vector2.y, vector2.x) - Math.atan2(vector1.y, vector1.x)) * 180 / Math.PI;
}
var btwn = function(a, b1, b2) {
if ((a >= b1) && (a <= b2)) { return true; }
if ((a >= b2) && (a <= b1)) { return true; }
return false;
}
var line_line_intersect = function(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"; }
}
}
var path_line_intersections = function(pathEl, line) {
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 line2 = {x1: line.attr('x1'), x2: line.attr('x2'),
y1: line.attr('y1'), y2: line.attr('y2')};
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);
}
var first_path_line_intersection = function(pathEl, line) {
var start_pt = {x: line.attr('x1'), y: line.attr('y1')};
var int_pts_and_segs = path_line_intersections(pathEl, line);
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';
}
}
var draw_reflection = function(pt, angle, int_seg ) {
var angle_text = Math.abs(angle);
if (angle_text > 90) { angle_text = Math.abs(180 - angle_text); }
if (angle_text > 90) { angle_text = Math.abs(180 - angle_text); }
highlights.selectAll("text").remove();
highlights.selectAll("line").remove();
highlights.append("text")
.attr("x", pt.x + 10)
.attr("y", pt.y + 10)
.text(angle_text.toPrecision(4) + "º");
highlights.append("line")
.attr("class", "reflection")
.attr("x1", pt.x)
.attr("y1", pt.y)
.attr("x2", pt.x + 20 * (int_seg.x2 - int_seg.x1))
.attr("y2", pt.y + 20 * (int_seg.y2 - int_seg.y1))
.attr("transform", "rotate(" + angle + " " + pt.x + " " + pt.y + ")");
highlights.append("line")
.attr("class", "reflection")
.attr("x1", pt.x)
.attr("y1", pt.y)
.attr("x2", line.attr('x1'))
.attr("y2", line.attr('y1'));
}
var compute_angle = function() {
var intersection = first_path_line_intersection(pathEl, line);
if (typeof(intersection) != "string") {
var int_pt = intersection[0];
var int_seg = intersection[1];
var line2 = {x1: line.attr('x1'), x2: line.attr('x2'),
y1: line.attr('y1'), y2: line.attr('y2')};
var angle = angle_btwn_lines(line2, int_seg);
draw_reflection( int_pt, angle, int_seg );
} else {
highlights.selectAll("text").remove();
highlights.selectAll("line").remove();
}
}
compute_angle();
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment