Christiaan Huygens arc length approximation through the arc height and chord length
Last active
April 29, 2024 11:53
-
-
Save w8r/e4963a52331637f4eb3b11c42233a4fd to your computer and use it in GitHub Desktop.
Huygens' arc length approximation
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> | |
<html> | |
<head> | |
<title>Huygens' arc length approximation</title> | |
<script src="https://unpkg.com/d3@4.4.1"></script> | |
<style> | |
circle { | |
fill-opacity: 1; | |
stroke: #000000; | |
stroke-width: 0.5; | |
} | |
text { | |
font-family: sans-serif; | |
font-size: 10px; | |
fill: #000000; | |
} | |
.drawing { | |
z-index: 0; | |
} | |
.controls { | |
z-index: 1; | |
} | |
.circle { | |
stroke-width: 2; | |
fill: none; | |
z-index: 0; | |
} | |
.point { | |
cursor: move; | |
fill: #cccccc; | |
fill-opacity: 1; | |
z-index: 10; | |
} | |
.active { | |
stroke-width: 1; | |
cursor: move; | |
} | |
.chord, .sagitta, .half-chord, .radius { | |
stroke-width: 1; | |
stroke: #000000; | |
} | |
.radius { | |
stroke: #2222ff; | |
stroke-opacity: 0.3; | |
} | |
.control { | |
position: absolute; | |
top: 20px; | |
right: 20px; | |
padding: 10px 20px; | |
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | |
font-weight: 300; | |
background: #fff; | |
border-radius: 4px; | |
box-shadow: 0 0 5px rgba(0,0,0,0.5); | |
} | |
.control h4 { | |
font-weight: 300; | |
} | |
.control input[type=number] { | |
width: 50px; | |
} | |
.arc { | |
stroke-width: 3; | |
stroke: #aa5555; | |
} | |
.name { | |
font-weight: bold; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="control"> | |
<h4><a href="https://en.wikipedia.org/wiki/Christiaan_Huygens" target="_blank">Huygens</a>' arc length approximation</h4> | |
<table> | |
<tr> | |
<td class="name">Angle</td> | |
<td id="angle"></td> | |
</tr> | |
<tr> | |
<td class="name">R × θ</td> | |
<td id="classic"></td> | |
</tr> | |
<tr> | |
<td class="name">Huygens</td> | |
<td id="huygens"></td> | |
<tr> | |
<td class="name">Error</td> | |
<td id="error"></td> | |
</tr> | |
</table> | |
</div> | |
<script src="index.js"></script> | |
</body> | |
</html> |
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
const h = document.documentElement.clientHeight; | |
const w = h; | |
const arcWeight = 5; | |
const deg2rad = Math.PI / 180; | |
const angleVal = document.getElementById('angle'); | |
const classicVal = document.getElementById('classic'); | |
const huygensVal = document.getElementById('huygens'); | |
const errorVal = document.getElementById('error'); | |
const color = d3.interpolateRgb('steelblue', 'brown'); | |
const container = d3.select('body') | |
.append('svg') | |
.attr('width', w) | |
.attr('height', h) | |
.attr('viewBox', [-w/2, -h/2, w, h].join(' ')); | |
// canvas | |
const svg = container.append('g') | |
.attr('class', 'drawing'); | |
const controls = container.append('g') | |
.attr('class', 'controls'); | |
let circles, arc, span, startAngle, endAngle; | |
var C = [ 0, 0]; | |
var R = (w / 2) * 0.75; | |
var sizes = [10, 10]; | |
var angles = [0, Math.PI / 2]; | |
var X = [0, 0]; | |
var Y = [0, 0]; | |
circles = controls.selectAll('circle') | |
.data(sizes) | |
.enter() | |
.append('circle') | |
.attr('cx', function (size, i) { return X[i]; }) | |
.attr('cy', function (size, i) { return Y[i]; }) | |
.attr('r', function (size) { return size; }) | |
.attr('class', 'point') | |
.attr('data-index', function (s, i) { return i; }) | |
.call(d3.drag() | |
.on('start', dragstart) | |
.on('drag', drag) | |
.on('end', dragend)); | |
controls | |
.append('circle') | |
.attr('class', 'center') | |
.attr('cx', C[0]).attr('cy', C[1]) | |
.attr('r', 2); | |
controls | |
.append('circle') | |
.attr('class', 'circle') | |
.attr('cx', C[0]).attr('cy', C[1]) | |
.attr('r', R); | |
circles.raise(); | |
function crossProduct (ax, ay, bx, by, cx, cy) { | |
// compute the cross product of vectors (center -> a) x (center -> b) | |
var det = (ax - cx) * (by - cy) - (bx - cx) * (ay - cy); | |
if (det < 0) return 1; | |
if (det > 0) return -1; | |
return 0; // same dir | |
} | |
function dotProduct (ax, ay, bx, by, cx, cy) { | |
return (ax - cx) * (bx - cx) + (ay - cy) * (by - cy); | |
} | |
function distance (ax, ay, bx, by) { | |
var dx = ax - bx; | |
var dy = ay - by; | |
return Math.sqrt(dx * dx + dy * dy); | |
} | |
function closestPointOnCircle (px, py) { | |
let vX = px - C[0]; | |
let vY = py - C[1]; | |
let magV = Math.sqrt(vX * vX + vY * vY); | |
return [ | |
C[0] + vX / magV * R, | |
C[1] + vY / magV * R | |
]; | |
} | |
function dragstart (d) { | |
d3.select(this) | |
.raise() | |
.classed('active', true); | |
} | |
function drag (d) { | |
const closest = closestPointOnCircle(d3.event.x, d3.event.y); | |
const i = parseInt(this.getAttribute('data-index')); | |
X[i] = closest[0]; | |
Y[i] = closest[1]; | |
d3.select(this) | |
.attr('cx', closest[0]) | |
.attr('cy', closest[1]); | |
render(); | |
} | |
function dragend (d, i) { | |
d3.select(this) | |
.classed('active', false); | |
} | |
function arcPath () { | |
startAngle = Math.atan2(Y[0] - C[1], X[0] - C[0]) + Math.PI / 2; | |
endAngle = Math.atan2(Y[1] - C[1], X[1] - C[0]) + Math.PI / 2; | |
return d3.arc() | |
.innerRadius(R - arcWeight / 2) | |
.outerRadius(R + arcWeight / 2)({ startAngle, endAngle }); | |
} | |
N = 2; | |
sizes = new Array(N).fill(10); | |
function generate () { | |
angles = new Array(N).fill(359).map(a => (0 + a * Math.random()) % 360); | |
R = (w / 2) * 0.75; | |
//R *= 3; | |
angles.forEach((a, i) => { | |
X[i] = C[0] + R * Math.sin(a * deg2rad); | |
Y[i] = C[1] - R * Math.cos(a * deg2rad); | |
}); | |
render(); | |
} | |
function render () { | |
svg.selectAll('circle').remove(); | |
svg.selectAll('line').remove(); | |
svg.selectAll('path').remove(); | |
circles | |
.attr('cx', function (size, i) { return X[i]; }) | |
.attr('cy', function (size, i) { return Y[i]; }); | |
svg | |
.append('path') | |
.attr('class', 'arc') | |
.attr('d', arcPath); | |
svg | |
.append('line') | |
.attr('class', 'chord') | |
.attr('x1', X[0]).attr('y1', Y[0]) | |
.attr('x2', X[1]).attr('y2', Y[1]); | |
var P = [(X[0] + X[1]) / 2, (Y[0] + Y[1]) / 2]; | |
svg | |
.append('circle') | |
.attr('cx', P[0]).attr('cy', P[1]) | |
.attr('r', 3); | |
var angleDiff = Math.abs(endAngle - startAngle); | |
var angle = Math.atan2(P[1] - C[1], P[0] - C[0]); | |
if (angleDiff > Math.PI) angle = angle - Math.PI; | |
var CP = [ | |
C[0] + R * Math.cos(angle), | |
C[1] + R * Math.sin(angle) | |
]; | |
svg | |
.append('circle') | |
.attr('cx', CP[0]).attr('cy', CP[1]) | |
.attr('r', 8) | |
svg | |
.append('line') | |
.attr('class', 'sagitta') | |
.attr('x1', P[0]).attr('y1', P[1]) | |
.attr('x2', CP[0]).attr('y2', CP[1]); | |
svg | |
.append('line') | |
.attr('class', 'half-chord') | |
.attr('x1', X[0]).attr('y1', Y[0]) | |
.attr('x2', CP[0]).attr('y2', CP[1]); | |
svg | |
.append('line') | |
.attr('class', 'half-chord') | |
.attr('x1', X[1]).attr('y1', Y[1]) | |
.attr('x2', CP[0]).attr('y2', CP[1]); | |
svg | |
.append('line') | |
.attr('class', 'radius') | |
.attr('x1', C[0]).attr('y1', C[1]) | |
.attr('x2', X[0]).attr('y2', Y[0]); | |
svg | |
.append('line') | |
.attr('class', 'radius') | |
.attr('x1', C[0]).attr('y1', C[1]) | |
.attr('x2', X[1]).attr('y2', Y[1]); | |
var approx = huygens([X[0], Y[0]], [X[1], Y[1]], CP); | |
var exact = R * angleDiff; | |
huygensVal.innerHTML = approx; | |
classicVal.innerHTML = exact; | |
angleVal.innerHTML = (angleDiff * 180 / Math.PI).toFixed(2) + '°'; | |
errorVal.innerHTML = Math.abs(((approx - exact) / exact) * 100).toFixed(4) + '%'; | |
} | |
function huygens (a, b, c) { | |
var l = Math.sqrt( | |
(a[0] - c[0]) * (a[0] - c[0]) + (a[1] - c[1]) * (a[1] - c[1])); | |
var L = Math.sqrt( | |
(a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1])); | |
return 2 * l + (2 * l - L) / 3; | |
} | |
generate(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi there,
There is a precise formula for the arc length with a given chord and arc height (Russian article):
https://isicad.ru/ru/articles.php?article_num=22566