Skip to content

Instantly share code, notes, and snippets.

@Kcnarf
Last active December 3, 2018 08:27
Show Gist options
  • Save Kcnarf/89a53e5cbdc9e7f9b24153f6d06e2f33 to your computer and use it in GitHub Desktop.
Save Kcnarf/89a53e5cbdc9e7f9b24153f6d06e2f33 to your computer and use it in GitHub Desktop.
Simplex Wave + Explanations
license: gpl-3.0
border: no
<html>
<head>
<meta charset="utf-8">
<title>Simplex Wave + Explanations</title>
<meta content="How to produce a continuous radial noise with Simplex" name="description">
<style>
#under-construction {
display: none;
position: absolute;
top: 200px;
left: 300px;
font-size: 40px;
}
div.step {
position: relative;
}
div.require {
position: absolute;
left: 150px;
bottom: -10px;
}
.desc {
position: absolute;
left: 300px;
padding: 10px;
}
.desc .highlighted {
color: red;
}
.title {
font-weight: bold;
}
ul {
margin: 0px;
padding-left: 25px
}
canvas {
margin: 10px;
border: solid lightgrey 1px;
border-radius: 5px;
box-shadow: 2px 2px 6px grey;
}
</style>
<body>
<div id="under-construction">
UNDER CONSTRUCTION
</div>
<div id="radial-wave" class="step">
<div class="desc">
<div class="title">Radial wave</div>
<ul>
<li>radial path with a continuous and balanced noise</li>
<li>emerge from the summation of 2 continuous-but-unbalanced radial noises</li>
</ul>
</div>
<canvas></canvas>
<div id="traces" class="require">&downarrow; mixes</div>
</div>
<div id="continuous-radial-noises" class="step">
<div class="desc">
<div class="title">Continuous-but-unbalanced radial noises</div>
<ul>
<li>discontinuities of the 2 underlying radial noises are eliminated by weighting each radial noise with an appropriate function (I use a cos-based function)</li>
<li>red segments highlight where underlying radial noises are discontinuous</li>
</ul>
</div>
<canvas></canvas>
<div id="uses" class="require">&downarrow; weights</div>
</div>
<div id="radial-noises" class="step">
<div class="desc">
<div class="title">Non-continuous radial noises</div>
<ul>
<li>applies the underlying noises in a radial way</li>
<li>red segments highlight where corresponding underlying noises are discontinuous</li>
<li>the characteristics of the underlying noises makes discontinuities diametrically opposed</li>
</ul>
</div>
<canvas></canvas>
<div id="transforms" class="require">&downarrow; applies</div>
</div>
<div id="noises" class="step">
<div class="desc">
<div class="title">SimplexNoises</div>
<ul>
<li>noise values are computed thanks to SimplexNoise</li>
<li>noise values depend on angles (each 4°)</li>
<li>the second noise is shifted by &pi;, so that discontinuities arises at distinct angles</li>
</ul>
</div>
<canvas></canvas>
</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="simplex-noise.min.js"></script>
<script>
var _2PI = 2*Math.PI,
_3PI = 3*Math.PI;
var cbw = 1, //canvas border width
cm = 10, //canvas margin
totalWidth = 300,
totalHeight = 500,
width = totalWidth-(cm+cbw)*2,
height = (totalHeight-((cm+cbw)*2)*4)/4,
midWidth = width/2,
midHeight = height/2,
firstWaveXAlign = midWidth/2,
secondWaveXAlign = 3*midWidth/2;
var simplex = new SimplexNoise(),
angleBasedLength = 1, //lower values make more heratic waves' shapes
timeBasedLength = 5000, //lower values make radiations' shapes evolving faster
noiseStrength = midHeight/4;
var vertexCount = 360/4, // segment per wave
waveRadius = midHeight - noiseStrength,
wave = [];
var noiseScale= d3.scaleLinear().domain([0, 3*Math.PI]).range([-midWidth/2+10, midWidth/2-10]);
initLayout();
initWave();
var radialWaveContext = document.querySelector("#radial-wave canvas").getContext("2d"),
continuousRadialNoisesContext = document.querySelector("#continuous-radial-noises canvas").getContext("2d"),
radialNoisesContext = document.querySelector("#radial-noises canvas").getContext("2d"),
noisesContext = document.querySelector("#noises canvas").getContext("2d");
d3.interval(function(elapsed) {
updateWave(elapsed);
redrawRadialWave();
redrawContinuousRadialNoises();
redrawRadialNoises();
redrawNoises();
});
function initLayout() {
d3.selectAll("canvas")
.attr("width", width)
.attr("height", height);
}
function initWave() {
var angle, oppositeAngle, noise;
for (var v=0; v<vertexCount; v++) {
angle = _2PI*v/vertexCount;
wave[v] = {
angle: angle, // [0, vertexCount] -> [0, 2PI[; discontinuous around 0 rad; will produce noise values discontinued around 0 rad;
oppositeAngle : (angle+Math.PI)%_2PI, // [0, vertexCount] -> [PI, ..., 2PI, 0, ..., PI[; discontinuous around v = 0; will produce noise values discontinued around PI;
continuityCoef: (Math.cos(angle+Math.PI)+1)/2, // allows to produce a continous noise from 'angle'-based noise and 'oppositeAngle'-based noise; allows to eliminate discontinuities around 0 rad and PI;
cos: Math.cos(angle),
sin: Math.sin(angle),
angleNoise: 0, // 'angle'-based noise; updated at each frame
oppositeAngleNoise: 0 // 'opppositeAngle'-based noise; updated at each frame
};
}
}
function updateWave(elapsed) {
var timeBasedChange = elapsed/timeBasedLength;
var vertex;
//begin: update each waves's shape using SimplexNoise
for (var v=0; v<vertexCount; v++) {
vertex = wave[v];
vertex.angleNoise = noiseStrength*simplex.noise2D(vertex.angle/angleBasedLength, timeBasedChange);
vertex.oppositeAngleNoise = noiseStrength*simplex.noise2D(vertex.oppositeAngle/angleBasedLength, timeBasedChange+10);
}
//end: update each waves's shape using SimplexNoise
}
function redrawRadialWave() {
var noisedRadius;
radialWaveContext.setTransform(1, 0, 0, 1, 0, 0);
radialWaveContext.translate(midWidth, midHeight);
//begin: delete existing image
radialWaveContext.clearRect(-width/2, -height/2, width, height)
//begin: delete existing image
//begin: draw reference circles
radialWaveContext.strokeStyle = 'lightgrey';
radialWaveContext.beginPath();
radialWaveContext.arc(0, 0, waveRadius, 0, _2PI);
radialWaveContext.stroke();
//end: draw reference circles
//begin: insert new wave's shape
radialWaveContext.strokeStyle = 'grey';
radialWaveContext.beginPath();
for (var v=0; v<vertexCount; v++) {
noisedRadius = (waveRadius+wave[v].continuityCoef*wave[v].angleNoise+(1-wave[v].continuityCoef)*wave[v].oppositeAngleNoise);
radialWaveContext.lineTo(
noisedRadius*wave[v].cos,
noisedRadius*wave[v].sin
);
}
radialWaveContext.closePath();
radialWaveContext.stroke();
//end: insert new wave's shape
}
function redrawContinuousRadialNoises() {
var noisedRadius;
//begin: delete existing image
continuousRadialNoisesContext.setTransform(1, 0, 0, 1, 0, 0);
continuousRadialNoisesContext.clearRect(0, 0, width, height)
//begin: delete existing image
//begin: draw reference circles
continuousRadialNoisesContext.strokeStyle = 'lightgrey';
continuousRadialNoisesContext.beginPath();
continuousRadialNoisesContext.arc(firstWaveXAlign, midHeight, waveRadius, 0, _2PI);
continuousRadialNoisesContext.moveTo(secondWaveXAlign+waveRadius, midHeight);
continuousRadialNoisesContext.arc(secondWaveXAlign, midHeight, waveRadius, 0, _2PI);
continuousRadialNoisesContext.stroke();
//end: draw reference circles
//begin: draw first radial noise
continuousRadialNoisesContext.strokeStyle = 'grey';
continuousRadialNoisesContext.setTransform(1, 0, 0, 1, 0, 0);
continuousRadialNoisesContext.translate(firstWaveXAlign, midHeight);
continuousRadialNoisesContext.beginPath();
for (var v=0; v<vertexCount; v++) {
noisedRadius = waveRadius+wave[v].continuityCoef*wave[v].angleNoise;
continuousRadialNoisesContext.lineTo(
noisedRadius*wave[v].cos,
noisedRadius*wave[v].sin
);
}
continuousRadialNoisesContext.stroke();
//begin: draw junction of first radial noise
continuousRadialNoisesContext.strokeStyle = 'red';
continuousRadialNoisesContext.beginPath();
for (var v=vertexCount-1; v<vertexCount+1; v++) {
noisedRadius = waveRadius+wave[v%vertexCount].continuityCoef*wave[v%vertexCount].angleNoise;
continuousRadialNoisesContext.lineTo(
noisedRadius*wave[v%vertexCount].cos,
noisedRadius*wave[v%vertexCount].sin
);
}
continuousRadialNoisesContext.closePath();
continuousRadialNoisesContext.stroke();
//end: draw junction of first radial noise
//end: draw first radial noise
//begin: draw second radial noise
continuousRadialNoisesContext.strokeStyle = 'grey';
continuousRadialNoisesContext.setTransform(1, 0, 0, 1, 0, 0);
continuousRadialNoisesContext.translate(secondWaveXAlign, midHeight);
continuousRadialNoisesContext.beginPath();
for (var v=0; v<vertexCount; v++) {
noisedRadius = waveRadius+(1-wave[v].continuityCoef)*wave[v].oppositeAngleNoise;
continuousRadialNoisesContext.lineTo(
noisedRadius*wave[v].cos,
noisedRadius*wave[v].sin
);
}
continuousRadialNoisesContext.closePath();
continuousRadialNoisesContext.stroke();
//begin: draw junction of second radial noise
continuousRadialNoisesContext.strokeStyle = 'red';
continuousRadialNoisesContext.beginPath();
for (var v=vertexCount/2-1; v<vertexCount/2+1; v++) {
noisedRadius = waveRadius+(1-wave[v].continuityCoef)*wave[v].oppositeAngleNoise;
continuousRadialNoisesContext.lineTo(
noisedRadius*wave[v].cos,
noisedRadius*wave[v].sin
);
}
continuousRadialNoisesContext.stroke();
//end: draw junction of second radial noise
//end: draw second radial noise
}
function redrawRadialNoises() {
var noisedRadius;
//begin: delete existing image
radialNoisesContext.setTransform(1, 0, 0, 1, 0, 0);
radialNoisesContext.clearRect(0, 0, width, height)
//begin: delete existing image
//begin: draw reference circles
radialNoisesContext.strokeStyle = 'lightgrey';
radialNoisesContext.beginPath();
radialNoisesContext.arc(firstWaveXAlign, midHeight, waveRadius, 0, _2PI);
radialNoisesContext.moveTo(secondWaveXAlign+waveRadius, midHeight);
radialNoisesContext.arc(secondWaveXAlign, midHeight, waveRadius, 0, _2PI);
radialNoisesContext.stroke();
//end: draw reference circles
//begin: draw first radial noise
radialNoisesContext.strokeStyle = 'grey';
radialNoisesContext.setTransform(1, 0, 0, 1, 0, 0);
radialNoisesContext.translate(firstWaveXAlign, midHeight);
radialNoisesContext.beginPath();
for (var v=0; v<vertexCount; v++) {
noisedRadius = waveRadius+wave[v].angleNoise;
radialNoisesContext.lineTo(
noisedRadius*wave[v].cos,
noisedRadius*wave[v].sin
);
}
radialNoisesContext.stroke();
//begin: draw junction of first radial noise
radialNoisesContext.strokeStyle = 'red';
radialNoisesContext.beginPath();
for (var v=vertexCount-1; v<vertexCount+1; v++) {
noisedRadius = waveRadius+wave[v%vertexCount].angleNoise;
radialNoisesContext.lineTo(
noisedRadius*wave[v%vertexCount].cos,
noisedRadius*wave[v%vertexCount].sin
);
}
radialNoisesContext.stroke();
//end: draw junction of first radial noise
//end: draw first radial noise
//begin: draw second radial noise
radialNoisesContext.strokeStyle = 'grey';
radialNoisesContext.setTransform(1, 0, 0, 1, 0, 0);
radialNoisesContext.translate(secondWaveXAlign, midHeight);
radialNoisesContext.beginPath();
for (var v=0; v<vertexCount; v++) {
noisedRadius = waveRadius+wave[v].oppositeAngleNoise;
radialNoisesContext.lineTo(
noisedRadius*wave[v].cos,
noisedRadius*wave[v].sin
);
}
radialNoisesContext.closePath();
radialNoisesContext.stroke();
//begin: draw junction of second radial noise
radialNoisesContext.strokeStyle = 'red';
radialNoisesContext.beginPath();
for (var v=vertexCount/2-1; v<vertexCount/2+1; v++) {
noisedRadius = waveRadius+wave[v].oppositeAngleNoise;
radialNoisesContext.lineTo(
noisedRadius*wave[v].cos,
noisedRadius*wave[v].sin
);
}
radialNoisesContext.stroke();
//end: draw junction of second radial noise
//end: draw second radial noise
}
function redrawNoises() {
//begin: delete existing image
noisesContext.setTransform(1, 0, 0, 1, 0, 0);
noisesContext.clearRect(0, 0, width, height);
//begin: delete existing image
//begin: draw reference axes
noisesContext.strokeStyle = 'lightgrey';
noisesContext.beginPath();
noisesContext.moveTo(firstWaveXAlign+noiseScale(0), midHeight);
noisesContext.lineTo(firstWaveXAlign+noiseScale(3*Math.PI), midHeight);
noisesContext.moveTo(secondWaveXAlign+noiseScale(0), midHeight);
noisesContext.lineTo(secondWaveXAlign+noiseScale(3*Math.PI), midHeight);
noisesContext.stroke();
var textY = 3*midHeight/2;
noisesContext.fillStyle = 'lightgrey';
noisesContext.textAlign = "start";
noisesContext.fillText("0", firstWaveXAlign+noiseScale(0), textY);
noisesContext.fillText("0", secondWaveXAlign+noiseScale(0), textY);
noisesContext.textAlign = "center";
noisesContext.fillText("π", firstWaveXAlign+noiseScale(Math.PI), textY);
noisesContext.fillText("π", secondWaveXAlign+noiseScale(Math.PI), textY);
noisesContext.fillText("2π", firstWaveXAlign+noiseScale(_2PI), textY);
noisesContext.fillText("2π", secondWaveXAlign+noiseScale(_2PI), textY);
noisesContext.textAlign = "end";
noisesContext.fillText("3π", firstWaveXAlign+noiseScale(_3PI), textY);
noisesContext.fillText("3π", secondWaveXAlign+noiseScale(_3PI), textY);
//end: draw reference axes
//begin: draw first noise
noisesContext.setTransform(1, 0, 0, 1, 0, 0);
noisesContext.translate(firstWaveXAlign, midHeight);
//begin: draw junction of first noise
noisesContext.strokeStyle = 'pink';
noisesContext.beginPath();
for (var v=vertexCount-1; v<vertexCount+1; v++) {
noisesContext.lineTo(
noiseScale(wave[v%vertexCount].angle),
wave[v%vertexCount].angleNoise
);
}
noisesContext.stroke();
//end: draw junction of first noise
noisesContext.strokeStyle = 'grey';
noisesContext.beginPath();
for (var v=0; v<vertexCount; v++) {
noisesContext.lineTo(
noiseScale(wave[v].angle),
wave[v].angleNoise
);
}
noisesContext.stroke();
//end: draw first noise
//begin: draw second noise
noisesContext.setTransform(1, 0, 0, 1, 0, 0);
noisesContext.translate(secondWaveXAlign, midHeight);
//begin: draw junction of second noise
noisesContext.strokeStyle = 'pink';
noisesContext.beginPath();
for (var v=vertexCount/2-1; v<vertexCount/2+1; v++) {
noisesContext.lineTo(
noiseScale(Math.PI+wave[v%vertexCount].oppositeAngle),
wave[v%vertexCount].oppositeAngleNoise
);
}
noisesContext.stroke();
//end: draw junction of second noise
noisesContext.strokeStyle = 'grey';
noisesContext.beginPath();
for (var v=vertexCount/2; v<vertexCount; v++) {
noisesContext.lineTo(
noiseScale(Math.PI+wave[v].oppositeAngle),
wave[v].oppositeAngleNoise
);
}
for (var v=0; v<vertexCount/2; v++) {
noisesContext.lineTo(
noiseScale(Math.PI+wave[v].oppositeAngle),
wave[v].oppositeAngleNoise
);
}
noisesContext.stroke();
//end: draw second noise
}
</script>
</body>
</html>
/*! simplex-noise.js: copyright 2012 Jonas Wagner, licensed under a MIT license. See https://github.com/jwagner/simplex-noise.js for details */
(function(){function o(e){e||(e=Math.random),this.p=new Uint8Array(256),this.perm=new Uint8Array(512),this.permMod12=new Uint8Array(512);for(var t=0;t<256;t++)this.p[t]=e()*256;for(t=0;t<512;t++)this.perm[t]=this.p[t&255],this.permMod12[t]=this.perm[t]%12}var e=.5*(Math.sqrt(3)-1),t=(3-Math.sqrt(3))/6,n=1/3,r=1/6,i=(Math.sqrt(5)-1)/4,s=(5-Math.sqrt(5))/20;o.prototype={grad3:new Float32Array([1,1,0,-1,1,0,1,-1,0,-1,-1,0,1,0,1,-1,0,1,1,0,-1,-1,0,-1,0,1,1,0,-1,1,0,1,-1,0,-1,-1]),grad4:new Float32Array([0,1,1,1,0,1,1,-1,0,1,-1,1,0,1,-1,-1,0,-1,1,1,0,-1,1,-1,0,-1,-1,1,0,-1,-1,-1,1,0,1,1,1,0,1,-1,1,0,-1,1,1,0,-1,-1,-1,0,1,1,-1,0,1,-1,-1,0,-1,1,-1,0,-1,-1,1,1,0,1,1,1,0,-1,1,-1,0,1,1,-1,0,-1,-1,1,0,1,-1,1,0,-1,-1,-1,0,1,-1,-1,0,-1,1,1,1,0,1,1,-1,0,1,-1,1,0,1,-1,-1,0,-1,1,1,0,-1,1,-1,0,-1,-1,1,0,-1,-1,-1,0]),noise2D:function(n,r){var i=this.permMod12,s=this.perm,o=this.grad3,u,a,f,l=(n+r)*e,c=Math.floor(n+l),h=Math.floor(r+l),p=(c+h)*t,d=c-p,v=h-p,m=n-d,g=r-v,y,b;m>g?(y=1,b=0):(y=0,b=1);var w=m-y+t,E=g-b+t,S=m-1+2*t,x=g-1+2*t,T=c&255,N=h&255,C=.5-m*m-g*g;if(C<0)u=0;else{var k=i[T+s[N]]*3;C*=C,u=C*C*(o[k]*m+o[k+1]*g)}var L=.5-w*w-E*E;if(L<0)a=0;else{var A=i[T+y+s[N+b]]*3;L*=L,a=L*L*(o[A]*w+o[A+1]*E)}var O=.5-S*S-x*x;if(O<0)f=0;else{var M=i[T+1+s[N+1]]*3;O*=O,f=O*O*(o[M]*S+o[M+1]*x)}return 70*(u+a+f)},noise3D:function(e,t,i){var s=this.permMod12,o=this.perm,u=this.grad3,a,f,l,c,h=(e+t+i)*n,p=Math.floor(e+h),d=Math.floor(t+h),v=Math.floor(i+h),m=(p+d+v)*r,g=p-m,y=d-m,b=v-m,w=e-g,E=t-y,S=i-b,x,T,N,C,k,L;w<E?E<S?(x=0,T=0,N=1,C=0,k=1,L=1):w<S?(x=0,T=1,N=0,C=0,k=1,L=1):(x=0,T=1,N=0,C=1,k=1,L=0):E<S?w<S?(x=0,T=0,N=1,C=1,k=0,L=1):(x=1,T=0,N=0,C=1,k=0,L=1):(x=1,T=0,N=0,C=1,k=1,L=0);var A=w-x+r,O=E-T+r,M=S-N+r,_=w-C+2*r,D=E-k+2*r,P=S-L+2*r,H=w-1+3*r,B=E-1+3*r,j=S-1+3*r,F=p&255,I=d&255,q=v&255,R=.6-w*w-E*E-S*S;if(R<0)a=0;else{var U=s[F+o[I+o[q]]]*3;R*=R,a=R*R*(u[U]*w+u[U+1]*E+u[U+2]*S)}var z=.6-A*A-O*O-M*M;if(z<0)f=0;else{var W=s[F+x+o[I+T+o[q+N]]]*3;z*=z,f=z*z*(u[W]*A+u[W+1]*O+u[W+2]*M)}var X=.6-_*_-D*D-P*P;if(X<0)l=0;else{var V=s[F+C+o[I+k+o[q+L]]]*3;X*=X,l=X*X*(u[V]*_+u[V+1]*D+u[V+2]*P)}var $=.6-H*H-B*B-j*j;if($<0)c=0;else{var J=s[F+1+o[I+1+o[q+1]]]*3;$*=$,c=$*$*(u[J]*H+u[J+1]*B+u[J+2]*j)}return 32*(a+f+l+c)},noise4D:function(e,t,n,r){var o=this.permMod12,u=this.perm,a=this.grad4,f,l,c,h,p,d=(e+t+n+r)*i,v=Math.floor(e+d),m=Math.floor(t+d),g=Math.floor(n+d),y=Math.floor(r+d),b=(v+m+g+y)*s,w=v-b,E=m-b,S=g-b,x=y-b,T=e-w,N=t-E,C=n-S,k=r-x,L=0,A=0,O=0,M=0;T>N?L++:A++,T>C?L++:O++,T>k?L++:M++,N>C?A++:O++,N>k?A++:M++,C>k?O++:M++;var _,D,P,H,B,j,F,I,q,R,U,z;_=L<3?0:1,D=A<3?0:1,P=O<3?0:1,H=M<3?0:1,B=L<2?0:1,j=A<2?0:1,F=O<2?0:1,I=M<2?0:1,q=L<1?0:1,R=A<1?0:1,U=O<1?0:1,z=M<1?0:1;var W=T-_+s,X=N-D+s,V=C-P+s,$=k-H+s,J=T-B+2*s,K=N-j+2*s,Q=C-F+2*s,G=k-I+2*s,Y=T-q+3*s,Z=N-R+3*s,et=C-U+3*s,tt=k-z+3*s,nt=T-1+4*s,rt=N-1+4*s,it=C-1+4*s,st=k-1+4*s,ot=v&255,ut=m&255,at=g&255,ft=y&255,lt=.6-T*T-N*N-C*C-k*k;if(lt<0)f=0;else{var ct=u[ot+u[ut+u[at+u[ft]]]]%32*4;lt*=lt,f=lt*lt*(a[ct]*T+a[ct+1]*N+a[ct+2]*C+a[ct+3]*k)}var ht=.6-W*W-X*X-V*V-$*$;if(ht<0)l=0;else{var pt=u[ot+_+u[ut+D+u[at+P+u[ft+H]]]]%32*4;ht*=ht,l=ht*ht*(a[pt]*W+a[pt+1]*X+a[pt+2]*V+a[pt+3]*$)}var dt=.6-J*J-K*K-Q*Q-G*G;if(dt<0)c=0;else{var vt=u[ot+B+u[ut+j+u[at+F+u[ft+I]]]]%32*4;dt*=dt,c=dt*dt*(a[vt]*J+a[vt+1]*K+a[vt+2]*Q+a[vt+3]*G)}var mt=.6-Y*Y-Z*Z-et*et-tt*tt;if(mt<0)h=0;else{var gt=u[ot+q+u[ut+R+u[at+U+u[ft+z]]]]%32*4;mt*=mt,h=mt*mt*(a[gt]*Y+a[gt+1]*Z+a[gt+2]*et+a[gt+3]*tt)}var yt=.6-nt*nt-rt*rt-it*it-st*st;if(yt<0)p=0;else{var bt=u[ot+1+u[ut+1+u[at+1+u[ft+1]]]]%32*4;yt*=yt,p=yt*yt*(a[bt]*nt+a[bt+1]*rt+a[bt+2]*it+a[bt+3]*st)}return 27*(f+l+c+h+p)}},typeof define!="undefined"&&define.amd?define(function(){return o}):typeof window!="undefined"&&(window.SimplexNoise=o),typeof exports!="undefined"&&(exports.SimplexNoise=o),typeof module!="undefined"&&(module.exports=o)})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment