150 lines
6.8 KiB
JavaScript
150 lines
6.8 KiB
JavaScript
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// ) ( //
|
|
// ( /( ( ( ) ( ( ( ( )\ ) ( ( //
|
|
// )\()) ))\ )( ( ( )\ ) )\))( )\ ( (()/( ( )\))( ( //
|
|
// ((_)\ /((_|()\ )\ ) )\ '(()/( ((_)()((_) )\ ) ((_)))\((_)()\ )\ //
|
|
// | |(_|_))( ((_)_(_/( _((_)) )(_)) _(()((_|_)_(_/( _| |((_)(()((_|(_) //
|
|
// | '_ \ || | '_| ' \)) | ' \()| || | \ V V / | ' \)) _` / _ \ V V (_-< //
|
|
// |_.__/\_,_|_| |_||_| |_|_|_| \_, | \_/\_/|_|_||_|\__,_\___/\_/\_//__/ //
|
|
// |__/ //
|
|
// Copyright (c) 2021 Simon Schneegans //
|
|
// Released under the GPLv3 or later. See LICENSE file for details. //
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
'use strict';
|
|
|
|
const {Clutter, GObject} = imports.gi;
|
|
|
|
const ExtensionUtils = imports.misc.extensionUtils;
|
|
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
|
const shaderSnippets = Me.imports.src.extension.shaderSnippets;
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// The FireShader uses a procedural gray-scale noise texture. This texture is moved //
|
|
// vertically over time and mapped to a configurable color gradient. It is faded to //
|
|
// transparency towards the edges of the window. In addition, there are a couple of //
|
|
// moving gradients which fade-in or fade-out the fire effect. //
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
var FireShader = GObject.registerClass({Properties: {}, Signals: {}},
|
|
class FireShader extends Clutter.ShaderEffect {
|
|
_init(settings) {
|
|
super._init({shader_type: Clutter.ShaderType.FRAGMENT_SHADER});
|
|
|
|
// Load the gradient values from the settings. We directly inject the values in the
|
|
// GLSL code below. The shader is compiled once for each window-closing anyways. In
|
|
// the future, we may want to prevent this frequent recompilations of shaders,
|
|
// though.
|
|
const gradient = [];
|
|
for (let i = 1; i <= 5; i++) {
|
|
const color = Clutter.Color.from_string(settings.get_string('fire-color-' + i))[1];
|
|
gradient.push(`vec4(${color.red / 255}, ${color.green / 255}, ${
|
|
color.blue / 255}, ${color.alpha / 255})`);
|
|
}
|
|
|
|
this.set_shader_source(`
|
|
|
|
// Inject some common shader snippets.
|
|
${shaderSnippets.standardUniforms()}
|
|
${shaderSnippets.noise()}
|
|
|
|
// These may be configurable in the future.
|
|
const float EDGE_FADE = 70;
|
|
const float FADE_WIDTH = 0.1;
|
|
const float HIDE_TIME = 0.4;
|
|
const vec2 FIRE_SCALE = vec2(400, 600) * ${settings.get_double('flame-scale')};
|
|
const float FIRE_SPEED = ${settings.get_double('flame-movement-speed')};
|
|
|
|
// This maps the input value from [0..1] to a color from the gradient.
|
|
vec4 getFireColor(float v) {
|
|
const float steps[5] = float[](0.0, 0.2, 0.35, 0.5, 0.8);
|
|
const vec4 colors[5] = vec4[](
|
|
${gradient[0]},
|
|
${gradient[1]},
|
|
${gradient[2]},
|
|
${gradient[3]},
|
|
${gradient[4]}
|
|
);
|
|
|
|
if (v < steps[0]) {
|
|
return colors[0];
|
|
}
|
|
|
|
for (int i=0; i<4; ++i) {
|
|
if (v <= steps[i+1]) {
|
|
return mix(colors[i], colors[i+1], vec4(v -
|
|
steps[i])/(steps[i+1]-steps[i]));
|
|
}
|
|
}
|
|
|
|
return colors[4];
|
|
}
|
|
|
|
// This method requires the uniforms from standardUniforms() to be available.
|
|
// It returns two values: The first is an alpha value which can be used for the window
|
|
// texture. This gradually dissolves the window from top to bottom. The second can be used
|
|
// to mask any effect, it will be most opaque where the window is currently fading and
|
|
// gradually dissolve to zero over time.
|
|
// hideTime: A value in [0..1]. It determines the percentage of the animation which
|
|
// is spent for hiding the window. 1-hideTime will be spent thereafter for
|
|
// dissolving the effect mask.
|
|
// fadeWidth: The relative size of the window-hiding gradient in [0..1].
|
|
// edgeFadeWidth: The pixel width of the effect fading range at the edges of the window.
|
|
vec2 effectMask(float hideTime, float fadeWidth, float edgeFadeWidth) {
|
|
float burnProgress = clamp(uProgress/hideTime, 0, 1);
|
|
float afterBurnProgress = clamp((uProgress-hideTime)/(1-hideTime), 0, 1);
|
|
|
|
// Gradient from top to bottom.
|
|
float t = cogl_tex_coord_in[0].t * (1 - fadeWidth);
|
|
|
|
// Visible part of the window. Gradually dissolves towards the bottom.
|
|
float windowMask = 1 - clamp((burnProgress - t) / fadeWidth, 0, 1);
|
|
|
|
// Gradient from top burning window.
|
|
float effectMask = clamp(t*(1-windowMask)/burnProgress, 0, 1);
|
|
|
|
// Fade-out when the window burned down.
|
|
if (uProgress > hideTime) {
|
|
float fade = sqrt(1-afterBurnProgress*afterBurnProgress);
|
|
effectMask *= mix(1, 1-t, afterBurnProgress) * fade;
|
|
}
|
|
|
|
// Fade at window borders.
|
|
vec2 pos = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY);
|
|
effectMask *= smoothstep(0, 1, clamp(pos.x / edgeFadeWidth, 0, 1));
|
|
effectMask *= smoothstep(0, 1, clamp(pos.y / edgeFadeWidth, 0, 1));
|
|
effectMask *= smoothstep(0, 1, clamp((uSizeX - pos.x) / edgeFadeWidth, 0, 1));
|
|
effectMask *= smoothstep(0, 1, clamp((uSizeY - pos.y) / edgeFadeWidth, 0, 1));
|
|
|
|
return vec2(windowMask, effectMask);
|
|
}
|
|
|
|
void main() {
|
|
|
|
// Get a noise value which moves vertically in time.
|
|
vec2 uv = cogl_tex_coord_in[0].st * vec2(uSizeX, uSizeY) / FIRE_SCALE;
|
|
uv.y += uTime * FIRE_SPEED;
|
|
float noise = perlinNoise(uv, 10.0, 5, 0.5);
|
|
|
|
// Modulate noise by effect mask.
|
|
vec2 effectMask = effectMask(HIDE_TIME, FADE_WIDTH, EDGE_FADE);
|
|
noise *= effectMask.y;
|
|
|
|
// Map noise value to color.
|
|
vec4 fire = getFireColor(noise);
|
|
fire.rgb *= fire.a;
|
|
|
|
// Get the window texture and fade it according to the effect mask.
|
|
cogl_color_out = texture2D(uTexture, cogl_tex_coord_in[0].st) * effectMask.x;
|
|
|
|
// Add the fire to the window.
|
|
cogl_color_out += fire;
|
|
|
|
// These are pretty useful for understanding how this works.
|
|
// cogl_color_out = vec4(vec3(noise), 1);
|
|
// cogl_color_out = vec4(vec3(effectMask.x), 1);
|
|
// cogl_color_out = vec4(vec3(effectMask.y), 1);
|
|
}
|
|
`);
|
|
};
|
|
}); |