This block experiments SimplexNoise (see also 3, 2, 1). More particularly, how to produce a continous radial noise.
Work initiated by this block from elenstar, Drawing Vector Field, and the outstanding Neonflames.
xxxxxxxxxx
<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">↓ 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">↓ 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">↓ 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 π, 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>
https://d3js.org/d3.v4.min.js