2019-08-23 21:27:41 +08:00

1410 lines
30 KiB
JavaScript

/**
* @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 ) {
this.color = new Float32Array( 4 );
this.buffer = new TG.Buffer( width, height );
this.bufferCopy = new TG.Buffer( width, height );
};
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;',
'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 ); },
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;
}
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.Program = function ( object ) {
var tint = new Float32Array( [ 1, 1, 1 ] );
object.tint = function ( r, g, b ) {
tint[ 0 ] = r;
tint[ 1 ] = g;
tint[ 2 ] = b;
return this;
};
object.getTint = function () {
return tint;
};
return object;
};
TG.Number = function () {
return new TG.Program( {
getParams: function () {},
getSource: function () {
return [
'color[ 0 ] = 1;',
'color[ 1 ] = 1;',
'color[ 2 ] = 1;'
].join('\n');
}
} );
};
TG.SinX = function () {
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( ( x + params.offset ) * params.frequency );',
'color[ 0 ] = value;',
'color[ 1 ] = value;',
'color[ 2 ] = value;'
].join('\n');
}
} );
};
TG.SinY = function () {
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');
}
} );
};
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');
}
} );
};
TG.XOR = 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');
}
} );
};
TG.Noise = function () {
var params = {
seed: Date.now()
};
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');
}
} );
};
TG.FractalNoise = function () {
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
};
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;',
'for ( var j = 1; j <= params.octaves; j++ ) {',
'x1 = Math.floor( x * freq ), y1 = Math.floor( y * freq );',
'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 );',
'params.interpolator.set( [',
'{ pos: 0, color: [ v1 ] },',
'{ pos: 1, color: [ v2 ] }',
'] );',
'i1 = params.interpolator.getColorAt( dx );',
'params.interpolator.set( [',
'{ pos: 0, color: [ v3 ] },',
'{ pos: 1, color: [ v4 ] }',
'] );',
'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.CellularNoise = function () {
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 );',
'var value = 1 - ( p.dist / params.density );',
'if ( params.density < 0 ) value -= 1;',
'color[ 0 ] = value;',
'color[ 1 ] = value;',
'color[ 2 ] = value;'
].join('\n');
}
} );
};
TG.VoronoiNoise = function () {
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 );',
'color[ 0 ] = p.value;',
'color[ 1 ] = p.value;',
'color[ 2 ] = p.value;'
].join('\n');
}
} );
};
TG.CellularFractal = function () {
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;',
'for ( var j = 1; j <= params.octaves; j++ ) {',
'p = TG.Utils.cellNoiseBase( x, y, params.seed * j, dens, params.weightRange );',
'p.dist = 1 - ( p.dist / dens );',
'if ( dens < 0 ) p.dist -= 1;',
'value += p.dist * amp;',
'dens /= params.step;',
'amp *= params.persistence;',
'}',
'color[ 0 ] = value;',
'color[ 1 ] = value;',
'color[ 2 ] = value;',
].join('\n');
}
} );
};
TG.VoronoiFractal = function () {
var params = {
seed: Date.now(),
weightRange: 0,
baseDensity: 64,
amplitude: 0.6,
persistence: 0.6,
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;',
'for ( var j = 1; j <= params.octaves; j++ ) {',
'p = TG.Utils.cellNoiseBase( x, y, params.seed * j, dens, params.weightRange );',
'value += p.value * amp;',
'dens /= params.step;',
'amp *= params.persistence;',
'}',
'color[ 0 ] = value;',
'color[ 1 ] = value;',
'color[ 2 ] = value;',
].join('\n');
}
} );
};
TG.CheckerBoard = function () {
var params = {
size: [ 32, 32 ],
offset: [ 0, 0 ],
rowShift: 0
};
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;
},
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');
}
} );
};
TG.Rect = function () {
var params = {
position: [ 0, 0 ],
size: [ 32, 32 ]
};
return new TG.Program( {
position: function ( x, y ) {
params.position = [ x, y ];
return this;
},
size: function ( x, y ) {
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');
}
} );
};
TG.Circle = function () {
var params = {
position: [ 0, 0 ],
radius: 50,
delta: 1
};
return new TG.Program( {
delta: function ( value ) {
params.delta = value;
return this;
},
position: function ( x, y ) {
params.position = [ x, y ];
return this;
},
radius: function ( value ) {
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');
}
} );
};
TG.PutTexture = function ( texture ) {
var params = {
offset: [ 0, 0 ],
repeat: false,
srcTex: texture.buffer
};
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;
},
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 ] );',
'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 );',
'}',
'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' );
}
} );
};
// Filters
TG.SineDistort = function () {
var params = {
sines: [ 4, 4 ],
offset: [ 0, 0 ],
amplitude: [ 16, 16 ]
};
return new TG.Program( {
sines: function ( x, y ) {
params.sines = [ x, y ];
return this;
},
offset: function ( x, y ) {
params.offset = [ x, y ];
return this;
},
amplitude: function ( x, y ) {
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' );
}
} );
};
TG.Twirl = function () {
var params = {
strength: 0,
radius: 120,
position: [ 128, 128 ]
};
return new TG.Program( {
strength: function ( value ) {
params.strength = value / 100.0;
return this;
},
radius: function ( value ) {
params.radius = value;
return this;
},
position: function ( x, y ) {
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 ] );',
// no distortion if outside of whirl radius.
'if (dist < params.radius) {',
'dist = Math.pow(params.radius - dist, 2) / params.radius;',
'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;',
'}',
'color.set( src.getPixelBilinear( s, t ) );',
].join( '\n' );
}
} );
};
TG.Transform = function () {
var params = {
offset: [ 0, 0 ],
angle: 0,
scale: [ 1, 1 ]
};
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' );
}
} );
};
TG.Pixelate = function () {
var params = {
size: [ 1, 1 ]
};
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 ]);',
'color.set( src.getPixelNearest( s, t ) );'
].join( '\n' );
}
} );
};
TG.GradientMap = function () {
var params = {
gradient: new TG.ColorInterpolator( TG.ColorInterpolatorMethod.LINEAR )
};
return new TG.Program( {
repeat: function ( value ) {
params.gradient.setRepeat( value );
return this;
},
interpolation: function ( value ) {
params.gradient.setInterpolation( value );
return this;
},
point: function ( position, color ) {
params.gradient.addPoint( position, color );
return this;
},
getParams: function () {
return params;
},
getSource: function () {
return [
'var v = src.getPixelNearest( x, 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 ];',
'color[ 0 ] = r;',
'color[ 1 ] = g;',
'color[ 2 ] = b;'
].join('\n');
}
} );
};
TG.Normalize = function () {
var params = {
multiplier: 0,
offset: 0
};
return new TG.Program( {
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 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' );
}
} );
};
TG.Posterize = function () {
var params = {
step: 2
};
return new TG.Program( {
step: function ( value ) {
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( 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' );
}
} );
};
// Buffer
TG.Buffer = function ( width, height ) {
this.width = width;
this.height = height;
this.array = new Float32Array( width * height * 4 );
this.color = new Float32Array( 4 );
};
TG.Buffer.prototype = {
constructor: TG.Buffer,
copy: function ( buffer ) {
this.array.set( buffer.array );
},
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 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.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 ) {
this.points = [];
this.low = 0;
this.high = 0;
this.interpolation = ( typeof( method ) == 'undefined' ) ? TG.ColorInterpolatorMethod.LINEAR : method;
this.repeat = false;
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 ) {
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 p1 = points[ i ];
var p2 = points[ i + 1 ];
var delta = ( pos - p1.pos ) / ( p2.pos - p1.pos );
if ( this.interpolation == TG.ColorInterpolatorMethod.STEP ) {
return p1.color;
} else if ( this.interpolation == TG.ColorInterpolatorMethod.LINEAR ) {
return TG.Utils.mixColors( p1.color, p2.color, delta );
} else if ( this.interpolation == TG.ColorInterpolatorMethod.SPLINE ) {
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 ag = 2 * p1.color[ 1 ] - 2 * p2.color[ 1 ];
var bg = -3 * p1.color[ 1 ] + 3 * p2.color[ 1 ];
var dg = p1.color[ 1 ];
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 delta2 = delta * delta;
var delta3 = delta2 * delta;
return [
ar * delta3 + br * delta2 + dr,
ag * delta3 + bg * delta2 + dg,
ab * delta3 + bb * delta2 + db
];
}
}
};
TG.RadialGradient = function () {
var params = {
gradient: new TG.ColorInterpolator( TG.ColorInterpolatorMethod.LINEAR ),
radius: 255,
center: [ 128, 128 ],
};
return new TG.Program( {
repeat: function ( value ) {
params.gradient.setRepeat( value );
return this;
},
radius: function ( value ) {
params.radius = value;
return this;
},
interpolation: function ( value ) {
params.gradient.setInterpolation( value );
return this;
},
center: function ( x, y ) {
params.center = [ x, y ];
return this;
},
getParams: function () {
return params;
},
point: function ( position, color ) {
params.gradient.addPoint( position, color );
return this;
},
getSource: function () {
return [
'var dist = TG.Utils.distance( x, y, params.center[ 0 ], params.center[ 1 ] );',
'color = params.gradient.getColorAt( dist / params.radius );',
].join('\n');
}
} );
};
TG.LinearGradient = function () {
var params = {
gradient: new TG.ColorInterpolator( TG.ColorInterpolatorMethod.LINEAR )
};
return new TG.Program( {
repeat: function ( value ) {
params.gradient.setRepeat( value );
return this;
},
interpolation: function ( value ) {
params.gradient.setInterpolation( value );
return this;
},
getParams: function () {
return params;
},
point: function ( position, color ) {
params.gradient.addPoint( position, color );
return this;
},
getSource: function () {
return [
'color = params.gradient.getColorAt( x / width );',
].join('\n');
}
} );
};
//
TG.Utils = {
smoothStep: function ( edge0, edge1, x ) {
// 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 );
},
mixColors: function( c1, c2, delta ) {
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,
];
},
distance: function( x0, y0, x1, y1 ) {
var dx = x1 - x0, dy = y1 - y0;
return Math.sqrt( dx * dx + dy * dy );
},
clamp: function( value, min, max ) {
return Math.min( Math.max( value, min ), max );
},
wrap: function ( value, min, max ) {
var v = value - min;
var r = max - min;
return ( ( r + v % r ) % r ) + min;
},
mirroredWrap: function ( value, min, max ) {
var v = value - min;
var r = ( max - min ) * 2;
v = ( r + v % r ) % r;
if ( v > max - min ) {
return ( r - v ) + min;
} else {
return v + min;
}
},
deg2rad: function ( deg ) {
return deg * Math.PI / 180;
},
hashRNG: function ( seed, x, y ) {
seed = ( Math.abs( seed % 2147483648 ) == 0 ) ? 1 : seed;
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;
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;
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;
px = ( rx + qx ) * density;
py = ( ry + qy ) * density;
dx = Math.abs( px - x );
dy = Math.abs( py - y );
dist = ( dx * dx + dy * dy ) * w;
if ( dist < shortest ) {
shortest = dist;
value = rx;
}
}
}
return { dist: Math.sqrt( shortest ), value: value };
}
};