diff --git a/src/Renderer/PointsMaterial.js b/src/Renderer/PointsMaterial.js index 90df53389c..9b810c70c2 100644 --- a/src/Renderer/PointsMaterial.js +++ b/src/Renderer/PointsMaterial.js @@ -186,57 +186,56 @@ class PointsMaterial extends THREE.ShaderMaterial { * pointMaterial.recomputeClassification(); */ constructor(options = {}) { - const intensityRange = options.intensityRange || new THREE.Vector2(1, 65536); - const elevationRange = options.elevationRange || new THREE.Vector2(0, 1000); - const angleRange = options.angleRange || new THREE.Vector2(-90, 90); - const classificationScheme = options.classification || ClassificationScheme.DEFAULT; - const discreteScheme = options.discreteScheme || DiscreteScheme.DEFAULT; - const size = options.size || 0; - const mode = options.mode || PNTS_MODE.COLOR; - const shape = options.shape || PNTS_SHAPE.CIRCLE; - const sizeMode = size === 0 ? PNTS_SIZE_MODE.ATTENUATED : (options.sizeMode || PNTS_SIZE_MODE.VALUE); - const minAttenuatedSize = options.minAttenuatedSize || 3; - const maxAttenuatedSize = options.maxAttenuatedSize || 10; - let gradients = Gradients; - if (options.gradient) { - gradients = { - ...options.gradient, - ...Gradients, - }; - } + const gradients = { + ...options.gradient, + ...Gradients, + }; + options.gradient = Object.values(gradients)[0]; + + const { + intensityRange = new THREE.Vector2(1, 65536), + elevationRange = new THREE.Vector2(0, 1000), + angleRange = new THREE.Vector2(-90, 90), + classificationScheme = ClassificationScheme.DEFAULT, + discreteScheme = DiscreteScheme.DEFAULT, + size = 1, + mode = PNTS_MODE.COLOR, + shape = PNTS_SHAPE.CIRCLE, + sizeMode = PNTS_SIZE_MODE.ATTENUATED, + minAttenuatedSize = 3, + maxAttenuatedSize = 10, + gradient, + scale = 0.05 * 0.5 / Math.tan(1.0 / 2.0), + ...materialOptions + } = options; + + super({ + ...materialOptions, + fog: true, + precision: 'highp', + vertexColors: true, + }); + this.uniforms = THREE.UniformsUtils.merge([ + // THREE.PointsMaterial uniforms + THREE.UniformsLib.points, + THREE.UniformsLib.fog, + ]); + this.vertexShader = PointsVS; + this.fragmentShader = PointsFS; - delete options.intensityRange; - delete options.elevationRange; - delete options.angleRange; - delete options.classification; - delete options.discreteScheme; - delete options.size; - delete options.mode; - delete options.shape; - delete options.sizeMode; - delete options.minAttenuatedSize; - delete options.maxAttenuatedSize; - delete options.gradient; - - super(options); this.userData.needTransparency = {}; this.gradients = gradients; this.gradientTexture = new THREE.CanvasTexture(); - this.vertexShader = PointsVS; - - const scale = options.scale || 0.05 * 0.5 / Math.tan(1.0 / 2.0); // autosizing scale - CommonMaterial.setDefineMapping(this, 'PNTS_MODE', PNTS_MODE); CommonMaterial.setDefineMapping(this, 'PNTS_SHAPE', PNTS_SHAPE); CommonMaterial.setDefineMapping(this, 'PNTS_SIZE_MODE', PNTS_SIZE_MODE); - CommonMaterial.setUniformProperty(this, 'size', size); + this.size = size; CommonMaterial.setUniformProperty(this, 'mode', mode); CommonMaterial.setUniformProperty(this, 'shape', shape); CommonMaterial.setUniformProperty(this, 'picking', false); CommonMaterial.setUniformProperty(this, 'opacity', this.opacity); - CommonMaterial.setUniformProperty(this, 'overlayColor', options.overlayColor || new THREE.Vector4(0, 0, 0, 0)); CommonMaterial.setUniformProperty(this, 'intensityRange', intensityRange); CommonMaterial.setUniformProperty(this, 'elevationRange', elevationRange); CommonMaterial.setUniformProperty(this, 'angleRange', angleRange); @@ -268,16 +267,104 @@ class PointsMaterial extends THREE.ShaderMaterial { this.recomputeDiscreteTexture(); // Gradient texture for continuous values - this.gradient = Object.values(gradients)[0]; + this.gradient = gradient; CommonMaterial.setUniformProperty(this, 'gradientTexture', this.gradientTexture); - this.fragmentShader = PointsFS; - if (__DEBUG__) { this.defines.DEBUG = 1; } } + /** + * Copy the parameters from the passed material into this material. + * @override + * @param {THREE.PointsMaterial} source + * @returns {this} + */ + copy(source) { + if (source.isShaderMaterial) { + super.copy(source); + } else { + THREE.Material.prototype.copy.call(this, source); + } + + // Parameters of THREE.PointsMaterial + this.color.copy(source.color); + this.map = source.map; + this.alphaMap = source.alphaMap; + this.size = source.size; + this.sizeAttenuation = source.sizeAttenuation; + this.fog = source.fog; + + return this; + } + + /** @returns {THREE.Color} */ + get color() { + return this.uniforms.diffuse.value; + } + + /** @param {THREE.Color} color */ + set color(color) { + this.uniforms.diffuse.value.copy(color); + } + + /** @returns {THREE.Texture | null} */ + get map() { + return this.uniforms.map.value; + } + + /** @param {THREE.Texture | null} map */ + set map(map) { + this.uniforms.map.value = map; + if (!map) { return; } + + if (map.matrixAutoUpdate) { + map.updateMatrix(); + } + + this.uniforms.uvTransform.value.copy(map.matrix); + } + + /** @returns {THREE.Texture | null} */ + get alphaMap() { + return this.uniforms.alphaMap.value; + } + + /** @param {THREE.Texture | null} map */ + set alphaMap(map) { + this.uniforms.alphaMap.value = map; + if (!map) { return; } + + if (map.matrixAutoUpdate) { + map.updateMatrix(); + } + + this.uniforms.alphaMapTransform.value.copy(map.matrix); + } + + /** @returns {number} */ + get size() { + return this.uniforms.size.value; + } + + /** @param {number} size */ + set size(size) { + this.uniforms.size.value = size; + } + + /** @returns {boolean} */ + get sizeAttenuation() { + return this.sizeMode !== PNTS_SIZE_MODE.VALUE; + } + + /** @param {boolean} value */ + set sizeAttenuation(value) { + this.sizeMode = value ? + PNTS_SIZE_MODE.ATTENUATED : + PNTS_SIZE_MODE.VALUE; + } + recomputeClassification() { const needTransparency = recomputeTexture(this.classificationScheme, this.classificationTexture, 256); this.userData.needTransparency[PNTS_MODE.CLASSIFICATION] = needTransparency; @@ -299,36 +386,11 @@ class PointsMaterial extends THREE.ShaderMaterial { }); } - copy(source) { - super.copy(source); - return this; - } - enablePicking(picking) { this.picking = picking; this.blending = picking ? THREE.NoBlending : THREE.NormalBlending; } - update(source) { - this.visible = source.visible; - this.opacity = source.opacity; - this.transparent = source.transparent; - this.size = source.size; - this.mode = source.mode; - this.shape = source.shape; - this.sizeMode = source.sizeMode; - this.minAttenuatedSize = source.minAttenuatedSize; - this.maxAttenuatedSize = source.maxAttenuatedSize; - this.picking = source.picking; - this.scale = source.scale; - this.overlayColor.copy(source.overlayColor); - this.intensityRange.copy(source.intensityRange); - this.elevationRange.copy(source.elevationRange); - this.angleRange.copy(source.angleRange); - Object.assign(this.defines, source.defines); - return this; - } - set gradient(value) { this.gradientTexture = generateGradientTexture(value); } diff --git a/src/Renderer/Shader/PointsFS.glsl b/src/Renderer/Shader/PointsFS.glsl index e0644729da..2a79ed4e17 100644 --- a/src/Renderer/Shader/PointsFS.glsl +++ b/src/Renderer/Shader/PointsFS.glsl @@ -1,13 +1,23 @@ -#include +#define USE_COLOR_ALPHA + +#include +#include +#include +#include +#include #include +#include + +uniform vec3 diffuse; +uniform float opacity; -varying vec4 vColor; uniform bool picking; uniform int shape; void main() { - #include - //square shape does not require any change. + +// Early discard (clipping planes and shape) +#include if (shape == PNTS_SHAPE_CIRCLE) { //circular rendering in glsl if ((length(gl_PointCoord - 0.5) > 0.5) || (vColor.a == 0.0)) { @@ -15,5 +25,19 @@ void main() { } } - gl_FragColor = vColor; +#include + + vec4 diffuseColor = vec4(diffuse, opacity); +#include +#include + +#include +#include + + vec3 outgoingLight = diffuseColor.rgb; +#include // gl_FragColor +#include +#include +#include + } diff --git a/src/Renderer/Shader/PointsVS.glsl b/src/Renderer/Shader/PointsVS.glsl index f1be5fc9c5..6dae914c36 100644 --- a/src/Renderer/Shader/PointsVS.glsl +++ b/src/Renderer/Shader/PointsVS.glsl @@ -1,6 +1,14 @@ -#include #include +#include +#include #include +#include +varying vec4 vColor; // color_pars_vertex + +#ifdef USE_POINTS_UV + varying vec2 vUv; + uniform mat3 uvTransform; +#endif #define NB_CLASS 8. @@ -9,8 +17,6 @@ uniform float scale; uniform bool picking; uniform int mode; -uniform float opacity; -uniform vec4 overlayColor; uniform vec2 elevationRange; uniform vec2 intensityRange; @@ -23,8 +29,6 @@ uniform int sizeMode; uniform float minAttenuatedSize; uniform float maxAttenuatedSize; -attribute vec3 color; -attribute vec2 range; attribute vec4 unique_id; attribute float intensity; attribute float classification; @@ -34,22 +38,22 @@ attribute float returnNumber; attribute float numberOfReturns; attribute float scanAngle; -varying vec4 vColor; - void main() { - + vColor = vec4(1.0); if (picking) { vColor = unique_id; } else { - vColor.a = 1.0; if (mode == PNTS_MODE_CLASSIFICATION) { vec2 uv = vec2(classification/255., 0.5); vColor = texture2D(classificationTexture, uv); } else if (mode == PNTS_MODE_NORMAL) { vColor.rgb = abs(normal); } else if (mode == PNTS_MODE_COLOR) { - // default to color mode - vColor.rgb = mix(color, overlayColor.rgb, overlayColor.a); +#if defined(USE_COLOR) + vColor.rgb = color.rgb; +#elif defined(USE_COLOR_ALPHA) + vColor = color; +#endif } else if (mode == PNTS_MODE_RETURN_NUMBER) { vec2 uv = vec2(returnNumber/255., 0.5); vColor = texture2D(discreteTexture, uv); @@ -96,12 +100,13 @@ void main() { vec2 uv = vec2(i, (1. - i)); vColor = texture2D(gradientTexture, uv); } - - vColor.a *= opacity; } - #include - #include +#define USE_COLOR_ALPHA +#include +#include +#include +#include gl_PointSize = size; @@ -114,5 +119,8 @@ void main() { } } - #include +#include +#include +#include +#include } diff --git a/test/unit/pointsmaterial.js b/test/unit/pointsmaterial.js new file mode 100644 index 0000000000..a2c963cfa8 --- /dev/null +++ b/test/unit/pointsmaterial.js @@ -0,0 +1,97 @@ +import assert from 'assert'; +import { + Color, + Matrix3, + PointsMaterial as TPointsMaterial, + Texture, +} from 'three'; +import PointsMaterial, { PNTS_SIZE_MODE } from 'Renderer/PointsMaterial'; + +const uvTransform = new Matrix3().setUvTransform( + 4, 4, // offset + 2, 2, // repeat + Math.PI, 0, 0, // center +); + +function assertPointsMaterialEqual(m1, m2) { + assert.deepEqual(m1.color, m2.color); + assert.equal(m1.fog, m2.fog); + assert.equal(m1.map, m2.map); + assert.equal(m1.size, m2.size); + assert.equal(m1.sizeAttenuation, m2.sizeAttenuation); +} + +describe('PointsMaterial', function () { + describe('.constructor()', function () { + it('should default like a THREE.PointsMaterial', function () { + assertPointsMaterialEqual( + new TPointsMaterial(), + new PointsMaterial(), + ); + }); + }); + + describe('#copy()', function () { + it('should copy a THREE.PointsMaterial', function () { + const material = new TPointsMaterial(); + + // THREE.Material properties + material.vertexColors = true; + material.transparent = true; + material.depthWrite = false; + + // THREE.PointsMaterial properties + material.color = new Color(0, 0, 1); + material.fog = false; + material.map = new Texture(); + material.size = 10; + material.sizeAttenuation = false; + + const copiedMaterial = new PointsMaterial().copy(material); + assertPointsMaterialEqual(copiedMaterial, material); + assert.equal(copiedMaterial.vertexColors, material.vertexColors); + assert.equal(copiedMaterial.transparent, material.transparent); + assert.equal(copiedMaterial.depthWrite, material.depthWrite); + }); + }); + + describe('#map', function () { + it('should update uvTransform from texture matrix', function () { + const material = new PointsMaterial(); + const texture = new Texture(); + + texture.matrix = uvTransform; + material.map = texture; + + const uniforms = material.uniforms; + assert.equal(uniforms.map.value, texture); + assert.deepEqual(uniforms.uvTransform.value, texture.matrix); + }); + }); + + describe('#alphaMap', function () { + it('should update alphaMapTransform from texture matrix', function () { + const material = new PointsMaterial(); + const texture = new Texture(); + + texture.matrix = uvTransform; + material.alphaMap = texture; + + const uniforms = material.uniforms; + assert.equal(uniforms.alphaMap.value, texture); + assert.deepEqual(uniforms.alphaMapTransform.value, texture.matrix); + }); + }); + + describe('#sizeAttenuation', function () { + it('should sync with sizeMode', function () { + const material = new PointsMaterial(); + + material.sizeAttenuation = false; + assert.equal(material.sizeMode, PNTS_SIZE_MODE.VALUE); + + material.sizeMode = PNTS_SIZE_MODE.ATTENUATED; + assert.equal(material.sizeAttenuation, true); + }); + }); +});