This block experiments SimplexNoise (see also 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.
In this block, I use SimplexNoise to produce each radiation's shape and growth. The shape is composed of lines joining several verteces. Verteces evolve thanks to values produced with SimplexNoise. The challenge was to compute a continuous noise between the first few verteces (from 0 rad) and the last few verteces (before 2*PI). The technique I use is:
The Simplex Wave + Explanations block explains this technique in a more appealing way (with drawings ;-).
This block is also the first one that allows me to use SimplexNoise with polar coordinates (instead of cartesian coordinates). Noise depends on r and θ (intead of the more classical x and y).
xxxxxxxxxx
<meta charset="utf-8">
<style>
#under-construction {
display: none;
position: absolute;
top: 200px;
left: 300px;
font-size: 40px;
}
#radiation {
margin: 1px;
border-radius: 1000px;
box-shadow: 2px 2px 6px grey;
cursor: crosshair;
}
</style>
<body>
<div id="under-construction">
UNDER CONSTRUCTION
</div>
<canvas id="radiation"></canvas>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="simplex-noise.min.js"></script>
<script>
var _2PI = 2*Math.PI;
var simplex = new SimplexNoise();
radiusBasedLength = 100,//lower values make more heratic growth
angleBasedLength = 0.5, //lower values make more heratic radiations' shapes
timeBasedLength = 1000; //lower values make radiations' shapes evolving faster
var spawnRate = 500,
maxRadiationCount = 10,
vertexCount = 360/4, // segment per radiation
maxRadiationGrowth = 2,
root = {}, // initial radiation's center
rootObjective = {}, // allows root to move toward mouse pointer
radiations = [];
var cbw = 1, //canvas border width
cm = 10, //canvas margintotalWidth = 960,
totalWidth = 960,
totalHeight = 500;
width = totalHeight-(cm+cbw)*2,
height = totalHeight-(cm+cbw)*2;
initLayout();
root = {x: width/2, y: height/2};
rootObjective = {x: width/2, y: height/2};
addRadiation();
var radiationContext = document.querySelector("#radiation").getContext("2d");
d3.interval(function(elapsed) {
addRadiation();
}, spawnRate);
d3.interval(function(elapsed) {
updateRadiatons(elapsed);
redrawRadiations();
});
function initLayout() {
d3.select("#radiation")
.attr("width", width)
.attr("height", height)
.on("touchmove mousemove", moved)
.on("mouseout", exited);
}
function moved() {
var coords = d3.mouse(this);
rootObjective = {x:coords[0], y: coords[1]};
}
function exited() {
rootObjective = {x:width/2, y: height/2};
}
function addRadiation() {
var radiation, angle;
radiation = new Array(vertexCount);
for (var v=0; v<vertexCount; v++) {
angle = _2PI*v/vertexCount;
radiation[v] = {
x : root.x,
y : root.y,
r: 0,
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)
};
}
radiations.unshift(radiation);
if (radiations.length>maxRadiationCount) {
radiations.pop();
}
}
function updateRadiatons(elapsed) {
var timeBasedChange = elapsed/timeBasedLength;
var radiation, vertex,
noise1, // noise based on vertex.angle; discontinuous at 0 rad
noise2, // noise based on vertex.oppositeAngle; discontinuous at PI
continuousNoise, // noise based on noise1 and noise2; always continuous; only constituted of noise2 when noise1 is discontinuous (and vice versa);
allVertecesOut;
//begin: damping root's movement toward target
root.x = 0.995*root.x + 0.005*rootObjective.x;
root.y = 0.995*root.y + 0.005*rootObjective.y;
//end: damping root's movement toward target
for (var r=0; r<radiations.length; r++) {
radiation = radiations[r];
allVertecesOut = true;
//begin: update each vertex's radius using SimplexNoise
for (var v=0; v<vertexCount; v++) {
vertex = radiation[v];
noise1 = simplex.noise3D(vertex.r/radiusBasedLength, vertex.angle/angleBasedLength, timeBasedChange);
noise2 = simplex.noise3D(vertex.r/radiusBasedLength, vertex.oppositeAngle/angleBasedLength, timeBasedChange+10);
continuousNoise = noise1*vertex.continuityCoef + noise2*(1-vertex.continuityCoef);
vertex.r += (1 + (maxRadiationGrowth-1)*(1+continuousNoise)/2);
if (vertex.r < height/2) {
allVertecesOut = false;
}
}
//end: update each vertex's radius using SimplexNoise
if (allVertecesOut) {
radiations.pop();
}
}
}
function redrawRadiations() {
var radiation;
radiationContext.strokeStyle = 'rgba(0, 0, 0, 0.05)';
radiationContext.lineWidth = maxRadiationGrowth;
radiationContext.lineJoin = "round";
//begin: fade existing image
radiationContext.fillStyle = 'rgba(255, 255, 255, 0.05)';
radiationContext.fillRect(0, 0, width, height);
//begin: fade existing image
//begin: insert radiations (black)
for (var r=0; r<radiations.length; r++) {
radiation = radiations[r];
radiationContext.beginPath();
for (var v=0; v<vertexCount; v++) {
radiationContext.lineTo(
radiation[v].x + radiation[v].r*radiation[v].cos,
radiation[v].y + radiation[v].r*radiation[v].sin
);
}
radiationContext.closePath();
radiationContext.stroke();
}
//end: insert radiations (black)
//begin: insert root (red)
radiationContext.fillStyle = 'rgba(255, 0, 0, 0.05)';
radiationContext.beginPath();
radiationContext.arc(root.x, root.y, 2, 0, _2PI);
radiationContext.fill();
//end: insert root (red)
}
</script>
</body>
https://d3js.org/d3.v4.min.js