diff --git a/demo/index.ts b/demo/index.ts index 11c9d7c..d9c7d09 100644 --- a/demo/index.ts +++ b/demo/index.ts @@ -121,7 +121,7 @@ const lightTransparentColorsByLabel = hues.map( const heavyTransparentColorsByLabel = hues.map( hue => `hsla(${hue}, 100%, 50%, 0.75)` ); -const opaqueColorsByLabel = hues.map(hue => `hsla(${hue}, 100%, 50%, 1)`); +const opaqueColorsByLabel = hues.map(hue => `hsla(${hue}, 100%, 60%, 1)`); document .querySelectorAll('input[name="color"]') diff --git a/package.json b/package.json index 1e7a00b..9b0aa69 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "scatter-gl", - "version": "0.0.13", + "version": "0.0.14", "description": "webgl accelerated 3D/2D scatterplot renderer", "author": { "name": "Andy Coenen", @@ -34,10 +34,11 @@ "typescript": "^3.7", "webpack": "^4.29.5", "webpack-cli": "^3.2.3", - "webpack-dev-server": "^3.4.1" + "webpack-dev-server": "^3.4.1", + "@types/three": "0.161" }, "dependencies": { - "three": "0.125" + "three": "0.161" }, "prettier": { "bracketSpacing": false, diff --git a/src/scatter_gl.ts b/src/scatter_gl.ts index 4d1589c..e7aa177 100644 --- a/src/scatter_gl.ts +++ b/src/scatter_gl.ts @@ -474,10 +474,12 @@ export class ScatterGL { let unselectedColor = colorUnselected; let noSelectionColor = colorNoSelection; + let hoverColor = colorHover; if (this.renderMode === RenderMode.TEXT) { unselectedColor = this.styles.label3D.colorUnselected; noSelectionColor = this.styles.label3D.colorNoSelection; + hoverColor = this.styles.label3D.colorHover; } if (this.renderMode === RenderMode.SPRITE) { @@ -530,7 +532,7 @@ export class ScatterGL { // Last, color the hover point. if (hoverPointIndex != null) { - const c = parseColor(colorHover); + const c = parseColor(hoverColor); let dst = hoverPointIndex * RGBA_NUM_ELEMENTS; colors[dst++] = c.r; colors[dst++] = c.g; diff --git a/src/scatter_plot.ts b/src/scatter_plot.ts index 192701f..d0c0c23 100644 --- a/src/scatter_plot.ts +++ b/src/scatter_plot.ts @@ -225,7 +225,6 @@ export class ScatterPlot { occ.zoomSpeed = this.orbitControlParams.zoomSpeed; occ.enableRotate = cameraIs3D; occ.autoRotate = false; - occ.enableKeys = false; occ.rotateSpeed = this.orbitControlParams.mouseRotateSpeed; if (cameraIs3D) { occ.mouseButtons.LEFT = THREE.MOUSE.LEFT; // Orbit @@ -550,7 +549,6 @@ export class ScatterPlot { if (this.worldSpacePointPositions == null) { return []; } - const pointCount = this.worldSpacePointPositions.length / 3; const dpr = window.devicePixelRatio || 1; const x = Math.floor(boundingBox.x * dpr); @@ -576,11 +574,13 @@ export class ScatterPlot { let pointIndicesSelection = new Uint8Array( this.worldSpacePointPositions.length ); + for (let i = 0; i < width * height; i++) { - const id = - (pixelBuffer[i * 4] << 16) | - (pixelBuffer[i * 4 + 1] << 8) | - pixelBuffer[i * 4 + 2]; + const id = util.decodeIdFromRgb( + pixelBuffer[i * 4], + pixelBuffer[i * 4 + 1], + pixelBuffer[i * 4 + 2] + ); if (id !== 0xffffff && id < pointCount) { pointIndicesSelection[id] = 1; } @@ -881,6 +881,7 @@ export class ScatterPlot { renderCanvasSize.width * pixelRatio, renderCanvasSize.height * pixelRatio ); + this.pickingTexture.texture.minFilter = THREE.LinearFilter; } diff --git a/src/scatter_plot_visualizer_3d_labels.ts b/src/scatter_plot_visualizer_3d_labels.ts index f034356..653adfa 100644 --- a/src/scatter_plot_visualizer_3d_labels.ts +++ b/src/scatter_plot_visualizer_3d_labels.ts @@ -108,7 +108,7 @@ export class ScatterPlotVisualizer3DLabels implements ScatterPlotVisualizer { private pickingColors = new Float32Array(0); private renderColors = new Float32Array(0); private material!: THREE.ShaderMaterial; - private uniforms: { [uniform: string]: THREE.IUniform } = {}; + private uniforms: {[uniform: string]: THREE.IUniform} = {}; private labelsMesh!: THREE.Mesh; private positions!: THREE.BufferAttribute; private totalVertexCount = 0; @@ -172,11 +172,12 @@ export class ScatterPlotVisualizer3DLabels implements ScatterPlotVisualizer { this.totalVertexCount * RGB_NUM_ELEMENTS ); for (let i = 0; i < pointCount; i++) { - const pickingColor = new THREE.Color(i); this.labelVertexMap[i].forEach(j => { - this.pickingColors[RGB_NUM_ELEMENTS * j] = pickingColor.r; - this.pickingColors[RGB_NUM_ELEMENTS * j + 1] = pickingColor.g; - this.pickingColors[RGB_NUM_ELEMENTS * j + 2] = pickingColor.b; + const encodedId = util.encodeIdToRgb(i); + + this.pickingColors[RGB_NUM_ELEMENTS * j] = encodedId.r; + this.pickingColors[RGB_NUM_ELEMENTS * j + 1] = encodedId.g; + this.pickingColors[RGB_NUM_ELEMENTS * j + 2] = encodedId.b; this.renderColors[RGB_NUM_ELEMENTS * j] = 1.0; this.renderColors[RGB_NUM_ELEMENTS * j + 1] = 1.0; this.renderColors[RGB_NUM_ELEMENTS * j + 2] = 1.0; @@ -315,6 +316,7 @@ export class ScatterPlotVisualizer3DLabels implements ScatterPlotVisualizer { } src += RGBA_NUM_ELEMENTS; } + colors.needsUpdate = true; } diff --git a/src/scatter_plot_visualizer_polylines.ts b/src/scatter_plot_visualizer_polylines.ts index 22c7a64..1e17f92 100644 --- a/src/scatter_plot_visualizer_polylines.ts +++ b/src/scatter_plot_visualizer_polylines.ts @@ -67,8 +67,8 @@ export class ScatterPlotVisualizerPolylines implements ScatterPlotVisualizer { for (let i = 0; i < this.sequences.length; i++) { const geometry = new THREE.BufferGeometry(); - geometry.addAttribute('position', this.polylinePositionBuffer[i]); - geometry.addAttribute('color', this.polylineColorBuffer[i]); + geometry.setAttribute('position', this.polylinePositionBuffer[i]); + geometry.setAttribute('color', this.polylineColorBuffer[i]); const material = new THREE.LineBasicMaterial({ linewidth: 1, // unused default, overwritten by width array. @@ -148,7 +148,7 @@ export class ScatterPlotVisualizerPolylines implements ScatterPlotVisualizer { const material = this.polylines[i].material as THREE.LineBasicMaterial; material.opacity = renderContext.polylineOpacities[i]; material.linewidth = renderContext.polylineWidths[i]; - this.polylineColorBuffer[i].array = renderContext.polylineColors[i]; + this.polylineColorBuffer[i].array.set(renderContext.polylineColors[i]); this.polylineColorBuffer[i].needsUpdate = true; } } diff --git a/src/scatter_plot_visualizer_sprites.ts b/src/scatter_plot_visualizer_sprites.ts index 1ec4ec2..09592d7 100644 --- a/src/scatter_plot_visualizer_sprites.ts +++ b/src/scatter_plot_visualizer_sprites.ts @@ -299,13 +299,13 @@ export class ScatterPlotVisualizerSprites implements ScatterPlotVisualizer { // Fill pickingColors with each point's unique id as its color. this.pickingColors = new Float32Array(n * RGBA_NUM_ELEMENTS); { - let dst = 0; for (let i = 0; i < n; i++) { - const c = new THREE.Color(i); - this.pickingColors[dst++] = c.r; - this.pickingColors[dst++] = c.g; - this.pickingColors[dst++] = c.b; - this.pickingColors[dst++] = 1; + const encodedId = util.encodeIdToRgb(i); + + this.pickingColors[i * 4] = encodedId.r; + this.pickingColors[i * 4 + 1] = encodedId.g; + this.pickingColors[i * 4 + 2] = encodedId.b; + this.pickingColors[i * 4 + 3] = 1; // Alpha } } @@ -437,8 +437,12 @@ export class ScatterPlotVisualizerSprites implements ScatterPlotVisualizer { .geometry as THREE.BufferGeometry).getAttribute( 'position' ) as THREE.BufferAttribute; - positions.array = newPositions; - positions.count = newPositions.length / XYZ_NUM_ELEMENTS; + + this.points.geometry.setAttribute( + 'position', + new THREE.BufferAttribute(newPositions, XYZ_NUM_ELEMENTS) + ); + positions.needsUpdate = true; } @@ -456,17 +460,19 @@ export class ScatterPlotVisualizerSprites implements ScatterPlotVisualizer { let colors = (this.points.geometry as THREE.BufferGeometry).getAttribute( 'color' ) as THREE.BufferAttribute; + this.points.geometry.setAttribute( + 'color', + new THREE.BufferAttribute(this.pickingColors, RGBA_NUM_ELEMENTS) + ); colors.array = this.pickingColors; - colors.count = this.pickingColors.length / RGBA_NUM_ELEMENTS; colors.needsUpdate = true; let scaleFactors = (this.points .geometry as THREE.BufferGeometry).getAttribute( 'scaleFactor' ) as THREE.BufferAttribute; + scaleFactors.array = rc.pointScaleFactors; - scaleFactors.count = rc.pointScaleFactors.length; - scaleFactors.count = rc.pointScaleFactors.length / INDEX_NUM_ELEMENTS; scaleFactors.needsUpdate = true; } @@ -497,14 +503,13 @@ export class ScatterPlotVisualizerSprites implements ScatterPlotVisualizer { ) as THREE.BufferAttribute; this.renderColors = rc.pointColors; colors.array = this.renderColors; - colors.count = this.renderColors.length / RGBA_NUM_ELEMENTS; colors.needsUpdate = true; + let scaleFactors = (this.points .geometry as THREE.BufferGeometry).getAttribute( 'scaleFactor' ) as THREE.BufferAttribute; scaleFactors.array = rc.pointScaleFactors; - scaleFactors.count = rc.pointScaleFactors.length / INDEX_NUM_ELEMENTS; scaleFactors.needsUpdate = true; } diff --git a/src/styles.ts b/src/styles.ts index d4505f2..8e46c61 100644 --- a/src/styles.ts +++ b/src/styles.ts @@ -36,6 +36,7 @@ export interface Label3DStyles { backgroundColor: Color; colorUnselected: Color; colorNoSelection: Color; + colorHover: Color; } export interface PointStyles { @@ -146,6 +147,7 @@ const makeDefaultStyles = () => { backgroundColor: '#ffffff', colorUnselected: '#ffffff', colorNoSelection: '#ffffff', + colorHover: 'yellow', }, point: { @@ -163,7 +165,7 @@ const makeDefaultStyles = () => { endHue: 360, saturation: 1, lightness: 0.3, - defaultOpacity: 0.2, + defaultOpacity: 0.8, defaultLineWidth: 2, selectedOpacity: 0.9, selectedLineWidth: 3, diff --git a/src/util.ts b/src/util.ts index 92b406e..a5e969a 100644 --- a/src/util.ts +++ b/src/util.ts @@ -200,3 +200,17 @@ export function getDefaultPointInPolylineColor( const hsl = `hsl(${hue}, ${toPercent(saturation)}, ${toPercent(lightness)})`; return new THREE.Color(hsl); } + +/** Given a numeric id, encodes its value into an rgb. Can be used to store id values in "color" */ +export function encodeIdToRgb(i: number): {r: number; g: number; b: number} { + const r = (i >> 16) & 0xff; + const g = (i >> 8) & 0xff; + const b = i & 0xff; + + return {r: r / 255, g: g / 255, b: b / 255}; +} + +/** Given an rgb color, decodes the encoded numeric id value */ +export function decodeIdFromRgb(r: number, g: number, b: number): number { + return (r << 16) | (g << 8) | b; +} diff --git a/tsconfig.json b/tsconfig.json index 6a800f7..3cb8d6a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,8 @@ "allowUnreachableCode": true, "downlevelIteration": true, "lib": ["dom", "es2015", "es2016", "es2017"], - "strictPropertyInitialization": true + "strictPropertyInitialization": true, + "types": [] }, "compileOnSave": false, "include": ["src/"] diff --git a/webpack/demo.config.ts b/webpack/demo.config.ts index 30247aa..4b5dee3 100644 --- a/webpack/demo.config.ts +++ b/webpack/demo.config.ts @@ -27,7 +27,13 @@ module.exports = { { test: /\.ts$/, exclude: /node_modules/, - use: 'ts-loader', + use: [{ + loader: 'ts-loader', + options: { + configFile: "../tsconfig.json", + transpileOnly: true + } + }] }, ], }, diff --git a/yarn.lock b/yarn.lock index c3735fe..4e9f511 100644 --- a/yarn.lock +++ b/yarn.lock @@ -78,6 +78,26 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.1.tgz#238eb34a66431b71d2aaddeaa7db166f25971a0d" integrity sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA== +"@types/stats.js@*": + version "0.17.3" + resolved "https://registry.yarnpkg.com/@types/stats.js/-/stats.js-0.17.3.tgz#705446e12ce0fad618557dd88236f51148b7a935" + integrity sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ== + +"@types/three@0.161": + version "0.161.2" + resolved "https://registry.yarnpkg.com/@types/three/-/three-0.161.2.tgz#3c7e3f40869ad52970f517583cc200472e8918bf" + integrity sha512-DazpZ+cIfBzbW/p0zm6G8CS03HBMd748A3R1ZOXHpqaXZLv2I5zNgQUrRG//UfJ6zYFp2cUoCQaOLaz8ubH07w== + dependencies: + "@types/stats.js" "*" + "@types/webxr" "*" + fflate "~0.6.10" + meshoptimizer "~0.18.1" + +"@types/webxr@*": + version "0.5.16" + resolved "https://registry.yarnpkg.com/@types/webxr/-/webxr-0.5.16.tgz#28955aa2d1197d1ef0b9826ae0f7e68f7eca0601" + integrity sha512-0E0Cl84FECtzrB4qG19TNTqpunw0F1YF0QZZnFMF6pDw1kNKJtrlTKlVB34stGIsHbZsYQ7H0tNjPfZftkHHoA== + "@types/yargs-parser@*": version "15.0.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" @@ -2060,6 +2080,11 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fflate@~0.6.10: + version "0.6.10" + resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.6.10.tgz#5f40f9659205936a2d18abf88b2e7781662b6d43" + integrity sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg== + figgy-pudding@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" @@ -3901,6 +3926,11 @@ merge@^1.2.0: resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" integrity sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ== +meshoptimizer@~0.18.1: + version "0.18.1" + resolved "https://registry.yarnpkg.com/meshoptimizer/-/meshoptimizer-0.18.1.tgz#cdb90907f30a7b5b1190facd3b7ee6b7087797d8" + integrity sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw== + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -5767,10 +5797,10 @@ test-exclude@^4.2.1: read-pkg-up "^1.0.1" require-main-filename "^1.0.1" -three@0.125: - version "0.125.2" - resolved "https://registry.yarnpkg.com/three/-/three-0.125.2.tgz#dcba12749a2eb41522e15212b919cd3fbf729b12" - integrity sha512-7rIRO23jVKWcAPFdW/HREU2NZMGWPBZ4XwEMt0Ak0jwLUKVJhcKM55eCBWyGZq/KiQbeo1IeuAoo/9l2dzhTXA== +three@0.161: + version "0.161.0" + resolved "https://registry.yarnpkg.com/three/-/three-0.161.0.tgz#38aafaa82fe5467fde2e33933515d1b6beb17d91" + integrity sha512-LC28VFtjbOyEu5b93K0bNRLw1rQlMJ85lilKsYj6dgTu+7i17W+JCCEbvrpmNHF1F3NAUqDSWq50UD7w9H2xQw== throat@^4.0.0: version "4.1.0"