Skip to content

Commit

Permalink
Initial commit!
Browse files Browse the repository at this point in the history
  • Loading branch information
vaneenige committed Jul 12, 2018
0 parents commit 1066ce4
Show file tree
Hide file tree
Showing 12 changed files with 2,579 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*]
indent_size = 2
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist
node_modules
.DS_Store
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2018 Colin van Eenige

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
152 changes: 152 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@

# Phenomenon

Phenomenon is a very small, low-level WebGL library that provides the essentials to deliver a high performance experience. Its core functionality is built around the idea of moving *millions of particles* around using the power of the GPU.

#### Features:

- Small in size, no dependencies
- GPU based for high performance
- Low-level & highly configurable
- Helper functions with options
- Add & destroy instances dynamically

*Want to see some magic right away? Have a look <a href="https://codepen.io/collection/AOpMrm/">here</a>!*

## Install

```
$ npm install --save phenomenon
```

## Usage

```js
// Import the library
import Phenomenon from 'phenomenon';

// Create a renderer
const phenomenon = new Phenomenon(options);

// Add an instance
phenomenon.add("particles", options);
```

> For a better understanding of how to use the library, read along or have a look at the demo!
## API

### Phenomenon(options)

Returns an instance of Phenomenon.

> Throughout this documentation we'll refer to an instance of this as `renderer`.
#### options.canvas
Type: `HTMLElement` <br/>
Default: `document.querySelector('canvas')` <br/>

The element where the scene, with all of its instances, will be rendered to. The provided element has to be `<canvas>` otherwise it won't work.

#### options.context
Type: `Object`<br/>
Default: `{}`<br/>

Overrides that are used when getting the WebGL context from the canvas. The library overrides two settings by default.

| Name | Default | Description |
| --------- | --------| --------------------------------------------------------------------------------------------------------------------------------------- |
| Alpha | `false` | Setting this property to `true` will result in the canvas having a transparent background. By default clearColor is used instead. |
| Antialias | `false` | Setting this property to `true` will make the edges sharper, but could negatively impact performance. See for yourself if it's worth it! |

> Read more about all the possible overrides on <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext">MDN</a>.
#### options.settings
Type: `Object`<br/>
Default: `{}`<br/>

@TODO
Overrides that can be used to alter the behaviour of the experience.

| Name | Value | Default | Description |
| ---------------------------------------- | ---------- | ----------- | --------------------------------------------------------------------------- |
| devicePixelRatio | `number` | `1` | The resolution multiplier by which the scene is rendered relative to the canvas' resolution. Use `window.devicePixelRatio` for the highest possible quality, `1` for the best performance. |
| clearColor | `array` | `[1,1,1,1]` | The color in `rgba` that is used as the background of the scene. |
| position | `number` | `{x:0,y:0,z:2}` | The distance in 3D space between the center of the scene and the camera. |
| shouldRender | `boolean` | `true` | A boolean indicating whether the scene should start rendering automatically. |
| uniforms | `object` | `{}` | Shared values between all instances that can be updated at any given moment. By default this feature is used to render all the instances with the same `uProjectionMatrix`, `uModelMatrix` and `uViewMatrix`. It's also useful for moving everything around with the same progress value; `uProgress`. |
| willRender(renderer) didRender(renderer) | `function` | `undefined` | A render hook that is invoked every frame before / after the scene is rendered to the screen. Use this to update `renderer.uniforms`. |

### .resize()

Update all values that are based on the dimensions of the canvas to make it look good on all screen sizes.

### .toggle(shouldRender)

Toggle the rendering state of the scene. When shouldRender is false `requestAnimationFrame` is disabled so no resources are used.

#### shouldRender
Type: `Boolean` <br/>
Default: `undefined` <br/>

An optional boolean to set the rendering state to a specific value. Leaving this value empty will result in a regular boolean switch.

### .add(key, settings)

This function is used to add instances to the renderer. These instances can be as *simple* or *complex* as you'd like them to be. There's no limit to how many of these you can add. Make sure they all have a different key!

#### key
Type: `String`<br/>
Default: `undefined`<br/>

Every instance should have a unique name. This name can also be used to destroy the instance specifically later.

#### settings
Type: `Object`<br/>
Default: `{}`<br/>

An object containing overrides for parameters that are used when getting the WebGL context from the canvas.

| Name | Value | Default | Description |
| ----------- | ---------- | -------- | --------------------------------------------------------------------------- |
| attributes | `array` | - | Values used in the program that are stored once, directly on the GPU. |
| uniforms | `object` | `{}` | Values used in the program that can be updated on the fly. |
| vertex | `string` | - | The vertex shader is used to position the geometry in 3D space. |
| fragment | `string` | - | The fragment shader is used to provide the geometry with color or texture. |
| multiplier | `number` | - | The amount of duplicates that will be created for the same instance. |
| geometry | `object` | `{}` | Vertices (and optional normals) of a model. |
| modifiers | `object` | `{}` | Modifiers to alter the attributes data on initialize. |
| willRender | `function` | `null` | A render hook that is invoked every frame before the instance is rendered. |
| didRender | `function` | `null` | A render hook that is invoked every frame after the instance is rendered. |

> Note: Less instances with a higher multiplier will be faster than more instances with a lower multiplier!
### .remove(key)

Remove an instance from the scene (and from memory) by its key.

### .destroy()

Remove all instances and the renderer itself. The canvas element will remain in the DOM.

## Examples

1. <a href="https://codepen.io/cvaneenige/pen/odpVPW">Particles</a>
2. <a href="https://codepen.io/cvaneenige/pen/GdGpaN">Types</a>
3. <a href="https://codepen.io/cvaneenige/pen/Lmrzmd">Transition</a>
4. <a href="https://codepen.io/cvaneenige/pen/zjaydg">Easing</a>
5. <a href="https://codepen.io/cvaneenige/pen/wjXVgr">Shapes</a>
6. <a href="https://codepen.io/cvaneenige/pen/MGBZpB">Instances</a>
7. <a href="https://codepen.io/cvaneenige/pen/LmXWeM">Movement</a>

## Contribute

Are you excited about this library and have interesting ideas on how to improve it? Please tell me or contribute directly! 🙌

```
npm install > npm run demo > http://localhost:8080
```

## License

MIT © <a href="https://use-the-platform.com">Colin van Eenige</a>
15 changes: 15 additions & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<html>

<head>
<title>Demo</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="icon" href="/" />
<link rel="stylesheet" href="src/style.css" />
</head>

<body>
<canvas></canvas>
<script src="./dist/bundle.js"></script>
</body>

</html>
127 changes: 127 additions & 0 deletions demo/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Import module from source
import Phenomenon from './../../src/index.js';

// Import optional utils
import { getRandom, rotateX, rotateY } from './utils';

// The amount of particles that will be created
const multiplier = 400000;

// Percentage of how long every particle will move
const duration = 0.9;

// Update value for every frame
const step = 0.01;

// Multiplier of the canvas resolution
const devicePixelRatio = 1;

// Every attribute must have:
// - Name (used in the shader)
// - Data (returns data for every particle)
// - Size (amount of variables in the data)
const attributes = [
{
name: 'aPositionStart',
data: () => [getRandom(0.5), getRandom(0.5), getRandom(0.5)],
size: 3,
},
{
name: 'aPositionEnd',
data: () => [getRandom(1.5), getRandom(1.5), getRandom(1.5)],
size: 3,
},
{
name: 'aColor',
data: () => Math.random() > 0.5 ? [29 / 255, 233 / 255, 182 / 255, 1] : [4 / 255, 208 / 255, 157 / 255, 1],
size: 3,
},
{
name: 'aOffset',
data: i => [i * ((1 - duration) / (multiplier - 1))],
size: 1,
},
];

// Every uniform must have:
// - Key (used in the shader)
// - Type (what kind of value)
// - Value (based on the type)
const uniforms = {
uProgress: {
type: 'float',
value: 0.0,
},
};

// Vertex shader used to calculate the position
const vertex = `
attribute vec3 aPositionStart;
attribute vec3 aControlPointOne;
attribute vec3 aControlPointTwo;
attribute vec3 aPositionEnd;
attribute vec3 aPosition;
attribute vec3 aColor;
attribute float aOffset;
uniform float uProgress;
uniform mat4 uProjectionMatrix;
uniform mat4 uModelMatrix;
uniform mat4 uViewMatrix;
varying vec3 vColor;
float easeInOutQuint(float t){
return t < 0.5 ? 16.0 * t * t * t * t * t : 1.0 + 16.0 * (--t) * t * t * t * t;
}
void main(){
float tProgress = easeInOutQuint(min(1.0, max(0.0, (uProgress - aOffset)) / ${duration}));
vec3 newPosition = mix(aPositionStart, aPositionEnd, tProgress);
gl_Position = uProjectionMatrix * uModelMatrix * uViewMatrix * vec4(newPosition + aPosition, 1.0);
gl_PointSize = ${devicePixelRatio.toFixed(1)};
vColor = aColor;
}
`;

// Fragment shader to draw the colored pixels to the canvas
const fragment = `
precision mediump float;
varying vec3 vColor;
void main(){
gl_FragColor = vec4(vColor, 1.0);
}
`;

// Boolean value to switch direction
let forward = true;

// Create the renderer
const phenomenon = new Phenomenon({
settings: {
devicePixelRatio,
position: { x: 0, y: 0, z: 3 },
shouldRender: true,
uniforms,
willRender: r => {
const { uProgress, uModelMatrix } = r.uniforms;
uProgress.value += forward ? step : -step;

if (uProgress.value >= 1) forward = false;
else if (uProgress.value <= 0) forward = true;

rotateY(uModelMatrix.value, step * 2);
rotateX(uModelMatrix.value, step * 2);
},
},
});

// Add an instance to the renderer
phenomenon.add('cube', {
attributes,
multiplier,
vertex,
fragment,
});
14 changes: 14 additions & 0 deletions demo/src/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}

canvas {
position: fixed;
width: 100%;
height: 100%;
image-rendering: pixelated;
}
36 changes: 36 additions & 0 deletions demo/src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export function rotateX(m, angle) {
let c = Math.cos(angle);
let s = Math.sin(angle);
let mv1 = m[1];
let mv5 = m[5];
let mv9 = m[9];

m[1] = m[1] * c - m[2] * s;
m[5] = m[5] * c - m[6] * s;
m[9] = m[9] * c - m[10] * s;

m[2] = m[2] * c + mv1 * s;
m[6] = m[6] * c + mv5 * s;
m[10] = m[10] * c + mv9 * s;
}

export function rotateY(m, angle) {
let c = Math.cos(angle);
let s = Math.sin(angle);
let mv0 = m[0];
let mv4 = m[4];
let mv8 = m[8];

m[0] = c * m[0] + s * m[2];
m[4] = c * m[4] + s * m[6];
m[8] = c * m[8] + s * m[10];

m[2] = c * m[2] - s * mv0;
m[6] = c * m[6] - s * mv4;
m[10] = c * m[10] - s * mv8;
}

export function getRandom(value) {
const floor = -value;
return floor + Math.random() * value * 2;
}
Loading

0 comments on commit 1066ce4

Please sign in to comment.