Skip to content

Instantly share code, notes, and snippets.

@Gro-Tsen
Created July 2, 2020 23:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Gro-Tsen/f2f8c018e4387152fa5df53697f90fb6 to your computer and use it in GitHub Desktop.
Save Gro-Tsen/f2f8c018e4387152fa5df53697f90fb6 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta charset="utf-8" />
<title>Bring geodesics</title>
</head>
<body>
<h1>Bring geodesics</h1>
<div id="candiv" style="position: relative; width: 600px; height: 600px">
<canvas id="canvas0" width="600" height="600"
style="position: absolute; top: 0; left: 0">
</canvas>
<canvas id="canvas1" width="600" height="600"
style="position: absolute; top: 0; left: 0">
</canvas>
</div>
<script type="text/javascript">
// <![CDATA[
"use strict";
// The Möbius transformations z -> (a*z + b)/(c*z + d) is represented
// as an array of 4 values: Re(a), Im(a), Re(b), Im(b); we normalize
// a*d-b*c=1 (note that there are still two equivalent representations
// of each, up to a global sign), and d and c are the conjugates of a
// and b respectively because we always preserve the unit circle.
var moebius_idn = [1,0,0,0]; // Identity matrix
var moebius_movement_forward = [1.1441228056353685, 0, -0.55589297025142126, 0];
var moebius_movement_turnleft = [0.70710678118654746,-0.70710678118654746,0,0]
var moebius_movement_turnaround = [0,1,0,0]
var moebius_movement_turnright = [0.70710678118654746,0.70710678118654746,0,0]
var moebius_neigh = [
[12.090169943749473, 0, -12.048743057628176, 0],
[5.5450849718747364, -6.5450849718747364, -6.024371528814088, 6.024371528814088],
[16.135254915624209, 1.8090169943749475, -15.772009423292449, -3.7232663656642728],
[5.0450849718747364, 7.6631189606246313, -3.7232663656642728, -8.3254766919639032],
[5.5450849718747364, -2.9270509831248424, -6.024371528814088, 1.4221612025144577],
[5.5450849718747364, 12.399186938124423, -1.4221612025144577, -13.470904260142635],
[16.135254915624209, -1.8090169943749475, -15.772009423292449, -3.7232663656642728],
[5.0450849718747364, 1.8090169943749475, -3.7232663656642728, -3.7232663656642728],
[5.5450849718747364, 12.399186938124423, 1.4221612025144577, -13.470904260142635],
[5.0450849718747364, -7.6631189606246313, -8.3254766919639032, 3.7232663656642728],
[5.5450849718747364, -2.9270509831248424, -6.024371528814088, -1.4221612025144577],
[5.5450849718747364, 2.9270509831248424, -1.4221612025144577, -6.024371528814088],
[5.0450849718747364, 7.6631189606246313, 3.7232663656642728, -8.3254766919639032],
[5.5450849718747364, -12.399186938124423, -13.470904260142635, 1.4221612025144577],
[5.0450849718747364, -1.8090169943749475, -3.7232663656642728, -3.7232663656642728],
[16.135254915624209, 1.8090169943749475, -3.7232663656642728, -15.772009423292449],
[5.5450849718747364, -12.399186938124423, -13.470904260142635, -1.4221612025144577],
[5.5450849718747364, 2.9270509831248424, 1.4221612025144577, -6.024371528814088],
[5.0450849718747364, -7.6631189606246313, -8.3254766919639032, -3.7232663656642728],
[16.135254915624209, -1.8090169943749475, -3.7232663656642728, -15.772009423292449],
[5.5450849718747364, 6.5450849718747364, 6.024371528814088, -6.024371528814088],
[12.090169943749473, 0, 0, -12.048743057628176],
[5.5450849718747364, -6.5450849718747364, -6.024371528814088, -6.024371528814088],
[16.135254915624209, 1.8090169943749475, 3.7232663656642728, -15.772009423292449],
[5.0450849718747364, 7.6631189606246313, 8.3254766919639032, -3.7232663656642728],
[5.5450849718747364, -2.9270509831248424, -1.4221612025144577, -6.024371528814088],
[5.5450849718747364, 12.399186938124423, 13.470904260142635, -1.4221612025144577],
[16.135254915624209, -1.8090169943749475, 3.7232663656642728, -15.772009423292449],
[5.0450849718747364, 1.8090169943749475, 3.7232663656642728, -3.7232663656642728],
[5.5450849718747364, 12.399186938124423, 13.470904260142635, 1.4221612025144577],
[5.0450849718747364, -7.6631189606246313, -3.7232663656642728, -8.3254766919639032],
[5.5450849718747364, -2.9270509831248424, 1.4221612025144577, -6.024371528814088],
[5.5450849718747364, 2.9270509831248424, 6.024371528814088, -1.4221612025144577],
[5.0450849718747364, 7.6631189606246313, 8.3254766919639032, 3.7232663656642728],
[5.5450849718747364, -12.399186938124423, -1.4221612025144577, -13.470904260142635],
[5.0450849718747364, -1.8090169943749475, 3.7232663656642728, -3.7232663656642728],
[16.135254915624209, 1.8090169943749475, 15.772009423292449, -3.7232663656642728],
[5.5450849718747364, -12.399186938124423, 1.4221612025144577, -13.470904260142634],
[5.5450849718747364, 2.9270509831248424, 6.024371528814088, 1.4221612025144577],
[5.0450849718747364, -7.6631189606246313, 3.7232663656642728, -8.3254766919639032],
[16.135254915624209, -1.8090169943749475, 15.772009423292449, -3.7232663656642728],
[5.5450849718747364, 6.5450849718747364, 6.024371528814088, 6.024371528814088],
[12.090169943749473, 0, 12.048743057628176, 0],
[5.5450849718747364, -6.5450849718747364, 6.024371528814088, -6.024371528814088],
[16.135254915624209, 1.8090169943749475, 15.772009423292449, 3.7232663656642728],
[5.0450849718747364, 7.6631189606246313, 3.7232663656642728, 8.3254766919639032],
[5.5450849718747364, -2.9270509831248424, 6.024371528814088, -1.4221612025144577],
[5.5450849718747364, 12.399186938124423, 1.4221612025144577, 13.470904260142635],
[16.135254915624209, -1.8090169943749475, 15.772009423292449, 3.7232663656642728],
[5.0450849718747364, 1.8090169943749475, 3.7232663656642728, 3.7232663656642728],
[5.5450849718747364, 12.399186938124423, -1.4221612025144577, 13.470904260142635],
[5.0450849718747364, -7.6631189606246313, 8.3254766919639032, -3.7232663656642728],
[5.5450849718747364, -2.9270509831248424, 6.024371528814088, 1.4221612025144577],
[5.5450849718747364, 2.9270509831248424, 1.4221612025144577, 6.024371528814088],
[5.0450849718747364, 7.6631189606246313, -3.7232663656642728, 8.3254766919639032],
[5.5450849718747364, -12.399186938124423, 13.470904260142635, -1.4221612025144577],
[5.0450849718747364, -1.8090169943749475, 3.7232663656642728, 3.7232663656642728],
[16.135254915624209, 1.8090169943749475, 3.7232663656642728, 15.772009423292449],
[5.5450849718747364, -12.399186938124423, 13.470904260142635, 1.4221612025144577],
[5.5450849718747364, 2.9270509831248424, -1.4221612025144577, 6.024371528814088],
[5.0450849718747364, -7.6631189606246313, 8.3254766919639032, 3.7232663656642728],
[16.135254915624209, -1.8090169943749475, 3.7232663656642728, 15.772009423292449],
[5.5450849718747364, 6.5450849718747364, -6.024371528814088, 6.024371528814088],
[12.090169943749473, 0, 0, 12.048743057628176],
[5.5450849718747364, -6.5450849718747364, 6.024371528814088, 6.024371528814088],
[16.135254915624209, 1.8090169943749475, -3.7232663656642728, 15.772009423292449],
[5.0450849718747364, 7.6631189606246313, -8.3254766919639032, 3.7232663656642728],
[5.5450849718747364, -2.9270509831248424, 1.4221612025144577, 6.024371528814088],
[5.5450849718747364, 12.399186938124423, -13.470904260142635, 1.4221612025144577],
[16.135254915624209, -1.8090169943749475, -3.7232663656642728, 15.772009423292449],
[5.0450849718747364, 1.8090169943749475, -3.7232663656642728, 3.7232663656642728],
[5.5450849718747364, 12.399186938124423, -13.470904260142635, -1.4221612025144577],
[5.0450849718747364, -7.6631189606246313, 3.7232663656642728, 8.3254766919639032],
[5.5450849718747364, -2.9270509831248424, -1.4221612025144577, 6.024371528814088],
[5.5450849718747364, 2.9270509831248424, -6.024371528814088, 1.4221612025144577],
[5.0450849718747364, 7.6631189606246313, -8.3254766919639032, -3.7232663656642728],
[5.5450849718747364, -12.399186938124423, 1.4221612025144577, 13.470904260142635],
[5.0450849718747364, -1.8090169943749475, -3.7232663656642728, 3.7232663656642728],
[16.135254915624209, 1.8090169943749475, -15.772009423292449, 3.7232663656642728],
[5.5450849718747364, -12.399186938124423, -1.4221612025144577, 13.470904260142635],
[5.5450849718747364, 2.9270509831248424, -6.024371528814088, -1.4221612025144577],
[5.0450849718747364, -7.6631189606246313, -3.7232663656642728, 8.3254766919639032],
[16.135254915624209, -1.8090169943749475, -15.772009423292449, 3.7232663656642728],
[5.5450849718747364, 6.5450849718747364, -6.024371528814088, -6.024371528814088],
];
var moebius_domain = [
[1, 0, 0, 0],
[1.1441228056353685, 0, 0.55589297025142126, 0],
[0.80901699437494745, 0.80901699437494745, 0.39307568887871169, 0.39307568887871169],
[0, 1.1441228056353685, 0, 0.55589297025142126],
[0.80901699437494745, -0.80901699437494745, 0.39307568887871169, -0.39307568887871169],
[1.6180339887498949, 0, 1.272019649514069, 0],
[1.1441228056353685, 0.70710678118654746, 0.89945371997393364, 0],
[1.1441228056353685, -0.70710678118654746, 0.89945371997393364, 0],
[1.1441228056353685, 1.1441228056353685, 0.89945371997393364, 0.89945371997393364],
[0.30901699437494745, 1.3090169943749475, 0.63600982475703449, 0.63600982475703449],
[1.3090169943749475, 0.30901699437494745, 0.63600982475703449, 0.63600982475703449],
[0, 1.6180339887498949, 0, 1.272019649514069],
[0.70710678118654746, -1.1441228056353685, 0, -0.89945371997393364],
[0.70710678118654746, 1.1441228056353685, 0, 0.89945371997393364],
[1.1441228056353685, -1.1441228056353685, 0.89945371997393364, -0.89945371997393364],
[1.3090169943749475, -0.30901699437494745, 0.63600982475703449, -0.63600982475703449],
[0.30901699437494745, -1.3090169943749475, 0.63600982475703449, -0.63600982475703449],
[2.5583363680084634, 0, 2.3548004101992888, 0],
[1.8090169943749475, 0.80901699437494745, 1.6650953383927805, -0.39307568887871169],
[1.8090169943749475, -0.80901699437494745, 1.6650953383927805, 0.39307568887871169],
[1.8090169943749475, 0.80901699437494745, 1.6650953383927805, 0.39307568887871169],
[1.851229586821916, 0, 1.4553466902253547, 0.55589297025142126],
[1.8090169943749475, -0.80901699437494745, 1.6650953383927805, -0.39307568887871169],
[1.851229586821916, 0, 1.4553466902253547, -0.55589297025142126],
[1.8090169943749475, 1.8090169943749475, 1.6650953383927805, 1.6650953383927805],
[0.70710678118654746, 1.851229586821916, 1.4553466902253547, 0.89945371997393364],
[1.851229586821916, 0.70710678118654746, 0.89945371997393364, 1.4553466902253547],
[0.70710678118654746, 1.851229586821916, 0.89945371997393364, 1.4553466902253547],
[1.3090169943749475, 1.3090169943749475, 0.63600982475703449, 1.4221612025144577],
[1.851229586821916, 0.70710678118654746, 1.4553466902253547, 0.89945371997393364],
[1.3090169943749475, 1.3090169943749475, 1.4221612025144577, 0.63600982475703449],
[0, 2.5583363680084634, 0, 2.3548004101992888],
[0.80901699437494745, -1.8090169943749475, -0.39307568887871169, -1.6650953383927805],
[0.80901699437494745, 1.8090169943749475, -0.39307568887871169, 1.6650953383927805],
[0.80901699437494745, -1.8090169943749475, 0.39307568887871169, -1.6650953383927805],
[0, 1.851229586821916, -0.55589297025142126, 1.4553466902253547],
[0.80901699437494745, 1.8090169943749475, 0.39307568887871169, 1.6650953383927805],
[0, 1.851229586821916, 0.55589297025142126, 1.4553466902253547],
[1.8090169943749475, -1.8090169943749475, 1.6650953383927805, -1.6650953383927805],
[1.851229586821916, -0.70710678118654746, 0.89945371997393364, -1.4553466902253547],
[0.70710678118654746, -1.851229586821916, 1.4553466902253547, -0.89945371997393364],
[1.851229586821916, -0.70710678118654746, 1.4553466902253547, -0.89945371997393364],
[1.3090169943749475, -1.3090169943749475, 1.4221612025144577, -0.63600982475703449],
[0.70710678118654746, -1.851229586821916, 0.89945371997393364, -1.4553466902253547],
[1.3090169943749475, -1.3090169943749475, 0.63600982475703449, -1.4221612025144577],
];
function moebius_product (m1, m2, m) {
"use strict";
// Compute the product of two Möbius transformations. Store the
// result in m, or allocate a new value if no m is provided.
if ( typeof(m) == "undefined" )
m = new Array(4);
m[0] = (m1[0]*m2[0] - m1[1]*m2[1] + m1[2]*m2[2] + m1[3]*m2[3]);
m[1] = (m1[0]*m2[1] + m1[1]*m2[0] - m1[2]*m2[3] + m1[3]*m2[2]);
m[2] = (m1[0]*m2[2] - m1[1]*m2[3] + m1[2]*m2[0] + m1[3]*m2[1]);
m[3] = (m1[0]*m2[3] + m1[1]*m2[2] - m1[2]*m2[1] + m1[3]*m2[0]);
return m;
}
function moebius_inverse (m) {
"use strict";
// Compute the inverse of a Möbius transformation.
return [m[0], -m[1], -m[2], -m[3]];
}
function moebius_apply (m, z) {
"use strict";
// Apply a Möbius transformation to a complex z.
var numer0 = m[0]*z[0] - m[1]*z[1] + m[2];
var numer1 = m[0]*z[1] + m[1]*z[0] + m[3];
var denom0 = m[2]*z[0] + m[3]*z[1] + m[0];
var denom1 = m[2]*z[1] - m[3]*z[0] - m[1];
var denom_norm = denom0*denom0 + denom1*denom1;
return [(numer0*denom0+numer1*denom1)/denom_norm,
(-numer0*denom1+numer1*denom0)/denom_norm];
}
function moebius_diff (m, z) {
"use strict";
// Compute the differential of a Möbius transformation at z.
var denom0 = m[2]*z[0] + m[3]*z[1] + m[0];
var denom1 = m[2]*z[1] - m[3]*z[0] - m[1];
var denom_norm = denom0*denom0 + denom1*denom1;
return [(denom0*denom0-denom1*denom1)/(denom_norm*denom_norm),
-(2*denom0*denom1)/(denom_norm*denom_norm)];
}
function moebius_renormalize (m) {
"use strict";
// Enforce the determinant of m to be 1 (for numeric stability).
var det = m[0]*m[0] + m[1]*m[1] - m[2]*m[2] - m[3]*m[3];
var nr = 1/Math.sqrt(det);
for ( var i=0 ; i<4 ; i++ )
m[i] *= nr;
}
function draw_hyperbolic_circle_poincare (ctx, m, col, orad) {
"use strict";
// Since the circle is centered at the origin before
// transformation, we can afford to use the exact formulæ for the
// transform of a circle by a Möbius transformation.
var cen0, cen1, radius;
function transform_circle (oradius) {
"use strict";
// (NB: oradius is tanh(r/2) where r is the true hyperbolic radius.)
var denom = m[0]*m[0] + m[1]*m[1] - (m[2]*m[2] + m[3]*m[3])*oradius*oradius;
cen0 = (m[2]*m[0] - m[3]*m[1] - (m[0]*m[2] - m[1]*m[3])*oradius*oradius) / denom;
cen1 = (m[3]*m[0] + m[2]*m[1] - (m[1]*m[2] + m[0]*m[3])*oradius*oradius) / denom;
radius = oradius / denom;
// vradius = (m[0]*m[0] + m[1]*m[1] + (m[2]*m[2] + m[3]*m[3])*oradius*oradius)/(denom*denom);
}
ctx.fillStyle = col;
ctx.beginPath();
transform_circle(orad);
ctx.arc(-cen1, -cen0, radius, 0, 2*Math.PI, false);
ctx.fill();
}
function poincare_to_bk (p) {
"use strict";
// Convert from Poincaré to Beltrami-Klein coordinates.
var t = 1 + p[0]*p[0] + p[1]*p[1];
return [2*p[0]/t, 2*p[1]/t];
}
function draw_hyperbolic_circle_bk (ctx, m, col, orad) {
"use strict";
var bkcen0, bkcen1, bkangle, bkmaj, bkmin;
function transform_circle (oradius) {
"use strict";
var denom = m[0]*m[0] + m[1]*m[1] - (m[2]*m[2] + m[3]*m[3])*oradius*oradius;
var pcen0 = (m[2]*m[0] - m[3]*m[1] - (m[0]*m[2] - m[1]*m[3])*oradius*oradius) / denom;
var pcen1 = (m[3]*m[0] + m[2]*m[1] - (m[1]*m[2] + m[0]*m[3])*oradius*oradius) / denom;
var pradius = oradius / denom;
var pcdst = Math.sqrt(pcen0*pcen0 + pcen1*pcen1);
var up0 = pcen0/pcdst;
var up1 = pcen1/pcdst;
if ( pcdst < 1.e-9 ) {
up0 = 1; up1 = 0;
}
var bkfar = poincare_to_bk([pcen0 + pradius*up0, pcen1 + pradius*up1]);
var bknear = poincare_to_bk([pcen0 - pradius*up0, pcen1 - pradius*up1]);
var bkcen = [(bkfar[0]+bknear[0])/2, (bkfar[1]+bknear[1])/2];
bkcen0 = bkcen[0]; bkcen1 = bkcen[1]; // Center of the ellipse
bkangle = Math.atan2(pcen1, pcen0); // Angle of minor axis
// Semi-minor axis:
bkmin = Math.sqrt((bkfar[0]-bkcen[0])*(bkfar[0]-bkcen[0])
+ (bkfar[1]-bkcen[1])*(bkfar[1]-bkcen[1]));
var bkaux = poincare_to_bk([pcen0 - pradius*up1, pcen1 + pradius*up0]);
var dpar = Math.abs((bkaux[0]-bkcen[0])*up0 + (bkaux[1]-bkcen[1])*up1);
var dort = Math.abs((bkaux[0]-bkcen[0])*up1 - (bkaux[1]-bkcen[1])*up0);
// Semi-major axis:
bkmaj = dort/Math.sqrt(1-(dpar/bkmin)*(dpar/bkmin));
}
ctx.fillStyle = col;
ctx.beginPath();
transform_circle(orad);
ctx.save(); ctx.scale(1, -1); ctx.rotate(Math.PI/2);
ctx.translate(bkcen0, bkcen1); ctx.rotate(bkangle);
ctx.scale(bkmin, bkmaj); ctx.arc(0, 0, 1, 0, 2*Math.PI, false);
ctx.fill(); ctx.restore();
}
var draw_hyperbolic_circle = draw_hyperbolic_circle_poincare;
var canvas0 = document.getElementById("canvas0");
var canvas1 = document.getElementById("canvas1");
if ( typeof(canvas0.getContext) != "function" ) {
alert("Your browser does not support the HTML5 <canvas> element.\n"
+ "This page will not function.");
throw new Error("Canvas unsupported");
}
var ctx0 = canvas0.getContext("2d");
var ctx1 = canvas1.getContext("2d");
var scale = Math.min(canvas0.width/2, canvas0.height/2) - 10;
var nbpoints = 12;
var colortab = [ // Color of the points (should have nbpoints entries)
"rgb(255,0,0)", "rgb(255,128,0)", "rgb(255,255,0)", "rgb(128,255,0)",
"rgb(0,255,0)", "rgb(0,255,128)", "rgb(0,255,255)", "rgb(0,128,255)",
"rgb(0,0,255)", "rgb(128,0,255)", "rgb(255,0,255)", "rgb(255,0,128)"
];
var pts;
function init_points() {
"use strict";
var t = Math.random();
var u = Math.log(Math.random()) * 0.1;
var basepoint = moebius_product([Math.cos(Math.PI*t), Math.sin(Math.PI*t), 0, 0],
[Math.cosh(u), 0, Math.sinh(u), 0]);
pts = new Array(nbpoints);
for ( var j=0 ; j<nbpoints ; j++ )
pts[j] = moebius_product(basepoint,
[Math.cos(Math.PI*j/nbpoints), Math.sin(Math.PI*j/nbpoints), 0, 0]);
}
init_points();
if ( typeof(Math.cosh) == "function" ) {
// Good boy!
} else {
Math.cosh = function(t) { return (Math.exp(t)+Math.exp(-t))/2; }
Math.sinh = function(t) { return (Math.exp(t)-Math.exp(-t))/2; }
}
function forward_points() {
"use strict";
for ( var j=0 ; j<nbpoints ; j++ ) {
pts[j] = moebius_product(pts[j], [Math.cosh(0.01/2), 0, Math.sinh(0.01/2), 0]);
moebius_renormalize(pts[j]);
}
}
function draw_background() {
"use strict";
// Draw the canvas 0.
ctx0.fillStyle = "rgb(255,255,255)";
ctx0.fillRect(0,0,canvas1.width,canvas1.height);
ctx0.save();
ctx0.translate(canvas1.width/2, canvas1.height/2);
ctx0.scale(scale, scale);
ctx0.fillStyle = "rgb(128,128,128)";
ctx0.beginPath();
ctx0.arc(0, 0, 1, 0, 2*Math.PI, false);
ctx0.fill();
var baserad = 0.062419;
for ( var j=0 ; j<moebius_domain.length ; j++ ) {
var pt = moebius_domain[j];
draw_hyperbolic_circle (ctx0, pt, "rgb(96,96,96)", baserad);
for ( var i=0 ; i<moebius_neigh.length ; i++ ) {
var m = moebius_product(moebius_neigh[i], pt);
draw_hyperbolic_circle (ctx0, m, "rgb(96,96,96)", baserad);
}
}
baserad = 0.18533;
if ( 1 ) {
draw_hyperbolic_circle (ctx0, moebius_idn, "rgb(192,192,192)", baserad);
for ( var i=0 ; i<moebius_neigh.length ; i++ ) {
draw_hyperbolic_circle (ctx0, moebius_neigh[i], "rgb(192,192,192)", baserad);
}
}
ctx0.restore();
}
draw_background();
function draw_points() {
"use strict";
// Draw the canvas 1.
ctx1.clearRect(0,0,canvas1.width,canvas1.height);
ctx1.save();
ctx1.translate(canvas1.width/2, canvas1.height/2);
ctx1.scale(scale, scale);
var baserad = 0.062419;
for ( var j=0 ; j<nbpoints ; j++ ) {
var pt = pts[j];
var newpt = pt;
var newpt_altered = false;
draw_hyperbolic_circle (ctx1, pt, colortab[j], baserad);
for ( var i=0 ; i<moebius_neigh.length ; i++ ) {
var m = moebius_product(moebius_neigh[i], pt);
draw_hyperbolic_circle (ctx1, m, colortab[j], baserad);
if ( m[2]*m[2]+m[3]*m[3] < newpt[2]*newpt[2]+newpt[3]*newpt[3] ) {
newpt = m;
newpt_altered = true;
}
}
if ( newpt_altered )
pts[j] = newpt;
}
ctx1.restore();
}
draw_points();
function update() {
"use strict";
forward_points();
draw_points();
window.setTimeout(update, 50);
}
window.setTimeout(update, 50);
// ]]>
</script>
<hr />
<address><a href="http://www.madore.org/~david/">David Madore</a></address>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment