diff --git a/README.md b/README.md index 998e590..9910a25 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Full documentation is in progress at the [wiki](https://github.com/brianchirls/S - Color Complements - Color Generator - [Color Cube](http://www.youtube.com/watch?v=rfQ8rKGTVlg&t=24m30s) +- Color Select - [Daltonize](http://www.daltonize.org/p/about.html) - Directional Blur - Displacement Map @@ -43,6 +44,7 @@ Full documentation is in progress at the [wiki](https://github.com/brianchirls/S - Expressions - Fader - False Color +- Fast Approximate Anti-Aliasing - Film Grain - Freeze Frame - Gaussian Blur @@ -54,6 +56,7 @@ Full documentation is in progress at the [wiki](https://github.com/brianchirls/S - Layers - Linear Transfer - Luma Key +- Mirror - Night Vision - Panorama - Polar Coordinates diff --git a/effects/seriously.accumulator.js b/effects/seriously.accumulator.js index 8a725c1..279d822 100644 --- a/effects/seriously.accumulator.js +++ b/effects/seriously.accumulator.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; /* diff --git a/effects/seriously.ascii.js b/effects/seriously.ascii.js index a08ebff..fa06d7b 100644 --- a/effects/seriously.ascii.js +++ b/effects/seriously.ascii.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; /* diff --git a/effects/seriously.bleach-bypass.js b/effects/seriously.bleach-bypass.js index cf50bc3..205165c 100644 --- a/effects/seriously.bleach-bypass.js +++ b/effects/seriously.bleach-bypass.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; /* diff --git a/effects/seriously.blend.js b/effects/seriously.blend.js index 3ecc170..f0db0b8 100644 --- a/effects/seriously.blend.js +++ b/effects/seriously.blend.js @@ -498,6 +498,12 @@ draw(shader, model, uniforms, frameBuffer); } }, + requires: function (sourceName) { + if (!this.inputs.opacity && sourceName === 'top') { + return false; + } + return true; + }, inputs: { top: { type: 'image', @@ -527,6 +533,7 @@ defaultValue: 1, min: 0, max: 1, + updateSources: true, update: function (opacity) { if (topUniforms) { topUniforms.opacity = opacity; diff --git a/effects/seriously.blur.js b/effects/seriously.blur.js index d65a661..60492f1 100644 --- a/effects/seriously.blur.js +++ b/effects/seriously.blur.js @@ -23,7 +23,7 @@ http://v002.info/plugins/v002-blurs/ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; var passes = [0.2, 0.3, 0.5, 0.8, 1], @@ -84,7 +84,6 @@ http://v002.info/plugins/v002-blurs/ shaderSource.vertex = [ defineVaryings, - '#define PI ' + Math.PI, 'precision mediump float;', 'attribute vec4 position;', diff --git a/effects/seriously.brightness-contrast.js b/effects/seriously.brightness-contrast.js index 3198bcb..7a098c8 100644 --- a/effects/seriously.brightness-contrast.js +++ b/effects/seriously.brightness-contrast.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('brightness-contrast', { diff --git a/effects/seriously.channels.js b/effects/seriously.channels.js index 71afd8a..9e4ad99 100644 --- a/effects/seriously.channels.js +++ b/effects/seriously.channels.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; var channelOptions = [ diff --git a/effects/seriously.checkerboard.js b/effects/seriously.checkerboard.js index a51a8d0..ea4ac8b 100644 --- a/effects/seriously.checkerboard.js +++ b/effects/seriously.checkerboard.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('checkerboard', function () { diff --git a/effects/seriously.chroma.js b/effects/seriously.chroma.js index 093ffca..6f6945c 100644 --- a/effects/seriously.chroma.js +++ b/effects/seriously.chroma.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; /* experimental chroma key algorithm diff --git a/effects/seriously.color-select.js b/effects/seriously.color-select.js new file mode 100644 index 0000000..faebcb8 --- /dev/null +++ b/effects/seriously.color-select.js @@ -0,0 +1,264 @@ +/* global define, require */ +(function (root, factory) { + 'use strict'; + + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['seriously'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS + factory(require('seriously')); + } else { + if (!root.Seriously) { + root.Seriously = { plugin: function (name, opt) { this[name] = opt; } }; + } + factory(root.Seriously); + } +}(this, function (Seriously) { + 'use strict'; + + Seriously.plugin('color-select', { + shader: function(inputs, shaderSource, utilities) { + shaderSource.vertex = [ + 'precision mediump float;', + + 'attribute vec4 position;', + 'attribute vec2 texCoord;', + + 'uniform vec2 resolution;', + 'uniform mat4 transform;', + + 'uniform float hueMin;', + 'uniform float hueMax;', + 'uniform float hueMinFalloff;', + 'uniform float hueMaxFalloff;', + 'uniform float saturationMin;', + 'uniform float saturationMax;', + 'uniform float saturationMinFalloff;', + 'uniform float saturationMaxFalloff;', + 'uniform float lightnessMin;', + 'uniform float lightnessMax;', + 'uniform float lightnessMinFalloff;', + 'uniform float lightnessMaxFalloff;', + + 'varying vec2 vTexCoord;', + 'varying vec4 adjustedHueRange;', + 'varying vec4 saturationRange;', + 'varying vec4 lightnessRange;', + + 'void main(void) {', + // first convert to screen space + ' vec4 screenPosition = vec4(position.xy * resolution / 2.0, position.z, position.w);', + ' screenPosition = transform * screenPosition;', + + // convert back to OpenGL coords + ' gl_Position.xy = screenPosition.xy * 2.0 / resolution;', + ' gl_Position.z = screenPosition.z * 2.0 / (resolution.x / resolution.y);', + ' gl_Position.w = screenPosition.w;', + ' vTexCoord = texCoord;', + + ' float hueOffset = hueMin - hueMinFalloff;', + ' adjustedHueRange = mod(vec4(' + + 'hueOffset, ' + + 'hueMin - hueOffset, ' + + 'hueMax - hueOffset, ' + + 'hueMax + hueMaxFalloff - hueOffset' + + '), 360.0);', + ' if (hueMin != hueMax) {', + ' if (adjustedHueRange.z == 0.0) {', + ' adjustedHueRange.z = 360.0;', + ' adjustedHueRange.w += 360.0;', + ' } else if (adjustedHueRange.w == 0.0) {', + ' adjustedHueRange.w += 360.0;', + ' }', + ' }', + ' saturationRange = vec4(' + + 'saturationMin - saturationMinFalloff, ' + + 'saturationMin, ' + + 'saturationMax, ' + + 'saturationMax + saturationMaxFalloff ' + + ');', + + ' lightnessRange = vec4(' + + 'lightnessMin - lightnessMinFalloff, ' + + 'lightnessMin, ' + + 'lightnessMax, ' + + 'lightnessMax + lightnessMaxFalloff ' + + ');', + '}' + ].join('\n'); + + shaderSource.fragment = [ + 'precision mediump float;', + + 'varying vec2 vTexCoord;', + + 'uniform sampler2D source;', + 'uniform bool mask;', + + 'varying vec4 adjustedHueRange;', + 'varying vec4 saturationRange;', + 'varying vec4 lightnessRange;', + + 'vec3 calcHSL(vec3 c) {', + ' float minColor = min(c.r, min(c.g, c.b));', + ' float maxColor = max(c.r, max(c.g, c.b));', + ' float delta = maxColor - minColor;', + ' vec3 hsl = vec3(0.0, 0.0, (maxColor + minColor) / 2.0);', + ' if (delta > 0.0) {', + ' if (hsl.z < 0.5) {', + ' hsl.y = delta / (maxColor + minColor);', + ' } else {', + ' hsl.y = delta / (2.0 - maxColor - minColor);', + ' }', + ' if (c.r == maxColor) {', + ' hsl.x = (c.g - c.b) / delta;', + ' } else if (c.g == maxColor) {', + ' hsl.x = 2.0 + (c.b - c.r) / delta;', + ' } else {', + ' hsl.x = 4.0 + (c.r - c.g) / delta;', + ' }', + ' hsl.x = hsl.x * 360.0 / 6.0;', + ' if (hsl.x < 0.0) {', + ' hsl.x += 360.0;', + ' } else {', + ' hsl.x = mod(hsl.x, 360.0);', + ' }', + ' }', + ' return hsl;', + '}', + + 'void main(void) {', + ' vec4 color = texture2D(source, vTexCoord);', + ' vec3 hsl = calcHSL(color.rgb);', + ' float adjustedHue = mod(hsl.x - adjustedHueRange.x, 360.0);', + + // calculate hue mask + ' float maskValue;', + ' if (adjustedHue < adjustedHueRange.y) {', + ' maskValue = smoothstep(0.0, adjustedHueRange.y, adjustedHue);', + ' } else if (adjustedHue < adjustedHueRange.z) {', + ' maskValue = 1.0;', + ' } else {', + ' maskValue = 1.0 - smoothstep(adjustedHueRange.z, adjustedHueRange.w, adjustedHue);', + ' }', + + // calculate saturation maskValue + ' if (maskValue > 0.0) {', + ' if (hsl.y < saturationRange.y) {', + ' maskValue = min(maskValue, smoothstep(saturationRange.x, saturationRange.y, hsl.y));', + ' } else {', + ' maskValue = min(maskValue, 1.0 - smoothstep(saturationRange.z, saturationRange.w, hsl.y));', + ' }', + ' }', + + // calculate lightness maskValue + ' if (maskValue > 0.0) {', + ' if (hsl.z < lightnessRange.y) {', + ' maskValue = min(maskValue, smoothstep(lightnessRange.x, lightnessRange.z, hsl.y));', + ' } else {', + ' maskValue = min(maskValue, 1.0 - smoothstep(lightnessRange.z, lightnessRange.w, hsl.z));', + ' }', + ' }', + + ' if (mask) {', + ' gl_FragColor = vec4(maskValue, maskValue, maskValue, 1.0);', + ' } else {', + ' color.a = min(color.a, maskValue);', + ' gl_FragColor = color;', + ' }', + '}' + ].join('\n'); + + return shaderSource; + }, + inPlace: true, + inputs: { + source: { + type: 'image', + uniform: 'source' + }, + hueMin: { + type: 'number', + uniform: 'hueMin', + defaultValue: 0 + }, + hueMax: { + type: 'number', + uniform: 'hueMax', + defaultValue: 360 + }, + hueMinFalloff: { + type: 'number', + uniform: 'hueMinFalloff', + defaultValue: 0, + min: 0 + }, + hueMaxFalloff: { + type: 'number', + uniform: 'hueMaxFalloff', + defaultValue: 0, + min: 0 + }, + saturationMin: { + type: 'number', + uniform: 'saturationMin', + defaultValue: 0, + min: 0, + max: 1 + }, + saturationMax: { + type: 'number', + uniform: 'saturationMax', + defaultValue: 1, + min: 0, + max: 1 + }, + saturationMinFalloff: { + type: 'number', + uniform: 'saturationMinFalloff', + defaultValue: 0, + min: 0 + }, + saturationMaxFalloff: { + type: 'number', + uniform: 'saturationMaxFalloff', + defaultValue: 0, + min: 0 + }, + lightnessMin: { + type: 'number', + uniform: 'lightnessMin', + defaultValue: 0, + min: 0, + max: 1 + }, + lightnessMax: { + type: 'number', + uniform: 'lightnessMax', + defaultValue: 1, + min: 0, + max: 1 + }, + lightnessMinFalloff: { + type: 'number', + uniform: 'lightnessMinFalloff', + defaultValue: 0, + min: 0 + }, + lightnessMaxFalloff: { + type: 'number', + uniform: 'lightnessMaxFalloff', + defaultValue: 0, + min: 0 + }, + mask: { + type: 'boolean', + defaultValue: false, + uniform: 'mask' + } + }, + title: 'Color Select', + description: 'Create a mask by hue, saturation and lightness range.' + }); +})); diff --git a/effects/seriously.color.js b/effects/seriously.color.js index 775713e..940801b 100644 --- a/effects/seriously.color.js +++ b/effects/seriously.color.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('color', function () { diff --git a/effects/seriously.colorcomplements.js b/effects/seriously.colorcomplements.js index f913321..73f0502 100644 --- a/effects/seriously.colorcomplements.js +++ b/effects/seriously.colorcomplements.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('colorcomplements', { diff --git a/effects/seriously.colorcube.js b/effects/seriously.colorcube.js index dff5cde..a838458 100644 --- a/effects/seriously.colorcube.js +++ b/effects/seriously.colorcube.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; //based on tutorial by to Gregg Tavares diff --git a/effects/seriously.daltonize.js b/effects/seriously.daltonize.js index f5c3eab..ff11175 100644 --- a/effects/seriously.daltonize.js +++ b/effects/seriously.daltonize.js @@ -15,7 +15,7 @@ } factory(Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; //todo: add Simulate mode http://mudcu.be/labs/Color/Vision/Javascript/Color.Vision.Simulate.js diff --git a/effects/seriously.directionblur.js b/effects/seriously.directionblur.js index 54ac631..0d0c48c 100644 --- a/effects/seriously.directionblur.js +++ b/effects/seriously.directionblur.js @@ -23,7 +23,7 @@ http://v002.info/plugins/v002-blurs/ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; var passes = [0.2, 0.3, 0.5, 0.8], diff --git a/effects/seriously.displacement.js b/effects/seriously.displacement.js index 84e64cb..871a6ca 100644 --- a/effects/seriously.displacement.js +++ b/effects/seriously.displacement.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; var fillModes = { @@ -86,6 +86,12 @@ return shaderSource; }, + requires: function (sourceName) { + if (!this.inputs.mapScale && sourceName === 'map') { + return false; + } + return true; + }, resize: function () { var source = this.inputs.source, map = this.inputs.map; @@ -140,7 +146,8 @@ type: 'vector', dimensions: 2, uniform: 'mapScale', - defaultValue: [1, 1] + defaultValue: [1, 1], + updateSources: true }, amount: { type: 'number', diff --git a/effects/seriously.dither.js b/effects/seriously.dither.js index 3db44c8..553f8c9 100644 --- a/effects/seriously.dither.js +++ b/effects/seriously.dither.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; /* diff --git a/effects/seriously.edge.js b/effects/seriously.edge.js index f747305..320c2f8 100644 --- a/effects/seriously.edge.js +++ b/effects/seriously.edge.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; // Adapted from http://rastergrid.com/blog/2011/01/frei-chen-edge-detector/ diff --git a/effects/seriously.emboss.js b/effects/seriously.emboss.js index b3b6995..126cf52 100644 --- a/effects/seriously.emboss.js +++ b/effects/seriously.emboss.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('emboss', { diff --git a/effects/seriously.exposure.js b/effects/seriously.exposure.js old mode 100644 new mode 100755 index 0a9fc88..c02592a --- a/effects/seriously.exposure.js +++ b/effects/seriously.exposure.js @@ -14,18 +14,9 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; - /* - Shader code: - * Copyright vade - Anton Marini - * Creative Commons, Attribution - Non Commercial - Share Alike 3.0 - - http://v002.info/?page_id=34 - - */ - Seriously.plugin('exposure', { commonShader: true, shader: function (inputs, shaderSource) { @@ -38,11 +29,9 @@ 'uniform float exposure;', - 'const float sqrtoftwo = 1.41421356237;', - 'void main (void) {', ' vec4 pixel = texture2D(source, vTexCoord);', - ' gl_FragColor = log2(vec4(pow(exposure + sqrtoftwo, 2.0))) * pixel;', + ' gl_FragColor = vec4(pow(2.0, exposure) * pixel.rgb, pixel.a);', '}' ].join('\n'); return shaderSource; @@ -57,9 +46,9 @@ exposure: { type: 'number', uniform: 'exposure', - defaultValue: 0.6, - min: 0, - max: 1 + defaultValue: 1, + min: -8, + max: 8 } }, title: 'Exposure', diff --git a/effects/seriously.expression.js b/effects/seriously.expression.js index 4cb94c4..392c7bb 100644 --- a/effects/seriously.expression.js +++ b/effects/seriously.expression.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; function formatFloat(n) { diff --git a/effects/seriously.fader.js b/effects/seriously.fader.js index d40ef77..a4cf786 100644 --- a/effects/seriously.fader.js +++ b/effects/seriously.fader.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('fader', { @@ -36,6 +36,9 @@ ].join('\n'); return shaderSource; }, + requires: function (sourceName, inputs) { + return inputs.amount < 1; + }, inPlace: true, inputs: { source: { diff --git a/effects/seriously.falsecolor.js b/effects/seriously.falsecolor.js index 6ddd37e..320a0d8 100644 --- a/effects/seriously.falsecolor.js +++ b/effects/seriously.falsecolor.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('falsecolor', { diff --git a/effects/seriously.filmgrain.js b/effects/seriously.filmgrain.js index 333ca89..96401e6 100644 --- a/effects/seriously.filmgrain.js +++ b/effects/seriously.filmgrain.js @@ -25,7 +25,7 @@ Modified to preserve alpha } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('filmgrain', { diff --git a/effects/seriously.freeze.js b/effects/seriously.freeze.js index cc81342..8ea4686 100644 --- a/effects/seriously.freeze.js +++ b/effects/seriously.freeze.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('freeze', { @@ -23,6 +23,9 @@ draw(shader, model, uniforms, frameBuffer); } }, + requires: function () { + return !this.inputs.frozen; + }, inPlace: true, inputs: { source: { @@ -31,7 +34,8 @@ }, frozen: { type: 'boolean', - defaultValue: false + defaultValue: false, + updateSources: true } }, title: 'Freeze', diff --git a/effects/seriously.fxaa.js b/effects/seriously.fxaa.js new file mode 100644 index 0000000..859c322 --- /dev/null +++ b/effects/seriously.fxaa.js @@ -0,0 +1,142 @@ +/* global define, require */ +(function (root, factory) { + 'use strict'; + + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['seriously'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS + factory(require('seriously')); + } else { + if (!root.Seriously) { + root.Seriously = { plugin: function (name, opt) { this[name] = opt; } }; + } + factory(root.Seriously); + } +}(this, function (Seriously) { + 'use strict'; + + /* + http://en.wikipedia.org/wiki/Fast_approximate_anti-aliasing + + adapted from: + http://horde3d.org/wiki/index.php5?title=Shading_Technique_-_FXAA + */ + + Seriously.plugin('fxaa', { + commonShader: true, + shader: function (inputs, shaderSource) { + shaderSource.vertex = [ + 'precision mediump float;', + + 'attribute vec4 position;', + 'attribute vec2 texCoord;', + + 'uniform vec2 resolution;', + 'uniform mat4 transform;', + + 'varying vec2 vTexCoord;', + 'varying vec2 vTexCoordNW;', + 'varying vec2 vTexCoordNE;', + 'varying vec2 vTexCoordSW;', + 'varying vec2 vTexCoordSE;', + + 'const vec2 diag = vec2(1.0, -1.0);', + + 'void main(void) {', + // first convert to screen space + ' vec4 screenPosition = vec4(position.xy * resolution / 2.0, position.z, position.w);', + ' screenPosition = transform * screenPosition;', + + // convert back to OpenGL coords + ' gl_Position.xy = screenPosition.xy * 2.0 / resolution;', + ' gl_Position.z = screenPosition.z * 2.0 / (resolution.x / resolution.y);', + ' gl_Position.w = screenPosition.w;', + + ' vTexCoord = texCoord;', + + ' vec2 invRes = 1.0 / resolution;', + ' vTexCoordNW = texCoord - invRes;', + ' vTexCoordNE = texCoord + invRes * diag;', + ' vTexCoordSW = texCoord - invRes * diag;', + ' vTexCoordSE = texCoord + invRes;', + '}\n' + ].join('\n'); + + shaderSource.fragment = [ + 'precision mediump float;', + + '#define FXAA_REDUCE_MIN (1.0 / 128.0)', + '#define FXAA_REDUCE_MUL (1.0 / 8.0)', + '#define FXAA_SPAN_MAX 8.0', + + 'varying vec2 vTexCoord;', + 'varying vec2 vTexCoordNW;', + 'varying vec2 vTexCoordNE;', + 'varying vec2 vTexCoordSW;', + 'varying vec2 vTexCoordSE;', + + 'uniform vec2 resolution;', + 'uniform sampler2D source;', + + 'const vec3 luma = vec3(0.299, 0.587, 0.114);', + + 'void main(void) {', + ' vec4 original = texture2D(source, vTexCoord);', + ' vec3 rgbNW = texture2D(source, vTexCoordNW).rgb;', + ' vec3 rgbNE = texture2D(source, vTexCoordNE).rgb;', + ' vec3 rgbSW = texture2D(source, vTexCoordSW).rgb;', + ' vec3 rgbSE = texture2D(source, vTexCoordSE).rgb;', + + ' float lumaNW = dot(rgbNW, luma);', + ' float lumaNE = dot(rgbNE, luma);', + ' float lumaSW = dot(rgbSW, luma);', + ' float lumaSE = dot(rgbSE, luma);', + ' float lumaM = dot(original.rgb, luma);', + + ' float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));', + ' float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));', + + ' vec2 dir = vec2(' + + '-((lumaNW + lumaNE) - (lumaSW + lumaSE)), ' + + '((lumaNW + lumaSW) - (lumaNE + lumaSE))' + + ');', + + ' float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * 0.25 * FXAA_REDUCE_MUL, FXAA_REDUCE_MIN);', + + ' float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);', + + ' dir = min(vec2(FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX), dir * rcpDirMin)) / resolution;', + + ' vec3 rgbA = 0.5 * (', + ' texture2D(source, vTexCoord + dir * (1.0 / 3.0 - 0.5)).rgb +', + ' texture2D(source, vTexCoord + dir * (2.0 / 3.0 - 0.5)).rgb);', + + ' vec3 rgbB = rgbA * 0.5 + 0.25 * (', + ' texture2D(source, vTexCoord - dir * 0.5).rgb +', + ' texture2D(source, vTexCoord + dir * 0.5).rgb);', + + ' float lumaB = dot(rgbB, luma);', + ' if (lumaB < lumaMin || lumaB > lumaMax) {', + ' gl_FragColor = vec4(rgbA, original.a);', + ' } else {', + ' gl_FragColor = vec4(rgbB, original.a);', + ' }', + '}' + ].join('\n'); + + return shaderSource; + }, + inPlace: true, + inputs: { + source: { + type: 'image', + uniform: 'source', + shaderDirty: false + } + }, + title: 'FXAA', + description: 'Fast approximate anti-aliasing' + }); +})); diff --git a/effects/seriously.gradientwipe.js b/effects/seriously.gradientwipe.js new file mode 100644 index 0000000..b45f4cf --- /dev/null +++ b/effects/seriously.gradientwipe.js @@ -0,0 +1,190 @@ +/* global define, require */ +(function (root, factory) { + 'use strict'; + + if (typeof exports === 'object') { + // Node/CommonJS + factory(require('seriously')); + } else if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['seriously'], factory); + } else { + if (!root.Seriously) { + root.Seriously = { plugin: function (name, opt) { this[name] = opt; } }; + } + factory(root.Seriously); + } +}(this, function (Seriously) { + 'use strict'; + + Seriously.plugin('gradientwipe', function () { + this.uniforms.resGradient = [1, 1]; + this.uniforms.resSource = [1, 1]; + + return { + shader: function (inputs, shaderSource) { + shaderSource.vertex = [ + 'precision mediump float;', + + 'attribute vec4 position;', + 'attribute vec2 texCoord;', + + 'uniform vec2 resolution;', + 'uniform vec2 resSource;', + 'uniform vec2 resGradient;', + + 'varying vec2 texCoordSource;', + 'varying vec2 texCoordGradient;', + + 'const vec2 HALF = vec2(0.5);', + + 'void main(void) {', + //we don't need to do a transform in this shader, since this effect is not "inPlace" + ' gl_Position = position;', + + ' vec2 adjusted = (texCoord - HALF) * resolution;', + + ' texCoordSource = adjusted / resSource + HALF;', + ' texCoordGradient = adjusted / resGradient + HALF;', + '}' + ].join('\n'); + + shaderSource.fragment = [ + 'precision mediump float;\n', + + 'varying vec2 texCoordSource;', + 'varying vec2 texCoordGradient;', + + 'uniform sampler2D source;', + 'uniform sampler2D gradient;', + + 'uniform float transition;', + 'uniform float smoothness;', + 'uniform bool invert;', + + 'const vec3 lumcoeff = vec3(0.2125,0.7154,0.0721);', + + 'void main(void) {', + ' float gradientVal = 1.0 - dot(texture2D(gradient, texCoordGradient).rgb, lumcoeff);', + + ' if (invert) {', + ' gradientVal = 1.0 - gradientVal;', + ' }', + + ' float amount = 1.0 - transition;', + + ' float mn = (amount - smoothness * (1.0 - amount));', + ' float mx = (amount + smoothness * amount);', + + ' if (gradientVal <= mn) {', + ' gl_FragColor = texture2D(source, texCoordSource);', + ' return;', + ' }', + + ' if (gradientVal >= mx) {', + ' gl_FragColor = vec4(0.0);', + ' return;', + ' }', + + ' float alpha = mix(1.0, 0.0, smoothstep(mn, mx, gradientVal));', + ' vec4 pixel = texture2D(source, texCoordSource);', + + ' gl_FragColor = vec4(pixel.rgb, pixel.a * alpha);', + '}' + ].join('\n'); + + return shaderSource; + }, + draw: function (shader, model, uniforms, frameBuffer, parent) { + var gl; + + //* + if (uniforms.transition <= 0) { + //uniforms.source = uniforms.sourceB; + parent(this.baseShader, model, uniforms, frameBuffer); + return; + } + //*/ + + //* + if (uniforms.transition >= 1) { + gl = this.gl; + + gl.viewport(0, 0, this.width, this.height); + gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer); + gl.clearColor(0.0, 0.0, 0.0, 0.0); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + return; + } + //*/ + + parent(shader, model, uniforms, frameBuffer); + }, + inPlace: false, + requires: function (sourceName, inputs) { + + if (sourceName === 'source' && inputs.transition >= 1) { + return false; + } + + if (sourceName === 'gradient' && + (inputs.transition <= 0 || inputs.transition >= 1)) { + return false; + } + + return true; + }, + resize: function () { + var source = this.inputs.source, + gradient = this.inputs.gradient; + + if (source) { + this.uniforms.resSource[0] = source.width; + this.uniforms.resSource[1] = source.height; + } else { + this.uniforms.resSource[0] = 1; + this.uniforms.resSource[1] = 1; + } + + if (gradient) { + this.uniforms.resGradient[0] = gradient.width; + this.uniforms.resGradient[1] = gradient.height; + } else { + this.uniforms.resGradient[0] = 1; + this.uniforms.resGradient[1] = 1; + } + } + }; + }, + { + inputs: { + source: { + type: 'image', + uniform: 'source' + }, + gradient: { + type: 'image', + uniform: 'gradient' + }, + transition: { + type: 'number', + uniform: 'transition', + defaultValue: 0 + }, + invert: { + type: 'boolean', + uniform: 'invert', + defaultValue: false + }, + smoothness: { + type: 'number', + uniform: 'smoothness', + defaultValue: 0, + min: 0, + max: 1 + } + }, + title: 'Gradient Wipe' + }); +})); diff --git a/effects/seriously.hex.js b/effects/seriously.hex.js index ce74c28..a43c875 100644 --- a/effects/seriously.hex.js +++ b/effects/seriously.hex.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; /* diff --git a/effects/seriously.highlights-shadows.js b/effects/seriously.highlights-shadows.js index 2bab01c..5b24012 100644 --- a/effects/seriously.highlights-shadows.js +++ b/effects/seriously.highlights-shadows.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('highlights-shadows', { diff --git a/effects/seriously.hue-saturation.js b/effects/seriously.hue-saturation.js index df6e61c..5a6a52b 100644 --- a/effects/seriously.hue-saturation.js +++ b/effects/seriously.hue-saturation.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; //inspired by Evan Wallace (https://github.com/evanw/glfx.js) diff --git a/effects/seriously.invert.js b/effects/seriously.invert.js index 9425503..47a6b04 100644 --- a/effects/seriously.invert.js +++ b/effects/seriously.invert.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('invert', { diff --git a/effects/seriously.kaleidoscope.js b/effects/seriously.kaleidoscope.js index a28625a..a3bdf7f 100644 --- a/effects/seriously.kaleidoscope.js +++ b/effects/seriously.kaleidoscope.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('kaleidoscope', { diff --git a/effects/seriously.layers.js b/effects/seriously.layers.js index f9eb605..3b4dff5 100644 --- a/effects/seriously.layers.js +++ b/effects/seriously.layers.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; var identity = new Float32Array([ @@ -75,6 +75,7 @@ defaultValue: 1, min: 0, max: 1, + updateSources: true }; } diff --git a/effects/seriously.linear-transfer.js b/effects/seriously.linear-transfer.js index b94d6f1..d0b1cf5 100644 --- a/effects/seriously.linear-transfer.js +++ b/effects/seriously.linear-transfer.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('linear-transfer', { diff --git a/effects/seriously.lumakey.js b/effects/seriously.lumakey.js index 47e6fa0..e8b2332 100644 --- a/effects/seriously.lumakey.js +++ b/effects/seriously.lumakey.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('lumakey', { diff --git a/effects/seriously.mirror.js b/effects/seriously.mirror.js new file mode 100644 index 0000000..b2de740 --- /dev/null +++ b/effects/seriously.mirror.js @@ -0,0 +1,46 @@ +/* global define, require */ +(function (root, factory) { + 'use strict'; + + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['seriously'], factory); + } else if (typeof exports === 'object') { + // Node/CommonJS + factory(require('seriously')); + } else { + if (!root.Seriously) { + root.Seriously = { plugin: function (name, opt) { this[name] = opt; } }; + } + factory(root.Seriously); + } +}(this, function (Seriously) { + 'use strict'; + + Seriously.plugin('mirror', { + commonShader: true, + shader: function (inputs, shaderSource) { + shaderSource.fragment = [ + 'precision mediump float;', + + 'uniform vec2 resolution;', + 'uniform sampler2D source;', + + 'void main(void) {', + ' vec2 uv = gl_FragCoord.xy / resolution.xy;', + ' gl_FragColor = texture2D(source, vec2(0.5 - abs(0.5 - uv.x), uv.y));', + '}' + ].join('\n'); + return shaderSource; + }, + inPlace: true, + inputs: { + source: { + type: 'image', + uniform: 'source' + } + }, + title: 'Mirror', + description: 'Shader Mirror Effect' + }); +})); diff --git a/effects/seriously.nightvision.js b/effects/seriously.nightvision.js index 4221501..eeae7fe 100644 --- a/effects/seriously.nightvision.js +++ b/effects/seriously.nightvision.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; //based on tutorial: http://www.geeks3d.com/20091009/shader-library-night-vision-post-processing-filter-glsl/ diff --git a/effects/seriously.noise.js b/effects/seriously.noise.js index 4710515..2fdb984 100644 --- a/effects/seriously.noise.js +++ b/effects/seriously.noise.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('noise', { diff --git a/effects/seriously.panorama.js b/effects/seriously.panorama.js index 3fe1b61..b518a20 100644 --- a/effects/seriously.panorama.js +++ b/effects/seriously.panorama.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('panorama', function () { diff --git a/effects/seriously.polar.js b/effects/seriously.polar.js index ed136c1..1af9278 100644 --- a/effects/seriously.polar.js +++ b/effects/seriously.polar.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('polar', { diff --git a/effects/seriously.repeat.js b/effects/seriously.repeat.js index dce5b41..b88af9f 100644 --- a/effects/seriously.repeat.js +++ b/effects/seriously.repeat.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; var identity = new Float32Array([ diff --git a/effects/seriously.ripple.js b/effects/seriously.ripple.js index 3a549ca..bb26c1f 100644 --- a/effects/seriously.ripple.js +++ b/effects/seriously.ripple.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; //http://msdn.microsoft.com/en-us/library/bb313868(v=xnagamestudio.10).aspx diff --git a/effects/seriously.scanlines.js b/effects/seriously.scanlines.js index 486b184..43240d5 100644 --- a/effects/seriously.scanlines.js +++ b/effects/seriously.scanlines.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('scanlines', { @@ -34,7 +34,7 @@ 'void main(void) {', ' vec4 pixel = texture2D(source, vTexCoord);', - ' float darken = 2.0 * abs( fract(gl_FragCoord.y * lines / 2.0) - 0.5);', + ' float darken = 2.0 * abs( fract(vTexCoord.y * lines / 2.0) - 0.5);', ' darken = clamp(darken - width + 0.5, 0.0, 1.0);', ' darken = 1.0 - ((1.0 - darken) * intensity);', ' gl_FragColor = vec4(pixel.rgb * darken, 1.0);', diff --git a/effects/seriously.select.js b/effects/seriously.select.js index 3a455a5..bed1a63 100644 --- a/effects/seriously.select.js +++ b/effects/seriously.select.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; var intRegex = /\d+/; @@ -25,16 +25,18 @@ i, inputs; + function resize() { + me.resize(); + } + function update() { var i = me.inputs.active, source; source = me.inputs['source' + i]; me.texture = source && source.texture; - } - function resize() { - me.resize(); + resize(); } if (typeof options === 'number' && options >= 2) { @@ -51,7 +53,8 @@ min: 0, max: count - 1, defaultValue: 0, - update: resize + update: update, + updateSources: true }, sizeMode: { type: 'enum', @@ -72,7 +75,7 @@ //source inputs['source' + i] = { type: 'image', - update: resize + update: update }; } @@ -156,7 +159,13 @@ //check the source texture on every draw just in case the source nodes pulls //shenanigans with its texture. - draw: update, + draw: function () { + var i = me.inputs.active, + source; + + source = me.inputs['source' + i]; + me.texture = source && source.texture; + }, inputs: inputs }; }, diff --git a/effects/seriously.sepia.js b/effects/seriously.sepia.js index fec553a..d7c6ce1 100644 --- a/effects/seriously.sepia.js +++ b/effects/seriously.sepia.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; // sepia coefficients borrowed from: diff --git a/effects/seriously.simplex.js b/effects/seriously.simplex.js index aae568e..d9d2285 100644 --- a/effects/seriously.simplex.js +++ b/effects/seriously.simplex.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('simplex', function () { diff --git a/effects/seriously.sketch.js b/effects/seriously.sketch.js index 12996be..6061c29 100644 --- a/effects/seriously.sketch.js +++ b/effects/seriously.sketch.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; /* inspired by http://lab.adjazent.com/2009/01/09/more-pixel-bender/ */ diff --git a/effects/seriously.split.js b/effects/seriously.split.js index 9370a03..ebdb2e2 100644 --- a/effects/seriously.split.js +++ b/effects/seriously.split.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('split', function () { @@ -255,7 +255,8 @@ uniform: 'split', defaultValue: 0.5, min: 0, - max: 1 + max: 1, + updateSources: true }, angle: { type: 'number', diff --git a/effects/seriously.throttle.js b/effects/seriously.throttle.js index c849571..9d8d16e 100644 --- a/effects/seriously.throttle.js +++ b/effects/seriously.throttle.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('throttle', function () { diff --git a/effects/seriously.tone.js b/effects/seriously.tone.js index 4108949..b233439 100644 --- a/effects/seriously.tone.js +++ b/effects/seriously.tone.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('tone', { diff --git a/effects/seriously.tvglitch.js b/effects/seriously.tvglitch.js index f5cb176..744e44b 100644 --- a/effects/seriously.tvglitch.js +++ b/effects/seriously.tvglitch.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; //particle parameters @@ -206,7 +206,7 @@ vsyncPeriod = 1; uniforms.vsync = 0; } - uniforms.time = (this.inputs.time % (10000 * vsyncPeriod)) / 1000; + uniforms.time = (this.inputs.time % (1000 * vsyncPeriod)); uniforms.distortion = Math.random() * this.inputs.distortion; //render particle canvas and attach uniform diff --git a/effects/seriously.vibrance.js b/effects/seriously.vibrance.js index 9615148..76c6516 100644 --- a/effects/seriously.vibrance.js +++ b/effects/seriously.vibrance.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; /* diff --git a/effects/seriously.vignette.js b/effects/seriously.vignette.js index 2a5f263..9ffaae0 100644 --- a/effects/seriously.vignette.js +++ b/effects/seriously.vignette.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; Seriously.plugin('vignette', { diff --git a/effects/seriously.whitebalance.js b/effects/seriously.whitebalance.js index 29aeec1..47adc17 100644 --- a/effects/seriously.whitebalance.js +++ b/effects/seriously.whitebalance.js @@ -14,7 +14,7 @@ } factory(root.Seriously); } -}(this, function (Seriously, undefined) { +}(this, function (Seriously) { 'use strict'; /* diff --git a/examples/accumulator/index.html b/examples/accumulator/index.html index d7ebb7f..e202b10 100644 --- a/examples/accumulator/index.html +++ b/examples/accumulator/index.html @@ -126,9 +126,8 @@ resize(); window.onresize = resize; - seriously.go(function () { - var now = Date.now(), - diff = (now - lastFrameTime) / 1000; + seriously.go(function (now) { + var diff = (now - lastFrameTime) / 1000; vx = Math.min(Math.max(vx + (Math.random() - 0.5) * 100, -200), 200); vy = Math.min(Math.max(vy + (Math.random() - 0.5) * 100, -200), 200); diff --git a/examples/animate/index.html b/examples/animate/index.html index 7a3aa13..81cad8e 100644 --- a/examples/animate/index.html +++ b/examples/animate/index.html @@ -50,9 +50,8 @@ split.fuzzy = 0.25; target.source = split; - seriously.go(function() { - var now = Date.now(), - minutes; + seriously.go(function(now) { + var minutes; minutes = now / 60000; split.angle = (minutes - Math.floor(minutes)) * PI2; diff --git a/examples/blur/blur.html b/examples/blur/blur.html index 2766925..8f3b155 100644 --- a/examples/blur/blur.html +++ b/examples/blur/blur.html @@ -94,15 +94,13 @@ target = seriously.target('#canvas'); target.source = blur; - seriously.go(function () { + seriously.go(function (now) { // callback runs before render - var now; rS('rAF').tick(); rS('FPS').frame(); if (oscillating) { - now = Date.now(); if (lastRender) { if (amount <= 0.01 || amount >= 0.99) { direction *= -1; diff --git a/examples/camera/camera-source.html b/examples/camera/camera-source.html index 0d94031..e01a023 100644 --- a/examples/camera/camera-source.html +++ b/examples/camera/camera-source.html @@ -6,7 +6,6 @@
- diff --git a/examples/camera/index.html b/examples/camera/index.html index cba34be..f07fa64 100644 --- a/examples/camera/index.html +++ b/examples/camera/index.html @@ -9,8 +9,6 @@ - - diff --git a/examples/demo/drunk.html b/examples/demo/drunk.html index 0aa1f78..65735ba 100644 --- a/examples/demo/drunk.html +++ b/examples/demo/drunk.html @@ -39,7 +39,6 @@ var seriously, // the main object that holds the entire composition target, // a wrapper object for our target canvas shake, - scale, reformat, accumulator, x = 0, @@ -60,29 +59,27 @@ seriously = new Seriously(); reformat = seriously.transform('reformat'); - scale = seriously.transform('2d'); shake = seriously.transform('camerashake'); accumulator = seriously.effect('accumulator'); target = seriously.target('#canvas'); reformat.mode = 'cover'; - scale.scale(1.2); shake.rotation = 4; shake.frequency = 0.22; shake.amplitudeX = 16; shake.amplitudeY = 8; + shake.preScale = 0; accumulator.opacity = 0.05; - scale.source = seriously.source('camera'); - shake.source = scale; + shake.source = seriously.source('camera'); accumulator.source = shake; reformat.source = accumulator; target.source = reformat; resize(); - seriously.go(function () { - shake.time = Date.now() / 200; + seriously.go(function (now) { + shake.time = now / 200; }); }); diff --git a/examples/demo/fog.html b/examples/demo/fog.html index be9e21f..d747c45 100644 --- a/examples/demo/fog.html +++ b/examples/demo/fog.html @@ -120,9 +120,9 @@ target.source = blend; - seriously.go(function () { + seriously.go(function (now) { //animate fog - var time = (Date.now() / 1000 - start) % 1000; + var time = (now / 1000 - start) % 1000; simplex.time = time / 10; noiseOffset[0] = time / 10; diff --git a/examples/demo/panorama.html b/examples/demo/panorama.html index 5af2195..85fe0e4 100644 --- a/examples/demo/panorama.html +++ b/examples/demo/panorama.html @@ -84,8 +84,8 @@ resize(); - seriously.go(function () { - panorama.yaw = (Date.now() / 200) % 360; + seriously.go(function (now) { + panorama.yaw = (now / 200) % 360; //panorama.pitch = Math.sin(Date.now() / 2000) * 10; }); }); diff --git a/examples/demo/threejs.html b/examples/demo/threejs.html new file mode 100644 index 0000000..c303916 --- /dev/null +++ b/examples/demo/threejs.html @@ -0,0 +1,478 @@ + + + +