Source: components/CameraPostEffectComponent.js

import { ObjectProxy } from '@uino/base-thing';
import { Utils } from '../common/Utils';
import { BaseComponent } from './BaseComponent';

const __ = {
	private: Symbol('private'),
}

/**
 * @class CameraPostEffectComponent
 * The camera post effect compnent.
 * @memberof THING
 * @extends THING.BaseComponent
 * @public
 */
class CameraPostEffectComponent extends BaseComponent {

	/**
	 * The rendering effect by camera, some screen post effect(s) can be changed here.
	 */
	constructor() {
		super();

		this[__.private] = {};
		let _private = this[__.private];

		_private.needsRefresh = false;

		/**
		 * Temporal super sampling
		 * @typedef {Object} TemporalSuperSampling
		 * @property {Boolean} enable True indicates enable it.
		 * @property {Number} size The max frames.
		 */

		_private.temporalSuperSampling = {
			enable: false,
			size: 30
		};

		/**
		 * Configuration about bloom post effect
		 * @typedef {Object} Bloom
		 * @property {Boolean} enable True indicates enable it.
		 * @property {Number} strength The intensity.
		 * @property {Number} radius The radius.
		 * @property {Number} threshold The threshold.
		 */

		_private.bloom = {
			enable: false,
			strength: 0.14,
			radius: 0.4,
			threshold: 0.7
		};

		/**
		 * Configuration about screen space ambient occulusion (SSAO).
		 * @typedef {Object} ScreenSpaceAmbientOcclusion
		 * @property {Boolean} enable True indicates enable it.
		 * @property {Number} radius The radius.
		 * Sampling radius in work space.
		 * Larger will produce more soft concat shadow.
		 * But also needs higher quality or it will have more obvious artifacts
		 * @property {String} quality Quality of SSAO. 'low'|'medium'|'high'|'ultra'.
		 * @property {Number} intensity The intensity.
		 * @property {Boolean} temporalFilter The temporal filter in temporal super sampling mode.
		 * @property {Boolean} ignoreTransparent if ignore transparent objects.
		 */

		_private.screenSpaceAmbientOcclusion = {
			enable: false,
			radius: 0.2,
			quality: 'medium',
			intensity: 0.8,
			temporalFilter: true,
			ignoreTransparent: false
		};

		/**
		 * Configuration about screen space reflection.
		 * @typedef {Object} ScreenSpaceReflection
		 * @property {Boolean} enable True indicates enable it.
		 * @property {Number} maxRayDistance
		 * @property {Number} pixelStride
		 * @property {Number} pixelStrideZCutoff
		 * @property {Number} screenEdgeFadeStart
		 * @property {Number} eyeFadeStart
		 * @property {Number} eyeFadeEnd
		 * @property {Number} minGlossiness
		 */

		_private.screenSpaceReflection = {
			enable: false,
			maxRayDistance: 200,
			pixelStride: 16,
			pixelStrideZCutoff: 50,
			screenEdgeFadeStart: 0.9,
			eyeFadeStart: 0.4,
			eyeFadeEnd: 0.8,
			minGlossiness: 0.2
		};

		/**
		 * Configuration about color correction.
		 * @typedef {Object} ColorCorrection
		 * @property {Boolean} enable True indicates enable it.
		 * @property {Number} exposure
		 * @property {Number} brightness
		 * @property {Number} contrast
		 * @property {Number} saturation
		 * @property {Number} gamma
		 */

		_private.colorCorrection = {
			enable: true,
			exposure: 0,
			brightness: 0,
			contrast: 1.1,
			saturation: 1.1,
			gamma: 1
		};

		/**
		 * Configuration about dof.
		 * @typedef {Object} Dof
		 * @property {Boolean} enable True indicates enable it.
		 * @property {Number} focalDepth
		 * @property {Number} focalLength
		 * @property {Number} fstop
		 * @property {Number} maxblur
		 * @property {Number} threshold
		 * @property {Number} gain
		 * @property {Number} bias
		 * @property {Number} dithering
		 */

		_private.dof = {
			enable: false,
			focalDepth: 1,
			focalLength: 24,
			fstop: 0.9,
			maxblur: 1.0,
			threshold: 0.9,
			gain: 1.0,
			bias: 0.5,
			dithering: 0.0001
		};

		/**
		 * Configuration about vignetting.
		 * @typedef {Object} Vignetting
		 * @property {Boolean} enable True indicates enable it.
		 * @property {Array<Number>} color The color, only for color type
		 * @property {Number} offset The offset.
		 */

		_private.vignetting = {
			enable: false,
			color: [0, 0, 0],
			offset: 1.5
		};

		/**
		 * Configuration about blur edge.
		 * @typedef {Object} BlurEdge
		 * @property {Boolean} enable True indicates enable it.
		 * @property {Number} offset The offset.
		 */

		_private.blurEdge = {
			enable: false,
			offset: 1.0
		};

		/**
		 * Configuration about film effect.
		 * @typedef {Object} Film
		 * @property {Boolean} enable True indicates enable it.
		 * @property {Boolean} grayscale
		 * @property {Number} noiseIntensity
		 * @property {Number} scanlinesIntensity
		 * @property {Number} scanlinesCount
		 */

		_private.film = {
			enable: false,
			grayscale: false,
			noiseIntensity: 0.35,
			scanlinesIntensity: 0,
			scanlinesCount: 2048
		};

		/**
		 * Chromatic aberration.
		 * @typedef {Object} ChromaticAberration
		 * @property {Boolean} enable True indicates enable it.
		 * @property {Number} chromaFactor
		 */

		_private.chromaticAberration = {
			enable: false,
			chromaFactor: 0.025
		};

		/**
		 * Configuration about FXAA.
		 * @typedef {Object} FXAA
		 * @property {Boolean} enable True indicates enable it.
		 */

		_private.FXAA = {
			enable: false
		};

		/**
		 * Configuration about MSAA.
		 * @typedef {Object} MSAA
		 * @property {Boolean} enable True indicates enable it.
		 */

		_private.MSAA = {
			enable: true
		};

		_private.onChange = null;

		_private.objectProxy = new ObjectProxy({
			data: {
				enable: true,
				bloom: _private.bloom,
				screenSpaceAmbientOcclusion: _private.screenSpaceAmbientOcclusion,
				screenSpaceReflection: _private.screenSpaceReflection,
				colorCorrection: _private.colorCorrection,
				dof: _private.dof,
				vignetting: _private.vignetting,
				blurEdge: _private.blurEdge,
				film: _private.film,
				chromaticAberration: _private.chromaticAberration,
				FXAA: _private.FXAA,
				MSAA: _private.MSAA,
				temporalSuperSampling: _private.temporalSuperSampling,
			},
			onConvertValue: function (type, value) {
				if (type == 'color') {
					return Utils.parseColor(value, [1, 1, 1]);
				}
			},
			onChange: function (ev) {
				_private.needsRefresh = true;

				if (_private.onChange) {
					_private.onChange(ev);
				}
			}
		});

		// Refresh effect by current config
		this._refresh();

		// Show config always
		Object.defineProperty(this, 'customFormatters', {
			enumerable: false,
			configurable: false,
			get: function () {
				return ['object', { object: _private.objectProxy.dataProxy }];
			}
		});
	}

	// #region Private Functions

	_refresh() {
		let object = this.object;
		if (!object) {
			return;
		}

		let _private = this[__.private];

		if (!_private.needsRefresh) {
			return;
		}

		_private.needsRefresh = false;

		object.node.setPostEffect({
			temporalSuperSampling: _private.temporalSuperSampling,
			postEffect: {
				enable: _private.objectProxy.dataProxy.enable,
				bloom: _private.bloom,
				screenSpaceAmbientOcclusion: _private.screenSpaceAmbientOcclusion,
				screenSpaceReflection: _private.screenSpaceReflection,
				colorCorrection: _private.colorCorrection,
				dof: _private.dof,
				vignetting: _private.vignetting,
				blurEdge: _private.blurEdge,
				film: _private.film,
				chromaticAberration: _private.chromaticAberration,
				FXAA: _private.FXAA,
				MSAA: _private.MSAA,
			}
		});
	}

	// #endregion

	// #region BaseComponent Overrides

	onAdd(object) {
		super.onAdd(object);

		// Refresh config before render if needed
		this.app.addBeforeRenderCallback(this._onBeforeRenderCallback = () => {
			this._refresh();
		});
	}

	onRemove() {
		let _private = this[__.private];

		_private.objectProxy.dispose();

		this.app.removeBeforeRenderCallback(this._onBeforeRenderCallback);

		super.onRemove();
	}

	onCopy(component) {
		this.config = component.config;
	}

	// #endregion

	/**
	 * Enable/Disable effect.
	 * @type {Boolean}
	 * @example
     * let app = THING.App.current
	 * app.camera.postEffect.enable = false;
	 * // @expect(app.camera.postEffect.enable == false)
	 * @public
	 */
	get enable() {
		let _private = this[__.private];

		return _private.objectProxy.dataProxy.enable;
	}
	set enable(value) {
		let _private = this[__.private];

		if (_private.objectProxy.dataProxy.enable == value) {
			return;
		}

		_private.objectProxy.dataProxy.enable = value;

		this._refresh();
	}

	/**
	 * Get temporal super sampling.
	 * @type {TemporalSuperSampling}
	 * @example
     * let app = THING.App.current
	 * app.camera.postEffect.config.temporalSuperSampling.enable = true;
	 * // @expect(app.camera.postEffect.temporalSuperSampling.enable == true)
	 * @public
	 */
	get temporalSuperSampling() {
		return this[__.private].objectProxy.dataProxy.temporalSuperSampling;
	}

	/**
	 * Get bloom.
	 * @type {Bloom}
	 * @example
     * let app = THING.App.current
	 * app.camera.postEffect.config.bloom.enable = true;
	 * // @expect(app.camera.postEffect.bloom.enable == true)
	 * @public
	 */
	get bloom() {
		return this[__.private].objectProxy.dataProxy.bloom;
	}

	/**
	 * Get screen space ambient occlusion.
	 * @type {ScreenSpaceAmbientOcclusion}
	 * @example
     * let app = THING.App.current
	 * app.camera.postEffect.config.screenSpaceAmbientOcclusion.enable = true;
	 * // @expect(app.camera.postEffect.screenSpaceAmbientOcclusion.enable == true)
	 * @public
	*/
	get screenSpaceAmbientOcclusion() {
		return this[__.private].objectProxy.dataProxy.screenSpaceAmbientOcclusion;
	}

	/**
	 * Get screen space reflection.
	 * @type {ScreenSpaceReflection}
	 * @example
     * let app = THING.App.current
	 * app.camera.postEffect.config.screenSpaceReflection.enable = true;
	 * // @expect(app.camera.postEffect.screenSpaceReflection.enable == true)
	 * @public
	 */
	get screenSpaceReflection() {
		return this[__.private].objectProxy.dataProxy.screenSpaceReflection;
	}

	/**
	 * Get color correction.
	 * @type {ColorCorrection}
	 * @example
     * let app = THING.App.current
	 * app.camera.postEffect.config.colorCorrection.enable = true;
	 * // @expect(app.camera.postEffect.colorCorrection.enable == true)
	 * @public
	 */
	get colorCorrection() {
		return this[__.private].objectProxy.dataProxy.colorCorrection;
	}

	/**
	 * Get dof.
	 * @type {Dof}
	 * @example
     * let app = THING.App.current
	 * app.camera.postEffect.config.dof.enable = true;
	 * // @expect(app.camera.postEffect.dof.enable == true)
	 * @public
	 */
	get dof() {
		return this[__.private].objectProxy.dataProxy.dof;
	}

	/**
	 * Get vignetting.
	 * @type {Vignetting}
	 * @example
     * let app = THING.App.current
	 * app.camera.postEffect.config.vignetting.enable = true;
	 * // @expect(app.camera.postEffect.vignetting.enable == true)
	 * @public
	 */
	get vignetting() {
		return this[__.private].objectProxy.dataProxy.vignetting;
	}

	/**
	 * Get blur edge.
	 * @type {BlurEdge}
	 * @example
     * let app = THING.App.current
	 * app.camera.postEffect.config.blurEdge.enable = true;
	 * // @expect(app.camera.postEffect.blurEdge.enable == true)
	 * @public
	 */
	get blurEdge() {
		return this[__.private].objectProxy.dataProxy.blurEdge;
	}

	/**
	 * Get film.
	 * @type {Film}
	 * @example
     * let app = THING.App.current
	 * app.camera.postEffect.config.film.enable = true;
	 * // @expect(app.camera.postEffect.film.enable == true)
	 * @public
	 */
	get film() {
		return this[__.private].objectProxy.dataProxy.film;
	}

	/**
	 * Get chromatic aberration.
	 * @type {ChromaticAberration}
	 * @example
     * let app = THING.App.current
	 * app.camera.postEffect.config.chromaticAberration.enable = true;
	 * // @expect(app.camera.postEffect.chromaticAberration.enable == true)
	 * @public
	 */
	get chromaticAberration() {
		return this[__.private].objectProxy.dataProxy.chromaticAberration;
	}

	/**
	 * Get FXAA.
	 * @type {FXAA}
	 * @example
     * let app = THING.App.current
	 * app.camera.postEffect.config.FXAA.enable = true;
	 * // @expect(app.camera.postEffect.FXAA.enable == true)
	 * @public
	 */
	get FXAA() {
		return this[__.private].objectProxy.dataProxy.FXAA;
	}

	/**
	 * Get MSAA.
	 * @type {MSAA}
	 * @example
     * let app = THING.App.current
	 * app.camera.postEffect.config.MSAA.enable = false;
	 * // @expect(app.camera.postEffect.MSAA.enable == false)
	 * @public
	 */
	get MSAA() {
		return this[__.private].objectProxy.dataProxy.MSAA;
	}

	/**
	 * Configuration about post effect.
	 * @typedef {Object} PostEffectInfo
	 * @property {Boolean} enable True indicates enable it.
	 * @property {Bloom} bloom The bloom effect.
	 * @property {ScreenSpaceAmbientOcclusion} screenSpaceAmbientOcclusion The screen space ambient occlusion effect.
	 * @property {ScreenSpaceReflection} screenSpaceReflection The screen space reflection effect.
	 * @property {ColorCorrection} colorCorrection The color correction effect.
	 * @property {Dof} dof The dof effect.
	 * @property {Vignetting} vignetting The vignetting effect.
	 * @property {BlurEdge} blurEdge The blur edge effect.
	 * @property {Film} film The film effect.
	 * @property {ChromaticAberration} chromaticAberration The chromatic aberration effect.
	 * @property {FXAA} FXAA The FXAA effect.
	 * @property {MSAA} MSAA The MSAA effect.
	 * @property {TemporalSuperSampling} temporalSuperSampling The temporal super sampling effect.
	 */

	/**
	 * Get/Set config.
	 * @type {PostEffectInfo}
	 * @example
     * let app = THING.App.current
	 * app.camera.postEffect.config = {
	 *   FXAA: {enable: true}
	 * };
	 * // @expect(app.camera.postEffect.config.FXAA.enable == true)
	 * @public
	 */
	get config() {
		return this[__.private].objectProxy.dataProxy;
	}
	set config(value = {}) {
		let _private = this[__.private];

		let to = _private.objectProxy.dataProxy;
		if (Utils.isValid(value.enable)) {
			to.enable = value.enable;
		}

		Utils.mergeObject(to.bloom, value.bloom, true);
		Utils.mergeObject(to.screenSpaceAmbientOcclusion, value.screenSpaceAmbientOcclusion, true);
		Utils.mergeObject(to.screenSpaceReflection, value.screenSpaceReflection, true);
		Utils.mergeObject(to.colorCorrection, value.colorCorrection, true);
		Utils.mergeObject(to.dof, value.dof, true);
		Utils.mergeObject(to.vignetting, value.vignetting, true);
		Utils.mergeObject(to.blurEdge, value.blurEdge, true);
		Utils.mergeObject(to.film, value.film, true);
		Utils.mergeObject(to.chromaticAberration, value.chromaticAberration, true);
		Utils.mergeObject(to.FXAA, value.FXAA, true);
		Utils.mergeObject(to.MSAA, value.MSAA, true);
		Utils.mergeObject(to.temporalSuperSampling, value.temporalSuperSampling, true);
	}

	/**
	 * Get/Set when change callback function.
	 * @type {Function}
	 * @private
	 */
	get onChange() {
		let _private = this[__.private];

		return _private.onChange;
	}
	set onChange(value) {
		let _private = this[__.private];

		_private.onChange = value;
	}

}

export { CameraPostEffectComponent }