diff --git a/README.md b/README.md
index 7d16414..f88a1f4 100644
--- a/README.md
+++ b/README.md
@@ -8,10 +8,10 @@ Procedural Texture Generator
```javascript
var texture = new TG.Texture( 256, 256 )
.add( new TG.XOR().tint( 1, 0.5, 0.7 ) )
- .add( new TG.SinX().frequency( 0.004 ).tint( 0.5, 0, 0 ) )
- .mul( new TG.SinY().frequency( 0.004 ).tint( 0.5, 0, 0 ) )
- .add( new TG.SinX().frequency( 0.0065 ).tint( 0.1, 0.5, 0.2 ) )
- .add( new TG.SinY().frequency( 0.0065 ).tint( 0.5, 0.5, 0.5 ) )
+ .add( new TG.SinX().frequency( 500 ).tint( 0.5, 0, 0 ) )
+ .mul( new TG.SinY().frequency( 500 ).tint( 0.5, 0, 0 ) )
+ .add( new TG.SinX().frequency( 2 / 0.0065 ).tint( 0.1, 0.5, 0.2 ) )
+ .add( new TG.SinY().frequency( 2 / 0.0065 ).tint( 0.5, 0.5, 0.5 ) )
.add( new TG.Noise().tint( 0.1, 0.1, 0.2 ) )
.toCanvas();
diff --git a/examples/animated.html b/examples/animated.html
index 9363b8e..253023a 100644
--- a/examples/animated.html
+++ b/examples/animated.html
@@ -30,12 +30,12 @@
function render() {
var texture = new TG.Texture( size, size )
- .add( new TG.SinX().frequency( 0.05+i/1000 ) )
- .mul( new TG.SinX().frequency( 0.08-i/2000 ) )
- .add( new TG.SinY().frequency( 0.05-i/1000 ) )
- .mul( new TG.SinY().frequency( 0.08+i/2000 ) )
- .div( new TG.Number().tint( 1, 2, 1 ) )
- .add( new TG.SinX().frequency( 0.003 ).tint( 0.5, 0, 0 ) )
+ .add( new TG.SinX().frequency( 2/(0.05+i/1000) ) )
+ .mul( new TG.SinX().frequency( 2/(0.08-i/2000) ) )
+ .add( new TG.SinY().frequency( 2/(0.05-i/1000) ) )
+ .mul( new TG.SinY().frequency( 2/(0.08+i/2000) ) )
+ .div( new TG.Fill().tint( 1, 2, 1 ) )
+ .add( new TG.SinX().frequency( 2/0.003 ).tint( 0.5, 0, 0 ) )
.toImageData(context);
context.putImageData( texture, 0, 0 );
@@ -43,11 +43,11 @@
//
var texture = new TG.Texture( size, size )
- .add( new TG.SinX().frequency( 0.066 + 0.05*Math.sin(i/100) ) )
- .add( new TG.SinY().frequency( 0.066 + 0.05*Math.sin(i/100) ) )
- .mul( new TG.SinX().offset( 32 ).frequency( 0.044 + 0.09*Math.sin(i/100) ).tint( 2, 2, 2 ) )
- .mul( new TG.SinY().offset( 16 ).frequency( 0.044 + 0.09*Math.sin(i/100) ).tint( 2, 2, 2 ) )
- .sub( new TG.Number().tint( 0.5, 2, 4 ) )
+ .add( new TG.SinX().frequency( 2/(0.066 + 0.05*Math.sin(i/100)) ) )
+ .add( new TG.SinY().frequency( 2/(0.066 + 0.05*Math.sin(i/100)) ) )
+ .mul( new TG.SinX().offset( 32 ).frequency( 2/(0.044 + 0.09*Math.sin(i/100)) ).tint( 2, 2, 2 ) )
+ .mul( new TG.SinY().offset( 16 ).frequency( 2/(0.044 + 0.09*Math.sin(i/100)) ).tint( 2, 2, 2 ) )
+ .sub( new TG.Fill().tint( 0.5, 2, 4 ) )
.toImageData(context);
context.putImageData( texture, size, 0 );
@@ -55,10 +55,10 @@
//
var texture = new TG.Texture( size, size )
- .add( new TG.SinX().frequency( 0.004 + 0.002*Math.sin(i/100)) )
- .mul( new TG.SinY().frequency( 0.004 + 0.002*Math.sin(i/100)) )
- .mul( new TG.SinY().offset( 32 ).frequency( 0.04 + 0.02*Math.sin(i/100) ) )
- .div( new TG.SinX().frequency( 0.02 ).tint( 8, 5, 4 ) )
+ .add( new TG.SinX().frequency( 2/(0.004 + 0.002*Math.sin(i/100))) )
+ .mul( new TG.SinY().frequency( 2/(0.004 + 0.002*Math.sin(i/100))) )
+ .mul( new TG.SinY().offset( 32 ).frequency( 2/(0.04 + 0.02*Math.sin(i/100)) ) )
+ .div( new TG.SinX().frequency( 2/0.02 ).tint( 8, 5, 4 ) )
.add( new TG.Noise().tint( 0.1, 0, 0 ) )
.add( new TG.Noise().tint( 0, 0.1, 0 ) )
.add( new TG.Noise().tint( 0, 0, 0.1 ) )
diff --git a/examples/index.html b/examples/index.html
index 8d718f6..7626766 100644
--- a/examples/index.html
+++ b/examples/index.html
@@ -1,491 +1,434 @@
-
-
- texgen.js
-
-
-
-
- Change size: 128x128 256x256 512x512 custom...
+
+
+ texgen.js
+
+
+
+
+
+
+
+ Change size: 128x128 256x256 512x512 custom...
+
+
+
-
-
-
-
-
-
-
+
+
+
+
diff --git a/examples/noise.html b/examples/noise.html
index 4c63ce6..bd44e54 100644
--- a/examples/noise.html
+++ b/examples/noise.html
@@ -190,21 +190,21 @@
new TG.Texture(size, size)
.set(new TG.FractalNoise().baseFrequency(64).octaves(6).step(2).interpolation(2) )
.sub(new TG.Noise().tint(0, 0.1, 0) )
- .min(new TG.Number().tint(0, 0.6, 0) )
- .sub(new TG.Number().tint(0, 0.5, 0) )
- .mul(new TG.Number().tint(0, 5, 0) )
+ .min(new TG.Fill().tint(0, 0.6, 0) )
+ .sub(new TG.Fill().tint(0, 0.5, 0) )
+ .mul(new TG.Fill().tint(0, 5, 0) )
.sub(new TG.SineDistort().sines(2, 2).amplitude(8, 8).tint(0, 0.75, 0) )
- .add(new TG.Number().tint(0.06, 0, 0.085) )
+ .add(new TG.Fill().tint(0.06, 0, 0.085) )
.addToPage("Example 2");
new TG.Texture(size, size)
.set(new TG.FractalNoise().baseFrequency(128).octaves(6).step(2).interpolation(2) )
.sub(new TG.Noise().tint(0, 0.1, 0) )
- .min(new TG.Number().tint(0, 0.63, 0) )
- .sub(new TG.Number().tint(0, 0.53, 0) )
- .mul(new TG.Number().tint(0, 6, 0) )
+ .min(new TG.Fill().tint(0, 0.63, 0) )
+ .sub(new TG.Fill().tint(0, 0.53, 0) )
+ .mul(new TG.Fill().tint(0, 6, 0) )
.sub(new TG.SineDistort().sines(2, 2).amplitude(4, 4).tint(0, 0.75, 0) )
- .add(new TG.Number().tint(0.065, 0, 0.095) )
+ .add(new TG.Fill().tint(0.065, 0, 0.095) )
.addToPage("Example 2 (less distortion)");
line = document.createElement("br");
diff --git a/src/TexGen.js b/src/TexGen.js
index f1ca8a0..f47fe3a 100644
--- a/src/TexGen.js
+++ b/src/TexGen.js
@@ -2,635 +2,511 @@
* @author mrdoob / http://mrdoob.com/
*/
-var TG = {
- OP: {
- SET: function ( x, y ) { return y; },
- ADD: function ( x, y ) { return x + y; },
- SUB: function ( x, y ) { return x - y; },
- MUL: function ( x, y ) { return x * y; },
- DIV: function ( x, y ) { return x / y; },
- AND: function ( x, y ) { return x & y; },
- XOR: function ( x, y ) { return x ^ y; },
- MIN: function ( x, y ) { return Math.min( x, y ); },
- MAX: function ( x, y ) { return Math.max( x, y ); }
- }
-};
-
-TG.Texture = function ( width, height ) {
+var TG = {};
- this.color = new Float32Array( 4 );
+//
- this.buffer = new TG.Buffer( width, height );
- this.bufferCopy = new TG.Buffer( width, height );
+TG.Utils = {
-};
+ globalSeed: Date.now(), // used as a somewhat random seed, increased on each use so that each default seed is different
-TG.Texture.prototype = {
+ smoothStep: function ( edge0, edge1, x ) {
- constructor: TG.Texture,
+ // Scale, bias and saturate x to 0..1 range
+ x = TG.Utils.clamp( ( x - edge0 ) / ( edge1 - edge0 ), 0, 1 );
- set: function ( program, operation ) {
+ // Evaluate polynomial
+ return x * x * ( 3 - 2 * x );
- if ( operation === undefined ) operation = TG.OP.SET;
+ },
- this.bufferCopy.copy( this.buffer );
+ mixColors: function( c1, c2, delta ) {
- var string = [
- 'var x = 0, y = 0;',
- 'var array = dst.array;',
- 'var width = dst.width, height = dst.height;',
- 'for ( var i = 0, il = array.length; i < il; i += 4 ) {',
- ' ' + program.getSource(),
- ' array[ i ] = op( array[ i ], color[ 0 ] * tint[ 0 ] );',
- ' array[ i + 1 ] = op( array[ i + 1 ], color[ 1 ] * tint[ 1 ] );',
- ' array[ i + 2 ] = op( array[ i + 2 ], color[ 2 ] * tint[ 2 ] );',
- ' if ( ++x === width ) { x = 0; y ++; }',
- '}'
- ].join( '\n' );
+ return [
+ c1[ 0 ] * ( 1 - delta ) + c2[ 0 ] * delta,
+ c1[ 1 ] * ( 1 - delta ) + c2[ 1 ] * delta,
+ c1[ 2 ] * ( 1 - delta ) + c2[ 2 ] * delta,
+ c1[ 3 ] * ( 1 - delta ) + c2[ 3 ] * delta,
+ ];
+ },
- new Function( 'op, dst, src, color, params, tint', string )( operation, this.buffer, this.bufferCopy, this.color, program.getParams(), program.getTint() );
+ distance: function( x0, y0, x1, y1 ) {
- return this;
+ var dx = x1 - x0, dy = y1 - y0;
+ return Math.sqrt( dx * dx + dy * dy );
},
- add: function ( program ) { return this.set( program, TG.OP.ADD ); },
+ clamp: function( value, min, max ) {
- sub: function ( program ) { return this.set( program, TG.OP.SUB ); },
+ return Math.min( Math.max( value, min ), max );
- mul: function ( program ) { return this.set( program, TG.OP.MUL ); },
+ },
- div: function ( program ) { return this.set( program, TG.OP.DIV ); },
+ wrap: function ( value, min, max ) {
+ var v = value - min;
+ var r = max - min;
- and: function ( program ) { return this.set( program, TG.OP.AND ); },
+ return ( ( r + v % r ) % r ) + min;
+ },
- xor: function ( program ) { return this.set( program, TG.OP.XOR ); },
+ mirroredWrap: function ( value, min, max ) {
+ var v = value - min;
+ var r = ( max - min ) * 2;
- min: function ( program ) { return this.set( program, TG.OP.MIN ); },
+ v = ( r + v % r ) % r;
- max: function ( program ) { return this.set( program, TG.OP.MAX ); },
+ if ( v > max - min ) {
+ return ( r - v ) + min;
+ } else {
+ return v + min;
+ }
+ },
- toImageData: function ( context ) {
+ deg2rad: function ( deg ) {
- var buffer = this.buffer;
- var array = buffer.array;
+ return deg * Math.PI / 180;
- var imagedata = context.createImageData( buffer.width, buffer.height );
- var data = imagedata.data;
+ },
- for ( var i = 0, il = array.length; i < il; i += 4 ) {
+ hashRNG: function ( seed, x, y ) {
+ seed = Math.abs(seed % 0x7FFFFFFF) + 1;
- data[ i ] = array[ i ] * 255;
- data[ i + 1 ] = array[ i + 1 ] * 255;
- data[ i + 2 ] = array[ i + 2 ] * 255;
- data[ i + 3 ] = 255;
+ var a = ( seed * ( ( x ^ seed * 777 ) || 556 ) * ( ( y ^ seed * 314 ) || 989 ) + seed * 123 ) % 0x7FFFFFFF;
+ a = (a ^ 61) ^ (a >> 16);
+ a = a + (a << 3);
+ a = a ^ (a >> 4);
+ a = a * 0x27d4eb2d;
+ a = a ^ (a >> 15);
+ a = a / 0x7FFFFFFF;
- }
+ return a;
+ },
- return imagedata;
+ cellNoiseBase: function ( x, y, seed, density, weightRange ) {
+ var qx, qy, rx, ry, w, px, py, dx, dy;
+ var dist, value;
+ var shortest = Infinity;
+ density = Math.abs( density );
- },
+ for ( var sx = -2; sx <= 2; sx++ ) {
+ for ( var sy = -2; sy <= 2; sy++ ) {
+ qx = Math.ceil( x / density ) + sx;
+ qy = Math.ceil( y / density ) + sy;
- toCanvas: function ( canvas ) {
+ rx = TG.Utils.hashRNG( seed, qx, qy );
+ ry = TG.Utils.hashRNG( seed * 2, qx, qy );
+ w = ( weightRange > 0 ) ? 1 + TG.Utils.hashRNG( seed * 3, qx, qy ) * weightRange : 1;
- if ( canvas === undefined ) canvas = document.createElement( 'canvas' );
- canvas.width = this.buffer.width;
- canvas.height = this.buffer.height;
+ px = ( rx + qx ) * density;
+ py = ( ry + qy ) * density;
- var context = canvas.getContext( '2d' );
- var imagedata = this.toImageData( context );
+ dx = Math.abs( px - x );
+ dy = Math.abs( py - y );
- context.putImageData( imagedata, 0, 0 );
+ dist = ( dx * dx + dy * dy ) * w;
- return canvas;
+ if ( dist < shortest ) {
+ shortest = dist;
+ value = rx;
+ }
+ }
+ }
+ return { dist: Math.sqrt( shortest ), value: value };
}
};
-//
+TG.ColorInterpolatorMethod = {
+ STEP: 0,
+ LINEAR: 1,
+ SPLINE: 2,
+ COSINE: 3,
+};
-TG.Program = function ( object ) {
+TG.ColorInterpolator = function( method ) {
- var tint = new Float32Array( [ 1, 1, 1 ] );
+ this.points = []; // points must be a set pair (point, color): [{ pos-n: [r,g,b,a] } , ..., { pos-N: [r,g,b,a] } ]
+ this.low = 0;
+ this.high = 0;
+ this.interpolation = ( typeof( method ) == 'undefined' ) ? TG.ColorInterpolatorMethod.LINEAR : method;
+ this.repeat = false;
- object.tint = function ( r, g, b ) {
- tint[ 0 ] = r;
- tint[ 1 ] = g;
- tint[ 2 ] = b;
- return this;
- };
+ return this;
+};
- object.getTint = function () {
- return tint;
- };
+TG.ColorInterpolator.prototype = {
- return object;
+ set: function ( points ) {
-};
+ this.points = points;
+ this.points.sort( function( a, b ) {
+ return a.pos - b.pos;
+ });
-TG.Number = function () {
+ this.low = this.points[ 0 ].pos;
+ this.high = this.points[ this.points.length - 1 ].pos;
- return new TG.Program( {
- getParams: function () {},
- getSource: function () {
- return [
- 'color[ 0 ] = 1;',
- 'color[ 1 ] = 1;',
- 'color[ 2 ] = 1;'
- ].join('\n');
+ return this;
+
+ },
+
+ addPoint: function ( position, r, g, b ) {
+ if ( Array.isArray( r ) ) {
+ b = r[ 2 ]; g = r[ 1 ]; r = r[ 0 ];
}
- } );
+ if ( typeof g == 'undefined' ) g = r;
+ if ( typeof b == 'undefined' ) b = r;
-};
+ this.points.push( { pos: position, color: [ r, g, b ] } );
+ this.points.sort( function( a, b ) {
+ return a.pos - b.pos;
+ });
-TG.SinX = function () {
+ this.low = this.points[ 0 ].pos;
+ this.high = this.points[ this.points.length - 1 ].pos;
- var params = {
- frequency: 1,
- offset: 0
- };
+ return this;
- return new TG.Program( {
- frequency: function ( value ) {
- params.frequency = value * Math.PI;
- return this;
- },
- offset: function ( value ) {
- params.offset = value;
- return this;
- },
- getParams: function () {
- return params;
- },
- getSource: function () {
- return [
- 'var value = Math.sin( ( x + params.offset ) * params.frequency );',
- 'color[ 0 ] = value;',
- 'color[ 1 ] = value;',
- 'color[ 2 ] = value;'
- ].join('\n');
- }
- } );
+ },
-};
+ setRepeat: function ( value ) {
-TG.SinY = function () {
+ this.repeat = value;
+ return this;
- var params = {
- frequency: 1,
- offset: 0
- };
+ },
- return new TG.Program( {
- frequency: function ( value ) {
- params.frequency = value * Math.PI;
- return this;
- },
- offset: function ( value ) {
- params.offset = value;
- return this;
- },
- getParams: function () {
- return params;
- },
- getSource: function () {
- return [
- 'var value = Math.sin( ( y + params.offset ) * params.frequency );',
- 'color[ 0 ] = value;',
- 'color[ 1 ] = value;',
- 'color[ 2 ] = value;'
- ].join('\n');
- }
- } );
+ setInterpolation: function ( value ) {
-};
+ this.interpolation = value;
+ return this;
-TG.OR = function () {
+ },
- return new TG.Program( {
- getParams: function () {},
- getSource: function () {
- return [
- 'var value = ( x | y ) / width;',
- 'color[ 0 ] = value;',
- 'color[ 1 ] = value;',
- 'color[ 2 ] = value;'
- ].join('\n');
- }
- } );
+ getColorAt: function ( pos ) {
-};
+ if ( this.repeat == 2 ) pos = TG.Utils.mirroredWrap( pos, this.low, this.high );
+ else if ( this.repeat ) pos = TG.Utils.wrap( pos, this.low, this.high );
+ else pos = TG.Utils.clamp( pos, this.low, this.high );
-TG.XOR = function () {
+ var i = 0, points = this.points;
- return new TG.Program( {
- getParams: function () {},
- getSource: function () {
- return [
- 'var value = ( x ^ y ) / width;',
- 'color[ 0 ] = value;',
- 'color[ 1 ] = value;',
- 'color[ 2 ] = value;'
- ].join('\n');
- }
- } );
+ while ( points[ i + 1 ].pos < pos ) i ++;
-};
+ var p1 = points[ i ];
+ var p2 = points[ i + 1 ];
-TG.Noise = function () {
+ var delta = ( pos - p1.pos ) / ( p2.pos - p1.pos );
- var params = {
- seed: Date.now()
- };
+ if ( this.interpolation == TG.ColorInterpolatorMethod.STEP ) {
- return new TG.Program( {
- seed: function ( value ) {
- params.seed = value;
- return this;
- },
- getParams: function () {
- return params;
- },
- getSource: function () {
- return [
- 'var value = TG.Utils.hashRNG( params.seed, x, y );',
- 'color[ 0 ] = value;',
- 'color[ 1 ] = value;',
- 'color[ 2 ] = value;'
- ].join('\n');
- }
- } );
+ return p1.color;
-};
+ } else if ( this.interpolation == TG.ColorInterpolatorMethod.LINEAR ) {
-TG.FractalNoise = function () {
+ return TG.Utils.mixColors( p1.color, p2.color, delta );
- var params = {
- interpolator: new TG.ColorInterpolator( TG.ColorInterpolatorMethod.STEP ),
- seed: Date.now(),
- baseFrequency: 0.03125,
- amplitude: 0.4,
- persistence: 0.72,
- octaves: 4,
- step: 4
- };
+ } else if ( this.interpolation == TG.ColorInterpolatorMethod.SPLINE ) {
- return new TG.Program( {
- seed: function ( value ) {
- params.seed = value;
- return this;
- },
- baseFrequency: function ( value ) {
- params.baseFrequency = 1 / value;
- return this;
- },
- amplitude: function ( value ) {
- params.amplitude = value;
- return this;
- },
- persistence: function ( value ) {
- params.persistence = value;
- return this;
- },
- octaves: function ( value ) {
- params.octaves = Math.max( 1, value );
- return this;
- },
- step: function ( value ) {
- params.step = Math.max( 1, value );
- return this;
- },
- interpolation: function ( value ) {
- params.interpolator.setInterpolation( value );
- return this;
- },
- getParams: function () {
- return params;
- },
- getSource: function () {
- return [
- 'var value = 0;',
- 'var amp = params.amplitude;',
- 'var freq = params.baseFrequency;',
- 'var x1, y1, dx, dy;',
- 'var v1, v2, v3, v4;',
- 'var i1, i2;',
+ var ar = 2 * p1.color[ 0 ] - 2 * p2.color[ 0 ];
+ var br = -3 * p1.color[ 0 ] + 3 * p2.color[ 0 ];
+ var dr = p1.color[ 0 ];
- 'for ( var j = 1; j <= params.octaves; j++ ) {',
- 'x1 = Math.floor( x * freq ), y1 = Math.floor( y * freq );',
+ var ag = 2 * p1.color[ 1 ] - 2 * p2.color[ 1 ];
+ var bg = -3 * p1.color[ 1 ] + 3 * p2.color[ 1 ];
+ var dg = p1.color[ 1 ];
- 'if ( params.interpolator.interpolation == TG.ColorInterpolatorMethod.STEP ) {',
- 'value += TG.Utils.hashRNG( params.seed * j, x1, y1 ) * amp;',
- '} else {',
- 'dx = ( x * freq ) - x1, dy = ( y * freq ) - y1;',
+ var ab = 2 * p1.color[ 2 ] - 2 * p2.color[ 2 ];
+ var bb = -3 * p1.color[ 2 ] + 3 * p2.color[ 2 ];
+ var db = p1.color[ 2 ];
- 'v1 = TG.Utils.hashRNG( params.seed * j, x1 , y1 );',
- 'v2 = TG.Utils.hashRNG( params.seed * j, x1 + 1, y1 );',
- 'v3 = TG.Utils.hashRNG( params.seed * j, x1 , y1 + 1 );',
- 'v4 = TG.Utils.hashRNG( params.seed * j, x1 + 1, y1 + 1 );',
+ var delta2 = delta * delta;
+ var delta3 = delta2 * delta;
- 'params.interpolator.set( [',
- '{ pos: 0, color: [ v1 ] },',
- '{ pos: 1, color: [ v2 ] }',
- '] );',
+ return [
+ ar * delta3 + br * delta2 + dr,
+ ag * delta3 + bg * delta2 + dg,
+ ab * delta3 + bb * delta2 + db
+ ];
- 'i1 = params.interpolator.getColorAt( dx );',
+ } else if ( this.interpolation == TG.ColorInterpolatorMethod.COSINE ) {
+ var cos = (1 - Math.cos(delta * Math.PI)) / 2;
- 'params.interpolator.set( [',
- '{ pos: 0, color: [ v3 ] },',
- '{ pos: 1, color: [ v4 ] }',
- '] );',
+ return [
+ ( p1.color[ 0 ] * ( 1 - cos ) ) + ( p2.color[ 0 ] * cos ),
+ ( p1.color[ 1 ] * ( 1 - cos ) ) + ( p2.color[ 1 ] * cos ),
+ ( p1.color[ 2 ] * ( 1 - cos ) ) + ( p2.color[ 2 ] * cos ),
+ ];
- 'i2 = params.interpolator.getColorAt( dx );',
+ }
- 'params.interpolator.set( [',
- '{ pos: 0, color: [ i1[ 0 ] ] },',
- '{ pos: 1, color: [ i2[ 0 ] ] }',
- '] );',
+ }
- 'value += params.interpolator.getColorAt( dy )[ 0 ] * amp;',
- '}',
+};
- 'freq *= params.step;',
- 'amp *= params.persistence;',
- '}',
+//
- 'color[ 0 ] = value;',
- 'color[ 1 ] = value;',
- 'color[ 2 ] = value;',
- ].join('\n');
- }
- } );
+TG.Buffer = function ( width, height ) {
-};
+ this.width = width;
+ this.height = height;
-TG.CellularNoise = function () {
+ this.array = new Float32Array( width * height * 4 );
+ this.color = new Float32Array( 4 );
- var params = {
- seed: Date.now(),
- density: 32,
- weightRange: 0
- };
+};
- return new TG.Program( {
- seed: function ( value ) {
- params.seed = value;
- return this;
- },
- density: function ( value ) {
- params.density = value;
- return this;
- },
- weightRange: function ( value ) {
- params.weightRange = Math.max( 0, value );
- return this;
- },
- getParams: function () {
- return params;
- },
- getSource: function () {
- return [
- 'var p = TG.Utils.cellNoiseBase( x, y, params.seed, params.density, params.weightRange );',
+TG.Buffer.prototype = {
- 'var value = 1 - ( p.dist / params.density );',
- 'if ( params.density < 0 ) value -= 1;',
+ constructor: TG.Buffer,
- 'color[ 0 ] = value;',
- 'color[ 1 ] = value;',
- 'color[ 2 ] = value;'
- ].join('\n');
- }
- } );
+ copy: function ( buffer ) {
-};
+ this.array.set( buffer.array );
-TG.VoronoiNoise = function () {
+ },
- var params = {
- seed: Date.now(),
- density: 32,
- weightRange: 0
- };
+ getPixelNearest: function ( x, y ) {
- return new TG.Program( {
- seed: function ( value ) {
- params.seed = value;
- return this;
- },
- density: function ( value ) {
- params.density = value;
- return this;
- },
- weightRange: function ( value ) {
- params.weightRange = Math.max( 0, value );
- return this;
- },
- getParams: function () {
- return params;
- },
- getSource: function () {
- return [
- 'var p = TG.Utils.cellNoiseBase( x, y, params.seed, params.density, params.weightRange );',
+ if ( y >= this.height ) y -= this.height;
+ if ( y < 0 ) y += this.height;
+ if ( x >= this.width ) x -= this.width;
+ if ( x < 0 ) x += this.width;
- 'color[ 0 ] = p.value;',
- 'color[ 1 ] = p.value;',
- 'color[ 2 ] = p.value;'
- ].join('\n');
- }
- } );
+ var array = this.array;
+ var color = this.color;
+ var offset = Math.round( y ) * this.width * 4 + Math.round( x ) * 4;
-};
+ color[ 0 ] = array[ offset ];
+ color[ 1 ] = array[ offset + 1 ];
+ color[ 2 ] = array[ offset + 2 ];
-TG.CellularFractal = function () {
+ return this.color;
- var params = {
- seed: Date.now(),
- weightRange: 0,
- baseDensity: 64,
- amplitude: 0.7,
- persistence: 0.45,
- octaves: 4,
- step: 2
- };
+ },
- return new TG.Program( {
- seed: function ( value ) {
- params.seed = value;
- return this;
- },
- baseDensity: function ( value ) {
- params.baseDensity = value;
- return this;
- },
- weightRange: function ( value ) {
- params.weightRange = Math.max( 0, value );
- return this;
- },
- amplitude: function ( value ) {
- params.amplitude = value;
- return this;
- },
- persistence: function ( value ) {
- params.persistence = value;
- return this;
- },
- octaves: function ( value ) {
- params.octaves = Math.max( 1, value );
- return this;
- },
- step: function ( value ) {
- params.step = Math.max( 1, value );
- return this;
- },
- getParams: function () {
- return params;
- },
- getSource: function () {
- return [
- 'var p;',
- 'var value = 0;',
- 'var amp = params.amplitude;',
- 'var dens = params.baseDensity;',
+ getPixelBilinear: function ( x, y ) {
- 'for ( var j = 1; j <= params.octaves; j++ ) {',
- 'p = TG.Utils.cellNoiseBase( x, y, params.seed * j, dens, params.weightRange );',
+ var px = Math.floor( x );
+ var py = Math.floor( y );
+ var p0 = px + py * this.width;
- 'p.dist = 1 - ( p.dist / dens );',
- 'if ( dens < 0 ) p.dist -= 1;',
+ var array = this.array;
+ var color = this.color;
- 'value += p.dist * amp;',
- 'dens /= params.step;',
- 'amp *= params.persistence;',
- '}',
+ // Calculate the weights for each pixel
+ var fx = x - px;
+ var fy = y - py;
+ var fx1 = 1 - fx;
+ var fy1 = 1 - fy;
- 'color[ 0 ] = value;',
- 'color[ 1 ] = value;',
- 'color[ 2 ] = value;',
- ].join('\n');
- }
- } );
+ var w1 = fx1 * fy1;
+ var w2 = fx * fy1;
+ var w3 = fx1 * fy ;
+ var w4 = fx * fy ;
+
+ var p1 = p0 * 4; // 0 + 0 * w
+ var p2 = ( 1 + p0 ) * 4; // 1 + 0 * w
+ var p3 = ( 1 * this.width + p0 ) * 4; // 0 + 1 * w
+ var p4 = ( 1 + 1 * this.width + p0 ) * 4; // 1 + 1 * w
+
+ var len = this.width * this.height * 4;
+
+ if ( p1 >= len ) p1 -= len;
+ if ( p1 < 0 ) p1 += len;
+ if ( p2 >= len ) p2 -= len;
+ if ( p2 < 0 ) p2 += len;
+ if ( p3 >= len ) p3 -= len;
+ if ( p3 < 0 ) p3 += len;
+ if ( p4 >= len ) p4 -= len;
+ if ( p4 < 0 ) p4 += len;
+
+ // Calculate the weighted sum of pixels (for each color channel)
+ color[ 0 ] = array[ p1 + 0 ] * w1 + array[ p2 + 0 ] * w2 + array[ p3 + 0 ] * w3 + array[ p4 + 0 ] * w4;
+ color[ 1 ] = array[ p1 + 1 ] * w1 + array[ p2 + 1 ] * w2 + array[ p3 + 1 ] * w3 + array[ p4 + 1 ] * w4;
+ color[ 2 ] = array[ p1 + 2 ] * w1 + array[ p2 + 2 ] * w2 + array[ p3 + 2 ] * w3 + array[ p4 + 2 ] * w4;
+ color[ 3 ] = array[ p1 + 3 ] * w1 + array[ p2 + 3 ] * w2 + array[ p3 + 3 ] * w3 + array[ p4 + 3 ] * w4;
+
+ return this.color;
+ },
+
+ getPixelOffset: function ( offset ) {
+
+ var array = this.array;
+ var color = this.color;
+
+ offset = parseInt( offset * 4 );
+
+ color[ 0 ] = array[ offset ];
+ color[ 1 ] = array[ offset + 1 ];
+ color[ 2 ] = array[ offset + 2 ];
+ color[ 3 ] = array[ offset + 3 ];
+
+ return this.color;
+ },
};
-TG.VoronoiFractal = function () {
+TG.OP = {
+ SET: function ( x, y ) { return y; },
+ ADD: function ( x, y ) { return x + y; },
+ SUB: function ( x, y ) { return x - y; },
+ MUL: function ( x, y ) { return x * y; },
+ DIV: function ( x, y ) { return x / y; },
+ AND: function ( x, y ) { return x & y; },
+ XOR: function ( x, y ) { return x ^ y; },
+ MIN: function ( x, y ) { return Math.min( x, y ); },
+ MAX: function ( x, y ) { return Math.max( x, y ); },
+ POW: function ( x, y ) { return Math.pow( x, y ); }
+};
- var params = {
- seed: Date.now(),
- weightRange: 0,
- baseDensity: 64,
- amplitude: 0.6,
- persistence: 0.6,
- octaves: 4,
- step: 2
- };
+TG.Texture = function ( width, height ) {
- return new TG.Program( {
- seed: function ( value ) {
- params.seed = value;
- return this;
- },
- baseDensity: function ( value ) {
- params.baseDensity = value;
- return this;
- },
- weightRange: function ( value ) {
- params.weightRange = Math.max( 0, value );
- return this;
- },
- amplitude: function ( value ) {
- params.amplitude = value;
- return this;
- },
- persistence: function ( value ) {
- params.persistence = value;
- return this;
- },
- octaves: function ( value ) {
- params.octaves = Math.max( 1, value );
- return this;
- },
- step: function ( value ) {
- params.step = Math.max( 1, value );
- return this;
- },
- getParams: function () {
- return params;
- },
- getSource: function () {
- return [
- 'var p;',
- 'var value = 0;',
- 'var amp = params.amplitude;',
- 'var dens = params.baseDensity;',
+ this.color = new Float32Array( 4 );
- 'for ( var j = 1; j <= params.octaves; j++ ) {',
- 'p = TG.Utils.cellNoiseBase( x, y, params.seed * j, dens, params.weightRange );',
+ this.buffer = new TG.Buffer( width, height );
+ this.bufferCopy = new TG.Buffer( width, height );
- 'value += p.value * amp;',
- 'dens /= params.step;',
- 'amp *= params.persistence;',
- '}',
+};
+
+TG.Texture.prototype = {
+
+ constructor: TG.Texture,
+
+ set: function ( program, operation ) {
+
+ if ( operation === undefined ) operation = TG.OP.SET;
+
+ this.bufferCopy.copy( this.buffer );
+
+ var string = [
+ 'var x = 0, y = 0;',
+ 'var array = dst.array;',
+ 'var width = dst.width, height = dst.height;',
+ ( typeof program.getInit == 'function' ) ? program.getInit() : '',
+ 'for ( var i = 0, il = array.length; i < il; i += 4 ) {',
+ ' ' + program.getSource(),
+ ' array[ i ] = op( array[ i ], color[ 0 ] * tint[ 0 ] );',
+ ' array[ i + 1 ] = op( array[ i + 1 ], color[ 1 ] * tint[ 1 ] );',
+ ' array[ i + 2 ] = op( array[ i + 2 ], color[ 2 ] * tint[ 2 ] );',
+ ' if ( ++x === width ) { x = 0; y ++; }',
+ '}'
+ ].join( '\n' );
+
+ new Function( 'op, dst, src, color, params, tint', string )( operation, this.buffer, this.bufferCopy, this.color, program.getParams(), program.getTint() );
+
+ return this;
+
+ },
+
+ add: function ( program ) { return this.set( program, TG.OP.ADD ); },
+
+ sub: function ( program ) { return this.set( program, TG.OP.SUB ); },
+
+ mul: function ( program ) { return this.set( program, TG.OP.MUL ); },
+
+ div: function ( program ) { return this.set( program, TG.OP.DIV ); },
+
+ and: function ( program ) { return this.set( program, TG.OP.AND ); },
+
+ xor: function ( program ) { return this.set( program, TG.OP.XOR ); },
+
+ min: function ( program ) { return this.set( program, TG.OP.MIN ); },
+
+ max: function ( program ) { return this.set( program, TG.OP.MAX ); },
+
+ pow: function ( program ) { return this.set( program, TG.OP.POW ); },
+
+ toImageData: function ( context ) {
+
+ var buffer = this.buffer;
+ var array = buffer.array;
+
+ var imagedata = context.createImageData( buffer.width, buffer.height );
+ var data = imagedata.data;
+
+ for ( var i = 0, il = array.length; i < il; i += 4 ) {
+
+ data[ i ] = array[ i ] * 255;
+ data[ i + 1 ] = array[ i + 1 ] * 255;
+ data[ i + 2 ] = array[ i + 2 ] * 255;
+ data[ i + 3 ] = 255;
- 'color[ 0 ] = value;',
- 'color[ 1 ] = value;',
- 'color[ 2 ] = value;',
- ].join('\n');
}
- } );
+
+ return imagedata;
+
+ },
+
+ toCanvas: function ( canvas ) {
+
+ if ( canvas === undefined ) canvas = document.createElement( 'canvas' );
+ canvas.width = this.buffer.width;
+ canvas.height = this.buffer.height;
+
+ var context = canvas.getContext( '2d' );
+ var imagedata = this.toImageData( context );
+
+ context.putImageData( imagedata, 0, 0 );
+
+ return canvas;
+
+ }
};
-TG.CheckerBoard = function () {
+TG.Program = function ( object ) {
- var params = {
- size: [ 32, 32 ],
- offset: [ 0, 0 ],
- rowShift: 0
+ var tint = new Float32Array( [ 1, 1, 1 ] );
+
+ object.tint = function ( r, g, b ) { // multiplies each color channel of the generated image by 'r', 'g' and 'b' respectively
+ tint[ 0 ] = r;
+ tint[ 1 ] = ( typeof g == 'undefined' ) ? r : g;
+ tint[ 2 ] = ( typeof b == 'undefined' ) ? r : b;
+ return this;
+ };
+
+ object.getTint = function () {
+ return tint;
};
+ return object;
+
+};
+
+// --- Generators ---
+
+TG.Fill = function () { // every pixel set to 1; together with tint can be used to do basic calculations (e.g. .mul( TG.Fill().tint( 2 ) ) -> double the value of each pixel)
+
return new TG.Program( {
- size: function ( x, y ) {
- params.size = [ x, y ];
- return this;
- },
- offset: function ( x, y ) {
- params.offset = [ x, y ];
- return this;
- },
- rowShift: function ( value ) {
- params.rowShift = value;
- return this;
- },
- getParams: function () {
- return params;
- },
+ getParams: function () {},
getSource: function () {
return [
- 'var value = ( ( ( y + params.offset[ 1 ] ) / params.size[ 1 ] ) & 1 ) ^ ( ( ( x + params.offset[ 0 ] + parseInt( y / params.size[ 1 ] ) * params.rowShift ) / params.size[ 0 ] ) & 1 ) ? 0 : 1',
- 'color[ 0 ] = value;',
- 'color[ 1 ] = value;',
- 'color[ 2 ] = value;'
+ 'color[ 0 ] = 1;',
+ 'color[ 1 ] = 1;',
+ 'color[ 2 ] = 1;'
].join('\n');
}
} );
};
-TG.Rect = function () {
+TG.SinX = function () { // generates a sine wave on the x and value (brightness) axes
var params = {
- position: [ 0, 0 ],
- size: [ 32, 32 ]
+ frequency: 1,
+ offset: 0
};
return new TG.Program( {
- position: function ( x, y ) {
- params.position = [ x, y ];
+ frequency: function ( value ) { // sets the 'width' of the sine wave in pixels
+ params.frequency = ( 2 / value ) * Math.PI;
return this;
},
- size: function ( x, y ) {
- params.size = [ x, y ];
+ offset: function ( value ) { // moves the wave by 'value' pixels
+ params.offset = -value;
return this;
},
getParams: function () {
@@ -638,7 +514,7 @@ TG.Rect = function () {
},
getSource: function () {
return [
- 'var value = ( x >= params.position[ 0 ] && x <= ( params.position[ 0 ] + params.size[ 0 ] ) && y <= ( params.position[ 1 ] + params.size[ 1 ] ) && y >= params.position[ 1 ] ) ? 1 : 0;',
+ 'var value = Math.sin( ( x + params.offset ) * params.frequency );',
'color[ 0 ] = value;',
'color[ 1 ] = value;',
'color[ 2 ] = value;'
@@ -648,25 +524,20 @@ TG.Rect = function () {
};
-TG.Circle = function () {
+TG.SinY = function () { // generates a sine wave on the y and value (brightness) axes
var params = {
- position: [ 0, 0 ],
- radius: 50,
- delta: 1
+ frequency: 1,
+ offset: 0
};
return new TG.Program( {
- delta: function ( value ) {
- params.delta = value;
+ frequency: function ( value ) { // sets the 'width' of the sine wave in pixels
+ params.frequency = ( 2 / value ) * Math.PI;
return this;
},
- position: function ( x, y ) {
- params.position = [ x, y ];
- return this;
- },
- radius: function ( value ) {
- params.radius = value;
+ offset: function ( value ) { // moves the wave by 'value' pixels
+ params.offset = -value;
return this;
},
getParams: function () {
@@ -674,8 +545,7 @@ TG.Circle = function () {
},
getSource: function () {
return [
- 'var dist = TG.Utils.distance( x, y, params.position[ 0 ], params.position[ 1 ] );',
- 'var value = 1 - TG.Utils.smoothStep( params.radius - params.delta, params.radius, dist );',
+ 'var value = Math.sin( ( y + params.offset ) * params.frequency );',
'color[ 0 ] = value;',
'color[ 1 ] = value;',
'color[ 2 ] = value;'
@@ -685,85 +555,52 @@ TG.Circle = function () {
};
-TG.PutTexture = function ( texture ) {
-
- var params = {
- offset: [ 0, 0 ],
- repeat: false,
- srcTex: texture.buffer
- };
+TG.OR = function () { // generates a pattern using bitwise OR on the x and y coordinates
return new TG.Program( {
- offset: function ( x, y ) {
- params.offset = [ x, y ];
- return this;
- },
- repeat: function ( value ) {
- params.repeat = value;
- return this;
- },
- getParams: function () {
- return params;
- },
+ getParams: function () {},
getSource: function () {
return [
- 'var texWidth = params.srcTex.width;',
- 'var texHeight = params.srcTex.height;',
-
- 'var texX = Math.floor( x - params.offset[ 0 ] );',
- 'var texY = Math.floor( y - params.offset[ 1 ] );',
+ 'var value = ( x | y ) / width;',
+ 'color[ 0 ] = value;',
+ 'color[ 1 ] = value;',
+ 'color[ 2 ] = value;'
+ ].join('\n');
+ }
+ } );
- 'if ( texX >= texWidth || texY >= texHeight || texX < 0 || texY < 0 ) {',
- 'if ( params.repeat ) {',
- 'var nx, ny;',
- 'var rangeX = texWidth - 1;',
- 'var rangeY = texHeight - 1;',
+};
- 'if ( params.repeat == 1 ) {',
- 'nx = TG.Utils.wrap( texX, 0, texWidth );',
- 'ny = TG.Utils.wrap( texY, 0, texHeight );',
- '} else if ( params.repeat == 2 ) {',
- 'nx = TG.Utils.mirroredWrap( texX, 0, rangeX );',
- 'ny = TG.Utils.mirroredWrap( texY, 0, rangeY );',
- '} else if ( params.repeat == 3 ) {',
- 'nx = TG.Utils.clamp( texX, 0, rangeX );',
- 'ny = TG.Utils.clamp( texY, 0, rangeY );',
- '}',
+TG.XOR = function () { // generates a pattern using bitwise XOR on the x and y coordinates
- 'color = params.srcTex.getPixelNearest( nx, ny );',
- '} else {',
- 'color[ 0 ] = 0;',
- 'color[ 1 ] = 0;',
- 'color[ 2 ] = 0;',
- '}',
- '} else color = params.srcTex.getPixelNearest( texX, texY );',
- ].join( '\n' );
+ return new TG.Program( {
+ getParams: function () {},
+ getSource: function () {
+ return [
+ 'var value = ( x ^ y ) / width;',
+ 'color[ 0 ] = value;',
+ 'color[ 1 ] = value;',
+ 'color[ 2 ] = value;'
+ ].join('\n');
}
} );
};
-// Filters
-
-TG.SineDistort = function () {
+TG.Noise = function () { // generates a random noise pattern
var params = {
- sines: [ 4, 4 ],
- offset: [ 0, 0 ],
- amplitude: [ 16, 16 ]
+ seed: TG.Utils.globalSeed++,
+ size: 1
};
return new TG.Program( {
- sines: function ( x, y ) {
- params.sines = [ x, y ];
- return this;
- },
- offset: function ( x, y ) {
- params.offset = [ x, y ];
+ seed: function ( value ) { // the same seed always results in the same noise pattern
+ params.seed = value;
return this;
},
- amplitude: function ( x, y ) {
- params.amplitude = [ x, y ];
+ size: function ( value ) { // sets the size of the pixel grid for the noise function
+ params.size = value;
return this;
},
getParams: function () {
@@ -771,34 +608,55 @@ TG.SineDistort = function () {
},
getSource: function () {
return [
- 'var s = Math.sin( params.sines[ 0 ] / 100 * y + params.offset[ 0 ] ) * params.amplitude[ 0 ] + x;',
- 'var t = Math.sin( params.sines[ 1 ] / 100 * x + params.offset[ 1 ] ) * params.amplitude[ 1 ] + y;',
- 'color.set( src.getPixelBilinear( s, t ) );',
- ].join( '\n' );
+ 'var value = TG.Utils.hashRNG( params.seed, Math.floor( x / params.size ), Math.floor( y / params.size ) );',
+ 'color[ 0 ] = value;',
+ 'color[ 1 ] = value;',
+ 'color[ 2 ] = value;'
+ ].join('\n');
}
} );
};
-TG.Twirl = function () {
+TG.FractalNoise = function () { // generates a noise pattern with extra 'depth' by overlaying noise of different sizes
var params = {
- strength: 0,
- radius: 120,
- position: [ 128, 128 ]
+ interpolator: new TG.ColorInterpolator( TG.ColorInterpolatorMethod.STEP ),
+ seed: TG.Utils.globalSeed++,
+ baseFrequency: 0.03125,
+ amplitude: 0.4,
+ persistence: 0.72,
+ octaves: 4,
+ step: 4
};
return new TG.Program( {
- strength: function ( value ) {
- params.strength = value / 100.0;
+ seed: function ( value ) { // the same seed always results in the same noise pattern
+ params.seed = value;
return this;
},
- radius: function ( value ) {
- params.radius = value;
+ baseFrequency: function ( value ) { // sets the size of the noise for the first octave in pixels
+ params.baseFrequency = 1 / value;
return this;
},
- position: function ( x, y ) {
- params.position = [ x, y ];
+ amplitude: function ( value ) { // sets how much 'contrast' the noise should initially have; gets decreased with each octave
+ params.amplitude = value;
+ return this;
+ },
+ persistence: function ( value ) { // how much the amplitude should be decreased with each octave
+ params.persistence = value;
+ return this;
+ },
+ octaves: function ( value ) { // how many different noise patterns are layered on top of each other
+ params.octaves = Math.max( 1, value );
+ return this;
+ },
+ step: function ( value ) { // how much the frequency gets decreased with each octave (e.g. a value of 2 halves the size each time)
+ params.step = Math.max( 0, value );
+ return this;
+ },
+ interpolation: function ( value ) { // which interpolation algorithm should be used for non-whole coordinates -> should the noise be smooth or rough? (see TG.ColorInterpolator below)
+ params.interpolator.setInterpolation( value );
return this;
},
getParams: function () {
@@ -806,113 +664,80 @@ TG.Twirl = function () {
},
getSource: function () {
return [
- 'var dist = TG.Utils.distance( x, y, params.position[ 0 ], params.position[ 1 ] );',
-
- // no distortion if outside of whirl radius.
- 'if (dist < params.radius) {',
- 'dist = Math.pow(params.radius - dist, 2) / params.radius;',
+ 'var value = 0;',
+ 'var amp = params.amplitude;',
+ 'var freq = params.baseFrequency;',
+ 'var x1, y1, dx, dy;',
+ 'var v1, v2, v3, v4;',
+ 'var i1, i2;',
- 'var angle = 2.0 * Math.PI * (dist / (params.radius / params.strength));',
- 'var s = (((x - params.position[ 0 ]) * Math.cos(angle)) - ((y - params.position[ 0 ]) * Math.sin(angle)) + params.position[ 0 ] + 0.5);',
- 'var t = (((y - params.position[ 1 ]) * Math.cos(angle)) + ((x - params.position[ 1 ]) * Math.sin(angle)) + params.position[ 1 ] + 0.5);',
- '} else {',
- 'var s = x;',
- 'var t = y;',
- '}',
+ 'for ( var j = 1; j <= params.octaves; j++ ) {',
+ 'x1 = Math.floor( x * freq ), y1 = Math.floor( y * freq );',
- 'color.set( src.getPixelBilinear( s, t ) );',
- ].join( '\n' );
- }
- } );
+ 'if ( params.interpolator.interpolation == TG.ColorInterpolatorMethod.STEP ) {',
+ 'value += TG.Utils.hashRNG( params.seed * j, x1, y1 ) * amp;',
+ '} else {',
+ 'dx = ( x * freq ) - x1, dy = ( y * freq ) - y1;',
-};
+ 'v1 = TG.Utils.hashRNG( params.seed * j, x1 , y1 );',
+ 'v2 = TG.Utils.hashRNG( params.seed * j, x1 + 1, y1 );',
+ 'v3 = TG.Utils.hashRNG( params.seed * j, x1 , y1 + 1 );',
+ 'v4 = TG.Utils.hashRNG( params.seed * j, x1 + 1, y1 + 1 );',
-TG.Transform = function () {
+ 'params.interpolator.set( [',
+ '{ pos: 0, color: [ v1 ] },',
+ '{ pos: 1, color: [ v2 ] }',
+ '] );',
- var params = {
- offset: [ 0, 0 ],
- angle: 0,
- scale: [ 1, 1 ]
- };
+ 'i1 = params.interpolator.getColorAt( dx )[ 0 ];',
- return new TG.Program( {
- offset: function ( x, y ) {
- params.offset = [ x, y ];
- return this;
- },
- angle: function ( value ) {
- params.angle = TG.Utils.deg2rad( value );
- return this;
- },
- scale: function ( x, y ) {
- if ( x === 0 || y === 0 ) return;
- params.scale = [ x, y ];
- return this;
- },
- getParams: function () {
- return params;
- },
- getSource: function () {
- return [
- 'var x2 = x - width / 2;',
- 'var y2 = y - height / 2;',
-
- 'var s = x2 * ( Math.cos( params.angle ) / params.scale[ 0 ] ) + y2 * -( Math.sin( params.angle ) / params.scale[ 0 ] );',
- 'var t = x2 * ( Math.sin( params.angle ) / params.scale[ 1 ] ) + y2 * ( Math.cos( params.angle ) / params.scale[ 1 ] );',
-
- 's += params.offset[ 0 ] + width / 2;',
- 't += params.offset[ 1 ] + height / 2;',
-
- 'color.set( src.getPixelBilinear( s, t ) );',
- ].join( '\n' );
- }
- } );
+ 'params.interpolator.set( [',
+ '{ pos: 0, color: [ v3 ] },',
+ '{ pos: 1, color: [ v4 ] }',
+ '] );',
-};
+ 'i2 = params.interpolator.getColorAt( dx )[ 0 ];',
-TG.Pixelate = function () {
+ 'params.interpolator.set( [',
+ '{ pos: 0, color: [ i1 ] },',
+ '{ pos: 1, color: [ i2 ] }',
+ '] );',
- var params = {
- size: [ 1, 1 ]
- };
+ 'value += params.interpolator.getColorAt( dy )[ 0 ] * amp;',
+ '}',
- return new TG.Program( {
- size: function ( x, y ) {
- params.size = [ x, y ];
- return this;
- },
- getParams: function () {
- return params;
- },
- getSource: function () {
- return [
- 'var s = params.size[ 0 ] * Math.floor(x/params.size[ 0 ]);',
- 'var t = params.size[ 1 ] * Math.floor(y/params.size[ 1 ]);',
+ 'freq *= params.step;',
+ 'amp *= params.persistence;',
+ '}',
- 'color.set( src.getPixelNearest( s, t ) );'
- ].join( '\n' );
+ 'color[ 0 ] = value;',
+ 'color[ 1 ] = value;',
+ 'color[ 2 ] = value;',
+ ].join('\n');
}
} );
};
-TG.GradientMap = function () {
+TG.CellularNoise = function () { // noise based on the distance of randomly distributed points on the xy-plane
var params = {
- gradient: new TG.ColorInterpolator( TG.ColorInterpolatorMethod.LINEAR )
+ seed: TG.Utils.globalSeed++,
+ density: 32,
+ weightRange: 0
};
return new TG.Program( {
- repeat: function ( value ) {
- params.gradient.setRepeat( value );
+ seed: function ( value ) { // the same seed always results in the same noise pattern
+ params.seed = value;
return this;
},
- interpolation: function ( value ) {
- params.gradient.setInterpolation( value );
+ density: function ( value ) { // the average distance betweeen each point in pixels; negative values invert the pattern
+ params.density = value;
return this;
},
- point: function ( position, color ) {
- params.gradient.addPoint( position, color );
+ weightRange: function ( value ) { // gives some points more or less 'influence' making them bigger or smaller; too high values can break the point finding algorithm!
+ params.weightRange = Math.max( 0, value );
return this;
},
getParams: function () {
@@ -920,67 +745,96 @@ TG.GradientMap = function () {
},
getSource: function () {
return [
- 'var v = src.getPixelNearest( x, y );',
+ 'var p = TG.Utils.cellNoiseBase( x, y, params.seed, params.density, params.weightRange );',
- 'var r = params.gradient.getColorAt( v[ 0 ] )[ 0 ];',
- 'var g = params.gradient.getColorAt( v[ 1 ] )[ 1 ];',
- 'var b = params.gradient.getColorAt( v[ 2 ] )[ 2 ];',
+ 'var value = 1 - ( p.dist / params.density );',
+ 'if ( params.density < 0 ) value -= 1;',
- 'color[ 0 ] = r;',
- 'color[ 1 ] = g;',
- 'color[ 2 ] = b;'
+ 'color[ 0 ] = value;',
+ 'color[ 1 ] = value;',
+ 'color[ 2 ] = value;'
].join('\n');
}
} );
+
};
-TG.Normalize = function () {
+TG.VoronoiNoise = function () { // noise based on voronoi diagrams of randomly distributed points on the xy-plane
var params = {
- multiplier: 0,
- offset: 0
+ seed: TG.Utils.globalSeed++,
+ density: 32,
+ weightRange: 0
};
return new TG.Program( {
+ seed: function ( value ) { // the same seed always results in the same noise pattern
+ params.seed = value;
+ return this;
+ },
+ density: function ( value ) { // the average distance betweeen each point in pixels
+ params.density = value;
+ return this;
+ },
+ weightRange: function ( value ) { // gives some points more or less 'influence'; too high values can break the point finding algorithm!
+ params.weightRange = Math.max( 0, value );
+ return this;
+ },
getParams: function () {
return params;
},
getSource: function () {
return [
- 'if ( !params.init ) {',
- 'var high = -Infinity;',
- 'var low = Infinity;',
-
- 'for ( var j = 0, len = src.array.length; j < len; j++ ) {',
- 'if ( j % 4 == 3 ) continue;',
-
- 'high = ( src.array[ j ] > high ) ? src.array[ j ] : high;',
- 'low = ( src.array[ j ] < low ) ? src.array[ j ] : low;',
- '}',
-
- 'params.offset = -low;',
- 'params.multiplier = 1 / ( high - low );',
- 'params.init = true;',
- '}',
+ 'var p = TG.Utils.cellNoiseBase( x, y, params.seed, params.density, params.weightRange );',
- 'var v = src.getPixelNearest( x, y );',
- 'color[ 0 ] = ( v[ 0 ] + params.offset ) * params.multiplier;',
- 'color[ 1 ] = ( v[ 1 ] + params.offset ) * params.multiplier;',
- 'color[ 2 ] = ( v[ 2 ] + params.offset ) * params.multiplier;'
- ].join( '\n' );
+ 'color[ 0 ] = p.value;',
+ 'color[ 1 ] = p.value;',
+ 'color[ 2 ] = p.value;'
+ ].join('\n');
}
} );
+
};
-TG.Posterize = function () {
+TG.CellularFractal = function () { // generates a noise pattern with extra 'depth' by overlaying cellular noise with different densities
var params = {
+ seed: TG.Utils.globalSeed++,
+ weightRange: 0,
+ baseDensity: 64,
+ amplitude: 0.7,
+ persistence: 0.45,
+ octaves: 4,
step: 2
};
return new TG.Program( {
- step: function ( value ) {
- params.step = Math.max( value, 2 )
+ seed: function ( value ) { // the same seed always results in the same noise pattern
+ params.seed = value;
+ return this;
+ },
+ baseDensity: function ( value ) { // sets the density for the first octave
+ params.baseDensity = value;
+ return this;
+ },
+ weightRange: function ( value ) { // sets the weightRange for the cellular noise; see TG.CellularNoise above
+ params.weightRange = Math.max( 0, value );
+ return this;
+ },
+ amplitude: function ( value ) { // sets how much 'contrast' the noise should initially have; gets decreased with each octave
+ params.amplitude = value;
+ return this;
+ },
+ persistence: function ( value ) { // how much the amplitude should be decreased with each octave
+ params.persistence = value;
+ return this;
+ },
+ octaves: function ( value ) { // how many different noise patterns are layered on top of each other
+ params.octaves = Math.max( 1, value );
+ return this;
+ },
+ step: function ( value ) { // how much the density gets decreased with each octave (e.g. a value of 2 halves the density each time)
+ params.step = Math.max( 1, value );
return this;
},
getParams: function () {
@@ -988,237 +842,264 @@ TG.Posterize = function () {
},
getSource: function () {
return [
- 'var v = src.getPixelNearest( x, y );',
- 'color[ 0 ] = Math.floor( Math.floor( v[ 0 ] * 255 / ( 255 / params.step ) ) * 255 / ( params.step - 1 ) ) / 255;',
- 'color[ 1 ] = Math.floor( Math.floor( v[ 1 ] * 255 / ( 255 / params.step ) ) * 255 / ( params.step - 1 ) ) / 255;',
- 'color[ 2 ] = Math.floor( Math.floor( v[ 2 ] * 255 / ( 255 / params.step ) ) * 255 / ( params.step - 1 ) ) / 255;'
- ].join( '\n' );
- }
- } );
-
-};
+ 'var p;',
+ 'var value = 0;',
+ 'var amp = params.amplitude;',
+ 'var dens = params.baseDensity;',
-// Buffer
+ 'for ( var j = 1; j <= params.octaves; j++ ) {',
+ 'p = TG.Utils.cellNoiseBase( x, y, params.seed * j, dens, params.weightRange );',
-TG.Buffer = function ( width, height ) {
+ 'p.dist = 1 - ( p.dist / dens );',
+ 'if ( dens < 0 ) p.dist -= 1;',
- this.width = width;
- this.height = height;
+ 'value += p.dist * amp;',
+ 'dens /= params.step;',
+ 'amp *= params.persistence;',
+ '}',
- this.array = new Float32Array( width * height * 4 );
- this.color = new Float32Array( 4 );
+ 'color[ 0 ] = value;',
+ 'color[ 1 ] = value;',
+ 'color[ 2 ] = value;',
+ ].join('\n');
+ }
+ } );
};
-TG.Buffer.prototype = {
-
- constructor: TG.Buffer,
-
- copy: function ( buffer ) {
-
- this.array.set( buffer.array );
+TG.VoronoiFractal = function () { // generates a noise pattern with extra 'depth' by overlaying voronoi noise with different densities
- },
-
- getPixelNearest: function ( x, y ) {
-
- if ( y >= this.height ) y -= this.height;
- if ( y < 0 ) y += this.height;
- if ( x >= this.width ) x -= this.width;
- if ( x < 0 ) x += this.width;
-
- var array = this.array;
- var color = this.color;
- var offset = Math.round( y ) * this.width * 4 + Math.round( x ) * 4;
-
- color[ 0 ] = array[ offset ];
- color[ 1 ] = array[ offset + 1 ];
- color[ 2 ] = array[ offset + 2 ];
-
- return this.color;
-
- },
-
- getPixelBilinear: function ( x, y ) {
-
- var px = Math.floor( x );
- var py = Math.floor( y );
- var p0 = px + py * this.width;
-
- var array = this.array;
- var color = this.color;
-
- // Calculate the weights for each pixel
- var fx = x - px;
- var fy = y - py;
- var fx1 = 1 - fx;
- var fy1 = 1 - fy;
-
- var w1 = fx1 * fy1;
- var w2 = fx * fy1;
- var w3 = fx1 * fy ;
- var w4 = fx * fy ;
-
- var p1 = p0 * 4; // 0 + 0 * w
- var p2 = ( 1 + p0 ) * 4; // 1 + 0 * w
- var p3 = ( 1 * this.width + p0 ) * 4; // 0 + 1 * w
- var p4 = ( 1 + 1 * this.width + p0 ) * 4; // 1 + 1 * w
-
- var len = this.width * this.height * 4;
-
- if ( p1 >= len ) p1 -= len;
- if ( p1 < 0 ) p1 += len;
- if ( p2 >= len ) p2 -= len;
- if ( p2 < 0 ) p2 += len;
- if ( p3 >= len ) p3 -= len;
- if ( p3 < 0 ) p3 += len;
- if ( p4 >= len ) p4 -= len;
- if ( p4 < 0 ) p4 += len;
-
- // Calculate the weighted sum of pixels (for each color channel)
- color[ 0 ] = array[ p1 + 0 ] * w1 + array[ p2 + 0 ] * w2 + array[ p3 + 0 ] * w3 + array[ p4 + 0 ] * w4;
- color[ 1 ] = array[ p1 + 1 ] * w1 + array[ p2 + 1 ] * w2 + array[ p3 + 1 ] * w3 + array[ p4 + 1 ] * w4;
- color[ 2 ] = array[ p1 + 2 ] * w1 + array[ p2 + 2 ] * w2 + array[ p3 + 2 ] * w3 + array[ p4 + 2 ] * w4;
- color[ 3 ] = array[ p1 + 3 ] * w1 + array[ p2 + 3 ] * w2 + array[ p3 + 3 ] * w3 + array[ p4 + 3 ] * w4;
-
- return this.color;
- },
-
- getPixelOffset: function ( offset ) {
+ var params = {
+ seed: TG.Utils.globalSeed++,
+ weightRange: 0,
+ baseDensity: 64,
+ amplitude: 0.6,
+ persistence: 0.6,
+ octaves: 4,
+ step: 2
+ };
- var array = this.array;
- var color = this.color;
+ return new TG.Program( {
+ seed: function ( value ) { // the same seed always results in the same noise pattern
+ params.seed = value;
+ return this;
+ },
+ baseDensity: function ( value ) { // sets the density for the first octave
+ params.baseDensity = value;
+ return this;
+ },
+ weightRange: function ( value ) { // sets the weightRange for the voronoi noise; see TG.VoronoiNoise above
+ params.weightRange = Math.max( 0, value );
+ return this;
+ },
+ amplitude: function ( value ) { // sets how much 'contrast' the noise should initially have; gets decreased with each octave
+ params.amplitude = value;
+ return this;
+ },
+ persistence: function ( value ) { // how much the amplitude should be decreased with each octave (values over 1 increase the amplitude instead)
+ params.persistence = value;
+ return this;
+ },
+ octaves: function ( value ) { // how many different noise patterns are layered on top of each other
+ params.octaves = Math.max( 1, value );
+ return this;
+ },
+ step: function ( value ) { // how much the density gets decreased with each octave (e.g. a value of 2 halves the density each time)
+ params.step = Math.max( 1, value );
+ return this;
+ },
+ getParams: function () {
+ return params;
+ },
+ getSource: function () {
+ return [
+ 'var p;',
+ 'var value = 0;',
+ 'var amp = params.amplitude;',
+ 'var dens = params.baseDensity;',
- offset = parseInt( offset * 4 );
+ 'for ( var j = 1; j <= params.octaves; j++ ) {',
+ 'p = TG.Utils.cellNoiseBase( x, y, params.seed * j, dens, params.weightRange );',
- color[ 0 ] = array[ offset ];
- color[ 1 ] = array[ offset + 1 ];
- color[ 2 ] = array[ offset + 2 ];
- color[ 3 ] = array[ offset + 3 ];
+ 'value += p.value * amp;',
+ 'dens /= params.step;',
+ 'amp *= params.persistence;',
+ '}',
- return this.color;
- },
+ 'color[ 0 ] = value;',
+ 'color[ 1 ] = value;',
+ 'color[ 2 ] = value;',
+ ].join('\n');
+ }
+ } );
};
-//
+TG.CheckerBoard = function () { // generates a grid pattern of alternating black and white cells
-TG.ColorInterpolatorMethod = {
- STEP: 0,
- LINEAR: 1,
- SPLINE: 2,
-};
-
-// points must be a set pair (point, color):
-// [{ pos-n: [r,g,b,a] } , ..., { pos-N: [r,g,b,a] } ]
-TG.ColorInterpolator = function( method ) {
+ var params = {
+ size: [ 32, 32 ],
+ offset: [ 0, 0 ],
+ rowShift: 0
+ };
- this.points = [];
- this.low = 0;
- this.high = 0;
- this.interpolation = ( typeof( method ) == 'undefined' ) ? TG.ColorInterpolatorMethod.LINEAR : method;
- this.repeat = false;
+ return new TG.Program( {
+ size: function ( x, y ) { // sets the width and height of each square in pixels
+ if ( typeof y == "undefined" ) y = x;
+ params.size = [ x, y ];
+ return this;
+ },
+ offset: function ( x, y ) { // moves the origin of the pattern to 'x' and 'y' in pixels
+ params.offset = [ -x, -y ];
+ return this;
+ },
+ rowShift: function ( value ) { // offsets each row by 'value' pixels
+ params.rowShift = value;
+ return this;
+ },
+ getParams: function () {
+ return params;
+ },
+ getSource: function () {
+ return [
+ 'var value = ( ( ( y + params.offset[ 1 ] ) / params.size[ 1 ] ) & 1 ) ^ ( ( ( x + params.offset[ 0 ] + parseInt( y / params.size[ 1 ] ) * params.rowShift ) / params.size[ 0 ] ) & 1 ) ? 0 : 1',
+ 'color[ 0 ] = value;',
+ 'color[ 1 ] = value;',
+ 'color[ 2 ] = value;'
+ ].join('\n');
+ }
+ } );
- return this;
};
-TG.ColorInterpolator.prototype = {
-
- set: function ( points ) {
-
- this.points = points;
- this.points.sort( function( a, b ) {
- return a.pos - b.pos;
- });
-
- this.low = this.points[ 0 ].pos;
- this.high = this.points[ this.points.length - 1 ].pos;
-
- return this;
-
- },
-
- addPoint: function ( position, color ) {
-
- this.points.push( { pos: position, color: color } );
- this.points.sort( function( a, b ) {
- return a.pos - b.pos;
- });
-
- this.low = this.points[ 0 ].pos;
- this.high = this.points[ this.points.length - 1 ].pos;
-
- return this;
-
- },
-
- setRepeat: function ( value ) {
+TG.Rect = function () { // generates a basic white rectangle
- this.repeat = value;
- return this;
-
- },
-
- setInterpolation: function ( value ) {
-
- this.interpolation = value;
- return this;
-
- },
-
- getColorAt: function ( pos ) {
-
- if ( this.repeat == 2 ) pos = TG.Utils.mirroredWrap( pos, this.low, this.high );
- else if ( this.repeat ) pos = TG.Utils.wrap( pos, this.low, this.high );
- else pos = TG.Utils.clamp( pos, this.low, this.high );
-
- var i = 0, points = this.points;
-
- while ( points[ i + 1 ].pos < pos ) i ++;
+ var params = {
+ position: [ 0, 0 ],
+ size: [ 32, 32 ]
+ };
- var p1 = points[ i ];
- var p2 = points[ i + 1 ];
+ return new TG.Program( {
+ position: function ( x, y ) { // sets the coordinates of the top-leftmost point of the rectangle
+ params.position = [ x, y ];
+ return this;
+ },
+ size: function ( x, y ) { // sets the width and height of the rectangle
+ if ( typeof y == "undefined" ) y = x;
+ params.size = [ x, y ];
+ return this;
+ },
+ getParams: function () {
+ return params;
+ },
+ getSource: function () {
+ return [
+ 'var value = ( x >= params.position[ 0 ] && x <= ( params.position[ 0 ] + params.size[ 0 ] ) && y <= ( params.position[ 1 ] + params.size[ 1 ] ) && y >= params.position[ 1 ] ) ? 1 : 0;',
+ 'color[ 0 ] = value;',
+ 'color[ 1 ] = value;',
+ 'color[ 2 ] = value;'
+ ].join('\n');
+ }
+ } );
- var delta = ( pos - p1.pos ) / ( p2.pos - p1.pos );
+};
- if ( this.interpolation == TG.ColorInterpolatorMethod.STEP ) {
+TG.Circle = function () { // generates a basic white circle
- return p1.color;
+ var params = {
+ position: [ 0, 0 ],
+ radius: 50,
+ delta: 1
+ };
- } else if ( this.interpolation == TG.ColorInterpolatorMethod.LINEAR ) {
+ return new TG.Program( {
+ delta: function ( value ) { // sets how many pixels from the edge of the circle the brightness should gradually decrease; 0 results in a completely crisp circle
+ params.delta = value;
+ return this;
+ },
+ position: function ( x, y ) { // sets the coordinates of the center of the circle
+ params.position = [ x, y ];
+ return this;
+ },
+ radius: function ( value ) { // sets the radius of the circle in pixels
+ params.radius = value;
+ return this;
+ },
+ getParams: function () {
+ return params;
+ },
+ getSource: function () {
+ return [
+ 'var dist = TG.Utils.distance( x, y, params.position[ 0 ], params.position[ 1 ] );',
+ 'var value = 1 - TG.Utils.smoothStep( params.radius - params.delta, params.radius, dist );',
+ 'color[ 0 ] = value;',
+ 'color[ 1 ] = value;',
+ 'color[ 2 ] = value;'
+ ].join('\n');
+ }
+ } );
- return TG.Utils.mixColors( p1.color, p2.color, delta );
+};
- } else if ( this.interpolation == TG.ColorInterpolatorMethod.SPLINE ) {
+TG.PutTexture = function ( texture ) { // puts an already existing texture onto another one
- var ar = 2 * p1.color[ 0 ] - 2 * p2.color[ 0 ];
- var br = -3 * p1.color[ 0 ] + 3 * p2.color[ 0 ];
- var dr = p1.color[ 0 ];
+ var params = {
+ offset: [ 0, 0 ],
+ repeat: false,
+ srcTex: texture.buffer
+ };
- var ag = 2 * p1.color[ 1 ] - 2 * p2.color[ 1 ];
- var bg = -3 * p1.color[ 1 ] + 3 * p2.color[ 1 ];
- var dg = p1.color[ 1 ];
+ return new TG.Program( {
+ offset: function ( x, y ) { // sets the coordinates of the top-leftmost point
+ params.offset = [ x, y ];
+ return this;
+ },
+ repeat: function ( value ) { // which algorithm should be used on coordinates outside of the texture; 1 = wrap around, 2 = wrap around but mirrored, 3 = extend the last pixel
+ params.repeat = value;
+ return this;
+ },
+ getParams: function () {
+ return params;
+ },
+ getSource: function () {
+ return [
+ 'var texWidth = params.srcTex.width;',
+ 'var texHeight = params.srcTex.height;',
- var ab = 2 * p1.color[ 2 ] - 2 * p2.color[ 2 ];
- var bb = -3 * p1.color[ 2 ] + 3 * p2.color[ 2 ];
- var db = p1.color[ 2 ];
+ 'var texX = Math.floor( x - params.offset[ 0 ] );',
+ 'var texY = Math.floor( y - params.offset[ 1 ] );',
- var delta2 = delta * delta;
- var delta3 = delta2 * delta;
+ 'if ( texX >= texWidth || texY >= texHeight || texX < 0 || texY < 0 ) {',
+ 'if ( params.repeat ) {',
+ 'var nx, ny;',
+ 'var rangeX = texWidth - 1;',
+ 'var rangeY = texHeight - 1;',
- return [
- ar * delta3 + br * delta2 + dr,
- ag * delta3 + bg * delta2 + dg,
- ab * delta3 + bb * delta2 + db
- ];
+ 'if ( params.repeat == 1 ) {',
+ 'nx = TG.Utils.wrap( texX, 0, texWidth );',
+ 'ny = TG.Utils.wrap( texY, 0, texHeight );',
+ '} else if ( params.repeat == 2 ) {',
+ 'nx = TG.Utils.mirroredWrap( texX, 0, rangeX );',
+ 'ny = TG.Utils.mirroredWrap( texY, 0, rangeY );',
+ '} else if ( params.repeat == 3 ) {',
+ 'nx = TG.Utils.clamp( texX, 0, rangeX );',
+ 'ny = TG.Utils.clamp( texY, 0, rangeY );',
+ '}',
+ 'color = params.srcTex.getPixelNearest( nx, ny );',
+ '} else {',
+ 'color[ 0 ] = 0;',
+ 'color[ 1 ] = 0;',
+ 'color[ 2 ] = 0;',
+ '}',
+ '} else color = params.srcTex.getPixelNearest( texX, texY );',
+ ].join( '\n' );
}
-
- }
+ } );
};
-TG.RadialGradient = function () {
+TG.RadialGradient = function () { // generates a circular gradient around a point
var params = {
gradient: new TG.ColorInterpolator( TG.ColorInterpolatorMethod.LINEAR ),
@@ -1227,29 +1108,29 @@ TG.RadialGradient = function () {
};
return new TG.Program( {
- repeat: function ( value ) {
+ repeat: function ( value ) { // sets how the gradient should repeat if outside of the range of the added points; 0 = clamp to the last point, 1 = wrap around, 2 = wrap around but mirrored
params.gradient.setRepeat( value );
return this;
},
- radius: function ( value ) {
+ radius: function ( value ) { // sets how far from the center the last point (i.e. position 1) of the gradient is
params.radius = value;
return this;
},
- interpolation: function ( value ) {
+ interpolation: function ( value ) { // sets the interpolation method of the gradient; 0 = step -> do not interpolate, 1 = linear-, 2 = spline-, 3 = cosine-interpolation
params.gradient.setInterpolation( value );
return this;
},
- center: function ( x, y ) {
+ center: function ( x, y ) { // sets the center around which the gradient is generated
params.center = [ x, y ];
return this;
},
+ point: function ( position, r, g, b ) { // adds a point to the gradient; position 0 is the center and 1 is the radius
+ params.gradient.addPoint( position, r, g, b );
+ return this;
+ },
getParams: function () {
return params;
},
- point: function ( position, color ) {
- params.gradient.addPoint( position, color );
- return this;
- },
getSource: function () {
return [
@@ -1262,28 +1143,28 @@ TG.RadialGradient = function () {
};
-TG.LinearGradient = function () {
+TG.LinearGradient = function () { // generates a gradient across the whole texture
var params = {
gradient: new TG.ColorInterpolator( TG.ColorInterpolatorMethod.LINEAR )
};
return new TG.Program( {
- repeat: function ( value ) {
+ repeat: function ( value ) { // sets how the gradient should repeat if outside of the range of the added points; 0 = clamp to the last point, 1 = wrap around, 2 = wrap around but mirrored
params.gradient.setRepeat( value );
return this;
},
- interpolation: function ( value ) {
+ interpolation: function ( value ) { // sets the interpolation method of the gradient; 0 = step -> do not interpolate, 1 = linear-, 2 = spline-, 3 = cosine-interpolation
params.gradient.setInterpolation( value );
return this;
},
+ point: function ( position, r, g, b ) { // adds a point to the gradient; position 0 is x0 and 1 is the width of the texture
+ params.gradient.addPoint( position, r, g, b );
+ return this;
+ },
getParams: function () {
return params;
},
- point: function ( position, color ) {
- params.gradient.addPoint( position, color );
- return this;
- },
getSource: function () {
return [
@@ -1295,115 +1176,258 @@ TG.LinearGradient = function () {
};
+// --- Filters ---
-//
+TG.SineDistort = function () { // warps the texture in a wavy pattern
-TG.Utils = {
+ var params = {
+ sines: [ 4, 4 ],
+ offset: [ 0, 0 ],
+ amplitude: [ 16, 16 ]
+ };
- smoothStep: function ( edge0, edge1, x ) {
+ return new TG.Program( {
+ sines: function ( x, y ) { // sets the width of the sine waves on the x and y axis respectively
+ if ( typeof y == "undefined" ) y = x;
+ params.sines = [ x, y ];
+ return this;
+ },
+ offset: function ( x, y ) { // shifts the 'phase' of each wave
+ params.offset = [ x, y ];
+ return this;
+ },
+ amplitude: function ( x, y ) { // sets the 'amplitude' of each wave (or intensity of the filter)
+ if ( typeof y == "undefined" ) y = x;
+ params.amplitude = [ x, y ];
+ return this;
+ },
+ getParams: function () {
+ return params;
+ },
+ getSource: function () {
+ return [
+ 'var s = Math.sin( params.sines[ 0 ] / 100 * y + params.offset[ 0 ] ) * params.amplitude[ 0 ] + x;',
+ 'var t = Math.sin( params.sines[ 1 ] / 100 * x + params.offset[ 1 ] ) * params.amplitude[ 1 ] + y;',
+ 'color.set( src.getPixelBilinear( s, t ) );',
+ ].join( '\n' );
+ }
+ } );
- // Scale, bias and saturate x to 0..1 range
- x = TG.Utils.clamp( ( x - edge0 ) / ( edge1 - edge0 ), 0, 1 );
+};
- // Evaluate polynomial
- return x * x * ( 3 - 2 * x );
+TG.Twirl = function () { // distorts the texture into a vortex around a point
- },
+ var params = {
+ strength: 0,
+ radius: 120,
+ position: [ 128, 128 ]
+ };
- mixColors: function( c1, c2, delta ) {
+ return new TG.Program( {
+ strength: function ( value ) { // sets how much the texture should be rotated
+ params.strength = value / 100.0;
+ return this;
+ },
+ radius: function ( value ) { // sets the radius of influence
+ params.radius = value;
+ return this;
+ },
+ position: function ( x, y ) { // sets the coordinates of the center of the swirl
+ params.position = [ x, y ];
+ return this;
+ },
+ getParams: function () {
+ return params;
+ },
+ getSource: function () {
+ return [
+ 'var dist = TG.Utils.distance( x, y, params.position[ 0 ], params.position[ 1 ] );',
- return [
- c1[ 0 ] * ( 1 - delta ) + c2[ 0 ] * delta,
- c1[ 1 ] * ( 1 - delta ) + c2[ 1 ] * delta,
- c1[ 2 ] * ( 1 - delta ) + c2[ 2 ] * delta,
- c1[ 3 ] * ( 1 - delta ) + c2[ 3 ] * delta,
- ];
- },
+ // no distortion if outside of whirl radius.
+ 'if (dist < params.radius) {',
+ 'dist = Math.pow(params.radius - dist, 2) / params.radius;',
- distance: function( x0, y0, x1, y1 ) {
+ 'var angle = 2.0 * Math.PI * (dist / (params.radius / params.strength));',
+ 'var s = (((x - params.position[ 0 ]) * Math.cos(angle)) - ((y - params.position[ 0 ]) * Math.sin(angle)) + params.position[ 0 ] + 0.5);',
+ 'var t = (((y - params.position[ 1 ]) * Math.cos(angle)) + ((x - params.position[ 1 ]) * Math.sin(angle)) + params.position[ 1 ] + 0.5);',
+ '} else {',
+ 'var s = x;',
+ 'var t = y;',
+ '}',
- var dx = x1 - x0, dy = y1 - y0;
- return Math.sqrt( dx * dx + dy * dy );
+ 'color.set( src.getPixelBilinear( s, t ) );',
+ ].join( '\n' );
+ }
+ } );
- },
+};
- clamp: function( value, min, max ) {
+TG.Transform = function () { // moves, rotates or scales the texture
- return Math.min( Math.max( value, min ), max );
+ var params = {
+ offset: [ 0, 0 ],
+ angle: 0,
+ scale: [ 1, 1 ]
+ };
- },
-
- wrap: function ( value, min, max ) {
- var v = value - min;
- var r = max - min;
+ return new TG.Program( {
+ offset: function ( x, y ) { // moves the texture by 'x' and 'y' pixels
+ params.offset = [ -x, -y ];
+ return this;
+ },
+ angle: function ( value ) { // rotates the texture by 'value' degrees around the origin (x: 0, y: 0)
+ params.angle = TG.Utils.deg2rad( value );
+ return this;
+ },
+ scale: function ( x, y ) { // scales the texture by 'x' and 'y' (e.g. 2 doubles the size)
+ x = x || 1;
+ y = y || x;
- return ( ( r + v % r ) % r ) + min;
- },
+ params.scale = [ x, y ];
+ return this;
+ },
+ getParams: function () {
+ return params;
+ },
+ getSource: function () {
+ return [
+ 'var x2 = x - width / 2;',
+ 'var y2 = y - height / 2;',
- mirroredWrap: function ( value, min, max ) {
- var v = value - min;
- var r = ( max - min ) * 2;
+ 'var s = x2 * ( Math.cos( params.angle ) / params.scale[ 0 ] ) + y2 * -( Math.sin( params.angle ) / params.scale[ 0 ] );',
+ 'var t = x2 * ( Math.sin( params.angle ) / params.scale[ 1 ] ) + y2 * ( Math.cos( params.angle ) / params.scale[ 1 ] );',
- v = ( r + v % r ) % r;
+ 's += params.offset[ 0 ] + width / 2;',
+ 't += params.offset[ 1 ] + height / 2;',
- if ( v > max - min ) {
- return ( r - v ) + min;
- } else {
- return v + min;
+ 'color.set( src.getPixelBilinear( s, t ) );',
+ ].join( '\n' );
}
- },
+ } );
- deg2rad: function ( deg ) {
+};
- return deg * Math.PI / 180;
+TG.Pixelate = function () { // divides the texture into 'pixels'
- },
+ var params = {
+ size: [ 1, 1 ]
+ };
- hashRNG: function ( seed, x, y ) {
- seed = ( Math.abs( seed % 2147483648 ) == 0 ) ? 1 : seed;
+ return new TG.Program( {
+ size: function ( x, y ) { // set the width and height of each 'pixel'
+ if ( typeof y == "undefined" ) y = x;
+ params.size = [ x, y ];
+ return this;
+ },
+ getParams: function () {
+ return params;
+ },
+ getSource: function () {
+ return [
+ 'var s = params.size[ 0 ] * Math.floor(x/params.size[ 0 ]);',
+ 'var t = params.size[ 1 ] * Math.floor(y/params.size[ 1 ]);',
- var a = ( ( seed * ( x + 1 ) * 777 ) ^ ( seed * ( y + 1 ) * 123 ) ) % 2147483647;
- a = (a ^ 61) ^ (a >> 16);
- a = a + (a << 3);
- a = a ^ (a >> 4);
- a = a * 0x27d4eb2d;
- a = a ^ (a >> 15);
- a = a / 2147483647;
+ 'color.set( src.getPixelNearest( s, t ) );'
+ ].join( '\n' );
+ }
+ } );
- return a;
- },
-
- cellNoiseBase: function ( x, y, seed, density, weightRange ) {
- var qx, qy, rx, ry, w, px, py, dx, dy;
- var dist, value;
- var shortest = Infinity;
- density = Math.abs( density );
+};
- for ( var sx = -2; sx <= 2; sx++ ) {
- for ( var sy = -2; sy <= 2; sy++ ) {
- qx = Math.ceil( x / density ) + sx;
- qy = Math.ceil( y / density ) + sy;
+TG.GradientMap = function () { // takes the value of each pixel and maps it to a color in a gradient; best used on a grayscale image
- rx = TG.Utils.hashRNG( seed, qx, qy );
- ry = TG.Utils.hashRNG( seed * 2, qx, qy );
- w = ( weightRange > 0 ) ? 1 + TG.Utils.hashRNG( seed * 3, qx, qy ) * weightRange : 1;
+ var params = {
+ gradient: new TG.ColorInterpolator( TG.ColorInterpolatorMethod.LINEAR )
+ };
- px = ( rx + qx ) * density;
- py = ( ry + qy ) * density;
+ return new TG.Program( {
+ repeat: function ( value ) { // how to map values that are out of range onto the gradient; 0 = clamp to the last (first) point of the gradient, 1 = wrap around, 2 = wrap around but mirrored
+ params.gradient.setRepeat( value );
+ return this;
+ },
+ interpolation: function ( value ) { // set the interpolation method for the gradient; 0 = step -> do not interpolate, 1 = linear-, 2 = spline-, 3 = cosine-interpolation
+ params.gradient.setInterpolation( value );
+ return this;
+ },
+ point: function ( position, r, g, b ) { // add a point to the gradient; position 0 is black and 1 is white in the original texture
+ params.gradient.addPoint( position, r, g, b );
+ return this;
+ },
+ getParams: function () {
+ return params;
+ },
+ getSource: function () {
+ return [
+ 'var v = src.getPixelNearest( x, y );',
- dx = Math.abs( px - x );
- dy = Math.abs( py - y );
+ 'var r = params.gradient.getColorAt( v[ 0 ] )[ 0 ];',
+ 'var g = params.gradient.getColorAt( v[ 1 ] )[ 1 ];',
+ 'var b = params.gradient.getColorAt( v[ 2 ] )[ 2 ];',
- dist = ( dx * dx + dy * dy ) * w;
+ 'color[ 0 ] = r;',
+ 'color[ 1 ] = g;',
+ 'color[ 2 ] = b;'
+ ].join('\n');
+ }
+ } );
- if ( dist < shortest ) {
- shortest = dist;
- value = rx;
- }
- }
+};
+
+TG.Normalize = function () { // adjusts the whole texture so that every pixel is in the visible range (0 - 1)
+
+ return new TG.Program( {
+ getParams: function () {},
+ getInit: function () {
+ return [
+ 'var high = -Infinity;',
+ 'var low = Infinity;',
+
+ 'for ( var j = 0, len = src.array.length; j < len; j++ ) {',
+ 'if ( j % 4 == 3 ) continue;',
+
+ 'high = ( src.array[ j ] > high ) ? src.array[ j ] : high;',
+ 'low = ( src.array[ j ] < low ) ? src.array[ j ] : low;',
+ '}',
+
+ 'var offset = -low;',
+ 'var multiplier = 1 / ( high - low );',
+ ].join( '\n' );
+ },
+ getSource: function () {
+ return [
+ 'var v = src.getPixelNearest( x, y );',
+ 'color[ 0 ] = ( v[ 0 ] + offset ) * multiplier;',
+ 'color[ 1 ] = ( v[ 1 ] + offset ) * multiplier;',
+ 'color[ 2 ] = ( v[ 2 ] + offset ) * multiplier;'
+ ].join( '\n' );
}
+ } );
- return { dist: Math.sqrt( shortest ), value: value };
- }
+};
+
+TG.Posterize = function () { // reduces the amount of colors in a texture
+
+ var params = {
+ step: 2
+ };
+
+ return new TG.Program( {
+ step: function ( value ) { // sets how many possible values each color channel is divided into (2 means possible values are 0 and 1, 3 means 0, 0.5 and 1 etc.)
+ params.step = Math.max( value, 2 );
+ return this;
+ },
+ getParams: function () {
+ return params;
+ },
+ getSource: function () {
+ return [
+ 'var v = src.getPixelNearest( x, y );',
+ 'color[ 0 ] = Math.floor( v[ 0 ] / ( 1 / params.step ) ) / ( params.step - 1 );',
+ 'color[ 1 ] = Math.floor( v[ 1 ] / ( 1 / params.step ) ) / ( params.step - 1 );',
+ 'color[ 2 ] = Math.floor( v[ 2 ] / ( 1 / params.step ) ) / ( params.step - 1 );'
+ ].join( '\n' );
+ }
+ } );
};
+