├── .gitignore ├── README.md ├── example ├── simple.js └── sph-gallery.js ├── multi.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules/* 16 | *.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # multi-regl 2 | Lets you use a single [regl](https://regl-project.github.io) context to render to multiple windows within a single page. 3 | 4 | [Demo](https://regl-project.github.io/multi-regl/index.html). 5 | 6 | ## Example 7 | 8 | ```javascript 9 | const multiREGL = require('multi-regl') 10 | 11 | const div1 = document.createElement('div') 12 | div1.style.width = '500px' 13 | div1.style.height = '500px' 14 | document.body.appendChild(div1) 15 | 16 | const regl1 = multiREGL(div1) 17 | 18 | regl1.frame(() => { 19 | regl1.clear({ 20 | color: [1, 0, 0, 1], 21 | depth: 1 22 | }) 23 | }) 24 | 25 | const div2 = document.createElement('div') 26 | div2.style.width = '500px' 27 | div2.style.height = '500px' 28 | document.body.appendChild(div2) 29 | 30 | const regl2 = multiREGL(div2) 31 | 32 | regl2.frame(() => { 33 | regl2.clear({ 34 | color: [0, 0, 1, 1], 35 | depth: 1 36 | }) 37 | }) 38 | ``` 39 | 40 | ## API 41 | 42 | ### Constructor 43 | 44 | #### `var multiREGL = require('multi-regl')([options])` 45 | Creates a multiplexed regl context across several div elements. `options` takes the same inputs as `regl`'s constructor. It returns a procedure 46 | 47 | ### Properties 48 | 49 | #### `multiREGL.regl` 50 | A reference to the underlying `regl` object. 51 | 52 | ### Methods 53 | 54 | #### `var regl = multiREGL(containerElement)` 55 | Calling `multiREGL` with a DOM element returns a wrapped `regl` instance where `regl.frame` is overloaded to draw within the element. 56 | 57 | Calling `.destroy()` on this context removes the multiregl instance. 58 | 59 | ## How this works 60 | `multi-regl` creates a full screen canvas over the window which is fixed to the screen resolution. Each frame all the visible 61 | 62 | ## License 63 | (c) 2016 Mikola Lysenko. MIT License 64 | -------------------------------------------------------------------------------- /example/simple.js: -------------------------------------------------------------------------------- 1 | const multiREGL = require('../multi')() 2 | 3 | const colors = [ 4 | [1, 0, 0, 1], 5 | [0, 1, 0, 1], 6 | [0, 0, 1, 1], 7 | [1, 1, 0, 1], 8 | [1, 0, 1, 1], 9 | [0, 1, 1, 1], 10 | [0, 0, 0, 1] 11 | ] 12 | 13 | colors.forEach((color, i) => { 14 | const div = document.createElement('div') 15 | div.style.width = '500px' 16 | div.style.height = '500px' 17 | div.style.background = '#ff' + (i % 2 ? '8000' : '0080') 18 | div.style.margin = '100px' 19 | document.body.appendChild(div) 20 | 21 | const regl = multiREGL(div) 22 | 23 | regl.frame(() => { 24 | regl.clear({ 25 | color 26 | }) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /example/sph-gallery.js: -------------------------------------------------------------------------------- 1 | const multiREGL = require('../multi')() 2 | const sphere = require('sphere-mesh')(30, 1) 3 | const perspective = require('gl-mat4/perspective') 4 | const lookAt = require('gl-mat4/lookAt') 5 | const regl = multiREGL.regl 6 | 7 | const view = new Float32Array(16) 8 | const projection = new Float32Array(16) 9 | 10 | const drawSPH = regl({ 11 | vert: ` 12 | precision highp float; 13 | attribute vec3 position; 14 | uniform mat4 view, projection; 15 | uniform vec3 color, degree, direction; 16 | varying vec3 fragColor; 17 | 18 | float sph () { 19 | vec3 s = position * degree; 20 | float theta = length(s); 21 | return 1.0 + cos(theta) * dot(s, normalize(direction)); 22 | } 23 | 24 | void main () { 25 | fragColor = color + position; 26 | gl_Position = projection * view * vec4(sph() * position, 1); 27 | } 28 | `, 29 | 30 | frag: ` 31 | precision highp float; 32 | varying vec3 fragColor; 33 | void main () { 34 | float lo = min(fragColor.x, min(fragColor.y, fragColor.z)); 35 | float hi = max(fragColor.x, max(fragColor.y, fragColor.z)); 36 | gl_FragColor = vec4((fragColor - lo) / (hi - lo), 1); 37 | } 38 | `, 39 | 40 | uniforms: { 41 | projection: ({viewportWidth, viewportHeight}) => 42 | perspective(projection, 43 | Math.PI / 4.0, 44 | viewportWidth / viewportHeight, 45 | 0.1, 46 | 1000.0), 47 | view: ({tick}) => { 48 | const t = 0.01 * tick 49 | return lookAt(view, 50 | [20.0 * Math.cos(t), 0, 20.0 * Math.sin(t)], 51 | [0, 0, 0], 52 | [0, 1, 0]) 53 | }, 54 | direction: regl.prop('direction'), 55 | degree: regl.prop('degree'), 56 | color: regl.prop('color') 57 | }, 58 | 59 | attributes: { 60 | position: sphere.positions 61 | }, 62 | 63 | elements: sphere.cells 64 | }) 65 | 66 | const colors = [ 67 | [1, 0, 0], 68 | [0, 1, 0], 69 | [0, 0, 1], 70 | [1, 1, 0], 71 | [1, 0, 1], 72 | [0, 1, 1], 73 | [0, 0, 0] 74 | ] 75 | 76 | const degrees = [ 77 | [1, 1, 1], 78 | [3, 4, 5], 79 | [8, 9, 1], 80 | [2, 3, 0], 81 | [8, 8, 1], 82 | [10, 20, 30], 83 | [2, -1, 3] 84 | ] 85 | 86 | const directions = [ 87 | [1, 0, 0], 88 | [1, 3, 2], 89 | [2, 2, 9], 90 | [0, 8, 1], 91 | [1, -1, 3], 92 | [9, -2, 1], 93 | [0, 0, 1] 94 | ] 95 | 96 | colors.forEach((color, i) => { 97 | const div = document.createElement('div') 98 | div.style.width = '500px' 99 | div.style.height = '500px' 100 | div.style.margin = '100px' 101 | document.body.appendChild(div) 102 | 103 | const regl = multiREGL(div) 104 | 105 | regl.frame(() => { 106 | regl.clear({ 107 | color: [0, 0, 0, 1], 108 | depth: 1 109 | }) 110 | 111 | drawSPH({ 112 | color, 113 | degree: degrees[i], 114 | direction: directions[i] 115 | }) 116 | }) 117 | }) 118 | -------------------------------------------------------------------------------- /multi.js: -------------------------------------------------------------------------------- 1 | const createREGL = require('regl') 2 | 3 | module.exports = function createMultiplexor (inputs) { 4 | var reglInput = {} 5 | if (inputs) { 6 | Object.keys(inputs).forEach(function (input) { 7 | reglInput[input] = inputs[input] 8 | }) 9 | } 10 | 11 | var pixelRatio = reglInput.pixelRatio || window.devicePixelRatio 12 | reglInput.pixelRatio = pixelRatio 13 | 14 | var canvas = document.createElement('canvas') 15 | var canvasStyle = canvas.style 16 | canvasStyle.position = 'fixed' 17 | canvasStyle.left = 18 | canvasStyle.top = '0px' 19 | canvasStyle.width = 20 | canvasStyle.height = '100%' 21 | canvasStyle['pointer-events'] = 'none' 22 | canvasStyle['touch-action'] = 'none' 23 | canvasStyle['z-index'] = '1000' 24 | 25 | function resize () { 26 | canvas.width = pixelRatio * window.innerWidth 27 | canvas.height = pixelRatio * window.innerHeight 28 | } 29 | 30 | resize() 31 | 32 | window.addEventListener('resize', resize, false) 33 | 34 | document.body.appendChild(canvas) 35 | 36 | reglInput.canvas = canvas 37 | delete reglInput.gl 38 | delete reglInput.container 39 | 40 | var regl = createREGL(reglInput) 41 | var subcontexts = [] 42 | 43 | var viewBox = { 44 | x: 0, 45 | y: 0, 46 | width: 0, 47 | height: 0 48 | } 49 | 50 | function createSubContext (input) { 51 | var element 52 | if (typeof input === 'object' && input) { 53 | if (typeof input.getBoundingClientRect === 'function') { 54 | element = input 55 | } else if (input.element) { 56 | element = input.element 57 | } 58 | } else if (typeof input === 'string') { 59 | element = document.querySelector(element) 60 | } 61 | if (!element) { 62 | element = document.body 63 | } 64 | 65 | var subcontext = { 66 | tick: 0, 67 | element: element, 68 | callbacks: [] 69 | } 70 | 71 | subcontexts.push(subcontext) 72 | 73 | function wrapBox (boxDesc) { 74 | return boxDesc 75 | } 76 | 77 | function subREGL (options) { 78 | if ('viewport' in options) { 79 | options.viewport = wrapBox(options.viewport) 80 | } 81 | if ('scissor' in options) { 82 | if ('box' in options) { 83 | options.scissor.box = wrapBox(options.scissor.box) 84 | } 85 | if ('enable' in options) { 86 | options.scissor.box = true 87 | } 88 | } 89 | return regl.apply(regl, Array.prototype.slice.call(arguments)) 90 | } 91 | 92 | Object.keys(regl).forEach(function (option) { 93 | subREGL[option] = regl[option] 94 | }) 95 | 96 | subREGL.frame = function subFrame (cb) { 97 | subcontext.callbacks.push(cb) 98 | return { 99 | cancel: function () { 100 | subcontext.callbacks.splice(subcontext.callbacks.indexOf(cb), 1) 101 | } 102 | } 103 | } 104 | 105 | subREGL.destroy = function () { 106 | subcontexts.splice(subcontexts.indexOf(subcontext), 1) 107 | } 108 | 109 | subREGL.container = element 110 | 111 | return subREGL 112 | } 113 | 114 | createSubContext.destroy = function () { 115 | regl.destroy() 116 | } 117 | 118 | createSubContext.regl = regl 119 | 120 | var setViewport = regl({ 121 | context: { 122 | tick: regl.prop('subcontext.tick') 123 | }, 124 | 125 | viewport: regl.prop('viewbox'), 126 | 127 | scissor: { 128 | enable: true, 129 | box: regl.prop('scissorbox') 130 | } 131 | }) 132 | 133 | function executeCallbacks (context, props) { 134 | var callbacks = props.subcontext.callbacks 135 | for (var i = 0; i < callbacks.length; ++i) { 136 | (callbacks[i])(context) 137 | } 138 | } 139 | 140 | regl.frame(function (context) { 141 | regl.clear({ 142 | color: [0, 0, 0, 0] 143 | }) 144 | 145 | var width = window.innerWidth 146 | var height = window.innerHeight 147 | 148 | var pixelRatio = context.pixelRatio 149 | for (var i = 0; i < subcontexts.length; ++i) { 150 | var sc = subcontexts[i] 151 | var rect = sc.element.getBoundingClientRect() 152 | 153 | if (rect.right < 0 || rect.bottom < 0 || 154 | width < rect.left || height < rect.top) { 155 | continue 156 | } 157 | 158 | viewBox.x = pixelRatio * (rect.left) 159 | viewBox.y = pixelRatio * (height - rect.bottom) 160 | viewBox.width = pixelRatio * (rect.right - rect.left) 161 | viewBox.height = pixelRatio * (rect.bottom - rect.top) 162 | 163 | setViewport({ 164 | subcontext: sc, 165 | viewbox: viewBox, 166 | scissorbox: viewBox 167 | }, executeCallbacks) 168 | 169 | sc.tick += 1 170 | } 171 | }) 172 | 173 | createSubContext.canvas = canvas 174 | return createSubContext 175 | } 176 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "multi-regl", 3 | "version": "1.1.1", 4 | "description": "Multiplex a regl context across many HTML elements", 5 | "main": "multi.js", 6 | "directories": { 7 | "example": "example" 8 | }, 9 | "dependencies": { 10 | "regl": "^1.1.1" 11 | }, 12 | "devDependencies": { 13 | "gl-mat4": "^1.1.4", 14 | "sphere-mesh": "^0.2.2" 15 | }, 16 | "scripts": { 17 | "test": "echo \"Error: no test specified\" && exit 1" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/regl-project/multi-regl.git" 22 | }, 23 | "keywords": [ 24 | "multi", 25 | "regl", 26 | "element", 27 | "graphics" 28 | ], 29 | "author": "Mikola Lysenko", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/regl-project/multi-regl/issues" 33 | }, 34 | "homepage": "https://github.com/regl-project/multi-regl#readme" 35 | } 36 | --------------------------------------------------------------------------------