This block experiments SimplexNoise.
Work initiated by this block from elenstar, Drawing Vector Field, and the outstanding Neonflames.
Cherry on the cake: this is my first block with canvas. Yeaheahhhhhhh
forked from Kcnarf's block: Simplex Worms + Explanations
xxxxxxxxxx
<meta charset="utf-8">
<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;
}
#particles .desc {
top: -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="worms" class="step">
<div class="desc">
<div class="title">Worms / particles' pathes</div>
<ul>
<li>at each frame, persists each particle</li>
<li>the <span class="highlighted">red worm</span> traces the <span class="highlighted">red particle</span>, which behaves using the <span class="highlighted">red direction</span>, which is based on the <span class="highlighted">red noise</span>; it restarts at center</li>
</ul>
</div>
<canvas></canvas>
<div id="traces" class="require">↓ traces</div>
</div>
<div id="particles" class="step">
<div class="desc">
<div class="title">Particles' moves</div>
<ul>
<li>at each frame, updates each particle's position with the corresponding direction</li>
<li>the direction of a particle <em>p</em> is computed with <em class="highlighted">noise3D(p.x, p.y, t)</em>; the <em>t</em> param. states for time, allowing the noise to evolve among time</li>
<li>the underlying <em>noise3D</em> is tweaked so that:<ul>
<li>at a particular frame, <span class="coord-based-desc">close particles behave +/- the same way</span></li>
<li>particles passing a particular place <span class="time-based-desc">in close frames behave +/- the same way</span></li>
</ul></li>
</ul>
</div>
<canvas></canvas>
<div id="uses" class="require">↓ uses</div>
</div>
<div id="directions" class="step">
<div class="desc">
<div class="title">Particles' directions</div>
<ul>
<li>only drawn for the sake of understanding; shows the mapping <em>noise3D(x,y,t)→direction</em></li>
<li>the underlying <em>noise3D</em> is tweaked so that:<ul>
<li>at a particular frame, <span class="coord-based-desc">close places produce +/- the same direction</span></li>
<li>at a particular place, <span class="time-based-desc">close frames produce +/- the same direction</span></li>
</ul></li>
</ul>
</div>
<canvas></canvas>
<div id="transforms" class="require">↓ transforms</div>
</div>
<div id="noises" class="step">
<div class="desc">
<div class="title">SimplexNoise</div>
<ul>
<li>only drawn for the sake of understanding; shows the mapping <em>noise3D(x,y,t)→noise</em></li>
<li><span class="highlighted">now, let's play</span> <em>(and probably have some headaches ;-)</em><ul>
<li>at a particular frame, <select id="coord-based-config" onchange="updateConfigAndDesc()">
<option value=100000>all places produce the same noise</option>
<option value=100 selected="selected">close places produce +/- the same noise</option>
<option value=10>close places produce different noises</option>
</select></li>
<li>at a particular place, <select id="time-based-config" onchange="updateConfigAndDesc()">
<option value=20000000>all frames produce the same noise</option>
<option value=20000 selected="selected">close frames produce +/- the same noise</option>
<option value=2000>close frames produce different noises</option>
</select></li>
</ul></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;
var simplex = new SimplexNoise(),
timeBasedLength = 20000, //lower values make the noise evolving faster
coordBasedLength = 100; //lower values make close particles behave more differently
var particleCount = 15,
particleRadius = 1,
particles = new Array(particleCount),
particleSpeed = 0.5;
var interval = 10, //a direction/noise every interval
midInterval = interval/2,
fieldValues = [];
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;
initLayout();
initParticles();
var wormsContext = document.querySelector("#worms canvas").getContext("2d"),
particlesContext = document.querySelector("#particles canvas").getContext("2d"),
directionsContext = document.querySelector("#directions canvas").getContext("2d"),
noisesContext = document.querySelector("#noises canvas").getContext("2d");
d3.interval(function(elapsed) {
updateSimplexField(elapsed);
updateParticles(elapsed);
redrawWorms();
redrawParticles();
redrawDirections();
redrawNoises();
});
function initLayout() {
d3.selectAll("canvas")
.attr("width", width)
.attr("height", height);
updateConfigAndDesc();
}
function initParticles() {
for (var i = 0; i < particleCount; ++i) {
particles[i] = {
x: width*Math.random(),
y: height*Math.random(),
noise: 0,
direction: 0
};
}
}
function updateSimplexField (elapsed) {
var timeBasedChange = elapsed/timeBasedLength;
//begin: compute a value every 10-x and 10-y
fieldValues = [];
for (var x = interval; x < width-midInterval; x+=interval) {
for (var y = interval; y < height-interval; y+=interval) {
var noise = simplex.noise3D(x/coordBasedLength, y/coordBasedLength, timeBasedChange);
var noiseBasedAngle = _2PI*noise;
fieldValues.push({
x: x,
y: y,
noise: noise,
direction: noiseBasedAngle
})
}
}
//end: compute a value every 10-x and 10-y
}
function updateParticles(elapsed) {
var timeBasedChange = elapsed/timeBasedLength;
//begin: update each particle's coord. using SimplexNoise
for (var i = 0; i < particleCount; ++i) {
var p = particles[i];
var noise = simplex.noise3D(p.x/coordBasedLength, p.y/coordBasedLength, timeBasedChange);
var noiseBasedAngle = _2PI*noise;
var change = {vx: particleSpeed*Math.cos(noiseBasedAngle),
vy: particleSpeed*Math.sin(noiseBasedAngle)}
p.x += change.vx;
p.y += change.vy;
p.noise = noise;
p.direction = noiseBasedAngle;
if (p.x < -particleRadius || p.x > width+particleRadius ||
p.y < -particleRadius || p.y > height+particleRadius) {
p.x = (i===0)? width/2 : width*Math.random();
p.y = (i===0)? height/2 : height*Math.random();
p.noise = 0;
p.direction = 0;
}
}
//end: update each particle's coord. using SimplexNoise
}
function redrawWorms() {
//begin: fade existing image
wormsContext.globalCompositeOperation = 'destination-out';
wormsContext.fillStyle = 'rgba(255, 255, 255, .05)';
wormsContext.fillRect(0, 0, width, height);
wormsContext.globalCompositeOperation = 'source-over';
//begin: fade existing image
//begin: insert first particle (red, large)
wormsContext.fillStyle = 'rgba(255, 0, 0, 0.5)';
wormsContext.beginPath();
wormsContext.arc(particles[0].x, particles[0].y, 2*particleRadius, 0, _2PI);
wormsContext.fill();
//end: insert first particle (red, large)
//begin: insert other particles (black, small)
wormsContext.fillStyle = 'rgba(0, 0, 0, .5)'
for (var i = 1; i < particleCount; ++i) {
wormsContext.beginPath();
wormsContext.arc(particles[i].x, particles[i].y, particleRadius, 0, _2PI);
wormsContext.fill();
}
//end: insert other particles (black, small)
}
function redrawParticles() {
//begin: delete existing image
particlesContext.clearRect(0, 0, width, height)
//begin: delete existing image
//begin: insert first particle (red, large)
particlesContext.fillStyle = 'red';
particlesContext.beginPath();
particlesContext.arc(particles[0].x, particles[0].y, 2*particleRadius, 0, _2PI);
particlesContext.fill();
//end: insert first particle (red, large)
//begin: insert other particles (black, small)
particlesContext.fillStyle = 'black'
for (var i = 1; i < particleCount; ++i) {
particlesContext.beginPath();
particlesContext.arc(particles[i].x, particles[i].y, particleRadius, 0, _2PI);
particlesContext.fill();
}
//end: insert other particles(black, small)
}
function redrawDirections() {
function drawDirectionArrow(context) {
context.moveTo(0,0);
context.lineTo(midInterval, 0);
context.lineTo(midInterval-2, -1);
context.lineTo(midInterval-2, +1);
context.lineTo(midInterval, 0);
}
//begin: delete existing image
directionsContext.setTransform(1, 0, 0, 1, 0, 0);
directionsContext.clearRect(0, 0, width, height);
//begin: delete existing image
//begin: draw field
directionsContext.strokeStyle = 'black';
for (var i = 0; i < fieldValues.length; ++i) {
directionsContext.beginPath();
directionsContext.setTransform(1, 0, 0, 1, fieldValues[i].x, fieldValues[i].y);
directionsContext.rotate(fieldValues[i].direction);
drawDirectionArrow(directionsContext);
directionsContext.stroke();
}
//end: draw field
//begin: insert 1st particle's direction (red)
directionsContext.strokeStyle = 'red';
directionsContext.beginPath();
directionsContext.translate(10, 10);
directionsContext.setTransform(1, 0, 0, 1, particles[0].x, particles[0].y);
directionsContext.rotate(particles[0].direction);
drawDirectionArrow(directionsContext);
directionsContext.stroke();
//end: insert 1st particle's direction (red)
}
function redrawNoises() {
//begin: delete existing image
noisesContext.clearRect(0, 0, width, height);
//begin: delete existing image
//begin: draw field
noisesContext.fillStyle = 'black';
for (var i = 0; i < fieldValues.length; ++i) {
noisesContext.beginPath();
noisesContext.arc(fieldValues[i].x, fieldValues[i].y, midInterval/2*(1+fieldValues[i].noise), 0, _2PI);
noisesContext.fill();
}
//end: draw field
//begin: insert 1st particle's noise (red)
noisesContext.fillStyle = 'red';
noisesContext.beginPath();
noisesContext.arc(particles[0].x, particles[0].y, midInterval/2*(1+particles[0].noise), 0, _2PI);
noisesContext.fill();
//end: insert 1st particle's noise (red)
}
function updateConfigAndDesc() {
timeBasedLength = document.getElementById('time-based-config').value;
coordBasedLength = document.getElementById('coord-based-config').value;
coordBasedDesc = {
100000: [
"all particles behave the same way",
"all places produce the same direction"
],
100: [
"close particles behave +/- the same way",
"close places produce +/- the same direction"
],
10: [
"close particles behave differently",
"close places produce different direction"
]
}
timeBasedDesc = {
20000000: [
"will always behave the same way",
"close frames produce the same direction"
],
20000: [
"in close frames behave +/- the same way",
"close frames produce +/- the same direction"
],
2000: [
"in close frames behave differently",
"close frames produce different direction"
]
}
d3.selectAll(".time-based-desc").data(timeBasedDesc[timeBasedLength])
.text(function(d) { return d; });
d3.selectAll(".coord-based-desc").data(coordBasedDesc[coordBasedLength])
.text(function(d) { return d; });
}
</script>
</body>
Modified http://d3js.org/d3.v4.min.js to a secure url
https://d3js.org/d3.v4.min.js