text: 
  import * as THREE from "../../libs/three.js/build/three.module.js";
  import {Utils} from "../utils.js";
  import {Gradients} from "./Gradients.js";
  import {Shaders} from "../../build/shaders/shaders.js";
  import {ClassificationScheme} from "./ClassificationScheme.js";
  import {PointSizeType, PointShape, TreeType, ElevationGradientRepeat} from "../defines.js";
  
  //
  // how to calculate the radius of a projected sphere in screen space
  // http://stackoverflow.com/questions/21648630/radius-of-projected-sphere-in-screen-space
  // http://stackoverflow.com/questions/3717226/radius-of-projected-sphere
  //
  
  
  export class PointCloudMaterial extends THREE.RawShaderMaterial {
  	constructor (parameters = {}) {
  		super();
  
  		this.visibleNodesTexture = Utils.generateDataTexture(2048, 1, new THREE.Color(0xffffff));
  		this.visibleNodesTexture.minFilter = THREE.NearestFilter;
  		this.visibleNodesTexture.magFilter = THREE.NearestFilter;
  
  		let getValid = (a, b) => {
  			if(a !== undefined){
  				return a;
  			}else{
  				return b;
  			}
  		}
  
  		let pointSize = getValid(parameters.size, 1.0);
  		let minSize = getValid(parameters.minSize, 2.0);
  		let maxSize = getValid(parameters.maxSize, 50.0);
  		let treeType = getValid(parameters.treeType, TreeType.OCTREE);
  
  		this._pointSizeType = PointSizeType.FIXED;
  		this._shape = PointShape.SQUARE;
  		this._useClipBox = false;
  		this.clipBoxes = [];
  		this.clipPolygons = [];
  		this._weighted = false;
  		this._gradient = Gradients.SPECTRAL;
  		this.gradientTexture = PointCloudMaterial.generateGradientTexture(this._gradient);
  		this._matcap = "matcap.jpg";
  		this.matcapTexture = Potree.PointCloudMaterial.generateMatcapTexture(this._matcap);
  		this.lights = false;
  		this.fog = false;
  		this._treeType = treeType;
  		this._useEDL = false;
  		this.defines = new Map();
  
  		this.ranges = new Map();
  
  		this._activeAttributeName = null;
  
  		this._defaultIntensityRangeChanged = false;
  		this._defaultElevationRangeChanged = false;
  
  		{
  			const [width, height] = [256, 1];
  			let data = new Uint8Array(width * 4);
  			let texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat);
  			texture.magFilter = THREE.NearestFilter;
  			texture.needsUpdate = true;
  
  			this.classificationTexture = texture;
  		}
  
  		this.attributes = {
  			position: { type: 'fv', value: [] },
  			color: { type: 'fv', value: [] },
  			normal: { type: 'fv', value: [] },
  			intensity: { type: 'f', value: [] },
  			classification: { type: 'f', value: [] },
  			returnNumber: { type: 'f', value: [] },
  			numberOfReturns: { type: 'f', value: [] },
  			pointSourceID: { type: 'f', value: [] },
  			indices: { type: 'fv', value: [] }
  		};
  
  		this.uniforms = {
  			level:				{ type: "f", value: 0.0 },
  			vnStart:			{ type: "f", value: 0.0 },
  			spacing:			{ type: "f", value: 1.0 },
  			blendHardness:		{ type: "f", value: 2.0 },
  			blendDepthSupplement:	{ type: "f", value: 0.0 },
  			fov:				{ type: "f", value: 1.0 },
  			screenWidth:		{ type: "f", value: 1.0 },
  			screenHeight:		{ type: "f", value: 1.0 },
  			near:				{ type: "f", value: 0.1 },
  			far:				{ type: "f", value: 1.0 },
  			uColor:				{ type: "c", value: new THREE.Color( 0xffffff ) },
  			uOpacity:			{ type: "f", value: 1.0 },
  			size:				{ type: "f", value: pointSize },
  			minSize:			{ type: "f", value: minSize },
  			maxSize:			{ type: "f", value: maxSize },
  			octreeSize:			{ type: "f", value: 0 },
  			bbSize:				{ type: "fv", value: [0, 0, 0] },
  			elevationRange:		{ type: "2fv", value: [0, 0] },
  
  			clipBoxCount:		{ type: "f", value: 0 },
  			//clipSphereCount:	{ type: "f", value: 0 },
  			clipPolygonCount:	{ type: "i", value: 0 },
  			clipBoxes:			{ type: "Matrix4fv", value: [] },
  			//clipSpheres:		{ type: "Matrix4fv", value: [] },
  			clipPolygons:		{ type: "3fv", value: [] },
  			clipPolygonVCount:	{ type: "iv", value: [] },
  			clipPolygonVP:		{ type: "Matrix4fv", value: [] },
  
  			visibleNodes:		{ type: "t", value: this.visibleNodesTexture },
  			pcIndex:			{ type: "f", value: 0 },
  			gradient:			{ type: "t", value: this.gradientTexture },
  			classificationLUT:	{ type: "t", value: this.classificationTexture },
  			uHQDepthMap:		{ type: "t", value: null },
  			toModel:			{ type: "Matrix4f", value: [] },
  			diffuse:			{ type: "fv", value: [1, 1, 1] },
  			transition:			{ type: "f", value: 0.5 },
  
  			 intensityRange:		{ type: "fv", value: [Infinity, -Infinity] },
  
  			intensity_gbc: 		{ type: "fv", value: [1, 0, 0]},
  			uRGB_gbc:	 		{ type: "fv", value: [1, 0, 0]},
  			// intensityGamma:		{ type: "f", value: 1 },
  			// intensityContrast:	{ type: "f", value: 0 },
  			// intensityBrightness:{ type: "f", value: 0 },
  			// rgbGamma:			{ type: "f", value: 1 },
  			// rgbContrast:		{ type: "f", value: 0 },
  			// rgbBrightness:		{ type: "f", value: 0 },
  			wRGB:				{ type: "f", value: 1 },
  			wIntensity:			{ type: "f", value: 0 },
  			wElevation:			{ type: "f", value: 0 },
  			wClassification:	{ type: "f", value: 0 },
  			wReturnNumber:		{ type: "f", value: 0 },
  			wSourceID:			{ type: "f", value: 0 },
  			useOrthographicCamera: { type: "b", value: false },
  			elevationGradientRepat: { type: "i", value: ElevationGradientRepeat.CLAMP },
  			clipTask:			{ type: "i", value: 1 },
  			clipMethod:			{ type: "i", value: 1 },
  			uShadowColor:		{ type: "3fv", value: [0, 0, 0] },
  
  			uExtraScale:		{ type: "f", value: 1},
  			uExtraOffset:		{ type: "f", value: 0},
  			uExtraRange:		{ type: "2fv", value: [0, 1] },
  			uExtraGammaBrightContr:	{ type: "3fv", value: [1, 0, 0] },
  
  			uFilterReturnNumberRange:		{ type: "fv", value: [0, 7]},
  			uFilterNumberOfReturnsRange:	{ type: "fv", value: [0, 7]},
  			uFilterGPSTimeClipRange:		{ type: "fv", value: [0, 7]},
  			uFilterPointSourceIDClipRange:		{ type: "fv", value: [0, 65535]},
  			matcapTextureUniform: 	{ type: "t", value: this.matcapTexture },
  			backfaceCulling: { type: "b", value: false },
  		};
  
  		this.classification = ClassificationScheme.DEFAULT;
  
  		this.defaultAttributeValues.normal = [0, 0, 0];
  		this.defaultAttributeValues.classification = [0, 0, 0];
  		this.defaultAttributeValues.indices = [0, 0, 0, 0];
  
  		this.vertexShader = Shaders['pointcloud.vs'];
  		this.fragmentShader = Shaders['pointcloud.fs'];
  		
  		this.vertexColors = THREE.VertexColors;
  
  		this.updateShaderSource();
  	}
  
  	setDefine(key, value){
  		if(value !== undefined && value !== null){
  			if(this.defines.get(key) !== value){
  				this.defines.set(key, value);
  				this.updateShaderSource();
  			}
  		}else{
  			this.removeDefine(key);
  		}
  	}
  
  	removeDefine(key){
  		this.defines.delete(key);
  	}
  
  	updateShaderSource () {
  
  		let vs = Shaders['pointcloud.vs'];
  		let fs = Shaders['pointcloud.fs'];
  		let definesString = this.getDefines();
  
  		let vsVersionIndex = vs.indexOf("#version ");
  		let fsVersionIndex = fs.indexOf("#version ");
  
  		if(vsVersionIndex >= 0){
  			vs = vs.replace(/(#version .*)/, `$1\n${definesString}`)
  		}else{
  			vs = `${definesString}\n${vs}`;
  		}
  
  		if(fsVersionIndex >= 0){
  			fs = fs.replace(/(#version .*)/, `$1\n${definesString}`)
  		}else{
  			fs = `${definesString}\n${fs}`;
  		}
  
  		this.vertexShader = vs;
  		this.fragmentShader = fs;
  
  		if (this.opacity === 1.0) {
  			this.blending = THREE.NoBlending;
  			this.transparent = false;
  			this.depthTest = true;
  			this.depthWrite = true;
  			this.depthFunc = THREE.LessEqualDepth;
  		} else if (this.opacity < 1.0 && !this.useEDL) {
  			this.blending = THREE.AdditiveBlending;
  			this.transparent = true;
  			this.depthTest = false;
  			this.depthWrite = true;
  			this.depthFunc = THREE.AlwaysDepth;
  		}
  
  		if (this.weighted) {
  			this.blending = THREE.AdditiveBlending;
  			this.transparent = true;
  			this.depthTest = true;
  			this.depthWrite = false;
  		}
  
  		this.needsUpdate = true;
  	}
  
  	getDefines () {
  		let defines = [];
  
  		if (this.pointSizeType === PointSizeType.FIXED) {
  			defines.push('#define fixed_point_size');
  		} else if (this.pointSizeType === PointSizeType.ATTENUATED) {
  			defines.push('#define attenuated_point_size');
  		} else if (this.pointSizeType === PointSizeType.ADAPTIVE) {
  			defines.push('#define adaptive_point_size');
  		}
  
  		if (this.shape === PointShape.SQUARE) {
  			defines.push('#define square_point_shape');
  		} else if (this.shape === PointShape.CIRCLE) {
  			defines.push('#define circle_point_shape');
  		} else if (this.shape === PointShape.PARABOLOID) {
  			defines.push('#define paraboloid_point_shape');
  		}
  
  		if (this._useEDL) {
  			defines.push('#define use_edl');
  		}
  
  		if(this.activeAttributeName){
  			let attributeName = this.activeAttributeName.replace(/[^a-zA-Z0-9]/g, '_');
  
  			defines.push(`#define color_type_${attributeName}`);
  		}
  		
  		if(this._treeType === TreeType.OCTREE){
  			defines.push('#define tree_type_octree');
  		}else if(this._treeType === TreeType.KDTREE){
  			defines.push('#define tree_type_kdtree');
  		}
  
  		if (this.weighted) {
  			defines.push('#define weighted_splats');
  		}
  
  		for(let [key, value] of this.defines){
  			defines.push(value);
  		}
  
  		return defines.join("\n");
  	}
  
  	setClipBoxes (clipBoxes) {
  		if (!clipBoxes) {
  			return;
  		}
  
  		let doUpdate = (this.clipBoxes.length !== clipBoxes.length) && (clipBoxes.length === 0 || this.clipBoxes.length === 0);
  
  		this.uniforms.clipBoxCount.value = this.clipBoxes.length;
  		this.clipBoxes = clipBoxes;
  
  		if (doUpdate) {
  			this.updateShaderSource();
  		}
  
  		this.uniforms.clipBoxes.value = new Float32Array(this.clipBoxes.length * 16);
  
  		for (let i = 0; i < this.clipBoxes.length; i++) {
  			let box = clipBoxes[i];
  
  			this.uniforms.clipBoxes.value.set(box.inverse.elements, 16 * i);
  		}
  
  		for (let i = 0; i < this.uniforms.clipBoxes.value.length; i++) {
  			if (Number.isNaN(this.uniforms.clipBoxes.value[i])) {
  				this.uniforms.clipBoxes.value[i] = Infinity;
  			}
  		}
  	}
  
  	setClipPolygons(clipPolygons, maxPolygonVertices) {
  		if(!clipPolygons){
  			return;
  		}
  
  		this.clipPolygons = clipPolygons;
  
  		let doUpdate = (this.clipPolygons.length !== clipPolygons.length);
  
  		if(doUpdate){
  			this.updateShaderSource();
  		}
  	}
  	
  	get gradient(){
  		return this._gradient;
  	}
  
  	set gradient (value) {
  		if (this._gradient !== value) {
  			this._gradient = value;
  			this.gradientTexture = PointCloudMaterial.generateGradientTexture(this._gradient);
  			this.uniforms.gradient.value = this.gradientTexture;
  		}
  	}
  
  	get matcap(){
  		return this._matcap;
  	}
  
  	set matcap (value) {
  		if (this._matcap !== value) {
  			this._matcap = value;
  			this.matcapTexture = Potree.PointCloudMaterial.generateMatcapTexture(this._matcap);
  			this.uniforms.matcapTextureUniform.value = this.matcapTexture;
  		}
  	}
  	get useOrthographicCamera() {
  		return this.uniforms.useOrthographicCamera.value;
  	}
  
  	set useOrthographicCamera(value) {
  		if(this.uniforms.useOrthographicCamera.value !== value){
  			this.uniforms.useOrthographicCamera.value = value;
  		}
  	}
  	get backfaceCulling() {
  		return this.uniforms.backfaceCulling.value;
  	}
  
  	set backfaceCulling(value) {
  		if(this.uniforms.backfaceCulling.value !== value){
  			this.uniforms.backfaceCulling.value = value;
  			this.dispatchEvent({type: 'backface_changed', target: this});
  		}
  	}
  
  	recomputeClassification () {
  		const classification = this.classification;
  		const data = this.classificationTexture.image.data;
  
  		let width = 256;
  		const black = [1, 1, 1, 1];
  
  		let valuesChanged = false;
  
  		for (let i = 0; i < width; i++) {
  
  			let color;
  			let visible = true;
  
  			if (classification[i]) {
  				color = classification[i].color;
  				visible = classification[i].visible;
  			} else if (classification[i % 32]) {
  				color = classification[i % 32].color;
  				visible = classification[i % 32].visible;
  			} else if(classification.DEFAULT) {
  				color = classification.DEFAULT.color;
  				visible = classification.DEFAULT.visible;
  			}else{
  				color = black;
  			}
  
  			const r = parseInt(255 * color[0]);
  			const g = parseInt(255 * color[1]);
  			const b = parseInt(255 * color[2]);
  			const a = visible ? parseInt(255 * color[3]) : 0;
  
  
  			if(data[4 * i + 0] !== r){
  				data[4 * i + 0] = r;
  				valuesChanged = true;
  			}
  
  			if(data[4 * i + 1] !== g){
  				data[4 * i + 1] = g;
  				valuesChanged = true;
  			}
  
  			if(data[4 * i + 2] !== b){
  				data[4 * i + 2] = b;
  				valuesChanged = true;
  			}
  
  			if(data[4 * i + 3] !== a){
  				data[4 * i + 3] = a;
  				valuesChanged = true;
  			}
  		}
  
  		if(valuesChanged){
  			this.classificationTexture.needsUpdate = true;
  
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get spacing () {
  		return this.uniforms.spacing.value;
  	}
  
  	set spacing (value) {
  		if (this.uniforms.spacing.value !== value) {
  			this.uniforms.spacing.value = value;
  		}
  	}
  
  	get useClipBox () {
  		return this._useClipBox;
  	}
  
  	set useClipBox (value) {
  		if (this._useClipBox !== value) {
  			this._useClipBox = value;
  			this.updateShaderSource();
  		}
  	}
  
  	get clipTask(){
  		return this.uniforms.clipTask.value;
  	}
  
  	set clipTask(mode){
  		this.uniforms.clipTask.value = mode;
  	}
  
  	get elevationGradientRepat(){
  		return this.uniforms.elevationGradientRepat.value;
  	}
  
  	set elevationGradientRepat(mode){
  		this.uniforms.elevationGradientRepat.value = mode;
  	}
  
  	get clipMethod(){
  		return this.uniforms.clipMethod.value;
  	}
  
  	set clipMethod(mode){
  		this.uniforms.clipMethod.value = mode;
  	}
  
  	get weighted(){
  		return this._weighted;
  	}
  
  	set weighted (value) {
  		if (this._weighted !== value) {
  			this._weighted = value;
  			this.updateShaderSource();
  		}
  	}
  
  	get fov () {
  		return this.uniforms.fov.value;
  	}
  
  	set fov (value) {
  		if (this.uniforms.fov.value !== value) {
  			this.uniforms.fov.value = value;
  			// this.updateShaderSource();
  		}
  	}
  
  	get screenWidth () {
  		return this.uniforms.screenWidth.value;
  	}
  
  	set screenWidth (value) {
  		if (this.uniforms.screenWidth.value !== value) {
  			this.uniforms.screenWidth.value = value;
  			// this.updateShaderSource();
  		}
  	}
  
  	get screenHeight () {
  		return this.uniforms.screenHeight.value;
  	}
  
  	set screenHeight (value) {
  		if (this.uniforms.screenHeight.value !== value) {
  			this.uniforms.screenHeight.value = value;
  			// this.updateShaderSource();
  		}
  	}
  
  	get near () {
  		return this.uniforms.near.value;
  	}
  
  	set near (value) {
  		if (this.uniforms.near.value !== value) {
  			this.uniforms.near.value = value;
  		}
  	}
  
  	get far () {
  		return this.uniforms.far.value;
  	}
  
  	set far (value) {
  		if (this.uniforms.far.value !== value) {
  			this.uniforms.far.value = value;
  		}
  	}
  	
  	get opacity(){
  		return this.uniforms.uOpacity.value;
  	}
  
  	set opacity (value) {
  		if (this.uniforms && this.uniforms.uOpacity) {
  			if (this.uniforms.uOpacity.value !== value) {
  				this.uniforms.uOpacity.value = value;
  				this.updateShaderSource();
  				this.dispatchEvent({
  					type: 'opacity_changed',
  					target: this
  				});
  				this.dispatchEvent({
  					type: 'material_property_changed',
  					target: this
  				});
  			}
  		}
  	}
  
  	get activeAttributeName(){
  		return this._activeAttributeName;
  	}
  
  	set activeAttributeName(value){
  		if (this._activeAttributeName !== value) {
  			this._activeAttributeName = value;
  
  			this.updateShaderSource();
  			this.dispatchEvent({
  				type: 'active_attribute_changed',
  				target: this
  			});
  
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get pointSizeType () {
  		return this._pointSizeType;
  	}
  
  	set pointSizeType (value) {
  		if (this._pointSizeType !== value) {
  			this._pointSizeType = value;
  			this.updateShaderSource();
  			this.dispatchEvent({
  				type: 'point_size_type_changed',
  				target: this
  			});
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get useEDL(){
  		return this._useEDL;
  	}
  
  	set useEDL (value) {
  		if (this._useEDL !== value) {
  			this._useEDL = value;
  			this.updateShaderSource();
  		}
  	}
  
  	get color () {
  		return this.uniforms.uColor.value;
  	}
  
  	set color (value) {
  		if (!this.uniforms.uColor.value.equals(value)) {
  			this.uniforms.uColor.value.copy(value);
  			
  			this.dispatchEvent({
  				type: 'color_changed',
  				target: this
  			});
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get shape () {
  		return this._shape;
  	}
  
  	set shape (value) {
  		if (this._shape !== value) {
  			this._shape = value;
  			this.updateShaderSource();
  			this.dispatchEvent({type: 'point_shape_changed', target: this});
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get treeType () {
  		return this._treeType;
  	}
  
  	set treeType (value) {
  		if (this._treeType !== value) {
  			this._treeType = value;
  			this.updateShaderSource();
  		}
  	}
  
  	get bbSize () {
  		return this.uniforms.bbSize.value;
  	}
  
  	set bbSize (value) {
  		this.uniforms.bbSize.value = value;
  	}
  
  	get size () {
  		return this.uniforms.size.value;
  	}
  
  	set size (value) {
  		if (this.uniforms.size.value !== value) {
  			this.uniforms.size.value = value;
  
  			this.dispatchEvent({
  				type: 'point_size_changed',
  				target: this
  			});
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get minSize(){
  		return this.uniforms.minSize.value;
  	}
  
  	set minSize(value){
  		if (this.uniforms.minSize.value !== value) {
  			this.uniforms.minSize.value = value;
  
  			this.dispatchEvent({
  				type: 'point_size_changed',
  				target: this
  			});
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get elevationRange () {
  		return this.uniforms.elevationRange.value;
  	}
  
  	set elevationRange (value) {
  		let changed = this.uniforms.elevationRange.value[0] !== value[0]
  			|| this.uniforms.elevationRange.value[1] !== value[1];
  
  		if(changed){
  			this.uniforms.elevationRange.value = value;
  
  			this._defaultElevationRangeChanged = true;
  
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get heightMin () {
  		return this.uniforms.elevationRange.value[0];
  	}
  
  	set heightMin (value) {
  		this.elevationRange = [value, this.elevationRange[1]];
  	}
  
  	get heightMax () {
  		return this.uniforms.elevationRange.value[1];
  	}
  
  	set heightMax (value) {
  		this.elevationRange = [this.elevationRange[0], value];
  	}
  
  	get transition () {
  		return this.uniforms.transition.value;
  	}
  
  	set transition (value) {
  		this.uniforms.transition.value = value;
  	}
  
  	get intensityRange () {
  		return this.uniforms.intensityRange.value;
  	}
  
  	set intensityRange (value) {
  		if (!(value instanceof Array && value.length === 2)) {
  			return;
  		}
  
  		if (value[0] === this.uniforms.intensityRange.value[0] &&
  			value[1] === this.uniforms.intensityRange.value[1]) {
  			return;
  		}
  
  		this.uniforms.intensityRange.value = value;
  
  		this._defaultIntensityRangeChanged = true;
  
  		this.dispatchEvent({
  			type: 'material_property_changed',
  			target: this
  		});
  	}
  
  	get intensityGamma () {
  		return this.uniforms.intensity_gbc.value[0];
  	}
  
  	set intensityGamma (value) {
  		if (this.uniforms.intensity_gbc.value[0] !== value) {
  			this.uniforms.intensity_gbc.value[0] = value;
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get intensityContrast () {
  		return this.uniforms.intensity_gbc.value[2];
  	}
  
  	set intensityContrast (value) {
  		if (this.uniforms.intensity_gbc.value[2] !== value) {
  			this.uniforms.intensity_gbc.value[2] = value;
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get intensityBrightness () {
  		return this.uniforms.intensity_gbc.value[1];
  	}
  
  	set intensityBrightness (value) {
  		if (this.uniforms.intensity_gbc.value[1] !== value) {
  			this.uniforms.intensity_gbc.value[1] = value;
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get rgbGamma () {
  		return this.uniforms.uRGB_gbc.value[0];
  	}
  
  	set rgbGamma (value) {
  		if (this.uniforms.uRGB_gbc.value[0] !== value) {
  			this.uniforms.uRGB_gbc.value[0] = value;
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get rgbContrast () {
  		return this.uniforms.uRGB_gbc.value[2];
  	}
  
  	set rgbContrast (value) {
  		if (this.uniforms.uRGB_gbc.value[2] !== value) {
  			this.uniforms.uRGB_gbc.value[2] = value;
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get rgbBrightness () {
  		return this.uniforms.uRGB_gbc.value[1];
  	}
  
  	set rgbBrightness (value) {
  		if (this.uniforms.uRGB_gbc.value[1] !== value) {
  			this.uniforms.uRGB_gbc.value[1] = value;
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	
  	get extraGamma () {
  		return this.uniforms.uExtraGammaBrightContr.value[0];
  	}
  
  	set extraGamma (value) {
  		if (this.uniforms.uExtraGammaBrightContr.value[0] !== value) {
  			this.uniforms.uExtraGammaBrightContr.value[0] = value;
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get extraBrightness () {
  		return this.uniforms.uExtraGammaBrightContr.value[1];
  	}
  
  	set extraBrightness (value) {
  		if (this.uniforms.uExtraGammaBrightContr.value[1] !== value) {
  			this.uniforms.uExtraGammaBrightContr.value[1] = value;
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get extraContrast () {
  		return this.uniforms.uExtraGammaBrightContr.value[2];
  	}
  
  	set extraContrast (value) {
  		if (this.uniforms.uExtraGammaBrightContr.value[2] !== value) {
  			this.uniforms.uExtraGammaBrightContr.value[2] = value;
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	getRange(attributeName){
  		return this.ranges.get(attributeName);
  	}
  
  	setRange(attributeName, newRange){
  
  		let rangeChanged = false;
  
  		let oldRange = this.ranges.get(attributeName);
  
  		if(oldRange != null && newRange != null){
  			rangeChanged = oldRange[0] !== newRange[0] || oldRange[1] !== newRange[1];
  		}else{
  			rangeChanged = true;
  		}
  
  		this.ranges.set(attributeName, newRange);
  
  		if(rangeChanged){
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get extraRange () {
  		return this.uniforms.uExtraRange.value;
  	}
  
  	set extraRange (value) {
  		if (!(value instanceof Array && value.length === 2)) {
  			return;
  		}
  
  		if (value[0] === this.uniforms.uExtraRange.value[0] &&
  			value[1] === this.uniforms.uExtraRange.value[1]) {
  			return;
  		}
  
  		this.uniforms.uExtraRange.value = value;
  
  		this._defaultExtraRangeChanged = true;
  
  		this.dispatchEvent({
  			type: 'material_property_changed',
  			target: this
  		});
  	}
  
  	get weightRGB () {
  		return this.uniforms.wRGB.value;
  	}
  
  	set weightRGB (value) {
  		if(this.uniforms.wRGB.value !== value){
  			this.uniforms.wRGB.value = value;
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get weightIntensity () {
  		return this.uniforms.wIntensity.value;
  	}
  
  	set weightIntensity (value) {
  		if(this.uniforms.wIntensity.value !== value){
  			this.uniforms.wIntensity.value = value;
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get weightElevation () {
  		return this.uniforms.wElevation.value;
  	}
  
  	set weightElevation (value) {
  		if(this.uniforms.wElevation.value !== value){
  			this.uniforms.wElevation.value = value;
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get weightClassification () {
  		return this.uniforms.wClassification.value;
  	}
  
  	set weightClassification (value) {
  		if(this.uniforms.wClassification.value !== value){
  			this.uniforms.wClassification.value = value;
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get weightReturnNumber () {
  		return this.uniforms.wReturnNumber.value;
  	}
  
  	set weightReturnNumber (value) {
  		if(this.uniforms.wReturnNumber.value !== value){
  			this.uniforms.wReturnNumber.value = value;
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	get weightSourceID () {
  		return this.uniforms.wSourceID.value;
  	}
  
  	set weightSourceID (value) {
  		if(this.uniforms.wSourceID.value !== value){
  			this.uniforms.wSourceID.value = value;
  			this.dispatchEvent({
  				type: 'material_property_changed',
  				target: this
  			});
  		}
  	}
  
  	static generateGradientTexture (gradient) {
  		let size = 64;
  
  		// create canvas
  		let canvas = document.createElement('canvas');
  		canvas.width = size;
  		canvas.height = size;
  
  		// get context
  		let context = canvas.getContext('2d');
  
  		// draw gradient
  		context.rect(0, 0, size, size);
  		let ctxGradient = context.createLinearGradient(0, 0, size, size);
  
  		for (let i = 0; i < gradient.length; i++) {
  			let step = gradient[i];
  
  			ctxGradient.addColorStop(step[0], '#' + step[1].getHexString());
  		}
  
  		context.fillStyle = ctxGradient;
  		context.fill();
  		
  		//let texture = new THREE.Texture(canvas);
  		let texture = new THREE.CanvasTexture(canvas);
  		texture.needsUpdate = true;
  		
  		texture.minFilter = THREE.LinearFilter;
  		texture.wrap = THREE.RepeatWrapping;
  		texture.repeat = 2;
  		// textureImage = texture.image;
  
  		return texture;
  	}
  	
  	static generateMatcapTexture (matcap) {
  	var url = new URL(Potree.resourcePath + "/textures/matcap/" + matcap).href;
  	let texture = new THREE.TextureLoader().load( url );
  		texture.magFilter = texture.minFilter = THREE.LinearFilter; 
  		texture.needsUpdate = true;
  		// PotreeConverter_1.6_2018_07_29_windows_x64\PotreeConverter.exe autzen_xyzrgbXYZ_ascii.xyz -f xyzrgbXYZ -a RGB NORMAL -o autzen_xyzrgbXYZ_ascii_a -p index --overwrite
  		// Switch matcap texture on the fly : viewer.scene.pointclouds[0].material.matcap = 'matcap1.jpg'; 
  		// For non power of 2, use LinearFilter and dont generate mipmaps, For power of 2, use NearestFilter and generate mipmaps : matcap2.jpg 1 2 8 11 12 13
  		return texture; 
  	}
  
  	disableEvents(){
  		if(this._hiddenListeners === undefined){
  			this._hiddenListeners = this._listeners;
  			this._listeners = {};
  		}
  	};
  
  	enableEvents(){
  		this._listeners = this._hiddenListeners;
  		this._hiddenListeners = undefined;
  	};
  
  	// copyFrom(from){
  
  	// 	var a = 10;
  
  	// 	for(let name of Object.keys(this.uniforms)){
  	// 		this.uniforms[name].value = from.uniforms[name].value;
  	// 	}
  	// }
  
  	// copy(from){
  	// 	this.copyFrom(from);
  	// }
  
  }
  