Skip to content

Commit 0f339c0

Browse files
committed
adds streamlines checkbox
1 parent ad959b9 commit 0f339c0

File tree

6 files changed

+173
-91
lines changed

6 files changed

+173
-91
lines changed

index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@
7171
<input type="checkbox" id="check-earthquakes" />
7272
<label for="check-earthquakes" id="check-earthquakes-label">Earthquake locations</label>
7373
</p>
74+
<p>
75+
<input type="checkbox" id="check-streamlines" />
76+
<label for="check-streamlines" id="check-streamlines-label">Streamlines (static)</label>
77+
</p>
7478
<p><br></p>
7579
<!-- icons -->
7680
<p style="text-align: right; padding-right: 10px;">

js/particles.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111

1212
import * as vectorField from "./vectorField.js"; // vector field
1313

14-
// flag to add particles to view
15-
const ADD_PARTICLES = true;
1614

1715
// total number of particles
1816
let numParticles = 10000;
@@ -30,7 +28,6 @@ const VELOCITY_FACTOR = 0.5; // slow down, relax - it's earth material
3028

3129
// particle drawing
3230
const particleSize = 1.0;
33-
3431
const ADAPT_PARTICLESIZE = false; // adapt line width to zoom factor
3532

3633
// blend color to fade out existing trails
@@ -52,8 +49,6 @@ const DEGREE_TO_RADIAN = Math.PI / 180;
5249

5350
// Initialize particles
5451
function initializeParticles() {
55-
// checks if anything to do
56-
if (! ADD_PARTICLES) return;
5752

5853
// check if vector field is ready
5954
if (! vectorField.isGradientValid()) return;
@@ -335,9 +330,6 @@ function updateParticles(projection,width,height) {
335330
function moveParticles() {
336331
// move particle positions & age
337332

338-
// checks if anything to do
339-
if (! ADD_PARTICLES) return;
340-
341333
// check if particle array is valid
342334
if (particles == null) return;
343335
if (particles.length == 0) return;
@@ -395,8 +387,6 @@ function moveParticles() {
395387

396388
// draw particles
397389
function drawParticles(projection, context, width, height) {
398-
// checks if anything to do
399-
if (! ADD_PARTICLES) return;
400390

401391
// check if particle array is valid
402392
if (particles == null) return;

js/renderer.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ function updateVisiblePoints(projection,width,height){
4444
// update particles
4545
if (state.showParticles) { particles.updateParticles(projection,width,height); }
4646

47+
// streamlines
48+
if (state.showStreamlines) { streamlines.updateStreamlines(); }
49+
4750
// check if anything left to do
4851
if (!state.showBumpMap && !state.showVectorField) return;
4952

js/streamlines.js

Lines changed: 118 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@
1111

1212
import * as vectorField from "./vectorField.js"; // vector field
1313

14-
// flag to add streamlines to view
15-
const ADD_STREAMLINES = true;
1614

15+
// streamlines
1716
const numPaths = 64800; // number of streamlines: 64800 -> 1 x 1 degree samples
1817
const numPathLength = 6; // number of point positions per streamline
1918

@@ -22,24 +21,29 @@ const useRegularLocations = true; // use regular lon/lat start positions instea
2221
// streamlines array
2322
let streamlines = null;
2423

24+
// streamlines velocity factor
25+
const STRETCH_FACTOR = 4.0; // to make streamlines longer
26+
2527
// streamline drawing
26-
const streamlineSize = 2;
28+
const streamlineSize = 1.0;
29+
const ADAPT_STREAMLINESIZE = false; // adapt line width to zoom factor
30+
31+
// quantized color scale for color buckets
32+
let colorScale = null;
33+
const NUM_COLORS = 10; // number of quantized colors
2734

2835
// regular stepping for start locations
29-
let currentLon = -180.0, currentLat = -90.0;
36+
let StartLon = -180.0, StartLat = -90.0;
3037
let deltaLon = 1.0, deltaLat = 1.0;
3138
let nLonSteps = 0, nLatSteps = 0;
39+
let currentLon, currentLat;
3240

3341
// constants
3442
const DEGREE_TO_RADIAN = Math.PI / 180;
3543

36-
// pre-allocate vector
37-
const vector = new Float32Array(2);
3844

3945
// Initialize streamlines
4046
function initializeStreamlines() {
41-
// checks if anything to do
42-
if (! ADD_STREAMLINES) return;
4347

4448
// check if vector field is ready
4549
if (! vectorField.isGradientValid()) return;
@@ -50,12 +54,26 @@ function initializeStreamlines() {
5054
const numPositions = numPaths * numPathLength;
5155

5256
// streamlines array
53-
streamlines = new Float32Array(numPositions * 4); // x, y, vx, vy
57+
if (streamlines == null) {
58+
streamlines = new Float32Array(numPositions * 4); // x, y, vx, vy
59+
}
5460

5561
// loop over paths
5662
for (let n = 0; n < numPaths; n++) {
5763
createPath(n);
5864
}
65+
66+
// Create a quantized color scale with 10 discrete steps
67+
const startColor = "rgba(155, 155, 155, 0.4)";
68+
const endColor = "rgba(255, 255, 155, 0.4)";
69+
70+
// interpolator
71+
const colorInterpolator = d3.interpolateRgb(startColor, endColor);
72+
73+
// scale
74+
colorScale = d3.scaleQuantize()
75+
.domain([0.0, 1.0]) // normalized velocity range
76+
.range(d3.quantize(colorInterpolator, NUM_COLORS));
5977
}
6078

6179

@@ -86,14 +104,17 @@ function createPath(n) {
86104
nLonSteps += 1;
87105
}
88106

89-
deltaLon = 360.0 / (nLonSteps + 1);
90-
deltaLat = 180.0 / (nLatSteps + 1);
107+
if (nLonSteps <= 0) nLonSteps = 1;
108+
if (nLatSteps <= 0) nLatSteps = 1;
109+
110+
deltaLon = 360.0 / nLonSteps;
111+
deltaLat = 180.0 / nLatSteps;
91112

92113
console.log(`streamlines: regular paths ${numPaths} - nsteps ${nLonSteps} ${nLatSteps} delta lon/lat = ${deltaLon}/${deltaLat}`);
93114

94115
// shift away from pole and meridian
95-
currentLon += deltaLon / 2;
96-
currentLat += deltaLat / 2;
116+
currentLon = StartLon + deltaLon / 2;
117+
currentLat = StartLat + deltaLat / 2;
97118
}
98119

99120
// set to current start position
@@ -104,7 +125,7 @@ function createPath(n) {
104125
currentLon += deltaLon;
105126
if ((n > 0) && (n % nLonSteps == 0)){
106127
// reached end of longitude line, increment latitude and reset longitude
107-
currentLon = -180.0 + deltaLon / 2; // re-start longitudes
128+
currentLon = StartLon + deltaLon / 2; // re-start longitudes
108129
currentLat += deltaLat;
109130
}
110131

@@ -131,11 +152,18 @@ function createPath(n) {
131152
// moves points according to vector field:
132153
// vx - x direction == lon
133154
// vy - y direction == lat
155+
// pre-allocate vector
156+
const vector = new Float32Array(2);
157+
134158
vectorField.getVectorField(lon, lat, vector);
135159

136160
// check if valid
137161
if (!vector[0] || !vector[1]){ return; }
138162

163+
// scale velocities
164+
vector[0] *= STRETCH_FACTOR; // vx
165+
vector[1] *= STRETCH_FACTOR; // vy
166+
139167
// or simple vector field
140168
//const lonRad = lon * DEGREE_TO_RADIAN;
141169
//const latRad = lat * DEGREE_TO_RADIAN;
@@ -165,28 +193,42 @@ function createPath(n) {
165193

166194
// updates velocity direction
167195
vectorField.getVectorField(lon, lat, vector);
196+
168197
// check if valid
169198
if (!vector[0] || !vector[1]){ return; }
199+
200+
// scale velocities
201+
vector[0] *= STRETCH_FACTOR; // vx
202+
vector[1] *= STRETCH_FACTOR; // vy
170203
}
171204
}
172205

206+
// clear streamline arrays
207+
function clearStreamlines() {
208+
209+
console.log(`streamlines: clearStreamlines`);
210+
211+
// check if streamlines array is valid
212+
if (!streamlines) return;
213+
214+
// reset reference array
215+
streamlines = null;
216+
}
217+
173218

174219
// update streamline positions
175220
function updateStreamlines() {
176-
// checks if anything to do
177-
if (! ADD_STREAMLINES) return;
178221

179222
// check if vector field is ready
180223
if (! vectorField.isGradientValid()) return;
181224

182225
// check if streamlines array is valid
183226
if (!streamlines) return;
184227

185-
console.log(`updateStreamlines: streamlines ${streamlines.length/4}`);
186-
187228
// check if regular start locations, then we keep those
188229
if (useRegularLocations) return;
189230

231+
//console.log(`updateStreamlines: streamlines ${streamlines.length/4}`);
190232
console.time('updateStreamlines');
191233

192234
// loop over paths
@@ -195,13 +237,10 @@ function updateStreamlines() {
195237
}
196238

197239
console.timeEnd('updateStreamlines');
198-
199240
}
200241

201242
// draw streamlines
202243
function drawStreamlines(projection, context) {
203-
// checks if anything to do
204-
if (! ADD_STREAMLINES) return;
205244

206245
//console.log(`drawStreamlines: streamlines ${streamlines.length/4}`);
207246

@@ -238,17 +277,29 @@ function drawStreamlines(projection, context) {
238277
const vCenter = [ Math.cos(latRad) * Math.cos(lonRad), Math.cos(latRad) * Math.sin(lonRad), Math.sin(latRad) ];
239278

240279
// determine line width based on zoom
241-
let k = 1.0, scaleZoom = 1.0;
242-
const transform = d3.zoomTransform(d3.select("#navigation").node());
243-
if (transform != null){
244-
k = transform.k; // 313 to 6266...
245-
const windowSize = Math.min(context.canvas.width,context.canvas.height);
246-
scaleZoom = k / windowSize; // stronger zoom -> higher k -> thicker lines
280+
let lineWidth = streamlineSize;
281+
if (ADAPT_STREAMLINESIZE) {
282+
let k = 1.0, scaleZoom = 1.0;
283+
const transform = d3.zoomTransform(d3.select("#navigation").node());
284+
if (transform != null){
285+
k = transform.k; // 313 to 6266...
286+
const windowSize = Math.min(context.canvas.width,context.canvas.height);
287+
scaleZoom = k / windowSize; // stronger zoom -> higher k -> thicker lines
288+
}
289+
lineWidth = (scaleZoom < 1) ? streamlineSize : Math.floor(streamlineSize * scaleZoom);
290+
//console.log(`drawStreamlines: line width ${lineWidth}`);
247291
}
248-
const lineWidth = (scaleZoom < 1) ? streamlineSize : Math.floor(streamlineSize * scaleZoom);
249292

250293
projection.clipAngle(90);
251294

295+
// position vectors
296+
const v0 = [0, 0, 0];
297+
const v1 = [0, 0, 0];
298+
299+
let p0 = [0,0];
300+
let p1 = [0,0];
301+
302+
//debug
252303
//let isDone = false;
253304

254305
for (let i = 0; i < streamlines.length/4; i++) {
@@ -267,56 +318,70 @@ function drawStreamlines(projection, context) {
267318
const lon0 = streamlines[index];
268319
const lat0 = streamlines[index + 1];
269320

321+
const vx = streamlines[index + 2];
322+
const vy = streamlines[index + 3];
323+
324+
// velocity strength
325+
const norm = Math.sqrt(vx * vx + vy * vy) / STRETCH_FACTOR; // in [0,1]
326+
270327
// updated position
271-
const lon1 = lon0 + streamlines[index + 2];
272-
const lat1 = lat0 + streamlines[index + 3];
328+
const lon1 = lon0 + vx;
329+
const lat1 = lat0 + vy;
273330

274331
// current point location vector
275-
lonRad = lon0 * DEGREE_TO_RADIAN;
276-
latRad = lat0 * DEGREE_TO_RADIAN;
277-
const v0 = [ Math.cos(latRad) * Math.cos(lonRad), Math.cos(latRad) * Math.sin(lonRad), Math.sin(latRad) ];
278-
279-
lonRad = lon1 * DEGREE_TO_RADIAN;
280-
latRad = lat1 * DEGREE_TO_RADIAN;
281-
const v1 = [ Math.cos(latRad) * Math.cos(lonRad), Math.cos(latRad) * Math.sin(lonRad), Math.sin(latRad) ];
332+
const lonRad0 = lon0 * DEGREE_TO_RADIAN;
333+
const latRad0 = lat0 * DEGREE_TO_RADIAN;
334+
v0[0] = Math.cos(latRad0) * Math.cos(lonRad0);
335+
v0[1] = Math.cos(latRad0) * Math.sin(lonRad0);
336+
v0[2] = Math.sin(latRad0);
337+
338+
const lonRad1 = lon1 * DEGREE_TO_RADIAN;
339+
const latRad1 = lat1 * DEGREE_TO_RADIAN;
340+
v1[0] = Math.cos(latRad1) * Math.cos(lonRad1);
341+
v1[1] = Math.cos(latRad1) * Math.sin(lonRad1);
342+
v1[2] = Math.sin(latRad1);
282343

283344
if(! isPointVisibleHemisphere(vCenter,v0)) { continue; }
284345
if(! isPointVisibleHemisphere(vCenter,v1)) { continue; }
285346

286347
// converted to x/y
287-
const p0 = projection([lon0,lat0]);
288-
const p1 = projection([lon1,lat1]);
348+
p0 = projection([lon0,lat0]);
349+
p1 = projection([lon1,lat1]);
289350

290351
// check if valid
291352
if (!p0 || isNaN(p0[0]) || isNaN(p0[1])) { continue; }
292353
if (!p1 || isNaN(p1[0]) || isNaN(p1[1])) { continue; }
293354

294-
const [x0,y0] = p0;
295-
const [x1,y1] = p1;
296-
297355
// check if point is visible area
298356
// doesn't work, returned [x,y] pixel positions are all within globe area...
357+
//const [x0,y0] = p0;
358+
//const [x1,y1] = p1;
299359
//if (! isPointClose([x0,y0])) { continue; }
300360
//if (! isPointClose([x1,y1])) { continue; }
301361

302362
// Draw streamline
363+
// coloring
364+
//let color;
365+
// same as bumpMap coloring
366+
//color = d3.interpolatePuBu(1 - val);
367+
// red
368+
//color = d3.interpolateYlOrBr(1 - val);
369+
// Determine color based on velocity
370+
let color = colorScale(norm);
371+
303372
// alpha value based on path position index
304373
const n = i % numPathLength;
305374
const val = ((n+1) / numPathLength); // in [0.1,1]
306-
const alpha = 0.2 * val;
307375

308-
//if (index == 0) console.log(`drawStreamlines: ${lon0}/${lat0} to ${lon1}/${lat1} alpha ${alpha}`);
376+
let alpha = 0.1 + 0.2 * val;
377+
if (alpha > 1.0) alpha = 1.0;
309378

310-
// coloring
311-
let color;
312-
// same as bumpMap coloring
313-
//color = d3.interpolatePuBu(1 - val);
314-
// red
315-
color = d3.interpolateYlOrBr(1 - val);
379+
//if (index == 0) console.log(`drawStreamlines: ${lon0}/${lat0} to ${lon1}/${lat1} alpha ${alpha}`);
316380

317381
// add transparency
318382
color = d3.color(color).copy({opacity: alpha});
319383

384+
//debug
320385
//if (! isDone) {
321386
// console.log(`drawStreamlines: transform ${transform} k ${k} ${scaleZoom} ${context.canvas.width} ${context.canvas.height} lineWidth ${lineWidth}`);
322387
// isDone = true;
@@ -326,13 +391,13 @@ function drawStreamlines(projection, context) {
326391
context.beginPath();
327392
context.strokeStyle = color;
328393
context.lineWidth = lineWidth;
329-
context.moveTo(x0, y0);
330-
context.lineTo(x1, y1);
394+
context.moveTo(p0[0], p0[1]);
395+
context.lineTo(p1[0], p1[1]);
331396
context.stroke();
332397
}
333398

334399
console.timeEnd('drawStreamlines');
335400
}
336401

337402

338-
export { initializeStreamlines, updateStreamlines, drawStreamlines };
403+
export { initializeStreamlines, clearStreamlines, updateStreamlines, drawStreamlines };

0 commit comments

Comments
 (0)