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.
Path-Line Reflection
This file contains 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 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> | |
.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