├── .gitignore ├── Gruntfile.js ├── README.md ├── build ├── blotter.js ├── blotter.min.js └── materials │ ├── channelSplitMaterial.js │ ├── fliesMaterial.js │ ├── liquidDistortMaterial.js │ ├── rollingDistortMaterial.js │ └── slidingDoorMaterial.js ├── license.txt ├── package.json ├── src ├── assets │ └── shaders │ │ ├── README.md │ │ ├── blinnphongspecular.js │ │ ├── core │ │ ├── blending.js │ │ ├── inf.js │ │ └── line_math.js │ │ ├── easing.js │ │ ├── gamma.js │ │ ├── map.js │ │ ├── noise.js │ │ ├── noise2D.js │ │ ├── noise3D.js │ │ ├── noise4D.js │ │ ├── pi.js │ │ └── random.js ├── blotter.js ├── builders │ ├── mappingBuilder.js │ ├── mappingMaterialBuilder.js │ └── textures │ │ ├── boundsDataTextureBuilder.js │ │ ├── indicesDataTextureBuilder.js │ │ └── textTextureBuilder.js ├── extras │ ├── canvasUtils.js │ ├── core │ │ ├── math.js │ │ ├── messaging.js │ │ ├── overrides.js │ │ └── vendorPrefixes.js │ ├── objects │ │ └── modelEventBinding.js │ ├── textUtils.js │ └── uniformUtils.js ├── mapping │ ├── mapping.js │ └── mappingMaterial.js ├── materials │ ├── material.js │ └── shaderMaterial.js ├── rendering │ ├── renderScope.js │ └── renderer.js └── texts │ └── text.js ├── third_party ├── bin-packing │ ├── LICENSE │ └── packer.growing.js ├── event_emitter │ ├── EventEmitter.js │ └── UNLICENSE ├── request_animation_frame │ ├── LICENSE │ └── requestAnimationFrame.js ├── set_immediate │ ├── LICENSE │ └── setimmediate.js ├── three │ ├── Detector.js │ ├── LICENSE │ └── three.custom.js └── underscore │ ├── LICENSE │ └── underscore.js └── utils └── three_build.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | pkg: grunt.file.readJSON("package.json"), 5 | meta: { 6 | 7 | licenseFile : "license.txt", 8 | 9 | depFiles : [ 10 | "third_party/underscore/underscore.js", 11 | "third_party/three/three.custom.js", 12 | "third_party/three/Detector.js", 13 | "third_party/set_immediate/setimmediate.js", 14 | "third_party/event_emitter/EventEmitter.js", 15 | "third_party/bin-packing/packer.growing.js", 16 | "third_party/request_animation_frame/requestAnimationFrame.js" 17 | ], 18 | 19 | srcFiles : [ 20 | "src/blotter.js", 21 | "src/extras/*/*.js", 22 | "src/extras/*.js", 23 | "src/texts/*.js", 24 | "src/assets/*/*/*.js", 25 | "src/assets/*/*.js", 26 | "src/mapping/*.js", 27 | "src/materials/*.js", 28 | "src/rendering/*.js", 29 | "src/builders/*/*.js", 30 | "src/builders/*.js", 31 | ] 32 | }, 33 | 34 | watch: { 35 | scripts: { 36 | files: ["src/**/*.js"], 37 | tasks: ["jshint", "concat"] 38 | } 39 | }, 40 | 41 | jshint: { 42 | options: { 43 | jshintrc: true, 44 | reporterOutput: "" 45 | }, 46 | all: ["Gruntfile.js", "src/**/*.js"] 47 | }, 48 | 49 | concat: { 50 | options: { 51 | separator: "\n" 52 | }, 53 | dist: { 54 | src: [ 55 | "<%= meta.licenseFile %>", 56 | "<%= meta.depFiles %>", 57 | "<%= meta.srcFiles %>" 58 | ], 59 | dest: "build/blotter.js" 60 | } 61 | }, 62 | 63 | uglify: { 64 | options: { 65 | preserveComments: "some" 66 | }, 67 | release: { 68 | src: ["build/blotter.js"], 69 | dest: "build/blotter.min.js" 70 | } 71 | } 72 | }); 73 | 74 | // Load tasks 75 | grunt.loadNpmTasks("grunt-contrib-watch"); 76 | grunt.loadNpmTasks("grunt-contrib-uglify"); 77 | grunt.loadNpmTasks("grunt-contrib-jshint"); 78 | grunt.loadNpmTasks("grunt-contrib-concat"); 79 | grunt.loadNpmTasks("grunt-closure-tools"); 80 | 81 | // Default task 82 | grunt.registerTask("default", ["jshint" , "concat", "uglify"]); 83 | }; 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Blotter logo 2 | 3 | A JavaScript API for drawing unconventional text effects on the web. 4 | 5 | [Home](https://blotter.js.org) — 6 | [Help](https://github.com/bradley/blotter/issues?labels=question) 7 | 8 | ## Overview 9 | 10 | When applying effects to text on the web, designers have traditionally been constrained to those provided by CSS. In the majority of cases this is entirely suitable – text is text right? Yet still, there exist numerous examples of designers combining CSS properties or gifs and images to create effects that evoke something more playful. Precisely here, Blotter exists to provide an alternative. 11 | 12 | #### GLSL Backed Text Effects with Ease 13 | 14 | *Blotter provides a simple interface for building and manipulating text effects that utilize GLSL shaders without requiring that the designer write GLSL. Blotter has a growing library of configurable effects while also providing ways for student or experienced GLSL programmers to quickly bootstrap new ones.* 15 | 16 | #### Atlasing Effects in a Single WebGL Back Buffer 17 | 18 | *Blotter renders all texts in a single WebGL context and limits the number of draw calls it makes by using atlases. When multiple texts share the same effect they are mapped into a single texture and rendered together. The resulting image data is then output to individual 2d contexts for each element.* 19 | 20 | #### Animation Loop 21 | 22 | *Rather than executing on a time based interval, Blotter's internal animation loop uses requestAnimationFrame to match the browser's display refresh rate and pause when the user navigates to other browser tabs; improving performance and preserving the battery life on the user's device.* 23 | 24 | #### What Blotter Isn't 25 | 26 | *Any texts you pass to Blotter can be individually configured using familiar style properties. You can use custom font faces through the `@font-face` spec. However, Blotter ultimately renders the texts passed to it into canvas elements. This means rendered text won't be selectable. Blotter is great for elements like titles, headings, and texts used for graphic purposes. It's not recommended that Blotter be used for lengthy bodies of text, and should in most cases be applied to words individually.* 27 | 28 | 29 | ## Usage 30 | 31 | Download the [minified version](https://raw.githubusercontent.com/bradley/Blotter/master/build/blotter.min.js). 32 | 33 | To apply text effects, you'll also want to include at least one [material](https://github.com/bradley/Blotter/tree/master/build/materials/), so download one of Blotter's ready-made effects, such as the [ChannelSplitMaterial](https://raw.githubusercontent.com/bradley/Blotter/master/build/materials/channelSplitMaterial.js). 34 | 35 | Include both in your HTML. 36 | 37 | ```html 38 | 39 | 40 | ``` 41 | 42 | The following illustrates how to render Blotter's [ChannelSplitMaterial](https://blotter.js.org/#/materials/ChannelSplitMaterial) in the `body` of your page with default settings. 43 | 44 | ```html 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 67 | 68 | 69 | ``` 70 | 71 | 72 | ## Making Changes / Custom Builds 73 | 74 | Firstly, install Blotter's build dependencies (OSX): 75 | 76 | ``` 77 | $ cd ~/path/to/blotter 78 | $ npm install 79 | ``` 80 | 81 | The `blotter.js` and `blotter.min.js` files are built from source files in the `/src` directory. Do not edit these built files directly. Instead, edit the source files within the `/src` directory and then run the following to build the generated files: 82 | 83 | ``` 84 | $ npm run build 85 | ``` 86 | 87 | You will the updated build files at `/build/blotter.js` and `/build/blotter.min.js`. 88 | 89 | #### Without Three.js / Without Underscore.js 90 | 91 | Blotter.js requires Three.js and Underscore.js. If you're already including these files in your project, you should remove them from the `defFiles` array in the [Gruntfile](https://github.com/bradley/Blotter/blob/master/Gruntfile.js) and re-run the build script. 92 | 93 | *Note: In order to decrease the total build size, Blotter uses a custom build of Three.js that only includes modules Blotter.js relies on. For more information view the `build-custom-three` script in [package.json](https://github.com/bradley/Blotter/blob/master/package.json).* 94 | 95 | 96 | ## Custom Materials 97 | 98 | The documentation for creating custom materials can be found in [the Wiki](https://github.com/bradley/Blotter/wiki/Custom-Materials). 99 | 100 | 101 | ## Credits 102 | 103 | Blotter is not possible without these contributions to JavaScript. 104 |
105 | 106 | * [Underscore.js](http://underscorejs.org/)
107 | Utility functions for JavaScript. 108 | * [Three.js](https://threejs.org/)
109 | WebGL Render Pipeline. 110 | * [requestAnimationFrame](https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/)
111 | Polyfill by Paul Irish. 112 | * [bin-packing](https://github.com/jakesgordon/bin-packing)
113 | How Blotter positions texts for batched rendering. 114 | * [EventEmitter](https://github.com/Olical/EventEmitter)
115 | Simple API for JavaScript events. 116 | * [dat.GUI](https://github.com/dataarts/dat.gui)
117 | A lightweight GUI for changing variables in JavaScript. Used in Blotter's [Material](https://blotter.js.org/#/materials) documentation pages. 118 | 119 |
120 | Some projects and people who have helped inspire along the way. 121 |
122 |
123 | 124 | * [Two.js](https://two.js.org/)
125 | Jono Brandel's Two.js has provided much inspiration for Blotter's documentation and API design. 126 | * [Reza Ali](http://www.syedrezaali.com/)
127 | Reza's [Fragment](http://www.syedrezaali.com/store/fragment-osx-app) was a fundamental part of the development process for writing Blotter's Fragment shaders, and Reza kindly allowed Blotter to include an array of shader [helper functions](https://github.com/bradley/Blotter/tree/master/src/assets/shaders) from Fragment. 128 | * [Mitch Paone](https://twitter.com/DIA_Mitch)
129 | I was introduced to Mitch's work in computational typography while working on Blotter, and the work Mitch has done with [DIA](http://dia.tv/) has been hugely motivational. 130 | * [Stan Haanappel](https://www.instagram.com/stanhaanappel/)
131 | Stan Haanappel is a designer whose work with type has been inspirational to Blotter. 132 | * [The Book of Shaders](https://thebookofshaders.com/)
133 | The Book of Shaders by [Patricio Gonzalez Vivo](http://patriciogonzalezvivo.com/) and [Jen Lowe](http://jenlowe.net/) is where anyone looking to learn more about writing shaders should begin. 134 | * [Shadertoy](https://www.shadertoy.com/)
135 | Shadertoy has been a critical part of my personal learning experience while working on Blotter. 136 | 137 |
138 |
139 | 140 | ✌️ - [Bradley Griffith](http://bradley.computer) 141 | -------------------------------------------------------------------------------- /build/materials/channelSplitMaterial.js: -------------------------------------------------------------------------------- 1 | (function(Blotter) { 2 | 3 | Blotter.ChannelSplitMaterial = function() { 4 | Blotter.Material.apply(this, arguments); 5 | }; 6 | 7 | Blotter.ChannelSplitMaterial.prototype = Object.create(Blotter.Material.prototype); 8 | 9 | Blotter._extendWithGettersSetters(Blotter.ChannelSplitMaterial.prototype, (function () { 10 | 11 | function _mainImageSrc () { 12 | var mainImageSrc = [ 13 | Blotter.Assets.Shaders.PI, 14 | Blotter.Assets.Shaders.LineMath, 15 | Blotter.Assets.Shaders.Random, 16 | 17 | 18 | "const int MAX_STEPS = 200;", 19 | 20 | 21 | "// Fix a floating point number to two decimal places", 22 | "float toFixedTwo(float f) {", 23 | " return float(int(f * 100.0)) / 100.0;", 24 | "}", 25 | 26 | 27 | "vec2 motionBlurOffsets(vec2 fragCoord, float deg, float spread) {", 28 | 29 | " // Setup", 30 | " // -------------------------------", 31 | 32 | " vec2 centerUv = vec2(0.5);", 33 | " vec2 centerCoord = uResolution.xy * centerUv;", 34 | 35 | " deg = toFixedTwo(deg);", 36 | " float slope = normalizedSlope(slopeForDegrees(deg), uResolution.xy);", 37 | 38 | 39 | " // Find offsets", 40 | " // -------------------------------", 41 | 42 | " vec2 k = offsetsForCoordAtDistanceOnSlope(spread, slope);", 43 | " if (deg <= 90.0 || deg >= 270.0) {", 44 | " k *= -1.0;", 45 | " }", 46 | 47 | 48 | " return k;", 49 | "}", 50 | 51 | 52 | "float noiseWithWidthAtUv(float width, vec2 uv) {", 53 | " float noiseModifier = 1.0;", 54 | " if (uAnimateNoise > 0.0) {", 55 | " noiseModifier = sin(uGlobalTime);", 56 | " }", 57 | 58 | " vec2 noiseRowCol = floor((uv * uResolution.xy) / width);", 59 | " vec2 noiseFragCoord = ((noiseRowCol * width) + (width / 2.0));", 60 | " vec2 noiseUv = noiseFragCoord / uResolution.xy;", 61 | 62 | " return random(noiseUv * noiseModifier) * 0.125;", 63 | "}", 64 | 65 | 66 | "vec4 motionBlur(vec2 uv, vec2 blurOffset, float maxOffset) {", 67 | " float noiseWidth = 3.0;", 68 | " float randNoise = noiseWithWidthAtUv(noiseWidth, uv);", 69 | 70 | " vec4 result = textTexture(uv);", 71 | 72 | " float maxStepsReached = 0.0;", 73 | 74 | " // Note: Step by 2 to optimize performance. We conceal lossiness here via applied noise.", 75 | " // If you do want maximum fidelity, change `i += 2` to `i += 1` below.", 76 | " for (int i = 1; i <= MAX_STEPS; i += 2) {", 77 | " if (abs(float(i)) > maxOffset) { break; }", 78 | " maxStepsReached += 1.0;", 79 | 80 | " // Divide blurOffset by 2.0 so that motion blur starts half way behind itself", 81 | " // preventing blur from shoving samples in any direction", 82 | " vec2 offset = (blurOffset / 2.0) - (blurOffset * (float(i) / maxOffset));", 83 | " vec4 stepSample = textTexture(uv + (offset / uResolution.xy));",, 84 | 85 | " result += stepSample;", 86 | " }", 87 | 88 | " if (maxOffset >= 1.0) {", 89 | " result /= maxStepsReached;", 90 | " //result.a = pow(result.a, 2.0); // Apply logarithmic smoothing to alpha", 91 | " result.a -= (randNoise * (1.0 - result.a)); // Apply noise to smoothed alpha", 92 | " }", 93 | 94 | 95 | " return result;", 96 | "}", 97 | 98 | 99 | "void mainImage( out vec4 mainImage, in vec2 fragCoord ) {", 100 | 101 | " // Setup", 102 | " // -------------------------------", 103 | 104 | " vec2 uv = fragCoord.xy / uResolution.xy;", 105 | 106 | " float offset = min(float(MAX_STEPS), uResolution.y * uOffset);", 107 | 108 | " float slope = normalizedSlope(slopeForDegrees(uRotation), uResolution);", 109 | 110 | " // We want the blur to be the full offset amount in each direction", 111 | " // and to adjust with our logarithmic adjustment made later, so multiply by 4", 112 | " float adjustedOffset = offset;// * 4.0;", 113 | 114 | " vec2 blurOffset = motionBlurOffsets(fragCoord, uRotation, adjustedOffset);", 115 | 116 | 117 | " // Set Starting Points", 118 | " // -------------------------------", 119 | 120 | " vec2 rUv = uv;", 121 | " vec2 gUv = uv;", 122 | " vec2 bUv = uv;", 123 | 124 | " vec2 k = offsetsForCoordAtDistanceOnSlope(offset, slope) / uResolution;", 125 | 126 | " if (uRotation <= 90.0 || uRotation >= 270.0) {", 127 | " rUv += k;", 128 | " bUv -= k;", 129 | " }", 130 | " else {", 131 | " rUv -= k;", 132 | " bUv += k;", 133 | " }", 134 | 135 | 136 | " // Blur and Split Channels", 137 | " // -------------------------------", 138 | 139 | " vec4 resultR = vec4(0.0);", 140 | " vec4 resultG = vec4(0.0);", 141 | " vec4 resultB = vec4(0.0);", 142 | 143 | " if (uApplyBlur > 0.0) {", 144 | " resultR = motionBlur(rUv, blurOffset, adjustedOffset);", 145 | " resultG = motionBlur(gUv, blurOffset, adjustedOffset);", 146 | " resultB = motionBlur(bUv, blurOffset, adjustedOffset);", 147 | " } else {", 148 | " resultR = textTexture(rUv);", 149 | " resultG = textTexture(gUv);", 150 | " resultB = textTexture(bUv);", 151 | " }", 152 | 153 | " float a = resultR.a + resultG.a + resultB.a;", 154 | 155 | " resultR = normalBlend(resultR, uBlendColor);", 156 | " resultG = normalBlend(resultG, uBlendColor);", 157 | " resultB = normalBlend(resultB, uBlendColor);", 158 | 159 | 160 | 161 | " mainImage = vec4(resultR.r, resultG.g, resultB.b, a);", 162 | "}" 163 | ].join("\n"); 164 | 165 | return mainImageSrc; 166 | } 167 | 168 | return { 169 | 170 | constructor : Blotter.ChannelSplitMaterial, 171 | 172 | init : function () { 173 | this.mainImage = _mainImageSrc(); 174 | this.uniforms = { 175 | uOffset : { type : "1f", value : 0.05 }, 176 | uRotation : { type : "1f", value : 45.0 }, 177 | uApplyBlur : { type : "1f", value : 1.0 }, 178 | uAnimateNoise : { type : "1f", value : 1.0 } 179 | }; 180 | } 181 | }; 182 | 183 | })()); 184 | 185 | })( 186 | this.Blotter 187 | ); 188 | -------------------------------------------------------------------------------- /build/materials/fliesMaterial.js: -------------------------------------------------------------------------------- 1 | (function(Blotter) { 2 | 3 | Blotter.FliesMaterial = function() { 4 | Blotter.Material.apply(this, arguments); 5 | }; 6 | 7 | Blotter.FliesMaterial.prototype = Object.create(Blotter.Material.prototype); 8 | 9 | Blotter._extendWithGettersSetters(Blotter.FliesMaterial.prototype, (function () { 10 | 11 | function _mainImageSrc () { 12 | var mainImageSrc = [ 13 | Blotter.Assets.Shaders.Random, 14 | 15 | "vec2 random2(vec2 p) {", 16 | " return fract(sin(vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)))) * 43758.5453);", 17 | "}", 18 | 19 | "float isParticle(out vec3 particleColor, vec2 fragCoord, float pointRadius, float pointCellWidth, float dodge, vec2 dodgePosition, float dodgeSpread, float speed) { ", 20 | " if (pointCellWidth == 0.0) { return 0.0; };", 21 | 22 | " vec2 uv = fragCoord.xy / uResolution.xy;", 23 | 24 | " float pointRadiusOfCell = pointRadius / pointCellWidth;", 25 | 26 | " vec2 totalCellCount = uResolution.xy / pointCellWidth;", 27 | " vec2 cellUv = uv * totalCellCount;", 28 | 29 | " // Tile the space", 30 | " vec2 iUv = floor(cellUv);", 31 | " vec2 fUv = fract(cellUv);", 32 | 33 | " float minDist = 1.0; // minimun distance", 34 | 35 | " vec4 baseSample = textTexture(cellUv);", 36 | " float maxWeight = 0.0;", 37 | " particleColor = baseSample.rgb;", 38 | 39 | " for (int y= -1; y <= 1; y++) {", 40 | " for (int x= -1; x <= 1; x++) {", 41 | " // Neighbor place in the grid", 42 | " vec2 neighbor = vec2(float(x), float(y));", 43 | 44 | " // Random position from current + neighbor place in the grid", 45 | " vec2 point = random2(iUv + neighbor);", 46 | 47 | " // Get cell weighting from cell's center alpha", 48 | " vec2 cellRowCol = floor(fragCoord / pointCellWidth) + neighbor;", 49 | " vec2 cellFragCoord = ((cellRowCol * pointCellWidth) + (pointCellWidth / 2.0));", 50 | " vec4 cellSample = textTexture(cellFragCoord / uResolution.xy);", 51 | " float cellWeight = cellSample.a;", 52 | 53 | " if (cellWeight < 1.0) {", 54 | " // If the cell is not fully within our text, we should disregard it", 55 | " continue;", 56 | " }", 57 | 58 | " maxWeight = max(maxWeight, cellWeight);", 59 | " if (cellWeight == maxWeight) {", 60 | " particleColor = cellSample.rgb;", 61 | " }", 62 | 63 | " float distanceFromDodge = distance(dodgePosition * uResolution.xy, cellFragCoord) / uResolution.y;", 64 | " distanceFromDodge = 1.0 - smoothstep(0.0, dodgeSpread, distanceFromDodge);", 65 | 66 | " // Apply weighting to noise and dodge if dodge is set to 1.0", 67 | " cellWeight = step(cellWeight, random(cellRowCol)) + (distanceFromDodge * dodge);", 68 | 69 | " // Animate the point", 70 | " point = 0.5 + 0.75 * sin((uGlobalTime * speed) + 6.2831 * point);", 71 | 72 | " // Vector between the pixel and the point", 73 | " vec2 diff = neighbor + point - fUv;", 74 | 75 | " // Distance to the point", 76 | " float dist = length(diff);", 77 | " dist += cellWeight; // Effectively remove point", 78 | 79 | " // Keep the closer distance", 80 | " minDist = min(minDist, dist);", 81 | " }", 82 | " }", 83 | 84 | 85 | " float pointEasing = pointRadiusOfCell - (1.0 / pointCellWidth);", 86 | 87 | " float isParticle = 1.0 - smoothstep(pointEasing, pointRadiusOfCell, minDist);", 88 | 89 | " return isParticle;", 90 | "}", 91 | 92 | "void mainImage( out vec4 mainImage, in vec2 fragCoord ) {", 93 | " vec2 uv = fragCoord.xy / uResolution.xy;", 94 | 95 | " // Convert uPointCellWidth to pixels, keeping it between 1 and the total y resolution of the text", 96 | " // Note: floor uPointCellWidth here so that we dont have half pixel widths on retina displays", 97 | " float pointCellWidth = floor(max(0.0, min(1.0, uPointCellWidth) * uResolution.y));", 98 | 99 | " // Ensure uPointRadius allow points to exceed the width of their cells", 100 | " float pointRadius = uPointRadius * 0.8;", 101 | " pointRadius = min(pointRadius * pointCellWidth, pointCellWidth);", 102 | 103 | " float dodge = ceil(uDodge);", 104 | 105 | " vec3 outColor = vec3(0.0);", 106 | " float point = isParticle(outColor, fragCoord, pointRadius, pointCellWidth, dodge, uDodgePosition, uDodgeSpread, uSpeed);", 107 | 108 | " mainImage = vec4(outColor, point);", 109 | "}" 110 | ].join("\n"); 111 | 112 | return mainImageSrc; 113 | } 114 | 115 | return { 116 | 117 | constructor : Blotter.FliesMaterial, 118 | 119 | init : function () { 120 | this.mainImage = _mainImageSrc(); 121 | this.uniforms = { 122 | uPointCellWidth : { type : "1f", value : 0.04 }, 123 | uPointRadius : { type : "1f", value : 0.75 }, 124 | uDodge : { type : "1f", value : 0.0 }, 125 | uDodgePosition : { type : "2f", value : [0.5, 0.5] }, 126 | uDodgeSpread : { type : "1f", value : 0.25 }, 127 | uSpeed : { type : "1f", value : 1.0 } 128 | }; 129 | } 130 | }; 131 | 132 | })()); 133 | 134 | })( 135 | this.Blotter 136 | ); 137 | -------------------------------------------------------------------------------- /build/materials/liquidDistortMaterial.js: -------------------------------------------------------------------------------- 1 | (function(Blotter) { 2 | 3 | Blotter.LiquidDistortMaterial = function() { 4 | Blotter.Material.apply(this, arguments); 5 | }; 6 | 7 | Blotter.LiquidDistortMaterial.prototype = Object.create(Blotter.Material.prototype); 8 | 9 | Blotter._extendWithGettersSetters(Blotter.LiquidDistortMaterial.prototype, (function () { 10 | 11 | function _mainImageSrc () { 12 | var mainImageSrc = [ 13 | Blotter.Assets.Shaders.Noise3D, 14 | 15 | "void mainImage( out vec4 mainImage, in vec2 fragCoord )", 16 | "{", 17 | " // Setup ========================================================================", 18 | 19 | " vec2 uv = fragCoord.xy / uResolution.xy;", 20 | " float z = uSeed + uGlobalTime * uSpeed;", 21 | 22 | " uv += snoise(vec3(uv, z)) * uVolatility;", 23 | 24 | " mainImage = textTexture(uv);", 25 | 26 | "}" 27 | ].join("\n"); 28 | 29 | return mainImageSrc; 30 | } 31 | 32 | return { 33 | 34 | constructor : Blotter.LiquidDistortMaterial, 35 | 36 | init : function () { 37 | this.mainImage = _mainImageSrc(); 38 | this.uniforms = { 39 | uSpeed : { type : "1f", value : 1.0 }, 40 | uVolatility : { type : "1f", value : 0.15 }, 41 | uSeed : { type : "1f", value : 0.1 } 42 | }; 43 | } 44 | }; 45 | 46 | })()); 47 | 48 | })( 49 | this.Blotter 50 | ); 51 | -------------------------------------------------------------------------------- /build/materials/rollingDistortMaterial.js: -------------------------------------------------------------------------------- 1 | (function(Blotter) { 2 | 3 | Blotter.RollingDistortMaterial = function() { 4 | Blotter.Material.apply(this, arguments); 5 | }; 6 | 7 | Blotter.RollingDistortMaterial.prototype = Object.create(Blotter.Material.prototype); 8 | 9 | Blotter._extendWithGettersSetters(Blotter.RollingDistortMaterial.prototype, (function () { 10 | 11 | function _mainImageSrc () { 12 | var mainImageSrc = [ 13 | Blotter.Assets.Shaders.PI, 14 | Blotter.Assets.Shaders.LineMath, 15 | Blotter.Assets.Shaders.Noise, 16 | 17 | "// Fix a floating point number to two decimal places", 18 | "float toFixedTwo(float f) {", 19 | " return float(int(f * 100.0)) / 100.0;", 20 | "}", 21 | 22 | "// Via: http://www.iquilezles.org/www/articles/functions/functions.htm", 23 | "float impulse(float k, float x) {", 24 | " float h = k * x;", 25 | " return h * exp(1.0 - h);", 26 | "}", 27 | 28 | "vec2 waveOffset(vec2 fragCoord, float sineDistortSpread, float sineDistortCycleCount, float sineDistortAmplitude, float noiseDistortVolatility, float noiseDistortAmplitude, vec2 distortPosition, float deg, float speed) {", 29 | 30 | " // Setup", 31 | " // -------------------------------", 32 | 33 | " deg = toFixedTwo(deg);", 34 | 35 | " float centerDistance = 0.5;", 36 | " vec2 centerUv = vec2(centerDistance);", 37 | " vec2 centerCoord = uResolution.xy * centerUv;", 38 | 39 | " float changeOverTime = uGlobalTime * speed;", 40 | 41 | " float slope = normalizedSlope(slopeForDegrees(deg), uResolution.xy);", 42 | " float perpendicularDeg = mod(deg + 90.0, 360.0); // Offset angle by 90.0, but keep it from exceeding 360.0", 43 | " float perpendicularSlope = normalizedSlope(slopeForDegrees(perpendicularDeg), uResolution.xy);", 44 | 45 | 46 | " // Find intersects for line with edges of viewport", 47 | " // -------------------------------", 48 | 49 | " vec2 edgeIntersectA = vec2(0.0);", 50 | " vec2 edgeIntersectB = vec2(0.0);", 51 | " intersectsOnRectForLine(edgeIntersectA, edgeIntersectB, vec2(0.0), uResolution.xy, centerCoord, slope);", 52 | " float crossSectionLength = distance(edgeIntersectA, edgeIntersectB);", 53 | 54 | " // Find the threshold for degrees at which our intersectsOnRectForLine function would flip", 55 | " // intersects A and B because of the order in which it finds them. This is the angle at which", 56 | " // the y coordinate for the hypotenuse of a right triangle whose oposite adjacent edge runs from", 57 | " // vec2(0.0, centerCoord.y) to centerCoord and whose opposite edge runs from vec2(0.0, centerCoord.y) to", 58 | " // vec2(0.0, uResolution.y) exceeeds uResolution.y", 59 | " float thresholdDegA = atan(centerCoord.y / centerCoord.x) * (180.0 / PI);", 60 | " float thresholdDegB = mod(thresholdDegA + 180.0, 360.0);", 61 | 62 | " vec2 edgeIntersect = vec2(0.0);", 63 | " if (deg < thresholdDegA || deg > thresholdDegB) {", 64 | " edgeIntersect = edgeIntersectA;", 65 | " } else {", 66 | " edgeIntersect = edgeIntersectB;", 67 | " }", 68 | 69 | " vec2 perpendicularIntersectA = vec2(0.0);", 70 | " vec2 perpendicularIntersectB = vec2(0.0);", 71 | " intersectsOnRectForLine(perpendicularIntersectA, perpendicularIntersectB, vec2(0.0), uResolution.xy, centerCoord, perpendicularSlope); ", 72 | " float perpendicularLength = distance(perpendicularIntersectA, perpendicularIntersectA);", 73 | 74 | " vec2 coordLineIntersect = vec2(0.0);", 75 | " lineLineIntersection(coordLineIntersect, centerCoord, slope, fragCoord, perpendicularSlope);", 76 | 77 | 78 | " // Define placement for distortion ", 79 | " // -------------------------------", 80 | 81 | " vec2 distortPositionIntersect = vec2(0.0);", 82 | " lineLineIntersection(distortPositionIntersect, distortPosition * uResolution.xy, perpendicularSlope, edgeIntersect, slope);", 83 | " float distortDistanceFromEdge = (distance(edgeIntersect, distortPositionIntersect) / crossSectionLength);// + sineDistortSpread;", 84 | 85 | " float uvDistanceFromDistort = distance(edgeIntersect, coordLineIntersect) / crossSectionLength;", 86 | " float noiseDistortVarianceAdjuster = uvDistanceFromDistort + changeOverTime;", 87 | " uvDistanceFromDistort += -centerDistance + distortDistanceFromEdge + changeOverTime;", 88 | " uvDistanceFromDistort = mod(uvDistanceFromDistort, 1.0); // For sine, keep distance between 0.0 and 1.0", 89 | 90 | 91 | " // Define sine distortion ", 92 | " // -------------------------------", 93 | 94 | " float minThreshold = centerDistance - sineDistortSpread;", 95 | " float maxThreshold = centerDistance + sineDistortSpread;", 96 | 97 | " uvDistanceFromDistort = clamp(((uvDistanceFromDistort - minThreshold)/(maxThreshold - minThreshold)), 0.0, 1.0);", 98 | " if (sineDistortSpread < 0.5) {", 99 | " // Add smoother decay to sin distort when it isnt taking up the full viewport.", 100 | " uvDistanceFromDistort = impulse(uvDistanceFromDistort, uvDistanceFromDistort);", 101 | " }", 102 | 103 | " float sineDistortion = sin(uvDistanceFromDistort * PI * sineDistortCycleCount) * sineDistortAmplitude;", 104 | 105 | 106 | " // Define noise distortion ", 107 | " // -------------------------------", 108 | 109 | " float noiseDistortion = noise(noiseDistortVolatility * noiseDistortVarianceAdjuster) * noiseDistortAmplitude;", 110 | " if (noiseDistortVolatility > 0.0) {", 111 | " noiseDistortion -= noiseDistortAmplitude / 2.0; // Adjust primary distort so that it distorts in two directions.", 112 | " }", 113 | " noiseDistortion *= (sineDistortion > 0.0 ? 1.0 : -1.0); // Adjust primary distort to account for sin variance.", 114 | 115 | 116 | " // Combine distortions to find UV offsets ", 117 | " // -------------------------------", 118 | 119 | " vec2 kV = offsetsForCoordAtDistanceOnSlope(sineDistortion + noiseDistortion, perpendicularSlope);", 120 | " if (deg <= 0.0 || deg >= 180.0) {", 121 | " kV *= -1.0;", 122 | " }", 123 | 124 | 125 | " return kV;", 126 | "}", 127 | 128 | 129 | "void mainImage( out vec4 mainImage, in vec2 fragCoord )", 130 | "{", 131 | " // Setup", 132 | " // -------------------------------", 133 | 134 | " vec2 uv = fragCoord.xy / uResolution.xy;", 135 | 136 | " // Minor hacks to ensure our waves start horizontal and animating in a downward direction by default.", 137 | " uRotation = mod(uRotation + 270.0, 360.0);", 138 | " uDistortPosition.y = 1.0 - uDistortPosition.y;", 139 | 140 | 141 | " // Distortion", 142 | " // -------------------------------", 143 | 144 | " vec2 offset = waveOffset(fragCoord, uSineDistortSpread, uSineDistortCycleCount, uSineDistortAmplitude, uNoiseDistortVolatility, uNoiseDistortAmplitude, uDistortPosition, uRotation, uSpeed);", 145 | 146 | " mainImage = textTexture(uv + offset);", 147 | "}" 148 | ].join("\n"); 149 | 150 | return mainImageSrc; 151 | } 152 | 153 | return { 154 | 155 | constructor : Blotter.RollingDistortMaterial, 156 | 157 | init : function () { 158 | this.mainImage = _mainImageSrc(); 159 | this.uniforms = { 160 | uSineDistortSpread : { type : "1f", value : 0.05 }, 161 | uSineDistortCycleCount : { type : "1f", value : 2.0 }, 162 | uSineDistortAmplitude : { type : "1f", value : 0.25 }, 163 | uNoiseDistortVolatility : { type : "1f", value : 20.0 }, 164 | uNoiseDistortAmplitude : { type : "1f", value : 0.01 }, 165 | uDistortPosition : { type : "2f", value : [0.5, 0.5] }, 166 | uRotation : { type : "1f", value : 170.0 }, 167 | uSpeed : { type : "1f", value : 0.08 } 168 | }; 169 | } 170 | }; 171 | 172 | })()); 173 | 174 | })( 175 | this.Blotter 176 | ); 177 | -------------------------------------------------------------------------------- /build/materials/slidingDoorMaterial.js: -------------------------------------------------------------------------------- 1 | (function(Blotter) { 2 | 3 | Blotter.SlidingDoorMaterial = function() { 4 | Blotter.Material.apply(this, arguments); 5 | }; 6 | 7 | Blotter.SlidingDoorMaterial.prototype = Object.create(Blotter.Material.prototype); 8 | 9 | Blotter._extendWithGettersSetters(Blotter.SlidingDoorMaterial.prototype, (function () { 10 | 11 | function _mainImageSrc () { 12 | var mainImageSrc = [ 13 | Blotter.Assets.Shaders.PI, 14 | 15 | "float easingForPositionWithDeadzoneWidth(float position, float deadzoneWidth) {", 16 | " // Offset position buy 0.25 so that sin wave begins on a downslope at 0.0", 17 | " position += 0.25;", 18 | 19 | " // Distance of adjusted position from 0.75, min of 0.0 and max of 0.5", 20 | " float firstDist = distance(position, 0.75);", 21 | 22 | " // Divide deadzoneWidth by two, as we will be working it out in either direction from the center position.", 23 | " float halfDeadzoneWidth = deadzoneWidth / 2.0;", 24 | 25 | " // Clamp distance of position from center (0.75) to something between 0.5 and the halfDeadzoneWidth from center.", 26 | " float removedDistance = max(firstDist, halfDeadzoneWidth);", 27 | 28 | " // Find the percentage of removedDistance within the range of halfDeadzoneWidth..0.5", 29 | " float distanceOfRange = (removedDistance - halfDeadzoneWidth) / (0.5 - halfDeadzoneWidth);", 30 | 31 | " // Convert distanceOfRange to a number between 0.0 and 0.5. This means that for any pixel +/- halfDeadzoneWidth from center, the value will be 0.5.", 32 | " float offsetDist = (0.5 * (1.0 - (distanceOfRange)));", 33 | 34 | " if (position < 0.75) {", 35 | " position = 0.25 + offsetDist;", 36 | " } else {", 37 | " position = 1.25 - offsetDist;", 38 | " }", 39 | 40 | 41 | " return sin((position) * PI * 2.0) / 2.0;", 42 | "}", 43 | 44 | 45 | "void mainImage( out vec4 mainImage, in vec2 fragCoord )", 46 | "{", 47 | " // Setup ========================================================================", 48 | 49 | " vec2 uv = fragCoord.xy / uResolution.xy;", 50 | 51 | " float time = uGlobalTime * uSpeed;", 52 | 53 | " float directionalAdjustment = uFlipAnimationDirection > 0.0 ? -1.0 : 1.0;", 54 | 55 | 56 | " if (uSpeed > 0.0) {", 57 | 58 | " // Define Axis-Based Striping ===================================================", 59 | 60 | " float divisions = uDivisions;", 61 | " float effectPosition = fragCoord.y;", 62 | " float effectDimension = uResolution.y;", 63 | " if (uAnimateHorizontal > 0.0) {", 64 | " effectPosition = fragCoord.x;", 65 | " effectDimension = uResolution.x;", 66 | " divisions = floor((divisions * (uResolution.x / uResolution.y)) + 0.5);", 67 | " }", 68 | " float stripe = floor(effectPosition / (effectDimension / divisions));", 69 | 70 | 71 | " // Animate =====================================================================", 72 | 73 | " float timeAdjustedForStripe = time - ((uDivisionWidth / divisions) * stripe) * directionalAdjustment;", 74 | " float offsetAtTime = mod(timeAdjustedForStripe, 1.0);", 75 | 76 | " // Divide sin output by 2 and add to 0.5 so that sin wave move between 0.0 and 1.0 rather than -1.0 and 1.0.", 77 | " float easing = 0.5 + easingForPositionWithDeadzoneWidth(offsetAtTime, uDivisionWidth);", 78 | 79 | " // Mulptiply offsetAtTime by 2.0 and subtract from 1.0 so that position changes over a range of -1.0 to 1.0 rather than 0.0 to 1.0.", 80 | " if (uAnimateHorizontal > 0.0) {", 81 | " uv.x -= ((1.0 - (offsetAtTime * 2.0)) * easing) * directionalAdjustment;", 82 | " } else {", 83 | " uv.y -= ((1.0 - (offsetAtTime * 2.0)) * easing) * directionalAdjustment;", 84 | " }", 85 | " }", 86 | 87 | " mainImage = textTexture(uv);", 88 | 89 | "}" 90 | ].join("\n"); 91 | 92 | return mainImageSrc; 93 | } 94 | 95 | return { 96 | 97 | constructor : Blotter.SlidingDoorMaterial, 98 | 99 | init : function () { 100 | this.mainImage = _mainImageSrc(); 101 | this.uniforms = { 102 | uDivisions : { type : "1f", value : 5.0 }, 103 | uDivisionWidth : { type : "1f", value : 0.25 }, 104 | uAnimateHorizontal : { type : "1f", value : 0.0 }, 105 | uFlipAnimationDirection : { type : "1f", value : 0.0 }, 106 | uSpeed : { type : "1f", value : 0.2 } 107 | }; 108 | } 109 | }; 110 | 111 | })()); 112 | 113 | })( 114 | this.Blotter 115 | ); 116 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | // ███ █ ████▄ ▄▄▄▄▀ ▄▄▄▄▀ ▄███▄ █▄▄▄▄ 3 | // █ █ █ █ █ ▀▀▀ █ ▀▀▀ █ █▀ ▀ █ ▄▀ 4 | // █ ▀ ▄ █ █ █ █ █ ██▄▄ █▀▀▌ 5 | // █ ▄▀ ███▄ ▀████ █ █ █▄ ▄▀ █ █ 6 | // ███ ▀ ▀ ▀ ▀███▀ █ 7 | // ▀ 8 | // The MIT License 9 | // 10 | // Copyright © 1986 - ∞, Blotter / Bradley Griffith / http://bradley.computer 11 | // 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy 13 | // of this software and associated documentation files (the "Software"), to deal 14 | // in the Software without restriction, including without limitation the rights 15 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | // copies of the Software, and to permit persons to whom the Software is 17 | // furnished to do so, subject to the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be included in 20 | // all copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | // THE SOFTWARE. 29 | */ 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blotter.js", 3 | "version": "0.1.0", 4 | "description": "A JavaScript API for drawing unconventional text effects on the web.", 5 | "author": { 6 | "name": "Bradley Griffith", 7 | "email": "bradley.j.griffith@gmail.com", 8 | "url": "http://bradley.computer" 9 | }, 10 | "license": "MIT", 11 | "licenses": [ 12 | { 13 | "type": "MIT", 14 | "url": "https://github.com/bradley/blotter/blob/master/license.txt" 15 | } 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/bradley/blotter" 20 | }, 21 | "main": "build/blotter.js", 22 | "unpkg": "build/blotter.min.js", 23 | "files": [ 24 | "build/blotter.js", 25 | "build/blotter.min.js" 26 | ], 27 | "homepage": "http://bradley.github.io/Blotter", 28 | "bugs": { 29 | "url": "https://github.com/bradley/blotter/issues" 30 | }, 31 | "keywords": [ 32 | "webgl", 33 | "glsl", 34 | "glsl-shaders", 35 | "javascript", 36 | "typography", 37 | "text", 38 | "css", 39 | "animation", 40 | "design", 41 | "creative-coding", 42 | "lsd" 43 | ], 44 | "scripts": { 45 | "build-custom-three": "npx browserify utils/three_build.js -p [ threejs-tree-shake --loose ] > third_party/three/three.custom.js", 46 | "build": "npm run build-custom-three && grunt" 47 | }, 48 | "dependencies": { 49 | "three": "^0.89.0" 50 | }, 51 | "devDependencies": { 52 | "browserify": "^15.2.0", 53 | "grunt": "^0.4.0", 54 | "grunt-closure-tools": "^0.9.0", 55 | "grunt-contrib-concat": "^0.4.0", 56 | "grunt-contrib-jshint": "^0.10.0", 57 | "grunt-contrib-uglify": "^0.4.0", 58 | "grunt-contrib-watch": "^0.6.0", 59 | "npx": "^9.7.1", 60 | "threejs-tree-shake": "^1.0.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/assets/shaders/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | Files within this folder allow you to import useful glsl helper functions into your glsl source strings. 3 | 4 | In order to access methods within these files, strings exposed by the files in this folder should be inserted directly into shader source strings. 5 | 6 | Example: 7 | 8 | ``` 9 | var myFragmentSrc = [ 10 | Blotter.Assets.Shaders.PI, // Includes constants defined in pi.js 11 | 12 | "// Do something with PI", 13 | "float myFunction(float f) {", 14 | " return f / PI;", 15 | "}", 16 | 17 | "..." 18 | ].join("\n"); 19 | ``` 20 | 21 | # Licensing 22 | The majority of files within this folder (all but those within `./core`) were supplied by third party developers. Please see license information at the top of each file's source. 23 | -------------------------------------------------------------------------------- /src/assets/shaders/blinnphongspecular.js: -------------------------------------------------------------------------------- 1 | // See License comments in shader string 2 | 3 | (function(Blotter, _) { 4 | 5 | Blotter.Assets.Shaders.BlinnPhongSpecular = [ 6 | "//", 7 | "// Author : Reza Ali", 8 | "// License : Distributed under the MIT License.", 9 | "//", 10 | "", 11 | "float blinnPhongSpecular( vec3 lightDirection, vec3 viewDirection, vec3 surfaceNormal, float shininess ) {", 12 | "", 13 | " //Calculate Blinn-Phong power", 14 | " vec3 H = normalize(viewDirection + lightDirection);", 15 | " return pow(max(0.0, dot(surfaceNormal, H)), shininess);", 16 | "}" 17 | ].join("\n"); 18 | 19 | })( 20 | this.Blotter, this._ 21 | ); 22 | -------------------------------------------------------------------------------- /src/assets/shaders/core/blending.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _) { 2 | 3 | Blotter.Assets.Shaders.Blending = [ 4 | "//", 5 | "// Author : Bradley Griffith", 6 | "// License : Distributed under the MIT License.", 7 | "//", 8 | "", 9 | "// Returns the resulting blend color by blending a top color over a base color", 10 | "highp vec4 normalBlend(highp vec4 topColor, highp vec4 baseColor) {", 11 | " highp vec4 blend = vec4(0.0);", 12 | 13 | " // HACK", 14 | " // Cant divide by 0 (see the 'else' alpha) and after a lot of attempts", 15 | " // this simply seems like the only solution Im going to be able to come up with to get alpha back.", 16 | " if (baseColor.a == 1.0) {", 17 | " baseColor.a = 0.9999999;", 18 | " };", 19 | 20 | " if (topColor.a >= 1.0) {", 21 | " blend.a = topColor.a;", 22 | " blend.r = topColor.r;", 23 | " blend.g = topColor.g;", 24 | " blend.b = topColor.b;", 25 | " } else if (topColor.a == 0.0) {", 26 | " blend.a = baseColor.a;", 27 | " blend.r = baseColor.r;", 28 | " blend.g = baseColor.g;", 29 | " blend.b = baseColor.b;", 30 | " } else {", 31 | " blend.a = 1.0 - (1.0 - topColor.a) * (1.0 - baseColor.a); // alpha", 32 | " blend.r = (topColor.r * topColor.a / blend.a) + (baseColor.r * baseColor.a * (1.0 - topColor.a) / blend.a);", 33 | " blend.g = (topColor.g * topColor.a / blend.a) + (baseColor.g * baseColor.a * (1.0 - topColor.a) / blend.a);", 34 | " blend.b = (topColor.b * topColor.a / blend.a) + (baseColor.b * baseColor.a * (1.0 - topColor.a) / blend.a);", 35 | " }", 36 | 37 | " return blend;", 38 | "}", 39 | 40 | "// Returns a vec4 representing the original top color that would have been needed to blend", 41 | "// against a passed in base color in order to result in the passed in blend color.", 42 | "highp vec4 normalUnblend(highp vec4 blendColor, highp vec4 baseColor) {", 43 | " highp vec4 unblend = vec4(0.0);", 44 | 45 | " // HACKY", 46 | " // Cant divide by 0 (see alpha) and after a lot of attempts", 47 | " // this simply seems like the only solution Im going to be able to come up with to get alpha back.", 48 | " if (baseColor.a == 1.0) {", 49 | " baseColor.a = 0.9999999;", 50 | " }", 51 | 52 | " unblend.a = 1.0 - ((1.0 - blendColor.a) / (1.0 - baseColor.a));", 53 | " // Round to two decimal places", 54 | " unblend.a = (sign(100.0 * unblend.a) * floor(abs(100.0 * unblend.a) + 0.5)) / 100.0;", 55 | 56 | " if (unblend.a >= 1.0) {", 57 | " unblend.r = blendColor.r;", 58 | " unblend.g = blendColor.g;", 59 | " unblend.b = blendColor.b;", 60 | " } else if (unblend.a == 0.0) {", 61 | " unblend.r = baseColor.r;", 62 | " unblend.g = baseColor.g;", 63 | " unblend.b = baseColor.b;", 64 | " } else {", 65 | " unblend.r = (blendColor.r - (baseColor.r * baseColor.a * (1.0 - unblend.a) / blendColor.a)) / (unblend.a / blendColor.a);", 66 | " unblend.g = (blendColor.g - (baseColor.g * baseColor.a * (1.0 - unblend.a) / blendColor.a)) / (unblend.a / blendColor.a);", 67 | " unblend.b = (blendColor.b - (baseColor.b * baseColor.a * (1.0 - unblend.a) / blendColor.a)) / (unblend.a / blendColor.a);", 68 | " }", 69 | 70 | " return unblend;", 71 | "}", 72 | ].join("\n"); 73 | 74 | })( 75 | this.Blotter, this._ 76 | ); 77 | -------------------------------------------------------------------------------- /src/assets/shaders/core/inf.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _) { 2 | 3 | Blotter.Assets.Shaders.Inf = [ 4 | "//", 5 | "// Author : Bradley Griffith", 6 | "// License : Distributed under the MIT License.", 7 | "//", 8 | "bool isinf(float val) {", 9 | " return (val != 0.0 && val * 2.0 == val) ? true : false;", 10 | "}", 11 | ].join("\n"); 12 | 13 | })( 14 | this.Blotter, this._ 15 | ); 16 | -------------------------------------------------------------------------------- /src/assets/shaders/core/line_math.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _) { 2 | 3 | Blotter.Assets.Shaders.LineMath = [ 4 | Blotter.Assets.Shaders.Inf, 5 | "", 6 | "//", 7 | "// Author : Bradley Griffith", 8 | "// License : Distributed under the MIT License.", 9 | "//", 10 | "", 11 | "// Returns the slope of a line given the degrees of the angle on which that line is rotated;", 12 | "float slopeForDegrees(float deg) {", 13 | " // Ensure degrees stay withing 0.0 - 360.0", 14 | " deg = mod(deg, 360.0);", 15 | 16 | " float radians = deg * (PI / 180.0);", 17 | 18 | " return tan(radians);", 19 | "}", 20 | 21 | "// Given x, a slope, and another point, find y for x.", 22 | "float yForXOnSlope(float x, float slope, vec2 p2) {", 23 | " return -1.0 * ((slope * (p2.x - x)) - p2.y);", 24 | "}", 25 | 26 | "// Given y, a slope, and another point, find x for y.", 27 | "float xForYOnSlope(float y, float slope, vec2 p2) {", 28 | " return ((y - p2.y) + (slope * p2.x)) / slope;", 29 | "}", 30 | 31 | "// Returns slope adjusted for screen ratio.", 32 | "float normalizedSlope(float slope, vec2 resolution) {", 33 | " vec2 p = vec2(1.0) / resolution;", 34 | " return ((slope * 100.0) / p.x) / (100.0 / p.x);", 35 | "}", 36 | 37 | "// Returns offsets (+/-) for any coordinate at distance given slope.", 38 | "// Note: This function does not normalize distance.", 39 | "// Note: This function does not adjust slope for screen ratio.", 40 | "vec2 offsetsForCoordAtDistanceOnSlope(float d, float slope) {", 41 | " return vec2(", 42 | " (d * cos(atan(slope))),", 43 | " (d * sin(atan(slope)))", 44 | " );", 45 | "}", 46 | "// Returns a boolean designating whether or not an infinite line intersects with an infinite line, and sets an `out` variable for the intersection point if it is found.", 47 | "// Note: This function does not adjust slope for screen ratio.", 48 | "bool lineLineIntersection (out vec2 intersect, in vec2 p1, in float m1, in vec2 p2, in float m2) {", 49 | " // See: http://gamedev.stackexchange.com/questions/44720/line-intersection-from-parametric-equation", 50 | " // http://stackoverflow.com/questions/41687083/formula-to-determine-if-an-infinite-line-and-a-line-segment-intersect/41687904#41687904", 51 | 52 | " bool isIntersecting = false;", 53 | 54 | " float dx = 1.0;", 55 | " float dy = m1;", 56 | 57 | " float dxx = 1.0;", 58 | " float dyy = m2;", 59 | 60 | " float denominator = ((dxx * dy) - (dyy * dx));", 61 | " if (denominator == 0.0) {", 62 | " // Lines are parallel", 63 | " return isIntersecting;", 64 | " }", 65 | 66 | " if (isinf(dy)) {", 67 | " float y = yForXOnSlope(p1.x, m2, p2);", 68 | " isIntersecting = true;", 69 | " intersect = vec2(p1.x, y);", 70 | " return isIntersecting;", 71 | " }", 72 | 73 | " if (isinf(dyy)) {", 74 | " float y = yForXOnSlope(p2.x, m1, p1);", 75 | " isIntersecting = true;", 76 | " intersect = vec2(p2.x, y);", 77 | " return isIntersecting;", 78 | " }", 79 | 80 | " float u = ((dx * (p2.y - p1.y)) + (dy * (p1.x - p2.x))) / denominator;", 81 | 82 | " isIntersecting = true;", 83 | " intersect = p2 + (u * vec2(dxx, dyy));", 84 | 85 | " return isIntersecting;", 86 | "}", 87 | 88 | "// Returns a boolean designating whether or not an infinite line intersects with a line segment, and sets an `out` variable for the intersection point if it is found.", 89 | "// Note: This function does not adjust slope for screen ratio.", 90 | "bool lineLineSegmentIntersection (out vec2 intersect, in vec2 point, in float m, in vec2 pA, in vec2 pB) {", 91 | " // See: http://gamedev.stackexchange.com/questions/44720/line-intersection-from-parametric-equation", 92 | " // http://stackoverflow.com/questions/41687083/formula-to-determine-if-an-infinite-line-and-a-line-segment-intersect/41687904#41687904", 93 | 94 | " bool isIntersecting = false;", 95 | 96 | " float dx = 1.0;", 97 | " float dy = m;", 98 | 99 | " float dxx = pB.x - pA.x;", 100 | " float dyy = pB.y - pA.y;", 101 | 102 | " float denominator = ((dxx * dy) - (dyy * dx));", 103 | " if (denominator == 0.0 || (isinf(dyy / dxx) && isinf(dy))) {", 104 | " // Lines are parallel", 105 | " return isIntersecting;", 106 | " }", 107 | 108 | " if (isinf(dy)) {", 109 | " float m2 = dyy / dxx;", 110 | " float y = yForXOnSlope(point.x, m2, pB);", 111 | " isIntersecting = true;", 112 | " intersect = vec2(point.x, y);", 113 | " return isIntersecting;", 114 | " }", 115 | 116 | " float u = ((dx * (pA.y - point.y)) + (dy * (point.x - pA.x))) / denominator;", 117 | 118 | " if (u >= 0.0 && u <= 1.0) {", 119 | " // Intersection occured on line segment", 120 | " isIntersecting = true;", 121 | " intersect = pA + (u * vec2(dxx, dyy));", 122 | " }", 123 | 124 | " return isIntersecting;", 125 | "}", 126 | "// Dev Note: Terrible code. Needs refactor. Just trying to find ", 127 | "// which two edges of the rect the intersections occur at.", 128 | "void intersectsOnRectForLine(out vec2 iA, out vec2 iB, in vec2 rMinXY, in vec2 rMaxXY, in vec2 point, in float slope) {", 129 | " bool firstIntersectFound = false;", 130 | 131 | " vec2 intersectLeft = vec2(0.0);", 132 | " vec2 intersectTop = vec2(0.0);", 133 | " vec2 intersectRight = vec2(0.0);", 134 | " vec2 intersectBottom = vec2(0.0);", 135 | 136 | " bool intersectsLeft = lineLineSegmentIntersection(intersectLeft, point, slope, rMinXY, vec2(rMinXY.x, rMaxXY.y));", 137 | " bool intersectsTop = lineLineSegmentIntersection(intersectTop, point, slope, vec2(rMinXY.x, rMaxXY.y), rMaxXY);", 138 | " bool intersectsRight = lineLineSegmentIntersection(intersectRight, point, slope, rMaxXY, vec2(rMaxXY.x, rMinXY.y));", 139 | " bool intersectsBottom = lineLineSegmentIntersection(intersectBottom, point, slope, rMinXY, vec2(rMaxXY.x, rMinXY.y));", 140 | 141 | 142 | " if (intersectsLeft) {", 143 | " if (firstIntersectFound) {", 144 | " iB = intersectLeft;", 145 | " }", 146 | " else {", 147 | " iA = intersectLeft;", 148 | " firstIntersectFound = true;", 149 | " }", 150 | " }", 151 | 152 | " if (intersectsTop) {", 153 | " if (firstIntersectFound) {", 154 | " iB = intersectTop;", 155 | " }", 156 | " else {", 157 | " iA = intersectTop;", 158 | " firstIntersectFound = true;", 159 | " }", 160 | " }", 161 | 162 | " if (intersectsRight) {", 163 | " if (firstIntersectFound) {", 164 | " iB = intersectRight;", 165 | " }", 166 | " else {", 167 | " iA = intersectRight;", 168 | " firstIntersectFound = true;", 169 | " }", 170 | " }", 171 | 172 | " if (intersectsBottom) {", 173 | " if (firstIntersectFound) {", 174 | " iB = intersectBottom;", 175 | " }", 176 | " else {", 177 | " iA = intersectBottom;", 178 | " }", 179 | " }", 180 | "}" 181 | 182 | ].join("\n"); 183 | 184 | })( 185 | this.Blotter, this._ 186 | ); 187 | -------------------------------------------------------------------------------- /src/assets/shaders/easing.js: -------------------------------------------------------------------------------- 1 | // See License comments in shader string 2 | 3 | (function(Blotter, _) { 4 | 5 | Blotter.Assets.Shaders.Easing = [ 6 | "//", 7 | "// Author : Reza Ali", 8 | "// License : Distributed under the MIT License.", 9 | "//", 10 | "", 11 | "float linear( float t, float b, float c, float d )", 12 | "{", 13 | " return t * ( c / d ) + b;", 14 | "}", 15 | "", 16 | "float linear( float t )", 17 | "{", 18 | " return t;", 19 | "}", 20 | "", 21 | "float inQuad( float t, float b, float c, float d )", 22 | "{", 23 | " return c * ( t /= d ) * t + b;", 24 | "}", 25 | "", 26 | "float inQuad( float t )", 27 | "{", 28 | " return t * t;", 29 | "}", 30 | "", 31 | "float outQuad( float t, float b, float c, float d )", 32 | "{", 33 | " return -c * ( t /= d ) * ( t - 2.0 ) + b;", 34 | "}", 35 | "", 36 | "float outQuad( float t )", 37 | "{", 38 | " return - ( t -= 1.0 ) * t + 1.0;", 39 | "}", 40 | "", 41 | "float inOutQuad( float t, float b, float c, float d )", 42 | "{", 43 | " if( ( t /= d / 2.0 ) < 1.0 ) {", 44 | " return c / 2.0 * t * t + b;", 45 | " }", 46 | " return - c / 2.0 * ( ( --t ) * ( t - 2.0 ) - 1.0 ) + b;", 47 | "}", 48 | "", 49 | "float inOutQuad( float t )", 50 | "{", 51 | " if( ( t /= 0.5 ) < 1.0 ) return 0.5 * t * t;", 52 | " return -0.5 * ( ( --t ) * ( t-2 ) - 1 );", 53 | "}", 54 | "", 55 | "float inCubic( float t, float b, float c, float d )", 56 | "{", 57 | " return c * ( t /= d ) * t * t + b;", 58 | "}", 59 | "", 60 | "float inCubic( float t )", 61 | "{", 62 | " return t * t * t;", 63 | "}", 64 | "", 65 | "float outCubic( float t, float b, float c, float d )", 66 | "{", 67 | " return c * ( ( t = t/d - 1.0 ) * t * t + 1.0 ) + b;", 68 | "}", 69 | "", 70 | "float outCubic( float t )", 71 | "{", 72 | " return ( ( --t ) * t * t + 1.0 );", 73 | "}", 74 | "", 75 | "float inOutCubic( float t, float b, float c, float d )", 76 | "{", 77 | " if( ( t /= d / 2.0 ) < 1.0 ) return c / 2.0 * t * t * t + b;", 78 | " return c / 2.0 * ( ( t -= 2.0 ) * t * t + 2.0 ) + b;", 79 | "}", 80 | "", 81 | "float inOutCubic( float t )", 82 | "{", 83 | " if( ( t /= 0.5 ) < 1.0 ) return 0.5 * t * t * t;", 84 | " return 0.5 * ( ( t -= 2.0 ) * t * t + 2.0 );", 85 | "}", 86 | "", 87 | "float inQuart( float t, float b, float c, float d )", 88 | "{", 89 | " return c * ( t /= d ) * t * t * t + b;", 90 | "}", 91 | "", 92 | "float inQuart( float t )", 93 | "{", 94 | " return t * t * t * t;", 95 | "}", 96 | "", 97 | "float outQuart( float t, float b, float c, float d )", 98 | "{", 99 | " return -c * ( ( t = t/d - 1.0 ) * t * t * t - 1.0 ) + b;", 100 | "}", 101 | "", 102 | "float outQuart( float t )", 103 | "{", 104 | " return - ( ( --t ) * t * t * t - 1.0 );", 105 | "}", 106 | "", 107 | "float inOutQuart( float t, float b, float c, float d )", 108 | "{", 109 | " if ( ( t /= d / 2.0 ) < 1.0 ) return c / 2.0 * t * t * t * t + b;", 110 | " return -c / 2.0 * ( ( t -= 2.0 ) * t * t * t - 2.0 ) + b;", 111 | "}", 112 | "", 113 | "float inOutQuart( float t )", 114 | "{", 115 | " if ( (t /= 0.5 ) < 1.0 ) return 0.5 * t * t * t * t;", 116 | " return -0.5 * ( ( t -= 2.0 ) * t * t * t - 2.0 );", 117 | "}", 118 | "", 119 | "float inQuint( float t, float b, float c, float d )", 120 | "{", 121 | " return c * ( t /= d ) * t * t * t * t + b;", 122 | "}", 123 | "", 124 | "float inQuint( float t )", 125 | "{", 126 | " return t * t * t * t * t;", 127 | "}", 128 | "", 129 | "float outQuint( float t, float b, float c, float d )", 130 | "{", 131 | " return c * ( ( t = t/d - 1.0) * t * t * t * t + 1.0 ) + b;", 132 | "}", 133 | "", 134 | "float outQuint( float t )", 135 | "{", 136 | " return ( ( --t ) * t * t * t * t + 1.0 );", 137 | "}", 138 | "", 139 | "float inOutQuint( float t, float b, float c, float d )", 140 | "{", 141 | " if( ( t /= d /2.0 ) < 1.0 ) return c / 2.0 * t * t * t * t * t + b;", 142 | " return c / 2.0 * ( ( t -= 2.0 ) * t * t * t * t + 2.0) + b;", 143 | "}", 144 | "", 145 | "float inOutQuint( float t )", 146 | "{", 147 | " if ( ( t /= 0.5 ) < 1.0 ) return 0.5 * t * t * t * t * t;", 148 | " return 0.5 * ( ( t -= 2 ) * t * t * t * t + 2.0 );", 149 | "}", 150 | "", 151 | "float inSine( float t, float b, float c, float d )", 152 | "{", 153 | " return -c * cos( t / d * ( 1.5707963268 ) ) + c + b;", 154 | "}", 155 | "", 156 | "float inSine( float t )", 157 | "{", 158 | " return -1.0 * cos( t * ( 1.5707963268 ) ) + 1.0;", 159 | "}", 160 | "", 161 | "float outSine( float t, float b, float c, float d )", 162 | "{", 163 | " return c * sin( t / d * ( 1.5707963268 ) ) + b;", 164 | "}", 165 | "", 166 | "float outSine( float t )", 167 | "{", 168 | " return sin( t * ( 1.5707963268 ) );", 169 | "}", 170 | "", 171 | "float inOutSine( float t, float b, float c, float d )", 172 | "{", 173 | " return - c / 2.0 * ( cos( 3.1415926536 * t / d ) - 1.0 ) + b;", 174 | "}", 175 | "", 176 | "float inOutSine( float t )", 177 | "{", 178 | " return -0.5 * ( cos( 3.1415926536 * t ) - 1.0 );", 179 | "}", 180 | "", 181 | "float inExpo( float t, float b, float c, float d )", 182 | "{", 183 | " return ( t == 0.0 ) ? b : c * pow( 2.0, 10.0 * ( t / d - 1.0 ) ) + b;", 184 | "}", 185 | "", 186 | "float inExpo( float t )", 187 | "{", 188 | " return ( t == 0 ) ? 0.0 : pow( 2.0, 10.0 * ( t - 1.0 ) );", 189 | "}", 190 | "", 191 | "float outExpo( float t, float b, float c, float d )", 192 | "{", 193 | " return ( t == d ) ? b + c : c * ( - pow( 2.0, -10.0 * t / d ) + 1.0 ) + b;", 194 | "}", 195 | "", 196 | "float outExpo( float t )", 197 | "{", 198 | " return ( t == 1.0 ) ? 1.0 : ( - pow( 2.0, -10.0 * t ) + 1.0 );", 199 | "}", 200 | "", 201 | "float inOutExpo( float t, float b, float c, float d )", 202 | "{", 203 | " if( t == 0 ) return b;", 204 | " if( t == d ) return b + c;", 205 | " if(( t /= d / 2.0 ) < 1.0 ) return c / 2.0 * pow( 2.0, 10.0 * ( t - 1.0 ) ) + b;", 206 | " return c / 2.0 * ( - pow( 2.0, -10.0 * --t ) + 2.0 ) + b;", 207 | "}", 208 | "", 209 | "float inOutExpo( float t )", 210 | "{", 211 | " if( t == 0.0 ) return 0.0;", 212 | " if( t == 1.0 ) return 1.0;", 213 | " if( ( t /= 0.5 ) < 1.0 ) return 0.5 * pow( 2.0, 10.0 * ( t - 1.0 ) );", 214 | " return 0.5 * ( - pow( 2.0, -10.0 * --t ) + 2.0 );", 215 | "}", 216 | "", 217 | "float inCirc( float t, float b, float c, float d )", 218 | "{", 219 | " return -c * ( sqrt( 1.0 - (t/=d)*t) - 1) + b;", 220 | "}", 221 | "", 222 | "float inCirc( float t )", 223 | "{", 224 | " return - ( sqrt( 1.0 - t*t) - 1);", 225 | "}", 226 | "", 227 | "float outCirc( float t, float b, float c, float d )", 228 | "{", 229 | " return c * sqrt( 1.0 - (t=t/d-1)*t) + b;", 230 | "}", 231 | "", 232 | "float outCirc( float t )", 233 | "{", 234 | " return sqrt( 1.0 - (--t)*t);", 235 | "}", 236 | "", 237 | "float inOutCirc( float t, float b, float c, float d )", 238 | "{", 239 | " if ( ( t /= d / 2.0 ) < 1 ) return - c / 2.0 * ( sqrt( 1.0 - t * t ) - 1.0 ) + b;", 240 | " return c / 2.0 * ( sqrt( 1.0 - ( t -= 2.0 ) * t ) + 1.0 ) + b;", 241 | "}", 242 | "", 243 | "float inOutCirc( float t )", 244 | "{", 245 | " if( ( t /= 0.5 ) < 1.0 ) return -0.5 * ( sqrt( 1.0 - t * t ) - 1.0 );", 246 | " return 0.5 * ( sqrt( 1.0 - ( t -= 2.0 ) * t ) + 1.0 );", 247 | "}", 248 | "", 249 | "float inElastic( float t, float b, float c, float d )", 250 | "{", 251 | " float s = 1.70158; float p = 0.0; float a = c;", 252 | " if( t == 0 ) return b; if( ( t /= d ) == 1 ) return b + c;", 253 | " p = d * 0.3;", 254 | " if( a < abs( c ) ) { a = c; float s = p / 4.0; }", 255 | " else s = 0.1591549431 * p / ( 6.2831853072 ) * asin( c / a );", 256 | " return -( a * pow( 2.0, 10.0 * ( t -= 1.0 ) ) * sin( ( t * d - s ) * ( 6.2831853072 ) / p ) ) + b;", 257 | "}", 258 | "", 259 | "float inElastic( float t )", 260 | "{", 261 | " float s = 1.70158; float p = 0.0; float a = 1.0;", 262 | " if( t == 0.0 ) return 0.0;", 263 | " if( t == 1.0 ) return 1.0;", 264 | " p = 0.3;", 265 | " s = p / ( 6.2831853072 ) * asin( 1.0 / a );", 266 | " return -( a * pow( 2.0, 10.0 * ( t -= 1.0 ) ) * sin( ( t - s ) * ( 6.2831853072 ) / p ) );", 267 | "}", 268 | "", 269 | "float outElastic( float t, float b, float c, float d )", 270 | "{", 271 | " float s = 1.70158; float p = 0.0; float a = c;", 272 | " if( t == 0.0 ) return b;", 273 | " if( (t /= d ) == 1.0 ) return b + c;", 274 | " p = d * 0.3;", 275 | " if( a < abs( c ) ) { a = c; s = p / 4.0; }", 276 | " else { s = p / ( 6.2831853072 ) * asin( c / a ); }", 277 | " return a * pow( 2.0, -10.0 * t ) * sin( ( t * d - s ) * ( 6.2831853072 ) / p ) + c + b;", 278 | "}", 279 | "", 280 | "float outElastic( float t )", 281 | "{", 282 | " float s = 1.70158; float p = 0.0 ; float a = 1.0;", 283 | " if( t == 0.0 ) return 0.0; if( t == 1.0 ) return 1.0;", 284 | " p = 0.3;", 285 | " s = p / ( 6.2831853072 ) * asin( 1.0 / a );", 286 | " return a * pow( 2.0, -10.0 * t ) * sin( ( t - s ) * ( 6.2831853072 ) / p ) + 1.0;", 287 | "}", 288 | "", 289 | "float inOutElastic( float t, float b, float c, float d )", 290 | "{", 291 | " float s = 1.70158; float p = 0.0; float a = c;", 292 | " if( t == 0.0 ) return b;", 293 | " if( ( t /= d / 2.0 ) == 2.0 ) return b + c;", 294 | " p = d * ( 0.3 * 1.5 );", 295 | " if( a < abs( c ) ) { a = c; s = p / 4.0; }", 296 | " else { s = p / ( 6.2831853072 ) * asin( c / a ); }", 297 | " if( t < 1.0 ) return -0.5 * ( a * pow( 2.0, 10.0 * ( t -= 1.0 ) ) * sin( ( t * d - s ) * ( 6.2831853072 ) / p ) ) + b;", 298 | " return a * pow( 2.0, -10.0 * ( t -= 1.0 ) ) * sin( ( t * d - s ) * ( 6.2831853072 ) / p ) * 0.5 + c + b;", 299 | "}", 300 | "", 301 | "float inOutElastic( float t )", 302 | "{", 303 | " float s = 1.70158; float p = 0; float a = 1.0;", 304 | " if( t == 0 ) return 0.0;", 305 | " if( ( t /= 0.5 ) == 2.0 ) return 1.0;", 306 | " p = ( 0.3 * 1.5 );", 307 | " s = p / ( 6.2831853072 ) * asin( 1.0 / a );", 308 | " if( t < 1.0 ) return -0.5 * ( a * pow( 2.0, 10.0 * ( t -= 1.0 ) ) * sin( ( t - s ) * ( 6.2831853072 ) / p ) );", 309 | " return a * pow( 2.0, -10.0 * ( t -= 1.0 ) ) * sin( ( t - s ) * ( 6.2831853072 ) / p ) * 0.5 + 1.0;", 310 | "}", 311 | "", 312 | "float inBack( float t, float b, float c, float d )", 313 | "{", 314 | " float s = 1.70158;", 315 | " return c * ( t /= d ) * t * ( ( s + 1.0 ) * t - s ) + b;", 316 | "}", 317 | "", 318 | "float inBack( float t )", 319 | "{", 320 | " float s = 1.70158;", 321 | " return t * t * ( ( s + 1.0 ) * t - s);", 322 | "}", 323 | "", 324 | "float outBack( float t, float b, float c, float d )", 325 | "{", 326 | " float s = 1.70158;", 327 | " return c * ( ( t = t / d - 1.0 ) * t * ( ( s + 1.0 ) * t + s ) + 1.0 ) + b;", 328 | "}", 329 | "", 330 | "float outBack( float t )", 331 | "{", 332 | " float s = 1.70158;", 333 | " return ( ( --t ) * t * ( ( s + 1.0 ) * t + s ) + 1.0);", 334 | "}", 335 | "", 336 | "float inOutBack( float t, float b, float c, float d )", 337 | "{", 338 | " float s = 1.70158;", 339 | " if( ( t /= d / 2.0 ) < 1.0 ) return c / 2.0 * ( t * t * ( ( ( s *= 1.525 ) + 1.0 ) * t - s ) ) + b;", 340 | " return c / 2.0 * ( ( t -= 2.0 ) * t * ( ( ( s *= ( 1.525 ) ) + 1.0 ) * t + s ) + 2.0 ) + b;", 341 | "}", 342 | "", 343 | "float inOutBack( float t )", 344 | "{", 345 | " float s = 1.70158;", 346 | " if( ( t /= 0.5 ) < 1.0 ) return 0.5 * ( t * t * ( ( ( s *= 1.525 ) + 1.0 ) * t - s ) );", 347 | " return 0.5 * ( ( t -= 2 ) * t * ( ( ( s *= ( 1.525 ) ) + 1.0 ) * t + s ) + 2.0 );", 348 | "}", 349 | "", 350 | "float outBounce( float t, float b, float c, float d )", 351 | "{", 352 | " if( ( t /= d ) < ( 1.0 / 2.75 ) ) {", 353 | " return c * ( 7.5625 * t * t ) + b;", 354 | " } else if ( t < ( 2.0 / 2.75 ) ) {", 355 | " return c * ( 7.5625 * ( t -= ( 1.5 / 2.75 ) ) * t + 0.75 ) + b;", 356 | " } else if ( t < ( 2.5 / 2.75 ) ) {", 357 | " return c * ( 7.5625 * ( t -= ( 2.25 / 2.75 ) ) * t + 0.9375 ) + b;", 358 | " } else {", 359 | " return c * ( 7.5625 * ( t -= ( 2.625 / 2.75 ) ) * t + 0.984375 ) + b;", 360 | " }", 361 | "}", 362 | "", 363 | "float outBounce( float t )", 364 | "{", 365 | " if( t < ( 1.0 / 2.75 ) ) {", 366 | " return ( 7.5625 * t * t );", 367 | " } else if ( t < ( 2.0 / 2.75 ) ) {", 368 | " return ( 7.5625 * ( t-= ( 1.5 / 2.75 ) ) * t + .75 );", 369 | " } else if ( t < ( 2.5 / 2.75 ) ) {", 370 | " return ( 7.5625 * ( t -= ( 2.25 / 2.75 ) ) * t + 0.9375 );", 371 | " } else {", 372 | " return ( 7.5625 * ( t -= ( 2.625 / 2.75 ) ) * t + 0.984375 );", 373 | " }", 374 | "}", 375 | "", 376 | "float inBounce( float t, float b, float c, float d )", 377 | "{", 378 | " return c - outBounce( d - t, 0.0, c, d ) + b;", 379 | "}", 380 | "", 381 | "float inBounce( float t )", 382 | "{", 383 | " return 1.0 - outBounce( 1.0 - t);", 384 | "}", 385 | "", 386 | "float inOutBounce( float t, float b, float c, float d )", 387 | "{", 388 | " if ( t < d /2.0 ) return inBounce ( t * 2.0, 0.0, c, d ) * 0.5 + b;", 389 | " return outBounce ( t * 2.0 - d, 0, c, d ) * 0.5 + c * 0.5 + b;", 390 | "}", 391 | "", 392 | "float inOutBounce( float t )", 393 | "{", 394 | " if ( t < 0.5 ) return inBounce( t * 2.0 ) * 0.5;", 395 | " return outBounce( t * 2.0 - 1.0 ) * 0.5 + 0.5;", 396 | "}" 397 | ].join("\n"); 398 | 399 | })( 400 | this.Blotter, this._ 401 | ); 402 | -------------------------------------------------------------------------------- /src/assets/shaders/gamma.js: -------------------------------------------------------------------------------- 1 | // See License comments in shader string 2 | 3 | (function(Blotter, _) { 4 | 5 | Blotter.Assets.Shaders.Gamma = [ 6 | "//", 7 | "// Author : Reza Ali", 8 | "// License : Distributed under the MIT License.", 9 | "//", 10 | "", 11 | "const vec3 cGammaCorrection = vec3( 0.4545454545 );", 12 | "", 13 | "vec3 gamma( in vec3 color )", 14 | "{", 15 | " return pow( color, cGammaCorrection );", 16 | "}", 17 | "", 18 | "vec4 gamma( in vec4 color )", 19 | "{", 20 | " return vec4( gamma( color.rgb ), color.a );", 21 | "}" 22 | ].join("\n"); 23 | 24 | })( 25 | this.Blotter, this._ 26 | ); 27 | -------------------------------------------------------------------------------- /src/assets/shaders/map.js: -------------------------------------------------------------------------------- 1 | // See License comments in shader string 2 | 3 | (function(Blotter, _) { 4 | 5 | Blotter.Assets.Shaders.Map = [ 6 | "//", 7 | "// Author : Reza Ali", 8 | "// License : Distributed under the MIT License.", 9 | "//", 10 | "", 11 | "float map( float value, float inMin, float inMax, float outMin, float outMax )", 12 | "{", 13 | " return ( (value - inMin) / ( inMax - inMin ) * ( outMax - outMin ) ) + outMin;", 14 | "}" 15 | ].join("\n"); 16 | 17 | })( 18 | this.Blotter, this._ 19 | ); 20 | -------------------------------------------------------------------------------- /src/assets/shaders/noise.js: -------------------------------------------------------------------------------- 1 | // See License comments in shader string 2 | 3 | (function(Blotter, _) { 4 | 5 | Blotter.Assets.Shaders.Noise = [ 6 | "//", 7 | "// Author : Patricio Gonzalez Vivo and Jen Lowe", 8 | "// License : Distributed under the MIT License.", 9 | "// Source : https://github.com/patriciogonzalezvivo/thebookofshaders", 10 | "//", 11 | "float random (in float _x) {", 12 | " return fract(sin(_x)*1e4);", 13 | "}", 14 | "", 15 | "float random (in vec2 co) {", 16 | " return fract(sin(dot(co.xy,vec2(12.9898,78.233)))*43758.5453);", 17 | "}", 18 | "", 19 | "float noise (in float _x) {", 20 | " float i = floor(_x);", 21 | " float f = fract(_x);", 22 | " float u = f * f * (3.0 - 2.0 * f);", 23 | " return mix(random(i), random(i + 1.0), u);", 24 | "}", 25 | "", 26 | "float noise (in vec2 _st) {", 27 | " vec2 i = floor(_st);", 28 | " vec2 f = fract(_st);", 29 | " // Four corners in 2D of a tile", 30 | " float a = random(i);", 31 | " float b = random(i + vec2(1.0, 0.0));", 32 | " float c = random(i + vec2(0.0, 1.0));", 33 | " float d = random(i + vec2(1.0, 1.0));", 34 | " vec2 u = f * f * (3.0 - 2.0 * f);", 35 | " return mix(a, b, u.x) + ", 36 | " (c - a)* u.y * (1.0 - u.x) + ", 37 | " (d - b) * u.x * u.y;", 38 | "}", 39 | ].join("\n"); 40 | 41 | })( 42 | this.Blotter, this._ 43 | ); 44 | -------------------------------------------------------------------------------- /src/assets/shaders/noise2D.js: -------------------------------------------------------------------------------- 1 | // See License comments in shader string 2 | 3 | (function(Blotter, _) { 4 | 5 | Blotter.Assets.Shaders.Noise2D = [ 6 | "//", 7 | "// Description : Array and textureless GLSL 2D simplex noise function.", 8 | "// Author : Ian McEwan, Ashima Arts.", 9 | "// Maintainer : ijm", 10 | "// Lastmod : 20110822 (ijm)", 11 | "// License : Copyright (C) 2011 Ashima Arts. All rights reserved.", 12 | "// Distributed under the MIT License. See LICENSE file.", 13 | "// https://github.com/ashima/webgl-noise", 14 | "//", 15 | "", 16 | "vec2 n2mod289(vec2 x) {", 17 | " return x - floor(x * (1.0 / 289.0)) * 289.0;", 18 | "}", 19 | "", 20 | "vec3 n2mod289(vec3 x) {", 21 | " return x - floor(x * (1.0 / 289.0)) * 289.0;", 22 | "}", 23 | "", 24 | "vec4 n2mod289(vec4 x) {", 25 | " return x - floor(x * (1.0 / 289.0)) * 289.0;", 26 | "}", 27 | "", 28 | "vec3 permute(vec3 x) {", 29 | " return n2mod289(((x*34.0)+1.0)*x);", 30 | "}", 31 | "", 32 | "float snoise(vec2 v)", 33 | " {", 34 | " const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0", 35 | " 0.366025403784439, // 0.5*(sqrt(3.0)-1.0)", 36 | " -0.577350269189626, // -1.0 + 2.0 * C.x", 37 | " 0.024390243902439); // 1.0 / 41.0", 38 | "// First corner", 39 | " vec2 i = floor(v + dot(v, C.yy) );", 40 | " vec2 x0 = v - i + dot(i, C.xx);", 41 | "", 42 | "// Other corners", 43 | " vec2 i1;", 44 | " //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0", 45 | " //i1.y = 1.0 - i1.x;", 46 | " i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);", 47 | " // x0 = x0 - 0.0 + 0.0 * C.xx ;", 48 | " // x1 = x0 - i1 + 1.0 * C.xx ;", 49 | " // x2 = x0 - 1.0 + 2.0 * C.xx ;", 50 | " vec4 x12 = x0.xyxy + C.xxzz;", 51 | " x12.xy -= i1;", 52 | "", 53 | "// Permutations", 54 | " i = n2mod289(i); // Avoid truncation effects in permutation", 55 | " vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))", 56 | " + i.x + vec3(0.0, i1.x, 1.0 ));", 57 | "", 58 | " vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);", 59 | " m = m*m ;", 60 | " m = m*m ;", 61 | "", 62 | "// Gradients: 41 points uniformly over a line, mapped onto a diamond.", 63 | "// The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)", 64 | "", 65 | " vec3 x = 2.0 * fract(p * C.www) - 1.0;", 66 | " vec3 h = abs(x) - 0.5;", 67 | " vec3 ox = floor(x + 0.5);", 68 | " vec3 a0 = x - ox;", 69 | "", 70 | "// Normalise gradients implicitly by scaling m", 71 | "// Approximation of: m *= inversesqrt( a0*a0 + h*h );", 72 | " m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );", 73 | "", 74 | "// Compute final noise value at P", 75 | " vec3 g;", 76 | " g.x = a0.x * x0.x + h.x * x0.y;", 77 | " g.yz = a0.yz * x12.xz + h.yz * x12.yw;", 78 | " return 130.0 * dot(m, g);", 79 | "}" 80 | ].join("\n"); 81 | 82 | })( 83 | this.Blotter, this._ 84 | ); 85 | -------------------------------------------------------------------------------- /src/assets/shaders/noise3D.js: -------------------------------------------------------------------------------- 1 | // See License comments in shader string 2 | 3 | (function(Blotter, _) { 4 | 5 | Blotter.Assets.Shaders.Noise3D = [ 6 | "//", 7 | "// Description : Array and textureless GLSL 2D/3D/4D simplex", 8 | "// noise functions.", 9 | "// Author : Ian McEwan, Ashima Arts.", 10 | "// Maintainer : ijm", 11 | "// Lastmod : 20110822 (ijm)", 12 | "// License : Copyright (C) 2011 Ashima Arts. All rights reserved.", 13 | "// Distributed under the MIT License. See LICENSE file.", 14 | "// https://github.com/ashima/webgl-noise", 15 | "//", 16 | "", 17 | "vec2 n3mod289(vec2 x) {", 18 | " return x - floor(x * (1.0 / 289.0)) * 289.0;", 19 | "}", 20 | "", 21 | "vec3 n3mod289(vec3 x) {", 22 | " return x - floor(x * (1.0 / 289.0)) * 289.0;", 23 | "}", 24 | "", 25 | "vec4 n3mod289(vec4 x) {", 26 | " return x - floor(x * (1.0 / 289.0)) * 289.0;", 27 | "}", 28 | "", 29 | "vec4 permute(vec4 x) {", 30 | " return n3mod289(((x*34.0)+1.0)*x);", 31 | "}", 32 | "", 33 | "vec4 taylorInvSqrt(vec4 r)", 34 | "{", 35 | " return 1.79284291400159 - 0.85373472095314 * r;", 36 | "}", 37 | "", 38 | "float snoise(vec3 v)", 39 | " {", 40 | " const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;", 41 | " const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);", 42 | "", 43 | "// First corner", 44 | " vec3 i = floor(v + dot(v, C.yyy) );", 45 | " vec3 x0 = v - i + dot(i, C.xxx) ;", 46 | "", 47 | "// Other corners", 48 | " vec3 g = step(x0.yzx, x0.xyz);", 49 | " vec3 l = 1.0 - g;", 50 | " vec3 i1 = min( g.xyz, l.zxy );", 51 | " vec3 i2 = max( g.xyz, l.zxy );", 52 | "", 53 | " // x0 = x0 - 0.0 + 0.0 * C.xxx;", 54 | " // x1 = x0 - i1 + 1.0 * C.xxx;", 55 | " // x2 = x0 - i2 + 2.0 * C.xxx;", 56 | " // x3 = x0 - 1.0 + 3.0 * C.xxx;", 57 | " vec3 x1 = x0 - i1 + C.xxx;", 58 | " vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y", 59 | " vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y", 60 | "", 61 | "// Permutations", 62 | " i = n3mod289(i);", 63 | " vec4 p = permute( permute( permute(", 64 | " i.z + vec4(0.0, i1.z, i2.z, 1.0 ))", 65 | " + i.y + vec4(0.0, i1.y, i2.y, 1.0 ))", 66 | " + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));", 67 | "", 68 | "// Gradients: 7x7 points over a square, mapped onto an octahedron.", 69 | "// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)", 70 | " float n_ = 0.142857142857; // 1.0/7.0", 71 | " vec3 ns = n_ * D.wyz - D.xzx;", 72 | "", 73 | " vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7)", 74 | "", 75 | " vec4 x_ = floor(j * ns.z);", 76 | " vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N)", 77 | "", 78 | " vec4 x = x_ *ns.x + ns.yyyy;", 79 | " vec4 y = y_ *ns.x + ns.yyyy;", 80 | " vec4 h = 1.0 - abs(x) - abs(y);", 81 | "", 82 | " vec4 b0 = vec4( x.xy, y.xy );", 83 | " vec4 b1 = vec4( x.zw, y.zw );", 84 | "", 85 | " //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;", 86 | " //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;", 87 | " vec4 s0 = floor(b0)*2.0 + 1.0;", 88 | " vec4 s1 = floor(b1)*2.0 + 1.0;", 89 | " vec4 sh = -step(h, vec4(0.0));", 90 | "", 91 | " vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;", 92 | " vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;", 93 | "", 94 | " vec3 p0 = vec3(a0.xy,h.x);", 95 | " vec3 p1 = vec3(a0.zw,h.y);", 96 | " vec3 p2 = vec3(a1.xy,h.z);", 97 | " vec3 p3 = vec3(a1.zw,h.w);", 98 | "", 99 | "//Normalise gradients", 100 | " vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));", 101 | " p0 *= norm.x;", 102 | " p1 *= norm.y;", 103 | " p2 *= norm.z;", 104 | " p3 *= norm.w;", 105 | "", 106 | "// Mix final noise value", 107 | " vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);", 108 | " m = m * m;", 109 | " return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),", 110 | " dot(p2,x2), dot(p3,x3) ) );", 111 | " }" 112 | ].join("\n"); 113 | 114 | })( 115 | this.Blotter, this._ 116 | ); 117 | -------------------------------------------------------------------------------- /src/assets/shaders/noise4D.js: -------------------------------------------------------------------------------- 1 | // See License comments in shader string 2 | 3 | (function(Blotter, _) { 4 | 5 | Blotter.Assets.Shaders.Noise4D = [ 6 | "//", 7 | "// Description : Array and textureless GLSL 2D/3D/4D simplex", 8 | "// noise functions.", 9 | "// Author : Ian McEwan, Ashima Arts.", 10 | "// Maintainer : ijm", 11 | "// Lastmod : 20110822 (ijm)", 12 | "// License : Copyright (C) 2011 Ashima Arts. All rights reserved.", 13 | "// Distributed under the MIT License. See LICENSE file.", 14 | "// https://github.com/ashima/webgl-noise", 15 | "//", 16 | "", 17 | "vec4 mod289(vec4 x) {", 18 | " return x - floor(x * (1.0 / 289.0)) * 289.0; }", 19 | "", 20 | "float mod289(float x) {", 21 | " return x - floor(x * (1.0 / 289.0)) * 289.0; }", 22 | "", 23 | "vec4 permute(vec4 x) {", 24 | " return mod289(((x*34.0)+1.0)*x);", 25 | "}", 26 | "", 27 | "float permute(float x) {", 28 | " return mod289(((x*34.0)+1.0)*x);", 29 | "}", 30 | "", 31 | "vec4 taylorInvSqrt(vec4 r)", 32 | "{", 33 | " return 1.79284291400159 - 0.85373472095314 * r;", 34 | "}", 35 | "", 36 | "float taylorInvSqrt(float r)", 37 | "{", 38 | " return 1.79284291400159 - 0.85373472095314 * r;", 39 | "}", 40 | "", 41 | "vec4 grad4(float j, vec4 ip)", 42 | " {", 43 | " const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0);", 44 | " vec4 p,s;", 45 | "", 46 | " p.xyz = floor( fract (vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0;", 47 | " p.w = 1.5 - dot(abs(p.xyz), ones.xyz);", 48 | " s = vec4(lessThan(p, vec4(0.0)));", 49 | " p.xyz = p.xyz + (s.xyz*2.0 - 1.0) * s.www;", 50 | "", 51 | " return p;", 52 | " }", 53 | "", 54 | "// (sqrt(5) - 1)/4 = F4, used once below", 55 | "#define F4 0.309016994374947451", 56 | "", 57 | "float snoise(vec4 v)", 58 | " {", 59 | " const vec4 C = vec4( 0.138196601125011, // (5 - sqrt(5))/20 G4", 60 | " 0.276393202250021, // 2 * G4", 61 | " 0.414589803375032, // 3 * G4", 62 | " -0.447213595499958); // -1 + 4 * G4", 63 | "", 64 | "// First corner", 65 | " vec4 i = floor(v + dot(v, vec4(F4)) );", 66 | " vec4 x0 = v - i + dot(i, C.xxxx);", 67 | "", 68 | "// Other corners", 69 | "", 70 | "// Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI)", 71 | " vec4 i0;", 72 | " vec3 isX = step( x0.yzw, x0.xxx );", 73 | " vec3 isYZ = step( x0.zww, x0.yyz );", 74 | "// i0.x = dot( isX, vec3( 1.0 ) );", 75 | " i0.x = isX.x + isX.y + isX.z;", 76 | " i0.yzw = 1.0 - isX;", 77 | "// i0.y += dot( isYZ.xy, vec2( 1.0 ) );", 78 | " i0.y += isYZ.x + isYZ.y;", 79 | " i0.zw += 1.0 - isYZ.xy;", 80 | " i0.z += isYZ.z;", 81 | " i0.w += 1.0 - isYZ.z;", 82 | "", 83 | " // i0 now contains the unique values 0,1,2,3 in each channel", 84 | " vec4 i3 = clamp( i0, 0.0, 1.0 );", 85 | " vec4 i2 = clamp( i0-1.0, 0.0, 1.0 );", 86 | " vec4 i1 = clamp( i0-2.0, 0.0, 1.0 );", 87 | "", 88 | " // x0 = x0 - 0.0 + 0.0 * C.xxxx", 89 | " // x1 = x0 - i1 + 1.0 * C.xxxx", 90 | " // x2 = x0 - i2 + 2.0 * C.xxxx", 91 | " // x3 = x0 - i3 + 3.0 * C.xxxx", 92 | " // x4 = x0 - 1.0 + 4.0 * C.xxxx", 93 | " vec4 x1 = x0 - i1 + C.xxxx;", 94 | " vec4 x2 = x0 - i2 + C.yyyy;", 95 | " vec4 x3 = x0 - i3 + C.zzzz;", 96 | " vec4 x4 = x0 + C.wwww;", 97 | "", 98 | "// Permutations", 99 | " i = mod289(i);", 100 | " float j0 = permute( permute( permute( permute(i.w) + i.z) + i.y) + i.x);", 101 | " vec4 j1 = permute( permute( permute( permute (", 102 | " i.w + vec4(i1.w, i2.w, i3.w, 1.0 ))", 103 | " + i.z + vec4(i1.z, i2.z, i3.z, 1.0 ))", 104 | " + i.y + vec4(i1.y, i2.y, i3.y, 1.0 ))", 105 | " + i.x + vec4(i1.x, i2.x, i3.x, 1.0 ));", 106 | "", 107 | "// Gradients: 7x7x6 points over a cube, mapped onto a 4-cross polytope", 108 | "// 7*7*6 = 294, which is close to the ring size 17*17 = 289.", 109 | " vec4 ip = vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ;", 110 | "", 111 | " vec4 p0 = grad4(j0, ip);", 112 | " vec4 p1 = grad4(j1.x, ip);", 113 | " vec4 p2 = grad4(j1.y, ip);", 114 | " vec4 p3 = grad4(j1.z, ip);", 115 | " vec4 p4 = grad4(j1.w, ip);", 116 | "", 117 | "// Normalise gradients", 118 | " vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));", 119 | " p0 *= norm.x;", 120 | " p1 *= norm.y;", 121 | " p2 *= norm.z;", 122 | " p3 *= norm.w;", 123 | " p4 *= taylorInvSqrt(dot(p4,p4));", 124 | "", 125 | "// Mix contributions from the five corners", 126 | " vec3 m0 = max(0.6 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2)), 0.0);", 127 | " vec2 m1 = max(0.6 - vec2(dot(x3,x3), dot(x4,x4) ), 0.0);", 128 | " m0 = m0 * m0;", 129 | " m1 = m1 * m1;", 130 | " return 49.0 * ( dot(m0*m0, vec3( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 )))", 131 | " + dot(m1*m1, vec2( dot( p3, x3 ), dot( p4, x4 ) ) ) ) ;", 132 | "", 133 | " }" 134 | ].join("\n"); 135 | 136 | })( 137 | this.Blotter, this._ 138 | ); 139 | -------------------------------------------------------------------------------- /src/assets/shaders/pi.js: -------------------------------------------------------------------------------- 1 | // See License comments in shader string 2 | 3 | (function(Blotter, _) { 4 | 5 | Blotter.Assets.Shaders.PI = [ 6 | "//", 7 | "// Author : Reza Ali", 8 | "// License : Distributed under the MIT License.", 9 | "//", 10 | "", 11 | "#define TWO_PI 6.2831853072", 12 | "#define PI 3.14159265359", 13 | "#define HALF_PI 1.57079632679" 14 | ].join("\n"); 15 | 16 | })( 17 | this.Blotter, this._ 18 | ); 19 | -------------------------------------------------------------------------------- /src/assets/shaders/random.js: -------------------------------------------------------------------------------- 1 | // See License comments in shader string 2 | 3 | (function(Blotter, _) { 4 | 5 | Blotter.Assets.Shaders.Random = [ 6 | "//", 7 | "// Author : Patricio Gonzalez Vivo and Jen Lowe", 8 | "// License : Distributed under the MIT License.", 9 | "// Source : https://github.com/patriciogonzalezvivo/thebookofshaders", 10 | "//", 11 | "", 12 | "float random (in float _x) {", 13 | " return fract(sin(_x)*1e4);", 14 | "}", 15 | "", 16 | "float random (in vec2 co) {", 17 | " return fract(sin(dot(co.xy,vec2(12.9898,78.233)))*43758.5453);", 18 | "}" 19 | ].join("\n"); 20 | 21 | })( 22 | this.Blotter, this._ 23 | ); 24 | -------------------------------------------------------------------------------- /src/blotter.js: -------------------------------------------------------------------------------- 1 | (function(previousBlotter, _, THREE, Detector, EventEmitter) { 2 | 3 | var root = this; 4 | 5 | var Blotter = root.Blotter = previousBlotter = function (material, options) { 6 | if (!Detector.webgl) { 7 | Blotter.Messaging.throwError("Blotter", false, "device does not support webgl"); 8 | } 9 | 10 | this._texts = []; 11 | this._textEventBindings = {}; 12 | 13 | this._scopes = {}; 14 | this._scopeEventBindings = {}; 15 | 16 | this._renderer = new Blotter.Renderer(); 17 | 18 | this._startTime = 0; 19 | this._lastDrawTime = 0; 20 | 21 | this.init.apply(this, arguments); 22 | }; 23 | 24 | Blotter.prototype = (function () { 25 | 26 | function _updateMaterialUniforms () { 27 | var now = Date.now(); 28 | 29 | this._material.uniforms.uTimeDelta.value = (now - (this._lastDrawTime || now)) / 1000; 30 | this._material.uniforms.uGlobalTime.value = (now - this._startTime) / 1000; 31 | 32 | this._lastDrawTime = now; 33 | } 34 | 35 | function _rendererRendered () { 36 | _updateMaterialUniforms.call(this); 37 | 38 | _.each(this._scopes, _.bind(function (scope) { 39 | if (scope.playing) { 40 | scope.render(); 41 | } 42 | this.trigger("render"); 43 | }, this)); 44 | } 45 | 46 | function _updateUniformValue (uniformName) { 47 | if (this.mappingMaterial) { 48 | var value = this._material.uniforms[uniformName].value; 49 | 50 | this.mappingMaterial.uniformInterface[uniformName].value = value; 51 | } 52 | } 53 | 54 | function _updateTextUniformValue (textId, uniformName) { 55 | if (this.mappingMaterial) { 56 | var scope = this._scopes[textId], 57 | value = scope.material.uniforms[uniformName].value; 58 | 59 | this.mappingMaterial.textUniformInterface[textId][uniformName].value = value; 60 | } 61 | } 62 | 63 | function _update () { 64 | var buildMapping, 65 | buildMappingMaterial, 66 | mappingMaterial, 67 | buildStages; 68 | 69 | buildMapping = _.bind(function () { 70 | return _.bind(function (next) { 71 | Blotter.MappingBuilder.build(this._texts, _.bind(function (newMapping) { 72 | this._mapping = newMapping; 73 | this._mapping.ratio = this.ratio; 74 | 75 | next(); 76 | }, this)); 77 | }, this); 78 | }, this); 79 | 80 | buildMappingMaterial = _.bind(function () { 81 | return _.bind(function (next) { 82 | Blotter.MappingMaterialBuilder.build(this._mapping, this._material, _.bind(function (newMappingMaterial) { 83 | this.mappingMaterial = newMappingMaterial; 84 | 85 | next(); 86 | }, this)); 87 | }, this); 88 | }, this); 89 | 90 | buildStages = [ 91 | buildMapping(), 92 | buildMappingMaterial() 93 | ]; 94 | 95 | _(buildStages).reduceRight(_.wrap, _.bind(function () { 96 | this._renderer.stop(); 97 | 98 | _.each(this._scopes, _.bind(function (scope, textId) { 99 | scope.mappingMaterial = this.mappingMaterial; 100 | scope.needsUpdate = true; 101 | }, this)); 102 | 103 | this._renderer.material = this.mappingMaterial.shaderMaterial; 104 | this._renderer.width = this._mapping.width; 105 | this._renderer.height = this._mapping.height; 106 | 107 | if (this.autostart) { 108 | this.start(); 109 | } 110 | 111 | this.trigger(this.lastUpdated ? "update" : "ready"); 112 | this.lastUpdated = Date.now(); 113 | }, this))(); 114 | } 115 | 116 | return { 117 | 118 | constructor : Blotter, 119 | 120 | get needsUpdate () { }, // jshint 121 | 122 | set needsUpdate (value) { 123 | if (value === true) { 124 | _update.call(this); 125 | } 126 | }, 127 | 128 | get material () { 129 | return this._material; 130 | }, 131 | 132 | set material (material) { 133 | this.setMaterial(material); 134 | }, 135 | 136 | get texts () { 137 | return this._texts; 138 | }, 139 | 140 | set texts (texts) { 141 | this.removeTexts(this._texts); 142 | this.addTexts(texts); 143 | }, 144 | 145 | get imageData () { 146 | return this._renderer.imageData; 147 | }, 148 | 149 | init : function (material, options) { 150 | options = options || {}; 151 | _.defaults(this, options, { 152 | ratio : Blotter.CanvasUtils.pixelRatio, 153 | autobuild : true, 154 | autostart : true, 155 | autoplay : true 156 | }); 157 | 158 | this.setMaterial(material); 159 | this.addTexts(options.texts); 160 | 161 | this._renderer.on("render", _.bind(_rendererRendered, this)); 162 | 163 | if (this.autobuild) { 164 | this.needsUpdate = true; 165 | } 166 | 167 | if (this.autostart) { 168 | this.start(); 169 | } 170 | }, 171 | 172 | start : function () { 173 | this.autostart = true; 174 | this._startTime = Date.now(); 175 | this._renderer.start(); 176 | }, 177 | 178 | stop : function () { 179 | this.autostart = false; 180 | this._renderer.stop(); 181 | }, 182 | 183 | teardown : function () { 184 | this._renderer.teardown(); 185 | }, 186 | 187 | setMaterial : function (material) { 188 | Blotter.Messaging.ensureInstanceOf(material, Blotter.Material, "Blotter.Material", "Blotter", "setMaterial"); 189 | 190 | this._material = material; 191 | 192 | if (this._materialEventBinding) { 193 | this._materialEventBinding.unsetEventCallbacks(); 194 | } 195 | 196 | this._materialEventBinding = new Blotter.ModelEventBinding(material, { 197 | update : _.bind(function () { 198 | _update.call(this); 199 | }, this), 200 | 201 | updateUniform : _.bind(function (uniformName) { 202 | _updateUniformValue.call(this, uniformName); 203 | }, this), 204 | }); 205 | material.on("update", this._materialEventBinding.eventCallbacks.update); 206 | material.on("update:uniform", this._materialEventBinding.eventCallbacks.updateUniform); 207 | }, 208 | 209 | addText : function (text) { 210 | this.addTexts(text); 211 | }, 212 | 213 | addTexts : function (texts) { 214 | var filteredTexts = Blotter.TextUtils.filterTexts(texts), 215 | newTexts = _.difference(filteredTexts, this._texts); 216 | 217 | _.each(newTexts, _.bind(function (text) { 218 | this._texts.push(text); 219 | 220 | this._textEventBindings[text.id] = new Blotter.ModelEventBinding(text, { 221 | update : _.bind(function () { 222 | _update.call(this); 223 | }, this) 224 | }); 225 | text.on("update", this._textEventBindings[text.id].eventCallbacks.update); 226 | 227 | this._scopes[text.id] = new Blotter.RenderScope(text, this); 228 | 229 | this._scopeEventBindings[text.id] = new Blotter.ModelEventBinding(this._scopes[text.id], { 230 | updateUniform : _.bind(function (uniformName) { 231 | _updateTextUniformValue.call(this, text.id, uniformName); 232 | }, this), 233 | }); 234 | this._scopes[text.id].on("update:uniform", this._scopeEventBindings[text.id].eventCallbacks.updateUniform); 235 | }, this)); 236 | }, 237 | 238 | removeText : function (text) { 239 | this.removeTexts(text); 240 | }, 241 | 242 | removeTexts : function (texts) { 243 | var filteredTexts = Blotter.TextUtils.filterTexts(texts), 244 | removedTexts = _.intersection(this._texts, filteredTexts); 245 | 246 | _.each(removedTexts, _.bind(function (text) { 247 | this._texts = _.without(this._texts, text); 248 | 249 | this._textEventBindings[text.id].unsetEventCallbacks(); 250 | this._scopeEventBindings[text.id].unsetEventCallbacks(); 251 | 252 | delete this._textEventBindings[text.id]; 253 | delete this._scopeEventBindings[text.id]; 254 | delete this._scopes[text.id]; 255 | }, this)); 256 | }, 257 | 258 | forText : function (text) { 259 | Blotter.Messaging.ensureInstanceOf(text, Blotter.Text, "Blotter.Text", "Blotter", "forText"); 260 | 261 | if (!(this._scopes[text.id])) { 262 | Blotter.Messaging.logError("Blotter", "forText", "Blotter.Text object not found in blotter"); 263 | return; 264 | } 265 | 266 | return this._scopes[text.id]; 267 | }, 268 | 269 | boundsForText : function (text) { 270 | Blotter.Messaging.ensureInstanceOf(text, Blotter.Text, "Blotter.Text", "Blotter", "boundsForText"); 271 | 272 | if (!(this._scopes[text.id])) { 273 | Blotter.Messaging.logError("Blotter", "boundsForText", "Blotter.Text object not found in blotter"); 274 | return; 275 | } 276 | 277 | if (this._mapping) { 278 | return this.mappingMaterial.boundsForText(text); 279 | } 280 | } 281 | }; 282 | })(); 283 | 284 | _.extend(Blotter.prototype, EventEmitter.prototype); 285 | 286 | Blotter.Version = "v0.1.0"; 287 | 288 | // Use a single webgl context regardless of number of blotter instances. 289 | Blotter.webglRenderer = Blotter.webglRenderer || new THREE.WebGLRenderer({ antialias: true, alpha: true, premultipliedAlpha : false }); 290 | 291 | Blotter.Assets = Blotter.Assets || {}; 292 | Blotter.Assets.Shaders = Blotter.Assets.Shaders || {}; 293 | 294 | })( 295 | this.Blotter, this._, this.THREE, this.Detector, this.EventEmitter 296 | ); 297 | -------------------------------------------------------------------------------- /src/builders/mappingBuilder.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _, THREE, GrowingPacker, setImmediate) { 2 | 3 | Blotter.MappingBuilder = (function () { 4 | 5 | // Sort texts based on area of space required for any given text, descending 6 | 7 | function _sortTexts (textA, textB) { 8 | var areaA = textA.w * textA.h, 9 | areaB = textB.w * textB.h; 10 | 11 | return areaB - areaA; 12 | } 13 | 14 | function _getTextSizes (texts) { 15 | return _.reduce(texts, function (textSizes, text) { 16 | var size = Blotter.TextUtils.sizeForText(text.value, text.properties); 17 | textSizes[text.id] = size; 18 | return textSizes; 19 | }, []); 20 | } 21 | 22 | return { 23 | 24 | build : function (texts, completion) { 25 | setImmediate(function() { 26 | var filteredTexts = Blotter.TextUtils.filterTexts(texts), 27 | textSizes = _getTextSizes(filteredTexts), 28 | packer = new GrowingPacker(), 29 | tempTextBounds = [], 30 | textBounds = {}, 31 | mapping; 32 | 33 | // Build array of objects holding a Text object's id, width, and height for sorting. 34 | for (var textId in textSizes) { 35 | if (textSizes.hasOwnProperty(textId)) { 36 | var tempSizesObject = textSizes[textId]; 37 | tempSizesObject.referenceId = textId; 38 | tempTextBounds.push(tempSizesObject); 39 | } 40 | } 41 | 42 | // Add fit object to all objects in tempTextBounds. 43 | packer.fit(tempTextBounds.sort(_sortTexts)); 44 | 45 | // Add fit objects back into this._textBounds for each Text id. 46 | for (var i = 0; i < tempTextBounds.length; i++) { 47 | var packedSizesObject = tempTextBounds[i]; 48 | textBounds[packedSizesObject.referenceId] = { 49 | w : packedSizesObject.w, 50 | h : packedSizesObject.h, 51 | x : packedSizesObject.fit.x, 52 | y : packer.root.h - (packedSizesObject.fit.y + packedSizesObject.h) 53 | }; 54 | } 55 | 56 | completion(new Blotter.Mapping(filteredTexts, textBounds, packer.root.w, packer.root.h)); 57 | }); 58 | } 59 | }; 60 | })(); 61 | 62 | })( 63 | this.Blotter, this._, this.THREE, this.GrowingPacker, this.setImmediate 64 | ); 65 | -------------------------------------------------------------------------------- /src/builders/mappingMaterialBuilder.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _, THREE) { 2 | 3 | Blotter.MappingMaterialBuilder = (function() { 4 | 5 | function _vertexSrc () { 6 | var vertexSrc = [ 7 | 8 | "varying vec2 _vTexCoord;", 9 | 10 | "void main() {", 11 | 12 | " _vTexCoord = uv;", 13 | " gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);", 14 | 15 | "}" 16 | 17 | ]; 18 | 19 | return vertexSrc.join("\n"); 20 | } 21 | 22 | function _fragmentSrc (userUniformDataTextureObjects, textsLength, mainImageSrc) { 23 | var fragmentSrc, 24 | userUniforms = { 25 | // Strings of uniform declarations for each publicly facing version of each user defined and default uniform. 26 | publicUniformDeclarations : "", 27 | // Strings of uniform definitions for each publicly facing version of each user defined and default uniform. 28 | publicUniformDefinitions : "" 29 | }, 30 | halfPixel = ((1 / userUniformDataTextureObjects.data.length) / 2).toFixed(20), 31 | userUniformsTextureWidth = (userUniformDataTextureObjects.texture.image.width).toFixed(1); 32 | 33 | _.reduce(userUniformDataTextureObjects.userUniforms, function (userUniforms, uniformObject, uniformName) { 34 | var glslSwizzle = Blotter.UniformUtils.fullSwizzleStringForUniformType(uniformObject.userUniform.type), 35 | glslDataType = Blotter.UniformUtils.glslDataTypeForUniformType(uniformObject.userUniform.type); 36 | 37 | // This is super convoluted. Sorry. All user uniforms are passed in a single texture, where for each uniform 38 | // there is a single rgba value per text. Within this texture data we need to be able to locate the position 39 | // of any given text's value for the given uniform. The maths here use the `textIndex` value, a value 40 | // sampled via the `indicesValueTexture`, to locate the text value for the given uniform, and then offsets that 41 | // value by half a texel in the overall _userUniformsTexture 42 | var uniformSamplePosition = "((" + (uniformObject.position).toFixed(1) + " + ((textIndex - ((1.0 / " + textsLength.toFixed(1) + ") / 2.0)) * " + textsLength.toFixed(1) + ")) / " + userUniformsTextureWidth + ") + " + halfPixel; 43 | 44 | userUniforms.publicUniformDeclarations += glslDataType + " " + uniformName + ";\n"; 45 | userUniforms.publicUniformDefinitions += " " + uniformName + " = texture2D(_userUniformsTexture, vec2(" + uniformSamplePosition + ", 0.5))." + glslSwizzle + ";\n"; 46 | 47 | return userUniforms; 48 | }, userUniforms); 49 | 50 | fragmentSrc = [ 51 | 52 | Blotter.Assets.Shaders.Blending, 53 | 54 | Blotter.Assets.Shaders.TextTexture, 55 | 56 | // Private blotter defined uniforms. 57 | "uniform sampler2D _uSampler;", 58 | "uniform vec2 _uCanvasResolution;", 59 | "uniform sampler2D _uTextIndicesTexture;", 60 | "uniform sampler2D _uTextBoundsTexture;", 61 | 62 | // Private texCoord and bounds information. 63 | "varying vec2 _vTexCoord;", 64 | "vec4 _textBounds;", 65 | 66 | // Private versions of user defined and default uniform declarations 67 | "uniform sampler2D _userUniformsTexture;", 68 | 69 | // Public versions of user defined and default uniform declarations 70 | userUniforms.publicUniformDeclarations, 71 | 72 | "// Helper function used by user programs to retrieve texel color information within the bounds of", 73 | "// any given text. This is to be used instead of `texture2D` in the fragment sources for all Blotter materials.", 74 | "vec4 textTexture(vec2 coord) {", 75 | " vec2 adjustedFragCoord = _textBounds.xy + vec2((_textBounds.z * coord.x), (_textBounds.w * coord.y));", 76 | " vec2 uv = adjustedFragCoord.xy / _uCanvasResolution;", 77 | 78 | " // If adjustedFragCoord falls outside the bounds of the current texel's text, return `vec4(0.0)`.", 79 | " if (adjustedFragCoord.x < _textBounds.x ||", 80 | " adjustedFragCoord.x > _textBounds.x + _textBounds.z ||", 81 | " adjustedFragCoord.y < _textBounds.y ||", 82 | " adjustedFragCoord.y > _textBounds.y + _textBounds.w) {", 83 | " return vec4(0.0);", 84 | " }", 85 | 86 | " return texture2D(_uSampler, uv);", 87 | "}", 88 | 89 | "void mainImage(out vec4 mainImage, in vec2 fragCoord);", 90 | 91 | mainImageSrc, 92 | 93 | "void main(void) {", 94 | 95 | // Retrieve text index and text alpha for text bounds in which texel is contained. 96 | " vec4 textIndexData = texture2D(_uTextIndicesTexture, _vTexCoord);", 97 | " float textIndex = textIndexData.r;", 98 | " float textAlpha = textIndexData.a;", 99 | 100 | // Make bounds for the current text globally visible. 101 | " _textBounds = texture2D(_uTextBoundsTexture, vec2(textIndex, 0.5));", 102 | 103 | // Set "uniform" values visible to user. 104 | userUniforms.publicUniformDefinitions, 105 | " uResolution = _textBounds.zw;", 106 | 107 | // Set fragment coordinate in respect to position within text bounds. 108 | " vec2 fragCoord = gl_FragCoord.xy - _textBounds.xy;", 109 | // Call user defined fragment function, setting outColor on return. 110 | " vec4 outColor;", 111 | " mainImage(outColor, fragCoord);", 112 | 113 | // Multiply alpha by original textIndexData's fourth value." 114 | // this will be 0 for texels not within any 'text' area." 115 | " outColor.a = outColor.a * textAlpha;", 116 | " gl_FragColor = outColor;", 117 | "}" 118 | 119 | ]; 120 | 121 | return fragmentSrc.join("\n"); 122 | } 123 | 124 | function _buildMappedTextsTexture (mapping, completion) { 125 | Blotter.TextTextureBuilder.build(mapping, function (texture) { 126 | completion(texture); 127 | }); 128 | } 129 | 130 | function _buildMappingDataTextureObjects (mapping, completion) { 131 | var buildIndicesTexture, 132 | buildBoundsTexture, 133 | mappingDataTextureObjects = [], 134 | buildStages; 135 | 136 | buildIndicesTexture = function () { 137 | return function (next) { 138 | Blotter.IndicesDataTextureBuilder.build(mapping, function (texture) { 139 | mappingDataTextureObjects.push({ 140 | uniformName : "_uTextIndicesTexture", 141 | texture : texture 142 | }); 143 | next(); 144 | }); 145 | }; 146 | }; 147 | 148 | buildBoundsTexture = function () { 149 | return function (next) { 150 | Blotter.BoundsDataTextureBuilder.build(mapping, function (texture) { 151 | mappingDataTextureObjects.push({ 152 | uniformName : "_uTextBoundsTexture", 153 | texture : texture 154 | }); 155 | next(); 156 | }); 157 | }; 158 | }; 159 | 160 | buildStages = [ 161 | buildIndicesTexture(), 162 | buildBoundsTexture() 163 | ]; 164 | 165 | _(buildStages).reduceRight(_.wrap, function () { 166 | completion(mappingDataTextureObjects); 167 | })(); 168 | } 169 | 170 | function _buildUserUniformDataTextureObjects (userUniforms, textsLength, completion) { 171 | Blotter.UniformUtils.ensureHasRequiredDefaultUniforms(userUniforms, 172 | "Blotter.MappingMaterialBuilder", 173 | "_buildUserUniformDataTextureObjects"); 174 | 175 | userUniforms = Blotter.UniformUtils.extractValidUniforms(userUniforms); 176 | 177 | var uniformsDataLength = Object.keys(userUniforms).length * textsLength; 178 | var data = new Float32Array(uniformsDataLength * 4); 179 | var texture = new THREE.DataTexture(data, uniformsDataLength, 1, THREE.RGBAFormat, THREE.FloatType); 180 | 181 | var userUniformDataTextureObjects = { 182 | data : data, 183 | texture : texture, 184 | userUniforms : {} 185 | }; 186 | 187 | _.reduce(userUniforms, function (memo, userUniform, uniformName) { 188 | var uniformPosition = Object.keys(userUniforms).indexOf(uniformName) * textsLength; 189 | 190 | memo.userUniforms[uniformName] = { 191 | userUniform : userUniform, 192 | position : uniformPosition 193 | }; 194 | 195 | return memo; 196 | }, userUniformDataTextureObjects); 197 | 198 | completion(userUniformDataTextureObjects); 199 | } 200 | 201 | function _getUniformsForMappingDataTextureObjects (mappingDataTextureObjects) { 202 | return _.reduce(mappingDataTextureObjects, function (memo, mappingDataTextureObject) { 203 | memo[mappingDataTextureObject.uniformName] = { 204 | type : "t", 205 | value : mappingDataTextureObject.texture 206 | }; 207 | return memo; 208 | }, {}); 209 | } 210 | 211 | function _getUniformsForUserUniformDataObjects (userUniformDataObjects) { 212 | return { 213 | _userUniformsTexture: { 214 | type : "t", 215 | value : userUniformDataObjects.texture 216 | } 217 | }; 218 | } 219 | 220 | function _getUniforms (width, height, mappedTextsTexture, mappingDataTextureObjects, userUniformDataTextureObjects) { 221 | var uniforms = { 222 | _uSampler : { type : "t", value : mappedTextsTexture }, 223 | _uCanvasResolution : { type : "2f", value : [width, height] } 224 | }; 225 | 226 | _.extend(uniforms, _getUniformsForMappingDataTextureObjects(mappingDataTextureObjects)); 227 | _.extend(uniforms, _getUniformsForUserUniformDataObjects(userUniformDataTextureObjects)); 228 | 229 | return uniforms; 230 | } 231 | 232 | function _getThreeMaterial (vertexSrc, fragmentSrc, uniforms) { 233 | var threeMaterial = new THREE.ShaderMaterial({ 234 | vertexShader : vertexSrc, 235 | fragmentShader : fragmentSrc, 236 | uniforms : uniforms 237 | }); 238 | 239 | threeMaterial.depthTest = false; 240 | threeMaterial.depthWrite = false; 241 | threeMaterial.premultipliedAlpha = false; 242 | 243 | return threeMaterial; 244 | } 245 | 246 | return { 247 | 248 | build : function (mapping, material, completion) { 249 | var buildMappedTextsTexture, 250 | buildMappingDataTextureObjects, 251 | buildUserUniformDataAndDataTextureObjects, 252 | mappedTextsTexture, 253 | mappingDataTextureObjects, 254 | userUniformDataAndDataTextureObjects, 255 | buildStages; 256 | 257 | buildMappedTextsTexture = function () { 258 | return function (next) { 259 | _buildMappedTextsTexture(mapping, function (texture) { 260 | mappedTextsTexture = texture; 261 | next(); 262 | }); 263 | }; 264 | }; 265 | 266 | buildMappingDataTextureObjects = function () { 267 | return function (next) { 268 | _buildMappingDataTextureObjects(mapping, function (objects) { 269 | mappingDataTextureObjects = objects; 270 | next(); 271 | }); 272 | }; 273 | }; 274 | 275 | buildUserUniformDataTextureObjects = function () { 276 | return function (next) { 277 | _buildUserUniformDataTextureObjects(material.uniforms, mapping.texts.length, function (objects) { 278 | userUniformDataTextureObjects = objects; 279 | next(); 280 | }); 281 | }; 282 | }; 283 | 284 | buildStages = [ 285 | buildMappedTextsTexture(), 286 | buildMappingDataTextureObjects(), 287 | buildUserUniformDataTextureObjects() 288 | ]; 289 | 290 | _(buildStages).reduceRight(_.wrap, function () { 291 | var uniforms = _getUniforms( 292 | mapping.width, 293 | mapping.height, 294 | mappedTextsTexture, 295 | mappingDataTextureObjects, 296 | userUniformDataTextureObjects 297 | ), 298 | userUniforms = _.omit(uniforms, "_uCanvasResolution", "_uSampler", "_uTextBoundsTexture", "_uTextIndicesTexture"), 299 | threeMaterial = _getThreeMaterial(_vertexSrc(), _fragmentSrc(userUniformDataTextureObjects, mapping.texts.length, material.mainImage), uniforms); 300 | 301 | completion(new Blotter.MappingMaterial(mapping, material, threeMaterial, userUniformDataTextureObjects)); 302 | })(); 303 | } 304 | }; 305 | })(); 306 | 307 | })( 308 | this.Blotter, this._, this.THREE 309 | ); 310 | -------------------------------------------------------------------------------- /src/builders/textures/boundsDataTextureBuilder.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _, THREE, setImmediate) { 2 | 3 | // Create a Data Texture holding the boundaries (x/y offset and w/h) that should be available to any given texel for any given text. 4 | 5 | Blotter.BoundsDataTextureBuilder = (function () { 6 | 7 | function _boundsDataForMapping (mapping) { 8 | var texts = mapping.texts, 9 | data = new Float32Array(texts.length * 4); 10 | 11 | for (var i = 0; i < texts.length; i++) { 12 | var text = texts[i], 13 | bounds = mapping.boundsForText(text); 14 | 15 | data[4*i] = bounds.x; // x 16 | data[4*i+1] = mapping.height - (bounds.y + bounds.h); // y 17 | data[4*i+2] = bounds.w; // w 18 | data[4*i+3] = bounds.h; // h 19 | } 20 | 21 | return data; 22 | } 23 | 24 | return { 25 | 26 | build : function (mapping, completion) { 27 | setImmediate(function() { 28 | var data = _boundsDataForMapping(mapping), 29 | texture = new THREE.DataTexture(data, mapping.texts.length, 1, THREE.RGBAFormat, THREE.FloatType); 30 | 31 | texture.needsUpdate = true; 32 | 33 | completion(texture); 34 | }); 35 | } 36 | }; 37 | })(); 38 | 39 | })( 40 | this.Blotter, this._, this.THREE, this.setImmediate 41 | ); 42 | -------------------------------------------------------------------------------- /src/builders/textures/indicesDataTextureBuilder.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _, THREE, setImmediate) { 2 | 3 | // Create a Data Texture the size of our text map wherein every texel holds the index of text whose boundaries contain the given texel's position. 4 | 5 | Blotter.IndicesDataTextureBuilder = (function () { 6 | 7 | function _indicesDataForMapping (mapping, width, height, sampleAccuracy) { 8 | 9 | var ratio = mapping.ratio, 10 | points = new Float32Array((height * width) * 4), 11 | widthStepModifier = width % 1, 12 | indicesOffset = (1 / mapping.texts.length) / 2; // Values stored in this texture will be sampled from the 'middle' of their texel position. 13 | 14 | for (var i = 1; i < points.length / 4; i++) { 15 | 16 | var y = Math.ceil(i / (width - widthStepModifier)), 17 | x = i - ((width - widthStepModifier) * (y - 1)), 18 | refIndex = 0.0, 19 | bg = 0.0, 20 | a = 0.0; 21 | 22 | for (var ki = 0; ki < mapping.texts.length; ki++) { 23 | var text = mapping.texts[ki], 24 | bounds = mapping.boundsForText(text), 25 | bW = (bounds.w / ratio) * sampleAccuracy, 26 | bH = (bounds.h / ratio) * sampleAccuracy, 27 | bX = (bounds.x / ratio) * sampleAccuracy, 28 | bY = (bounds.y / ratio) * sampleAccuracy; 29 | 30 | // If x and y are within the fit bounds of the text space within our mapped texts texture. 31 | if (y >= bY && 32 | y <= bY + bH && 33 | x >= bX && 34 | x <= bX + bW) { 35 | refIndex = (ki / mapping.texts.length) + indicesOffset; 36 | a = 1.0; 37 | break; 38 | } 39 | } 40 | 41 | var index = i - 1; 42 | 43 | points[4*index+0] = refIndex; 44 | points[4*index+1] = bg; 45 | points[4*index+2] = bg; 46 | points[4*index+3] = a; 47 | } 48 | return points; 49 | } 50 | 51 | return { 52 | 53 | build : function (mapping, completion) { 54 | // There is a negative coorelation between the sampleAccuracy value and 55 | // the speed at which texture generation happens. 56 | // However, the lower this value, the less sampleAccuracy you can expect 57 | // for indexing into uniforms for any given text. 58 | // Value must be between 0.0 and 1.0, and you are advised to keep it around 0.5. 59 | var sampleAccuracy = 0.5; 60 | 61 | setImmediate(function() { 62 | var width = (mapping.width / mapping.ratio) * sampleAccuracy, 63 | height = (mapping.height / mapping.ratio) * sampleAccuracy, 64 | data = _indicesDataForMapping(mapping, width, height, sampleAccuracy), 65 | texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat, THREE.FloatType); 66 | 67 | texture.flipY = true; 68 | texture.needsUpdate = true; 69 | 70 | completion(texture); 71 | }); 72 | } 73 | }; 74 | })(); 75 | 76 | })( 77 | this.Blotter, this._, this.THREE, this.setImmediate 78 | ); 79 | 80 | -------------------------------------------------------------------------------- /src/builders/textures/textTextureBuilder.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _, THREE) { 2 | 3 | Blotter.TextTextureBuilder = (function() { 4 | 5 | return { 6 | 7 | build : function (mapping, completion) { 8 | var loader = new THREE.TextureLoader(), 9 | url; 10 | 11 | mapping.toCanvas(_.bind(function(canvas) { 12 | url = canvas.toDataURL(); 13 | 14 | loader.load(url, _.bind(function(texture) { 15 | texture.minFilter = THREE.LinearFilter; 16 | texture.magFilter = THREE.LinearFilter; 17 | texture.generateMipmaps = true; 18 | texture.needsUpdate = true; 19 | 20 | completion(texture); 21 | }, this)); 22 | }, this)); 23 | } 24 | }; 25 | })(); 26 | 27 | })( 28 | this.Blotter, this._, this.THREE 29 | ); 30 | -------------------------------------------------------------------------------- /src/extras/canvasUtils.js: -------------------------------------------------------------------------------- 1 | (function(Blotter) { 2 | 3 | Blotter.CanvasUtils = { 4 | 5 | // Creates and returns a high a canvas 6 | 7 | canvas : function (w, h, options) { 8 | options = options || {}; 9 | var canvas = document.createElement("canvas"); 10 | 11 | canvas.className = options.class; 12 | canvas.innerHTML = options.html; 13 | 14 | canvas.width = w; 15 | canvas.height = h; 16 | 17 | return canvas; 18 | }, 19 | 20 | // Creates and returns a high DPI canvas based on a device specific pixel ratio 21 | 22 | hiDpiCanvas : function (w, h, ratio, options) { 23 | ratio = ratio || this.pixelRatio; 24 | options = options || {}; 25 | var canvas = document.createElement("canvas"); 26 | 27 | canvas.className = options.class; 28 | canvas.innerHTML = options.html; 29 | 30 | this.updateCanvasSize(canvas, w, h, ratio); 31 | 32 | return canvas; 33 | }, 34 | 35 | updateCanvasSize : function (canvas, w, h, ratio) { 36 | ratio = ratio || 1; 37 | 38 | canvas.width = w * ratio; 39 | canvas.height = h * ratio; 40 | canvas.style.width = w + "px"; 41 | canvas.style.height = h + "px"; 42 | canvas.getContext("2d").setTransform(ratio, 0, 0, ratio, 0, 0); 43 | }, 44 | 45 | // Returns the device's pixel ratio 46 | 47 | pixelRatio : (function () { 48 | var ctx = document.createElement("canvas").getContext("2d"), 49 | dpr = window.devicePixelRatio || 1, 50 | bsr = ctx.backingStorePixelRatio; 51 | 52 | for(var x = 0; x < Blotter.VendorPrefixes.length && !bsr; ++x) { 53 | bsr = ctx[Blotter.VendorPrefixes[x]+"BackingStorePixelRatio"]; 54 | } 55 | 56 | bsr = bsr || 1; 57 | 58 | return (dpr / bsr); 59 | })(), 60 | 61 | // Returns the mouse position within a canvas 62 | 63 | mousePosition : function (canvas, event) { 64 | var rect = canvas.getBoundingClientRect(); 65 | return { 66 | x: event.clientX - rect.left, 67 | y: event.clientY - rect.top 68 | }; 69 | }, 70 | 71 | // Returns the mouse position within a canvas, normalized to a value between 0 and 1 72 | 73 | normalizedMousePosition : function (canvas, event) { 74 | var rect = canvas.getBoundingClientRect(), 75 | position = this.mousePosition(canvas, event); 76 | 77 | return { 78 | x: position.x / rect.width, 79 | y: position.y / rect.height 80 | }; 81 | } 82 | }; 83 | 84 | })( 85 | this.Blotter 86 | ); 87 | -------------------------------------------------------------------------------- /src/extras/core/math.js: -------------------------------------------------------------------------------- 1 | (function(Blotter) { 2 | 3 | Blotter.Math = { 4 | generateUUID : (function () { 5 | // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136 6 | var lut = []; 7 | 8 | for ( var i = 0; i < 256; i ++ ) { 9 | lut[i] = ( i < 16 ? '0' : '' ) + (i).toString(16).toUpperCase(); 10 | } 11 | 12 | return function generateUUID() { 13 | var d0 = Math.random() * 0xffffffff | 0; 14 | var d1 = Math.random() * 0xffffffff | 0; 15 | var d2 = Math.random() * 0xffffffff | 0; 16 | var d3 = Math.random() * 0xffffffff | 0; 17 | return lut[d0 & 0xff] + lut[d0 >> 8 & 0xff] + lut[d0 >> 16 & 0xff] + lut[d0 >> 24 & 0xff] + '-' + 18 | lut[d1 & 0xff] + lut[d1 >> 8 & 0xff] + '-' + lut[d1 >> 16 & 0x0f | 0x40] + lut[d1 >> 24 & 0xff] + '-' + 19 | lut[d2 & 0x3f | 0x80] + lut[d2 >> 8 & 0xff] + '-' + lut[d2 >> 16 & 0xff] + lut[d2 >> 24 & 0xff] + 20 | lut[d3 & 0xff] + lut[d3 >> 8 & 0xff] + lut[d3 >> 16 & 0xff] + lut[d3 >> 24 & 0xff]; 21 | }; 22 | })() 23 | }; 24 | 25 | })( 26 | this.Blotter 27 | ); 28 | -------------------------------------------------------------------------------- /src/extras/core/messaging.js: -------------------------------------------------------------------------------- 1 | (function(Blotter) { 2 | 3 | Blotter.Messaging = (function () { 4 | 5 | function _formattedMessage (domain, method, message) { 6 | return domain + (method ? ("#" + method) : "") + ": " + message; 7 | } 8 | 9 | return { 10 | 11 | ensureInstanceOf : function (object, constructor, constructorStr, domain, method) { 12 | if (!(object instanceof constructor)) { 13 | this.logError(domain, method, "argument must be instanceof " + constructorStr); 14 | return; 15 | } 16 | }, 17 | 18 | logError : function (domain, method, message) { 19 | var formatted = _formattedMessage(domain, method, message); 20 | 21 | console.error(formatted); 22 | }, 23 | 24 | logWarning : function (domain, method, message) { 25 | var formatted = _formattedMessage(domain, method, message); 26 | 27 | console.warn(formatted); 28 | }, 29 | 30 | throwError : function (domain, method, message) { 31 | var formatted = _formattedMessage(domain, method, message); 32 | 33 | throw formatted; 34 | } 35 | }; 36 | })(); 37 | 38 | })( 39 | this.Blotter 40 | ); 41 | -------------------------------------------------------------------------------- /src/extras/core/overrides.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _) { 2 | 3 | Blotter._extendWithGettersSetters = function (obj) { 4 | _.each(Array.prototype.slice.call(arguments, 1), function (source) { 5 | if (source) { 6 | for (var prop in source) { 7 | if (obj[prop] && Object.getOwnPropertyDescriptor(obj, prop) && Object.getOwnPropertyDescriptor(obj, prop).set) { 8 | Object.getOwnPropertyDescriptor(obj, prop).set(source[prop]); 9 | } else { 10 | obj[prop] = source[prop]; 11 | } 12 | } 13 | } 14 | }); 15 | return obj; 16 | }; 17 | 18 | })( 19 | this.Blotter, this._ 20 | ); 21 | -------------------------------------------------------------------------------- /src/extras/core/vendorPrefixes.js: -------------------------------------------------------------------------------- 1 | (function(Blotter) { 2 | 3 | Blotter.VendorPrefixes = ["ms", "moz", "webkit", "o"]; 4 | 5 | })( 6 | this.Blotter 7 | ); 8 | -------------------------------------------------------------------------------- /src/extras/objects/modelEventBinding.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _) { 2 | 3 | Blotter.ModelEventBinding = function (model, eventCallbacks) { 4 | this.model = model; 5 | this.eventCallbacks = eventCallbacks || {}; 6 | }; 7 | 8 | Blotter.ModelEventBinding.prototype = { 9 | 10 | constructor : Blotter.ModelEventBinding, 11 | 12 | unsetEventCallbacks : function () { 13 | _.each(this.eventCallbacks, _.bind(function (callback, eventKey) { 14 | this.model.off(eventKey, callback); 15 | }, this)); 16 | } 17 | }; 18 | 19 | })( 20 | this.Blotter, this._ 21 | ); 22 | -------------------------------------------------------------------------------- /src/extras/textUtils.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _) { 2 | 3 | Blotter.PropertyDefaults = { 4 | family : 'sans-serif', 5 | size : 12, 6 | leading : 1.5, 7 | fill : '#000', 8 | style : 'normal', 9 | weight : 400, 10 | padding : 0, 11 | paddingTop : 0, 12 | paddingRight : 0, 13 | paddingBottom: 0, 14 | paddingLeft : 0 15 | }; 16 | 17 | Blotter.TextUtils = { 18 | 19 | Properties : _.keys(Blotter.PropertyDefaults), 20 | 21 | // Recieves property values (optional) and fills in any missing values with default values 22 | 23 | ensurePropertyValues : function(properties) { 24 | properties = _.defaults(properties || {}, Blotter.PropertyDefaults); 25 | return properties; 26 | }, 27 | 28 | filterTexts : function(texts) { 29 | if (texts instanceof Blotter.Text) { 30 | texts = [texts]; 31 | } else { 32 | texts = _.toArray(texts); 33 | } 34 | 35 | return _.filter(texts, _.bind(function (text) { 36 | var isText = text instanceof Blotter.Text; 37 | 38 | if (!isText) { 39 | Blotter.Messaging.logWarning("Blotter.TextUtils", "filterTexts", "object must be instance of Blotter.Text"); 40 | } 41 | 42 | return isText; 43 | }, this)); 44 | }, 45 | 46 | // Format padding values from style properties for passing to document 47 | 48 | stringifiedPadding : function(properties) { 49 | var _properties = properties || this.ensurePropertyValues(), 50 | pTop = properties.paddingTop || _properties.padding, 51 | pRight = _properties.paddingRight || _properties.padding, 52 | pBottom = _properties.paddingBottom || _properties.padding, 53 | pLeft = _properties.paddingLeft || _properties.padding; 54 | 55 | return pTop + "px " + pRight + "px " + pBottom + "px " + pLeft + "px"; 56 | }, 57 | 58 | // Determines size of text within the document given certain style properties 59 | 60 | sizeForText : function(textValue, properties) { 61 | // Using a here may not be the best approach. In theory a user's stylesheet 62 | // could override the necessary styling for determining sizes below. With growing 63 | // support for custom tags in html, we may consider using them if this raises problems. 64 | var el = document.createElement("span"), 65 | size; 66 | 67 | properties = this.ensurePropertyValues(properties); 68 | 69 | el.innerHTML = textValue; 70 | el.style.display = "inline-block"; 71 | el.style.fontFamily = properties.family; 72 | el.style.fontSize = properties.size + "px"; 73 | el.style.fontWeight = properties.weight; 74 | el.style.fontStyle = properties.style; 75 | el.style.lineHeight = properties.leading; 76 | el.style.maxWidth = "none"; 77 | el.style.padding = this.stringifiedPadding(properties); 78 | el.style.position = "absolute"; 79 | el.style.width = "auto"; 80 | el.style.visibility = "hidden"; 81 | 82 | 83 | document.body.appendChild(el); 84 | 85 | size = { 86 | w: el.offsetWidth, 87 | h: el.offsetHeight 88 | }; 89 | 90 | document.body.removeChild(el); 91 | 92 | return size; 93 | } 94 | }; 95 | 96 | })( 97 | this.Blotter, this._ 98 | ); 99 | -------------------------------------------------------------------------------- /src/extras/uniformUtils.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _) { 2 | 3 | Blotter.UniformUtils = { 4 | 5 | // Uniform type values we accept for public uniforms 6 | 7 | UniformTypes : ["1f", "2f", "3f", "4f"], 8 | 9 | // Default uniforms (required) provided to all materials 10 | 11 | defaultUniforms : { 12 | uResolution : { type : "2f", value : [0.0, 0.0] }, // Resolution of individual text areas within mapping texture 13 | uGlobalTime : { type : "1f", value : 0.0 }, // The global time in seconds 14 | uTimeDelta : { type : "1f", value : 0.0 }, // The render time in seconds 15 | uBlendColor : { type : "4f", value : [1.0, 1.0, 1.0, 1.0] }, 16 | uPixelRatio : { type : "1f", value : Blotter.CanvasUtils.pixelRatio } // The pixel ratio of the user's device 17 | }, 18 | 19 | // Determine if value is valid for public uniform type 20 | 21 | validValueForUniformType : function (type, value) { 22 | var valid = false, 23 | isValid = function (element) { 24 | return !isNaN(element); 25 | }; 26 | 27 | switch (type) { 28 | case "1f": 29 | valid = !isNaN(value) && [value].every(isValid); 30 | break; 31 | 32 | case "2f": 33 | valid = _.isArray(value) && value.length == 2 && value.every(isValid); 34 | break; 35 | 36 | case "3f": 37 | valid = _.isArray(value) && value.length == 3 && value.every(isValid); 38 | break; 39 | 40 | case "4f": 41 | valid = _.isArray(value) && value.length == 4 && value.every(isValid); 42 | break; 43 | 44 | default: 45 | break; 46 | } 47 | 48 | return valid; 49 | }, 50 | 51 | glslDataTypeForUniformType : function (type) { 52 | var dataType; 53 | switch (type) { 54 | case "1f": 55 | dataType = "float"; 56 | break; 57 | 58 | case "2f": 59 | dataType = "vec2"; 60 | break; 61 | 62 | case "3f": 63 | dataType = "vec3"; 64 | break; 65 | 66 | case "4f": 67 | dataType = "vec4"; 68 | break; 69 | 70 | default: 71 | break; 72 | } 73 | 74 | return dataType; 75 | }, 76 | 77 | fullSwizzleStringForUniformType : function (type) { 78 | var swizzleString; 79 | 80 | switch (type) { 81 | case "1f": 82 | swizzleString = "x"; 83 | break; 84 | 85 | case "2f": 86 | swizzleString = "xy"; 87 | break; 88 | 89 | case "3f": 90 | swizzleString = "xyz"; 91 | break; 92 | 93 | case "4f": 94 | swizzleString = "xyzw"; 95 | break; 96 | 97 | default: 98 | break; 99 | } 100 | 101 | return swizzleString; 102 | }, 103 | 104 | // Given an object containing uniform descriptions, return an object containing only valid uniforms based on the uniform's type and value 105 | 106 | extractValidUniforms : function (uniforms) { 107 | uniforms = uniforms || {}; 108 | return _.reduce(uniforms, function (memo, uniformDescription, uniformName) { 109 | if (Blotter.UniformUtils.UniformTypes.indexOf(uniformDescription.type) == -1) { 110 | Blotter.Messaging.logError("Blotter.UniformUtils", "extractValidUniforms", "uniforms must be one of type: " + 111 | Blotter.UniformUtils.UniformTypes.join(", ")); 112 | return memo; 113 | } 114 | 115 | if (!Blotter.UniformUtils.validValueForUniformType(uniformDescription.type, uniformDescription.value)) { 116 | Blotter.Messaging.logError("Blotter.UniformUtils", "extractValidUniforms", "uniform value for " + uniformName + " is incorrect for type: " + uniformDescription.type); 117 | return memo; 118 | } 119 | 120 | memo[uniformName] = _.pick(uniformDescription, "type", "value"); 121 | return memo; 122 | }, {}); 123 | }, 124 | 125 | ensureHasRequiredDefaultUniforms : function (uniforms, domain, method) { 126 | if (!(Blotter.UniformUtils.hasRequiredDefaultUniforms(uniforms))) { 127 | this.logError(domain, method, "uniforms object is missing required default uniforms defined in Blotter.UniformUtils.defaultUniforms"); 128 | return; 129 | } 130 | }, 131 | 132 | hasRequiredDefaultUniforms : function (uniforms) { 133 | var missingKeys = _.difference(_.allKeys(Blotter.UniformUtils.defaultUniforms), _.allKeys(uniforms)); 134 | 135 | return !!!missingKeys.length; 136 | } 137 | 138 | }; 139 | 140 | })( 141 | this.Blotter, this._ 142 | ); 143 | -------------------------------------------------------------------------------- /src/mapping/mapping.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _) { 2 | 3 | Blotter.Mapping = function (texts, textBounds, width, height) { 4 | this.texts = texts; 5 | 6 | this._textBounds = textBounds; 7 | 8 | this._width = width; 9 | this._height = height; 10 | 11 | this._ratio = 1; 12 | }; 13 | 14 | Blotter.Mapping.prototype = (function () { 15 | 16 | function _getLineHeightPixels (size, lineHeight) { 17 | lineHeight = lineHeight || Blotter.TextUtils.ensurePropertyValues().leading; 18 | if (!isNaN(lineHeight)) { 19 | lineHeight = size * lineHeight; 20 | } else if (lineHeight.toString().indexOf("px") !== -1) { 21 | lineHeight = parseInt(lineHeight); 22 | } else if (lineHeight.toString().indexOf("%") !== -1) { 23 | lineHeight = (parseInt(lineHeight) / 100) * size; 24 | } 25 | 26 | return lineHeight; 27 | } 28 | 29 | return { 30 | 31 | constructor : Blotter.Mapping, 32 | 33 | get ratio () { 34 | return this._ratio; 35 | }, 36 | 37 | set ratio (ratio) { 38 | this._ratio = ratio || 1; 39 | }, 40 | 41 | get width () { 42 | return this._width * this._ratio; 43 | }, 44 | 45 | get height () { 46 | return this._height * this._ratio; 47 | }, 48 | 49 | boundsForText : function (text) { 50 | Blotter.Messaging.ensureInstanceOf(text, Blotter.Text, "Blotter.Text", "Blotter.Mapping", "boundsForText"); 51 | 52 | var bounds = this._textBounds[text.id]; 53 | 54 | if (bounds) { 55 | bounds = { 56 | w : bounds.w * this._ratio, 57 | h : bounds.h * this._ratio, 58 | x : bounds.x * this._ratio, 59 | y : bounds.y * this._ratio 60 | }; 61 | } 62 | 63 | return bounds; 64 | }, 65 | 66 | toCanvas : function (completion) { 67 | var canvas = Blotter.CanvasUtils.hiDpiCanvas(this._width, this._height, this._ratio), 68 | ctx = canvas.getContext("2d", { alpha: false }), 69 | img = new Image(); 70 | 71 | ctx.textBaseline = "middle"; 72 | 73 | for (var i = 0; i < this.texts.length; i++) { 74 | var text = this.texts[i], 75 | bounds = this._textBounds[text.id], 76 | halfLH = (_getLineHeightPixels.call(this, text.properties.size, text.properties.leading) / 2); 77 | 78 | ctx.font = text.properties.style + 79 | " " + text.properties.weight + 80 | " " + text.properties.size + "px" + 81 | " " + text.properties.family; 82 | 83 | ctx.save(); 84 | 85 | ctx.translate( 86 | bounds.x + text.properties.paddingLeft, 87 | (this._height - (bounds.y + bounds.h)) + text.properties.paddingTop 88 | ); 89 | ctx.fillStyle = text.properties.fill; 90 | ctx.fillText( 91 | text.value, 92 | 0, 93 | Math.round(halfLH) 94 | ); 95 | 96 | ctx.restore(); 97 | } 98 | 99 | img.onload = _.bind(function () { 100 | // Flip Y for WebGL 101 | ctx.save(); 102 | ctx.scale(1, -1); 103 | ctx.clearRect(0, this._height * -1, this._width, this._height); 104 | ctx.drawImage(img, 0, this._height * -1, this._width, this._height); 105 | ctx.restore(); 106 | 107 | completion(canvas); 108 | }, this); 109 | 110 | img.src = canvas.toDataURL("image/png"); 111 | } 112 | }; 113 | })(); 114 | 115 | })( 116 | this.Blotter, this._ 117 | ); 118 | -------------------------------------------------------------------------------- /src/mapping/mappingMaterial.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _, EventEmitter) { 2 | 3 | Blotter.MappingMaterial = function(mapping, material, shaderMaterial, userUniformDataTextureObjects) { 4 | this.mapping = mapping; 5 | this.material = material; 6 | this.shaderMaterial = shaderMaterial; 7 | 8 | this._userUniformDataTextureObjects = userUniformDataTextureObjects; 9 | 10 | this.init.apply(this, arguments); 11 | }; 12 | 13 | Blotter.MappingMaterial.prototype = (function() { 14 | 15 | function _setValueAtIndexInDataTextureObject (value, i, data, userUniform) { 16 | var type = userUniform.type; 17 | 18 | if (type == "1f") { 19 | data[4*i] = value; // x (r) 20 | data[4*i+1] = 0.0; 21 | data[4*i+2] = 0.0; 22 | data[4*i+3] = 0.0; 23 | } else if (type == "2f") { 24 | data[4*i] = value[0]; // x (r) 25 | data[4*i+1] = value[1]; // y (g) 26 | data[4*i+2] = 0.0; 27 | data[4*i+3] = 0.0; 28 | } else if (type == "3f") { 29 | data[4*i] = value[0]; // x (r) 30 | data[4*i+1] = value[1]; // y (g) 31 | data[4*i+2] = value[2]; // z (b) 32 | data[4*i+3] = 0.0; 33 | } else if (type == "4f") { 34 | data[4*i] = value[0]; // x (r) 35 | data[4*i+1] = value[1]; // y (g) 36 | data[4*i+2] = value[2]; // z (b) 37 | data[4*i+3] = value[3]; // w (a) 38 | } else { 39 | data[4*i] = 0.0; 40 | data[4*i+1] = 0.0; 41 | data[4*i+2] = 0.0; 42 | data[4*i+3] = 0.0; 43 | } 44 | } 45 | 46 | function _getUniformInterfaceForDataTextureObject (dataTextureObject) { 47 | var interface = { 48 | _type : dataTextureObject.userUniform.type, 49 | _value : dataTextureObject.userUniform.value, 50 | 51 | get value () { 52 | return this._value; 53 | }, 54 | 55 | set value (v) { 56 | if (!Blotter.UniformUtils.validValueForUniformType(this._type, v)) { 57 | Blotter.Messaging.logError("Blotter.MappingMaterial", false, "uniform value not valid for uniform type: " + this._type); 58 | return; 59 | } 60 | this._value = v; 61 | 62 | this.trigger("update"); 63 | } 64 | }; 65 | 66 | _.extend(interface, EventEmitter.prototype); 67 | 68 | return interface; 69 | } 70 | 71 | function _getTextUniformInterface (mapping, userUniformDataTextureObjects) { 72 | return _.reduce(mapping.texts, function (memo, text, textIndex) { 73 | memo[text.id] = _.reduce(userUniformDataTextureObjects.userUniforms, function (memo, dataTextureObject, uniformName) { 74 | var uniformIndex = dataTextureObject.position + textIndex; 75 | 76 | memo[uniformName] = _getUniformInterfaceForDataTextureObject(dataTextureObject); 77 | 78 | memo[uniformName].on("update", function () { 79 | _setValueAtIndexInDataTextureObject( 80 | memo[uniformName].value, 81 | uniformIndex, 82 | userUniformDataTextureObjects.data, 83 | dataTextureObject.userUniform 84 | ); 85 | 86 | userUniformDataTextureObjects.texture.needsUpdate = true; 87 | }); 88 | 89 | memo[uniformName].value = dataTextureObject.userUniform.value; 90 | 91 | return memo; 92 | }, {}); 93 | 94 | return memo; 95 | }, {}); 96 | } 97 | 98 | function _getUniformInterface (mapping, userUniformDataTextureObjects, textUniformInterface) { 99 | return _.reduce(userUniformDataTextureObjects.userUniforms, function (memo, dataTextureObject, uniformName) { 100 | memo[uniformName] = _getUniformInterfaceForDataTextureObject(dataTextureObject); 101 | 102 | memo[uniformName].on("update", function () { 103 | _.each(mapping.texts, function (text) { 104 | textUniformInterface[text.id][uniformName].value = memo[uniformName].value; 105 | }); 106 | 107 | userUniformDataTextureObjects.texture.needsUpdate = true; 108 | }); 109 | 110 | return memo; 111 | }, {}); 112 | } 113 | 114 | return { 115 | 116 | constructor : Blotter.MappingMaterial, 117 | 118 | get uniforms () { 119 | return this.material.uniforms; 120 | }, 121 | 122 | get mainImage () { 123 | return this.material.mainImage; 124 | }, 125 | 126 | get width () { 127 | return this.mapping.width; 128 | }, 129 | 130 | get height () { 131 | return this.mapping.height; 132 | }, 133 | 134 | get ratio () { 135 | return this.mapping.ratio; 136 | }, 137 | 138 | init : function (mapping, material, shaderMaterial, userUniformDataTextureObjects) { 139 | this.textUniformInterface = _getTextUniformInterface(this.mapping, this._userUniformDataTextureObjects); 140 | this.uniformInterface = _getUniformInterface(this.mapping, this._userUniformDataTextureObjects, this.textUniformInterface); 141 | }, 142 | 143 | boundsForText : function (text) { 144 | Blotter.Messaging.ensureInstanceOf(text, Blotter.Text, "Blotter.Text", "Blotter.MappingMaterial", "boundsForText"); 145 | return this.mapping.boundsForText(text); 146 | } 147 | }; 148 | })(); 149 | 150 | })( 151 | this.Blotter, this._, this.EventEmitter 152 | ); 153 | -------------------------------------------------------------------------------- /src/materials/material.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _, EventEmitter) { 2 | 3 | Blotter.Material = function () { 4 | this.init.apply(this, arguments); 5 | }; 6 | 7 | Blotter.Material.prototype = (function() { 8 | 9 | function _defaultMainImageSrc () { 10 | var mainImage = [ 11 | 12 | "void mainImage( out vec4 mainImage, in vec2 fragCoord ) {", 13 | 14 | "mainImage = textTexture(fragCoord / uResolution);", 15 | 16 | "}" 17 | 18 | ]; 19 | 20 | return mainImage.join("\n"); 21 | } 22 | 23 | function _getUniformInterfaceForUniformDescription (uniformDescription) { 24 | var interface = { 25 | _type : uniformDescription.type, 26 | _value : uniformDescription.value, 27 | 28 | get type () { 29 | return this._type; 30 | }, 31 | 32 | set type (v) { 33 | this._type = v; 34 | }, 35 | 36 | get value () { 37 | return this._value; 38 | }, 39 | 40 | set value (v) { 41 | if (!Blotter.UniformUtils.validValueForUniformType(this._type, v)) { 42 | Blotter.Messaging.logError("Blotter.Material", false, "uniform value not valid for uniform type: " + this._type); 43 | return; 44 | } 45 | this._value = v; 46 | 47 | this.trigger("update"); 48 | } 49 | }; 50 | 51 | _.extend(interface, EventEmitter.prototype); 52 | 53 | return interface; 54 | } 55 | 56 | function _getUniformInterface (uniforms) { 57 | return _.reduce(uniforms, _.bind(function (memo, uniformDescription, uniformName) { 58 | memo[uniformName] = _getUniformInterfaceForUniformDescription(uniformDescription); 59 | memo[uniformName].on("update", _.bind(function () { 60 | this.trigger("update:uniform", [uniformName]); 61 | }, this)); 62 | 63 | return memo; 64 | }, this), {}); 65 | } 66 | 67 | return { 68 | 69 | constructor : Blotter.Material, 70 | 71 | get needsUpdate () { }, // jshint 72 | 73 | set needsUpdate (value) { 74 | if (value === true) { 75 | this.trigger("update"); 76 | } 77 | }, 78 | 79 | get mainImage () { 80 | return this._mainImage; 81 | }, 82 | 83 | set mainImage (mainImage) { 84 | this._mainImage = mainImage || _defaultMainImageSrc(); 85 | }, 86 | 87 | get uniforms () { 88 | return this._uniforms; 89 | }, 90 | 91 | set uniforms (uniforms) { 92 | this._uniforms = _getUniformInterface.call(this, Blotter.UniformUtils.extractValidUniforms( 93 | _.extend(uniforms, Blotter.UniformUtils.defaultUniforms) 94 | )); 95 | }, 96 | 97 | init : function () { 98 | this.mainImage = _defaultMainImageSrc(); 99 | this.uniforms = {}; 100 | } 101 | }; 102 | })(); 103 | 104 | Blotter._extendWithGettersSetters(Blotter.Material.prototype, EventEmitter.prototype); 105 | 106 | })( 107 | this.Blotter, this._, this.EventEmitter 108 | ); 109 | -------------------------------------------------------------------------------- /src/materials/shaderMaterial.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _) { 2 | 3 | Blotter.ShaderMaterial = function(mainImage, options) { 4 | Blotter.Material.apply(this, arguments); 5 | }; 6 | 7 | Blotter.ShaderMaterial.prototype = Object.create(Blotter.Material.prototype); 8 | 9 | Blotter._extendWithGettersSetters(Blotter.ShaderMaterial.prototype, (function () { 10 | 11 | return { 12 | 13 | constructor : Blotter.ShaderMaterial, 14 | 15 | init : function (mainImage, options) { 16 | _.defaults(this, options); 17 | 18 | this.mainImage = mainImage; 19 | } 20 | }; 21 | 22 | })()); 23 | 24 | })( 25 | this.Blotter, this._ 26 | ); 27 | -------------------------------------------------------------------------------- /src/rendering/renderScope.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _, EventEmitter) { 2 | 3 | Blotter.RenderScope = function (text, blotter) { 4 | this.text = text; 5 | this.blotter = blotter; 6 | 7 | this.material = { 8 | mainImage : this.blotter.material.mainImage 9 | }; 10 | 11 | this._mappingMaterial = blotter.mappingMaterial; 12 | 13 | this.playing = this.blotter.autoplay; 14 | this.timeDelta = 0; 15 | this.lastDrawTime = false; 16 | this.frameCount = 0; 17 | 18 | this.domElement = Blotter.CanvasUtils.hiDpiCanvas(0, 0, this.blotter.ratio, { 19 | class : "b-canvas", 20 | html : text.value 21 | }); 22 | 23 | this.context = this.domElement.getContext("2d"); 24 | }; 25 | 26 | Blotter.RenderScope.prototype = (function () { 27 | 28 | function _setMouseEventListeners () { 29 | var self = this, 30 | eventNames = ["mousedown", "mouseup", "mousemove", "mouseenter", "mouseleave"]; 31 | 32 | function setMouseListener (eventName) { 33 | self.domElement.addEventListener(eventName, function(e) { 34 | var position = Blotter.CanvasUtils.normalizedMousePosition(self.domElement, e); 35 | 36 | self.emit(eventName, position); 37 | }, false); 38 | } 39 | 40 | for (var i = 0; i < eventNames.length; i++) { 41 | var eventName = eventNames[i]; 42 | 43 | setMouseListener(eventName); 44 | } 45 | } 46 | 47 | function _getBoundsForMappingMaterialAndText (mappingMaterial, text) { 48 | var bounds = mappingMaterial.boundsForText(text); 49 | 50 | if (bounds) { 51 | return { 52 | w : bounds.w, 53 | h : bounds.h, 54 | x : -1 * Math.floor(bounds.x), 55 | y : -1 * Math.floor(mappingMaterial.height - (bounds.y + bounds.h)) 56 | }; 57 | } 58 | } 59 | 60 | function _transferInferfaceValues (oldInterface, newInterface) { 61 | _.each(oldInterface, function(interfaceObject, uniformName) { 62 | var newInterfaceObject = newInterface[uniformName]; 63 | 64 | if (newInterfaceObject) { 65 | var typesMatch = newInterfaceObject.type == interfaceObject.type, 66 | valuesMatch = newInterfaceObject.value == interfaceObject.value; 67 | 68 | if (typesMatch && !valuesMatch) { 69 | newInterfaceObject.value = interfaceObject.value; 70 | } 71 | } 72 | }); 73 | } 74 | 75 | function _getUniformInterfaceForUniformDescription (uniformDescription) { 76 | var interface = { 77 | _type : uniformDescription.type, 78 | _value : uniformDescription.value, 79 | 80 | get type () { 81 | return this._type; 82 | }, 83 | 84 | set type (v) { 85 | Blotter.Messaging.logError("Blotter.RenderScope", false, "uniform types may not be updated through a text scope"); 86 | }, 87 | 88 | get value () { 89 | return this._value; 90 | }, 91 | 92 | set value (v) { 93 | if (!Blotter.UniformUtils.validValueForUniformType(this._type, v)) { 94 | Blotter.Messaging.logError("Blotter.RenderScope", false, "uniform value not valid for uniform type: " + this._type); 95 | return; 96 | } 97 | this._value = v; 98 | 99 | this.trigger("update"); 100 | } 101 | }; 102 | 103 | _.extend(interface, EventEmitter.prototype); 104 | 105 | return interface; 106 | } 107 | 108 | function _getUniformInterfaceForMaterialUniforms (uniforms) { 109 | return _.reduce(uniforms, _.bind(function (memo, uniformDescription, uniformName) { 110 | memo[uniformName] = _getUniformInterfaceForUniformDescription(uniformDescription); 111 | memo[uniformName].on("update", _.bind(function () { 112 | this.trigger("update:uniform", [uniformName]); 113 | }, this)); 114 | 115 | return memo; 116 | }, this), {}); 117 | } 118 | 119 | function _update () { 120 | var mappingMaterial = this._mappingMaterial, 121 | bounds = mappingMaterial && _getBoundsForMappingMaterialAndText(mappingMaterial, this.text), 122 | previousUniforms = this.material.uniforms; 123 | 124 | if (mappingMaterial && bounds) { 125 | Blotter.CanvasUtils.updateCanvasSize( 126 | this.domElement, 127 | bounds.w / this.blotter.ratio, 128 | bounds.h / this.blotter.ratio, 129 | this.blotter.ratio 130 | ); 131 | this.domElement.innerHTML = this.text.value; 132 | 133 | this.bounds = bounds; 134 | 135 | this.material.uniforms = _getUniformInterfaceForMaterialUniforms.call(this, mappingMaterial.uniforms); 136 | this.material.mainImage = mappingMaterial.mainImage; 137 | 138 | if (previousUniforms) { 139 | _transferInferfaceValues(previousUniforms, this.material.uniforms); 140 | } 141 | 142 | this.trigger(this.lastUpdated ? "update" : "ready"); 143 | this.lastUpdated = Date.now(); 144 | } 145 | } 146 | 147 | return { 148 | 149 | constructor : Blotter.RenderScope, 150 | 151 | get needsUpdate () { }, // jshint 152 | 153 | set needsUpdate (value) { 154 | if (value === true) { 155 | _update.call(this); 156 | } 157 | }, 158 | 159 | get mappingMaterial () { }, 160 | 161 | set mappingMaterial (mappingMaterial) { 162 | this._mappingMaterial = mappingMaterial; 163 | }, 164 | 165 | play : function () { 166 | this.playing = true; 167 | }, 168 | 169 | pause : function () { 170 | this.playing = false; 171 | }, 172 | 173 | render : function () { 174 | if (this.bounds) { 175 | var now = Date.now(); 176 | 177 | this.frameCount += 1; 178 | this.timeDelta = (now - (this.lastDrawTime || now)) / 1000; 179 | this.lastDrawTime = now; 180 | 181 | this.context.clearRect(0, 0, this.domElement.width, this.domElement.height); 182 | 183 | this.context.putImageData( 184 | this.blotter.imageData, 185 | this.bounds.x, 186 | this.bounds.y 187 | ); 188 | 189 | this.trigger("render", [this.frameCount]); 190 | } 191 | }, 192 | 193 | appendTo : function (element) { 194 | if (typeof element.append === "function") { 195 | element.append(this.domElement); 196 | } else { 197 | element.appendChild(this.domElement); 198 | } 199 | 200 | _setMouseEventListeners.call(this); 201 | 202 | return this; 203 | } 204 | }; 205 | })(); 206 | 207 | Blotter._extendWithGettersSetters(Blotter.RenderScope.prototype, EventEmitter.prototype); 208 | 209 | })( 210 | this.Blotter, this._, this.EventEmitter 211 | ); 212 | -------------------------------------------------------------------------------- /src/rendering/renderer.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _, THREE, EventEmitter) { 2 | 3 | var root = this; 4 | 5 | Blotter.Renderer = function () { 6 | this._currentAnimationLoop = false; 7 | 8 | this._scene = new THREE.Scene(); 9 | 10 | this._plane = new THREE.PlaneGeometry(1, 1); 11 | 12 | this._material = new THREE.MeshBasicMaterial(); // Stub material. 13 | 14 | this._mesh = new THREE.Mesh(this._plane, this._material); 15 | this._scene.add(this._mesh); 16 | 17 | this._camera = new THREE.OrthographicCamera(0.5, 0.5, 0.5, 0.5, 0, 100); 18 | 19 | this.init.apply(this, arguments); 20 | }; 21 | 22 | Blotter.Renderer.prototype = (function () { 23 | 24 | function _getRenderTargetWithSize (width, height) { 25 | var renderTarget = new THREE.WebGLRenderTarget(width, height, { 26 | minFilter: THREE.LinearFilter, 27 | magFilter: THREE.LinearFilter, 28 | format: THREE.RGBAFormat, 29 | type: THREE.UnsignedByteType 30 | }); 31 | 32 | renderTarget.texture.generateMipmaps = false; 33 | renderTarget.width = width; 34 | renderTarget.height = height; 35 | 36 | return renderTarget; 37 | } 38 | 39 | function _loop () { 40 | Blotter.webglRenderer.render(this._scene, this._camera, this._renderTarget); 41 | 42 | Blotter.webglRenderer.readRenderTargetPixels( 43 | this._renderTarget, 44 | 0, 45 | 0, 46 | this._renderTarget.width, 47 | this._renderTarget.height, 48 | this._imageDataArray 49 | ); 50 | 51 | this.trigger("render"); 52 | 53 | this._currentAnimationLoop = root.requestAnimationFrame(_.bind(_loop, this)); 54 | } 55 | 56 | return { 57 | 58 | constructor : Blotter.Renderer, 59 | 60 | get material () { }, // jshint 61 | 62 | set material (material) { 63 | if (material instanceof THREE.Material) { 64 | this._material = material; 65 | this._mesh.material = material; 66 | } 67 | }, 68 | 69 | get width () { 70 | return this._width; 71 | }, 72 | 73 | set width (width) { 74 | this.setSize(width, this._height); 75 | }, 76 | 77 | get height () { 78 | return this._height; 79 | }, 80 | 81 | set height (height) { 82 | this.setSize(this._width, height); 83 | }, 84 | 85 | init : function () { 86 | this.setSize(1, 1); 87 | }, 88 | 89 | start : function () { 90 | if (!this._currentAnimationLoop) { 91 | _loop.call(this); 92 | } 93 | }, 94 | 95 | stop : function () { 96 | if (this._currentAnimationLoop) { 97 | root.cancelAnimationFrame(this._currentAnimationLoop); 98 | this._currentAnimationLoop = undefined; 99 | } 100 | }, 101 | 102 | setSize : function (width, height) { 103 | this._width = Math.trunc(width) || 1; 104 | this._height = Math.trunc(height) || 1; 105 | 106 | this._mesh.scale.set(this._width, this._height, 1); 107 | 108 | this._camera.left = this._width / - 2; 109 | this._camera.right = this._width / 2; 110 | this._camera.top = this._height / 2; 111 | this._camera.bottom = this._height / - 2; 112 | this._camera.updateProjectionMatrix(); 113 | 114 | this._renderTarget = _getRenderTargetWithSize(this._width, this._height); 115 | 116 | this._viewBuffer = new ArrayBuffer(this._width * this._height * 4); 117 | this._imageDataArray = new Uint8Array(this._viewBuffer); 118 | this._clampedImageDataArray = new Uint8ClampedArray(this._viewBuffer); 119 | 120 | this.imageData = new ImageData(this._clampedImageDataArray, this._width, this._height); 121 | }, 122 | 123 | teardown : function () { 124 | this.stop(); 125 | } 126 | }; 127 | })(); 128 | 129 | Blotter._extendWithGettersSetters(Blotter.Renderer.prototype, EventEmitter.prototype); 130 | 131 | })( 132 | this.Blotter, this._, this.THREE, this.EventEmitter 133 | ); 134 | -------------------------------------------------------------------------------- /src/texts/text.js: -------------------------------------------------------------------------------- 1 | (function(Blotter, _, THREE, EventEmitter) { 2 | 3 | Blotter.Text = function (value, properties) { 4 | this.id = Blotter.Math.generateUUID(); 5 | this.value = value; 6 | this.properties = properties; 7 | }; 8 | 9 | Blotter.Text.prototype = { 10 | constructor : Blotter.Text, 11 | 12 | get needsUpdate () { }, // jshint 13 | 14 | set needsUpdate (value) { 15 | if (value === true) { 16 | this.trigger("update"); 17 | } 18 | }, 19 | 20 | get properties () { 21 | return this._properties; 22 | }, 23 | 24 | set properties (properties) { 25 | this._properties = Blotter.TextUtils.ensurePropertyValues(properties); 26 | } 27 | }; 28 | 29 | Blotter._extendWithGettersSetters(Blotter.Text.prototype, EventEmitter.prototype); 30 | 31 | })( 32 | this.Blotter, this._, this.THREE, this.EventEmitter 33 | ); 34 | -------------------------------------------------------------------------------- /third_party/bin-packing/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 Jake Gordon and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /third_party/bin-packing/packer.growing.js: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | 3 | This is a binary tree based bin packing algorithm that is more complex than 4 | the simple Packer (packer.js). Instead of starting off with a fixed width and 5 | height, it starts with the width and height of the first block passed and then 6 | grows as necessary to accomodate each subsequent block. As it grows it attempts 7 | to maintain a roughly square ratio by making 'smart' choices about whether to 8 | grow right or down. 9 | 10 | When growing, the algorithm can only grow to the right OR down. Therefore, if 11 | the new block is BOTH wider and taller than the current target then it will be 12 | rejected. This makes it very important to initialize with a sensible starting 13 | width and height. If you are providing sorted input (largest first) then this 14 | will not be an issue. 15 | 16 | A potential way to solve this limitation would be to allow growth in BOTH 17 | directions at once, but this requires maintaining a more complex tree 18 | with 3 children (down, right and center) and that complexity can be avoided 19 | by simply chosing a sensible starting block. 20 | 21 | Best results occur when the input blocks are sorted by height, or even better 22 | when sorted by max(width,height). 23 | 24 | Inputs: 25 | ------ 26 | 27 | blocks: array of any objects that have .w and .h attributes 28 | 29 | Outputs: 30 | ------- 31 | 32 | marks each block that fits with a .fit attribute pointing to a 33 | node with .x and .y coordinates 34 | 35 | Example: 36 | ------- 37 | 38 | var blocks = [ 39 | { w: 100, h: 100 }, 40 | { w: 100, h: 100 }, 41 | { w: 80, h: 80 }, 42 | { w: 80, h: 80 }, 43 | etc 44 | etc 45 | ]; 46 | 47 | var packer = new GrowingPacker(); 48 | packer.fit(blocks); 49 | 50 | for(var n = 0 ; n < blocks.length ; n++) { 51 | var block = blocks[n]; 52 | if (block.fit) { 53 | Draw(block.fit.x, block.fit.y, block.w, block.h); 54 | } 55 | } 56 | 57 | 58 | ******************************************************************************/ 59 | 60 | GrowingPacker = function() { }; 61 | 62 | GrowingPacker.prototype = { 63 | 64 | fit: function(blocks) { 65 | var n, node, block, len = blocks.length; 66 | var w = len > 0 ? blocks[0].w : 0; 67 | var h = len > 0 ? blocks[0].h : 0; 68 | this.root = { x: 0, y: 0, w: w, h: h }; 69 | for (n = 0; n < len ; n++) { 70 | block = blocks[n]; 71 | if (node = this.findNode(this.root, block.w, block.h)) 72 | block.fit = this.splitNode(node, block.w, block.h); 73 | else 74 | block.fit = this.growNode(block.w, block.h); 75 | } 76 | }, 77 | 78 | findNode: function(root, w, h) { 79 | if (root.used) 80 | return this.findNode(root.right, w, h) || this.findNode(root.down, w, h); 81 | else if ((w <= root.w) && (h <= root.h)) 82 | return root; 83 | else 84 | return null; 85 | }, 86 | 87 | splitNode: function(node, w, h) { 88 | node.used = true; 89 | node.down = { x: node.x, y: node.y + h, w: node.w, h: node.h - h }; 90 | node.right = { x: node.x + w, y: node.y, w: node.w - w, h: h }; 91 | return node; 92 | }, 93 | 94 | growNode: function(w, h) { 95 | var canGrowDown = (w <= this.root.w); 96 | var canGrowRight = (h <= this.root.h); 97 | 98 | var shouldGrowRight = canGrowRight && (this.root.h >= (this.root.w + w)); // attempt to keep square-ish by growing right when height is much greater than width 99 | var shouldGrowDown = canGrowDown && (this.root.w >= (this.root.h + h)); // attempt to keep square-ish by growing down when width is much greater than height 100 | 101 | if (shouldGrowRight) 102 | return this.growRight(w, h); 103 | else if (shouldGrowDown) 104 | return this.growDown(w, h); 105 | else if (canGrowRight) 106 | return this.growRight(w, h); 107 | else if (canGrowDown) 108 | return this.growDown(w, h); 109 | else 110 | return null; // need to ensure sensible root starting size to avoid this happening 111 | }, 112 | 113 | growRight: function(w, h) { 114 | this.root = { 115 | used: true, 116 | x: 0, 117 | y: 0, 118 | w: this.root.w + w, 119 | h: this.root.h, 120 | down: this.root, 121 | right: { x: this.root.w, y: 0, w: w, h: this.root.h } 122 | }; 123 | var node = this.findNode(this.root, w, h); 124 | if (node) 125 | return this.splitNode(node, w, h); 126 | else 127 | return null; 128 | }, 129 | 130 | growDown: function(w, h) { 131 | this.root = { 132 | used: true, 133 | x: 0, 134 | y: 0, 135 | w: this.root.w, 136 | h: this.root.h + h, 137 | down: { x: 0, y: this.root.h, w: this.root.w, h: h }, 138 | right: this.root 139 | }; 140 | var node = this.findNode(this.root, w, h); 141 | if (node) 142 | return this.splitNode(node, w, h); 143 | else 144 | return null; 145 | } 146 | 147 | }; 148 | -------------------------------------------------------------------------------- /third_party/event_emitter/EventEmitter.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * EventEmitter v4.2.11 - git.io/ee 3 | * Unlicense - http://unlicense.org/ 4 | * Oliver Caldwell - http://oli.me.uk/ 5 | * @preserve 6 | */ 7 | 8 | ;(function () { 9 | 'use strict'; 10 | 11 | /** 12 | * Class for managing events. 13 | * Can be extended to provide event functionality in other classes. 14 | * 15 | * @class EventEmitter Manages event registering and emitting. 16 | */ 17 | function EventEmitter() {} 18 | 19 | // Shortcuts to improve speed and size 20 | var proto = EventEmitter.prototype; 21 | var exports = this; 22 | var originalGlobalValue = exports.EventEmitter; 23 | 24 | /** 25 | * Finds the index of the listener for the event in its storage array. 26 | * 27 | * @param {Function[]} listeners Array of listeners to search through. 28 | * @param {Function} listener Method to look for. 29 | * @return {Number} Index of the specified listener, -1 if not found 30 | * @api private 31 | */ 32 | function indexOfListener(listeners, listener) { 33 | var i = listeners.length; 34 | while (i--) { 35 | if (listeners[i].listener === listener) { 36 | return i; 37 | } 38 | } 39 | 40 | return -1; 41 | } 42 | 43 | /** 44 | * Alias a method while keeping the context correct, to allow for overwriting of target method. 45 | * 46 | * @param {String} name The name of the target method. 47 | * @return {Function} The aliased method 48 | * @api private 49 | */ 50 | function alias(name) { 51 | return function aliasClosure() { 52 | return this[name].apply(this, arguments); 53 | }; 54 | } 55 | 56 | /** 57 | * Returns the listener array for the specified event. 58 | * Will initialise the event object and listener arrays if required. 59 | * Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object containing bar and baz. But only if you have either defined them with defineEvent or added some listeners to them. 60 | * Each property in the object response is an array of listener functions. 61 | * 62 | * @param {String|RegExp} evt Name of the event to return the listeners from. 63 | * @return {Function[]|Object} All listener functions for the event. 64 | */ 65 | proto.getListeners = function getListeners(evt) { 66 | var events = this._getEvents(); 67 | var response; 68 | var key; 69 | 70 | // Return a concatenated array of all matching events if 71 | // the selector is a regular expression. 72 | if (evt instanceof RegExp) { 73 | response = {}; 74 | for (key in events) { 75 | if (events.hasOwnProperty(key) && evt.test(key)) { 76 | response[key] = events[key]; 77 | } 78 | } 79 | } 80 | else { 81 | response = events[evt] || (events[evt] = []); 82 | } 83 | 84 | return response; 85 | }; 86 | 87 | /** 88 | * Takes a list of listener objects and flattens it into a list of listener functions. 89 | * 90 | * @param {Object[]} listeners Raw listener objects. 91 | * @return {Function[]} Just the listener functions. 92 | */ 93 | proto.flattenListeners = function flattenListeners(listeners) { 94 | var flatListeners = []; 95 | var i; 96 | 97 | for (i = 0; i < listeners.length; i += 1) { 98 | flatListeners.push(listeners[i].listener); 99 | } 100 | 101 | return flatListeners; 102 | }; 103 | 104 | /** 105 | * Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful. 106 | * 107 | * @param {String|RegExp} evt Name of the event to return the listeners from. 108 | * @return {Object} All listener functions for an event in an object. 109 | */ 110 | proto.getListenersAsObject = function getListenersAsObject(evt) { 111 | var listeners = this.getListeners(evt); 112 | var response; 113 | 114 | if (listeners instanceof Array) { 115 | response = {}; 116 | response[evt] = listeners; 117 | } 118 | 119 | return response || listeners; 120 | }; 121 | 122 | /** 123 | * Adds a listener function to the specified event. 124 | * The listener will not be added if it is a duplicate. 125 | * If the listener returns true then it will be removed after it is called. 126 | * If you pass a regular expression as the event name then the listener will be added to all events that match it. 127 | * 128 | * @param {String|RegExp} evt Name of the event to attach the listener to. 129 | * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling. 130 | * @return {Object} Current instance of EventEmitter for chaining. 131 | */ 132 | proto.addListener = function addListener(evt, listener) { 133 | var listeners = this.getListenersAsObject(evt); 134 | var listenerIsWrapped = typeof listener === 'object'; 135 | var key; 136 | 137 | for (key in listeners) { 138 | if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) { 139 | listeners[key].push(listenerIsWrapped ? listener : { 140 | listener: listener, 141 | once: false 142 | }); 143 | } 144 | } 145 | 146 | return this; 147 | }; 148 | 149 | /** 150 | * Alias of addListener 151 | */ 152 | proto.on = alias('addListener'); 153 | 154 | /** 155 | * Semi-alias of addListener. It will add a listener that will be 156 | * automatically removed after its first execution. 157 | * 158 | * @param {String|RegExp} evt Name of the event to attach the listener to. 159 | * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling. 160 | * @return {Object} Current instance of EventEmitter for chaining. 161 | */ 162 | proto.addOnceListener = function addOnceListener(evt, listener) { 163 | return this.addListener(evt, { 164 | listener: listener, 165 | once: true 166 | }); 167 | }; 168 | 169 | /** 170 | * Alias of addOnceListener. 171 | */ 172 | proto.once = alias('addOnceListener'); 173 | 174 | /** 175 | * Defines an event name. This is required if you want to use a regex to add a listener to multiple events at once. If you don't do this then how do you expect it to know what event to add to? Should it just add to every possible match for a regex? No. That is scary and bad. 176 | * You need to tell it what event names should be matched by a regex. 177 | * 178 | * @param {String} evt Name of the event to create. 179 | * @return {Object} Current instance of EventEmitter for chaining. 180 | */ 181 | proto.defineEvent = function defineEvent(evt) { 182 | this.getListeners(evt); 183 | return this; 184 | }; 185 | 186 | /** 187 | * Uses defineEvent to define multiple events. 188 | * 189 | * @param {String[]} evts An array of event names to define. 190 | * @return {Object} Current instance of EventEmitter for chaining. 191 | */ 192 | proto.defineEvents = function defineEvents(evts) { 193 | for (var i = 0; i < evts.length; i += 1) { 194 | this.defineEvent(evts[i]); 195 | } 196 | return this; 197 | }; 198 | 199 | /** 200 | * Removes a listener function from the specified event. 201 | * When passed a regular expression as the event name, it will remove the listener from all events that match it. 202 | * 203 | * @param {String|RegExp} evt Name of the event to remove the listener from. 204 | * @param {Function} listener Method to remove from the event. 205 | * @return {Object} Current instance of EventEmitter for chaining. 206 | */ 207 | proto.removeListener = function removeListener(evt, listener) { 208 | var listeners = this.getListenersAsObject(evt); 209 | var index; 210 | var key; 211 | 212 | for (key in listeners) { 213 | if (listeners.hasOwnProperty(key)) { 214 | index = indexOfListener(listeners[key], listener); 215 | 216 | if (index !== -1) { 217 | listeners[key].splice(index, 1); 218 | } 219 | } 220 | } 221 | 222 | return this; 223 | }; 224 | 225 | /** 226 | * Alias of removeListener 227 | */ 228 | proto.off = alias('removeListener'); 229 | 230 | /** 231 | * Adds listeners in bulk using the manipulateListeners method. 232 | * If you pass an object as the second argument you can add to multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. You can also pass it an event name and an array of listeners to be added. 233 | * You can also pass it a regular expression to add the array of listeners to all events that match it. 234 | * Yeah, this function does quite a bit. That's probably a bad thing. 235 | * 236 | * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add to multiple events at once. 237 | * @param {Function[]} [listeners] An optional array of listener functions to add. 238 | * @return {Object} Current instance of EventEmitter for chaining. 239 | */ 240 | proto.addListeners = function addListeners(evt, listeners) { 241 | // Pass through to manipulateListeners 242 | return this.manipulateListeners(false, evt, listeners); 243 | }; 244 | 245 | /** 246 | * Removes listeners in bulk using the manipulateListeners method. 247 | * If you pass an object as the second argument you can remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. 248 | * You can also pass it an event name and an array of listeners to be removed. 249 | * You can also pass it a regular expression to remove the listeners from all events that match it. 250 | * 251 | * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to remove from multiple events at once. 252 | * @param {Function[]} [listeners] An optional array of listener functions to remove. 253 | * @return {Object} Current instance of EventEmitter for chaining. 254 | */ 255 | proto.removeListeners = function removeListeners(evt, listeners) { 256 | // Pass through to manipulateListeners 257 | return this.manipulateListeners(true, evt, listeners); 258 | }; 259 | 260 | /** 261 | * Edits listeners in bulk. The addListeners and removeListeners methods both use this to do their job. You should really use those instead, this is a little lower level. 262 | * The first argument will determine if the listeners are removed (true) or added (false). 263 | * If you pass an object as the second argument you can add/remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. 264 | * You can also pass it an event name and an array of listeners to be added/removed. 265 | * You can also pass it a regular expression to manipulate the listeners of all events that match it. 266 | * 267 | * @param {Boolean} remove True if you want to remove listeners, false if you want to add. 268 | * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add/remove from multiple events at once. 269 | * @param {Function[]} [listeners] An optional array of listener functions to add/remove. 270 | * @return {Object} Current instance of EventEmitter for chaining. 271 | */ 272 | proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) { 273 | var i; 274 | var value; 275 | var single = remove ? this.removeListener : this.addListener; 276 | var multiple = remove ? this.removeListeners : this.addListeners; 277 | 278 | // If evt is an object then pass each of its properties to this method 279 | if (typeof evt === 'object' && !(evt instanceof RegExp)) { 280 | for (i in evt) { 281 | if (evt.hasOwnProperty(i) && (value = evt[i])) { 282 | // Pass the single listener straight through to the singular method 283 | if (typeof value === 'function') { 284 | single.call(this, i, value); 285 | } 286 | else { 287 | // Otherwise pass back to the multiple function 288 | multiple.call(this, i, value); 289 | } 290 | } 291 | } 292 | } 293 | else { 294 | // So evt must be a string 295 | // And listeners must be an array of listeners 296 | // Loop over it and pass each one to the multiple method 297 | i = listeners.length; 298 | while (i--) { 299 | single.call(this, evt, listeners[i]); 300 | } 301 | } 302 | 303 | return this; 304 | }; 305 | 306 | /** 307 | * Removes all listeners from a specified event. 308 | * If you do not specify an event then all listeners will be removed. 309 | * That means every event will be emptied. 310 | * You can also pass a regex to remove all events that match it. 311 | * 312 | * @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed. 313 | * @return {Object} Current instance of EventEmitter for chaining. 314 | */ 315 | proto.removeEvent = function removeEvent(evt) { 316 | var type = typeof evt; 317 | var events = this._getEvents(); 318 | var key; 319 | 320 | // Remove different things depending on the state of evt 321 | if (type === 'string') { 322 | // Remove all listeners for the specified event 323 | delete events[evt]; 324 | } 325 | else if (evt instanceof RegExp) { 326 | // Remove all events matching the regex. 327 | for (key in events) { 328 | if (events.hasOwnProperty(key) && evt.test(key)) { 329 | delete events[key]; 330 | } 331 | } 332 | } 333 | else { 334 | // Remove all listeners in all events 335 | delete this._events; 336 | } 337 | 338 | return this; 339 | }; 340 | 341 | /** 342 | * Alias of removeEvent. 343 | * 344 | * Added to mirror the node API. 345 | */ 346 | proto.removeAllListeners = alias('removeEvent'); 347 | 348 | /** 349 | * Emits an event of your choice. 350 | * When emitted, every listener attached to that event will be executed. 351 | * If you pass the optional argument array then those arguments will be passed to every listener upon execution. 352 | * Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately. 353 | * So they will not arrive within the array on the other side, they will be separate. 354 | * You can also pass a regular expression to emit to all events that match it. 355 | * 356 | * @param {String|RegExp} evt Name of the event to emit and execute listeners for. 357 | * @param {Array} [args] Optional array of arguments to be passed to each listener. 358 | * @return {Object} Current instance of EventEmitter for chaining. 359 | */ 360 | proto.emitEvent = function emitEvent(evt, args) { 361 | var listenersMap = this.getListenersAsObject(evt); 362 | var listeners; 363 | var listener; 364 | var i; 365 | var key; 366 | var response; 367 | 368 | for (key in listenersMap) { 369 | if (listenersMap.hasOwnProperty(key)) { 370 | listeners = listenersMap[key].slice(0); 371 | i = listeners.length; 372 | 373 | while (i--) { 374 | // If the listener returns true then it shall be removed from the event 375 | // The function is executed either with a basic call or an apply if there is an args array 376 | listener = listeners[i]; 377 | 378 | if (listener.once === true) { 379 | this.removeListener(evt, listener.listener); 380 | } 381 | 382 | response = listener.listener.apply(this, args || []); 383 | 384 | if (response === this._getOnceReturnValue()) { 385 | this.removeListener(evt, listener.listener); 386 | } 387 | } 388 | } 389 | } 390 | 391 | return this; 392 | }; 393 | 394 | /** 395 | * Alias of emitEvent 396 | */ 397 | proto.trigger = alias('emitEvent'); 398 | 399 | /** 400 | * Subtly different from emitEvent in that it will pass its arguments on to the listeners, as opposed to taking a single array of arguments to pass on. 401 | * As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it. 402 | * 403 | * @param {String|RegExp} evt Name of the event to emit and execute listeners for. 404 | * @param {...*} Optional additional arguments to be passed to each listener. 405 | * @return {Object} Current instance of EventEmitter for chaining. 406 | */ 407 | proto.emit = function emit(evt) { 408 | var args = Array.prototype.slice.call(arguments, 1); 409 | return this.emitEvent(evt, args); 410 | }; 411 | 412 | /** 413 | * Sets the current value to check against when executing listeners. If a 414 | * listeners return value matches the one set here then it will be removed 415 | * after execution. This value defaults to true. 416 | * 417 | * @param {*} value The new value to check for when executing listeners. 418 | * @return {Object} Current instance of EventEmitter for chaining. 419 | */ 420 | proto.setOnceReturnValue = function setOnceReturnValue(value) { 421 | this._onceReturnValue = value; 422 | return this; 423 | }; 424 | 425 | /** 426 | * Fetches the current value to check against when executing listeners. If 427 | * the listeners return value matches this one then it should be removed 428 | * automatically. It will return true by default. 429 | * 430 | * @return {*|Boolean} The current value to check for or the default, true. 431 | * @api private 432 | */ 433 | proto._getOnceReturnValue = function _getOnceReturnValue() { 434 | if (this.hasOwnProperty('_onceReturnValue')) { 435 | return this._onceReturnValue; 436 | } 437 | else { 438 | return true; 439 | } 440 | }; 441 | 442 | /** 443 | * Fetches the events object and creates one if required. 444 | * 445 | * @return {Object} The events storage object. 446 | * @api private 447 | */ 448 | proto._getEvents = function _getEvents() { 449 | return this._events || (this._events = {}); 450 | }; 451 | 452 | /** 453 | * Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version. 454 | * 455 | * @return {Function} Non conflicting EventEmitter class. 456 | */ 457 | EventEmitter.noConflict = function noConflict() { 458 | exports.EventEmitter = originalGlobalValue; 459 | return EventEmitter; 460 | }; 461 | 462 | // Expose the class either via AMD, CommonJS or the global object 463 | if (typeof define === 'function' && define.amd) { 464 | define(function () { 465 | return EventEmitter; 466 | }); 467 | } 468 | else if (typeof module === 'object' && module.exports){ 469 | module.exports = EventEmitter; 470 | } 471 | else { 472 | exports.EventEmitter = EventEmitter; 473 | } 474 | }.call(this)); -------------------------------------------------------------------------------- /third_party/event_emitter/UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /third_party/request_animation_frame/LICENSE: -------------------------------------------------------------------------------- 1 | MIT license -------------------------------------------------------------------------------- /third_party/request_animation_frame/requestAnimationFrame.js: -------------------------------------------------------------------------------- 1 | /** 2 | * http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 3 | * And modified to work with node.js 4 | */ 5 | 6 | (function() { 7 | 8 | var root = this; 9 | var lastTime = 0; 10 | var vendors = ['ms', 'moz', 'webkit', 'o']; 11 | 12 | for (var x = 0; x < vendors.length && !root.requestAnimationFrame; ++x) { 13 | root.requestAnimationFrame = root[vendors[x]+'RequestAnimationFrame']; 14 | root.cancelAnimationFrame = 15 | root[vendors[x]+'CancelAnimationFrame'] || root[vendors[x]+'CancelRequestAnimationFrame']; 16 | } 17 | 18 | if (!root.requestAnimationFrame) 19 | root.requestAnimationFrame = raf; 20 | 21 | if (!root.cancelAnimationFrame) 22 | root.cancelAnimationFrame = function(id) { 23 | clearTimeout(id); 24 | }; 25 | 26 | if (typeof exports !== 'undefined') { 27 | if (typeof module !== 'undefined' && module.exports) { 28 | exports = module.exports = root.requestAnimationFrame; 29 | } 30 | exports.requestAnimationFrame = root.requestAnimationFrame; 31 | } else { 32 | root.requestAnimationFrame = root.requestAnimationFrame; 33 | } 34 | 35 | if (typeof define === 'function' && define.amd) { 36 | define('requestAnimationFrame', [], function() { 37 | return root.requestAnimationFrame; 38 | }); 39 | } 40 | 41 | function raf(callback, element) { 42 | var currTime = new Date().getTime(); 43 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 44 | var id = root.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); 45 | lastTime = currTime + timeToCall; 46 | return id; 47 | } 48 | 49 | })(); -------------------------------------------------------------------------------- /third_party/set_immediate/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Barnesandnoble.com, llc, Donavon West, and Domenic Denicola 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /third_party/set_immediate/setimmediate.js: -------------------------------------------------------------------------------- 1 | /* jshint -W067 */ 2 | /* jshint unused: false */ 3 | (function(global, undefined) { 4 | 'use strict'; 5 | 6 | if (!notUseNative() && (global.msSetImmediate || global.setImmediate)) { 7 | if (!global.setImmediate) { 8 | global.setImmediate = global.msSetImmediate; 9 | global.clearImmediate = global.msClearImmediate; 10 | } 11 | 12 | return; 13 | } 14 | 15 | var doc = global.document; 16 | var slice = Array.prototype.slice; 17 | var toString = Object.prototype.toString; 18 | var Timer = {}; 19 | 20 | Timer.polifill = {}; 21 | Timer.nextId = 1; 22 | Timer.tasks = {}; 23 | Timer.lock = false; 24 | 25 | Timer.run = function(handleId) { 26 | if (Timer.lock) { 27 | global.setTimeout( Timer.wrap( Timer.run, handleId ), 0 ); 28 | 29 | } else { 30 | var task = Timer.tasks[ handleId ]; 31 | 32 | if (task) { 33 | Timer.lock = true; 34 | 35 | try { 36 | task(); 37 | 38 | } finally { 39 | Timer.clear( handleId ); 40 | Timer.lock = false; 41 | } 42 | } 43 | } 44 | }; 45 | 46 | Timer.wrap = function(handler) { 47 | var args = slice.call(arguments, 1); 48 | 49 | return function() { 50 | handler.apply(undefined, args); 51 | }; 52 | }; 53 | 54 | Timer.create = function(args) { 55 | Timer.tasks[ Timer.nextId ] = Timer.wrap.apply(undefined, args); 56 | return Timer.nextId++; 57 | }; 58 | 59 | Timer.clear = function(handleId) { 60 | delete Timer.tasks[ handleId ]; 61 | }; 62 | 63 | /* polifill/messageChannel.js begin */ 64 | /* global global, Timer */ 65 | 66 | Timer.polifill.messageChannel = function() { 67 | var channel = new global.MessageChannel(); 68 | 69 | channel.port1.onmessage = function(event) { 70 | Timer.run(Number(event.data)); 71 | }; 72 | 73 | return function() { 74 | var handleId = Timer.create(arguments); 75 | channel.port2.postMessage(handleId); 76 | return handleId; 77 | }; 78 | }; 79 | 80 | /* polifill/messageChannel.js end */ 81 | 82 | /* polifill/nextTick.js begin */ 83 | /* global global, Timer */ 84 | 85 | Timer.polifill.nextTick = function() { 86 | return function() { 87 | var handleId = Timer.create(arguments); 88 | global.process.nextTick( Timer.wrap( Timer.run, handleId ) ); 89 | return handleId; 90 | }; 91 | }; 92 | 93 | /* polifill/nextTick.js end */ 94 | 95 | /* polifill/postMessage.js begin */ 96 | /* global global, Timer */ 97 | 98 | Timer.polifill.postMessage = function() { 99 | var messagePrefix = 'setImmediate$' + Math.random() + '$'; 100 | 101 | var onGlobalMessage = function(event) { 102 | if (event.source === global && 103 | typeof(event.data) === 'string' && 104 | event.data.indexOf(messagePrefix) === 0) { 105 | 106 | Timer.run(Number(event.data.slice(messagePrefix.length))); 107 | } 108 | }; 109 | 110 | if (global.addEventListener) { 111 | global.addEventListener('message', onGlobalMessage, false); 112 | 113 | } else { 114 | global.attachEvent('onmessage', onGlobalMessage); 115 | } 116 | 117 | return function() { 118 | var handleId = Timer.create(arguments); 119 | global.postMessage(messagePrefix + handleId, '*'); 120 | return handleId; 121 | }; 122 | }; 123 | 124 | /* polifill/postMessage.js end */ 125 | 126 | /* polifill/readyStateChange.js begin */ 127 | /* global Timer, doc */ 128 | 129 | Timer.polifill.readyStateChange = function() { 130 | var html = doc.documentElement; 131 | 132 | return function() { 133 | var handleId = Timer.create(arguments); 134 | var script = doc.createElement('script'); 135 | 136 | script.onreadystatechange = function() { 137 | Timer.run(handleId); 138 | script.onreadystatechange = null; 139 | html.removeChild(script); 140 | script = null; 141 | }; 142 | 143 | html.appendChild(script); 144 | 145 | return handleId; 146 | }; 147 | }; 148 | 149 | /* polifill/readyStateChange.js end */ 150 | 151 | /* polifill/setTimeout.js begin */ 152 | /* global global, Timer */ 153 | 154 | Timer.polifill.setTimeout = function() { 155 | return function() { 156 | var handleId = Timer.create(arguments); 157 | global.setTimeout( Timer.wrap( Timer.run, handleId ), 0 ); 158 | return handleId; 159 | }; 160 | }; 161 | 162 | /* polifill/setTimeout.js end */ 163 | 164 | 165 | 166 | 167 | function canUsePostMessage() { 168 | if (global.postMessage && !global.importScripts) { 169 | var asynch = true; 170 | var oldOnMessage = global.onmessage; 171 | global.onmessage = function() { 172 | asynch = false; 173 | }; 174 | global.postMessage('', '*'); 175 | global.onmessage = oldOnMessage; 176 | return asynch; 177 | } 178 | } 179 | 180 | function notUseNative() { 181 | // @see http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/ 182 | return (global.navigator && /Trident/.test(global.navigator.userAgent)); 183 | } 184 | 185 | 186 | var polifill; 187 | 188 | if (notUseNative()) { 189 | polifill = 'setTimeout'; 190 | 191 | // Don't get fooled by e.g. browserify environments. 192 | // For Node.js before 0.9 193 | } else if (toString.call(global.process) === '[object process]') { 194 | polifill = 'nextTick'; 195 | 196 | // For non-IE10 modern browsers 197 | } else if (canUsePostMessage()) { 198 | polifill = 'postMessage'; 199 | 200 | // For web workers, where supported 201 | } else if (global.MessageChannel) { 202 | polifill = 'messageChannel'; 203 | 204 | // For IE 6–8 205 | } else if (doc && ('onreadystatechange' in doc.createElement('script'))) { 206 | polifill = 'readyStateChange'; 207 | 208 | // For older browsers 209 | } else { 210 | polifill = 'setTimeout'; 211 | } 212 | 213 | // If supported, we should attach to the prototype of global, since that is where setTimeout et al. live. 214 | var attachTo = Object.getPrototypeOf && Object.getPrototypeOf(global); 215 | attachTo = (attachTo && attachTo.setTimeout ? attachTo : global); 216 | 217 | attachTo.setImmediate = Timer.polifill[ polifill ](); 218 | attachTo.setImmediate.usePolifill = polifill; 219 | attachTo.msSetImmediate = attachTo.setImmediate; 220 | 221 | attachTo.clearImmediate = Timer.clear; 222 | attachTo.msClearImmediate = Timer.clear; 223 | 224 | }(function() { 225 | return this || (1, eval)('this'); 226 | }())); -------------------------------------------------------------------------------- /third_party/three/Detector.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * @author mr.doob / http://mrdoob.com/ 4 | */ 5 | 6 | var Detector = { 7 | 8 | canvas: !! window.CanvasRenderingContext2D, 9 | webgl: ( function () { 10 | 11 | try { 12 | 13 | var canvas = document.createElement( 'canvas' ); return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) ); 14 | 15 | } catch ( e ) { 16 | 17 | return false; 18 | 19 | } 20 | 21 | } )(), 22 | workers: !! window.Worker, 23 | fileapi: window.File && window.FileReader && window.FileList && window.Blob, 24 | 25 | getWebGLErrorMessage: function () { 26 | 27 | var element = document.createElement( 'div' ); 28 | element.id = 'webgl-error-message'; 29 | element.style.fontFamily = 'monospace'; 30 | element.style.fontSize = '13px'; 31 | element.style.fontWeight = 'normal'; 32 | element.style.textAlign = 'center'; 33 | element.style.background = '#fff'; 34 | element.style.color = '#000'; 35 | element.style.padding = '1.5em'; 36 | element.style.width = '400px'; 37 | element.style.margin = '5em auto 0'; 38 | 39 | if ( ! this.webgl ) { 40 | 41 | element.innerHTML = window.WebGLRenderingContext ? [ 42 | 'Your graphics card does not seem to support WebGL.
', 43 | 'Find out how to get it here.' 44 | ].join( '\n' ) : [ 45 | 'Your browser does not seem to support WebGL.
', 46 | 'Find out how to get it here.' 47 | ].join( '\n' ); 48 | 49 | } 50 | 51 | return element; 52 | 53 | }, 54 | 55 | addGetWebGLMessage: function ( parameters ) { 56 | 57 | var parent, id, element; 58 | 59 | parameters = parameters || {}; 60 | 61 | parent = parameters.parent !== undefined ? parameters.parent : document.body; 62 | id = parameters.id !== undefined ? parameters.id : 'oldie'; 63 | 64 | element = Detector.getWebGLErrorMessage(); 65 | element.id = id; 66 | 67 | parent.appendChild( element ); 68 | 69 | } 70 | 71 | }; 72 | 73 | // browserify support 74 | if ( typeof module === 'object' ) { 75 | 76 | module.exports = Detector; 77 | 78 | } -------------------------------------------------------------------------------- /third_party/three/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright © 2010-2016 three.js authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /third_party/underscore/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative 2 | Reporters & Editors 3 | 4 | Permission is hereby granted, free of charge, to any person 5 | obtaining a copy of this software and associated documentation 6 | files (the "Software"), to deal in the Software without 7 | restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /utils/three_build.js: -------------------------------------------------------------------------------- 1 | global.THREE = require('three'); 2 | 3 | THREE = { 4 | ClampToEdgeWrapping: THREE.ClampToEdgeWrapping, 5 | DataTexture: THREE.DataTexture, 6 | FloatType: THREE.FloatType, 7 | LinearFilter: THREE.LinearFilter, 8 | Material: THREE.Material, 9 | Mesh: THREE.Mesh, 10 | MeshBasicMaterial: THREE.MeshBasicMaterial, 11 | NearestFilter: THREE.NearestFilter, 12 | NearestMipMapNearestFilter: THREE.NearestMipMapNearestFilter, 13 | OrthographicCamera: THREE.OrthographicCamera, 14 | PlaneGeometry: THREE.PlaneGeometry, 15 | RepeatWrapping: THREE.RepeatWrapping, 16 | RGBAFormat: THREE.RGBAFormat, 17 | Scene: THREE.Scene, 18 | ShaderMaterial: THREE.ShaderMaterial, 19 | TextureLoader: THREE.TextureLoader, 20 | UnsignedByteType: THREE.UnsignedByteType, 21 | WebGLRenderer: THREE.WebGLRenderer, 22 | WebGLRenderTarget: THREE.WebGLRenderTarget 23 | }; 24 | --------------------------------------------------------------------------------