• Stars
    star
    1,108
  • Rank 41,912 (Top 0.9 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created about 7 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A vector field explorer

Field Play

Vector fields explorer. Made with WebGL, love and passion.

field field 2 field 3 field 4

more examples...

What?

Let's assign to every point on a grid a vector (1, 0). This means we have an arrow, pointing to the right:

Vector field V(1, 0)

Let's pretend these vectors represent velocity. What if we drop a thousand particles onto this grid? How would they move?

Moving particles in V(1, 0)

When we assigned a vector to each point on the plain, we created a mathematical structure called Vector Field.

Let's create a bit more interesting vector field:

  • Points with even y coordinate get vector (1, 0);
  • Points with odd y coordinate get an opposite vector (-1, 0);

Even odd directions

Again we drop a few thousands particles and see what happens:

Moving even odd directions

The field above can be written in a single formula:

v.x = -2.0 * mod(floor(y), 2.0) + 1.0;
v.y = 0.0;

The remainder after integer division y/2 can be either 1 or 0. Then we transform the remainder, so that the final vector is either (-1, 0) or (1, 0).

So far, we've used only one component of the velocity vector v.x. And particles moved only horizontally. Let's try to set both components and see what happens

v.x = -2.0 * mod(floor(y), 2.0) + 1.0;
v.y = -2.0 * mod(floor(x), 2.0) + 1.0;

Field x, y Animated field x, y

Wow! Two simple operations, and the final animation looks like an art piece!

Field x, y

Vector fields turns out to be very flexible generative framework.

How this project works?

This project is inspired by Vladimir Agafonkin's article: How I built a wind map with WebGL. Vladimir shows how to render up to a million particles at 60 frames per second, entirely on GPU.

I used almost the same technique with a few modifications:

  1. The vector field is defined in shader language with GLSL code, so that mathematical formulas can be expressed in free form
  2. Position of a particle is computed with 4th order Runge-Kutta method on GPU
  3. Each dimension X and Y is computed independently, so that we can store positions more accurately
  4. Added pan/zoom with panzoom library
  5. The vector field definition is saved in the URL with query-state library. So that you can bookmark/share your vector fields easily

GLSL code for vector field

Every time when vector field code is changed I compile a new shader:

shader compilation

The biggest challenge here was... to provide informative error messages when code is wrong. Web browser gives basic info, which can be confusing sometimes.

For example, the following code has a tiny error:

vec2 velocity(vec2 p) {
  return p
}

If we compile this shader, the browser will say: ERROR: 0:3: '}' : syntax error. What's wrong? - There is a missing semicolon on line 2, the correct version is:

vec2 velocity(vec2 p) {
  return p;
}

It would be better if I could just tell that the semicolon is missing.

Luckily, there is a great glsl type checker and minifier evanw/glslx. It is written in Skew, and provides great many more feature than I needed.

After several hours of playing with code, I extracted parsing bits specific to type checking, and got validation (fingers crossed) more intuitive:

glsl validation

Page load time

Unfortunately, GLSL parsing came at cost - the library is ~64KB of compressed javascript. Together with vue.js (~26KB) users would have to download almost 90KB of code, that is not immediately needed to render the vector field.

At the same time, the code that loads vector field from query string, compiles it, and shows it on the screen is very small. Less than 28KB.

For a regular Wi-Fi connection this may seem like not a big deal. But when you try to open the website on slow 3G mobile network, the difference between extra 90KB of code becomes painfully obvious.

So, how can we load the website faster?

I am using webpack vuejs template, and solution came almost trivial. I just needed to split library into chunks.

The main website's entry point would be my small WebGL renderer, which initializes the scene and kicks of download of vue.js immediately:

initVectorFieldApp(canvas);

// Tell webpack to split bundle, and download settings UI later.
require.ensure('@/vueApp.js', () => {
  // Settings UI is ready, initialize vue.js application
  require('@/vueApp.js');
});

Similarly, the GLSL parser is lazy-loaded. By default I create a naive parser that assumes GLSL code is fine. Once the real parser is loaded, the naive parser is replaced:

var glslParser = {
  check(code) { return no errors }
};

// Load heavy-weight parser:
require.ensure('glsl-parser', () => {
  // ... and replace the naive parser with the real one.
  glslParser = require('glsl-parser');
});

You might be wondering what happens when the parser is being loaded? Well, if there are no errors we immediately show the vector field on the screen. If there are errors, the browser would not compile the shader, and the website visitors will not see informative error message until the real GLSL parser is loaded.

Was the split worth the effort?

I think so. On slow 3G network the vector field is visible in ~3,000ms. On my regular Wi-Fi network, the first frame with vector field is rendered in less than 500ms:

split results

Screenshot is taken with Chrome's developer tools, disabled cache. Start time is counted from the navigation start

Float packing

The core idea of WebGL based computation is quite simple.

GPU can render images very fast. Each image is a collection of pixels. Each pixels is just a number, that represents color, usually written in 32 bits (RGBA format).

But who said that these 32 bits per pixel have to represent a color? Why can't we compute some number, and store it into 32 bits? This number could be, for example, position of a particle that follows along some velocity vector...

If we do so, the GPU would still treat these numbers as colors:

colorful mess

Luckily, we don't have to make this seemingly random images visible to the users. WebGL allows to render things onto "virtual" screens, called frame buffers.

These virtual screens are just images (textures) in the video memory. With two textures we can trick GPU into solving math problems. On each frame the algorithm works like this:

1. Tell GPU, to read data from a "background" texture;
2. Tell GPU, to write data to a "screen" texture using frame buffer;
3. Swap "background" with "screen";

In theory this should work nice. In practice there is a problem. WebGL doesn't let you write floating point numbers into textures. So we need to convert a float number into RGBA format, with 8 bits per channel.

In his article, Vladimir used the following encoding/decoding schema:

// decode particle position (x, y) from pixel RGBA color
vec2 pos = vec2(
    color.r / 255.0 + color.b,
    color.g / 255.0 + color.a);
... // move the position
// encode the position back into RGBA
gl_FragColor = vec4(
    fract(pos * 255.0),
    floor(pos * 255.0) / 255.0);

Here both X and Y coordinate of the particle are stored into a single 32bit number. I used this approach in the beginning, and it worked well on desktop and on my Android phone.

However, when I opened a website on iPhone, unpleasant surprise was waiting for me. Severe artifacts appeared without any apparent reason.

Compare. The same code runs on desktop (left) and on the iPhone (right)

regular circle iPhone banding effect

What's even worse, when field is static (velocity is 0 everywhere), the particles on iPhone were kept moving:

Desktop - no movement, fine iPhone - moving. Why?

I checked that requested floating point resolution was set to the highest available (highp). Yet the artifacts were to obvious to let them be.

How can we fix this?

I didn't want to go the easiest path of enabling floating point textures. They are not as much widely supported as I'd like. Instead, I did what years of non-GPU programming told me not to do.

I decided to solve thousands of ordinary differential equations not just once per frame. But one time per each dimension. I'd pass an attribute to the shader, telling which dimension needs to be written as an output for this "draw" call:

if (u_out_coordinate == 0) gl_FragColor = encodeFloatRGBA(pos.x);
else if (u_out_coordinate == 1) gl_FragColor = encodeFloatRGBA(pos.y);

In pseudo-code it looks like this:

Frame 1:
  Step 1: Hey WebGL, set u_out_coordinate to 0 and render everything into `texture_x`;
  Step 2: Hey WebGL, set u_out_coordinate to 1 and render everything AGAIN into `texture_y`;

We solve the same problem and throw away everything but x component of the solution. Then repeat it for y.

It seemed crazy to me, as I thought this would kill performance. But even my low-end Android phone had no problems with this approach.

The encodeFloatRGBA() uses all 32 bits to encode float as RGBA vector. I found its implementation somewhere on stackoverflow, and I'm not sure if it's the best possible way of packing (if you know better, please let me know).

The good news is that artifacts were gone:

No artifacts

Sharing

Many times I was amazed by how beautiful some vector fields are. To encourage exploration I created a very naive random vector field generator. You can trigger it by pressing "Randomize" button.

Generator

But please don't think that what generator can do is all there is. It's just a tip of an iceberg, and I hope you play with vector field yourself.

When you find something interesting - don't forget to share! Just copy the link and share it away. The link holds all necessary information to restore vector field state (this is done with help of query-state library).

Video recording

If you'd like to record a video with a vector tool, please refer to instructions in this file.

Auto Mode

You can have it automatically shuffle through a variety of interesting fields by using the autotime URL param, which specifies how long to hold each field. For instance autotime=30s will show a field for 30 seconds and then move to the next one. You can use ms, s, m, or h to specify milliseconds, seconds, minutes, or hours, respectively. You can also specify whether the fields are presets or generated or both, using autosource=presets, autosource=generator, or autosource=both. Give it a try!

Local development

Clone this repository, then:

# install dependencies
npm install

# serve with hot reload at http://localhost:8880
npm run dev

License

The project is released under the MIT license.

Thanks!

I learned a lot building this project. I hope you too liked this short voyage into world of vector fields, math and WebGL.

Please let me know what you think.

Have fun!

More Repositories

1

city-roads

Visualization of all roads within any city
JavaScript
5,402
star
2

VivaGraphJS

Graph drawing library for JavaScript
JavaScript
3,646
star
3

ngraph.path

Path finding in a graph
JavaScript
2,801
star
4

atree

Just a simple Christmas tree, based on reddit story
JavaScript
2,424
star
5

panzoom

Universal pan and zoom library (DOM, SVG, Custom)
JavaScript
1,570
star
6

pm

package managers visualization
JavaScript
1,408
star
7

ngraph

Beautiful Graphs
1,360
star
8

npmgraph.an

2d visualization of npm
JavaScript
1,160
star
9

sayit

Visualization of related subreddits
JavaScript
937
star
10

vs

Visualization of Google's autocomplete
JavaScript
919
star
11

word2vec-graph

Exploring word2vec embeddings as a graph of nearest neighbors
Python
691
star
12

time

Simple Google Sheets interface to track time
JavaScript
623
star
13

graph-drawing-libraries

Trying to compare known graph drawing libraries
JavaScript
584
star
14

peak-map

Make a ridge line chart from any region on Earth
JavaScript
583
star
15

map-of-reddit

Interactive map of reddit
JavaScript
540
star
16

common-words

visualization of common words in different programming languages
JavaScript
495
star
17

ngraph.graph

Graph data structure in JavaScript
JavaScript
463
star
18

ngraph.pixel

Fast graph renderer based on low level ShaderMaterial from three.js
JavaScript
295
star
19

npmrank

npm dependencies graph metrics
JavaScript
284
star
20

streamlines

Streamlines calculator
JavaScript
275
star
21

isect

Segments intersection detection library
JavaScript
253
star
22

gauss-distribution

A fun little project to show distribution of pixels in Gauss's portrait
HTML
249
star
23

oflow

Optical flow detection in JavaScript
JavaScript
200
star
24

ghindex

Creates github index for similar repositories discovery
JavaScript
189
star
25

gazer

GitHub analysis and discovery
JavaScript
186
star
26

git-also

For a `file` in your git repository, prints other files that are most often committed together
JavaScript
185
star
27

allnpmviz3d

3d visualization of npm
JavaScript
179
star
28

ngraph.path.demo

This is a demo project for ngraph.path
JavaScript
165
star
29

map-of-github

Inspirational Mapping
Vue
157
star
30

ngraph.forcelayout

Force directed graph layout
JavaScript
146
star
31

pixchart

Turn any image into delightful splash of colors and order
JavaScript
112
star
32

city-script

Collection of scripts that can be loaded into city-roads
JavaScript
112
star
33

winvelviz

Wind visualization over time
JavaScript
100
star
34

yasiv-youtube

Graph of related videos from YouTube
JavaScript
100
star
35

query-state

Application state in query string
JavaScript
97
star
36

e-sum

Visualization of exponential sums
JavaScript
97
star
37

ngraph.forcelayout3d

Force directed graph layout in 3d
JavaScript
95
star
38

index-large-cities

A simple indexer of road networks from OSM. Data for @anvaka/city-roads
JavaScript
92
star
39

graph-start

a simple graph shell to explore ideas
JavaScript
88
star
40

greview

Books that I read and their neighborhoods
86
star
41

lsystem

A simple L-Systems explorer powered by WebGL
JavaScript
86
star
42

jsruntime

Chrome Extension to explore javascript runtime.
JavaScript
85
star
43

map-of-reddit-data

Contains scripts and data to render map of reddit
JavaScript
82
star
44

redsim

reddit discovery
JavaScript
82
star
45

w-gl

A simple WebGL renderer
TypeScript
80
star
46

pplay

Create, play and share pixels. Online WebGL shader editor.
GLSL
76
star
47

ngraph.hde

High dimensional embedding of a graph and its layout
JavaScript
76
star
48

dotparser

Parser of GraphViz dot file format
PEG.js
72
star
49

wind-lines

Streamline animation of wind data
JavaScript
63
star
50

set-vs-object

What is faster Set or Object?
JavaScript
62
star
51

three.map.control

A three.js camera that mimics 2d maps navigation with pan and zoom
JavaScript
53
star
52

ngraph.native

C++ implementation of force-based layout from ngraph
C++
49
star
53

circles

A simple spirograph toy
JavaScript
49
star
54

yaot

Yet another octree
JavaScript
48
star
55

ngraph.three

3D graph renderer powered by three.js
JavaScript
44
star
56

ngraph.generators

Graph generators
JavaScript
42
star
57

citations

Most cited papers by keyword
C++
42
star
58

rafor

requestAnimationFrame friendly async for iterator
JavaScript
41
star
59

ngraph.fabric

Fabric.js graph renderer
JavaScript
38
star
60

playground

Just a set of experiments that I want to play with, but they are too small to be in their own repository
JavaScript
35
star
61

ngraph.centrality

Module to calculate graph centrality metrics
JavaScript
33
star
62

streak

Streak tracking with Google Sheets
JavaScript
33
star
63

sayit-data

data with similar subreddits graph
JavaScript
32
star
64

ngraph.offline.layout

Performs offline layout of large graphs and saves results to the disk
JavaScript
32
star
65

cord-19

exploring research papers about coronaviruses
JavaScript
31
star
66

how-to-debug-node-js-addons

How to debug node.js addons in xcode
30
star
67

wheel

Mouse wheel event unified for all browsers
JavaScript
30
star
68

npmgraphbuilder

Builds graph of npm dependencies from npm registry
JavaScript
29
star
69

generator-n

minimalistic node package yeoman generator
JavaScript
28
star
70

allgithub

Crawling github data
JavaScript
27
star
71

tiny.xml

Tiny (1.6KB) in-browser xml parser
JavaScript
27
star
72

mars

Map of Mars
JavaScript
26
star
73

ngraph.pixi

PIXI.js graph renderer
JavaScript
26
star
74

ngraph.pagerank

PageRank calculation for ngraph.graph
JavaScript
25
star
75

noisylines

Tracking noise with streamlines
JavaScript
23
star
76

nb

Neighborhood beautification: Graph layout through message passing
JavaScript
23
star
77

ngraph.physics.simulator

Physics library for ngraph
JavaScript
23
star
78

strangeb

The strangest thing happens when you rotate Bezier control points
JavaScript
22
star
79

graph-to-vector-field

Converts a graph into vector field texture
JavaScript
20
star
80

amator

Tiny animation library
JavaScript
20
star
81

ngraph.louvain

Given a graph instance detects communities using the Louvain Method
JavaScript
20
star
82

npmgraph

Visualization of NPM dependencies
JavaScript
19
star
83

similar-cities

Visualization of cities with similar road networks
JavaScript
19
star
84

allnpm

Graph generator for entire npm registry
JavaScript
18
star
85

twitter-recommended-graph

Building a proposal for Twitter to show a map of recommended people
JavaScript
18
star
86

streaming-svg-parser

Streaming SVG/XML parser with zero dependencies
JavaScript
18
star
87

extract-osm-roads

A simple utility to fetch a city graph from OSM
JavaScript
18
star
88

quadtree.cc

A C++ implementation of quadtree
C++
17
star
89

rules-of-ml

A simple visualization of Martin Zinkevich article
JavaScript
17
star
90

ngraph.events

Events support in ngraph.*
JavaScript
17
star
91

simplegrad

Simple reverse mode automatic differentiation of scalar values in javascript
JavaScript
16
star
92

portrait

Portrait of quotes
JavaScript
16
star
93

sunburst

For a given tree builds an SVG based SunBurst diagram
JavaScript
15
star
94

local-chat

Local instance of ChatGPT for my kiddo
HTML
15
star
95

what-people-google

Visualization of what people google
JavaScript
15
star
96

vuereddit

A simple reddit client written as a vue component.
Vue
14
star
97

ngraph.fromjson

Library to load graph from simple json format
JavaScript
12
star
98

color-high

A demo of ngraph.forcelayout in 6D space
JavaScript
12
star
99

color-force-vis

Visualizing forces acting on nodes during force layout
JavaScript
11
star
100

allnpmviz.an

Visualization of entire npm
JavaScript
11
star